The numbers, how we measure them, the one false alarm we produced in the wild, and what these numbers do not tell you.
Measured on engine 0.1.0. Corpus results from the harness that runs on every push. Wild sweep from 2026-09-01.
Most AI code review tools compete on how much they find. We think that is the wrong contest. A reviewer that is wrong one time in five gets muted within a month, and a muted reviewer catches nothing.
So Codecop runs three deterministic checks and stays silent when it is not sure. Every finding carries evidence you can verify yourself: the registry URL that returned 404, the redacted match and the rule that fired, the test block that has no assertion in it. If we cannot show the evidence, we do not post the finding.
That only works if we hold ourselves to it. This page is the measurement.
runEngine function production calls. Nothing in the checks is mocked. Only the package registry is replaced with an offline stub, so the numbers do not depend on npm being up at test time.| Group | Cases | What it is | Drives |
|---|---|---|---|
| Seeded failures | 30 | Failures planted on purpose. 8 hallucinated dependencies (fake package in package.json, typosquat of a popular package, fake bare import, subpath import, CommonJS require, dynamic import, fake line in requirements.txt, fake entry in pyproject). 8 leaked secrets (GitHub PAT, AWS-style key, Stripe live key, Slack bot token, GitLab PAT, private key block, high-entropy generic key, a token buried in a big diff). 14 test-theater cases across JavaScript and Python (no assertions, empty body, self-comparison, constant assertion, skips added next to a source change, xdescribe, assert True, pass-only, docstring-only). |
Recall |
| Clean diffs | 16 | Honest changes the engine must stay silent on. 12 written by hand: a normal feature diff with real dependencies, honest JS and Python tests, a refactor moving code between files, a docs-only change, a PR that removes a leaked secret, a requirements bump of real packages, a config file full of URLs and IDs, mocked tests, a large mixed diff. Plus 4 real merged pull requests, stored as their exact patches: axios #11172, fastify #7003, flask #6133, requests #7609. | Precision |
| Traps | 22 | Diffs engineered to look like failures. Silence required. Listed in full below, because the traps are where the work is. | Precision |
@acmecorp/http-client)codecop.ymlscripts and engines keys in package.jsonfile: dependenciesimport cv2)expect(a).not.toBe(a))skipif platform gateassert| Check | Planted | Caught | Wrong findings | Precision | Recall |
|---|---|---|---|---|---|
deps-exist | 8 | 8 | 0 | 100% | 100% |
secrets | 8 | 8 | 0 | 100% | 100% |
test-theater | 14 | 14 | 0 | 100% | 100% |
| Overall | 30 | 30 | 0 | 100% | 100% |
The wrong-findings column counts every finding on the clean diffs and the traps as well. Zero means the engine said nothing on all 38 of them.
The build fails when corpus precision drops under 90% or seeded recall under 80%. A regression in a check cannot ship without turning the build red.
We did not take that on faith. We deliberately broke the tautology rule and pushed: seeded recall fell to 53.3%, the build failed, and we restored the rule. The gate works.
A corpus we wrote ourselves can only say so much, so we also ran the engine, read-only, against public pull requests we did not write. Live registries, real gitleaks, same runEngine.
Codecop's authorship detector tagged 43 of the 57 as AI-authored, including Claude Code PRs inside next.js and a Codex PR in flask:
| Agent detected | PRs |
|---|---|
| Claude Code | 13 |
| Devin | 12 |
| GitHub Copilot | 10 |
| Dependabot | 6 |
| Codex | 1 |
| GitHub Actions | 1 |
| None detected | 14 |
Verdicts: 56 LOW, 1 MEDIUM. The engine produced exactly one finding across all 57, and it was wrong.
Pull request 583 on michaelayoade/dotmac_starter_mt, in tests/unit/test_deployment_foundation_execution_plan.py:
def test_the_digest_is_stable_across_renders() -> None:
"""Nothing here reads a clock, an environment variable or a filesystem."""
assert _rendered().digest() == _rendered().digest()
The tautology rule saw two textually identical sides and flagged it. But each side is a fresh call, and comparing two invocations is exactly how determinism and idempotency tests are written. x == x is a tautology. f() == f() is a test.
What changed the same day:
After the fix: 0 false positives in 57 real pull requests. Also 0 true positives. Nothing in this sample carried a hallucinated dependency or a leaked key, which matches the base rate. These are rare per pull request, and rare is exactly why human review misses them when they do happen.
The corpus, the harness, and the sweep script ship in the Codecop repository. You need Node 22 and gitleaks on your PATH.
git clone https://github.com/ShreyanshVaibhaw/Codecop
cd Codecop
npm ci
npm run eval # replays the corpus, prints the per-check table, writes eval/results.json
The wild sweep needs a GitHub token and a Postgres database for the registry cache:
npm run build
GITHUB_TOKEN=$(gh auth token) DATABASE_URL=postgres://... node dist/sweep.js
# writes eval/sweep-results.json: one row per PR with verdict, findings, and the agent detected
The demo pull request is a scripted bad AI change: a hallucinated npm package, a GitHub token in a settings file, and four tests that test nothing. Codecop returned HIGH with 6 findings in 5 seconds, each one an inline annotation with evidence.
Or install Codecop on a repository and open a pull request. The default mode is advisory: it never blocks a merge until you ask it to.