Skip to content

Latest commit

 

History

History
204 lines (153 loc) · 6.78 KB

File metadata and controls

204 lines (153 loc) · 6.78 KB

忽略目录 @ 引用

Date: 2026-01-21

Context

在当前的 src/at.ts 实现中,当用户使用 @目录 语法时,系统会递归遍历该目录下的所有文件,并将所有文件的内容注入到 prompt 中。这种行为会导致:

  1. 性能问题: 大目录会导致大量文件被读取,消耗时间和资源
  2. token 浪费: 用户可能并不需要目录下的所有文件内容
  3. 用户体验差: 用户无法预期会有多少内容被注入

用户希望当使用 @目录 时,系统完全不处理该路径,保持静默(不添加任何内容,也不显示警告)。

Discussion

关键问题与决策

Q1: 当用户使用 @目录 时,希望如何处理?

  • 选择: 保持现状不做任何处理(完全忽略)
  • ❌ 只显示目录结构树,不包含任何文件内容
  • ❌ 显示目录结构树,但允许用户手动选择要读取的文件
  • ❌ 只读取目录下的第一层文件,不递归子目录
  • ❌ 根据文件类型或规则智能过滤

Q2: 当识别到 @ 后面是目录时,具体应该怎么做?

  • 选择: 完全忽略 @目录,不添加任何内容
  • ❌ 显示警告提示,告知用户目录不支持
  • ❌ 显示目录结构树,但不读取文件内容

探索的方案

方案 1: 早期过滤 (✅ 最终选择)

extractAtPaths() 阶段就检测并过滤掉目录路径,只保留文件路径。

优点:

  • 性能最优,避免后续不必要的目录遍历和文件系统操作
  • 代码改动最小,逻辑最清晰
  • 对用户完全透明,不会有任何输出

缺点:

  • 用户可能不知道为什么 @目录 没有效果(静默失败)

复杂度:

方案 2: 中期过滤

getContent() 方法中分类文件和目录时,直接丢弃目录分支,不调用 getAllFilesInDirectory()

优点:

  • 逻辑清晰,在分类阶段就明确处理策略
  • 仍然保持较好的性能

缺点:

  • 相比方案1有额外的文件系统检查(statSync)
  • 代码改动稍多一些

方案 3: 添加验证提示

在识别到目录时,返回一个友好的提示信息,告知用户目录不被支持。

优点:

  • 用户体验更好,明确知道为什么没有内容
  • 可以提供使用建议

缺点:

  • 会在 prompt 中添加内容(虽然是提示信息)
  • 与"完全不处理"的需求不完全一致

Approach

采用方案 1: 早期过滤策略。

核心思路:extractAtPaths() 方法中,当解析完 @路径 后,立即进行文件系统检查。如果检测到路径指向目录,则直接跳过该路径,不将其加入返回的 AtPath[] 数组。

数据流变化:

当前流程:
用户输入 @目录 
→ extractAtPaths 提取所有路径
→ getContent 分类文件/目录
→ 目录被遍历,所有文件被注入

新流程:
用户输入 @目录
→ extractAtPaths 提取路径时检测到是目录
→ 直接跳过,不加入返回数组
→ getContent 收到空数组或只有文件的数组
→ 目录完全不被处理

预期效果:

  • @文件 继续正常工作
  • @目录 被完全忽略,不产生任何输出
  • 混合使用时(如 @file1 @dir1 @file2),只处理文件部分

Architecture

修改点

核心修改位置: src/at.ts 中的 extractAtPaths() 方法

实现细节

  1. 在解析路径后立即添加目录检查:

    • 将相对路径转换为绝对路径: path.resolve(this.cwd, filePath)
    • 检查路径是否存在: fs.existsSync(absolutePath)
    • 如果存在,检查是否为目录: fs.statSync(absolutePath).isDirectory()
    • 如果是目录,执行 continue 跳过本次循环
  2. 代码插入位置:

    • extractAtPaths() 方法的 while 循环体内
    • 在解析完 filePathlineRange 之后
    • 在将路径添加到 pathsMap 之前
  3. 伪代码示例:

// 在 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);
}

错误处理

  1. 文件系统访问异常:

    • fs.existsSync()fs.statSync() 因权限问题抛出异常时
    • 采用保守策略: 捕获异常后,仍然将路径加入 pathsMap
    • 理由: 让后续的 getContent() 方法处理,保持错误处理的一致性
  2. 符号链接处理:

    • fs.statSync() 会跟随符号链接,返回目标的状态
    • 如果符号链接指向目录,会被正确过滤
    • 如果符号链接损坏,会抛出异常,按上述策略处理

边界场景

场景 当前行为 新行为 说明
@src/ 遍历所有文件并注入 完全忽略 ✅ 符合需求
@src/index.ts 读取文件内容 读取文件内容 ✅ 保持不变
@不存在的路径 后续报错 后续报错 ✅ 保持不变
@src/:10-20 目录+行号(无意义) 完全忽略 ✅ 合理忽略
@file1 @dir1 @file2 3个都处理 只处理 file1 和 file2 ✅ 符合需求

性能影响

  • 增加的开销: 每个 @路径 会增加 1-2 次文件系统调用
  • 减少的开销: 避免了目录遍历,尤其是大目录时性能提升显著
  • 净影响: 对于纯文件场景,性能影响可忽略不计;对于包含目录的场景,性能显著提升

测试策略

  1. 基础功能测试:

    • @文件路径 仍然正常工作
    • @目录路径 被完全忽略
    • @文件:10-20 带行号的文件正常工作
    • @目录:10 带行号的目录被忽略
  2. 混合场景测试:

    输入: "@src/index.ts @src/ @README.md"
    预期: 只注入 index.ts 和 README.md 的内容,src/ 被忽略
    
  3. 边界测试:

    • 空目录的处理
    • 符号链接指向目录的处理
    • 没有权限访问的路径处理
    • 不存在的路径(保持原有错误提示)
  4. 回归测试:

    • 确保原有的文件处理逻辑完全不受影响
    • 确保 renderFilesToXml() 方法仍然正常工作
    • 确保行号范围功能不受影响

影响范围

  • 修改文件: 仅 src/at.ts
  • 修改方法: 仅 extractAtPaths() 方法
  • 代码行数: 约 5-10 行
  • 向后兼容: 完全兼容,所有现有文件处理功能不受影响
  • 不需要修改的部分:
    • getContent() 方法
    • getAllFilesInDirectory() 方法(将不会被调用)
    • renderDirectoriesToTree() 方法(将不会被调用)
    • renderFilesToXml() 方法