Coverage-gated LLM test generation. It points at a repo, finds the uncovered lines, asks Claude for candidate tests, and keeps only the candidates that survive a gate. Everything else is discarded automatically.
No model ever runs in CI. Generated tests are committed and reviewed like any other code. The design follows the published work on Uber AutoCover (ICSE-SEIP 2026), Meta TestGen-LLM, and CoverUp.
A generated test is worth nothing if it only moves a percentage. Every candidate has to pass all five of these before it is written to a spec file:
gate.k is 3 consecutive runs, so a test that only passes sometimes is rejected as flaky rather than merged.npm i -g covergen covergen --help
Node 22 or newer. Generation runs on a Claude subscription by default, so no API key
is needed until you switch to generator: api.
Write a covergen.yaml next to the repos you want covered. One entry per repo:
generator: claude-code # a Claude subscription, the default
gate:
k: 3 # consecutive passing runs required
mutation:
enabled: true # plant bugs on the lines a test covers
min_killed: 1 # catch fewer than this and the test is rejected
repos:
- name: node-api
root: ../node-api
runner: vitest
sources: ["src/**/*.ts"]
covergen preflight --repo node-api
preflight is offline and answers in about a second: it says whether the
repo can produce lcov at all, and what is missing when it cannot. Then
covergen sweep --repo node-api --pr generates, gates, and opens a draft pull request
with whatever survived.
Every number in the body comes from a real gate run, so the pull request is the audit trail a reviewer reads before approving. Rejected candidates are listed too, with the bugs they let through. Example, file names synthetic:
covergen: 2 tests accepted in node-api
Tests that catch regressions: caught 7 of 9 planted bugs (78%).
3 source files targeted, 5 candidates generated, 2 accepted in 412.6s.
Line coverage over the repo's sources: 61.4% before, 68.2% after.
| Spec | Symbol | Lines newly covered | Before | After | Planted bugs caught | Assertions |
|---|---|---|---|---|---|---|
src/__tests__/retry.test.ts | withRetry | 5 (22, 23, 24, 25, 26) | 41.2% | 76.5% | 4/5 | toBe, toThrow, toHaveBeenCalledTimes |
src/__tests__/parse-window.test.ts | parseWindow | 4 (14, 15, 16, 17) | 52.0% | 84.0% | 3/4 | toEqual, toThrow |
formatWindow in src/__tests__/format-window.test.ts
>= replaced with >clampWindow in src/__tests__/clamp-window.test.tsinput 184,320, output 12,905, cache read 921,600, cache write 24,110
billed to a Claude subscription (generator: claude-code), no dollar figure
Every test in this PR was generated by a model and kept only because it built, passed the gate repeatedly, and strictly raised line coverage. No model runs in CI. Review these tests exactly as you would any hand-written code: check that the assertions describe intended behavior and not just current behavior.
Covergen never instruments anything itself. It runs the repo's own test runner and reads the lcov that runner writes. Each row below is backed by a committed fixture the test suite runs, or by a read-only dry run against a real repository. The full evidence, including what each repo has to install first, is in the support matrix.
| Runner | Language | Status | Known gap |
|---|---|---|---|
vitest | TypeScript, ESM | works | Monorepos supported. Component targets need a DOM environment installed |
jest | JavaScript, CommonJS | works | Proven on plain CommonJS; a repo with a transform pipeline is untried |
bun | TypeScript, ESM | works | No whole-project baseline: a source file no test loads is missing from the map rather than reported at zero |
pytest | Python | works | Coverage cannot be narrowed to one file, so a gate run measures the whole package |
go | Go | works | Statement coverage projected onto lines; tests are selected by package |
cargo | Rust | works | Tests are selected by crate, and #[cfg(test)] lines are stripped from the lcov to keep the gate honest |
rspec | Ruby | unproven in this environment | Needs the SimpleCov opt-in that preflight prints; the fixture is skipped on a machine without a bundle |
Java, C#, PHP, Elixir and node:test are not supported yet. For each of
them the coverage data is not the problem: what is missing is a runner adapter of about a hundred
lines plus an idiom pack so generated tests match the ecosystem's conventions.