Proof, with the commands
Three claims hold this product up. Each one is a real run against a named commit, each one followed by the command that reproduces it. Dated 2026-08-06.
Claim one
It was written from a pull request that went red
nishuzumi/moss PR #157,
2026-08-01. Eight vendored modules lived under a path containing dist/. The root
.gitignore excludes dist/ at any depth, so git dropped all eight from
the commit while they sat on disk untracked. Locally the suite was 26 tests green. In CI two
tests failed, reading files that had never been pushed.
The diff was innocent. The absence was the bug. Nothing in a diff shows you a file that is not there.
margyn /tmp/moss # reconstructed at c6cbb45, untracked files restored 2 findings, each with a reproduction you can run. 1. packages/protocols/aave/abis-src/dist/AaveV3Monad.mjs is read by packages/protocols/aave/README.md but git ignores it HIGH ignored-source ignore rule: .gitignore:2:dist/ 2. packages/protocols/aave/abis-src/dist/abis/IPool.mjs is read by packages/protocols/aave/abis-src/VENDOR.json but git ignores it HIGH ignored-source ignore rule: .gitignore:2:dist/
Both reproductions answer, which is what turns a finding into a fact. Two statements about one path that disagree:
$ git archive HEAD | tar -t | grep -qE '(^|/)<the path the reader asks for>$' || echo 'NOTHING in HEAD answers <path>' NOTHING in HEAD answers dist/abis/IPool.mjs $ test -f '<path>' && echo 'PRESENT on disk' PRESENT on disk
Against the fixed tree at 0c743c2 both high
findings are gone and only the two medium advisories remain. A checker that cannot be shown to
go quiet is as hollow as the checks it hunts, so that direction is tested too.
Claim two
What it says about five public repositories
Run on 2026-08-06 against a shallow clone of each, at the commit named. Nothing was tuned for these repositories and nothing was left out because the number was inconvenient. Re-derived on 2026-08-15 with 0.2.1, at the same five commits: every number below still holds, down to the file and line of each named finding.
| Repository | Commit | Findings |
|---|---|---|
| chalk/chalk | 661317e | 0 |
| sindresorhus/execa | 8017b27 | 0 |
| sindresorhus/got | e3924aa | 1 unrun gate |
| expressjs/express | a371447 | 3 unrun gates |
| fastify/fastify | 39e87e8 | 7 tests with no assertion, 2 that cannot fail, 4 unrun gates |
Reproduce any row in two commands. A clone is enough, since the static checks need no account:
git clone --depth 1 https://github.com/fastify/fastify.git /tmp/fastify npx margyn-scan /tmp/fastify
What a clean clone cannot show
ignored-source found nothing on any of the five. It could not
have. It reports a file that is on disk and not in the commit, which by construction cannot
exist in a fresh clone. That check fires on a working tree, which is where the moss defect
lived. Saying so is the difference between a table and a claim.
All six no-assertion findings, named
Each one is a file and a line anyone can open. These are tests whose only failure mode is an exception: they run the code, assert nothing, then report green whatever came back. The table above says six, so all six are here rather than a selection.
fastify/fastify at 39e87e8 test/decorator.test.js:869 plugin required decorators test/http2/closing.test.js:118 http/2 closes successfully with async await test/http2/closing.test.js:133 https/2 closes successfully with async await test/schema-special-usage.test.js:423 side effect on schema let the server crash test/schema-special-usage.test.js:469 only response schema trigger AJV pollution test/schema-special-usage.test.js:493 only response schema trigger AJV pollution #2
Two of those are worth reading before you judge the
check: an await close() with no assertion is a deliberate smoke test in a lot of
suites. That is why the finding says what it says rather than "cannot fail". It is reported
with the line instead of a count for the same reason.
The same run found seven false positives, so they were fixed
The first pass reported 17 on fastify, not 10. Seven of those were in
test/trust-proxy.test.js and every one of them was wrong: the tests declare
t.plan(11) then assert through a helper that takes the test context. A planned
count fails the test when it comes up short, so a body carrying one cannot be hollow.
Both rules are now in the check and both directions are tested. That is the whole reason the number above is 10. A scanner that cries wolf is hollow itself, so a false positive is a defect here rather than a tuning preference.
What fires this check, what does not · The release it landed in
Claim three
What it said about itself, and what we did about it
The mutation proof inverts one line, runs the suite, then reports the suite that stayed green anyway. Pointed at this repository on 2026-08-12 it reported four survivors of four tried at the default cap and seven at a cap of twelve. One of them was inside the mutation checker itself. That list is below, unedited.
bin/build-pages.mjs === -> !== suite still passed bin/contrast.mjs === -> !== suite still passed src/checks/ignored-source.mjs return true -> false suite still passed src/checks/lint-blindspots.mjs && -> || suite still passed src/checks/mutation.mjs return true -> false suite still passed src/checks/unrun-checks.mjs !== -> === suite still passed src/cli.mjs === -> !== suite still passed
Publishing that list was the easy half. On 2026-08-15 a
test was written for every line on it, plus src/prove.mjs, which had joined the
list. The suite went from 63 tests to 93. Two more source files shipped after that, so the run
was measured again on 2026-08-17 over everything now in scope:
$ every candidate file, cap 80
40 files tracked as source, 27 carry a mutation this tool knows how to make
27 mutated, 27 caught by the suite, 0 survivors, 208 seconds
108 tests, 0 failing
Three and a half minutes is the honest cost of the check. Every mutation runs the whole suite once, so 27 of them is 27 test runs. That is why the default cap is four and why this run has to be asked for.
Each of those tests pins the behaviour the mutation
changed, not the mutation. src/cli.mjs had no test at all, so its exit code, its
JSON and its locked-licence message are now checked by running the real binary.
worker/index.mjs got the one that mattered most: inverted, the entitlement branch
refuses a licence to every customer who has paid. Nothing was watching it.
The proof also found a defect that was not a missing test.
bin/build-pages.mjs ran its build as a side effect of being imported, so a test that
imported it rebuilt the whole site, and under a mutated mapping it wrote
web/public/.html and sent every page to the wrong file. The build is behind a
run-as-a-command guard now.
The paid gate is on the CLI flag rather than on the code, so a clone reproduces this without a licence:
git clone https://github.com/zkasuran/margyn && cd margyn && npm test
node --input-type=module -e 'import { mutationProof } from "./src/checks/mutation.mjs";
console.log(mutationProof(process.cwd(), { max: 60 }).map(f => f.summary));'
Zero survivors is a claim about this suite against these seven mutation operators, not a claim that the code is correct. A stronger operator set would find more, which is the honest reading of any mutation score.
The number this page does not have
An earlier run across five other repositories went from 132, 51, 20, 6 and 12 findings to 0, 2, 2, 2 and 0 after four precision fixes. It is a true story about how the matching rules were built. It is also the weakest number we have ever published, because those five repositories were never written down, so nobody can re-run it. That is why the table above exists and names every commit.
If you want the older number to mean something, the fix is not an argument, it is a list of five repositories and a date. Until then this page carries the run you can repeat.
Run it on your own repository
npx margyn-scan /path/to/repo
Nothing is uploaded and no account is needed. If it finds nothing, you learned that for free.