#!/usr/bin/env python3 """check_laws_wired.py [ROOT]: the layout of the laws (AGENTS.md, Tests). Every law lives in LAWS.bend or in a file of laws/ that LAWS.bend imports (laws/.bend, or laws//
.bend once the area's file passes 1,000 lines), and every proof and proof lemma in a file of proof/ that PROOF.bend imports (the Bend Bank layout: proof//.bend per law, proof//base.bend and proof/base.bend for shared lemmas): - PROOF.bend imports LAWS.bend and holds no def; - every law has a fill `def .NAME(` in a proof/ file PROOF.bend imports, where is that file's name for the law's file; - a file proof//.bend but base.bend fills law ; when lives in laws/.bend or laws//
.bend, is (the folders a file past 1,000 lines becomes, proof/base/, proof//base/ and proof///, hold lemmas); - no laws/ file but those LAWS.bend imports; no proof*.bend file but PROOF.bend; - no other .bend file declares a `law` or fills one. Exit 1 with the list of gaps.""" import re import subprocess import sys from pathlib import Path IMPORT = re.compile(r"^import\s+(\S+\.bend)\s+as\s+(\S+)", re.M) LAW = re.compile(r"^law (\w+)", re.M) DEF = re.compile(r"^def ", re.M) SKIP = {".git", "target", "node_modules", "vendor"} def bend_files(root: Path) -> list[Path]: """Every .bend file of the tree: tracked and untracked, not ignored.""" try: out = subprocess.run( ["git", "ls-files", "-co", "--exclude-standard", "--", "*.bend"], cwd=root, capture_output=True, text=True, check=True, ).stdout return sorted(root / p for p in out.splitlines() if (root / p).exists()) except (OSError, subprocess.CalledProcessError): return sorted( p for p in root.rglob("*.bend") if not SKIP & set(p.relative_to(root).parts) ) def imports(text: str, path: Path) -> dict[Path, str]: """The files `text` (the file at `path`) imports, with their aliases.""" return {(path.parent / p).resolve(): a for p, a in IMPORT.findall(text)} def fills_of(text: str, alias: str) -> set[str]: return set(re.findall(rf"^def {re.escape(alias)}\.(\w+)\(", text, re.M)) def area_of(law_file: Path, laws_dir: Path) -> str: """The area of a laws/ file: for laws/.bend and laws//
.bend.""" return Path(law_file.relative_to(laws_dir).parts[0]).stem def gaps_of(root: Path) -> list[str]: root = root.resolve() laws_path = (root / "LAWS.bend").resolve() proof_path = (root / "PROOF.bend").resolve() laws_dir = (root / "laws").resolve() proof_dir = (root / "proof").resolve() gaps = [] if not laws_path.exists() or not proof_path.exists(): return gaps + ["LAWS.bend and PROOF.bend must both exist at the root"] # the files that may declare laws: LAWS.bend and the laws/ files it imports law_files = [laws_path] + [p for p in imports(laws_path.read_text(), laws_path) if laws_dir in p.parents] home = {} # law -> the file that declares it for lf in law_files: if not lf.exists(): continue for n in LAW.findall(lf.read_text()): home[n] = lf proof = proof_path.read_text() if laws_path not in imports(proof, proof_path): gaps.append("PROOF.bend: does not import ./LAWS.bend") if DEF.search(proof): gaps.append("PROOF.bend: holds a def; it only imports the proof/ files") # the proof/ files PROOF.bend imports: they may fill laws proof_files = {p for p in imports(proof, proof_path) if proof_dir in p.parents} filled = set() for f in bend_files(root): rf = f.resolve() if rf in (laws_path, proof_path): continue rel = f.relative_to(root).as_posix() text = f.read_text() if laws_dir in rf.parents and rf not in law_files: gaps.append(f"{rel}: LAWS.bend does not import it") if f.name.lower().startswith("proof"): gaps.append(f"{rel}: every proof and proof lemma lives in a proof/ file") if rf not in law_files: for n in LAW.findall(text): gaps.append(f"{rel}: declares law {n}; every law lives in LAWS.bend or laws/") mine = set() for p, alias in imports(text, f).items(): if p in law_files: mine |= {n for n in fills_of(text, alias) if home.get(n) == p} for n in sorted(mine): if rf in proof_files: filled.add(n) else: gaps.append(f"{rel}: proves law {n}; every proof lives in a proof/ file PROOF.bend imports") if rf in proof_files and rf.parent.parent == proof_dir and "base" not in (rf.stem, rf.parent.name): law = rf.stem if law not in mine: gaps.append(f"{rel}: does not fill law {law}") elif home[law].parent != root and area_of(home[law], laws_dir) != rf.parent.name: a = area_of(home[law], laws_dir) gaps.append(f"{rel}: law {law} lives in {home[law].relative_to(root).as_posix()}; its proof in proof/{a}/") for n in sorted(set(home) - filled): gaps.append(f"{home[n].relative_to(root).as_posix()}: law {n} has no fill in a proof/ file PROOF.bend imports") return gaps if __name__ == "__main__": gaps = gaps_of(Path(sys.argv[1] if len(sys.argv) > 1 else ".")) print("\n".join(gaps) if gaps else "every law is in LAWS.bend or laws/ and proven in proof/") sys.exit(1 if gaps else 0)