Context and Problem Statement

When spec-kitty was a per-project tool, spec-kitty init wrote slash command files into each project's agent directories:

.claude/commands/spec-kitty.implement.md
.codex/prompts/spec-kitty.plan.md
.github/prompts/spec-kitty.specify.md

After the global runtime was introduced, spec-kitty also began installing these commands globally:

~/.kittify/agent-skills/claude/spec-kitty.implement.md
~/.claude/commands/spec-kitty.implement.md   (depending on agent and global config)

Projects that upgraded now have both local (per-project) and global command files. AI tools that index multiple command directories — including Claude Code, Codex, and GitHub Copilot — present all discovered commands to the user. A developer sees spec-kitty.implement twice in their slash command list. This is the double-prompt problem.

Migration 3.1.2 (Original Behavior — Unsafe)

m_3_1_2_globalize_commands.py was introduced to address the double-prompt problem. It removes local per-project command files by deleting .claude/commands/spec-kitty.*.md, .codex/prompts/spec-kitty.*.md, and so on.

Critical flaw: Migration 3.1.2 deletes local command files unconditionally — it performs no check for the presence of global equivalents. On a developer's machine where ~/.kittify/ was never created, the migration deletes local command files and leaves the developer with no slash commands at all. There is no recourse; the migration does not leave the local files as a fallback.

That original 3.1.2 behavior was correct enough to identify the desired cleanup, but unsafe for upgrade paths where the global runtime had not been bootstrapped yet.

Main-Branch Correction for 3.2.0a4 (This Feature — Safe)

On main, before the 3.2.0a4 pre-release ships, the command-globalization flow is corrected in two places:

  1. m_3_1_2_globalize_commands.py is hardened in place so upgrades from versions before 3.1.2 get the safe behavior immediately.
  2. m_3_2_0a4_safe_globalize_commands.py adds the same safety rules under a new migration ID for projects that already recorded 3.1.2_globalize_commands on main.

Together, these migrations implement four safety invariants before any deletion:

  1. Global runtime present: Checks that ~/.kittify/ exists and is a valid runtime directory. If absent, the migration logs a warning and skips all deletions.

  2. Global agent commands present: For each agent being processed, checks that at least one spec-kitty command file exists in the global command location for that agent. If the global equivalent is absent, that agent is skipped (its local files are preserved).

  3. Version marker check: Command files generated by spec-kitty contain a version marker on the first line:

    <!-- spec-kitty-command-version: 3.1.0 -->
    

    Any file without this marker is treated as user-authored and is never touched.

  4. Per-agent skip on failure: If any check fails for a given agent, that agent's local files are left intact. The migration does not fail globally; it reports which agents were processed and which were skipped.

Why Both 3.1.2 and 3.2.0a4 Exist

The next actual release line is 3.2.0a4, so main can still harden m_3_1_2 before that release ships. That covers upgrade chains coming from versions older than 3.1.2.

However, some projects already recorded 3.1.2_globalize_commands on main. Those projects would never re-run the hardened 3.1.2 body, because the migration runner tracks applied migrations by migration_id. m_3_2_0a4_safe_globalize_commands.py exists to give that population a safe second pass without mutating migration history.

Decision Drivers

  • Safety first: No user should lose all slash commands because a migration ran before the global runtime was bootstrapped.
  • Release-graph correctness: Projects upgrading from pre-3.1.2 versions and projects that already recorded 3.1.2 on main both need a safe path.
  • Version marker as fingerprint: User-authored command files must never be touched. Only spec-kitty-generated files can be removed.
  • Per-agent granularity: A failure or missing global state for one agent must not block processing of other agents.

Considered Options

  • Option A: Harden m_3_1_2 on main and add m_3_2_0a4_safe_globalize_commands for already-upgraded projects (chosen)
  • Option B: Only add a later safe migration
  • Option C: Force re-run of m_3_1_2 via a flag or migration log reset

Decision Outcome

Chosen option: Option A — Harden m_3_1_2_globalize_commands.py on main and add m_3_2_0a4_safe_globalize_commands.py for projects that already recorded 3.1.2_globalize_commands.

Safety Invariants in Detail

