specpro-cli 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- specpro_cli/__init__.py +16 -0
- specpro_cli/assets/commands/specpro.analyze.md +1102 -0
- specpro_cli/assets/commands/specpro.checklist.md +335 -0
- specpro_cli/assets/commands/specpro.clarify.md +581 -0
- specpro_cli/assets/commands/specpro.constitution.md +488 -0
- specpro_cli/assets/commands/specpro.feature.md +115 -0
- specpro_cli/assets/commands/specpro.implement.md +1881 -0
- specpro_cli/assets/commands/specpro.manual-test.md +206 -0
- specpro_cli/assets/commands/specpro.plan.md +3284 -0
- specpro_cli/assets/commands/specpro.qc.md +1489 -0
- specpro_cli/assets/commands/specpro.scenarios.md +154 -0
- specpro_cli/assets/commands/specpro.specify.md +1449 -0
- specpro_cli/assets/commands/specpro.status.md +863 -0
- specpro_cli/assets/commands/specpro.tasks.md +1207 -0
- specpro_cli/assets/commands/specpro.test-implement.md +462 -0
- specpro_cli/assets/commands/specpro.test-plan.md +383 -0
- specpro_cli/assets/commands/specpro.user-manual.md +178 -0
- specpro_cli/assets/scripts/bash/check-anti-coupling.sh +293 -0
- specpro_cli/assets/scripts/bash/check-prerequisites.sh +176 -0
- specpro_cli/assets/scripts/bash/common.sh +88 -0
- specpro_cli/assets/scripts/bash/create-new-feature.sh +336 -0
- specpro_cli/assets/scripts/bash/qc-auto-fix.sh +121 -0
- specpro_cli/assets/scripts/bash/setup-plan.sh +60 -0
- specpro_cli/assets/scripts/bash/verify-cumulative-records.sh +203 -0
- specpro_cli/assets/scripts/bash/verify-deliverables-tracked.sh +147 -0
- specpro_cli/assets/scripts/bash/verify-deployment.sh +239 -0
- specpro_cli/assets/scripts/bash/verify-frontmatter-yaml.sh +63 -0
- specpro_cli/assets/scripts/bash/verify-ledger.sh +376 -0
- specpro_cli/assets/scripts/bash/verify-shapes.sh +1082 -0
- specpro_cli/assets/scripts/git-hooks/pre-commit +243 -0
- specpro_cli/assets/scripts/install-git-hooks.sh +67 -0
- specpro_cli/assets/scripts/powershell/check-anti-coupling.ps1 +249 -0
- specpro_cli/assets/scripts/powershell/check-prerequisites.ps1 +148 -0
- specpro_cli/assets/scripts/powershell/common.ps1 +95 -0
- specpro_cli/assets/scripts/powershell/create-new-feature.ps1 +229 -0
- specpro_cli/assets/scripts/powershell/qc-auto-fix.ps1 +110 -0
- specpro_cli/assets/scripts/powershell/setup-plan.ps1 +61 -0
- specpro_cli/assets/scripts/powershell/verify-cumulative-records.ps1 +133 -0
- specpro_cli/assets/scripts/powershell/verify-deliverables-tracked.ps1 +112 -0
- specpro_cli/assets/scripts/powershell/verify-deployment.ps1 +278 -0
- specpro_cli/assets/scripts/powershell/verify-frontmatter-yaml.ps1 +56 -0
- specpro_cli/assets/scripts/powershell/verify-ledger.ps1 +383 -0
- specpro_cli/assets/scripts/powershell/verify-shapes.ps1 +978 -0
- specpro_cli/assets/templates/agent-context-template.md +49 -0
- specpro_cli/assets/templates/assumptions-template.md +248 -0
- specpro_cli/assets/templates/checklist-template.md +40 -0
- specpro_cli/assets/templates/clarifications-template.md +155 -0
- specpro_cli/assets/templates/constitution-template.md +50 -0
- specpro_cli/assets/templates/feature-spec-template.md +66 -0
- specpro_cli/assets/templates/plan-overview-template.md +150 -0
- specpro_cli/assets/templates/plan-template.md +387 -0
- specpro_cli/assets/templates/protocol-golden-bytes-guide.md +195 -0
- specpro_cli/assets/templates/requirements-template.md +356 -0
- specpro_cli/assets/templates/spec-template.md +267 -0
- specpro_cli/assets/templates/tasks-template.md +252 -0
- specpro_cli/assets/templates/test-tasks-template.md +174 -0
- specpro_cli/cli/__init__.py +5 -0
- specpro_cli/cli/cmd_init.py +416 -0
- specpro_cli/cli/cmd_remove.py +122 -0
- specpro_cli/cli/entry.py +181 -0
- specpro_cli/integrations/__init__.py +36 -0
- specpro_cli/integrations/base.py +601 -0
- specpro_cli/integrations/claude/__init__.py +101 -0
- specpro_cli/integrations/copilot/__init__.py +153 -0
- specpro_cli/integrations/cursor_agent/__init__.py +51 -0
- specpro_cli/integrations/gemini/__init__.py +44 -0
- specpro_cli/integrations/opencode/__init__.py +48 -0
- specpro_cli/integrations/qodercli/__init__.py +54 -0
- specpro_cli/integrations/registry.py +88 -0
- specpro_cli/packaged/__init__.py +5 -0
- specpro_cli/packaged/sync.py +106 -0
- specpro_cli-0.1.0.dist-info/METADATA +117 -0
- specpro_cli-0.1.0.dist-info/RECORD +76 -0
- specpro_cli-0.1.0.dist-info/WHEEL +4 -0
- specpro_cli-0.1.0.dist-info/entry_points.txt +2 -0
- specpro_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""`specpro remove` — uninstall the specpro files of ONE agent from a project.
|
|
2
|
+
|
|
3
|
+
The safety of this command lives entirely in one rule, fixed by the contract
|
|
4
|
+
(`install-cli.md` → `## Uninstall Contract`) and not re-argued here:
|
|
5
|
+
|
|
6
|
+
**Remove only paths derivable from the registry** — for each command template,
|
|
7
|
+
exactly the path `setup()` would write (`planned_paths()` is that derivation).
|
|
8
|
+
Three corollaries are the acceptance scenario's three clauses, and they hold by
|
|
9
|
+
construction, not by checking:
|
|
10
|
+
1. a user's own files are not in the set, so they cannot be deleted;
|
|
11
|
+
2. another agent's installed files belong to a DIFFERENT integration's set;
|
|
12
|
+
3. a specpro file the user RENAMED is no longer derivable, so it is not removed —
|
|
13
|
+
reported as zero removals, not as a failure.
|
|
14
|
+
|
|
15
|
+
`--agent` is REQUIRED with no detection: detection reads the existence of the
|
|
16
|
+
layout root, and after an uninstall that directory may still be there (created by
|
|
17
|
+
the user or by other tooling) — using it as the removal target would report a
|
|
18
|
+
successful uninstall of something that was never installed.
|
|
19
|
+
|
|
20
|
+
The shared helpers (`_check_runtime`, `_abort`) are imported from `cmd_init`
|
|
21
|
+
rather than copied: they are the same validation block's step 1 and the same
|
|
22
|
+
failure shape, and a copy would drift the first time one of them is tuned.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
import typer
|
|
30
|
+
|
|
31
|
+
from specpro_cli.cli.cmd_init import _abort, _check_runtime
|
|
32
|
+
from specpro_cli.integrations import registry
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def cmd_remove(
|
|
36
|
+
path: Path = typer.Argument(
|
|
37
|
+
Path("."),
|
|
38
|
+
help="Target project root.",
|
|
39
|
+
show_default=False,
|
|
40
|
+
),
|
|
41
|
+
agent: str = typer.Option(
|
|
42
|
+
..., "--agent", help="Agent key to uninstall (required — no detection)."
|
|
43
|
+
),
|
|
44
|
+
source: Path | None = typer.Option(
|
|
45
|
+
None, "--source", help="Read assets from this path instead of the built-in snapshot."
|
|
46
|
+
),
|
|
47
|
+
) -> None:
|
|
48
|
+
"""Remove the specpro files of one agent; never touch anything else."""
|
|
49
|
+
project_root = path.expanduser().resolve()
|
|
50
|
+
|
|
51
|
+
# --- pre-removal validation (contract: mirrors init's block) ------------------------
|
|
52
|
+
_check_runtime() # 1 FR-002 — removal runs through the same Python runtime
|
|
53
|
+
|
|
54
|
+
integration = registry.get(agent) # 2 FR-005 — unknown key names the valid set
|
|
55
|
+
if integration is None:
|
|
56
|
+
keys = registry.keys()
|
|
57
|
+
_abort(
|
|
58
|
+
f"unknown agent {agent!r}.",
|
|
59
|
+
f"Valid keys: {', '.join(keys) if keys else '(none registered)'}",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# 3 a missing layout root is SUCCESS with a statement, not an error — there is
|
|
63
|
+
# nothing installed for this agent, and "uninstalled zero files" is the truth.
|
|
64
|
+
dest_root = integration.commands_dest(project_root).parent # the layout root
|
|
65
|
+
if not dest_root.is_dir():
|
|
66
|
+
typer.echo(f"specpro remove → {project_root}")
|
|
67
|
+
typer.echo(f"agent: {agent} ({(integration.config or {}).get('name') or agent})")
|
|
68
|
+
typer.echo(f"nothing installed for {agent!r} under {dest_root}")
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
# --- removal: the registry-derived set, nothing else --------------------------------
|
|
72
|
+
from specpro_cli.cli.cmd_init import assets_commands_dir
|
|
73
|
+
|
|
74
|
+
# Same asset-sourcing rule as install (Uninstall Contract's `--source` row): the
|
|
75
|
+
# derivable path set needs the template NAME set, and a source checkout has no
|
|
76
|
+
# built-in snapshot to take it from.
|
|
77
|
+
commands_dir = assets_commands_dir(source)
|
|
78
|
+
if not commands_dir.is_dir():
|
|
79
|
+
_abort(
|
|
80
|
+
f"command templates not found at {commands_dir}",
|
|
81
|
+
"The built-in snapshot ships with the wheel; without it the derivable "
|
|
82
|
+
"path set cannot be computed, and guessing paths is exactly what this "
|
|
83
|
+
"command must never do.",
|
|
84
|
+
"In a source checkout, pass the repository (or its commands/ dir): "
|
|
85
|
+
"--source <path>",
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
dest_roots = set(integration.deploy_roots(project_root))
|
|
89
|
+
|
|
90
|
+
removed: list[Path] = []
|
|
91
|
+
for target in integration.planned_paths(project_root, commands_dir):
|
|
92
|
+
if not target.is_file():
|
|
93
|
+
continue # never installed (or already removed) — not in the report
|
|
94
|
+
target.unlink()
|
|
95
|
+
removed.append(target)
|
|
96
|
+
# Skills layout: the file sits in its own `specpro-<stem>/` directory. When
|
|
97
|
+
# this command's removal empties it, take the empty directory too — a
|
|
98
|
+
# NON-recursive rmdir, so a directory the user has put anything else into is
|
|
99
|
+
# left (with their files) rather than destroyed. Deploy roots themselves are
|
|
100
|
+
# never removed (the user's own files may live beside ours there).
|
|
101
|
+
parent = target.parent
|
|
102
|
+
if parent not in dest_roots:
|
|
103
|
+
try:
|
|
104
|
+
parent.rmdir()
|
|
105
|
+
except OSError:
|
|
106
|
+
pass # not empty — the user's files stay, silently
|
|
107
|
+
|
|
108
|
+
# --- report (contract Output Contract: same shape as install's) ---------------------
|
|
109
|
+
display = (integration.config or {}).get("name") or integration.key
|
|
110
|
+
typer.echo(f"specpro remove → {project_root}")
|
|
111
|
+
typer.echo(f"agent: {integration.key} ({display})")
|
|
112
|
+
typer.echo(f"removed: {len(removed)}")
|
|
113
|
+
for p in removed:
|
|
114
|
+
typer.echo(f" - {p}")
|
|
115
|
+
if not removed:
|
|
116
|
+
# The contract names this explicitly: zero removals against an EXISTING root
|
|
117
|
+
# is a real outcome (everything renamed or already gone), and it must be
|
|
118
|
+
# said — not folded into a silent success.
|
|
119
|
+
typer.echo(
|
|
120
|
+
f"no recognizable specpro files for {integration.key!r} "
|
|
121
|
+
f"(renamed files are never removed) — nothing was deleted."
|
|
122
|
+
)
|
specpro_cli/cli/entry.py
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""The `specpro` entry point — the Typer app and the registry-derived lookups.
|
|
2
|
+
|
|
3
|
+
`specs/contracts/install-cli.md` -> "入口层的职责边界" fixes what may live here:
|
|
4
|
+
parse arguments, validate preconditions, orchestrate, report. Two prohibitions:
|
|
5
|
+
|
|
6
|
+
- MUST NOT hardcode any agent name, path, or format — **everything is queried from the
|
|
7
|
+
registry**. That is what makes "add an agent = add one subclass" true (FR-003); a
|
|
8
|
+
single hardcoded name here would break it, and this file is where it would happen.
|
|
9
|
+
- MUST NOT contain format-conversion logic — that belongs to the format base classes.
|
|
10
|
+
|
|
11
|
+
`cmd_init` (the main command) is a User Story 1 deliverable and is not registered here
|
|
12
|
+
yet; this skeleton carries `check` and `version` only, which is what T016 specifies.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
import typer
|
|
20
|
+
|
|
21
|
+
from specpro_cli.cli import cmd_init as _cmd_init
|
|
22
|
+
from specpro_cli.cli import cmd_remove as _cmd_remove
|
|
23
|
+
from specpro_cli.integrations import registry
|
|
24
|
+
|
|
25
|
+
# FR-067 — brand header, printed on the bare invocation and before EVERY subcommand's
|
|
26
|
+
# output. One constant each, defined once; the callback below is the only place the
|
|
27
|
+
# header is emitted from.
|
|
28
|
+
BANNER = r"""
|
|
29
|
+
███████╗██████╗ ███████╗ ██████╗██╗███████╗██╗ ██╗
|
|
30
|
+
██╔════╝██╔══██╗██╔════╝██╔════╝██║██╔══╝╚██╗ ██╔╝
|
|
31
|
+
███████╗██████╔╝█████╗ ██║ ██║█████╗ ╚████╔╝
|
|
32
|
+
╚════██║██╔═══╝ ██╔══╝ ██║ ██║██╔══╝ ╚██╔╝
|
|
33
|
+
███████║██║ ███████╗╚██████╗██║██║ ██║
|
|
34
|
+
╚══════╝╚═╝ ╚══════╝ ╚═════╝╚═╝╚═╝ ╚═╝
|
|
35
|
+
"""
|
|
36
|
+
SLOGAN = "Spec Pro - Better Spec-Driven Development Toolkit for AI Coding Agents"
|
|
37
|
+
|
|
38
|
+
app = typer.Typer(
|
|
39
|
+
name="specpro",
|
|
40
|
+
help="Install specpro's distributed assets into a project, for a supported AI coding agent.",
|
|
41
|
+
# The header callback must also run on a BARE invocation; without this Click
|
|
42
|
+
# rejects the missing subcommand before the callback fires ("Missing command").
|
|
43
|
+
invoke_without_command=True,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@app.callback()
|
|
48
|
+
def _header(ctx: typer.Context) -> None:
|
|
49
|
+
"""Print the brand header, then dispatch.
|
|
50
|
+
|
|
51
|
+
A bare invocation (no subcommand) gets header + help — the two together are what
|
|
52
|
+
FR-067 means by "裸调用与每一个子命令的输出". `no_args_is_help` is deliberately NOT
|
|
53
|
+
used: its help path bypasses the callback, which would make the bare invocation the
|
|
54
|
+
ONE output without the header.
|
|
55
|
+
"""
|
|
56
|
+
typer.echo(BANNER)
|
|
57
|
+
typer.echo(f" {SLOGAN}")
|
|
58
|
+
typer.echo("")
|
|
59
|
+
if ctx.invoked_subcommand is None:
|
|
60
|
+
typer.echo(ctx.get_help())
|
|
61
|
+
raise typer.Exit()
|
|
62
|
+
|
|
63
|
+
# ---------------------------------------------------------------------------------------
|
|
64
|
+
# Registry-derived lookups — the ONLY way this layer learns about agents
|
|
65
|
+
# ---------------------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def agent_keys() -> list[str]:
|
|
69
|
+
"""Every selectable `--agent` value, from the registry."""
|
|
70
|
+
return registry.keys()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def require_agent_key(key: str) -> None:
|
|
74
|
+
"""Reject an unknown `--agent` value, listing the valid ones.
|
|
75
|
+
|
|
76
|
+
The list is not decoration: FR-005 requires the command to exit with the valid keys
|
|
77
|
+
named, because an agent key is not something a user can guess — it is the agent's
|
|
78
|
+
binary name, and a typo is indistinguishable from an unsupported agent without it.
|
|
79
|
+
"""
|
|
80
|
+
known = agent_keys()
|
|
81
|
+
if key not in known:
|
|
82
|
+
raise typer.BadParameter(
|
|
83
|
+
f"unknown agent {key!r}. Valid keys: {', '.join(known) if known else '(none registered)'}"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def detect_candidates(project_root: Path) -> list[str]:
|
|
88
|
+
"""Agent keys whose deploy root already exists under `project_root`.
|
|
89
|
+
|
|
90
|
+
Derived from each integration's own `commands_dest()`, so the detection rule lives
|
|
91
|
+
with the integration that declares the layout — never here. FR-004 requires the
|
|
92
|
+
caller to ask the user when this returns more than one.
|
|
93
|
+
"""
|
|
94
|
+
found: list[str] = []
|
|
95
|
+
for key, integration in sorted(registry.registered().items()):
|
|
96
|
+
if integration.commands_dest(project_root).parent.exists():
|
|
97
|
+
found.append(key)
|
|
98
|
+
return found
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _package_version() -> str:
|
|
102
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
return version("specpro-cli")
|
|
106
|
+
except PackageNotFoundError: # running from a source tree with no install
|
|
107
|
+
return "0.0.0+source"
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# ---------------------------------------------------------------------------------------
|
|
111
|
+
# Commands
|
|
112
|
+
# ---------------------------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# The main command. Registered here rather than in `cmd_init` itself so that this file
|
|
116
|
+
# stays the single place where the CLI's surface is declared — the same reason `check`
|
|
117
|
+
# and `version` live here.
|
|
118
|
+
app.command(name="init")(_cmd_init.cmd_init)
|
|
119
|
+
app.command(name="remove")(_cmd_remove.cmd_remove)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@app.command()
|
|
123
|
+
def version() -> None:
|
|
124
|
+
"""Print the installed specpro CLI version and system information (FR-069)."""
|
|
125
|
+
import platform
|
|
126
|
+
import sys
|
|
127
|
+
|
|
128
|
+
typer.echo(f"specpro-cli {_package_version()}")
|
|
129
|
+
typer.echo(f"python {sys.version.split()[0]} ({sys.executable})")
|
|
130
|
+
typer.echo(f"os {platform.system()} {platform.release()} ({platform.machine()})")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@app.command()
|
|
134
|
+
def check() -> None:
|
|
135
|
+
"""Self-check: runtime preconditions and which agents are already installed here.
|
|
136
|
+
|
|
137
|
+
Read-only by construction — it must remain safe to run in any directory, including
|
|
138
|
+
one that is not a specpro project.
|
|
139
|
+
"""
|
|
140
|
+
import shutil
|
|
141
|
+
import subprocess
|
|
142
|
+
import sys
|
|
143
|
+
|
|
144
|
+
typer.echo(f"specpro-cli {_package_version()}")
|
|
145
|
+
typer.echo(f"python {sys.version.split()[0]} ({sys.executable})")
|
|
146
|
+
|
|
147
|
+
# FR-068 — probe the tools the workflow itself depends on, one row per tool,
|
|
148
|
+
# present or missing NAMED (a bare count leaves the user unable to act).
|
|
149
|
+
git = shutil.which("git")
|
|
150
|
+
if git:
|
|
151
|
+
ver = subprocess.run(["git", "--version"], capture_output=True, text=True)
|
|
152
|
+
typer.echo(f"git: present — {ver.stdout.strip() or '(version not reported)'}")
|
|
153
|
+
else:
|
|
154
|
+
typer.echo("git: MISSING — install it (https://git-scm.com) — specpro commits per repository")
|
|
155
|
+
|
|
156
|
+
keys = agent_keys()
|
|
157
|
+
if keys:
|
|
158
|
+
typer.echo(f"registered agents ({len(keys)}): {', '.join(keys)}")
|
|
159
|
+
else:
|
|
160
|
+
# Not an error: the registry fills as agent modules are imported, and the
|
|
161
|
+
# skeleton ships before they exist. Saying so beats printing an empty list.
|
|
162
|
+
typer.echo("registered agents (0): none — no integration modules are imported yet")
|
|
163
|
+
|
|
164
|
+
# Each integration declares its own CLI (`cli_command`); the entry layer only
|
|
165
|
+
# probes what the registry supplies — never a hardcoded name.
|
|
166
|
+
for key in keys:
|
|
167
|
+
integration = registry.registered()[key]
|
|
168
|
+
cli = getattr(integration, "cli_command", None)
|
|
169
|
+
if cli is None:
|
|
170
|
+
typer.echo(f"{key}: IDE-only — no standalone CLI to probe")
|
|
171
|
+
elif shutil.which(cli):
|
|
172
|
+
typer.echo(f"{key}: present — {cli} on PATH")
|
|
173
|
+
else:
|
|
174
|
+
typer.echo(f"{key}: CLI MISSING — `{cli}` not on PATH (the IDE may still be usable)")
|
|
175
|
+
|
|
176
|
+
candidates = detect_candidates(Path.cwd())
|
|
177
|
+
typer.echo(f"detected in {Path.cwd()}: {', '.join(candidates) if candidates else '(none)'}")
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
if __name__ == "__main__": # pragma: no cover
|
|
181
|
+
app()
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Integration layer — `IntegrationBase` + the five format base classes + one module per agent.
|
|
2
|
+
|
|
3
|
+
Each agent module declares its own `key` / `config` / `registrar_config` and registers
|
|
4
|
+
itself on import. Modules MUST NOT import one another (FR-003); the registration block
|
|
5
|
+
below is the only place that knows they exist **as a set**.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
# ---------------------------------------------------------------------------------------
|
|
11
|
+
# Registration order — explicit, alphabetical, and the only list of its kind (FR-003)
|
|
12
|
+
# ---------------------------------------------------------------------------------------
|
|
13
|
+
#
|
|
14
|
+
# ⚠️ **Why explicit imports rather than directory discovery.** A `pkgutil.iter_modules`
|
|
15
|
+
# walk would make "which agents are supported" a property of the filesystem, and the
|
|
16
|
+
# failure mode of a filesystem property is **a missing file**: a module that fails to
|
|
17
|
+
# import, or is skipped by a bad glob, simply does not appear — and nothing reports it,
|
|
18
|
+
# because at every call site an absent agent and an unsupported agent have the same
|
|
19
|
+
# shape (`registry.get(key)` returns `None` for both). The explicit list converts that
|
|
20
|
+
# into an `ImportError` at package-import time.
|
|
21
|
+
#
|
|
22
|
+
# ⚠️ **Why alphabetical.** The CLI reads the registry through `registry.keys()`, which
|
|
23
|
+
# sorts, so this order does not decide what the user sees. It decides **registration**
|
|
24
|
+
# order — and an unsorted list has no rule for where a new line goes, which is exactly
|
|
25
|
+
# how the duplicate-key check stops being obviously complete. Alphabetical is the one
|
|
26
|
+
# rule a reader can verify at a glance.
|
|
27
|
+
#
|
|
28
|
+
# ⚠️ **Complete as of T025**: all six first-batch agents have a module, each named on
|
|
29
|
+
# its import line with the task that added it. The explicit-imports rule above stands —
|
|
30
|
+
# a seventh agent lands here as a new line (alphabetical), never as directory discovery.
|
|
31
|
+
from specpro_cli.integrations import claude # noqa: F401 (registers `claude`)
|
|
32
|
+
from specpro_cli.integrations import copilot # noqa: F401 (registers `copilot`) # T021
|
|
33
|
+
from specpro_cli.integrations import cursor_agent # noqa: F401 (registers `cursor-agent`) # T022
|
|
34
|
+
from specpro_cli.integrations import gemini # noqa: F401 (registers `gemini`) # T023
|
|
35
|
+
from specpro_cli.integrations import opencode # noqa: F401 (registers `opencode`) # T024
|
|
36
|
+
from specpro_cli.integrations import qodercli # noqa: F401 (registers `qodercli`) # T025
|