Handbooks
核心概念

架构

四层十一个包、严格单向的依赖方向,以及让确定性的那一半可以单独复用的边界。

分层

包分层:入口、能力、引擎、地基
职责
入口clistudio人或容器直接运行的东西
能力pipelinerendererskillplannerpatcherresync各司一职,可独立使用
引擎analyzerllm其他一切构建于其上的两样东西
地基core数据模型、配置注册表、工具函数

依赖只会向下

cli → pipeline / renderer / skill / planner / patcher / resync → analyzer / llm → core

三条让它保持健康的规则

1. 单向依赖,强制执行

core 不导入任何内部包。没有任何包导入 cli。出现依赖环或向上导入会让 pnpm check:workspace 失败;这项检查还会验证每个包的 TypeScript 项目引用与其 package.json 依赖完全一致——缺一个引用会让 tsc -b 以错误的顺序构建,而根目录构建会把这个问题掩盖掉。

2. LLM 隔离是包边界,不是约定

只有 llmpipelineplannerresync 可以与模型对话,且只能通过 ChatClient 接口:

interface ChatClient {
  readonly model: string;
  complete(prompt: string, options?: ChatOptions): Promise<ChatResult>;
}

analyzerrendererskillpatcher 完全不依赖 @handbooks/llm。它们是完全确定性的,在任何见不到 LLM 的地方都能复用。这就是 renderskillvalidateapplyrollback 可以放心跑在 CI 里的原因。

这也是整个测试套件可以离线运行的原因:一道接缝,一个 mock。

3. 渲染器的边界是一个类型

HandbookModel(定义在 core 中)是渲染器唯一知道的东西。它从不读取流水线内部。

interface HandbookModel {
  title: string;
  lang: NarrateLang;
  skeleton: Skeleton;
  cards: Record<string, FileCard>;
  assignment: Assignment;
  organization: Organization;
  narration: Narration;
  registers: RegisterEntry[];
  provenance?: { commit?: string; generatedAt: string };
}

任何能填出一个 HandbookModel 的生产者,都免费获得渲染、skill 打包和规划。 如果你想用别的方式生成手册,需要满足的契约就这么多。

数据流

source tree
   │  analyzer — tree-sitter WASM, one adapter per language

phase1/graph.json · functions.csv · graph.dot · dropped-calls.json · scan-coverage.json
   │  pipeline 2a — cards (batched LLM, three-tier degradation, resumable)

phase2/cards/<rel>.json + _coverage.json
   │  pipeline 2b — skeleton synthesis (+ doctor loop) + file assignment

phase2/skeleton.yaml + assignment.json
   │  pipeline 2c — call-graph topological order + LLM grouping (flat fallback)

phase2/organization.yaml
   │  pipeline 3 — bottom-up narration + register extraction (content-hash cached)

phase3/narration.json + registers.json
   │  loadHandbookModel()

HandbookModel ──▶ renderer ──▶ handbook/  (md · html/ · handbook.html · agent/ · llms.txt)

                     └──▶ skill ──▶ SKILL.md + references/ (+ coverage.json)

**工作目录契约:**每个阶段只读上游产物、只写自己的产物,全部在读取时做带 version 字段的 schema 校验。任何阶段都可以单独重跑。崩溃后可恢复——卡片按批写入,叙述有内容哈希缓存。

人类产物负责解释,智能体产物负责定位

一个 HandbookModel,两份工作确实不同的输出——这个分工本身就是设计,不是打包时的细节。

markdown 和 HTML 手册是写来的:文字、次序、一条叙事主线。agent/ 是写来 grep 的:symbols.tsv 用一行回答*“sendPayment 定义在哪里”*,这是再多文字也做不到的。

它们从前是同一份文字的两种形状,代价很具体:智能体索引最后体积是人类索引的 2.1 倍,却一个符号位置都不含,因为其中 42% 是从人类页面逐字节抄过来的模型文字。如今智能体这一侧承载的是事实,外加每个文件一行剪短的文字;需要解释的地方,每个阶段页面链接到人类页面,而不是把它复制一份。

