Back to all posts

1.3 millioner linjer, 630 PR’er om ugen: Sådan holder vi en vibe-kodet kodebase vedligeholdelig

1.3 millioner linjer, 630 PR’er om ugen: Sådan holder vi en vibe-kodet kodebase vedligeholdelig

I juli 2026 skrev Robert C. Martin, at han ikke længere læser den kode, hans kodningsagenter producerer.

Udtalelsen vakte stor opmærksomhed. Resten af hans indlæg er vigtigere for at forstå, hvordan han arbejder. Martin omgiver agenterne med enhedstests, Gherkin-accepttests, QA-procedurer, mutationstests, dækningskontroller og kvalitetsmålinger. Koden vinder hans tillid ved at passere gennem dette system.

Han havde fremført et lignende synspunkt nogle måneder tidligere. I stedet for at inspicere implementeringen ser han på testdækning, afhængighedsstruktur, cyklomatisk kompleksitet, modulstørrelse og mutationstest-resultater. Da folk tolkede dette som et totalt fravalg af review, præciserede han: "Jeg reviewer masser — bare ikke selve koden."

Hans kommentarer beskriver et praktisk problem i AI-tung softwareudvikling. Kodningsagenter kan lave ændringer hurtigere, end mennesker kan læse dem. Når mængden af AI-genereret kode overstiger teamets reviewkapacitet, kan linje-for-linje-inspektion ikke længere være den eneste kilde til tillid.

Vi har håndteret det samme problem hos vm0 de seneste otte måneder. Størstedelen af implementeringen i vores repository er skrevet af agenter — vibe coding, i den gængse forkortelse — mens seks ingeniører stadig er ansvarlige for, hvad koden gør i produktion.

Den korte version

vm0 er en vibe-kodet kodebase: agenter skriver det meste af implementeringen, og seks ingeniører ejer, hvad den gør i produktion. Otte måneder inde rummer repositoryet 1,329,170 linjer og fik flettet 630 pull requests på én uge. Fem mekanismer bærer nu kvalitetsbyrden, som linje-for-linje-review ikke længere kan bære alene.

  • Konvergerede miljøer. Ingeniører og agenter arbejder i samme dev-container, og hver pull request kan få sin egen databasegren. En kommando, der virkede for en agent, virker for en person.
  • Eksekverbare begrænsninger. Strikt TypeScript, cirka 135 typede API-kontraktmoduler, Oxlint, ESLint og arkitekturregler, hvor CI ikke accepterer advarsler. En regel, der kan kontrolleres maskinelt, overlades ikke til review.
  • Grænsefokuserede tests. Et testtrofæ frem for en testpyramide: integrationstests gennem offentlige modulgrænser, ægte PostgreSQL og rigtige migrationer, mocks kun ved eksterne systemer. Testkode udgør cirka 36 procent af repositoryet.
  • Komplette feedbackloops. Agenter starter applikation og database, kører migrationer og styrer en rigtig browser, og rapporterer derefter de kommandoer, de kørte, stien de testede og screenshots. Den typiske pull request flettes efter cirka 53 minutter.
  • Kontinuerlig oprydning. Knip fjerner deterministisk død kode, daglige workflows rydder AI-rester op, som ingen linter kan navngive, og et mønster, der gentager sig ofte nok, bliver til en lintregel eller en typebegrænsning.

Hvad vibe coding og agentisk kodning egentlig betyder

Vibe coding er at give en LLM et prompt for kode, køre det den producerer, bede om ændringer og ikke interessere sig for den genererede kode. Martin Fowlers definition er bevidst snæver, og han fremhæver sætningen "glem at koden overhovedet eksisterer." Det passer til prototyper, engangssoftware og små værktøjer med begrænsede konsekvenser.

