Skip to content

Online extraction: listed inputs, quoted tool output and aggregate check results are not findings - #72

Open
BasRizk wants to merge 79 commits into
withmartian:mainfrom
BasRizk:extract-skip-non-finding-content
Open

BasRizk wants to merge 79 commits into
withmartian:mainfrom
BasRizk:extract-skip-non-finding-content

Conversation

@BasRizk

@BasRizk BasRizk commented Nov 11, 2026 •

Copy link
Copy Markdown

What this changes

Step 1 (EXTRACT_BOT_SUGGESTIONS in online/etl/llm/prompts.py) gets one new paragraph, after the existing skip rules. The rest of the prompt is unchanged:

Also skip inputs the bot lists as having used for its review, such as linter or static-analysis
output, code guidelines, or configured review instructions (for example a section where the bot
lists the inputs or context it used). Do not extract findings from these inputs, even when a listed
tool result names a file and line of the changed code: the bot did not raise it as its own comment.
Tool output quoted as supporting context inside a comment, such as a linter or static-analysis log
next to the bot's own text, is not its own finding either: only problems the bot states in its own
words are findings. A tool result the bot posts as its own comment at a location in the changed code
is a finding, and so is a linter or static-analysis result the bot writes up as a problem in its own
comment text. An aggregate check result is not a finding either: a check whose result is a count, a
percentage, or a pass/fail measured over many functions or files (for example docstring or test
coverage against a threshold), including a request to add docstrings or tests to "the touched
functions". A check that states a specific problem with the pull request, such as with its title or
description, is not aggregate: it is a finding. So is a request that names a function, or a file and
line, in the changed code. A concrete finding the bot states in its own words in a review-body
section rather than as an inline comment, or inside a collapsed or nested section, is still a
finding: extract it.

It also adds a version stamp, EXTRACTION_PROMPT_VERSION = "extract-v3". Each analysis now stores model_name as "<model>+extract-v3", so analyses made with different prompts can be told apart. tests/test_prompts.py pins the sha256 of every input the version stands for: the step prompts, the code that builds step input, the actor matching, the metric arithmetic, the response schemas and the LLM call. The test fails when one of them changes without a new version. In CI it also compares the table with the one at the merge-base, so a past entry cannot be edited in place. online/etl/README.md describes this.

Why

This is a correctness fix. The rule is the same for every bot.

  • Some bots list the inputs they used for a review, such as linter results, guidelines or review instructions. Those inputs are not the bot's findings. The bot did not raise them as its own comments, even when a listed tool result names a file and a line.
  • Linter or tool output quoted inside a comment, as context for the bot's own text, is not a separate finding either. A tool result the bot posts as its own comment at a location in the code is still a finding.
  • An aggregate check result is not a finding. That is a check whose result is a count, a percentage or a pass/fail over many functions or files, such as docstring coverage below a threshold. A check that states a specific problem with the pull request, such as its title or description, is a finding. So is a request that names a function, or a file and line.
  • A concrete finding the bot states in its own words is still a finding when it sits in a review-body section or a collapsed section. This covers only the bot's own words, so it cannot undo the skip of listed inputs.

The rule is the same for every bot. How much it changes a bot's numbers depends on how often that bot posts such content.

Measured effect

We replayed step 1 and step 3 on a sample of public PRs from the benchmark's stored data. The input is the one the default Search API path builds: every review and every review-thread comment of the bot, across all review rounds, and no issue comments. The human actions were held fixed. The judge was gpt-5.2 at temperature 1.0. P and R use the leaderboard arithmetic: the mean over PRs that have both. Intervals are 95% paired bootstrap intervals over PRs. These numbers come from a sample, and the judge may not be the model you run. They are not what the leaderboard would show after this change.

The whole sample

This run used an earlier wording of the paragraph, without the check-result sentence.

