Files
Pascal Michel ae3ec3ca89 Abgleich mit dem Vault-Stand 22761eb
11 Datei(en) geändert.
Erzeugt von .vault-tools/auto-push/werkzeuge_spiegel.py.
2026-11-03 03:00:04 +01:00

40 KiB
Raw Permalink Blame History

type, status, title, summary, tags, created, updated
type status title summary tags created updated
resource active Planwerkzeuge: Plan auf eine Kopie anwenden und jede Welle dagegen vergleichen Skripte für die Standardschleife bei Plänen mit ausgeschriebenem Code — schlanke Kopie bauen, Plan auf die Kopie anwenden, je Welle Menge und Bytes vergleichen, Aus-wird-Blöcke prüfen, Codeblöcke auf unsichtbare Zeichen absuchen, Escape-Folgen per Codepunkt setzen. Entstanden 2026-09-17 in der Trading Plattform (Teil 3b).
vault-tools
codeworkflow
2026-09-17 2026-11-02

Planwerkzeuge für die Kopie mit angewendetem Plan

Die Regel dazu steht in AI/Regelwerk/codeworkflow („Die Kopie mit dem angewendeten Plan bleibt stehen …"). Hier steht nur, wie man die Skripte benutzt. Alle laufen mit dem Windows-Python: PYTHONIOENCODING=utf-8 python -X utf8 <skript> ….

Ablauf

  1. Kopie bauen — erst, wenn der Planer fertig gemeldet hat. Den Projektbaum ohne .git, ohne Geheimnisdateien und ohne node_modules in einen Wegwerfordner kopieren. Ausschlüsse als Pfade vom Wurzelordner angeben, nicht als Ordnernamen: Ein Ausschluss von „jedem Ordner namens data" hat am 2026-09-17 auch das Paket trading/data getroffen, und die Suite der Kopie brach mit Sammelfehlern ab. Nach dem Kopieren die Paketliste gegen das Original halten. Beides erledigt kopie_bauen.py <projekt> <ziel> --ohne <relpfad> …: Es nimmt Ausschlüsse nur als Pfade vom Wurzelordner, meldet einen Ausschluss ohne Gegenstück („ein Name statt eines Pfads?"), überspringt Verknüpfungen und Junctions, lässt __pycache__ weg, weigert sich bei einem vorhandenen Ziel und vergleicht am Ende die Python-Pakete beider Bäume. Für die Trading Plattform: --ohne .git dashboard/node_modules dashboard/dist data artifacts config/secrets.toml docs/plans. Braucht die Kopie node_modules, eine Junction setzen (mklink /J) — und sie vor dem Löschen der Kopie mit cmd //c rmdir <pfad> wieder lösen, sonst trifft rm -rf das echte node_modules. Nie tsc -b durch die Junction fahren (schreibt nach node_modules/.tmp); npx tsc -p tsconfig.app.json --noEmit nehmen. Auch Vitest schreibt hinter die Junction: Vite legt beim Laden der Konfiguration node_modules/.vite-temp an und löscht die Datei danach, unabhängig von cacheDir (gemessen 2026-11-02 im Nexhorizons Hub, Vite 8.3.0); cacheDir in einer Wegwerf-Konfiguration hält nur den eigentlichen Cache aus dem fremden node_modules heraus.

  2. Plan anwenden: plan_materialisiere.py <planordner> <kopie> task-01.md task-02.md … in Wellenreihenfolge. Erkennt die Marken Aus: / wird:, „Die ganze Datei ist neu", „vollständig ersetzt" und „Häng diesen Block ans Ende"; Dateien, die ## Zieldateien als „(gelöscht)" führt, löscht es in der Kopie. Prosa-Tasks („wird: ein neuer Abschnitt, er muss sagen …") lassen sich nicht anwenden. Vor dem ersten Schreibvorgang prüft es die Zäune aller übergebenen Taskdateien; ein kaputter Zaun ist ein Befund mit Exit 2, und der Lauf schreibt nichts (unten, „Seit 2026-11-02“). Eine Datei ganz ohne Endung (host/logrotate/edge-traefik, Dockerfile, .gitignore) ist Ziel, wenn derselbe Pfad unter ## Zieldateien steht. Fehlt der Eintrag, ist ihr Block ein Befund mit Exit 2, außer bei einer neuen Datei ohne Schrägstrich (unten, „Seit 2026-10-17").

  3. Auf der Kopie fahren: Suite, Typprüfung, Mutanten. Ein Mutantenlauf gibt je Mutant die Fehlerzeile aus, nicht nur den Exitcode.

  4. Vor jeder Welle und nach jeder Plankorrektur: plan_bloecke.py vor <tasks…> --projekt <projekt> (jeder Aus-Block steht genau einmal im echten Baum) und plan_zeichen.py <planordner> (keine Zeilen- und Absatztrenner, Format- und Steuerzeichen in Codeblöcken).

  5. Nach jeder Welle: welle_vergleich.py --projekt <projekt> --mat <kopie> [--prosa <rel> …] -- <taskdateien der Welle>. Prüft, ob genau die Zieldateien geändert sind und ob jede byte-gleich mit der Kopie ist. Es zeigt je Datei nur die erste Abweichung — danach diff auf die ganze Datei. Eigene Plankorrekturen im Baum meldet es zu Recht als „außerhalb der Zieldateien". Eine Zieldatei, die git ignoriert (etwa Prüfdateien unter tests/.work/), vergleicht es seit 2026-10-17 ebenfalls byte-genau mit der Kopie und zählt sie in der Schlusszeile als „von git ignoriert"; vorher meldete es sie als „unveraendert gegenueber HEAD" und verglich sie nicht.

  6. Escape-Folgen setzen: plan_escape.py <datei> <zeile> <codepunkt-hex> … ersetzt echte Zeichen in einer Zeile durch ihre Escape-Folge, ohne dass eine Schreibschicht mitredet.

  7. Rot-Nachweis je Task (seit 2026-09-18): rot_nachweis.py <basis-kopie> <planordner> <arbeitsordner> <spec.json> [--fixturen <ordner>] [--node-modules <pfad>] [--conftest-als-test] [--venv <wsl-pfad>] [--frontend <ordner>] [--projektname <ordnername>]. Für ein anderes Projekt als die Trading Plattform braucht es die drei letzten Schalter (seit 2026-09-24): Ihre Vorgaben sind die der Trading Plattform (~/venvs/trading, dashboard, trading-plattform). Jedes andere Projekt nennt seine Werte, für den Nexhorizons Hub --venv '~/venvs/hub' --frontend frontend --projektname 'active - Nexhorizons Hub', das Venv in einfachen Anführungszeichen, sonst macht die Git-Bash aus ~ einen Windows-Pfad. Das Skript druckt die benutzten Werte in der ersten Zeile. Die Vorgabe trading-plattform passt nur auf Pläne mit Arbeitsbaum-Pfaden; ältere Trading-Pläne mit absoluten Pfaden unter active - Trading Plattform brauchen --projektname 'active - Trading Plattform', sonst endet der Lauf mit einem ValueError. Weiter fest verdrahtet bleiben die Distribution Debian, die Ordner tests/ und tests/fixtures/, die Endungen .test.ts/.test.tsx und npx.cmd vitest. Vorbedingung: Das Projekt ist im Venv nicht editierbar installiert (pip install -e); sonst löst der Finder von setuptools ein fehlendes Untermodul gegen das echte Projekt auf, und ein Test ohne Umsetzung läuft grün. tests/conftest.py gilt dabei als Produktivseite und wird zurückgesetzt; nur wenn die neue Fixture eines Tasks dort liegt, lässt --conftest-als-test sie stehen (Beleg unten, 2026-09-22). plan_materialisiere.py wendet eine Taskdatei immer ganz an; „erst der Test, dann die Umsetzung" lässt sich damit nicht beobachten. Das Skript stellt den Zustand dazwischen auf Wegwerfkopien her — Basis plus Voraussetzungstasks plus der Task, danach die produktiven Zieldateien zurück auf den Stand vor dem Task — und fährt die Testdateien des Tasks (pytest in WSL, vitest unter Windows über eine Junction, die es vor dem Löschen löst). Es meldet je Task ROT mit der ersten Fehlerzeile oder GRUEN; ein Test, der ohne Umsetzung grün ist, misst sie nicht. Die Basis-Kopie zieht man vor der ersten Welle. spec.json nennt je Task die Voraussetzungstasks. Erster Einsatz am 2026-09-18 an vier Plänen der Trading Plattform: 35 Tasks, alle rot an der gemeinten Stelle, keiner grün ohne Umsetzung. Die Executer brauchen den Tanz mit dem Beiseitelegen der Produktivdatei dann nicht mehr; ihr Auftrag sagt das ausdrücklich.

  8. Mutanten fahren (seit 2026-09-27): mutanten_lauf.py --wurzel <wegwerfkopie> --mutanten <planordner>/mutanten.toml [--nur ID,…] [--nur-muster] [--bericht <datei.json>] [--zeitlimit <s>], in WSL mit einem Python ab 3.11. Die Mutanten stehen nicht mehr als Prosa im Plan, sondern in mutanten.toml im Planordner. Das Format, die Status und die Abdichtung stehen in mutanten_lauf_ENTWURF.md. Das Skript verweigert eine Wurzel mit .git und jeden Pfad aus ohne. Seit 2026-10-06 sucht es die pytest-Konfiguration wie pytest selbst, aber nie oberhalb der Wurzel, und gibt sie mit -c vor; eine fremde pyproject.toml im Windows-Temp stört damit nicht mehr, und ein Bedienfehler von pytest steht mit dem Ende der pytest-Ausgabe im Befund statt als FEHLT je Test (Entwurf, Abschnitt 3, „Konfiguration“ und „Abbruch von pytest“). Ob die Suche pytest selbst entspricht, prüft mutanten_lauf_probe_konfig.py <mutanten_lauf.py> <arbeitsordner> mit dem Python eines Venvs, in dem pytest liegt (26 Aufbauten, Exit 0 ohne Abweichung). Es fährt zuerst die Grundlinie, dann je Mutant nur die genannten Tests, setzt die Datei zurück und prüft das per SHA-256. Je Test gibt es die Fehlermeldung mit aus; ob ein Test an der gemeinten Stelle fällt, liest man dort. --nur-muster prüft nur, ob jedes Muster genau einmal dasteht, ohne pytest. Die Kopie legt man in WSL unter /tmp an (cp -a, danach __pycache__ löschen). Auf /mnt/d läuft es auch, nur langsamer. Aus der Git-Bash ein Skript nie mit wsl … bash /mnt/d/… starten: Die Git-Bash macht aus dem Pfad einen Windows-Pfad. Aus der PowerShell starten oder MSYS_NO_PATHCONV=1 davorsetzen. Ein SIGTERM auf mutanten_lauf.py selbst (etwa timeout -k oder ein Hilfsagent, der abgebrochen wird) löst dort einen eigenen Handler aus: Der laufende Mutant wird per SHA-Probe zurückgesetzt, die Temp-Ordner werden geräumt, und die Ausgabe trägt ABBRUCH: SIGTERM (Treiber-Fall 35, mutanten_lauf_treiber.py). Die Plankritik fährt die Mutanten selbst (seit 2026-09-27, nachgebessert 2026-09-28): Liegt mutanten.toml im Planordner, startet plankritik.js in der Vorbereitung mutanten_lauf_kopie.sh. Das Skript kopiert die Kopie mit angewendetem Plan nach /tmp in WSL, fährt dort den Läufer in einem eigenen Netz-Namensraum (unshare --map-current-user --net, gemessen in WSL Debian als Benutzer claude: UID innen bleibt 1000, nur Schnittstelle lo, jede Verbindung nach außen scheitert; seit 2026-10-09 mit lo oben, siehe Nachtrag unten) und gibt die Ausgabe an die Linse arithmetik. Fehlt unshare ohne Netz, fehlen ip, setpriv oder python3 oder kommt lo im Namensraum nicht hoch, bricht das Skript vorher mit MUTANTEN_LAUF_FEHLER ab, statt ungesperrt oder ohne lo zu laufen. Der Arg mutanten wählt voll (Vorgabe), muster oder aus; die Rückgabe trägt dazu mutanten_status (aus, ohne_kopie, keine_toml, ohne_ergebnis oder exit_<n>) und mutanten_modus (voll, muster, aus). Der Wrapper gibt vor dem Lauf eine Zeile modus: voll oder modus: muster (…) aus; bei modus: muster weiß die Linse, dass kein Mutant gefahren ist. Der ganze Lauf hat 540 s (BUDGET_S, timeout -k 15), weil das Bash-Werkzeug eines Hilfsagenten nach zehn Minuten endet; eine überschrittene Grenze meldet die Zeile ZEITGRENZE. Je Mutant gilt zusätzlich ein Zeitlimit ZEITLIMIT_S (Vorgabe 120 s, muss kleiner sein als BUDGET_S): B1 mit 74 Mutanten brauchte 168 s gesamt, rund 2 s je Mutant. Bei Exit 2 oder 3 des Läufers sagt die Linse, der Lauf sei vor oder während der Mutanten gescheitert. Pfade übergibt man mit Vorwärtsschrägstrichen: wsl -d Debian -- … reicht die Befehlszeile an die Linux-Shell, und die verschluckt Backslashes.

  9. Korrektur als Ersetzungsliste (seit 2026-10-13): ersetzungen_einsetzen.py <planordner> <korrekturdatei> [--pruefen] setzt eine Liste aus Abschnitten ## Ersetzung N — Titel mit Datei:, Alt: und Neu: (Blöcke zwischen ~~~~text und ~~~~) der Reihe nach ein. Jeder Alt-Text muss zum Zeitpunkt seiner Ersetzung genau einmal in seiner Datei stehen, sonst schreibt das Skript gar nichts. Erst mit --pruefen, dann ohne; danach Schritt 4 und eine frisch gebaute Kopie. Die Korrekturdatei liest das Skript relativ zum aktuellen Ordner, nicht zum Planordner. Belegt am 2026-10-13 im Paket FP der Trading Plattform: 129 Ersetzungen in sechs Dateien, Prüflauf und Einsetzen ohne Befund.

