Files
PDFToMD/.codex/hooks/drift_policy.py
T
김경종 7e985ae94a add files
2026-04-30 17:05:19 +09:00

82 lines
2.2 KiB
Python

#!/usr/bin/env python3
"""Catch high-confidence documentation drift before a Codex turn ends."""
from __future__ import annotations
import json
import subprocess
import sys
from pathlib import Path
def changed_paths(root: Path) -> set[str]:
result = subprocess.run(
["git", "status", "--porcelain"],
cwd=root,
capture_output=True,
text=True,
timeout=20,
)
if result.returncode != 0:
return set()
paths: set[str] = set()
for line in result.stdout.splitlines():
if not line.strip():
continue
path = line[3:].replace("\\", "/")
if " -> " in path:
path = path.split(" -> ", 1)[1]
paths.add(path)
return paths
def block(reason: str) -> int:
json.dump({"decision": "block", "reason": reason}, sys.stdout)
return 0
def main() -> int:
try:
payload = json.load(sys.stdin)
except json.JSONDecodeError:
return 0
if payload.get("stop_hook_active"):
return 0
root = Path(payload.get("cwd") or ".").resolve()
paths = changed_paths(root)
if "requirements.txt" in paths and "docs/TOOLCHAIN.md" not in paths:
return block(
"requirements.txt changed without docs/TOOLCHAIN.md. "
"Update the toolchain notes with dependency compatibility rationale."
)
sample_pdf_changed = any(path.startswith("samples/") and path.lower().endswith(".pdf") for path in paths)
metadata_changed = "samples/metadata.json" in paths
if sample_pdf_changed and not metadata_changed:
return block(
"A sample PDF changed without samples/metadata.json. "
"Update the sample metadata mapping so quality tests know the corpus traits."
)
policy_docs = {
"docs/ARCHITECTURE.md",
"docs/CONVERSION_POLICY.md",
"docs/ADR.md",
}
touched_policy_docs = policy_docs.intersection(paths)
if touched_policy_docs and "PROGRESS.md" not in paths:
return block(
"Architecture or conversion policy docs changed without PROGRESS.md. "
"Record the decision and handoff context in PROGRESS.md."
)
return 0
if __name__ == "__main__":
raise SystemExit(main())