bot PRs draws per arm P before P after dP [95% CI] R before R after dR [95% CI]
CodeRabbit 313 2 70.0 70.0 -0.0 [-1.1, +1.1] 52.0 52.2 +0.2 [-0.5, +1.1]
Six other bots pooled (Greptile, cubic, Claude, Codex, Copilot, Qodo) 386 bot-PR pairs 2 79.4 79.7 +0.3 [-0.8, +1.3] 42.2 42.6 +0.4 [-0.2, +1.1]

There is no measurable change for CodeRabbit or for the six other bots. Two draws of the unchanged prompt differ by up to 1.2 points. The sample has 395 bot-PR pairs for the six other bots. We stopped spending after 9 of them had only one draw of the new prompt, so the table uses the 386 pairs with two draws each.

The paragraph as shipped

On this input, check results reach step 1 only through review bodies. In the sample, 15 CodeRabbit PRs have a docstring coverage check there. We ran the paragraph as shipped on those 15, on 40 other CodeRabbit PRs picked at random, and on 70 other-bot pairs picked at random. Two draws per arm, against the current prompt:

PRs scored dP [95% CI] dR [95% CI] coverage-check items, current / this change (matched)
CodeRabbit, with a coverage check 15 (11 with P and R) +4.3 [-2.6, +13.7] -0.9 [-6.4, +4.8] 15 (2) / 0
CodeRabbit, others 40 (30) +0.3 [-1.4, +1.9] +1.4 [+0.0, +3.4] 0 / 0
Six other bots pooled 70 (57) +2.1 [-0.7, +5.3] +1.7 [-0.2, +4.2] 0 / 0

The change removes every coverage-check item. On the 15 PRs with a coverage check, precision moves up by about 4 points. With 11 scored PRs that is within the noise: two draws of the current prompt differ by -4.2 [-9.2, +0.0] there. Elsewhere there is no measurable change.

Three CodeRabbit reviews in the sample state a title, description or linked-issue check in the review body. Neither the current prompt nor this change extracted an item from them, so nothing was lost there. We read the 5 PRs where this change matched fewer human changes than the current prompt. One lost match was an aggregate coverage item, which this change removes on purpose; the human change it had matched added docstrings. In one PR, two items on the same code were extracted as one. In one, a test-gap item that the current prompt extracted in one of its two draws was not extracted. The rest were the same items, paired differently by the matching step.

The version stamp lets the leaderboard tell analyses made with this prompt from older ones.

Tests

cd online/etl
uv sync --dev
uv run ruff check .
uv run pytest tests/
  • tests/test_prompts.py pins the wording of each sentence of the new paragraph. It also pins the version table.
  • tests/test_extraction_scope_fixtures.py uses 12 trimmed public review fixtures. It checks that the labelled text reaches step 1, and maps each labelled kind to the prompt sentence that decides it, or explicitly to no rule. It checks that the sentence is in the prompt, and that labels of a kind sit on the side its sentence gives: a description-check row is a finding and a coverage row is not. It cannot show how the model applies the sentences.
  • An opt-in check runs the prompt on those fixtures with your model. It makes one paid call per fixture:
RUN_EXTRACTION_SCOPE_EVAL=1 MARTIAN_API_KEY=... MARTIAN_MODEL_NAME=... \
  uv run pytest tests/test_extraction_scope_live.py -v

…arve out problems named in summaries; scope tool output
… unasserted label gives a reason; kornia fixture
…del_name; --reanalyze reuses stored actions, selects unstamped rows, clears labels, warns at --limit
…sion to the prompt sha256; keep re-analyzed PRs out of 'skipped'; pin the comparison baseline to the pre-change commit
…n PRs in compare_prompts; pin every prompt and formatter per version; 'path-specific instructions'
…uest in the aggregate sentence

The rating bullet names priority and impact (the fixtures already label them ratings), and the
aggregate-results sentence names the located coverage request as its other exclusion, so the two
no longer contradict the tie-break. No new exclusion; extract-v2 digests updated (unreleased).
…omplete status, merged-name ignore

