Files
troupe-standup/wizard-work/ledger/repo-2026-11-01-commit-census.py
T

137 lines
4.9 KiB
Python

#!/usr/bin/env python3
"""Extract a daily commit-author census for 2026-11-01 from the troupe-standup repo.
Lane: Extract (data extraction into a table).
Writes:
/root/.hermes/wizard-outputs/ledger/repo-2026-11-01-commit-census.tsv
/root/.hermes/wizard-outputs/ledger/repo-2026-11-01-commit-census.json
"""
import json
import subprocess
import sys
REPO = "/root/troupe-standup"
OUTDIR = "/root/.hermes/wizard-outputs/ledger"
DATE = "2026-11-01"
TSV = f"{OUTDIR}/repo-2026-11-01-commit-census.tsv"
JSON = f"{OUTDIR}/repo-2026-11-01-commit-census.json"
# Email -> canonical wizard name. Derived from the repo's own roster (WIZARDS-TABLE.md)
# and `.mailmap`; kept here as a transparent mapping.
WIZARD_BY_EMAIL = {
"timmy@stackchain.ai": "Timmy",
"timmy@alexanderwhitestone.com": "Timmy",
"timmy@agents.invalid": "Timmy",
"timmy@stackchain.invalid": "Timmy",
"timmy@hermes": "Timmy",
"timmy@stacktimmy.invalid": "Timmy",
"timmy@localhost": "Timmy",
"timmy@alexandrewswhitestone.com": "Timmy",
"timmy@stachain.ai": "Timmy",
"zephyrus-sparkborn@agents.invalid": "Zephyrus Sparkborn",
"zephyrus-sparkborn@users.noreply.forge.alexanderwhitestone.com": "Zephyrus Sparkborn",
"zephyrus-sparkborn@users.noreply.github.com": "Zephyrus Sparkborn",
"zephyrus-sparkborn@noreply.local": "Zephyrus Sparkborn",
"zephyrus@alexanderwhitestone.com": "Zephyrus Sparkborn",
"cassian@agents.invalid": "Cassian Assayborn",
"cassian@forge.local": "Cassian Assayborn",
"volt@agents.invalid": "Volt",
"volt@troupe.invalid": "Volt",
"volt@troupe.local": "Volt",
"volt@stackchain.local": "Volt",
"volt@alexanderwhitestone.com": "Volt",
"volt@stackchain.ai": "Volt",
"ledger@agents.invalid": "Ledger",
"forge@agents.invalid": "Forge",
"mercury@agents.invalid": "Mercury",
"merlin@agents.invalid": "Merlin",
"sol@agents.invalid": "Sol",
"quill@agents.invalid": "Quill",
"sphinx@agents.invalid": "Sphinx",
"null@agents.invalid": "Null",
"argus@agents.invalid": "Argus",
"argus@troupe": "Argus",
"sentinel@troupe-standup.internal": "Sentinel",
"beam@agents.invalid": "Beam",
"troupe-pipe@agents.invalid": "Troupe-pipe",
"pipe@localhost": "Pipe",
}
def git(*args):
return subprocess.run(
["git", "-C", REPO] + list(args), check=True, capture_output=True, text=True
).stdout
def main():
# name, email, authordate_iso, sha, subject (commits on DATE only)
rows = []
out = git(
"log",
"--format=%an%x09%ae%x09%aI%x09%H%x09%s",
"--no-renames",
).splitlines()
for line in out:
if not line:
continue
parts = line.split("\t")
if len(parts) != 5:
continue
name, email, adate, sha, subject = parts
if adate.startswith(DATE + "T"):
rows.append({"name": name, "email": email, "date": adate, "sha": sha, "subject": subject})
# group
groups = {}
for r in rows:
email = r["email"]
# strip a displayed typo that git recorded for Timmy (stacktimmy vs stachain)
wizard = WIZARD_BY_EMAIL.get(email, "Unmapped")
groups.setdefault(wizard, []).append(r)
total = len(rows)
total_commits = len(rows)
# verify the day's commit total against git itself
day_total = int(git(
"rev-list", "--count", f"--since={DATE}T00:00:00+00:00", f"--until={DATE}T23:59:59+00:00", "HEAD"
).strip())
if day_total != total:
print(f"WARN: day_total({day_total}) != in-memory rows({total})", file=sys.stderr)
# order: by commits desc, then name
order = sorted(groups.items(), key=lambda kv: (-len(kv[1]), kv[0]))
lines = ["wizard\tcommits\tfirst_commit\tlast_commit\tlatest_sha\tsubjects"]
for wizard, rs in order:
rs_sorted = sorted(rs, key=lambda r: r["date"])
subjects = "; ".join(f"{r['date'][11:19]} {r['subject']}" for r in rs_sorted)
lines.append(
f"{wizard}\t{len(rs)}\t{rs_sorted[0]['date']}\t{rs_sorted[-1]['date']}\t"
f"{rs_sorted[-1]['sha']}\t{subjects}"
)
text = "\n".join(lines) + "\n"
with open(TSV, "w", encoding="utf-8") as f:
f.write(text)
with open(JSON, "w", encoding="utf-8") as f:
json.dump(
[
{
"wizard": w,
"commits": len(rs),
"first_commit": min(r["date"] for r in rs),
"last_commit": max(r["date"] for r in rs),
"latest_sha": max(rs, key=lambda r: r["date"])["sha"],
"subjects": [f"{r['date'][11:19]} {r['subject']}" for r in sorted(rs, key=lambda r: r['date'])],
}
for w, rs in order
],
f,
indent=2,
)
print(text)
print(f"# rows={total} total_commits={total_commits} day_total_git={day_total} files={TSV}, {JSON}", file=sys.stderr)
if __name__ == "__main__":
main()