Skip to content

第 01 章:项目总览与架构设计 ​

学习目标 ​

读完本章,你应该能够:

  1. 用一句话解释 Claude Code 的核心架构——"一个以 AsyncGenerator 管道为骨骼、以 React 终端组件为皮肤的 AI 编程智能体"
  2. 根据功能需求快速定位对应的模块目录,不必在 1884 个源文件里盲目搜索
  3. 描述数据从用户击键到最终渲染的完整流转路径
  4. 理解 Feature Flag(功能标志)在构建时死码消除(Dead Code Elimination)中的作用

1. Claude Code 是什么 ​

Claude Code 是一个运行在终端里的 AI 编程智能体(Autonomous Coding Agent)。它不是简单的命令行包装器,而是能够自主读写文件、执行 bash 命令、调用外部工具、并在一次对话中持续完成复杂编程任务的完整系统。

从用户视角看,你在终端输入自然语言,Claude Code 会理解意图,拆解任务,调用 BashTool 跑测试、调用 FileEditTool 修改代码、调用 AgentTool 派生子智能体并发处理——最终给出结果。这套能力背后是一个精心设计的多层架构,而不是一段线性脚本。

从工程视角看,Claude Code 有几个核心特点值得注意:

CLI 优先的 UI。界面完全跑在终端里,使用了自研的 Ink 框架分支(基于 React),将 React 组件树渲染为 ANSI 转义序列。这意味着你看到的彩色对话框、进度条、权限提示,背后都是 React 组件的 useState 和重新渲染。

AsyncGenerator 驱动的数据流。核心查询循环不是 Promise,而是 AsyncGenerator。每一轮 API 调用、每一次工具执行的中间结果,都以 yield 的形式流出,使得调用方(无论是交互式 REPL 还是无头 SDK)可以在任意时刻订阅、暂停或中止。

Feature Flag 的编译时分叉。源码里大量出现 feature('DAEMON')、feature('BRIDGE_MODE') 这样的调用。这不是普通的运行时开关,feature() 来自 bun:bundle,在 Bun 打包时会被求值,未启用的分支被彻底从产物中裁剪掉——内部版本(Ant 员工用)和外部发布版因此可以共享同一份源码,但生成完全不同的二进制。


2. 技术栈总览 ​

层次技术用途
运行时Bun执行环境、构建工具、bun:bundle Feature Flag
语言TypeScript全量类型化,含 TSX
终端 UIInk(自研分支,96 个文件)React 到终端的渲染器
UI 框架React 19组件模型、Hooks、状态管理
Schema 验证Zod v4工具输入参数的运行时类型检查
CLI 解析Commander.js命令行参数定义与分发
API 客户端@anthropic-ai/sdk与 Claude API 通信
扩展协议MCP(Model Context Protocol)连接第三方工具服务器
语言服务LSP(Language Server Protocol)IDE 级别的代码理解

Bun 在这里不只是 Node.js 的替代者——它的 bun:bundle 模块在编译期暴露 feature() 函数,使得功能分支可以做到编译时死码消除,这在 Node.js 生态里是很罕见的能力。


3. 目录结构逐一讲解 ​

Claude Code 的 src/ 目录下有 35 个模块,以下按文件数量降序排列并逐一说明其职责:

