topchester-ai 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/cli.mjs +95 -6
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Topchester Agent
|
|
2
2
|
|
|
3
|
+
Website: https://topchester.com
|
|
4
|
+
|
|
3
5
|
## Overview
|
|
4
6
|
|
|
5
7
|
Topchester Agent is a terminal-native TUI coding agent tightly coupled to a committed project knowledge base. The normal workflow is to compile project knowledge first, then let the agent use that knowledge while planning, editing, checking drift, and updating the repository.
|
package/dist/cli.mjs
CHANGED
|
@@ -2641,7 +2641,7 @@ const editFileTool = defineTool({
|
|
|
2641
2641
|
execute: (context, args) => editWorkspaceFile(context.workspaceRoot, args, { logger: context.logger })
|
|
2642
2642
|
});
|
|
2643
2643
|
async function editWorkspaceFile(workspaceRoot, args, options = {}) {
|
|
2644
|
-
const scopedPath = resolveWorkspaceScopedPath$
|
|
2644
|
+
const scopedPath = resolveWorkspaceScopedPath$3(workspaceRoot, args.path);
|
|
2645
2645
|
return enqueueFileMutation(scopedPath.path, async () => {
|
|
2646
2646
|
const fileStat = await statExistingFile(scopedPath.path, args.path);
|
|
2647
2647
|
const beforeBytes = await readFile(scopedPath.path);
|
|
@@ -2704,7 +2704,7 @@ function applyExactEdits(content, edits, path = "file") {
|
|
|
2704
2704
|
firstChangedLine: getFirstChangedLine(document.body, newBody)
|
|
2705
2705
|
};
|
|
2706
2706
|
}
|
|
2707
|
-
function resolveWorkspaceScopedPath$
|
|
2707
|
+
function resolveWorkspaceScopedPath$3(workspaceRoot, path) {
|
|
2708
2708
|
if (path.includes("\0")) throw new Error("edit_file path is invalid.");
|
|
2709
2709
|
const resolvedWorkspace = resolve(workspaceRoot);
|
|
2710
2710
|
const resolvedPath = isAbsolute(path) ? resolve(path) : resolve(resolvedWorkspace, path);
|
|
@@ -2908,7 +2908,7 @@ const findFileTool = defineTool({
|
|
|
2908
2908
|
})
|
|
2909
2909
|
});
|
|
2910
2910
|
async function findWorkspaceFilesByName(workspaceRoot, args, options = {}) {
|
|
2911
|
-
const scopedPath = resolveWorkspaceScopedPath$
|
|
2911
|
+
const scopedPath = resolveWorkspaceScopedPath$2(workspaceRoot, args.path);
|
|
2912
2912
|
const matches = (await collectWorkspaceFiles(scopedPath.workspaceRoot, scopedPath.path, scopedPath.relativePath, options)).map((path) => {
|
|
2913
2913
|
const score = scoreFileMatch(args.query, path);
|
|
2914
2914
|
return score > 0 ? {
|
|
@@ -3106,7 +3106,7 @@ function scoreSubsequenceMatch(query, value) {
|
|
|
3106
3106
|
function normalize(value) {
|
|
3107
3107
|
return value.trim().toLowerCase().replaceAll("\\", "/");
|
|
3108
3108
|
}
|
|
3109
|
-
function resolveWorkspaceScopedPath$
|
|
3109
|
+
function resolveWorkspaceScopedPath$2(workspaceRoot, path) {
|
|
3110
3110
|
const resolvedWorkspace = resolve(workspaceRoot);
|
|
3111
3111
|
const resolvedPath = isAbsolute(path) ? resolve(path) : resolve(resolvedWorkspace, path);
|
|
3112
3112
|
const relativePath = relative(resolvedWorkspace, resolvedPath);
|
|
@@ -3164,7 +3164,7 @@ const grepTool = defineTool({
|
|
|
3164
3164
|
})
|
|
3165
3165
|
});
|
|
3166
3166
|
async function grepWorkspace(workspaceRoot, args, options = {}) {
|
|
3167
|
-
const scopedPath = resolveWorkspaceScopedPath(workspaceRoot, args.path ?? ".");
|
|
3167
|
+
const scopedPath = resolveWorkspaceScopedPath$1(workspaceRoot, args.path ?? ".");
|
|
3168
3168
|
const executable = await findSearchExecutable(options.pathEnv);
|
|
3169
3169
|
if (!executable) {
|
|
3170
3170
|
const warning = "grep could not run because neither rg nor grep is available on PATH.";
|
|
@@ -3228,7 +3228,7 @@ async function grepWorkspace(workspaceRoot, args, options = {}) {
|
|
|
3228
3228
|
content: truncateToolOutput(result.stdout.trimEnd() || "No matches.")
|
|
3229
3229
|
};
|
|
3230
3230
|
}
|
|
3231
|
-
function resolveWorkspaceScopedPath(workspaceRoot, path) {
|
|
3231
|
+
function resolveWorkspaceScopedPath$1(workspaceRoot, path) {
|
|
3232
3232
|
const resolvedWorkspace = resolve(workspaceRoot);
|
|
3233
3233
|
const resolvedPath = isAbsolute(path) ? resolve(path) : resolve(resolvedWorkspace, path);
|
|
3234
3234
|
const relativePath = relative(resolvedWorkspace, resolvedPath);
|
|
@@ -3286,6 +3286,93 @@ function truncateToolOutput(output) {
|
|
|
3286
3286
|
function isRecord$1(value) {
|
|
3287
3287
|
return typeof value === "object" && value !== null;
|
|
3288
3288
|
}
|
|
3289
|
+
const listFilesTool = defineTool({
|
|
3290
|
+
name: "list_files",
|
|
3291
|
+
description: "List files and directories inside a workspace folder.",
|
|
3292
|
+
prompt: "list_files: list files and directories inside the workspace; top-level by default, recursive only when requested, with \"/\" after directory names. To use it, reply with only JSON: {\"tool\":\"list_files\",\"args\":{\"path\":\"src\",\"recursive\":false,\"limit\":500}}",
|
|
3293
|
+
argsSchema: z.object({
|
|
3294
|
+
path: z.string().optional().default("."),
|
|
3295
|
+
recursive: z.boolean().optional().default(false),
|
|
3296
|
+
limit: z.number().int().min(1).max(2e3).optional().default(500)
|
|
3297
|
+
}),
|
|
3298
|
+
execute: (context, args) => listWorkspaceFiles(context.workspaceRoot, args)
|
|
3299
|
+
});
|
|
3300
|
+
async function listWorkspaceFiles(workspaceRoot, args) {
|
|
3301
|
+
const scopedPath = resolveWorkspaceScopedPath(workspaceRoot, args.path);
|
|
3302
|
+
if (!(await stat(scopedPath.path)).isDirectory()) throw new Error(`list_files can only list directories inside the workspace: ${args.path}`);
|
|
3303
|
+
const entries = args.recursive ? await collectRecursiveEntries(scopedPath.workspaceRoot, scopedPath.path, args.limit) : await collectTopLevelEntries(scopedPath.workspaceRoot, scopedPath.path, args.limit);
|
|
3304
|
+
const truncated = entries.truncated;
|
|
3305
|
+
const lines = entries.items.map((entry) => formatEntryPath(entry.relativePath, entry.isDirectory));
|
|
3306
|
+
const notices = [];
|
|
3307
|
+
if (truncated) notices.push(`[${args.limit} entry limit reached. Use a narrower path or a higher limit for more.]`);
|
|
3308
|
+
return {
|
|
3309
|
+
tool: "list_files",
|
|
3310
|
+
path: scopedPath.relativePath,
|
|
3311
|
+
content: [...lines.length > 0 ? lines : ["(empty directory)"], ...notices].join("\n"),
|
|
3312
|
+
warning: truncated ? `${args.limit} entry limit reached.` : void 0
|
|
3313
|
+
};
|
|
3314
|
+
}
|
|
3315
|
+
async function collectTopLevelEntries(workspaceRoot, startPath, limit) {
|
|
3316
|
+
const entries = await sortedDirectoryEntries(startPath);
|
|
3317
|
+
const items = [];
|
|
3318
|
+
for (const entry of entries) {
|
|
3319
|
+
if (items.length >= limit) return {
|
|
3320
|
+
items,
|
|
3321
|
+
truncated: true
|
|
3322
|
+
};
|
|
3323
|
+
const absolutePath = join(startPath, entry.name);
|
|
3324
|
+
items.push({
|
|
3325
|
+
relativePath: relative(workspaceRoot, absolutePath) || ".",
|
|
3326
|
+
isDirectory: entry.isDirectory()
|
|
3327
|
+
});
|
|
3328
|
+
}
|
|
3329
|
+
return {
|
|
3330
|
+
items,
|
|
3331
|
+
truncated: false
|
|
3332
|
+
};
|
|
3333
|
+
}
|
|
3334
|
+
async function collectRecursiveEntries(workspaceRoot, startPath, limit) {
|
|
3335
|
+
const items = [];
|
|
3336
|
+
const pending = [startPath];
|
|
3337
|
+
while (pending.length > 0) {
|
|
3338
|
+
const currentPath = pending.shift() ?? startPath;
|
|
3339
|
+
const entries = await sortedDirectoryEntries(currentPath);
|
|
3340
|
+
for (const entry of entries) {
|
|
3341
|
+
if (items.length >= limit) return {
|
|
3342
|
+
items,
|
|
3343
|
+
truncated: true
|
|
3344
|
+
};
|
|
3345
|
+
const absolutePath = join(currentPath, entry.name);
|
|
3346
|
+
const item = {
|
|
3347
|
+
relativePath: relative(workspaceRoot, absolutePath) || ".",
|
|
3348
|
+
isDirectory: entry.isDirectory()
|
|
3349
|
+
};
|
|
3350
|
+
items.push(item);
|
|
3351
|
+
if (item.isDirectory) pending.push(absolutePath);
|
|
3352
|
+
}
|
|
3353
|
+
}
|
|
3354
|
+
return {
|
|
3355
|
+
items,
|
|
3356
|
+
truncated: false
|
|
3357
|
+
};
|
|
3358
|
+
}
|
|
3359
|
+
async function sortedDirectoryEntries(path) {
|
|
3360
|
+
return (await readdir(path, { withFileTypes: true })).sort((left, right) => left.name.toLowerCase().localeCompare(right.name.toLowerCase()));
|
|
3361
|
+
}
|
|
3362
|
+
function formatEntryPath(path, isDirectory) {
|
|
3363
|
+
return isDirectory ? `${path}/` : path;
|
|
3364
|
+
}
|
|
3365
|
+
function resolveWorkspaceScopedPath(workspaceRoot, path) {
|
|
3366
|
+
const resolvedWorkspace = resolve(workspaceRoot);
|
|
3367
|
+
const resolvedPath = isAbsolute(path) ? resolve(path) : resolve(resolvedWorkspace, path);
|
|
3368
|
+
const relativePath = relative(resolvedWorkspace, resolvedPath);
|
|
3369
|
+
if (relativePath.startsWith("..") || isAbsolute(relativePath)) throw new Error(`list_files can only list directories inside the workspace: ${path}`);
|
|
3370
|
+
return {
|
|
3371
|
+
workspaceRoot: resolvedWorkspace,
|
|
3372
|
+
path: resolvedPath,
|
|
3373
|
+
relativePath: relativePath || "."
|
|
3374
|
+
};
|
|
3375
|
+
}
|
|
3289
3376
|
const readFileTool = defineTool({
|
|
3290
3377
|
name: "read_file",
|
|
3291
3378
|
description: "Read a UTF-8 file inside the workspace.",
|
|
@@ -3311,6 +3398,7 @@ async function readWorkspaceFile(workspaceRoot, path) {
|
|
|
3311
3398
|
//#region src/agent/tools/registry.ts
|
|
3312
3399
|
const toolRegistry = {
|
|
3313
3400
|
[readFileTool.name]: readFileTool,
|
|
3401
|
+
[listFilesTool.name]: listFilesTool,
|
|
3314
3402
|
[grepTool.name]: grepTool,
|
|
3315
3403
|
[findFileTool.name]: findFileTool,
|
|
3316
3404
|
[editFileTool.name]: editFileTool
|
|
@@ -3617,6 +3705,7 @@ function formatToolResultForPrompt(result) {
|
|
|
3617
3705
|
function formatToolCallMessage(call, result) {
|
|
3618
3706
|
switch (call.tool) {
|
|
3619
3707
|
case "read_file": return `Tool read_file: ${call.args.path}`;
|
|
3708
|
+
case "list_files": return `Tool list_files: ${call.args.path}${call.args.recursive ? " (recursive)" : ""}`;
|
|
3620
3709
|
case "grep": return `Tool grep: ${call.args.pattern} in ${call.args.path ?? "."}`;
|
|
3621
3710
|
case "find_file": return `Tool find_file: ${call.args.query} in ${call.args.path}`;
|
|
3622
3711
|
case "edit_file": return `Tool edit_file: ${call.args.path}${formatEditFileChangeSummary(result)}`;
|