Compare commits
3
Commits
5bded8094b
...
8dbbb26f55
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8dbbb26f55 | ||
|
|
4bdd23e70e | ||
|
|
9f97f411c9 |
No files matched your search
@@ -1,4 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
cd "$(dirname "$(readlink -f "$0")")"
|
|
||||||
python3 test_damerau_levenshtein.py
|
|
||||||
@@ -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 `<slug>-<type>-<date>` 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.
|
||||||
@@ -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: <slug>-<type>-<date>.(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 <path> [<path> ...]
|
||||||
|
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 <slug>-<type>-<date>")
|
||||||
|
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 <path> [<path> ...]", 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())
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
This test file contains exactly ten words total here now
|
||||||
@@ -23,7 +23,7 @@ def main():
|
|||||||
text = sys.stdin.read()
|
text = sys.stdin.read()
|
||||||
|
|
||||||
count = count_words(text)
|
count = count_words(text)
|
||||||
print(f"Word count: {count}")
|
print(count)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in new issue
Block a user