src/
├── utils/           (564 个文件) 工具函数大本营,含 bash/permissions/settings/swarm 等子目录
├── components/      (389 个文件) React 终端 UI 组件库,所有可见界面元素
├── commands/        (189 个文件) 70+ 斜杠命令(/clear、/compact、/commit 等)的实现
├── tools/           (184 个文件) 工具实现:BashTool、FileEditTool、AgentTool 等
├── services/        (130 个文件) API 客户端、MCP 服务、分析上报、LSP 服务等
├── hooks/           (104 个文件) React Hooks,业务逻辑与 UI 状态的桥梁
├── ink/             (96 个文件)  自研 Ink 终端渲染框架分支
├── cli/             (19 个文件)  SSE/WebSocket 传输层、远程 IO 适配
├── constants/       (21 个文件)  常量定义(提示词片段、XML 标签、错误码等)
├── keybindings/     (14 个文件)  自定义快捷键配置与处理
├── tasks/           (12 个文件)  任务管理(TodoList 相关)
├── migrations/      (11 个文件)  配置文件版本迁移
├── entrypoints/     (8 个文件)   入口点:cli.tsx、init.ts、mcp.ts、SDK 入口等
├── memdir/          (8 个文件)   项目记忆文件(MEMORY.md)的读写管理
├── skills/          (20 个文件)  Markdown 驱动的技能(Skill)系统
├── bridge/          (31 个文件)  远程控制桥接,允许从移动端操控本机 Claude
├── context/         (9 个文件)   上下文管理(统计、状态传递等)
├── state/           (6 个文件)   AppState 定义与 Store
├── buddy/           (6 个文件)   终端吉祥物(Companion)相关
├── vim/             (5 个文件)   Vim 模式(Normal/Insert/Visual)
├── query/           (4 个文件)   查询配置、Token 预算等辅助类型
├── remote/          (4 个文件)   远程会话管理
├── native-ts/       (4 个文件)   原生 TypeScript 桥接层
├── server/          (3 个文件)   Unix 域套接字服务端(进程间通信)
├── screens/         (3 个文件)   主屏幕(REPL.tsx)及其他全屏视图
├── plugins/         (2 个文件)   插件系统入口
├── upstreamproxy/   (2 个文件)   上游代理配置
├── coordinator/     (1 个文件)   Coordinator 模式(多智能体协调)
├── bootstrap/       (1 个文件)   全局单例状态(state.ts),进程生命周期内唯一
├── assistant/       (1 个文件)   助手会话历史
├── voice/           (1 个文件)   语音输入支持
├── outputStyles/    (1 个文件)   输出样式配置
├── moreright/       (1 个文件)   "更多内容"右侧栏 UI
├── schemas/         (1 个文件)   共享 Schema 定义
└── 根目录单文件模块         QueryEngine.ts、Task.ts、Tool.ts、tools.ts、
                              query.ts、commands.ts、main.tsx、
                              replLauncher.tsx、context.ts、history.ts 等

有几个目录值得额外说明:

bootstrap/state.ts 是整个进程的全局单例。文件开头有一行注释极具代表性:

typescript
// DO NOT ADD MORE STATE HERE - BE JUDICIOUS WITH GLOBAL STATE

(src/bootstrap/state.ts:31)

这个文件导出 State 类型,里面包含了从 totalCostUSD、sessionId、到各种 beta 功能的 latch 标志共约 60 个字段。任何需要跨组件、跨工具调用的持久化状态,最终都汇集到这里——但作者非常警惕地限制着它的膨胀。

entrypoints/cli.tsx 是真正意义上的进程入口,它是一个快速路径分发器(Fast-Path Dispatcher),会在加载庞大的主程序之前,先检测命令行参数是否命中特殊路径,从而避免不必要的模块加载开销:

typescript
// Fast-path for --version/-v: zero module loading needed
if (args.length === 1 && (args[0] === '--version' || args[0] === '-v')) {
  console.log(`${MACRO.VERSION} (Claude Code)`);
  return;
}

(src/entrypoints/cli.tsx:36-42)

这种设计使得 claude --version 几乎是瞬时响应,因为它根本没有触发 TypeScript 模块的动态 import 链。


4. 架构图:事件驱动的 AsyncGenerator 管道 ​

以下是 Claude Code 的核心运行时架构:

这张图描述的是一次完整的用户问答轮次(Turn)。注意几个关键点:

工具执行结果并不直接返回给用户——它作为 user 类型消息(内含 tool_result 内容块)重新进入 query() 循环,触发下一轮 API 调用。这就是 Claude 能"多步完成任务"的根本原因:它看到工具结果后,可以继续调用更多工具,直到认为任务完成。

