Repository navigation
feat: feed prior bot comments to model for conceptual dedup #27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
88c9273
4fd9170
17c997f
534aa70
f09d764
0dc4d67
a23ed30
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -48,6 +48,7 @@ export type ReviewContext = { | |
| relatedDocs: PromptFile[] | ||
| annotatedDiff: string | ||
| priorFindings: Finding[] | ||
| priorBotComments: string[] | ||
| } | ||
|
|
||
| export type GenerateFindings = ( | ||
|
|
@@ -72,6 +73,14 @@ export type OrchestrateDeps = { | |
| generateFindings: GenerateFindings | ||
| } | ||
|
|
||
| /** Bounds token cost of prior bot comments in the prompt (~200 tokens each). */ | ||
| const PRIOR_COMMENT_CAP = 30 | ||
|
|
||
| /** Strips the trailing dedup anchor from a comment body so the model | ||
| * doesn't see the dedup infrastructure in the prior-comments section. */ | ||
| const stripAnchorComment = (body: string): string => | ||
| body.replace(/\n*<!-- umm-actually:.+? -->\s*$/, "") | ||
|
|
||
| const buildSkipBody = (reason: string): string => | ||
| `**umm-actually** — review skipped\n\n${reason}\n\n---\n*umm-actually*` | ||
|
|
||
|
|
@@ -81,29 +90,38 @@ const describeError = (error: unknown): string => { | |
| : String(error) | ||
| } | ||
|
|
||
| /** Anchors from the bot's inline review comments (live positions). Empty on | ||
| type InlineCommentState = { | ||
| anchors: AnchorEntry[] | ||
| commentBodies: string[] | ||
| } | ||
|
|
||
| /** Anchors and raw bodies from the bot's inline review comments. Empty on | ||
| * fetch failure — findings then post as new; duplicates beat losing them. */ | ||
| const fetchInlineAnchors = async ( | ||
| const fetchInlineCommentState = async ( | ||
| { githubClient, prNumber }: { githubClient: GithubClient; prNumber: number }, | ||
| logger: Logger, | ||
| ): Promise<AnchorEntry[]> => { | ||
| ): Promise<InlineCommentState> => { | ||
| try { | ||
| const existingComments = await githubClient.fetchBotReviewComments({ | ||
| prNumber, | ||
| }) | ||
| return extractAnchors(existingComments) | ||
| return { | ||
| anchors: extractAnchors(existingComments), | ||
| commentBodies: existingComments.map((comment) => comment.body), | ||
| } | ||
| } catch (fetchError) { | ||
| logger.warn( | ||
| "failed to fetch inline comments — treating their findings as new", | ||
| { error: describeError(fetchError) }, | ||
| ) | ||
| return [] | ||
| return { anchors: [], commentBodies: [] } | ||
| } | ||
| } | ||
|
|
||
| type IssueCommentState = { | ||
| statusCommentExists: boolean | ||
| anchors: AnchorEntry[] | ||
| findingBodies: string[] | ||
| } | ||
|
|
||
| /** The bot's issue comments carry the rest of the cross-run state: the | ||
|
|
@@ -116,25 +134,27 @@ const fetchIssueCommentState = async ( | |
| ): Promise<IssueCommentState> => { | ||
| try { | ||
| const comments = await githubClient.fetchBotIssueComments({ prNumber }) | ||
| const findingComments = comments.filter( | ||
| (comment) => !comment.body.startsWith(STATUS_ANCHOR), | ||
| ) | ||
| return { | ||
| // startsWith, not includes: a finding comment's model-generated text | ||
| // could quote the marker mid-body and misclassify the run as a re-run. | ||
| statusCommentExists: comments.some((comment) => | ||
| comment.body.startsWith(STATUS_ANCHOR), | ||
| ), | ||
| statusCommentExists: comments.length > findingComments.length, | ||
| anchors: extractAnchors( | ||
| comments.map((comment) => ({ | ||
| findingComments.map((comment) => ({ | ||
| body: comment.body, | ||
| line: null, | ||
| originalLine: null, | ||
| })), | ||
| ), | ||
| findingBodies: findingComments.map((comment) => comment.body), | ||
|
aliasunder marked this conversation as resolved.
Comment on lines
144
to
+151
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [low/correctness] Collect findingBodies only from anchored comments in fetchIssueCommentState (confidence: low) findingBodies is derived from findingComments, which includes any non-status comment regardless of whether it contains a valid anchor. A bot comment that is not a status comment and lacks an anchor would still appear in priorBotComments, polluting the prompt with non-finding text. Failure scenario: A bot posts a standalone PR comment that does not start with STATUS_ANCHOR and lacks a hidden anchor (e.g., a follow-up note or a future non-finding comment). The comment body is included in priorBotComments, wasting context and potentially confusing the model's conceptual dedup. Suggested fixDerive findingBodies only from comments that extractAnchors successfully parses, or filter comments to those containing the anchor pattern before mapping. |
||
| } | ||
| } catch (fetchError) { | ||
| logger.warn("failed to fetch issue comments — treating as a first run", { | ||
| error: describeError(fetchError), | ||
| }) | ||
| return { statusCommentExists: false, anchors: [] } | ||
| return { statusCommentExists: false, anchors: [], findingBodies: [] } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -396,6 +416,24 @@ export const orchestrate = async ( | |
| ) | ||
| } | ||
|
|
||
| // Step 9.5: fetch prior bot comments — needed both for the prompt (the | ||
| // model sees what's already posted and self-suppresses conceptual dupes) | ||
| // and for positional dedup after generation. | ||
| const inlineState = await fetchInlineCommentState( | ||
| { githubClient, prNumber: prContext.prNumber }, | ||
| logger, | ||
| ) | ||
| const issueState = await fetchIssueCommentState( | ||
| { githubClient, prNumber: prContext.prNumber }, | ||
| logger, | ||
| ) | ||
| const priorBotComments = [ | ||
| ...inlineState.commentBodies, | ||
| ...issueState.findingBodies, | ||
| ] | ||
| .map(stripAnchorComment) | ||
| .slice(-PRIOR_COMMENT_CAP) | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| // Step 10–11: generate findings (V1: single combined phase) | ||
| const phase = phases[0] | ||
| if (!phase) { | ||
|
|
@@ -410,6 +448,7 @@ export const orchestrate = async ( | |
| relatedDocs, | ||
| annotatedDiff, | ||
| priorFindings: [], | ||
| priorBotComments, | ||
| }) | ||
|
|
||
| const { modelUsed, attempts } = structuredResult | ||
|
|
@@ -428,22 +467,15 @@ export const orchestrate = async ( | |
| // is just the case where no bot comments exist yet. Sources: the bot's | ||
| // inline comments (live positions) and its beyond-diff issue comments | ||
| // (anchor lines). Runs before the cap so duplicates don't consume slots. | ||
| const inlineAnchors = await fetchInlineAnchors( | ||
| { githubClient, prNumber: prContext.prNumber }, | ||
| logger, | ||
| ) | ||
| const issueState = await fetchIssueCommentState( | ||
| { githubClient, prNumber: prContext.prNumber }, | ||
| logger, | ||
| ) | ||
| const existingAnchors = [...inlineAnchors, ...issueState.anchors] | ||
| const existingAnchors = [...inlineState.anchors, ...issueState.anchors] | ||
| const newFindings = realFindings.filter( | ||
| (finding) => !isDuplicateFinding(finding, existingAnchors), | ||
| ) | ||
|
|
||
| logger.info("cross-run dedup against prior bot comments", { | ||
| statusCommentFound: issueState.statusCommentExists, | ||
| existingAnchorCount: existingAnchors.length, | ||
| priorBotCommentCount: priorBotComments.length, | ||
| findingsAfterFilter: realFindings.length, | ||
| findingsSurvivedDedup: newFindings.length, | ||
| }) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.