Files
rune/scripts/install_mimo_hook.py
Nick 586dc3b9c4 fix(spawn-door): canonical subagent-protocol-guard in the MANAGED set + tightened lexical pass (adversarial-escape Door 5, red-first)
The spawn guard was unversioned (no canonical copy, absent from
install_mimo_hook.py MANAGED — the drift-undetected rot that killed
builder-bridge pre-T22) and lexically porous. Canonical copy now lives
in config/mimo-hooks/ and is hash-drift-checked like its siblings;
installed copy synced.

Tightened classification (classifyBrief, exported for direct testing):
- a build brief needs the admission marker AND a target surface (repo
  path or project name, judged outside the admission boilerplate) —
  marker-only briefs are refused with typed reasons
  BUILD_BRIEF_NO_ADMISSION / BUILD_BRIEF_NO_TARGET;
- build markers match on word boundaries (a 'report' mention is no
  longer build-shaped via substring 'port');
- the research exemption requires an explicit leading 'research:'
  prefix — self-declared 'research artifacts to ...' briefs are no
  longer exempt.
2026-10-22 09:09:23 +01:00

74 lines
2.5 KiB
Python
Executable File

#!/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/<same-name>.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",
"subagent-protocol-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())