200 lines
7.5 KiB
Python
200 lines
7.5 KiB
Python
#!/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/<date>-<key>*.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/<date>-<key>*.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))
|