Repository navigation
[quality] npm run check decides its membership by regex, and a multi-segment check: gate silently falls out #1281
Description
Finding
npm run check does not name its members. package.json:
"check": "npm run seq -- $(npm run -s _list:check:*)",
"_list:check:*": "npm run --loglevel=warn | grep -Ee '^\\s*check:[^:]+$'",Membership is decided by a regular expression over script names, at runtime,
in a subshell. The one thing that regex cannot match is a name with a second
colon.
That is a live trap rather than a theoretical one, because the repository
already names things that way: test:unit:coverage, validate:metrics,
_check:markdown:all, _check:format:delta. A gate added as
check:links:external would be a check:* gate by every other measure in the
suite — tests/workflow-scripts.test.mjs:423 counts it with
/^(?:validate|check):/ and demands that a workflow run it or that the
omission be recorded in GATES_NOT_RUN_BY_CI — while never running under
npm run check. Nothing fails. The aggregate just quietly stops being one.
The blast radius is the two things npm run check backs:
"test": "npm run check && npm run test:unit", the command a contributor
runs before pushing;CONTRIBUTING.md:208, which tells contributors to treatnpm run checkas a
superset of the CI checks.
No test reads the expansion. tests/workflow-scripts.test.mjs:96-107
explicitly skips it (if (target.includes('*')) continue; — "a literal glob is
not a script name"), which is correct for that file's purpose and leaves the
aggregate unexamined by anything.
Evidence and provenance
- Repository revision
365e11f(main), clean checkout,npm ci,
node v26.10.0. - This is not a defect today, and the issue does not claim one. The real
expansion, read fromnpm run -s _list:check:*, ischeck:audit,
check:format,check:links,check:markdown,check:spelling,
check:community-group-links— every declaredcheck:*script, all
single-segment.CONTRIBUTING.md:208-220documents that membership
deliberately, including thatcheck:community-group-linksrewrites
data/community-groups.json. The gap is that nothing holds it there. - Unit.
npm run test:unit:coverage(TZ=UTC) run locally 2026-10-10:
src files | 100.00 | 100.00 | 9015/9015 lines | 2713/2713 regions,
harness files | 100.00 | 99.19 | 3890/3890 lines | 1467/1479 regions.
Everyscripts/**andsrc/**file is at 100% lines and regions, so this is
filed as test-infrastructure, notcoverage-gap: nothing here is
uncovered code, it is an unasserted contract over a shell expansion. - End-to-end. Not applicable and not claimed:
collectE2ECoverage
attributes only paths undersrc/, so the Playwright run never observes
package.json.
Recommendation
One deliverable, one new test file: read the real expansion and hold it to the
declared gates, both directions.
- Every
check:*scriptpackage.jsondeclares appears in the expansion,
or is recorded in aGATES_NOT_IN_CHECKmap with a reason — the same
conventionGATES_NOT_RUN_BY_CIalready uses, with the same staleness
guard so an entry cannot outlive its fault. - Everything the expansion lists is a declared
check:*script, so a name
that merely looks like a gate cannot be run by the aggregate while every
gate guard in the suite ignores it. - A non-vacuity guard: the expansion must still be several gates including
the lint trio, andscripts.checkmust still be the_list:check:*
form this file reads.
The guards read the expansion out of npm rather than re-implementing the grep,
so they are evidence about what npm run check does rather than a second copy
that can agree with itself while disagreeing with the shell. Only the
enumerating helper runs — no gate, no network, no file written.
Green at 365e11f with no change to package.json, and mutation-checked:
adding a check:links:external script fails the membership rule by name.
Nothing here is under .github/workflows/. No open pull request touches
tests/workflow-scripts.test.mjs; #1250 touches package.json, which this
adds nothing to and only reads.
Priority
- Impact: medium (the pre-push command every contributor is pointed at decides
what it runs by a regex nothing tests, and the failure mode is silent
omission rather than an error) - Effort: low
Filed by quality agent (hold-gated mode)
🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5n31 | SHA: f72490a
— hive: agent=quality backend=copilot model=claude-opus-5 copilot=1.0.88
Activity