feat(review): optional delta summary comment on follow-up reviews - #459
Merged
Conversation
Only the first review posts a summary, so a maintainer on a busy PR cannot see what changed since the last bot pass without reading every inline thread. An opt-in follow-up delta comment reports new, resolved and still-open finding counts, disabled by default and skipped entirely when the delta is empty. Refs #327
The first-review test for the delta comment passed both `isFirstReview` and `summaryPosted` as true, so the short-circuit meant it stayed green with the first-review check removed from the guard — it proved only that a round which already posted a summary skips the delta, which a separate test already covers. It now drives the first-review path on its own (`summaryPosted == false`, the shape a swallowed summary-post failure produces) and pins that the round leaves exactly one comment on the PR: the summary itself. The orchestrator delta test likewise verified only that a matching comment was created, which an extra non-matching summary comment would not have contradicted; it now also pins the total comment count. Adds the missing fail-soft case: a delta comment whose post throws must be swallowed so the review still reaches the PR and the session completes rather than failing, which was the one uncovered branch in the new code. Also corrects the superseded wording in the renderer's javadoc — the caller skips the delta when the summary re-post lands, not merely because a finding was superseded — and records that the counts inherit the previous-findings defect tracked in #455. Refs #327
Contributor
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
🤖 ThrillhouseBot PR SummaryWhat this PR doesAdds an opt-in delta summary comment on follow-up reviews, displaying new, resolved, and still-open finding counts, with a truncation disclosure when the diff was partial. Controlled by a new configuration flag, disabled by default to preserve existing quiet follow-up behavior. Control-Flow Diagram🔀 Show diagramflowchart TD
A["ReviewOrchestrator.review()"] --> B["publishSummaryBestEffort"]
B --> C["summaryPosted"]
A --> D["publishFollowUpDeltaBestEffort(auth, req, result, summaryPosted)"]
D --> E{"enabled && !firstReview && !summaryPosted?"}
E -- no --> F["return"]
E -- yes --> G["FollowUpDeltaSummary.render(result)"]
G --> H{"delta present?"}
H -- no --> I["log no delta, return"]
H -- yes --> J["commentClient.createComment(...)"]
J --> K["return true"]
Changes Overview
Changed Files
Risk Assessment
No new issues found in this PR, but the review cannot be approved until CI is confirmed green.
|
| Check | Type | Status | Detail |
|---|---|---|---|
| test | check-run | ⏳ Pending | - |
| frontend | check-run | ⏳ Pending | - |
| actionlint | check-run | ⏳ Pending | - |
| format | check-run | ⏳ Pending | - |
| trivy | check-run | ⏳ Pending | - |
| changes | check-run | ⏳ Pending | - |
| build | check-run | ⏳ Pending | - |
| dependency-review | check-run | ⏳ Pending | - |
Automated review by ThrillhouseBot. Reply with /review to re-run.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
…llowup-delta-summary # Conflicts: # README.md
…llowup-delta-summary
…nent #460 added a pathInstructions component to the RepoSettings record after PrImprovementServiceTest was written, so its two-argument construction no longer compiles and testCompile fails on release/v0.6.0 and every branch merging it. Both call sites exercise the per-repo ignore filter and declare no scoped review rules, so they pass an empty path-instructions list and keep asserting exactly what they asserted before. Refs #327
|
18 tasks
devops-thiago
added a commit
that referenced
this pull request
Aug 8, 2026
…ared-command-batching Resolves the config table in README.md, where this branch's rewrite of the REVIEW_MAX_INPUT_TOKENS row (it now covers /describe and /changelog too) and the REVIEW_FOLLOW_UP_SUMMARY_ENABLED row #459 added landed on the same lines. Both rows are kept. No source conflict: #464 had already restored the three-argument RepoSettings construction on base, and this branch carried the identical change, so the ignore-filter tests of /describe, /changelog and /improve merged as-is. No batching behaviour changes.
18 tasks
devops-thiago
added a commit
that referenced
this pull request
Aug 9, 2026
…463) ## What type of PR is this? - [ ] 🐛 Bug fix - [x] ✨ Feature - [x] 📝 Documentation - [x] 🔧 Refactor - [ ] 🚀 Performance - [ ] ✅ Test - [ ] 🔒 Security - [ ] 📦 Dependency update - [ ] 🏗️ CI/CD ## Description `max-diff-lines` predates token budgeting and had become a second, cruder ceiling sitting in front of it. The review path stopped using it when map-reduce (#53) landed, and `/improve` was moved onto that in #316 — but `/describe` and `/changelog` still shrank a large PR to the first `max-diff-lines` of its rendered diff. A description was therefore written from a partial diff, and a CHANGELOG entry drafted from one, with whole files never reaching the model at all. **The seam.** The three methods `/improve` proved — batch planning, the shared prompt overhead, and the per-repo ignore re-filter — are lifted into `AbstractPrSuggestionGenerator`, parameterised by each command's own prompt constants. `/improve` is refactored onto the lifted versions rather than keeping a private copy. Every on-request suggestion command now plans batches over the reviewable **file list** under the per-call token budget and makes one call per batch. One call: ```java var plan = planBatches(reviewable, inputs, ownSystemPrompt, ownUserPrompt, reservedCalls); ``` **The reduce step is per-command, because the reductions genuinely differ.** Batching is only the map step: | Command | Reduce | Extra AI call? | |---|---|---| | `/describe` | Per-batch partial descriptions **synthesized** into one coherent title + description | Yes — reserved, spent only when >1 batch | | `/changelog` | Per-batch candidate entries **merged** into one entry | Yes — reserved, spent only when >1 candidate | | `/improve` | Local union of per-batch suggestions, deduped by `file:line` | No | | `/generate-tests` | Local union of per-batch test files, deduped by path | No | Nothing is concatenated. Stapling `/describe`'s partials together repeats the overview once per part and reads as several pull requests; for `/changelog`, a deterministic merge could unify headings and drop identical bullets, but the duplicates that actually arise are *not* identical — two batches that saw different files of one feature describe that change in two different sentences, which only a reader that understands them can collapse. Both reduce calls are reserved out of `max-ai-calls` up front, the same way the review path reserves one for its summary, so a run never exceeds the ceiling of one review; a single-batch PR still costs exactly one call. **Also in this change** - The shared overhead is assembled from each command's **own** prompts. Sizing a batch against another command's prompts would let an "in-budget" batch overshoot the real input limit. - Coverage disclosure now comes from `BudgetPlan.omittedFiles()` / `clippedFiles()` — files **named**, not counted — keeping #296's wording. `Inputs.omittedFiles` (the line-cap count) is gone, so nothing can reach for the wrong number. - Coverage on a huge PR is bounded by `max-ai-calls`, not the file list. Files that never got a batch are named. When *no* file fit any batch at all, every command says so and names the files rather than going silent — a misconfigured budget must not look like a bot that ignored the command. An empty plan that omitted *nothing*, because the repository ignores every changed file, is the opposite case and posts nothing. - Per-repo ignore patterns (#449) are applied on top of the global set for every command, and the filtered list stays authoritative for everything downstream — batches *and* the line map alike. That is the bug #452's audit found; the lifted method carries the invariant in its javadoc. - `max-input-tokens <= 0` keeps budgeting off as a single uncapped batch rather than regressing to the line-capped string. - A batch whose model call fails is skipped rather than failing the run, and the shortfall is disclosed. - Sizing callers reach the prompts through `systemPrompt()` / `userPrompt()` accessors: a reference to a `static final String` is inlined into the caller's class file at compile time, and a third copy of a multi-kilobyte prompt trips SpotBugs' `HSC_HUGE_SHARED_STRING_CONSTANT`. The accessors are deliberately *not* named `system()` / `user()` — differing from the constant only by capitalization reads as a typo at the call site. **`/add-docs` is deliberately out of scope.** It does not extend `AbstractPrSuggestionGenerator`; it orders its loading around a hard head-SHA precondition (every output is an inline suggestion, so no head SHA means nothing postable and a distinct user-facing message), and it feeds its assistant a different input set — project stack, a combined `PromptSections.prContext(...)` block, and a pre-rendered instructions section built from `ResolvedInstructions` rather than the content string the shared `Inputs` carries. Folding it in therefore means changing the shared `Inputs` contract at the same time as first lifting the seam, on the command that posts committable edits. It is worth doing and should be tracked separately; `/add-docs` remains line-capped and the README now says so precisely. Note it also still has **no per-repo ignore filter at all**, which is worth carrying into that follow-up. **Path-scoped instructions (#460) are not part of what a command batch carries.** `PathScopedInstructions` is resolved only by `ReviewContextLoader` and rendered only into `ReviewPromptAssembler`'s trailing-guidance slot, so it reaches the review prompt and nothing else. The `repoInstructions` slot of the batched commands is fed solely by `InstructionsResolver.resolve(...).content()` — the global instructions file — which `sharedPromptOverhead(...)` already counts in full. No batch is mis-sized by the scoped rules. ## `/generate-tests` is migrated onto the seam in this PR #461 merged before this one, so `/generate-tests` landed on `release/v0.6.0` still line-capped and still reading `Inputs.omittedFiles`, which this PR removes. The migration #461's agent was going to perform *after* this merged is therefore done **here** — there is no "later", because without it base does not compile. `UnitTestGenerator` now: 1. resolves its effective file list once via `respectPerRepoIgnores(...)` and plans from it, 2. plans token-budgeted batches and sends `batch.text()` per batch, 3. discloses coverage from `disclosure(plan)`, 4. reserves **0** calls — its reduce is a local union, so the whole `max-ai-calls` allowance buys batches. **Two things this surfaced that are worth reading closely.** **The shared overhead was not sufficient for this command, and using it unchanged would have been a real bug.** `sharedPromptOverhead(...)` counts system + user + fence + title + body + instructions. `/generate-tests` also sends the resolved **project stack** on every call — dependency manifests, kilobytes, not a rounding error — so the estimate would have undercounted every batch by the size of the stack and let "in-budget" batches overshoot the model's real input limit. That is precisely the failure the overhead exists to prevent. `planBatches(...)` therefore gains a six-argument form taking the command's own extra per-call sections, and `/generate-tests` declares the stack there. A future adopter with its own extra section must do the same rather than reach for the five-argument form. **It uses its own prompt templates, not the shared ones.** `UnitTestAssistant` is annotated with `UnitTestAssistantPrompts.SYSTEM` / `UnitTestAssistantPrompts.USER` — it does **not** share `PrSuggestionPrompts.USER`, because its user template carries the project-stack section. Sizing its batches against the shared user template would measure the wrong prompt. `UnitTestAssistantPrompts` had no accessors (it is new from #461), so `systemPrompt()` / `userPrompt()` are added to it — SpotBugs failed the build without them, exactly as the accessor javadoc predicts. **Why dedupe by path rather than merge.** Batches partition the file list, so two batches usually propose disjoint test paths. When they do collide, each proposal's `code` is a *complete* compilable file — package, imports and fixtures included, posted verbatim to paste at that path — so two of them at one path are alternatives, not additions. Rendering both would invite pasting the second over the first and silently losing the first's cases, and merging them properly would need a model call for a rare collision. The first wins and the rest are counted in a disclosure line, so the maintainer can re-run for the others. ## Related Issues Fixes #457 ## How Has This Been Tested? - [x] Unit tests - [ ] Integration tests - [ ] Manual testing Every new behavior was validated red/green: the test was written, the production change was mutated to neutralize exactly that behavior, the test was confirmed to **fail**, and the mutation was reverted to confirm it passes. **One mutation initially stayed green and the test was rewritten.** `proposesTestsForFilesThatTheLineCapWouldHaveDroppedEntirely` first asserted only that each batch *contained* its file — which is also true when every call is handed the whole-PR diff, the very behavior being replaced. Strengthened to assert the partition (batch 1 contains `Foo` and **not** `Other`, batch 2 the reverse), it goes red properly: ``` [ERROR] UnitTestGeneratorTest.proposesTestsForFilesThatTheLineCapWouldHaveDroppedEntirely:439 [[THRILLHOUSEBOT-UNTRUSTED-DATA-3d24804df8d0a8a72a18bb0d9f6a121f]] ``` **The project stack is counted in the budget.** Mutation: use the five-argument `planBatches(...)`, leaving the stack out of the overhead. With a 20k-character stack no file can honestly fit, so the correct run makes no call at all; the mutant ships batches that overshoot: ``` [ERROR] UnitTestGeneratorTest.countsTheProjectStackInTheBudgetSoBatchesAreNotOversized:496 No interactions wanted here: ``` **Per-repo ignores stay authoritative.** Mutation: plan from `inputs.reviewableFiles()` instead of the filtered list. ``` [ERROR] UnitTestGeneratorTest.leavesFilesTheRepositoryAskedTheBotToIgnoreOutOfScope:520 [[THRILLHOUSEBOT-UNTRUSTED-DATA-5c207a4fc3e69a526faf47a8f7df5769]] [ERROR] UnitTestGeneratorTest.staysSilentWhenEveryChangedFileIsOutOfScope:574 ``` **Same-path proposals are deduped.** Mutation: drop the `seenPaths` guard. ``` [ERROR] UnitTestGeneratorTest.keepsOneProposalPerPathAndSaysHowManyWereLeftOut:479 ## 🤖 ThrillhouseBot — suggested unit tests ``` **Disclosure comes from the budget plan.** Mutation: `disclosure(plan)` returns `""` and the empty-plan branch returns `null`. ``` [ERROR] UnitTestGeneratorTest.disclosesPartialCoverageEvenWhenNoTestsWereProposed:321 🧪 ThrillhouseBot found nothing in this PR's changes that warrants a new unit test. ==> expected: <true> but was: <false> [ERROR] UnitTestGeneratorTest.namesTheFilesLeftUncoveredWhenTheBatchBudgetRunsOut:303 ## 🤖 ThrillhouseBot — suggested unit tests [ERROR] UnitTestGeneratorTest.namesTheFilesWhenTheBudgetCouldNotCoverASingleOne:555 expected: not <null> ``` Earlier rounds for `/describe`, `/changelog` and `/improve` (batch text vs. line-capped render, synthesis vs. concatenation, reserved reduce call, per-repo ignores, disclosure, budgeting-disabled, nothing-covered, merge declines) all went red as recorded before; the `<= 0` guard mutation remains the one that does not, because `max-input-tokens=0` reaches `Integer.MAX_VALUE` down the fall-through path and yields the same single batch. Build results, on the merge of `release/v0.6.0` at `ad36d22` (#458, #460, #464, #459, #461): ``` ./mvnw -B spotless:apply # clean ./mvnw -B clean compile spotbugs:check spotless:check # BUILD SUCCESS ./mvnw -B clean test # Tests run: 2226, Failures: 0, Errors: 0, Skipped: 0 cd website && npm run build # "All internal links are valid." ``` ## Checklist - [x] My code follows the project's coding standards - [x] I have performed a self-review of my own code - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] I have updated the documentation accordingly - [x] My changes generate no new warnings or errors ## Notes for anything still in flight `Inputs.omittedFiles` is gone. It carried how many files the `max-diff-lines` render dropped; once a command plans its own token-budgeted batches that number describes a render nothing sends to a model, so it is not merely redundant but wrong. Nothing on `release/v0.6.0` or in this tree still reads it. Any command extending `AbstractPrSuggestionGenerator` that is still in flight needs the same four steps `/generate-tests` just took: 1. `disclosure(plan)` rather than a line-cap count. 2. `planBatches(reviewable, inputs, <its own system prompt>, <its own user prompt>, reservedCalls)` — its **own** prompt constants, and the six-argument form if it repeats a section the shared overhead does not know about. Check the merged tree for whether the class exposes `systemPrompt()` / `userPrompt()` accessors or only constants; adding them is required if a sizing reference would inline a third copy. 3. `batch.text()` per batch, never `inputs.diff()`. 4. `respectPerRepoIgnores(target, COMMAND, inputs.reviewableFiles())`, with that same list used for anything that anchors onto the diff. ## Additional Notes **Operator-visible cost change.** `/describe` and `/changelog` on a PR that needs more than one batch now cost one more model call than the batches alone, reserved out of `REVIEW_MAX_AI_CALLS`, so the ceiling per run is unchanged. `/improve` and `/generate-tests` reserve nothing. Documented in the config table, the "AI call budget" section, the command prose, and the Known limitations bullet. **The ignore filter is authoritative for the line resolver, not just the planner.** `/improve` threads the resolved list into both `planBatches(...)` and `post(...)`, where the resolver is built as `new DiffLineResolver(diffFormatter().patchesByReviewableFiles(reviewable))` — never from `inputs.reviewableFiles()`. `PrImprovementServiceTest.neverCommitsASuggestionToAFileTheRepositoryAskedTheBotToIgnore` pins it. `/describe`, `/changelog` and `/generate-tests` build no line map (a proposed test file is a new file with no diff line to anchor to), so they cannot exercise it, but the rule is stated in `respectPerRepoIgnores(...)`'s javadoc for future adopters. **New prompts.** `PrDescribeAssistantPrompts.SYNTHESIS_SYSTEM`/`SYNTHESIS_USER` and `ChangelogAssistantPrompts.MERGE_SYSTEM`/`MERGE_USER`, with matching `synthesize(...)` / `merge(...)` methods on the assistants. Both user templates mirror `PrSuggestionPrompts.USER` — same context sections, same random-fence untrusted-data block — with the partials/candidates in place of the diff. `AiServicePromptRenderingTest` drives both through the real rendering pipeline and asserts every `@V` reaches the message. **No new config keys.**
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



