From a4a1bce449b5c2f887e8f6b083cfc03361cd79fa Mon Sep 17 00:00:00 2001 From: xmtp-coder-agent <> Date: Mon, 30 Mar 2026 15:09:35 +0000 Subject: [PATCH] feat: include file path and line number in PR review comment messages When a PR review comment is forwarded to a Coder task, the message now includes the file path and line number where the comment was placed, giving the agent the context it needs to locate the code in question. Resolves #81 Co-Authored-By: Claude Opus 4.6 --- src/handler-dispatcher.ts | 2 ++ src/handlers/pr-comment.test.ts | 29 ++++++++++++++++++++++++++++ src/handlers/pr-comment.ts | 4 ++++ src/messages.test.ts | 34 +++++++++++++++++++++++++++++++++ src/messages.ts | 8 +++++++- src/webhook-router.test.ts | 2 ++ src/webhook-router.ts | 4 ++++ 7 files changed, 82 insertions(+), 1 deletion(-) diff --git a/src/handler-dispatcher.ts b/src/handler-dispatcher.ts index 95f37e5..6ffd23b 100644 --- a/src/handler-dispatcher.ts +++ b/src/handler-dispatcher.ts @@ -114,6 +114,8 @@ export class HandlerDispatcher { commentCreatedAt: ctx.commentCreatedAt, isReviewComment: ctx.isReviewComment, isReviewSubmission: ctx.isReviewSubmission, + filePath: ctx.filePath, + lineNumber: ctx.lineNumber, }, logger, ); diff --git a/src/handlers/pr-comment.test.ts b/src/handlers/pr-comment.test.ts index 4ee9334..4fb9393 100644 --- a/src/handlers/pr-comment.test.ts +++ b/src/handlers/pr-comment.test.ts @@ -329,6 +329,35 @@ describe("PRCommentHandler", () => { expect(coder.sendTaskInput).toHaveBeenCalledTimes(1); }); + test("includes file path and line number in forwarded message", async () => { + const ctx: PRCommentContext = { + ...validContext, + commentUrl: + "https://github.com/xmtp/libxmtp/pull/5/changes#r2962833476", + commentBody: "This variable name is unclear", + isReviewComment: true, + filePath: "src/handlers/pr-comment.ts", + lineNumber: 42, + }; + const handler = new PRCommentHandler( + coder, + github as unknown as import("../github-client").GitHubClient, + baseInputs, + ctx, + logger, + ); + await handler.run(); + + const sentMessage = ( + coder.sendTaskInput.mock.calls[0] as unknown as [ + string, + unknown, + string, + ] + )[2]; + expect(sentMessage).toContain("File: src/handlers/pr-comment.ts:42"); + }); + test("adds 👀 reaction via review comment endpoint", async () => { const handler = new PRCommentHandler( coder, diff --git a/src/handlers/pr-comment.ts b/src/handlers/pr-comment.ts index c24cae3..24632cc 100644 --- a/src/handlers/pr-comment.ts +++ b/src/handlers/pr-comment.ts @@ -21,6 +21,8 @@ export interface PRCommentContext { commentCreatedAt: string; isReviewComment?: boolean; isReviewSubmission?: boolean; + filePath?: string; + lineNumber?: number; } export class PRCommentHandler { @@ -93,6 +95,8 @@ export class PRCommentHandler { commenter: this.context.commenterLogin, timestamp: this.context.commentCreatedAt, body: this.context.commentBody, + filePath: this.context.filePath, + lineNumber: this.context.lineNumber, }); await sendInputWithRetry(this.coder, task, message, this.logger); this.logger.info(`Comment forwarded to task ${taskName}`); diff --git a/src/messages.test.ts b/src/messages.test.ts index 80d23de..8bd5066 100644 --- a/src/messages.test.ts +++ b/src/messages.test.ts @@ -48,6 +48,40 @@ describe("formatPRCommentMessage", () => { expect(msg).toContain("👍"); expect(msg).toContain("automated"); }); + + test("includes file path and line number for review comments", () => { + const msg = formatPRCommentMessage({ + commentUrl: "https://github.com/org/repo/pull/1#comment-1", + commenter: "reviewer", + timestamp: "2026-03-17T12:00:00Z", + body: "This variable name is unclear", + filePath: "src/handlers/pr-comment.ts", + lineNumber: 42, + }); + expect(msg).toContain("File: src/handlers/pr-comment.ts:42"); + }); + + test("includes file path without line number when line is undefined", () => { + const msg = formatPRCommentMessage({ + commentUrl: "https://github.com/org/repo/pull/1#comment-1", + commenter: "reviewer", + timestamp: "2026-03-17T12:00:00Z", + body: "This variable name is unclear", + filePath: "src/handlers/pr-comment.ts", + }); + expect(msg).toContain("File: src/handlers/pr-comment.ts"); + expect(msg).not.toContain("File: src/handlers/pr-comment.ts:"); + }); + + test("omits file line when filePath is not provided", () => { + const msg = formatPRCommentMessage({ + commentUrl: "https://github.com/org/repo/pull/1#comment-1", + commenter: "reviewer", + timestamp: "2026-03-17T12:00:00Z", + body: "General comment", + }); + expect(msg).not.toContain("File:"); + }); }); describe("formatIssueCommentMessage", () => { diff --git a/src/messages.ts b/src/messages.ts index 368414b..5d06239 100644 --- a/src/messages.ts +++ b/src/messages.ts @@ -5,6 +5,8 @@ interface CommentMessageParams { commenter: string; timestamp: string; body: string; + filePath?: string; + lineNumber?: number; } interface FailedCheckParams { @@ -16,9 +18,13 @@ interface FailedCheckParams { } export function formatPRCommentMessage(params: CommentMessageParams): string { + const locationLine = + params.filePath != null + ? `\nFile: ${params.filePath}${params.lineNumber != null ? `:${params.lineNumber}` : ""}` + : ""; return `New Comment on PR: ${params.commentUrl} Commenter: ${params.commenter} -Timestamp: ${params.timestamp} +Timestamp: ${params.timestamp}${locationLine} [INSTRUCTIONS] First, determine whether this comment requires action. diff --git a/src/webhook-router.test.ts b/src/webhook-router.test.ts index 980e8be..26f24ce 100644 --- a/src/webhook-router.test.ts +++ b/src/webhook-router.test.ts @@ -283,6 +283,8 @@ describe("WebhookRouter", () => { expect(ctx.isReviewSubmission).toBe(false); expect(ctx.repoName).toBe("coder-action"); expect(ctx.repoOwner).toBe("xmtplabs"); + expect(ctx.filePath).toBe("dist/server.js"); + expect(ctx.lineNumber).toBe(1); }); test("pull_request_review_comment.edited, PR by agent, comment by human → dispatched as pr_comment", async () => { diff --git a/src/webhook-router.ts b/src/webhook-router.ts index 0508a3a..eb1d026 100644 --- a/src/webhook-router.ts +++ b/src/webhook-router.ts @@ -46,6 +46,8 @@ export type PRCommentContext = { commenterLogin: string; isReviewComment: boolean; isReviewSubmission: boolean; + filePath?: string; + lineNumber?: number; }; export type IssueCommentContext = { @@ -363,6 +365,8 @@ export class WebhookRouter { commenterLogin: commentUserLogin, isReviewComment: true, isReviewSubmission: false, + filePath: payload.comment.path, + lineNumber: payload.comment.line ?? undefined, }, }; }