Files
claude-code/docs/outline-output/design/00-prologue.md
2026-06-15 16:51:29 +08:00

11 KiB
Raw Blame History

序章:一份被反编译重建的 CLI为什么处处是"约束的印记"

这不是原版代码,而是反编译产物在 Bun/JSC 约束下重建出来的东西——每一个奇怪的设计都有具体的根因。

反编译的语义stub、feature gate、_c() 都是正常的

打开 src/types/global.d.ts:1,你会看到这份代码开宗明义的声明:

/**
 * Global declarations for compile-time macros and internal-only identifiers
 * that are eliminated via Bun's MACRO/bundle feature system.
 */

这不是普通的 TypeScript 项目。这份代码的源头是编译后的产物,而不是人类手写的源码。类型声明文件里塞满了"只在编译期存在、运行时会被消除"的标识符:MACRO.VERSIONMACRO.BUILD_TIMEresolveAntModel()GatesTungstenPill()。这些东西在原版 Anthropic 内部构建链里是真实的函数和对象,但在反编译产物里,它们只剩下一个类型签名——一个空壳。

再往下看 global.d.ts:59

// T — Generic type parameter leaked from React compiler output
// (react/compiler-runtime emits compiled JSX that loses generic type params)
declare type T = unknown

T = unknown。这不是谁偷懒写了无意义的类型别名。React Compilerreact-compiler-runtime在编译 JSX 时会把泛型参数丢掉,反编译产物于是到处出现裸露的 T。为了让 TypeScript 编译器不报错,只能声明 type T = unknown。这是一个典型的"反编译痕迹"——它不是设计决策,而是信息丢失后的补救。

打开 src/types/react-compiler-runtime.d.ts:1,类型声明更简洁:

declare module 'react/compiler-runtime' {
  export function c(size: number): unknown[]
}

一个函数 c,接受一个数字参数,返回 unknown[]。这个函数在原版 Anthropic 代码库里是 React Compiler 的运行时 memoization 辅助函数,用于生成 $ 变量(你在反编译的 React 组件里会看到 const $ = _c(N) 这样的模式)。但在反编译产物里,编译器把它内联了,原始模块不复存在。为了不破坏下游 import只能声明一个 unknown[] 返回值——类型系统在说"我知道这里有东西,但我不知道它是什么"。

全书的叙事主线:约束驱动架构

这本书的组织逻辑不是"这个项目有什么功能",而是"哪些约束逼出了哪些设计决策"。这个区别很重要。

你将要读到的每一章,都在追问同一个问题:如果不这么做会怎样?

  • 第一章讲 Code Splitting——答案是"RSS 暴涨到 1GBCLI 启动就要吃掉你一整 GB 内存"。这不是优化,是生存需求。
  • 第三章讲 performanceShim——答案是"JSC 的 Performance 实现有个永不收缩的 C++ Vector长会话累积数百 MB 死容量"。
  • 第五章讲 Feature Flag 的三个硬约束——答案是"Bun 编译器 DCE 的 AST 模式匹配限制,feature() 只能出现在 if 条件位置"。

这本书里几乎每一个看似奇怪的设计——feature() 不能赋值给变量、--version 必须零模块加载、构建产物要正则替换 globalThis.Bun——都指向同一个主题:你面对的不是一张白纸,而是 JSC 内存模型、Bun 编译器限制、反编译信息丢失这三重约束的交叉压力。

如何阅读本书:打开编辑器,对照锚点

每个章节末尾的"锚点"不是装饰,而是邀请。每一条锚点都是 文件:行号 格式,指向代码库中真实存在的代码。

比如本章提到 src/types/global.d.ts:59T = unknown。你可以现在就打开那个文件,跳到第 59 行,亲眼看到那行代码和它上方的注释。再比如本章开头引用了 CLAUDE.md(项目根目录下的那份),第一句话就是:

This is a reverse-engineered / decompiled version of Anthropic's official Claude Code CLI tool.

这不是隐喻。这份代码库的每一个角落都带着反编译的指纹。有些指纹很明显——declare type T = unknownexport function c(size: number): unknown[]有些指纹很隐蔽——feature flag 系统的硬约束、模块级单例状态、"42 条 lint 规则关闭"(那是第十五章的内容)。

建议你用 VS Code 或任何编辑器打开这个项目的根目录。每次看到锚点引用时,花十秒钟跳过去看一下。你会发现文档描述和实际代码之间的对应关系非常精确——这比任何架构图都直观。

两类禁用 feature丢失的 stub vs 原本就 stubbed 的

scripts/defines.ts:39DEFAULT_BUILD_FEATURES 列表里有 65+ 个 feature flag。其中有 8 个被注释掉了:

// 'HISTORY_SNIP', // 已禁用snip 功能暂时关闭
// 'CONTEXT_COLLAPSE', // 已禁用:实现是空壳 stub启用后会抑制 auto compact 导致上下文管理完全失效
// 'FORK_SUBAGENT', // 已禁用:通过 Agent tool 的特殊方式实现了等效功能,无需再开
// 'UDS_INBOX', // 进程间通信管道inbox/pipe/peers 等命令)构建后 nodejs 环境卡住
// 'LAN_PIPES', // 局域网管道,依赖 UDS_INBOX 构建后 nodejs 环境卡住
// 'REVIEW_ARTIFACT', // 代码审查产物API 请求无响应,待排查 schema 兼容性)
// 'SKILL_LEARNING',
// 'TEAMMEM', // 已禁用:依赖 COORDINATOR_MODE邮箱文件无限增长

