import { Gitea } from "@go-gitea/sdk.js"; import type { Comment, ChangedFile, PullReview, Reaction, } from "@go-gitea/sdk.js"; import type { PRContext } from "./types.ts"; const client = new Gitea({ baseUrl: process.env.GITEA_URL, auth: process.env.BOT_TOKEN, }); export async function getPRDiff(pr: PRContext): Promise { console.log(`Fetching PR #${pr.prNumber} diff for ${pr.owner}/${pr.repo}`); const res = await client.rest.repository.repoDownloadPullDiffOrPatch({ owner: pr.owner, repo: pr.repo, index: pr.prNumber, diffType: "diff", }); return res.data; } export async function getPRFiles(pr: PRContext): Promise { console.log( `Fetching PR #${pr.prNumber} changed files for ${pr.owner}/${pr.repo}`, ); const res = await client.rest.repository.repoGetPullRequestFiles({ owner: pr.owner, repo: pr.repo, index: pr.prNumber, }); return res.data; } export async function postReviewComment( pr: PRContext, body: string, ): Promise { console.log( `Posting review comment on PR #${pr.prNumber} for ${pr.owner}/${pr.repo}`, ); const res = await client.rest.issue.issueCreateComment({ owner: pr.owner, repo: pr.repo, index: pr.prNumber, body: { body }, }); return res.data; } export async function postInlineComment( pr: PRContext, file: string, line: number, body: string, ): Promise { console.log( `Posting inline review comment on PR #${pr.prNumber} for ${pr.owner}/${pr.repo} at ${file}:${line}`, ); const res = await client.rest.repository.repoCreatePullReview({ owner: pr.owner, repo: pr.repo, index: pr.prNumber, body: { event: "COMMENT", commit_id: pr.headSha, comments: [{ path: file, new_position: line, body }], }, }); return res.data; } export async function addReaction( pr: PRContext, commentId: number, reaction: string, ): Promise { const res = await client.rest.issue.issuePostCommentReaction({ owner: pr.owner, repo: pr.repo, id: commentId, content: { content: reaction }, }); return res.data; } export async function removeReaction( pr: PRContext, commentId: number, reaction: string, ): Promise { await client.rest.issue.issueDeleteCommentReaction({ owner: pr.owner, repo: pr.repo, id: commentId, content: { content: reaction }, }); } export async function getPR(owner: string, repo: string, prNumber: number) { console.log(`Fetching PR #${prNumber} data for ${owner}/${repo}`); const res = await client.rest.repository.repoGetPullRequest({ owner, repo, index: prNumber, }); return res.data; } export async function getFileContents( pr: PRContext, filePath: string, ): Promise { console.log( `Fetching contents of ${filePath} at PR #${pr.prNumber} head for ${pr.owner}/${pr.repo}`, ); const res = await client.rest.repository.repoGetContents({ owner: pr.owner, repo: pr.repo, filepath: filePath, ref: pr.headSha, }); const entry = Array.isArray(res.data) ? res.data[0] : res.data; if (!entry?.content || entry.type !== "file") return ""; // content is base64-encoded; Buffer handles embedded newlines return Buffer.from(entry.content, "base64").toString("utf8"); } export async function listDir( pr: PRContext, dirPath: string, ): Promise { console.log( `Listing directory ${dirPath} at PR #${pr.prNumber} head for ${pr.owner}/${pr.repo}`, ); const res = await client.rest.repository.repoGetContents({ owner: pr.owner, repo: pr.repo, filepath: dirPath, ref: pr.headSha, }); const entries = Array.isArray(res.data) ? res.data : [res.data]; return entries.map((e) => e.name ?? "").filter(Boolean); } // Gitea REST API v1 has no code search endpoint — returns repo names matching the query. // mcp.ts should strongly prefer disk grep (find_symbol) when workspace is available. export async function searchCode( pr: PRContext, query: string, ): Promise { console.log( `Searching code for "${query}" at PR #${pr.prNumber} head for ${pr.owner}/${pr.repo}`, ); const res = await client.rest.repository.repoSearch({ q: query, limit: 20, }); return (res.data.data ?? []).map((r) => r.full_name ?? "").filter(Boolean); }