AI writes the diff in minutes. Reviewing it still eats your afternoon.
That imbalance is the new bottleneck. A recent DEV discussion put it sharply: AI promoted every developer to reviewer, but nobody tested the reviewer. The fix isn't more review hours. It's a machine-checkable definition of "ready to ship" — concrete gates, named evidence, and a fail-closed default.
Here's a checklist your team can copy, plus a free-stack bot that enforces it.
Why paper checklists rot
Most teams have a PR checklist. Most of them are fiction.
Boxes get ticked from memory, not from evidence. The same way READMEs rot when nobody runs the examples, a checklist rots when nothing verifies the answers. I've written before about catching slow commits and outdated READMEs on a $0 stack; this is the same principle pointed at the review process itself.
First rule: every gate must name the evidence that proves it. If the evidence can't be produced, the gate fails.
The checklist
Six gates. Each one has a question, the evidence it needs, and a fail-closed behavior.
| Gate | Question | Required evidence | Pass condition | Fail-closed behavior |
|---|---|---|---|---|
| Tests | Does the change add or update tests? | Test file paths + coverage delta | At least one test per changed behavior | Block merge |
| Dependencies | Does the lockfile change? | Lockfile diff + audit output | No new known-vulnerable packages | Block merge |
| Secrets | Are credentials exposed? | Secret scan report | Zero high-confidence hits | Block merge + notify |
| Performance | Does the hot path regress? | Before/after benchmark | p95 within 5% | Block merge |
| Docs | Does a public API change update docs? | Docs diff | Every public symbol documented | Block merge |
| Rollback | Can this change be undone? | Up/down migration + revert commit | Down migration exists | Block merge |
Copy the table. Then delete the gates that don't apply to your repo. Keep the evidence column — that's the part that makes everything else enforceable.
Enforcing the gates with a free stack
MonkeyCode is an open source project that covers the two things this bot needs: free model access and a free server option to host the webhook. Disclosure: This article was prepared as part of MonkeyCode's product outreach.
The design rule: the model doesn't decide anything. It collects evidence and applies your checklist. The verdict is a small JSON object, not prose.
Here's a minimal FastAPI handler that does exactly that:
# app.py — fail-closed PR gate on a free server
from fastapi import FastAPI, Request
app = FastAPI()
GATES = [
{"id": "tests", "ask": "List the test files changed in this PR."},
{"id": "secrets", "ask": "Find any hardcoded credentials in the diff."},
{"id": "docs", "ask": "Does this PR change a public API without doc updates?"},
]
def collect_evidence(payload):
"""Gather real artifacts: test report, audit log, benchmark output."""
return {
"test_files": payload.get("test_files", []),
"audit": payload.get("audit", []),
"benchmark": payload.get("benchmark", {}),
}
def judge(gate, answer, evidence):
# Fail-closed: missing evidence is a failure, never a pass.
if gate["id"] == "tests":
if not evidence["test_files"]:
return {"gate": "tests", "status": "fail",
"reason": "No test files changed."}
return {"gate": "tests", "status": "pass",
"evidence": ", ".join(evidence["test_files"])}
if gate["id"] == "secrets":
hits = [h for h in answer.get("hits", []) if h.get("confidence") == "high"]
if hits:
return {"gate": "secrets", "status": "fail",
"reason": f"{len(hits)} high-confidence secret(s) found."}
return {"gate": "secrets", "status": "pass"}
if gate["id"] == "docs":
if answer.get("api_changed") and not answer.get("docs_updated"):
return {"gate": "docs", "status": "fail",
"reason": "Public API changed without doc updates."}
return {"gate": "docs", "status": "pass"}
return {"gate": gate["id"], "status": "fail", "reason": "Unknown gate."}
@app.post("/webhook/pr")
async def review_pr(req: Request):
payload = await req.json()
evidence = collect_evidence(payload)
verdicts = []
for gate in GATES:
answer = await ask_model(gate["ask"], payload["diff"], evidence)
verdicts.append(judge(gate, answer, evidence))
blocked = [v for v in verdicts if v["status"] == "fail"]
return {
"conclusion": "blocked" if blocked else "approved",
"gates": verdicts,
}
The ask_model call goes to the MonkeyCode free model endpoint; the webhook itself runs on the free server option. Wire it to your repo's PR webhook, post the verdict as a commit status, and you have a gate that blocks merges with a reason attached.
The verdict is deliberately boring:
{
"conclusion": "blocked",
"gates": [
{"gate": "tests", "status": "pass", "evidence": "test_api.py, test_auth.py"},
{"gate": "secrets", "status": "fail", "reason": "2 high-confidence secret(s) found."}
]
}
Post that to the PR as a status check. It shows up right next to CI, and the message is evidence, not a vibe.
Why fail-closed is the whole point
A reviewer bot is only useful if its default answer is "no".
| Model says | Evidence says | Verdict |
|---|---|---|
| Pass | Evidence present | Pass |
| Pass | Evidence missing | Fail — cannot verify |
| Fail | Evidence present | Fail |
| Fail | Evidence missing | Fail |
The model only gets to say pass when the evidence column is non-empty. Everything else is a block. That's the difference between a checklist and a suggestion. A suggestion gets ignored at 11pm; a failing status check starts a conversation.
Limitations and who should skip this
Be honest about the edges:
- The free model can misread a diff. Treat it as a triage layer, not a replacement for human review.
- The current free tier includes 10 million tokens — plenty for a weekend experiment, but a busy repo will burn through it. Cache evidence and only re-review changed files.
- The free server option suits one repo or a small team. It's not a high-availability CI replacement.
- Don't use this as the only gate for auth, payments, or other security-critical paths. Keep a named human owner for those gates.
- Skip this approach entirely if your org requires formal audit trails, or if compliance rules forbid sending code to a hosted model.
Start with one gate
Don't roll out all six at once. Start with the tests gate — it has the clearest evidence and the least ambiguity.
Wire the webhook, let it block one merge, and you'll feel the difference. The rest of the checklist can wait until your evidence pipeline exists.
If you want to try it, MonkeyCode is open source, and the current free tier includes 10 million tokens plus a free server option — enough to run this bot against a real repo this weekend.
Top comments (0)