Was belegt ist

Seit 2026-09-27 gibt es mutanten_lauf.py und mutanten_lauf_treiber.py (Punkt 3 der Arbeitsweise vom 2026-09-26). Der Treiber fährt die 35 Angriffsfälle aus dem Entwurf auf einem kleinen Projekt unter /tmp und prüft nach jedem Fall, dass das Projekt byte-gleich zurückgesetzt ist. Am selben Tag hat der Koordinator neun Fehler in eine Kopie des Läufers eingebaut, und der Treiber hat jeden an seinem Fall gefangen:

  • „passed“ zählt als rot
  • kein Zurücksetzen
  • keine .git-Prüfung
  • aus gleich wird erlaubt
  • HOME nicht abgedichtet
  • Sammelfehler nicht rot
  • Grundlinie lässt Übersprungenes durch
  • Zeitlimit zählt als OK
  • eine Teilzeile zählt als Treffer

Das Skript dazu liegt außerhalb des Vaults und ist nicht Teil der Werkzeuge. Erster echter Einsatz war am selben Tag B1 der Trading Plattform: 74 von 74 Mutanten getötet, alle Fehlschläge Zusicherungen, die Kopie danach unverändert. Eine Lehre aus der Übertragung: Nennt ein Plan einen parametrisierten Test ohne Fall, gehören nur die Fälle in rot_bei, die der Mutant umkippt. Bei D1 wären drei von fünf Fällen unter return False grün geblieben.

