#!/usr/bin/env python3 """install_mimo_hook.py — T21.2 §21.2D (T22: multi-hook): canonical MiMo hook installer. Canonical sources: config/mimo-hooks/*.ts Targets: ~/.mimocode/hooks/.ts Every hook is hash-verified after install; drift is detected in both directions. T22 added builder-bridge.ts to the managed set — it was previously installed by hand, unversioned, which is how it silently rotted (hardcoded ephemeral interpreter). A hook that reaches the builder must be version-controlled and drift-checked like any other authority. Usage: python3 scripts/install_mimo_hook.py # sync all managed hooks python3 scripts/install_mimo_hook.py --check # verify only, install nothing """ import hashlib import shutil import sys from pathlib import Path REPO = Path(__file__).resolve().parent.parent CANONICAL_DIR = REPO / "config" / "mimo-hooks" TARGET_DIR = Path.home() / ".mimocode" / "hooks" # The managed set. Adding a file here is what makes a hook version-controlled. MANAGED = ["event-wait-guard.ts", "builder-bridge.ts", "scaffolding-guard.ts"] def sha256(p: Path) -> str: return hashlib.sha256(p.read_bytes()).hexdigest() def sync_one(name: str, check_only: bool) -> bool: canonical = CANONICAL_DIR / name target = TARGET_DIR / name if not canonical.is_file(): print(f"canonical source missing: {canonical}") return False canon_hash = sha256(canonical) if target.is_file() and sha256(target) == canon_hash: print(f"in-sync: {name} {canon_hash[:12]}") return True if check_only: state = "DRIFT" if target.is_file() else "MISSING" inst = sha256(target)[:12] if target.is_file() else "-" print(f"{state}: {name} canonical={canon_hash[:12]} installed={inst}") return False if target.is_file(): print(f"DRIFT: {name} canonical={canon_hash[:12]} installed={sha256(target)[:12]}") else: print(f"hook missing at {target}") TARGET_DIR.mkdir(parents=True, exist_ok=True) shutil.copy2(canonical, target) if sha256(target) != canon_hash: print(f"INSTALL FAILED: {name} hash mismatch after copy") return False print(f"installed: {name} {canon_hash[:12]}") return True def main() -> int: check_only = "--check" in sys.argv ok = True for name in MANAGED: ok = sync_one(name, check_only) and ok return 0 if ok else 1 if __name__ == "__main__": sys.exit(main())