def apply(self, project_path: Path, dry_run: bool = False) -> MigrationResult:
    global_runtime = Path.home() / ".kittify"

    # Invariant 1: Global runtime must exist
    if not global_runtime.exists():
        return MigrationResult.skipped(
            "Global runtime (~/.kittify) not found. "
            "Run `spec-kitty init` first."
        )

    agent_dirs = get_agent_dirs_for_project(project_path)
    results = []

    for agent_root, subdir in agent_dirs:
        local_cmd_dir = project_path / agent_root / subdir

        # Invariant 2: Local directory must exist
        if not local_cmd_dir.exists():
            continue

        # Invariant 3: Per-agent: global commands must be present
        global_cmd_dir = global_runtime / "agent-commands" / agent_root.lstrip(".")
        if not global_cmd_dir.exists() or not any(global_cmd_dir.iterdir()):
            results.append(f"Skipped {agent_root}: no global commands found")
            continue

        for local_file in local_cmd_dir.glob("spec-kitty.*.md"):
            # Invariant 4: Version marker check
            first_line = local_file.read_text().splitlines()[0] if local_file.exists() else ""
            if "spec-kitty-command-version:" not in first_line:
                continue  # User-authored file, do not touch

            if not dry_run:
                local_file.unlink()

    return MigrationResult.success(results)

Consequences

Positive

  • Developers who have never run spec-kitty init (e.g., CI environments, new machines) do not lose their local command files.
  • User-authored command files in agent directories are never touched.
  • Per-agent granularity means a missing global config for one agent does not block cleanup of other agents.
  • The double-prompt problem is resolved for developers who have completed machine-level setup.

Negative

  • The existence of two safe command-globalization paths (m_3_1_2 and m_3_2_0a4) may confuse developers reading the migration log. The relationship between them must be documented in the migration modules.
  • Projects that already had local files deleted by the original unsafe m_3_1_2 behavior do not regain those files automatically. These users must rely on the global runtime for commands — which is the intended steady state.

Neutral

  • The version marker (<!-- spec-kitty-command-version: X.Y.Z -->) becomes a documented invariant for spec-kitty-generated command files. All future generated files must include it.

Confirmation

Correct behavior is confirmed when: running spec-kitty upgrade on a project without ~/.kittify/ leaves local command files intact and emits a warning; and running the same upgrade on a project with a valid global runtime removes local spec-kitty command files while leaving user-authored files untouched. Integration tests cover Scenario F and Scenario G from the spec.

Pros and Cons of the Options

Option A: Harden m_3_1_2 on main and add m_3_2_0a4 for already-upgraded projects (chosen)

Main-branch hardening plus a follow-up pre-release migration. Operates safely across both upgrade populations that matter for 3.2.0a4.

Pros:

  • Runs on all projects (new ID not in migration log).
  • Safety invariants prevent data loss.
  • Does not require migration log manipulation.

Cons:

  • Two migrations addressing the same concern appear in the migration log; requires documentation.

Option B: Only add a later safe migration

Leave m_3_1_2 untouched and rely solely on a later migration ID.

Pros:

  • Keeps m_3_1_2 immutable.
  • Still provides a second pass for projects that already recorded 3.1.2_globalize_commands.

Cons:

  • Projects upgrading from versions older than 3.1.2 would still encounter the original unsafe behavior on the way to 3.2.0a4.
  • The active upgrade path would remain wrong until a later migration repairs it.

Why Rejected: The next actual release is 3.2.0a4, so leaving the active 3.1.2 path unsafe would ship known-bad behavior to users upgrading from older versions.

Option C: Force re-run via a flag or migration log reset

Add a mechanism to force m_3_1_2 to re-run, or clear it from the migration log.

Pros:

  • Avoids creating a second migration for the same concern.

Cons:

  • Forcing re-execution of a migration that already ran and deleted files on some projects may delete files that were never supposed to be deleted (e.g., files created manually after the first run).
  • Migration log reset is a destructive operation. It voids the audit trail of what state transitions have occurred.
  • Adds complexity (a new "force" mechanism) to solve a problem that is solved cleanly by a new migration ID.

Why Rejected: Manipulating the migration log is a destructive operation with unpredictable side effects. A new migration ID is the designed mechanism for "do this again, but correctly".

More Information

  • Spec: kitty-specs/076-init-command-overhaul/spec.md — FR-021 (migration removes per-project command files when global equivalents are confirmed present), FR-022 (must never delete if global equivalent absent), FR-023 (must not touch non-spec-kitty files)
  • Related ADR: ADR-A (2026-04-08-1) — Global ~/.kittify/ as machine-level runtime (the global runtime whose presence is the prerequisite for safe deletion)
  • Related ADR: ADR-C (2026-04-08-3) — Global skill installation with per-project symlinks (root cause of why per-project copies exist)
  • Related ADR: ADR-6 (2026-01-23-6) — Config-driven agent management (the get_agent_dirs_for_project() helper used by the migration)
  • Code locations:
    • src/specify_cli/upgrade/migrations/m_3_1_2_globalize_commands.py — hardened active migration
    • src/specify_cli/upgrade/migrations/m_3_2_0a4_safe_globalize_commands.py — follow-up safe migration for projects already past 3.1.2
    • src/specify_cli/shims/generator.py:101 — version marker generation (referenced in invariant 4)
    • Spec: Scenario F and Scenario G in kitty-specs/076-init-command-overhaul/spec.md