Agentisk kodning, som Fowler kalder agentic programming, er det, teams gør, når de forventer at skulle vedligeholde resultatet. En agent læser repositoryet, redigerer filer, kører tests og arbejder selvstændigt over længere tid, mens mennesker bevarer ejerskab over struktur og adfærd og reviewer beviserne fra en run: testresultater, kvalitetssignaler, previews og produktionsadfærd.

Forskellen er ikke, hvor meget kode en model skriver. Det er, hvor menneskelig opmærksomhed rettes hen.

Vibe codingAgentisk kodning
Agent-autonomiPrompt, kør, prompt igenLæser repository, redigerer, tester, itererer
Hvem læser kodenIngenFolk læser det, der indebærer risiko
Hvad verificeresOm output ser rigtigt udTyper, kontrakter, tests, previews, produktionssignaler
Bedst egnet tilPrototyper og engangsværktøjerSoftware med vedligeholdelseshorisont
Typisk fejlKode ingen forstårVerifikation for svag til mængden

Agenter, der redigerer et repository, er én gren af et bredere skifte; vi har set på den ikke-udviklende side af det separat.

Vi kalder ofte vm0 et vibe-kodet projekt, fordi udtrykket er blevet gængs for software skrevet primært af AI. I Fowlers termer ligger det tættere på agentisk kodning. Vores ingeniører skriver mindre af implementeringen end før, men ejer stadig arkitektur, vedligeholdelsesomkostning og produktionsadfærd.

Efterhånden som kodegenerering blev hurtigere, flyttede mere af ansvaret over i udviklingsmiljøet, typesystemet, testsuiten og automatiserede vedligeholdelsesworkflows.

Otte måneders vækst i en vibe-kodet kodebase

vm0-repositoryet blev oprettet i november 2025. Ved vores seneste ugentlige engineering scale-rapport var det cirka otte og en halv måned gammelt.

Repositoryet indeholdt:

MetrikAntal
Ikke-tomme logiske linjer1,329,170
Produktionslinjer850,913
Testlinjer478,257
Kilde-filer5,549
Testfiler1,327
Pakker44
Commits på main branch14,005

Tallene stammer fra vores ugentlige engineering scale-rapport for ugen 20–26, 2026, målt direkte fra monorepoet. Testkode udgjorde cirka 36 procent af den målte kodebase. Det er en andel af kodemængden, ikke en testdækningsprocent, men det giver et indtryk af, hvor meget implementering der er bygget op omkring verifikation.

Over tre nylige hele uger lavede seks ingeniører henholdsvis 541, 640 og 631 commits. I ugen 20–26 registrerede GitHub 630 flettede pull requests. Seks ingeniører stod bag 556 af dem, mens release-automatisering stod for de resterende 74.

Medianen for tiden fra åbning af en pull request til fletning var cirka 53 minutter. 90. percentil lå omkring 7,4 timer.

Med denne ændringshastighed er menneskelig review stadig nyttig, men kan ikke bære hele kvalitetssystemet. Vi har brug for uafhængige signaler flere steder i ændringens livscyklus.

Vores nuværende tilgang har fem gennemgående temaer: konvergerede miljøer, eksekverbare begrænsninger, grænsefokuserede tests, komplette feedbackloops og kontinuerlig oprydning.

Konvergering af udviklingsmiljøet med dev-containere

Mange fejl, der er svære at genskabe, skyldes tilstand, der aldrig optræder i en pull request.

En udvikler kan have en uofficielt miljøvariabel, et globalt installeret værktøj, en gammel konfigurationsfil eller en database, der har kørt i måneder. Folk vænner sig til disse detaljer. Agenter kan som regel ikke se dem.

Vi holdt gradvist op med at betragte værtsmaskinen som standardudviklingsmiljø. Ingeniører og agenter arbejder i en dev-container. Udviklingsbilledet inkluderer projektets toolchain, PostgreSQL, pgvector, Chromium og browserautomatiseringsværktøjer.

