#!/usr/bin/env python3 """ draft_verifier_v1.py — Verify draft artifacts against naming + evidence rules. Measured checks: 1. Filename matches the convention: --.(md|txt) 2. File contains a VERDICT: line. 3. File contains a CHECK: line with a non-empty command. 4. File is non-empty (> 50 bytes). 5. File mentions at least one measured/claimed tag. Usage: python3 draft_verifier_v1.py [ ...] python3 draft_verifier_v1.py /root/troupe-standup/wizard-work/quill/ Exit codes: 0 all files pass 1 one or more files fail 2 no files found """ import os import re import sys from pathlib import Path APPROVED_TYPES = { "brief", "template", "runbook", "rfc", "sop", "checklist" } FAILED: list[str] = [] PASSED: list[str] = [] def check_name(path: Path) -> list[str]: errors = [] stem = path.stem # no extension parts = stem.split("-") if len(parts) < 3: errors.append("filename must have at least --") return errors *slug_parts, typ, date_str = parts slug = "-".join(slug_parts) if not slug: errors.append("slug is empty") if typ not in APPROVED_TYPES: errors.append(f"type '{typ}' not in approved list {sorted(APPROVED_TYPES)}") if not re.fullmatch(r"\d{8}", date_str): errors.append(f"date '{date_str}' is not YYYYMMDD") return errors def check_verdict(content: str) -> list[str]: errors = [] if not re.search(r"(?m)^VERDICT:", content): errors.append("missing VERDICT: line") if not re.search(r"(?m)^CHECK:", content): errors.append("missing CHECK: line") # Ensure CHECK has a real command, not just a tautology check_match = re.search(r"(?m)^CHECK:\s*(.+)", content) if check_match: cmd = check_match.group(1).strip() if not cmd: errors.append("CHECK: command is empty") elif cmd in {"true", "false", ":", "exit 0", "exit 1"}: errors.append(f"CHECK: command '{cmd}' is a tautology") return errors def check_size(path: Path) -> list[str]: errors = [] if path.stat().st_size <= 50: errors.append("file is too short (<= 50 bytes)") return errors def check_evidence(content: str) -> list[str]: errors = [] has_measured = bool(re.search(r"(?i)\bMEASURED\b", content)) has_claimed = bool(re.search(r"(?i)\bCLAIMED\b", content)) if not (has_measured or has_claimed): errors.append("no MEASURED or CLAIMED tags found") return errors def verify_file(path: Path) -> bool: try: content = path.read_text(encoding="utf-8", errors="replace") except Exception as e: FAILED.append(f"{path}: unreadable ({e})") return False checks = [ ("naming", check_name(path)), ("verdict", check_verdict(content)), ("size", check_size(path)), ("evidence", check_evidence(content)), ] ok = True for label, errs in checks: for e in errs: FAILED.append(f"{path}: [{label}] {e}") ok = False if ok: PASSED.append(str(path)) return ok def main() -> int: if len(sys.argv) < 2: print("Usage: python3 draft_verifier_v1.py [ ...]", file=sys.stderr) return 2 targets = [Path(p) for p in sys.argv[1:]] files: list[Path] = [] for t in targets: if t.is_file(): files.append(t) elif t.is_dir(): files.extend(sorted(t.rglob("*.md"))) files.extend(sorted(t.rglob("*.txt"))) else: FAILED.append(f"{t}: not found") if not files: print("draft_verifier_v1: no files to verify", file=sys.stderr) return 2 all_ok = True for f in files: if not verify_file(f): all_ok = False print(f"Checked: {len(files)} Passed: {len(PASSED)} Failed: {len(FAILED)}") if FAILED: print("-- FAILURES --") for line in FAILED: print(line) return 0 if all_ok else 1 if __name__ == "__main__": raise SystemExit(main())