Date: 2026-01-21
在当前的 src/at.ts 实现中,当用户使用 @目录 语法时,系统会递归遍历该目录下的所有文件,并将所有文件的内容注入到 prompt 中。这种行为会导致:
- 性能问题: 大目录会导致大量文件被读取,消耗时间和资源
- token 浪费: 用户可能并不需要目录下的所有文件内容
- 用户体验差: 用户无法预期会有多少内容被注入
用户希望当使用 @目录 时,系统完全不处理该路径,保持静默(不添加任何内容,也不显示警告)。
Q1: 当用户使用 @目录 时,希望如何处理?
- ✅ 选择: 保持现状不做任何处理(完全忽略)
- ❌ 只显示目录结构树,不包含任何文件内容
- ❌ 显示目录结构树,但允许用户手动选择要读取的文件
- ❌ 只读取目录下的第一层文件,不递归子目录
- ❌ 根据文件类型或规则智能过滤
Q2: 当识别到 @ 后面是目录时,具体应该怎么做?
- ✅ 选择: 完全忽略
@目录,不添加任何内容 - ❌ 显示警告提示,告知用户目录不支持
- ❌ 显示目录结构树,但不读取文件内容
在 extractAtPaths() 阶段就检测并过滤掉目录路径,只保留文件路径。
优点:
- 性能最优,避免后续不必要的目录遍历和文件系统操作
- 代码改动最小,逻辑最清晰
- 对用户完全透明,不会有任何输出
缺点:
- 用户可能不知道为什么
@目录没有效果(静默失败)
复杂度: 低
在 getContent() 方法中分类文件和目录时,直接丢弃目录分支,不调用 getAllFilesInDirectory()。
优点:
- 逻辑清晰,在分类阶段就明确处理策略
- 仍然保持较好的性能
缺点:
- 相比方案1有额外的文件系统检查(
statSync) - 代码改动稍多一些
在识别到目录时,返回一个友好的提示信息,告知用户目录不被支持。
优点:
- 用户体验更好,明确知道为什么没有内容
- 可以提供使用建议
缺点:
- 会在 prompt 中添加内容(虽然是提示信息)
- 与"完全不处理"的需求不完全一致
采用方案 1: 早期过滤策略。
核心思路:
在 extractAtPaths() 方法中,当解析完 @路径 后,立即进行文件系统检查。如果检测到路径指向目录,则直接跳过该路径,不将其加入返回的 AtPath[] 数组。
数据流变化:
当前流程:
用户输入 @目录
→ extractAtPaths 提取所有路径
→ getContent 分类文件/目录
→ 目录被遍历,所有文件被注入
新流程:
用户输入 @目录
→ extractAtPaths 提取路径时检测到是目录
→ 直接跳过,不加入返回数组
→ getContent 收到空数组或只有文件的数组
→ 目录完全不被处理
预期效果:
@文件继续正常工作@目录被完全忽略,不产生任何输出- 混合使用时(如
@file1 @dir1 @file2),只处理文件部分
核心修改位置: src/at.ts 中的 extractAtPaths() 方法
-
在解析路径后立即添加目录检查:
- 将相对路径转换为绝对路径:
path.resolve(this.cwd, filePath) - 检查路径是否存在:
fs.existsSync(absolutePath) - 如果存在,检查是否为目录:
fs.statSync(absolutePath).isDirectory() - 如果是目录,执行
continue跳过本次循环
- 将相对路径转换为绝对路径:
-
代码插入位置:
- 在
extractAtPaths()方法的 while 循环体内 - 在解析完
filePath和lineRange之后 - 在将路径添加到
pathsMap之前
- 在
-
伪代码示例:
// 在 extractAtPaths() 方法中
while (match !== null) {
// ... 解析 filePath 和 lineRange ...
// 🆕 添加目录检查
try {
const absolutePath = path.resolve(this.cwd, filePath);
if (fs.existsSync(absolutePath) && fs.statSync(absolutePath).isDirectory()) {
match = regex.exec(prompt);
continue; // 跳过目录
}
} catch {
// 权限问题等异常,保持原逻辑,让后续处理
}
// 创建 key 并添加到 pathsMap
const key = `${filePath}:${lineRange ? ... : 'all'}`;
pathsMap.set(key, { path: filePath, lineRange });
match = regex.exec(prompt);
}-
文件系统访问异常:
- 当
fs.existsSync()或fs.statSync()因权限问题抛出异常时 - 采用保守策略: 捕获异常后,仍然将路径加入
pathsMap - 理由: 让后续的
getContent()方法处理,保持错误处理的一致性
- 当
-
符号链接处理:
fs.statSync()会跟随符号链接,返回目标的状态- 如果符号链接指向目录,会被正确过滤
- 如果符号链接损坏,会抛出异常,按上述策略处理
| 场景 | 当前行为 | 新行为 | 说明 |
|---|---|---|---|
@src/ |
遍历所有文件并注入 | 完全忽略 | ✅ 符合需求 |
@src/index.ts |
读取文件内容 | 读取文件内容 | ✅ 保持不变 |
@不存在的路径 |
后续报错 | 后续报错 | ✅ 保持不变 |
@src/:10-20 |
目录+行号(无意义) | 完全忽略 | ✅ 合理忽略 |
@file1 @dir1 @file2 |
3个都处理 | 只处理 file1 和 file2 | ✅ 符合需求 |
- 增加的开销: 每个
@路径会增加 1-2 次文件系统调用 - 减少的开销: 避免了目录遍历,尤其是大目录时性能提升显著
- 净影响: 对于纯文件场景,性能影响可忽略不计;对于包含目录的场景,性能显著提升
-
基础功能测试:
- ✅
@文件路径仍然正常工作 - ✅
@目录路径被完全忽略 - ✅
@文件:10-20带行号的文件正常工作 - ✅
@目录:10带行号的目录被忽略
- ✅
-
混合场景测试:
输入: "@src/index.ts @src/ @README.md" 预期: 只注入 index.ts 和 README.md 的内容,src/ 被忽略 -
边界测试:
- 空目录的处理
- 符号链接指向目录的处理
- 没有权限访问的路径处理
- 不存在的路径(保持原有错误提示)
-
回归测试:
- 确保原有的文件处理逻辑完全不受影响
- 确保
renderFilesToXml()方法仍然正常工作 - 确保行号范围功能不受影响
- 修改文件: 仅
src/at.ts - 修改方法: 仅
extractAtPaths()方法 - 代码行数: 约 5-10 行
- 向后兼容: 完全兼容,所有现有文件处理功能不受影响
- 不需要修改的部分:
getContent()方法getAllFilesInDirectory()方法(将不会被调用)renderDirectoriesToTree()方法(将不会被调用)renderFilesToXml()方法