verification integrity, checked per diff

Did this change weaken your tests?

An AI coding agent told to “make the tests pass” has two options: fix the code, or make the tests stop checking. tampercheck reads the diff and catches the second one — deleted tests, .only, || true, hollowed assertions, swallowed errors — before a green run stops meaning anything.

Deterministic — no LLM calls Offline — reads a diff, nothing else Provider-agnostic — Claude, Codex, or human Python · JS/TS · Rust · shell/CI Apache-2.0

the problem

Green was engineered, not earned

Standard tooling cannot see this

A linter checks the code that exists — it has no opinion about the test that was removed. CI reports green, because green is exactly what was engineered. The only defence left is a human reading every diff, which is the thing that stops happening as agent output scales.

tampercheck reads the change itself

It asks one question: did this change make the checking weaker than it was before? Every detection fires only on lines the change added — pre-existing conditions in a legacy codebase are never reported, which is what makes it survivable as a required CI check.

coverage

The eight detections

Each one catches a specific way a change can produce a green run without earning it. Severity decides what fails the build — the default gate is high.

DetectionSeverityWhat it catches
test-deletedcriticalA test file removed by this change
test-focusedcriticalA new .only / fit / fdescribe — silently excludes every other test while still reporting green
unconditional-successcriticalNew || true on a test command, bare exit 0 in CI, process.exit(0) / sys.exit(0) in tests, assert True
test-skippedhighA new unconditional .skip / .todo / xit / @pytest.mark.skip / #[ignore]
test-filteredhighA new test-selection filter — --grep, pytest -k, jest -t, cargo test <name> — shrinking what runs without changing what's reported
assertion-weakenedhighA test file that removes materially more executable assertions than it adds — tests still run, still pass, just check less
swallowed-errorhighA new empty catch, broad except: pass, or empty Err(_) => {} arm
placeholdermediumA new TODO / FIXME / not implemented / todo!() standing where behaviour should be

workflow

Any diff in, one honest verdict out

in Three ways to feed it a change

git diff | tampercheck
tampercheck --from main --to HEAD
tampercheck --pr 123   # via gh

Local working tree, any two refs, or a GitHub pull request. Add --json for a stable machine-readable schema.

gate Justification travels with the diff

# tampercheck: allow test_cart.py
#   superseded by test_cart_v2.py

Sometimes deleting a test is correct. Acknowledge it in the change itself — visible to reviewers, unable to drift like an ignore-file. Findings print their own remedy, and --allow KIND:PATH covers one-off runs.

out A crashed check is never a pass

The exit codes are the contract, and the tool ships a test that forces an internal failure to prove 2 stays distinct from 1. A verification tool that fails open manufactures false confidence.

0

Clean. Nothing at or above the failure threshold — the receipt that checking didn't get weaker.

1

Findings. The change weakened verification; fix it or justify it in the diff.

2

Tool failure. Bad input or git error — loudly distinct from a clean result, by contract and by test.

evidence, not vibes

Tuned on 1,331 real commits, proven detector by detector

A checker earns trust by staying quiet on honest work. The patterns were run against the full recent history of three actively developed repositories and tightened until ordinary changes stopped lighting it up.

1,331
real historical commits scanned for false positives
5.6% → 1.4%
commits gating at the default threshold, before → after corpus tuning
~0.7%
measured genuine false positives — over half the residual is real broad-except swallows
8 / 8
detections proven red→green through the real CLI, in CI, on every push

Every detector has a paired fixture — a diff that must be flagged and a near-identical clean twin that must not be — and CI demonstrates each one observably firing before it is trusted. A detector that has never been seen going red is not proven to work. Tuning decisions are recorded next to the patterns they shaped.

honesty

What it deliberately does not catch

tampercheck is a line-oriented diff check. It does not run your tests and does not judge code quality. It will not catch:

  • a test whose assertion is subtly wrong rather than removed
  • a new test that also passes on the pre-change code and therefore proves nothing — that requires executing tests against the base commit
  • mocking a dependency so broadly the test can't fail
  • slow architectural degradation that passes honest tests

It reduces the cheapest forms of verification tampering to zero cost for a reviewer. It does not replace the reviewer.

works with

One layer of a three-layer toolset

tampercheck stands alone — any repo, any CI, any author. It also slots into a layered stack for keeping AI-agent work honest.

quick start

Ten seconds to the first verdict

Run it

Zero-install via uv, or install normally. Then pipe it any diff.

uvx tampercheck --version
# or
pip install tampercheck

git diff | tampercheck

Gate a pull request

Pin the version in CI so a locally edited copy can never change what's enforced.

- uses: actions/checkout@v4
  with: { fetch-depth: 0 }
- run: |
    git diff origin/main...HEAD \
      | uvx tampercheck==0.1.1