# Step 10: 源码对照

一句话导读:Phase 1 收官不写新功能,而是用一段脚本真的去读 claude-code-src/ 的源文件、数它们的行数,把我们 9 步的骨架摆在真实血肉旁边——用「规模差」验证一个假设:核心交互模式我们已经写对了,剩下的都是在骨架上填肌肉。


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

step10 是一个纯「对照复盘」步骤。它做了三件事:

  1. 真读源码数行数:脚本用 readFileSync 打开真实的 QueryEngine.ts / Tool.ts / cli.tsx 等文件,split('\n').length 数出真实行数——不是凭记忆写的数字;
  2. 抽真实骨架片段:用 range() / excerpt() 截取真实源码的类定义、类型定义片段打印出来,和我们的实现并列;
  3. 画功能对照表:对话循环、工具系统、UI、权限、System Prompt、MCP、多代理各维度,列「我们的实现」vs「真实源码」。

它是 Phase 1 的「阶段验收」,也为 Phase 2 起了个头(下一步开始实现 Tool 系统)。


# 二、面试官视角:为什么要做?(Why)

面试题:都能自己写一个 mini 版了,为什么还要专门花一步去对照真实源码?直接继续往下写不行吗?

因为「自己能跑起来」和「按真实架构在演进」是两回事。定期做源码对照有三个不可替代的价值:

目的 不做对照 做源码对照
校准方向 容易越写越像自己的玩具,偏离真实设计 每个 Phase 确认「抽象是否和真实同构」
量化差距 只有模糊的「还差很多」 精确到行数/文件数,知道差在规模还是设计
区分「省略」与「缺陷」 分不清哪些是刻意简化、哪些是设计错了 明确标注「未实现(Phase X)」vs「实现错了」
建立信心与路线图 不知道自己覆盖了多少 确认核心已覆盖,后续 40 步是填充

一句话:源码对照是「阶段性对表」。学习一个大型系统最怕闭门造车,越写越像自己臆想的版本。每隔几步和真实源码对一次表,能及时发现「我的抽象和真实是不是同构」——如果同构,只是规模小,那方向就对了。

# 关键结论:核心是同构的,差的是规模

对照下来最重要的发现是——真实 QueryEngine 的核心逻辑和我们一样:都是配 API 客户端、发消息、处理流式响应、维护历史队列。差别不在「思路」,而在「规模和生产级特性」:它有完整的 Token 预算、对话压缩、错误恢复、并发限制。这个结论直接决定了后续路线:我们已经把最核心的交互模式写对了,剩下 40 步是在这个正确骨架上填肌肉。


# 三、原理:它是怎么工作的(How,这里指「对照脚本怎么工作」)

# 不背数字,而是让脚本真去读文件

这一步最讲究的地方是:所有行数都是运行时数出来的,不是手写的。脚本定义了几个读源码的小工具:

const SRC = 'D:/牛马/源码学习/claude-code-src/src';

function read(path: string): string {
  return readFileSync(SRC + '/' + path, 'utf-8');
}
function lines(path: string): number {
  return read(path).split('\n').length;   // ← 真实行数,现场数
}
function range(path, start, end): string {
  return read(path).split('\n').slice(start - 1, end).join('\n');
}
1
2
3
4
5
6
7
8
9
10
11

然后用它数真实文件规模:

const files = [
  ['QueryEngine.ts',      '对话引擎', lines('QueryEngine.ts')],
  ['Tool.ts',             '工具接口', lines('Tool.ts')],
  ['tools.ts',            '工具注册', lines('tools.ts')],
  ['main.tsx',            '主入口',   lines('main.tsx')],
  ['entrypoints/cli.tsx', 'CLI 入口', lines('entrypoints/cli.tsx')],
];
1
2
3
4
5
6
7

这样做的意义:对照是可复现、可信的。源码更新了,重跑一次数字自动变,不会像手写数字那样过期骗人。

