目录

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 }>

实现策略:

  1. 提取 glob 模式的基础目录(静态前缀)
  2. 使用 ripgrep --files --glob <pattern> 列出匹配文件
  3. 按修改时间排序(--sort=modified
  4. 支持忽略模式和隐藏文件

环境变量控制:

  • 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

优化技术:

  1. O(1) 位图拒绝:预计算每个路径包含的字符位图,快速排除不包含所有查询字符的路径
  2. SIMD 加速的 indexOf:JSC/V8 引擎的 indexOf 经过 SIMD 优化
  3. 提前拒绝:gap 惩罚计算后,若最佳情况也可能无法超过阈值则跳过边界计算
  4. Top-K 维护:维护 size=limit 的有序数组,O(n) 而不是 O(n log n)
  5. 异步索引构建:每 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) continue

2. 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
}
  1. system:使用系统安装的 ripgrep(通过 findExecutable 查找)
  2. builtin:使用 vendored 的 ripgrep 二进制
  3. embedded:在 Bun 运行时中内嵌 ripgrep

安全性考虑:

// SECURITY: 使用 'rg' 而非完整路径,防止 PATH 劫持
return { mode: 'system', command: 'rg', args: [] }

健壮性处理:

  1. EAGAIN 重试:资源受限环境(Docker/CI)中单线程重试
    if (!isRetry && isEagainError(stderr)) {
      ripGrepRaw(args, target, abortSignal, callback, true)  // -j 1
    }
  2. 超时处理:WSL 60s,其他平台 20s;SIGTERM → SIGKILL 升级(5秒后)
  3. 部分结果返回:超时/缓冲区溢出时返回已获取的结果
  4. 关键错误抛出:ENOENT/EACCES/EPERM 抛出错误而非静默返回空
  5. 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 的语义会话搜索功能。

搜索流程:

  1. 预过滤:在元数据字段中查找查询词
  2. 填充转录本:加载完整日志(lite log → full log)
  3. 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)

可搜索内容类型:

  1. 用户消息:文本内容和工具结果
  2. 助手消息:文本块和工具调用输入
  3. 附件:relevant_memories、queued_command
  4. 折叠组:相关内存内容

弱缓存机制:

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)

将连续的读取/搜索操作折叠成摘要组,减少上下文膨胀。

折叠规则:

  1. 连续的 Grep/Glob/Read 操作合并
  2. 遇到助手文本时打断折叠
  3. 遇到写/编辑操作时打断折叠
  4. 内存文件操作单独追踪

分组累积器(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)
  : positionScore

5.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)

主要职责:

  1. 管理对话生命周期
  2. 处理用户消息输入
  3. 调用 query() 生成器
  4. 处理 SDK 消息规范化
  5. 管理会话持久化

核心方法:

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 = 3

6.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=1

7. 关键文件索引

文件职责
src/query.ts查询循环核心实现(1729 行)
src/QueryEngine.tsQueryEngine 类和 ask() 函数
src/tools/GrepTool/GrepTool.tsGrep 工具实现(577 行)
src/tools/GlobTool/GlobTool.tsGlob 工具实现(198 行)
src/utils/ripgrep.tsRipgrep 封装和配置(679 行)
src/utils/glob.tsGlob 底层实现(130 行)
src/native-ts/file-index/index.ts模糊文件索引(370 行)
src/utils/fileStateCache.ts文件状态缓存(142 行)
src/utils/collapseReadSearch.ts读取/搜索折叠(1109 行)
src/utils/agenticSessionSearch.tsAI 会话搜索(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 textGET /find
find fileGET /find/file
find symbolGET /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),是实际实现搜索、索引、缓存逻辑的服务端组件。