You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
ci: enforce the ≥90% per-file coverage gate in CI #1550
Wire the per-file coverage gate into CI so that a PR fails when any file falls below the ≥ 90% threshold on all four dimensions (lines, statements, functions, branches), as directed in AGENTS.md.
Background
Per AGENTS.md, each client's test:coverage already enforces a uniform per-file gate of ≥ 90 on lines, statements, functions, and branches across clients/web, clients/cli, clients/tui, and clients/launcher. The aggregate npm run coverage runs the gate (and, for web, the integration project).
However, CI does NOT currently run coverage — the gate is local-only. CI runs validate (fast — format:check + lint + build + test, no coverage) plus a standalone web test:integration step and smoke. This means a PR can drop a file below 90% coverage without CI catching it; the gate is only enforced if a contributor remembers to run npm run coverage locally before pushing.
Goal
Add a CI step that runs the coverage gate so a PR is blocked if coverage drops below 90% across the board.
Acceptance criteria
CI runs the per-file coverage gate (e.g. npm run coverage, or per-client coverage:*) on PRs.
A PR that drops any file below 90% on lines, statements, functions, or branches fails CI.
The gate covers all four clients (web, cli, tui, launcher) consistent with the local coverage target.
Decide whether this replaces or supplements the existing validate + standalone test:integration CI steps (the web test:coverage already includes the integration project), and avoid running the integration suite twice.
Update AGENTS.md to reflect that the coverage gate now runs in CI (the doc currently states "CI does NOT run coverage — the gate is local-only").
Notes
Watch CI runtime: coverage is slower than validate (adds v8 instrumentation + the web integration project). Consider caching / parallelizing per-client jobs.
smoke:tui self-skips under CI (needs a real TTY) — unrelated to coverage but keep that constraint in mind when restructuring CI jobs.
Independent verification of the CI coverage gate (branch 1550-ci-coverage-gate, base 1579-wave-1, HEAD 61ca2200). The claim under test: CI now fails when per-file coverage drops below 90%. I confirmed the mechanism rather than trusting it.
A step Enforce per-file coverage gate (≥90% on all four dimensions) running npm run coverage was ADDED.
The standalone Run web integration tests step (npm run test:integration) was REMOVED.
node-version: '22.x' unchanged; Validate, Run cross-client smokes (npm run smoke), and the Storybook play-function step all remain.
No double integration run:clients/web/package.json → test:coverage runs vitest run --project=unit --project=integration --coverage, so the integration project is already inside the gate — removing the standalone step is correct.
Scope: only two files changed — main.yml and AGENTS.md.
2. CI-run evidence (the gate ran green)
Run id 28603853503 (workflow main.yml, headSha 61ca2200… = PR HEAD), overall conclusion success.
Step Enforce per-file coverage gate (≥90% on all four dimensions) → conclusion: success. All other steps (validate, smokes, Storybook) also success.
3. Load-bearing demonstration (the key proof)
Since CI runs npm run coverage, I proved that command fails on a coverage drop by temporarily injecting an untested exported function with dead branches into a gated source file (clients/web/src/lib/downloadFile.ts), then running the exact web gate npm run test:coverage. Not committed; reverted immediately.
With injection → EXIT 1, explicit threshold errors:
downloadFile.ts | 75 | 64.7 | 85.71 | 74.07 | 77-84
ERROR: Coverage for lines (74.07%) does not meet global threshold (90%) for src/lib/downloadFile.ts
ERROR: Coverage for functions (85.71%) does not meet global threshold (90%) for src/lib/downloadFile.ts
ERROR: Coverage for statements (75%) does not meet global threshold (90%) for src/lib/downloadFile.ts
ERROR: Coverage for branches (64.7%) does not meet global threshold (90%) for src/lib/downloadFile.ts
After revert → EXIT 0, zero threshold errors, downloadFile.ts back to 100 | 100 | 100 | 100.
A coverage regression now fails the exact command CI runs (npm run coverage) → CI blocks the PR. Gate confirmed load-bearing.
Injected probe (reverted, never committed)
// Added above the ExportKind type in clients/web/src/lib/downloadFile.tsexportfunctionauditCoverageProbe(n: number): string{if(n>100){return"big";}elseif(n>10){return"medium";}elseif(n>0){return"small";}return"none";}
Verification loop:
# inject the fn above, then:cd clients/web && npm run test:coverage;echo"EXIT=$?"# -> EXIT=1 + ERROR: ... threshold lines
git checkout -- clients/web/src/lib/downloadFile.ts # revertcd clients/web && npm run test:coverage;echo"EXIT=$?"# -> EXIT=0, no threshold errors
4. AGENTS.md accuracy audit
All coverage-related claims were flipped correctly and no now-false statement remains:
Both standalone test:integration CI references updated: "CI does not run test:integration as its own step — the integration project is covered by the CI coverage gate."
smoke section: "CI runs validate, then the coverage gate (which also covers the web integration project), then smoke."
Remaining local-only mention (smoke:tui self-skips under CI) is unrelated to coverage and still accurate.
TypeScript / Mantine: N/A — this PR is CI config + docs only, no product/TS/React source touched.
Verdict: PASS
Both conditions met: (1) the gate command (npm run coverage / web test:coverage) fails with exit 1 + explicit threshold errors on an injected coverage drop and passes clean on revert, and (2) the actual CI run 28603853503 is green with the coverage-gate step = success. Scope is limited to main.yml + AGENTS.md; AGENTS.md edits are accurate. Working tree left clean, injection reverted, no commits.
Activity
cliffhall commentedon Jul 2, 2026
Smoke test & AGENTS.md audit — PR #1603
Independent verification of the CI coverage gate (branch
1550-ci-coverage-gate, base1579-wave-1, HEAD61ca2200). The claim under test: CI now fails when per-file coverage drops below 90%. I confirmed the mechanism rather than trusting it.1. Workflow diff confirmation (
.github/workflows/main.yml)Enforce per-file coverage gate (≥90% on all four dimensions)runningnpm run coveragewas ADDED.Run web integration testsstep (npm run test:integration) was REMOVED.node-version: '22.x'unchanged;Validate,Run cross-client smokes(npm run smoke), and the Storybook play-function step all remain.clients/web/package.json→test:coveragerunsvitest run --project=unit --project=integration --coverage, so the integration project is already inside the gate — removing the standalone step is correct.main.ymlandAGENTS.md.2. CI-run evidence (the gate ran green)
main.yml,headSha 61ca2200…= PR HEAD), overall conclusion success.Enforce per-file coverage gate (≥90% on all four dimensions)→ conclusion: success. All other steps (validate, smokes, Storybook) also success.3. Load-bearing demonstration (the key proof)
Since CI runs
npm run coverage, I proved that command fails on a coverage drop by temporarily injecting an untested exported function with dead branches into a gated source file (clients/web/src/lib/downloadFile.ts), then running the exact web gatenpm run test:coverage. Not committed; reverted immediately.With injection → EXIT 1, explicit threshold errors:
After revert → EXIT 0, zero threshold errors,
downloadFile.tsback to100 | 100 | 100 | 100.A coverage regression now fails the exact command CI runs (
npm run coverage) → CI blocks the PR. Gate confirmed load-bearing.Injected probe (reverted, never committed)
Verification loop:
4. AGENTS.md accuracy audit
All coverage-related claims were flipped correctly and no now-false statement remains:
coverage— the gate is local-only" → "CI runscoverageon every push (ci: enforce the ≥90% per-file coverage gate in CI #1550) … CI-enforced."npm run coverage" note updated: "CI runscoveragetoo (ci: enforce the ≥90% per-file coverage gate in CI #1550) … running it locally first just catches failures before CI does."test:integrationCI references updated: "CI does not runtest:integrationas its own step — the integration project is covered by the CIcoveragegate."smokesection: "CI runsvalidate, then thecoveragegate (which also covers the web integration project), thensmoke."local-onlymention (smoke:tuiself-skips underCI) is unrelated to coverage and still accurate.TypeScript / Mantine: N/A — this PR is CI config + docs only, no product/TS/React source touched.
Verdict: PASS
Both conditions met: (1) the gate command (
npm run coverage/ webtest:coverage) fails with exit 1 + explicit threshold errors on an injected coverage drop and passes clean on revert, and (2) the actual CI run 28603853503 is green with the coverage-gate step = success. Scope is limited tomain.yml+AGENTS.md; AGENTS.md edits are accurate. Working tree left clean, injection reverted, no commits.ci: enforce the ≥90% per-file coverage gate in CI (closes modelcontex…
test(cli): cover cliOAuth.ts step-up/recovery paths to the ≥90% gate