Covergen

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.

GitHub npm Design notes Support matrix MIT license

The gate

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:

  1. It builds.A candidate that does not compile or load is dropped before it costs a test run.
  2. It passes k times in a row.Default gate.k is 3 consecutive runs, so a test that only passes sometimes is rejected as flaky rather than merged.
  3. It strictly raises line coverage.Measured against a baseline of the repo's own suite. A candidate that covers nothing new is rejected, however good it looks.
  4. It passes the repo's own checks.Whatever the project already gates on, lint, type checks, or any other validate command, runs against the new test too.
  5. It catches a planted bug.Covergen breaks the lines the test just covered, one change at a time, and the test has to notice at least one of them. This is the gate that separates a real test from an assertion that passes whatever the code did.

Install and first run

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.

Point it at a repo

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"]

Check the ground before spending a token

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.

What a generated pull request looks like

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.

Accepted tests

SpecSymbolLines newly coveredBeforeAfterPlanted bugs caughtAssertions
src/__tests__/retry.test.tswithRetry5 (22, 23, 24, 25, 26)41.2%76.5%4/5toBe, toThrow, toHaveBeenCalledTimes
src/__tests__/parse-window.test.tsparseWindow4 (14, 15, 16, 17)52.0%84.0%3/4toEqual, toThrow

Rejected candidates

Token usage

input 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.

Runners

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.

RunnerLanguageStatusKnown gap
vitestTypeScript, ESMworksMonorepos supported. Component targets need a DOM environment installed
jestJavaScript, CommonJSworksProven on plain CommonJS; a repo with a transform pipeline is untried
bunTypeScript, ESMworksNo whole-project baseline: a source file no test loads is missing from the map rather than reported at zero
pytestPythonworksCoverage cannot be narrowed to one file, so a gate run measures the whole package
goGoworksStatement coverage projected onto lines; tests are selected by package
cargoRustworksTests are selected by crate, and #[cfg(test)] lines are stripped from the lcov to keep the gate honest
rspecRubyunproven in this environmentNeeds 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.

Read more