90 lines
2.3 KiB
Python
90 lines
2.3 KiB
Python
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())
|