spec-kitty-cli 0.12.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. spec_kitty_cli-0.12.1.dist-info/METADATA +1767 -0
  2. spec_kitty_cli-0.12.1.dist-info/RECORD +242 -0
  3. spec_kitty_cli-0.12.1.dist-info/WHEEL +4 -0
  4. spec_kitty_cli-0.12.1.dist-info/entry_points.txt +2 -0
  5. spec_kitty_cli-0.12.1.dist-info/licenses/LICENSE +21 -0
  6. specify_cli/__init__.py +171 -0
  7. specify_cli/acceptance.py +627 -0
  8. specify_cli/agent_utils/README.md +157 -0
  9. specify_cli/agent_utils/__init__.py +9 -0
  10. specify_cli/agent_utils/status.py +356 -0
  11. specify_cli/cli/__init__.py +6 -0
  12. specify_cli/cli/commands/__init__.py +46 -0
  13. specify_cli/cli/commands/accept.py +189 -0
  14. specify_cli/cli/commands/agent/__init__.py +22 -0
  15. specify_cli/cli/commands/agent/config.py +382 -0
  16. specify_cli/cli/commands/agent/context.py +191 -0
  17. specify_cli/cli/commands/agent/feature.py +1057 -0
  18. specify_cli/cli/commands/agent/release.py +11 -0
  19. specify_cli/cli/commands/agent/tasks.py +1253 -0
  20. specify_cli/cli/commands/agent/workflow.py +801 -0
  21. specify_cli/cli/commands/context.py +246 -0
  22. specify_cli/cli/commands/dashboard.py +85 -0
  23. specify_cli/cli/commands/implement.py +973 -0
  24. specify_cli/cli/commands/init.py +827 -0
  25. specify_cli/cli/commands/init_help.py +62 -0
  26. specify_cli/cli/commands/merge.py +755 -0
  27. specify_cli/cli/commands/mission.py +240 -0
  28. specify_cli/cli/commands/ops.py +265 -0
  29. specify_cli/cli/commands/orchestrate.py +640 -0
  30. specify_cli/cli/commands/repair.py +175 -0
  31. specify_cli/cli/commands/research.py +165 -0
  32. specify_cli/cli/commands/sync.py +364 -0
  33. specify_cli/cli/commands/upgrade.py +249 -0
  34. specify_cli/cli/commands/validate_encoding.py +186 -0
  35. specify_cli/cli/commands/validate_tasks.py +186 -0
  36. specify_cli/cli/commands/verify.py +310 -0
  37. specify_cli/cli/helpers.py +123 -0
  38. specify_cli/cli/step_tracker.py +91 -0
  39. specify_cli/cli/ui.py +192 -0
  40. specify_cli/core/__init__.py +53 -0
  41. specify_cli/core/agent_context.py +311 -0
  42. specify_cli/core/config.py +96 -0
  43. specify_cli/core/context_validation.py +362 -0
  44. specify_cli/core/dependency_graph.py +351 -0
  45. specify_cli/core/git_ops.py +129 -0
  46. specify_cli/core/multi_parent_merge.py +323 -0
  47. specify_cli/core/paths.py +260 -0
  48. specify_cli/core/project_resolver.py +110 -0
  49. specify_cli/core/stale_detection.py +263 -0
  50. specify_cli/core/tool_checker.py +79 -0
  51. specify_cli/core/utils.py +43 -0
  52. specify_cli/core/vcs/__init__.py +114 -0
  53. specify_cli/core/vcs/detection.py +341 -0
  54. specify_cli/core/vcs/exceptions.py +85 -0
  55. specify_cli/core/vcs/git.py +1304 -0
  56. specify_cli/core/vcs/jujutsu.py +1208 -0
  57. specify_cli/core/vcs/protocol.py +285 -0
  58. specify_cli/core/vcs/types.py +249 -0
  59. specify_cli/core/version_checker.py +261 -0
  60. specify_cli/core/worktree.py +506 -0
  61. specify_cli/dashboard/__init__.py +28 -0
  62. specify_cli/dashboard/diagnostics.py +204 -0
  63. specify_cli/dashboard/handlers/__init__.py +17 -0
  64. specify_cli/dashboard/handlers/api.py +143 -0
  65. specify_cli/dashboard/handlers/base.py +65 -0
  66. specify_cli/dashboard/handlers/features.py +390 -0
  67. specify_cli/dashboard/handlers/router.py +81 -0
  68. specify_cli/dashboard/handlers/static.py +50 -0
  69. specify_cli/dashboard/lifecycle.py +541 -0
  70. specify_cli/dashboard/scanner.py +437 -0
  71. specify_cli/dashboard/server.py +123 -0
  72. specify_cli/dashboard/static/dashboard/dashboard.css +722 -0
  73. specify_cli/dashboard/static/dashboard/dashboard.js +1424 -0
  74. specify_cli/dashboard/static/spec-kitty.png +0 -0
  75. specify_cli/dashboard/templates/__init__.py +36 -0
  76. specify_cli/dashboard/templates/index.html +258 -0
  77. specify_cli/doc_generators.py +621 -0
  78. specify_cli/doc_state.py +408 -0
  79. specify_cli/frontmatter.py +384 -0
  80. specify_cli/gap_analysis.py +915 -0
  81. specify_cli/gitignore_manager.py +300 -0
  82. specify_cli/guards.py +145 -0
  83. specify_cli/legacy_detector.py +83 -0
  84. specify_cli/manifest.py +286 -0
  85. specify_cli/merge/__init__.py +63 -0
  86. specify_cli/merge/executor.py +653 -0
  87. specify_cli/merge/forecast.py +215 -0
  88. specify_cli/merge/ordering.py +126 -0
  89. specify_cli/merge/preflight.py +230 -0
  90. specify_cli/merge/state.py +185 -0
  91. specify_cli/merge/status_resolver.py +354 -0
  92. specify_cli/mission.py +654 -0
  93. specify_cli/missions/documentation/command-templates/implement.md +309 -0
  94. specify_cli/missions/documentation/command-templates/plan.md +275 -0
  95. specify_cli/missions/documentation/command-templates/review.md +344 -0
  96. specify_cli/missions/documentation/command-templates/specify.md +206 -0
  97. specify_cli/missions/documentation/command-templates/tasks.md +189 -0
  98. specify_cli/missions/documentation/mission.yaml +113 -0
  99. specify_cli/missions/documentation/templates/divio/explanation-template.md +192 -0
  100. specify_cli/missions/documentation/templates/divio/howto-template.md +168 -0
  101. specify_cli/missions/documentation/templates/divio/reference-template.md +179 -0
  102. specify_cli/missions/documentation/templates/divio/tutorial-template.md +146 -0
  103. specify_cli/missions/documentation/templates/generators/jsdoc.json.template +18 -0
  104. specify_cli/missions/documentation/templates/generators/sphinx-conf.py.template +36 -0
  105. specify_cli/missions/documentation/templates/plan-template.md +269 -0
  106. specify_cli/missions/documentation/templates/release-template.md +222 -0
  107. specify_cli/missions/documentation/templates/spec-template.md +172 -0
  108. specify_cli/missions/documentation/templates/task-prompt-template.md +140 -0
  109. specify_cli/missions/documentation/templates/tasks-template.md +159 -0
  110. specify_cli/missions/research/command-templates/merge.md +388 -0
  111. specify_cli/missions/research/command-templates/plan.md +125 -0
  112. specify_cli/missions/research/command-templates/review.md +144 -0
  113. specify_cli/missions/research/command-templates/tasks.md +225 -0
  114. specify_cli/missions/research/mission.yaml +115 -0
  115. specify_cli/missions/research/templates/data-model-template.md +33 -0
  116. specify_cli/missions/research/templates/plan-template.md +161 -0
  117. specify_cli/missions/research/templates/research/evidence-log.csv +18 -0
  118. specify_cli/missions/research/templates/research/source-register.csv +18 -0
  119. specify_cli/missions/research/templates/research-template.md +35 -0
  120. specify_cli/missions/research/templates/spec-template.md +64 -0
  121. specify_cli/missions/research/templates/task-prompt-template.md +148 -0
  122. specify_cli/missions/research/templates/tasks-template.md +114 -0
  123. specify_cli/missions/software-dev/command-templates/accept.md +75 -0
  124. specify_cli/missions/software-dev/command-templates/analyze.md +183 -0
  125. specify_cli/missions/software-dev/command-templates/checklist.md +286 -0
  126. specify_cli/missions/software-dev/command-templates/clarify.md +157 -0
  127. specify_cli/missions/software-dev/command-templates/constitution.md +432 -0
  128. specify_cli/missions/software-dev/command-templates/dashboard.md +101 -0
  129. specify_cli/missions/software-dev/command-templates/implement.md +41 -0
  130. specify_cli/missions/software-dev/command-templates/merge.md +383 -0
  131. specify_cli/missions/software-dev/command-templates/plan.md +171 -0
  132. specify_cli/missions/software-dev/command-templates/review.md +32 -0
  133. specify_cli/missions/software-dev/command-templates/specify.md +321 -0
  134. specify_cli/missions/software-dev/command-templates/tasks.md +566 -0
  135. specify_cli/missions/software-dev/mission.yaml +100 -0
  136. specify_cli/missions/software-dev/templates/plan-template.md +132 -0
  137. specify_cli/missions/software-dev/templates/spec-template.md +116 -0
  138. specify_cli/missions/software-dev/templates/task-prompt-template.md +140 -0
  139. specify_cli/missions/software-dev/templates/tasks-template.md +159 -0
  140. specify_cli/orchestrator/__init__.py +75 -0
  141. specify_cli/orchestrator/agent_config.py +224 -0
  142. specify_cli/orchestrator/agents/__init__.py +170 -0
  143. specify_cli/orchestrator/agents/augment.py +112 -0
  144. specify_cli/orchestrator/agents/base.py +243 -0
  145. specify_cli/orchestrator/agents/claude.py +112 -0
  146. specify_cli/orchestrator/agents/codex.py +106 -0
  147. specify_cli/orchestrator/agents/copilot.py +137 -0
  148. specify_cli/orchestrator/agents/cursor.py +139 -0
  149. specify_cli/orchestrator/agents/gemini.py +115 -0
  150. specify_cli/orchestrator/agents/kilocode.py +94 -0
  151. specify_cli/orchestrator/agents/opencode.py +132 -0
  152. specify_cli/orchestrator/agents/qwen.py +96 -0
  153. specify_cli/orchestrator/config.py +455 -0
  154. specify_cli/orchestrator/executor.py +642 -0
  155. specify_cli/orchestrator/integration.py +1230 -0
  156. specify_cli/orchestrator/monitor.py +898 -0
  157. specify_cli/orchestrator/scheduler.py +832 -0
  158. specify_cli/orchestrator/state.py +508 -0
  159. specify_cli/orchestrator/testing/__init__.py +122 -0
  160. specify_cli/orchestrator/testing/availability.py +346 -0
  161. specify_cli/orchestrator/testing/fixtures.py +684 -0
  162. specify_cli/orchestrator/testing/paths.py +218 -0
  163. specify_cli/plan_validation.py +107 -0
  164. specify_cli/scripts/debug-dashboard-scan.py +61 -0
  165. specify_cli/scripts/tasks/acceptance_support.py +695 -0
  166. specify_cli/scripts/tasks/task_helpers.py +506 -0
  167. specify_cli/scripts/tasks/tasks_cli.py +848 -0
  168. specify_cli/scripts/validate_encoding.py +180 -0
  169. specify_cli/task_metadata_validation.py +274 -0
  170. specify_cli/tasks_support.py +447 -0
  171. specify_cli/template/__init__.py +47 -0
  172. specify_cli/template/asset_generator.py +206 -0
  173. specify_cli/template/github_client.py +334 -0
  174. specify_cli/template/manager.py +193 -0
  175. specify_cli/template/renderer.py +99 -0
  176. specify_cli/templates/AGENTS.md +190 -0
  177. specify_cli/templates/POWERSHELL_SYNTAX.md +229 -0
  178. specify_cli/templates/agent-file-template.md +35 -0
  179. specify_cli/templates/checklist-template.md +42 -0
  180. specify_cli/templates/claudeignore-template +58 -0
  181. specify_cli/templates/command-templates/accept.md +141 -0
  182. specify_cli/templates/command-templates/analyze.md +253 -0
  183. specify_cli/templates/command-templates/checklist.md +352 -0
  184. specify_cli/templates/command-templates/clarify.md +224 -0
  185. specify_cli/templates/command-templates/constitution.md +432 -0
  186. specify_cli/templates/command-templates/dashboard.md +175 -0
  187. specify_cli/templates/command-templates/implement.md +190 -0
  188. specify_cli/templates/command-templates/merge.md +374 -0
  189. specify_cli/templates/command-templates/plan.md +171 -0
  190. specify_cli/templates/command-templates/research.md +88 -0
  191. specify_cli/templates/command-templates/review.md +510 -0
  192. specify_cli/templates/command-templates/specify.md +321 -0
  193. specify_cli/templates/command-templates/status.md +92 -0
  194. specify_cli/templates/command-templates/tasks.md +199 -0
  195. specify_cli/templates/git-hooks/pre-commit +22 -0
  196. specify_cli/templates/git-hooks/pre-commit-agent-check +37 -0
  197. specify_cli/templates/git-hooks/pre-commit-encoding-check +142 -0
  198. specify_cli/templates/plan-template.md +108 -0
  199. specify_cli/templates/spec-template.md +118 -0
  200. specify_cli/templates/task-prompt-template.md +165 -0
  201. specify_cli/templates/tasks-template.md +161 -0
  202. specify_cli/templates/vscode-settings.json +13 -0
  203. specify_cli/text_sanitization.py +225 -0
  204. specify_cli/upgrade/__init__.py +18 -0
  205. specify_cli/upgrade/detector.py +239 -0
  206. specify_cli/upgrade/metadata.py +182 -0
  207. specify_cli/upgrade/migrations/__init__.py +65 -0
  208. specify_cli/upgrade/migrations/base.py +80 -0
  209. specify_cli/upgrade/migrations/m_0_10_0_python_only.py +359 -0
  210. specify_cli/upgrade/migrations/m_0_10_12_constitution_cleanup.py +99 -0
  211. specify_cli/upgrade/migrations/m_0_10_14_update_implement_slash_command.py +176 -0
  212. specify_cli/upgrade/migrations/m_0_10_1_populate_slash_commands.py +174 -0
  213. specify_cli/upgrade/migrations/m_0_10_2_update_slash_commands.py +172 -0
  214. specify_cli/upgrade/migrations/m_0_10_6_workflow_simplification.py +174 -0
  215. specify_cli/upgrade/migrations/m_0_10_8_fix_memory_structure.py +252 -0
  216. specify_cli/upgrade/migrations/m_0_10_9_repair_templates.py +168 -0
  217. specify_cli/upgrade/migrations/m_0_11_0_workspace_per_wp.py +182 -0
  218. specify_cli/upgrade/migrations/m_0_11_1_improved_workflow_templates.py +173 -0
  219. specify_cli/upgrade/migrations/m_0_11_1_update_implement_slash_command.py +160 -0
  220. specify_cli/upgrade/migrations/m_0_11_2_improved_workflow_templates.py +173 -0
  221. specify_cli/upgrade/migrations/m_0_11_3_workflow_agent_flag.py +114 -0
  222. specify_cli/upgrade/migrations/m_0_12_0_documentation_mission.py +155 -0
  223. specify_cli/upgrade/migrations/m_0_12_1_remove_kitty_specs_from_gitignore.py +183 -0
  224. specify_cli/upgrade/migrations/m_0_2_0_specify_to_kittify.py +80 -0
  225. specify_cli/upgrade/migrations/m_0_4_8_gitignore_agents.py +118 -0
  226. specify_cli/upgrade/migrations/m_0_5_0_encoding_hooks.py +141 -0
  227. specify_cli/upgrade/migrations/m_0_6_5_commands_rename.py +169 -0
  228. specify_cli/upgrade/migrations/m_0_6_7_ensure_missions.py +228 -0
  229. specify_cli/upgrade/migrations/m_0_7_2_worktree_commands_dedup.py +89 -0
  230. specify_cli/upgrade/migrations/m_0_7_3_update_scripts.py +114 -0
  231. specify_cli/upgrade/migrations/m_0_8_0_remove_active_mission.py +82 -0
  232. specify_cli/upgrade/migrations/m_0_8_0_worktree_agents_symlink.py +148 -0
  233. specify_cli/upgrade/migrations/m_0_9_0_frontmatter_only_lanes.py +346 -0
  234. specify_cli/upgrade/migrations/m_0_9_1_complete_lane_migration.py +656 -0
  235. specify_cli/upgrade/migrations/m_0_9_2_research_mission_templates.py +221 -0
  236. specify_cli/upgrade/registry.py +121 -0
  237. specify_cli/upgrade/runner.py +284 -0
  238. specify_cli/validators/__init__.py +14 -0
  239. specify_cli/validators/paths.py +154 -0
  240. specify_cli/validators/research.py +428 -0
  241. specify_cli/verify_enhanced.py +270 -0
  242. specify_cli/workspace_context.py +224 -0
@@ -0,0 +1,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)