# 抽真实骨架,眼见为实

脚本还用 range() 把真实 QueryEngine.ts 第 130-200 行的类定义骨架捞出来打印,让你亲眼看到「真实的 QueryEngine 也就是个 class + constructor + async run」,祛魅它的神秘感。

# 数据流

step10 脚本启动
   ↓
readFileSync 真实 claude-code-src/src/*.ts
   ↓
lines() 数行数 → 打印规模对比表
range() 抽片段 → 打印真实类/类型骨架
   ↓
手写功能对照表(对话循环/工具/UI/权限/...)
   ↓
输出「Phase 1 覆盖了什么 + Phase 2 预告」
1
2
3
4
5
6
7
8
9
10

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

Q:规模对比里我们 ~200 行 vs 真实 QueryEngine ~1234 行,这 1000 行差距主要是什么? A:不是「核心逻辑」的差距,而是生产级特性:Token 预算精确控制、上下文自动压缩、错误恢复与重试、并发/速率限制、流式事件的精细处理、边界情况兜底。核心的「发消息—收流—维护历史」我们和它一样,那 1000 行是把这个核心包裹成生产可用的健壮系统。

Q:为什么行数要现场数,不直接写死在文章里? A:写死的数字会过期且不可信。源码是活的,版本一升行数就变。用脚本 readFileSync + split 现场数,保证任何时候重跑得到的都是当前真实值——这也是「忠于源码、不编造」的工程化保证。

Q:对照表里很多格子写「无 / 未实现」,这是承认失败吗? A:不是。这些是刻意的、有序的省略,并标注了将在哪个 Phase 补上(如工具系统 Phase 2、权限、UI、MCP、多代理各有其步)。区分「暂未实现的路线图项」和「实现错了的缺陷」正是对照的价值——前者是计划,后者才是问题,而这一步没发现后者。

Q:既然核心同构,后面 40 步是不是就没技术含量了? A:恰恰相反。骨架好写,把骨架变成生产级系统的那些「肌肉」——工具系统的权限沙箱、UI 的 React+Ink 渲染、MCP 的插件协议、多代理的上下文隔离——才是真正的工程难点。step10 的结论是「地基对了」,不是「盖楼很简单」。


# 五、踩坑 / 设计权衡

  • 对照脚本硬编码了源码绝对路径 D:/牛马/源码学习/claude-code-src/src:这是学习项目的权宜,换机器就得改。生产工具会用相对路径或配置项,但对一次性对照脚本,简单直接优先。
  • 功能对照表是手写的:规模数字能自动数,但「我们的实现 vs 真实设计」的语义对照需要人的理解,无法脚本化。这也提醒:自动化能测「量」,测不了「架构是否同构」这种需要判断的「质」。

# 六、与真实源码的对照

我们的实现(Phase 1,10 步) Claude Code 源码
对话循环 while + streamAI ~20 行 QueryEngine.ts ~1234 行
工具系统 Tool 接口(仅预留) Tool.ts ~754 行 + tools/ 53 个工具
API 调用 streamAI() ~30 行 services/api/ 20 个文件
终端 UI console.log React + Ink 406 个组件文件
权限系统:无 utils/permissions/ 24 个文件
System Prompt:4 行字符串 constants/system.ts ~500 行
插件/MCP:无 services/mcp/ 23 个文件
多代理:无 coordinator/ + AgentTool/

# 七、一句话总结

step10 = 阶段性对表:用脚本真读真数得出「核心同构、规模有差」的结论——我们 10 步已把 REPL 循环、流式输出、消息历史、System Prompt、持久化这套最核心的交互模式写对了,后续 40 步不是推倒重来,而是在这个正确骨架上填工具、权限、UI、插件、多代理的肌肉。

# 下一节预告

Phase 2 开始。step11 动手实现 Tool 系统——定义 ToolDef / ToolResult / ToolRegistry 三大抽象,这是把「对话」升级成「能动手干活」的分水岭。