Code / tools/check_headers.py

tools/check_headers.py 107 lines
#!/usr/bin/env python3
# =============================================================================
#  Project   : modelmap
#  File      : tools/check_headers.py
#  Purpose   : Fail if any tracked source file lacks the mandatory author header
#  Author    : Simon-Pierre Boucher
#  Contact   : contact@spboucher.ai
#  Website   : https://modelmap.io
#  Created   : 2026-08-12
#  Modified  : 2026-08-12
#  Platform  : macOS / Apple Silicon (arm64)
#  License   : All rights reserved (research code)
# =============================================================================
"""Header compliance checker (charter §0.1).

Scans git-tracked source files and verifies each begins (after an optional
shebang) with the standardized author header, or — for markdown — with YAML
front matter carrying the author fields. Exits non-zero on any violation,
so it can gate commits.
"""

from __future__ import annotations

import subprocess
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent

HASH_COMMENT_EXT = {".py", ".sh", ".zsh", ".yaml", ".yml", ".toml", ".cff"}
SLASH_COMMENT_EXT = {".c", ".cpp", ".h", ".hpp", ".metal", ".swift", ".m", ".js", ".ts", ".css"}
MARKDOWN_EXT = {".md"}
HASH_COMMENT_NAMES = {"Makefile"}

REQUIRED_TOKENS = (
    "Project   : modelmap",
    "Author    : Simon-Pierre Boucher",
    "Contact   : contact@spboucher.ai",
)
MD_REQUIRED_TOKENS = (
    "project: modelmap",
    "author: Simon-Pierre Boucher",
    "contact: contact@spboucher.ai",
)

# Files that carry no comments / are data or licenses. CLAUDE.md is the
# authoritative charter (its content is the spec itself, not a research doc).
EXEMPT = {"LICENSE", ".gitignore", "package.json", "package-lock.json", "CLAUDE.md"}


def tracked_files() -> list[Path]:
    out = subprocess.run(
        ["git", "ls-files"], cwd=ROOT, capture_output=True, text=True, check=True
    ).stdout
    return [ROOT / line for line in out.splitlines() if line.strip()]


def head_of(path: Path, n_bytes: int = 4096) -> str:
    try:
        return path.read_text(errors="replace")[:n_bytes]
    except OSError:
        return ""


def check(path: Path) -> str | None:
    """Return an error string, or None if compliant / not applicable."""
    name = path.name
    if name in EXEMPT:
        return None
    ext = path.suffix.lower()
    text = head_of(path)
    if text.startswith("#!"):
        text = text.split("\n", 1)[1] if "\n" in text else ""

    if ext in MARKDOWN_EXT:
        if not text.lstrip().startswith("---"):
            return "missing YAML front matter"
        missing = [t for t in MD_REQUIRED_TOKENS if t not in text]
        return f"front matter missing: {', '.join(missing)}" if missing else None

    if ext in HASH_COMMENT_EXT or ext in SLASH_COMMENT_EXT or name in HASH_COMMENT_NAMES:
        missing = [t for t in REQUIRED_TOKENS if t not in text]
        return f"header missing: {', '.join(missing)}" if missing else None

    return None  # extension not governed by the header rule


def main() -> int:
    errors: list[tuple[Path, str]] = []
    for f in tracked_files():
        if not f.is_file():
            continue
        err = check(f)
        if err:
            errors.append((f, err))
    if errors:
        print(f"check_headers: {len(errors)} non-compliant file(s):")
        for f, err in errors:
            print(f"  {f.relative_to(ROOT)}: {err}")
        return 1
    print("check_headers: all tracked source files carry the mandatory header.")
    return 0


if __name__ == "__main__":
    sys.exit(main())