QueryEngine(src/QueryEngine.ts)是 SDK 路径和 REPL 路径的共同核心。它的 submitMessage() 方法是一个 AsyncGenerator,外层不管是终端 UI 还是编程 API,都通过 for await 消费它产出的 SDKMessage:

typescript
async *submitMessage(
  prompt: string | ContentBlockParam[],
  options?: { uuid?: string; isMeta?: boolean },
): AsyncGenerator<SDKMessage, void, unknown> {
  // ... 内部调用 query(),yield 每一条消息 ...
}

(src/QueryEngine.ts:209-212)


5. 数据流总览 ​

下面把数据流拆解成六个阶段,逐步说明:

阶段一:用户输入捕获

用户在终端敲字,Ink 框架通过 process.stdin 捕获原始键盘事件,经过 hooks/useTextInput 处理后,触发 REPL 组件的状态更新。

阶段二:输入预处理(processUserInput)

原始输入进入 utils/processUserInput/processUserInput.ts。在这里,斜杠命令(如 /clear、/compact)会被识别并路由到 commands/ 目录下的对应处理函数,普通自然语言则被打包成 UserMessage 传入下一阶段。

阶段三:query 循环

src/query.ts 的 query() 函数是整个系统的核心循环。它负责:

  • 组装系统提示词(System Prompt),包括来自 CLAUDE.md 的用户自定义指令
  • 将消息列表归一化后发送给 Claude API
  • 以 AsyncGenerator 形式逐条 yield API 返回的流式事件
  • 处理自动上下文压缩(Auto Compact)、Token 预算超限等边界情况

阶段四:工具执行

当 API 返回一条包含 tool_use 的 assistant 消息时,query.ts 会找到对应工具(通过 findToolByName),构建 ToolUseContext 并调用 tool.call()。

ToolUseContext 是工具执行环境的完整快照,包含了工具在执行过程中可能需要的一切——AppState 读写函数、AbortController、权限检查回调、UI 更新通道等:

typescript
export type ToolUseContext = {
  options: {
    commands: Command[]
    mainLoopModel: string
    tools: Tools
    mcpClients: MCPServerConnection[]
    // ... 更多选项
  }
  abortController: AbortController
  getAppState(): AppState
  setAppState(f: (prev: AppState) => AppState): void
  setToolJSX?: SetToolJSXFn   // 工具可以渲染自己的 React 界面
  // ...
}

(src/Tool.ts:158-300)

阶段五:结果归集与递归

工具执行结果(ToolResult)被包装成 user 消息,追加到消息列表后,重新进入 query() 循环,触发下一轮 API 调用。这个循环直到以下条件之一满足才结束:Claude 返回 stop_reason: 'end_turn'、Token 预算耗尽、用户主动中断,或者达到 maxTurns 限制。

阶段六:渲染

QueryEngine 的 AsyncGenerator yield 出每一条 SDKMessage,REPL 组件通过 useLogMessages Hook 订阅这些消息,更新 React 状态,触发 Ink 重新渲染终端输出。


6. 关键配置:CLAUDE.md、settings.json 与 Feature Flag ​

CLAUDE.md ​

CLAUDE.md 是用户为项目定制 Claude 行为的主要手段。Claude Code 在启动时会自动查找并加载以下位置的 CLAUDE.md:

  • ~/.claude/CLAUDE.md(全局用户级别)
  • {项目根目录}/CLAUDE.md(项目级别)
  • {当前工作目录}/CLAUDE.md(本地级别)

这些文件的内容会被注入到系统提示词的用户上下文(User Context)部分,在每次 API 调用时随消息一起发送。全局状态中有专门的缓存字段:

typescript
// CLAUDE.md content cached by context.ts for the auto-mode classifier.
cachedClaudeMdContent: string | null

(src/bootstrap/state.ts:123)

settings.json ​

Claude Code 维护多层配置文件,优先级由低到高依次为:用户全局设置(~/.claude/settings.json)、项目设置(.claude/settings.json)、本地设置(.claude/settings.local.json)、命令行 Flag 设置。

