Repository navigation
Conversation
Reuse integration teardown to remove generated commands, native event entries, and the last managed dispatcher before cleaning project artifacts. Add explicit constitution/specs preservation options, ownership-aware recovery, declared global-scope handling, and positive and negative lifecycle coverage. Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
Confirmed deletion-scope, preservation, and recovery defects prevent safe teardown.
7 open issues
Add Ruff E402 suppression to the late root-command import · New Validate both dotted and formatted command names before cleanup · New Exclude preserved constitution claims before deleting tracked symlinks · New Handle Claude uninstall when only the init options inventory exists · New Validate modified owned files during destroy planning · New Retain cleanup registries until package deletion completes · New Guard ownership rewrites when adapter manifests are missing · New
What changed in this PR
Adds specify destroy as the project-uninstall counterpart to specify init, reusing integration teardown before removing Spec Kit infrastructure.
Changes:
- Adds preview, confirmation, structured JSON, and artifact-preservation options.
- Extracts shared integration uninstall logic and adds contribution-path and native event-cleanup validation.
- Documents deletion boundaries and expands lifecycle, preservation, recovery, and external-adapter coverage.
Validation used isolated standard-library checks; project tests and lint were not run.
AI review by GitHub Copilot for the requesting reviewer (model unavailable; automated mode, default settings), covering analysis and suggested fixes.
| File | Description |
|---|---|
tests/specify_cli/test_operation_destroy.py |
Covers ownership, preservation, deletion scope, and failure recovery. |
tests/specify_cli/test_command_init.py |
Updates expected root-command registration order. |
tests/specify_cli/test_command_destroy.py |
Tests confirmation, preservation flags, and JSON contracts. |
tests/specify_cli/integrations/test_operation_uninstall.py |
Tests the shared uninstall operation. |
tests/specify_cli/integrations/test_installed_adapters.py |
Adds external-adapter teardown and preservation coverage. |
src/specify_cli/mcp_destroy.py |
Records destroy as unavailable through MCP. |
src/specify_cli/integrations/command_uninstall.py |
Delegates CLI uninstall to the shared operation. |
src/specify_cli/integrations/_operation_uninstall.py |
Centralizes teardown, recovery, and package cleanup. |
src/specify_cli/integrations/_lifecycle.py |
Removes uninstall-specific orchestration moved to the shared operation. |
src/specify_cli/events/__init__.py |
Adds native event-cleanup verification. |
src/specify_cli/command_destroy.py |
Implements command options, preview, confirmation, and output. |
src/specify_cli/agents.py |
Enumerates recorded contribution output paths. |
src/specify_cli/_operation_destroy.py |
Plans, validates, and executes project teardown. |
src/specify_cli/__init__.py |
Registers the destroy command. |
docs/reference/overview.md |
Introduces project uninstall in the command overview. |
docs/reference/core.md |
Documents options, deletion boundaries, and recovery behavior. |
Review effort: Max
Preflight all contribution cleanup names and dispatcher ownership/content, preserve symlinked constitution claims lexically, reject incomplete legacy inventories before mutation, retain package registries through purge failures, and allow ordinary external uninstall without a manifest. Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Addressed the review in b7b7297. Cleanup now preflights dotted and formatted command paths, preserves symlinked constitution claims by lexical manifest key, rejects legacy-only inventories during preview with migration guidance, checks dispatcher ownership/content before any uninstall, retains extension/preset registries until package deletion completes, and permits ordinary external uninstall when its manifest is missing. Added regression coverage, including purge retry and regenerated LF/CRLF dispatchers. The exact CI command On behalf of @mnriem: GitHub Copilot, GPT-6.1 Sol ( |
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved preservation and teardown-recovery defects can cause data loss or leave cleanup incomplete.
9 open issues
Preserve constitution using filesystem-aware identity · New Force cleanup of validated whole-owned extension paths · New Exclude preserved constitution claims before deleting tracked symlinks Resolve ownership across stacked preset skill claims · New Validate native settings before destructive teardown · New Validate dispatcher cleanup against the last claimant · New Synchronize preset hashes before destroy planning · New Delete specs before removing infrastructure · New Render structured destroy error recovery details · New
6 resolved since last review
Validate both dotted and formatted command names before cleanup Add Ruff E402 suppression to the late root-command import Guard ownership rewrites when adapter manifests are missing Retain cleanup registries until package deletion completes Validate modified owned files during destroy planning Handle Claude uninstall when only the init options inventory exists
Review effort: Max
| for name in candidate_leaves: | ||
| path = root / name | ||
| digests = expected.get(name, set()) | ||
| if digests and ( | ||
| path.is_symlink() or hashlib.sha256(files.read_bytes(name)).hexdigest() not in digests | ||
| ): | ||
| requiring_force.add(name) |
Handle case-insensitive artifact preservation without following links, force validated generic contribution cleanup before dropping integration state, resolve stacked preset skill provenance, preflight native settings and the final dispatcher claimant, synchronize hashes after managed preset initialization, finish specs-directory deletion before infrastructure, and expose human recovery details. Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Addressed the preservation and cleanup findings in d5bf78d. Constitution retention now uses filesystem-aware entry identity without following leaf links, including alternate case spellings and tracked symlinks. Validated generic contribution cleanup honors force and verifies completion before dropping integration state. Stacked preset skill claims share the lifecycle's validated override provenance; preset initialization updates hashes only for files changed by managed rendering. Planning parses built-in native settings and checks the dispatcher against its last claimant before any teardown. Specs files and directories are removed before infrastructure, and human-readable errors include remaining paths and completed work. Regression cases reproduced all eight new behavioral findings before fixes; additional tests cover retry and preservation boundaries. The full local suite passed with 13,279 passed and 128 skipped; CI-pinned Ruff and the real workflow smoke (20 runs, ten resumes) also pass. New-head CI is green: Ruff and all six Ubuntu/macOS/Windows Python 3.13/3.14 test jobs passed, as did Markdown lint, ShellCheck, CodeQL, and dependency checks. Review threads are left open for confirmation. On behalf of @mnriem: GitHub Copilot, GPT-6.1 Sol ( |
There was a problem hiding this comment.
🔵 Needs a closer look
Destructive cleanup spans project and home-scoped files, restructures the uninstall lifecycle, and retains unresolved ownership and recovery failures.
12 open issues
Avoid reading arbitrary files during path validation · New Validate preset IDs before resolving provenance · New Use Hermes home skill paths for inventory and cleanup · New Distinguish legacy preset skills from explicit ownership claims · New Report completed integration deletions on failure · New Validate SPECIFY_INIT_DIR through the shared override resolver · New Refresh hashes after preset add changes · New Distinguish dangling symlinks from absent files · New Preserve registrations for skipped contribution cleanup · New Restore installation state after late cleanup failure · New Convert teardown SystemExit into reported uninstall errors · New Synchronize preset hashes before destroy planning
8 resolved since last review
Force cleanup of validated whole-owned extension paths Preserve constitution using filesystem-aware identity Exclude preserved constitution claims before deleting tracked symlinks Render structured destroy error recovery details Delete specs before removing infrastructure Validate dispatcher cleanup against the last claimant Validate native settings before destructive teardown Resolve ownership across stacked preset skill claims
Review effort: Max
| raise FileHelperError(f"Refusing symlink during destroy: {relative}") | ||
| leaves.add(relative) | ||
| elif stat.S_ISREG(mode): | ||
| files.read_bytes(relative) |
| if kind == "presets": | ||
| from .presets import PresetManager | ||
|
|
||
| override_sources = PresetManager(root).skill_ownership_sources(identifier) |
| candidates = registrar.registered_artifact_paths( | ||
| agent, root, commands=[], skills=[skill], registrar_config=config, | ||
| ) |
| if source not in sources: | ||
| raise ValueError(f"Cannot establish skill ownership at {candidate}") |
| warnings.extend(result.warnings) | ||
| uninstalled.append(key) |
| changed = False | ||
| for name, previous in before_preset.items(): | ||
| path = project_path / name | ||
| if ( | ||
| path.is_file() and not path.is_symlink() | ||
| and hashlib.sha256(path.read_bytes()).hexdigest() != previous | ||
| ): | ||
| manifest.record_existing(name) | ||
| changed = True | ||
| if changed: | ||
| manifest.save() |
| except FileNotFoundError: | ||
| continue |
| warnings.extend(unregister_integration_artifacts( | ||
| root, key, require_complete=request.require_complete, force=request.force, | ||
| )) | ||
| if request.require_complete and skipped: |
| _remove_integration_json(root) | ||
| if default == key: | ||
| _clear_init_options_for_integration(root, key) |
| with _retain_recovery_manifest(request, key): | ||
| result = _uninstall(request, warnings) |


Description
Add
specify destroyto uninstall Spec Kit from a project without manuallyfinding generated integration files.
operation also used by
specify integration uninstall. Preserve nativeuser settings while removing managed event entries and releasing the last
dispatcher consumer.
.specify/and literal project-localspecs/by default.--preserve-constitutionand--preserve-specsretain their respective SDDartifacts;
--preserve-allcombines both. Force never overrides preservation.external-adapter ownership/recovery rules, and retain original manifest
evidence when strict teardown fails.
project files. Never follow a feature-directory override to enlarge deletion.
This deliberately changes no core prompt/command templates and adds no runtime
dependencies. The constitution/specification deletion scope is explicitly
described in CLI help and documentation.
Testing
uv run specify --helpuv sync && uv run pytestThe exact bare test command in the template was not used. Per the documented
worktree-environment guidance, dependencies and tests used this checkout's
own virtualenv instead:
uv sync --extra test— passed.LC_ALL=en_US.UTF-8 .venv/bin/python -m pytest -n 4 -q— passed:13,246 passed, 128 skipped, 62 warnings on macOS/Python 3.13.17.
ruff check --output-format concise src/specify_cli/_operation_destroy.py src/specify_cli/command_destroy.py src/specify_cli/mcp_destroy.py src/specify_cli/integrations/_operation_uninstall.py src/specify_cli/integrations/command_uninstall.py tests/specify_cli/test_operation_destroy.py tests/specify_cli/test_command_destroy.py tests/specify_cli/integrations/test_operation_uninstall.py— passed. Existing tracked files have no new Ruff diagnostic fingerprints
compared with the committed baseline; unrelated repository-wide lint
findings remain.
git diff --check— passed.Sample-project validation used temporary projects and isolated homes, not the
development checkout:
command/skill layouts, and Hermes; five preservation choices; force on/off.
repeat, retained-byte verification, and external feature-directory isolation.
specify workflow add <smoke-package> --dev,specify workflow run destroy-smoke --input python=<interpreter> --json,and
specify workflow status <run-id> --json— passed.Ten were resumed with
specify workflow resume <run-id> --input approval=approve --jsonandcompleted; ten remained paused before destruction. All preservation choices
and force modes were checked after workflow-generated constitution/specs,
source files, and persisted run state existed.
The workflow smoke used real deterministic shell steps and an approval gate;
it did not mock execution or invoke the bundled AI-driven full SDD cycle.
The workflow generated artifacts, paused, resumed without replaying the
completed step, and verified completion before cleanup. Workflow state and
installed tooling were removed; selected SDD artifacts and unrelated source
were retained byte-for-byte.
Before/after evidence includes the initially missing destroy command, tracked
constitution loss, same-named skill ownership, missing recovery metadata
after teardown failures, global contribution discovery, and preservation flag
parsing/defaults. Regression tests cover the corrected positive and negative
paths. Collection increased from 13,141 at feature start to 13,374; no existing
tests were removed. Cross-platform CI has not been run locally.
AI Disclosure
AI disclosure: Authored on behalf of @mnriem with GitHub Copilot, powered by
GPT-6.1 Sol (
gpt-6.1-sol), in autonomous/agent mode with runtime-defaultreasoning settings and user-directed requirements and iterative feedback.
AI assistance covered feature specification/planning, implementation,
documentation, tests, review/reproduction, executable smoke checks, and this
PR description. The reported automated checks were run by the agent; no claim
of independent human line-by-line review or manual testing is made.