findings.py
python
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1
docs: MuseHub-first before ISR #74 — staging solidify NEXT
Human
12 hours ago
| 1 | """Finding validation, stable sort, and verdict derivation (§K5.6).""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | from tools.freeze_reviewer.types import Finding, Verdict |
| 6 | |
| 7 | SEVERITY_RANK = {"BLOCKER": 3, "MAJOR": 2, "MINOR": 1} |
| 8 | |
| 9 | SYNTHETIC_UNCITED_MESSAGE = ( |
| 10 | "Reviewer engine emitted an invalid finding (missing or mismatched citation)." |
| 11 | ) |
| 12 | |
| 13 | |
| 14 | def stable_sort_findings(findings: list[Finding]) -> list[Finding]: |
| 15 | """Sort findings per §K5.6 stable sort rule.""" |
| 16 | return sorted( |
| 17 | findings, |
| 18 | key=lambda item: ( |
| 19 | item.path, |
| 20 | item.line, |
| 21 | -SEVERITY_RANK.get(item.severity, 0), |
| 22 | item.check, |
| 23 | item.message, |
| 24 | ), |
| 25 | ) |
| 26 | |
| 27 | |
| 28 | def assign_finding_ids(findings: list[Finding]) -> list[Finding]: |
| 29 | """Assign F1..Fn after stable sort.""" |
| 30 | sorted_items = stable_sort_findings(findings) |
| 31 | for index, finding in enumerate(sorted_items, start=1): |
| 32 | finding.id = f"F{index}" |
| 33 | return sorted_items |
| 34 | |
| 35 | |
| 36 | def validate_and_repair_findings( |
| 37 | findings: list[Finding], |
| 38 | *, |
| 39 | artifact_path: str, |
| 40 | ) -> list[Finding]: |
| 41 | """Enforce citation hard rule; synthesize blocked finding on violation.""" |
| 42 | repaired: list[Finding] = [] |
| 43 | for finding in findings: |
| 44 | if finding.is_valid_citation(): |
| 45 | repaired.append(finding) |
| 46 | continue |
| 47 | repaired.append( |
| 48 | Finding( |
| 49 | check="OTHER", |
| 50 | severity="BLOCKER", |
| 51 | category="other", |
| 52 | path=artifact_path, |
| 53 | line=1, |
| 54 | message=SYNTHETIC_UNCITED_MESSAGE, |
| 55 | ).with_citation() |
| 56 | ) |
| 57 | return assign_finding_ids(repaired) |
| 58 | |
| 59 | |
| 60 | def derive_verdict( |
| 61 | findings: list[Finding], |
| 62 | *, |
| 63 | human_escalation: list[str], |
| 64 | ) -> Verdict: |
| 65 | """Map findings to pass | findings | blocked.""" |
| 66 | if not findings: |
| 67 | return "pass" |
| 68 | escalation = set(human_escalation) |
| 69 | for finding in findings: |
| 70 | if finding.category in escalation: |
| 71 | return "blocked" |
| 72 | for finding in findings: |
| 73 | if finding.severity == "BLOCKER": |
| 74 | return "blocked" |
| 75 | return "findings" |
| 76 | |
| 77 | |
| 78 | def verdict_exit_code(verdict: Verdict) -> int: |
| 79 | """Map verdict to review-specific exit code.""" |
| 80 | if verdict == "pass": |
| 81 | return 0 |
| 82 | if verdict == "findings": |
| 83 | return 7 |
| 84 | return 8 |
File History
2 commits
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1
docs: MuseHub-first before ISR #74 — staging solidify NEXT
Human
12 hours ago
sha256:4671b7f787ddbe63ced31c895b688c77ab495653b65a730b423329f26b3c1439
feat: K1-P1 complete — agent provenance, build-verification…
Sonnet 4.6
patch
52 days ago