Nachbesserung vom 2026-09-28 (Prüfbericht desselben Tages): mutanten_lauf_kopie.sh läuft jetzt unter unshare --map-current-user --net, mit python3 -u und einem Zeitlimit je Mutant (ZEITLIMIT_S, kleiner als BUDGET_S), schreibt vor dem Lauf eine Modus-Zeile und räumt bei SIGTERM auf (timeout -k 15, eigener Handler in mutanten_lauf.py). plankritik.js gibt zusätzlich mutanten_status und mutanten_modus zurück und erklärt der Linse arithmetik Modus muster und Exit 2/3. Treiber-Fall 35 (SIGTERM mitten in einem hängenden Mutanten) und die zusätzlichen Fälle in faelle_workflows.mjs/mutanten_workflows.py sind gefahren und grün; die Rot-Ausgaben davor stehen im Bericht des Executers vom selben Tag.

Nachbesserung vom 2026-10-06 (Plankritik 2 von C1 im Projekt After Install, Plan docs/plans/2026-10-06-pytest-konfiguration.md und seine Nachbesserung): pytest-Konfiguration wie pytest selbst, aber nie oberhalb der Wurzel; Abbruch von pytest mit Ursache statt FEHLT. Belegt am selben Tag:

  • Treiber 45 von 45 grün. Die neuen Fälle 36 bis 45 waren je mindestens einmal rot: gegen den Läufer vor der Änderung (36, 38, 40), gegen den Stand vor der Nachbesserung (40, 41, 43, 45) und gegen kaputte Varianten (37 ohne eigene Konfiguration, 39 ohne --confcutdir, 42 ohne Rückgabewert 3, 43 ohne setup.py-Zweig oder ohne zweite Suche, 44 nach dem ersten Abbruch ohne weitere Gruppe).
  • Gegenprobe an allen sechs mutanten.toml im Vault, alter gegen neuer Läufer auf Kopien aus git archive HEAD unter /tmp: Headscale A, Mattermost, After Install C1 und D-40 gleich (175, 130, 69 und 41 Mutanten OK); Headscale B und Trading Plattform WH enden bei beiden mit Exit 2, weil ihr Stand nicht zum Plan passt. Unter dem Windows-Temp endete C1 mit dem alten Läufer mit 101 × FEHLT, mit dem neuen mit 69 OK.
  • Ein pruefer fand in der ersten Fassung den Symlink-Fall und fehlende Fälle; die Nachbesserung behob beides. Ein zweiter pruefer fand danach keinen Verhaltensfehler mehr, nur eine Aufräumliste (Kopfzeile je Interpreter ohne eigenen Fall, Kurzfassung bei Rückgabewert 3, ; und Seitenvorschub in tox.ini, Selbstprüfung der Probe, lange Fassung im Bericht, zwei Lücken benennen); sie ist in Nachbesserung 2 erledigt, Treiber 46 von 46.
  • Rohdaten: AI/secondbrain/linux-haertung-after-install-memory/2026-10-06-c1-abschluss/roh/ (02-*, 03-gegenprobe/, 07-*, 08-gegenprobe2/ mit dem nachgebesserten Läufer, 09-* für Nachbesserung 2).

