Compare commits

...

24 Commits

Author SHA1 Message Date
Manfred Riem
b59b82813c fix: only delete manifest when full tracked set was processed
When remove_tracked_files is called with an explicit files dict (subset),
skip manifest deletion to avoid losing tracking of entries not in the
subset. Manifest cleanup only runs when the full set is read from the
manifest itself (files=None).
2026-03-23 15:15:28 -05:00
Manfred Riem
38ae759568 fix: stale manifest cleanup, resolve with project_path, AGENTS.md add docs
- remove_tracked_files: count only still-existing files as remaining;
  user-deleted files no longer prevent manifest cleanup
- init --agent: pass project_path to resolve_agent_pack so project-level
  overrides (.specify/agents/) are honored during --here init
- AGENTS.md: update agent add to show --from <path> requirement and note
  catalog fetch is not yet implemented
2026-03-23 12:44:25 -05:00
Manfred Riem
ca9c73da0f docs: explain extension file overlap in finalize_setup scan comment 2026-03-23 12:18:57 -05:00
Manfred Riem
433502b72d fix: force flag passthrough, cross-platform hashes, manifest retention, docstring accuracy
- agent_switch: pass force=force (user's actual flag) instead of
  force=True so hash-check protection is preserved for unconfirmed files
- _hash_file_list: use as_posix() for POSIX-stable manifest keys;
  guard relative_to with try/except to skip files outside project root
- remove_tracked_files: updated docstring to accurately describe hash
  comparison behavior (values ARE used, not ignored); manifest is only
  deleted when all tracked files were removed (preserves tracking of
  skipped modified files)
2026-03-23 12:17:19 -05:00
Manfred Riem
720ac509d2 fix: path traversal guard, rollback extension re-registration, lifecycle docs
- remove_tracked_files: validate resolved path stays within project_path
  before unlinking; reject entries with '../' that escape the project root
- Rollback: call _reregister_extension_commands() during rollback (same
  as success path) so extension files are properly restored
- AgentBootstrap: comprehensive lifecycle flow docstring documenting the
  setup → finalize_setup → get_tracked_files → check_modified → teardown
  chain and explaining why tracking all files is safe (hash check)
2026-03-23 11:56:50 -05:00
Manfred Riem
48392ea865 fix: hash-check before deletion, track all files, fix overrides bug, update help text
- remove_tracked_files: always compare SHA-256 hash before deleting,
  even when called with explicit files dict; skip modified files unless
  --force is set (was unconditionally deleting all tracked files)
- finalize_setup: track ALL files from setup() (no agent-root filter);
  safe because removal now checks hashes
- list_all_agents: track embedded versions in separate dict so overrides
  always reference the correct embedded version, not a catalog/project
  pack that overwrote the seen dict
- --ai-skills help text: updated to say 'requires --ai or --agent'
2026-03-23 11:24:53 -05:00
Manfred Riem
34fa61e1cc docs: clarify that tracking all setup files is intentional (safe due to hash check) 2026-03-23 11:00:42 -05:00
Manfred Riem
790448294e fix: address PR review round 2 — legacy rmtree confirmation, agent_pack flag, registrar alias, manifest ID validation
- Legacy rmtree: prompt user before deleting agent directory in legacy
  fallback path (both no-manifest and AgentPackError cases), respects --force
- Set options['agent_pack'] = True during agent_switch so projects
  originally created with --ai reflect pack-based management after switch
- Add cursor-agent alias in CommandRegistrar.AGENT_CONFIGS so extension
  re-registration works when switching to/from cursor-agent
- Validate manifest.id matches agent_id in resolve_agent_pack() to
  prevent malicious override packs from injecting different IDs
2026-03-23 10:58:58 -05:00
Manfred Riem
b94e541234 fix: address PR review — legacy teardown, generic agent, ~/.specify paths, 3-segment commands_dir, full file tracking
- Legacy --ai teardown: detect empty tracked files and fall back to
  AGENT_CONFIG-based directory removal during agent switch
- --agent generic: falls through to legacy flow (no embedded pack)
- User/catalog dirs: use ~/.specify/ instead of platformdirs for
  consistency with extensions/presets
- DefaultBootstrap: join all path segments after first for COMMANDS_SUBDIR
  (fixes 3+-segment commands_dir like .tabnine/agent/commands)
- agent_add --from: validate manifest.id matches provided agent_id
- finalize_setup: track all files from setup(), not just agent-root files
- setup() docstring: reference --agent not --ai
- AGENTS.md: document generic agent fallback behavior
2026-03-23 10:28:15 -05:00
copilot-swe-agent[bot]
ab8c58ff23 fix: improve test match specificity and rollback error message per code review
Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
Agent-Logs-Url: https://github.com/github/spec-kit/sessions/40d5aec5-d8e9-4e3f-ae60-6cf67ff491f3
2026-03-23 14:35:15 +00:00
copilot-swe-agent[bot]
00117c5074 feat: address all 10 code quality issues — ID validation, rollback, DefaultBootstrap, logging, CLI fixes, docs
Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
Agent-Logs-Url: https://github.com/github/spec-kit/sessions/40d5aec5-d8e9-4e3f-ae60-6cf67ff491f3
2026-03-23 14:32:46 +00:00
copilot-swe-agent[bot]
795f1e7703 fix: add explanatory comments to all empty except clauses (code quality)
Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
Agent-Logs-Url: https://github.com/github/spec-kit/sessions/bb495c08-5d15-410f-9ba9-89d3fc413904
2026-03-23 14:01:21 +00:00
copilot-swe-agent[bot]
55bcbd3977 fix: resolve all ruff check failures (F541 f-string placeholders, F401 unused imports, F841 unused variable)
Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
Agent-Logs-Url: https://github.com/github/spec-kit/sessions/e19bd25e-f084-4f38-85b6-8105cbb50494
2026-03-23 13:36:36 +00:00
copilot-swe-agent[bot]
978addc390 refactor: simplify finalize_setup scan to agent_root only, improve comments
Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
Agent-Logs-Url: https://github.com/github/spec-kit/sessions/054690bb-c048-41e0-b553-377d5cb36b78
2026-03-20 22:32:36 +00:00
copilot-swe-agent[bot]
9b580a536b feat: setup() owns scaffolding and returns actual installed files
- AgentBootstrap._scaffold_project() calls scaffold_from_core_pack,
  snapshots before/after, returns all new files
- finalize_setup() filters agent_files to only track files under the
  agent's own directory tree (shared .specify/ files not tracked)
- All 25 bootstrap setup() methods call _scaffold_project() and return
  the actual file list instead of []
- --agent init flow routes through setup() for scaffolding instead of
  calling scaffold_from_core_pack directly
- 100 new tests (TestSetupReturnsFiles): verify every agent's setup()
  returns non-empty, existing, absolute paths including agent-dir files
- Parity tests use CliRunner to invoke the real init command
- finalize_setup bug fix: skills-migrated agents (agy) now have their
  skills directory scanned correctly
- 1262 tests pass (452 in test_agent_pack.py alone)

Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
Agent-Logs-Url: https://github.com/github/spec-kit/sessions/054690bb-c048-41e0-b553-377d5cb36b78
2026-03-20 22:29:33 +00:00
copilot-swe-agent[bot]
d6016ab9db style: simplify --agent help text, normalize comment spelling
Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
Agent-Logs-Url: https://github.com/github/spec-kit/sessions/930d8c4d-ce42-41fb-a40f-561fb1468e81
2026-03-20 21:54:56 +00:00
copilot-swe-agent[bot]
c2227a7ffd feat: add --agent flag to init for pack-based flow with file tracking
- `specify init --agent claude` resolves through the pack system and
  records all installed files in .specify/agent-manifest-<id>.json via
  finalize_setup() after the init pipeline finishes
- --agent and --ai are mutually exclusive; --agent additionally enables
  tracked teardown/switch
- init-options.json gains "agent_pack" key when --agent is used
- 4 new parity tests verify: pack resolution matches AGENT_CONFIG,
  commands_dir parity, finalize_setup records pipeline-created files,
  pack metadata matches CommandRegistrar configuration

Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
Agent-Logs-Url: https://github.com/github/spec-kit/sessions/930d8c4d-ce42-41fb-a40f-561fb1468e81
2026-03-20 21:53:03 +00:00
copilot-swe-agent[bot]
c3efd1fb71 style: fix f-string formatting in _reregister_extension_commands
Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
Agent-Logs-Url: https://github.com/github/spec-kit/sessions/32e470fc-6bf5-453c-bf6c-79a8521efa56
2026-03-20 21:37:27 +00:00
copilot-swe-agent[bot]
e190116d13 refactor: setup reports files, CLI checks modifications before teardown, categorised manifest
- setup() returns List[Path] of installed files so CLI can record them
- finalize_setup() accepts agent_files + extension_files for combined tracking
- Install manifest categorises files: agent_files and extension_files
- get_tracked_files() returns (agent_files, extension_files) split
- remove_tracked_files() accepts explicit files dict for CLI-driven teardown
- agent_switch checks for modifications BEFORE teardown and prompts user
- _reregister_extension_commands() returns List[Path] of created files
- teardown() accepts files parameter to receive explicit file lists
- All 25 bootstraps updated with new signatures
- 5 new tests: categorised manifest, get_tracked_files, explicit file teardown,
  extension file modification detection

Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
Agent-Logs-Url: https://github.com/github/spec-kit/sessions/32e470fc-6bf5-453c-bf6c-79a8521efa56
2026-03-20 21:34:59 +00:00
copilot-swe-agent[bot]
a63c248c80 Move file recording to finalize_setup() — called after init pipeline writes files
Address code review: setup() now only creates directories, while
finalize_setup() (on base class) scans the agent's commands_dir
for all files and records them. This ensures files are tracked
after the full init pipeline has written them, not before.

- Add AgentBootstrap.finalize_setup() that scans commands_dir
- Remove premature record_installed_files() from all 25 setup() methods
- agent_switch calls finalize_setup() after setup() completes
- Update test helper to match new pattern

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
2026-03-20 21:20:22 +00:00
copilot-swe-agent[bot]
b5a5e3fc35 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
2026-03-20 21:15:48 +00:00
copilot-swe-agent[bot]
ec5471af61 Fix code review issues: safe teardown for shared dirs, less brittle test assertions
- Copilot: only remove .github/agents/ (preserves workflows, templates)
- Tabnine: only remove .tabnine/agent/ (preserves other config)
- Amp/Codex: only remove respective subdirs (commands/skills)
  to avoid deleting each other's files in shared .agents/ dir
- Tests: use flexible assertions instead of hardcoded >= 25 counts

Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
Agent-Logs-Url: https://github.com/github/spec-kit/sessions/ef8b4682-7f1a-4b04-a112-df0878236b6b
2026-03-20 21:03:34 +00:00
copilot-swe-agent[bot]
3212309e7c Add agent pack infrastructure with embedded packs, manifest validation, resolution, and CLI commands
- Create src/specify_cli/agent_pack.py with AgentBootstrap base class,
  AgentManifest schema/validation, pack resolution (user > project > catalog > embedded)
- Generate all 25 official agent packs under src/specify_cli/core_pack/agents/
  with speckit-agent.yml manifests and bootstrap.py modules
- Add 'specify agent' CLI subcommands: list, info, validate, export,
  switch, search, add, remove
- Update pyproject.toml to bundle agent packs in the wheel
- Add comprehensive tests (39 tests): manifest validation, bootstrap API,
  resolution order, discovery, consistency with AGENT_CONFIG

Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
Agent-Logs-Url: https://github.com/github/spec-kit/sessions/ef8b4682-7f1a-4b04-a112-df0878236b6b
2026-03-20 21:01:16 +00:00
copilot-swe-agent[bot]
8b20d0b336 Initial plan 2026-03-20 20:46:50 +00:00
31 changed files with 3705 additions and 5 deletions

View File

@@ -427,4 +427,56 @@ When adding new agents:
---
## Agent Pack System (new)
The agent pack system is a declarative, self-contained replacement for the legacy `AGENT_CONFIG` + case/switch architecture. Each agent is defined by a `speckit-agent.yml` manifest and an optional `bootstrap.py` module. When `bootstrap.py` is absent, the built-in `DefaultBootstrap` class derives its directory layout from the manifest's `commands_dir` field.
### `--agent` flag on `specify init`
`specify init --agent <id>` uses the pack-based init flow instead of the legacy `--ai` flow. Both accept the same agent IDs, but `--agent` additionally enables installed-file tracking so that `specify agent switch` can cleanly tear down agent files later.
```bash
specify init my-project --agent claude # Pack-based flow (with file tracking)
specify init --here --agent gemini --ai-skills # With skills
```
`--agent` and `--ai` are mutually exclusive. When `--agent` is used, `init-options.json` gains `"agent_pack": true`. The `generic` agent (which requires `--ai-commands-dir`) falls through to the legacy flow since it has no embedded pack.
### `specify agent` subcommands
| Command | Description |
| ------------------------------- | ----------- |
| `specify agent list` | List all available agent packs |
| `specify agent list --installed`| List only agents installed in the current project |
| `specify agent info <id>` | Show detailed information about an agent pack |
| `specify agent switch <id>` | Switch the active agent (tears down old, sets up new) |
| `specify agent search [query]` | Search agents by name, ID, description, or tags |
| `specify agent validate <path>` | Validate an agent pack directory |
| `specify agent export <id>` | Export an agent pack for editing |
| `specify agent add <id> --from <path>` | Install an agent pack from a local directory |
| `specify agent remove <id>` | Remove a cached/override agent pack |
> **Note:** `specify agent add <id>` without `--from <path>` is reserved for future catalog-based installation, which is not yet implemented.
### Pack resolution order
Agent packs resolve by priority (highest first):
1. **User-level** (`~/.specify/agents/<id>/`) — applies to all projects
2. **Project-level** (`.specify/agents/<id>/`) — project-specific override
3. **Catalog cache** (downloaded via `specify agent add`)
4. **Embedded** (bundled in the specify-cli wheel)
### Trust boundary
Agent packs can include a `bootstrap.py` module that is dynamically imported and executed. Pack authors can run arbitrary code through this mechanism. Only install packs from trusted sources. The 4-level resolution stack means that placing a pack in any of the resolution directories causes its code to run when the agent is loaded.
### Installed-file tracking
When using `--agent`, all installed files are recorded in `.specify/agent-manifest-<id>.json` with SHA-256 hashes. During `specify agent switch`, the CLI:
1. Checks for user-modified files before teardown
2. Prompts for confirmation if files were changed
3. Feeds tracked file lists into teardown for precise, file-level removal (directories are never deleted)
---
*This documentation should be updated whenever new agents are added to maintain accuracy and completeness.*

View File

@@ -43,6 +43,8 @@ packages = ["src/specify_cli"]
"scripts/powershell" = "specify_cli/core_pack/scripts/powershell"
".github/workflows/scripts/create-release-packages.sh" = "specify_cli/core_pack/release_scripts/create-release-packages.sh"
".github/workflows/scripts/create-release-packages.ps1" = "specify_cli/core_pack/release_scripts/create-release-packages.ps1"
# Official agent packs (embedded in wheel for zero-config offline operation)
"src/specify_cli/core_pack/agents" = "specify_cli/core_pack/agents"
[project.optional-dependencies]
test = [

View File

@@ -36,7 +36,7 @@ import json5
import stat
import yaml
from pathlib import Path
from typing import Any, Optional, Tuple
from typing import Any, List, Optional, Tuple
import typer
import httpx
@@ -1715,6 +1715,7 @@ def _handle_agent_skills_migration(console: Console, agent_key: str) -> None:
def init(
project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here, or use '.' for current directory)"),
ai_assistant: str = typer.Option(None, "--ai", help=AI_ASSISTANT_HELP),
agent: str = typer.Option(None, "--agent", help="AI agent to use (enables file tracking for clean teardown when switching agents). Accepts the same agent IDs as --ai. Use --ai generic for custom agent directories."),
ai_commands_dir: str = typer.Option(None, "--ai-commands-dir", help="Directory for agent command files (required with --ai generic, e.g. .myagent/commands/)"),
script_type: str = typer.Option(None, "--script", help="Script type to use: sh or ps"),
ignore_agent_tools: bool = typer.Option(False, "--ignore-agent-tools", help="Skip checks for AI agent tools like Claude Code"),
@@ -1724,7 +1725,7 @@ def init(
skip_tls: bool = typer.Option(False, "--skip-tls", help="Skip SSL/TLS verification (not recommended)"),
debug: bool = typer.Option(False, "--debug", help="Show verbose diagnostic output for network and extraction failures"),
github_token: str = typer.Option(None, "--github-token", help="GitHub token to use for API requests (or set GH_TOKEN or GITHUB_TOKEN environment variable)"),
ai_skills: bool = typer.Option(False, "--ai-skills", help="Install Prompt.MD templates as agent skills (requires --ai)"),
ai_skills: bool = typer.Option(False, "--ai-skills", help="Install Prompt.MD templates as agent skills (requires --ai or --agent)"),
offline: bool = typer.Option(False, "--offline", help="Use assets bundled in the specify-cli package instead of downloading from GitHub (no network access required). Bundled assets will become the default in v0.6.0 and this flag will be removed."),
preset: str = typer.Option(None, "--preset", help="Install a preset during initialization (by preset ID)"),
branch_numbering: str = typer.Option(None, "--branch-numbering", help="Branch numbering strategy: 'sequential' (001, 002, ...) or 'timestamp' (YYYYMMDD-HHMMSS)"),
@@ -1753,6 +1754,7 @@ def init(
Examples:
specify init my-project
specify init my-project --ai claude
specify init my-project --agent claude # Pack-based flow (with file tracking)
specify init my-project --ai copilot --no-git
specify init --ignore-agent-tools my-project
specify init . --ai claude # Initialize in current directory
@@ -1765,6 +1767,7 @@ def init(
specify init --here --force # Skip confirmation when current directory not empty
specify init my-project --ai claude --ai-skills # Install agent skills
specify init --here --ai gemini --ai-skills
specify init my-project --agent claude --ai-skills # Pack-based flow with skills
specify init my-project --ai generic --ai-commands-dir .myagent/commands/ # Unsupported agent
specify init my-project --offline # Use bundled assets (no network access)
specify init my-project --ai claude --preset healthcare-compliance # With preset
@@ -1772,6 +1775,20 @@ def init(
show_banner()
# --agent and --ai are interchangeable for agent selection, but --agent
# additionally opts into the pack-based flow (file tracking via
# finalize_setup for tracked teardown/switch).
use_agent_pack = False
if agent:
if ai_assistant:
console.print("[red]Error:[/red] --agent and --ai cannot both be specified. Use one or the other.")
raise typer.Exit(1)
ai_assistant = agent
# "generic" uses --ai-commands-dir and has no embedded pack,
# so it falls through to the legacy flow.
if agent != "generic":
use_agent_pack = True
# Detect when option values are likely misinterpreted flags (parameter ordering issue)
if ai_assistant and ai_assistant.startswith("--"):
console.print(f"[red]Error:[/red] Invalid value for --ai: '{ai_assistant}'")
@@ -1802,7 +1819,7 @@ def init(
raise typer.Exit(1)
if ai_skills and not ai_assistant:
console.print("[red]Error:[/red] --ai-skills requires --ai to be specified")
console.print("[red]Error:[/red] --ai-skills requires --ai or --agent to be specified")
console.print("[yellow]Usage:[/yellow] specify init <project> --ai <agent> --ai-skills")
raise typer.Exit(1)
@@ -1854,6 +1871,19 @@ def init(
"copilot"
)
# When --agent is used, validate that the agent resolves through the pack
# system and prepare the bootstrap for post-init file tracking.
agent_bootstrap = None
if use_agent_pack:
from .agent_pack import resolve_agent_pack, load_bootstrap, PackResolutionError, AgentPackError
try:
resolved = resolve_agent_pack(selected_ai, project_path=project_path)
agent_bootstrap = load_bootstrap(resolved.path, resolved.manifest)
console.print(f"[dim]Pack-based flow: {resolved.manifest.name} ({resolved.source})[/dim]")
except (PackResolutionError, AgentPackError) as exc:
console.print(f"[red]Error resolving agent pack:[/red] {exc}")
raise typer.Exit(1)
# Agents that have moved from explicit commands/prompts to agent skills.
if selected_ai in AGENT_SKILLS_MIGRATIONS and not ai_skills:
# If selected interactively (no --ai provided), automatically enable
@@ -1957,7 +1987,10 @@ def init(
"This will become the default in v0.6.0."
)
if use_github:
if use_agent_pack:
# Pack-based flow: setup() owns scaffolding, always uses bundled assets.
tracker.add("scaffold", "Apply bundled assets")
elif use_github:
for key, label in [
("fetch", "Fetch latest release"),
("download", "Download template"),
@@ -1992,7 +2025,26 @@ def init(
verify = not skip_tls
local_ssl_context = ssl_context if verify else False
if use_github:
# -- scaffolding ------------------------------------------------
# Pack-based flow (--agent): setup() owns scaffolding and
# returns every file it created. Legacy flow (--ai): scaffold
# directly or download from GitHub.
agent_setup_files: list[Path] = []
if use_agent_pack and agent_bootstrap is not None:
tracker.start("scaffold")
try:
agent_setup_files = agent_bootstrap.setup(
project_path, selected_script, {"here": here})
tracker.complete(
"scaffold",
f"{selected_ai} ({len(agent_setup_files)} files)")
except Exception as exc:
tracker.error("scaffold", str(exc))
if not here and project_path.exists():
shutil.rmtree(project_path)
raise typer.Exit(1)
elif use_github:
with httpx.Client(verify=local_ssl_context) as local_client:
download_and_extract_template(project_path, selected_ai, selected_script, here, verbose=False, tracker=tracker, client=local_client, debug=debug, github_token=github_token)
else:
@@ -2090,6 +2142,7 @@ def init(
"ai": selected_ai,
"ai_skills": ai_skills,
"ai_commands_dir": ai_commands_dir,
"agent_pack": use_agent_pack,
"branch_numbering": branch_numbering or "sequential",
"here": here,
"preset": preset,
@@ -2133,6 +2186,16 @@ def init(
if not use_github:
tracker.skip("cleanup", "not needed (no download)")
# When --agent is used, record all installed agent files for
# tracked teardown. setup() already returned the files it
# created; pass them to finalize_setup so the manifest is
# accurate. finalize_setup also scans the agent directory
# to catch any additional files created by later pipeline
# steps (skills, extensions, presets).
if use_agent_pack and agent_bootstrap is not None:
agent_bootstrap.finalize_setup(
project_path, agent_files=agent_setup_files)
tracker.complete("final", "project ready")
except (typer.Exit, SystemExit):
raise
@@ -2366,6 +2429,616 @@ def version():
console.print()
# ===== Agent Commands =====
agent_app = typer.Typer(
name="agent",
help="Manage agent packs for AI assistants",
add_completion=False,
)
app.add_typer(agent_app, name="agent")
@agent_app.command("list")
def agent_list(
installed: bool = typer.Option(False, "--installed", help="Only show agents that have files present in the current project"),
):
"""List available agent packs."""
from .agent_pack import list_all_agents, list_embedded_agents, _manifest_path
show_banner()
project_path = Path.cwd()
agents = list_all_agents(project_path=project_path)
if installed:
# Filter to only agents that have an install manifest in the
# current project, i.e. agents whose files are actually present.
agents = [
a for a in agents
if _manifest_path(project_path, a.manifest.id).is_file()
]
if not agents and not installed:
agents_from_embedded = list_embedded_agents()
if not agents_from_embedded:
console.print("[yellow]No agent packs found.[/yellow]")
console.print("[dim]Agent packs are embedded in the specify-cli wheel.[/dim]")
raise typer.Exit(0)
if not agents and installed:
console.print("[yellow]No agents are installed in the current project.[/yellow]")
console.print("[dim]Use 'specify init --agent <id>' or 'specify agent switch <id>' to install one.[/dim]")
raise typer.Exit(0)
title = "Installed Agents" if installed else "Available Agent Packs"
table = Table(title=title, show_lines=False)
table.add_column("ID", style="cyan", no_wrap=True)
table.add_column("Name", style="white")
table.add_column("Version", style="dim")
table.add_column("Source", style="green")
table.add_column("CLI Required", style="yellow", justify="center")
for resolved in agents:
m = resolved.manifest
cli_marker = "" if m.requires_cli else ""
source_display = resolved.source
if resolved.overrides:
source_display += f" (overrides {resolved.overrides})"
table.add_row(m.id, m.name, m.version, source_display, cli_marker)
console.print(table)
console.print(f"\n[dim]{len(agents)} agent(s) {'installed' if installed else 'available'}[/dim]")
@agent_app.command("info")
def agent_info(
agent_id: str = typer.Argument(..., help="Agent pack ID (e.g. 'claude', 'gemini')"),
):
"""Show detailed information about an agent pack."""
from .agent_pack import resolve_agent_pack, PackResolutionError
show_banner()
try:
resolved = resolve_agent_pack(agent_id, project_path=Path.cwd())
except PackResolutionError as exc:
console.print(f"[red]Error:[/red] {exc}")
raise typer.Exit(1)
m = resolved.manifest
info_table = Table(show_header=False, box=None, padding=(0, 2))
info_table.add_column("Key", style="cyan", justify="right")
info_table.add_column("Value", style="white")
info_table.add_row("Agent", f"{m.name} ({m.id})")
info_table.add_row("Version", m.version)
info_table.add_row("Description", m.description or "")
info_table.add_row("Author", m.author or "")
info_table.add_row("License", m.license or "")
info_table.add_row("", "")
source_display = resolved.source
if resolved.source == "catalog":
source_display = f"catalog — {resolved.path}"
elif resolved.source == "embedded":
source_display = "embedded (bundled in specify-cli wheel)"
info_table.add_row("Source", source_display)
if resolved.overrides:
info_table.add_row("Overrides", resolved.overrides)
info_table.add_row("Pack Path", str(resolved.path))
info_table.add_row("", "")
info_table.add_row("Requires CLI", "Yes" if m.requires_cli else "No")
if m.install_url:
info_table.add_row("Install URL", m.install_url)
if m.cli_tool:
info_table.add_row("CLI Tool", m.cli_tool)
info_table.add_row("", "")
info_table.add_row("Commands Dir", m.commands_dir or "")
info_table.add_row("Format", m.command_format)
info_table.add_row("Arg Placeholder", m.arg_placeholder)
info_table.add_row("File Extension", m.file_extension)
info_table.add_row("", "")
info_table.add_row("Tags", ", ".join(m.tags) if m.tags else "")
info_table.add_row("Speckit Version", m.speckit_version)
panel = Panel(
info_table,
title=f"[bold cyan]Agent: {m.name}[/bold cyan]",
border_style="cyan",
padding=(1, 2),
)
console.print(panel)
@agent_app.command("validate")
def agent_validate(
pack_path: str = typer.Argument(..., help="Path to the agent pack directory to validate"),
):
"""Validate an agent pack's structure and manifest."""
from .agent_pack import validate_pack, ManifestValidationError, AgentManifest, MANIFEST_FILENAME
show_banner()
path = Path(pack_path).resolve()
if not path.is_dir():
console.print(f"[red]Error:[/red] Not a directory: {path}")
raise typer.Exit(1)
try:
warnings = validate_pack(path)
except ManifestValidationError as exc:
console.print(f"[red]Validation failed:[/red] {exc}")
raise typer.Exit(1)
manifest = AgentManifest.from_yaml(path / MANIFEST_FILENAME)
console.print(f"[green]✓[/green] Pack '{manifest.id}' ({manifest.name}) is valid")
if warnings:
console.print(f"\n[yellow]Warnings ({len(warnings)}):[/yellow]")
for w in warnings:
console.print(f" [yellow]⚠[/yellow] {w}")
else:
console.print("[green]No warnings.[/green]")
@agent_app.command("export")
def agent_export(
agent_id: str = typer.Argument(..., help="Agent pack ID to export"),
to: str = typer.Option(..., "--to", help="Destination directory for the exported pack"),
):
"""Export the active agent pack to a directory for editing."""
from .agent_pack import export_pack, PackResolutionError
show_banner()
dest = Path(to).resolve()
try:
result = export_pack(agent_id, dest, project_path=Path.cwd())
except PackResolutionError as exc:
console.print(f"[red]Error:[/red] {exc}")
raise typer.Exit(1)
console.print(f"[green]✓[/green] Exported '{agent_id}' pack to {result}")
console.print(f"[dim]Edit files in {result} and use as a project-level override.[/dim]")
@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.
Tears down the current agent and sets up the new one.
Preserves specs, plans, tasks, constitution, memory, templates, and scripts.
"""
from .agent_pack import (
resolve_agent_pack,
load_bootstrap,
check_modified_files,
get_tracked_files,
PackResolutionError,
AgentPackError,
)
show_banner()
project_path = Path.cwd()
init_options_file = project_path / ".specify" / "init-options.json"
if not init_options_file.exists():
console.print("[red]Error:[/red] Not a Specify project (missing .specify/init-options.json)")
console.print("[yellow]Hint:[/yellow] Run 'specify init --here' first.")
raise typer.Exit(1)
# Load current project options
try:
options = json.loads(init_options_file.read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError) as exc:
console.print(f"[red]Error reading init options:[/red] {exc}")
raise typer.Exit(1)
current_agent = options.get("ai")
script_type = options.get("script", "sh")
# Resolve the new agent pack
try:
resolved = resolve_agent_pack(agent_id, project_path=project_path)
except PackResolutionError as exc:
console.print(f"[red]Error:[/red] {exc}")
raise typer.Exit(1)
console.print(f"[bold]Switching agent: {current_agent or '(none)'}{agent_id}[/bold]")
# Snapshot tracked files before teardown so we can attempt rollback
# if the new agent's setup fails after teardown.
old_tracked_agent: dict[str, str] = {}
old_tracked_ext: dict[str, str] = {}
# Teardown current agent (best effort — may have been set up with old system)
if current_agent:
try:
current_resolved = resolve_agent_pack(current_agent, project_path=project_path)
current_bootstrap = load_bootstrap(current_resolved.path, current_resolved.manifest)
# Check for modified files BEFORE teardown and prompt for confirmation
modified = check_modified_files(project_path, current_agent)
if modified and not force:
console.print("[yellow]The following files have been modified since installation:[/yellow]")
for f in modified:
console.print(f" {f}")
if not typer.confirm("Remove these modified files?"):
console.print("[dim]Aborted. Use --force to skip this check.[/dim]")
raise typer.Exit(0)
# Retrieve tracked file lists and feed them into teardown
old_tracked_agent, old_tracked_ext = get_tracked_files(project_path, current_agent)
all_files = {**old_tracked_agent, **old_tracked_ext}
if all_files:
console.print(f" [dim]Tearing down {current_agent}...[/dim]")
current_bootstrap.teardown(
project_path,
force=force,
files=all_files,
)
console.print(f" [green]✓[/green] {current_agent} removed")
else:
# No install manifest (legacy --ai project) — fall back
# to removing the agent directory via AGENT_CONFIG.
agent_config = AGENT_CONFIG.get(current_agent, {})
agent_folder = agent_config.get("folder")
if agent_folder:
agent_dir = project_path / agent_folder.rstrip("/")
if agent_dir.is_dir():
if not force:
console.print(f"[yellow]No install manifest found for '{current_agent}' (legacy project).[/yellow]")
console.print(f" Directory to remove: {agent_dir}")
if not typer.confirm("Remove this directory?"):
console.print("[dim]Aborted. Use --force to skip this check.[/dim]")
raise typer.Exit(0)
shutil.rmtree(agent_dir)
console.print(f" [green]✓[/green] {current_agent} directory removed (legacy)")
else:
console.print(f" [yellow]Warning:[/yellow] No tracked files or AGENT_CONFIG entry for '{current_agent}' — skipping teardown")
except AgentPackError:
# If pack-based resolution/load fails, try legacy cleanup via AGENT_CONFIG
agent_config = AGENT_CONFIG.get(current_agent, {})
agent_folder = agent_config.get("folder")
if agent_folder:
agent_dir = project_path / agent_folder.rstrip("/")
if agent_dir.is_dir():
if not force:
console.print(f"[yellow]No agent pack found for '{current_agent}' (legacy project).[/yellow]")
console.print(f" Directory to remove: {agent_dir}")
if not typer.confirm("Remove this directory?"):
console.print("[dim]Aborted. Use --force to skip this check.[/dim]")
raise typer.Exit(0)
shutil.rmtree(agent_dir)
console.print(f" [green]✓[/green] {current_agent} directory removed (legacy)")
# Setup new agent — with rollback on failure
try:
new_bootstrap = load_bootstrap(resolved.path, resolved.manifest)
console.print(f" [dim]Setting up {agent_id}...[/dim]")
agent_files = new_bootstrap.setup(project_path, script_type, options)
console.print(f" [green]✓[/green] {agent_id} installed")
except (AgentPackError, Exception) as exc:
console.print(f"[red]Error setting up {agent_id}:[/red] {exc}")
# Attempt to restore the old agent so the project is not left
# in a broken state after teardown succeeded but setup failed.
if current_agent:
console.print(f"[yellow]Attempting to restore previous agent ({current_agent})...[/yellow]")
try:
rollback_resolved = resolve_agent_pack(current_agent, project_path=project_path)
rollback_bs = load_bootstrap(rollback_resolved.path, rollback_resolved.manifest)
rollback_files = rollback_bs.setup(project_path, script_type, options)
# Re-register extension commands (same as the success path)
rollback_ext_files = _reregister_extension_commands(project_path, current_agent)
rollback_bs.finalize_setup(
project_path,
agent_files=rollback_files,
extension_files=rollback_ext_files,
)
console.print(f" [green]✓[/green] {current_agent} restored")
except Exception:
# Rollback also failed — mark error state in init-options
console.print(
f"[red]Rollback failed.[/red] "
f"The project may be in a broken state — "
f"run 'specify init --here --agent {current_agent}' to repair."
)
options["agent_switch_error"] = (
f"Switch to '{agent_id}' failed after teardown of "
f"'{current_agent}'. Run 'specify init --here --agent "
f"{current_agent}' to restore."
)
init_options_file.write_text(
json.dumps(options, indent=2), encoding="utf-8"
)
raise typer.Exit(1)
# Update init options
options["ai"] = agent_id
options["agent_pack"] = True
options.pop("agent_switch_error", None) # clear any previous error
init_options_file.write_text(json.dumps(options, indent=2), encoding="utf-8")
# Re-register extension commands for the new agent
extension_files = _reregister_extension_commands(project_path, agent_id)
# Record all installed files (agent + extensions) for tracked teardown
new_bootstrap.finalize_setup(
project_path,
agent_files=agent_files,
extension_files=extension_files,
)
console.print(f"\n[bold green]Successfully switched to {resolved.manifest.name}[/bold green]")
def _reregister_extension_commands(project_path: Path, agent_id: str) -> List[Path]:
"""Re-register all installed extension commands for a new agent after switching.
Returns:
List of absolute file paths created by extension registration.
"""
created_files: List[Path] = []
registry_file = project_path / ".specify" / "extensions" / ".registry"
if not registry_file.is_file():
return created_files
try:
registry_data = json.loads(registry_file.read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError):
return created_files
extensions = registry_data.get("extensions", {})
if not extensions:
return created_files
try:
from .agents import CommandRegistrar
registrar = CommandRegistrar()
except ImportError:
return created_files
# Snapshot the commands directory before registration so we can
# detect which files were created by extension commands.
agent_config = registrar.AGENT_CONFIGS.get(agent_id)
if agent_config:
commands_dir = project_path / agent_config["dir"]
pre_existing = set(commands_dir.rglob("*")) if commands_dir.is_dir() else set()
else:
pre_existing = set()
reregistered = 0
for ext_id, ext_data in extensions.items():
commands = ext_data.get("registered_commands", {})
if not commands:
continue
ext_dir = project_path / ".specify" / "extensions" / ext_id
if not ext_dir.is_dir():
continue
# Get the command list from the manifest
manifest_file = ext_dir / "extension.yml"
if not manifest_file.is_file():
continue
try:
from .extensions import ExtensionManifest
manifest = ExtensionManifest(manifest_file)
if manifest.commands:
registered = registrar.register_commands(
agent_id, manifest.commands, ext_id, ext_dir / "commands", project_path
)
if registered:
reregistered += len(registered)
except Exception as exc:
import logging as _logging
_logging.getLogger(__name__).debug(
"Failed to re-register extension '%s' for agent '%s': %s",
ext_id, agent_id, exc,
)
continue
# Collect files created by extension registration
if agent_config:
commands_dir = project_path / agent_config["dir"]
if commands_dir.is_dir():
for p in commands_dir.rglob("*"):
if p.is_file() and p not in pre_existing:
created_files.append(p)
if reregistered:
console.print(
f" [green]✓[/green] Re-registered {reregistered} extension command(s) ({len(created_files)} file(s))"
)
return created_files
@agent_app.command("search")
def agent_search(
query: str = typer.Argument(None, help="Search query (matches agent ID, name, or tags)"),
tag: str = typer.Option(None, "--tag", help="Filter by tag"),
):
"""Search for agent packs across embedded and catalog sources."""
from .agent_pack import list_all_agents
show_banner()
all_agents = list_all_agents(project_path=Path.cwd())
if query:
query_lower = query.lower()
all_agents = [
a for a in all_agents
if query_lower in a.manifest.id.lower()
or query_lower in a.manifest.name.lower()
or query_lower in a.manifest.description.lower()
or any(query_lower in t.lower() for t in a.manifest.tags)
]
if tag:
tag_lower = tag.lower()
all_agents = [
a for a in all_agents
if any(tag_lower == t.lower() for t in a.manifest.tags)
]
if not all_agents:
console.print("[yellow]No agents found matching your search.[/yellow]")
raise typer.Exit(0)
table = Table(title="Search Results", show_lines=False)
table.add_column("ID", style="cyan", no_wrap=True)
table.add_column("Name", style="white")
table.add_column("Description", style="dim")
table.add_column("Tags", style="green")
table.add_column("Source", style="yellow")
for resolved in all_agents:
m = resolved.manifest
table.add_row(
m.id, m.name,
(m.description[:50] + "...") if len(m.description) > 53 else m.description,
", ".join(m.tags),
resolved.source,
)
console.print(table)
console.print(f"\n[dim]{len(all_agents)} result(s)[/dim]")
@agent_app.command("add")
def agent_add(
agent_id: str = typer.Argument(..., help="Agent pack ID to install"),
from_path: str = typer.Option(None, "--from", help="Install from a local path instead of a catalog"),
):
"""Install an agent pack from a catalog or local path."""
from .agent_pack import (
_catalog_agents_dir,
AgentManifest,
ManifestValidationError,
MANIFEST_FILENAME,
)
show_banner()
if from_path:
source = Path(from_path).resolve()
if not source.is_dir():
console.print(f"[red]Error:[/red] Not a directory: {source}")
raise typer.Exit(1)
manifest_file = source / MANIFEST_FILENAME
if not manifest_file.is_file():
console.print(f"[red]Error:[/red] No {MANIFEST_FILENAME} found in {source}")
raise typer.Exit(1)
try:
manifest = AgentManifest.from_yaml(manifest_file)
except ManifestValidationError as exc:
console.print(f"[red]Validation failed:[/red] {exc}")
raise typer.Exit(1)
if manifest.id != agent_id:
console.print(
f"[red]Error:[/red] Manifest ID '{manifest.id}' does not match "
f"the specified agent ID '{agent_id}'."
)
raise typer.Exit(1)
dest = _catalog_agents_dir() / manifest.id
dest.mkdir(parents=True, exist_ok=True)
shutil.copytree(source, dest, dirs_exist_ok=True)
console.print(f"[green]✓[/green] Installed '{manifest.id}' ({manifest.name}) from {source}")
else:
# Catalog fetch — placeholder for future catalog integration
console.print("[yellow]Catalog fetch not yet implemented.[/yellow]")
console.print("[dim]Use --from <path> to install from a local directory.[/dim]")
raise typer.Exit(1)
@agent_app.command("remove")
def agent_remove(
agent_id: str = typer.Argument(..., help="Agent pack ID to remove"),
force: bool = typer.Option(False, "--force", help="Skip confirmation prompts"),
):
"""Remove a cached/override agent pack.
If the agent is an official embedded agent, removing the override
falls back to the embedded version.
"""
from .agent_pack import (
_catalog_agents_dir,
_user_agents_dir,
_embedded_agents_dir,
MANIFEST_FILENAME,
)
show_banner()
removed = False
# Check user-level — prompt because this affects all projects globally
user_pack = _user_agents_dir() / agent_id
if user_pack.is_dir():
if not force:
console.print(
f"[yellow]User-level override for '{agent_id}' affects all projects globally.[/yellow]"
)
if not typer.confirm("Remove this user-level override?"):
console.print("[dim]Skipped user-level override removal.[/dim]")
else:
shutil.rmtree(user_pack)
console.print(f"[green]✓[/green] Removed user-level override for '{agent_id}'")
removed = True
else:
shutil.rmtree(user_pack)
console.print(f"[green]✓[/green] Removed user-level override for '{agent_id}'")
removed = True
# Check project-level
project_pack = Path.cwd() / ".specify" / "agents" / agent_id
if project_pack.is_dir():
shutil.rmtree(project_pack)
console.print(f"[green]✓[/green] Removed project-level override for '{agent_id}'")
removed = True
# Check catalog cache
catalog_pack = _catalog_agents_dir() / agent_id
if catalog_pack.is_dir():
shutil.rmtree(catalog_pack)
console.print(f"[green]✓[/green] Removed catalog-cached version of '{agent_id}'")
removed = True
if not removed:
# Check if it's an embedded agent
embedded_pack = _embedded_agents_dir() / agent_id / MANIFEST_FILENAME
if embedded_pack.is_file():
console.print(f"[yellow]'{agent_id}' is an embedded official agent and cannot be removed.[/yellow]")
console.print("[dim]It has no overrides to remove.[/dim]")
else:
console.print(f"[red]Error:[/red] Agent '{agent_id}' not found.")
raise typer.Exit(1)
else:
# Check for embedded fallback
embedded_pack = _embedded_agents_dir() / agent_id / MANIFEST_FILENAME
if embedded_pack.is_file():
console.print(f"[dim]Embedded version of '{agent_id}' is now active.[/dim]")
# ===== Extension Commands =====
extension_app = typer.Typer(

File diff suppressed because it is too large Load Diff

View File

@@ -47,6 +47,12 @@ class CommandRegistrar:
"args": "$ARGUMENTS",
"extension": ".md"
},
"cursor-agent": {
"dir": ".cursor/commands",
"format": "markdown",
"args": "$ARGUMENTS",
"extension": ".md"
},
"qwen": {
"dir": ".qwen/commands",
"format": "markdown",

View File

@@ -0,0 +1,23 @@
schema_version: "1.0"
agent:
id: "agy"
name: "Antigravity"
version: "1.0.0"
description: "Antigravity IDE for AI-assisted development"
author: "github"
license: "MIT"
runtime:
requires_cli: false
requires:
speckit_version: ">=0.1.0"
tags: ['ide', 'antigravity']
command_registration:
commands_dir: ".agent/commands"
format: "markdown"
arg_placeholder: "$ARGUMENTS"
file_extension: ".md"

View File

@@ -0,0 +1,25 @@
schema_version: "1.0"
agent:
id: "amp"
name: "Amp"
version: "1.0.0"
description: "Amp CLI for AI-assisted development"
author: "github"
license: "MIT"
runtime:
requires_cli: true
install_url: "https://ampcode.com/manual#install"
cli_tool: "amp"
requires:
speckit_version: ">=0.1.0"
tags: ['cli', 'amp']
command_registration:
commands_dir: ".agents/commands"
format: "markdown"
arg_placeholder: "$ARGUMENTS"
file_extension: ".md"

View File

@@ -0,0 +1,25 @@
schema_version: "1.0"
agent:
id: "auggie"
name: "Auggie CLI"
version: "1.0.0"
description: "Auggie CLI for AI-assisted development"
author: "github"
license: "MIT"
runtime:
requires_cli: true
install_url: "https://docs.augmentcode.com/cli/setup-auggie/install-auggie-cli"
cli_tool: "auggie"
requires:
speckit_version: ">=0.1.0"
tags: ['cli', 'augment', 'auggie']
command_registration:
commands_dir: ".augment/commands"
format: "markdown"
arg_placeholder: "$ARGUMENTS"
file_extension: ".md"

View File

@@ -0,0 +1,23 @@
schema_version: "1.0"
agent:
id: "bob"
name: "IBM Bob"
version: "1.0.0"
description: "IBM Bob IDE for AI-assisted development"
author: "github"
license: "MIT"
runtime:
requires_cli: false
requires:
speckit_version: ">=0.1.0"
tags: ['ide', 'ibm', 'bob']
command_registration:
commands_dir: ".bob/commands"
format: "markdown"
arg_placeholder: "$ARGUMENTS"
file_extension: ".md"

View File

@@ -0,0 +1,25 @@
schema_version: "1.0"
agent:
id: "claude"
name: "Claude Code"
version: "1.0.0"
description: "Anthropic's Claude Code CLI for AI-assisted development"
author: "github"
license: "MIT"
runtime:
requires_cli: true
install_url: "https://docs.anthropic.com/en/docs/claude-code/setup"
cli_tool: "claude"
requires:
speckit_version: ">=0.1.0"
tags: ['cli', 'anthropic', 'claude']
command_registration:
commands_dir: ".claude/commands"
format: "markdown"
arg_placeholder: "$ARGUMENTS"
file_extension: ".md"

View File

@@ -0,0 +1,25 @@
schema_version: "1.0"
agent:
id: "codebuddy"
name: "CodeBuddy"
version: "1.0.0"
description: "CodeBuddy CLI for AI-assisted development"
author: "github"
license: "MIT"
runtime:
requires_cli: true
install_url: "https://www.codebuddy.ai/cli"
cli_tool: "codebuddy"
requires:
speckit_version: ">=0.1.0"
tags: ['cli', 'codebuddy']
command_registration:
commands_dir: ".codebuddy/commands"
format: "markdown"
arg_placeholder: "$ARGUMENTS"
file_extension: ".md"

View File

@@ -0,0 +1,25 @@
schema_version: "1.0"
agent:
id: "codex"
name: "Codex CLI"
version: "1.0.0"
description: "OpenAI Codex CLI with project skills support"
author: "github"
license: "MIT"
runtime:
requires_cli: true
install_url: "https://github.com/openai/codex"
cli_tool: "codex"
requires:
speckit_version: ">=0.1.0"
tags: ['cli', 'openai', 'codex', 'skills']
command_registration:
commands_dir: ".agents/skills"
format: "markdown"
arg_placeholder: "$ARGUMENTS"
file_extension: "/SKILL.md"

View File

@@ -0,0 +1,23 @@
schema_version: "1.0"
agent:
id: "copilot"
name: "GitHub Copilot"
version: "1.0.0"
description: "GitHub Copilot for AI-assisted development in VS Code"
author: "github"
license: "MIT"
runtime:
requires_cli: false
requires:
speckit_version: ">=0.1.0"
tags: ['ide', 'github', 'copilot']
command_registration:
commands_dir: ".github/agents"
format: "markdown"
arg_placeholder: "$ARGUMENTS"
file_extension: ".agent.md"

View File

@@ -0,0 +1,23 @@
schema_version: "1.0"
agent:
id: "cursor-agent"
name: "Cursor"
version: "1.0.0"
description: "Cursor IDE for AI-assisted development"
author: "github"
license: "MIT"
runtime:
requires_cli: false
requires:
speckit_version: ">=0.1.0"
tags: ['ide', 'cursor']
command_registration:
commands_dir: ".cursor/commands"
format: "markdown"
arg_placeholder: "$ARGUMENTS"
file_extension: ".md"

View File

@@ -0,0 +1,25 @@
schema_version: "1.0"
agent:
id: "gemini"
name: "Gemini CLI"
version: "1.0.0"
description: "Google's Gemini CLI for AI-assisted development"
author: "github"
license: "MIT"
runtime:
requires_cli: true
install_url: "https://github.com/google-gemini/gemini-cli"
cli_tool: "gemini"
requires:
speckit_version: ">=0.1.0"
tags: ['cli', 'google', 'gemini']
command_registration:
commands_dir: ".gemini/commands"
format: "toml"
arg_placeholder: "{{args}}"
file_extension: ".toml"

View File

@@ -0,0 +1,25 @@
schema_version: "1.0"
agent:
id: "iflow"
name: "iFlow CLI"
version: "1.0.0"
description: "iFlow CLI by iflow-ai for AI-assisted development"
author: "github"
license: "MIT"
runtime:
requires_cli: true
install_url: "https://docs.iflow.cn/en/cli/quickstart"
cli_tool: "iflow"
requires:
speckit_version: ">=0.1.0"
tags: ['cli', 'iflow']
command_registration:
commands_dir: ".iflow/commands"
format: "markdown"
arg_placeholder: "$ARGUMENTS"
file_extension: ".md"

View File

@@ -0,0 +1,25 @@
schema_version: "1.0"
agent:
id: "junie"
name: "Junie"
version: "1.0.0"
description: "Junie by JetBrains for AI-assisted development"
author: "github"
license: "MIT"
runtime:
requires_cli: true
install_url: "https://junie.jetbrains.com/"
cli_tool: "junie"
requires:
speckit_version: ">=0.1.0"
tags: ['cli', 'jetbrains', 'junie']
command_registration:
commands_dir: ".junie/commands"
format: "markdown"
arg_placeholder: "$ARGUMENTS"
file_extension: ".md"

View File

@@ -0,0 +1,23 @@
schema_version: "1.0"
agent:
id: "kilocode"
name: "Kilo Code"
version: "1.0.0"
description: "Kilo Code IDE for AI-assisted development"
author: "github"
license: "MIT"
runtime:
requires_cli: false
requires:
speckit_version: ">=0.1.0"
tags: ['ide', 'kilocode']
command_registration:
commands_dir: ".kilocode/workflows"
format: "markdown"
arg_placeholder: "$ARGUMENTS"
file_extension: ".md"

View File

@@ -0,0 +1,25 @@
schema_version: "1.0"
agent:
id: "kimi"
name: "Kimi Code"
version: "1.0.0"
description: "Kimi Code CLI by Moonshot AI with skills support"
author: "github"
license: "MIT"
runtime:
requires_cli: true
install_url: "https://code.kimi.com/"
cli_tool: "kimi"
requires:
speckit_version: ">=0.1.0"
tags: ['cli', 'moonshot', 'kimi', 'skills']
command_registration:
commands_dir: ".kimi/skills"
format: "markdown"
arg_placeholder: "$ARGUMENTS"
file_extension: "/SKILL.md"

View File

@@ -0,0 +1,25 @@
schema_version: "1.0"
agent:
id: "kiro-cli"
name: "Kiro CLI"
version: "1.0.0"
description: "Kiro CLI by Amazon for AI-assisted development"
author: "github"
license: "MIT"
runtime:
requires_cli: true
install_url: "https://kiro.dev/docs/cli/"
cli_tool: "kiro-cli"
requires:
speckit_version: ">=0.1.0"
tags: ['cli', 'amazon', 'kiro']
command_registration:
commands_dir: ".kiro/prompts"
format: "markdown"
arg_placeholder: "$ARGUMENTS"
file_extension: ".md"

View File

@@ -0,0 +1,25 @@
schema_version: "1.0"
agent:
id: "opencode"
name: "opencode"
version: "1.0.0"
description: "opencode CLI for AI-assisted development"
author: "github"
license: "MIT"
runtime:
requires_cli: true
install_url: "https://opencode.ai"
cli_tool: "opencode"
requires:
speckit_version: ">=0.1.0"
tags: ['cli', 'opencode']
command_registration:
commands_dir: ".opencode/command"
format: "markdown"
arg_placeholder: "$ARGUMENTS"
file_extension: ".md"

View File

@@ -0,0 +1,25 @@
schema_version: "1.0"
agent:
id: "pi"
name: "Pi Coding Agent"
version: "1.0.0"
description: "Pi terminal coding agent for AI-assisted development"
author: "github"
license: "MIT"
runtime:
requires_cli: true
install_url: "https://www.npmjs.com/package/@mariozechner/pi-coding-agent"
cli_tool: "pi"
requires:
speckit_version: ">=0.1.0"
tags: ['cli', 'pi']
command_registration:
commands_dir: ".pi/prompts"
format: "markdown"
arg_placeholder: "$ARGUMENTS"
file_extension: ".md"

View File

@@ -0,0 +1,25 @@
schema_version: "1.0"
agent:
id: "qodercli"
name: "Qoder CLI"
version: "1.0.0"
description: "Qoder CLI for AI-assisted development"
author: "github"
license: "MIT"
runtime:
requires_cli: true
install_url: "https://qoder.com/cli"
cli_tool: "qodercli"
requires:
speckit_version: ">=0.1.0"
tags: ['cli', 'qoder']
command_registration:
commands_dir: ".qoder/commands"
format: "markdown"
arg_placeholder: "$ARGUMENTS"
file_extension: ".md"

View File

@@ -0,0 +1,25 @@
schema_version: "1.0"
agent:
id: "qwen"
name: "Qwen Code"
version: "1.0.0"
description: "Alibaba's Qwen Code CLI for AI-assisted development"
author: "github"
license: "MIT"
runtime:
requires_cli: true
install_url: "https://github.com/QwenLM/qwen-code"
cli_tool: "qwen"
requires:
speckit_version: ">=0.1.0"
tags: ['cli', 'alibaba', 'qwen']
command_registration:
commands_dir: ".qwen/commands"
format: "markdown"
arg_placeholder: "$ARGUMENTS"
file_extension: ".md"

View File

@@ -0,0 +1,23 @@
schema_version: "1.0"
agent:
id: "roo"
name: "Roo Code"
version: "1.0.0"
description: "Roo Code IDE for AI-assisted development"
author: "github"
license: "MIT"
runtime:
requires_cli: false
requires:
speckit_version: ">=0.1.0"
tags: ['ide', 'roo']
command_registration:
commands_dir: ".roo/commands"
format: "markdown"
arg_placeholder: "$ARGUMENTS"
file_extension: ".md"

View File

@@ -0,0 +1,25 @@
schema_version: "1.0"
agent:
id: "shai"
name: "SHAI"
version: "1.0.0"
description: "SHAI CLI for AI-assisted development"
author: "github"
license: "MIT"
runtime:
requires_cli: true
install_url: "https://github.com/ovh/shai"
cli_tool: "shai"
requires:
speckit_version: ">=0.1.0"
tags: ['cli', 'ovh', 'shai']
command_registration:
commands_dir: ".shai/commands"
format: "markdown"
arg_placeholder: "$ARGUMENTS"
file_extension: ".md"

View File

@@ -0,0 +1,25 @@
schema_version: "1.0"
agent:
id: "tabnine"
name: "Tabnine CLI"
version: "1.0.0"
description: "Tabnine CLI for AI-assisted development"
author: "github"
license: "MIT"
runtime:
requires_cli: true
install_url: "https://docs.tabnine.com/main/getting-started/tabnine-cli"
cli_tool: "tabnine"
requires:
speckit_version: ">=0.1.0"
tags: ['cli', 'tabnine']
command_registration:
commands_dir: ".tabnine/agent/commands"
format: "toml"
arg_placeholder: "{{args}}"
file_extension: ".toml"

View File

@@ -0,0 +1,23 @@
schema_version: "1.0"
agent:
id: "trae"
name: "Trae"
version: "1.0.0"
description: "Trae IDE for AI-assisted development"
author: "github"
license: "MIT"
runtime:
requires_cli: false
requires:
speckit_version: ">=0.1.0"
tags: ['ide', 'trae']
command_registration:
commands_dir: ".trae/rules"
format: "markdown"
arg_placeholder: "$ARGUMENTS"
file_extension: ".md"

View File

@@ -0,0 +1,25 @@
schema_version: "1.0"
agent:
id: "vibe"
name: "Mistral Vibe"
version: "1.0.0"
description: "Mistral Vibe CLI for AI-assisted development"
author: "github"
license: "MIT"
runtime:
requires_cli: true
install_url: "https://github.com/mistralai/mistral-vibe"
cli_tool: "vibe"
requires:
speckit_version: ">=0.1.0"
tags: ['cli', 'mistral', 'vibe']
command_registration:
commands_dir: ".vibe/prompts"
format: "markdown"
arg_placeholder: "$ARGUMENTS"
file_extension: ".md"

View File

@@ -0,0 +1,23 @@
schema_version: "1.0"
agent:
id: "windsurf"
name: "Windsurf"
version: "1.0.0"
description: "Windsurf IDE for AI-assisted development"
author: "github"
license: "MIT"
runtime:
requires_cli: false
requires:
speckit_version: ">=0.1.0"
tags: ['ide', 'windsurf']
command_registration:
commands_dir: ".windsurf/workflows"
format: "markdown"
arg_placeholder: "$ARGUMENTS"
file_extension: ".md"

1353
tests/test_agent_pack.py Normal file

File diff suppressed because it is too large Load Diff