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:
m_3_1_2_globalize_commands.pyis hardened in place so upgrades from versions before3.1.2get the safe behavior immediately.m_3_2_0a4_safe_globalize_commands.pyadds the same safety rules under a new migration ID for projects that already recorded3.1.2_globalize_commandsonmain.
Together, these migrations implement four safety invariants before any deletion:
Global runtime present: Checks that
~/.kittify/exists and is a valid runtime directory. If absent, the migration logs a warning and skips all deletions.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).
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.
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.2versions and projects that already recorded3.1.2onmainboth 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_2onmainand addm_3_2_0a4_safe_globalize_commandsfor already-upgraded projects (chosen) - Option B: Only add a later safe migration
- Option C: Force re-run of
m_3_1_2via 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_2andm_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_2behavior 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_2immutable. - Still provides a second pass for projects that already recorded
3.1.2_globalize_commands.
Cons:
- Projects upgrading from versions older than
3.1.2would still encounter the original unsafe behavior on the way to3.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 migrationsrc/specify_cli/upgrade/migrations/m_3_2_0a4_safe_globalize_commands.py— follow-up safe migration for projects already past3.1.2src/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