#!/usr/bin/env python3 """build_engine_verses.py — the Talmudic Engine's verse facts, derived from L1. Architecture §3.4: verse(Book, Chapter, Verse, HebrewRoots, Gloss) is "the reasoning layer's view of the specification — not a replacement for L1". Until 2026-10-19 that view was 32 hand-written verses, so any root the engine knew lit the same handful and every reading cited the same list. This script writes talmudic_engine/src/specification/l1_verses.pl from l1/torah — every verse, its roots as the engine names them (Hebrew shoresh → letter names, finals folded: ברא → bet_resh_aleph), the consonantal text as the gloss — and records the SHA-256 of the L1 files it read in the header, so the facts can be checked against the scroll. scripts/build_engine_verses.py # write the facts scripts/build_engine_verses.py --check # exit 1 if the facts do not match L1 as it stands Devarim 4:2 — nothing added, nothing taken: the engine reads the scroll's roots, not the builder's memory of them. """ import argparse, hashlib, json, os, sys ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) L1 = os.path.join(ROOT, "l1", "torah") OUT = os.path.join(ROOT, "talmudic_engine", "src", "specification", "l1_verses.pl") BOOKS = ["bereishit", "shemot", "vayikra", "bamidbar", "devarim"] NAMES = { "א": "aleph", "ב": "bet", "ג": "gimel", "ד": "dalet", "ה": "he", "ו": "vav", "ז": "zayin", "ח": "chet", "ט": "tet", "י": "yod", "כ": "kaf", "ך": "kaf", "ל": "lamed", "מ": "mem", "ם": "mem", "נ": "nun", "ן": "nun", "ס": "samekh", "ע": "ayin", "פ": "pe", "ף": "pe", "צ": "tsadi", "ץ": "tsadi", "ק": "qof", "ר": "resh", "ש": "shin", "ת": "tav", } def root_atom(shoresh): if not shoresh: return None names = [NAMES.get(c) for c in shoresh] if not names or any(n is None for n in names): return None return "_".join(names) def quote(s): return "'" + s.replace("\\", "\\\\").replace("'", "\\'") + "'" def verse_files(): out = [] for book in BOOKS: bdir = os.path.join(L1, book) for ch in sorted((d for d in os.listdir(bdir) if d.isdigit()), key=int): cdir = os.path.join(bdir, ch) for f in sorted((f for f in os.listdir(cdir) if f.endswith(".json") and f[:-5].isdigit()), key=lambda x: int(x[:-5])): out.append((book, int(ch), int(f.split(".")[0]), os.path.join(cdir, f))) return out def build(): h = hashlib.sha256() facts = [] nverses = nroots = 0 for book, ch, vs, path in verse_files(): data = open(path, "rb").read() h.update(f"{book}/{ch}/{vs}\n".encode()) h.update(data) d = json.loads(data) roots = [] for w in d.get("words", []): a = root_atom(w.get("shoresh")) if a and a not in roots: roots.append(a) gloss = " ".join(w.get("consonants", "") for w in d.get("words", []) if w.get("consonants")) facts.append(f"verse({book}, {ch}, {vs}, [{', '.join(roots)}], {quote(gloss)}).") nverses += 1 nroots += len(roots) header = f"""%% l1_verses.pl — GENERATED by scripts/build_engine_verses.py from l1/torah. %% Do not edit: regenerate. verse(Book, Chapter, Verse, Roots, Gloss) for %% every verse of L1; Roots are the engine's letter-name atoms derived from %% each word's shoresh (finals folded), once per verse in word order; Gloss %% is the consonantal text. Included by torah_text.pl. %% %% l1_sha256: {h.hexdigest()} %% verses: {nverses} root_occurrences: {nroots} """ return header + "\n".join(facts) + "\n", h.hexdigest(), nverses def main(): ap = argparse.ArgumentParser() ap.add_argument("--check", action="store_true") a = ap.parse_args() text, digest, n = build() if a.check: try: current = open(OUT, encoding="utf-8").read() except FileNotFoundError: print(f"engine verses: {OUT} missing — run scripts/build_engine_verses.py", file=sys.stderr) sys.exit(1) if current != text: print(f"engine verses: {OUT} does not match L1 (l1_sha256 now {digest[:12]}…) — regenerate", file=sys.stderr) sys.exit(1) print(f"engine verses: match L1 ({n} verses, l1_sha256 {digest[:12]}…)") return with open(OUT, "w", encoding="utf-8") as f: f.write(text) print(f"wrote {OUT}: {n} verses, l1_sha256 {digest[:12]}…") if __name__ == "__main__": main()