What type of PR is this?
Description
Follow-up reviews carry their signal in the review itself: only the first review posts a summary comment, so a maintainer on a busy PR has to read every inline thread to see what moved since the last bot pass. This adds an opt-in short delta comment on follow-up reviews.
FollowUpDeltaSummary.render(ReviewResult)is a pure function of the counts the follow-up pipeline already produced, and returns four lines:plus the shared partial-coverage disclosure (
ReviewResult.truncationDisclosure) when the diff was truncated.ReviewPublisher.publishFollowUpDelta(...)posts it, andReviewOrchestratorcalls it best-effort between the summary post andpostReview.Design decisions worth a reviewer's attention:
/summaryre-posts and superseded-round refreshes never get a delta beside them), and the delta is non-empty.summaryPosted. That flag gates the redundant-review skips inpostReviewand means "the PR already carries this round's verdict in a comment". A delta comment carries counts, not a verdict, sopublishFollowUpDeltaBestEffortdiscards its return value; a failed summary post still leaves review posting enabled, exactly as before.PrSummaryGenerator.SUMMARY_HEADING. That heading is the markerReviewContextLoader.isBotSummaryCommentuses to decide whether a review is the first one, and which comment the superseded path edits in place — a delta comment carrying it would be mistaken for a summary. A test pins that it is not.resolvedPreviousCount()is strictly theresolvedstatus.justifiedis a maintainer's decline, not a fix, andsupersededis an auto-close because the targeted code left the diff; counting either would overstate what the round fixed. Both are therefore in neither the resolved nor the still-open count.postReview.Default is
false; an untouched deployment keeps today's quiet follow-up review.Known limitation — delta accuracy depends on #455
The still-open count this comment renders is
ReviewResult.unresolvedPreviousCount(), and #455 documents that the previous-findings context is corrupted whenever a review round returns zero findings:FollowUpAnalyzer's fallback feeds the bot's own review body back as a "finding", which both drops real findings out of tracking (so they can never be counted resolved again) and inflates the unresolved count. This PR does not fix that — it is tracked separately — so while #455 is open the counts here inherit its inaccuracy in both directions.Two things limit the blast radius, and both were checked rather than assumed:
unresolved, so new findings and resolved are both zero and the no-delta guard fires. That is the exact shape of the fix(review): previous-findings fallback feeds the bot's own review body back as a "finding", losing real findings and inflating the unresolved count #455 repro (round N raises a finding, round N+1 raises none, round N+2 reports an inflated unresolved count), andnoNewFindingsAndNothingResolvedRendersNothingcovers it. The feature therefore never originates a comment off a miscounted carry-over; it can only display one alongside a genuine new-or-resolved delta.unresolvedPreviousMessageand the APPROVE→COMMENT demotion.The renderer's javadoc records the dependency so it is not lost.
Related Issues
Fixes #327
Depends for count accuracy on #455 (not fixed here).
How Has This Been Tested?
Full suite green (2060 tests),
BugInstance size is 0, spotless clean. JaCoCo shows 100% line and branch coverage on every new method:render8/8 lines and 4/4 branches,publishFollowUpDelta10/10 lines and 8/8 branches,publishFollowUpDeltaBestEffort9/9 lines including the catch,resolvedPreviousCount1/1.cd website && npm ci && npm run buildwas reproduced for the README config-table row and reports "All internal links are valid."Every new test was mutation-tested: production code was neutralized in a targeted way and the test had to go red. Test files were never edited to force a failure.
FollowUpDeltaSummaryTest1. Widen the no-delta guard to
newFindings == 0 && resolved == 0 && unresolvedPreviousCount() == 0, i.e. let a stalled round post:This is the anti-noise case, so it was mutated hardest. Previous findings that merely stayed open must not produce a comment on their own.
2. Replace the no-delta guard with
if (false)— always render. Three red:3. Make
resolvedPreviousCount()also countjustifiedandsuperseded:4. Render the still-open line as a hardcoded
0:5. Drop
ReviewResult.truncationDisclosure(...)from the body:6. Always disclose —
truncationDisclosure(omittedFiles() + 1). The negative case discriminates too:7.
DELTA_HEADING = PrSummaryGenerator.SUMMARY_HEADING:ReviewPublisherTest— each clause of the four-part guard removed in turnDrop
!config.review().followUpSummary().enabled():Drop
result.isFirstReview():Drop
summaryPosted:The
isFirstReviewrow is the one repaired in this PR. As first written the test passedisFirstReview = trueandsummaryPosted = true, so the short-circuit meant it stayed green with the first-review clause removed — it duplicated the already-posted-a-summary test instead of covering the acceptance criterion. It now drives the first-review path alone (summaryPosted = false, the shape a swallowed summary-post failure produces), assertspublishSummaryposts whilepublishFollowUpDeltadoes not, and pins that the round leaves exactly one comment on the PR — the summary, verified by body.ReviewOrchestratorTest— end-to-end throughreview()Run with
-Dtest='ReviewOrchestratorTest$ReviewErrorPaths'.Never call
publishFollowUpDeltaBestEffortfromreview():Remove the
try/catcharound the delta post so the failure propagates — the review never posts and the run falls into failure handling:That second test is new in this PR: the catch in
publishFollowUpDeltaBestEffortwas the only uncovered branch in the new code. It also pins that the failed delta produces no follow-on failure-notice comment.FollowUpSummaryDefaultOffTestSet
thrillhousebot.review.follow-up-summary.enabled=${REVIEW_FOLLOW_UP_SUMMARY_ENABLED:true}inapplication.properties:Set
@WithDefault("true")onFollowUpSummaryConfig.enabled()— survived, and that is correct.application.propertiesalways supplies a value for this key, so the annotation default is unreachable in a running deployment. The test pins the effective default, which is the one that ships, and that is what the mutation above proves. This matches every other flag inReviewConfig.The full suite was run before each commit, not just the touched classes.
Checklist
Screenshots / Logs
Rendered comment on a truncated follow-up round:
Additional Notes
Acceptance criteria
false—@WithDefault("false")onThrillhouseConfig.FollowUpSummaryConfig.enabled(),thrillhousebot.review.follow-up-summary.enabled=${REVIEW_FOLLOW_UP_SUMMARY_ENABLED:false}inapplication.properties,#REVIEW_FOLLOW_UP_SUMMARY_ENABLED=falsein.env.example, andfalsein the README config table. All four agree, andFollowUpSummaryDefaultOffTestasserts through the resolved configuration rather than the annotation.followUpDeltaIsPostedWhenEnabledAndTheDeltaIsNonEmptyat the publisher,shouldPostDeltaCommentOnFollowUpReviewWhenFollowUpSummaryEnabledend-to-end.isBotSummaryComment, which is what would otherwise corrupt first-review detection on the next round.Scope
REVIEW_FOLLOW_UP_SUMMARY_ENABLEDis documented in the README config table and.env.example.website/src/content/docs/configuration.mdmirrors that README section through its include marker, so no separate docs edit was needed, and the site build was run to confirm. NoCHANGELOG.mdentry, matching the other v0.6.0 wave PRs (#450, #451, #453).Worth knowing
justified) posts nothing, by the arithmetic above. The three counts therefore do not necessarily sum to the previous-finding total — each label states exactly what it counts, so nothing is misreported, but a reviewer should be aware of it.publishSummaryBestEffortswallows it and reportsfalse, so a delta comment can post on that round instead. That is deliberate: enrichment degrading to a shorter form beats degrading to nothing.