Files
planer2/LEARNINGS.md
2026-07-29 15:19:41 +02:00

20 KiB
Raw Blame History

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 = 4 von 6 — eine echte Lücke war immer einstimmig; strittige Stellen lagen darunter und kippten zwischen Läufen
  • TITEL_KANAL = 0.85, INHALT_KANAL = 0.7 — darunter explodierten die Kandidatenpaare
  • KERN_MIN = 3 — unter drei Wörtern kennt die Überlappung nur 0, 0,5 und 1
  • BUENDEL = 6 Stellen 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ähigkeit reroll, nicht store. 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.