# Step 28: QueryEngine 源码对照

一句话导读:Phase 3 收官,不写一行新代码,而是翻开真实的 claude-code-src/src/,把我们 11–27 步做出的 17 个功能模块逐项对回源码——量化「简化版」覆盖了真身的多少(约 40%),并看清剩下的 60% 到底缺在哪。


# 一、这一步做了什么(What)

这是一次对照式复盘,产出三样东西:

  1. 一张 17 模块的功能对照表:每个功能 → 对应 Step → 真实源码文件/行数 → 我们的实现方式;
  2. 一组量化结论:真实 QueryEngine.ts 1296 行、我们实现 17 模块、覆盖约 40%、最大缺口在终端 UI/多代理/MCP;
  3. 一份「未实现清单」+ 优先级,作为后续 Phase 的路线图。

它不增加任何功能,价值全在「把学到的东西锚回真实坐标系」。


# 二、面试官视角:为什么要专门做一次源码对照?(Why)

面试题:功能都跑通了,为什么还要停下来花一整步做「源码对照」这种不产出代码的事?

因为**「能跑」和「懂真身」之间隔着一整个工程量的鸿沟,不对照就不知道自己站在哪**。

不做对照 做一次对照
以为「我实现了 Bash 工具」= 会了 发现真实 Bash 是 18 文件(沙箱/超时/流式/持久化),自己只是一句 execSync
学习悬空,不知深浅 每个模块都能指回真实文件,知道简化在哪、为什么简化
不知道下一步该学啥 缺口清单 + 优先级 = 现成的学习路线图
容易高估自己 用 40% 这个数字校准认知,保持敬畏

核心动机有三:一是校准认知——功能名相同不代表工程量相同,execSync 和 18 文件的 BashTool 差一个数量级,对照能戳破「我会了」的幻觉;二是让学习不悬空——简化版最大的价值在于「每个模块都能映射到真实源码的具体文件」,映射一断,学的东西就飘着;三是规划下一步——把没实现的东西列清、排好优先级,Phase 4/5/6/7 该干什么一目了然。这一步本质是一次元认知练习:知道自己知道多少,比多知道一点更重要。


# 三、原理:对照是怎么做的(How)

# 17 模块对照表(节选真实数据)

功能 Step 真实源码 行数/文件 我们的实现
Tool 接口 11 Tool.ts 793 行 接口定义
ToolRegistry 11 tools.ts 390 行 注册管理
Bash 工具 12 tools/BashTool/ 18 文件 execSync
Read/Write/Glob/Grep 13–16 各自 tool 目录 3–6 文件 fs + fast-glob
Tool Calling 17 QueryEngine.ts 1296 行 核心循环
权限询问 19 utils/permissions/ 24 文件 y/n 提示
对话压缩 24 services/compact/ 15 文件 字符串拼接
模型切换 25 utils/model/ 16 文件 3 种模型
输出格式 27 utils/styles.ts ~20 行 3 种模式

最刺眼的一行是 Bash:功能名一样,我们一句 execSync,真实版 18 个文件——里面是沙箱隔离、超时控制、流式输出、shell 会话持久化、跨平台差异。这就是「40% 覆盖」的真实质感:核心循环我们抓到了,但每个工具的健壮性外壳几乎都省掉了。

# 为什么覆盖率恰好是「值得的 40%」

真实 QueryEngine.ts = 1296 行
        ├── 我们复刻的 ~40%  = 工具调用循环 + 权限 + 压缩 + 成本 + 模型切换
        │                     ↑ 这 40% = 「一个能跑的 headless mini Claude Code」的全部核心
        └── 缺的 ~60%        = 终端 UI + 多代理 + MCP/插件 + 安全沙箱 + 各种健壮性
                              ↑ 这 60% 多是「健壮性 + 生态」,不是「能不能跑」
1
2
3
4
5

这是本步最重要的洞察:核心循环很小,外围很重。让 Agent「能对话、能调工具、能自我修复」的主干只占约 40%;剩下 60% 是把它从「demo」变成「产品」的工程——UI 体验、多代理协调、插件生态、安全沙箱。40% 不多,但它是那个「跑得起来的最小完整核心」。

# 缺口清单与优先级(真实规划)

未实现功能 优先级 规划阶段
Ink 终端 UI 高 Phase 7
多代理协调 高 Phase 4
MCP 集成 高 Phase 5
插件系统 中 Phase 5
安全沙箱 中 Phase 6

