Ik heb eerder betoogd dat je moet stoppen met vriendelijk vragen aan je agent en je conventie moet vastleggen op een plek waar hij wel moet kijken. Die post eindigt met een vuistregel: de tweede keer dat je dezelfde correctie geeft, schrijf je er een ADR of een lint-regel voor.
Probeer dat op een echte repository en je loopt meteen vast.
De conventie die je het vaakst corrigeert, is meestal precies de conventie die je codebase al op driehonderd plekken schendt. Die regel kun je niet op error zetten, want dan staat de build rood op code waar vandaag niemand aan werkt. Dus zet je hem op warn, en op warn sterft een conventie een stille dood. Niemand leest driehonderd waarschuwingen. Je agent leest ze als bewijs dat deze codebase het nu eenmaal zo doet.
Voor precies dit geval bestaat er een tweede soort controle, en bijna niemand bouwt hem bewust in.
De regel die je niet aan kunt zetten
Een categorische regel zegt nooit. Een drempel zegt nooit slechter dan gisteren.
Het mechanisme is altijd hetzelfde. Je draait de controle één keer, schrijft het resultaat naar een bestand en commit dat bestand. Vanaf dan vergelijkt de controle met het opgeslagen resultaat en faalt alleen op het verschil. Wat er al stond, krijgt vrijstelling. Wat de agent er net bij heeft gezet, niet. En het getal mag nog maar één kant op.
PHPStan bouwde dit in oktober 2019 en beschreef het in één zin die nog steeds de helderste formulering is: de baseline "allows you to be interested in violations only in new and changed code". De documentatie is ook eerlijk over waar het ophoudt. Het werkt voor enkele tientallen tot enkele honderden fouten, en bij vijftienduizend kun je beter je configuratie herzien.
Drie vormen
De bevroren lijst. Elke overtreding die er nu is, vastgelegd per regel en per bestand. PHPStan schrijft phpstan-baseline.neon, detekt schrijft een baseline.xml, en ESLint kan hetzelfde sinds v9.24.0 uit april 2025:
# repareer wat automatisch kan, bevries de rest
eslint --fix --suppress-allDat schrijft eslint-suppressions.json, en de documentatie zegt erbij dat je het bestand moet committen "so that the suppressions are shared with all the developers". Twee details wegen zwaarder dan de vlag zelf. Alleen regels die op "error" staan, worden onderdrukt, dus dit werkt uitsluitend voor standaarden die je echt serieus neemt. En detekt splitst zijn bestand in CurrentIssues en ManuallySuppressedIssues, precies het onderscheid dat de meeste teams kwijtraken: wat er staat omdat het er in de loop van de tijd is ingeslopen, tegenover wat er staat omdat iemand dat zo besloten heeft.
Het enkele getal. Een ondergrens voor de dekking per bestand, een bundle-budget, het aantal overlevende mutanten, type-coverage --at-least 98. Eén waarde, gecommit, en een build die faalt zodra hij de verkeerde kant op beweegt. type-coverage heeft zelfs --update-if-higher, waarmee de opgeslagen drempel automatisch strakker wordt zodra een run beter scoort.
De diff-poort. Sonar bewaart helemaal geen lijst. Het bepaalt wat als nieuwe code telt, en in een pull request-analyse, gemeten tegen de doelbranch, geldt "Only issues on new code are reported." Niets krijgt expliciet vrijstelling, want de diff bepaalt wat van jou is.
| Vorm | Waar het geheugen zit | Faalt wanneer |
|---|---|---|
| Bevroren lijst | Een gecommit baseline-bestand | Er een overtreding buiten de lijst valt |
| Enkel getal | Een drempel in config of JSON | Het getal de verkeerde kant op gaat |
| Diff-poort | De git-historie | De diff de regel schendt |
Waarom dit bij een agent werkt
Een agent corrigeert zichzelf prima, zolang de feedback deterministisch is. Geef hem een falende controle met een duidelijke melding, dan lost hij de overtreding op en schrijft hij het volgende bestand dichter bij het patroon. Dat is het hele argument voor eigen lint-regels.
Een drempel maakt die melding scherper op een manier die hier telt. De fout noemt het bestand dat de agent dertig seconden geleden schreef en verder niets. Hij hoeft niet te graven in oude ruis, niet af te wegen welke van de driehonderd waarschuwingen zijn probleem zijn, en hij kan het niet afschuiven op de gewoonten van de codebase. De baseline is die gewoonten, vastgelegd in een bestand en al meegeteld.
Laat de controle meedraaien in de sessie van de agent zelf, niet alleen in CI. Een controle die aan het eind van de beurt draait, lost de agent op. Een controle die tien minuten later in CI draait, los jij op.
Vier manieren waarop een drempel verweert
De baseline opnieuw genereren. Dit is de manier die het hele idee sloopt, en PHPStan zegt het in zijn eigen documentatie het scherpst: "The life goal of a baseline file is to not exist." Opnieuw genereren is één commando, het maakt de build groen en het ziet er in een diff uit als onderhoud. Behandel een groeiende baseline als iets wat langs een reviewer moet, met een reden erbij, net als een test die op skip gaat.
ESLint neemt de helft daarvan van je over. Zodra een onderdrukte overtreding niet meer voorkomt, faalt de run met There are suppressions left that do not occur anymore, dus het bestand kan niet stilletjes plek blijven reserveren voor problemen die je allang hebt opgelost. De andere helft, het bestand dat groeit, blijft mensenwerk.
De agent die het bestand aanpast. Dat gebeurt. Een baseline is een tekstbestand in de repo, de falende controle noemt het pad, en het groter maken is de kortste route naar groen. Zet de baseline achter het mechanisme waarmee je harnas paden op slot doet.
In deze repository doet een protect-paths.sh-hook dat werk, met een vaste lijst met paden waar niets naartoe geschreven mag worden en een melding die de agent terugkrijgt. Tot ik deze post schreef, stonden de vastgelegde auditruns onder docs/standards/ niet op die lijst. Die leunden op een instructie in het bestand zelf die zegt dat ze nooit bewerkt worden, en dat is een afspraak, dus die houdt precies zo goed stand als een afspraak houdt. Ze staan er nu op omdat ik mijn eigen bewering wilde controleren, en zo is ongeveer elke regel in die hook daar terechtgekomen.
De meting afdwingen in plaats van de eigenschap. SpecBench, gepubliceerd in mei 2026, opent met de reden waarom dit ertoe doet: "As long-horizon coding agents produce more code than any developer can review, oversight collapses onto a single surface: the automated test suite." Elke frontier-agent in hun runs krijgt de zichtbare tests volledig groen, het gat met de achtergehouden tests groeit met 28 procentpunt per vertienvoudiging van de hoeveelheid code, en één van de manieren waarop dat misging was een hashtabel van 2.900 regels die de testinvoer uit het hoofd had geleerd.
Een dekkingspercentage is dus het slechtste wat je kunt afdwingen, want de goedkoopste manier om het omhoog te krijgen is tests schrijven die niets controleren. Kies iets wat je niet goedkoop kunt namaken: overlevende mutanten, het aantal keer dat één specifieke overtreding voorkomt, een bundelgrootte in bytes.
Niemand haalt hem omlaag. Een baseline die alleen maar opnieuw wordt gegenereerd, is een budget met betere manieren. Het tegengif is saai: zet een datum op elke run, bewaar hem, en vergelijk per kwartaal in plaats van per commit. De gedateerde JSON-bestanden onder docs/standards/ in deze repo staan daarvoor, één per run, nooit bewerkt, want wat telt is wat de score drie maanden later nog steeds zegt.
Wat het oplevert vóór de review
Faros AI kwam in het AI Engineering Report 2026 uit op de conclusie waar deze post op rust: "the code arriving for review is often not review-ready. Reviewers are not just assessing more code, they appear to be working harder to bring that code up to a standard it should have met before the PR was opened."
Een drempel haalt één soort reviewopmerking permanent uit die wachtrij. Niet door iemand een betere reviewer te maken, maar door de standaard af te dwingen voordat de pull request bestaat, zodat de reviewer er nooit meer om hoeft te vragen.
Het dekt alleen de helft die een machine kan zien, dezelfde grens die elk instrument op deze blog heeft. Een groen dashboard zegt dat de controleerbare helft in orde is. Het zegt niets over de vraag of de code doet wat het ticket vroeg, en daar heb je nog steeds je eigen ogen voor nodig.
Nog één eerlijke kanttekening. Betterer, de tool die dit patroon voor JavaScript generaliseerde met een .betterer.results-bestand en een smaller-constraint, kreeg met v5.4.0 in augustus 2022 zijn laatste stabiele release, en sinds een alpha in december 2024 is er helemaal niets meer verschenen. Het idee heeft de tooling overleefd. Leer het patroon en bouw het op de checker die je stack toch al draait.
Begin met één conventie. Neem degene die je deze week twee keer hebt gecorrigeerd en die je niet repo-breed kunt oplossen, zet hem op error, genereer vandaag de baseline, commit hem, en draai de controle op de plek waar de agent hem ziet falen. De churn die je wilt vermijden bestaat grotendeels uit code die moest voldoen aan een standaard die op het moment van typen door niemand werd afgedwongen.
De code van gisteren is een gegeven waar je mee moet leven. De volgende tweehonderd regels zijn nog een keuze.