Repository navigation
[quality] the unit coverage gate is aggregate-only: 23 source regions can go uncovered without failing it #1190
Description
Finding
npm run test:unit:coverage:check floors source region coverage in
aggregate and nowhere else:
test:unit:coverage:check = TZ=UTC node tests/tools/coverage-report.mjs \
--check 99 --check-source 100 --check-regions 95 --check-source-regions 99 \
--require-source-files
— package.json:70
--check-source-regions is cleared by the whole of scripts/ and src/ at
once, so the regions one file loses are paid for by every other file that
still has them. At the suite's current 2545/2547 source regions, a 99% floor
leaves 23 regions of slack, and nothing in the reporter stops a single
file from taking all of them: (2545 - 23) / 2547 = 99.02%, still above the
gate, and the src files row is the only thing the gate reads.
The companion line gate does not close it, and the reason is structural rather
than a question of how much slack it has. A lost region need not be a lost
line: an unexecuted ?? fallback or ternary arm sits on a line the
surrounding statement still covers, so the file keeps reporting 100% lines
while its region percentage falls. --check-source 100 is therefore green
across exactly the regression --check-source-regions averages away. That
shape is not hypothetical in this repository — the e2e report at the same
revision lists src/components/MemberDirectory/MemberProfile.js at
100% lines and 89.29% regions (report.json, run
37641752914).
--require-source-files is the only non-ratio guard the reporter has, and it
answers a different question: whether a file was measured at all, not whether
the file that was measured still covers what it used to.
Evidence and provenance
- Unit — measured.
npm run test:unit:coverage(TZ=UTC node tests/tools/coverage-report.mjs, node v26.10.0) run locally on 2026-10-08
at03cfcfe:
src files | 100.00 | 99.92 | 8984/8984 lines | 2545/2547 regions,
all files | 99.44 | 95.14 | 50047/50329 lines | 8278/8701 regions.
The per-file minimum among source rows is
scripts/lib/architecture-content.mjsat 97.78% regions; the next are
scripts/lib/svg-active-content.mjs99.52%, and every other source file is
at 100%. - E2E — not relevant to this finding. This is a property of the unit
lane's gate configuration rather than of a code path, so no end-to-end
coverage claim is made. The e2e figure quoted above is cited only as a
measured example of the 100%-lines/sub-100%-regions shape, from artifact
e2e-coverage(id11492367935) of run
37641752914,
workflowValidate repository, jobEnd-to-end coverage,merge_groupat
03cfcfe. - This is a
coverage-reportingfinding, not acoverage-gap: no source path
is claimed to be untested.
Recommendation
Add a per-file floor to tests/tools/coverage-report.mjs and pass it from the
gate script, so a regression concentrated in one file fails on that file's
name instead of being averaged away:
-
tests/tools/coverage-report.mjsaccepts
--check-source-file-regions <percent>and fails when any source file
with at least one region falls below it, naming the file, its ratio and
the start lines of its uncovered regions - the flag refuses to pass vacuously when the run recorded no source
region at all, the same guard--check-source-regionsalready carries -
package.json'stest:unit:coverage:checkpasses the new flag at 97,
floored from the 97.78% the worst source file measures today -
tests/coverage-gate-thresholds.test.mjsrecords the matching floor, so
lowering it later has to be argued for
tests/coverage-gate-thresholds.test.mjs:93 already asserts that every
--check* flag the reporter supports is passed by the gate script, so the
third box is enforced by the suite rather than left to a reader.
Coordination
The change touches tests/tools/coverage-report.mjs,
tests/coverage-gate-thresholds.test.mjs, package.json (the
scripts.test:unit:coverage:check line only) and a new
tests/coverage-report-file-region-floor.test.mjs.
Disjoint from every open hold-gated PR: #1163 and #1174 edit
tests/tools/e2e-coverage-report.mjs and tests/tools/e2e-coverage-run.mjs,
which are different files; #1161 edits scripts/audit-gate.mjs; #1165
tests/architecture-content-mirror.test.mjs; #1166 CONTRIBUTING.md; #1168,
#1176 and #1181 the markdown/CSS sanitisers; #1171 the e2e data-fixture
overlay; #1178 and #1185 e2e specs. #1183 is the only other open PR touching
package.json and it adds an engines block, not a scripts entry.
No workflow file is involved: .github/workflows/ci.yml:40 already runs
npm run test:unit:coverage:check, so the new gate takes effect through the
script it already invokes.
Priority
- Impact: medium (the gate that protects 2547 source regions cannot see a
regression smaller than 23 of them, and the line gate is structurally blind
to the sub-line case) - Effort: low
🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5n31 | SHA: 03cfcfe
— hive: agent=quality backend=copilot model=claude-opus-5 copilot=1.0.88
Activity