Claude Code 查询引擎源码解读
Claude Code 查询引擎源码解读
概述
Claude Code 的查询引擎是整个系统的核心,负责处理用户输入、执行工具调用、管理对话上下文,并与 Claude API 进行流式交互。本文档深入分析 /mnt/e/code/cc/claude-code-main 项目中查询引擎的实现。
1. 文件/目录查询接口
1.1 GlobTool (src/tools/GlobTool/GlobTool.ts)
GlobTool 提供了按通配符模式查找文件的能力。
核心特性:
- 输入:glob 模式字符串和可选路径
- 输出:匹配的文件路径数组、耗时、截断标志
- 结果限制:默认最多 100 个结果(可通过
globLimits.maxResults配置)
实现细节:
async call(input, { abortController, getAppState, globLimits }) {
const start = Date.now()
const limit = globLimits?.maxResults ?? 100
const { files, truncated } = await glob(
input.pattern,
GlobTool.getPath(input),
{ limit, offset: 0 },
abortController.signal,
appState.toolPermissionContext,
)
const filenames = files.map(toRelativePath)
return {
data: {
filenames,
durationMs: Date.now() - start,
numFiles: filenames.length,
truncated,
}
}
}权限检查:
- 验证目标路径存在且为目录
- 检查 UNC 路径安全(防止 NTLM 凭据泄露)
- 使用
checkReadPermissionForTool进行权限验证
1.2 Glob 实现 (src/utils/glob.ts)
底层使用 ripgrep 的 --files 模式实现高性能文件查找。
关键设计:
export async function glob(
filePattern: string,
cwd: string,
{ limit, offset }: { limit: number; offset: number },
abortSignal: AbortSignal,
toolPermissionContext: ToolPermissionContext,
): Promise<{ files: string[]; truncated: boolean }>实现策略:
- 提取 glob 模式的基础目录(静态前缀)
- 使用 ripgrep
--files --glob <pattern>列出匹配文件 - 按修改时间排序(
--sort=modified) - 支持忽略模式和隐藏文件
环境变量控制:
CLAUDE_CODE_GLOB_NO_IGNORE- 是否遵守 .gitignore(默认 true)CLAUDE_CODE_GLOB_HIDDEN- 是否包含隐藏文件(默认 true)
ripgrep 参数构建:
const args = [
'--files',
'--glob',
searchPattern,
'--sort=modified',
...(noIgnore ? ['--no-ignore'] : []),
...(hidden ? ['--hidden'] : []),
]1.3 模糊文件索引 (src/native-ts/file-index/index.ts)
FileIndex 类提供了高性能的模糊文件搜索,使用纯 TypeScript 实现了 nucleo(fzf 底层引擎)的算法。
核心特性:
- 模糊匹配:支持智能大小写、边界匹配、驼峰匹配
- 异步构建:大型索引(27 万+文件)分块构建,每 4ms 让出事件循环
- Top-K 优化:维护有序结果数组,避免全量排序
评分语义(分数越低越好):
const SCORE_MATCH = 16 // 每个匹配字符的基础分数
const BONUS_BOUNDARY = 8 // 匹配在边界(/ \ - _ . 空格)上
const BONUS_CAMEL = 6 // 驼峰匹配
const BONUS_CONSECUTIVE = 4 // 连续匹配
const BONUS_FIRST_CHAR = 8 // 首字符匹配
const PENALTY_GAP_START = 3 // 间隔惩罚起点
const PENALTY_GAP_EXTENSION = 1 // 间隔惩罚延伸
评分公式:
let score = nLen * SCORE_MATCH + consecBonus - gapPenalty
score += scoreBonusAt(path, posBuf[0]!, true) // 首字符/边界/驼峰加分
score += Math.max(0, 32 - (hLen >> 2)) // 短路径奖励
// test 文件惩罚(1.05x,上限 1.0)
const finalScore = path.includes('test')
? Math.min(positionScore * 1.05, 1.0)
: positionScore优化技术:
- O(1) 位图拒绝:预计算每个路径包含的字符位图,快速排除不包含所有查询字符的路径
- SIMD 加速的 indexOf:JSC/V8 引擎的 indexOf 经过 SIMD 优化
- 提前拒绝:gap 惩罚计算后,若最佳情况也可能无法超过阈值则跳过边界计算
- Top-K 维护:维护 size=limit 的有序数组,O(n) 而不是 O(n log n)
- 异步索引构建:每 256 次迭代检查时间,超过 4ms 则让出事件循环
位图拒绝算法:
// 预计算:每个路径的小写字母位图
private indexPath(i: number): void {
const lp = this.paths[i]!.toLowerCase()
let bits = 0
for (let j = 0; j < lp.length; j++) {
const c = lp.charCodeAt(j)
if (c >= 97 && c <= 122) bits |= 1 << (c - 97)
}
this.charBits[i] = bits
}
// 查询时 O(1) 检查:路径必须包含查询中所有字母
if ((charBits[i]! & needleBitmap) !== needleBitmap) continue2. Grep 搜索实现
2.1 GrepTool (src/tools/GrepTool/GrepTool.ts)
GrepTool 是 Claude Code 中最常用的内容搜索工具。
输入模式:
| 模式 | 说明 |
|---|---|
content | 显示匹配行(支持 -A/-B/-C 上下文) |
files_with_matches | 仅显示文件名(默认) |
count | 显示每个文件的匹配数 |
核心参数:
interface Input {
pattern: string // 正则表达式模式
path?: string // 搜索路径(默认 cwd)
glob?: string // 文件类型过滤
output_mode?: 'content' | 'files_with_matches' | 'count'
'-B'?: number // 匹配前上下文行数
'-A'?: number // 匹配后上下文行数
'-C'?: number // 前后上下文
'-n'?: boolean // 显示行号(默认 true)
'-i'?: boolean // 大小写不敏感
type?: string // 文件类型(js/py/rust 等)
head_limit?: number // 结果限制(默认 250)
offset?: number // 跳过前 N 个结果
multiline?: boolean // 多行模式
}默认排除的 VCS 目录:
const VCS_DIRECTORIES_TO_EXCLUDE = ['.git', '.svn', '.hg', '.bzr', '.jj', '.sl']结果限制处理:
function applyHeadLimit<T>(
items: T[],
limit: number | undefined,
offset: number = 0,
): { items: T[]; appliedLimit: number | undefined } {
if (limit === 0) {
return { items: items.slice(offset), appliedLimit: undefined }
}
const effectiveLimit = limit ?? DEFAULT_HEAD_LIMIT // 250
const sliced = items.slice(offset, offset + effectiveLimit)
const wasTruncated = items.length - offset > effectiveLimit
return {
items: sliced,
appliedLimit: wasTruncated ? effectiveLimit : undefined,
}
}2.2 Ripgrep 封装 (src/utils/ripgrep.ts)
Claude Code 使用 ripgrep 作为底层搜索引擎,提供三种运行模式:
模式选择:
type RipgrepConfig = {
mode: 'system' | 'builtin' | 'embedded'
command: string
args: string[]
argv0?: string
}- system:使用系统安装的 ripgrep(通过
findExecutable查找) - builtin:使用 vendored 的 ripgrep 二进制
- embedded:在 Bun 运行时中内嵌 ripgrep
安全性考虑:
// SECURITY: 使用 'rg' 而非完整路径,防止 PATH 劫持
return { mode: 'system', command: 'rg', args: [] }健壮性处理:
- EAGAIN 重试:资源受限环境(Docker/CI)中单线程重试
if (!isRetry && isEagainError(stderr)) { ripGrepRaw(args, target, abortSignal, callback, true) // -j 1 } - 超时处理:WSL 60s,其他平台 20s;SIGTERM → SIGKILL 升级(5秒后)
- 部分结果返回:超时/缓冲区溢出时返回已获取的结果
- 关键错误抛出:ENOENT/EACCES/EPERM 抛出错误而非静默返回空
- macOS 代码签名:对 builtin ripgrep 二进制进行代码签名和隔离移除
超时处理(SIGKILL 升级):
const timeoutId = setTimeout(() => {
if (process.platform === 'win32') {
child.kill()
} else {
child.kill('SIGTERM')
killTimeoutId = setTimeout(c => c.kill('SIGKILL'), 5_000, child)
}
}, timeout)流式输出支持:
export async function ripGrepStream(
args: string[],
target: string,
abortSignal: AbortSignal,
onLines: (lines: string[]) => void,
): Promise<void>2.3 结果处理与排序
文件匹配模式排序(files_with_matches):
// 按修改时间降序排序,文件名升序作为 tiebreaker
const sortedMatches = results
.map((_, i) => {
const r = stats[i]!
return [_, r.status === 'fulfilled' ? (r.value.mtimeMs ?? 0) : 0] as const
})
.sort((a, b) => {
const timeComparison = b[1] - a[1]
if (timeComparison === 0) {
return a[0].localeCompare(b[0])
}
return timeComparison
})内容模式(content): 无排序,结果即为 ripgrep 输出顺序
计数模式(count): 解析 filename:count 格式,计算总匹配数和文件数
3. 语义查询能力
3.1 语义搜索 (src/utils/agenticSessionSearch.ts)
Claude Code 提供了基于 AI 的语义会话搜索功能。
搜索流程:
- 预过滤:在元数据字段中查找查询词
- 填充转录本:加载完整日志(lite log → full log)
- AI 排序:使用小模型对会话进行语义排序
系统提示词策略(优先级顺序):
1. 精确标签匹配(最高优先级 - 用户明确分类)
2. 部分标签匹配或标签相关词
3. 标题匹配
4. 分支名匹配
5. 摘要和转录本内容匹配
6. 语义相似度和相关概念限制参数:
const MAX_TRANSCRIPT_CHARS = 2000 // 每会话最大转录本字符
const MAX_MESSAGES_TO_SCAN = 100 // 扫描的最大消息数
const MAX_SESSIONS_TO_SEARCH = 100 // 发送给 API 的最大会话数
AI 排序调用:
const response = await sideQuery({
model: getSmallFastModel(),
system: SESSION_SEARCH_SYSTEM_PROMPT,
messages: [{ role: 'user', content: userMessage }],
})3.2 搜索文本提取 (src/utils/transcriptSearch.ts)
可搜索内容类型:
- 用户消息:文本内容和工具结果
- 助手消息:文本块和工具调用输入
- 附件:relevant_memories、queued_command
- 折叠组:相关内存内容
弱缓存机制:
const searchTextCache = new WeakMap<RenderableMessage, string>()
// 消息是不可变的,缓存永远有效
3.3 工具搜索 (src/utils/toolSearch.ts)
ToolSearch 功能支持 MCP 工具的动态加载。
模式:
tst:始终启用工具搜索tst-auto:仅当工具描述超过阈值时启用standard:禁用,所有工具内联暴露
自动阈值:
const DEFAULT_AUTO_TOOL_SEARCH_PERCENTAGE = 10 // 上下文窗口的 10%
4. 索引与缓存
4.1 文件状态缓存 (src/utils/fileStateCache.ts)
FileStateCache 基于 LRU-Cache 实现,缓存文件读取结果。
核心结构:
export type FileState = {
content: string
timestamp: number
offset: number | undefined
limit: number | undefined
isPartialView?: boolean // 部分视图标志(如 MEMORY.md 注入)
}配置参数:
const READ_FILE_STATE_CACHE_SIZE = 100 // 默认最大条目数
const DEFAULT_MAX_CACHE_SIZE_BYTES = 25 * 1024 * 1024 // 25MB
路径规范化:
get(key: string): FileState | undefined {
return this.cache.get(normalize(key)) // 统一使用 path.normalize
}LRU 缓存配置:
this.cache = new LRUCache<string, FileState>({
max: maxEntries,
maxSize: maxSizeBytes,
sizeCalculation: value => Math.max(1, Buffer.byteLength(value.content)),
})4.2 折叠读取/搜索 (src/utils/collapseReadSearch.ts)
将连续的读取/搜索操作折叠成摘要组,减少上下文膨胀。
折叠规则:
- 连续的 Grep/Glob/Read 操作合并
- 遇到助手文本时打断折叠
- 遇到写/编辑操作时打断折叠
- 内存文件操作单独追踪
分组累积器(GroupAccumulator):
type GroupAccumulator = {
messages: CollapsibleMessage[]
searchCount: number
readFilePaths: Set<string>
readOperationCount: number // 无文件路径的读取操作计数
listCount: number // 目录列表操作计数
toolUseIds: Set<string>
memorySearchCount: number
memoryReadFilePaths: Set<string>
memoryWriteCount: number
mcpCallCount?: number
mcpServerNames?: Set<string>
bashCount?: number
bashCommands?: Map<string, string> // 用于扫描 Git 操作
commits?: { sha: string; kind: CommitKind }[]
pushes?: { branch: string }[]
branches?: { ref: string; action: BranchAction }[]
prs?: { number: number; url?: string; action: PrAction }[]
relevantMemories?: { path: string; content: string; mtimeMs: number }[]
}折叠算法核心:
export function collapseReadSearchGroups(
messages: RenderableMessage[],
tools: Tools,
): RenderableMessage[] {
// 遍历消息,根据类型决定是加入当前组还是打断折叠
// - CollapsibleToolUse: 加入当前组
// - TextBreaker (助手文本): 打断折叠
// - NonCollapsibleToolUse (Write/Edit): 打断折叠
}4.3 上下文压缩 (src/services/compact/compact.ts)
上下文压缩系统通过 summarization 减少历史消息长度。
压缩类型:
microcompact:轻微折叠,保持细节autocompact:自动压缩,当上下文接近限制时触发reactive compact:响应式压缩,处理 413 错误恢复
5. 查询结果排序与过滤
5.1 Grep 结果排序
文件匹配模式(files_with_matches):
// 排序策略:修改时间降序,文件名升序作为 tiebreaker
.sort((a, b) => {
const timeComparison = b[1] - a[1]
if (timeComparison === 0) {
return a[0].localeCompare(b[0])
}
return timeComparison
})内容模式: 无排序,结果即为 ripgrep 输出顺序
计数模式: 无排序,汇总统计
5.2 Glob 结果排序
// ripgrep --sort=modified 按修改时间排序(最新优先)
args.push('--sort=modified')5.3 模糊搜索排序 (src/native-ts/file-index/index.ts)
Top-K 维护实现:
const topK: { path: string; fuzzScore: number }[] = []
let threshold = -Infinity
// 分数计算(越低越好)
let score = nLen * SCORE_MATCH + consecBonus - gapPenalty
score += scoreBonusAt(path, posBuf[0]!, true)
score += Math.max(0, 32 - (hLen >> 2))
// test 文件惩罚
const finalScore = path.includes('test')
? Math.min(positionScore * 1.05, 1.0)
: positionScore5.4 会话搜索排序 (src/utils/agenticSessionSearch.ts)
预过滤阶段:
function logContainsQuery(log: LogOption, queryLower: string): boolean {
// 检查 title、customTitle、tag、branch、summary、firstPrompt、transcript
}AI 排序阶段:
const response = await sideQuery({
model: getSmallFastModel(),
system: SESSION_SEARCH_SYSTEM_PROMPT,
messages: [{ role: 'user', content: userMessage }],
})6. 查询引擎核心流程
6.1 QueryEngine (src/QueryEngine.ts)
主要职责:
- 管理对话生命周期
- 处理用户消息输入
- 调用 query() 生成器
- 处理 SDK 消息规范化
- 管理会话持久化
核心方法:
export class QueryEngine {
async *submitMessage(
prompt: string | ContentBlockParam[],
options?: { uuid?: string; isMeta?: boolean },
): AsyncGenerator<SDKMessage, void, unknown>
}6.2 Query 循环 (src/query.ts)
查询循环状态:
type State = {
messages: Message[]
toolUseContext: ToolUseContext
autoCompactTracking: AutoCompactTrackingState | undefined
maxOutputTokensRecoveryCount: number
turnCount: number
transition: Continue | undefined
}查询循环流程(简化版):
while (true) {
1. 前处理
- 内存预取
- Snip 压缩
- 微压缩
- 上下文折叠
- 自动压缩检查
2. API 调用
- 创建流式工具执行器
- 调用模型(支持 fallback)
- 处理流式响应
3. 工具执行
- 处理 tool_use 块
- 执行工具
- 处理工具结果
4. 错误恢复
- max_output_tokens 恢复
- 上下文溢出恢复
- 媒体大小错误恢复
5. 后处理
- 停止钩子
- 工具使用摘要
}关键配置参数:
const MAX_OUTPUT_TOKENS_RECOVERY_LIMIT = 3
const MAX_OUTPUT_TOKENS_RECOVERY_LIMIT = 36.3 查询分析 (src/utils/queryProfiler.ts)
性能分析工具,追踪查询管道各阶段耗时。
检查点:
query_user_input_received // 用户输入开始
query_fn_entry // query() 函数入口
query_microcompact_start/end // 微压缩
query_autocompact_start/end // 自动压缩
query_setup_start/end // 流式工具执行器设置
query_api_streaming_start/end // API 流式调用
query_tool_execution_start/end // 工具执行
启用方式:
CLAUDE_CODE_PROFILE_QUERY=17. 关键文件索引
| 文件 | 职责 |
|---|---|
src/query.ts | 查询循环核心实现(1729 行) |
src/QueryEngine.ts | QueryEngine 类和 ask() 函数 |
src/tools/GrepTool/GrepTool.ts | Grep 工具实现(577 行) |
src/tools/GlobTool/GlobTool.ts | Glob 工具实现(198 行) |
src/utils/ripgrep.ts | Ripgrep 封装和配置(679 行) |
src/utils/glob.ts | Glob 底层实现(130 行) |
src/native-ts/file-index/index.ts | 模糊文件索引(370 行) |
src/utils/fileStateCache.ts | 文件状态缓存(142 行) |
src/utils/collapseReadSearch.ts | 读取/搜索折叠(1109 行) |
src/utils/agenticSessionSearch.ts | AI 会话搜索(307 行) |
src/utils/toolSearch.ts | 工具动态加载 |
src/utils/queryProfiler.ts | 查询性能分析 |
8. 设计亮点
8.1 高性能
- Ripgrep 优先:所有文件搜索委托给 ripgrep,利用其高度优化的 C 实现
- 位图索引:O(1) 复杂字符集检查,89-90% 拒绝率
- Top-K 维护:避免全量排序,O(n) 而不是 O(n log n)
- 异步索引构建:事件循环让出(每 4ms 检查),避免 UI 阻塞
- SIMD 加速:利用 JSC/V8 的原生 indexOf 优化
8.2 可靠性
- 超时兜底:部分结果返回,而非无限等待
- EAGAIN 重试:资源受限环境适应,单线程模式
- 信号处理:正确的 SIGTERM → SIGKILL 升级(5秒宽限期)
- macOS 代码签名:自动签名 vendored ripgrep 二进制
8.3 上下文管理
- 多层压缩:microcompact → collapse → autocompact
- 折叠组:减少连续搜索的上下文开销
- 内存追踪:区分普通文件和内存文件操作
- Team Memory:独立的团队内存操作计数
8.4 安全性
- 路径规范化:防止缓存污染(path.normalize)
- UNC 路径检查:防止 NTLM 凭据泄露
- PATH 劫持防护:使用命令名而非完整路径
- 权限上下文:工具权限检查与文件系统权限分离
8.5 智能排序
- 修改时间优先:最新文件优先展示
- test 文件惩罚:1.05x 惩罚降低测试文件排名
- 短路径奖励:路径越短评分越高
- 边界/驼峰加分:匹配在有意义的边界位置获得额外分数
9. 与 OpenCode CLI 的关系
重要说明:/mnt/d/fe/opencode_cli 是 OpenCode Server 的 Go 语言命令行客户端(项目名 oho),本身不实现查询引擎。它通过 REST API 调用服务器端能力:
| CLI 命令 | API 端点 |
|---|---|
find text | GET /find |
find file | GET /find/file |
find symbol | GET /find/symbol |
本地实现文件:
/mnt/d/fe/opencode_cli/oho/cmd/find/find.go- Find 命令入口/mnt/d/fe/opencode_cli/oho/internal/client/client.go- HTTP 客户端/mnt/d/fe/opencode_cli/oho/internal/types/types.go- 数据类型
本文档描述的查询引擎属于 Claude Code 源码项目(/mnt/e/code/cc/claude-code-main),是实际实现搜索、索引、缓存逻辑的服务端组件。