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,240 @@
1
+ """Mission management CLI commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Iterable, List, Optional
7
+
8
+ import typer
9
+ from rich.panel import Panel
10
+ from rich.table import Table
11
+
12
+ from specify_cli.cli.helpers import check_version_compatibility, console, get_project_root_or_exit
13
+ from specify_cli.mission import (
14
+ Mission,
15
+ MissionError,
16
+ MissionNotFoundError,
17
+ discover_missions,
18
+ get_active_mission,
19
+ get_mission_by_name,
20
+ list_available_missions,
21
+ )
22
+
23
+ app = typer.Typer(
24
+ name="mission",
25
+ help="View available Spec Kitty missions. Missions are selected per-feature during /spec-kitty.specify.",
26
+ no_args_is_help=True,
27
+ )
28
+
29
+
30
+ def _resolve_primary_repo_root(project_root: Path) -> Path:
31
+ """Return the primary repository root even when invoked from a worktree."""
32
+ resolved = project_root.resolve()
33
+ parts = list(resolved.parts)
34
+ if ".worktrees" not in parts:
35
+ return resolved
36
+
37
+ idx = parts.index(".worktrees")
38
+ # Rebuild the path up to (but excluding) ".worktrees"
39
+ base = Path(parts[0])
40
+ for segment in parts[1:idx]:
41
+ base /= segment
42
+ return base
43
+
44
+
45
+ def _list_active_worktrees(repo_root: Path) -> List[str]:
46
+ """Return list of active worktree directories relative to the repo root."""
47
+ worktrees_dir = repo_root / ".worktrees"
48
+ if not worktrees_dir.exists():
49
+ return []
50
+
51
+ active: List[str] = []
52
+ for entry in sorted(worktrees_dir.iterdir()):
53
+ if not entry.is_dir():
54
+ continue
55
+ try:
56
+ rel = entry.relative_to(repo_root)
57
+ except ValueError:
58
+ rel = entry
59
+ active.append(str(rel))
60
+ return active
61
+
62
+
63
+ def _mission_details_lines(mission: Mission, include_description: bool = True) -> List[str]:
64
+ """Return formatted mission details."""
65
+ details: List[str] = [
66
+ f"[cyan]Name:[/cyan] {mission.name}",
67
+ f"[cyan]Domain:[/cyan] {mission.domain}",
68
+ f"[cyan]Version:[/cyan] {mission.version}",
69
+ f"[cyan]Path:[/cyan] {mission.path}",
70
+ ]
71
+ if include_description and mission.description:
72
+ details.append(f"[cyan]Description:[/cyan] {mission.description}")
73
+ details.extend(["", "[cyan]Workflow Phases:[/cyan]"])
74
+ for phase in mission.config.workflow.phases:
75
+ details.append(f" • {phase.name} – {phase.description}")
76
+
77
+ details.extend(["", "[cyan]Required Artifacts:[/cyan]"])
78
+ if mission.config.artifacts.required:
79
+ for artifact in mission.config.artifacts.required:
80
+ details.append(f" • {artifact}")
81
+ else:
82
+ details.append(" • (none)")
83
+
84
+ if mission.config.artifacts.optional:
85
+ details.extend(["", "[cyan]Optional Artifacts:[/cyan]"])
86
+ for artifact in mission.config.artifacts.optional:
87
+ details.append(f" • {artifact}")
88
+
89
+ details.extend(["", "[cyan]Validation Checks:[/cyan]"])
90
+ if mission.config.validation.checks:
91
+ for check in mission.config.validation.checks:
92
+ details.append(f" • {check}")
93
+ else:
94
+ details.append(" • (none)")
95
+
96
+ if mission.config.paths:
97
+ details.extend(["", "[cyan]Path Conventions:[/cyan]"])
98
+ for key, value in mission.config.paths.items():
99
+ details.append(f" • {key}: {value}")
100
+
101
+ if mission.config.mcp_tools:
102
+ details.extend(["", "[cyan]MCP Tools:[/cyan]"])
103
+ details.append(f" • Required: {', '.join(mission.config.mcp_tools.required) or 'none'}")
104
+ details.append(f" • Recommended: {', '.join(mission.config.mcp_tools.recommended) or 'none'}")
105
+ details.append(f" • Optional: {', '.join(mission.config.mcp_tools.optional) or 'none'}")
106
+
107
+ return details
108
+
109
+
110
+ def _print_available_missions(project_root: Path) -> None:
111
+ """Print available missions with source indicators (project/built-in)."""
112
+ missions = discover_missions(project_root)
113
+ if not missions:
114
+ console.print("[yellow]No missions found in .kittify/missions/[/yellow]")
115
+ return
116
+
117
+ table = Table(title="Available Missions", show_header=True)
118
+ table.add_column("Key", style="cyan")
119
+ table.add_column("Name", style="green")
120
+ table.add_column("Domain", style="magenta")
121
+ table.add_column("Description", overflow="fold")
122
+ table.add_column("Source", style="dim")
123
+
124
+ for key, (mission, source) in sorted(missions.items()):
125
+ table.add_row(
126
+ key,
127
+ mission.name,
128
+ mission.domain,
129
+ mission.description or "",
130
+ source,
131
+ )
132
+
133
+ console.print(table)
134
+ console.print()
135
+ console.print("[dim]Missions are selected per-feature during /spec-kitty.specify[/dim]")
136
+
137
+
138
+ @app.command("list")
139
+ def list_cmd() -> None:
140
+ """List all available missions with their source (project/built-in)."""
141
+ project_root = get_project_root_or_exit()
142
+ check_version_compatibility(project_root, "mission")
143
+ kittify_dir = project_root / ".kittify"
144
+ if not kittify_dir.exists():
145
+ console.print(f"[red]Spec Kitty project not initialized at:[/red] {project_root}")
146
+ console.print("[dim]Run 'spec-kitty init <project-name>' or execute this command from a feature worktree created under .worktrees/<feature>/.[/dim]")
147
+ raise typer.Exit(1)
148
+
149
+ try:
150
+ _print_available_missions(project_root)
151
+ except typer.Exit:
152
+ raise
153
+ except Exception as exc:
154
+ console.print(f"[red]Error listing missions:[/red] {exc}")
155
+ raise typer.Exit(1)
156
+
157
+
158
+ @app.command("current")
159
+ def current_cmd() -> None:
160
+ """Show currently active mission."""
161
+ project_root = get_project_root_or_exit()
162
+ check_version_compatibility(project_root, "mission")
163
+ try:
164
+ mission = get_active_mission(project_root)
165
+ except MissionNotFoundError as exc:
166
+ console.print(f"[red]Error:[/red] {exc}")
167
+ raise typer.Exit(1)
168
+ except MissionError as exc:
169
+ console.print(f"[red]Failed to load active mission:[/red] {exc}")
170
+ raise typer.Exit(1)
171
+
172
+ panel = Panel(
173
+ "\n".join(_mission_details_lines(mission)),
174
+ title="Active Mission",
175
+ border_style="cyan",
176
+ )
177
+ console.print(panel)
178
+
179
+
180
+ @app.command("info")
181
+ def info_cmd(
182
+ mission_name: str = typer.Argument(..., help="Mission name to display details for"),
183
+ ) -> None:
184
+ """Show details for a specific mission without switching."""
185
+ project_root = get_project_root_or_exit()
186
+ check_version_compatibility(project_root, "mission")
187
+ kittify_dir = project_root / ".kittify"
188
+
189
+ try:
190
+ mission = get_mission_by_name(mission_name, kittify_dir)
191
+ except MissionNotFoundError:
192
+ console.print(f"[red]Mission not found:[/red] {mission_name}")
193
+ available = list_available_missions(kittify_dir)
194
+ if available:
195
+ console.print("\n[yellow]Available missions:[/yellow]")
196
+ for name in available:
197
+ console.print(f" • {name}")
198
+ raise typer.Exit(1)
199
+ except MissionError as exc:
200
+ console.print(f"[red]Error loading mission '{mission_name}':[/red] {exc}")
201
+ raise typer.Exit(1)
202
+
203
+ panel = Panel(
204
+ "\n".join(_mission_details_lines(mission, include_description=True)),
205
+ title=f"Mission Details · {mission.name}",
206
+ border_style="cyan",
207
+ )
208
+ console.print(panel)
209
+
210
+
211
+ def _print_active_worktrees(active_worktrees: Iterable[str]) -> None:
212
+ console.print("[red]Cannot switch missions: active features exist[/red]")
213
+ console.print("\n[yellow]Active worktrees:[/yellow]")
214
+ for wt in active_worktrees:
215
+ console.print(f" • {wt}")
216
+ console.print(
217
+ "\n[cyan]Suggestion:[/cyan] Complete, merge, or remove these worktrees before switching missions."
218
+ )
219
+
220
+
221
+ @app.command("switch", deprecated=True)
222
+ def switch_cmd(
223
+ mission_name: str = typer.Argument(..., help="Mission name (no longer supported)"),
224
+ force: bool = typer.Option(False, "--force", help="(ignored)"),
225
+ ) -> None:
226
+ """[REMOVED] Switch active mission - this command was removed in v0.8.0."""
227
+ console.print("[bold red]Error:[/bold red] The 'mission switch' command was removed in v0.8.0.")
228
+ console.print()
229
+ console.print("Missions are now selected [bold]per-feature[/bold] during [cyan]/spec-kitty.specify[/cyan].")
230
+ console.print()
231
+ console.print("[cyan]New workflow:[/cyan]")
232
+ console.print(" 1. Run [bold]/spec-kitty.specify[/bold] to start a new feature")
233
+ console.print(" 2. The system will infer and confirm the appropriate mission")
234
+ console.print(" 3. Mission is stored in the feature's [dim]meta.json[/dim]")
235
+ console.print()
236
+ console.print("[cyan]To see available missions:[/cyan]")
237
+ console.print(" spec-kitty mission list")
238
+ console.print()
239
+ console.print("[dim]See: https://github.com/your-org/spec-kitty#per-feature-missions[/dim]")
240
+ raise typer.Exit(1)
@@ -0,0 +1,265 @@
1
+ """Ops command - operation history and undo functionality.
2
+
3
+ This command provides access to VCS operation history and undo capabilities.
4
+ For jj, this leverages the full operation log with undo support.
5
+ For git, this provides read-only access to the reflog.
6
+
7
+ Key differences:
8
+ - jj: Full operation log with complete undo capability
9
+ - git: Reflog as read-only operation history (no undo)
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from pathlib import Path
15
+
16
+ import typer
17
+ from rich.console import Console
18
+ from rich.table import Table
19
+
20
+ from specify_cli.core.vcs import (
21
+ OperationInfo,
22
+ VCSBackend,
23
+ get_vcs,
24
+ )
25
+
26
+ app = typer.Typer(
27
+ name="ops",
28
+ help="Operation history and undo (jj: full undo, git: reflog only)",
29
+ no_args_is_help=True,
30
+ )
31
+
32
+ console = Console()
33
+
34
+
35
+ def _display_operations(ops: list[OperationInfo], backend: VCSBackend) -> None:
36
+ """Display operation history in a formatted table.
37
+
38
+ Args:
39
+ ops: List of operations to display
40
+ backend: VCS backend (affects column display)
41
+ """
42
+ if not ops:
43
+ console.print("[dim]No operations found[/dim]")
44
+ return
45
+
46
+ backend_label = "jj" if backend == VCSBackend.JUJUTSU else "git reflog"
47
+ table = Table(title=f"Operation History ({backend_label})")
48
+
49
+ table.add_column("ID", style="cyan", no_wrap=True)
50
+ table.add_column("Time", style="dim")
51
+ table.add_column("Description")
52
+
53
+ if backend == VCSBackend.JUJUTSU:
54
+ table.add_column("Undoable", style="green", justify="center")
55
+
56
+ for op in ops:
57
+ # Truncate operation ID for display
58
+ short_id = op.operation_id[:12] if len(op.operation_id) > 12 else op.operation_id
59
+
60
+ # Format timestamp
61
+ time_str = op.timestamp.strftime("%Y-%m-%d %H:%M")
62
+
63
+ # Truncate description if too long
64
+ desc = op.description[:60] + "..." if len(op.description) > 60 else op.description
65
+
66
+ row = [short_id, time_str, desc]
67
+
68
+ if backend == VCSBackend.JUJUTSU:
69
+ row.append("✓" if op.is_undoable else "")
70
+
71
+ table.add_row(*row)
72
+
73
+ console.print(table)
74
+
75
+
76
+ @app.command()
77
+ def log(
78
+ limit: int = typer.Option(
79
+ 20,
80
+ "--limit",
81
+ "-n",
82
+ help="Number of operations to show",
83
+ ),
84
+ verbose: bool = typer.Option(
85
+ False,
86
+ "--verbose",
87
+ "-v",
88
+ help="Show full operation IDs and details",
89
+ ),
90
+ ) -> None:
91
+ """Show operation history.
92
+
93
+ For jj workspaces, shows the operation log with undo information.
94
+ For git workspaces, shows the reflog (read-only history).
95
+
96
+ Examples:
97
+ # Show recent operations
98
+ spec-kitty ops log
99
+
100
+ # Show last 5 operations
101
+ spec-kitty ops log --limit 5
102
+
103
+ # Show with full details
104
+ spec-kitty ops log --verbose
105
+ """
106
+ workspace_path = Path.cwd()
107
+
108
+ # Get VCS implementation
109
+ try:
110
+ vcs = get_vcs(workspace_path)
111
+ except Exception as e:
112
+ console.print(f"[red]Error:[/red] Failed to detect VCS: {e}")
113
+ raise typer.Exit(1)
114
+
115
+ console.print(f"\n[cyan]Backend:[/cyan] git")
116
+ console.print()
117
+
118
+ # Get operation history (git reflog only)
119
+ from specify_cli.core.vcs.git import git_get_reflog
120
+
121
+ ops = git_get_reflog(workspace_path, limit=limit)
122
+
123
+ _display_operations(ops, vcs.backend)
124
+
125
+ if verbose and ops:
126
+ console.print("\n[dim]Full operation IDs:[/dim]")
127
+ for op in ops[:5]: # Show first 5 full IDs
128
+ console.print(f" {op.operation_id}")
129
+
130
+ console.print()
131
+
132
+
133
+ @app.command()
134
+ def undo(
135
+ operation_id: str = typer.Argument(
136
+ None,
137
+ help="Operation ID to undo (jj only, defaults to last operation)",
138
+ ),
139
+ ) -> None:
140
+ """Undo last operation (jj only).
141
+
142
+ Reverts the repository to the state before the last operation.
143
+ This is only supported for jj workspaces - git does not have
144
+ reversible operation history.
145
+
146
+ Examples:
147
+ # Undo last operation
148
+ spec-kitty ops undo
149
+
150
+ # Undo specific operation
151
+ spec-kitty ops undo abc123def456
152
+ """
153
+ workspace_path = Path.cwd()
154
+
155
+ # Get VCS implementation
156
+ try:
157
+ vcs = get_vcs(workspace_path)
158
+ except Exception as e:
159
+ console.print(f"[red]Error:[/red] Failed to detect VCS: {e}")
160
+ raise typer.Exit(1)
161
+
162
+ # Capability check - undo only supported for jj
163
+ if not vcs.capabilities.supports_operation_undo:
164
+ console.print(f"\n[red]✗ Undo not supported for {vcs.backend.value}[/red]")
165
+ console.print()
166
+ console.print("[dim]Git does not have reversible operation history.[/dim]")
167
+ console.print("[dim]Consider using these alternatives manually:[/dim]")
168
+ console.print(" • git reset --soft HEAD~1 (undo last commit, keep changes)")
169
+ console.print(" • git reset --hard HEAD~1 (undo last commit, discard changes)")
170
+ console.print(" • git revert <commit> (create reverting commit)")
171
+ console.print(" • git reflog (find previous states)")
172
+ console.print()
173
+ raise typer.Exit(1)
174
+
175
+ # jj undo
176
+ console.print("\n[cyan]Undoing operation...[/cyan]")
177
+
178
+ from specify_cli.core.vcs.jujutsu import jj_undo_operation
179
+
180
+ success = jj_undo_operation(workspace_path, operation_id)
181
+
182
+ if success:
183
+ console.print("[green]✓ Operation undone successfully[/green]")
184
+ console.print()
185
+ console.print("[dim]Use 'spec-kitty ops log' to see the updated history.[/dim]")
186
+ else:
187
+ console.print("[red]✗ Undo failed[/red]")
188
+ console.print()
189
+ console.print("[dim]Try these commands to debug:[/dim]")
190
+ console.print(" jj op log # View operation history")
191
+ console.print(" jj status # Check current state")
192
+ raise typer.Exit(1)
193
+
194
+ console.print()
195
+
196
+
197
+ @app.command()
198
+ def restore(
199
+ operation_id: str = typer.Argument(
200
+ ...,
201
+ help="Operation ID to restore to (jj only)",
202
+ ),
203
+ ) -> None:
204
+ """Restore to a specific operation (jj only).
205
+
206
+ Restores the repository to the exact state at a specific operation.
207
+ This is more powerful than undo - it can jump to any point in history.
208
+
209
+ Examples:
210
+ # Restore to specific operation
211
+ spec-kitty ops restore abc123def456
212
+ """
213
+ workspace_path = Path.cwd()
214
+
215
+ # Get VCS implementation
216
+ try:
217
+ vcs = get_vcs(workspace_path)
218
+ except Exception as e:
219
+ console.print(f"[red]Error:[/red] Failed to detect VCS: {e}")
220
+ raise typer.Exit(1)
221
+
222
+ # Capability check - restore only supported for jj
223
+ if not vcs.capabilities.supports_operation_undo:
224
+ console.print(f"\n[red]✗ Restore not supported for {vcs.backend.value}[/red]")
225
+ console.print()
226
+ console.print("[dim]Git does not support operation-level restore.[/dim]")
227
+ console.print("[dim]Consider using these alternatives:[/dim]")
228
+ console.print(" • git checkout <commit> (detached HEAD)")
229
+ console.print(" • git reset --hard <commit>(destructive)")
230
+ console.print()
231
+ raise typer.Exit(1)
232
+
233
+ # jj restore
234
+ console.print(f"\n[cyan]Restoring to operation {operation_id[:12]}...[/cyan]")
235
+
236
+ import subprocess
237
+
238
+ try:
239
+ result = subprocess.run(
240
+ ["jj", "op", "restore", operation_id],
241
+ cwd=workspace_path,
242
+ capture_output=True,
243
+ text=True,
244
+ check=False,
245
+ timeout=60,
246
+ )
247
+
248
+ if result.returncode == 0:
249
+ console.print("[green]✓ Restored successfully[/green]")
250
+ console.print()
251
+ console.print("[dim]Use 'spec-kitty ops log' to see the updated history.[/dim]")
252
+ else:
253
+ console.print("[red]✗ Restore failed[/red]")
254
+ if result.stderr:
255
+ console.print(f"[dim]{result.stderr.strip()}[/dim]")
256
+ raise typer.Exit(1)
257
+
258
+ except subprocess.TimeoutExpired:
259
+ console.print("[red]✗ Restore timed out[/red]")
260
+ raise typer.Exit(1)
261
+ except FileNotFoundError:
262
+ console.print("[red]✗ jj command not found[/red]")
263
+ raise typer.Exit(1)
264
+
265
+ console.print()