Implementation Plan: Op Record Git Durability

Branch: main | Date: 2026-06-05 | Spec: spec.md Mission: op-records-git-durability-01KTB49K (mid8: 01KTB49K) Input: kitty-specs/op-records-git-durability-01KTB49K/spec.md


Branch Contract (repeated per workflow requirement)

  • Current branch at plan start: main
  • Planning/base branch: main
  • Final merge target: main
  • Branch matches target: yes

Summary

Move Op record storage from .kittify/events/profile-invocations/ (gitignored) to kitty-ops/ (git-tracked). Wire an auto-commit after every successful Op completion, guarded by an orphan invariant (started-but-never-completed Ops are never committed). Add mission_id and wp_id optional fields to InvocationRecord for timeline correlation. Fix the zero-persistence gap in do_cmd._build_executor. This is Step 1 of 3 from issue #1688; SaaS propagator replacement and module rename are out of scope.


Technical Context

Language/Version: Python 3.11+ Primary Dependencies: pydantic v2 (InvocationRecord model), python-ulid (ULID generation), subprocess/git (auto-commit), stdlib pathlib / json / datetime Storage: kitty-ops/ JSONL files on the local filesystem, git-tracked Testing: pytest (90%+ coverage on changed modules), mypy --strict, integration tests require git repo fixture Target Platform: Cross-platform (Linux, macOS, Windows 10+) Project Type: Single Python CLI package (specify_cli) Performance Goals: Op write + commit latency must not add visible delay beyond what safe_commit already takes for status commits Constraints: No .gitignore changes; no external dependencies added; InvocationRecord model must stay frozen (Pydantic v2 frozen=True)


Charter Check

Gate: must pass before Phase 0 research. Re-check after Phase 1 design.

CheckStatusNotes
DIRECTIVE_003: Material decisions documentedCommit strategy decision (direct git vs safe_commit) documented in research.md
DIRECTIVE_010: Implementation faithful to specPlan covers all 10 FR items
NFR: 90%+ test coverage for new code✅ Planned7 test scenarios mapped in data-model.md
NFR: mypy --strict passes✅ PlannedNew fields use `str \
No .gitignore changes (C-001)kitty-ops/ not currently listed; tracked by default
Step 2 / Step 3 out of scope (C-003, C-004)Propagator and module rename excluded from all work packages

Project Structure

Documentation (this feature)

kitty-specs/op-records-git-durability-01KTB49K/
├── plan.md              # This file
├── research.md          # Phase 0 findings
├── data-model.md        # Field contracts and test matrix
├── contracts/           # JSONL event schema, commit message contract
└── tasks.md             # Phase 2 output (/spec-kitty.tasks)

Source Code (affected files)

src/specify_cli/invocation/
├── writer.py            # EVENTS_DIR, INDEX_PATH constants + _append_to_index fix
├── lifecycle.py         # LIFECYCLE_LOG_RELATIVE_PATH constant
├── record.py            # InvocationRecord: add mission_id, wp_id fields + MVTP update
├── executor.py          # complete_invocation(): wire auto-commit after write_completed()
└── propagator.py        # PROPAGATION_ERRORS_PATH path (move to kitty-ops/)

src/specify_cli/cli/commands/
└── do_cmd.py            # _build_executor: no change needed (executor owns the commit)

src/specify_cli/doctor/
└── ops.py               # NEW: spec-kitty doctor ops orphan listing

tests/specify_cli/invocation/
├── test_writer.py       # Add: EVENTS_DIR resolves to kitty-ops/, index in kitty-ops/
├── test_executor.py     # Add: complete_invocation wires git commit; orphan not committed
├── test_record.py       # Add: mission_id/wp_id field presence, null for standalone
└── test_doctor_ops.py   # NEW: orphan listing via doctor ops

Key Implementation Decision: Commit Mechanism

safe_commit (in src/specify_cli/git/commit_helpers.py) requires a worktree_root parameter and refuses commits to protected branches (including main). Op commits must work from any invocation context — including standalone ask/advise/do on main. Therefore Op auto-commits use direct git subprocess calls rather than safe_commit. See research.md §Commit Strategy for full rationale.

The commit sequence:

git -C <repo_root> add -- kitty-ops/<op_id>.jsonl kitty-ops/ops-index.jsonl
git -C <repo_root> commit --no-verify -m "op(<profile_id>): <action> [<op_id[:8]>]"

--no-verify skips pre-commit hooks. This is intentional: Op JSONL files are audit-trail records, not source code, so hook enforcement (e.g. ruff, mypy) is not applicable. The same pattern is used by safe_commit for bookkeeping commits in lane worktrees.

This is consistent with how planning commands (specify, plan, tasks) commit their artifacts to main — they also use direct git, not safe_commit.


Complexity Tracking

ConcernWhy Acceptable
Direct git commit instead of safe_commitsafe_commit is a status mutation guard designed for lane-worktree bookkeeping commits. Op records are audit trail artifacts, not mission status — the same class as kitty-specs/ planning artifacts which also commit directly.

Phase 0 Output

See research.md — all technical unknowns resolved.

Phase 1 Output

See data-model.md and contracts/.


Branch Contract (final report)

  • Current branch: main
  • Planning/base branch: main
  • Final merge target for completed changes: main

Next step: /spec-kitty.tasks