#!/usr/bin/env python3 """Reject commits consisting only of generated indexes/projection refreshes. Applies to every author: automation must accompany an actual source change. --staged supports a pre-commit hook; BASE..HEAD checks every commit for CI. """ import argparse from pathlib import PurePosixPath import subprocess def generated(path): parsed = PurePosixPath(path.lower()) name = parsed.name return (name in {"work-records.md", ".custodian-brief.md", "source-index.md", "publication-index.md", "index.generated.md"} or name.startswith("source-inventory") or name.startswith("decision-packets") or name.startswith("publication-index") or name.startswith("projection-refresh") or str(parsed).startswith("generated/") or str(parsed).startswith("docs/attended-window/") or any(part in {"evidence", "decision-packets", "evidence-packets"} for part in parsed.parts[:-1])) def standalone(paths): return bool(paths) and all(generated(path) for path in paths) def git(*args): return subprocess.check_output(["git", *args]) def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--staged", action="store_true") parser.add_argument("--range", default="HEAD^..HEAD") args = parser.parse_args() commits = [None] if args.staged else git("rev-list", "--reverse", args.range).decode().splitlines() failed = [] for commit in commits: raw = git("diff", "--cached", "--name-only", "-z") if commit is None else git( "diff-tree", "--root", "--no-commit-id", "-r", "--name-only", "-z", commit) paths = [path.decode() for path in raw.split(b"\0") if path] if standalone(paths): failed.append(commit or "staged") if failed: parser.exit(1, "Generated-only commits rejected: " + ", ".join(failed) + "\n") if __name__ == "__main__": main()