import type { RestEndpointMethodTypes } from "@octokit/rest"; import { type } from "arktype"; import { contextualize, tool } from "./shared.ts"; export const Review = type({ pull_number: type.number.describe("The pull request number to review"), body: type.string .describe( "Brief summary or general feedback that doesn't apply to specific code locations. Keep it concise - most feedback should be in the 'comments' array." ) .optional(), commit_id: type.string .describe("Optional SHA of the commit being reviewed. Defaults to latest.") .optional(), comments: type({ path: type.string.describe("The file path to comment on (relative to repo root)"), line: type.number.describe( "The line number in the file (use line numbers from the diff - usually the RIGHT side/new code)" ), side: type .enumerated("LEFT", "RIGHT") .describe( "Side of the diff: LEFT (old code) or RIGHT (new code). Defaults to RIGHT if not provided." ) .optional(), body: type.string.describe("The comment text for this specific line"), start_line: type.number .describe("Start line for multi-line comments (optional, for commenting on ranges)") .optional(), }) .array() .describe( "REQUIRED: Array of inline comments for specific code issues. Use this for all location-specific feedback. Use 'git diff origin/...origin/' to find the correct line numbers (typically use the line numbers shown on the RIGHT side for new code, LEFT side for old code)." ) .optional(), }); export const ReviewTool = tool({ name: "submit_pull_request_review", description: "Submit a review (approve, request changes, or comment) for an existing pull request. " + "IMPORTANT: Use 'comments' array for ALL specific code issues at the line-level. " + "Only use 'body' for a brief summary or feedback that doesn't apply to a specific location.", parameters: Review, execute: contextualize(async ({ pull_number, body, commit_id, comments = [] }, ctx) => { // Get the PR to determine the head commit if commit_id not provided const pr = await ctx.octokit.rest.pulls.get({ owner: ctx.owner, repo: ctx.name, pull_number, }); // Compose the request const params: RestEndpointMethodTypes["pulls"]["createReview"]["parameters"] = { owner: ctx.owner, repo: ctx.name, pull_number, event: "COMMENT", }; if (body) params.body = body; if (commit_id) { params.commit_id = commit_id; } else { params.commit_id = pr.data.head.sha; } if (comments.length > 0) { type ReviewComment = (typeof params.comments & {})[number]; // Convert comments to the format expected by GitHub API params.comments = comments.map((comment) => { const reviewComment: ReviewComment = { ...comment, }; reviewComment.side = comment.side || "RIGHT"; if (comment.start_line) { reviewComment.start_line = comment.start_line; reviewComment.start_side = comment.side || "RIGHT"; } return reviewComment; }); } const result = await ctx.octokit.rest.pulls.createReview(params); return { success: true, reviewId: result.data.id, html_url: result.data.html_url, state: result.data.state, user: result.data.user?.login, submitted_at: result.data.submitted_at, }; }), });