CI kører versionerede toolchain-billeder bygget fra samme multi-stage Dockerfile-familie. Udviklings- og CI-billederne tjener forskellige formål, og produktion har sin egen deploymentsform. Den nyttige egenskab er konvergens: værktøjsversioner, afhængigheder og runtime-forudsætninger er eksplicitte og versionerede.

Det giver os en enkel forventning. Kommandoer, som en agent kører, skal kunne gentages af en ingeniør i samme udviklingscontainer. Checks, der består under udvikling, skal køre under en nært beslægtet toolchain i CI.

Vi anvender en lignende idé på data. Hver pull-request-preview kan have sin egen databasegren og køre rigtige migrationer og seed-steps. En agent kan oprette data, ændre dem og gentage destruktive tests uden at låne en udviklers eksisterende database eller arve tilstand fra en anden pull request.

Miljøændringer føres gennem repositoryet. Værktøjsopgraderinger, browserversioner og databaseudvidelser bliver reviewet og udrullet på samme måde som applikationskode.

Gøre engineering-standarder eksekverbare i typer og lintregler

Nedskrevne standarder hjælper folk med at forstå designbeslutninger. De garanterer dog ikke, at enhver ændring følger disse beslutninger, især under lange agentsessioner.

Vi lægger stabile regler ind i typesystemet, linters og CI, når reglen kan udtrykkes pålideligt.

vm0 bruger strikse TypeScript-indstillinger, herunder strict og noUncheckedIndexedAccess. Nogle pakker aktiverer yderligere checks for ubrugte værdier og implicitte returns.

API’et bruger et schema-first typet REST-kontraktlag bygget med tRPC’s typemekanik og Zod-schemas. Drizzle forbinder databaseadgang til TypeScript-typer. På tidspunktet for denne gennemgang indeholdt repositoryet cirka 135 kontraktmoduler.

Disse valg fanger ikke forkerte produktbeslutninger. De afslører dog interface-drift tidligt. Når et felt eller svar ændres, fejler relaterede kaldere typisk under statisk analyse. Den resulterende fejl er som regel specifik nok til, at en agent kan bruge den i næste iteration.

Linting dækker en anden gruppe regler. Platformen kører Oxlint, typebevidste checks og ESLint samt projektspecifikke arkitekturregler. CI accepterer ingen advarsler. Advarsler, der kan blive liggende, bliver baggrundsstøj, og baggrundsstøj er let for både mennesker og agenter at ignorere.

Når et problem gentager sig, overvejer vi, hvor checket hører hjemme:

  1. Kan typesystemet udtrykke det?
  2. Kan en linter eller et strukturelt værktøj opdage det præcist?
  3. Kræver det semantisk vurdering på tværs af repositoryet?

De to første giver hurtig, deterministisk feedback på enhver ændring. Vi håndterer den tredje gruppe med tilbagevendende workflows, som beskrives senere i artiklen.

Indsnævring af fejludsatte mønstre i AI-genereret kode

Nogle sprogfunktioner har legitime anvendelser og optræder også ofte i skrøbelig agentgenereret kode.

try/catch kan udviske en fejlafgrænsning. Når en agent støder på en fejl, er det let at tilføje et catch-blok og fallback for at holde den aktuelle sti kørende, men derved mistes den oprindelige fejl. At blande Promise .then() og .catch() med async/await kan sprede kontrolflowet over flere stilarter.

React’s useEffect skaber et lignende problem i tilstandshåndtering. Det bruges ofte til at kopiere tilstand, synkronisere to sandheder eller kode en rækkefølgeafhængighed, der er svær at se i datamodellen.

Core web-platformen begrænser disse mønstre som standard. En begrundet undtagelse kan blive, med en eksplicit forklaring ved siden af.

Produktionskoden i core web-platformen indeholder aktuelt ingen useEffect. Vi bruger ccstate og andre effektløse mønstre til at modellere afhængigheder og sideeffekter mere eksplicit. Et lille antal produktions-useEffect-kald findes stadig andre steder i monorepoet, primært i delt UI- og desktopkode, så påstanden gælder kun for det nævnte scope.

