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,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"]
|