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,640 @@
1
+ """Orchestrate command for autonomous multi-agent feature implementation.
2
+
3
+ This module implements the `spec-kitty orchestrate` CLI command with:
4
+ - --feature: Start new orchestration for a feature (T038)
5
+ - --status: Show current orchestration progress (T039)
6
+ - --resume: Resume paused orchestration (T040)
7
+ - --abort: Stop orchestration and cleanup (T041)
8
+ - Help text and documentation (T042)
9
+
10
+ WP08: Initial CLI structure
11
+ WP09: Full integration with real agent execution
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import asyncio
17
+ import subprocess
18
+ import uuid
19
+ from datetime import datetime, timezone
20
+ from pathlib import Path
21
+ from typing import TYPE_CHECKING
22
+
23
+ import typer
24
+ from rich.console import Console
25
+ from rich.panel import Panel
26
+ from rich.table import Table
27
+
28
+ from specify_cli.cli.helpers import get_project_root_or_exit
29
+ from specify_cli.orchestrator.config import (
30
+ OrchestrationStatus,
31
+ OrchestratorConfig,
32
+ WPStatus,
33
+ load_config,
34
+ )
35
+ from specify_cli.orchestrator.integration import (
36
+ CircularDependencyError,
37
+ NoAgentsError,
38
+ ValidationError,
39
+ print_summary,
40
+ run_orchestration_loop,
41
+ validate_agents,
42
+ validate_feature,
43
+ )
44
+ from specify_cli.orchestrator.scheduler import (
45
+ build_wp_graph,
46
+ get_topological_order,
47
+ validate_wp_graph,
48
+ )
49
+ from specify_cli.orchestrator.state import (
50
+ OrchestrationRun,
51
+ WPExecution,
52
+ clear_state,
53
+ has_active_orchestration,
54
+ load_state,
55
+ save_state,
56
+ )
57
+
58
+ if TYPE_CHECKING:
59
+ pass
60
+
61
+ console = Console()
62
+
63
+
64
+ # =============================================================================
65
+ # App Definition (T042)
66
+ # =============================================================================
67
+
68
+
69
+ app = typer.Typer(
70
+ name="orchestrate",
71
+ help="""
72
+ Orchestrate autonomous feature implementation using multiple AI agents.
73
+
74
+ This command coordinates multiple AI coding agents to implement work
75
+ packages in parallel, with automatic review, retry, and fallback handling.
76
+
77
+ \b
78
+ USAGE EXAMPLES:
79
+ spec-kitty orchestrate --feature 020-my-feature
80
+ spec-kitty orchestrate --status
81
+ spec-kitty orchestrate --resume
82
+ spec-kitty orchestrate --abort
83
+
84
+ \b
85
+ WORKFLOW:
86
+ 1. Plan feature with tasks.md and work packages
87
+ 2. Configure agents in .kittify/agents.yaml (or use auto-detected defaults)
88
+ 3. Run: spec-kitty orchestrate --feature <slug>
89
+ 4. Monitor progress: spec-kitty orchestrate --status
90
+ 5. If paused due to failure: fix issue and --resume
91
+ """,
92
+ no_args_is_help=True,
93
+ )
94
+
95
+
96
+ # =============================================================================
97
+ # Helper Functions
98
+ # =============================================================================
99
+
100
+
101
+ def detect_current_feature() -> str | None:
102
+ """Auto-detect feature slug from current directory.
103
+
104
+ Checks if current directory is inside a feature worktree
105
+ and extracts the feature slug.
106
+
107
+ Returns:
108
+ Feature slug or None if not detected.
109
+ """
110
+ cwd = Path.cwd()
111
+
112
+ # Check if we're in a worktree
113
+ if ".worktrees" in str(cwd):
114
+ # Pattern: .worktrees/###-feature-WP##
115
+ parts = cwd.parts
116
+ for i, part in enumerate(parts):
117
+ if part == ".worktrees" and i + 1 < len(parts):
118
+ worktree_name = parts[i + 1]
119
+ # Extract feature slug (remove WP## suffix if present)
120
+ if "-WP" in worktree_name:
121
+ return worktree_name.rsplit("-WP", 1)[0]
122
+ return worktree_name
123
+
124
+ # Check kitty-specs directory
125
+ kitty_specs = cwd / "kitty-specs"
126
+ if not kitty_specs.exists():
127
+ # Try parent
128
+ project_root = get_project_root_or_exit(cwd)
129
+ kitty_specs = project_root / "kitty-specs"
130
+
131
+ if kitty_specs.exists():
132
+ # Look for most recently modified feature
133
+ features = [
134
+ d for d in kitty_specs.iterdir()
135
+ if d.is_dir() and not d.name.startswith(".")
136
+ ]
137
+ if features:
138
+ # Return most recent
139
+ features.sort(key=lambda x: x.stat().st_mtime, reverse=True)
140
+ return features[0].name
141
+
142
+ return None
143
+
144
+
145
+ def format_elapsed(seconds: float) -> str:
146
+ """Format elapsed time in human-readable format."""
147
+ if seconds < 60:
148
+ return f"{int(seconds)}s"
149
+ minutes = int(seconds // 60)
150
+ secs = int(seconds % 60)
151
+ if minutes < 60:
152
+ return f"{minutes}m {secs}s"
153
+ hours = minutes // 60
154
+ mins = minutes % 60
155
+ return f"{hours}h {mins}m"
156
+
157
+
158
+ # =============================================================================
159
+ # Status Display (T039)
160
+ # =============================================================================
161
+
162
+
163
+ def show_status(repo_root: Path | None = None) -> None:
164
+ """Display current orchestration status.
165
+
166
+ Shows progress, active WPs, and agent assignments.
167
+ """
168
+ if repo_root is None:
169
+ repo_root = get_project_root_or_exit()
170
+
171
+ state = load_state(repo_root)
172
+
173
+ if state is None:
174
+ console.print("[yellow]No orchestration in progress[/yellow]")
175
+ console.print("\nStart with: spec-kitty orchestrate --feature <slug>")
176
+ return
177
+
178
+ # Calculate stats
179
+ total = state.wps_total
180
+ completed = state.wps_completed
181
+ failed = state.wps_failed
182
+ pending = total - completed - failed
183
+
184
+ progress_pct = (completed / total * 100) if total > 0 else 0
185
+
186
+ # Create progress bar
187
+ filled = int(progress_pct / 5) # 20 chars total
188
+ bar = "[green]" + "█" * filled + "[/green]" + "░" * (20 - filled)
189
+
190
+ # Status color
191
+ status_color = {
192
+ OrchestrationStatus.PENDING: "yellow",
193
+ OrchestrationStatus.RUNNING: "green",
194
+ OrchestrationStatus.PAUSED: "red",
195
+ OrchestrationStatus.COMPLETED: "bright_green",
196
+ OrchestrationStatus.FAILED: "red",
197
+ }.get(state.status, "white")
198
+
199
+ # Calculate elapsed time
200
+ elapsed = (datetime.now(timezone.utc) - state.started_at).total_seconds()
201
+
202
+ # Print header
203
+ console.print()
204
+ console.print(Panel(
205
+ f"[bold]Feature:[/bold] {state.feature_slug}\n"
206
+ f"[bold]Status:[/bold] [{status_color}]{state.status.value}[/{status_color}]\n"
207
+ f"[bold]Progress:[/bold] {bar} {completed}/{total} ({progress_pct:.1f}%)\n"
208
+ f"[bold]Elapsed:[/bold] {format_elapsed(elapsed)}",
209
+ title="Orchestration Status",
210
+ border_style="blue",
211
+ ))
212
+
213
+ # Show active WPs
214
+ active_wps = [
215
+ (wp_id, wp)
216
+ for wp_id, wp in state.work_packages.items()
217
+ if wp.status in [WPStatus.IMPLEMENTATION, WPStatus.REVIEW]
218
+ ]
219
+
220
+ if active_wps:
221
+ console.print("\n[bold]Active Work Packages:[/bold]")
222
+ table = Table(show_header=True, header_style="bold")
223
+ table.add_column("WP")
224
+ table.add_column("Phase")
225
+ table.add_column("Agent")
226
+ table.add_column("Elapsed")
227
+
228
+ for wp_id, wp in active_wps:
229
+ if wp.status == WPStatus.IMPLEMENTATION:
230
+ phase = "implementation"
231
+ agent = wp.implementation_agent or "?"
232
+ started = wp.implementation_started
233
+ else:
234
+ phase = "review"
235
+ agent = wp.review_agent or "?"
236
+ started = wp.review_started
237
+
238
+ if started:
239
+ wp_elapsed = (datetime.now(timezone.utc) - started).total_seconds()
240
+ elapsed_str = format_elapsed(wp_elapsed)
241
+ else:
242
+ elapsed_str = "-"
243
+
244
+ table.add_row(wp_id, phase, agent, elapsed_str)
245
+
246
+ console.print(table)
247
+
248
+ # Show completed
249
+ completed_wps = [
250
+ wp_id for wp_id, wp in state.work_packages.items()
251
+ if wp.status == WPStatus.COMPLETED
252
+ ]
253
+ if completed_wps:
254
+ console.print(f"\n[green]Completed:[/green] {', '.join(sorted(completed_wps))}")
255
+
256
+ # Show failed
257
+ failed_wps = [
258
+ wp_id for wp_id, wp in state.work_packages.items()
259
+ if wp.status == WPStatus.FAILED
260
+ ]
261
+ if failed_wps:
262
+ console.print(f"[red]Failed:[/red] {', '.join(sorted(failed_wps))}")
263
+
264
+ # Show pending
265
+ pending_wps = [
266
+ wp_id for wp_id, wp in state.work_packages.items()
267
+ if wp.status in [WPStatus.PENDING, WPStatus.READY]
268
+ ]
269
+ if pending_wps:
270
+ console.print(f"[yellow]Pending:[/yellow] {', '.join(sorted(pending_wps))}")
271
+
272
+ # Show hint for paused state
273
+ if state.status == OrchestrationStatus.PAUSED:
274
+ console.print()
275
+ console.print("[bold red]Orchestration is paused.[/bold red]")
276
+ console.print("Fix any issues and run: spec-kitty orchestrate --resume")
277
+
278
+ console.print()
279
+
280
+
281
+ # =============================================================================
282
+ # Start Orchestration (T038)
283
+ # =============================================================================
284
+
285
+
286
+ async def start_orchestration_async(
287
+ feature_slug: str,
288
+ repo_root: Path,
289
+ impl_agent: str | None = None,
290
+ review_agent: str | None = None,
291
+ ) -> None:
292
+ """Start new orchestration for a feature (async implementation)."""
293
+ feature_dir = repo_root / "kitty-specs" / feature_slug
294
+
295
+ # Validate feature and build graph (T046 - edge case handling)
296
+ console.print(f"Validating feature [bold]{feature_slug}[/bold]...")
297
+ try:
298
+ graph = validate_feature(feature_dir)
299
+ except CircularDependencyError as e:
300
+ console.print(f"[red]Error: Circular dependency detected[/red]")
301
+ console.print(str(e))
302
+ console.print("\nFix the dependency cycle in your tasks.md and WP frontmatter.")
303
+ raise typer.Exit(1)
304
+ except ValidationError as e:
305
+ console.print(f"[red]Error:[/red] {e}")
306
+ raise typer.Exit(1)
307
+
308
+ # Check for existing orchestration
309
+ if has_active_orchestration(repo_root):
310
+ console.print("[red]Error:[/red] An orchestration is already in progress.")
311
+ console.print("Use --status to check progress, --resume to continue, or --abort to cancel.")
312
+ raise typer.Exit(1)
313
+
314
+ # Load and validate config
315
+ config_path = repo_root / ".kittify" / "agents.yaml"
316
+ try:
317
+ config = load_config(config_path)
318
+ except Exception as e:
319
+ console.print(f"[red]Error loading config:[/red] {e}")
320
+ console.print("\nCreate config with: spec-kitty agent config init")
321
+ raise typer.Exit(1)
322
+
323
+ # Validate agents are available (T046 - no agents installed)
324
+ try:
325
+ available_agents = validate_agents(config)
326
+ console.print(f"Available agents: {', '.join(available_agents)}")
327
+ except NoAgentsError as e:
328
+ console.print(f"[red]Error:[/red] {e}")
329
+ raise typer.Exit(1)
330
+
331
+ # Get topological order
332
+ wp_order = get_topological_order(graph)
333
+ console.print(f"Work packages: {', '.join(wp_order)}")
334
+
335
+ # Initialize state
336
+ state = OrchestrationRun(
337
+ run_id=str(uuid.uuid4()),
338
+ feature_slug=feature_slug,
339
+ started_at=datetime.now(timezone.utc),
340
+ status=OrchestrationStatus.PENDING,
341
+ config_hash="", # TODO: compute hash
342
+ concurrency_limit=config.global_concurrency,
343
+ wps_total=len(wp_order),
344
+ work_packages={
345
+ wp_id: WPExecution(wp_id=wp_id, status=WPStatus.PENDING)
346
+ for wp_id in wp_order
347
+ },
348
+ )
349
+
350
+ # Save initial state
351
+ save_state(state, repo_root)
352
+
353
+ console.print()
354
+ console.print(Panel(
355
+ f"Starting orchestration for [bold]{feature_slug}[/bold]\n\n"
356
+ f"Work packages: {len(wp_order)}\n"
357
+ f"Concurrency: {config.global_concurrency}\n"
358
+ f"Agents: {', '.join(available_agents)}",
359
+ title="Orchestration Started",
360
+ border_style="green",
361
+ ))
362
+
363
+ # Run the full orchestration loop (T043)
364
+ await run_orchestration_loop(
365
+ state, config, feature_dir, repo_root, console,
366
+ override_impl_agent=impl_agent,
367
+ override_review_agent=review_agent,
368
+ )
369
+
370
+
371
+ def start_orchestration(
372
+ feature_slug: str,
373
+ impl_agent: str | None = None,
374
+ review_agent: str | None = None,
375
+ ) -> None:
376
+ """Start new orchestration for a feature."""
377
+ repo_root = get_project_root_or_exit()
378
+ asyncio.run(start_orchestration_async(feature_slug, repo_root, impl_agent, review_agent))
379
+
380
+
381
+ # =============================================================================
382
+ # Resume Orchestration (T040)
383
+ # =============================================================================
384
+
385
+
386
+ async def resume_orchestration_async(
387
+ repo_root: Path,
388
+ impl_agent: str | None = None,
389
+ review_agent: str | None = None,
390
+ ) -> None:
391
+ """Resume paused orchestration (async implementation)."""
392
+ state = load_state(repo_root)
393
+
394
+ if state is None:
395
+ console.print("[red]Error:[/red] No orchestration to resume.")
396
+ console.print("Start with: spec-kitty orchestrate --feature <slug>")
397
+ raise typer.Exit(1)
398
+
399
+ if state.status == OrchestrationStatus.COMPLETED:
400
+ console.print("[green]Orchestration already completed.[/green]")
401
+ return
402
+
403
+ if state.status == OrchestrationStatus.RUNNING:
404
+ console.print("[yellow]Orchestration is already running.[/yellow]")
405
+ console.print("Use --status to check progress.")
406
+ return
407
+
408
+ # Set to running
409
+ state.status = OrchestrationStatus.RUNNING
410
+ save_state(state, repo_root)
411
+
412
+ # Load config
413
+ config_path = repo_root / ".kittify" / "agents.yaml"
414
+ config = load_config(config_path)
415
+
416
+ # Get feature directory
417
+ feature_dir = repo_root / "kitty-specs" / state.feature_slug
418
+
419
+ console.print(f"Resuming orchestration for [bold]{state.feature_slug}[/bold]...")
420
+ console.print(f"Progress: {state.wps_completed}/{state.wps_total} completed")
421
+
422
+ # Continue orchestration loop with full integration
423
+ await run_orchestration_loop(
424
+ state, config, feature_dir, repo_root, console,
425
+ override_impl_agent=impl_agent,
426
+ override_review_agent=review_agent,
427
+ )
428
+
429
+
430
+ def resume_orchestration(
431
+ impl_agent: str | None = None,
432
+ review_agent: str | None = None,
433
+ ) -> None:
434
+ """Resume paused orchestration."""
435
+ repo_root = get_project_root_or_exit()
436
+ asyncio.run(resume_orchestration_async(repo_root, impl_agent, review_agent))
437
+
438
+
439
+ # =============================================================================
440
+ # Abort Orchestration (T041)
441
+ # =============================================================================
442
+
443
+
444
+ def abort_orchestration(cleanup: bool = False) -> None:
445
+ """Abort orchestration and optionally cleanup worktrees."""
446
+ repo_root = get_project_root_or_exit()
447
+ state = load_state(repo_root)
448
+
449
+ if state is None:
450
+ console.print("[yellow]No orchestration to abort.[/yellow]")
451
+ return
452
+
453
+ console.print(f"Aborting orchestration for [bold]{state.feature_slug}[/bold]...")
454
+
455
+ # Update state
456
+ state.status = OrchestrationStatus.FAILED
457
+ state.completed_at = datetime.now(timezone.utc)
458
+ save_state(state, repo_root)
459
+
460
+ # Ask about cleanup if not specified
461
+ if not cleanup:
462
+ cleanup = typer.confirm(
463
+ "Remove created worktrees?",
464
+ default=False,
465
+ )
466
+
467
+ if cleanup:
468
+ console.print("Cleaning up worktrees...")
469
+ for wp_id, wp in state.work_packages.items():
470
+ if wp.worktree_path and wp.worktree_path.exists():
471
+ try:
472
+ subprocess.run(
473
+ ["git", "worktree", "remove", str(wp.worktree_path), "--force"],
474
+ cwd=repo_root,
475
+ capture_output=True,
476
+ )
477
+ console.print(f" Removed: {wp.worktree_path.name}")
478
+ except Exception as e:
479
+ console.print(f" [yellow]Failed to remove {wp.worktree_path.name}: {e}[/yellow]")
480
+
481
+ # Clear state file
482
+ clear_state(repo_root)
483
+
484
+ console.print("[yellow]Orchestration aborted.[/yellow]")
485
+
486
+
487
+ # =============================================================================
488
+ # Skip WP (T041 extension)
489
+ # =============================================================================
490
+
491
+
492
+ def skip_wp(wp_id: str) -> None:
493
+ """Skip a failed WP and continue orchestration."""
494
+ repo_root = get_project_root_or_exit()
495
+ state = load_state(repo_root)
496
+
497
+ if state is None:
498
+ console.print("[red]Error:[/red] No orchestration in progress.")
499
+ raise typer.Exit(1)
500
+
501
+ if wp_id not in state.work_packages:
502
+ console.print(f"[red]Error:[/red] Unknown work package: {wp_id}")
503
+ raise typer.Exit(1)
504
+
505
+ wp = state.work_packages[wp_id]
506
+ if wp.status != WPStatus.FAILED:
507
+ console.print(f"[yellow]WP {wp_id} is not failed (status: {wp.status.value})[/yellow]")
508
+ return
509
+
510
+ # Mark as completed (skipped)
511
+ wp.status = WPStatus.COMPLETED
512
+ state.wps_failed -= 1
513
+ state.wps_completed += 1
514
+ save_state(state, repo_root)
515
+
516
+ console.print(f"[yellow]Skipped {wp_id}[/yellow]")
517
+ console.print("Use --resume to continue orchestration.")
518
+
519
+
520
+ # =============================================================================
521
+ # Main Command (T042)
522
+ # =============================================================================
523
+
524
+
525
+ @app.callback(invoke_without_command=True)
526
+ def orchestrate(
527
+ ctx: typer.Context,
528
+ feature: str = typer.Option(
529
+ None,
530
+ "--feature",
531
+ "-f",
532
+ help="Feature slug to orchestrate (e.g., 020-my-feature)",
533
+ ),
534
+ status: bool = typer.Option(
535
+ False,
536
+ "--status",
537
+ "-s",
538
+ help="Show current orchestration status and progress",
539
+ ),
540
+ resume: bool = typer.Option(
541
+ False,
542
+ "--resume",
543
+ "-r",
544
+ help="Resume a paused orchestration",
545
+ ),
546
+ abort: bool = typer.Option(
547
+ False,
548
+ "--abort",
549
+ "-a",
550
+ help="Abort orchestration and optionally cleanup worktrees",
551
+ ),
552
+ skip: str = typer.Option(
553
+ None,
554
+ "--skip",
555
+ help="Skip a failed WP and continue (e.g., --skip WP03)",
556
+ ),
557
+ cleanup: bool = typer.Option(
558
+ False,
559
+ "--cleanup",
560
+ help="Also remove worktrees when aborting",
561
+ ),
562
+ impl_agent: str = typer.Option(
563
+ None,
564
+ "--impl-agent",
565
+ "--implementer",
566
+ help="Override implementation agent (e.g., claude, codex, opencode)",
567
+ ),
568
+ review_agent: str = typer.Option(
569
+ None,
570
+ "--review-agent",
571
+ "--reviewer",
572
+ help="Override review agent (e.g., claude, codex, opencode)",
573
+ ),
574
+ ) -> None:
575
+ """Orchestrate autonomous feature implementation.
576
+
577
+ Coordinates multiple AI coding agents to implement work packages
578
+ in parallel, with automatic review, retry, and fallback handling.
579
+
580
+ \b
581
+ EXAMPLES:
582
+ Start orchestration for a feature:
583
+ spec-kitty orchestrate --feature 020-my-feature
584
+
585
+ Check progress:
586
+ spec-kitty orchestrate --status
587
+
588
+ Resume after fixing an issue:
589
+ spec-kitty orchestrate --resume
590
+
591
+ Skip a problematic WP and continue:
592
+ spec-kitty orchestrate --skip WP03
593
+
594
+ Stop orchestration:
595
+ spec-kitty orchestrate --abort
596
+
597
+ Stop and remove worktrees:
598
+ spec-kitty orchestrate --abort --cleanup
599
+ """
600
+ # Handle mutual exclusivity
601
+ options_count = sum([bool(feature), status, resume, abort, bool(skip)])
602
+
603
+ if options_count == 0:
604
+ # Auto-detect feature
605
+ detected = detect_current_feature()
606
+ if detected:
607
+ if typer.confirm(f"Start orchestration for {detected}?"):
608
+ start_orchestration(detected, impl_agent=impl_agent, review_agent=review_agent)
609
+ return
610
+ console.print("[red]Error:[/red] No feature specified.")
611
+ console.print("Use: spec-kitty orchestrate --feature <slug>")
612
+ console.print("Or check status: spec-kitty orchestrate --status")
613
+ raise typer.Exit(1)
614
+
615
+ if options_count > 1:
616
+ console.print("[red]Error:[/red] Only one of --feature, --status, --resume, --abort, --skip can be used.")
617
+ raise typer.Exit(1)
618
+
619
+ # Dispatch to appropriate handler
620
+ if status:
621
+ show_status()
622
+ elif resume:
623
+ resume_orchestration(impl_agent=impl_agent, review_agent=review_agent)
624
+ elif abort:
625
+ abort_orchestration(cleanup=cleanup)
626
+ elif skip:
627
+ skip_wp(skip)
628
+ elif feature:
629
+ start_orchestration(feature, impl_agent=impl_agent, review_agent=review_agent)
630
+
631
+
632
+ __all__ = [
633
+ "app",
634
+ "orchestrate",
635
+ "show_status",
636
+ "start_orchestration",
637
+ "resume_orchestration",
638
+ "abort_orchestration",
639
+ "skip_wp",
640
+ ]