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,310 @@
|
|
|
1
|
+
"""Verify setup command implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Optional
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
from rich.table import Table
|
|
12
|
+
|
|
13
|
+
from specify_cli.cli import StepTracker
|
|
14
|
+
from specify_cli.cli.helpers import check_version_compatibility, console, get_project_root_or_exit
|
|
15
|
+
from specify_cli.core.tool_checker import check_tool_for_tracker
|
|
16
|
+
from specify_cli.dashboard.diagnostics import run_diagnostics
|
|
17
|
+
from specify_cli.tasks_support import TaskCliError, find_repo_root
|
|
18
|
+
from specify_cli.verify_enhanced import run_enhanced_verify
|
|
19
|
+
|
|
20
|
+
TOOL_LABELS = [
|
|
21
|
+
("git", "Git version control"),
|
|
22
|
+
("claude", "Claude Code CLI"),
|
|
23
|
+
("gemini", "Gemini CLI"),
|
|
24
|
+
("qwen", "Qwen Code CLI"),
|
|
25
|
+
("code", "Visual Studio Code"),
|
|
26
|
+
("code-insiders", "Visual Studio Code Insiders"),
|
|
27
|
+
("cursor-agent", "Cursor IDE agent"),
|
|
28
|
+
("windsurf", "Windsurf IDE"),
|
|
29
|
+
("kilocode", "Kilo Code IDE"),
|
|
30
|
+
("opencode", "opencode"),
|
|
31
|
+
("codex", "Codex CLI"),
|
|
32
|
+
("auggie", "Auggie CLI"),
|
|
33
|
+
("q", "Amazon Q Developer CLI"),
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def verify_setup(
|
|
38
|
+
feature: Optional[str] = typer.Option(None, "--feature", help="Feature slug to verify (auto-detected when omitted)"),
|
|
39
|
+
json_output: bool = typer.Option(False, "--json", help="Output in JSON format for AI agents"),
|
|
40
|
+
check_files: bool = typer.Option(True, "--check-files", help="Check mission file integrity"),
|
|
41
|
+
check_tools: bool = typer.Option(True, "--check-tools", help="Check for installed development tools"),
|
|
42
|
+
diagnostics: bool = typer.Option(False, "--diagnostics", help="Show detailed diagnostics with dashboard health"),
|
|
43
|
+
) -> None:
|
|
44
|
+
"""Verify that the current environment matches Spec Kitty expectations."""
|
|
45
|
+
output_data: dict[str, object] = {}
|
|
46
|
+
|
|
47
|
+
# If diagnostics mode requested, use diagnostics output
|
|
48
|
+
if diagnostics:
|
|
49
|
+
_run_diagnostics_mode(json_output, check_tools)
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
# Check tools if requested
|
|
53
|
+
tool_statuses = {}
|
|
54
|
+
if check_tools:
|
|
55
|
+
if not json_output:
|
|
56
|
+
console.print("[bold]Checking for installed tools...[/bold]\n")
|
|
57
|
+
|
|
58
|
+
tracker = StepTracker("Check Available Tools")
|
|
59
|
+
for key, label in TOOL_LABELS:
|
|
60
|
+
tracker.add(key, label)
|
|
61
|
+
|
|
62
|
+
tool_statuses = {key: check_tool_for_tracker(key, tracker) for key, _ in TOOL_LABELS}
|
|
63
|
+
|
|
64
|
+
if not json_output:
|
|
65
|
+
console.print(tracker.render())
|
|
66
|
+
console.print()
|
|
67
|
+
|
|
68
|
+
if not tool_statuses.get("git", False):
|
|
69
|
+
console.print("[dim]Tip: Install git for repository management[/dim]")
|
|
70
|
+
if not any(tool_statuses[key] for key in tool_statuses if key != "git"):
|
|
71
|
+
console.print("[dim]Tip: Install an AI assistant for the best experience[/dim]")
|
|
72
|
+
console.print()
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
repo_root = find_repo_root()
|
|
76
|
+
except TaskCliError as exc:
|
|
77
|
+
if json_output:
|
|
78
|
+
output_data["error"] = str(exc)
|
|
79
|
+
if check_tools:
|
|
80
|
+
output_data["tools"] = {key: {"available": available} for key, available in tool_statuses.items()}
|
|
81
|
+
print(json.dumps(output_data))
|
|
82
|
+
else:
|
|
83
|
+
console.print(f"[red]✗[/red] Repository detection failed: {exc}")
|
|
84
|
+
console.print(
|
|
85
|
+
"\n[yellow]Solution:[/yellow] Run this command from a Spec Kitty project root or from a feature worktree inside .worktrees/<feature>/ (use 'spec-kitty init <name>' to create a project)."
|
|
86
|
+
)
|
|
87
|
+
raise typer.Exit(1)
|
|
88
|
+
|
|
89
|
+
project_root = get_project_root_or_exit(repo_root)
|
|
90
|
+
check_version_compatibility(project_root, "verify")
|
|
91
|
+
cwd = Path.cwd()
|
|
92
|
+
|
|
93
|
+
result = run_enhanced_verify(
|
|
94
|
+
repo_root=repo_root,
|
|
95
|
+
project_root=project_root,
|
|
96
|
+
cwd=cwd,
|
|
97
|
+
feature=feature,
|
|
98
|
+
json_output=json_output,
|
|
99
|
+
check_files=check_files,
|
|
100
|
+
console=console,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Add tool checking results to JSON output
|
|
104
|
+
if json_output and check_tools:
|
|
105
|
+
result["tools"] = {key: {"available": available} for key, available in tool_statuses.items()}
|
|
106
|
+
|
|
107
|
+
if json_output:
|
|
108
|
+
print(json.dumps(result, indent=2))
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _run_diagnostics_mode(json_output: bool, check_tools: bool) -> None:
|
|
115
|
+
"""Run diagnostics mode with detailed health information."""
|
|
116
|
+
try:
|
|
117
|
+
project_path = Path.cwd()
|
|
118
|
+
diag = run_diagnostics(project_path)
|
|
119
|
+
|
|
120
|
+
# Add tool checking if requested
|
|
121
|
+
if check_tools:
|
|
122
|
+
tracker = StepTracker("Check Available Tools")
|
|
123
|
+
for key, label in TOOL_LABELS:
|
|
124
|
+
tracker.add(key, label)
|
|
125
|
+
tool_statuses = {key: check_tool_for_tracker(key, tracker) for key, _ in TOOL_LABELS}
|
|
126
|
+
diag["tools"] = {key: {"available": available} for key, available in tool_statuses.items()}
|
|
127
|
+
|
|
128
|
+
if json_output:
|
|
129
|
+
# Machine-readable output for scripts and tools
|
|
130
|
+
console.print(json.dumps(diag, indent=2, default=str))
|
|
131
|
+
else:
|
|
132
|
+
# Human-readable output with Rich panels
|
|
133
|
+
_print_diagnostics(diag, check_tools)
|
|
134
|
+
|
|
135
|
+
except Exception as exc:
|
|
136
|
+
if json_output:
|
|
137
|
+
error_output = {
|
|
138
|
+
"status": "error",
|
|
139
|
+
"message": str(exc),
|
|
140
|
+
}
|
|
141
|
+
console.print(json.dumps(error_output, indent=2))
|
|
142
|
+
else:
|
|
143
|
+
console.print(f"[red]✗ Diagnostics failed:[/red] {exc}")
|
|
144
|
+
raise typer.Exit(1)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _print_diagnostics(diag: dict[str, Any], check_tools: bool) -> None:
|
|
148
|
+
"""Print diagnostics in human-readable format using Rich panels."""
|
|
149
|
+
# Tool checking first if enabled
|
|
150
|
+
if check_tools and "tools" in diag:
|
|
151
|
+
tool_statuses = {k: v["available"] for k, v in diag["tools"].items()}
|
|
152
|
+
|
|
153
|
+
console.print("[bold]Checking for installed tools...[/bold]\n")
|
|
154
|
+
tracker = StepTracker("Check Available Tools")
|
|
155
|
+
for key, label in TOOL_LABELS:
|
|
156
|
+
tracker.add(key, label)
|
|
157
|
+
if tool_statuses.get(key):
|
|
158
|
+
tracker.complete(key, "available")
|
|
159
|
+
else:
|
|
160
|
+
tracker.skip(key, "not found")
|
|
161
|
+
|
|
162
|
+
console.print(tracker.render())
|
|
163
|
+
console.print()
|
|
164
|
+
|
|
165
|
+
if not tool_statuses.get("git", False):
|
|
166
|
+
console.print("[dim]Tip: Install git for repository management[/dim]")
|
|
167
|
+
if not any(tool_statuses[key] for key in tool_statuses if key != "git"):
|
|
168
|
+
console.print("[dim]Tip: Install an AI assistant for the best experience[/dim]")
|
|
169
|
+
console.print()
|
|
170
|
+
|
|
171
|
+
# Project info panel
|
|
172
|
+
project_info = f"""
|
|
173
|
+
[bold]Project Path:[/bold] {diag['project_path']}
|
|
174
|
+
[bold]Current Directory:[/bold] {diag['current_working_directory']}
|
|
175
|
+
[bold]Git Branch:[/bold] {diag.get('git_branch') or '[yellow]Not detected[/yellow]'}
|
|
176
|
+
[bold]Active Mission:[/bold] {diag.get('active_mission') or '[yellow]None[/yellow]'}
|
|
177
|
+
"""
|
|
178
|
+
console.print(Panel(project_info.strip(), title="Project Information", border_style="cyan"))
|
|
179
|
+
|
|
180
|
+
# File integrity
|
|
181
|
+
file_integrity = diag.get("file_integrity", {})
|
|
182
|
+
total_expected = file_integrity.get("total_expected", 0)
|
|
183
|
+
total_present = file_integrity.get("total_present", 0)
|
|
184
|
+
total_missing = file_integrity.get("total_missing", 0)
|
|
185
|
+
|
|
186
|
+
if total_missing == 0:
|
|
187
|
+
integrity_status = "[green]✓ All files present[/green]"
|
|
188
|
+
else:
|
|
189
|
+
integrity_status = f"[yellow]⚠ {total_missing} files missing[/yellow]"
|
|
190
|
+
|
|
191
|
+
file_info = f"""
|
|
192
|
+
[bold]Files:[/bold] {total_present}/{total_expected} present {integrity_status}
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
if file_integrity.get("missing_files"):
|
|
196
|
+
file_info += f"\n[red]Missing:[/red]\n"
|
|
197
|
+
for missing in file_integrity.get("missing_files", [])[:5]:
|
|
198
|
+
file_info += f" • {missing}\n"
|
|
199
|
+
if len(file_integrity.get("missing_files", [])) > 5:
|
|
200
|
+
file_info += f" ... and {len(file_integrity.get('missing_files', [])) - 5} more\n"
|
|
201
|
+
|
|
202
|
+
console.print(Panel(file_info.strip(), title="File Integrity", border_style="cyan"))
|
|
203
|
+
|
|
204
|
+
# Worktree overview
|
|
205
|
+
worktree_overview = diag.get("worktree_overview", {})
|
|
206
|
+
in_worktree = diag.get("in_worktree", False)
|
|
207
|
+
worktrees_exist = diag.get("worktrees_exist", False)
|
|
208
|
+
|
|
209
|
+
worktree_info = f"""
|
|
210
|
+
[bold]Worktrees Exist:[/bold] {'[green]Yes[/green]' if worktrees_exist else '[red]No[/red]'}
|
|
211
|
+
[bold]Currently in Worktree:[/bold] {'[green]Yes[/green]' if in_worktree else '[red]No[/red]'}
|
|
212
|
+
[bold]Active Worktrees:[/bold] {worktree_overview.get('active_worktrees', 0)}
|
|
213
|
+
[bold]Total Features:[/bold] {worktree_overview.get('total_features', 0)}
|
|
214
|
+
"""
|
|
215
|
+
console.print(Panel(worktree_info.strip(), title="Worktrees", border_style="cyan"))
|
|
216
|
+
|
|
217
|
+
# Dashboard health
|
|
218
|
+
dashboard_health = diag.get("dashboard_health", {})
|
|
219
|
+
metadata_exists = dashboard_health.get("metadata_exists", False)
|
|
220
|
+
startup_test = dashboard_health.get("startup_test")
|
|
221
|
+
|
|
222
|
+
if metadata_exists:
|
|
223
|
+
responding = dashboard_health.get("responding", False)
|
|
224
|
+
dashboard_info = f"""
|
|
225
|
+
[bold]Metadata File:[/bold] {'[green]Exists[/green]' if metadata_exists else '[red]Missing[/red]'}
|
|
226
|
+
[bold]Port:[/bold] {dashboard_health.get('port', 'Unknown')}
|
|
227
|
+
[bold]Process PID:[/bold] {dashboard_health.get('pid', 'Not tracked')}
|
|
228
|
+
[bold]Responding:[/bold] {'[green]Yes[/green]' if responding else '[red]No[/red]'}
|
|
229
|
+
"""
|
|
230
|
+
if not responding:
|
|
231
|
+
dashboard_info += f"[red]⚠️ Dashboard is not responding - may need restart[/red]\n"
|
|
232
|
+
else:
|
|
233
|
+
# No dashboard - show startup test results
|
|
234
|
+
if startup_test == 'SUCCESS':
|
|
235
|
+
dashboard_info = f"""
|
|
236
|
+
[bold]Status:[/bold] [green]Can start successfully[/green]
|
|
237
|
+
[bold]Test Port:[/bold] {dashboard_health.get('test_port', 'N/A')}
|
|
238
|
+
"""
|
|
239
|
+
elif startup_test == 'FAILED':
|
|
240
|
+
dashboard_info = f"""
|
|
241
|
+
[bold]Status:[/bold] [red]Cannot start[/red]
|
|
242
|
+
[bold]Error:[/bold] {dashboard_health.get('startup_error', 'Unknown')}
|
|
243
|
+
[red]⚠️ Dashboard startup is broken for this project[/red]
|
|
244
|
+
"""
|
|
245
|
+
else:
|
|
246
|
+
dashboard_info = "[yellow]Dashboard not running (startup not tested)[/yellow]"
|
|
247
|
+
|
|
248
|
+
console.print(Panel(dashboard_info.strip(), title="Dashboard Health", border_style="cyan"))
|
|
249
|
+
|
|
250
|
+
# Current feature
|
|
251
|
+
current_feature = diag.get("current_feature", {})
|
|
252
|
+
if current_feature.get("detected"):
|
|
253
|
+
feature_info = f"""
|
|
254
|
+
[bold]Detected Feature:[/bold] {current_feature.get('name')}
|
|
255
|
+
[bold]State:[/bold] {current_feature.get('state')}
|
|
256
|
+
[bold]Branch Exists:[/bold] {'[green]Yes[/green]' if current_feature.get('branch_exists') else '[red]No[/red]'}
|
|
257
|
+
[bold]Worktree Exists:[/bold] {'[green]Yes[/green]' if current_feature.get('worktree_exists') else '[red]No[/red]'}
|
|
258
|
+
"""
|
|
259
|
+
else:
|
|
260
|
+
feature_info = "[yellow]No feature detected in current context[/yellow]"
|
|
261
|
+
|
|
262
|
+
console.print(Panel(feature_info.strip(), title="Current Feature", border_style="cyan"))
|
|
263
|
+
|
|
264
|
+
# All features table
|
|
265
|
+
all_features = diag.get("all_features", [])
|
|
266
|
+
if all_features:
|
|
267
|
+
table = Table(title="All Features", show_lines=False, header_style="bold cyan")
|
|
268
|
+
table.add_column("Feature", style="bright_cyan")
|
|
269
|
+
table.add_column("State", style="bright_white")
|
|
270
|
+
table.add_column("Branch", justify="center")
|
|
271
|
+
table.add_column("Merged", justify="center")
|
|
272
|
+
table.add_column("Worktree", justify="center")
|
|
273
|
+
|
|
274
|
+
for feature in all_features:
|
|
275
|
+
branch_emoji = "✓" if feature.get("branch_exists") else "✗"
|
|
276
|
+
merged_emoji = "✓" if feature.get("branch_merged") else "○"
|
|
277
|
+
worktree_emoji = "✓" if feature.get("worktree_exists") else "✗"
|
|
278
|
+
|
|
279
|
+
table.add_row(
|
|
280
|
+
feature.get("name", "Unknown"),
|
|
281
|
+
feature.get("state", "Unknown"),
|
|
282
|
+
branch_emoji,
|
|
283
|
+
merged_emoji,
|
|
284
|
+
worktree_emoji,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
console.print(table)
|
|
288
|
+
else:
|
|
289
|
+
console.print("[yellow]No features found[/yellow]")
|
|
290
|
+
|
|
291
|
+
# Observations and issues
|
|
292
|
+
observations = diag.get("observations", [])
|
|
293
|
+
issues = diag.get("issues", [])
|
|
294
|
+
|
|
295
|
+
if observations or issues:
|
|
296
|
+
console.print()
|
|
297
|
+
if observations:
|
|
298
|
+
console.print("[bold cyan]📝 Observations:[/bold cyan]")
|
|
299
|
+
for obs in observations:
|
|
300
|
+
console.print(f" • {obs}")
|
|
301
|
+
|
|
302
|
+
if issues:
|
|
303
|
+
console.print("[bold red]⚠️ Issues:[/bold red]")
|
|
304
|
+
for issue in issues:
|
|
305
|
+
console.print(f" • {issue}")
|
|
306
|
+
else:
|
|
307
|
+
console.print("\n[bold green]✓ No issues or observations[/bold green]")
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
__all__ = ["verify_setup"]
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Shared CLI helpers for Spec Kitty commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
from rich.align import Align
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.text import Text
|
|
13
|
+
from typer.core import TyperGroup
|
|
14
|
+
|
|
15
|
+
from specify_cli.core.config import BANNER
|
|
16
|
+
from specify_cli.core.project_resolver import locate_project_root
|
|
17
|
+
|
|
18
|
+
console = Console()
|
|
19
|
+
TAGLINE = "Spec Kitty - Spec-Driven Development Toolkit (forked from GitHub Spec Kit)"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class BannerGroup(TyperGroup):
|
|
23
|
+
"""Custom Typer group that renders the banner before help output."""
|
|
24
|
+
|
|
25
|
+
def format_help(self, ctx, formatter):
|
|
26
|
+
show_banner()
|
|
27
|
+
super().format_help(ctx, formatter)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def show_banner() -> None:
|
|
31
|
+
"""Display the ASCII art banner with gradient styling."""
|
|
32
|
+
banner_lines = BANNER.strip().split("\n")
|
|
33
|
+
colors = ["bright_blue", "blue", "cyan", "bright_cyan", "white", "bright_white"]
|
|
34
|
+
max_width = max((len(line) for line in banner_lines), default=0)
|
|
35
|
+
|
|
36
|
+
styled_banner = Text()
|
|
37
|
+
for index, line in enumerate(banner_lines):
|
|
38
|
+
color = colors[index % len(colors)]
|
|
39
|
+
padded_line = line.ljust(max_width)
|
|
40
|
+
styled_banner.append(padded_line + "\n", style=color)
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
pkg_version = version("spec-kitty-cli")
|
|
44
|
+
version_text = f"v{pkg_version}"
|
|
45
|
+
except PackageNotFoundError:
|
|
46
|
+
version_text = "dev"
|
|
47
|
+
|
|
48
|
+
console.print(Align.center(styled_banner))
|
|
49
|
+
console.print(Align.center(Text(TAGLINE, style="italic bright_yellow")))
|
|
50
|
+
console.print(Align.center(Text(version_text, style="dim cyan")))
|
|
51
|
+
console.print()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def callback(ctx: typer.Context) -> None:
|
|
55
|
+
"""Display the banner when CLI is invoked without a subcommand."""
|
|
56
|
+
if ctx.invoked_subcommand is None and "--help" not in sys.argv and "-h" not in sys.argv:
|
|
57
|
+
show_banner()
|
|
58
|
+
console.print(Align.center("[dim]Run 'spec-kitty --help' for usage information[/dim]"))
|
|
59
|
+
console.print()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_project_root_or_exit(start: Path | None = None) -> Path:
|
|
63
|
+
"""Return the project root or exit when .kittify cannot be located."""
|
|
64
|
+
project_root = locate_project_root(start)
|
|
65
|
+
if project_root is None:
|
|
66
|
+
console.print("[red]Error:[/red] Unable to locate the Spec Kitty project root (.kittify directory not found).")
|
|
67
|
+
console.print("[dim]Run this command from the project root or from a feature worktree under .worktrees/<feature>/.[/dim]")
|
|
68
|
+
console.print("[dim]Tip: Initialize a project with 'spec-kitty init <name>' if one does not exist.[/dim]")
|
|
69
|
+
raise typer.Exit(1)
|
|
70
|
+
return project_root
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def check_version_compatibility(project_root: Path, command_name: str) -> None:
|
|
74
|
+
"""Check CLI/project version compatibility and exit if mismatch.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
project_root: Path to project root (.kittify parent)
|
|
78
|
+
command_name: Name of command being run (for should_check_version)
|
|
79
|
+
|
|
80
|
+
Raises:
|
|
81
|
+
typer.Exit(1) if version mismatch detected
|
|
82
|
+
"""
|
|
83
|
+
from specify_cli.core.version_checker import (
|
|
84
|
+
get_cli_version,
|
|
85
|
+
get_project_version,
|
|
86
|
+
compare_versions,
|
|
87
|
+
format_version_error,
|
|
88
|
+
should_check_version,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Skip check for certain commands
|
|
92
|
+
if not should_check_version(command_name):
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
cli_version = get_cli_version()
|
|
96
|
+
project_version = get_project_version(project_root)
|
|
97
|
+
|
|
98
|
+
# Handle missing metadata (legacy project)
|
|
99
|
+
if project_version is None:
|
|
100
|
+
console.print("[yellow]Warning:[/yellow] Project metadata not found (.kittify/metadata.yaml)")
|
|
101
|
+
console.print("[yellow]Please run:[/yellow] spec-kitty upgrade")
|
|
102
|
+
console.print()
|
|
103
|
+
return # Warn but don't block
|
|
104
|
+
|
|
105
|
+
comparison, mismatch_type = compare_versions(cli_version, project_version)
|
|
106
|
+
|
|
107
|
+
# Handle version mismatches
|
|
108
|
+
if mismatch_type != "match":
|
|
109
|
+
if mismatch_type == "unknown":
|
|
110
|
+
console.print("[yellow]Warning:[/yellow] Unable to determine version compatibility")
|
|
111
|
+
console.print(f" CLI version: {cli_version}")
|
|
112
|
+
console.print(f" Project version: {project_version}")
|
|
113
|
+
console.print()
|
|
114
|
+
return # Warn but don't block
|
|
115
|
+
|
|
116
|
+
# Hard error for known version mismatches
|
|
117
|
+
error_msg = format_version_error(cli_version, project_version, mismatch_type)
|
|
118
|
+
console.print(error_msg)
|
|
119
|
+
console.print()
|
|
120
|
+
raise typer.Exit(1)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
__all__ = ["BannerGroup", "callback", "check_version_compatibility", "console", "get_project_root_or_exit", "show_banner"]
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Progress tracking helpers for Spec Kitty CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from rich.tree import Tree
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class StepTracker:
|
|
9
|
+
"""Track and render hierarchical steps with Rich trees."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, title: str):
|
|
12
|
+
self.title = title
|
|
13
|
+
self.steps = [] # list of dicts: {key, label, status, detail}
|
|
14
|
+
self.status_order = {"pending": 0, "running": 1, "done": 2, "error": 3, "skipped": 4}
|
|
15
|
+
self._refresh_cb = None # callable to trigger UI refresh
|
|
16
|
+
|
|
17
|
+
def attach_refresh(self, cb):
|
|
18
|
+
self._refresh_cb = cb
|
|
19
|
+
|
|
20
|
+
def add(self, key: str, label: str):
|
|
21
|
+
if key not in [s["key"] for s in self.steps]:
|
|
22
|
+
self.steps.append({"key": key, "label": label, "status": "pending", "detail": ""})
|
|
23
|
+
self._maybe_refresh()
|
|
24
|
+
|
|
25
|
+
def start(self, key: str, detail: str = ""):
|
|
26
|
+
self._update(key, status="running", detail=detail)
|
|
27
|
+
|
|
28
|
+
def complete(self, key: str, detail: str = ""):
|
|
29
|
+
self._update(key, status="done", detail=detail)
|
|
30
|
+
|
|
31
|
+
def error(self, key: str, detail: str = ""):
|
|
32
|
+
self._update(key, status="error", detail=detail)
|
|
33
|
+
|
|
34
|
+
def skip(self, key: str, detail: str = ""):
|
|
35
|
+
self._update(key, status="skipped", detail=detail)
|
|
36
|
+
|
|
37
|
+
def _update(self, key: str, status: str, detail: str):
|
|
38
|
+
for s in self.steps:
|
|
39
|
+
if s["key"] == key:
|
|
40
|
+
s["status"] = status
|
|
41
|
+
if detail:
|
|
42
|
+
s["detail"] = detail
|
|
43
|
+
self._maybe_refresh()
|
|
44
|
+
return
|
|
45
|
+
# If not present, add it
|
|
46
|
+
self.steps.append({"key": key, "label": key, "status": status, "detail": detail})
|
|
47
|
+
self._maybe_refresh()
|
|
48
|
+
|
|
49
|
+
def _maybe_refresh(self):
|
|
50
|
+
if self._refresh_cb:
|
|
51
|
+
try:
|
|
52
|
+
self._refresh_cb()
|
|
53
|
+
except Exception:
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
def render(self) -> Tree:
|
|
57
|
+
tree = Tree(f"[cyan]{self.title}[/cyan]", guide_style="grey50")
|
|
58
|
+
for step in self.steps:
|
|
59
|
+
label = step["label"]
|
|
60
|
+
detail_text = step["detail"].strip() if step["detail"] else ""
|
|
61
|
+
|
|
62
|
+
status = step["status"]
|
|
63
|
+
if status == "done":
|
|
64
|
+
symbol = "[green]●[/green]"
|
|
65
|
+
elif status == "pending":
|
|
66
|
+
symbol = "[green dim]○[/green dim]"
|
|
67
|
+
elif status == "running":
|
|
68
|
+
symbol = "[cyan]○[/cyan]"
|
|
69
|
+
elif status == "error":
|
|
70
|
+
symbol = "[red]●[/red]"
|
|
71
|
+
elif status == "skipped":
|
|
72
|
+
symbol = "[yellow]○[/yellow]"
|
|
73
|
+
else:
|
|
74
|
+
symbol = " "
|
|
75
|
+
|
|
76
|
+
if status == "pending":
|
|
77
|
+
if detail_text:
|
|
78
|
+
line = f"{symbol} [bright_black]{label} ({detail_text})[/bright_black]"
|
|
79
|
+
else:
|
|
80
|
+
line = f"{symbol} [bright_black]{label}[/bright_black]"
|
|
81
|
+
else:
|
|
82
|
+
if detail_text:
|
|
83
|
+
line = f"{symbol} [white]{label}[/white] [bright_black]({detail_text})[/bright_black]"
|
|
84
|
+
else:
|
|
85
|
+
line = f"{symbol} [white]{label}[/white]"
|
|
86
|
+
|
|
87
|
+
tree.add(line)
|
|
88
|
+
return tree
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
__all__ = ["StepTracker"]
|