9. 搜索工具 Grep 与 Glob所属分组工具系统概述GrepTool 与 GlobTool 是 Claude Code 的两个搜索工具——前者基于 ripgrep 做内容搜索后者基于rg --files --glob做文件名匹配。两者在 prompt 设计上各自侧重GrepTool 详细说明 ripgrep 语法与三种 output modeGlobTool 则强调按修改时间排序与开放式搜索用 AgentTool。在 UI 层面两者高度复用——GlobTool 直接 re-export GrepTool 的renderToolResultMessage因为它们共享 SearchResultSummary 组件。底层utils/ripgrep.ts是一个完整的 ripgrep 进程管理器它处理 system/builtin/embedded 三种 rg 模式、EAGAIN 重试、超时 SIGKILL 升级、流式输出等。utils/glob.ts则把rg --files --glob包装成简洁的glob()函数。本文从 prompt、UI、底层 utils 三个层面剖析这对搜索双胞胎。源码位置[tools/GrepTool/prompt.ts](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/tools/GrepTool/prompt.ts)[tools/GrepTool/UI.tsx](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/tools/GrepTool/UI.tsx)[tools/GlobTool/prompt.ts](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/tools/GlobTool/prompt.ts)[tools/GlobTool/UI.tsx](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/tools/GlobTool/UI.tsx)[utils/ripgrep.ts](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/utils/ripgrep.ts)[utils/glob.ts](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/utils/glob.ts)核心实现分析1. GrepTool prompt明确NEVER invoke grep or rg as a Bash commandgetDescription()短小但语义密集returnA powerful search tool built on ripgrep Usage: - ALWAYS use${GREP_TOOL_NAME}for search tasks. NEVER invoke \grep\ or \rg\ as a${BASH_TOOL_NAME}command. The${GREP_TOOL_NAME}tool has been optimized for correct permissions and access. - Supports full regex syntax (e.g., log.*Error, function\\s\\w) - Filter files with glob parameter (e.g., *.js, **/*.tsx) or type parameter (e.g., js, py, rust) - Output modes: content shows matching lines, files_with_matches shows only file paths (default), count shows match counts - Use${AGENT_TOOL_NAME}tool for open-ended searches requiring multiple rounds - Pattern syntax: Uses ripgrep (not grep) - literal braces need escaping (use \interface\\{\\}\ to find \interface{}\ in Go code) - Multiline matching: By default patterns match within single lines only. For cross-line patterns like \struct \\{[\\s\\S]*?field\, use \multiline: true\几个值得关注的点① 直接禁止模型用 Bash 调用 grep/rg理由是optimized for correct permissions and access——权限和访问路径已在工具内做了正确处理② 三种 output mode 明确列出content / files_with_matches / count让模型按需选择③ 专门提示 Go 代码中interface{}的转义陷阱ripgrep 字面括号需转义④ 跨行匹配需显式multiline: true避免默认贪婪匹配误伤。2. GlobTool prompt四条短描述GlobTool 的 prompt 极简exportconstDESCRIPTION- Fast file pattern matching tool that works with any codebase size - Supports glob patterns like **/*.js or src/**/*.ts - Returns matching file paths sorted by modification time - Use this tool when you need to find files by name patterns - When you are doing an open ended search that may require multiple rounds of globbing and grepping, use the Agent tool instead第三条sorted by modification time是关键特性——--sortmodified让最近修改的文件排在前这对模型定位最近改过的文件非常有用。第五条把开放式多轮搜索引导到 AgentTool与 GrepTool 形成职责切分。3. SearchResultSummary搜索结果的统一渲染GrepTool UI 中定义的SearchResultSummary组件被三种 output mode 共享functionSearchResultSummary({count,countLabel,secondaryCount,secondaryLabel,content,verbose}){// primary: Found 3 files// secondary: across 5 directories// verbose: 显示 ⎿ 缩进的 content// non-verbose: 单行 CtrlOToExpand 提示}它的精妙之处在于复数处理——当 count 为 1 时把countLabel.slice(0, -1)去掉尾部 ‘s’让显示从1 files变成1 file。secondaryCount/secondaryLabel用于 count 模式Found 5 matches across 3 files。verbose 模式额外渲染⎿缩进的 content非 verbose 模式仅显示一行带 CtrlOToExpand 提示。4. GrepTool 的 renderToolResultMessage 三分支exportfunctionrenderToolResultMessage({modefiles_with_matches,filenames,numFiles,content,numLines,numMatches},_progress,{verbose}){if(modecontent){returnSearchResultSummary count{numLines??0}countLabellinescontent{content}verbose{verbose}/}if(modecount){returnSearchResultSummary count{numMatches??0}countLabelmatchessecondaryCount{numFiles}secondaryLabelfilescontent{content}verbose{verbose}/}// files_with_matches modeconstfileListContentfilenames.map(filenamefilename).join(\n)returnSearchResultSummary count{numFiles}countLabelfilescontent{fileListContent}verbose{verbose}/}content 模式报 lines 数count 模式报 matchesfiles 双重计数files_with_matches 模式报 files 数——三种模式各自选择最有信息量的统计维度。5. GlobTool UI完全复用 GrepTool 渲染GlobTool 的 UI 文件极其精简最有意思的是这一行// Note: GlobTool reuses GrepTools renderToolResultMessageexportconstrenderToolResultMessageGrepTool.renderToolResultMessage;直接 re-export GrepTool 的渲染函数——因为 GlobTool 的输出也走files_with_matches形态一串文件路径完美匹配 GrepTool 的默认分支。userFacingName()返回Search而不是Glob让 UI 上 Glob 与 Grep 显示一致——对用户而言两者都是搜索。6. utils/ripgrep.ts三种 rg 模式getRipgrepConfig()根据运行环境选择三种 ripgrep 实现之一constgetRipgrepConfigmemoize(():RipgrepConfig{constuserWantsSystemRipgrepisEnvDefinedFalsy(process.env.USE_BUILTIN_RIPGREP)if(userWantsSystemRipgrep){const{cmd:systemPath}findExecutable(rg,[])if(systemPath!rg){// SECURITY: Use command name rg instead of systemPath to prevent PATH hijackingreturn{mode:system,command:rg,args:[]}}}if(isInBundledMode()){return{mode:embedded,command:process.execPath,args:[--no-config],argv0:rg}}constrgRootpath.resolve(__dirname,vendor,ripgrep)constcommandprocess.platformwin32?path.resolve(rgRoot,${process.arch}-win32,rg.exe) : path.resolve(rgRoot,${process.arch}-${process.platform},rg)return{mode:builtin,command,args:[]}})注释里有一处安全细节找到系统 rg 后仍用rg字面名而非完整路径防止恶意 ./rg.exe 在当前目录被 PATH 解析命中——OS 的NoDefaultCurrentDirectoryInExePath保护会让rg安全解析。三种模式分别是system用户系统装的 rgUSE_BUILTIN_RIPGREPfalse时启用embeddedbun 打包模式下rg 静态编译进 bun通过argv0rg派生builtinvendored 的预编译 rg 二进制按 arch-platform 分目录存放7. ripGrepRaw 的 SIGTERM→SIGKILL 升级embedded 模式下用spawn而非execFile因为 execFile 不支持 argv0。超时处理做了 SIGTERM→SIGKILL 升级letkillTimeoutId:ReturnTypetypeofsetTimeout|undefinedconsttimeoutIdsetTimeout((){if(process.platformwin32){child.kill()}else{child.kill(SIGTERM)killTimeoutIdsetTimeout(cc.kill(SIGKILL),5_000,child)}},timeout)注释解释ripgrep 在不可中断 I/O深度文件系统遍历状态下SIGTERM 可能被忽略。5 秒后升级到 SIGKILL 才能保证进程结束。Windows 上child.kill(SIGTERM)会抛错所以用默认 signal。8. EAGAIN 重试的单线程降级ripGrep()在处理 EAGAIN 错误时做了一次性单线程重试if(!isRetryisEagainError(stderr)){logForDebugging(rg EAGAIN error detected, retrying with single-threaded mode (-j 1))logEvent(tengu_ripgrep_eagain_retry,{})ripGrepRaw(args,target,abortSignal,(retryError,retryStdout,retryStderr){handleResult(retryError,retryStdout,retryStderr,true)},true)// Force single-threaded mode for this retry onlyreturn}注释强调Persisting single-threaded mode globally caused timeouts on large repos where EAGAIN was just a transient startup error——EAGAIN 是 Docker/CI 资源受限环境的瞬时启动错误全局降级到单线程会让大仓库超时。所以只对当次调用降级。9. RipgrepTimeoutError 与部分结果保留超时时不直接 reject而是判断是否有部分输出if(isTimeoutlines.length0){reject(newRipgrepTimeoutError(Ripgrep search timed out after${getPlatform()wsl?60:20}seconds. The search may have matched files but did not complete in time. Try searching a more specific path or pattern.,lines,))return}resolve(lines)有部分输出时丢弃最后一行可能不完整后返回——让模型至少能看到部分匹配而不是完全失败。WSL 默认 60 秒其他平台 20 秒反映了 WSL2 文件读取 3-5x 性能惩罚。10. ripGrepStream 的流式输出ripGrepStream()是为交互式搜索优化的流式版本exportasyncfunctionripGrepStream(args,target,abortSignal,onLines){// ...letremainderchild.stdout?.on(data,(chunk:Buffer){constdataremainderchunk.toString()constlinesdata.split(\n)remainderlines.pop()??if(lines.length)onLines(lines.map(stripCR))})// ...}注释提到这是 fzf 的change:reload模式——“first results paint while rg is still walking the tree”。remainder跨 chunk 保留半行避免行被切断。AbortSignal 用于提前停止例如用户输入变化触发新搜索。11. countFilesRoundedRg 的隐私化文件计数countFilesRoundedRg()用于遥测把文件数 round 到最近的 10 的幂constmagnitudeMath.floor(Math.log10(count))constpowerMath.pow(10,magnitude)returnMath.round(count/power)*power注释示例“8 → 10, 42 → 100, 350 → 100, 750 → 1000”。这是隐私保护——避免把仓库确切文件数上报。函数还跳过 home 目录计数避免触发 macOS TCC 权限对话框Desktop/Downloads/Documents 等需要用户授权。12. utils/glob.ts把 rg --files 包装成 glob()glob()函数把 ripgrep 的--files模式包装成简洁的文件名匹配 APIconstargs[--files,--glob,searchPattern,--sortmodified,...(noIgnore?[--no-ignore]:[]),...(hidden?[--hidden]:[]),]// Add ignore patternsfor(constpatternofignorePatterns){args.push(--glob,!${pattern})}// Exclude orphaned plugin version directoriesfor(constexclusionofawaitgetGlobExclusionsForPluginCache(searchDir)){args.push(--glob,exclusion)}constallPathsawaitripGrep(args,searchDir,abortSignal)注释说明--no-ignore默认 true不遵守 .gitignore--hidden默认 true包含隐藏文件——这与 BashTool prompt 里用 Glob 而非 find的设计意图一致让模型看到完整文件集避免被 .gitignore 误隐藏。getGlobExclusionsForPluginCache排除孤立插件版本目录是 plugin 系统的卫生措施。13. extractGlobBaseDirectory绝对路径拆分ripgrep 的--glob只接受相对模式所以extractGlobBaseDirectory把绝对路径拆成 baseDir relativePatternexportfunctionextractGlobBaseDirectory(pattern:string){constglobChars/[*?[{]/constmatchpattern.match(globChars)if(!match||match.indexundefined){constdirdirname(pattern)constfilebasename(pattern)return{baseDir:dir,relativePattern:file}}conststaticPrefixpattern.slice(0,match.index)constlastSepIndexMath.max(staticPrefix.lastIndexOf(/),staticPrefix.lastIndexOf(sep))// ...}Windows 下还处理了C:vsC:/的语义差异——C:是驱动器 C 的当前目录相对路径C:/才是驱动器根。这是跨平台兼容性的硬性细节。关键设计要点UI 复用极致化GlobTool 直接 re-export GrepTool 的renderToolResultMessageuserFacingName也统一为Search对用户隐藏工具边界。三种 rg 模式自适应system / embedded / builtin 三种 ripgrep 实现按环境择优security 注释明确防止 PATH 劫持。SIGTERM→SIGKILL 升级超时杀进程时先 SIGTERM5 秒后升级到 SIGKILL应对 ripgrep 在不可中断 I/O 中的杀不掉问题。EAGAIN 一次性单线程重试资源受限环境的瞬时错误用-j 1重试一次但绝不全局降级避免大仓库超时。部分结果保留超时和 buffer overflow 时丢弃可能不完整的最后一行后返回部分结果让模型至少能利用已得信息。ripgrep 隐私化计数countFilesRoundedRg把文件数 round 到 10 的幂跳过 home 目录避免触发 TCC 对话框体现遥测的隐私意识。与其他模块的关系AgentToolGrepTool 和 GlobTool 的 prompt 都把开放式多轮搜索引导到 AgentTool形成精确单次搜索用 Grep/Glob探索性搜索用 Agent的分工。BashToolGrepTool prompt 明确禁止模型用 Bash 调 grep/rg把搜索流量集中到工具系统内。permissions/filesystemgetFileReadIgnorePatterns给 glob 提供 ignore 模式normalizePatternsToPath把它们规范化到 searchDir 下。plugins/orphanedPluginFiltergetGlobExclusionsForPluginCache排除孤立插件版本目录避免搜索结果污染。components/CtrlOToExpand非 verbose 模式下搜索结果展示CtrlO 展开提示与 FileWriteTool 等共享同一交互范式。execFileNoThrowtestRipgrepOnFirstUse、codesignRipgrepIfNecessary都用这个安全包装调用子进程。小结GrepTool 与 GlobTool 是一对职责互补 UI 共享的搜索双胞胎。GrepTool 用详细 prompt 教模型 ripgrep 语法和三种 output modeGlobTool 用极简 prompt 强调按修改时间排序与开放式搜索转 AgentTool。底层utils/ripgrep.ts是一个完整的 rg 进程管理器处理三种实现模式、EAGAIN 重试、SIGTERM→SIGKILL 升级、流式输出、隐私化计数等真实工程问题。utils/glob.ts则把rg --files --glob包装成简洁 API处理绝对路径拆分与跨平台 Windows 驱动器语义。这套搜索基础设施让 Claude Code 在百万行代码库上也能秒级响应模型查询。
09-搜索工具Grep与Glob
发布时间:2026/7/17 1:33:36
9. 搜索工具 Grep 与 Glob所属分组工具系统概述GrepTool 与 GlobTool 是 Claude Code 的两个搜索工具——前者基于 ripgrep 做内容搜索后者基于rg --files --glob做文件名匹配。两者在 prompt 设计上各自侧重GrepTool 详细说明 ripgrep 语法与三种 output modeGlobTool 则强调按修改时间排序与开放式搜索用 AgentTool。在 UI 层面两者高度复用——GlobTool 直接 re-export GrepTool 的renderToolResultMessage因为它们共享 SearchResultSummary 组件。底层utils/ripgrep.ts是一个完整的 ripgrep 进程管理器它处理 system/builtin/embedded 三种 rg 模式、EAGAIN 重试、超时 SIGKILL 升级、流式输出等。utils/glob.ts则把rg --files --glob包装成简洁的glob()函数。本文从 prompt、UI、底层 utils 三个层面剖析这对搜索双胞胎。源码位置[tools/GrepTool/prompt.ts](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/tools/GrepTool/prompt.ts)[tools/GrepTool/UI.tsx](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/tools/GrepTool/UI.tsx)[tools/GlobTool/prompt.ts](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/tools/GlobTool/prompt.ts)[tools/GlobTool/UI.tsx](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/tools/GlobTool/UI.tsx)[utils/ripgrep.ts](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/utils/ripgrep.ts)[utils/glob.ts](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/utils/glob.ts)核心实现分析1. GrepTool prompt明确NEVER invoke grep or rg as a Bash commandgetDescription()短小但语义密集returnA powerful search tool built on ripgrep Usage: - ALWAYS use${GREP_TOOL_NAME}for search tasks. NEVER invoke \grep\ or \rg\ as a${BASH_TOOL_NAME}command. The${GREP_TOOL_NAME}tool has been optimized for correct permissions and access. - Supports full regex syntax (e.g., log.*Error, function\\s\\w) - Filter files with glob parameter (e.g., *.js, **/*.tsx) or type parameter (e.g., js, py, rust) - Output modes: content shows matching lines, files_with_matches shows only file paths (default), count shows match counts - Use${AGENT_TOOL_NAME}tool for open-ended searches requiring multiple rounds - Pattern syntax: Uses ripgrep (not grep) - literal braces need escaping (use \interface\\{\\}\ to find \interface{}\ in Go code) - Multiline matching: By default patterns match within single lines only. For cross-line patterns like \struct \\{[\\s\\S]*?field\, use \multiline: true\几个值得关注的点① 直接禁止模型用 Bash 调用 grep/rg理由是optimized for correct permissions and access——权限和访问路径已在工具内做了正确处理② 三种 output mode 明确列出content / files_with_matches / count让模型按需选择③ 专门提示 Go 代码中interface{}的转义陷阱ripgrep 字面括号需转义④ 跨行匹配需显式multiline: true避免默认贪婪匹配误伤。2. GlobTool prompt四条短描述GlobTool 的 prompt 极简exportconstDESCRIPTION- Fast file pattern matching tool that works with any codebase size - Supports glob patterns like **/*.js or src/**/*.ts - Returns matching file paths sorted by modification time - Use this tool when you need to find files by name patterns - When you are doing an open ended search that may require multiple rounds of globbing and grepping, use the Agent tool instead第三条sorted by modification time是关键特性——--sortmodified让最近修改的文件排在前这对模型定位最近改过的文件非常有用。第五条把开放式多轮搜索引导到 AgentTool与 GrepTool 形成职责切分。3. SearchResultSummary搜索结果的统一渲染GrepTool UI 中定义的SearchResultSummary组件被三种 output mode 共享functionSearchResultSummary({count,countLabel,secondaryCount,secondaryLabel,content,verbose}){// primary: Found 3 files// secondary: across 5 directories// verbose: 显示 ⎿ 缩进的 content// non-verbose: 单行 CtrlOToExpand 提示}它的精妙之处在于复数处理——当 count 为 1 时把countLabel.slice(0, -1)去掉尾部 ‘s’让显示从1 files变成1 file。secondaryCount/secondaryLabel用于 count 模式Found 5 matches across 3 files。verbose 模式额外渲染⎿缩进的 content非 verbose 模式仅显示一行带 CtrlOToExpand 提示。4. GrepTool 的 renderToolResultMessage 三分支exportfunctionrenderToolResultMessage({modefiles_with_matches,filenames,numFiles,content,numLines,numMatches},_progress,{verbose}){if(modecontent){returnSearchResultSummary count{numLines??0}countLabellinescontent{content}verbose{verbose}/}if(modecount){returnSearchResultSummary count{numMatches??0}countLabelmatchessecondaryCount{numFiles}secondaryLabelfilescontent{content}verbose{verbose}/}// files_with_matches modeconstfileListContentfilenames.map(filenamefilename).join(\n)returnSearchResultSummary count{numFiles}countLabelfilescontent{fileListContent}verbose{verbose}/}content 模式报 lines 数count 模式报 matchesfiles 双重计数files_with_matches 模式报 files 数——三种模式各自选择最有信息量的统计维度。5. GlobTool UI完全复用 GrepTool 渲染GlobTool 的 UI 文件极其精简最有意思的是这一行// Note: GlobTool reuses GrepTools renderToolResultMessageexportconstrenderToolResultMessageGrepTool.renderToolResultMessage;直接 re-export GrepTool 的渲染函数——因为 GlobTool 的输出也走files_with_matches形态一串文件路径完美匹配 GrepTool 的默认分支。userFacingName()返回Search而不是Glob让 UI 上 Glob 与 Grep 显示一致——对用户而言两者都是搜索。6. utils/ripgrep.ts三种 rg 模式getRipgrepConfig()根据运行环境选择三种 ripgrep 实现之一constgetRipgrepConfigmemoize(():RipgrepConfig{constuserWantsSystemRipgrepisEnvDefinedFalsy(process.env.USE_BUILTIN_RIPGREP)if(userWantsSystemRipgrep){const{cmd:systemPath}findExecutable(rg,[])if(systemPath!rg){// SECURITY: Use command name rg instead of systemPath to prevent PATH hijackingreturn{mode:system,command:rg,args:[]}}}if(isInBundledMode()){return{mode:embedded,command:process.execPath,args:[--no-config],argv0:rg}}constrgRootpath.resolve(__dirname,vendor,ripgrep)constcommandprocess.platformwin32?path.resolve(rgRoot,${process.arch}-win32,rg.exe) : path.resolve(rgRoot,${process.arch}-${process.platform},rg)return{mode:builtin,command,args:[]}})注释里有一处安全细节找到系统 rg 后仍用rg字面名而非完整路径防止恶意 ./rg.exe 在当前目录被 PATH 解析命中——OS 的NoDefaultCurrentDirectoryInExePath保护会让rg安全解析。三种模式分别是system用户系统装的 rgUSE_BUILTIN_RIPGREPfalse时启用embeddedbun 打包模式下rg 静态编译进 bun通过argv0rg派生builtinvendored 的预编译 rg 二进制按 arch-platform 分目录存放7. ripGrepRaw 的 SIGTERM→SIGKILL 升级embedded 模式下用spawn而非execFile因为 execFile 不支持 argv0。超时处理做了 SIGTERM→SIGKILL 升级letkillTimeoutId:ReturnTypetypeofsetTimeout|undefinedconsttimeoutIdsetTimeout((){if(process.platformwin32){child.kill()}else{child.kill(SIGTERM)killTimeoutIdsetTimeout(cc.kill(SIGKILL),5_000,child)}},timeout)注释解释ripgrep 在不可中断 I/O深度文件系统遍历状态下SIGTERM 可能被忽略。5 秒后升级到 SIGKILL 才能保证进程结束。Windows 上child.kill(SIGTERM)会抛错所以用默认 signal。8. EAGAIN 重试的单线程降级ripGrep()在处理 EAGAIN 错误时做了一次性单线程重试if(!isRetryisEagainError(stderr)){logForDebugging(rg EAGAIN error detected, retrying with single-threaded mode (-j 1))logEvent(tengu_ripgrep_eagain_retry,{})ripGrepRaw(args,target,abortSignal,(retryError,retryStdout,retryStderr){handleResult(retryError,retryStdout,retryStderr,true)},true)// Force single-threaded mode for this retry onlyreturn}注释强调Persisting single-threaded mode globally caused timeouts on large repos where EAGAIN was just a transient startup error——EAGAIN 是 Docker/CI 资源受限环境的瞬时启动错误全局降级到单线程会让大仓库超时。所以只对当次调用降级。9. RipgrepTimeoutError 与部分结果保留超时时不直接 reject而是判断是否有部分输出if(isTimeoutlines.length0){reject(newRipgrepTimeoutError(Ripgrep search timed out after${getPlatform()wsl?60:20}seconds. The search may have matched files but did not complete in time. Try searching a more specific path or pattern.,lines,))return}resolve(lines)有部分输出时丢弃最后一行可能不完整后返回——让模型至少能看到部分匹配而不是完全失败。WSL 默认 60 秒其他平台 20 秒反映了 WSL2 文件读取 3-5x 性能惩罚。10. ripGrepStream 的流式输出ripGrepStream()是为交互式搜索优化的流式版本exportasyncfunctionripGrepStream(args,target,abortSignal,onLines){// ...letremainderchild.stdout?.on(data,(chunk:Buffer){constdataremainderchunk.toString()constlinesdata.split(\n)remainderlines.pop()??if(lines.length)onLines(lines.map(stripCR))})// ...}注释提到这是 fzf 的change:reload模式——“first results paint while rg is still walking the tree”。remainder跨 chunk 保留半行避免行被切断。AbortSignal 用于提前停止例如用户输入变化触发新搜索。11. countFilesRoundedRg 的隐私化文件计数countFilesRoundedRg()用于遥测把文件数 round 到最近的 10 的幂constmagnitudeMath.floor(Math.log10(count))constpowerMath.pow(10,magnitude)returnMath.round(count/power)*power注释示例“8 → 10, 42 → 100, 350 → 100, 750 → 1000”。这是隐私保护——避免把仓库确切文件数上报。函数还跳过 home 目录计数避免触发 macOS TCC 权限对话框Desktop/Downloads/Documents 等需要用户授权。12. utils/glob.ts把 rg --files 包装成 glob()glob()函数把 ripgrep 的--files模式包装成简洁的文件名匹配 APIconstargs[--files,--glob,searchPattern,--sortmodified,...(noIgnore?[--no-ignore]:[]),...(hidden?[--hidden]:[]),]// Add ignore patternsfor(constpatternofignorePatterns){args.push(--glob,!${pattern})}// Exclude orphaned plugin version directoriesfor(constexclusionofawaitgetGlobExclusionsForPluginCache(searchDir)){args.push(--glob,exclusion)}constallPathsawaitripGrep(args,searchDir,abortSignal)注释说明--no-ignore默认 true不遵守 .gitignore--hidden默认 true包含隐藏文件——这与 BashTool prompt 里用 Glob 而非 find的设计意图一致让模型看到完整文件集避免被 .gitignore 误隐藏。getGlobExclusionsForPluginCache排除孤立插件版本目录是 plugin 系统的卫生措施。13. extractGlobBaseDirectory绝对路径拆分ripgrep 的--glob只接受相对模式所以extractGlobBaseDirectory把绝对路径拆成 baseDir relativePatternexportfunctionextractGlobBaseDirectory(pattern:string){constglobChars/[*?[{]/constmatchpattern.match(globChars)if(!match||match.indexundefined){constdirdirname(pattern)constfilebasename(pattern)return{baseDir:dir,relativePattern:file}}conststaticPrefixpattern.slice(0,match.index)constlastSepIndexMath.max(staticPrefix.lastIndexOf(/),staticPrefix.lastIndexOf(sep))// ...}Windows 下还处理了C:vsC:/的语义差异——C:是驱动器 C 的当前目录相对路径C:/才是驱动器根。这是跨平台兼容性的硬性细节。关键设计要点UI 复用极致化GlobTool 直接 re-export GrepTool 的renderToolResultMessageuserFacingName也统一为Search对用户隐藏工具边界。三种 rg 模式自适应system / embedded / builtin 三种 ripgrep 实现按环境择优security 注释明确防止 PATH 劫持。SIGTERM→SIGKILL 升级超时杀进程时先 SIGTERM5 秒后升级到 SIGKILL应对 ripgrep 在不可中断 I/O 中的杀不掉问题。EAGAIN 一次性单线程重试资源受限环境的瞬时错误用-j 1重试一次但绝不全局降级避免大仓库超时。部分结果保留超时和 buffer overflow 时丢弃可能不完整的最后一行后返回部分结果让模型至少能利用已得信息。ripgrep 隐私化计数countFilesRoundedRg把文件数 round 到 10 的幂跳过 home 目录避免触发 TCC 对话框体现遥测的隐私意识。与其他模块的关系AgentToolGrepTool 和 GlobTool 的 prompt 都把开放式多轮搜索引导到 AgentTool形成精确单次搜索用 Grep/Glob探索性搜索用 Agent的分工。BashToolGrepTool prompt 明确禁止模型用 Bash 调 grep/rg把搜索流量集中到工具系统内。permissions/filesystemgetFileReadIgnorePatterns给 glob 提供 ignore 模式normalizePatternsToPath把它们规范化到 searchDir 下。plugins/orphanedPluginFiltergetGlobExclusionsForPluginCache排除孤立插件版本目录避免搜索结果污染。components/CtrlOToExpand非 verbose 模式下搜索结果展示CtrlO 展开提示与 FileWriteTool 等共享同一交互范式。execFileNoThrowtestRipgrepOnFirstUse、codesignRipgrepIfNecessary都用这个安全包装调用子进程。小结GrepTool 与 GlobTool 是一对职责互补 UI 共享的搜索双胞胎。GrepTool 用详细 prompt 教模型 ripgrep 语法和三种 output modeGlobTool 用极简 prompt 强调按修改时间排序与开放式搜索转 AgentTool。底层utils/ripgrep.ts是一个完整的 rg 进程管理器处理三种实现模式、EAGAIN 重试、SIGTERM→SIGKILL 升级、流式输出、隐私化计数等真实工程问题。utils/glob.ts则把rg --files --glob包装成简洁 API处理绝对路径拆分与跨平台 Windows 驱动器语义。这套搜索基础设施让 Claude Code 在百万行代码库上也能秒级响应模型查询。