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.
- spec_kitty_cli-0.12.1.dist-info/METADATA +1767 -0
- spec_kitty_cli-0.12.1.dist-info/RECORD +242 -0
- spec_kitty_cli-0.12.1.dist-info/WHEEL +4 -0
- spec_kitty_cli-0.12.1.dist-info/entry_points.txt +2 -0
- spec_kitty_cli-0.12.1.dist-info/licenses/LICENSE +21 -0
- specify_cli/__init__.py +171 -0
- specify_cli/acceptance.py +627 -0
- specify_cli/agent_utils/README.md +157 -0
- specify_cli/agent_utils/__init__.py +9 -0
- specify_cli/agent_utils/status.py +356 -0
- specify_cli/cli/__init__.py +6 -0
- specify_cli/cli/commands/__init__.py +46 -0
- specify_cli/cli/commands/accept.py +189 -0
- specify_cli/cli/commands/agent/__init__.py +22 -0
- specify_cli/cli/commands/agent/config.py +382 -0
- specify_cli/cli/commands/agent/context.py +191 -0
- specify_cli/cli/commands/agent/feature.py +1057 -0
- specify_cli/cli/commands/agent/release.py +11 -0
- specify_cli/cli/commands/agent/tasks.py +1253 -0
- specify_cli/cli/commands/agent/workflow.py +801 -0
- specify_cli/cli/commands/context.py +246 -0
- specify_cli/cli/commands/dashboard.py +85 -0
- specify_cli/cli/commands/implement.py +973 -0
- specify_cli/cli/commands/init.py +827 -0
- specify_cli/cli/commands/init_help.py +62 -0
- specify_cli/cli/commands/merge.py +755 -0
- specify_cli/cli/commands/mission.py +240 -0
- specify_cli/cli/commands/ops.py +265 -0
- specify_cli/cli/commands/orchestrate.py +640 -0
- specify_cli/cli/commands/repair.py +175 -0
- specify_cli/cli/commands/research.py +165 -0
- specify_cli/cli/commands/sync.py +364 -0
- specify_cli/cli/commands/upgrade.py +249 -0
- specify_cli/cli/commands/validate_encoding.py +186 -0
- specify_cli/cli/commands/validate_tasks.py +186 -0
- specify_cli/cli/commands/verify.py +310 -0
- specify_cli/cli/helpers.py +123 -0
- specify_cli/cli/step_tracker.py +91 -0
- specify_cli/cli/ui.py +192 -0
- specify_cli/core/__init__.py +53 -0
- specify_cli/core/agent_context.py +311 -0
- specify_cli/core/config.py +96 -0
- specify_cli/core/context_validation.py +362 -0
- specify_cli/core/dependency_graph.py +351 -0
- specify_cli/core/git_ops.py +129 -0
- specify_cli/core/multi_parent_merge.py +323 -0
- specify_cli/core/paths.py +260 -0
- specify_cli/core/project_resolver.py +110 -0
- specify_cli/core/stale_detection.py +263 -0
- specify_cli/core/tool_checker.py +79 -0
- specify_cli/core/utils.py +43 -0
- specify_cli/core/vcs/__init__.py +114 -0
- specify_cli/core/vcs/detection.py +341 -0
- specify_cli/core/vcs/exceptions.py +85 -0
- specify_cli/core/vcs/git.py +1304 -0
- specify_cli/core/vcs/jujutsu.py +1208 -0
- specify_cli/core/vcs/protocol.py +285 -0
- specify_cli/core/vcs/types.py +249 -0
- specify_cli/core/version_checker.py +261 -0
- specify_cli/core/worktree.py +506 -0
- specify_cli/dashboard/__init__.py +28 -0
- specify_cli/dashboard/diagnostics.py +204 -0
- specify_cli/dashboard/handlers/__init__.py +17 -0
- specify_cli/dashboard/handlers/api.py +143 -0
- specify_cli/dashboard/handlers/base.py +65 -0
- specify_cli/dashboard/handlers/features.py +390 -0
- specify_cli/dashboard/handlers/router.py +81 -0
- specify_cli/dashboard/handlers/static.py +50 -0
- specify_cli/dashboard/lifecycle.py +541 -0
- specify_cli/dashboard/scanner.py +437 -0
- specify_cli/dashboard/server.py +123 -0
- specify_cli/dashboard/static/dashboard/dashboard.css +722 -0
- specify_cli/dashboard/static/dashboard/dashboard.js +1424 -0
- specify_cli/dashboard/static/spec-kitty.png +0 -0
- specify_cli/dashboard/templates/__init__.py +36 -0
- specify_cli/dashboard/templates/index.html +258 -0
- specify_cli/doc_generators.py +621 -0
- specify_cli/doc_state.py +408 -0
- specify_cli/frontmatter.py +384 -0
- specify_cli/gap_analysis.py +915 -0
- specify_cli/gitignore_manager.py +300 -0
- specify_cli/guards.py +145 -0
- specify_cli/legacy_detector.py +83 -0
- specify_cli/manifest.py +286 -0
- specify_cli/merge/__init__.py +63 -0
- specify_cli/merge/executor.py +653 -0
- specify_cli/merge/forecast.py +215 -0
- specify_cli/merge/ordering.py +126 -0
- specify_cli/merge/preflight.py +230 -0
- specify_cli/merge/state.py +185 -0
- specify_cli/merge/status_resolver.py +354 -0
- specify_cli/mission.py +654 -0
- specify_cli/missions/documentation/command-templates/implement.md +309 -0
- specify_cli/missions/documentation/command-templates/plan.md +275 -0
- specify_cli/missions/documentation/command-templates/review.md +344 -0
- specify_cli/missions/documentation/command-templates/specify.md +206 -0
- specify_cli/missions/documentation/command-templates/tasks.md +189 -0
- specify_cli/missions/documentation/mission.yaml +113 -0
- specify_cli/missions/documentation/templates/divio/explanation-template.md +192 -0
- specify_cli/missions/documentation/templates/divio/howto-template.md +168 -0
- specify_cli/missions/documentation/templates/divio/reference-template.md +179 -0
- specify_cli/missions/documentation/templates/divio/tutorial-template.md +146 -0
- specify_cli/missions/documentation/templates/generators/jsdoc.json.template +18 -0
- specify_cli/missions/documentation/templates/generators/sphinx-conf.py.template +36 -0
- specify_cli/missions/documentation/templates/plan-template.md +269 -0
- specify_cli/missions/documentation/templates/release-template.md +222 -0
- specify_cli/missions/documentation/templates/spec-template.md +172 -0
- specify_cli/missions/documentation/templates/task-prompt-template.md +140 -0
- specify_cli/missions/documentation/templates/tasks-template.md +159 -0
- specify_cli/missions/research/command-templates/merge.md +388 -0
- specify_cli/missions/research/command-templates/plan.md +125 -0
- specify_cli/missions/research/command-templates/review.md +144 -0
- specify_cli/missions/research/command-templates/tasks.md +225 -0
- specify_cli/missions/research/mission.yaml +115 -0
- specify_cli/missions/research/templates/data-model-template.md +33 -0
- specify_cli/missions/research/templates/plan-template.md +161 -0
- specify_cli/missions/research/templates/research/evidence-log.csv +18 -0
- specify_cli/missions/research/templates/research/source-register.csv +18 -0
- specify_cli/missions/research/templates/research-template.md +35 -0
- specify_cli/missions/research/templates/spec-template.md +64 -0
- specify_cli/missions/research/templates/task-prompt-template.md +148 -0
- specify_cli/missions/research/templates/tasks-template.md +114 -0
- specify_cli/missions/software-dev/command-templates/accept.md +75 -0
- specify_cli/missions/software-dev/command-templates/analyze.md +183 -0
- specify_cli/missions/software-dev/command-templates/checklist.md +286 -0
- specify_cli/missions/software-dev/command-templates/clarify.md +157 -0
- specify_cli/missions/software-dev/command-templates/constitution.md +432 -0
- specify_cli/missions/software-dev/command-templates/dashboard.md +101 -0
- specify_cli/missions/software-dev/command-templates/implement.md +41 -0
- specify_cli/missions/software-dev/command-templates/merge.md +383 -0
- specify_cli/missions/software-dev/command-templates/plan.md +171 -0
- specify_cli/missions/software-dev/command-templates/review.md +32 -0
- specify_cli/missions/software-dev/command-templates/specify.md +321 -0
- specify_cli/missions/software-dev/command-templates/tasks.md +566 -0
- specify_cli/missions/software-dev/mission.yaml +100 -0
- specify_cli/missions/software-dev/templates/plan-template.md +132 -0
- specify_cli/missions/software-dev/templates/spec-template.md +116 -0
- specify_cli/missions/software-dev/templates/task-prompt-template.md +140 -0
- specify_cli/missions/software-dev/templates/tasks-template.md +159 -0
- specify_cli/orchestrator/__init__.py +75 -0
- specify_cli/orchestrator/agent_config.py +224 -0
- specify_cli/orchestrator/agents/__init__.py +170 -0
- specify_cli/orchestrator/agents/augment.py +112 -0
- specify_cli/orchestrator/agents/base.py +243 -0
- specify_cli/orchestrator/agents/claude.py +112 -0
- specify_cli/orchestrator/agents/codex.py +106 -0
- specify_cli/orchestrator/agents/copilot.py +137 -0
- specify_cli/orchestrator/agents/cursor.py +139 -0
- specify_cli/orchestrator/agents/gemini.py +115 -0
- specify_cli/orchestrator/agents/kilocode.py +94 -0
- specify_cli/orchestrator/agents/opencode.py +132 -0
- specify_cli/orchestrator/agents/qwen.py +96 -0
- specify_cli/orchestrator/config.py +455 -0
- specify_cli/orchestrator/executor.py +642 -0
- specify_cli/orchestrator/integration.py +1230 -0
- specify_cli/orchestrator/monitor.py +898 -0
- specify_cli/orchestrator/scheduler.py +832 -0
- specify_cli/orchestrator/state.py +508 -0
- specify_cli/orchestrator/testing/__init__.py +122 -0
- specify_cli/orchestrator/testing/availability.py +346 -0
- specify_cli/orchestrator/testing/fixtures.py +684 -0
- specify_cli/orchestrator/testing/paths.py +218 -0
- specify_cli/plan_validation.py +107 -0
- specify_cli/scripts/debug-dashboard-scan.py +61 -0
- specify_cli/scripts/tasks/acceptance_support.py +695 -0
- specify_cli/scripts/tasks/task_helpers.py +506 -0
- specify_cli/scripts/tasks/tasks_cli.py +848 -0
- specify_cli/scripts/validate_encoding.py +180 -0
- specify_cli/task_metadata_validation.py +274 -0
- specify_cli/tasks_support.py +447 -0
- specify_cli/template/__init__.py +47 -0
- specify_cli/template/asset_generator.py +206 -0
- specify_cli/template/github_client.py +334 -0
- specify_cli/template/manager.py +193 -0
- specify_cli/template/renderer.py +99 -0
- specify_cli/templates/AGENTS.md +190 -0
- specify_cli/templates/POWERSHELL_SYNTAX.md +229 -0
- specify_cli/templates/agent-file-template.md +35 -0
- specify_cli/templates/checklist-template.md +42 -0
- specify_cli/templates/claudeignore-template +58 -0
- specify_cli/templates/command-templates/accept.md +141 -0
- specify_cli/templates/command-templates/analyze.md +253 -0
- specify_cli/templates/command-templates/checklist.md +352 -0
- specify_cli/templates/command-templates/clarify.md +224 -0
- specify_cli/templates/command-templates/constitution.md +432 -0
- specify_cli/templates/command-templates/dashboard.md +175 -0
- specify_cli/templates/command-templates/implement.md +190 -0
- specify_cli/templates/command-templates/merge.md +374 -0
- specify_cli/templates/command-templates/plan.md +171 -0
- specify_cli/templates/command-templates/research.md +88 -0
- specify_cli/templates/command-templates/review.md +510 -0
- specify_cli/templates/command-templates/specify.md +321 -0
- specify_cli/templates/command-templates/status.md +92 -0
- specify_cli/templates/command-templates/tasks.md +199 -0
- specify_cli/templates/git-hooks/pre-commit +22 -0
- specify_cli/templates/git-hooks/pre-commit-agent-check +37 -0
- specify_cli/templates/git-hooks/pre-commit-encoding-check +142 -0
- specify_cli/templates/plan-template.md +108 -0
- specify_cli/templates/spec-template.md +118 -0
- specify_cli/templates/task-prompt-template.md +165 -0
- specify_cli/templates/tasks-template.md +161 -0
- specify_cli/templates/vscode-settings.json +13 -0
- specify_cli/text_sanitization.py +225 -0
- specify_cli/upgrade/__init__.py +18 -0
- specify_cli/upgrade/detector.py +239 -0
- specify_cli/upgrade/metadata.py +182 -0
- specify_cli/upgrade/migrations/__init__.py +65 -0
- specify_cli/upgrade/migrations/base.py +80 -0
- specify_cli/upgrade/migrations/m_0_10_0_python_only.py +359 -0
- specify_cli/upgrade/migrations/m_0_10_12_constitution_cleanup.py +99 -0
- specify_cli/upgrade/migrations/m_0_10_14_update_implement_slash_command.py +176 -0
- specify_cli/upgrade/migrations/m_0_10_1_populate_slash_commands.py +174 -0
- specify_cli/upgrade/migrations/m_0_10_2_update_slash_commands.py +172 -0
- specify_cli/upgrade/migrations/m_0_10_6_workflow_simplification.py +174 -0
- specify_cli/upgrade/migrations/m_0_10_8_fix_memory_structure.py +252 -0
- specify_cli/upgrade/migrations/m_0_10_9_repair_templates.py +168 -0
- specify_cli/upgrade/migrations/m_0_11_0_workspace_per_wp.py +182 -0
- specify_cli/upgrade/migrations/m_0_11_1_improved_workflow_templates.py +173 -0
- specify_cli/upgrade/migrations/m_0_11_1_update_implement_slash_command.py +160 -0
- specify_cli/upgrade/migrations/m_0_11_2_improved_workflow_templates.py +173 -0
- specify_cli/upgrade/migrations/m_0_11_3_workflow_agent_flag.py +114 -0
- specify_cli/upgrade/migrations/m_0_12_0_documentation_mission.py +155 -0
- specify_cli/upgrade/migrations/m_0_12_1_remove_kitty_specs_from_gitignore.py +183 -0
- specify_cli/upgrade/migrations/m_0_2_0_specify_to_kittify.py +80 -0
- specify_cli/upgrade/migrations/m_0_4_8_gitignore_agents.py +118 -0
- specify_cli/upgrade/migrations/m_0_5_0_encoding_hooks.py +141 -0
- specify_cli/upgrade/migrations/m_0_6_5_commands_rename.py +169 -0
- specify_cli/upgrade/migrations/m_0_6_7_ensure_missions.py +228 -0
- specify_cli/upgrade/migrations/m_0_7_2_worktree_commands_dedup.py +89 -0
- specify_cli/upgrade/migrations/m_0_7_3_update_scripts.py +114 -0
- specify_cli/upgrade/migrations/m_0_8_0_remove_active_mission.py +82 -0
- specify_cli/upgrade/migrations/m_0_8_0_worktree_agents_symlink.py +148 -0
- specify_cli/upgrade/migrations/m_0_9_0_frontmatter_only_lanes.py +346 -0
- specify_cli/upgrade/migrations/m_0_9_1_complete_lane_migration.py +656 -0
- specify_cli/upgrade/migrations/m_0_9_2_research_mission_templates.py +221 -0
- specify_cli/upgrade/registry.py +121 -0
- specify_cli/upgrade/runner.py +284 -0
- specify_cli/validators/__init__.py +14 -0
- specify_cli/validators/paths.py +154 -0
- specify_cli/validators/research.py +428 -0
- specify_cli/verify_enhanced.py +270 -0
- 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
|
+
]
|