From a442f766aadb4c7c160c850e0656253d6438e76c Mon Sep 17 00:00:00 2001 From: "pullfrog[bot]" <226033991+pullfrog[bot]@users.noreply.github.com> Date: Thu, 12 Feb 2026 15:25:21 +0000 Subject: [PATCH] feat: Resolve threads in AddressReviews mode (#266) * Add review thread resolution to AddressReviews mode - Add thread_id to comment metadata in buildThreadBlocks - Implement ResolveReviewThreadTool with GraphQL mutation - Register new tool in MCP server - Update AddressReviews mode to resolve threads after addressing feedback Closes #227 * Fix typo: use log.warning instead of log.warn * refactor: DRY up catch block by extracting isResolved condition * fix lint. * fix: avoid using any. * fix: combining log statements around the message. * fix(test): Adjusting snapshot. --------- Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com> Co-authored-by: Robin Tail --- entry | 51 +++++++++++++- mcp/__snapshots__/reviewComments.test.ts.snap | 2 +- mcp/reviewComments.ts | 69 ++++++++++++++++++- mcp/server.ts | 7 +- modes.ts | 2 +- 5 files changed, 125 insertions(+), 6 deletions(-) diff --git a/entry b/entry index e4c8c30..98e13cd 100755 --- a/entry +++ b/entry @@ -143354,7 +143354,7 @@ function buildThreadBlocks(threads, filePatchMap, reviewId) { const isTargetReview = comment.pullRequestReview?.databaseId === reviewId; const marker = isTargetReview ? " *" : ""; block.push( - `\`\`\`\`comment author=${author} id=${comment.fullDatabaseId ?? "unknown"} review=${comment.pullRequestReview?.databaseId ?? "unknown"}${marker}` + `\`\`\`\`comment author=${author} id=${comment.fullDatabaseId ?? "unknown"} review=${comment.pullRequestReview?.databaseId ?? "unknown"} thread=${thread.id}${marker}` ); block.push(comment.body || "(no comment body)"); block.push("````"); @@ -143496,6 +143496,52 @@ function ListPullRequestReviewsTool(ctx) { }) }); } +var RESOLVE_REVIEW_THREAD_MUTATION = ` +mutation($threadId: ID!) { + resolveReviewThread(input: {threadId: $threadId}) { + thread { + id + isResolved + } + } +} +`; +var ResolveReviewThread = type({ + thread_id: type.string.describe("The GraphQL node ID of the review thread to resolve") +}); +function ResolveReviewThreadTool(ctx) { + return tool({ + name: "resolve_review_thread", + description: "Mark a review thread as resolved using GitHub's GraphQL API. Only call this after addressing the review feedback, implementing fixes, testing them, and posting a reply. Do not resolve threads that are already resolved, threads where no action was taken, or threads where you disagree with the feedback.", + parameters: ResolveReviewThread, + execute: execute(async (params) => { + try { + const response = await ctx.octokit.graphql(RESOLVE_REVIEW_THREAD_MUTATION, { + threadId: params.thread_id + }); + const thread = response.resolveReviewThread.thread; + log.debug(`resolved thread ${thread.id}, isResolved=${thread.isResolved}`); + return { + thread_id: thread.id, + is_resolved: thread.isResolved, + success: true, + message: "Thread resolved successfully" + }; + } catch (error49) { + const errorMessage = error49 instanceof Error ? error49.message : String(error49); + const isResolved = errorMessage.includes("already resolved") || errorMessage.includes("isResolved"); + const message = isResolved ? `thread ${params.thread_id} was already resolved` : `failed to resolve thread ${params.thread_id}: ${errorMessage}`; + log.warning(message); + return { + thread_id: params.thread_id, + is_resolved: isResolved, + success: isResolved, + message + }; + } + }) + }); +} // mcp/selectMode.ts var SelectMode = type({ @@ -143644,6 +143690,7 @@ function buildTools(ctx) { CheckoutPrTool(ctx), GetReviewCommentsTool(ctx), ListPullRequestReviewsTool(ctx), + ResolveReviewThreadTool(ctx), GetCheckSuiteLogsTool(ctx), AddLabelsTool(ctx), PushBranchTool(ctx), @@ -143829,7 +143876,7 @@ function computeModes() { 6. Make the necessary code changes to address the feedback. Work through each review comment systematically. -7. **CRITICAL: Reply to EACH review comment individually.** After fixing each comment, use ${ghPullfrogMcpName}/reply_to_review_comment to reply directly to that comment thread. Keep replies extremely brief (1 sentence max, e.g., "Fixed by renaming to X" or "Added null check"). If suggesting a small, specific, self-contained code change, use GitHub's suggestion format with \`\`\`suggestion blocks. +7. **CRITICAL: Reply to EACH review comment individually.** After fixing each comment, use ${ghPullfrogMcpName}/reply_to_review_comment to reply directly to that comment thread. Keep replies extremely brief (1 sentence max, e.g., "Fixed by renaming to X" or "Added null check"). If suggesting a small, specific, self-contained code change, use GitHub's suggestion format with \`\`\`suggestion blocks. After addressing a comment and posting your reply, use ${ghPullfrogMcpName}/resolve_review_thread with the thread_id to mark it as resolved. Only resolve threads where you made code changes to address the feedback\u2014don't resolve threads that are already resolved, threads where no action was taken, or threads where you disagree with the feedback. 8. Test your changes to ensure they work correctly. diff --git a/mcp/__snapshots__/reviewComments.test.ts.snap b/mcp/__snapshots__/reviewComments.test.ts.snap index bb683f5..16dbe72 100644 --- a/mcp/__snapshots__/reviewComments.test.ts.snap +++ b/mcp/__snapshots__/reviewComments.test.ts.snap @@ -11,7 +11,7 @@ exports[`formatReviewThreads > formats thread blocks with TOC and correct line n ## .github/workflows/test.yml:7 [RESOLVED] -\`\`\`\`comment author=cursor id=2544544046 review=3485940013 * +\`\`\`\`comment author=cursor id=2544544046 review=3485940013 thread=PRRT_kwDOPaxxp85iysVl * ### Bug: GitHub Actions workflow triggered for wrong branch diff --git a/mcp/reviewComments.ts b/mcp/reviewComments.ts index 13288cb..b07f140 100644 --- a/mcp/reviewComments.ts +++ b/mcp/reviewComments.ts @@ -395,7 +395,7 @@ export function buildThreadBlocks( const marker = isTargetReview ? " *" : ""; block.push( - `\`\`\`\`comment author=${author} id=${comment.fullDatabaseId ?? "unknown"} review=${comment.pullRequestReview?.databaseId ?? "unknown"}${marker}` + `\`\`\`\`comment author=${author} id=${comment.fullDatabaseId ?? "unknown"} review=${comment.pullRequestReview?.databaseId ?? "unknown"} thread=${thread.id}${marker}` ); block.push(comment.body || "(no comment body)"); block.push("````"); @@ -576,3 +576,70 @@ export function ListPullRequestReviewsTool(ctx: ToolContext) { }), }); } + +const RESOLVE_REVIEW_THREAD_MUTATION = ` +mutation($threadId: ID!) { + resolveReviewThread(input: {threadId: $threadId}) { + thread { + id + isResolved + } + } +} +`; + +export const ResolveReviewThread = type({ + thread_id: type.string.describe("The GraphQL node ID of the review thread to resolve"), +}); + +export function ResolveReviewThreadTool(ctx: ToolContext) { + return tool({ + name: "resolve_review_thread", + description: + "Mark a review thread as resolved using GitHub's GraphQL API. " + + "Only call this after addressing the review feedback, implementing fixes, testing them, and posting a reply. " + + "Do not resolve threads that are already resolved, threads where no action was taken, or threads where you disagree with the feedback.", + parameters: ResolveReviewThread, + execute: execute(async (params) => { + try { + const response = await ctx.octokit.graphql<{ + resolveReviewThread: { + thread: { + id: string; + isResolved: boolean; + }; + }; + }>(RESOLVE_REVIEW_THREAD_MUTATION, { + threadId: params.thread_id, + }); + + const thread = response.resolveReviewThread.thread; + log.debug(`resolved thread ${thread.id}, isResolved=${thread.isResolved}`); + + return { + thread_id: thread.id, + is_resolved: thread.isResolved, + success: true, + message: "Thread resolved successfully", + }; + } catch (error) { + // handle common error cases gracefully + const errorMessage = error instanceof Error ? error.message : String(error); + const isResolved = + errorMessage.includes("already resolved") || errorMessage.includes("isResolved"); + + const message = isResolved + ? `thread ${params.thread_id} was already resolved` + : `failed to resolve thread ${params.thread_id}: ${errorMessage}`; + log.warning(message); + + return { + thread_id: params.thread_id, + is_resolved: isResolved, + success: isResolved, + message, + }; + } + }), + }); +} diff --git a/mcp/server.ts b/mcp/server.ts index 56873ad..b64d2e1 100644 --- a/mcp/server.ts +++ b/mcp/server.ts @@ -106,7 +106,11 @@ import { SetOutputTool } from "./output.ts"; import { CreatePullRequestTool } from "./pr.ts"; import { PullRequestInfoTool } from "./prInfo.ts"; import { CreatePullRequestReviewTool } from "./review.ts"; -import { GetReviewCommentsTool, ListPullRequestReviewsTool } from "./reviewComments.ts"; +import { + GetReviewCommentsTool, + ListPullRequestReviewsTool, + ResolveReviewThreadTool, +} from "./reviewComments.ts"; import { SelectModeTool } from "./selectMode.ts"; import { addTools } from "./shared.ts"; import { UploadFileTool } from "./upload.ts"; @@ -166,6 +170,7 @@ function buildTools(ctx: ToolContext): Tool[] { CheckoutPrTool(ctx), GetReviewCommentsTool(ctx), ListPullRequestReviewsTool(ctx), + ResolveReviewThreadTool(ctx), GetCheckSuiteLogsTool(ctx), AddLabelsTool(ctx), PushBranchTool(ctx), diff --git a/modes.ts b/modes.ts index 11ee55c..0608923 100644 --- a/modes.ts +++ b/modes.ts @@ -85,7 +85,7 @@ export function computeModes(): Mode[] { 6. Make the necessary code changes to address the feedback. Work through each review comment systematically. -7. **CRITICAL: Reply to EACH review comment individually.** After fixing each comment, use ${ghPullfrogMcpName}/reply_to_review_comment to reply directly to that comment thread. Keep replies extremely brief (1 sentence max, e.g., "Fixed by renaming to X" or "Added null check"). If suggesting a small, specific, self-contained code change, use GitHub's suggestion format with \`\`\`suggestion blocks. +7. **CRITICAL: Reply to EACH review comment individually.** After fixing each comment, use ${ghPullfrogMcpName}/reply_to_review_comment to reply directly to that comment thread. Keep replies extremely brief (1 sentence max, e.g., "Fixed by renaming to X" or "Added null check"). If suggesting a small, specific, self-contained code change, use GitHub's suggestion format with \`\`\`suggestion blocks. After addressing a comment and posting your reply, use ${ghPullfrogMcpName}/resolve_review_thread with the thread_id to mark it as resolved. Only resolve threads where you made code changes to address the feedback—don't resolve threads that are already resolved, threads where no action was taken, or threads where you disagree with the feedback. 8. Test your changes to ensure they work correctly.