Passante A Passante publication

En Passante

In Passing
Field Kit

The suite that ran 43 of 256 files

Any test that has to be hand-registered somewhere to run will eventually stop being registered; discover tests by glob so a file starts protecting you the moment it exists.

What happened

An audit dated 2026-07-13 found a test directory holding 256 files of which only 43 were named by any package script — 213 tests had never run anywhere, CI invoked 6 hand-listed scripts, and switching the runner to a glob surfaced 51 failures nobody had ever seen.

2026-07-13 Node · pnpm · a globbed test runner measured: test files on disk vs files any script actually runs
4 prompts, in order
  1. Count the tests that never run You suspect the suite is decorative but have no number for it.
  2. Wire the suite by glob You have the orphan count and want tests to run by existing, not by being remembered.
  3. Triage failures without rewriting assertions The glob just surfaced a pile of failures that have never been seen.
  4. Build a quarantine list that only shrinks You have known-broken tests you cannot fix today but refuse to hide.

1Count the tests that never run

Run it when
You suspect the suite is decorative but have no number for it.
It asks you for
A full listing of the test directory, every test-related entry in the package scripts, and every test step in the CI config.
You get back
Three counts — files on disk, files reachable from some script, orphans — plus the explicit orphan list and which script (if any) claims each reachable file.
It refuses if
It cannot see both the complete file listing and every script and CI invocation; it will not estimate a coverage gap from a sample.
copy this into a model
<stance>
You are auditing a test suite that has been green for a long time, and you have learned that a green suite proves only that the tests someone remembered to list passed. The persuasive wrong answer here is "the runner probably picks the rest up" — test runners with hand-enumerated file arguments do not pick anything up. You care about one number: how many test files exist that no command anywhere will ever execute. In the case that prompted this kit, that number was 213 out of 256.
</stance>

<inputs>
Ask for, and wait for, all of these before doing anything:
1. A complete recursive listing of the test directory, with file sizes.
2. The full scripts block of the package manifest, verbatim.
3. Every step in the CI config that runs tests, verbatim.
4. The naming convention that marks a file as an executable test versus a fixture or helper.
If any of the four is missing or partial, say which one and stop. Do not proceed on a directory listing that has been trimmed for brevity.
</inputs>

<method>

1. From the listing and the naming convention, build set A: every file that is an executable test. Exclude fixtures and helpers, and state explicitly which subdirectories you excluded and why.
2. From the scripts block and the CI steps, build set B: every file path any command would actually execute. Expand each script literally — if a script names files by hand, only those files are in B. If a script invokes another script, follow it.
3. Compute A minus B. That is the orphan set. Report its size and its size as a percentage of A.
4. For each file in B, note which script claims it. Files claimed twice are fine; note them anyway, because duplicate claims are how hand-lists get out of sync.
5. Report separately: how many CI steps run tests at all, and whether any CI step runs something broader than the hand-lists.
6. If a script uses a wildcard or shell expansion you cannot resolve from what you were given, say so per script rather than guessing what it matches.

</method>

<deliver>

- Counts: files on disk, files reachable, orphans, orphan percentage
- Orphan list, full, no truncation
- Claim map: reachable file to the script that runs it
- Unresolvable scripts, if any

</deliver>

<refuse>
Do not estimate. Do not say "roughly" or "most likely all of them run". If the listing is truncated, if a script's expansion is unknown, or if the naming convention was not given, stop and name the missing input. Do not offer to fix anything yet — this prompt produces a number, not a patch. Do not treat a coverage report as evidence that a file ran; a file that never executes produces no coverage rows and its absence is silent.
</refuse>

2Wire the suite by glob

Run it when
You have the orphan count and want tests to run by existing, not by being remembered.
It asks you for
The current runner invocation, the test naming convention, which subdirectories are fixtures, and the CI test steps to replace.
You get back
A runner script that discovers test files by glob, the package script entries that call it, the CI step change, and a printed file/test count on every run.
It refuses if
It cannot tell which files under the test directory are executable tests and which are fixtures or helpers.
copy this into a model
<stance>
You are replacing hand-maintained test lists with discovery. You have seen the failure mode this fixes: a suite where new tests were real and working and protected nothing, because adding a file and adding it to a script were two separate acts and only the first one happened. The seductive shortcut is to widen the existing hand-list to include the orphans you just found — that fixes today and rebuilds the same trap for tomorrow. The fix is that a test file runs the moment it exists.
</stance>

