ci(test): coverage report with soft targets as sticky PR comment (#110) #111
No reviewers
Labels
No labels
area:auth
area:ci
area:db
area:infra
area:native
area:pwa
area:service
epic
feature
foundation
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
james/carol!111
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "110-ci-coverage"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
A new
coveragejob in.forgejo/workflows/pr.ymlruns vitest with@vitest/coverage-v8on every PR and posts a sticky comment with overall + patch coverage. It never blocks the workflow. Coverage drops change the emoji in the comment from ✅ to ⚠️ but the job exits successfully — soft signal, not gate.The sticky-comment mechanism reuses the ADR-0016 scanner pattern with two intentional differences:
<!-- coverage-comment -->(no per-scanner variant — only one coverage comment per PR).What's reported
UI is excluded from the calculation per CLAUDE.md ("UI changes are tested in the browser"). Canonical exclusion list lives as a regex in
scripts/ci/coverage-report.mjs; thevitest.config.tsexclude is a hint for local dev output. ADR-0018 documents why.End-to-end smoke check
Ran the report script locally against the past three commits (which includes the OIDC userinfo fallback PR) to verify the patch-coverage computation:
The 60% on the callback route reflects added error-class branches that aren't all directly exercised in the OAuth test suite — that's the kind of real signal the soft threshold was designed to surface.
Test plan
npm run typecheck— clean.npm run lint— clean (addedcoverage/to eslint ignores).npm test— 225 passed / 38 skipped.actionlint .forgejo/workflows/pr.yml— clean.npm run test:coveragelocally — emitscoverage/coverage-final.json+ text summary.node scripts/ci/coverage-report.mjsreads JSON + git-diff, writes Markdown body, printsflag=<bool>.flag=falsesince the changes are all scripts / docs / config (no testable-surface source touched).Files
scripts/ci/coverage-report.mjs,scripts/ci/post-coverage-comment.mjs,docs/adr/0018-coverage-soft-targets.md.package.json(+@vitest/coverage-v8,test:coverage),vitest.config.ts(coverage block),.forgejo/workflows/pr.yml(coverage job),docs/ci.md,CLAUDE.md,docs/adr/README.md,eslint.config.mjs.Closes #110.
🤖 Generated with Claude Code
📊 Test coverage
Patch coverage: no testable lines changed.
Overall (
app/,lib/,db/, excluding UI per ADR-0019):Soft thresholds per ADR-0019. Coverage is informational and does not block merge.
semgrep's carol-no-shell-exec-interpolation rule flagged `execSync(\`git diff ${baseRef}...\`)` in coverage-report.mjs. `baseRef` originates from a CI env var; even though the workflow sets it to `origin/<base>.ref`, the rule's "never interpolate into a shell command" stance is correct — the spawn-via-argv pattern makes the safety guarantee obvious at the call site instead of relying on careful env hygiene. `*.ts` / `*.tsx` here are git pathspecs (git's own wildcard matching), not shell globs — they work identically with execFile.14b0cfbf1253cf535f3b53cf535f3b92e4c55602