Regelwerk, Agenten, Skills und Werkzeuge veröffentlichen

Erster Stand des öffentlichen Ausschnitts aus dem Vault. Die .gitignore
arbeitet als Positivliste: ausgeschlossen ist alles, eingeschlossen nur,
was ausdrücklich wieder aufgenommen wird. Notizen, Projekte, Finanzen,
das Sitzungsgedächtnis und settings.local.json bleiben damit außen vor.

Der Kundenname in AI/Regelwerk/seo.md ist durch eine neutrale
Bezeichnung ersetzt; die Regeln selbst sind unverändert.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
Pascal MichelandClaude Opus 5 committed 2026-09-17 22:38:15 +02:00
commit 2c9a4475fc
97 files changed
+14627

No files matched your search

@@ -0,0 +1,165 @@
"""Mutantenlauf auf der materialisierten Kopie: Schlaegt jeder neue Test an, wenn sein Gegenstand kaputt ist?
Laeuft in WSL; Aufruf: python mutanten_py.py <wsl-pfad-der-kopie>. Je Mutant: Datei als Bytes sichern, genau eine Stelle ersetzen, die genannten Tests
fahren, Datei aus der Sicherung zurueckspielen. Erwartet ist ROT; ein gruener Mutant ist ein Befund.
Zuerst die Grundlinie: Ohne Mutant muessen dieselben Tests gruen sein.
"""
import subprocess
import sys
from pathlib import Path
MAT = Path(sys.argv[1]) # WSL-Pfad der Kopie mit dem angewendeten Plan
PY = str(Path.home() / "venvs/trading/bin/python")
MUTANTEN = [
(
"lies_lauf trennt mit der Zeilentrennung von str",
"trading/dashboard/laeufe.py",
' with pfad.open("rb") as strom:\n for roh in strom:\n if not roh.strip():',
' with pfad.open("rb") as strom:\n for roh in [z.encode("utf-8") for z in strom.read().decode("utf-8").splitlines()]:\n if not roh.strip():',
["tests/test_dashboard_laeufe.py::test_die_einzelansicht_trennt_nicht_an_u2028"],
),
(
"_ist_protokolldatei laesst alles durch",
"trading/dashboard/laeufe.py",
" return pfad.is_file() and not pfad.is_symlink()",
" return True",
[
"tests/test_dashboard_laeufe.py::test_ein_ordner_mit_der_endung_jsonl_ist_kein_lauf",
"tests/test_dashboard_laeufe.py::test_eine_symbolische_verknuepfung_ist_kein_lauf",
],
),
(
"_ist_protokolldatei folgt Verknuepfungen",
"trading/dashboard/laeufe.py",
" return pfad.is_file() and not pfad.is_symlink()",
" return pfad.is_file()",
["tests/test_dashboard_laeufe.py::test_eine_symbolische_verknuepfung_ist_kein_lauf"],
),
(
"letzte_zeilen gibt ein Bruchstueck ohne Trenner zurueck",
"trading/dashboard/laeufe.py",
" if not trenner:\n return []",
" if not trenner:\n return [_bruchstueck]",
["tests/test_dashboard_laeufe.py::test_eine_letzte_zeile_ueber_dem_block_ohne_zeilenende_liefert_nichts"],
),
(
"Eimeranfang mit festem Stundenschritt",
"trading/dashboard/markt.py",
" anfang_min = HANDELSSTART_MIN + nummer * aufloesung_min",
" anfang_min = HANDELSSTART_MIN + nummer * 60",
["tests/test_dashboard_markt.py::test_15_und_5_minuten_teilen_den_handelstag_ohne_rest"],
),
(
"Tagesachse in der Achsenzone statt in der Handelszone",
"trading/dashboard/markt.py",
" return zeitpunkt.tz_convert(HANDELSZONE).date().isoformat()",
" return zeitpunkt.tz_convert(zone).date().isoformat()",
["tests/test_dashboard_markt.py::test_eine_tageskerze_bekommt_den_new_yorker_kalendertag_als_string"],
),
(
"Tagesachse in UTC",
"trading/dashboard/markt.py",
" return zeitpunkt.tz_convert(HANDELSZONE).date().isoformat()",
" return zeitpunkt.date().isoformat()",
["tests/test_dashboard_markt.py::test_eine_tageskerze_bekommt_den_new_yorker_kalendertag_als_string"],
),
(
"Handelstag mit 391 Kerzen",
"trading/dashboard/markt.py",
"KERZEN_JE_HANDELSTAG = 390",
"KERZEN_JE_HANDELSTAG = 391",
["tests/test_dashboard_markt.py::test_der_handelstag_hat_390_minuten_und_die_lesespanne_folgt_daraus"],
),
(
"Kalenderfaktor 1,5 statt 1,6",
"trading/dashboard/markt.py",
"KALENDERFAKTOR_MINUTEN = 1.6",
"KALENDERFAKTOR_MINUTEN = 1.5",
["tests/test_dashboard_markt.py::test_der_handelstag_hat_390_minuten_und_die_lesespanne_folgt_daraus"],
),
(
"lies_kerzen liest die Metadaten ungeschuetzt",
"trading/dashboard/markt.py",
" meta = _meta(katalog, bar_type)\n bis = _bis(ende, meta)",
" meta = katalog.meta(bar_type)\n bis = _bis(ende, meta)",
["tests/test_dashboard_markt.py::test_lies_kerzen_uebersteht_eine_unlesbare_metadatei"],
),
(
"katalog() prueft nur die Wurzel",
"trading/dashboard/app.py",
' if not (wurzel / "_meta").is_dir():',
" if not wurzel.is_dir():",
["tests/test_dashboard_endpunkte.py::test_ein_ordner_ohne_meta_ist_kein_katalog_und_bekommt_keines"],
),
(
"_ersetze laesst den alten Snapshot liegen",
"trading/dashboard/verteiler.py",
" if warteschlange.full():\n try:",
" if warteschlange.full():\n return\n if warteschlange.full():\n try:",
["tests/test_dashboard_verteiler.py::test_eine_volle_warteschlange_bekommt_den_neuen_snapshot"],
),
(
"killswitch_uebung prueft den Schalter erst nach der Konfiguration",
"scripts/killswitch_uebung.py",
" if not parse_args().ausfuehren:\n print(OHNE_SCHALTER, file=sys.stderr)\n return 2\n\n limits = lade_limits(\"config/risk.toml\")",
" limits = lade_limits(\"config/risk.toml\")\n if not parse_args().ausfuehren:\n print(OHNE_SCHALTER, file=sys.stderr)\n return 2\n",
[
"tests/test_skripte_starten.py::test_die_killswitch_uebung_ohne_schalter_endet_mit_exit_2",
],
),
]
GRUND = ""
def fahre(tests: list[str]) -> bool:
"""Exitcode allein ist kein Beleg: Ein Mutant mit Syntaxfehler waere genauso rot.
Deshalb wird die erste Fehlerzeile mitgeschrieben und in der Ausgabe gezeigt."""
global GRUND
ergebnis = subprocess.run(
[PY, "-m", "pytest", "-q", "-p", "no:cacheprovider", "-x", "--tb=short", *tests],
cwd=MAT,
capture_output=True,
text=True,
stdin=subprocess.DEVNULL,
)
zeilen = [z.strip() for z in ergebnis.stdout.splitlines() if z.startswith("E ")]
GRUND = " | ".join(zeilen[:2])[:260]
return ergebnis.returncode == 0
def main() -> int:
befunde = 0
alle = sorted({t for m in MUTANTEN for t in m[4]})
if not fahre(alle):
print("BEFUND Grundlinie: ohne Mutant sind nicht alle Tests gruen -- der Lauf misst nichts.")
return 2
print(f"Grundlinie gruen ({len(alle)} Tests).")
for name, datei, alt, neu, tests in MUTANTEN:
pfad = MAT / datei
sicherung = pfad.read_bytes()
text = sicherung.decode("utf-8")
anzahl = text.count(alt)
if anzahl != 1:
print(f"BEFUND Mutant '{name}': Stelle steht {anzahl}-mal in {datei}")
befunde += 1
continue
try:
pfad.write_bytes(text.replace(alt, neu).encode("utf-8"))
gruen = fahre(tests)
finally:
pfad.write_bytes(sicherung)
if gruen:
print(f"BEFUND Mutant '{name}' bleibt GRUEN: {tests}")
befunde += 1
else:
print(f"rot {name}\n {GRUND}")
print(f"{befunde} Befund(e).")
return 2 if befunde else 0
if __name__ == "__main__":
sys.exit(main())