<inputs>
Ask for, and wait for:
1. The exact command each current test script runs, verbatim.
2. The filename pattern that identifies an executable test.
3. Every subdirectory under the test tree that holds fixtures, helpers, or data rather than tests.
4. The CI steps that currently invoke tests.
5. Whether any known-broken tests must be excluded from the main run (if yes, get the list).
If 2 or 3 is unclear, stop and ask — a glob that sweeps in fixtures produces noise that gets the whole change reverted.
</inputs>

<method>

1. Write a single runner script that globs the test tree for the naming pattern, excludes the declared fixture directories, excludes any declared quarantine list, and executes each match.
2. Make the runner print, before it starts, the number of files it discovered — and at the end, the number of tests and the number of failures. This printed count is what later gets reconciled against the directory listing.
3. Add one package script that runs the glob suite, and one that runs the excluded set on purpose so the broken ones stay visible rather than deleted.
4. Replace the hand-listed CI test steps with the single globbed script. Report how many steps you removed and what each one used to cover, so nothing quietly disappears.
5. Make the runner exit non-zero on any failure and on discovering zero files. A suite that finds nothing must fail loudly, not pass.
6. Run it and report the discovered file count and test count. If the discovered count does not match the on-disk count minus the exclusions, stop and reconcile before shipping.

</method>

<deliver>

- Runner script, complete
- Package script entries: main suite and excluded set
- CI diff: steps removed, step added
- First-run counts: files discovered, tests, failures
- Reconciliation: on-disk minus exclusions versus discovered

</deliver>

<refuse>
Do not extend the hand-written lists as a stopgap. Do not delete or rename failing files to get a clean first run. Do not exclude a directory the user did not declare as fixtures. If the counts do not reconcile, say so and stop rather than shipping a runner whose coverage you cannot state exactly. If the user asks you to make the runner tolerate failures so CI stays green, refuse and explain that a tolerant runner is the same as no runner.
</refuse>

3Triage failures without rewriting assertions

Run it when
The glob just surfaced a pile of failures that have never been seen.
It asks you for
The failure output, and for each failure the referenced path or identifier plus whether that target currently exists.
You get back
Failures sorted into stale-reference repairs (with verified new targets) and real assertion failures escalated as product decisions, never silently changed.
It refuses if
Asked to change an expected value to match current output, or to repoint a reference whose new target has not been verified to exist.
copy this into a model
<stance>
You are triaging failures from tests that were invisible for a long time, so most of them are not mysteries — they are casualties of renames and moves that nobody caught because nothing ran. In the case behind this kit, 51 failures surfaced at once: 22 came from a single component and library rename, 8 from a route directory being moved, and the rest were genuine. You know the tempting and wrong move: edit the expected value until the test passes. That is the moment a test starts lying, and a lying test is worse than the orphan it used to be.
</stance>

<inputs>
Ask for, and wait for:
1. The complete failure output, all failures, not a sample.
2. For each failure, the path, import, or identifier the test references.
3. Whether that target exists today, and if it was renamed or moved, the new location.
4. The commits or changes suspected of causing the breakage, if known.
If you do not have the referenced target for a failure, do not classify it — list it as unclassified and say what you need.
</inputs>

<method>

1. Sort every failure into one of three buckets: (a) stale reference — the test points at a path, component, or fixture that was renamed or moved; (b) genuine assertion failure — the target exists and the assertion is about behaviour or content; (c) unclassified.
2. For bucket (a), before proposing any repoint, verify the new target exists. Then verify that the specific thing the test asserts still holds at the new location. A path that resolves is not enough; the asserted marker must still be there. If it is not, the failure is bucket (b), not a repoint.
3. Group bucket (a) by cause — one rename, one move — and report the count per cause. Grouped counts tell the reviewer whether one change did the damage or many did.
4. For bucket (b), state what the test expects, what the code now produces, and whether choosing a new expectation requires knowing product intent. Where an asserted string exists nowhere in the codebase, picking its replacement is a product decision and must be escalated, not guessed.
5. Produce the bucket (a) repairs as a reviewable set. Leave bucket (b) failing.
6. For anything in bucket (c), name the missing evidence.

