Files
weichen 5618cfc8cf feat(tooling): add Cursor governance hooks, skills, and rule pointers
Layer advisory Cursor hooks and skills on the PI executor, shrink the
test-report rule to the validation contract, and fix MCP relative paths.
2026-09-24 09:25:38 +00:00

102 lines
3.2 KiB
Python

#!/usr/bin/env python3
"""sessionStart / stop: audit + optional fail-open inventory (no context injection)."""
from __future__ import annotations
import json
import os
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
import _lib as lib # noqa: E402
HOOK = "audit-session"
INVENTORY_TIMEOUT = 15
def _run_inventory(root: Path) -> dict:
out: dict = {"cleanup_report": None, "docs_drift": None, "errors": []}
if os.environ.get("CURSOR_HOOKS_NO_INVENTORY", "").strip() in ("1", "true", "TRUE", "yes"):
out["skipped"] = "CURSOR_HOOKS_NO_INVENTORY"
return out
agentctl = root / "scripts" / "agentctl.py"
if not agentctl.is_file():
out["errors"].append("agentctl.py missing")
return out
for label, args in (
("cleanup_report", ["cleanup", "report"]),
("docs_drift", ["docs", "drift"]),
):
try:
r = subprocess.run(
["python3", "-B", str(agentctl), *args],
cwd=str(root),
capture_output=True,
text=True,
timeout=INVENTORY_TIMEOUT,
check=False,
)
payload = None
for stream in (r.stdout, r.stderr):
for line in (stream or "").splitlines():
line = line.strip()
if line.startswith("{"):
try:
payload = json.loads(line)
break
except json.JSONDecodeError:
continue
if payload is not None:
break
out[label] = {
"exit_code": r.returncode,
"payload": payload,
"stdout_tail": (r.stdout or "")[-2000:],
"stderr_tail": (r.stderr or "")[-1000:],
}
except subprocess.TimeoutExpired:
out["errors"].append(f"{label}: timeout>{INVENTORY_TIMEOUT}s")
except OSError as exc:
out["errors"].append(f"{label}: {exc}")
return out
def main() -> None:
raw = lib.read_stdin()
lib.maybe_probe(raw, HOOK)
data = lib.parse_json_loose(raw) or {}
event = str(
lib.first_key(data, "hook_event_name", "event", "event_name", "type") or "unknown"
)
root = lib.repo_root()
if "start" in event.lower() or event == "unknown":
inv = _run_inventory(root)
inv_path = None
try:
inv_dir = lib.inventory_dir()
inv_dir.mkdir(parents=True, exist_ok=True)
ts = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
path = inv_dir / f"inventory-{ts}.json"
path.write_text(json.dumps(inv, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
inv_path = str(path)
except OSError:
pass
lib.audit(
HOOK,
"SESSION_AUDIT",
event=event,
inventory_path=inv_path,
inventory_errors=inv.get("errors"),
)
else:
lib.audit(HOOK, "SESSION_AUDIT", event=event)
lib.emit({})
raise SystemExit(0)
if __name__ == "__main__":
main()