#!/usr/bin/env python3 """Extract the live wizard roster (2026-10-29) as a data table. Lane: Extract (data extraction and tables). Sources (real, from this checkout host): 1. WIZARDS-TABLE.md -- canonical wizard roster (name, model, lane). 2. checkins/ -- newest check-in per wizard (max date in filename). 3. git log -- newest commit per wizard (author-date). Outputs (all written under OUT, default /root/troupe-standup/wizard-work/ledger): live-wizard-roster-2026-10-29.tsv -- summary table (TSV) live-wizard-roster-2026-10-29.csv -- full file manifest (CSV) live-wizard-roster-2026-10-29.md -- markdown table (human artifact) live-wizard-roster-2026-10-29.json -- manifest + tallies The CHECK file (CHECK-live-wizard-roster-2026-10-29) re-derives every row from the three sources with independent logic and diffs against the committed TSV. The verdict is the script's exit code: 0 = correct, nonzero = wrong. """ import csv import json import os import subprocess from collections import defaultdict from datetime import datetime, timezone from pathlib import Path REPO_ROOT = Path(os.environ.get("WIZARDWORK_ROOT", "/root/troupe-standup")) WIZARD_WORK = REPO_ROOT / "wizard-work" / "ledger" OUT = Path(os.environ.get("WIZARD_ROSTER_OUT", str(WIZARD_WORK))) TABLE = REPO_ROOT / "WIZARDS-TABLE.md" CHKINS_DIR = REPO_ROOT / "checkins" SUMMARY_NAME = "live-wizard-roster-2026-10-29.tsv" MANIFEST_NAME = "live-wizard-roster-2026-10-29.csv" MD_NAME = "live-wizard-roster-2026-10-29.md" JSON_NAME = "live-wizard-roster-2026-10-29.json" # Canonical wizard names, fixed by WIZARDS-TABLE.md roster table. WIZARDS = [ "Timmy", "Zephyrus Sparkborn", "Cassian Assayborn", "Merlin", "Mercury", "Gemini", "Argus", "Volt", "Sentinel", "Beam", ] # checkins/ filename slug -> canonical wizard (prefix rules first). def slug_to_wizard(slug): if slug.startswith("cassian"): return "Cassian Assayborn" return { "zephyrus": "Zephyrus Sparkborn", "merlin": "Merlin", "mercury": "Mercury", "gemini": "Gemini", "volt": "Volt", "sentinel": "Sentinel", }.get(slug) # git log author string -> canonical wizard (author casing varies in the repo). AUTHOR_TO_WIZARD = {} for _w in WIZARDS: for _a in [_w.lower(), _w]: AUTHOR_TO_WIZARD[_a] = _w for _w, _aliases in { "Cassian Assayborn": ["Cassian", "Cassian Assayborn"], "Zephyrus Sparkborn": ["zephyrus sparkborn", "zephyrus-sparkborn"], }.items(): for _a in _aliases: AUTHOR_TO_WIZARD[_a.lower()] = _w def canonical_wizard(author): return AUTHOR_TO_WIZARD.get(author.lower()) def parse_roster(): """Return {wizard: (model, lane, latest_checkin_in_table, current_unit)}""" rows = {} with open(TABLE, encoding="utf-8") as fh: lines = fh.read().splitlines() in_table = False for line in lines: if line.startswith("| Wizard |"): in_table = True continue if in_table: if not line.startswith("|"): break cells = [c.strip() for c in line.split("|") if c.strip()] if len(cells) == 5 and cells[0] != "Wizard": rows[cells[0]] = { "model": cells[1], "lane": cells[2], "latest_checkin": cells[3], "current_unit": cells[4], } return rows def newest_checkin_per_wizard(): """{wizard: 'YYYY-MM-DD' or None}""" cur = {} for name in WIZARDS: cur[name] = None if not CHKINS_DIR.is_dir(): return cur for fh in CHKINS_DIR.iterdir(): if not fh.is_file() or not fh.name.endswith(".md"): continue parts = fh.name[:-3].split("-", 3) if len(parts) < 4: continue date, slug = "-".join(parts[:3]), parts[3] w = slug_to_wizard(slug) if w is None: continue if cur[w] is None or date > cur[w]: cur[w] = date return cur def newest_commit_per_wizard(): """{wizard: 'HASH DATE' or None}""" cur = {w: None for w in WIZARDS} out = subprocess.run( ["git", "log", "--format=%an%x00%H%x00%ad", "--date=short"], cwd=REPO_ROOT, capture_output=True, text=True, ).stdout best = defaultdict(lambda: None) # wizard -> (date, hash) for line in out.splitlines(): if "\x00" not in line: continue author, hsh, date = line.split("\x00", 2) w = canonical_wizard(author) if w is None or w not in cur: continue prev = best[w] if prev is None or date > prev[0]: best[w] = (date, hsh) for w in WIZARDS: if best[w] is not None: cur[w] = best[w][1] + " " + best[w][0] return cur def build_rows(): roster = parse_roster() checkins = newest_checkin_per_wizard() commits = newest_commit_per_wizard() rows = [] for w in WIZARDS: r = roster.get(w, {}) rows.append({ "wizard": w, "model": r.get("model", "—"), "lane": r.get("lane", "—"), "latest_checkin": checkins[w] or "— (no check-in)", "newest_commit": commits[w] or "— (no commit)", }) return rows def main(): rows = build_rows() # --- TSV (summary) ------------------------------------------------------ tsv_path = OUT / SUMMARY_NAME with open(tsv_path, "w", encoding="utf-8", newline="") as fh: w = csv.writer(fh, delimiter="\t", lineterminator="\n") w.writerow(["Wizard", "Model", "Lane", "Latest check-in", "Newest commit"]) for r in rows: w.writerow([r["wizard"], r["model"], r["lane"], r["latest_checkin"], r["newest_commit"]]) # --- CSV manifest --------------------------------------------------------- manifest_path = OUT / MANIFEST_NAME with open(manifest_path, "w", encoding="utf-8", newline="") as fh: w = csv.writer(fh, delimiter=",", lineterminator="\n") w.writerow(["source", "path", "kind", "bytes", "modified_utc"]) for label, p in [ ("table", TABLE), ("checkins", CHKINS_DIR), ("git", REPO_ROOT / ".git"), ]: if p.is_dir(): for f in sorted(p.rglob("*")): if f.is_file(): wt = datetime.fromtimestamp(f.stat().st_mtime, timezone.utc) w.writerow([label, str(f.relative_to(REPO_ROOT)), "file", f.stat().st_size, wt.strftime("%Y-%m-%dT%H:%M:%SZ")]) else: wt = datetime.fromtimestamp(p.stat().st_mtime, timezone.utc) if p.exists() else None w.writerow([label, str(p), "file", p.stat().st_size if p.exists() else 0, wt.strftime("%Y-%m-%dT%H:%M:%SZ") if wt else ""]) # --- MD ------------------------------------------------------------------- md_path = OUT / MD_NAME with open(md_path, "w", encoding="utf-8") as fh: fh.write("# Live wizard roster (2026-10-29)\n\n") fh.write("Evidence: roster from `WIZARDS-TABLE.md`; latest check-in from " "`checkins/` files; newest commit from `git log`.\n\n") fh.write("| Wizard | Model | Lane | Latest check-in | Newest commit |\n") fh.write("|--------|-------|------|-----------------|---------------|\n") for r in rows: fh.write(f"| {r['wizard']} | {r['model']} | {r['lane']} " f"| {r['latest_checkin']} | {r['newest_commit']} |\n") fh.write("\n*Last refreshed 2026-10-29 (UTC).*\n") # --- JSON manifest ---------------------------------------------------------- counts = {"check-ins": sum(1 for r in rows if r["latest_checkin"] != "— (no check-in)"), "newest-commits": sum(1 for r in rows if r["newest_commit"] != "— (no commit)"), "rows": len(rows)} json_path = OUT / JSON_NAME with open(json_path, "w", encoding="utf-8") as fh: json.dump({"extracted_at_utc": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), "source": "WIZARDS-TABLE.md + checkins/ + git log", "rows": rows, "counts": counts}, fh, indent=2) print(f"wrote {tsv_path}") print(f"wrote {manifest_path}") print(f"wrote {md_path}") print(f"wrote {json_path}") for r in rows: print(f" {r['wizard']:22s} checkin={r['latest_checkin']:22s} commit={r['newest_commit']}") if __name__ == "__main__": main()