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.
Files changed (242) hide show
  1. spec_kitty_cli-0.12.1.dist-info/METADATA +1767 -0
  2. spec_kitty_cli-0.12.1.dist-info/RECORD +242 -0
  3. spec_kitty_cli-0.12.1.dist-info/WHEEL +4 -0
  4. spec_kitty_cli-0.12.1.dist-info/entry_points.txt +2 -0
  5. spec_kitty_cli-0.12.1.dist-info/licenses/LICENSE +21 -0
  6. specify_cli/__init__.py +171 -0
  7. specify_cli/acceptance.py +627 -0
  8. specify_cli/agent_utils/README.md +157 -0
  9. specify_cli/agent_utils/__init__.py +9 -0
  10. specify_cli/agent_utils/status.py +356 -0
  11. specify_cli/cli/__init__.py +6 -0
  12. specify_cli/cli/commands/__init__.py +46 -0
  13. specify_cli/cli/commands/accept.py +189 -0
  14. specify_cli/cli/commands/agent/__init__.py +22 -0
  15. specify_cli/cli/commands/agent/config.py +382 -0
  16. specify_cli/cli/commands/agent/context.py +191 -0
  17. specify_cli/cli/commands/agent/feature.py +1057 -0
  18. specify_cli/cli/commands/agent/release.py +11 -0
  19. specify_cli/cli/commands/agent/tasks.py +1253 -0
  20. specify_cli/cli/commands/agent/workflow.py +801 -0
  21. specify_cli/cli/commands/context.py +246 -0
  22. specify_cli/cli/commands/dashboard.py +85 -0
  23. specify_cli/cli/commands/implement.py +973 -0
  24. specify_cli/cli/commands/init.py +827 -0
  25. specify_cli/cli/commands/init_help.py +62 -0
  26. specify_cli/cli/commands/merge.py +755 -0
  27. specify_cli/cli/commands/mission.py +240 -0
  28. specify_cli/cli/commands/ops.py +265 -0
  29. specify_cli/cli/commands/orchestrate.py +640 -0
  30. specify_cli/cli/commands/repair.py +175 -0
  31. specify_cli/cli/commands/research.py +165 -0
  32. specify_cli/cli/commands/sync.py +364 -0
  33. specify_cli/cli/commands/upgrade.py +249 -0
  34. specify_cli/cli/commands/validate_encoding.py +186 -0
  35. specify_cli/cli/commands/validate_tasks.py +186 -0
  36. specify_cli/cli/commands/verify.py +310 -0
  37. specify_cli/cli/helpers.py +123 -0
  38. specify_cli/cli/step_tracker.py +91 -0
  39. specify_cli/cli/ui.py +192 -0
  40. specify_cli/core/__init__.py +53 -0
  41. specify_cli/core/agent_context.py +311 -0
  42. specify_cli/core/config.py +96 -0
  43. specify_cli/core/context_validation.py +362 -0
  44. specify_cli/core/dependency_graph.py +351 -0
  45. specify_cli/core/git_ops.py +129 -0
  46. specify_cli/core/multi_parent_merge.py +323 -0
  47. specify_cli/core/paths.py +260 -0
  48. specify_cli/core/project_resolver.py +110 -0
  49. specify_cli/core/stale_detection.py +263 -0
  50. specify_cli/core/tool_checker.py +79 -0
  51. specify_cli/core/utils.py +43 -0
  52. specify_cli/core/vcs/__init__.py +114 -0
  53. specify_cli/core/vcs/detection.py +341 -0
  54. specify_cli/core/vcs/exceptions.py +85 -0
  55. specify_cli/core/vcs/git.py +1304 -0
  56. specify_cli/core/vcs/jujutsu.py +1208 -0
  57. specify_cli/core/vcs/protocol.py +285 -0
  58. specify_cli/core/vcs/types.py +249 -0
  59. specify_cli/core/version_checker.py +261 -0
  60. specify_cli/core/worktree.py +506 -0
  61. specify_cli/dashboard/__init__.py +28 -0
  62. specify_cli/dashboard/diagnostics.py +204 -0
  63. specify_cli/dashboard/handlers/__init__.py +17 -0
  64. specify_cli/dashboard/handlers/api.py +143 -0
  65. specify_cli/dashboard/handlers/base.py +65 -0
  66. specify_cli/dashboard/handlers/features.py +390 -0
  67. specify_cli/dashboard/handlers/router.py +81 -0
  68. specify_cli/dashboard/handlers/static.py +50 -0
  69. specify_cli/dashboard/lifecycle.py +541 -0
  70. specify_cli/dashboard/scanner.py +437 -0
  71. specify_cli/dashboard/server.py +123 -0
  72. specify_cli/dashboard/static/dashboard/dashboard.css +722 -0
  73. specify_cli/dashboard/static/dashboard/dashboard.js +1424 -0
  74. specify_cli/dashboard/static/spec-kitty.png +0 -0
  75. specify_cli/dashboard/templates/__init__.py +36 -0
  76. specify_cli/dashboard/templates/index.html +258 -0
  77. specify_cli/doc_generators.py +621 -0
  78. specify_cli/doc_state.py +408 -0
  79. specify_cli/frontmatter.py +384 -0
  80. specify_cli/gap_analysis.py +915 -0
  81. specify_cli/gitignore_manager.py +300 -0
  82. specify_cli/guards.py +145 -0
  83. specify_cli/legacy_detector.py +83 -0
  84. specify_cli/manifest.py +286 -0
  85. specify_cli/merge/__init__.py +63 -0
  86. specify_cli/merge/executor.py +653 -0
  87. specify_cli/merge/forecast.py +215 -0
  88. specify_cli/merge/ordering.py +126 -0
  89. specify_cli/merge/preflight.py +230 -0
  90. specify_cli/merge/state.py +185 -0
  91. specify_cli/merge/status_resolver.py +354 -0
  92. specify_cli/mission.py +654 -0
  93. specify_cli/missions/documentation/command-templates/implement.md +309 -0
  94. specify_cli/missions/documentation/command-templates/plan.md +275 -0
  95. specify_cli/missions/documentation/command-templates/review.md +344 -0
  96. specify_cli/missions/documentation/command-templates/specify.md +206 -0
  97. specify_cli/missions/documentation/command-templates/tasks.md +189 -0
  98. specify_cli/missions/documentation/mission.yaml +113 -0
  99. specify_cli/missions/documentation/templates/divio/explanation-template.md +192 -0
  100. specify_cli/missions/documentation/templates/divio/howto-template.md +168 -0
  101. specify_cli/missions/documentation/templates/divio/reference-template.md +179 -0
  102. specify_cli/missions/documentation/templates/divio/tutorial-template.md +146 -0
  103. specify_cli/missions/documentation/templates/generators/jsdoc.json.template +18 -0
  104. specify_cli/missions/documentation/templates/generators/sphinx-conf.py.template +36 -0
  105. specify_cli/missions/documentation/templates/plan-template.md +269 -0
  106. specify_cli/missions/documentation/templates/release-template.md +222 -0
  107. specify_cli/missions/documentation/templates/spec-template.md +172 -0
  108. specify_cli/missions/documentation/templates/task-prompt-template.md +140 -0
  109. specify_cli/missions/documentation/templates/tasks-template.md +159 -0
  110. specify_cli/missions/research/command-templates/merge.md +388 -0
  111. specify_cli/missions/research/command-templates/plan.md +125 -0
  112. specify_cli/missions/research/command-templates/review.md +144 -0
  113. specify_cli/missions/research/command-templates/tasks.md +225 -0
  114. specify_cli/missions/research/mission.yaml +115 -0
  115. specify_cli/missions/research/templates/data-model-template.md +33 -0
  116. specify_cli/missions/research/templates/plan-template.md +161 -0
  117. specify_cli/missions/research/templates/research/evidence-log.csv +18 -0
  118. specify_cli/missions/research/templates/research/source-register.csv +18 -0
  119. specify_cli/missions/research/templates/research-template.md +35 -0
  120. specify_cli/missions/research/templates/spec-template.md +64 -0
  121. specify_cli/missions/research/templates/task-prompt-template.md +148 -0
  122. specify_cli/missions/research/templates/tasks-template.md +114 -0
  123. specify_cli/missions/software-dev/command-templates/accept.md +75 -0
  124. specify_cli/missions/software-dev/command-templates/analyze.md +183 -0
  125. specify_cli/missions/software-dev/command-templates/checklist.md +286 -0
  126. specify_cli/missions/software-dev/command-templates/clarify.md +157 -0
  127. specify_cli/missions/software-dev/command-templates/constitution.md +432 -0
  128. specify_cli/missions/software-dev/command-templates/dashboard.md +101 -0
  129. specify_cli/missions/software-dev/command-templates/implement.md +41 -0
  130. specify_cli/missions/software-dev/command-templates/merge.md +383 -0
  131. specify_cli/missions/software-dev/command-templates/plan.md +171 -0
  132. specify_cli/missions/software-dev/command-templates/review.md +32 -0
  133. specify_cli/missions/software-dev/command-templates/specify.md +321 -0
  134. specify_cli/missions/software-dev/command-templates/tasks.md +566 -0
  135. specify_cli/missions/software-dev/mission.yaml +100 -0
  136. specify_cli/missions/software-dev/templates/plan-template.md +132 -0
  137. specify_cli/missions/software-dev/templates/spec-template.md +116 -0
  138. specify_cli/missions/software-dev/templates/task-prompt-template.md +140 -0
  139. specify_cli/missions/software-dev/templates/tasks-template.md +159 -0
  140. specify_cli/orchestrator/__init__.py +75 -0
  141. specify_cli/orchestrator/agent_config.py +224 -0
  142. specify_cli/orchestrator/agents/__init__.py +170 -0
  143. specify_cli/orchestrator/agents/augment.py +112 -0
  144. specify_cli/orchestrator/agents/base.py +243 -0
  145. specify_cli/orchestrator/agents/claude.py +112 -0
  146. specify_cli/orchestrator/agents/codex.py +106 -0
  147. specify_cli/orchestrator/agents/copilot.py +137 -0
  148. specify_cli/orchestrator/agents/cursor.py +139 -0
  149. specify_cli/orchestrator/agents/gemini.py +115 -0
  150. specify_cli/orchestrator/agents/kilocode.py +94 -0
  151. specify_cli/orchestrator/agents/opencode.py +132 -0
  152. specify_cli/orchestrator/agents/qwen.py +96 -0
  153. specify_cli/orchestrator/config.py +455 -0
  154. specify_cli/orchestrator/executor.py +642 -0
  155. specify_cli/orchestrator/integration.py +1230 -0
  156. specify_cli/orchestrator/monitor.py +898 -0
  157. specify_cli/orchestrator/scheduler.py +832 -0
  158. specify_cli/orchestrator/state.py +508 -0
  159. specify_cli/orchestrator/testing/__init__.py +122 -0
  160. specify_cli/orchestrator/testing/availability.py +346 -0
  161. specify_cli/orchestrator/testing/fixtures.py +684 -0
  162. specify_cli/orchestrator/testing/paths.py +218 -0
  163. specify_cli/plan_validation.py +107 -0
  164. specify_cli/scripts/debug-dashboard-scan.py +61 -0
  165. specify_cli/scripts/tasks/acceptance_support.py +695 -0
  166. specify_cli/scripts/tasks/task_helpers.py +506 -0
  167. specify_cli/scripts/tasks/tasks_cli.py +848 -0
  168. specify_cli/scripts/validate_encoding.py +180 -0
  169. specify_cli/task_metadata_validation.py +274 -0
  170. specify_cli/tasks_support.py +447 -0
  171. specify_cli/template/__init__.py +47 -0
  172. specify_cli/template/asset_generator.py +206 -0
  173. specify_cli/template/github_client.py +334 -0
  174. specify_cli/template/manager.py +193 -0
  175. specify_cli/template/renderer.py +99 -0
  176. specify_cli/templates/AGENTS.md +190 -0
  177. specify_cli/templates/POWERSHELL_SYNTAX.md +229 -0
  178. specify_cli/templates/agent-file-template.md +35 -0
  179. specify_cli/templates/checklist-template.md +42 -0
  180. specify_cli/templates/claudeignore-template +58 -0
  181. specify_cli/templates/command-templates/accept.md +141 -0
  182. specify_cli/templates/command-templates/analyze.md +253 -0
  183. specify_cli/templates/command-templates/checklist.md +352 -0
  184. specify_cli/templates/command-templates/clarify.md +224 -0
  185. specify_cli/templates/command-templates/constitution.md +432 -0
  186. specify_cli/templates/command-templates/dashboard.md +175 -0
  187. specify_cli/templates/command-templates/implement.md +190 -0
  188. specify_cli/templates/command-templates/merge.md +374 -0
  189. specify_cli/templates/command-templates/plan.md +171 -0
  190. specify_cli/templates/command-templates/research.md +88 -0
  191. specify_cli/templates/command-templates/review.md +510 -0
  192. specify_cli/templates/command-templates/specify.md +321 -0
  193. specify_cli/templates/command-templates/status.md +92 -0
  194. specify_cli/templates/command-templates/tasks.md +199 -0
  195. specify_cli/templates/git-hooks/pre-commit +22 -0
  196. specify_cli/templates/git-hooks/pre-commit-agent-check +37 -0
  197. specify_cli/templates/git-hooks/pre-commit-encoding-check +142 -0
  198. specify_cli/templates/plan-template.md +108 -0
  199. specify_cli/templates/spec-template.md +118 -0
  200. specify_cli/templates/task-prompt-template.md +165 -0
  201. specify_cli/templates/tasks-template.md +161 -0
  202. specify_cli/templates/vscode-settings.json +13 -0
  203. specify_cli/text_sanitization.py +225 -0
  204. specify_cli/upgrade/__init__.py +18 -0
  205. specify_cli/upgrade/detector.py +239 -0
  206. specify_cli/upgrade/metadata.py +182 -0
  207. specify_cli/upgrade/migrations/__init__.py +65 -0
  208. specify_cli/upgrade/migrations/base.py +80 -0
  209. specify_cli/upgrade/migrations/m_0_10_0_python_only.py +359 -0
  210. specify_cli/upgrade/migrations/m_0_10_12_constitution_cleanup.py +99 -0
  211. specify_cli/upgrade/migrations/m_0_10_14_update_implement_slash_command.py +176 -0
  212. specify_cli/upgrade/migrations/m_0_10_1_populate_slash_commands.py +174 -0
  213. specify_cli/upgrade/migrations/m_0_10_2_update_slash_commands.py +172 -0
  214. specify_cli/upgrade/migrations/m_0_10_6_workflow_simplification.py +174 -0
  215. specify_cli/upgrade/migrations/m_0_10_8_fix_memory_structure.py +252 -0
  216. specify_cli/upgrade/migrations/m_0_10_9_repair_templates.py +168 -0
  217. specify_cli/upgrade/migrations/m_0_11_0_workspace_per_wp.py +182 -0
  218. specify_cli/upgrade/migrations/m_0_11_1_improved_workflow_templates.py +173 -0
  219. specify_cli/upgrade/migrations/m_0_11_1_update_implement_slash_command.py +160 -0
  220. specify_cli/upgrade/migrations/m_0_11_2_improved_workflow_templates.py +173 -0
  221. specify_cli/upgrade/migrations/m_0_11_3_workflow_agent_flag.py +114 -0
  222. specify_cli/upgrade/migrations/m_0_12_0_documentation_mission.py +155 -0
  223. specify_cli/upgrade/migrations/m_0_12_1_remove_kitty_specs_from_gitignore.py +183 -0
  224. specify_cli/upgrade/migrations/m_0_2_0_specify_to_kittify.py +80 -0
  225. specify_cli/upgrade/migrations/m_0_4_8_gitignore_agents.py +118 -0
  226. specify_cli/upgrade/migrations/m_0_5_0_encoding_hooks.py +141 -0
  227. specify_cli/upgrade/migrations/m_0_6_5_commands_rename.py +169 -0
  228. specify_cli/upgrade/migrations/m_0_6_7_ensure_missions.py +228 -0
  229. specify_cli/upgrade/migrations/m_0_7_2_worktree_commands_dedup.py +89 -0
  230. specify_cli/upgrade/migrations/m_0_7_3_update_scripts.py +114 -0
  231. specify_cli/upgrade/migrations/m_0_8_0_remove_active_mission.py +82 -0
  232. specify_cli/upgrade/migrations/m_0_8_0_worktree_agents_symlink.py +148 -0
  233. specify_cli/upgrade/migrations/m_0_9_0_frontmatter_only_lanes.py +346 -0
  234. specify_cli/upgrade/migrations/m_0_9_1_complete_lane_migration.py +656 -0
  235. specify_cli/upgrade/migrations/m_0_9_2_research_mission_templates.py +221 -0
  236. specify_cli/upgrade/registry.py +121 -0
  237. specify_cli/upgrade/runner.py +284 -0
  238. specify_cli/validators/__init__.py +14 -0
  239. specify_cli/validators/paths.py +154 -0
  240. specify_cli/validators/research.py +428 -0
  241. specify_cli/verify_enhanced.py +270 -0
  242. specify_cli/workspace_context.py +224 -0