Disse regler udsprang af tilbagevendende fejl i dette repository. Når et mønster gentagne gange skaber det samme vedligeholdelsesproblem, flytter vi det fra reviewvejledning til en eksekverbar begrænsning.

Test ved modulgrænser: et testtrofæ, ikke en pyramide

vm0 følger ikke en traditionel testpyramide. Vores testvejledning beskriver et testtrofæ: statisk analyse i bunden, integrationstests som det dominerende lag og et lille antal end-to-end-tests for kritiske brugerrejser.

Enhedstests er relativt sjældne. Vi bruger dem selektivt til sikkerhedsfølsom logik, algoritmer og tilstandsmaskiner. De fleste forretningsregler testes gennem et moduls offentlige grænse.

API-tests kalder den rigtige applikation gennem dens kontrakter. Testdata forberedes gennem produktionsendpoints, hvor det er praktisk, og assertioner laves gennem offentlig adfærd. Tests undgår at gå direkte ind i en intern service eller ændre databasen, da det kobler dem til den aktuelle implementering.

Intern infrastruktur forbliver ægte, hvor omkostningen er rimelig:

  • PostgreSQL og pgvector
  • databasemigrationer
  • filsystemet
  • interne services
  • mocks ved eksterne systemgrænser

Disse integrationstests kører langsommere end stærkt isolerede enhedstests og kræver et mere komplet miljø. De mindsker også afstanden mellem "testen bestod" og "applikationen kan udføre denne operation med en rigtig database."

Grænsetests giver plads til store interne refaktoreringer. En agent kan omorganisere moduler, opdele en service eller ændre dataadgangslaget, mens den offentlige adfærd forbliver beskyttet.

Uncle Bob foretrækker en anden blanding med tung brug af enhedstests, Gherkin og mutationstests. Det fælles princip er uafhængig verifikation. Processen, der genererede koden, bør ikke være den eneste kilde, der hævder, at koden virker.

Give kodningsagenter et komplet feedbackloop

Vores tidlige kodningsagent-runs endte ofte med en velkendt rapport: koden er ændret og TypeScript består; start venligst applikationen og tjek siden.

Det efterlader halvdelen af udviklingsloopen hos ingeniøren. Nogen skal stadig forberede en database, køre services, åbne en browser, oprette data, observere fejlen og beskrive den tilbage til agenten.

Agenter har nu nok af udviklingsmiljøet til at udføre mere af dette arbejde. De kan starte applikation og database, køre migrationer, oprette testdata, besøge en pull-request-preview og udføre rigtige browserinteraktioner.

Browserverifikation fanger en klasse af problemer, som typechecks og API-tests ikke dækker godt: ødelagt navigation, loading-tilstande der aldrig afsluttes, tilladelsesfejl der kun opstår i et komplet flow og visuelle regressioner.

Efter en run rapporterer agenten de kommandoer, den udførte, stien den testede og hvad den observerede. For brugergrænsefladeændringer kan den inkludere screenshots. En ingeniør kan inspicere disse beviser, før de beslutter, om de selv vil åbne previewet.

Et screenshot er ikke en test og beviser ikke fravær af andre fejl. Det sænker dog omkostningen ved at genskabe agentens kontekst. For en lille interfaceændring er reproducerbare trin og et slut-screenshot langt mere nyttigt end en besked om, at ændringen "burde være rettet."

Kvaliteten af en agents arbejde afhænger i høj grad af den feedback, den kan få uden at vente på et menneske.

Hold grenene korte med trunk-based development

Hurtig kodegenerering kan skabe et stort lager af grene.

Langlivede featuregrene samler mergekonflikter, dobbeltarbejde og forældet kontekst. Når pull requests vokser, bliver review sværere og langsommere. Vi bruger trunk-based development for at holde grenene korte og integrere kontinuerligt omkring main.