Nachbesserung vom 2026-10-09 (Paket RB3 der Trading Plattform, Plan docs/plans/2026-10-09-mutanten-kopie-loopback.md): Im frischen Netz-Namensraum war lo aus. Jeder Test mit einem Server auf 127.0.0.1 scheiterte dort mit „Network is unreachable“ (Errno 101), auch ohne Mutant; die Mutanten T1 und T12 des Pakets liefen deshalb von Hand außerhalb des Namensraums. mutanten_lauf_kopie.sh startet den Läufer jetzt mit unshare --map-current-user --net --keep-caps, fährt lo mit ip hoch, wirft mit setpriv alle vererbbaren und ambienten Fähigkeiten ab und geht per exec in python3 über, ohne Zwischenschale. Eine Vorprüfung vor dem Kopieren verlangt ip, setpriv und python3, ein nutzbares lo mit einer Verbindung über 127.0.0.1 und CapEff und CapAmb gleich 0; sonst endet das Skript mit MUTANTEN_LAUF_FEHLER und Exit 2. 127.0.0.1 ist im Namensraum dessen eigenes lo, ein Dienst des Rechners bleibt unerreichbar. Belegt am selben Tag:

  • mutanten_lauf_kopie_treiber.py ruft das Skript wie die Plankritik mit Windows-Pfaden auf und fährt die Fälle W1 bis W13: Server auf 127.0.0.1 (W1), keine Route nach außen und ENETUNREACH (W2), uid wie außen und keine Fähigkeiten (W3), ip, setpriv oder python3 fehlen (W4, W5, W9), Zeitgrenze mit timeout als Elternprozess des Läufers und ohne Prozessreste (W6), Grundlinie ohne Server in beiden Modi (W7), Dienst des Rechners unerreichbar (W8). Seit Nachbesserung 1 auch: ip scheitert in der Probe (W10), ip meldet Erfolg und lo bleibt aus (W11), ip scheitert erst im Hauptaufruf nach der Modus-Zeile (W12), setpriv wirft keine Fähigkeit ab (W13). W10 bis W13 ersetzen dazu einzelne Werkzeuge durch kleine sh-Skripte. Neuer Stand ohne Fehler.
  • Gegen das Skript vor der Änderung rot: W1, W2, W4, W5, W9. Gegen kaputte Varianten genau an ihrem Fall, gefahren mit W1 bis W9: ohne setpriv W3, setpriv ohne exec W6, ohne Vorprüfung W4, W5 und W9, ohne --keep-caps alle (die Vorprüfung meldet Operation not permitted), ohne Namensraum W2 und W8. Gefahren mit W1 bis W13 (Nachbesserung 1, docs/plans/2026-10-09-mutanten-kopie-loopback-nachbesserung-1.md): ohne exit 2 nach der Probe W10, W11 und W13; Probe ohne setpriv W1 bis W9 und W12; Hauptaufruf ohne den Zweig nach || W12; Probe ohne Fähigkeitsprüfung W13.
  • Nicht abgeworfen und nicht geprüft: die Begrenzungsmenge (CapBnd) und no_new_privs. plankritik.js führt eine Vorprüfung mit MUTANTEN_LAUF_FEHLER wie bisher bei fehlendem unshare als ohne_ergebnis, ohne den Text der Meldung.
  • Die Fähigkeiten prüft das Skript nur an der Probe und nur CapEff und CapAmb, nicht am Läufer selbst. Alle vier Mengen am Läufer prüft Treiber-Fall W3.
  • Die Vorprüfung steht vor der Suche nach mutanten.toml. Fehlt auf einem Rechner ip, setpriv oder python3 oder kommt lo nicht hoch, endet das Skript deshalb auch für einen Planordner ohne mutanten.toml mit MUTANTEN_LAUF_FEHLER und Exit 2 statt mit KEINE_MUTANTEN_TOML. plankritik.js meldet dann ohne_ergebnis statt keine_toml, und die Linse erfährt nicht, dass die mutanten.toml fehlt. Das bleibt so.
  • Lauf mit echten Daten: die 73 Mutanten des Pakets RB3 (Kopie rb3d-kopie) durch das neue Skript, alle OK, T1 und T12 im Namensraum.
  • Ein pruefer fand keinen blockierenden Befund. Seine Lücken (kein Fall für den Fehlerzweig der Probe und den Zweig nach || im Hauptaufruf, Kopfkommentar zu weit gefasst, der Satz zur Reihenfolge oben) behebt Nachbesserung 1. Rohdaten: AI/secondbrain/trading-plattform-memory/2026-10-09-mkopie-roh/.

Seit 2026-10-11 schreibt plan_materialisiere.py nur noch innerhalb der Kopie. Vorher schlug ein absoluter Pfad in einer Überschrift unter Windows die Kopie (kopie / "D:/…" ist der absolute Pfad): Das Skript ersetzte dann die echte Datei, mit Exit 0 und „0 Befund(e)“. Gemessen an einer Probe-Datei im Scratchpad. Entdeckt beim Planen von Paket E9 der Trading Plattform, dessen Tasks 725 und 726 Dateien unter D:/Claude/.vault-tools/ ändern. Jetzt ist ein Ziel außerhalb der Kopie (absolut oder mit ..) ein Befund mit Exit 2, und nichts wird geschrieben. Belegt am selben Tag mit drei Fällen (Aus/wird absolut, ganze Datei absolut, ../): je Befund, die Dateien draußen unverändert; und mit einer Gegenprobe: Die Tasks 723 und 724 von E9 ergeben in einer frischen Kopie dieselben Zählungen und byte-gleiche Dateien. Pläne für Dateien außerhalb des Projekts nennen sie deshalb relativ zu einem Wurzelordner (etwa .vault-tools/trading-plattform-pruefung/abendkontrolle.py gegen eine Kopie, die diesen Ordner enthält).

