Contracts
cli-do-output.md
Contract: spec-kitty do Output (open-Op dispatch)
Behavior
spec-kitty do "<request>" [--profile <id>] [--json]: 1. Routes request → profile/action (unchanged; routing failure → no Op, exit 1 with recovery text). 2. Loads governance context, writes started event only. No completed event is written by do under any outcome. 3. Propagates the started event to SaaS via the shared propagator (async, best-effort, sync-gated) — parity with ask/advise. 4. Prints capsule + close contract; exits 0.
Rich output (additions/changes)
`` This Op is OPEN. After completing the work, close it with the real outcome: spec-kitty profile-invocation complete --invocation-id <id> --outcome <done|failed|abandoned> [--evidence <file>] [--artifact <path>] [--commit <sha>] Unclosed Ops are reported by spec-kitty doctor ops and swept to 'abandoned' when stale. ``
- Governance capsule unchanged (profile, action, confidence, invocation id, glossary warnings, governance context).
- REMOVED:
Op record written — commit it: git add kitty-ops/<id>.jsonl - ADDED: close contract block:
JSON output (additions)
Existing payload fields preserved, plus:
{
"invocation_id": "01KT…",
"status": "open",
"close_contract": {
"command": "spec-kitty profile-invocation complete --invocation-id 01KT… --outcome <done|failed|abandoned>",
"outcomes": ["done", "failed", "abandoned"],
"evidence_flag": "--evidence",
"artifact_flag": "--artifact",
"commit_flag": "--commit"
}
}
evidence_flag is omitted when the Op's mode_of_work is advisory or query, because profile-invocation complete refuses --evidence for those modes (InvalidModeForEvidenceError, FR-009).
Close surface (informative summary)
> Normative source for record lifecycle and git behavior: op-record-events.md. This section is an informative summary for CLI consumers; on any divergence, op-record-events.md wins.
spec-kitty profile-invocation complete --invocation-id <id> --outcome <o> [--evidence …] [--artifact …]* [--commit <sha>]
- Writes
OpCompletedEventwithclosed_by="agent". - Idempotent: second close →
AlreadyClosedError, exit 1, structured error JSON in--jsonmode. - Auto-commits the Op record at close.
doctor-ops-close-stale.md
Contract: spec-kitty doctor ops Stale Sweep
Report mode (default, unchanged + extended)
spec-kitty doctor ops [--json] lists all open Ops (started, no completed event) with invocation_id, profile_id, started_at, and age. Exit 1 when any open Op exists (existing behavior).
Sweep mode (new)
spec-kitty doctor ops --close-stale [--threshold <hours>] [--json]
- Closes every open Op with
started_atolder than<hours>(default 24;--threshold 0closes all open Ops). - Each close goes through the canonical executor close path:
OpCompletedEventwithoutcome="abandoned",closed_by="doctor_sweep", followed by close-time auto-commit. - Ops younger than the threshold: reported, never closed.
- Race with concurrent manual close (
AlreadyClosedError): reported asalready_closed, sweep continues, not a failure. - Exit codes: 0 when sweep completes (even if some were already closed); 1 only on write/IO errors. When open-but-fresh Ops remain after sweep, report them and exit 1 (consistent with report mode).
JSON shape
{
"open_ops": [{"invocation_id": "01KT…", "profile_id": "…", "started_at": "…", "age_hours": 3.2, "action_taken": "none|closed_abandoned|already_closed"}],
"swept": 2,
"skipped_fresh": 1,
"threshold_hours": 24
}
Performance
Sweep completes in <5 s at 10,000 Op files (NFR-002); orphan detection uses the existing file-scan path (list_orphan_ops).
Session presence (Claude Code)
spec-kitty session-startoutput appends, when open Ops exist: count, ids with ages, and the close + sweep commands.- Stop hook entry (registrar-generalized) prints a non-blocking reminder listing open Ops; always exits 0.
op-record-events.md
Contract: Op Record Events (v2)
File: kitty-ops/<invocation_id>.jsonl — append-only JSONL, one event per line.
Started event
{"event":"started","invocation_id":"01KTK5JBD69FQ8XVRFV1J630MJ","profile_id":"implementer-iris","action":"implement","request_text":"fix that bug","actor":"claude","mode_of_work":"task_execution","governance_context_hash":"d5ccab5678dcc4c8","governance_context_available":true,"router_confidence":"canonical_verb","started_at":"2026-06-10T20:00:00+00:00"}
Rules: write-once (exclusive create); action non-empty; mission_id/wp_id present only when non-null.
Completed event
{"event":"completed","invocation_id":"01KTK5JBD69FQ8XVRFV1J630MJ","completed_at":"2026-06-10T20:25:00+00:00","outcome":"done","closed_by":"agent","evidence_ref":".kittify/evidence/01KTK5JBD69FQ8XVRFV1J630MJ"}
Rules: outcome required ∈ {done, failed, abandoned}; closed_by required ∈ {agent, doctor_sweep}; no started-only fields; appended at most once (AlreadyClosedError on repeat); evidence_ref omitted when None and refused for advisory/query modes.
Unchanged events
artifact_link, commit_link, glossary_checked retain their existing shapes.
Git behavior
- Open Op (started only): file remains uncommitted in working tree.
- At close: record auto-committed, message
op(<profile-id>): <action> [<id8>](grep-able viagit log --grep="^op("), including sweep closes.
SaaS propagation envelope
Envelope dicts are rebuilt from the v2 models; shape follows the events above 1:1 (decision 01KTSJEQANMNEV16WMSAJP6FR1 — no wire-compat with the pre-mission envelope). Propagation remains async, best-effort, gated by resolve_checkout_sync_routing(); errors append to kitty-ops/propagation-errors.jsonl.