From edaef631e5d54f6a0bd973b87a172b45d75825fb Mon Sep 17 00:00:00 2001 From: Volt Date: Sun, 18 Oct 2026 00:23:34 +0000 Subject: [PATCH] tools (volt): checkin_check -- adds --since so the gate can be adopted forward on a dirty history; 26 cells --- tools/checkin_check.py | 43 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/tools/checkin_check.py b/tools/checkin_check.py index 8d2128a..1f1bcd4 100644 --- a/tools/checkin_check.py +++ b/tools/checkin_check.py @@ -66,7 +66,7 @@ USAGE: python3 checkin_check.py --repo /root/troupe-standup python3 checkin_check.py --repo . --gaps --json /tmp/checkins.json python3 checkin_check.py --dir /tmp/fake/checkins --roster volt,zephyrus --today 2026-10-18 - python3 checkin_check.py --selftest # 24 cells, names, dates, roster, refusals + python3 checkin_check.py --selftest # 26 cells, names, dates, roster, refusals """ from __future__ import annotations @@ -293,7 +293,8 @@ def banner(text: str) -> str: return f"== {text} ==" -def run(repo: str, path: str, roster, today: date, show_gaps: bool, quiet=False): +def run(repo: str, path: str, roster, today: date, show_gaps: bool, quiet=False, + since: str | None = None): """Everything except printing the file table. Returns the report dict.""" if not os.path.isdir(path): raise CannotGrade(f"cannot read checkins dir {path}: not a directory") @@ -305,7 +306,19 @@ def run(repo: str, path: str, roster, today: date, show_gaps: bool, quiet=False) if n.endswith(".md") and n not in SKIP_NAMES and os.path.isfile(os.path.join(path, n))] skipped = [n for n in names if n in SKIP_NAMES] - if not files: + # --since grades forward only: the archive's 25 historical names are the + # record, and a gate that can only ever be red is a gate nobody adopts. + excluded_since = 0 + if since: + keep = [] + for p in files: + m = AMBIENT_RE.match(os.path.basename(p)) + if m and f"{m.group(1)}-{m.group(2)}-{m.group(3)}" >= since: + keep.append(p) + else: + excluded_since += 1 + files = keep + if not files and not since: raise CannotGrade(f"cannot read checkins dir {path}: no *.md files") rows = [grade_file(p, roster, today) for p in files] @@ -326,6 +339,7 @@ def run(repo: str, path: str, roster, today: date, show_gaps: bool, quiet=False) rep = { "dir": path, "checked": len(files), "today": today.isoformat(), "roster_size": len(roster), "skipped": skipped, + "since": since, "excluded_since": excluded_since, "faults": {c: sum(1 for r in rows if c in r["class"]) for c in FAULTS}, "fault_rows": len(fault_rows), "duplicate_groups": [{"date": d, "handle": h, "files": fl} @@ -346,6 +360,9 @@ def run(repo: str, path: str, roster, today: date, show_gaps: bool, quiet=False) def print_report(rep, show_gaps: bool, fail: bool): print(banner(f"{rep['checked']} check-in files in {rep['dir']}")) print(f"roster: {rep['roster_size']} handles; today {rep['today']}") + if rep.get("since"): + print(f"since {rep['since']}: {rep['checked']} graded, " + f"{rep['excluded_since']} older file(s) not graded") for n in rep.get("skipped") or []: print(f" SKIP {n} (the directory's own README: not a check-in)") fault_rows = [r for r in rep["rows"] if r["class"] or r["duplicate"]] @@ -524,8 +541,15 @@ def selftest(): rep["skipped"] == ["README.md"] and "README.md" not in [r["file"] for r in rep["rows"]] and rep["checked"] == 7, f"{rep['skipped']} {rep['checked']}") + rep_since = run(repo, ck, roster, today, False, since="2026-10-16") + cell("c25 --since grades forward only, and says how many it left out", + rep_since["checked"] == 5 and rep_since["excluded_since"] == 3 + and "2026-10-15-volt.md" not in [r["file"] for r in rep_since["rows"]], + f"{rep_since['checked']}/{rep_since['excluded_since']}") + cell("c26 a malformed --since refuses at rc 2, never 1", + main(["--dir", ck, "--roster", "volt", "--since", "16-10-2026"]) == 2) - print(f"selftest: 24 cells, {24 - len(bad)} passed, {len(bad)} failed") + print(f"selftest: 26 cells, {26 - len(bad)} passed, {len(bad)} failed") return 1 if bad else 0 @@ -540,6 +564,8 @@ def main(argv=None): ap.add_argument("--roster", help="comma-separated handles, instead of the roster files") ap.add_argument("--today", help="YYYY-MM-DD for the future-date rule (default: UTC today)") ap.add_argument("--gaps", action="store_true", help="print per-handle coverage and gaps") + ap.add_argument("--since", help="grade only check-ins dated on/after this YYYY-MM-DD " + "(the archive's older names are its record)") ap.add_argument("--json", help="write the full report here") ap.add_argument("--fail", action="store_true", help="exit 1 when any file has a fault") ap.add_argument("--selftest", action="store_true", help="fixture corpus") @@ -559,9 +585,16 @@ def main(argv=None): repo = os.path.abspath(a.repo) path = os.path.abspath(a.dir) if a.dir else os.path.join(repo, "checkins") + since = a.since + if since: + try: + date.fromisoformat(since) + except ValueError: + print(f"cannot grade: --since is not YYYY-MM-DD: {since}", file=sys.stderr) + return 2 try: roster, src = load_roster(repo, a.roster) - rep = run(repo, path, roster, today, a.gaps) + rep = run(repo, path, roster, today, a.gaps, since=since) except CannotGrade as exc: print(str(exc), file=sys.stderr) return 2