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 c5da36cb41
commit f86a54a6e7
29 files changed
+6067 -79

No files matched your search

+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