Skip to content

feat(cli): add project uninstall with specify destroy - #5100

Open
mnriem wants to merge 1 commit into
github:mainfrom
mnriem:mnriem-specify-destroy
Open

mnriem wants to merge 1 commit into
github:mainfrom
mnriem:mnriem-specify-destroy

Conversation

@mnriem

@mnriem mnriem commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator

Description

Add specify destroy to uninstall Spec Kit from a project without manually
finding generated integration files.

  • Uninstall non-default integrations before the default through a shared
    operation also used by specify integration uninstall. Preserve native
    user settings while removing managed event entries and releasing the last
    dispatcher consumer.
  • Remove .specify/ and literal project-local specs/ by default.
    --preserve-constitution and --preserve-specs retain their respective SDD
    artifacts; --preserve-all combines both. Force never overrides preservation.
  • Preview before confirmation, keep JSON failures structured, respect existing
    external-adapter ownership/recovery rules, and retain original manifest
    evidence when strict teardown fails.
  • Handle declared home-scoped integration contributions separately from
    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

  • Tested locally with uv run specify --help
  • Ran existing tests with uv sync && uv run pytest
  • Tested with a sample project (if applicable)

The 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:

  • 60 initialized-project combinations across Copilot, Claude, Gemini, generic
    command/skill layouts, and Hermes; five preservation choices; force on/off.
  • 30 additional actual CLI subprocess scenarios for init, preview, destroy,
    repeat, retained-byte verification, and external feature-directory isolation.
  • 20 actual workflow invocations using
    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 --json and
    completed; 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

  • I did not use AI assistance for this contribution
  • I did use AI assistance (fill in the disclosure below)

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-default
reasoning 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.

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>
Copilot AI max review requested due to automatic review settings October 23, 2026 22:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Confirmed deletion-scope, preservation, and recovery defects prevent safe teardown.

7 open issues
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 thread src/specify_cli/agents.py
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

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants