Gates first, hooks last — verification that survives an agent edit loop
Sep 16 Maxim Berenshtein: sixteen repo gates + edit-time hook + gate-inventory; prose in AGENTS.md fails silently — conventions become unignorable when the check refuses before CI.
Source: Guardrails for AI-Assisted Development: Skills, Gates, Hooks and Mutation Tests
Maxim Berenshtein’s Sep 16 walkthrough starts where continuous factories actually break: once a model writes the code, writing stops being the expensive part. Proving you didn’t break something nobody was looking at becomes the expensive part. The receipt that sold him was not a failed test — it was a skill copy that landed under .gemini/skills/ while .claude/ and .gemini/ sat in .gitignore. Invisible to every diff and every review. Prose in AGENTS.md had no enforcement mechanism. That is a category failure, not a discipline failure.
His stack is deliberately boring and ordered:
- One skill root on disk (
.agents/skills/), withlint:skillsrefusing copies, orphaned links, and unmapped sections. - Gates — scripts that read the repo and exit non-zero. Nothing executes. The decision rule: silent violations of repository shape belong in a gate; module behavior belongs in a test; judgments a machine cannot make stay prose, named in the gate’s doc comment so a green gate never claims what it never checked.
- Gate inventory that refuses a check missing from
verify:core, an inventory row with no gate, or alint:*command that doesn’t run its own failing test first — because the cheapest fix for a red gate is deleting it from the chain. - PostToolUse hook only after 1–4 exist: maps the just-edited path to the gates that could care, exits 2 so stderr returns to the agent while context is warm, and explicitly tells the model not to weaken the check or rewrite its test.
The error-body gate is the worked example: not a regex for error.message, but binding rules that make spelling irrelevant, with the blind spot (body assembled into a variable) stated in the comment rather than discovered later. Every rule carries its incident and date; a rule with no incident is a deletion candidate. Mutation testing stays narrow and config-linted so the score cannot lie by omitting files.
Why it matters for code review
Always-on agent throughput only compounds if refusal arrives in-loop, not at 11:00 about a 10:00 edit buried under later work. Berenshtein’s ordering — incident → fail-able gate → verify chain → then the hook — is how modest verified momentum beats a peak Claude Code session that ships conventions nobody can see breaking.