#!/usr/bin/env python3 """Reject executable v1 compatibility paths and symbols from the v2 tree.""" from __future__ import annotations import argparse import re import sys from pathlib import Path LEGACY_PATHS = ( Path("backend/app"), Path("backend/backup"), Path("backend/tests"), Path("frontend/src/api/client.ts"), ) SCAN_ROOTS = (Path("backend/src"), Path("frontend/src"), Path("openapi")) TEXT_SUFFIXES = {".json", ".py", ".ts", ".tsx", ".yaml", ".yml"} FORBIDDEN = ( re.compile(r"/api/v1(?:/|\b)"), re.compile(r"\blegacy_(?:reader|importer?|converter?)\b", re.IGNORECASE), re.compile(r"\btimestamp_directory\b", re.IGNORECASE), re.compile(r"\b(?:app\.main|backup\.engine)\b"), ) def scan(root: Path) -> list[str]: findings: list[str] = [] for relative in LEGACY_PATHS: if (root / relative).exists(): findings.append(f"legacy path exists: {relative}") for relative_root in SCAN_ROOTS: scan_root = root / relative_root if not scan_root.exists(): continue for path in sorted(scan_root.rglob("*")): if not path.is_file() or path.suffix not in TEXT_SUFFIXES: continue try: text = path.read_text(encoding="utf-8") except (OSError, UnicodeError) as error: findings.append(f"cannot read {path.relative_to(root)}: {error}") continue for pattern in FORBIDDEN: if pattern.search(text): findings.append( f"forbidden symbol {pattern.pattern!r}: {path.relative_to(root)}" ) return findings def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("root", nargs="?", default=".", type=Path) args = parser.parse_args() root = args.root.resolve() findings = scan(root) if findings: print("v1 compatibility scan failed:", file=sys.stderr) for finding in findings: print(f"- {finding}", file=sys.stderr) return 1 print("v1 compatibility scan: OK") return 0 if __name__ == "__main__": raise SystemExit(main())