Erste Fassung: Claude-Werkzeuge aus dem Vault
.vault-tools (ohne trading-plattform-pruefung und watermarks-remover), .claude/agents, .claude/skills, .claude/workflows, .claude/settings.json. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
commit
fa1c26b407
137 files changed
+43926
No files matched your search
@@ -0,0 +1,754 @@
|
||||
"""Faelle fuer quorum_werkzeug.py: je Fall ein kleines Git-Repo oder ein Dokumentensatz im Temp, das Skript laeuft als eigener Prozess.
|
||||
|
||||
Die Grundlinie laeuft zuerst; scheitert sie, messen die uebrigen Faelle nichts. Kein Fall
|
||||
startet Codex oder geht ins Netz: Die Codex-Faelle schreiben die Ergebnisdatei selbst, in der
|
||||
Form, die codex-companion.mjs result <job> --json liefert (Plugin codex 1.0.6,
|
||||
scripts/codex-companion.mjs Zeilen 910-926). Die Berichte im Temp haben das Frontmatter, das
|
||||
quorum.js schreibt (Funktion kopf dort).
|
||||
Aufruf: python faelle_quorum_werkzeug.py [<pfad zu quorum_werkzeug.py>]
|
||||
Exit 0, wenn alle Faelle bestehen, sonst 1.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
SKRIPT = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(__file__).with_name("quorum_werkzeug.py")
|
||||
HEUTE = date.today().isoformat()
|
||||
MARKE = "0123456789abcdef"
|
||||
FAELLE = []
|
||||
ZEILE_FELDER = ["ordner", "anlass", "art", "wurzel", "leer", "basis", "stand", "anzahl_dateien", "lesemarke",
|
||||
"bericht_pfad", "nummer", "datum", "vorher", "suite", "hinweise", "paket_summe"]
|
||||
|
||||
|
||||
def fall(name):
|
||||
def eintragen(funktion):
|
||||
FAELLE.append((name, funktion))
|
||||
return funktion
|
||||
return eintragen
|
||||
|
||||
|
||||
def soll(bedingung, text):
|
||||
return [] if bedingung else [text]
|
||||
|
||||
|
||||
def schreibe(pfad, text):
|
||||
pfad.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(pfad, "w", encoding="utf-8", newline="\n") as datei:
|
||||
datei.write(text)
|
||||
|
||||
|
||||
def summe(pfad):
|
||||
return hashlib.sha256(pfad.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def git(wurzel, *argumente):
|
||||
return subprocess.run(
|
||||
["git", "-C", str(wurzel), "-c", "user.name=Quorum Test", "-c", "[email protected]",
|
||||
"-c", "core.autocrlf=false", "-c", "commit.gpgsign=false", *argumente],
|
||||
check=True, capture_output=True, text=True, encoding="utf-8", errors="replace").stdout.strip()
|
||||
|
||||
|
||||
def repo(tmp):
|
||||
"""Repo mit zwei Commits. A legt a.py, b.py und CHANGELOG.md an; B aendert a.py und das CHANGELOG."""
|
||||
w = tmp / "repo"
|
||||
w.mkdir()
|
||||
git(w, "init", "-q")
|
||||
schreibe(w / "a.py", "x = 1\n")
|
||||
schreibe(w / "b.py", "y = 2\n")
|
||||
schreibe(w / "CHANGELOG.md", "# Changelog\n\n## 1.0.0\n- erste Fassung\n")
|
||||
git(w, "add", "-A")
|
||||
git(w, "commit", "-q", "-m", "A")
|
||||
a = git(w, "rev-parse", "HEAD")
|
||||
schreibe(w / "a.py", "x = 2\n")
|
||||
schreibe(w / "CHANGELOG.md", "# Changelog\n\n## 1.1.0\n- neue Zeile im Log\n\n## 1.0.0\n- erste Fassung, korrigiert\n")
|
||||
git(w, "commit", "-q", "-am", "B")
|
||||
return w, a, git(w, "rev-parse", "HEAD")
|
||||
|
||||
|
||||
def bericht(ordner, name, nummer, stand, urteil="frei"):
|
||||
"""Ein Bericht im Frontmatter-Format, das quorum.js schreibt."""
|
||||
if isinstance(stand, list):
|
||||
stand_text = "stand:\n" + "".join(f' - "{s}"\n' for s in stand)
|
||||
else:
|
||||
stand_text = f"stand: {stand}\n"
|
||||
schreibe(ordner / name,
|
||||
"---\ntype: note\n"
|
||||
f'title: "Quorum {nummer}: einspielen"\n'
|
||||
"status: done\n"
|
||||
'summary: "Urteil frei: kein bestaetigter und kein ungepruefter blockierender Befund; Stimmen Opus, Sonnet, Codex."\n'
|
||||
"tags: [quorum]\ncreated: 2026-09-20\nupdated: 2026-09-20\n"
|
||||
f"nummer: {nummer}\nanlass: einspielen\nurteil: {urteil}\n"
|
||||
'basis: "0000000000000000000000000000000000000000"\n'
|
||||
f"{stand_text}codex: abgegeben\n---\n\n# Quorum {nummer}: einspielen\n")
|
||||
|
||||
|
||||
def workflow_bericht(pfad, nummer, basis, stand_zeilen=("stand: PAKET",), rumpf="", zeilenende="\n"):
|
||||
"""Ein Bericht, wie ihn quorum.js als bericht_md liefert: stand steht als Platzhalter PAKET."""
|
||||
zeilen = ["---", "type: note", f'title: "Quorum {nummer}: einspielen"', "status: done",
|
||||
'summary: "Urteil frei."', "tags: [quorum]", f"created: {HEUTE}", f"updated: {HEUTE}",
|
||||
f"nummer: {nummer}", "anlass: einspielen", "urteil: frei", f'basis: "{basis}"',
|
||||
*stand_zeilen, "codex: abgegeben", "---", "", f"# Quorum {nummer}: einspielen", "", rumpf]
|
||||
pfad.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(pfad, "w", encoding="utf-8", newline="") as datei:
|
||||
datei.write(zeilenende.join(zeilen) + zeilenende)
|
||||
return pfad
|
||||
|
||||
|
||||
def fahre(*argumente):
|
||||
umgebung = dict(os.environ)
|
||||
umgebung["PYTHONIOENCODING"] = "utf-8"
|
||||
# Das Skript muss GIT_OPTIONAL_LOCKS selbst setzen; ein Wert aus der aufrufenden Shell verdeckte das.
|
||||
umgebung.pop("GIT_OPTIONAL_LOCKS", None)
|
||||
lauf = subprocess.run([sys.executable, str(SKRIPT), *[str(x) for x in argumente]], capture_output=True, env=umgebung)
|
||||
aus = lauf.stdout.decode("utf-8", "replace").replace("\r\n", "\n")
|
||||
return lauf.returncode, aus, lauf.stderr.decode("utf-8", "replace").replace("\r\n", "\n")
|
||||
|
||||
|
||||
def paket_json(ausgabe):
|
||||
for zeile in ausgabe.splitlines():
|
||||
if zeile.startswith("QUORUM_PAKET "):
|
||||
return json.loads(zeile[len("QUORUM_PAKET "):])
|
||||
return None
|
||||
|
||||
|
||||
def voll(ziel):
|
||||
"""Der Inhalt von paket.json, oder None."""
|
||||
pfad = ziel / "paket.json"
|
||||
return json.loads(pfad.read_text(encoding="utf-8")) if pfad.exists() else None
|
||||
|
||||
|
||||
def code_paket(tmp, w, *zusatz):
|
||||
ziel = Path(tempfile.mkdtemp(dir=tmp))
|
||||
code, aus, err = fahre("paket", "--anlass", "einspielen", "--projekt", w, "--ziel", ziel, *zusatz)
|
||||
return code, aus + err, paket_json(aus), ziel
|
||||
|
||||
|
||||
def dokusatz(tmp):
|
||||
o = tmp / "kunde"
|
||||
schreibe(o / "00-uebersicht.md", "# Uebersicht\n")
|
||||
schreibe(o / "kapitel" / "01.md", "# Eins\n")
|
||||
schreibe(o / "kapitel" / "02.md", "# Zwei\n")
|
||||
return o
|
||||
|
||||
|
||||
def doku_paket(tmp, o, *dateien, uebersicht="00-uebersicht.md"):
|
||||
ziel = Path(tempfile.mkdtemp(dir=tmp))
|
||||
code, aus, err = fahre("paket", "--anlass", "kundendokument", "--ordner", o, "--uebersicht", uebersicht, "--ziel", ziel, "--dateien", *dateien)
|
||||
return code, aus + err, paket_json(aus), ziel
|
||||
|
||||
|
||||
def ablegen(paket_ordner, bericht_datei, ziel, paket_summe=None):
|
||||
"""ablegen mit --summe; ohne Angabe die SHA-256 von paket.json im Paketordner, Nullen, wenn es fehlt."""
|
||||
if paket_summe is None:
|
||||
pfad = Path(paket_ordner) / "paket.json"
|
||||
paket_summe = summe(pfad) if pfad.exists() else "0" * 64
|
||||
code, aus, err = fahre("ablegen", "--paket", paket_ordner, "--bericht", bericht_datei, "--ziel", ziel, "--summe", paket_summe)
|
||||
return code, aus + err
|
||||
|
||||
|
||||
ANTWORT = {"lesebeleg": f"Lesemarke: {MARKE}", "befunde": [{"datei": "a.py", "stelle": "1", "schwere": "Wichtig", "aussage": "x ist falsch", "beleg": "x = 2"}], "nicht_pruefbar": []}
|
||||
ANTWORT_LIMIT = {**ANTWORT, "befunde": [{**ANTWORT["befunde"][0], "aussage": "zaehlt jedes usage limit im Log", "beleg": 'USAGE_LIMIT = re.compile(r"usage limit", re.I)'}]}
|
||||
LOG_FEHLALARM = (
|
||||
"[2026-09-30T10:00:00.000Z] Starting Codex task.\n"
|
||||
'[2026-09-30T10:00:05.000Z] Running command: rg -n "usage limit" .vault-tools/quorum\n'
|
||||
"[2026-09-30T10:01:00.000Z] Assistant message captured: Der Zaehler fuer usage limit liest das ganze Log.\n"
|
||||
"\n[2026-09-30T10:01:00.000Z] Assistant message\n"
|
||||
"Der Zaehler fuer usage limit liest das ganze Log.\n"
|
||||
"Codex error: You've hit your usage limit. (Zitat aus einem alten Log)\n"
|
||||
)
|
||||
|
||||
|
||||
def block(antwort):
|
||||
return "QUORUM-ANTWORT-ANFANG\n" + json.dumps(antwort, ensure_ascii=False) + "\nQUORUM-ANTWORT-ENDE\n"
|
||||
|
||||
|
||||
def ergebnisdatei(tmp, roh, status="completed", log="[2026-09-30T10:00:00Z] Turn completed.\n", mit_log=True):
|
||||
logdatei = tmp / "job.log"
|
||||
if mit_log:
|
||||
schreibe(logdatei, log)
|
||||
job = {"id": "task-q1", "status": status, "logFile": logdatei.as_posix(), "startedAt": "2026-09-30T10:00:00.000Z", "completedAt": "2026-09-30T10:03:12.000Z"}
|
||||
pfad = tmp / "ergebnis.json"
|
||||
schreibe(pfad, json.dumps({"job": job, "storedJob": {**job, "result": {"status": 0, "rawOutput": roh}}}))
|
||||
return pfad
|
||||
|
||||
|
||||
def auswerten(pfad):
|
||||
code, aus, err = fahre("codex", "--ergebnis", pfad)
|
||||
zeilen = dict(z.split(": ", 1) for z in aus.splitlines() if ": " in z and not z.startswith((" ", "{", "}")))
|
||||
antwort = None
|
||||
if "ANTWORT-JSON:\n" in aus:
|
||||
rest = aus.split("ANTWORT-JSON:\n", 1)[1].strip()
|
||||
antwort = None if rest == "keine" else json.loads(rest)
|
||||
return code, aus + err, zeilen, antwort
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- paket, Code
|
||||
|
||||
@fall("Grundlinie: Code mit Vorbericht, Diff, CHANGELOG, Lesemarke und Codex-Auftrag")
|
||||
def f_grundlinie(tmp):
|
||||
w, a, b = repo(tmp)
|
||||
bericht(w / "docs" / "quorum", "2026-09-20-einspielen.md", 1, a)
|
||||
git(w, "add", "-A")
|
||||
git(w, "commit", "-q", "-m", "Bericht 1")
|
||||
code, text, p, ziel = code_paket(tmp, w)
|
||||
f = soll(code == 0 and p is not None, f"Exit {code}: {text}")
|
||||
if not p:
|
||||
return f
|
||||
v = voll(ziel) or {}
|
||||
f += soll(p["basis"] == a and p["stand"] == git(w, "rev-parse", "HEAD") and p["leer"] is False, f"basis/stand/leer {p['basis']} {p['stand']} {p['leer']}")
|
||||
f += soll(v.get("dateien") == [{"pfad": "CHANGELOG.md", "art": "geaendert"}, {"pfad": "a.py", "art": "geaendert"}] and p["anzahl_dateien"] == 2, f"dateien {v.get('dateien')} {p['anzahl_dateien']}")
|
||||
f += soll(p["nummer"] == 2 and p["vorher"]["nummer"] == 1 and p["vorher"]["urteil"] == "frei" and p["art"] == "code", f"nummer/vorher {p['nummer']} {p['vorher']}")
|
||||
f += soll(p["bericht_pfad"] == (w / "docs" / "quorum" / f"{HEUTE}-einspielen.md").as_posix(), f"bericht_pfad {p['bericht_pfad']}")
|
||||
md = (ziel / "paket.md").read_text(encoding="utf-8")
|
||||
auftrag = (ziel / "codex-auftrag.md").read_text(encoding="utf-8")
|
||||
diff = (ziel / "diff.patch").read_text(encoding="utf-8")
|
||||
f += soll(re.fullmatch(r"[0-9a-f]{16}", p["lesemarke"]) is not None and f"\nLesemarke: {p['lesemarke']}\n" in md, "Lesemarke fehlt in paket.md")
|
||||
f += soll(p["lesemarke"] not in auftrag, "Lesemarke steht im Codex-Auftrag")
|
||||
f += soll((ziel / "paket.md").as_posix() in auftrag and "QUORUM-ANTWORT-ANFANG" in auftrag and "QUORUM-ANTWORT-ENDE" in auftrag, "Codex-Auftrag ohne Paketpfad oder Marken")
|
||||
f += soll("- neue Zeile im Log" in md and "- erste Fassung, korrigiert" in md and "\n- erste Fassung\n" not in md, "CHANGELOG-Zeilen falsch")
|
||||
f += soll("+x = 2" in diff and "docs/quorum" not in diff, "diff.patch falsch")
|
||||
f += soll(sorted(p) == sorted(ZEILE_FELDER), f"Felder der Zeile {sorted(p)}")
|
||||
f += soll({k: x for k, x in v.items() if k not in ("dateien", "stand_liste")} == {k: x for k, x in p.items() if k != "paket_summe"} and v.get("stand_liste", 0) is None, "paket.json weicht von QUORUM_PAKET ab")
|
||||
f += soll(p.get("paket_summe") == hashlib.sha256((ziel / "paket.json").read_bytes()).hexdigest(), f"paket_summe {p.get('paket_summe')} passt nicht zu paket.json")
|
||||
return f
|
||||
|
||||
|
||||
@fall("Erstes Quorum ohne --basis: Befund, kein Paket")
|
||||
def f_erstes_ohne_basis(tmp):
|
||||
w, a, b = repo(tmp)
|
||||
code, text, p, ziel = code_paket(tmp, w)
|
||||
return soll(code == 2 and "--basis angeben" in text and not (ziel / "paket.md").exists() and "Traceback" not in text, f"Exit {code}: {text}")
|
||||
|
||||
|
||||
@fall("Erstes Quorum mit --basis: nummer 1, kein Vorbericht")
|
||||
def f_erstes_mit_basis(tmp):
|
||||
w, a, b = repo(tmp)
|
||||
code, text, p, ziel = code_paket(tmp, w, "--basis", a)
|
||||
return soll(code == 0 and p is not None and p["nummer"] == 1 and p["vorher"] is None and p["basis"] == a and p["anzahl_dateien"] == 2, f"Exit {code}: {text}")
|
||||
|
||||
|
||||
@fall("Erstes Quorum mit --basis HEAD: leerer Unterschied ist ein Befund")
|
||||
def f_erstes_leer(tmp):
|
||||
w, a, b = repo(tmp)
|
||||
code, text, p, ziel = code_paket(tmp, w, "--basis", "HEAD")
|
||||
return soll(code == 2 and "leerem Unterschied" in text, f"Exit {code}: {text}")
|
||||
|
||||
|
||||
@fall("Unbekannte basis: Befund ohne Traceback")
|
||||
def f_basis_unbekannt(tmp):
|
||||
w, a, b = repo(tmp)
|
||||
code, text, p, ziel = code_paket(tmp, w, "--basis", "gibt-es-nicht")
|
||||
return soll(code == 2 and "BEFUND" in text and "rev-parse" in text and "gibt-es-nicht" in text and "Traceback" not in text, f"Exit {code}: {text}")
|
||||
|
||||
|
||||
@fall("Hinweise: basis weicht vom letzten Stand ab, basis ist kein Vorfahr; gleiche basis ohne Hinweis")
|
||||
def f_hinweise(tmp):
|
||||
w, a, b = repo(tmp)
|
||||
git(w, "checkout", "-q", "-b", "seite", a)
|
||||
schreibe(w / "s.py", "s = 1\n")
|
||||
git(w, "add", "-A")
|
||||
git(w, "commit", "-q", "-m", "S")
|
||||
s = git(w, "rev-parse", "HEAD")
|
||||
git(w, "checkout", "-q", "-")
|
||||
bericht(w / "docs" / "quorum", "2026-09-20-einspielen.md", 1, a)
|
||||
f = []
|
||||
for basis, weicht, vorfahr in (("HEAD~1", False, False), (s, True, True)):
|
||||
code, text, p, ziel = code_paket(tmp, w, "--basis", basis)
|
||||
h = " ".join(p["hinweise"]) if p else ""
|
||||
f += soll(code == 0 and p is not None and ("basis weicht" in h) == weicht and ("kein Vorfahr" in h) == vorfahr, f"basis {basis}: Exit {code}: {h} {text}")
|
||||
return f
|
||||
|
||||
|
||||
@fall("Leerer Unterschied mit unversioniertem Bericht: leer, Urteil des Vorberichts reicht durch")
|
||||
def f_leer(tmp):
|
||||
w, a, b = repo(tmp)
|
||||
bericht(w / "docs" / "quorum", "2026-09-20-einspielen.md", 1, b, urteil="nicht_frei")
|
||||
code, text, p, ziel = code_paket(tmp, w)
|
||||
return soll(code == 0 and p is not None and p["leer"] is True and p["anzahl_dateien"] == 0 and p["vorher"]["urteil"] == "nicht_frei" and p["nummer"] == 2, f"Exit {code}: {text}")
|
||||
|
||||
|
||||
@fall("Seit der Basis nur docs/quorum geaendert: leer")
|
||||
def f_nur_berichte(tmp):
|
||||
w, a, b = repo(tmp)
|
||||
bericht(w / "docs" / "quorum", "2026-09-20-einspielen.md", 1, b)
|
||||
git(w, "add", "-A")
|
||||
git(w, "commit", "-q", "-m", "Bericht 1")
|
||||
code, text, p, ziel = code_paket(tmp, w)
|
||||
return soll(code == 0 and p is not None and p["leer"] is True and p["basis"] == b, f"Exit {code}: {text}")
|
||||
|
||||
|
||||
@fall("Geaenderte Datei im Arbeitsbaum: Befund")
|
||||
def f_unsauber_geaendert(tmp):
|
||||
w, a, b = repo(tmp)
|
||||
schreibe(w / "a.py", "x = 3\n")
|
||||
code, text, p, ziel = code_paket(tmp, w, "--basis", a)
|
||||
return soll(code == 2 and "nicht sauber" in text, f"Exit {code}: {text}")
|
||||
|
||||
|
||||
@fall("Unversionierte Datei ausserhalb docs/quorum: Befund")
|
||||
def f_unsauber_neu(tmp):
|
||||
w, a, b = repo(tmp)
|
||||
schreibe(w / "neu.py", "z = 1\n")
|
||||
code, text, p, ziel = code_paket(tmp, w, "--basis", a)
|
||||
return soll(code == 2 and "nicht sauber" in text and "neu.py" in text, f"Exit {code}: {text}")
|
||||
|
||||
|
||||
@fall("Unversionierte Datei bei status.showUntrackedFiles=no: trotzdem Befund")
|
||||
def f_unsauber_verborgen(tmp):
|
||||
w, a, b = repo(tmp)
|
||||
git(w, "config", "status.showUntrackedFiles", "no")
|
||||
schreibe(w / "neu.py", "z = 1\n")
|
||||
code, text, p, ziel = code_paket(tmp, w, "--basis", a)
|
||||
return soll(code == 2 and "nicht sauber" in text and "neu.py" in text, f"Exit {code}: {text}")
|
||||
|
||||
|
||||
@fall("Hoechste nummer gewinnt, an erster und an letzter Stelle der Dateiliste")
|
||||
def f_hoechste_nummer(tmp):
|
||||
f = []
|
||||
for erste, letzte in ((3, 2), (2, 3)):
|
||||
teil = tmp / f"r{erste}"
|
||||
teil.mkdir()
|
||||
w, a, b = repo(teil)
|
||||
bericht(w / "docs" / "quorum", "2026-09-01-version.md", erste, b if erste == 3 else a)
|
||||
bericht(w / "docs" / "quorum", "2026-09-30-einspielen.md", letzte, b if letzte == 3 else a)
|
||||
code, text, p, ziel = code_paket(teil, w)
|
||||
f += soll(code == 0 and p is not None and p["basis"] == b and p["nummer"] == 4 and p["vorher"]["nummer"] == 3, f"erste {erste}, letzte {letzte}: Exit {code}: {text}")
|
||||
return f
|
||||
|
||||
|
||||
@fall("Zwei Berichte mit derselben nummer: Befund")
|
||||
def f_nummer_doppelt(tmp):
|
||||
w, a, b = repo(tmp)
|
||||
bericht(w / "docs" / "quorum", "2026-09-01-version.md", 1, a)
|
||||
bericht(w / "docs" / "quorum", "2026-09-30-einspielen.md", 1, b)
|
||||
code, text, p, ziel = code_paket(tmp, w)
|
||||
return soll(code == 2 and "zwei Berichten" in text, f"Exit {code}: {text}")
|
||||
|
||||
|
||||
@fall("Bericht ohne nummer, mit nummer als Wort und Datei ohne Frontmatter: je ein Befund")
|
||||
def f_bericht_kaputt(tmp):
|
||||
f = []
|
||||
for name, inhalt, erwartet in (
|
||||
("ohne-nummer", "---\ntype: note\nstand: abc\n---\n", "nummer fehlt"),
|
||||
("nummer-wort", "---\nnummer: drei\nstand: abc\n---\n", "nummer fehlt"),
|
||||
("ohne-kopf", "# nur Text\n", "kein Frontmatter"),
|
||||
):
|
||||
teil = tmp / name
|
||||
teil.mkdir()
|
||||
w, a, b = repo(teil)
|
||||
schreibe(w / "docs" / "quorum" / f"{name}.md", inhalt)
|
||||
code, text, p, ziel = code_paket(teil, w)
|
||||
f += soll(code == 2 and erwartet in text and "Traceback" not in text, f"{name}: Exit {code}: {text}")
|
||||
return f
|
||||
|
||||
|
||||
@fall("Stand eines Code-Berichts als Liste: Befund")
|
||||
def f_code_stand_liste(tmp):
|
||||
w, a, b = repo(tmp)
|
||||
bericht(w / "docs" / "quorum", "2026-09-20-einspielen.md", 1, ["0" * 64 + " a.py"])
|
||||
code, text, p, ziel = code_paket(tmp, w)
|
||||
return soll(code == 2 and "erwartet ist ein Commit" in text, f"Exit {code}: {text}")
|
||||
|
||||
|
||||
@fall("Neue, geaenderte und entfernte Datei im Unterschied")
|
||||
def f_arten(tmp):
|
||||
w, a, b = repo(tmp)
|
||||
schreibe(w / "c.py", "z = 3\n")
|
||||
(w / "b.py").unlink()
|
||||
git(w, "add", "-A")
|
||||
git(w, "commit", "-q", "-m", "C")
|
||||
code, text, p, ziel = code_paket(tmp, w, "--basis", a)
|
||||
arten = {d["pfad"]: d["art"] for d in (voll(ziel) or {}).get("dateien", [])}
|
||||
return soll(code == 0 and arten == {"CHANGELOG.md": "geaendert", "a.py": "geaendert", "b.py": "entfernt", "c.py": "neu"}, f"Exit {code}: {arten} {text}")
|
||||
|
||||
|
||||
@fall("Umbenannte Datei: alter Pfad entfernt, neuer Pfad neu")
|
||||
def f_umbenannt(tmp):
|
||||
w, a, b = repo(tmp)
|
||||
git(w, "config", "diff.renames", "true")
|
||||
git(w, "mv", "b.py", "umbenannt.py")
|
||||
git(w, "commit", "-q", "-m", "C")
|
||||
code, text, p, ziel = code_paket(tmp, w, "--basis", b)
|
||||
arten = {d["pfad"]: d["art"] for d in (voll(ziel) or {}).get("dateien", [])}
|
||||
return soll(code == 0 and arten == {"b.py": "entfernt", "umbenannt.py": "neu"}, f"Exit {code}: {arten} {text}")
|
||||
|
||||
|
||||
@fall("Berichtspfad schon belegt: Zaehler -2")
|
||||
def f_pfad_belegt(tmp):
|
||||
w, a, b = repo(tmp)
|
||||
bericht(w / "docs" / "quorum", f"{HEUTE}-einspielen.md", 1, a)
|
||||
code, text, p, ziel = code_paket(tmp, w)
|
||||
return soll(code == 0 and p is not None and p["bericht_pfad"].endswith(f"/docs/quorum/{HEUTE}-einspielen-2.md"), f"Exit {code}: {text}")
|
||||
|
||||
|
||||
@fall("Suite-Datei: letzte EXIT-Zeile und genau die letzten 80 Zeilen stehen im Paket")
|
||||
def f_suite(tmp):
|
||||
w, a, b = repo(tmp)
|
||||
suite = tmp / "suite.txt"
|
||||
schreibe(suite, "EXIT suite: 0\n" + "".join(f"zeile {n}\n" for n in range(1, 120)) + "EXIT suite: 1\n")
|
||||
code, text, p, ziel = code_paket(tmp, w, "--basis", a, "--suite-datei", suite)
|
||||
md = (ziel / "paket.md").read_text(encoding="utf-8") if (ziel / "paket.md").exists() else ""
|
||||
f = soll(code == 0 and p is not None and p["suite"] == "exit 1", f"Exit {code}: {text}")
|
||||
return f + soll("Die letzten 80 Zeilen der Ausgabe:" in md and "\nzeile 41\n" in md and "\nzeile 40\n" not in md and "\nEXIT suite: 1\n```\n" in md, f"Zeilen im Paket: {md[-400:]}")
|
||||
|
||||
|
||||
@fall("Suite-Datei fehlt: Befund")
|
||||
def f_suite_fehlt(tmp):
|
||||
w, a, b = repo(tmp)
|
||||
code, text, p, ziel = code_paket(tmp, w, "--basis", a, "--suite-datei", tmp / "gibt-es-nicht.txt")
|
||||
return soll(code == 2 and "suite-datei" in text, f"Exit {code}: {text}")
|
||||
|
||||
|
||||
@fall("Kontext: vorhandene Datei steht im Paket, fehlende ist ein Befund")
|
||||
def f_kontext(tmp):
|
||||
w, a, b = repo(tmp)
|
||||
plan = tmp / "plan" / "00-uebersicht.md"
|
||||
schreibe(plan, "# Plan\n")
|
||||
code, text, p, ziel = code_paket(tmp, w, "--basis", a, "--kontext", plan)
|
||||
md = (ziel / "paket.md").read_text(encoding="utf-8") if (ziel / "paket.md").exists() else ""
|
||||
f = soll(code == 0 and f"- {plan.as_posix()}" in md, f"Exit {code}: {text}")
|
||||
code, text, p, ziel = code_paket(tmp, w, "--basis", a, "--kontext", plan, tmp / "fehlt.md")
|
||||
return f + soll(code == 2 and "kontext fehlt" in text, f"Exit {code}: {text}")
|
||||
|
||||
|
||||
@fall("projekt ist ein Unterordner des Repos: Befund")
|
||||
def f_unterordner(tmp):
|
||||
w, a, b = repo(tmp)
|
||||
schreibe(w / "unter" / "d.py", "d = 1\n")
|
||||
git(w, "add", "-A")
|
||||
git(w, "commit", "-q", "-m", "C")
|
||||
ziel = Path(tempfile.mkdtemp(dir=tmp))
|
||||
code, aus, err = fahre("paket", "--anlass", "version", "--projekt", w / "unter", "--basis", a, "--ziel", ziel)
|
||||
return soll(code == 2 and "nicht die Wurzel" in aus, f"Exit {code}: {aus} {err}")
|
||||
|
||||
|
||||
@fall("Ziel mit vorhandenem paket.md: Befund, nichts ueberschrieben")
|
||||
def f_ziel_belegt(tmp):
|
||||
w, a, b = repo(tmp)
|
||||
ziel = Path(tempfile.mkdtemp(dir=tmp))
|
||||
schreibe(ziel / "paket.md", "alt\n")
|
||||
code, aus, err = fahre("paket", "--anlass", "einspielen", "--projekt", w, "--basis", a, "--ziel", ziel)
|
||||
return soll(code == 2 and "schon ein paket.md" in aus and (ziel / "paket.md").read_text(encoding="utf-8") == "alt\n", f"Exit {code}: {aus} {err}")
|
||||
|
||||
|
||||
@fall("paket laesst .git/index unberuehrt, auch wo git status ihn auffrischen wuerde (GIT_OPTIONAL_LOCKS=0)")
|
||||
def f_index_unberuehrt(tmp):
|
||||
"""Zwei gleiche Repos mit veralteten Stat-Daten fuer a.py. Die Gegenprobe zeigt, dass ein
|
||||
gewoehnliches git status dort .git/index schreibt; ohne sie misst der zweite Teil nichts."""
|
||||
f = []
|
||||
for teil in ("gegenprobe", "paket"):
|
||||
(tmp / teil).mkdir()
|
||||
w, a, b = repo(tmp / teil)
|
||||
zeit = (w / "a.py").stat().st_mtime - 1000
|
||||
os.utime(w / "a.py", (zeit, zeit))
|
||||
index = w / ".git" / "index"
|
||||
vorher = summe(index)
|
||||
if teil == "gegenprobe":
|
||||
umgebung = {k: v for k, v in os.environ.items() if k != "GIT_OPTIONAL_LOCKS"}
|
||||
subprocess.run(["git", "-C", str(w), "status", "--porcelain"], check=True, capture_output=True, env=umgebung)
|
||||
f += soll(summe(index) != vorher, "Gegenprobe: git status ohne GIT_OPTIONAL_LOCKS=0 laesst .git/index unveraendert; der Fall misst so nichts")
|
||||
else:
|
||||
code, text, p, ziel = code_paket(tmp / teil, w, "--basis", a)
|
||||
f += soll(code == 0 and p is not None, f"Exit {code}: {text}")
|
||||
f += soll(summe(index) == vorher, "paket hat .git/index geschrieben")
|
||||
return f
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- paket, Kundendokument
|
||||
|
||||
@fall("Kundendokument, erstes Quorum: alle Dateien neu, Stand sortiert, kein Diff")
|
||||
def f_doku_erstes(tmp):
|
||||
o = dokusatz(tmp)
|
||||
code, text, p, ziel = doku_paket(tmp, o, "kapitel/02.md", "kapitel/01.md")
|
||||
if not p:
|
||||
return [f"Exit {code}: {text}"]
|
||||
v = voll(ziel) or {}
|
||||
liste = [f"{summe(o / 'kapitel' / '01.md')} kapitel/01.md", f"{summe(o / 'kapitel' / '02.md')} kapitel/02.md"]
|
||||
f = soll(code == 0 and p["art"] == "doku" and p["basis"] == "erstes Quorum" and p["nummer"] == 1 and p["vorher"] is None, f"Kopf {p}")
|
||||
f += soll(v.get("stand_liste") == liste and p["stand"] == "sha256:" + hashlib.sha256("\n".join(sorted(liste)).encode("utf-8")).hexdigest(), f"stand {p['stand']} {v.get('stand_liste')}")
|
||||
f += soll(sorted(d["art"] for d in v.get("dateien", [])) == ["neu", "neu"] and p["anzahl_dateien"] == 2 and p["leer"] is False, f"dateien {v.get('dateien')}")
|
||||
f += soll(p["bericht_pfad"] == (o / "quorum" / f"{HEUTE}-kundendokument.md").as_posix() and not (ziel / "diff.patch").exists(), f"bericht_pfad {p['bericht_pfad']}")
|
||||
return f
|
||||
|
||||
|
||||
@fall("Kundendokument: stand in der Zeile ist die Pruefsumme ueber die sortierte Stand-Liste")
|
||||
def f_doku_stand_summe(tmp):
|
||||
o = dokusatz(tmp)
|
||||
eins = summe(o / "kapitel" / "01.md")
|
||||
for n in range(200):
|
||||
schreibe(o / "kapitel" / "02.md", f"# Zwei {n}\n")
|
||||
if summe(o / "kapitel" / "02.md") < eins:
|
||||
break
|
||||
liste = [f"{eins} kapitel/01.md", f"{summe(o / 'kapitel' / '02.md')} kapitel/02.md"]
|
||||
richtig = "sha256:" + hashlib.sha256("\n".join(sorted(liste)).encode("utf-8")).hexdigest()
|
||||
falsch = "sha256:" + hashlib.sha256("\n".join(liste).encode("utf-8")).hexdigest()
|
||||
f = soll(richtig != falsch, "Fixture unterscheidet sortiert und unsortiert nicht")
|
||||
code, text, p, ziel = doku_paket(tmp, o, "kapitel/01.md", "kapitel/02.md")
|
||||
v = voll(ziel) or {}
|
||||
f += soll(code == 0 and p is not None and p["stand"] == richtig, f"Exit {code}: stand {p['stand'] if p else None} {text}")
|
||||
return f + soll(v.get("stand_liste") == liste, f"stand_liste {v.get('stand_liste')}")
|
||||
|
||||
|
||||
@fall("Kundendokument, zweites Quorum: geaendert, neu und entfernt, unveraendert fehlt")
|
||||
def f_doku_zweites(tmp):
|
||||
o = dokusatz(tmp)
|
||||
schreibe(o / "kapitel" / "03.md", "# Drei\n")
|
||||
alt = [f"{summe(o / 'kapitel' / n)} kapitel/{n}" for n in ("01.md", "02.md", "03.md")]
|
||||
bericht(o / "quorum", "2026-09-20-kundendokument.md", 1, alt)
|
||||
schreibe(o / "kapitel" / "01.md", "# Eins, neu gefasst\n")
|
||||
schreibe(o / "kapitel" / "04.md", "# Vier\n")
|
||||
code, text, p, ziel = doku_paket(tmp, o, "kapitel/01.md", "kapitel/02.md", "kapitel/04.md")
|
||||
arten = {d["pfad"]: d["art"] for d in (voll(ziel) or {}).get("dateien", [])}
|
||||
return soll(code == 0 and arten == {"kapitel/01.md": "geaendert", "kapitel/04.md": "neu", "kapitel/03.md": "entfernt"} and p["basis"] == "2026-09-20-kundendokument.md" and p["nummer"] == 2, f"Exit {code}: {arten} {text}")
|
||||
|
||||
|
||||
@fall("Kundendokument ohne Aenderung: leer")
|
||||
def f_doku_leer(tmp):
|
||||
o = dokusatz(tmp)
|
||||
stand = [f"{summe(o / 'kapitel' / n)} kapitel/{n}" for n in ("01.md", "02.md")]
|
||||
bericht(o / "quorum", "2026-09-20-kundendokument.md", 1, stand)
|
||||
code, text, p, ziel = doku_paket(tmp, o, "kapitel/01.md", "kapitel/02.md")
|
||||
return soll(code == 0 and p is not None and p["leer"] is True and p["anzahl_dateien"] == 0, f"Exit {code}: {text}")
|
||||
|
||||
|
||||
@fall("Kundendokument: Pfad nach aussen, absoluter Pfad, fehlende und doppelte Datei sind Befunde")
|
||||
def f_doku_pfade(tmp):
|
||||
o = dokusatz(tmp)
|
||||
schreibe(tmp / "draussen.md", "# Draussen\n")
|
||||
f = []
|
||||
for dateien, erwartet in (
|
||||
(["../draussen.md"], "nur Pfade innerhalb"),
|
||||
([(tmp / "draussen.md").as_posix()], "nur Pfade innerhalb"),
|
||||
(["kapitel/09.md"], "Datei fehlt"),
|
||||
(["kapitel/01.md", "kapitel/01.md"], "doppelt"),
|
||||
):
|
||||
code, text, p, ziel = doku_paket(tmp, o, *dateien)
|
||||
f += soll(code == 2 and erwartet in text and "Traceback" not in text, f"{dateien}: Exit {code}: {text}")
|
||||
return f
|
||||
|
||||
|
||||
@fall("Kundendokument: fehlende Uebersicht und --projekt sind Befunde")
|
||||
def f_doku_args(tmp):
|
||||
o = dokusatz(tmp)
|
||||
code, text, p, ziel = doku_paket(tmp, o, "kapitel/01.md", uebersicht="fehlt.md")
|
||||
f = soll(code == 2 and "uebersicht fehlt" in text, f"uebersicht: Exit {code}: {text}")
|
||||
ziel = Path(tempfile.mkdtemp(dir=tmp))
|
||||
code, aus, err = fahre("paket", "--anlass", "kundendokument", "--ordner", o, "--uebersicht", "00-uebersicht.md", "--dateien", "kapitel/01.md", "--projekt", tmp, "--ziel", ziel)
|
||||
return f + soll(code == 2 and "gelten nicht fuer kundendokument" in aus, f"projekt: Exit {code}: {aus} {err}")
|
||||
|
||||
|
||||
@fall("Kundendokument: stand als Text oder ohne Pruefsumme ist ein Befund")
|
||||
def f_doku_stand_kaputt(tmp):
|
||||
f = []
|
||||
for name, stand, erwartet in (("text", "abc", "keine Liste"), ("ohne-summe", ["kapitel/01.md"], "unlesbar")):
|
||||
teil = tmp / name
|
||||
teil.mkdir()
|
||||
o = dokusatz(teil)
|
||||
bericht(o / "quorum", "2026-09-20-kundendokument.md", 1, stand)
|
||||
code, text, p, ziel = doku_paket(teil, o, "kapitel/01.md")
|
||||
f += soll(code == 2 and erwartet in text, f"{name}: Exit {code}: {text}")
|
||||
return f
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- ablegen
|
||||
|
||||
@fall("ablegen, Code: stand PAKET wird der Commit, CRLF wird LF, Ordner angelegt, naechster Lauf ist leer")
|
||||
def f_ablegen_code(tmp):
|
||||
w, a, b = repo(tmp)
|
||||
code, text, p, ziel = code_paket(tmp, w, "--basis", a)
|
||||
if not p:
|
||||
return [f"paket: Exit {code}: {text}"]
|
||||
quelle = workflow_bericht(tmp / "bericht.md", p["nummer"], p["basis"], zeilenende="\r\n")
|
||||
code, text = ablegen(ziel, quelle, p["bericht_pfad"], p["paket_summe"])
|
||||
abgelegt = Path(p["bericht_pfad"])
|
||||
roh = abgelegt.read_bytes() if abgelegt.exists() else b""
|
||||
erwartet = quelle.read_bytes().replace(b"\r\n", b"\n").replace(b"\nstand: PAKET\n", f"\nstand: {b}\n".encode("ascii"))
|
||||
f = soll(code == 0 and roh == erwartet, f"Exit {code}: {text} {roh[:400]!r}")
|
||||
code, text, p2, ziel2 = code_paket(tmp, w)
|
||||
return f + soll(code == 0 and p2 is not None and p2["leer"] is True and p2["basis"] == b and p2["nummer"] == 2, f"zweiter Lauf: Exit {code}: {text}")
|
||||
|
||||
|
||||
@fall("ablegen, Kundendokument: stand PAKET wird die Liste, nur im Frontmatter, naechster Lauf ist leer")
|
||||
def f_ablegen_doku(tmp):
|
||||
o = dokusatz(tmp)
|
||||
code, text, p, ziel = doku_paket(tmp, o, "kapitel/01.md", "kapitel/02.md")
|
||||
if not p:
|
||||
return [f"paket: Exit {code}: {text}"]
|
||||
quelle = workflow_bericht(tmp / "bericht.md", p["nummer"], p["basis"], rumpf="stand: PAKET")
|
||||
code, text = ablegen(ziel, quelle, p["bericht_pfad"], p["paket_summe"])
|
||||
abgelegt = Path(p["bericht_pfad"])
|
||||
inhalt = abgelegt.read_text(encoding="utf-8") if abgelegt.exists() else ""
|
||||
liste = "".join(f' - "{summe(o / "kapitel" / n)} kapitel/{n}"\n' for n in ("01.md", "02.md"))
|
||||
f = soll(code == 0 and f'basis: "erstes Quorum"\nstand:\n{liste}codex: abgegeben\n' in inhalt and inhalt.rstrip("\n").endswith("\nstand: PAKET"), f"Exit {code}: {text} {inhalt[:600]}")
|
||||
code, text, p2, ziel2 = doku_paket(tmp, o, "kapitel/01.md", "kapitel/02.md")
|
||||
return f + soll(code == 0 and p2 is not None and p2["leer"] is True and p2["nummer"] == 2, f"zweiter Lauf: Exit {code}: {text}")
|
||||
|
||||
|
||||
@fall("ablegen: falsche nummer, falsche basis, stand PAKET fehlt oder doppelt, Ziel vorhanden, ohne Frontmatter, ohne paket.json sind Befunde; nichts geschrieben")
|
||||
def f_ablegen_befunde(tmp):
|
||||
w, a, b = repo(tmp)
|
||||
code, text, p, ziel = code_paket(tmp, w, "--basis", a)
|
||||
if not p:
|
||||
return [f"paket: Exit {code}: {text}"]
|
||||
vorhanden = tmp / "vorhanden.md"
|
||||
schreibe(vorhanden, "alt\n")
|
||||
f = []
|
||||
for name, nummer, basis, stand_zeilen, nach, erwartet in (
|
||||
("nummer", p["nummer"] + 1, p["basis"], ("stand: PAKET",), tmp / "n" / "x.md", "nummer"),
|
||||
("basis", p["nummer"], b, ("stand: PAKET",), tmp / "b" / "x.md", "basis"),
|
||||
("ohne-stand", p["nummer"], p["basis"], ("stand: x",), tmp / "o" / "x.md", "stand: PAKET"),
|
||||
("stand-doppelt", p["nummer"], p["basis"], ("stand: PAKET", "stand: PAKET"), tmp / "d" / "x.md", "stand: PAKET"),
|
||||
("ziel-da", p["nummer"], p["basis"], ("stand: PAKET",), vorhanden, "gibt es schon"),
|
||||
):
|
||||
quelle = workflow_bericht(tmp / f"{name}.md", nummer, basis, stand_zeilen)
|
||||
code, text = ablegen(ziel, quelle, nach)
|
||||
unberuehrt = nach.read_text(encoding="utf-8") == "alt\n" if nach == vorhanden else not nach.parent.exists()
|
||||
f += soll(code == 2 and "BEFUND" in text and erwartet in text and unberuehrt and "Traceback" not in text, f"{name}: Exit {code}: {text}")
|
||||
ohne_kopf = tmp / "ohne-kopf.md"
|
||||
schreibe(ohne_kopf, "# nur Text\nstand: PAKET\n")
|
||||
code, text = ablegen(ziel, ohne_kopf, tmp / "k" / "x.md")
|
||||
f += soll(code == 2 and "ohne Frontmatter" in text and not (tmp / "k").exists(), f"ohne-kopf: Exit {code}: {text}")
|
||||
leer = Path(tempfile.mkdtemp(dir=tmp))
|
||||
code, text = ablegen(leer, tmp / "nummer.md", tmp / "p" / "x.md")
|
||||
return f + soll(code == 2 and "paket.json nicht lesbar" in text and not (tmp / "p").exists(), f"ohne-paket: Exit {code}: {text}")
|
||||
|
||||
|
||||
@fall("ablegen: --summe eines anderen Pakets, umformatiertes paket.json oder fehlendes --summe: Exit 2, nichts geschrieben")
|
||||
def f_ablegen_summe(tmp):
|
||||
w, a, b = repo(tmp)
|
||||
code, text, p, ziel = code_paket(tmp, w, "--basis", a)
|
||||
code2, text2, p2, ziel2 = code_paket(tmp, w, "--basis", a)
|
||||
if not p or not p2:
|
||||
return [f"paket: Exit {code}/{code2}: {text} {text2}"]
|
||||
f = soll(p["nummer"] == p2["nummer"] and p["basis"] == p2["basis"] and p["paket_summe"] != p2["paket_summe"], "Fixture: Die beiden Pakete sollen sich in paket_summe unterscheiden, nicht in nummer und basis")
|
||||
quelle = workflow_bericht(tmp / "bericht.md", p["nummer"], p["basis"])
|
||||
code, text = ablegen(ziel2, quelle, tmp / "fremd" / "x.md", p["paket_summe"])
|
||||
f += soll(code == 2 and "BEFUND" in text and "weicht von der SHA-256" in text and not (tmp / "fremd").exists(), f"fremdes Paket: Exit {code}: {text}")
|
||||
schreibe(ziel / "paket.json", (ziel / "paket.json").read_text(encoding="utf-8") + "\n")
|
||||
code, text = ablegen(ziel, quelle, tmp / "umformatiert" / "x.md", p["paket_summe"])
|
||||
f += soll(code == 2 and "weicht von der SHA-256" in text and not (tmp / "umformatiert").exists(), f"paket.json umformatiert: Exit {code}: {text}")
|
||||
code, aus, err = fahre("ablegen", "--paket", ziel2, "--bericht", quelle, "--ziel", tmp / "ohne" / "x.md")
|
||||
return f + soll(code == 2 and "--summe" in aus + err and not (tmp / "ohne").exists(), f"ohne --summe: Exit {code}: {aus} {err}")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- codex
|
||||
|
||||
@fall("Codex: completed, Log ohne usage limit, Antwort gelesen, Schwere klein geschrieben")
|
||||
def f_codex_grund(tmp):
|
||||
code, text, z, antwort = auswerten(ergebnisdatei(tmp, "Ich habe geprueft.\n" + block(ANTWORT)))
|
||||
f = soll(code == 0 and z.get("job_status") == "completed" and z.get("log_gelesen") == "ja" and z.get("usage_limit_treffer") == "0", f"Exit {code}: {text}")
|
||||
f += soll(z.get("antwort_gefunden") == "ja" and z.get("dauer_s") == "192", f"antwort/dauer {z}")
|
||||
return f + soll(antwort is not None and antwort["lesebeleg"] == f"Lesemarke: {MARKE}" and antwort["befunde"][0]["schwere"] == "wichtig", f"antwort {antwort}")
|
||||
|
||||
|
||||
@fall("Codex: usage limit im Log, gross geschrieben")
|
||||
def f_codex_limit(tmp):
|
||||
code, text, z, antwort = auswerten(ergebnisdatei(tmp, block(ANTWORT), log="[x] Codex error: You've hit your Usage Limit. Try again later.\n"))
|
||||
return soll(code == 0 and z.get("usage_limit_treffer") == "1", f"Exit {code}: {text}")
|
||||
|
||||
|
||||
@fall("Codex: usage limit nur in Befehl, Nachricht und Final output zaehlt nicht")
|
||||
def f_codex_limit_fehlalarm(tmp):
|
||||
roh = block(ANTWORT_LIMIT)
|
||||
log = LOG_FEHLALARM + "\n[2026-09-30T10:02:00.000Z] Final output\n" + roh + "[2026-09-30T10:02:01.000Z] Turn completed.\n"
|
||||
code, text, z, antwort = auswerten(ergebnisdatei(tmp, roh, log=log))
|
||||
return soll(code == 0 and z.get("job_status") == "completed" and z.get("usage_limit_treffer") == "0" and z.get("antwort_gefunden") == "ja", f"Exit {code}: {text}")
|
||||
|
||||
|
||||
@fall("Codex: echte Fehlerzeile nach anderen Zeilen, im Final output wiederholt, zaehlt einmal")
|
||||
def f_codex_limit_echt(tmp):
|
||||
log = ("[2026-09-05T11:37:00.000Z] Starting Codex task.\n"
|
||||
'[2026-09-05T11:37:10.000Z] Running command: rg -n "TODO" .\n'
|
||||
"[2026-09-05T11:37:32.037Z] Codex error: You've hit your usage limit. Upgrade to Plus or try again later.\n"
|
||||
"\n[2026-09-05T11:37:32.100Z] Final output\n"
|
||||
"Codex error: You've hit your usage limit. Upgrade to Plus or try again later.\n")
|
||||
code, text, z, antwort = auswerten(ergebnisdatei(tmp, "", status="failed", log=log))
|
||||
return soll(code == 0 and z.get("usage_limit_treffer") == "1", f"Exit {code}: {text}")
|
||||
|
||||
|
||||
@fall("Codex: Log fehlt")
|
||||
def f_codex_ohne_log(tmp):
|
||||
code, text, z, antwort = auswerten(ergebnisdatei(tmp, block(ANTWORT), mit_log=False))
|
||||
return soll(code == 0 and z.get("log_gelesen") == "nein", f"Exit {code}: {text}")
|
||||
|
||||
|
||||
@fall("Codex: Zwischenmeldung, kaputtes JSON, unbekannte Schwere, fehlender Beleg und fehlende Ende-Marke ergeben keine Antwort")
|
||||
def f_codex_ohne_antwort(tmp):
|
||||
f = []
|
||||
falsch_schwere = {**ANTWORT, "befunde": [{**ANTWORT["befunde"][0], "schwere": "high"}]}
|
||||
ohne_beleg = {**ANTWORT, "befunde": [{k: v for k, v in ANTWORT["befunde"][0].items() if k != "beleg"}]}
|
||||
for name, roh in (
|
||||
("zwischenmeldung", "Ich pruefe nun die Dateien im Projekt."),
|
||||
("kaputtes-json", "QUORUM-ANTWORT-ANFANG\n{kein json\nQUORUM-ANTWORT-ENDE\n"),
|
||||
("schwere-high", block(falsch_schwere)),
|
||||
("ohne-beleg", block(ohne_beleg)),
|
||||
("ohne-ende", "QUORUM-ANTWORT-ANFANG\n" + json.dumps(ANTWORT)),
|
||||
):
|
||||
teil = tmp / name
|
||||
teil.mkdir()
|
||||
code, text, z, antwort = auswerten(ergebnisdatei(teil, roh))
|
||||
f += soll(code == 0 and z.get("antwort_gefunden") == "nein" and antwort is None and z.get("fehler"), f"{name}: Exit {code}: {text}")
|
||||
return f
|
||||
|
||||
|
||||
@fall("Codex: zwei Bloecke, der letzte zaehlt")
|
||||
def f_codex_zwei_bloecke(tmp):
|
||||
zweite = {**ANTWORT, "lesebeleg": "Lesemarke: ffffffffffffffff"}
|
||||
code, text, z, antwort = auswerten(ergebnisdatei(tmp, block(ANTWORT) + "Nachtrag\n" + block(zweite)))
|
||||
return soll(code == 0 and antwort is not None and antwort["lesebeleg"] == "Lesemarke: ffffffffffffffff", f"Exit {code}: {text}")
|
||||
|
||||
|
||||
@fall("Codex: failed geht als job_status durch")
|
||||
def f_codex_failed(tmp):
|
||||
code, text, z, antwort = auswerten(ergebnisdatei(tmp, "", status="failed"))
|
||||
return soll(code == 0 and z.get("job_status") == "failed" and z.get("antwort_gefunden") == "nein", f"Exit {code}: {text}")
|
||||
|
||||
|
||||
@fall("Codex: Ergebnisdatei kein JSON, ohne job oder fehlend ist ein Befund")
|
||||
def f_codex_datei_kaputt(tmp):
|
||||
kaputt = tmp / "kaputt.json"
|
||||
schreibe(kaputt, "{kein json")
|
||||
ohne_job = tmp / "ohne-job.json"
|
||||
schreibe(ohne_job, json.dumps({"storedJob": {}}))
|
||||
f = []
|
||||
for pfad, erwartet in ((kaputt, "nicht lesbar"), (ohne_job, "ohne job"), (tmp / "fehlt.json", "nicht lesbar")):
|
||||
code, text, z, antwort = auswerten(pfad)
|
||||
f += soll(code == 2 and erwartet in text and "Traceback" not in text, f"{pfad.name}: Exit {code}: {text}")
|
||||
return f
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- Lauf
|
||||
|
||||
def main():
|
||||
gescheitert = 0
|
||||
grundlinie_gescheitert = False
|
||||
for nummer, (name, funktion) in enumerate(FAELLE):
|
||||
with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as ordner:
|
||||
try:
|
||||
fehler = funktion(Path(ordner))
|
||||
except Exception as e: # Ein Absturz im Fall ist ein Befund, kein Abbruch des Laufs.
|
||||
fehler = [f"Absturz im Fall: {type(e).__name__}: {e}"]
|
||||
if fehler:
|
||||
gescheitert += 1
|
||||
grundlinie_gescheitert = grundlinie_gescheitert or nummer == 0
|
||||
print(f"FEHLER {name}")
|
||||
for text in fehler:
|
||||
print(f" {text[:600]}")
|
||||
else:
|
||||
print(f"OK {name}")
|
||||
if grundlinie_gescheitert:
|
||||
print("HINWEIS Grundlinie gescheitert: Die uebrigen Faelle messen nichts.")
|
||||
print(f"{len(FAELLE)} Faelle, {gescheitert} gescheitert")
|
||||
return 1 if gescheitert else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user