spec-kitty-cli 0.12.1__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.
Files changed (242) hide show
  1. spec_kitty_cli-0.12.1.dist-info/METADATA +1767 -0
  2. spec_kitty_cli-0.12.1.dist-info/RECORD +242 -0
  3. spec_kitty_cli-0.12.1.dist-info/WHEEL +4 -0
  4. spec_kitty_cli-0.12.1.dist-info/entry_points.txt +2 -0
  5. spec_kitty_cli-0.12.1.dist-info/licenses/LICENSE +21 -0
  6. specify_cli/__init__.py +171 -0
  7. specify_cli/acceptance.py +627 -0
  8. specify_cli/agent_utils/README.md +157 -0
  9. specify_cli/agent_utils/__init__.py +9 -0
  10. specify_cli/agent_utils/status.py +356 -0
  11. specify_cli/cli/__init__.py +6 -0
  12. specify_cli/cli/commands/__init__.py +46 -0
  13. specify_cli/cli/commands/accept.py +189 -0
  14. specify_cli/cli/commands/agent/__init__.py +22 -0
  15. specify_cli/cli/commands/agent/config.py +382 -0
  16. specify_cli/cli/commands/agent/context.py +191 -0
  17. specify_cli/cli/commands/agent/feature.py +1057 -0
  18. specify_cli/cli/commands/agent/release.py +11 -0
  19. specify_cli/cli/commands/agent/tasks.py +1253 -0
  20. specify_cli/cli/commands/agent/workflow.py +801 -0
  21. specify_cli/cli/commands/context.py +246 -0
  22. specify_cli/cli/commands/dashboard.py +85 -0
  23. specify_cli/cli/commands/implement.py +973 -0
  24. specify_cli/cli/commands/init.py +827 -0
  25. specify_cli/cli/commands/init_help.py +62 -0
  26. specify_cli/cli/commands/merge.py +755 -0
  27. specify_cli/cli/commands/mission.py +240 -0
  28. specify_cli/cli/commands/ops.py +265 -0
  29. specify_cli/cli/commands/orchestrate.py +640 -0
  30. specify_cli/cli/commands/repair.py +175 -0
  31. specify_cli/cli/commands/research.py +165 -0
  32. specify_cli/cli/commands/sync.py +364 -0
  33. specify_cli/cli/commands/upgrade.py +249 -0
  34. specify_cli/cli/commands/validate_encoding.py +186 -0
  35. specify_cli/cli/commands/validate_tasks.py +186 -0
  36. specify_cli/cli/commands/verify.py +310 -0
  37. specify_cli/cli/helpers.py +123 -0
  38. specify_cli/cli/step_tracker.py +91 -0
  39. specify_cli/cli/ui.py +192 -0
  40. specify_cli/core/__init__.py +53 -0
  41. specify_cli/core/agent_context.py +311 -0
  42. specify_cli/core/config.py +96 -0
  43. specify_cli/core/context_validation.py +362 -0
  44. specify_cli/core/dependency_graph.py +351 -0
  45. specify_cli/core/git_ops.py +129 -0
  46. specify_cli/core/multi_parent_merge.py +323 -0
  47. specify_cli/core/paths.py +260 -0
  48. specify_cli/core/project_resolver.py +110 -0
  49. specify_cli/core/stale_detection.py +263 -0
  50. specify_cli/core/tool_checker.py +79 -0
  51. specify_cli/core/utils.py +43 -0
  52. specify_cli/core/vcs/__init__.py +114 -0
  53. specify_cli/core/vcs/detection.py +341 -0
  54. specify_cli/core/vcs/exceptions.py +85 -0
  55. specify_cli/core/vcs/git.py +1304 -0
  56. specify_cli/core/vcs/jujutsu.py +1208 -0
  57. specify_cli/core/vcs/protocol.py +285 -0
  58. specify_cli/core/vcs/types.py +249 -0
  59. specify_cli/core/version_checker.py +261 -0
  60. specify_cli/core/worktree.py +506 -0
  61. specify_cli/dashboard/__init__.py +28 -0
  62. specify_cli/dashboard/diagnostics.py +204 -0
  63. specify_cli/dashboard/handlers/__init__.py +17 -0
  64. specify_cli/dashboard/handlers/api.py +143 -0
  65. specify_cli/dashboard/handlers/base.py +65 -0
  66. specify_cli/dashboard/handlers/features.py +390 -0
  67. specify_cli/dashboard/handlers/router.py +81 -0
  68. specify_cli/dashboard/handlers/static.py +50 -0
  69. specify_cli/dashboard/lifecycle.py +541 -0
  70. specify_cli/dashboard/scanner.py +437 -0
  71. specify_cli/dashboard/server.py +123 -0
  72. specify_cli/dashboard/static/dashboard/dashboard.css +722 -0
  73. specify_cli/dashboard/static/dashboard/dashboard.js +1424 -0
  74. specify_cli/dashboard/static/spec-kitty.png +0 -0
  75. specify_cli/dashboard/templates/__init__.py +36 -0
  76. specify_cli/dashboard/templates/index.html +258 -0
  77. specify_cli/doc_generators.py +621 -0
  78. specify_cli/doc_state.py +408 -0
  79. specify_cli/frontmatter.py +384 -0
  80. specify_cli/gap_analysis.py +915 -0
  81. specify_cli/gitignore_manager.py +300 -0
  82. specify_cli/guards.py +145 -0
  83. specify_cli/legacy_detector.py +83 -0
  84. specify_cli/manifest.py +286 -0
  85. specify_cli/merge/__init__.py +63 -0
  86. specify_cli/merge/executor.py +653 -0
  87. specify_cli/merge/forecast.py +215 -0
  88. specify_cli/merge/ordering.py +126 -0
  89. specify_cli/merge/preflight.py +230 -0
  90. specify_cli/merge/state.py +185 -0
  91. specify_cli/merge/status_resolver.py +354 -0
  92. specify_cli/mission.py +654 -0
  93. specify_cli/missions/documentation/command-templates/implement.md +309 -0
  94. specify_cli/missions/documentation/command-templates/plan.md +275 -0
  95. specify_cli/missions/documentation/command-templates/review.md +344 -0
  96. specify_cli/missions/documentation/command-templates/specify.md +206 -0
  97. specify_cli/missions/documentation/command-templates/tasks.md +189 -0
  98. specify_cli/missions/documentation/mission.yaml +113 -0
  99. specify_cli/missions/documentation/templates/divio/explanation-template.md +192 -0
  100. specify_cli/missions/documentation/templates/divio/howto-template.md +168 -0
  101. specify_cli/missions/documentation/templates/divio/reference-template.md +179 -0
  102. specify_cli/missions/documentation/templates/divio/tutorial-template.md +146 -0
  103. specify_cli/missions/documentation/templates/generators/jsdoc.json.template +18 -0
  104. specify_cli/missions/documentation/templates/generators/sphinx-conf.py.template +36 -0
  105. specify_cli/missions/documentation/templates/plan-template.md +269 -0
  106. specify_cli/missions/documentation/templates/release-template.md +222 -0
  107. specify_cli/missions/documentation/templates/spec-template.md +172 -0
  108. specify_cli/missions/documentation/templates/task-prompt-template.md +140 -0
  109. specify_cli/missions/documentation/templates/tasks-template.md +159 -0
  110. specify_cli/missions/research/command-templates/merge.md +388 -0
  111. specify_cli/missions/research/command-templates/plan.md +125 -0
  112. specify_cli/missions/research/command-templates/review.md +144 -0
  113. specify_cli/missions/research/command-templates/tasks.md +225 -0
  114. specify_cli/missions/research/mission.yaml +115 -0
  115. specify_cli/missions/research/templates/data-model-template.md +33 -0
  116. specify_cli/missions/research/templates/plan-template.md +161 -0
  117. specify_cli/missions/research/templates/research/evidence-log.csv +18 -0
  118. specify_cli/missions/research/templates/research/source-register.csv +18 -0
  119. specify_cli/missions/research/templates/research-template.md +35 -0
  120. specify_cli/missions/research/templates/spec-template.md +64 -0
  121. specify_cli/missions/research/templates/task-prompt-template.md +148 -0
  122. specify_cli/missions/research/templates/tasks-template.md +114 -0
  123. specify_cli/missions/software-dev/command-templates/accept.md +75 -0
  124. specify_cli/missions/software-dev/command-templates/analyze.md +183 -0
  125. specify_cli/missions/software-dev/command-templates/checklist.md +286 -0
  126. specify_cli/missions/software-dev/command-templates/clarify.md +157 -0
  127. specify_cli/missions/software-dev/command-templates/constitution.md +432 -0
  128. specify_cli/missions/software-dev/command-templates/dashboard.md +101 -0
  129. specify_cli/missions/software-dev/command-templates/implement.md +41 -0
  130. specify_cli/missions/software-dev/command-templates/merge.md +383 -0
  131. specify_cli/missions/software-dev/command-templates/plan.md +171 -0
  132. specify_cli/missions/software-dev/command-templates/review.md +32 -0
  133. specify_cli/missions/software-dev/command-templates/specify.md +321 -0
  134. specify_cli/missions/software-dev/command-templates/tasks.md +566 -0
  135. specify_cli/missions/software-dev/mission.yaml +100 -0
  136. specify_cli/missions/software-dev/templates/plan-template.md +132 -0
  137. specify_cli/missions/software-dev/templates/spec-template.md +116 -0
  138. specify_cli/missions/software-dev/templates/task-prompt-template.md +140 -0
  139. specify_cli/missions/software-dev/templates/tasks-template.md +159 -0
  140. specify_cli/orchestrator/__init__.py +75 -0
  141. specify_cli/orchestrator/agent_config.py +224 -0
  142. specify_cli/orchestrator/agents/__init__.py +170 -0
  143. specify_cli/orchestrator/agents/augment.py +112 -0
  144. specify_cli/orchestrator/agents/base.py +243 -0
  145. specify_cli/orchestrator/agents/claude.py +112 -0
  146. specify_cli/orchestrator/agents/codex.py +106 -0
  147. specify_cli/orchestrator/agents/copilot.py +137 -0
  148. specify_cli/orchestrator/agents/cursor.py +139 -0
  149. specify_cli/orchestrator/agents/gemini.py +115 -0
  150. specify_cli/orchestrator/agents/kilocode.py +94 -0
  151. specify_cli/orchestrator/agents/opencode.py +132 -0
  152. specify_cli/orchestrator/agents/qwen.py +96 -0
  153. specify_cli/orchestrator/config.py +455 -0
  154. specify_cli/orchestrator/executor.py +642 -0
  155. specify_cli/orchestrator/integration.py +1230 -0
  156. specify_cli/orchestrator/monitor.py +898 -0
  157. specify_cli/orchestrator/scheduler.py +832 -0
  158. specify_cli/orchestrator/state.py +508 -0
  159. specify_cli/orchestrator/testing/__init__.py +122 -0
  160. specify_cli/orchestrator/testing/availability.py +346 -0
  161. specify_cli/orchestrator/testing/fixtures.py +684 -0
  162. specify_cli/orchestrator/testing/paths.py +218 -0
  163. specify_cli/plan_validation.py +107 -0
  164. specify_cli/scripts/debug-dashboard-scan.py +61 -0
  165. specify_cli/scripts/tasks/acceptance_support.py +695 -0
  166. specify_cli/scripts/tasks/task_helpers.py +506 -0
  167. specify_cli/scripts/tasks/tasks_cli.py +848 -0
  168. specify_cli/scripts/validate_encoding.py +180 -0
  169. specify_cli/task_metadata_validation.py +274 -0
  170. specify_cli/tasks_support.py +447 -0
  171. specify_cli/template/__init__.py +47 -0
  172. specify_cli/template/asset_generator.py +206 -0
  173. specify_cli/template/github_client.py +334 -0
  174. specify_cli/template/manager.py +193 -0
  175. specify_cli/template/renderer.py +99 -0
  176. specify_cli/templates/AGENTS.md +190 -0
  177. specify_cli/templates/POWERSHELL_SYNTAX.md +229 -0
  178. specify_cli/templates/agent-file-template.md +35 -0
  179. specify_cli/templates/checklist-template.md +42 -0
  180. specify_cli/templates/claudeignore-template +58 -0
  181. specify_cli/templates/command-templates/accept.md +141 -0
  182. specify_cli/templates/command-templates/analyze.md +253 -0
  183. specify_cli/templates/command-templates/checklist.md +352 -0
  184. specify_cli/templates/command-templates/clarify.md +224 -0
  185. specify_cli/templates/command-templates/constitution.md +432 -0
  186. specify_cli/templates/command-templates/dashboard.md +175 -0
  187. specify_cli/templates/command-templates/implement.md +190 -0
  188. specify_cli/templates/command-templates/merge.md +374 -0
  189. specify_cli/templates/command-templates/plan.md +171 -0
  190. specify_cli/templates/command-templates/research.md +88 -0
  191. specify_cli/templates/command-templates/review.md +510 -0
  192. specify_cli/templates/command-templates/specify.md +321 -0
  193. specify_cli/templates/command-templates/status.md +92 -0
  194. specify_cli/templates/command-templates/tasks.md +199 -0
  195. specify_cli/templates/git-hooks/pre-commit +22 -0
  196. specify_cli/templates/git-hooks/pre-commit-agent-check +37 -0
  197. specify_cli/templates/git-hooks/pre-commit-encoding-check +142 -0
  198. specify_cli/templates/plan-template.md +108 -0
  199. specify_cli/templates/spec-template.md +118 -0
  200. specify_cli/templates/task-prompt-template.md +165 -0
  201. specify_cli/templates/tasks-template.md +161 -0
  202. specify_cli/templates/vscode-settings.json +13 -0
  203. specify_cli/text_sanitization.py +225 -0
  204. specify_cli/upgrade/__init__.py +18 -0
  205. specify_cli/upgrade/detector.py +239 -0
  206. specify_cli/upgrade/metadata.py +182 -0
  207. specify_cli/upgrade/migrations/__init__.py +65 -0
  208. specify_cli/upgrade/migrations/base.py +80 -0
  209. specify_cli/upgrade/migrations/m_0_10_0_python_only.py +359 -0
  210. specify_cli/upgrade/migrations/m_0_10_12_constitution_cleanup.py +99 -0
  211. specify_cli/upgrade/migrations/m_0_10_14_update_implement_slash_command.py +176 -0
  212. specify_cli/upgrade/migrations/m_0_10_1_populate_slash_commands.py +174 -0
  213. specify_cli/upgrade/migrations/m_0_10_2_update_slash_commands.py +172 -0
  214. specify_cli/upgrade/migrations/m_0_10_6_workflow_simplification.py +174 -0
  215. specify_cli/upgrade/migrations/m_0_10_8_fix_memory_structure.py +252 -0
  216. specify_cli/upgrade/migrations/m_0_10_9_repair_templates.py +168 -0
  217. specify_cli/upgrade/migrations/m_0_11_0_workspace_per_wp.py +182 -0
  218. specify_cli/upgrade/migrations/m_0_11_1_improved_workflow_templates.py +173 -0
  219. specify_cli/upgrade/migrations/m_0_11_1_update_implement_slash_command.py +160 -0
  220. specify_cli/upgrade/migrations/m_0_11_2_improved_workflow_templates.py +173 -0
  221. specify_cli/upgrade/migrations/m_0_11_3_workflow_agent_flag.py +114 -0
  222. specify_cli/upgrade/migrations/m_0_12_0_documentation_mission.py +155 -0
  223. specify_cli/upgrade/migrations/m_0_12_1_remove_kitty_specs_from_gitignore.py +183 -0
  224. specify_cli/upgrade/migrations/m_0_2_0_specify_to_kittify.py +80 -0
  225. specify_cli/upgrade/migrations/m_0_4_8_gitignore_agents.py +118 -0
  226. specify_cli/upgrade/migrations/m_0_5_0_encoding_hooks.py +141 -0
  227. specify_cli/upgrade/migrations/m_0_6_5_commands_rename.py +169 -0
  228. specify_cli/upgrade/migrations/m_0_6_7_ensure_missions.py +228 -0
  229. specify_cli/upgrade/migrations/m_0_7_2_worktree_commands_dedup.py +89 -0
  230. specify_cli/upgrade/migrations/m_0_7_3_update_scripts.py +114 -0
  231. specify_cli/upgrade/migrations/m_0_8_0_remove_active_mission.py +82 -0
  232. specify_cli/upgrade/migrations/m_0_8_0_worktree_agents_symlink.py +148 -0
  233. specify_cli/upgrade/migrations/m_0_9_0_frontmatter_only_lanes.py +346 -0
  234. specify_cli/upgrade/migrations/m_0_9_1_complete_lane_migration.py +656 -0
  235. specify_cli/upgrade/migrations/m_0_9_2_research_mission_templates.py +221 -0
  236. specify_cli/upgrade/registry.py +121 -0
  237. specify_cli/upgrade/runner.py +284 -0
  238. specify_cli/validators/__init__.py +14 -0
  239. specify_cli/validators/paths.py +154 -0
  240. specify_cli/validators/research.py +428 -0
  241. specify_cli/verify_enhanced.py +270 -0
  242. specify_cli/workspace_context.py +224 -0
