mirror of
https://github.com/github/spec-kit.git
synced 2026-03-22 05:13:08 +00:00
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:
committed by
GitHub
parent
ec5471af61
commit
b5a5e3fc35
@@ -3,7 +3,7 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from specify_cli.agent_pack import AgentBootstrap
|
||||
from specify_cli.agent_pack import AgentBootstrap, record_installed_files, remove_tracked_files
|
||||
|
||||
|
||||
class Agy(AgentBootstrap):
|
||||
@@ -16,10 +16,15 @@ class Agy(AgentBootstrap):
|
||||
"""Install Antigravity agent files into the project."""
|
||||
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||
# Record installed files for tracked teardown
|
||||
installed = [p for p in commands_dir.rglob("*") if p.is_file()]
|
||||
record_installed_files(project_path, self.manifest.id, installed)
|
||||
|
||||
def teardown(self, project_path: Path) -> None:
|
||||
"""Remove Antigravity agent files from the project."""
|
||||
import shutil
|
||||
agent_dir = project_path / self.AGENT_DIR
|
||||
if agent_dir.is_dir():
|
||||
shutil.rmtree(agent_dir)
|
||||
def teardown(self, project_path: Path, *, force: bool = False) -> None:
|
||||
"""Remove Antigravity agent files from the project.
|
||||
|
||||
Only removes individual tracked files — directories are never
|
||||
deleted. Raises ``AgentFileModifiedError`` if any tracked file
|
||||
was modified and *force* is ``False``.
|
||||
"""
|
||||
remove_tracked_files(project_path, self.manifest.id, force=force)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from specify_cli.agent_pack import AgentBootstrap
|
||||
from specify_cli.agent_pack import AgentBootstrap, record_installed_files, remove_tracked_files
|
||||
|
||||
|
||||
class Amp(AgentBootstrap):
|
||||
@@ -16,18 +16,15 @@ class Amp(AgentBootstrap):
|
||||
"""Install Amp agent files into the project."""
|
||||
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||
# Record installed files for tracked teardown
|
||||
installed = [p for p in commands_dir.rglob("*") if p.is_file()]
|
||||
record_installed_files(project_path, self.manifest.id, installed)
|
||||
|
||||
def teardown(self, project_path: Path) -> None:
|
||||
def teardown(self, project_path: Path, *, force: bool = False) -> None:
|
||||
"""Remove Amp agent files from the project.
|
||||
|
||||
Only removes the commands/ subdirectory — preserves other .agents/
|
||||
content (e.g. Codex skills/) which shares the same parent directory.
|
||||
Only removes individual tracked files — directories are never
|
||||
deleted. Raises ``AgentFileModifiedError`` if any tracked file
|
||||
was modified and *force* is ``False``.
|
||||
"""
|
||||
import shutil
|
||||
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||
if commands_dir.is_dir():
|
||||
shutil.rmtree(commands_dir)
|
||||
# Remove .agents/ only if now empty
|
||||
agents_dir = project_path / self.AGENT_DIR
|
||||
if agents_dir.is_dir() and not any(agents_dir.iterdir()):
|
||||
agents_dir.rmdir()
|
||||
remove_tracked_files(project_path, self.manifest.id, force=force)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from specify_cli.agent_pack import AgentBootstrap
|
||||
from specify_cli.agent_pack import AgentBootstrap, record_installed_files, remove_tracked_files
|
||||
|
||||
|
||||
class Auggie(AgentBootstrap):
|
||||
@@ -16,10 +16,15 @@ class Auggie(AgentBootstrap):
|
||||
"""Install Auggie CLI agent files into the project."""
|
||||
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||
# Record installed files for tracked teardown
|
||||
installed = [p for p in commands_dir.rglob("*") if p.is_file()]
|
||||
record_installed_files(project_path, self.manifest.id, installed)
|
||||
|
||||
def teardown(self, project_path: Path) -> None:
|
||||
"""Remove Auggie CLI agent files from the project."""
|
||||
import shutil
|
||||
agent_dir = project_path / self.AGENT_DIR
|
||||
if agent_dir.is_dir():
|
||||
shutil.rmtree(agent_dir)
|
||||
def teardown(self, project_path: Path, *, force: bool = False) -> None:
|
||||
"""Remove Auggie CLI agent files from the project.
|
||||
|
||||
Only removes individual tracked files — directories are never
|
||||
deleted. Raises ``AgentFileModifiedError`` if any tracked file
|
||||
was modified and *force* is ``False``.
|
||||
"""
|
||||
remove_tracked_files(project_path, self.manifest.id, force=force)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from specify_cli.agent_pack import AgentBootstrap
|
||||
from specify_cli.agent_pack import AgentBootstrap, record_installed_files, remove_tracked_files
|
||||
|
||||
|
||||
class Bob(AgentBootstrap):
|
||||
@@ -16,10 +16,15 @@ class Bob(AgentBootstrap):
|
||||
"""Install IBM Bob agent files into the project."""
|
||||
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||
# Record installed files for tracked teardown
|
||||
installed = [p for p in commands_dir.rglob("*") if p.is_file()]
|
||||
record_installed_files(project_path, self.manifest.id, installed)
|
||||
|
||||
def teardown(self, project_path: Path) -> None:
|
||||
"""Remove IBM Bob agent files from the project."""
|
||||
import shutil
|
||||
agent_dir = project_path / self.AGENT_DIR
|
||||
if agent_dir.is_dir():
|
||||
shutil.rmtree(agent_dir)
|
||||
def teardown(self, project_path: Path, *, force: bool = False) -> None:
|
||||
"""Remove IBM Bob agent files from the project.
|
||||
|
||||
Only removes individual tracked files — directories are never
|
||||
deleted. Raises ``AgentFileModifiedError`` if any tracked file
|
||||
was modified and *force* is ``False``.
|
||||
"""
|
||||
remove_tracked_files(project_path, self.manifest.id, force=force)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from specify_cli.agent_pack import AgentBootstrap
|
||||
from specify_cli.agent_pack import AgentBootstrap, record_installed_files, remove_tracked_files
|
||||
|
||||
|
||||
class Claude(AgentBootstrap):
|
||||
@@ -16,10 +16,15 @@ class Claude(AgentBootstrap):
|
||||
"""Install Claude Code agent files into the project."""
|
||||
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||
# Record installed files for tracked teardown
|
||||
installed = [p for p in commands_dir.rglob("*") if p.is_file()]
|
||||
record_installed_files(project_path, self.manifest.id, installed)
|
||||
|
||||
def teardown(self, project_path: Path) -> None:
|
||||
"""Remove Claude Code agent files from the project."""
|
||||
import shutil
|
||||
agent_dir = project_path / self.AGENT_DIR
|
||||
if agent_dir.is_dir():
|
||||
shutil.rmtree(agent_dir)
|
||||
def teardown(self, project_path: Path, *, force: bool = False) -> None:
|
||||
"""Remove Claude Code agent files from the project.
|
||||
|
||||
Only removes individual tracked files — directories are never
|
||||
deleted. Raises ``AgentFileModifiedError`` if any tracked file
|
||||
was modified and *force* is ``False``.
|
||||
"""
|
||||
remove_tracked_files(project_path, self.manifest.id, force=force)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from specify_cli.agent_pack import AgentBootstrap
|
||||
from specify_cli.agent_pack import AgentBootstrap, record_installed_files, remove_tracked_files
|
||||
|
||||
|
||||
class Codebuddy(AgentBootstrap):
|
||||
@@ -16,10 +16,15 @@ class Codebuddy(AgentBootstrap):
|
||||
"""Install CodeBuddy agent files into the project."""
|
||||
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||
# Record installed files for tracked teardown
|
||||
installed = [p for p in commands_dir.rglob("*") if p.is_file()]
|
||||
record_installed_files(project_path, self.manifest.id, installed)
|
||||
|
||||
def teardown(self, project_path: Path) -> None:
|
||||
"""Remove CodeBuddy agent files from the project."""
|
||||
import shutil
|
||||
agent_dir = project_path / self.AGENT_DIR
|
||||
if agent_dir.is_dir():
|
||||
shutil.rmtree(agent_dir)
|
||||
def teardown(self, project_path: Path, *, force: bool = False) -> None:
|
||||
"""Remove CodeBuddy agent files from the project.
|
||||
|
||||
Only removes individual tracked files — directories are never
|
||||
deleted. Raises ``AgentFileModifiedError`` if any tracked file
|
||||
was modified and *force* is ``False``.
|
||||
"""
|
||||
remove_tracked_files(project_path, self.manifest.id, force=force)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from specify_cli.agent_pack import AgentBootstrap
|
||||
from specify_cli.agent_pack import AgentBootstrap, record_installed_files, remove_tracked_files
|
||||
|
||||
|
||||
class Codex(AgentBootstrap):
|
||||
@@ -16,18 +16,15 @@ class Codex(AgentBootstrap):
|
||||
"""Install Codex CLI agent files into the project."""
|
||||
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||
# Record installed files for tracked teardown
|
||||
installed = [p for p in commands_dir.rglob("*") if p.is_file()]
|
||||
record_installed_files(project_path, self.manifest.id, installed)
|
||||
|
||||
def teardown(self, project_path: Path) -> None:
|
||||
def teardown(self, project_path: Path, *, force: bool = False) -> None:
|
||||
"""Remove Codex CLI agent files from the project.
|
||||
|
||||
Only removes the skills/ subdirectory — preserves other .agents/
|
||||
content (e.g. Amp commands/) which shares the same parent directory.
|
||||
Only removes individual tracked files — directories are never
|
||||
deleted. Raises ``AgentFileModifiedError`` if any tracked file
|
||||
was modified and *force* is ``False``.
|
||||
"""
|
||||
import shutil
|
||||
skills_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||
if skills_dir.is_dir():
|
||||
shutil.rmtree(skills_dir)
|
||||
# Remove .agents/ only if now empty
|
||||
agents_dir = project_path / self.AGENT_DIR
|
||||
if agents_dir.is_dir() and not any(agents_dir.iterdir()):
|
||||
agents_dir.rmdir()
|
||||
remove_tracked_files(project_path, self.manifest.id, force=force)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from specify_cli.agent_pack import AgentBootstrap
|
||||
from specify_cli.agent_pack import AgentBootstrap, record_installed_files, remove_tracked_files
|
||||
|
||||
|
||||
class Copilot(AgentBootstrap):
|
||||
@@ -16,18 +16,15 @@ class Copilot(AgentBootstrap):
|
||||
"""Install GitHub Copilot agent files into the project."""
|
||||
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||
# Record installed files for tracked teardown
|
||||
installed = [p for p in commands_dir.rglob("*") if p.is_file()]
|
||||
record_installed_files(project_path, self.manifest.id, installed)
|
||||
|
||||
def teardown(self, project_path: Path) -> None:
|
||||
def teardown(self, project_path: Path, *, force: bool = False) -> None:
|
||||
"""Remove GitHub Copilot agent files from the project.
|
||||
|
||||
Only removes the agents/ subdirectory — preserves other .github
|
||||
content (workflows, issue templates, etc.).
|
||||
Only removes individual tracked files — directories are never
|
||||
deleted. Raises ``AgentFileModifiedError`` if any tracked file
|
||||
was modified and *force* is ``False``.
|
||||
"""
|
||||
import shutil
|
||||
agents_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||
if agents_dir.is_dir():
|
||||
shutil.rmtree(agents_dir)
|
||||
# Also clean up companion .github/prompts/ if empty
|
||||
prompts_dir = project_path / self.AGENT_DIR / "prompts"
|
||||
if prompts_dir.is_dir() and not any(prompts_dir.iterdir()):
|
||||
prompts_dir.rmdir()
|
||||
remove_tracked_files(project_path, self.manifest.id, force=force)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from specify_cli.agent_pack import AgentBootstrap
|
||||
from specify_cli.agent_pack import AgentBootstrap, record_installed_files, remove_tracked_files
|
||||
|
||||
|
||||
class CursorAgent(AgentBootstrap):
|
||||
@@ -16,10 +16,15 @@ class CursorAgent(AgentBootstrap):
|
||||
"""Install Cursor agent files into the project."""
|
||||
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||
# Record installed files for tracked teardown
|
||||
installed = [p for p in commands_dir.rglob("*") if p.is_file()]
|
||||
record_installed_files(project_path, self.manifest.id, installed)
|
||||
|
||||
def teardown(self, project_path: Path) -> None:
|
||||
"""Remove Cursor agent files from the project."""
|
||||
import shutil
|
||||
agent_dir = project_path / self.AGENT_DIR
|
||||
if agent_dir.is_dir():
|
||||
shutil.rmtree(agent_dir)
|
||||
def teardown(self, project_path: Path, *, force: bool = False) -> None:
|
||||
"""Remove Cursor agent files from the project.
|
||||
|
||||
Only removes individual tracked files — directories are never
|
||||
deleted. Raises ``AgentFileModifiedError`` if any tracked file
|
||||
was modified and *force* is ``False``.
|
||||
"""
|
||||
remove_tracked_files(project_path, self.manifest.id, force=force)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from specify_cli.agent_pack import AgentBootstrap
|
||||
from specify_cli.agent_pack import AgentBootstrap, record_installed_files, remove_tracked_files
|
||||
|
||||
|
||||
class Gemini(AgentBootstrap):
|
||||
@@ -16,10 +16,15 @@ class Gemini(AgentBootstrap):
|
||||
"""Install Gemini CLI agent files into the project."""
|
||||
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||
# Record installed files for tracked teardown
|
||||
installed = [p for p in commands_dir.rglob("*") if p.is_file()]
|
||||
record_installed_files(project_path, self.manifest.id, installed)
|
||||
|
||||
def teardown(self, project_path: Path) -> None:
|
||||
"""Remove Gemini CLI agent files from the project."""
|
||||
import shutil
|
||||
agent_dir = project_path / self.AGENT_DIR
|
||||
if agent_dir.is_dir():
|
||||
shutil.rmtree(agent_dir)
|
||||
def teardown(self, project_path: Path, *, force: bool = False) -> None:
|
||||
"""Remove Gemini CLI agent files from the project.
|
||||
|
||||
Only removes individual tracked files — directories are never
|
||||
deleted. Raises ``AgentFileModifiedError`` if any tracked file
|
||||
was modified and *force* is ``False``.
|
||||
"""
|
||||
remove_tracked_files(project_path, self.manifest.id, force=force)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from specify_cli.agent_pack import AgentBootstrap
|
||||
from specify_cli.agent_pack import AgentBootstrap, record_installed_files, remove_tracked_files
|
||||
|
||||
|
||||
class Iflow(AgentBootstrap):
|
||||
@@ -16,10 +16,15 @@ class Iflow(AgentBootstrap):
|
||||
"""Install iFlow CLI agent files into the project."""
|
||||
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||
# Record installed files for tracked teardown
|
||||
installed = [p for p in commands_dir.rglob("*") if p.is_file()]
|
||||
record_installed_files(project_path, self.manifest.id, installed)
|
||||
|
||||
def teardown(self, project_path: Path) -> None:
|
||||
"""Remove iFlow CLI agent files from the project."""
|
||||
import shutil
|
||||
agent_dir = project_path / self.AGENT_DIR
|
||||
if agent_dir.is_dir():
|
||||
shutil.rmtree(agent_dir)
|
||||
def teardown(self, project_path: Path, *, force: bool = False) -> None:
|
||||
"""Remove iFlow CLI agent files from the project.
|
||||
|
||||
Only removes individual tracked files — directories are never
|
||||
deleted. Raises ``AgentFileModifiedError`` if any tracked file
|
||||
was modified and *force* is ``False``.
|
||||
"""
|
||||
remove_tracked_files(project_path, self.manifest.id, force=force)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from specify_cli.agent_pack import AgentBootstrap
|
||||
from specify_cli.agent_pack import AgentBootstrap, record_installed_files, remove_tracked_files
|
||||
|
||||
|
||||
class Junie(AgentBootstrap):
|
||||
@@ -16,10 +16,15 @@ class Junie(AgentBootstrap):
|
||||
"""Install Junie agent files into the project."""
|
||||
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||
# Record installed files for tracked teardown
|
||||
installed = [p for p in commands_dir.rglob("*") if p.is_file()]
|
||||
record_installed_files(project_path, self.manifest.id, installed)
|
||||
|
||||
def teardown(self, project_path: Path) -> None:
|
||||
"""Remove Junie agent files from the project."""
|
||||
import shutil
|
||||
agent_dir = project_path / self.AGENT_DIR
|
||||
if agent_dir.is_dir():
|
||||
shutil.rmtree(agent_dir)
|
||||
def teardown(self, project_path: Path, *, force: bool = False) -> None:
|
||||
"""Remove Junie agent files from the project.
|
||||
|
||||
Only removes individual tracked files — directories are never
|
||||
deleted. Raises ``AgentFileModifiedError`` if any tracked file
|
||||
was modified and *force* is ``False``.
|
||||
"""
|
||||
remove_tracked_files(project_path, self.manifest.id, force=force)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from specify_cli.agent_pack import AgentBootstrap
|
||||
from specify_cli.agent_pack import AgentBootstrap, record_installed_files, remove_tracked_files
|
||||
|
||||
|
||||
class Kilocode(AgentBootstrap):
|
||||
@@ -16,10 +16,15 @@ class Kilocode(AgentBootstrap):
|
||||
"""Install Kilo Code agent files into the project."""
|
||||
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||
# Record installed files for tracked teardown
|
||||
installed = [p for p in commands_dir.rglob("*") if p.is_file()]
|
||||
record_installed_files(project_path, self.manifest.id, installed)
|
||||
|
||||
def teardown(self, project_path: Path) -> None:
|
||||
"""Remove Kilo Code agent files from the project."""
|
||||
import shutil
|
||||
agent_dir = project_path / self.AGENT_DIR
|
||||
if agent_dir.is_dir():
|
||||
shutil.rmtree(agent_dir)
|
||||
def teardown(self, project_path: Path, *, force: bool = False) -> None:
|
||||
"""Remove Kilo Code agent files from the project.
|
||||
|
||||
Only removes individual tracked files — directories are never
|
||||
deleted. Raises ``AgentFileModifiedError`` if any tracked file
|
||||
was modified and *force* is ``False``.
|
||||
"""
|
||||
remove_tracked_files(project_path, self.manifest.id, force=force)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from specify_cli.agent_pack import AgentBootstrap
|
||||
from specify_cli.agent_pack import AgentBootstrap, record_installed_files, remove_tracked_files
|
||||
|
||||
|
||||
class Kimi(AgentBootstrap):
|
||||
@@ -16,10 +16,15 @@ class Kimi(AgentBootstrap):
|
||||
"""Install Kimi Code agent files into the project."""
|
||||
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||
# Record installed files for tracked teardown
|
||||
installed = [p for p in commands_dir.rglob("*") if p.is_file()]
|
||||
record_installed_files(project_path, self.manifest.id, installed)
|
||||
|
||||
def teardown(self, project_path: Path) -> None:
|
||||
"""Remove Kimi Code agent files from the project."""
|
||||
import shutil
|
||||
agent_dir = project_path / self.AGENT_DIR
|
||||
if agent_dir.is_dir():
|
||||
shutil.rmtree(agent_dir)
|
||||
def teardown(self, project_path: Path, *, force: bool = False) -> None:
|
||||
"""Remove Kimi Code agent files from the project.
|
||||
|
||||
Only removes individual tracked files — directories are never
|
||||
deleted. Raises ``AgentFileModifiedError`` if any tracked file
|
||||
was modified and *force* is ``False``.
|
||||
"""
|
||||
remove_tracked_files(project_path, self.manifest.id, force=force)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from specify_cli.agent_pack import AgentBootstrap
|
||||
from specify_cli.agent_pack import AgentBootstrap, record_installed_files, remove_tracked_files
|
||||
|
||||
|
||||
class KiroCli(AgentBootstrap):
|
||||
@@ -16,10 +16,15 @@ class KiroCli(AgentBootstrap):
|
||||
"""Install Kiro CLI agent files into the project."""
|
||||
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||
# Record installed files for tracked teardown
|
||||
installed = [p for p in commands_dir.rglob("*") if p.is_file()]
|
||||
record_installed_files(project_path, self.manifest.id, installed)
|
||||
|
||||
def teardown(self, project_path: Path) -> None:
|
||||
"""Remove Kiro CLI agent files from the project."""
|
||||
import shutil
|
||||
agent_dir = project_path / self.AGENT_DIR
|
||||
if agent_dir.is_dir():
|
||||
shutil.rmtree(agent_dir)
|
||||
def teardown(self, project_path: Path, *, force: bool = False) -> None:
|
||||
"""Remove Kiro CLI agent files from the project.
|
||||
|
||||
Only removes individual tracked files — directories are never
|
||||
deleted. Raises ``AgentFileModifiedError`` if any tracked file
|
||||
was modified and *force* is ``False``.
|
||||
"""
|
||||
remove_tracked_files(project_path, self.manifest.id, force=force)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from specify_cli.agent_pack import AgentBootstrap
|
||||
from specify_cli.agent_pack import AgentBootstrap, record_installed_files, remove_tracked_files
|
||||
|
||||
|
||||
class Opencode(AgentBootstrap):
|
||||
@@ -16,10 +16,15 @@ class Opencode(AgentBootstrap):
|
||||
"""Install opencode agent files into the project."""
|
||||
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||
# Record installed files for tracked teardown
|
||||
installed = [p for p in commands_dir.rglob("*") if p.is_file()]
|
||||
record_installed_files(project_path, self.manifest.id, installed)
|
||||
|
||||
def teardown(self, project_path: Path) -> None:
|
||||
"""Remove opencode agent files from the project."""
|
||||
import shutil
|
||||
agent_dir = project_path / self.AGENT_DIR
|
||||
if agent_dir.is_dir():
|
||||
shutil.rmtree(agent_dir)
|
||||
def teardown(self, project_path: Path, *, force: bool = False) -> None:
|
||||
"""Remove opencode agent files from the project.
|
||||
|
||||
Only removes individual tracked files — directories are never
|
||||
deleted. Raises ``AgentFileModifiedError`` if any tracked file
|
||||
was modified and *force* is ``False``.
|
||||
"""
|
||||
remove_tracked_files(project_path, self.manifest.id, force=force)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from specify_cli.agent_pack import AgentBootstrap
|
||||
from specify_cli.agent_pack import AgentBootstrap, record_installed_files, remove_tracked_files
|
||||
|
||||
|
||||
class Pi(AgentBootstrap):
|
||||
@@ -16,10 +16,15 @@ class Pi(AgentBootstrap):
|
||||
"""Install Pi Coding Agent agent files into the project."""
|
||||
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||
# Record installed files for tracked teardown
|
||||
installed = [p for p in commands_dir.rglob("*") if p.is_file()]
|
||||
record_installed_files(project_path, self.manifest.id, installed)
|
||||
|
||||
def teardown(self, project_path: Path) -> None:
|
||||
"""Remove Pi Coding Agent agent files from the project."""
|
||||
import shutil
|
||||
agent_dir = project_path / self.AGENT_DIR
|
||||
if agent_dir.is_dir():
|
||||
shutil.rmtree(agent_dir)
|
||||
def teardown(self, project_path: Path, *, force: bool = False) -> None:
|
||||
"""Remove Pi Coding Agent agent files from the project.
|
||||
|
||||
Only removes individual tracked files — directories are never
|
||||
deleted. Raises ``AgentFileModifiedError`` if any tracked file
|
||||
was modified and *force* is ``False``.
|
||||
"""
|
||||
remove_tracked_files(project_path, self.manifest.id, force=force)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from specify_cli.agent_pack import AgentBootstrap
|
||||
from specify_cli.agent_pack import AgentBootstrap, record_installed_files, remove_tracked_files
|
||||
|
||||
|
||||
class Qodercli(AgentBootstrap):
|
||||
@@ -16,10 +16,15 @@ class Qodercli(AgentBootstrap):
|
||||
"""Install Qoder CLI agent files into the project."""
|
||||
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||
# Record installed files for tracked teardown
|
||||
installed = [p for p in commands_dir.rglob("*") if p.is_file()]
|
||||
record_installed_files(project_path, self.manifest.id, installed)
|
||||
|
||||
def teardown(self, project_path: Path) -> None:
|
||||
"""Remove Qoder CLI agent files from the project."""
|
||||
import shutil
|
||||
agent_dir = project_path / self.AGENT_DIR
|
||||
if agent_dir.is_dir():
|
||||
shutil.rmtree(agent_dir)
|
||||
def teardown(self, project_path: Path, *, force: bool = False) -> None:
|
||||
"""Remove Qoder CLI agent files from the project.
|
||||
|
||||
Only removes individual tracked files — directories are never
|
||||
deleted. Raises ``AgentFileModifiedError`` if any tracked file
|
||||
was modified and *force* is ``False``.
|
||||
"""
|
||||
remove_tracked_files(project_path, self.manifest.id, force=force)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from specify_cli.agent_pack import AgentBootstrap
|
||||
from specify_cli.agent_pack import AgentBootstrap, record_installed_files, remove_tracked_files
|
||||
|
||||
|
||||
class Qwen(AgentBootstrap):
|
||||
@@ -16,10 +16,15 @@ class Qwen(AgentBootstrap):
|
||||
"""Install Qwen Code agent files into the project."""
|
||||
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||
# Record installed files for tracked teardown
|
||||
installed = [p for p in commands_dir.rglob("*") if p.is_file()]
|
||||
record_installed_files(project_path, self.manifest.id, installed)
|
||||
|
||||
def teardown(self, project_path: Path) -> None:
|
||||
"""Remove Qwen Code agent files from the project."""
|
||||
import shutil
|
||||
agent_dir = project_path / self.AGENT_DIR
|
||||
if agent_dir.is_dir():
|
||||
shutil.rmtree(agent_dir)
|
||||
def teardown(self, project_path: Path, *, force: bool = False) -> None:
|
||||
"""Remove Qwen Code agent files from the project.
|
||||
|
||||
Only removes individual tracked files — directories are never
|
||||
deleted. Raises ``AgentFileModifiedError`` if any tracked file
|
||||
was modified and *force* is ``False``.
|
||||
"""
|
||||
remove_tracked_files(project_path, self.manifest.id, force=force)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from specify_cli.agent_pack import AgentBootstrap
|
||||
from specify_cli.agent_pack import AgentBootstrap, record_installed_files, remove_tracked_files
|
||||
|
||||
|
||||
class Roo(AgentBootstrap):
|
||||
@@ -16,10 +16,15 @@ class Roo(AgentBootstrap):
|
||||
"""Install Roo Code agent files into the project."""
|
||||
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||
# Record installed files for tracked teardown
|
||||
installed = [p for p in commands_dir.rglob("*") if p.is_file()]
|
||||
record_installed_files(project_path, self.manifest.id, installed)
|
||||
|
||||
def teardown(self, project_path: Path) -> None:
|
||||
"""Remove Roo Code agent files from the project."""
|
||||
import shutil
|
||||
agent_dir = project_path / self.AGENT_DIR
|
||||
if agent_dir.is_dir():
|
||||
shutil.rmtree(agent_dir)
|
||||
def teardown(self, project_path: Path, *, force: bool = False) -> None:
|
||||
"""Remove Roo Code agent files from the project.
|
||||
|
||||
Only removes individual tracked files — directories are never
|
||||
deleted. Raises ``AgentFileModifiedError`` if any tracked file
|
||||
was modified and *force* is ``False``.
|
||||
"""
|
||||
remove_tracked_files(project_path, self.manifest.id, force=force)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from specify_cli.agent_pack import AgentBootstrap
|
||||
from specify_cli.agent_pack import AgentBootstrap, record_installed_files, remove_tracked_files
|
||||
|
||||
|
||||
class Shai(AgentBootstrap):
|
||||
@@ -16,10 +16,15 @@ class Shai(AgentBootstrap):
|
||||
"""Install SHAI agent files into the project."""
|
||||
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||
# Record installed files for tracked teardown
|
||||
installed = [p for p in commands_dir.rglob("*") if p.is_file()]
|
||||
record_installed_files(project_path, self.manifest.id, installed)
|
||||
|
||||
def teardown(self, project_path: Path) -> None:
|
||||
"""Remove SHAI agent files from the project."""
|
||||
import shutil
|
||||
agent_dir = project_path / self.AGENT_DIR
|
||||
if agent_dir.is_dir():
|
||||
shutil.rmtree(agent_dir)
|
||||
def teardown(self, project_path: Path, *, force: bool = False) -> None:
|
||||
"""Remove SHAI agent files from the project.
|
||||
|
||||
Only removes individual tracked files — directories are never
|
||||
deleted. Raises ``AgentFileModifiedError`` if any tracked file
|
||||
was modified and *force* is ``False``.
|
||||
"""
|
||||
remove_tracked_files(project_path, self.manifest.id, force=force)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from specify_cli.agent_pack import AgentBootstrap
|
||||
from specify_cli.agent_pack import AgentBootstrap, record_installed_files, remove_tracked_files
|
||||
|
||||
|
||||
class Tabnine(AgentBootstrap):
|
||||
@@ -16,18 +16,15 @@ class Tabnine(AgentBootstrap):
|
||||
"""Install Tabnine CLI agent files into the project."""
|
||||
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||
# Record installed files for tracked teardown
|
||||
installed = [p for p in commands_dir.rglob("*") if p.is_file()]
|
||||
record_installed_files(project_path, self.manifest.id, installed)
|
||||
|
||||
def teardown(self, project_path: Path) -> None:
|
||||
def teardown(self, project_path: Path, *, force: bool = False) -> None:
|
||||
"""Remove Tabnine CLI agent files from the project.
|
||||
|
||||
Removes the agent/ subdirectory under .tabnine/ to preserve
|
||||
any other Tabnine configuration.
|
||||
Only removes individual tracked files — directories are never
|
||||
deleted. Raises ``AgentFileModifiedError`` if any tracked file
|
||||
was modified and *force* is ``False``.
|
||||
"""
|
||||
import shutil
|
||||
agent_subdir = project_path / self.AGENT_DIR
|
||||
if agent_subdir.is_dir():
|
||||
shutil.rmtree(agent_subdir)
|
||||
# Remove .tabnine/ only if now empty
|
||||
tabnine_dir = project_path / ".tabnine"
|
||||
if tabnine_dir.is_dir() and not any(tabnine_dir.iterdir()):
|
||||
tabnine_dir.rmdir()
|
||||
remove_tracked_files(project_path, self.manifest.id, force=force)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from specify_cli.agent_pack import AgentBootstrap
|
||||
from specify_cli.agent_pack import AgentBootstrap, record_installed_files, remove_tracked_files
|
||||
|
||||
|
||||
class Trae(AgentBootstrap):
|
||||
@@ -16,10 +16,15 @@ class Trae(AgentBootstrap):
|
||||
"""Install Trae agent files into the project."""
|
||||
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||
# Record installed files for tracked teardown
|
||||
installed = [p for p in commands_dir.rglob("*") if p.is_file()]
|
||||
record_installed_files(project_path, self.manifest.id, installed)
|
||||
|
||||
def teardown(self, project_path: Path) -> None:
|
||||
"""Remove Trae agent files from the project."""
|
||||
import shutil
|
||||
agent_dir = project_path / self.AGENT_DIR
|
||||
if agent_dir.is_dir():
|
||||
shutil.rmtree(agent_dir)
|
||||
def teardown(self, project_path: Path, *, force: bool = False) -> None:
|
||||
"""Remove Trae agent files from the project.
|
||||
|
||||
Only removes individual tracked files — directories are never
|
||||
deleted. Raises ``AgentFileModifiedError`` if any tracked file
|
||||
was modified and *force* is ``False``.
|
||||
"""
|
||||
remove_tracked_files(project_path, self.manifest.id, force=force)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from specify_cli.agent_pack import AgentBootstrap
|
||||
from specify_cli.agent_pack import AgentBootstrap, record_installed_files, remove_tracked_files
|
||||
|
||||
|
||||
class Vibe(AgentBootstrap):
|
||||
@@ -16,10 +16,15 @@ class Vibe(AgentBootstrap):
|
||||
"""Install Mistral Vibe agent files into the project."""
|
||||
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||
# Record installed files for tracked teardown
|
||||
installed = [p for p in commands_dir.rglob("*") if p.is_file()]
|
||||
record_installed_files(project_path, self.manifest.id, installed)
|
||||
|
||||
def teardown(self, project_path: Path) -> None:
|
||||
"""Remove Mistral Vibe agent files from the project."""
|
||||
import shutil
|
||||
agent_dir = project_path / self.AGENT_DIR
|
||||
if agent_dir.is_dir():
|
||||
shutil.rmtree(agent_dir)
|
||||
def teardown(self, project_path: Path, *, force: bool = False) -> None:
|
||||
"""Remove Mistral Vibe agent files from the project.
|
||||
|
||||
Only removes individual tracked files — directories are never
|
||||
deleted. Raises ``AgentFileModifiedError`` if any tracked file
|
||||
was modified and *force* is ``False``.
|
||||
"""
|
||||
remove_tracked_files(project_path, self.manifest.id, force=force)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from specify_cli.agent_pack import AgentBootstrap
|
||||
from specify_cli.agent_pack import AgentBootstrap, record_installed_files, remove_tracked_files
|
||||
|
||||
|
||||
class Windsurf(AgentBootstrap):
|
||||
@@ -16,10 +16,15 @@ class Windsurf(AgentBootstrap):
|
||||
"""Install Windsurf agent files into the project."""
|
||||
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||
# Record installed files for tracked teardown
|
||||
installed = [p for p in commands_dir.rglob("*") if p.is_file()]
|
||||
record_installed_files(project_path, self.manifest.id, installed)
|
||||
|
||||
def teardown(self, project_path: Path) -> None:
|
||||
"""Remove Windsurf agent files from the project."""
|
||||
import shutil
|
||||
agent_dir = project_path / self.AGENT_DIR
|
||||
if agent_dir.is_dir():
|
||||
shutil.rmtree(agent_dir)
|
||||
def teardown(self, project_path: Path, *, force: bool = False) -> None:
|
||||
"""Remove Windsurf agent files from the project.
|
||||
|
||||
Only removes individual tracked files — directories are never
|
||||
deleted. Raises ``AgentFileModifiedError`` if any tracked file
|
||||
was modified and *force* is ``False``.
|
||||
"""
|
||||
remove_tracked_files(project_path, self.manifest.id, force=force)
|
||||
|
||||
Reference in New Issue
Block a user