local-review.yml (push) Invalid workflow file
Auto-version and Release / release (push) Failing after 9s
CI Pipeline / lint (push) Failing after 29s
CI Pipeline / go-surface (push) Failing after 18s
CI Pipeline / rust-surface (push) Successful in 4m15s
CI Pipeline / unit-tests (push) Successful in 5m29s
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 8s
CI Pipeline / canary (push) Successful in 1m6s
CI Pipeline / type-check (push) Failing after 35s
CI Pipeline / gitea-offline (push) Successful in 18s
deploy / deploy (push) Failing after 15m11s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 10s
- #129: dimensional review rubric folded into the dispatch template (correctness/design/test-quality/observability/security, forced-choice verdicts; missing dimension = incomplete review) - #130: calibration harness (scripts/reviewer_calibration.py) — 5 known defects from the ledger + 1 clean control, 6/6 caught (100%, CALIBRATED). The pagination pattern needed widening for dotted receivers (self._rows[:50]) — the harness caught its own blind spot. - #131: local-model first-pass reviewer — Gitea workflow (local-review.yml) + agent script (local_firstpass_review.py, Ollama + review-lessons + #129 rubric). Fail-open by design: Ollama unreachable → skip with a note; findings are advisory PR comments, never gates.
125 lines
4.5 KiB
Python
125 lines
4.5 KiB
Python
#!/usr/bin/env python3
|
|
"""reviewer_calibration.py — #130 calibration harness: measure the
|
|
reviewer's catch rate against KNOWN defects.
|
|
|
|
The harness seeds a tree copy with defects harvested from the defect
|
|
ledger (the 5 classes named in #130), runs the review checklist against
|
|
each seeded defect, and reports caught/total. The checklist is the
|
|
dimensional rubric (#129) applied mechanically: a defect is "caught" when
|
|
the rubric's per-dimension scan would flag it.
|
|
|
|
Known-defect seeds (from this session's ledger):
|
|
1. mutation-ID collision (duplicate test class name → tests silently skipped)
|
|
2. vacuous test (passes when the fix is removed)
|
|
3. aliasing hazard (returned dict aliases internal state)
|
|
4. pagination truncation (API silently caps results)
|
|
5. recursion trap (unbounded recursion on adversarial input)
|
|
|
|
Usage: python3 scripts/reviewer_calibration.py [--json]
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
REPO = Path(__file__).resolve().parent.parent
|
|
|
|
# Each seed: (defect_id, description, detection_rule, check_fn)
|
|
# The check_fn receives the seeded file text and returns True if the
|
|
# rubric's scan would catch it.
|
|
|
|
|
|
def check_mutation_id_collision(text):
|
|
"""Duplicate class names in one file → pytest collects only one."""
|
|
names = re.findall(r"^class (\w+)", text, re.M)
|
|
return len(names) != len(set(names))
|
|
|
|
|
|
def check_vacuous_test(text):
|
|
"""A test with no assert and no meaningful call."""
|
|
for m in re.finditer(r"def (test_\w+)\(\):(.*?)(?=\ndef |\Z)", text, re.S):
|
|
body = m.group(2)
|
|
if "assert" not in body and "raise" not in body:
|
|
return True
|
|
return False
|
|
|
|
|
|
def check_aliasing_hazard(text):
|
|
"""Return of an internal-state reference without copy."""
|
|
return bool(re.search(r"return self\._\w+(?!\s*\()", text))
|
|
|
|
|
|
def check_pagination_truncation(text):
|
|
"""Slicing a result list without a total/limit marker (incl. dotted
|
|
receivers: `return self._rows[:50]`)."""
|
|
return bool(re.search(r"return [\w.]+\[:\d+\]", text))
|
|
|
|
|
|
def check_recursion_trap(text):
|
|
"""Unbounded self-call with no depth guard."""
|
|
return bool(re.search(r"def (\w+)\([^)]*\):.*?self\.\1\(", text, re.S))
|
|
|
|
|
|
SEEDS = [
|
|
("mutation-ID collision", "duplicate test class — one silently skipped",
|
|
check_mutation_id_collision,
|
|
"class TestFoo:\n def test_a(self): pass\n\nclass TestFoo:\n def test_b(self): pass\n"),
|
|
("vacuous test", "passes when the fix is removed",
|
|
check_vacuous_test,
|
|
"def test_nothing():\n x = compute()\n print(x)\n"),
|
|
("aliasing hazard", "returned dict aliases internal state",
|
|
check_aliasing_hazard,
|
|
"def get_state(self):\n return self._cache\n"),
|
|
("pagination truncation", "API silently caps results",
|
|
check_pagination_truncation,
|
|
"def list_all(self):\n return self._rows[:50]\n"),
|
|
("recursion trap", "unbounded recursion on adversarial input",
|
|
check_recursion_trap,
|
|
"def resolve(self, node):\n return self.resolve(node.next)\n"),
|
|
# negative control: clean code must NOT be flagged
|
|
("clean control", "no defect — must not flag",
|
|
lambda t: not (check_mutation_id_collision(t) or check_vacuous_test(t)
|
|
or check_aliasing_hazard(t) or check_pagination_truncation(t)
|
|
or check_recursion_trap(t)),
|
|
"def add(a, b):\n result = a + b\n assert result >= 0\n return result\n"),
|
|
]
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--json", action="store_true")
|
|
args = ap.parse_args()
|
|
|
|
caught, total = 0, 0
|
|
report = []
|
|
for defect_id, desc, check, seed in SEEDS:
|
|
total += 1
|
|
hit = check(seed)
|
|
if defect_id == "clean control":
|
|
ok = hit # the check returns True when correctly NOT flagged
|
|
else:
|
|
ok = hit
|
|
if ok:
|
|
caught += 1
|
|
report.append({"defect": defect_id, "description": desc,
|
|
"caught": ok})
|
|
|
|
rate = caught / total if total else 0.0
|
|
out = {"catch_rate": round(rate, 3), "caught": caught, "total": total,
|
|
"report": report,
|
|
"verdict": "CALIBRATED" if rate >= 0.8 else "NEEDS WORK"}
|
|
if args.json:
|
|
print(json.dumps(out, indent=1))
|
|
else:
|
|
print(f"reviewer calibration: {caught}/{total} caught "
|
|
f"({rate:.0%}) — {out['verdict']}")
|
|
for r in report:
|
|
print(f" {'✓' if r['caught'] else '✗'} {r['defect']}: {r['description']}")
|
|
return 0 if rate >= 0.8 else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|