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,218 @@
|
|
|
1
|
+
"""Test path selection based on agent availability.
|
|
2
|
+
|
|
3
|
+
This module determines which test execution path to use based on the number
|
|
4
|
+
of authenticated agents available. The path affects how tests are structured:
|
|
5
|
+
|
|
6
|
+
- 1-agent: Same agent handles both implementation and review
|
|
7
|
+
- 2-agent: Different agents for implementation vs review
|
|
8
|
+
- 3+-agent: Third agent available for fallback scenarios
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import asyncio
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from typing import TYPE_CHECKING, Literal
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
# Module-level cache for test path
|
|
21
|
+
_test_path_cache: TestPath | None = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class TestPath:
|
|
26
|
+
"""Selected test path based on runtime agent availability."""
|
|
27
|
+
|
|
28
|
+
path_type: Literal["1-agent", "2-agent", "3+-agent"]
|
|
29
|
+
"""The test path variant to execute."""
|
|
30
|
+
|
|
31
|
+
available_agents: list[str]
|
|
32
|
+
"""List of authenticated agent IDs available for this run."""
|
|
33
|
+
|
|
34
|
+
implementation_agent: str
|
|
35
|
+
"""Agent to use for implementation phase."""
|
|
36
|
+
|
|
37
|
+
review_agent: str
|
|
38
|
+
"""Agent to use for review phase."""
|
|
39
|
+
|
|
40
|
+
fallback_agent: str | None
|
|
41
|
+
"""Third agent for fallback scenarios (None for 1/2-agent paths)."""
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def is_cross_agent(self) -> bool:
|
|
45
|
+
"""True if implementation and review use different agents."""
|
|
46
|
+
return self.implementation_agent != self.review_agent
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def has_fallback(self) -> bool:
|
|
50
|
+
"""True if a fallback agent is available."""
|
|
51
|
+
return self.fallback_agent is not None
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def agent_count(self) -> int:
|
|
55
|
+
"""Number of available agents."""
|
|
56
|
+
return len(self.available_agents)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def determine_path_type(agent_count: int) -> Literal["1-agent", "2-agent", "3+-agent"]:
|
|
60
|
+
"""Determine test path type based on available agent count.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
agent_count: Number of available (authenticated) agents
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Path type string indicating which test variant to run
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
ValueError: If no agents available
|
|
70
|
+
"""
|
|
71
|
+
if agent_count == 0:
|
|
72
|
+
raise ValueError("No agents available for testing")
|
|
73
|
+
elif agent_count == 1:
|
|
74
|
+
return "1-agent"
|
|
75
|
+
elif agent_count == 2:
|
|
76
|
+
return "2-agent"
|
|
77
|
+
else:
|
|
78
|
+
return "3+-agent"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def assign_agents(
|
|
82
|
+
available_agents: list[str],
|
|
83
|
+
path_type: Literal["1-agent", "2-agent", "3+-agent"],
|
|
84
|
+
) -> tuple[str, str, str | None]:
|
|
85
|
+
"""Assign agents to roles based on path type.
|
|
86
|
+
|
|
87
|
+
Agents are sorted alphabetically for deterministic assignment:
|
|
88
|
+
- First agent: implementation role
|
|
89
|
+
- Second agent: review role
|
|
90
|
+
- Third agent: fallback role (if available)
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
available_agents: List of available agent IDs
|
|
94
|
+
path_type: The test path type
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Tuple of (implementation_agent, review_agent, fallback_agent)
|
|
98
|
+
|
|
99
|
+
Raises:
|
|
100
|
+
ValueError: If no agents available
|
|
101
|
+
"""
|
|
102
|
+
if not available_agents:
|
|
103
|
+
raise ValueError("No agents available")
|
|
104
|
+
|
|
105
|
+
# Sort for deterministic assignment
|
|
106
|
+
agents = sorted(available_agents)
|
|
107
|
+
|
|
108
|
+
if path_type == "1-agent":
|
|
109
|
+
# Same agent for both roles
|
|
110
|
+
return agents[0], agents[0], None
|
|
111
|
+
|
|
112
|
+
elif path_type == "2-agent":
|
|
113
|
+
# Different agents, no fallback
|
|
114
|
+
return agents[0], agents[1], None
|
|
115
|
+
|
|
116
|
+
else: # 3+-agent
|
|
117
|
+
# Different agents with fallback
|
|
118
|
+
return agents[0], agents[1], agents[2]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def clear_test_path_cache() -> None:
|
|
122
|
+
"""Clear the cached test path.
|
|
123
|
+
|
|
124
|
+
Call this when agent availability may have changed (e.g., during
|
|
125
|
+
test setup or teardown).
|
|
126
|
+
"""
|
|
127
|
+
global _test_path_cache
|
|
128
|
+
_test_path_cache = None
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
async def select_test_path(force_path: str | None = None) -> TestPath:
|
|
132
|
+
"""Select test path based on available agents.
|
|
133
|
+
|
|
134
|
+
This is the main entry point for test path selection. It:
|
|
135
|
+
1. Detects available (authenticated) agents
|
|
136
|
+
2. Determines the appropriate path type
|
|
137
|
+
3. Assigns agents to roles
|
|
138
|
+
4. Caches the result for session duration
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
force_path: Optional path type to force (for testing).
|
|
142
|
+
Valid values: "1-agent", "2-agent", "3+-agent"
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
TestPath with agent assignments
|
|
146
|
+
|
|
147
|
+
Raises:
|
|
148
|
+
ValueError: If no agents available
|
|
149
|
+
"""
|
|
150
|
+
global _test_path_cache
|
|
151
|
+
|
|
152
|
+
if _test_path_cache is not None and force_path is None:
|
|
153
|
+
return _test_path_cache
|
|
154
|
+
|
|
155
|
+
# Import here to avoid circular imports and allow WP01 to be merged first
|
|
156
|
+
from specify_cli.orchestrator.testing.availability import (
|
|
157
|
+
detect_all_agents,
|
|
158
|
+
get_available_agents,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
# Detect agents
|
|
162
|
+
await detect_all_agents()
|
|
163
|
+
available = get_available_agents()
|
|
164
|
+
|
|
165
|
+
if not available:
|
|
166
|
+
raise ValueError(
|
|
167
|
+
"No agents available for testing. "
|
|
168
|
+
"Install and authenticate at least one agent."
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Determine path type
|
|
172
|
+
if force_path:
|
|
173
|
+
# Validate force_path
|
|
174
|
+
if force_path not in ("1-agent", "2-agent", "3+-agent"):
|
|
175
|
+
raise ValueError(
|
|
176
|
+
f"Invalid force_path: {force_path}. "
|
|
177
|
+
"Must be '1-agent', '2-agent', or '3+-agent'"
|
|
178
|
+
)
|
|
179
|
+
path_type: Literal["1-agent", "2-agent", "3+-agent"] = force_path # type: ignore[assignment]
|
|
180
|
+
else:
|
|
181
|
+
path_type = determine_path_type(len(available))
|
|
182
|
+
|
|
183
|
+
# Assign agents
|
|
184
|
+
impl_agent, review_agent, fallback = assign_agents(available, path_type)
|
|
185
|
+
|
|
186
|
+
test_path = TestPath(
|
|
187
|
+
path_type=path_type,
|
|
188
|
+
available_agents=available,
|
|
189
|
+
implementation_agent=impl_agent,
|
|
190
|
+
review_agent=review_agent,
|
|
191
|
+
fallback_agent=fallback,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
if force_path is None:
|
|
195
|
+
_test_path_cache = test_path
|
|
196
|
+
|
|
197
|
+
return test_path
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def select_test_path_sync(force_path: str | None = None) -> TestPath:
|
|
201
|
+
"""Synchronous wrapper for select_test_path.
|
|
202
|
+
|
|
203
|
+
Useful for pytest fixtures and non-async contexts.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
force_path: Optional path type to force (for testing)
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
TestPath with agent assignments
|
|
210
|
+
"""
|
|
211
|
+
try:
|
|
212
|
+
loop = asyncio.get_running_loop()
|
|
213
|
+
except RuntimeError:
|
|
214
|
+
# No running loop - create one
|
|
215
|
+
return asyncio.run(select_test_path(force_path))
|
|
216
|
+
|
|
217
|
+
# Running loop exists - use it
|
|
218
|
+
return loop.run_until_complete(select_test_path(force_path))
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Plan validation utilities for Spec Kitty CLI.
|
|
2
|
+
|
|
3
|
+
Detects whether a plan.md file has been meaningfully filled out or is still in template form.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Template markers that indicate an unfilled plan
|
|
13
|
+
TEMPLATE_MARKERS = [
|
|
14
|
+
"[FEATURE]",
|
|
15
|
+
"[###-feature-name]",
|
|
16
|
+
"[DATE]",
|
|
17
|
+
"[link]",
|
|
18
|
+
"[Extract from feature spec:",
|
|
19
|
+
"ACTION REQUIRED: Replace the content",
|
|
20
|
+
"[e.g., Python 3.11",
|
|
21
|
+
"or NEEDS CLARIFICATION",
|
|
22
|
+
"# [REMOVE IF UNUSED]",
|
|
23
|
+
"[Gates determined based on constitution file]",
|
|
24
|
+
"[Document the selected structure",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
# Minimum number of template markers that must be removed for plan to be considered filled
|
|
28
|
+
MIN_MARKERS_TO_REMOVE = 5
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class PlanValidationError(Exception):
|
|
32
|
+
"""Raised when plan.md validation fails."""
|
|
33
|
+
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def detect_unfilled_plan(plan_path: Path) -> tuple[bool, list[str]]:
|
|
38
|
+
"""Check if plan.md is still in template form.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
plan_path: Path to the plan.md file
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Tuple of (is_unfilled, list of markers found)
|
|
45
|
+
- is_unfilled: True if the plan appears to be unfilled template
|
|
46
|
+
- markers: List of template markers still present in the file
|
|
47
|
+
"""
|
|
48
|
+
if not plan_path.exists():
|
|
49
|
+
return False, []
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
content = plan_path.read_text(encoding="utf-8-sig")
|
|
53
|
+
except Exception:
|
|
54
|
+
# If we can't read it, assume it's filled (don't block progress)
|
|
55
|
+
return False, []
|
|
56
|
+
|
|
57
|
+
found_markers = []
|
|
58
|
+
for marker in TEMPLATE_MARKERS:
|
|
59
|
+
if marker in content:
|
|
60
|
+
found_markers.append(marker)
|
|
61
|
+
|
|
62
|
+
# Consider unfilled if multiple key markers are still present
|
|
63
|
+
is_unfilled = len(found_markers) >= MIN_MARKERS_TO_REMOVE
|
|
64
|
+
|
|
65
|
+
return is_unfilled, found_markers
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def validate_plan_filled(
|
|
69
|
+
plan_path: Path,
|
|
70
|
+
*,
|
|
71
|
+
feature_slug: Optional[str] = None,
|
|
72
|
+
strict: bool = True,
|
|
73
|
+
) -> None:
|
|
74
|
+
"""Validate that plan.md has been filled out.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
plan_path: Path to the plan.md file
|
|
78
|
+
feature_slug: Optional feature slug for error messages
|
|
79
|
+
strict: If True, raise error on unfilled plan. If False, just warn.
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
PlanValidationError: If plan is unfilled and strict=True
|
|
83
|
+
"""
|
|
84
|
+
is_unfilled, markers = detect_unfilled_plan(plan_path)
|
|
85
|
+
|
|
86
|
+
if not is_unfilled:
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
feature_display = f" for feature '{feature_slug}'" if feature_slug else ""
|
|
90
|
+
marker_list = "\n - ".join(markers[:5]) # Show first 5 markers
|
|
91
|
+
more_markers = f"\n ... and {len(markers) - 5} more" if len(markers) > 5 else ""
|
|
92
|
+
|
|
93
|
+
error_msg = (
|
|
94
|
+
f"plan.md{feature_display} appears to be unfilled (template form).\n"
|
|
95
|
+
f"Found {len(markers)} template markers:\n - {marker_list}{more_markers}\n\n"
|
|
96
|
+
f"Please complete the /spec-kitty.plan workflow before proceeding to research or tasks.\n"
|
|
97
|
+
f"The plan.md file must have technical details filled in, not just template placeholders."
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if strict:
|
|
101
|
+
raise PlanValidationError(error_msg)
|
|
102
|
+
else:
|
|
103
|
+
import sys
|
|
104
|
+
print(f"Warning: {error_msg}", file=sys.stderr)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
__all__ = ["PlanValidationError", "detect_unfilled_plan", "validate_plan_filled"]
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Debug script to test dashboard feature scanning."""
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
# Add src to path
|
|
8
|
+
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
|
|
9
|
+
|
|
10
|
+
from specify_cli.dashboard.scanner import scan_all_features, gather_feature_paths
|
|
11
|
+
|
|
12
|
+
def main():
|
|
13
|
+
if len(sys.argv) > 1:
|
|
14
|
+
project_dir = Path(sys.argv[1]).resolve()
|
|
15
|
+
else:
|
|
16
|
+
project_dir = Path.cwd()
|
|
17
|
+
|
|
18
|
+
print(f"Scanning project directory: {project_dir}")
|
|
19
|
+
print()
|
|
20
|
+
|
|
21
|
+
# Test gather_feature_paths
|
|
22
|
+
print("=== Feature Paths ===")
|
|
23
|
+
feature_paths = gather_feature_paths(project_dir)
|
|
24
|
+
if not feature_paths:
|
|
25
|
+
print(" No features found!")
|
|
26
|
+
print()
|
|
27
|
+
print("Checking directories:")
|
|
28
|
+
print(f" Main specs: {project_dir / 'kitty-specs'} exists: {(project_dir / 'kitty-specs').exists()}")
|
|
29
|
+
print(f" Worktrees: {project_dir / '.worktrees'} exists: {(project_dir / '.worktrees').exists()}")
|
|
30
|
+
|
|
31
|
+
if (project_dir / '.worktrees').exists():
|
|
32
|
+
for wt_dir in (project_dir / '.worktrees').iterdir():
|
|
33
|
+
if wt_dir.is_dir():
|
|
34
|
+
wt_specs = wt_dir / 'kitty-specs'
|
|
35
|
+
print(f" {wt_dir.name}/kitty-specs exists: {wt_specs.exists()}")
|
|
36
|
+
if wt_specs.exists():
|
|
37
|
+
for feat_dir in wt_specs.iterdir():
|
|
38
|
+
if feat_dir.is_dir():
|
|
39
|
+
print(f" Feature: {feat_dir.name}")
|
|
40
|
+
else:
|
|
41
|
+
for feature_id, feature_path in feature_paths.items():
|
|
42
|
+
print(f" {feature_id}: {feature_path}")
|
|
43
|
+
print()
|
|
44
|
+
|
|
45
|
+
# Test scan_all_features
|
|
46
|
+
print("=== Scanned Features ===")
|
|
47
|
+
features = scan_all_features(project_dir)
|
|
48
|
+
if not features:
|
|
49
|
+
print(" No features scanned!")
|
|
50
|
+
else:
|
|
51
|
+
for feature in features:
|
|
52
|
+
print(f" ID: {feature['id']}")
|
|
53
|
+
print(f" Name: {feature['name']}")
|
|
54
|
+
print(f" Path: {feature['path']}")
|
|
55
|
+
print(f" Artifacts: {feature['artifacts']}")
|
|
56
|
+
print(f" Workflow: {feature['workflow']}")
|
|
57
|
+
print(f" Kanban: {feature['kanban_stats']}")
|
|
58
|
+
print()
|
|
59
|
+
|
|
60
|
+
if __name__ == "__main__":
|
|
61
|
+
main()
|