ADLC
Toolkit

hollow-test

Diff-scoped mutation gate. Mutates only the lines your change touched and fails if any mutant survives your test suite, which proves the coverage is load-bearing.

hollow-test

ADLC phase: P3 Rail (C4) · Gate: every changed line is constrained by an assertion, so no mutant survives.

The ADLC lifecycle, with P3 highlighted. Read the theory for P3

What it defends against

F5: Reward hacking (theory ↗)

Executed lines are not tested lines. A suite can run every changed statement and still assert nothing about its behavior. That is hollow coverage, and a reward-seeking model will happily produce it. hollow-test mutates only the lines in your diff (invert a comparison, flip a boolean, null a return), reruns the suite against each mutant, and fails if any survives. A survivor is proof the line's behavior is unconstrained. Diff-scoping keeps the run in seconds-to-minutes instead of the hours whole-codebase mutation testing costs.

Usage

adlc hollow-test --test-cmd "node --test test/" [--base <ref>] [--max <n>] [--timeout-ms <n>] [--target <file>] [--rails <ticket-file>] [--json]
FlagDescription
--test-cmd <cmd>(required) Shell command to run the suite; must exit non-zero on failure.
--base <ref>Git base ref for the diff. Defaults to the merge-base with a trunk; fails closed if none resolves.
--max <n>Max mutants across all files (default 20), spread round-robin.
--timeout-ms <n>Per-mutant test-command timeout (default 120000).
--target <file>Mutate this file whole, independent of the diff (repeatable). For rails-authoring / characterization tickets where the behavior file itself did not change.
--rails <ticket-file>Expand a ticket's declared rails globs against tracked files and mutate them (repeatable).
--jsonMachine-readable output for orchestrators.
--helpShow usage and exit 0.

Refuses to run on a dirty working tree, and requires a green baseline before mutating so a failing suite cannot masquerade as killing every mutant.

Exit codes

0: gate passes. Every generated mutant was killed by the suite.1: operational error. Dirty tree, not a git repo, bad arguments, non-green baseline, nothing eligible to mutate with no --target/--rails, or a selected file received no mutation budget (see below).2: gate fails. One or more mutants survived (hollow coverage).

--max too small for the diff: a starved file fails closed

--max spreads the mutant budget round-robin across every selected file, and a small --max against a wide diff can leave one or more diff-derived files with zero quota — never mutated at all. That file's changed code is then unverified, so hollow-test refuses to report a pass: it exits 1 and names every starved file on stderr rather than silently passing on whatever other files did get budget. Raise --max to cover every selected file (one per file changed, at minimum), or narrow the diff. This does not apply to a file that received budget but has no mutable line at all (a comment-only or import-only change) — that case still only warns, since the file genuinely has nothing to mutate rather than having been skipped for lack of budget.

Example

$ adlc hollow-test --test-cmd "node --test test/" --base main
src/calc.mjs:7  null-return   survived   return a + b;    return null;
hollow coverage 1 mutation(s) pass your tests
exit 2

Go deeper

On this page