This commit is contained in:
2026-06-01 20:47:26 -05:00
parent 108b8243a9
commit eb7de776e4
5 changed files with 201 additions and 49 deletions
+49 -17
View File
@@ -2,7 +2,11 @@ import { existsSync, readFileSync, readdirSync } from "node:fs";
import { execFileSync } from "node:child_process";
import { join, resolve, relative } from "node:path";
import type { Tool } from "ollama";
import { getFileContents, listDir as giteaListDir, searchCode } from "./gitea.ts";
import {
getFileContents,
listDir as giteaListDir,
searchCode,
} from "./gitea.ts";
import type { PRContext } from "./types.ts";
const MAX_FILE_LINES = 200;
@@ -18,7 +22,10 @@ export const TOOLS: Tool[] = [
type: "object",
required: ["path"],
properties: {
path: { type: "string", description: "Repo-relative path to the file" },
path: {
type: "string",
description: "Repo-relative path to the file",
},
},
},
},
@@ -41,12 +48,16 @@ export const TOOLS: Tool[] = [
type: "function",
function: {
name: "find_symbol",
description: "Search for a symbol, function, or pattern across the repository.",
description:
"Search for a symbol, function, or pattern across the repository.",
parameters: {
type: "object",
required: ["symbol"],
properties: {
symbol: { type: "string", description: "Symbol name or regex pattern to search for" },
symbol: {
type: "string",
description: "Symbol name or regex pattern to search for",
},
},
},
},
@@ -117,7 +128,8 @@ export class ToolServer {
if (this.workspace) {
const full = join(this.workspace, safe);
// Verify resolved path stays inside workspace
if (!resolve(full).startsWith(resolve(this.workspace))) return "[invalid path]";
if (!resolve(full).startsWith(resolve(this.workspace)))
return "[invalid path]";
if (!existsSync(full)) return "[file not found]";
const content = readFileSync(full, "utf8");
const lines = content.split("\n");
@@ -136,10 +148,13 @@ export class ToolServer {
if (this.workspace) {
const full = join(this.workspace, safe);
if (!resolve(full).startsWith(resolve(this.workspace))) return "[invalid path]";
if (!resolve(full).startsWith(resolve(this.workspace)))
return "[invalid path]";
if (!existsSync(full)) return "[directory not found]";
const entries = readdirSync(full, { withFileTypes: true });
return entries.map((e) => `${e.isDirectory() ? "d" : "f"} ${e.name}`).join("\n");
return entries
.map((e) => `${e.isDirectory() ? "d" : "f"} ${e.name}`)
.join("\n");
}
const entries = await giteaListDir(this.pr, safe);
@@ -155,20 +170,32 @@ export class ToolServer {
const out = execFileSync(
"grep",
[
"-r", "-n",
"--include=*.ts", "--include=*.tsx",
"--include=*.js", "--include=*.jsx",
"--include=*.py", "--include=*.go",
"--include=*.rs", "--include=*.rb",
"--include=*.java", "--include=*.kt",
"-r",
"-n",
"--include=*.ts",
"--include=*.tsx",
"--include=*.js",
"--include=*.jsx",
"--include=*.py",
"--include=*.go",
"--include=*.rs",
"--include=*.rb",
"--include=*.java",
"--include=*.kt",
symbol,
this.workspace,
],
{ encoding: "utf8", timeout: 5000, maxBuffer: 256 * 1024 },
);
const lines = out.split("\n").filter(Boolean).slice(0, MAX_GREP_RESULTS);
const lines = out
.split("\n")
.filter(Boolean)
.slice(0, MAX_GREP_RESULTS);
// Strip workspace prefix so paths are repo-relative
return lines.map((l) => l.replace(this.workspace! + "/", "")).join("\n") || "[not found]";
return (
lines.map((l) => l.replace(this.workspace! + "/", "")).join("\n") ||
"[not found]"
);
} catch {
return "[not found]";
}
@@ -179,8 +206,13 @@ export class ToolServer {
}
}
export function createMcpServer(pr: PRContext, maxToolCalls: number): ToolServer {
const ws = process.env.GITEA_WORKSPACE ?? process.env.GITHUB_WORKSPACE ?? null;
export function createMcpServer(
pr: PRContext,
maxToolCalls: number,
): ToolServer {
console.log("Initializing tool server...");
const ws =
process.env.GITEA_WORKSPACE ?? process.env.GITHUB_WORKSPACE ?? null;
const workspace = ws && existsSync(ws) ? ws : null;
return new ToolServer(pr, workspace, maxToolCalls);
}