Seit 2026-11-02 prüfen plan_materialisiere.py und plan_bloecke.py die Zäune einer Taskdatei, bevor sie einen Block anfassen. Anlass war Auftrag 13 der Kette 050 (Trading Plattform) am 2026-11-01: In task-1020 klebte der schließende Zaun zweimal an ]). Der Block lief bis in den nächsten Task, das erste Aus/wird-Paar dort ging ohne eigene Meldung verloren, und das Werkzeug sagte nur „Aus-Block steht 0-mal“. Das sah nach einem falschen Anker aus. Unter „Die ganze Datei ist neu“ schrieb derselbe Fehler Planprosa in die Zieldatei, mit Exit 0 und „0 Befund(e)“. Die Regel jetzt, n ist die Zaunlänge des Blocks:

  • Ein Block schließt mit mindestens n Backticks in einer Zeile ohne weiteren Text, höchstens drei Spalten tiefer als der Öffner (CommonMark 0.31.2). Tiefer ist es Inhalt. Vorher schloss jeder Einzug, auch ein Zaun in einer eingerückten Zeichenkette, und auch zwei Zäune auf einer Zeile.
  • Befund ist eine Zeile im Block, die auf mindestens n Backticks endet und nicht schließt (klebender Zaun, auch ein zu tief eingerückter).
  • Befund ist eine Zeile im Block, die mit mindestens n Backticks beginnt und nicht schließt: Der Schlusszaun fehlt, oder der Außenzaun ist zu kurz.
  • Befund ist ein Block, der bis zum Dateiende nicht schließt, und eine Taskdatei, die sich nicht lesen lässt.
  • plan_materialisiere.py prüft alle übergebenen Taskdateien vor dem ersten Schreibvorgang. Bei einem Befund schreibt und löscht der ganze Lauf nichts und endet mit Exit 2, auch die heilen Taskdateien davor bleiben ungeschrieben. Die Zählzeile je Task entfällt dann.
  • plan_bloecke.py meldet denselben Befund und prüft die Blöcke dieser Taskdatei nicht, die übrigen Taskdateien prüft es weiter. Ein Zaunbefund endet immer mit Exit 2, auch wenn sonst nichts geprüft wurde. Die Blocksuche ist jetzt dieselbe wie in plan_materialisiere.py (zaun() und schliesst()), vorher sah es nur Zäune in Spalte 0.
  • Eine Zeile mit weiterem Backtick hinter dem Zaun öffnet nichts, sie ist Inline-Code (CommonMark 0.31.2: „Info strings for backtick code blocks cannot contain backticks“). Vorher schluckte so eine Zeile den nächsten Block.
  • Bewusst entschieden: Ein Text im Block, etwa eine Zeichenkette in einem Test, der auf n Backticks endet oder mit ihnen beginnt, ist derselbe Befund. Von einem klebenden Zaun ist er nicht zu unterscheiden. Abhilfe ist ein längerer Zaun um den Block. Beim Text am Zeilenende steht oft ein Folgebefund weiter hinten, weil der echte Schlusszaun für die Suche einen Block öffnet; der längere Zaun löst beide.
  • Nicht entscheidbar: Ein nackter Zaun als eigene Zeile im Text, höchstens drei Spalten tiefer als der Öffner, schließt den Block. Geht die Zahl der Zäune auf, wird der Rest Prosa, und es kommt kein Befund. Text mit Zäunen braucht deshalb immer einen längeren Zaun um den Block. Der Treiberfall „Luecke: nackter Zaun als Zeile im Text“ hält das fest.
  • Kein Befund sind Markdown-Vorlagen in längeren Zäunen mit ##-Überschriften und kürzeren Zäunen darin, eingerückte Blöcke, Inline-Code und Zaunfolgen mitten in der Zeile oder in Anführungszeichen, Prosa außerhalb von Blöcken.
  • welle_vergleich.py liest keine Blöcke, nur ## Zieldateien, und bleibt unverändert. plan_zeichen.py und plan-diff/plan_diff.py haben je eine eigene Blocksuche mit derselben Lücke. Sie schreiben nichts und bleiben unverändert; was ein kaputter Zaun dort bewirkt, ist nicht gefahren, nur am Quelltext gelesen.

Belegt am 2026-11-02 (Rohdaten und Messskripte: AI/secondbrain/regelwerk-memory/2026-11-01-zaunpruefung/):

  • plan_zaun_treiber.py [--werkzeuge <ordner>] [--mutanten] fährt 114 Fälle, die Grundlinie zuerst, und prüft je Fall Exit, jede Befundzeile samt Zeilennummer, die Zahl der Befunde, die Summenzeile und jede Datei der Kopie. Die Angriffe aus paar() haben ein geheiltes Gegenstück, das schreiben muss; sonst wäre „nichts geschrieben“ auch grün, wenn die Fixture nichts anwendet. Gefahren unter Windows und in WSL Debian.
  • Alte Fassung 65 von 114 Fällen rot, neue 0. plan_ziel_treiber.py bleibt bei 33 von 33.
  • --mutanten baut 39 Fehler ein, jeder ist an seinem Fall gefangen.
  • task-1020 vor der Korrektur: genau zwei Befunde an den Zeilen 1678 und 1729, die Kopie bleibt unverändert. Die alte Fassung meldete dort 80 Folgefehler und schrieb fünf Dateien.
  • Alle Taskdateien im Vault und in den Jobordnern (2 253 verschiedene, 59 790 Blöcke): bloecke() alt und neu liefern dasselbe. Die Zaunprüfung meldet acht Zeilen in drei alten Doku-Tasks (task-89 und task-94 der Trading Plattform, task-08 von After Install). Alle acht sind echt: Der Außenzaun war zu kurz, der Block endete zu früh. plan_bloecke.py vor weicht in genau diesen drei von 188 Planordnern ab.
  • Kette 050: alle 50 Lieferungen, angewendet wie in kette_050.py (Basis plus transitive Vorgänger), mit alter und neuer Fassung: gleiche Meldungen, gleiche Bäume. Die eingesetzte Fassung meldet in den 52 Taskdateien der Lieferungen keinen Zaunbefund, zaun_scan.py kein KLEBT und kein OFFEN. Seine STRUKTUR-Treffer sind Markdown-Überschriften als Inhalt geschlossener markdown- und text-Blöcke und bleiben ohne Befund.
  • Zustandssuche über die echten Funktionen (messung/zaun_suche.py, 40 000 zufällige heile Dateien, je ein Zaun beschädigt): keine heile Datei mit Befund, wobei der Generator in Dreierblöcken keine inneren Zäune erzeugt; alle 4 915 klebenden Zäune gefunden.
  • Ein pruefer fand keinen blockierenden Befund. Eingearbeitet sind der Einzug, die zwei Zäune auf einer Zeile, die weitere Regel für den Zeilenanfang und die Lücken seiner Treiberfälle.
  • Lücken: Ein nackter Zaun als Öffner ist von einem Schlusszaun nicht zu unterscheiden. Fehlt der Schlusszaun und kommt als Nächstes ein nackter Zaun derselben oder größerer Länge, schließt der den Block, und die Rollen tauschen bis zum nächsten Öffner mit Sprache oder bis zum Dateiende; zwei solche Fehler heben sich auf. In der Zufallssuche blieben rund 2 % der fehlenden Schlusszäune still, rund 13 % der fehlenden oder klebenden Öffner. Dasselbe gilt für einen Backtick in der Sprachangabe, für Zäune hinter Listenpunkt oder Zitatzeichen und für Backtick-Zäune in Tilde-Blöcken.

