plan-kopie: Zieldatei ohne Endung über ## Zieldateien, sonst Befund statt stillem Fehlziel
Anlass: Plankritik Paket R1 im Edge-Stack, Befunde 1 und 5. Die Überschrift "Stelle 30 — `host/logrotate/edge-traefik`" setzte kein Ziel; der Block "Die ganze Datei ist neu" überschrieb tests/systemd/test-units.sh mit Exit 0. - ordne() in plan_materialisiere.py, von plan_bloecke.py importiert: Ziel ist der letzte PFAD-Treffer, ohne ihn der letzte Token, der unter ## Zieldateien steht (absolut relativ zu --projekt bzw. zum Ordner vor docs/plans). - Überschrift ohne Ziel, die nach einer Datei aussieht (relativer Pfad mit /, Datei in Kopie/Projekt, Dateiname eines Eintrags): jeder schreibende Block danach ist Befund, Exit 2, nichts geschrieben. - plan_ziel_treiber.py: 33 Fälle; Ursprung 22 rot, jetzt 33 grün; 21 eingebaute Fehler gefangen. - Vorher/nachher über 2 223 Plandateien: Abweichung nur R1 task-01 und Trading Task 90, beide jetzt ins richtige Ziel. - LIESMICH und planer.md nachgezogen. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
07675bf541
commit
5cbe75c5e3
5 files changed
+433
-21
No files matched your search
@@ -23,7 +23,7 @@ Du denkst, du tippst nicht. Dein Ergebnis ist ein Plan, den ein Executer ohne R
|
|||||||
- `rot_bei` nennt parametrisierte Tests mit voller Kennung, und zwar nur die Fälle, die der Mutant umkippt. Bei B1 (2026-09-27) wären unter `return False` drei von fünf Fällen grün geblieben.
|
- `rot_bei` nennt parametrisierte Tests mit voller Kennung, und zwar nur die Fälle, die der Mutant umkippt. Bei B1 (2026-09-27) wären unter `return False` drei von fünf Fällen grün geblieben.
|
||||||
- `aus` und `wird` stehen in `'''…'''`, damit Backslashes wörtlich bleiben.
|
- `aus` und `wird` stehen in `'''…'''`, damit Backslashes wörtlich bleiben.
|
||||||
- **Keine Platzhalter.** Kein „analog zu Task 2", kein „hier noch ergänzen", kein Pseudocode. Was im Plan steht, ist tippfertig.
|
- **Keine Platzhalter.** Kein „analog zu Task 2", kein „hier noch ergänzen", kein Pseudocode. Was im Plan steht, ist tippfertig.
|
||||||
- **Blöcke in der Form, die `plan_materialisiere.py` anwendet.** Die Marke steht als letzte nicht leere Zeile vor dem Zaun, ohne Formatierung: `Aus:`, `wird:`, `Die ganze Datei ist neu:` oder `Häng diesen Block ans Ende:`. Die Überschrift (`##` bis `####`) über dem Block endet mit dem Pfad der Zieldatei in Backticks; ein Pfad ohne Schrägstrich und ohne Endung `.py`, `.md`, `.toml`, `.json`, `.ts` oder `.css` steht als `./name` (etwa `./versions.env`). Eine Löschung steht in `## Zieldateien` als ``- `pfad` (gelöscht)``. Eine fette Marke, eine andere Formulierung („mit dem Write-Werkzeug anlegen“, „löschen“) oder eine Überschrift, deren letzter Pfad nicht das Ziel ist, übergeht das Werkzeug ohne Fehler. Am 2026-10-11 im Edge-Stack betraf das 8 von 11 Taskdateien; die Plankritik hätte ohne angewendete Kopie gearbeitet. Prüfskripte im Plan, die Marken zählen, suchen genau diese Form.
|
- **Blöcke in der Form, die `plan_materialisiere.py` anwendet.** Die Marke steht als letzte nicht leere Zeile vor dem Zaun, ohne Formatierung: `Aus:`, `wird:`, `Die ganze Datei ist neu:` oder `Häng diesen Block ans Ende:`. Die Überschrift (`##` bis `####`) über dem Block endet mit dem Pfad der Zieldatei in Backticks; ein Pfad ohne Schrägstrich und ohne Endung `.py`, `.md`, `.toml`, `.json`, `.ts` oder `.css` steht als `./name` (etwa `./versions.env`). Eine Datei ganz ohne Endung (`host/logrotate/edge-traefik`, `Dockerfile`, `.gitignore`) erkennt das Werkzeug nur, wenn derselbe Pfad unter `## Zieldateien` steht; sonst ist ihr Block ein Befund (seit 2026-10-17). Eine Löschung steht in `## Zieldateien` als ``- `pfad` (gelöscht)``. Eine fette Marke, eine andere Formulierung („mit dem Write-Werkzeug anlegen“, „löschen“) oder eine Überschrift, deren letzter Pfad nicht das Ziel ist, übergeht das Werkzeug ohne Fehler. Am 2026-10-11 im Edge-Stack betraf das 8 von 11 Taskdateien; die Plankritik hätte ohne angewendete Kopie gearbeitet. Prüfskripte im Plan, die Marken zählen, suchen genau diese Form.
|
||||||
- **Nachbesserung nach einer ausgeführten Welle schreibst du als neue Taskdateien, nicht als Neufassung.** Du hast kein Edit-Werkzeug und schreibst jede überarbeitete Datei komplett neu; am 2026-09-23 im Nexhorizons Hub waren dabei 10 von 17 Befunden der Nachprüfung durch das Umschreiben selbst entstanden. Deshalb: neue `task-NN-nachbesserung-<thema>.md` mit `Aus:`/`wird:`-Blöcken gegen den Baum, bestehende Taskdateien bleiben byte-gleich. Was ältere Tasks an den neuen Stand anpassen muss, schreibst du als Folgenliste `nachbesserung-<welle>-folgen.md` im selben `Aus:`/`wird:`-Format; eingesetzt wird sie vom Koordinator oder einem Executer. Für Korrekturen vor der ersten Ausführung gilt das nicht, außer der Auftrag verlangt es.
|
- **Nachbesserung nach einer ausgeführten Welle schreibst du als neue Taskdateien, nicht als Neufassung.** Du hast kein Edit-Werkzeug und schreibst jede überarbeitete Datei komplett neu; am 2026-09-23 im Nexhorizons Hub waren dabei 10 von 17 Befunden der Nachprüfung durch das Umschreiben selbst entstanden. Deshalb: neue `task-NN-nachbesserung-<thema>.md` mit `Aus:`/`wird:`-Blöcken gegen den Baum, bestehende Taskdateien bleiben byte-gleich. Was ältere Tasks an den neuen Stand anpassen muss, schreibst du als Folgenliste `nachbesserung-<welle>-folgen.md` im selben `Aus:`/`wird:`-Format; eingesetzt wird sie vom Koordinator oder einem Executer. Für Korrekturen vor der ersten Ausführung gilt das nicht, außer der Auftrag verlangt es.
|
||||||
- **Offene Entscheidungen benennen, nicht überspielen.** Wo der Auftrag mehrdeutig ist: Annahme treffen, im Abschnitt „Getroffene Annahmen" explizit nennen. Nur abbrechen, wenn jede Annahme den Plan unbrauchbar machen würde.
|
- **Offene Entscheidungen benennen, nicht überspielen.** Wo der Auftrag mehrdeutig ist: Annahme treffen, im Abschnitt „Getroffene Annahmen" explizit nennen. Nur abbrechen, wenn jede Annahme den Plan unbrauchbar machen würde.
|
||||||
|
|
||||||
|
|||||||
@@ -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)."
|
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]
|
tags: [vault-tools, codeworkflow]
|
||||||
created: 2026-09-17
|
created: 2026-09-17
|
||||||
updated: 2026-10-13
|
updated: 2026-10-17
|
||||||
---
|
---
|
||||||
|
|
||||||
# Planwerkzeuge für die Kopie mit angewendetem Plan
|
# Planwerkzeuge für die Kopie mit angewendetem Plan
|
||||||
@@ -15,7 +15,7 @@ Die Regel dazu steht in [[AI/Regelwerk/codeworkflow|codeworkflow.md]] („Die Ko
|
|||||||
## Ablauf
|
## 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.
|
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.
|
||||||
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.
|
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. 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.
|
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).
|
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".
|
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".
|
||||||
@@ -62,7 +62,27 @@ Nachbesserung vom 2026-10-09 (Paket RB3 der Trading Plattform, Plan `docs/plans/
|
|||||||
|
|
||||||
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-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-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`. Der Treiber fährt dazu drei weitere Fälle (relative Grundlinie, relative Abweichung, `..` nach außen), zusammen 17.
|
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), zusammen 17.
|
||||||
|
|
||||||
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-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).
|
||||||
|
|
||||||
@@ -76,6 +96,6 @@ Offen seit 2026-09-23: `plan_materialisiere.py` stirbt mit `FileNotFoundError`,
|
|||||||
|
|
||||||
## Was die Skripte nicht sehen
|
## Was die Skripte nicht sehen
|
||||||
|
|
||||||
Dateien, die `.gitignore` ausblendet; 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 „с"). Sie sind ein Fangnetz, kein Nachweis — den liefert der Lauf mit echten Daten.
|
Dateien, die `.gitignore` ausblendet; 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. 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.
|
Das ältere `.vault-tools/plan-diff/plan_diff.py` vergleicht Ganzdatei-Blöcke einer früheren Plankonvention und bleibt dafür in Gebrauch.
|
||||||
@@ -2,9 +2,19 @@
|
|||||||
|
|
||||||
Koordinator-Werkzeug im Scratchpad. Geltungsbereich (Fangnetz, kein Nachweis):
|
Koordinator-Werkzeug im Scratchpad. Geltungsbereich (Fangnetz, kein Nachweis):
|
||||||
- Sieht nur Codebloecke, deren letzte nicht leere Zeile vor dem Zaun genau "Aus:" oder
|
- Sieht nur Codebloecke, deren letzte nicht leere Zeile vor dem Zaun genau "Aus:" oder
|
||||||
"wird:" ist (Projektkonvention der Planer-Taskdateien seit Teil 5). Die Zieldatei ist der
|
"wird:" ist (Projektkonvention der Planer-Taskdateien seit Teil 5). Welche Datei Ziel ist, regelt
|
||||||
letzte Pfad in Backticks in einer Ueberschrift (##, ###, ####) oberhalb des Blocks, der
|
ordne() in plan_materialisiere.py (importiert, die Regel steht dort im Kopf): der letzte PFAD-Treffer
|
||||||
auf eine im Projekt vorhandene oder unter "## Zieldateien" genannte Datei zeigt.
|
einer Ueberschrift (##, ###, ####) oberhalb des Blocks, ohne ihn ein Token, das genau so unter
|
||||||
|
"## Zieldateien" steht (absolute Eintraege relativ zu --projekt). Ob die Zieldatei im Projekt liegt,
|
||||||
|
prueft erst der Modus. Bis 2026-10-17 stand hier, das Ziel muesse unter Zieldateien stehen; der Code
|
||||||
|
tat das nie.
|
||||||
|
- Befund statt stillem Ziel (seit 2026-10-17, Plankritik Paket R1 im Edge-Stack): Nennt eine
|
||||||
|
Ueberschrift kein Ziel, aber einen Token, der nach einer Datei aussieht (relativer Pfad mit
|
||||||
|
Schraegstrich; Name, der direkt in --projekt als Datei liegt oder Dateiname eines Eintrags ist), ist
|
||||||
|
jeder Aus- und wird-Block danach ein Befund, bis eine Ueberschrift mit Ziel kommt; vorher wurde still
|
||||||
|
gegen das vorige Ziel geprueft. Luecken wie in plan_materialisiere.py; Nachweis: plan_ziel_treiber.py.
|
||||||
|
- Exit 3 hat Vorrang: Wurde in keiner Taskdatei ein Block gegen eine Datei geprueft, endet es mit 3,
|
||||||
|
auch wenn Befunde ausgegeben sind.
|
||||||
- Modus vor: jeder Aus-Block steht genau einmal in seiner Zieldatei (0 = Befund, >1 = Befund
|
- Modus vor: jeder Aus-Block steht genau einmal in seiner Zieldatei (0 = Befund, >1 = Befund
|
||||||
"mehrdeutig"). Belegt, dass der Plan den heutigen Stand byte-genau zitiert.
|
"mehrdeutig"). Belegt, dass der Plan den heutigen Stand byte-genau zitiert.
|
||||||
- Modus nach: jeder wird-Block steht in der Zieldatei; jeder Aus-Block steht nicht mehr darin,
|
- Modus nach: jeder wird-Block steht in der Zieldatei; jeder Aus-Block steht nicht mehr darin,
|
||||||
@@ -18,11 +28,15 @@ Exit: 0 kein Befund, 2 Befunde, 3 kein einziger Aus-/wird-Block gefunden (nichts
|
|||||||
import argparse
|
import argparse
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path, PureWindowsPath
|
||||||
|
|
||||||
|
# Die Zielregel (PFAD, Zieldateien, offene Token) steht einmal, in plan_materialisiere.py im selben Ordner;
|
||||||
|
# der Ordner kommt ausdruecklich in den Suchpfad, sonst scheitert der Import unter "python -I"
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||||
|
from plan_materialisiere import ordne, zieldateien # noqa: E402
|
||||||
|
|
||||||
ZAUN = re.compile(r"^```")
|
ZAUN = re.compile(r"^```")
|
||||||
UEBERSCHRIFT = re.compile(r"^#{2,4} ")
|
UEBERSCHRIFT = re.compile(r"^#{2,4} ")
|
||||||
PFAD = re.compile(r"`([^`\s]+/[^`\s]+\.[A-Za-z0-9]+|[^`\s/]+\.(?:py|ts|tsx|toml|md|css|json))`")
|
|
||||||
|
|
||||||
|
|
||||||
def normal(text: str) -> str:
|
def normal(text: str) -> str:
|
||||||
@@ -32,13 +46,20 @@ def normal(text: str) -> str:
|
|||||||
def lies_bloecke(taskdatei: Path, projekt: Path) -> list[dict]:
|
def lies_bloecke(taskdatei: Path, projekt: Path) -> list[dict]:
|
||||||
zeilen = normal(taskdatei.read_text(encoding="utf-8")).split("\n")
|
zeilen = normal(taskdatei.read_text(encoding="utf-8")).split("\n")
|
||||||
bloecke: list[dict] = []
|
bloecke: list[dict] = []
|
||||||
|
# Absolute Eintraege unter "## Zieldateien" zaehlen relativ zu --projekt, wie in welle_vergleich.py
|
||||||
|
geplant = zieldateien(zeilen, [PureWindowsPath(projekt.absolute()), PureWindowsPath(projekt.resolve())])
|
||||||
ziel: str | None = None
|
ziel: str | None = None
|
||||||
|
# (Zeile, Token) der letzten Ueberschrift ohne Ziel, die nach einer Datei aussieht; None, sobald ein Ziel kommt
|
||||||
|
ungeklaert: tuple[int, list[str]] | None = None
|
||||||
i = 0
|
i = 0
|
||||||
while i < len(zeilen):
|
while i < len(zeilen):
|
||||||
zeile = zeilen[i]
|
zeile = zeilen[i]
|
||||||
if UEBERSCHRIFT.match(zeile):
|
if UEBERSCHRIFT.match(zeile):
|
||||||
for treffer in PFAD.findall(zeile):
|
neu, offen = ordne(zeile, geplant, projekt)
|
||||||
ziel = treffer
|
if neu:
|
||||||
|
ziel, ungeklaert = neu, None
|
||||||
|
elif offen:
|
||||||
|
ungeklaert = (i + 1, offen)
|
||||||
if ZAUN.match(zeile):
|
if ZAUN.match(zeile):
|
||||||
start = i
|
start = i
|
||||||
# Ein Zaun aus n Backticks schliesst erst mit mindestens n Backticks: Ein
|
# Ein Zaun aus n Backticks schliesst erst mit mindestens n Backticks: Ein
|
||||||
@@ -58,7 +79,8 @@ def lies_bloecke(taskdatei: Path, projekt: Path) -> list[dict]:
|
|||||||
if k >= 0:
|
if k >= 0:
|
||||||
marke = zeilen[k].strip()
|
marke = zeilen[k].strip()
|
||||||
if marke in ("Aus:", "wird:"):
|
if marke in ("Aus:", "wird:"):
|
||||||
bloecke.append({"art": marke[:-1], "ziel": ziel, "zeile": start + 1, "text": "\n".join(inhalt)})
|
bloecke.append({"art": marke[:-1], "ziel": ziel, "zeile": start + 1, "text": "\n".join(inhalt),
|
||||||
|
"ungeklaert": ungeklaert})
|
||||||
i += 1
|
i += 1
|
||||||
return bloecke
|
return bloecke
|
||||||
|
|
||||||
@@ -78,6 +100,13 @@ def main() -> int:
|
|||||||
bloecke = lies_bloecke(taskdatei, projekt)
|
bloecke = lies_bloecke(taskdatei, projekt)
|
||||||
paare = 0
|
paare = 0
|
||||||
for nummer, block in enumerate(bloecke):
|
for nummer, block in enumerate(bloecke):
|
||||||
|
if block["ungeklaert"]:
|
||||||
|
zeile_u, pfade = block["ungeklaert"]
|
||||||
|
print(f"BEFUND {taskdatei.name}:{block['zeile']}: {block['art']}-Block unter Ueberschrift in Zeile {zeile_u}, "
|
||||||
|
f"die nennt {', '.join(f'`{p}`' for p in pfade)} und keine erkannte Zieldatei (fehlt sie unter ## Zieldateien?); "
|
||||||
|
f"geprueft wuerde gegen {block['ziel'] or 'nichts'}")
|
||||||
|
befunde += 1
|
||||||
|
continue
|
||||||
if block["ziel"] is None:
|
if block["ziel"] is None:
|
||||||
print(f"BEFUND {taskdatei.name}:{block['zeile']}: {block['art']}-Block ohne Zieldatei in einer Ueberschrift")
|
print(f"BEFUND {taskdatei.name}:{block['zeile']}: {block['art']}-Block ohne Zieldatei in einer Ueberschrift")
|
||||||
befunde += 1
|
befunde += 1
|
||||||
|
|||||||
@@ -3,7 +3,24 @@
|
|||||||
Koordinator-Werkzeug (entstanden 2026-09-17, Trading Plattform Teil 3b). Es tut etwas und beurteilt
|
Koordinator-Werkzeug (entstanden 2026-09-17, Trading Plattform Teil 3b). Es tut etwas und beurteilt
|
||||||
nichts; das Urteil faellt danach mit Suite, Mutanten und welle_vergleich.py.
|
nichts; das Urteil faellt danach mit Suite, Mutanten und welle_vergleich.py.
|
||||||
Geltungsbereich:
|
Geltungsbereich:
|
||||||
- Zieldatei ist der letzte Pfad in Backticks in einer Ueberschrift (## bis ####) oberhalb des Blocks.
|
- Zieldatei ist der letzte Pfad in Backticks in einer Ueberschrift (## bis ####) oberhalb des Blocks, den
|
||||||
|
PFAD erkennt: mit Schraegstrich und Endung, ohne Schraegstrich nur mit py, ts, tsx, toml, md, css, json.
|
||||||
|
Erkennt PFAD in der Ueberschrift nichts, ist Ziel der letzte Token, der (ohne "./") genau so unter
|
||||||
|
"## Zieldateien" derselben Taskdatei steht (seit 2026-10-17): So wird eine Datei ohne Endung
|
||||||
|
(`host/logrotate/edge-traefik`, `Dockerfile`, `.gitignore`) planbar. Ein absoluter Eintrag zaehlt
|
||||||
|
relativ zum Ordner vor "docs/plans" im Pfad des Planordners; ohne diesen zaehlen nur relative Eintraege.
|
||||||
|
Ordner, Muster mit "*" und Pfade mit ".." unter Zieldateien sind kein Ziel.
|
||||||
|
- Befund statt stillem Ziel (seit 2026-10-17): Nennt eine Ueberschrift kein Ziel, aber einen Token, der
|
||||||
|
nach einer Datei aussieht, ist jeder schreibende Block danach ein Befund, auch unter Ueberschriften
|
||||||
|
ohne Pfad, bis eine Ueberschrift mit Ziel kommt; nichts wird geschrieben. Nach einer Datei sehen aus:
|
||||||
|
ein relativer Pfad mit Schraegstrich und ein Name ohne Schraegstrich, der direkt in der Kopie als Datei
|
||||||
|
liegt oder Dateiname eines Eintrags unter Zieldateien ist. Vorher ging der Block still ins vorige Ziel.
|
||||||
|
Kein Befund sind Ordner (hinten "/"), absolute Pfade (vorn "/"), Token mit Leerraum und Bezeichner
|
||||||
|
ohne gleichnamige Datei: Bestehende Plaene nennen sie in Ueberschriften als Kontext und meinen das
|
||||||
|
vorige Ziel. Nicht gesehen: eine neue Datei ohne Endung, ohne Schraegstrich und ohne Eintrag unter
|
||||||
|
Zieldateien, ein absoluter Pfad ohne Endung und eine Ueberschrift, die neben einem PFAD-Treffer eine
|
||||||
|
Datei ohne Endung nennt (dann gilt der PFAD-Treffer). Gleiche Regel in plan_bloecke.py (dort mit
|
||||||
|
--projekt statt Kopie und Planordner); Regressionsnachweis: plan_ziel_treiber.py.
|
||||||
- Dateien, die "## Zieldateien" als "(gelöscht)" fuehrt, werden in der Kopie geloescht.
|
- Dateien, die "## Zieldateien" als "(gelöscht)" fuehrt, werden in der Kopie geloescht.
|
||||||
- Bloecke mit Einrueckung (Befehle in Listen) und Prosa-Vorgaben ("wird: ein neuer Abschnitt ...")
|
- Bloecke mit Einrueckung (Befehle in Listen) und Prosa-Vorgaben ("wird: ein neuer Abschnitt ...")
|
||||||
werden uebergangen: Ein Doku-Task mit frei zu formulierendem Text laesst sich nicht anwenden.
|
werden uebergangen: Ein Doku-Task mit frei zu formulierendem Text laesst sich nicht anwenden.
|
||||||
@@ -21,26 +38,117 @@ Exit 0 ohne Befund, 2 mit Befund.
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path, PureWindowsPath
|
||||||
|
|
||||||
UEBERSCHRIFT = re.compile(r"^#{2,4} ")
|
UEBERSCHRIFT = re.compile(r"^#{2,4} ")
|
||||||
PFAD = re.compile(r"`([^`\s]+/[^`\s]+\.[A-Za-z0-9]+|[^`\s/]+\.(?:py|ts|tsx|toml|md|css|json))`")
|
PFAD = re.compile(r"`([^`\s]+/[^`\s]+\.[A-Za-z0-9]+|[^`\s/]+\.(?:py|ts|tsx|toml|md|css|json))`")
|
||||||
|
TOKEN = re.compile(r"`([^`\s]+)`")
|
||||||
|
# Eintrag unter "## Zieldateien", dieselbe Form wie in welle_vergleich.py
|
||||||
|
ZIEL = re.compile(r"^- `(.+?)`(?:\s*\((.*?)\))?")
|
||||||
|
|
||||||
|
|
||||||
|
def wurzeln(planordner: Path) -> list[PureWindowsPath]:
|
||||||
|
"""Der Projektordner vor "docs/plans" im Pfad des Planordners, wie uebergeben und aufgeloest."""
|
||||||
|
aus = []
|
||||||
|
for pfad in (planordner.absolute(), planordner.resolve()):
|
||||||
|
teile = pfad.parts
|
||||||
|
for i in range(len(teile) - 2, 0, -1):
|
||||||
|
if teile[i] == "docs" and teile[i + 1] == "plans":
|
||||||
|
aus.append(PureWindowsPath(*teile[:i]))
|
||||||
|
break
|
||||||
|
return aus
|
||||||
|
|
||||||
|
|
||||||
|
def zieldateien(zeilen: list[str], wurzeln: list[PureWindowsPath]) -> set[str]:
|
||||||
|
"""Relative Pfade unter "## Zieldateien"; ein absoluter zaehlt relativ zu einer der wurzeln.
|
||||||
|
|
||||||
|
Ordner (hinten "/"), Muster mit "*" und Pfade mit ".." sind kein Ziel.
|
||||||
|
"""
|
||||||
|
drin, menge = False, set()
|
||||||
|
for zeile in zeilen:
|
||||||
|
if zeile.startswith("## "):
|
||||||
|
drin = zeile.startswith("## Zieldateien")
|
||||||
|
continue
|
||||||
|
treffer = ZIEL.match(zeile) if drin else None
|
||||||
|
if not treffer or treffer.group(1).endswith(("/", "\\")) or "*" in treffer.group(1):
|
||||||
|
continue
|
||||||
|
pfad = PureWindowsPath(treffer.group(1))
|
||||||
|
if pfad.drive or pfad.root:
|
||||||
|
relativ = None
|
||||||
|
for wurzel in wurzeln:
|
||||||
|
try:
|
||||||
|
relativ = pfad.relative_to(wurzel)
|
||||||
|
break
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
if relativ is None:
|
||||||
|
continue
|
||||||
|
pfad = relativ
|
||||||
|
if ".." not in pfad.parts:
|
||||||
|
menge.add(pfad.as_posix())
|
||||||
|
return menge
|
||||||
|
|
||||||
|
|
||||||
|
def ist_datei(ordner: Path | None, name: str) -> bool:
|
||||||
|
"""Liegt `name` als Datei direkt im ordner? Nie fuer einen Namen mit Laufwerk oder Wurzel."""
|
||||||
|
if ordner is None or PureWindowsPath(name).drive or PureWindowsPath(name).root:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
return (ordner / name).is_file()
|
||||||
|
except (OSError, ValueError):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def ordne(zeile: str, geplant: set[str], ordner: Path | None) -> tuple[str | None, list[str]]:
|
||||||
|
"""Zieldatei einer Ueberschrift und die Token, die nach einer Datei aussehen, ohne erkannt zu sein.
|
||||||
|
|
||||||
|
Ziel ist der letzte PFAD-Treffer, ohne ihn der letzte Token, der (ohne "./") unter "## Zieldateien"
|
||||||
|
steht. Ohne Ziel gelten als offen: relative Pfade mit Schraegstrich (kein Ordner, nicht absolut) und
|
||||||
|
Namen ohne Schraegstrich, die direkt im ordner als Datei liegen oder Dateiname eines Eintrags sind.
|
||||||
|
Nachbesserung 2026-10-17 (Plankritik Paket R1 im Edge-Stack, Befunde 1 und 5): "Stelle 30 —
|
||||||
|
`host/logrotate/edge-traefik`" setzte kein Ziel, der Block "Die ganze Datei ist neu" ueberschrieb
|
||||||
|
`tests/systemd/test-units.sh` mit Exit 0; `Dockerfile` ging ebenso still ins vorige Ziel. `config/`
|
||||||
|
und `/proc` stehen in bestehenden Plaenen ueber Bloecken, die das vorige Ziel meinen.
|
||||||
|
"""
|
||||||
|
treffer = PFAD.findall(zeile)
|
||||||
|
if treffer:
|
||||||
|
return treffer[-1], []
|
||||||
|
token = TOKEN.findall(zeile)
|
||||||
|
ziele = [PureWindowsPath(t).as_posix() for t in token if PureWindowsPath(t).as_posix() in geplant]
|
||||||
|
if ziele:
|
||||||
|
return ziele[-1], []
|
||||||
|
namen = {eintrag.rsplit("/", 1)[-1] for eintrag in geplant}
|
||||||
|
offen = [t for t in token if ("/" in t and not t.startswith("/") and not t.endswith("/"))
|
||||||
|
or ("/" not in t and (t in namen or ist_datei(ordner, t)))]
|
||||||
|
return None, offen
|
||||||
|
|
||||||
|
|
||||||
|
def schreibt(marke: str) -> bool:
|
||||||
|
"""Marken, nach denen main() in die Zieldatei schreibt; bei einer neuen Marke hier mitziehen."""
|
||||||
|
return (marke == "wird:" or "Die ganze Datei ist neu" in marke or "vollständig ersetzt" in marke
|
||||||
|
or marke.startswith("Häng diesen Block ans Ende"))
|
||||||
|
|
||||||
|
|
||||||
def normal(text: str) -> str:
|
def normal(text: str) -> str:
|
||||||
return text.replace("\r\n", "\n").replace("\r", "\n")
|
return text.replace("\r\n", "\n").replace("\r", "\n")
|
||||||
|
|
||||||
|
|
||||||
def bloecke(taskdatei: Path):
|
def bloecke(taskdatei: Path, kopie: Path | None = None):
|
||||||
zeilen = normal(taskdatei.read_text(encoding="utf-8")).split("\n")
|
zeilen = normal(taskdatei.read_text(encoding="utf-8")).split("\n")
|
||||||
|
geplant = zieldateien(zeilen, wurzeln(taskdatei.parent))
|
||||||
ziel = None
|
ziel = None
|
||||||
|
# (Zeile, Token) der letzten Ueberschrift ohne Ziel, die nach einer Datei aussieht; None, sobald ein Ziel kommt
|
||||||
|
ungeklaert = None
|
||||||
i = 0
|
i = 0
|
||||||
while i < len(zeilen):
|
while i < len(zeilen):
|
||||||
zeile = zeilen[i]
|
zeile = zeilen[i]
|
||||||
if UEBERSCHRIFT.match(zeile):
|
if UEBERSCHRIFT.match(zeile):
|
||||||
treffer = PFAD.findall(zeile)
|
# Der Generator laeuft verzahnt mit main(): Die Kopie traegt hier schon die Bloecke davor
|
||||||
if treffer:
|
neu, offen = ordne(zeile, geplant, kopie)
|
||||||
ziel = treffer[-1]
|
if neu:
|
||||||
|
ziel, ungeklaert = neu, None
|
||||||
|
elif offen:
|
||||||
|
ungeklaert = (i + 1, offen)
|
||||||
if zeile.lstrip().startswith("```"):
|
if zeile.lstrip().startswith("```"):
|
||||||
einzug = len(zeile) - len(zeile.lstrip())
|
einzug = len(zeile) - len(zeile.lstrip())
|
||||||
kern = zeile.lstrip()
|
kern = zeile.lstrip()
|
||||||
@@ -58,7 +166,8 @@ def bloecke(taskdatei: Path):
|
|||||||
while k >= 0 and not zeilen[k].strip():
|
while k >= 0 and not zeilen[k].strip():
|
||||||
k -= 1
|
k -= 1
|
||||||
marke = zeilen[k].strip() if k >= 0 else ""
|
marke = zeilen[k].strip() if k >= 0 else ""
|
||||||
yield {"ziel": ziel, "marke": marke, "zeile": start + 1, "text": "\n".join(inhalt), "einzug": einzug}
|
yield {"ziel": ziel, "marke": marke, "zeile": start + 1, "text": "\n".join(inhalt), "einzug": einzug,
|
||||||
|
"ungeklaert": ungeklaert}
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
|
|
||||||
@@ -114,7 +223,7 @@ def main() -> int:
|
|||||||
taskdatei = plan / name
|
taskdatei = plan / name
|
||||||
alt = None
|
alt = None
|
||||||
zaehler = {"neu": 0, "ersetzt": 0, "angehaengt": 0, "uebergangen": 0}
|
zaehler = {"neu": 0, "ersetzt": 0, "angehaengt": 0, "uebergangen": 0}
|
||||||
for block in bloecke(taskdatei):
|
for block in bloecke(taskdatei, kopie):
|
||||||
marke, ziel = block["marke"], block["ziel"]
|
marke, ziel = block["marke"], block["ziel"]
|
||||||
if block["einzug"] > 0:
|
if block["einzug"] > 0:
|
||||||
zaehler["uebergangen"] += 1
|
zaehler["uebergangen"] += 1
|
||||||
@@ -122,6 +231,14 @@ def main() -> int:
|
|||||||
if marke == "Aus:" or marke.startswith("Aus, Zeile"):
|
if marke == "Aus:" or marke.startswith("Aus, Zeile"):
|
||||||
alt = block
|
alt = block
|
||||||
continue
|
continue
|
||||||
|
if block["ungeklaert"] and schreibt(marke):
|
||||||
|
zeile_u, pfade = block["ungeklaert"]
|
||||||
|
print(f"BEFUND {name}:{block['zeile']}: Ueberschrift in Zeile {zeile_u} nennt "
|
||||||
|
f"{', '.join(f'`{p}`' for p in pfade)} und keine erkannte Zieldatei (fehlt sie unter ## Zieldateien?); "
|
||||||
|
f"der Block ginge nach {ziel or 'keinem Ziel'}, nichts geschrieben")
|
||||||
|
befunde += 1
|
||||||
|
alt = None
|
||||||
|
continue
|
||||||
if marke == "wird:":
|
if marke == "wird:":
|
||||||
if alt is None or ziel is None:
|
if alt is None or ziel is None:
|
||||||
print(f"BEFUND {name}:{block['zeile']}: wird-Block ohne Aus-Block oder ohne Zieldatei")
|
print(f"BEFUND {name}:{block['zeile']}: wird-Block ohne Aus-Block oder ohne Zieldatei")
|
||||||
|
|||||||
@@ -0,0 +1,246 @@
|
|||||||
|
"""Regressionsnachweis fuer die Zielzuordnung von plan_materialisiere.py und plan_bloecke.py.
|
||||||
|
|
||||||
|
Anlass 2026-10-17 (Plankritik von Paket R1 im Edge-Stack, Befunde 1 und 5): Die Ueberschrift
|
||||||
|
"#### Stelle 30 — `host/logrotate/edge-traefik`" nennt einen Pfad ohne Endung. PFAD erkannte ihn nicht,
|
||||||
|
das Ziel blieb beim vorigen (`tests/systemd/test-units.sh`), und der Block "Die ganze Datei ist neu"
|
||||||
|
ueberschrieb es mit Exit 0 und "0 Befund(e)". Die Faelle unten bauen diese Form nach, an erster und an
|
||||||
|
letzter Stelle, ueber eine pfadlose Unterueberschrift hinweg und fuer jede schreibende Marke, dazu die
|
||||||
|
Ueberschriften aus bestehenden Plaenen, die kein Befund sein duerfen (Ordner, absoluter Pfad, erkannte
|
||||||
|
Zieldatei neben einem Pfad ohne Endung, Token mit Leerraum).
|
||||||
|
Seit dem Nachtrag vom selben Tag ist eine Datei ohne Endung Ziel, wenn sie genau so unter "## Zieldateien"
|
||||||
|
steht, auch als absoluter Eintrag; ein Name ohne Schraegstrich, der in der Kopie liegt oder der Dateiname
|
||||||
|
eines Eintrags ist, ist ein Befund (vorher ging `Dockerfile` still ins vorige Ziel). Die Faelle dazu stehen
|
||||||
|
am Ende der Liste; "{PROJEKT}" in einer Taskdatei ist der absolute Projektordner des Falls.
|
||||||
|
Grundlinie zuerst, dann jeder Fall einzeln in einem frischen Wegwerfordner. Je Fall werden Exit-Code,
|
||||||
|
ausloesende Meldung und bei plan_materialisiere.py der Inhalt jeder Datei der Kopie geprueft.
|
||||||
|
Aufruf: python plan_ziel_treiber.py [--werkzeuge <ordner>] Vorgabe: der Ordner dieses Skripts.
|
||||||
|
Exit 0, wenn jeder Fall so ausgeht wie erwartet, sonst 2.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Projekt jedes Falls: eine Datei mit Endung, eine ohne, eine Doku, zwei ohne Endung im Wurzelordner
|
||||||
|
BASIS = {
|
||||||
|
"tests/a.sh": "eins\nzwei\ndrei\n",
|
||||||
|
"host/logrotate/edge-traefik": "alt\n",
|
||||||
|
"README.md": "# Projekt\n\n- host/ Ordner\n",
|
||||||
|
"Dockerfile": "FROM alt\n",
|
||||||
|
".gitignore": "data/\n",
|
||||||
|
}
|
||||||
|
Z = "```"
|
||||||
|
|
||||||
|
|
||||||
|
def aus_wird(ueberschrift: str, alt: str, neu: str) -> str:
|
||||||
|
return f"{ueberschrift}\n\nAus:\n\n{Z}text\n{alt}\n{Z}\n\nwird:\n\n{Z}text\n{neu}\n{Z}\n\n"
|
||||||
|
|
||||||
|
|
||||||
|
def ganz(ueberschrift: str, text: str) -> str:
|
||||||
|
return f"{ueberschrift}\n\nDie ganze Datei ist neu:\n\n{Z}text\n{text}\n{Z}\n\n"
|
||||||
|
|
||||||
|
|
||||||
|
def anhaengen(ueberschrift: str, text: str) -> str:
|
||||||
|
return f"{ueberschrift}\n\nHäng diesen Block ans Ende der Datei:\n\n{Z}text\n{text}\n{Z}\n\n"
|
||||||
|
|
||||||
|
|
||||||
|
def befehl(ueberschrift: str) -> str:
|
||||||
|
return f"{ueberschrift}\n\nAufruf:\n\n{Z}bash\nbash tests/.work/r1-01/umgebung.sh\n{Z}\n\n"
|
||||||
|
|
||||||
|
|
||||||
|
def zieldateien(*pfade: str) -> str:
|
||||||
|
return "# Task 01\n\n## Zieldateien\n\n" + "".join(f"- `{p}` (geändert)\n" for p in pfade) + "\n## Prüfstand\n\n"
|
||||||
|
|
||||||
|
|
||||||
|
KOPF = "# Task 01\n\n## Prüfstand\n\n"
|
||||||
|
STELLE_A = aus_wird("#### Stelle 1 — `tests/a.sh`", "zwei", "ZWEI")
|
||||||
|
OHNE = "`host/logrotate/edge-traefik`"
|
||||||
|
MELDUNG = f"nennt {OHNE} und keine erkannte Zieldatei"
|
||||||
|
GEPLANT = zieldateien("tests/a.sh", "host/logrotate/edge-traefik")
|
||||||
|
|
||||||
|
# Name, Werkzeug, Taskdatei, erwarteter Exit, erwartete Meldung, Zahl der BEFUND-Zeilen,
|
||||||
|
# erwartete Dateien nach dem Lauf (nur "mat"; jede nicht genannte Datei bleibt wie in BASIS)
|
||||||
|
FAELLE = [
|
||||||
|
("Grundlinie", "mat", KOPF + STELLE_A, 0, "0 Befund(e).", 0,
|
||||||
|
{"tests/a.sh": "eins\nZWEI\ndrei\n"}),
|
||||||
|
# Die Form aus R1: Pfad ohne Endung nach einem erkannten Ziel (Schaden an letzter Stelle)
|
||||||
|
("R1-Form: ganze Datei, letzte Stelle", "mat",
|
||||||
|
KOPF + STELLE_A + ganz(f"#### Stelle 2 — {OHNE}", "neu"), 2, MELDUNG, 1,
|
||||||
|
{"tests/a.sh": "eins\nZWEI\ndrei\n"}),
|
||||||
|
# Dieselbe Form als erste Stelle: vorher kein Ziel, der Block ging still als "uebergangen" durch
|
||||||
|
("ganze Datei, erste Stelle", "mat",
|
||||||
|
KOPF + ganz(f"#### Stelle 1 — {OHNE}", "neu") + aus_wird("#### Stelle 2 — `tests/a.sh`", "zwei", "ZWEI"), 2, MELDUNG, 1,
|
||||||
|
{"tests/a.sh": "eins\nZWEI\ndrei\n"}),
|
||||||
|
("Aus/wird unter Pfad ohne Endung", "mat",
|
||||||
|
KOPF + aus_wird("#### Stelle 1 — `tests/a.sh`", "eins", "EINS") + aus_wird(f"#### Stelle 2 — {OHNE}", "zwei", "ZWEI"),
|
||||||
|
2, MELDUNG, 1, {"tests/a.sh": "EINS\nzwei\ndrei\n"}),
|
||||||
|
("anhaengen unter Pfad ohne Endung", "mat",
|
||||||
|
KOPF + STELLE_A + anhaengen(f"#### Stelle 2 — {OHNE}", "vier"), 2, MELDUNG, 1,
|
||||||
|
{"tests/a.sh": "eins\nZWEI\ndrei\n"}),
|
||||||
|
# Das Ziel bestimmt die letzte Ueberschrift mit Pfad; eine pfadlose Unterueberschrift aendert daran nichts
|
||||||
|
("ueber pfadlose Unterueberschrift", "mat",
|
||||||
|
KOPF + STELLE_A + f"### Stelle 2 — {OHNE}\n\n" + ganz("#### Neue Fassung", "neu"), 2, MELDUNG, 1,
|
||||||
|
{"tests/a.sh": "eins\nZWEI\ndrei\n"}),
|
||||||
|
("zwei Stellen, zwei Befunde", "mat",
|
||||||
|
KOPF + ganz(f"#### Stelle 1 — {OHNE}", "neu") + STELLE_A + ganz(f"#### Stelle 3 — {OHNE}", "neu"), 2, "2 Befund(e).", 2,
|
||||||
|
{"tests/a.sh": "eins\nZWEI\ndrei\n"}),
|
||||||
|
# Kein Befund: eine Ueberschrift mit erkannter Zieldatei hebt den Pfad ohne Endung davor auf
|
||||||
|
("Pfad ohne Endung, danach erkanntes Ziel", "mat",
|
||||||
|
KOPF + f"### Hinweis zu {OHNE}\n\nProsa.\n\n" + STELLE_A, 0, "0 Befund(e).", 0,
|
||||||
|
{"tests/a.sh": "eins\nZWEI\ndrei\n"}),
|
||||||
|
("Pfad ohne Endung ueber einem Befehl", "mat",
|
||||||
|
KOPF + befehl("#### Messordner `tests/.work/r1-01`") + STELLE_A, 0, "0 Befund(e).", 0,
|
||||||
|
{"tests/a.sh": "eins\nZWEI\ndrei\n"}),
|
||||||
|
# Kein Befund: Ueberschriften aus bestehenden Plaenen (Paket E2 Task 367, S3 Task 298, NF Task 703)
|
||||||
|
("Ordner mit Schraegstrich", "mat",
|
||||||
|
KOPF + STELLE_A + aus_wird("#### Änderung 2 — dass `host/` nicht der einzige Ort ist", "drei", "DREI"), 0, "0 Befund(e).", 0,
|
||||||
|
{"tests/a.sh": "eins\nZWEI\nDREI\n"}),
|
||||||
|
("absoluter Pfad", "mat",
|
||||||
|
KOPF + STELLE_A + aus_wird("#### Änderung 2 — ein Wegwerf-`/proc` statt zweier Dateien", "drei", "DREI"), 0, "0 Befund(e).", 0,
|
||||||
|
{"tests/a.sh": "eins\nZWEI\nDREI\n"}),
|
||||||
|
("erkannte Zieldatei neben Pfad ohne Endung", "mat",
|
||||||
|
KOPF + STELLE_A + aus_wird("#### Änderung in `README.md`: `glattstellen/beginn` ohne Schlüssel", "host/ Ordner", "host/ Ordner, neu"),
|
||||||
|
0, "0 Befund(e).", 0, {"tests/a.sh": "eins\nZWEI\ndrei\n", "README.md": "# Projekt\n\n- host/ Ordner, neu\n"}),
|
||||||
|
("Token mit Leerraum", "mat",
|
||||||
|
KOPF + STELLE_A + aus_wird("#### Endpunkt `GET /api/paper`", "drei", "DREI"), 0, "0 Befund(e).", 0,
|
||||||
|
{"tests/a.sh": "eins\nZWEI\nDREI\n"}),
|
||||||
|
# plan_bloecke.py, Modus vor: Der Aus-Block steht zufaellig auch im vorigen Ziel, deshalb vorher still.
|
||||||
|
# Je Stelle zwei Befunde (Aus und wird); keiner davon darf zusaetzlich gegen das vorige Ziel laufen.
|
||||||
|
("bloecke: Grundlinie", "vor", KOPF + STELLE_A, 0, "0 Befund(e).", 0, None),
|
||||||
|
("bloecke: letzte Stelle", "vor",
|
||||||
|
KOPF + STELLE_A + aus_wird(f"#### Stelle 2 — {OHNE}", "drei", "DREI"), 2, MELDUNG, 2, None),
|
||||||
|
("bloecke: erste Stelle", "vor",
|
||||||
|
KOPF + aus_wird(f"#### Stelle 1 — {OHNE}", "drei", "DREI") + STELLE_A, 2, MELDUNG, 2, None),
|
||||||
|
("bloecke: ueber pfadlose Unterueberschrift", "vor",
|
||||||
|
KOPF + STELLE_A + f"### Stelle 2 — {OHNE}\n\n" + aus_wird("#### Änderung", "drei", "DREI"), 2, MELDUNG, 2, None),
|
||||||
|
("bloecke: Ordner, absolut, Leerraum, erkannte Zieldatei", "vor",
|
||||||
|
KOPF + STELLE_A + aus_wird("#### Änderung 2 — dass `host/` nicht der einzige Ort ist", "drei", "DREI")
|
||||||
|
+ aus_wird("#### Änderung 3 — Wegwerf-`/proc`", "eins", "EINS")
|
||||||
|
+ aus_wird("#### Endpunkt `GET /api/paper`", "eins", "EINS")
|
||||||
|
+ aus_wird("#### Änderung in `README.md`: `glattstellen/beginn`", "host/ Ordner", "host/ Ordner, neu"),
|
||||||
|
0, "0 Befund(e).", 0, None),
|
||||||
|
# Modus nach: Der Baum traegt schon den wird-Text im vorigen Ziel
|
||||||
|
("bloecke nach: letzte Stelle", "nach",
|
||||||
|
KOPF + aus_wird("#### Stelle 1 — `tests/a.sh`", "eins", "EINS") + aus_wird(f"#### Stelle 2 — {OHNE}", "drei", "DREI"),
|
||||||
|
2, MELDUNG, 2, None),
|
||||||
|
# Nachtrag: Datei ohne Endung unter "## Zieldateien" ist Ziel (R1 Stelle 30 mit Eintrag), letzte und erste Stelle
|
||||||
|
("Zieldateien: ganze Datei ohne Endung, letzte Stelle", "mat",
|
||||||
|
GEPLANT + STELLE_A + ganz(f"#### Stelle 2 — {OHNE}", "neu"), 0, "0 Befund(e).", 0,
|
||||||
|
{"tests/a.sh": "eins\nZWEI\ndrei\n", "host/logrotate/edge-traefik": "neu\n"}),
|
||||||
|
("Zieldateien: ganze Datei ohne Endung, erste Stelle", "mat",
|
||||||
|
GEPLANT + ganz(f"#### Stelle 1 — {OHNE}", "neu") + aus_wird("#### Stelle 2 — `tests/a.sh`", "zwei", "ZWEI"),
|
||||||
|
0, "0 Befund(e).", 0, {"tests/a.sh": "eins\nZWEI\ndrei\n", "host/logrotate/edge-traefik": "neu\n"}),
|
||||||
|
("Zieldateien: Aus/wird in Datei ohne Endung", "mat",
|
||||||
|
GEPLANT + STELLE_A + aus_wird(f"#### Stelle 2 — {OHNE}", "alt", "ALT"), 0, "0 Befund(e).", 0,
|
||||||
|
{"tests/a.sh": "eins\nZWEI\ndrei\n", "host/logrotate/edge-traefik": "ALT\n"}),
|
||||||
|
# "./Dockerfile" in der Ueberschrift, "Dockerfile" unter Zieldateien
|
||||||
|
("Zieldateien: ./Dockerfile", "mat",
|
||||||
|
zieldateien("tests/a.sh", "Dockerfile") + STELLE_A + ganz("#### Stelle 2 — `./Dockerfile`", "FROM neu"),
|
||||||
|
0, "0 Befund(e).", 0, {"tests/a.sh": "eins\nZWEI\ndrei\n", "Dockerfile": "FROM neu\n"}),
|
||||||
|
# Absoluter Eintrag: relativ zum Ordner vor docs/plans (Trading Plattform, Task 90: `.gitignore`)
|
||||||
|
("Zieldateien: absoluter Eintrag", "mat",
|
||||||
|
zieldateien("tests/a.sh", "{PROJEKT}\\.gitignore") + STELLE_A + aus_wird("### Schritt 2 — `.gitignore`", "data/", "data/\nartefakte/"),
|
||||||
|
0, "0 Befund(e).", 0, {"tests/a.sh": "eins\nZWEI\ndrei\n", ".gitignore": "data/\nartefakte/\n"}),
|
||||||
|
# Vorrang: Ein PFAD-Treffer in derselben Ueberschrift bleibt Ziel, auch neben einer Zieldatei ohne Endung
|
||||||
|
("Zieldateien: PFAD-Treffer hat Vorrang", "mat",
|
||||||
|
GEPLANT + STELLE_A + aus_wird(f"#### Änderung in `README.md`, Zeile {OHNE}", "host/ Ordner", "host/ Ordner, neu"),
|
||||||
|
0, "0 Befund(e).", 0, {"tests/a.sh": "eins\nZWEI\ndrei\n", "README.md": "# Projekt\n\n- host/ Ordner, neu\n"}),
|
||||||
|
# Ein Muster unter Zieldateien ist kein Ziel; der Pfad bleibt ein Befund
|
||||||
|
("Zieldateien: Muster ist kein Ziel", "mat",
|
||||||
|
zieldateien("tests/a.sh", "host/logrotate/*") + STELLE_A + ganz("#### Stelle 2 — `host/logrotate/*`", "neu"),
|
||||||
|
2, "nennt `host/logrotate/*` und keine erkannte Zieldatei", 1, {"tests/a.sh": "eins\nZWEI\ndrei\n"}),
|
||||||
|
# Name ohne Schraegstrich: liegt in der Kopie oder ist Dateiname eines Eintrags -> Befund statt stillem Ziel
|
||||||
|
("Dockerfile ohne Eintrag, letzte Stelle", "mat",
|
||||||
|
KOPF + STELLE_A + ganz("#### Stelle 2 — `Dockerfile`", "FROM neu"), 2, "nennt `Dockerfile` und keine erkannte Zieldatei", 1,
|
||||||
|
{"tests/a.sh": "eins\nZWEI\ndrei\n"}),
|
||||||
|
("Dockerfile ohne Eintrag, erste Stelle", "mat",
|
||||||
|
KOPF + ganz("#### Stelle 1 — `Dockerfile`", "FROM neu") + aus_wird("#### Stelle 2 — `tests/a.sh`", "zwei", "ZWEI"),
|
||||||
|
2, "nennt `Dockerfile` und keine erkannte Zieldatei", 1, {"tests/a.sh": "eins\nZWEI\ndrei\n"}),
|
||||||
|
("Dateiname eines Eintrags ohne Pfad", "mat",
|
||||||
|
GEPLANT + STELLE_A + ganz("#### Stelle 2 — `edge-traefik`", "neu"), 2, "nennt `edge-traefik` und keine erkannte Zieldatei", 1,
|
||||||
|
{"tests/a.sh": "eins\nZWEI\ndrei\n"}),
|
||||||
|
# Kein Befund: ein Bezeichner ohne Datei dieses Namens (bestehende Plaene: `main`, `pytest`, `Order`)
|
||||||
|
("Bezeichner ohne Datei", "mat",
|
||||||
|
KOPF + STELLE_A + aus_wird("#### Änderung 2 — `main` ruft `pruefe` auf", "drei", "DREI"), 0, "0 Befund(e).", 0,
|
||||||
|
{"tests/a.sh": "eins\nZWEI\nDREI\n"}),
|
||||||
|
("bloecke: Zieldateien, Datei ohne Endung", "vor",
|
||||||
|
GEPLANT + STELLE_A + aus_wird(f"#### Stelle 2 — {OHNE}", "alt", "ALT"), 0, "0 Befund(e).", 0, None),
|
||||||
|
("bloecke: Zieldateien, absoluter Eintrag", "vor",
|
||||||
|
zieldateien("tests/a.sh", "{PROJEKT}\\.gitignore") + STELLE_A + aus_wird("### Schritt 2 — `.gitignore`", "data/", "data/\nartefakte/"),
|
||||||
|
0, "0 Befund(e).", 0, None),
|
||||||
|
("bloecke: Dockerfile ohne Eintrag", "vor",
|
||||||
|
KOPF + STELLE_A + aus_wird("#### Stelle 2 — `Dockerfile`", "drei", "DREI"), 2, "nennt `Dockerfile` und keine erkannte Zieldatei", 2, None),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def schreibe(pfad: Path, text: str) -> None:
|
||||||
|
pfad.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
pfad.write_bytes(text.encode("utf-8"))
|
||||||
|
|
||||||
|
|
||||||
|
def fahre(werkzeuge: Path, art: str, wurzel: Path, plan: Path) -> subprocess.CompletedProcess:
|
||||||
|
if art == "mat":
|
||||||
|
befehl = [str(werkzeuge / "plan_materialisiere.py"), str(plan), str(wurzel / "kopie"), "task-01.md"]
|
||||||
|
else:
|
||||||
|
befehl = [str(werkzeuge / "plan_bloecke.py"), art, str(plan / "task-01.md"), "--projekt", str(wurzel / "kopie")]
|
||||||
|
return subprocess.run([sys.executable, "-X", "utf8", "-B", *befehl], capture_output=True, text=True,
|
||||||
|
encoding="utf-8", errors="replace", stdin=subprocess.DEVNULL)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
zerleger = argparse.ArgumentParser()
|
||||||
|
zerleger.add_argument("--werkzeuge", default=str(Path(__file__).resolve().parent))
|
||||||
|
werkzeuge = Path(zerleger.parse_args().werkzeuge)
|
||||||
|
fehler = 0
|
||||||
|
for name, art, task, exit_erwartet, meldung, anzahl, dateien in FAELLE:
|
||||||
|
wurzel = Path(tempfile.mkdtemp(prefix="pz_"))
|
||||||
|
try:
|
||||||
|
basis = dict(BASIS)
|
||||||
|
if art == "nach":
|
||||||
|
# Stand nach der Welle: Stelle 1 angewendet, Stelle 2 im vorigen Ziel statt in edge-traefik
|
||||||
|
basis["tests/a.sh"] = "EINS\nzwei\nDREI\n"
|
||||||
|
for rel, text in basis.items():
|
||||||
|
schreibe(wurzel / "kopie" / rel, text)
|
||||||
|
# {PROJEKT}: fuer plan_bloecke der Ordner hinter --projekt; fuer plan_materialisiere ein eigener
|
||||||
|
# Projektordner, unter dessen docs/plans die Taskdatei liegt (die Kopie liegt woanders)
|
||||||
|
plan = wurzel / "plan"
|
||||||
|
if "{PROJEKT}" in task:
|
||||||
|
projekt = wurzel / ("projekt" if art == "mat" else "kopie")
|
||||||
|
plan = projekt / "docs" / "plans" / "p" if art == "mat" else plan
|
||||||
|
task = task.replace("{PROJEKT}", str(projekt))
|
||||||
|
schreibe(plan / "task-01.md", task)
|
||||||
|
lauf = fahre(werkzeuge, art, wurzel, plan)
|
||||||
|
ausgabe = lauf.stdout + lauf.stderr
|
||||||
|
gruende = []
|
||||||
|
if lauf.returncode != exit_erwartet:
|
||||||
|
gruende.append(f"Exit {lauf.returncode}, erwartet {exit_erwartet}")
|
||||||
|
if meldung not in ausgabe:
|
||||||
|
gruende.append(f"Meldung fehlt: {meldung}")
|
||||||
|
if ausgabe.count("BEFUND ") != anzahl:
|
||||||
|
gruende.append(f"{ausgabe.count('BEFUND ')} BEFUND-Zeile(n), erwartet {anzahl}")
|
||||||
|
# Jede Datei der Kopie: erwarteter Inhalt, sonst unveraendert wie BASIS
|
||||||
|
for rel, text in basis.items() if dateien is not None else ():
|
||||||
|
soll = dateien.get(rel, text)
|
||||||
|
ist = (wurzel / "kopie" / rel).read_bytes().decode("utf-8", errors="replace")
|
||||||
|
if ist != soll:
|
||||||
|
gruende.append(f"{rel}: {ist!r}, erwartet {soll!r}")
|
||||||
|
print(f"{'ok ' if not gruende else 'FEHLER'} {name}: Exit {lauf.returncode}")
|
||||||
|
if gruende:
|
||||||
|
fehler += 1
|
||||||
|
for grund in gruende:
|
||||||
|
print(" " + grund)
|
||||||
|
print(" " + ausgabe.strip().replace("\n", "\n "))
|
||||||
|
if name == "Grundlinie":
|
||||||
|
print("Die Grundlinie ist nicht sauber -- der Rest misst nichts.")
|
||||||
|
return 2
|
||||||
|
finally:
|
||||||
|
shutil.rmtree(wurzel, ignore_errors=True)
|
||||||
|
print(f"{fehler} Fall/Faelle mit falschem Ausgang von {len(FAELLE)}.")
|
||||||
|
return 2 if fehler else 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
Reference in new issue
Block a user