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
specify_cli/manifest.py
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Manifest system for spec-kitty file verification.
|
|
3
|
+
This module generates and checks expected files based on the active mission.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Dict, List, Optional, Set, Tuple
|
|
8
|
+
import yaml
|
|
9
|
+
import subprocess
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FileManifest:
|
|
13
|
+
"""Manages the expected file manifest for spec-kitty missions."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, kittify_dir: Path):
|
|
16
|
+
self.kittify_dir = kittify_dir
|
|
17
|
+
self.active_mission = self._detect_active_mission()
|
|
18
|
+
self.mission_dir = kittify_dir / "missions" / self.active_mission if self.active_mission else None
|
|
19
|
+
|
|
20
|
+
def _detect_active_mission(self) -> Optional[str]:
|
|
21
|
+
"""Detect the active mission from the symlink or file."""
|
|
22
|
+
active_mission_path = self.kittify_dir / "active-mission"
|
|
23
|
+
if active_mission_path.exists():
|
|
24
|
+
if active_mission_path.is_symlink():
|
|
25
|
+
# It's a symlink, resolve it
|
|
26
|
+
target = active_mission_path.resolve()
|
|
27
|
+
return target.name
|
|
28
|
+
elif active_mission_path.is_file():
|
|
29
|
+
# It's a file with the mission name
|
|
30
|
+
return active_mission_path.read_text(encoding='utf-8-sig').strip()
|
|
31
|
+
|
|
32
|
+
# Default to software-dev if no active mission
|
|
33
|
+
return "software-dev"
|
|
34
|
+
|
|
35
|
+
def get_expected_files(self) -> Dict[str, List[str]]:
|
|
36
|
+
"""
|
|
37
|
+
Get a categorized list of expected files for the active mission.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Dict with categories as keys and file paths as values
|
|
41
|
+
"""
|
|
42
|
+
if not self.mission_dir or not self.mission_dir.exists():
|
|
43
|
+
return {}
|
|
44
|
+
|
|
45
|
+
manifest = {
|
|
46
|
+
"commands": [],
|
|
47
|
+
"templates": [],
|
|
48
|
+
"scripts": [],
|
|
49
|
+
"mission_files": []
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Mission config file
|
|
53
|
+
mission_yaml = self.mission_dir / "mission.yaml"
|
|
54
|
+
if mission_yaml.exists():
|
|
55
|
+
manifest["mission_files"].append(str(mission_yaml.relative_to(self.kittify_dir)))
|
|
56
|
+
|
|
57
|
+
# Commands
|
|
58
|
+
commands_dir = self.mission_dir / "command-templates"
|
|
59
|
+
if commands_dir.exists():
|
|
60
|
+
for cmd_file in commands_dir.glob("*.md"):
|
|
61
|
+
manifest["commands"].append(str(cmd_file.relative_to(self.kittify_dir)))
|
|
62
|
+
|
|
63
|
+
# Templates
|
|
64
|
+
templates_dir = self.mission_dir / "templates"
|
|
65
|
+
if templates_dir.exists():
|
|
66
|
+
for tmpl_file in templates_dir.glob("*.md"):
|
|
67
|
+
manifest["templates"].append(str(tmpl_file.relative_to(self.kittify_dir)))
|
|
68
|
+
|
|
69
|
+
# Scripts referenced in commands
|
|
70
|
+
manifest["scripts"] = self._get_referenced_scripts()
|
|
71
|
+
|
|
72
|
+
return manifest
|
|
73
|
+
|
|
74
|
+
def _get_referenced_scripts(self) -> List[str]:
|
|
75
|
+
"""Extract script references from command files, filtered by platform."""
|
|
76
|
+
import platform
|
|
77
|
+
scripts = set()
|
|
78
|
+
|
|
79
|
+
if not self.mission_dir:
|
|
80
|
+
return []
|
|
81
|
+
|
|
82
|
+
commands_dir = self.mission_dir / "command-templates"
|
|
83
|
+
if not commands_dir.exists():
|
|
84
|
+
return []
|
|
85
|
+
|
|
86
|
+
# Determine which script type to look for based on platform
|
|
87
|
+
is_windows = platform.system() == 'Windows'
|
|
88
|
+
script_key = 'ps:' if is_windows else 'sh:'
|
|
89
|
+
|
|
90
|
+
# Parse command files for script references
|
|
91
|
+
for cmd_file in commands_dir.glob("*.md"):
|
|
92
|
+
content = cmd_file.read_text(encoding='utf-8-sig')
|
|
93
|
+
lines = content.split('\n')
|
|
94
|
+
|
|
95
|
+
# Look for script references in YAML frontmatter
|
|
96
|
+
in_frontmatter = False
|
|
97
|
+
for line in lines:
|
|
98
|
+
if line.strip() == '---':
|
|
99
|
+
in_frontmatter = not in_frontmatter
|
|
100
|
+
if not in_frontmatter and in_frontmatter == False:
|
|
101
|
+
break # End of frontmatter
|
|
102
|
+
elif in_frontmatter:
|
|
103
|
+
# Only check for scripts relevant to this platform
|
|
104
|
+
if script_key in line:
|
|
105
|
+
# Extract script path
|
|
106
|
+
parts = line.split(':', 1)
|
|
107
|
+
if len(parts) == 2:
|
|
108
|
+
script_line = parts[1].strip().strip('"').strip("'")
|
|
109
|
+
# Extract just the script path, not the arguments
|
|
110
|
+
# Script path is the first part before any spaces or arguments
|
|
111
|
+
script_parts = script_line.split()
|
|
112
|
+
if script_parts:
|
|
113
|
+
script_path = script_parts[0]
|
|
114
|
+
# Only include actual .kittify/scripts/ files
|
|
115
|
+
# Skip CLI commands (spec-kitty, git, python, etc.)
|
|
116
|
+
if script_path.startswith('.kittify/scripts/'):
|
|
117
|
+
script_path = script_path.replace('.kittify/', '', 1)
|
|
118
|
+
scripts.add(script_path)
|
|
119
|
+
|
|
120
|
+
return sorted(list(scripts))
|
|
121
|
+
|
|
122
|
+
def check_files(self) -> Dict[str, Dict[str, str]]:
|
|
123
|
+
"""
|
|
124
|
+
Check which expected files exist and which are missing.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Dict with 'present', 'missing', and 'extra' keys
|
|
128
|
+
"""
|
|
129
|
+
expected = self.get_expected_files()
|
|
130
|
+
result = {
|
|
131
|
+
"present": {},
|
|
132
|
+
"missing": {},
|
|
133
|
+
"modified": {},
|
|
134
|
+
"extra": []
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
# Check each category
|
|
138
|
+
for category, files in expected.items():
|
|
139
|
+
for file_path in files:
|
|
140
|
+
full_path = self.kittify_dir / file_path
|
|
141
|
+
if full_path.exists():
|
|
142
|
+
result["present"][file_path] = category
|
|
143
|
+
else:
|
|
144
|
+
result["missing"][file_path] = category
|
|
145
|
+
|
|
146
|
+
# TODO: Check for modifications using git or checksums
|
|
147
|
+
# TODO: Find extra files not in manifest
|
|
148
|
+
|
|
149
|
+
return result
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class WorktreeStatus:
|
|
153
|
+
"""Manages worktree and feature branch status."""
|
|
154
|
+
|
|
155
|
+
def __init__(self, repo_root: Path):
|
|
156
|
+
self.repo_root = repo_root
|
|
157
|
+
|
|
158
|
+
def get_all_features(self) -> List[str]:
|
|
159
|
+
"""Get all feature branches and directories."""
|
|
160
|
+
features = set()
|
|
161
|
+
|
|
162
|
+
# Get features from branches
|
|
163
|
+
try:
|
|
164
|
+
result = subprocess.run(
|
|
165
|
+
["git", "branch", "-a"],
|
|
166
|
+
cwd=self.repo_root,
|
|
167
|
+
capture_output=True,
|
|
168
|
+
text=True,
|
|
169
|
+
check=True
|
|
170
|
+
)
|
|
171
|
+
for line in result.stdout.split('\n'):
|
|
172
|
+
line = line.strip().replace('* ', '')
|
|
173
|
+
# Match feature branch pattern (###-name)
|
|
174
|
+
if line and not line.startswith('remotes/'):
|
|
175
|
+
parts = line.split('/')
|
|
176
|
+
branch = parts[-1]
|
|
177
|
+
if branch and branch[0].isdigit() and '-' in branch:
|
|
178
|
+
features.add(branch)
|
|
179
|
+
except subprocess.CalledProcessError:
|
|
180
|
+
pass
|
|
181
|
+
|
|
182
|
+
# Get features from kitty-specs
|
|
183
|
+
kitty_specs = self.repo_root / "kitty-specs"
|
|
184
|
+
if kitty_specs.exists():
|
|
185
|
+
for feature_dir in kitty_specs.iterdir():
|
|
186
|
+
if feature_dir.is_dir() and feature_dir.name[0].isdigit() and '-' in feature_dir.name:
|
|
187
|
+
features.add(feature_dir.name)
|
|
188
|
+
|
|
189
|
+
return sorted(list(features))
|
|
190
|
+
|
|
191
|
+
def get_feature_status(self, feature: str) -> Dict[str, any]:
|
|
192
|
+
"""Get comprehensive status for a feature."""
|
|
193
|
+
status = {
|
|
194
|
+
"name": feature,
|
|
195
|
+
"branch_exists": False,
|
|
196
|
+
"branch_merged": False,
|
|
197
|
+
"worktree_exists": False,
|
|
198
|
+
"worktree_path": None,
|
|
199
|
+
"artifacts_in_main": [],
|
|
200
|
+
"artifacts_in_worktree": [],
|
|
201
|
+
"last_activity": None,
|
|
202
|
+
"state": "unknown" # not_started, in_development, ready_to_merge, merged, abandoned
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
# Check if branch exists
|
|
206
|
+
try:
|
|
207
|
+
result = subprocess.run(
|
|
208
|
+
["git", "show-ref", f"refs/heads/{feature}"],
|
|
209
|
+
cwd=self.repo_root,
|
|
210
|
+
capture_output=True,
|
|
211
|
+
text=True
|
|
212
|
+
)
|
|
213
|
+
status["branch_exists"] = result.returncode == 0
|
|
214
|
+
except subprocess.CalledProcessError:
|
|
215
|
+
pass
|
|
216
|
+
|
|
217
|
+
# Check if merged
|
|
218
|
+
if status["branch_exists"]:
|
|
219
|
+
try:
|
|
220
|
+
result = subprocess.run(
|
|
221
|
+
["git", "branch", "--merged", "main"],
|
|
222
|
+
cwd=self.repo_root,
|
|
223
|
+
capture_output=True,
|
|
224
|
+
text=True,
|
|
225
|
+
check=True
|
|
226
|
+
)
|
|
227
|
+
status["branch_merged"] = feature in result.stdout
|
|
228
|
+
except subprocess.CalledProcessError:
|
|
229
|
+
pass
|
|
230
|
+
|
|
231
|
+
# Check worktree
|
|
232
|
+
worktree_path = self.repo_root / ".worktrees" / feature
|
|
233
|
+
if worktree_path.exists():
|
|
234
|
+
status["worktree_exists"] = True
|
|
235
|
+
status["worktree_path"] = str(worktree_path)
|
|
236
|
+
|
|
237
|
+
# Check artifacts in main
|
|
238
|
+
main_artifacts_path = self.repo_root / "kitty-specs" / feature
|
|
239
|
+
if main_artifacts_path.exists():
|
|
240
|
+
for artifact in main_artifacts_path.glob("*.md"):
|
|
241
|
+
status["artifacts_in_main"].append(artifact.name)
|
|
242
|
+
|
|
243
|
+
# Check artifacts in worktree
|
|
244
|
+
if status["worktree_exists"]:
|
|
245
|
+
worktree_artifacts_path = worktree_path / "kitty-specs" / feature
|
|
246
|
+
if worktree_artifacts_path.exists():
|
|
247
|
+
for artifact in worktree_artifacts_path.glob("*.md"):
|
|
248
|
+
status["artifacts_in_worktree"].append(artifact.name)
|
|
249
|
+
|
|
250
|
+
# Determine state
|
|
251
|
+
if not status["branch_exists"] and not status["artifacts_in_main"]:
|
|
252
|
+
status["state"] = "not_started"
|
|
253
|
+
elif status["branch_merged"] and status["artifacts_in_main"]:
|
|
254
|
+
status["state"] = "merged"
|
|
255
|
+
elif status["worktree_exists"] or status["artifacts_in_worktree"]:
|
|
256
|
+
status["state"] = "in_development"
|
|
257
|
+
elif status["branch_exists"] and not status["worktree_exists"]:
|
|
258
|
+
status["state"] = "ready_to_merge"
|
|
259
|
+
elif not status["branch_exists"] and status["artifacts_in_main"]:
|
|
260
|
+
status["state"] = "merged" # Branch was deleted after merge
|
|
261
|
+
|
|
262
|
+
return status
|
|
263
|
+
|
|
264
|
+
def get_worktree_summary(self) -> Dict[str, int]:
|
|
265
|
+
"""Get summary counts of worktree states."""
|
|
266
|
+
features = self.get_all_features()
|
|
267
|
+
summary = {
|
|
268
|
+
"total_features": len(features),
|
|
269
|
+
"active_worktrees": 0,
|
|
270
|
+
"merged_features": 0,
|
|
271
|
+
"in_development": 0,
|
|
272
|
+
"not_started": 0
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
for feature in features:
|
|
276
|
+
status = self.get_feature_status(feature)
|
|
277
|
+
if status["worktree_exists"]:
|
|
278
|
+
summary["active_worktrees"] += 1
|
|
279
|
+
if status["state"] == "merged":
|
|
280
|
+
summary["merged_features"] += 1
|
|
281
|
+
elif status["state"] == "in_development":
|
|
282
|
+
summary["in_development"] += 1
|
|
283
|
+
elif status["state"] == "not_started":
|
|
284
|
+
summary["not_started"] += 1
|
|
285
|
+
|
|
286
|
+
return summary
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Merge subpackage for spec-kitty merge operations.
|
|
2
|
+
|
|
3
|
+
This package provides functionality for merging work package branches
|
|
4
|
+
back into the main branch with pre-flight validation, conflict forecasting,
|
|
5
|
+
and automatic status file resolution.
|
|
6
|
+
|
|
7
|
+
Modules:
|
|
8
|
+
preflight: Pre-flight validation before merge
|
|
9
|
+
forecast: Conflict prediction for dry-run mode
|
|
10
|
+
ordering: Dependency-based merge ordering
|
|
11
|
+
status_resolver: Auto-resolution of status file conflicts
|
|
12
|
+
state: Merge state persistence and resume
|
|
13
|
+
executor: Core merge execution logic
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from specify_cli.merge.executor import (
|
|
19
|
+
MergeExecutionError,
|
|
20
|
+
MergeResult,
|
|
21
|
+
execute_legacy_merge,
|
|
22
|
+
execute_merge,
|
|
23
|
+
)
|
|
24
|
+
from specify_cli.merge.forecast import ConflictPrediction, is_status_file, predict_conflicts
|
|
25
|
+
from specify_cli.merge.ordering import MergeOrderError, get_merge_order, has_dependency_info
|
|
26
|
+
from specify_cli.merge.preflight import PreflightResult, WPStatus, run_preflight
|
|
27
|
+
from specify_cli.merge.state import (
|
|
28
|
+
MergeState,
|
|
29
|
+
clear_state,
|
|
30
|
+
has_active_merge,
|
|
31
|
+
load_state,
|
|
32
|
+
save_state,
|
|
33
|
+
)
|
|
34
|
+
from specify_cli.merge.status_resolver import ResolutionResult, resolve_status_conflicts
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
# Executor
|
|
38
|
+
"execute_merge",
|
|
39
|
+
"execute_legacy_merge",
|
|
40
|
+
"MergeResult",
|
|
41
|
+
"MergeExecutionError",
|
|
42
|
+
# Forecast
|
|
43
|
+
"predict_conflicts",
|
|
44
|
+
"ConflictPrediction",
|
|
45
|
+
"is_status_file",
|
|
46
|
+
# Ordering
|
|
47
|
+
"get_merge_order",
|
|
48
|
+
"MergeOrderError",
|
|
49
|
+
"has_dependency_info",
|
|
50
|
+
# Preflight
|
|
51
|
+
"run_preflight",
|
|
52
|
+
"PreflightResult",
|
|
53
|
+
"WPStatus",
|
|
54
|
+
# Status resolver
|
|
55
|
+
"resolve_status_conflicts",
|
|
56
|
+
"ResolutionResult",
|
|
57
|
+
# State persistence
|
|
58
|
+
"MergeState",
|
|
59
|
+
"save_state",
|
|
60
|
+
"load_state",
|
|
61
|
+
"clear_state",
|
|
62
|
+
"has_active_merge",
|
|
63
|
+
]
|