Nicht jede Zeile verdient ein Review

Von hyretic

Ein Lager sagt: Wer seinen KI-Code noch selbst liest, ist zu langsam, die Agenten reviewen ohnehin besser. Das andere Lager sagt: Wer ihn nicht liest, schiebt Slop in Produktion. Beide streiten über die falsche Frage. Ob du einen Diff lesen musst, entscheidet nicht das Modell, das ihn geschrieben hat, sondern die Stelle im System, an der er landet.

Key Takeaways (TL;DR)

  • Review-Aufwand staffeln: Lies Code nach seiner Reichweite im System, nicht nach seiner Länge im Diff.
  • Rückweg vor dem Merge sichern: Plane den Schalter, der eine Änderung abschaltet, bevor du die erste Zeile generieren lässt.
  • Beweise statt Beschreibungen einfordern: Akzeptiere keinen Pull Request, dessen Testplan aus Prosa besteht.
  • Autor und Prüfer trennen: Lass den Code von einem Agenten prüfen, der die Entstehungsgeschichte nicht kennt.
  • Merge und Launch unterscheiden: Behandle einen grünen Pull Request als Zwischenstand, nicht als Freigabe für Nutzer.

Die Review-Frage beginnt nicht beim Pull Request

Mit Agenten entsteht mehr Code, als ein einzelner Entwickler lesen kann. Wer mehrere Agenten parallel laufen lässt, kommt mit dem Review des eigenen Codes nicht mehr hinterher, von den Pull Requests der Kollegen ganz abgesehen. Die Antwort darauf ist nicht, weniger gründlich zu lesen, sondern an anderer Stelle gründlich zu lesen. Und sie beginnt lange vor dem Diff.

Der Agent gehört in jede Phase, die Code bis in Produktion durchläuft: Planen, Bauen, Prüfen, Launchen. Beim Planen entstehen Spezifikation und Validierungskriterien, bevor eine Zeile Code existiert. Beim Bauen validiert der Agent sich selbst und korrigiert eigene Fehler. Beim Prüfen liefert er Beweise und ein zweiter Agent sucht Fehler. Beim Launchen liegt das Feature hinter einem Gate und wird schrittweise freigeschaltet. Jede Phase reduziert, was du in der nächsten lesen musst.

PhaseDer Agent liefertDu entscheidest
PlanenSpezifikation, Zerlegung in isolierte Teile und Integrationen, ValidierungskriterienWo das Gate sitzt und welches Verhalten sich nicht ändern darf
BauenAtomare Änderungen, Selbstvalidierung gegen Lint, Typecheck und TestsWelche Prüfungen Pflicht sind, bevor ein Diff als fertig gilt
PrüfenTests, Logs, Screenshots, Einschätzung der eigenen Sicherheit, adversariales ReviewWie tief du selbst liest, abhängig vom Blast Radius
LaunchenAudit zu Performance, Sicherheit und Spezifikation, Beobachtung der AusrollungWann das Gate öffnet und wann es wieder schließt

Das Tutorial geht diese vier Phasen in zehn Schritten durch. Die Code-Beispiele sind Illustrationen an einem kleinen Kartenspiel mit Reducer, Spielkarten und einem neuen Speichern-Button, keine produktiven Implementierungen. Passe Pfade, Namen und Befehle an dein Projekt an.

Die Codebasis ist ein Abhängigkeitsbaum, und der Stamm trägt das Risiko

Das zentrale Denkmodell für die Review-Tiefe ist der Abhängigkeitsbaum deiner Codebasis. Am Stamm liegt Code, von dem alles andere abhängt: der Einstiegspunkt der Anwendung, in dem alle Helfer registriert werden, der Reducer mit geteiltem State, die Bildverarbeitung, die Netzwerkschicht. Ein Fehler dort wirkt auf jedes Feature. An den Blättern liegt Code, von dem nichts abhängt: eine neue Komponente, ein neuer Endpunkt, ein Modul, das noch niemand aufruft.

Die Review-Tiefe folgt dem Blast Radius dieser Stelle. Eine Änderung an der Bild-Pipeline mit einem Dutzend nachgelagerter Aufrufer verdient ein Review Zeile für Zeile. Eine isolierte Komponente mit Snapshot-Test und Screenshot im Pull Request wird auf offensichtliche Fehler überflogen, mehr nicht. Dazwischen liegt ein Gradient, und dein Aufwand bewegt sich auf diesem Gradienten mit.