Vores main-branch-regler kræver:

  • pull requests for ændringer
  • lineær historik og squash-merges
  • en merge-kø
  • Turbo-, Rust- og sikkerhedstjek
  • ingen rutinemæssig omgåelse af påkrævede gates

Automatisering følger samme vej. En agent kan oprette en pull request, og nogle lavrisiko-vedligeholdelsesopgaver kan muliggøre automatisk fletning, men ændringen skal stadig gennem CI og merge-køen.

Størrelsen og levetiden af pull requests forklarer, hvordan 630 af dem kunne flettes på én uge. En lille ændring kræver mindre kontekst, er lettere at verificere og har mindre risiko for at konflikte med produktarbejde eller en anden automatisk reparation.

Knip som repository-garbage collection: fjernelse af død kode

Kodegenerering tilføjer naturligt filer og abstraktioner. Sletning kræver ofte et separat prompt.

Efter en refaktorering kan gamle filer blive liggende i repositoryet. Fjernelse af en funktion kan efterlade exports, afhængigheder og entry points. Disse artefakter bryder sjældent tests, men gør kodebasen sværere at navigere over tid.

Vi bruger Knip til at finde ubrugte filer, exports, afhængigheder og entry points. TypeScript kan bekræfte, at koden er gyldig; Knip spørger, om den stadig deltager i systemet.

Resten har en ekstra omkostning for kodningsagenter. Repositoryet er en af deres vigtigste kontekstkilder. En forældet helper eller forladt implementering kan ligne et godkendt mønster for næste agent, der læser den. At holde denne kontekst ren er den usexede halvdel af kontekstengineering: hvad en agent læser fra repositoryet, er lige så meget et input som det prompt, den fik.

At fjerne død kode forbedrer også input til fremtidige runs. Knip håndterer den deterministiske del hurtigt nok til at blive et fast kvalitetstjek.

Tilbagevendende AI-slop-oprydningsworkflows

Knip og ESLint har klare grænser. Mange former for forfald kræver projektkontekst og semantisk vurdering.

Vi bruger "AI slop" som en praktisk betegnelse for denne rest: unødvendige fallbacks, duplikerede abstraktioner, tests der omgår en offentlig grænse eller defensive grene for umulige tilstande. Hver enkelt ser harmløs ud. Samlet gør de repositoryet sværere at forstå og giver fremtidige agenter dårlige eksempler at følge.

Flere tilbagevendende vm0-workflows scanner efter disse mønstre på en tidsplan.

En daglig AI-slop-oprydning leder efter nye rester og vælger et lille sæt rettelser med høj sikkerhed for korrekthed og lav risiko. Andre workflows inspicerer API-tests, der rækker ind i interne services, React- og ccstate-antipatterns og teknisk gæld, der kan håndteres sikkert.

Hver workflow holder sine ændringer smalle. Den åbner en pull request og bruger derefter de normale typechecks, lintregler, tests og merge-kø. En pull request sat op til automatisk fletning skal stadig bestå de samme gates.

At køre agenter på en tidsplan er ikke gratis, og vi har skrevet særskilt om at holde de omkostninger nede. Workflows forsøger ikke at rydde al teknisk gæld på én gang. Et lille dagligt batch er lettere at verificere og mindre forstyrrende end en stor oprydning hvert par måneder.

Når et tilbagevendende workflow finder det samme mønster ofte nok, overvejer vi at flytte checket ind i ESLint, Knip eller typesystemet. Det semantiske workflow fungerer som et sted at observere og forfine reglen, før den bliver til et billigere deterministisk check.

Gør ustabile testfejl til automatiske reparationer

En anden gruppe workflows starter fra fejl i GitHub Actions.

Når en test fejler på main branch eller i merge-køen, læser et workflow loggene, tjekker for tegn på ustabilitet og ser på retries, timing og miljøfaktorer. Hvis beviserne peger på en specifik reparation, opdaterer det testen eller implementeringen, åbner en pull request og kører hele CI-stien igen.