The coverage template's free tails let a gate line followed by a concrete problem count as a gate.
Scope and remediation are now fixed phrases (touched functions, add missing docstrings to meet the
threshold), so anything else makes the item a finding; four such cases are negative tests. Review
status also matches did-not-complete / failed / not-run notes and a no-findings note. ignored_tools
is checked against the merged username, as db.rs does. The step 1 README names priority and impact
ratings and the located coverage request.
…ributors from the results file

For each bot it compares two arms on the leaderboard means and lists the PRs whose precision turns
undefined or defined (with the shift that causes), the PRs whose own value moves each mean most
(with their contribution), and those PRs' matched suggestions in every arm. No LLM call. A test
pins the figures the PR description quotes to the results file.
…c763a)

The head arm now comes from the exact head prompt; the previous head arm is kept as 'previous
revision cee5e6cc'. The figures test_what_moved pins are updated to the new head draw.
The contributions double-counted the change in the PR count. Each mean's change is now split into
the cohort effect of PRs leaving (on base values), of PRs entering (on arm values), and each
retained PR's change over the shared retained count; the parts sum to the total exactly. Tests: a
leaving PR beside a worsening one, an entering PR, and the sum on every bot and arm pair of the
results file.
…s; pr_remark rule map

Each arm in the results file now has a header row with the sha256 of the EXTRACT_BOT_SUGGESTIONS it
was measured with, and a test checks the head arm's equals the shipped prompt. Review status also
matches two-word product names and the 'reviewed N out of M changed files and generated no comments'
line, as fixed whole-text templates with positive and negative tests. The pr_remark fixture kind
maps to the prompt's explicit 'is a finding, wherever it sits' sentence.
…three words; fleetdm review effort is a run setting

