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,270 @@
1
+ """
2
+ Enhanced verify_setup implementation for spec-kitty.
3
+ """
4
+
5
+ import json
6
+ import subprocess
7
+ from pathlib import Path
8
+ from typing import Dict, Optional
9
+ from rich.console import Console
10
+ from rich.table import Table
11
+ from rich.panel import Panel
12
+
13
+ from .manifest import FileManifest, WorktreeStatus
14
+
15
+
16
+ def run_enhanced_verify(
17
+ repo_root: Path,
18
+ project_root: Path,
19
+ cwd: Path,
20
+ feature: Optional[str],
21
+ json_output: bool,
22
+ check_files: bool,
23
+ console: Console
24
+ ) -> Dict:
25
+ """
26
+ Run the enhanced verification with manifest checking and worktree status.
27
+
28
+ Returns a dict suitable for JSON output if needed.
29
+ """
30
+ output_data = {
31
+ "environment": {},
32
+ "feature_detection": {},
33
+ "worktree_status": {},
34
+ "file_integrity": {},
35
+ "feature_analysis": {},
36
+ "recommendations": []
37
+ }
38
+
39
+ # Initialize helpers
40
+ kittify_dir = project_root / ".kittify"
41
+ manifest = FileManifest(kittify_dir)
42
+ worktree_status = WorktreeStatus(repo_root)
43
+
44
+ # 1. Environment Information
45
+ in_worktree = '.worktrees' in str(cwd)
46
+
47
+ try:
48
+ current_branch = subprocess.run(
49
+ ["git", "branch", "--show-current"],
50
+ cwd=cwd,
51
+ capture_output=True,
52
+ text=True,
53
+ check=True
54
+ ).stdout.strip()
55
+ except subprocess.CalledProcessError:
56
+ current_branch = None
57
+
58
+ output_data["environment"] = {
59
+ "working_directory": str(cwd),
60
+ "repo_root": str(repo_root),
61
+ "project_root": str(project_root),
62
+ "in_worktree": in_worktree,
63
+ "current_branch": current_branch,
64
+ "active_mission": manifest.active_mission
65
+ }
66
+
67
+ if not json_output:
68
+ console.print("\n[bold]System Integrity Check[/bold]\n")
69
+
70
+ # Environment section
71
+ console.print("[cyan]1. Environment[/cyan]")
72
+ console.print(f" Working directory: {cwd}")
73
+ console.print(f" Repository root: {repo_root}")
74
+
75
+ if in_worktree:
76
+ console.print(f" [green]✓[/green] In worktree")
77
+ else:
78
+ console.print(f" [dim]○[/dim] Not in worktree")
79
+
80
+ if current_branch:
81
+ console.print(f" Current branch: {current_branch}")
82
+ if current_branch == "main":
83
+ console.print(f" [yellow]⚠[/yellow] On main branch")
84
+ else:
85
+ console.print(f" [yellow]⚠[/yellow] Could not detect branch")
86
+
87
+ # 2. File Integrity Check
88
+ if check_files:
89
+ file_check = manifest.check_files()
90
+ expected_files = manifest.get_expected_files()
91
+
92
+ total_expected = sum(len(files) for files in expected_files.values())
93
+ total_present = len(file_check["present"])
94
+ total_missing = len(file_check["missing"])
95
+
96
+ output_data["file_integrity"] = {
97
+ "active_mission": manifest.active_mission,
98
+ "total_expected": total_expected,
99
+ "total_present": total_present,
100
+ "total_missing": total_missing,
101
+ "missing_files": file_check["missing"],
102
+ "categories": {}
103
+ }
104
+
105
+ # Count by category
106
+ for category, files in expected_files.items():
107
+ present_in_category = sum(1 for f in files if f in file_check["present"])
108
+ output_data["file_integrity"]["categories"][category] = {
109
+ "expected": len(files),
110
+ "present": present_in_category,
111
+ "missing": len(files) - present_in_category
112
+ }
113
+
114
+ if not json_output:
115
+ console.print("\n[cyan]2. Mission File Integrity[/cyan]")
116
+ console.print(f" Active mission: {manifest.active_mission}")
117
+
118
+ if total_missing == 0:
119
+ console.print(f" [green]✓[/green] All {total_expected} expected files present")
120
+ else:
121
+ console.print(f" [yellow]⚠[/yellow] {total_missing} of {total_expected} files missing")
122
+
123
+ # Show missing by category
124
+ for category in ["commands", "templates", "scripts"]:
125
+ cat_missing = [f for f, c in file_check["missing"].items() if c == category]
126
+ if cat_missing:
127
+ console.print(f" Missing {category}:")
128
+ for file in cat_missing[:3]: # Show first 3
129
+ console.print(f" - {file}")
130
+ if len(cat_missing) > 3:
131
+ console.print(f" ... and {len(cat_missing) - 3} more")
132
+
133
+ # 3. Worktree Status Overview
134
+ worktree_summary = worktree_status.get_worktree_summary()
135
+ output_data["worktree_status"] = worktree_summary
136
+
137
+ if not json_output:
138
+ console.print("\n[cyan]3. Worktree Overview[/cyan]")
139
+ console.print(f" Total features: {worktree_summary['total_features']}")
140
+ console.print(f" Active worktrees: {worktree_summary['active_worktrees']}")
141
+ console.print(f" Merged features: {worktree_summary['merged_features']}")
142
+ console.print(f" In development: {worktree_summary['in_development']}")
143
+
144
+ # 4. Feature Detection and Analysis
145
+ try:
146
+ from .acceptance import detect_feature_slug, AcceptanceError
147
+ feature_slug = (feature or detect_feature_slug(repo_root, cwd=cwd)).strip()
148
+
149
+ output_data["feature_detection"] = {
150
+ "detected": True,
151
+ "feature": feature_slug
152
+ }
153
+
154
+ # Get detailed status for this feature
155
+ feature_status = worktree_status.get_feature_status(feature_slug)
156
+ output_data["feature_analysis"] = feature_status
157
+
158
+ if not json_output:
159
+ console.print("\n[cyan]4. Current Feature Status[/cyan]")
160
+ console.print(f" Feature: {feature_slug}")
161
+ console.print(f" State: {feature_status['state'].upper()}")
162
+
163
+ # Status indicators
164
+ if feature_status["branch_exists"]:
165
+ status_text = "merged" if feature_status["branch_merged"] else "active"
166
+ console.print(f" [green]✓[/green] Branch exists ({status_text})")
167
+ else:
168
+ console.print(f" [dim]○[/dim] No branch")
169
+
170
+ if feature_status["worktree_exists"]:
171
+ console.print(f" [green]✓[/green] Worktree at: {feature_status['worktree_path']}")
172
+ else:
173
+ console.print(f" [dim]○[/dim] No worktree")
174
+
175
+ # Artifacts
176
+ if feature_status["artifacts_in_main"]:
177
+ console.print(f" Artifacts in main: {', '.join(feature_status['artifacts_in_main'])}")
178
+ if feature_status["artifacts_in_worktree"]:
179
+ console.print(f" Artifacts in worktree: {', '.join(feature_status['artifacts_in_worktree'])}")
180
+
181
+ # State-based observations
182
+ if feature_status["state"] == "merged":
183
+ console.print(" [green]✓[/green] Feature appears to be merged")
184
+ elif feature_status["state"] == "in_development":
185
+ console.print(" [blue]●[/blue] Feature is in active development")
186
+ elif feature_status["state"] == "not_started":
187
+ console.print(" [dim]○[/dim] Feature not yet started")
188
+
189
+ except AcceptanceError as exc:
190
+ output_data["feature_detection"] = {
191
+ "detected": False,
192
+ "error": str(exc)
193
+ }
194
+
195
+ if not json_output:
196
+ console.print("\n[cyan]4. Feature Detection[/cyan]")
197
+ console.print(f" [yellow]⚠[/yellow] Could not detect feature: {exc}")
198
+
199
+ # 5. All Features Status Table
200
+ all_features = worktree_status.get_all_features()
201
+
202
+ if not json_output and all_features:
203
+ console.print("\n[cyan]5. All Features Status[/cyan]")
204
+
205
+ table = Table(show_header=True, header_style="bold")
206
+ table.add_column("Feature", style="cyan")
207
+ table.add_column("State", style="white")
208
+ table.add_column("Branch", style="white")
209
+ table.add_column("Worktree", style="white")
210
+ table.add_column("Artifacts", style="white")
211
+
212
+ for feat in all_features[:10]: # Show first 10
213
+ feat_status = worktree_status.get_feature_status(feat)
214
+
215
+ # Determine display values
216
+ state_display = {
217
+ "merged": "[green]MERGED[/green]",
218
+ "in_development": "[yellow]ACTIVE[/yellow]",
219
+ "ready_to_merge": "[blue]READY[/blue]",
220
+ "not_started": "[dim]NOT STARTED[/dim]",
221
+ "unknown": "[dim]?[/dim]"
222
+ }.get(feat_status["state"], feat_status["state"])
223
+
224
+ branch_display = "✓" if feat_status["branch_exists"] else "-"
225
+ if feat_status["branch_merged"]:
226
+ branch_display = "merged"
227
+
228
+ worktree_display = "✓" if feat_status["worktree_exists"] else "-"
229
+
230
+ artifact_count = len(feat_status["artifacts_in_main"]) + len(feat_status["artifacts_in_worktree"])
231
+ artifacts_display = str(artifact_count) if artifact_count > 0 else "-"
232
+
233
+ table.add_row(
234
+ feat,
235
+ state_display,
236
+ branch_display,
237
+ worktree_display,
238
+ artifacts_display
239
+ )
240
+
241
+ console.print(table)
242
+
243
+ if len(all_features) > 10:
244
+ console.print(f" [dim]... and {len(all_features) - 10} more features[/dim]")
245
+
246
+ # 6. Observations (not recommendations)
247
+ observations = []
248
+
249
+ if current_branch == "main" and in_worktree:
250
+ observations.append("Unusual: In worktree but on main branch")
251
+
252
+ if output_data.get("feature_analysis", {}).get("state") == "in_development":
253
+ if not output_data["feature_analysis"].get("worktree_exists"):
254
+ observations.append(f"Feature {feature_slug} has no worktree but has development artifacts")
255
+
256
+ if total_missing > 0 and check_files:
257
+ observations.append(f"Mission integrity: {total_missing} expected files not found")
258
+
259
+ output_data["observations"] = observations
260
+
261
+ if not json_output and observations:
262
+ console.print("\n[cyan]6. Observations[/cyan]")
263
+ for obs in observations:
264
+ console.print(f" • {obs}")
265
+
266
+ # Final summary
267
+ if not json_output:
268
+ console.print("\n[bold green]✓ Verification complete[/bold green]\n")
269
+
270
+ return output_data
@@ -0,0 +1,224 @@
1
+ """Workspace context management for runtime visibility.
2
+
3
+ This module manages persistent workspace context files stored in .kittify/workspaces/.
4
+ These files provide runtime visibility into workspace state for LLM agents and CLI tools.
5
+
6
+ Context files are:
7
+ - Created during `spec-kitty implement` command
8
+ - Stored in main repo's .kittify/workspaces/ directory
9
+ - Readable from both main repo and worktrees (via relative path)
10
+ - Cleaned up during merge or explicit workspace deletion
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ from dataclasses import asdict, dataclass
17
+ from datetime import datetime, timezone
18
+ from pathlib import Path
19
+ from typing import Any, Dict
20
+
21
+
22
+ @dataclass
23
+ class WorkspaceContext:
24
+ """
25
+ Runtime context for a work package workspace.
26
+
27
+ Provides all information an agent needs to understand workspace state.
28
+ Stored as JSON in .kittify/workspaces/###-feature-WP##.json
29
+ """
30
+
31
+ # Identity
32
+ wp_id: str # e.g., "WP02"
33
+ feature_slug: str # e.g., "010-workspace-per-wp"
34
+
35
+ # Paths
36
+ worktree_path: str # Relative path from repo root (e.g., ".worktrees/010-feature-WP02")
37
+ branch_name: str # Git branch name (e.g., "010-feature-WP02")
38
+
39
+ # Base tracking
40
+ base_branch: str # Branch this was created from (e.g., "010-feature-WP01" or "main")
41
+ base_commit: str # Git SHA this was created from
42
+
43
+ # Dependencies
44
+ dependencies: list[str] # List of WP IDs this depends on (e.g., ["WP01"])
45
+
46
+ # Metadata
47
+ created_at: str # ISO timestamp when workspace was created
48
+ created_by: str # Command that created this (e.g., "implement-command")
49
+ vcs_backend: str # "git" or "jj"
50
+
51
+ def to_dict(self) -> Dict[str, Any]:
52
+ """Convert to dictionary for JSON serialization."""
53
+ return asdict(self)
54
+
55
+ @classmethod
56
+ def from_dict(cls, data: Dict[str, Any]) -> WorkspaceContext:
57
+ """Create from dictionary (JSON deserialization)."""
58
+ return cls(**data)
59
+
60
+
61
+ def get_workspaces_dir(repo_root: Path) -> Path:
62
+ """Get or create the workspaces context directory.
63
+
64
+ Args:
65
+ repo_root: Repository root path
66
+
67
+ Returns:
68
+ Path to .kittify/workspaces/ directory
69
+ """
70
+ workspaces_dir = repo_root / ".kittify" / "workspaces"
71
+ workspaces_dir.mkdir(parents=True, exist_ok=True)
72
+ return workspaces_dir
73
+
74
+
75
+ def get_context_path(repo_root: Path, workspace_name: str) -> Path:
76
+ """Get path to workspace context file.
77
+
78
+ Args:
79
+ repo_root: Repository root path
80
+ workspace_name: Workspace name (e.g., "010-feature-WP02")
81
+
82
+ Returns:
83
+ Path to context JSON file
84
+ """
85
+ workspaces_dir = get_workspaces_dir(repo_root)
86
+ return workspaces_dir / f"{workspace_name}.json"
87
+
88
+
89
+ def save_context(repo_root: Path, context: WorkspaceContext) -> Path:
90
+ """Save workspace context to JSON file.
91
+
92
+ Args:
93
+ repo_root: Repository root path
94
+ context: Workspace context to save
95
+
96
+ Returns:
97
+ Path to saved context file
98
+ """
99
+ workspace_name = f"{context.feature_slug}-{context.wp_id}"
100
+ context_path = get_context_path(repo_root, workspace_name)
101
+
102
+ # Write JSON with pretty formatting
103
+ context_path.write_text(
104
+ json.dumps(context.to_dict(), indent=2) + "\n",
105
+ encoding="utf-8"
106
+ )
107
+
108
+ return context_path
109
+
110
+
111
+ def load_context(repo_root: Path, workspace_name: str) -> WorkspaceContext | None:
112
+ """Load workspace context from JSON file.
113
+
114
+ Args:
115
+ repo_root: Repository root path
116
+ workspace_name: Workspace name (e.g., "010-feature-WP02")
117
+
118
+ Returns:
119
+ WorkspaceContext if file exists, None otherwise
120
+ """
121
+ context_path = get_context_path(repo_root, workspace_name)
122
+
123
+ if not context_path.exists():
124
+ return None
125
+
126
+ try:
127
+ data = json.loads(context_path.read_text(encoding="utf-8"))
128
+ return WorkspaceContext.from_dict(data)
129
+ except (json.JSONDecodeError, TypeError, KeyError):
130
+ # Malformed context file
131
+ return None
132
+
133
+
134
+ def delete_context(repo_root: Path, workspace_name: str) -> bool:
135
+ """Delete workspace context file.
136
+
137
+ Args:
138
+ repo_root: Repository root path
139
+ workspace_name: Workspace name (e.g., "010-feature-WP02")
140
+
141
+ Returns:
142
+ True if deleted, False if didn't exist
143
+ """
144
+ context_path = get_context_path(repo_root, workspace_name)
145
+
146
+ if context_path.exists():
147
+ context_path.unlink()
148
+ return True
149
+
150
+ return False
151
+
152
+
153
+ def list_contexts(repo_root: Path) -> list[WorkspaceContext]:
154
+ """List all workspace contexts.
155
+
156
+ Args:
157
+ repo_root: Repository root path
158
+
159
+ Returns:
160
+ List of all workspace contexts (empty if none exist)
161
+ """
162
+ workspaces_dir = get_workspaces_dir(repo_root)
163
+
164
+ if not workspaces_dir.exists():
165
+ return []
166
+
167
+ contexts = []
168
+ for context_file in workspaces_dir.glob("*.json"):
169
+ workspace_name = context_file.stem
170
+ context = load_context(repo_root, workspace_name)
171
+ if context:
172
+ contexts.append(context)
173
+
174
+ return contexts
175
+
176
+
177
+ def find_orphaned_contexts(repo_root: Path) -> list[tuple[str, WorkspaceContext]]:
178
+ """Find context files for workspaces that no longer exist.
179
+
180
+ Args:
181
+ repo_root: Repository root path
182
+
183
+ Returns:
184
+ List of (workspace_name, context) tuples for orphaned contexts
185
+ """
186
+ orphaned = []
187
+
188
+ for context in list_contexts(repo_root):
189
+ workspace_path = repo_root / context.worktree_path
190
+ if not workspace_path.exists():
191
+ workspace_name = f"{context.feature_slug}-{context.wp_id}"
192
+ orphaned.append((workspace_name, context))
193
+
194
+ return orphaned
195
+
196
+
197
+ def cleanup_orphaned_contexts(repo_root: Path) -> int:
198
+ """Remove context files for deleted workspaces.
199
+
200
+ Args:
201
+ repo_root: Repository root path
202
+
203
+ Returns:
204
+ Number of orphaned contexts cleaned up
205
+ """
206
+ orphaned = find_orphaned_contexts(repo_root)
207
+
208
+ for workspace_name, _ in orphaned:
209
+ delete_context(repo_root, workspace_name)
210
+
211
+ return len(orphaned)
212
+
213
+
214
+ __all__ = [
215
+ "WorkspaceContext",
216
+ "get_workspaces_dir",
217
+ "get_context_path",
218
+ "save_context",
219
+ "load_context",
220
+ "delete_context",
221
+ "list_contexts",
222
+ "find_orphaned_contexts",
223
+ "cleanup_orphaned_contexts",
224
+ ]