DEV Community

Quinn Li
Quinn Li

Posted on

Deciding If AI Code Review Fits Your Repo: A Throwaway Experiment That Costs Nothing

Buying an AI review tool to find out whether AI review works on your codebase is backwards. The sensible order is: define what "works" means, run a cheap experiment against code you already understand, then spend money only if the numbers justify it.

This article is that experiment. It's a deliberately disposable setup — a Node script, a handful of your own historical pull requests, and a spreadsheet-style labeling pass — hosted on free infrastructure so the total cost of answering the question stays at zero.

Start with a claim you can disprove

"Does AI code review help?" is not testable. This is:

For our typical PRs, does a general-purpose model produce at least one finding a human reviewer would act on, per PR, without flooding us with comments we'd immediately dismiss?

Two numbers fall out of that framing: actionable findings per PR and noise ratio. Everything below exists to measure those two things and nothing else. If you skip this step, you'll finish the experiment with vibes instead of a decision.

Why free infrastructure is enough for this

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

The experiment needs two things: model access and a place to run the script. MonkeyCode's free model access and free server option cover both, which is why I'm using them here — but the script talks to any OpenAI-compatible endpoint, so nothing below is locked to one provider. Point the base URL elsewhere and it runs unchanged.

Free tiers are the right tool for an evaluation specifically because the experiment is short-lived. You're not building production capacity; you're buying information.

The review runner

// pr-probe.mjs — usage: node pr-probe.mjs <path-to-diff>
import { readFileSync, writeFileSync, existsSync } from 'node:fs';

const target = process.argv[2];
if (!target) {
  console.error('need a diff file');
  process.exit(1);
}

const patch = readFileSync(target, 'utf8');
const changedLines = patch.split('\n').filter(l => /^[+-]/.test(l) && !/^[+-]{3}/.test(l)).length;

if (changedLines > 250) {
  console.error(`skipping: ${changedLines} changed lines is out of scope for this probe`);
  process.exit(1);
}

const system = `You review code diffs for a senior engineer.
Respond with JSON only — an array where each element is:
{"path": string, "approx_line": number, "kind": "defect"|"hazard"|"style", "note": string}
Rules: one sentence per note. Flag only things that would change behavior,
corrupt data, leak secrets, or break under load. Never repeat a linter.
Empty array if clean.`;

const response = await fetch(`${process.env.PROBE_BASE_URL}/v1/chat/completions`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.PROBE_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    model: process.env.PROBE_MODEL,
    temperature: 0,
    messages: [
      { role: 'system', content: system },
      { role: 'user', content: patch },
    ],
  }),
});

if (!response.ok) {
  console.error(`API error ${response.status}: ${await response.text()}`);
  process.exit(1);
}

const payload = await response.json();
const raw = payload.choices?.[0]?.message?.content ?? '[]';

const record = {
  source: target,
  changed_lines: changedLines,
  model_reply: raw,
  captured_at: new Date().toISOString(),
};

const logFile = 'probe-log.jsonl';
writeFileSync(logFile, (existsSync(logFile) ? readFileSync(logFile, 'utf8') : '') + JSON.stringify(record) + '\n');
console.log(raw);
Enter fullscreen mode Exit fullscreen mode

Two deliberate choices worth noting:

  • Temperature 0. You want run-to-run consistency so disagreements between runs mean something. A creative reviewer is the last thing you need when measuring.
  • Raw output is logged verbatim. You label it by hand afterward. Automating the judgment step at this stage would just move the unverified assumption somewhere harder to see.

Generating ground truth from your own history

The clever part of this experiment is that you don't need new code. Your merged PRs from last month already contain the answer key: whatever human reviewers caught, plus whatever slipped into production and got fixed later.

mkdir -p patches
git log --since="6 weeks ago" --merges --format=%H -12 | while read sha; do
  git diff "${sha}^1" "$sha" > "patches/${sha:0:10}.patch"
done

for p in patches/*.patch; do
  node pr-probe.mjs "$p"
  sleep 3   # stay polite to free-tier rate limits
done
Enter fullscreen mode Exit fullscreen mode

For each PR, jot down before you look at the model output: what did the human reviewer flag, and did anything from this PR cause a follow-up fix? That's your baseline. Looking at model output first will contaminate your memory of what humans actually said.

Labeling and the decision rule

Open probe-log.jsonl and tag every model finding:

Tag Definition Effect on verdict
hit A human would have acted on this Counts as value
noise Wrong, unverifiable, or pure taste Counts against
echo Duplicate of another finding in the same PR Counts against
gap Something the follow-up fix proved was real, that the model missed Weakens the case

Set your thresholds now, before labeling. Mine were: proceed to a paid pilot only if hit / total PRs >= 0.5 and noise <= hits. Yours should reflect how patient your reviewers are — a team of two tolerates more noise than a team of twenty. Writing thresholds down first is the whole trick; otherwise you'll grade on a curve and "validate" whatever happened.

Running it on the free server

For a batch job over twelve historical PRs, a laptop is fine. Where the free server option earns its place is the next step: a tiny webhook listener that probes each newly opened PR for a week, so you're evaluating live diffs rather than archaeology.

Keep the deployment stateless — append results to a gist or a remote JSONL file — because the honest end state of this experiment is often "delete everything." If teardown takes more than a minute, you've accidentally built infrastructure instead of running an experiment.

What this probe cannot tell you

  • Nothing about latency or throughput. Free-tier performance is not a preview of a paid plan. Don't time these requests and extrapolate.
  • Nothing about large changes. The 250-line cutoff is a scope guard, not a suggestion. Big refactors need chunked context and per-file passes, which this script deliberately doesn't attempt.
  • Nothing about model choice. One prompt, one model, temperature zero. You're testing the workflow hypothesis on your diffs, not shopping for the best reviewer.
  • Whether your org allows it. Check code-handling policy before sending real diffs anywhere. If the answer is murky, run the probe against a toy repo and accept the weaker signal.

When to skip this entirely

If AI review already runs in your CI and you're tuning comment volume, this is a step backward — you have production data, use that instead. Also skip it if your diffs are dominated by generated code, lockfiles, or migrations; diff-only review degrades badly there and the experiment will tell you something false.

The actual point

The deliverable of this exercise isn't a configured tool. It's a sentence you can say in a planning meeting: "On our last twelve PRs, a model found N things humans would have acted on, at a noise level we'd tolerate" — or the opposite, which is equally valuable and much cheaper than learning it from an invoice. If you want to run that loop without opening a billing page, MonkeyCode's free model and server tier is one convenient substrate; the probe itself doesn't care where it points.

Top comments (0)