全局状态中明确记录了允许的配置来源:

typescript
allowedSettingSources: [
  'userSettings',
  'projectSettings',
  'localSettings',
  'flagSettings',
  'policySettings',
],

(src/bootstrap/state.ts:313-319)

这种分层设计使得企业可以通过 policySettings 锁定某些配置,防止用户在本地覆盖。

Feature Flag ​

Feature Flag 在 Claude Code 里有两种用途,在代码中体现为两种完全不同的机制:

编译时 Feature Flag:通过 bun:bundle 的 feature() 函数实现。这类 Flag 在 Bun 打包时被求值,未启用的代码块被彻底从产物中裁剪。工具注册表 src/tools.ts 中充满了这种用法:

typescript
const SleepTool =
  feature('PROACTIVE') || feature('KAIROS')
    ? require('./tools/SleepTool/SleepTool.js').SleepTool
    : null
const cronTools = feature('AGENT_TRIGGERS')
  ? [
      require('./tools/ScheduleCronTool/CronCreateTool.js').CronCreateTool,
      // ...
    ]
  : []

(src/tools.ts:25-35)

这意味着外部用户的 Claude Code 二进制包里根本不存在 SleepTool 的代码——不是禁用了,而是物理上不存在。

运行时 Feature Flag:通过 GrowthBook 服务实现,可以在不重新部署的情况下动态开启或关闭功能。这类 Flag 通常用于 A/B 测试和灰度发布,代码中通过 src/services/growthbook/ 模块访问。

入口文件中两种用法的区别一目了然:

typescript
// feature() 必须保持内联,以便构建时死码消除(DCE)生效
if (feature('BRIDGE_MODE') && (args[0] === 'remote-control' || ...)) {
  // isBridgeEnabled() 才是运行时的 GrowthBook 检查
  const disabledReason = await getBridgeDisabledReason();
}

(src/entrypoints/cli.tsx:112)


7. 全局单例状态的设计哲学 ​

src/bootstrap/state.ts 是整个代码库里注释最密集的文件之一,也是唯一带有三条强调性注释的文件:

typescript
// DO NOT ADD MORE STATE HERE - BE JUDICIOUS WITH GLOBAL STATE

// ALSO HERE - THINK THRICE BEFORE MODIFYING

// AND ESPECIALLY HERE
const STATE: State = getInitialState()

(src/bootstrap/state.ts:31、259、428-429)

这三条注释并排出现,说明了作者对全局状态的态度:全局状态是必要的恶,而不是便利的工具。整个文件里的 State 类型有约 60 个字段,但每个字段都有清晰的注释说明其存在的理由。

该文件的另一个值得注意的设计是函数式 setter。没有 STATE.sessionId = newId 这样的直接赋值暴露在外,所有修改都通过类似 setAppState(f: (prev: AppState) => AppState) 的函数式更新来进行,这使得状态变更可追踪,也方便在 React 的不可变状态模型里使用。


关键要点 ​

Claude Code 的架构可以用三个词概括:管道(Pipeline)、插件(Plugin)、分层(Layered)。

AsyncGenerator 管道是骨骼,它使得 API 调用、工具执行、UI 渲染三件事可以同步推进而不互相阻塞。工具系统是插件,Tool 接口定义了统一的契约(call、isEnabled、isReadOnly 等方法),任何满足此接口的对象都可以注入系统。分层配置是保护层,从 policySettings 到 flagSettings 的优先级链,使得同一套代码可以在个人开发者和企业环境里以不同的行为运行。

理解了这三点,源码里大量看似繁琐的条件分支和回调嵌套,就都有了合理的解释。

后续各章将沿着数据流的方向深入每个子系统:第 02 章从入口的 main.tsx 出发,详细拆解启动流程;第 03 章进入 query.ts,剖析多轮对话循环的每一个细节;第 04 章解析 Tool 接口,看清工具系统的完整设计。

Built for learners who want to read Claude Code like a real system.