import json import re import subprocess import sys from pathlib import Path def _repo_root(cwd: Path) -> Path: try: root = subprocess.check_output( ["git", "rev-parse", "--show-toplevel"], cwd=cwd, text=True, stderr=subprocess.DEVNULL, ).strip() except (subprocess.CalledProcessError, FileNotFoundError): return cwd return Path(root) def _is_git_commit(command: str) -> bool: return re.search( r"^\s*git(?:\s+(?:-[A-Za-z]\s+\S+|--[A-Za-z0-9-]+(?:=\S+)?))*\s+commit\b", command, ) is not None def _deny(reason: str) -> None: print( json.dumps( { "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": reason, } } ) ) def _tail(text: str, limit: int = 1200) -> str: text = text.strip() if len(text) <= limit: return text return text[-limit:] def _build_pre_commit_commands(root: Path) -> list[list[str]]: return [ [sys.executable, "-m", "unittest", "discover", "-s", "scripts", "-p", "test_*.py"], [sys.executable, "scripts/validate_workspace.py"], ] def _run_checks(root: Path) -> str | None: for command in _build_pre_commit_commands(root): result = subprocess.run(command, cwd=root, capture_output=True, text=True) if result.returncode != 0: details = _tail(result.stdout + "\n" + result.stderr) label = " ".join(command) if details: return f"{label} failed:\n{details}" return f"{label} failed with exit code {result.returncode}." return None def main() -> int: try: payload = json.load(sys.stdin) except json.JSONDecodeError: return 0 command = payload.get("tool_input", {}).get("command", "") if not isinstance(command, str) or not _is_git_commit(command): return 0 cwd = Path(payload.get("cwd") or Path.cwd()) root = _repo_root(cwd) failure = _run_checks(root) if failure: _deny(f"PRE-COMMIT CHECKS: {failure}") return 0 if __name__ == "__main__": raise SystemExit(main())