At bestå ved retry gør ikke den oprindelige fejl harmløs. Teams, der stoler på retry-knappen, mister gradvist tilliden til røde builds. Når det sker, bliver fejlede checks en ny form for baggrundsstøj.

Et automatiseret reparationsworkflow gør en intermitterende fejl til en sporbar kodeændring. Diagnose, patch og verifikation forbliver synlige i pull requesten. Ingeniører kan inspicere højrisikoændringer, mens smalle rettelser med stærke beviser kan fortsætte gennem merge-køen.

Vores kvalitetssystem har aktuelt tre brede lag:

FaseMekanismerTypiske bekymringer
Under skrivningTypeScript, kontrakter, Drizzle, ESLinttypefejl, interface-drift, kendte kodemønstre
Før fletningKnip, integrationstests, rigtige databaser, previews, merge-kødød kode, moduladfærd, komplette runtime-resultater
Efter fletningplanlagte og hændelsesdrevne vm0-workflowsAI slop, semantiske antipatterns, ustabile tests, arkitekturdrejning

Lagene fodrer hinanden. Problemer fundet af workflows kan blive til statiske regler. Fejl fundet i CI eller produktion kan blive til tests og ny engineering-vejledning.

Hvor menneskelig opmærksomhed bruges ved review af AI-genereret kode

vm0-ingeniører læser stadig kode, især ved arkitekturændringer, sikkerhedsfølsomt arbejde, betalinger og datamigrationer. Vi har ikke gjort "aldrig læs koden" til en teamregel.

Det, der har ændret sig, er fordelingen af opmærksomhed i code review. Kodlæsning er ét signal blandt kontrakter, testgrænser, preview-adfærd, screenshots, kvalitetsmålinger og workflow-diagnostik.

Flere beslutninger kræver stadig erfaren vurdering:

  • om kravet er komplet
  • hvor modulgrænser skal ligge
  • hvilke fejl der kan håndteres
  • hvor stor forretningspåvirkning en fejl kan skabe
  • om en sikkerhedsmodel er passende
  • hvilket nyt fejlmønster de nuværende regler ikke dækker

Et lignende skifte skete på designsiden, hvor design-as-code flyttede visuelle beslutninger ind i samme repository og reviewsti. Ingeniører vedligeholder også miljøet omkring agenterne. Når et problem gentager sig, beslutter vi, om vi skal tilføje en typebegrænsning, lintregel, test eller tilbagevendende workflow. Udviklingssystemet er blevet en vigtig engineering-artefakt i sig selv.

Læsbar kode er stadig vigtig. Den næste læser kan være en ingeniør eller en anden agent. Sammenfiltret kode bruger mere kontekst, udvider omfanget af fremtidige ændringer og gør verifikation mindre pålidelig.

Sikkerhed og de ændringer, der ikke går hurtigere

Hastighed gælder ikke for alt. vm0 holder en kort liste over ændringer, der følger deres eget tempo: sikkerhedsfølsomt arbejde, betalinger, datamigrationer og arkitekturbeslutninger. En ingeniør læser dem linje for linje, uanset hvem eller hvad der har skrevet dem.

Sikkerhedsrisikoen ved AI-genereret kode er efter vores erfaring mindre eksotiske sårbarheder og mere plausibel kode, som ingen ejer. De kontroller, der betyder noget, er de almindelige, anvendt konsekvent:

  • Enhedstests, som vi ellers bruger sparsomt, bruges bevidst til sikkerhedsfølsom logik, algoritmer og tilstandsmaskiner.
  • Tests mocker kun ved eksterne systemgrænser. Intern infrastruktur forbliver ægte, så en ændring, der bryder en intern kontrakt, fejler i CI i stedet for i produktion.
  • Agenter arbejder i dev-containeren mod en databasegren pr. pull request, ikke på udviklerens maskine og ikke mod en delt database.
  • Merge-køen og påkrævede checks kan ikke rutinemæssigt omgås, heller ikke for en pull request, en agent har åbnet og markeret til automatisk fletning.
  • try/catch er som standard begrænset, så en fejl er mindre tilbøjelig til at blive opslugt af et fallback, en agent har tilføjet for at holde en sti kørende.

