diff --git a/.vault-tools/plan-kopie/LIESMICH.md b/.vault-tools/plan-kopie/LIESMICH.md index 3f2c1a9..dac5000 100644 --- a/.vault-tools/plan-kopie/LIESMICH.md +++ b/.vault-tools/plan-kopie/LIESMICH.md @@ -5,7 +5,7 @@ title: "Planwerkzeuge: Plan auf eine Kopie anwenden und jede Welle dagegen vergl summary: "Skripte für die Standardschleife bei Plänen mit ausgeschriebenem Code — schlanke Kopie bauen, Plan auf die Kopie anwenden, je Welle Menge und Bytes vergleichen, Aus-wird-Blöcke prüfen, Codeblöcke auf unsichtbare Zeichen absuchen, Escape-Folgen per Codepunkt setzen. Entstanden 2026-09-17 in der Trading Plattform (Teil 3b)." tags: [vault-tools, codeworkflow] created: 2026-09-17 -updated: 2026-09-17 +updated: 2026-09-20 --- # Planwerkzeuge für die Kopie mit angewendetem Plan @@ -27,6 +27,8 @@ Die Regel dazu steht in [[AI/Regelwerk/codeworkflow|codeworkflow.md]] („Die Ko Seit 2026-09-18 nimmt `welle_vergleich.py` unter `## Zieldateien` auch Pfade relativ zum Projekt (ohne Laufwerk und ohne Wurzel), weil Pläne für einen Git-Worktree keine absoluten Pfade nennen; ein `..` darin ist ein Befund wie ein fremder absoluter Pfad. Jede Zeile braucht weiter die Klammer hinter dem Pfad (`(neu)`, `(geändert)`, `(gelöscht)`). `plan_materialisiere.py` erkennt eine Zieldatei ohne Schrägstrich nur mit den Endungen py, ts, tsx, toml, md, css und json — für `compose.yaml` in der Projektwurzel schreibt die Überschrift `./compose.yaml`. Der Treiber fährt dazu drei weitere Fälle (relative Grundlinie, relative Abweichung, `..` nach außen), zusammen 17. +Seit 2026-09-20 quotet `rot_nachweis.py` den Arbeitsordner für den WSL-Aufruf (`shlex.quote`). Ein Temp-Pfad mit Leerzeichen — der Benutzername steckt im Scratchpad-Pfad — ließ vorher das `cd` scheitern: Alle sieben Tasks eines Plans der Trading Plattform standen als „ROT pytest: keine Summenzeile" da, ohne dass ein einziger Test lief. Nach der Korrektur derselbe Lauf mit echten Summenzeilen (`3 failed, 17 passed`, `1 error`, …). Wer „keine Summenzeile" bei **jedem** Task sieht, hat kein Rot gemessen, sondern ein kaputtes Werkzeug. + `welle_vergleich_treiber.py` fährt 14 Angriffsfälle gegen `welle_vergleich.py` (Grundlinie zuerst; fremde Datei, unveränderte Zieldatei, Abweichung in erster und letzter Zeile und in der letzten Zieldatei, CRLF, Datei fehlt in der Kopie, Task ohne Zieldateien, Kopie fehlt, Löschung nicht ausgeführt, Prosa-Datei unverändert, Zieldatei außerhalb des Projekts, Datei länger als der Plan). `plan_zeichen.py` hat am 2026-09-17 an einem echten Fund rot gezeigt (U+2028 im Plan). `plan_materialisiere.py` reproduziert Task 108 des Teil-3b-Plans aus dem Stand vor den Wellen byte-genau. `kopie_bauen.py` ist am 2026-09-17 an drei kaputten Eingaben rot gesehen: Ziel existiert schon (Exit 2, nichts kopiert), Ausschluss `node_modules` ohne Gegenstück im Wurzelordner (Befund), und ein aus der Kopie entferntes Paket `trading/data` (die Paketprüfung nennt es; nach dem Zurücklegen leer). Einen Treiber dazu gibt es noch nicht — die drei Fälle sind von Hand gefahren. ## Was die Skripte nicht sehen diff --git a/.vault-tools/plan-kopie/rot_nachweis.py b/.vault-tools/plan-kopie/rot_nachweis.py index 7781805..6acfea2 100644 --- a/.vault-tools/plan-kopie/rot_nachweis.py +++ b/.vault-tools/plan-kopie/rot_nachweis.py @@ -28,6 +28,7 @@ spec.json: [{"task": "task-141-....md", "vor": ["task-...md", ...]}, ...] import json import os import re +import shlex import shutil import subprocess import sys @@ -148,7 +149,10 @@ def main() -> int: 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" + # 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" lauf = subprocess.run(["wsl.exe", "-d", "Debian", "--", "bash", "-lc", befehl], capture_output=True, text=True, encoding="utf-8", errors="replace") ausgabe = lauf.stdout diff --git a/.vault-tools/trading-plattform-pruefung/LIESMICH.md b/.vault-tools/trading-plattform-pruefung/LIESMICH.md index d515b94..694523f 100644 --- a/.vault-tools/trading-plattform-pruefung/LIESMICH.md +++ b/.vault-tools/trading-plattform-pruefung/LIESMICH.md @@ -5,7 +5,7 @@ title: "Trading Plattform: Prüfwerkzeuge des Koordinators fürs Dashboard" summary: "Eigener Lauf mit echten Daten zum Dashboard der Trading Plattform — Vergleich der Endpunkte gegen Vollparse und pandas, zwei WebSocket-Verbindungen, Browser-Lauf und Wiederaufbau mit Playwright, gefahrlose Proben der zwei Skripte, Mutantenläufe. Entstanden 2026-09-17 (Teil 3b), erweitert 2026-09-18 (Teil 10: Anmeldung, Angriffsfälle, Beobachtung, Kerzenlatenz), bewusst nicht im Projekt-Repo." tags: [vault-tools, trading-plattform] created: 2026-09-17 -updated: 2026-09-18 +updated: 2026-09-21 --- # Prüfwerkzeuge fürs Dashboard der Trading Plattform @@ -41,6 +41,25 @@ Koordinator-Werkzeuge, nicht fürs Repo. Sie starten **nie** den echten Broker: **Seit 2026-09-18 kennen `angriffe.py` und `browserlauf_zugang.py` https.** `angriffe.py` erwartet je nach Schema der Basis-URL `Secure` am Cookie und `Strict-Transport-Security` (https) oder beides nicht (http), dazu unter https keine Antwort 200 auf Klartext; `--ca ` nennt das Zertifikat, dem vertraut wird (geprüft, nicht abgeschaltet). `browserlauf_zugang.py --selbstsigniert` schaltet die Zertifikatsprüfung des Browsers ab. Regressionsnachweis vom selben Tag: gegen ein TLS-Backend mit einem Mutanten von `sicherheit.py` (ohne `Secure`, ohne HSTS) fielen genau die vier https-Fälle rot, Exit 2; Grundlinie über http 59 Fälle, über https 60. Läuft das Backend auf einem anderen Rechner, fährt man `angriffe.py` dort in einem Wegwerf-Container mit `--network host` und legt die drei Dateien der Ablage (`zugang.json`, `status.json`, `anmeldungen.jsonl`) per Verknüpfung in einen Ordner — das Werkzeug braucht Dateizugriff auf die Ablage des Backends. +## Paket F (2026-09-20): Produktivbetrieb als Dienst + +| Datei | Was sie tut | Aufruf | +|---|---|---| +| `pause_echtlauf.py` | Tagesverlust-Schutz in der Pause auf den echten IEX-Kerzen eines Tages: rechnet aus den Rohkerzen vor, in welchem Takt die Grenze reißen muss (long und short, Grenze bei der Hälfte des größten Tagesverlusts), und hält das gegen das Protokoll der echten `PaperSitzung` im Zustand `angehalten`; dazu die Gegenprobe ohne Position. Einziger Netzzugriff: die Kerzen, lesend; Meldungen gehen an einen mitschreibenden Transport. Gemessen am 2026-09-17: long Takt 6, short Takt 173, je erwartet gleich gemessen | aus dem Projektordner in WSL: `python pause_echtlauf.py JJJJ-MM-TT` | +| `dienste_stand.sh` | nur lesend: Unit-Dateien, `is-enabled`, Zustand, Startzeiten und letzter Exit-Code von Dashboard-Dienst und Runner, Zeitgeber, `/api/gesund`, Start von systemd, die letzten Journalzeilen beider Dienste, `status.json` und `tagesstart.json` ohne Kontowerte. Das Journal geht ohne `sudo`, wenn der Benutzer in `adm` oder `systemd-journal` ist (hier: ja); sonst nimmt das Skript `sudo -S` mit `WSL_PW` aus der Umgebung | aus PowerShell: `wsl -d Debian --exec bash /mnt/d/Claude/.vault-tools/trading-plattform-pruefung/dienste_stand.sh` | +| `hole_kerzen.py` | holt die echten IEX-Minutenkerzen eines Handelstags lesend und legt sie als JSON ab — die Eingabe für `neustart_echtlauf.py`. Gemessen am 2026-09-20 für den 2026-09-17: 390 Kerzen, byte-gleich mit dem Abruf des Abschlussprüfers | aus dem Projektordner in WSL: `python hole_kerzen.py JJJJ-MM-TT ` | +| `neustart_echtlauf.py` | der Tagesverlust nach einem Neustart mitten am Tag: rechnet aus den Rohkerzen vor, bei welcher Kerze die echte Grenze (0,2 Prozent) gegen den Tagesstart reißt und bei welcher gegen den Neustartkurs, und fährt dann zwei echte `PaperSitzung`en nacheinander mit gemeinsamer `tagesstart.json`, dazu die Gegenprobe ohne Datei. Kein Netz. Gemessen am 2026-09-20 auf den Kerzen des 2026-09-17 und 2026-09-18: mit Datei Riss bei der vorgerechneten Kerze (5, 150, 19), ohne erst bei 18, 173, 42 | aus dem Projektordner in WSL: `python neustart_echtlauf.py ` | +| `ntfy_lesen.py` | liest über die Abfrage-Schnittstelle von ntfy zurück, was in der letzten Stunde unter dem Thema der Maschine ankam — Titel, Priorität, Zeit, erste Zeile. **Schickt nichts und gibt das Thema nie aus**, auch nicht in einer Fehlermeldung | aus dem Projektordner in WSL: `python ntfy_lesen.py` | +| `wiederanlauf_probe.ps1` | beendet die Distribution hart (`wsl --terminate`) und misst, wann geplante Aufgabe, Distribution und Dashboard von selbst zurück sind. **Ein laufender Runner stirbt mit — nur außerhalb der Handelszeit, nur mit `-Ausfuehren`** (ohne den Schalter Exit 2, nichts getan). Während der Messung kein anderer `wsl.exe`-Aufruf, jeder Klient weckt die Distribution selbst. Gemessen am 2026-09-20: Rückkehr am nächsten Fünf-Minuten-Rasterpunkt, Dashboard 13 s danach | `powershell -File wiederanlauf_probe.ps1 -Ausfuehren` | + +## Paket H (2026-09-21): Kennzahlen nach Home Assistant + +| Datei | Was sie tut | Aufruf | +|---|---|---| +| `ha_einrichten.py` | richtet in Home Assistant die Gegenstelle für den Hausmelder ein: acht Helfer mit dem Präfix `trading_` und eine Automation mit Webhook-Auslöser (`local_only`, nur POST). Die Webhook-ID entsteht im Skript (64 Hex-Zeichen), wird nie ausgegeben und landet nur in `[homeassistant]` der `config/secrets.toml` des Trading-Projekts, mit `aktiv = false`. `pruefen` und `lesen` lesen nur (`lesen` zeigt zusätzlich die Zustände der Helfer), `probe` schickt eine Beispiel-Nutzlast und liest zurück, `entfernen` baut Helfer und Automation wieder ab. **Läuft unter Windows mit dem Python des Home-Assistant-Projekts** — dort liegen der Klient `src/ha_verbindung.py`, die Adresse der Instanz und der Name der Token-Variable; das Token bleibt auf der Windows-Seite und kommt nie auf den Trading-Rechner. Gemessen am 2026-09-20/21: eingerichtet, `probe` rückgelesen wie gesendet, und ein POST des echten `Hausmelder` aus WSL kam in den Helfern an | `\.venv\Scripts\python.exe -X utf8 ha_einrichten.py "" ""` | + +Das Einrichten selbst (`deploy/dienste-einrichten.sh` unter `sudo -S`, `deploy/windows/wsl-wachhalten.ps1 -Aktion einrichten`) und das Eintragen des ntfy-Themas liefen aus dem Scratchpad der Sitzung; die Befehle stehen in der Administrator-Anleitung des Projekts, Abschnitt „Produktivbetrieb als Dienst". + Die Compose-Probe (Bauen aus dem echten Baum mit verankerten Ausschlüssen, Start, Healthcheck, Angriffsfälle, Runner-Probe im Container, Aufräumen) lag als `compose_probe_echt.sh` im Scratchpad der Sitzung und braucht `sudo -S` mit dem WSL-Passwort aus `$WSL_PW`; sie ist deshalb nicht hier abgelegt. Muster: `tar --exclude=./data` — ohne Anker fällt auch `trading/data` weg. Vor dem Browser-Lauf `npm run build` im Projekt, sonst dient das Backend eine alte Oberfläche aus. Die Datensatz- und Dateinamen in `browserlauf.py` und `pruef_teil3b.py` sind die des Bestands vom 2026-09-17; nach einem neuen Katalog oder neuen Protokollen nachziehen. Die Statustexte in `wiederaufbau.py` sind aus `Kopfzeile.tsx` abgeschrieben. diff --git a/.vault-tools/trading-plattform-pruefung/dienste_stand.sh b/.vault-tools/trading-plattform-pruefung/dienste_stand.sh new file mode 100644 index 0000000..f3e79ad --- /dev/null +++ b/.vault-tools/trading-plattform-pruefung/dienste_stand.sh @@ -0,0 +1,52 @@ +#!/bin/bash +# Nur lesend: Stand der eingerichteten Dienste der Trading Plattform in WSL-Debian. +# Das Journal liest, wer in der Gruppe adm oder systemd-journal ist (hier: claude in adm); +# sonst mit sudo -- dann das Passwort ueber WSLENV als WSL_PW durchreichen, nie ausgeben. +PROJEKT="/mnt/d/Claude/Code Projekte/active - Trading Plattform" +journal() { + if journalctl -u "$1" --no-pager -n 1 >/dev/null 2>&1; then + journalctl -u "$1" --no-pager -n "$2" -o short-iso | cut -c1-200 + elif [ -n "$WSL_PW" ]; then + printf '%s\n' "$WSL_PW" | sudo -S -p '' journalctl -u "$1" --no-pager -n "$2" -o short-iso | cut -c1-200 + else + echo "(Journal nicht lesbar: weder Gruppe adm/systemd-journal noch WSL_PW)" + fi +} +echo "=== $(date -Is) ===" +echo "--- dateien ---" +ls -la /etc/systemd/system/ | grep -i "trading" | awk '{print $1, $5, $9}' +echo "--- enabled ---" +for u in trading-dashboard.service trading-paper.timer trading-paper.service; do + printf '%s: ' "$u"; systemctl is-enabled "$u" 2>&1 +done +echo "--- zustand ---" +systemctl show -p ActiveState -p SubState -p Result -p ExecMainStartTimestamp -p NRestarts trading-dashboard.service | tr '\n' ' '; echo +systemctl show -p ActiveState -p SubState -p Result -p ExecMainStartTimestamp -p ExecMainExitTimestamp -p ExecMainStatus trading-paper.service | tr '\n' ' '; echo +echo "--- zeitgeber ---" +systemctl list-timers trading-paper.timer --all --no-pager | head -4 +echo "--- gesund ---" +curl -s -o /dev/null -w 'dashboard in WSL: HTTP %{http_code}\n' --max-time 4 http://127.0.0.1:8144/api/gesund +echo "--- systemd laeuft seit ---" +systemctl show -p UserspaceTimestamp --value +echo "--- journal runner (letzte 16) ---" +journal trading-paper.service 16 +echo "--- journal dashboard (letzte 8) ---" +journal trading-dashboard.service 8 +echo "--- status.json und tagesstart.json (ohne Kontowerte) ---" +python3 - "$PROJEKT" <<'EOF' +import json +import sys +from pathlib import Path + +ordner = Path(sys.argv[1]) / "data" / "paper" +for name, felder in ( + ("status.json", ("zeit", "phase", "killswitch", "position", "letzter_grund", "pid", "boot")), + ("tagesstart.json", ("handelstag",)), +): + try: + daten = json.loads((ordner / name).read_text(encoding="utf-8")) + except (OSError, ValueError) as ausnahme: + print(f"{name}: nicht lesbar ({type(ausnahme).__name__})") + continue + print(name, {k: daten.get(k) for k in felder}) +EOF diff --git a/.vault-tools/trading-plattform-pruefung/ha_einrichten.py b/.vault-tools/trading-plattform-pruefung/ha_einrichten.py new file mode 100644 index 0000000..65fb974 --- /dev/null +++ b/.vault-tools/trading-plattform-pruefung/ha_einrichten.py @@ -0,0 +1,231 @@ +"""Richtet in Home Assistant die Gegenstelle fuer die Kennzahlen der Trading Plattform ein. + +Koordinator-Werkzeug, nicht Teil des Repos. Laeuft unter WINDOWS mit dem Python des HA-Projekts +(dort liegen `websockets` und `src/ha_verbindung.py`; Adresse der Instanz und der Name der +Token-Variable stehen in jenem Modul, nicht hier). Das HA-Token bleibt auf der Windows-Seite und +wird nie ausgegeben; auf den Trading-Rechner kommt nur die Webhook-Adresse. + +Aktionen: + pruefen nur lesend: Version, vorhandene Helfer mit dem Praefix, vorhandene Automation + lesen nur lesend: wie pruefen, dazu die Zustaende der Helfer, wie sie gerade in Home Assistant stehen + einrichten legt acht Helfer und eine Automation mit Webhook-Ausloeser an (local_only, nur POST), + erzeugt dafuer eine Webhook-ID (64 Hex-Zeichen) und traegt die Adresse in die + config/secrets.toml des Trading-Projekts ein ([homeassistant], aktiv = false). + Gibt die ID nie aus. Bricht ab, wenn es den Abschnitt oder die Automation schon gibt. + probe schickt eine Beispiel-Nutzlast an den Webhook (Adresse aus der Geheimnisdatei) und + liest die Helfer ueber die HA-API zurueck + entfernen loescht Automation und Helfer wieder; der Abschnitt in der Geheimnisdatei bleibt stehen + und wird genannt + +Aufruf: \\.venv\\Scripts\\python.exe ha_einrichten.py +""" + +import json +import os +import secrets +import sys +import time +import tomllib +import urllib.request +from pathlib import Path + +AUTOMATION_ID = "trading_plattform_kennzahlen" +ALIAS = "Trading Plattform: Kennzahlen uebernehmen" +PRAEFIX = "trading_" + +TEXTE = ("Trading Phase", "Trading Modus", "Trading Killswitch") +ZAHLEN = ( + ("Trading Position", -100000, 100000, 1, "Stk"), + ("Trading Tagesergebnis USD", -1000000, 1000000, 0.01, "USD"), + ("Trading Herzschlag Alter s", -1, 100000000, 1, "s"), +) +SCHALTER = ("Trading Veraltet",) +ZEITEN = ("Trading Gesendet",) + + +def aktionen() -> list[dict]: + def text(ziel: str, schluessel: str, vorgabe: str) -> dict: + return { + "service": "input_text.set_value", + "target": {"entity_id": f"input_text.{ziel}"}, + "data": {"value": "{{ (trigger.json." + schluessel + " | default('" + vorgabe + "') | string)[:100] }}"}, + "continue_on_error": True, + } + + def zahl(ziel: str, schluessel: str, filter_: str) -> dict: + return { + "service": "input_number.set_value", + "target": {"entity_id": f"input_number.{ziel}"}, + "data": {"value": "{{ trigger.json." + schluessel + " | default(0) | " + filter_ + " }}"}, + "continue_on_error": True, + } + + return [ + text("trading_phase", "phase", "unbekannt"), + text("trading_modus", "modus", ""), + text("trading_killswitch", "killswitch", "unbekannt"), + zahl("trading_position", "position", "int(0)"), + zahl("trading_tagesergebnis_usd", "tagesergebnis_usd", "float(0) | round(2)"), + zahl("trading_herzschlag_alter_s", "herzschlag_alter_s", "int(-1)"), + { + "service": "input_boolean.turn_{{ 'on' if (trigger.json.veraltet | default(false)) else 'off' }}", + "target": {"entity_id": "input_boolean.trading_veraltet"}, + "continue_on_error": True, + }, + { + "service": "input_datetime.set_datetime", + "target": {"entity_id": "input_datetime.trading_gesendet"}, + "data": {"timestamp": "{{ now().timestamp() }}"}, + "continue_on_error": True, + }, + ] + + +def lies_abschnitt(datei: Path) -> dict | None: + return tomllib.loads(datei.read_text(encoding="utf-8")).get("homeassistant") + + +def trage_ein(datei: Path, adresse: str) -> None: + roh = datei.read_text(encoding="utf-8") + vorher = tomllib.loads(roh) + block = ( + "\n[homeassistant]\n" + "# Webhook in Home Assistant fuer die Kennzahlen des Paper-Runners. Die ID in der Adresse ist das\n" + "# Geheimnis: Wer sie kennt, kann die Anzeige in HA faelschen. Eingeschaltet wird mit aktiv = true.\n" + f'webhook_url = "{adresse}"\n' + "aktiv = false\n" + ) + neu = roh.rstrip("\n") + "\n" + block + nachher = tomllib.loads(neu) + if {k: v for k, v in nachher.items() if k != "homeassistant"} != vorher: + raise SystemExit("ABBRUCH: bestehende Abschnitte haetten sich geaendert") + nachbar = datei.with_name(datei.name + ".neu") + with open(nachbar, "w", encoding="utf-8", newline="\n") as strom: + strom.write(neu) + os.replace(nachbar, datei) + + +def helfer_liste(ha) -> dict[str, list[dict]]: + ergebnis = {} + for art in ("input_text", "input_number", "input_boolean", "input_datetime"): + eintraege = ha.befehl_ok(f"{art}/list") or [] + ergebnis[art] = [e for e in eintraege if str(e.get("id", "")).startswith(PRAEFIX)] + return ergebnis + + +def main() -> int: + aktion, ha_projekt, trading = sys.argv[1], Path(sys.argv[2]), Path(sys.argv[3]) + sys.path.insert(0, str(ha_projekt / "src")) + import ha_verbindung as hv + + geheimnisse = trading / "config" / "secrets.toml" + token = hv.lies_token() + print(f"Token gelesen (Laenge {len(token)}), Instanz aus dem HA-Projekt, Aktion {aktion}") + + # nur_lesen=False auch fuers Pruefen: Die Leseliste jenes Moduls kennt die Listenbefehle der + # Helfer und get_states nicht. 'pruefen' und 'probe' senden trotzdem nur lesende Befehle. + with hv.HaVerbindung(hv.BASIS_URL, token, nur_lesen=False) as ha: + vorhanden = helfer_liste(ha) + zahl_helfer = sum(len(v) for v in vorhanden.values()) + automationen = [ + s for s in (ha.befehl_ok("get_states") or []) + if s.get("entity_id", "").startswith("automation.") and s.get("attributes", {}).get("id") == AUTOMATION_ID + ] + abschnitt = lies_abschnitt(geheimnisse) + print(f"Helfer mit Praefix {PRAEFIX}: {zahl_helfer} -- " + ", ".join(f"{a}: {[e['id'] for e in v]}" for a, v in vorhanden.items() if v)) + print(f"Automation {AUTOMATION_ID}: {'vorhanden' if automationen else 'fehlt'}") + print("[homeassistant] in der Geheimnisdatei: " + ("fehlt" if abschnitt is None else str({k: (type(v).__name__, len(v) if isinstance(v, str) else v) for k, v in abschnitt.items()}))) + + if aktion == "pruefen": + return 0 + + if aktion == "lesen": + # Nur lesen: die Zustaende der Helfer, wie sie gerade in Home Assistant stehen. + zustaende = {s["entity_id"]: s["state"] for s in (ha.befehl_ok("get_states") or []) if f".{PRAEFIX}" in s.get("entity_id", "")} + for name in sorted(zustaende): + print(f" {name} = {zustaende[name]}") + return 0 + + if aktion == "einrichten": + if abschnitt is not None or automationen or zahl_helfer: + print("ABBRUCH: Es gibt schon Teile der Einrichtung; erst 'entfernen' oder von Hand klaeren.") + return 3 + for name in TEXTE: + ha.befehl_ok("input_text/create", name=name, min=0, max=100, mode="text", icon="mdi:chart-line") + for name, kleinst, groesst, schritt, einheit in ZAHLEN: + ha.befehl_ok("input_number/create", name=name, min=kleinst, max=groesst, step=schritt, mode="box", unit_of_measurement=einheit, icon="mdi:chart-line") + for name in SCHALTER: + ha.befehl_ok("input_boolean/create", name=name, icon="mdi:alert-outline") + for name in ZEITEN: + ha.befehl_ok("input_datetime/create", name=name, has_date=True, has_time=True, icon="mdi:clock-outline") + webhook_id = secrets.token_hex(32) + status, antwort = ha.http_post( + f"/api/config/automation/config/{AUTOMATION_ID}", + { + "alias": ALIAS, + "description": "Uebernimmt Phase, Killswitch, Position und Tagesergebnis des Paper-Runners aus dem Webhook der Trading Plattform. Angelegt am 2026-09-20 ueber ha_einrichten.py; Rueckbau mit der Aktion entfernen.", + "trigger": [{"platform": "webhook", "webhook_id": webhook_id, "allowed_methods": ["POST"], "local_only": True}], + "condition": [], + "action": aktionen(), + "mode": "queued", + "max": 5, + }, + ) + print(f"Automation angelegt: HTTP {status}") + if status != 200: + print("ABBRUCH: Die Automation wurde nicht angenommen:", str(antwort)[:200].replace(webhook_id, "***")) + return 2 + trage_ein(geheimnisse, f"{hv.BASIS_URL}/api/webhook/{webhook_id}") + print("Adresse eingetragen ([homeassistant], aktiv = false); die ID wurde nicht ausgegeben.") + return 0 + + if aktion == "probe": + if abschnitt is None: + print("ABBRUCH: kein [homeassistant] in der Geheimnisdatei") + return 3 + nutzlast = { + "phase": "probe_der_einrichtung", "modus": "probe", "killswitch": "angehalten", "position": 7, + "tagesergebnis_usd": -12.34, "herzschlag_alter_s": 5, "veraltet": False, + "gesendet": time.strftime("%Y-%m-%dT%H:%M:%S"), + } + anfrage = urllib.request.Request( + abschnitt["webhook_url"], data=json.dumps(nutzlast).encode("utf-8"), + headers={"Content-Type": "application/json"}, method="POST", + ) + try: + with urllib.request.urlopen(anfrage, timeout=5) as antwort: + print(f"Webhook: HTTP {antwort.status}") + except Exception as ausnahme: + # Nur der Typ: Die Ausnahme traegt die Adresse und damit die ID. + print(f"Webhook GESCHEITERT: {type(ausnahme).__name__}") + return 2 + time.sleep(2.0) + zustaende = {s["entity_id"]: s["state"] for s in (ha.befehl_ok("get_states") or []) if f".{PRAEFIX}" in s.get("entity_id", "")} + for name in sorted(zustaende): + print(f" {name} = {zustaende[name]}") + erwartet = { + "input_text.trading_phase": "probe_der_einrichtung", "input_text.trading_killswitch": "angehalten", + "input_number.trading_position": "7.0", "input_number.trading_tagesergebnis_usd": "-12.34", + "input_boolean.trading_veraltet": "off", + } + falsch = {k: (v, zustaende.get(k)) for k, v in erwartet.items() if zustaende.get(k) != v} + print("Rueckgelesen wie gesendet." if not falsch else f"BEFUND: {falsch}") + return 0 if not falsch else 1 + + if aktion == "entfernen": + status = ha.http_delete(f"/api/config/automation/config/{AUTOMATION_ID}") + print(f"Automation geloescht: HTTP {status}") + for art, eintraege in vorhanden.items(): + for eintrag in eintraege: + ha.befehl_ok(f"{art}/delete", **{f"{art}_id": eintrag["id"]}) + print(f" geloescht: {art}.{eintrag['id']}") + if abschnitt is not None: + print("Hinweis: [homeassistant] steht weiter in der Geheimnisdatei des Trading-Projekts; von Hand entfernen.") + return 0 + + print("unbekannte Aktion") + return 2 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.vault-tools/trading-plattform-pruefung/hole_kerzen.py b/.vault-tools/trading-plattform-pruefung/hole_kerzen.py new file mode 100644 index 0000000..0895a9c --- /dev/null +++ b/.vault-tools/trading-plattform-pruefung/hole_kerzen.py @@ -0,0 +1,48 @@ +"""Holt die echten IEX-Minutenkerzen eines Handelstags und legt sie als JSON ab. Nur lesend. + +Koordinator-Werkzeug, nicht Teil des Repos. Der einzige Netzzugriff ist der lesende Kerzenabruf +bei Alpaca; ausgegeben werden Anzahl und Eckkurse, nie ein Schluessel. Die Datei ist die Eingabe +fuer neustart_echtlauf.py. Entstanden in der Abschlusspruefung zu Paket F (2026-09-20). +Aufruf (WSL, aus dem Projektordner): python hole_kerzen.py JJJJ-MM-TT +""" + +import json +import sys +from datetime import datetime +from pathlib import Path + +sys.path.insert(0, str(Path.cwd())) + +from trading.paper.config import lade_paper_config +from trading.paper.kerzen import HANDELSZONE +from trading.paper.kerzen import AlpacaKerzenquelle +from trading.paper.kerzen import handelsende + + +def main() -> int: + datum, ziel = sys.argv[1], Path(sys.argv[2]) + config = lade_paper_config("config/paper.toml") + mitternacht = datetime.strptime(datum, "%Y-%m-%d").replace(tzinfo=HANDELSZONE) + quelle = AlpacaKerzenquelle.from_secrets("config/secrets.toml", feed=config.feed, timeout=20.0) + kerzen = quelle.kerzen_des_tages(config.instrument_id, handelsende(mitternacht)) + ziel.write_text( + json.dumps( + [ + {"zeit": k.zeit.isoformat(), "open": k.open, "high": k.high, + "low": k.low, "close": k.close, "volume": k.volume} + for k in kerzen + ] + ), + encoding="utf-8", + ) + print(f"{datum}: {len(kerzen)} Kerzen in {config.instrument_id} nach {ziel}") + if kerzen: + kurse = [k.close for k in kerzen] + print(f" erste {kerzen[0].zeit.isoformat()} Schluss {kurse[0]}, " + f"letzte {kerzen[-1].zeit.isoformat()} Schluss {kurse[-1]}, " + f"Tief {min(kurse)}, Hoch {max(kurse)}") + return 0 if kerzen else 2 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.vault-tools/trading-plattform-pruefung/neustart_echtlauf.py b/.vault-tools/trading-plattform-pruefung/neustart_echtlauf.py new file mode 100644 index 0000000..7821b5f --- /dev/null +++ b/.vault-tools/trading-plattform-pruefung/neustart_echtlauf.py @@ -0,0 +1,197 @@ +"""Eigener Lauf mit echten Daten: Der Tagesverlust zaehlt nach einem Neustart weiter ab dem Tagesstart. + +Koordinator-Pruefcode, nicht Teil des Repos. Echte IEX-Kerzen eines Tages (JSON aus dem lesenden +Abruf der Abschlusspruefung). Aus den Rohkerzen wird vorgerechnet, bei welcher Kerze die Grenze von +0,2 Prozent gegen den Tagesstart reisst und bei welcher gegen den Neustartkurs; dann laufen zwei +echte PaperSitzungen nacheinander (vor und nach dem Neustart) mit gemeinsamer Tagesstart-Datei, +dazu die Gegenprobe ohne Datei. Kein Netz: Meldungen gehen an einen mitschreibenden Transport. +Aufruf (WSL, aus dem Projektordner): python neustart_echtlauf.py +""" + +import json +import sys +import tempfile +from datetime import datetime +from datetime import timedelta +from pathlib import Path + +sys.path.insert(0, str(Path.cwd())) + +from trading.killswitch.killswitch import lade_killswitch_config +from trading.paper.config import enge_limits +from trading.paper.config import lade_paper_config +from trading.paper.kerzen import FesteKerzenquelle +from trading.paper.kerzen import Kerze +from trading.paper.melder import Melder +from trading.paper.melder import MelderConfig +from trading.paper.probe import ProbeBroker +from trading.paper.sitzung import PaperSitzung +from trading.paper.status import lies_tagesstart +from trading.paper.uhr import ProbeUhr +from trading.registry.registry import Registry +from trading.risk.limits import lade_limits + +STUECK = 20 +ZIEL_KAPITAL = 12_676.20 # so klein, dass 20 Stueck die echte Grenze von 0,2 Prozent erreichen + + +class Protokoll: + def __init__(self): + self.zeilen = [] + + def schreibe(self, eintrag): + self.zeilen.append(dict(eintrag)) + + +class Antwort: + status_code = 200 + + +class Transport: + def __init__(self): + self.aufrufe = [] + + def __call__(self, url, *, data=None, headers=None, timeout=None): + self.aufrufe.append(headers["Title"]) + return Antwort() + + +class Flach: + def predict(self, beobachtung, deterministic=False): + return 2, None + + +def lade(pfad): + roh = json.loads(Path(pfad).read_text(encoding="utf-8")) + return [ + Kerze(zeit=datetime.fromisoformat(e["zeit"]), open=e["open"], high=e["high"], + low=e["low"], close=e["close"], volume=e["volume"]) + for e in roh + ] + + +def lauf(ordner, kerzen, *, beginn, position, cash, tagesstart, config, limits, ks_config): + ordner.mkdir(parents=True, exist_ok=True) + quelle = FesteKerzenquelle(kerzen) + uhr = ProbeUhr(beginn) + broker = ProbeBroker(kerzenquelle=quelle, uhr=uhr, instrument_id=config.instrument_id, + startkapital=cash, position=position) + registry = Registry(db_pfad=ordner / "registry.db", artefakt_wurzel=ordner / "artifacts") + registry.setze_killswitch_zustand("angehalten", ausloeser="neustart") + protokoll = Protokoll() + transport = Transport() + sitzung = PaperSitzung( + broker=broker, kerzenquelle=quelle, agent=Flach(), agent_beschreibung={"quelle": "echtlauf"}, + registry=registry, limits=limits, killswitch_config=ks_config, config=config, uhr=uhr, + protokoll=protokoll, protokoll_pfad="echtlauf.jsonl", status_pfad=ordner / "status.json", + modus="probe", soll_abbrechen=lambda: False, + melder=Melder(config=MelderConfig(aktiv=True, ntfy_url="https://ntfy.sh", + thema="echtlauf-geht-nie-ins-netz", zeitlimit_s=3.0, + stille_nach_s=180.0), token="", transport=transport), + tagesstart_pfad=tagesstart, + ) + code = sitzung.laufe() + return code, protokoll.zeilen, transport.aufrufe, broker, registry + + +def kerze_beim_grenzereignis(zeilen, index_nach_zeit): + """Index der juengsten Kerze in dem Takt, in dem die Zeile grenzwert steht, oder None.""" + for stelle, zeile in enumerate(zeilen): + if zeile.get("was") == "grenzwert": + takt = next(z for z in zeilen[stelle:] if z.get("art") == "takt") + return index_nach_zeit[takt["letzte_kerze"]], takt["schritt"] + return None, None + + +def erster_riss(schluesse, *, ab, cash, position, bezug, anteil): + for c in range(ab, len(schluesse)): + if (cash + position * schluesse[c] - bezug) / bezug < -anteil: + return c + return None + + +def main(): + kerzen = lade(sys.argv[1]) + schluesse = [k.close for k in kerzen] + index_nach_zeit = {k.zeit.isoformat(): i for i, k in enumerate(kerzen)} + config = lade_paper_config("config/paper.toml") + limits = enge_limits(lade_limits("config/risk.toml"), config) + ks_config = lade_killswitch_config("config/killswitch.toml") + anteil = limits.max_tagesverlust_anteil + tag = kerzen[0].zeit.date().isoformat() + print(f"{tag}: {len(kerzen)} Kerzen, Schluss Kerze 0 {schluesse[0]:.2f}, " + f"Tief {min(schluesse):.2f}, Hoch {max(schluesse):.2f}, Grenze {anteil}") + + befunde = 0 + for seite, position in (("long", STUECK), ("short", -STUECK)): + cash = ZIEL_KAPITAL - position * schluesse[0] + tagesstart_wert = cash + position * schluesse[0] + riss_tag = erster_riss(schluesse, ab=0, cash=cash, position=position, + bezug=tagesstart_wert, anteil=anteil) + if riss_tag is None or riss_tag < 3: + print(f"[{seite}] der Tag reisst die Grenze gegen den Tagesstart nicht (oder sofort): uebersprungen") + continue + # Neustart so, dass die Sitzung davor die Kerze des Risses nicht mehr sieht: Sie laeuft + # ueber kerzen[:r] und sieht die Kerzen 0 bis r-2. Die Sitzung danach beginnt in der + # Minute der Kerze r und sieht zuerst die Kerze r-1. + r = riss_tag + bezug_neu = cash + position * schluesse[r - 1] + riss_neu = erster_riss(schluesse, ab=r - 1, cash=cash, position=position, + bezug=bezug_neu, anteil=anteil) + print(f"[{seite}] Tagesstart {tagesstart_wert:.2f}; vorgerechnet: Riss gegen den Tagesstart " + f"bei Kerze {riss_tag}, gegen den Neustartkurs ({bezug_neu:.2f}) bei Kerze {riss_neu}") + + with tempfile.TemporaryDirectory(prefix="neustart-echtlauf-") as wurzel: + wurzel = Path(wurzel) + datei = wurzel / "paper" / "tagesstart.json" + gemeinsam = dict(position=position, cash=cash, config=config, limits=limits, + ks_config=ks_config) + + code1, zeilen1, _, _, reg1 = lauf( + wurzel / "vorher", kerzen[:r], beginn=kerzen[0].zeit - timedelta(minutes=1), + tagesstart=datei, **gemeinsam) + riss1, _ = kerze_beim_grenzereignis(zeilen1, index_nach_zeit) + gemerkt = lies_tagesstart(datei, handelstag=tag) + print(f"[{seite}] vor dem Neustart: Exit {code1}, Killswitch " + f"{reg1.killswitch_zustand().zustand}, Riss {riss1}, gemerkt {gemerkt}") + + code2, zeilen2, titel2, broker2, reg2 = lauf( + wurzel / "nachher", kerzen, beginn=kerzen[r].zeit - timedelta(minutes=1), + tagesstart=datei, **gemeinsam) + riss2, schritt2 = kerze_beim_grenzereignis(zeilen2, index_nach_zeit) + takte2 = [z for z in zeilen2 if z.get("art") == "takt"] + # Vor dem Ende des with-Blocks lesen: Danach ist das Wegwerf-Register weg. + zustand2 = reg2.killswitch_zustand().zustand + position2 = sum(p.menge for p in broker2.positionen()) + print(f"[{seite}] nach dem Neustart mit Datei: Startkapital {takte2[0]['startkapital']}, " + f"Riss bei Kerze {riss2} (Schritt {schritt2}), Exit {code2}, Killswitch " + f"{zustand2}, Position {position2}, Meldungen {titel2}") + + code3, zeilen3, _, broker3, reg3 = lauf( + wurzel / "ohne", kerzen, beginn=kerzen[r].zeit - timedelta(minutes=1), + tagesstart=None, **gemeinsam) + riss3, _ = kerze_beim_grenzereignis(zeilen3, index_nach_zeit) + takte3 = [z for z in zeilen3 if z.get("art") == "takt"] + print(f"[{seite}] nach dem Neustart ohne Datei: Startkapital {takte3[0]['startkapital']}, " + f"Riss bei Kerze {riss3}, Exit {code3}, Killswitch {reg3.killswitch_zustand().zustand}") + + ok = ( + riss1 is None and code1 == 4 + and gemerkt is not None and abs(gemerkt - tagesstart_wert) < 1e-9 + and takte2[0]["startkapital"] == gemerkt + and riss2 == riss_tag + and zustand2 == "gesperrt" + and position2 == 0 + and titel2 == ["Paper-Runner wartet", "Grenze verletzt", "Paper-Runner beendet"] + and abs(takte3[0]["startkapital"] - bezug_neu) < 1e-9 + and riss3 == riss_neu + ) + print(f"[{seite}] {'OK' if ok else 'BEFUND'}") + befunde += 0 if ok else 1 + + print(f"{befunde} Befund(e).") + return 2 if befunde else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.vault-tools/trading-plattform-pruefung/ntfy_lesen.py b/.vault-tools/trading-plattform-pruefung/ntfy_lesen.py new file mode 100644 index 0000000..c3553ac --- /dev/null +++ b/.vault-tools/trading-plattform-pruefung/ntfy_lesen.py @@ -0,0 +1,52 @@ +"""Liest nur zurueck, was in der letzten Stunde bei ntfy unter dem Thema der Maschine ankam. + +Koordinator-Skript, nicht Teil des Repos. Schickt nichts. Das Thema wird nie ausgegeben -- weder +hier noch in einer Fehlermeldung. Aufruf aus dem Projektordner (WSL): python ntfy_lesen.py +""" + +import json +import sys +from datetime import datetime +from pathlib import Path + +sys.path.insert(0, str(Path.cwd())) + +import requests + +from trading.paper.melder import lade_melder_config +from trading.paper.melder import lies_ntfy_geheimnisse + + +def main() -> int: + config = lade_melder_config("config/paper.toml", secrets_pfad="config/secrets.toml") + thema, token = lies_ntfy_geheimnisse("config/secrets.toml") + kopf = {"Authorization": f"Bearer {token}"} if token else {} + try: + antwort = requests.get( + f"{config.ntfy_url}/{thema}/json", params={"poll": "1", "since": "1h"}, + headers=kopf, timeout=10, allow_redirects=False, + ) + except Exception as ausnahme: + # Nur der Typ: Die Ausnahme von requests traegt die Adresse und damit das Thema. + print(f"Ruecklesen gescheitert: {type(ausnahme).__name__}") + return 1 + print(f"Ruecklesen: HTTP {antwort.status_code}") + anzahl = 0 + for zeile in antwort.text.splitlines(): + try: + eintrag = json.loads(zeile) + except ValueError: + continue + if eintrag.get("event") != "message": + continue + anzahl += 1 + # Ohne Angabe fuehrt ntfy die Prioritaet 3 (default) und laesst das Feld weg. + print(f" bei ntfy: Titel '{eintrag.get('title')}', Prioritaet {eintrag.get('priority', 3)}, " + f"Zeit {datetime.fromtimestamp(eintrag.get('time', 0)).isoformat()}, " + f"erste Zeile '{str(eintrag.get('message', '')).splitlines()[0][:70]}'") + print(f"{anzahl} Meldung(en) in der letzten Stunde.") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.vault-tools/trading-plattform-pruefung/pause_echtlauf.py b/.vault-tools/trading-plattform-pruefung/pause_echtlauf.py new file mode 100644 index 0000000..9a22cd7 --- /dev/null +++ b/.vault-tools/trading-plattform-pruefung/pause_echtlauf.py @@ -0,0 +1,184 @@ +"""Eigener Lauf mit echten Daten: Tagesverlust-Schutz in der Pause auf den IEX-Kerzen eines echten Tages. + +Koordinator-Pruefcode, nicht Teil des Repos. Er rechnet aus den Rohkerzen vor, in welchem Takt die +Grenze reissen muss, und haelt das gegen das Protokoll der echten PaperSitzung. Der einzige +Netzzugriff holt lesend die Kerzen; Meldungen gehen an einen mitschreibenden Transport, nie ins Netz. +Aufruf (aus dem Projektordner, in WSL): python pause_echtlauf.py JJJJ-MM-TT +""" + +import sys +import tempfile +from datetime import datetime +from datetime import timedelta +from pathlib import Path + +sys.path.insert(0, str(Path.cwd())) + +from trading.killswitch.killswitch import lade_killswitch_config +from trading.paper.config import enge_limits +from trading.paper.config import lade_paper_config +from trading.paper.kerzen import HANDELSZONE +from trading.paper.kerzen import AlpacaKerzenquelle +from trading.paper.kerzen import FesteKerzenquelle +from trading.paper.kerzen import handelsende +from trading.paper.melder import Melder +from trading.paper.melder import MelderConfig +from trading.paper.probe import ProbeBroker +from trading.paper.sitzung import PaperSitzung +from trading.paper.uhr import ProbeUhr +from trading.registry.registry import Registry +from trading.risk.limits import lade_limits +from dataclasses import replace + + +class Protokoll: + def __init__(self): + self.zeilen = [] + + def schreibe(self, eintrag): + self.zeilen.append(dict(eintrag)) + + +class Antwort: + status_code = 200 + + +class Transport: + def __init__(self): + self.aufrufe = [] + + def __call__(self, url, *, data=None, headers=None, timeout=None): + self.aufrufe.append((headers["Title"], headers["Priority"], data.decode("utf-8"))) + return Antwort() + + +class Flach: + def predict(self, beobachtung, deterministic=False): + return 2, None + + +def lauf(kerzen, *, config, limits, killswitch_config, zustand, position): + ordner = Path(tempfile.mkdtemp(prefix="pause-echtlauf-")) + quelle = FesteKerzenquelle(kerzen) + uhr = ProbeUhr(kerzen[0].zeit - timedelta(minutes=1)) + broker = ProbeBroker( + kerzenquelle=quelle, uhr=uhr, instrument_id=config.instrument_id, position=position + ) + registry = Registry(db_pfad=ordner / "registry.db", artefakt_wurzel=ordner / "artifacts") + registry.setze_killswitch_zustand(zustand, ausloeser="echtlauf") + protokoll = Protokoll() + transport = Transport() + melder = Melder( + config=MelderConfig( + aktiv=True, + ntfy_url="https://ntfy.sh", + thema="echtlauf-geht-nie-ins-netz", + zeitlimit_s=3.0, + stille_nach_s=180.0, + ), + token="", + transport=transport, + ) + sitzung = PaperSitzung( + broker=broker, + kerzenquelle=quelle, + agent=Flach(), + agent_beschreibung={"quelle": "echtlauf"}, + registry=registry, + limits=limits, + killswitch_config=killswitch_config, + config=config, + uhr=uhr, + protokoll=protokoll, + protokoll_pfad="echtlauf.jsonl", + status_pfad=ordner / "status.json", + modus="probe", + soll_abbrechen=lambda: False, + melder=melder, + ) + code = sitzung.laufe() + return code, protokoll.zeilen, transport.aufrufe, broker, registry + + +def main(): + datum = sys.argv[1] + mitternacht = datetime.strptime(datum, "%Y-%m-%d").replace(tzinfo=HANDELSZONE) + config = lade_paper_config("config/paper.toml") + killswitch_config = lade_killswitch_config("config/killswitch.toml") + quelle = AlpacaKerzenquelle.from_secrets("config/secrets.toml", feed=config.feed, timeout=10.0) + kerzen = list(quelle.kerzen_des_tages(config.instrument_id, handelsende(mitternacht))) + print(f"{datum}: {len(kerzen)} IEX-Kerzen in {config.instrument_id}") + schluesse = [k.close for k in kerzen] + print(f"Schluss erste Kerze {schluesse[0]:.2f}, Tief {min(schluesse):.2f}, Hoch {max(schluesse):.2f}") + + stueck = 20 + cash = 100_000.0 + befunde = 0 + for seite, position in (("long", stueck), ("short", -stueck)): + # Eigene Rechnung aus den Rohkerzen: Takt k (schritt k) sieht die Kerze k-1 als juengste + # abgeschlossene. Startkapital ist der Kontowert im ersten Takt. + start = cash + position * schluesse[0] + schlechtester = min(cash + position * s for s in schluesse) - start + if schlechtester >= 0: + print(f"[{seite}] der Tag bringt dieser Seite keinen Verlust gegen den ersten Takt; uebersprungen") + continue + # Die Grenze liegt bei der Haelfte des groessten Verlusts dieses Tages: Der Tag reisst sie + # wirklich, und nicht im ersten Takt. + anteil = abs(schlechtester) / start / 2.0 + erwartet = None + for k, s in enumerate(schluesse, start=1): + if (cash + position * s - start) / start < -anteil: + erwartet = k + break + limits = replace(enge_limits(lade_limits("config/risk.toml"), config), max_tagesverlust_anteil=anteil) + code, zeilen, meldungen, broker, registry = lauf( + kerzen, config=config, limits=limits, killswitch_config=killswitch_config, + zustand="angehalten", position=position, + ) + takte = [z for z in zeilen if z.get("art") == "takt"] + grenz = [z for z in zeilen if z.get("was") == "grenzwert"] + # Der Takt, in dem die Zeile grenzwert steht: die Taktzeile danach traegt seinen Schritt. + stelle = zeilen.index(grenz[0]) if grenz else None + schritt = next((z["schritt"] for z in zeilen[stelle:] if z.get("art") == "takt"), None) if grenz else None + titel = [m[0] for m in meldungen] + print(f"[{seite}] Startkapital {start:.2f}, Grenze {anteil:.6f} (= {anteil * start:.2f} USD), " + f"erwarteter Takt {erwartet}, gemessen {schritt}") + print(f"[{seite}] Zustand {registry.killswitch_zustand().zustand}, Position danach " + f"{sum(p.menge for p in broker.positionen())}, Exit {code}, Meldungen {titel}") + print(f"[{seite}] schliesse_alle: {broker.aufrufe.count('schliesse_alle')}, Takte {len(takte)}, " + f"grenzwert-Zeilen {len(grenz)}") + ok = ( + schritt == erwartet + and registry.killswitch_zustand().zustand == "gesperrt" + and broker.aufrufe.count("schliesse_alle") == 1 + and len(grenz) == 1 + and titel == ["Paper-Runner wartet", "Grenze verletzt", "Paper-Runner beendet"] + and code == 0 + ) + print(f"[{seite}] {'OK' if ok else 'BEFUND'}") + befunde += 0 if ok else 1 + + # Gegenprobe: dieselben Kerzen, angehalten, ohne Position -- genau eine Kontoabfrage, nichts geschlossen. + limits = enge_limits(lade_limits("config/risk.toml"), config) + code, zeilen, meldungen, broker, registry = lauf( + kerzen, config=config, limits=limits, killswitch_config=killswitch_config, + zustand="angehalten", position=0, + ) + titel = [m[0] for m in meldungen] + ok = ( + broker.aufrufe.count("konto") == 1 + and "schliesse_alle" not in broker.aufrufe + and titel == ["Paper-Runner wartet", "Paper-Runner beendet"] + and code == 0 + ) + print(f"[ohne Position] konto {broker.aufrufe.count('konto')}, Meldungen {titel}, Exit {code}: " + f"{'OK' if ok else 'BEFUND'}") + befunde += 0 if ok else 1 + + # Gegenprobe 2: aktiv mit dem echten Haltemuster aendert sich nicht -- das prueft die Probe des Skripts. + print(f"{befunde} Befund(e).") + return 2 if befunde else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.vault-tools/trading-plattform-pruefung/wiederanlauf_probe.ps1 b/.vault-tools/trading-plattform-pruefung/wiederanlauf_probe.ps1 new file mode 100644 index 0000000..29c1d4d --- /dev/null +++ b/.vault-tools/trading-plattform-pruefung/wiederanlauf_probe.ps1 @@ -0,0 +1,53 @@ +# Wiederanlauf-Probe: beendet die Distribution HART (wsl --terminate) und misst, wann Wachhalter, +# Distribution und Dashboard von selbst zurueck sind. Ein laufender Paper-Runner stirbt dabei mit -- +# deshalb nur ausserhalb der Handelszeit (15:30 bis 22:00 Uhr Berliner Zeit) und nur mit -Ausfuehren. +# Waehrend der Messung keinen anderen wsl.exe-Aufruf absetzen: Jeder Klient weckt die Distribution +# selbst und verfaelscht das Ergebnis. Die Datei ist reines ASCII (PowerShell 5.1 liest ohne BOM ANSI). +# Gemessen am 2026-09-20: terminate 19:30:17, Aufgabe und Distribution Running um 19:35:06, HTTP 200 um 19:35:13. +param( + [string]$Name = "Trading Plattform - WSL wachhalten", + [int]$MaxSekunden = 480, + [switch]$Ausfuehren +) + +if (-not $Ausfuehren) { + "Nichts getan. Diese Probe beendet die WSL-Distribution hart; ein laufender Runner stirbt mit." + "Aufruf ausserhalb der Handelszeit: powershell -File wiederanlauf_probe.ps1 -Ausfuehren" + exit 2 +} + +$wsl = "$env:SystemRoot\System32\wsl.exe" + +function Distro { + $roh = ((& $wsl -l -v) | Out-String).Replace([string][char]0, '') + foreach ($zeile in ($roh -split "\r?\n")) { + if ($zeile -match 'Debian\s+(\w+)') { return $Matches[1] } + } + return "unbekannt" +} + +function Probe { + try { + $r = Invoke-WebRequest -UseBasicParsing -TimeoutSec 3 http://127.0.0.1:8144/api/gesund + return "HTTP " + $r.StatusCode + } catch { + return "tot" + } +} + +"Vorher: Aufgabe " + (Get-ScheduledTask -TaskName $Name).State + ", Distro " + (Distro) + ", Dashboard " + (Probe) +& $wsl --terminate Debian | Out-Null +$start = Get-Date +"terminate um " + $start.ToString("HH:mm:ss") +$letzter = "" +while (((Get-Date) - $start).TotalSeconds -lt $MaxSekunden) { + $a = (Get-ScheduledTask -TaskName $Name).State + $d = Distro + $p = Probe + $s = [int]((Get-Date) - $start).TotalSeconds + $jetzt = "Aufgabe $a / Distro $d / Dashboard $p" + if ($jetzt -ne $letzter) { "+${s}s (" + (Get-Date).ToString("HH:mm:ss") + "): $jetzt"; $letzter = $jetzt } + if ($s -gt 15 -and $a -eq "Running" -and $d -eq "Running" -and $p -eq "HTTP 200") { break } + Start-Sleep -Seconds 5 +} +"Ende der Messung um " + (Get-Date).ToString("HH:mm:ss") diff --git a/AI/Regelwerk/codeworkflow.md b/AI/Regelwerk/codeworkflow.md index 12a2dad..f2e3d2c 100644 --- a/AI/Regelwerk/codeworkflow.md +++ b/AI/Regelwerk/codeworkflow.md @@ -5,7 +5,7 @@ title: Codeworkflow summary: Arbeitsweise für Code-Projekte — Standardschleife, Planaufteilung je Task, Subagenten und ihre Modellstufen, Gegenprüfung, Lauf mit echten Daten, Aufgabenmanagement. tags: [regelwerk, codeworkflow, code-projekte] created: 2026-08-03 -updated: 2026-09-18 +updated: 2026-09-20 --- # Codeworkflow @@ -25,10 +25,12 @@ Dateikopf, CHANGELOG-Regel und Codedoku stehen seit 2026-08-30 in [[AI/Regelwerk └──── Befund ─────┘ ``` -**1 Klären.** Offene Entscheidungen vor dem Plan stellen, nicht mittendrin. Nur Fragen, deren Antwort die Arbeit verändert — alles andere selbst entscheiden und die Annahme benennen. +**1 Klären.** Offene Entscheidungen vor dem Plan stellen, nicht mittendrin. Nur Fragen, deren Antwort die Arbeit verändert — alles andere selbst entscheiden und die Annahme benennen. Ausnahme: Soll ein Ablauf übernommen werden, den nur Pascal kennt (ein wiederkehrender Job, ein neues Vorhaben ohne Vorlage im Code), gilt die Interviewform aus [[AI/Regelwerk/wiederkehrende-jobs|wiederkehrende-jobs.md]], Abschnitt 1 — eine Frage je Nachricht, mindestens acht, und keine Datei vor Pascals „fertig“. **2 Planen.** Detailplan unter `docs/plans/JJJJ-MM-TT-/` mit echtem Test- und Implementierungscode, exakten Zieldateipfaden und Verifikationsbefehlen. Keine Platzhalter, kein „analog zu Task N". Der Executer sieht nur den Plan, nicht dieses Gespräch. +**Die Abnahme im Plan ist eine „Fertig heißt“-Liste.** Fünf bis zehn Checks, die zu genau diesem Vorhaben passen, jeder mit einem Beleg außerhalb der eigenen Meinung prüfbar; Qualitätswörter wie „klar“ oder „professionell“ sind kein Check. Die Form steht in [[AI/Regelwerk/wiederkehrende-jobs|wiederkehrende-jobs.md]], Abschnitt 3. Schreibt der `planer` den Plan, steht die Form im Auftrag an ihn — er lädt das Regelwerk nicht. + **Plankritik vor dem ersten Executer.** Ein Plan ab etwa drei Tasks wird, bevor ein Executer startet, von `pruefer`-Subagenten (Opus) mit getrennten Linsen gelesen: (1) Fixture-Arithmetik und Rot-Nachweise — jede Mindestzahl im Produktivcode gegen jede Fixture gerechnet, jede erwartete Fehlermeldung wörtlich; (2) Bezeichner, Signaturen über Taskgrenzen, Byte-Treue der Aus-Blöcke, Parsen der Codeblöcke, Prüf-greps nur auf Code; (3) Spec-Abdeckung in beide Richtungen. Der wirksamste Griff: Der Kritiker wendet alle Blöcke des Plans auf einer Kopie unter `/tmp` an und fährt die Suite dort. Beleg vom 2026-09-16/17 im Projekt „Trading Plattform" (Teil 9, fünf Tasks): 55 Befunde vor dem ersten Executer, darunter zwei blockierende Rechenfehler in Testerwartungen, dreimal ein grep auf die eigenen Kommentare und ein Fehler in der Referenzmessung des Koordinators; der Nachkritiker fand auf der materialisierten Fassung sechs weitere, darunter eine falsche Gültigkeitsgrenze („weit vor Zeile 30" — gemessen Zeile 60) und einen Mutanten, der nur ein zurückgespiegeltes Konfigurationsfeld traf. Danach liefen drei Executer-Wellen ohne eine einzige Abweichung durch. Ohne diese Runde wäre jeder dieser Befunde eine Executer-Meldung oder eine Korrekturrunde geworden — und der Koordinatorkontext ist die knappe Ressource (siehe Schritt 5). **Die Kopie mit dem angewendeten Plan bleibt stehen und ist der Vergleichsstand für jede Welle.** Steht der Code wörtlich im Plan, wendet der Koordinator alle Blöcke vor der ersten Welle per Skript auf eine Kopie des Baums an, fährt dort Suite und Mutanten und vergleicht nach jeder Welle zwei Dinge: ob genau die Zieldateien der Taskdateien geändert sind, und ob jede byte-gleich mit der Kopie ist. Beleg vom 2026-09-17 („Trading Plattform", 15 Tasks, vier Wellen): acht abweichende Dateien, sechs davon aus Berichten mit „keine Abweichungen", eine davon ein sichtbarer Fehler bei grünen Tests. Der Vergleich zeigt je Datei nur die erste Abweichung — danach immer `diff` auf die ganze Datei. Abweichungen an Einzelstellen setzt der Koordinator aus der Kopie, statt denselben Executer-Typ ein zweites Mal an dieselbe Fehlerklasse zu lassen. Die Skripte dafür liegen unter `.vault-tools/plan-kopie/` (Gebrauch in `LIESMICH.md` dort, samt der Warnung zur `node_modules`-Junction). @@ -92,7 +94,9 @@ Nach jeder programmatischen Änderung an einem Plan: eingebettete Codeblöcke pa **Ablaufdokumente werden in Ausführungsreihenfolge geprüft, nicht Block für Block.** Ein Installationskapitel wechselte mitten im Text das Arbeitsverzeichnis. Jeder Codeblock war für sich richtig — genau deshalb fällt so etwas beim Lesen nicht auf und beim Kunden schon. Ein Dokument, das jemand von oben nach unten abarbeitet, wird einmal von oben nach unten durchgespielt. -**6 Abschließen.** Der Koordinator committet (Subagenten nie), ergänzt `tasks/lessons.md` um jeden Befund aus Schritt 4 und den Abschnitt „Review" in `todo.md`. +**6 Abschließen.** Bevor Pascal irgendetwas als fertig sieht, fährst du die „Fertig heißt“-Liste aus dem Plan in der Reihenfolge aus [[AI/Regelwerk/wiederkehrende-jobs|wiederkehrende-jobs.md]], Abschnitt 3: alle Checks ausführen, Durchgefallenes reparieren, alle noch einmal ausführen, je Check eine Zeile mit Ergebnis und Beleg, und alles benennen, was du nicht prüfen konntest. Fallen im ersten Durchlauf mehr als zwei Checks durch, flickst du nicht, sondern benennst den Teil des Ablaufs, der das verursacht hat — das ist „Läuft etwas schief, hörst du auf und planst neu“ mit einer Zahl daran. Die Liste ersetzt Schritt 4 nicht; sie ist das, was Pascal davon zu sehen bekommt. + +Der Koordinator committet (Subagenten nie), ergänzt `tasks/lessons.md` um jeden Befund aus Schritt 4 und den Abschnitt „Review" in `todo.md`. Im „Review" steht auch, was in diesem Durchlauf neu gebaut wurde und wieder gebraucht wird (Prüfskript, Vorlage, Auftragstext) — das sind Kandidaten für die Ablage nach Abschnitt 4 derselben Datei. Pascal entscheidet, was davon gespeichert wird. ### Lauf mit echten Daten @@ -223,6 +227,7 @@ Dateikopf, CHANGELOG statt Versionsnummern in der Datei, was aus dem Code versch - [[CLAUDE|↑ Claude.md (Stamm)]] - [[AI/Regelwerk/codedoku|codedoku.md]] — Dateikopf und Codedoku - [[AI/Regelwerk/pruefwerkzeuge|pruefwerkzeuge.md]] — alles, was beurteilt statt tut +- [[AI/Regelwerk/wiederkehrende-jobs|wiederkehrende-jobs.md]] — Interviewform, „Fertig heißt“-Checks, Ablage - [[AI/Regelwerk/fable-5-1|fable-5-1.md]] — nur für Fable 5.1: modellspezifische Verhaltensunterschiede - [[AI/Regelwerk/prompting|prompting.md]] — Aufträge an Subagenten formulieren - [[AI/Regelwerk/opus-5|opus-5.md]], [[AI/Regelwerk/sonnet-5|sonnet-5.md]] — Verhalten der Zielmodelle von Planer/Prüfer bzw. Executer diff --git a/AI/Regelwerk/projekte.md b/AI/Regelwerk/projekte.md index aa26503..9689511 100644 --- a/AI/Regelwerk/projekte.md +++ b/AI/Regelwerk/projekte.md @@ -5,7 +5,7 @@ status: reference summary: "Projekt-Tag mit Ableitungsregel, README als Einstieg, Verbindung zum Themen-Seed, Administrator-Anleitung und Dump-Ordner für Projekte in Code Projekte und Security - Compliance Projekte." tags: [regelwerk, projekte] created: 2026-08-03 -updated: 2026-09-13 +updated: 2026-09-20 --- # Projekte-Regeln @@ -20,6 +20,7 @@ Gilt beim Anlegen oder Bearbeiten eines Projekts unter `/Code Projekte` oder `/S 4. **Projekt und Themenwissen verbinden.** Gibt es im IT Workspace einen Themenordner zum selben Gegenstand, bekommt dessen Seed eine Zeile „Zugehöriges Projekt" mit Link aufs README. Die Oberseed-Zeile des Projekts nennt umgekehrt den Seed. **Vault-Links bleiben außerhalb des Projekt-Repos:** Das README geht nach Gitea, wo Wikilinks nur Text sind. Die Verbindung in Richtung Seed steht deshalb im Oberseed, nicht im README. 5. **Dump-Ordner** heißen `AI/secondbrain/-memory/`, mit dem Tag aus Regel 1. Vier ältere Ordner tragen noch den Ordnernamen mit Präfix. Sie werden nicht umbenannt, weil Pfade im Harness-Gedächtnis und in Projektdateien darauf zeigen. 6. **Referenzprojekte, die nur Volltexte tragen** (unter Security - Compliance), haben keinen Hub. Der Oberseed verlinkt den Volltext direkt. Das Ordnerpräfix `active - ` ist der Projektstatus, `status: reference` in der Datei der Dokumentstatus. Das ist kein Widerspruch. +7. **Wiederkehrende Arbeit im Projekt wird ein Playbook** (angeordnet am 2026-09-20). Steht in einem Projekt etwas immer wieder an (Monatsbericht, Rezertifizierung, Update-Runde), wird daraus ein Skill nach [[AI/Regelwerk/wiederkehrende-jobs|wiederkehrende-jobs.md]]: erst das Interview, dann die SKILL.md. Und jedes Projektergebnis, das Pascal abnimmt, bekommt vorher die „Fertig heißt“-Zeilen aus Abschnitt 3 derselben Datei — je Check bestanden oder nicht, mit Beleg, dazu das, was nicht prüfbar war. Das gilt auch für Dokumente unter Security - Compliance, nicht nur für Code. ## Referenzen @@ -27,3 +28,4 @@ Gilt beim Anlegen oder Bearbeiten eines Projekts unter `/Code Projekte` oder `/S - [[Code Projekte/_MOC-Code-Projekte|Code Projekte (Oberseed)]] - [[Security - Compliance Projekte/_MOC-Security-Compliance|Security - Compliance Projekte (Oberseed)]] - [[AI/Regelwerk/frontmatter|frontmatter.md]]: README-Frontmatter (`type: project`) +- [[AI/Regelwerk/wiederkehrende-jobs|wiederkehrende-jobs.md]]: Playbook, „Fertig heißt“, Ablage diff --git a/AI/Regelwerk/wiederkehrende-jobs.md b/AI/Regelwerk/wiederkehrende-jobs.md new file mode 100644 index 0000000..6d97d93 --- /dev/null +++ b/AI/Regelwerk/wiederkehrende-jobs.md @@ -0,0 +1,107 @@ +--- +type: resource +title: "Wiederkehrende Jobs als Playbook" +status: reference +summary: "Wie aus einem wiederkehrenden Job ein Skill wird, den der Agent ohne Pascal erledigt: Interview mit einer Frage je Nachricht, SKILL.md mit festen Abschnitten, Fertig-heißt-Checks mit Beleg und die Ablage für wiederverwendbare Dateien." +tags: [regelwerk, playbook, skills, abnahme] +created: 2026-09-20 +updated: 2026-09-20 +--- + +# Wiederkehrende Jobs als Playbook + +Angeordnet am 2026-09-20. Gilt, sobald Pascal einen Job abgeben will, den er immer wieder macht. Am Ende steht ein Skill, mit dem du den Job ohne Rückfrage erledigst. + +Die Abschnitte 3 („Fertig heißt“) und 4 (Ablage) gelten auch außerhalb solcher Jobs. Wo sie in Projekten greifen, steht in [[AI/Regelwerk/codeworkflow|codeworkflow.md]] (Schritte 2 und 6) und in [[AI/Regelwerk/projekte|projekte.md]] (Regel 7). Die Form der Checks steht nur hier. + +## 1 Interview — erst fragen, dann schreiben + +Pascal beschreibt den Job in einer Zeile. Danach interviewst du ihn: + +- **Immer nur eine Frage je Nachricht.** Dann wartest du auf die Antwort. +- **Du schreibst keine Datei, bevor zwei Dinge erfüllt sind:** Du hast mindestens acht Fragen gestellt, und Pascal hat gesagt, dass ihr fertig seid. Eins von beiden reicht nicht. + +Das Interview deckt alles hier ab: + +- was den Job auslöst und wie oft er ansteht +- die Eingaben und wo genau jede davon liegt +- die Schritte in der Reihenfolge, in der Pascal sie wirklich macht +- jede Entscheidung, die er trifft, und die Regel dahinter +- was er prüft, bevor er etwas fertig nennt +- die Sonderfälle, die schon mal schiefgegangen sind +- den Ton oder Standard, den das Ergebnis treffen muss +- wie eine schlechte Version aussieht + +Das ist die Ausnahme zu Schritt 1 der Standardschleife („nur Fragen, deren Antwort die Arbeit verändert“). Dort liegt das meiste im Code. Hier liegt der Ablauf nur in Pascals Kopf, und jede nicht gestellte Frage wird später eine Annahme im Skill. + +## 2 Die SKILL.md + +Sobald du den Job ohne Pascal erledigen könntest, legst du `.claude/skills//SKILL.md` an. Der Ordnername ist kebab-case, ohne Datum und ohne Version. Das Frontmatter hat das Claude-Code-Format mit `name` und `description`; die `description` steht in Anführungszeichen ([[AI/Regelwerk/frontmatter|frontmatter.md]], „Quoting“). + +Die Abschnitte, in dieser Reihenfolge: + +1. **Zweck** — eine Zeile. +2. **Wann nutzen** +3. **Eingaben** — jede mit ihrem Ort. +4. **Schritte** — nummeriert, in einfacher Sprache, kein Fachchinesisch. +5. **Entscheidungen** — in der Form „wenn X, dann Y“. +6. **Fertig heißt** — nach Abschnitt 3. +7. **Sonderfälle** + +Danach zeigst du Pascal die komplette Datei und stellst zwei Fragen: Was habe ich falsch verstanden? Was hast du vergessen, mir zu sagen? + +Zwei Dinge aus dem Vault kommen dazu: + +- **Der Skill verweist auf das Regelwerk, statt es zu kopieren.** Braucht ein Schritt den Schreibstil oder die Windows-Shell-Regeln, nennt er die Datei. +- **`.claude/skills/` wird täglich um 04:00 veröffentlicht, ohne Freigabe** ([[AI/Regelwerk/veroeffentlichung|veroeffentlichung.md]]). In SKILL.md und Ablage stehen deshalb keine Kundennamen, Domains, IP-Adressen oder Zugangsdaten. Hängt der Job an einem Kunden, liegen dessen Details außerhalb von `.claude/skills/`, und der Skill nennt nur den Pfad. + +## 3 Fertig heißt + +Der Abschnitt „Fertig heißt“ enthält fünf bis zehn Checks, die speziell zu diesem Job passen. **Jeder Check ist mit einem Beleg außerhalb deiner eigenen Meinung prüfbar:** + +- eine Zahl, die bis zu ihrer Quelldatei zurückgeht +- ein Link, der lädt +- ein Screenshot vom fertigen Ergebnis +- ein Test, der läuft +- eine Aussage, die zu einem Zitat passt +- eine Zählung oder ein Wert, der gegen eine Referenzdatei geprüft ist + +Schwammige Qualitätswörter wie „klar“, „professionell“ oder „hochwertig“ sind kein Check. Niemand kann sie nachmessen, du auch nicht. + +**Jedes Mal, wenn du den Job fertig hast, und bevor du Pascal irgendetwas zeigst:** + +1. Führ jeden Check aus. +2. Reparier, was durchfällt. +3. Führ alle Checks noch einmal aus, nicht nur die reparierten. +4. Gib Pascal je Check eine Zeile: bestanden oder nicht bestanden, mit dem Beleg, den du benutzt hast. +5. Nenn alles, was du nicht prüfen konntest, und warum. Nimm nicht an, dass es passt. + +**Fallen beim ersten Durchlauf mehr als zwei Checks durch, hörst du auf.** Du sagst Pascal, welcher Teil des Ablaufs das verursacht hat, statt das Ergebnis zu flicken. Drei kaputte Checks sind ein Fehler im Ablauf, und den findet man nicht, indem man das Ergebnis repariert. + +Wird aus einem Check ein Skript, gilt [[AI/Regelwerk/pruefwerkzeuge|pruefwerkzeuge.md]]: Es zählt erst, wenn du es an kaputter Eingabe hast anschlagen sehen. + +## 4 Ablage — was du wieder brauchst, baust du nur einmal + +Ist bei einem Job etwas entstanden, das Pascal noch einmal braucht (ein Skript, eine Vorlage, eine E-Mail, eine Checkliste), dann: + +1. Speicher es in `/ablage/` mit einem sprechenden Namen, zum Beispiel `zahlen-ziehen.py` oder `update-vorlage.md`. Kein Datum und keine Versionsnummer im Namen. +2. Ersetz Pascals Details durch Platzhalter in eckigen Klammern, damit es wiederverwendbar ist. Darunter bleibt ein ausgefülltes Beispiel als Referenz stehen. Wegen der Veröffentlichung ist das Beispiel mit neutralen Werten gefüllt, nicht mit echten Kundendaten. +3. Aktualisier die SKILL.md: Der Schritt, der die Datei braucht, nennt sie beim Namen. Dazu steht, wann sie genutzt wird und wann nicht. +4. Trag eine Zeile in `/ablage/INDEX.md` ein: Dateiname, wofür sie da ist, heutiges Datum. Die `INDEX.md` ist ein Verzeichnis und bekommt kein Frontmatter. + +**Nicht in die Ablage:** einmalige Ergebnisse, nichts mit Passwort oder API-Schlüssel, und kein Entwurf, den Pascal nicht freigegeben hat. + +Danach erledigst du den ganzen Job noch einmal von vorn. Du sagst Pascal, welche gespeicherten Dateien du benutzt hast und welche Teile du komplett neu gebaut hast. Alles neu Gebaute ist ein Kandidat für die Ablage. Du sagst also auch, was du als Nächstes speichern würdest. + +## 5 Wenn du keine Dateien schreiben kannst + +In Claude Code kannst du schreiben. In einer Sitzung ohne Dateizugriff tust du nicht so, als hättest du es getan. Du gibst den kompletten Dateiinhalt im Chat aus, nennst den genauen Dateinamen und wo er in den Ordner gehört. Und du erinnerst Pascal daran, die alte hochgeladene Datei zu ersetzen, statt eine zweite anzulegen. + +## Referenzen + +- [[CLAUDE|↑ Claude.md (Stamm)]] +- [[AI/Regelwerk/codeworkflow|codeworkflow.md]] — „Fertig heißt“ als Abnahme im Plan und vor der Fertigmeldung +- [[AI/Regelwerk/projekte|projekte.md]] — wiederkehrende Arbeit im Projekt +- [[AI/Regelwerk/pruefwerkzeuge|pruefwerkzeuge.md]] — wenn ein Check ein Skript wird +- [[AI/Regelwerk/veroeffentlichung|veroeffentlichung.md]] — warum Skill und Ablage keine Kundendaten tragen +- [[AI/Regelwerk/frontmatter|frontmatter.md]] — Quoting der `description` diff --git a/CLAUDE.md b/CLAUDE.md index d472004..1551c56 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -78,6 +78,7 @@ Jede Datei triggert nur den betreffenden Teil — nicht bei jeder Aufgabe alles - Code-Projekt planen, umsetzen, debuggen → [[AI/Regelwerk/codeworkflow|codeworkflow.md]] — die **Standardschleife** (ich kläre und plane, der Executer setzt um, ich prüfe gegen) ist bei jeder nicht trivialen Code-Aufgabe der Regelfall. - Produktivdatei anlegen oder anfassen → [[AI/Regelwerk/codedoku|codedoku.md]] - Projekt in Code Projekte / Security-Compliance anlegen oder bearbeiten → [[AI/Regelwerk/projekte|projekte.md]] +- Wiederkehrenden Job in ein Playbook (Skill) verwandeln, „Fertig heißt“-Checks schreiben oder etwas Wiederverwendbares ablegen → [[AI/Regelwerk/wiederkehrende-jobs|wiederkehrende-jobs.md]] — erst das Interview, eine Frage je Nachricht, dann Dateien. - Mechanische Prüfung, Prüfskript, Hook oder Gate-Erweiterung bauen → [[AI/Regelwerk/pruefwerkzeuge|pruefwerkzeuge.md]] — **vor der ersten Zeile Code laden.** - Skript oder Shellbefehl unter Windows (PowerShell, Python, Git-Bash, WSL, ssh) → [[AI/Regelwerk/windows-shell|windows-shell.md]] - Veröffentlichten Umfang ändern, `.gitignore` im Vault-Root anfassen oder eine Auto-Push-Meldung auflösen → [[AI/Regelwerk/veroeffentlichung|veroeffentlichung.md]]