Files
coyote/assets/agents/code-reviewer/scripts/render.py
T
Dark-Alex-17 156f3193a9 fix(review-suite): detect nested finding-verifier faults; harden render/fault markers
code-reviewer verdict.py: finding-verifier now completes "successfully"
with fault markers INSIDE its FINDING_VERIFIER_RESULTS payload
(parse_fault's `pipeline-fault` sentinel id, verify_fault / verdict_gate
crash-guard notes prefixed PIPELINE-FAULT:), which the old
"Agent node failed:" prefix check never saw — a dead parse degraded
silently to "unverified" with no fault recorded. Detect them, anchored on
the sentinel id / note prefix only (never a substring over the report),
counted over the raw entry list so duplicate crash-guard ids are not
collapsed. Faults lead the attention list uncapped; the display cap of 5
applies to non-fault entries; the "always-human trigger(s) fired" reason
fires only when non-fault triggers exist.

code-reviewer render.py: CONTRACT comments at the verdict and summary
lines review-gauntlet's gate parses; every LLM-influenced value rendered
after the summary line (dropped_titles, rigor, bar_provenance,
resolved_surfaces, reason) is newline-sanitised so none can forge a
second summary line.

finding-verifier verdict_gate.py: the outer crash-guard note carries the
PIPELINE-FAULT: prefix (the after-retry UNVERIFIABLE path stays
unprefixed — model misbehaviour, not infrastructure); README names all
three emitters.

deep-research: vet_fault's UNVETTED note tells the critic not to request
revision solely because the assessment is unavailable (it matched
critique's PREPRINT/UNVERIFIED REVISE trigger verbatim, burning the
revision budget on an infra fault); vet_fault and critique_fault dedupe
repeated faults and preserve accumulated faults on crash via
safe_faults(); question_fault keeps its plan-pinned prefix and appends the
lane's question identity so distinct dead lanes no longer collapse in
combine_findings' dedupe.
2026-09-14 10:50:34 -06:00

126 lines
5.1 KiB
Python

#!/usr/bin/env python3
"""Deterministic report assembly for the code-reviewer graph.
Builds the standard report format from the structured findings and prose
sections. Pure templating — no judgment, no re-summarization, no finding
ever added or removed here.
"""
import json
import os
def load_state():
if path := os.environ.get("GRAPH_STATE_FILE"):
with open(path) as f:
return json.load(f)
return json.loads(os.environ.get("GRAPH_STATE", "{}"))
# LLM-influenced values rendered after the summary line: a newline could
# forge a second summary line that review-gauntlet/verdict_gate.py parses.
def one_line(x):
return str(x).replace("\n", " ")
def main():
state = load_state()
v = state.get("verdict_out") or {}
findings = v.get("findings_final") or []
counts = v.get("counts") or {}
changed = state.get("changed_files") or []
rigor = one_line(state.get("resolved_rigor") or "production")
# A fault can leave changed_files empty (a parse fault dies before the
# diff is resolved; a verifier fault fires even on an empty diff) — a
# fault-degraded verdict must still render as a full report. parse_fault
# puts the marker in the reason; verdict.py prepends it to attention.
faulted = str(v.get("reason") or "").startswith("PIPELINE-FAULT:") or any(
isinstance(a, str) and a.startswith("PIPELINE-FAULT:") for a in v.get("attention") or []
)
if not changed and not faulted:
print(json.dumps({"final_report": "# Code Review Summary\n\nNo changes to review."}))
return
verdict = v.get("verdict", "NEEDS-HUMAN")
lines = [
"# Code Review Summary",
"",
# CONTRACT: review-gauntlet/verdict_gate.py anchors on this wording
f"**Verdict: {verdict}** — {one_line(v.get('reason', ''))}",
"",
"## Walkthrough",
state.get("walkthrough") or "(no walkthrough provided)",
"",
"## Changes",
"",
"| File | Changes | Findings |",
"|------|---------|----------|",
]
desc_by_file = {}
for row in state.get("changes_rows") or []:
if isinstance(row, dict) and row.get("file"):
desc_by_file[row["file"]] = row.get("desc", "")
blocking = [f for f in findings if f.get("section") == "blocking"]
deferred = [f for f in findings if f.get("section") == "deferred"]
for f in changed:
per = [x for x in blocking if x.get("file") == f]
tally = " ".join(
f"{icon} {n}" for icon, n in
((i, sum(1 for x in per if x.get("icon") == i)) for i in ("🔴", "🟡", "🟢", "💡"))
if n
) or "—"
lines.append(f"| `{f}` | {desc_by_file.get(f, '')} | {tally} |")
lines += ["", "## Detailed Findings", ""]
by_file = {}
for x in blocking:
by_file.setdefault(x.get("file", "(unattributed)"), []).append(x)
if by_file:
for f in sorted(by_file):
lines += [f"### `{f}`", ""]
for x in by_file[f]:
lines += [x.get("block", ""), ""]
else:
lines += ["No blocking findings.", ""]
lines += ["## Cross-File Concerns", state.get("cross_file") or "None", ""]
if (state.get("operational_history") or "").strip():
lines += ["## Operational history", state["operational_history"], ""]
if (state.get("org_context_section") or "").strip():
lines += ["## Org context", state["org_context_section"], ""]
if deferred and rigor != "production":
lines += ["## Deferred by quality bar", ""]
for x in deferred:
lines += [x.get("block", ""), ""]
attention = v.get("attention") or []
if verdict == "NEEDS-HUMAN" and attention:
lines += ["## Human attention required", ""]
lines += [f"- {a}" for a in attention]
lines += [""]
miss_ids = sorted({
m for x in findings for m in
__import__("re").findall(r"MISS-\d+", (x.get("title") or "") + (x.get("block") or ""))
})
dropped = ", ".join(one_line(t) for t in v.get("dropped_titles", [])[:5])
lines += [
"---",
# CONTRACT: review-gauntlet/verdict_gate.py parses this line — keep the format in sync
f"*Reviewed {len(changed)} files, found {counts.get('🔴', 0)} critical, "
f"{counts.get('🟡', 0)} warnings, {counts.get('🟢', 0)} suggestions, "
f"{counts.get('💡', 0)} nitpicks ({v.get('deferred_count', 0)} deferred by quality bar)*",
f"*Quality bar: {rigor} — provenance: {one_line(state.get('bar_provenance', '?'))}; "
f"surfaces: {one_line(state.get('resolved_surfaces') or 'none')}*",
f"*Verdict: {verdict}; verifier: dropped {v.get('dropped_count', 0)} provably-false"
+ (f" ({dropped})" if v.get("dropped_count") else "")
+ f"; review-miss patterns applied: {', '.join(miss_ids) if miss_ids else 'none relevant'}*",
]
print(json.dumps({"final_report": "\n".join(lines)}))
try:
main()
except Exception as e: # noqa: BLE001
print(json.dumps({"final_report": f"# Code Review Summary\n\n**Verdict: NEEDS-HUMAN** — report rendering error: {e}"}))