mirror of
https://github.com/theclaymethod/unslop.git
synced 2026-09-19 01:45:09 +08:00
d81f519616
* Optimize and harden eval loop * Run behavioral acceptance on Luna * Define core product acceptance gate * Make behavioral acceptance honest and cheaper * Harden clean no-op and contextual scanner gating * Freeze blind Luna core benchmark v2 * Allow verified no-op benchmark results * Keep advisory benchmark signals non-blocking * Transform UNSLOP around the core rewrite contract * Fix release lint failures * Restore Python 3.8 compatibility --------- Co-authored-by: Clayton Kim <claytonkim@users.noreply.github.com>
374 lines
14 KiB
Python
374 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
"""Offline contribution scaffolder for new unslop AI-isms."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from datetime import date, datetime
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
CONTRIB_ROOT = ROOT / ".unslop" / "contrib"
|
|
TODO_MARKER = "TODO:"
|
|
SLUG_RE = re.compile(r"^[a-z0-9][a-z0-9_-]{0,63}$")
|
|
|
|
GATE_COMMANDS = [
|
|
["python3", "evals/check.py", "--full"],
|
|
]
|
|
|
|
|
|
def read_text(path: Path) -> str:
|
|
try:
|
|
return path.read_text(encoding="utf-8")
|
|
except OSError as exc:
|
|
print(json.dumps({"error": f"could not read {path}: {exc}"}))
|
|
raise SystemExit(2) from exc
|
|
|
|
|
|
def write_json(path: Path, data: object) -> None:
|
|
path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
|
|
|
|
def sha256_text(text: str) -> str:
|
|
return hashlib.sha256(text.encode("utf-8")).hexdigest()
|
|
|
|
|
|
def word_count(text: str) -> int:
|
|
return len(re.findall(r"[A-Za-z0-9']+", text))
|
|
|
|
|
|
def shorten(text: str, limit: int = 72) -> str:
|
|
one_line = " ".join(text.split())
|
|
if len(one_line) <= limit:
|
|
return one_line
|
|
return one_line[: limit - 1].rstrip() + "..."
|
|
|
|
|
|
def run_scanner(command: list[str], text: str) -> subprocess.CompletedProcess[str]:
|
|
return subprocess.run(command, input=text, capture_output=True, text=True, cwd=ROOT, timeout=30)
|
|
|
|
|
|
def scanner_findings(text: str) -> list[dict[str, object]]:
|
|
findings: list[dict[str, object]] = []
|
|
for name, command in (
|
|
("banned_phrase", ["python3", "scripts/banned_phrase_scan.py"]),
|
|
("structure", ["python3", "scripts/structure_scan.py"]),
|
|
):
|
|
proc = run_scanner(command, text)
|
|
try:
|
|
data = json.loads(proc.stdout)
|
|
except json.JSONDecodeError:
|
|
data = {}
|
|
violations = data.get("violations", [])
|
|
if name == "structure":
|
|
violations = [
|
|
{
|
|
"phrase": flag.get("metric", ""),
|
|
"category": flag.get("metric", ""),
|
|
"severity": flag.get("severity", ""),
|
|
}
|
|
for flag in data.get("flags", [])
|
|
]
|
|
for violation in violations:
|
|
findings.append(
|
|
{
|
|
"scanner": name,
|
|
"phrase": violation.get("phrase", ""),
|
|
"category": violation.get("category", ""),
|
|
"severity": violation.get("severity", ""),
|
|
}
|
|
)
|
|
return findings
|
|
|
|
|
|
def redaction_pairs(values: list[str]) -> list[tuple[str, str]]:
|
|
pairs = []
|
|
for value in values:
|
|
if "=" not in value:
|
|
print(json.dumps({"error": "--redact values must use orig=REPL"}))
|
|
raise SystemExit(2)
|
|
orig, repl = value.split("=", 1)
|
|
pairs.append((orig, repl))
|
|
return pairs
|
|
|
|
|
|
def apply_redactions(text: str, tell: str, pairs: list[tuple[str, str]]) -> str:
|
|
before_count = text.count(tell)
|
|
redacted = text
|
|
for orig, repl in pairs:
|
|
redacted = redacted.replace(orig, repl)
|
|
if before_count == 0 or redacted.count(tell) != before_count:
|
|
print(json.dumps({"error": "redaction alters the specimen", "tell": tell}))
|
|
raise SystemExit(4)
|
|
return redacted
|
|
|
|
|
|
def row_fn(slug: str, tell: str, snippet: str) -> dict[str, object]:
|
|
assertion_tell = tell.casefold()
|
|
return {
|
|
"id": f"CONTRIB-FN-{slug}",
|
|
"target": "script",
|
|
"category": "scanner_false_negative",
|
|
"stdin": snippet,
|
|
"assertions": [
|
|
{"type": "json", "path": "total_violations", "gte": 1},
|
|
{"type": "violation_phrase_contains", "value": assertion_tell},
|
|
],
|
|
}
|
|
|
|
|
|
def row_fp_template(slug: str, category: str, tell: str) -> dict[str, object]:
|
|
return {
|
|
"id": f"CONTRIB-FP-{slug}",
|
|
"category": "scanner_false_positive",
|
|
"protects": category,
|
|
"target": "script",
|
|
"stdin": "TODO: add a literal or domain-specific use that should remain clean.",
|
|
"assertions": [{"type": "json", "path": "total_violations", "equals": 0}],
|
|
}
|
|
|
|
|
|
def render_report(
|
|
manifest: dict[str, object],
|
|
snippet: str,
|
|
gate_results: str = "",
|
|
include_rec: bool = False,
|
|
) -> str:
|
|
redactions = manifest.get("redactions", [])
|
|
redaction_note = "names/numbers redacted; tell verbatim" if redactions else "none"
|
|
rows = [
|
|
f"| CONTRIB-FN-{manifest['pattern_name']} | FN | exact specimen flags `{manifest['tell']}` |",
|
|
f"| CONTRIB-FP-{manifest['pattern_name']} | FP | literal-use protection for `{manifest['tell']}` |",
|
|
]
|
|
if include_rec:
|
|
rows.append("| CONTRIB-REC | REC | existing-word recall still flags |")
|
|
quoted = "\n".join(f"> {line}" if line else ">" for line in snippet.splitlines())
|
|
pattern_added = manifest.get(
|
|
"pattern_added",
|
|
f"TODO: regex or phrase for `{manifest['tell']}`",
|
|
)
|
|
return (
|
|
f"# Add {manifest['category']} pattern: {shorten(str(manifest['tell']))}\n\n"
|
|
"## The specimen\n\n"
|
|
f"{quoted}\n\n"
|
|
f"- Source genre: {manifest.get('source_genre', 'TODO: source genre')}\n"
|
|
f"- Date: {manifest['date']}\n"
|
|
f"- Redaction note: {redaction_note}\n\n"
|
|
"## Why it's an AI-ism\n\n"
|
|
f"{manifest.get('rationale', 'TODO: explain why this phrase is a reusable AI-writing tell in 2-4 sentences.')}\n\n"
|
|
"## Detection\n\n"
|
|
f"- Pattern added: {pattern_added}\n"
|
|
f"- Severity: {manifest.get('severity', 'TODO: hard or soft')}\n"
|
|
f"- Gating rationale: {manifest.get('gating_rationale', 'TODO: explain literal-use boundary')}\n"
|
|
"- Catalog entry location: references/taboo-phrases.md\n\n"
|
|
"## Evals\n\n"
|
|
"| row id | kind | what it pins |\n"
|
|
"|---|---|---|\n"
|
|
+ "\n".join(rows)
|
|
+ "\n\n"
|
|
"The FN stdin is the unmodified specimen after approved redaction.\n\n"
|
|
"## Gate results\n\n"
|
|
f"{gate_results or 'TODO: paste gate tails from verify.'}\n\n"
|
|
"## Checklist\n\n"
|
|
"- [ ] eval-first (row was red before the pattern)\n"
|
|
"- [ ] literal-use FP row included\n"
|
|
"- [ ] REC row if an existing word was gated\n"
|
|
"- [ ] catalog + scanner parity green\n"
|
|
"- [ ] coverage gate green (pattern exercised)\n"
|
|
"- [ ] snippet publication approved by the user\n"
|
|
)
|
|
|
|
|
|
def cmd_precheck(args: argparse.Namespace) -> int:
|
|
snippet = read_text(Path(args.snippet_file))
|
|
findings = scanner_findings(snippet)
|
|
if findings:
|
|
print(json.dumps({"status": "already_covered", "findings": findings}, indent=2, sort_keys=True))
|
|
return 3
|
|
print(json.dumps({"status": "clean", "words": word_count(snippet)}, indent=2, sort_keys=True))
|
|
return 0
|
|
|
|
|
|
def cmd_scaffold(args: argparse.Namespace) -> int:
|
|
if not SLUG_RE.match(args.pattern_name):
|
|
print(
|
|
json.dumps(
|
|
{
|
|
"error": f"invalid pattern name: {args.pattern_name!r}; use lowercase letters, digits, - or _",
|
|
}
|
|
)
|
|
)
|
|
return 2
|
|
source = Path(args.snippet)
|
|
snippet = read_text(source)
|
|
if args.tell not in snippet:
|
|
print(json.dumps({"error": "tell substring not found", "tell": args.tell}))
|
|
return 2
|
|
try:
|
|
manifest_date = datetime.strptime(args.date, "%Y-%m-%d").date().isoformat()
|
|
except ValueError:
|
|
print(json.dumps({"error": "--date must use YYYY-MM-DD", "date": args.date}))
|
|
return 2
|
|
redactions = redaction_pairs(args.redact or [])
|
|
redacted = apply_redactions(snippet, args.tell, redactions)
|
|
bundle = CONTRIB_ROOT / args.pattern_name
|
|
bundle.mkdir(parents=True, exist_ok=True)
|
|
manifest = {
|
|
"category": args.category,
|
|
"date": manifest_date,
|
|
"pattern_name": args.pattern_name,
|
|
"pre_redaction_sha256": sha256_text(snippet),
|
|
"post_redaction_sha256": sha256_text(redacted),
|
|
"redactions": [{"from": orig, "to": repl} for orig, repl in redactions],
|
|
"tell": args.tell,
|
|
"word_count": word_count(redacted),
|
|
}
|
|
write_json(bundle / "row_fn.json", row_fn(args.pattern_name, args.tell, redacted))
|
|
write_json(bundle / "row_fp_TEMPLATE.json", row_fp_template(args.pattern_name, args.category, args.tell))
|
|
write_json(bundle / "manifest.json", manifest)
|
|
(bundle / "snippet.txt").write_text(redacted, encoding="utf-8")
|
|
(bundle / "report.md").write_text(render_report(manifest, redacted), encoding="utf-8")
|
|
print(json.dumps({"bundle": str(bundle.relative_to(ROOT)), "status": "scaffolded"}, indent=2, sort_keys=True))
|
|
return 0
|
|
|
|
|
|
def run_row(row: dict[str, object]) -> subprocess.CompletedProcess[str]:
|
|
return subprocess.run(
|
|
["python3", "scripts/banned_phrase_scan.py"],
|
|
input=str(row.get("stdin", "")),
|
|
capture_output=True,
|
|
text=True,
|
|
cwd=ROOT,
|
|
timeout=30,
|
|
)
|
|
|
|
|
|
def row_assertions_pass(row: dict[str, object], proc: subprocess.CompletedProcess[str]) -> bool:
|
|
try:
|
|
data = json.loads(proc.stdout)
|
|
except json.JSONDecodeError:
|
|
return False
|
|
for assertion in row.get("assertions", []):
|
|
if assertion.get("type") == "json":
|
|
cur = data
|
|
for part in assertion["path"].split("."):
|
|
cur = cur[int(part)] if isinstance(cur, list) else cur[part]
|
|
if "gte" in assertion and not cur >= assertion["gte"]:
|
|
return False
|
|
if "equals" in assertion and cur != assertion["equals"]:
|
|
return False
|
|
elif assertion.get("type") == "violation_phrase_contains":
|
|
phrases = [v.get("phrase", "") for v in data.get("violations", [])]
|
|
needle = str(assertion["value"]).casefold()
|
|
if not any(needle in str(phrase).casefold() for phrase in phrases):
|
|
return False
|
|
else:
|
|
return False
|
|
return True
|
|
|
|
|
|
def tail(text: str, lines: int = 12) -> str:
|
|
split = text.splitlines()
|
|
return "\n".join(split[-lines:])
|
|
|
|
|
|
def verify_bundle(bundle: Path, run_gates: bool = True) -> tuple[bool, str]:
|
|
if not bundle.exists():
|
|
return False, "missing bundle"
|
|
row_path = bundle / "row_fn.json"
|
|
report_path = bundle / "report.md"
|
|
manifest_path = bundle / "manifest.json"
|
|
snippet_path = bundle / "snippet.txt"
|
|
for path in (row_path, report_path, manifest_path, snippet_path):
|
|
if not path.exists():
|
|
return False, f"missing {path.name}"
|
|
report = report_path.read_text(encoding="utf-8")
|
|
if TODO_MARKER in report:
|
|
return False, "report contains TODO markers"
|
|
row = json.loads(row_path.read_text(encoding="utf-8"))
|
|
snippet = snippet_path.read_text(encoding="utf-8")
|
|
if row.get("stdin") != snippet:
|
|
return False, "row_fn stdin does not equal snippet.txt byte-for-byte"
|
|
proc = run_row(row)
|
|
assertion_ok = row_assertions_pass(row, proc)
|
|
red_first = not assertion_ok
|
|
gate_results = [f"red-first: {'ok' if red_first else 'already green'}"]
|
|
ok = red_first
|
|
if not red_first:
|
|
ok = True
|
|
gate_results[0] = "red-first: already green; proposed pattern appears active"
|
|
if run_gates:
|
|
for command in GATE_COMMANDS:
|
|
proc = subprocess.run(command, capture_output=True, text=True, cwd=ROOT, timeout=120)
|
|
gate_results.append(
|
|
"### "
|
|
+ " ".join(command)
|
|
+ f"\n\n```text\nexit={proc.returncode}\n{tail(proc.stdout or proc.stderr)}\n```"
|
|
)
|
|
ok = ok and proc.returncode == 0
|
|
final = "\n\n".join(gate_results)
|
|
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
report_path.write_text(render_report(manifest, snippet, final, (bundle / "row_rec.json").exists()), encoding="utf-8")
|
|
return ok, final
|
|
|
|
|
|
def cmd_verify(args: argparse.Namespace) -> int:
|
|
ok, message = verify_bundle(Path(args.bundle), run_gates=not args.no_gates)
|
|
print(message)
|
|
return 0 if ok else 1
|
|
|
|
|
|
def cmd_report(args: argparse.Namespace) -> int:
|
|
bundle = Path(args.bundle)
|
|
if not bundle.exists():
|
|
print(json.dumps({"error": "missing bundle", "bundle": str(bundle)}))
|
|
return 2
|
|
manifest = json.loads((bundle / "manifest.json").read_text(encoding="utf-8"))
|
|
snippet = (bundle / "snippet.txt").read_text(encoding="utf-8")
|
|
report = (bundle / "report.md").read_text(encoding="utf-8")
|
|
gate_marker = "## Gate results\n\n"
|
|
checklist_marker = "\n\n## Checklist"
|
|
gate_results = ""
|
|
if gate_marker in report and checklist_marker in report:
|
|
gate_results = report.split(gate_marker, 1)[1].split(checklist_marker, 1)[0]
|
|
print(render_report(manifest, snippet, gate_results, (bundle / "row_rec.json").exists()).rstrip())
|
|
return 0
|
|
|
|
|
|
def parse_args(argv: list[str]) -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
precheck = sub.add_parser("precheck")
|
|
precheck.add_argument("snippet_file")
|
|
precheck.set_defaults(func=cmd_precheck)
|
|
scaffold = sub.add_parser("scaffold")
|
|
scaffold.add_argument("--snippet", required=True)
|
|
scaffold.add_argument("--tell", required=True)
|
|
scaffold.add_argument("--category", required=True)
|
|
scaffold.add_argument("--pattern-name", required=True)
|
|
scaffold.add_argument("--redact", action="append")
|
|
scaffold.add_argument("--date", default=date.today().isoformat())
|
|
scaffold.set_defaults(func=cmd_scaffold)
|
|
verify = sub.add_parser("verify")
|
|
verify.add_argument("--bundle", required=True)
|
|
verify.add_argument("--no-gates", action="store_true")
|
|
verify.set_defaults(func=cmd_verify)
|
|
report = sub.add_parser("report")
|
|
report.add_argument("--bundle", required=True)
|
|
report.set_defaults(func=cmd_report)
|
|
return parser.parse_args(argv)
|
|
|
|
|
|
def main(argv: list[str]) -> int:
|
|
args = parse_args(argv)
|
|
return args.func(args)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main(sys.argv[1:]))
|