Seit 2026-10-17 geht ein Block nicht mehr still in die falsche Datei, wenn seine Überschrift eine Datei ohne Endung nennt. Beide Werkzeuge nutzen dafür ordne() in plan_materialisiere.py; plan_bloecke.py importiert die Funktion. Anlass war die Plankritik von Paket R1 im Edge-Stack (Befunde 1 und 5): Die Überschrift „Stelle 30 — host/logrotate/edge-traefik" setzte kein Ziel, das Ziel blieb bei tests/systemd/test-units.sh aus Stelle 29, und der Block „Die ganze Datei ist neu" überschrieb diese Datei, mit Exit 0 und „0 Befund(e)". Die Regel jetzt:

  • Ziel ist der letzte PFAD-Treffer der Überschrift. Ohne ihn ist Ziel der letzte Token, der (ohne ./) genau so unter ## Zieldateien derselben Taskdatei steht. So wird eine Datei ohne Endung planbar: host/logrotate/edge-traefik, Dockerfile, .gitignore.
  • Ein absoluter Eintrag unter Zieldateien zählt relativ zum Projekt. Das ist in plan_bloecke.py --projekt, wie in welle_vergleich.py, und in plan_materialisiere.py der Ordner vor docs/plans im Pfad des Planordners. Ordner, Muster mit * und Pfade mit .. unter Zieldateien sind kein Ziel.
  • Nennt eine Überschrift kein Ziel, aber einen Token, der nach einer Datei aussieht, ist jeder schreibende Block danach ein Befund mit Exit 2. Das gilt auch unter Überschriften ohne Pfad, bis eine Überschrift mit Ziel kommt. Nach einer Datei sehen aus: ein relativer Pfad mit Schrägstrich, ein Name ohne Schrägstrich, der direkt in der Kopie (bei plan_bloecke.py im Projekt) als Datei liegt, und der Dateiname eines Eintrags unter Zieldateien. plan_materialisiere.py schreibt den Block nicht; plan_bloecke.py prüft ihn nicht gegen das vorige Ziel.
  • Kein Befund sind Ordner (config/), absolute Pfade (/proc), Token mit Leerraum (GET /api/paper) und Bezeichner ohne gleichnamige Datei (main, Order). Bestehende Pläne nennen sie in Überschriften über Blöcken, die das vorige Ziel meinen.
  • Verworfen ist, jeden Pfad ohne Endung als Ziel zu erkennen. Das hätte jede Überschrift, die nur einen Ordner ohne Schrägstrich am Ende (tests/.work/r1-01) oder einen API-Pfad (glattstellen/beginn) nennt, zum Ziel aller Blöcke danach gemacht. Der Eintrag unter Zieldateien zeigt, dass der Plan die Datei meint.
  • Folge in den Workflows: Ein Befund ist Exit 2. plankritik.js und welle-mit-pruefung.js verwerfen die Kopie dann, rot_nachweis.py meldet den Task als ANWENDEN GESCHEITERT.

