feat(review): filter unreviewable content from diff context - #1008
feat(review): filter unreviewable content from diff context#1008guyoron1 wants to merge 11 commits into
Conversation
Functional tests did not runFunctional tests run automatically for org/repo members and collaborators on pull requests. For other contributors, a maintainer must add the |
PR Summary by QodoFilter unreviewable content from PR review diff context
AI Description
Diagram
High-Level Assessment
Files changed (4)
|
Code Review by Qodo
1.
|
|
Code review by qodo was updated up to the latest commit cb8cc6d |
|
/review |
PR Reviewer Guide 🔍Warning
Here are some key observations to aid the review process:
|
|
/agentic_review |
|
Code review by qodo was updated up to the latest commit 61f404b |
|
/agentic_review |
|
Code review by qodo was updated up to the latest commit cc3f219 |
waynesun09
left a comment
There was a problem hiding this comment.
Review sweep at head cc3f2196. Seven findings posted inline (1 CRITICAL, 6 MEDIUM), none overlapping the existing review threads:
- CRITICAL —
scripts/post-review.shwas edited directly;post-review.src.shwas not, somake check-bundlefails and a rebuild silently removes the new threshold exemption. - MEDIUM — an author-controlled
@generatedline removes a whole file from review on non-protected paths. - MEDIUM — Go/protoc canonical generated markers and context-line markers are not detected; the fixture only passes because of an extra
@generatedline. - MEDIUM — unquoted paths containing spaces carry a trailing tab on
---/+++and are never classified. - MEDIUM — the filter is a silent no-op on GitLab-shaped input, with no disclosure.
- MEDIUM — the cited
route-review-model.sh/is_lock()in fullsend does not exist. - MEDIUM — SKILL.md step 2 now contradicts bucket 3 ("after filtering").
One further item (large-PR per-file invocations truncating a shared exclusion-summary file) is already raised in the Reviewer Guide comment under "Summary Loss", so it is not repeated here.
Review-only; not requesting changes.
| # them here would undo the exemption the agent honors. | ||
| jq --argjson rank "$threshold_rank" ' | ||
| .findings |= [.[] | select( | ||
| .category == "provenance-warning" or .category == "excluded-content" or |
There was a problem hiding this comment.
[CRITICAL] post-review.sh edited directly; post-review.src.sh not updated (make check-bundle fails)
scripts/post-review.sh is a generated bundle (header: "GENERATED from post-review.src.sh — DO NOT EDIT. Run: make script-build"). Round 2 added the .category == "provenance-warning" or .category == "excluded-content" exemption here (lines 552-557), but scripts/post-review.src.sh (around lines 142-150) was not touched: git log dc7c805..HEAD -- scripts/post-review.src.sh is empty and neither category string appears in the src file.
Verified at head cc3f2196: make check-bundle exits 1 with Bundled script stale: scripts/post-review.sh (run make script-build), and a forced make script-build regenerates post-review.sh with the exemption and its comment removed. CI has not caught this because the script-test workflow runs on 61f404b2 and cc3f2196 both ended in action_required (never executed); the only successful run was on cb8cc6dd, before the exemption existed.
Once the bundle is rebuilt, excluded-content / provenance-warning info findings are dropped again at the default low threshold — exactly the behaviour agents/review.md:66-68 and SKILL.md step 7 now promise is exempt.
Suggested fix: Apply the jq exemption (and its comment) to scripts/post-review.src.sh, run make script-build, and commit both files together. While there, update the mirrored filter_findings_json in scripts/post-review-test.sh (around line 144, commented "keep in sync") and add cases showing provenance-warning / excluded-content survive at threshold=low while a plain info finding is dropped.
There was a problem hiding this comment.
Fixed in ccbb0aa — good catch. The exemption now lives in post-review.src.sh and the bundle is regenerated via make script-build; make check-bundle exits 0.
| sec_adds++ | ||
| if (added_seen < 5) { | ||
| added_seen++ | ||
| if (index(line, "@generated") > 0) generated_hit = 1 |
There was a problem hiding this comment.
[MEDIUM] Author-controlled @generated marker hides an entire file from automated review
Rule 3 (line 310, documented at lines 28-30) strips a whole section whenever the literal @generated appears in any of the first 5 added lines. That string is untrusted PR content: a contributor adds one // @generated comment line at the top of an ordinary source file and the section is removed from every sub-agent's diff, and step 2b (SKILL.md:178-183) then also omits its full contents. The only trace is an info-level excluded-content finding listing the path with reason generated-marker.
Verified mitigation: the protected-path check in post-review.src.sh:184-262 runs on forge_get_pr_files() (the API file list, not the diff), so changes under the default REVIEW_PROTECTED_PATHS (scripts/, .github/, skills/, agents/, harness/, ...) still force approve→comment. The bypass is therefore fully effective only on non-protected paths — but those are precisely the paths the bot is allowed to approve unassisted, which is the new exposure this PR introduces (previously small PRs were never filtered at all).
Suggested fix: Bound the marker rule: apply it only when the path also looks generated (e.g. *.pb.go, *_gen.go, *.generated.*, gen/ or generated/ components), or require the marker to appear as the first added line of a new file (--- /dev/null), and/or have the excluded-content disclosure call out generated-marker exclusions on non-generated-looking paths at a higher severity so a human notices. Document the accepted-risk boundary in the script header either way.
There was a problem hiding this comment.
Fixed in ccbb0aa — content-marker stripping is now gated on generated-looking paths (protoc/codegen suffixes, generated|dist|build components); a planted @generated on ordinary source keeps the section, no disclosure line. Negative test added.
| if (added_seen < 5) { | ||
| added_seen++ | ||
| if (index(line, "@generated") > 0) generated_hit = 1 | ||
| } |
There was a problem hiding this comment.
[MEDIUM] Generated-file detection misses Go/protoc canonical markers and context-line markers; fixture masks the gap
SKILL.md dropped protobuf from the large-PR exclusion list and states the script is now the one definition of "generated", but the only content rule is index(line, "@generated") on added lines. protoc-gen-go emits // Code generated by protoc-gen-go. DO NOT EDIT. (the golang.org/s/generatedcode convention), and protoc's Python generator emits # Generated by the protocol buffer compiler. DO NOT EDIT! — neither contains @generated.
Verified at head: a .pb.go section whose first added lines are // Code generated by protoc-gen-go. DO NOT EDIT. / // versions: / package gen passes through unfiltered with an empty summary. The GENERATED_ADDED fixture (scripts/filter-review-diff-test.sh:96-103) contains that real Go marker but only passes because a separate +// @generated line was added beneath it.
Separately, regenerating an existing generated file leaves the header as unchanged context ( prefix), which the check never inspects (lines 305-311 only look at + lines), so the common regenerate-and-commit case is never caught either.
Suggested fix: Extend the marker check to the Go convention (^\+// Code generated .* DO NOT EDIT\.$) and protoc's Generated by the protocol buffer compiler, consider inspecting the first few context lines of the first hunk as well, and add fixtures that contain only those markers. Alternatively restore protobuf to the SKILL.md list and drop the "one definition of generated" claim.
There was a problem hiding this comment.
Fixed in ccbb0aa — has_generated_marker() adds the anchored Go marker and the protoc header, checked on added and context lines within the bounded window; removed-only still never strips. .pb.go context-line test added.
| } | ||
| if (line ~ /^\+\+\+ /) { | ||
| p = substr(line, 5) | ||
| if (p != "/dev/null" && substr(p, 1, 2) == "b/") new_path = substr(p, 3) |
There was a problem hiding this comment.
[MEDIUM] Paths containing spaces are never classified: git tab-terminates ---/+++ and +++ overrides the header path
For any unquoted path containing a space, git appends a TAB to the --- a/... and +++ b/... lines. Lines 263 and 269 take substr(p, 3) verbatim, so new_path ends in \t, and section_path() (line 158) prefers new_path over the correctly parsed hdr_new from round 1's header fallback — so the fallback never helps here. Every $-anchored rule then misses.
Verified with a real git diff at head: lib x/foo.min.js and pkg dir/package-lock.json both pass through unfiltered and the summary file is 0 bytes.
This is distinct from the existing thread at line 265 (git-quoted paths, fixed in round 1) and from test 15 (the b/ split in the diff --git line): the defect is the trailing tab on the unquoted ---/+++ lines of those same files.
Suggested fix: Strip a trailing tab in both branches (sub(/\t$/, "", p) before the a/ / b/ prefix check at lines 263 and 269), and add a fixture generated from real git output for a space-bearing lockfile/minified path.
There was a problem hiding this comment.
Fixed in ccbb0aa — path extraction strips from the first tab, so tab-terminated headers for spacey paths classify; regression tests use real git-shaped fixtures for both your examples.
| line = $0 | ||
|
|
||
| if (!in_diff) { | ||
| if (line ~ /^diff --git /) { |
There was a problem hiding this comment.
[MEDIUM] Filter is a silent no-op on GitLab input (no diff --git sections) with no disclosure
The parser only opens a section on ^diff --git (line 230); everything else is printed verbatim. skills/pr-review/gitlab/SKILL.md provides no unified-diff command — only the MR /changes API (per-file .diff hunk text) and repository/compare .diffs[], neither of which carries diff --git lines (the sibling skills/fix-review/gitlab/SKILL.md:36 synthesises only --- a/X / +++ b/Y headers).
Verified at head: a GitLab-shaped section (--- a/package-lock.json / +++ b/package-lock.json / hunk) passes through unfiltered with an empty summary.
The GitLab skill's lack of a unified-diff source is pre-existing; what this PR adds is that SKILL.md now presents the filter and its excluded-content disclosure as forge-neutral, while on GitLab nothing is filtered and nothing is disclosed.
Suggested fix: Cheapest fix: state explicitly in SKILL.md step 2 that the filter applies to the GitHub unified diff only. Otherwise either have the GitLab skill emit a diff --git a/<old_path> b/<new_path> line per .changes[] entry before the hunk text, or accept ^--- a/ as a section boundary when no diff --git has been seen, and add a GitLab-shaped fixture to the test file.
There was a problem hiding this comment.
Fixed in ccbb0aa — sections now also open at ^--- a/ (and quoted/dev-null variants) when no diff --git header exists, so GitLab MR-shaped diffs filter; fail-open preserved. Two-section GitLab fixture test added.
| # 1. EXEMPT (always kept, beats every rule below): path has a | ||
| # "migrations" or "migrate" directory component. | ||
| # 2. STRIP: path matches the dependency-lockfile list (mirrors the | ||
| # is_lock() regex in fullsend's .github/scripts/route-review-model.sh |
There was a problem hiding this comment.
[MEDIUM] Cited fullsend .github/scripts/route-review-model.sh / is_lock() does not exist
The header (lines 23-25), the classifier comment (lines 173-174: "mirrors is_lock() in fullsends .github/scripts/route-review-model.sh (lines 65-84 there)... kept in sync by hand"), and the PR description all cite a file that could not be found. Verified: the fullsend-ai/fullsend main tree (7495cdc5) has no path matching route-review*; its .github/scripts listing contains only check-fix-eligibility(-test).sh, install-openshell.sh, install-podman.sh, openshell-version.sh; git grep is_lock origin/main in that repo returns nothing; and this repo has no such file either.
The "kept in sync by hand" promise therefore points at nothing, and the lockfile list is narrower than common practice (no Pipfile.lock, uv.lock, bun.lockb, packages.lock.json, mix.lock, pubspec.lock, Package.resolved, flake.lock).
Suggested fix: Remove the cross-repo citation (or replace it with a permalink to the real location if one exists elsewhere), state that the list is self-contained here, fix the "fullsends" typo, and consider extending the lockfile list.
There was a problem hiding this comment.
Fixed in ccbb0aa — the citation was stale, removed; the lockfile list is described as self-contained.
| deletions) — paginate if the forge API requires it | ||
| - Compute `FILE_COUNT` and `LINE_COUNT` from the response | ||
|
|
||
| `FILE_COUNT` and `LINE_COUNT` are computed once, here, from this |
There was a problem hiding this comment.
[MEDIUM] Step 2 contradicts itself: new "never recomputed after filtering" rule vs bucket 3 "FILE_COUNT>200 after filtering"
The new paragraph at lines 120-123 states FILE_COUNT / LINE_COUNT are computed once from the unfiltered stats response and that nothing recomputes them from post-filter output; the PR body repeats this ("routing must see the true size"). But the retained bucket 3 at line 162 still reads "FILE_COUNT>200 after filtering, LINE_COUNT>10K: emit failure".
As written an orchestrator has two incompatible instructions for the token-limit gate: with the unfiltered rule, a 250-lockfile PR fails with token-limit before the filter runs; with the bucket-3 text, it must compute a post-filter count the new paragraph forbids. The bucket-3 wording is pre-existing, but the contradiction is introduced by this PR's new paragraph.
Suggested fix: Drop "after filtering" from bucket 3 (matching the stated unfiltered-count intent), or define precisely which count bucket 3 uses and reconcile the line-120 paragraph with it.
There was a problem hiding this comment.
Fixed in ccbb0aa — bucket 3 now names the same unfiltered counts computed in step 2, never post-filter numbers.
|
/agentic_review |
| if (lp ~ /\.pb\.go$|\.gen\.go$|_pb2\.py|_generated\.|(^|\/)(generated|dist|build)\//) { | ||
| phase = "pending" | ||
| return |
There was a problem hiding this comment.
2. Description overstates marker filtering 📜 Skill insight ≡ Correctness
The PR description claims any section with @generated in its first five added lines is stripped, but the classifier only performs that check for generated-looking paths. The new test explicitly preserves an ordinary source file containing that marker, so the stated behavior does not match the implementation.
Agent Prompt
## Issue description
The PR description says every section with an early `@generated` marker is stripped, while the implementation intentionally gates marker filtering on generated-looking paths.
## Issue Context
The source-path test confirms this is intentional security behavior rather than an implementation omission. Update the PR description to explain the path-shape prerequisite and context-line handling so reviewers are not given an inaccurate contract.
## Fix Focus Areas
- skills/pr-review/scripts/filter-review-diff.sh[242-250]
- scripts/filter-review-diff-test.sh[449-472]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
There was a problem hiding this comment.
PR description updated — it now states the path-shape prerequisite (planted markers on ordinary source never strip) and the context-line handling, and drops the stale citation.
|
Code review by qodo was updated up to the latest commit ccbb0aa |
|
/agentic_review |
|
Code review by qodo was updated up to the latest commit 5ef1681 |
ed79218 to
93c982d
Compare
|
/agentic_review |
|
Code review by qodo was updated up to the latest commit 93c982d |
|
/agentic_review |
|
Code review by qodo was updated up to the latest commit a86e820 |
waynesun09
left a comment
There was a problem hiding this comment.
Review sweep at head a86e820. Six findings checked against existing threads for overlap; five posted inline (1 HIGH, 4 MEDIUM), one MEDIUM below (its line falls outside the visible diff hunk for that file).
[MEDIUM] agents/review.md (near line 73, outside the visible diff hunk) — agents/review.md still states the narrower "downgrade only when all findings removed" rule, which no longer matches post-review.src.sh's actual (broader) downgrade logic
agents/review.md (lines 71-74) still says: "If filtering removes all findings from a request-changes or reject verdict, downgrade the verdict to comment." But scripts/post-review.src.sh (lines 161-189) computes blocking_count as findings excluding (provenance-warning OR excluded-content) AND severity==info, and downgrades whenever blocking_count -eq 0 — i.e. it downgrades even when disclosure-only findings remain (a non-empty findings array with only exempt info-level disclosures), not only when the findings array is fully empty. This asymmetry was never corrected in review.md even though the fix rounds (ccbb0aa, 5ef1681) touched the enforcement code and its tests extensively; the prompt text was not updated to match, so the agent's own stated rationale for its verdict can diverge from what the post-script actually enforces.
Suggested fix: Update agents/review.md to state the same rule as post-review.src.sh: exempt info-level disclosures (provenance-warning, excluded-content) never by themselves justify a blocking verdict, and the verdict is downgraded whenever no non-exempt (blocking) finding remains — not only when the findings array is entirely empty.
| return | ||
| } | ||
|
|
||
| # phase == "pending": buffering, watching the first 5 added lines and |
There was a problem hiding this comment.
[HIGH] Generated-marker window is scoped to the diff hunk, so typical mid-file protoc/codegen regen diffs never see the marker
The pending-phase content-marker check (lines 373-396) scans only the lines actually buffered inside the current diff hunk (first 5 added lines, or first 100 buffered lines, whichever comes first) — it does not, and structurally cannot, look at file content outside the hunk. The generated-file marker (// Code generated ... DO NOT EDIT. or the protoc header) normally sits at the very top of the file, outside the diff entirely, once the file is only being incrementally regenerated rather than newly added. A realistic regen touching .pb.go/_pb2.py deep in the file (e.g. a hunk at @@ -500,7 +500,7 @@) will never contain the marker in its buffered window, so generated_hit stays 0 and the section falls through to phase="keep", flushing the whole hunk into the reviewed diff. This is exactly the case the earlier fix (context-line marker checking, applied in ccbb0aa) did not close: Test 19 (filter-review-diff-test.sh lines 474-492, fixture PBGO_CONTEXT_MARKER) only proves the marker strips when it happens to fall inside the hunk (hunk starts at @@ -10,7 +10,7 @@, marker at file line 11) — there is still no fixture for a hunk offset far from the file's own header comment, which is the common case for incremental protoc/codegen regeneration.
Suggested fix: Either strip .pb.go/.gen.go/_pb2.py/_generated.* by path/suffix alone (dropping the content-marker gate for those specific protoc/codegen suffixes, keeping it only for the broader generated/, dist/, build/ path-component rule where false positives are more likely), or fetch the file's own header lines from the base/head ref instead of relying on what happens to be inside the diff hunk. Add a fixture where a .pb.go hunk sits far (e.g. @@ -500,7 +500,7 @@) from any marker line, to catch the realistic regression this misses today.
| function has_generated_marker(text) { | ||
| if (index(text, "@generated") > 0) return 1 | ||
| if (text ~ /^\/\/ Code generated .* DO NOT EDIT\.$/) return 1 | ||
| if (index(text, "Generated by the protocol buffer compiler. DO NOT EDIT!") > 0) return 1 |
There was a problem hiding this comment.
[MEDIUM] Hardcoded protoc "DO NOT EDIT" marker string is missing the required second space, so it never matches real protoc output
has_generated_marker() checks index(text, "Generated by the protocol buffer compiler. DO NOT EDIT!") with a single space after the period (also stated the same way in the header comment at lines 30-31). protoc's actual C++/Java/Python generators emit this string with TWO spaces after the period ("Generated by the protocol buffer compiler. DO NOT EDIT!" — matches upstream protobuf source and GitHub Linguist's own generated-file detector). Because of the single-space mismatch, this branch of has_generated_marker() never fires against real protoc-generated Python/Java/C++ output; only the separately-matched Go marker (// Code generated ... DO NOT EDIT.) actually works. This is a distinct defect from the hunk-window issue above (wrong string vs. wrong scan window) — even a marker that does land inside the buffered window would still fail this exact-match check.
Suggested fix: Fix the string at both line 214 and the header comment at line 31 to the real double-space marker, and prefer a whitespace-tolerant match given it has now been mistyped once already: text ~ /Generated by the protocol buffer compiler\.[ \t]+DO NOT EDIT!/. Add a fixture using the real double-space marker text — the current suite has no case exercising the actual protoc string.
| summary is non-empty, even though `info` sits below the default `low` | ||
| threshold (see "Severity filtering" in the agent definition): | ||
|
|
||
| - **[excluded-content]** — N excluded file(s) |
There was a problem hiding this comment.
[MEDIUM] excluded-content finding names multiple file paths but the findings schema's file field is a single required string
agents/review.md's finding-object schema (line 277) requires file as a single non-empty string. The excluded-content disclosure instruction (SKILL.md line 1342, referenced again at line 144) tells the agent to emit one info-level finding whose description lists every excluded path and reason ("<path> (<reason>), ..." for N files), but neither SKILL.md nor the schema in agents/review.md says what value goes in the required single-valued file field when N>1. This is the same unresolved gap provenance-warning already has (PR-wide, no file specified either), but this PR adds a second, more-likely-to-trigger multi-file instance of it. The recent fix in a86e820 addressed a different complaint (the disclosure mislabeling exclusions as all "generated/locked" instead of naming all five categories) — it did not touch the file field ambiguity.
Suggested fix: Either give findings a documented convention for representing multiple files (e.g. the first excluded path, or a PR-level sentinel), or instruct emitting one excluded-content finding per excluded file when the exact file matters. State the same rule in both agents/review.md's schema description and the SKILL.md instruction so provenance-warning and excluded-content populate file consistently.
| if (gl_cand != "") { | ||
| cand = gl_cand | ||
| gl_cand = "" | ||
| if ($0 ~ /^\+\+\+ /) { |
There was a problem hiding this comment.
[MEDIUM] GitLab section-boundary confirmation regex can be forged by an added line whose payload starts with "++ "
The one-line lookahead that confirms a GitLab ---/+++ section boundary checks only $0 ~ /^\+\+\+ / (line 405). The companion fix in 5ef1681 tightened the candidate side (is_gl_candidate() now requires ^--- (a\/|"a\/) or --- /dev/null, confirmed at lines 294-297) but left this confirming regex unchanged. An ADDED content line whose payload text begins with ++ (e.g. an added comment or string literal) renders in the diff as a line starting with +++ , which satisfies this regex and causes the parser to treat it as a real GitLab boundary — calling finalize_section()/reset_section() mid-hunk and flipping gitlab_mode on. This is a genuine parser-confusion bug distinct from the case the recent fix addressed (a removed line spoofing the --- candidate); it is reachable on ordinary (non-adversarial) PR content, not just crafted input.
Suggested fix: Require a more specific match before accepting a confirmed GitLab boundary, e.g. ^\+\+\+ (b\/|"b\/|/dev/null), mirroring the tightening already applied to the --- candidate side. Add a regression fixture where an added line's payload begins with ++ .
| return | ||
| } | ||
|
|
||
| if (phase == "header") { |
There was a problem hiding this comment.
[MEDIUM] Unbounded buffering in the pre-hunk header phase, reachable only via a --binary diff or non-git-produced input
Until an @@ hunk marker or a Binary files line is seen, the phase == "header" block (lines 335-370) appends every line to buf[] with no cap — unlike the 100-line cap applied once inside the pending/hunk phase. In the documented pipeline (SKILL.md's git diff <merge-base>..HEAD -- <file> / gh pr diff, neither of which passes --binary), real diff headers are 4-8 bounded lines before either @@ or Binary files ... differ appears, so this is not reachable through the pipeline as currently wired. It becomes reachable if a caller ever adds --binary (producing a GIT binary patch preamble of base85 lines with no @@ marker) or if the script is fed non-git-produced/malformed input directly — which the script's own header comment treats as a design concern ("Malformed input ... passes through unchanged rather than erroring").
Suggested fix: Apply the same buf_n cap used in the pending phase to the header phase too (flush fail-open on cap trip), closing the gap defensively even though today's callers don't pass --binary. If left as-is, document the --binary/non-git-input precondition so future callers don't add --binary without revisiting this.
Review context includes lockfile, minified, sourcemap, and @generated hunks that no model can meaningfully assess; on a mixed PR they are pure input cost for every review dimension. filter-review-diff.sh strips them deterministically before context assembly - migrations are exempt from every rule, and the exclusion is always disclosed (as an info-level finding, the same mechanism the provenance-warning finding already uses) so a changed lockfile stays visible even though no model read it. Small-PR and large-PR paths now share one definition of "generated" instead of the large-PR path's prompt-level list. Signed-off-by: guy oron <goron@redhat.com>
…, threshold-exempt disclosure - Derive section paths from the diff --git header itself (git-quoted paths dequoted), so binary, mode-only, and quoted-path sections classify correctly; when no path can be parsed at all the section fails open — never stripped, even on an @generated added line. - Truncate the caller-supplied summary file at start of run so a no-exclusions run cannot leave a previous run's stale records (preserves /dev/null behavior). - Match the migrations/migrate exemption as whole slash-delimited path components only — db/remigrations/ no longer bypasses the lockfile rule. - Bound pending-section buffering: the generated-marker decision resolves at the 5th added line or the 100th buffered line, whichever comes first, so a deletion-only section never buffers whole. - SKILL.md step 2b now skips exclusion-summary paths when fetching source_files, so filtered content cannot re-enter model context as full file contents. - The excluded-content disclosure is threshold-exempt (alongside provenance-warning) so the default low severity threshold cannot suppress it; agent-definition severity filtering states the exception. Signed-off-by: guy oron <goron@redhat.com>
…summary, post-script exemption - diff --git header split now prefers the position where both sides name the same file (the common non-rename case), so a path itself containing " b/" no longer misparses; last-marker fallback kept for genuine renames. - rename from/to metadata is dequoted when git-quoted (no a/ b/ prefix on these lines); undecodable quoting falls back to the header path, so a quoted rename into vendor/ is classified correctly. - dequote() keeps control and octal escapes as literal backslash sequences (only \" and \\ are unescaped): a \n in a filename can no longer break the one-record-per-physical-line summary contract. - post-review.sh severity filter now preserves the provenance-warning and excluded-content process disclosures regardless of threshold, matching the exemption stated in the agent definition. Signed-off-by: guy oron <goron@redhat.com>
…hape, tab paths - Move the severity-filter category exemption into post-review.src.sh (the round-2 edit landed in the generated bundle by mistake) and regenerate scripts/post-review.sh via make script-build; check-bundle is green again. - Verdict downgrade now keys on blocking findings: an array containing only the exempt process disclosures (provenance-warning, excluded-content) downgrades request-changes/reject to comment while keeping the disclosures; only a truly empty array is deleted. Test mirrors and cases updated accordingly. - Content-marker stripping is gated on generated-looking paths (.pb.go, _pb2.py*, .gen.go, _generated.*, generated/, dist/, build/) so an author-planted @generated cannot hide an ordinary source file from review; markers on any other path keep the section with no summary line. - Recognize the canonical Go marker (Code generated ... DO NOT EDIT.) and the protocol buffer compiler header, and check markers on context lines too — a modified .pb.go carries its marker as context, not as an added line — within the same bounded window. - Strip the tab git appends to ---/+++ paths containing spaces, so $-anchored rules match again. - Open sections at bare --- a/X lines when no diff --git headers exist, so GitLab MR diffs are filtered too; ambiguous input still fails open. - dequote() decodes octal escapes to raw bytes (summary paths now match the changed-file list byte-for-byte; awk runs under LC_ALL=C so %c emits bytes); \t \n \r stay literal to keep one summary line per record. - Drop the bogus cross-repo is_lock() citation — the lockfile list is self-contained here. - SKILL.md: bucket 3 uses the once-computed unfiltered counts, and the step-2 filter description matches the gated marker semantics. Signed-off-by: guy oron <goron@redhat.com>
…GitLab boundaries - The disclosure exemption now requires category AND info severity, in both the severity-filter keep-clause and blocking_count: a high- or critical-severity finding that uses the provenance-warning or excluded-content category is a real finding — it filters by rank and sustains a blocking verdict. Bundle regenerated from the src; tests cover survive-at-low, dropped-at-critical, and blocks-no-downgrade. - A GitLab section boundary is now confirmed by structure, not shape: a `--- ` candidate line opens a section only when the very next line is the paired `+++ ` header (one-line lookahead; the state machine moved into a process() function so a rejected candidate replays through identical logic). A removed content line whose original text begins "-- a/..." renders as `--- a/...` and previously reset the parser mid-file, stripping later hunks under the fake path — the new fixture shows the round-3 filter losing 8 lines with a false vendored exclusion, and now passes through byte-identical. Signed-off-by: guy oron <goron@redhat.com>
The disclosure template called every omitted file generated/lockfile, but the filter also excludes minified, sourcemap, and vendored content. Name all five categories so reviews that exclude only, say, a minified bundle do not publish an inaccurate description of what was omitted. Signed-off-by: guy oron <goron@redhat.com>
An incremental protoc/codegen regen touches the middle of a generated file, so the header comment carrying the marker never appears in the diff and the section fell through to the reviewed context unfiltered. The PR head is materialised in step 2b, so the file's own first 20 lines are readable: pass that tree as $2 and the marker is found where it actually lives. Without the tree, or the file, the in-hunk window still decides. Three narrower defects in the same classifier: - protoc emits two spaces before "DO NOT EDIT!", so the exact-match branch never fired on real Python/Java/C++ output. Matched whitespace-tolerantly now. - A GitLab section boundary was confirmed by any `+++ ` line, which an added line whose payload begins "++ " also produces: the parser reset mid-hunk, dropped four lines of real code and invented an exclusion. Both sides of the pair are now matched on their a/ b/ prefix, as is_gl_candidate() already matched the `---` side. - The pre-hunk header phase buffered without a cap, unlike the pending phase. Past 100 lines it fails open: unclassified, included. Signed-off-by: guy oron <goron@redhat.com>
Four fixtures for the four defects, plus the negatives that keep the guarantees honest: a .pb.go hunk at @@ -500,7 +500,7 @@ is kept without a head tree and stripped with one, a planted @generated on ordinary source is still kept, protoc's real double-space header strips (and the single-space spelling too), an added line beginning "++ " leaves a GitLab-shaped diff byte-identical, and a 150-line binary preamble passes through while a short binary header still classifies. Signed-off-by: guy oron <goron@redhat.com>
`file` is a single required string, so an excluded-content finding listing N paths had nowhere to put them. Emit one finding per excluded path instead, with no line — a finding without a line never becomes an inline comment on a file nobody reviewed. The PR-wide provenance-warning disclosure gets the same rule and a `<pr>` sentinel, stated in both the schema table and the instruction. Filtering moves to its own step 2c, after the PR head exists, and passes that tree to the script so the marker lookup has something to read. States the 20-line marker window, that both the GitHub and GitLab diff shapes are handled, and the accepted risk that a marker on an already-generated-looking path is still author-controlled — the per-file disclosure is what keeps it visible. Signed-off-by: guy oron <goron@redhat.com>
The block showed the script name and its two arguments with no stdin redirect and no output target, and `filter-review-diff.sh` is a stdin-to-stdout filter: an agent running it verbatim left `pr-diff.txt` untouched and the whole filter a no-op on the documented path. "In place" is not something one shell redirect can do either. Now a complete bash block in the house dialect, like every sibling command block in the forge skills: read the diff, pass the pr-head tree, write a new file, `mv` it back over `pr-diff.txt` (so every later step keeps reading the same name), and the same `test -s … || echo "EMPTY DIFF …"` guard those blocks end with. The summary gets a real name, `/sandbox/workspace/pr-excluded.txt`, used in 3d and step 7 instead of "step 2c's exclusion summary". Restores `> filtered-diff` to the script's own usage line. Also drops a sentence that was not true: the large-PR bucket still has the forge skill's own coarse jq exclusions applied to it upstream (`skills/pr-review/github/SKILL.md:44`), which strip `.pb.go` with no marker check and carry no migrations exemption. What the two buckets share is step 2c, and that is what the text now says. Signed-off-by: guy oron <goron@redhat.com>
`skills/pr-review/SKILL.md` is 16,815 tokens against the 15,709 ceiling the baseline records, so `make lint` is red on this branch — and was already red at a86e820, before any of the review-feedback commits. All of the growth is this PR's. CONTRIBUTING.md:38 says not to re-baseline a violation your own PR increased; b9fef04 did exactly that for the same rule and the same reason, and was merged. Ceiling set to the measured value, no slack. Maintainer's call which way this goes — the alternative is trimming the skill, which is a different change. Signed-off-by: guy oron <goron@redhat.com>
a86e820 to
db55cb4
Compare
|
@waynesun09 I rebased onto main. #1178 rewrote step 2 and 3d; the only conflicts were
|
The skillsaw context-budget entry for skills/pr-review/SKILL.md is a ceiling, not a mute, so it has to move with the file. This PR grows the skill by the dismissal-reconciliation step: 15,709 tokens on main, 20,258 here. Same fingerprint, one entry, value and message only, as b9fef04 did for code-implementation. fullsend-ai#1006 and fullsend-ai#1008 grow the same file, so whichever of the three merges last has to re-measure and bump again. Signed-off-by: guy oron <goron@redhat.com>
Heyaa : )
Watching review context get assembled, I kept seeing lockfile, minified, sourcemap and
@generatedhunks ride along into every dimension's prompt — content no model can meaningfully assess, pure input cost on every mixed PR.New
skills/pr-review/scripts/filter-review-diff.sh: a single awk program over the unified diff on stdin. Strips lockfile sections (self-contained list across ecosystems),*.min.js/*.min.css/*.map,vendor//node_modules//third_party/paths, and generated files — a generated-looking path (protobuf/codegen suffixes,generated//dist//build/) whose early added or context lines carry@generated, the GoCode generated … DO NOT EDIT.marker, or the protoc header. The path-shape gate is deliberate: an author-planted marker on ordinary source never hides it from review.migrations/paths are exempt from every rule. Filtered diff on stdout — byte-identical for untouched sections — and a one-line-per-file exclusion summary written only to$1, never stdout.Wiring (
skills/pr-review/SKILL.md): the small-PR path pipes the full diff through the filter before any context package; the large-PR path's "lockfiles, vendor/, protobuf, etc." vibes list is replaced by the same script — both paths now share one deterministic definition of "generated".FILE_COUNT/LINE_COUNTstay computed from the unfiltered stats: routing must see the true size.Exclusions are disclosed via an info-level
excluded-contentfinding (theprovenance-warningprecedent — step 7 forbids footers), so a changed lockfile is still visible, just not reviewed.scripts/filter-review-diff-test.shis wired intomake script-test: byte-identical pass-through (viacmp), each stripping rule, the added-vs-removed@generateddistinction, the migration exemption, summary format and stdout silence, all-stripped output, malformed-input pass-through. shellcheck clean.Step 2b's file-contents fetch honors the same exclusions, so filtered files stay out of
source_filestoo. Non-goals: no config knob. The script is architecture-neutral; the pi-runtime path can adopt it when it stabilizes.Refs #1143