表面上看它们都是"被禁用的",但禁用的原因截然不同。混淆这两类会导致严重误判。

第一类:反编译丢失导致的 stub。 CONTEXT_COLLAPSEHISTORY_SNIPFORK_SUBAGENTUDS_INBOXLAN_PIPESREVIEW_ARTIFACT 属于这一类。

打开 src/setup.ts:290 你会看到:

if (feature('CONTEXT_COLLAPSE')) {
  require('./services/contextCollapse/index.js').initContextCollapse()
}

src/services/contextCollapse/ 目录确实存在,里面有 index.tsoperations.tspersist.ts 三个文件。但注释明确说"实现是空壳 stub启用后会抑制 auto compact 导致上下文管理完全失效"。反编译过程保留了文件结构和函数签名,但丢失了核心逻辑。如果你强行启用 FEATURE_CONTEXT_COLLAPSE=1init 函数会跑起来,但它做的事情是错误的——它会抑制自动压缩,导致长对话的上下文管理彻底崩溃。

HISTORY_SNIP 的情况类似。打开 src/commands.ts:92

const forceSnip = feature('HISTORY_SNIP')
  ? require('./commands/force-snip.js').default
  : null

src/commands/force-snip/ 目录根本不存在。如果你启用这个 feature运行时会直接 MODULE_NOT_FOUND。这个 feature 在原版里指向一个完整的消息历史裁剪子系统(src/utils/messages.ts:2652 里有它的运行时检查逻辑),但反编译过程丢失了 force-snip 命令模块。

第二类:功能原本就 stubbed 的。 SKILL_LEARNINGTEAMMEM 属于这一类。

打开 src/services/skillLearning/featureCheck.ts:11

export function isSkillLearningCompiledIn(): boolean {
  if (feature('SKILL_LEARNING')) return true
  return false
}

这个目录下有 20+ 个文件(agentGenerator.tsevolution.tsinstinctParser.tsskillLifecycle.ts 等),结构完整。这不是反编译丢失——这是 Anthropic 原版里本身就 stubbed 的功能。feature flag 注释写的也很清楚:SKILL_LEARNING 的 slash command 被编译进 build但运行时默认 OFF需要 operator 主动 /skill-learning start 开启。这不是"丢了",而是"还没开放"。

TEAMMEM 也是类似情况。src/memdir/memdir.ts:7src/utils/memoryFileDetection.ts:17 等多处引用了 feature('TEAMMEM') 的分支逻辑,相关代码路径是完整的。禁用的原因是"依赖 COORDINATOR_MODE邮箱文件无限增长"——这是一个产品决策,不是反编译事故。

区分这两类的实用方法:看被注释掉的那行注释。如果注释说"实现是空壳 stub"或"构建后环境卡住",那是反编译丢失(第一类)。如果注释说"依赖某 feature"或"待排查",那是功能本身的问题(第二类)。第一类强行启用会破坏核心功能;第二类启用后可能有 bug 但不会让系统崩溃。

bun:bundle 的幽灵模块

src/types/internal-modules.d.ts:10 声明了一个不存在的模块:

declare module 'bun:bundle' {
  export function feature(name: string): boolean
}

bun:bundle 是 Bun 运行时的内置模块,由 Bun 编译器在构建时解析。你在 Bun 以外的环境里跑 import { feature } from 'bun:bundle' 会报错——这个模块只存在于 Bun 的编译管道里。类型声明文件把它写出来,纯粹是为了让 TypeScript 不报 Cannot find module 'bun:bundle' 错误。

这个幽灵模块贯穿整个代码库。scripts/vite-plugin-feature-flags.ts:29 里有一个 Rollup 插件,专门在 Vite 构建时把 bun:bundle 虚拟化为一个始终返回 false 的 stub

load(id) {
  if (id === resolvedVirtualModuleId) {
    return 'export function feature(name) { return false; }'
  }
}

同一个 feature() 函数,在 Bun 构建里是编译器的 DCEdead code elimination钩子在 Vite 构建里被插件替换为字面量。两种构建管道对同一个函数的理解完全不同,但产出的行为一致。这种"双管道、单语义"的设计是反编译重建工作的典型特征——你不需要理解原版为什么这么做,你只需要在两条路径上复现相同的行为。

反编译产物的类型补丁成本

bun:bundle 不是唯一的幽灵模块。同一个文件里还声明了 bun:ffiinternal-modules.d.ts:14),以及 bidi-jsasciichart@napi-rs/keyring 等没有 @types 包的第三方模块。所有导出都被类型化为 any 或最小接口。

这意味着什么?意味着你在阅读代码时看到的类型签名,有很多是"人为补丁"而非"原始设计"。T = unknown 是最极端的例子,但更常见的模式是 Record<string, unknown>——当反编译丢掉了结构信息时,退化为字典类型是唯一安全的选项。

如果你在代码里看到某个函数接收 Record<string, unknown> 参数,或者在某个地方有 as unknown as SomeType 的双重断言,那大概率是反编译信息丢失的痕迹。这不是代码质量问题,而是信息损失的必然结果——就像你把一栋建筑拆成零件再重建,总有些螺丝的规格对不上,只能用万能件替代。

延伸阅读