Find and fill the highest-value gaps in test coverage for $ARGUMENTS — targeting untested critical paths, not line percentage.
If $ARGUMENTS is empty, pick the target by concrete signal, not vibes: rank source files by logic density (count of branches/if/switch/loops and exported functions) crossed with risk — recent churn (git log --since / most-changed files), no matching test file at all, or a test file far smaller than its source. Take the highest-scoring file, state which you picked and the signals that ranked it, then proceed.
Detect conventions first
Never assume a stack. Inspect the repo to learn how it tests:
- Runner and assertion style (Jest/Vitest/Pytest/Go test/RSpec/JUnit...), config, and how the suite is invoked (
package.jsonscripts,Makefile, CI config). - Existing test file location, naming, and structure. Read 2-3 nearby tests and mirror their layout, imports, fixtures, mocking approach, and naming.
- Greenfield case — if the target or the whole repo has zero tests to mirror: pick the ecosystem-standard runner for the detected stack (Vitest/Jest for JS/TS, Pytest for Python,
go testfor Go, RSpec for Ruby, etc.), install/confirm its dev-dependency, wire the invocation (atestscript,Makefiletarget, or the native command), create the conventional test-file path, and land one trivial passing test to prove the harness runs before writing real ones. - The coverage tool if configured (
--coverage,pytest-cov,go test -cover). Run it to get a baseline, but treat the report as a map of untested lines, not the goal.
Find the critical paths
Read the target source and rank untested behavior by blast radius, not by line count. Prioritize:
- Error handling and failure modes — thrown/returned errors, catch blocks, retries, rollback, cleanup.
- Branch and boundary conditions — every
if/else/switcharm, empty/null/undefined, zero, negative, off-by-one, max length, overflow. - Edge cases in parsing, math, dates/timezones, concurrency, ordering, and encoding.
- Public API contracts and invariants callers depend on.
- Security-adjacent logic — authz checks, input validation, injection sinks. Deprioritize trivial getters, pass-through wrappers, and generated code.
Write the tests
- Add tests for the highest-value gaps first. Each test asserts one specific behavior with a name that states it (e.g.
throws when the batch exceeds the limit). - Test observable behavior and contracts, not implementation details. Assert on real return values, thrown errors, and state changes — not on mock call internals unless the interaction is the contract.
- Cover the failure path explicitly: assert the exact error type/message/code, not just "it throws".
- Reuse the project's existing fixtures and factories; if none exist yet, build the minimal fixture inline and keep it local to the test rather than inventing a framework. Only mock true external boundaries (network, clock, filesystem, randomness); do not mock the unit under test.
- Keep tests deterministic — no reliance on wall-clock time, network, ordering of unordered collections, or shared mutable state between tests.
Run and verify
- Run the new tests and confirm they pass. Then, for each test, temporarily mutate the exact source it targets (flip the comparison, delete the
throw, skip the guard, change the boundary) and confirm that test fails — ideally only that test — then revert the mutation. This empirical break-it step is mandatory; do not substitute reasoning about whether it would fail. A test that stays green when its target is broken is worthless. - Run the full suite for the touched area to confirm no regressions. Re-run the coverage tool and note the delta.
Report
Output:
- Baseline — what was untested and why it mattered.
- Added — each test file/case with the one-line behavior it now pins down.
- Coverage delta — before/after if a tool is available; otherwise the critical paths now covered.
- Deliberately not covered — gaps you skipped, each with a one-line reason (trivial, generated, needs infra you shouldn't stub, requires a refactor to be testable).
Rules
- Never add tests that assert current buggy behavior. If you find a real bug while testing, stop and report it — do not encode it as expected.
- Never inflate coverage with assertion-free tests, snapshot dumps of everything, or tests that only exercise mocks.
- Never rewrite existing passing tests or change source logic to make testing easier without flagging it.
- Always match the project's existing style and structure exactly; a new test must be indistinguishable from ones written by the team.