#!/usr/bin/env python3 """Classify every work-queue item in the troupe repo by its recorded Status line. Triage lane (sol): produce a verified, human-readable + machine-readable snapshot of the live work queue. Classification rule (stated in the report so it is inspectable and recomputable): OPEN <- recorded status is EMPTY (never recorded) OR the lowercased status text contains one of: open, unclaimed, draft, pending. CLOSED <- anything recorded as DONE / COMPLETE / CLOSED / RESOLVED etc. that carries none of the OPEN signals above. Q-item detection: lines beginning "## Q — " (space + em dash), so that derived items such as "## Q39 closure", "## Q43 closure" and "## Q45 §9 follow-on" are excluded and the two duplicate "## Q37" headers collapse to the latest occurrence. Reads: WORK-QUEUE.md (repo root) Writes: sol/workqueue-triage.{json,md} """ import hashlib import json import os import re import sys from datetime import datetime, timezone REPO = "/root/troupe-standup" REPO_WIZARD_OUT = os.path.join(REPO, "wizard-work", "sol") SRC = os.path.join(REPO, "WORK-QUEUE.md") OUT_DIR = "/root/.hermes/wizard-outputs/sol" OUT_JSON = os.path.join(OUT_DIR, "workqueue-triage.json") OUT_MD = os.path.join(OUT_DIR, "workqueue-triage.md") REPO_JSON = os.path.join(REPO_WIZARD_OUT, "workqueue-triage.json") REPO_MD = os.path.join(REPO_WIZARD_OUT, "workqueue-triage.md") HEADER = re.compile(r"^## Q(\d+)\s+—\s+(.*)$") # line beginning "**Status:" or "*Status:" or "**Status:**" etc. STATUS_LINE = re.compile(r"^\s*\*\*?Status:\s*(.*)$") OPEN_SIGNALS = ("open", "unclaimed", "draft", "pending") def classify(status_text): t = status_text.lower() if not status_text.strip(): return True return any(sig in t for sig in OPEN_SIGNALS) def extract_status(lines, start): for i in range(start, len(lines)): line = lines[i] m = STATUS_LINE.match(line) if m: body = m.group(1).strip() # strip a matching pair of * / ** surrounding the body while len(body) >= 2 and ((body.startswith("**") and body.endswith("**")) or (body.startswith("*") and body.endswith("*"))): body = body[1:-1].strip() return body return "" def parse_queue(path): with open(path, encoding="utf-8") as fh: lines = fh.read().split("\n") records = [] last_end = 0 for idx, line in enumerate(lines): m = HEADER.match(line) if not m: continue qid = m.group(1) title = m.group(2).strip() status = extract_status(lines, idx + 1) records.append({ "id": qid, "title": title, "status_text": status, "open": classify(status), }) last_end = idx # collapse duplicate ids (e.g. the two "## Q37" blocks) to the last one deduped = {} for rec in reversed(records): deduped[rec["id"]] = rec return [deduped[k] for k in sorted(deduped, key=int)] def md_report(records): total = len(records) open_n = sum(1 for r in records if r["open"]) closed_n = total - open_n top = next((r["id"] for r in records if r["open"]), None) lines = [] lines.append("# Work-queue triage — sol") lines.append("") lines.append("Source: `WORK-QUEUE.md` (sha256 %s)" % sha256_file(SRC)) lines.append("Generated: %s" % generated_utc()) lines.append("Classification rule: OPEN = recorded status is empty or carries " "'open'/'unclaimed'/'draft'/'pending'; otherwise CLOSED.") lines.append("") lines.append("## Summary") lines.append("") lines.append("- total items: %d" % total) lines.append("- OPEN: %d" % open_n) lines.append("- CLOSED: %d" % closed_n) lines.append("- top open item (earliest id): %s" % top) lines.append("") lines.append("## Records") lines.append("") lines.append("| Q | title | status | open |") lines.append("|---|---|---|---|") for r in records: st = r["status_text"][:90] lines.append("| Q%d | %s | %s | %s |" % (int(r["id"]), r["title"], st, "true" if r["open"] else "false")) lines.append("") lines.append("## Open items (%d)" % open_n) lines.append("") if not open_n: lines.append("- none") for r in records: if r["open"]: lines.append("- **Q%d** — %s" % (int(r["id"]), r["title"])) lines.append("") return "\n".join(lines) def sha256_file(path): h = hashlib.sha256() with open(path, "rb") as fh: for chunk in iter(lambda: fh.read(1 << 20), b""): h.update(chunk) return h.hexdigest() def generated_utc(): return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") def main(): os.makedirs(OUT_DIR, exist_ok=True) if not os.path.exists(SRC): print("FATAL: source %s missing" % SRC, file=sys.stderr) sys.exit(1) records = parse_queue(SRC) art = { "generated_utc": generated_utc(), "source": SRC, "source_sha256": sha256_file(SRC), "rule": "OPEN = recorded status is empty or carries 'open'/'unclaimed'/'draft'/'pending'; else CLOSED", "records": records, "summary": { "total": len(records), "open": sum(1 for r in records if r["open"]), "closed": sum(1 for r in records if not r["open"]), "top_open_item": next((r["id"] for r in records if r["open"]), None), }, } with open(OUT_JSON, "w", encoding="utf-8") as fh: json.dump(art, fh, indent=2) fh.write("\n") with open(OUT_MD, "w", encoding="utf-8") as fh: fh.write(md_report(records)) fh.write("\n") for dst_json, dst_md in ((REPO_JSON, REPO_MD),): os.makedirs(REPO_WIZARD_OUT, exist_ok=True) json.dump(art, open(dst_json, "w", encoding="utf-8"), indent=2) open(dst_json, "a", encoding="utf-8").write("\n") open(dst_md, "w", encoding="utf-8").write(md_report(records)) open(dst_md, "a", encoding="utf-8").write("\n") print(md_report(records)) if __name__ == "__main__": main()