ledger: daily commit census 2026-11-04 (extract lane)
This commit is contained in:
1 parent
95a5998a2d
commit
ade23054b6
7 files changed
+355
No files matched your search
+37
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
# CHECK -- daily commit census (Extract lane). Exit 0 iff the committed
|
||||
# daily-commit-census.csv is a byte-identical re-derivation from git at the rev
|
||||
# the committed extraction_manifest.json pins. Independent of the extractor --
|
||||
# pure bash + git + coreutils (sort/uniq/awk), no Python.
|
||||
set -u
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")" || exit 1
|
||||
SCRIPT_DIR="$(pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
||||
OUT="$REPO_ROOT/wizard-work/ledger/2026-11-04-daily-commit-census/daily-commit-census.csv"
|
||||
MANIFEST="$REPO_ROOT/wizard-work/ledger/2026-11-04-daily-commit-census/extraction_manifest.json"
|
||||
|
||||
test -s "$OUT" || { echo "FAIL: CSV missing or empty: $OUT" >&2; exit 1; }
|
||||
test -s "$MANIFEST" || { echo "FAIL: manifest missing: $MANIFEST" >&2; exit 1; }
|
||||
|
||||
HEAD_FULL="$(sed -n 's/.*"head_full": "\([^"]*\)".*/\1/p' "$MANIFEST")"
|
||||
test -n "$HEAD_FULL" || { echo "FAIL: no head_full in manifest" >&2; exit 1; }
|
||||
|
||||
tmp="$(mktemp)"
|
||||
trap 'rm -f "$tmp"' EXIT
|
||||
|
||||
{
|
||||
echo "date,commits"
|
||||
git -C "$REPO_ROOT" log --date=short --pretty=format:%ad "$HEAD_FULL" | sort | uniq -c | awk '{print $2 "," $1}' | sort
|
||||
sum="$(git -C "$REPO_ROOT" log --date=short --pretty=format:%ad "$HEAD_FULL" | sort | uniq -c | awk '{s += $1} END {print s}')"
|
||||
echo "TOTAL,$sum"
|
||||
} > "$tmp"
|
||||
|
||||
if cmp -s "$OUT" "$tmp"; then
|
||||
echo "PASS: daily-commit-census.csv re-derives from git at pinned head $HEAD_FULL"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "FAIL: daily-commit-census.csv differs from re-derivation at pinned head $HEAD_FULL" >&2
|
||||
diff -u "$OUT" "$tmp" >&2 || true
|
||||
exit 1
|
||||
@@ -0,0 +1,36 @@
|
||||
# Ledger: daily commit census (2026-11-04)
|
||||
|
||||
Lane: **Extract — data extraction and tables**.
|
||||
|
||||
A machine-readable census of every commit on the troupe repository's `main`
|
||||
branch, grouped by ISO calendar date.
|
||||
|
||||
## Source and method
|
||||
|
||||
- Source: `git log --date=short --pretty=format:%ad` on `/root/troupe-standup`
|
||||
(branch `main`).
|
||||
- Each commit contributes its ISO date `%ad`. Commits are then grouped by date
|
||||
and counted; rows sorted ascending by date.
|
||||
- Output gate: `daily-commit-census.csv` (41 day rows + 1 TOTAL row) is re-derived
|
||||
from git at the pinned rev and diffed byte-identically by `verify_daily_commit_census.py`
|
||||
and the standalone `CHECK` script.
|
||||
|
||||
## Metrics (measured, not asserted)
|
||||
|
||||
- Day rows: 41
|
||||
- Commit total: 2612
|
||||
- Pinned head: `4a4723c69b90799a833e6efba936972fc5659d58` (`git rev-parse HEAD`
|
||||
at capture, 2026-11-04)
|
||||
- Earliest date: 2026-09-24
|
||||
- Latest date: 2026-11-04
|
||||
- Busiest day: 2026-10-23 (171 commits)
|
||||
- Quietest day (non-zero): 2026-11-04 (2 commits)
|
||||
|
||||
## Files
|
||||
|
||||
- `daily-commit-census.csv` — `date,commits` (one row per date, plus a `TOTAL` row)
|
||||
- `extraction_manifest.json` — provenance: repo, pinned head, date range, counts
|
||||
- `extract_daily_commit_census.py` — extractor (regenerates the CSV)
|
||||
- `verify_daily_commit_census.py` — self-check gate (re-derives from pinned head)
|
||||
- `CHECK` — independent bash re-derivation gate (no Python)
|
||||
- `daily-commit-census.md` — top summary table for humans
|
||||
@@ -0,0 +1,43 @@
|
||||
date,commits
|
||||
2026-09-24,31
|
||||
2026-09-25,4
|
||||
2026-09-26,24
|
||||
2026-09-27,5
|
||||
2026-09-28,11
|
||||
2026-09-30,6
|
||||
2026-10-01,17
|
||||
2026-10-02,3
|
||||
2026-10-03,21
|
||||
2026-10-04,13
|
||||
2026-10-05,14
|
||||
2026-10-06,40
|
||||
2026-10-07,25
|
||||
2026-10-08,21
|
||||
2026-10-09,8
|
||||
2026-10-10,44
|
||||
2026-10-11,63
|
||||
2026-10-12,106
|
||||
2026-10-13,90
|
||||
2026-10-14,90
|
||||
2026-10-15,46
|
||||
2026-10-16,26
|
||||
2026-10-17,41
|
||||
2026-10-18,42
|
||||
2026-10-19,50
|
||||
2026-10-20,92
|
||||
2026-10-21,123
|
||||
2026-10-22,115
|
||||
2026-10-23,171
|
||||
2026-10-24,167
|
||||
2026-10-25,153
|
||||
2026-10-26,141
|
||||
2026-10-27,147
|
||||
2026-10-28,154
|
||||
2026-10-29,84
|
||||
2026-10-30,107
|
||||
2026-10-31,98
|
||||
2026-11-01,80
|
||||
2026-11-02,80
|
||||
2026-11-03,57
|
||||
2026-11-04,2
|
||||
TOTAL,2612
|
||||
|
@@ -0,0 +1,46 @@
|
||||
# Daily commit census — troupe repo `main` (2026-11-04)
|
||||
|
||||
| date | commits |
|
||||
|---|---|
|
||||
| 2026-09-24 | 31 |
|
||||
| 2026-09-25 | 4 |
|
||||
| 2026-09-26 | 24 |
|
||||
| 2026-09-27 | 5 |
|
||||
| 2026-09-28 | 11 |
|
||||
| 2026-09-30 | 6 |
|
||||
| 2026-10-01 | 17 |
|
||||
| 2026-10-02 | 3 |
|
||||
| 2026-10-03 | 21 |
|
||||
| 2026-10-04 | 13 |
|
||||
| 2026-10-05 | 14 |
|
||||
| 2026-10-06 | 40 |
|
||||
| 2026-10-07 | 25 |
|
||||
| 2026-10-08 | 21 |
|
||||
| 2026-10-09 | 8 |
|
||||
| 2026-10-10 | 44 |
|
||||
| 2026-10-11 | 63 |
|
||||
| 2026-10-12 | 106 |
|
||||
| 2026-10-13 | 90 |
|
||||
| 2026-10-14 | 90 |
|
||||
| 2026-10-15 | 46 |
|
||||
| 2026-10-16 | 26 |
|
||||
| 2026-10-17 | 41 |
|
||||
| 2026-10-18 | 42 |
|
||||
| 2026-10-19 | 50 |
|
||||
| 2026-10-20 | 92 |
|
||||
| 2026-10-21 | 123 |
|
||||
| 2026-10-22 | 115 |
|
||||
| 2026-10-23 | 171 |
|
||||
| 2026-10-24 | 167 |
|
||||
| 2026-10-25 | 153 |
|
||||
| 2026-10-26 | 141 |
|
||||
| 2026-10-27 | 147 |
|
||||
| 2026-10-28 | 154 |
|
||||
| 2026-10-29 | 84 |
|
||||
| 2026-10-30 | 107 |
|
||||
| 2026-10-31 | 98 |
|
||||
| 2026-11-01 | 80 |
|
||||
| 2026-11-02 | 80 |
|
||||
| 2026-11-03 | 57 |
|
||||
| 2026-11-04 | 2 |
|
||||
| **TOTAL** | **2612** |
|
||||
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Extract the daily commit census of the troupe repository.
|
||||
|
||||
Lane: Extract - data extraction and tables.
|
||||
|
||||
Source: live `git log --date=short --pretty=format:%ad` on the `main` branch of
|
||||
/root/troupe-standup. Commits are grouped by ISO calendar date and counted.
|
||||
Rows are sorted by date ascending. The pinned head (full + short) is recorded in
|
||||
extraction_manifest.json so the extraction is reproducible at a fixed rev.
|
||||
|
||||
Output (written to <out_dir>):
|
||||
daily-commit-census.csv - header, one row per date + a TOTAL row (41+1 lines)
|
||||
extraction_manifest.json - provenance: repo, head, date range, row count, sums
|
||||
|
||||
Usage: python3 extract_daily_commit_census.py <out_dir>
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from collections import Counter
|
||||
|
||||
REPO = "/root/troupe-standup"
|
||||
DATE_FMT = "%Y-%m-%d"
|
||||
|
||||
|
||||
def git(*args):
|
||||
return subprocess.run(
|
||||
["git", "-C", REPO] + list(args),
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
).stdout
|
||||
|
||||
|
||||
def build_rows():
|
||||
dates = git("log", "--date=short", "--pretty=format:%ad").splitlines()
|
||||
counts = Counter(d for d in dates if d)
|
||||
return [{"date": d, "commits": c} for d, c in sorted(counts.items())]
|
||||
|
||||
|
||||
def render_csv(rows):
|
||||
lines = ["date,commits"]
|
||||
for r in rows:
|
||||
lines.append("{date},{commits}".format(**r))
|
||||
total = sum(r["commits"] for r in rows)
|
||||
lines.append("TOTAL,{total}".format(total=total))
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def render_manifest(head_full, head_short, rows):
|
||||
dates = [r["date"] for r in rows]
|
||||
return {
|
||||
"repo": REPO,
|
||||
"branch": "main",
|
||||
"source_command": "git log --date=short --pretty=format:%ad",
|
||||
"head_full": head_full,
|
||||
"head_short": head_short,
|
||||
"earliest": dates[0],
|
||||
"latest": dates[-1],
|
||||
"day_rows": len(rows),
|
||||
"total_commits": sum(r["commits"] for r in rows),
|
||||
"method": "counts ISO calendar date of every commit on main",
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
out_dir = sys.argv[1] if len(sys.argv) > 1 else "."
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
|
||||
head_full = git("rev-parse", "HEAD").strip()
|
||||
head_short = git("rev-parse", "--short", "HEAD").strip()
|
||||
rows = build_rows()
|
||||
|
||||
csv_path = os.path.join(out_dir, "daily-commit-census.csv")
|
||||
with open(csv_path, "w", encoding="utf-8") as f:
|
||||
f.write(render_csv(rows))
|
||||
|
||||
manifest = render_manifest(head_full, head_short, rows)
|
||||
with open(os.path.join(out_dir, "extraction_manifest.json"), "w", encoding="utf-8") as f:
|
||||
json.dump(manifest, f, indent=2)
|
||||
f.write("\n")
|
||||
|
||||
print("rows:", manifest["day_rows"], "total:", manifest["total_commits"],
|
||||
"head:", head_short, "earliest:", manifest["earliest"], "latest:", manifest["latest"])
|
||||
print("wrote:", csv_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"repo": "/root/troupe-standup",
|
||||
"branch": "main",
|
||||
"source_command": "git log --date=short --pretty=format:%ad",
|
||||
"head_full": "4a4723c69b90799a833e6efba936972fc5659d58",
|
||||
"head_short": "4a4723c",
|
||||
"earliest": "2026-09-24",
|
||||
"latest": "2026-11-04",
|
||||
"day_rows": 41,
|
||||
"total_commits": 2612,
|
||||
"method": "counts ISO calendar date of every commit on main"
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env python3
|
||||
"""CHECK gate for the daily-commit-census extraction.
|
||||
|
||||
Re-derives the daily commit census from git at the PINNED head recorded in the
|
||||
committed extraction_manifest.json, re-renders the CSV with the identical
|
||||
format the extractor uses, and diffs it against the committed file. Exits 0 only
|
||||
when every check matches exactly. This is a re-derivation, not an assertion of a
|
||||
stored number.
|
||||
|
||||
Usage: python3 verify_daily_commit_census.py <artifact_dir>
|
||||
"""
|
||||
|
||||
import difflib
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
REPO = "/root/troupe-standup"
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, HERE)
|
||||
from extract_daily_commit_census import render_csv, build_rows # noqa: E402
|
||||
|
||||
CSV_NAME = "daily-commit-census.csv"
|
||||
|
||||
|
||||
def git(*args):
|
||||
return subprocess.run(
|
||||
["git", "-C", REPO] + list(args), check=True, capture_output=True, text=True
|
||||
).stdout
|
||||
|
||||
|
||||
def read_bytes(name):
|
||||
with open(name, "rb") as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def main():
|
||||
artdir = sys.argv[1] if len(sys.argv) > 1 else "."
|
||||
os.chdir(artdir)
|
||||
|
||||
with open("extraction_manifest.json", encoding="utf-8") as f:
|
||||
manifest = json.load(f)
|
||||
head = manifest.get("head_full")
|
||||
if not head:
|
||||
print("FAIL: manifest has no head_full")
|
||||
sys.exit(1)
|
||||
|
||||
# Re-derive from the pinned head.
|
||||
raw = git("log", "--date=short", "--pretty=format:%ad", head).splitlines()
|
||||
from collections import Counter
|
||||
|
||||
counts = Counter(d for d in raw if d)
|
||||
rows = [{"date": d, "commits": c} for d, c in sorted(counts.items())]
|
||||
expected = render_csv(rows)
|
||||
|
||||
ok = True
|
||||
|
||||
with open(CSV_NAME, encoding="utf-8") as f:
|
||||
committed = f.read()
|
||||
|
||||
if committed == expected:
|
||||
print("PASS: daily-commit-census.csv re-derives byte-identically from git "
|
||||
"at pinned head %s" % head)
|
||||
else:
|
||||
ok = False
|
||||
print("FAIL: daily-commit-census.csv diverges from re-derivation at "
|
||||
"pinned head %s" % head)
|
||||
exp = expected.splitlines()
|
||||
obs = committed.splitlines()
|
||||
for line in list(difflib.unified_diff(exp, obs, "expected@", "committed@"))[:12]:
|
||||
print(" " + line)
|
||||
|
||||
# Independent integrity: the TOTAL row must equal the sum of the day rows.
|
||||
day_sum = sum(int(r["commits"]) for r in rows)
|
||||
total_lines = [ln for ln in committed.splitlines() if ln.startswith("TOTAL,")]
|
||||
if total_lines == ["TOTAL,%d" % day_sum]:
|
||||
print("PASS: TOTAL row %r == sum of day rows %d" % (total_lines[0], day_sum))
|
||||
else:
|
||||
ok = False
|
||||
print("FAIL: TOTAL row %r != sum of day rows %d" % (total_lines, day_sum))
|
||||
|
||||
if ok:
|
||||
print("ALL CHECKS PASSED")
|
||||
sys.exit(0)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user