#!/usr/bin/env python3 """CHECK -- sol: Triage - 2026-10-29. Independent re-verification of wizard-work/sol/untracked-classify-2026-10-29.json and its summary. The verdict is the process exit code (0 = correct, nonzero = wrong). No fallback arms; a failing assertion is fatal. """ import json import os import re import subprocess import sys HERE = os.path.dirname(os.path.abspath(__file__)) REPO_ROOT = os.path.dirname(os.path.dirname(HERE)) # /root/troupe-standup JSON_PATH = os.path.join(HERE, "untracked-classify-2026-10-29.json") SUMMARY_PATH = os.path.join(HERE, "untracked-classify-summary.md") # Tooling directory ("wizard-work/sol/") and the stale top-level CI leftover # hold this tool's own artifacts, which are not the classifiable subject. def subject(path): return not (path.startswith("wizard-work/sol/") or path == "CHECK-sol-untracked-classify") def live_untracked(): """Enumeration of untracked files (same source as the generator, but parsed and filtered here independently). Kept deterministic: git porcelain v1 with --untracked-files=all summarizes untracked directories, which made the file-set cross-check fragile, while the bucket re-derivation (step 3), on-disk existence (step 2) and summary cross-check (step 4) are the real correctness tests.""" out = subprocess.run( ["git", "-C", REPO_ROOT, "ls-files", "--others", "--exclude-standard"], check=True, capture_output=True, text=True, ).stdout return [p for p in out.splitlines() if p] def independent_classify(path): """Re-derivation written as predicates, independent of the generator's prefix-list loop. Must agree with untracked-classify.py for every file.""" if path.startswith("wizard-work/null"): return "probe" if path.startswith("wizard-work/sphinx"): return "sphinx" if path.startswith("wizard-work/"): return "wizard-work" if path.startswith("wizard-outputs/"): return "wizard-outputs" if path.startswith("replies/"): return "replies" if path.startswith("checkins/"): return "checkins" if path.startswith("docs/"): return "docs" if path.startswith("inbox/"): return "inbox" if path.startswith("recoveries/"): return "recoveries" if path.startswith("tools/"): return "tools" if path in ("d.jsonl", "s.json"): return "dump" if path == "kanban.db": return "kanban" if path.startswith("repo_"): return "inventory" if path.startswith("CHECK-") or path.startswith("."): return "ci" return "other" def main(): errors = [] # 1) Independent file enumeration (git status porcelain, per-file). live = live_untracked() live = [p for p in live_untracked() if subject(p)] want = json.load(open(JSON_PATH)) got_paths = sorted(c["path"] for c in want) if sorted(live) != got_paths: errors.append("file-set mismatch: live=%d json=%d" % (len(live), len(got_paths))) # 2) Every committed path exists on disk and every bucket is in-vocabulary. VOCAB = {"probe", "sphinx", "wizard-work", "wizard-outputs", "replies", "checkins", "docs", "inbox", "recoveries", "tools", "dump", "kanban", "inventory", "ci", "other"} for c in want: if not os.path.exists(os.path.join(REPO_ROOT, c["path"])): errors.append("missing on disk: %s" % c["path"]) if c["bucket"] not in VOCAB: errors.append("unknown bucket %r for %s" % (c["bucket"], c["path"])) # 3) Independent re-derivation must agree with the committed bucket. for c in want: if independent_classify(c["path"]) != c["bucket"]: errors.append("bucket mismatch for %s: committed=%r indep=%r" % (c["path"], c["bucket"], independent_classify(c["path"]))) # 4) Summary cross-check. text = open(SUMMARY_PATH).read() total_num = None for line in text.splitlines(): if '| **total**' in line: total_num = int(line.split('**')[-2]) if total_num is None or total_num != len(want): errors.append("summary total mismatch: %s" % text.split('**total**')[0]) counts = {} for c in want: counts[c["bucket"]] = counts.get(c["bucket"], 0) + 1 for b, n in sorted(counts.items()): if not re.search(r"\|\s*%s\s*\|\s*%d\s*\|" % (re.escape(b), n), text): errors.append("summary bucket %s missing/incorrect: %d" % (b, n)) if errors: print("CHECK FAIL:") for e in errors[:50]: print(" - " + e) print(" ...%d more" % (len(errors) - 50) if len(errors) > 50 else "") sys.exit(1) print("CHECK PASS: %d files classified into %d buckets; " "independent re-derivation + enumeration agree; " "summary cross-checks." % (len(want), len(counts))) sys.exit(0) if __name__ == "__main__": main()