@@ -0,0 +1,506 @@
1
+ """Worktree management utilities for spec-kitty feature development.
2
+
3
+ This module provides functions for creating and managing workspaces (git worktrees
4
+ or jj workspaces) for parallel feature development. Uses the VCS abstraction layer
5
+ to support both git and jujutsu backends.
6
+
7
+ All functions are location-aware and work correctly whether called from main
8
+ repository or existing worktree/workspace.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import platform
14
+ import shutil
15
+ import subprocess
16
+ import warnings
17
+ from pathlib import Path
18
+ from typing import Optional, Tuple
19
+
20
+ from .paths import locate_project_root
21
+ from .vcs import VCSBackend, get_vcs
22
+
23
+
24
+ def _exclude_from_git(worktree_path: Path, patterns: list[str]) -> None:
25
+ """Add patterns to worktree's .git/info/exclude to prevent committing.
26
+
27
+ This prevents symlinks created in worktrees from being committed and
28
+ overwriting real files in main on merge (fixes issue #79).
29
+
30
+ Args:
31
+ worktree_path: Path to the worktree root
32
+ patterns: List of patterns to exclude (e.g., [".kittify/memory"])
33
+ """
34
+ # In a worktree, .git is a file pointing to the real git dir
35
+ git_path = worktree_path / ".git"
36
+ if not git_path.exists():
37
+ return
38
+
39
+ # Find the actual git directory
40
+ if git_path.is_file():
41
+ # Worktree: .git file contains "gitdir: /path/to/real/.git/worktrees/name"
42
+ try:
43
+ content = git_path.read_text().strip()
44
+ if content.startswith("gitdir:"):
45
+ git_dir = Path(content[7:].strip())
46
+ exclude_file = git_dir / "info" / "exclude"
47
+ else:
48
+ return
49
+ except (OSError, ValueError):
50
+ return
51
+ else:
52
+ # Regular repo or already resolved
53
+ exclude_file = git_path / "info" / "exclude"
54
+
55
+ # Ensure info directory exists
56
+ exclude_file.parent.mkdir(parents=True, exist_ok=True)
57
+
58
+ # Read existing exclusions
59
+ existing = set()
60
+ if exclude_file.exists():
61
+ try:
62
+ existing = set(exclude_file.read_text().splitlines())
63
+ except OSError:
64
+ pass
65
+
66
+ # Add new patterns if not already present
67
+ new_patterns = [p for p in patterns if p not in existing]
68
+ if new_patterns:
69
+ try:
70
+ with exclude_file.open("a") as f:
71
+ # Add comment if this is our first addition
72
+ marker = "# Added by spec-kitty (worktree symlinks)"
73
+ if marker not in existing:
74
+ f.write(f"\n{marker}\n")
75
+ for pattern in new_patterns:
76
+ f.write(f"{pattern}\n")
77
+ except OSError:
78
+ # If we can't write, just skip - not critical
79
+ pass
80
+
81
+
82
+ def get_next_feature_number(repo_root: Path) -> int:
83
+ """Determine next sequential feature number.
84
+
85
+ Scans both kitty-specs/ and .worktrees/ directories for existing features
86
+ (###-name format) and returns next number in sequence. This prevents number
87
+ reuse when features exist only in worktrees.
88
+
89
+ Args:
90
+ repo_root: Repository root path
91
+
92
+ Returns:
93
+ Next feature number (e.g., 9 if highest existing is 008)
94
+
95
+ Examples:
96
+ >>> repo_root = Path("/path/to/repo")
97
+ >>> next_num = get_next_feature_number(repo_root)
98
+ >>> assert next_num > 0
99
+ """
100
+ max_number = 0
101
+
102
+ # Scan kitty-specs/ for feature numbers
103
+ specs_dir = repo_root / "kitty-specs"
104
+ if specs_dir.exists():
105
+ for item in specs_dir.iterdir():
106
+ if item.is_dir() and len(item.name) >= 3 and item.name[:3].isdigit():
107
+ try:
108
+ number = int(item.name[:3])
109
+ max_number = max(max_number, number)
110
+ except ValueError:
111
+ # Not a valid number, skip
112
+ continue
113
+
114
+ # Also scan .worktrees/ for feature numbers
115
+ worktrees_dir = repo_root / ".worktrees"
116
+ if worktrees_dir.exists():
117
+ for item in worktrees_dir.iterdir():
118
+ if item.is_dir() and len(item.name) >= 3 and item.name[:3].isdigit():
119
+ try:
120
+ number = int(item.name[:3])
121
+ max_number = max(max_number, number)
122
+ except ValueError:
123
+ # Not a valid number, skip
124
+ continue
125
+
126
+ return max_number + 1
127
+
128
+
129
+ def create_feature_worktree(
130
+ repo_root: Path,
131
+ feature_slug: str,
132
+ feature_number: Optional[int] = None
133
+ ) -> Tuple[Path, Path]:
134
+ """Create workspace (git worktree or jj workspace) for feature development.
135
+
136
+ Creates a new workspace with a feature branch and sets up the
137
+ feature directory structure. Uses VCS abstraction to support both
138
+ git and jujutsu backends.
139
+
140
+ Args:
141
+ repo_root: Repository root path
142
+ feature_slug: Feature identifier (e.g., "test-feature")
143
+ feature_number: Optional feature number (auto-detected if None)
144
+
145
+ Returns:
146
+ Tuple of (worktree_path, feature_dir)
147
+
148
+ Raises:
149
+ RuntimeError: If workspace creation fails
150
+ FileExistsError: If worktree path already exists
151
+
152
+ Examples:
153
+ >>> repo_root = Path("/path/to/repo")
154
+ >>> worktree, feature_dir = create_feature_worktree(repo_root, "new-feature")
155
+ >>> assert worktree.exists()
156
+ >>> assert feature_dir.exists()
157
+ """
158
+ # Auto-detect feature number if not provided
159
+ if feature_number is None:
160
+ feature_number = get_next_feature_number(repo_root)
161
+
162
+ # Format: 001-test-feature
163
+ branch_name = f"{feature_number:03d}-{feature_slug}"
164
+
165
+ # Create worktree at .worktrees/001-test-feature
166
+ worktree_path = repo_root / ".worktrees" / branch_name
167
+
168
+ # Ensure .worktrees directory exists
169
+ worktree_path.parent.mkdir(parents=True, exist_ok=True)
170
+
171
+ # Check if worktree already exists
172
+ if worktree_path.exists():
173
+ # Check if it's a valid workspace using VCS abstraction
174
+ is_valid_workspace = False
175
+ try:
176
+ vcs = get_vcs(worktree_path)
177
+ is_valid_workspace = vcs.is_repo(worktree_path)
178
+ except Exception:
179
+ pass
180
+
181
+ # If VCS says no (or failed), fall back to simple git check
182
+ # A valid git worktree has .git as a file (pointing to main repo)
183
+ # or as a directory (standalone repo)
184
+ if not is_valid_workspace:
185
+ git_marker = worktree_path / ".git"
186
+ is_valid_workspace = git_marker.exists()
187
+
188
+ if is_valid_workspace:
189
+ feature_dir = worktree_path / "kitty-specs" / branch_name
190
+ return (worktree_path, feature_dir)
191
+
192
+ raise FileExistsError(f"Worktree path already exists: {worktree_path}")
193
+
194
+ # Get VCS implementation and create workspace
195
+ # NOTE: We do NOT use sparse_exclude for kitty-specs/ here because:
196
+ # - Users need to add research artifacts, patterns, etc. to kitty-specs/
197
+ # - The locate_work_package() function always uses main repo's kitty-specs/
198
+ # for WP operations, so stale copies in worktrees don't affect lane changes
199
+ try:
200
+ vcs = get_vcs(repo_root)
201
+ result = vcs.create_workspace(
202
+ workspace_path=worktree_path,
203
+ workspace_name=branch_name,
204
+ repo_root=repo_root,
205
+ )
206
+
207
+ if not result.success:
208
+ raise RuntimeError(f"Failed to create workspace: {result.error}")
209
+
210
+ except Exception as e:
211
+ # If VCS abstraction fails, fall back to direct git command with warning
212
+ warnings.warn(
213
+ "VCS abstraction failed, falling back to direct git commands. "
214
+ "See: kitty-specs/015-first-class-jujutsu-vcs-integration/",
215
+ DeprecationWarning,
216
+ stacklevel=2,
217
+ )
218
+ try:
219
+ subprocess.run(
220
+ ["git", "worktree", "add", str(worktree_path), "-b", branch_name],
221
+ cwd=repo_root,
222
+ check=True,
223
+ capture_output=True,
224
+ text=True
225
+ )
226
+ except subprocess.CalledProcessError as git_error:
227
+ raise RuntimeError(
228
+ f"Failed to create workspace: {git_error.stderr}"
229
+ ) from git_error
230
+
231
+ # Create feature directory structure
232
+ feature_dir = worktree_path / "kitty-specs" / branch_name
233
+ feature_dir.mkdir(parents=True, exist_ok=True)
234
+
235
+ # Setup feature directory (symlinks, subdirectories, etc.)
236
+ setup_feature_directory(feature_dir, worktree_path, repo_root)
237
+
238
+ return (worktree_path, feature_dir)
239
+
240
+
241
+ def setup_feature_directory(
242
+ feature_dir: Path,
243
+ worktree_path: Path,
244
+ repo_root: Path,
245
+ create_symlinks: bool = True
246
+ ) -> None:
247
+ """Setup standard feature directory structure.
248
+
249
+ Creates:
250
+ - kitty-specs/###-name/ directory
251
+ - Subdirectories: checklists/, research/, tasks/
252
+ - Symlinks to .kittify/memory/ (or file copies on Windows)
253
+ - spec.md from template
254
+ - tasks/README.md
255
+
256
+ Args:
257
+ feature_dir: Feature directory path
258
+ worktree_path: Worktree root path
259
+ repo_root: Main repository root path
260
+ create_symlinks: If True, create symlinks; else copy files (Windows)
261
+
262
+ Examples:
263
+ >>> feature_dir = Path("/path/to/.worktrees/001-feature/kitty-specs/001-feature")
264
+ >>> setup_feature_directory(feature_dir, feature_dir.parent.parent, repo_root)
265
+ >>> assert (feature_dir / "checklists").exists()
266
+ """
267
+ # Ensure feature directory exists
268
+ feature_dir.mkdir(parents=True, exist_ok=True)
269
+
270
+ # Create subdirectories
271
+ (feature_dir / "checklists").mkdir(exist_ok=True)
272
+ (feature_dir / "research").mkdir(exist_ok=True)
273
+ tasks_dir = feature_dir / "tasks"
274
+ tasks_dir.mkdir(exist_ok=True)
275
+
276
+ # Create tasks/.gitkeep and README.md
277
+ (tasks_dir / ".gitkeep").touch()
278
+
279
+ # Create tasks/README.md with frontmatter format reference
280
+ tasks_readme_content = '''# Tasks Directory
281
+
282
+ This directory contains work package (WP) prompt files with lane status in frontmatter.
283
+
284
+ ## Directory Structure (v0.9.0+)
285
+
286
+ ```
287
+ tasks/
288
+ ├── WP01-setup-infrastructure.md
289
+ ├── WP02-user-authentication.md
290
+ ├── WP03-api-endpoints.md
291
+ └── README.md
292
+ ```
293
+
294
+ All WP files are stored flat in `tasks/`. The lane (planned, doing, for_review, done) is stored in the YAML frontmatter `lane:` field.
295
+
296
+ ## Work Package File Format
297
+
298
+ Each WP file **MUST** use YAML frontmatter:
299
+
300
+ ```yaml
301
+ ---
302
+ work_package_id: "WP01"
303
+ title: "Work Package Title"
304
+ lane: "planned"
305
+ subtasks:
306
+ - "T001"
307
+ - "T002"
308
+ phase: "Phase 1 - Setup"
309
+ assignee: ""
310
+ agent: ""
311
+ shell_pid: ""
312
+ review_status: ""
313
+ reviewed_by: ""
314
+ history:
315
+ - timestamp: "2025-01-01T00:00:00Z"
316
+ lane: "planned"
317
+ agent: "system"
318
+ action: "Prompt generated via /spec-kitty.tasks"
319
+ ---
320
+
321
+ # Work Package Prompt: WP01 – Work Package Title
322
+
323
+ [Content follows...]
324
+ ```
325
+
326
+ ## Valid Lane Values
327
+
328
+ - `planned` - Ready for implementation
329
+ - `doing` - Currently being worked on
330
+ - `for_review` - Awaiting review
331
+ - `done` - Completed
332
+
333
+ ## Moving Between Lanes
334
+
335
+ Use the CLI (updates frontmatter only, no file movement):
336
+ ```bash
337
+ spec-kitty agent tasks move-task <WPID> --to <lane>
338
+ ```
339
+
340
+ Example:
341
+ ```bash
342
+ spec-kitty agent tasks move-task WP01 --to doing
343
+ ```
344
+
345
+ ## File Naming
346
+
347
+ - Format: `WP01-kebab-case-slug.md`
348
+ - Examples: `WP01-setup-infrastructure.md`, `WP02-user-auth.md`
349
+ '''
350
+ (tasks_dir / "README.md").write_text(tasks_readme_content)
351
+
352
+ # Create worktree .kittify directory if it doesn't exist
353
+ worktree_kittify = worktree_path / ".kittify"
354
+ worktree_kittify.mkdir(exist_ok=True)
355
+
356
+ # Setup shared constitution and AGENTS.md via symlink (or copy on Windows)
357
+ # Calculate relative path from worktree to main repo
358
+ # Worktree: .worktrees/001-feature/.kittify/memory
359
+ # Main: .kittify/memory
360
+ # Relative: ../../../.kittify/memory
361
+ relative_memory_path = Path("../../../.kittify/memory")
362
+ relative_agents_path = Path("../../../.kittify/AGENTS.md")
363
+
364
+ worktree_memory = worktree_kittify / "memory"
365
+ worktree_agents = worktree_kittify / "AGENTS.md"
366
+
367
+ # Detect if we're on Windows or symlinks are not supported
368
+ is_windows = platform.system() == "Windows"
369
+ use_copy = is_windows or not create_symlinks
370
+
371
+ # Setup memory/ symlink or copy
372
+ if worktree_memory.is_symlink():
373
+ # Remove existing symlink first (can't use rmtree on symlinks)
374
+ worktree_memory.unlink()
375
+ elif worktree_memory.exists() and worktree_memory.is_dir():
376
+ # Remove existing directory (from git worktree add)
377
+ shutil.rmtree(worktree_memory)
378
+
379
+ if use_copy:
380
+ # Copy memory directory
381
+ main_memory = repo_root / ".kittify" / "memory"
382
+ if main_memory.exists() and main_memory.is_dir():
383
+ shutil.copytree(main_memory, worktree_memory)
384
+ else:
385
+ # Create relative symlink
386
+ try:
387
+ worktree_memory.symlink_to(relative_memory_path, target_is_directory=True)
388
+ except (OSError, NotImplementedError):
389
+ # Symlink failed, fall back to copy
390
+ main_memory = repo_root / ".kittify" / "memory"
391
+ if main_memory.exists() and main_memory.is_dir():
392
+ shutil.copytree(main_memory, worktree_memory)
393
+
394
+ # Setup AGENTS.md symlink or copy
395
+ if worktree_agents.exists():
396
+ worktree_agents.unlink()
397
+
398
+ main_agents = repo_root / ".kittify" / "AGENTS.md"
399
+ if main_agents.exists():
400
+ if use_copy:
401
+ shutil.copy2(main_agents, worktree_agents)
402
+ else:
403
+ try:
404
+ worktree_agents.symlink_to(relative_agents_path)
405
+ except (OSError, NotImplementedError):
406
+ shutil.copy2(main_agents, worktree_agents)
407
+
408
+ # Exclude symlinks from git to prevent them from being committed
409
+ # This fixes issue #79: symlinks overwriting main repo files on merge
410
+ _exclude_from_git(worktree_path, [".kittify/memory", ".kittify/AGENTS.md"])
411
+
412
+ # Copy spec template if it exists
413
+ spec_file = feature_dir / "spec.md"
414
+ if not spec_file.exists():
415
+ # Try to find spec template
416
+ spec_template_candidates = [
417
+ repo_root / ".kittify" / "templates" / "spec-template.md",
418
+ repo_root / "templates" / "spec-template.md",
419
+ ]
420
+
421
+ for template in spec_template_candidates:
422
+ if template.exists():
423
+ shutil.copy2(template, spec_file)
424
+ break
425
+ else:
426
+ # No template found, create empty spec.md
427
+ spec_file.touch()
428
+
429
+
430
+ def validate_feature_structure(
431
+ feature_dir: Path,
432
+ check_tasks: bool = False
433
+ ) -> dict:
434
+ """Validate feature directory structure and required files.
435
+
436
+ Checks for:
437
+ - Required files: spec.md
438
+ - Recommended directories: checklists/, research/, tasks/
439
+ - Optional: tasks.md (if check_tasks=True)
440
+
441
+ Args:
442
+ feature_dir: Feature directory path
443
+ check_tasks: If True, validate tasks.md and task files exist
444
+
445
+ Returns:
446
+ Dictionary with validation results:
447
+ {
448
+ "valid": bool,
449
+ "errors": [list of error messages],
450
+ "warnings": [list of warning messages],
451
+ "paths": {dict of important paths}
452
+ }
453
+
454
+ Examples:
455
+ >>> feature_dir = Path("/path/to/kitty-specs/001-feature")
456
+ >>> result = validate_feature_structure(feature_dir)
457
+ >>> assert "valid" in result
458
+ >>> assert "errors" in result
459
+ """
460
+ errors = []
461
+ warnings = []
462
+ paths = {}
463
+
464
+ # Check if feature directory exists
465
+ if not feature_dir.exists():
466
+ errors.append(f"Feature directory not found: {feature_dir}")
467
+ return {
468
+ "valid": False,
469
+ "errors": errors,
470
+ "warnings": warnings,
471
+ "paths": paths
472
+ }
473
+
474
+ # Check required files exist
475
+ spec_file = feature_dir / "spec.md"
476
+ if not spec_file.exists():
477
+ errors.append("Missing required file: spec.md")
478
+ else:
479
+ paths["spec_file"] = str(spec_file)
480
+
481
+ # Check directory structure
482
+ recommended_dirs = ["checklists", "research", "tasks"]
483
+ for dir_name in recommended_dirs:
484
+ dir_path = feature_dir / dir_name
485
+ if not dir_path.exists():
486
+ warnings.append(f"Missing recommended directory: {dir_name}/")
487
+ else:
488
+ paths[f"{dir_name}_dir"] = str(dir_path)
489
+
490
+ # Check task files if requested
491
+ if check_tasks:
492
+ tasks_file = feature_dir / "tasks.md"
493
+ if not tasks_file.exists():
494
+ errors.append("Missing required file: tasks.md")
495
+ else:
496
+ paths["tasks_file"] = str(tasks_file)
497
+
498
+ # Always include feature_dir in paths
499
+ paths["feature_dir"] = str(feature_dir)
500
+
501
+ return {
502
+ "valid": len(errors) == 0,
503
+ "errors": errors,
504
+ "warnings": warnings,
505
+ "paths": paths
506
+ }
@@ -0,0 +1,28 @@
1
+ """Dashboard package public API."""
2
+
3
+ from .diagnostics import run_diagnostics
4
+ from .lifecycle import ensure_dashboard_running, stop_dashboard
5
+ from .scanner import (
6
+ format_path_for_display,
7
+ get_feature_artifacts,
8
+ get_workflow_status,
9
+ resolve_feature_dir,
10
+ scan_all_features,
11
+ scan_feature_kanban,
12
+ )
13
+ from .server import find_free_port, run_dashboard_server, start_dashboard
14
+
15
+ __all__ = [
16
+ "ensure_dashboard_running",
17
+ "stop_dashboard",
18
+ "find_free_port",
19
+ "start_dashboard",
20
+ "run_dashboard_server",
21
+ "scan_all_features",
22
+ "scan_feature_kanban",
23
+ "get_feature_artifacts",
24
+ "get_workflow_status",
25
+ "resolve_feature_dir",
26
+ "format_path_for_display",
27
+ "run_diagnostics",
28
+ ]