diff --git a/apps/server/src/services/file_ops.ts b/apps/server/src/services/file_ops.ts index 541d6b9..41a55fd 100644 --- a/apps/server/src/services/file_ops.ts +++ b/apps/server/src/services/file_ops.ts @@ -1,5 +1,5 @@ import { readFile, readdir, stat } from 'node:fs/promises'; -import { resolve, basename, relative } from 'node:path'; +import { resolve, relative } from 'node:path'; import { spawn } from 'node:child_process'; import type { Stats } from 'node:fs'; import { pathGuard, PathScopeError } from './path_guard.js'; @@ -8,7 +8,7 @@ const MAX_FILE_BYTES = 5 * 1024 * 1024; const DEFAULT_VIEW_LINES = 200; const MAX_GREP_RESULTS = 200; const DEFAULT_GREP_RESULTS = 100; -const MAX_FIND_RESULTS = 1000; +const MAX_FIND_RESULTS = 200; const DEFAULT_FIND_RESULTS = 100; const MAX_DIR_ENTRIES = 500; @@ -44,6 +44,7 @@ export interface GrepResult { export interface FindFilesResult { files: string[]; + total: number; truncated: boolean; } @@ -195,15 +196,18 @@ export async function grep( export async function findFiles( projectRoot: string, pattern?: string, - opts?: { type?: 'file' | 'dir'; max_results?: number } + opts?: { type?: 'file' | 'dir'; max_results?: number; path?: string } ): Promise { const limit = Math.min( Math.max(opts?.max_results ?? DEFAULT_FIND_RESULTS, 1), MAX_FIND_RESULTS ); + const target = opts?.path != null + ? await pathGuard(projectRoot, opts.path) + : projectRoot; const args = ['--files']; if (pattern) args.push('--glob', pattern); - args.push(projectRoot); + args.push(target); return new Promise((resolveP, rejectP) => { const child = spawn('rg', args, { cwd: projectRoot }); @@ -243,6 +247,7 @@ export async function findFiles( } resolveP({ files, + total, truncated: total > files.length, }); }); diff --git a/apps/server/src/services/tools.ts b/apps/server/src/services/tools.ts index 84c2c90..1918460 100644 --- a/apps/server/src/services/tools.ts +++ b/apps/server/src/services/tools.ts @@ -1,9 +1,8 @@ import { readFile, readdir, stat } from 'node:fs/promises'; import { resolve, basename, relative } from 'node:path'; -import { spawn } from 'node:child_process'; import { z } from 'zod'; import { pathGuard, PathScopeError } from './path_guard.js'; -import { grep as fileOpsGrep } from './file_ops.js'; +import { grep as fileOpsGrep, findFiles as fileOpsFindFiles } from './file_ops.js'; const MAX_FILE_BYTES = 5 * 1024 * 1024; const DEFAULT_VIEW_LINES = 200; @@ -249,55 +248,21 @@ export const findFiles: ToolDef = { }, }, async execute(input, projectRoot) { - const target = await pathGuard(projectRoot, input.path ?? projectRoot); const limit = Math.min( Math.max(input.max_results ?? DEFAULT_FIND_RESULTS, 1), MAX_FIND_RESULTS ); - return await new Promise((resolveP, rejectP) => { - const args = ['--files', '--glob', input.pattern, target]; - const child = spawn('rg', args, { cwd: projectRoot }); - const paths: string[] = []; - let total = 0; - let buf = ''; - let stderr = ''; - child.stdout.setEncoding('utf8'); - child.stderr.setEncoding('utf8'); - child.stdout.on('data', (chunk: string) => { - buf += chunk; - let idx; - while ((idx = buf.indexOf('\n')) >= 0) { - const line = buf.slice(0, idx); - buf = buf.slice(idx + 1); - if (!line) continue; - total++; - if (paths.length < limit) { - paths.push(relative(projectRoot, line) || line); - } - } - }); - child.stderr.on('data', (chunk: string) => { - stderr += chunk; - }); - child.on('error', (err) => rejectP(err)); - child.on('close', (code) => { - if (code === 2) { - rejectP(new Error(`ripgrep failed: ${stderr.slice(0, 300)}`)); - return; - } - if (buf.length > 0) { - total++; - if (paths.length < limit) { - paths.push(relative(projectRoot, buf) || buf); - } - } - resolveP({ - paths, - total, - truncated: total > paths.length, - }); - }); + // Delegate to file_ops.findFiles; reshape { files, total, truncated } to + // preserve the LLM-visible output format { paths, total, truncated } + const result = await fileOpsFindFiles(projectRoot, input.pattern, { + path: input.path, + max_results: limit, }); + return { + paths: result.files, + total: result.total, + truncated: result.truncated, + }; }, };