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,827 @@
1
+ """Init command implementation for Spec Kitty CLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import shutil
7
+ import sys
8
+ from pathlib import Path
9
+ from typing import Callable
10
+
11
+ import httpx
12
+ import typer
13
+ from rich.console import Console
14
+ from rich.live import Live
15
+ from rich.panel import Panel
16
+ from ruamel.yaml import YAML
17
+
18
+ from specify_cli.cli import StepTracker, select_with_arrows, multi_select_with_arrows
19
+ from specify_cli.core import (
20
+ AGENT_TOOL_REQUIREMENTS,
21
+ AI_CHOICES,
22
+ DEFAULT_MISSION_KEY,
23
+ DEFAULT_TEMPLATE_REPO,
24
+ MISSION_CHOICES,
25
+ SCRIPT_TYPE_CHOICES,
26
+ check_tool,
27
+ init_git_repo,
28
+ is_git_repo,
29
+ )
30
+ from specify_cli.core.vcs import (
31
+ is_git_available,
32
+ VCSBackend,
33
+ )
34
+ from specify_cli.dashboard import ensure_dashboard_running
35
+ from specify_cli.gitignore_manager import GitignoreManager, ProtectionResult
36
+ from specify_cli.orchestrator.agent_config import (
37
+ AgentConfig,
38
+ AgentSelectionConfig,
39
+ SelectionStrategy,
40
+ save_agent_config,
41
+ )
42
+ from .init_help import INIT_COMMAND_DOC
43
+ from specify_cli.template import (
44
+ GitHubClientError,
45
+ SSL_CONTEXT,
46
+ build_http_client,
47
+ copy_specify_base_from_local,
48
+ copy_specify_base_from_package,
49
+ download_and_extract_template,
50
+ generate_agent_assets,
51
+ get_local_repo_root,
52
+ parse_repo_slug,
53
+ prepare_command_templates,
54
+ )
55
+
56
+ # Module-level variables to hold injected dependencies
57
+ _console: Console | None = None
58
+ _show_banner: Callable[[], None] | None = None
59
+ _activate_mission: Callable[[Path, str, str, Console], str] | None = None
60
+ _ensure_executable_scripts: Callable[[Path, StepTracker | None], None] | None = None
61
+
62
+
63
+ # =============================================================================
64
+ # VCS Detection and Configuration
65
+ # =============================================================================
66
+
67
+
68
+ class VCSNotFoundError(Exception):
69
+ """Raised when no VCS tools are available."""
70
+
71
+ pass
72
+
73
+
74
+ def _detect_default_vcs() -> VCSBackend:
75
+ """Detect the default VCS based on tool availability.
76
+
77
+ Returns VCSBackend.GIT if git is available.
78
+ Raises VCSNotFoundError if git is not available.
79
+
80
+ Note: jj support removed due to sparse checkout incompatibility.
81
+ """
82
+ if is_git_available():
83
+ return VCSBackend.GIT
84
+ else:
85
+ raise VCSNotFoundError("git is not available. Please install git.")
86
+
87
+
88
+ def _display_vcs_info(detected_vcs: VCSBackend, console: Console) -> None:
89
+ """Display informational message about VCS selection.
90
+
91
+ Args:
92
+ detected_vcs: The detected/selected VCS backend (always GIT)
93
+ console: Rich console for output
94
+ """
95
+ console.print("[green]✓ git detected[/green] - will be used for version control")
96
+
97
+
98
+ def _save_vcs_config(config_path: Path, detected_vcs: VCSBackend) -> None:
99
+ """Save VCS preference to config.yaml.
100
+
101
+ Args:
102
+ config_path: Path to .kittify directory
103
+ detected_vcs: The detected/selected VCS backend (always GIT)
104
+ """
105
+ config_file = config_path / "config.yaml"
106
+
107
+ yaml = YAML()
108
+ yaml.preserve_quotes = True
109
+
110
+ # Load existing config or create new
111
+ if config_file.exists():
112
+ with open(config_file, "r") as f:
113
+ config = yaml.load(f) or {}
114
+ else:
115
+ config = {}
116
+ config_path.mkdir(parents=True, exist_ok=True)
117
+
118
+ # Add/update vcs section (git only)
119
+ config["vcs"] = {
120
+ "type": "git",
121
+ }
122
+
123
+ # Write back
124
+ with open(config_file, "w") as f:
125
+ yaml.dump(config, f)
126
+
127
+
128
+ def _install_git_hooks(project_path: Path, templates_root: Path | None = None, tracker: StepTracker | None = None) -> None:
129
+ """Install git hooks from templates to .git/hooks directory.
130
+
131
+ Args:
132
+ project_path: Path to the project root
133
+ templates_root: Path to the templates directory (if available)
134
+ tracker: Optional progress tracker
135
+ """
136
+ git_hooks_dir = project_path / ".git" / "hooks"
137
+ # Use templates_root if available, otherwise fall back to user project (for backwards compat)
138
+ if templates_root:
139
+ template_hooks_dir = templates_root / "git-hooks"
140
+ else:
141
+ template_hooks_dir = project_path / ".kittify" / "templates" / "git-hooks"
142
+
143
+ if not git_hooks_dir.exists():
144
+ if tracker:
145
+ tracker.skip("git-hooks", ".git/hooks directory not found")
146
+ return
147
+
148
+ if not template_hooks_dir.exists():
149
+ if tracker:
150
+ tracker.skip("git-hooks", "no hook templates found")
151
+ return
152
+
153
+ installed_count = 0
154
+ for hook_template in template_hooks_dir.iterdir():
155
+ if hook_template.is_file() and not hook_template.name.startswith('.'):
156
+ hook_dest = git_hooks_dir / hook_template.name
157
+ shutil.copy2(hook_template, hook_dest)
158
+ # Make executable on POSIX systems
159
+ if os.name != "nt":
160
+ hook_dest.chmod(0o755)
161
+ installed_count += 1
162
+
163
+ if tracker:
164
+ if installed_count > 0:
165
+ tracker.complete("git-hooks", f"{installed_count} hook(s) installed")
166
+ else:
167
+ tracker.skip("git-hooks", "no hooks to install")
168
+
169
+
170
+ def init(
171
+ project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here, or use '.' for current directory)"),
172
+ ai_assistant: str = typer.Option(None, "--ai", help="Comma-separated AI assistants (claude,codex,gemini,...)", rich_help_panel="Selection"),
173
+ script_type: str = typer.Option(None, "--script", help="Script type to use: sh or ps", rich_help_panel="Selection"),
174
+ mission_key: str = typer.Option(None, "--mission", hidden=True, help="[DEPRECATED] Mission selection moved to /spec-kitty.specify"),
175
+ ignore_agent_tools: bool = typer.Option(False, "--ignore-agent-tools", help="Skip checks for AI agent tools like Claude Code"),
176
+ no_git: bool = typer.Option(False, "--no-git", help="Skip git repository initialization"),
177
+ here: bool = typer.Option(False, "--here", help="Initialize project in the current directory instead of creating a new one"),
178
+ force: bool = typer.Option(False, "--force", help="Force merge/overwrite when using --here (skip confirmation)"),
179
+ skip_tls: bool = typer.Option(False, "--skip-tls", help="Skip SSL/TLS verification (not recommended)"),
180
+ debug: bool = typer.Option(False, "--debug", help="Show verbose diagnostic output for network and extraction failures"),
181
+ github_token: str = typer.Option(None, "--github-token", help="GitHub token to use for API requests (or set GH_TOKEN or GITHUB_TOKEN environment variable)"),
182
+ template_root: str = typer.Option(None, "--template-root", help="Override default template location (useful for development mode)"),
183
+ ):
184
+ """Initialize a new Spec Kitty project."""
185
+ # Use the injected dependencies
186
+ assert _console is not None
187
+ assert _show_banner is not None
188
+ assert _activate_mission is not None
189
+ assert _ensure_executable_scripts is not None
190
+
191
+ _show_banner()
192
+
193
+ # Handle '.' as shorthand for current directory (equivalent to --here)
194
+ if project_name == ".":
195
+ here = True
196
+ project_name = None # Clear project_name to use existing validation logic
197
+
198
+ if here and project_name:
199
+ _console.print("[red]Error:[/red] Cannot specify both project name and --here flag")
200
+ raise typer.Exit(1)
201
+
202
+ if not here and not project_name:
203
+ _console.print("[red]Error:[/red] Must specify either a project name, use '.' for current directory, or use --here flag")
204
+ raise typer.Exit(1)
205
+
206
+ if here:
207
+ try:
208
+ project_path = Path.cwd()
209
+ project_name = project_path.name
210
+ except (OSError, FileNotFoundError) as e:
211
+ _console.print("[red]Error:[/red] Cannot access current directory")
212
+ _console.print(f"[dim]{e}[/dim]")
213
+ _console.print("[yellow]Hint:[/yellow] Your current directory may have been deleted or is no longer accessible")
214
+ raise typer.Exit(1)
215
+
216
+ existing_items = list(project_path.iterdir())
217
+ if existing_items:
218
+ _console.print(f"[yellow]Warning:[/yellow] Current directory is not empty ({len(existing_items)} items)")
219
+ _console.print("[yellow]Template files will be merged with existing content and may overwrite existing files[/yellow]")
220
+ if force:
221
+ _console.print("[cyan]--force supplied: skipping confirmation and proceeding with merge[/cyan]")
222
+ else:
223
+ response = typer.confirm("Do you want to continue?")
224
+ if not response:
225
+ _console.print("[yellow]Operation cancelled[/yellow]")
226
+ raise typer.Exit(0)
227
+ else:
228
+ project_path = Path(project_name).resolve()
229
+ if project_path.exists():
230
+ error_panel = Panel(
231
+ f"Directory '[cyan]{project_name}[/cyan]' already exists\n"
232
+ "Please choose a different project name or remove the existing directory.",
233
+ title="[red]Directory Conflict[/red]",
234
+ border_style="red",
235
+ padding=(1, 2)
236
+ )
237
+ _console.print()
238
+ _console.print(error_panel)
239
+ raise typer.Exit(1)
240
+
241
+ current_dir = Path.cwd()
242
+
243
+ setup_lines = [
244
+ "[cyan]Specify Project Setup[/cyan]",
245
+ "",
246
+ f"{'Project':<15} [green]{project_path.name}[/green]",
247
+ f"{'Working Path':<15} [dim]{current_dir}[/dim]",
248
+ ]
249
+
250
+ # Add target path only if different from working dir
251
+ if not here:
252
+ setup_lines.append(f"{'Target Path':<15} [dim]{project_path}[/dim]")
253
+
254
+ _console.print(Panel("\n".join(setup_lines), border_style="cyan", padding=(1, 2)))
255
+
256
+ # Check git only if we might need it (not --no-git)
257
+ # Only set to True if the user wants it and the tool is available
258
+ should_init_git = False
259
+ if not no_git:
260
+ should_init_git = check_tool("git", "https://git-scm.com/downloads")
261
+ if not should_init_git:
262
+ _console.print("[yellow]Git not found - will skip repository initialization[/yellow]")
263
+
264
+ # Detect VCS (git only, jj support removed)
265
+ selected_vcs: VCSBackend | None = None
266
+ try:
267
+ selected_vcs = _detect_default_vcs()
268
+ _console.print()
269
+ _display_vcs_info(selected_vcs, _console)
270
+ _console.print()
271
+ except VCSNotFoundError:
272
+ # git not available - not an error, just informational
273
+ selected_vcs = None
274
+ _console.print("[yellow]ℹ git not detected[/yellow] - install git for version control")
275
+
276
+ if ai_assistant:
277
+ raw_agents = [part.strip().lower() for part in ai_assistant.replace(";", ",").split(",") if part.strip()]
278
+ if not raw_agents:
279
+ _console.print("[red]Error:[/red] --ai flag did not contain any valid agent identifiers")
280
+ raise typer.Exit(1)
281
+ selected_agents: list[str] = []
282
+ seen_agents: set[str] = set()
283
+ invalid_agents: list[str] = []
284
+ for key in raw_agents:
285
+ if key not in AI_CHOICES:
286
+ invalid_agents.append(key)
287
+ continue
288
+ if key not in seen_agents:
289
+ selected_agents.append(key)
290
+ seen_agents.add(key)
291
+ if invalid_agents:
292
+ _console.print(
293
+ f"[red]Error:[/red] Invalid AI assistant(s): {', '.join(invalid_agents)}. "
294
+ f"Choose from: {', '.join(AI_CHOICES.keys())}"
295
+ )
296
+ raise typer.Exit(1)
297
+ else:
298
+ selected_agents = multi_select_with_arrows(
299
+ AI_CHOICES,
300
+ "Choose your AI assistant(s):",
301
+ default_keys=["copilot"],
302
+ )
303
+
304
+ if not selected_agents:
305
+ _console.print("[red]Error:[/red] No AI assistants selected")
306
+ raise typer.Exit(1)
307
+
308
+ # Check agent tools unless ignored
309
+ if not ignore_agent_tools:
310
+ missing_agents: list[tuple[str, str, str]] = [] # (agent key, display, url)
311
+ for agent_key in selected_agents:
312
+ requirement = AGENT_TOOL_REQUIREMENTS.get(agent_key)
313
+ if not requirement:
314
+ continue
315
+ tool_name, url = requirement
316
+ if not check_tool(tool_name, url, agent_name=agent_key):
317
+ missing_agents.append((agent_key, AI_CHOICES[agent_key], url))
318
+
319
+ if missing_agents:
320
+ lines = []
321
+ for agent_key, display_name, url in missing_agents:
322
+ lines.append(f"[cyan]{display_name}[/cyan] ({agent_key}) → install: [cyan]{url}[/cyan]")
323
+ lines.append("")
324
+ lines.append("These tools are optional. You can install them later to enable additional features.")
325
+ warning_panel = Panel(
326
+ "\n".join(lines),
327
+ title="[yellow]Optional Agent Tool(s) Not Found[/yellow]",
328
+ border_style="yellow",
329
+ padding=(1, 2),
330
+ )
331
+ _console.print()
332
+ _console.print(warning_panel)
333
+ # Continue with init instead of blocking
334
+
335
+ # ==========================================================================
336
+ # Agent Selection Strategy (for orchestrator)
337
+ # ==========================================================================
338
+ _console.print()
339
+ _console.print("[cyan]Agent Selection Strategy[/cyan]")
340
+ _console.print("[dim]This determines how agents are chosen for implementation and review tasks.[/dim]")
341
+ _console.print()
342
+
343
+ strategy_choices = {
344
+ "preferred": "Preferred Agents - You choose which agent implements and which reviews",
345
+ "random": "Random - Randomly select from available agents each time",
346
+ }
347
+ selected_strategy = select_with_arrows(
348
+ strategy_choices,
349
+ "How should agents be selected for tasks?",
350
+ default_key="preferred",
351
+ )
352
+
353
+ preferred_implementer: str | None = None
354
+ preferred_reviewer: str | None = None
355
+
356
+ if selected_strategy == "preferred":
357
+ # Ask for preferred implementer
358
+ agent_display_map = {key: AI_CHOICES[key] for key in selected_agents}
359
+
360
+ _console.print()
361
+ preferred_implementer = select_with_arrows(
362
+ agent_display_map,
363
+ "Which agent should be the preferred IMPLEMENTER?",
364
+ default_key=selected_agents[0],
365
+ )
366
+
367
+ # Ask for preferred reviewer (prefer different from implementer)
368
+ _console.print()
369
+ if len(selected_agents) > 1:
370
+ # Default to a different agent for review
371
+ default_reviewer = next((a for a in selected_agents if a != preferred_implementer), selected_agents[0])
372
+ preferred_reviewer = select_with_arrows(
373
+ agent_display_map,
374
+ "Which agent should be the preferred REVIEWER?",
375
+ default_key=default_reviewer,
376
+ )
377
+ if preferred_reviewer == preferred_implementer and len(selected_agents) > 1:
378
+ _console.print("[yellow]Note:[/yellow] Same agent for implementation and review (cross-review disabled)")
379
+ else:
380
+ # Only one agent - same for both
381
+ preferred_reviewer = preferred_implementer
382
+ _console.print(f"[dim]Single agent mode: {AI_CHOICES[preferred_implementer]} will do both implementation and review[/dim]")
383
+
384
+ # Build agent config to save later
385
+ agent_config = AgentConfig(
386
+ available=selected_agents,
387
+ selection=AgentSelectionConfig(
388
+ strategy=SelectionStrategy(selected_strategy),
389
+ preferred_implementer=preferred_implementer,
390
+ preferred_reviewer=preferred_reviewer,
391
+ ),
392
+ )
393
+
394
+ # Determine script type (explicit or auto-detect)
395
+ if script_type:
396
+ if script_type not in SCRIPT_TYPE_CHOICES:
397
+ _console.print(f"[red]Error:[/red] Invalid script type '{script_type}'. Choose from: {', '.join(SCRIPT_TYPE_CHOICES.keys())}")
398
+ raise typer.Exit(1)
399
+ selected_script = script_type
400
+ else:
401
+ # Auto-detect based on platform
402
+ selected_script = "ps" if os.name == "nt" else "sh"
403
+ _console.print(f"[dim]Auto-detected script type:[/dim] [cyan]{SCRIPT_TYPE_CHOICES[selected_script]}[/cyan]")
404
+
405
+ # Mission selection deprecated - missions are now per-feature
406
+ if mission_key:
407
+ _console.print("[yellow]Warning:[/yellow] The --mission flag is deprecated. Missions are now selected per-feature during /spec-kitty.specify")
408
+ _console.print("[dim]Ignoring --mission flag and continuing with initialization...[/dim]")
409
+ _console.print()
410
+
411
+ # No longer select a project-level mission - just use software-dev for initial setup
412
+ selected_mission = DEFAULT_MISSION_KEY
413
+ mission_display = MISSION_CHOICES.get(selected_mission, "Software Dev Kitty")
414
+
415
+ template_mode = "package"
416
+ local_repo = get_local_repo_root(override_path=template_root)
417
+ if local_repo is not None:
418
+ template_mode = "local"
419
+ if debug:
420
+ _console.print(f"[cyan]Using local templates from[/cyan] {local_repo}")
421
+
422
+ repo_owner = repo_name = None
423
+ remote_slug_env = os.environ.get("SPECIFY_TEMPLATE_REPO")
424
+ if remote_slug_env:
425
+ try:
426
+ repo_owner, repo_name = parse_repo_slug(remote_slug_env)
427
+ except ValueError as exc:
428
+ _console.print(f"[red]Error:[/red] {exc}")
429
+ raise typer.Exit(1)
430
+ template_mode = "remote"
431
+ if debug:
432
+ _console.print(f"[cyan]Using remote templates from[/cyan] {repo_owner}/{repo_name}")
433
+ elif template_mode == "package" and debug:
434
+ _console.print("[cyan]Using templates bundled with specify_cli package[/cyan]")
435
+
436
+ ai_display = ", ".join(AI_CHOICES[key] for key in selected_agents)
437
+ _console.print(f"[cyan]Selected AI assistant(s):[/cyan] {ai_display}")
438
+ _console.print(f"[cyan]Selected script type:[/cyan] {selected_script}")
439
+ _console.print(f"[cyan]Selected mission:[/cyan] {mission_display}")
440
+
441
+ # Download and set up project
442
+ # New tree-based progress (no emojis); include earlier substeps
443
+ tracker = StepTracker("Initialize Specify Project")
444
+ # Flag to allow suppressing legacy headings
445
+ sys._specify_tracker_active = True
446
+ # Pre steps recorded as completed before live rendering
447
+ tracker.add("precheck", "Check required tools")
448
+ tracker.complete("precheck", "ok")
449
+ tracker.add("ai-select", "Select AI assistant(s)")
450
+ tracker.complete("ai-select", ai_display)
451
+ tracker.add("script-select", "Select script type")
452
+ tracker.complete("script-select", selected_script)
453
+ tracker.add("mission-select", "Select mission")
454
+ tracker.complete("mission-select", mission_display)
455
+ tracker.add("mission-activate", "Activate mission")
456
+ for agent_key in selected_agents:
457
+ label = AI_CHOICES[agent_key]
458
+ tracker.add(f"{agent_key}-fetch", f"{label}: fetch latest release")
459
+ tracker.add(f"{agent_key}-download", f"{label}: download template")
460
+ tracker.add(f"{agent_key}-extract", f"{label}: extract template")
461
+ tracker.add(f"{agent_key}-zip-list", f"{label}: archive contents")
462
+ tracker.add(f"{agent_key}-extracted-summary", f"{label}: extraction summary")
463
+ tracker.add(f"{agent_key}-cleanup", f"{label}: cleanup")
464
+ for key, label in [
465
+ ("chmod", "Ensure scripts executable"),
466
+ ("git", "Initialize git repository"),
467
+ ("final", "Finalize"),
468
+ ]:
469
+ tracker.add(key, label)
470
+
471
+ if template_mode in ("local", "package") and not here and not project_path.exists():
472
+ project_path.mkdir(parents=True)
473
+
474
+ command_templates_dir: Path | None = None
475
+ render_templates_dir: Path | None = None
476
+ templates_root: Path | None = None # Track template source for later use
477
+ base_prepared = False
478
+ if template_mode == "remote" and (repo_owner is None or repo_name is None):
479
+ repo_owner, repo_name = parse_repo_slug(DEFAULT_TEMPLATE_REPO)
480
+
481
+ with Live(tracker.render(), console=_console, refresh_per_second=8, transient=True) as live:
482
+ tracker.attach_refresh(lambda: live.update(tracker.render()))
483
+ try:
484
+ # Create a httpx client with verify based on skip_tls
485
+ local_client = build_http_client(skip_tls=skip_tls)
486
+
487
+ for index, agent_key in enumerate(selected_agents):
488
+ if template_mode in ("local", "package"):
489
+ source_detail = "local checkout" if template_mode == "local" else "packaged data"
490
+ tracker.start(f"{agent_key}-fetch")
491
+ tracker.complete(f"{agent_key}-fetch", source_detail)
492
+ tracker.start(f"{agent_key}-download")
493
+ tracker.complete(f"{agent_key}-download", "local files")
494
+ tracker.start(f"{agent_key}-extract")
495
+ try:
496
+ if not base_prepared:
497
+ if template_mode == "local":
498
+ command_templates_dir = copy_specify_base_from_local(local_repo, project_path, selected_script)
499
+ else:
500
+ command_templates_dir = copy_specify_base_from_package(project_path, selected_script)
501
+ base_prepared = True
502
+ # Track templates root for later use (AGENTS.md, .claudeignore, git-hooks)
503
+ if command_templates_dir:
504
+ templates_root = command_templates_dir.parent
505
+ if command_templates_dir is None:
506
+ raise RuntimeError("Command templates directory was not prepared")
507
+ if render_templates_dir is None:
508
+ mission_templates_dir = (
509
+ project_path
510
+ / ".kittify"
511
+ / "missions"
512
+ / selected_mission
513
+ / "command-templates"
514
+ )
515
+ render_templates_dir = prepare_command_templates(
516
+ command_templates_dir,
517
+ mission_templates_dir,
518
+ )
519
+ generate_agent_assets(render_templates_dir, project_path, agent_key, selected_script)
520
+ except Exception as exc:
521
+ tracker.error(f"{agent_key}-extract", str(exc))
522
+ raise
523
+ else:
524
+ tracker.complete(f"{agent_key}-extract", "commands generated")
525
+ tracker.start(f"{agent_key}-zip-list")
526
+ tracker.complete(f"{agent_key}-zip-list", "templates ready")
527
+ tracker.start(f"{agent_key}-extracted-summary")
528
+ tracker.complete(f"{agent_key}-extracted-summary", "commands ready")
529
+ tracker.start(f"{agent_key}-cleanup")
530
+ tracker.complete(f"{agent_key}-cleanup", "done")
531
+ else:
532
+ is_current_dir_flag = here if index == 0 else True
533
+ allow_existing_flag = index > 0
534
+ download_and_extract_template(
535
+ project_path,
536
+ agent_key,
537
+ selected_script,
538
+ is_current_dir_flag,
539
+ verbose=False,
540
+ tracker=tracker,
541
+ tracker_prefix=agent_key,
542
+ allow_existing=allow_existing_flag,
543
+ client=local_client,
544
+ debug=debug,
545
+ github_token=github_token,
546
+ repo_owner=repo_owner,
547
+ repo_name=repo_name,
548
+ )
549
+
550
+ tracker.start("mission-activate")
551
+ try:
552
+ mission_status = _activate_mission(project_path, selected_mission, mission_display, _console)
553
+ except Exception as exc:
554
+ tracker.error("mission-activate", str(exc))
555
+ raise
556
+ else:
557
+ tracker.complete("mission-activate", mission_status)
558
+
559
+ # Ensure scripts are executable (POSIX)
560
+ _ensure_executable_scripts(project_path, tracker=tracker)
561
+
562
+ # Git step - must happen BEFORE hook installation
563
+ if not no_git:
564
+ tracker.start("git")
565
+ if is_git_repo(project_path):
566
+ tracker.complete("git", "existing repo detected")
567
+ elif should_init_git:
568
+ if init_git_repo(project_path, quiet=True):
569
+ tracker.complete("git", "initialized")
570
+ else:
571
+ tracker.error("git", "init failed")
572
+ else:
573
+ tracker.skip("git", "git not available")
574
+ else:
575
+ tracker.skip("git", "--no-git flag")
576
+
577
+ # Install git hooks AFTER git is initialized
578
+ if not no_git and is_git_repo(project_path):
579
+ tracker.add("git-hooks", "Install git hooks")
580
+ tracker.start("git-hooks")
581
+ _install_git_hooks(project_path, templates_root=templates_root, tracker=tracker)
582
+
583
+ tracker.complete("final", "project ready")
584
+ except Exception as e:
585
+ tracker.error("final", str(e))
586
+ _console.print(Panel(f"Initialization failed: {e}", title="Failure", border_style="red"))
587
+ if debug:
588
+ _env_pairs = [
589
+ ("Python", sys.version.split()[0]),
590
+ ("Platform", sys.platform),
591
+ ("CWD", str(Path.cwd())),
592
+ ]
593
+ _label_width = max(len(k) for k, _ in _env_pairs)
594
+ env_lines = [f"{k.ljust(_label_width)} → [bright_black]{v}[/bright_black]" for k, v in _env_pairs]
595
+ _console.print(Panel("\n".join(env_lines), title="Debug Environment", border_style="magenta"))
596
+ if not here and project_path.exists():
597
+ shutil.rmtree(project_path)
598
+ raise typer.Exit(1)
599
+ finally:
600
+ # Force final render
601
+ pass
602
+
603
+ # Final static tree (ensures finished state visible after Live context ends)
604
+ _console.print(tracker.render())
605
+ _console.print("\n[bold green]Project ready.[/bold green]")
606
+
607
+ # Agent folder security notice
608
+ agent_folder_map = {
609
+ "claude": ".claude/",
610
+ "gemini": ".gemini/",
611
+ "cursor": ".cursor/",
612
+ "qwen": ".qwen/",
613
+ "opencode": ".opencode/",
614
+ "codex": ".codex/",
615
+ "windsurf": ".windsurf/",
616
+ "kilocode": ".kilocode/",
617
+ "auggie": ".augment/",
618
+ "copilot": ".github/",
619
+ "roo": ".roo/",
620
+ "q": ".amazonq/"
621
+ }
622
+
623
+ notice_entries = []
624
+ for agent_key in selected_agents:
625
+ folder = agent_folder_map.get(agent_key)
626
+ if folder:
627
+ notice_entries.append((AI_CHOICES[agent_key], folder))
628
+
629
+ if notice_entries:
630
+ body_lines = [
631
+ "Some agents may store credentials, auth tokens, or other identifying and private artifacts in the agent folder within your project.",
632
+ "Consider adding the following folders (or subsets) to [cyan].gitignore[/cyan]:",
633
+ "",
634
+ ]
635
+ body_lines.extend(f"- {display}: [cyan]{folder}[/cyan]" for display, folder in notice_entries)
636
+ security_notice = Panel(
637
+ "\n".join(body_lines),
638
+ title="[yellow]Agent Folder Security[/yellow]",
639
+ border_style="yellow",
640
+ padding=(1, 2),
641
+ )
642
+ _console.print()
643
+ _console.print(security_notice)
644
+
645
+ # Boxed "Next steps" section
646
+ steps_lines = []
647
+ step_num = 1
648
+ if not here:
649
+ steps_lines.append(f"{step_num}. Go to the project folder: [cyan]cd {project_name}[/cyan]")
650
+ else:
651
+ steps_lines.append(f"{step_num}. You're already in the project directory!")
652
+ step_num += 1
653
+
654
+ steps_lines.append(f"{step_num}. Available missions: [cyan]software-dev[/cyan], [cyan]research[/cyan] (selected per-feature during [cyan]/spec-kitty.specify[/cyan])")
655
+ step_num += 1
656
+
657
+ steps_lines.append(f"{step_num}. Start using slash commands with your AI agent (in workflow order):")
658
+ step_num += 1
659
+
660
+ steps_lines.append(" - [cyan]/spec-kitty.dashboard[/] - Open the real-time kanban dashboard")
661
+ steps_lines.append(" - [cyan]/spec-kitty.constitution[/] - Establish project principles")
662
+ steps_lines.append(" - [cyan]/spec-kitty.specify[/] - Create baseline specification")
663
+ steps_lines.append(" - [cyan]/spec-kitty.plan[/] - Create implementation plan")
664
+ steps_lines.append(" - [cyan]/spec-kitty.research[/] - Run mission-specific Phase 0 research scaffolding")
665
+ steps_lines.append(" - [cyan]/spec-kitty.tasks[/] - Generate tasks and kanban-ready prompt files")
666
+ steps_lines.append(" - [cyan]/spec-kitty.implement[/] - Execute implementation from /tasks/doing/")
667
+ steps_lines.append(" - [cyan]/spec-kitty.review[/] - Review prompts and move them to /tasks/done/")
668
+ steps_lines.append(" - [cyan]/spec-kitty.accept[/] - Run acceptance checks and verify feature complete")
669
+ steps_lines.append(" - [cyan]/spec-kitty.merge[/] - Merge feature into main and cleanup worktree")
670
+
671
+ steps_panel = Panel("\n".join(steps_lines), title="Next Steps", border_style="cyan", padding=(1,2))
672
+ _console.print()
673
+ _console.print(steps_panel)
674
+
675
+ enhancement_lines = [
676
+ "Optional commands that you can use for your specs [bright_black](improve quality & confidence)[/bright_black]",
677
+ "",
678
+ f"○ [cyan]/spec-kitty.clarify[/] [bright_black](optional)[/bright_black] - Ask structured questions to de-risk ambiguous areas before planning (run before [cyan]/spec-kitty.plan[/] if used)",
679
+ f"○ [cyan]/spec-kitty.analyze[/] [bright_black](optional)[/bright_black] - Cross-artifact consistency & alignment report (after [cyan]/spec-kitty.tasks[/], before [cyan]/spec-kitty.implement[/])",
680
+ f"○ [cyan]/spec-kitty.checklist[/] [bright_black](optional)[/bright_black] - Generate quality checklists to validate requirements completeness, clarity, and consistency (after [cyan]/spec-kitty.plan[/])"
681
+ ]
682
+ enhancements_panel = Panel("\n".join(enhancement_lines), title="Enhancement Commands", border_style="cyan", padding=(1,2))
683
+ _console.print()
684
+ _console.print(enhancements_panel)
685
+
686
+ # Start or reconnect to the dashboard server as a detached background process
687
+ _console.print()
688
+ try:
689
+ dashboard_url, port, started = ensure_dashboard_running(project_path)
690
+
691
+ title = "[bold green]Spec Kitty Dashboard Started[/bold green]" if started else "[bold green]Spec Kitty Dashboard Ready[/bold green]"
692
+ status_line = (
693
+ "[dim]The dashboard is running in the background and will continue even after\n"
694
+ "this command exits. It will automatically update as you work.[/dim]"
695
+ if started
696
+ else "[dim]An existing dashboard instance is running and ready.[/dim]"
697
+ )
698
+
699
+ dashboard_panel = Panel(
700
+ f"[bold cyan]Dashboard URL:[/bold cyan] {dashboard_url}\n"
701
+ f"[bold cyan]Port:[/bold cyan] {port}\n\n"
702
+ f"{status_line}\n\n"
703
+ f"[yellow]Tip:[/yellow] Run [cyan]/spec-kitty.dashboard[/cyan] or [cyan]spec-kitty dashboard[/cyan] to open it in your browser",
704
+ title=title,
705
+ border_style="green",
706
+ padding=(1, 2)
707
+ )
708
+ _console.print(dashboard_panel)
709
+ _console.print()
710
+ except Exception as e:
711
+ _console.print(f"[yellow]Warning: Could not start dashboard: {e}[/yellow]")
712
+ _console.print("[dim]Continuing without dashboard...[/dim]")
713
+
714
+ # Protect ALL agent directories in .gitignore
715
+ manager = GitignoreManager(project_path)
716
+ result = manager.protect_all_agents() # Note: ALL agents, not just selected
717
+
718
+ # Display results to user
719
+ if result.modified:
720
+ _console.print("[cyan]Updated .gitignore to exclude AI agent directories:[/cyan]")
721
+ for entry in result.entries_added:
722
+ _console.print(f" • {entry}")
723
+ if result.entries_skipped:
724
+ _console.print(f" ({len(result.entries_skipped)} already protected)")
725
+ elif result.entries_skipped:
726
+ _console.print(f"[dim]All {len(result.entries_skipped)} agent directories already in .gitignore[/dim]")
727
+
728
+ # Show warnings (especially for .github/)
729
+ for warning in result.warnings:
730
+ _console.print(f"[yellow]⚠️ {warning}[/yellow]")
731
+
732
+ # Show errors if any
733
+ for error in result.errors:
734
+ _console.print(f"[red]❌ {error}[/red]")
735
+
736
+ # Copy AGENTS.md from template source (not user project)
737
+ if templates_root:
738
+ agents_target = project_path / ".kittify" / "AGENTS.md"
739
+ agents_template = templates_root / "AGENTS.md"
740
+ if not agents_target.exists() and agents_template.exists():
741
+ shutil.copy2(agents_template, agents_target)
742
+
743
+ # Generate .claudeignore from template source
744
+ claudeignore_template = templates_root / "claudeignore-template"
745
+ claudeignore_dest = project_path / ".claudeignore"
746
+ if claudeignore_template.exists() and not claudeignore_dest.exists():
747
+ shutil.copy2(claudeignore_template, claudeignore_dest)
748
+ _console.print("[dim]Created .claudeignore to optimize AI assistant scanning[/dim]")
749
+
750
+ # Create project metadata for upgrade tracking
751
+ try:
752
+ from datetime import datetime
753
+ import platform as plat
754
+ import sys as system
755
+ from specify_cli import __version__
756
+ from specify_cli.upgrade.metadata import ProjectMetadata
757
+
758
+ metadata = ProjectMetadata(
759
+ version=__version__,
760
+ initialized_at=datetime.now(),
761
+ python_version=plat.python_version(),
762
+ platform=system.platform,
763
+ platform_version=plat.platform(),
764
+ )
765
+ metadata.save(project_path / ".kittify")
766
+ except Exception as e:
767
+ # Don't fail init if metadata creation fails
768
+ _console.print(f"[dim]Note: Could not create project metadata: {e}[/dim]")
769
+
770
+ # Save VCS preference to config.yaml
771
+ if selected_vcs:
772
+ try:
773
+ _save_vcs_config(project_path / ".kittify", selected_vcs)
774
+ except Exception as e:
775
+ # Don't fail init if VCS config creation fails
776
+ _console.print(f"[dim]Note: Could not save VCS config: {e}[/dim]")
777
+
778
+ # Save agent configuration to config.yaml
779
+ try:
780
+ save_agent_config(project_path, agent_config)
781
+ _console.print(f"[dim]Saved agent configuration ({selected_strategy} strategy)[/dim]")
782
+ except Exception as e:
783
+ # Don't fail init if agent config creation fails
784
+ _console.print(f"[dim]Note: Could not save agent config: {e}[/dim]")
785
+
786
+ # Clean up templates directory - it's only needed during init
787
+ # User projects should only have the generated agent commands, not the source templates
788
+ templates_dir = project_path / ".kittify" / "templates"
789
+ if templates_dir.exists():
790
+ try:
791
+ shutil.rmtree(templates_dir)
792
+ except PermissionError:
793
+ _console.print("[dim]Note: Could not remove .kittify/templates/ (permission denied)[/dim]")
794
+ except Exception as e:
795
+ # Log but don't fail init if cleanup fails
796
+ _console.print(f"[dim]Note: Could not remove .kittify/templates/: {e}[/dim]")
797
+
798
+
799
+ def register_init_command(
800
+ app: typer.Typer,
801
+ *,
802
+ console: Console,
803
+ show_banner: Callable[[], None],
804
+ activate_mission: Callable[[Path, str, str, Console], str],
805
+ ensure_executable_scripts: Callable[[Path, StepTracker | None], None],
806
+ ) -> None:
807
+ """Register the init command with injected dependencies."""
808
+ global _console, _show_banner, _activate_mission, _ensure_executable_scripts
809
+
810
+ # Store the dependencies
811
+ _console = console
812
+ _show_banner = show_banner
813
+ _activate_mission = activate_mission
814
+ _ensure_executable_scripts = ensure_executable_scripts
815
+
816
+ # Set the docstring
817
+ init.__doc__ = INIT_COMMAND_DOC
818
+
819
+ # Ensure app is in multi-command mode by checking if there are existing commands
820
+ # If not, add a hidden dummy command to force subcommand mode
821
+ if not hasattr(app, 'registered_commands') or not getattr(app, 'registered_commands'):
822
+ @app.command("__force_multi_command_mode__", hidden=True)
823
+ def _dummy():
824
+ pass
825
+
826
+ # Register the command with explicit name to ensure it's always a subcommand
827
+ app.command("init")(init)