Skip to content

fix(review): disclose overturned declines on every body and name the findings a round closed - #720

Merged
devops-thiago merged 2 commits into
mainfrom
fix/713-714-followup-disclosure
Aug 15, 2026
Merged

fix(review): disclose overturned declines on every body and name the findings a round closed#720
devops-thiago merged 2 commits into
mainfrom
fix/713-714-followup-disclosure

Conversation

@devops-thiago

Copy link
Copy Markdown
Owner

What type of PR is this?

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

Description

Two follow-up round disclosure gaps found in round-7 dogfooding, both on the
publisher surfaces, so they ship together.

#713 — an overturned decline was explained only when the round found nothing new.
appendReopenedDeclineNotes had a single call site, inside the no-new-findings
review body. Any round that also raised a finding took the with-findings path and
dropped the note, so two very different outcomes were indistinguishable from the
PR: the model never reading a reply as a decline and reporting the finding
unresolved, versus the model marking it justified and the deterministic re-check
overturning it on diff evidence. Both rendered as the bare "N previous finding(s)
remain unresolved" — a maintainer who wrote a considered decline saw no
acknowledgement it was read, no contradicting evidence, and no way to tell whether
to argue or to fix. Same undisclosed-decision harm as #542/#598.

The note now rides both with-findings branches (the un-anchored/cap-skipped
fallback body and the ordinary one), placed above the partial-coverage banner so
the coverage caveat stays outermost. It needs no unresolved-count lead-in: each
note is self-contained, and that sentence opens with "No new issues in this
revision", which a round posting findings must not claim. The same filter applies
on both surfaces — only a note carrying the RebuttalContradiction lead-in is
surfaced, never the backstop's generic carry-over note or the model's own prose.