Håndtering af credentials er et særskilt designproblem med sin egen løsning: vi har beskrevet broker-mønsteret, der holder tokens ude af agentens hænder, i en særskilt artikel.

Intet af dette gør genereret kode sikker i sig selv. Det indsnævrer det sæt af ændringer, hvor en menneskelig beslutning er den eneste kontrol, og gør det sæt eksplicit.

Hvad disse tal ikke viser

Den ugentlige rapport demonstrerer repositoryets størrelse og leveringshastighed. Den viser ikke i sig selv produktionspålidelighed.

At vurdere effekten på runtime-kvalitet kræver tilgængelighed, produktionsfejlrater, hændelsestællinger, ændringsfejlrater, rollback-frekvens og gennemsnitlig tid til genopretning. Et grønt CI-run beskriver én del af leveringsprocessen.

Vi er fortsat i gang med at samle disse outcome-målinger. Engineering-praksis forklarer, hvordan et system håndterer risiko; produktionsdata viser, hvor godt den håndtering virker.

FAQ

Hvad er vibe coding? Vibe coding er at give en LLM et prompt for kode, køre det den returnerer, bede om ændringer og ikke læse den genererede kode. Martin Fowlers definition er bevidst snæver: udvikleren skal "glemme, at koden overhovedet eksisterer." Det passer til prototyper, engangssoftware og værktøjer med begrænsede konsekvenser.

Hvad er agentisk kodning? Agentisk kodning er en længerevarende form for AI-assisteret udvikling, hvor en agent læser et repository, redigerer filer, kører tests og itererer selvstændigt over længere tid. Mennesker ejer stadig arkitektur og adfærd og reviewer beviserne fra en run i stedet for hver linje, agenten skrev.

Hvad er forskellen på vibe coding og agentisk kodning? Opmærksomhed, ikke forfatterskab. Ved vibe coding inspiceres koden aldrig. Ved agentisk kodning arbejder agenten selvstændigt, mens ingeniører tjekker beviserne omkring den: testresultater, kvalitetssignaler, previews og produktionsadfærd. vm0 beskrives ofte som vibe-kodet; i Fowlers termer er det agentisk kodning.

Hvad er AI slop? AI slop er de rester, AI-genereret kode efterlader: unødvendige fallbacks, duplikerede abstraktioner, tests der omgår en offentlig grænse, defensive grene for umulige tilstande. Hver enkelt ser harmløs ud. Samlet gør de et repository sværere at forstå og giver næste agent dårlige eksempler at følge.

Hvordan reviewer I AI-genereret kode ved 630 pull requests om ugen? Ikke linje for linje. Hos vm0 går menneskelig review derhen, hvor vurdering er påkrævet: arkitektur, sikkerhedsfølsomt arbejde, betalinger, datamigrationer. Alt andet bæres af mekanismer: strikse typer og kontrakter, integrationstests ved modulgrænser, rigtige databaser i previews pr. pull request, browserverifikation, Knip og en merge-kø, som ingen ændring rutinemæssigt omgår.

Hvad er best practices for vibe coding i stor skala? Fem holder på tværs af otte måneder hos vm0: konverger udviklingsmiljøet, så agenter og mennesker kører de samme kommandoer; gør standarder eksekverbare i typer, linters og CI i stedet for dokumenter; test ved modulgrænser mod rigtig infrastruktur; giv agenter et komplet feedbackloop inkl. browser; og ryd op løbende i stedet for i store sjældne omgange.

