Implementation Plan: Codex .agents/skills manifest-drift idempotency
Branch: issue-702-codex-skills-drift | Date: 2026-10-12 | Spec: kitty-specs/codex-skills-drift-01M4RXXH/spec.md Input: Mission specification from kitty-specs/codex-skills-drift-01M4RXXH/spec.md (GitHub #702)
Summary
Make the DERIVED, git-ignored command-skill projection (.agents/skills/spec-kitty.<command>/SKILL.md) idempotent against its tracked manifest. The command-skill installer currently classifies a manifest-owned projection whose on-disk bytes match neither the recorded content_hash nor the current render as a drifted user edit (consent_required) and keeps it. Because the surface is DERIVED/IGNORED (never committed, always regenerable) and the drift is a legitimate cross-checkout/stale-output disagreement, the correct behaviour is to regenerate it to the canonical render during the install/upgrade/sync/init reconcile. Non-manifest / third-party paths stay untouched (installer NFR-002).
Technical Context
Language/Version: Python 3.11+ (repo runs 3.13 locally; CI matrix includes 3.11+). Primary Dependencies: internal — specify_cli.skills.command_installer, command_renderer, manifest_store; specify_cli.tool_surface.providers.command_skills; specify_cli.state.contract. Storage: filesystem — .agents/skills/ (ignored) + .kittify/command-skills-manifest.json (tracked). Testing: pytest; red-first ATDD through the real spec-kitty upgrade/init reconcile entry point, plus focused installer unit coverage. make test-fast + tests/specify_cli/skills/ + tests/agent/ + doctor/agent-config suites. Target Platform: Linux/macOS/Windows CLI. Project Type: single (CLI). Performance Goals: N/A — a reconcile touches O(commands × agents) files; no new hot path. Constraints: ruff + ruff format --check + mypy zero issues; complexity ≤ 15; no suppressions; change confined to the DERIVED command-skill surface (C-001). Scale/Scope: ≤ ~5 lines of behavioural change in command_installer.preserve() plus a state-contract-premise guard; test updates for the superseded policy; docs + CHANGELOG.
Charter Check
GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.
- Single canonical authority ✓ — the regenerate decision is grounded in the one authority for this surface's class,
state/contract.pyshared_skills_projection(DERIVED/IGNORED); a guard test pins that premise (C-003). - Architectural alignment ✓ — change stays inside the module that already owns all
.agents/skills/mutations; no new cross-module coupling, no shared-package-boundary impact. - DDD + tiered rigour ✓ — one bounded behaviour (install-time drift disposition) in one aggregate (the installer plan).
- ATDD-first ✓ — red-first regression through the real entry point precedes the fix.
- Terminology adherence ✓ — no new/retired terms; "command skill", "projection", "manifest", "disposition" are canonical.
- Smallest-viable-diff / locality-of-change ✓ — single decision branch flipped; consent plumbing left intact for the explicit-repair path.
No violations → Complexity Tracking empty.
Project Structure
Documentation (this mission)
kitty-specs/codex-skills-drift-01M4RXXH/
├── spec.md # mission spec
├── plan.md # this file
├── research/ # Phase 0 — diagnosis-702.md (reproduction + root cause)
├── analysis-report.md # cross-artifact consistency
├── tasks.md # Phase 2 (/spec-kitty.tasks)
└── tasks/WP01-*.md # the single work package
Source Code (repository root)
src/specify_cli/skills/
├── command_installer.py # EDIT: preserve() drift branch → regenerate for the DERIVED surface
├── command_renderer.py # (read only) canonical render
├── manifest_store.py # (read only) fingerprint + manifest I/O
└── verifier.py # (read only) drift/gap scanner used by doctor skills
src/specify_cli/state/contract.py # (read only) shared_skills_projection = DERIVED/IGNORED authority
tests/specify_cli/skills/
├── test_command_installer.py # UPDATE: tests pinning the superseded consent-on-drift policy
└── test_command_installer_derived_regeneration_702.py # NEW: red-first regression through the real reconcile
docs/ + docs/changelog/CHANGELOG.md # policy clarification + [Unreleased] entry
Structure Decision: Single CLI project. The behavioural edit is localized to src/specify_cli/skills/command_installer.py; the state contract is read to justify (and a guard pins) the DERIVED classification.
Complexity Tracking
No Charter Check violations — section intentionally empty.
Implementation Concern Map
IC-01 — Derived-surface drift disposition
- Purpose: Make a drifted, manifest-owned command-skill projection regenerate to the canonical render instead of landing in
consent_required, because the surface is DERIVED/IGNORED and always regenerable. - Relevant requirements: FR-001, NFR-001, NFR-003, C-003.
- Affected surfaces:
src/specify_cli/skills/command_installer.py(preserve()drift branch); premise guard againststate/contract.pyshared_skills_projection. - Sequencing/depends-on: none.
- Risks: Must not widen to the
existing is None(non-manifest) branch — that would clobber user content (FR-002/NFR-002). Must not regress the clean version-upgrade re-render path (on-disk == recorded hash, ≠ new render) which already works. The superseded policy is pinned by existing tests that must be updated with #702 justification (not deleted silently).
IC-02 — Documentation & verification clarity
- Purpose: State the generated/ignored policy, the idempotent-regeneration repair, and fresh-clone behaviour in docs + CHANGELOG, per #702's acceptance criteria.
- Relevant requirements: FR-003, SC-004.
- Affected surfaces: Codex / agent-skills docs;
docs/changelog/CHANGELOG.md[Unreleased]. - Sequencing/depends-on: IC-01 (describe the shipped behaviour).
- Risks: Terminology guard (
test_no_legacy_terminology.py) and docs-freshness index if a doc page/ADR is added.