diff --git a/scripts/check_repository_hygiene.py b/scripts/check_repository_hygiene.py new file mode 100644 index 00000000..5b5bfffb --- /dev/null +++ b/scripts/check_repository_hygiene.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import subprocess +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +BLOCKED_PREFIXES = (".codex-input/", ".codex-artifacts/") +BLOCKED_SUFFIXES = (".db-wal", ".db-shm") +LARGE_FILE_LIMIT = 20 * 1024 * 1024 +LARGE_FILE_ALLOWLIST_PREFIXES = ( + "artifacts/evidence/accuracy/", + "docs/assets/", + "frontend/public/portfolio/", + "output/pdf/", +) + + +def tracked_files() -> list[str]: + result = subprocess.run( + ["git", "ls-files", "-z"], + cwd=ROOT, + check=True, + capture_output=True, + ) + return [item for item in result.stdout.decode("utf-8").split("\0") if item] + + +def main() -> int: + violations: list[str] = [] + for relative in tracked_files(): + normalized = relative.replace("\\", "/") + if normalized.startswith(BLOCKED_PREFIXES): + violations.append(f"{normalized}: local agent scratch must not be tracked") + continue + if normalized.endswith(BLOCKED_SUFFIXES): + violations.append(f"{normalized}: transient database state must not be tracked") + continue + + path = ROOT / relative + if not path.is_file(): + continue + size = path.stat().st_size + if size > LARGE_FILE_LIMIT and not normalized.startswith( + LARGE_FILE_ALLOWLIST_PREFIXES + ): + violations.append( + f"{normalized}: {size} bytes exceeds the " + f"{LARGE_FILE_LIMIT}-byte tracked-file budget" + ) + + if violations: + print("Repository hygiene violations:", file=sys.stderr) + for violation in violations: + print(f" - {violation}", file=sys.stderr) + return 1 + + print("Repository hygiene check passed.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())