Dood elke mutant in een bestand en je hebt iets echts bewezen: de tests merken het als het gedrag verandert. Daar schreef ik een paar weken geleden over, inclusief Chaos-MCP, de tool die ik bouwde om die loop vanuit de agent zelf te draaien, en dat getal is de moeite waard.
Het zegt ook niets over de functie onder die tests. Of die vijf niveaus diep genest zit. Of drie andere bestanden onder een andere naam al hetzelfde doen. Of hij zomaar een module binnenstapt waar hij niets te zoeken heeft. Een mutation score is een uitspraak over de tests. Over de code zelf heeft hij geen mening.
Een andere vraag dan "ving de test het"
Coverage beantwoordt "draaide deze regel". Mutation testing beantwoordt "zou een test hier een fout hebben gevangen". Allebei vragen over correctheid op één moment.
Geen van beide gaat over wat er de komende zes maanden met de codebase gebeurt. Of de functie die je vandaag oplevert een goede plek is voor de volgende die erin moet werken, mens of agent. Dat is een andere as, met eigen meetinstrumenten: complexiteit, duplicatie en architectuur. Geen daarvan is zichtbaar in een groene CI-badge.
Complexiteit groeit waar je niet keek
Een grootschalige studie naar AI-gegenereerde pull requests in praktijkrepositories, gebouwd op de AIDev-dataset, vond een cyclomatische complexiteit voor AI-functies die nauwelijks hoger lag dan bij mensen: 2,62 tegen 2,47, een klein verschil. Interessanter was waar die complexiteit zat. AI-code bouwde hem op via diepere nesting, vooral in loops, terwijl menselijke code hem concentreerde in conditionele logica. Andere vorm, vergelijkbaar getal.
Daar zit een mechanisme achter. Een model genereert één token per keer, geoptimaliseerd op wat lokaal klopt, zonder bij te houden hoeveel een functie al heeft opgebouwd tegen regel veertig. De meest recente leaderboard-run van Sonar, met GPT-5.2 High, Opus 4.5 Thinking, Claude Sonnet 4.5 en Gemini 3 Pro (dit dateert van vóór de lancering van Opus 5 en Sonnet 5 deze maand, dus lees het patroon als houdbaar en de exacte cijfers als een momentopname), vond dat code smells 92 tot 96 procent van alle gemelde issues uitmaakten, bij elk model, ongeacht hoe capabel het was. Hun conclusie: naarmate modellen geavanceerdere, stateful oplossingen proberen, is eenvoud het eerste wat sneuvelt. Een slimmer model schrijft een ambitieuzere functie, en in die ambitie verstopt zich de complexiteit.
Wat er op het spel staat, bleek uit een Carnegie Mellon-studie naar de invoering van Cursor onder 807 GitHub-projecten tegenover 1.380 vergelijkbare controleprojecten, over 20 maanden, bekroond als Distinguished Paper op MSR '26. Toegevoegde regels code schoten in de eerste maand met 281% omhoog, zakten in maand twee naar 48%, en waren in maand drie vrijwel verdwenen. Waarschuwingen van statische analyse stegen met 30%. De complexiteit met 41%. Geen van beide zakte ooit weer terug. De studie vond ook een terugkoppeling: een verdubbeling van de complexiteit hing samen met een daling van 64,5% in toekomstige snelheid. De snelheid waar je op joeg, is binnen een kwartaal verdampt. De complexiteit die hij achterliet, verdwijnt niet, en stuurt elke volgende PR de rekening.
Een complexiteitsplafond op het hele bestand signaleert alleen bestanden die toch al slecht waren. Volg in plaats daarvan de delta per pull request: maakt deze specifieke wijziging de functie lastiger te lezen dan gisteren. SonarQube's cognitive-complexity-regel, ESLint's complexity-regel, radon voor Python, gocyclo voor Go: ze kunnen allemaal zo'n delta rapporteren, naast het absolute getal. Zet de gate op de delta en je vangt de langzame klim voordat het een bestand is dat niemand meer wil openen.
De clone-detector die de clones niet ziet
Ik schreef eerder over een geërfde codebase met twintig manieren om een bedrag te formatteren, geen ervan met dezelfde naam, geen ervan te vinden met grep. Zet een echte clone-detector op zo'n codebase los en je zou een rood rapport verwachten.
Het kan andersom uitpakken. Diezelfde AIDev-studie vond dat AI-gegenereerde code een lager percentage gedupliceerde regels had dan menselijke code: 18,69% tegenover 25,89%. Op papier oogt de AI-codebase schoner.
De tools verklaren het gat. jscpd en PMD CPD werken allebei op basis van token-matching, Rabin-Karp-stringmatching onder de motorkap, uitstekend in het vinden van exacte of hernoemde kopieën: Type-1- en Type-2-clones. Wat een agent aflevert is geen van beide. Elke near-duplicate wordt koud gegenereerd, met eigen variabelenamen en eigen vorm, omdat het model geen geheugen heeft van de formatter die het drie prompts eerder schreef. Dat is een Type-3- of Type-4-clone, hetzelfde gedrag, andere structuur, en precies het type dat een tokenmatcher is gebouwd om te missen.
Een schoon jscpd-rapport op een AI-zware codebase zegt meer over wat de tool niet ziet dan over of de code echt DRY is. Combineer het met de grep-op-werkwoord-aanpak uit de duplicatiepost, en behandel een verdacht laag duplicatiepercentage met evenveel argwaan als een verdacht hoog percentage.
Architectuur die per bestand verschuift
Daar bestaat een term voor in de onderzoeksliteratuur: architecture erosion, het gat tussen de architectuur waarmee een systeem is ontworpen en de architectuur die het inmiddels heeft. Een systematische mapping study van Li, Liang, Soliman en Avgeriou beschrijft het vanuit vier hoeken: schendingen, structureel verval, impact op kwaliteit, en hoe het zich ontwikkelt. Niets daarvan vereist één dramatische wijziging. Het stapelt zich op.
Een agent kent geen lagen. Hij ziet het bestand dat openstaat in zijn context en welke import het probleem voor hem oplost. Vraag hem om de facturatiegeschiedenis van een gebruiker op te halen, en als de kortste weg rechtstreeks van de UI naar de betaalmodule loopt, neemt hij die weg, omdat niets in zijn context vertelde dat daar een regel voor bestond. In zijn hoofd is het gewoon een import die de test liet slagen.
Hier verdienen fitness functions hun plek: uitvoerbare regels die de architectuur controleren, op dezelfde manier waarop tests het gedrag controleren. ArchUnit doet dat voor Java, en stelt vast welke packages van welke mogen afhangen. dependency-cruiser doet hetzelfde voor JavaScript en TypeScript, loopt de imports af en laat de build falen bij een verboden verbinding. Beide maken van "de UI roept de betaalmodule niet rechtstreeks aan" een check die draait, in plaats van een zin in een wiki.
Ik bouw hier zelf een variant van voor agents: Knossos-MCP, nog vroeg in ontwikkeling, scant een repository één keer in een onderbouwde dependency graph, zodat een agent kan vragen "wat breekt er als ik dit aanpas" of een boundary-schending gemeld krijgt zonder bij elke prompt de hele boom opnieuw te lezen. Zelfde idee als ArchUnit en dependency-cruiser, maar gericht op de context van de agent zelf, niet alleen op de build.
Het deel dat het gedrag van de agent echt verandert, is waar je die check plaatst. Draait de fitness function alleen in CI nadat de pull request al openstaat, dan vangt een mens de schending drie dagen later, in review, terwijl de agent allang met iets anders bezig is. Zet dezelfde check in het commando dat de agent na elke wijziging draait, zoals hooks je deterministische controle geven over wat een agent doet, en de agent ziet de fout meteen, als feedback waar hij in dezelfde beurt op kan reageren. De ene versie leert de agent waar de grens ligt. De andere houdt alleen bij dat hij overschreden is.
Lees het als een trend
Coverage misleidde deels doordat het één keer werd gemeten en als antwoord werd behandeld. Diezelfde val geldt voor elke metriek in dit artikel zodra je alleen naar het absolute getal kijkt. Churn liet dat als eerste zien: wat telt, zijn de regels die blijven staan. Complexiteit, duplicatie en architectuurschendingen verdienen dezelfde behandeling: continu gevolgd, wekelijks of per PR.
Niets hiervan vervangt oordeelsvermogen. Een mutation score, een complexiteitsdelta, een duplicatieratio, een architecture fitness function: elk beantwoordt een smalle vraag, en elk zwijgt zodra je stopt met vragen. Stel ze allemaal, op een vast ritme, en de verschuiving wordt zichtbaar terwijl het nog een diff is. Stop met vragen, en je komt erachter zoals de meeste teams: op de dag dat iemand het bestand opent en beseft dat niemand het al maanden begrijpt.