Files
msd-core/scripts/check-coverage-gate.cjs
Tom Boucher 7d298d6d4d fix(#2474): gate worktree dispatch on project-level USE_WORKTREES too (#2561)
* test(#2474): update dispatch gate test for dual-gate behavior

The #2772 test asserted the gate reads USE_WORKTREES_FOR_PLAN only.
Update to accept the dual-gate (USE_WORKTREES + USE_WORKTREES_FOR_PLAN).

* fix(#2474): gate worktree dispatch on project-level USE_WORKTREES too

The per-plan dispatch condition checked only USE_WORKTREES_FOR_PLAN
(submodule-derived), ignoring the project-level USE_WORKTREES flag.
Add USE_WORKTREES to the gate. Net-negative edit: compress two
nearby prose lines to offset the added shell condition (93353 bytes,
down from 93368).

Closes #2474

* docs(#2474): backfill changeset PR number (2561)

* fix: merge coverage gate into single-process check (#2474)

The test:coverage:unit script chained two c8 invocations with &&:
the first ran tests and wrote coverage data to .nyc_output/, the
second read that data for per-file branch checks. On fast CI runners
(ubuntu/24), the second process started before the filesystem flushed
the first process's writes — a classic TOCTOU race that caused
intermittent coverage gate failures.

Replace the two-process chain with a single c8 invocation that
generates both text and json-summary reports, followed by a Node
script (scripts/check-coverage-gate.cjs) that reads the JSON summary
once and checks both overall and per-file thresholds. No filesystem
race is possible because the JSON report is fully written before the
check script reads it.
2026-07-23 09:14:02 -04:00

52 lines
1.4 KiB
JavaScript

#!/usr/bin/env node
'use strict';
const fs = require('fs');
const path = require('path');
const summaryPath = path.join(process.cwd(), 'coverage', 'coverage-summary.json');
if (!fs.existsSync(summaryPath)) {
console.error('check-coverage-gate: coverage/coverage-summary.json not found.');
console.error('Ensure c8 runs with --reporter json-summary before this script.');
process.exitCode = 1;
return;
}
const summary = JSON.parse(fs.readFileSync(summaryPath, 'utf8'));
const errors = [];
const OVERALL_LINES = 70;
const OVERALL_BRANCHES = 60;
const PER_FILE_BRANCHES = 70;
const PER_FILE_FILES = [
'gsd-core/bin/lib/state.cjs',
'gsd-core/bin/lib/phase.cjs',
'gsd-core/bin/lib/verify.cjs',
'gsd-core/bin/lib/init.cjs',
];
const overall = summary.total;
if (overall.lines.pct < OVERALL_LINES) {
errors.push(`Overall lines ${overall.lines.pct}% < ${OVERALL_LINES}%`);
}
if (overall.branches.pct < OVERALL_BRANCHES) {
errors.push(`Overall branches ${overall.branches.pct}% < ${OVERALL_BRANCHES}%`);
}
for (const file of PER_FILE_FILES) {
const key = Object.keys(summary).find((k) => k.endsWith(file));
if (!key) {
errors.push(`${file}: not found in coverage summary`);
continue;
}
const pct = summary[key].branches.pct;
if (pct < PER_FILE_BRANCHES) {
errors.push(`${file}: branches ${pct}% < ${PER_FILE_BRANCHES}%`);
}
}
if (errors.length > 0) {
for (const e of errors) console.error(`ERROR: ${e}`);
process.exitCode = 1;
}