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,801 @@
|
|
|
1
|
+
"""Workflow commands for AI agents - display prompts and instructions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import tempfile
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
from typing_extensions import Annotated
|
|
11
|
+
|
|
12
|
+
from specify_cli.core.paths import locate_project_root, find_feature_slug
|
|
13
|
+
from specify_cli.core.dependency_graph import build_dependency_graph, get_dependents
|
|
14
|
+
from specify_cli.tasks_support import (
|
|
15
|
+
extract_scalar,
|
|
16
|
+
locate_work_package,
|
|
17
|
+
split_frontmatter,
|
|
18
|
+
set_scalar,
|
|
19
|
+
append_activity_log,
|
|
20
|
+
build_document,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _write_prompt_to_file(
|
|
25
|
+
command_type: str,
|
|
26
|
+
wp_id: str,
|
|
27
|
+
content: str,
|
|
28
|
+
) -> Path:
|
|
29
|
+
"""Write full prompt content to a temp file for agents with output limits.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
command_type: "implement" or "review"
|
|
33
|
+
wp_id: Work package ID (e.g., "WP01")
|
|
34
|
+
content: Full prompt content to write
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Path to the written file
|
|
38
|
+
"""
|
|
39
|
+
# Use system temp directory (gets cleaned up automatically)
|
|
40
|
+
prompt_file = Path(tempfile.gettempdir()) / f"spec-kitty-{command_type}-{wp_id}.md"
|
|
41
|
+
prompt_file.write_text(content, encoding="utf-8")
|
|
42
|
+
return prompt_file
|
|
43
|
+
|
|
44
|
+
app = typer.Typer(
|
|
45
|
+
name="workflow",
|
|
46
|
+
help="Workflow commands that display prompts and instructions for agents",
|
|
47
|
+
no_args_is_help=True
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _find_feature_slug() -> str:
|
|
52
|
+
"""Find the current feature slug from the working directory or git branch.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Feature slug (e.g., "008-unified-python-cli")
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
typer.Exit: If feature slug cannot be determined
|
|
59
|
+
"""
|
|
60
|
+
cwd = Path.cwd().resolve()
|
|
61
|
+
repo_root = locate_project_root(cwd)
|
|
62
|
+
|
|
63
|
+
if repo_root is None:
|
|
64
|
+
print("Error: Not in a spec-kitty project.")
|
|
65
|
+
raise typer.Exit(1)
|
|
66
|
+
|
|
67
|
+
slug = find_feature_slug(repo_root)
|
|
68
|
+
if slug is None:
|
|
69
|
+
print("Error: Could not auto-detect feature slug.")
|
|
70
|
+
print(" - Not in a kitty-specs/###-feature-slug directory")
|
|
71
|
+
print(" - Git branch name doesn't match ###-slug format")
|
|
72
|
+
print(" - Use --feature <slug> to specify explicitly")
|
|
73
|
+
raise typer.Exit(1)
|
|
74
|
+
|
|
75
|
+
return slug
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _normalize_wp_id(wp_arg: str) -> str:
|
|
79
|
+
"""Normalize WP ID from various formats to standard WPxx format.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
wp_arg: User input (e.g., "wp01", "WP01", "WP01-foo-bar")
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Normalized WP ID (e.g., "WP01")
|
|
86
|
+
"""
|
|
87
|
+
# Handle formats: wp01 → WP01, WP01 → WP01, WP01-foo-bar → WP01
|
|
88
|
+
wp_upper = wp_arg.upper()
|
|
89
|
+
|
|
90
|
+
# Extract just the WPxx part
|
|
91
|
+
if wp_upper.startswith("WP"):
|
|
92
|
+
# Split on hyphen and take first part
|
|
93
|
+
return wp_upper.split("-")[0]
|
|
94
|
+
else:
|
|
95
|
+
# Assume it's like "01" or "1", prefix with WP
|
|
96
|
+
return f"WP{wp_upper.lstrip('WP')}"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _find_first_planned_wp(repo_root: Path, feature_slug: str) -> Optional[str]:
|
|
100
|
+
"""Find the first WP file with lane: "planned".
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
repo_root: Repository root path
|
|
104
|
+
feature_slug: Feature slug
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
WP ID of first planned task, or None if not found
|
|
108
|
+
"""
|
|
109
|
+
from specify_cli.core.paths import is_worktree_context
|
|
110
|
+
|
|
111
|
+
cwd = Path.cwd().resolve()
|
|
112
|
+
|
|
113
|
+
# Check if we're in a worktree - if so, use worktree's kitty-specs
|
|
114
|
+
if is_worktree_context(cwd):
|
|
115
|
+
# We're in a worktree, look for kitty-specs relative to cwd
|
|
116
|
+
if (cwd / "kitty-specs" / feature_slug).exists():
|
|
117
|
+
tasks_dir = cwd / "kitty-specs" / feature_slug / "tasks"
|
|
118
|
+
else:
|
|
119
|
+
# Walk up to find kitty-specs
|
|
120
|
+
current = cwd
|
|
121
|
+
while current != current.parent:
|
|
122
|
+
if (current / "kitty-specs" / feature_slug).exists():
|
|
123
|
+
tasks_dir = current / "kitty-specs" / feature_slug / "tasks"
|
|
124
|
+
break
|
|
125
|
+
current = current.parent
|
|
126
|
+
else:
|
|
127
|
+
# Fallback to repo_root
|
|
128
|
+
tasks_dir = repo_root / "kitty-specs" / feature_slug / "tasks"
|
|
129
|
+
else:
|
|
130
|
+
# We're in main repo
|
|
131
|
+
tasks_dir = repo_root / "kitty-specs" / feature_slug / "tasks"
|
|
132
|
+
|
|
133
|
+
if not tasks_dir.exists():
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
# Find all WP files
|
|
137
|
+
wp_files = sorted(tasks_dir.glob("WP*.md"))
|
|
138
|
+
|
|
139
|
+
for wp_file in wp_files:
|
|
140
|
+
content = wp_file.read_text(encoding="utf-8-sig")
|
|
141
|
+
frontmatter, _, _ = split_frontmatter(content)
|
|
142
|
+
lane = extract_scalar(frontmatter, "lane")
|
|
143
|
+
|
|
144
|
+
if lane == "planned":
|
|
145
|
+
wp_id = extract_scalar(frontmatter, "work_package_id")
|
|
146
|
+
if wp_id:
|
|
147
|
+
return wp_id
|
|
148
|
+
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@app.command(name="implement")
|
|
153
|
+
def implement(
|
|
154
|
+
wp_id: Annotated[Optional[str], typer.Argument(help="Work package ID (e.g., WP01, wp01, WP01-slug) - auto-detects first planned if omitted")] = None,
|
|
155
|
+
feature: Annotated[Optional[str], typer.Option("--feature", help="Feature slug (auto-detected if omitted)")] = None,
|
|
156
|
+
agent: Annotated[Optional[str], typer.Option("--agent", help="Agent name (required for auto-move to doing lane)")] = None,
|
|
157
|
+
) -> None:
|
|
158
|
+
"""Display work package prompt with implementation instructions.
|
|
159
|
+
|
|
160
|
+
This command outputs the full work package prompt content so agents can
|
|
161
|
+
immediately see what to implement, without navigating the file system.
|
|
162
|
+
|
|
163
|
+
Automatically moves WP from planned to doing lane (requires --agent to track who is working).
|
|
164
|
+
|
|
165
|
+
Examples:
|
|
166
|
+
spec-kitty agent workflow implement WP01 --agent claude
|
|
167
|
+
spec-kitty agent workflow implement wp01 --agent codex
|
|
168
|
+
spec-kitty agent workflow implement --agent gemini # auto-detects first planned WP
|
|
169
|
+
"""
|
|
170
|
+
try:
|
|
171
|
+
# Get repo root and feature slug
|
|
172
|
+
repo_root = locate_project_root()
|
|
173
|
+
if repo_root is None:
|
|
174
|
+
print("Error: Could not locate project root")
|
|
175
|
+
raise typer.Exit(1)
|
|
176
|
+
|
|
177
|
+
feature_slug = feature or _find_feature_slug()
|
|
178
|
+
|
|
179
|
+
# Determine which WP to implement
|
|
180
|
+
if wp_id:
|
|
181
|
+
normalized_wp_id = _normalize_wp_id(wp_id)
|
|
182
|
+
else:
|
|
183
|
+
# Auto-detect first planned WP
|
|
184
|
+
normalized_wp_id = _find_first_planned_wp(repo_root, feature_slug)
|
|
185
|
+
if not normalized_wp_id:
|
|
186
|
+
print("Error: No planned work packages found. Specify a WP ID explicitly.")
|
|
187
|
+
raise typer.Exit(1)
|
|
188
|
+
|
|
189
|
+
# Load work package
|
|
190
|
+
wp = locate_work_package(repo_root, feature_slug, normalized_wp_id)
|
|
191
|
+
|
|
192
|
+
# Move to "doing" lane if not already there
|
|
193
|
+
current_lane = extract_scalar(wp.frontmatter, "lane") or "planned"
|
|
194
|
+
if current_lane != "doing":
|
|
195
|
+
# Require --agent parameter to track who is working
|
|
196
|
+
if not agent:
|
|
197
|
+
print("Error: --agent parameter required when starting implementation.")
|
|
198
|
+
print(f" Usage: spec-kitty agent workflow implement {normalized_wp_id} --agent <your-name>")
|
|
199
|
+
print(" Example: spec-kitty agent workflow implement WP01 --agent claude")
|
|
200
|
+
print()
|
|
201
|
+
print("If you're using a generated agent command file, --agent is already included.")
|
|
202
|
+
print("This tracks WHO is working on the WP (prevents abandoned tasks).")
|
|
203
|
+
raise typer.Exit(1)
|
|
204
|
+
|
|
205
|
+
from datetime import datetime, timezone
|
|
206
|
+
import os
|
|
207
|
+
|
|
208
|
+
# Capture current shell PID
|
|
209
|
+
shell_pid = str(os.getppid()) # Parent process ID (the shell running this command)
|
|
210
|
+
|
|
211
|
+
# Update lane, agent, and shell_pid in frontmatter
|
|
212
|
+
updated_front = set_scalar(wp.frontmatter, "lane", "doing")
|
|
213
|
+
updated_front = set_scalar(updated_front, "agent", agent)
|
|
214
|
+
updated_front = set_scalar(updated_front, "shell_pid", shell_pid)
|
|
215
|
+
|
|
216
|
+
# Build history entry
|
|
217
|
+
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
218
|
+
history_entry = f"- {timestamp} – {agent} – shell_pid={shell_pid} – lane=doing – Started implementation via workflow command"
|
|
219
|
+
|
|
220
|
+
# Add history entry to body
|
|
221
|
+
updated_body = append_activity_log(wp.body, history_entry)
|
|
222
|
+
|
|
223
|
+
# Build and write updated document
|
|
224
|
+
updated_doc = build_document(updated_front, updated_body, wp.padding)
|
|
225
|
+
wp.path.write_text(updated_doc, encoding="utf-8")
|
|
226
|
+
|
|
227
|
+
# Auto-commit to main (enables instant status sync)
|
|
228
|
+
import subprocess
|
|
229
|
+
|
|
230
|
+
# Get main repo root (might be in worktree)
|
|
231
|
+
git_file = Path.cwd() / ".git"
|
|
232
|
+
if git_file.is_file():
|
|
233
|
+
git_content = git_file.read_text().strip()
|
|
234
|
+
if git_content.startswith("gitdir:"):
|
|
235
|
+
gitdir = Path(git_content.split(":", 1)[1].strip())
|
|
236
|
+
main_repo_root = gitdir.parent.parent.parent
|
|
237
|
+
else:
|
|
238
|
+
main_repo_root = repo_root
|
|
239
|
+
else:
|
|
240
|
+
main_repo_root = repo_root
|
|
241
|
+
|
|
242
|
+
actual_wp_path = wp.path.resolve()
|
|
243
|
+
commit_result = subprocess.run(
|
|
244
|
+
["git", "commit", str(actual_wp_path), "-m", f"chore: Start {normalized_wp_id} implementation [{agent}]"],
|
|
245
|
+
cwd=main_repo_root,
|
|
246
|
+
capture_output=True,
|
|
247
|
+
text=True,
|
|
248
|
+
check=False
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
if commit_result.returncode == 0:
|
|
252
|
+
print(f"✓ Claimed {normalized_wp_id} (agent: {agent}, PID: {shell_pid})")
|
|
253
|
+
else:
|
|
254
|
+
# Commit failed - file might already be committed in this state
|
|
255
|
+
pass
|
|
256
|
+
|
|
257
|
+
# Reload to get updated content
|
|
258
|
+
wp = locate_work_package(repo_root, feature_slug, normalized_wp_id)
|
|
259
|
+
else:
|
|
260
|
+
print(f"⚠️ {normalized_wp_id} is already in lane: {current_lane}. Workflow implement will not move it to doing.")
|
|
261
|
+
|
|
262
|
+
# Check review status
|
|
263
|
+
review_status = extract_scalar(wp.frontmatter, "review_status")
|
|
264
|
+
has_feedback = review_status == "has_feedback"
|
|
265
|
+
|
|
266
|
+
# Calculate workspace path
|
|
267
|
+
workspace_name = f"{feature_slug}-{normalized_wp_id}"
|
|
268
|
+
workspace_path = repo_root / ".worktrees" / workspace_name
|
|
269
|
+
|
|
270
|
+
# Ensure workspace exists (create if needed)
|
|
271
|
+
if not workspace_path.exists():
|
|
272
|
+
import subprocess
|
|
273
|
+
|
|
274
|
+
# Ensure .worktrees directory exists
|
|
275
|
+
worktrees_dir = repo_root / ".worktrees"
|
|
276
|
+
worktrees_dir.mkdir(parents=True, exist_ok=True)
|
|
277
|
+
|
|
278
|
+
# Create worktree with sparse-checkout
|
|
279
|
+
branch_name = workspace_name
|
|
280
|
+
result = subprocess.run(
|
|
281
|
+
["git", "worktree", "add", str(workspace_path), "-b", branch_name],
|
|
282
|
+
cwd=repo_root,
|
|
283
|
+
capture_output=True,
|
|
284
|
+
text=True,
|
|
285
|
+
check=False
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
if result.returncode != 0:
|
|
289
|
+
print(f"Warning: Could not create workspace: {result.stderr}")
|
|
290
|
+
else:
|
|
291
|
+
# Configure sparse-checkout to exclude kitty-specs/
|
|
292
|
+
sparse_checkout_result = subprocess.run(
|
|
293
|
+
["git", "rev-parse", "--git-path", "info/sparse-checkout"],
|
|
294
|
+
cwd=workspace_path,
|
|
295
|
+
capture_output=True,
|
|
296
|
+
text=True,
|
|
297
|
+
check=False
|
|
298
|
+
)
|
|
299
|
+
if sparse_checkout_result.returncode == 0:
|
|
300
|
+
sparse_checkout_file = Path(sparse_checkout_result.stdout.strip())
|
|
301
|
+
subprocess.run(["git", "config", "core.sparseCheckout", "true"], cwd=workspace_path, capture_output=True, check=False)
|
|
302
|
+
subprocess.run(["git", "config", "core.sparseCheckoutCone", "false"], cwd=workspace_path, capture_output=True, check=False)
|
|
303
|
+
sparse_checkout_file.parent.mkdir(parents=True, exist_ok=True)
|
|
304
|
+
sparse_checkout_file.write_text("/*\n!/kitty-specs/\n!/kitty-specs/**\n", encoding="utf-8")
|
|
305
|
+
subprocess.run(["git", "read-tree", "-mu", "HEAD"], cwd=workspace_path, capture_output=True, check=False)
|
|
306
|
+
|
|
307
|
+
# Add .gitignore to block WP status files but allow research artifacts
|
|
308
|
+
gitignore_path = workspace_path / ".gitignore"
|
|
309
|
+
gitignore_entry = "# Block WP status files (managed in main repo, prevents merge conflicts)\n# Research artifacts in kitty-specs/**/research/ are allowed\nkitty-specs/**/tasks/*.md\n"
|
|
310
|
+
if gitignore_path.exists():
|
|
311
|
+
content = gitignore_path.read_text(encoding="utf-8")
|
|
312
|
+
if "kitty-specs/**/tasks/*.md" not in content:
|
|
313
|
+
# Remove old blanket rule if present
|
|
314
|
+
if "kitty-specs/\n" in content:
|
|
315
|
+
content = content.replace("# Prevent worktree-local kitty-specs/ (status managed in main repo)\nkitty-specs/\n", "")
|
|
316
|
+
content = content.replace("kitty-specs/\n", "")
|
|
317
|
+
gitignore_path.write_text(content.rstrip() + "\n" + gitignore_entry, encoding="utf-8")
|
|
318
|
+
else:
|
|
319
|
+
gitignore_path.write_text(gitignore_entry, encoding="utf-8")
|
|
320
|
+
|
|
321
|
+
print(f"✓ Created workspace: {workspace_path}")
|
|
322
|
+
|
|
323
|
+
# Build full prompt content for file
|
|
324
|
+
lines = []
|
|
325
|
+
lines.append("=" * 80)
|
|
326
|
+
lines.append(f"IMPLEMENT: {normalized_wp_id}")
|
|
327
|
+
lines.append("=" * 80)
|
|
328
|
+
lines.append("")
|
|
329
|
+
lines.append(f"Source: {wp.path}")
|
|
330
|
+
lines.append("")
|
|
331
|
+
lines.append(f"Workspace: {workspace_path}")
|
|
332
|
+
lines.append("")
|
|
333
|
+
|
|
334
|
+
# CRITICAL: WP isolation rules
|
|
335
|
+
lines.append("╔" + "=" * 78 + "╗")
|
|
336
|
+
lines.append("║ 🚨 CRITICAL: WORK PACKAGE ISOLATION RULES ║")
|
|
337
|
+
lines.append("╠" + "=" * 78 + "╣")
|
|
338
|
+
lines.append(f"║ YOU ARE ASSIGNED TO: {normalized_wp_id:<55} ║")
|
|
339
|
+
lines.append("║ ║")
|
|
340
|
+
lines.append("║ ✅ DO: ║")
|
|
341
|
+
lines.append(f"║ • Only modify status of {normalized_wp_id:<47} ║")
|
|
342
|
+
lines.append(f"║ • Only mark subtasks belonging to {normalized_wp_id:<36} ║")
|
|
343
|
+
lines.append("║ • Ignore git commits and status changes from other agents ║")
|
|
344
|
+
lines.append("║ ║")
|
|
345
|
+
lines.append("║ ❌ DO NOT: ║")
|
|
346
|
+
lines.append(f"║ • Change status of any WP other than {normalized_wp_id:<34} ║")
|
|
347
|
+
lines.append("║ • React to or investigate other WPs' status changes ║")
|
|
348
|
+
lines.append(f"║ • Mark subtasks that don't belong to {normalized_wp_id:<33} ║")
|
|
349
|
+
lines.append("║ ║")
|
|
350
|
+
lines.append("║ WHY: Multiple agents work in parallel. Each owns exactly ONE WP. ║")
|
|
351
|
+
lines.append("║ Git commits from other WPs are other agents - ignore them. ║")
|
|
352
|
+
lines.append("╚" + "=" * 78 + "╝")
|
|
353
|
+
lines.append("")
|
|
354
|
+
|
|
355
|
+
# Next steps
|
|
356
|
+
lines.append("=" * 80)
|
|
357
|
+
lines.append("WHEN YOU'RE DONE:")
|
|
358
|
+
lines.append("=" * 80)
|
|
359
|
+
lines.append(f"✓ Implementation complete and tested:")
|
|
360
|
+
lines.append(f" 1. Mark all subtasks as done first:")
|
|
361
|
+
lines.append(f" spec-kitty agent tasks mark-status T001 T002 T003 --status done")
|
|
362
|
+
lines.append(f" 2. Move WP to review:")
|
|
363
|
+
lines.append(f" spec-kitty agent tasks move-task {normalized_wp_id} --to for_review --note \"Ready for review\"")
|
|
364
|
+
lines.append("")
|
|
365
|
+
lines.append(f"✗ Blocked or cannot complete:")
|
|
366
|
+
lines.append(f" spec-kitty agent tasks add-history {normalized_wp_id} --note \"Blocked: <reason>\"")
|
|
367
|
+
lines.append("=" * 80)
|
|
368
|
+
lines.append("")
|
|
369
|
+
lines.append(f"📍 WORKING DIRECTORY:")
|
|
370
|
+
lines.append(f" cd {workspace_path}")
|
|
371
|
+
lines.append(f" # All implementation work happens in this workspace")
|
|
372
|
+
lines.append(f" # When done, return to main: cd {repo_root}")
|
|
373
|
+
lines.append("")
|
|
374
|
+
lines.append("📋 STATUS TRACKING:")
|
|
375
|
+
lines.append(f" kitty-specs/ is excluded via sparse-checkout (status tracked in main)")
|
|
376
|
+
lines.append(f" Status changes auto-commit to main branch (visible to all agents)")
|
|
377
|
+
lines.append(f" ⚠️ You will see commits from other agents - IGNORE THEM")
|
|
378
|
+
lines.append("=" * 80)
|
|
379
|
+
lines.append("")
|
|
380
|
+
|
|
381
|
+
if has_feedback:
|
|
382
|
+
lines.append("⚠️ This work package has review feedback. Check the '## Review Feedback' section below.")
|
|
383
|
+
lines.append("")
|
|
384
|
+
|
|
385
|
+
# WP content marker and content
|
|
386
|
+
lines.append("╔" + "=" * 78 + "╗")
|
|
387
|
+
lines.append("║ WORK PACKAGE PROMPT BEGINS ║")
|
|
388
|
+
lines.append("╚" + "=" * 78 + "╝")
|
|
389
|
+
lines.append("")
|
|
390
|
+
lines.append(wp.path.read_text(encoding="utf-8"))
|
|
391
|
+
lines.append("")
|
|
392
|
+
lines.append("╔" + "=" * 78 + "╗")
|
|
393
|
+
lines.append("║ WORK PACKAGE PROMPT ENDS ║")
|
|
394
|
+
lines.append("╚" + "=" * 78 + "╝")
|
|
395
|
+
lines.append("")
|
|
396
|
+
|
|
397
|
+
# Completion instructions at end
|
|
398
|
+
lines.append("=" * 80)
|
|
399
|
+
lines.append("🎯 IMPLEMENTATION COMPLETE? RUN THESE COMMANDS:")
|
|
400
|
+
lines.append("=" * 80)
|
|
401
|
+
lines.append("")
|
|
402
|
+
lines.append(f"✅ Implementation complete and tested:")
|
|
403
|
+
lines.append(f" 1. Mark all subtasks as done first:")
|
|
404
|
+
lines.append(f" spec-kitty agent tasks mark-status T001 T002 T003 --status done")
|
|
405
|
+
lines.append(f" 2. Move WP to review:")
|
|
406
|
+
lines.append(f" spec-kitty agent tasks move-task {normalized_wp_id} --to for_review --note \"Ready for review: <summary>\"")
|
|
407
|
+
lines.append("")
|
|
408
|
+
lines.append(f"⚠️ Blocked or cannot complete:")
|
|
409
|
+
lines.append(f" spec-kitty agent tasks add-history {normalized_wp_id} --note \"Blocked: <reason>\"")
|
|
410
|
+
lines.append("")
|
|
411
|
+
lines.append("⚠️ NOTE: You MUST mark subtasks as done BEFORE moving to for_review!")
|
|
412
|
+
lines.append(" The move-task command will fail if unchecked subtasks remain.")
|
|
413
|
+
lines.append("=" * 80)
|
|
414
|
+
|
|
415
|
+
# Write full prompt to file
|
|
416
|
+
full_content = "\n".join(lines)
|
|
417
|
+
prompt_file = _write_prompt_to_file("implement", normalized_wp_id, full_content)
|
|
418
|
+
|
|
419
|
+
# Output concise summary with directive to read the prompt
|
|
420
|
+
print()
|
|
421
|
+
print(f"📍 Workspace: cd {workspace_path}")
|
|
422
|
+
if has_feedback:
|
|
423
|
+
print(f"⚠️ Has review feedback - check prompt file")
|
|
424
|
+
print()
|
|
425
|
+
print("▶▶▶ NEXT STEP: Read the full prompt file now:")
|
|
426
|
+
print(f" cat {prompt_file}")
|
|
427
|
+
print()
|
|
428
|
+
print("After implementation, run:")
|
|
429
|
+
print(f" ✅ spec-kitty agent tasks move-task {normalized_wp_id} --to for_review --note \"Ready for review\"")
|
|
430
|
+
|
|
431
|
+
except Exception as e:
|
|
432
|
+
print(f"Error: {e}")
|
|
433
|
+
raise typer.Exit(1)
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def _find_first_for_review_wp(repo_root: Path, feature_slug: str) -> Optional[str]:
|
|
437
|
+
"""Find the first WP file with lane: "for_review".
|
|
438
|
+
|
|
439
|
+
Args:
|
|
440
|
+
repo_root: Repository root path
|
|
441
|
+
feature_slug: Feature slug
|
|
442
|
+
|
|
443
|
+
Returns:
|
|
444
|
+
WP ID of first for_review task, or None if not found
|
|
445
|
+
"""
|
|
446
|
+
from specify_cli.core.paths import is_worktree_context
|
|
447
|
+
|
|
448
|
+
cwd = Path.cwd().resolve()
|
|
449
|
+
|
|
450
|
+
# Check if we're in a worktree - if so, use worktree's kitty-specs
|
|
451
|
+
if is_worktree_context(cwd):
|
|
452
|
+
# We're in a worktree, look for kitty-specs relative to cwd
|
|
453
|
+
if (cwd / "kitty-specs" / feature_slug).exists():
|
|
454
|
+
tasks_dir = cwd / "kitty-specs" / feature_slug / "tasks"
|
|
455
|
+
else:
|
|
456
|
+
# Walk up to find kitty-specs
|
|
457
|
+
current = cwd
|
|
458
|
+
while current != current.parent:
|
|
459
|
+
if (current / "kitty-specs" / feature_slug).exists():
|
|
460
|
+
tasks_dir = current / "kitty-specs" / feature_slug / "tasks"
|
|
461
|
+
break
|
|
462
|
+
current = current.parent
|
|
463
|
+
else:
|
|
464
|
+
# Fallback to repo_root
|
|
465
|
+
tasks_dir = repo_root / "kitty-specs" / feature_slug / "tasks"
|
|
466
|
+
else:
|
|
467
|
+
# We're in main repo
|
|
468
|
+
tasks_dir = repo_root / "kitty-specs" / feature_slug / "tasks"
|
|
469
|
+
|
|
470
|
+
if not tasks_dir.exists():
|
|
471
|
+
return None
|
|
472
|
+
|
|
473
|
+
# Find all WP files
|
|
474
|
+
wp_files = sorted(tasks_dir.glob("WP*.md"))
|
|
475
|
+
|
|
476
|
+
for wp_file in wp_files:
|
|
477
|
+
content = wp_file.read_text(encoding="utf-8-sig")
|
|
478
|
+
frontmatter, _, _ = split_frontmatter(content)
|
|
479
|
+
lane = extract_scalar(frontmatter, "lane")
|
|
480
|
+
|
|
481
|
+
if lane == "for_review":
|
|
482
|
+
wp_id = extract_scalar(frontmatter, "work_package_id")
|
|
483
|
+
if wp_id:
|
|
484
|
+
return wp_id
|
|
485
|
+
|
|
486
|
+
return None
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
@app.command(name="review")
|
|
490
|
+
def review(
|
|
491
|
+
wp_id: Annotated[Optional[str], typer.Argument(help="Work package ID (e.g., WP01) - auto-detects first for_review if omitted")] = None,
|
|
492
|
+
feature: Annotated[Optional[str], typer.Option("--feature", help="Feature slug (auto-detected if omitted)")] = None,
|
|
493
|
+
agent: Annotated[Optional[str], typer.Option("--agent", help="Agent name (required for auto-move to doing lane)")] = None,
|
|
494
|
+
) -> None:
|
|
495
|
+
"""Display work package prompt with review instructions.
|
|
496
|
+
|
|
497
|
+
This command outputs the full work package prompt (including any review
|
|
498
|
+
feedback from previous reviews) so agents can review the implementation.
|
|
499
|
+
|
|
500
|
+
Automatically moves WP from for_review to doing lane (requires --agent to track who is reviewing).
|
|
501
|
+
|
|
502
|
+
Examples:
|
|
503
|
+
spec-kitty agent workflow review WP01 --agent claude
|
|
504
|
+
spec-kitty agent workflow review wp02 --agent codex
|
|
505
|
+
spec-kitty agent workflow review --agent gemini # auto-detects first for_review WP
|
|
506
|
+
"""
|
|
507
|
+
try:
|
|
508
|
+
# Get repo root and feature slug
|
|
509
|
+
repo_root = locate_project_root()
|
|
510
|
+
if repo_root is None:
|
|
511
|
+
print("Error: Could not locate project root")
|
|
512
|
+
raise typer.Exit(1)
|
|
513
|
+
|
|
514
|
+
feature_slug = feature or _find_feature_slug()
|
|
515
|
+
|
|
516
|
+
# Determine which WP to review
|
|
517
|
+
if wp_id:
|
|
518
|
+
normalized_wp_id = _normalize_wp_id(wp_id)
|
|
519
|
+
else:
|
|
520
|
+
# Auto-detect first for_review WP
|
|
521
|
+
normalized_wp_id = _find_first_for_review_wp(repo_root, feature_slug)
|
|
522
|
+
if not normalized_wp_id:
|
|
523
|
+
print("Error: No work packages ready for review. Specify a WP ID explicitly.")
|
|
524
|
+
raise typer.Exit(1)
|
|
525
|
+
|
|
526
|
+
# Load work package
|
|
527
|
+
wp = locate_work_package(repo_root, feature_slug, normalized_wp_id)
|
|
528
|
+
|
|
529
|
+
# Move to "doing" lane if not already there
|
|
530
|
+
current_lane = extract_scalar(wp.frontmatter, "lane") or "for_review"
|
|
531
|
+
if current_lane != "doing":
|
|
532
|
+
# Require --agent parameter to track who is reviewing
|
|
533
|
+
if not agent:
|
|
534
|
+
print("Error: --agent parameter required when starting review.")
|
|
535
|
+
print(f" Usage: spec-kitty agent workflow review {normalized_wp_id} --agent <your-name>")
|
|
536
|
+
print(" Example: spec-kitty agent workflow review WP01 --agent claude")
|
|
537
|
+
print()
|
|
538
|
+
print("If you're using a generated agent command file, --agent is already included.")
|
|
539
|
+
print("This tracks WHO is reviewing the WP (prevents abandoned reviews).")
|
|
540
|
+
raise typer.Exit(1)
|
|
541
|
+
|
|
542
|
+
from datetime import datetime, timezone
|
|
543
|
+
import os
|
|
544
|
+
|
|
545
|
+
# Capture current shell PID
|
|
546
|
+
shell_pid = str(os.getppid()) # Parent process ID (the shell running this command)
|
|
547
|
+
|
|
548
|
+
# Update lane, agent, and shell_pid in frontmatter
|
|
549
|
+
updated_front = set_scalar(wp.frontmatter, "lane", "doing")
|
|
550
|
+
updated_front = set_scalar(updated_front, "agent", agent)
|
|
551
|
+
updated_front = set_scalar(updated_front, "shell_pid", shell_pid)
|
|
552
|
+
|
|
553
|
+
# Build history entry
|
|
554
|
+
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
555
|
+
history_entry = f"- {timestamp} – {agent} – shell_pid={shell_pid} – lane=doing – Started review via workflow command"
|
|
556
|
+
|
|
557
|
+
# Add history entry to body
|
|
558
|
+
updated_body = append_activity_log(wp.body, history_entry)
|
|
559
|
+
|
|
560
|
+
# Build and write updated document
|
|
561
|
+
updated_doc = build_document(updated_front, updated_body, wp.padding)
|
|
562
|
+
wp.path.write_text(updated_doc, encoding="utf-8")
|
|
563
|
+
|
|
564
|
+
# Auto-commit to main (enables instant status sync)
|
|
565
|
+
import subprocess
|
|
566
|
+
|
|
567
|
+
# Get main repo root (might be in worktree)
|
|
568
|
+
git_file = Path.cwd() / ".git"
|
|
569
|
+
if git_file.is_file():
|
|
570
|
+
git_content = git_file.read_text().strip()
|
|
571
|
+
if git_content.startswith("gitdir:"):
|
|
572
|
+
gitdir = Path(git_content.split(":", 1)[1].strip())
|
|
573
|
+
main_repo_root = gitdir.parent.parent.parent
|
|
574
|
+
else:
|
|
575
|
+
main_repo_root = repo_root
|
|
576
|
+
else:
|
|
577
|
+
main_repo_root = repo_root
|
|
578
|
+
|
|
579
|
+
actual_wp_path = wp.path.resolve()
|
|
580
|
+
commit_result = subprocess.run(
|
|
581
|
+
["git", "commit", str(actual_wp_path), "-m", f"chore: Start {normalized_wp_id} review [{agent}]"],
|
|
582
|
+
cwd=main_repo_root,
|
|
583
|
+
capture_output=True,
|
|
584
|
+
text=True,
|
|
585
|
+
check=False
|
|
586
|
+
)
|
|
587
|
+
|
|
588
|
+
if commit_result.returncode == 0:
|
|
589
|
+
print(f"✓ Claimed {normalized_wp_id} for review (agent: {agent}, PID: {shell_pid})")
|
|
590
|
+
else:
|
|
591
|
+
# Commit failed - file might already be committed in this state
|
|
592
|
+
pass
|
|
593
|
+
|
|
594
|
+
# Reload to get updated content
|
|
595
|
+
wp = locate_work_package(repo_root, feature_slug, normalized_wp_id)
|
|
596
|
+
else:
|
|
597
|
+
print(f"⚠️ {normalized_wp_id} is already in lane: {current_lane}. Workflow review will not move it to doing.")
|
|
598
|
+
|
|
599
|
+
# Calculate workspace path
|
|
600
|
+
workspace_name = f"{feature_slug}-{normalized_wp_id}"
|
|
601
|
+
workspace_path = repo_root / ".worktrees" / workspace_name
|
|
602
|
+
|
|
603
|
+
# Ensure workspace exists (create if needed)
|
|
604
|
+
if not workspace_path.exists():
|
|
605
|
+
import subprocess
|
|
606
|
+
|
|
607
|
+
# Ensure .worktrees directory exists
|
|
608
|
+
worktrees_dir = repo_root / ".worktrees"
|
|
609
|
+
worktrees_dir.mkdir(parents=True, exist_ok=True)
|
|
610
|
+
|
|
611
|
+
# Create worktree with sparse-checkout
|
|
612
|
+
branch_name = workspace_name
|
|
613
|
+
result = subprocess.run(
|
|
614
|
+
["git", "worktree", "add", str(workspace_path), "-b", branch_name],
|
|
615
|
+
cwd=repo_root,
|
|
616
|
+
capture_output=True,
|
|
617
|
+
text=True,
|
|
618
|
+
check=False
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
if result.returncode != 0:
|
|
622
|
+
print(f"Warning: Could not create workspace: {result.stderr}")
|
|
623
|
+
else:
|
|
624
|
+
# Configure sparse-checkout to exclude kitty-specs/
|
|
625
|
+
sparse_checkout_result = subprocess.run(
|
|
626
|
+
["git", "rev-parse", "--git-path", "info/sparse-checkout"],
|
|
627
|
+
cwd=workspace_path,
|
|
628
|
+
capture_output=True,
|
|
629
|
+
text=True,
|
|
630
|
+
check=False
|
|
631
|
+
)
|
|
632
|
+
if sparse_checkout_result.returncode == 0:
|
|
633
|
+
sparse_checkout_file = Path(sparse_checkout_result.stdout.strip())
|
|
634
|
+
subprocess.run(["git", "config", "core.sparseCheckout", "true"], cwd=workspace_path, capture_output=True, check=False)
|
|
635
|
+
subprocess.run(["git", "config", "core.sparseCheckoutCone", "false"], cwd=workspace_path, capture_output=True, check=False)
|
|
636
|
+
sparse_checkout_file.parent.mkdir(parents=True, exist_ok=True)
|
|
637
|
+
sparse_checkout_file.write_text("/*\n!/kitty-specs/\n!/kitty-specs/**\n", encoding="utf-8")
|
|
638
|
+
subprocess.run(["git", "read-tree", "-mu", "HEAD"], cwd=workspace_path, capture_output=True, check=False)
|
|
639
|
+
|
|
640
|
+
# Add .gitignore to block WP status files but allow research artifacts
|
|
641
|
+
gitignore_path = workspace_path / ".gitignore"
|
|
642
|
+
gitignore_entry = "# Block WP status files (managed in main repo, prevents merge conflicts)\n# Research artifacts in kitty-specs/**/research/ are allowed\nkitty-specs/**/tasks/*.md\n"
|
|
643
|
+
if gitignore_path.exists():
|
|
644
|
+
content = gitignore_path.read_text(encoding="utf-8")
|
|
645
|
+
if "kitty-specs/**/tasks/*.md" not in content:
|
|
646
|
+
# Remove old blanket rule if present
|
|
647
|
+
if "kitty-specs/\n" in content:
|
|
648
|
+
content = content.replace("# Prevent worktree-local kitty-specs/ (status managed in main repo)\nkitty-specs/\n", "")
|
|
649
|
+
content = content.replace("kitty-specs/\n", "")
|
|
650
|
+
gitignore_path.write_text(content.rstrip() + "\n" + gitignore_entry, encoding="utf-8")
|
|
651
|
+
else:
|
|
652
|
+
gitignore_path.write_text(gitignore_entry, encoding="utf-8")
|
|
653
|
+
|
|
654
|
+
print(f"✓ Created workspace: {workspace_path}")
|
|
655
|
+
|
|
656
|
+
# Capture dependency warning for both file and summary
|
|
657
|
+
dependents_warning = []
|
|
658
|
+
feature_dir = repo_root / "kitty-specs" / feature_slug
|
|
659
|
+
graph = build_dependency_graph(feature_dir)
|
|
660
|
+
dependents = get_dependents(normalized_wp_id, graph)
|
|
661
|
+
if dependents:
|
|
662
|
+
incomplete: list[str] = []
|
|
663
|
+
for dependent_id in dependents:
|
|
664
|
+
try:
|
|
665
|
+
dependent_wp = locate_work_package(repo_root, feature_slug, dependent_id)
|
|
666
|
+
except FileNotFoundError:
|
|
667
|
+
continue
|
|
668
|
+
lane = extract_scalar(dependent_wp.frontmatter, "lane")
|
|
669
|
+
if lane in {"planned", "doing", "for_review"}:
|
|
670
|
+
incomplete.append(dependent_id)
|
|
671
|
+
if incomplete:
|
|
672
|
+
dependents_list = ", ".join(sorted(incomplete))
|
|
673
|
+
dependents_warning.append(f"⚠️ Dependency Alert: {dependents_list} depend on {normalized_wp_id} (not yet done)")
|
|
674
|
+
dependents_warning.append(" If you request changes, notify those agents to rebase.")
|
|
675
|
+
|
|
676
|
+
# Build full prompt content for file
|
|
677
|
+
lines = []
|
|
678
|
+
lines.append("=" * 80)
|
|
679
|
+
lines.append(f"REVIEW: {normalized_wp_id}")
|
|
680
|
+
lines.append("=" * 80)
|
|
681
|
+
lines.append("")
|
|
682
|
+
lines.append(f"Source: {wp.path}")
|
|
683
|
+
lines.append("")
|
|
684
|
+
lines.append(f"Workspace: {workspace_path}")
|
|
685
|
+
lines.append("")
|
|
686
|
+
|
|
687
|
+
# Add dependency warning to file
|
|
688
|
+
if dependents_warning:
|
|
689
|
+
lines.extend(dependents_warning)
|
|
690
|
+
lines.append("")
|
|
691
|
+
|
|
692
|
+
# CRITICAL: WP isolation rules
|
|
693
|
+
lines.append("╔" + "=" * 78 + "╗")
|
|
694
|
+
lines.append("║ 🚨 CRITICAL: WORK PACKAGE ISOLATION RULES ║")
|
|
695
|
+
lines.append("╠" + "=" * 78 + "╣")
|
|
696
|
+
lines.append(f"║ YOU ARE REVIEWING: {normalized_wp_id:<56} ║")
|
|
697
|
+
lines.append("║ ║")
|
|
698
|
+
lines.append("║ ✅ DO: ║")
|
|
699
|
+
lines.append(f"║ • Only modify status of {normalized_wp_id:<47} ║")
|
|
700
|
+
lines.append("║ • Ignore git commits and status changes from other agents ║")
|
|
701
|
+
lines.append("║ ║")
|
|
702
|
+
lines.append("║ ❌ DO NOT: ║")
|
|
703
|
+
lines.append(f"║ • Change status of any WP other than {normalized_wp_id:<34} ║")
|
|
704
|
+
lines.append("║ • React to or investigate other WPs' status changes ║")
|
|
705
|
+
lines.append(f"║ • Review or approve any WP other than {normalized_wp_id:<32} ║")
|
|
706
|
+
lines.append("║ ║")
|
|
707
|
+
lines.append("║ WHY: Multiple agents work in parallel. Each owns exactly ONE WP. ║")
|
|
708
|
+
lines.append("║ Git commits from other WPs are other agents - ignore them. ║")
|
|
709
|
+
lines.append("╚" + "=" * 78 + "╝")
|
|
710
|
+
lines.append("")
|
|
711
|
+
|
|
712
|
+
# Next steps
|
|
713
|
+
lines.append("=" * 80)
|
|
714
|
+
lines.append("WHEN YOU'RE DONE:")
|
|
715
|
+
lines.append("=" * 80)
|
|
716
|
+
lines.append(f"✓ Review passed, no issues:")
|
|
717
|
+
lines.append(f" spec-kitty agent tasks move-task {normalized_wp_id} --to done --note \"Review passed\"")
|
|
718
|
+
lines.append("")
|
|
719
|
+
lines.append(f"⚠️ Changes requested:")
|
|
720
|
+
lines.append(f" 1. Add feedback to the WP file's '## Review Feedback' section")
|
|
721
|
+
lines.append(f" 2. spec-kitty agent tasks move-task {normalized_wp_id} --to planned --note \"Changes requested\"")
|
|
722
|
+
lines.append("=" * 80)
|
|
723
|
+
lines.append("")
|
|
724
|
+
lines.append(f"📍 WORKING DIRECTORY:")
|
|
725
|
+
lines.append(f" cd {workspace_path}")
|
|
726
|
+
lines.append(f" # Review the implementation in this workspace")
|
|
727
|
+
lines.append(f" # Read code, run tests, check against requirements")
|
|
728
|
+
lines.append(f" # When done, return to main: cd {repo_root}")
|
|
729
|
+
lines.append("")
|
|
730
|
+
lines.append("📋 STATUS TRACKING:")
|
|
731
|
+
lines.append(f" kitty-specs/ is excluded via sparse-checkout (status tracked in main)")
|
|
732
|
+
lines.append(f" Status changes auto-commit to main branch (visible to all agents)")
|
|
733
|
+
lines.append(f" ⚠️ You will see commits from other agents - IGNORE THEM")
|
|
734
|
+
lines.append("=" * 80)
|
|
735
|
+
lines.append("")
|
|
736
|
+
lines.append("Review the implementation against the requirements below.")
|
|
737
|
+
lines.append("Check code quality, tests, documentation, and adherence to spec.")
|
|
738
|
+
lines.append("")
|
|
739
|
+
|
|
740
|
+
# WP content marker and content
|
|
741
|
+
lines.append("╔" + "=" * 78 + "╗")
|
|
742
|
+
lines.append("║ WORK PACKAGE PROMPT BEGINS ║")
|
|
743
|
+
lines.append("╚" + "=" * 78 + "╝")
|
|
744
|
+
lines.append("")
|
|
745
|
+
lines.append(wp.path.read_text(encoding="utf-8"))
|
|
746
|
+
lines.append("")
|
|
747
|
+
lines.append("╔" + "=" * 78 + "╗")
|
|
748
|
+
lines.append("║ WORK PACKAGE PROMPT ENDS ║")
|
|
749
|
+
lines.append("╚" + "=" * 78 + "╝")
|
|
750
|
+
lines.append("")
|
|
751
|
+
|
|
752
|
+
# Completion instructions at end
|
|
753
|
+
lines.append("=" * 80)
|
|
754
|
+
lines.append("🎯 REVIEW COMPLETE? RUN ONE OF THESE COMMANDS:")
|
|
755
|
+
lines.append("=" * 80)
|
|
756
|
+
lines.append("")
|
|
757
|
+
lines.append(f"✅ APPROVE (no issues found):")
|
|
758
|
+
lines.append(f" spec-kitty agent tasks move-task {normalized_wp_id} --to done --note \"Review passed: <summary>\"")
|
|
759
|
+
lines.append("")
|
|
760
|
+
# Create unique temp file path for review feedback (avoids conflicts between agents)
|
|
761
|
+
review_feedback_path = Path(tempfile.gettempdir()) / f"spec-kitty-review-feedback-{normalized_wp_id}.md"
|
|
762
|
+
|
|
763
|
+
lines.append(f"❌ REQUEST CHANGES (issues found):")
|
|
764
|
+
lines.append(f" 1. Write feedback:")
|
|
765
|
+
lines.append(f" cat > {review_feedback_path} <<'EOF'")
|
|
766
|
+
lines.append(f"**Issue 1**: <description and how to fix>")
|
|
767
|
+
lines.append(f"**Issue 2**: <description and how to fix>")
|
|
768
|
+
lines.append(f"EOF")
|
|
769
|
+
lines.append("")
|
|
770
|
+
lines.append(f" 2. Move to planned with feedback:")
|
|
771
|
+
lines.append(f" spec-kitty agent tasks move-task {normalized_wp_id} --to planned --review-feedback-file {review_feedback_path}")
|
|
772
|
+
lines.append("")
|
|
773
|
+
lines.append("⚠️ NOTE: You MUST run one of these commands to complete the review!")
|
|
774
|
+
lines.append(" The Python script handles all file updates automatically.")
|
|
775
|
+
lines.append("=" * 80)
|
|
776
|
+
|
|
777
|
+
# Write full prompt to file
|
|
778
|
+
full_content = "\n".join(lines)
|
|
779
|
+
prompt_file = _write_prompt_to_file("review", normalized_wp_id, full_content)
|
|
780
|
+
|
|
781
|
+
# Create unique temp file path for review feedback (same as in prompt)
|
|
782
|
+
review_feedback_path = Path(tempfile.gettempdir()) / f"spec-kitty-review-feedback-{normalized_wp_id}.md"
|
|
783
|
+
|
|
784
|
+
# Output concise summary with directive to read the prompt
|
|
785
|
+
print()
|
|
786
|
+
if dependents_warning:
|
|
787
|
+
for line in dependents_warning:
|
|
788
|
+
print(line)
|
|
789
|
+
print()
|
|
790
|
+
print(f"📍 Workspace: cd {workspace_path}")
|
|
791
|
+
print()
|
|
792
|
+
print("▶▶▶ NEXT STEP: Read the full prompt file now:")
|
|
793
|
+
print(f" cat {prompt_file}")
|
|
794
|
+
print()
|
|
795
|
+
print("After review, run:")
|
|
796
|
+
print(f" ✅ spec-kitty agent tasks move-task {normalized_wp_id} --to done --note \"Review passed\"")
|
|
797
|
+
print(f" ❌ spec-kitty agent tasks move-task {normalized_wp_id} --to planned --review-feedback-file {review_feedback_path}")
|
|
798
|
+
|
|
799
|
+
except Exception as e:
|
|
800
|
+
print(f"Error: {e}")
|
|
801
|
+
raise typer.Exit(1)
|