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:
@@ -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,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user