The solo developer opens a pull request and finds they were the author. The diff is long enough to blur. The tests pass on the branch. The assistant that drafted part of it is confident. The rest is a blur from last night.
Every AI-assisted builder became a reviewer overnight. A solo founder cannot accept that promotion. There is no queue to spread the load, only the diff, the deadline, and a bill that must stay at zero.
A common thread asks who tests the reviewer. The solo answer is nobody. Attention is the only budget left that cannot be topped up.
The fix is not a smarter model; it is a cheaper decision. Most changes need an honest risk score, not a full review. The few risky ones keep the founder's full attention.
The gate reads a diff, runs the affected tests, and returns a verdict. It has to live somewhere free. The version below uses MonkeyCode, an open-source project, for two parts of the pipeline: free model access and a free server slot.
Disclosure: This article was prepared as part of MonkeyCode's product outreach. The allowance published at the time of writing is ten million tokens. Quotas move. Check the project documentation before you trust the number.
The gate is deliberately dumb. It labels every patch low, medium, or high risk. The model answers in JSON because parsing prose is how prompt drift starts.
#!/usr/bin/env python3
"""solo_gate.py — cheap pre-merge triage for a one-person team."""
import json
import os
import subprocess
import sys
RISK_PROMPT = """You are a conservative code reviewer.
A solo developer sends a patch and its test output.
Classify the merge risk.
low = trivial and covered by tests
medium = needs one human look soon
high = touches auth, money, or data, or tests fail without a clear cause
Reply with exactly one JSON object:
{"risk": "low" | "medium" | "high", "reason": "one sentence"}
Never fix the code."""
def changed_files(patch_text: str) -> list[str]:
return [line[6:] for line in patch_text.splitlines()
if line.startswith("+++ b/") and "/dev/null" not in line]
def run_tests(files: list[str]) -> str:
if not files:
return "no code files changed"
target = " ".join(files[:5])
result = subprocess.run(
f"pytest {target} --exitfirst -q",
shell=True,
capture_output=True,
text=True,
)
if result.returncode == 0:
return "tests passed"
return (result.stdout + result.stderr)[-2000:]
def ask_model(patch: str, test_output: str) -> dict:
import urllib.request
# Set MODEL_URL and MODEL_KEY from the project docs.
body = json.dumps({
"messages": [
{"role": "system", "content": RISK_PROMPT},
{"role": "user",
"content": f"---PATCH---\n{patch[:12000]}\n---TESTS---\n{test_output}"},
]
}).encode()
req = urllib.request.Request(
os.environ["MODEL_URL"],
data=body,
headers={
"Authorization": f"Bearer {os.environ['MODEL_KEY']}",
"Content-Type": "application/json",
},
)
with urllib.request.urlopen(req, timeout=60) as resp:
reply = json.load(resp)["choices"][0]["message"]["content"]
return json.loads(reply)
def evaluate(patch_text: str) -> dict:
return ask_model(patch_text, run_tests(changed_files(patch_text)))
def main() -> int:
patch_text = sys.stdin.read().strip()
if not patch_text:
print("[solo_gate] nothing to gate.")
return 0
verdict = evaluate(patch_text)
print(f"[solo_gate] {verdict['risk'].upper()} - {verdict.get('reason', '')}")
if verdict["risk"] == "high":
print("[solo_gate] blocked. Read this diff before the push continues.")
return 1
if verdict["risk"] == "medium":
print("[solo_gate] allowed. Schedule a human look within 24 hours.")
return 0
if __name__ == "__main__":
sys.exit(main())
Three actions happen inside. File names are extracted from the diff headers. The test suite runs on only those files.
The model receives the patch and the trace, then replies with JSON. The reason field becomes the gate's log entry.
A bouncer at a venue works the same way. The guest list has three entries. Most people wave through; only the messy cases reach the owner.
The hook runs before the push leaves the laptop. That point is earlier than a pull request review. For a team of one, a public failure is also the only outage notification.
The hook that installs the gate is three commands.
cat > .git/hooks/pre-push <<'EOF'
#!/bin/sh
git diff origin/main...HEAD | python3 solo_gate.py
EOF
chmod +x .git/hooks/pre-push
Only a high-risk verdict blocks the push. Medium means merge now, review tonight. The policy stays with the founder; rename the base branch if your default is not main.
The same evaluate function sits behind a webhook. The free server slot turns the gate into an endpoint any machine can call. CI, a phone, a laptop at a coffee shop — all ask the same question.
# gate_server.py — hosted on the free server slot
from flask import Flask, request
from solo_gate import evaluate
app = Flask(__name__)
@app.post("/gate")
def gate_webhook():
patch = request.get_data(as_text=True)
return evaluate(patch)
Set the same two environment variables on the server. The endpoint then behaves like the hook, minus the local repository.
A cold start costs a few seconds. A small patch plus a test trace usually lands in the low thousands of tokens, so the allowance covers hundreds of gated merges. Large refactors get truncated at twelve thousand characters, and their score becomes a hint, not a verdict.
Token spend deserves one number anyway. Run the gate on ten real merges and read the provider's usage page. If one diff eats a tenth of the allowance, split it or review it by hand.
The thresholds live in the prompt, not in the script. A founder who wants stricter merges edits one sentence; the function stays untouched. That separation keeps the gate maintainable at midnight.
The gate is a filter, not a reviewer. It never sees the codebase around the patch. It trusts the test suite exactly as much as the suite trusts itself, so weak tests mean a weak score.
Two setups break it. Diffs that touch money, auth, or user data should not end with a free-model verdict. Repos with secrets in every diff must fix that first, because the patch content is sent to the model.
Teams that answer to auditors should skip this. A model verdict is not an audit trail. Regulated code, healthcare tooling, and compliance checklists stay on human review.
Everyone else can try it: side projects, internal tools, anything where a broken push costs minutes. The gate will block harmless renames and wave through bugs the tests cannot see. That is the price of a zero-dollar merge policy.
Most reviews for AI-written code still happen in a chair with one occupant. The gate does not replace that occupant. It stops the occupant from re-reading every rename at full price.
Start on a side project and let it be wrong twice. Tune the prompt, adjust the thresholds, then let it guard the code you ship on Monday. The gate costs nothing to run and nothing to remove.
Top comments (0)