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,246 @@
1
+ """Context command - query workspace context information.
2
+
3
+ Provides visibility into current workspace context for LLM agents and users.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ from pathlib import Path
10
+
11
+ import typer
12
+ from rich.console import Console
13
+ from rich.table import Table
14
+
15
+ from specify_cli.tasks_support import find_repo_root, TaskCliError
16
+ from specify_cli.workspace_context import (
17
+ cleanup_orphaned_contexts,
18
+ find_orphaned_contexts,
19
+ list_contexts,
20
+ load_context,
21
+ )
22
+
23
+ console = Console()
24
+ app = typer.Typer(help="Query workspace context information")
25
+
26
+
27
+ def detect_current_workspace(cwd: Path, repo_root: Path) -> str | None:
28
+ """Detect if current directory is inside a worktree.
29
+
30
+ Args:
31
+ cwd: Current working directory
32
+ repo_root: Repository root path
33
+
34
+ Returns:
35
+ Workspace name if inside worktree, None otherwise
36
+ """
37
+ # Check if .worktrees is in path
38
+ if ".worktrees" not in cwd.parts:
39
+ return None
40
+
41
+ # Extract workspace name from path
42
+ for i, part in enumerate(cwd.parts):
43
+ if part == ".worktrees" and i + 1 < len(cwd.parts):
44
+ return cwd.parts[i + 1]
45
+
46
+ return None
47
+
48
+
49
+ @app.command(name="info")
50
+ def info_command(
51
+ workspace: str = typer.Option(None, "--workspace", "-w", help="Workspace name (auto-detected if inside worktree)"),
52
+ json_output: bool = typer.Option(False, "--json", help="Output in JSON format"),
53
+ ) -> None:
54
+ """Show context information for current or specified workspace.
55
+
56
+ Examples:
57
+ # Auto-detect from current directory (if inside worktree)
58
+ spec-kitty context info
59
+
60
+ # Explicit workspace
61
+ spec-kitty context info --workspace 010-feature-WP02
62
+
63
+ # JSON output
64
+ spec-kitty context info --json
65
+ """
66
+ try:
67
+ repo_root = find_repo_root()
68
+ except TaskCliError as e:
69
+ console.print(f"[red]Error:[/red] {e}")
70
+ raise typer.Exit(1)
71
+
72
+ # Auto-detect workspace if not provided
73
+ if workspace is None:
74
+ workspace = detect_current_workspace(Path.cwd(), repo_root)
75
+ if workspace is None:
76
+ console.print("[red]Error:[/red] Not inside a worktree and no --workspace specified")
77
+ console.print("\nRun from inside a worktree or use --workspace flag:")
78
+ console.print(" spec-kitty context info --workspace 010-feature-WP02")
79
+ raise typer.Exit(1)
80
+
81
+ # Load context
82
+ context = load_context(repo_root, workspace)
83
+ if context is None:
84
+ console.print(f"[red]Error:[/red] No context found for workspace: {workspace}")
85
+ console.print("\nContext file not found:")
86
+ console.print(f" {repo_root / '.kittify' / 'workspaces' / f'{workspace}.json'}")
87
+ raise typer.Exit(1)
88
+
89
+ # Output
90
+ if json_output:
91
+ print(json.dumps(context.to_dict(), indent=2))
92
+ else:
93
+ console.print("\n[bold cyan]📍 Workspace Context[/bold cyan]")
94
+ console.print("─" * 50)
95
+
96
+ # Build info table
97
+ table = Table(show_header=False, box=None, padding=(0, 2))
98
+ table.add_column("Field", style="dim")
99
+ table.add_column("Value")
100
+
101
+ table.add_row("Work Package", f"[bold]{context.wp_id}[/bold]")
102
+ table.add_row("Feature", context.feature_slug)
103
+ table.add_row("Base Branch", f"[cyan]{context.base_branch}[/cyan]")
104
+ table.add_row("Base Commit", f"[dim]{context.base_commit[:12]}[/dim]")
105
+ table.add_row("Dependencies", ", ".join(context.dependencies) if context.dependencies else "[dim]none[/dim]")
106
+ table.add_row("Created", context.created_at)
107
+ table.add_row("Worktree", context.worktree_path)
108
+ table.add_row("Branch", context.branch_name)
109
+ table.add_row("VCS Backend", context.vcs_backend)
110
+
111
+ console.print(table)
112
+ console.print()
113
+
114
+
115
+ @app.command(name="list")
116
+ def list_command(
117
+ json_output: bool = typer.Option(False, "--json", help="Output in JSON format"),
118
+ show_orphaned: bool = typer.Option(False, "--orphaned", help="Show only orphaned contexts"),
119
+ ) -> None:
120
+ """List all workspace contexts.
121
+
122
+ Examples:
123
+ # List all contexts
124
+ spec-kitty context list
125
+
126
+ # List only orphaned contexts (worktree deleted)
127
+ spec-kitty context list --orphaned
128
+
129
+ # JSON output
130
+ spec-kitty context list --json
131
+ """
132
+ try:
133
+ repo_root = find_repo_root()
134
+ except TaskCliError as e:
135
+ console.print(f"[red]Error:[/red] {e}")
136
+ raise typer.Exit(1)
137
+
138
+ if show_orphaned:
139
+ orphaned = find_orphaned_contexts(repo_root)
140
+ if json_output:
141
+ print(json.dumps([
142
+ {"workspace": name, "context": ctx.to_dict()}
143
+ for name, ctx in orphaned
144
+ ], indent=2))
145
+ else:
146
+ if not orphaned:
147
+ console.print("[green]✓[/green] No orphaned contexts found")
148
+ return
149
+
150
+ console.print(f"\n[yellow]⚠️ Found {len(orphaned)} orphaned context(s)[/yellow]\n")
151
+ for name, ctx in orphaned:
152
+ console.print(f" [bold]{name}[/bold]")
153
+ console.print(f" Worktree: [dim]{ctx.worktree_path} (deleted)[/dim]")
154
+ console.print(f" Created: {ctx.created_at}")
155
+ console.print()
156
+
157
+ console.print("[dim]Clean up with: spec-kitty context cleanup[/dim]\n")
158
+ else:
159
+ contexts = list_contexts(repo_root)
160
+ if json_output:
161
+ print(json.dumps([ctx.to_dict() for ctx in contexts], indent=2))
162
+ else:
163
+ if not contexts:
164
+ console.print("[dim]No workspace contexts found[/dim]")
165
+ return
166
+
167
+ console.print(f"\n[bold]Workspace Contexts[/bold] ({len(contexts)} total)\n")
168
+
169
+ table = Table(show_header=True)
170
+ table.add_column("WP", style="bold")
171
+ table.add_column("Feature", style="dim")
172
+ table.add_column("Base", style="cyan")
173
+ table.add_column("Dependencies")
174
+ table.add_column("Status")
175
+
176
+ for ctx in sorted(contexts, key=lambda c: (c.feature_slug, c.wp_id)):
177
+ # Check if worktree exists
178
+ worktree_path = repo_root / ctx.worktree_path
179
+ status = "[green]Active[/green]" if worktree_path.exists() else "[yellow]Orphaned[/yellow]"
180
+
181
+ deps = ", ".join(ctx.dependencies) if ctx.dependencies else "[dim]none[/dim]"
182
+
183
+ table.add_row(
184
+ ctx.wp_id,
185
+ ctx.feature_slug,
186
+ ctx.base_branch,
187
+ deps,
188
+ status,
189
+ )
190
+
191
+ console.print(table)
192
+ console.print()
193
+
194
+
195
+ @app.command(name="cleanup")
196
+ def cleanup_command(
197
+ dry_run: bool = typer.Option(False, "--dry-run", help="Show what would be cleaned up without deleting"),
198
+ ) -> None:
199
+ """Clean up orphaned workspace contexts.
200
+
201
+ Removes context files for workspaces that no longer exist.
202
+
203
+ Examples:
204
+ # Preview cleanup
205
+ spec-kitty context cleanup --dry-run
206
+
207
+ # Clean up orphaned contexts
208
+ spec-kitty context cleanup
209
+ """
210
+ try:
211
+ repo_root = find_repo_root()
212
+ except TaskCliError as e:
213
+ console.print(f"[red]Error:[/red] {e}")
214
+ raise typer.Exit(1)
215
+
216
+ orphaned = find_orphaned_contexts(repo_root)
217
+
218
+ if not orphaned:
219
+ console.print("[green]✓[/green] No orphaned contexts to clean up")
220
+ return
221
+
222
+ console.print(f"\n[yellow]Found {len(orphaned)} orphaned context(s):[/yellow]\n")
223
+ for name, ctx in orphaned:
224
+ console.print(f" [bold]{name}[/bold]")
225
+ console.print(f" {ctx.worktree_path}")
226
+
227
+ console.print()
228
+
229
+ if dry_run:
230
+ console.print("[dim]Dry run - no files deleted[/dim]")
231
+ console.print(f"[dim]Would delete {len(orphaned)} context file(s)[/dim]")
232
+ else:
233
+ cleaned = cleanup_orphaned_contexts(repo_root)
234
+ console.print(f"[green]✓[/green] Cleaned up {cleaned} orphaned context(s)")
235
+
236
+
237
+ # Default command when no subcommand specified
238
+ @app.callback(invoke_without_command=True)
239
+ def main(ctx: typer.Context):
240
+ """Query workspace context information."""
241
+ if ctx.invoked_subcommand is None:
242
+ # No subcommand - default to "info"
243
+ info_command()
244
+
245
+
246
+ __all__ = ["app"]
@@ -0,0 +1,85 @@
1
+ """Dashboard command implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import webbrowser
6
+ from typing import Optional
7
+
8
+ import typer
9
+
10
+ from specify_cli.cli.helpers import check_version_compatibility, console, get_project_root_or_exit
11
+ from specify_cli.dashboard import ensure_dashboard_running, stop_dashboard
12
+
13
+
14
+ def dashboard(
15
+ port: Optional[int] = typer.Option(
16
+ None,
17
+ "--port",
18
+ help="Preferred port for the dashboard (falls back to the first available port).",
19
+ ),
20
+ kill: bool = typer.Option(
21
+ False,
22
+ "--kill",
23
+ help="Stop the running dashboard for this project and clear its metadata.",
24
+ ),
25
+ ) -> None:
26
+ """Open or stop the Spec Kitty dashboard."""
27
+ project_root = get_project_root_or_exit()
28
+ check_version_compatibility(project_root, "dashboard")
29
+
30
+ console.print()
31
+
32
+ if kill:
33
+ stopped, message = stop_dashboard(project_root)
34
+ console.print(f"[green]✅ {message}[/green]" if stopped else f"[yellow]⚠️ {message}[/yellow]")
35
+ console.print()
36
+ return
37
+
38
+ if port is not None and not (1 <= port <= 65535):
39
+ console.print("[red]❌ Invalid port specified. Use a value between 1 and 65535.[/red]")
40
+ console.print()
41
+ raise typer.Exit(1)
42
+
43
+ try:
44
+ dashboard_url, active_port, started = ensure_dashboard_running(project_root, preferred_port=port)
45
+ except Exception as exc: # pragma: no cover
46
+ console.print("[red]❌ Unable to start or locate the dashboard[/red]")
47
+ console.print(f" {exc}")
48
+ console.print()
49
+ console.print("[yellow]💡 Try running:[/yellow]")
50
+ console.print(f" [cyan]cd {project_root}[/cyan]")
51
+ console.print(" [cyan]spec-kitty init .[/cyan]")
52
+ console.print()
53
+ raise typer.Exit(1)
54
+
55
+ console.print("[bold green]Spec Kitty Dashboard[/bold green]")
56
+ console.print("[cyan]" + "=" * 60 + "[/cyan]")
57
+ console.print()
58
+ console.print(f" [bold cyan]Project Root:[/bold cyan] {project_root}")
59
+ console.print(f" [bold cyan]URL:[/bold cyan] {dashboard_url}")
60
+ console.print(f" [bold cyan]Port:[/bold cyan] {active_port}")
61
+ if port is not None and port != active_port:
62
+ console.print(f" [yellow]⚠️ Requested port {port} was unavailable; using {active_port} instead.[/yellow]")
63
+ console.print()
64
+
65
+ status_msg = (
66
+ f" [green]✅ Status:[/green] Started new dashboard instance on port {active_port}"
67
+ if started
68
+ else f" [green]✅ Status:[/green] Dashboard already running on port {active_port}"
69
+ )
70
+ console.print(status_msg)
71
+ console.print()
72
+ console.print("[cyan]" + "=" * 60 + "[/cyan]")
73
+ console.print()
74
+
75
+ try:
76
+ webbrowser.open(dashboard_url)
77
+ console.print("[green]✅ Opening dashboard in your browser...[/green]")
78
+ console.print()
79
+ except Exception:
80
+ console.print("[yellow]⚠️ Could not automatically open browser[/yellow]")
81
+ console.print(f" Please open this URL manually: [cyan]{dashboard_url}[/cyan]")
82
+ console.print()
83
+
84
+
85
+ __all__ = ["dashboard"]