Reference · schema_version 1.2

JSON report schema reference

mutineer run --format json emits a single JSON object (one line, newline-terminated) describing the whole run. It is the machine-readable contract for tooling — CI gates, dashboards, and AI coding agents.

Output is deterministic: arrays are sorted by (file, line, operator) regardless of --jobs worker finish order, so two runs of the same inputs produce byte-identical output.

Versioning contract

The top-level schema_version (a string, e.g. "1.2") follows these rules:

  • Additive changes (new keys on existing objects, new top-level keys) bump the minor version (1.01.1). Existing keys keep their meaning. Consumers MUST ignore unknown keys.
  • Breaking changes (renaming/removing a key, changing a value's type or meaning) bump the major version (1.x2.0).

A consumer should accept any 1.x document and read only the keys it knows.

Top-level shape

{
  "schema_version": "1.2",
  "summary":      { /* run totals, see below */ },
  "survivors":    [ /* mutants the suite failed to catch — the actionable gaps */ ],
  "no_coverage":  [ /* mutants on lines no test exercises */ ],
  "uncapturable": [ /* mutants whose would-be test errored during coverage capture */ ],
  "no_verdict":   [ /* attempted mutants that produced no verdict: errored, timeout, uncapturable */ ],
  "ignored":      [ /* mutants the user suppressed (equivalent mutants) */ ],
  "per_source":   [ /* per-file roll-up */ ],
  "baseline":     { /* present ONLY with --baseline: the delta vs a prior run */ }
}

summary (object)

KeyTypeMeaning
totalintTotal mutants generated (every status, before exclusions).
killedintMutants a test caught (suite went red).
survivedintMutants no test caught. These are the actionable test gaps.
no_coverageintMutants on a line no test exercises (excluded from score).
uncapturableintMutants whose covering test errored during capture — a broken harness, not a gap (excluded).
skipped_invalidintMutants that didn't re-parse and were never run (excluded).
erroredintMutants whose run raised (excluded).
timeoutintMutants whose run exceeded the per-mutant timeout (excluded).
ignoredintMutants suppressed via # mutineer:disable-line or .mutineer.yml ignore: (excluded).
attemptedintMutants actually run: killed + survived + no_verdict. Not total — no-coverage, skipped and ignored mutants were never attempted.
no_verdictintAttempted mutants that produced no verdict: errored + timeout + uncapturable. The completeness gate is no_verdict / attempted.
scorefloat | nullkilled / (killed + survived) * 100, rounded. null when the denominator is empty (no covered mutants) — never 0.0.

survivors[] (array of object)

Each surviving mutant — the records an agent or reviewer acts on:

KeyTypeMeaning
subjectstringFully-qualified subject, e.g. Calculator#add.
filestringSource file path (as passed to the run).
lineint1-based line of the mutation.
operatorstringOperator name, e.g. arithmetic, comparison.
idstringStable, offset-free id (12 hex chars). Survives edits elsewhere in the file. Paste into .mutineer.yml ignore:, or diff between runs (this is what --baseline matches on).
tokenstringThe exact code being mutated (whitespace-collapsed), e.g. a + b.
diffstringA unified diff (@@ -line +line @@ with -original / +mutant). Ready to hand to an agent as "write a test that fails under this change."

no_coverage[] and uncapturable[] (array of object)

Both use the lean shape { subject, file, line }. no_coverage is a genuine coverage gap; uncapturable means the test that should cover the line errored while capturing coverage (fix the harness, not the test).

no_verdict[] (array of object)

Every mutant that was attempted and produced no verdict: { subject, file, line, id, status, details }. status is "error", "timeout" or "uncapturable", and its length equals summary.no_verdict. details carries the cause for "error"; it is null for the other two, where the status is the whole story.

A failure before the mutant could be forked has no subject, so subject, file, line and id are null on that entry — id is therefore not a reliable join key here, unlike in survivors[]. These mutants are excluded from the score's denominator, so a broken harness raises the score rather than lowering it; that is why --threshold gates on completeness as well.

ignored[] (array of object)

Suppressed (equivalent) mutants, so you can audit what's silenced: { subject, file, line, operator, token, id }.

per_source[] (array of object)

Per-file roll-up: { file, total, killed, survived, no_coverage, score } (score is float | null as above).

baseline (object, only with --baseline)

The delta versus the prior --format json report, matched by stable id:

KeyTypeMeaning
regressedboolTrue if there are new survivors OR a score drop. Drives exit 1.
score_beforefloat | nullBaseline score.
score_afterfloat | nullThis run's score.
score_droppedboolTrue if score_after < score_before - epsilon.
new_survivors[]arraySurvivors present now but absent in the baseline: { subject, file, line, operator, token, id }.
fixed_survivors[]arrayBaseline survivors no longer present: { subject, file, line, operator, id }.

Exit codes

CodeMeaning
0Score ≥ threshold (or no gate) and no baseline regression.
1Score below --threshold, OR nothing could be scored and something broke, or more than one mutant produced no verdict and they exceed 10% of those attempted, OR a --baseline regression, OR a runtime error.
2Usage / invalid-flag error (mistyped flag, bad path, unreadable baseline).

--threshold and --baseline are independent gates OR'd together (the worse code wins); usage errors (2) always win. This lets a pipeline distinguish "tests too weak" (1) from "you invoked me wrong" (2).

See the agent & CI recipes for how to consume this in a loop or a PR gate.