A few weeks ago I wrote about building a reproducible test harness for comparing free AI coding models before you commit. That harness answers one question: which model should I use?
It does not answer the harder follow-up: once a model generates a patch for my real codebase, when is it safe to merge?
This week there was a great discussion on DEV about "understanding over origin" — the idea that it doesn't matter whether code came from a human or a model, only whether someone actually understands it. I agree with the principle, but principles don't survive contact with a busy afternoon. What survives is a checklist with teeth. So here is the pipeline I bolted onto my model harness: every AI-generated patch has to pass through a scripted review gate before I even read it, and the script produces a scorecard that tells me how carefully I need to read it.
The problem with eyeballing diffs
When a model produces a 40-line diff that looks idiomatic, my brain does a dangerous thing: it pattern-matches on style and skips semantics. The code reads like something I'd write, so I approve it like something I'd write. The failures I've actually shipped from AI-generated code were never syntax errors — the tests even passed. They were things like:
- A retry loop that retried on the wrong exception type, so real errors got swallowed.
- A query filter that was subtly wider than the one it replaced (tests passed because fixtures were too small to notice).
- A dependency added for a one-liner the standard library already covers.
All three would have been caught by asking four boring questions before reading the code. So I scripted the questions.
The review gate: a reproducible artifact
The gate is a small shell script. It takes a patch file, applies it to a throwaway worktree, and runs four checks. It never touches my working branch, and it prints a one-line verdict at the end.
#!/usr/bin/env bash
# review-gate.sh <patch-file> <base-branch>
set -euo pipefail
PATCH="$1"
BASE="${2:-main}"
WT=$(mktemp -d /tmp/ai-review.XXXXXX)
echo "== 1. Isolate =="
git worktree add --detach "$WT" "$BASE" >/dev/null
if ! git -C "$WT" apply --check "$PATCH" 2>/dev/null; then
echo "VERDICT: REJECT (patch does not apply cleanly to $BASE)"
git worktree remove --force "$WT"; exit 1
fi
git -C "$WT" apply "$PATCH"
echo "== 2. Full test suite (not just the touched package) =="
if ! (cd "$WT" && npm test --silent 2>&1 | tail -5); then
echo "VERDICT: REJECT (tests fail)"
git worktree remove --force "$WT"; exit 1
fi
echo "== 3. Diff surface audit =="
git -C "$WT" diff "$BASE" --stat
NEW_DEPS=$(git -C "$WT" diff "$BASE" -- package.json \
| grep -c '^+.*".*":' || true)
echo "new dependency entries: $NEW_DEPS"
echo "== 4. Behavior-change heuristics =="
# Flag the patterns that have burned me before
SUSPECT=$(git -C "$WT" diff "$BASE" \
| grep -E '^\+.*(catch|except|retry|timeout|WHERE|filter\()' \
| wc -l | tr -d ' ')
echo "error-handling/query lines added: $SUSPECT"
if [ "$NEW_DEPS" -gt 0 ] || [ "$SUSPECT" -gt 5 ]; then
echo "VERDICT: REVIEW CLOSELY (expanded surface area)"
else
echo "VERDICT: STANDARD REVIEW"
fi
git worktree remove --force "$WT"
Adapt steps 2–4 to your stack. The point isn't my specific greps — it's that the checks run before my opinion does, every time, identically. Step 1 exists because a model's patch often "works" only against the stale context it was shown; applying it to a clean checkout of current main is itself a finding.
Where the free compute fits
This loop has two compute-hungry halves: generating candidate patches, and running the gate (full test suites per candidate get slow). Both halves are exactly where I've been using MonkeyCode. Disclosure: This article was prepared as part of MonkeyCode's product outreach.
Concretely: I use MonkeyCode's free model access to generate two or three independent candidate patches for the same task, and its free server option to run the gate script against each candidate so my laptop isn't tied up running three test suites in parallel. Generating multiple candidates is the underrated move here — when two independent generations converge on the same approach, my review confidence goes up; when they diverge, the divergence points are exactly where I read line-by-line.
I'm deliberately not quoting quotas, model names, or performance numbers, because those change and you should check the current state yourself. What matters for this workflow is just: the generation step and the sandboxed-test step cost me nothing and don't monopolize my machine.
The decision table
After the gate runs, I use this to decide how much of my own attention to spend:
| Gate output | My review depth | Typical action |
|---|---|---|
| Patch doesn't apply | None | Re-prompt with fresher context; don't hand-fix |
| Tests fail | Read the failure only | Regenerate or discard; never patch the patch blindly |
| Passes, no new deps, low suspect count | Read every changed line once | Merge after reading |
| Passes, new deps or high suspect count | Read lines + write one adversarial test | Merge only if my new test passes |
| Two candidates diverge on approach | Read both diffs at the divergence point | Pick one, write down why in the commit message |
The last row is the one that pays rent. "Write down why" is my personal guardrail for the understanding-over-origin principle: if I can't explain in one sentence why this diff is correct, it doesn't merge, regardless of how green the tests are.
Limitations, and who shouldn't do this
- The heuristics in step 4 are mine, tuned to my past failures. Yours will be different. Start with an empty list and add a pattern every time AI-generated code burns you — the script should grow scar tissue, not ship with mine.
- Green tests are a floor, not a ceiling. If your test suite has weak coverage, the gate's value collapses to step 1 and the greps. Fix coverage first.
- Don't run this for trivial diffs. A five-line config change doesn't need a worktree and a scorecard; ceremony has a cost too.
- This is not a security review. Dependency additions get flagged, not audited. If the model adds a package, that package still needs a real look.
- If your repo's tests can't run in a clean checkout (hidden local state, manual env setup), step 1 will fail constantly and teach you nothing. That's actually a useful signal about your repo, but fix it before adopting the gate.
Closing
Model comparison tells you which generator to trust on average. A review gate tells you whether to trust this specific patch. The first question is interesting; the second is the one that decides what your users run. If you're generating candidates with free model access anyway, the marginal cost of gating every patch through a clean-worktree script is about fifteen lines of bash — and one honest commit message at a time, it keeps the understanding on your side of the merge button.
If you've built your own version of this — especially the failure-pattern greps — I'd genuinely like to see what's in your list.
Top comments (0)