From 22704dda35b911194d35fb6d2c5a52fe193690e6 Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Wed, 21 Jan 2026 03:12:44 +0000 Subject: [PATCH] Improve prInfo. Fix prompt duplication --- entry | 41 +++++++++++++++++++++++++++++++++-------- external.ts | 12 ++++++------ mcp/prInfo.ts | 51 ++++++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 81 insertions(+), 23 deletions(-) diff --git a/entry b/entry index 86152b6..5168328 100755 --- a/entry +++ b/entry @@ -121084,32 +121084,57 @@ function CreatePullRequestTool(ctx) { } // mcp/prInfo.ts +var CLOSING_ISSUES_QUERY = ` +query($owner: String!, $repo: String!, $number: Int!) { + repository(owner: $owner, name: $repo) { + pullRequest(number: $number) { + closingIssuesReferences(first: 10) { + nodes { number title } + } + } + } +} +`; var PullRequestInfo = type({ pull_number: type.number.describe("The pull request number to fetch") }); function PullRequestInfoTool(ctx) { return tool({ name: "get_pull_request", - description: "Retrieve PR metadata (number, title, state, base/head branches, fork status). To checkout a PR branch locally, use checkout_pr instead.", + description: "Retrieve PR metadata (title, body, state, branches, author, labels, linked issues). To checkout a PR branch locally, use checkout_pr instead.", parameters: PullRequestInfo, execute: execute(async ({ pull_number }) => { - const pr = await ctx.octokit.rest.pulls.get({ - owner: ctx.repo.owner, - repo: ctx.repo.name, - pull_number - }); - const data = pr.data; + const [restResponse, graphqlResponse] = await Promise.all([ + ctx.octokit.rest.pulls.get({ + owner: ctx.repo.owner, + repo: ctx.repo.name, + pull_number + }), + ctx.octokit.graphql(CLOSING_ISSUES_QUERY, { + owner: ctx.repo.owner, + repo: ctx.repo.name, + number: pull_number + }) + ]); + const data = restResponse.data; const isFork = data.head.repo?.full_name !== data.base.repo.full_name; + const closingIssues = graphqlResponse.repository.pullRequest.closingIssuesReferences.nodes; return { number: data.number, url: data.html_url, title: data.title, + body: data.body, state: data.state, draft: data.draft, merged: data.merged, + maintainerCanModify: data.maintainer_can_modify, base: data.base.ref, head: data.head.ref, - isFork + isFork, + author: data.user?.login, + assignees: data.assignees?.map((a) => a.login), + labels: data.labels.map((l) => l.name), + closingIssues: closingIssues.map((i) => ({ number: i.number, title: i.title })) }; }) }); diff --git a/external.ts b/external.ts index 90bad42..bb723b9 100644 --- a/external.ts +++ b/external.ts @@ -138,8 +138,8 @@ interface PullRequestReviewCommentCreatedEvent extends BasePayloadEvent { is_pr: true; title: string; comment_id: number; - /** comment body is the primary content */ - body: string; + /** comment body is the primary content (null if already in prompt) */ + body: string | null; thread?: any; branch: string; } @@ -168,8 +168,8 @@ interface IssuesLabeledEvent extends BasePayloadEvent { interface IssueCommentCreatedEvent extends BasePayloadEvent { trigger: "issue_comment_created"; comment_id: number; - /** comment body is the primary content */ - body: string; + /** comment body is the primary content (null if already in prompt) */ + body: string | null; issue_number: number; // PR-specific fields (only present when is_pr is true) is_pr?: true; @@ -212,8 +212,8 @@ interface ImplementPlanEvent extends BasePayloadEvent { trigger: "implement_plan"; issue_number: number; plan_comment_id: number; - /** plan content is the primary content */ - body: string; + /** plan content is the primary content (null if already in prompt) */ + body: string | null; } interface UnknownEvent extends BasePayloadEvent { diff --git a/mcp/prInfo.ts b/mcp/prInfo.ts index d496cc4..7206a6a 100644 --- a/mcp/prInfo.ts +++ b/mcp/prInfo.ts @@ -2,6 +2,26 @@ import { type } from "arktype"; import type { ToolContext } from "./server.ts"; import { execute, tool } from "./shared.ts"; +const CLOSING_ISSUES_QUERY = ` +query($owner: String!, $repo: String!, $number: Int!) { + repository(owner: $owner, name: $repo) { + pullRequest(number: $number) { + closingIssuesReferences(first: 10) { + nodes { number title } + } + } + } +} +`; + +type ClosingIssuesResponse = { + repository: { + pullRequest: { + closingIssuesReferences: { nodes: Array<{ number: number; title: string }> }; + }; + }; +}; + export const PullRequestInfo = type({ pull_number: type.number.describe("The pull request number to fetch"), }); @@ -10,30 +30,43 @@ export function PullRequestInfoTool(ctx: ToolContext) { return tool({ name: "get_pull_request", description: - "Retrieve PR metadata (number, title, state, base/head branches, fork status). To checkout a PR branch locally, use checkout_pr instead.", + "Retrieve PR metadata (title, body, state, branches, author, labels, linked issues). To checkout a PR branch locally, use checkout_pr instead.", parameters: PullRequestInfo, execute: execute(async ({ pull_number }) => { - const pr = await ctx.octokit.rest.pulls.get({ - owner: ctx.repo.owner, - repo: ctx.repo.name, - pull_number, - }); + // fetch REST and GraphQL in parallel + const [restResponse, graphqlResponse] = await Promise.all([ + ctx.octokit.rest.pulls.get({ + owner: ctx.repo.owner, + repo: ctx.repo.name, + pull_number, + }), + ctx.octokit.graphql(CLOSING_ISSUES_QUERY, { + owner: ctx.repo.owner, + repo: ctx.repo.name, + number: pull_number, + }), + ]); - const data = pr.data; - - // detect fork PRs - head repo differs from base repo (head.repo can be null if fork was deleted) + const data = restResponse.data; const isFork = data.head.repo?.full_name !== data.base.repo.full_name; + const closingIssues = graphqlResponse.repository.pullRequest.closingIssuesReferences.nodes; return { number: data.number, url: data.html_url, title: data.title, + body: data.body, state: data.state, draft: data.draft, merged: data.merged, + maintainerCanModify: data.maintainer_can_modify, base: data.base.ref, head: data.head.ref, isFork, + author: data.user?.login, + assignees: data.assignees?.map((a) => a.login), + labels: data.labels.map((l) => l.name), + closingIssues: closingIssues.map((i) => ({ number: i.number, title: i.title })), }; }), });