Drei Fragen reichen für die Einordnung, und alle drei zielen auf Sicherheit statt auf Stil. Welches bestehende Verhalten darf sich nicht ändern? Was passiert im schlimmsten Fall, wenn die Änderung falsch ist? Und lässt sich die Änderung zurücknehmen, ohne ein Deployment zu bauen? Wer die dritte Frage mit Ja beantworten kann, darf bei den ersten beiden deutlich weniger Zeit investieren. Je mehr eine Änderung eine Einbahnstraße ist, desto länger liest du.

Stelle im BaumBeispieleReview-Tiefe
StammEinstiegspunkt, Reducer mit geteiltem State, Bild- und Netzwerkschicht, Auth, Datenbank-MigrationenZeile für Zeile, mit Blick auf Seiteneffekte und nachgelagerte Aufrufer
IntegrationDie wenigen Zeilen, die ein neues Blatt an den Stamm anschließenZeile für Zeile, aber kurz, weil das Gate den Rest isoliert
BlattNeue Komponente, neuer Endpunkt, neue Logik ohne AufruferÜberfliegen; Tests, Snapshot und Screenshot im PR zählen als Prüfung

Schritt 1: Kartiere Stamm und Blätter deiner Codebasis

Bevor du die Review-Tiefe staffeln kannst, muss der Stamm benannt sein. Für ein kleines Projekt reicht ein Blick auf die Importe: Module, die von vielen anderen Dateien importiert werden, sind Stamm. Für ein größeres Projekt hilft ein Dependency-Graph, etwa mit madge für JavaScript und TypeScript. Der folgende Befehl listet für ein Modul alle Dateien, die davon abhängen, und zeigt damit den Fan-in:

# madge 8.x, im Projektwurzelverzeichnis
npx madge --extensions ts,tsx --depends src/state/gameReducer.ts src

Das Ergebnis gehört in die Steuerungsdatei deines Agenten, also in die CLAUDE.md oder AGENTS.md im Projektwurzelverzeichnis. Dort steht nicht nur, welche Pfade Stamm sind, sondern auch, was daraus für den Agenten folgt: keine Verhaltensänderung an diesen Pfaden ohne Gate, keine Änderung ohne Hinweis im Pull Request. So könnte dieser Abschnitt aussehen:

