#!/usr/bin/env bash # Verify the queue status index reproduces from WORK-QUEUE.md. # Exit 0 = correct, nonzero = wrong. set -u Q=/root/troupe-standup/WORK-QUEUE.md J=/root/troupe-standup/wizard-work/sol/queue-status-index.json M=/root/troupe-standup/wizard-work/sol/queue-status-index.md [ -f "$Q" ] || { echo "MISSING $Q"; exit 1; } [ -f "$J" ] || { echo "MISSING $J"; exit 1; } python3 - "$Q" "$J" <<'PY' import json, re, sys, collections qpath, jpath = sys.argv[1], sys.argv[2] lines = open(qpath, encoding="utf-8").read().split("\n") N = len(lines) heads = [(i, ln) for i, ln in enumerate(lines, 1) if re.match(r"^##\s+\S", ln)] ITEM_RE = re.compile(r"^##\s+Q(\d+)\s+(?!closure\b)(?=[—–-]|[A-Za-z0-9])") def clean(txt): return re.sub(r"\*\*", "", txt).strip() def extract(block): for idx, bl in enumerate(block): m = re.match(r"^\s*\*\*Status:\s*(.*)$", bl) if not m: continue raw = m.group(1) prim = re.match(r"^\s*\*\*Status:\s*(.*?)\*\*", bl) if prim and prim.group(1) and prim.group(1).find("**") == -1: return clean(prim.group(1)), idx, True txt = raw for b2 in block[idx+1:]: txt += "\n" + b2 if b2.rstrip().endswith("**"): break return clean(txt), idx, True return None, None, False def cat(text): if text is None: return "NO-MARKER" low = text.lower() if "falsified" in low: return "FALSIFIED" if "verified" in low and "closed" in low: return "VERIFIED-CLOSED" if "unclaimed" in low: return "UNCLAIMED" if "resolved" in low: return "RESOLVED" if "re-scoped" in low or "closed" in low: return "CLOSED" if "satisfied" in low: return "DONE" if "decision" in low or "pending" in low or "undecided" in low: return "OPEN" if "open" in low: return "OPEN" if "done" in low or "complete" in low: return "DONE" return "UNSPECIFIED" def section(a, b): return lines[a-1:b-1] expected = [] for hi, (i, head) in enumerate(heads): mm = ITEM_RE.match(head) if not mm: continue q = int(mm.group(1)) nxt = heads[hi+1][0] if hi+1 < len(heads) else N + 1 st, _, found = extract(section(i, nxt)) expected.append({"q": q, "expected_category": cat(st), "found": found, "status": st}) data = json.load(open(jpath, encoding="utf-8")) items = data.get("items", []) errors = [] if data.get("total_items") != len(expected): errors.append(f"total_items {data.get('total_items')} != re-derived {len(expected)}") for e, rec in zip(expected, items): if rec.get("q") != e["q"]: errors.append(f"q mismatch {rec.get('q')} vs {e['q']}") continue if rec.get("category") != e["expected_category"]: errors.append(f"Q{e['q']}: category {rec.get('category')} != {e['expected_category']}") if rec.get("status") != e["status"]: errors.append(f"Q{e['q']}: status text mismatch") if rec.get("found") != e["found"]: errors.append(f"Q{e['q']}: found mismatch") if errors: print("VERIFY FAIL:") for e2 in errors: print(" -", e2) sys.exit(1) print(f"VERIFY OK: {len(expected)} items, categories {dict(collections.Counter(e['expected_category'] for e in expected))}") PY rc=$? if [ $rc -ne 0 ]; then exit $rc; fi python3 - "$Q" <<'PY' import re, sys lines = open(sys.argv[1], encoding="utf-8").read().split("\n") states = {"CLOSED":0, "DONE":0, "RESOLVED":0, "UNCLAIMED":0, "OPEN":0, "NO-MARKER":0, "FALSIFIED":0, "VERIFIED-CLOSED":0, "UNSPECIFIED":0} for ln in lines: if re.match(r"^\s*\*\*Status:", ln): t = re.sub(r"\*\*", "", ln) tl = t.lower() if "falsified" in tl: states["FALSIFIED"] += 1 elif "verified" in tl and "closed" in tl: states["VERIFIED-CLOSED"] += 1 elif "unclaimed" in tl: states["UNCLAIMED"] += 1 elif "resolved" in tl: states["RESOLVED"] += 1 elif "closed" in tl: states["CLOSED"] += 1 elif "open" in tl: states["OPEN"] += 1 elif "done" in tl or "complete" in tl: states["DONE"] += 1 else: states["UNSPECIFIED"] += 1 print("independent marker counts:", states) PY echo "INDEX CHECK: OK"