# Audit

> Run diff-scoped mutation analysis and report surviving mutants, the code under test is restored and the restoration verified, tracked source is either byte-identical at the end or the run fails naming what it could not restore, and no test is written by this skill. Generates at most one mutant per changed line, executes the covering tests, then delegates the productive-versus-arid-versus-equivalent judgment to a fresh-context reviewer before reporting; ranks files by oracle gap and hands survivors to the test-authoring lane. Use when: the user asks to run mutation testing or wants a mutation score for a change ('run mutation testing'), doubts a suite whose coverage report looks healthy ('my coverage is high but I do not trust it'), asks whether the tests actually check the code, asks to audit test quality, or asks for the survivors persisted for the fix pass; after tests go green and before review. Flags: `--full` (whole configured scope, not the diff), `--paths <globs>`, `--max <n>`, `--no-suppress` (report

- Skill: `melodic-software/audit-13` (Agent Skill, multi-file: 6 files)
- Install (CLI): `npx skillmds@latest add melodic-software/audit-13`
- Raw SKILL.md: https://api.skillmd.com/api/skills/melodic-software/audit-13/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: melodic-software (https://skillmd.com/u/melodic-software)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/melodic-software/audit-13

---


## Repository context. Gather first

Collect these with **individual** Bash calls, one command per call, never combined into a single
invocation:

- Current branch, `git branch --show-current`
- Working tree status (empty = clean), `git status --porcelain | head -20`
- Changed files vs HEAD, `git diff --name-only HEAD`

The pipe is the bound and belongs in the command. A read-time cap ("read only the first 20 entries")
bounds nothing: the Bash tool returns the command's complete output into context before there is
anything to decide about.

Treat a failure (not a repository, git unavailable) as an unknown value and carry on. Keep these as
separate body Bash calls rather than pre-compute lines: the harness runs a skill's whole pre-compute
block as one shell invocation, and a worktree-isolated session refuses a compound command that
contains git.

## Variables

Arguments: `$ARGUMENTS`

## Argument parsing

- **Scope** (optional): a path limiting which changed files are considered. Default: every changed
  file inside the configured `mutate` globs.
- **`--full`**: mutate the whole configured scope instead of the diff. Expensive and rarely correct,
  state the estimated cost from `baseline-suite-ms` and confirm before running.
- **`--paths <globs>`**: mutate these paths regardless of the diff.
- **`--max <n>`**: cap generated mutants for this run, overriding `max-mutants`.
- **`--no-suppress`**: include mutants that the arid-node record would otherwise suppress, marked as
  suppressed. Read-only inspection of the suppression policy; it never edits the record.
- **`--persist-findings`**: after reporting, also write the survivors as a findings file the
  `review:fanout` `fix` action consumes ([Phase 6](#phase-6--persist-opt-in)). Off by default.

### Effort, the mutant cap of last resort

Caller effort for this run is `${CLAUDE_EFFORT}`. If that reads as a literal placeholder rather than
one of `low`, `medium`, `high`, `xhigh`, or `max`, this body was read directly instead of
skill-loaded, so the substitution never ran: treat the run as `high` and leave the cap to the config.

Effort supplies a **default cap only when nothing else sets one**. The precedence is `--max` first,
then the configured `max-mutants`, then this table; effort never lowers a cap the caller or the
config chose, and never raises one:

| Effort | Cap when neither `--max` nor `max-mutants` is set |
|---|---|
| `low` | 5 mutants |
| `medium` | 15 mutants |
| `high`, `xhigh`, `max` | uncapped, the current behavior |

An effort-derived cap is a cap like any other, so Phase 1 step 5 already governs how it is reported:
say what was dropped, because a truncated run must never read as a clean one. Nothing downstream
moves. Phase 4 triage still runs in fresh context on every surviving mutant, at every effort level.

## The contract this skill holds

Three properties, stated first because everything below depends on them:

1. **Read-only with respect to tracked source.** A mutant is applied, measured, and reverted.
   Restoration is verified against the Phase 0 snapshot at the earliest point the configured write
   regime permits, so a run **either** ends with tracked source byte-identical to tracked source at
   the start **or** ends in failure naming what it could not restore, never in a reported outcome
   over edited source. The first tracked path that cannot be confirmed restored is that failure: no
   later phase runs and nothing is persisted ([Phase 3](#phase-3--execute)). Per the
   naming doctrine's verb contract, `audit` reports and stops, and bare invocation does exactly
   that. `--persist-findings` is the explicit user override that verb contract sanctions
   (the marketplace's `docs/PLUGIN-PHILOSOPHY.md` verb table). Its writes, the findings file and
   the self-ignore guard's own `.gitignore` when a governing checkout was found and the guard heals
   that root, are each **proven outside tracked space before that write is made**, never in tracked
   source and never in a file another producer owns.
2. **No tests are written here.** Survivors are handed to the test-authoring lane. This skill never
   both creates a gap and closes it.
3. **No verdict this skill produces is graded by the context that produced it.** See
   [Phase 4](#phase-4--triage-fresh-context).

## Phase 0 — Preflight

Refuse to proceed, with the specific remediation, when any of these fail:

- **Config missing** → `/mutation-testing:setup apply`.
- **Tool unavailable** → `/mutation-testing:setup check` names the install line.
- **Working tree dirty in a file about to be mutated** → stop. A mutation harness reverts by
  restoring a known state; uncommitted edits in the target make "revert" ambiguous and risk
  discarding the user's work. Ask them to commit or stash first. This is a hard stop, not a warning.
- **Baseline suite red** → stop and report the failure. A red suite kills every mutant and reports a
  perfect score. Invoke `/testing:diagnose` via the Skill tool when the `testing` plugin is installed; otherwise diagnose
  with the project's own test command before returning here.

Record the baseline run's result and wall-clock. Every later "killed" verdict is meaningful only
against a green baseline captured in this run, not against the one `setup` recorded, which may be
stale.

**Capture `git status --porcelain` here.** This is *the Phase 0 snapshot* every later restoration
check compares against, and the rest of this skill refers to it by that name. It is taken before the
first mutant is applied and never re-taken: a snapshot refreshed mid-run would absorb the very
difference it exists to detect.

**Resolve the write regime here too**, because it decides which restoration gate
[Phase 3](#phase-3--execute) can run: does the configured tool write mutants out of tree, rewrite the
working file whole once, or apply and revert it per mutant? Read the project's own config and the
installed tool version for it. Two rows of the `principles` skill's
[`tooling.md`](../principles/reference/tooling.md) table can land in tree: StrykerJS under
`inPlace: true`, and mutmut at 2.x or below, where the installed major version *is* the regime. The
rest are constants. Resolve it from what this project actually configured, never from the tool's
reputation.
State the resolved regime in the scope report. Refuse when the regime is in-tree per mutant and the
tool offers neither per-mutant observability nor interrupt safety: the gate that regime requires
cannot be run, and a check that cannot run is not a check.

## Phase 1 — Scope

1. Resolve the changed lines: `git diff --unified=0 <diff-target>...HEAD` for the files inside the
   configured `mutate` globs, intersected with any `--paths` or scope argument.
2. Drop lines with no test coverage, if a coverage report is available, a mutant on an uncovered
   line reports "no coverage", which the coverage report already said more cheaply.
3. Apply the arid-node suppression record per
   [`context/suppression.md`](context/suppression.md), which owns this plugin's read of the
   finding-suppression contract. Grade every layer that exists before applying anything, with
   `bash "${CLAUDE_PLUGIN_ROOT}/scripts/suppression-lint.sh" <record> [<record> ...]`: it prints
   `ok <finding_id>` or one of `malformed`, `mismatch`, `unknown-kind` per entry with what failed,
   and exits 0 when every entry passes, 1 when any fails, and 2 when a record cannot be graded at
   all. **Only an entry the lint reports `ok` suppresses.** Every other one carries the lint's own
   line into Phase 5's did-not-apply section, which is where the malformed and stale-key entries
   become visible rather than silently stopping suppressing. Three more things that are easy to get
   wrong and are not optional: a **personal-layer entry the team layer does not carry does not
   suppress** (this surface inverts the cascade default), and is reported
   `personal-only, not applied`; matching is by derived `finding_id`, never by bare file:line; and a
   layer the lint cannot grade is reported unreadable and the read continues, never failing whole.
   Under `--no-suppress` nothing is dropped and every entry that would have applied is marked as
   such.

   Entries are dispositioned **only when their anchored node is one this run generated a mutant
   for**. Inside the changed-line set from step 1, after the coverage drop in step 2. Anything else
   is *not-examined*: left untouched and counted, never resolved. Scope by node, not by file: a file
   with a suppressed survivor at line 100 and an unrelated edit at line 10 was "touched" but that
   node was never examined, and treating it as a disappearance would fail the skill's own self-check
   on nearly every run.
4. **Select the covering tests once, and cache the selection.** Test selection is fixed overhead per
   *target*, not per *mutant*; re-deriving it for each mutant is the difference between a run that
   finishes and one that does not.
5. Report the scope before running: files, changed lines, mutants to be generated, suppressed count,
   and the estimated wall-clock from `baseline-suite-ms × mutants`. If a cap truncates the set, say
   what was dropped, a truncated run must never read as a clean one.

## Phase 2 — Generate

**At most one mutant per changed line.** Not every operator at every location. The marginal value of
a second mutant on a line is near zero: if the line is unchecked, one mutant proves it.

Where the configured tool supports diff-scoped generation, use it. `--since`, `--incremental`,
`--git-diff-lines`. Where `tool: manual`, apply the single-operator protocol from the `principles`
skill's `tooling.md`: prefer statement/block removal, then relational-operator inversion.

## Phase 3 — Execute

For each mutant: apply, run the cached covering tests, record the state
(killed / survived / no-coverage / timeout / invalid), revert. **Where the mutant was written to
tracked source, verify the revert**, which regime below says when that is.

**This phase is a deterministic gate and is deliberately not delegated.** The tests' pass/fail *is*
the verdict; there is no judgment to bias and no independence to buy. Spending a subagent here would
be delegation cost with nothing bought, the narrow exemption the fresh-eyes rule states for
mechanical judgments.

Read [context/restoration-regimes.md](context/restoration-regimes.md) before the first mutant is
applied, and again before reporting a restoration as verified: it owns the comparison against the
Phase 0 snapshot rather than a clean tree, the three regimes that decide when that comparison can
run, and what a failed restore does to the rest of the run. Phases 5 and 6 exist only for a run
whose restoration was verified here.

## Phase 4 — Triage (fresh context)

Every surviving mutant is one of three things, and the difference is a judgment:

| Disposition | Meaning | Downstream |
|---|---|---|
| **Productive** | A genuine gap, the behavior is unchecked | Hand to the test-authoring lane |
| **Equivalent** | Semantically identical to the original; no test can kill it | **Not** a suppression, the check is wrong for that node |
| **Arid** | Killable, but killing it would not improve the suite | Propose a complete suppression entry. Its `claim` binding a node kind from the vocabulary, its `reason` naming the unasserted behavior. Without that, the verdict is *unclassified*, not arid |

**This judgment is delegated to a fresh-context (non-fork) subagent, mandatorily.** It is the
`self-grade` bias class: a context that generated the mutants and ran them is the weakest place to
decide whether its own findings are worth reporting, and a fork inherits that reasoning rather than
removing it. Hand over the artifact, the mutated line, its surrounding code, and the tests that
covered it, never the reasoning that produced the mutant.

For the **equivalence** call specifically, prefer a cross-vendor advisor when one is installed and
set up (invoked per its own documentation), falling back to the same-vendor fresh-context subagent.
Equivalence is formally undecidable, so the risk is a correlated blind spot rather than a lapse of
attention, and that is the case the top rung of the ladder exists for.

**Every verdict that WITHHOLDS a survivor must cite evidence, and a verdict that cannot is reported
as *unclassified*.** Equivalent and arid are the two that withhold, so the rule binds both. Asserting
either from inspection alone is exactly where this technique manufactures false confidence:

- **Equivalent** requires the demonstration: what was run, what was identical, and under which inputs.
- **Arid** requires a complete proposed suppression entry, all five keys, id derived from them,
  whose `claim` is `arid(kind=<node-kind>)` and whose `reason` names the behavior the suite
  deliberately does not assert on. **This context runs the lint over the proposal the subagent
  returned**, piping it in so nothing is written:
  `printf '%s\n' '<the proposed entry under a suppressions: mapping>' | bash "${CLAUDE_PLUGIN_ROOT}/scripts/suppression-lint.sh" -`.
  The check stays here rather than going out with the judgment because it is mechanical, the same
  exemption [Phase 3](#phase-3--execute) states: there is no independence to buy from a derivation
  that has one answer. The verdict is arid only when the lint reports `ok`; `malformed`, `mismatch`,
  or `unknown-kind` makes it *unclassified*. The lint reads the kind vocabulary at run time from the `principles`
  skill's [`scaling-and-suppression.md`](../principles/reference/scaling-and-suppression.md) "The
  node-kind vocabulary", which owns the rule that **a survivor fitting no node kind is not arid**.
  "Killing this would not improve the suite" is a conclusion, not the evidence for one. Aridity is
  the easier label to reach for, because its bar is otherwise a judgment about value rather than
  about observable behavior, the node-kind membership test is what makes it checkable rather than
  rhetorical, and running it is what makes it checked.

**This bar lives here, at classification, rather than at persist time, so one survivor has ONE
disposition.** Phase 5 reports and Phase 6 persists from the same classification, so an operator
reading the report and then the findings file cannot be shown "arid" in one and "unclassified" in the
other. It also means the bar binds a bare run, not only `--persist-findings`, the human-facing
report is exactly where an unevidenced withholding claim does its damage.

## Phase 5 — Report

Per file, ranked by **oracle gap**, not by score. The gap is defined once, in the `principles`
skill's [`${CLAUDE_PLUGIN_ROOT}/skills/principles/reference/metrics.md`](../principles/reference/metrics.md), and this skill does not restate it:

```text
oracle gap = mutation score − code coverage
```

A large **negative** gap is the bad direction. Exercised but not checked. So rank **ascending**,
most negative first. The top row is where the reader's belief about the suite is most wrong, which
is the only thing this metric is good for.

Write the report from [`templates/report.md`](templates/report.md). Read it at the start of Phase 5,
before assembling any section: it owns the heading order, the five result classes, what each row
must carry, and why the two suppression sections are obligations of the finding-suppression
contract rather than reporting garnish. Phase 6 persists whatever Phase 5 wrote, so a section
invented here is a section persisted.

Report the covered-code score as the headline and the plain mutation score beside it, the first
answers "are my tests weak", the second mixes that with "do I have tests at all".

Then stop, unless `--persist-findings` was passed. Remediation is delegated. This phase is reached
only by a run whose restoration Phase 3 verified; a failed restore ended it there.

## Phase 6 — Persist (opt-in)

Runs **only** under `--persist-findings`, and only on a run whose restoration Phase 3 verified.
Without the flag this phase does not exist and Phase 5 is the end of the run; without a verified
restoration there is no Phase 5 either, because the run already ended in failure. The flag is not the
only gate, and treating it as one is the defect: it decides whether *conforming* findings are
persisted, never whether the tree they describe still exists.

**This gate reads Phase 3's verdict; it never re-derives one.** The comparison against the Phase 0
snapshot has already run by the time this phase is reachable, so a run that persists over a failed
restore is not missing a check. It is declining to read one it already holds.

The flag exists because the survivors this skill detects are real findings with
no route to a remediation surface: writing one conforming file is that route, and it needs no wiring
on the consuming side, the `review:fanout` `fix` action locates its input by frontmatter, never by
provenance.

The mechanics are owned by [`context/persist-findings.md`](context/persist-findings.md), which reads
the detector-findings producer contract for this plugin. Six things there are easy to get wrong and
are not optional: the destination comes from the contract's **whole** rung order, taking its
**non-interactive collapse** for the rungs that confirm or ask, never a hardcoded default;
**each** write this phase makes, the findings file and the self-ignore guard's `.gitignore` where a
governing checkout was found, is proven outside tracked space before **that** write is made, against
the checkout that governs the destination rather than the invoking worktree, with the guard's own
write proven before the guard heals rather than reported afterwards, and with **nothing written at
all** where a resolved root has no governing checkout, the guard's create-when-absent rule could
land on a tracked-but-deleted `.gitignore` with no check having been possible, and the findings file
on a tracked deletion it would modify rather than create, while the contract's
`${CLAUDE_PLUGIN_DATA}` fallback is written normally, being outside every checkout by construction
(a memory root inside tracked space leaves `git status`
identical either way and so cannot detect itself, while a root outside the worktree is a layout the
consumer supports and a worktree-anchored probe could only ever refuse); the Phase 4 **verdict
class** selects the contract rule and the rule decides `Tier`, never the finding's prose, with
`Confidence: low` never emitted; every cell describes a mutant this run
actually executed, never an illustrative one; a run that examined mutants writes even when it found
nothing, while a run that examined **none** writes nothing at all; and an existing path is never
overwritten.

Persisting does not trade away the property in "The contract this skill holds": this phase is
reachable only from a run whose restoration verified, and a destination that cannot be proven
outside tracked space is not written to at all.

**This producer's remediation is off-site**, the missing assertion belongs in the covering test, not
at the mutated node the row's `Location` names. Every emitted row names that target in `Action`, and
the consumer surfaces such a row to a human rather than auto-applying it. The spoke records why
`Location` is never retargeted and no column is invented.

## Remediation. Delegated

- **Write the killing tests**. `/testing:write` when the `testing` plugin is installed, handed the
  survivor list. Otherwise report the survivors and let the user author tests with their project's
  own conventions; do not author them here.
- **Verify the new test actually kills the mutant**. Re-run this skill scoped to that file. This is
  the property that makes the loop trustworthy: the agent that wrote the test cannot grade itself
  into a pass, because the harness re-runs the mutant. A test that does not turn the mutant red has
  not closed the gap, however plausible it reads.
- **Record accepted arid mutants**. Append to `.claude/mutation-testing-arid.md` per
  [`context/suppression.md`](context/suppression.md). A complete entry carries all five required keys
  (`check`, `claim`, `sites`, `reason`, `date`) with the `finding_id` derived from the constituents,
  never hand-written: `bash "${CLAUDE_PLUGIN_ROOT}/scripts/suppression-lint.sh" <record>` over the
  record the user accepted reports the derived id for any entry whose key is wrong. The user accepts each entry; this skill proposes and never writes suppressions
  unprompted, and writes only the **team** layer, a personal-layer entry would not suppress anything.
  An **equivalent** mutant is never recorded here; the convention's record is not for a finding that
  is simply wrong.

## Next

- Survivors remain and the killing tests are due: `/testing:write`.
- A survivor is about to be called arid or equivalent: `/mutation-testing:principles`.

## Gotchas

Failure modes documented from the literature and from measurement, not anticipated in the abstract.
Each one produces a *plausible* result, which is what makes them worth listing.

- **A red baseline reports a perfect score.** Every mutant is "killed" by a test that was already
  failing. This is the most dangerous failure mode because the number looks excellent. Phase 0 stops
  on it; never skip that probe to save a suite run.
- **Flaky tests inflate the score by an unknown margin.** A flaky failure kills a mutant by accident.
  There is no correction factor. Either fix the flakes or report the score with the caveat attached.
- **A timeout counts as detected, and that is correct**, an infinite loop *is* a detected behavior
  change. But a score leaning heavily on timeouts is being carried by wall-clock rather than
  assertions; report the timeout share when it is large.
- **"No coverage" is not a weak test, it is an absent one.** Keeping it out of the headline number
  is the entire reason the covered-code score is the one reported.
- **A partially-completed run must report as partial.** Mutants that never ran are named as not-run,
  never counted as killed, never silently omitted. The same rule applies to a mutant set truncated
  by a cap. **A run cut short by a failed restore is not this case**. It reports failure, not a
  partial result ([Phase 3](#phase-3--execute)). A partial report describes a tree that is intact;
  that one is not.
- **Reaching for a withholding label is the standard way this technique manufactures false
  confidence.** "Equivalent" is the convenient explanation for any survivor whose test is hard to
  write. [Phase 4](#phase-4--triage-fresh-context) holds the bar for both withholding labels; do not
  soften it when a survivor is inconvenient.
- **A persisted findings file written to the wrong directory fails silently.** Nothing reports the
  miss: the run says it persisted, the file exists, and the consumer never scans that path. It is the
  failure mode of resolving only the documented default on a repo that configured its own memory
  root, which is why Phase 6 runs the whole rung order rather than its last rung.
- **A high mutation score is not a correctness argument.** The coupling effect covers faults composed
  of local errors. It says nothing about a wrong algorithm, a missing requirement, a concurrency
  interleaving, or an unexpressed security property.

## What this skill does NOT do

- Write or modify tests, or leave any mutation in the tree.
- Persist anything on bare invocation. The findings file is written only under `--persist-findings`,
  and only into a memory tier proven to sit outside tracked space.
- Apply its own findings, or read the consumer's consumption ledger. It writes one file and stops;
  what happens to that file belongs to the `fix` action.
- Write suppressions without the user accepting them.
- Fail a build on a score. There is no threshold to configure; see the `principles` skill's
  `scaling-and-suppression.md`.
- Answer test-design questions. Those belong to `/tdd:principles` when installed; otherwise use the
  project's own test-design guidance.

