+65
-15
@@ -1,6 +1,7 @@
|
||||
import { relative, resolve } from "node:path";
|
||||
import { type } from "arktype";
|
||||
import { $ } from "../utils/shell.ts";
|
||||
import { handleToolError, handleToolSuccess, tool, type ToolResult } from "./shared.ts";
|
||||
import { handleToolError, handleToolSuccess, type ToolResult, tool } from "./shared.ts";
|
||||
|
||||
export const ListFiles = type({
|
||||
path: type.string
|
||||
@@ -12,32 +13,81 @@ export const ListFiles = type({
|
||||
export const ListFilesTool = tool({
|
||||
name: "list_files",
|
||||
description:
|
||||
"List files in the repository using git ls-files. Useful for discovering the file structure and locating files.",
|
||||
"List files in the repository, including both git-tracked and untracked files. Useful for discovering the file structure and locating files, including newly created files that haven't been committed yet.",
|
||||
parameters: ListFiles,
|
||||
execute: async ({ path }: { path?: string }): Promise<ToolResult> => {
|
||||
try {
|
||||
// Use git ls-files to list tracked files
|
||||
// This respects .gitignore and gives a clean list of source files
|
||||
const pathStr = path ?? ".";
|
||||
const output = $("git", pathStr === "." ? ["ls-files"] : ["ls-files", pathStr], {
|
||||
log: false,
|
||||
});
|
||||
const files = output.split("\n").filter((f) => f.trim() !== "");
|
||||
const cwd = process.cwd();
|
||||
|
||||
if (files.length === 0) {
|
||||
// Fallback for non-git environments or untracked files
|
||||
// Get git-tracked files
|
||||
let gitFiles: string[] = [];
|
||||
let gitFailed = false;
|
||||
try {
|
||||
const gitArgs = pathStr === "." ? ["ls-files"] : ["ls-files", pathStr];
|
||||
const gitOutput = $("git", gitArgs, { log: false });
|
||||
gitFiles = gitOutput
|
||||
.split("\n")
|
||||
.filter((f) => f.trim() !== "")
|
||||
.map((f) => f.trim());
|
||||
} catch {
|
||||
// git might fail, that's ok - we'll use find instead
|
||||
gitFailed = true;
|
||||
}
|
||||
|
||||
// Always also check filesystem for untracked files
|
||||
// This is important because newly created files won't be in git yet
|
||||
let filesystemFiles: string[] = [];
|
||||
let findFailed = false;
|
||||
try {
|
||||
const findOutput = $(
|
||||
"find",
|
||||
[pathStr, "-maxdepth", "3", "-not", "-path", "*/.*", "-type", "f"],
|
||||
{ log: false }
|
||||
);
|
||||
return handleToolSuccess({
|
||||
files: findOutput.split("\n").filter((f) => f.trim() !== ""),
|
||||
method: "find",
|
||||
});
|
||||
filesystemFiles = findOutput
|
||||
.split("\n")
|
||||
.filter((f) => f.trim() !== "")
|
||||
.map((f) => {
|
||||
const trimmed = f.trim();
|
||||
// normalize to relative paths for comparison
|
||||
try {
|
||||
return relative(cwd, resolve(cwd, trimmed));
|
||||
} catch {
|
||||
return trimmed;
|
||||
}
|
||||
});
|
||||
} catch {
|
||||
// find might fail, that's ok - we'll just use git files
|
||||
findFailed = true;
|
||||
}
|
||||
|
||||
return handleToolSuccess({ files, method: "git" });
|
||||
// if both methods failed, return an error
|
||||
if (gitFailed && findFailed) {
|
||||
return handleToolError(
|
||||
new Error(
|
||||
`Failed to list files: both git ls-files and find commands failed. ` +
|
||||
`Path: ${pathStr}, working directory: ${cwd}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Create a Set of git files for efficient lookup
|
||||
const gitFilesSet = new Set(gitFiles);
|
||||
|
||||
// Combine both lists, removing duplicates
|
||||
const allFiles = [...new Set([...gitFiles, ...filesystemFiles])].sort();
|
||||
|
||||
// Calculate actual untracked count (files in filesystem but not in git)
|
||||
const untrackedFiles = filesystemFiles.filter((f) => !gitFilesSet.has(f));
|
||||
const untrackedCount = untrackedFiles.length;
|
||||
|
||||
return handleToolSuccess({
|
||||
files: allFiles,
|
||||
method: "combined",
|
||||
trackedCount: gitFiles.length,
|
||||
untrackedCount,
|
||||
});
|
||||
} catch (error) {
|
||||
return handleToolError(error);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user