diff --git a/wizard-work/ledger/2026-10-27-checkin-census.files b/wizard-work/ledger/2026-10-27-checkin-census.files new file mode 100644 index 0000000..8bd6040 --- /dev/null +++ b/wizard-work/ledger/2026-10-27-checkin-census.files @@ -0,0 +1,96 @@ +# pin checkins@e0ee5cbd4894515930c4045607b26382c08862b9 tree=bb08e9a56d92a2ba81aad0dd6774c641f1e9c7d1 +2026-09-24-zephyrus-02.md +2026-09-24-zephyrus.md +2026-09-25-zephyrus.md +2026-09-26-zephyrus-02.md +2026-09-26-zephyrus.md +2026-09-27-zephyrus.md +2026-09-28-zephyrus-02.md +2026-09-28-zephyrus.md +2026-09-30-zephyrus.md +2026-10-01-zephyrus-03.md +2026-10-01-zephyrus-04.md +2026-10-01-zephyrus-05.md +2026-10-01-zephyrus-06.md +2026-10-01-zephyrus-07.md +2026-10-01-zephyrus.md +2026-10-02-zephyrus-01.md +2026-10-03-zephyrus-02.md +2026-10-03-zephyrus-03.md +2026-10-03-zephyrus.md +2026-10-04-zephyrus-02.md +2026-10-04-zephyrus.md +2026-10-05-zephyrus-02.md +2026-10-05-zephyrus-03.md +2026-10-05-zephyrus.md +2026-10-06-cassian.md +2026-10-06-zephyrus-02.md +2026-10-06-zephyrus-04.md +2026-10-06-zephyrus-05.md +2026-10-06-zephyrus-06.md +2026-10-06-zephyrus-wizards-02.md +2026-10-06-zephyrus-wizards-03.md +2026-10-06-zephyrus-wizards.md +2026-10-06-zephyrus.md +2026-10-07-cassian-02.md +2026-10-07-cassian-q5-census.md +2026-10-07-cassian-q5-recount.md +2026-10-07-cassian.md +2026-10-07-zephyrus-02.md +2026-10-07-zephyrus.md +2026-10-08-merlin.md +2026-10-08-zephyrus.md +2026-10-09-merlin.md +2026-10-09-zephyrus.md +2026-10-10-mercury.md +2026-10-10-merlin.md +2026-10-10-zephyrus.md +2026-10-11-mercury.md +2026-10-11-merlin.md +2026-10-11-zephyrus.md +2026-10-12-mercury.md +2026-10-12-merlin.md +2026-10-12-zephyrus.md +2026-10-13-gemini.md +2026-10-13-mercury.md +2026-10-13-merlin.md +2026-10-13-zephyrus.md +2026-10-14-mercury.md +2026-10-14-merlin.md +2026-10-14-zephyrus.md +2026-10-15-mercury.md +2026-10-15-merlin.md +2026-10-15-volt.md +2026-10-15-zephyrus.md +2026-10-16-mercury.md +2026-10-16-volt.md +2026-10-16-zephyrus.md +2026-10-17-mercury.md +2026-10-17-volt.md +2026-10-17-zephyrus.md +2026-10-18-mercury.md +2026-10-18-volt.md +2026-10-18-zephyrus.md +2026-10-19-mercury.md +2026-10-19-volt.md +2026-10-19-zephyrus.md +2026-10-20-mercury.md +2026-10-20-volt.md +2026-10-20-zephyrus.md +2026-10-21-mercury.md +2026-10-21-volt.md +2026-10-21-zephyrus.md +2026-10-22-mercury.md +2026-10-22-zephyrus.md +2026-10-23-mercury.md +2026-10-23-volt.md +2026-10-23-zephyrus.md +2026-10-24-mercury.md +2026-10-24-zephyrus.md +2026-10-25-mercury.md +2026-10-25-volt.md +2026-10-25-zephyrus.md +2026-10-26-mercury.md +2026-10-26-zephyrus.md +2026-10-27-mercury.md +2026-10-27-zephyrus.md diff --git a/wizard-work/ledger/2026-10-27-checkin-census.md b/wizard-work/ledger/2026-10-27-checkin-census.md index 74ce448..786f802 100644 --- a/wizard-work/ledger/2026-10-27-checkin-census.md +++ b/wizard-work/ledger/2026-10-27-checkin-census.md @@ -1,7 +1,8 @@ # Check-in census -Extract lane table: per-wizard tally of `checkins/*.md` files (README.md -excluded). The latest day present is 2026-10-27 (three files filed). +Extract lane table: per-wizard tally of `checkins/*.md` at a pinned +revision (README.md and any other undated file excluded). The latest day +at the pin is 2026-10-27 (2 files). | wizard | checkin_files | |---|---| @@ -14,9 +15,8 @@ excluded). The latest day present is 2026-10-27 (three files filed). | cassian-q5-census | 1 | | cassian-q5-recount | 1 | | gemini | 1 | -| sentinel | 1 | -**Total files:** 96 across 33 dates (README.md excluded). +**Total files:** 95 across 33 dates (undated files excluded). ## Per-date appendix @@ -54,12 +54,12 @@ excluded). The latest day present is 2026-10-27 (three files filed). | 2026-10-24 | 2 | | 2026-10-25 | 3 | | 2026-10-26 | 2 | -| 2026-10-27 | 3 | +| 2026-10-27 | 2 | ## Provenance -- Source: `checkins/*.md` in the troupe repo (as of 2026-10-27 06:00Z; 96 date-prefixed files, 33 distinct dates). -- Files excluded (no leading date): 1 (README). +- Pin: `checkins/` at commit `e0ee5cbd4894515930c4045607b26382c08862b9`, tree `bb08e9a56d92a2ba81aad0dd6774c641f1e9c7d1` (`2026-10-27-checkin-census.files` beside this file holds the sorted name list). +- Date-prefixed files at the pin: 95; distinct dates: 33; files excluded: 0 (none). - `wizard` = filename with leading `YYYY-MM-DD-` and trailing `-{digits}` stripped. -- Date-prefixed files: 96; files excluded: 1. -- Check: `CHECK` recomputes this table from `checkins/` and diffs against the TSV. +- Correction: the `e0ee5cb` revision of this artifact counted the live directory and published 96 files / 33 dates; no revision of `checkins/` holds that `sentinel` file, and the numbers here are recomputed from the pin. +- Check: `CHECK-2026-10-27-checkin-census` recomputes the table above from the pinned name list and diffs it; it never reads today's directory. diff --git a/wizard-work/ledger/2026-10-27-checkin-census.tsv b/wizard-work/ledger/2026-10-27-checkin-census.tsv index fab77a2..e04b4f1 100644 --- a/wizard-work/ledger/2026-10-27-checkin-census.tsv +++ b/wizard-work/ledger/2026-10-27-checkin-census.tsv @@ -8,4 +8,3 @@ zephyrus-wizards 3 cassian-q5-census 1 cassian-q5-recount 1 gemini 1 -sentinel 1 diff --git a/wizard-work/ledger/CHECK-2026-10-27-checkin-census b/wizard-work/ledger/CHECK-2026-10-27-checkin-census index 8e9db9a..c172504 100755 --- a/wizard-work/ledger/CHECK-2026-10-27-checkin-census +++ b/wizard-work/ledger/CHECK-2026-10-27-checkin-census @@ -1,45 +1,114 @@ #!/usr/bin/env bash -# CHECK — verifier for the check-in census artifact (Extract lane: data extraction). +# CHECK-2026-10-27-checkin-census — verifier for the 2026-10-27 check-in census. # -# Exit 0 iff: the committed TSV (wizard-work/ledger/2026-10-27-checkin-census.tsv) -# is byte-identical to an independent recompute of the census straight from the -# `checkins/*.md` source directory. +# Verdict: exit 0 iff the committed TSV equals a re-derivation of the census from +# the PINNED name list beside it, and the pin itself is a real revision of +# `checkins/`. Exit 1 on any fault, including "the input scope cannot be +# established" — the check refuses to grade rather than falling back to grading +# today's directory. # -# The recompute uses only ls/grep/awk/sort/uniq/date/cmp (no Python, no shared -# extractor logic), so a hand-edited or stale TSV cannot pass by accident. +# WHAT CHANGED AND WHY (Q88). The previous revision recomputed the row set from +# `checkins/*.md` as the directory stood when the CHECK ran, so the dated artifact +# went red on the next check-in for no reason — and it graded a `date,count` +# schema the artifact had stopped publishing at `e0ee5cb`, so it was red on every +# run. The input is now the committed `2026-10-27-checkin-census.files`. # -# Header is emitted first by construction; data rows (dates with files) come -# next, sorted ascending; today's row (0 when no check-in file has been filed -# yet) is the final row. All are produced by independent shell tools. +# The recompute is awk/sort/cmp over the name list — it does not import, read or +# re-run `extract_checkin_census_2026-10-27.py`, so the grader and the generator +# cannot agree by sharing a bug. # -# Repo root is derived from this file's directory, so the CHECK runs from any -# working directory and does not depend on a specific clone path. +# `set -u`, no `set -e`: each leg reports and the script decides, so a green +# verdict always means every leg ran. set -u -cd "$(dirname "${BASH_SOURCE[0]}")" || exit 1 -REPO_ROOT="$(cd ../.. && pwd)" -SRC="$REPO_ROOT/checkins" -OUT="$REPO_ROOT/wizard-work/ledger/2026-10-27-checkin-census.tsv" +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO="$(git -C "$HERE" rev-parse --show-toplevel 2>/dev/null)" \ + || REPO="$(cd "$HERE/../.." && pwd)" +FILES="$HERE/2026-10-27-checkin-census.files" +TSV="$HERE/2026-10-27-checkin-census.tsv" +MD="$HERE/2026-10-27-checkin-census.md" +TAB="$(printf '\t')" -test -s "$OUT" || { echo "FAIL: TSV missing or empty: $OUT" >&2; exit 1; } +fail() { printf 'FAIL: %s\n' "$*" >&2; exit 1; } +scope_fail() { printf 'FAIL: SCOPE NOT ESTABLISHED: %s\n' "$*" >&2; exit 1; } -tmp="$(mktemp)" -trap 'rm -f "$tmp"' EXIT +tmp="$(mktemp -d)" +trap 'rm -rf "$tmp"' EXIT -{ - echo "date,checkin_files" - ls "$SRC"/*.md 2>/dev/null | grep -v 'README\.md$' \ - | awk -F/ '{n=split($0, a, "/"); name=a[n]; sub(/\.md$/, "", name); print substr(name, 1, 10)}' \ - | sort | uniq -c | awk '{print $2 "," $1}' - echo "$(date -u +%F),0" -} > "$tmp" +# ---------------------------------------------------------------- leg 1: scope +# The pin is the artifact's input. If it is absent, malformed, unsorted, +# duplicated or undated there is nothing to recompute from, and the check says so +# instead of grading whatever the directory holds. +[ -s "$FILES" ] || scope_fail "pinned name list missing or empty: ${FILES#"$REPO"/}" -if cmp -s "$OUT" "$tmp"; then - echo "PASS: rows=$(tail -n +2 "$OUT" | grep -c .) (dates with files + today=0) recomputed from $SRC" - exit 0 +header="$(head -n 1 "$FILES")" +if ! [[ $header =~ ^#\ pin\ checkins@([0-9a-f]{40})\ tree=([0-9a-f]{40})$ ]]; then + scope_fail "pin header not understood: '$header'" +fi +pin_commit="${BASH_REMATCH[1]}" +pin_tree="${BASH_REMATCH[2]}" + +tail -n +2 "$FILES" > "$tmp/names" +[ -s "$tmp/names" ] || scope_fail "pin names no files" + +undated="$(grep -cvE '^[0-9]{4}-[0-9]{2}-[0-9]{2}-.*\.md$' "$tmp/names" || true)" +[ "$undated" = "0" ] || scope_fail "$undated pinned name(s) are not YYYY-MM-DD-*.md" + +dupes="$(sort "$tmp/names" | uniq -d | wc -l)" +[ "$dupes" = "0" ] || scope_fail "$dupes duplicated name(s) in the pin" + +if ! LC_ALL=C sort "$tmp/names" | cmp -s - "$tmp/names"; then + scope_fail "pin names are not sorted (LC_ALL=C) — the list is not a stable input" +fi +echo "PASS scope: $(wc -l < "$tmp/names") pinned dated file(s), pin ${pin_commit:0:7}" + +# --------------------------------------------------- leg 2: the pin is a revision +# Immutable history makes this leg free of false reds; a clone that does not carry +# the pin object says so, and the legs below still grade the name list. +if git -C "$REPO" cat-file -e "${pin_commit}^{commit}" 2>/dev/null; then + real_tree="$(git -C "$REPO" rev-parse "$pin_commit:checkins" 2>/dev/null)" \ + || fail "pin commit $pin_commit has no checkins/ tree" + [ "$real_tree" = "$pin_tree" ] \ + || fail "pin tree $pin_tree is not $pin_commit:checkins ($real_tree)" + git -C "$REPO" ls-tree -r --name-only "$pin_tree" \ + | grep -E '(^|/)[0-9]{4}-[0-9]{2}-[0-9]{2}-.*\.md$' \ + | sed 's#.*/##' | LC_ALL=C sort > "$tmp/git_names" + if ! cmp -s "$tmp/git_names" "$tmp/names"; then + printf 'FAIL: pin name list != checkins/ at %s\n' "${pin_commit:0:7}" >&2 + diff -u "$tmp/git_names" "$tmp/names" >&2 || true + exit 1 + fi + echo "PASS pin: name list == checkins/ tree ${pin_tree:0:7} at ${pin_commit:0:7}" +else + echo "SKIP pin: commit ${pin_commit:0:7} not in this object database —" \ + "the committed name list is the pin" fi -echo "FAIL: census mismatch against independent recompute" >&2 -diff -u "$OUT" "$tmp" >&2 || true -exit 1 +# ------------------------------------------- leg 3: derive the per-wizard table +awk ' + { n = $0 + sub(/\.md$/, "", n) + sub(/^[0-9]{4}-[0-9]{2}-[0-9]{2}-/, "", n) + sub(/-[0-9]+$/, "", n) + c[n]++ + } + END { for (w in c) printf "%s\t%d\n", w, c[w] } +' "$tmp/names" | LC_ALL=C sort -t "$TAB" -k2,2nr -k1,1 > "$tmp/tsv.body" + +{ printf 'wizard%scheckin_files\n' "$TAB"; cat "$tmp/tsv.body"; } > "$tmp/tsv.expected" + +[ -s "$TSV" ] || fail "TSV missing or empty: ${TSV#"$REPO"/}" +if ! cmp -s "$tmp/tsv.expected" "$TSV"; then + printf 'FAIL: TSV != recompute from the pin\n' >&2 + diff -u "$TSV" "$tmp/tsv.expected" >&2 || true + exit 1 +fi +echo "PASS tsv: $(($(wc -l < "$TSV") - 1)) wizard row(s)," \ + "$(awk -F"$TAB" 'NR>1{s+=$2} END{print s+0}' "$TSV") file(s)" + +# -------------------------------------------------------------------- verdict +printf 'VERDICT=PASS census-2026-10-27 rows=%s files=%s pin=%s\n' \ + "$(($(wc -l < "$TSV") - 1))" \ + "$(awk -F"$TAB" 'NR>1{s+=$2} END{print s+0}' "$TSV")" "${pin_commit:0:7}" +exit 0 diff --git a/wizard-work/ledger/extract_checkin_census_2026-10-27.py b/wizard-work/ledger/extract_checkin_census_2026-10-27.py index 7abba4d..1a4d5f7 100644 --- a/wizard-work/ledger/extract_checkin_census_2026-10-27.py +++ b/wizard-work/ledger/extract_checkin_census_2026-10-27.py @@ -1,81 +1,118 @@ #!/usr/bin/env python3 -"""extract_checkin_census_2026-10-27.py -- check-in census table. +"""extract_checkin_census_2026-10-27.py -- check-in census table, from a PIN. -Extract lane (data extraction + tables). Reads checkins/*.md in the troupe -repo, groups files by their leading YYYY-MM-DD date, and emits a per-wizard -tally table plus a full provenance record. The canonical table is one row per -wizard: `wizardcheckin_files`, sorted by count descending, name -ascending. +Extract lane (data extraction + tables). Counts `checkins/*.md` at a named +revision, not on whatever the directory holds when the script runs, and emits: -Artifact (written beside this script, under wizard-work/ledger/): - * 2026-10-27-checkin-census.tsv wizardcheckin_files table - * 2026-10-27-checkin-census.md human summary + per-date appendix -Reads use paths relative to this file's directory (repo-root agnostic). + * 2026-10-27-checkin-census.files the pin: one header line, then every dated + filename at that revision, sorted + * 2026-10-27-checkin-census.tsv wizardcheckin_files, count desc then name + * 2026-10-27-checkin-census.md the same table, a per-date appendix, provenance + +Why a pin. The 2026-10-27 revision of this artifact was produced by walking the +live `checkins/` directory (`os.scandir`), so it counted a file that no commit +holds: it published 96 files / 33 dates and a `sentinel` row, while the tree of +the commit that carries it (`e0ee5cb`) holds 95 dated files and no sentinel +check-in. A dated artifact whose input is "the directory as it stands" cannot be +re-derived, and every re-run publishes a different day. The pin makes the input +a committed name list at a fixed revision (Q88). + +Commands (each is exercised by `--help`): + + python3 extract_checkin_census_2026-10-27.py --pin e0ee5cb + python3 extract_checkin_census_2026-10-27.py --pin e0ee5cb --write + python3 extract_checkin_census_2026-10-27.py --manifest 2026-10-27-checkin-census.files --write + python3 extract_checkin_census_2026-10-27.py --outdir /tmp/scratch --write + +Without `--write` nothing is written: the derivation is printed so a reader can +compare it with the committed tables. Paths resolve from this file's directory +(`--repo` overrides the git tree read), so a clone grades its own tree. """ +import argparse import collections import os import re +import subprocess +import sys HERE = os.path.dirname(os.path.abspath(__file__)) -# Troupe repo root. Override with EXT_CENSUS_ROOT env var if set. -_ROOT = os.environ.get("EXT_CENSUS_ROOT") or ( - "/root/troupe-standup" - if os.path.isdir("/root/troupe-standup/checkins") - else os.path.dirname(os.path.dirname(HERE)) -) -SRC = os.path.join(_ROOT, "checkins") -OUT_TSV = os.path.join(HERE, "2026-10-27-checkin-census.tsv") -OUT_MD = os.path.join(HERE, "2026-10-27-checkin-census.md") - -DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}-") # leading date on the filename -SUFFIX_RE = re.compile(r"-\d+$") # trailing -NNNN -> strip +DATED = re.compile(r"^(\d{4}-\d{2}-\d{2})-(.+)\.md$") +SUFFIX = re.compile(r"-\d+$") +HEADER = re.compile(r"^# pin checkins@([0-9a-f]{40}) tree=([0-9a-f]{40})$") +MANIFEST_NAME = "2026-10-27-checkin-census.files" -def extract(): - """Return (per_date Counter, per_wizard Counter, excluded list).""" - per_date = collections.Counter() - per_wizard = collections.Counter() - excluded = [] - with os.scandir(SRC) as it: - for entry in sorted(it, key=lambda e: e.name): - name = entry.name - if not name.endswith(".md"): - continue - if not DATE_RE.match(name): - excluded.append(name[:-3]) - continue - date = name[:10] - rest = name[11:-3] - wizard = SUFFIX_RE.sub("", rest) - per_date[date] += 1 - per_wizard[wizard] += 1 - return per_date, per_wizard, excluded +def git(repo, *args): + out = subprocess.run(["git", "-C", repo] + list(args), + capture_output=True, text=True) + if out.returncode != 0: + sys.exit(f"git {' '.join(args)} failed: {out.stderr.strip()}") + return out.stdout -def write_tables(per_date, per_wizard, excluded): - # Canonical table: one row per wizard, tab-separated. - rows = sorted(per_wizard.items(), key=lambda kv: (-kv[1], kv[0])) - with open(OUT_TSV, "w", encoding="utf-8") as f: - f.write("wizard\tcheckin_files\n") - for w, n in rows: - f.write(f"{w}\t{n}\n") +def names_from_pin(repo, rev): + commit = git(repo, "rev-parse", "--verify", f"{rev}^{{commit}}").strip() + tree = git(repo, "rev-parse", f"{commit}:checkins").strip() + listing = git(repo, "ls-tree", "-r", "--name-only", tree) + names = [line.rsplit("/", 1)[-1] for line in listing.splitlines() if line.strip()] + return commit, tree, names - total_files = sum(per_date.values()) - total_dates = len(per_date) + +def names_from_manifest(path): + with open(path, encoding="utf-8") as fh: + lines = [line.rstrip("\n") for line in fh if line.strip()] + head = HEADER.match(lines[0]) if lines else None + if not head: + sys.exit(f"manifest header not understood: {path}") + return head.group(1), head.group(2), lines[1:] + + +def derive(names): + """(per_date, per_wizard, excluded, dated) from filenames.""" + per_date, per_wizard, excluded, dated = (collections.Counter(), collections.Counter(), + [], []) + for name in sorted(names): + if not name.endswith(".md"): + excluded.append(f"{name} (not .md)") + continue + m = DATED.match(name) + if not m: + excluded.append(name[:-3]) + continue + dated.append(name) + per_date[m.group(1)] += 1 + per_wizard[SUFFIX.sub("", m.group(2))] += 1 + return per_date, per_wizard, excluded, dated + + +def rows_of(per_wizard): + return sorted(per_wizard.items(), key=lambda kv: (-kv[1], kv[0])) + + +def render_tsv(per_wizard): + out = ["wizard\tcheckin_files"] + out += [f"{w}\t{n}" for w, n in rows_of(per_wizard)] + return "\n".join(out) + "\n" + + +def render_md(per_date, per_wizard, excluded, commit, tree, dated): + total_files, total_dates = len(dated), len(per_date) + latest = max(per_date) lines = [ "# Check-in census", "", - "Extract lane table: per-wizard tally of `checkins/*.md` files (README.md", - "excluded). The latest day present is 2026-10-27 (three files filed).", + "Extract lane table: per-wizard tally of `checkins/*.md` at a pinned", + f"revision (README.md and any other undated file excluded). The latest day", + f"at the pin is {latest} ({per_date[latest]} files).", "", "| wizard | checkin_files |", "|---|---|", ] - lines += [f"| {w} | {n} |" for w, n in rows] + lines += [f"| {w} | {n} |" for w, n in rows_of(per_wizard)] lines += [ "", f"**Total files:** {total_files} across {total_dates} dates " - "(README.md excluded).", + "(undated files excluded).", "", "## Per-date appendix", "", @@ -87,23 +124,71 @@ def write_tables(per_date, per_wizard, excluded): "", "## Provenance", "", - f"- Source: `checkins/*.md` in the troupe repo (as of 2026-10-27 06:00Z; " - f"{total_files} date-prefixed files, {total_dates} distinct dates).", - f"- Files excluded (no leading date): {len(excluded)} " - f"({', '.join(excluded) if excluded else 'none'}).", + f"- Pin: `checkins/` at commit `{commit}`, tree `{tree}` " + f"(`{MANIFEST_NAME}` beside this file holds the sorted name list).", + f"- Date-prefixed files at the pin: {total_files}; distinct dates: {total_dates}; " + f"files excluded: {len(excluded)}" + + (f" ({', '.join(excluded)})." if excluded else " (none)."), "- `wizard` = filename with leading `YYYY-MM-DD-` and trailing `-{digits}` stripped.", - f"- Date-prefixed files: {total_files}; files excluded: {len(excluded)}.", - "- Check: `CHECK` recomputes this table from `checkins/` and diffs against the TSV.", + "- Correction: the `e0ee5cb` revision of this artifact counted the live " + "directory and published 96 files / 33 dates; no revision of `checkins/` " + "holds that `sentinel` file, and the numbers here are recomputed from the pin.", + "- Check: `CHECK-2026-10-27-checkin-census` recomputes the table above " + "from the pinned name list and diffs it; it never reads today's directory.", ] - with open(OUT_MD, "w", encoding="utf-8") as f: - f.write("\n".join(lines) + "\n") + return "\n".join(lines) + "\n" - print(f"dates_with_files={total_dates} total_files={total_files} excluded={len(excluded)}") - print(f"wizards={len(rows)}") - print(f"wrote {OUT_TSV}") - print(f"wrote {OUT_MD}") + +def main(argv=None): + ap = argparse.ArgumentParser( + description="Check-in census from a pinned revision of checkins/.") + ap.add_argument("--pin", metavar="REV", + help="pin to this revision's checkins/ tree (e.g. e0ee5cb)") + ap.add_argument("--manifest", metavar="PATH", + help=f"read the pin from this committed name list " + f"(default: ./{MANIFEST_NAME})") + ap.add_argument("--repo", default=os.path.dirname(os.path.dirname(HERE)), + help="repo to read git objects from (default: this file's repo)") + ap.add_argument("--outdir", default=HERE, + help="directory to write into (default: beside this script)") + ap.add_argument("--write", action="store_true", + help="write the manifest and both tables (default: print only)") + args = ap.parse_args(argv) + + if args.pin: + commit, tree, names = names_from_pin(args.repo, args.pin) + else: + manifest = args.manifest or os.path.join(HERE, MANIFEST_NAME) + if not os.path.exists(manifest): + ap.error(f"no --pin given and no manifest at {manifest}") + commit, tree, names = names_from_manifest(manifest) + + per_date, per_wizard, excluded, dated = derive(names) + tsv, md = render_tsv(per_wizard), render_md(per_date, per_wizard, excluded, + commit, tree, dated) + print(f"pin commit={commit} tree={tree}") + print(f"dated_files={len(dated)} dates={len(per_date)} " + f"wizards={len(per_wizard)} excluded={len(excluded)}") + for w, n in rows_of(per_wizard): + print(f" {w}\t{n}") + + if not args.write: + print("dry run (pass --write to write the manifest and tables)") + return 0 + + os.makedirs(args.outdir, exist_ok=True) + manifest_path = os.path.join(args.outdir, MANIFEST_NAME) + with open(manifest_path, "w", encoding="utf-8") as fh: + fh.write(f"# pin checkins@{commit} tree={tree}\n") + fh.write("".join(f"{n}\n" for n in sorted(dated))) + for name, payload in (("2026-10-27-checkin-census.tsv", tsv), + ("2026-10-27-checkin-census.md", md)): + with open(os.path.join(args.outdir, name), "w", encoding="utf-8") as fh: + fh.write(payload) + print(f"wrote {manifest_path}, 2026-10-27-checkin-census.tsv, " + f"2026-10-27-checkin-census.md") + return 0 if __name__ == "__main__": - per_date, per_wizard, excluded = extract() - write_tables(per_date, per_wizard, excluded) + sys.exit(main())