import { writeFileSync } from "node:fs"; import { join } from "node:path"; import { type } from "arktype"; import type { PullReviewComment } from "@go-gitea/sdk.js"; import { stripExistingFooter } from "../utils/buildShockbotFooter.ts"; import { log } from "../utils/log.ts"; import type { ToolContext } from "./server.ts"; import { execute, tool } from "./shared.ts"; export const GetReviewComments = type({ pull_number: type.number.describe("The pull request number to get review comments for"), }); export function GetReviewCommentsTool(ctx: ToolContext) { return tool({ name: "get_review_comments", description: "Get all inline review comments for a pull request. " + "Example: `get_review_comments({ pull_number: 1234 })`.", parameters: GetReviewComments, execute: execute(async ({ pull_number }) => { ctx.toolState.issueNumber = pull_number; // Get all reviews, then collect their comments const reviews = await ctx.gitea.paginate(ctx.gitea.rest.repository.repoListPullReviews, { owner: ctx.repo.owner, repo: ctx.repo.name, index: pull_number, }); const allComments: PullReviewComment[] = []; for (const review of reviews) { if (!review.id) continue; try { const commentsResult = await ctx.gitea.rest.repository.repoGetPullReviewComments({ owner: ctx.repo.owner, repo: ctx.repo.name, index: pull_number, id: review.id, }); allComments.push(...commentsResult.data); } catch { // best-effort } } const tempDir = process.env.SHOCKBOT_TEMP_DIR; let filePath: string | undefined; const rendered = allComments.map((c) => ({ id: c.id, path: c.path, // PullReviewComment uses `position` for the diff offset; Gitea doesn't // expose separate new_position/old_position on GET — use original_position as fallback line: c.original_position ?? c.position, side: "RIGHT" as const, body: stripExistingFooter(c.body ?? ""), author: c.user?.login, diffHunk: c.diff_hunk, reviewId: c.pull_request_review_id, })); if (tempDir && rendered.length > 0) { filePath = join(tempDir, `pr-${pull_number}-review-comments.json`); writeFileSync(filePath, JSON.stringify(rendered, null, 2)); log.debug(`wrote review comments to ${filePath}`); } return { pull_number, comments: rendered, count: rendered.length, ...(filePath ? { filePath } : {}), }; }), }); } export const ListPullRequestReviews = type({ pull_number: type.number.describe("The pull request number"), }); export function ListPullRequestReviewsTool(ctx: ToolContext) { return tool({ name: "list_pull_request_reviews", description: "List all reviews submitted on a pull request. " + "Example: `list_pull_request_reviews({ pull_number: 1234 })`.", parameters: ListPullRequestReviews, execute: execute(async ({ pull_number }) => { const reviews = await ctx.gitea.paginate(ctx.gitea.rest.repository.repoListPullReviews, { owner: ctx.repo.owner, repo: ctx.repo.name, index: pull_number, }); return { pull_number, reviews: reviews.map((r) => ({ id: r.id, user: r.user?.login, state: r.state, body: r.body, submitted_at: r.submitted_at, commit_id: r.commit_id, })), count: reviews.length, }; }), }); }