ledger: troupe tree census extract at HEAD 484821b
This commit is contained in:
1 parent
484821b232
commit
67884625b8
5 files changed
+288
No files matched your search
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env bash
|
||||
# CHECK-troupe-tree-census
|
||||
#
|
||||
# Independently re-derives the top-level directory census of the troupe repo for a
|
||||
# single pinned revision and compares it byte-for-byte against the committed
|
||||
# artifact. Exit code is the verdict: 0 = correct, nonzero = wrong.
|
||||
#
|
||||
# The pinned revision is read from troupe-tree-census-2026-11-02.md (the "pin
|
||||
# revision" line), so the check is deterministic: it grades a fixed revision
|
||||
# rather than a moving HEAD, and it never imports the extractor or hardcodes
|
||||
# numbers. Size derivation uses `git cat-file -s` (a different read path than the
|
||||
# `ls-tree -l` size column used by the extractor).
|
||||
set -u
|
||||
|
||||
REPO="/root/troupe-standup"
|
||||
MD="${REPO}/wizard-work/ledger/troupe-tree-census-2026-11-02.md"
|
||||
ARTIFACT="${REPO}/wizard-work/ledger/troupe-tree-census-2026-11-02.csv"
|
||||
WORK="$(mktemp -d)"
|
||||
trap 'rm -rf "$WORK"' EXIT
|
||||
|
||||
# --- 1. read the pinned revision from the md -----------------------------------
|
||||
REV="$(grep -oE '[0-9a-f]{40}' "$MD" | head -n1)"
|
||||
if [ -z "$REV" ]; then
|
||||
echo "CHECK: FAIL - no 40-hex pin revision found in the md"
|
||||
exit 2
|
||||
fi
|
||||
REV="$(git -C "$REPO" rev-parse "$REV")" || { echo "CHECK: FAIL - cannot resolve pin $REV"; exit 2; }
|
||||
|
||||
# --- 2. independent re-derivation of the census for the pin --------------------
|
||||
: > "${WORK}/recomputed.csv"
|
||||
python3 - "$WORK/recomputed.csv" "$REV" <<'PY'
|
||||
import subprocess, sys
|
||||
from collections import defaultdict
|
||||
|
||||
out, rev = sys.argv[1], sys.argv[2]
|
||||
counts = defaultdict(int)
|
||||
sizes = defaultdict(int)
|
||||
|
||||
ls = subprocess.run(
|
||||
["git", "-C", "/root/troupe-standup", "ls-tree", "-r", rev],
|
||||
capture_output=True, text=True, check=True,
|
||||
).stdout
|
||||
for line in ls.splitlines():
|
||||
if not line.strip():
|
||||
continue
|
||||
head, sep, path = line.partition("\t")
|
||||
if not sep:
|
||||
raise SystemExit("bad ls-tree line: missing tab")
|
||||
fhead = head.split()
|
||||
if len(fhead) < 3:
|
||||
raise SystemExit("bad ls-tree line: too few fields")
|
||||
sha = fhead[2]
|
||||
size = subprocess.run(
|
||||
["git", "-C", "/root/troupe-standup", "cat-file", "-s", sha],
|
||||
capture_output=True, text=True, check=True,
|
||||
).stdout.strip()
|
||||
try:
|
||||
size = int(size)
|
||||
except ValueError:
|
||||
size = 0
|
||||
top = "(root)" if "/" not in path else path.split("/", 1)[0]
|
||||
counts[top] += 1
|
||||
sizes[top] += size
|
||||
|
||||
with open(out, "w", encoding="utf-8", newline="\n") as fh:
|
||||
fh.write("top_level,files,sized_bytes\n")
|
||||
for top in sorted(counts):
|
||||
fh.write(f"{top},{counts[top]},{sizes[top]}\n")
|
||||
PY
|
||||
|
||||
# --- 3. byte-for-byte compare with the committed artifact -----------------------
|
||||
if ! diff -u "${WORK}/recomputed.csv" "${ARTIFACT}" > "${WORK}/diff.txt"; then
|
||||
echo "CHECK: FAIL (pin $REV) - recomputed census != artifact"
|
||||
cat "${WORK}/diff.txt"
|
||||
exit 1
|
||||
fi
|
||||
echo "CHECK: PASS (pin $REV) - recomputed census identical to artifact"
|
||||
|
||||
# --- 4. headline cross-checks against the pin revision -------------------------
|
||||
total_files=$(git -C "$REPO" ls-tree -r "$REV" | wc -l | tr -d ' ')
|
||||
total_bytes=$(git -C "$REPO" ls-tree -r -l "$REV" | awk '{print $4}' | paste -sd+ | bc)
|
||||
recomputed_files=$(cut -d, -f2 "${WORK}/recomputed.csv" | tail -n +2 | paste -sd+ | bc)
|
||||
recomputed_bytes=$(cut -d, -f3 "${WORK}/recomputed.csv" | tail -n +2 | paste -sd+ | bc)
|
||||
|
||||
if [ "$total_files" -ne "$recomputed_files" ]; then
|
||||
echo "CHECK: FAIL (pin $REV) - file count mismatch: git=$total_files recomputed=$recomputed_files"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$total_bytes" -ne "$recomputed_bytes" ]; then
|
||||
echo "CHECK: FAIL (pin $REV) - byte total mismatch: git=$total_bytes recomputed=$recomputed_bytes"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "CHECK: headline ok ($recomputed_files files, $recomputed_bytes bytes)"
|
||||
exit 0
|
||||
@@ -0,0 +1,38 @@
|
||||
import subprocess
|
||||
from collections import defaultdict
|
||||
|
||||
REPO = "/root/troupe-standup"
|
||||
|
||||
def parse(extra):
|
||||
counts = defaultdict(int)
|
||||
ls = subprocess.run(["git", "-C", REPO, "ls-tree", "-r", extra, "HEAD"],
|
||||
capture_output=True, text=True, check=True).stdout
|
||||
for line in ls.splitlines():
|
||||
if not line.strip():
|
||||
continue
|
||||
head, sep, path = line.partition("\t")
|
||||
fhead = head.split()
|
||||
top = "(root)" if "/" not in path else path.split("/", 1)[0]
|
||||
counts[top] += 1
|
||||
return counts
|
||||
|
||||
for extra in ["-l", "-r"]:
|
||||
c = parse(extra)
|
||||
print("EXTRA", extra, "groups:", dict(c))
|
||||
|
||||
ls = subprocess.run(["git", "-C", REPO, "ls-tree", "-r", "HEAD"],
|
||||
capture_output=True, text=True, check=True).stdout
|
||||
lines = [l for l in ls.splitlines() if l.strip()]
|
||||
print("total ls-tree lines:", len(lines))
|
||||
from collections import Counter
|
||||
first = Counter()
|
||||
rootfiles = 0
|
||||
for l in lines:
|
||||
path = l.partition("\t")[2]
|
||||
top = "(root)" if "/" not in path else path.split("/", 1)[0]
|
||||
first[top] += 1
|
||||
if "/" not in path:
|
||||
rootfiles += 1
|
||||
print("unique first-components:", sorted(first))
|
||||
print("root-group file count by direct count:", rootfiles)
|
||||
print("sum of groups:", sum(first.values()))
|
||||
@@ -0,0 +1,15 @@
|
||||
top_level,files,sized_bytes
|
||||
(root),31,2497350
|
||||
access,17,5414
|
||||
audits,2,15203
|
||||
career-lane,31,757169
|
||||
checkins,111,1198002
|
||||
decisions,5,12436
|
||||
docs,10,385225
|
||||
harness-reflections,12,33844
|
||||
inbox,1,9484
|
||||
notes,5,16982
|
||||
recoveries,63,1378660
|
||||
replies,434,2866416
|
||||
tools,27,928314
|
||||
wizard-work,4092,195498089
|
||||
|
@@ -0,0 +1,30 @@
|
||||
# Troupe repo -- top-level directory census (extract lane)
|
||||
|
||||
- **pin revision:** `484821b2325b2b72c5f85e942c63f5a667dc4cfb` (HEAD at generation)
|
||||
- **method:** `git ls-tree -r -l <pin>` -> group every tracked file by
|
||||
its top-level path component -> count files and sum blob sizes per group.
|
||||
Working tree excluded.
|
||||
- **headline:** 4841 tracked files, 205602588 bytes (~196.1 MiB), 14 top-level groups.
|
||||
|
||||
| top_level | files | sized_bytes |
|
||||
|---|---|---|
|
||||
| (root) | 31 | 2497350 |
|
||||
| access | 17 | 5414 |
|
||||
| audits | 2 | 15203 |
|
||||
| career-lane | 31 | 757169 |
|
||||
| checkins | 111 | 1198002 |
|
||||
| decisions | 5 | 12436 |
|
||||
| docs | 10 | 385225 |
|
||||
| harness-reflections | 12 | 33844 |
|
||||
| inbox | 1 | 9484 |
|
||||
| notes | 5 | 16982 |
|
||||
| recoveries | 63 | 1378660 |
|
||||
| replies | 434 | 2866416 |
|
||||
| tools | 27 | 928314 |
|
||||
| wizard-work | 4092 | 195498089 |
|
||||
|
||||
**Reading:** the repository is a ledger of lanes - `wizard-work/` alone
|
||||
holds 4092 files / 186.4 MB (~95% of the tree); the other 13 directories are
|
||||
thin metadata, check-ins, and queue tooling.
|
||||
|
||||
**Files:** `troupe_tree_census_2026_11_02.py` (extractor), `troupe-tree-census-2026-11-02.csv` (table), `CHECK-troupe-tree-census` (independent re-derivation grader).
|
||||
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Extract the committed tree of the troupe repo into a top-level directory census.
|
||||
|
||||
Extract lane ("ledger" bookkeeping): turn a repository tree into a table.
|
||||
Scope: a single committed revision (the pin), never the working tree.
|
||||
|
||||
Outputs (in the repo's ledger lane):
|
||||
troupe-tree-census-2026-11-02.csv one row per top-level entry (for the pin)
|
||||
troupe-tree-census-2026-11-02.md human-readable table + headline + pin
|
||||
CHECK-troupe-tree-census independent re-derivation grader for the pin
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
REPO = "/root/troupe-standup"
|
||||
OUT = os.path.join(REPO, "wizard-work/ledger")
|
||||
|
||||
# Pin this revision so the census is a reproducible snapshot, not a live pulse.
|
||||
PIN = subprocess.run(
|
||||
["git", "-C", REPO, "rev-parse", "HEAD"],
|
||||
capture_output=True, text=True, check=True,
|
||||
).stdout.strip()
|
||||
|
||||
|
||||
def git(*args):
|
||||
proc = subprocess.run(
|
||||
["git", "-C", REPO, *args], capture_output=True, text=True, check=True
|
||||
)
|
||||
return proc.stdout
|
||||
|
||||
|
||||
def derive():
|
||||
"""Return ordered list of (top_level, agg) for the pinned revision."""
|
||||
out = git("ls-tree", "-r", "-l", PIN)
|
||||
agg = {}
|
||||
for line in out.splitlines():
|
||||
if not line.strip():
|
||||
continue
|
||||
head, _, _ = line.partition("\t")
|
||||
fhead = head.split()
|
||||
if len(fhead) < 3:
|
||||
continue
|
||||
typ = fhead[1]
|
||||
try:
|
||||
size = int(fhead[3]) if len(fhead) >= 4 else 0
|
||||
except ValueError:
|
||||
size = 0
|
||||
path = line.split("\t", 1)[1]
|
||||
top = "(root)" if "/" not in path else path.split("/", 1)[0]
|
||||
kind = "file"
|
||||
key = (top, kind)
|
||||
entry = agg.setdefault(key, {"files": 0, "sized_bytes": 0})
|
||||
entry["files"] += 1
|
||||
if kind == "file":
|
||||
entry["sized_bytes"] += size
|
||||
|
||||
def sort_key(item):
|
||||
top, _ = item
|
||||
if top == "(root)":
|
||||
return ("\x00", top)
|
||||
return ("\x01", top)
|
||||
|
||||
return [(top, entry) for (top, _), entry in sorted(agg.items(), key=sort_key)]
|
||||
|
||||
|
||||
def write_csv(rows, path):
|
||||
with open(path, "w", encoding="utf-8", newline="\n") as fh:
|
||||
fh.write("top_level,files,sized_bytes\n")
|
||||
for top, entry in rows:
|
||||
fh.write(f"{top},{entry['files']},{entry['sized_bytes']}\n")
|
||||
|
||||
|
||||
def main():
|
||||
rows = derive()
|
||||
csv_path = os.path.join(OUT, "troupe-tree-census-2026-11-02.csv")
|
||||
write_csv(rows, csv_path)
|
||||
md_path = os.path.join(OUT, "troupe-tree-census-2026-11-02.md")
|
||||
with open(md_path, "w", encoding="utf-8") as fh:
|
||||
fh.write("# Troupe repo -- top-level directory census (extract lane)\n\n")
|
||||
fh.write("- **pin revision:** `%s` (HEAD at generation)\n" % PIN)
|
||||
fh.write("- **method:** `git ls-tree -r -l <pin>` -> group every tracked file by\n")
|
||||
fh.write(" its top-level path component -> count files and sum blob sizes per group.\n")
|
||||
fh.write(" Working tree excluded.\n")
|
||||
fh.write("- **headline:** %d tracked files, %d bytes (~%.1f MiB), %d top-level groups.\n\n"
|
||||
% (sum(e["files"] for _, e in rows),
|
||||
sum(e["sized_bytes"] for _, e in rows),
|
||||
sum(e["sized_bytes"] for _, e in rows) / 1048576.0,
|
||||
len(rows)))
|
||||
fh.write("| top_level | files | sized_bytes |\n|---|---|---|\n")
|
||||
for top, entry in rows:
|
||||
fh.write("| %s | %d | %d |\n" % (top, entry["files"], entry["sized_bytes"]))
|
||||
fh.write("\n**Reading:** the repository is a ledger of lanes - `wizard-work/` alone\n")
|
||||
fh.write("holds %d files / %.1f MB (~95%% of the tree); the other %d directories are\n"
|
||||
% (rows[13][1]["files"], rows[13][1]["sized_bytes"] / 1048576.0, len(rows) - 1))
|
||||
fh.write("thin metadata, check-ins, and queue tooling.\n\n")
|
||||
fh.write("**Files:** `troupe_tree_census_2026_11_02.py` (extractor), `"
|
||||
"troupe-tree-census-2026-11-02.csv` (table), `CHECK-troupe-tree-census` "
|
||||
"(independent re-derivation grader).\n" % ())
|
||||
print("rows:", len(rows))
|
||||
print("total_files:", sum(e["files"] for _, e in rows))
|
||||
print("total_bytes:", sum(e["sized_bytes"] for _, e in rows))
|
||||
print("pin:", PIN[:12])
|
||||
print("wrote", csv_path)
|
||||
print("wrote", md_path)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in new issue
Block a user