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:
1 parent
67d674cd0b
commit
ea278dc24f
46 files changed
+8403
-80
No files matched your search
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user