batch3 T4 review fixes: harmonize find_files cap; delegate to file_ops

- file_ops.MAX_FIND_RESULTS: 1000 -> 200 to match existing tool cap and
  preserve LLM behavior
- tools.find_files now delegates to file_ops.findFiles (parallels how
  grep already delegates); drops ~50 LOC of duplicated path resolution
  and rg subprocess
- Drop unused basename import in file_ops

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-15 15:18:44 +00:00
parent 890d229875
commit 89f1b7e862
2 changed files with 20 additions and 50 deletions

View File

@@ -1,5 +1,5 @@
import { readFile, readdir, stat } from 'node:fs/promises'; 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 { spawn } from 'node:child_process';
import type { Stats } from 'node:fs'; import type { Stats } from 'node:fs';
import { pathGuard, PathScopeError } from './path_guard.js'; import { pathGuard, PathScopeError } from './path_guard.js';
@@ -8,7 +8,7 @@ const MAX_FILE_BYTES = 5 * 1024 * 1024;
const DEFAULT_VIEW_LINES = 200; const DEFAULT_VIEW_LINES = 200;
const MAX_GREP_RESULTS = 200; const MAX_GREP_RESULTS = 200;
const DEFAULT_GREP_RESULTS = 100; const DEFAULT_GREP_RESULTS = 100;
const MAX_FIND_RESULTS = 1000; const MAX_FIND_RESULTS = 200;
const DEFAULT_FIND_RESULTS = 100; const DEFAULT_FIND_RESULTS = 100;
const MAX_DIR_ENTRIES = 500; const MAX_DIR_ENTRIES = 500;
@@ -44,6 +44,7 @@ export interface GrepResult {
export interface FindFilesResult { export interface FindFilesResult {
files: string[]; files: string[];
total: number;
truncated: boolean; truncated: boolean;
} }
@@ -195,15 +196,18 @@ export async function grep(
export async function findFiles( export async function findFiles(
projectRoot: string, projectRoot: string,
pattern?: string, pattern?: string,
opts?: { type?: 'file' | 'dir'; max_results?: number } opts?: { type?: 'file' | 'dir'; max_results?: number; path?: string }
): Promise<FindFilesResult> { ): Promise<FindFilesResult> {
const limit = Math.min( const limit = Math.min(
Math.max(opts?.max_results ?? DEFAULT_FIND_RESULTS, 1), Math.max(opts?.max_results ?? DEFAULT_FIND_RESULTS, 1),
MAX_FIND_RESULTS MAX_FIND_RESULTS
); );
const target = opts?.path != null
? await pathGuard(projectRoot, opts.path)
: projectRoot;
const args = ['--files']; const args = ['--files'];
if (pattern) args.push('--glob', pattern); if (pattern) args.push('--glob', pattern);
args.push(projectRoot); args.push(target);
return new Promise((resolveP, rejectP) => { return new Promise((resolveP, rejectP) => {
const child = spawn('rg', args, { cwd: projectRoot }); const child = spawn('rg', args, { cwd: projectRoot });
@@ -243,6 +247,7 @@ export async function findFiles(
} }
resolveP({ resolveP({
files, files,
total,
truncated: total > files.length, truncated: total > files.length,
}); });
}); });

View File

@@ -1,9 +1,8 @@
import { readFile, readdir, stat } from 'node:fs/promises'; import { readFile, readdir, stat } from 'node:fs/promises';
import { resolve, basename, relative } from 'node:path'; import { resolve, basename, relative } from 'node:path';
import { spawn } from 'node:child_process';
import { z } from 'zod'; import { z } from 'zod';
import { pathGuard, PathScopeError } from './path_guard.js'; 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 MAX_FILE_BYTES = 5 * 1024 * 1024;
const DEFAULT_VIEW_LINES = 200; const DEFAULT_VIEW_LINES = 200;
@@ -249,55 +248,21 @@ export const findFiles: ToolDef<FindFilesInputT> = {
}, },
}, },
async execute(input, projectRoot) { async execute(input, projectRoot) {
const target = await pathGuard(projectRoot, input.path ?? projectRoot);
const limit = Math.min( const limit = Math.min(
Math.max(input.max_results ?? DEFAULT_FIND_RESULTS, 1), Math.max(input.max_results ?? DEFAULT_FIND_RESULTS, 1),
MAX_FIND_RESULTS MAX_FIND_RESULTS
); );
return await new Promise((resolveP, rejectP) => { // Delegate to file_ops.findFiles; reshape { files, total, truncated } to
const args = ['--files', '--glob', input.pattern, target]; // preserve the LLM-visible output format { paths, total, truncated }
const child = spawn('rg', args, { cwd: projectRoot }); const result = await fileOpsFindFiles(projectRoot, input.pattern, {
const paths: string[] = []; path: input.path,
let total = 0; max_results: limit,
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,
});
});
}); });
return {
paths: result.files,
total: result.total,
truncated: result.truncated,
};
}, },
}; };