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,310 @@
1
+ """Verify setup command implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+ from typing import Any, Optional
8
+
9
+ import typer
10
+ from rich.panel import Panel
11
+ from rich.table import Table
12
+
13
+ from specify_cli.cli import StepTracker
14
+ from specify_cli.cli.helpers import check_version_compatibility, console, get_project_root_or_exit
15
+ from specify_cli.core.tool_checker import check_tool_for_tracker
16
+ from specify_cli.dashboard.diagnostics import run_diagnostics
17
+ from specify_cli.tasks_support import TaskCliError, find_repo_root
18
+ from specify_cli.verify_enhanced import run_enhanced_verify
19
+
20
+ TOOL_LABELS = [
21
+ ("git", "Git version control"),
22
+ ("claude", "Claude Code CLI"),
23
+ ("gemini", "Gemini CLI"),
24
+ ("qwen", "Qwen Code CLI"),
25
+ ("code", "Visual Studio Code"),
26
+ ("code-insiders", "Visual Studio Code Insiders"),
27
+ ("cursor-agent", "Cursor IDE agent"),
28
+ ("windsurf", "Windsurf IDE"),
29
+ ("kilocode", "Kilo Code IDE"),
30
+ ("opencode", "opencode"),
31
+ ("codex", "Codex CLI"),
32
+ ("auggie", "Auggie CLI"),
33
+ ("q", "Amazon Q Developer CLI"),
34
+ ]
35
+
36
+
37
+ def verify_setup(
38
+ feature: Optional[str] = typer.Option(None, "--feature", help="Feature slug to verify (auto-detected when omitted)"),
39
+ json_output: bool = typer.Option(False, "--json", help="Output in JSON format for AI agents"),
40
+ check_files: bool = typer.Option(True, "--check-files", help="Check mission file integrity"),
41
+ check_tools: bool = typer.Option(True, "--check-tools", help="Check for installed development tools"),
42
+ diagnostics: bool = typer.Option(False, "--diagnostics", help="Show detailed diagnostics with dashboard health"),
43
+ ) -> None:
44
+ """Verify that the current environment matches Spec Kitty expectations."""
45
+ output_data: dict[str, object] = {}
46
+
47
+ # If diagnostics mode requested, use diagnostics output
48
+ if diagnostics:
49
+ _run_diagnostics_mode(json_output, check_tools)
50
+ return
51
+
52
+ # Check tools if requested
53
+ tool_statuses = {}
54
+ if check_tools:
55
+ if not json_output:
56
+ console.print("[bold]Checking for installed tools...[/bold]\n")
57
+
58
+ tracker = StepTracker("Check Available Tools")
59
+ for key, label in TOOL_LABELS:
60
+ tracker.add(key, label)
61
+
62
+ tool_statuses = {key: check_tool_for_tracker(key, tracker) for key, _ in TOOL_LABELS}
63
+
64
+ if not json_output:
65
+ console.print(tracker.render())
66
+ console.print()
67
+
68
+ if not tool_statuses.get("git", False):
69
+ console.print("[dim]Tip: Install git for repository management[/dim]")
70
+ if not any(tool_statuses[key] for key in tool_statuses if key != "git"):
71
+ console.print("[dim]Tip: Install an AI assistant for the best experience[/dim]")
72
+ console.print()
73
+
74
+ try:
75
+ repo_root = find_repo_root()
76
+ except TaskCliError as exc:
77
+ if json_output:
78
+ output_data["error"] = str(exc)
79
+ if check_tools:
80
+ output_data["tools"] = {key: {"available": available} for key, available in tool_statuses.items()}
81
+ print(json.dumps(output_data))
82
+ else:
83
+ console.print(f"[red]✗[/red] Repository detection failed: {exc}")
84
+ console.print(
85
+ "\n[yellow]Solution:[/yellow] Run this command from a Spec Kitty project root or from a feature worktree inside .worktrees/<feature>/ (use 'spec-kitty init <name>' to create a project)."
86
+ )
87
+ raise typer.Exit(1)
88
+
89
+ project_root = get_project_root_or_exit(repo_root)
90
+ check_version_compatibility(project_root, "verify")
91
+ cwd = Path.cwd()
92
+
93
+ result = run_enhanced_verify(
94
+ repo_root=repo_root,
95
+ project_root=project_root,
96
+ cwd=cwd,
97
+ feature=feature,
98
+ json_output=json_output,
99
+ check_files=check_files,
100
+ console=console,
101
+ )
102
+
103
+ # Add tool checking results to JSON output
104
+ if json_output and check_tools:
105
+ result["tools"] = {key: {"available": available} for key, available in tool_statuses.items()}
106
+
107
+ if json_output:
108
+ print(json.dumps(result, indent=2))
109
+ return
110
+
111
+ return
112
+
113
+
114
+ def _run_diagnostics_mode(json_output: bool, check_tools: bool) -> None:
115
+ """Run diagnostics mode with detailed health information."""
116
+ try:
117
+ project_path = Path.cwd()
118
+ diag = run_diagnostics(project_path)
119
+
120
+ # Add tool checking if requested
121
+ if check_tools:
122
+ tracker = StepTracker("Check Available Tools")
123
+ for key, label in TOOL_LABELS:
124
+ tracker.add(key, label)
125
+ tool_statuses = {key: check_tool_for_tracker(key, tracker) for key, _ in TOOL_LABELS}
126
+ diag["tools"] = {key: {"available": available} for key, available in tool_statuses.items()}
127
+
128
+ if json_output:
129
+ # Machine-readable output for scripts and tools
130
+ console.print(json.dumps(diag, indent=2, default=str))
131
+ else:
132
+ # Human-readable output with Rich panels
133
+ _print_diagnostics(diag, check_tools)
134
+
135
+ except Exception as exc:
136
+ if json_output:
137
+ error_output = {
138
+ "status": "error",
139
+ "message": str(exc),
140
+ }
141
+ console.print(json.dumps(error_output, indent=2))
142
+ else:
143
+ console.print(f"[red]✗ Diagnostics failed:[/red] {exc}")
144
+ raise typer.Exit(1)
145
+
146
+
147
+ def _print_diagnostics(diag: dict[str, Any], check_tools: bool) -> None:
148
+ """Print diagnostics in human-readable format using Rich panels."""
149
+ # Tool checking first if enabled
150
+ if check_tools and "tools" in diag:
151
+ tool_statuses = {k: v["available"] for k, v in diag["tools"].items()}
152
+
153
+ console.print("[bold]Checking for installed tools...[/bold]\n")
154
+ tracker = StepTracker("Check Available Tools")
155
+ for key, label in TOOL_LABELS:
156
+ tracker.add(key, label)
157
+ if tool_statuses.get(key):
158
+ tracker.complete(key, "available")
159
+ else:
160
+ tracker.skip(key, "not found")
161
+
162
+ console.print(tracker.render())
163
+ console.print()
164
+
165
+ if not tool_statuses.get("git", False):
166
+ console.print("[dim]Tip: Install git for repository management[/dim]")
167
+ if not any(tool_statuses[key] for key in tool_statuses if key != "git"):
168
+ console.print("[dim]Tip: Install an AI assistant for the best experience[/dim]")
169
+ console.print()
170
+
171
+ # Project info panel
172
+ project_info = f"""
173
+ [bold]Project Path:[/bold] {diag['project_path']}
174
+ [bold]Current Directory:[/bold] {diag['current_working_directory']}
175
+ [bold]Git Branch:[/bold] {diag.get('git_branch') or '[yellow]Not detected[/yellow]'}
176
+ [bold]Active Mission:[/bold] {diag.get('active_mission') or '[yellow]None[/yellow]'}
177
+ """
178
+ console.print(Panel(project_info.strip(), title="Project Information", border_style="cyan"))
179
+
180
+ # File integrity
181
+ file_integrity = diag.get("file_integrity", {})
182
+ total_expected = file_integrity.get("total_expected", 0)
183
+ total_present = file_integrity.get("total_present", 0)
184
+ total_missing = file_integrity.get("total_missing", 0)
185
+
186
+ if total_missing == 0:
187
+ integrity_status = "[green]✓ All files present[/green]"
188
+ else:
189
+ integrity_status = f"[yellow]⚠ {total_missing} files missing[/yellow]"
190
+
191
+ file_info = f"""
192
+ [bold]Files:[/bold] {total_present}/{total_expected} present {integrity_status}
193
+ """
194
+
195
+ if file_integrity.get("missing_files"):
196
+ file_info += f"\n[red]Missing:[/red]\n"
197
+ for missing in file_integrity.get("missing_files", [])[:5]:
198
+ file_info += f" • {missing}\n"
199
+ if len(file_integrity.get("missing_files", [])) > 5:
200
+ file_info += f" ... and {len(file_integrity.get('missing_files', [])) - 5} more\n"
201
+
202
+ console.print(Panel(file_info.strip(), title="File Integrity", border_style="cyan"))
203
+
204
+ # Worktree overview
205
+ worktree_overview = diag.get("worktree_overview", {})
206
+ in_worktree = diag.get("in_worktree", False)
207
+ worktrees_exist = diag.get("worktrees_exist", False)
208
+
209
+ worktree_info = f"""
210
+ [bold]Worktrees Exist:[/bold] {'[green]Yes[/green]' if worktrees_exist else '[red]No[/red]'}
211
+ [bold]Currently in Worktree:[/bold] {'[green]Yes[/green]' if in_worktree else '[red]No[/red]'}
212
+ [bold]Active Worktrees:[/bold] {worktree_overview.get('active_worktrees', 0)}
213
+ [bold]Total Features:[/bold] {worktree_overview.get('total_features', 0)}
214
+ """
215
+ console.print(Panel(worktree_info.strip(), title="Worktrees", border_style="cyan"))
216
+
217
+ # Dashboard health
218
+ dashboard_health = diag.get("dashboard_health", {})
219
+ metadata_exists = dashboard_health.get("metadata_exists", False)
220
+ startup_test = dashboard_health.get("startup_test")
221
+
222
+ if metadata_exists:
223
+ responding = dashboard_health.get("responding", False)
224
+ dashboard_info = f"""
225
+ [bold]Metadata File:[/bold] {'[green]Exists[/green]' if metadata_exists else '[red]Missing[/red]'}
226
+ [bold]Port:[/bold] {dashboard_health.get('port', 'Unknown')}
227
+ [bold]Process PID:[/bold] {dashboard_health.get('pid', 'Not tracked')}
228
+ [bold]Responding:[/bold] {'[green]Yes[/green]' if responding else '[red]No[/red]'}
229
+ """
230
+ if not responding:
231
+ dashboard_info += f"[red]⚠️ Dashboard is not responding - may need restart[/red]\n"
232
+ else:
233
+ # No dashboard - show startup test results
234
+ if startup_test == 'SUCCESS':
235
+ dashboard_info = f"""
236
+ [bold]Status:[/bold] [green]Can start successfully[/green]
237
+ [bold]Test Port:[/bold] {dashboard_health.get('test_port', 'N/A')}
238
+ """
239
+ elif startup_test == 'FAILED':
240
+ dashboard_info = f"""
241
+ [bold]Status:[/bold] [red]Cannot start[/red]
242
+ [bold]Error:[/bold] {dashboard_health.get('startup_error', 'Unknown')}
243
+ [red]⚠️ Dashboard startup is broken for this project[/red]
244
+ """
245
+ else:
246
+ dashboard_info = "[yellow]Dashboard not running (startup not tested)[/yellow]"
247
+
248
+ console.print(Panel(dashboard_info.strip(), title="Dashboard Health", border_style="cyan"))
249
+
250
+ # Current feature
251
+ current_feature = diag.get("current_feature", {})
252
+ if current_feature.get("detected"):
253
+ feature_info = f"""
254
+ [bold]Detected Feature:[/bold] {current_feature.get('name')}
255
+ [bold]State:[/bold] {current_feature.get('state')}
256
+ [bold]Branch Exists:[/bold] {'[green]Yes[/green]' if current_feature.get('branch_exists') else '[red]No[/red]'}
257
+ [bold]Worktree Exists:[/bold] {'[green]Yes[/green]' if current_feature.get('worktree_exists') else '[red]No[/red]'}
258
+ """
259
+ else:
260
+ feature_info = "[yellow]No feature detected in current context[/yellow]"
261
+
262
+ console.print(Panel(feature_info.strip(), title="Current Feature", border_style="cyan"))
263
+
264
+ # All features table
265
+ all_features = diag.get("all_features", [])
266
+ if all_features:
267
+ table = Table(title="All Features", show_lines=False, header_style="bold cyan")
268
+ table.add_column("Feature", style="bright_cyan")
269
+ table.add_column("State", style="bright_white")
270
+ table.add_column("Branch", justify="center")
271
+ table.add_column("Merged", justify="center")
272
+ table.add_column("Worktree", justify="center")
273
+
274
+ for feature in all_features:
275
+ branch_emoji = "✓" if feature.get("branch_exists") else "✗"
276
+ merged_emoji = "✓" if feature.get("branch_merged") else "○"
277
+ worktree_emoji = "✓" if feature.get("worktree_exists") else "✗"
278
+
279
+ table.add_row(
280
+ feature.get("name", "Unknown"),
281
+ feature.get("state", "Unknown"),
282
+ branch_emoji,
283
+ merged_emoji,
284
+ worktree_emoji,
285
+ )
286
+
287
+ console.print(table)
288
+ else:
289
+ console.print("[yellow]No features found[/yellow]")
290
+
291
+ # Observations and issues
292
+ observations = diag.get("observations", [])
293
+ issues = diag.get("issues", [])
294
+
295
+ if observations or issues:
296
+ console.print()
297
+ if observations:
298
+ console.print("[bold cyan]📝 Observations:[/bold cyan]")
299
+ for obs in observations:
300
+ console.print(f" • {obs}")
301
+
302
+ if issues:
303
+ console.print("[bold red]⚠️ Issues:[/bold red]")
304
+ for issue in issues:
305
+ console.print(f" • {issue}")
306
+ else:
307
+ console.print("\n[bold green]✓ No issues or observations[/bold green]")
308
+
309
+
310
+ __all__ = ["verify_setup"]
@@ -0,0 +1,123 @@
1
+ """Shared CLI helpers for Spec Kitty commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from importlib.metadata import PackageNotFoundError, version
7
+ from pathlib import Path
8
+
9
+ import typer
10
+ from rich.align import Align
11
+ from rich.console import Console
12
+ from rich.text import Text
13
+ from typer.core import TyperGroup
14
+
15
+ from specify_cli.core.config import BANNER
16
+ from specify_cli.core.project_resolver import locate_project_root
17
+
18
+ console = Console()
19
+ TAGLINE = "Spec Kitty - Spec-Driven Development Toolkit (forked from GitHub Spec Kit)"
20
+
21
+
22
+ class BannerGroup(TyperGroup):
23
+ """Custom Typer group that renders the banner before help output."""
24
+
25
+ def format_help(self, ctx, formatter):
26
+ show_banner()
27
+ super().format_help(ctx, formatter)
28
+
29
+
30
+ def show_banner() -> None:
31
+ """Display the ASCII art banner with gradient styling."""
32
+ banner_lines = BANNER.strip().split("\n")
33
+ colors = ["bright_blue", "blue", "cyan", "bright_cyan", "white", "bright_white"]
34
+ max_width = max((len(line) for line in banner_lines), default=0)
35
+
36
+ styled_banner = Text()
37
+ for index, line in enumerate(banner_lines):
38
+ color = colors[index % len(colors)]
39
+ padded_line = line.ljust(max_width)
40
+ styled_banner.append(padded_line + "\n", style=color)
41
+
42
+ try:
43
+ pkg_version = version("spec-kitty-cli")
44
+ version_text = f"v{pkg_version}"
45
+ except PackageNotFoundError:
46
+ version_text = "dev"
47
+
48
+ console.print(Align.center(styled_banner))
49
+ console.print(Align.center(Text(TAGLINE, style="italic bright_yellow")))
50
+ console.print(Align.center(Text(version_text, style="dim cyan")))
51
+ console.print()
52
+
53
+
54
+ def callback(ctx: typer.Context) -> None:
55
+ """Display the banner when CLI is invoked without a subcommand."""
56
+ if ctx.invoked_subcommand is None and "--help" not in sys.argv and "-h" not in sys.argv:
57
+ show_banner()
58
+ console.print(Align.center("[dim]Run 'spec-kitty --help' for usage information[/dim]"))
59
+ console.print()
60
+
61
+
62
+ def get_project_root_or_exit(start: Path | None = None) -> Path:
63
+ """Return the project root or exit when .kittify cannot be located."""
64
+ project_root = locate_project_root(start)
65
+ if project_root is None:
66
+ console.print("[red]Error:[/red] Unable to locate the Spec Kitty project root (.kittify directory not found).")
67
+ console.print("[dim]Run this command from the project root or from a feature worktree under .worktrees/<feature>/.[/dim]")
68
+ console.print("[dim]Tip: Initialize a project with 'spec-kitty init <name>' if one does not exist.[/dim]")
69
+ raise typer.Exit(1)
70
+ return project_root
71
+
72
+
73
+ def check_version_compatibility(project_root: Path, command_name: str) -> None:
74
+ """Check CLI/project version compatibility and exit if mismatch.
75
+
76
+ Args:
77
+ project_root: Path to project root (.kittify parent)
78
+ command_name: Name of command being run (for should_check_version)
79
+
80
+ Raises:
81
+ typer.Exit(1) if version mismatch detected
82
+ """
83
+ from specify_cli.core.version_checker import (
84
+ get_cli_version,
85
+ get_project_version,
86
+ compare_versions,
87
+ format_version_error,
88
+ should_check_version,
89
+ )
90
+
91
+ # Skip check for certain commands
92
+ if not should_check_version(command_name):
93
+ return
94
+
95
+ cli_version = get_cli_version()
96
+ project_version = get_project_version(project_root)
97
+
98
+ # Handle missing metadata (legacy project)
99
+ if project_version is None:
100
+ console.print("[yellow]Warning:[/yellow] Project metadata not found (.kittify/metadata.yaml)")
101
+ console.print("[yellow]Please run:[/yellow] spec-kitty upgrade")
102
+ console.print()
103
+ return # Warn but don't block
104
+
105
+ comparison, mismatch_type = compare_versions(cli_version, project_version)
106
+
107
+ # Handle version mismatches
108
+ if mismatch_type != "match":
109
+ if mismatch_type == "unknown":
110
+ console.print("[yellow]Warning:[/yellow] Unable to determine version compatibility")
111
+ console.print(f" CLI version: {cli_version}")
112
+ console.print(f" Project version: {project_version}")
113
+ console.print()
114
+ return # Warn but don't block
115
+
116
+ # Hard error for known version mismatches
117
+ error_msg = format_version_error(cli_version, project_version, mismatch_type)
118
+ console.print(error_msg)
119
+ console.print()
120
+ raise typer.Exit(1)
121
+
122
+
123
+ __all__ = ["BannerGroup", "callback", "check_version_compatibility", "console", "get_project_root_or_exit", "show_banner"]
@@ -0,0 +1,91 @@
1
+ """Progress tracking helpers for Spec Kitty CLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from rich.tree import Tree
6
+
7
+
8
+ class StepTracker:
9
+ """Track and render hierarchical steps with Rich trees."""
10
+
11
+ def __init__(self, title: str):
12
+ self.title = title
13
+ self.steps = [] # list of dicts: {key, label, status, detail}
14
+ self.status_order = {"pending": 0, "running": 1, "done": 2, "error": 3, "skipped": 4}
15
+ self._refresh_cb = None # callable to trigger UI refresh
16
+
17
+ def attach_refresh(self, cb):
18
+ self._refresh_cb = cb
19
+
20
+ def add(self, key: str, label: str):
21
+ if key not in [s["key"] for s in self.steps]:
22
+ self.steps.append({"key": key, "label": label, "status": "pending", "detail": ""})
23
+ self._maybe_refresh()
24
+
25
+ def start(self, key: str, detail: str = ""):
26
+ self._update(key, status="running", detail=detail)
27
+
28
+ def complete(self, key: str, detail: str = ""):
29
+ self._update(key, status="done", detail=detail)
30
+
31
+ def error(self, key: str, detail: str = ""):
32
+ self._update(key, status="error", detail=detail)
33
+
34
+ def skip(self, key: str, detail: str = ""):
35
+ self._update(key, status="skipped", detail=detail)
36
+
37
+ def _update(self, key: str, status: str, detail: str):
38
+ for s in self.steps:
39
+ if s["key"] == key:
40
+ s["status"] = status
41
+ if detail:
42
+ s["detail"] = detail
43
+ self._maybe_refresh()
44
+ return
45
+ # If not present, add it
46
+ self.steps.append({"key": key, "label": key, "status": status, "detail": detail})
47
+ self._maybe_refresh()
48
+
49
+ def _maybe_refresh(self):
50
+ if self._refresh_cb:
51
+ try:
52
+ self._refresh_cb()
53
+ except Exception:
54
+ pass
55
+
56
+ def render(self) -> Tree:
57
+ tree = Tree(f"[cyan]{self.title}[/cyan]", guide_style="grey50")
58
+ for step in self.steps:
59
+ label = step["label"]
60
+ detail_text = step["detail"].strip() if step["detail"] else ""
61
+
62
+ status = step["status"]
63
+ if status == "done":
64
+ symbol = "[green]●[/green]"
65
+ elif status == "pending":
66
+ symbol = "[green dim]○[/green dim]"
67
+ elif status == "running":
68
+ symbol = "[cyan]○[/cyan]"
69
+ elif status == "error":
70
+ symbol = "[red]●[/red]"
71
+ elif status == "skipped":
72
+ symbol = "[yellow]○[/yellow]"
73
+ else:
74
+ symbol = " "
75
+
76
+ if status == "pending":
77
+ if detail_text:
78
+ line = f"{symbol} [bright_black]{label} ({detail_text})[/bright_black]"
79
+ else:
80
+ line = f"{symbol} [bright_black]{label}[/bright_black]"
81
+ else:
82
+ if detail_text:
83
+ line = f"{symbol} [white]{label}[/white] [bright_black]({detail_text})[/bright_black]"
84
+ else:
85
+ line = f"{symbol} [white]{label}[/white]"
86
+
87
+ tree.add(line)
88
+ return tree
89
+
90
+
91
+ __all__ = ["StepTracker"]