Codeworkflow: Korrektur nach der Plankritik als Ersetzungsliste (pending-review)

Beleg aus dem Paket FP der Trading Plattform: 129 Ersetzungen in sechs Dateien ohne Bruch. Das
Werkzeug ersetzungen_einsetzen.py liegt jetzt unter .vault-tools/plan-kopie/ (byte-gleich aus der
B4-Uebergabe), Gebrauch in LIESMICH.md, Schritt 9.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_0126RDfXiECscXipzf67uypJ
This commit is contained in:
Pascal MichelandClaude Opus 5.5 committed 2026-10-13 18:24:45 +02:00
1 parent eec993d171
commit 6f9659ad06
3 files changed
+87 -4

No files matched your search

+3 -1
View File
@@ -5,7 +5,7 @@ title: "Planwerkzeuge: Plan auf eine Kopie anwenden und jede Welle dagegen vergl
summary: "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)."
tags: [vault-tools, codeworkflow]
created: 2026-09-17
updated: 2026-10-09
updated: 2026-10-13
---
# Planwerkzeuge für die Kopie mit angewendetem Plan
@@ -26,6 +26,8 @@ Die Regel dazu steht in [[AI/Regelwerk/codeworkflow|codeworkflow.md]] („Die Ko
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:
@@ -0,0 +1,81 @@
"""Setzt eine Ersetzungsliste eines Planers in die Taskdateien ein, alles oder nichts.
Format je Abschnitt (siehe Auftrag an den Planer):
## Ersetzung N — Titel
Datei: <taskdatei>
Alt:
~~~~text
...
~~~~
Neu:
~~~~text
...
~~~~
Jeder Alt-Text muss zum Zeitpunkt seiner Ersetzung genau einmal in der Datei stehen. Bricht beim ersten
Verstoss ab, ohne eine Datei zu schreiben. Mit --pruefen nur pruefen, nicht schreiben.
Aufruf: python ersetzungen_einsetzen.py <planordner> <korrekturdatei> [--pruefen]
"""
import sys
from pathlib import Path
ordner = Path(sys.argv[1])
korrektur = Path(sys.argv[2])
nur_pruefen = "--pruefen" in sys.argv[3:]
zeilen = korrektur.read_text(encoding="utf-8").split("\n")
ersetzungen = []
i = 0
while i < len(zeilen):
if not zeilen[i].startswith("## Ersetzung "):
i += 1
continue
titel = zeilen[i]
i += 1
datei = alt = neu = None
while i < len(zeilen) and not zeilen[i].startswith("## Ersetzung "):
z = zeilen[i]
if z.startswith("Datei:"):
datei = z.split(":", 1)[1].strip().strip("`")
elif z.strip() in ("Alt:", "Neu:"):
art = z.strip()[:-1]
if i + 1 >= len(zeilen) or not zeilen[i + 1].startswith("~~~~"):
sys.exit(f"{titel}: nach {art}: fehlt die Zeile ~~~~text")
j = i + 2
inhalt = []
while j < len(zeilen) and zeilen[j].rstrip() != "~~~~":
inhalt.append(zeilen[j])
j += 1
if j >= len(zeilen):
sys.exit(f"{titel}: {art}-Block ohne schliessende ~~~~")
text = "\n".join(inhalt)
if art == "Alt":
alt = text
else:
neu = text
i = j
i += 1
if datei is None or alt is None or neu is None:
sys.exit(f"{titel}: Datei, Alt oder Neu fehlt")
ersetzungen.append((titel, datei, alt, neu))
texte: dict[str, str] = {}
for titel, datei, alt, neu in ersetzungen:
if datei not in texte:
pfad = ordner / datei
if not pfad.is_file():
sys.exit(f"{titel}: Datei {datei} gibt es nicht")
texte[datei] = pfad.read_text(encoding="utf-8")
n = texte[datei].count(alt)
if n != 1:
sys.exit(f"{titel}: Alt-Text steht {n}-mal in {datei} statt einmal; nichts geschrieben")
texte[datei] = texte[datei].replace(alt, neu)
print(f"ok {titel} ({datei})")
print(f"{len(ersetzungen)} Ersetzungen in {len(texte)} Datei(en) geprueft")
if nur_pruefen:
print("nur geprueft, nichts geschrieben")
else:
for datei, text in texte.items():
(ordner / datei).write_text(text, encoding="utf-8", newline="\n")
print("geschrieben:", ", ".join(sorted(texte)))