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,175 @@
|
|
|
1
|
+
"""Repair command to fix broken templates and diagnose worktrees."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from specify_cli.upgrade.migrations.m_0_10_9_repair_templates import RepairTemplatesMigration
|
|
9
|
+
from specify_cli.core.paths import locate_project_root, get_main_repo_root, is_worktree_context
|
|
10
|
+
|
|
11
|
+
app = typer.Typer()
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@app.command()
|
|
16
|
+
def repair(
|
|
17
|
+
project_path: Path = typer.Option(
|
|
18
|
+
Path.cwd(),
|
|
19
|
+
"--project-path",
|
|
20
|
+
"-p",
|
|
21
|
+
help="Path to project to repair"
|
|
22
|
+
),
|
|
23
|
+
dry_run: bool = typer.Option(
|
|
24
|
+
False,
|
|
25
|
+
"--dry-run",
|
|
26
|
+
help="Show what would be changed without making changes"
|
|
27
|
+
)
|
|
28
|
+
):
|
|
29
|
+
"""Repair broken templates caused by v0.10.0-0.10.8 bundling bug.
|
|
30
|
+
|
|
31
|
+
This command fixes templates that reference non-existent bash scripts
|
|
32
|
+
by regenerating them from the correct source. Run this if you see errors
|
|
33
|
+
like "scripts/bash/check-prerequisites.sh: No such file or directory".
|
|
34
|
+
"""
|
|
35
|
+
console.print("[bold]Spec Kitty Template Repair[/bold]")
|
|
36
|
+
console.print()
|
|
37
|
+
|
|
38
|
+
migration = RepairTemplatesMigration()
|
|
39
|
+
|
|
40
|
+
# Detect if repair needed
|
|
41
|
+
needs_repair = migration.detect(project_path)
|
|
42
|
+
|
|
43
|
+
if not needs_repair:
|
|
44
|
+
console.print("[green]✓ No broken templates detected - project is healthy![/green]")
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
console.print("[yellow]⚠ Broken templates detected[/yellow]")
|
|
48
|
+
console.print("Found bash script references in slash commands")
|
|
49
|
+
console.print()
|
|
50
|
+
|
|
51
|
+
if dry_run:
|
|
52
|
+
console.print("[cyan]Dry run mode - showing what would be changed:[/cyan]")
|
|
53
|
+
|
|
54
|
+
# Apply repair
|
|
55
|
+
result = migration.apply(project_path, dry_run=dry_run)
|
|
56
|
+
|
|
57
|
+
if result.success:
|
|
58
|
+
console.print()
|
|
59
|
+
console.print("[green]✓ Repair completed successfully[/green]")
|
|
60
|
+
for change in result.changes_made:
|
|
61
|
+
console.print(f" • {change}")
|
|
62
|
+
else:
|
|
63
|
+
console.print()
|
|
64
|
+
console.print("[red]✗ Repair failed[/red]")
|
|
65
|
+
for error in result.errors:
|
|
66
|
+
console.print(f" • [red]{error}[/red]")
|
|
67
|
+
|
|
68
|
+
if result.warnings:
|
|
69
|
+
console.print()
|
|
70
|
+
console.print("[yellow]Warnings:[/yellow]")
|
|
71
|
+
for warning in result.warnings:
|
|
72
|
+
console.print(f" • {warning}")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@app.command(name="worktree")
|
|
76
|
+
def repair_worktree(
|
|
77
|
+
all_worktrees: bool = typer.Option(
|
|
78
|
+
False,
|
|
79
|
+
"--all",
|
|
80
|
+
help="Check all worktrees in .worktrees/ directory"
|
|
81
|
+
),
|
|
82
|
+
worktree_path: Optional[Path] = typer.Argument(
|
|
83
|
+
None,
|
|
84
|
+
help="Specific worktree path to check (defaults to current directory if in a worktree)"
|
|
85
|
+
),
|
|
86
|
+
):
|
|
87
|
+
"""Diagnose worktree kitty-specs/ status.
|
|
88
|
+
|
|
89
|
+
This command checks if worktrees have kitty-specs/ directories and explains
|
|
90
|
+
how WP operations work:
|
|
91
|
+
|
|
92
|
+
- WP lane changes (move-task) ALWAYS use main repo's kitty-specs/
|
|
93
|
+
- Research artifacts can be added to worktree's kitty-specs/
|
|
94
|
+
- Stale WP files in worktrees don't affect lane operations
|
|
95
|
+
|
|
96
|
+
Examples:
|
|
97
|
+
spec-kitty repair worktree # Check current worktree
|
|
98
|
+
spec-kitty repair worktree --all # Check all worktrees
|
|
99
|
+
"""
|
|
100
|
+
console.print("[bold]Spec Kitty Worktree Diagnostics[/bold]")
|
|
101
|
+
console.print()
|
|
102
|
+
|
|
103
|
+
# Find project root
|
|
104
|
+
cwd = Path.cwd().resolve()
|
|
105
|
+
repo_root = locate_project_root(cwd)
|
|
106
|
+
|
|
107
|
+
if repo_root is None:
|
|
108
|
+
console.print("[red]Error:[/red] Could not locate project root")
|
|
109
|
+
raise typer.Exit(1)
|
|
110
|
+
|
|
111
|
+
main_root = get_main_repo_root(repo_root)
|
|
112
|
+
|
|
113
|
+
worktrees_to_check: list[Path] = []
|
|
114
|
+
|
|
115
|
+
if all_worktrees:
|
|
116
|
+
# Check all worktrees in .worktrees/
|
|
117
|
+
worktrees_dir = main_root / ".worktrees"
|
|
118
|
+
if worktrees_dir.exists():
|
|
119
|
+
for item in worktrees_dir.iterdir():
|
|
120
|
+
if item.is_dir() and (item / ".git").exists():
|
|
121
|
+
worktrees_to_check.append(item)
|
|
122
|
+
if not worktrees_to_check:
|
|
123
|
+
console.print("[yellow]No worktrees found in .worktrees/[/yellow]")
|
|
124
|
+
return
|
|
125
|
+
elif worktree_path:
|
|
126
|
+
# Use specified path
|
|
127
|
+
worktree_path = worktree_path.resolve()
|
|
128
|
+
if not worktree_path.exists():
|
|
129
|
+
console.print(f"[red]Error:[/red] Worktree path does not exist: {worktree_path}")
|
|
130
|
+
raise typer.Exit(1)
|
|
131
|
+
worktrees_to_check.append(worktree_path)
|
|
132
|
+
else:
|
|
133
|
+
# Try current directory
|
|
134
|
+
if is_worktree_context(cwd):
|
|
135
|
+
# Find the worktree root
|
|
136
|
+
current = cwd
|
|
137
|
+
while current != current.parent:
|
|
138
|
+
if (current / ".git").exists():
|
|
139
|
+
worktrees_to_check.append(current)
|
|
140
|
+
break
|
|
141
|
+
current = current.parent
|
|
142
|
+
if not worktrees_to_check:
|
|
143
|
+
console.print("[red]Error:[/red] Could not find worktree root")
|
|
144
|
+
raise typer.Exit(1)
|
|
145
|
+
else:
|
|
146
|
+
console.print("[yellow]Not in a worktree. Use --all to check all worktrees.[/yellow]")
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
console.print(f"Found {len(worktrees_to_check)} worktree(s) to check")
|
|
150
|
+
console.print()
|
|
151
|
+
|
|
152
|
+
for wt_path in worktrees_to_check:
|
|
153
|
+
has_kitty_specs = (wt_path / "kitty-specs").exists()
|
|
154
|
+
has_tasks = (wt_path / "kitty-specs").exists() and any(
|
|
155
|
+
(wt_path / "kitty-specs").rglob("tasks/*.md")
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
console.print(f"[bold]{wt_path.name}[/bold]")
|
|
159
|
+
|
|
160
|
+
if not has_kitty_specs:
|
|
161
|
+
console.print(" [dim]No kitty-specs/ directory[/dim]")
|
|
162
|
+
else:
|
|
163
|
+
console.print(f" kitty-specs/: [green]present[/green]")
|
|
164
|
+
if has_tasks:
|
|
165
|
+
console.print(f" tasks/*.md: [yellow]present (stale copies)[/yellow]")
|
|
166
|
+
else:
|
|
167
|
+
console.print(f" tasks/*.md: [dim]none[/dim]")
|
|
168
|
+
|
|
169
|
+
console.print()
|
|
170
|
+
console.print("[bold cyan]How WP operations work:[/bold cyan]")
|
|
171
|
+
console.print(" • [green]move-task[/green] always updates [bold]main repo's[/bold] kitty-specs/")
|
|
172
|
+
console.print(" • Research artifacts can be added to worktree's kitty-specs/")
|
|
173
|
+
console.print(" • Stale WP files in worktrees are [dim]ignored[/dim] by lane operations")
|
|
174
|
+
console.print()
|
|
175
|
+
console.print("[dim]No repair needed - this is informational only.[/dim]")
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Research command implementation for Spec Kitty CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import shutil
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
|
|
12
|
+
from specify_cli.acceptance import AcceptanceError, detect_feature_slug
|
|
13
|
+
from specify_cli.cli import StepTracker
|
|
14
|
+
from specify_cli.cli.helpers import check_version_compatibility, console, get_project_root_or_exit, show_banner
|
|
15
|
+
from specify_cli.core import MISSION_CHOICES
|
|
16
|
+
from specify_cli.core.project_resolver import resolve_template_path, resolve_worktree_aware_feature_dir
|
|
17
|
+
from specify_cli.mission import get_feature_mission_key
|
|
18
|
+
from specify_cli.plan_validation import PlanValidationError, validate_plan_filled
|
|
19
|
+
from specify_cli.tasks_support import TaskCliError, find_repo_root
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def research(
|
|
23
|
+
feature: Optional[str] = typer.Option(None, "--feature", help="Feature slug to target (auto-detected when omitted)"),
|
|
24
|
+
force: bool = typer.Option(False, "--force", help="Overwrite existing research artifacts"),
|
|
25
|
+
) -> None:
|
|
26
|
+
"""Execute Phase 0 research workflow to scaffold artifacts."""
|
|
27
|
+
|
|
28
|
+
show_banner()
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
repo_root = find_repo_root()
|
|
32
|
+
except TaskCliError as exc:
|
|
33
|
+
console.print(f"[red]Error:[/red] {exc}")
|
|
34
|
+
raise typer.Exit(1)
|
|
35
|
+
|
|
36
|
+
project_root = get_project_root_or_exit(repo_root)
|
|
37
|
+
check_version_compatibility(project_root, "research")
|
|
38
|
+
|
|
39
|
+
tracker = StepTracker("Research Phase Setup")
|
|
40
|
+
tracker.add("project", "Locate project root")
|
|
41
|
+
tracker.add("feature", "Resolve feature directory")
|
|
42
|
+
tracker.add("research-md", "Ensure research.md")
|
|
43
|
+
tracker.add("data-model", "Ensure data-model.md")
|
|
44
|
+
tracker.add("research-csv", "Ensure research CSV stubs")
|
|
45
|
+
tracker.add("summary", "Summarize outputs")
|
|
46
|
+
console.print()
|
|
47
|
+
|
|
48
|
+
tracker.start("project")
|
|
49
|
+
tracker.complete("project", str(project_root))
|
|
50
|
+
|
|
51
|
+
tracker.start("feature")
|
|
52
|
+
try:
|
|
53
|
+
feature_slug = (feature or detect_feature_slug(repo_root, cwd=Path.cwd())).strip()
|
|
54
|
+
except AcceptanceError as exc:
|
|
55
|
+
tracker.error("feature", str(exc))
|
|
56
|
+
console.print(tracker.render())
|
|
57
|
+
console.print(f"[red]Error:[/red] {exc}")
|
|
58
|
+
raise typer.Exit(1)
|
|
59
|
+
|
|
60
|
+
feature_dir = resolve_worktree_aware_feature_dir(repo_root, feature_slug, Path.cwd(), console)
|
|
61
|
+
feature_dir.mkdir(parents=True, exist_ok=True)
|
|
62
|
+
|
|
63
|
+
# Get mission from feature's meta.json (not project-level default)
|
|
64
|
+
mission_key = get_feature_mission_key(feature_dir)
|
|
65
|
+
mission_display = MISSION_CHOICES.get(mission_key, mission_key)
|
|
66
|
+
tracker.complete("feature", f"{feature_dir} ({mission_display})")
|
|
67
|
+
|
|
68
|
+
# Validate that plan.md has been filled out before proceeding
|
|
69
|
+
plan_path = feature_dir / "plan.md"
|
|
70
|
+
try:
|
|
71
|
+
validate_plan_filled(plan_path, feature_slug=feature_slug, strict=True)
|
|
72
|
+
except PlanValidationError as exc:
|
|
73
|
+
console.print(tracker.render())
|
|
74
|
+
console.print()
|
|
75
|
+
console.print(f"[red]Error:[/red] {exc}")
|
|
76
|
+
console.print()
|
|
77
|
+
console.print("[yellow]Next steps:[/yellow]")
|
|
78
|
+
console.print(" 1. Run [cyan]/spec-kitty.plan[/cyan] to fill in the technical architecture")
|
|
79
|
+
console.print(" 2. Complete all [FEATURE], [DATE], and technical context placeholders")
|
|
80
|
+
console.print(" 3. Remove [REMOVE IF UNUSED] sections and choose your project structure")
|
|
81
|
+
console.print(" 4. Then run [cyan]/spec-kitty.research[/cyan] again")
|
|
82
|
+
raise typer.Exit(1)
|
|
83
|
+
|
|
84
|
+
created_paths: list[Path] = []
|
|
85
|
+
|
|
86
|
+
def _copy_asset(step_key: str, label: str, relative_path: Path, template_name: Path) -> None:
|
|
87
|
+
tracker.start(step_key)
|
|
88
|
+
dest_path = feature_dir / relative_path
|
|
89
|
+
template_path = resolve_template_path(project_root, mission_key, template_name)
|
|
90
|
+
|
|
91
|
+
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
|
92
|
+
try:
|
|
93
|
+
if dest_path.exists() and not force:
|
|
94
|
+
created_paths.append(dest_path)
|
|
95
|
+
return
|
|
96
|
+
if template_path and template_path.is_file():
|
|
97
|
+
shutil.copy2(template_path, dest_path)
|
|
98
|
+
else:
|
|
99
|
+
if dest_path.exists():
|
|
100
|
+
dest_path.unlink()
|
|
101
|
+
dest_path.touch()
|
|
102
|
+
created_paths.append(dest_path)
|
|
103
|
+
tracker.complete(step_key, label)
|
|
104
|
+
except Exception as exc: # pragma: no cover - surfaces filesystem errors
|
|
105
|
+
tracker.error(step_key, str(exc))
|
|
106
|
+
console.print(tracker.render())
|
|
107
|
+
raise typer.Exit(1)
|
|
108
|
+
|
|
109
|
+
_copy_asset("research-md", "research.md ready", Path("research.md"), Path("research.md"))
|
|
110
|
+
_copy_asset("data-model", "data-model.md ready", Path("data-model.md"), Path("data-model.md"))
|
|
111
|
+
|
|
112
|
+
tracker.start("research-csv")
|
|
113
|
+
csv_targets = [
|
|
114
|
+
(Path("research") / "evidence-log.csv", Path("research") / "evidence-log.csv"),
|
|
115
|
+
(Path("research") / "source-register.csv", Path("research") / "source-register.csv"),
|
|
116
|
+
]
|
|
117
|
+
csv_errors: list[str] = []
|
|
118
|
+
for dest_rel, template_rel in csv_targets:
|
|
119
|
+
dest_path = feature_dir / dest_rel
|
|
120
|
+
template_path = resolve_template_path(project_root, mission_key, template_rel)
|
|
121
|
+
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
|
122
|
+
try:
|
|
123
|
+
if dest_path.exists() and not force:
|
|
124
|
+
created_paths.append(dest_path)
|
|
125
|
+
continue
|
|
126
|
+
if template_path and template_path.is_file():
|
|
127
|
+
shutil.copy2(template_path, dest_path)
|
|
128
|
+
else:
|
|
129
|
+
if dest_path.exists():
|
|
130
|
+
dest_path.unlink()
|
|
131
|
+
dest_path.touch()
|
|
132
|
+
created_paths.append(dest_path)
|
|
133
|
+
except Exception as exc: # pragma: no cover
|
|
134
|
+
csv_errors.append(f"{dest_rel}: {exc}")
|
|
135
|
+
|
|
136
|
+
if csv_errors:
|
|
137
|
+
tracker.error("research-csv", "; ".join(csv_errors))
|
|
138
|
+
console.print(tracker.render())
|
|
139
|
+
raise typer.Exit(1)
|
|
140
|
+
else:
|
|
141
|
+
tracker.complete("research-csv", "CSV templates ready")
|
|
142
|
+
|
|
143
|
+
tracker.start("summary")
|
|
144
|
+
tracker.complete("summary", f"{len(created_paths)} artifacts ready")
|
|
145
|
+
|
|
146
|
+
console.print(tracker.render())
|
|
147
|
+
|
|
148
|
+
relative_paths = [
|
|
149
|
+
str(path.relative_to(feature_dir)) if path.is_relative_to(feature_dir) else str(path)
|
|
150
|
+
for path in created_paths
|
|
151
|
+
]
|
|
152
|
+
summary_lines = "\n".join(f"- [cyan]{rel}[/cyan]" for rel in sorted(set(relative_paths)))
|
|
153
|
+
console.print()
|
|
154
|
+
console.print(
|
|
155
|
+
Panel(
|
|
156
|
+
summary_lines or "No artifacts were created (existing files kept).",
|
|
157
|
+
title="Research Artifacts",
|
|
158
|
+
border_style="cyan",
|
|
159
|
+
padding=(1, 2),
|
|
160
|
+
)
|
|
161
|
+
)
|
|
162
|
+
console.print()
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
__all__ = ["research"]
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
"""Sync command - synchronize workspace with upstream changes.
|
|
2
|
+
|
|
3
|
+
This command updates a workspace with changes from its base branch or parent.
|
|
4
|
+
For git workspaces, this performs a rebase. For jj workspaces, this updates
|
|
5
|
+
the workspace revision.
|
|
6
|
+
|
|
7
|
+
Key difference:
|
|
8
|
+
- git: Sync may fail on conflicts (must be resolved before continuing)
|
|
9
|
+
- jj: Sync always succeeds (conflicts are stored and can be resolved later)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import re
|
|
15
|
+
import subprocess
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
import typer
|
|
19
|
+
from rich.console import Console
|
|
20
|
+
from rich.panel import Panel
|
|
21
|
+
from rich.table import Table
|
|
22
|
+
|
|
23
|
+
from specify_cli.core.vcs import (
|
|
24
|
+
ChangeInfo,
|
|
25
|
+
ConflictInfo,
|
|
26
|
+
SyncResult,
|
|
27
|
+
SyncStatus,
|
|
28
|
+
VCSBackend,
|
|
29
|
+
get_vcs,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
console = Console()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _detect_workspace_context() -> tuple[Path, str | None]:
|
|
36
|
+
"""Detect current workspace and feature context.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Tuple of (workspace_path, feature_slug)
|
|
40
|
+
If not in a workspace, returns (cwd, None)
|
|
41
|
+
"""
|
|
42
|
+
cwd = Path.cwd()
|
|
43
|
+
|
|
44
|
+
# Check if we're in a .worktrees directory
|
|
45
|
+
parts = cwd.parts
|
|
46
|
+
for i, part in enumerate(parts):
|
|
47
|
+
if part == ".worktrees" and i + 1 < len(parts):
|
|
48
|
+
# Found a worktree path like: /repo/.worktrees/010-feature-WP01
|
|
49
|
+
workspace_name = parts[i + 1]
|
|
50
|
+
# Extract feature slug from workspace name (###-feature-WP##)
|
|
51
|
+
match = re.match(r"^(\d{3}-[a-zA-Z0-9-]+)-WP\d+$", workspace_name)
|
|
52
|
+
if match:
|
|
53
|
+
return cwd, match.group(1)
|
|
54
|
+
|
|
55
|
+
# Try to detect from git branch
|
|
56
|
+
try:
|
|
57
|
+
result = subprocess.run(
|
|
58
|
+
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
|
59
|
+
capture_output=True,
|
|
60
|
+
text=True,
|
|
61
|
+
check=False,
|
|
62
|
+
cwd=cwd,
|
|
63
|
+
)
|
|
64
|
+
if result.returncode == 0:
|
|
65
|
+
branch_name = result.stdout.strip()
|
|
66
|
+
# Check if branch matches WP pattern (###-feature-WP##)
|
|
67
|
+
match = re.match(r"^(\d{3}-[a-zA-Z0-9-]+)-WP\d+$", branch_name)
|
|
68
|
+
if match:
|
|
69
|
+
return cwd, match.group(1)
|
|
70
|
+
except (FileNotFoundError, OSError):
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
# Not in a recognized workspace
|
|
74
|
+
return cwd, None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _display_changes_integrated(changes: list[ChangeInfo]) -> None:
|
|
78
|
+
"""Display changes that were integrated during sync."""
|
|
79
|
+
if not changes:
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
console.print(f"\n[cyan]Changes integrated ({len(changes)}):[/cyan]")
|
|
83
|
+
for change in changes[:5]: # Show first 5 changes
|
|
84
|
+
short_id = change.commit_id[:7] if change.commit_id else "unknown"
|
|
85
|
+
# Truncate message to 50 chars
|
|
86
|
+
msg = change.message[:50] + "..." if len(change.message) > 50 else change.message
|
|
87
|
+
console.print(f" • [dim]{short_id}[/dim] {msg}")
|
|
88
|
+
|
|
89
|
+
if len(changes) > 5:
|
|
90
|
+
console.print(f" [dim]... and {len(changes) - 5} more[/dim]")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _display_conflicts(conflicts: list[ConflictInfo]) -> None:
|
|
94
|
+
"""Display conflicts with actionable details.
|
|
95
|
+
|
|
96
|
+
Shows:
|
|
97
|
+
- File path
|
|
98
|
+
- Line ranges (if available)
|
|
99
|
+
- Conflict type
|
|
100
|
+
- Resolution hints
|
|
101
|
+
"""
|
|
102
|
+
if not conflicts:
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
console.print(f"\n[yellow]Conflicts ({len(conflicts)} files):[/yellow]")
|
|
106
|
+
|
|
107
|
+
# Create a table for better formatting
|
|
108
|
+
table = Table(show_header=True, header_style="bold yellow", show_lines=False)
|
|
109
|
+
table.add_column("File", style="cyan")
|
|
110
|
+
table.add_column("Type", style="dim")
|
|
111
|
+
table.add_column("Lines", style="dim")
|
|
112
|
+
|
|
113
|
+
for conflict in conflicts:
|
|
114
|
+
# Format line ranges
|
|
115
|
+
if conflict.line_ranges:
|
|
116
|
+
lines = ", ".join(f"{start}-{end}" for start, end in conflict.line_ranges)
|
|
117
|
+
else:
|
|
118
|
+
lines = "entire file"
|
|
119
|
+
|
|
120
|
+
table.add_row(
|
|
121
|
+
str(conflict.file_path),
|
|
122
|
+
conflict.conflict_type.value,
|
|
123
|
+
lines,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
console.print(table)
|
|
127
|
+
|
|
128
|
+
# Show resolution hints
|
|
129
|
+
console.print("\n[dim]To resolve conflicts:[/dim]")
|
|
130
|
+
console.print("[dim] 1. Edit the conflicted files to resolve markers[/dim]")
|
|
131
|
+
console.print("[dim] 2. Continue your work (jj) or commit resolution (git)[/dim]")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _git_repair(workspace_path: Path) -> bool:
|
|
135
|
+
"""Attempt git workspace recovery.
|
|
136
|
+
|
|
137
|
+
This is a best-effort recovery that tries:
|
|
138
|
+
1. Abort any in-progress rebase/merge
|
|
139
|
+
2. Reset to HEAD
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
True if recovery succeeded, False otherwise
|
|
143
|
+
|
|
144
|
+
Note: This may lose uncommitted work.
|
|
145
|
+
"""
|
|
146
|
+
try:
|
|
147
|
+
# First, try to abort any in-progress operations
|
|
148
|
+
for abort_cmd in [
|
|
149
|
+
["git", "rebase", "--abort"],
|
|
150
|
+
["git", "merge", "--abort"],
|
|
151
|
+
["git", "cherry-pick", "--abort"],
|
|
152
|
+
]:
|
|
153
|
+
subprocess.run(
|
|
154
|
+
abort_cmd,
|
|
155
|
+
cwd=workspace_path,
|
|
156
|
+
capture_output=True,
|
|
157
|
+
check=False,
|
|
158
|
+
timeout=10,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
# Reset to HEAD (keeping changes in working tree)
|
|
162
|
+
result = subprocess.run(
|
|
163
|
+
["git", "reset", "--mixed", "HEAD"],
|
|
164
|
+
cwd=workspace_path,
|
|
165
|
+
capture_output=True,
|
|
166
|
+
text=True,
|
|
167
|
+
check=False,
|
|
168
|
+
timeout=30,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
return result.returncode == 0
|
|
172
|
+
|
|
173
|
+
except (subprocess.TimeoutExpired, OSError):
|
|
174
|
+
return False
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _jj_repair(workspace_path: Path) -> bool:
|
|
178
|
+
"""Attempt jj workspace recovery via operation undo.
|
|
179
|
+
|
|
180
|
+
Jujutsu has much better recovery capabilities via the operation log.
|
|
181
|
+
This function tries to undo the last operation.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
True if recovery succeeded, False otherwise
|
|
185
|
+
"""
|
|
186
|
+
try:
|
|
187
|
+
# Try to undo the last operation
|
|
188
|
+
result = subprocess.run(
|
|
189
|
+
["jj", "undo"],
|
|
190
|
+
cwd=workspace_path,
|
|
191
|
+
capture_output=True,
|
|
192
|
+
text=True,
|
|
193
|
+
check=False,
|
|
194
|
+
timeout=30,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
if result.returncode == 0:
|
|
198
|
+
return True
|
|
199
|
+
|
|
200
|
+
# If undo fails, try to update the workspace
|
|
201
|
+
result = subprocess.run(
|
|
202
|
+
["jj", "workspace", "update-stale"],
|
|
203
|
+
cwd=workspace_path,
|
|
204
|
+
capture_output=True,
|
|
205
|
+
text=True,
|
|
206
|
+
check=False,
|
|
207
|
+
timeout=30,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
return result.returncode == 0
|
|
211
|
+
|
|
212
|
+
except (subprocess.TimeoutExpired, OSError):
|
|
213
|
+
return False
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def sync(
|
|
217
|
+
repair: bool = typer.Option(
|
|
218
|
+
False,
|
|
219
|
+
"--repair",
|
|
220
|
+
"-r",
|
|
221
|
+
help="Attempt workspace recovery (may lose uncommitted work)",
|
|
222
|
+
),
|
|
223
|
+
verbose: bool = typer.Option(
|
|
224
|
+
False,
|
|
225
|
+
"--verbose",
|
|
226
|
+
"-v",
|
|
227
|
+
help="Show detailed sync output",
|
|
228
|
+
),
|
|
229
|
+
) -> None:
|
|
230
|
+
"""Synchronize workspace with upstream changes.
|
|
231
|
+
|
|
232
|
+
Updates the current workspace with changes from its base branch or parent.
|
|
233
|
+
This is equivalent to:
|
|
234
|
+
- git: `git rebase <base-branch>`
|
|
235
|
+
- jj: `jj workspace update-stale` + auto-rebase
|
|
236
|
+
|
|
237
|
+
Key difference between VCS backends:
|
|
238
|
+
- git: Sync may FAIL on conflicts (must resolve before continuing)
|
|
239
|
+
- jj: Sync always SUCCEEDS (conflicts stored, resolve later)
|
|
240
|
+
|
|
241
|
+
Examples:
|
|
242
|
+
# Sync current workspace
|
|
243
|
+
spec-kitty sync
|
|
244
|
+
|
|
245
|
+
# Sync with verbose output
|
|
246
|
+
spec-kitty sync --verbose
|
|
247
|
+
|
|
248
|
+
# Attempt recovery from broken state
|
|
249
|
+
spec-kitty sync --repair
|
|
250
|
+
"""
|
|
251
|
+
console.print()
|
|
252
|
+
|
|
253
|
+
# Detect workspace context
|
|
254
|
+
workspace_path, feature_slug = _detect_workspace_context()
|
|
255
|
+
|
|
256
|
+
if feature_slug is None:
|
|
257
|
+
console.print("[yellow]⚠ Not in a recognized workspace[/yellow]")
|
|
258
|
+
console.print("Run this command from a worktree directory:")
|
|
259
|
+
console.print(" cd .worktrees/<feature>-WP##/")
|
|
260
|
+
raise typer.Exit(1)
|
|
261
|
+
|
|
262
|
+
console.print(f"[cyan]Workspace:[/cyan] {workspace_path.name}")
|
|
263
|
+
|
|
264
|
+
# Get VCS implementation
|
|
265
|
+
try:
|
|
266
|
+
vcs = get_vcs(workspace_path)
|
|
267
|
+
except Exception as e:
|
|
268
|
+
console.print(f"[red]Error:[/red] Failed to detect VCS: {e}")
|
|
269
|
+
raise typer.Exit(1)
|
|
270
|
+
|
|
271
|
+
console.print(f"[cyan]Backend:[/cyan] git")
|
|
272
|
+
console.print()
|
|
273
|
+
|
|
274
|
+
# Handle repair mode
|
|
275
|
+
if repair:
|
|
276
|
+
console.print("[yellow]Attempting workspace recovery...[/yellow]")
|
|
277
|
+
console.print("[dim]Note: This may lose uncommitted work[/dim]")
|
|
278
|
+
console.print()
|
|
279
|
+
|
|
280
|
+
if vcs.backend == VCSBackend.JUJUTSU:
|
|
281
|
+
success = _jj_repair(workspace_path)
|
|
282
|
+
else:
|
|
283
|
+
success = _git_repair(workspace_path)
|
|
284
|
+
|
|
285
|
+
if success:
|
|
286
|
+
console.print("[green]✓ Recovery successful[/green]")
|
|
287
|
+
console.print("Workspace state has been reset.")
|
|
288
|
+
else:
|
|
289
|
+
console.print("[red]✗ Recovery failed[/red]")
|
|
290
|
+
console.print("Manual intervention may be required.")
|
|
291
|
+
console.print()
|
|
292
|
+
if vcs.backend == VCSBackend.GIT:
|
|
293
|
+
console.print("[dim]Try these commands manually:[/dim]")
|
|
294
|
+
console.print(" git status")
|
|
295
|
+
console.print(" git rebase --abort")
|
|
296
|
+
console.print(" git reset --hard HEAD")
|
|
297
|
+
else:
|
|
298
|
+
console.print("[dim]Try these commands manually:[/dim]")
|
|
299
|
+
console.print(" jj status")
|
|
300
|
+
console.print(" jj op log")
|
|
301
|
+
console.print(" jj undo")
|
|
302
|
+
raise typer.Exit(1)
|
|
303
|
+
|
|
304
|
+
return
|
|
305
|
+
|
|
306
|
+
# Perform sync
|
|
307
|
+
console.print("[cyan]Syncing workspace...[/cyan]")
|
|
308
|
+
|
|
309
|
+
result: SyncResult = vcs.sync_workspace(workspace_path)
|
|
310
|
+
|
|
311
|
+
# Display result based on status
|
|
312
|
+
if result.status == SyncStatus.UP_TO_DATE:
|
|
313
|
+
console.print("\n[green]✓ Already up to date[/green]")
|
|
314
|
+
if result.message:
|
|
315
|
+
console.print(f"[dim]{result.message}[/dim]")
|
|
316
|
+
|
|
317
|
+
elif result.status == SyncStatus.SYNCED:
|
|
318
|
+
stats_parts = []
|
|
319
|
+
if result.files_updated > 0:
|
|
320
|
+
stats_parts.append(f"{result.files_updated} updated")
|
|
321
|
+
if result.files_added > 0:
|
|
322
|
+
stats_parts.append(f"{result.files_added} added")
|
|
323
|
+
if result.files_deleted > 0:
|
|
324
|
+
stats_parts.append(f"{result.files_deleted} deleted")
|
|
325
|
+
|
|
326
|
+
stats = ", ".join(stats_parts) if stats_parts else "no file changes"
|
|
327
|
+
console.print(f"\n[green]✓ Synced[/green] - {stats}")
|
|
328
|
+
|
|
329
|
+
if verbose:
|
|
330
|
+
_display_changes_integrated(result.changes_integrated)
|
|
331
|
+
|
|
332
|
+
if result.message:
|
|
333
|
+
console.print(f"[dim]{result.message}[/dim]")
|
|
334
|
+
|
|
335
|
+
elif result.status == SyncStatus.CONFLICTS:
|
|
336
|
+
# jj: This means sync succeeded but there are conflicts to resolve
|
|
337
|
+
console.print("\n[yellow]⚠ Synced with conflicts[/yellow]")
|
|
338
|
+
|
|
339
|
+
if vcs.backend == VCSBackend.JUJUTSU:
|
|
340
|
+
console.print("[dim]Conflicts are stored in the commit.[/dim]")
|
|
341
|
+
console.print("[dim]You can continue working and resolve later.[/dim]")
|
|
342
|
+
else:
|
|
343
|
+
console.print("[dim]You must resolve conflicts before continuing.[/dim]")
|
|
344
|
+
|
|
345
|
+
_display_conflicts(result.conflicts)
|
|
346
|
+
|
|
347
|
+
if verbose:
|
|
348
|
+
_display_changes_integrated(result.changes_integrated)
|
|
349
|
+
|
|
350
|
+
elif result.status == SyncStatus.FAILED:
|
|
351
|
+
console.print(f"\n[red]✗ Sync failed[/red]")
|
|
352
|
+
if result.message:
|
|
353
|
+
console.print(f"[dim]{result.message}[/dim]")
|
|
354
|
+
|
|
355
|
+
# Show conflicts if any
|
|
356
|
+
if result.conflicts:
|
|
357
|
+
_display_conflicts(result.conflicts)
|
|
358
|
+
|
|
359
|
+
console.print()
|
|
360
|
+
console.print("[dim]Try:[/dim]")
|
|
361
|
+
console.print(" spec-kitty sync --repair")
|
|
362
|
+
raise typer.Exit(1)
|
|
363
|
+
|
|
364
|
+
console.print()
|