#!/usr/bin/env python3 """Stellt sicher, dass ein Worker-Profil die Agent-Hooks kennt (Faden 8, 13.07.2026; erweitert 19.07.2026 um die No-Progress-Bremse und Mehrfach-Eintraege pro Event). Kanban-Worker laufen unter IHREM Profil (HERMES_HOME=~/.hermes/profiles/) und lesen dessen config.yaml — NICHT die Top-Level ~/.hermes/config.yaml. Die worker-relevanten Hooks muessen also in JEDER Worker-Profil-config.yaml stehen, sonst feuern sie fuer Worker nie (Bug-Fund 13.07.). WICHTIG (Falle, 19.07.): Ein Event darf nur EINMAL als YAML-Key existieren. Existiert `pre_tool_call:` bereits (tabu-pfade-guard), wird ein weiterer Eintrag IN den Block eingefuegt — ein zweiter `pre_tool_call:`-Key wuerde beim YAML-Laden den ersten verschlucken und den Tabu-Guard lautlos deaktivieren. Idempotent + validiert + Backup. Anker fuer neue Event-Bloecke ist der bestehende `pre_verify`-Block. Arg: Pfad zur Profil-config.yaml. """ import sys import os import datetime import shutil try: import yaml except Exception: print(" (PyYAML fehlt — ensure-profile-hooks uebersprungen)") sys.exit(0) AH = os.path.expanduser("~/.hermes/agent-hooks") # Reihenfolge zaehlt: tabu-pfade-guard MUSS vor der Bremse stehen (Schutz vor Komfort). WANT = [ ("pre_llm_call", f"{AH}/box-steckbrief-inject.sh", 10), ("pre_tool_call", f"{AH}/tabu-pfade-guard.py", 10), ("pre_tool_call", f"{AH}/no-progress-bremse.py pre", 10), ("post_tool_call", f"{AH}/no-progress-bremse.py post", 10), ] if len(sys.argv) < 2: print(" usage: ensure-profile-hooks.py ") sys.exit(0) p = sys.argv[1] if not os.path.isfile(p): sys.exit(0) txt = open(p, encoding="utf-8").read() anchor = (" pre_verify:\n" f" - command: {AH}/pre-verify-gates.sh\n" " timeout: 60\n") if txt.count(anchor) != 1: print(f" [{os.path.basename(os.path.dirname(p))}] kein eindeutiger pre_verify-Anker — SKIP") sys.exit(0) new = txt changed = False for event, cmd, timeout in WANT: if cmd in new: continue entry = f" - command: {cmd}\n timeout: {timeout}\n" ev_line = f" {event}:\n" if ev_line in new: # Eintrag ANS ENDE des bestehenden Event-Blocks: direkt nach dem Key einfuegen # waere auch ok, aber hinter dem letzten Eintrag haelt die gewollte Reihenfolge. idx = new.index(ev_line) + len(ev_line) end = idx for line in new[idx:].splitlines(keepends=True): if line.startswith((" - ", " ")): end += len(line) else: break new = new[:end] + entry + new[end:] else: new = new.replace(anchor, anchor + ev_line + entry, 1) changed = True if not changed: print(f" [{os.path.basename(os.path.dirname(p))}] Hooks schon registriert.") sys.exit(0) try: d = yaml.safe_load(new) h = d["hooks"] for event, cmd, _t in WANT: assert any(cmd == x.get("command", "") for x in h.get(event, [])), f"{event}: {cmd} fehlt" assert any("pre-verify-gates.sh" in x.get("command", "") for x in h["pre_verify"]) # Tabu-Guard muss VOR der Bremse stehen (beide in pre_tool_call). cmds = [x.get("command", "") for x in h["pre_tool_call"]] assert cmds.index(f"{AH}/tabu-pfade-guard.py") < cmds.index(f"{AH}/no-progress-bremse.py pre") except Exception as exc: print(f" [{p}] Validierung fehlgeschlagen ({exc}) — config UNVERAENDERT.") sys.exit(0) bak = p + ".bak-hooks-" + datetime.datetime.now().strftime("%Y%m%d-%H%M%S") shutil.copy2(p, bak) tmp = p + ".tmp-hooks" open(tmp, "w", encoding="utf-8").write(new) os.replace(tmp, p) print(f" [{os.path.basename(os.path.dirname(p))}] Hooks ergaenzt (Backup {os.path.basename(bak)}).")