@saber2pr/ai-agent 0.0.44 → 0.0.46

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.
@@ -426,12 +426,9 @@ class McpGraphAgent {
426
426
  3. **禁止空操作**:如果你认为任务已完成或不需要调用工具,请不要输出任何 Action 结构。严禁使用 "None"、"null" 或空字符串作为工具名称。
427
427
 
428
428
  # 🎯 核心指令
429
- 1. **任务终结判定**:当你已经读取了用户要求的文件、回答了问题或完成了审计目标时,必须立即提供最终回复。
429
+ 1. **任务终结判定**:当你已经读取了用户要求的文件、回答了问题或完成了代码编写时,必须立即提供最终回复。
430
430
  2. **回复格式**:任务完成时,请以 "Final Answer:" 开头进行总结,此时不再调用任何工具。
431
431
 
432
- # 📝 审计任务专项
433
- 1. 避免在同一个文件上陷入无限循环尝试。
434
- 2. 优先通过 \`list_directory\` 了解全局,再深入具体文件。
435
432
  {extraPrompt}`;
436
433
  // 2. 核心逻辑:处理消息上下文
437
434
  let inputMessages;
@@ -4,7 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getFilesystemTools = void 0;
7
- /* eslint-disable no-continue */
8
7
  const promises_1 = __importDefault(require("fs/promises"));
9
8
  const minimatch_1 = require("minimatch");
10
9
  const path_1 = __importDefault(require("path"));
@@ -27,25 +26,9 @@ const WriteFileArgsSchema = zod_1.z.object({
27
26
  path: zod_1.z.string(),
28
27
  content: zod_1.z.string(),
29
28
  });
30
- const EditOperation = zod_1.z.object({
31
- oldText: zod_1.z.string().describe('Text to search for - must match exactly'),
32
- newText: zod_1.z.string().describe('Text to replace with'),
33
- });
34
- const EditFileArgsSchema = zod_1.z.object({
35
- path: zod_1.z.string(),
36
- edits: zod_1.z.array(EditOperation),
37
- dryRun: zod_1.z
38
- .boolean()
39
- .optional()
40
- .default(false)
41
- .describe('Preview changes using git-style diff format'),
42
- });
43
29
  const CreateDirectoryArgsSchema = zod_1.z.object({
44
30
  path: zod_1.z.string(),
45
31
  });
46
- const ListDirectoryArgsSchema = zod_1.z.object({
47
- path: zod_1.z.string(),
48
- });
49
32
  const ListDirectoryWithSizesArgsSchema = zod_1.z.object({
50
33
  path: zod_1.z.string(),
51
34
  sortBy: zod_1.z
@@ -57,6 +40,7 @@ const ListDirectoryWithSizesArgsSchema = zod_1.z.object({
57
40
  const DirectoryTreeArgsSchema = zod_1.z.object({
58
41
  path: zod_1.z.string(),
59
42
  excludePatterns: zod_1.z.array(zod_1.z.string()).optional().default([]),
43
+ depth: zod_1.z.number().optional().default(2).describe('递归深度,默认 2 层。增加深度会消耗更多 Token'),
60
44
  });
61
45
  const MoveFileArgsSchema = zod_1.z.object({
62
46
  source: zod_1.z.string(),
@@ -71,9 +55,19 @@ const GetFileInfoArgsSchema = zod_1.z.object({
71
55
  path: zod_1.z.string(),
72
56
  });
73
57
  const GrepSearchArgsSchema = zod_1.z.object({
74
- path: zod_1.z.string().describe('The starting directory path for searching content'),
75
- query: zod_1.z.string().describe('The text keyword to search for'),
76
- includePattern: zod_1.z.string().optional().default('**/*').describe('Only search in files matching this pattern, for example "**/*.ts"'),
58
+ path: zod_1.z.string().describe('搜索的起始目录路径'),
59
+ query: zod_1.z.string().describe('要搜索的文本关键字'),
60
+ includePattern: zod_1.z.string().optional().default('**/*').describe('匹配模式,例如 "**/*.ts"'),
61
+ maxFiles: zod_1.z.number().optional().default(100).describe('最大扫描文件数,防止大型项目超时'),
62
+ });
63
+ const PatchEditArgsSchema = zod_1.z.object({
64
+ path: zod_1.z.string().describe('文件路径'),
65
+ patches: zod_1.z.array(zod_1.z.object({
66
+ startLine: zod_1.z.number().describe('起始行号(包含)'),
67
+ endLine: zod_1.z.number().describe('结束行号(包含)'),
68
+ replacement: zod_1.z.string().describe('要插入的新代码内容'),
69
+ originalSnippet: zod_1.z.string().optional().describe('可选:该行范围内的原始代码片段,用于二次校验防止行号偏移'),
70
+ })).describe('补丁列表。注意:若有多个补丁,建议从文件尾部向头部执行,或确保行号不重叠'),
77
71
  });
78
72
  const getFilesystemTools = (targetDir) => {
79
73
  (0, lib_1.setAllowedDirectories)([targetDir]);
@@ -97,44 +91,48 @@ const getFilesystemTools = (targetDir) => {
97
91
  };
98
92
  const readTextFileTool = (0, createTool_1.createTool)({
99
93
  name: 'read_text_file',
100
- description: 'Read the complete contents of a file from the file system as text. ' +
101
- 'Handles various text encodings and provides detailed error messages ' +
102
- 'if the file cannot be read. Use this tool when you need to examine ' +
103
- "the contents of a single file. Use the 'head' parameter to read only " +
104
- "the first N lines of a file, or the 'tail' parameter to read only " +
105
- 'the last N lines of a file. Operates on the file as text regardless of extension.',
94
+ description: '读取文件全文。若超过100行则禁止使用,必须改用 read_file_range。支持 head/tail 参数。',
106
95
  parameters: ReadTextFileArgsSchema,
107
96
  handler: readTextFileHandler,
108
97
  });
109
98
  const readMultipleFilesTool = (0, createTool_1.createTool)({
110
99
  name: 'read_multiple_files',
111
- description: 'Read the contents of multiple files simultaneously. This is more ' +
112
- 'efficient than reading files one by one when you need to analyze ' +
113
- "or compare multiple files. Each file's content is returned with its " +
114
- "path as a reference. Failed reads for individual files won't stop " +
115
- 'the entire operation. Only works within allowed directories.',
100
+ description: '同时读取多个文件的内容。当你需要对比多个文件或分析跨文件关联时使用。' +
101
+ '注意:为了防止 Token 溢出,本工具一次最多读取 10 个文件,且每个文件仅展示前 6000 字符。' +
102
+ '若需查看完整大文件或特定逻辑,请改用 read_file_range。',
116
103
  parameters: ReadMultipleFilesArgsSchema,
117
104
  handler: async (args) => {
118
- const results = await Promise.all(args.paths.map(async (filePath) => {
105
+ // 保护 1:文件数量限制 (防止 AI 一次传入几十个文件)
106
+ const MAX_FILES = 10;
107
+ const pathsToRead = args.paths.slice(0, MAX_FILES);
108
+ const isTruncatedByCount = args.paths.length > MAX_FILES;
109
+ // 保护 2:单文件字符数限制 (防止读入超大型二进制或日志文件)
110
+ const MAX_CHARS_PER_FILE = 6000;
111
+ const results = await Promise.all(pathsToRead.map(async (filePath) => {
119
112
  try {
113
+ // 沿用你现有的路径验证逻辑
120
114
  const validPath = await (0, lib_1.validatePath)(targetDir, filePath);
121
115
  const content = await (0, lib_1.readFileContent)(validPath);
116
+ if (content.length > MAX_CHARS_PER_FILE) {
117
+ return `${filePath} (内容已截断):\n${content.substring(0, MAX_CHARS_PER_FILE)}\n\n[... 内容过长,仅展示前 ${MAX_CHARS_PER_FILE} 字符。若需查看后续内容,请使用 read_file_range 指定行号读取 ...]`;
118
+ }
122
119
  return `${filePath}:\n${content}\n`;
123
120
  }
124
121
  catch (error) {
125
122
  const errorMessage = error instanceof Error ? error.message : String(error);
126
- return `${filePath}: Error - ${errorMessage}`;
123
+ return `${filePath}: 读取失败 - ${errorMessage}`;
127
124
  }
128
125
  }));
129
- const text = results.join('\n---\n');
126
+ let text = results.join('\n---\n');
127
+ if (isTruncatedByCount) {
128
+ text += `\n\n⚠️ 注意:一次请求最多处理 ${MAX_FILES} 个文件。剩余 ${args.paths.length - MAX_FILES} 个文件未读取,请分批请求。`;
129
+ }
130
130
  return text;
131
131
  },
132
132
  });
133
133
  const writeFileTool = (0, createTool_1.createTool)({
134
134
  name: 'write_file',
135
- description: 'Create a new file or completely overwrite an existing file with new content. ' +
136
- 'Use with caution as it will overwrite existing files without warning. ' +
137
- 'Handles text content with proper encoding. Only works within allowed directories.',
135
+ description: '仅用于创建新文件。严禁用于修改现有源代码。',
138
136
  parameters: WriteFileArgsSchema,
139
137
  handler: async (args) => {
140
138
  const validPath = await (0, lib_1.validatePath)(targetDir, args.path);
@@ -143,18 +141,6 @@ const getFilesystemTools = (targetDir) => {
143
141
  return text;
144
142
  },
145
143
  });
146
- const editFileTool = (0, createTool_1.createTool)({
147
- name: 'edit_file',
148
- description: 'Make line-based edits to a text file. Each edit replaces exact line sequences ' +
149
- 'with new content. Returns a git-style diff showing the changes made. ' +
150
- 'Only works within allowed directories.',
151
- parameters: EditFileArgsSchema,
152
- handler: async (args) => {
153
- const validPath = await (0, lib_1.validatePath)(targetDir, args.path);
154
- const result = await (0, lib_1.applyFileEdits)(validPath, args.edits, args.dryRun);
155
- return result;
156
- },
157
- });
158
144
  const createDirectoryTool = (0, createTool_1.createTool)({
159
145
  name: 'create_directory',
160
146
  description: 'Create a new directory or ensure a directory exists. Can create multiple ' +
@@ -169,24 +155,8 @@ const getFilesystemTools = (targetDir) => {
169
155
  return text;
170
156
  },
171
157
  });
172
- const listDirectoryTool = (0, createTool_1.createTool)({
173
- name: 'list_directory',
174
- description: 'Get a detailed listing of all files and directories in a specified path. ' +
175
- 'Results clearly distinguish between files and directories with [FILE] and [DIR] ' +
176
- 'prefixes. This tool is essential for understanding directory structure and ' +
177
- 'finding specific files within a directory. Only works within allowed directories.',
178
- parameters: ListDirectoryArgsSchema,
179
- handler: async (args) => {
180
- const validPath = await (0, lib_1.validatePath)(targetDir, args.path);
181
- const entries = await promises_1.default.readdir(validPath, { withFileTypes: true });
182
- const formatted = entries
183
- .map(entry => `${entry.isDirectory() ? '[DIR]' : '[FILE]'} ${entry.name}`)
184
- .join('\n');
185
- return formatted;
186
- },
187
- });
188
158
  const listDirectoryWithSizesTool = (0, createTool_1.createTool)({
189
- name: 'list_directory_with_sizes',
159
+ name: 'list_directory',
190
160
  description: 'Get a detailed listing of all files and directories in a specified path, including sizes. ' +
191
161
  'Results clearly distinguish between files and directories with [FILE] and [DIR] ' +
192
162
  'prefixes. This tool is useful for understanding directory structure and ' +
@@ -241,46 +211,36 @@ const getFilesystemTools = (targetDir) => {
241
211
  });
242
212
  const directoryTreeTool = (0, createTool_1.createTool)({
243
213
  name: 'directory_tree',
244
- description: 'Get a recursive tree view of files and directories as a JSON structure. ' +
245
- "Each entry includes 'name', 'type' (file/directory), and 'children' for directories. " +
246
- 'Files have no children array, while directories always have a children array (which may be empty). ' +
247
- 'The output is formatted with 2-space indentation for readability. Only works within allowed directories.',
214
+ description: '获取目录的递归树状 JSON 结构。' +
215
+ '默认仅展示 2 层深度以节省 Token。如果需要看更深层级,请调大 depth 参数。',
248
216
  parameters: DirectoryTreeArgsSchema,
249
217
  handler: async (args) => {
250
218
  const rootPath = args.path;
251
- async function buildTree(currentPath, excludePatterns = []) {
219
+ async function buildTree(currentPath, currentDepth, maxDepth, excludePatterns = []) {
220
+ if (currentDepth > maxDepth)
221
+ return []; // 深度限制
252
222
  const validPath = await (0, lib_1.validatePath)(targetDir, currentPath);
253
223
  const entries = await promises_1.default.readdir(validPath, { withFileTypes: true });
254
224
  const result = [];
255
225
  for (const entry of entries) {
256
226
  const relativePath = path_1.default.relative(rootPath, path_1.default.join(currentPath, entry.name));
257
- const shouldExclude = excludePatterns.some(pattern => {
258
- if (pattern.includes('*')) {
259
- return (0, minimatch_1.minimatch)(relativePath, pattern, { dot: true });
260
- }
261
- // For files: match exact name or as part of path
262
- // For directories: match as directory path
263
- return ((0, minimatch_1.minimatch)(relativePath, pattern, { dot: true }) ||
264
- (0, minimatch_1.minimatch)(relativePath, `**/${pattern}`, { dot: true }) ||
265
- (0, minimatch_1.minimatch)(relativePath, `**/${pattern}/**`, { dot: true }));
266
- });
227
+ const shouldExclude = excludePatterns.some(pattern => (0, minimatch_1.minimatch)(relativePath, pattern, { dot: true }));
267
228
  if (shouldExclude)
268
229
  continue;
269
230
  const entryData = {
270
231
  name: entry.name,
271
232
  type: entry.isDirectory() ? 'directory' : 'file',
272
233
  };
273
- if (entry.isDirectory()) {
234
+ if (entry.isDirectory() && currentDepth < maxDepth) {
274
235
  const subPath = path_1.default.join(currentPath, entry.name);
275
- entryData.children = await buildTree(subPath, excludePatterns);
236
+ entryData.children = await buildTree(subPath, currentDepth + 1, maxDepth, excludePatterns);
276
237
  }
277
238
  result.push(entryData);
278
239
  }
279
240
  return result;
280
241
  }
281
- const treeData = await buildTree(rootPath, args.excludePatterns);
282
- const text = JSON.stringify(treeData, null, 2);
283
- return text;
242
+ const treeData = await buildTree(rootPath, 1, args.depth, args.excludePatterns);
243
+ return JSON.stringify(treeData, null, 2);
284
244
  },
285
245
  });
286
246
  const moveFileTool = (0, createTool_1.createTool)({
@@ -301,7 +261,8 @@ const getFilesystemTools = (targetDir) => {
301
261
  const searchFilesTool = (0, createTool_1.createTool)({
302
262
  name: 'search_files',
303
263
  description: 'Search for files matching a specific pattern in a specified path. ' +
304
- 'Returns a list of files that match the pattern. Only works within allowed directories.',
264
+ 'Returns a list of files that match the pattern. Only works within allowed directories.' +
265
+ 'Used only for filename search',
305
266
  parameters: SearchFilesArgsSchema,
306
267
  handler: async (args) => {
307
268
  const validPath = await (0, lib_1.validatePath)(targetDir, args.path);
@@ -314,68 +275,152 @@ const getFilesystemTools = (targetDir) => {
314
275
  });
315
276
  const getFileInfoTool = (0, createTool_1.createTool)({
316
277
  name: 'get_file_info',
317
- description: 'Get detailed information about a file, including its size, last modified time, and type. ' +
318
- 'Only works within allowed directories.',
278
+ description: '查看文件元数据(大小、行数、修改时间)。读取大文件前务必先调用此工具。',
319
279
  parameters: GetFileInfoArgsSchema,
320
280
  handler: async (args) => {
321
281
  const validPath = await (0, lib_1.validatePath)(targetDir, args.path);
322
- const info = await (0, lib_1.getFileStats)(validPath);
323
- const text = Object.entries(info)
282
+ const stats = await promises_1.default.stat(validPath);
283
+ // 计算行数:读取内容并按换行符分割
284
+ // 注意:对于极大的文件,这种方式可能稍慢,但对普通源代码文件非常有效
285
+ const content = await promises_1.default.readFile(validPath, 'utf-8');
286
+ const lineCount = content.split('\n').length;
287
+ const info = {
288
+ size: `${(stats.size / 1024).toFixed(2)} KB`,
289
+ lineCount: lineCount, // 新增行号字段
290
+ mtime: stats.mtime.toLocaleString(),
291
+ type: path_1.default.extname(validPath) || 'unknown'
292
+ };
293
+ return Object.entries(info)
324
294
  .map(([key, value]) => `${key}: ${value}`)
325
295
  .join('\n');
326
- return text;
327
296
  },
328
297
  });
329
298
  const grepSearchTool = (0, createTool_1.createTool)({
330
299
  name: 'grep_search',
331
- description: 'Search for a specific keyword in the content of files in a specified directory. ' +
332
- 'This tool will recursively read files and return the paths of all files containing the keyword. ' +
333
- 'Useful for finding variable definitions, function calls, or specific text.',
300
+ description: '在指定目录的文件内容中搜索关键字。' +
301
+ '该工具会返回包含关键字的文件路径及匹配行的预览。' +
302
+ '请尽量通过 includePattern 缩小搜索范围。',
334
303
  parameters: GrepSearchArgsSchema,
335
304
  handler: async (args) => {
336
305
  const startPath = await (0, lib_1.validatePath)(targetDir, args.path);
337
- // 1. 利用你现有的工具先筛选出文件列表
338
- const allFiles = await (0, lib_1.searchFilesWithValidation)(targetDir, startPath, args.includePattern, [targetDir], { excludePatterns: ['node_modules', 'dist', '.git'] });
306
+ const allFiles = await (0, lib_1.searchFilesWithValidation)(targetDir, startPath, args.includePattern, [targetDir], { excludePatterns: ['node_modules', 'dist', '.git', 'build'] });
307
+ // 限制扫描文件数,防止爆炸
308
+ const filesToScan = allFiles.slice(0, args.maxFiles);
339
309
  const matches = [];
340
- // 2. 并发读取文件内容进行关键词匹配 (限制并发数为 10,防止内存或句柄爆炸)
341
310
  const concurrencyLimit = 10;
342
- for (let i = 0; i < allFiles.length; i += concurrencyLimit) {
343
- const chunk = allFiles.slice(i, i + concurrencyLimit);
311
+ for (let i = 0; i < filesToScan.length; i += concurrencyLimit) {
312
+ const chunk = filesToScan.slice(i, i + concurrencyLimit);
344
313
  await Promise.all(chunk.map(async (filePath) => {
345
314
  try {
346
- // 注意:这里确保只搜索文件,不搜索目录
347
315
  const stats = await promises_1.default.stat(filePath);
348
316
  if (!stats.isFile())
349
317
  return;
350
318
  const content = await (0, lib_1.readFileContent)(filePath);
351
319
  if (content.includes(args.query)) {
352
- // 返回相对路径,方便查看
353
- matches.push(path_1.default.relative(targetDir, filePath));
320
+ const relativePath = path_1.default.relative(targetDir, filePath);
321
+ // 找到匹配的那一行(预览用)
322
+ const lines = content.split('\n');
323
+ const matchLineIndex = lines.findIndex(l => l.includes(args.query));
324
+ matches.push(`${relativePath} (Line ${matchLineIndex + 1}: "${lines[matchLineIndex].trim().substring(0, 100)}")`);
354
325
  }
355
326
  }
356
- catch {
357
- // 忽略读取失败的文件(如二进制文件或无权限文件)
358
- }
327
+ catch { /* 忽略错误文件 */ }
359
328
  }));
360
329
  }
361
- return matches.length > 0
362
- ? `Found keyword "${args.query}" in the following files:\n${matches.join('\n')}`
363
- : `No content containing "${args.query}" found in the specified range.`;
330
+ let response = matches.length > 0
331
+ ? `找到关键词 "${args.query}" 的位置如下:\n${matches.join('\n')}`
332
+ : `未找到包含 "${args.query}" 的内容。`;
333
+ if (allFiles.length > args.maxFiles) {
334
+ response += `\n\n注意:搜索已达到限制,仅扫描了前 ${args.maxFiles} 个文件。若未找到结果,请提供更精确的 path 或 includePattern。`;
335
+ }
336
+ return response;
337
+ },
338
+ });
339
+ const readFileRangeTool = (0, createTool_1.createTool)({
340
+ name: 'read_file_range',
341
+ description: '精准读取指定行范围(包含行号前缀)。修改代码前或根据报错定位时必用。',
342
+ parameters: zod_1.z.object({
343
+ path: zod_1.z.string().describe('相对于目标目录的文件路径'),
344
+ startLine: zod_1.z.number().describe('起始行号(从 1 开始计)'),
345
+ endLine: zod_1.z.number().describe('结束行号'),
346
+ }),
347
+ handler: async (args) => {
348
+ // 1. 验证路径安全(沿用你代码中的 validatePath 逻辑)
349
+ const validPath = await (0, lib_1.validatePath)(targetDir, args.path);
350
+ try {
351
+ const content = await promises_1.default.readFile(validPath, 'utf-8');
352
+ const lines = content.split('\n');
353
+ const totalLines = lines.length;
354
+ // 2. 边界保护:确保行号不越界
355
+ const start = Math.max(1, args.startLine);
356
+ const end = Math.min(totalLines, args.endLine);
357
+ if (start > totalLines) {
358
+ return `错误:文件仅有 ${totalLines} 行,起始行号 ${start} 超出范围。`;
359
+ }
360
+ if (start > end) {
361
+ return `错误:起始行号 ${start} 不能大于结束行号 ${end}。`;
362
+ }
363
+ // 3. 截取并添加行号索引(核心:增强 AI 的位置感)
364
+ const selectedLines = lines.slice(start - 1, end);
365
+ const formattedContent = selectedLines
366
+ .map((line, index) => `${start + index}| ${line}`)
367
+ .join('\n');
368
+ return `[文件: ${args.path} | 第 ${start} 至 ${end} 行 / 共 ${totalLines} 行]\n${formattedContent}`;
369
+ }
370
+ catch (error) {
371
+ return `读取文件范围失败: ${error.message}`;
372
+ }
373
+ },
374
+ });
375
+ const editFileTool = (0, createTool_1.createTool)({
376
+ name: 'edit_file',
377
+ description: '基于行号范围替换代码。修改逻辑的唯一工具。调用前须通过 read_file_range 获取最新行号。支持删除(空内容)或单行替换。',
378
+ parameters: PatchEditArgsSchema,
379
+ handler: async (args) => {
380
+ const validPath = await (0, lib_1.validatePath)(targetDir, args.path);
381
+ try {
382
+ const content = await promises_1.default.readFile(validPath, 'utf-8');
383
+ let lines = content.split('\n');
384
+ // 按起始行号从大到小排序,这样修改前面的行不会影响后面待修改行的索引
385
+ const sortedPatches = [...args.patches].sort((a, b) => b.startLine - a.startLine);
386
+ for (const patch of sortedPatches) {
387
+ // 校验行号合法性
388
+ if (patch.startLine < 1 || patch.endLine > lines.length || patch.startLine > patch.endLine) {
389
+ return `错误:行号范围 ${patch.startLine}-${patch.endLine} 超出文件实际范围 (1-${lines.length})`;
390
+ }
391
+ // 可选:二次校验(防止 AI 记忆了错误的行号)
392
+ if (patch.originalSnippet) {
393
+ const currentText = lines.slice(patch.startLine - 1, patch.endLine).join('\n');
394
+ // 模糊对比,如果差异太大则报错
395
+ if (!currentText.includes(patch.originalSnippet.trim()) && currentText.trim().length > 0) {
396
+ return `警告:第 ${patch.startLine} 行的内容已发生变动,与你预想的代码不符。请重新读取文件获取最新行号。`;
397
+ }
398
+ }
399
+ // 执行替换:splice(开始索引, 删除数量, 替换内容)
400
+ // 索引需要减 1
401
+ lines.splice(patch.startLine - 1, (patch.endLine - patch.startLine) + 1, patch.replacement);
402
+ }
403
+ await promises_1.default.writeFile(validPath, lines.join('\n'), 'utf-8');
404
+ return `成功通过行号更新了 ${args.path} 的 ${args.patches.length} 处代码。`;
405
+ }
406
+ catch (error) {
407
+ return `Patch 失败: ${error.message}`;
408
+ }
364
409
  },
365
410
  });
366
411
  return [
412
+ readFileRangeTool,
413
+ editFileTool,
414
+ directoryTreeTool,
415
+ listDirectoryWithSizesTool,
416
+ grepSearchTool,
417
+ getFileInfoTool,
367
418
  readTextFileTool,
368
419
  readMultipleFilesTool,
420
+ searchFilesTool,
369
421
  writeFileTool,
370
- editFileTool,
371
422
  createDirectoryTool,
372
- listDirectoryTool,
373
- listDirectoryWithSizesTool,
374
- directoryTreeTool,
375
423
  moveFileTool,
376
- searchFilesTool,
377
- getFileInfoTool,
378
- grepSearchTool,
379
424
  ];
380
425
  };
381
426
  exports.getFilesystemTools = getFilesystemTools;
@@ -8,8 +8,19 @@ const getTsLspTools = (targetDir) => {
8
8
  const engine = new ts_context_mcp_1.PromptEngine(targetDir);
9
9
  return [
10
10
  (0, createTool_1.createTool)({
11
- name: "get_repo_map",
12
- description: "获取项目全局文件结构及导出清单,用于快速定位代码",
11
+ name: 'get_method_body',
12
+ description: '【仅限TS/JS】通过方法名提取代码块。比行号读取更抗干扰,参考逻辑时首选。',
13
+ parameters: zod_1.z.object({
14
+ filePath: zod_1.z.string().describe('文件相对路径'),
15
+ methodName: zod_1.z.string().describe('方法名或函数名'),
16
+ }),
17
+ handler: async ({ filePath, methodName }) => {
18
+ return engine.getMethodImplementation(filePath, methodName);
19
+ },
20
+ }),
21
+ (0, createTool_1.createTool)({
22
+ name: 'get_repo_map',
23
+ description: '获取项目全局文件结构及导出清单,用于快速定位代码',
13
24
  parameters: zod_1.z.object({}),
14
25
  handler: async () => {
15
26
  engine.refresh();
@@ -17,18 +28,18 @@ const getTsLspTools = (targetDir) => {
17
28
  },
18
29
  }),
19
30
  (0, createTool_1.createTool)({
20
- name: "analyze_deps",
21
- description: "分析指定文件的依赖关系,支持 tsconfig 路径别名解析",
22
- parameters: zod_1.z.object({ filePath: zod_1.z.string().describe("文件相对路径") }),
31
+ name: 'analyze_deps',
32
+ description: '分析指定文件的依赖关系,支持 tsconfig 路径别名解析',
33
+ parameters: zod_1.z.object({ filePath: zod_1.z.string().describe('文件相对路径') }),
23
34
  handler: async ({ filePath }) => engine.getDeps(filePath),
24
35
  }),
25
36
  (0, createTool_1.createTool)({
26
- name: "read_skeleton",
27
- description: "提取文件的结构定义(接口、类、方法签名),忽略具体实现以节省 Token",
28
- parameters: zod_1.z.object({ filePath: zod_1.z.string().describe("文件相对路径") }),
37
+ name: 'read_skeleton',
38
+ description: '提取文件的结构定义(接口、类、方法签名),忽略具体实现以节省 Token',
39
+ parameters: zod_1.z.object({ filePath: zod_1.z.string().describe('文件相对路径') }),
29
40
  handler: async (args) => {
30
41
  const pathArg = args === null || args === void 0 ? void 0 : args.filePath;
31
- if (typeof pathArg !== "string" || pathArg.trim() === "") {
42
+ if (typeof pathArg !== 'string' || pathArg.trim() === '') {
32
43
  return `Error: 参数 'filePath' 无效。收到的是: ${JSON.stringify(pathArg)}`;
33
44
  }
34
45
  try {
@@ -41,14 +52,6 @@ const getTsLspTools = (targetDir) => {
41
52
  }
42
53
  },
43
54
  }),
44
- (0, createTool_1.createTool)({
45
- name: "get_method_body",
46
- description: "获取指定文件内某个方法或函数的完整实现代码",
47
- parameters: zod_1.z.object({ filePath: zod_1.z.string().describe("文件相对路径"), methodName: zod_1.z.string().describe("方法名或函数名") }),
48
- handler: async ({ filePath, methodName }) => {
49
- return engine.getMethodImplementation(filePath, methodName);
50
- },
51
- }),
52
55
  ];
53
56
  };
54
57
  exports.getTsLspTools = getTsLspTools;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saber2pr/ai-agent",
3
- "version": "0.0.44",
3
+ "version": "0.0.46",
4
4
  "description": "AI Assistant CLI.",
5
5
  "author": "saber2pr",
6
6
  "license": "ISC",