TestGuard
Proves that a test suite actually defends the claims a project makes — by injecting the faults those claims say cannot happen, and reporting every fault the tests fail to detect.
Not a test generator. A claim verifier. Test generation is what happens after a claim turns out to be unfalsifiable.
Third tool following the Guard pattern, alongside
docguard-cli (docs ↔ code) and
websec-validator (attack
surface ↔ code). All three run one loop:
declare what must be true → try mechanically to falsify it → freeze a baseline → gate only the delta → brief the agent before it writes code.
Why
Coverage cannot tell a test that pins correct behaviour from one that pins a defect. An agent that writes both the code and its tests encodes whatever it believed — including its bugs — and the suite goes green.
Measured on a real, entirely AI-authored production codebase with ~4,900
disciplined tests (no snapshots, 0.4% zero-assertion): 8 of 9 real
historical bugs were invisible to the suite, worst case 2,451 tests green
on known-broken code. The largest gap was a compliance-critical path with
100% coverage, where the one assertion that mattered used
expect.objectContaining({...}) and omitted the field carrying the data.
Install
| How | Command |
|---|---|
| npx (no install) | npx testguard-cli probe |
| npm | npm i -D testguard-cli then npx testguard probe |
| pip | pip install testguard-cli then testguard probe (needs Node ≥ 20) |
| Homebrew | brew tap raccioly/tap && brew install testguard |
| GitHub Action | uses: raccioly/testguard@v0.1.0 — see action.yml |
| pre-commit | repo: https://github.com/raccioly/testguard, hooks testguard-claims, testguard-probe |
How it works
npx testguard-cli claims # what does this project claim, and is every claim probeable?
npx testguard-cli probe # try to falsify each claim; report what the tests missed
npx testguard-cli baseline # freeze today's unproven findings; from now on only new ones gate
npx testguard-cli brief # tell the agent where the suite is blind, before it writes
-
Claims live in
testguard.claims.json: a statement, where it comes from, which tests supposedly defend it, and one or more faults — each a deterministic source change that would make the statement false. Every claim and every fault records who produced it.testguard claimsvalidates the file and reports drift against@claim <ID>annotations in source. -
Probe confirms the defenders are green N times unmodified, applies each fault in a scratch git worktree (your tree is never touched), runs the defenders N times, re-runs survivors against the whole suite with N-run attribution, restores, and classifies. Verdicts are a closed set:
Verdict Meaning killeda test body rejected the behaviour, N/N — the only pass SURVIVEDthe defenders stayed green while the claim was false NOCOVERno test file defends the claim at all UNVERIFIABLEthe fault's anchor is missing or ambiguous — loud, never a skip TIMEOUTthe defenders hung; a hang is not a detection FAULT-INVALIDthe replacement does not load — a bad fault, not a finding FLAKY-DEFENDERthe defenders are not reliably green, or disagreed across runs Never a single score. Findings are ranked by severity, claim provenance and blast radius, and written to
.testguard/evidence.json— validated against the spec before it is written. -
Baseline freezes every non-passing fingerprint. Later probes suppress what was already known and exit non-zero only on what is new. Claims whose source and defenders are unchanged reuse their prior verdict, so a probe in CI costs only what changed.
-
Brief turns evidence plus baseline into a ranked, capped
## TEST BLINDSPOT CONTEXTblock. Wire it into an agent's session start — for Claude Code, in.claude/settings.json:{ "hooks": { "SessionStart": [ { "hooks": [ { "type": "command", "command": "npx testguard-cli brief --text" } ] } ] } }
--textprints only, and exits 0 silently when there is no evidence yet, so the hook can never break a session.
Commit .testguard/baseline.json; ignore evidence.json and brief.json.
The baseline is the frozen contract; the other two are regenerated per run.
The fault model is the auditable artifact. You never reach 100% of
correctness; you reach 100% of stated claims verified, and the statement
of claims is what an assessor reads. A claims file is code — its replace
strings run under your test runner — so review it like code.
Try it
The repository ships a known-answer fixture with a real blind spot:
git clone <this repo> && cd testguard && npm install
npm test # includes probing the fixture end to end
fixtures/known-answer/ is a tiny project whose audit-row test asserts with
expect.objectContaining({...}) and omits the content key. Swap the
redacted text for the raw input and the test stays green. probe reports it
as SURVIVED; the fixture's README walks
through every verdict.
Status
v0.1. Four commands, vitest runner, hand-authored faults. The contract
spine — six JSON Schemas shared with the other Guard tools — is under
spec/. Zero runtime dependencies; Node ≥ 20.
Not yet: test generation (the two-gate acceptance loop), other runners, mechanical fault producers, and calibration of fault classes against real escaped bugs. Each is designed for; none is claimed.
Licence
MIT.
Release files for testguard-cli 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| testguard_cli-0.1.0.tar.gz | 78.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| testguard_cli-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 85.1 kB
Release files / testguard_cli-0.1.0.tar.gz
| Download URL | testguard_cli-0.1.0.tar.gz |
|---|---|
| Size | 78.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ad0eb834ac48ea10f8be7dfef36ea29aa2066a796a568b52b83d1042ec52bb98
|
|
BLAKE2b-256 checksum How to use checksums |
9a80818d30fd960908b4a705a0976469e41b52701cdb8b2c697cd9f5f077244b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 17, 2026.
Transparency logRelease files / testguard_cli-0.1.0-py3-none-any.whl
| Download URL | testguard_cli-0.1.0-py3-none-any.whl |
|---|---|
| Size | 6.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
eda649b5a84e3c733da77a9dc235b1dfbc92745961efdedaa1704f8273cd3ff5
|
|
BLAKE2b-256 checksum How to use checksums |
bccb84dd94f469bdf2f0bb8bd181daa63dbb0c1d6854750ec3f67d62012e7ec3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 17, 2026.
Transparency log