spb/air Public MIT
AIR — The Language of Accounting.
Python 100%
1#!/usr/bin/env python32# =============================================================================3# Projet : AIR — Accounting Intermediate Representation4# Auteur : Simon-Pierre Boucher5# Contact : contact@spboucher.ai6# Fichier : check_headers.py7# Description : CI gate — fails if any project file is missing the author header.8# =============================================================================9"""Verify that every project file carries the mandatory author header.1011Usage: python3 scripts/check_headers.py [root]12Exit code 0 if all files pass, 1 otherwise (with a list of offenders).13"""14from __future__ import annotations1516import json17import sys18from pathlib import Path1920AUTHOR = "Simon-Pierre Boucher"21CONTACT = "contact@spboucher.ai"2223# Extensions checked for a comment header containing author + contact.24COMMENT_EXTS = {25 ".py", ".ts", ".js", ".rs", ".go", ".c", ".h", ".zig",26 ".md", ".yaml", ".yml", ".toml", ".sql", ".sh",27}28SKIP_DIRS = {".git", ".venv", "venv", "node_modules", "__pycache__",29 ".pytest_cache", ".mypy_cache", ".hypothesis", ".claude", "dist", "build"}30SKIP_FILES = {".gitignore", "LICENSE", "py.typed"}31HEADER_WINDOW = 15 # header must appear within the first N lines323334def has_header(path: Path) -> bool:35 if path.suffix == ".json":36 try:37 data = json.loads(path.read_text(encoding="utf-8"))38 except (json.JSONDecodeError, UnicodeDecodeError):39 return False40 return isinstance(data, dict) and CONTACT in str(data.get("_author", ""))41 try:42 head = "".join(43 path.read_text(encoding="utf-8").splitlines(keepends=True)[:HEADER_WINDOW]44 )45 except UnicodeDecodeError:46 return True # binary-ish file, not subject to header rule47 return AUTHOR in head and CONTACT in head484950def main() -> int:51 root = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(__file__).resolve().parent.parent52 offenders: list[Path] = []53 for path in sorted(root.rglob("*")):54 if not path.is_file():55 continue56 if any(part in SKIP_DIRS for part in path.parts):57 continue58 if path.name in SKIP_FILES:59 continue60 if path.suffix not in COMMENT_EXTS and path.suffix != ".json":61 continue62 if not has_header(path):63 offenders.append(path.relative_to(root))64 if offenders:65 print("Missing author header (Simon-Pierre Boucher / contact@spboucher.ai):")66 for p in offenders:67 print(f" - {p}")68 return 169 print("check_headers: OK — all files carry the author header.")70 return 0717273if __name__ == "__main__":74 raise SystemExit(main())75