Margyn
Colour theme
Get Watch

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.

RepositoryCommitFindings
chalk/chalk661317e0
sindresorhus/execa8017b270
sindresorhus/gote3924aa1 unrun gate
expressjs/expressa3714473 unrun gates
fastify/fastify39e87e87 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.

Pricing · Documentation · Why there is no hosted scan