Belegt am selben Tag in zwei Stufen, erst der Befund, dann die Erkennung über Zieldateien (Rohdaten: AI/secondbrain/edge-stack-traefik-crowdsec-memory/2026-10-17-plan-ziel-roh/, die zweite Stufe im Unterordner nachtrag/):

  • plan_ziel_treiber.py [--werkzeuge <ordner>] fährt 33 Fälle, die Grundlinie zuerst, und prüft je Fall Exit, Meldung, Zahl der Befunde und jede Datei der Kopie.
  • Angriffe: R1-Form an letzter und erster Stelle, Aus/wird, anhängen, über eine Überschrift ohne Pfad hinweg, zwei Stellen. Dazu eine Datei ohne Endung mit Eintrag an letzter und erster Stelle, mit Aus/wird, als ./Dockerfile und als absoluter Eintrag; Dockerfile ohne Eintrag an letzter und erster Stelle; der Dateiname eines Eintrags ohne Pfad. Für plan_bloecke.py in den Modi vor und nach.
  • Gegenfälle ohne Befund: Ordner, absoluter Pfad, PFAD-Treffer neben einer Datei ohne Endung, Token mit Leerraum, Bezeichner, Pfad ohne Endung über einem Befehl und vor einem Ziel. Ein Muster unter Zieldateien bleibt Befund.
  • Ursprünglicher Stand 22 von 33 Fällen rot, Stand nach Stufe 1 noch 11 (genau die Angriffe der Stufe 2), jetzt 33 grün.
  • Eingebaute Fehler, jeder an seinem Fall gefangen: 12 gegen Stufe 1, 21 gegen die jetzige Fassung (Liste in nachtrag/mutanten.txt). Von den 21 sind 10 die Fehler der Stufe 1 an ihrer neuen Stelle, 11 treffen die neue Regel: Zieldateien-Erkennung aus, Zieldateien vor PFAD, absolute Einträge übergangen, ./ nicht normiert, Muster zählt als Ziel, Wurzel vor docs/plans nicht erkannt, Datei in der Kopie zählt nicht, Dateiname eines Eintrags zählt nicht, Kopie oder Projekt nicht übergeben, keine Wurzel in plan_bloecke.py.
  • Vorher/nachher über alle Plandateien im Vault, je Projekt in eigenen Kopien aus git archive HEAD oder einer Ordnerkopie, Ausgaben je Datei und Bäume byte-genau, der Kopiepfad in Absturzmeldungen normiert. Stufe 1 gegen den ursprünglichen Stand, 2 205 Dateien: einzige Abweichung R1 task-01-logrotate.md, Befund statt überschriebener test-units.sh.
  • Stufe 2 gegen Stufe 1, 2 223 Dateien: R1 task-01-logrotate.md schreibt Stelle 30 nach host/logrotate/edge-traefik. Trading Plattform Task 90 schreibt Schritt 2 nach .gitignore statt still nach trading/data/nachrichten.py (vorher dort „Aus-Block steht 0-mal"). Sonst keine Abweichung; die Bäume unterscheiden sich nur in diesen beiden Dateien. plan_bloecke.py weicht nur an Task 90 ab: gegen den Projektordner geprüft jetzt gegen .gitignore ohne Befund, vorher „Aus-Block steht nicht in trading/data/nachrichten.py".
  • Zehn Abstürze in allen Ständen gleich (fehlende Zieldatei beim Anhängen, siehe „Offen seit 2026-09-23").
  • R1 auf git archive 67cbe0b des Edge-Stack, Task 01 allein und die Welle 01 bis 03: ursprünglich Exit 0 und der Kopf der logrotate-Datei in test-units.sh; nach Stufe 1 Exit 2 und ein Befund; jetzt Exit 0, host/logrotate/edge-traefik byte-gleich mit dem Block aus Stelle 30, test-units.sh unberührt.
  • Lücken: Eine neue Datei ohne Endung, ohne Schrägstrich und ohne Eintrag unter Zieldateien bleibt still, ebenso ein absoluter Pfad ohne Endung in der Überschrift. Nennt eine Überschrift einen PFAD-Treffer und dazu eine Datei ohne Endung, gilt der PFAD-Treffer. Ein absoluter Eintrag außerhalb des Projekts zählt nicht, etwa in einem Arbeitsbaum-Plan, der den Hauptordner nennt. Wird in keiner übergebenen Taskdatei ein Block gegen eine Datei geprüft, endet plan_bloecke.py wie bisher mit 3, auch mit Befunden. plan_bloecke.py braucht plan_materialisiere.py im selben Ordner.

Seit 2026-09-18 nimmt welle_vergleich.py unter ## Zieldateien auch Pfade relativ zum Projekt (ohne Laufwerk und ohne Wurzel), weil Pläne für einen Git-Worktree keine absoluten Pfade nennen; ein .. darin ist ein Befund wie ein fremder absoluter Pfad. Jede Zeile braucht weiter die Klammer hinter dem Pfad ((neu), (geändert), (gelöscht)). plan_materialisiere.py erkennt eine Zieldatei ohne Schrägstrich nur mit den Endungen py, ts, tsx, toml, md, css und json — für compose.yaml in der Projektwurzel schreibt die Überschrift ./compose.yaml. Eine Datei ganz ohne Endung braucht ihren Eintrag unter ## Zieldateien (seit 2026-10-17, oben). Der Treiber fährt dazu drei weitere Fälle (relative Grundlinie, relative Abweichung, .. nach außen).

Seit 2026-09-24 hat rot_nachweis.py die Schalter --venv, --frontend und --projektname; vorher waren ~/venvs/trading, dashboard und trading-plattform/ fest verdrahtet. Ein Hub-Lauf im Trading-Venv wäre für die Tasks mit argon2-cffi und pyotp rot aus einem fehlenden Paket gewesen, nicht aus der fehlenden Umsetzung. Fehlt das Venv in WSL, steht beim Task jetzt VORAUSSETZUNG FEHLT statt ROT, und die Schlusszeile zählt diese Läufe getrennt. Belegt am selben Tag: Der Vorgabe-Befehl ist byte-gleich mit der alten Fassung, falsche Schalterwerte enden mit Exit 3, und der erste echte Lauf über 38 Tasks des Hub meldete keinen grün ohne Umsetzung. Im selben Lauf zeigte sich eine zweite Lücke: Beim Zurücksetzen blieben Ordner stehen, die erst der Task angelegt hatte. Ein leeres hub/auth/ liest Python als Namespace-Paket, und die Fehlerzeile nannte hub.auth.passwort statt hub.auth. Jetzt gehen solche Ordner mit, sobald sie leer sind; derselbe Lauf für die Tasks 7, 9 und 11 zeigt danach genau die Zeile aus dem Plan. Die unabhängige Prüfung desselben Tages fand eine dritte Lücke: Ein vergessenes --venv fiel still auf das Trading-Venv zurück, und Task 7 stand als ROT … No module named 'argon2' da. Seitdem wird eine erste Fehlerzeile, die ein Modul außerhalb des Projekts und außerhalb der Zieldateien vermisst, als VORAUSSETZUNG FEHLT gemeldet. Das trifft auch den Ende-zu-Ende-Task des Hub, dessen Tests playwright brauchen. Mit den Vorgaben lieferte das Skript für die sieben Tasks des Trading-Pakets K vor und nach allen Änderungen dieselben Zeilen. Zwei weitere Lücken desselben Tages, beide ein ROT, das nichts misst: Ein Arbeitsordner mit Windows-Kurznamen (C:/Users/PASCAL~1/…) ließ vitest dort starten, die Dateien aber über den langen Namen auflösen, und jede Oberflächen-Testdatei endete als Cannot find module '/src/…'. Das Skript löst den Arbeitsordner jetzt auf den langen Namen auf; danach zeigte Trading-Task 395 wieder 9 failed | 52 passed statt no tests, und die Hub-Tasks 24, 25, 29 und 37 die Zeile aus dem Plan (Error: Failed to resolve import "./kontrast" …). Die erste Error:-Zeile steht seitdem mit in der Ergebniszeile, weil die FAIL-Zeile allein den Grund nicht zeigt. Ein Test, der über Vite eine Datei aus node_modules lädt (?raw), scheitert hinter der Junction mit Denied ID, weil Vite nichts außerhalb seines Wurzelordners ausliefert; das Skript meldet das als VORAUSSETZUNG FEHLT. Für solche Tasks läuft der Nachweis in einer Kopie mit echtem node_modules (242 MB, gut 20 Sekunden).

Seit 2026-09-22 behandelt rot_nachweis.py tests/conftest.py als Produktivseite. Vorher zählte alles unter tests/ als Testdatei — lag die Korrektur eines Tasks selbst in conftest.py (Trading Plattform, Task 369: die Ersatzfunktionen der Netzsperre trugen eine andere Vorgabe als die Originale), wendete der Lauf „ohne Umsetzung" sie mit an und meldete GRUEN pytest: 147 passed — der eine Zustand, in dem der Nachweis nichts misst, sah aus wie ein sauberer Befund. Gemessen an derselben Basis-Kopie und demselben Task, altes gegen neues Skript: GRUEN 147 passed → ROT 3 failed, 144 passed mit der gemeinten Fehlerzeile (assert ('', '') == ('probe-thema', '')). Die Gegenprobe für den anderen Fall — eine neue Fixture des Tasks liegt in conftest.py (Task 368) — mit --conftest-als-test ROT 1 failed, 11 passed wie vor der Änderung, ohne Schalter ROT 6 failed, 6 passed mit AttributeError: module 'conftest' has no attribute '_gesperrte_pfade' als erster Fehlerzeile: rot aus dem falschen Grund, aber sichtbar, und das Skript sagt je Task, wie es conftest.py behandelt hat. Was es weiter nicht sieht: ob ein Task beides in conftest.py legt, Fixture und Korrektur — dann ist keine der beiden Einstellungen richtig, und der Nachweis läuft von Hand.

Seit 2026-09-20 quotet rot_nachweis.py den Arbeitsordner für den WSL-Aufruf (shlex.quote). Ein Temp-Pfad mit Leerzeichen — der Benutzername steckt im Scratchpad-Pfad — ließ vorher das cd scheitern: Alle sieben Tasks eines Plans der Trading Plattform standen als „ROT pytest: keine Summenzeile" da, ohne dass ein einziger Test lief. Nach der Korrektur derselbe Lauf mit echten Summenzeilen (3 failed, 17 passed, 1 error, …). Wer „keine Summenzeile" bei jedem Task sieht, hat kein Rot gemessen, sondern ein kaputtes Werkzeug.

welle_vergleich_treiber.py fährt Angriffsfälle gegen welle_vergleich.py (Grundlinie zuerst; fremde Datei, unveränderte Zieldatei, Abweichung in erster und letzter Zeile und in der letzten Zieldatei, CRLF, Datei fehlt in der Kopie, Task ohne Zieldateien, Kopie fehlt, Löschung nicht ausgeführt, Prosa-Datei unverändert, Zieldatei außerhalb des Projekts, Datei länger als der Plan). Seit 2026-10-17 kommen acht Fälle zu Zieldateien dazu, die git ignoriert: gleich, Abweichung an erster und an letzter Stelle der Liste, fehlt im Baum, fehlt in der Kopie, gelöscht, Löschung nicht ausgeführt und eine getrackte Datei unter einem ignorierten Muster, die weiter als unverändert gilt. Sechs davon waren auf der alten Fassung rot; fünf Mutanten der neuen Zeilen macht der Treiber je rot. Am Stand von Welle 2 aus Paket R1 im Edge-Stack meldete die alte Fassung 12 Fehlalarme für tests/.work/, die neue 0 Befunde mit 20 byte-gleichen Zieldateien, 12 davon ignoriert; eine verfälschte ignorierte Datei meldet sie als Abweichung. plan_zeichen.py hat am 2026-09-17 an einem echten Fund rot gezeigt (U+2028 im Plan). plan_materialisiere.py reproduziert Task 108 des Teil-3b-Plans aus dem Stand vor den Wellen byte-genau. kopie_bauen.py ist am 2026-09-17 an drei kaputten Eingaben rot gesehen: Ziel existiert schon (Exit 2, nichts kopiert), Ausschluss node_modules ohne Gegenstück im Wurzelordner (Befund), und ein aus der Kopie entferntes Paket trading/data (die Paketprüfung nennt es; nach dem Zurücklegen leer). Einen Treiber dazu gibt es noch nicht — die drei Fälle sind von Hand gefahren.

Offen seit 2026-09-23: plan_materialisiere.py stirbt mit FileNotFoundError, wenn die Zieldatei eines „Häng diesen Block ans Ende"-Blocks in der Kopie fehlt (Trading Plattform, Task 384 hängte einen Nachtrag an die Planübersicht an; die Kopie war ohne docs/plans gebaut). Ein Absturz mitten im Task lässt die davor angewendeten Blöcke halb drin. Bis das ein Befund ist: Kopien für Pläne, die Plandateien selbst anfassen, mit dem Planordner bauen — und eine Kopie nie nach dem Start der Welle ziehen (sie trägt dann den halben Stand des Arbeitsbaums).

Was die Skripte nicht sehen

Dateien, die .gitignore ausblendet und die keine Zieldatei sind; bei einer ignorierten Zieldatei, ob sie vor der Welle schon so aussah; ob die Kopie selbst sauber ist (eine verschmutzte Kopie erzeugt Fehlalarm, kein falsches Grün); ob die Kopie älter ist als der Plan — wird eine Taskdatei nach dem Anwenden noch überarbeitet, meldet welle_vergleich.py eine Abweichung, die keine ist (2026-09-17, Teil 4c: der Planer lief noch, als die Kopie entstand). Dann entscheidet plan_bloecke.py nach gegen den echten Baum, und die betroffenen Dateien der Kopie werden aus git show HEAD:<pfad> neu aufgebaut und der Task erneut angewendet; verwechselbare sichtbare Zeichen (kyrillisches „с"); eine neue Zieldatei ohne Endung und ohne Schrägstrich, die nicht unter ## Zieldateien steht — ihr Block geht still ins vorige Ziel; ein nackter Zaun als Zeile im Text, ein fehlender Schlusszaun vor einem nackten Zaun und ein fehlender Öffner. Sie sind ein Fangnetz, kein Nachweis — den liefert der Lauf mit echten Daten.

Das ältere .vault-tools/plan-diff/plan_diff.py vergleicht Ganzdatei-Blöcke einer früheren Plankonvention und bleibt dafür in Gebrauch.