20 KiB
Learnings
Was beim Bau von planer2 gemessen wurde. Nicht was plausibel klang — was sich nachweisen ließ. Geschrieben, bevor Prüfung 3 entfernt wurde, damit ihr Wissen bleibt.
Stand: 29.07.2026
Der Aufbau in einem Absatz
Markdown-Dateien sind die Wahrheit. Eine Karte ist ein Auftrag, eine Funktion etwas, das man für sich testen kann, eine Regel eine Zusicherung. Der Code eines fremden Projekts ist Beleg, nie Quelle der Wahrheit. Drei Prüfungen glichen ab: Stil, „hat jede Funktion Code" und „hat jede Codestelle eine Funktion".
1. Was funktioniert hat
Beweislast statt Meinung
Jede Aussage über den Code muss eine wörtlich zitierte Zeile mitbringen.
quellcode.belegt() schlägt sie gegen die Datei. Ein misslungenes Zitat zählt
nicht als Fund.
Das ist der wichtigste Baustein des ganzen Systems. Ohne ihn schlug ein früher Versuch vor, gültige Inhalte zu löschen — das Modell urteilte direkt und lag falsch. Mit ihm ist ein Fund nachprüfbar und ein Nichtfund die einzige Aussage, die überhaupt Stimmen braucht.
Die Asymmetrie ist der Hebel. Vorhandensein lässt sich zitieren, Nichtvorhandensein nicht. Also: ein geprüfter Fund genügt zum Freispruch, sechs Fehlversuche sind die Anklage.
Das Urteilsgedächtnis
Ein Urteil kostet sechs Anfragen und fällt bei Grenzfällen mal so, mal so. Genau daran scheiterte die Wiederholbarkeit: derselbe Stand lieferte in Runde 1 einen Befund, in Runde 2 keinen, in Runde 3 wieder einen.
Gespeichert wird deshalb das Urteil, nicht nur das Ergebnis (ledger.py,
.pruefung.json im Projektordner). Ein Eintrag gilt weiter, solange seine
Voraussetzung nachweisbar dasteht:
- ein Beleg, solange die zitierte Zeile noch in der Datei steht
- eine Fehlmeldung, solange sich am Quelltext nichts geändert hat
Das machte gleichen Stand zu gleichem Ergebnis. Ohne dieses Gedächtnis gibt es keine Konvergenz — der Loop würfelt jede Runde neu.
Der Zuschnitt des Fingerabdrucks entscheidet über die Kosten. Anfangs ging der Abdruck über den ganzen Funktionstext. Eine Stilkorrektur an einer Regel tötete damit auch das Inhaltsurteil. Gemessen: R1 code 384 Anfragen, R2 code 384 Anfragen — null Treffer im Gedächtnis. Nach der Kürzung auf Karten- und Funktionstitel: R3 feature 30 statt 259, R3 code 21 statt 201. Rund 88 % Trefferquote, sobald die Stiländerungen abgeklungen sind.
Regel daraus: ein Urteil verfällt nur, wenn sich ändert, worauf es tatsächlich beruht. Nicht wenn sich der Text drumherum ändert.
Frühabbruch statt fester Stimmenzahl
Sechs Stimmen je Einheit waren gemessen richtig für die Schwelle, aber verschwenderisch für den Normalfall. Ein nachgeschlagener Beleg beendet die Frage — die anderen fünf Antworten wurden bezahlt und weggeworfen.
Stufen (1, dann 2, dann 3) mit Abbruchprüfung nach jeder Stufe:
- Prüfung 2: von 6 auf ~1 Anfrage im Normalfall
- Prüfung 3: von 6 auf ~3
Gesamter Loop auf planer2: 2719 → 1354 Anfragen bei gleicher Rundenzahl.
Eine Falle dabei: Eine ausgefallene Stimme ist keine Ja-Stimme. Der erste Wurf brach ab, sobald die verbleibenden Stimmen die Schwelle rechnerisch nicht mehr reißen konnten — und hielt damit einen HTTP-429 für „ist abgedeckt". Fix: nur abbrechen, wenn alle gestellten Fragen auch beantwortet wurden.
Schwellen misst man, man rät sie nicht
Alle Zahlen in inhalt.py stammen aus Messungen an gepflanzten Lücken:
FEHLT_AB = 4von 6 — eine echte Lücke war immer einstimmig; strittige Stellen lagen darunter und kippten zwischen LäufenTITEL_KANAL = 0.85,INHALT_KANAL = 0.7— darunter explodierten die KandidatenpaareKERN_MIN = 3— unter drei Wörtern kennt die Überlappung nur 0, 0,5 und 1BUENDEL = 6Stellen je Anfrage — größere Bündel verloren Stellen aus der Mitte
Der Versuch, Grenzfälle lexikalisch zu filtern, ist gescheitert: „Produkt merken" und „Konfiguration merken" sehen identisch aus, einer ist eine echte Lücke, der andere nicht. Die Lösung war eine Entscheidung, keine Formel: empfindlich sein (4 von 6) und Ablehnungen dauerhaft merken.
Der Musterkatalog als Daten
Was eine auslösbare Stelle ist, steht in muster/*.toml, nicht im Code. Ein neues
Rahmenwerk ist eine Datei, keine Codeänderung.
Auslöser dafür: Bei einem Shopware-Plugin fand das Inventar genau eine Stelle,
weil Twig, Symfony-Attribute und element.addEventListener als Regex im Programm
nicht vorkamen. Nach dem Katalog: 19 Stellen.
Zwei Nachbesserungen, die viel ausmachten:
store.reroll()ist die Fähigkeitreroll, nichtstore. Ohne diese Regel fielen zwölf Knöpfe eines Stores auf einen einzigen Eintrag zusammen.- Symfony schreibt Routen benannt (
path: '/x') oder positionell (Route('/x', ...)). Nur die benannte Form zu kennen fand 5 von 127 Routen.
Erzeugen aus dem Inventar, nicht aus dem Quelltext
Der Erzeuger geht von den Stellen aus, nicht vom Code als Ganzem. Damit ist Vollständigkeit Bauart, nicht Hoffnung: jede Stelle bekommt eine Funktion, jede Funktion stammt von mindestens einer Stelle.
Was das Modell frei aufzählt, war jede Runde anders, und niemand konnte sagen, ob etwas fehlt.
Vier Schritte: benennen (je Stelle) → falten (gleiche Titel zusammenlegen) → verdichten (alle Funktionen nebeneinander neu ordnen) → füllen (je Funktion Ziel, Regeln, Testschritte aus dem Code).
Verdichten ist der Schritt, an dem Minimalität entsteht. Ohne ihn kamen aus 21 Stellen 17 Karten mit je einer Funktion. Mit ihm: 5 Karten, 19 Funktionen.
Nachfassen statt Verlust
Fällt eine Anfrage aus, fehlen sechs Stellen auf einmal. Bei 19 Stellen ist das fast die Hälfte — und die Karten waren still unvollständig. Gemessen: 8 von 19 Stellen blieben unbenannt.
Fix: kleinere Bündel nachfragen (6 → 2 → 1), und was auch einzeln keinen Namen bekommt, aus dem Schlüssel benennen. Ein grober Titel fällt auf; eine fehlende Stelle fällt niemandem auf.
Dasselbe Muster beim Verdichten: fiel eine Zuordnung aus, blieben ihre zwölf Funktionen auf Einzelkarten stehen — aus 19 Fähigkeiten wurden wieder 16 Karten.
2. Was nicht funktioniert hat
Prüfung 3 — drei Konstruktionsfehler
Die Prüfung „hat jede Codestelle eine Funktion" hat nie zuverlässig funktioniert. Nicht wegen des Modells, sondern wegen der Grundmenge, gegen die sie zählt. Gemessen an planer2 (64 Stellen, 45 Funktionen):
1. Eingabefelder fehlen ganz. v-model="suche" ist kein Muster. Vier solcher
Felder im Projekt, keins im Inventar. Die Feature-Suche existiert, wird aber nie
gezählt und nie gemeldet.
2. Argumente fallen weg. Der Schlüssel ist der Funktionsname. Alle vier
Prüfknöpfe rufen startePruefung(projekt, art) → eine Stelle statt vier. Vier
Fähigkeiten, ein Punkt.
3. Gleichnamige Funktionen fallen zusammen. alleAnnehmen gibt es in
FeatureKarte.vue und in FeatureListe.vue — zwei verschiedene Dinge, ein
Punkt. Der neue Sammelknopf „Alle N Karten anlegen" ist dadurch unsichtbar.
Ergebnis: Gegen das eigene Inventar meldete Prüfung 3 62 von 64 = 97 %. Gegen das, was das Programm wirklich kann, waren es rund 44 von 52 = 85 %.
Dazu zwei falsche Abgedeckt-Urteile. DELETE /pruefung und GET /chat
tragen keine Funktion. Prüfung 3 meldete null Lücken — das Modell hielt sie für
gedeckt, weil eine ähnlich klingende Funktion danebenstand.
Zusicherungen ohne Auslöser sind unsichtbar
Der größte Fund, und er gilt weit über Prüfung 3 hinaus.
Vieles, was ein Programm zusichert, hängt an keinem Auslöser: eine Beschriftung im Markup, eine Vorbelegung beim ersten Aufruf, die Reihenfolge von Dingen, ein Standardfall, eine Grenze.
Gemessen an MtoVariantConfigurator: Von acht Anforderungen fehlten fünf auf den erzeugten Karten, obwohl der Code sie umsetzte. Transitivität der Dropdowns, Reihenfolge aus dem Backend, Vorbelegung aus dem Elternartikel, Schritt-Badge, Hinweistexte. Alle vorhanden, alle unsichtbar.
Die Vollständigkeitsprüfung meldete dabei null Lücken — sie zählt nur Auslöser.
Der Fix, der wirkte: Karten bekamen eigenes Ziel und eigene Regeln, gefüllt aus den ganzen Dateien statt aus fünf Zeilen um die Stelle. Dazu ein Nachtrag-Schritt je Karte: „was steht im Code und fehlt hier?". Danach 8 von 8.
Was nicht wirkte: Der Prompt allein. „Nenne die Sache, nicht die Umsetzung" stand von Anfang an drin und half nicht.
Dubletten: Zeichenähnlichkeit fängt Typ 4 nicht
„Galerie vorwärts blättern" und „Galerie nächste anzeigen" sind dieselbe Sache. Gemessene Titelähnlichkeit: 0,53. Schwelle: 0,85. Das Paar wurde nie Kandidat — kein Modell hat es je gesehen.
Die Klon-Typologie erklärt es: Typ 1 (exakt) und Typ 2 (umbenannt) fängt String-Matching. Typ 4 (gleiche Bedeutung, andere Worte) nicht.
Der Fix: ein vierter Kanal — alles auf derselben Karte wird Kandidat. Bezahlbar, weil eine Dublette fast immer neben ihrer Zwillingsschwester steht. Zehn Funktionen ergeben 45 Paare, nicht Tausende. Fand sofort zwei echte Dubletten, die vier Runden lang durchgerutscht waren.
Streuung: gemessen, nicht behoben
Gleicher Code, gleiche Einstellungen, andere Karten. Lange nur eine Beobachtung, dann eine Zahl.
Adjusted Rand Index über die Zerlegung der Codestellen (Titel ändern sich zwischen Läufen, Stellen nicht):
| Mittel | Minimum | |
|---|---|---|
| Karten | 0,34 | 0,28 |
| Funktionen | 0,53 | 0,51 |
Kartenzahl über drei Läufe: 9, 9, 13.
0,34 heißt: die Kartenzuordnung ist näher am Zufall als an einer Entscheidung. Das ist der ungelöste Kern des Erzeugers.
Was die Streuung senkte, aber nicht beseitigte: die Codezeile als Anker beim Umbenennen. Ohne sie wurde aus „Konfigurator öffnen" ein „Erweiterung aktivieren" — das Modell sah nur den Titel.
Der volle Stilkatalog verdirbt Struktur-Antworten
Nach Kartentiteln gefragt, antwortete das Modell mit ganzen Karten samt Testschritten. Die Beispiele im Katalog sind stärker als die Formatangabe.
Fix: nur die Titelregeln mitgeben. Allgemeine Lehre — gib einem Schritt nur den Teil des Katalogs, den er braucht.
Zu viel Parallelität
Vier gleichzeitige Läufe → HTTP 429. Ausgefallene Stimmen sahen aus wie „nichts gefunden". Nacheinander laufen lassen war die Lösung, nicht mehr Wiederholungen.
3. Was aus der Recherche belegt ist
Eine Recherche zu Verfahren für vollständige und minimale Extraktion lieferte Belege für einiges, was hier gebaut wurde, und ein paar Warnungen.
Bestätigt:
- Adaptive Consistency (Aggarwal et al., EMNLP 2023): Frühabbruch senkt das Stichprobenbudget „by up to 7.9 times with an average accuracy drop of less than 0.1%". Deckt sich mit der hier gemessenen Halbierung.
- Early cutoff aus der Build-System-Forschung (Mokhov et al., ICFP 2018): ein Urteil verfällt nur, wenn sich seine echten Abhängigkeiten ändern. Genau das Problem des zu groben Fingerabdrucks.
- Mutanten als Ersatz für echte Fehler (Just et al., FSE 2014): 73 % Kopplung über 357 echte Fehler. Rechtfertigt gepflanzte Lücken als Vollständigkeitsmaß — mit der Einschränkung, dass rund ein Viertel ungekoppelt bleibt.
- Extraktive statt abstraktive Erzeugung senkt Halluzination messbar. Hier: das Zitat, das gegen die Datei geschlagen wird.
Gewarnt:
- Chao1 und Capture-Recapture schätzen Vollständigkeit systematisch zu niedrig, besonders bei kleinen Stichproben. Für 19 Einheiten wertlos.
- Testsuite-Minimierung erkauft Größenreduktion mit Erkennungsverlust: „over 70 %" Reduktion, „around 12.5 %" weniger gefundene Fehler. Aggressive Minimalität hat einen Preis.
- Self-Consistency hilft bei konvergenter richtiger Antwort und schadet bei systematischem Bias. Mehr Stimmen ersetzen keine bessere Verankerung.
Nicht anwendbar hier: Dynamische Invariantenerkennung (Daikon) und Coverage-Kriterien brauchen einen laufenden Zielprozess mit Tests. Das System liest nur Dateien fremder Projekte.
4. Synchronisation zwischen Planung und Code
Zweite Recherche, gezielt auf die Frage: wie hält man eine fachliche Beschreibung und den Code dauerhaft synchron, in beide Richtungen? 25 Behauptungen gegengeprüft, 13 überlebt, 12 verworfen.
Beide Richtungen enden bei etwa der Hälfte
Planung → Code: 53,5 %. E2EDevBench (50 reale PyPI-Projekte, Anforderungsdokument als einziger Eingang) misst 53,50 % umgesetzte Anforderungen in der besten Konfiguration. Ein unabhängig gebauter Benchmark mit anderen Autoren und anderen Agenten-Rahmenwerken kommt auf 53,75 %. „The other half of the requirements remained unfulfilled."
Die Abnahme durch einen Menschen ist damit kein Sicherheitsgurt, sondern die gemessene Notwendigkeit.
Code → Planung: 59 %. Polikarpova, Ciupa, Meyer (ISSTA 2009, 25 Klassen, 831 menschliche Klauseln, über 19.500 abgeleitete): Automatische Inferenz findet 59 % aller von Programmierern geschriebenen Zusicherungen wieder — mit kleiner Testsuite nur 50 %. Autorenfazit: „contract inference cannot completely replace the manual work of writing assertions."
Und die Ausbeute ist verrauscht: 90 % der abgeleiteten Klauseln sind korrekt, aber nur 64 % relevant. Rund ein Drittel muss aussortiert werden.
Der brauchbarste Einzelbefund
Der Engpass ist nicht das Denkvermögen, sondern der Ausdrucksvorrat.
Von der Abdeckungslücke gehen 31 Prozentpunkte auf „nicht ausdrückbar" und nur etwa 10 auf „ausdrückbar, aber nicht gefunden" — Verhältnis rund 3:1. Und von den nicht ausdrückbaren Klauseln scheiterten 54 % allein daran, dass sie eine Funktion mit einem Argument aufrufen, weitere 19 % an fehlenden Implikations-Schablonen.
Die Autoren widersprechen ausdrücklich der naheliegenden Deutung: „It is tempting to draw the conclusion that at present the tool cannot compete with humans on higher levels of abstraction. However … 'abstract' assertions are mostly not expressible by the tool."
Übertragen auf uns, als Hypothese ohne Messung: dem Extraktor das Fachvokabular mitzugeben — Funktionsnamen, Domänenbegriffe — schlägt vermutlich ein besseres Modell.
Die Beleglücke ist der eigentliche Befund
Für drei Dinge liefert der ganze Korpus keinen einzigen belegten Mechanismus:
- Inkrementelle Synchronisation aus Commits, Diffs, Pull Requests. Alles Gemessene ist Greenfield-Vollerzeugung.
- Drift-Erkennung ohne Vollvergleich. Die einzige geprüfte Behauptung dazu — Invarianten als Verhaltens-Fingerabdruck — wurde 1:2 verworfen.
- Zwei-Wege-Konflikte. In dem untersuchten Spec-Driven-Rahmenwerk kommen die Begriffe traceability, drift, bidirectional und round-trip null mal vor. Diese Verfahren sind einbahnig.
Das heißt nicht, dass es nicht geht. Es heißt, dass niemand es gemessen hat.
Was gemessen wurde und fast nichts brachte
Prozess- und Strukturarbeit auf der Spezifikationsseite:
- Kontext-Hooks in einem Vier-Phasen-Ablauf (Specify → Plan → Tasks → Implement): +3,0 % der Skala
- Spezifikation nach Epics gliedern: +1,4 Prozentpunkte Abdeckung, gleichzeitig −4,0 Prozentpunkte auf der lexikalischen Messgröße
Der einzige stabile Gewinn kommt aus der Erdung im echten Code und aus ausführbaren Testsuiten. Das deckt sich mit unserem eigenen Fund: der Prompt allein half nie, das Zeigen ganzer Dateien half sofort.
Verworfen — und nicht durch die Hintertür zurückholen
Sechs Mechanismen, die für unsere Architektur naheliegend gewesen wären, haben die Gegenprüfung nicht überstanden:
| Behauptung | Abstimmung |
|---|---|
| Invarianten-Vergleich als Drift-Fingerabdruck | 1:2 |
| LLM-Richter je Anforderung als referenzfreie Abdeckungsmessung | 0:3 |
| Spurious Scenario Rate als Überspezifikations-Melder | 0:3 |
| Einbettungs- statt Wortabdeckung als Messgröße | 0:3 |
| Daikon-Präzision über 95 % gegen ESC/Java | 0:3 |
| Ein Zwischenartefakt Plan/Design schade der Treue | 0:3 |
Besonders bitter: der Fingerabdruck-Gedanke. Er hätte genau unsere Drift-Frage gelöst.
Ein Widerspruch, der offen bleibt
Die Behauptung, Inferenz decke systematisch Zusicherungen auf, wo kein Mensch etwas notiert hatte — im Mittel 4,9-fach so viele relevante Klauseln, 13,3-fach bei Schleifeninvarianten — wurde als eigenständige Behauptung 0:3 verworfen. Dieselben Zahlen tauchen aber in der bestätigten Belegkette einer anderen Behauptung wieder auf. Der Widerspruch ist ungelöst.
Die Zahlen gelten damit nicht als belegt. Die Richtung — Extraktion ergänzt den Menschen, ersetzt ihn nicht — ist mehrfach gestützt.
Grenzen dieser Recherche
- Alter. Die härtesten Zahlen (59 %, 64 %, 54 %) sind von 2009 und hängen am damaligen Schablonensatz. Sie sind keine Obergrenze für LLM-Extraktion — dazu existiert keine Messung.
- Quellenlage. Vier der sechs tragenden Quellen sind unbegutachtete Preprints. Begutachtet sind zwei plus eine Industriefallstudie.
- Transfergrenze. Alle harten Zahlen messen maschinenprüfbare Artefakte — ausführbare Vor- und Nachbedingungen, Eiffel-Kontrakte, Gherkin. Unsere Planung ist Prosa für Projektmanager. Der Schluss von „59 % der formalen Zusicherungen" auf „x % der fachlichen Beschreibung" ist Plausibilität, keine Messung.
- Zirkularität. Die 53,5 % beruhen auf einem Modell als Richter, das teilweise eigene Ausgaben bewertet.
Was das für planer2 heißt
- Prüfung 2 sitzt auf dem belegten Teil. Beleg gegen die Datei geschlagen, ein Fund genügt, ein Nichtfund braucht Mehrheit.
- Prüfung 3 hätte auf dem unbelegten gesessen. Das Streichen war richtig — und jetzt auch von außen gestützt.
- Der inkrementelle Normalfall ist Neuland. Kein Beleg dagegen, aber auch keiner dafür. Wer ihn baut, misst selbst oder weiß nichts.
- Die Kostenlage stützt ihn trotzdem: Vollsynchronisation kostete im Benchmark ~7 $ und 5,24 Mio. gesendete Token je Projekt — für 53,5 %.
5. Handkarten gegen erzeugte Karten
Ein direkter Vergleich auf MtoVariantConfigurator.
| Handkarten | Erzeuger | |
|---|---|---|
| Runden bis still | 2 | 3 |
| Anfragen | 361 | 1044 |
| Karten | 7 | 4 |
| Funktionen | 26 | 17 |
| Anforderungen erfüllt | 8 von 8 | 5 von 8 |
Die Handkarten entstanden aus Issue, Wiki und allen 47 Kommentaren plus dem Code. Die Prüfungen änderten daran nur drei Stilkleinigkeiten.
Der Unterschied liegt nicht bei den Funktionen — die findet der Erzeuger. Er liegt bei den Kartenregeln, also bei den Zusicherungen ohne Auslöser.
Ein Fehler dabei, der eine eigene Lehre ist: Beim ersten Anlauf habe ich nur Beschreibung und Wiki gelesen und „8 von 8 erfüllt" gemeldet. Die 47 Kommentare standen in derselben Datei. Die Zahl war falsch, weil die Grundmenge falsch war. Wer Vollständigkeit misst, muss zuerst prüfen, ob er die Quelle ganz gelesen hat.
6. Zahlen zum Nachschlagen
Vier Projekte, Loop aus Erzeugen und drei Prüfungen bis zur Ruhe.
| Projekt | Dateien | Stellen | Karten | Funktionen | Runden | Anfragen |
|---|---|---|---|---|---|---|
| MtoVariantConfigurator | 19 | 19 | 4–7 | 17–26 | 2–3 | 361–1044 |
| tft | 44 | 21 | 5 | 20 | 3 | 616 |
| planer2 | 34 | 64 | 8–13 | 46–51 | 4–5 | 1354–2719 |
| MtoRagSystem | 475 | 183 | 23–33 | 150–168 | 5+ | ~4000 |
MtoRagSystem: 6,6 Mio. Zeichen Quelltext. Prüfung 2 schickte anfangs alles je Funktion und fiel für das ganze Projekt aus. Mit Vorauswahl (erst fragen, welche Dateien, dann nur die zeigen): 149 von 150 Funktionen belegt, 0 Fehler.
7. Offene Fragen
- Streuung. ARI 0,34 bei den Karten. Verankerung half, löste es nicht. Deterministische Gruppierung wäre reproduzierbar, aber nicht zwingend richtig.
- Grundmenge für Zusicherungen. Karten-Regeln plus Nachtrag-Schritt haben den konkreten Fall gelöst. Ein zählbares Maß dafür gibt es nicht.
- Belege je Funktion. Prüfung 2 merkt sich nur einen Beleg — den ersten, der sich nachschlagen ließ. Für eine spätere, deterministische Abdeckungsrechnung bräuchte sie alle.
- Körnung. Wann sind zwei Dinge eine Funktion, wann zwei? Es gibt kein belegtes absolutes Maß dafür ohne Referenzmodell.