Contracts

lightweight-review-baseline.md

Contract: lightweight review missing-baseline diagnostic (issue #989)

Inputs

spec-kitty review --mission <handle> --mode lightweight

Mission state: meta.json has mission_id set (modern mission), baseline_merge_commit is null.

Output (failure path, modern mission)

  • Exit code: non-zero.
  • Verdict: non-passing.
  • Structured diagnostic emitted with code LIGHTWEIGHT_REVIEW_MISSING_BASELINE.
  • Payload fields:
  • code: "LIGHTWEIGHT_REVIEW_MISSING_BASELINE"
  • mission_id: canonical ULID
  • mission_slug: human slug
  • remediation: string containing the substring baseline_merge_commit

Output (legacy mission, no mission_id in meta.json)

  • Exit code: zero (preserves historical behavior).
  • Verdict: passing, but tagged with LEGACY_MISSION_DEAD_CODE_SKIP so the path is greppable and discoverable.

Output (modern mission, baseline_merge_commit populated)

  • Unchanged from today: dead-code scan runs normally.

Invariants

  • Modern missions with baseline_merge_commit: null MUST NOT exit zero with a clean pass.
  • The diagnostic code is referenced at least once in production source and once in tests so rg "LIGHTWEIGHT_REVIEW_MISSING_BASELINE" returns hits in both surfaces (NFR-004).

merge-dry-run-review-artifact.md

Contract: merge --dry-run review-artifact consistency gate (issue #991)

Inputs

spec-kitty merge --mission <handle> --dry-run [--json]

Mission state: at least one WP has lane approved AND its latest tasks/<WP>/review-cycle-N.md artifact has verdict: rejected (or otherwise fails find_rejected_review_artifact_conflicts()).

Output (failure path, --json)

  • Exit code: non-zero.
  • JSON payload includes REJECTED_REVIEW_ARTIFACT_CONFLICT under the same key the real merge uses (typically a blockers / errors array).
  • Each entry identifies the offending wp_id and the offending review-cycle file path.
  • Diagnostic payload shape MUST match the real merge's emission of the same conflict (consumers should not need to branch on dry-run vs. real-merge JSON).

Output (failure path, human / non-JSON)

  • Exit code: non-zero.
  • Human output contains a clearly-labeled REJECTED_REVIEW_ARTIFACT_CONFLICT line that names the offending WP.

Output (success path)

  • Unchanged from today: existing dry-run preview is emitted with no behavior change.

Invariants

  • Dry-run and real merge MUST share a single helper that calls find_rejected_review_artifact_conflicts().
  • Existing tests in tests/post_merge/test_review_artifact_consistency.py and tests/merge/test_merge_post_merge_invariant.py remain green.
  • The success path of dry-run produces no new false-positive blockers (spec C-002).

next-json-claimability.md

Contract: next --json claimability payload (issue #988)

Inputs

spec-kitty next --mission <handle> --json

Mission state: mission_state == "implement", preview_step == "implement", at least one WP in lane planned whose dependencies are satisfied.

Output (JSON, success path)

{
  "mission_state": "implement",
  "planned_wps": <int>,
  "preview_step": "implement",
  "wp_id": "WP##",          // REQUIRED: concrete WP that `agent action implement` would claim
  "selection_reason": null  // null when wp_id is set
  // ... existing fields unchanged
}

Output (JSON, no-candidate path)

{
  "mission_state": "implement",
  "planned_wps": <int>,
  "preview_step": "implement",
  "wp_id": null,
  "selection_reason": "no_planned_wps" | "all_wps_in_progress" | "dependencies_unsatisfied" | "baseline_violation"
  // ... existing fields unchanged
}

Invariants

  • wp_id != null iff selection_reason == null.
  • wp_id MUST equal the WP that an immediately-following spec-kitty agent action implement --mission <handle> --agent <name> would claim, given the same mission state.
  • Non-implement mission_state values keep their existing payload shape (wire shape is unchanged for them — see spec C-001).