Implementation Plan: Init Command Overhaul (076)
Branch: main | Date: 2026-04-08 | Spec: spec.md
Branch contract:
- Current branch at plan start:
main - Planning/base branch:
main - Final merge target:
main branch_matches_target: true
Summary
Redesign spec-kitty init from a per-project installer into a global machine-level bootstrapper. Remove 11 CLI flags, 9 processing stages, dead template infrastructure, and the entirely unused preferred_implementer/preferred_reviewer preference system from the full codebase. Add a new migration (m_3_2_2) that implements the safe per-project command removal with all four safety invariants (the existing m_3_1_2 cannot be hardened in-place — see Critical Findings). Author 7 ADRs documenting all decisions made during the overhaul.
Technical Context
Language/Version: Python 3.11+ CLI framework: typer Console output: rich YAML parsing: ruamel.yaml Testing: pytest (90%+ coverage on new/modified code) Type checking: mypy --strict (must pass on all modified files) Storage: filesystem only (.kittify/config.yaml, migration files, ADR markdown) Target platform: macOS, Linux, WSL
Critical finding — Migration B cannot be hardened in-place: m_3_1_2_globalize_commands.py lacks all four safety invariants but cannot be modified in-place. The migration runner (runner.py:182–190) tracks applied migrations by migration_id. Projects that already ran "3.1.2_globalize_commands" will skip any changes to that file. WP04 must create m_3_2_2_harden_globalize_commands.py with a new ID. The existing 3.1.2 file is left as-is.
Critical finding — agents → tools rename vs load_agent_config() mismatch: m_2_0_1_tool_config_key_rename.py renames agents to tools in config.yaml, but load_agent_config() only reads from agents. On post-2.0.1 projects, agent config silently returns empty defaults. WP01 must patch load_agent_config() to try tools first, then fall back to agents. Migration A (WP03) already handles both keys for the selection cleanup; this fix ensures the underlying read path is consistent.
Critical finding — skills installation loses its home in WP02: install_skills_for_agent() is called inside the per-agent loop that WP02 removes (init.py:1177). ensure_runtime() in bootstrap.py copies missions and scripts but does not install agent skills. After WP02, nothing calls _sync_global_skill() during init. WP02 must add a global skill installation pass to ensure_runtime() or a dedicated call after the global runtime bootstrap, before the per-agent loop is removed.
Key research finding — version marker: A file is spec-kitty-generated iff its first line starts with <!-- spec-kitty-command-version: (written by shims/generator.py:101, read by m_2_1_4_enforce_command_file_state.py:39).
Key research finding — migration version ordering: The migration registry orders by packaging.version.Version(target_version). WP03 uses 3.2.1, WP04 uses 3.2.2. No collision.
Charter Check
Directives in scope:
- DIRECTIVE_010 (Specification Fidelity): Implementation must match spec; any deviation requires explicit documentation
- DIRECTIVE_003 (Decision Documentation): All 7 ADRs must be authored as part of this feature — not deferred
Policy compliance:
- typer ✓ (existing framework, no change)
- mypy --strict ✓ (required on all touched files)
- pytest 90%+ ✓ (WP01, WP02, WP03, WP04 each include test updates)
- Integration tests for CLI commands ✓ (WP02 covers
spec-kitty init)
No charter violations.
Project Structure
Documentation (this feature)
kitty-specs/076-init-command-overhaul/
├── spec.md ✓ complete
├── research.md ✓ complete (this planning phase)
├── plan.md ← this file
└── tasks/ ← generated by /spec-kitty.tasks
Source Code (affected paths)
src/specify_cli/
├── cli/commands/
│ └── init.py WP02: remove 11 flags + 9 stages
├── core/
│ └── agent_config.py WP01: remove AgentSelectionConfig + dead methods
├── template/
│ ├── github_client.py WP02: DELETE FILE
│ └── manager.py WP02: remove get_local_repo_root + script-copy logic
└── upgrade/migrations/
├── m_3_1_2_globalize_commands.py WP04: harden with safety invariants
└── m_3_2_1_strip_selection_config.py WP03: NEW — strip selection keys from config.yaml
architecture/adrs/
└── 2026-04-08-{N}-*.md × 7 WP05: 7 new ADR files
docs/
├── how-to/non-interactive-init.md WP06: remove preference flag examples
├── how-to/manage-agents.md WP06: remove preference field refs
├── reference/cli-commands.md WP06: update init flag table
├── reference/configuration.md WP06: remove selection block entries
└── 2x/model-discipline-routing.md WP06: remove preference reference
tests/
├── agent/test_agent_config_unit.py WP01: remove preference assertions
├── agent/test_init_command.py WP02: remove removed-flag test cases
├── specify_cli/cli/commands/
│ └── test_init_doctrine.py WP02: DELETE FILE
└── upgrade/migrations/
├── test_m_2_0_1_*.py WP03: update selection key fixtures
└── test_m_3_1_2_*.py WP04: add safety invariant test cases
Execution Lane Plan
Three lanes run in parallel. Sequential ordering within each lane.
Lane A ──── WP01 ──────────── WP02 ──────────────────────────────► merge last
agent_config init.py + template
(+ load_agent_config (+ global skill install)
tools/agents fix)
Lane B ──── WP03 ──────────── WP04 ──────────────────────────────► merge second
Migration A Migration B (NEW FILE m_3_2_2)
(3.2.1) (3.2.2 — new ID, not in-place edit)
Lane C ──── WP05 ──────────── WP06 ──────────────────────────────► merge first
7 ADRs doc updates
Conflict surface at merge:
| Lane pair | Overlap | Risk | Resolution |
|---|---|---|---|
| A ↔ B | None | None | — |
| A ↔ C | Doc examples reference init flags being removed | Low | Keep Lane A's code truth; Lane C prose framing wraps cleanly |
| B ↔ C | None | None | — |
| All | CHANGELOG.md | Low | Keep CHANGELOG out of all WPs; update at merge time |
Merge order: C first (pure text), B second (migrations only), A last (core code + most test deletes).
Work Packages
WP01 — Remove AgentSelectionConfig, Dead Methods, and Fix load_agent_config
Lane: A · Position: 1 of 2 · Depends on: nothing
Files:
src/specify_cli/core/agent_config.pytests/agent/test_agent_config_unit.py
Tasks:
1. Delete AgentSelectionConfig dataclass (lines 28–37) 2. Delete select_implementer() method (lines 57–72) 3. Delete select_reviewer() method (lines 75–99) 4. Remove selection: AgentSelectionConfig field from AgentConfig 5. Remove selection construction in load_agent_config() (lines 158–162) 6. Remove selection block in save_agent_config() (lines 195–196) 7. Remove AgentSelectionConfig from __all__ 8. Fix load_agent_config() for the agents → tools rename: m_2_0_1_tool_config_key_rename.py renames the config root key from agents to tools, but load_agent_config() only reads agents. Patch it to try data.get("agents") or data.get("tools", {}) so both old and new config layouts work correctly. 9. In test_agent_config_unit.py: remove all preferred_implementer / preferred_reviewer fixture data and assertions; add tests confirming that a tools-keyed config is read correctly
Acceptance gate:
grep -r "AgentSelectionConfig\|select_implementer\|select_reviewer" src/→ zero resultsload_agent_config()returns correctavailablelist for bothagents-keyed andtools-keyed config filesmypy --strict src/specify_cli/core/agent_config.py→ cleanpytest tests/agent/test_agent_config_unit.py→ green
WP02 — Strip init.py, Remove Dead Template Code
Lane: A · Position: 2 of 2 · Depends on: WP01
Files:
src/specify_cli/cli/commands/init.pysrc/specify_cli/template/github_client.py(DELETE)src/specify_cli/template/manager.pytests/specify_cli/cli/commands/test_init_doctrine.py(DELETE)tests/agent/test_init_command.pytests/upgrade/migrations/test_m_2_0_1_tool_config_key_rename.py
Flags to remove from init.py:
| Flag | Approx location |
|---|---|
--here | line 740, usage 768–800 |
--script / --script-type | line 734, usage 982–992 |
--preferred-implementer | line 735, usage 911–937 |
--preferred-reviewer | line 736, usage 949–967 |
--force | line 741, usage 790 |
--template-root | line 750, usage 1007 |
--debug | line 744, usage 1010–1024 |
--skip-tls | line 743, usage 1077 |
--github-token | lines 745–748, usage 1160 |
--mission (hidden/deprecated) | line 737, usage 995–1000 |
--ignore-agent-tools | line 738, usage 884–908 |
Stages to remove from init.py:
| Stage / function | Location |
|---|---|
_resolve_preferred_agents() helper | lines 650–671 |
| Stage 3: preferred agent selection | lines 911–970 |
| Stage 4: script type selection | lines 982–992 |
| Stage 5: mission selection | lines 994–1005 |
Stage 9: _activate_mission() call | lines 1199–1211 |
_apply_doctrine_defaults() definition | lines 392–429 |
_run_inline_interview() definition | lines 433–516 |
_run_doctrine_stack_init() definition + call | lines 520–572, call 1261 |
_maybe_generate_structure_templates() definition + call | lines 576–623, call 1260 |
ensure_dashboard_running import + call | line 38 (import), line 1347 |
| Initial git commit block | lines 1469–1490 |
| Remote template mode branch | lines 1006–1021, 1144–1163 |
ensure_runtime() error handling fix (FR-003): Replace the silent except at line 1107 with:
except Exception as exc:
_console.print(f"[red]Error:[/red] Failed to bootstrap global runtime: {exc}")
raise typer.Exit(1)
Global skill installation (FR-007): The per-agent loop being removed (lines ~1165–1191) is currently the only call site for _sync_global_skill(). After WP02, nothing installs skills globally during init. Fix: immediately after ensure_runtime() succeeds, call a batch global skill install. Inspect src/specify_cli/skills/installer.py for a suitable entry point; if none exists, add install_all_global_skills(global_skills_root) that iterates the skill registry and calls _sync_global_skill() for each. This is required for FR-007.
Positive flow of the redesigned init (after all removals):
1. Show banner
2. Resolve project path (current dir or new subdir from project_name arg)
3. Validate target path (no conflict)
4. Check git availability
5. Detect/prompt AI agents (global scope — show existing, allow add/remove, warn machine-wide)
6. bootstrap global runtime: ensure_runtime() — hard fail on error
7. Install all skills globally
8. Write minimal per-project scaffolding:
a. Create project dir if needed
b. Write .gitignore (FR-011)
c. Write .claudeignore (FR-012)
d. Write .kittify/metadata.yaml (FR-013)
e. Write .kittify/config.yaml VCS + agent list (FR-014)
9. Initialize git repo if needed (FR-010)
10. Save agent config globally
11. Exit cleanly
Template module:
- Delete
src/specify_cli/template/github_client.py - In
manager.py: removeget_local_repo_root()function and all callers; removebash/andpowershell/branch logic fromcopy_specify_base_from_local()andcopy_specify_base_from_package()
Test changes:
- Delete
tests/specify_cli/cli/commands/test_init_doctrine.py - In
test_init_command.py: remove test cases for all removed flags - In
test_m_2_0_1_tool_config_key_rename.py: update fixtures containingpreferred_implementerkey
Acceptance gate:
spec-kitty init --helpshows:--ai,--non-interactive,--no-git(≤4 option flags)spec-kitty init --non-interactive --ai claudeexits 0, no prompts, no errormypy --strict src/specify_cli/cli/commands/init.py src/specify_cli/template/manager.py→ cleanpytestfull suite → green
WP03 — Migration A: Strip selection Keys from config.yaml
Lane: B · Position: 1 of 2 · Depends on: nothing
Files:
src/specify_cli/upgrade/migrations/m_3_2_1_strip_selection_config.py(NEW)tests/upgrade/migrations/test_m_3_2_1_strip_selection_config.py(NEW)
Migration spec:
migration_id = "3.2.1_strip_selection_config"
target_version = "3.2.1" # WP04 uses 3.2.2 — no collision
description = "Remove agents.selection.preferred_implementer/reviewer from config.yaml"
detect():
return config.yaml exists and (agents.selection or tools.selection) is present
apply():
load YAML preserving quotes and comments
if data.agents.selection exists: del data["agents"]["selection"]
if data.tools.selection exists: del data["tools"]["selection"] # handles post-m_2_0_1 projects
write back
dry_run: report keys would be removed, no write
Test cases:
- Project with
agents.selection.preferred_implementer→ key removed, other config unchanged - Project with
tools.selection(post-rename migration) → key removed - Project with no
selectionkey → detect() = False, no changes - Partial config (selection present but empty) → no-op, no crash
- Dry-run → no file write, correct reporting
Acceptance gate:
mypy --stricton migration file → clean- All 5 test cases green
WP04 — Migration B: New Safe Local Command Removal (m_3_2_2)
Lane: B · Position: 2 of 2 · Depends on: nothing (sequential for lane order only)
⚠ Do NOT modify m_3_1_2_globalize_commands.py. The migration runner tracks applied migrations by migration_id. Projects that already ran "3.1.2_globalize_commands" will never re-execute changes to that file. A new migration file with a new ID is required.
Files:
src/specify_cli/upgrade/migrations/m_3_2_2_safe_globalize_commands.py(NEW)tests/upgrade/migrations/test_m_3_2_2_safe_globalize_commands.py(NEW)m_3_1_2_globalize_commands.py— do not touch
New migration spec (m_3_2_2_safe_globalize_commands.py):
migration_id = "3.2.2_safe_globalize_commands"
target_version = "3.2.2"
description = "Safe removal of per-project spec-kitty command files with global-presence checks"
detect(): same as 3.1.2 — returns True if any spec-kitty.* file exists in any configured agent command dir.
apply(): iterate configured agents with four safety invariants per-agent.
Safety invariants (must all pass per-agent before any deletion):
1. Global runtime present: Path.home() / ".kittify" / "missions" exists and contains at least one subdirectory 2. Global agent commands present: Path.home() / agent_root / subdir exists and contains at least one spec-kitty.* file 3. File is spec-kitty-generated: the local target file's first line starts with <!-- spec-kitty-command-version:
Invariant failure behavior: skip entire agent, append f"Skipped {agent_root}: {reason}" to changes. Do not raise; return MigrationResult(success=True, changes_made=changes) — the migration is advisory, not blocking.
Remove: the best-effort agent_dir.rmdir() empty-directory cleanup — too aggressive.
New test cases:
- Global runtime absent (
~/.kittify/missions/missing) → all agents skipped, warning in changes - Global commands absent for agent
claudebut present forcodex→ claude skipped, codex cleaned - Local file lacks version header → that file skipped, sibling files with header removed
- Dry-run with all invariants met → "Would remove:" entries, no filesystem changes
- Mixed project (2 agents safe, 1 not) → correct per-agent independent behavior
- Existing passing tests continue to pass
Acceptance gate:
mypy --stricton migration file → clean- All new + existing migration tests green
- Manual verification: running migration on a project with no global runtime produces zero deletions
WP05 — Author 7 ADRs
Lane: C · Position: 1 of 2 · Depends on: nothing
Output directory: architecture/adrs/ Naming: 2026-04-08-{N}-{slug}.md where N continues the existing sequence
Use architecture/adr-template.md as structure. Reference 2026-01-23-6-config-driven-agent-management.md as quality bar for depth and cross-referencing.
| ADR | Slug | Core content |
|---|---|---|
| ADR-A | global-kittify-machine-level-runtime | init bootstraps ~/.kittify/; per-project .kittify/ is a thin overlay; what belongs at each level |
| ADR-B | package-bundled-templates-sole-source | remote download removed; --template-root CLI flag removed; SPEC_KITTY_TEMPLATE_ROOT env var remains for maintainers; no network at init time |
| ADR-C | global-skill-installation-per-project-symlinks | skills canonical in ~/.kittify/agent-skills/; symlink-first fallback to copy; per-project wiring is a future concern |
| ADR-D | charter-doctrine-not-init-time | charter belongs in /spec-kitty.charter post-specify; init is machine-setup, not project-governance |
| ADR-E | shim-generation-supersedes-script-dispatch | .sh/.ps1 dead; shim generator is canonical; --script flag safe to remove |
| ADR-F | global-agent-commands-supersede-project-copies | m_3_1_2 hardened with 4 safety invariants; version marker as generated-file fingerprint |
| ADR-G | preferred-agent-roles-removed-unused-concept | AgentSelectionConfig collected data no runtime code ever consumed; deleted in full; cleanup migration |
Each ADR must include:
- Status: Accepted
- Date: 2026-04-08
- Deciders: spec-kitty core team
- Context and problem statement
- Decision drivers
- Decision outcome with positive/negative consequences
- At least 2 alternatives considered with rejection rationale
- Cross-references to spec and related ADRs
Acceptance gate:
- 7 new files present in
architecture/adrs/ - Each file follows the template structure completely
- No
[placeholder]text remaining
WP06 — Documentation Updates
Lane: C · Position: 2 of 2 · Depends on: nothing (sequential for lane order only)
Files and changes:
| File | Required changes |
|---|---|
docs/how-to/non-interactive-init.md | Remove --preferred-implementer, --preferred-reviewer, --script from all examples, tables, and prose |
docs/reference/cli-commands.md | Remove the 11 removed flags from the init command reference table |
docs/reference/configuration.md | Remove selection.preferred_implementer and selection.preferred_reviewer from the agents.selection block; remove the selection subsection entirely if nothing remains |
docs/how-to/manage-agents.md | Remove preference field references (~lines 200–201) |
docs/2x/model-discipline-routing.md | Remove the reference to static preferred_implementer/preferred_reviewer config (~line 19) |
Acceptance gate:
grep -r "preferred_implementer\|preferred_reviewer\|--preferred\|--script " docs/→ zero results- All 5 files render valid markdown (no broken table rows or dangling references)
Test Strategy for Redesigned Init (WP02)
The deleted test_init_doctrine.py and removed flag tests must be replaced with tests covering the new positive flow. Required test cases by FR:
| Test case | FR |
|---|---|
init with no args creates project in current dir | FR-001 |
init my-project creates subdirectory | FR-002 |
init when ~/.kittify/ absent → ensure_runtime() called, global runtime created | FR-003 |
init when ensure_runtime() raises → exits 1 with error message (not silent) | FR-003 |
| Interactive agent selection shows existing global agents | FR-004 |
--ai claude,codex selects without prompt | FR-008 |
--non-interactive --ai claude exits 0, no stdin required | FR-009 |
--no-git skips git init | FR-010 |
.gitignore contains all agent dir entries after init | FR-011 |
.claudeignore exists after init | FR-012 |
.kittify/metadata.yaml exists with version/timestamp | FR-013 |
.kittify/config.yaml has no selection block | FR-014 |
| Re-running init on already-initialized project → zero file changes | FR-016 |
Global skills are installed to ~/.kittify/agent-skills/ after init | FR-007 |
Post-Merge Verification Checklist
- □
spec-kitty init --helplists ≤4 option flags (--ai,--non-interactive,--no-git,--help) - □
grep -r "AgentSelectionConfig\|select_implementer\|select_reviewer\|preferred_implementer\|preferred_reviewer" src/→ 0 results - □
grep -r "preferred_implementer\|preferred_reviewer" docs/→ 0 results - □
architecture/adrs/has 7 new files dated 2026-04-08 - □
src/specify_cli/template/github_client.pydoes not exist - □
tests/specify_cli/cli/commands/test_init_doctrine.pydoes not exist - □
src/specify_cli/upgrade/migrations/m_3_2_2_safe_globalize_commands.pyexists (WP04 new file) - □
m_3_1_2_globalize_commands.pyis unchanged (WP04 must not touch it) - □
spec-kitty init --non-interactive --ai claudeexits 0 - □
~/.kittify/agent-skills/is populated after init (skills installed globally) - □
load_agent_config()returns correct agents for bothagents-keyed andtools-keyed config - □
spec-kitty upgradeon a project with no global runtime →m_3_2_2skips all deletions - □
.kittify/config.yamlwritten by new init has noselectionblock - □
pytestfull suite exits 0 - □
mypy --strict src/exits 0
Branch Contract (repeated per protocol)
- Planning base:
main - Merge target:
main - All WPs branch from
mainand merge back tomain - Merge order: Lane C → Lane B → Lane A