"""Täglicher Lauf: committet und pusht die Anweisungsschicht und spiegelt die Werkzeuge. Ersteller: Pascal Michel Erstellt: 2026-09-17 Geändert: 2026-10-14 (Werkzeug-Spiegel nach claude-werkzeuge) WARUM LOKAL UND NICHT ALS COWORK-ROUTINE Eine Cowork-Routine läuft in Anthropics Cloud und hat weder Zugriff auf D:\\Claude noch auf die Umgebungsvariable GITEA_TOKEN dieser Maschine (so wörtlich in der Routinen-Doku, gelesen am 2026-09-17). Sie könnte hier nichts committen. Deshalb die Windows-Aufgabenplanung. WAS ES TUT 1. Genau dasselbe wie der frühere PostToolUse-Hook: die Arbeitsfunktion aus auto_push.py, unter derselben Sperre. Der Umfang steht in der .gitignore des Vault-Roots, nicht hier. 2. Danach, noch unter der Sperre, spiegelt werkzeuge_spiegel.py den committeten Stand von .vault-tools/ und .claude/ in das öffentliche Repo claude-werkzeuge. Scheitert Schritt 1, entfällt Schritt 2. WAS ES MELDET Jeder Lauf schreibt eine Zeile je Schritt nach lauf.log im selben Ordner, auch ein Lauf ohne Änderung. Eine Protokolldatei, die nur bei Erfolg schreibt, lässt sich von einer nicht gestarteten Aufgabe nicht unterscheiden, und genau das war am 2026-09-07 beim Depotbericht das Problem. Exit 0: gepusht oder nichts zu tun, in beiden Schritten. Exit 1: Befund in einem Schritt, Text steht im Protokoll. Die Aufgabenplanung zeigt den Code als „Letztes Ergebnis". """ from __future__ import annotations import importlib.util import sys import traceback from datetime import datetime from pathlib import Path HIER = Path(__file__).resolve().parent PROTOKOLL = HIER / "lauf.log" MAX_PROTOKOLL_ZEILEN = 400 def lade(name: str): spec = importlib.util.spec_from_file_location(name, HIER / f"{name}.py") if spec is None or spec.loader is None: raise RuntimeError(f"{name}.py nicht ladbar: {HIER / f'{name}.py'}") modul = importlib.util.module_from_spec(spec) spec.loader.exec_module(modul) return modul def schreibe(zeile: str) -> None: stempel = datetime.now().strftime("%Y-%m-%d %H:%M:%S") text = f"{stempel} {zeile}\n" try: alt = PROTOKOLL.read_text(encoding="utf-8").splitlines(keepends=True) except OSError: alt = [] alt.append(text) PROTOKOLL.write_text("".join(alt[-MAX_PROTOKOLL_ZEILEN:]), encoding="utf-8") sys.stdout.write(text) def vault_push(hook, wurzel: Path) -> int: try: vorher = hook.git(wurzel, "rev-parse", "HEAD").strip() hook.arbeite(wurzel) nachher = hook.git(wurzel, "rev-parse", "HEAD").strip() except hook.GitFehler as fehler: schreibe(f"BEFUND {fehler.kopf} | {' '.join(fehler.rumpf.split())[:400]}") return 1 except Exception as fehler: # noqa: BLE001 schreibe(f"BEFUND unerwarteter Abbruch: {type(fehler).__name__}: {fehler}") schreibe(" " + " ".join(traceback.format_exc().split())[:400]) return 1 if vorher == nachher: schreibe(f"ok nichts zu tun, HEAD {nachher[:8]}") else: schreibe(f"ok gepusht, HEAD {vorher[:8]} -> {nachher[:8]}") return 0 def werkzeuge_spiegeln(wurzel: Path) -> int: try: spiegel = lade("werkzeuge_spiegel") status, text = spiegel.spiegele(wurzel) except Exception as fehler: # noqa: BLE001 - SpiegelFehler und jeder Absturz sind Befunde schreibe(f"BEFUND werkzeuge: {type(fehler).__name__}: {' '.join(str(fehler).split())[:400]}") return 1 schreibe(f"ok werkzeuge {status}, {text}") return 0 def main() -> int: try: hook = lade("auto_push") except Exception as fehler: # noqa: BLE001 schreibe(f"BEFUND auto_push.py nicht ladbar: {type(fehler).__name__}: {fehler}") return 1 wurzel = HIER.parents[1] sperre = wurzel / hook.SPERRE_REL deskriptor = hook.hole_sperre(sperre) if deskriptor is None: # Kein Befund: ein anderer Lauf arbeitet und nimmt alles mit. schreibe("uebersprungen Sperre belegt, ein anderer Lauf arbeitet") return 0 try: if vault_push(hook, wurzel) != 0: return 1 return werkzeuge_spiegeln(wurzel) finally: hook.gib_sperre_frei(deskriptor, sperre) if __name__ == "__main__": try: sys.stdout.reconfigure(encoding="utf-8") except AttributeError: pass raise SystemExit(main())