Skip to content

feat(review): carry a context-grounded finding's own evidence to the verifier - #871

Merged
devops-thiago merged 15 commits into
mainfrom
feat/475-self-carrying-evidence
Sep 16, 2026
Merged

devops-thiago merged 15 commits into
mainfrom
feat/475-self-carrying-evidence

Conversation

@devops-thiago

@devops-thiago devops-thiago commented Sep 16, 2026

Copy link
Copy Markdown
Owner

What type of PR is this?

  • 🐛 Bug fix
  • ✨ Feature
  • 📝 Documentation
  • 🔧 Refactor
  • 🚀 Performance
  • ✅ Test
  • 🔒 Security
  • 📦 Dependency update
  • 🏗️ CI/CD

Description

FindingVerifier is the second-opinion pass that can downgrade or drop a finding before the maintainer sees it, and its inputs are fixed: candidates, PR context, diff, project stack, previous findings. The review pass gets more than that. patchCoverage (#115) and pathInstructions (#33) reach the reviewer and never reach the verifier, so a finding raised from one of them arrives as a claim about material nobody showed it — the exact shape the verifier's generic rejection ground deletes. configKeyContext (#108/#109) is the counter-example the ticket points at: it reaches the verifier transitively, inside the finding itself, and the verifier has a rule for judging it.

This generalizes that pattern, composed with #650 rather than duplicating it.

Which dimensions carry evidence. Patch coverage and path-scoped instructions. Linked-issue context is deliberately out (see the scope section below).

How the material is quoted. ContextEvidenceResolver matches each candidate against the sections the review actually supplied and attaches a context_evidence field to the candidate JSON, beside #650's cited_location:

  • the coverage section lists the cited line → "The patch-coverage section this review supplied lists line 42 of `path` among the added lines the report for this commit records as executable and never executed. The lines it lists for that file: 40-47, 61."
  • a scope governs the cited file → "The maintainers scoped review rules to files matching `src/**`, and `path` is one of the files in this pull request they govern. Their text for that glob, verbatim:" followed by the rules.

The supporting direction is attached whenever it exists, with no test on the finding's wording: the measurement belongs to the line, not to how the finding phrased itself, and it is what stops the verifier assuming some unshown test covers that line.

What is read. The rendered sections, not the resolvers that produced them. What the verifier must be told is what the reviewer was shown, caps and disclosures and all. ContextEvidenceResolverTest.readsBackTheSectionTheRendererItselfEmits parses PatchCoverageResolver.render's own output so the two sides cannot drift. That cap also changed here: it fell at a character count and could cut a file's entry mid-range, and half of an uncovered line's 61 is 6, a line number too — so a finding anchored there would have carried a measurement nobody took. It now falls on a line boundary, and the section this reads never holds a partial entry.

How it is bounded. One budget over all attached evidence, as the ticket asks. EvidenceBudget is extracted out of CitedLocationResolver.Round (same MAX_NOTE_CHARS 1,200 / MAX_TOTAL_CHARS 6,000, same monitor and the same #866-review reserve-then-charge semantics) and ReviewEvidence.forReview hands the one instance to both rounds. A quoted scope's rules get their own 500-character cap so one verbose scope cannot crowd out every other note. Anything that no longer fits is simply not attached — the pre-#475 behaviour — and anything the per-note cap cuts ends in a marker, since a note carries material the prompt presents as the real thing. The extraction also fixes a latent detail: the cap now cuts on a surrogate boundary (ConfigKeyContextResolver.truncate) instead of mid-pair.

What the verifier is told when the evidence is missing or unreadable. The field is absent, and the prompt says absence means only that nothing was matched and implies nothing either way — the same contract #650 gave cited_location. Nothing is inferred from silence.

How a fabricated quote is handled. This is the direction that matters, and it is not symmetric with the supporting one. A finding that names coverage material is checked against the section: when the section lists that file but not that line, when it lists the file not at all, or when this review read no report at all, the field carries that fact and the verifier is told the attributed fact is not established, the attribution earns the finding nothing, and the finding is rejected when the attribution was all it had. A quoted measurement nobody took is not evidence. The contradiction is gated on one stem — does the finding's own prose say coverage — because a note saying "this review measured nothing" on a finding that never mentioned it would invite exactly the inverse inference #115 forbids; the stem stops short of outcome wording such as "never executed", which is also how a finding grounded in the diff alone describes unreachable code. The scan only ever adds a contradiction, so a phrasing it misses attaches nothing and the finding is judged as it is today. No such guard is needed for a scoped rule: the scope's real text is attached beside the finding, so a rule the maintainers never wrote is visible as absent from it.

What the contradiction does not claim. The section is capped at a file count, a per-file range count and a total size, and it discloses each of those in its own words. A file it does not list may be one the cap dropped, so the note carries the render's roll-up count ("the section also says 2 further changed file(s) have uncovered added lines without naming them") or, when the cut took the roll-up line itself, the cut. A line beyond a file's named ranges may be one the range cap dropped, so when the ranges carry "and N more range(s)" the note says the section names only part of that file's uncovered lines rather than asserting the line was not measured. Absence in a capped section is not a refutation, and the note never reads as one. Two matching files settle nothing, for coverage and for scopes alike: an ambiguous citation attaches nothing rather than naming another file's material as this finding's.

Verifier slots are unchanged. No new @V. The evidence rides on the candidate JSON, which is where #668 and the ticket both point, and is why the next context dimension needs no slot of its own. The generator prompt gets the general rule (SELF-CARRYING EVIDENCE) plus one sentence per dimension, and the verifier prompt gets one general context_evidence rule that ends by saying it is the pattern every future context section follows — the "documented next to the verifier prompt" acceptance criterion.

Two corrections to the ticket, verified against the code. The verifier has five template slots, not four: prContext was added by #711's fix and is in FindingVerifierPrompts.USER today. And configKeyContext does have a rendered section (ReviewPromptAssembler.configKeyContextSection), which the ticket's table describes correctly; it is dimension 10's quoting requirement, not the section, that carries it to the verifier. Neither changes the design.

Out of scope, drawn deliberately

Related Issues

Fixes #475

How Has This Been Tested?

  • Unit tests
  • Integration tests
  • Manual testing

Red first. The behaviour was neutered in place (context notes suppressed, the candidate field passed null, the four prompt additions removed) and the new tests run against it:

[ERROR]   ContextEvidenceResolverTest.carriesTheMeasurementForALineTheCoverageSectionLists:75 a finding on a measured-uncovered line must carry its measurement ==> expected: not <null>
[ERROR]   ContextEvidenceResolverTest.carriesTheMeasurementEvenWhenTheFindingNeverMentionsCoverage:91 the measurement belongs to the line, not to the finding's wording ==> expected: not <null>
[ERROR]   ContextEvidenceResolverTest.contradictsACoverageClaimAboutALineTheSectionDoesNotList:101 an attributed measurement the section lacks must be contradicted ==> expected: not <null>
[ERROR]   ContextEvidenceResolverTest.contradictsACoverageClaimAboutAFileTheSectionDoesNotList:113 a measurement claimed for an unlisted file must be contradicted ==> expected: not <null>
[ERROR]   ContextEvidenceResolverTest.contradictsACoverageClaimWhenThisReviewSuppliedNoSection:129 a measurement claimed where none was taken must be contradicted ==> expected: not <null>
[ERROR]   ContextEvidenceResolverTest.quotesTheScopedRulesGoverningTheFindingsFile:148 a finding raised under a scoped rule must carry the rule ==> expected: not <null>
[ERROR]   ContextEvidenceResolverTest.findsTheSectionsPathForACitationThatLostALeadingDirectory:211 a citation missing a leading directory still names one changed file ==> expected: not <null>
[ERROR]   ContextEvidenceResolverTest.boundsAVerboseScopesRules:169 NullPointer Cannot invoke "String.contains(java.lang.CharSequence)" because "note" is null
[ERROR]   ContextEvidenceResolverTest.carriesBothDimensionsWhenTheFindingRestsOnBoth:181 NullPointer Cannot invoke "String.contains(java.lang.CharSequence)" because "note" is null
[ERROR]   ContextEvidenceResolverTest.refusesToGuessBetweenTwoListedFilesACitationMatches:221 NullPointer Cannot invoke "String.contains(java.lang.CharSequence)" because "note" is null
[ERROR]   ReviewEvidenceTest.carriesBothResolversEvidenceForOneFinding:117 the measurement is attached ==> expected: not <null>
[ERROR]   FindingVerificationServiceTest.attachesTheMatchedContextEvidenceToTheCandidateItBelongsTo:2682 [ {
[ERROR]   FindingVerifierPromptsContentTest.verifierTreatsMatchedContextEvidenceAsEstablishedMaterial:221->assertContains:30 the verifier must be told what the matched context material is (#475) — missing marker: "A candidate may also carry a "context_evidence" field" ==> expected: <true> but was: <false>
[ERROR]   PrReviewPromptsContentTest.findingsMustCarryTheContextTheyRestOn:700->assertContains:42 a context-grounded finding must quote its grounding (#475) — missing marker: "SELF-CARRYING EVIDENCE" ==> expected: <true> but was: <false>
[ERROR]   PrReviewPromptsContentTest.patchCoverageRequestMakesUntestedChangedLogicReportable:686->assertContains:42 a coverage-grounded finding must quote the measurement it rests on (#475) — missing marker: "Carry the measurement in any finding you raise from this section" ==> expected: <true> but was: <false>
[ERROR]   ReviewPromptAssemblerTest.scopedGuidanceAsksTheFindingToQuoteTheRuleItRestsOn ## Project-Specific Instructions (from .github/thrillhousebot.md) …

Green after the change, full suite:

[INFO] Tests run: 3967, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS

Gates, from the worktree root:

./mvnw -B clean compile spotbugs:check spotless:check
[INFO] BugInstance size is 0
[INFO] BUILD SUCCESS

Patch coverage of the src/main diff against origin/main, every added line and branch: GAPS: none.

Also covered: the budget is shared across both resolvers (ReviewEvidenceTest.spendsOneBudgetAcrossBothResolvers — cited-location notes that fill it leave nothing for context evidence), a scope that governs another file attaches nothing, a citation matching two listed files resolves to neither, the rendered section is parsed back from PatchCoverageResolver.render's own output, and the candidate carries neither field when nothing resolved.

The eval corpus (-Peval) needs provider credentials and was not run here; the two prompt-content pin suites it guards alongside are green.

Checklist

  • My code follows the project's coding standards
  • I have performed a self-review of my own code
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have updated the documentation accordingly
  • My changes generate no new warnings or errors

Screenshots / Logs

A finding whose attribution the section does not carry is logged at INFO, in the voice of #650's citation corrections:

Finding '<title>' (<path>:<line>) attributes a claim to patch coverage that this review's section does not carry; the verifier is told what the section actually lists

Additional Notes

No configuration, no database change, no new dependency, no added model call. Behaviour is unchanged for a review with no coverage report and no path-scoped rules, which is the common case: nothing is attached and the verifier's material is exactly what it is today. CHANGELOG.md gets an ### Added entry under [Unreleased], beside #650's.

…verifier

The review pass is handed context the verification pass is not: the patch
coverage measurement for the head commit (#115) and the maintainers'
path-scoped rules (#33). A finding raised from one of those reaches the
verifier as a claim about material nobody showed it, which is the ground the
verifier's own prompt tells it to reject on, so every context dimension added
to the review pass makes the verifier more likely to demote findings the
reviewer was right to raise.

The finding now carries its grounding. The review prompt requires a finding
raised from a context section to quote the measurement or the rule it rests
on, and before the verification call each finding is matched against the
sections the review actually supplied: a finding anchored to a line the
coverage section lists carries that measurement, and a finding on a file a
scope governs carries that scope's rules verbatim. The verifier is told to
treat that material as established, so it stops demoting such a finding for
resting on an artifact outside its own material.

The other direction is attached too. A finding that credits a coverage report
for a line the section does not list, or in a review that read no report at
all, carries that fact instead, and the verifier is told the attributed
measurement is not established and earns the finding nothing. A quoted
measurement nobody took must not buy a finding a verdict it has not earned.
No such note is needed for a scoped rule: the scope's real text travels beside
the finding, so a rule the maintainers never wrote is visible as absent from
it.

The evidence rides on the candidate JSON beside the resolved cited location
(#650) rather than in a verifier input of its own, so the next context
dimension reaches the verifier without a new prompt slot. Both notes are
charged to one character budget held by the review, extracted from the
cited-location round for that reason, so what is attached can never rival the
diff the verifier is reading. What is read is the rendered section the
reviewer was shown, caps and all, so a file the section's own render dropped
is one no finding can claim a measurement for. No model call is added, and
when nothing matches, nothing is attached.
@devops-thiago devops-thiago added this to the v0.6.9 milestone Sep 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@thrillhousebot

Copy link
Copy Markdown
Contributor

🤖 ThrillhouseBot PR Summary

What this PR does

Candidates are now handed the review-context material they rest on: the bot matches each finding against the rendered patch-coverage section and the maintainers' path-scoped rules and attaches a context_evidence field to it, supporting or contradicting, before the verification call. The two evidence resolvers share one character budget with the #650 cited-location resolution, and the generator and verifier prompts are taught to quote and trust (or distrust) that grounding.

Description vs. Implementation

No mismatch found between the PR description and the change.

Control-Flow Diagram

🔀 Show diagram
flowchart TD
  O["ReviewOrchestrator.review"] --> R["ReviewEvidence.forReview"]
  R --> B["EvidenceBudget shared"]
  R --> CLR["CitedLocationResolver.Round"]
  R --> CER["ContextEvidenceResolver.Round"]
  CLR --> FP["FindingPipeline.run"]
  CER --> FP
  FP --> FE["forFindings(batch findings)"]
  FE --> FVS["FindingVerificationService.verify"]
  FVS --> RC["renderCandidates: context_evidence on candidate"]
  RC --> V["Verification model call"]
Loading

Changes Overview

  • Files changed: 20
  • Lines added: +1306
  • Lines removed: -197

Changed Files

File Change Summary
CHANGELOG.md Modified Adds an Unreleased Added entry describing the #475 self-carrying-evidence feature beside #650's.
src/main/java/dev/thiagogonzaga/thrillhousebot/review/CitedLocationResolver.java Modified Budget fields and reserve logic replaced by the injected shared EvidenceBudget; private Key moved out to FindingKey.
src/main/java/dev/thiagogonzaga/thrillhousebot/review/ContextEvidenceResolver.java Added New resolver: parses the rendered coverage section, matches findings to coverage lines and scoped rules, attaches supporting or contradicting notes.
src/main/java/dev/thiagogonzaga/thrillhousebot/review/EvidenceBudget.java Added New shared character budget (1200/note, 6000/round) extracted from CitedLocationResolver, with reserve-then-charge under one monitor and surrogate-safe truncation.
src/main/java/dev/thiagogonzaga/thrillhousebot/review/FindingKey.java Added New shared (file, line, title) lookup key usable after the pipeline rebuilds findings.
src/main/java/dev/thiagogonzaga/thrillhousebot/review/FindingPipeline.java Modified Pipeline threads a ReviewEvidence through the batch and single-call lanes, resolving both resolvers against findings as the model raised them, before the quote validator.
src/main/java/dev/thiagogonzaga/thrillhousebot/review/ReviewEvidence.java Added New record opening the cited-location and context-evidence rounds together over one shared budget, with a NONE default.
src/main/java/dev/thiagogonzaga/thrillhousebot/review/ReviewOrchestrator.java Modified Orchestrator now opens one ReviewEvidence (both rounds, one budget) and passes it to FindingPipeline.run.
src/main/java/dev/thiagogonzaga/thrillhousebot/review/ReviewPromptAssembler.java Modified Scoped-rules guidance now asks the finding to quote the rule sentence and the glob that scoped it.
src/main/java/dev/thiagogonzaga/thrillhousebot/review/ai/FindingVerificationService.java Modified Candidates gain a nullable context_evidence field beside cited_location; CitedLocations/ContextEvidence are bundled into a FindingEvidence record passed to renderCandidates.
src/main/java/dev/thiagogonzaga/thrillhousebot/review/ai/FindingVerifierPrompts.java Modified Adds the verifier's context_evidence rule: matched material is established, an uncarried attribution earns nothing, absence implies nothing.
src/main/java/dev/thiagogonzaga/thrillhousebot/review/ai/PrReviewPrompts.java Modified Adds the SELF-CARRYING EVIDENCE rule to the system prompt and a carry-the-measurement instruction to the coverage request.
src/test/java/dev/thiagogonzaga/thrillhousebot/review/CitedLocationResolverTest.java Modified Calls now pass an explicit EvidenceBudget and read the cap constants from EvidenceBudget.
src/test/java/dev/thiagogonzaga/thrillhousebot/review/ContextEvidenceResolverTest.java Added Covers supporting, contradicting, both-dimension, ambiguity-refusal, budget-drop and render-parity cases for the new resolver.
src/test/java/dev/thiagogonzaga/thrillhousebot/review/FindingPipelineTest.java Modified Mechanical rename of the disabled cited-location round to ReviewEvidence.NONE; one test constructs a combined evidence round.
src/test/java/dev/thiagogonzaga/thrillhousebot/review/ReviewEvidenceTest.java Added Verifies the budget is shared across both resolvers and that NONE attaches nothing.
src/test/java/dev/thiagogonzaga/thrillhousebot/review/ReviewPromptAssemblerTest.java Modified Pins the scoped-guidance sentence asking findings to quote the rule they rest on.
src/test/java/dev/thiagogonzaga/thrillhousebot/review/ai/FindingVerificationServiceTest.java Modified Pins that renderCandidates emits context_evidence on the right candidate and omits both fields when nothing resolved.
src/test/java/dev/thiagogonzaga/thrillhousebot/review/ai/FindingVerifierPromptsContentTest.java Modified Pins the verifier's context_evidence prompt markers, including the fabricated-measurement guard wording.
src/test/java/dev/thiagogonzaga/thrillhousebot/review/ai/PrReviewPromptsContentTest.java Modified Pins the new SELF-CARRYING EVIDENCE and carry-the-measurement prompt markers.

Risk Assessment

Risk Count
🔴 Critical 0
🟠 High 0
🟡 Medium 0
🔵 Low 2

Key Findings

  • LOW: Comment for the removed NO_CITED_LOCATIONS constant now dangles on the pipeline field (src/test/java/dev/thiagogonzaga/thrillhousebot/review/FindingPipelineTest.java:77)

Things to double-check

1 lower-confidence finding
  • LOW: governs() trusts an abbreviated file list and never refuses a suffix-ambiguous citation (src/main/java/dev/thiagogonzaga/thrillhousebot/review/ContextEvidenceResolver.java:274) (low confidence — verify before acting)

⚠️ Required CI Checks Status

Some required checks are still pending or have failed:

Check Type Status Detail
test check-run ⏳ Pending -

Automated review by ThrillhouseBot. Reply with /review to re-run.

Comment thread src/test/java/dev/thiagogonzaga/thrillhousebot/review/FindingPipelineTest.java Outdated

@thrillhousebot thrillhousebot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ThrillhouseBot noted 1 lower-confidence item(s) under Things to double-check in the PR summary (not posted as inline threads):

  • LOW: governs() trusts an abbreviated file list and never refuses a suffix-ambiguous citation (src/main/java/dev/thiagogonzaga/thrillhousebot/review/ContextEvidenceResolver.java:274)
    The scope gate is: 'return scope.files().stream().anyMatch(f -> f.equals(cited) || CitedLocationResolver.sharesPathSuffix(f, cited));' while the rule note it feeds asserts '' + cited + ' is one of the files in this pull request they govern'. Two fidelity gaps are visible against other material in this PR. (1) The scoped-rules guidance in ReviewPromptAssembler states 'that list may be abbreviated; the glob is what decides' — so a scope that genuinely governs the cited file can attach nothing if the cited file was dropped from the abbreviation, silently sending the finding to the verifier without its grounding, the exact failure this PR exists to fix. If AppliedScope.files() is the abbreviated list, matching the scope glob (as the prompt text says it decides) would be the correct test rather than membership in files(). (2) Unlike coveragePathFor, which explicitly refuses to guess when two listed files match one citation ('Two listed files match the citation, so which one it means is not settled; saying nothing beats naming another file's measurement as this finding's'), governs() uses anyMatch and would attach a scope's rules for, say, 'Renderer.java' when the scope lists src/a/Renderer.java but the finding actually concerns src/b/Renderer.java — asserting a governance fact that may be false, the direction the PR's own thesis ('a quoted measurement nobody took is not evidence') guards against for measurements. Verify what InstructionsResolver puts into AppliedScope.files() and the exact contract of sharesPathSuffix; an ambiguity-refusing variant mirroring coveragePathFor is a safe local fix for (2) regardless.

@thrillhousebot thrillhousebot Bot added enhancement New feature or request java Pull requests that update java code labels Sep 16, 2026
@codecov

codecov Bot commented Sep 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…ranches

The new resolver's defensive paths went untested: a review that loaded no
coverage section and no scopes, a finding keyed at an anchor another finding
already claimed, a finding citing no file, two scopes governing one file, a
citation that names a governed file without its leading directory, a scope
whose rules are empty and a finding carrying no prose at all, and a range
bound the section's roll-up leaves unparseable. Each is a shape the review
pass can produce, and none of them may throw or attach material the sections
do not hold.
@thrillhousebot

Copy link
Copy Markdown
Contributor

🤖 ThrillhouseBot — changes since the last review

  • New findings this round: 2
  • Previous findings resolved: 0
  • Previous findings still open: 2

@thrillhousebot thrillhousebot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ThrillhouseBot noted 2 lower-confidence item(s) under Things to double-check in the PR summary (not posted as inline threads):

  • MEDIUM: Size-cap-cut section lines claim to fail parsing but can parse into a forged measurement (src/main/java/dev/thiagogonzaga/thrillhousebot/review/ContextEvidenceResolver.java:324)
    The parseUncovered javadoc asserts: "the heading, the prose, the roll-up count and a line the section's own size cap cut in half all fail to parse and are skipped, which is the honest outcome for material the reviewer could not read either." The code directly below contradicts that for a class of cuts: a "- " line containing ": " parses wherever the cut lands after the colon. Counterexample within the domain the comment itself establishes (the class doc says the section has a total size cap, and the comment anticipates mid-line cuts): the cap cutting "- src/main/java/app/App.java: 40-47, 61" after the colon leaves "- src/App.java: 40-47, 6"; lines 322-326 put the value in the map, and in rangesContain the bare token "6" parses through parseLine as line 6, so rangeContains returns true. A finding on line 6 then receives the supporting note "The patch-coverage section this review supplied lists line 6 ... among the added lines ... never executed" — a measurement created by truncation rather than by the report, the exact fabricated-evidence direction this class's own design ("A quoted measurement nobody took is not evidence") forbids. A cut landing immediately after "path: " stores an empty ranges string, which also renders into the contradiction message as "lists these added lines of path as never executed: .". A maintainer trusting the comment would believe cut lines are safely inert; they are not. The honest fix is on the renderer side — PatchCoverageResolver.render should end the section on a line boundary or emit a truncation marker the parser can decline — since a cut "61"→"6" is indistinguishable from a genuine line token at parse time. Confidence medium because PatchCoverageResolver.render is not in the diff; verify where its total-size cap truncates.
  • LOW: Attribution literal "never executed" also matches diff-grounded unreachable-code findings (src/main/java/dev/thiagogonzaga/thrillhousebot/review/ContextEvidenceResolver.java:93)
    COVERAGE_ATTRIBUTIONS (lines 87-95) includes the bare phrase "never executed" (line 93), described as "the wording the review prompt itself asks for". But the same phrase is the natural wording of a finding grounded purely in the diff with no coverage claim at all — synthesized input not in the diff: "This branch is never executed: the guard above returns early on every path", a static-unreachable claim, in a review that supplied a coverage section listing the cited file but not that line. For such a finding attributesToCoverage returns true, so coverageNote returns the contradiction "The patch-coverage section this review supplied lists these added lines of path as never executed: ... The cited line N is not among them.", and the verifier prompt instructs that "the attribution earns the finding nothing" and to "reject the finding when the attribution was all it had" — a verifier-side push against a sound finding that claimed no measurement. The class comment justifies the narrow list by saying a broader scan would "start contradicting findings that never claimed a measurement at all", yet this literal can hit exactly that case. The consequence is mitigated (the verifier is also instructed to judge the diff remainder on its merits), and the false-negative direction — a coverage wording the scan misses — is deliberately accepted, so risk stays low and the wording is held at low confidence; verify the behavior against a dead-code finding in a review with a supplied coverage section. A stricter gate would require a phrase naming the report ("coverage report" / "patch coverage") rather than the outcome phrase alone.

@thrillhousebot thrillhousebot Bot added the testing Test coverage and test quality label Sep 16, 2026
A caught exception the handler never reads is written as an unnamed pattern
elsewhere in the codebase; the range parser's fallback was the one place that
still bound a variable for it.
@thrillhousebot

Copy link
Copy Markdown
Contributor

🤖 ThrillhouseBot — changes since the last review

  • New findings this round: 2
  • Previous findings resolved: 0
  • Previous findings still open: 4

Comment thread src/test/java/dev/thiagogonzaga/thrillhousebot/review/FindingPipelineTest.java Outdated

@thrillhousebot thrillhousebot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ThrillhouseBot noted 1 lower-confidence item(s) under Things to double-check in the PR summary (not posted as inline threads):

  • LOW: disabled() round's 'attaches nothing' comment contradicts its contradiction notes (src/main/java/dev/thiagogonzaga/thrillhousebot/review/ContextEvidenceResolver.java:121)
    The javadoc on ContextEvidenceResolver.disabled() states: 'A round that attaches nothing, for a caller that loaded no review context.' The body it documents — 'return new Round("", PathScopedInstructions.NONE, new EvidenceBudget());' — contradicts that in a demonstrable case: a Round built from a blank section has coverageSupplied == false, and coverageNote then returns contradiction(...) for any finding whose title/description matches COVERAGE_ATTRIBUTIONS ('coverage report', 'never executed', …), i.e. it attaches a verifier-facing note. The PR's own test proves this exact state attaches something: attachesNothingWhenTheReviewLoadedNoSectionAndNoScopes builds the equivalent round via 'ContextEvidenceResolver.forReview(null, null, new EvidenceBudget())' and asserts the evidence 'contains("supplied no patch-coverage section")'. The same false statement appears a second time on ReviewEvidence.NONE ('Resolves nothing, for a caller with no repository access and no loaded context.'). Contrast with the established sibling CitedLocationResolver.disabled(), whose comment 'A round that resolves nothing' is true because locate() short-circuits on prClient == null — the new symmetric-looking API diverges. A maintainer who picks disabled()/ReviewEvidence.NONE for a no-context caller trusting the comments believes the verifier's material is unchanged, when in fact coverage-attributing findings gain a contradiction note. Fix the comments (the behavior itself is the honest design direction); if genuine inertness is intended for disabled(), gate the contradiction accordingly — but that would change ReviewEvidence.NONE callers, so the comment correction is the safe fix.

@devops-thiago

Copy link
Copy Markdown
Owner Author

/review

@thrillhousebot

Copy link
Copy Markdown
Contributor

🤖 ThrillhouseBot — changes since the last review

  • New findings this round: 2
  • Previous findings resolved: 0
  • Previous findings still open: 6

@thrillhousebot thrillhousebot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ThrillhouseBot noted 2 lower-confidence item(s) under Things to double-check in the PR summary (not posted as inline threads):

  • MEDIUM: parseUncovered javadoc claims cut section lines fail to parse; parseable truncations are accepted instead (src/main/java/dev/thiagogonzaga/thrillhousebot/review/ContextEvidenceResolver.java:311)
    The javadoc on parseUncovered states: "the heading, the prose, the roll-up count and a line the section's own size cap cut in half all fail to parse and are skipped, which is the honest outcome for material the reviewer could not read either." The code it documents disagrees: byPath.put(entry.substring(0, colon), entry.substring(colon + 2).strip()); accepts any line that retains the "- " prefix, the ": " separator and a numeric range prefix — it has no way to detect that a line was truncated, so a parseable cut is trusted as if it were complete. The class's own javadoc elsewhere establishes that cut lines occur at all: "The coverage section is capped at a file count, a range count per file and a total size, so a file the render dropped is a file the reviewer never saw." Synthesized input (not in the diff): a total-size truncation of the rendered per-file line "- src/main/java/app/Renderer.java: 40-47, 61" that lands after ", 6" leaves "- src/main/java/app/Renderer.java: 40-47, 6", which parses to ranges "40-47, 6"; rangesContain then returns true for line 6 and coverageNote attaches "lists line 6 of …Renderer.java among the added lines … never executed" — fabricated supporting evidence produced by an artifact of truncation, exactly the false-evidence class this PR exists to prevent (a cut after "40-4" instead yields the never-matching range 40..4 and silently loses the supporting note the reviewer could partially see). Whether PatchCoverageResolver's total-size cap truncates mid-line, and at which character boundaries, is governed by that renderer's cap mechanics, which are not in the provided material — verify where its cap cuts before treating a mid-line cut as reachable; the contradiction between the comment's universal "all fail to parse" claim and the accepting parse code is demonstrable from the diff alone. Suggested fix direction: have the parser reject a trailing truncation marker or verify the section's declared integrity before trusting parsed ranges, or correct the comment to state what actually happens to parseable cuts.
  • LOW: "never executed" attribution phrase also matches static dead-code prose, attaching a contradiction note to findings that never claimed a measurement (src/main/java/dev/thiagogonzaga/thrillhousebot/review/ContextEvidenceResolver.java:91)
    COVERAGE_ATTRIBUTIONS includes the literal "never executed" (beside "coverage report", "patch coverage", …). The class javadoc defends the scan's width only against wider phrases: "a broader scan ('untested', 'no tests') would start contradicting findings that never claimed a measurement at all" — but the same false-attribute shape exists for this entry. Synthesized input (not in the diff): a finding whose description reads "The catch block on this line is unreachable — the guard two lines up returns first, so it is never executed." That is static dead-code reasoning, not an attribution to a coverage report, yet text::contains matches the literal, so with a supplied section listing the file without that line, coverageNote falls through to contradiction(...) — the candidate is told the cited line is "not among them" the section's list, and contradiction() logs at INFO: "attributes a claim to patch coverage that this review's section does not carry" for a finding that made no such claim. The verifier prompt then instructs: "Judge what is left on the diff alone, and reject the finding when the attribution was all it had", and "the attribution earns the finding nothing". The project's own test (contradictsACoverageClaimWhenThisReviewSuppliedNoSection) uses "This line is never executed, so the branch is untested." as its attribution fixture, so treating this phrasing as attribution-adjacent is documented intent, and the verifier is still told to judge the remaining substance on the diff alone — which is why impact is bounded and the risk is low. Verification request: run the eval corpus over static dead-branch phrasings that naturally contain "never executed" ("this branch is never executed when input is empty", "unreachable, so never executed") and confirm the contradiction note plus the mislabeled INFO log do not suppress valid findings; if they do, the literal needs a co-occurring coverage-report cue (e.g. match "never executed" only alongside "coverage"/"report" in the same text).

…idence

Four defects in the evidence this branch attaches, all in the direction the
feature exists to close: material a finding must not be able to claim.

The coverage section's size cap cut mid-line, and half of "61" is "6". An
entry the cap cut reads to everything downstream as a measurement the report
never made, and nothing there can tell a cut token from a real one, so a
finding anchored at that number would have carried supporting evidence for a
line nobody measured. The cap now falls on a line boundary and the section
never holds a partial entry.

The scan that decides whether a finding credits the coverage report matched
"never executed", which is also how a finding grounded in the diff alone
describes unreachable code. Such a finding was told its measurement is not in
the section, and the verifier is instructed to give an unestablished
attribution nothing — against a claim it never made. The phrases now all name
the report.

A citation that lost a leading directory was matched against the scoped file
lists with anyMatch, so a finding about `src/b/Renderer.java` citing
`Renderer.java` could be handed the rules of a scope that governs
`src/a/Renderer.java` alone, asserting a governance fact that is not true.
The lookup now refuses an ambiguous citation and names the file it matched,
the same discipline the coverage lookup already applied.

A round built by disabled() said "this review supplied no patch-coverage
section" to a coverage-attributing finding. A caller that loaded no context
knows nothing about what the review measured, so that is a statement about
the caller; the round is silent instead, which is also the contract its
cited-location sibling has.

Also drops the javadoc left over the pipeline test's field when the constant
it documented was removed.
@devops-thiago

Copy link
Copy Markdown
Owner Author

@thrillhousebot resolved src/main/java/dev/thiagogonzaga/thrillhousebot/review/ContextEvidenceResolver.java:324 — Size-cap-cut section lines claim to fail parsing but can parse into a forged measurement

Correct, and it reproduced. PatchCoverageResolver.render ended with ConfigKeyContextResolver.truncate(rendered, MAX_TOTAL_CHARS) + "\n… (patch coverage truncated)", which cuts at a character count, so the last entry could keep its - , its : and a range list ending in half a number. A test rendering twelve wide files fails against the unfixed renderer with exactly the shape described:

[ERROR]   PatchCoverageResolverTest.cutsTheSectionOnALineBoundary a surviving entry must be a whole rendered line: - src/main/java/dev/thiagogonzaga/thrillhousebot/review/VeryLongName10.java: 10001, 1 ==> expected: <true> but was: <false>

1 there is the surviving half of 10003, and a finding anchored at line 1 of that file would have been handed "the report records this line as never executed" for a measurement nobody took.

Fixed in 6846c20, on the renderer side as suggested, because nothing downstream can tell a cut token from a real one: the cap now falls on the last line boundary before it, so the section never holds a partial entry. PatchCoverageResolverTest.cutsTheSectionOnALineBoundary renders each file alone and asserts every surviving entry is one of those whole lines.

One half of the report is refuted: a cut landing immediately after path: does not store an empty ranges string. parseUncovered strips each line before testing it, so - path: becomes - path:, indexOf(": ") returns -1, and the line is skipped. ContextEvidenceResolverTest.skipsASectionLineThatCarriesNoRanges pins that.

The parseUncovered javadoc no longer claims a cut line fails to parse. It now says what is true: the heading, the prose, the roll-up count and a line carrying no ranges do not parse, and a mid-range cut cannot reach this parser because the cap is applied on a line boundary where the section is written.

@thrillhousebot

Copy link
Copy Markdown
Contributor

The next review will close every previous finding this comment names by its path:line and title; anything it does not name stays open.

@devops-thiago

Copy link
Copy Markdown
Owner Author

@thrillhousebot resolved src/main/java/dev/thiagogonzaga/thrillhousebot/review/ContextEvidenceResolver.java:91 — "never executed" attribution phrase also matches static dead-code prose, attaching a contradiction note to findings that never claimed a measurement

Correct. "The catch block is unreachable: the guard two lines up returns first, so it is never executed" is diff-grounded dead-code reasoning that names no report, and with a coverage section present listing that file but not that line it was told "the cited line is not among them" — a contradiction of a claim the finding never made, plus an INFO line saying it attributed something to patch coverage.

Fixed in 6846c20, along the line suggested: every phrase in COVERAGE_ATTRIBUTIONS now names the source rather than describing the outcome — coverage report, patch coverage, coverage section, coverage measurement, coverage data. never executed, no test executes and not executed by any test are gone. The wording the review prompt asks for ("the coverage report shows this line is never executed") still matches on its first two words, and a phrasing the scan misses attaches nothing, which is the behaviour the verifier had before this branch. ContextEvidenceResolverTest.saysNothingToAFindingThatNamesNoReport uses your example verbatim and asserts nothing is attached; the no-section fixture was reworded to name the report, since it exists to test an attribution.

@devops-thiago

Copy link
Copy Markdown
Owner Author

@thrillhousebot resolved src/main/java/dev/thiagogonzaga/thrillhousebot/review/ContextEvidenceResolver.java:274 — governs() trusts an abbreviated file list and never refuses a suffix-ambiguous citation

Half right, and the half that is right is fixed.

(2) is a real defect and matches the asymmetry you name with coveragePathFor. With one scope listing src/a/Renderer.java and another src/b/Renderer.java, a finding citing Renderer.java was handed the first scope's rules and told that path "is one of the files in this pull request they govern" — a governance fact that is not known. Fixed in 6846c20: the lookup is now governedPathFor, which takes an exact match first and otherwise accepts a suffix match only when exactly one governed file in the whole review matches, returning nothing when two do. When the matched path differs from the cited one the note says so ("The cited path Renderer.java matches src/a/Renderer.java, the only file under a maintainer-scoped glob it matches") instead of asserting the citation is itself a governed file. refusesToGuessWhichGovernedFileASuffixCitationNames and governsAFileACitationNamesWithoutItsLeadingDirectory cover both directions.

(1) is refuted. AppliedScope.files() is not the abbreviated list. PathScopedInstructions.resolve stores every match — var matched = filenames.stream().filter(globs::matches).toList(); — and the abbreviation the guidance warns about happens later and only at render time, in PromptSections.formatFiles, which caps the printed list at MAX_LISTED_FILES and rolls the rest up into "and N more matching this glob". So membership in files() is membership in the glob's full match set for this pull request, and a governed file can never be missing from it. Matching the glob again here would compile the same glob a second time to reproduce a list the review context already holds.

@thrillhousebot

Copy link
Copy Markdown
Contributor

The next review will close every previous finding this comment names by its path:line and title; anything it does not name stays open.

1 similar comment
@thrillhousebot

Copy link
Copy Markdown
Contributor

The next review will close every previous finding this comment names by its path:line and title; anything it does not name stays open.

@devops-thiago

Copy link
Copy Markdown
Owner Author

@thrillhousebot resolved src/main/java/dev/thiagogonzaga/thrillhousebot/review/ContextEvidenceResolver.java:121 — disabled() round's 'attaches nothing' comment contradicts its contradiction notes

Correct: a round built from a blank section had coverageSupplied == false, so any coverage-attributing finding got the "this review supplied no patch-coverage section" note, and both javadocs said otherwise.

Fixed in 6846c20 by making the behaviour match the comment rather than the other way round, because the note was also wrong on its own terms: a caller that loaded no context has not established that no coverage report was read — it never looked — so the sentence is a statement about the caller, not about the review. disabled() now builds a silent round whose locate returns ContextEvidence.NONE whatever it is asked, which is the contract CitedLocationResolver.disabled() already had and the symmetry the shared ReviewEvidence record implies. ReviewEvidence.NONE's javadoc says "attaches nothing at all", which is now true. aDisabledRoundAttachesNothingAtAll pins it, and the no-context test builds its round through forReview(null, null, …) — a real review that read no report, which does still carry the contradiction — so both paths stay covered.

@thrillhousebot

Copy link
Copy Markdown
Contributor

The next review will close every previous finding this comment names by its path:line and title; anything it does not name stays open.

The entry describes what the attached evidence reads; the cap that decides
where that material ends is part of it.
@devops-thiago

Copy link
Copy Markdown
Owner Author

/review

@thrillhousebot thrillhousebot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No new issues in this revision, but 1 previous finding(s) remain unresolved — fix them, or reply on their review thread with why they are deferred. A finding listed only under "Things to double-check" has no thread: clear it by commenting @thrillhousebot resolved path/to/File.java:42 — <the finding's title> on this PR.

The note for a line a listed file's ranges do not name asserted that the line
is not among them. A file's rendered ranges are capped like the file list is,
and the render discloses that with its own roll-up, so a line past the named
ones may well have been measured and the assertion could cost a real
coverage-grounded finding its verdict — the failure this evidence exists to
prevent. When the ranges carry that roll-up the note says the section names
only part of the file's uncovered lines and leaves the question open.

The file-level hedge had a hole of its own: the size cap takes whole lines and
the roll-up is the last of them, so a cut section can name fewer files than
the report measured with no count left to say so. The parse now carries the
cut, and the note says the section may not name every file.
@thrillhousebot

Copy link
Copy Markdown
Contributor

🤖 ThrillhouseBot — changes since the last review

  • New findings this round: 0
  • Previous findings resolved: 1
    • src/main/java/dev/thiagogonzaga/thrillhousebot/review/ContextEvidenceResolver.java:244 — Contradiction note denies a coverage measurement the section's own range roll-up may hide
  • Previous findings still open: 0

@thrillhousebot thrillhousebot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ThrillhouseBot found no issues in this PR, but some checks are still pending or failed:

  • Check test is pending

ThrillhouseBot closed 1 previous finding(s) this round:

  • src/main/java/dev/thiagogonzaga/thrillhousebot/review/ContextEvidenceResolver.java:244 — Contradiction note denies a coverage measurement the section's own range roll-up may hide

thrillhousebot[bot]
thrillhousebot Bot previously approved these changes Sep 16, 2026

@thrillhousebot thrillhousebot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Required CI is now green for 5e6339a, so the approval the earlier review held back is posted. The code was not re-reviewed: that review found no issues, and only the CI gate held its approval.

@devops-thiago

Copy link
Copy Markdown
Owner Author

/review

@thrillhousebot

Copy link
Copy Markdown
Contributor

🤖 ThrillhouseBot — changes since the last review

  • New findings this round: 2
  • Previous findings resolved: 0
  • Previous findings still open: 0

Comment thread src/main/java/dev/thiagogonzaga/thrillhousebot/review/EvidenceBudget.java Outdated

@thrillhousebot thrillhousebot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ThrillhouseBot noted 1 lower-confidence item(s) under Things to double-check in the PR summary (not posted as inline threads):

  • LOW: Single-stem coverage scan misses fabricated attributions worded without the word 'coverage' (src/main/java/dev/thiagogonzaga/thrillhousebot/review/ContextEvidenceResolver.java:421)
    Dimension 5/heuristic: namesCoverage gates the contradicting direction on one substring, 'return text.contains(COVERAGE_MENTION);' where COVERAGE_MENTION = "coverage". Any fabricated coverage attribution worded around the stem escapes it and reaches the verifier with neither the measurement nor a contradiction attached — and the new absence rule in FindingVerifierPrompts says absence 'implies nothing either way', so the verifier no longer has the old 'claim about unshown material' rejection ground to fall back on. Synthesized probe (input not in the diff): a finding whose description reads 'The CI instrumentation run for this commit confirms this branch has no test reaching it' — it credits a measurement nobody took, contains no 'coverage' stem, and travels uncontradicted. The class javadoc discloses this trade-off deliberately (scanning outcome words like 'never executed' would contradict diff-grounded dead-code findings), so this is a design-cost observation, not a confirmed defect: verify whether the scan should add a second stem of report-artifact nouns that cannot appear in a diff-grounded finding ('instrumentation report', 'the CI measurements'). A test in-diff exercises paraphrase handling (contradictsAParaphrasedCoverageClaimToo uses 'Per the coverage analysis', which still contains the stem), so a no-stem phrasing is beyond its reach.

The constant came out of the cited-location round with its comment unchanged,
and the comment still described the per-round cap the extraction replaced. One
review holds one budget and every resolver charges it; reading the cap as
per-round again would double what a review can attach.
@thrillhousebot

Copy link
Copy Markdown
Contributor

🤖 ThrillhouseBot — changes since the last review

  • New findings this round: 1
  • Previous findings resolved: 1
    • src/main/java/dev/thiagogonzaga/thrillhousebot/review/EvidenceBudget.java:33 — MAX_TOTAL_CHARS comment still says the cap covers 'one round', but it now covers the whole review
  • Previous findings still open: 1

@thrillhousebot thrillhousebot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ThrillhouseBot closed 1 previous finding(s) this round:

  • src/main/java/dev/thiagogonzaga/thrillhousebot/review/EvidenceBudget.java:33 — MAX_TOTAL_CHARS comment still says the cap covers 'one round', but it now covers the whole review

@devops-thiago

Copy link
Copy Markdown
Owner Author

/review

@thrillhousebot thrillhousebot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No new issues in this revision, but 2 previous finding(s) remain unresolved — fix them, or reply on their review thread with why they are deferred. A finding listed only under "Things to double-check" has no thread: clear it by commenting @thrillhousebot resolved path/to/File.java:42 — <the finding's title> on this PR.

…'s files

Resolving a finding's scoped rules walked every file any scope governs, once
per finding, and then re-walked each scope's file list to find the scopes
holding the match. Both scans are sized by the pull request, so the cost grew
with findings times changed files for a lookup the review context can answer
directly.

The round now builds two indexes once: the scopes governing each file, and
the governed files by their last path segment. A suffix match asks one path to
end in the other, so it always shares that segment, and a citation that lost a
leading directory is a lookup in the name index rather than a walk. Behaviour
is unchanged and pinned by the existing citation tests, with the file name
that belongs to another directory added to them.
@devops-thiago

Copy link
Copy Markdown
Owner Author

@thrillhousebot resolved src/main/java/dev/thiagogonzaga/thrillhousebot/review/ContextEvidenceResolver.java:421 — Single-stem coverage scan misses fabricated attributions worded without the word 'coverage'

Accurate about the mechanics, and it is the trade the constant records rather than a defect to close. Keeping it, with reasons.

The miss is not a regression. A phrasing the scan does not catch attaches nothing, which is exactly the material the verifier had before this branch: the finding arrives as it always did and is judged on the diff. Nothing this branch adds makes such a claim stronger.

The verifier is not disarmed by the absence rule. "Absence of the field … implies nothing either way about the finding" says the absence establishes nothing — it does not establish the attribution either. A finding that credits "the CI instrumentation run for this commit" is still a claim about an artifact the verifier was not shown, and the prompt's standing grounds for that are untouched: unverifiable claims about artifacts not in the diff are capped at medium risk with low confidence, and a claim resting on an execution signal with no such signal in the provided material is rejected outright.

A second stem list is the direction this branch already backed out of once. "Never executed" was in the list and had to go, because it is also how a diff-grounded finding describes unreachable code; "instrumentation report" and "the CI measurements" are the same shape one step further out, and the list can never be complete — a fabricator's wording is unbounded, so each addition buys a narrow catch at the price of contradicting findings that claimed nothing. The scan is one containment test precisely so its behaviour is something a test can pin, and its errors fall in the direction that costs a finding nothing.

The generator side already narrows the gap. The patch-coverage block requires a finding raised from it to quote the list's own entry for that file, path and range as written, so a fabrication has no measurement to quote and reads as an unsupported claim about an unshown artifact even before this field is consulted.

If dogfooding turns up no-stem fabrications in practice, the fix I would reach for is not a wider scan but the eval corpus deciding which phrasings are real, which is the probe named in the PR body as needing provider credentials this branch cannot run.

@thrillhousebot

Copy link
Copy Markdown
Contributor

The next review will close every previous finding this comment names by its path:line and title; anything it does not name stays open.

@thrillhousebot

Copy link
Copy Markdown
Contributor

🤖 ThrillhouseBot — changes since the last review

  • New findings this round: 0
  • Previous findings resolved: 1
    • src/main/java/dev/thiagogonzaga/thrillhousebot/review/ContextEvidenceResolver.java:209 — Per-finding linear scans make context evidence resolution quadratic in PR size
  • Previous findings still open: 1

@thrillhousebot thrillhousebot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ThrillhouseBot found no issues in this PR, but some checks are still pending or failed:

  • Check test is pending

Additionally, No new issues in this revision, but 1 previous finding(s) remain unresolved — fix them, or reply on their review thread with why they are deferred. A finding listed only under "Things to double-check" has no thread: clear it by commenting @thrillhousebot resolved path/to/File.java:42 — <the finding's title> on this PR.

ThrillhouseBot closed 1 previous finding(s) this round:

  • src/main/java/dev/thiagogonzaga/thrillhousebot/review/ContextEvidenceResolver.java:209 — Per-finding linear scans make context evidence resolution quadratic in PR size

@sonarqubecloud

Copy link
Copy Markdown

@devops-thiago
devops-thiago merged commit c5b6c24 into main Sep 16, 2026
18 checks passed
@devops-thiago

Copy link
Copy Markdown
Owner Author

/review

@devops-thiago
devops-thiago deleted the feat/475-self-carrying-evidence branch September 16, 2026 17:22

@thrillhousebot thrillhousebot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything's coming up Thrillhouse! 🎉

No issues found in this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request java Pull requests that update java code testing Test coverage and test quality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(review): self-carrying evidence — context-grounded findings quote the material they rest on, and the verifier judges it

1 participant