@@ -0,0 +1,189 @@
1
+ """Accept command implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import List, Optional
7
+
8
+ import typer
9
+ from rich.table import Table
10
+
11
+ from specify_cli.acceptance import (
12
+ AcceptanceError,
13
+ AcceptanceResult,
14
+ AcceptanceSummary,
15
+ choose_mode,
16
+ collect_feature_summary,
17
+ detect_feature_slug,
18
+ perform_acceptance,
19
+ )
20
+ from specify_cli.cli import StepTracker
21
+ from specify_cli.cli.helpers import check_version_compatibility, console, show_banner
22
+ from specify_cli.tasks_support import LANES, TaskCliError, find_repo_root
23
+
24
+
25
+ def _print_acceptance_summary(summary: AcceptanceSummary) -> None:
26
+ table = Table(title="Work Packages by Lane", header_style="cyan")
27
+ table.add_column("Lane")
28
+ table.add_column("Count", justify="right")
29
+ table.add_column("Work Packages", justify="left")
30
+ for lane in LANES:
31
+ items = summary.lanes.get(lane, [])
32
+ display = ", ".join(items) if items else "-"
33
+ table.add_row(lane, str(len(items)), display)
34
+ console.print(table)
35
+
36
+ outstanding = summary.outstanding()
37
+ if outstanding:
38
+ console.print("\n[bold red]Outstanding items[/bold red]")
39
+ for key, values in outstanding.items():
40
+ console.print(f"[red]- {key}[/red]")
41
+ for value in values:
42
+ console.print(f" • {value}")
43
+ else:
44
+ console.print("\n[green]No outstanding acceptance issues detected.[/green]")
45
+
46
+ if summary.optional_missing:
47
+ console.print(
48
+ "\n[yellow]Optional artifacts missing:[/yellow] "
49
+ + ", ".join(summary.optional_missing)
50
+ )
51
+ console.print()
52
+
53
+
54
+ def _print_acceptance_result(result: AcceptanceResult) -> None:
55
+ console.print(
56
+ "\n[bold]Acceptance metadata[/bold]\n"
57
+ f"• Feature: {result.summary.feature}\n"
58
+ f"• Accepted at: {result.accepted_at}\n"
59
+ f"• Accepted by: {result.accepted_by}"
60
+ )
61
+ if result.accept_commit:
62
+ console.print(f"• Acceptance commit: {result.accept_commit}")
63
+ if result.parent_commit:
64
+ console.print(f"• Parent commit: {result.parent_commit}")
65
+ if not result.commit_created:
66
+ console.print("• Commit status: no changes were committed (dry-run)")
67
+
68
+ if result.instructions:
69
+ console.print("\n[bold]Next steps[/bold]")
70
+ for idx, instruction in enumerate(result.instructions, start=1):
71
+ console.print(f" {idx}. {instruction}")
72
+
73
+ if result.cleanup_instructions:
74
+ console.print("\n[bold]Cleanup[/bold]")
75
+ for idx, instruction in enumerate(result.cleanup_instructions, start=1):
76
+ console.print(f" {idx}. {instruction}")
77
+
78
+ if result.notes:
79
+ console.print("\n[bold]Notes[/bold]")
80
+ for note in result.notes:
81
+ console.print(f" - {note}")
82
+
83
+
84
+ def accept(
85
+ feature: Optional[str] = typer.Option(None, "--feature", help="Feature slug to accept (auto-detected by default)"),
86
+ mode: str = typer.Option("auto", "--mode", case_sensitive=False, help="Acceptance mode: auto, pr, local, or checklist"),
87
+ actor: Optional[str] = typer.Option(None, "--actor", help="Name to record as the acceptance actor"),
88
+ test: List[str] = typer.Option([], "--test", help="Validation command executed (repeatable)", show_default=False),
89
+ json_output: bool = typer.Option(False, "--json", help="Emit JSON instead of formatted text"),
90
+ lenient: bool = typer.Option(False, "--lenient", help="Skip strict metadata validation"),
91
+ no_commit: bool = typer.Option(False, "--no-commit", help="Skip auto-commit; report only"),
92
+ allow_fail: bool = typer.Option(False, "--allow-fail", help="Return checklist even when issues remain"),
93
+ ) -> None:
94
+ """Validate feature readiness before merging to main."""
95
+
96
+ show_banner()
97
+
98
+ try:
99
+ repo_root = find_repo_root()
100
+ except TaskCliError as exc:
101
+ console.print(f"[red]Error:[/red] {exc}")
102
+ raise typer.Exit(1)
103
+
104
+ check_version_compatibility(repo_root, "accept")
105
+
106
+ tracker = StepTracker("Feature Acceptance")
107
+ tracker.add("detect", "Identify feature slug")
108
+ tracker.add("verify", "Run readiness checks")
109
+ console.print()
110
+
111
+ tracker.start("detect")
112
+ try:
113
+ feature_slug = (feature or detect_feature_slug(repo_root)).strip()
114
+ except AcceptanceError as exc:
115
+ tracker.error("detect", str(exc))
116
+ console.print(tracker.render())
117
+ console.print(f"[red]Error:[/red] {exc}")
118
+ raise typer.Exit(1)
119
+ tracker.complete("detect", feature_slug)
120
+
121
+ requested_mode = (mode or "auto").lower()
122
+ actual_mode = choose_mode(requested_mode, repo_root)
123
+ commit_required = actual_mode != "checklist" and not no_commit
124
+ if commit_required:
125
+ tracker.add("commit", "Record acceptance metadata")
126
+ tracker.add("guide", "Share next steps")
127
+
128
+ tracker.start("verify")
129
+ summary = collect_feature_summary(
130
+ repo_root,
131
+ feature_slug,
132
+ strict_metadata=not lenient,
133
+ )
134
+ tracker.complete("verify", "ready" if summary.ok else "issues found")
135
+
136
+ if actual_mode == "checklist":
137
+ if json_output:
138
+ console.print(json.dumps(summary.to_dict(), indent=2))
139
+ else:
140
+ _print_acceptance_summary(summary)
141
+ raise typer.Exit(0 if summary.ok else 1)
142
+
143
+ if not summary.ok:
144
+ if json_output:
145
+ console.print(json.dumps(summary.to_dict(), indent=2))
146
+ else:
147
+ _print_acceptance_summary(summary)
148
+ if not allow_fail:
149
+ console.print(
150
+ "\n[red]Outstanding acceptance issues detected. Resolve them before merging or rerun with --allow-fail for a checklist-only report.[/red]"
151
+ )
152
+ raise typer.Exit(1)
153
+ raise typer.Exit(1)
154
+
155
+ acceptance_tests = list(test)
156
+
157
+ try:
158
+ if commit_required:
159
+ tracker.start("commit")
160
+ result = perform_acceptance(
161
+ summary,
162
+ mode=actual_mode,
163
+ actor=actor,
164
+ tests=acceptance_tests,
165
+ auto_commit=commit_required,
166
+ )
167
+ if commit_required:
168
+ detail = "commit created" if result.commit_created else "no changes"
169
+ tracker.complete("commit", detail)
170
+ except AcceptanceError as exc:
171
+ if commit_required:
172
+ tracker.error("commit", str(exc))
173
+ console.print(tracker.render())
174
+ console.print(f"[red]Error:[/red] {exc}")
175
+ raise typer.Exit(1)
176
+
177
+ tracker.start("guide")
178
+ tracker.complete("guide", "instructions ready")
179
+ console.print(tracker.render())
180
+
181
+ if json_output:
182
+ console.print(json.dumps(result.to_dict(), indent=2))
183
+ return
184
+
185
+ _print_acceptance_summary(result.summary)
186
+ _print_acceptance_result(result)
187
+
188
+
189
+ __all__ = ["accept"]
@@ -0,0 +1,22 @@
1
+ """Agent command namespace for AI agents to execute spec-kitty workflows programmatically."""
2
+
3
+ import typer
4
+ from typing_extensions import Annotated
5
+
6
+ from . import config, feature, tasks, context, release, workflow
7
+
8
+ app = typer.Typer(
9
+ name="agent",
10
+ help="Commands for AI agents to execute spec-kitty workflows programmatically",
11
+ no_args_is_help=True
12
+ )
13
+
14
+ # Register sub-apps for each command module
15
+ app.add_typer(config.app, name="config")
16
+ app.add_typer(feature.app, name="feature")
17
+ app.add_typer(tasks.app, name="tasks")
18
+ app.add_typer(context.app, name="context")
19
+ app.add_typer(release.app, name="release")
20
+ app.add_typer(workflow.app, name="workflow")
21
+
22
+ __all__ = ["app"]
@@ -0,0 +1,382 @@
1
+ """Agent configuration management commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import shutil
6
+ from pathlib import Path
7
+ from typing import List
8
+
9
+ import typer
10
+ from rich.console import Console
11
+ from rich.table import Table
12
+
13
+ from specify_cli.orchestrator.agent_config import (
14
+ load_agent_config,
15
+ save_agent_config,
16
+ AgentConfig,
17
+ )
18
+ from specify_cli.upgrade.migrations.m_0_9_1_complete_lane_migration import (
19
+ AGENT_DIR_TO_KEY,
20
+ CompleteLaneMigration,
21
+ )
22
+ from specify_cli.tasks_support import find_repo_root
23
+
24
+ app = typer.Typer(
25
+ name="config",
26
+ help="Manage project AI agent configuration (add, remove, list agents)",
27
+ no_args_is_help=True,
28
+ )
29
+ console = Console()
30
+
31
+ # Reverse mapping: key to (dir, subdir)
32
+ KEY_TO_AGENT_DIR = {
33
+ AGENT_DIR_TO_KEY[agent_dir]: (agent_dir, subdir)
34
+ for agent_dir, subdir in CompleteLaneMigration.AGENT_DIRS
35
+ if agent_dir in AGENT_DIR_TO_KEY
36
+ }
37
+
38
+
39
+ @app.command(name="list")
40
+ def list_agents():
41
+ """List configured agents and their status."""
42
+ try:
43
+ repo_root = find_repo_root()
44
+ except Exception as e:
45
+ console.print(f"[red]Error:[/red] {e}")
46
+ raise typer.Exit(1)
47
+
48
+ # Load config
49
+ config = load_agent_config(repo_root)
50
+
51
+ if not config.available:
52
+ console.print("[yellow]No agents configured.[/yellow]")
53
+ console.print("\nRun 'spec-kitty init' or use 'spec-kitty agent config add' to add agents.")
54
+ return
55
+
56
+ # Display configured agents
57
+ console.print("[cyan]Configured agents:[/cyan]")
58
+ for agent_key in config.available:
59
+ agent_dir_info = KEY_TO_AGENT_DIR.get(agent_key)
60
+ if agent_dir_info:
61
+ agent_dir, subdir = agent_dir_info
62
+ agent_path = repo_root / agent_dir / subdir
63
+ status = "✓" if agent_path.exists() else "⚠"
64
+ console.print(f" {status} {agent_key} ({agent_dir}/{subdir}/)")
65
+ else:
66
+ console.print(f" ✗ {agent_key} (unknown agent)")
67
+
68
+ # Show available but not configured
69
+ all_agent_keys = set(AGENT_DIR_TO_KEY.values())
70
+ not_configured = all_agent_keys - set(config.available)
71
+
72
+ if not_configured:
73
+ console.print("\n[dim]Available but not configured:[/dim]")
74
+ for agent_key in sorted(not_configured):
75
+ console.print(f" - {agent_key}")
76
+
77
+
78
+ @app.command(name="add")
79
+ def add_agents(
80
+ agents: List[str] = typer.Argument(..., help="Agent keys to add (e.g., claude codex)"),
81
+ ):
82
+ """Add agents to the project.
83
+
84
+ Creates agent directories and updates config.yaml.
85
+
86
+ Example:
87
+ spec-kitty agent config add claude codex
88
+ """
89
+ try:
90
+ repo_root = find_repo_root()
91
+ except Exception as e:
92
+ console.print(f"[red]Error:[/red] {e}")
93
+ raise typer.Exit(1)
94
+
95
+ # Load current config
96
+ config = load_agent_config(repo_root)
97
+
98
+ # Validate agent keys
99
+ invalid = [a for a in agents if a not in AGENT_DIR_TO_KEY.values()]
100
+ if invalid:
101
+ console.print(f"[red]Error:[/red] Invalid agent keys: {', '.join(invalid)}")
102
+ console.print(f"\nValid agents: {', '.join(sorted(AGENT_DIR_TO_KEY.values()))}")
103
+ raise typer.Exit(1)
104
+
105
+ added = []
106
+ already_configured = []
107
+ errors = []
108
+
109
+ for agent_key in agents:
110
+ # Check if already configured
111
+ if agent_key in config.available:
112
+ already_configured.append(agent_key)
113
+ continue
114
+
115
+ # Get directory for this agent
116
+ agent_dir_info = KEY_TO_AGENT_DIR.get(agent_key)
117
+ if not agent_dir_info:
118
+ errors.append(f"Unknown agent: {agent_key}")
119
+ continue
120
+
121
+ agent_root, subdir = agent_dir_info
122
+ agent_dir = repo_root / agent_root / subdir
123
+
124
+ # Create directory structure
125
+ try:
126
+ agent_dir.mkdir(parents=True, exist_ok=True)
127
+
128
+ # Generate templates for this agent
129
+ # Copy from mission templates
130
+ missions_dir = repo_root / ".kittify" / "missions" / "software-dev" / "command-templates"
131
+
132
+ if missions_dir.exists():
133
+ for template_file in missions_dir.glob("*.md"):
134
+ dest_file = agent_dir / f"spec-kitty.{template_file.name}"
135
+ shutil.copy2(template_file, dest_file)
136
+
137
+ added.append(agent_key)
138
+ config.available.append(agent_key)
139
+ console.print(f"[green]✓[/green] Added {agent_root}/{subdir}/")
140
+
141
+ except OSError as e:
142
+ errors.append(f"Failed to create {agent_root}/{subdir}/: {e}")
143
+
144
+ # Save updated config
145
+ if added:
146
+ save_agent_config(repo_root, config)
147
+ console.print(f"\n[cyan]Updated config.yaml:[/cyan] added {', '.join(added)}")
148
+
149
+ if already_configured:
150
+ console.print(f"\n[dim]Already configured:[/dim] {', '.join(already_configured)}")
151
+
152
+ if errors:
153
+ console.print("\n[red]Errors:[/red]")
154
+ for error in errors:
155
+ console.print(f" - {error}")
156
+ raise typer.Exit(1)
157
+
158
+
159
+ @app.command(name="remove")
160
+ def remove_agents(
161
+ agents: List[str] = typer.Argument(..., help="Agent keys to remove"),
162
+ keep_config: bool = typer.Option(
163
+ False,
164
+ "--keep-config",
165
+ help="Keep in config.yaml but delete directory",
166
+ ),
167
+ ):
168
+ """Remove agents from the project.
169
+
170
+ Deletes agent directories and updates config.yaml.
171
+
172
+ Example:
173
+ spec-kitty agent config remove codex gemini
174
+ """
175
+ try:
176
+ repo_root = find_repo_root()
177
+ except Exception as e:
178
+ console.print(f"[red]Error:[/red] {e}")
179
+ raise typer.Exit(1)
180
+
181
+ # Load current config
182
+ config = load_agent_config(repo_root)
183
+
184
+ # Validate agent keys
185
+ invalid = [a for a in agents if a not in AGENT_DIR_TO_KEY.values()]
186
+ if invalid:
187
+ console.print(f"[red]Error:[/red] Invalid agent keys: {', '.join(invalid)}")
188
+ console.print(f"\nValid agents: {', '.join(sorted(AGENT_DIR_TO_KEY.values()))}")
189
+ raise typer.Exit(1)
190
+
191
+ removed = []
192
+ errors = []
193
+
194
+ for agent_key in agents:
195
+ # Get directory for this agent
196
+ agent_dir_info = KEY_TO_AGENT_DIR.get(agent_key)
197
+ if not agent_dir_info:
198
+ errors.append(f"Unknown agent: {agent_key}")
199
+ continue
200
+
201
+ agent_root, subdir = agent_dir_info
202
+
203
+ # Delete directory
204
+ agent_path = repo_root / agent_root
205
+ if agent_path.exists():
206
+ try:
207
+ shutil.rmtree(agent_path)
208
+ removed.append(agent_key)
209
+ console.print(f"[green]✓[/green] Removed {agent_root}/")
210
+ except OSError as e:
211
+ errors.append(f"Failed to remove {agent_root}/: {e}")
212
+ else:
213
+ console.print(f"[dim]• {agent_root}/ already removed[/dim]")
214
+
215
+ # Update config (unless --keep-config)
216
+ if not keep_config and agent_key in config.available:
217
+ config.available.remove(agent_key)
218
+
219
+ # Save updated config
220
+ if not keep_config and (removed or any(a in config.available for a in agents)):
221
+ save_agent_config(repo_root, config)
222
+ console.print(f"\n[cyan]Updated config.yaml:[/cyan] removed {', '.join(removed)}")
223
+
224
+ if errors:
225
+ console.print("\n[yellow]Warnings:[/yellow]")
226
+ for error in errors:
227
+ console.print(f" - {error}")
228
+
229
+
230
+ @app.command(name="status")
231
+ def agent_status():
232
+ """Show which agents are configured vs present on filesystem.
233
+
234
+ Identifies:
235
+ - Configured and present (✓)
236
+ - Configured but missing (⚠)
237
+ - Not configured but present (orphaned) (✗)
238
+ """
239
+ try:
240
+ repo_root = find_repo_root()
241
+ except Exception as e:
242
+ console.print(f"[red]Error:[/red] {e}")
243
+ raise typer.Exit(1)
244
+
245
+ # Load config
246
+ config = load_agent_config(repo_root)
247
+
248
+ # Check filesystem for each agent
249
+ table = Table(title="Agent Status")
250
+ table.add_column("Agent Key", style="cyan")
251
+ table.add_column("Directory", style="dim")
252
+ table.add_column("Configured", justify="center")
253
+ table.add_column("Exists", justify="center")
254
+ table.add_column("Status")
255
+
256
+ all_agent_keys = sorted(AGENT_DIR_TO_KEY.values())
257
+
258
+ for agent_key in all_agent_keys:
259
+ agent_dir_info = KEY_TO_AGENT_DIR.get(agent_key)
260
+ if not agent_dir_info:
261
+ continue
262
+
263
+ agent_root, subdir = agent_dir_info
264
+ agent_path = repo_root / agent_root / subdir
265
+
266
+ configured = "✓" if agent_key in config.available else "✗"
267
+ exists = "✓" if agent_path.exists() else "✗"
268
+
269
+ if agent_key in config.available and agent_path.exists():
270
+ status = "[green]OK[/green]"
271
+ elif agent_key in config.available and not agent_path.exists():
272
+ status = "[yellow]Missing[/yellow]"
273
+ elif agent_key not in config.available and agent_path.exists():
274
+ status = "[red]Orphaned[/red]"
275
+ else:
276
+ status = "[dim]Not used[/dim]"
277
+
278
+ table.add_row(agent_key, f"{agent_root}/{subdir}", configured, exists, status)
279
+
280
+ console.print(table)
281
+
282
+ # Summary
283
+ orphaned = [
284
+ key
285
+ for key in all_agent_keys
286
+ if key not in config.available and (repo_root / KEY_TO_AGENT_DIR[key][0]).exists()
287
+ ]
288
+
289
+ if orphaned:
290
+ console.print(
291
+ f"\n[yellow]⚠ {len(orphaned)} orphaned directories found[/yellow] "
292
+ f"(present but not configured)"
293
+ )
294
+ console.print(f"Run 'spec-kitty agent config sync --remove-orphaned' to clean up")
295
+
296
+
297
+ @app.command(name="sync")
298
+ def sync_agents(
299
+ create_missing: bool = typer.Option(
300
+ False,
301
+ "--create-missing",
302
+ help="Create directories for configured agents that are missing",
303
+ ),
304
+ remove_orphaned: bool = typer.Option(
305
+ True,
306
+ "--remove-orphaned/--keep-orphaned",
307
+ help="Remove directories for agents not in config",
308
+ ),
309
+ ):
310
+ """Sync filesystem with config.yaml.
311
+
312
+ By default, removes orphaned directories (present but not configured).
313
+ Use --create-missing to also create directories for configured agents.
314
+ """
315
+ try:
316
+ repo_root = find_repo_root()
317
+ except Exception as e:
318
+ console.print(f"[red]Error:[/red] {e}")
319
+ raise typer.Exit(1)
320
+
321
+ # Load config
322
+ config = load_agent_config(repo_root)
323
+
324
+ changes_made = False
325
+
326
+ # Remove orphaned directories
327
+ if remove_orphaned:
328
+ console.print("[cyan]Checking for orphaned directories...[/cyan]")
329
+ all_agent_keys = set(AGENT_DIR_TO_KEY.values())
330
+ orphaned = [
331
+ key
332
+ for key in all_agent_keys
333
+ if key not in config.available and (repo_root / KEY_TO_AGENT_DIR[key][0]).exists()
334
+ ]
335
+
336
+ for agent_key in orphaned:
337
+ agent_root, _ = KEY_TO_AGENT_DIR[agent_key]
338
+ agent_path = repo_root / agent_root
339
+
340
+ try:
341
+ shutil.rmtree(agent_path)
342
+ console.print(f" [green]✓[/green] Removed orphaned {agent_root}/")
343
+ changes_made = True
344
+ except OSError as e:
345
+ console.print(f" [red]✗[/red] Failed to remove {agent_root}/: {e}")
346
+
347
+ # Create missing directories
348
+ if create_missing:
349
+ console.print("\n[cyan]Checking for missing directories...[/cyan]")
350
+ missions_dir = repo_root / ".kittify" / "missions" / "software-dev" / "command-templates"
351
+
352
+ for agent_key in config.available:
353
+ agent_dir_info = KEY_TO_AGENT_DIR.get(agent_key)
354
+ if not agent_dir_info:
355
+ console.print(f" [yellow]⚠[/yellow] Unknown agent: {agent_key}")
356
+ continue
357
+
358
+ agent_root, subdir = agent_dir_info
359
+ agent_dir = repo_root / agent_root / subdir
360
+
361
+ if not agent_dir.exists():
362
+ try:
363
+ agent_dir.mkdir(parents=True, exist_ok=True)
364
+
365
+ # Copy templates if available
366
+ if missions_dir.exists():
367
+ for template_file in missions_dir.glob("*.md"):
368
+ dest_file = agent_dir / f"spec-kitty.{template_file.name}"
369
+ shutil.copy2(template_file, dest_file)
370
+
371
+ console.print(f" [green]✓[/green] Created {agent_root}/{subdir}/")
372
+ changes_made = True
373
+ except OSError as e:
374
+ console.print(f" [red]✗[/red] Failed to create {agent_root}/{subdir}/: {e}")
375
+
376
+ if not changes_made:
377
+ console.print("[dim]No changes needed - filesystem matches config[/dim]")
378
+ else:
379
+ console.print("\n[green]✓ Sync complete[/green]")
380
+
381
+
382
+ __all__ = ["app"]