分析器内部

每种语言实现一个 LanguageAdapterdiscoveranalyze,以及可选的 statementSpans。所有语法都是 WebAssembly,因此安装时从不编译原生代码。

适配器对每个模块跑两遍

  1. 扫描——声明、导入、类和方法,以及每个函数的事实:签名、行号范围、是否 async、装饰器、self/this 属性的读写、带类型的参数,以及从构造函数赋值学到的属性类型。
  2. 解析——每个调用点变成一条带类型的边:self_methodself_attr_methodparam_methodinternal_funcinternal_constructorboundaryboundary_constructor——或者 unresolved,由图构建器隔离进 dropped-calls.json 并标注类别。

保留下来的图里只有解析成功、有名有姓的被调方。 这正是图中每条边值得信任的原因。

同一条规则也适用于往上一层——整个文件。适配器读不了的文件、让语法抛错的文件、以及带着语法错误解析出来的文件,都会连同原因记进 scan-coverage.json;前两类还会被挡在 scannedFiles 之外,这样后面任何一个阶段都不可能去描述一个解析器从没见过的文件。

nav-pack 是从图推导出来的确定性定位摘要——目录汇总、入口点候选、扇出、外部子系统。它是骨架合成器能看到的唯一代码库视图,这让那条提示词既小又有据可依。

流水线的质量机制

三级卡片降级(2a)。 整批 → 单文件 → 对超大文件按函数分块。仍然失败的文件得到一张诚实的空卡片,并被列入 _coverage.json。覆盖率在构造上就是完整的;缺失是可见的,而不是无声的。

actor–critic 骨架医生(2b)。 actor 依据真实调用图的统计数据提出至多三项结构改动;三个角色扮演的 critic(工程师、架构师、读者)并行评审;幸存下来的每项改动在应用前都要再经过一次机械校验;受影响的文件会被重新归属。循环在收敛或连续两轮无进展时停止。critic 出故障按 REJECT 计——失效的评审者绝不能放行改动。

处处有确定性兜底(2c、3)。 组织退回调用图顺序。叙述退回阶段描述。寄存器提取失败得到空列表。生成运行只降级,不阻塞

内容哈希缓存(3)。 阶段和系统的文字缓存在 phase3/cache/ 下,键由提示词版本、语言和完整提示词哈希组成——重跑和 resync 只为真正变化的部分付费。

并发与安全

  • 每个工作目录同时只跑一个任务。 generateHandbookresyncHandbook 拿的是同一把可重入目录锁,所以一次 CLI 运行和一个 Studio 任务不可能在同一批产物上交错写入。
  • 原子写入。 每件产物都先写临时文件再重命名。崩溃永远不会留下半截文件让下一次运行噎住。
  • 协作式取消。 AbortSignal 在阶段之间和每个批次检查点被检查,并穿入每次 LLM 调用,让在途请求中止。被中止的运行保留已保存的内容,且写运行清单。

值得了解的决策

#决策原因
1只用 WASM 版 tree-sitter零原生构建;所有语言共用一条加载路径;语法版本锁定
2手写的 fetch LLM 客户端OpenAI 兼容端点五花八门;一个带显式重试的薄客户端胜过一个 SDK 依赖。接口这道接缝比传输层更重要
3一条流水线,两种策略大小两套流水线会重复适配器、critic、客户端和渲染器;一个策略标志砍掉了约 40% 的这类表面积
4version 的 zod 校验产物损坏或手改的产物在边界处大声失败,而不是毒害后续阶段
5卡片中事实与文字分离模型只是在一份完整的、由图推导的清单上做注解。文字可以为空;事实不能出错
6单轮规划器协议任何端点都能用、极易 mock、对话记录可以审查。代价——重复发送 token——在规划器的规模下可以接受
7ESM + tsc -b,不用打包器库交付经过类型检查的 dist/.d.ts;composite 引用零额外工具就得到增量构建
8单一配置注册表标志、环境变量名、YAML 键和三份生成文档都源自同一张表,因此不可能漂移

下一步

本页目录