Hvad er sikkerhedsrisiciene ved AI-genereret kode? Den almindelige risiko er ikke en eksotisk sårbarhed, men plausibel kode, som ingen ejer. vm0 holder sikkerhedsfølsomt arbejde, betalinger og datamigrationer på menneskelig review, bruger enhedstests bevidst til den logik, holder intern infrastruktur ægte i tests, begrænser mønstre som try/catch der opsluger fejl, og tillader ingen rutinemæssig omgåelse af påkrævede checks.

Skaber AI-genereret kode teknisk gæld? Det skaber en særlig slags: kode, der stadig kompilerer og består tests, men ikke længere deltager i systemet, plus semantiske rester, ingen linter kan navngive. Knip fjerner den deterministiske del. Tilbagevendende workflows håndterer resten i små daglige batches, og et mønster, der gentager sig ofte nok, bliver til en lintregel eller typebegrænsning.

Hvor meget af vm0-kodebasen er skrevet af AI? Størstedelen af implementeringen. Seks ingeniører stod bag 556 af de 630 pull requests, der blev flettet i ugen 20–26, 2026, med release-automatisering for resten, og agenter skrev hovedparten af koden i disse pull requests. Det, ingeniørerne ejer, er arkitektur, begrænsninger og produktionsadfærd.

Hvad er et testtrofæ, og hvorfor ikke en testpyramide? Et testtrofæ placerer statisk analyse i bunden, integrationstests som det dominerende lag og et lille antal end-to-end-tests øverst. vm0 bruger det, fordi tests skrevet gennem et moduls offentlige grænse fortsat beskytter adfærd, mens en agent omorganiserer implementeringen nedenunder, hvilket et stort enhedstestlag ikke gør.

Hvordan finder I død kode i et AI-skrevet repository? Kodegenerering tilføjer filer og abstraktioner; sletning kræver som regel et separat prompt. vm0 kører Knip som et fast tjek for at finde ubrugte filer, exports, afhængigheder og entry points. TypeScript bekræfter, at koden er gyldig; Knip spørger, om den stadig deltager i systemet, hvilket er det relevante spørgsmål for død kode.

Er vibe-kodet kode sikker at køre i produktion? Det afhænger af, hvad der verificerer den, ikke hvem der har skrevet den. De signaler, vi kræver, er ikke ændret: typer og kontrakter, tests gennem offentlige grænser, rigtig infrastruktur og en pipeline, der skal være grøn. Tallene i denne artikel beskriver repositoryets størrelse og leveringshastighed. Tilgængelighed, fejlrater og ændringsfejlrater er de mål, der besvarer produktionsspørgsmålet, og vi er stadig i gang med at samle dem.

Otte måneder inde

Otte måneder er for tidligt til at erklære en endelig metode. Modeller, agentværktøjer og repositoryet ændrer sig fortsat, og vores regler og workflows ændrer sig med dem.

Én ændring er allerede tydelig. Efterhånden som kodegenerering blev hurtigere, overtog miljøet, begrænsninger, tests og feedbacksystemet mere af kvalitetsbyrden. Ingeniører bruger mindre tid på at skrive implementering og mere tid på at definere adfærd, designe grænser og forbedre verifikation.

vm0-kodebasen vil fortsætte med at vokse. Knip fjerner deterministiske rester. Statiske regler blokerer fejlmønstre, vi allerede forstår. Integrationstests beskytter moduladfærd. Tilbagevendende workflows håndterer forfald, vi endnu ikke kan udtrykke maskinelt.

Kode betyder stadig noget. Best practices i denne artikel handler ikke om at skrive mindre kode, men om at beslutte, hvad der skal være sandt, før en ændring hører hjemme på main branch. Vi bruger nu mere maskineksekverbart bevis for at afgøre, om en ændring hører hjemme på main branch, og om dens implementering skal forblive i repositoryet.

Stay in the loop

// Get the latest insights on AI teammates and collaboration.