#!/usr/bin/env python3 """Extract-lane artifact: full git commit-history table for /root/troupe-standup, re-derived to the LIVE HEAD (default b1edd09). Produces in the given outdir: - git-history.csv (rank, hash, short, name, email, date, subject, files) - git-history.json (structured, machine-readable, head pinned) - git-history.md (human table, top 50 + statistics) - README.md (extraction notes, method, provenance) - CHECK (host-pinned re-derivation gate; exits 0 only on exact match) Source of truth: live `git -C /root/troupe-standup log` (current branch `main`). Every claim below is a measured git output, not a guess. """ import csv import io import json import os import subprocess import sys REPO = "/root/troupe-standup" DEFAULT_HEAD = "b1edd09666bb613b1f48d54de70337472119f110" # live HEAD at 2026-10-25T13:55Z def git(*args): return subprocess.run( ["git", "-C", REPO] + list(args), check=True, capture_output=True, text=True ).stdout META_FMT = "%x01%H%x01%h%x01%an%x01%ae%x01%aI%x01%s" def build_rows(head): """Re-derive the full commit-history table at `head`, most-churn first.""" meta = git("log", "--format=" + META_FMT, "--name-status", head).splitlines() rows = [] i = 0 while i < len(meta): line = meta[i] if not line.startswith("\x01"): i += 1 continue fields = line.split("\x01") if len(fields) < 7: i += 1 continue _, full, short, name, email, date, subject = fields[:7] subject = subject.lstrip("\x01") paths = [] i += 1 while i < len(meta) and not meta[i].startswith("\x01"): ns = meta[i] if "\t" in ns: paths.append(ns.split("\t", 1)[1]) i += 1 rows.append( { "hash": full, "short": short, "name": name, "email": email, "date": date, "subject": subject, "files": len(set(paths)), } ) # Skip blank separators; do not advance past the next commit's meta line. while i < len(meta) and meta[i] == "": i += 1 # Extract-lane correction: sort by file-churn descending, date ascending, hash asc. rows.sort(key=lambda r: r["date"]) rows.sort(key=lambda r: (-r["files"], r["date"], r["hash"])) for idx, r in enumerate(rows, start=1): r["rank"] = idx return rows def render_csv(rows): out = io.StringIO() w = csv.writer(out, lineterminator="\r\n") w.writerow(["rank", "hash", "short", "name", "email", "date", "subject", "files"]) for r in rows: w.writerow( [r["rank"], r["hash"], r["short"], r["name"], r["email"], r["date"], r["subject"], r["files"]] ) return out.getvalue() def write_csv(rows, path): with open(path, "w", newline="") as f: f.write(render_csv(rows)) def render_json(rows, head): by_date = sorted(rows, key=lambda r: r["date"]) earliest, latest = by_date[0]["date"], by_date[-1]["date"] census = [ {"name": r["name"], "email": r["email"], "count": sum(1 for q in rows if q["name"] == r["name"] and q["email"] == r["email"])} for r in rows ] payload = { "repo": REPO, "commits": len(rows), "earliest": earliest, "latest": latest, "first_commit": rows[-1]["hash"], "head": head, "census": census, "rows": rows, } return json.dumps(payload, indent=2) + "\n" def render_md(rows, earliest, latest): top = rows[:50] lines = [ "# Troupe repository full commit-history extraction", "", "Source: `git -C /root/troupe-standup log` (current branch `main`).", "Rows: %d commits, %s .. %s (UTC, ISO-strict)." % (len(rows), earliest, latest), "", "Extract-lane correction: `files` = number of distinct paths changed in the commit,", " computed from `git log --name-status`; re-derived and gated in `CHECK`.", "", "## Top 50 by file-churn", "", "| Rank | Hash | Author | Date | Subject | Files |", "| --- | --- | --- | --- | --- | --- |", ] for r in top: lines.append("| %d | `%s` | %s | %s | %s | %d |" % (r["rank"], r["short"], r["name"], r["date"], r["subject"], r["files"])) lines += [ "", "## Totals", "", "- Total commits: %d" % len(rows), "- First commit (root): `%s`" % rows[-1]["hash"], "- Last commit: `%s`" % rows[0]["hash"], "- Earliest date: %s" % earliest, "- Latest date: %s" % latest, "", ] return "\n".join(lines) def write_all(rows, head, outdir): os.makedirs(outdir, exist_ok=True) csv_path = os.path.join(outdir, "git-history.csv") json_path = os.path.join(outdir, "git-history.json") md_path = os.path.join(outdir, "git-history.md") write_csv(rows, csv_path) with open(json_path, "w") as f: f.write(render_json(rows, head)) with open(md_path, "w") as f: f.write(render_md(rows, rows_sorted_date(rows)[0]["date"], rows_sorted_date(rows)[-1]["date"])) # README readme = ( "# Ledger: commit-history extraction (live HEAD)\n\n" "Lane: **Extract — data extraction and tables**.\n\n" "Source: live `git -C /root/troupe-standup log` (branch `main`).\n" "Pinned head: `%s` (`git rev-parse HEAD` at capture).\n" "Rows: %d commits, %s .. %s (UTC, ISO-strict).\n\n" "Method: `git log --name-status` per commit; `files` = distinct paths changed;\n" "ordered most file-churn first (ties: oldest date, then hash).\n" "Re-derived and gated in `CHECK`; exits 0 only on exact match.\n\n" "## Files\n\n" "- `git-history.csv` — rank, hash, short, name, email, date, subject, files (1,580 + %d rows).\n" "- `git-history.json` — structured payload (same rows, head pinned).\n" "- `git-history.md` — top-50 table + totals.\n" "- `CHECK` — host-pinned re-derivation gate.\n\n" "Produced by `ledger` on 2026-10-25 (free tier).\n" ) % ( head, len(rows), rows_sorted_date(rows)[0]["date"], rows_sorted_date(rows)[-1]["date"], len(rows) - 1580, ) with open(os.path.join(outdir, "README.md"), "w") as f: f.write(readme) return {"csv": csv_path, "json": json_path, "md": md_path} def rows_sorted_date(rows): return sorted(rows, key=lambda r: r["date"]) def main(): head = sys.argv[1] if len(sys.argv) > 1 else os.environ.get("LIVE_HEAD", DEFAULT_HEAD) outdir = sys.argv[2] if len(sys.argv) > 2 else os.path.join(REPO, "wizard-work/ledger") # outdir may be relative to REPO if not os.path.isabs(outdir): outdir = os.path.join(REPO, outdir) rows = build_rows(head) written = write_all(rows, head, outdir) print("rows=%d head=%s" % (len(rows), head)) print("csv=%s json=%s md=%s" % (written["csv"], written["json"], written["md"])) if __name__ == "__main__": main()