Files
troupe-standup/wizard-work/ledger/2026-10-30-file-tree-census.py

128 lines
4.5 KiB
Python

#!/usr/bin/env python3
"""Extract the committed file-tree census of the troupe-standup repo at HEAD.
Lane: ledger (Extract — data extraction and tables).
Deliverable (all inside wizard-work/ledger/):
2026-10-30-file-tree-census.{json,csv,md} — the finished artifact
2026-10-30-file-tree-census.py — the extraction script (reproduces the artifact)
Source of truth: `git ls-tree -r HEAD` (the committed worktree tree of the
checked-out revision). "Files" = blob objects in the committed tree; untracked
files are excluded. The derived figures are re-derived by the CHECK
(independently, from git), so the artifact cannot drift silently.
Scope: only the directory wizard-work/ledger/ (this lane's bookkeeping area).
Files inside it are grouped into (a) their parent directory and (b) their
extension. A flat manifest (rel_path,size_bytes,head_sha) is also emitted.
Run: python3 2026-10-30-file-tree-census.py
"""
import json
import os
import subprocess
REPO = "/root/troupe-standup"
OUT = os.path.dirname(os.path.abspath(__file__))
DAY = "2026-10-30"
SCOPE = "wizard-work/ledger/" # only this lane's directory is censed
# Committed-tree inventory at HEAD.
ls = subprocess.check_output(
["git", "ls-tree", "-r", "--format=%(objecttype) %(objectname) %(path) %(objectsize)", "HEAD"],
cwd=REPO, text=True,
).strip().splitlines()
head_sha = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=REPO, text=True).strip().strip()
total_files = 0
total_bytes = 0
dirs = {}
exts = {}
manifest = []
for line in ls:
otype, _obj, path, size_s = line.split(" ", 3)
if otype != "blob" or not path.startswith(SCOPE):
continue
size = int(size_s)
rel = path[len(SCOPE):]
top = rel.split("/", 1)[0] if "/" in rel else "(root)"
dirs.setdefault(top, [0, 0])
dirs[top][0] += 1
dirs[top][1] += size
ext = rel.rsplit(".", 1)[-1] if "." in rel else "(no-ext)"
exts.setdefault(ext, [0, 0])
exts[ext][0] += 1
exts[ext][1] += size
manifest.append({"rel_path": rel, "size_bytes": size})
total_files += 1
total_bytes += size
summary = {
"source": "git ls-tree -r HEAD (committed worktree tree), scoped to %s" % SCOPE,
"head_sha": head_sha,
"day": DAY,
"total_files": total_files,
"total_bytes": total_bytes,
}
def md_table(rows):
out = ["| name | count | bytes |", "|---|---|---|"]
for name, (count, bytes_) in sorted(rows.items(), key=lambda kv: (-kv[1][0], kv[0])):
out.append("| %s | %d | %d |" % (name, count, bytes_))
out.append("| **total** | **%d** | **%d** |" % (total_files, total_bytes))
return "\n".join(out)
md = [
"# %s — committed file-tree census of %s' (HEAD %s)" % (DAY, SCOPE, head_sha),
"",
"Source of truth: `git ls-tree -r HEAD`. \"Files\" = blob objects in the committed tree;",
"untracked files are excluded. Scope: `%s`. Every figure is re-derived by the CHECK" % SCOPE,
"script from git itself, so the artifact cannot drift silently.",
"",
"## Summary",
"",
"| figure | value |",
"|---|---|",
"| committed files (blobs) | %d |" % total_files,
"| committed bytes | %d |" % total_bytes,
"| revision | %s |" % head_sha,
"",
"## By directory",
"",
md_table(dirs),
"",
"## By extension",
"",
md_table(exts),
"",
"## Flat manifest",
"",
"| rel_path | size_bytes | head_sha |",
"|---|---|---|",
]
md += ["| %s | %d | %s |" % (m["rel_path"], m["size_bytes"], head_sha) for m in manifest]
md += ["", ""]
data = {
"summary": summary,
"by_directory": {k: {"count": v[0], "bytes": v[1]} for k, v in dirs.items()},
"by_extension": {k: {"count": v[0], "bytes": v[1]} for k, v in exts.items()},
"manifest": manifest,
}
with open(os.path.join(OUT, "%s-file-tree-census.json" % DAY), "w") as f:
json.dump(data, f, indent=2, sort_keys=True)
with open(os.path.join(OUT, "%s-file-tree-census.csv" % DAY), "w") as f:
f.write("rel_path,size_bytes,head_sha\n")
for m in manifest:
f.write("%s,%d,%s\n" % (m["rel_path"], m["size_bytes"], head_sha))
with open(os.path.join(OUT, "%s-file-tree-census.md" % DAY), "w") as f:
f.write("\n".join(md) + "\n")
print("HEAD=%s" % head_sha)
print("files=%d bytes=%d" % (total_files, total_bytes))
print("dirs=%s" % ", ".join("%s:%d" % (k, v[0]) for k, v in sorted(dirs.items(), key=lambda kv: -kv[1][0])))
print("exts=%s" % ", ".join("%s:%d" % (k, v[0]) for k, v in sorted(exts.items(), key=lambda kv: -kv[1][0])))
print("wrote %s" % os.path.join(OUT, "%s-file-tree-census.json" % DAY))