Four defects, all found reviewing PR #9 (two of them by pragent-bot's own
review of that PR, which the anchoring bug then misplaced):
* compress_diff dropped context lines but copied the original `@@` hunk
header verbatim, so the header no longer described the lines beneath it.
parse_diff_anchors then walked stale headers and produced anchor sets
shifted by the number of elided lines, misplacing inline comments or
demoting them to bullets. Each surviving run of lines is now re-emitted as
its own hunk with a recomputed `@@ -a,b +c,d @@`, so the output stays a
valid unified diff whose numbers describe the real post-change file. The
pseudo-marker `@@ … N context line(s) omitted … @@` is gone; it parsed as
a hunk header and reset the anchor counter to 0. Anchoring additionally
runs on the raw diff now, so the prompt window can never shrink the
anchorable set.
* compress_diff's `_FILE_HEADER` regex matched diff *body* lines: a removed
YAML `---` separator or an added `++` line was read as a file header,
truncating the hunk and dropping its `@@` header with it. Body detection
is now prefix-based, with a full-shape hunk-header regex.
* extract_finding_bullets could not match the bullets pragent itself posts:
summary_bullets renders an emoji severity badge between the `-` and the
`[SEV]` tag, which the regex rejected, so compact_prior_reviews always
returned [] and every re-review repeated its previous findings.
* triage returning `{"lenses":[]}` — documented in .opencode/agents/triage.md
as "no lens has surface, skip the fan-out" — ran every lens instead, since
_intersect_with_triage mapped an empty selection to "all" and the call site
had a second `or reviewers` fallback. `[]` and None are now distinct
outcomes: `[]` skips, None fails open. A roster naming only unknown lens
ids now fails open rather than silencing the review. The skip path returns
a well-formed empty-findings response instead of "", which had landed in
ai_review's unparseable-output branch and posted "AI review produced no
parseable output" — a malfunction message for a normal verdict.
Also: non-URL references (a CVE id, a doc title) rendered as
`[CVE-2024-1234](CVE-2024-1234)`, a broken relative link in Gitea — now
plain text. PRAGENT_DIFF_CONTEXT and friends parse through _int_env, so a
typo logs and falls back instead of killing a review mid-flight. Removed
format_usage_section, dead since the collapsible usage block replaced it and
carrying a duplicate copy of the price-target logic.
Tests: 290 -> 301. New coverage for hunk-header fidelity before/after
compression, header-shaped content lines, the bullet round-trip against the
real renderer, and triage's three outcomes (previously untested).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B11e8TZZxJyzHW7jj7KWUN
The canalhandia PR review lost all findings because the agent ran out of
context before emitting the closing json fence. Three failure modes hit
the old regex \{.*?\}:
* nested objects inside the fence truncated at the first }
* bare arrays (no {summary, findings} wrapper) returned []
* unfenced JSON in the prose tail was never reached (first not last)
Replace the regex with a balanced-brace scanner:
* _last_json_block walks the fence contents with a depth counter so
nested objects survive
* _last_balanced_json + _balanced_json_substring handle bare arrays and
prose-tail JSON when no fence is present
* _parse_json_tolerant returns list as well as dict; parse_findings and
parse_review_output accept a bare array as the outer value
Agent prompt tightened: reserve the final step for emitting the JSON
block so the analysis isn't lost when context runs out.
10 new tests in tests/pilot/test_ai_review.py cover the new shapes.
Co-Authored-By: Claude <noreply@anthropic.com>
Two pure stdlib helpers that shrink what lands in the model prompt:
* compress_diff(diff, *, context=2) — re-renders a unified diff so each
hunk keeps only unchanged lines on either side of its +/- lines.
File headers + hunk headers + +/- lines preserved verbatim. Pure-context
hunks dropped (rare but legal — git emits them on whitespace-only diffs).
Collapsed gaps of >=5 lines emit a single '@@ … N context line(s) omitted
… @@' marker so the reviewer knows code was elided. Smaller gaps stay
silent — the marker would be longer than the elision.
* extract_finding_bullets(review_body) — pulls the lines of a prior review
that look like a pragent finding (- **[SEVERITY]** path:line — …) and
drops everything else. The model already has the diff; repeating the
prose is just token burn.
No I/O, no network. Tolerant of malformed input — never raises. 14 unit
tests cover both helpers, including an anchor-preservation check against
parse_diff_anchors to guarantee compress-then-anchor still works.
Wiring in ai_review/opencode_review lives in the next commit.