Automatischer Push: 19 Datei(en) geändert
- .vault-tools/plan-kopie/LIESMICH.md - .vault-tools/plan-kopie/rot_nachweis.py - .vault-tools/plan-kopie/welle_vergleich.py - .vault-tools/plan-kopie/welle_vergleich_treiber.py - .vault-tools/trading-plattform-pruefung/LIESMICH.md - .vault-tools/trading-plattform-pruefung/angriffe.py - .vault-tools/trading-plattform-pruefung/browserlauf.py - .vault-tools/trading-plattform-pruefung/browserlauf_zugang.py - .vault-tools/trading-plattform-pruefung/c_eigenlauf.py - .vault-tools/trading-plattform-pruefung/d_eigenlauf.py - .vault-tools/trading-plattform-pruefung/handelstag_auswertung.py - .vault-tools/trading-plattform-pruefung/ntfy_probe_server.py - … und 7 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
371a1f5d2a
commit
395642c480
19 files changed
+1636
-32
No files matched your search
@@ -0,0 +1,195 @@
|
||||
"""Rot-Nachweis je Task: die neuen Tests eines Tasks gegen den Produktivcode VOR dem Task, auf Wegwerfkopien.
|
||||
|
||||
Koordinator-Werkzeug (entstanden 2026-09-18, Trading Plattform). plan_materialisiere.py wendet eine
|
||||
Taskdatei immer ganz an; "erst der Test, dann die Umsetzung" laesst sich damit nicht beobachten. Dieses
|
||||
Skript stellt den Zustand dazwischen her: Basis + Voraussetzungstasks + der Task selbst, danach die
|
||||
PRODUKTIVEN Zieldateien des Tasks zurueck auf den Stand vor dem Task (neue Produktivdateien geloescht).
|
||||
Dann laufen die Testdateien des Tasks. Es tut etwas und berichtet; das Urteil faellt der Koordinator:
|
||||
ROT mindestens ein Fehlschlag oder Sammelfehler -- die erste Fehlerzeile steht dabei, damit man sieht,
|
||||
ob der Test an der gemeinten Stelle faellt und nicht an einem Tippfehler.
|
||||
GRUEN kein Fehlschlag ohne die Umsetzung -- ein Test, der so gruen ist, misst die Umsetzung nicht
|
||||
(ausser der Plan fuehrt ihn ausdruecklich als Regressionsnetz).
|
||||
Geltungsbereich und Luecken:
|
||||
- Testdateien sind Zieldateien unter tests/ sowie *.test.ts und *.test.tsx; alles unter tests/fixtures/
|
||||
gilt als Testseite und bleibt stehen. Alles andere ist produktiv.
|
||||
- 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.
|
||||
- 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>]
|
||||
--node-modules nennt das node_modules des Projekts, wenn die Basis-Kopie keines traegt.
|
||||
spec.json: [{"task": "task-141-....md", "vor": ["task-...md", ...]}, ...]
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
HIER = Path(__file__).resolve().parent
|
||||
ZIEL = re.compile(r"^- `(.+?)`")
|
||||
PROJEKTNAME = "trading-plattform/"
|
||||
|
||||
|
||||
def zieldateien(taskdatei: Path) -> list[str]:
|
||||
"""Zieldateien relativ zum Projekt. Absolute Pfade werden hinter dem Projektordner abgeschnitten."""
|
||||
drin = False
|
||||
ziele: list[str] = []
|
||||
for zeile in taskdatei.read_text(encoding="utf-8").replace("\r\n", "\n").split("\n"):
|
||||
if zeile.startswith("## "):
|
||||
drin = zeile.startswith("## Zieldateien")
|
||||
continue
|
||||
treffer = ZIEL.match(zeile) if drin else None
|
||||
if treffer:
|
||||
pfad = treffer.group(1).replace("\\", "/").removeprefix("./")
|
||||
if re.match(r"^[A-Za-z]:/", pfad):
|
||||
stelle = pfad.lower().find(PROJEKTNAME)
|
||||
if stelle < 0:
|
||||
raise ValueError(f"{taskdatei.name}: absoluter Pfad ausserhalb des Projekts: {pfad}")
|
||||
pfad = pfad[stelle + len(PROJEKTNAME) :]
|
||||
ziele.append(pfad)
|
||||
return ziele
|
||||
|
||||
|
||||
def ist_test(pfad: str) -> bool:
|
||||
return pfad.startswith("tests/") or pfad.endswith((".test.ts", ".test.tsx"))
|
||||
|
||||
|
||||
def wende_an(planordner: Path, kopie: Path, tasks: list[str]) -> str:
|
||||
if not tasks:
|
||||
return ""
|
||||
lauf = subprocess.run(
|
||||
[sys.executable, "-X", "utf8", str(HIER / "plan_materialisiere.py"), str(planordner), str(kopie), *tasks],
|
||||
capture_output=True, text=True, encoding="utf-8", errors="replace",
|
||||
)
|
||||
return "" if lauf.returncode == 0 else (lauf.stdout + lauf.stderr)[-600:]
|
||||
|
||||
|
||||
def wsl_pfad(pfad: Path) -> str:
|
||||
text = str(pfad.resolve()).replace("\\", "/")
|
||||
return f"/mnt/{text[0].lower()}{text[2:]}"
|
||||
|
||||
|
||||
def loesche(ordner: Path) -> None:
|
||||
junction = ordner / "dashboard" / "node_modules"
|
||||
if junction.exists():
|
||||
os.rmdir(junction) # loest nur die Verknuepfung, nie das Ziel
|
||||
shutil.rmtree(ordner, ignore_errors=True)
|
||||
|
||||
|
||||
def erste_fehlerzeile(ausgabe: str) -> str:
|
||||
for zeile in ausgabe.splitlines():
|
||||
kern = zeile.strip()
|
||||
if kern.startswith(("E ", "FAILED", "ERROR", "AssertionError", "Error:", "TypeError", "ReferenceError")) or " FAIL " in zeile:
|
||||
return kern[:220]
|
||||
return ""
|
||||
|
||||
|
||||
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]
|
||||
if len(argumente) != 4:
|
||||
print(__doc__)
|
||||
return 3
|
||||
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)
|
||||
node_modules = None
|
||||
for kandidat in (node_modules_arg, basis / "dashboard" / "node_modules"):
|
||||
if kandidat is not None and kandidat.exists():
|
||||
node_modules = kandidat
|
||||
break
|
||||
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)
|
||||
shutil.copytree(basis, vor, ignore=shutil.ignore_patterns("node_modules", "__pycache__"))
|
||||
fehler = wende_an(planordner, vor, voraus)
|
||||
shutil.copytree(vor, rot)
|
||||
fehler = fehler or wende_an(planordner, rot, [task])
|
||||
if fehler:
|
||||
ergebnisse.append((task, "ANWENDEN GESCHEITERT", fehler.replace("\n", " | ")))
|
||||
print(f"{task}: ANWENDEN GESCHEITERT: {fehler[-300:]}")
|
||||
continue
|
||||
ziele = zieldateien(planordner / task)
|
||||
tests = [z for z in ziele if ist_test(z) and not z.startswith("tests/fixtures/")]
|
||||
for ziel in ziele:
|
||||
if ist_test(ziel):
|
||||
if not (rot / ziel).exists() and fixturen is not None and (fixturen / Path(ziel).name).is_file():
|
||||
(rot / ziel).parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copyfile(fixturen / Path(ziel).name, rot / ziel)
|
||||
continue
|
||||
if (vor / ziel).is_file():
|
||||
shutil.copyfile(vor / ziel, rot / ziel)
|
||||
elif (rot / ziel).is_file():
|
||||
(rot / ziel).unlink()
|
||||
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:
|
||||
befehl = f"cd {wsl_pfad(rot)} && ~/venvs/trading/bin/python -m pytest {' '.join(python_tests)} -q -p no:cacheprovider 2>&1 | tail -60"
|
||||
lauf = subprocess.run(["wsl.exe", "-d", "Debian", "--", "bash", "-lc", befehl],
|
||||
capture_output=True, text=True, encoding="utf-8", errors="replace")
|
||||
ausgabe = lauf.stdout
|
||||
summe = next((z.strip() for z in reversed(ausgabe.splitlines()) if re.search(r"\d+ (passed|failed|error)", z)), "keine Summenzeile")
|
||||
# Gruen ist nur ein Lauf mit Summenzeile und ohne Fehlschlag. Ohne Summenzeile hat pytest gar
|
||||
# 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)}")
|
||||
if ober_tests:
|
||||
if node_modules is None:
|
||||
zeilen.append("VORAUSSETZUNG FEHLT: node_modules in der Basis-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",
|
||||
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 ")), "")
|
||||
if not summe:
|
||||
# Ohne Summenzeile ist vitest nicht bis zum Ende gekommen (Importfehler, Startfehler):
|
||||
# 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 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)
|
||||
gruen = [e for e in ergebnisse if e[1] == "GRUEN"]
|
||||
print(f"\n{len(ergebnisse)} Laeufe, davon {len(gruen)} gruen ohne Umsetzung.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
sys.exit(main())
|
||||
except Exception as ausnahme: # noqa: BLE001 - ein Absturz des Werkzeugs ist selbst ein Befund
|
||||
print(f"BEFUND: das Werkzeug ist abgebrochen: {ausnahme!r}")
|
||||
sys.exit(2)
|
||||
Reference in new issue
Block a user