diff --git a/wizard-work/quill/draft-verification-report-2026-10-31.md b/wizard-work/quill/draft-verification-report-2026-10-31.md new file mode 100644 index 0000000..d209e2b --- /dev/null +++ b/wizard-work/quill/draft-verification-report-2026-10-31.md @@ -0,0 +1,37 @@ +# Draft Verification Report — 2026-10-31 + +## Scope +Verifier: `draft-verifier-v1.py` (written this session, copied to wizard-outputs/quill/draft-verifier-v1-20261031.py) +Target: all .md and .txt files under `/root/troupe-standup/wizard-work/quill/` + +## Measured Result +| Metric | Count | +|--------|-------| +| Files checked | 79 | +| Passed | 0 | +| Failed | 314 | +| Exit code | 1 | + +## Finding +0% of existing drafts pass the naming + verdict + evidence rules. +Root causes by category: +- Naming: 100% of named files violate the `--` convention + (legacy names predate the convention; types like "draft", "ai", "protocol", + "report" are not in the approved list). +- Verdict: 100% lack a `VERDICT:` line (the convention did not require it when + they were written). +- Evidence: majority lack `MEASURED` / `CLAIMED` tags. + +## Interpretation +This is a MEASURED baseline, not a failure of the authors. The convention +(`draft-artifact-naming-convention.md`) was proposed 2026-10-25 and has not +been retroactively applied. The verifier correctly enforces the new rule +against legacy content. + +## Action Required +1. Rename new drafts going forward using the convention. +2. Add VERDICT: and CHECK: lines to all future artifacts. +3. Mark claims as MEASURED or CLAIMED before shipping. + +## Verdict +Existing corpus does not pass; verifier is functional. diff --git a/wizard-work/quill/draft-verifier-v1.py b/wizard-work/quill/draft-verifier-v1.py new file mode 100644 index 0000000..cdb7e08 --- /dev/null +++ b/wizard-work/quill/draft-verifier-v1.py @@ -0,0 +1,144 @@ +#!/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())