Automatischer Push: 46 Datei(en) geändert

- .claude/agents/planer.md
- .claude/skills/plan-executer-pruefung/SKILL.md
- .claude/workflows/plankritik.js
- .claude/workflows/planung.js
- .claude/workflows/pruefung.js
- .claude/workflows/verbrauch.js
- .claude/workflows/welle-mit-pruefung.js
- .vault-tools/plan-kopie/LIESMICH.md
- .vault-tools/plan-kopie/plan_zeichen.py
- .vault-tools/plan-kopie/rot_nachweis.py
- .vault-tools/trading-plattform-pruefung/LIESMICH.md
- .vault-tools/trading-plattform-pruefung/abendkontrolle.py
- … und 34 weitere

Erzeugt vom PostToolUse-Hook .vault-tools/auto-push/auto_push.py.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
Pascal MichelandClaude Opus 5 committed 2026-09-25 04:00:02 +02:00
1 parent 67d674cd0b
commit ea278dc24f
46 files changed
+8403 -80

No files matched your search

+4 -2
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-09-20
updated: 2026-09-24
---
# Planwerkzeuge für die Kopie mit angewendetem Plan
@@ -21,12 +21,14 @@ Die Regel dazu steht in [[AI/Regelwerk/codeworkflow|codeworkflow.md]] („Die Ko
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".
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]`. `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.
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.
## Was belegt ist
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-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.
+3
View File
@@ -16,6 +16,9 @@ from pathlib import Path
def main() -> int:
# Die Windows-Konsole kodiert cp1252; ein sichtbares Zeichen wie U+2192 in der Zusammenfassung
# liess das Skript sonst mit UnicodeEncodeError abbrechen (2026-09-24, Plan AF der Trading Plattform).
sys.stdout.reconfigure(errors="backslashreplace")
plan = Path(sys.argv[1])
dateien = sorted(plan.glob("*.md"))
if not dateien:
+139 -33
View File
@@ -20,16 +20,29 @@ Geltungsbereich und Luecken:
Sammelfehler -- das ist ROT mit "fixture ... not found" in der ersten Fehlerzeile, also sichtbar.
Wer diesen Fall hat, ruft mit --conftest-als-test auf; dann gilt conftest.py wie frueher als Testdatei.
Das Skript sagt je Task, wie es conftest.py behandelt hat.
- Python-Tests laufen in WSL (Debian, ~/venvs/trading), Oberflaechentests unter Windows mit npx vitest;
dafuer setzt das Skript eine Junction auf das node_modules des Projekts und loest sie vor dem Loeschen.
- Python-Tests laufen in WSL (Debian, Venv aus --venv), Oberflaechentests unter Windows mit npx vitest im
Ordner aus --frontend; dafuer setzt das Skript eine Junction auf das node_modules des Projekts und loest
sie vor dem Loeschen. Die Vorgaben der drei Schalter sind die der Trading Plattform (~/venvs/trading,
dashboard, trading-plattform/); jedes andere Projekt nennt seine Werte (seit 2026-09-24, Nexhorizons Hub).
Fehlt das Venv in WSL oder der Frontend-Ordner in der Kopie, steht beim Task VORAUSSETZUNG FEHLT statt
ROT -- sonst saehe ein Lauf im falschen Venv aus wie ein sauberer Rot-Nachweis. Dasselbe gilt, wenn die
erste pytest-Fehlerzeile ein Modul vermisst, das weder im Projekt liegt noch vom Task angelegt wird
(vergessenes --venv, eine Abhaengigkeit, die nur der Stack hat). Das greift nur an der ersten
Fehlerzeile; ein fremdes Modul, das erst hinter einem Projektmodul fehlt, sieht das Skript nicht.
- Vorbedingung: Das Projekt ist im Venv NICHT editierbar installiert (pip install -e). Sonst loest der
Finder von setuptools ein fehlendes Untermodul gegen das echte Projekt auf, und ein Test ohne Umsetzung
liefe gruen (Pruefung 2026-09-24, setuptools editable_wheel.py).
- Erzeugte Fixtures (der Plan laesst sie mit altem Code erzeugen) holt --fixturen aus einem Ordner nach,
wenn sie nach dem Anwenden fehlen.
- Es sieht nicht, WELCHE Tests rot sein muessten; es zaehlt und zeigt die erste Fehlerzeile.
- Die Basis ist eine Kopie, die der Aufrufer VOR der ersten Welle gezogen hat; das Skript fasst das
Projekt selbst nie an.
Aufruf: python rot_nachweis.py <basis-kopie> <planordner> <arbeitsordner> <spec.json> [--fixturen <ordner>] [--node-modules <pfad>] [--conftest-als-test]
Aufruf: python rot_nachweis.py <basis-kopie> <planordner> <arbeitsordner> <spec.json> [--fixturen <ordner>] [--node-modules <pfad>] [--conftest-als-test] [--venv <wsl-pfad>] [--frontend <ordner>] [--projektname <ordnername>]
--node-modules nennt das node_modules des Projekts, wenn die Basis-Kopie keines traegt.
--venv nennt das Venv in WSL (Vorgabe ~/venvs/trading), --frontend den Oberflaechenordner relativ zum
Projekt (Vorgabe dashboard), --projektname den Ordnernamen, hinter dem absolute Zielpfade abgeschnitten
werden (Vorgabe trading-plattform).
--conftest-als-test laesst tests/conftest.py als Testdatei stehen (nur, wenn die neue Fixture des Tasks dort liegt).
spec.json: [{"task": "task-141-....md", "vor": ["task-...md", ...]}, ...]
"""
@@ -46,10 +59,16 @@ from pathlib import Path
HIER = Path(__file__).resolve().parent
ZIEL = re.compile(r"^- `(.+?)`")
PROJEKTNAME = "trading-plattform/"
VENV = "~/venvs/trading"
FRONTEND = "dashboard"
CONFTEST = "tests/conftest.py"
# Das Venv steht ungequotet im bash-Aufruf, damit ~ expandiert; deshalb nur diese Zeichen.
VENV_MUSTER = re.compile(r"^~?[A-Za-z0-9._/-]+\Z")
FRONTEND_MUSTER = re.compile(r"^[A-Za-z0-9._-]+(/[A-Za-z0-9._-]+)*\Z")
FEHLENDES_MODUL = re.compile(r"No module named '([^']+)'")
def zieldateien(taskdatei: Path) -> list[str]:
def zieldateien(taskdatei: Path, projektname: str = PROJEKTNAME) -> list[str]:
"""Zieldateien relativ zum Projekt. Absolute Pfade werden hinter dem Projektordner abgeschnitten."""
drin = False
ziele: list[str] = []
@@ -61,10 +80,10 @@ def zieldateien(taskdatei: Path) -> list[str]:
if treffer:
pfad = treffer.group(1).replace("\\", "/").removeprefix("./")
if re.match(r"^[A-Za-z]:/", pfad):
stelle = pfad.lower().find(PROJEKTNAME)
stelle = pfad.lower().find(projektname)
if stelle < 0:
raise ValueError(f"{taskdatei.name}: absoluter Pfad ausserhalb des Projekts: {pfad}")
pfad = pfad[stelle + len(PROJEKTNAME) :]
pfad = pfad[stelle + len(projektname) :]
ziele.append(pfad)
return ziele
@@ -92,13 +111,57 @@ def wsl_pfad(pfad: Path) -> str:
return f"/mnt/{text[0].lower()}{text[2:]}"
def loesche(ordner: Path) -> None:
junction = ordner / "dashboard" / "node_modules"
def loesche(ordner: Path, frontend: str = FRONTEND) -> None:
junction = ordner / frontend / "node_modules"
if junction.exists():
os.rmdir(junction) # loest nur die Verknuepfung, nie das Ziel
shutil.rmtree(ordner, ignore_errors=True)
def pytest_befehl(rot: Path, python_tests: list[str], venv: str = VENV) -> str:
# shlex.quote: Ein Arbeitsordner mit Leerzeichen (Benutzername im Temp-Pfad) liess das cd
# scheitern; jeder Task stand dann als "ROT pytest: keine Summenzeile" da, ohne dass ein Test
# lief (gefunden 2026-09-20).
return f"cd {shlex.quote(wsl_pfad(rot))} && {venv}/bin/python -m pytest {' '.join(python_tests)} -q -p no:cacheprovider 2>&1 | tail -60"
def venv_da(venv: str) -> bool:
lauf = subprocess.run(["wsl.exe", "-d", "Debian", "--", "bash", "-lc", f"test -x {venv}/bin/python && echo VENV_DA"],
capture_output=True, text=True, encoding="utf-8", errors="replace")
return "VENV_DA" in lauf.stdout
def fremdes_modul(fehlerzeile: str, ziele: list[str], kopie: Path) -> str | None:
"""Das fehlende Modul, wenn es weder im Projekt liegt noch vom Task angelegt wird.
Ein solcher Fehler misst das Venv, nicht die fehlende Umsetzung: ein vergessenes --venv
(No module named 'argon2' im Trading-Venv) oder eine Abhaengigkeit, die nur der Stack hat
(playwright). Gefunden 2026-09-24 in der Pruefung des Werkzeugs.
"""
treffer = FEHLENDES_MODUL.search(fehlerzeile)
if not treffer:
return None
oben = treffer.group(1).split(".")[0]
eigene = set()
for ziel in ziele:
eigene.update(Path(ziel).parts[:-1])
eigene.add(Path(ziel).stem)
if oben in eigene or any(kopie.rglob(f"{oben}.py")) or any(p.is_dir() for p in kopie.rglob(oben)):
return None
return treffer.group(1)
def nimm_wert(argumente: list[str], schalter: str) -> str | None:
if schalter not in argumente:
return None
stelle = argumente.index(schalter)
if stelle + 1 >= len(argumente) or argumente[stelle + 1].startswith("--"):
raise ValueError(f"{schalter} ohne Wert")
wert = argumente[stelle + 1]
del argumente[stelle : stelle + 2]
return wert
def erste_fehlerzeile(ausgabe: str) -> str:
for zeile in ausgabe.splitlines():
kern = zeile.strip()
@@ -109,16 +172,23 @@ def erste_fehlerzeile(ausgabe: str) -> str:
def main() -> int:
argumente = sys.argv[1:]
fixturen = None
if "--fixturen" in argumente:
stelle = argumente.index("--fixturen")
fixturen = Path(argumente[stelle + 1])
del argumente[stelle : stelle + 2]
node_modules_arg = None
if "--node-modules" in argumente:
stelle = argumente.index("--node-modules")
node_modules_arg = Path(argumente[stelle + 1])
del argumente[stelle : stelle + 2]
try:
fixturen_arg = nimm_wert(argumente, "--fixturen")
node_modules_wert = nimm_wert(argumente, "--node-modules")
venv = nimm_wert(argumente, "--venv") or VENV
frontend = (nimm_wert(argumente, "--frontend") or FRONTEND).replace("\\", "/").strip("/")
projektname = (nimm_wert(argumente, "--projektname") or PROJEKTNAME).replace("\\", "/").strip("/").lower() + "/"
except ValueError as fehler:
print(f"AUFRUF FALSCH: {fehler}")
return 3
fixturen = Path(fixturen_arg) if fixturen_arg else None
node_modules_arg = Path(node_modules_wert) if node_modules_wert else None
if not VENV_MUSTER.match(venv):
print(f"AUFRUF FALSCH: --venv {venv!r} -- erlaubt sind nur ~, Buchstaben, Ziffern und ._/-")
return 3
if not FRONTEND_MUSTER.match(frontend) or ".." in frontend.split("/"):
print(f"AUFRUF FALSCH: --frontend {frontend!r} -- ein Ordner relativ zum Projekt, ohne ..")
return 3
conftest_als_test = False
if "--conftest-als-test" in argumente:
conftest_als_test = True
@@ -126,23 +196,30 @@ def main() -> int:
if len(argumente) != 4:
print(__doc__)
return 3
print(f"Einstellungen: venv={venv} frontend={frontend} projektname={projektname}")
basis, planordner, arbeit, spec_pfad = (Path(a) for a in argumente)
if not basis.is_dir() or not planordner.is_dir() or not spec_pfad.is_file():
print("VORAUSSETZUNG FEHLT: Basis-Kopie, Planordner oder Spec")
return 3
arbeit.mkdir(parents=True, exist_ok=True)
# Den langen Namen erzwingen: Kommt der Arbeitsordner als Windows-Kurzname (PASCAL~1), startet
# vitest dort, loest die Dateien aber ueber den langen Namen auf und meldet "Cannot find module"
# fuer die eigene Testdatei -- ein ROT, das nichts misst (2026-09-24, Hub-Tasks 24, 25, 29, 37
# und Trading-Task 395).
arbeit = arbeit.resolve()
node_modules = None
for kandidat in (node_modules_arg, basis / "dashboard" / "node_modules"):
for kandidat in (node_modules_arg, basis / frontend / "node_modules"):
if kandidat is not None and kandidat.exists():
node_modules = kandidat
break
venv_geprueft: bool | None = None # erst pruefen, wenn ein Task Python-Tests hat
ergebnisse = []
for eintrag in json.loads(spec_pfad.read_text(encoding="utf-8")):
task, voraus = eintrag["task"], eintrag.get("vor", [])
name = task.split("-")[1] if "-" in task else task
vor, rot = arbeit / f"{name}_vor", arbeit / f"{name}_rot"
for ordner in (vor, rot):
loesche(ordner)
loesche(ordner, frontend)
shutil.copytree(basis, vor, ignore=shutil.ignore_patterns("node_modules", "__pycache__"))
fehler = wende_an(planordner, vor, voraus)
shutil.copytree(vor, rot)
@@ -151,7 +228,7 @@ def main() -> int:
ergebnisse.append((task, "ANWENDEN GESCHEITERT", fehler.replace("\n", " | ")))
print(f"{task}: ANWENDEN GESCHEITERT: {fehler[-300:]}")
continue
ziele = zieldateien(planordner / task)
ziele = zieldateien(planordner / task, projektname)
tests = [z for z in ziele if ist_test(z, conftest_als_test) and not z.startswith("tests/fixtures/")]
if CONFTEST in ziele:
# Sichtbar machen, was mit conftest.py geschah -- der Koordinator liest die erste Fehlerzeile
@@ -168,14 +245,22 @@ def main() -> int:
shutil.copyfile(vor / ziel, rot / ziel)
elif (rot / ziel).is_file():
(rot / ziel).unlink()
# Ordner, die erst der Task angelegt hat, gehen mit, sobald sie leer sind: Ein leeres
# hub/auth/ liest Python als Namespace-Paket, und die Fehlerzeile nannte dann das
# Untermodul statt des fehlenden Pakets (2026-09-24, Nexhorizons Hub, Tasks 7, 9, 11).
ordner = (rot / ziel).parent
while ordner != rot and not (vor / ordner.relative_to(rot)).exists() and not any(ordner.iterdir()):
ordner.rmdir()
ordner = ordner.parent
python_tests = [t for t in tests if t.endswith(".py")]
ober_tests = [t for t in tests if not t.endswith(".py")]
zeilen = []
if python_tests:
# shlex.quote: Ein Arbeitsordner mit Leerzeichen (Benutzername im Temp-Pfad) liess das cd
# scheitern; jeder Task stand dann als "ROT pytest: keine Summenzeile" da, ohne dass ein Test
# lief (gefunden 2026-09-20).
befehl = f"cd {shlex.quote(wsl_pfad(rot))} && ~/venvs/trading/bin/python -m pytest {' '.join(python_tests)} -q -p no:cacheprovider 2>&1 | tail -60"
if python_tests and venv_geprueft is None:
venv_geprueft = venv_da(venv)
if python_tests and not venv_geprueft:
zeilen.append(f"VORAUSSETZUNG FEHLT: {venv}/bin/python ist in WSL nicht ausfuehrbar")
elif python_tests:
befehl = pytest_befehl(rot, python_tests, venv)
lauf = subprocess.run(["wsl.exe", "-d", "Debian", "--", "bash", "-lc", befehl],
capture_output=True, text=True, encoding="utf-8", errors="replace")
ausgabe = lauf.stdout
@@ -184,15 +269,22 @@ def main() -> int:
# nicht gesammelt (etwa ein ImportError in tests/conftest.py) -- das ist rot, nicht gruen.
# Gefunden am 2026-09-18: Task 172 stand als GRUEN da, obwohl kein einziger Test lief.
rot_ja = bool(re.search(r"\d+ (failed|error)", summe)) or summe == "keine Summenzeile"
zeilen.append(("ROT " if rot_ja else "GRUEN") + f" pytest: {summe} | {erste_fehlerzeile(ausgabe)}")
erste = erste_fehlerzeile(ausgabe)
fremd = fremdes_modul(erste, ziele, rot) if rot_ja else None
if fremd:
zeilen.append(f"VORAUSSETZUNG FEHLT: Modul {fremd!r} liegt weder im Projekt noch im Venv {venv} | pytest: {summe} | {erste}")
else:
zeilen.append(("ROT " if rot_ja else "GRUEN") + f" pytest: {summe} | {erste}")
if ober_tests:
if node_modules is None:
zeilen.append("VORAUSSETZUNG FEHLT: node_modules in der Basis-Kopie")
elif not (rot / frontend).is_dir():
zeilen.append(f"VORAUSSETZUNG FEHLT: Frontend-Ordner {frontend} fehlt in der Kopie")
else:
import _winapi
_winapi.CreateJunction(str(node_modules.resolve()), str(rot / "dashboard" / "node_modules"))
relativ = [t.removeprefix("dashboard/") for t in ober_tests]
lauf = subprocess.run(["npx.cmd", "vitest", "run", *relativ], cwd=rot / "dashboard",
_winapi.CreateJunction(str(node_modules.resolve()), str(rot / frontend / "node_modules"))
relativ = [t.removeprefix(f"{frontend}/") for t in ober_tests]
lauf = subprocess.run(["npx.cmd", "vitest", "run", *relativ], cwd=rot / frontend,
capture_output=True, text=True, encoding="utf-8", errors="replace")
ausgabe = re.sub(r"\x1b\[[0-9;]*m", "", lauf.stdout + lauf.stderr)
summe = next((z.strip() for z in ausgabe.splitlines() if z.strip().startswith("Tests ")), "")
@@ -201,16 +293,30 @@ def main() -> int:
# das Ende der Ausgabe zeigen, statt "rot" zu behaupten, ohne zu wissen woran.
summe = "keine Summenzeile; Ende der Ausgabe: " + " / ".join(z.strip() for z in ausgabe.splitlines()[-6:] if z.strip())[:300]
rot_ja = "failed" in summe or lauf.returncode != 0
zeilen.append(("ROT " if rot_ja else "GRUEN") + f" vitest: {summe} | {erste_fehlerzeile(ausgabe)}")
if "Denied ID" in ausgabe:
# Vite liefert keine Datei ausserhalb seines Wurzelordners, und durch die Junction
# liegt node_modules ausserhalb der Kopie: Ein Test mit "?raw" aus node_modules
# scheitert dann als ganze Datei (2026-09-24, Nexhorizons Hub, Task 42).
zeilen.append(f"VORAUSSETZUNG FEHLT: Vite verweigert eine Datei hinter der Junction auf node_modules; mit echter Kopie von node_modules fahren | vitest: {summe}")
else:
# Bei einer Datei, die schon am Import scheitert, steht vor der Fehlermeldung nur
# die FAIL-Zeile; die Meldung selbst zeigt erst, ob der Grund der gemeinte ist.
erste = erste_fehlerzeile(ausgabe)
meldung = next((z.strip()[:220] for z in ausgabe.splitlines()
if z.strip().startswith(("Error:", "AssertionError", "TypeError", "ReferenceError", "SyntaxError"))), "")
zusatz = f" | {meldung}" if meldung and meldung not in erste else ""
zeilen.append(("ROT " if rot_ja else "GRUEN") + f" vitest: {summe} | {erste}{zusatz}")
if not tests:
zeilen.append("ohne Testdatei unter den Zieldateien")
for zeile in zeilen:
print(f"{task}: {zeile}")
ergebnisse.append((task, zeile[:5].strip(), zeile))
for ordner in (vor, rot):
loesche(ordner)
loesche(ordner, frontend)
gruen = [e for e in ergebnisse if e[1] == "GRUEN"]
print(f"\n{len(ergebnisse)} Laeufe, davon {len(gruen)} gruen ohne Umsetzung.")
ohne_lauf = [e for e in ergebnisse if e[1] in ("VORAU", "ANWENDEN GESCHEITERT")]
print(f"\n{len(ergebnisse)} Laeufe, davon {len(gruen)} gruen ohne Umsetzung und {len(ohne_lauf)} ohne Lauf "
"(Voraussetzung fehlt oder Anwenden gescheitert).")
return 0