Add installed-file tracking with SHA-256 hashes for safe agent teardown

Setup records installed files and their SHA-256 hashes in
.specify/agent-manifest-<agent_id>.json. Teardown uses the manifest
to remove only individual files (never directories). If any tracked
file was modified since installation, teardown requires --force.

- Add record_installed_files(), check_modified_files(), remove_tracked_files()
  and AgentFileModifiedError to agent_pack.py
- Update all 25 bootstrap modules to use file-tracked setup/teardown
- Add --force flag to 'specify agent switch'
- Add 11 new tests for file tracking (record, check, remove, force,
  directory preservation, deleted-file handling, manifest structure)

Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
Agent-Logs-Url: https://github.com/github/spec-kit/sessions/779eabf6-21d5-428b-9f01-dd363df4c84a
This commit is contained in:
copilot-swe-agent[bot]
2026-03-20 21:15:48 +00:00
committed by GitHub
parent ec5471af61
commit b5a5e3fc35
28 changed files with 639 additions and 207 deletions

View File

@@ -2533,6 +2533,7 @@ def agent_export(
@agent_app.command("switch")
def agent_switch(
agent_id: str = typer.Argument(..., help="Agent pack ID to switch to"),
force: bool = typer.Option(False, "--force", help="Remove agent files even if they were modified since installation"),
):
"""Switch the active AI agent in the current project.
@@ -2544,6 +2545,7 @@ def agent_switch(
load_bootstrap,
PackResolutionError,
AgentPackError,
AgentFileModifiedError,
)
show_banner()
@@ -2581,8 +2583,12 @@ def agent_switch(
current_resolved = resolve_agent_pack(current_agent, project_path=project_path)
current_bootstrap = load_bootstrap(current_resolved.path, current_resolved.manifest)
console.print(f" [dim]Tearing down {current_agent}...[/dim]")
current_bootstrap.teardown(project_path)
current_bootstrap.teardown(project_path, force=force)
console.print(f" [green]✓[/green] {current_agent} removed")
except AgentFileModifiedError as exc:
console.print(f"[red]Error:[/red] {exc}")
console.print("[yellow]Hint:[/yellow] Use --force to remove modified files.")
raise typer.Exit(1)
except AgentPackError:
# If pack-based teardown fails, try legacy cleanup via AGENT_CONFIG
agent_config = AGENT_CONFIG.get(current_agent, {})