#714 — a cleared finding was never named. The delta comment rendered three
bare integers, so nothing said which finding closed. A maintainer who cleared
one by directive saw "1" and had to diff GitHub thread state across rounds to
learn whether the match took; one who named several in a single comment could not
tell which the reviewer matched and which it silently skipped — and a skip is a
real outcome, since matching is by path:line plus title and the guards reject
several spellings. Worse for a finding published without an inline thread (#712),
where the directive is the only action that can close it and there is no thread
state to diff either.

Both directions from the issue are implemented:

  • The delta names each closed finding under the count it explains, one
    `path:line` — title line each — the same identifiers the directive uses.
    Every finding the resolved count covers is named, not only directive-cleared
    ones: the list has to add up to the integer above it. Bounded at ten names with
    the remainder rolled up as a count, matching ReviewResult.nameList's bound on
    the coverage disclosure's file names; both halves route through MarkdownSafe.
  • A cleared finding's thread gets a reply before it resolves, leaving the record
    at the point of the original discussion. Deterministic prose, the directive
    rendered inside a code span (the form FollowUpAnalyzer.isClearDirective reads
    as documentation rather than a command), and a failed reply is swallowed so it
    can never cost the thread resolution that follows.

Out of scope, per the issue: refreshing the round-1 summary comment, whose Key
Findings block still lists a closed finding as live. That is the larger change and
may not be worth it; a delta that names what it closed removes most of the
ambiguity on its own.

Related Issues

Closes #713
Closes #714

How Has This Been Tested?

  • Unit tests
  • Integration tests
  • Manual testing

Every new test was run against the unfixed code first and failed there.

#713reopenedDeclineNoteAlsoReachesTheBodyOfARoundThatPostsFindings failed
with Wanted but not invoked: reviewClient.createReview(...) (the round posted its
inline comment and no review body at all), and
reopenedDeclineNoteReachesTheBodyWhenNoFindingCouldBeAnchored failed on a body
carrying only the un-anchored list and the partial-coverage banner. A third test
pins the negative case: a plain unresolved note and a resolved finding's note are
both left out, so that round still posts no review body.

#714 — with the naming and the reply removed (signatures kept), the five new
tests failed on delta bodies rendering **Previous findings resolved:** N with no
list under it, and on Wanted but not invoked: reviewClient.replyToReviewComment(...). Restored, all pass.

Gates, in order:

./mvnw -B spotless:apply                                → BUILD SUCCESS
./mvnw -B clean compile spotbugs:check spotless:check   → BugInstance size is 0
                                                          Spotless: 0 needs changes
./mvnw -B clean test                                    → Tests run: 3258,
                                                          Failures: 0, Errors: 0

Patch coverage: target/site/jacoco/jacoco.xml intersected with
git diff -U0 origin/main -- reports zero uncovered lines and zero uncovered
branches across the three changed main files.

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

The delta comment gains the named sub-list under the count it explains:

## 🤖 ThrillhouseBot — changes since the last review

- **New findings this round:** 3
- **Previous findings resolved:** 2
  - `src/main/java/A.java:10` — Guard the null token
  - `src/main/java/C.java:30` — Close the stream
- **Previous findings still open:** 12

Additional Notes

publishFollowUpDelta and FollowUpDeltaSummary.render take the previous round's
findings so the comment can name what it closed; ReviewOrchestrator already had
that list in hand for thread resolution and passes the same one.

The reopened-decline note rendered only from the no-new-findings body, so a
round that also raised a finding took the with-findings path and dropped it.
That made two very different outcomes indistinguishable from the PR: the model
never reading a reply as a decline and reporting the finding unresolved, versus
the model accepting the decline and the deterministic re-check overturning it on
diff evidence. Both showed as the bare "N previous finding(s) remain
unresolved", so a maintainer who wrote a considered decline saw no
acknowledgement it was read, no contradicting evidence, and no way to tell
whether to argue or to fix — the same undisclosed-decision harm as #542/#598.

The note now rides both with-findings branches: the un-anchored/cap-skipped
fallback body and the ordinary one, placed above the partial-coverage banner so
the coverage caveat stays outermost. Each note is self-contained, naming the
claim, the contradicting line and the way to keep the decline, so it needs no
unresolved-count lead-in above it — that sentence opens with "No new issues in
this revision", which a round posting findings must not claim.

The same filter applies on both surfaces: only a note carrying the
RebuttalContradiction lead-in is surfaced, never the backstop's generic
carry-over note or the model's own unresolved prose.
The follow-up delta comment reported three bare integers, so nothing on the PR
said which finding a round closed. A maintainer who cleared one with an
@thrillhousebot resolved directive saw "1" and had to diff the PR's thread state
across rounds to learn whether the match took; a maintainer who named several in
one comment could not tell which the reviewer matched and which it silently
skipped, and a skip is a real outcome — matching is by path:line plus title and
the guards reject several spellings. It is worse for a finding published with no
inline thread, where the directive is the only action that can close it and
there is no thread state to diff either.

The delta now names each closed finding under the count it explains, one
`path:line` — title line each, using the identifiers the directive itself uses.
Every finding the resolved count covers is named, not only the ones a directive
cleared: the list has to add up to the integer above it. The list is capped at
ten with the remainder rolled up as a count, matching the bound the coverage
disclosure puts on its file names, and both halves of a line route through
MarkdownSafe since they are model text spliced into a list item.

A cleared finding's thread also gets a reply before it resolves, so the record
sits at the point of the original discussion rather than only in a count on a
separate comment. It is deterministic prose stating what happened, with the
directive inside a code span — the form the analyzer reads as documentation
rather than a command — and a failed reply is swallowed so it can never cost the
thread resolution that follows.

Refreshing the round-1 summary comment, whose Key Findings block still lists a
closed finding as live, is deliberately out of scope: it is the larger change,
and a delta that names what it closed removes most of the ambiguity on its own.
@github-actions

Copy link
Copy Markdown
Contributor

Dependency Review

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

Scanned Files

None

@codecov

codecov Bot commented Aug 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@sonarqubecloud

Copy link
Copy Markdown

@thrillhousebot

Copy link
Copy Markdown
Contributor

🤖 ThrillhouseBot PR Summary

What this PR does

Fixes two round-7 disclosure gaps: the overturned-decline explanation note now rides every review body shape (the no-issues body, the un-anchored fallback body, and the ordinary with-findings body), and the follow-up delta comment names each finding a round closed by `path:line` and title (bounded at 10 with a count rollup) while threads closed by a clear directive receive a deterministic reply before they resolve. publishFollowUpDelta and FollowUpDeltaSummary.render now take the previous round's findings, and resolveAddressedThreads gates the reply on the exact conversationClearedNote.

Description vs. Implementation

No mismatch found between the PR description and the change.

Control-Flow Diagram

🔀 Show diagram
flowchart TD
    A["review round completes"] --> B{"posts new findings?"}
    B -- "yes" --> C["with-findings body: append reopened-decline notes"]
    B -- "no" --> D["no-issues body: append reopened-decline note"]
    C --> E["append partial-coverage banner last"]
    D --> E
    A --> F["publishFollowUpDelta with previous findings"]
    F --> G{"any resolved previous finding?"}
    G -- "yes" --> H["name closed findings as path:line and title, cap 10 plus rollup"]
    A --> I["resolveAddressedThreads"]
    I --> J{"note equals conversationClearedNote?"}
    J -- "yes and thread open" --> K["reply on thread, then resolve"]
    J -- "no" --> L["resolve thread only"]
Loading

Changes Overview

  • Files changed: 6
  • Lines added: +515
  • Lines removed: -54

Changed Files

File Change Summary
src/main/java/dev/thiagogonzaga/thrillhousebot/review/FollowUpDeltaSummary.java Modified Delta now names each resolved finding as path:line plus title, bounded at 10 with a rollup.
src/main/java/dev/thiagogonzaga/thrillhousebot/review/ReviewOrchestrator.java Modified Threads the previous round's findings through to the follow-up delta publisher.
src/main/java/dev/thiagogonzaga/thrillhousebot/review/ReviewPublisher.java Modified Adds decline notes to with-findings bodies; replies on directive-cleared threads before resolving.
src/test/java/dev/thiagogonzaga/thrillhousebot/review/FollowUpDeltaSummaryTest.java Modified Covers named resolved findings, the name cap with rollup, null titles, and missing-previous-round fallback.
src/test/java/dev/thiagogonzaga/thrillhousebot/review/ReviewOrchestratorTest.java Modified Tests decline-note on with-findings bodies, cleared-thread reply ordering, and reply-failure resilience.
src/test/java/dev/thiagogonzaga/thrillhousebot/review/ReviewPublisherTest.java Modified Asserts the posted delta names closed findings; updates call sites for the new render signature.

Risk Assessment

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

No new issues found in this PR, but the review cannot be approved until required CI is confirmed green.

⚠️ Required CI Checks Status

Some required checks are still pending or have failed:

Check Type Status Detail
test check-run ⏳ Pending -
format check-run ⏳ Pending -
trivy check-run ⏳ Pending -
frontend check-run ⏳ Pending -
dependency-review check-run ⏳ Pending -

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

@thrillhousebot thrillhousebot Bot added bug Something isn't working enhancement New feature or request java Pull requests that update java code labels Aug 15, 2026
@devops-thiago
devops-thiago merged commit 69a8f95 into main Aug 15, 2026
17 checks passed
@devops-thiago
devops-thiago deleted the fix/713-714-followup-disclosure branch August 15, 2026 02:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working enhancement New feature or request java Pull requests that update java code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A cleared finding is never named, and the summary still lists it as open An overturned decline is explained only when the review has no new findings

1 participant