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,270 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Enhanced verify_setup implementation for spec-kitty.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import subprocess
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Dict, Optional
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
from rich.panel import Panel
|
|
12
|
+
|
|
13
|
+
from .manifest import FileManifest, WorktreeStatus
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def run_enhanced_verify(
|
|
17
|
+
repo_root: Path,
|
|
18
|
+
project_root: Path,
|
|
19
|
+
cwd: Path,
|
|
20
|
+
feature: Optional[str],
|
|
21
|
+
json_output: bool,
|
|
22
|
+
check_files: bool,
|
|
23
|
+
console: Console
|
|
24
|
+
) -> Dict:
|
|
25
|
+
"""
|
|
26
|
+
Run the enhanced verification with manifest checking and worktree status.
|
|
27
|
+
|
|
28
|
+
Returns a dict suitable for JSON output if needed.
|
|
29
|
+
"""
|
|
30
|
+
output_data = {
|
|
31
|
+
"environment": {},
|
|
32
|
+
"feature_detection": {},
|
|
33
|
+
"worktree_status": {},
|
|
34
|
+
"file_integrity": {},
|
|
35
|
+
"feature_analysis": {},
|
|
36
|
+
"recommendations": []
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# Initialize helpers
|
|
40
|
+
kittify_dir = project_root / ".kittify"
|
|
41
|
+
manifest = FileManifest(kittify_dir)
|
|
42
|
+
worktree_status = WorktreeStatus(repo_root)
|
|
43
|
+
|
|
44
|
+
# 1. Environment Information
|
|
45
|
+
in_worktree = '.worktrees' in str(cwd)
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
current_branch = subprocess.run(
|
|
49
|
+
["git", "branch", "--show-current"],
|
|
50
|
+
cwd=cwd,
|
|
51
|
+
capture_output=True,
|
|
52
|
+
text=True,
|
|
53
|
+
check=True
|
|
54
|
+
).stdout.strip()
|
|
55
|
+
except subprocess.CalledProcessError:
|
|
56
|
+
current_branch = None
|
|
57
|
+
|
|
58
|
+
output_data["environment"] = {
|
|
59
|
+
"working_directory": str(cwd),
|
|
60
|
+
"repo_root": str(repo_root),
|
|
61
|
+
"project_root": str(project_root),
|
|
62
|
+
"in_worktree": in_worktree,
|
|
63
|
+
"current_branch": current_branch,
|
|
64
|
+
"active_mission": manifest.active_mission
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if not json_output:
|
|
68
|
+
console.print("\n[bold]System Integrity Check[/bold]\n")
|
|
69
|
+
|
|
70
|
+
# Environment section
|
|
71
|
+
console.print("[cyan]1. Environment[/cyan]")
|
|
72
|
+
console.print(f" Working directory: {cwd}")
|
|
73
|
+
console.print(f" Repository root: {repo_root}")
|
|
74
|
+
|
|
75
|
+
if in_worktree:
|
|
76
|
+
console.print(f" [green]✓[/green] In worktree")
|
|
77
|
+
else:
|
|
78
|
+
console.print(f" [dim]○[/dim] Not in worktree")
|
|
79
|
+
|
|
80
|
+
if current_branch:
|
|
81
|
+
console.print(f" Current branch: {current_branch}")
|
|
82
|
+
if current_branch == "main":
|
|
83
|
+
console.print(f" [yellow]⚠[/yellow] On main branch")
|
|
84
|
+
else:
|
|
85
|
+
console.print(f" [yellow]⚠[/yellow] Could not detect branch")
|
|
86
|
+
|
|
87
|
+
# 2. File Integrity Check
|
|
88
|
+
if check_files:
|
|
89
|
+
file_check = manifest.check_files()
|
|
90
|
+
expected_files = manifest.get_expected_files()
|
|
91
|
+
|
|
92
|
+
total_expected = sum(len(files) for files in expected_files.values())
|
|
93
|
+
total_present = len(file_check["present"])
|
|
94
|
+
total_missing = len(file_check["missing"])
|
|
95
|
+
|
|
96
|
+
output_data["file_integrity"] = {
|
|
97
|
+
"active_mission": manifest.active_mission,
|
|
98
|
+
"total_expected": total_expected,
|
|
99
|
+
"total_present": total_present,
|
|
100
|
+
"total_missing": total_missing,
|
|
101
|
+
"missing_files": file_check["missing"],
|
|
102
|
+
"categories": {}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# Count by category
|
|
106
|
+
for category, files in expected_files.items():
|
|
107
|
+
present_in_category = sum(1 for f in files if f in file_check["present"])
|
|
108
|
+
output_data["file_integrity"]["categories"][category] = {
|
|
109
|
+
"expected": len(files),
|
|
110
|
+
"present": present_in_category,
|
|
111
|
+
"missing": len(files) - present_in_category
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if not json_output:
|
|
115
|
+
console.print("\n[cyan]2. Mission File Integrity[/cyan]")
|
|
116
|
+
console.print(f" Active mission: {manifest.active_mission}")
|
|
117
|
+
|
|
118
|
+
if total_missing == 0:
|
|
119
|
+
console.print(f" [green]✓[/green] All {total_expected} expected files present")
|
|
120
|
+
else:
|
|
121
|
+
console.print(f" [yellow]⚠[/yellow] {total_missing} of {total_expected} files missing")
|
|
122
|
+
|
|
123
|
+
# Show missing by category
|
|
124
|
+
for category in ["commands", "templates", "scripts"]:
|
|
125
|
+
cat_missing = [f for f, c in file_check["missing"].items() if c == category]
|
|
126
|
+
if cat_missing:
|
|
127
|
+
console.print(f" Missing {category}:")
|
|
128
|
+
for file in cat_missing[:3]: # Show first 3
|
|
129
|
+
console.print(f" - {file}")
|
|
130
|
+
if len(cat_missing) > 3:
|
|
131
|
+
console.print(f" ... and {len(cat_missing) - 3} more")
|
|
132
|
+
|
|
133
|
+
# 3. Worktree Status Overview
|
|
134
|
+
worktree_summary = worktree_status.get_worktree_summary()
|
|
135
|
+
output_data["worktree_status"] = worktree_summary
|
|
136
|
+
|
|
137
|
+
if not json_output:
|
|
138
|
+
console.print("\n[cyan]3. Worktree Overview[/cyan]")
|
|
139
|
+
console.print(f" Total features: {worktree_summary['total_features']}")
|
|
140
|
+
console.print(f" Active worktrees: {worktree_summary['active_worktrees']}")
|
|
141
|
+
console.print(f" Merged features: {worktree_summary['merged_features']}")
|
|
142
|
+
console.print(f" In development: {worktree_summary['in_development']}")
|
|
143
|
+
|
|
144
|
+
# 4. Feature Detection and Analysis
|
|
145
|
+
try:
|
|
146
|
+
from .acceptance import detect_feature_slug, AcceptanceError
|
|
147
|
+
feature_slug = (feature or detect_feature_slug(repo_root, cwd=cwd)).strip()
|
|
148
|
+
|
|
149
|
+
output_data["feature_detection"] = {
|
|
150
|
+
"detected": True,
|
|
151
|
+
"feature": feature_slug
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# Get detailed status for this feature
|
|
155
|
+
feature_status = worktree_status.get_feature_status(feature_slug)
|
|
156
|
+
output_data["feature_analysis"] = feature_status
|
|
157
|
+
|
|
158
|
+
if not json_output:
|
|
159
|
+
console.print("\n[cyan]4. Current Feature Status[/cyan]")
|
|
160
|
+
console.print(f" Feature: {feature_slug}")
|
|
161
|
+
console.print(f" State: {feature_status['state'].upper()}")
|
|
162
|
+
|
|
163
|
+
# Status indicators
|
|
164
|
+
if feature_status["branch_exists"]:
|
|
165
|
+
status_text = "merged" if feature_status["branch_merged"] else "active"
|
|
166
|
+
console.print(f" [green]✓[/green] Branch exists ({status_text})")
|
|
167
|
+
else:
|
|
168
|
+
console.print(f" [dim]○[/dim] No branch")
|
|
169
|
+
|
|
170
|
+
if feature_status["worktree_exists"]:
|
|
171
|
+
console.print(f" [green]✓[/green] Worktree at: {feature_status['worktree_path']}")
|
|
172
|
+
else:
|
|
173
|
+
console.print(f" [dim]○[/dim] No worktree")
|
|
174
|
+
|
|
175
|
+
# Artifacts
|
|
176
|
+
if feature_status["artifacts_in_main"]:
|
|
177
|
+
console.print(f" Artifacts in main: {', '.join(feature_status['artifacts_in_main'])}")
|
|
178
|
+
if feature_status["artifacts_in_worktree"]:
|
|
179
|
+
console.print(f" Artifacts in worktree: {', '.join(feature_status['artifacts_in_worktree'])}")
|
|
180
|
+
|
|
181
|
+
# State-based observations
|
|
182
|
+
if feature_status["state"] == "merged":
|
|
183
|
+
console.print(" [green]✓[/green] Feature appears to be merged")
|
|
184
|
+
elif feature_status["state"] == "in_development":
|
|
185
|
+
console.print(" [blue]●[/blue] Feature is in active development")
|
|
186
|
+
elif feature_status["state"] == "not_started":
|
|
187
|
+
console.print(" [dim]○[/dim] Feature not yet started")
|
|
188
|
+
|
|
189
|
+
except AcceptanceError as exc:
|
|
190
|
+
output_data["feature_detection"] = {
|
|
191
|
+
"detected": False,
|
|
192
|
+
"error": str(exc)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if not json_output:
|
|
196
|
+
console.print("\n[cyan]4. Feature Detection[/cyan]")
|
|
197
|
+
console.print(f" [yellow]⚠[/yellow] Could not detect feature: {exc}")
|
|
198
|
+
|
|
199
|
+
# 5. All Features Status Table
|
|
200
|
+
all_features = worktree_status.get_all_features()
|
|
201
|
+
|
|
202
|
+
if not json_output and all_features:
|
|
203
|
+
console.print("\n[cyan]5. All Features Status[/cyan]")
|
|
204
|
+
|
|
205
|
+
table = Table(show_header=True, header_style="bold")
|
|
206
|
+
table.add_column("Feature", style="cyan")
|
|
207
|
+
table.add_column("State", style="white")
|
|
208
|
+
table.add_column("Branch", style="white")
|
|
209
|
+
table.add_column("Worktree", style="white")
|
|
210
|
+
table.add_column("Artifacts", style="white")
|
|
211
|
+
|
|
212
|
+
for feat in all_features[:10]: # Show first 10
|
|
213
|
+
feat_status = worktree_status.get_feature_status(feat)
|
|
214
|
+
|
|
215
|
+
# Determine display values
|
|
216
|
+
state_display = {
|
|
217
|
+
"merged": "[green]MERGED[/green]",
|
|
218
|
+
"in_development": "[yellow]ACTIVE[/yellow]",
|
|
219
|
+
"ready_to_merge": "[blue]READY[/blue]",
|
|
220
|
+
"not_started": "[dim]NOT STARTED[/dim]",
|
|
221
|
+
"unknown": "[dim]?[/dim]"
|
|
222
|
+
}.get(feat_status["state"], feat_status["state"])
|
|
223
|
+
|
|
224
|
+
branch_display = "✓" if feat_status["branch_exists"] else "-"
|
|
225
|
+
if feat_status["branch_merged"]:
|
|
226
|
+
branch_display = "merged"
|
|
227
|
+
|
|
228
|
+
worktree_display = "✓" if feat_status["worktree_exists"] else "-"
|
|
229
|
+
|
|
230
|
+
artifact_count = len(feat_status["artifacts_in_main"]) + len(feat_status["artifacts_in_worktree"])
|
|
231
|
+
artifacts_display = str(artifact_count) if artifact_count > 0 else "-"
|
|
232
|
+
|
|
233
|
+
table.add_row(
|
|
234
|
+
feat,
|
|
235
|
+
state_display,
|
|
236
|
+
branch_display,
|
|
237
|
+
worktree_display,
|
|
238
|
+
artifacts_display
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
console.print(table)
|
|
242
|
+
|
|
243
|
+
if len(all_features) > 10:
|
|
244
|
+
console.print(f" [dim]... and {len(all_features) - 10} more features[/dim]")
|
|
245
|
+
|
|
246
|
+
# 6. Observations (not recommendations)
|
|
247
|
+
observations = []
|
|
248
|
+
|
|
249
|
+
if current_branch == "main" and in_worktree:
|
|
250
|
+
observations.append("Unusual: In worktree but on main branch")
|
|
251
|
+
|
|
252
|
+
if output_data.get("feature_analysis", {}).get("state") == "in_development":
|
|
253
|
+
if not output_data["feature_analysis"].get("worktree_exists"):
|
|
254
|
+
observations.append(f"Feature {feature_slug} has no worktree but has development artifacts")
|
|
255
|
+
|
|
256
|
+
if total_missing > 0 and check_files:
|
|
257
|
+
observations.append(f"Mission integrity: {total_missing} expected files not found")
|
|
258
|
+
|
|
259
|
+
output_data["observations"] = observations
|
|
260
|
+
|
|
261
|
+
if not json_output and observations:
|
|
262
|
+
console.print("\n[cyan]6. Observations[/cyan]")
|
|
263
|
+
for obs in observations:
|
|
264
|
+
console.print(f" • {obs}")
|
|
265
|
+
|
|
266
|
+
# Final summary
|
|
267
|
+
if not json_output:
|
|
268
|
+
console.print("\n[bold green]✓ Verification complete[/bold green]\n")
|
|
269
|
+
|
|
270
|
+
return output_data
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"""Workspace context management for runtime visibility.
|
|
2
|
+
|
|
3
|
+
This module manages persistent workspace context files stored in .kittify/workspaces/.
|
|
4
|
+
These files provide runtime visibility into workspace state for LLM agents and CLI tools.
|
|
5
|
+
|
|
6
|
+
Context files are:
|
|
7
|
+
- Created during `spec-kitty implement` command
|
|
8
|
+
- Stored in main repo's .kittify/workspaces/ directory
|
|
9
|
+
- Readable from both main repo and worktrees (via relative path)
|
|
10
|
+
- Cleaned up during merge or explicit workspace deletion
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
from dataclasses import asdict, dataclass
|
|
17
|
+
from datetime import datetime, timezone
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any, Dict
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class WorkspaceContext:
|
|
24
|
+
"""
|
|
25
|
+
Runtime context for a work package workspace.
|
|
26
|
+
|
|
27
|
+
Provides all information an agent needs to understand workspace state.
|
|
28
|
+
Stored as JSON in .kittify/workspaces/###-feature-WP##.json
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
# Identity
|
|
32
|
+
wp_id: str # e.g., "WP02"
|
|
33
|
+
feature_slug: str # e.g., "010-workspace-per-wp"
|
|
34
|
+
|
|
35
|
+
# Paths
|
|
36
|
+
worktree_path: str # Relative path from repo root (e.g., ".worktrees/010-feature-WP02")
|
|
37
|
+
branch_name: str # Git branch name (e.g., "010-feature-WP02")
|
|
38
|
+
|
|
39
|
+
# Base tracking
|
|
40
|
+
base_branch: str # Branch this was created from (e.g., "010-feature-WP01" or "main")
|
|
41
|
+
base_commit: str # Git SHA this was created from
|
|
42
|
+
|
|
43
|
+
# Dependencies
|
|
44
|
+
dependencies: list[str] # List of WP IDs this depends on (e.g., ["WP01"])
|
|
45
|
+
|
|
46
|
+
# Metadata
|
|
47
|
+
created_at: str # ISO timestamp when workspace was created
|
|
48
|
+
created_by: str # Command that created this (e.g., "implement-command")
|
|
49
|
+
vcs_backend: str # "git" or "jj"
|
|
50
|
+
|
|
51
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
52
|
+
"""Convert to dictionary for JSON serialization."""
|
|
53
|
+
return asdict(self)
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def from_dict(cls, data: Dict[str, Any]) -> WorkspaceContext:
|
|
57
|
+
"""Create from dictionary (JSON deserialization)."""
|
|
58
|
+
return cls(**data)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_workspaces_dir(repo_root: Path) -> Path:
|
|
62
|
+
"""Get or create the workspaces context directory.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
repo_root: Repository root path
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Path to .kittify/workspaces/ directory
|
|
69
|
+
"""
|
|
70
|
+
workspaces_dir = repo_root / ".kittify" / "workspaces"
|
|
71
|
+
workspaces_dir.mkdir(parents=True, exist_ok=True)
|
|
72
|
+
return workspaces_dir
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def get_context_path(repo_root: Path, workspace_name: str) -> Path:
|
|
76
|
+
"""Get path to workspace context file.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
repo_root: Repository root path
|
|
80
|
+
workspace_name: Workspace name (e.g., "010-feature-WP02")
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Path to context JSON file
|
|
84
|
+
"""
|
|
85
|
+
workspaces_dir = get_workspaces_dir(repo_root)
|
|
86
|
+
return workspaces_dir / f"{workspace_name}.json"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def save_context(repo_root: Path, context: WorkspaceContext) -> Path:
|
|
90
|
+
"""Save workspace context to JSON file.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
repo_root: Repository root path
|
|
94
|
+
context: Workspace context to save
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Path to saved context file
|
|
98
|
+
"""
|
|
99
|
+
workspace_name = f"{context.feature_slug}-{context.wp_id}"
|
|
100
|
+
context_path = get_context_path(repo_root, workspace_name)
|
|
101
|
+
|
|
102
|
+
# Write JSON with pretty formatting
|
|
103
|
+
context_path.write_text(
|
|
104
|
+
json.dumps(context.to_dict(), indent=2) + "\n",
|
|
105
|
+
encoding="utf-8"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
return context_path
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def load_context(repo_root: Path, workspace_name: str) -> WorkspaceContext | None:
|
|
112
|
+
"""Load workspace context from JSON file.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
repo_root: Repository root path
|
|
116
|
+
workspace_name: Workspace name (e.g., "010-feature-WP02")
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
WorkspaceContext if file exists, None otherwise
|
|
120
|
+
"""
|
|
121
|
+
context_path = get_context_path(repo_root, workspace_name)
|
|
122
|
+
|
|
123
|
+
if not context_path.exists():
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
data = json.loads(context_path.read_text(encoding="utf-8"))
|
|
128
|
+
return WorkspaceContext.from_dict(data)
|
|
129
|
+
except (json.JSONDecodeError, TypeError, KeyError):
|
|
130
|
+
# Malformed context file
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def delete_context(repo_root: Path, workspace_name: str) -> bool:
|
|
135
|
+
"""Delete workspace context file.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
repo_root: Repository root path
|
|
139
|
+
workspace_name: Workspace name (e.g., "010-feature-WP02")
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
True if deleted, False if didn't exist
|
|
143
|
+
"""
|
|
144
|
+
context_path = get_context_path(repo_root, workspace_name)
|
|
145
|
+
|
|
146
|
+
if context_path.exists():
|
|
147
|
+
context_path.unlink()
|
|
148
|
+
return True
|
|
149
|
+
|
|
150
|
+
return False
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def list_contexts(repo_root: Path) -> list[WorkspaceContext]:
|
|
154
|
+
"""List all workspace contexts.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
repo_root: Repository root path
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
List of all workspace contexts (empty if none exist)
|
|
161
|
+
"""
|
|
162
|
+
workspaces_dir = get_workspaces_dir(repo_root)
|
|
163
|
+
|
|
164
|
+
if not workspaces_dir.exists():
|
|
165
|
+
return []
|
|
166
|
+
|
|
167
|
+
contexts = []
|
|
168
|
+
for context_file in workspaces_dir.glob("*.json"):
|
|
169
|
+
workspace_name = context_file.stem
|
|
170
|
+
context = load_context(repo_root, workspace_name)
|
|
171
|
+
if context:
|
|
172
|
+
contexts.append(context)
|
|
173
|
+
|
|
174
|
+
return contexts
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def find_orphaned_contexts(repo_root: Path) -> list[tuple[str, WorkspaceContext]]:
|
|
178
|
+
"""Find context files for workspaces that no longer exist.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
repo_root: Repository root path
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
List of (workspace_name, context) tuples for orphaned contexts
|
|
185
|
+
"""
|
|
186
|
+
orphaned = []
|
|
187
|
+
|
|
188
|
+
for context in list_contexts(repo_root):
|
|
189
|
+
workspace_path = repo_root / context.worktree_path
|
|
190
|
+
if not workspace_path.exists():
|
|
191
|
+
workspace_name = f"{context.feature_slug}-{context.wp_id}"
|
|
192
|
+
orphaned.append((workspace_name, context))
|
|
193
|
+
|
|
194
|
+
return orphaned
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def cleanup_orphaned_contexts(repo_root: Path) -> int:
|
|
198
|
+
"""Remove context files for deleted workspaces.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
repo_root: Repository root path
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Number of orphaned contexts cleaned up
|
|
205
|
+
"""
|
|
206
|
+
orphaned = find_orphaned_contexts(repo_root)
|
|
207
|
+
|
|
208
|
+
for workspace_name, _ in orphaned:
|
|
209
|
+
delete_context(repo_root, workspace_name)
|
|
210
|
+
|
|
211
|
+
return len(orphaned)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
__all__ = [
|
|
215
|
+
"WorkspaceContext",
|
|
216
|
+
"get_workspaces_dir",
|
|
217
|
+
"get_context_path",
|
|
218
|
+
"save_context",
|
|
219
|
+
"load_context",
|
|
220
|
+
"delete_context",
|
|
221
|
+
"list_contexts",
|
|
222
|
+
"find_orphaned_contexts",
|
|
223
|
+
"cleanup_orphaned_contexts",
|
|
224
|
+
]
|