Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,18 @@ jobs:
- name: Coverage gate guidance
if: ${{ failure() && steps.coverage.conclusion == 'failure' }}
run: |
echo "::error title=Coverage gate::Tests or the 97% coverage gate failed."
echo "The repo enforces 97% global coverage for lines, statements, functions, and branches."
echo "Review the per-file coverage table printed above to find undercovered files and branch arms."
echo "Run 'npm run test:coverage' locally and add tests for the missing branches, fallback paths, and sanitizer rules."
echo "See CONTRIBUTING.md (Testing & coverage): aim for 98%+ branch coverage locally so small CI variance does not fail near the threshold."
echo "::error title=Coverage gate::Tests failed, or coverage fell below the 90% global backstop."
echo "The 97% requirement is enforced by Codecov on *changed lines* (the codecov/patch check), not here."
echo "This vitest step only fails on a catastrophic global drop below 90%."
echo "Review the per-file coverage table printed above, run 'npm run test:coverage' locally, and add tests for the missing branches, fallback paths, and sanitizer rules."

- name: Upload coverage to Codecov
if: ${{ !cancelled() }}
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage/lcov.info
fail_ci_if_error: false

- name: Worker runtime tests
run: npm run test:workers
Expand Down
14 changes: 10 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,16 @@ npm run ui:build
npm audit --audit-level=moderate
```

`npm run test:ci` runs the normal combined gate. Coverage must stay at or above **97%** for
statements, branches, functions, and lines. Run `npm run test:coverage` locally when you change
behavior, and aim for **98%+ branch coverage** locally so small CI variance does not fail near the
threshold.
`npm run test:ci` runs the normal combined gate. The coverage requirement is **patch coverage**:
every line your PR adds or changes must be **97%+ covered** (statements, branches, functions, lines).
This is enforced by Codecov's `codecov/patch` status check, which looks only at your diff — so it
depends solely on your own changes and is unaffected by what else merges. Run `npm run test:coverage`
locally when you change behavior and make sure your new branches, fallback paths, and sanitizer rules
are tested.

The repo total is tracked by Codecov as a trend (informational, non-blocking). The local vitest run
keeps a loose **90%** global backstop that only trips on a catastrophic drop (e.g. a deleted test
file); it is intentionally well below actual coverage so routine PRs never fail on it.

Maintainer/release smoke checks:

Expand Down
35 changes: 35 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Codecov configuration.
#
# The real coverage gate is `patch`: every line a PR changes must be >=97%
# covered. Patch coverage depends only on the PR's own diff, so merging one PR
# can never push another below the bar -- this is what kills the cross-PR churn
# the old global vitest threshold caused.
#
# `project` (whole-repo total) is informational only: it is reported as a trend
# but never blocks a merge. vitest keeps a loose 90% local backstop separately.
coverage:
status:
patch:
default:
target: 97%
threshold: 0%
# Only fail if the PR actually changes coverable lines.
if_ci_failed: error
project:
default:
informational: true

# Don't post a verdict until the CI run that produced the report has finished.
require_ci_to_pass: true

comment:
layout: "condensed_header, diff, flags, files"
require_changes: false

# Coverage is collected by vitest (v8) over src/**; mirror its exclusions so the
# Codecov total matches the local report.
ignore:
- "src/env.d.ts"
- "apps/**"
- "test/**"
- "scripts/**"
16 changes: 12 additions & 4 deletions vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,19 @@ export default defineConfig({
provider: "v8",
include: ["src/**/*.ts"],
exclude: ["src/env.d.ts", "apps/**"],
// Emit lcov for Codecov to compute patch (changed-lines) coverage.
reporter: ["text", "lcov"],
// The 97% requirement now lives in codecov.yml as a *patch* gate (changed
// lines only), which is compositional: merging one PR can't drop another
// below the bar. These global thresholds are only a loose catastrophe net
// (e.g. a deleted test file), set well below actual coverage so routine
// PRs never trip them. Do NOT raise these toward the real coverage number
// or the cross-PR churn returns.
thresholds: {
lines: 97,
functions: 97,
branches: 97,
statements: 97,
lines: 90,
functions: 90,
branches: 90,
statements: 90,
},
},
},
Expand Down
Loading