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.
- spec_kitty_cli-0.12.1.dist-info/METADATA +1767 -0
- spec_kitty_cli-0.12.1.dist-info/RECORD +242 -0
- spec_kitty_cli-0.12.1.dist-info/WHEEL +4 -0
- spec_kitty_cli-0.12.1.dist-info/entry_points.txt +2 -0
- spec_kitty_cli-0.12.1.dist-info/licenses/LICENSE +21 -0
- specify_cli/__init__.py +171 -0
- specify_cli/acceptance.py +627 -0
- specify_cli/agent_utils/README.md +157 -0
- specify_cli/agent_utils/__init__.py +9 -0
- specify_cli/agent_utils/status.py +356 -0
- specify_cli/cli/__init__.py +6 -0
- specify_cli/cli/commands/__init__.py +46 -0
- specify_cli/cli/commands/accept.py +189 -0
- specify_cli/cli/commands/agent/__init__.py +22 -0
- specify_cli/cli/commands/agent/config.py +382 -0
- specify_cli/cli/commands/agent/context.py +191 -0
- specify_cli/cli/commands/agent/feature.py +1057 -0
- specify_cli/cli/commands/agent/release.py +11 -0
- specify_cli/cli/commands/agent/tasks.py +1253 -0
- specify_cli/cli/commands/agent/workflow.py +801 -0
- specify_cli/cli/commands/context.py +246 -0
- specify_cli/cli/commands/dashboard.py +85 -0
- specify_cli/cli/commands/implement.py +973 -0
- specify_cli/cli/commands/init.py +827 -0
- specify_cli/cli/commands/init_help.py +62 -0
- specify_cli/cli/commands/merge.py +755 -0
- specify_cli/cli/commands/mission.py +240 -0
- specify_cli/cli/commands/ops.py +265 -0
- specify_cli/cli/commands/orchestrate.py +640 -0
- specify_cli/cli/commands/repair.py +175 -0
- specify_cli/cli/commands/research.py +165 -0
- specify_cli/cli/commands/sync.py +364 -0
- specify_cli/cli/commands/upgrade.py +249 -0
- specify_cli/cli/commands/validate_encoding.py +186 -0
- specify_cli/cli/commands/validate_tasks.py +186 -0
- specify_cli/cli/commands/verify.py +310 -0
- specify_cli/cli/helpers.py +123 -0
- specify_cli/cli/step_tracker.py +91 -0
- specify_cli/cli/ui.py +192 -0
- specify_cli/core/__init__.py +53 -0
- specify_cli/core/agent_context.py +311 -0
- specify_cli/core/config.py +96 -0
- specify_cli/core/context_validation.py +362 -0
- specify_cli/core/dependency_graph.py +351 -0
- specify_cli/core/git_ops.py +129 -0
- specify_cli/core/multi_parent_merge.py +323 -0
- specify_cli/core/paths.py +260 -0
- specify_cli/core/project_resolver.py +110 -0
- specify_cli/core/stale_detection.py +263 -0
- specify_cli/core/tool_checker.py +79 -0
- specify_cli/core/utils.py +43 -0
- specify_cli/core/vcs/__init__.py +114 -0
- specify_cli/core/vcs/detection.py +341 -0
- specify_cli/core/vcs/exceptions.py +85 -0
- specify_cli/core/vcs/git.py +1304 -0
- specify_cli/core/vcs/jujutsu.py +1208 -0
- specify_cli/core/vcs/protocol.py +285 -0
- specify_cli/core/vcs/types.py +249 -0
- specify_cli/core/version_checker.py +261 -0
- specify_cli/core/worktree.py +506 -0
- specify_cli/dashboard/__init__.py +28 -0
- specify_cli/dashboard/diagnostics.py +204 -0
- specify_cli/dashboard/handlers/__init__.py +17 -0
- specify_cli/dashboard/handlers/api.py +143 -0
- specify_cli/dashboard/handlers/base.py +65 -0
- specify_cli/dashboard/handlers/features.py +390 -0
- specify_cli/dashboard/handlers/router.py +81 -0
- specify_cli/dashboard/handlers/static.py +50 -0
- specify_cli/dashboard/lifecycle.py +541 -0
- specify_cli/dashboard/scanner.py +437 -0
- specify_cli/dashboard/server.py +123 -0
- specify_cli/dashboard/static/dashboard/dashboard.css +722 -0
- specify_cli/dashboard/static/dashboard/dashboard.js +1424 -0
- specify_cli/dashboard/static/spec-kitty.png +0 -0
- specify_cli/dashboard/templates/__init__.py +36 -0
- specify_cli/dashboard/templates/index.html +258 -0
- specify_cli/doc_generators.py +621 -0
- specify_cli/doc_state.py +408 -0
- specify_cli/frontmatter.py +384 -0
- specify_cli/gap_analysis.py +915 -0
- specify_cli/gitignore_manager.py +300 -0
- specify_cli/guards.py +145 -0
- specify_cli/legacy_detector.py +83 -0
- specify_cli/manifest.py +286 -0
- specify_cli/merge/__init__.py +63 -0
- specify_cli/merge/executor.py +653 -0
- specify_cli/merge/forecast.py +215 -0
- specify_cli/merge/ordering.py +126 -0
- specify_cli/merge/preflight.py +230 -0
- specify_cli/merge/state.py +185 -0
- specify_cli/merge/status_resolver.py +354 -0
- specify_cli/mission.py +654 -0
- specify_cli/missions/documentation/command-templates/implement.md +309 -0
- specify_cli/missions/documentation/command-templates/plan.md +275 -0
- specify_cli/missions/documentation/command-templates/review.md +344 -0
- specify_cli/missions/documentation/command-templates/specify.md +206 -0
- specify_cli/missions/documentation/command-templates/tasks.md +189 -0
- specify_cli/missions/documentation/mission.yaml +113 -0
- specify_cli/missions/documentation/templates/divio/explanation-template.md +192 -0
- specify_cli/missions/documentation/templates/divio/howto-template.md +168 -0
- specify_cli/missions/documentation/templates/divio/reference-template.md +179 -0
- specify_cli/missions/documentation/templates/divio/tutorial-template.md +146 -0
- specify_cli/missions/documentation/templates/generators/jsdoc.json.template +18 -0
- specify_cli/missions/documentation/templates/generators/sphinx-conf.py.template +36 -0
- specify_cli/missions/documentation/templates/plan-template.md +269 -0
- specify_cli/missions/documentation/templates/release-template.md +222 -0
- specify_cli/missions/documentation/templates/spec-template.md +172 -0
- specify_cli/missions/documentation/templates/task-prompt-template.md +140 -0
- specify_cli/missions/documentation/templates/tasks-template.md +159 -0
- specify_cli/missions/research/command-templates/merge.md +388 -0
- specify_cli/missions/research/command-templates/plan.md +125 -0
- specify_cli/missions/research/command-templates/review.md +144 -0
- specify_cli/missions/research/command-templates/tasks.md +225 -0
- specify_cli/missions/research/mission.yaml +115 -0
- specify_cli/missions/research/templates/data-model-template.md +33 -0
- specify_cli/missions/research/templates/plan-template.md +161 -0
- specify_cli/missions/research/templates/research/evidence-log.csv +18 -0
- specify_cli/missions/research/templates/research/source-register.csv +18 -0
- specify_cli/missions/research/templates/research-template.md +35 -0
- specify_cli/missions/research/templates/spec-template.md +64 -0
- specify_cli/missions/research/templates/task-prompt-template.md +148 -0
- specify_cli/missions/research/templates/tasks-template.md +114 -0
- specify_cli/missions/software-dev/command-templates/accept.md +75 -0
- specify_cli/missions/software-dev/command-templates/analyze.md +183 -0
- specify_cli/missions/software-dev/command-templates/checklist.md +286 -0
- specify_cli/missions/software-dev/command-templates/clarify.md +157 -0
- specify_cli/missions/software-dev/command-templates/constitution.md +432 -0
- specify_cli/missions/software-dev/command-templates/dashboard.md +101 -0
- specify_cli/missions/software-dev/command-templates/implement.md +41 -0
- specify_cli/missions/software-dev/command-templates/merge.md +383 -0
- specify_cli/missions/software-dev/command-templates/plan.md +171 -0
- specify_cli/missions/software-dev/command-templates/review.md +32 -0
- specify_cli/missions/software-dev/command-templates/specify.md +321 -0
- specify_cli/missions/software-dev/command-templates/tasks.md +566 -0
- specify_cli/missions/software-dev/mission.yaml +100 -0
- specify_cli/missions/software-dev/templates/plan-template.md +132 -0
- specify_cli/missions/software-dev/templates/spec-template.md +116 -0
- specify_cli/missions/software-dev/templates/task-prompt-template.md +140 -0
- specify_cli/missions/software-dev/templates/tasks-template.md +159 -0
- specify_cli/orchestrator/__init__.py +75 -0
- specify_cli/orchestrator/agent_config.py +224 -0
- specify_cli/orchestrator/agents/__init__.py +170 -0
- specify_cli/orchestrator/agents/augment.py +112 -0
- specify_cli/orchestrator/agents/base.py +243 -0
- specify_cli/orchestrator/agents/claude.py +112 -0
- specify_cli/orchestrator/agents/codex.py +106 -0
- specify_cli/orchestrator/agents/copilot.py +137 -0
- specify_cli/orchestrator/agents/cursor.py +139 -0
- specify_cli/orchestrator/agents/gemini.py +115 -0
- specify_cli/orchestrator/agents/kilocode.py +94 -0
- specify_cli/orchestrator/agents/opencode.py +132 -0
- specify_cli/orchestrator/agents/qwen.py +96 -0
- specify_cli/orchestrator/config.py +455 -0
- specify_cli/orchestrator/executor.py +642 -0
- specify_cli/orchestrator/integration.py +1230 -0
- specify_cli/orchestrator/monitor.py +898 -0
- specify_cli/orchestrator/scheduler.py +832 -0
- specify_cli/orchestrator/state.py +508 -0
- specify_cli/orchestrator/testing/__init__.py +122 -0
- specify_cli/orchestrator/testing/availability.py +346 -0
- specify_cli/orchestrator/testing/fixtures.py +684 -0
- specify_cli/orchestrator/testing/paths.py +218 -0
- specify_cli/plan_validation.py +107 -0
- specify_cli/scripts/debug-dashboard-scan.py +61 -0
- specify_cli/scripts/tasks/acceptance_support.py +695 -0
- specify_cli/scripts/tasks/task_helpers.py +506 -0
- specify_cli/scripts/tasks/tasks_cli.py +848 -0
- specify_cli/scripts/validate_encoding.py +180 -0
- specify_cli/task_metadata_validation.py +274 -0
- specify_cli/tasks_support.py +447 -0
- specify_cli/template/__init__.py +47 -0
- specify_cli/template/asset_generator.py +206 -0
- specify_cli/template/github_client.py +334 -0
- specify_cli/template/manager.py +193 -0
- specify_cli/template/renderer.py +99 -0
- specify_cli/templates/AGENTS.md +190 -0
- specify_cli/templates/POWERSHELL_SYNTAX.md +229 -0
- specify_cli/templates/agent-file-template.md +35 -0
- specify_cli/templates/checklist-template.md +42 -0
- specify_cli/templates/claudeignore-template +58 -0
- specify_cli/templates/command-templates/accept.md +141 -0
- specify_cli/templates/command-templates/analyze.md +253 -0
- specify_cli/templates/command-templates/checklist.md +352 -0
- specify_cli/templates/command-templates/clarify.md +224 -0
- specify_cli/templates/command-templates/constitution.md +432 -0
- specify_cli/templates/command-templates/dashboard.md +175 -0
- specify_cli/templates/command-templates/implement.md +190 -0
- specify_cli/templates/command-templates/merge.md +374 -0
- specify_cli/templates/command-templates/plan.md +171 -0
- specify_cli/templates/command-templates/research.md +88 -0
- specify_cli/templates/command-templates/review.md +510 -0
- specify_cli/templates/command-templates/specify.md +321 -0
- specify_cli/templates/command-templates/status.md +92 -0
- specify_cli/templates/command-templates/tasks.md +199 -0
- specify_cli/templates/git-hooks/pre-commit +22 -0
- specify_cli/templates/git-hooks/pre-commit-agent-check +37 -0
- specify_cli/templates/git-hooks/pre-commit-encoding-check +142 -0
- specify_cli/templates/plan-template.md +108 -0
- specify_cli/templates/spec-template.md +118 -0
- specify_cli/templates/task-prompt-template.md +165 -0
- specify_cli/templates/tasks-template.md +161 -0
- specify_cli/templates/vscode-settings.json +13 -0
- specify_cli/text_sanitization.py +225 -0
- specify_cli/upgrade/__init__.py +18 -0
- specify_cli/upgrade/detector.py +239 -0
- specify_cli/upgrade/metadata.py +182 -0
- specify_cli/upgrade/migrations/__init__.py +65 -0
- specify_cli/upgrade/migrations/base.py +80 -0
- specify_cli/upgrade/migrations/m_0_10_0_python_only.py +359 -0
- specify_cli/upgrade/migrations/m_0_10_12_constitution_cleanup.py +99 -0
- specify_cli/upgrade/migrations/m_0_10_14_update_implement_slash_command.py +176 -0
- specify_cli/upgrade/migrations/m_0_10_1_populate_slash_commands.py +174 -0
- specify_cli/upgrade/migrations/m_0_10_2_update_slash_commands.py +172 -0
- specify_cli/upgrade/migrations/m_0_10_6_workflow_simplification.py +174 -0
- specify_cli/upgrade/migrations/m_0_10_8_fix_memory_structure.py +252 -0
- specify_cli/upgrade/migrations/m_0_10_9_repair_templates.py +168 -0
- specify_cli/upgrade/migrations/m_0_11_0_workspace_per_wp.py +182 -0
- specify_cli/upgrade/migrations/m_0_11_1_improved_workflow_templates.py +173 -0
- specify_cli/upgrade/migrations/m_0_11_1_update_implement_slash_command.py +160 -0
- specify_cli/upgrade/migrations/m_0_11_2_improved_workflow_templates.py +173 -0
- specify_cli/upgrade/migrations/m_0_11_3_workflow_agent_flag.py +114 -0
- specify_cli/upgrade/migrations/m_0_12_0_documentation_mission.py +155 -0
- specify_cli/upgrade/migrations/m_0_12_1_remove_kitty_specs_from_gitignore.py +183 -0
- specify_cli/upgrade/migrations/m_0_2_0_specify_to_kittify.py +80 -0
- specify_cli/upgrade/migrations/m_0_4_8_gitignore_agents.py +118 -0
- specify_cli/upgrade/migrations/m_0_5_0_encoding_hooks.py +141 -0
- specify_cli/upgrade/migrations/m_0_6_5_commands_rename.py +169 -0
- specify_cli/upgrade/migrations/m_0_6_7_ensure_missions.py +228 -0
- specify_cli/upgrade/migrations/m_0_7_2_worktree_commands_dedup.py +89 -0
- specify_cli/upgrade/migrations/m_0_7_3_update_scripts.py +114 -0
- specify_cli/upgrade/migrations/m_0_8_0_remove_active_mission.py +82 -0
- specify_cli/upgrade/migrations/m_0_8_0_worktree_agents_symlink.py +148 -0
- specify_cli/upgrade/migrations/m_0_9_0_frontmatter_only_lanes.py +346 -0
- specify_cli/upgrade/migrations/m_0_9_1_complete_lane_migration.py +656 -0
- specify_cli/upgrade/migrations/m_0_9_2_research_mission_templates.py +221 -0
- specify_cli/upgrade/registry.py +121 -0
- specify_cli/upgrade/runner.py +284 -0
- specify_cli/validators/__init__.py +14 -0
- specify_cli/validators/paths.py +154 -0
- specify_cli/validators/research.py +428 -0
- specify_cli/verify_enhanced.py +270 -0
- 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()
|