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 pairOverlapRiskResolution
A ↔ BNoneNone
A ↔ CDoc examples reference init flags being removedLowKeep Lane A's code truth; Lane C prose framing wraps cleanly
B ↔ CNoneNone
AllCHANGELOG.mdLowKeep 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.py
  • tests/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 results
  • load_agent_config() returns correct available list for both agents-keyed and tools-keyed config files
  • mypy --strict src/specify_cli/core/agent_config.py → clean
  • pytest 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.py
  • src/specify_cli/template/github_client.py (DELETE)
  • src/specify_cli/template/manager.py
  • tests/specify_cli/cli/commands/test_init_doctrine.py (DELETE)
  • tests/agent/test_init_command.py
  • tests/upgrade/migrations/test_m_2_0_1_tool_config_key_rename.py

Flags to remove from init.py:

FlagApprox location
--hereline 740, usage 768–800
--script / --script-typeline 734, usage 982–992
--preferred-implementerline 735, usage 911–937
--preferred-reviewerline 736, usage 949–967
--forceline 741, usage 790
--template-rootline 750, usage 1007
--debugline 744, usage 1010–1024
--skip-tlsline 743, usage 1077
--github-tokenlines 745–748, usage 1160
--mission (hidden/deprecated)line 737, usage 995–1000
--ignore-agent-toolsline 738, usage 884–908

Stages to remove from init.py:

Stage / functionLocation
_resolve_preferred_agents() helperlines 650–671
Stage 3: preferred agent selectionlines 911–970
Stage 4: script type selectionlines 982–992
Stage 5: mission selectionlines 994–1005
Stage 9: _activate_mission() calllines 1199–1211
_apply_doctrine_defaults() definitionlines 392–429
_run_inline_interview() definitionlines 433–516
_run_doctrine_stack_init() definition + calllines 520–572, call 1261
_maybe_generate_structure_templates() definition + calllines 576–623, call 1260
ensure_dashboard_running import + callline 38 (import), line 1347
Initial git commit blocklines 1469–1490
Remote template mode branchlines 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: remove get_local_repo_root() function and all callers; remove bash/ and powershell/ branch logic from copy_specify_base_from_local() and copy_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 containing preferred_implementer key

Acceptance gate:

  • spec-kitty init --help shows: --ai, --non-interactive, --no-git (≤4 option flags)
  • spec-kitty init --non-interactive --ai claude exits 0, no prompts, no error
  • mypy --strict src/specify_cli/cli/commands/init.py src/specify_cli/template/manager.py → clean
  • pytest full 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 selection key → detect() = False, no changes
  • Partial config (selection present but empty) → no-op, no crash
  • Dry-run → no file write, correct reporting

Acceptance gate:

  • mypy --strict on 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.pydo 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 claude but present for codex → 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 --strict on 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.

ADRSlugCore content
ADR-Aglobal-kittify-machine-level-runtimeinit bootstraps ~/.kittify/; per-project .kittify/ is a thin overlay; what belongs at each level
ADR-Bpackage-bundled-templates-sole-sourceremote download removed; --template-root CLI flag removed; SPEC_KITTY_TEMPLATE_ROOT env var remains for maintainers; no network at init time
ADR-Cglobal-skill-installation-per-project-symlinksskills canonical in ~/.kittify/agent-skills/; symlink-first fallback to copy; per-project wiring is a future concern
ADR-Dcharter-doctrine-not-init-timecharter belongs in /spec-kitty.charter post-specify; init is machine-setup, not project-governance
ADR-Eshim-generation-supersedes-script-dispatch.sh/.ps1 dead; shim generator is canonical; --script flag safe to remove
ADR-Fglobal-agent-commands-supersede-project-copiesm_3_1_2 hardened with 4 safety invariants; version marker as generated-file fingerprint
ADR-Gpreferred-agent-roles-removed-unused-conceptAgentSelectionConfig 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:

FileRequired changes
docs/how-to/non-interactive-init.mdRemove --preferred-implementer, --preferred-reviewer, --script from all examples, tables, and prose
docs/reference/cli-commands.mdRemove the 11 removed flags from the init command reference table
docs/reference/configuration.mdRemove 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.mdRemove preference field references (~lines 200–201)
docs/2x/model-discipline-routing.mdRemove 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 caseFR
init with no args creates project in current dirFR-001
init my-project creates subdirectoryFR-002
init when ~/.kittify/ absent → ensure_runtime() called, global runtime createdFR-003
init when ensure_runtime() raises → exits 1 with error message (not silent)FR-003
Interactive agent selection shows existing global agentsFR-004
--ai claude,codex selects without promptFR-008
--non-interactive --ai claude exits 0, no stdin requiredFR-009
--no-git skips git initFR-010
.gitignore contains all agent dir entries after initFR-011
.claudeignore exists after initFR-012
.kittify/metadata.yaml exists with version/timestampFR-013
.kittify/config.yaml has no selection blockFR-014
Re-running init on already-initialized project → zero file changesFR-016
Global skills are installed to ~/.kittify/agent-skills/ after initFR-007

Post-Merge Verification Checklist

  • spec-kitty init --help lists ≤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.py does not exist
  • tests/specify_cli/cli/commands/test_init_doctrine.py does not exist
  • src/specify_cli/upgrade/migrations/m_3_2_2_safe_globalize_commands.py exists (WP04 new file)
  • m_3_1_2_globalize_commands.py is unchanged (WP04 must not touch it)
  • spec-kitty init --non-interactive --ai claude exits 0
  • ~/.kittify/agent-skills/ is populated after init (skills installed globally)
  • load_agent_config() returns correct agents for both agents-keyed and tools-keyed config
  • spec-kitty upgrade on a project with no global runtime → m_3_2_2 skips all deletions
  • .kittify/config.yaml written by new init has no selection block
  • pytest full suite exits 0
  • mypy --strict src/ exits 0

Branch Contract (repeated per protocol)

  • Planning base: main
  • Merge target: main
  • All WPs branch from main and merge back to main
  • Merge order: Lane C → Lane B → Lane A