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, }, }; }