</method>

<deliver>

- Bucket counts: stale references, genuine failures, unclassified
- Stale-reference repairs, each with the verified new target and the marker checked
- Genuine failures, each with expected, actual, and the decision the human owes
- Unclassified failures with the evidence needed

</deliver>

<refuse>
Never edit an expectation to match current output. Never repoint a reference you have not confirmed exists. Never delete or skip a failing test to reduce the count. If the failure output is partial, say so and triage only what you were given, stating how many failures remain unseen. If a repair would require you to guess a user-facing string, stop and hand it back as a product decision.
</refuse>

4Build a quarantine list that only shrinks

Run it when
You have known-broken tests you cannot fix today but refuse to hide.
It asks you for
The list of tests to quarantine, a written reason per entry, and the CI config.
You get back
A quarantine manifest with a reason per entry, a script that runs them expected-red, and a CI ratchet that fails if the list grows.
It refuses if
Any proposed entry has no reason, or the request is to add a test that is failing because the code is wrong.
copy this into a model
<stance>
You are drawing the line between visible debt and silent rot. A deleted test is invisible. A skipped test with no note is invisible. A named list with a reason per line is work someone can pick up. You know the abuse pattern: quarantine becomes a dumping ground, grows every sprint, and within a year is the real suite. So the list is a ratchet — it may shrink, and adding to it is an admission on the record that a test is broken.
</stance>

<inputs>
Ask for, and wait for:
1. Each test file proposed for quarantine.
2. For each one, a written reason: what is broken and who or what decision unblocks it.
3. The current CI config, verbatim.
4. Confirmation of whether each entry fails because the test is stale or because the code under it is wrong.
If any entry arrives without a reason, do not add it. Say which entry and stop.
</inputs>

<method>

1. Write the manifest as data, not as comments in code — one entry per test, each with the file and the reason string. It must be readable by both the main runner (to exclude) and the quarantine runner (to include).
2. Wire the main suite to exclude exactly the manifest entries and nothing else, and to print how many it excluded on every run.
3. Add a second script that runs only the quarantined set. It is expected red. Its job is to show that the entries are still broken — and to catch the day one of them starts passing, which means it can leave the list.
4. Add a CI check comparing the manifest length against the committed baseline: fail if it grew. State the baseline number in the check so a reviewer sees it in the diff.
5. Verify the arithmetic: executable files on disk should equal the main suite's discovered count plus the manifest length. Print all three. If they do not add up, something is excluded that nobody declared.
6. For any entry where the code is wrong rather than the test, reject the quarantine and say the test is doing its job.

</method>

<deliver>

- Quarantine manifest, one entry per test, reason included
- Main suite exclusion wiring and its printed exclusion count
- Quarantine runner script, expected-red
- CI ratchet check with the baseline number stated
- Arithmetic: on-disk = discovered + quarantined

</deliver>

<refuse>
Do not accept an entry without a reason. Do not quarantine a test that is correctly reporting broken code — quarantining that is hiding a bug, and say so plainly. Do not implement the ratchet as a warning; it either fails the build or it is decoration. If the three counts do not reconcile, stop and report the gap rather than shipping a manifest that hides an unknown number of files.
</refuse>
How you know it worked

Make the counts reconcile out loud: the number of executable test files on disk must equal the file count the runner reports plus the length of the quarantine list. In the commit that was 234 executed + 22 quarantined = 256 on disk. If those three numbers do not add up, files are still invisible — and this check can come back negative, which is the point.

from a real incident, 2026-07-13 · stack Node · pnpm · a globbed test runner · verified by test files on disk vs files any script actually runs · testingcidead-coderefactoring

← every Field Kit