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>
Contributor
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
| # ===== Root Commands ===== | ||
|
|
||
| from . import command_check as _command_check # noqa: E402 | ||
| from . import command_destroy as _command_destroy |
Comment on lines
+1421
to
+1426
| output_name = self._compute_output_name(agent_name, name, config) | ||
| for destination in destinations: | ||
| if config["extension"] == "/SKILL.md": | ||
| paths.add(destination / output_name / "SKILL.md") | ||
| else: | ||
| paths.add(destination / f"{output_name}{config['extension']}") |
Comment on lines
+147
to
+148
| if Path(name).is_relative_to(preserved): | ||
| manifest.remove(name) |
Comment on lines
+346
to
+349
| if state is None: | ||
| legacy_agent = read_project_options(root).get("ai") | ||
| if isinstance(legacy_agent, str) and legacy_agent: | ||
| installed = [legacy_agent] |
Comment on lines
+383
to
+384
| if relative.startswith(".specify/"): | ||
| continue |
Comment on lines
+529
to
+531
| for name in sorted(leaves, key=lambda name: (name.startswith(".specify/"), name)): | ||
| files.delete(name) | ||
| removed.append(name) |
| + ", ".join(preserved) + "; manual event cleanup may be needed.", | ||
| ) | ||
| manifest.save() | ||
| elif binding is not None: |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


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.