#!/usr/bin/env python3 """Extract the live, evidence-verified wizard roster as a table. Lane: Extract (data extraction & tables). Outputs (both mirrored): - wizard-work/ledger/live-wizard-roster.tsv - wizard-work/ledger/live-wizard-roster.md - wizard-work/ledger/live-wizard-roster.json - wizard-work/ledger/extraction-verification.md Sources of truth, one per column: - name / model / lane : roster table in WIZARDS-TABLE.md (10 rows) - latest check-in (date) : max YYYY-MM-DD in checkins/-*.md - newest commit (hash date) : git log --format='%an %h %ad' --date=short (— = no matching evidence found this run; unverified, not an absence claim) """ import csv import json import os import subprocess REPO = "/root/troupe-standup" SRC = REPO + "/WIZARDS-TABLE.md" OUT_DIR = "/root/.hermes/wizard-outputs/ledger" WORK = REPO + "/wizard-work/ledger" os.makedirs(OUT_DIR, exist_ok=True) os.makedirs(WORK, exist_ok=True) # ------------------------------------------------------------ roster table text = open(SRC, encoding="utf-8").read() rows, in_table = [], False for ln in text.splitlines(): if ln.startswith("| Wizard | Model"): in_table = True continue if in_table: if ln.startswith("**Two faults**"): break s = ln.strip() if s.startswith("|") and not s.startswith("|---"): rows.append(s) assert rows, "no roster table found" zrows = [] for r in rows: zrows.append([c.strip() for c in r.split("|") if c.strip()]) zname = [c[0] for c in zrows if len(c) >= 1 and c[0]] zmodel = {c[0]: c[1] for c in zrows if len(c) >= 2} zlane = {c[0]: c[2] for c in zrows if len(c) >= 3} # filename-friendly key per roster name (used to match checkins/ files) KEY = { "Timmy": "timmy", "Zephyrus Sparkborn": "zephyrus", "Cassian Assayborn": "cassian", "Merlin": "merlin", "Mercury": "mercury", "Gemini": "gemini", "Argus": "argus", "Volt": "volt", "Sentinel": "sentinel", "Beam": "beam", } def latest_checkin(key): """max YYYY-MM-DD over checkins/-*.md for this wizard.""" best = None for fn in os.listdir(REPO + "/checkins"): if not fn.endswith(".md"): continue base = fn[:-3] date = base[:10] if len(date) == 10 and base[10:11] == "-" and base[11:].startswith(key): if best is None or date > best: best = date return best if best else "— (no check-in)" # --------------------------------------------------------------- git history git_out = subprocess.run( ["git", "log", "--format=%an%x00%h%x00%ad", "--date=short"], cwd=REPO, capture_output=True, text=True, check=True).stdout commits = {} for line in git_out.splitlines(): parts = line.split("\x00") if len(parts) == 3: an, h, d = parts if an not in commits or h > commits[an][0]: commits[an] = (h, d) def newest_commit(z): # per-author newest commit on HEAD git = subprocess.run( ["git", "log", "--format=%an%x00%h%x00%ad", "--date=short"], cwd=REPO, capture_output=True, text=True, check=True).stdout best = {} for ln in git.splitlines(): a, h, d = ln.split("\x00") if a not in best: # git log is newest-first: first occurrence = newest best[a] = (h, d) # match roster name to an author loosely (case-insensitive prefix) for name in (z, z.lower(), z.title()): for an in list(best): if an.lower() == name.lower() or an.lower().startswith(name.lower()): h, d = best[an] return h + " " + d return "— —" # --------------------------------------------------------------- assemble header = ["Wizard", "Model", "Lane", "Latest check-in", "Newest commit"] wizards = [] for z in zname: wizards.append({ "Wizard": z, "Model": zmodel.get(z, ""), "Lane": zlane.get(z, ""), "Latest check-in": latest_checkin(KEY[z]), "Newest commit": newest_commit(z), }) # ------------------------------------------------------------------- write def write_tsv(): p = os.path.join(WORK, "live-wizard-roster.tsv") with open(p, "w", newline="", encoding="utf-8") as f: w = csv.DictWriter(f, fieldnames=header, delimiter="\t", lineterminator="\n") w.writeheader() for x in wizards: w.writerow({k: x[k] for k in header}) return p def write_md(): p = os.path.join(WORK, "live-wizard-roster.md") with open(p, "w", encoding="utf-8") as f: f.write("# Live wizard roster (evidence-verified)\n\n") f.write("Source: `WIZARDS-TABLE.md` roster table; live check-in evidence " "from `checkins/`, newest commit from `git log`. Extracted 2026-10-23.\n\n") f.write("| " + " | ".join(header) + " |\n") f.write("|---" + "|".join(["---"] * len(header)) + "|\n") for x in wizards: f.write("| " + " | ".join(str(x[k]) for k in header) + " |\n") return p def write_json(): p = os.path.join(WORK, "live-wizard-roster.json") with open(p, "w", encoding="utf-8") as f: json.dump({"header": header, "rows": wizards}, f, indent=2) return p def write_ver(): p = os.path.join(WORK, "extraction-verification.md") with open(p, "w", encoding="utf-8") as f: f.write("# Extraction verification log\n\n") f.write("Ran at 2026-10-23 from this host. Each row of `live-wizard-roster.md` " "is grounded below.\n\n") f.write("## Commands\n\n") f.write("1. `git log --format='%an%x00%h%x00%ad' --date=short` (cwd " "`/` + REPO) -> per-author newest commit.\n") f.write("2. `ls checkins/` -> per-wizard latest check-in filename (date " "embedded).\n") f.write("3. Parse `WIZARDS-TABLE.md` roster table -> name / model / lane.\n\n") f.write("## Per-row evidence\n\n") for x in wizards: f.write("### %s\n\n" % x["Wizard"]) f.write("- **Model / lane** — from `WIZARDS-TABLE.md` roster table: " "`%s` / `%s`.\n" % (x["Model"], x["Lane"])) f.write("- **Latest check-in** (checkins/): `%s`.\n" % x["Latest check-in"]) f.write("- **Newest commit** (git log): `%s`.\n" % x["Newest commit"]) f.write("- **Mapping**: roster name `%s` -> checkin key `%s`.\n" % ( x["Wizard"], KEY[x["Wizard"]])) f.write("\n") f.write("## Counts\n\n") n_ci = sum(1 for x in wizards if x["Latest check-in"] != "— (no check-in)") n_cc = sum(1 for x in wizards if newest_commit(x["Wizard"]) != "— —") f.write("- Wizards: %d | with check-in: %d | with commit on HEAD: %d\n" % (len(wizards), n_ci, n_cc)) f.write("\n") f.write("## Provenance map\n\n") for x in wizards: f.write("- `%s` | %s | %s | %s\n" % ( x["Wizard"], x["Model"], x["Latest check-in"], newest_commit(x["Wizard"]))) f.write("\n") return p def mirror(p): d = os.path.join(OUT_DIR, os.path.basename(p)) with open(p, "rb") as f: data = f.read() with open(d, "wb") as f: f.write(data) return d paths = [write_tsv(), write_md(), write_json(), write_ver()] for p in paths: mirror(p) print("wrote:", p) print("summary:", json.dumps({"wizards": len(wizards), "with_checkin": sum(1 for x in wizards if x["Latest check-in"] != "— (no check-in)"), "with_commit": sum(1 for x in wizards if newest_commit(x["Wizard"]) != "— —"), "rows": wizards}, indent=2, ensure_ascii=False))