<!-- CLAUDE.md, illustrativ -->
## Stamm-Pfade (erhöhte Vorsicht)
- src/main.ts (Einstiegspunkt, registriert alle Helfer)
- src/state/gameReducer.ts (geteilter State aller Features)
- src/infra/imageRenderer.ts (alle Kartengrafiken laufen hier durch)
- src/infra/http/* (Netzwerkschicht)

## Regeln für Stamm-Pfade
- Bestehendes Verhalten darf sich nur hinter einem Feature Gate ändern.
- Jede Berührung eines Stamm-Pfads wird im PR unter "Blast Radius" genannt.
- Neue Logik kommt als eigenes Modul (Blatt) und wird über das Gate angeschlossen.

Diese Liste ist kein Dokument für die Schublade. Der Agent liest sie bei jeder Aufgabe, der Review-Agent prüft gegen sie, und du selbst siehst beim Öffnen eines Pull Requests in Sekunden, ob der Stamm berührt wurde. Wer seine Codebasis kennt, reviewt schneller, weil die Einordnung vor dem Lesen passiert statt währenddessen.

Schritt 2: Plane das Feature in Blättern und Integrationen

Die Planung mit dem Agenten entscheidet, wie viel Review später nötig ist. Ein Plan, der ein Feature als eine große Änderung beschreibt, erzeugt einen Diff, in dem Stamm und Blatt vermischt sind. Ein Plan, der zuerst die isolierten Teile und dann die Integrationspunkte auflistet, erzeugt zwei Sorten von Diffs mit zwei Sorten von Review-Aufwand.

Lass den Agenten deshalb einen Plan nach festem Schema schreiben, bevor er Code anfasst. Das Schema fragt nach dem, was neu ist und nichts Bestehendes berührt, nach den Stellen, an denen das Neue in den Stamm greift, nach dem Gate, das diese Stellen schützt, und nach den Validierungskriterien, die am Ende als Beweis gelten. So könnte das Schema für den Speichern-Button aussehen:

<!-- docs/plans/save-card.md, illustrativ -->
## Blätter (isoliert, kein bestehender Aufrufer)
- src/features/saveCard/SaveCardButton.tsx (neue Komponente)
- src/features/saveCard/savedCardsSelector.ts (neue Logik)
- src/features/saveCard/*.test.ts (Komponenten- und Snapshot-Tests)

## Integrationen (berühren Stamm)
- src/state/gameReducer.ts: neue Aktion SAVE_CARD
- src/ui/CardView.tsx: Button einhängen

## Gate
- flags.saveCard, Default aus, beide Zustände getestet

## Validierungskriterien (Beweise im PR)
- Reducer-Tests mit Gate an und aus
- Snapshot der Komponente, Screenshot des Buttons im gespeicherten Zustand
- Logauszug für den Pfad SAVE_CARD

Mit diesem Plan weißt du vor der ersten generierten Zeile, dass drei Dateien Blätter sind und zwei Dateien Stamm berühren. Dein Review-Budget verteilt sich entsprechend: Die beiden Integrationen liest du, die drei Blätter prüfst du über ihre Beweise. Die Validierungskriterien stehen im Plan, damit der Agent sie beim Bauen anstrebt, statt sie am Ende nachzureichen.

Schritt 3: Lege das Feature Gate an, bevor Code entsteht

Ein Feature Gate ist eine Verzweigung im Code, die neues Verhalten ein- oder ausschaltet. Werkzeuge wie LaunchDarkly bringen dafür Targeting nach Nutzergruppen, prozentuale Ausrollung und Auswertung mit, und ein vollständiges Gating-System hat durchaus Infrastruktur. Für den Anfang reicht ein Boolean in einer Konfigurationsdatei, und dieser Anfang verändert dein Review mehr als jedes Werkzeug.

Damit beide Gate-Zustände ohne Mocking testbar sind, nimmt der Reducer die Flags als Parameter entgegen und greift nicht auf einen globalen Zustand zu. So könnte das aussehen:

// src/state/gameReducer.ts, TypeScript 5.x, illustrativ
import { defaultFlags, type FeatureFlags } from "../config/flags";

export type GameAction =
  | { type: "ADVANCE_CARD" }
  | { type: "SAVE_CARD"; cardId: string };

export interface GameState {
  readonly deck: readonly string[];
  readonly savedCards: readonly string[];
}

export function gameReducer(
  state: GameState,
  action: GameAction,
  flags: FeatureFlags = defaultFlags,
): GameState {
  switch (action.type) {
    case "ADVANCE_CARD":
      return { ...state, deck: state.deck.slice(1) };
    case "SAVE_CARD":
      if (!flags.saveCard) {
        return state; // Gate geschlossen: Verhalten wie vor dem Diff
      }
      return { ...state, savedCards: [...state.savedCards, action.cardId] };
  }
}

Die Datei mit den Flags ist bewusst trivial. Sie exportiert einen Typ und einen Default, in dem jedes neue Feature auf false steht. Dazu gehört ein Test, der beide Zustände abdeckt, denn das Gate ist nur dann ein Rückweg, wenn der geschlossene Zustand nachweislich das alte Verhalten liefert:

// src/state/gameReducer.test.ts, Vitest 3.x, illustrativ
import { describe, expect, it } from "vitest";
import { gameReducer, type GameState } from "./gameReducer";

const state: GameState = { deck: ["ace", "king"], savedCards: [] };

describe("SAVE_CARD hinter dem Gate", () => {
  it("ändert bei geschlossenem Gate nichts am State", () => {
    const next = gameReducer(state, { type: "SAVE_CARD", cardId: "ace" }, { saveCard: false });
    expect(next).toBe(state);
  });

  it("speichert die Karte bei geöffnetem Gate", () => {
    const next = gameReducer(state, { type: "SAVE_CARD", cardId: "ace" }, { saveCard: true });
    expect(next.savedCards).toEqual(["ace"]);
    expect(next.deck).toEqual(state.deck);
  });
});

Mit geschlossenem Gate verhält sich der Reducer exakt wie vor dem Diff, und genau das verändert dein Review. Die neue Logik hinter dem Schalter ist ein Blatt: testbar, isoliert, ohne Wirkung auf Produktion. Die drei Zeilen, die das Gate prüfen, sind Stamm, und die liest du. Ohne Gate wäre der gesamte Diff Stamm, weil jede Zeile das Verhalten in Produktion ändert.

Schritt 4: Lass den Agenten atomar bauen und sich selbst validieren

Das Bauen selbst ist der Teil, über den am meisten geschrieben wird und der für das Review am wenigsten zählt. Zwei Dinge zählen trotzdem. Erstens arbeitet der Agent in atomaren Änderungen, also eine Aufgabe aus dem Plan pro Commit, damit der Diff später der Zerlegung aus Schritt 2 entspricht. Zweitens validiert er sich selbst gegen dieselben Prüfungen, die später im Pull Request als Beweis gelten, und korrigiert eigene Fehler, bevor du den Code siehst.

Dafür braucht der Agent einen einzigen Befehl, der alle Prüfungen ausführt und bei der ersten fehlgeschlagenen abbricht. Je mehr Sichtbarkeit dieser Befehl liefert, desto öfter trifft der Agent den richtigen Code im ersten Anlauf. Lint, Typecheck und Tests sind die Basis; Laufzeit-Logs des neuen Pfads und ein Screenshot per Browser-Tooling kommen dazu, sobald dein Projekt sie hergibt. So könnte der Befehl aussehen:

#!/usr/bin/env bash
# tools/validate.sh, illustrativ
set -euo pipefail

npm run lint
npm run typecheck
npm test -- --run --reporter=verbose
npm run build

Ein Punkt verdient einen eigenen Hinweis: Agenten schreiben gern viele Tests, darunter nutzlose. Ein Test, der prüft, ob ein Mock den Wert zurückgibt, mit dem er konfiguriert wurde, erzeugt grüne Häkchen und keinen Beweis. Das verhindert eine eigene Skill-Datei, die der Agent bei jeder Testerstellung liest. Für Claude Code liegt sie unter .claude/skills/, der Inhalt ist werkzeugunabhängig:

<!-- .claude/skills/useful-tests/SKILL.md, illustrativ -->
---
name: useful-tests
description: Regeln für Tests, die als Beweis im Pull Request zählen
---

- Jeder Test prüft Verhalten, das ein Nutzer oder ein Aufrufer beobachten kann.
- Kein Test prüft, ob ein Mock seinen konfigurierten Wert zurückgibt.
- Kein Test wiederholt einen bestehenden Test mit anderem Namen.
- Grenzfälle vor Happy Path: leere Eingabe, doppelter Eintrag, geschlossenes Gate.
- Der Testname beschreibt das erwartete Verhalten, nicht die Methode.

Wie weit diese Selbstvalidierung reicht, hängt davon ab, was dein Setup dem Agenten zeigt. Ein Agent, der die falsche Kopie des Codes prüft, liefert ein korrektes Testergebnis zum falschen Stand. Die Übertragung in die Laufzeitumgebung gehört deshalb zur Validierung dazu, so wie es im Agent-Setup mit bb, WSL2 und Docker beschrieben ist.

Schritt 5: Fordere Beweise im Pull Request ein, keine Beschreibungen

Was ein Agent im Pull Request abliefert, entscheidet darüber, wie viel du selbst nachprüfen musst. Deshalb gehören Beweise hinein, in vier Formen. Unit-Tests, deren Namen und Asserts beim Überfliegen zeigen, welche Logik abgedeckt ist. Laufzeit-Evidenz als Logauszug des neuen Pfads. Visuelle Evidenz als Screenshot oder Aufnahme, sobald UI betroffen ist. Und eine Einschätzung des Agenten selbst, wie sicher er sich bei Logik, Gate und Integration ist.

Der Effekt dieser Beweise ist direkt messbar an deiner Zeit. Ein Screenshot des Save-Buttons im gespeicherten Zustand ist schwer zu fälschen und in zwei Sekunden geprüft. Eine Liste von Testnamen wie „speichert die Karte bei geöffnetem Gate“ und „ändert bei geschlossenem Gate nichts am State“ sagt dir ohne eine Zeile Implementierung, welche Logik abgedeckt ist. Je mehr Beweise vorne im Pull Request stehen, desto weniger Code musst du hinten lesen.

Bei den Tests gilt trotzdem Vorsicht: Tests aus derselben Generierung wie die Implementierung belegen Konsistenz, nicht Korrektheit, wie der Beitrag über Code Ownership statt Babysitting zeigt. Die Skill aus Schritt 4 reduziert das Problem, löst es aber nicht. Für Stamm-Änderungen prüfst du deshalb die Asserts selbst, für Blätter reicht der Blick auf die Namen.

Damit die Beweise an derselben Stelle stehen und nicht in einem Fließtext versinken, braucht der Agent eine Vorlage. Ohne Vorlage liefert er einen Aufsatz. So könnte eine PR-Vorlage aussehen, die der Agent ausfüllen muss:

<!-- .github/pull_request_template.md, illustrativ -->
## Änderung
Ein Satz. Kürzer als der Diff.

## Blast Radius
- Berührte Stamm-Pfade: (keine | src/state/gameReducer.ts, ...)
- Gate: flags.saveCard (Default: aus)
- Rollback: Gate schließen, kein Deployment nötig

## Beweise
- Tests: Befehl, Anzahl bestanden, Ausgabe als Anhang
- Laufzeit: Logauszug des Pfads SAVE_CARD
- Visuell: Screenshot oder Aufnahme des Save-Buttons

## Einschätzung des Agenten
- Logik: hoch | Gate: hoch | Integration: mittel (mit Begründung)

Ein Pull Request, dessen Zusammenfassung länger ist als sein Diff, hat das Verhältnis von Beweis und Behauptung umgedreht. Die Vorlage verhindert das, weil sie dem Agenten keinen Platz für die Geschichte lässt, nur für die Belege. Und sie macht Pull Requests über Agenten und Entwickler hinweg gleich lesbar: Der Blast-Radius-Block steht immer an derselben Stelle, und du siehst beim Öffnen, ob du zwei Minuten oder zwanzig brauchst.

Schritt 6: Lies Blätter in Minuten und den Stamm Zeile für Zeile

Mit Plan, Gate und Beweisen im Pull Request sieht das eigentliche Lesen für die zwei Sorten Code unterschiedlich aus. Für ein Blatt prüfst du drei Dinge: Entspricht der Diff der Zerlegung aus dem Plan, also berührt er wirklich keinen Stamm-Pfad? Decken die Testnamen die Fälle aus den Validierungskriterien ab? Zeigt der Screenshot das, was die Änderung behauptet? Danach überfliegst du den Code auf Dinge, die offensichtlich falsch sind, etwa eine Komponente, die direkt in den State schreibt. Das war das Review.

Für den Stamm gilt das Gegenteil. Nimm die Integration in den Reducer aus Schritt 3: Ein Diff, der eine neue Aktion hinter dem Gate ergänzt, ist kurz und schnell gelesen. Ein Diff, der die bestehende Aktion ADVANCE_CARD verändert, etwa weil ein neuer Kartentyp eine andere Reihenfolge braucht, ändert Produktionsverhalten für jeden Nutzer, und zwar ohne Gate. Hier liest du jede Zeile, fragst nach jedem Aufrufer und nach jedem Seiteneffekt, und du lässt dir vom Agenten die Liste der nachgelagerten Abhängigen aus Schritt 1 zeigen.

Die Einordnung läuft als Drehregler, nicht als Schalter. Zuerst die Frage, ob ein Stamm-Pfad oder Infrastruktur mit vielen Abhängigen berührt ist. Falls ja, Aufwand hoch. Dann die Frage nach Gate und Rückweg: Was passiert, wenn es schiefgeht, und wie schnell ist es zurückgenommen? Je schlechter die Antwort, desto weiter dreht der Regler nach oben. Das Ergebnis ist, dass du nach dieser Methode vermutlich mehr Code liest als vor den Agenten, nur eben nicht denselben.

Schritt 7: Verlagere Nits auf Typecheck, Linter und Review-Agenten

Ein großer Teil klassischer Reviews bestand aus Stilfragen: Einrückung, Benennung, die bevorzugte Art, über eine Map zu iterieren, die Lieblingsschreibweise des Reviewers. Diese Diskussionen haben im Review von Agenten-Code keinen Platz mehr, und zwar aus zwei Gründen. Erstens erledigen Typecheck, Linter und Formatter diese Arbeit zuverlässiger als ein Mensch im Kommentarfeld, und ein Review-Agent meldet verbleibende Lint-Verstöße ohnehin als ersten Punkt. Zweitens sind Refactorings mit guter Validierung billig geworden: Eine Stildiskussion, die früher einen Tag im Pull Request hing, ist heute ein Prompt, sobald die Tests das Verhalten absichern.

Praktisch heißt das, die statischen Prüfungen in den Validierungsbefehl aus Schritt 4 aufzunehmen und so streng zu konfigurieren, wie das Team es im Review ohnehin verlangt hätte. Was der Linter nicht prüfen kann, aber im Team Konsens ist, gehört als Regel in die CLAUDE.md. Was weder Linter noch Regel abdecken, ist Geschmack, und Geschmack ist kein Review-Kommentar. Die Debatte über Einrückung wurde nicht entschieden. Sie wurde vom Linter übernommen.

Schritt 8: Richte einen adversarialen Review-Agenten ein

Meine Einschätzung: Review-Agenten finden in einem Diff heute mehr als die meisten menschlichen Reviewer, und trotzdem werden sie meist falsch eingesetzt. Der häufigste Fehler ist, dass derselbe Agent in derselben Session den Auftrag bekommt, seine eigene Arbeit zu prüfen. Er kennt den gesamten Verlauf, die Annahmen aus dem Plan und seine eigenen Abkürzungen, und er bestätigt sie. Ein Agent mit dem Kontext der Erstellung schummelt, nicht aus Absicht, sondern weil der Kontext ihn in dieselbe Richtung zieht.

Die Prüfung gehört deshalb in einen frischen Subagenten oder eine separate Session ohne diesen Kontext, mit dem Auftrag, Fehler zu finden statt Fertigstellung zu bestätigen. Claude Code bringt dafür /code-review mit, Codex einen eigenen Review-Modus, und beide taugen als Grundlage. Für eine eigene Review-Skill, die gegen deine Stamm-Liste und deine PR-Vorlage prüft, könnte die Anweisung so aussehen:

<!-- .claude/skills/adversarial-review/SKILL.md, illustrativ -->
---
name: adversarial-review
description: Prüft einen Diff ohne Kenntnis seiner Entstehung auf Fehler und fehlende Beweise
---

Du hast den Code nicht geschrieben und kennst den Plan nicht. Deine Aufgabe ist, Gründe gegen den Merge zu finden.

1. Lies CLAUDE.md und prüfe, ob der Diff Stamm-Pfade berührt, die im PR nicht unter Blast Radius stehen.
2. Prüfe für jede Verhaltensänderung an einem Stamm-Pfad, ob sie hinter einem Gate liegt.
3. Prüfe, ob jedes Validierungskriterium aus dem Plan durch einen Beweis im PR belegt ist.
4. Suche nach Tests, die nur Mocks bestätigen, und nach Asserts, die nichts prüfen.
5. Melde jeden Fund mit Datei, Zeile, konkretem Fehlerszenario und Schwere.
6. Eine leere Liste ist ein gültiges Ergebnis. Erfinde keine Funde, um die Liste zu füllen.

Der letzte Punkt ist wichtiger, als er aussieht. Ein Review-Agent ohne diese Regel produziert bei jedem Lauf Kommentare, weil er Kommentare als seine Aufgabe versteht. Mit der Regel bekommst du bei einem sauberen Blatt eine leere Liste und bei einer Stamm-Änderung ohne Gate einen Fund mit Schwere hoch, und genau diese Unterscheidung spart dir Zeit.

Schritt 9: Lass die Kommentar-Schleife von einem Goal-Loop drehen

Codex lässt sich zusätzlich als Reviewer in GitHub einhängen und prüft dann jeden geöffneten Pull Request oder reagiert auf eine Mention im Kommentar. Das kostet Geld, und es erzeugt eine Dynamik, die wir alle erlebt haben: Der Reviewer hinterlässt zehn Kommentare, du behebst sie und pushst, er findet drei neue, du behebst sie, er findet einen. Jede Runde kostet dich Kontextwechsel, obwohl keine davon dein Urteil braucht.

Diese Schleife muss kein Mensch drehen. Ein Goal-Loop, der den Pull Request in festem Intervall prüft, offene Anmerkungen des Review-Agenten behebt, die lokalen Validierungen aus Schritt 4 erneut laufen lässt, die PR-Notizen aktualisiert und pusht, erledigt das selbst, nach dem Muster der Goal-based Loops aus dem Loop Engineering. Der Auftrag an den Agenten ist kurz:

Betreue PR #142 bis zur Freigabe. Prüfe stündlich auf neue Review-Kommentare.
Für jeden Kommentar: Behebe ihn oder begründe im Thread, warum nicht.
Nach jeder Änderung: ./tools/validate.sh ausführen, PR-Beschreibung nach Vorlage
aktualisieren, pushen. Stoppe, wenn der Review-Agent keine offenen Punkte mehr meldet
oder ein Kommentar einen Stamm-Pfad ohne Gate betrifft. Den letzten Fall meldest du mir.

Die Abbruchbedingung am Ende ist der Teil, den du nicht weglassen darfst. Ein Loop ohne harte Stopp-Regel behebt auch Kommentare, die eine Entscheidung von dir gebraucht hätten, und ein Review-Kommentar zu einer ungegateten Stamm-Änderung ist genau so eine Entscheidung. Alles andere, von der Benennung bis zum fehlenden Test, kann der Loop ohne dich erledigen.

Das stärkste Gegenargument: Die nächste Modellgeneration macht das Lesen überflüssig

Das Argument des ersten Lagers ist nicht dumm. Jede Modellgeneration macht weniger Fehler, und wer heute jede Zeile liest, verschenkt Geschwindigkeit. Gut möglich, dass in zwei Generationen für den Stamm gilt, was heute für die Blätter gilt. Die Schwelle wandert, und sie wandert in eine Richtung.

Das Argument übersieht, was ein Review außer der Fehlersuche leistet. Ein Agent findet den Off-by-one-Fehler im Reducer. Ob diese Reducer-Änderung eine Einbahnstraße ist, ob sie ein Verhalten ändert, auf das drei andere Teams sich verlassen, und welche Alerts feuern, wenn sie schiefgeht, weiß nur jemand, der das System kennt. Diese Einordnung ist der Teil des Reviews, der sich nicht delegieren lässt, und sie wird mit jedem Stück Codebasis-Wissen schneller. Die zehn Schritte hier verschieben deshalb nicht die Frage, ob du liest, sondern sorgen dafür, dass die Agenten dir vorher alles abnehmen, was kein Urteil braucht.

Schritt 10: Trenne merge-ready von launch-ready

Wer so arbeitet, ist am Ende der Coding-Session nicht fertig. Das Feature liegt gated im Main, die Blätter sind überflogen, der Stamm ist gelesen, die Beweise stehen im PR. Das ist ein grober Entwurf in voller Breite, als Daumenregel etwa 80 Prozent des Wegs. Die restlichen 20 Prozent machen aus einem mergefähigen Stand einen launchfähigen, und rechne damit, dass sie so lange dauern wie die ersten 80.

In dieser Phase laufen Audit-Agenten über den gesamten neuen Code, jeder mit einer festen Frage statt mit dem Auftrag „schau mal drüber“. Ein Agent prüft Performance, einer Sicherheit, einer offene Bugs, einer den Abgleich gegen die Spezifikation aus Schritt 2. Jeder liefert seine Funde im selben Format wie der Review-Agent aus Schritt 8, und jeder darf eine leere Liste zurückgeben. So könnte der Auftrag an einen der vier aussehen:

Audit: Spezifikation. Lies docs/plans/save-card.md und den Code unter
src/features/saveCard sowie die Integrationen in gameReducer.ts und CardView.tsx.
Liste jedes Validierungskriterium aus dem Plan und markiere es als erfüllt,
teilweise erfüllt oder nicht erfüllt, jeweils mit Datei und Zeile als Beleg.
Nenne zusätzlich Verhalten, das implementiert ist, aber im Plan nicht steht.

Nach den Audits kommt der Teil, den dir kein Agent abnimmt: das Refactoring über den gesamten neuen Code hinweg, das Aufräumen von Strukturen, die in atomaren Schritten entstanden sind, das Nachziehen von Animationen und Details. An diesen Details entscheidet sich, ob ein Feature fertig wirkt oder generiert, und das ist eine Frage des Urteils, nicht der Prüfung. Hier wird der Code sauber und produktionsreif, und hier liegt der Großteil deines eigenen Aufwands.

Der Launch selbst läuft über dasselbe Gate aus Schritt 3. Du schaltest es für einen kleinen Teil des Traffics frei und beobachtest Crashes, Fehlerraten und Alerts. Bei einer kleinen Nutzerbasis hat ein A/B-Test keine statistische Power, aber darum geht es an dieser Stelle nicht: Ein Canary zeigt dir den Crash, bevor alle Nutzer ihn sehen, und der Weg zurück ist ein Boolean. Für die prozentuale Freischaltung reicht am Anfang eine deterministische Zuordnung über die Nutzer-ID, damit derselbe Nutzer bei jedem Request dasselbe Verhalten sieht:

// src/config/rollout.ts, TypeScript 5.x, illustrativ
export function isInRollout(userId: string, percent: number): boolean {
  let hash = 0;
  for (const char of userId) {
    hash = (hash * 31 + char.charCodeAt(0)) >>> 0;
  }
  return hash % 100 < percent;
}

// Aufrufer: flags.saveCard && isInRollout(user.id, 5)

Steigt die Fehlerrate, setzt du den Prozentwert auf null, und die Änderung ist zurückgenommen, ohne dass ein Deployment läuft. Steigt sie nicht, erhöhst du schrittweise. Erst bei hundert Prozent und einer stabilen Woche entfernst du Gate und Rollout-Code, und zwar als eigenen, kleinen Pull Request, der selbst wieder ein Stamm-Diff ist und so gelesen wird.

10 Punkte, mit denen du deinen Review-Prozess abnimmst

  1. Stamm-Pfade stehen in der Steuerungsdatei: Einstiegspunkte, geteilter State und Infrastrukturschichten sind in der CLAUDE.md oder AGENTS.md benannt, zusammen mit der Regel, dass dort nichts ohne Gate verändert wird. Wie solche Regeln aus echten Fehlern wachsen, beschreibt das Ratchet-Prinzip im Harness Engineering.
  2. Jedes Feature hat einen Plan nach Schema: Blätter, Integrationen, Gate und Validierungskriterien sind aufgelistet, bevor Code entsteht.
  3. Das Gate existiert vor dem Code: Default aus, beide Zustände getestet, geschlossener Zustand liefert nachweislich das alte Verhalten.
  4. Ein Befehl validiert alles: Lint, Typecheck, Tests und Build laufen in einem Aufruf, und der Agent führt ihn vor jedem Commit aus.
  5. Eine Skill verbietet nutzlose Tests: Tests prüfen beobachtbares Verhalten, keine Mocks, keine Duplikate.
  6. Die PR-Vorlage erzwingt Beweise: Blast Radius, Gate, Rollback, Tests, Logs, Screenshot und Einschätzung des Agenten stehen in festen Abschnitten.
  7. Die Review-Tiefe folgt dem Baum: Blätter werden über ihre Beweise geprüft, Stamm-Änderungen Zeile für Zeile mit Blick auf Aufrufer und Seiteneffekte.
  8. Ein adversarialer Agent prüft ohne Kontext: Frischer Subagent oder separate Session, mit der Erlaubnis, eine leere Liste zurückzugeben.
  9. Die Kommentar-Schleife läuft als Goal-Loop: Mit festem Intervall und einer harten Stopp-Regel für alles, was dein Urteil braucht.
  10. Launch läuft über das Gate: Audits mit festen Fragen, Refactoring, dann prozentuale Freischaltung mit Blick auf Alerts und einem Rückweg, der kein Deployment braucht.

Wer jede Zeile gleich gründlich liest, reviewt den Diff und nicht das System.

Häufig gestellte Fragen (FAQ)

Muss ich KI-generierten Code vollständig lesen? ↓

Nein, aber du musst wissen, welche Teile du lesen musst. Code mit vielen Abhängigen wie Einstiegspunkte, geteilter State oder Infrastrukturschichten wird Zeile für Zeile geprüft. Isolierter Code ohne Aufrufer, der zusätzlich hinter einem Feature Gate liegt, wird anhand von Tests, Logs und Screenshots überflogen. Der Review-Aufwand folgt dem Blast Radius der Änderung, nicht der Länge des Diffs.

Was gehört als Beweis in einen Pull Request eines Coding-Agenten? ↓

Vier Dinge: Unit-Tests, deren Namen und Asserts die abgedeckte Logik zeigen, Laufzeit-Evidenz in Form von Logauszügen des neuen Pfads, visuelle Evidenz als Screenshot oder Aufnahme bei UI-Änderungen und eine Einschätzung des Agenten, wie sicher er sich bei Logik, Gate und Integration ist. Eine feste PR-Vorlage erzwingt diese Struktur und verhindert, dass der Testplan länger wird als der Diff.

Warum sollte ein anderer Agent den Code reviewen als der, der ihn geschrieben hat? ↓

Ein Agent, der seine eigene Arbeit in derselben Session prüft, kennt alle Annahmen aus Plan und Verlauf und bestätigt sie. Ein frischer Subagent oder eine separate Session ohne diesen Kontext prüft adversarial, also mit dem Auftrag, Fehler zu finden statt Fertigstellung zu bestätigen. Claude Code bringt dafür /code-review mit, Codex einen eigenen Review-Modus und eine GitHub-Integration.

Wann ist ein KI-generiertes Feature launchfähig? ↓

Nicht, wenn der Pull Request grün ist. Der gated und gemergte Stand ist ein grober Entwurf. Danach folgen Audit-Agenten mit festen Fragen zu Performance, Sicherheit, offenen Bugs und Abgleich gegen die Spezifikation, dann Refactoring und Detailarbeit. Der Launch selbst läuft über das Feature Gate, schrittweise für einen Teil des Traffics, mit Blick auf Crashes und Alerts.

Was ist ein Feature Gate und reicht ein Boolean dafür? ↓

Ein Feature Gate ist eine Verzweigung im Code, die neues Verhalten ein- oder ausschaltet. Für den Anfang reicht ein Boolean in einer Konfigurationsdatei, das der Reducer oder Service prüft. Werkzeuge wie LaunchDarkly ergänzen Targeting nach Nutzergruppen, prozentuale Ausrollung und Auswertung. Entscheidend ist, dass das Gate vor dem Coding geplant wird und bei geschlossenem Zustand exakt das alte Verhalten liefert.

Autor hyretic

Senior Full-Stack Developer mit Fokus auf stabiler Software-Architektur, pragmatischem Engineering und der Realität von KI im Entwickler-Alltag. Seine Wurzeln liegen im praktischen Lösen komplexer Probleme unter realen Bedingungen.

github.com/hyretic-dev

Ähnliche Artikel