On a push after a force-push, github.event.before can name a commit that is no longer in the clone,
and git merge-base then raised. The check now skips a raw sha that git cat-file cannot find, as it
already skips the all-zero sha; a named ref (a pull request's base) still fails loudly. The review
status template allows product names of up to three words ('Qodo Merge Pro review was paused.').
Copilot's 'Review effort: Balanced' on fleetdm/fleet#55421 is a run setting, so its label kind is
run_status; it stays an asserted non-finding with the same pattern.
…ames every pinned input

The type patterns behind items_* were written on CodeRabbit's text, so the other bots' run-status
notes (Codex 'did not complete' and 'usage limits reached', Qodo 'reviews are paused', Claude
'could not confirm CI', Greptile 'no user flows identified') were counted as code findings. Every
unlocated item of the six other bots was read; those notes are now items_run_status, and a test
checks the excluded-only PRs' items in every arm that extracted them. Metrics are unchanged. The
version-bump comment in prompts.py and the ETL README now list the inputs prompt_pins.py pins,
actor matching and analyze_single_pr's arithmetic included.
…through is still a finding

Qodo keeps its earlier findings in its summary comment, struck through and marked Resolved or
Dismissed, and the head prompt dropped several of them, among them resolved findings matched to the
author's fix. One sentence, by kind rather than by vendor, says such a finding is still a finding
wherever it is listed: the marker is the finding's state, not review status or run metadata, and
the follow-up-confirmation skip covers only the confirmation. A trimmed Qodo fixture
(anolilab/neore#451) asserts two resolved findings and one dismissed one; the TajsTokens dismissed
finding is now asserted. extract-v2 (unreleased) pins the new prompt digest.
…kept as its own arm

Every bot was re-run once on the head prompt with the same harness (CodeRabbit's 313 PRs, the six
co-reviewed bots, the step-1-only bots); the previous head's rows stay as 'previous revision
093c763a, one draw', with its prompt sha in the arm header. The head's status notes are typed by
hand like the other arms'. what_moved figures in the tests follow the new head. The scylla
description-check row (1 of 9 draws on this prompt) and the TajsTokens dismissed finding (2 of 9)
are borderline in the model, so they are no longer asserted; the reasons say so. The README step 1
description names resolved and dismissed findings as findings.
…he scylla and TajsTokens rows

The 70-word sentence regressed other checks (the scylla description-check row fell to 1 of 9
draws). It is now one sentence beside the collapsed-section rule, and the follow-up-comment skip
says in a few words that it covers the confirmation, not the finding it refers to. Chosen from three
short wordings on the fixture draws (scylla 5 of 6, zizmor kept out 6 of 6, the Qodo fixture 6 of
6). The scylla row and the TajsTokens dismissed finding are asserted again; extract-v2 pins the new
digest.
…s in EXTRACTION_SCOPE_DETAILS.md

Every bot was re-run once on the head prompt with the same harness (CodeRabbit's 313 PRs, the six
co-reviewed bots, the step-1-only bots); the previous head stays as 'previous revision a2fcbe51,
one draw' with its prompt sha in the arm header, and the head's status notes are typed by hand like
the other arms'. The per-arm tables, the CodeRabbit type table, the Qodo resolved-findings counts,
the ruling counts, the fixture draws for each wording tried and the generated what_moved output now
live in prompt_eval/EXTRACTION_SCOPE_DETAILS.md, so the PR description can stay short. The pinned
what_moved figures follow the new head.
…ut; README mirrors the prompt

pcsx-redux#2544's rating-explanation finding (0 of 6 on the head, 1 of 6 upstream) and
local-search-mcp#27's linked-issue row (0 of 6 with both) stay unasserted, with the new counts in
their reasons and in the details file. The details file says the dropout lands unevenly (the
Claude and Greptile status notes are still extracted) and names status notes by type. The README
step 1 description uses the prompt's wording for resolved findings.
… 1 says no more than the prompt

One rule for finding labels: a finding stays asserted whatever its pass rate, and its failures are
disclosed. pcsx-redux#2544's rating-explanation finding and local-search-mcp#27's linked-issue row
are asserted again; with TajsTokens' dismissed finding they are the three the live run fails on the
head (also missed by the current upstream prompt). Only overview sentences carry no pattern. The
README step 1 description now names product text among the exclusions, a concrete problem the bot
itself names, and a tool result posted at a location in the changed code, as the prompt does; the
details file paraphrases the bots' status marks.
…wording 2 was chosen

README step 1 says a rule violation reported at a specific location in the changed code, as the
prompt does. The details file names the rule that chose wording 2 (most clean draws over the four
fixtures, 18 of 24 against 17 and 15) and notes the neutral linked-issue row that has no pattern.
…out a model name

what_moved reports a mean's change as unavailable when either arm has no PR with both values, and
skips the split when no PR is scored in both arms, instead of computing against an invented zero;
tests cover both cases, and the output on the results file is byte-identical for every bot and pair
of arms. stamped_model_name keeps model_name None when there is no model name rather than storing
'unknown+extract-v2'. The details file says why the chosen wording's CodeRabbit differences favour
CodeRabbit, and that the intro's linked-issue wording may contribute to the cubic row's miss (a
reword did not help, so it was not adopted).
…s state the wording margins

The cubic linked-issue row's kind (check_finding) now maps to the sentence that judges other
PR-level content like any other comment, not to a sentence inside the gate bullet that leaves
linked-issue checks out. Narrowing that carve-out to title and description checks did not help the
row (0 of 6) and lowered the scylla row (1 of 6), so the prompt is unchanged; the details file
records both tries and states the wording-choice margins exactly. coverage_only's docstring says its
templates follow CodeRabbit's lines, so f is understated more for the other bots.
@BasRizk BasRizk changed the title Online extraction: do not count PR overviews, check results, run metadata or quoted tool output as findings Online extraction: do not count PR overviews, aggregate check statuses, run metadata or quoted tool output as findings Nov 11, 2026
…lines labelled control

With --bot all, coverage_only prints once that f is not comparable across bots (its templates follow
CodeRabbit's lines, so f is understated more for the other bots); a test checks it. Copilot's
'Review effort: Balanced' and CodeRabbit's 'Review profile: QUIET' are review settings, so their
fixture labels are control rather than run status (both stay asserted non-findings, same patterns).
The details file adds the caveat and says both rejected linked-issue tries would have favoured
CodeRabbit's score.
…ngs lines labelled alike

The details file states what each rejected linked-issue try would have done per fixture and per bot
(the scylla CodeRabbit row less often, which would have favoured CodeRabbit; zizmor kept out less
often in try 1, against CodeRabbit; TajsTokens' dismissed Qodo finding more often, for Qodo), notes
the cross-session noise, and says where the step-1-only PRs live. Qodo's 'Review mode' line is
labelled control like Copilot's and CodeRabbit's settings lines; the control rule map now holds the
whole bullet sentence including 'configuration or review-settings details'. coverage_only prints
its cross-bot caveat for any bot other than CodeRabbit.
…ies' TajsTokens gain named

The zizmor head baseline now counts the two live draws like the scylla and TajsTokens baselines
(8 of 8), and each rejected linked-issue try is described as worse on the CodeRabbit fixtures and
better on TajsTokens.
@BasRizk BasRizk closed this Nov 12, 2026
@BasRizk BasRizk reopened this Nov 12, 2026
EXTRACT_BOT_SUGGESTIONS keeps upstream's text and adds one paragraph after its skip rules: skip the
inputs the bot lists as used for its review, tool output quoted as context inside a comment is not
a finding, a tool result the bot posts at a location as its own comment (or writes up in its own
words) is a finding, and findings in review-body or collapsed sections are findings. extract-v2's
rules for overviews, ratings, coverage gates, controls, run metadata, resolved findings and title
remarks are dropped: on the default Search API input the current prompt extracts nothing from those
kinds. EXTRACTION_PROMPT_VERSION = "extract-v3", pinned in tests/test_prompts.py.

The wording tests pin the paragraph that is there and check that the dropped rules are absent. The
fixture test maps each labelled kind to the sentence that decides it, or to no rule; the live check
does not assert non-findings of a kind with no rule (NO_RULE_KINDS). The packaged prompt_eval
results were measured on extract-v2, and their test now says so.
…ackaged arms

The packaged results' newest arm is the unreleased extract-v2 draft, so it is labelled
"extract-v2 af4797d5, one draw" instead of "this PR at head". The version ledger marks extract-v2
as an unreleased draft and extract-v3 as the first released version.

Removed, as they only made sense for the draft's rules: EXTRACTION_SCOPE_DETAILS.md (its
measurement write-up), coverage_only.py (counts analyses whose only items those rules excluded)
and what_moved.py, with their tests. The README's step 1 text describes the one paragraph the
prompt adds.
…uest is

One sentence added to the extract-v3 paragraph: a status, a metric against a threshold, or a
request to add docstrings or tests to "the touched functions" is not a finding, and a request that
names a function, or a file and line, in the changed code is. extract-v3 is unreleased, so its
ledger entry takes the new digest. The coverage_check fixture labels now have a rule, and the live
check asserts them again.
@BasRizk BasRizk changed the title Online extraction: do not count PR overviews, aggregate check statuses, run metadata or quoted tool output as findings Online extraction: listed inputs, quoted tool output and aggregate check results are not findings Nov 13, 2026
…o the bot's own words

An aggregate check result is now defined: a count, a percentage or a pass/fail measured over many
functions or files (docstring or test coverage against a threshold). A check that states a specific
problem with the pull request, such as its title or description, is not aggregate and is a finding,
as the scylladb fixture labels its description-check row. The closing sentence covers only findings
the bot states in its own words, so it cannot override the listed-inputs skip.

The fixture test maps the description, PR-remark and check-finding kinds to the sentence that keeps
them, checks that check labels sit on the side the sentences give, and its docstring says what it
can and cannot show. extract-v3 is unreleased: its ledger entry takes the new digest.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant