Implementation Plan: Migration and Shim Ownership Rules

Branch: kitty/mission-migration-shim-ownership-rules-01KPDYDW | Date: 2026-04-19 | Spec: spec.md Input: kitty-specs/migration-shim-ownership-rules-01KPDYDW/spec.md


Summary

Write the compatibility-shim rulebook (architecture/2.x/06_migration_and_shim_rules.md), the machine-readable shim registry (architecture/2.x/shim-registry.yaml), and the CLI enforcement check (spec-kitty doctor shim-registry). Every downstream extraction mission (#612, #613, #614) will cite the rulebook and land a registry entry; CI will gate overdue shims via exit code. No live shim is added, modified, or removed by this mission.


Technical Context

Language/Version: Python 3.11+ Primary Dependencies: typer, rich, ruamel.yaml (YAML parsing), packaging (semver comparison — explicit dep, already a transitive dep), pytest, mypy --strict Storage: Filesystem only — architecture/2.x/shim-registry.yaml (YAML), architecture/2.x/06_migration_and_shim_rules.md (Markdown), Python shim modules read via AST Testing: pytest with 90%+ coverage on new code; mypy --strict; integration tests for the new spec-kitty doctor shim-registry CLI subcommand Target Platform: Cross-platform CLI (Linux, macOS, Windows 10+) Project Type: Single project (spec-kitty CLI monorepo) Performance Goals: spec-kitty doctor shim-registry completes in ≤2 s for ≤50 registry entries (NFR-001); registry schema pytest completes in ≤500 ms (NFR-002) Constraints: Read-only CI check (C-004); no live shim mutations (C-001); no version bump (C-008); spec-kitty doctor subcommand group must integrate cleanly with existing doctor commands (C-007)


Charter Check

Evaluated against .kittify/charter/charter.md v1.1.0

Charter ruleCompliance
Python 3.11+✅ — only baseline requirement
typer / rich / ruamel.yaml as primary deps✅ — all present; adding packaging as explicit dep (was transitive)
pytest 90%+ coverage for new code✅ — planned across all new modules
mypy --strict✅ — enforced in CI; all new code annotated
Integration tests for CLI commands✅ — tests/doctor/test_shim_registry.py covers spec-kitty doctor shim-registry
No version bump✅ — C-008 confirmed
Terminology: Mission / Work Package✅ — C-006 confirmed; no --feature flags introduced
ADR for material architectural decisions✅ — the rulebook itself documents the shim lifecycle pattern; no new ADR required (rule change is the deliverable, not infrastructure)

Gate result: PASS — no violations.


Project Structure

Documentation (this mission)

kitty-specs/migration-shim-ownership-rules-01KPDYDW/
├── spec.md
├── plan.md              ← this file
├── research.md          ← Phase 0 complete
├── data-model.md        ← Phase 1 complete
├── quickstart.md        ← Phase 1 complete
├── contracts/
│   ├── shim-registry-schema.yaml
│   ├── doctor-shim-registry-cli.md
│   └── README.md
└── tasks/               ← generated by /spec-kitty.tasks (not yet)

Source code layout

architecture/2.x/
├── 06_migration_and_shim_rules.md   # NEW — rulebook (FR-001, FR-002..FR-005, FR-012..FR-014)
└── shim-registry.yaml               # NEW — machine-readable registry (FR-006, FR-007, FR-008)

src/specify_cli/
├── compat/                          # NEW package — Python compat-shim infrastructure
│   ├── __init__.py
│   ├── registry.py                  # load/validate shim-registry.yaml (R3 design)
│   └── doctor.py                    # check_shim_registry() called by CLI (R1, R2, R6)
└── cli/commands/doctor.py           # ADD shim-registry subcommand (C-007)

tests/
├── architectural/
│   ├── conftest.py                  # existing — provides repo_root and pytestarch fixtures
│   ├── test_shim_registry_schema.py # NEW — FR-011: YAML schema validation
│   └── test_unregistered_shim_scanner.py  # NEW — FR-010: AST-based shim scanner
└── doctor/
    └── test_shim_registry.py        # NEW — FR-009: CLI integration (pending/overdue/grandfathered)

CHANGELOG.md                         # FR-015: Unreleased/Added entry

Structure decision: New src/specify_cli/compat/ package for compat-shim infrastructure. src/specify_cli/shims/ already exists but owns agent-skill shim routing — a separate domain. To avoid name collision and confusion, compat-shim registry logic lands in compat/. This also mirrors the doctor.py pattern of importing from domain packages (specify_cli.runtime.doctor, specify_cli.state.doctor, specify_cli.compat.doctor).

> Research correction (R3): The research document named src/specify_cli/architecture/shim_registry.py as the target. Discovery found that src/specify_cli/shims/ is already claimed by an unrelated domain. src/specify_cli/compat/ is the correct, conflict-free home.


Key Design Decisions

D1 — Semver comparator

Use packaging.version.Version (R1). Handles pre-release suffixes correctly (current project version is 3.2.0). Add packaging as an explicit pyproject.toml dependency if not already declared (it is a transitive dep today; the first WP confirms and adds it if needed).

D2 — Project version reader

Use stdlib tomllib to read [project].version from pyproject.toml at repo root (R2). Exit code 2 on missing file or missing key, distinct from exit code 1 (overdue shim).

D3 — Registry YAML validation

Manual validation in src/specify_cli/compat/registry.py using ruamel.yaml safe loader — no new validation framework (R3). Accumulate all errors before raising RegistrySchemaError. Schema contract documented in contracts/shim-registry-schema.yaml.

D4 — Shim file existence probe

For legacy_path = "specify_cli.charter", probe src/specify_cli/charter.py first, then src/specify_cli/charter/__init__.py (R6). First match = exists; neither = removed.

D5 — Unregistered-shim scanner (FR-010)

AST-based walk (no import — no DeprecationWarning side-effects) of all .py under src/specify_cli/. Detect module-level __deprecated__ = True. Assert every detected path is in the registry. Lives in tests/architectural/ alongside existing layer-rules tests.

D6 — Doctor subcommand integration

New @app.command(name="shim-registry") in src/specify_cli/cli/commands/doctor.py, importing from specify_cli.compat.doctor import check_shim_registry. Follows the exact same pattern as command-files, state-roots, identity, sparse-checkout subcommands (C-007).

D7 — Registry initial state

Current project version is 3.2.0. No modules in src/specify_cli/ carry __deprecated__ = True at mission start (confirmed by grep — zero results). The registry begins empty (shims: []). The FR-010 scanner test passes against an empty registry on day one; each downstream extraction mission adds its entry in its own PR.


Charter Check (Post-Design)

Re-evaluated after Phase 1 design:

AreaCheck
New packaging explicit dep✅ — defensive hardening; no incompatibility with existing deps
src/specify_cli/compat/ new package✅ — no circular import risk; pure read-only logic
Doctor subcommand is read-only (C-004)✅ — all writes are explicitly excluded from the implementation
architecture/ directory as home for rulebook + registry✅ — matches assumption A1; architecture/2.x/ already contains 05_ownership_map.md and 05_ownership_manifest.yaml
No new grandfathered entries after mission lands✅ — registry starts empty; FR-008 one-shot exception period is now open (future violations caught by a separate pytest)

Gate result: PASS — no conflicts introduced by Phase 1 design.


Complexity Tracking

No charter violations identified. Section left empty per template rules.


Risks and Mitigations (Premortem)

RiskLikelihoodMitigation
packaging not actually available as transitive dep in some install pathsLowWP01 explicitly checks and adds to pyproject.toml if absent
Future extraction PR adds shim without registering itMediumFR-010 scanner test catches it in CI before merge
removal_target_release semantics ambiguous for pre-release versions (e.g., 3.3.0a1)LowR1 documents: Version("3.3.0") >= Version("3.3.0a1") is True — stable release ships the shim out
Doctor subcommand breaks existing doctor command groupLowSubcommand is appended; existing commands are not modified; CI runs full test suite
Worked-example content in rulebook outdated before reviewLowRulebook cites mission slug + mid8; if #610 already removed specify_cli.charter, the example uses the "no-shim" baseline case (documented in R5)