这张表不是随手写的,它就是后续 Phase 的路线图:Phase 4 补多代理、Phase 5 补 MCP/插件、Phase 6 补沙箱、Phase 7 补 UI。

# 状态字段的对应关系

真实 QueryEngine 类字段         我们散在 index.ts 的变量
mutableMessages[]     ←→        messages[]         (step17)
totalUsage            ←→        CostTracker        (step23)
permissionDenials     ←→        checkPerm          (step19)
readFileState         ←→        ReadTool 状态       (step13)
1
2
3
4
5

对照还暴露一个结构性差距:真实引擎把这些状态收进一个类的字段集中管理,我们却散在 index.ts 的一堆局部变量里。这直接指明了下一阶段(Phase 3.5)的方向——把循环抽成 QueryEngine 类,让状态归位。


# 四、深入追问(面试常见 follow-up)

Q:「覆盖 40%」这个数字是怎么算出来的,靠谱吗? A:它是功能模块维度的粗略估计(17 个我们做了的 / 真实核心模块总数),不是代码行数比。按行数算我们连 5% 都不到(自己几百行 vs 真实几万行)。40% 想表达的是「核心功能类别覆盖了近一半」,是个用于校准认知的量级参考,不是精确指标——面试里要说清它的口径。

Q:既然真实版每个工具都比我们复杂一个数量级,学简化版还有意义吗? A:意义恰恰最大。真实 BashTool 的 18 个文件里,17 个是健壮性外壳(沙箱/超时/流式/跨平台),只有 1 个是「调用 shell」的主干。简化版专门保留那个主干、砍掉外壳,让你先看清「一个工具本质在做什么」。理解了骨架,再看真实版的血肉才不会迷失。先骨架后血肉,是读大型源码的正确姓势。

Q:为什么把「多代理、MCP、UI」这些留到后面,而不是这一步就补上? A:因为它们都建立在「一个能跑的单引擎」之上——多代理是 fork 多个引擎、MCP 是给引擎接外部工具、UI 是给引擎套界面。没有稳固的核心循环,这些都是空中楼阁。Phase 3 先把 40% 的核心夯实,正是为了让后续 60% 有地基可依。这也是这次对照排优先级的依据。

Q:对照发现「状态散在 index.ts」,为什么这是个问题,非改不可吗? A:不是不能跑,而是不可维护、不可复用。状态散落意味着无法「new 一个新引擎」——而这正是子代理(后面 step38)的前提。真实版用类字段集中状态,才能一行 new QueryEngine() 开一个独立子代理。所以这次对照发现的「状态归位」需求,其实是为几十步之后的多代理能力埋的伏笔。


# 五、踩坑 / 设计权衡

  • 对照的度量陷阱:用「模块数」还是「代码行」算覆盖率,结论差 10 倍。复盘时必须说清口径,否则「40%」会误导——它是功能类别的覆盖,不是完成度。
  • 简化的边界要诚实:对照的价值在于诚实标注「我们省了什么」。若把 execSync 说成「实现了 Bash 工具」而不提缺失的沙箱/超时,就是自欺。好的复盘要主动暴露简化。
  • 路线图 vs 过度规划:缺口清单给了方向,但优先级会随认知变化(表里 UI 一度标 Phase 4,后调整到 Phase 7)。规划是活的,不是刻死的。

# 六、与真实源码的对照

我们做的(Phase 3 全貌) Claude Code 源码
17 个功能模块,约 40% 覆盖 完整 QueryEngine.ts 1296 行 + 数十个支撑目录
核心循环(工具调用/权限/压缩/成本/模型) 同样的核心,外加健壮性与生态
状态散在 index.ts 局部变量 集中在 QueryEngine 类字段(我们 Phase 3.5 重构)
缺终端 UI/多代理/MCP/沙箱 均已实现,对应我们 Phase 4–7 路线

# 七、一句话总结

源码对照 = 给学习装一次 GPS:不加功能,只把 17 个模块锚回真实源码坐标,用「约 40% 覆盖、核心循环小外围重、状态待归位」三个结论校准认知——它戳破「我会了」的幻觉、把学习钉在真实坐标系上、并生成后续 Phase 的路线图,是 Phase 3 最有价值的「非代码」一步。

# 下一节预告

复盘暴露了一个真实的坑:/compact 压缩若在工具调用对中间切断,会留下「孤儿 tool_result」导致 API 报 400。step29 就来修这个边界安全问题。