#!/usr/bin/env python3 """Build the Stackchain Library drops catalog from the source folder. Source: /root/stackchain-library-updates/drops (42 top-level entries: 39 day-drop folders, one probe JSON, and two global checksum files). One row per entry with a recursive file-type breakdown. Idempotent: re-running re-derives the same numbers from the same files. Artifacts written to BOTH: - /root/.hermes/profiles/ledger/wizard-outputs/ledger/ (profile outputs) - /root/troupe-standup/wizard-work/ledger/ (troupe repo) """ import csv import os from collections import Counter DROPS = "/root/stackchain-library-updates/drops" OUT_JSON = None # not used here OUT_CSV = "drops-catalog.csv" OUT_MD = "drops-catalog.md" TARGETS = ( "/root/.hermes/profiles/ledger/wizard-outputs/ledger", "/root/troupe-standup/wizard-work/ledger", ) # Explicit top-level entries that are files, not folders. FILE_ENTRIES = ( "2026-10-23-probe-blocks.json", "checksums.sha256", "checksums.sha256.NOTES.md", ) def classify(name): """Return a category key for a filename.""" low = name.lower() if low.endswith(".mp4"): return "mp4" if low.endswith(".json"): return "json" if low.endswith(".txt"): return "txt" if low.endswith(".md"): return "md" if low.endswith((".jpg", ".jpeg", ".png", ".gif", ".webp", ".webm")): return "jpg" return "other" def catalog_entry(path): is_file = os.path.isfile(path) if is_file: try: size = os.path.getsize(path) except OSError: size = 0 return { "path": os.path.basename(path), "kind": "file", "total_files": 1, "mp4": 0, "json": 0, "txt": 0, "md": 0, "jpg": 0, "other": 0, "size_bytes": size, } total = 0 counts = Counter() size = 0 for root, _dirs, files in os.walk(path): for fn in files: fp = os.path.join(root, fn) total += 1 counts[classify(fn)] += 1 try: size += os.path.getsize(fp) except OSError: pass return { "path": os.path.basename(path.rstrip("/")), "kind": "dir", "total_files": total, "mp4": counts["mp4"], "json": counts["json"], "txt": counts["txt"], "md": counts["md"], "jpg": counts["jpg"], "other": counts["other"], "size_bytes": size, } def build_rows(): rows = [] if os.path.isdir(DROPS): for name in sorted(os.listdir(DROPS)): p = os.path.join(DROPS, name) rows.append(catalog_entry(p)) for name in sorted(FILE_ENTRIES): p = os.path.join(DROPS, name) if os.path.isfile(p) and not any(r["path"] == name for r in rows): rows.append(catalog_entry(p)) return rows def write_csv(rows, target): path = os.path.join(target, OUT_CSV) with open(path, "w", encoding="utf-8", newline="") as f: writer = csv.DictWriter(f, fieldnames=list(rows[0].keys())) writer.writeheader() writer.writerows(rows) return path def write_md(rows, target): path = os.path.join(target, OUT_MD) lines = [] lines.append("# Stackchain library drops catalog") lines.append("") lines.append("Source: `%s`. One row per top-level entry (39 day-drop folders, " "one probe JSON, two checksum files). `md` counts reproduced " "`MANIFEST.md` files." % DROPS) lines.append("") lines.append("| Path | Kind | Files | MP4 | JSON | TXT | MANIFEST(md) | JPG | Other | Size(B) |") lines.append("|---|------|-------|-----|------|-----|--------------|-----|-----|---------|") for r in rows: lines.append( "| `{path}` | {kind} | {total} | {mp4} | {json} | {txt} | {md} | {jpg} | " "{other} | {size} |".format( path=r["path"], kind=r["kind"], total=r["total_files"], mp4=r["mp4"], json=r["json"], txt=r["txt"], md=r["md"], jpg=r["jpg"], other=r["other"], size=r["size_bytes"])) lines.append("") totals = {k: sum(r[k] for r in rows) for k in ("total_files", "mp4", "json", "txt", "md", "jpg", "other", "size_bytes")} lines.append("**Summary:** {n} entries · {files} total files · {md} with " "MANIFEST.md · {mp4} mp4 · {json} json · {txt} txt · {jpg} jpg · " "{other} other · {size} bytes.".format( n=len(rows), files=totals["total_files"], md=totals["md"], mp4=totals["mp4"], json=totals["json"], txt=totals["txt"], jpg=totals["jpg"], other=totals["other"], size=totals["size_bytes"])) lines.append("") with open(path, "w", encoding="utf-8") as f: f.write("\n".join(lines)) return path def main(): rows = build_rows() for target in TARGETS: os.makedirs(target, exist_ok=True) csv_path = write_csv(rows, target) md_path = write_md(rows, target) print("wrote", csv_path) print("wrote", md_path) print("total entries:", len(rows)) print("by kind:", {k: sum(1 for r in rows if r["kind"] == k) for k in ("dir", "file")}) if __name__ == "__main__": main()