From 07693b282aff9bd09feccd29d5629e1d543f8996 Mon Sep 17 00:00:00 2001 From: luyao618 <364939526@qq.com> Date: Wed, 27 May 2026 17:18:20 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E4=BF=AE=E5=A4=8D=E7=AB=A0=E5=8F=B7=20?= =?UTF-8?q?vs=20=E7=AF=87=E5=8F=B7=E4=BD=93=E7=B3=BB=E5=85=A8=E9=9D=A2?= =?UTF-8?q?=E9=94=99=E4=B9=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v2 重排后,几乎每章开篇仍写"本篇是系列的第 X 篇",但 X 与文件名 章号完全对不上(第 03 章自称第 17 篇、第 04 章自称第 26 篇、 第 33 章自称第 3 篇等等)。跨章引用("第 21 篇讲了 Ink")也全错。 本次统一修正: - 34 个章节文件开篇 self-ref 全部对齐到文件名 NN,"本篇"→"本章" - v1 沿用章节的正文交叉引用按 v1→v2 映射表全部 remap,"篇"→"章" - 8 个 v2 新增章节的正文引用按目标 v2 章号手动修正 - 第 34 章"过去 33 篇"、"这 34 篇"统一为"章" - 第 21 章"前面 23 篇" → "前面 20 章"(开篇时已读章数对齐) bun run check:docs 全绿。 Co-Authored-By: Claude Opus 4.6 --- ...5\205\245\345\217\243\345\275\242\346\200\201.md" | 4 ++-- ...5\220\257\345\212\250\344\274\230\345\214\226.md" | 4 ++-- ...63\273\344\270\216\344\274\201\344\270\232MDM.md" | 6 +++--- ...7\247\273\345\215\263\344\273\243\347\240\201.md" | 6 +++--- ...0\257\235\344\270\273\345\276\252\347\216\257.md" | 6 +++--- ...44\270\216OutputStyle\346\263\250\345\205\245.md" | 4 ++-- ...5\216\213\347\274\251\345\256\266\346\227\217.md" | 12 ++++++------ "docs/08-PromptCache\346\250\252\345\210\207.md" | 10 +++++----- "docs/09-Thinking-Effort-\344\270\216-Advisor.md" | 8 ++++---- ...46\263\250\345\206\214\344\270\216-ToolSearch.md" | 6 +++--- .../11-BashTool-PowerShellTool-\345\217\214shell.md" | 6 +++--- ...\216-LSP-\345\215\217\344\275\234\346\227\217.md" | 4 ++-- ...5\220\210\346\210\220\345\267\245\345\205\267.md" | 4 ++-- ...7\344\270\216SubAgent\350\260\203\347\224\250.md" | 4 ++-- ...0\256\276\350\256\241\346\250\241\345\274\217.md" | 8 ++++---- ...3\344\270\216TaskType\350\260\261\347\263\273.md" | 10 +++++----- ...5\256\232\346\227\266\350\260\203\345\272\246.md" | 10 +++++----- ...5\215\217\350\256\256\345\256\236\347\216\260.md" | 2 +- ...6\235\203\351\231\220\345\233\236\347\201\214.md" | 4 ++-- "docs/20-Hooks\347\263\273\347\273\237.md" | 2 +- ...4\270\211\346\211\251\345\261\225\347\202\271.md" | 12 ++++++------ ...0\257\221\346\234\237\344\274\230\345\214\226.md" | 6 +++--- ...76\223\344\270\216API\351\207\215\350\257\225.md" | 6 +++--- ...0\277\234\347\250\213\344\274\232\350\257\235.md" | 12 ++++++------ ...4\270\212\346\270\270\344\273\243\347\220\206.md" | 6 +++--- ...6\267\261\345\272\246\345\256\232\345\210\266.md" | 2 +- ...0\256\276\350\256\241\347\263\273\347\273\237.md" | 4 ++-- ...-Vim\344\270\216Voice\350\276\223\345\205\245.md" | 10 +++++----- "docs/29-Buddy\345\256\240\347\211\251.md" | 8 ++++---- ...44\270\216OutputStyle\344\275\223\351\252\214.md" | 2 +- ...7\263\273\347\273\237\345\205\250\346\231\257.md" | 6 +++--- ...7\263\273\347\273\237\345\205\250\346\231\257.md" | 4 ++-- ...0\267\250\350\277\233\347\250\213\346\241\245.md" | 2 +- ...6\250\241\345\274\217\346\200\273\347\273\223.md" | 8 ++++---- 34 files changed, 104 insertions(+), 104 deletions(-) diff --git "a/docs/01-\351\241\271\347\233\256\345\205\250\346\231\257\344\270\216\345\233\233\347\247\215\345\205\245\345\217\243\345\275\242\346\200\201.md" "b/docs/01-\351\241\271\347\233\256\345\205\250\346\231\257\344\270\216\345\233\233\347\247\215\345\205\245\345\217\243\345\275\242\346\200\201.md" index f542062..efccde6 100644 --- "a/docs/01-\351\241\271\347\233\256\345\205\250\346\231\257\344\270\216\345\233\233\347\247\215\345\205\245\345\217\243\345\275\242\346\200\201.md" +++ "b/docs/01-\351\241\271\347\233\256\345\205\250\346\231\257\344\270\216\345\233\233\347\247\215\345\205\245\345\217\243\345\275\242\346\200\201.md" @@ -1,6 +1,6 @@ # 第 1 章:项目全景与四种入口形态 — 一个 AI CLI 产品的技术蓝图 -> 本篇是《深入 Claude Code 源码》系列的开篇。我们将从技术栈选型、启动链路、模块依赖三个维度,建立对整个项目的全局认知。 +> 本章是《深入 Claude Code 源码》系列的开篇。我们将从技术栈选型、启动链路、模块依赖三个维度,建立对整个项目的全局认知。 ## 为什么要从全景开始? @@ -8,7 +8,7 @@ Claude Code 是 Anthropic 公司开发的 AI 驱动命令行编程助手。它不是一个简单的 API wrapper —— 它是一个完整的 AI Agent 运行时,包含了从终端 UI 渲染、多 Agent 编排、工具系统、权限安全到 Prompt 工程的全技术栈。理解它的全貌,等于理解了一个生产级 AI 产品的完整架构。 -本篇将回答四个核心问题: +本章将回答四个核心问题: 1. **同一份源码是怎么对外露出的?** — `entrypoints/` 下四种入口形态(CLI / SDK / MCP server / Sandbox runner)共用一份代码 2. **为什么选择这些技术?** — Bun + TypeScript + Ink + Commander.js 的技术栈选型逻辑 3. **程序是怎么启动的?** — 从 `cli.tsx` 到 REPL 的启动链路 diff --git "a/docs/02-\345\220\257\345\212\250\351\223\276\350\267\257\344\270\216\345\206\267\345\220\257\345\212\250\344\274\230\345\214\226.md" "b/docs/02-\345\220\257\345\212\250\351\223\276\350\267\257\344\270\216\345\206\267\345\220\257\345\212\250\344\274\230\345\214\226.md" index bf2bcc8..bee26e2 100644 --- "a/docs/02-\345\220\257\345\212\250\351\223\276\350\267\257\344\270\216\345\206\267\345\220\257\345\212\250\344\274\230\345\214\226.md" +++ "b/docs/02-\345\220\257\345\212\250\351\223\276\350\267\257\344\270\216\345\206\267\345\220\257\345\212\250\344\274\230\345\214\226.md" @@ -1,6 +1,6 @@ # 第 2 章:启动链路与冷启动优化 — 毫秒级 CLI 启动的工程艺术 -> 本篇是《深入 Claude Code 源码》系列的第 2 篇。我们将深入 `cli.tsx` 和 `main.tsx` 的启动路径,揭示 Claude Code 团队如何将 CLI 启动时间优化到毫秒级别。 +> 本章是《深入 Claude Code 源码》系列第 2 章。我们将深入 `cli.tsx` 和 `main.tsx` 的启动路径,揭示 Claude Code 团队如何将 CLI 启动时间优化到毫秒级别。 ## 为什么启动速度如此重要? @@ -8,7 +8,7 @@ CLI 工具的第一印象就是**启动速度**。用户在终端输入 `claude` 对于 Claude Code 这样一个重量级应用——近 1900 个 TypeScript 文件、依赖 React/Ink/Yoga 渲染引擎、需要连接 MCP 服务器和 Anthropic API——要做到毫秒级启动,绝非易事。 -本篇将揭示 Claude Code 采用的 **6 大启动优化策略**: +本章将揭示 Claude Code 采用的 **6 大启动优化策略**: 1. **快速路径(Fast Path)**:对简单命令实现零 import 返回 2. **侧效果前置(Side-Effect Hoisting)**:利用模块求值时间并行执行 I/O diff --git "a/docs/03-\351\205\215\347\275\256\344\275\223\347\263\273\344\270\216\344\274\201\344\270\232MDM.md" "b/docs/03-\351\205\215\347\275\256\344\275\223\347\263\273\344\270\216\344\274\201\344\270\232MDM.md" index ac79eae..44cb673 100644 --- "a/docs/03-\351\205\215\347\275\256\344\275\223\347\263\273\344\270\216\344\274\201\344\270\232MDM.md" +++ "b/docs/03-\351\205\215\347\275\256\344\275\223\347\263\273\344\270\216\344\274\201\344\270\232MDM.md" @@ -1,6 +1,6 @@ # 第 3 章:配置体系与企业 MDM — 多层配置的合并之道 -> 本篇是《深入 Claude Code 源码》系列第 17 篇。我们将剖析 Settings 系统如何从 5 个正式配置源(加 1 个 Plugin 基底层)中读取、验证、合并配置,以及如何在运行时监听变更并热更新 —— 一个面向企业级部署的多层配置合并架构。除了文件层的合并管线之外,Claude Code 还有两条独立运行的组织级服务管线 —— `services/policyLimits/` 与 `services/settingsSync/` —— 它们不参与设置合并,但同样在「企业管控」与「跨设备一致性」两个维度上塑造了用户最终看到的运行时配置面貌。 +> 本章是《深入 Claude Code 源码》系列第 3 章。我们将剖析 Settings 系统如何从 5 个正式配置源(加 1 个 Plugin 基底层)中读取、验证、合并配置,以及如何在运行时监听变更并热更新 —— 一个面向企业级部署的多层配置合并架构。除了文件层的合并管线之外,Claude Code 还有两条独立运行的组织级服务管线 —— `services/policyLimits/` 与 `services/settingsSync/` —— 它们不参与设置合并,但同样在「企业管控」与「跨设备一致性」两个维度上塑造了用户最终看到的运行时配置面貌。 ## 为什么需要多层配置? @@ -234,7 +234,7 @@ function parseSettingsFileUncached(path: string): { ### 3.2 SettingsSchema 的向后兼容设计 -`SettingsSchema` 定义在 `utils/settings/types.ts` 中,使用 `lazySchema()` 延迟构造(与工具系统一致的模式,见第 9 篇)。它的设计严格遵循向后兼容原则: +`SettingsSchema` 定义在 `utils/settings/types.ts` 中,使用 `lazySchema()` 延迟构造(与工具系统一致的模式,见第 10 章)。它的设计严格遵循向后兼容原则: ```typescript // utils/settings/types.ts:210-241(注释节选) @@ -321,7 +321,7 @@ mdm/ └── settings.ts — 解析、缓存、first-source-wins 逻辑 ``` -**为什么要这样拆?** 因为 `rawRead.ts` 在 `main.tsx` 模块求值阶段就被调用(第 2 篇提到的侧效果前置),此时不能引入任何重量级模块。MDM 读取分为**两个阶段**: +**为什么要这样拆?** 因为 `rawRead.ts` 在 `main.tsx` 模块求值阶段就被调用(第 2 章提到的侧效果前置),此时不能引入任何重量级模块。MDM 读取分为**两个阶段**: **阶段一:预启动子进程**(`main.tsx:3-4`,模块求值期) diff --git "a/docs/04-\351\205\215\347\275\256\350\277\201\347\247\273\345\215\263\344\273\243\347\240\201.md" "b/docs/04-\351\205\215\347\275\256\350\277\201\347\247\273\345\215\263\344\273\243\347\240\201.md" index 68150fe..2a5eed2 100644 --- "a/docs/04-\351\205\215\347\275\256\350\277\201\347\247\273\345\215\263\344\273\243\347\240\201.md" +++ "b/docs/04-\351\205\215\347\275\256\350\277\201\347\247\273\345\215\263\344\273\243\347\240\201.md" @@ -1,6 +1,6 @@ # 第 4 章:配置迁移即代码 — 把每一次破坏性配置改动写成一段小函数 -> 本篇是《深入 Claude Code 源码》系列第 26 篇。我们将剖析 `migrations/` 目录下 11 个迁移文件与 `main.tsx` 的 `runMigrations()` 调度,看 Claude Code 如何把"产品在用户身后偷偷换零件"这件事写成一组幂等、可独立 review、由版本号守护串行执行的小函数。 +> 本章是《深入 Claude Code 源码》系列第 4 章。我们将剖析 `migrations/` 目录下 11 个迁移文件与 `main.tsx` 的 `runMigrations()` 调度,看 Claude Code 如何把"产品在用户身后偷偷换零件"这件事写成一组幂等、可独立 review、由版本号守护串行执行的小函数。 ## 为什么配置迁移值得单独一篇? @@ -32,7 +32,7 @@ resetProToOpusDefault.ts 11 个文件全部是顶层文件——没有子目录、没有公共基类、没有"框架",每个文件导出一个同名的无参函数,函数名就是它做的那件事。这种克制的组织方式背后有一个很清楚的意图:**每一次破坏性的配置改动,都是一段可以被独立 review、独立删除、独立写测试的小代码片段**。你不需要去理解一个"迁移引擎"——只需要打开你感兴趣的那个文件,从头读到尾,三五十行内它就讲完了自己的故事。 -本篇按下面的顺序拆这 11 个函数:先看 `main.tsx` 里那条把它们串起来的流水线(§1),再分三组挨个看 —— 把字段从一处搬到另一处的 3 个(§2)、围绕模型字符串做重命名的 6 个(§3)、围绕"旧键名 / 旧弹窗选项"做清理的 2 个(§4),最后把贯穿全章的几条纪律收束起来(§5)。 +本章按下面的顺序拆这 11 个函数:先看 `main.tsx` 里那条把它们串起来的流水线(§1),再分三组挨个看 —— 把字段从一处搬到另一处的 3 个(§2)、围绕模型字符串做重命名的 6 个(§3)、围绕"旧键名 / 旧弹窗选项"做清理的 2 个(§4),最后把贯穿全章的几条纪律收束起来(§5)。 --- @@ -69,7 +69,7 @@ function runMigrations(): void { **接着是一段固定顺序。** 9 个无条件迁移 + 2 个带条件的,**顺序写死在源码里**,没有依赖图、没有拓扑排序。这个顺序不是随便排的,它隐含了几条事实关系:`resetProToOpusDefault` 要先于所有 Sonnet/Opus 模型字符串改名跑,因为它会基于"用户当时有没有自定义模型"决定要不要弹横幅;`migrateSonnet1mToSonnet45` 要先于 `migrateSonnet45ToSonnet46`,因为它产出的是 `sonnet-4-5-20250929[1m]` 这种显式串、正好是后者要往回卷成 `sonnet[1m]` 别名的输入;`migrateLegacyOpusToCurrent` 和 `migrateOpusToOpus1m` 一前一后,前者把 `claude-opus-4-0` 这种远古串收敛到 `opus` 别名,后者再把 `opus` 升级成 `opus[1m]`。顺序就是事实顺序,不需要装出 DAG 的样子。 -**然后是两段条件分支。** `feature('TRANSCRIPT_CLASSIFIER')` 是一道编译期 / 运行期同款的开关,命中才跑 `resetAutoModeOptInForDefaultOffer`;写得最有意思的是那段 `if ("external" === 'ant')`,这是 bundler 留下的字符串折叠点——发到外部用户的 bundle 里这一支永远是死代码、`migrateFennecToOpus` 被裁掉,而内部 `ant` 用户的 bundle 里这一支永远进入、迁移会跑。这是 Claude Code 把"内部独有逻辑"与"外发产物"用同一份源码一起维护的典型手法(详见第 19 篇 Feature Flag 与编译期优化)。 +**然后是两段条件分支。** `feature('TRANSCRIPT_CLASSIFIER')` 是一道编译期 / 运行期同款的开关,命中才跑 `resetAutoModeOptInForDefaultOffer`;写得最有意思的是那段 `if ("external" === 'ant')`,这是 bundler 留下的字符串折叠点——发到外部用户的 bundle 里这一支永远是死代码、`migrateFennecToOpus` 被裁掉,而内部 `ant` 用户的 bundle 里这一支永远进入、迁移会跑。这是 Claude Code 把"内部独有逻辑"与"外发产物"用同一份源码一起维护的典型手法(详见第 22 章 Feature Flag 与编译期优化)。 **最后是落版本号。** `saveGlobalConfig(prev => prev.migrationVersion === CURRENT_MIGRATION_VERSION ? prev : { ...prev, migrationVersion: CURRENT_MIGRATION_VERSION })` 这一行也有讲究:内层先比一次再决定要不要返回新对象,避免一次空写——`saveGlobalConfig` 的 updater 如果返回的是同一个对象引用,下游能省一次磁盘 IO 与一次内存中"已经一致就别落盘"的判断。这种"防御性同等性"在 11 个迁移内部到处都是。 diff --git "a/docs/05-QueryEngine\344\270\216\345\257\271\350\257\235\344\270\273\345\276\252\347\216\257.md" "b/docs/05-QueryEngine\344\270\216\345\257\271\350\257\235\344\270\273\345\276\252\347\216\257.md" index fccd8f3..8038f80 100644 --- "a/docs/05-QueryEngine\344\270\216\345\257\271\350\257\235\344\270\273\345\276\252\347\216\257.md" +++ "b/docs/05-QueryEngine\344\270\216\345\257\271\350\257\235\344\270\273\345\276\252\347\216\257.md" @@ -1,10 +1,10 @@ # 第 5 章:QueryEngine 与对话主循环 — 一次完整 AI 交互的心跳 -> 本篇是《深入 Claude Code 源码》系列的第 5 篇。我们将深入 `query.ts` 这个 1729 行的核心文件,揭示一次完整的 AI 对话是如何被驱动的——从消息组装、API 调用、工具执行到错误恢复,理解这个 Agent 运行时的"心跳"。 +> 本章是《深入 Claude Code 源码》系列第 5 章。我们将深入 `query.ts` 这个 1729 行的核心文件,揭示一次完整的 AI 对话是如何被驱动的——从消息组装、API 调用、工具执行到错误恢复,理解这个 Agent 运行时的"心跳"。 ## 为什么需要理解对话循环? -如果把 Claude Code 比作一个人体,那 `query.ts` 就是它的**心脏**——对话循环的编排入口。当然,心脏需要血管系统才能工作:重试逻辑在 `services/api/withRetry.ts`,工具执行在 `services/tools/`,停止钩子在 `query/stopHooks.ts`,环境配置在 `query/config.ts`。本篇会覆盖这个完整的"循环系统",而不仅仅是 `query.ts` 一个文件。每一次用户提问,都会触发这个循环: +如果把 Claude Code 比作一个人体,那 `query.ts` 就是它的**心脏**——对话循环的编排入口。当然,心脏需要血管系统才能工作:重试逻辑在 `services/api/withRetry.ts`,工具执行在 `services/tools/`,停止钩子在 `query/stopHooks.ts`,环境配置在 `query/config.ts`。本章会覆盖这个完整的"循环系统",而不仅仅是 `query.ts` 一个文件。每一次用户提问,都会触发这个循环: ``` 用户输入 → 组装消息 → 调用 API → 模型返回 → 执行工具 → 结果回传 → 模型继续... @@ -18,7 +18,7 @@ - **模型降级**:主模型不可用时,自动切换到 fallback 模型 - **并发工具执行**:只读工具可以并行,写入工具必须串行 -本篇将从宏观到微观,层层展开这个循环的设计。 +本章将从宏观到微观,层层展开这个循环的设计。 --- diff --git "a/docs/06-SystemPrompt\344\270\216OutputStyle\346\263\250\345\205\245.md" "b/docs/06-SystemPrompt\344\270\216OutputStyle\346\263\250\345\205\245.md" index a7ab459..be6201a 100644 --- "a/docs/06-SystemPrompt\344\270\216OutputStyle\346\263\250\345\205\245.md" +++ "b/docs/06-SystemPrompt\344\270\216OutputStyle\346\263\250\345\205\245.md" @@ -1,6 +1,6 @@ # 第 6 章:System Prompt 与 Output Style 注入 — 精密控制模型行为的提示词体系 -> 本篇是《深入 Claude Code 源码》系列的第 4 篇。我们将深入 `constants/prompts.ts`(914 行,commit `290fdc94`)这个核心文件,揭示 Claude Code 如何通过精心设计的 System Prompt 架构,在「精确控制模型行为」和「最大化 Prompt Cache 命中率」之间取得平衡。 +> 本章是《深入 Claude Code 源码》系列第 6 章。我们将深入 `constants/prompts.ts`(914 行,commit `290fdc94`)这个核心文件,揭示 Claude Code 如何通过精心设计的 System Prompt 架构,在「精确控制模型行为」和「最大化 Prompt Cache 命中率」之间取得平衡。 ## 为什么 System Prompt 值得单独一篇? @@ -11,7 +11,7 @@ 3. **它是模型行为的根基** — 代码风格约束、安全指令、工具使用优先级、输出格式,全部编码在这里 4. **它有内外版差异** — 通过 `process.env.USER_TYPE === 'ant'` 区分内部版(Anthropic 员工)和外部版(公开用户),同一套代码产出不同的行为指引 -本篇将回答三个核心问题: +本章将回答三个核心问题: 1. **System Prompt 由哪些模块组成,如何组装?** — `getSystemPrompt()` 的完整流程 2. **静态与动态内容如何分离以优化缓存?** — `SYSTEM_PROMPT_DYNAMIC_BOUNDARY` 机制 3. **提示词中编码了哪些关键的行为引导技巧?** — 从安全指令到代码风格约束 diff --git "a/docs/07-\344\270\212\344\270\213\346\226\207\345\216\213\347\274\251\345\256\266\346\227\217.md" "b/docs/07-\344\270\212\344\270\213\346\226\207\345\216\213\347\274\251\345\256\266\346\227\217.md" index b2e33c1..7da6885 100644 --- "a/docs/07-\344\270\212\344\270\213\346\226\207\345\216\213\347\274\251\345\256\266\346\227\217.md" +++ "b/docs/07-\344\270\212\344\270\213\346\226\207\345\216\213\347\274\251\345\256\266\346\227\217.md" @@ -1,8 +1,8 @@ # 第 7 章:上下文压缩家族 — 无限对话的秘密 -> 本篇是《深入 Claude Code 源码》系列的第 6 篇。我们将深入分析 Claude Code 如何在有限的 context window 内支撑无限长度的对话——从 token 预算管理、多级压缩策略,到文件状态缓存与 compact 后恢复的设计。 +> 本章是《深入 Claude Code 源码》系列第 7 章。我们将深入分析 Claude Code 如何在有限的 context window 内支撑无限长度的对话——从 token 预算管理、多级压缩策略,到文件状态缓存与 compact 后恢复的设计。 > -> **范围说明**:上下文的"构建"(System Prompt 组装、CLAUDE.md 注入、git status 获取)已在第 4 篇中覆盖,缓存策略的横切视角将在第 7 篇中展开。本篇聚焦于上下文构建完成后的**压缩、清理与恢复**——即当 context window 不够用时,Claude Code 如何在不中断对话的情况下释放空间并重建必要的工作上下文。 +> **范围说明**:上下文的"构建"(System Prompt 组装、CLAUDE.md 注入、git status 获取)已在第 6 章中覆盖,缓存策略的横切视角将在第 8 章中展开。本章聚焦于上下文构建完成后的**压缩、清理与恢复**——即当 context window 不够用时,Claude Code 如何在不中断对话的情况下释放空间并重建必要的工作上下文。 ## 为什么上下文管理如此重要? @@ -350,7 +350,7 @@ export async function autoCompactIfNeeded( 除了这两条最直接的递归保护,源码在 `shouldAutoCompact()` 里还排布了几道针对新一代上下文机制的护栏(`services/compact/autoCompact.ts:174-223`),它们都属于"看似与 compact 无关、一旦同时点火就会互相拆台"那一类隐患: - **`marble_origami` 这个 ctx-agent**:开启了 `CONTEXT_COLLAPSE` 之后,contextCollapse 自己也是一个 forked agent(querySource = `marble_origami`)。如果它在工作过程中又触发 autocompact,`runPostCompactCleanup` 会顺手调用 `resetContextCollapse()`,把主线程那一份模块级 collapse log 全部清掉。源码注释把这个连锁反应讲得很直白——所以这条 querySource 直接被列入"绝不再触发 autocompact"的黑名单。 -- **`REACTIVE_COMPACT` 反应式模式**:当 `tengu_cobalt_raccoon` gate 打开时,全局策略是"前置不再 proactive 压缩,等 API 真的返回 prompt-too-long 再由 reactive compact 兜底",因此 `shouldAutoCompact()` 直接返回 `false`,把决策权完全交给第 5 篇里讲过的 413 恢复链。 +- **`REACTIVE_COMPACT` 反应式模式**:当 `tengu_cobalt_raccoon` gate 打开时,全局策略是"前置不再 proactive 压缩,等 API 真的返回 prompt-too-long 再由 reactive compact 兜底",因此 `shouldAutoCompact()` 直接返回 `false`,把决策权完全交给第 5 章里讲过的 413 恢复链。 - **`CONTEXT_COLLAPSE` 启用时的让位**:collapse 自己在 90% 触发承诺、95% 触发阻塞生成,而 autocompact 的阈值落在 effective 减去 13K 的位置(200K 模型上即 167K,相当于 93%)——恰好夹在这两条线中间。如果不让位,autocompact 通常会抢在 collapse 之前点火,把 collapse 即将保住的细粒度上下文一刀切掉。这里通过 `isContextCollapseEnabled()` 二次判定(而不是直接看 feature flag),是为了让 `CLAUDE_CONTEXT_COLLAPSE` 环境变量这种本地覆盖也能生效。 这一串护栏背后的共同主题是:**当一个进程里同时活着多种上下文管理子系统时,谁先点火谁就赢,而点火顺序错了会发生静默的状态破坏**。把所有"我现在不该跑"的判定收拢到 `shouldAutoCompact()` 一个入口,比让每个子系统各自试探安全得多。 @@ -361,7 +361,7 @@ export async function autoCompactIfNeeded( 这是 compact 的一个创新路径:不调用模型生成总结,而是**直接使用 Session Memory 系统已有的对话记忆**作为压缩后的总结。 -Session Memory 是一个独立的后台系统(将在第 23 篇详述),它在对话过程中持续异步提取关键信息到磁盘文件。当 compact 触发时,如果 Session Memory 已经有内容,就直接用它作为总结,跳过昂贵的 API 调用: +Session Memory 是一个独立的后台系统(将在第 31 章详述),它在对话过程中持续异步提取关键信息到磁盘文件。当 compact 触发时,如果 Session Memory 已经有内容,就直接用它作为总结,跳过昂贵的 API 调用: ```typescript export async function trySessionMemoryCompaction( @@ -542,7 +542,7 @@ export class FileStateCache { **文件**:`services/compact/compactWarningState.ts` -这是一个有趣的小模块——它复用了第 3 篇介绍的极简 Store 来管理 compact 告警的抑制状态: +这是一个有趣的小模块——它复用了第 33 章介绍的极简 Store 来管理 compact 告警的抑制状态: ```typescript import { createStore } from '../../state/store.js' @@ -573,7 +573,7 @@ export function useCompactWarningSuppression(): boolean { } ``` -这个 hook 只有 7 行有效代码,单独成文件是为了**让 `compactWarningState.ts` 保持 React-free**——`microCompact.ts` 在每个 turn 的预处理阶段都会读写这个 store,如果状态文件 import 了 React,那么整条 print-mode 启动路径都会被迫把 React 拖进模块图,这是源码注释里直接写明的考量。这种"状态文件零依赖、订阅 hook 拆出去"的拆法在第 3 篇 Store 模式之外又添了一个小的样本。 +这个 hook 只有 7 行有效代码,单独成文件是为了**让 `compactWarningState.ts` 保持 React-free**——`microCompact.ts` 在每个 turn 的预处理阶段都会读写这个 store,如果状态文件 import 了 React,那么整条 print-mode 启动路径都会被迫把 React 拖进模块图,这是源码注释里直接写明的考量。这种"状态文件零依赖、订阅 hook 拆出去"的拆法在第 33 章 Store 模式之外又添了一个小的样本。 --- diff --git "a/docs/08-PromptCache\346\250\252\345\210\207.md" "b/docs/08-PromptCache\346\250\252\345\210\207.md" index c2be2d1..af0f642 100644 --- "a/docs/08-PromptCache\346\250\252\345\210\207.md" +++ "b/docs/08-PromptCache\346\250\252\345\210\207.md" @@ -1,6 +1,6 @@ # 第 8 章:Prompt Cache 横切 — 跨模块的缓存策略如何降低 API 成本 -> 本篇是《深入 Claude Code 源码》系列的第 7 篇。我们将从一个横切视角,解析 Prompt Cache 机制如何贯穿 System Prompt、对话循环、上下文管理三大模块,以及 Fork Agent 如何通过精密的参数对齐实现跨进程缓存共享。 +> 本章是《深入 Claude Code 源码》系列第 8 章。我们将从一个横切视角,解析 Prompt Cache 机制如何贯穿 System Prompt、对话循环、上下文管理三大模块,以及 Fork Agent 如何通过精密的参数对齐实现跨进程缓存共享。 ## 为什么需要理解 Prompt Cache? @@ -8,9 +8,9 @@ Anthropic 的 Prompt Caching 机制可以将 **重复前缀** 的成本降低 90%。服务端会缓存请求前缀(System Prompt + 工具定义 + 消息历史前缀),只要下次请求的前缀字节完全一致,就可以从缓存中读取,而非重新处理。 -但"字节完全一致"这个要求极其严苛 —— **哪怕多一个空格、换一个 JSON 字段顺序、改一个 `cache_control` 的 scope,整个缓存就失效了。** Claude Code 的源码中充斥着对这个约束的防御性设计。本篇将揭示这些设计的全貌。 +但"字节完全一致"这个要求极其严苛 —— **哪怕多一个空格、换一个 JSON 字段顺序、改一个 `cache_control` 的 scope,整个缓存就失效了。** Claude Code 的源码中充斥着对这个约束的防御性设计。本章将揭示这些设计的全貌。 -本篇将回答三个核心问题: +本章将回答三个核心问题: 1. **缓存是怎么标记的?** — `cache_control` 标记的放置策略与作用域选择 2. **缓存是怎么保护的?** — 从 System Prompt 分段到 Cached Microcompact,如何避免缓存失效 3. **缓存是怎么共享的?** — Fork Agent 如何通过 `CacheSafeParams` 实现跨线程缓存命中 @@ -180,7 +180,7 @@ function should1hCacheTTL(querySource?: QuerySource): boolean { ### 2.1 `SYSTEM_PROMPT_DYNAMIC_BOUNDARY`:静态与动态的分界线 -System Prompt 由多个 section 组成(详见第 4 篇),其中一些是所有用户共享的(如核心指令、工具使用规范),另一些是会话特定的(如 Git 状态、CLAUDE.md 内容、语言设置)。如果把它们合并成一个块做缓存标记,任何动态部分的变化都会导致整个 System Prompt 的缓存失效。 +System Prompt 由多个 section 组成(详见第 6 章),其中一些是所有用户共享的(如核心指令、工具使用规范),另一些是会话特定的(如 Git 状态、CLAUDE.md 内容、语言设置)。如果把它们合并成一个块做缓存标记,任何动态部分的变化都会导致整个 System Prompt 的缓存失效。 Claude Code 的解决方案是插入一个 **边界标记**: @@ -280,7 +280,7 @@ export async function resolveSystemPromptSections( ### 3.1 问题:清理旧的工具结果 vs 保护缓存前缀 -在长对话中,早期的工具调用结果(如 `FileRead` 读取的文件内容、`Bash` 的命令输出)会占用大量 token,但随着对话推进,这些结果已经不再有用。第 6 篇介绍了 Microcompact 机制来清理它们。 +在长对话中,早期的工具调用结果(如 `FileRead` 读取的文件内容、`Bash` 的命令输出)会占用大量 token,但随着对话推进,这些结果已经不再有用。第 7 章介绍了 Microcompact 机制来清理它们。 但传统的 Microcompact 有一个问题:**修改消息内容会改变请求前缀的字节,导致缓存失效**。假设服务端缓存了 `[sysprompt, msg1, msg2(tool_result="file content..."), msg3]` 这个前缀,如果我们把 `msg2` 的 `tool_result` 替换为 `"[Old tool result content cleared]"`,前缀就不匹配了。 diff --git "a/docs/09-Thinking-Effort-\344\270\216-Advisor.md" "b/docs/09-Thinking-Effort-\344\270\216-Advisor.md" index b8ff31c..135e04d 100644 --- "a/docs/09-Thinking-Effort-\344\270\216-Advisor.md" +++ "b/docs/09-Thinking-Effort-\344\270\216-Advisor.md" @@ -1,6 +1,6 @@ # 第 9 章:Thinking、Effort 与 Advisor — 让模型「想」多少 -> 本篇是《深入 Claude Code 源码》系列的第 8 篇。我们将深入分析 Claude Code 如何精细控制模型的推理深度——从 Extended Thinking 的三种模式、Effort 级别系统、`ultrathink` 关键词触发,到 Advisor 这一"更强模型审阅"机制。 +> 本章是《深入 Claude Code 源码》系列第 9 章。我们将深入分析 Claude Code 如何精细控制模型的推理深度——从 Extended Thinking 的三种模式、Effort 级别系统、`ultrathink` 关键词触发,到 Advisor 这一"更强模型审阅"机制。 > > 这些机制共同回答了一个核心问题:**在速度、质量和成本之间,如何给用户最大的控制权?** @@ -562,11 +562,11 @@ export function hasUltrathinkKeyword(text: string): boolean { } ``` -双重门控:编译期 `feature('ULTRATHINK')` 控制代码是否包含在构建中,GrowthBook `tengu_turtle_carbon` 控制运行时是否启用。这是第 19 篇将详述的 Feature Flag 模式的典型应用。 +双重门控:编译期 `feature('ULTRATHINK')` 控制代码是否包含在构建中,GrowthBook `tengu_turtle_carbon` 控制运行时是否启用。这是第 22 章将详述的 Feature Flag 模式的典型应用。 ### 4.2 通过 Attachment 系统注入 -关键词检测后,ultrathink 通过 Attachment 系统(第 5 篇提到的消息附件机制)注入到对话中: +关键词检测后,ultrathink 通过 Attachment 系统(第 5 章提到的消息附件机制)注入到对话中: **文件**:`utils/attachments.ts:1446-1452` @@ -943,7 +943,7 @@ export function shouldEnablePromptSuggestion(): boolean { // 节省的那点 effort 钱远抵不上 cache miss 多花的钱。 ``` -这就是第 7 篇 Prompt Cache 那一章反复强调的"前缀稳定"原则在这里的反向印证——**子系统宁可多花点 token 跑一遍同等 effort 的模型,也不能动主对话的缓存前缀**。这条约束和本章 §二 里 Thinking 的 `thinkingClearLatched` 是一个家族的设计:所有"会改变 API 请求关键字段"的开关,都要先回答"它会不会让主对话的缓存掉血"。 +这就是第 8 章 Prompt Cache 那一章反复强调的"前缀稳定"原则在这里的反向印证——**子系统宁可多花点 token 跑一遍同等 effort 的模型,也不能动主对话的缓存前缀**。这条约束和本章 §二 里 Thinking 的 `thinkingClearLatched` 是一个家族的设计:所有"会改变 API 请求关键字段"的开关,都要先回答"它会不会让主对话的缓存掉血"。 ### 番外.3 早退守卫与父缓存温度 diff --git "a/docs/10-\345\267\245\345\205\267\345\215\217\350\256\256-\346\263\250\345\206\214\344\270\216-ToolSearch.md" "b/docs/10-\345\267\245\345\205\267\345\215\217\350\256\256-\346\263\250\345\206\214\344\270\216-ToolSearch.md" index 199cdcf..45ab07e 100644 --- "a/docs/10-\345\267\245\345\205\267\345\215\217\350\256\256-\346\263\250\345\206\214\344\270\216-ToolSearch.md" +++ "b/docs/10-\345\267\245\345\205\267\345\215\217\350\256\256-\346\263\250\345\206\214\344\270\216-ToolSearch.md" @@ -1,6 +1,6 @@ # 第 10 章:工具协议、注册与 ToolSearch — buildTool() 的抽象之美 -> 本篇是《深入 Claude Code 源码》系列的第 9 篇。我们将深入工具系统的核心设计:从 `Tool` 接口的核心方法,到 `buildTool()` 的 builder 模式,到 `tools.ts` 的注册表架构,再到 ToolSearch 的延迟加载机制,揭示一个生产级 AI Agent 如何管理一整族内置工具(具体清单与分类见 [附录 A](./appendix/A.md))和无限数量的 MCP 工具。 +> 本章是《深入 Claude Code 源码》系列第 10 章。我们将深入工具系统的核心设计:从 `Tool` 接口的核心方法,到 `buildTool()` 的 builder 模式,到 `tools.ts` 的注册表架构,再到 ToolSearch 的延迟加载机制,揭示一个生产级 AI Agent 如何管理一整族内置工具(具体清单与分类见 [附录 A](./appendix/A.md))和无限数量的 MCP 工具。 ## 为什么工具系统是 Agent 的灵魂? @@ -13,7 +13,7 @@ Claude Code 内置工具的真实集合不是一个固定数字,而是「`tool 3. **条件注册**:不同构建版本、不同运行模式下可用的工具不同 4. **规模可扩展**:当工具数量超过模型上下文窗口的承载能力时,需要动态发现机制 -本篇将回答这些问题,并从中提炼出可迁移到自己项目的设计模式。 +本章将回答这些问题,并从中提炼出可迁移到自己项目的设计模式。 --- @@ -530,7 +530,7 @@ export type ToolUseContext = { } ``` -`ToolUseContext` 是第 3 篇讲过的"运行时上下文容器"。它最关键的设计是 `getAppState/setAppState` 对 —— subagent 的 `setAppState` 可以是 no-op(参见 `createSubagentContext()`),实现了 Agent 隔离。 +`ToolUseContext` 是第 33 章讲过的"运行时上下文容器"。它最关键的设计是 `getAppState/setAppState` 对 —— subagent 的 `setAppState` 可以是 no-op(参见 `createSubagentContext()`),实现了 Agent 隔离。 --- diff --git "a/docs/11-BashTool-PowerShellTool-\345\217\214shell.md" "b/docs/11-BashTool-PowerShellTool-\345\217\214shell.md" index 1ee407a..87ea3a8 100644 --- "a/docs/11-BashTool-PowerShellTool-\345\217\214shell.md" +++ "b/docs/11-BashTool-PowerShellTool-\345\217\214shell.md" @@ -1,10 +1,10 @@ # 第 11 章:BashTool / PowerShellTool 双 shell — 最复杂的单个工具家族 -> 本篇是《深入 Claude Code 源码》系列第 10 篇。BashTool 是 Claude Code 中代码量最大、安全逻辑最复杂的单个工具,总计 12,411 行代码。我们将从命令语义分析、多层安全防线、沙箱执行、输出处理、权限匹配五个维度,完整剖析它如何让 AI 安全地执行 Shell 命令;末尾再以 PowerShellTool 作为 Windows 路径上的对照实现。 +> 本章是《深入 Claude Code 源码》系列第 11 章。BashTool 是 Claude Code 中代码量最大、安全逻辑最复杂的单个工具,总计 12,411 行代码。我们将从命令语义分析、多层安全防线、沙箱执行、输出处理、权限匹配五个维度,完整剖析它如何让 AI 安全地执行 Shell 命令;末尾再以 PowerShellTool 作为 Windows 路径上的对照实现。 ## 为什么 BashTool 是最复杂的工具? -在第 9 篇中我们了解了 `buildTool()` 的抽象体系和 Tool 接口的设计。所有工具都遵循相同的 `name / inputSchema / call() / checkPermissions()` 协议。但 BashTool 的特殊之处在于:**它是唯一一个允许 AI 在用户机器上执行任意代码的工具**。 +在第 10 章中我们了解了 `buildTool()` 的抽象体系和 Tool 接口的设计。所有工具都遵循相同的 `name / inputSchema / call() / checkPermissions()` 协议。但 BashTool 的特殊之处在于:**它是唯一一个允许 AI 在用户机器上执行任意代码的工具**。 这意味着它必须同时解决两个相互矛盾的需求: 1. **足够强大** — AI 需要通过 Shell 命令完成 git 操作、运行测试、安装依赖、查看日志等几乎一切任务 @@ -369,7 +369,7 @@ type ShellPermissionRule = | { type: 'wildcard'; pattern: string } // 通配符: "git *" ``` -权限规则有三种来源:`alwaysAllowRules`(自动批准)、`alwaysDenyRules`(自动拒绝)、`alwaysAskRules`(总是询问)。规则按 source 分层(参见第 16 篇权限系统)。 +权限规则有三种来源:`alwaysAllowRules`(自动批准)、`alwaysDenyRules`(自动拒绝)、`alwaysAskRules`(总是询问)。规则按 source 分层(参见第 19 章权限系统)。 ### 3.2 环境变量剥离与包装器剥离 diff --git "a/docs/12-\346\226\207\344\273\266-\344\273\243\347\240\201-\344\270\216-LSP-\345\215\217\344\275\234\346\227\217.md" "b/docs/12-\346\226\207\344\273\266-\344\273\243\347\240\201-\344\270\216-LSP-\345\215\217\344\275\234\346\227\217.md" index f2b959b..28b9a68 100644 --- "a/docs/12-\346\226\207\344\273\266-\344\273\243\347\240\201-\344\270\216-LSP-\345\215\217\344\275\234\346\227\217.md" +++ "b/docs/12-\346\226\207\344\273\266-\344\273\243\347\240\201-\344\270\216-LSP-\345\215\217\344\275\234\346\227\217.md" @@ -1,12 +1,12 @@ # 第 12 章:文件、代码与 LSP 协作族 — 从 Read 到 LSP 的工程一致性 -> 本篇是《深入 Claude Code 源码》系列对工具家族的第二次深入。上一篇剖完了 `Tool` 接口、`buildTool()` 与 ToolSearch 的抽象骨架,这一篇把镜头收近到八个具体工具上:`FileRead` / `FileWrite` / `FileEdit` / `NotebookEdit` / `Glob` / `Grep` / `LSPTool` / `REPLTool`,外加 `services/lsp/` 这一摞被 LSPTool 直接架在上面的服务。它们合起来回答了一个问题:当一个 Agent 想"读懂并改动一份代码仓库"时,Claude Code 的源码里到底铺了多少条不显眼的规矩。 +> 本章是《深入 Claude Code 源码》系列对工具家族的第二次深入。上一篇剖完了 `Tool` 接口、`buildTool()` 与 ToolSearch 的抽象骨架,这一篇把镜头收近到八个具体工具上:`FileRead` / `FileWrite` / `FileEdit` / `NotebookEdit` / `Glob` / `Grep` / `LSPTool` / `REPLTool`,外加 `services/lsp/` 这一摞被 LSPTool 直接架在上面的服务。它们合起来回答了一个问题:当一个 Agent 想"读懂并改动一份代码仓库"时,Claude Code 的源码里到底铺了多少条不显眼的规矩。 ## 为什么要把文件、代码与 LSP 放在一起讲? 如果你只看工具名,很容易以为这八个工具是平行的:四个负责文件读写、两个负责搜索、一个负责调用语言服务、一个负责 REPL。但在源码里它们其实共享同一条暗线 —— **任何想动文件的工具,都被强制经过同一份"读过没有 / 读完之后改没改"的核账逻辑**;任何想看代码的工具,最终都从 `ripGrep` 或 LSP 这两条路里走出去;任何想压缩 deferred 工具集面的入口,都收口到同一个 REPL 沙箱里。 -也正因为它们在工程上是一组人共用的同一份契约,把它们拆成八章去讲会显得啰嗦:每个工具的 README 内核重复度很高,但**它们彼此之间靠对方留出的不变量在工作**。所以本篇按"读 → 写 → 搜 → 看代码 → 收口到 REPL"的顺序串一遍,重点不在每个工具的 API 表面,而在它们之间互相留下的接缝。 +也正因为它们在工程上是一组人共用的同一份契约,把它们拆成八章去讲会显得啰嗦:每个工具的 README 内核重复度很高,但**它们彼此之间靠对方留出的不变量在工作**。所以本章按"读 → 写 → 搜 → 看代码 → 收口到 REPL"的顺序串一遍,重点不在每个工具的 API 表面,而在它们之间互相留下的接缝。 读完之后,你应该能回答这几个问题:FileEdit 为什么必须先看到 FileRead 的回执?Grep 的 `--max-columns 500` 到底在防什么?LSPTool 为什么是一个 deferred 工具而不是常驻?写文件之后,LSP 诊断为什么会"自己刷一遍"?REPL 模式打开后,那八个最常用的工具去哪了? diff --git "a/docs/13-\351\200\232\344\277\241\350\260\203\345\272\246\351\227\256\350\257\242\344\270\216\345\220\210\346\210\220\345\267\245\345\205\267.md" "b/docs/13-\351\200\232\344\277\241\350\260\203\345\272\246\351\227\256\350\257\242\344\270\216\345\220\210\346\210\220\345\267\245\345\205\267.md" index 3950bb2..071abe4 100644 --- "a/docs/13-\351\200\232\344\277\241\350\260\203\345\272\246\351\227\256\350\257\242\344\270\216\345\220\210\346\210\220\345\267\245\345\205\267.md" +++ "b/docs/13-\351\200\232\344\277\241\350\260\203\345\272\246\351\227\256\350\257\242\344\270\216\345\220\210\346\210\220\345\267\245\345\205\267.md" @@ -1,6 +1,6 @@ # 第 13 章:通信、调度、问询与合成工具 — Agent 与外部世界之间的十条窄通道 -> 本篇是《深入 Claude Code 源码》系列对工具家族的第三次深入。前两篇分别讲了 `Tool` 协议骨架,以及文件、代码与 LSP 一族围绕"先读后写"这条暗规的工程一致性。这一篇把镜头掉转 180°:不再看 Agent 怎么动本地代码,而是看它怎么开口——怎么往外抓一段网页、怎么把自己挂到 cron 上、怎么和另一个 Agent 互相通讯、怎么向用户问一道选择题、怎么交一份机器可读的最终答卷。 +> 本章是《深入 Claude Code 源码》系列对工具家族的第三次深入。前两篇分别讲了 `Tool` 协议骨架,以及文件、代码与 LSP 一族围绕"先读后写"这条暗规的工程一致性。这一篇把镜头掉转 180°:不再看 Agent 怎么动本地代码,而是看它怎么开口——怎么往外抓一段网页、怎么把自己挂到 cron 上、怎么和另一个 Agent 互相通讯、怎么向用户问一道选择题、怎么交一份机器可读的最终答卷。 ## 为什么把这十把工具放在一篇里讲? @@ -10,7 +10,7 @@ 也正因为都在凿窄通道,这十把工具的设计里都重复着同一组工程克制:明确的超时上限、明确的缓存策略、对外不暴露的预审名单、给二级模型读的安全提示词。把它们放在一起讲,是想把这条暗线显形——而不是给每个工具都单写一节,让读者读到第八个的时候已经记不清前面六个的差异。 -本篇按四组叙事来讲: +本章按四组叙事来讲: 1. **往外抓一段事实** — `WebFetchTool` 与 `WebSearchTool` 2. **把自己挂到时间或外触发上** — `ScheduleCron` 三件套、`RemoteTriggerTool`、`SleepTool` diff --git "a/docs/14-Agent\347\263\273\347\273\237\344\270\216SubAgent\350\260\203\347\224\250.md" "b/docs/14-Agent\347\263\273\347\273\237\344\270\216SubAgent\350\260\203\347\224\250.md" index ac9f4a7..6dc2321 100644 --- "a/docs/14-Agent\347\263\273\347\273\237\344\270\216SubAgent\350\260\203\347\224\250.md" +++ "b/docs/14-Agent\347\263\273\347\273\237\344\270\216SubAgent\350\260\203\347\224\250.md" @@ -1,6 +1,6 @@ # 第 14 章:Agent 系统与 Sub-Agent 调用 — 从单体到多智能体协作 -> 本篇是《深入 Claude Code 源码》系列的第 12 篇。我们将深入剖析 Agent 子系统的完整架构:从 Agent 定义的数据结构与加载机制,到 `runAgent()` 的完整生命周期,再到 `createSubagentContext()` 如何实现 context 隔离与选择性共享。 +> 本章是《深入 Claude Code 源码》系列第 14 章。我们将深入剖析 Agent 子系统的完整架构:从 Agent 定义的数据结构与加载机制,到 `runAgent()` 的完整生命周期,再到 `createSubagentContext()` 如何实现 context 隔离与选择性共享。 ## 为什么需要多 Agent? @@ -13,7 +13,7 @@ Claude Code 的解决方案是**多 Agent 协作**:主 Agent 可以按需生 2. **专业化分工**:不同类型的子 Agent 可以有不同的工具集、权限和 System Prompt 3. **并行执行**:多个子 Agent 可以同时在后台运行,互不干扰 -本篇将回答以下问题: +本章将回答以下问题: - Agent 定义的数据结构长什么样?如何从多种来源加载? - `runAgent()` 的完整生命周期经历哪些阶段? - 子 Agent 的 context 隔离是怎样实现的?哪些状态被共享,哪些被隔离? diff --git "a/docs/15-\345\206\205\347\275\256Agent\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/docs/15-\345\206\205\347\275\256Agent\350\256\276\350\256\241\346\250\241\345\274\217.md" index f962ccf..7b4f12b 100644 --- "a/docs/15-\345\206\205\347\275\256Agent\350\256\276\350\256\241\346\250\241\345\274\217.md" +++ "b/docs/15-\345\206\205\347\275\256Agent\350\256\276\350\256\241\346\250\241\345\274\217.md" @@ -1,10 +1,10 @@ # 第 15 章:内置 Agent 设计模式 — Explore、Plan、Verification 的 Prompt 设计 -> 本篇是《深入 Claude Code 源码》系列第 13 篇。我们将深入 6 个内置 Agent 的 System Prompt 设计,揭示如何通过 prompt 工程将同一套工具系统塑造出截然不同的 Agent 人格与行为模式,并解析自定义 Agent 的 markdown frontmatter 配置全貌。 +> 本章是《深入 Claude Code 源码》系列第 15 章。我们将深入 6 个内置 Agent 的 System Prompt 设计,揭示如何通过 prompt 工程将同一套工具系统塑造出截然不同的 Agent 人格与行为模式,并解析自定义 Agent 的 markdown frontmatter 配置全貌。 ## 为什么需要内置 Agent? -在第 12 篇中,我们了解了 Agent 系统的整体架构 —— `AgentDefinition` 数据结构、`runAgent()` 生命周期、`createSubagentContext()` 的隔离机制。但架构只是骨架,真正赋予 Agent "灵魂"的是它们的 **System Prompt 设计**。 +在第 14 章中,我们了解了 Agent 系统的整体架构 —— `AgentDefinition` 数据结构、`runAgent()` 生命周期、`createSubagentContext()` 的隔离机制。但架构只是骨架,真正赋予 Agent "灵魂"的是它们的 **System Prompt 设计**。 一个核心问题:Claude Code 拥有 40+ 个工具,为什么不让一个通用 Agent 做所有事?答案藏在工程实践中: @@ -13,7 +13,7 @@ 3. **prompt 精度** —— 越专注的角色定义,模型的行为越可预测 4. **token 节约** —— 只读 Agent 不需要 CLAUDE.md 中的 commit/PR/lint 规则,省下 5-15 Gtok/周 -Claude Code 通过 6 个内置 Agent(General-purpose、Statusline-setup、Explore、Plan、Guide、Verification),展示了一套**角色化 prompt 设计模式** —— 同样的工具集合,通过不同的 System Prompt 约束,产生完全不同的行为。本篇重点剖析其中 5 个设计最具代表性的 Agent(Statusline-setup 偏领域特化,仅在架构图中列出)。 +Claude Code 通过 6 个内置 Agent(General-purpose、Statusline-setup、Explore、Plan、Guide、Verification),展示了一套**角色化 prompt 设计模式** —— 同样的工具集合,通过不同的 System Prompt 约束,产生完全不同的行为。本章重点剖析其中 5 个设计最具代表性的 Agent(Statusline-setup 偏领域特化,仅在架构图中列出)。 --- @@ -369,7 +369,7 @@ const effectiveType = subagent_type ?? (isForkSubagentEnabled() ? undefined : GENERAL_PURPOSE_AGENT.agentType); ``` -当 fork subagent 功能启用时(编译期 `feature('FORK_SUBAGENT')` + 非 Coordinator 模式 + 交互式会话),省略 `subagent_type` 会走 fork path 而非 general-purpose。Fork path 会让子 Agent 继承父 Agent 的完整对话上下文,这是一种完全不同的执行模式(详见第 12 篇)。 +当 fork subagent 功能启用时(编译期 `feature('FORK_SUBAGENT')` + 非 Coordinator 模式 + 交互式会话),省略 `subagent_type` 会走 fork path 而非 general-purpose。Fork path 会让子 Agent 继承父 Agent 的完整对话上下文,这是一种完全不同的执行模式(详见第 14 章)。 General-purpose 的设计哲学是"不限制,但给引导" —— 不限制工具,但通过 prompt 引导"不要镀金(gold-plate)"和"不要主动创建文档"。 diff --git "a/docs/16-\344\273\273\345\212\241\346\250\241\345\236\213\344\270\216TaskType\350\260\261\347\263\273.md" "b/docs/16-\344\273\273\345\212\241\346\250\241\345\236\213\344\270\216TaskType\350\260\261\347\263\273.md" index 8dc7cbf..1870f53 100644 --- "a/docs/16-\344\273\273\345\212\241\346\250\241\345\236\213\344\270\216TaskType\350\260\261\347\263\273.md" +++ "b/docs/16-\344\273\273\345\212\241\346\250\241\345\236\213\344\270\216TaskType\350\260\261\347\263\273.md" @@ -1,10 +1,10 @@ # 第 16 章:任务模型与 TaskType 谱系 — Agent 的并发执行引擎 -> 本篇是《深入 Claude Code 源码》系列的第 14 篇。我们将深入分析 Claude Code 如何通过一套统一的任务系统,管理从后台 Shell 命令到多 Agent 并行协作的所有异步工作。 +> 本章是《深入 Claude Code 源码》系列第 16 章。我们将深入分析 Claude Code 如何通过一套统一的任务系统,管理从后台 Shell 命令到多 Agent 并行协作的所有异步工作。 ## 为什么需要任务系统? -在前几篇中,我们已经见过了 Agent 系统(第 12 篇)和内置 Agent 设计模式(第 13 篇)。但有一个关键问题尚未解决:**当模型同时发起多个 Agent、多个后台 Shell 命令、甚至一个"做梦"式的记忆整理任务时,这些并发工作如何被统一管理?** +在前几篇中,我们已经见过了 Agent 系统(第 14 章)和内置 Agent 设计模式(第 15 章)。但有一个关键问题尚未解决:**当模型同时发起多个 Agent、多个后台 Shell 命令、甚至一个"做梦"式的记忆整理任务时,这些并发工作如何被统一管理?** 想象一个真实场景:Coordinator 模式下,主 Agent 同时派出 3 个 Worker 去研究代码库的不同部分,每个 Worker 又可能启动自己的后台 Shell 命令。此时系统需要: @@ -156,7 +156,7 @@ export function getAllTasks(): Task[] { } ``` -注意 `LocalWorkflowTask` 和 `MonitorMcpTask` 使用了 `feature()` 门控 + 条件 `require()` 的模式(与第 1 篇介绍的编译期 DCE 机制一致)。在外部构建中,这两个任务类型的代码会被完全删除。 +注意 `LocalWorkflowTask` 和 `MonitorMcpTask` 使用了 `feature()` 门控 + 条件 `require()` 的模式(与第 1 章介绍的编译期 DCE 机制一致)。在外部构建中,这两个任务类型的代码会被完全删除。 --- @@ -219,7 +219,7 @@ export function updateTaskState( } ``` -这个函数的**引用相等性优化**值得注意:如果 updater 返回了相同的引用(表示无需更新),它跳过 spread 操作,避免触发 React 不必要的重渲染。这与第 3 篇介绍的 Store 中 `Object.is` 检查是同一个模式。 +这个函数的**引用相等性优化**值得注意:如果 updater 返回了相同的引用(表示无需更新),它跳过 spread 操作,避免触发 React 不必要的重渲染。这与第 33 章介绍的 Store 中 `Object.is` 检查是同一个模式。 **evictTerminalTask()** — 终态任务的提前驱逐: @@ -792,7 +792,7 @@ export type TeammateIdentity = { ## 六、上下文隔离:createSubagentContext() 的设计 -> **交叉引用**:`createSubagentContext()` 的完整接口设计在[第 12 篇](./14-Agent系统与SubAgent调用.md)第四节已详述。本节聚焦于它在**并发任务场景**下的特殊考量。 +> **交叉引用**:`createSubagentContext()` 的完整接口设计在[第 14 章](./14-Agent系统与SubAgent调用.md)第四节已详述。本节聚焦于它在**并发任务场景**下的特殊考量。 所有 Agent 协作模式都依赖 `createSubagentContext()` 来创建隔离的执行上下文。这个函数定义在 `utils/forkedAgent.ts` 中,是整个并发体系的安全基石: diff --git "a/docs/17-Coordinator-Cron-\344\270\216\345\256\232\346\227\266\350\260\203\345\272\246.md" "b/docs/17-Coordinator-Cron-\344\270\216\345\256\232\346\227\266\350\260\203\345\272\246.md" index e36ea4b..899d6f8 100644 --- "a/docs/17-Coordinator-Cron-\344\270\216\345\256\232\346\227\266\350\260\203\345\272\246.md" +++ "b/docs/17-Coordinator-Cron-\344\270\216\345\256\232\346\227\266\350\260\203\345\272\246.md" @@ -1,6 +1,6 @@ # 第 17 章:Coordinator、Cron 与定时调度 — 让会话在没人按回车时继续转 -> 本篇是《深入 Claude Code 源码》系列的第 17 篇。前面几篇我们都在讨论「一次对话里模型怎么把事情做完」。这一篇换一个角度:**如果根本没人盯着这个 REPL,事情还能继续往前推吗?** +> 本章是《深入 Claude Code 源码》系列第 17 章。前面几篇我们都在讨论「一次对话里模型怎么把事情做完」。这一篇换一个角度:**如果根本没人盯着这个 REPL,事情还能继续往前推吗?** 答案藏在两块看起来不相干、骨子里互相支撑的代码里: @@ -22,7 +22,7 @@ Coordinator 看起来像「多 Agent 编排」,Cron 看起来像「定时任 两者对外暴露的工具不在同一份注册表里(`AgentTool` / `TaskStopTool` vs `CronCreate` / `CronDelete` / `CronList`),但对**对话循环的入侵点是同一处**:都走 `enqueuePendingNotification()`,都走 `messageQueueManager` 的 `'later'` 优先级,都把自己当成「来自后台的一条用户消息」插队进 query loop。 -意识到这件事之后,第 14 篇(Agent)和第 16 篇(任务模型)里反复出现的 `pendingMessages` / `pendingNotification` 这一组 API,就不再只是 Agent 系统的内部细节,而是这个 CLI 在「无人值守」这一维度上留出来的统一入口。 +意识到这件事之后,第 14 章(Agent)和第 16 章(任务模型)里反复出现的 `pendingMessages` / `pendingNotification` 这一组 API,就不再只是 Agent 系统的内部细节,而是这个 CLI 在「无人值守」这一维度上留出来的统一入口。 这一章按这条线索往下走:先看 Coordinator 怎么把一个普通会话改写成项目经理;再看 Cron 工具家族怎么把「一段 prompt + 一个 cron 表达式」落到磁盘上;最后看 scheduler 怎么在每个 tick 上把到点的任务塞回主循环。 @@ -42,7 +42,7 @@ export function isCoordinatorMode(): boolean { } ``` -两道门一起把。第一道是 `feature('COORDINATOR_MODE')` 这一层编译期开关 -- 按第 19 篇讲过的 DCE 机制,外部构建里这整块逻辑会被完整剔掉,不留任何字节;第二道才是运行时的 env truthy 判断。 +两道门一起把。第一道是 `feature('COORDINATOR_MODE')` 这一层编译期开关 -- 按第 22 章讲过的 DCE 机制,外部构建里这整块逻辑会被完整剔掉,不留任何字节;第二道才是运行时的 env truthy 判断。 为什么要叠两道?Coordinator 模式不是给所有用户的默认行为,它会把模型平时拿在手里的 Read / Write / Edit / Bash 这一摞工具全部抽走,换成完全不一样的工具集。如果不小心被外部用户撞开,体验上会像「Claude 突然不会改文件了」。所以外部构建宁可让这段代码不存在,也不要它存在但默认关。 @@ -91,7 +91,7 @@ const INTERNAL_WORKER_TOOLS = new Set([ Coordinator 解决了「不让用户每一步都按回车」的问题,但它仍然要求**有一个人或一个上游进程在跟模型对话**。如果你想让 Claude Code 在凌晨 3 点自己醒来跑一段质量检查、或者在 5 分钟后自动检查 CI 结果,光靠 Coordinator 就不够 -- 你需要一个真正的定时器。 -`tools/ScheduleCronTool/` 这个目录里没有一个叫 `ScheduleCronTool.ts` 的入口文件,它是一组 leaf tool 的家族:`CronCreateTool` / `CronDeleteTool` / `CronListTool`。第 10 篇讲过 family tool 与 leaf tool 的关系 -- family 在 `` 里只露一个名字,三个 leaf 由 family 工具自己暴露 schema 给模型。Cron 走的就是这一条路。 +`tools/ScheduleCronTool/` 这个目录里没有一个叫 `ScheduleCronTool.ts` 的入口文件,它是一组 leaf tool 的家族:`CronCreateTool` / `CronDeleteTool` / `CronListTool`。第 10 章讲过 family tool 与 leaf tool 的关系 -- family 在 `` 里只露一个名字,三个 leaf 由 family 工具自己暴露 schema 给模型。Cron 走的就是这一条路。 ### 3.1 三个 leaf 工具的边界 @@ -343,7 +343,7 @@ Coordinator 的方式:把主线程变成项目经理,让被派出的 Worker Cron 的方式:在没有任何模型在跑的时刻,由 `setInterval(1000)` 这个小心脏来产生「下一回合」的契机,把 prompt 通过同一个 `enqueuePendingNotification()` 塞回主线程的 query loop。 -两条线最终都收口在 `'later'` 优先级队列上 -- 这是 messageQueueManager 留出来的「来自后台」的入口。第 16 篇讲任务通知机制时已经介绍过这套队列的三层优先级(now / next / later),现在你看到的是这套机制的全部使用方:来自后台任务的通知、来自 cron 的触发、来自 teammate 的 idle 信号、来自 Coordinator 派出去的 Worker 完成报告 -- 它们走的都是同一条入口。 +两条线最终都收口在 `'later'` 优先级队列上 -- 这是 messageQueueManager 留出来的「来自后台」的入口。第 16 章讲任务通知机制时已经介绍过这套队列的三层优先级(now / next / later),现在你看到的是这套机制的全部使用方:来自后台任务的通知、来自 cron 的触发、来自 teammate 的 idle 信号、来自 Coordinator 派出去的 Worker 完成报告 -- 它们走的都是同一条入口。 这也回答了为什么 Cron 系统的 jitter 配置那么细。如果 cron 触发跟用户输入抢同一个 `'now'` 优先级,整点的雷阵雨会直接打在用户体验上。`'later'` 这一优先级的意义就在于:用户输入永远先处理完,后台的事再说。Cron 的 jitter + `'later'` 优先级 + WORKLOAD_CRON 的 QoS 分类,三者叠在一起,构成了一个相当克制的「后台任务不要打扰前台」的工程承诺。 diff --git "a/docs/18-MCP\345\215\217\350\256\256\345\256\236\347\216\260.md" "b/docs/18-MCP\345\215\217\350\256\256\345\256\236\347\216\260.md" index 353fe84..b58b591 100644 --- "a/docs/18-MCP\345\215\217\350\256\256\345\256\236\347\216\260.md" +++ "b/docs/18-MCP\345\215\217\350\256\256\345\256\236\347\216\260.md" @@ -1,6 +1,6 @@ # 第 18 章:MCP 协议实现 — 连接外部工具的标准化桥梁 -> 本篇是《深入 Claude Code 源码》系列的第 15 篇。我们将剖析 Claude Code 如何实现 Model Context Protocol(MCP),包括类型系统设计、多层配置合并、传输层适配、连接生命周期管理、Tool 发现与代理,以及认证体系。 +> 本章是《深入 Claude Code 源码》系列第 18 章。我们将剖析 Claude Code 如何实现 Model Context Protocol(MCP),包括类型系统设计、多层配置合并、传输层适配、连接生命周期管理、Tool 发现与代理,以及认证体系。 ## 为什么需要 MCP? diff --git "a/docs/19-\346\235\203\351\231\220\347\263\273\347\273\237\344\270\216\350\277\234\347\250\213\346\235\203\351\231\220\345\233\236\347\201\214.md" "b/docs/19-\346\235\203\351\231\220\347\263\273\347\273\237\344\270\216\350\277\234\347\250\213\346\235\203\351\231\220\345\233\236\347\201\214.md" index 76c403c..a7853be 100644 --- "a/docs/19-\346\235\203\351\231\220\347\263\273\347\273\237\344\270\216\350\277\234\347\250\213\346\235\203\351\231\220\345\233\236\347\201\214.md" +++ "b/docs/19-\346\235\203\351\231\220\347\263\273\347\273\237\344\270\216\350\277\234\347\250\213\346\235\203\351\231\220\345\233\236\347\201\214.md" @@ -1,6 +1,6 @@ # 第 19 章:权限系统与远程权限回灌 — AI 安全的最后一道防线 -> 本篇是《深入 Claude Code 源码》系列第 16 篇。我们将深入权限系统的完整架构:从权限模式、规则体系、决策管线到 AI Classifier 辅助判断,揭示一个生产级 AI Agent 如何在"自动化"与"安全"之间找到平衡。 +> 本章是《深入 Claude Code 源码》系列第 19 章。我们将深入权限系统的完整架构:从权限模式、规则体系、决策管线到 AI Classifier 辅助判断,揭示一个生产级 AI Agent 如何在"自动化"与"安全"之间找到平衡。 ## 为什么权限系统是 AI 产品中最关键的模块? @@ -12,7 +12,7 @@ 2. **风险谱系宽广**:从读取文件(无害)到 `rm -rf /`(灾难性),所有操作都通过同一个工具接口 3. **效率与安全的矛盾**:每次都弹确认对话框会让用户抓狂,但完全自动化又有安全风险 -Claude Code 的权限系统用 **多层防线 + 渐进式信任** 的方式解决了这个矛盾。本篇将从三个层面解析: +Claude Code 的权限系统用 **多层防线 + 渐进式信任** 的方式解决了这个矛盾。本章将从三个层面解析: 1. **权限模式(Permission Mode)**:用户的全局信任级别 2. **规则系统(Permission Rules)**:细粒度的 allow/deny/ask 规则 diff --git "a/docs/20-Hooks\347\263\273\347\273\237.md" "b/docs/20-Hooks\347\263\273\347\273\237.md" index 5675326..0b48e85 100644 --- "a/docs/20-Hooks\347\263\273\347\273\237.md" +++ "b/docs/20-Hooks\347\263\273\347\273\237.md" @@ -1,6 +1,6 @@ # 第 20 章:Hooks 系统 — 用 Shell 命令扩展 AI 行为 -> 本篇是《深入 Claude Code 源码》系列第 18 篇。我们将深入 Hooks 系统的完整实现,揭示 Claude Code 如何让用户在 AI 生命周期的关键节点注入自定义逻辑,以及这套系统在安全性、可扩展性和性能上的精巧设计。 +> 本章是《深入 Claude Code 源码》系列第 20 章。我们将深入 Hooks 系统的完整实现,揭示 Claude Code 如何让用户在 AI 生命周期的关键节点注入自定义逻辑,以及这套系统在安全性、可扩展性和性能上的精巧设计。 ## 为什么需要 Hooks? diff --git "a/docs/21-Skill-Plugin-OutputStyle\344\270\211\346\211\251\345\261\225\347\202\271.md" "b/docs/21-Skill-Plugin-OutputStyle\344\270\211\346\211\251\345\261\225\347\202\271.md" index 9ecba05..9d73ec1 100644 --- "a/docs/21-Skill-Plugin-OutputStyle\344\270\211\346\211\251\345\261\225\347\202\271.md" +++ "b/docs/21-Skill-Plugin-OutputStyle\344\270\211\346\211\251\345\261\225\347\202\271.md" @@ -1,6 +1,6 @@ # 第 21 章:Skill / Plugin / Output Style 三扩展点 — 基于源码理解扩展点 -> 本篇是《深入 Claude Code 源码》系列的第 24 篇。前面 23 篇我们深入分析了 Claude Code 的内部架构,现在是时候"反过来用"了 —— 站在扩展开发者的视角,理解如何编写自定义 Agent、Skill、Plugin 和 Hook 脚本。本篇的独特价值在于:每一个配置字段、每一个行为约定,我们都能追溯到源码中的具体实现。 +> 本章是《深入 Claude Code 源码》系列第 21 章。前面 20 章我们深入分析了 Claude Code 的内部架构,现在是时候"反过来用"了 —— 站在扩展开发者的视角,理解如何编写自定义 Agent、Skill、Plugin 和 Hook 脚本。本章的独特价值在于:每一个配置字段、每一个行为约定,我们都能追溯到源码中的具体实现。 ## 为什么需要理解扩展点? @@ -17,9 +17,9 @@ Hook 脚本 → Skill 文件 → Agent 定义 → Plugin 包 - **Agent**:一个 Markdown 文件,定义一个独立的 AI 角色(有自己的 prompt、工具集、模型) - **Plugin**:一个完整的目录包,可以同时提供 Skill、Agent、Hook、MCP 服务器 -除了这四档"行为面"扩展,还有一条很容易被忽略的"体验面"扩展路径 —— **Output Style**。它不改变模型能调哪些工具、也不在循环里塞 prompt,而是在 system prompt 末端追加一段 `# Output Style: ...` 段落(`constants/prompts.ts:151-157`),把"该用什么腔调说话"显式写到模型面前;同时它可以让 Claude Code 在拼装 system prompt 时选择性地省略默认那段"写代码助手任务清单"(`constants/prompts.ts:564-567` 中 `getSimpleDoingTasksSection()` 的开关)。Plugin 可以把自己的 Output Style 一起带进来,但 Output Style 本身也能独立存在于 `.claude/output-styles/` 目录里,单独使用。所以读完前四档之后,本篇会专门留一节给它,把它放回到 Skill / Plugin 的同一张图里看。 +除了这四档"行为面"扩展,还有一条很容易被忽略的"体验面"扩展路径 —— **Output Style**。它不改变模型能调哪些工具、也不在循环里塞 prompt,而是在 system prompt 末端追加一段 `# Output Style: ...` 段落(`constants/prompts.ts:151-157`),把"该用什么腔调说话"显式写到模型面前;同时它可以让 Claude Code 在拼装 system prompt 时选择性地省略默认那段"写代码助手任务清单"(`constants/prompts.ts:564-567` 中 `getSimpleDoingTasksSection()` 的开关)。Plugin 可以把自己的 Output Style 一起带进来,但 Output Style 本身也能独立存在于 `.claude/output-styles/` 目录里,单独使用。所以读完前四档之后,本章会专门留一节给它,把它放回到 Skill / Plugin 的同一张图里看。 -本篇将逐一解析这四个扩展点的编写方式,并指出它们在源码中是如何被发现、解析和执行的。 +本章将逐一解析这四个扩展点的编写方式,并指出它们在源码中是如何被发现、解析和执行的。 --- @@ -336,7 +336,7 @@ You are a test runner agent. Your job is to... ### 2.3 工具约束三策略 -Agent 的工具控制有三种策略,在 `runAgent()` 中实现了三层过滤(详见第 12 篇): +Agent 的工具控制有三种策略,在 `runAgent()` 中实现了三层过滤(详见第 14 章): **白名单**(`tools`):只允许使用列出的工具。 ```yaml @@ -579,7 +579,7 @@ type LoadedPlugin = { ### 4.1 Hook 配置格式 -Hook 可以在三个地方配置:settings.json、Agent frontmatter、Skill frontmatter。格式统一为三层嵌套结构(详见第 18 篇): +Hook 可以在三个地方配置:settings.json、Agent frontmatter、Skill frontmatter。格式统一为三层嵌套结构(详见第 20 章): ```json { @@ -851,7 +851,7 @@ const name = `${pluginName}:${baseStyleName}` ### 跟 Skill / Plugin 是什么关系 -回到本篇开头那个问题:Output Style 和前面四档究竟是什么关系? +回到本章开头那个问题:Output Style 和前面四档究竟是什么关系? 如果说 Skill 是给模型"一段当前回合可以参考的指令",那么 Output Style 改的是模型"做完每一回合后该用什么腔调说话"。两者的生命周期完全不同 —— Skill 的注入一般只活在它被调用的那一回合(或 fork 出去的 sub-agent 内),而 Output Style 一旦切换就贯穿整段会话,直到用户再次 `/output-style` 切走。 diff --git "a/docs/22-FeatureFlag\344\270\216\347\274\226\350\257\221\346\234\237\344\274\230\345\214\226.md" "b/docs/22-FeatureFlag\344\270\216\347\274\226\350\257\221\346\234\237\344\274\230\345\214\226.md" index a36f106..34f47bb 100644 --- "a/docs/22-FeatureFlag\344\270\216\347\274\226\350\257\221\346\234\237\344\274\230\345\214\226.md" +++ "b/docs/22-FeatureFlag\344\270\216\347\274\226\350\257\221\346\234\237\344\274\230\345\214\226.md" @@ -1,6 +1,6 @@ # 第 22 章:Feature Flag 与编译期优化 — 同一份代码构建两个产品 -> 本篇揭示 Claude Code 如何用一套代码库同时维护内部版和外部版两个产品。你将看到 Bun 的 `feature()` 编译期常量折叠、`process.env.USER_TYPE` 构建时 `--define` 常量、`MACRO.*` 构建时值注入、以及 GrowthBook A/B 测试平台如何在不同的时间维度上协同工作。 +> 本章揭示 Claude Code 如何用一套代码库同时维护内部版和外部版两个产品。你将看到 Bun 的 `feature()` 编译期常量折叠、`process.env.USER_TYPE` 构建时 `--define` 常量、`MACRO.*` 构建时值注入、以及 GrowthBook A/B 测试平台如何在不同的时间维度上协同工作。 ## 为什么需要多层 Feature Flag? @@ -577,7 +577,7 @@ Feature Flag 最大的风险是 mid-session 翻转导致不一致状态。Claude ### 6.1 Latch 模式(单向锁存) -在 Prompt Cache 系统中(第 7 篇详述),多个 flag 使用 **Latch 模式**:一旦开启就不再关闭: +在 Prompt Cache 系统中(第 8 章详述),多个 flag 使用 **Latch 模式**:一旦开启就不再关闭: > AFK header / cache editing header / fast mode header 一旦开启不关闭,防止 mid-session 翻转破坏缓存。 @@ -596,7 +596,7 @@ const TOOL_SCHEMA_CACHE = new Map() ### 6.3 QueryConfig 刻意排除 feature() ``` -// query/config.ts — 第 5 篇提到的设计 +// query/config.ts — 第 5 章提到的设计 // QueryConfig 是不可变环境快照,刻意排除 feature() gate 以保留 DCE ``` diff --git "a/docs/23-\345\256\242\346\210\267\347\253\257\344\274\240\350\276\223\344\270\216API\351\207\215\350\257\225.md" "b/docs/23-\345\256\242\346\210\267\347\253\257\344\274\240\350\276\223\344\270\216API\351\207\215\350\257\225.md" index a83eb13..bbdcbfc 100644 --- "a/docs/23-\345\256\242\346\210\267\347\253\257\344\274\240\350\276\223\344\270\216API\351\207\215\350\257\225.md" +++ "b/docs/23-\345\256\242\346\210\267\347\253\257\344\274\240\350\276\223\344\270\216API\351\207\215\350\257\225.md" @@ -1,6 +1,6 @@ # 第 23 章:客户端传输与 API 重试 — 面向不可靠网络的鲁棒设计 -> 本篇是《深入 Claude Code 源码》系列第 20 篇。我们将沿着一次 `messages.create` 调用从应用层进入传输层,看一个生产级 AI CLI 如何在不可靠网络、过期凭证、容量过载、TLS 拦截代理、空闲连接被回收等多重不确定性下保持稳定运行。 +> 本章是《深入 Claude Code 源码》系列第 23 章。我们将沿着一次 `messages.create` 调用从应用层进入传输层,看一个生产级 AI CLI 如何在不可靠网络、过期凭证、容量过载、TLS 拦截代理、空闲连接被回收等多重不确定性下保持稳定运行。 ## 为什么需要如此复杂的错误恢复? @@ -15,7 +15,7 @@ 如果每种错误都让用户手动重试,用户体验将是灾难性的 —— 想象你在一个需要 10 分钟的 agentic 编程任务中途遇到一次 529,不得不从头开始。再想象远程会话用户的笔记本盖上 20 分钟,醒来时希望 CLI 自动追上中间漏掉的所有事件,而不是抛出 `ECONNRESET` 让一切归零。 -Claude Code 的解决方案是一套**两条主线 + 一组共享工具**的架构:一条是面向模型 API 的 `withRetry` 通用重试层,处理 429/529/认证/上下文溢出;另一条是面向会话服务的传输层(WebSocket / SSE / Hybrid 三态),处理长连接断线、批量上传、状态合并;两条主线共享同一套错误分类、SSL 提示、HTML 清洗工具。本篇按这个层次逐段拆解,最后给出可迁移的设计模式。流式与非流式的双模式切换、404 端点降级在源码里属于 `claude.ts` 的内部职责,下一篇 C25 会专门处理,本篇不再展开。 +Claude Code 的解决方案是一套**两条主线 + 一组共享工具**的架构:一条是面向模型 API 的 `withRetry` 通用重试层,处理 429/529/认证/上下文溢出;另一条是面向会话服务的传输层(WebSocket / SSE / Hybrid 三态),处理长连接断线、批量上传、状态合并;两条主线共享同一套错误分类、SSL 提示、HTML 清洗工具。本章按这个层次逐段拆解,最后给出可迁移的设计模式。流式与非流式的双模式切换、404 端点降级在源码里属于 `claude.ts` 的内部职责,下一篇 C25 会专门处理,本章不再展开。 --- @@ -412,7 +412,7 @@ if (isStaleConnection && getFeatureValue_CACHED_MAY_BE_STALE( Claude Code 的 API 调用默认走流式(SSE),失败时自动降级到非流式;某些不支持 SSE 端点的代理返回 404 时也会走相同的降级路径。这一组机制涉及 `claude.ts` 内部的 `queryModel` 主流程、Stream Idle Watchdog、Stall 监控、`executeNonStreamingRequest` 包装、以及 404 端点降级,链路深、状态多,单独成章更易维持代码与叙事的对齐度。 -> 这部分内容已迁移到[第 25 篇:DirectConnect 与上游代理](./25-DirectConnect-与上游代理.md)。本节作为占位锚点保留,便于从历史目录直接跳转,并提示读者:流式降级与本篇的 `withRetry` 主循环、传输层长连接是三条互不相同的代码路径,请勿混淆。 +> 这部分内容已迁移到[第 34 章:DirectConnect 与上游代理](./25-DirectConnect-与上游代理.md)。本节作为占位锚点保留,便于从历史目录直接跳转,并提示读者:流式降级与本章的 `withRetry` 主循环、传输层长连接是三条互不相同的代码路径,请勿混淆。 ## 客户端传输层:WebSocket / SSE / Hybrid 三态 diff --git "a/docs/24-Bridge-IPC-\344\270\216\350\277\234\347\250\213\344\274\232\350\257\235.md" "b/docs/24-Bridge-IPC-\344\270\216\350\277\234\347\250\213\344\274\232\350\257\235.md" index 918c4e9..7fb03b2 100644 --- "a/docs/24-Bridge-IPC-\344\270\216\350\277\234\347\250\213\344\274\232\350\257\235.md" +++ "b/docs/24-Bridge-IPC-\344\270\216\350\277\234\347\250\213\344\274\232\350\257\235.md" @@ -1,16 +1,16 @@ # 第 24 章:Bridge IPC 与远程会话 — 把本地 CLI 接到手机和浏览器上的那条线 -> 本篇是《深入 Claude Code 源码》系列的第 24 篇。前面 23 篇我们都把 Claude Code 当成一个跑在终端里的进程:你敲 `claude`、它开 REPL、你按回车、它回答、退出。这一篇换一个场景:**你人在地铁上,会话却得继续跑**。 +> 本章是《深入 Claude Code 源码》系列第 24 章。前面 23 章我们都把 Claude Code 当成一个跑在终端里的进程:你敲 `claude`、它开 REPL、你按回车、它回答、退出。这一篇换一个场景:**你人在地铁上,会话却得继续跑**。 读者第一次看到 `bridge/` 这个目录、加上一旁同样陌生的 `remote/`、`commands/bridge/`、`commands/remote-setup/`、`commands/remote-env/`,第一反应往往是「这不是把一个 CLI 改成 server 了吗」。其实没那么夸张——这一摞代码做的只是一件事:**让你笔记本上跑着的那个 Claude Code 进程,对外暴露成一条能被手机点亮、被浏览器接管、被 Web UI 续写指令的会话**。 为什么这件事值得单独一篇?因为它把一个看似单进程的 CLI 拆成了**两端**:你按下回车的那一端,和真正动手干活的那一端,被一条由 WebSocket、JWT、子进程、控制帧、消息转译胶水起来的链路串了起来。链路里任何一段出错,对面就只看到一句「断线了」。Claude Code 在这条线上的工程心思和前面看过的对话循环、工具系统、Agent 编排是同一种气质——把异步、失败、续期都写在明面上,把「正常路径」缩到很小一段。 -> **风格说明**:本章对齐第 1 篇《项目全景》与第 2 篇《启动优化》的写法——以「问题先行 → 源码佐证 → 设计推演」三段式推进,结尾以「可迁移的设计模式 + 实战示例」收束。 +> **风格说明**:本章对齐第 1 章《项目全景》与第 2 章《启动优化》的写法——以「问题先行 → 源码佐证 → 设计推演」三段式推进,结尾以「可迁移的设计模式 + 实战示例」收束。 > > 本章因涉及未公开的服务端契约,对 wire 协议帧布局、企业安全策略、上游服务器 endpoint 命名仅作接口层叙述,省略具体二进制 layout 与签名算法细节。阅读时请把出现的 URL 路径理解成「一类端点」,不要当作公开稳定契约。 -本篇将回答四个核心问题: +本章将回答四个核心问题: 1. **为什么不能用一条 HTTP 长连接解决全部?** — 三个绕不开的协议需求把架构推成两层 2. **一台本地机器是怎么被服务端「派单」的?** — `register → poll → work secret` 的握手协议 @@ -122,7 +122,7 @@ const TOOL_VERBS: Record = { 这张映射表很有趣。它把工具的英文名换成进行时——`Read` 变成 `Reading`、`Bash` 变成 `Running`。手机上你看到的「正在读 `package.json`」「正在跑 `npm test`」就是这张表的直接产物。它不在服务端、也不在前端,就在你的本地 `sessionRunner` 里。这种「把状态文案的源头放到最靠近事实的那一端」是 Claude Code 反复出现的工程偏好——上一篇 Cron 调度里我们就看到过同样的写法。 -`extractActivities()` 函数负责扫子进程的 stdout JSON 流,把 `tool_use` / `text` / `result` / `error` 这四类事件转成 activity 上报。它和第 5 篇看过的 SSE 解码逻辑同源——同一份 JSON 块,在终端里你看到的是渲染好的彩色界面,从这里看出去就是一条条扁平的事件。 +`extractActivities()` 函数负责扫子进程的 stdout JSON 流,把 `tool_use` / `text` / `result` / `error` 这四类事件转成 activity 上报。它和第 5 章看过的 SSE 解码逻辑同源——同一份 JSON 块,在终端里你看到的是渲染好的彩色界面,从这里看出去就是一条条扁平的事件。 子进程怎么起?`spawnScriptArgs()`(`bridge/sessionRunner.ts:47-54` 附近)会判断当前 `claude` 是编译进 bundled binary 的还是 npm dev 模式,给出不同的命令行。源码注释直接指到上游 issue [anthropics/claude-code#28334](https://github.com/anthropics/claude-code/issues/28334)——记录的是「编译态 vs npm 态在子进程参数解析上的行为差」。读者翻 git log 会看到它前后被改过好几次,每一次都是「编译进二进制带来的细节代价」。 @@ -143,7 +143,7 @@ const TOOL_VERBS: Record = { // buildCCRv2SdkUrl: 用 worker_jwt + worker_epoch 拼出 v2 ingress URL ``` -它有一道单独的 feature flag `tengu_bridge_repl_v2` 把守,**仅** REPL 走这条路;daemon 模式和 print 模式还留在环境制下。这种「老的没废、新的并跑」的策略和第 19 篇编译期优化里讨论过的灰度模式一脉相承——给一条新链路一个独立 flag,让灰度发布期间老路径继续兜底。 +它有一道单独的 feature flag `tengu_bridge_repl_v2` 把守,**仅** REPL 走这条路;daemon 模式和 print 模式还留在环境制下。这种「老的没废、新的并跑」的策略和第 22 章编译期优化里讨论过的灰度模式一脉相承——给一条新链路一个独立 flag,让灰度发布期间老路径继续兜底。 `worker_epoch` 是这条链路里的小亮点。每调用一次 `/bridge` 端点,服务端就把会话的 epoch 加 1,并返回给客户端。客户端在所有后续 WebSocket 帧上都带这个 epoch;如果同一条会话被另一台机器**抢占**——你换到办公室那台笔记本接着用——旧的 epoch 立刻失效。这把 `register` 时代靠服务端单独维护 worker 注册表的事情压缩成了一个递增整数:更便宜、更不容易写错。 @@ -185,7 +185,7 @@ if (message.type === 'control_response') { /* 控制帧确认 */ } if (isSDKMessage(message)) { this.callbacks.onMessage(message) } ``` -正常的对话消息直接交给上层 UI 渲染;控制请求走单独的 `handleControlRequest`(`remote/RemoteSessionManager.ts:189-214`);控制取消走单独的清理路径。这种「按消息 type 分发」的写法你在第 5 篇 query 主循环里见过——同一种风格在网络层、对话层、UI 层反复出现,是这套代码的稳态。 +正常的对话消息直接交给上层 UI 渲染;控制请求走单独的 `handleControlRequest`(`remote/RemoteSessionManager.ts:189-214`);控制取消走单独的清理路径。这种「按消息 type 分发」的写法你在第 5 章 query 主循环里见过——同一种风格在网络层、对话层、UI 层反复出现,是这套代码的稳态。 `RemoteSessionConfig` 里还有一个值得留意的小字段 `viewerOnly`(`remote/RemoteSessionManager.ts:56-61` 注释):当对面是 `claude assistant` 这类「只想看一眼」的客户端时,Ctrl+C / Escape 不会真的把 interrupt 信号发给远端;60 秒断线超时也被禁用;会话标题永远不被更新。这是把「观察者」和「驾驶者」在协议层就分开——不需要服务端帮忙,本地包装类自己就知道自己处于哪种身份。 diff --git "a/docs/25-DirectConnect-\344\270\216\344\270\212\346\270\270\344\273\243\347\220\206.md" "b/docs/25-DirectConnect-\344\270\216\344\270\212\346\270\270\344\273\243\347\220\206.md" index 6dc8f1f..6bbff93 100644 --- "a/docs/25-DirectConnect-\344\270\216\344\270\212\346\270\270\344\273\243\347\220\206.md" +++ "b/docs/25-DirectConnect-\344\270\216\344\270\212\346\270\270\344\273\243\347\220\206.md" @@ -1,12 +1,12 @@ # 第 25 章:DirectConnect 与上游代理 — 把同一个 CLI 接到远端服务端和企业代理两条暗线上 -> 本篇是《深入 Claude Code 源码》系列的第 30 篇。前一章我们看的是 Bridge IPC:本地 CLI 怎么被手机和浏览器接管。这一章把视角再切一次——这次本地 CLI 不是被接管者,而是接管者。我们看两段乍看八竿子打不着、实际上对称得很的代码:`server/` 和 `upstreamproxy/`。 +> 本章是《深入 Claude Code 源码》系列第 25 章。前一章我们看的是 Bridge IPC:本地 CLI 怎么被手机和浏览器接管。这一章把视角再切一次——这次本地 CLI 不是被接管者,而是接管者。我们看两段乍看八竿子打不着、实际上对称得很的代码:`server/` 和 `upstreamproxy/`。 > -> **风格说明**:本章对齐第 1 篇《项目全景》与第 2 篇《启动优化》的写法——以「问题先行 → 源码佐证 → 设计推演」三段式推进,结尾以「可迁移的设计模式 + 实战示例」收束。 +> **风格说明**:本章对齐第 1 章《项目全景》与第 2 章《启动优化》的写法——以「问题先行 → 源码佐证 → 设计推演」三段式推进,结尾以「可迁移的设计模式 + 实战示例」收束。 > > **法律边界**:本章涉及企业内网拓扑、未公开的上游服务端契约、CCR 控制面 endpoint 与认证协议、MITM 注入策略。对 wire 帧布局、签发流程、密钥派生与审计字段仅作接口层叙述,省略具体协议层细节与企业安全配置项。出现的 URL 路径请按「一类端点」理解,不要当作公开稳定契约。 -本篇将回答四个核心问题: +本章将回答四个核心问题: 1. **DirectConnect 是什么?** — 一条让本地 REPL 变成远端会话客户端的薄通道 2. **Upstream Proxy 是什么?** — 一条把容器内所有出网流量劫持到企业代理的暗线 diff --git "a/docs/26-Ink\346\241\206\346\236\266\346\267\261\345\272\246\345\256\232\345\210\266.md" "b/docs/26-Ink\346\241\206\346\236\266\346\267\261\345\272\246\345\256\232\345\210\266.md" index 2dfcf9d..ad2d136 100644 --- "a/docs/26-Ink\346\241\206\346\236\266\346\267\261\345\272\246\345\256\232\345\210\266.md" +++ "b/docs/26-Ink\346\241\206\346\236\266\346\267\261\345\272\246\345\256\232\345\210\266.md" @@ -1,6 +1,6 @@ # 第 26 章:Ink 框架深度定制 — 在终端中运行 React -> 本篇深入 Claude Code 的 forked Ink 框架(`ink/` 目录,96 个 `.ts` / `.tsx` 文件、19,842 行),揭示如何在终端中构建一个完整的 React 渲染引擎:从自定义 Reconciler、Yoga 布局、双缓冲渲染管线,到虚拟滚动、鼠标事件、文本选择等深度定制。这一版还要把视线拉到与 `ink/` 配套的 `native-ts/` 目录——团队把原本走 WASM / NAPI 的三段原生模块(`yoga-layout` / `color-diff` / `file-index`)重新写成了纯 TypeScript 实现,Ink 的布局引擎就是其中第一段。 +> 本章深入 Claude Code 的 forked Ink 框架(`ink/` 目录,96 个 `.ts` / `.tsx` 文件、19,842 行),揭示如何在终端中构建一个完整的 React 渲染引擎:从自定义 Reconciler、Yoga 布局、双缓冲渲染管线,到虚拟滚动、鼠标事件、文本选择等深度定制。这一版还要把视线拉到与 `ink/` 配套的 `native-ts/` 目录——团队把原本走 WASM / NAPI 的三段原生模块(`yoga-layout` / `color-diff` / `file-index`)重新写成了纯 TypeScript 实现,Ink 的布局引擎就是其中第一段。 ## 为什么要 Fork Ink? diff --git "a/docs/27-\347\273\204\344\273\266\344\270\216\350\256\276\350\256\241\347\263\273\347\273\237.md" "b/docs/27-\347\273\204\344\273\266\344\270\216\350\256\276\350\256\241\347\263\273\347\273\237.md" index 2af9fbc..897e7bc 100644 --- "a/docs/27-\347\273\204\344\273\266\344\270\216\350\256\276\350\256\241\347\263\273\347\273\237.md" +++ "b/docs/27-\347\273\204\344\273\266\344\270\216\350\256\276\350\256\241\347\263\273\347\273\237.md" @@ -1,12 +1,12 @@ # 第 27 章:组件与设计系统 — 终端 UI 的组件化实践 -> 本篇深入 Claude Code 的 `components/design-system/` 目录,解析一套完整的终端 UI 设计系统是如何构建的 —— 从主题系统、基础组件到工具 UI 协议。 +> 本章深入 Claude Code 的 `components/design-system/` 目录,解析一套完整的终端 UI 设计系统是如何构建的 —— 从主题系统、基础组件到工具 UI 协议。 ## 为什么终端应用需要设计系统? 当你用 `console.log` 输出几行文字时,不需要设计系统。但当你的终端应用有**流式输出、多工具并行进度条、权限确认对话框、Tab 切换面板、模糊搜索选择器、dark/light 主题切换**时,情况就完全不同了。 -Claude Code 面对的正是这样的复杂度。它的 UI 不是静态的信息打印,而是一个**高度动态的交互界面**。在第 21 篇中我们看到了 forked Ink 框架如何在终端中运行 React;本篇则关注在这个框架之上,团队如何构建了一套**可复用的组件化设计系统**。 +Claude Code 面对的正是这样的复杂度。它的 UI 不是静态的信息打印,而是一个**高度动态的交互界面**。在第 26 章中我们看到了 forked Ink 框架如何在终端中运行 React;本章则关注在这个框架之上,团队如何构建了一套**可复用的组件化设计系统**。 这套设计系统回答三个核心问题: 1. **颜色从哪来?** — 6 套主题 × 80+ 语义化颜色 token 的主题系统 diff --git "a/docs/28-Keybindings-Vim\344\270\216Voice\350\276\223\345\205\245.md" "b/docs/28-Keybindings-Vim\344\270\216Voice\350\276\223\345\205\245.md" index 15a8752..9602901 100644 --- "a/docs/28-Keybindings-Vim\344\270\216Voice\350\276\223\345\205\245.md" +++ "b/docs/28-Keybindings-Vim\344\270\216Voice\350\276\223\345\205\245.md" @@ -1,6 +1,6 @@ # 第 28 章:Keybindings、Vim 模式与 Voice 输入 — 终端输入层的三种解释 -> 本篇是《深入 Claude Code 源码》系列的第 31 篇。我们把 `keybindings/`、`vim/`、`voice/` 三套子系统放在一起讲,因为它们解决的是同一个问题的不同切面:**Ink 给的是"按下了哪个键",这一层要回答的是"这一下按键意味着什么"**。 +> 本章是《深入 Claude Code 源码》系列第 28 章。我们把 `keybindings/`、`vim/`、`voice/` 三套子系统放在一起讲,因为它们解决的是同一个问题的不同切面:**Ink 给的是"按下了哪个键",这一层要回答的是"这一下按键意味着什么"**。 ## 为什么把这三块放在同一篇里讲? @@ -10,9 +10,9 @@ 放在书脊上看: -- 第 21 篇讲了 Ink 的渲染与组件系统; -- 第 27 篇讲了多模态文件上传; -- 第 31 篇要补的,是介于"原始按键"与"高层意图"之间的那一层胶水。 +- 第 26 章讲了 Ink 的渲染与组件系统; +- 第 27 章讲了组件与设计系统; +- 第 28 章要补的,是介于"原始按键"与"高层意图"之间的那一层胶水。 三套子系统的目录是分开的,但共同点很清晰:都坐在 Ink 的 `useInput` 之上,又都要绕开 Ink 默认的"按一下出一个字符"的语义。 @@ -22,7 +22,7 @@ vim/ # 5 个文件 — 按键序列 → 一次完整 vim 命令 voice/, services/voice*, hooks/useVoice* # 按住一个键 → 一段 PCM 流 ``` -本篇按三段来讲:先讲 Keybindings,因为它是其他两套的基础设施;再讲 Vim 状态机;最后讲 Voice 推流——这是少数几个会跳出 React 渲染循环、绕开 Ink、直接和 Node 子进程、原生模块、WebSocket 对话的子系统。 +本章按三段来讲:先讲 Keybindings,因为它是其他两套的基础设施;再讲 Vim 状态机;最后讲 Voice 推流——这是少数几个会跳出 React 渲染循环、绕开 Ink、直接和 Node 子进程、原生模块、WebSocket 对话的子系统。 --- diff --git "a/docs/29-Buddy\345\256\240\347\211\251.md" "b/docs/29-Buddy\345\256\240\347\211\251.md" index bbca91c..71d9601 100644 --- "a/docs/29-Buddy\345\256\240\347\211\251.md" +++ "b/docs/29-Buddy\345\256\240\347\211\251.md" @@ -1,6 +1,6 @@ # 第 29 章:Buddy 宠物 — 在 PromptInput 边上养一只随机生成的小动物 -> 本篇是《深入 Claude Code 源码》系列第 32 篇。我们要看的,是 `buddy/` 目录下的 6 个源码文件,以及它们怎么悄悄接进 REPL、PromptInput、配置、附件、消息流——最后在一个本来全是黑底白字的终端里,挤出一只会眨眼、会冒话框、会被你按 Enter 摸一下脑袋的小动物。 +> 本章是《深入 Claude Code 源码》系列第 29 章。我们要看的,是 `buddy/` 目录下的 6 个源码文件,以及它们怎么悄悄接进 REPL、PromptInput、配置、附件、消息流——最后在一个本来全是黑底白字的终端里,挤出一只会眨眼、会冒话框、会被你按 Enter 摸一下脑袋的小动物。 ## 为什么单独写一篇讲一只小动物? @@ -23,7 +23,7 @@ Claude Code 的答案可以一句话概括:**把"骨"和"魂"切开存,把 - `prompt.ts` 管给大模型的"第三人称介绍" - `useBuddyNotification.tsx` 管短窗口里的彩虹色入口提示 -本篇也按这个顺序拆:先看"骨与魂"怎么切(§一)、18 个物种的名字怎么躲过打包扫描(§二)、500ms 一拍的眨眼摸头怎么转起来(§三)、窄屏和全屏两种排版怎么各让一步(§四)、第三人称介绍怎么把小动物钉在"旁观者"位置(§五)、最后看 `/buddy` 入口、彩虹高亮、footer 和两道编译门如何把 Buddy 整体藏起来(§六)。后两节是可以照搬到自己项目里的设计模式(§七)和一份"想在 PromptInput 边上塞个小装饰" walkthrough(§八)。 +本章也按这个顺序拆:先看"骨与魂"怎么切(§一)、18 个物种的名字怎么躲过打包扫描(§二)、500ms 一拍的眨眼摸头怎么转起来(§三)、窄屏和全屏两种排版怎么各让一步(§四)、第三人称介绍怎么把小动物钉在"旁观者"位置(§五)、最后看 `/buddy` 入口、彩虹高亮、footer 和两道编译门如何把 Buddy 整体藏起来(§六)。后两节是可以照搬到自己项目里的设计模式(§七)和一份"想在 PromptInput 边上塞个小装饰" walkthrough(§八)。 --- @@ -427,9 +427,9 @@ const allCommands = [ ]; ``` -`feature('BUDDY')` 是 [§第 19 篇](./22-FeatureFlag与编译期优化.md) 里讲过的"compile-time feature flag"——构建时根据当前渠道把它折叠成 `true` 或 `false`,配合 `require(…)` 的 lazy resolve 和 tree-shaker,整张 buddy 命令子树在 `feature('BUDDY') === false` 的产物里彻底消失。再加上 `useBuddyNotification.tsx` 里 `'external' === 'ant'` 这种字面量比较,构建时整段表达式可以直接被替换成常量布尔,余下的代码被压成无效分支删掉。 +`feature('BUDDY')` 是 [§第 22 章](./22-FeatureFlag与编译期优化.md) 里讲过的"compile-time feature flag"——构建时根据当前渠道把它折叠成 `true` 或 `false`,配合 `require(…)` 的 lazy resolve 和 tree-shaker,整张 buddy 命令子树在 `feature('BUDDY') === false` 的产物里彻底消失。再加上 `useBuddyNotification.tsx` 里 `'external' === 'ant'` 这种字面量比较,构建时整段表达式可以直接被替换成常量布尔,余下的代码被压成无效分支删掉。 -两道门一道由 `feature('BUDDY')` 控制特性总开关,另一道由 `'external' === 'ant'` 字面量给特定渠道再开一道边门。这种"compile-time 双重 gating"在第 19 篇里见过 `migrateFennecToOpus()` 同样的写法——一句普通的 `if`,对编译器是常量条件,对源码读者是渠道意图的明示。 +两道门一道由 `feature('BUDDY')` 控制特性总开关,另一道由 `'external' === 'ant'` 字面量给特定渠道再开一道边门。这种"compile-time 双重 gating"在第 22 章里见过 `migrateFennecToOpus()` 同样的写法——一句普通的 `if`,对编译器是常量条件,对源码读者是渠道意图的明示。 --- diff --git "a/docs/30-Doctor\345\261\217\344\270\216OutputStyle\344\275\223\351\252\214.md" "b/docs/30-Doctor\345\261\217\344\270\216OutputStyle\344\275\223\351\252\214.md" index 64d5338..b2283af 100644 --- "a/docs/30-Doctor\345\261\217\344\270\216OutputStyle\344\275\223\351\252\214.md" +++ "b/docs/30-Doctor\345\261\217\344\270\216OutputStyle\344\275\223\351\252\214.md" @@ -1,6 +1,6 @@ # 第 30 章:Doctor 屏与 Output Style 体验 — 给一个 CLI 装上自检仪表和换装系统 -> 本篇是《深入 Claude Code 源码》系列对终端 UI 一族的最后一篇。前面几章把 Ink 怎么把 React 搬进终端、设计系统如何收敛颜色与边距、键盘事件如何注入 React 树都讲透了。这一篇换一个角度:当 CLI 真的出问题时,用户怎么自己看清"问题在哪里";当用户想换一种说话方式时,怎么用一份 markdown 把模型的开场白替换掉。 +> 本章是《深入 Claude Code 源码》系列对终端 UI 一族的最后一篇。前面几章把 Ink 怎么把 React 搬进终端、设计系统如何收敛颜色与边距、键盘事件如何注入 React 树都讲透了。这一篇换一个角度:当 CLI 真的出问题时,用户怎么自己看清"问题在哪里";当用户想换一种说话方式时,怎么用一份 markdown 把模型的开场白替换掉。 ## 为什么把 Doctor 和 Output Style 放在同一章? diff --git "a/docs/31-Memory\345\255\220\347\263\273\347\273\237\345\205\250\346\231\257.md" "b/docs/31-Memory\345\255\220\347\263\273\347\273\237\345\205\250\346\231\257.md" index 8535323..84bb52f 100644 --- "a/docs/31-Memory\345\255\220\347\263\273\347\273\237\345\205\250\346\231\257.md" +++ "b/docs/31-Memory\345\255\220\347\263\273\347\273\237\345\205\250\346\231\257.md" @@ -1,6 +1,6 @@ # 第 31 章:Memory 子系统全景 — AI 记忆的多层架构 -> 本篇深入 Claude Code 的记忆系统。我们将看到,一个 AI Agent 如何在"无状态"的 LLM 对话模型上,构建出跨越单次会话、跨越多个项目、甚至跨越团队协作的持久化记忆能力。 +> 本章深入 Claude Code 的记忆系统。我们将看到,一个 AI Agent 如何在"无状态"的 LLM 对话模型上,构建出跨越单次会话、跨越多个项目、甚至跨越团队协作的持久化记忆能力。 ## 为什么 AI Agent 需要记忆? @@ -413,7 +413,7 @@ export function shouldExtractMemory(messages: Message[]): boolean { ### 4.3 与 Compact 的协同 -Session Memory 的核心价值在 compact(上下文压缩)时体现。当 auto-compact 触发时,Session Memory 提供了一个比让 LLM 重新总结更低成本的替代方案 —— `sessionMemoryCompact.ts`(第 6 篇已详述)可以直接复用后台已经提取好的 Session Memory 作为 compact 后的总结,**免去额外的 compact 总结 API 调用**。需要注意的是,Session Memory 自身的维护仍然是通过 forked agent 完成的(每次提取都要调用一次 API),但这些提取是在后台增量进行的,代价远低于在 compact 时从头生成摘要。 +Session Memory 的核心价值在 compact(上下文压缩)时体现。当 auto-compact 触发时,Session Memory 提供了一个比让 LLM 重新总结更低成本的替代方案 —— `sessionMemoryCompact.ts`(第 7 章已详述)可以直接复用后台已经提取好的 Session Memory 作为 compact 后的总结,**免去额外的 compact 总结 API 调用**。需要注意的是,Session Memory 自身的维护仍然是通过 forked agent 完成的(每次提取都要调用一次 API),但这些提取是在后台增量进行的,代价远低于在 compact 时从头生成摘要。 提取完成后会等待(`waitForSessionMemoryExtraction()`,15 秒超时)确保 compact 能拿到最新的笔记。 @@ -626,7 +626,7 @@ function isGateOpen(): boolean { } ``` -如果巩固失败,**回滚锁的 mtime** 让时间门控重新通过,下次会话会再次尝试。如果用户手动 kill 了 dream task,DreamTask 的 `kill()` 方法也会回滚 mtime —— 防止"梦被永久打断"(第 14 篇已详述)。 +如果巩固失败,**回滚锁的 mtime** 让时间门控重新通过,下次会话会再次尝试。如果用户手动 kill 了 dream task,DreamTask 的 `kill()` 方法也会回滚 mtime —— 防止"梦被永久打断"(第 16 章已详述)。 --- diff --git "a/docs/32-\345\221\275\344\273\244\347\263\273\347\273\237\345\205\250\346\231\257.md" "b/docs/32-\345\221\275\344\273\244\347\263\273\347\273\237\345\205\250\346\231\257.md" index 750559a..ee59fa3 100644 --- "a/docs/32-\345\221\275\344\273\244\347\263\273\347\273\237\345\205\250\346\231\257.md" +++ "b/docs/32-\345\221\275\344\273\244\347\263\273\347\273\237\345\205\250\346\231\257.md" @@ -1,6 +1,6 @@ # 第 32 章:命令系统全景 — 斜杠命令的聚合与扩展架构 -> 本篇是《深入 Claude Code 源码》系列第 11 篇。我们将深入 `commands.ts` 及其周边模块,揭示 Claude Code 如何将内建命令、用户自定义 Skill、Plugin 命令、Bundled Skill、MCP Skill 和 Workflow 命令统一到一套类型体系中,并实现懒加载、条件注册和动态发现。 +> 本章是《深入 Claude Code 源码》系列第 32 章。我们将深入 `commands.ts` 及其周边模块,揭示 Claude Code 如何将内建命令、用户自定义 Skill、Plugin 命令、Bundled Skill、MCP Skill 和 Workflow 命令统一到一套类型体系中,并实现懒加载、条件注册和动态发现。 ## 为什么需要一个命令系统? @@ -16,7 +16,7 @@ 关键问题是:如何用**一套统一的类型体系**将这些来源完全不同、执行方式各异的命令聚合在一起?如何让 70+ 个内建命令的加载不拖慢启动速度?如何让新的命令来源(如 Workflow、Dynamic Skill)无缝接入? -本篇将回答这三个核心问题。 +本章将回答这三个核心问题。 --- diff --git "a/docs/33-\347\212\266\346\200\201\347\256\241\347\220\206\344\270\216\350\267\250\350\277\233\347\250\213\346\241\245.md" "b/docs/33-\347\212\266\346\200\201\347\256\241\347\220\206\344\270\216\350\267\250\350\277\233\347\250\213\346\241\245.md" index 0874cf9..f43bf6c 100644 --- "a/docs/33-\347\212\266\346\200\201\347\256\241\347\220\206\344\270\216\350\267\250\350\277\233\347\250\213\346\241\245.md" +++ "b/docs/33-\347\212\266\346\200\201\347\256\241\347\220\206\344\270\216\350\267\250\350\277\233\347\250\213\346\241\245.md" @@ -1,6 +1,6 @@ # 第 33 章:状态管理与跨进程桥 — React 与非 React 世界的状态桥接 -> 本篇是《深入 Claude Code 源码》系列的第 3 篇。我们将深入分析 Claude Code 如何用一个 35 行的极简 Store 实现,桥接 React UI 与非 React 业务逻辑之间的状态管理,并理解三层状态架构的设计哲学。 +> 本章是《深入 Claude Code 源码》系列第 33 章。我们将深入分析 Claude Code 如何用一个 35 行的极简 Store 实现,桥接 React UI 与非 React 业务逻辑之间的状态管理,并理解三层状态架构的设计哲学。 ## 为什么状态管理值得单独一篇? diff --git "a/docs/34-\346\236\266\346\236\204\346\250\241\345\274\217\346\200\273\347\273\223.md" "b/docs/34-\346\236\266\346\236\204\346\250\241\345\274\217\346\200\273\347\273\223.md" index 58b1b8f..586fcea 100644 --- "a/docs/34-\346\236\266\346\236\204\346\250\241\345\274\217\346\200\273\347\273\223.md" +++ "b/docs/34-\346\236\266\346\236\204\346\250\241\345\274\217\346\200\273\347\273\223.md" @@ -1,14 +1,14 @@ # 第 34 章:架构模式总结 — 可迁移到你自己项目的设计模式 -> 本篇是《深入 Claude Code 源码》系列的终篇。我们将从前 33 篇的源码分析中,提炼出 11 个可复用的架构模式。每个模式都附有 Claude Code 中的真实代码、适用场景和迁移要点。 +> 本章是《深入 Claude Code 源码》系列的终篇。我们将从前 33 章的源码分析中,提炼出 11 个可复用的架构模式。每个模式都附有 Claude Code 中的真实代码、适用场景和迁移要点。 ## 为什么需要这篇总结? -在过去 33 篇文章中,我们逐一拆解了 Claude Code 的每个子系统——从启动链路到对话循环,从工具系统到权限防线,从 Prompt Cache 到 MCP 协议,再到 Bridge IPC、Coordinator、Migration 与 Output Style。每篇都聚焦于"**这个模块是怎么设计的**"。 +在过去 33 章中,我们逐一拆解了 Claude Code 的每个子系统——从启动链路到对话循环,从工具系统到权限防线,从 Prompt Cache 到 MCP 协议,再到 Bridge IPC、Coordinator、Migration 与 Output Style。每章都聚焦于"**这个模块是怎么设计的**"。 但工程师阅读源码的终极目的不是理解别人的代码,而是**把好的设计用到自己的项目里**。 -本篇将切换视角:不再关注 Claude Code 特有的业务逻辑,而是提取那些**跨项目可复用的架构模式**。这 11 个模式覆盖了从编译期优化到运行时状态管理、从工具注册到安全防线、从跨进程桥接到配置演化的全栈设计决策。 +本章将切换视角:不再关注 Claude Code 特有的业务逻辑,而是提取那些**跨项目可复用的架构模式**。这 11 个模式覆盖了从编译期优化到运行时状态管理、从工具注册到安全防线、从跨进程桥接到配置演化的全栈设计决策。 --- @@ -906,7 +906,7 @@ graph TD ## 写在最后 -在这 34 篇文章中,我们从一个约 1900 个文件的真实 AI 产品中,看到了工程决策背后的权衡逻辑。Claude Code 的源码展示了一个核心理念: +在这 34 章中,我们从一个约 1900 个文件的真实 AI 产品中,看到了工程决策背后的权衡逻辑。Claude Code 的源码展示了一个核心理念: > **好的架构不是追求"正确"的抽象,而是在矛盾的约束之间找到务实的平衡点。**