From b69ae319ebe6a0ce947795d25752b316e6d63bb1 Mon Sep 17 00:00:00 2001 From: Patrick Lu Date: Wed, 17 Dec 2025 18:08:00 +0900 Subject: [PATCH] existing comments --- src/agent/agent-system-prompt.ts | 43 +++++++++++++++++++++++++++++ src/agent/index.ts | 47 ++++++++++++++++++++++++++++++-- src/index.ts | 43 +++++++++++++++++++++++++++++ src/review-service.ts | 17 +++++++++++- src/types.ts | 21 ++++++++++++++ test/review-service.test.ts | 2 ++ 6 files changed, 170 insertions(+), 3 deletions(-) diff --git a/src/agent/agent-system-prompt.ts b/src/agent/agent-system-prompt.ts index dbddabc..74b6b62 100644 --- a/src/agent/agent-system-prompt.ts +++ b/src/agent/agent-system-prompt.ts @@ -204,6 +204,49 @@ export function getInteractiveSystemPrompt( If you cannot verify a claim, do not make it. + + + When other reviewers have already commented on the PR (shown in ): + + 1. **DO NOT DUPLICATE**: Never repeat feedback that another reviewer has already given. + If someone already pointed out an issue, do not post the same comment. + + 2. **USE EMOJI REACTIONS**: When you agree with an existing comment but have nothing + substantial to add, use an emoji reaction instead of posting a new comment. + This is a lightweight way to show your agreement without adding noise. + + The comment ID is provided in each element's \`\` field. + Use it to add a reaction: + \`gh api repos/OWNER/REPO/pulls/comments/COMMENT_ID/reactions -f content="+1"\` + + Available reactions: + • \`+1\` (👍) - Agree with the comment + • \`-1\` (👎) - Disagree (prefer posting a counter-opinion with explanation) + • \`heart\` (❤️) - Great catch/suggestion + • \`rocket\` (🚀) - Excellent improvement + • \`eyes\` (👀) - Interesting point, needs attention + • \`confused\` (😕) - Unclear or questionable suggestion + + 3. **REINFORCE when valuable**: If you strongly agree with an existing comment and have + additional context or a stronger argument, you MAY add a supporting comment. + Example: "I agree with @reviewer's point about X. Additionally, this could cause Y..." + + 4. **RESPECTFULLY DISAGREE**: If you believe an existing comment is incorrect or + the suggested change would be harmful, you may respectfully provide a counter-opinion. + Be constructive and explain your reasoning with evidence. + Example: "I have a different perspective on @reviewer's suggestion about X. + The current approach is actually preferred because..." + + 5. **FACTOR INTO ASSESSMENT**: Consider existing comments when deciding your overall + review verdict. If issues have already been raised that warrant changes, acknowledge + them in your review summary even if you don't add new comments about them. + + 6. **STILL DO A FULL REVIEW**: You should review ALL the code in the diff and form your + own judgement about everything. Existing comments don't mean you skip those areas. + Just avoid posting duplicate feedback for issues already raised - use emoji reactions + or add to the discussion with new insights instead. + + Don't just read the diff - actively investigate using your tools: diff --git a/src/agent/index.ts b/src/agent/index.ts index bd742da..f50bac3 100644 --- a/src/agent/index.ts +++ b/src/agent/index.ts @@ -2,7 +2,7 @@ import { Agent, Runner } from "@openai/agents"; import { aisdk } from "@openai/agents-extensions"; import { debugError, debugLog } from "../debug"; import { createModel } from "../model-factory"; -import { ModelConfig } from "../types"; +import { ExistingReviewComment, ModelConfig } from "../types"; import { getInteractiveSystemPrompt } from "./agent-system-prompt"; import { allTools, resetTodoList } from "./tools"; @@ -38,6 +38,41 @@ export interface PRContext { commitSha: string; } +/** + * Formats existing review comments into a readable string for the agent. + */ +function formatExistingComments(comments: ExistingReviewComment[]): string { + if (comments.length === 0) { + return ""; + } + + const formattedComments = comments.map((comment, index) => { + const lineInfo = comment.line ? `Line ${comment.line}` : "File-level"; + return ` + ${comment.author} + ${comment.path} + ${lineInfo} + ${comment.id} + ${comment.body} + +${comment.diffHunk} + +`; + }); + + return ` + + These are review comments made by other reviewers on this PR. + Consider them in your review: + - DO NOT duplicate feedback that has already been given + - If you agree with a comment, you may reinforce it or add additional context + - If you disagree with a comment, you may respectfully provide a counter-opinion + - Factor these comments into your overall assessment of the PR + +${formattedComments.join("\n\n")} +`; +} + /** * Reviews an entire PR diff using a single interactive agent. * The agent has full autonomy to: @@ -53,6 +88,7 @@ export async function reviewFullDiff( prContext: PRContext, maxTurns: number = 75, blockingOnly: boolean = false, + existingComments: ExistingReviewComment[] = [], ): Promise { // Reset todo list for fresh review resetTodoList(); @@ -76,6 +112,12 @@ export async function reviewFullDiff( ); } + // Format existing comments for inclusion + const existingCommentsSection = formatExistingComments(existingComments); + if (existingComments.length > 0) { + debugLog(`📝 Including ${existingComments.length} existing review comments in context`); + } + const initialMessage = ` You are reviewing PR #${prContext.prNumber} in repository ${prContext.repo}. Commit SHA: ${prContext.commitSha} @@ -87,6 +129,7 @@ ${fileList} ${fullDiff} +${existingCommentsSection} Please review this pull request. You have the complete diff above. @@ -98,7 +141,7 @@ Please review this pull request. You have the complete diff above. - Run \`gh pr view ${prContext.prNumber} --json body -q '.body'\` to check if description is blank - **If body is empty/blank, you MUST update it immediately:** \`gh pr edit ${prContext.prNumber} --body "## Summary\\n\\n\\n\\n## Changes\\n\\n- "\` - - Run \`gh api repos/${prContext.repo}/pulls/${prContext.prNumber}/comments\` to check existing comments (avoid duplicates) + - Review the section above (if present) to understand what other reviewers have already commented on 2. **Deep review each changed file:** For each file in the diff: diff --git a/src/index.ts b/src/index.ts index 43a69ca..df7faf0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ import * as core from "@actions/core"; import * as github from "@actions/github"; import { writeFileSync } from "node:fs"; import { resolve } from "node:path"; +import { ExistingReviewComment } from "./types"; interface TriggerConfig { runOnPrOpened: boolean; @@ -327,6 +328,48 @@ async function run(): Promise { core.info( `Generated diff file: ${diffFile} (${diffOutput.length} bytes)`, ); + + // Fetch existing review comments from other reviewers + const existingCommentsFile = resolve("pr-comments.json"); + try { + const { data: reviewComments } = + await octokit.rest.pulls.listReviewComments({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber, + }); + + // Filter out bot comments and map to our interface + const existingComments: ExistingReviewComment[] = reviewComments + .filter( + (comment) => + comment.user && + !comment.user.login.includes("[bot]") && + comment.user.login !== "github-actions", + ) + .map((comment) => ({ + id: comment.id, + author: comment.user?.login || "unknown", + body: comment.body, + path: comment.path, + line: comment.line ?? comment.original_line ?? null, + diffHunk: comment.diff_hunk, + createdAt: comment.created_at, + })); + + writeFileSync( + existingCommentsFile, + JSON.stringify(existingComments, null, 2), + ); + core.info( + `Found ${existingComments.length} existing review comments from other reviewers`, + ); + } catch (commentError) { + core.warning( + `Failed to fetch existing comments (continuing without them): ${commentError}`, + ); + writeFileSync(existingCommentsFile, "[]"); + } } catch (error) { core.setFailed(`Failed to fetch diff from GitHub API: ${error}`); return; diff --git a/src/review-service.ts b/src/review-service.ts index 3f0a741..df5e2ab 100644 --- a/src/review-service.ts +++ b/src/review-service.ts @@ -5,7 +5,7 @@ import { resolve } from "path"; import { PRContext, reviewFullDiff } from "./agent"; import { getModelConfig } from "./config"; import { debugLog } from "./debug"; -import type { ReviewConfig } from "./types"; +import type { ExistingReviewComment, ReviewConfig } from "./types"; /** * Service class that orchestrates the review process. @@ -81,6 +81,20 @@ export class ReviewService { console.error("COMMIT_SHA not set - agent will not be able to post inline comments"); } + // Load existing review comments from other reviewers + const commentsFile = resolve("pr-comments.json"); + let existingComments: ExistingReviewComment[] = []; + if (existsSync(commentsFile)) { + try { + existingComments = JSON.parse(readFileSync(commentsFile, "utf8")); + if (existingComments.length > 0) { + debugLog(`📝 Found ${existingComments.length} existing review comments from other reviewers`); + } + } catch (error) { + debugLog("⚠️ Failed to parse existing comments file, continuing without them"); + } + } + // Run the autonomous agent review debugLog("🚀 Starting autonomous PR review..."); const modelConfig = getModelConfig(); @@ -93,6 +107,7 @@ export class ReviewService { prContext, this.config.maxTurns, this.config.blockingOnly, + existingComments, ); debugLog("✅ Review completed!"); } catch (error: unknown) { diff --git a/src/types.ts b/src/types.ts index 3e08c53..12dabcd 100644 --- a/src/types.ts +++ b/src/types.ts @@ -29,3 +29,24 @@ export interface ParsedArgs { diff: string; pr: number; } + +/** + * Represents an existing review comment on the PR. + * These are comments made by other reviewers that the bot should be aware of. + */ +export interface ExistingReviewComment { + /** GitHub comment ID (for adding reactions) */ + id: number; + /** GitHub username of the commenter */ + author: string; + /** The comment body/text */ + body: string; + /** File path the comment is on */ + path: string; + /** Line number in the new version of the file (may be null for file-level comments) */ + line: number | null; + /** The diff hunk context around the comment */ + diffHunk: string; + /** ISO timestamp when the comment was created */ + createdAt: string; +} diff --git a/test/review-service.test.ts b/test/review-service.test.ts index f7a1333..cbbae1b 100644 --- a/test/review-service.test.ts +++ b/test/review-service.test.ts @@ -89,6 +89,7 @@ diff --git a/src/test.ts b/src/test.ts }), // prContext 20, // maxTurns false, // blockingOnly + [], // existingComments (empty when no pr-comments.json) ); }); @@ -107,6 +108,7 @@ diff --git a/src/test.ts b/src/test.ts expect.any(Object), 20, true, // blockingOnly should be true + [], // existingComments ); });