{
  "schema_version": 1,
  "audit_date_utc": "2026-10-14",
  "scope": "scripts/catalog.mjs 的 7 本教材章节目录，恰好 250 章；不含 README、前言、附录、示例源码与研究资料。",
  "methodology": "逐章扫读标题、选定小节和开篇正文；每条媒介建议逐章编写。针对首批 KV cache、BPE、GMP 细读相关正文。提取全体标题不等于逐行审校全部正文。未复跑书中实验，未审核所有外部来源，未作技术内容全面正确性保证。",
  "treatments": {
    "retain_prose": "保留正文、原有表格/代码/实验；暂不添加新交互。",
    "static": "优先使用或改进静态关系图、时序图或对照表；不表示现有静态图全部缺失。",
    "interactive": "一个局部概念值得交互辅助，原有完整叙述仍保留。是候选建议，不是已实现清单。"
  },
  "priorities": {
    "P0": "用户已选定首批演示的主放置章节，共 3 章。",
    "P1": "首批验收后值得优先考虑的后续候选；不表示已经承诺实现。",
    "P2": "可选后续增强，或以静态解释为先。",
    "P3": "当前优先保留正文与原有真实实验。"
  },
  "total_chapters": 250,
  "treatment_counts": {
    "interactive": 97,
    "static": 91,
    "retain_prose": 62
  },
  "priority_counts": {
    "P2": 152,
    "P1": 33,
    "P3": 62,
    "P0": 3
  },
  "book_counts": {
    "go": 115,
    "seo/book": 26,
    "seo/ai-workflow": 10,
    "llm/zero-to-hero": 31,
    "llm/nanogpt": 15,
    "llm/llms-from-scratch": 31,
    "llm/happy-llm": 22
  },
  "interactive_family_count": 70,
  "evidence_notice": "正文原有“实测”、版本与性能数字均为原书陈述，不是本次审计新做的实验。",
  "chapters": [
    {
      "id": "ch-001",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/01_语言基础/Go 函数.md",
      "title": "Go 函数：看清参数何时传入，代码何时执行",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "defer 登记与执行时机",
      "recommended_medium": "逐步执行时间线",
      "reason": "逐步显示参数何时保存、闭包何时读变量，比只猜最终输出更能解释差异。",
      "reuse_family": "go-execution",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先认清几种写法在做什么"
          },
          {
            "line": 20,
            "text": "## 多返回值：一次调用把相关结果交齐"
          },
          {
            "line": 43,
            "text": "## 函数也是值：把变化的规则传进去"
          },
          {
            "line": 75,
            "text": "## 闭包保留变量，后续调用会继续用它"
          },
          {
            "line": 99,
            "text": "### 循环里要看变量在哪里声明"
          },
          {
            "line": 123,
            "text": "## 可变参数：函数里拿到的是 slice"
          },
          {
            "line": 151,
            "text": "## defer：登记发生在现在，调用发生在函数退出前"
          },
          {
            "line": 209,
            "text": "### 保存的不只是参数，还有函数值"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "把函数传给另一个函数、返回一个闭包、在退出前执行清理，这几件事在 Go 里都很常见。难点往往不在语法，而在时间：函数拿到的是哪个值，闭包后来读到的是哪个变量，`return` 与 `defer` 谁先改结果。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这篇从普通调用开始，再把执行时间往后移，依次讲函数值、闭包和 `defer`。读完应该能判断一段函数代码的输出，也知道文件关闭、回调和循环里最容易出错的地方。示例实际运行于 Go 1.27.2、Linux/amd64；完整程序、测试和故意无法编译的反例放在文末。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "b968cfe725004ac26e05333a7f06afe8ec8a9cda5c7cc4dceb040a842a6578e0",
      "source_line_count": 597,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-002",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/01_语言基础/Go 包与可见性.md",
      "title": "Go 包与可见性：让依赖关系能被编译器检查",
      "priority": "P2",
      "treatment": "static",
      "concept": "包依赖、internal 边界与初始化",
      "recommended_medium": "依赖图与目录对照",
      "reason": "需要稳定地看清谁能导入谁；现有编译反例比额外控件更直接。",
      "reuse_family": "package-boundaries",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先分清模块、目录、包名和导入路径"
          },
          {
            "line": 41,
            "text": "## 导出看名字，但并不是所有大写名字都能在外面访问"
          },
          {
            "line": 98,
            "text": "## internal 约束的是“谁能导入这个目录”"
          },
          {
            "line": 143,
            "text": "## init 顺序：先依赖，再变量，最后初始化函数"
          },
          {
            "line": 190,
            "text": "### 文件名顺序与语言保证要分开"
          },
          {
            "line": 198,
            "text": "### init 可以做什么，又会带来什么成本"
          },
          {
            "line": 213,
            "text": "## 循环依赖为什么不能靠调整 import 顺序解决"
          },
          {
            "line": 249,
            "text": "## 用一个小接口把服务和存储接起来"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "把一个大文件拆成十个文件，代码不一定更容易维护；把一个包拆成十个互相导入的包，甚至会直接编译失败。Go 的组织边界是包：哪些名字能被外面使用，哪些目录只允许内部代码导入，启动时先初始化谁，都是沿着这个边界确定的。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这篇文章从一个小程序展开：它有计数器、内部实现、初始化依赖，以及用接口接上的存储层。读完应该能回答三个实际问题：一个名字为什么看得见或看不见；程序还没进 `main` 时运行了什么；两个包互相需要对方时该怎样拆。正文结果在 Go 1.27.2、Linux/amd64 上实测，完整项目在文末。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "e5e70ad8042abc349212e89e6a8c70a48e7eecc9a7183553ae91444b3ff8eae9",
      "source_line_count": 544,
      "existing_static_mermaid_count": 3,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-003",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/01_语言基础/Go 反射 unsafe cgo 与代码生成.md",
      "title": "Go 反射 unsafe cgo 与代码生成",
      "priority": "P2",
      "treatment": "static",
      "concept": "反射、地址、跨语言所有权",
      "recommended_medium": "生命周期图与边界表",
      "reason": "工具跨越不同阶段；示意必须保留具体版本和安全前提，不模拟任意指针操作。",
      "reuse_family": "ownership",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先分清它们在哪一步工作"
          },
          {
            "line": 19,
            "text": "## reflect 先看类型，再操作值"
          },
          {
            "line": 36,
            "text": "### Type 相同和 Kind 相同是两回事"
          },
          {
            "line": 63,
            "text": "### CanAddr 和 CanSet 为什么不总是一起为 true"
          },
          {
            "line": 126,
            "text": "### 无效 Value、nil 指针和 map 元素分别处理"
          },
          {
            "line": 161,
            "text": "### 反射的成本来自具体动作"
          },
          {
            "line": 177,
            "text": "## unsafe 拿到地址之后，还欠哪些保证"
          },
          {
            "line": 189,
            "text": "### Sizeof 量的是值本身，字段之间可能有填充"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "读框架源码时，会遇到反射；接 C 库时，会遇到 cgo；把模板带进二进制时，会遇到 `embed`；维护生成出来的接口实现时，又会遇到 `go:generate`。这些工具都很有用，但解决的问题不同。最容易出错的地方，是把某一种工具的保证带到另一种工具上：拿到了地址就以为能改值，C 函数返回了就以为指针还有效，源码里写了生成指令就以为构建会替自己执行。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这篇文章把这些边界逐个跑一遍。读完应当能判断什么时候需要它们，知道最常用的入口，并能查出几类危险的误用。示例实际运行于 **Go 1.27.2、Linux/amd64、GCC 14.2.0，CGO_ENABLED=1**。正文输出来自本次运行；完整源码、测试、资源文件和验证脚本放在文末。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "680603e322d3ba37a8b0c649281f280126e0e1f3d0a75086244e02a5392f65c1",
      "source_line_count": 1194,
      "existing_static_mermaid_count": 3,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-004",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/01_语言基础/Go 接口.md",
      "title": "Go 接口",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "接口动态类型与 typed nil",
      "recommended_medium": "双槽接口值检查器",
      "reason": "改变动态类型和值后显示 nil 判断，能揭示指针为空而接口非空的原因。",
      "reuse_family": "interface-value",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 17,
            "text": "## 从 io.Writer 看隐式实现"
          },
          {
            "line": 59,
            "text": "### 签名满足了，行为约定还要实现者自己守住"
          },
          {
            "line": 81,
            "text": "### 实现接口的是 T，还是 *T"
          },
          {
            "line": 93,
            "text": "## 小接口：按调用方的需要收窄能力"
          },
          {
            "line": 106,
            "text": "### 测试失败路径，不必搭一个真的故障服务"
          },
          {
            "line": 131,
            "text": "## 接口值：静态类型规定能做什么，动态类型记录装的是什么"
          },
          {
            "line": 146,
            "text": "### 接口再赋给接口，不会套一层“接口盒子”"
          },
          {
            "line": 163,
            "text": "### 语义模型和运行时布局要分开看"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "一个函数只是把结果写出去，为什么参数一定要是文件？如果它只需要“能写入一段字节”，就可以把参数声明成 `io.Writer`，文件、内存缓冲区和测试替身都能交给它。接口的价值在这里：让调用方只依赖要用的能力。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "接口也带来几种需要单独理解的行为：`any` 能装下整数，不代表能直接做整数运算；一个指针是 nil，放进接口后接口却可能不是 nil；两个接口都装着 slice，比较时可能 panic。这篇从一次写入操作讲起，再把动态类型、断言和 nil 串起来。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "b76136cb367bb0ca0c3436b174dae7d0c36ea03b6aa5cb836eb36332765c92a7",
      "source_line_count": 712,
      "existing_static_mermaid_count": 3,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-005",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/01_语言基础/Go 标准库必熟.md",
      "title": "Go 标准库必熟：从一件具体工作找到入口",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "按工作寻找标准库入口",
      "recommended_medium": "正文、短例与可运行测试",
      "reason": "本章是多包参考；统一做成交互会拉长检索路径，原有短例更适合查阅。",
      "reuse_family": "reference",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先认路：这件事由哪个包负责"
          },
          {
            "line": 31,
            "text": "## 一、处理文本：先分清格式化、切分和转换"
          },
          {
            "line": 33,
            "text": "### fmt：名字决定结果去哪，动词决定怎么写"
          },
          {
            "line": 78,
            "text": "### strings：去后缀和删除字符集合是两件事"
          },
          {
            "line": 115,
            "text": "### strconv：返回了一个数，不代表解析成功"
          },
          {
            "line": 150,
            "text": "### bytes：缓冲区交出的切片可能仍属于它"
          },
          {
            "line": 181,
            "text": "## 二、时间：时刻、时长、时区分别是什么"
          },
          {
            "line": 185,
            "text": "### 布局写的是参考时间，不是 YYYY"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "拿到一行配置，要把 `limit=25` 变成键和整数；收到 HTTP 请求，要限制大小、解析 JSON，再记一条不会泄露令牌的日志。这些工作会连续经过好几个标准库包。熟悉它们，靠的不是背下所有导出函数，而是知道每一步该交给谁，以及交接时哪些约定不能弄错。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这篇文章覆盖知识地图列出的 17 个包。先按工作找入口，再看短代码、实际输出和容易误用的地方。每个包都有可运行例子，完整源码与测试在文末。验证环境是 Go 1.27.2、Linux/amd64，日期为 2026-10-13；没有沿用其他系统上的基准数字，也不把本地小例子当作生产性能结论。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "c60f7ca2925a324424353604fe0cc63af788938075eb4e166133d361338e8dbe",
      "source_line_count": 1510,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-006",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/01_语言基础/Go 泛型.md",
      "title": "Go 泛型：同一套算法，怎样保留不同的具体类型",
      "priority": "P2",
      "treatment": "static",
      "concept": "泛型约束与类型集合",
      "recommended_medium": "类型集合示意与编译反例",
      "reason": "重点是语言规则和编译期约束；静态包含关系足够，不应以浏览器假装 Go 编译器。",
      "reuse_family": "type-sets",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 9,
            "text": "## 先写出一段真正需要复用的算法"
          },
          {
            "line": 43,
            "text": "## 约束既筛选类型，也限制函数体能做什么"
          },
          {
            "line": 85,
            "text": "### int64 和 ~int64 差在是否接受业务定义的类型"
          },
          {
            "line": 106,
            "text": "### 约束接口不一定能用来保存普通值"
          },
          {
            "line": 118,
            "text": "## ~[]E 为什么经常出现在标准库签名里"
          },
          {
            "line": 160,
            "text": "## comparable 允许比较，不代表所有运行时值都不会出问题"
          },
          {
            "line": 201,
            "text": "## 类型推断省掉重复信息，但仍需要足够的线索"
          },
          {
            "line": 238,
            "text": "## 泛型类型有自己的方法，Go 1.27 又多了一种能力"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "统计一组字符串出现的次数，和统计一组用户 ID 出现的次数，循环几乎一样：取出元素，把它在 map 里的计数加一。复制两份函数容易漏改；全部换成 `any`，又会让调用者失去“输入什么类型，就得到什么类型的键”这条关系。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "泛型让函数把这条关系写进签名，由编译器检查。本文用计数、筛选和一个小容器说明类型参数、约束、`comparable` 和类型推断，再讨论什么时候普通接口更合适。这里按 Go 1.27.2 讲解，特别核对了 Go 1.27 新增的泛型方法；不能再照搬早期教程里“方法永远不能有自己的类型参数”的说法。[^intro][^go127]",
            "truncated": false
          }
        ]
      },
      "source_sha256": "d354230be7c5c7bdad9e14b57cd25e3ce9d3f3dffdf0d7c71054cc8ea9fd4ae2",
      "source_line_count": 563,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-007",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/01_语言基础/Go 类型系统.md",
      "title": "Go 类型系统",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "slice 长度容量与底层数组共享",
      "recommended_medium": "可编辑数组和 slice 视图",
      "reason": "移动切片边界与执行 append/copy 能直接暴露共享、容量限制和复制的区别。",
      "reuse_family": "slice-aliasing",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 18,
            "text": "## 基本类型：先让编译器知道这个值代表什么"
          },
          {
            "line": 20,
            "text": "### 常用类型怎样选"
          },
          {
            "line": 53,
            "text": "### 定义类型与别名解决的问题不同"
          },
          {
            "line": 72,
            "text": "### 零值是起点，不保证所有操作都能做"
          },
          {
            "line": 89,
            "text": "## 数组与 slice：固定的一组元素，和查看数组的一段范围"
          },
          {
            "line": 102,
            "text": "### 同样传入函数，为什么修改结果不同"
          },
          {
            "line": 140,
            "text": "### len 决定能读哪些元素，cap 决定能扩到哪里"
          },
          {
            "line": 187,
            "text": "## append 与复制：先问底层数组还共不共用"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "写 Go 时，选对类型只是第一步。接下来还要知道：把一个值交给函数，函数能不能改到调用方的数据？一个容器看起来是空的，能不能直接往里放东西？一段中文的长度，到底应该怎么算？这篇把基本类型、数组、slice、map 和字符串放在一起讲，重点是把这些行为判断清楚。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "先认一下各自的用途。表中的“复制”指普通赋值和参数传递，不是序列化，也不是递归复制所有关联对象。Go 传参会复制参数值；值里包含引用时，复制之后仍可能访问同一份数据。[^values]",
            "truncated": false
          }
        ]
      },
      "source_sha256": "956515d837f597e872e1dbe3cb4eb9a2ad060d2000d00617bf91e9b6a52e1f2c",
      "source_line_count": 905,
      "existing_static_mermaid_count": 3,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-008",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/01_语言基础/Go 结构体与方法.md",
      "title": "Go 结构体与方法",
      "priority": "P2",
      "treatment": "static",
      "concept": "接收者复制与方法集",
      "recommended_medium": "值/指针接收者对照图",
      "reason": "方法可调用和接口实现需要并排核对规则，固定例子与表格更易查证。",
      "reuse_family": "method-sets",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 17,
            "text": "## 先有结构体，再给它定义操作"
          },
          {
            "line": 40,
            "text": "## 值接收者与指针接收者：复制的是结构体，还是指针"
          },
          {
            "line": 63,
            "text": "### 值接收者不等于深复制，也不等于只读"
          },
          {
            "line": 89,
            "text": "### 怎么选接收者"
          },
          {
            "line": 95,
            "text": "## 方法集：判断接口实现，不能只看某次调用能否编译"
          },
          {
            "line": 116,
            "text": "### 为什么 c.Inc() 能调，Counter 却不实现接口"
          },
          {
            "line": 147,
            "text": "### 放进接口的值，仍然遵循复制规则"
          },
          {
            "line": 163,
            "text": "## 把方法保存成函数时，接收者何时确定"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "结构体把相关数据放在一起，方法把围绕这些数据的操作放在类型上。真正容易写错的地方，是接收者到底拿到哪一份数据：`func (c Counter)` 与 `func (c *Counter)` 只差一个星号，却会影响修改是否留下来、接口能否实现，以及保存方法后会读到哪个值。",
            "truncated": false
          },
          {
            "line": 15,
            "text": "正文输出在 Go 1.27.2、Linux/amd64 上实际运行。方法与类型关系是语言规则；具体布局数字只对应这个工具链和架构。完整代码和反例都在文末。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "20b36a83b806eb0789254fab1609d1628448fd778f6b1cb24f169c35bd70b094",
      "source_line_count": 693,
      "existing_static_mermaid_count": 4,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-009",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/01_语言基础/Go 迭代器.md",
      "title": "Go 迭代器：把“怎样取下一个”留给集合",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "yield、break 与控制权交接",
      "recommended_medium": "迭代器单步调用图",
      "reason": "交替推进提供者和使用者，并停在 yield(false)，能看清停止如何传回去。",
      "reuse_family": "go-execution",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先认接口：迭代器究竟是什么类型"
          },
          {
            "line": 28,
            "text": "## 沿着一次 break，看清控制权怎样来回走"
          },
          {
            "line": 111,
            "text": "## 惰性来自代码怎样写，不来自 Seq 这个名字"
          },
          {
            "line": 143,
            "text": "## slices 与 maps：用现成入口接上自己的数据"
          },
          {
            "line": 183,
            "text": "### 迭代器通常没有替你做快照"
          },
          {
            "line": 204,
            "text": "## 读文件这样的过程，错误和资源该放在哪里"
          },
          {
            "line": 251,
            "text": "## 需要手动拿“下一项”时，再用 iter.Pull"
          },
          {
            "line": 280,
            "text": "## 取消需要显式检查，break 不会打断任意阻塞"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "一个目录里有十万个文件，调用者只想找到前三个符合条件的名字。如果 API 先返回一个大切片，它往往要先走完整个目录；如果 API 接收回调，调用者又得放弃熟悉的 `for range`。Go 的函数迭代器把这两件事接在了一起：提供者负责逐个产生值，使用者仍然写循环，并能用 `break` 叫停。`range` 遍历函数和标准库 `iter` 从 Go 1.23 开始可用。[^1]",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文要讲清的是这个停止信号如何传回去，切片、map 怎样接上这个协议，以及读取文件一类有资源、有错误的过程该怎样收尾。示例在 Go 1.27.2、Linux/amd64 上运行；语言规则、标准库约定与本次输出会分别说明。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "7aabdae573cc07565090ef8976dd323874cb21627ac835441c57c6d91bcef6fe",
      "source_line_count": 621,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-010",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/01_语言基础/Go 错误处理.md",
      "title": "Go 错误处理：保留原因，让调用者知道下一步怎么做",
      "priority": "P2",
      "treatment": "static",
      "concept": "错误包装、Is/As 与 Join",
      "recommended_medium": "错误树和判断路径图",
      "reason": "树形关系能表达多条原因；保留具体 API 代码，避免把匹配条件藏在控件里。",
      "reuse_family": "error-tree",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先认入口：创建、补充、判断和合并"
          },
          {
            "line": 22,
            "text": "## error 是一个接口，nil 表示成功需要认真兑现"
          },
          {
            "line": 50,
            "text": "### nil 指针装进 error 后，可能已经不是 nil"
          },
          {
            "line": 66,
            "text": "## 先确定调用者需要什么，再选错误形式"
          },
          {
            "line": 109,
            "text": "## %w 保留原因，%v 只把文字放进去"
          },
          {
            "line": 140,
            "text": "## Is 判断类别，As 提取细节"
          },
          {
            "line": 178,
            "text": "## Join 把错误链变成树"
          },
          {
            "line": 227,
            "text": "### 主操作和清理同时失败时，别把其中一个盖掉"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "读配置失败时，一句“出错了”不够用。排查的人需要知道在读哪份配置，调用者需要判断是参数不合法、没有权限，还是操作已经被取消。同一个错误要承担两种工作：给人看上下文，给程序保留可判断的信息。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "Go 用普通返回值传递这类失败。本文围绕一条“解析配置 → 添加上下文 → 调用方判断”的调用链，讲清 `error`、包装、`Is`、`As`、`Join`，再处理清理失败、取消和 panic。所有展示输出来自 Go 1.27.2、Linux/amd64 的新实验；文末放完整程序与测试。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "bb87a35668811e748bd75ff7b8c2ad3a91c837a0d56a7c58e07e579d22c73870",
      "source_line_count": 566,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-011",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/02_并发编程/Channel.md",
      "title": "Channel：把数据交出去，也把结束方式说清楚",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "channel 缓冲、关闭与阻塞",
      "recommended_medium": "发送/接收状态机",
      "reason": "手动发送、接收和关闭能让容量、等待、取完后的零值与 panic 边界可见。",
      "reuse_family": "channel-state",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先判断它会不会等"
          },
          {
            "line": 19,
            "text": "### 无缓冲：双方见面以后，发送才能继续"
          },
          {
            "line": 38,
            "text": "### 有缓冲：先放进去，代价是积压"
          },
          {
            "line": 70,
            "text": "## 关闭的是数据流，不是 goroutine"
          },
          {
            "line": 87,
            "text": "## nil channel 很危险，也很有用"
          },
          {
            "line": 126,
            "text": "## select 选的是当前能做的动作"
          },
          {
            "line": 138,
            "text": "## 超时要先决定：等一条，还是等整段工作"
          },
          {
            "line": 171,
            "text": "## 依据与本次验证"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "channel 同时做两件事：传值，让不同 goroutine 在明确的通信事件上同步。用好它，关键是能判断一次发送或接收会不会等待，以及谁有权宣布“以后不再有值”。这篇文章从这两个问题展开，再处理 `nil`、`select`、超时和默认分支。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "最基本的入口是 `make(chan T)`、`make(chan T, n)`、`ch <- v`、`v := <-ch`、`v, ok := <-ch` 和 `close(ch)`。`chan T` 可发送也可接收；`chan<- T` 只允许发送，`<-chan T` 只允许接收。函数返回只读 channel 能收窄调用者的能力，不过方向类型不会自动决定生命周期，也不会深拷贝传入的值。[^type]",
            "truncated": false
          }
        ]
      },
      "source_sha256": "a4cb844d4474ebb7d0c52891aba5dd82759d0ff2daf6ffcce0cc9b8ea71dbf01",
      "source_line_count": 316,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-012",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/02_并发编程/Go 内存模型.md",
      "title": "Go 内存模型：怎样证明另一个 goroutine 能看见你的写入",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "happens-before 与执行先后",
      "recommended_medium": "双 goroutine 事件图",
      "reason": "添加同步边并检查可证明的顺序，有助于区分碰巧先发生与语言保证；不生成未定义结果预测。",
      "reuse_family": "happens-before",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先看一条能证明的顺序"
          },
          {
            "line": 41,
            "text": "## 数据竞争到底指什么"
          },
          {
            "line": 57,
            "text": "## 平时用的同步工具，分别连起哪条边"
          },
          {
            "line": 72,
            "text": "### 缓冲会改变确认的位置"
          },
          {
            "line": 80,
            "text": "## 没有数据竞争，也能把一件商品卖两次"
          },
          {
            "line": 109,
            "text": "### 修正：让检查和更新成为一个不可拆开的决定"
          },
          {
            "line": 133,
            "text": "## go test -race：找到了就修，没找到还要继续推理"
          },
          {
            "line": 162,
            "text": "## 依据与本次验证"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "并发代码里，“我已经写了，他过一会儿再读”不是足够的理由。需要知道写和读之间是否有语言认可的同步关系。Go 内存模型就是用来回答这个问题：不同 goroutine 的读写，哪些顺序有保证，哪些程序存在数据竞争。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这篇文章不从 CPU 缓存示意图猜结论，而是从一段安全发布代码开始，再看一个没有数据竞争却仍然超卖的库存例子。学完应能分清三件事：执行时间上的先后、happens-before 关系、业务上的正确性。[^model]",
            "truncated": false
          }
        ]
      },
      "source_sha256": "04316f3d1594485d96cbf2c60500c8181cfc753306f5759afd2b75373335043b",
      "source_line_count": 330,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-013",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/02_并发编程/Go 并发工具选型.md",
      "title": "Go 并发工具选型：errgroup、singleflight、semaphore 各管哪一段",
      "priority": "P2",
      "treatment": "static",
      "concept": "errgroup、singleflight 与 semaphore",
      "recommended_medium": "职责与生命周期对照表",
      "reason": "本章核心是选对问题；一张对照表比把三种并发协议混成一个游戏清楚。",
      "reuse_family": "tool-selection",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 用一个问题选出入口"
          },
          {
            "line": 18,
            "text": "## errgroup：共同工作的错误边界"
          },
          {
            "line": 32,
            "text": "### 一个分支失败，另一个怎样退出"
          },
          {
            "line": 54,
            "text": "### 限数量要同时看“提交者”会不会等"
          },
          {
            "line": 68,
            "text": "## singleflight：只合并正在重叠的同 key 调用"
          },
          {
            "line": 80,
            "text": "### 用一个开关保证两次请求真的重叠"
          },
          {
            "line": 118,
            "text": "### 放弃等待，和取消共享工作，要分别决定"
          },
          {
            "line": 145,
            "text": "## semaphore：用权重表示资源占用"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "并发代码里有三种容易混淆的需求：一批任务一起成功或失败；同一个数据正在加载时别重复加载；资源不够时先排队。`errgroup`、`singleflight`、`semaphore` 分别解决它们。选型时先判断当前问题属于哪一种，比一开始就把三个工具叠起来更容易得到清楚的生命周期。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文标准库使用 Go 1.27.2，三个扩展包都固定为 `golang.org/x/sync v0.16.0`。这是一个独立发布的 Go 官方扩展模块，不是标准库的一部分；升级工具链不会自动升级项目里的 x/sync。本文讨论与测试均对应这个固定版本，不把 pkg.go.dev 上未来的默认版本行为混进来。[^module]",
            "truncated": false
          }
        ]
      },
      "source_sha256": "d246a42329a75552a53ec4e88e779bc07a63088f751d89e285e0926cb364bdbb",
      "source_line_count": 434,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-014",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/02_并发编程/Go 并发常见坑.md",
      "title": "Go 并发常见坑：用四个小反例找到真正的失误",
      "priority": "P2",
      "treatment": "static",
      "concept": "并发反例的失败条件",
      "recommended_medium": "等待环与代码对照",
      "reason": "已有可复现负例能准确展示版本与失败条件；静态等待图足以解释死锁。",
      "reuse_family": "wait-for-graph",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 闭包拿到的是变量，先看变量在哪里声明"
          },
          {
            "line": 53,
            "text": "### 工具链新，不等于这个包一定使用新循环语义"
          },
          {
            "line": 63,
            "text": "## 复制锁：复制出来的字段可能仍指向同一份数据"
          },
          {
            "line": 98,
            "text": "## 死锁：沿着“他在等谁”画一个圈"
          },
          {
            "line": 137,
            "text": "## 关闭后发送：不要先猜状态，再做动作"
          },
          {
            "line": 169,
            "text": "## 把失败从随机变成可解释"
          },
          {
            "line": 175,
            "text": "## 依据与本次验证"
          },
          {
            "line": 194,
            "text": "## 完整代码与复现方法"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "并发错误常常表现为“偶尔错一次”，但排查时不必先靠压测碰运气。循环闭包、复制锁、等待成环、关闭后发送，都可以缩成很小的例子，把出错的条件明确安排出来。这篇文章重点说明怎样读懂失败，而不只是列出禁忌。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文用 Go 1.27.2 实际运行。正常示例模块声明 `go 1.25.0`；另有一个 `go 1.21` 的独立模块，专门对比循环变量的旧语义。失败程序单独运行并加外部超时，避免混进正常测试后让整批验证中断。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "c8d2c2f3fef99d5a41b3696dbd097e08dd13c2e6cda16fb5d2712854ed53c3c9",
      "source_line_count": 330,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-015",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/02_并发编程/Go 并发模式.md",
      "title": "Go 并发模式：把任务分开以后，怎样一起收尾",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "流水线背压与取消收尾",
      "recommended_medium": "生产者/worker/消费者队列演示",
      "reason": "调慢消费者后显示积压和阻塞传播，再取消，能检验每个发送端是否能退出。",
      "reuse_family": "backpressure",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 这些模式分别管什么"
          },
          {
            "line": 21,
            "text": "## 一份完整的小流水线"
          },
          {
            "line": 57,
            "text": "### 先确定关闭者，再启动工作"
          },
          {
            "line": 68,
            "text": "## 背压从接收者一路传回生产者"
          },
          {
            "line": 76,
            "text": "## 提前返回最容易漏掉的是发送端"
          },
          {
            "line": 101,
            "text": "## 限并发与限速：先分清名额代表什么"
          },
          {
            "line": 115,
            "text": "## 发布订阅：同一事件要送多份，慢订阅者要有政策"
          },
          {
            "line": 152,
            "text": "## errgroup：一个分支失败，让其余分支知道"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "worker pool、fan-out、pipeline 经常一起出现在同一段代码里。它们不是互斥的架构选项，而是从不同角度描述任务怎么分配、结果怎么流动。真正需要先决定的是：最多同时做多少事，任务失败后谁停下，下游不再读时上游怎样退出。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这篇文章围绕一批整数求平方展开：一个生产者发任务，两个 worker 计算，一个收集者读取结果。随后在同一个生命周期框架里解释信号量、发布订阅和 errgroup。示例使用 Go 1.27.2，errgroup 固定为独立模块 `golang.org/x/sync v0.16.0`。[^pipeline][^group]",
            "truncated": false
          }
        ]
      },
      "source_sha256": "cfcf59ddf3757f0fe37c282308e95f0ec11ed15172279371dd2efd2014bf11c0",
      "source_line_count": 379,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-016",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/02_并发编程/Goroutine.md",
      "title": "Goroutine：启动以后，怎样让它按时结束",
      "priority": "P2",
      "treatment": "static",
      "concept": "goroutine 生命周期责任",
      "recommended_medium": "启动、取消、退出时序图",
      "reason": "主题是接口与所有权；保留阻塞点逐项检查，比自动播放任务数量更有用。",
      "reuse_family": "lifecycle",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 从调用方看，一项后台工作至少有两个出口"
          },
          {
            "line": 69,
            "text": "## “谁启动谁负责退出”，怎样落实到接口"
          },
          {
            "line": 84,
            "text": "### 每个阻塞点都要单独检查"
          },
          {
            "line": 102,
            "text": "## 创建成本：便宜到可以常用，但不是没有上限"
          },
          {
            "line": 118,
            "text": "## 泄漏通常藏在一个再也等不到的事件里"
          },
          {
            "line": 135,
            "text": "## 依据与本次验证"
          },
          {
            "line": 151,
            "text": "## 完整代码与复现方法"
          },
          {
            "line": 161,
            "text": "### go.mod"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "写一个 `go f()` 很容易。把它放进服务里之后，还要能回答：这项工作归谁管，卡住时谁通知它退出，调用方怎样知道它真的退出了。这篇文章用一个后台 worker 把这三个问题连起来，再看创建成本和泄漏排查。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "先约定范围：goroutine 是 Go 程序里独立推进的一条执行路径；并发允许多条路径交错推进，并行才表示它们同时在处理器上运行。启动多个 goroutine 不承诺它们同时运行，也不承诺先启动的先完成。`go` 语句的函数和参数在当前 goroutine 中求值，函数调用在新 goroutine 中执行；返回值会被丢弃。[^go]",
            "truncated": false
          }
        ]
      },
      "source_sha256": "512432c19a7537f119dbb3d05c9b2df1da0051649ecb96c89113d3cfdc23b3e0",
      "source_line_count": 289,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-017",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/02_并发编程/context.md",
      "title": "context：让一条调用链共用退出信号和时间预算",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "context 取消树与 deadline",
      "recommended_medium": "取消传播树",
      "reason": "选择取消父/子节点并改变期限，可直观看到传播方向及子级不能延长总预算。",
      "reuse_family": "cancellation-tree",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 16,
            "text": "## 先画出取消会走到哪里"
          },
          {
            "line": 45,
            "text": "## 每次派生都接住 cancel，即使预计很快会成功"
          },
          {
            "line": 67,
            "text": "### 子级只能收紧有效 deadline"
          },
          {
            "line": 91,
            "text": "## Err 回答取消类别，Cause 保留具体原因"
          },
          {
            "line": 117,
            "text": "## 有 Done 并不代表代码就会停"
          },
          {
            "line": 133,
            "text": "## WithValue 只放请求范围内需要跨层流转的元数据"
          },
          {
            "line": 168,
            "text": "## 两个扩展入口，用前先看清谁负责善后"
          },
          {
            "line": 174,
            "text": "## 依据与本次验证"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "一个 HTTP 请求要查缓存、访问数据库、调用下游服务。用户已经断开连接，或者请求的总时间用完了，这些分支还要不要继续？`context` 把这类决定沿调用链传下去。它不负责调度任务，也不强制杀掉 goroutine；使用它的函数需要主动检查信号，或把它交给真正支持取消的下游 API。[^package]",
            "truncated": false
          },
          {
            "line": 14,
            "text": "这些方法可以被多个 goroutine 同时调用。不过把一个可变 map 放进 `Value`，不会因此让 map 的并发读写安全。context 自己的并发约定与被携带对象的安全性，是两件事。[^context]",
            "truncated": false
          }
        ]
      },
      "source_sha256": "80585efd723e4875b2ef38547bebf1da0b15487774c7a3b3ef5c52fd0a73794d",
      "source_line_count": 322,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-018",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/02_并发编程/sync 包.md",
      "title": "sync 包：从保护一组状态，到等一批任务结束",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "sync 各工具守住的约束",
      "recommended_medium": "职责表与可运行例子",
      "reason": "包含多种语义不同的原语，优先保留逐项契约和测试，不做一个泛化锁模拟器。",
      "reuse_family": "reference",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先按工作选择入口"
          },
          {
            "line": 22,
            "text": "## Mutex：锁住的是约定，不是字段本身"
          },
          {
            "line": 49,
            "text": "### 锁的几个边界"
          },
          {
            "line": 56,
            "text": "### RWMutex：读多只是候选条件，不是性能结论"
          },
          {
            "line": 69,
            "text": "## WaitGroup：把“都结束了”做成一个可靠边界"
          },
          {
            "line": 87,
            "text": "## Once：执行一次，连失败语义也只认一次"
          },
          {
            "line": 115,
            "text": "## Cond：等待的主角是条件，通知只是催你再看一眼"
          },
          {
            "line": 158,
            "text": "## Pool：借出临时对象，归还后停止使用"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "`sync` 里有锁，也有计数器、一次性初始化和临时对象池。它们解决的问题不同，不能因为都带“并发安全”就相互替代。最省力的学法，是先说清自己要守住什么约束，再选对应的工具。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文使用 Go 1.27.2。`OnceFunc`、`OnceValue`、`OnceValues` 从 Go 1.21 起可用；`WaitGroup.Go` 从 Go 1.25 起可用。文末模块声明 Go 1.25，因此示例可以使用后者。类型化原子值在 `sync/atomic`，不是 `sync` 的子类型。[^sync][^atomic]",
            "truncated": false
          }
        ]
      },
      "source_sha256": "0f15c15187e5e6b0d43078a2829cc315029ee293f79457aed4ea000fe30f4886",
      "source_line_count": 447,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-019",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/03_运行时与底层原理/Go defer select timer 与 netpoller.md",
      "title": "Go defer、select、timer 与 netpoller：调用结束了，等待怎样结束",
      "priority": "P2",
      "treatment": "static",
      "concept": "defer、select、timer、netpoller 的责任",
      "recommended_medium": "事件和责任分层图",
      "reason": "API 行为带版本边界；先用静态时序解释到期、唤醒和执行的不同。",
      "reuse_family": "runtime-events",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先分清各自的责任"
          },
          {
            "line": 18,
            "text": "## defer：参数现在保存，调用以后执行"
          },
          {
            "line": 48,
            "text": "### 同样是 defer，实现成本可以差很多"
          },
          {
            "line": 80,
            "text": "## select：先求值，再选择，不按书写顺序排队"
          },
          {
            "line": 135,
            "text": "### 运行时怎样同时等待多个 channel"
          },
          {
            "line": 153,
            "text": "## timer：先区分“已经到期”和“接收方已经运行”"
          },
          {
            "line": 166,
            "text": "### Stop、Reset：旧文章最容易过期的一处"
          },
          {
            "line": 224,
            "text": "### Go 1.27.2 如何处理被移除的开关"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "`defer` 管清理，`select` 等多种条件，timer 把时间变成事件，netpoller 把内核 I/O 就绪变成 goroutine 的执行机会。这四件事经常出现在同一个请求里：建立连接、等待数据或超时、离开函数时清理资源。它们看起来都很简短，但“什么时候求值”“谁被停住”“返回时是否已经停止”决定了程序是否正确。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本篇先把可依赖的语言与 API 语义说清，再看 **Go 1.27.2、Linux/amd64** 的实现。普通程序、测试、三个 defer 基准以及 timer 兼容反例都实际运行了。netpoller 的 Linux 路径有本地 TCP 实验，kqueue 部分只核对源码，没有在 macOS 上执行。[^defer-spec][^select-spec][^time][^poll]",
            "truncated": false
          }
        ]
      },
      "source_sha256": "f580ba9e38a3e2eb8b7521e6b4c9272bbeb387814e9224aac46bde7079896b78",
      "source_line_count": 582,
      "existing_static_mermaid_count": 3,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-020",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/03_运行时与底层原理/Go 内存分配.md",
      "title": "Go 内存分配：一次申请怎样变成可用空间",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "对象大小、size class 与分配层级",
      "recommended_medium": "分配桶示意与字节账本",
      "reason": "调整对象尺寸观察内部空余，能解释申请字节与占用字节不同；表数据需绑定工具链。",
      "reuse_family": "allocation-budget",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先分层，别把几种“内存大小”相加"
          },
          {
            "line": 21,
            "text": "## 从对象到 span：按大小分组是在节省什么"
          },
          {
            "line": 36,
            "text": "## mcache、mcentral、mheap 各自省掉哪一步"
          },
          {
            "line": 64,
            "text": "## 看一眼实际分配：17 字节为什么记成 24 字节"
          },
          {
            "line": 96,
            "text": "## Go 1.27 的尺寸特化分配：层级还在，入口变了"
          },
          {
            "line": 112,
            "text": "## goroutine 栈为什么既能长大，也可能缩小"
          },
          {
            "line": 141,
            "text": "## 对象没用了，为什么 RSS 还在"
          },
          {
            "line": 158,
            "text": "## 依据与验证边界"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "在 Go 里写 `new(T)`，并不表示每次都向操作系统申请一块内存；对象不再使用，也不表示进程的 RSS 会马上下降。中间隔着编译器的分配决策、运行时的对象分配器、垃圾回收和向操作系统归还内存的过程。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这篇文章沿着一次申请往下走，解释 `mcache / mcentral / mheap`、size class、tiny 分配器和 goroutine 栈。读完应该能区分三个问题：程序申请了多少对象，运行时拿了多少空间，以及操作系统当前让多少页驻留在内存里。以下实现固定到 **Go 1.27.2、Linux/amd64**；阈值、内部结构和快路径都不是 Go 语言的永久承诺。[^malloc][^stats]",
            "truncated": false
          }
        ]
      },
      "source_sha256": "2a414e043a4863eb5cb23ab15238c134c5a3350aa8d6ae2497ff940725a842d6",
      "source_line_count": 323,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-021",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/03_运行时与底层原理/Go 垃圾回收.md",
      "title": "Go 垃圾回收：先看活着的对象，再谈调参",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "可达性标记与 GC 预算",
      "recommended_medium": "小对象图标记器及预算计算",
      "reason": "断开引用后逐轮标记，可纠正业务无用等于不可达；预算只演算公式，不预测暂停时间。",
      "reuse_family": "gc-reachability",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 垃圾是谁决定的：从根走得到，就先保留"
          },
          {
            "line": 20,
            "text": "### 三种颜色是在记录扫描进度"
          },
          {
            "line": 40,
            "text": "## 并发标记最难的地方：程序也在改指针"
          },
          {
            "line": 60,
            "text": "## 一轮 GC 哪些时候停，哪些时候并发"
          },
          {
            "line": 84,
            "text": "## Green Tea 改进了哪里，又没有改变什么"
          },
          {
            "line": 105,
            "text": "## GC assist：暂停很短，请求为什么仍然变慢"
          },
          {
            "line": 120,
            "text": "## GOGC：允许堆长多少，用空间换回收频率"
          },
          {
            "line": 138,
            "text": "### 用同一个分配过程看两种百分比"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "GC 能回收程序已经不需要的对象，但它判断“不需要”的依据是可达性，不是业务含义。缓存一直保存着过期数据、后台任务一直引用着请求对象时，GC 不知道这些东西已经没价值。反过来，对象已经不可达，也不表示内存马上回到操作系统。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "理解 GC，要把两件事接起来：它怎样在程序继续修改数据时安全地找出垃圾，以及为了及时完成这项工作，它要用多少 CPU 和内存。本篇覆盖三色标记、写屏障、并发阶段、STW、GC assist，以及 `GOGC`、`GOMEMLIMIT` 的选择。实现与实验固定到 **Go 1.27.2、Linux/amd64**；Go 语言规范不规定具体收集算法。当前默认收集器包含 Green Tea 的标记扫描机制，不能继续把旧版对象工作队列当成全部现状。[^spec][^mgc][^baseline]",
            "truncated": false
          }
        ]
      },
      "source_sha256": "b431e1595430f991df3c82b80b2bece6e9bc9f973542377cb7e3d62a9dd03bca",
      "source_line_count": 333,
      "existing_static_mermaid_count": 4,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-022",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/03_运行时与底层原理/Go 数据结构实现.md",
      "title": "Go 数据结构实现：值复制了，底下的数据复制了吗",
      "priority": "P2",
      "treatment": "static",
      "concept": "slice、map、channel、interface、string 的表示",
      "recommended_medium": "值与共享存储图",
      "reason": "多种布局适合并排参照；slice 与接口互动复用专章入口，避免重复实现。",
      "reuse_family": "shared-storage",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先看五种值各自带着什么"
          },
          {
            "line": 25,
            "text": "## slice：三个字段，解释共享与扩容"
          },
          {
            "line": 73,
            "text": "## map：Swiss Table 怎样少比较几个完整键"
          },
          {
            "line": 108,
            "text": "### 为什么删除需要墓碑，扩容需要目录"
          },
          {
            "line": 127,
            "text": "### 实现变了，这些语言行为没有变"
          },
          {
            "line": 159,
            "text": "## channel：缓冲队列之外，还要记录谁在等"
          },
          {
            "line": 198,
            "text": "## interface：先看动态类型，再看动态值"
          },
          {
            "line": 230,
            "text": "## string：只读字节序列，不是 rune 数组"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "把 slice、map、channel、interface 或 string 赋给另一个变量，复制的是这个值；至于值里面指向的数据是否共享，要看具体类型。很多“怎么连原来的数据也改了”“明明是 nil，判断却不等于 nil”的问题，就藏在这一层区别里。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本篇先讲语言保证的行为，再用 **Go 1.27.2、Linux/amd64** 的运行时表示解释原因。内部结构可以帮助推理，但字段、尺寸、扩容算法都不是稳定 ABI，不能照着图用 `unsafe` 去改运行时对象。map 使用当前 Swiss Table 实现，不把旧版 `hmap/bmap` 的溢出桶模型混进来。[^spec][^runtime][^swiss-history]",
            "truncated": false
          }
        ]
      },
      "source_sha256": "f0e97d9e48bf4f5cc90b116db5f90d9f9e4d41bcb4517c1828a1f0c42c902c0d",
      "source_line_count": 399,
      "existing_static_mermaid_count": 5,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-023",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/03_运行时与底层原理/什么是GMP.md",
      "title": "什么是 GMP：goroutine 怎样得到执行机会",
      "priority": "P0",
      "treatment": "interactive",
      "concept": "G、M、P 与等待后的调度",
      "recommended_medium": "可单步的教学调度模型",
      "reason": "让同一批 G 在本地/全局队列、执行和等待之间流动，区分 G 停放与系统调用占住 M。",
      "reuse_family": "gmp",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "targeted_main_explanation_read",
        "review_note": "正文从开篇到调度诊断段落；重点核对 G/M/P、队列、阻塞、P 移交与抢占边界。未逐行复核代码附录。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先分清三种资源"
          },
          {
            "line": 53,
            "text": "## 任务就绪以后，为什么还要排队"
          },
          {
            "line": 84,
            "text": "## 一个 G 阻塞时，究竟占住了什么"
          },
          {
            "line": 100,
            "text": "## 抢占为什么需要安全点"
          },
          {
            "line": 131,
            "text": "## 容器里的 GOMAXPROCS：先看谁决定了默认值"
          },
          {
            "line": 161,
            "text": "## 出问题时，按等待的位置找证据"
          },
          {
            "line": 174,
            "text": "## 依据与验证边界"
          },
          {
            "line": 192,
            "text": "## 完整代码与复现"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "一个 Go 服务可以同时保有很多 goroutine，但真正执行它们的是操作系统线程。GMP 要解决的，就是怎样把这些任务交给线程：有任务在等网络时，别让它占着执行资源；某个地方排了长队、另一个地方空闲时，把工作分过去；一个任务一直计算时，也要给别的任务运行的机会。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "理解这套机制以后，排查“goroutine 很多但 CPU 不高”“CPU 已经满了，请求还是排队”“容器只给两核，程序为什么开这么多线程”就有了不同的入口。下面以 **Go 1.27.2 的标准工具链实现**为准。G、M、P、队列形状和抢占策略是实现细节；语言规范承诺 `go` 语句启动并发执行，不承诺一个 goroutine 对应一个线程，也不承诺多久一定轮到它。[^spec][^runtime]",
            "truncated": false
          }
        ]
      },
      "source_sha256": "5a6606818c7608f0c6eddbbf1edbb9ebb335c4a053f948835fa4b7a231beff35",
      "source_line_count": 294,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-024",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/03_运行时与底层原理/逃逸分析.md",
      "title": "逃逸分析：为什么同一个指针，有时不分配堆内存",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "逃逸与实际分配的证据",
      "recommended_medium": "编译诊断、代码和复现实验",
      "reason": "优化器决定依赖版本和调用位置，浏览器规则模拟易误导；应保留真实编译结果。",
      "reuse_family": "compiler-evidence",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 栈和堆，首先是生命周期问题"
          },
          {
            "line": 32,
            "text": "## 先破除一个误判：取地址，不等于逃逸"
          },
          {
            "line": 60,
            "text": "## 返回指针：为什么调用位置会改变结果"
          },
          {
            "line": 98,
            "text": "## 常见逃逸场景，要沿着“谁还持有它”解释"
          },
          {
            "line": 111,
            "text": "### 闭包保留的不只是函数代码"
          },
          {
            "line": 130,
            "text": "### 接口装箱：有动态类型，不代表每次都申请一块堆"
          },
          {
            "line": 149,
            "text": "## 一个反直觉的当前行为：does not escape，仍可能走堆路径"
          },
          {
            "line": 192,
            "text": "## 怎样使用 -m，而不被一屏输出带偏"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "看到 `&x`，不能马上说“x 逃逸了”；看到编译器说 `does not escape`，也不能马上说“运行时一次堆分配都没有”。要判断分配，得把三件事接起来：值会活多久，编译器在这个调用位置知道多少，以及生成的代码实际走了哪条路径。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这篇用几个很小的对照程序解释这三件事。目标不是把所有逃逸都消掉，而是能读懂 `go build -gcflags=-m`，找到热路径上真正值得改的分配。本次编译、诊断和分配计数均固定到 **Go 1.27.2、Linux/amd64，普通优化构建**。语言允许的程序行为，与这版编译器选择栈还是堆，是不同层面的事实。[^spec][^analysis]",
            "truncated": false
          }
        ]
      },
      "source_sha256": "a3db9ab12514afac976c643f4203f91c1739f7760676db6d066343a75ce0c49d",
      "source_line_count": 329,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-025",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/04_工程化/Go 代码质量.md",
      "title": "Go 代码质量：格式、静态检查、测试和审查各管一段",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "质量门禁的分工",
      "recommended_medium": "检查清单与工具反例",
      "reason": "本章依赖真实检查器和审查判断，交互控件不会增加关键证据。",
      "reuse_family": "engineering-workflow",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 每个入口都应该有明确职责"
          },
          {
            "line": 22,
            "text": "## 格式自动化，留下注意力讨论行为"
          },
          {
            "line": 42,
            "text": "## 编译能过，vet 仍能发现问题"
          },
          {
            "line": 70,
            "text": "### go test 的 vet 子集：按版本查真实行为"
          },
          {
            "line": 82,
            "text": "## 静态分析看不懂你没有写出来的业务契约"
          },
          {
            "line": 104,
            "text": "## 三份风格资料该怎么用"
          },
          {
            "line": 116,
            "text": "## Code Review 看工具留给人的问题"
          },
          {
            "line": 128,
            "text": "## 把门禁连成短而可解释的流程"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "“CI 全绿”可能只代表代码排得整齐，也可能代表编译、静态检查、业务测试都跑过。要让这句话有用，先把门禁拆清楚。本文用两个故意写坏的小程序看工具能抓什么、抓不到什么，再把人工审查放回合适的位置。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "示例固定 Go 1.27.2、Linux/amd64。这里有一个与部分旧说明不同的实测结果：该版本的 `go test` 已经启用 slog 检查，帮助文字中的列表却仍漏写它。下文会把实际命令和对应源码放在一起，不用旧清单代替验证。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "3ef077accc49d4bc9671c06b6a299eedea20e43ff9405bbfa0771e08c9ab2009",
      "source_line_count": 223,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-026",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/04_工程化/Go 依赖注入.md",
      "title": "Go 依赖注入：谁创建对象，谁把依赖交给它，谁负责收尾",
      "priority": "P2",
      "treatment": "static",
      "concept": "依赖构造与逆序清理",
      "recommended_medium": "构造/失败回滚时序图",
      "reason": "对象图和逆序释放关系适合静态展开；保留本地代码验证错误路径。",
      "reuse_family": "ownership",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先让参数说清依赖"
          },
          {
            "line": 34,
            "text": "## 在入口处把依赖图接起来"
          },
          {
            "line": 62,
            "text": "## 初始化失败时，已经创建的资源也要释放"
          },
          {
            "line": 93,
            "text": "## 构造函数里的 nil 检查有明确边界"
          },
          {
            "line": 111,
            "text": "## 循环依赖通常先改职责，再挑工具"
          },
          {
            "line": 123,
            "text": "## 什么时候才值得引入代码生成"
          },
          {
            "line": 139,
            "text": "## 资料与验证"
          },
          {
            "line": 146,
            "text": "## 完整代码"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "报价服务需要一个价格库。最直接的写法是让服务自己打开数据库，但这样测试、命令行程序和 HTTP 程序都得跟着它的初始化方式走。依赖注入把这一步移到外面：服务声明需要什么，由程序入口把已经准备好的依赖传进去。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这件事在 Go 里通常不需要框架。本文从一个普通构造函数开始，再处理两个更容易漏的问题：构造到一半失败怎么办，正常退出时按什么顺序释放资源。示例固定 Go 1.27.2、Linux/amd64。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "afacf9adc6fd7ed8251d793bab9416e13452771ea57f669ce313bf7d0e65426f",
      "source_line_count": 289,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-027",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/04_工程化/Go 工程工具选型.md",
      "title": "Go 工程工具选型：沿着一个项目的重复劳动逐个选择",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "Go 工程工具的分层选型",
      "recommended_medium": "工具职责表与原例",
      "reason": "选型由项目约束和版本决定，避免用不可靠的自动打分器代替文字判断。",
      "reuse_family": "tool-selection",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先看哪一种重复劳动需要工具"
          },
          {
            "line": 26,
            "text": "## CLI 开始有多个操作时，用 Cobra 管命令边界"
          },
          {
            "line": 62,
            "text": "## Testify 减少断言样板，但不能替你决定测什么"
          },
          {
            "line": 82,
            "text": "## 配置来源变多之后：选约定完整的 Viper，还是显式组合的 Koanf"
          },
          {
            "line": 118,
            "text": "## 日志先选契约，再比较 slog 与 zap"
          },
          {
            "line": 135,
            "text": "## 把本地反馈缩短：golangci-lint 管检查，Air 管重建重启"
          },
          {
            "line": 152,
            "text": "### golangci-lint：规则少而清楚，通常比全开更有用"
          },
          {
            "line": 167,
            "text": "### Air：没有把新代码热补丁进旧进程"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "一个 Go 项目可能同时需要 CLI、测试、配置、日志和发布，但这不意味着开仓库的第一天就要把十几个工具全部装上。更有用的顺序是：先找到重复劳动，再检查一个工具怎样消除它、增加了什么约束，以及你能否在 CI 中复现它的行为。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文用一个报价 CLI 贯穿选择过程。Cobra、Testify、Viper、Koanf、zap 和 slog 有本次固定版本实测；golangci-lint、Testcontainers、Wire、Air、GoReleaser 提供经过固定版本资料核对的入口和完整配置示意，明确没有运行。尤其是本环境没有 Docker，不能把一段容器测试代码写成已验证集成测试。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "e7c02d34d3ef65e8599f408c61af439598e1c74e60cf512c665609557d9c64fd",
      "source_line_count": 721,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-028",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/04_工程化/Go 构建与发布.md",
      "title": "Go 构建与发布：从一个二进制追到它的源码、参数和验证结果",
      "priority": "P2",
      "treatment": "static",
      "concept": "构建输入、制品身份与发布",
      "recommended_medium": "构建溯源图",
      "reason": "关键是检查源码、参数、目标平台与制品之间的证据链，静态图已足够。",
      "reuse_family": "artifact-lineage",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 一份制品由哪些输入决定"
          },
          {
            "line": 34,
            "text": "## 先让程序自己报出版本"
          },
          {
            "line": 68,
            "text": "## build tag 选择的是源码，不是运行时开关"
          },
          {
            "line": 86,
            "text": "## 交叉编译可以很简单，目标运行环境未必简单"
          },
          {
            "line": 107,
            "text": "### cgo 增加了目标 C 工具链的要求"
          },
          {
            "line": 125,
            "text": "## Makefile 与 Taskfile：把重复命令写成一个可查入口"
          },
          {
            "line": 145,
            "text": "## 可重现性要靠约束输入来检验"
          },
          {
            "line": 157,
            "text": "## CI/CD：先验证，生成一份制品，再控制发布权限"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "“编译成功”只说明某组源码在某些构建条件下能变成制品。要把它交给别人运行，还得回答：为哪个系统和 CPU 构建，带了什么特性，来自哪个版本，做过哪些测试，失败后能否找回上一份制品。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文先实际生成两个功能版本和一个 Linux/arm64 二进制，再把同一套动作放进 Makefile、Taskfile 和 CI 的职责图中。所有本机实验固定 Go 1.27.2、Linux/amd64；arm64 制品只编译和检查元数据，没有在目标机器执行，也没有发布到线上。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "1226518f4f70bf31a5eb2993c53ea8b5965f7fb3ba112f9b60f64b76ceedf823",
      "source_line_count": 350,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-029",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/04_工程化/Go 模块管理.md",
      "title": "Go 模块管理：弄清这次构建到底用了哪份代码",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "最小版本选择 MVS",
      "recommended_medium": "可切换依赖版本的有向图",
      "reason": "修改一条最低版本需求后重新求构建列表，能解释名称中的“最小”并非选所有最低版。",
      "reuse_family": "dependency-resolution",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先分清四个名字"
          },
          {
            "line": 20,
            "text": "## go.mod 选依赖，go.sum 留校验依据"
          },
          {
            "line": 69,
            "text": "## “最小版本选择”为什么选出了较高版本"
          },
          {
            "line": 102,
            "text": "### 版本号表达的是兼容承诺"
          },
          {
            "line": 114,
            "text": "## replace 和 go work：方便联调，也会隐藏缺失的发布依赖"
          },
          {
            "line": 140,
            "text": "## 私有模块：先决定请求该发给谁"
          },
          {
            "line": 155,
            "text": "## vendor：把构建所需依赖带进仓库"
          },
          {
            "line": 163,
            "text": "## 资料与验证边界"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "同事改了一个依赖，你的程序为什么也变了？本地联调通过，CI 为什么找不到包？这些问题通常要同时看三件事：import 指向哪个模块、依赖图选中了哪个版本、主模块或工作区有没有把代码替换掉。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文沿着一次构建把它们连起来。示例固定 Go 1.27.2、Linux/amd64；公开依赖固定 `golang.org/x/text v0.28.0`，其余实验用本地小模块，不需要私有仓库账号。实际输出和完整目录都在文中，私有仓库访问只解释规则，不冒充已经登录验证。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "2c09c878a02493723d52d4203a23fcd25d079fb0cc0093ae2acf37b6d84f000f",
      "source_line_count": 338,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-030",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/04_工程化/Go 测试.md",
      "title": "Go 测试：让一次失败告诉你该改哪里",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "测试层次与可解释失败",
      "recommended_medium": "原测试、失败输出与复现命令",
      "reason": "交互不能替代编译运行、fake 与真实 HTTP 边界，保留已有实验更诚实。",
      "reuse_family": "engineering-workflow",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先把要验证的东西拆成三层"
          },
          {
            "line": 33,
            "text": "## 表驱动测试：把行为契约写成能读的用例"
          },
          {
            "line": 72,
            "text": "### Helper 让报错回到调用处，Cleanup 让资源跟测试范围一起结束"
          },
          {
            "line": 101,
            "text": "## fake：只替换这次测试不打算验证的边界"
          },
          {
            "line": 123,
            "text": "## httptest：同一个 handler，验证两种边界"
          },
          {
            "line": 151,
            "text": "## 覆盖率：先看哪一块没走到"
          },
          {
            "line": 172,
            "text": "## 基准测试：先说测的是什么，再看 ns/op"
          },
          {
            "line": 196,
            "text": "## 模糊测试：程序生成输入，你负责提供判据"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "一个测试只说“失败了”，你仍得从头调试；一个测试说“零件单价 250、数量 2，报价却不是 500”，问题范围就小得多。本文用一个报价服务把表驱动测试、fake、HTTP 测试、基准、模糊测试和覆盖率接起来，重点是每种测试回答什么问题，以及它没覆盖什么。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "示例在 Go 1.27.2、Linux/amd64 上运行。它有真实的 HTTP 网络测试，但价格来源是手写 fake，没有启动数据库。读到“集成测试”时要看清这个边界。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "b890887a1288f79343082e4e57a33996cc89bf4735e278fe6106d186588f5e19",
      "source_line_count": 485,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-031",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/04_工程化/Go 结构化日志.md",
      "title": "Go 结构化日志：让一次事件能查到，也别把秘密写进去",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "日志字段、脱敏与输出责任",
      "recommended_medium": "日志样例与测试",
      "reason": "本章着重事件契约和安全边界，不需要增加会诱导输入真实秘密的在线日志框。",
      "reuse_family": "logging",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先确定一条事件应该长什么样"
          },
          {
            "line": 32,
            "text": "## 日常用到哪些入口"
          },
          {
            "line": 47,
            "text": "## 级别改变过滤，不会自动改变告警"
          },
          {
            "line": 68,
            "text": "### 被过滤的日志也可能先算完参数"
          },
          {
            "line": 74,
            "text": "## context 里有请求 ID，不代表日志会自动出现它"
          },
          {
            "line": 99,
            "text": "## 脱敏要发生在信息进入日志之前"
          },
          {
            "line": 129,
            "text": "## 级别、采样和高基数字段是三件事"
          },
          {
            "line": 135,
            "text": "## 输出失败和阻塞，不能指望 Info 返回 error"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "“创建订单失败”这句话只告诉你发生了什么；如果同一秒有几千个请求，还需要知道它属于哪个请求、失败在哪个阶段、用了多久。结构化日志把这些信息保留成字段，方便机器筛选，也方便人顺着调用过程定位。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文用标准库 slog 建一个小日志入口，实际观察级别、context、请求 ID、脱敏和输出失败。示例固定 Go 1.27.2、Linux/amd64。为让输出可复现，示例特意移除了时间字段；生产日志通常应保留时间。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "7fa113d4ca46908144caa48bb2a072ee9315d3431a0010c06b5b4cd03704474c",
      "source_line_count": 287,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-032",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/04_工程化/Go 配置管理.md",
      "title": "Go 配置管理：启动前，把来源、类型和错误处理清楚",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "配置来源优先级与缺失值",
      "recommended_medium": "配置覆盖来源追踪器",
      "reason": "切换未设置、空串、零值后显示最终值来自哪一层，能揭示覆盖规则。",
      "reuse_family": "configuration-resolution",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先决定哪些东西属于配置"
          },
          {
            "line": 21,
            "text": "## 优先级必须能画出来，也能测出来"
          },
          {
            "line": 50,
            "text": "## 环境变量都是文本，空串不等于没设置"
          },
          {
            "line": 70,
            "text": "## 配置文件：拼错字段时，早点失败更有帮助"
          },
          {
            "line": 90,
            "text": "## 命令行参数也有“未设置”和“设置为零”的区别"
          },
          {
            "line": 104,
            "text": "## 敏感配置与普通配置要分开处理"
          },
          {
            "line": 118,
            "text": "## 热更新：发布整份有效快照，失败时保留上一份"
          },
          {
            "line": 139,
            "text": "## 资料与验证范围"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "同一个服务在本地监听 8080，到了测试环境却监听 9000。是文件改了、环境变量覆盖了，还是命令行参数生效了？配置管理首先要回答这个问题，然后才是挑 YAML、JSON 或某个配置库。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文写一个只有端口、超时和调试开关的加载器，沿着“默认值 → 文件 → 环境变量 → 命令行 → 校验”的过程观察结果。这个优先级是本例的公开约定，不是 Go 统一规定。示例在 Go 1.27.2、Linux/amd64 运行，不依赖外部配置服务。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "24db5df0749e4a5b20d783dc0617b85813083d9493c6717c3a0a33e885dd6726",
      "source_line_count": 349,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-033",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/04_工程化/Go 项目结构.md",
      "title": "Go 项目结构：让目录边界和依赖方向说同一件事",
      "priority": "P2",
      "treatment": "static",
      "concept": "项目目录与实际依赖方向",
      "recommended_medium": "目录树与 import 图",
      "reason": "目录名不是架构保证；并排显示实际依赖即可支持判断。",
      "reuse_family": "package-boundaries",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先区分工具强制的规则和团队惯例"
          },
          {
            "line": 22,
            "text": "## 三个包足以解释一个可工作的结构"
          },
          {
            "line": 85,
            "text": "## internal 的墙画在哪里"
          },
          {
            "line": 105,
            "text": "## 按领域还是按层：看修改会沿着哪里走"
          },
          {
            "line": 121,
            "text": "## 循环依赖是具体的编译错误，也可能是责任混在一起的信号"
          },
          {
            "line": 140,
            "text": "## 什么时候值得多建一个包"
          },
          {
            "line": 150,
            "text": "## 资料与实验范围"
          },
          {
            "line": 159,
            "text": "## 完整复现文件"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "一个订单接口修改价格计算，结果要同时改 handler、service、manager、repository 和 util，目录再整齐也难维护。本文要解决的是：哪些代码放在一起，哪些依赖可以跨过去，以及项目长大时从哪里拆。`cmd`、`internal`、`pkg` 会用到，但它们各自有不同的地位，不能当作必须集齐的文件夹。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "示例从一个只有三个包的订单预览程序开始。它不连接数据库，也不伪装成完整电商服务；这样可以直接用实际 import、运行输出和编译失败看清结构。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "6cca3369e67146743c6324ab1e0d9c446b11a6a786677ab1f37235d0c2024515",
      "source_line_count": 291,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-034",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/04_工程化/设计原则.md",
      "title": "设计原则：用 Go 守住业务规则，再决定需要多少抽象",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "以业务变化检验抽象",
      "recommended_medium": "贯穿案例和代码评审题",
      "reason": "设计选择需要上下文论证，强行量化或拖动层级会淡化实际约束。",
      "reuse_family": "design-judgment",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先把不能被破坏的规则写出来"
          },
          {
            "line": 39,
            "text": "## SOLID：用五个问题审查变化，而不是照搬类层次"
          },
          {
            "line": 57,
            "text": "## Option 模式：让可选项可读，也把默认值和错误写清楚"
          },
          {
            "line": 85,
            "text": "## 策略和工厂：一个负责做事，一个负责选谁做"
          },
          {
            "line": 108,
            "text": "## 装饰器与中间件：包装前后顺序是行为的一部分"
          },
          {
            "line": 122,
            "text": "## 整洁架构和六边形架构：关心依赖方向，不必复制同一目录树"
          },
          {
            "line": 145,
            "text": "## DDD：先对齐业务语言，再决定对象叫什么"
          },
          {
            "line": 168,
            "text": "## 用变化检验抽象是否值得保留"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "订单提交之后能不能继续加商品？新加一种配送方式，要不要改整个订单流程？HTTP 框架换掉时，计价规则需不需要跟着搬家？设计原则的价值在于帮你回答这些具体问题，不能只把五个字母或几层目录贴在项目上。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文从一个可运行的小订单对象开始，逐步解释 SOLID、Option、装饰器、策略、工厂，以及整洁架构、六边形架构和 DDD。代码是教学模型：只有数量、运费和提交状态，没有数据库、支付、库存或分布式事务。实验固定 Go 1.27.2、Linux/amd64。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "4a4e7747a1da0be0cd9f25ac36662a067c14f1e51fc0e4fde77dbc1254c5916d",
      "source_line_count": 376,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-035",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/05_计算机基础/Linux.md",
      "title": "Linux：从一个故障问题出发，选命令、读指标、写可控脚本",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "根据故障选 Linux 证据",
      "recommended_medium": "命令、输出与取证范围",
      "reason": "用户应练习读取真实证据；模拟终端容易被误当作已运行的系统状态。",
      "reuse_family": "operations-evidence",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先选问题，再选工具"
          },
          {
            "line": 38,
            "text": "## CPU 与进程：top 看变化，ps 留快照"
          },
          {
            "line": 40,
            "text": "### top：确认是谁忙，以及忙在哪一类 CPU 时间"
          },
          {
            "line": 57,
            "text": "### ps：一次性确认身份、父子关系和状态"
          },
          {
            "line": 72,
            "text": "## 套接字与文件：先确认对象真的存在"
          },
          {
            "line": 74,
            "text": "### ss 与 netstat：连接状态不是业务状态"
          },
          {
            "line": 99,
            "text": "### lsof：把进程与资源连起来"
          },
          {
            "line": 114,
            "text": "## 进一步取证：strace 与 tcpdump 都要限制范围"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "“CPU 高”“内存满”“端口不通”都还不是诊断。真正有用的下一句话是：哪个进程、哪段时间、哪种资源、看到了什么证据。Linux 命令很多，先记住每个工具能回答的问题，再记参数，比背一串命令更容易用对。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文按排查顺序介绍 `top`、`ps`、`ss` / `netstat`、`lsof`、`strace`、`tcpdump`、`df`、`free`、`vmstat`、`iostat`，再讲权限、systemd 与 Shell 脚本。实际输出来自当前 Linux/amd64 环境，只查看自身测试进程和临时工作目录；没有附着生产进程、抓取业务流量或修改系统服务。工具缺失、权限不足也按真实结果列出。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "6e674b11b0bf367a869d2078169d9880d1667a70d32ee268e411d9ca6baf1912",
      "source_line_count": 511,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-036",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/05_计算机基础/操作系统.md",
      "title": "操作系统：把“程序在等什么”和“资源归谁”讲清楚",
      "priority": "P2",
      "treatment": "static",
      "concept": "应用对象与内核资源的对应",
      "recommended_medium": "FD、内存映射与等待分层图",
      "reason": "本章跨度大，静态资源关系比一个省略大量内核行为的统一模拟更可靠。",
      "reuse_family": "os-resources",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先分清三种动作：进入内核、等待资源、换一个任务执行"
          },
          {
            "line": 20,
            "text": "## 进程、线程、协程：隔离什么，共享什么"
          },
          {
            "line": 22,
            "text": "### 进程给资源划边界，线程在边界内执行"
          },
          {
            "line": 50,
            "text": "### fork 后“值相同”，不代表普通内存仍是同一份可写状态"
          },
          {
            "line": 80,
            "text": "## 虚拟内存：地址、物理页和文件不是同一件东西"
          },
          {
            "line": 108,
            "text": "### MAP_PRIVATE 和 MAP_SHARED，差在修改往哪里去"
          },
          {
            "line": 134,
            "text": "### 页缓存让读取更快，也把“写成功”和“落盘”分开"
          },
          {
            "line": 140,
            "text": "## 文件描述符：整数只是入口，打开状态在另一层"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "一个 Go 服务开了十万条连接，不等于开了十万个内核线程；删除了日志文件，磁盘空间也未必马上回来；调用 `write` 成功，更不等于数据已经持久保存。这些现象背后都有一个共同问题：应用看到的对象，与内核实际管理的对象，不是一一对应的。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文用 Linux 把这层关系展开：谁执行代码，地址如何对应内存，文件描述符引用什么，I/O 如何等待，以及“零拷贝”究竟省掉哪一步。进程与线程是广泛存在的概念，epoll、sendfile 等接口及本文实验则是 Linux 语境，不冒充跨平台保证。实验使用 Linux 6.18.44、GCC 14.2.0；Go 相关解释核对了 Go 1.27.2 文档和固定版本源码。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "355821ea74d374c2d29525ff8991bf6ae8b3a2c67ec60b27f74e80931cb6a79c",
      "source_line_count": 460,
      "existing_static_mermaid_count": 5,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-037",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/05_计算机基础/数据结构与算法.md",
      "title": "数据结构与算法：从操作需求出发，理解每一种结构为什么存在",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "堆的不变量与局部修复",
      "recommended_medium": "小堆插入/删除单步演示",
      "reason": "只选堆这一处逐步更新父子关系，避免把八类结构做成无主线动画库。",
      "reuse_family": "heap-invariants",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 复杂度先写清口径：最坏、期望和摊还不能互换"
          },
          {
            "line": 37,
            "text": "## 先有一张操作地图"
          },
          {
            "line": 56,
            "text": "## 数组、链表、栈、队列：先解决位置与顺序"
          },
          {
            "line": 58,
            "text": "### 数组：位置可计算，所以随机访问便宜"
          },
          {
            "line": 66,
            "text": "### 链表：便宜的是已定位节点处的改链，不是按值查找"
          },
          {
            "line": 95,
            "text": "### 栈和队列是访问规则，可以由不同结构实现"
          },
          {
            "line": 120,
            "text": "## 哈希表与堆：一个按键找，一个按优先级取"
          },
          {
            "line": 122,
            "text": "### 哈希表：哈希值先缩小范围，最后仍要判断键相等"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "要保存一批数据，先问三个问题：按什么方式找，按什么顺序取，修改有多频繁。订单 ID 查详情、取当前最早到期任务、按时间扫描一段记录，看起来都是“存和取”，需要维护的关系却不同。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这篇文章把知识地图里的结构与算法逐一展开。每一项都看它维护什么不变量、怎样完成操作、适合什么场景，以及成本藏在哪里。文中的短程序用 Go 1.27.2 实际运行，完整代码和测试放在来源之后。B+ 树、红黑树、跳表和真实 LSM 引擎主要依据原论文、官方实现解释，没有把手绘结构或内存模拟说成数据库实测。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "93c14ccdb09d6baa67149ff7a1124a25207a3a526c57d3d3c84fd6b4935a7982",
      "source_line_count": 1097,
      "existing_static_mermaid_count": 8,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-038",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/05_计算机基础/计算机网络.md",
      "title": "计算机网络：沿着一次请求，把协议各自负责的事分清",
      "priority": "P2",
      "treatment": "static",
      "concept": "一次 HTTPS 请求的协议责任",
      "recommended_medium": "协议分层与双向时序图",
      "reason": "静态图可同时标明请求边界、TLS、DNS 和连接复用，不虚构公网时延。",
      "reuse_family": "network-lifecycle",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 一次 HTTPS 请求里，谁在解决什么问题"
          },
          {
            "line": 36,
            "text": "## TCP：可靠的是字节顺序，消息边界要自己定义"
          },
          {
            "line": 38,
            "text": "### 先看最容易写错的 Read"
          },
          {
            "line": 75,
            "text": "### 三次握手：让两边都确认起始序号"
          },
          {
            "line": 95,
            "text": "### 滑动窗口和拥塞控制，分别保护谁"
          },
          {
            "line": 111,
            "text": "### 四次挥手、半关闭和 TIME_WAIT"
          },
          {
            "line": 139,
            "text": "## UDP：有数据报边界，但没有替你承担可靠性"
          },
          {
            "line": 154,
            "text": "## HTTP 的版本差别，主要落在消息如何共用连接"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "请求超时，可能是域名没解析出来、连接没建立、证书没通过，也可能是请求已经到达服务端，只是响应还没回来。把这些情况都叫“网络不好”，很难知道下一步该查哪里。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文沿着“找到服务器 → 建立传输 → 保护数据 → 交换请求 → 分发流量”这条路，讲清 TCP、UDP、HTTP、TLS、DNS、WebSocket、CDN 和负载均衡。目标是能解释一个现象，并选对验证办法。协议规则以相应 RFC 为准；文中的程序实测来自 Go 1.27.2、Linux/amd64，只在本机回环接口运行。没有把回环测试写成公网性能结论。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "eeebc46c5768f57d1d4c2c5fceae821441eea00c2e822d7555ea51e9a5d532ef",
      "source_line_count": 583,
      "existing_static_mermaid_count": 6,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-039",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/06_Web与API/Go HTTP 中间件.md",
      "title": "Go HTTP 中间件：包住 Handler，也要守住响应边界",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "中间件包装与响应提交",
      "recommended_medium": "中间件顺序步进器",
      "reason": "改变鉴权、日志和 recover 顺序后观察进入/退出与首次写出，能解释组合边界。",
      "reuse_family": "middleware-order",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 一层包装的完整含义"
          },
          {
            "line": 48,
            "text": "## 请求 ID 和日志：先能找到一次请求，再解释发生了什么"
          },
          {
            "line": 56,
            "text": "### 状态码为什么需要包 ResponseWriter"
          },
          {
            "line": 68,
            "text": "## ResponseWriter 包装器会改变接口能力"
          },
          {
            "line": 96,
            "text": "## recover：响应写出去前后是两种处理"
          },
          {
            "line": 110,
            "text": "## CORS 和鉴权处理的是两个问题"
          },
          {
            "line": 129,
            "text": "## 限流先说清限制的是什么"
          },
          {
            "line": 160,
            "text": "## 超时取消业务，和超时结束响应，要分别设计"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "日志、鉴权、CORS、限流看起来都能写成“处理前做一点事，处理后做一点事”。真正难的是它们叠在一起以后：鉴权失败有没有日志？跨域页面能不能读到 401？SSE 的 Flush 有没有被包装器藏掉？Handler 写了一半再 panic，还能不能返回 500？",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这篇用标准 `net/http` 把这些关系拆开。所有实验在 Go 1.27.2、Linux/amd64 执行，完整中间件和测试放在文末。示例用于验证组合边界，不把一组短函数包装成开箱即用的生产安全组件。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "c1901d4475ace4f7ecf2eb3797967514c680a462c6b868d2d59c6f4eb78d3a28",
      "source_line_count": 673,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-040",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/06_Web与API/Go Web 与 RPC 工具选型.md",
      "title": "Go Web 与 RPC 工具选型：先分清它们替你承担哪一层",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "Web 与 RPC 工具层次",
      "recommended_medium": "职责和互通边界表",
      "reason": "框架选型主要是版本、协议和既有资产判断，不需要自动推荐控件。",
      "reuse_family": "tool-selection",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先按层选择，不必每层都选一个"
          },
          {
            "line": 33,
            "text": "## 四种 Web 框架：先看 Handler 是否仍是 net/http"
          },
          {
            "line": 44,
            "text": "### Gin：方便的 Context，也带有自动行为"
          },
          {
            "line": 52,
            "text": "### Echo：让处理函数把错误交给统一出口"
          },
          {
            "line": 60,
            "text": "### chi：保留标准 Handler 的组合方式"
          },
          {
            "line": 81,
            "text": "### Fiber：换了 HTTP 栈，就要重新验收边界"
          },
          {
            "line": 89,
            "text": "## validator：标签是程序，不能凭英文猜语义"
          },
          {
            "line": 119,
            "text": "## grpc-go 与 connect-go：同用 Protobuf，服务接入方式不同"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "Gin、Echo、chi、Fiber 是处理 HTTP 请求的框架或路由器；validator 检查字段；grpc-go 和 connect-go 处理 RPC；Buf 管理 Protobuf 契约；swag 与 oapi-codegen 在代码和接口文档之间生成文件。把这十个工具放进同一张性能榜，无法回答一个项目该怎么组合它们。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "选型先看已经拥有的代码、客户端和运维能力，再看工具能减少哪些重复工作、会引入哪些语义变化。本篇逐个说明常用入口与限制，并实际验证 chi、validator、Connect 三种协议兼容和 OpenAPI 模型生成。版本全部明确列出，固定版本是复现和查证基线，不表示应在新生产项目里不经审查地沿用。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "9247a0661c95f185b83d835337378de8c54cd112de923e60598ae1dd49f2e094",
      "source_line_count": 606,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-041",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/06_Web与API/Go 请求处理.md",
      "title": "Go 请求处理：把不可信输入变成明确结果",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "请求解析、大小限制与输入契约",
      "recommended_medium": "短请求样例和断言",
      "reason": "边界测试已是最直接教材；交互表单易掩盖具体 net/http 行为。",
      "reuse_family": "request-contracts",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 参数从哪里来，就从哪里明确读取"
          },
          {
            "line": 39,
            "text": "## JSON 接口先解决三个问题：多大、几份、什么字段"
          },
          {
            "line": 69,
            "text": "## 缺失、null、零值不是一回事"
          },
          {
            "line": 89,
            "text": "### omitempty 控制输出，不替你定义输入语义"
          },
          {
            "line": 143,
            "text": "## 时间和大整数要在契约里说清楚"
          },
          {
            "line": 166,
            "text": "## JSON 性能先测真实形状，再考虑换实现"
          },
          {
            "line": 179,
            "text": "## 文件上传：内存阈值不等于总大小上限"
          },
          {
            "line": 194,
            "text": "## 文件下载优先复用 ServeContent 的协议处理"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "一个请求到达 Handler 时，参数还只是客户端发来的字节。能解码不代表合法，合法不代表有权限，响应写成功也不代表客户端完整收到了。请求处理要把这些边界分开，才知道错误该在哪里被拒绝。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文从一个只接收 `count` 的 JSON 接口开始，再扩展到上传、下载和 SSE。示例运行于 Go 1.27.2、Linux/amd64；这一版本已经提供 `encoding/json/v2`，下文会明确区分它和原来的 `encoding/json`，不把两者默认行为混在一起。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "de9930008899f9a5f5282b622697980cdb9fb7d118d5459949b4ef9ae89233a0",
      "source_line_count": 686,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-042",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/06_Web与API/RESTful 设计.md",
      "title": "RESTful 设计：让调用方知道一次请求究竟做了什么",
      "priority": "P2",
      "treatment": "static",
      "concept": "重试、条件更新与 REST 契约",
      "recommended_medium": "请求/响应时序和状态表",
      "reason": "用两次并发更新及 ETag 静态对照即可解释丢失更新与幂等责任。",
      "reuse_family": "api-contracts",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先确定资源，再决定 URL"
          },
          {
            "line": 26,
            "text": "## 方法语义决定客户端能做什么假设"
          },
          {
            "line": 40,
            "text": "### 为什么 POST 重试需要业务幂等键"
          },
          {
            "line": 87,
            "text": "## 状态码先表达大类，错误体再解释细节"
          },
          {
            "line": 125,
            "text": "## 用条件请求避免重复传输和覆盖别人的修改"
          },
          {
            "line": 162,
            "text": "## 分页、过滤和排序必须一起设计"
          },
          {
            "line": 195,
            "text": "## 版本管理：改变路径并不能自动兼容"
          },
          {
            "line": 207,
            "text": "## OpenAPI 要写到能约束实现的程度"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "假设你要给订单系统设计 API。创建订单超时后，客户端能不能重试？两个人同时修改地址，后保存的人会不会覆盖先保存的内容？翻第二页时又插入了新订单，会不会重复看到上一页？这些问题不会因为 URL 写成复数名词就消失。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "RESTful 设计要把资源、操作语义、失败方式和演进规则放进一份稳定契约。本文用一个可修改的资源串起这些决定，并用 Go 1.27.2 的小实验验证条件请求、幂等去重与分页现象。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "f96bff7aaa5c4a842a6806394926cc2b10a0ca01bdf193a5230ceb88cea90daa",
      "source_line_count": 489,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-043",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/06_Web与API/RPC 与 gRPC.md",
      "title": "RPC 与 gRPC：像调用函数一样使用，也要按网络请求处理",
      "priority": "P2",
      "treatment": "static",
      "concept": "四种 RPC 流与半关闭",
      "recommended_medium": "四栏双向流时序图",
      "reason": "重点是两侧何时结束发送和如何读最终状态，静态时序更适合反复对照。",
      "reuse_family": "rpc-streams",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 从 proto 到一次调用，各层做什么"
          },
          {
            "line": 33,
            "text": "## Protobuf 的字段号是协议的一部分"
          },
          {
            "line": 64,
            "text": "### 演进时先看旧程序会怎样理解"
          },
          {
            "line": 77,
            "text": "## 四种调用模式，差别在两边何时结束发送"
          },
          {
            "line": 88,
            "text": "### 一元：error 也是协议结果"
          },
          {
            "line": 101,
            "text": "### 服务端流：读到消息以后还要读最终状态"
          },
          {
            "line": 128,
            "text": "### 客户端流：CloseAndRecv 表示发送完，再取汇总"
          },
          {
            "line": 147,
            "text": "### 双向流：CloseSend 只关闭自己发送的一半"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "本地函数返回错误时，调用方通常知道函数已经执行到哪一步。远程调用超时则不同：服务端可能没收到，也可能已经完成，只是结果没传回来。RPC 把编码、发包和分发藏在生成代码后面，却没有消除网络的不确定性。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "gRPC 适合需要强契约、多语言客户端和流式通信的服务。要用好它，先分清三层：Protocol Buffers 描述和编码消息，gRPC 定义调用、状态和流，业务自己负责权限、幂等与事务。本篇使用固定依赖 grpc-go v1.75.1、protobuf-go v1.36.6，在 Go 1.27.2、Linux/amd64 上实际测试四种调用模式；这些固定版本是复现基线，不是“当前最新版本”声明。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "40ac491706a855beeca73396ede61980cdf906cd17c3bd240e11ebbe449b7004",
      "source_line_count": 617,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-044",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/06_Web与API/WebSocket GraphQL 与 Webhook.md",
      "title": "WebSocket、GraphQL 与 Webhook：三种不同的问题，三条不同的链路",
      "priority": "P2",
      "treatment": "static",
      "concept": "WebSocket、GraphQL、Webhook 的链路",
      "recommended_medium": "三种数据方向图",
      "reason": "三者负责不同问题，不适合合成一个互动选择器；分别标清故障责任。",
      "reuse_family": "protocol-boundaries",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先看数据向哪里走"
          },
          {
            "line": 26,
            "text": "## WebSocket：从 HTTP 握手进入消息协议"
          },
          {
            "line": 32,
            "text": "### 握手前完成鉴权和 Origin 策略"
          },
          {
            "line": 54,
            "text": "### 心跳需要一直有人读"
          },
          {
            "line": 76,
            "text": "### 长连接要同时限制消息、队列和等待时间"
          },
          {
            "line": 95,
            "text": "## GraphQL：客户端选字段，服务端决定如何取得它们"
          },
          {
            "line": 113,
            "text": "### 为什么一次查询可能变成 N+1 次访问"
          },
          {
            "line": 145,
            "text": "### 同时存在 data 和 errors 是正常的协议情况"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "聊天界面需要双方随时发消息，页面想一次取齐几个关联对象，支付平台要通知你订单状态变化。这三件事都常出现在 Web 系统里，但它们不是三种可以互换的“更高级 HTTP”。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "WebSocket 改变连接上的通信方式，GraphQL 定义查询与执行模型，Webhook 约定系统间如何投递事件。先按问题选机制，再处理各自的失败边界。本篇在 Go 1.27.2、Linux/amd64 上实际运行 WebSocket 回显与心跳、GraphQL 字段执行和缓存、Webhook 签名与去重；完整代码与固定依赖放在文末。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "5019c8ee4bfd5b7b1438202d0b859fc445ab99043e907117edb49983f8e07299",
      "source_line_count": 578,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-045",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/06_Web与API/net-http.md",
      "title": "net/http：把一个请求从接收到收尾讲清楚",
      "priority": "P2",
      "treatment": "static",
      "concept": "HTTP 请求、Body、连接与退出",
      "recommended_medium": "资源生命周期图",
      "reason": "保留具体 Go 测试，图只帮助区分 handler 结束、body 读完与连接归还。",
      "reuse_family": "http-lifecycle",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先认清谁负责哪一段"
          },
          {
            "line": 40,
            "text": "## Handler 的边界就是一次请求的边界"
          },
          {
            "line": 56,
            "text": "### 谁来关闭 Body"
          },
          {
            "line": 66,
            "text": "## ServeMux：方法和路径一起决定处理函数"
          },
          {
            "line": 98,
            "text": "## 中间件就是把 Handler 再包一层"
          },
          {
            "line": 131,
            "text": "## Client 要复用，Transport 更要复用"
          },
          {
            "line": 158,
            "text": "### `Do` 成功以后，也可能超时"
          },
          {
            "line": 170,
            "text": "## 服务端超时保护的是不同阶段"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "写一个能返回 `200` 的 Go HTTP 服务只需要几行。接下来真正会影响服务稳定性的，是另外几个问题：处理函数什么时候结束？响应写出去以后还能改吗？请求已经超时，为什么业务还在跑？每次调用都建一个 Client，会不会不断新建连接？进程准备退出时，正在上传的文件怎么办？",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这篇沿着一个请求的生命周期回答这些问题。示例使用 Go 1.27.2、Linux/amd64，全部可运行代码放在文末。路由实验、HTTP/1.1 连接复用、响应体读取超时和优雅关闭都在本次环境实际执行；它们不代表真实代理、HTTP/2 和高并发部署的全部行为。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "eb06cba6e2531faa3ab5cea2c4dde67133db92a228abf63659efada46a4a650e",
      "source_line_count": 476,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-046",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/07_数据存储/Go 数据库工具选型.md",
      "title": "Go 数据库工具选型",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "数据库驱动、查询层与迁移选型",
      "recommended_medium": "工具分层表和 SQL 例子",
      "reason": "选型不具有可靠的单一评分，正文更能保留环境和维护代价。",
      "reuse_family": "tool-selection",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 9,
            "text": "## 1. 先选层，再选工具"
          },
          {
            "line": 43,
            "text": "## 2. 驱动：先确定连接池由谁管理"
          },
          {
            "line": 45,
            "text": "### pgx：PostgreSQL 原生能力与标准接口之间的选择"
          },
          {
            "line": 59,
            "text": "### go-sql-driver/mysql：在标准池之下实现 MySQL 协议"
          },
          {
            "line": 75,
            "text": "## 3. sqlx：保留 SQL，把重复扫描和绑定交给库"
          },
          {
            "line": 81,
            "text": "### 一个 slice 不会自动变成一串占位符"
          },
          {
            "line": 106,
            "text": "## 4. sqlc：把手写 SQL 变成有类型的 Go 调用"
          },
          {
            "line": 137,
            "text": "## 5. GORM：用模型组织 CRUD，也要看它实际写了什么"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "`pgx`、`sqlc` 和 GORM 经常出现在同一份选型表里，却不负责同一件事：一个主要负责与 PostgreSQL 通信，一个把 SQL 生成 Go 代码，一个提供对象映射和查询接口。把它们当作只能三选一，容易从一开始就选错问题。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文先分清驱动、查询层和迁移工具，再逐一说明 `pgx`、`go-sql-driver/mysql`、`sqlx`、`sqlc`、GORM、ent，以及 `golang-migrate`、goose、Atlas。最后用两个小实验观察参数绑定和零值更新，让“封装方便在哪里、隐藏了什么”变成可以检查的行为。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "be26f35017e1db1815a72bdf6bc889b1e500aef7e56927f9e2eb661f386a1e0c",
      "source_line_count": 493,
      "existing_static_mermaid_count": 3,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-047",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/07_数据存储/Go 数据访问.md",
      "title": "Go 数据访问：管好连接、事务和 SQL 边界",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "连接池借用、等待与释放",
      "recommended_medium": "有限连接池队列模型",
      "reason": "调小池容量或不归还 Rows 能直接展示请求如何排队；不把模型时间当数据库性能。",
      "reuse_family": "resource-pool",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## DB 是池，Tx 才是一条事务"
          },
          {
            "line": 37,
            "text": "## 四个连接池参数，各管一件事"
          },
          {
            "line": 52,
            "text": "### 实际把池压到一条连接"
          },
          {
            "line": 70,
            "text": "## Context 管期限，Rows 管结果资源"
          },
          {
            "line": 106,
            "text": "## 事务封装要把 Commit 错误带回去"
          },
          {
            "line": 132,
            "text": "## 预处理语句和参数绑定，别混成一件事"
          },
          {
            "line": 152,
            "text": "### 占位符不能代表任意 SQL 结构"
          },
          {
            "line": 165,
            "text": "## sql.Null* 让零值与缺失值分开"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "一段查询可以很短，却同时藏着四种问题：结果没有关闭、等待连接没有超时、事务内混进池上的操作、用户输入被拼进 SQL。换 ORM 不能自动消除这些问题，底层资源和数据库语义仍然在那里。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文围绕 database/sql 讲连接池、预处理、事务封装、NULL、注入和 N+1。Go 实验实际使用 1.27.2、Linux/amd64，配一个完整可控测试驱动，不连接真实数据库。它验证标准库的资源生命周期，不能证明 PostgreSQL/MySQL 的隔离、网络取消或 SQL 执行保证。另用实际 SQLite 3.53.1 做参数绑定反例，单独标明边界。全部程序与测试在文末。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "62459bb7bfd4c6544b6f6ad4abccec99c35aa779b805aaa07326fa7b1c15210e",
      "source_line_count": 630,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-048",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/07_数据存储/MySQL.md",
      "title": "MySQL：沿着一次提交，认识 InnoDB、日志与复制",
      "priority": "P2",
      "treatment": "static",
      "concept": "redo、undo、binlog 与复制确认",
      "recommended_medium": "提交和持久化时序图",
      "reason": "必须区分引擎和配置保证；现有文档解释配静态确认点最稳妥。",
      "reuse_family": "durability",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## MySQL Server 与 InnoDB 分别做什么"
          },
          {
            "line": 35,
            "text": "## redo、undo、binlog 不可互相替代"
          },
          {
            "line": 49,
            "text": "## 为什么要协调 redo 与 binlog"
          },
          {
            "line": 70,
            "text": "## “提交成功后会不会丢”，必须把配置说出来"
          },
          {
            "line": 92,
            "text": "## 从 source 的 binlog 到 replica 的可读数据"
          },
          {
            "line": 126,
            "text": "## 慢查询优化：先找到最值得改的，再解释为什么慢"
          },
          {
            "line": 146,
            "text": "## 本篇的验证边界"
          },
          {
            "line": 152,
            "text": "## 参考资料"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "应用收到“提交成功”后，数据页可能还没全部写回磁盘，副本也可能还查不到这笔数据。这两件事并不矛盾：InnoDB 通过日志恢复尚未写回的数据，而普通异步复制允许副本晚一些应用变化。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "理解 MySQL 时，先把本机事务、日志持久化和副本进度拆开。本文固定讨论 MySQL 8.4 的 InnoDB；内部提交路径还核对了官方 `mysql-8.4.0` tag 的源码。当前环境没有 MySQL 服务，因此本文没有 MySQL 实测输出，不用 PostgreSQL 或 SQLite 的结果代替。文末给出完整的只读检查 SQL 和临时表实验，明确列出要验证的现象。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "53d3ddd4315b50adcb10ed1c57d4254caf0c884c6458268e1dc93ee9b4eafca4",
      "source_line_count": 247,
      "existing_static_mermaid_count": 3,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-049",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/07_数据存储/NoSQL 与专用存储.md",
      "title": "NoSQL 与专用存储",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "专用存储按访问方式分工",
      "recommended_medium": "问题与存储能力对照",
      "reason": "重点是数据模型与产品约束，交互不会补足真实选型所需负载证据。",
      "reuse_family": "tool-selection",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 9,
            "text": "## 1. 先按访问方式分工"
          },
          {
            "line": 39,
            "text": "## 2. MongoDB：先决定什么应该放在一份文档里"
          },
          {
            "line": 45,
            "text": "### 嵌入还是引用，取决于生命周期"
          },
          {
            "line": 56,
            "text": "### 原子性要说清范围"
          },
          {
            "line": 71,
            "text": "### 从哪里读，与确认到什么程度，是两件事"
          },
          {
            "line": 75,
            "text": "## 3. Elasticsearch：先把文本变成可检索的词项"
          },
          {
            "line": 79,
            "text": "### 分词决定你实际上在搜什么"
          },
          {
            "line": 114,
            "text": "### 写成功不等于立刻能被搜索"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "商品信息、搜索框、销售报表、监控曲线和用户上传的图片，虽然都叫“数据”，读写方式却完全不同。选择存储时，先说清楚一条请求要取什么、改什么、多久必须可见，再讨论产品。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文把五类存储放回各自的工作中：MongoDB 如何组织文档，Elasticsearch 如何找到文本，ClickHouse 为什么适合大范围聚合，时序库如何管理持续产生的样本，以及 S3 / MinIO 如何保存对象。**“NoSQL”不是这些产品共有的事务规则，也不是“不用 SQL”的同义词。** ClickHouse 本身提供 SQL，TimescaleDB 建立在 PostgreSQL 上。[^ch-intro][^timescale]",
            "truncated": false
          }
        ]
      },
      "source_sha256": "e47436ce9c038a80abf9ceaa34cade0d620229d0709bd0fcc6ca0f3724dfc29e",
      "source_line_count": 304,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-050",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/07_数据存储/PostgreSQL.md",
      "title": "PostgreSQL：把版本清理、JSONB 与索引选对",
      "priority": "P2",
      "treatment": "static",
      "concept": "行版本、清理与专用索引",
      "recommended_medium": "版本链与候选过滤示意",
      "reason": "VACUUM 和索引语义适合固定图；不模拟优化器性能或实际物理布局。",
      "reuse_family": "database-versions",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 更新一行，为什么可能留下两个版本"
          },
          {
            "line": 40,
            "text": "## VACUUM、ANALYZE、VACUUM FULL 是三件不同的事"
          },
          {
            "line": 58,
            "text": "## JSONB 保存了结构，但不会替你决定数据模型"
          },
          {
            "line": 99,
            "text": "## 先看运算，再挑索引名字"
          },
          {
            "line": 112,
            "text": "### GIN：从“某个属性值”找到包含它的文档"
          },
          {
            "line": 137,
            "text": "### GiST：把“范围相交”交给理解范围的索引"
          },
          {
            "line": 151,
            "text": "### BRIN：先排除整片物理区域，再检查区域内的行"
          },
          {
            "line": 166,
            "text": "## 扩展生态：能力很强，也意味着多一份依赖"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "PostgreSQL 的灵活性很容易让人一次加入许多东西：一列 JSONB、一棵 GIN 索引、几个扩展，再把维护交给默认设置。真正需要理解的是，每个选择改变了什么：旧行版本由谁清理，查询用了哪类运算，索引能排除哪些候选，扩展增加了哪些部署责任。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文以 PostgreSQL 18 为文档基线，实际运行版本为 18.6、Linux/amd64。实验使用官方单用户离线模式，验证 SQL、行位置变化、JSONB 和 GIN/GiST/BRIN 执行计划；没有真实多会话，也不以它证明 autovacuum 调度、并发锁或生产性能。全部复现输入放在文末。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "db175cc554ef68e2f03463d7595791d371a1d7892c2d4fa274a05076cf9f26c9",
      "source_line_count": 334,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-051",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/07_数据存储/SQL 基础.md",
      "title": "SQL 基础：先确定一行代表什么，再写查询",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "JOIN、NULL、聚合与行数",
      "recommended_medium": "微型表格查询推演",
      "reason": "逐步连接两张小表，显示重复行如何产生以及聚合怎样放大金额，可暴露无语法错误的逻辑错。",
      "reuse_family": "relational-rows",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 一张表先回答两个问题"
          },
          {
            "line": 34,
            "text": "## SQL 默认保留重复，NULL 又引入第三种判断结果"
          },
          {
            "line": 52,
            "text": "## 连接先决定行数，再谈取哪些列"
          },
          {
            "line": 105,
            "text": "## 子查询：把“存在、集合、单值、临时结果”分开想"
          },
          {
            "line": 128,
            "text": "## 聚合把多行收成一行，窗口保留每一行"
          },
          {
            "line": 188,
            "text": "## 范式先解决重复事实，反范式要说明谁维护副本"
          },
          {
            "line": 215,
            "text": "## 用什么办法确认自己写对了"
          },
          {
            "line": 221,
            "text": "## 参考资料"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "订单报表最难排查的错误，往往没有语法报错：销售额多算了一倍，没有下单的客户消失了，排行榜每次的第一名不一样。SQL 能执行，只说明数据库懂了语句，不能说明语句表达了正确的问题。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文用“客户与订单”这组数据讲清建表、写入、连接、子查询、聚合和窗口函数，再回到表应该怎么拆。读完应能解释查询为什么产生这些行，知道哪些规则交给约束，哪些计算放在查询里。示例以 PostgreSQL 18 为方言；金额用整数分表示，避免把二进制浮点误差混进本篇的问题。完整 SQL 在文末。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "12e2f4140b7205b7beb651215119f76d82c04071a3446e2aa33e829c5f23ffae",
      "source_line_count": 383,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-052",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/07_数据存储/数据库事务.md",
      "title": "数据库事务：让一组修改成立，也让并发判断成立",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "事务交错、快照与业务约束",
      "recommended_medium": "两事务手动交错时间线",
      "reason": "让读者选择读/写/提交顺序并观察异常，能解释事务存在不等于业务正确。",
      "reuse_family": "transaction-interleavings",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## ACID 分别承诺什么"
          },
          {
            "line": 20,
            "text": "### 先实际观察整体撤销"
          },
          {
            "line": 43,
            "text": "## 三种常见异常，要看谁在什么时刻提交"
          },
          {
            "line": 54,
            "text": "## 四个隔离级别，先看最低语义，再看具体引擎"
          },
          {
            "line": 78,
            "text": "### 没有幻读，为什么还可能把业务做错"
          },
          {
            "line": 98,
            "text": "## MVCC 保存的是版本，快照决定哪个版本能被看见"
          },
          {
            "line": 115,
            "text": "## 行锁、间隙锁、临键锁、意向锁各保护什么"
          },
          {
            "line": 134,
            "text": "## 先把条件与修改合成一次动作"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "给账户 A 扣 30，再给账户 B 加 30，这两步必须一起成功。把它们放进事务，可以防止“扣了没加”。但如果两个请求都先读到 A 还有 100，再各自按这个旧值写余额，仅仅加上 `BEGIN` 和 `COMMIT`，仍不保证业务正确。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "事务要解决两层问题：一组修改能不能一起提交；并发事务的读和写，能不能保持业务约束。本文从这两个问题讲 ACID、隔离级别、MVCC 和锁。引擎边界固定为 PostgreSQL 18、MySQL 8.4 InnoDB。实际实验只在 PostgreSQL 18.6 的 single-user 离线模式运行，验证回滚、提交、保存点和条件更新；并发时序与锁行为按官方文档解释，未冒充实测。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "cf4088880b1b1eeb59d5edc7c81209ab640cf8de80c76480d9333cad284b5722",
      "source_line_count": 334,
      "existing_static_mermaid_count": 3,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-053",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/07_数据存储/数据库扩展性.md",
      "title": "数据库扩展性：先找瓶颈，再增加分布式边界",
      "priority": "P2",
      "treatment": "static",
      "concept": "数据库扩展与迁移边界",
      "recommended_medium": "读写路径和迁移检查点图",
      "reason": "扩展性依赖实际瓶颈；路由计算可转到数据分布专章，避免重复控件。",
      "reuse_family": "database-scaling",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先确认哪一种扩展解决眼前问题"
          },
          {
            "line": 20,
            "text": "## 读写分离最先改变的是读到什么"
          },
          {
            "line": 49,
            "text": "## 分表、分区、分库分别搬走了什么"
          },
          {
            "line": 64,
            "text": "### 分片数从四变五，取模会搬走多少数据"
          },
          {
            "line": 86,
            "text": "## 全局 ID 要兼顾唯一性、排序和生成责任"
          },
          {
            "line": 101,
            "text": "## 跨片查询会把一次本地工作变成分发与合并"
          },
          {
            "line": 115,
            "text": "## 数据迁移要先解决“谁的新旧顺序说了算”"
          },
          {
            "line": 145,
            "text": "### 一次可核查的切流，应有明确检查点"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "数据库慢了，增加副本未必解决写入瓶颈；数据量大了，拆成许多表也未必减少磁盘和锁争用。扩展方案要回答一个具体问题：哪部分负载可以搬走，搬走之后，原来一次事务、一次查询或一次发布完成的事情，会被拆成几步？",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文围绕读写分离、分库分表、全局 ID、迁移和在线 DDL、冷热分离建立选择顺序。数据库机制按 PostgreSQL 18、MySQL 8.4 文档说明；Vitess 22.0 的固定版本工作流用于解释分片迁移，不作为当前版本推荐。实际实验仅包括 Go 1.27.2 的路由计算，以及 PostgreSQL 18.6 单用户模式中的顺序回放模拟，没有运行复制集群或生产迁移。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "6ff7939b181a17319a7436324b7d23077b36471c3c84c23fe77e273e0eb4a09b",
      "source_line_count": 350,
      "existing_static_mermaid_count": 3,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-054",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/07_数据存储/数据库索引.md",
      "title": "数据库索引：从少读哪些数据，理解一条查询为什么快",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "联合索引排序与候选扫描",
      "recommended_medium": "小型有序索引范围查看器",
      "reason": "改变条件后高亮必须扫描的键区间，解释走索引不等于少读数据；不预测真实执行计划。",
      "reuse_family": "index-scan",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 一棵树为什么能少读很多页"
          },
          {
            "line": 32,
            "text": "## 同样叫“主键”，InnoDB 和 PostgreSQL 存的东西不同"
          },
          {
            "line": 55,
            "text": "## 联合索引是按多个键依次排序"
          },
          {
            "line": 71,
            "text": "## 一次真实实验：先看计划形状，暂时不比较毫秒"
          },
          {
            "line": 110,
            "text": "### 一个特意保留的反例：表达式查询仍然可能“走索引”"
          },
          {
            "line": 129,
            "text": "### 没有第一列条件，也不代表用不了联合索引"
          },
          {
            "line": 148,
            "text": "## 覆盖索引和索引下推分别省哪一步"
          },
          {
            "line": 164,
            "text": "## 少背“失效清单”，多问四个具体问题"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "“建了索引却没走”和“走了索引还是慢”，是两种问题。前者可能是优化器认为顺序读更便宜，后者可能只是把全表扫描换成了全索引扫描。判断索引是否有用，要看它减少了多少候选记录、多少表页访问，以及排序等后续工作。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文先解释 B+ 树和两种常见存储布局，再用 PostgreSQL 18.6 的实际执行计划观察联合索引、覆盖、表达式和跳跃扫描。MySQL 部分固定讨论 8.4 的 InnoDB，索引下推等行为依据官方文档，未在本次环境运行。完整建表、造数、查询和运行脚本都在文末。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "cbd9347fcbf2fad3ef1e92e56e626fec32f570dc34662de89a7016812bc6de9f",
      "source_line_count": 314,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-055",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/08_缓存/Go 缓存工具选型.md",
      "title": "Go 缓存工具选型",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "远程缓存客户端与本地缓存",
      "recommended_medium": "工具语义对照与实测原例",
      "reason": "先按层和一致性选型，避免用动画给出未经测量的快慢排名。",
      "reuse_family": "tool-selection",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 19,
            "text": "## 先决定要省哪一次工作"
          },
          {
            "line": 43,
            "text": "## go-redis：常用命令直接，部署入口明确"
          },
          {
            "line": 61,
            "text": "### 连接池与超时要按总预算配置"
          },
          {
            "line": 71,
            "text": "### 批量与客户端缓存也要按当前版本看"
          },
          {
            "line": 79,
            "text": "## rueidis：默认合并并发命令，显式区分可缓存读取"
          },
          {
            "line": 89,
            "text": "### 自动 Pipeline 省往返，但也可能引入排队"
          },
          {
            "line": 95,
            "text": "### DoCache：缓存响应，并接受服务端失效"
          },
          {
            "line": 117,
            "text": "## 本地缓存先比较语义，再比较吞吐"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "`go-redis`、`rueidis`、`ristretto`、`bigcache`、`otter` 经常同时出现在缓存推荐清单里，但前两个主要负责与 Redis 通信，后三个把数据留在当前 Go 进程。把五个名字排成一个速度榜，先把问题分错了层。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这篇文章先区分远程客户端与本地缓存，再逐个解释它们的入口、数据语义、内存管理和适用条件。最值得检查的几个行为都实际跑过：Redis 未命中怎么表示，客户端缓存是否命中，Ristretto 的 Set 成功是否等于保留，BigCache 超过 LifeWindow 是否还返回值，以及 Otter 的 loader 是否复用已有结果。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "1697491f21d67d8fe2ac1d77c7bde9ca5bce1e9757a82f009e2a05f1790284b2",
      "source_line_count": 513,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-056",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/08_缓存/Redis 原理.md",
      "title": "Redis 原理",
      "priority": "P2",
      "treatment": "static",
      "concept": "Redis 执行、持久化与复制",
      "recommended_medium": "请求确认点时序图",
      "reason": "重点是明确何时进入内存、落盘或副本，静态路径更能保留配置限制。",
      "reuse_family": "durability",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 一条命令经过哪些地方"
          },
          {
            "line": 25,
            "text": "### “单线程”指向哪一层"
          },
          {
            "line": 35,
            "text": "## 持久化：写进内存、写进内核、落到稳定存储并非一步"
          },
          {
            "line": 46,
            "text": "### RDB：把某个时刻留下来"
          },
          {
            "line": 52,
            "text": "### AOF：刷盘策略决定何时可以放心"
          },
          {
            "line": 68,
            "text": "## 过期与淘汰：一个按时间，一个按内存压力"
          },
          {
            "line": 104,
            "text": "## 多节点：副本、哨兵与 Cluster 各解决哪一层"
          },
          {
            "line": 112,
            "text": "### 复制不是每次写都等待所有副本"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "Redis 已经返回 `OK`，机器突然掉电，数据还在不在？把十条命令放进 Pipeline，其他客户端能不能插进来？主节点故障后，副本接任会不会丢掉刚才成功的写入？这三个问题分别属于持久化、执行隔离和复制，不能用一句“Redis 是单线程的”一起回答。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文沿着一条写请求往下看：谁读取和执行命令，数据如何离开内存，内存不足如何处理，多节点如何接手，以及应用怎样组合多条命令。实现边界固定为 Redis 8.2.2；单机命令行为在 Linux/amd64 上实测。持久化掉电、哨兵选举和 Cluster 分区没有在本次实验中演练，下面会把官方保证与本机观察分开。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "b23041a839d51b24ecd6cd95df4d06e0e783276142dbc4d1fe614ffd0d9fb18a",
      "source_line_count": 369,
      "existing_static_mermaid_count": 3,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-057",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/08_缓存/Redis 数据结构.md",
      "title": "Redis 数据结构",
      "priority": "P2",
      "treatment": "static",
      "concept": "Redis 类型接口与内部编码",
      "recommended_medium": "操作地图和编码示意",
      "reason": "九种结构是查阅内容，优先保留操作需求和版本边界，不做统一玩具 Redis。",
      "reuse_family": "data-structures",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 9,
            "text": "## 先选操作，再选类型"
          },
          {
            "line": 25,
            "text": "## 类型是接口，编码是实现"
          },
          {
            "line": 58,
            "text": "## String：看起来只有一个值，内部仍有三种存法"
          },
          {
            "line": 90,
            "text": "## Hash：适合局部字段更新，别把“一个 key”当成小对象"
          },
          {
            "line": 114,
            "text": "## List：两端队列很顺手，任务可靠性还要另外设计"
          },
          {
            "line": 129,
            "text": "## Set 与 ZSet：是否需要顺序，是关键区别"
          },
          {
            "line": 158,
            "text": "## Bitmap 与 HyperLogLog：都是省空间，但省掉的东西不同"
          },
          {
            "line": 160,
            "text": "### Bitmap 保存每个位置的一位"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "把一个商品放进 Redis，最省事的做法是序列化成 JSON，再用 `SET` 保存。问题通常出现在下一步：只想改库存，却要读改写整个 JSON；要按销量排序，必须把所有商品读出来；要统计独立访客，保存每个访客又太贵。这时需要按操作选择数据结构，而不只是按数据长什么样选择。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文讲知识地图列出的九种结构：String、Hash、List、Set、ZSet、Bitmap、HyperLogLog、Stream、Geo。目标是知道常用命令、底层如何存储，以及为什么同一份数据换一种组织方式，内存和延迟会变化。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "87c257f773918e79842ffa3cf884050733cecf5e6733a607dd4fbf65a3668a03",
      "source_line_count": 413,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-058",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/08_缓存/Redis 缓存进阶.md",
      "title": "Redis 缓存进阶",
      "priority": "P2",
      "treatment": "static",
      "concept": "热 key、大 key 与多级缓存",
      "recommended_medium": "访问量/大小双轴图",
      "reason": "二维关系可直接说明不同治理方向；具体吞吐必须继续依赖测量。",
      "reuse_family": "cache-load",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 9,
            "text": "## 热与大是两条轴，治理前先量清楚"
          },
          {
            "line": 23,
            "text": "## 怎么发现，而不在排查时再制造一次高峰"
          },
          {
            "line": 25,
            "text": "### 先用应用侧知道“谁在访问什么”"
          },
          {
            "line": 31,
            "text": "### 再用服务端工具核对尺寸与访问模式"
          },
          {
            "line": 46,
            "text": "## 治理热 key：先分清读热还是写热"
          },
          {
            "line": 48,
            "text": "### 读热：减少到同一个分片的往返"
          },
          {
            "line": 56,
            "text": "### 写热：能不能把一个动作拆成多份"
          },
          {
            "line": 62,
            "text": "## 治理大 key：同时减小存储单元和操作单元"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "一个 key 每秒被读十万次，与一个 key 装着几百万个元素，会给 Redis 带来不同的问题。前者可能压满一个分片的 CPU 或网络，后者可能让一次命令、传输、删除或复制变得很重。即使把热点搬进本地缓存，接下来还要回答如何失效；即使给重建加了锁，也要回答旧持有者暂停后回来会不会继续写。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文从热 key、大 key 的定位和治理讲起，再把本地加分布式的多级缓存、`SET NX PX` 锁、续期与 Redlock 争议连起来。重点是每次优化到底减少了什么，以及它把责任移到了哪里。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "4c1eb0444b9bbcc3bff7ef1e92df572d3a005433678058bf7599aa96b0cbc24e",
      "source_line_count": 348,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-059",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/08_缓存/缓存三大问题.md",
      "title": "缓存三大问题",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "缓存失效时的回源工作量",
      "recommended_medium": "可切换失效场景的队列模型",
      "reason": "对比集中失效、空值缓存和合并重建后数据库收到的工作量，能解释保护对象。",
      "reuse_family": "cache-load",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 9,
            "text": "## 先看负载，不急着给事故贴名字"
          },
          {
            "line": 39,
            "text": "## 穿透：查不到的结果也需要管理"
          },
          {
            "line": 43,
            "text": "### 空值缓存：把“确实没有”存成一种结果"
          },
          {
            "line": 61,
            "text": "### 布隆过滤器：便宜地回答“一定没有”"
          },
          {
            "line": 88,
            "text": "### 内存与误报率如何交换"
          },
          {
            "line": 100,
            "text": "## 击穿：让同一份重建工作只做有限次"
          },
          {
            "line": 104,
            "text": "### 互斥锁：拿到锁后还要再查一次"
          },
          {
            "line": 114,
            "text": "### singleflight：共享正在进行的一次函数调用"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "缓存正常时，数据库只接住少量未命中的请求；缓存突然不好用了，数据库却可能被全部流量同时击中。“穿透、击穿、雪崩”是三种常见表现，真正要保护的是回源能力。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文用请求是否存在、集中在哪些键、为什么同时失效来区分三者，再讲布隆过滤器、空值缓存、互斥锁、singleflight、逻辑过期、TTL 打散、多级缓存和降级怎样配合。每个办法都会留下边界：减少回源次数，不等于让数据自动一致。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "fb64e9cb0728cc637426fb2b6daf1de0c0bb01f66a4f4d82b1fc0dbcb50765e9",
      "source_line_count": 395,
      "existing_static_mermaid_count": 3,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-060",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/08_缓存/缓存模式.md",
      "title": "缓存模式",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "缓存双写与失效竞态",
      "recommended_medium": "数据库/缓存双轨事件线",
      "reason": "手动交错读、写、失效和失败，能证明延迟双删不是一致性保证。",
      "reuse_family": "cache-consistency",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 9,
            "text": "## 先说清“旧到什么程度还能用”"
          },
          {
            "line": 22,
            "text": "## 四种模式，差别在责任与确认点"
          },
          {
            "line": 35,
            "text": "### Cache-Aside：最容易接入，也最容易把责任散落到各处"
          },
          {
            "line": 53,
            "text": "### Read-Through：集中读入口，不自动增加一致性"
          },
          {
            "line": 59,
            "text": "### Write-Through：统一入口很有用，两份存储仍需协调"
          },
          {
            "line": 65,
            "text": "### Write-Behind：确认越早，持久性责任越重"
          },
          {
            "line": 77,
            "text": "## 为什么通常先更新库，再删除缓存"
          },
          {
            "line": 116,
            "text": "## 延迟双删：再清一次可能有用，但延迟不是锁"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "同一份商品资料同时存在数据库和缓存里，就有了两个会在不同时刻更新的副本。选缓存模式，实际上是在决定三件事：谁负责加载，写入成功究竟指写到了哪里，以及两个副本暂时不同步时怎么办。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文把 Cache-Aside、Read-Through、Write-Through、Write-Behind 放在同一张读写图上，再拆解“先更新库再删缓存”“延迟双删”“订阅 binlog”各自能解决什么。读完应能为一条业务链路说明可接受的陈旧程度和失败处理，而不只是背出一个更新顺序。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "44b94c3945e77ae1326df167a8e0dfee472191607aad744be272d46fc1fe07f7",
      "source_line_count": 363,
      "existing_static_mermaid_count": 3,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-061",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/09_消息队列/Go 消息与工作流工具选型.md",
      "title": "Go 消息与工作流工具选型：先选需要哪一层，再检查确认和重试",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "消息客户端、任务与工作流分层",
      "recommended_medium": "职责、确认和重试表",
      "reason": "工具版本与协议差异比动画重要；复用后续可靠性章节解释失败窗口。",
      "reuse_family": "tool-selection",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 9,
            "text": "## 三层工具不要放在一张“谁更快”的榜单上"
          },
          {
            "line": 36,
            "text": "## franz-go：控制得细，也要理解消费生命周期"
          },
          {
            "line": 62,
            "text": "## kafka-go：先分清 ReadMessage 与 FetchMessage"
          },
          {
            "line": 83,
            "text": "### Writer 的零值确认级别值得专门检查"
          },
          {
            "line": 99,
            "text": "## amqp091-go：生产确认与消费 Ack 是两个动作"
          },
          {
            "line": 122,
            "text": "### 旧教程的两条说法需要按版本更新"
          },
          {
            "line": 130,
            "text": "## nats.go：Core Publish 与 JetStream Publish 要分开"
          },
          {
            "line": 158,
            "text": "## Asynq：把 Redis 上的后台任务生命周期交给库"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "要把“生成账单”放到后台，至少有三种做法：直接消费消息并执行，用任务队列管理重试和执行状态，或者把它放进一个能等待、补偿、跨重启恢复的工作流。三者都可能运行 Go 函数，但替应用承担的责任不同。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文覆盖七个工具：Kafka 客户端 `franz-go`、`segmentio/kafka-go`，RabbitMQ 客户端 `amqp091-go`，NATS 客户端 `nats.go`，任务队列 `asynq`、`river`，以及工作流 `Temporal`。重点是常用入口、确认时点、失败以后谁负责恢复，以及哪类需求值得承担它的复杂度。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "0b16a9e5d1075ce39014eb5d24005f25b01c0a5285e15535d9dde6c8f428090d",
      "source_line_count": 872,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-062",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/09_消息队列/Kafka.md",
      "title": "Kafka：从分区日志理解复制、吞吐、Rebalance 与事务",
      "priority": "P2",
      "treatment": "static",
      "concept": "Kafka 分区、ISR 与消费位置",
      "recommended_medium": "日志和消费组分配图",
      "reason": "静态图能标出有序范围及提交点；完整 rebalance 模拟代价高且容易省错条件。",
      "reuse_family": "partition-log",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 一次读写会碰到哪些角色"
          },
          {
            "line": 34,
            "text": "## 分区提供并行，也规定有序的范围"
          },
          {
            "line": 42,
            "text": "## 副本数、ISR、确认条件分别回答三个问题"
          },
          {
            "line": 76,
            "text": "## Leader 选举：4.x 不能只背“从 ISR 里选”"
          },
          {
            "line": 97,
            "text": "## 高吞吐来自少做重复工作"
          },
          {
            "line": 121,
            "text": "### 零拷贝不等于所有数据从不复制"
          },
          {
            "line": 125,
            "text": "### 把批量压缩的收益单独拿出来试"
          },
          {
            "line": 144,
            "text": "## Rebalance：分区易主时，业务任务不一定已经停下"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "Kafka 的设计适合一类需求：很多事件持续写进来，多套应用分别读取，还要能保留历史、重新处理。理解它时，先把视角放在“每个分区是一条可复制、可重放的日志”上。分区解释并发边界，复制解释故障恢复，消费位置解释重放，事务再把部分读写操作连成一个提交结果。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文采用 Kafka 4.2 文档和 4.2.0 固定源码作为讲解基线，于 2026-10-13 核对；不声称这是最新主线。尤其注意 4.x 的 KRaft、Consumer Rebalance Protocol、Eligible Leader Replicas 与旧教程的区别。Go 实验运行于 Go 1.27.2、Linux/amd64，仅包含复制条件与事务可见性的简化模型、实际 gzip 压缩实验，没有启动 Kafka，也没有测集群吞吐。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "18cd2e2478d5d76d88920de3ed337d4ee6cc10418846d1e2ed51ec48ae35e84e",
      "source_line_count": 368,
      "existing_static_mermaid_count": 3,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-063",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/09_消息队列/消息队列其他选型.md",
      "title": "消息队列其他选型：从路由、重放和业务交接来选择",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "消息队列选型依据",
      "recommended_medium": "路由、重放与运维对照表",
      "reason": "应保留候选系统保证差异，不以一个简化 UI 给出产品排名。",
      "reuse_family": "tool-selection",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先用同一套问题比较"
          },
          {
            "line": 20,
            "text": "## RabbitMQ：先画出交换机和队列的关系"
          },
          {
            "line": 51,
            "text": "## RocketMQ：事务消息解决生产交接，消费正确性仍要自己做"
          },
          {
            "line": 83,
            "text": "## NATS 与 JetStream：同一生态里的两种承诺"
          },
          {
            "line": 107,
            "text": "### JetStream 多出的不是一个开关，而是一组持久状态"
          },
          {
            "line": 123,
            "text": "## Pulsar：订阅方式丰富，存储层也需要专门运维"
          },
          {
            "line": 142,
            "text": "## Redis Streams：不要把 PEL 当成自动重试引擎"
          },
          {
            "line": 183,
            "text": "## 怎样把候选缩到能做验证的两种"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "“哪个消息队列最好”很难给出有用答案。更具体的问法是：消息要按规则分发给多个服务，还是要长期保留供重放？消费者离线后必须补收吗？最棘手的问题是路由、订单提交与发布之间的裂缝，还是大量租户的存储和隔离？",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文逐一解释 RabbitMQ、RocketMQ、Core NATS、JetStream、Pulsar 和 Redis Streams 的使用入口及边界。版本基线是 RabbitMQ 4.2、RocketMQ 5.x、Pulsar 4.0.x；NATS 与 Redis 使用于 2026-10-13 核对的官方滚动文档，不把页面中的新特性默认归给旧服务器。没有运行这些服务器，文中的 Go 小程序只验证自建路由与待确认记录模型。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "e127162f0927df240b9eb60ec4c2f96f4099cf8fb4fdd4dd609887d1806dac17",
      "source_line_count": 348,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-064",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/09_消息队列/消息队列可靠性.md",
      "title": "消息队列可靠性：把每一个故障窗口处理清楚",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "处理完成与确认之间的故障窗口",
      "recommended_medium": "消息/业务效果/ACK 单步模型",
      "reason": "在业务提交和 ACK 间插入崩溃，直观看到重投递为何必须靠幂等兜底。",
      "reuse_family": "delivery-semantics",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 从“消息不丢”改成一个能验证的承诺"
          },
          {
            "line": 22,
            "text": "### 生产端确认、持久化和副本要一起看"
          },
          {
            "line": 30,
            "text": "## 先做业务，再确认，仍然会重复"
          },
          {
            "line": 60,
            "text": "## 幂等要挡住重复的业务效果"
          },
          {
            "line": 107,
            "text": "### 外部副作用不能塞进本地事务就算完成"
          },
          {
            "line": 111,
            "text": "## 顺序需要同时约束生产、存储和执行"
          },
          {
            "line": 126,
            "text": "## 积压：先算净处理能力，再决定是否扩容"
          },
          {
            "line": 147,
            "text": "## 重试与死信：让坏消息离开正常通道"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "“消息已经发送成功，为什么订单还没有处理？”这个问题可能发生在生产者、Broker、消费者，也可能发生在消费者调用的数据库里。可靠性需要沿整条链检查，不能靠一个 `ack=true` 或“开启持久化”概括。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文要解决六件事：消息怎样尽量不丢，重复怎样不变成重复业务，顺序在哪里成立，积压怎样恢复，坏消息怎样隔离，延迟任务怎样安全执行。以 Kafka 4.2、RabbitMQ 4.2 和 RocketMQ 5.x 文档解释具体边界。文中 Go 实验在 Go 1.27.2、Linux/amd64 运行，都是显式注入故障的内存模型；没有做真实 Broker 宕机或数据库提交实验。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "68d66f09848feb104d1ca1b57ff8603928032ec7cfe272493a2aea0d78d1b8cd",
      "source_line_count": 415,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-065",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/09_消息队列/消息队列应用模式.md",
      "title": "消息队列应用模式：从一次订单提交走到可靠的异步处理",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "Outbox、本地提交与重复投递",
      "recommended_medium": "订单与待发事件事务演示",
      "reason": "展示同事务消除第一道裂缝、relay 仍可能重复，能阻止把 Outbox 当全链 exactly-once。",
      "reuse_family": "outbox",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 9,
            "text": "## 事件、命令和任务，先分清谁在表达什么"
          },
          {
            "line": 23,
            "text": "## 双写的裂缝：调整先后顺序不能消除故障"
          },
          {
            "line": 37,
            "text": "## Outbox：把“稍后要发什么”写进同一个本地事务"
          },
          {
            "line": 63,
            "text": "### 事务只封住了第一道裂缝"
          },
          {
            "line": 100,
            "text": "### 轮询 relay 怎样领取工作"
          },
          {
            "line": 112,
            "text": "## CDC：从提交日志捕获变化，不再反复扫业务表"
          },
          {
            "line": 131,
            "text": "## 事件契约：今天能读，还要考虑明年重放"
          },
          {
            "line": 159,
            "text": "## 任务队列：把“谁领走、何时重试”变成显式状态"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "创建订单和通知仓库，看起来只是先写数据库、再发一条消息。但进程可以恰好在两步之间退出。仓库处理到一半也可能退出，或者消息已发送、确认却没回来。事件驱动架构是否可靠，取决于这些中间状态有没有留下可恢复的证据。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文沿着这条链解释四种常见做法：事件驱动、本地消息表/Outbox、CDC 和任务队列。它们不在同一层：事件驱动描述服务如何协作，Outbox 保护本地业务提交与待发布事件之间的关系，CDC 负责捕获已提交变化，任务队列管理要执行的工作。读完应能判断该把哪一段交给哪种机制，以及还剩哪些重复和失败窗口。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "4dc0375750d0c62d1459e4ba4305ba24bee829041bd6c0b7af028687d18097d0",
      "source_line_count": 418,
      "existing_static_mermaid_count": 3,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-066",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/09_消息队列/消息队列核心概念.md",
      "title": "消息队列核心概念：一条消息由谁保管，处理进度记在哪里",
      "priority": "P2",
      "treatment": "static",
      "concept": "消息保管与消费进度",
      "recommended_medium": "分区日志和偏移量图",
      "reason": "基础名词的关系适合静态总图，避免和可靠性示例重复。",
      "reuse_family": "partition-log",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先看消息走过的四个地方"
          },
          {
            "line": 36,
            "text": "## Topic 是分类，分区才是日志的排队单位"
          },
          {
            "line": 56,
            "text": "### RabbitMQ 的 queue 不能直接套进这张图"
          },
          {
            "line": 62,
            "text": "## 消费组：分担一份工作，还是各自看一份日志"
          },
          {
            "line": 77,
            "text": "## 偏移量：读到了哪里，与完成到了哪里，是两回事"
          },
          {
            "line": 124,
            "text": "## 推模式和拉模式：谁发起投递，谁限制在途数量"
          },
          {
            "line": 160,
            "text": "## 依据与验证边界"
          },
          {
            "line": 176,
            "text": "## 完整可复现代码"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "订单创建之后，要发通知、更新搜索索引、生成统计报表。把这些工作放进消息系统，可以让下单请求先结束，后面的服务按自己的速度处理。但这也多出几个问题：通知服务停了，消息放在哪里？两个通知实例会不会各发一次？重启以后从哪里接着读？",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文围绕这条订单消息解释生产者、消费者、Broker、Topic、分区、消费组、偏移量和推拉模式。先分清各自的职责，后面才有办法讨论“不丢”“不重”和“有序”。产品依据采用 Kafka 4.2 与 RabbitMQ 4.2 的版本化文档；它们是本文的比较基线，不代表所有消息系统使用同一套机制。示例在 Go 1.27.2、Linux/amd64 运行，是没有网络、磁盘和故障恢复的确定性教学模型，不是 Broker 实测。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "9b72c022d5749414d985f9f5c220b4455e3d1cc87908ad98bc041f48ddfbb8b2",
      "source_line_count": 323,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-067",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/10_微服务与分布式/Go 微服务治理工具选型.md",
      "title": "Go 微服务治理工具选型：先分层，再比较同一层的代价",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "协调、框架、代理与服务网格",
      "recommended_medium": "工具分层和边界表",
      "reason": "治理工具不在同一层竞争；上下文判断比互动评分更重要。",
      "reuse_family": "tool-selection",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先确认缺的是哪一层"
          },
          {
            "line": 31,
            "text": "## etcd：可靠的协调原语，服务治理需要自己接起来"
          },
          {
            "line": 41,
            "text": "## Consul：服务目录、健康信息和集群管理一起提供"
          },
          {
            "line": 49,
            "text": "## Nacos：把发现与配置的应用工作流放在一起"
          },
          {
            "line": 59,
            "text": "### 三者先按工作量比较"
          },
          {
            "line": 69,
            "text": "## go-zero：用代码生成和内建治理约定减少重复工作"
          },
          {
            "line": 77,
            "text": "## Kratos：围绕接口和中间件拼装服务"
          },
          {
            "line": 85,
            "text": "## Kitex：先核对 RPC 协议与互通，再看性能特性"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "etcd、Kratos、Envoy 和 Istio 经常出现在同一张“微服务技术栈”图上，但它们不解决同一件事。etcd 存协调数据；Kratos 帮 Go 程序组织接口和中间件；Envoy 转发流量；Istio 管理服务间的数据面与策略。把它们放进一张性能排名表，通常得不到有用的结论。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文逐一解释 etcd、Consul、Nacos，go-zero、Kratos、Kitex、go-micro，APISIX、Kong、Envoy，以及 Istio 的机制、使用条件和运维成本。重点是怎样缩小候选，而不是给一套必须全装的清单。产品说明来自官方资料；本次只运行文末标准库调用契约探针，没有安装这些产品或进行横向性能测试。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "a2b3efdf307980d01c401a9899c7fa87f90c4ae46f7006bb66453daec524c809",
      "source_line_count": 316,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-068",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/10_微服务与分布式/共识算法.md",
      "title": "共识算法：主节点换了，已经答应的事不能反悔",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "Raft 任期、日志与提交安全性",
      "recommended_medium": "受限事件脚本回放",
      "reason": "只演示已审查的选主与旧任期提交反例，不能把随机动画当完整协议验证器。",
      "reuse_family": "consensus",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先认清三层：决定、日志、业务状态"
          },
          {
            "line": 30,
            "text": "## 多数派交集有用，但它只是第一块积木"
          },
          {
            "line": 49,
            "text": "## Raft 选主：选票带着任期，也带着历史约束"
          },
          {
            "line": 95,
            "text": "## Raft 复制：先确认共同前缀，再补尾部"
          },
          {
            "line": 117,
            "text": "### 为什么旧任期日志在多数节点上，也不能直接按票数提交？"
          },
          {
            "line": 135,
            "text": "## 安全性不是一条多数公式"
          },
          {
            "line": 153,
            "text": "## Paxos：新提案必须继承可能已经选定的值"
          },
          {
            "line": 169,
            "text": "## ZAB：新主先接上旧历史，再广播新的状态变化"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "把同一份数据复制到三台机器，能防止一台机器宕机后数据完全不可用。但如果主节点在“已经写给部分副本、还没向所有人确认”时退出，新主该保留哪些记录？客户端收到成功的写入，会不会在换主后消失？这才是共识算法要解决的核心问题。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文用 Raft 讲清选主、日志复制与安全性，再用 Paxos 和 ZAB 对照它们各自组织问题的方式。要记住的是协议如何把旧决定带进新一轮，而不只是“超过半数就成功”。示例只验证几个有界条件，不是 Raft、Paxos 或 ZAB 的实现。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "0471d9ec66f84521196ef032c27691a5ce86db8bfcb6da32ebb18866f26d788c",
      "source_line_count": 335,
      "existing_static_mermaid_count": 4,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-069",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/10_微服务与分布式/分布式事务.md",
      "title": "分布式事务：两边不能一起提交时，怎样把账做完整",
      "priority": "P2",
      "treatment": "static",
      "concept": "2PC、TCC、Saga 的保证边界",
      "recommended_medium": "状态机与补偿时序",
      "reason": "多种协议的失败假设不同，静态状态对照更容易保留“结果未知”和补偿语义。",
      "reuse_family": "distributed-commit",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 能避免跨边界，就先避免"
          },
          {
            "line": 22,
            "text": "## 2PC：准备好以后，为什么不能自己反悔"
          },
          {
            "line": 47,
            "text": "### 协调者用 Raft 复制之后，还算 2PC 吗？"
          },
          {
            "line": 55,
            "text": "## 3PC：多一段状态，为超时决策提供更多信息"
          },
          {
            "line": 69,
            "text": "## TCC：把数据库的“准备”变成业务上的“预留”"
          },
          {
            "line": 89,
            "text": "## Saga：补偿是一笔新业务，不是时间倒流"
          },
          {
            "line": 112,
            "text": "## 本地消息表：先确保“业务事实”和“待发送事件”一起留下"
          },
          {
            "line": 143,
            "text": "### 出站可靠，不等于副作用只发生一次"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "创建订单成功，扣库存超时，支付又已经扣款。此时最危险的处理是把所有超时都当成失败，再重新执行一遍。一个跨服务操作可能已经完成一部分，还可能有一部分结果未知。分布式事务方案要规定这些中间状态如何保存、怎样恢复，以及谁负责把流程收束。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文从原子提交讲到业务补偿和消息方案。它们不是同一种保证的不同品牌：2PC 尝试让参与者对提交或中止达成一致；Saga 接受某些步骤先完成，再用新的业务动作补偿；消息方案常把“业务变化”和“稍后要做的事”连起来。选型前先决定哪些业务不变量必须立即成立。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "c390ee623c3fe2b54b35ac4070a6507a0be6b3bba65c4960f12dbb960b761cda",
      "source_line_count": 374,
      "existing_static_mermaid_count": 3,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-070",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/10_微服务与分布式/分布式基础组件.md",
      "title": "分布式基础组件：锁、ID、任务和会话都要面对“旧请求还活着”",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "租约失效与 fencing token",
      "recommended_medium": "旧/新持有者写入事件线",
      "reason": "暂停旧持有者再恢复写入，能显示有锁和仍有权写资源是两件事。",
      "reuse_family": "fencing",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 分布式锁：先区分“谁拿到锁”和“谁还能改资源”"
          },
          {
            "line": 28,
            "text": "### “写入前确认锁没过期”为什么仍有空隙？"
          },
          {
            "line": 53,
            "text": "## Redis、etcd、ZooKeeper 的锁有什么差别"
          },
          {
            "line": 61,
            "text": "### Redis：value 防误删，TTL 防永久占用"
          },
          {
            "line": 69,
            "text": "### etcd：可以把锁的条件带入 etcd 事务"
          },
          {
            "line": 77,
            "text": "### ZooKeeper：监听前驱，减少同一时刻一起抢锁"
          },
          {
            "line": 85,
            "text": "## 分布式 ID：先决定唯一性由谁承担"
          },
          {
            "line": 95,
            "text": "### 雪花算法：最难的两位不是移位运算"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "租约已经到期，旧进程却从长暂停里恢复；任务已执行，调度器没收到回执；新节点刚领到工作，旧节点还在发送写入。这些问题不能靠“再加一把分布式锁”统一解决。需要分别定义资源所有权、请求身份、重复执行和状态失效的边界。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文把分布式锁、ID、定时任务和会话放在一起讲，重点是每种组件到底承诺什么，以及应用还要补什么。代码实际演示过期持有者的写入拒绝、雪花 ID 的同毫秒序列与回拨拒绝、同一任务槽位的重复执行去重；所有示例都只是本地模型。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "5b77d6c5dc6db70eb5ff8669deb2f508496a46d7ec98db5524780d24a0e07534",
      "source_line_count": 359,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-071",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/10_微服务与分布式/分布式理论基础.md",
      "title": "分布式理论基础：先说清“什么结果算对”",
      "priority": "P2",
      "treatment": "static",
      "concept": "一致性、可用性与故障假设",
      "recommended_medium": "请求历史和允许结果对照图",
      "reason": "需要精确陈述理论假设；避免把 CAP 简化成任意三选二的互动三角形。",
      "reuse_family": "distributed-assumptions",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先把故障和时间假设摆出来"
          },
          {
            "line": 23,
            "text": "## 一致性模型：约束的是“别人会看到什么”"
          },
          {
            "line": 37,
            "text": "### 写已经成功，另一个人还能读到旧值吗？"
          },
          {
            "line": 76,
            "text": "## CAP：发生分区时，哪些请求必须让步？"
          },
          {
            "line": 96,
            "text": "## BASE：把收敛做成流程，不能只留一句承诺"
          },
          {
            "line": 112,
            "text": "## FLP：安全地等待为什么不能总有上界"
          },
          {
            "line": 120,
            "text": "## 拜占庭问题：为什么多数票不一定够"
          },
          {
            "line": 128,
            "text": "## 三种时钟各能回答什么"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "两台服务器都存着库存 1。网络断开后，一边刚确认卖出，另一边又收到了购买请求。后者等多久？能否直接用本地库存？恢复连接后，两个订单怎么处理？CAP、一致性模型和共识理论，讨论的就是这种具体选择。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "读完这篇，应当能给一个接口写清三件事：允许看到哪些结果，在哪些故障下仍要完成，以及判断先后顺序靠什么。文末有一个 Go 小模型，实际检查“可以排成某个顺序”和“尊重已经发生的先后”为什么不一样。它只检查很短的单寄存器历史，不模拟真实集群，也不证明某个数据库的正确性。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "fb8c64c892d6a8a121929dfd7b536462b3fd208476c738765b75c02a69b73df0",
      "source_line_count": 322,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-072",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/10_微服务与分布式/微服务拆分方法.md",
      "title": "微服务拆分方法：先把边界分清，再决定要不要跨网络",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "微服务拆分与业务边界",
      "recommended_medium": "案例、约束与迁移检查点",
      "reason": "选择高度依赖团队和业务，不适合用可视化自动判定拆分优劣。",
      "reuse_family": "design-judgment",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 三种形态改变的是什么"
          },
          {
            "line": 19,
            "text": "## 先找业务能力，不要先数数据库表"
          },
          {
            "line": 34,
            "text": "## 限界上下文：同一个词，在不同业务里可以有不同含义"
          },
          {
            "line": 52,
            "text": "## 在模块化单体里，把边界真的立起来"
          },
          {
            "line": 91,
            "text": "### 接口存在，还不等于模块边界存在"
          },
          {
            "line": 99,
            "text": "## 什么情况值得把模块移到独立进程"
          },
          {
            "line": 112,
            "text": "## 这些信号出现时，先别拆"
          },
          {
            "line": 125,
            "text": "## 渐进迁移：把每一步设计成能停住的状态"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "订单、库存和支付写在一个仓库里，不一定是坏架构；它们跑在三个进程里，也不一定已经解耦。如果改一个字段要同时改三个服务，发布必须一起发，数据库还互相写，这套系统只是把原来的耦合搬到了网络上。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "拆分的目标应是能独立理解、修改、部署和承担责任的一块业务。本文按单体、模块化单体、微服务的演进来讲，重点是如何识别边界、验证拆分收益，以及什么时候应该停下来。代码演示一个进程内的库存预留接口，让边界先在本地成立；它不是完整下单流程，也没有运行远程服务。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "cea7936bf6134be9ae607e594445a287f4227e6b1b8777e56aad238ecfb473c0",
      "source_line_count": 308,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-073",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/10_微服务与分布式/数据分布.md",
      "title": "数据分布：决定数据放哪里，也决定以后怎么搬",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "一致性哈希与成员变化",
      "recommended_medium": "哈希环和键迁移对照",
      "reason": "增删节点后高亮迁移的键，比较取模路由；明确虚拟节点不能解决所有热点。",
      "reuse_family": "consistent-hashing",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先分清分片、副本和路由"
          },
          {
            "line": 31,
            "text": "## 分片键选错，后面的路由算法救不了查询"
          },
          {
            "line": 48,
            "text": "## 一致性哈希：成员变化时，尽量少改键的位置"
          },
          {
            "line": 68,
            "text": "### 虚拟节点解决的是区间分配粒度"
          },
          {
            "line": 80,
            "text": "## 副本同步：收到、落盘、应用，确认的是哪一步？"
          },
          {
            "line": 94,
            "text": "### 单主、多主、无主，冲突发生的位置不同"
          },
          {
            "line": 106,
            "text": "## 再均衡：不能只把路由指向空的新机器"
          },
          {
            "line": 134,
            "text": "### 用 generation 拒绝旧路由"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "给订单表加几台机器，难点不只是把数据均匀分开。一次按用户查询是否还只访问一个分片？一个大客户能不能拖垮整台机器？扩容时，新旧路由同时存在，谁还有权接收写入？分片、副本和再均衡必须一起设计。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文从分片键开始，解释一致性哈希与虚拟节点，再区分复制拓扑，最后走一遍带代次检查的迁移流程。程序只模拟一个值从旧分片迁到新分片的受控过程，实际验证提前切换和过期路由会被拒绝；它不代表任何数据库的迁移协议。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "c85e2358dcc358f50047d2803985a132d7fe65b053d48e5bfba0b3598d1bcddd",
      "source_line_count": 312,
      "existing_static_mermaid_count": 3,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-074",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/10_微服务与分布式/服务治理.md",
      "title": "服务治理：让请求找到合适的实例，并在变化时有序退出",
      "priority": "P2",
      "treatment": "static",
      "concept": "服务发现、就绪与排空",
      "recommended_medium": "管理路径/请求路径双图",
      "reason": "快照、版本和生命周期可用静态时序说明，保留真实治理工具验证。",
      "reuse_family": "service-lifecycle",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先画请求路径，再画管理路径"
          },
          {
            "line": 32,
            "text": "## 注册与发现：先得到快照，再持续追踪变化"
          },
          {
            "line": 38,
            "text": "### 一份端点列表为什么会突然退回旧版本？"
          },
          {
            "line": 64,
            "text": "### 在线、就绪、可接新请求，不是一件事"
          },
          {
            "line": 83,
            "text": "## 负载均衡：平均分配什么？"
          },
          {
            "line": 98,
            "text": "### P2C 的比较值要先说清"
          },
          {
            "line": 122,
            "text": "## 配置中心：发布成功以后，还差“安全生效”"
          },
          {
            "line": 139,
            "text": "## API 网关与服务网格：放到流量边界上比较"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "订单服务从两台扩到十台，地址会变，实例会重启，新版本还要逐步接流量。客户端不能把所有地址写死，也不能认为“注册中心说在线”就等于“这台机器现在接得住请求”。服务治理要把这些变化接到真实请求路径上。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这篇按一次调用的顺序展开：发现可用端点，挑选一个实例，应用配置和调用预算，最后让旧实例安全退出。API 网关和服务网格放在这个路径里比较，避免每解决一个问题就多加一套组件。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "db56b440944fef852457cab93fea69223156dff7fc4342305d523eff4a11cce3",
      "source_line_count": 348,
      "existing_static_mermaid_count": 3,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-075",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/11_高并发与高可用/Go 稳定性与压测工具选型.md",
      "title": "Go 稳定性与压测工具选型：先选保护边界，再选测量方式",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "稳定性库与压测模型选型",
      "recommended_medium": "接口表与既有实验",
      "reason": "工具选择依赖具体策略顺序；算法互动集中到稳定性章节，不重复造压测器。",
      "reuse_family": "tool-selection",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 本文固定的版本与适用位置"
          },
          {
            "line": 24,
            "text": "## x/time/rate：令牌不够时，拒绝、预留还是等待"
          },
          {
            "line": 55,
            "text": "## gobreaker：错误如何进入统计，决定什么时候熔断"
          },
          {
            "line": 86,
            "text": "## failsafe-go：把策略写成有顺序的调用层"
          },
          {
            "line": 124,
            "text": "## sentinel-golang：资源入口和出口都属于协议"
          },
          {
            "line": 148,
            "text": "## 压测工具先比较负载模型，再比较语法"
          },
          {
            "line": 159,
            "text": "## k6：checks 记录判断，thresholds 决定测试是否失败"
          },
          {
            "line": 187,
            "text": "## Vegeta：用小型 Go 实验核对结果口径"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "限流器决定哪些调用能进入，熔断器决定何时暂停调用，而压测工具负责按一种模型制造请求、记录结果。它们可以配合，却不能互相证明：加了熔断器不等于系统可靠，压出很高 QPS 也不等于用户请求仍然正确。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "地图中的八个工具，这篇逐一讲入口、机制和限制。四个 Go 保护库实际跑了小型实验；Vegeta 使用 Go API，k6、wrk、ghz 使用命令行，目标全部是自己启动的本机回环服务。本文没有对它们做性能排名，也没有测生产容量。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "0476ff3eb6cc7e6c013912f6619937311af881cc0a2b8fdb495129842d5edc0f",
      "source_line_count": 1400,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-076",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/11_高并发与高可用/发布安全.md",
      "title": "发布安全：新版本能接班，旧版本也要能退场",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "发布时停止接流量与排空",
      "recommended_medium": "新旧实例发布时间线",
      "reason": "分步触发 ready、终止、摘流和超时，能让宽限期中的竞态可见。",
      "reuse_family": "deployment-lifecycle",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 启动、就绪、存活是三个问题"
          },
          {
            "line": 21,
            "text": "## 旧实例先收口，再排空"
          },
          {
            "line": 45,
            "text": "### Go 的 Shutdown 做什么，没做什么"
          },
          {
            "line": 73,
            "text": "## Pod 宽限期要覆盖整段退场过程"
          },
          {
            "line": 87,
            "text": "## 灰度、金丝雀、滚动、蓝绿，分别控制哪件事"
          },
          {
            "line": 102,
            "text": "## 金丝雀判定要看用户结果，也要能停止"
          },
          {
            "line": 119,
            "text": "## 功能开关把代码部署与用户暴露分开"
          },
          {
            "line": 133,
            "text": "## 真正决定能不能回滚的，常常是数据库"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "一次发布会让新旧版本同时存在，让连接在旧实例上继续运行，还可能改变数据库结构。只确认“新进程启动了”远远不够：新版本要准备好接流量，旧版本要停止接新任务并处理完已有工作，数据也要允许两个版本共存。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这篇文章把优雅启停、健康检查、灰度、蓝绿、金丝雀、功能开关和回滚连成一条发布流程。正文的 HTTP 输出来自本机真实的回环网络实验；Kubernetes 行为按官方文档解释，当前没有集群，因此不会把配置示例写成已经验证的零停机方案。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "6a25382e55d5b574e934579fc040753fe15d6197e1f579517df6d06e49cd28e6",
      "source_line_count": 454,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-077",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/11_高并发与高可用/可靠性度量.md",
      "title": "可靠性度量：先写清分母，再谈几个 9",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "SLI 分母、错误预算与 burn rate",
      "recommended_medium": "带口径说明的预算计算器",
      "reason": "改变坏事件、总事件和窗口即显示预算消耗，并明确无流量与缺数据不能算满分。",
      "reuse_family": "slo-budget",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## SLI、SLO、SLA 分别负责测量、目标与约定"
          },
          {
            "line": 19,
            "text": "## 一条可执行的 SLI 定义，至少包含这些字段"
          },
          {
            "line": 49,
            "text": "## 没有流量不等于百分之百成功"
          },
          {
            "line": 77,
            "text": "## 错误预算是容许的坏事件量"
          },
          {
            "line": 106,
            "text": "## Burn rate 把严重程度和持续时间连起来"
          },
          {
            "line": 129,
            "text": "## 几个 9 的时间换算，先固定窗口"
          },
          {
            "line": 157,
            "text": "## 预算用来安排工作，不是替故障开脱"
          },
          {
            "line": 165,
            "text": "## 本次验证与边界"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "服务监控显示成功率 99.99%，用户却说登录一直失败。可能是登录请求被海量健康检查稀释，也可能是服务端只统计到达它的请求，网关前的失败根本没进入分母。一个漂亮的百分比，只有在衡量了用户真正关心的操作后才有意义。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这篇文章说明怎样定义 SLI、设置 SLO、区分 SLA，再把错误预算和“几个 9”算对。文末程序使用有理数计算事件比例，避免把浮点舍入误差当成刚好达标或刚好超标；时间换算明确使用 30 天和 365 天。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "bceeb9f749f564b8df31162fb50c38a23b771a677db8dd2559c11d3f1c4aa764",
      "source_line_count": 308,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-078",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/11_高并发与高可用/容灾.md",
      "title": "容灾：副本能接班，备份能回到过去，恢复还需要演练",
      "priority": "P2",
      "treatment": "static",
      "concept": "副本、备份与可恢复历史",
      "recommended_medium": "故障域图和恢复时间线",
      "reason": "容灾应从真实恢复验收判断，交互时间拖动不能证明备份可用。",
      "reuse_family": "disaster-recovery",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先定目标：允许停多久，允许丢多久的数据"
          },
          {
            "line": 26,
            "text": "## 多副本先回答“故障域在哪里”"
          },
          {
            "line": 42,
            "text": "## 故障转移不是把备用节点改名"
          },
          {
            "line": 63,
            "text": "## 同城双活和异地多活，先说明谁能写什么"
          },
          {
            "line": 77,
            "text": "## 备份的价值在于保留可选择的历史"
          },
          {
            "line": 108,
            "text": "## 恢复演练要验证业务，不能只看文件能解压"
          },
          {
            "line": 142,
            "text": "## 混沌工程从一个可否证的假设开始"
          },
          {
            "line": 161,
            "text": "## 依据"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "数据库有三个副本，并不意味着一次误删后还有一份好数据。如果删除也是正常复制的一部分，三个副本会先后变成同样的错误状态。反过来，备份文件完整，也不意味着业务马上能恢复：还要找到合适的版本、恢复依赖、核对数据，再把流量切回来。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "容灾要从具体故障和业务目标出发。这篇文章依次讲多副本、故障转移、同城双活、异地多活、备份恢复与混沌实验，重点是每种手段保护什么，以及恢复过程中如何避免制造第二次事故。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "94c9c0e37ff3f14e221a46e57cfaf7d6e84fab276c957ae3149d5174c830f7a5",
      "source_line_count": 398,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-079",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/11_高并发与高可用/容量与弹性.md",
      "title": "容量与弹性：先量出可用容量，再讨论扩多少实例",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "开环/闭环负载与扩容延迟",
      "recommended_medium": "有说明的离散事件队列模型",
      "reason": "在服务变慢时比较发送率和已完成请求延迟，帮助理解协调遗漏与排队。",
      "reuse_family": "queueing",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 四类测试分别回答什么问题"
          },
          {
            "line": 22,
            "text": "## 开环与闭环，差别在下一次请求由谁决定"
          },
          {
            "line": 39,
            "text": "### 协调遗漏为什么会让慢服务看起来没那么慢"
          },
          {
            "line": 62,
            "text": "## Little 定律是口径检查器，不能把 p99 塞进去算容量"
          },
          {
            "line": 86,
            "text": "## 把压测结果变成有故障余量的容量计划"
          },
          {
            "line": 114,
            "text": "## 自动伸缩是一条有延迟的控制回路"
          },
          {
            "line": 136,
            "text": "## 无状态化的含义，是状态有明确归属"
          },
          {
            "line": 152,
            "text": "## 一份能够指导行动的压测结论"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "“这台机器能跑一万 QPS”没有说明请求是什么、错误算不算、尾延迟多大，也没有说明压测端是否真的发出了计划流量。容量规划需要的是更完整的一句话：在指定请求混合、数据规模、依赖和故障条件下，系统能持续完成多少工作，并且仍满足响应质量要求。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这篇文章说明如何选择压测模型、识别协调遗漏、用 Little 定律检查数据，再把结果转成副本数和自动伸缩策略。文末用离散事件模型演示慢请求如何改变测量；它没有模拟网络，也不是某台机器的性能测试。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "3aabeff774435c4bf4339836e754d8a5098795c9d18e7b1722af6b2bf4708713",
      "source_line_count": 337,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-080",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/11_高并发与高可用/幂等设计.md",
      "title": "幂等设计：超时以后，怎样安全地再试一次",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "幂等键、请求状态与重放",
      "recommended_medium": "同一意图的失败/重试状态机",
      "reason": "切断响应后重试并显示业务效果次数，能解释见过请求还不等于已有结果。",
      "reuse_family": "idempotency",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 把“相同请求”定义成一个协议"
          },
          {
            "line": 24,
            "text": "## 一个请求不是只有“见过”和“没见过”"
          },
          {
            "line": 52,
            "text": "## 唯一约束把并发竞争交给真正能裁决的一层"
          },
          {
            "line": 78,
            "text": "## 用一个本地模型，看清并发、重放和到期"
          },
          {
            "line": 112,
            "text": "## 去重记录的寿命，就是保证的边界"
          },
          {
            "line": 132,
            "text": "## 一次性 Token 机制，难点在“消费之后”"
          },
          {
            "line": 145,
            "text": "## 跨系统时，把“已提交”与“已送达”拆开处理"
          },
          {
            "line": 162,
            "text": "## 上线前要测的是失败窗口"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "客户端提交了一次订单，服务端已经写入数据库，但返回响应前连接断了。客户端只能看到超时。如果换一个请求重新提交，可能生成第二张订单；如果直接告诉用户失败，又可能让用户看不见已经成立的订单。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "幂等设计要解决的正是这种不确定性：同一个业务意图可以多次送达，但不重复产生本来只该发生一次的效果。要做到这一点，服务端得认出请求身份，原子地决定谁能执行，并在重试时找回结果。仅仅加一个 Redis 锁或请求头，还没有完成这些工作。[1]",
            "truncated": false
          }
        ]
      },
      "source_sha256": "da6934e5b11297f92268549642e342a4bdacb05997b7733d16d87992c5482355",
      "source_line_count": 417,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-081",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/11_高并发与高可用/稳定性手段.md",
      "title": "稳定性手段：让慢依赖和突发流量止步于有限资源",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "令牌桶、重试和熔断",
      "recommended_medium": "请求准入与熔断状态模型",
      "reason": "先只实现令牌桶或熔断器一种机制，显示突发与等待；不承诺生产稳定性。",
      "reuse_family": "rate-limiting",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先分清各手段控制什么"
          },
          {
            "line": 39,
            "text": "## 超时要覆盖整条路径，取消要有人接住"
          },
          {
            "line": 74,
            "text": "## 重试前，先决定这次失败有没有资格再做一次"
          },
          {
            "line": 120,
            "text": "## 限流算法的差异，在“突发怎么办”上最明显"
          },
          {
            "line": 139,
            "text": "## 熔断要看状态，降级要看业务含义"
          },
          {
            "line": 158,
            "text": "## 舱壁和负载卸除，让剩余工作有机会完成"
          },
          {
            "line": 187,
            "text": "## 验证范围与尚未证明的事"
          },
          {
            "line": 191,
            "text": "## 依据"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "一个下游接口平时 20 毫秒返回，故障时要等 2 秒。入口流量没有增加，服务却开始耗尽连接、堆积 goroutine，随后所有接口一起超时。问题在于每个请求占用资源的时间变长了；如果客户端再重试，系统还要处理额外的工作。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这篇文章围绕这条故障链讲超时、重试、限流、熔断、降级、舱壁和负载卸除。目标是能回答三个具体问题：请求还能等多久，系统还能接多少工作，已经接下的工作怎样结束。下文的策略组合是设计建议；Go 行为和演示程序另外给出可核对的依据。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "3a556c8c4affee3893dfd6f565ba2541b9c37af9d8533643b8f19d836626ca44",
      "source_line_count": 440,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-082",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/11_高并发与高可用/高并发手段.md",
      "title": "高并发手段：减少重复工作，并给共享资源设上限",
      "priority": "P2",
      "treatment": "static",
      "concept": "池化、批量与异步的成本转移",
      "recommended_medium": "工作与资源责任图",
      "reason": "方法众多且需证据选择，复用连接池和背压示例入口即可。",
      "reuse_family": "resource-costs",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先用现象缩小范围"
          },
          {
            "line": 22,
            "text": "## 三种池，复用的是三种东西"
          },
          {
            "line": 24,
            "text": "### 连接池：复用昂贵连接，也把容量变成排队点"
          },
          {
            "line": 40,
            "text": "### 协程池：限制执行者，还要限制等待者"
          },
          {
            "line": 81,
            "text": "### 对象池：降低分配压力，但不保存业务事实"
          },
          {
            "line": 104,
            "text": "## 批处理和合并请求，减少的是不同的重复"
          },
          {
            "line": 134,
            "text": "## 异步化把响应变短，也把责任延长"
          },
          {
            "line": 153,
            "text": "## 读写分离、缓存和预计算，要把新鲜度说清楚"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "接口慢了，第一反应常是多开 goroutine。可如果请求都在等同一个数据库，多开 goroutine 只会让更多请求挤到数据库前。优化前要先分清：时间花在计算、分配、网络往返、锁竞争，还是等待资源上；随后才能决定该复用、合并、搬到后台，还是减少工作。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这篇文章把连接池、协程池、对象池、批处理、请求合并、异步化、读写分离、缓存、预计算和锁优化放在同一条请求路径上。它们解决的问题不同，也会把成本转移到不同地方。所有性能选择都需要目标负载验证，本文的本地实验只验证机制和关闭路径，不提供通用吞吐排名。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "96e706fdefd2146decf30ec7b25f1d511e047657268d8dd2e32782c8643866eb",
      "source_line_count": 421,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-083",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/12_安全/Go 安全工具选型.md",
      "title": "Go 安全工具选型：先分清库、扫描器和密钥服务",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "安全库、扫描器和密钥服务分工",
      "recommended_medium": "职责、威胁与配置对照",
      "reason": "安全判断不能靠视觉绿灯，保留具体验证规则和失败反例。",
      "reuse_family": "tool-selection",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先按任务选入口"
          },
          {
            "line": 35,
            "text": "## 本文固定了什么，实际验证了什么"
          },
          {
            "line": 51,
            "text": "## golang-jwt/jwt：库能验哪些东西，哪些要你写出来"
          },
          {
            "line": 76,
            "text": "## x/crypto：选择具体包，不给整个模块贴安全标签"
          },
          {
            "line": 94,
            "text": "## Casbin：把策略从业务代码里分离出来，但不能分离责任"
          },
          {
            "line": 116,
            "text": "## go-oidc：验证 ID Token，不会替你完成整次登录"
          },
          {
            "line": 141,
            "text": "## 三种扫描器：看见的是三种不同材料"
          },
          {
            "line": 152,
            "text": "### govulncheck：这次真的出现了“模块有问题，但没有调用命中”"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "`jwt`、`go-oidc`、Casbin 和 Vault 都可能出现在登录系统里，但它们没有在解决同一个问题。JWT 库验证令牌格式和声明，OIDC 客户端处理联合身份的一部分，策略引擎判断权限，密钥服务管理秘密和密码学操作。扫描器则从旁检查代码或制品，不参与每一次业务授权。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文逐一说明九个工具或工具类别：`golang-jwt/jwt`、`golang.org/x/crypto`、Casbin、`go-oidc`、`govulncheck`、`gosec`、Trivy、Vault、云厂商 KMS。选型重点是它负责哪段边界、调用方还欠哪些工作，以及测试结果能说明什么。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "8270fe291afd347bf3ca061e60df65e73d6625a59a78ea8f88e780a9880b4e87",
      "source_line_count": 684,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-084",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/12_安全/供应链与运行时安全.md",
      "title": "供应链与运行时安全：从依赖来源追到线上进程",
      "priority": "P2",
      "treatment": "static",
      "concept": "依赖、制品、签名与线上身份",
      "recommended_medium": "软件供应链证据图",
      "reason": "强调每种证据证明什么，静态溯源图比模拟安全评分更可靠。",
      "reuse_family": "artifact-lineage",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 一份制品需要几种不同证据"
          },
          {
            "line": 36,
            "text": "## Go 依赖：固定版本与验证内容，解决的是两类问题"
          },
          {
            "line": 44,
            "text": "### 摘要能发现改动，但它没有自己的信任来源"
          },
          {
            "line": 60,
            "text": "## 依赖漏洞扫描：要读懂“发现”与“没发现”"
          },
          {
            "line": 88,
            "text": "## 密钥管理：减少分发范围，也设计好撤销"
          },
          {
            "line": 104,
            "text": "### 轮换至少包含发布、切换、停止接受与清理"
          },
          {
            "line": 120,
            "text": "## 最小化镜像：删掉不需要的东西，同时保留可维护性"
          },
          {
            "line": 146,
            "text": "### 运行时权限决定漏洞能波及多远"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "代码审查通过，不代表最终运行的程序就来自那份代码。构建环境可能取到了不同依赖，镜像里可能残留密钥，线上进程也可能拥有远超需要的权限。供应链与运行时安全要把这些环节接起来：知道用了什么、怎样生成、部署了哪个制品，以及运行后发生了什么。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文按依赖、构建、密钥、镜像和审计五个环节展开。实际实验只构建和扫描本地公开 Go 夹具，并验证摘要及日志边界；没有连接生产仓库、密钥服务或容器集群。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "60a9f3e715ede5e61059e913d9c649cb1d8d394677f2cf79aaae6ef3206dd40e",
      "source_line_count": 327,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-085",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/12_安全/密码学应用.md",
      "title": "密码学应用：先决定要保护什么，再选算法",
      "priority": "P2",
      "treatment": "static",
      "concept": "哈希、MAC、签名、加密的威胁边界",
      "recommended_medium": "发送者/验证者密钥关系图",
      "reason": "图可解释谁有能力伪造，交互不应接收真实密码或暗示实现经过安全审计。",
      "reuse_family": "cryptographic-boundaries",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 编码、哈希、加密：区别在它们承诺什么"
          },
          {
            "line": 30,
            "text": "## 密码存储：让每次离线猜测都付出代价"
          },
          {
            "line": 45,
            "text": "### 盐、参数、摘要一起保存，秘密另有用途"
          },
          {
            "line": 77,
            "text": "## 加密数据：默认把完整性一起考虑"
          },
          {
            "line": 83,
            "text": "### nonce 重复，不是“小概率多解出一条”那么简单"
          },
          {
            "line": 104,
            "text": "## HMAC 和签名：区别在验证者是否也能伪造"
          },
          {
            "line": 126,
            "text": "## TLS：验证证书只是建立安全通道的一部分"
          },
          {
            "line": 147,
            "text": "### 证书链可信，主机名仍然可能不对"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "保存用户密码、给数据库字段加密、校验 webhook、建立 HTTPS 连接，虽然都用到了密码学，目标却不同。把 SHA-256 换成 AES 不能自动让密码存储更安全，给数据做 Base64 也没有保密效果。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文围绕四个问题展开：以后需不需要还原原文，谁能验证内容，谁能生成可信内容，以及密钥泄漏后如何恢复。代码使用标准密码学实现，演示密码校验、认证加密、MAC、签名和证书验证，不设计新算法或新密码协议。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "43057d66c95221b7f1335f5276c18445c2b4dfa0b336e28f8bace867e5ffd30a",
      "source_line_count": 413,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-086",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/12_安全/常见安全漏洞.md",
      "title": "常见安全漏洞：沿着输入找到危险操作",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "输入到危险操作的安全检查",
      "recommended_medium": "局部代码反例和本地测试",
      "reason": "现有受控反例更贴近修复；不增加联网攻击靶场或任意代码执行入口。",
      "reuse_family": "security-boundaries",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## OWASP Top 10 要带版本看"
          },
          {
            "line": 26,
            "text": "## 先画输入到危险操作的路线"
          },
          {
            "line": 53,
            "text": "## SQL 注入：把数据交给参数，不让它变成语法"
          },
          {
            "line": 63,
            "text": "## XSS：同一个字符串，落在不同位置就有不同含义"
          },
          {
            "line": 85,
            "text": "## CSRF：凭据是浏览器自动带上的，操作却不是用户想发的"
          },
          {
            "line": 104,
            "text": "## SSRF：检查过 URL，不等于限制住了连接"
          },
          {
            "line": 110,
            "text": "### DNS 和重定向是两次容易漏掉的“重新选目标”"
          },
          {
            "line": 149,
            "text": "## 路径穿越：清理字符串与限制文件能力是两件事"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "审查一个上传、搜索或回调接口时，先追踪三件事：哪些值来自不可信输入，这些值最终控制了什么操作，中间有没有真正限制这种控制。用户输入进入 SQL 语法、HTML、命令行、网络地址或文件路径时，风险不同，修复方法也不同。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文按这条路径讲 SQL 注入、XSS、CSRF、SSRF、路径穿越、命令注入、反序列化与开放重定向。所有运行实验都只接触本地临时文件、内存或 loopback 测试服务器，没有外部扫描，也没有真实受害目标。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "f325846d68d5e160220c62de08bf5254d8228f759efaacb3574316e59e6cba05",
      "source_line_count": 478,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-087",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/12_安全/授权.md",
      "title": "授权：把“谁能对哪份数据做什么”写成可检查的规则",
      "priority": "P2",
      "treatment": "static",
      "concept": "主体、资源、动作与授权关系",
      "recommended_medium": "权限矩阵和关系图",
      "reason": "明确策略例外与检查时序更重要；可视化只展示虚构主体，不做真实权限变更。",
      "reuse_family": "authorization",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 一个授权判断至少有四个输入"
          },
          {
            "line": 34,
            "text": "## 三种模型，用同一个问题比较"
          },
          {
            "line": 48,
            "text": "### RBAC：角色让岗位权限不必逐人复制"
          },
          {
            "line": 56,
            "text": "### ABAC：条件更细，代价是必须解释属性"
          },
          {
            "line": 62,
            "text": "### ReBAC：共享权限来自关系，撤销也必须跟着关系变化"
          },
          {
            "line": 70,
            "text": "## 水平越权与垂直越权，在代码里分别长什么样"
          },
          {
            "line": 117,
            "text": "## 最小权限，不止是给用户少发几个角色"
          },
          {
            "line": 131,
            "text": "## 权限检查与业务操作之间，不能留下随意变化的空隙"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "登录成功后，服务端知道请求来自 Alice。接下来读取 `doc-1` 时，还必须回答：这份文档属于哪个租户？Alice 是所有者、共享成员还是管理员？这次动作是查看还是删除？文档是否已锁定？",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这些问题属于授权。本文用同一份文档策略说明 RBAC、ABAC、ReBAC 的差别，再把水平越权、垂直越权、缓存失效和检查时序放回请求链里。目标是让权限规则可以解释、可以测试，而不是把所有判断塞进一个不断变长的 `if admin`。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "eda640f12f8c3d8c5cae1478290992f604d5bf670c38296e98543868c96efb2a",
      "source_line_count": 365,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-088",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/12_安全/接口安全.md",
      "title": "接口安全：让每一道检查都有明确的边界",
      "priority": "P2",
      "treatment": "static",
      "concept": "校验、防重放与输出边界",
      "recommended_medium": "请求检查顺序图",
      "reason": "一条静态信任边界路线能保留不同安全机制的责任，不用交互绿灯代替审查。",
      "reuse_family": "security-boundaries",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 请求处理顺序，首先决定攻击者能消耗多少资源"
          },
          {
            "line": 27,
            "text": "## 输入校验分三层，不能相互替代"
          },
          {
            "line": 52,
            "text": "### 选择解码语义时，也是在选择协议"
          },
          {
            "line": 68,
            "text": "## 请求签名：签的是准确字节和用途，不能只签时间戳"
          },
          {
            "line": 95,
            "text": "## 防重放：有效签名还需要一次性状态"
          },
          {
            "line": 118,
            "text": "### 防重放与幂等处理的是不同问题"
          },
          {
            "line": 124,
            "text": "## 限流与防刷：额度应该对应你要保护的资源"
          },
          {
            "line": 142,
            "text": "## 输出编码和脱敏：发出去之前，先决定该不该发"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "一个接口可以同时使用 HTTPS、JWT 和限流，却仍然接受不该写入的字段、重复执行同一个请求，或者把敏感数据发给错误的人。安全机制要解决具体问题，不能靠“加了一层中间件”来判断。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文按请求经过服务端的顺序，说明输入校验、签名与防重放、限流、防刷、输出编码、脱敏、安全响应头和 CORS。实验是内存中的 HTTP Handler 测试，不开放外部服务；固定密钥和请求内容都是公开测试夹具。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "56ffa4ee24ba8d41bf53f762693317e971c764168fd7eeb649a9a622da827adb",
      "source_line_count": 556,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-089",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/12_安全/认证.md",
      "title": "认证：从“登录成功”到每次请求都认对人",
      "priority": "P2",
      "treatment": "static",
      "concept": "会话、JWT、OAuth/OIDC 与吊销",
      "recommended_medium": "登录/刷新/验证时序图",
      "reason": "协议细节和威胁条件必须明确，静态图更适合伴读官方流程。",
      "reuse_family": "authentication",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先把凭据、认证、会话和授权拆开"
          },
          {
            "line": 18,
            "text": "## Session 和 Token 不是两种互斥的登录方式"
          },
          {
            "line": 49,
            "text": "## JWT：先验它是谁签的，再看它究竟在说什么"
          },
          {
            "line": 68,
            "text": "### 不同签名算法，改变的是密钥分工"
          },
          {
            "line": 81,
            "text": "### 在 Go 中把验证规则写进代码"
          },
          {
            "line": 105,
            "text": "## 刷新与吊销：有效签名不会自动变成无效"
          },
          {
            "line": 143,
            "text": "## OAuth 2.0、OIDC 与 SSO 各管哪一段"
          },
          {
            "line": 186,
            "text": "## MFA 与 Passkey：提升登录强度，也别留下更弱的恢复入口"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "认证要解决两件事：第一次确认来者是谁，以及后续请求怎样可靠地延续这次确认。密码校验只是其中一环。一个系统即使密码没被破解，也可能因为会话固定、令牌验签不完整、刷新令牌失窃或账号恢复太宽松而认错人。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文沿着浏览器登录、API 验证、联合登录和账号恢复这条线，解释 Session、JWT、OAuth 2.0、OIDC、SSO、MFA 与 Passkey 各自负责什么。实验只在本地验证令牌和刷新状态机，不连接真实身份提供方，也不把演示代码当作完整登录系统。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "eaa47b42c77a0d66f02a8da87e9cfe3150a6d982184022512642d4aee139521c",
      "source_line_count": 449,
      "existing_static_mermaid_count": 3,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-090",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/13_可观测性/Go 可观测性工具选型.md",
      "title": "Go 可观测性工具选型：先分清谁采集、谁保存、谁帮你定位问题",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "遥测采集、存储与诊断工具",
      "recommended_medium": "工具职责表和输入输出",
      "reason": "多后端选型主要是团队需求与成本，交互仪表盘不会补充证据。",
      "reuse_family": "tool-selection",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 9,
            "text": "## 先按要做的事找工具"
          },
          {
            "line": 44,
            "text": "## OpenTelemetry Go SDK：先把应用侧和后端分开"
          },
          {
            "line": 63,
            "text": "## Prometheus：擅长回答趋势和比例，先把指标维度收住"
          },
          {
            "line": 73,
            "text": "## Grafana：把调查过程连起来，数据源权限仍然要管"
          },
          {
            "line": 81,
            "text": "## Loki：标签帮你缩小日志范围，正文仍要读"
          },
          {
            "line": 91,
            "text": "## ELK：为可检索的字段付出索引和运维成本"
          },
          {
            "line": 109,
            "text": "## Tempo 与 Jaeger：同是 trace 后端，查法和部署选择要分别看"
          },
          {
            "line": 111,
            "text": "### Tempo：从 trace ID 到 TraceQL，按实际查询评估成本"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "给 Go 服务接可观测性，容易从“装哪套工具”开始。更有用的起点是：出问题时，团队要回答哪些问题？如果只需要知道错误率是否上升，却先搭起多套 tracing 后端，花掉的维护时间未必能换来更快的排查。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这篇把知识地图里的九项工具放回各自的位置：Prometheus、Grafana、Loki、Tempo、Jaeger、OpenTelemetry Go SDK、Pyroscope、Sentry，以及 ELK。读完应能画出一条说得清的数据路径，知道每个组件提供什么、要付什么成本，以及哪些能力必须在选定版本上实测。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "f9b660d0f64ff0980ed7ae9a9bc537ad94f4eddeecad0230f0d4030ce81a2308",
      "source_line_count": 599,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-091",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/13_可观测性/OpenTelemetry.md",
      "title": "OpenTelemetry：让 Go 服务产生、传播并交付遥测数据",
      "priority": "P2",
      "treatment": "static",
      "concept": "Span 生命周期、传播与导出",
      "recommended_medium": "遥测数据路径与丢失点图",
      "reason": "分开记录、采样、结束和交付能解释缺 trace，静态分层足够。",
      "reuse_family": "telemetry-pipeline",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 9,
            "text": "## 先分清谁负责什么"
          },
          {
            "line": 38,
            "text": "## API 能调用，不代表会记录"
          },
          {
            "line": 64,
            "text": "### Resource、Scope、Attribute 分别描述谁"
          },
          {
            "line": 74,
            "text": "## 埋点与传播：前者产生操作，后者连接操作"
          },
          {
            "line": 105,
            "text": "### 自动埋点负责协议细节，手动埋点负责业务意义"
          },
          {
            "line": 117,
            "text": "## Context 生命周期与 Span 生命周期要分别管理"
          },
          {
            "line": 154,
            "text": "### 记录错误与标记失败也是两件事"
          },
          {
            "line": 164,
            "text": "## 采样决定能否记录，不改变业务请求是否执行"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "在 Go 程序里调用了 `tracer.Start`，后端却没有 Trace，故障可能发生在完全不同的地方：应用使用的是空实现；Span 没有结束；采样器决定不记录；导出队列丢了数据；接收端没有配置这条信号的流水线。只检查“是否安装了 OTel”无法区分这些情况。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "OpenTelemetry 提供遥测的接口、实现、数据格式和采集组件。它帮助应用以相对统一的方式产生并传送日志、指标和 Trace，但不替你完成长期存储、检索界面或业务告警设计。这篇从一条 Span 如何产生开始，把各层的责任和 Go 中容易接错的边界走一遍。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "bc53366b0117ea9cc4067db34dbec656cf444a6e425b7cb7ee0af86f469b15e0",
      "source_line_count": 534,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-092",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/13_可观测性/可观测性三大支柱.md",
      "title": "可观测性三大支柱：从“服务变慢了”找到下一条证据",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "直方图聚合与分位数陷阱",
      "recommended_medium": "两组延迟样本分布查看器",
      "reason": "合并样本后重算分位数，展示平均各实例 p99 不等于全局 p99。",
      "reuse_family": "measurement-denominators",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 9,
            "text": "## 先按问题选证据"
          },
          {
            "line": 35,
            "text": "## 日志：先把事件写清楚，再讨论检索"
          },
          {
            "line": 72,
            "text": "## 指标：类型决定能对它做什么运算"
          },
          {
            "line": 82,
            "text": "### Counter 重置：先对每条序列求速率，再聚合"
          },
          {
            "line": 94,
            "text": "### Histogram：累计桶可以相加，分位数通常不能直接平均"
          },
          {
            "line": 131,
            "text": "### 标签的成本来自组合数量"
          },
          {
            "line": 141,
            "text": "## RED 看请求，USE 看资源"
          },
          {
            "line": 156,
            "text": "## Trace：靠传播连接起来的 Span"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "服务的 P99 延迟突然升高，先搜错误日志不一定有结果。请求可能全都成功，只是在连接池前排了队。反过来，日志里出现一百条错误，也不能直接判断影响有多大：如果总请求量是一亿次，和总请求量是一百次，含义完全不同。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "日志、指标和链路追踪分别保存事件细节、数量分布和调用关系。掌握它们的目标，是面对一个问题知道先看什么、怎样把下一条证据找出来，同时知道哪些信息已经在聚合或采样中丢掉。所谓“三大支柱”是常用的组织方式，不是“装齐三套系统就能解释一切”的定理；CPU 剖析、部署记录和用户反馈仍可能不可缺少。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "eb863c4d710bd347161a336324edf0c4f49361853eadd02c177ffb7ee1187015",
      "source_line_count": 366,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-093",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/13_可观测性/告警设计.md",
      "title": "告警设计：让每一次叫醒都指向一件值得做的事",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "多窗口 burn rate 告警",
      "recommended_medium": "长短窗口事件回放",
      "reason": "移动坏事件突发区间并显示两个窗口条件，能解释为何用 AND 且不能把无数据当正常。",
      "reuse_family": "slo-budget",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先写响应契约，再写表达式"
          },
          {
            "line": 37,
            "text": "## 分级按响应紧迫度，不按看板颜色"
          },
          {
            "line": 49,
            "text": "## SLO 告警：关注错误预算消耗速度"
          },
          {
            "line": 65,
            "text": "### 为什么两个窗口用 AND"
          },
          {
            "line": 86,
            "text": "### 把窗口模型翻译成 PromQL 时，先约定输入"
          },
          {
            "line": 106,
            "text": "### 零流量和没有数据，都不是“服务正常”"
          },
          {
            "line": 120,
            "text": "## 收敛要减少重复通知，不能删掉独立故障"
          },
          {
            "line": 136,
            "text": "## 值班流程：谁在什么时候接手什么"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "“CPU 超过 80%”是一条判断条件，还不是完整的告警。收到消息的人仍然需要知道：哪项服务受影响、是否必须现在处理、先查什么、能做什么，以及没人响应时找谁。把阈值调得更灵敏，无法补上这些缺口。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "一条好的告警，把可相信的证据接到一个明确的响应动作。本文先设计这条链，再讨论分级、去重和收敛，最后用可运行的错误预算模型说明为什么 SLO 告警经常需要两个时间窗口。代码在 Go 1.27.2、Linux/amd64 实测；示例事故与流量均为合成数据，没有向任何值班系统发送通知。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "bd8ce1a3572c6a7d0d3318a80f3e6aab38aeeee872ec0bcbaf2343ca73a1bf63",
      "source_line_count": 384,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-094",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/13_可观测性/持续剖析.md",
      "title": "持续剖析：把一次性能排查，变成能回看时间和版本的证据",
      "priority": "P2",
      "treatment": "static",
      "concept": "CPU、内存与等待 profile 口径",
      "recommended_medium": "同一请求的计时/采样对照",
      "reason": "保留实测图与采样边界，避免生成看似真实的火焰图。",
      "reuse_family": "profiling-evidence",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先认清：你要解释的是哪一种“慢”"
          },
          {
            "line": 23,
            "text": "### CPU 时间不等于请求耗时"
          },
          {
            "line": 29,
            "text": "## 一个一秒钟实验：为什么 Sleep 没有占满 CPU 图"
          },
          {
            "line": 62,
            "text": "### 同一个进程，CPU 采集只能有一个负责人"
          },
          {
            "line": 76,
            "text": "## 内存图的两个视角：现在留下什么，过去分配过什么"
          },
          {
            "line": 113,
            "text": "## 锁等待：谁在等，谁让别人等"
          },
          {
            "line": 156,
            "text": "## 从本地文件到线上常驻：需要补齐哪些环节"
          },
          {
            "line": 185,
            "text": "### 版本归因：先确认样本属于谁"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "接口下午两点变慢，等到有人开始抓 CPU profile，流量已经降了下来。这时得到的火焰图可能完全正常。持续剖析要解决的是这个时间差：平时就按受控的频率留下性能样本，发生问题后，能按服务、版本和时间窗口找回当时的调用栈。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "读完这篇，你应该能做三件事：根据症状选对 profile；读懂 Go 的 CPU、内存和锁竞争报告；为线上采集设计版本标记、开销预算和安全边界。最后提供一个完全本地、有时间和内存上限的小实验。它在 Go 1.27.2、Linux/amd64 上实际运行过，没有连接 Pyroscope 或任何线上服务，也不代表已经验证了一套生产持续剖析系统。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "560cc18e3e8a45cc051b8bb6e9e53805308ed0e1fc94c38847fcb1b0b117ce9d",
      "source_line_count": 531,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-095",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/13_可观测性/故障复盘.md",
      "title": "故障复盘：把当时发生的事，变成可以验证的改进",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "事故证据、决策条件和改进验收",
      "recommended_medium": "时间线模板与复盘案例",
      "reason": "核心是事实核对和人的判断，互动控件没有比清晰叙述更强的解释力。",
      "reuse_family": "incident-review",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 9,
            "text": "## 恢复服务与解释事故，是两个不同阶段"
          },
          {
            "line": 22,
            "text": "## 无指责，是为了还原决策条件"
          },
          {
            "line": 37,
            "text": "## 先把影响和证据固定下来"
          },
          {
            "line": 43,
            "text": "### 时间线至少区分“发生时间”和“知道时间”"
          },
          {
            "line": 72,
            "text": "## 根因分析：把触发因素与促成条件拆开"
          },
          {
            "line": 100,
            "text": "## 改进项要改到一个具体条件"
          },
          {
            "line": 115,
            "text": "### 用一个小检查器暴露遗漏"
          },
          {
            "line": 140,
            "text": "## 复盘会怎样开，之后怎样才算结束"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "“配置写错了，已经回滚，后续加强检查。”这几句话可以结束一次讨论，却很难防止下一次故障。它没有说明错误配置为什么能进入生产、为什么没有提前发现、什么信号帮助了恢复，也没有告诉别人“加强检查”什么时候才算完成。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "故障复盘要留下两样东西：一份经得起核对的事件解释，以及一组有人接手、能够验收的改进。前者减少下次排障时重新猜测的成本，后者改变使事故发生或扩大影响的条件。本文不复述任何真实用户事故；贯穿全文的连接池案例和 JSON 数据都明确是合成教学材料。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "78f1fbfb28b9631097640761d65e8cf30a296ab145d185b36f4337ede4ead9c1",
      "source_line_count": 356,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-096",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/14_性能与调试/Go trace.md",
      "title": "Go trace：把“请求在等什么”放回时间线上",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "G 等待、就绪与实际运行",
      "recommended_medium": "带事件说明的教学 trace 时间线",
      "reason": "移动光标区分阻塞等待和调度等待，复用 GMP 概念；明确数据为教学构造。",
      "reuse_family": "go-trace",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 三种“慢”，需要看三段不同的时间"
          },
          {
            "line": 31,
            "text": "## 给一次本地任务加上可读的名字"
          },
          {
            "line": 73,
            "text": "## 从原始事件拆出两段等待"
          },
          {
            "line": 108,
            "text": "## 用汇总找入口，用时间线判断先后"
          },
          {
            "line": 142,
            "text": "## GC 长，不等于整个 GC 都在暂停服务"
          },
          {
            "line": 155,
            "text": "## 采集窗口、数据量和隐私一起控制"
          },
          {
            "line": 163,
            "text": "## 验证范围"
          },
          {
            "line": 167,
            "text": "## 依据"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "CPU profile 能告诉你哪些函数消耗了计算资源，却不能独自解释一个只算了很短时间、响应却慢了几十毫秒的请求。Go execution trace 记录运行时事件：goroutine 何时创建、阻塞、被唤醒、真正开始运行，以及 GC、系统调用、处理器状态怎样变化。把这些事件按时间排列，才能区分“还没等到结果”和“结果到了，但还没轮到它运行”。[1]",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文在 Go 1.27.2、Linux/amd64 上生成并解析一份真实的短 trace。例子只在本地睡眠 20 ms、做少量计算、执行一次受控 GC。它用来学习证据的读法，不代表生产系统的延迟构成。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "af62227f819eb1b9f4f49ca57e99458d13a8233145c7950285a69335435e0c7c",
      "source_line_count": 323,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-097",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/14_性能与调试/Go 典型故障排查.md",
      "title": "Go 典型故障排查：从八种症状找到下一份证据",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "根据症状查下一份证据",
      "recommended_medium": "诊断树和真实复現入口",
      "reason": "故障排查应联系真实指标与调用链，不能靠模拟的症状选择器替代取证。",
      "reuse_family": "operations-evidence",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 第一轮先缩小范围，不同时打开所有诊断开关"
          },
          {
            "line": 32,
            "text": "## 1. CPU 飙高：先确定 CPU 是谁用掉的"
          },
          {
            "line": 54,
            "text": "## 2. 内存泄漏 / 内存不降：先分清哪一种内存"
          },
          {
            "line": 77,
            "text": "## 3. goroutine 泄漏：查的是生命周期，不只是数量"
          },
          {
            "line": 100,
            "text": "## 4. 死锁：沿“等谁”画环"
          },
          {
            "line": 123,
            "text": "## 5. GC 压力过大：把分配速率和存活量分开看"
          },
          {
            "line": 137,
            "text": "## 6. 连接池耗尽：先看连接借走后去了哪里"
          },
          {
            "line": 154,
            "text": "## 7. 慢查询：客户端“慢”要拆成几段"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "CPU 高、内存涨、请求超时，常常会同时出现。它们未必是三个故障：慢查询可以占住连接，连接池排队又会积累 goroutine；请求重试再把 CPU 与内存推高。如果分别看到一个指标就改一个参数，很容易把问题放大。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "排查先保留一条共同时间线：哪个版本、哪些实例、什么请求比例，在什么时候开始变坏。再区分执行、等待和资源保留，找能否证伪当前猜测的证据。本文把八类故障逐项展开，并提供可在五秒超时保护下运行的本地实验。环境是 Go 1.27.2、Linux/amd64；没有压测公共服务，没有操作用户生产进程。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "c44b7884610b72f40b058c3ae3ee622884001bd1b37523d26918e706925f1005",
      "source_line_count": 540,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-098",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/14_性能与调试/Go 基准测试.md",
      "title": "Go 基准测试：让“更快了”有可以复核的含义",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "重复基准与统计比较",
      "recommended_medium": "原始测量、benchstat 与实验条件",
      "reason": "不补造测量；已有运行记录和反例是主证据，静态图可作为可选附件。",
      "reuse_family": "benchmark-evidence",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 从一个“操作”开始，而不是从计时器开始"
          },
          {
            "line": 41,
            "text": "## testing.B 的常用动作"
          },
          {
            "line": 57,
            "text": "## 基准能通过，也可能什么都没测"
          },
          {
            "line": 88,
            "text": "## 前后比较，先把其他变量固定"
          },
          {
            "line": 118,
            "text": "## benchstat 输出究竟允许说到哪一步"
          },
          {
            "line": 136,
            "text": "## 依据"
          },
          {
            "line": 143,
            "text": "## 完整复现代码、命令与原始测量"
          },
          {
            "line": 156,
            "text": "### go.mod"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "把一次 `ns/op` 变小叫优化，容易把机器噪声、输入变化甚至编译器删掉代码误认成收益。一份可靠的微基准至少要说清：测了什么操作，输入是否相同，结果有没有被算出来，前后各跑了多少次，以及变化有多大。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文把同一个字符串拼接函数编译成两个版本，交错运行 12 组，再用固定版本 benchstat 做比较。除此之外，还运行一个故意写错的基准，看编译器如何把被测乘法全部消掉。环境是 Go 1.27.2、Linux/amd64；这些数字只描述本地实验，不外推服务器容量。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "ae86a097ed170de0887658709f7dc86ddad5e78e913f5f78feece995f4448227",
      "source_line_count": 300,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-099",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/14_性能与调试/Go 常见优化点.md",
      "title": "Go 常见优化点：把成本降下来，也把语义保住",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "保留语义的性能优化",
      "recommended_medium": "前后代码、测试与测量",
      "reason": "优化是否值得取决于热点和真实成本，避免给滑块设置虚构加速比。",
      "reuse_family": "benchmark-evidence",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先选目标，再看哪个改动值得做"
          },
          {
            "line": 35,
            "text": "## 预分配：先知道规模，也知道返回值该是什么"
          },
          {
            "line": 71,
            "text": "## 减少逃逸：检查原因，不追求“全都上栈”"
          },
          {
            "line": 93,
            "text": "## sync.Pool：借用期间只有一个所有者"
          },
          {
            "line": 124,
            "text": "## strings.Builder：减少中间字符串，别复制已使用的 Builder"
          },
          {
            "line": 143,
            "text": "## 锁竞争与批处理：少拿几次锁，改变的可能还有可见性"
          },
          {
            "line": 163,
            "text": "## 反射与 JSON：先看热点，再看兼容性"
          },
          {
            "line": 195,
            "text": "## PGO：把代表性 profile 变成构建输入"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "优化最常见的失误，是还不知道哪个成本占主导，就开始换容器、加对象池或替换 JSON 库。一次修改也许让微基准好看，却留下更大的常驻内存、更长的排队时间或不同的协议行为。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这篇按证据选择改法：分配多时检查容量与逃逸，字符串中间对象多时用 Builder，等待多时检查锁与批量边界，反射和 JSON 真在热路径上时再评估替代方案，最后用 PGO 把代表性 CPU profile 交回编译器。例子均在 Go 1.27.2、Linux/amd64 运行；没有对线上进程做采样或压测。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "3e8f067943d38b8e725df65ec8b0fdcb8f461ebe68f79f7795dd2a13c9a2b2f6",
      "source_line_count": 561,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-100",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/14_性能与调试/Go 性能诊断工具选型.md",
      "title": "Go 性能诊断工具选型：六个工具，六种不同的问题",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "诊断工具各能回答的问题",
      "recommended_medium": "问题/证据对照表",
      "reason": "工具选型以问题和取证开销为中心，交互不会代替实际 profile。",
      "reuse_family": "tool-selection",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 用问题选入口"
          },
          {
            "line": 37,
            "text": "## 本文固定的版本"
          },
          {
            "line": 49,
            "text": "## pprof：强项是资源归因，先选 sample type"
          },
          {
            "line": 69,
            "text": "## fgprof：把不占 CPU 的等待也带进来"
          },
          {
            "line": 102,
            "text": "### fgprof 的代价随 goroutine 和栈增长"
          },
          {
            "line": 117,
            "text": "## go tool trace：需要时间与因果时再用"
          },
          {
            "line": 132,
            "text": "## goleak：把“关闭之后该退出”写成测试约束"
          },
          {
            "line": 162,
            "text": "## benchstat：比较的是重复测量，不会修复错误实验"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "“给我一张火焰图”还不是一个足够明确的诊断请求。CPU 的火焰图、fgprof 的墙钟采样图和 heap 的分配图，宽度表示的东西不同；一段耗时很长的等待，在 CPU 图里可能完全不宽。选工具前，先写下要证实的判断：哪里耗 CPU、哪里等待、谁没退出、这次改动是否更快，还是某个变量为什么错了。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文逐一说明 pprof、benchstat、dlv、fgprof、goleak 与 go tool trace，并用同一份本地工作负载比较 CPU 与墙钟采样。所有测量都限制在本地短命进程，没有生产压测、远程 attach 或公开诊断服务。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "68db37c902ff0ba82376328a757c1354443a97f677213309153a50bd34fa9057",
      "source_line_count": 502,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-101",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/14_性能与调试/Go 调试.md",
      "title": "Go 调试：断点看状态，GODEBUG 看运行时，core 留住现场",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "断点、core 与运行时日志",
      "recommended_medium": "命令和真实输出注释",
      "reason": "程序状态需真实调试器核验，浏览器假终端会造成不必要的混淆。",
      "reuse_family": "debugging-evidence",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先决定要观察什么，以及能否暂停"
          },
          {
            "line": 29,
            "text": "## 调试构建：让源码和机器码更容易对应"
          },
          {
            "line": 54,
            "text": "## Delve 的最小断点路线"
          },
          {
            "line": 94,
            "text": "## core：保存的是内存现场，不是一个可以重放的录像"
          },
          {
            "line": 110,
            "text": "### 能否生成 core，还取决于 OS 配置"
          },
          {
            "line": 116,
            "text": "## GODEBUG：有些开关只是观测，有些会改变行为"
          },
          {
            "line": 133,
            "text": "## 逐段读一条真实 gctrace"
          },
          {
            "line": 163,
            "text": "## GOTRACEBACK：先决定崩溃日志要多详细"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "一个变量为什么变成这个值，和 CPU 为什么变高，是不同的问题。前者常需要在某一行暂停程序，后者通常先看 profile。程序已经崩溃时，现场只剩下一份日志或 core，又是第三种情况。选对入口，才能避免一上来就暂停正常服务，或对着一份采样图找它根本没记录的变量。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文使用 Go 1.27.2、Linux/amd64 与 Delve v1.27.2。实际完成了普通/调试构建、符号检查、GODEBUG 输出、测试、race、vet，以及 Delve 版本和帮助核对。当前环境不允许 ptrace，因此没有运行断点、live attach 或 core 调试，也没有尝试绕过限制。相应命令明确作为已核对官方文档的操作教学，不伪装成会话实录。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "eaca5364770606038b5075d82eb11956193292123f850ae1d30ac836f2b90151",
      "source_line_count": 309,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-102",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/14_性能与调试/pprof.md",
      "title": "pprof：先选对“在量什么”，再找热点",
      "priority": "P2",
      "treatment": "static",
      "concept": "profile 类型、flat/cum 和宽度",
      "recommended_medium": "样本树与累计归因图",
      "reason": "静态小树能解释归因关系；不将火焰图横轴画成时间线。",
      "reuse_family": "profiling-evidence",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 七个入口，各自回答什么"
          },
          {
            "line": 35,
            "text": "## 让“留着的”和“用完的”出现在不同视图"
          },
          {
            "line": 75,
            "text": "## CPU 的 flat、cum 怎样看"
          },
          {
            "line": 98,
            "text": "### 火焰图的宽度不是执行时间线"
          },
          {
            "line": 104,
            "text": "## block 看等的人，mutex 看让人等的临界区"
          },
          {
            "line": 132,
            "text": "## 在线使用：调试端口要有自己的边界"
          },
          {
            "line": 169,
            "text": "## 本次验证与边界"
          },
          {
            "line": 175,
            "text": "## 依据"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "服务变慢时，先回答一个问题：时间花在执行代码，还是花在等待？CPU profile 只统计前一种。内存上升时，也先分清当前还留着多少对象，以及过去一共分配过多少。选错剖析类型，图可以很漂亮，结论仍然会错。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这篇用一个不到一秒的本地程序采集 profile，解释怎样把采样结果还原成可检验的假设。实测环境是 Go 1.27.2、Linux/amd64。所有请求测试都在 `httptest` 中运行，没有连接生产服务，也没有向公网暴露端口。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "5ee95312cbc87429ce13c6b5da6416de32c938ae018377cd1fc6603aebb81bc8",
      "source_line_count": 394,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-103",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/15_部署与云原生/Go CI-CD 与 GitOps.md",
      "title": "Go CI-CD 与 GitOps",
      "priority": "P2",
      "treatment": "static",
      "concept": "CI、制品晋级与 GitOps 纠偏",
      "recommended_medium": "制品身份贯穿的交付流程图",
      "reason": "持续协调与单次流水线需分清，现有本地验证比动画更重要。",
      "reuse_family": "artifact-lineage",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## CI、持续交付、持续部署，分别完成到哪一步"
          },
          {
            "line": 34,
            "text": "## 流水线按反馈速度和权限拆开"
          },
          {
            "line": 61,
            "text": "## 制品身份：tag 便于人读，摘要便于核对内容"
          },
          {
            "line": 91,
            "text": "## 签名、SBOM、provenance 各解决哪一个问题"
          },
          {
            "line": 105,
            "text": "## 环境晋级：换环境配置，不重新制作程序"
          },
          {
            "line": 122,
            "text": "## GitOps 真正多出来的是持续纠偏"
          },
          {
            "line": 150,
            "text": "## 权限越界往往发生在“顺手”加的一步"
          },
          {
            "line": 167,
            "text": "## 已运行的内容与部署前的剩余工作"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "“测试通过了”回答的是某次检查有没有发现问题；“生产跑的是哪一份程序”回答的是制品身份；“生产是否已经达到期望状态”还要看部署与健康。一个可靠的流水线需要把这三件事接上。本文从一次 Go 代码提交出发，解释如何验证、生成制品、晋级环境，再由 GitOps 控制器持续协调。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本次在 Go 1.27.2、Linux/amd64 上运行了完整本地构建、test、race、vet、校验和与篡改反例。GitHub Actions YAML 只做本地解析和少量安全约束检查，没有在 GitHub 触发工作流，也没有上传制品、签名、推镜像或连接集群。文末给出完整示例文件与未执行边界。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "09d20418174a3909ecccb86863dcbd982a0af6f7b89f4b0cd13b3d2aba9782ad",
      "source_line_count": 353,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-104",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/15_部署与云原生/Go 云原生工具选型.md",
      "title": "Go 云原生工具选型",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "云原生构建、打包与控制器选型",
      "recommended_medium": "责任分层表",
      "reason": "工具处于不同生命周期，保留选型理由和权限边界即可。",
      "reuse_family": "tool-selection",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先找自己缺的是哪一层"
          },
          {
            "line": 38,
            "text": "## Docker 和 ko：构建自由度与项目约定的取舍"
          },
          {
            "line": 55,
            "text": "## GitHub Actions 负责一次流程，Kubernetes 负责持续运行"
          },
          {
            "line": 65,
            "text": "## Helm 和 Argo CD：打包与长期所有权不要弄反"
          },
          {
            "line": 81,
            "text": "## client-go：想写集群工具，先从普通客户端开始"
          },
          {
            "line": 110,
            "text": "## controller-runtime：当你确实要长期协调资源"
          },
          {
            "line": 148,
            "text": "### 缓存和并发是控制器容易遗漏的成本"
          },
          {
            "line": 156,
            "text": "## RBAC 要和缓存范围、动作范围一起收窄"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "Docker、Kubernetes、Helm、Argo CD、GitHub Actions、ko、client-go、controller-runtime 经常出现在同一张架构图上，但它们没有在争夺同一份工作。真正需要决定的是：谁构建程序，谁保存部署期望，谁持续修复偏差，以及有没有必要自己写一个控制器。本篇按这些责任选工具，而不是建议一个 Go 服务把八个都装上。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "实际验证使用 Go 1.27.2、Linux/amd64，以及固定的 client-go v0.37.1、controller-runtime v0.25.2。文末给出真实客户端库 fake 实验、完整依赖锁定和 RBAC 配置。fake 在内存中运行，不读取 kubeconfig，也不连接集群；其通过不等于 Kubernetes 部署、权限或控制器运行已经验证。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "afaf807dc70c5fc4e0693388a4d14763be8391a87b1dda4b095338f8e3c1597a",
      "source_line_count": 649,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-105",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/15_部署与云原生/Go 在容器中.md",
      "title": "Go 在容器中",
      "priority": "P2",
      "treatment": "static",
      "concept": "CPU 配额、P 数、内存软限与退出",
      "recommended_medium": "三类资源限制对照图",
      "reason": "容量关系高度依赖运行时与内核，复用 GMP 入口但不模拟真实容器节流。",
      "reuse_family": "container-resources",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先把四个限制放到各自的位置"
          },
          {
            "line": 29,
            "text": "## CPU 配额限制的是吞吐，不是并行槽位"
          },
          {
            "line": 35,
            "text": "### Go 1.27.2 的默认值已经会看容器，但有兼容开关"
          },
          {
            "line": 82,
            "text": "## GOMEMLIMIT 能让 GC 更努力，不能回收还在使用的数据"
          },
          {
            "line": 117,
            "text": "## 优雅退出是一段有时限的协议"
          },
          {
            "line": 170,
            "text": "### HTTP 之外的工作不能交给 Shutdown 猜"
          },
          {
            "line": 182,
            "text": "## CGO_ENABLED=0：先确认项目有没有纯 Go 的构建路径"
          },
          {
            "line": 199,
            "text": "## 本次验证的边界"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "Go 程序进入容器后，最容易暴露的三个问题是：CPU 明明只分了一点，运行时却开了很多并行执行槽；内存 limit 已设，进程仍被 OOM 杀死；发布时收到终止信号，正在处理的请求被一起切断。它们分别涉及内核配额、Go 运行时预算和应用生命周期，不能靠一个环境变量全部解决。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文以实际运行的 Go 1.27.2、Linux/amd64 为准。所有 Go 示例、测试、race、vet 和子进程 SIGTERM 实验均已执行；没有创建容器、修改 cgroup 配额或部署 Kubernetes。CPU 动态限额与容器 PID 1 的说明来自对应官方文档及本地 Go 1.27.2 源码，不把普通进程实验包装成集群验证。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "0dd61dfac9ffee9d03f99b91b2c9c95015ca09a27a6d6cbbd2ac40001d75fbed",
      "source_line_count": 492,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-106",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/15_部署与云原生/Kubernetes.md",
      "title": "Kubernetes",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "声明状态与控制器协调",
      "recommended_medium": "受限 Deployment/Pod 状态演示",
      "reason": "删除一个教学 Pod 后逐步补齐期望副本，解释 desired 不等于 ready；不冒充真实集群。",
      "reuse_family": "reconciliation",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## “声明三个副本”以后，究竟是谁动手"
          },
          {
            "line": 30,
            "text": "## Pod 是共同生活的单位，Deployment 才是可替换副本的管理者"
          },
          {
            "line": 65,
            "text": "### StatefulSet 带来的是稳定身份，不是现成数据库"
          },
          {
            "line": 71,
            "text": "## 请求怎样找到会变化的 Pod"
          },
          {
            "line": 101,
            "text": "## 配置和秘密，为什么要分开管理"
          },
          {
            "line": 111,
            "text": "## 三种探针，失败后的动作完全不同"
          },
          {
            "line": 125,
            "text": "## requests 影响能否放进去，limits 影响运行时约束"
          },
          {
            "line": 150,
            "text": "## HPA 想扩六个副本，不代表马上就有六个"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "一个 Go 服务做成镜像后，还需要回答几件事：进程崩了谁重启，机器坏了谁补副本，新版本怎样逐步接流量，容量不够时该扩 Pod 还是扩机器。Kubernetes 把这些事情拆成可以独立观察、反复协调的 API 对象。学它时最有用的顺序，是顺着一次请求和一次发布，把这些对象接起来。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文覆盖 Pod、Deployment、StatefulSet、Service、Ingress 与 Gateway API，接着讲配置、探针、资源、HPA、滚动更新，最后补上调度、网络和 Operator。官方当前文档与发布页在 2026-10-13 显示 1.37 系列；正文使用成熟 API，不把新特性开关视为所有集群默认配置。本次没有 Kubernetes 集群或 kubectl：实际执行的是 Python 3.12.14、PyYAML 6.0.3 的本地清单解析、标签检查和数值模型。它们不能证明 API 接受、调度成功或真实网络可达。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "1e1722e57df4a015dcf48cb012ddc3d117e012c91f8023a52f57be568add942e",
      "source_line_count": 471,
      "existing_static_mermaid_count": 4,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-107",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/15_部署与云原生/后端云服务.md",
      "title": "后端云服务",
      "priority": "P2",
      "treatment": "static",
      "concept": "请求路径、故障域与云服务责任",
      "recommended_medium": "请求链及区域/可用区拓扑图",
      "reason": "云服务选型需要业务条件，静态责任与故障域图更清楚。",
      "reuse_family": "cloud-boundaries",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 11,
            "text": "## 一次请求，会经过哪些服务"
          },
          {
            "line": 61,
            "text": "## 先看故障域：区域、可用区与复制不是同一个概念"
          },
          {
            "line": 78,
            "text": "## 计算：扩的是整个服务，还是只有 CPU"
          },
          {
            "line": 94,
            "text": "### 扩容会放大下游连接数"
          },
          {
            "line": 114,
            "text": "## VPC 和负载均衡：能连通与有权限，要分开检查"
          },
          {
            "line": 118,
            "text": "### 子网的“公有 / 私有”来自路由与地址条件"
          },
          {
            "line": 134,
            "text": "### 负载均衡把请求送过去，不会替应用完成事务"
          },
          {
            "line": 146,
            "text": "## 存储：先确认访问语义，再看容量和价格"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "把 Go 程序搬到云服务器上，只解决了“在哪运行”。一个能长期对外提供服务的后端，还要回答：请求怎样进来，程序怎样访问数据库和文件，谁能调用这些接口，一个机房出问题后还能不能工作，以及流量突然上涨时哪里先撑不住。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "计算、VPC、负载均衡、存储、托管数据库、IAM、函数计算，就是沿这条请求链承担不同工作的组件。学习它们，比背产品名更重要的是找出边界：云厂商接手了哪些工作，应用仍然需要负责哪些事情。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "59aec2461eb8a3922267b1c526418a1c611ff9c14c667c2b2111d6692b1c1348",
      "source_line_count": 846,
      "existing_static_mermaid_count": 4,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-108",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/15_部署与云原生/基础设施即代码.md",
      "title": "基础设施即代码",
      "priority": "P2",
      "treatment": "static",
      "concept": "配置、state、计划与实际资源",
      "recommended_medium": "四层状态对照图",
      "reason": "plan 与 apply 的区别应清楚标注，避免可点击销毁一类拟真操作增加误解。",
      "reuse_family": "iac-state",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 9,
            "text": "## 先认清三种工作：管理资源、管理发布、加工清单"
          },
          {
            "line": 38,
            "text": "## Terraform / OpenTofu：先知道对象是谁，再决定怎么改"
          },
          {
            "line": 40,
            "text": "### 配置是期望，state 是对象身份，实际系统仍需查询"
          },
          {
            "line": 58,
            "text": "### plan 能预告动作，不等于已经把资源改好了"
          },
          {
            "line": 104,
            "text": "### state 锁解决并发写入，不能锁住整个云平台"
          },
          {
            "line": 128,
            "text": "### 敏感值被遮住，不表示没有写进文件"
          },
          {
            "line": 134,
            "text": "## 漂移、导入、重构与销毁：同样是“有差异”，处理方式不同"
          },
          {
            "line": 152,
            "text": "### “先创建后删除”也含有删除"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "改一台服务器的端口，登录控制台点几下就能完成。困难发生在下一次：生产和测试为什么不一样？是谁把数据库改成了单可用区？重建环境时，哪些步骤必须重复，哪些资源绝不能重建？基础设施即代码（Infrastructure as Code，IaC）要把这些决定写成可审查的配置，让工具比较配置与已有资源，再按明确的计划修改它们。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这篇文章沿一条交付链讲 Terraform / OpenTofu、Helm 和 Kustomize：先创建基础设施，再生成 Kubernetes 对象，最后由集群运行应用。读完应能分清谁负责哪层状态，读懂创建、修改、替换和删除的含义，并知道本地检查通过后还缺哪些上线证据。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "7cdb09e2fb7821340bb7d23f73879903d3ce626a1ca2d86fa79ca63e3911681c",
      "source_line_count": 757,
      "existing_static_mermaid_count": 3,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-109",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/15_部署与云原生/容器与 Docker.md",
      "title": "容器与 Docker",
      "priority": "P2",
      "treatment": "static",
      "concept": "镜像分层与容器隔离",
      "recommended_medium": "构建层和运行资源分层图",
      "reason": "静态文件层覆盖图即可解释删文件不等于移除旧层内容。",
      "reuse_family": "image-layers",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先分清四个对象"
          },
          {
            "line": 32,
            "text": "## 一个普通进程，怎样获得“自己的环境”"
          },
          {
            "line": 49,
            "text": "## 镜像分层：删除文件为什么不等于删除旧内容"
          },
          {
            "line": 80,
            "text": "## 多阶段构建：最终运行时不需要编译器"
          },
          {
            "line": 119,
            "text": "## scratch、distroless 和普通发行版怎么选"
          },
          {
            "line": 136,
            "text": "## 缓存应该节省什么，不能偷偷改变什么"
          },
          {
            "line": 152,
            "text": "## 验证记录与尚未验证的部分"
          },
          {
            "line": 158,
            "text": "## 依据"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "把 Go 程序编成 Linux 可执行文件，再放进镜像，只解决了部署的一半。另一半是：这个进程能看见哪些文件、能用多少资源、以谁的身份运行，以及出了问题怎样检查。本篇沿着“构建一个镜像，再启动一个进程”解释 Docker，最后给出可以自行复现的多阶段构建配置。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本次实际运行环境为 Go 1.27.2、Linux/amd64。完成了 Go 测试、race、vet、静态二进制检查及分层删除的教学实验；环境没有 Docker daemon，所以没有声称镜像已经构建、运行，或测出镜像大小。Docker、BuildKit 的配置以 2026-10-13 查阅的官方文档为依据，部署前仍须核对所用版本。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "28dfbb67c49b15a9e129e5eee8c200ffc3a42c9811b3bc890aefed2f7085d3f8",
      "source_line_count": 365,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-110",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/15_部署与云原生/网关与代理.md",
      "title": "网关与代理",
      "priority": "P2",
      "treatment": "static",
      "concept": "代理双连接、缓冲和超时",
      "recommended_medium": "两跳请求/响应时序图",
      "reason": "代理责任随配置改变，保留真实回环测试和限制，图只解释边界。",
      "reuse_family": "proxy-lifecycle",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 一次代理转发有两段连接"
          },
          {
            "line": 30,
            "text": "## 三种工具各把复杂性放在哪里"
          },
          {
            "line": 42,
            "text": "## TLS 终结只保护到终结点"
          },
          {
            "line": 57,
            "text": "## X-Forwarded-For 是一条声明，先问是谁写的"
          },
          {
            "line": 90,
            "text": "## 超时要按一跳一跳的预算设计"
          },
          {
            "line": 108,
            "text": "## 缓冲会改变资源消耗，也会改变用户看到的时间"
          },
          {
            "line": 124,
            "text": "## 限流先定身份和范围，再选算法"
          },
          {
            "line": 144,
            "text": "## 三份配置怎样读，怎样验证"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "Go 服务前面放一个代理，通常是为了统一入口、证书、路由和流量保护。但多一跳也会改变客户端地址、超时、缓冲和连接生命周期。服务在本地正常，接到代理后流式输出变成“一口气到达”，或者限流把所有人当成同一个 IP，常常就是这些边界没有对齐。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本篇先沿着请求走一遍，再比较 Nginx、Envoy、Caddy 的机制和选择条件。Go 1.27.2、Linux/amd64 的代理头清理、上游超时、并发令牌桶实验已实际运行；三种产品配置提供完整文件，但没有运行对应服务或配置校验器。2026-10-13 查阅官方发布页时，Nginx stable 为 1.30.5、mainline 为 1.31.6，Envoy release 为 1.39.4，Caddy release 为 2.11.7。下文不会把“latest 文档”当成已安装版本。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "db6e595e575bf4374d80505b5eba7bc35346d97eaf63bc76bc396841f51f963b",
      "source_line_count": 461,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-111",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/16_系统设计/Go 源码阅读方法.md",
      "title": "Go 源码阅读方法",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "以问题追踪源码责任",
      "recommended_medium": "问题导向阅读路线",
      "reason": "学习目标是阅读真实固定源码和收尾路径，不需再套动画层。",
      "reuse_family": "source-reading",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 9,
            "text": "## 先把问题、版本和入口固定下来"
          },
          {
            "line": 37,
            "text": "## 一次只追一条路径，但要追到责任结束"
          },
          {
            "line": 55,
            "text": "## net/http：`Do` 返回以后，请求还没有全部结束"
          },
          {
            "line": 57,
            "text": "### 先走客户端，再找连接归还的位置"
          },
          {
            "line": 99,
            "text": "### 服务端的取消由谁触发？"
          },
          {
            "line": 107,
            "text": "## sync：从 `Once` 读懂“抢到资格”与“工作完成”的区别"
          },
          {
            "line": 149,
            "text": "### 看懂一个类型后，怎样扩展到整个 sync？"
          },
          {
            "line": 160,
            "text": "## context：取消传播树里，谁保留谁，谁通知谁？"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "读完一个函数，却说不清它为什么要加锁；跟到网络写入，就不知道错误怎样回到调用方；看见 `Propose` 返回 nil，就以为 Raft 已经保存了数据。这些问题通常不靠多翻几个文件解决。需要沿着一个具体问题，把调用、状态和责任接起来。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这篇文章带着七个问题读七份源码：HTTP 连接何时复用、`Once` 为什么不能只用 CAS、取消怎样传给子任务、Gin 中间件为什么还在执行、一次 gRPC 调用怎样结束、Raft 为什么把写盘交给应用、go-redis 的命令何时真正发出。每条路线都给出固定版本、关键入口、需要看的分支和最小验证。目标是能独立解释一个行为，并知道这份解释的边界。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "fe4a4f714cbb75fb1576f460d7d5911a400690feddbcf64197873143d7527ae1",
      "source_line_count": 1153,
      "existing_static_mermaid_count": 7,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-112",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/16_系统设计/架构演进.md",
      "title": "架构演进：每拆开一个边界，都要重新回答谁负责提交和恢复",
      "priority": "P2",
      "treatment": "static",
      "concept": "架构边界、事件与投影恢复",
      "recommended_medium": "权威事实/派生视图数据图",
      "reason": "拓扑能帮助比较演进，但仍应保留业务约束与失败处理的完整叙述。",
      "reuse_family": "architecture-boundaries",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先分清这些词在拆什么"
          },
          {
            "line": 21,
            "text": "## 单体的第一步：让模块先能独立理解"
          },
          {
            "line": 48,
            "text": "## 垂直拆分：围绕业务能力，别只把层变成服务"
          },
          {
            "line": 63,
            "text": "## 服务化：进程边界变成失败边界"
          },
          {
            "line": 88,
            "text": "## 事件驱动：发布已经发生的事实"
          },
          {
            "line": 107,
            "text": "## CQRS：写入规则与查询形状可以分别设计"
          },
          {
            "line": 142,
            "text": "## 事件溯源：把变化序列作为权威事实"
          },
          {
            "line": 168,
            "text": "## 投影如何处理重复、缺口和重建"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "任务管理系统刚上线时，一个进程里创建任务、更新状态、发送通知，几个人就能维护。后来报表拖慢了任务列表，通知故障影响创建，两个团队又总要等对方一起发布。此时需要演进，但这三种问题未必需要同一种拆法。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文沿着这个假设系统说明单体、垂直拆分、服务化和事件驱动怎样解决不同问题，再分别解释 CQRS、事件溯源与读写模型分离。主线是数据责任：谁有权修改一项事实，什么必须一起提交，其他人看到旧状态时怎么办，以及迁移失败后怎样退回。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "164f34d8ee3a32c59b778661846ceb815acdb2fea614e47a590cebf43df2c7b7",
      "source_line_count": 621,
      "existing_static_mermaid_count": 4,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-113",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/16_系统设计/系统容量估算.md",
      "title": "系统容量估算：把请求、字节和等待时间算到同一张账上",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "QPS、字节、并发与冗余预算",
      "recommended_medium": "显式单位的容量估算器",
      "reason": "调整假设逐项显示平均/峰值、存储和平均并发，防止把 p99 代入 Little 定律。",
      "reuse_family": "capacity-budget",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先统一单位和统计边界"
          },
          {
            "line": 21,
            "text": "## QPS：平均值给规模，峰值决定接不接得住"
          },
          {
            "line": 35,
            "text": "## 存储：业务字节只是第一层"
          },
          {
            "line": 63,
            "text": "## 带宽：算方向、算大小，再算放大"
          },
          {
            "line": 83,
            "text": "## 延迟和并发：Little 定律用平均数，不用 p99"
          },
          {
            "line": 113,
            "text": "## 常见操作的延迟量级，怎样查才不会背错"
          },
          {
            "line": 138,
            "text": "## 队列、缓存和冗余：容量最容易漏掉的三处"
          },
          {
            "line": 163,
            "text": "## 把估算变成测量计划"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "“每天有一亿次请求”并不能直接回答要几台机器。请求有大有小，会在某些时段集中；一次用户操作可能读十次数据库；一台实例坏掉后，剩下的实例还要接住它的流量。估算要做的是把这些关系写清，让需要实测的数字露出来。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本文沿用一个假设的团队任务服务：10 万日活，每人每天读取 20 次、创建 2 次。先算 QPS、存储、带宽，再把它们和延迟、并发、队列联系起来。所有业务数字都是教学输入；文末计算器能重算。另有一组本机延迟测量，只说明指定环境中观察到的结果。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "3729af2f0e39f0261e6c474bd4617dede66f849c766ccb8297b684fd01f6dbaa",
      "source_line_count": 349,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-114",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/16_系统设计/系统设计方法论.md",
      "title": "系统设计方法论：把需求一步步变成能验证的方案",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "从需求到可验证架构的推理链",
      "recommended_medium": "完整案例和取舍说明",
      "reason": "方法论需要读完论证，交互组件可在引用的具体机制章学习，不拆碎主线。",
      "reuse_family": "design-judgment",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 先问会改变设计的问题"
          },
          {
            "line": 28,
            "text": "## 容量估算：先看哪一维可能限制方案"
          },
          {
            "line": 46,
            "text": "## 接口设计：把失败后的动作也设计进去"
          },
          {
            "line": 60,
            "text": "## 数据模型：先写必须守住的约束"
          },
          {
            "line": 73,
            "text": "## 高层架构：沿一次请求看数据去哪"
          },
          {
            "line": 94,
            "text": "## 深入一个组件：用户没收到响应，任务却已经创建"
          },
          {
            "line": 134,
            "text": "## 瓶颈与扩展：先分清缺的是哪种资源"
          },
          {
            "line": 149,
            "text": "## 取舍说明：留下别人能推翻的理由"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "拿到“设计一个任务管理系统”，直接画网关、缓存、消息队列，很快就能画满一页。难点在于解释：为什么需要它们？少一个会违反哪条要求？数据库提交后网络断了，用户再点一次，任务会不会多一份？",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这篇用一个贯穿始终的假设案例走完设计过程：团队成员创建任务、查看列表、修改状态，任务创建后异步通知负责人。重点是建立一条可检查的推理链：需求决定约束，数量级缩小选项，接口和数据模型表达约束，架构负责在失败时仍守住它们。案例数字是教学假设，不是某个真实产品的流量报告。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "31ca40b0c9d39beb1140f01087ff155419c47b903fc9124532a353e4f2f0ef50",
      "source_line_count": 291,
      "existing_static_mermaid_count": 2,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-115",
      "book": "go",
      "book_title": "Go 后端知识地图",
      "path": "books/go/articles/16_系统设计/系统设计经典题目.md",
      "title": "系统设计经典题目：十二种系统，十二组必须守住的约束",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "十二种系统的业务不变量",
      "recommended_medium": "分题案例和失败检查",
      "reason": "题型广且各自保证不同，应连接幂等、限流等专章互动，避免每题另造浅层动画。",
      "reuse_family": "design-judgment",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 1. 短链系统：先保证映射不会被覆盖"
          },
          {
            "line": 50,
            "text": "## 2. 秒杀／抢购：入口挡流量，库存事务守住不超卖"
          },
          {
            "line": 98,
            "text": "## 3. Feed 流：把合并工作放在写时还是读时"
          },
          {
            "line": 139,
            "text": "## 4. IM 即时通讯：连接在线，不代表消息已可靠送达"
          },
          {
            "line": 187,
            "text": "## 5. 限流器：限制哪种资源，决定算法与故障策略"
          },
          {
            "line": 219,
            "text": "## 6. 分布式 ID：唯一、排序和不可猜不是同一个要求"
          },
          {
            "line": 258,
            "text": "## 7. 延迟任务调度：到点、领走、执行成功是三个状态"
          },
          {
            "line": 294,
            "text": "## 8. 排行榜：先定义同分怎么排，再考虑 Top K"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "短链、秒杀、Feed 和支付系统都可以画成“入口、服务、数据库、缓存、队列”。图相似，不代表难点相同。短链不能因碰撞覆盖旧映射；秒杀不能多卖；聊天要让重连用户找回消息；支付超时后不能擅自认定失败。设计能不能成立，取决于这些约束在并发、重试和故障时是否仍成立。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "下面逐题给出需求边界、最小数据与接口、关键路径、失败恢复和选择理由。它们是教学方案，不是某家公司的完整生产架构。文末有十二个有界本地模型和反例测试，帮助观察关键行为；除秒杀模型外，这些状态机按单线程调用，不声称提供通用并发安全。全部模型都只用内存，不模拟真实数据库、消息集群或支付渠道。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "21f2b6eaf49d85ae1245b0e50ec2ca45808113bcbc3e53670db5abfaa74a74a9",
      "source_line_count": 1024,
      "existing_static_mermaid_count": 9,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-116",
      "book": "seo/book",
      "book_title": "SEO 系统教程",
      "path": "books/seo/book/chapters/01_seo_value.md",
      "title": "第 1 章 SEO 是什么以及它怎样创造商业价值",
      "priority": "P2",
      "treatment": "static",
      "concept": "搜索需求到商业结果的链路",
      "recommended_medium": "带分母的价值路径图",
      "reason": "重点是投入是否值得，静态链路能保留需求、转化和交付成本之间的条件。",
      "reuse_family": "seo-value",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 1.1 先确定你要解决的问题"
          },
          {
            "line": 15,
            "text": "## 1.2 自然搜索与付费搜索怎样分工"
          },
          {
            "line": 34,
            "text": "## 1.3 从需求到利润中间隔着什么"
          },
          {
            "line": 48,
            "text": "## 1.4 五种站点应该怎样定义价值"
          },
          {
            "line": 60,
            "text": "## 1.5 什么时候应该暂缓扩大 SEO"
          },
          {
            "line": 70,
            "text": "## 1.6 用一个小项目检验渠道选择"
          },
          {
            "line": 82,
            "text": "## 1.7 如何识别不可靠的 SEO 承诺"
          },
          {
            "line": 90,
            "text": "## 1.8 搜索正在变化但经营问题仍要回答"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "学完本章，你应当能向一个不了解网站的人解释搜索引擎优化的作用，区分自然搜索与广告，画出从搜索需求到商业结果的路径，并判断当前项目应不应该优先投入 SEO。最重要的能力不是记住缩写，而是在投入时间前回答：谁在什么情况下寻找什么，我能提供什么结果，这个结果为什么值得我持续经营。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "搜索引擎优化，英文 Search Engine Optimization，简称 SEO，是使公开的网站内容更容易被搜索引擎发现、理解，并让合适的人通过搜索找到和使用它的一组工作。Google 的入门文档同时强调搜索引擎对内容的理解，以及用户判断是否访问网站的能力。[A01] 本书进一步采用一个经营视角：SEO 是建立可被搜索发现的有效供给，使原本分散的需求有机会变成访问、使用、信任和交易。这是本书的工作定义，不是搜索引擎公布的排名公式。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "68118350e5e9865e9250a8a393d45c8144307e49337e4972d342e541961e726a",
      "source_line_count": 136,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-117",
      "book": "seo/book",
      "book_title": "SEO 系统教程",
      "path": "books/seo/book/chapters/02_search_pipeline.md",
      "title": "第 2 章 搜索引擎怎样发现和处理一个页面",
      "priority": "P2",
      "treatment": "static",
      "concept": "发现、抓取、渲染、索引与展示",
      "recommended_medium": "状态与证据对照图",
      "reason": "状态之间不保证自动推进；静态图加证据入口比虚构搜索引擎运行更可靠。",
      "reuse_family": "search-pipeline",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 2.1 把搜索过程当作一串有证据的状态"
          },
          {
            "line": 13,
            "text": "## 2.2 发现地址只是得到候选输入"
          },
          {
            "line": 25,
            "text": "## 2.3 抓取是一次受条件约束的请求"
          },
          {
            "line": 39,
            "text": "## 2.4 渲染解释了浏览器与抓取结果为什么会不同"
          },
          {
            "line": 51,
            "text": "## 2.5 索引是一种选择而非网页备份"
          },
          {
            "line": 63,
            "text": "## 2.6 排名和结果展示取决于具体查询"
          },
          {
            "line": 73,
            "text": "## 2.7 用两种时间视角读 URL 检查"
          },
          {
            "line": 85,
            "text": "## 2.8 一次从零开始的完整诊断"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "网站上线后，你可以在自己的浏览器中打开它，但搜索引擎还未必知道这个地址。即使搜索引擎知道地址，也可能尚未访问；访问成功不代表正文被正确理解；正文被理解不代表进入索引；进入索引也不代表在你关心的查询中得到展现。学完本章，你应能把“搜不到”拆成一个具体问题，并知道该找哪一种证据。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "Google 官方把搜索概括为抓取、索引和提供搜索结果三个阶段。[A07] 为便于排错，本书进一步拆出发现、抓取、渲染、索引、匹配与排名、结果展示六个观察节点。这个拆分是教学模型，不意味着 Google 的内部系统按一条严格串行的流水线执行。真实系统可以有队列、缓存、重试和并行处理，同一个页面也会被反复重新访问与评估。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "a3bb3730ee38d89ae4f5037f4c5cb69ca181f64c919cd15587f2c9efb846befe",
      "source_line_count": 151,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-118",
      "book": "seo/book",
      "book_title": "SEO 系统教程",
      "path": "books/seo/book/chapters/03_intent_quality.md",
      "title": "第 3 章 理解搜索意图与结果质量",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "从查询提出意图与质量假设",
      "recommended_medium": "真实页面观察与证据论证",
      "reason": "意图需要情境判断，自动分类互动容易让读者把假设当确定标签。",
      "reuse_family": "seo-judgment",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 3.1 先看用户要完成什么"
          },
          {
            "line": 13,
            "text": "## 3.2 查询的字面信息与缺失信息"
          },
          {
            "line": 23,
            "text": "## 3.3 SERP 是观察对象而不是照抄清单"
          },
          {
            "line": 35,
            "text": "## 3.4 相关性不等于重复查询文字"
          },
          {
            "line": 51,
            "text": "## 3.5 质量和可信度怎样落到页面上"
          },
          {
            "line": 63,
            "text": "## 3.6 证据应当与结论一一对应"
          },
          {
            "line": 73,
            "text": "## 3.7 标题与摘要是承诺的一部分"
          },
          {
            "line": 83,
            "text": "## 3.8 评分员和算法为什么不能混为一谈"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "如果你搜索“JSON 格式化”，可能希望马上粘贴文本并得到结果；搜索“JSON 为什么解析失败”，可能需要定位语法错误；搜索“JSON 与 YAML 的差别”，则可能正在做配置格式的选择。三个查询围绕同一个主题，却需要不同页面。一个人熟悉主题，不等于已经理解每个查询背后的任务。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "本章要训练的能力，是从查询和搜索结果中提出可检验的意图假设，判断一页内容是否相关、是否可信，以及是否真正帮助用户完成任务。我们还会区分搜索引擎公开说明、行业工作方法与尚未证实的算法推测。理解这些边界，可以让你既不过度迷信所谓排名因素，也不把所有结果变化都归为无法理解的黑箱。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "573ff7385600f156d7833ad786d3d4223e79009d588b4eb6cc955e7ce21669dd",
      "source_line_count": 158,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-119",
      "book": "seo/book",
      "book_title": "SEO 系统教程",
      "path": "books/seo/book/chapters/04_strategy_priorities.md",
      "title": "第 4 章 把 SEO 目标变成可执行的项目",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "项目取舍、预算与验收",
      "recommended_medium": "项目说明和路线图",
      "reason": "战略依赖业务事实和人力限制，不宜用未经校准的分数替代讨论。",
      "reuse_family": "seo-judgment",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 4.1 战略首先是一组有理由的取舍"
          },
          {
            "line": 13,
            "text": "## 4.2 用业务事实约束目标"
          },
          {
            "line": 23,
            "text": "## 4.3 区分可观察需求和想象中的市场"
          },
          {
            "line": 35,
            "text": "## 4.4 找到值得坚持的差异"
          },
          {
            "line": 45,
            "text": "## 4.5 为每类页面分配工作"
          },
          {
            "line": 55,
            "text": "## 4.6 按依赖关系安排优先级"
          },
          {
            "line": 65,
            "text": "## 4.7 预算要算维护和失败的成本"
          },
          {
            "line": 77,
            "text": "## 4.8 一份有验收条件的初期路线图"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "前三章回答了搜索为什么有价值、页面怎样被处理，以及什么样的结果可能满足用户。本章开始把这些理解放进现实限制：只有一个开发者怎么办，预算不多怎么办，产品还在变化怎么办，老板要求增长但没有说增长什么怎么办。学完后，你应能写出一页项目说明，建立有依据的工作顺序，并知道什么时候继续、缩小或者调整计划。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "SEO 战略不等于一张关键词表，也不等于“每天发布三篇文章”。它至少需要明确服务谁、解决什么问题、选择哪些页面承担任务、为什么有能力做好、投入多少资源，以及用什么证据判断方向。缺少前面几项，后面的产量计划很容易成为忙碌记录。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "b325c36135d5945ad4d940aac5595229db94aa8239d0e8bbc1eee9a2da499ebc",
      "source_line_count": 144,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-120",
      "book": "seo/book",
      "book_title": "SEO 系统教程",
      "path": "books/seo/book/chapters/05_keyword_discovery.md",
      "title": "第 5 章 关键词发现：从真实问题到可信的数据集",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "关键词候选的数据来源与口径",
      "recommended_medium": "来源记录和研究流程",
      "reason": "交互词表会偏向数量而不是证据，保留查证路径与工具边界更重要。",
      "reuse_family": "keyword-evidence",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 5.1 学习目标：先认识需求，再决定写什么"
          },
          {
            "line": 13,
            "text": "## 5.2 五个维度定义一个可研究的需求"
          },
          {
            "line": 23,
            "text": "## 5.3 建立种子词，避免从单一工具出发"
          },
          {
            "line": 33,
            "text": "## 5.4 用户研究：从原话中提取任务，而不是只采集名词"
          },
          {
            "line": 43,
            "text": "## 5.5 完全不依赖付费订阅的找词路径"
          },
          {
            "line": 59,
            "text": "## 5.6 关键词规划师与商业工具：把门槛和用途讲清楚"
          },
          {
            "line": 69,
            "text": "## 5.7 搜索量究竟测量什么"
          },
          {
            "line": 79,
            "text": "## 5.8 难度分数、点击成本与商业价值的边界"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "本章结束后，你应能独立建立一份可追溯的关键词候选库，解释每个词来自哪里、服务谁、数据代表什么，并且不会把工具给出的数字误当成市场事实。这里的关键词研究（Keyword Research），是研究人们如何表达问题、这些问题值得用什么页面解决的过程，不是下载一个大表之后按搜索量排序。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "搜索词或查询（Query）是用户实际输入的表达；目标关键词（Target Keyword）是站点规划时选定的研究单位；主题（Topic）是多个相关问题所属的知识或任务领域。三者并不一一对应。例如，用户可能分别搜索“json 转 go”“json to golang struct”“根据接口响应生成结构体”，但都在尝试把数据样本变成可用的类型定义。相反，“go json”可能指解析方法、转换工具、标准库文档或报错排查，不宜因为词短就当成一个清晰需求。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "4e6b37afc4a85886193d9ed4d84ac858d39d1111f08fac527178d2cf9922e082",
      "source_line_count": 132,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-121",
      "book": "seo/book",
      "book_title": "SEO 系统教程",
      "path": "books/seo/book/chapters/06_intent_mapping_priority.md",
      "title": "第 6 章 搜索意图、关键词聚类与页面规划",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "SERP 重合、聚类与页面职责",
      "recommended_medium": "小型集合重合查看器",
      "reason": "切换两组结果的共同项可复算重合比例，并明确它只是辅助证据，不是自动合并判决。",
      "reuse_family": "set-overlap",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 6.1 一张词表为什么还不能指导建站"
          },
          {
            "line": 13,
            "text": "## 6.2 意图标签只负责初筛"
          },
          {
            "line": 23,
            "text": "## 6.3 从 Google 搜索结果判断页面应该长什么样"
          },
          {
            "line": 33,
            "text": "## 6.4 聚类先按任务，再用算法扩大规模"
          },
          {
            "line": 43,
            "text": "## 6.5 一个可以手算的结果重合例子"
          },
          {
            "line": 55,
            "text": "## 6.6 为每个分组指定页面职责"
          },
          {
            "line": 69,
            "text": "## 6.7 什么时候合并，什么时候拆开"
          },
          {
            "line": 79,
            "text": "## 6.8 优先级：把预期收益和不确定性同时写进计划"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "上一章得到的是候选需求。真正开始建设页面时，你还要回答三个问题：哪些查询可以用同一个页面解决，哪些必须拆开，以及有限时间先投入哪里。本章将把词表转成可执行的页面地图，重点解释容易出错的边界，而不要求你相信某个软件一键给出的分类。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "继续使用假设中的轻舟开发工具。它收集了“JSON 转 Go”“Go struct generator”“Go JSON 教程”“Go JSON 解析失败”“JSON 转 Go 不上传数据”。如果按词频归类，它们都带有 Go 或 JSON；如果按用户下一步动作归类，却会分出转换、学习、排错和隐私确认。页面规划必须关注后者，否则你可能写出一篇什么都谈一点、却没有让用户完成任何任务的长文章。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "c09dc670e3cb13c447940d50d7e1e71d4391a87815be7007293a46f56a34699f",
      "source_line_count": 138,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-122",
      "book": "seo/book",
      "book_title": "SEO 系统教程",
      "path": "books/seo/book/chapters/07_competitor_serp_research.md",
      "title": "第 7 章 竞对与搜索结果研究：找到可以验证的差距",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "竞对差距的可验证依据",
      "recommended_medium": "样本矩阵和页面阅读记录",
      "reason": "核心是完整完成页面任务并记录差异，互动打分器容易制造排名公式幻觉。",
      "reuse_family": "seo-judgment",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 7.1 先决定和谁比较"
          },
          {
            "line": 13,
            "text": "## 7.2 用需求样本识别搜索竞对"
          },
          {
            "line": 23,
            "text": "## 7.3 写下观察条件，让结果可以复查"
          },
          {
            "line": 33,
            "text": "## 7.4 一次研究要读哪些内容"
          },
          {
            "line": 43,
            "text": "## 7.5 实际公开资料阅读：JSON 转 Go 的候选页面"
          },
          {
            "line": 55,
            "text": "## 7.6 内容差距不只有“别人有、我没有”"
          },
          {
            "line": 65,
            "text": "## 7.7 用矩阵帮助讨论，但不要把打分当排名公式"
          },
          {
            "line": 75,
            "text": "## 7.8 链接差距：研究被引用的理由"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "一家卖开发工具的公司，商业对手可能是另一家软件公司，但在 Google 中与它争夺点击的页面，还可能来自开源项目、官方文档、社区回答、个人博客和免费工具合集。如果只研究熟悉的商业品牌，你会错过真正出现在目标查询里的答案。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "本章把竞争对象分成三类。商业竞对争取相似客户和预算；搜索竞对在相似查询中出现；任务替代品帮助用户绕过原本的产品，例如编辑器插件、本地脚本或人工服务。三类可能重合，也可能完全不同。用户用标准库自己写完转换代码时，没有购买竞品，却仍完成了同一个任务。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "9b69ce749befa24c53e7209f4fa7f9caf4d589aaeb1bb88aa75c76081c777210",
      "source_line_count": 142,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-123",
      "book": "seo/book",
      "book_title": "SEO 系统教程",
      "path": "books/seo/book/chapters/08_long_tail_programmatic_validation.md",
      "title": "第 8 章 长尾需求、程序化 SEO 与机会验证",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "组合空间与允许发布集合",
      "recommended_medium": "资格约束逐层过滤器",
      "reason": "改变有资料、可交付和独特价值条件，显示组合数为何不等于可发布页数。",
      "reuse_family": "eligibility-filter",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 8.1 长尾为什么值得做，又为什么容易做错"
          },
          {
            "line": 13,
            "text": "## 8.2 从主任务寻找有意义的细分条件"
          },
          {
            "line": 23,
            "text": "## 8.3 零搜索量候选怎样验证"
          },
          {
            "line": 33,
            "text": "## 8.4 主题覆盖应沿着用户工作过程展开"
          },
          {
            "line": 43,
            "text": "## 8.5 什么需求适合程序化页面"
          },
          {
            "line": 53,
            "text": "## 8.6 数据模型：把来源、版本和缺失一起存下来"
          },
          {
            "line": 63,
            "text": "## 8.7 完整算例：从组合空间到允许发布的集合"
          },
          {
            "line": 73,
            "text": "## 8.8 模板要解决页面职责，不能只放关键词"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "一个新站很难只靠少数宽泛查询让用户认识自己。更现实的入口可能是一种具体格式转换、一个带版本条件的错误、一类明确规格的商品，或者一个真正能提供服务的地区。这些需求单独看可能不大，但如果它们与产品高度相关，而且能够被可靠地满足，就值得研究。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "长尾关键词（Long-tail Keyword）描述的是需求分布中数量很多、单项需求相对较小的部分，不能简单按词数判断。某个很长的热门产品名称可能需求很高，一个很短的专业型号也可能很少被搜索。中文没有天然的英文空格分词，更不适合用“超过四个词”这种机械标准筛选长尾。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "8f5c9f2e4b77f7c84e01695038e1b0989e43234ad002a317d7004a86dc87c085",
      "source_line_count": 152,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-124",
      "book": "seo/book",
      "book_title": "SEO 系统教程",
      "path": "books/seo/book/chapters/09_site_architecture.md",
      "title": "第 9 章 从域名到内链 建立可以持续生长的网站",
      "priority": "P2",
      "treatment": "static",
      "concept": "页面类型、稳定 URL 与内链",
      "recommended_medium": "站点结构和页面职责图",
      "reason": "结构需要长期维护，静态图足以呈现用户路径，不做无条件的内链评分。",
      "reuse_family": "site-topology",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 5,
            "text": "## 9.1 先定义网站服务的任务"
          },
          {
            "line": 13,
            "text": "### 先画页面类型 再画页面数量"
          },
          {
            "line": 19,
            "text": "## 9.2 域名 是品牌和运维资产"
          },
          {
            "line": 27,
            "text": "### 根域名 子域名 与子目录"
          },
          {
            "line": 35,
            "text": "## 9.3 按维护能力选择 CMS 与框架"
          },
          {
            "line": 43,
            "text": "### 用验收脚本代替销售页面"
          },
          {
            "line": 51,
            "text": "## 9.4 设计 URL 是在设计长期接口"
          },
          {
            "line": 61,
            "text": "### 页面身份与分类身份分开"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "本章的目标是把“做一个网站”变成一套能持续发布、被发现、被理解和被维护的系统。涉及搜索机制的说明以 Google 为主，架构建议还需要通过真实使用来验证。读完后，你应能选择适合自己的建站方式，为页面安排稳定地址，画出用户走得通的栏目结构，并建立不会随着文章增加而失控的内部链接。这里讨论产品和内容层面的架构决策；抓取、渲染、状态码和规范化的协议细节见技术章节。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "一个站点不是网址的集合，而是任务的集合。开发者最容易先选框架，再搭首页，再寻找可以填进去的文章。更可靠的顺序是先确定谁会来、来解决什么、完成后去哪里，以及你能持续提供什么。设想一个名为“舟行”的 Go 日志分析工具，目标用户是维护中小型服务的开发者。它可能需要销售产品、解释日志排错方法、提供安装文档，还需要让已有客户找到故障解决方案。这四件事互相支持，但不能用同一种页面模板处理。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "219e472a96372d180a5852255e1fd162173ba627579d8a0895d803a69bdadaa8",
      "source_line_count": 128,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-125",
      "book": "seo/book",
      "book_title": "SEO 系统教程",
      "path": "books/seo/book/chapters/10_onpage_optimization.md",
      "title": "第 10 章 页面优化 让搜索承诺与访问体验一致",
      "priority": "P2",
      "treatment": "static",
      "concept": "搜索承诺与页面任务一致",
      "recommended_medium": "有注释的页面解剖图",
      "reason": "并排展示标题、正文和行动入口比动画更利于编辑核对。",
      "reuse_family": "page-anatomy",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 5,
            "text": "## 10.1 先写页面任务 再检查标签"
          },
          {
            "line": 13,
            "text": "## 10.2 页面标题与搜索结果标题"
          },
          {
            "line": 19,
            "text": "### 长度是展示约束 不是神秘门槛"
          },
          {
            "line": 27,
            "text": "## 10.3 摘要要帮助选择"
          },
          {
            "line": 37,
            "text": "## 10.4 标题层级让读者看懂论证"
          },
          {
            "line": 47,
            "text": "## 10.5 正文的完整性来自任务覆盖"
          },
          {
            "line": 53,
            "text": "### 用可验证的细节代替形容词"
          },
          {
            "line": 61,
            "text": "## 10.6 图片 视频和代码要承担具体任务"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "一位访客搜索“Go 日志脱敏”，点击结果后发现整页都在介绍企业愿景，没有规则示例，也看不到能试用的工具。即使标题包含搜索词，这个页面仍没有完成任务。本章讨论页面内优化（On-page SEO）：如何安排标题、摘要、正文、媒体和行动入口，让页面准确表达自己，也让访客到达后得到承诺的东西。涉及搜索表现的机制以 Google 官方说明为主，设计方法则需要结合自己的用户验证。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "页面优化应从一句明确的任务说明开始。比如“让第一次接触日志脱敏的 Go 开发者，选择适合自己日志格式的处理方式，并看懂一个完整输入输出示例”。这句话包含人、问题和结果。它能帮助你判断要放什么，也能帮助你删除无关内容。只有“提高日志脱敏关键词排名”无法指导正文，因为它没有说明读者需要完成哪件事。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "68bb2a3e1da60a1604b8c24bf2832314fd35e8eba8e6e9b54061efed21765dc7",
      "source_line_count": 130,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-126",
      "book": "seo/book",
      "book_title": "SEO 系统教程",
      "path": "books/seo/book/chapters/11_content_strategy.md",
      "title": "第 11 章 内容策略 从选题到持续维护",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "内容证据、原创和维护责任",
      "recommended_medium": "brief、核查步骤和案例",
      "reason": "内容判断不能靠视觉控件证明，保留证据与论证完整性。",
      "reuse_family": "editorial-workflow",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 5,
            "text": "## 11.1 把关键词清单变成内容组合"
          },
          {
            "line": 13,
            "text": "### 不同内容承担不同工作"
          },
          {
            "line": 19,
            "text": "## 11.2 用内容说明单减少返工"
          },
          {
            "line": 27,
            "text": "### 一份可执行的编辑流程"
          },
          {
            "line": 33,
            "text": "## 11.3 原创不等于换一种说法"
          },
          {
            "line": 39,
            "text": "### 设计一个小规模研究"
          },
          {
            "line": 47,
            "text": "### 调查与观察的边界"
          },
          {
            "line": 53,
            "text": "## 11.4 可信度要有可以核查的依据"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "内容策略要解决的不是“下周写什么”，而是有限的人力应该长期回答哪些问题，以及如何保证答案可靠。会编程的人常把发布数量当作容易衡量的进度，但代码生成页面的速度可以很快，证据、判断和维护不会同步变得便宜。本章帮助你建立一套内容工作方法：选择主题、准备资料、创作独有信息、核验事实、安排更新，并合理使用 AI。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "关键词研究得到的是需求线索，内容策略决定怎样承接这些需求。一个查询可以由产品页回答，也可以由教程、工具或文档回答；不能看到每个关键词就新建一篇文章。先把需求放回用户的工作过程。开发者可能先意识到日志泄露问题，再寻找处理方法，再比较工具，最后接入产品。每一步需要的证据不同，因此内容形式也应不同。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "0baec16cbf9b76e37c2d913a9ace93cc368c106184fb1079ac6ad9e69e979a43",
      "source_line_count": 135,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-127",
      "book": "seo/book",
      "book_title": "SEO 系统教程",
      "path": "books/seo/book/chapters/12_site_type_playbooks.md",
      "title": "第 12 章 不同站型的 SEO 把通用方法放进真实业务",
      "priority": "P2",
      "treatment": "static",
      "concept": "站型对象与页面生命周期",
      "recommended_medium": "商品/文档/社区的生命周期表",
      "reason": "不同业务的下架、版本和审核条件应并排呈现，避免统一成一个发布按钮。",
      "reuse_family": "page-lifecycle",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 5,
            "text": "## 12.1 电商先建立可信的商品资料"
          },
          {
            "line": 13,
            "text": "### 商品变体如何安排"
          },
          {
            "line": 19,
            "text": "## 12.2 分类页是选择工具"
          },
          {
            "line": 25,
            "text": "### 筛选组合不等于独立落地页"
          },
          {
            "line": 33,
            "text": "### 分页与加载更多"
          },
          {
            "line": 39,
            "text": "## 12.3 库存变化需要页面生命周期"
          },
          {
            "line": 47,
            "text": "### 一个露营商店的处理实例"
          },
          {
            "line": 53,
            "text": "## 12.4 用户评价与社区内容"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "相同的标题和内链原则，放进电商、社区或软件文档，会遇到不同问题。电商要处理变化的库存和商品组合，社区要处理不可预先控制的发言，软件文档要照顾版本差异，视频站则需要让播放器成为真正可观看的内容。本章按业务对象解释如何应用前面的方法。搜索特性以 Google 为主，具体资格会变化；符合要求只代表具备被考虑的条件，不保证出现某种展示。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "商品详情页应让用户判断这是不是他需要的东西。最基本的信息是商品身份、规格、价格、可售状态、图片、配送条件和退换限制。对于规格复杂的商品，还要说明兼容性与不适用情形。一个页面写了很长的品牌故事，却没有壶口直径、充电接口或包装数量，用户仍无法购买。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "282fdcd257c3487dba1852a59ad7e4e2567b1432c9f665403815386606692810",
      "source_line_count": 149,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-128",
      "book": "seo/book",
      "book_title": "SEO 系统教程",
      "path": "books/seo/book/chapters/13_crawling_indexing.md",
      "title": "第 13 章 抓取与索引控制：让正确页面进入正确队列",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "robots、noindex、canonical 与重定向",
      "recommended_medium": "声明组合检查器",
      "reason": "切换少量声明后解释冲突，例如抓取被挡可能看不到 noindex；不承诺实际收录结果。",
      "reuse_family": "indexing-controls",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 13.1 学习目标与控制层次"
          },
          {
            "line": 13,
            "text": "## 13.2 robots.txt：抓取协议，不是保险柜"
          },
          {
            "line": 50,
            "text": "## 13.3 noindex：需要被看见的索引指令"
          },
          {
            "line": 75,
            "text": "### 只限制摘要时，不必排除整页"
          },
          {
            "line": 87,
            "text": "## 13.4 canonical：为重复内容选择代表地址"
          },
          {
            "line": 105,
            "text": "## 13.5 站点地图：把目录写成可维护的数据产品"
          },
          {
            "line": 129,
            "text": "## 13.6 HTTP 状态码要表达真实业务状态"
          },
          {
            "line": 141,
            "text": "## 13.7 抓取预算：先证明存在瓶颈"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "本章要解决的不是“如何强迫搜索引擎收录”，而是如何用可验证的技术设置，表达网站的真实意图。读完后，你应能解释一个地址为什么被发现却未抓取、被抓取却未进入索引，能为商品、文章、站内搜索、登录页面分别制定策略，并能找出 robots.txt、noindex、canonical、重定向和站点地图之间的冲突。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "把搜索入口想象成物流系统会更容易理解：内部链接提供路线，站点地图提供目录，robots.txt 管理哪些门允许进入，HTTP 状态描述这次访问的结果，页面内容与渲染结果构成货物，noindex 表达“不进入目录”的要求，canonical 表达多个近似包装中优先采用哪个地址。类比只帮助理解；这些设置并不是同一个开关，也不存在“每样都加一遍就更保险”的规律。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "f6061ae69aa3f7c2108384c0e16b094659c9350578a2f98db08afe5d8256d3a6",
      "source_line_count": 218,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-129",
      "book": "seo/book",
      "book_title": "SEO 系统教程",
      "path": "books/seo/book/chapters/14_rendering_experience.md",
      "title": "第 14 章 渲染、移动端与页面体验：让内容确实可见、可用",
      "priority": "P2",
      "treatment": "static",
      "concept": "CSR、SSR、SSG 与内容可见时间",
      "recommended_medium": "加载依赖与初始 HTML 对照图",
      "reason": "稳定图能区分内容出现与水合接管，真实性能仍须测量。",
      "reuse_family": "rendering-lifecycle",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 14.1 从“页面能打开”追问到“谁看见了什么”"
          },
          {
            "line": 13,
            "text": "## 14.2 CSR、SSR 与 SSG 分别把工作放在哪里"
          },
          {
            "line": 27,
            "text": "## 14.3 水合与路由：静态外观之后还有一次接管"
          },
          {
            "line": 47,
            "text": "## 14.4 为 Go 工具站设计一份渲染约定"
          },
          {
            "line": 112,
            "text": "## 14.5 怎样检查渲染，而不是猜测渲染"
          },
          {
            "line": 124,
            "text": "## 14.6 移动端优先，检查内容的一致性"
          },
          {
            "line": 134,
            "text": "## 14.7 核心网页指标：把分数变成可定位的问题"
          },
          {
            "line": 144,
            "text": "### LCP：先找最大内容何时才开始加载"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "假设你写了一个 JSON 格式化工具。自己打开时，编辑器、说明文档和示例一应俱全，SEO 检查工具却只提取到导航与页脚。这里最有价值的问题是：正文在什么时刻出现，依赖哪些请求，失败时剩下什么？本章沿着这些问题解释 JavaScript 渲染，再把渲染方案与移动端、性能、可访问性和安全连接起来。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "学习目标是能画出一张页面加载依赖图，判断哪些内容应该放进初始 HTML，能设计有证据的渲染检查，并读懂核心网页指标而不迷信单次分数。对于会编程的读者，代码本身通常不难；难点是把搜索引擎、浏览器、网络和服务器的不同观察角度分开。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "99a0004ba70e20638a61a4707b061241d82728ea04514214253aee6d2928fad0",
      "source_line_count": 212,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-130",
      "book": "seo/book",
      "book_title": "SEO 系统教程",
      "path": "books/seo/book/chapters/15_structured_data.md",
      "title": "第 15 章 结构化数据与富结果：把页面事实准确地交给机器",
      "priority": "P2",
      "treatment": "static",
      "concept": "页面事实与 JSON-LD 同源",
      "recommended_medium": "正文/数据字段映射图",
      "reason": "静态一致性对照足够说明结构化数据，不伪装 Google 富结果资格验证器。",
      "reuse_family": "structured-data",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 15.1 从一段价格文字到一个明确的数据对象"
          },
          {
            "line": 13,
            "text": "## 15.2 先把对象、字段和关系讲清楚"
          },
          {
            "line": 25,
            "text": "## 15.3 一份文章与面包屑的原创示例"
          },
          {
            "line": 91,
            "text": "## 15.4 不同业务该从哪些标注开始"
          },
          {
            "line": 111,
            "text": "## 15.5 功能会变化，模板不能停在旧教程里"
          },
          {
            "line": 121,
            "text": "## 15.6 验证分四层，先确定自己在验证什么"
          },
          {
            "line": 133,
            "text": "## 15.7 用同一份数据生成正文与标注"
          },
          {
            "line": 198,
            "text": "## 15.8 三个常见故障怎样逐步定位"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "页面写着“青禾台灯，二百九十九元，有货”，人通常能够理解哪个是商品、哪个是价格。程序却需要区分这是一件商品、另一篇文章引用的价格，还是已经过期的活动文案。结构化数据 structured data 用明确的类型和字段表达这些事实，帮助机器理解页面描述的对象及其关系。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "本章目标是让读者能选择合适标注，写出可维护的 JSON-LD，区分语法正确、功能资格与实际展示，并为真实数据缺失设计诚实的处理方式。它不教人给任何页面套上最多标签，而是从“这张页面实际向用户提供什么”开始。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "7c729f14035aa2ca928649d17e7be107f48f16fcf2832407ced95e68184e1135",
      "source_line_count": 258,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-131",
      "book": "seo/book",
      "book_title": "SEO 系统教程",
      "path": "books/seo/book/chapters/16_audit_migration_incidents.md",
      "title": "第 16 章 技术审计、日志与迁移：把问题缩小到能够修复的范围",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "审计证据、迁移映射与回滚",
      "recommended_medium": "审计工单和迁移清单",
      "reason": "成效来自真实清单及日志的核对；动画不能证明迁移正确。",
      "reuse_family": "seo-operations",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 16.1 审计的产物应该是可执行结论"
          },
          {
            "line": 13,
            "text": "## 16.2 建立四份清单，再比较它们的差异"
          },
          {
            "line": 27,
            "text": "## 16.3 从症状写出有证据的工单"
          },
          {
            "line": 37,
            "text": "## 16.4 日志让“爬虫有没有来过”变成可回答的问题"
          },
          {
            "line": 70,
            "text": "## 16.5 重复、分页与筛选参数要分开治理"
          },
          {
            "line": 84,
            "text": "## 16.6 迁移前先决定哪些东西不一起改"
          },
          {
            "line": 94,
            "text": "## 16.7 URL 映射与重定向要按相关性制定"
          },
          {
            "line": 108,
            "text": "## 16.8 上线窗口与迁移后的观察"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "一份包含几千条红色警告的爬虫报告，不等于完成了技术审计。读者真正需要知道的是：哪些问题影响重要页面，证据是什么，谁能修改，怎样验证，修完以后还剩什么不确定性。本章将前面几章的控制与渲染知识放到完整流程里，处理重复页面、分页、多参数、改版迁移和大型站点故障。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "完成本章后，你应能独立制定审计范围，结合内容清单、抓取结果、搜索报告和日志建立问题假设，设计迁移映射与回滚条件，并在流量变化时优先排除可验证的技术故障。这里所有案例与数字均为假设，示范脚本没有对真实站点运行。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "81a731574a33054e312dc513645b2902d5ac1904d703656457051553c1fed027",
      "source_line_count": 181,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-132",
      "book": "seo/book",
      "book_title": "SEO 系统教程",
      "path": "books/seo/book/chapters/17_international_seo.md",
      "title": "第 17 章 国际、多语言与多地区 SEO",
      "priority": "P2",
      "treatment": "static",
      "concept": "hreflang 家族与 canonical",
      "recommended_medium": "页面版本关系图",
      "reason": "互返、语言/地区与代表地址适合静态拓扑，避免错误推导为排名保证。",
      "reuse_family": "locale-topology",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 17.1 学习目标：让正确的人进入正确的版本"
          },
          {
            "line": 11,
            "text": "## 17.2 四个不能合并的维度"
          },
          {
            "line": 23,
            "text": "## 17.3 从市场研究到版本立项"
          },
          {
            "line": 35,
            "text": "### 同一引擎，不同地区的结果也会不同"
          },
          {
            "line": 43,
            "text": "## 17.4 域名、子域名与子目录怎么选"
          },
          {
            "line": 55,
            "text": "## 17.5 hreflang：一个页面家族的关系声明"
          },
          {
            "line": 74,
            "text": "## 17.6 canonical 和 hreflang 不应互相打架"
          },
          {
            "line": 84,
            "text": "## 17.7 翻译上线以后，谁负责更新"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "完成本章后，你应当能够把语言、文字体系、国家或地区、货币、业务覆盖范围分别建模；选择可维护的国际站地址结构；设计和测试 hreflang；判断一个页面应该翻译、改写、合并，还是根本不发布。更重要的是，你应当能解释：一个页面在某地区没有点击，究竟是没有需求、没有被发现、没有进入索引、版本匹配错误，还是产品本身无法服务当地用户。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "国际 SEO 的目标不是把中文网站批量变成几十种语言，而是让不同市场的人能够发现并完成任务。翻译是其中一道工序。假设你经营一个 Go 日志分析工具，英文读者可能需要安装命令、部署限制和计费说明；一个英语页面可以服务许多国家。但如果你销售实物，同一语言下不同地区的库存、配送、插头规格和退款流程可能不同。这时，仅有语言选项不足以表达业务差异。反过来，把一个全球可用的开源工具复制成二百个国家版本，也可能制造维护负担，而没有新增用户价值。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "029adf40ab43e91403a4955c7a94b7ba0f6f65a6da52893ca32bd440680b265f",
      "source_line_count": 150,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-133",
      "book": "seo/book",
      "book_title": "SEO 系统教程",
      "path": "books/seo/book/chapters/18_search_engine_ecosystems.md",
      "title": "第 18 章 以 Google 为基准理解搜索引擎与站长工具",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "站长工具报告与提交边界",
      "recommended_medium": "工具操作路径和证据层次",
      "reason": "功能易变且涉及账号，保留当前官方入口和核验方法，不做失真的工具模拟。",
      "reuse_family": "seo-operations",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 18.1 学习目标：知道该向谁提交，又该相信哪一层证据"
          },
          {
            "line": 11,
            "text": "## 18.2 Google Search Console 的作用和边界"
          },
          {
            "line": 21,
            "text": "## 18.3 先把资产和权限建对"
          },
          {
            "line": 33,
            "text": "## 18.4 用一个 URL 建立完整的观察链条"
          },
          {
            "line": 45,
            "text": "## 18.5 索引报告怎样变成待办事项"
          },
          {
            "line": 57,
            "text": "## 18.6 Google 的提交渠道及其限制"
          },
          {
            "line": 67,
            "text": "## 18.7 Bing 与 IndexNow：通知变化，不承诺排名"
          },
          {
            "line": 81,
            "text": "## 18.8 百度：知道哪些资料没有被核验"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "本章主要教你用 Google Search Console 建立一套能重复执行的发现、索引与诊断流程，再理解 Bing、百度及其他引擎有哪些不能照搬的地方。学完以后，你应当能够判断一个工具显示的“成功”究竟是提交成功、抓取成功，还是已经进入索引；能选择适合业务的引擎；能在官方资料不足时保留未知，而不是用旧经验补造最新规则。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "先拆开一个常见想法：“网站做好以后，把网址提交给搜索引擎就行。”搜索引擎需要先发现 URL，再决定何时抓取、如何处理内容、是否纳入索引，最后根据具体查询决定展现。提交最多帮助前面某些环节，不会自动完成后面所有判断。即使两个平台都把按钮叫作提交，它们接收的数据、适用内容和反馈含义也可能不同。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "6aee0e84fb0f72d9e09cb3616a9f5e66f74589ad6d8cd0f3a67bec40a6050434",
      "source_line_count": 152,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-134",
      "book": "seo/book",
      "book_title": "SEO 系统教程",
      "path": "books/seo/book/chapters/19_local_search.md",
      "title": "第 19 章 本地搜索、地图商家与位置页面",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "真实服务覆盖与位置页面资格",
      "recommended_medium": "业务案例和合规清单",
      "reason": "商家资格及服务能力是事实判断，不应由交互地图虚构覆盖。",
      "reuse_family": "seo-judgment",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 19.1 学习目标：先证明能服务，再争取被找到"
          },
          {
            "line": 13,
            "text": "## 19.2 看懂本地结果的三种入口"
          },
          {
            "line": 23,
            "text": "## 19.3 商家资料必须对应现实业务"
          },
          {
            "line": 35,
            "text": "## 19.4 到店、上门与混合业务分别怎么表达"
          },
          {
            "line": 47,
            "text": "## 19.5 位置落地页要回答现场问题"
          },
          {
            "line": 59,
            "text": "## 19.6 服务地区页面与门页之间的区别"
          },
          {
            "line": 69,
            "text": "## 19.7 评价流程应该收集真实体验"
          },
          {
            "line": 83,
            "text": "## 19.8 本地引用与网站信息一致性"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "本地 SEO 要解决一个很具体的问题：当一个人寻找附近的店铺或上门服务时，他能否找到你，并准确判断你能不能完成这件事。网站排名只是其中一部分。地图中的地址、营业时间、电话、评价和路线，也可能直接影响他是否联系，甚至决定他会不会白跑一趟。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "本章以 Google 搜索、地图和 Google Business Profile 为主，说明真实商家资格、位置页面、服务区域、评价流程和效果检查。学完后，你应能判断一个业务是否适合创建商家资料，能写出真正有用的位置页面，也能分清一次电话按钮点击与一次成交之间还有哪些环节。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "6b2e6cc67faebfbf67b8b05ee96f7a5fbb3daf858aeebd97caa2425ddf448045",
      "source_line_count": 158,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-135",
      "book": "seo/book",
      "book_title": "SEO 系统教程",
      "path": "books/seo/book/chapters/20_offsite_links_pr.md",
      "title": "第 20 章 站外 SEO：外链、数字公关与品牌可信度",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "链接的真实引用理由与风险",
      "recommended_medium": "链接案例和推广判断",
      "reason": "引用质量与合作情境无法由简单滑块评分，正文更能说明边界。",
      "reuse_family": "seo-judgment",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 20.1 学习目标：判断一条链接为什么存在"
          },
          {
            "line": 11,
            "text": "## 20.2 外链是关系，不是同等面额的票"
          },
          {
            "line": 23,
            "text": "## 20.3 引用不一定是推荐，品牌提及也不等于链接"
          },
          {
            "line": 33,
            "text": "## 20.4 Google 对付费链接与链接属性的要求"
          },
          {
            "line": 53,
            "text": "## 20.5 先做值得引用的东西"
          },
          {
            "line": 65,
            "text": "## 20.6 数字公关：把证据介绍给真正需要的人"
          },
          {
            "line": 77,
            "text": "## 20.7 客座文章、合作页与社区参与的取舍"
          },
          {
            "line": 89,
            "text": "## 20.8 外链审核从数据来源开始"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "站外 SEO 讨论网站之外的提及、链接与认知。本章重点放在 Google 的链接机制、合规推广、数字公关和风险检查。学完以后，你应能分辨编辑自愿引用与付费操纵，能规划一个值得别人参考的内容项目，也能在看到可疑外链时决定应当调查、忽略、修复还是寻求专业帮助。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "先把目标从“本月增加多少条外链”改成一个更具体的问题：哪些人因为什么理由，需要把读者带到你的页面？如果答案只是“因为我付钱要求放一个关键词链接”，项目的风险已经很清楚。若答案是“这里提供了一份可复现的性能测试，能帮助读者验证判断”，你就有了可以长期维护的内容与传播方向。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "647c13727bd2e6b55edb489345cfd002a668589e5e42ce5629150aa2273697d5",
      "source_line_count": 165,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-136",
      "book": "seo/book",
      "book_title": "SEO 系统教程",
      "path": "books/seo/book/chapters/21_measurement_attribution.md",
      "title": "第 21 章 SEO测量 归因与商业价值",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "点击、会话、转化与归因口径",
      "recommended_medium": "带缺失数据的分母对照器",
      "reason": "使用合成记录分开计数不同系统，说明不能把不相容分母硬接成漏斗。",
      "reuse_family": "measurement-denominators",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 21.1 学习目标与测量的起点"
          },
          {
            "line": 13,
            "text": "## 21.2 建立数据地图与指标字典"
          },
          {
            "line": 25,
            "text": "## 21.3 正确阅读Google Search Console"
          },
          {
            "line": 37,
            "text": "## 21.4 Bing与其他搜索数据如何并用"
          },
          {
            "line": 47,
            "text": "## 21.5 从事件设计到真实转化"
          },
          {
            "line": 59,
            "text": "## 21.6 搜索点击为什么不等于站内会话"
          },
          {
            "line": 69,
            "text": "## 21.7 归因分配功劳 因果解释增量"
          },
          {
            "line": 83,
            "text": "## 21.8 隐私限制与统计偏差"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "学完本章，你应能为一个网站设计最小可用的测量体系，解释搜索控制台与站内分析为什么对不上，把自然搜索表现连接到真实业务结果，并写出不夸大确定性的月度报告。你需要掌握的不是几十个仪表盘按钮，而是四个问题：正在测量什么，哪些人或行为没有被测到，变化可能来自哪里，以及哪项决策会因此改变。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "SEO测量有一条容易误解的链路：页面可以被抓取，不代表已进入索引；进入索引，不代表有展现；有展现，不代表有点击；有点击，不代表有效访问；有效访问，不代表产生转化；发生转化，也不代表它完全由这次SEO工作造成。这些环节应连接起来观察，却不能相互替代。技术指标负责暴露障碍，搜索指标负责描述可见性，行为指标负责解释访问质量，商业指标负责评价资源分配。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "6f41a9c9630535536c16a32ba2832afce9eb2ccd5615b0623163f1105f4f27ae",
      "source_line_count": 137,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-137",
      "book": "seo/book",
      "book_title": "SEO 系统教程",
      "path": "books/seo/book/chapters/22_experiments_diagnostics_operations.md",
      "title": "第 22 章 SEO实验 流量诊断与运营治理",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "前后变化与对照实验",
      "recommended_medium": "合成趋势和干预对照图",
      "reason": "让共同趋势同时影响两组，说明上涨不必然由改版引起；不用模拟结果宣称真实增量。",
      "reuse_family": "experiment-confounding",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 22.1 怎样知道一次修改是否真的有效"
          },
          {
            "line": 13,
            "text": "## 22.2 一张实验卡需要写什么"
          },
          {
            "line": 25,
            "text": "## 22.3 页面分组与对照如何建立"
          },
          {
            "line": 39,
            "text": "## 22.4 统计结果怎样解释才适合决策"
          },
          {
            "line": 51,
            "text": "## 22.5 运行测试时保护搜索与用户"
          },
          {
            "line": 61,
            "text": "## 22.6 流量下跌时先缩小问题"
          },
          {
            "line": 73,
            "text": "## 22.7 核心更新之后怎样评估与恢复"
          },
          {
            "line": 85,
            "text": "## 22.8 一次完整的故障诊断示例"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "本章要解决一个日常问题：你改了页面，几周后流量上涨，这次修改究竟有没有用？读完后，你应能写出可以被验证的假设，为适合的网站设计对照实验，为不适合实验的小站选择较诚实的观察方法，并在流量下跌时按证据排查，而非急着寻找一个算法名字。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "SEO发生在开放环境中。搜索需求、竞争页面、结果布局、网站发布、外部报道和产品活动都可能同时变化。前后比较容易做，却很难单独识别某项修改的贡献。比如你给旅行页面补充了预算计算，恰好进入假期，流量上涨可能主要来自需求。真正需要估计的是：同一时期，如果页面没有修改，流量大约会是多少。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "7dae1d0f9b4334b41ae2a37374abce1dae602f52ba4e0681b02774de3449b5aa",
      "source_line_count": 131,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-138",
      "book": "seo/book",
      "book_title": "SEO 系统教程",
      "path": "books/seo/book/chapters/23_ai_search_automation.md",
      "title": "第 23 章 AI搜索可见性与SEO自动化",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "AI 搜索引用、报告与控制边界",
      "recommended_medium": "带日期的官方证据和工作流",
      "reason": "平台机制快速变化，重点是核验与权限，互动模拟容易显得比证据更确定。",
      "reuse_family": "seo-operations",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 23.1 先区分可见性究竟指什么"
          },
          {
            "line": 13,
            "text": "## 23.2 模型知道的内容与临时检索的内容"
          },
          {
            "line": 25,
            "text": "## 23.3 Google在2026年的新报告与新控制"
          },
          {
            "line": 37,
            "text": "## 23.4 把内容做成可以核对的答案来源"
          },
          {
            "line": 49,
            "text": "## 23.5 AI可见性审计怎样做"
          },
          {
            "line": 61,
            "text": "## 23.6 Bing与OpenAI提供的补充视角"
          },
          {
            "line": 71,
            "text": "## 23.7 自动化先从可靠的数据获取开始"
          },
          {
            "line": 83,
            "text": "## 23.8 给Go开发者的最小数据流水线"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "本章以Google搜索为主线，介绍AI答案中的内容发现、引用与测量，再说明开发者怎样安全地使用API和自动化。学完后，你应能判断一项所谓GEO服务到底在改善什么，识别不同平台的控制与报告边界，并设计一个保留证据、允许人工复核的自动化流程。本章资料核验截至2026年10月13日，AI搜索界面和控制变化较快，执行前应重新阅读官方文档。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "生成式引擎优化（Generative Engine Optimization，GEO）与答案引擎优化（Answer Engine Optimization，AEO）是行业常见称呼。不同公司对它们的定义并不一致。有的强调让网页被引用，有的强调品牌在答案里出现，有的其实只是把传统内容服务改名。讨论之前，先问服务方：目标是哪种产品、哪类问题、哪种可观察结果，是否包括真实点击与业务成果。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "f1596ad4c2dd45f7309560834d7e1ed84dbe5be77a9d69f4bf9e6ea4ab2ff89d",
      "source_line_count": 135,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-139",
      "book": "seo/book",
      "book_title": "SEO 系统教程",
      "path": "books/seo/book/chapters/24_ninety_day_practice.md",
      "title": "第 24 章 从零到上线的九十天实践",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "九十天交付与运营验收",
      "recommended_medium": "分阶段计划和检查清单",
      "reason": "日程是项目建议，交互倒计时容易误导为索引或排名承诺。",
      "reuse_family": "project-planning",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 24.1 把学习变成一个可以交付的项目"
          },
          {
            "line": 13,
            "text": "## 24.2 第一天至第七天 明确用户与范围"
          },
          {
            "line": 25,
            "text": "## 24.3 第八天至第二十一天 建立页面与发布规则"
          },
          {
            "line": 37,
            "text": "## 24.4 第二十二天至第三十五天 产出第一批可信内容"
          },
          {
            "line": 49,
            "text": "## 24.5 第三十六天至第四十五天 上线与测量验收"
          },
          {
            "line": 61,
            "text": "## 24.6 第四十六天至第六十天 观察真实障碍"
          },
          {
            "line": 71,
            "text": "## 24.7 第六十一天至第七十五天 完成一次有边界的改进"
          },
          {
            "line": 81,
            "text": "## 24.8 第七十六天至第九十天 完成运营交接"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "本章不要求你在九十天内获得某个排名。它要求你完成一个范围清楚、可访问、可测量、可以持续改进的网站，并留下足够证据说明做过什么、为什么这样做、结果还有哪些未知。搜索系统是否抓取、何时索引、能获得多少流量都不由项目计划单方面决定。Google的入门指导也不把遵循建议等同于获得特定排名。[F31]",
            "truncated": false
          },
          {
            "line": 7,
            "text": "贯穿项目是一款假设的Go日志分析工具，暂称“LogKit演示项目”。它只用于教学，不代表现有公司或真实业务数据。产品支持用户上传指定格式日志，查看错误分类与时间分布；付费设想是保存项目与团队协作。你可以换成个人作品站、内容站、电商或本地服务，但应保留项目的验收方式，而不是照抄产品主题。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "e298e02cd0515d5e7f07f8fdfdb3c0fc051c6f00c33a9b4017c08d953c974cc7",
      "source_line_count": 142,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-140",
      "book": "seo/book",
      "book_title": "SEO 系统教程",
      "path": "books/seo/book/chapters/25_seo_monetization.md",
      "title": "第 25 章 如何通过 SEO 赚钱",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "变现模型、有效收入与持续成本",
      "recommended_medium": "情景收入/成本计算器",
      "reason": "输入浏览量、转化或佣金及维护成本，显示假设如何影响净额；默认值均标教学假设。",
      "reuse_family": "unit-economics",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 5,
            "text": "## 25.1 先找到付款方，再选择页面"
          },
          {
            "line": 27,
            "text": "## 25.2 广告：把有用内容的阅读量变成收入"
          },
          {
            "line": 43,
            "text": "## 25.3 联盟营销：帮助读者选对产品，赚取有效推荐佣金"
          },
          {
            "line": 57,
            "text": "## 25.4 获客与线索：先约定什么值得付钱"
          },
          {
            "line": 69,
            "text": "## 25.5 本地服务：搜索带来预约，现场交付决定利润"
          },
          {
            "line": 81,
            "text": "## 25.6 电商：利润藏在运费、退货和库存里"
          },
          {
            "line": 95,
            "text": "## 25.7 SaaS 与工具站：免费使用之后，为什么值得持续付费"
          },
          {
            "line": 121,
            "text": "## 25.8 数字产品、课程与会员：把能交付的成果写清楚"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "一个网页每月有一万次自然搜索访问，可能只能获得少量广告收入，也可能带来几笔足以维持业务的订单。区别在于访问者要解决什么问题、谁愿意付钱，以及你为了拿到这笔钱还要做多少工作。本章帮助你选定一种变现方式，算清它怎样成立，再用一个小项目验证。第 1 章解释商业价值，第 4 章安排项目，第 21 章处理测量与归因；这里集中回答：具体卖什么、向谁收费、怎样交付、什么时候值得继续投入。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "SEO 通常是获客渠道。广告商为注意力付费，商家为有效推荐付费，客户为产品或服务付费。即使使用 Google AdSense，收入也来自网站广告业务，不是 Google 为自然搜索排名发放报酬。先决定要完成哪一笔交易，才知道应该吸引什么搜索者。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "3f3fbc5f467a15a6c3a1727010e29c0813a17fe286084d9f50cd4fedea089cd8",
      "source_line_count": 218,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-141",
      "book": "seo/book",
      "book_title": "SEO 系统教程",
      "path": "books/seo/book/chapters/26_ai_for_seo_workflows.md",
      "title": "第 26 章 把 AI 用进 SEO：从资料整理到可审核的工作流",
      "priority": "P2",
      "treatment": "static",
      "concept": "资料、建议、核验与执行分工",
      "recommended_medium": "可审核 AI 工作流图",
      "reason": "清楚显示事实来源和批准边界，避免给演示接真实发布权限。",
      "reuse_family": "ai-workflow",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 26.1 先判断这一步是否值得交给 AI"
          },
          {
            "line": 17,
            "text": "## 26.2 给模型准备一份可追查的资料包"
          },
          {
            "line": 46,
            "text": "## 26.3 需求挖掘：从用户原话找任务，不让模型模拟市场"
          },
          {
            "line": 69,
            "text": "## 26.4 找词、清洗与聚类：把候选词送回真实需求验证"
          },
          {
            "line": 98,
            "text": "## 26.5 竞对差距：找未满足的任务，而非抄标题目录"
          },
          {
            "line": 119,
            "text": "## 26.6 内容工作流：先定 brief，再写稿，最后按断言核查"
          },
          {
            "line": 166,
            "text": "## 26.7 内链建议：说明读者为什么需要点过去"
          },
          {
            "line": 186,
            "text": "## 26.8 技术审计与抓取日志：让 AI 解释证据，不让它猜现场"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "一个工具站积累了几百条查询、几十页文档和一批访问日志。接下来该更新哪一页？读完这些资料可能花掉半天，写出建议却只要十分钟。AI 在这里有实际用途：先整理零散材料，指出需要核查的地方，把已有证据变成方便人作决定的草稿。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "第23章讨论网站怎样进入 AI 搜索，以及数据自动化的基础。本章换到操作者这一侧：怎样用 AI 完成需求研究、找词、内容、技术检查和效果分析。读完后，你应能给模型准备合适的资料，写出可复用的任务说明，检查结果，并决定哪些步骤值得自动化。以下提示词是原创工作模板，案例和数字都是教学假设，不代表某个模型的实测效果。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "6dadebcbf95b42e15437ab9c02b085863db850cb62eed63968585930fb8d6990",
      "source_line_count": 433,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-142",
      "book": "seo/ai-workflow",
      "book_title": "AI SEO 实践指南",
      "path": "books/seo/ai-workflow/docs/01_从案例到可验证假设.md",
      "title": "01 从案例到可验证假设",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "案例陈述到可验证假设",
      "recommended_medium": "证据分层和试点标准",
      "reason": "重点是别把案例当普遍收益证明，文字能保留未知与核验条件。",
      "reuse_family": "seo-judgment",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 9,
            "text": "## 先证明页面有用，再讨论做多少页"
          },
          {
            "line": 17,
            "text": "## 时长有用，但必须知道用户在做什么"
          },
          {
            "line": 25,
            "text": "## 收入要从正确的分母开始算"
          },
          {
            "line": 33,
            "text": "## 数据能读到，还要核对能否再发布"
          },
          {
            "line": 39,
            "text": "## 提交、索引、曝光，各自核验"
          },
          {
            "line": 49,
            "text": "## 小原型可以轻，生产系统必须算限制"
          },
          {
            "line": 57,
            "text": "## 第一轮怎样算有进展"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "这套工作流先解决一个具体问题：怎样让 AI 帮你找到值得做的页面、整理可靠资料、提出改进，再用真实结果决定是否扩大投入。第一轮的目标，是跑通一条能检查、能停下来的流程。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "[陈然的案例](https://ranchen.org/content/20261002-rancheng-article-content-site-ad-revenue)提供了一个出发点：用自动化经营免费内容站，再用运营记录指导迭代。文中的千页规模和每页月入一美元，应视为个人策略与理想情景，不能直接写进我们的验收标准。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "2ce68ebc29c8132f1631295b0c73f00aef52f50446c3b4a546c12da48df554fd",
      "source_line_count": 61,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-143",
      "book": "seo/ai-workflow",
      "book_title": "AI SEO 实践指南",
      "path": "books/seo/ai-workflow/docs/02_总架构与数据合同.md",
      "title": "02　让 AI 主导工作，让证据和权限决定能否执行",
      "priority": "P2",
      "treatment": "static",
      "concept": "证据、建议和执行权限分层",
      "recommended_medium": "实体关系和审批版本图",
      "reason": "数据合同与窄授权应稳定查阅，静态图比可点击权限界面更明确。",
      "reuse_family": "ai-workflow",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 13,
            "text": "## 原型到底实现了哪一段"
          },
          {
            "line": 21,
            "text": "## 2.1 一条闭环，同时保留三个事实层"
          },
          {
            "line": 50,
            "text": "## 2.2 四个角色，各自少拿一点权限"
          },
          {
            "line": 68,
            "text": "## 2.3 用稳定的实体 ID 把证据串起来"
          },
          {
            "line": 87,
            "text": "### 来源记录要能复核，但不能无限复制"
          },
          {
            "line": 93,
            "text": "## 2.4 指标有两种时间，还会被修订"
          },
          {
            "line": 101,
            "text": "## 2.5 批准是一张窄通行证"
          },
          {
            "line": 122,
            "text": "## 2.6 SQLite 先解决连续运行，不急着建平台"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "这个系统的基本单位是一项可以追溯的页面改进：它为什么值得做，用了哪些资料，改了哪个版本，谁批准了什么，结果是否支持继续。关键词、文章和报表围绕这项改进连接起来。这样，即使某个模型换了，数据迟到了，或者一次实验没有效果，工作也能从可靠的状态继续。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本章先给出完整的目标设计。交付原型只实现其中的本地闭环；生产身份认证、多机调度和远程发布并未因为画进架构图就自动具备。阅读时按以下三个等级判断能力。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "b2e19e487317ca648483aaeeb258ec56b89326faa52025c907e19ead37f49e17",
      "source_line_count": 158,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-144",
      "book": "seo/ai-workflow",
      "book_title": "AI SEO 实践指南",
      "path": "books/seo/ai-workflow/docs/03_找词与机会验证.md",
      "title": "03 找词与机会验证：先证明值得做，再让 AI 扩大生产",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "机会研究与有限生产",
      "recommended_medium": "需求卡和样本核验流程",
      "reason": "候选评分只是教学设计；交互批量生成会偏离先验证需求的主线。",
      "reuse_family": "keyword-evidence",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 5,
            "text": "## 3.1 把“有人需要”与“值得由我来做”分开"
          },
          {
            "line": 20,
            "text": "## 3.2 先写一张需求卡"
          },
          {
            "line": 30,
            "text": "## 3.3 分三路收集候选，再做有限扩展"
          },
          {
            "line": 54,
            "text": "## 3.4 清洗时不要把不同需求揉成一个词"
          },
          {
            "line": 62,
            "text": "## 3.5 搜索量接口只回答它擅长的问题"
          },
          {
            "line": 82,
            "text": "## 3.6 区分稳定需求、季节需求和事件需求"
          },
          {
            "line": 90,
            "text": "## 3.7 固定 SERP 采样条件，才能比较页面机会"
          },
          {
            "line": 100,
            "text": "## 3.8 打开页面完成任务，别只比较标题与字数"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "你可以让 AI 一次生成一万条关键词，但生成速度不能回答最重要的问题：谁会用这个页面，他要完成什么，现有页面哪里不够，以及你能否持续交付。找词阶段的结果应是一小批有证据的页面任务，允许大部分候选停在“还不确定”。本章沿用开发工具站场景。所有评分、样本数量和演算数字均为教学设计，没有购买搜索数据，也没有真实站点增长结果。",
            "truncated": false
          },
          {
            "line": 16,
            "text": "例如“JSON 转 Go”可能有明确任务，但你只有一篇解释 JSON 是什么的文章，交付检查仍不通过。“JSON null 与零值”的搜索量即使未知，只要真实用户在这里反复出错，它仍可能是重要的帮助页；这不等于证明它是高流量获客机会。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "11fc0ee626f544c870aa96753c5ee1c41423c70a01efb62d0e27c1a5d0d42381",
      "source_line_count": 162,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-145",
      "book": "seo/ai-workflow",
      "book_title": "AI SEO 实践指南",
      "path": "books/seo/ai-workflow/docs/04_从资料到有用页面.md",
      "title": "04 从资料到有用页面：先建立事实，再组织表达",
      "priority": "P2",
      "treatment": "static",
      "concept": "来源版本、断言与页面证据",
      "recommended_medium": "来源到断言到页面的映射图",
      "reason": "事实溯源是一张关系网，静态示例能强调未经核实的素材不能直接发布。",
      "reuse_family": "evidence-lineage",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 4.1 先写用户任务，再决定要不要建页面"
          },
          {
            "line": 24,
            "text": "### 怎样判断两个词要不要拆成两页"
          },
          {
            "line": 32,
            "text": "## 4.2 分类体系服务于发现与维护"
          },
          {
            "line": 54,
            "text": "## 4.3 资料进入系统之前，先明确使用边界"
          },
          {
            "line": 75,
            "text": "### 如果以后加联网采集器"
          },
          {
            "line": 81,
            "text": "## 4.4 保存资料版本，再提取事实"
          },
          {
            "line": 103,
            "text": "### 这段校验实际能挡住什么"
          },
          {
            "line": 116,
            "text": "### 外部文字只能当资料"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "一个关键词只说明有人在问，不说明我们已经有资格回答。建站流水线最容易漏掉的环节，就在这两者之间：页面里的事实从哪里来，读者能用它完成什么，资料变化之后谁来修正。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本章把这个环节拆开。示例围绕开发者的时间戳转换任务展开，避免用未经核实的医疗、金融或法律内容试验自动发布。项目提供了可运行的离线资料导入和引文校验模块；联网采集、完整事实审核和自动内容生产仍属于需要接入的生产流程。二者不能混为一谈。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "0d1a573c3f3ff33af321d5a05b3117b77f5f168bea87dba6ee89a14e6e6d34ed",
      "source_line_count": 181,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-146",
      "book": "seo/ai-workflow",
      "book_title": "AI SEO 实践指南",
      "path": "books/seo/ai-workflow/docs/05_自动建站与收录.md",
      "title": "05 自动建站与收录：把审核过的内容变成可检查、可撤回的页面",
      "priority": "P2",
      "treatment": "static",
      "concept": "审核、静态构建、预览与索引门",
      "recommended_medium": "两道发布门和构建产物图",
      "reason": "已有离线小站可验证，新增拟真发布操作不会带来更多教学价值。",
      "reuse_family": "publishing-boundaries",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 5.1 为什么第一版选择静态 HTML"
          },
          {
            "line": 25,
            "text": "## 5.2 页面记录就是模板合同"
          },
          {
            "line": 41,
            "text": "## 5.3 从记录到可用网页的构建过程"
          },
          {
            "line": 57,
            "text": "## 5.4 页面必须先让人走得通"
          },
          {
            "line": 67,
            "text": "## 5.5 转换器要真的完成任务"
          },
          {
            "line": 77,
            "text": "## 5.6 把预览与公开索引分成两道门"
          },
          {
            "line": 87,
            "text": "## 5.7 四种容易混淆的控制"
          },
          {
            "line": 100,
            "text": "## 5.8 sitemap 的 lastmod 不能是每日签到"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "这一环的输入应当是已经通过内容审核的页面记录，而不是模型刚刚吐出的草稿。输出也不只是一个网址：它包括可读 HTML、链接关系、资源文件、索引设置、版本清单和测试结果。这样，当下一轮实验需要换标题、改说明或恢复旧版本时，才能知道线上实际展示了什么。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本项目先做一个离线可运行的小站。它提供 Unix 时间戳转换器，以及解释秒和毫秒的英文指南。页面、样本和站点名称都是独立教学设计，没有复制来源文章的图片、品牌或模板；样本不代表真实关键词需求或商业成绩。代码只生成本地文件，不购买域名、不配置账户、不上传站点，也不向任何搜索引擎提交地址。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "bd0e239c6e2dc9ba885e85d9c0c28f0d63c94849540065c4285be19d2f865f24",
      "source_line_count": 217,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-147",
      "book": "seo/ai-workflow",
      "book_title": "AI SEO 实践指南",
      "path": "books/seo/ai-workflow/docs/06_AI编排与接口接入.md",
      "title": "06 AI 编排与接口接入：让建议可以检查，让执行有明确边界",
      "priority": "P2",
      "treatment": "static",
      "concept": "模型建议与执行合同分离",
      "recommended_medium": "结构化建议到审批的检查路径",
      "reason": "参数、预算与版本绑定适合一张拒绝路径图，不把 JSON schema 当授权器。",
      "reuse_family": "ai-workflow",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 6.1 实际交付了哪些能力"
          },
          {
            "line": 24,
            "text": "## 6.2 两份合同，分别服务分析与执行"
          },
          {
            "line": 50,
            "text": "## 6.3 从原始资料到建议，按这个顺序走"
          },
          {
            "line": 60,
            "text": "## 6.4 提示词怎样写，哪里不能依赖提示词"
          },
          {
            "line": 68,
            "text": "## 6.5 实际模型接口：显式开关、限制输入、严格拒绝异常"
          },
          {
            "line": 76,
            "text": "## 6.6 GSC、GA4 与收入数据不能揉成一列“表现”"
          },
          {
            "line": 86,
            "text": "## 6.7 DataForSEO：先管请求，再谈批量"
          },
          {
            "line": 94,
            "text": "## 6.8 预算、失败与重试的实际含义"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "这套原型把 AI 放在三个位置：解释候选机会、提出页面计划、分析反馈。它还能为一个已有页面提出改标题的候选。模型没有发布按钮，也没有服务器终端。每份建议都要经过独立代码检查，再由绑定具体版本的审批决定能否执行。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这样做会多出一些结构化字段和审核步骤，但能把两类问题分开处理：建议是否值得试，以及程序现在是否有权试。前者需要证据和判断，后者必须由确定的规则决定。一个语言流畅、格式正确的回答，不能替代任何一种检查。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "35572a1edc6a49ba081e719a8a855e3a7a52bb4e7898c6767ff3c7c4219ed340",
      "source_line_count": 117,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-148",
      "book": "seo/ai-workflow",
      "book_title": "AI SEO 实践指南",
      "path": "books/seo/ai-workflow/docs/07_观测数据与反馈决策.md",
      "title": "07 观测数据与反馈决策：知道该改什么，也知道什么时候先别动",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "数据成熟度、页面版本与行动门",
      "recommended_medium": "带迟到/修订的合成快照回放",
      "reason": "在数据未成熟或页面版本不匹配时暂停结论，可说明每天观察不等于每天改版。",
      "reuse_family": "evidence-readiness",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 7.1 每个数据源负责回答一个问题"
          },
          {
            "line": 21,
            "text": "## 7.2 先定事件，再安装统计"
          },
          {
            "line": 33,
            "text": "## 7.3 不要把三套分母拼成一个漂亮漏斗"
          },
          {
            "line": 47,
            "text": "## 7.4 日快照需要记录“数据版本”和“页面版本”"
          },
          {
            "line": 77,
            "text": "## 7.5 GSC：分页取完，不代表拿到全部搜索"
          },
          {
            "line": 91,
            "text": "## 7.6 GA4：用响应元数据保护判断"
          },
          {
            "line": 105,
            "text": "## 7.7 广告收入要与到账和成本分开"
          },
          {
            "line": 121,
            "text": "## 7.8 每天运行一次观测，不等于每天修改一次页面"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "站点上线后，最危险的自动化是看到昨天点击下降，就让 AI 重写所有标题。数据可能尚未处理完，查询构成可能变了，页面也可能刚发布，还没被搜索引擎重新抓取。反馈系统首先要判断“这些数字够不够支持行动”，然后才决定怎样修改。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本章设计一个可以长期运行的观测流程：每天保存带版本的数据快照，按来源检查口径与成熟度，生成有证据的改进建议，经测试和权限检查后小范围发布，再等待足够的结果。接口契约在 research/interfaces.json；实际运行前仍要核对账户权限与最新配额。本章没有使用真实账户或收入数据，所有演算都是教学示例。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "d2c3c26795d224e120006161ba10e6f91cc7b0c30980db904b8b3791ec3b0c7e",
      "source_line_count": 206,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-149",
      "book": "seo/ai-workflow",
      "book_title": "AI SEO 实践指南",
      "path": "books/seo/ai-workflow/docs/08_收益预算与规模化.md",
      "title": "08　先算清一批页面，再决定要不要扩大",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "Page RPM、维护成本与盈亏",
      "recommended_medium": "可复算的页面批次预算器",
      "reason": "章节已有明确人民币情景算例，改参数即可暴露分母及维护成本；不预测收入。",
      "reuse_family": "unit-economics",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 8.1 Page RPM 的分母是浏览量"
          },
          {
            "line": 20,
            "text": "## 8.2 一张能说明亏在哪里的月度账"
          },
          {
            "line": 45,
            "text": "## 8.3 每页成本里，最容易漏掉的是以后"
          },
          {
            "line": 53,
            "text": "## 8.4 广告接入有独立的验收门"
          },
          {
            "line": 63,
            "text": "## 8.5 不用牺牲完成任务来换收入"
          },
          {
            "line": 76,
            "text": "## 8.6 从 20—50 页试点开始，是资源建议"
          },
          {
            "line": 94,
            "text": "## 8.7 地区和语言扩展，先证明差异有用"
          },
          {
            "line": 102,
            "text": "## 8.8 学习要改进判断，不能偷偷改目标"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "内容站能否赚钱，需要分别回答三个问题：有人需要这些页面吗，网站能把访问变成符合政策的收入吗，收入是否覆盖持续成本？AI 可以降低部分生产成本，但也可能扩大审稿、纠错和维护负担。预算表应让这些代价可见，而不是只显示一次模型调用花了几分钱。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本章的数字全部是用于复算的情景假设，币种统一为人民币；不代表本项目真实流量、广告报价或收入。原型未加载广告脚本、未调用收费 API，也未接入结算账户，下面的经济核算属于运营设计，未与真实财务系统接通。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "5b5df73bfead3efcace79fc93875dabbf8f23feaeafbf1cf19db256e5fca2d90",
      "source_line_count": 120,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-150",
      "book": "seo/ai-workflow",
      "book_title": "AI SEO 实践指南",
      "path": "books/seo/ai-workflow/docs/09_原型运行与故障演练.md",
      "title": "09｜先跑通两个实验，再接真实世界",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "离线闭环与故障拒绝路径",
      "recommended_medium": "现有可运行原型及其日志",
      "reason": "原型已能展示保留和回滚，优先让读者真实运行；不再用浏览器动画冒充第二次验证。",
      "reuse_family": "prototype-evidence",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 9.1 需要什么，运行后会得到什么"
          },
          {
            "line": 31,
            "text": "## 9.2 一次 demo 到底做了哪些事"
          },
          {
            "line": 49,
            "text": "## 9.3 三条命令及其边界"
          },
          {
            "line": 61,
            "text": "## 9.4 数据库如何把动作连成一条证据链"
          },
          {
            "line": 83,
            "text": "## 9.5 模型建议为什么不能直接变成写入"
          },
          {
            "line": 107,
            "text": "## 9.6 什么样的数据才允许作判断"
          },
          {
            "line": 121,
            "text": "## 9.7 故障演练：确认程序停在哪里"
          },
          {
            "line": 162,
            "text": "## 9.8 预算到底限制了什么"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "本章对应随书交付的 `prototype/` 源码。它解决一个比“自动写一篇文章”更具体的问题：一个建议从哪里来，怎样获得执行资格，执行后如何留下证据，结果不合适时能否恢复原来的页面。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这次实际跑通的是**完全离线、使用合成数据的本地预览闭环**。第一轮保留一个标题变化，第二轮撤销另一个标题变化。这里的保留与撤销由预设规则和样例指标触发，不代表真实用户更喜欢某个标题，更不是搜索排名、广告收入或因果实验的证明。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "60b0830d9ddc8f8a0af8c9f82dea86b46da1c1c099e57ede9b7aed245fea5cd1",
      "source_line_count": 178,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-151",
      "book": "seo/ai-workflow",
      "book_title": "AI SEO 实践指南",
      "path": "books/seo/ai-workflow/docs/10_部署验收与运营路线.md",
      "title": "10　从本地演示走到可维护的真实站点",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "从本地演示到生产的验收门",
      "recommended_medium": "部署检查清单和恢复演练",
      "reason": "需要真实服务与授权才能验证，交互勾选不能代表已经部署。",
      "reuse_family": "project-planning",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 5,
            "text": "## 10.1 先划清交付边界"
          },
          {
            "line": 19,
            "text": "## 10.2 在干净目录复现结果"
          },
          {
            "line": 45,
            "text": "## 10.3 第一次接真实服务，先手动认清资源"
          },
          {
            "line": 62,
            "text": "## 10.4 发布经过四道门"
          },
          {
            "line": 89,
            "text": "## 10.5 自动任务只做已获准的事"
          },
          {
            "line": 129,
            "text": "## 10.6 用恢复演练证明备份可用"
          },
          {
            "line": 181,
            "text": "## 10.7 验收清单要包含拒绝和漂移"
          },
          {
            "line": 202,
            "text": "## 10.8 用 30／60／90 天安排交付，不安排排名"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "本交付能让你运行一条持久化的本地闭环，看见一次保留和一次回滚，并检查程序怎样拒绝没有依据的改动。它没有购买域名、连接真实账号、提交站点或发布生产。迈向真实运营时，先把数据接入、审批与部署分别验收，避免一次操作同时放开所有权限。",
            "truncated": false
          },
          {
            "line": 17,
            "text": "fixture 表示固定测试数据。它让相同输入可重复验证规则，不是当前市场采样。两轮演示的日期与数据均为教学构造；程序没有等待搜索引擎数周，也没有测得因果增长。生产使用前应把这种显著标注保留到所有报告和界面。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "8382bc23fc831d7ad09215f1b9a45a6474c45edf2ef3f8fd83dc20f8b395b563",
      "source_line_count": 229,
      "existing_static_mermaid_count": 1,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-152",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/01_derivatives_graphs.md",
      "title": "第 1 章　导数与计算图：程序怎样知道往哪儿改",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "局部导数、链式法则与步长",
      "recommended_medium": "小计算图和有限差分拖动器",
      "reason": "改变一个输入时同时显示局部导数与输出变化，帮助区分局部斜率和大步更新结果。",
      "reuse_family": "autodiff",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 1.1 从一个能运行的函数开始"
          },
          {
            "line": 43,
            "text": "## 1.2 多个输入：每次只动一个"
          },
          {
            "line": 65,
            "text": "## 1.3 链式法则是一条局部乘法规则"
          },
          {
            "line": 100,
            "text": "## 1.4 梯度描述局部方向，不保证任意步长有效"
          },
          {
            "line": 112,
            "text": "## 1.5 把计算结果交给测试"
          },
          {
            "line": 123,
            "text": "### 练习与解答"
          },
          {
            "line": 142,
            "text": "## 来源与边界"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "训练一个神经网络之前，可以先解决一个更小的问题：程序算出了一个不满意的数，输入该往哪个方向改？后端程序员习惯沿调用链查错。这里也要沿依赖关系往回走，不过传回去的是一个数：输出对某个输入的变化有多敏感。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本章只用标量，也就是单个实数。先把导数算对，再谈矩阵和 GPU，会少掉许多同时出现的概念。本章对应课程第一讲前半 notebook 的函数、差分、计算图和手工求导部分；本文重新组织推导，不是视频转录。[固定版本 notebook](https://github.com/karpathy/nn-zero-to-hero/blob/73c3fcc741f0ec104ca850b1fb0df90e7e8d4cde/lectures/micrograd/micrograd_lecture_first_half_roughly.ipynb)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "f0d8fc96f114268d47c15bba2e02f6b360d0e720b25928e565dab964e7b24b46",
      "source_line_count": 144,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-153",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/02_autodiff_engine.md",
      "title": "第 2 章　写出反向传播引擎",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "反传拓扑顺序与梯度累加",
      "recommended_medium": "逐边反传计算图",
      "reason": "共享节点收到多条贡献时累加，能直接发现把 += 写成 = 的错误。",
      "reuse_family": "autodiff",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 2.1 一个数还需要保存什么"
          },
          {
            "line": 40,
            "text": "## 2.2 为什么每条贡献都必须累加"
          },
          {
            "line": 48,
            "text": "## 2.3 拓扑排序保证先收齐，再往回传"
          },
          {
            "line": 76,
            "text": "## 2.4 只实现少量规则，组合出更多运算"
          },
          {
            "line": 100,
            "text": "## 2.5 一组能抓出真错误的测试"
          },
          {
            "line": 116,
            "text": "## 2.6 官方练习：让 Value 支持分类损失"
          },
          {
            "line": 130,
            "text": "## 来源与实现边界"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "手算一次计算图不难，难的是每次改表达式都要重写求导程序。解决办法是让每次运算顺手留下两样东西：它依赖哪些输入，以及拿到输出梯度后怎样给输入加梯度。等最终结果出来，再统一倒着走一次图。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这一章的完整实现是 `examples/part1/scalar_engine.py`。它借鉴课程的 `Value` 设计，同时明确改变了重复反向传播的行为，不能把它当成 micrograd 原库的原样复制。[课程实现](https://github.com/karpathy/nn-zero-to-hero/blob/73c3fcc741f0ec104ca850b1fb0df90e7e8d4cde/lectures/micrograd/micrograd_lecture_second_half_roughly.ipynb)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "6d27981d13ffcf5c3344d9fd652e2b8ff1bab532e012eb3fc9d61b9b8de0eb13",
      "source_line_count": 132,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-154",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/03_train_scalar_network.md",
      "title": "第 3 章　让小网络学会预测",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "损失、梯度与参数更新闭环",
      "recommended_medium": "四样本微型网络步进器",
      "reason": "一步步更新参数观察真实公式计算，训练范围很小且不宣称泛化能力。",
      "reuse_family": "tiny-training",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 5,
            "text": "## 3.1 神经元：带参数的加权求和"
          },
          {
            "line": 27,
            "text": "## 3.2 从一个神经元到一层，再到 MLP"
          },
          {
            "line": 47,
            "text": "## 3.3 用一个数表达 “预测得多差”"
          },
          {
            "line": 61,
            "text": "## 3.4 四步循环，每一步都能出错"
          },
          {
            "line": 77,
            "text": "## 3.5 真实小实验与它没有证明的事"
          },
          {
            "line": 92,
            "text": "## 3.6 notebook 与完整 micrograd 库有什么不同"
          },
          {
            "line": 105,
            "text": "### 练习与解答"
          },
          {
            "line": 113,
            "text": "## 来源与下一步"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "自动求导引擎不会自己知道什么叫 “预测正确”。我们还需要定义模型、目标和衡量误差的办法。三者接好以后，训练就是反复运行程序、求梯度、改参数。先用四条记录看清这个闭环，比直接面对成千上万行数据容易检查。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "输入向量 $x=(x_1,\\ldots,x_d)$ 有 $d$ 个数，权重 $w=(w_1,\\ldots,w_d)$ 与它等长，偏置 $b$ 是一个数。一个神经元计算",
            "truncated": false
          }
        ]
      },
      "source_sha256": "5501da0b1138c22a77fa60162b4e686352793a63d1d1c02cf5f6052577b389c3",
      "source_line_count": 115,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-155",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/04_character_data.md",
      "title": "第 4 章　把名字变成字符数据",
      "priority": "P2",
      "treatment": "static",
      "concept": "边界字符、ID 与 bigram 计数",
      "recommended_medium": "字符对和计数矩阵图",
      "reason": "短语料的对应关系可静态看清，互动放到下一章概率与采样避免重复。",
      "reuse_family": "text-to-targets",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 4.1 读文件后，先弄清记录代表什么"
          },
          {
            "line": 15,
            "text": "## 4.2 边界字符让长度也成为预测对象"
          },
          {
            "line": 29,
            "text": "## 4.3 字符 ID 是地址，不是数值特征"
          },
          {
            "line": 45,
            "text": "## 4.4 字典和矩阵记录的是同一件事"
          },
          {
            "line": 76,
            "text": "## 4.5 完整模型还差哪一步"
          },
          {
            "line": 80,
            "text": "### 练习与解答"
          },
          {
            "line": 88,
            "text": "## 来源与运行"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "现在给模型一批字符串，希望它继续生成风格相近的字符串。程序不必先理解名字的含义，只需回答一个局部问题：已经写到这个字符，下一个字符更可能是什么？把这个问题重复提出，就可以生成一个完整名字。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这种逐个预测再接回输入的方式叫自回归生成。本章先做最简单的 bigram 模型：预测下一个字符时，只看前一个字符。bigram 指由两个相邻元素组成的组合，这里两个元素都是字符。[课程 bigram notebook](https://github.com/karpathy/nn-zero-to-hero/blob/73c3fcc741f0ec104ca850b1fb0df90e7e8d4cde/lectures/makemore/makemore_part1_bigrams.ipynb)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "57e46580fd08cb2d27d121df869329684207724df1b54b6fe56019a3fe7c2b27",
      "source_line_count": 90,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-156",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/05_probability_sampling_loss.md",
      "title": "第 5 章　从计数得到概率、采样和损失",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "归一化、采样、平滑与 NLL",
      "recommended_medium": "转移计数/概率/损失联动图",
      "reason": "调整一格计数或平滑系数后重算整行，能看见零概率及采样频数的影响。",
      "reuse_family": "probability-loss",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 5,
            "text": "## 5.1 每一行单独归一化"
          },
          {
            "line": 31,
            "text": "## 5.2 广播不会替你判断数学意图"
          },
          {
            "line": 51,
            "text": "## 5.3 从概率抽样，为什么要带随机数生成器"
          },
          {
            "line": 74,
            "text": "## 5.4 给整份数据打分"
          },
          {
            "line": 96,
            "text": "## 5.5 没见过不等于不可能：加法平滑"
          },
          {
            "line": 115,
            "text": "### 练习与答案"
          },
          {
            "line": 121,
            "text": "## 来源与运行边界"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "一张计数表可以用来预测，也可以用来生成。预测时，给定当前字符，查看下一字符的整行分布；生成时，从这行分布抽一个字符，再把抽到的结果作为新的当前字符。两种用法共享同一张概率表，区别只是怎样使用输出。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "令 $N_{ij}$ 为转移次数，$N_i=\\sum_jN_{ij}$ 为第 $i$ 行总数。只要 $N_i>0$，定义",
            "truncated": false
          }
        ]
      },
      "source_sha256": "ebe6a0bf2f3c7a195ea10e2fcc09be9d31566742ada8b4cd29ebd2fb7a86149e",
      "source_line_count": 123,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-157",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/06_neural_bigram.md",
      "title": "第 6 章　用神经网络重写 bigram",
      "priority": "P2",
      "treatment": "static",
      "concept": "神经 bigram 与计数模型的对应",
      "recommended_medium": "one-hot 取行与 softmax 公式图",
      "reason": "重点是同一任务的两种写法，复用概率和小训练示例即可。",
      "reuse_family": "probability-loss",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 5,
            "text": "## 6.1 一批训练样本是什么形状"
          },
          {
            "line": 26,
            "text": "## 6.2 从任意打分到概率"
          },
          {
            "line": 46,
            "text": "## 6.3 用正确标签选出概率，再求平均"
          },
          {
            "line": 79,
            "text": "## 6.4 为什么梯度是 “预测概率减目标”"
          },
          {
            "line": 91,
            "text": "## 6.5 PyTorch 训练循环中的几个具体动作"
          },
          {
            "line": 107,
            "text": "## 6.6 平滑与权重惩罚相似，但不相等"
          },
          {
            "line": 127,
            "text": "### 练习与解答"
          },
          {
            "line": 133,
            "text": "## 来源与边界"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "计数法已经能得到 bigram 模型，为什么还要写神经网络？这一章不是为了用更复杂的代码胜过相对频率，而是用同一个简单任务接通张量、自动求导、损失与训练。确认两种写法到底哪里相同、哪里不同，后面增加上下文时就不容易把实现变化误当成模型能力变化。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "把每个字符对拆成输入 ID 与标签 ID。若一批有 $B$ 对，`xs` 和 `ys` 的形状都为 $(B,)$，数据类型为 `torch.long`。例如名字 `aba` 的四对样本可写为输入 `[0,1,2,1]`，标签 `[1,2,1,0]`，这里词表中的 `a=1,b=2`。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "3fd11d9fc76d164078213e2d775fb48276f5b20501eec07c6dcb64c19c4dcdd4",
      "source_line_count": 135,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-158",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/07_context_mlp.md",
      "title": "第 7 章　让模型看见上下文：嵌入与 MLP",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "上下文窗口、embedding 查表与拼接",
      "recommended_medium": "token 到向量的形状步进图",
      "reason": "改变窗口长度后逐项展示查表和拼接维度，避免把 ID 当数值特征。",
      "reuse_family": "tensor-shapes",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 7.1 一条字符串产生多条有上下文的记录"
          },
          {
            "line": 33,
            "text": "## 7.2 嵌入表就是可以训练的查表"
          },
          {
            "line": 47,
            "text": "## 7.3 把上下文连接起来，保留顺序"
          },
          {
            "line": 87,
            "text": "## 7.4 view、reshape 与底层存储"
          },
          {
            "line": 97,
            "text": "## 7.5 这套网络有多少参数"
          },
          {
            "line": 109,
            "text": "## 7.6 训练与生成使用同一个前向函数"
          },
          {
            "line": 126,
            "text": "### 练习与解答"
          },
          {
            "line": 132,
            "text": "## 来源与代码"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "bigram 在 `ana` 和 `ava` 后面看到的都是最后一个 `a`，所以给出的下一字符分布完全相同。要让模型区分这两种情况，可以把最近三个字符一起输入。但直接给所有三字符上下文各建一行表，需要 $V^3$ 行、$V$ 列，字符表稍微大一点，许多格子就永远没有数据。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这一章让多个上下文共用参数：先把字符映射成短向量，再把上下文里的向量连接起来，交给 MLP 预测下一字符。课程以 Bengio 等人 2003 年的神经概率语言模型为背景；本文实现的是简化的字符级版本，不是论文全部结构与实验的复现。[原论文](https://www.jmlr.org/papers/volume3/bengio03a/bengio03a.pdf)；[课程 MLP notebook](https://github.com/karpathy/nn-zero-to-hero/blob/73c3fcc741f0ec104ca850b1fb0df90e7e8d4cde/lectures/makemore/makemore_part2_mlp.ipynb)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "4b956c7120638913e877c1b4498162b14b2c2418498d6e521b4d616a06bfcd12",
      "source_line_count": 134,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-159",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/08_training_and_evaluation.md",
      "title": "第 8 章　让训练过程可比较",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "数据切分与可比较训练流程",
      "recommended_medium": "实验步骤、检查点和记录约定",
      "reason": "关键是避免泄漏及固定条件，交互曲线不能替代真实实验记录。",
      "reuse_family": "experiment-design",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 5,
            "text": "## 8.1 先切字符串，再展开成训练对"
          },
          {
            "line": 15,
            "text": "## 8.2 全批量正确，小批量实用"
          },
          {
            "line": 31,
            "text": "## 8.3 学习率扫描：观察范围，不把最低点当定理"
          },
          {
            "line": 48,
            "text": "## 8.4 记录损失时，记录的是哪一个模型"
          },
          {
            "line": 64,
            "text": "## 8.5 保存验证集最好的参数，不能只保存那个数字"
          },
          {
            "line": 82,
            "text": "## 8.6 从均匀预测检查初始化"
          },
          {
            "line": 90,
            "text": "### 练习与解答"
          },
          {
            "line": 96,
            "text": "## 来源与复现"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "一个模型能把训练损失降下去，还不能回答三个实际问题：它对没用来训练的数据怎样？当前学习率是否合适？这次修改真的比上次好吗？这些问题需要一套不会随手改变的实验流程。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "课程按 80%、10%、10% 切出训练、验证和测试集。训练集用来更新参数；验证集用来选择学习率、宽度和训练轮数；测试集用于最后评价。三者的作用不同，不只是把一个数组切成三段。[课程数据划分与训练单元](https://github.com/karpathy/nn-zero-to-hero/blob/73c3fcc741f0ec104ca850b1fb0df90e7e8d4cde/lectures/makemore/makemore_part2_mlp.ipynb)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "0fdcc7ef87bf369ef750e1295fa548f1e8e2afe5c8f141326d248c3df500b570",
      "source_line_count": 98,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-160",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/09_capacity_experiments.md",
      "title": "第 9 章　容量、泛化与一套完整实验",
      "priority": "P2",
      "treatment": "static",
      "concept": "容量、训练损失与验证表现",
      "recommended_medium": "现有实验结果对照图",
      "reason": "只能画书中记录，不用实时滑块编造参数量与泛化误差的必然关系。",
      "reuse_family": "experiment-evidence",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 5,
            "text": "## 9.1 先看同一条件下的三个小模型"
          },
          {
            "line": 19,
            "text": "## 9.2 生成样本与嵌入图能告诉我们什么"
          },
          {
            "line": 42,
            "text": "## 9.3 官方 bigram E01–E03：更长的表不一定更好"
          },
          {
            "line": 71,
            "text": "## 9.4 官方 MLP E01：一次真正的全量名字实验"
          },
          {
            "line": 98,
            "text": "## 9.5 官方 MLP E02 与 E03：初始化、论文和可检验的想法"
          },
          {
            "line": 112,
            "text": "## 9.6 从代码包重新跑一遍"
          },
          {
            "line": 128,
            "text": "### 进一步练习与判断标准"
          },
          {
            "line": 136,
            "text": "## 本部分完成了什么"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "增加隐藏单元，通常会让网络有更多方式拟合数据。但 “有更多参数”、“训练得更好” 和 “对新数据更好” 是三件事。最后一章用同一套数据和明确日志，把它们分开检查，并完成前三讲的官方练习。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "本书在 40 个自编、互不重复的名字上固定 32/4/4 划分，比较三组嵌入维数 $D$ 和隐藏宽度 $H$。上下文都是 3，词表都是 6，批量 16，每组最多 700 步，学习率与随机种子一致；每 50 步检查验证损失并保存参数。下面是实际运行的最佳检查点，不是挑选某个批次的最低损失。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "6f4a2c203e12d09b67573859f4076788f89acf068abe853b49c78ba7633093f7",
      "source_line_count": 140,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-161",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/10_signal_scale_initialization.md",
      "title": "第 10 章　让信号穿过网络：从初始损失到权重尺度",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "权重尺度与 tanh 饱和",
      "recommended_medium": "固定种子小张量分布图",
      "reason": "调节尺度后显示激活和导数分布，说明尺度为何影响信号传播；不推断大模型成绩。",
      "reuse_family": "initialization",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 10.1 先把张量的角色摆清楚"
          },
          {
            "line": 24,
            "text": "## 10.2 还没学过，就不该对随机答案很有把握"
          },
          {
            "line": 38,
            "text": "## 10.3 tanh 的输出有界，不代表计算稳定"
          },
          {
            "line": 60,
            "text": "## 10.4 为什么除以输入宽度的平方根"
          },
          {
            "line": 86,
            "text": "## 10.5 全零初始化为什么还能下降一点"
          },
          {
            "line": 100,
            "text": "## 10.6 动手检查与答案"
          },
          {
            "line": 110,
            "text": "### 来源与范围"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "一个程序能完成前向计算，也能调用 `backward()`，还不等于它适合训练。上一部分的字符 MLP 就会遇到这种情况：损失在下降，但开头一大段训练只是在修复随机初始化造成的坏状态。本章要学会在第一次更新前检查它。我们沿用“若干历史字符 → embedding → 隐藏层 → 下一字符”的任务，先处理输出过于自信，再处理隐藏层饱和。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本章对应课程第 4 讲的初始化部分。后面的数值实验由本书重新设计，使用小型随机张量，**不把它们当作原课程姓名数据的成绩复现**。[固定版本 notebook](https://github.com/karpathy/nn-zero-to-hero/blob/73c3fcc741f0ec104ca850b1fb0df90e7e8d4cde/lectures/makemore/makemore_part3_bn.ipynb)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "2ed733bdfa7ff442e8ef8d4d57916a2daf9cca479a3bf8afc234ce12bb1d6785",
      "source_line_count": 115,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-162",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/11_batchnorm_training_inference.md",
      "title": "第 11 章　BatchNorm：训练时看一批，推理时用固定统计量",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "BatchNorm 的统计轴与推理统计",
      "recommended_medium": "小矩阵逐列归一化器",
      "reason": "改变同批另一条样本并对比 train/eval 输出，可直观看到样本耦合。",
      "reuse_family": "normalization",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 11.1 一列一列地算，而不是一行一行地算"
          },
          {
            "line": 33,
            "text": "## 11.2 Bessel 校正与原生 PyTorch 的区别"
          },
          {
            "line": 41,
            "text": "## 11.3 为什么还要 gamma 和 beta"
          },
          {
            "line": 53,
            "text": "## 11.4 训练会让同批样本彼此影响"
          },
          {
            "line": 61,
            "text": "## 11.5 推理不该随着同行请求改变"
          },
          {
            "line": 83,
            "text": "## 11.6 官方 E02：把 BN 折进线性层"
          },
          {
            "line": 104,
            "text": "## 11.7 练习、提示与答案"
          },
          {
            "line": 114,
            "text": "### 来源与范围"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "初始化只安排了出发时的尺度。训练更新参数以后，中间信号仍会改变。Batch Normalization，简称 BatchNorm 或 BN，在网络中增加一项操作：按特征收集一组观测，减去它们的均值，再除以尺度，最后允许网络学习一次缩放和平移。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "它不是把整个矩阵统一压成一个标准正态分布，也不会让数据自动变成正态分布。本章要弄清三件事：统计哪些数，谁通过梯度学习，以及一个样本单独推理时该用谁的统计量。原论文提供训练与推理算法；课程则用手写层把它们拆开。[BatchNorm 原论文](https://arxiv.org/abs/1502.03167)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "fdca97c5e1bb522cbfabc0f432b1cae693a2745dcf0533abe3c34ef69c61e33e",
      "source_line_count": 118,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-163",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/12_training_diagnostics.md",
      "title": "第 12 章　训练诊断：不要只盯着一个 loss",
      "priority": "P2",
      "treatment": "static",
      "concept": "激活、梯度和更新比率诊断",
      "recommended_medium": "已有诊断分布并排图",
      "reason": "诊断要基于真实采样，保留原始分布与配置，不生成装饰性训练曲线。",
      "reuse_family": "training-diagnostics",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 12.1 给每层留一个可检查的出口"
          },
          {
            "line": 17,
            "text": "## 12.2 顺着前向和反向分别看尺度"
          },
          {
            "line": 29,
            "text": "## 12.3 没有非线性，深度并不会增加这类表达能力"
          },
          {
            "line": 49,
            "text": "## 12.4 参数梯度要与参数本身比较"
          },
          {
            "line": 66,
            "text": "## 12.5 为什么需要把 BN 带回来再看一遍"
          },
          {
            "line": 74,
            "text": "## 12.6 从手写层看懂 ResNet-50 的代码"
          },
          {
            "line": 82,
            "text": "## 12.7 把检查排成一次短实验"
          },
          {
            "line": 94,
            "text": "### 来源与范围"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "损失能告诉我们预测整体变好了没有，却不能指出哪个中间环节在阻碍训练。把网络加深以后，第一层可能已经几乎收不到梯度，最后一层仍能通过调整类别偏置让 loss 下降。诊断的目标是把这两件事区分开来。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "课程第 4 讲后半段将操作拆成 `Linear`、`BatchNorm1d`、`Tanh`，记录各层的输出，再看激活、梯度和参数更新。本章沿这条线建立一个可重复的检查顺序。第 15 章再扩充这些层接口，组成树形上下文模型。[固定 notebook，单元 12–24](https://github.com/karpathy/nn-zero-to-hero/blob/73c3fcc741f0ec104ca850b1fb0df90e7e8d4cde/lectures/makemore/makemore_part3_bn.ipynb)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "5bfb610de632fc86eb9cfc3ad4aa43af95831d42f682ab2a5c68a4d0aea9af10",
      "source_line_count": 98,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-164",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/13_tensor_backprop_cross_entropy.md",
      "title": "第 13 章　把反向传播写成张量运算",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "广播、归约与张量反传形状",
      "recommended_medium": "小张量反传步进器",
      "reason": "按元素高亮梯度从哪条轴求和，支持手算核验，比只列最终矩阵式更清楚。",
      "reuse_family": "autodiff",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 13.1 梯度的形状为什么应该跟原变量一样"
          },
          {
            "line": 23,
            "text": "## 13.2 从平均负对数概率开始往回走"
          },
          {
            "line": 59,
            "text": "## 13.3 减最大值的那条路径不能凭感觉删掉"
          },
          {
            "line": 75,
            "text": "## 13.4 官方练习 2：把交叉熵压缩成三行"
          },
          {
            "line": 103,
            "text": "## 13.5 矩阵乘法反传，先按一个元素推"
          },
          {
            "line": 125,
            "text": "## 13.6 怎样判断自己的反传真的正确"
          },
          {
            "line": 133,
            "text": "### 练习与答案"
          },
          {
            "line": 141,
            "text": "### 来源与范围"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "前面的小型自动求导引擎以标量为单位。现在一行矩阵乘法包含成千上万次乘加，但规则没有改变：沿每条使用路径应用链式法则，再把同一个变量收到的贡献相加。难点转移到了形状、广播与归约。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "课程第 5 讲让读者手写整个两层 MLP 的反传，而不是再实现一个通用 autograd 引擎。本章走完损失、输出线性层和 tanh；下一章继续 BatchNorm、输入线性层和 embedding。配套代码 `manual_backprop.py` 保留每一个中间张量，能逐项对照 PyTorch，而不是只看最终 loss 能否下降。[官方练习 notebook](https://colab.research.google.com/drive/1WV2oi2fh9XXyldh02wupFQX0wh5ZC-z-?usp=sharing)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "cc3b20d1a333e0f8f4a5ae24251b85d19e6b823db8ce634131b939fb3a6ea413",
      "source_line_count": 145,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-165",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/14_batchnorm_embedding_gradients.md",
      "title": "第 14 章　亲手走完 BatchNorm 与 embedding 的反向传播",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "BatchNorm 多路径与重复 embedding 索引",
      "recommended_medium": "共享贡献累加图",
      "reason": "只选重复索引的 scatter-add 演示，复用反传引擎图，不把整章化成难维护的第二套框架。",
      "reuse_family": "autodiff",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 14.1 先给每个中间量一个名字"
          },
          {
            "line": 31,
            "text": "## 14.2 方差与均值两条路都要走回来"
          },
          {
            "line": 66,
            "text": "## 14.3 官方练习 3：把 BN 反传化简成一式"
          },
          {
            "line": 105,
            "text": "## 14.4 第一层与 reshape：梯度按原来的路返回"
          },
          {
            "line": 117,
            "text": "## 14.5 embedding 最容易漏掉的是重复索引"
          },
          {
            "line": 136,
            "text": "## 14.6 从“看起来对”到确实经过检查"
          },
          {
            "line": 157,
            "text": "## 14.7 官方练习 4：不用 autograd 训练一次"
          },
          {
            "line": 172,
            "text": "### 练习、提示与答案"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "交叉熵会在一行的类别之间分配梯度，BatchNorm 则在一列的样本之间建立联系。后者最容易写错的地方，是同一个输入同时参与“自己的中心化残差”和“整批的均值、方差”。只对分子求导，会漏掉另外两条路径。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本章按课程第 5 讲的四项练习收尾：先逐节点反传，再化简 BN，然后走回输入线性层和字符表，最后用手写梯度真正更新参数。我们采用课程的 $B-1$ 方差，随后给出可兼容 $B$ 方差的统一式。[固定 notebook](https://github.com/karpathy/nn-zero-to-hero/blob/73c3fcc741f0ec104ca850b1fb0df90e7e8d4cde/lectures/makemore/makemore_part4_backprop.ipynb)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "c024f19f53d44d1437e7b75124348c1c5d0b73bbbe703864a76d77c62487da87",
      "source_line_count": 184,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-166",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/15_layer_interfaces_context_tree.md",
      "title": "第 15 章　把八个字符逐层合并：层接口与树形上下文",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "层级合并与感受野",
      "recommended_medium": "八字符合并树",
      "reason": "单击层数高亮当前位置依赖的原字符，解释拼接与平均池化不同。",
      "reuse_family": "receptive-field",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 15.1 用固定长度上下文预测一个字符"
          },
          {
            "line": 15,
            "text": "## 15.2 六种小类，分别负责一件事"
          },
          {
            "line": 34,
            "text": "## 15.3 FlattenConsecutive 不是平均池化"
          },
          {
            "line": 51,
            "text": "## 15.4 三层树，形状怎样一步步变"
          },
          {
            "line": 83,
            "text": "## 15.5 轴写错，程序也可能顺利训练"
          },
          {
            "line": 98,
            "text": "## 15.6 训练循环没有换，模型内部换了"
          },
          {
            "line": 116,
            "text": "## 15.7 练习与答案"
          },
          {
            "line": 126,
            "text": "### 来源与范围"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "上一部分的 MLP 把所有历史字符一口气拼成长向量，再交给第一层。上下文从 3 个字符扩到 8 个，输入宽度就从 $3D$ 变为 $8D$。它当然能工作，但还可以选择另一种处理顺序：先让相邻两个字符互动，再合并相邻两组，最后得到覆盖八个字符的表示。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这就是课程第 6 讲搭建的树形模型。它与 WaveNet 的层级感受野有联系，但没有实现论文的完整门控、残差、跳连和音频系统。先把这个较小结构做对，下一章再明确它怎样对应卷积。[第 6 讲官方说明](https://www.youtube.com/watch?v=t3YJ5hKiMQ0)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "78e11b9f3806a32110fc885541eaeb736bd191adaa83bbf0a25e23734f0503e2",
      "source_line_count": 130,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-167",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/16_causal_convolutions_experiments.md",
      "title": "第 16 章　从树到卷积：感受野、因果性与可靠迭代",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "因果卷积与扩张感受野",
      "recommended_medium": "可变扩张率依赖图",
      "reason": "改变核宽或扩张率后显示可见过去，验证未来输入不会影响当前输出。",
      "reuse_family": "receptive-field",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 16.1 一组 Linear 就能写成一个局部卷积"
          },
          {
            "line": 32,
            "text": "## 16.2 因果性由输入依赖决定，不由名称决定"
          },
          {
            "line": 51,
            "text": "## 16.3 扩张率如何让感受野长得更快"
          },
          {
            "line": 76,
            "text": "## 16.4 一棵滑动的树，怎样变成扩张卷积堆栈"
          },
          {
            "line": 88,
            "text": "## 16.5 与完整 WaveNet 的边界"
          },
          {
            "line": 102,
            "text": "## 16.6 课程的成绩日志应该怎样读"
          },
          {
            "line": 120,
            "text": "## 16.7 把“改进 loss”变成能核对的实验"
          },
          {
            "line": 128,
            "text": "### 练习与答案"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "树形模型每次读八个字符，输出下一个字符的分布。假设要对一句文本的相邻八个窗口都预测一遍，最直接的办法是循环调用模型八次。窗口大量重叠，里面的局部组合也会重复计算。课程第 6 讲最后用这个现象引出卷积，但尚未完整实现扩张因果卷积。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本章前半延续课程的“卷积是在多个位置复用同一种局部计算”这一线索；带有“补充”的段落给出本书新增的实际卷积代码、等价性与因果测试。树形教学网络和 2016 年完整 WaveNet 的区别始终保留，不用同一个名字抹掉它们。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "1defa6267152db6bb52fb35103e9bb392a86f35c33918488151a07c4ef562063",
      "source_line_count": 143,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-168",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/17_字符级语言模型与训练任务.md",
      "title": "第 17 章　先把 GPT 的训练题出对",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "输入与右移目标的对齐",
      "recommended_medium": "滑动窗口标签查看器",
      "reason": "逐步滑动窗口并连起每个输入位置和目标，能看出一段 T token 含 T 个预测任务。",
      "reuse_family": "text-to-targets",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 17.1 一个字符对应一个编号，编号本身没有大小含义"
          },
          {
            "line": 24,
            "text": "## 17.2 一段长度为 T 的输入，其实出了 T 道题"
          },
          {
            "line": 52,
            "text": "## 17.3 切分数据的时间比抽窗口早"
          },
          {
            "line": 58,
            "text": "## 17.4 bigram 是一张可训练的“下一项打分表”"
          },
          {
            "line": 78,
            "text": "## 17.5 训练与生成怎样使用同一个模型"
          },
          {
            "line": 86,
            "text": "## 17.6 动手验证：先检查数据，再看损失"
          },
          {
            "line": 98,
            "text": "### 练习与答案"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "给模型一段文字，让它猜下一个字符，这件事我们已经做过。现在要改变的不是题目，而是模型能利用的上下文：bigram 只看前一个字符，Transformer 要从前面的一串字符里选择有用信息。在安装注意力模块以前，先把数据、标签和损失的对应关系钉牢。否则模型可能跑得很快，却一直在解另一道题。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本章到第 20 章对应课程《Let's build GPT: from scratch, in code, spelled out》；本书沿用任务及构建顺序，使用原创小语料和缩小的 CPU 模型。原课以 tiny Shakespeare 为数据，不是本书实验语料。[课程与官方章节](https://www.youtube.com/watch?v=kCc8FmEb1nY)；[固定版课程代码](https://github.com/karpathy/ng-video-lecture/blob/52201428ed7b46804849dea0b3ccf0de9df1a5c3/gpt.py)。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "d1d637ed891b31f5b80d8f803528558069b4760cff5d5c2c4e47cb52959f4e85",
      "source_line_count": 106,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-169",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/18_从平均到因果自注意力.md",
      "title": "第 18 章　让当前位置挑选有用的过去",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "QK 打分、因果 mask 与加权和",
      "recommended_medium": "小矩阵注意力检查器",
      "reason": "选择一个 query 行观察遮罩前后权重与输出，未来位置必须保持不可见。",
      "reuse_family": "attention",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 18.1 把三个位置的平均写成一次矩阵乘法"
          },
          {
            "line": 27,
            "text": "## 18.2 softmax 把允许的分数转成权重"
          },
          {
            "line": 47,
            "text": "## 18.3 Q、K、V 是三种不同用途的投影"
          },
          {
            "line": 70,
            "text": "## 18.4 为什么除以根号 d"
          },
          {
            "line": 78,
            "text": "## 18.5 为什么还要给位置"
          },
          {
            "line": 84,
            "text": "## 18.6 编码器、解码器与交叉注意力，区别在哪里"
          },
          {
            "line": 90,
            "text": "## 18.7 用反例证明遮罩真的起作用"
          },
          {
            "line": 98,
            "text": "### 练习与答案"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "考虑 `the cat sat` 中的最后一个位置。只看 `a` 可能不够，模型还想知道前面出现了什么词。最直接的办法是把过去的向量求平均。但“每个位置同等重要”这个约定太死板。注意力要学习的，就是当前这道预测题该多看谁、少看谁。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本章按原课程的四步走：循环平均、矩阵平均、softmax 权重、由内容决定的权重。它们的共同操作始终是加权求和。来源是 [GPT 官方 notebook](https://colab.research.google.com/drive/1JMLa53HDuA-i7ZBmqV7ZnA3c_fvtXnx-) 和 [固定课程 Head 实现](https://github.com/karpathy/ng-video-lecture/blob/52201428ed7b46804849dea0b3ccf0de9df1a5c3/gpt.py)。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "e86ce791c5e418862711637482eb3db5299083afb158dbb49bf7fe46c685fca0",
      "source_line_count": 106,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-170",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/19_多头残差与Transformer块.md",
      "title": "第 19 章　把注意力装成能反复堆叠的块",
      "priority": "P2",
      "treatment": "static",
      "concept": "多头布局、残差与 LayerNorm",
      "recommended_medium": "张量尺寸和 block 顺序图",
      "reason": "结构关系需要并排看清，复用注意力与归一化示例，不再加另一套训练界面。",
      "reuse_family": "tensor-shapes",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 19.1 多头是在同一输入上作多次不同的投影"
          },
          {
            "line": 17,
            "text": "## 19.2 逐个写出 reshape，才知道头去了哪里"
          },
          {
            "line": 42,
            "text": "## 19.3 注意力交换信息，前馈层加工每个位置的表示"
          },
          {
            "line": 54,
            "text": "## 19.4 残差要求宽度一致，也给梯度留出直接路径"
          },
          {
            "line": 66,
            "text": "## 19.5 LayerNorm 的统计量属于一个位置，不属于一批样本"
          },
          {
            "line": 80,
            "text": "## 19.6 Pre-LN 的顺序不能随意调换"
          },
          {
            "line": 95,
            "text": "## 19.7 dropout 改变训练时的计算，不应混进评估"
          },
          {
            "line": 103,
            "text": "### 练习与答案"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "单个注意力头给每个位置生成一组“向过去取信息”的权重。但同一个位置可能同时需要词形、语法和更远处的主题线索。多头注意力让模型并行计算多组投影与权重，再把它们的结果合起来。接着，我们需要一种接口稳定、梯度可以穿过的结构，才能把这样的操作堆叠很多次。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本章实现以 [固定版课程 GPT](https://github.com/karpathy/ng-video-lecture/blob/52201428ed7b46804849dea0b3ccf0de9df1a5c3/gpt.py) 为教学参照，并用 [固定版 nanoGPT model.py](https://github.com/karpathy/nanoGPT/blob/3adf61e154c3fe3fca428ad6bc3818b27a3b8291/model.py) 对照多头并行的工程写法。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "66756be6a023c238cb57c8d60f66797f9877b8e3b56ea6c269b2dc83cbb76036",
      "source_line_count": 111,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-171",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/20_把小GPT训练并采样出来.md",
      "title": "第 20 章　用一条可检查的训练链路跑通小 GPT",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "训练、评估和生成完整闭环",
      "recommended_medium": "现有 tiny_gpt 程序及日志",
      "reason": "本章的价值在真实代码链和边界，不用浏览器生成样本冒充训练质量。",
      "reuse_family": "training-evidence",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 20.1 forward 必须同时满足训练和生成"
          },
          {
            "line": 15,
            "text": "## 20.2 AdamW 更新的是参数，不是输入编号"
          },
          {
            "line": 23,
            "text": "## 20.3 评估不能只看最后一个训练 batch"
          },
          {
            "line": 43,
            "text": "## 20.4 本次真实输出，以及它不能说明什么"
          },
          {
            "line": 67,
            "text": "## 20.5 生成每一步都要裁剪上下文"
          },
          {
            "line": 82,
            "text": "## 20.6 从这个小 GPT 到 ChatGPT，还缺什么"
          },
          {
            "line": 88,
            "text": "### 课程练习：把“跑出来”改成可检验的问题"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "现在我们已经有字符表、错位标签、因果多头注意力、前馈层和残差块。本章把它们接成完整程序，重点不在让输出“看起来像一段文学作品”，而是知道每个结果来自哪里：损失为何下降，验证是否可信，生成为什么仍会出错。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "完整文件为 `examples/part3/tiny_gpt.py`，它能在 CPU 上独立运行，不下载数据和权重。本书实现经过真实运行；原课程的莎士比亚训练配置与本书缩小配置分开列出。原课后来的工程化实现可对照 [nanoGPT model.py](https://github.com/karpathy/nanoGPT/blob/3adf61e154c3fe3fca428ad6bc3818b27a3b8291/model.py) 与 [train.py](https://github.com/karpathy/nanoGPT/blob/3adf61e154c3fe3fca428ad6bc3818b27a3b8291/train.py)。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "ee227c4e25ad1ff89a8e580eacba4f608d2046e36629fbe60b1deb0369197286",
      "source_line_count": 100,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-172",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/21_文本字节与token边界.md",
      "title": "第 21 章　模型看到的不是屏幕上的文字",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "字形、码点、UTF-8 字节与 token",
      "recommended_medium": "文本表示层检查器",
      "reason": "中文和组合重音可显示字符数/字节数不同；无真实 tokenizer 时明确 token 分组为教学规则。",
      "reuse_family": "unicode-bytes",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 21.1 可见字形、码点、字节和 token 是四件事"
          },
          {
            "line": 28,
            "text": "## 21.2 看起来相同，不保证字节相同"
          },
          {
            "line": 36,
            "text": "## 21.3 从 256 个字节开始，未知词的问题就变了"
          },
          {
            "line": 44,
            "text": "## 21.4 一个 token 的字节不必单独组成合法字符"
          },
          {
            "line": 57,
            "text": "## 21.5 tokenizer 训练和语言模型训练是两个阶段"
          },
          {
            "line": 67,
            "text": "### 练习与答案"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "前四章把字符直接编号，足以讲清 Transformer，但这套办法不方便处理开放世界的文本。新语言、新 emoji、代码、空格与控制字符不断出现。如果只给训练时见过的字符留编号，就会遇到未知字符；如果把所有 Unicode 码点都放进词表，词表又很大，而且许多位置很少训练。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "实际 GPT 系列的 tokenizer 常从 UTF-8 字节出发，再把经常一起出现的字节段合成 token。本章先把文字的几层表示分开，第 22 章才开始学习合并规则。这部分对应《Let's build the GPT Tokenizer》的 Unicode、编码和 tokenizer 独立训练部分。[官方课程](https://www.youtube.com/watch?v=zduSFxRajkE)；[官方 minbpe 固定版本](https://github.com/karpathy/minbpe/tree/1acefe89412b20245db5a22d2a02001e547dc602)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "8ad3c2961c9ea2b21c98326a2be2b4aeb08cf0a2b3c8d32e08e4299bd3a47002",
      "source_line_count": 75,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-173",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/22_从零训练BPE并保存词表.md",
      "title": "第 22 章　BPE 学的是一张有先后顺序的合并表",
      "priority": "P0",
      "treatment": "interactive",
      "concept": "BPE 合并顺序及训练/编码区别",
      "recommended_medium": "固定规则逐轮合并教学演示",
      "reason": "可见相邻对和先后顺序能澄清最长匹配误解；首版字符级规则必须与正文真实字节训练明确区分。",
      "reuse_family": "bpe",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "full_chapter_text_read",
        "review_note": "完整阅读本章文字、公式和小段代码；未重跑配套程序或独立复验技术结论。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 22.1 统计相邻对时允许重叠，替换时却不能重叠"
          },
          {
            "line": 19,
            "text": "## 22.2 训练循环同时维护 merges 和 vocab"
          },
          {
            "line": 36,
            "text": "## 22.3 编码新文本时，不再数谁出现得最多"
          },
          {
            "line": 56,
            "text": "## 22.4 解码容易，但可逆性的方向要说清楚"
          },
          {
            "line": 70,
            "text": "## 22.5 保存时，能重建结果比能打开文件更重要"
          },
          {
            "line": 78,
            "text": "## 22.6 真实小实验：压缩率只对这份文本成立"
          },
          {
            "line": 86,
            "text": "### 练习与答案"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "现在输入是一串 0–255 的字节编号。我们想把经常相邻的两项用一个新编号代替。比如语料里反复出现 `ab`，就给它一个新 token。随后 `ab` 又可能与后面的 `c` 合并成 `abc`。这样一点点扩充词表，就得到 Byte Pair Encoding，简称 BPE。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本章对应 [minbpe 官方练习第一阶段](https://github.com/karpathy/minbpe/blob/1acefe89412b20245db5a22d2a02001e547dc602/exercise.md)。完整实现是 `examples/part3/byte_bpe.py`，测试是 `check_bpe.py`。算法参考的 minbpe 为 MIT 许可，代码包保留其授权声明；正文解释和测试语料为本书另写。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "9b6aa45bed2646ee8bb7970483c0c1fe964909cbac38f455efb9d601ca5dd5c4",
      "source_line_count": 95,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-174",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/23_预切分特殊token与GPT兼容.md",
      "title": "第 23 章　相同的 BPE 算法，为什么还编码不出相同的 ID",
      "priority": "P2",
      "treatment": "static",
      "concept": "预切分、特殊 token 与精确 ID 兼容",
      "recommended_medium": "编码协议层次与反例对照",
      "reason": "兼容性需要真实 tokenizer 的逐 ID 测试，固定教学 BPE 不能充当 GPT 分词器。",
      "reuse_family": "tokenizer-protocol",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 23.1 先用正则分块，再在块内合并"
          },
          {
            "line": 25,
            "text": "## 23.2 空格既可能跟着词，也可能自己成片"
          },
          {
            "line": 39,
            "text": "## 23.3 GPT-2 的字节映射不是把原始文本重新改了编码"
          },
          {
            "line": 45,
            "text": "## 23.4 特殊 token 是协议的一部分，不是常见词的缩写"
          },
          {
            "line": 60,
            "text": "## 23.5 cl100k_base 的 rank 和基础字节 ID 也有历史约定"
          },
          {
            "line": 70,
            "text": "## 23.6 真正的兼容验证，要比较 ID 而不只比较文字"
          },
          {
            "line": 80,
            "text": "### 练习与答案"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "实现完基础 BPE，很容易以为复制一份词表就能成为 GPT tokenizer。真正对齐时还会遇到三层细节：文本先怎样分块，特殊字符串是否作为控制标记，字节编号和合并排名怎样对应。只要其中一层不同，编码结果就可能不同，尽管 decode 后看起来都是同一句话。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本章对应 [minbpe 练习第二到第四阶段](https://github.com/karpathy/minbpe/blob/1acefe89412b20245db5a22d2a02001e547dc602/exercise.md)。兼容目标明确固定为 **tiktoken 0.9.0 的 cl100k_base**；课程把它称作 GPT-4 tokenizer，这是课程所指模型时期的说法。本书不把这个名字推广到所有后来带有 GPT-4 名称的模型和 API。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "1ef710017bc3ebf3977aae673e860ddf8d766558869cce8c313091f6c9957394",
      "source_line_count": 88,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-175",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/24_分词选择如何影响模型.md",
      "title": "第 24 章　tokenizer 的选择，会一直留在模型里",
      "priority": "P2",
      "treatment": "static",
      "concept": "词表规模、序列长度与模型成本",
      "recommended_medium": "词表/序列/参数成本关系图",
      "reason": "切分影响需在固定文本测量，不能用通用滑块声称所有语言长度必然如何变化。",
      "reuse_family": "tokenizer-tradeoffs",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 24.1 SentencePiece 是工具，不是一种唯一的算法"
          },
          {
            "line": 28,
            "text": "## 24.2 用未知中文检查 byte_fallback"
          },
          {
            "line": 45,
            "text": "## 24.3 词表变大，序列常变短，但不是免费午餐"
          },
          {
            "line": 62,
            "text": "## 24.4 给模型加一个新 token，要改的不只有 tokenizer"
          },
          {
            "line": 70,
            "text": "## 24.5 拼写、算数、尾随空格：tokenizer 怎样改变题目"
          },
          {
            "line": 80,
            "text": "## 24.6 多模态的 token，不一定来自 BPE"
          },
          {
            "line": 86,
            "text": "## 24.7 真要选 tokenizer，先写验收条件"
          },
          {
            "line": 92,
            "text": "### 课程练习第五阶段与补充题"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "到这里，我们已经能训练一个字节 BPE，保存合并表，处理预切分和特殊标记，并在有限测试集上与一个发布版本精确对齐。但工程上的问题还没有结束：词表应该多大？换成 SentencePiece 会怎样？模型在数数、拼写和不同语言上的困难，哪些与 tokenizer 有关，哪些不能全怪它？",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本章覆盖课程后半段的 SentencePiece、词表规模、新增 token、多模态概念和 tokenization quirks。原课的强烈表述用于提醒读者不要忽视分词；本书把机制、可验证结论和未完成研究分开说明。[官方课程](https://www.youtube.com/watch?v=zduSFxRajkE)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "d4c997549c825c3ea38f7850684447bed8a959a46ae0412ec428a9e85ac1ba12",
      "source_line_count": 110,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-176",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/25-gpt2-architecture.md",
      "title": "第 25 章　从小 GPT 到 GPT-2：先把模型对上，再谈训练",
      "priority": "P2",
      "treatment": "static",
      "concept": "GPT-2 权重映射与参数共享",
      "recommended_medium": "矩阵方向、共享对象与结构图",
      "reason": "加载等价需要实际数值检查，图用来导航，不用动画代替验证。",
      "reuse_family": "weight-mapping",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 25.1　“124M”究竟描述什么"
          },
          {
            "line": 30,
            "text": "### 先观察一份权重文件，但不要过度解释图案"
          },
          {
            "line": 36,
            "text": "## 25.2　把多头注意力压进一次矩阵乘法"
          },
          {
            "line": 53,
            "text": "## 25.3　MLP 没有处理相邻位置，却仍然很重要"
          },
          {
            "line": 65,
            "text": "## 25.4　一份权重为什么会出现两次"
          },
          {
            "line": 91,
            "text": "## 25.5　导入权重时，矩阵的名字相同还不够"
          },
          {
            "line": 101,
            "text": "## 25.6　初始化不能只说“随机一点”"
          },
          {
            "line": 109,
            "text": "## 25.7　先让一个批次被记住"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "上一部分已经把字符模型变成了 Transformer。现在的问题更具体：如果别人交给你一份 GPT-2 的参数文件，你写的程序能否接住它，并算出同样的预测？这比“网络层数差不多”严格得多。模块名称、矩阵方向、归一化位置、激活函数和词表都得一致。一个模型可以顺利运行，也可以正常降低损失，却仍然不是你想复现的那个模型。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本章开始对应 Karpathy 的 [GPT-2（124M）复现课](https://www.youtube.com/watch?v=l8pRSuU81PU)。第 25—28 章以仓库 [build-nanogpt 的固定版本 6104ab1](https://github.com/karpathy/build-nanogpt/tree/6104ab1b53920f6e2159749676073ff7d815c1fa) 为代码依据，结合视频官方章节表组织内容。视频逐字字幕未取得，因此不能声称逐句覆盖；代码和 31 个官方章节主题的对应关系列在配套覆盖表。本书的 CPU 实验另写了微型模型，没有下载或运行 124M 预训练权重。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "57e301fc216610daf7c0bf3c5c41468623e39ff2dee9531c199099da296e628c",
      "source_line_count": 141,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-177",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/26-efficient-training.md",
      "title": "第 26 章　让同一份计算跑得更快：精度、编译和注意力",
      "priority": "P2",
      "treatment": "static",
      "concept": "精度、编译与注意力内存访问",
      "recommended_medium": "计算/读写/调用成本分层图",
      "reason": "速度依赖硬件与内核，静态机制图更能避免误称实际性能收益。",
      "reuse_family": "performance-accounting",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 26.1　先定义你到底在计时什么"
          },
          {
            "line": 15,
            "text": "## 26.2　少用一些有效位，换来什么"
          },
          {
            "line": 34,
            "text": "## 26.3　编译减少的是调度和中间开销"
          },
          {
            "line": 44,
            "text": "## 26.4　FlashAttention 没有把注意力公式换掉"
          },
          {
            "line": 54,
            "text": "## 26.5　用前向和反向一起验证替换"
          },
          {
            "line": 70,
            "text": "## 26.6　为什么把 50257 改成 50304"
          },
          {
            "line": 78,
            "text": "### 练习与答案"
          },
          {
            "line": 89,
            "text": "### 本章来源与验证边界"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "模型写对之后，训练时间仍可能差很多。瓶颈有时是矩阵乘法，有时是显存读写，有时是 Python 发起太多小操作。把所有问题都归结为“GPU 不够快”，就很难知道下一步该改哪里。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "GPT-2 复现课第二部分依次讨论 TF32、BF16、torch.compile、FlashAttention 和词表补齐。本章解释每一步减少了什么成本，以及怎样检查它没有把问题改掉。视频章节标题中的毫秒数属于讲者当时的硬件、软件和输入配置；本书没有在 GPU 上重新测量，也不把这些数字当作今天的性能承诺。[官方章节描述](https://www.youtube.com/watch?v=l8pRSuU81PU)。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "9343be61db28345965c39a5241c7c5d93bc4eddfa8aa7e8cc0f3a2d7e4af1f90",
      "source_line_count": 96,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-178",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/27-optimization-and-ddp.md",
      "title": "第 27 章　一次更新到底看了多少数据：AdamW、累积与多卡",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "微批次、累积、DDP 与全局平均",
      "recommended_medium": "批量和梯度分母计算器",
      "reason": "改变进程数和累积次数后同步显示 token/update 与平均系数，能防止悄悄改变训练目标。",
      "reuse_family": "gradient-accumulation",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 27.1　AdamW 的状态比参数本身多"
          },
          {
            "line": 31,
            "text": "## 27.2　先小步走，再逐渐减速"
          },
          {
            "line": 55,
            "text": "## 27.3　梯度累积的分母从哪里来"
          },
          {
            "line": 82,
            "text": "## 27.4　裁剪是在限制哪一根向量"
          },
          {
            "line": 94,
            "text": "## 27.5　DDP 复制模型，分配数据，再平均梯度"
          },
          {
            "line": 118,
            "text": "## 27.6　别让分布式日志制造另一个实验"
          },
          {
            "line": 124,
            "text": "### 练习与答案"
          },
          {
            "line": 135,
            "text": "### 本章来源与验证边界"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "把同一个网络搬到更多 GPU 上，并不会自动得到同一个训练过程。若损失平均方式变了，学习率变了，或者每个进程重复读取同一批 token，代码仍可能运行，却已经偏离原实验。要复现训练，必须同时说明“每次前向看多少数据”和“每次参数更新看多少数据”。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本章沿着课程的超参数、梯度累积和 DDP 部分，先建立一个明确的目标：每次更新最小化同一组 token 的平均负对数概率。所有缩放和通信都围绕这个目标展开。[课程固定训练实现](https://github.com/karpathy/build-nanogpt/blob/6104ab1b53920f6e2159749676073ff7d815c1fa/train_gpt2.py)。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "3131af372611219aa2e31b92c6a71df78ca3f045af0d71038cafcfdeac37352b",
      "source_line_count": 142,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-179",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/28-data-evaluation-reproduction.md",
      "title": "第 28 章　数据、评估和恢复：什么才算复现了一次训练",
      "priority": "P2",
      "treatment": "static",
      "concept": "分片、验证、评估与 checkpoint",
      "recommended_medium": "数据到恢复状态的证据链",
      "reason": "多阶段验收以真实文件和日志为依据，静态路径足以梳理责任。",
      "reuse_family": "training-state",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 28.1　先把“GPT-2 复现”说准确"
          },
          {
            "line": 15,
            "text": "## 28.2　从文档到磁盘分片"
          },
          {
            "line": 27,
            "text": "## 28.3　B×T+1 个 token 如何变成一批训练样本"
          },
          {
            "line": 46,
            "text": "## 28.4　验证损失应该保持一个稳定的问题"
          },
          {
            "line": 56,
            "text": "## 28.5　HellaSwag 问的是另一件事"
          },
          {
            "line": 77,
            "text": "## 28.6　checkpoint 是快照，还是能继续走的状态"
          },
          {
            "line": 91,
            "text": "## 28.7　怎样报告一个诚实的复现结果"
          },
          {
            "line": 97,
            "text": "### 把日志画出来，也要保留比较条件"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "模型结构相同，不代表训练实验相同。把原始网页换成另一个语料库、训练不同数量的 token、用不同方式切分验证集，都会改变结论。复现 GPT-2 的教学价值，在于把这些选择摊开，而不是只得到一个文件名里带 GPT-2 的权重。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本章把课程最后一部分拆成一条可核对的路径：文本如何进入分片，分片如何变成输入和标签，损失如何汇总，模型如何接受独立评估，以及中断后能恢复到什么程度。数据处理以固定版本的 [fineweb.py](https://github.com/karpathy/build-nanogpt/blob/6104ab1b53920f6e2159749676073ff7d815c1fa/fineweb.py)、[train_gpt2.py](https://github.com/karpathy/build-nanogpt/blob/6104ab1b53920f6e2159749676073ff7d815c1fa/train_gpt2.py) 和 [hellaswag.py](https://github.com/karpathy/build-nanogpt/blob/6104ab1b53920f6e2159749676073ff7d815c1fa/hellaswag.py) 为依据。本书没有执行下载大语料的脚本。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "8bbd68b5f65651e7a00dfca423e21ac846d82b4e403ffebae300fca08376e0a6",
      "source_line_count": 120,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-180",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/29-state-of-gpt-training.md",
      "title": "第 29 章　一个会续写的模型，怎样变成助手",
      "priority": "P2",
      "treatment": "static",
      "concept": "预训练、SFT 与偏好反馈",
      "recommended_medium": "训练数据和目标变化对照图",
      "reason": "阶段关系适合图解，不把微型互动损失等同于助手能力提升。",
      "reuse_family": "post-training",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 9,
            "text": "## 29.1　同一个下一 token 目标，面对的材料可以不同"
          },
          {
            "line": 23,
            "text": "## 29.2　“压缩文本”是有用的理解，但不是文件归档"
          },
          {
            "line": 33,
            "text": "## 29.3　SFT：把训练例子换成期望的交互"
          },
          {
            "line": 49,
            "text": "## 29.4　偏好反馈回答的是“哪一个更好”"
          },
          {
            "line": 64,
            "text": "## 29.5　RLHF 为什么还需要约束"
          },
          {
            "line": 88,
            "text": "## 29.6　训练管线不等于产品保证"
          },
          {
            "line": 94,
            "text": "### 练习与答案"
          },
          {
            "line": 105,
            "text": "### 本章来源与验证边界"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "前面训练的模型会预测下一个 token，但用户想要的是另一种行为：读懂任务，按要求回答，遇到信息不足时承认不确定。两者可以使用相似的网络结构，却不能只靠换一个界面就视为同一件事。模型学到什么，还取决于训练材料和反馈信号。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本章与下一章对应官方播放列表中的 [State of GPT | BRK216HFS](https://www.youtube.com/watch?v=bZQun8Y4L2A)，由 Karpathy 在 Microsoft Build 2023 主讲、Microsoft Developer 发布。它不是另一支名为 Intro to Large Language Models 的视频。官方描述明确列出 tokenization、pretraining、supervised finetuning、RLHF、prompting、finetuning 与 tools。本书据此建立主题覆盖，再用原论文和原创例子解释机制。未取得完整字幕；作者主页原 slides 链接在本次检查中返回主页而非 PDF，所以不声称逐页复述或覆盖演讲的每个现场示例。涉及产业状态的叙述限定在 2023 年历史语境，不把当年的产品、价格和能力当成今天的现状。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "9d7f0ba4d6764705dd4efe65b52cd73cf398219ccbbbca9960cdef9106c2b39c",
      "source_line_count": 112,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-181",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/30-prompts-tools-evaluation.md",
      "title": "第 30 章　把模型接进工作流程：提示、检索、微调和工具",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "提示、检索、工具与权限选择",
      "recommended_medium": "工程案例和评估集",
      "reason": "任务需按缺失信息和权限判断，不应在教材内默认接入真实工具执行。",
      "reuse_family": "application-judgment",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 30.1　提示改变上下文，微调改变参数"
          },
          {
            "line": 22,
            "text": "## 30.2　一个提示应该把哪个问题说清楚"
          },
          {
            "line": 40,
            "text": "## 30.3　给模型更多计算步骤，仍然要检查结果"
          },
          {
            "line": 50,
            "text": "## 30.4　检索能补资料，不能替你确认资料可信"
          },
          {
            "line": 60,
            "text": "## 30.5　工具调用要把建议和执行分开"
          },
          {
            "line": 82,
            "text": "## 30.6　外部文本不能给自己授予权限"
          },
          {
            "line": 90,
            "text": "## 30.7　用一个小评估集决定下一步"
          },
          {
            "line": 98,
            "text": "### 练习与答案"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "假设你要给一个后端服务加上报错分类功能。模型偶尔答对并不难，真正的问题是：输入含糊时怎么办，知识过时怎么办，返回值不能解析怎么办，模型建议调用的接口是否允许执行？这些问题需要同时考虑模型和外围程序。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "State of GPT 的官方描述把提示策略、微调与工具列为重要主题。本章不罗列可能很快变化的产品清单，而是把它们放到一个明确的工程问题里比较：究竟缺了什么信息、什么行为或什么执行能力。以下示例、验收表和安全边界是本书的原创补充，不是演讲逐字内容。[官方视频](https://www.youtube.com/watch?v=bZQun8Y4L2A)。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "4edb5b1022e6406f3be3ad2b5971dda471a3a1b5e20b68299023327ea421bc76",
      "source_line_count": 116,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-182",
      "book": "llm/zero-to-hero",
      "book_title": "从计算图到 GPT",
      "path": "books/llm/zero-to-hero/chapters/31-practice-and-next-experiments.md",
      "title": "第 31 章　把全书收成一次你能解释的实验",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "可解释实验与下一步验证",
      "recommended_medium": "综合实践路线和真实记录",
      "reason": "全书收束应保留完整实践，不用新的控件分散读者对证据的注意力。",
      "reuse_family": "reproduction",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 31.1　每个阶段都留下一个可检查的结果"
          },
          {
            "line": 27,
            "text": "## 31.2　先运行这组 CPU 实验"
          },
          {
            "line": 57,
            "text": "## 31.3　一个确实跑过的微型训练"
          },
          {
            "line": 74,
            "text": "## 31.4　怎样把一次失败变成有信息的实验"
          },
          {
            "line": 84,
            "text": "## 31.5　下一组实验应该只增加一个难点"
          },
          {
            "line": 98,
            "text": "## 31.6　把实验记录写给未来的自己"
          },
          {
            "line": 104,
            "text": "### 综合练习与参考答案"
          },
          {
            "line": 115,
            "text": "### 本章来源与配套文件"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "读到这里，最有价值的结果不只是“见过 Transformer”。你应该能够拿到一个训练程序，沿着数据、预测、损失、梯度和更新追踪一遍，说明每个维度和分母为什么存在；当损失异常时，还能设计一个小实验把问题缩小。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这一章把前面的内容收成一条实践路线。目标不是在普通 CPU 上假装复现大模型，而是完成一组确实能运行、能复查、能暴露错误的实验。所有新增练习均为本书补充，课程本身与本书实验的界线保持不变。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "39ac44382f9e09aecbba1f6ad2425f8c7ea3a81fb2e714068b0d4ed08cccbb65",
      "source_line_count": 121,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-183",
      "book": "llm/nanogpt",
      "book_title": "nanoGPT 全仓工程精读",
      "path": "books/llm/nanogpt/chapters/01_repository.md",
      "title": "第 1 章　沿一次训练看完整仓库",
      "priority": "P2",
      "treatment": "static",
      "concept": "nanoGPT 文件与训练责任",
      "recommended_medium": "仓库和数据流地图",
      "reason": "本章作用是导航，静态文件图能帮助追踪一次训练。",
      "reuse_family": "repository-map",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 1.1 我们到底要把什么训练起来"
          },
          {
            "line": 11,
            "text": "## 1.2 训练的一个最小闭环"
          },
          {
            "line": 21,
            "text": "## 1.3 文件地图与阅读顺序"
          },
          {
            "line": 36,
            "text": "## 1.4 哪些结果已经实际运行"
          },
          {
            "line": 52,
            "text": "## 1.5 练习：先建立可核对的问题"
          },
          {
            "line": 60,
            "text": "### 本章来源"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "假设手里只有一段文字，希望模型读到“ab”以后，能给下一个字符一个概率分布。模型不直接保存一张“前缀到答案”的字典，而是把文字变成整数、把整数查成向量，再经过多层计算。训练时，程序知道每个位置后面实际出现的 token，用它来纠正模型。生成时，程序从模型给出的概率分布中抽取一个 token，接回输入，继续预测。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "nanoGPT 把这条链分成了几个文件。`data/*/prepare.py` 负责把文字写成整数文件，`model.py` 定义可求导的计算，`train.py` 组织数据、梯度、优化器和保存，`sample.py` 负责把训练结果再变成文字。最值得学习的是这些文件之间的约定：训练脚本相信二进制文件使用正确的整数类型，模型相信编号没有超过词表，采样脚本相信解码器与训练时一致。任何一处“各自看起来没错”的改动，都可能破坏整条链。[固定版本仓库](https://github.com/karpathy/nanoGPT/tree/3adf61e154c3fe3fca428ad6bc3818b27a3b8291)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "b45ffde827d09a9e32b9ba087daf92210449ae2fb709de2be8013e9faf447961",
      "source_line_count": 64,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-184",
      "book": "llm/nanogpt",
      "book_title": "nanoGPT 全仓工程精读",
      "path": "books/llm/nanogpt/chapters/02_data.md",
      "title": "第 2 章　字符与 BPE：三条数据准备流水线",
      "priority": "P2",
      "treatment": "static",
      "concept": "字符/BPE 流水线与 uint16 协议",
      "recommended_medium": "同文不同编码的对照图",
      "reason": "真实 BPE ID 必须来自固定词表；复用教育 BPE 时应只讲机制，不伪造兼容结果。",
      "reuse_family": "tokenizer-protocol",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 2.1 同一段文字，token 可以完全不同"
          },
          {
            "line": 15,
            "text": "## 2.2 字符版：排序决定编号稳定性"
          },
          {
            "line": 29,
            "text": "## 2.3 Shakespeare BPE：复用 GPT-2 词表的必要性"
          },
          {
            "line": 39,
            "text": "## 2.4 OpenWebText：文档结束符与连续存储"
          },
          {
            "line": 49,
            "text": "## 2.5 uint16 是协议，也是一个硬边界"
          },
          {
            "line": 57,
            "text": "## 2.6 练习与检查"
          },
          {
            "line": 67,
            "text": "### 本章来源"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "模型处理的是整数编号，编号的意义由编码器规定。字符编码把一个字符变成一个编号；GPT-2 的 BPE 把字节序列按已有词表组合成 token。BPE token 不等于一个词，字符 token 也不等于一个 UTF-8 字节。若比较两个模型“看了多少 token”或“每 token 损失多少”，必须先确认它们使用相同编码。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "固定仓库提供三条准备流水线。两条使用同一份 Shakespeare 文本，区别在编码方式；另一条按文档处理 OpenWebText。它们最后都写出 `train.bin` 和 `val.bin`，但文件名相同不意味着语义可互换。[三条数据目录](https://github.com/karpathy/nanoGPT/tree/3adf61e154c3fe3fca428ad6bc3818b27a3b8291/data)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "75a52d9830d7305ca444f8d84474bc6aa0a5abc0054dec35d9f20b8075fd6c7a",
      "source_line_count": 72,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-185",
      "book": "llm/nanogpt",
      "book_title": "nanoGPT 全仓工程精读",
      "path": "books/llm/nanogpt/chapters/03_configuration.md",
      "title": "第 3 章　配置覆盖、类型检查与执行边界",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "默认值、文件和 CLI 覆盖",
      "recommended_medium": "配置来源逐层检查器",
      "reason": "选择有限的安全参数并显示覆盖顺序，能区分最终值和记录值；不执行任意 Python 配置。",
      "reuse_family": "configuration-resolution",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 3.1 一次运行为什么会有三份“配置”"
          },
          {
            "line": 17,
            "text": "## 3.2 命令行的类型检查到底检查了什么"
          },
          {
            "line": 34,
            "text": "## 3.3 把开关按它真正影响的东西分类"
          },
          {
            "line": 48,
            "text": "## 3.4 七份配置，各自省略了什么"
          },
          {
            "line": 56,
            "text": "## 3.5 练习：写出最终值，而不是凭文件名判断"
          },
          {
            "line": 62,
            "text": "## 3.6 默认值逐项参考"
          },
          {
            "line": 104,
            "text": "### 本章来源"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "`train.py` 开头先定义普通 Python 全局变量，随后执行 `configurator.py`，最后把选中的全局值收集成 `config` 字典。配置不是独立对象：脚本后面直接使用 `batch_size`、`learning_rate` 等名字。默认值、配置文件、命令行参数依次覆盖，顺序决定最终结果。[configurator.py](https://github.com/karpathy/nanoGPT/blob/3adf61e154c3fe3fca428ad6bc3818b27a3b8291/configurator.py)",
            "truncated": false
          },
          {
            "line": 13,
            "text": "先载入默认 `batch_size=12`，配置文件改为 64，末尾参数又改回 12。如果把同一个参数放在配置文件之前，后读的配置文件会覆盖它。这里没有“命令行永远优先”的特殊层级，只有从左到右执行。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "e53677ddfe3413b8530659702fec64e284620c4aae5e71fb52a6a690808a0ee6",
      "source_line_count": 109,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-186",
      "book": "llm/nanogpt",
      "book_title": "nanoGPT 全仓工程精读",
      "path": "books/llm/nanogpt/chapters/04_parameters.md",
      "title": "第 4 章　GPTConfig、参数规模与初始化",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "GPTConfig 与参数规模",
      "recommended_medium": "维度约束和参数账本",
      "reason": "调整层宽、层数、词表后显示逐矩阵参数量，区分有效 token 与为计算对齐的多余行。",
      "reuse_family": "model-budget",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 4.1 一个配置怎样变成一组矩阵"
          },
          {
            "line": 22,
            "text": "## 4.2 不靠打印的 M 数，自己数一遍"
          },
          {
            "line": 36,
            "text": "## 4.3 初始值如何进入残差网络"
          },
          {
            "line": 44,
            "text": "## 4.4 改结构时，先看哪个成本增长"
          },
          {
            "line": 52,
            "text": "### 本章来源"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "`GPTConfig` 是 dataclass，保存长度、词表、层数、头数、宽度、dropout 和 bias。默认值描述一个 GPT-2 小模型量级的结构：$T_{\\max}=1024,V=50304,L=12,H=12,C=768$。其中 50304 是把 GPT-2 的 50257 向上补到 64 的倍数，不是另一套已经定义了 50304 个有效 token 的编码器。多出的输出行仍会参加 softmax，解码时未必有对应 token。[GPTConfig 与 GPT 初始化](https://github.com/karpathy/nanoGPT/blob/3adf61e154c3fe3fca428ad6bc3818b27a3b8291/model.py#L108-L148)",
            "truncated": false
          },
          {
            "line": 7,
            "text": "`GPT.__init__` 先构造 `ModuleDict`：`wte` 是 token embedding，`wpe` 是绝对位置 embedding，`drop` 是输入 dropout，`h` 是长度为 $L$ 的 `ModuleList`，`ln_f` 是最后的 LayerNorm。再创建无偏置的输出 `lm_head`，并让输入嵌入与输出矩阵共享一个 Parameter。用这些容器不是为了多一层包装；它们保证子模块被 PyTorch 注册，能出现在参数遍历、设备迁移和 state dict 中。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "b30e62e24079a19fa03f34180caec876f9d7e5981ed3230a11031009234c7ea1",
      "source_line_count": 56,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-187",
      "book": "llm/nanogpt",
      "book_title": "nanoGPT 全仓工程精读",
      "path": "books/llm/nanogpt/chapters/05_attention.md",
      "title": "第 5 章　逐层推导因果注意力、MLP 和残差",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "因果注意力与多头张量形状",
      "recommended_medium": "复用小矩阵注意力检查器",
      "reason": "用 nanoGPT 对应布局解释遮罩必须在 softmax 前，避免新增重复组件。",
      "reuse_family": "attention",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 5.1 LayerNorm 沿哪个维度计算"
          },
          {
            "line": 13,
            "text": "## 5.2 Q、K、V 是三次线性投影，合成一次执行"
          },
          {
            "line": 33,
            "text": "## 5.3 掩码必须在 softmax 之前作用"
          },
          {
            "line": 50,
            "text": "## 5.4 名为 flash，不保证用了 FlashAttention 内核"
          },
          {
            "line": 58,
            "text": "## 5.5 MLP 改每个位置，注意力连接位置"
          },
          {
            "line": 73,
            "text": "### 本章来源"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "一个位置的特征为 $x\\in\\mathbb R^C$。LayerNorm 计算该位置内的均值 $\\mu=C^{-1}\\sum_c x_c$ 和方差 $\\sigma^2=C^{-1}\\sum_c(x_c-\\mu)^2$，再输出",
            "truncated": false
          },
          {
            "line": 7,
            "text": "$$y_c=\\gamma_c\\frac{x_c-\\mu}{\\sqrt{\\sigma^2+10^{-5}}}+\\beta_c.$$",
            "truncated": false
          }
        ]
      },
      "source_sha256": "0ed1b4679ea98e9e29fc604576e7f4c3a50bf358314844ed6961c0e73d9aa3db",
      "source_line_count": 77,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-188",
      "book": "llm/nanogpt",
      "book_title": "nanoGPT 全仓工程精读",
      "path": "books/llm/nanogpt/chapters/06_forward_weights.md",
      "title": "第 6 章　权重共享、损失、预训练映射与上下文裁剪",
      "priority": "P2",
      "treatment": "static",
      "concept": "共享参数的梯度与加载方向",
      "recommended_medium": "共享对象及张量映射图",
      "reason": "关键是同一 Parameter 与两条梯度来源，静态图配断言比训练动画清楚。",
      "reuse_family": "weight-mapping",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 6.1 从编号到每个位置的预测"
          },
          {
            "line": 11,
            "text": "## 6.2 交叉熵为什么要把前两维展平"
          },
          {
            "line": 23,
            "text": "## 6.3 共享不是拷贝，梯度会从两个角色汇合"
          },
          {
            "line": 35,
            "text": "## 6.4 从 GPT-2 导入时，名字和形状都要对齐"
          },
          {
            "line": 43,
            "text": "## 6.5 裁长度会换 Parameter，时机很重要"
          },
          {
            "line": 53,
            "text": "### 本章来源"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "`GPT.forward(idx, targets=None)` 先读出批次和长度，要求 $T\\leq T_{\\max}$，再创建位置编号 `0,...,T-1`。token embedding 为 $(B,T,C)$，position embedding 为 $(T,C)$，相加时位置向量在批次维广播。经过输入 dropout、$L$ 个 block 和最终 LayerNorm，隐藏状态仍是 $(B,T,C)$。[forward](https://github.com/karpathy/nanoGPT/blob/3adf61e154c3fe3fca428ad6bc3818b27a3b8291/model.py#L170-L193)",
            "truncated": false
          },
          {
            "line": 7,
            "text": "这套位置编码是学出来的绝对位置表。生成时若只保留最近窗口，留下来的 token 会重新从位置 0 编号；原代码没有旋转位置编码，也没有保留无限增长的全局位置。不要把别的模型的位置机制套到这里。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "3b8b5cf06de1ddbff63ddc56de66d226e30f6fd63dc794381b7a892345de4ffc",
      "source_line_count": 57,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-189",
      "book": "llm/nanogpt",
      "book_title": "nanoGPT 全仓工程精读",
      "path": "books/llm/nanogpt/chapters/07_batches.md",
      "title": "第 7 章　memmap 批次与下一 token 对齐",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "memmap 窗口与下一 token 对齐",
      "recommended_medium": "二进制索引/窗口查看器",
      "reason": "高亮 B×T 输入和右移目标及最后合法起点；数据必须为内置合成 ID。",
      "reuse_family": "text-to-targets",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 7.1 原始二进制没有自我说明能力"
          },
          {
            "line": 9,
            "text": "## 7.2 最后一扇窗口为什么从 N−T−1 开始"
          },
          {
            "line": 21,
            "text": "## 7.3 为什么磁盘上是 uint16，模型前是 int64"
          },
          {
            "line": 29,
            "text": "## 7.4 训练批次与验证批次共享随机数发生器"
          },
          {
            "line": 39,
            "text": "### 本章来源"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "`get_batch(split)` 把 `train.bin` 或 `val.bin` 以 `dtype=np.uint16, mode='r'` 打开。memmap 让操作系统按需映射文件页，不要求先把整个语料读成一个常驻 NumPy 数组。它并不消除磁盘读取，也不保证任何随机访问模式都快。源码每次取批重新建立映射，注释将原因指向一个内存增长问题；本书把它当作该版本的工程选择，不把旧讨论里的现象推成所有 NumPy 版本的普遍定律。[get_batch](https://github.com/karpathy/nanoGPT/blob/3adf61e154c3fe3fca428ad6bc3818b27a3b8291/train.py#L114-L132)",
            "truncated": false
          },
          {
            "line": 7,
            "text": "文件里没有 dtype 或词表标识。若读错 dtype，模型不一定立刻报错。第一步检查应包括：文件字节数能否被元素字节数整除、token 总数是否大于 block_size、编号是否非负且小于词表、训练与验证文件是否真的来自不同切分。随机抽几个值只能发现显眼错误，完整范围检查要在准备数据时完成。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "b0aa7f7270a1515f29163167469238b9c606ae6b336b54445a63beadfd962e32",
      "source_line_count": 43,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-190",
      "book": "llm/nanogpt",
      "book_title": "nanoGPT 全仓工程精读",
      "path": "books/llm/nanogpt/chapters/08_training.md",
      "title": "第 8 章　训练主循环、梯度累积、混合精度与裁剪",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "累积、缩放、裁剪和更新顺序",
      "recommended_medium": "训练微步和梯度分母查看器",
      "reason": "复用累积组件，明确先 unscale 再 clip 的顺序，不模拟 GPU AMP 性能。",
      "reuse_family": "gradient-accumulation",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 8.1 一步到底包含几次前向"
          },
          {
            "line": 16,
            "text": "## 8.2 优化器为什么分成两组"
          },
          {
            "line": 29,
            "text": "## 8.3 AMP 改计算精度，不等于把所有状态都减半"
          },
          {
            "line": 37,
            "text": "## 8.4 先还原梯度，再裁剪，再更新"
          },
          {
            "line": 47,
            "text": "## 8.5 日志中 loss 与时间的细节"
          },
          {
            "line": 57,
            "text": "### 本章来源"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "在 nanoGPT 里，`iter_num` 主要计数优化器更新；一个更新内部可以包含多个微批次。令本进程累积次数为 $a$，第 $r$ 个微批次平均损失为 $\\ell_r$，目标是优化它们的平均值",
            "truncated": false
          },
          {
            "line": 7,
            "text": "$$\\mathcal L=\\frac1a\\sum_{r=1}^{a}\\ell_r,\\qquad\n\\nabla_\\theta\\mathcal L=\\frac1a\\sum_{r=1}^{a}\\nabla_\\theta\\ell_r.$$",
            "truncated": false
          }
        ]
      },
      "source_sha256": "083861f2411c6bc1e128afa7f235ba59950937d70082d70c25b0ea0b3f19d4f9",
      "source_line_count": 62,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-191",
      "book": "llm/nanogpt",
      "book_title": "nanoGPT 全仓工程精读",
      "path": "books/llm/nanogpt/chapters/09_resume.md",
      "title": "第 9 章　学习率、验证、checkpoint 与恢复",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "warmup 和余弦日程的边界",
      "recommended_medium": "带逐段公式的学习率曲线",
      "reason": "移动 step 指针核对端点和 i+1 约定，checkpoint 语义继续保留正文。",
      "reuse_family": "learning-rate",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 9.1 逐段读学习率，而不是只说“余弦退火”"
          },
          {
            "line": 19,
            "text": "## 9.2 验证估计了什么"
          },
          {
            "line": 27,
            "text": "## 9.3 保存时机、best 字段与最后一步"
          },
          {
            "line": 37,
            "text": "## 9.4 checkpoint 有什么，没有什么"
          },
          {
            "line": 52,
            "text": "## 9.5 可信文件与版本边界"
          },
          {
            "line": 60,
            "text": "### 本章来源"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "令 $i$ 为当前迭代编号，$w$ 为 warmup 终点，$d$ 为衰减终点，$\\eta_{\\max}$ 与 $\\eta_{\\min}$ 为学习率上下限。固定 `get_lr` 的精确行为是",
            "truncated": false
          },
          {
            "line": 7,
            "text": "$$\\eta(i)=\\begin{cases}\n\\eta_{\\max}(i+1)/(w+1),&i<w,\\\\\n\\eta_{\\min}+(\\eta_{\\max}-\\eta_{\\min})\\dfrac{1+\\cos(\\pi(i-w)/(d-w))}{2},&w\\leq i\\leq d,\\\\\n\\eta_{\\min},&i>d.\n\\end{cases}$$",
            "truncated": false
          }
        ]
      },
      "source_sha256": "7af594e42d19bf9ba2189eeb39f32c58029e862d47a27daaba6830884ab91eca",
      "source_line_count": 64,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-192",
      "book": "llm/nanogpt",
      "book_title": "nanoGPT 全仓工程精读",
      "path": "books/llm/nanogpt/chapters/10_ddp.md",
      "title": "第 10 章　单机多卡与跨节点 DDP 的状态约束",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "DDP world size 与累积次数",
      "recommended_medium": "全局批量和同步边界图",
      "reason": "显示配置累积除以进程数后的本地微步，帮助防止重复数据和错误分母。",
      "reuse_family": "gradient-accumulation",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 10.1 多张卡复制模型，分担的是数据"
          },
          {
            "line": 9,
            "text": "## 10.2 配置里的累积次数先除以 world size"
          },
          {
            "line": 27,
            "text": "## 10.3 seed 不同，权重为什么仍应相同"
          },
          {
            "line": 33,
            "text": "## 10.4 为何只在最后一个微步同步"
          },
          {
            "line": 41,
            "text": "## 10.5 跨节点时，先核对进程数和通信条件"
          },
          {
            "line": 53,
            "text": "### 本章来源"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "DDP 的基本思路是每个进程持有完整模型，用不同数据求局部梯度，再同步梯度，使各副本作相同更新。它并不把一个大模型的参数自动拆到多张卡上；如果单卡放不下模型和优化器状态，增加 DDP 卡数未必解决。固定 README 把 FSDP 列为未来工作，不能把它视为已经实现的能力。[固定训练入口](https://github.com/karpathy/nanoGPT/blob/3adf61e154c3fe3fca428ad6bc3818b27a3b8291/train.py)",
            "truncated": false
          },
          {
            "line": 7,
            "text": "脚本通过环境变量 `RANK` 判断是否进入 DDP。全局 `RANK` 标识全部进程中的编号，`LOCAL_RANK` 是当前节点上的编号，`WORLD_SIZE` 是进程总数。初始化进程组后，脚本把设备强制设为 `cuda:LOCAL_RANK`，选卡，令 rank 0 负责主要日志和保存。即使把 backend 改成 gloo，也不意味着这份 DDP 分支就成为 CPU DDP：它仍显式设置 CUDA 设备。本书未启动此路径。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "e0f95fe74db7cae080553ee3659d675ce1dc5aad94661025b98899afdca16411",
      "source_line_count": 58,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-193",
      "book": "llm/nanogpt",
      "book_title": "nanoGPT 全仓工程精读",
      "path": "books/llm/nanogpt/chapters/11_generation.md",
      "title": "第 11 章　生成接口：温度、top-k、提示和随机性",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "温度、top-k 与采样随机性",
      "recommended_medium": "logits 到概率及候选截断图",
      "reason": "调温度和 k 观察概率比与支持集，固定种子可复现采样，不宣称改善事实准确性。",
      "reuse_family": "sampling",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 11.1 采样从一个非空提示开始"
          },
          {
            "line": 11,
            "text": "## 11.2 一次循环只增加一个 token"
          },
          {
            "line": 17,
            "text": "## 11.3 温度改变概率比，不改变正温度下的排序"
          },
          {
            "line": 27,
            "text": "## 11.4 top-k 的阈值实现有平分边界"
          },
          {
            "line": 42,
            "text": "## 11.5 eval、no_grad 和 seed 各做一件事"
          },
          {
            "line": 63,
            "text": "## 11.6 采样入口的全部默认开关"
          },
          {
            "line": 81,
            "text": "### 本章来源"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "`sample.py` 默认从 `out_dir/ckpt.pt` 恢复，按 `model_args` 建模、处理 `_orig_mod.` 前缀、载入状态，然后 `eval()` 并移到设备。也可从四种 GPT-2 型号初始化，但这会走预训练权重路径，本书不执行。脚本随后查 checkpoint 中的 dataset 名称，寻找对应 `meta.pkl`；找到就使用字符映射，否则假定 GPT-2 BPE。[采样脚本](https://github.com/karpathy/nanoGPT/blob/3adf61e154c3fe3fca428ad6bc3818b27a3b8291/sample.py)",
            "truncated": false
          },
          {
            "line": 7,
            "text": "这段默认逻辑有两个容易遗漏的条件。第一，自定义 BPE 不能只凭 `meta.pkl` 文件存在就自动工作，因为代码明确期待 `stoi` 和 `itos` 字符表。第二，即使用字符表，脚本顶层仍导入 `tiktoken`，所以运行原版 sample.py 需要安装该包，但字符路径不会调用 BPE 编码或下载词表。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "8cba7877092972b977262897f3fad21780e6c8f61ae2dfe9aa5eba91fed4ed3d",
      "source_line_count": 85,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-194",
      "book": "llm/nanogpt",
      "book_title": "nanoGPT 全仓工程精读",
      "path": "books/llm/nanogpt/chapters/12_finetuning.md",
      "title": "第 12 章　微调与 GPT-2 基线：域差异如何影响比较",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "微调的域差异与比较条件",
      "recommended_medium": "基线检查表和原训练路径",
      "reason": "结果依赖数据与实验，额外曲线不能凭空说明微调收益。",
      "reuse_family": "experiment-design",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 12.1 微调复用了哪一部分训练程序"
          },
          {
            "line": 9,
            "text": "## 12.2 配置注释中的 epoch 是估算"
          },
          {
            "line": 17,
            "text": "## 12.3 基线要对齐数据域与 tokenizer"
          },
          {
            "line": 34,
            "text": "## 12.4 小数据微调的失败方式"
          },
          {
            "line": 46,
            "text": "### 本章来源"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "在本仓库中，微调仍是下一 token 交叉熵训练，使用同一个 train.py、同一个 AdamW 和同一个因果模型。关键变化是从预训练权重开始、使用目标领域数据、通常采用更小学习率和更短日程。它没有新增 LoRA、指令模板、偏好优化或聊天角色机制。把原始戏剧文本微调成能续写戏剧，不能直接推成“训练了一个会遵循指令的助手”。[微调配置](https://github.com/karpathy/nanoGPT/blob/3adf61e154c3fe3fca428ad6bc3818b27a3b8291/config/finetune_shakespeare.py)",
            "truncated": false
          },
          {
            "line": 7,
            "text": "`init_from='gpt2-xl'` 导入的是权重，不是此前 GPT-2 训练的优化器状态。本地会新建 AdamW。`init_from='resume'` 才从本地 checkpoint 恢复优化器。这两种继续训练起点并不相同：一个保留参数知识、重新开始局部优化统计，另一个试图延续已有优化轨迹。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "b8683e4a1de8a5da328c6adda6b3bb49ab98ea6e176326b4bbe5017fa11ab55c",
      "source_line_count": 50,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-195",
      "book": "llm/nanogpt",
      "book_title": "nanoGPT 全仓工程精读",
      "path": "books/llm/nanogpt/chapters/13_performance.md",
      "title": "第 13 章　benchmark、MFU 与吞吐口径",
      "priority": "P2",
      "treatment": "static",
      "concept": "吞吐、MFU 与计时边界",
      "recommended_medium": "分子分母和耗时阶段图",
      "reason": "性能数字需要真实设备；静态算式可以展示口径，不给出伪测速结果。",
      "reuse_family": "performance-accounting",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 13.1 bench.py 故意删掉了什么"
          },
          {
            "line": 11,
            "text": "## 13.2 每秒 token 数需要说明分子和分母"
          },
          {
            "line": 17,
            "text": "## 13.3 FLOPs 公式从哪里来"
          },
          {
            "line": 29,
            "text": "## 13.4 DDP 的 MFU 分子应与分母匹配"
          },
          {
            "line": 37,
            "text": "## 13.5 profiler 能回答另一类问题"
          },
          {
            "line": 47,
            "text": "## 13.6 benchmark 的全部默认开关"
          },
          {
            "line": 63,
            "text": "### 本章来源"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "bench.py 保留取批、前向、反向和优化器更新，去掉验证、checkpoint、学习率日程和 DDP 管理。默认仍是 12 层、12 头、768 宽、长度 1024、词表 50304 的模型。配置解析器能覆盖已定义的全局变量，却没有提供 n_layer 等全局开关；不能用 `--n_layer=2` 就把原版 bench 变成小模型。本书没有运行默认大 benchmark。[bench.py](https://github.com/karpathy/nanoGPT/blob/3adf61e154c3fe3fca428ad6bc3818b27a3b8291/bench.py)",
            "truncated": false
          },
          {
            "line": 7,
            "text": "`real_data=True` 时，它只打开训练 memmap 一次，`get_batch(split)` 忽略 split 参数，始终从训练数据取窗，并无条件使用 pin_memory 和设备搬运。这与 train.py 每次重建映射、区分 CPU/CUDA 的实现不同。`real_data=False` 则预先生成固定随机 x/y，每次返回同一对张量，减少数据路径干扰，但测到的任务也不同。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "46a2da226dc23366e5204bde085e37d379185fd7c4c3b7c3ccd5f245c9c0cc86",
      "source_line_count": 68,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-196",
      "book": "llm/nanogpt",
      "book_title": "nanoGPT 全仓工程精读",
      "path": "books/llm/nanogpt/chapters/14_notebooks.md",
      "title": "第 14 章　参数估算与 scaling laws：逐单元核对",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "参数、FLOPs 与固定预算假设",
      "recommended_medium": "公式显式的模型预算器",
      "reason": "复算 notebook 的尺寸和预算关系，并把历史测量与输入假设分别标注。",
      "reuse_family": "model-budget",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 14.1 先把 notebook 当成待验证的计算记录"
          },
          {
            "line": 9,
            "text": "## 14.2 sizing：精确参数、近似 FLOPs 与假设时间"
          },
          {
            "line": 11,
            "text": "### 14.2.1 参数与存储：单元 0—7"
          },
          {
            "line": 23,
            "text": "### 14.2.2 FLOPs：单元 8—10"
          },
          {
            "line": 33,
            "text": "### 14.2.3 历史速度与预算：单元 11—16"
          },
          {
            "line": 48,
            "text": "## 14.3 scaling：参数口径中藏着的差异"
          },
          {
            "line": 56,
            "text": "## 14.4 scaling 的 FLOPs 不计哪两项"
          },
          {
            "line": 64,
            "text": "## 14.5 Approach 3：从固定预算推最优 N、D"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "`transformer_sizing.ipynb` 有 17 个单元，`scaling_laws.ipynb` 有 26 个。它们混合了可执行公式、实验假设、历史测量、绘图和未解决疑问。不能因为某个公式印在 notebook 里，就把旁边的 GPU 时间也当成自己测过。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "本书用 `examples/check_notebooks.py` 按原顺序执行全部代码单元，只删除 Jupyter 专用的 `%matplotlib inline` 显示指令，图使用离线 Agg 后端保存。没有改变模型公式或网格范围，没有下载数据或权重。每个单元的 stdout 单独保存在 `qa/author/notebook_execution.json`；Markdown 单元只阅读和解释，不标为执行成功。下文单元编号从 0 开始，与原始 JSON 索引一致。[两份 notebook 的固定版本](https://github.com/karpathy/nanoGPT/tree/3adf61e154c3fe3fca428ad6bc3818b27a3b8291)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "879a0994c43938e0562c9f04c223e359c22955fbc83126e401a84f5d1545f8bd",
      "source_line_count": 135,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-197",
      "book": "llm/nanogpt",
      "book_title": "nanoGPT 全仓工程精读",
      "path": "books/llm/nanogpt/chapters/15_reproduce.md",
      "title": "第 15 章　小规模复现、故障定位与仓库的历史边界",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "小规模复现与历史边界",
      "recommended_medium": "四个真实程序、检查结果和故障入口",
      "reason": "本章应带读者运行仓库，交互玩具不能替代恢复和生成验证。",
      "reuse_family": "reproduction",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 15.1 一条能在普通 CPU 上走完的路线"
          },
          {
            "line": 45,
            "text": "## 15.2 四个完整程序各负责一件事"
          },
          {
            "line": 57,
            "text": "## 15.3 真实输出怎样支持结论"
          },
          {
            "line": 74,
            "text": "## 15.4 按失败发生的位置排查"
          },
          {
            "line": 91,
            "text": "## 15.5 仓库外围文件也有工程含义"
          },
          {
            "line": 101,
            "text": "## 15.6 能继续研究的边界"
          },
          {
            "line": 111,
            "text": "### 本章来源"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "本书的首要验证目标是让原版的数据读取、模型、训练、保存、恢复和生成接成闭环，并用独立断言检查关键数学性质。运行规模刻意很小：五字符合成序列、2 层、2 头、宽度 16、窗口 8、微批次 2、累积 2。它不需要网络、API、预训练权重或大型语料。",
            "truncated": false
          },
          {
            "line": 21,
            "text": "在可信的本地目录解压。建议创建独立虚拟环境，依赖安装需联网，但下面四个实验本身离线。下面命令对应本书已经验证的 Linux CPU 环境，PyTorch 使用其 CPU 官方源；不要把不同 CUDA 构建的结果混为本次环境。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "896380629829eb13aaebd1d4f72025441642ab09971256bc7f0511e42b8d7fe8",
      "source_line_count": 116,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-198",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/01-task-boundaries.md",
      "title": "第 01 章　先画清语言模型的任务与边界",
      "priority": "P2",
      "treatment": "static",
      "concept": "语言模型输入输出和阶段边界",
      "recommended_medium": "预测链与训练阶段图",
      "reason": "开篇需要统一任务定义，静态图能把续写模型与应用界面分开。",
      "reuse_family": "task-boundaries",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 1.1 一次预测到底输入、输出什么"
          },
          {
            "line": 21,
            "text": "## 1.2 从局部预测到整段文本"
          },
          {
            "line": 42,
            "text": "## 1.3 预训练、微调与使用阶段改变了什么"
          },
          {
            "line": 58,
            "text": "## 1.4 “从零”应该让哪些部分可检查"
          },
          {
            "line": 66,
            "text": "## 1.5 按一条可验证的路线读"
          },
          {
            "line": 72,
            "text": "## 1.6 练习与检查"
          },
          {
            "line": 84,
            "text": "## 参考与覆盖边界"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "给程序输入“今天的天空很”，它返回“蓝”的分数比“桌子”高，这件事本身还不是一个对话产品。我们需要知道文本怎样变成数字、分数怎样得到、什么数据让这些分数有用，以及程序什么时候应该停止。本书沿着这条计算链构造模型，再讨论如何把模型接到分类、指令和交互任务中。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本书依据 rasbt/LLMs-from-scratch 的公开源码撰写，固定提交为 `cdbd33e6d57a71e20ea31434841095fac50cecd8`。上游 ch01 明确没有主章节代码，只提供阅读建议和两个视频入口。因此，本章的概率展开、示意图和练习是为本书新写的教学内容，并非出版书第 1 章的翻译，也不声称逐秒核对了视频。[来源：固定版本 ch01/README](https://github.com/rasbt/LLMs-from-scratch/blob/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch01/README.md)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "f94c2d08532c31a9f345024ffa71a16f0f49320aa8c1deb1ca9a35b6dbd0611f",
      "source_line_count": 88,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-199",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/02-pytorch-training-loop.md",
      "title": "第 02 章　张量、自动微分与最小训练闭环",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "自动微分与梯度清空顺序",
      "recommended_medium": "小张量训练单步器",
      "reason": "复用计算图组件显示连续 backward 的累加与 zero_grad，防止把图和参数混为一谈。",
      "reuse_family": "autodiff",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 2.1 张量不仅有数值，还有形状、类型和存储"
          },
          {
            "line": 17,
            "text": "## 2.2 从一个 sigmoid 手算梯度"
          },
          {
            "line": 45,
            "text": "## 2.3 计算图不会替你决定何时清空梯度"
          },
          {
            "line": 53,
            "text": "## 2.4 用 Module 组织三层网络"
          },
          {
            "line": 69,
            "text": "## 2.5 Dataset 和 DataLoader 分别负责什么"
          },
          {
            "line": 77,
            "text": "## 2.6 每一步更新都要有明确先后"
          },
          {
            "line": 93,
            "text": "## 2.7 保存的是哪些状态"
          },
          {
            "line": 99,
            "text": "## 2.8 从单设备到 DDP，代码多出来的责任"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "训练语言模型之前，先让一个小分类器完整地训练、检查和保存。这个例子没有文本，却包含后面几乎所有训练程序的基本动作。读完本章，应能追踪一个批次的形状、说明每一步梯度从哪里来，并区分数据、参数、计算图和设备。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "上游对应 `appendix-A` 的两个主 notebook、两种 DDP 启动脚本及练习。本章逐步解释这些实现；CPU 实验另用四个自造样本，避免把上游演示输出冒充本书实测。[固定版本 Appendix A](https://github.com/rasbt/LLMs-from-scratch/tree/cdbd33e6d57a71e20ea31434841095fac50cecd8/appendix-A/01_main-chapter-code)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "91c3becfaf2eea3d7419d3f402e5adfca1041184d0dec34db5bc7afbebc3e802",
      "source_line_count": 128,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-200",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/03-tokenization-bpe.md",
      "title": "第 03 章　文本怎样变成 token：词表、BPE 与特殊符号",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "BPE 训练与固定合并 rank",
      "recommended_medium": "教育 BPE 组件的带边界复用",
      "reason": "本章有相邻对与 rank 推导，适合链接/复用首批演示；字符示意不能替代字节编码和真实 ID 对照。",
      "reuse_family": "bpe",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 3.1 先实现一个会失败的分词器"
          },
          {
            "line": 17,
            "text": "## 3.2 特殊符号是协议，不是普通装饰"
          },
          {
            "line": 25,
            "text": "## 3.3 字节与字符的区别先于 BPE"
          },
          {
            "line": 33,
            "text": "## 3.4 训练 BPE：数相邻对，再无重叠替换"
          },
          {
            "line": 43,
            "text": "## 3.5 编码时为什么看合并排名，不重新训练"
          },
          {
            "line": 53,
            "text": "## 3.6 上游两种自写 BPE 的用途和实测边界"
          },
          {
            "line": 74,
            "text": "## 3.7 比较分词器时先比较结果，再比较速度"
          },
          {
            "line": 82,
            "text": "## 3.8 滑窗负责构造问题和答案"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "分词器最重要的约定不是“把一句话分成几个词”，而是同一段字节怎样稳定地映射成同一组整数。模型学到的是这些整数对应的规律。训练后随意调换两个 ID，就相当于把模型词典中的两个词交换了位置，即使词表大小没有变，含义也已经错位。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本章覆盖上游 ch02 的主 notebook、压缩版 dataloader、习题，以及六组目录中的 BPE 对照、embedding 对照、滑窗直觉、自写 BPE 和保留空白的 V3。教学实现之间的差异会单独说明，不能把“示例能往返”误写成“兼容所有 Unicode 或 GPT-2”。[固定版本 ch02](https://github.com/rasbt/LLMs-from-scratch/tree/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch02)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "d45d576765547b5c7f9f35cdb2cd40c2d0817fa017209279805a2e2991bf12f6",
      "source_line_count": 136,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-201",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/04-attention-causality.md",
      "title": "第 04 章　从加权平均推导自注意力与因果掩码",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "自注意力、因果 mask 与未来扰动",
      "recommended_medium": "小矩阵注意力和因果检查",
      "reason": "改变未来 token 时，先前位置输出必须不变；这是比遮罩颜色更强的机制展示。",
      "reuse_family": "attention",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 4.1 先把加权平均写清楚"
          },
          {
            "line": 27,
            "text": "## 4.2 为什么拆成查询、键和值"
          },
          {
            "line": 41,
            "text": "## 4.3 缩放因子来自点积的方差"
          },
          {
            "line": 53,
            "text": "## 4.4 因果掩码为什么应放在 softmax 前"
          },
          {
            "line": 71,
            "text": "## 4.5 通过修改未来输入来验收"
          },
          {
            "line": 77,
            "text": "## 4.6 dropout 并不保持每行权重和恰好为 1"
          },
          {
            "line": 83,
            "text": "## 4.7 加上批次和多个头"
          },
          {
            "line": 98,
            "text": "## 4.8 配套实现怎样对照每一行"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "一个 token 的输入向量只说明它自身的 ID 和位置。要预测续写，还需要根据当前上下文重新组合信息。注意力的基本动作是：当前位置提出一个查询，给各个可见位置打分，把分数转成权重，再加权汇总那些位置的值。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "上游 ch03 从没有可训练投影的点积示例，逐步加入 Q、K、V、因果限制、dropout、批次和多头。本章保留这条推导顺序，第 5 章再讨论不同实现怎样保持同一计算。[主 notebook](https://github.com/rasbt/LLMs-from-scratch/blob/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch03/01_main-chapter-code/ch03.ipynb)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "350a8a008defe13decabe00d65d52ea4c8bef6c93742b4d9e13c2f239ed7b96b",
      "source_line_count": 121,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-202",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/05-efficient-attention-buffers.md",
      "title": "第 05 章　多头注意力的实现选择与 buffer 生命周期",
      "priority": "P2",
      "treatment": "static",
      "concept": "不同注意力实现与 buffer 状态",
      "recommended_medium": "布局、API 契约和生命周期图",
      "reason": "实现比较与测量条件复杂，静态对照可避免把 API 名称当内核或性能保证。",
      "reuse_family": "attention-implementations",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 5.1 把三次投影合成一次，不改变线性代数"
          },
          {
            "line": 21,
            "text": "## 5.2 einsum 让索引关系写在代码里"
          },
          {
            "line": 29,
            "text": "## 5.3 SDPA 的 mask 与 dropout 约定"
          },
          {
            "line": 39,
            "text": "## 5.4 完整 MultiheadAttention 类不只是公式包装"
          },
          {
            "line": 45,
            "text": "## 5.5 FlexAttention 表达规则，执行条件另算"
          },
          {
            "line": 53,
            "text": "## 5.6 buffer 解决的是模块状态管理"
          },
          {
            "line": 63,
            "text": "## 5.7 怎样测量而不混淆问题"
          },
          {
            "line": 71,
            "text": "## 5.8 一张比较表，明确各自代价"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "注意力公式很短，实现却可能很不一样：逐头 Python 循环、一次性矩阵乘法、einsum、SDPA、完整的 MultiheadAttention 类，以及 FlexAttention。选择实现前，先分清哪些只是布局变化，哪些改变了参数或功能，再谈速度。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本章依据上游高效注意力 notebook 和 buffer notebook。上游大尺寸计时在本书没有运行，所有实测对照均为小型 CPU 张量。[实现集合](https://github.com/rasbt/LLMs-from-scratch/blob/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch03/02_bonus_efficient-multihead-attention/mha-implementations.ipynb)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "25a82b1a5b12dee19e897da3798db89950437337c64f1dc64d6b95592caffd2b",
      "source_line_count": 99,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-203",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/06-build-gpt.md",
      "title": "第 06 章　把注意力组装成 GPT：归一化、残差与输出头",
      "priority": "P2",
      "treatment": "static",
      "concept": "GPT block 的先后顺序与维度",
      "recommended_medium": "从 ID 到 logits 的张量路线图",
      "reason": "保留逐层接口与数值检查，互动集中到注意力和归一化，避免重复搭整模型。",
      "reuse_family": "tensor-shapes",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 6.1 配置是一组互相约束的尺寸"
          },
          {
            "line": 13,
            "text": "## 6.2 LayerNorm 沿最后一个特征轴工作"
          },
          {
            "line": 29,
            "text": "## 6.3 GELU 与逐位置前馈网络"
          },
          {
            "line": 49,
            "text": "## 6.4 残差提供一条直接传递的路径"
          },
          {
            "line": 63,
            "text": "## 6.5 一个完整 block 的先后顺序"
          },
          {
            "line": 76,
            "text": "## 6.6 从 ID 到 logits 的完整路线"
          },
          {
            "line": 84,
            "text": "## 6.7 用参数数量检查有没有漏层"
          },
          {
            "line": 102,
            "text": "## 6.8 与上游对齐，误差应该怎样解释"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "到这里，注意力已经能把上下文混合成新的向量。但仅有注意力还不是 GPT。我们还需要稳定特征尺度、提供逐位置的非线性变换、让多层信息能够传递，并把最后的向量转换成整个词表上的分数。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "上游先搭 `DummyGPTModel`，用不做任何计算的占位 block 和归一化检查接口，再逐项替换。这种顺序能提早发现维度问题，但占位模型只验证外壳，不具备真正 Transformer 的计算能力。[固定版本 ch04 主代码](https://github.com/rasbt/LLMs-from-scratch/tree/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch04/01_main-chapter-code)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "6950da2abe01915e09813bdf3287c81a0541a923c32fd5588fc909137a62e671",
      "source_line_count": 128,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-204",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/07-costs-flops-memory.md",
      "title": "第 07 章　参数、FLOPs、显存与吞吐怎样估算",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "参数、计算、权重和运行内存",
      "recommended_medium": "分项模型成本计算器",
      "reason": "改变长度与批量后分开显示线性/二次项及未计开销，不能把估算等同峰值显存。",
      "reuse_family": "model-budget",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 7.1 从一个矩阵乘法开始记账"
          },
          {
            "line": 13,
            "text": "## 7.2 一个普通 Transformer block 的主要运算"
          },
          {
            "line": 38,
            "text": "## 7.3 为何反向常粗估为前向的两倍"
          },
          {
            "line": 44,
            "text": "## 7.4 权重字节数只是内存的第一项"
          },
          {
            "line": 52,
            "text": "## 7.5 thop 的数字要问“数到了哪些算子”"
          },
          {
            "line": 58,
            "text": "## 7.6 二分搜索最大批量能证明什么"
          },
          {
            "line": 66,
            "text": "## 7.7 吞吐和 MFU 的量纲检查"
          },
          {
            "line": 79,
            "text": "## 7.8 用预算决定下一步，而不代替测量"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "“模型多大”至少可能指四件不同的事：参数数量、权重文件字节数、一次计算的浮点运算量，以及运行时峰值内存。它们有关联，却不能互相替代。一个参数不变的模型，序列长度翻倍后，注意力中间矩阵可能变成四倍。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "上游性能 notebook 使用 thop 做运算计数、搜索可容纳的批大小，再估算 GPU 利用率。本章先推导可核验的预算，再解释这些工具结果的适用范围。没有运行其大型模型、OOM 搜索或 GPU 性能表。[固定版本性能分析](https://github.com/rasbt/LLMs-from-scratch/blob/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch04/02_performance-analysis/flops-analysis.ipynb)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "4bd50255f8116dedf3dcadfebf2a2fc8ba5a4d61576524504d29f33cc9690872",
      "source_line_count": 97,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-205",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/08-kv-cache.md",
      "title": "第 08 章　KV cache：先证明等价，再讨论加速",
      "priority": "P0",
      "treatment": "interactive",
      "concept": "KV cache 的增量计算与存储代价",
      "recommended_medium": "逐 token 缓存和因果 mask 演示",
      "reason": "并排显示重算前缀与仅新增查询，读者能看到复用内容和仍增长的历史读取。",
      "reuse_family": "kv-cache",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "full_chapter_text_read",
        "review_note": "完整阅读本章文字、公式和小段代码；未重跑配套程序或独立复验技术结论。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 8.1 为什么可以按层复用"
          },
          {
            "line": 20,
            "text": "## 8.2 prefill 与 decode 的形状不同"
          },
          {
            "line": 30,
            "text": "## 8.3 上游朴素版怎样保存状态"
          },
          {
            "line": 38,
            "text": "## 8.4 本书采用显式输入、输出缓存"
          },
          {
            "line": 53,
            "text": "## 8.5 预分配减少重复分配，却引入容量管理"
          },
          {
            "line": 61,
            "text": "## 8.6 丢弃 K/V 不等于重算截断窗口"
          },
          {
            "line": 67,
            "text": "## 8.7 绝对位置容量仍是硬限制"
          },
          {
            "line": 73,
            "text": "## 8.8 计算量与存储要分开算"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "假设模型已经读过六个 token，现在只新增第七个。没有缓存的生成循环会重新计算前六个位置的所有层。因果模型中，前六个位置看不到第七个，它们原先的表示不会因为追加未来 token 而改变。KV cache 保存的正是这些可以复用的中间结果。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "这个结论有条件：权重不变、位置编号一致、可见上下文不变、关闭训练随机性。缓存是推理优化，不应在每次更新权重后的普通训练批次之间继续复用旧 K/V。[上游 KV cache 目录](https://github.com/rasbt/LLMs-from-scratch/tree/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch04/03_kv-cache)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "4822edc1e3006958f63bcedfcd60a56808ad5c1fb08f576385afaf38d87dbaa1",
      "source_line_count": 107,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-206",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/09_gqa_mla_kv_sharing.md",
      "title": "第 09 章　GQA、MLA 与跨层 KV 共享",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "GQA、MLA、跨层共享的压缩方向",
      "recommended_medium": "多种缓存账本对照器",
      "reason": "分别改变 KV 头数、潜在维度、独立生产层数，明确它们改变架构而非任意 MHA 的等价开关。",
      "reuse_family": "kv-budget",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 09.1 先把缓存账算在正确的对象上"
          },
          {
            "line": 15,
            "text": "## 09.2 GQA：多个问题，可以共用一套历史记录"
          },
          {
            "line": 29,
            "text": "## 09.3 MLA：先存一个低维向量，需要时再展开"
          },
          {
            "line": 47,
            "text": "## 09.4 跨层共享：消费者仍然提问，生产者负责写入"
          },
          {
            "line": 57,
            "text": "## 09.5 从注意力接回 GPT：哪些步骤没有改变"
          },
          {
            "line": 65,
            "text": "## 09.6 可复现实验、练习与答案"
          },
          {
            "line": 76,
            "text": "### 参考资料与边界"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "生成一句话时，模型每次只增加一个 token，却要反复读取历史的 key 和 value。第 08 章用缓存避免了重复投影；本章进一步问：缓存里的东西，能不能少存一些？三种方法压缩的是三个不同方向：GQA 减少 KV 头，MLA 减少每个 token 的存储维度，跨层共享减少独立保存 KV 的层数。它们改变了模型的约束，不能在任意已训练 MHA 模型上直接打开而期待输出不变。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本章源码基准为固定提交中的 [GQA](https://github.com/rasbt/LLMs-from-scratch/tree/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch04/04_gqa)、[MLA](https://github.com/rasbt/LLMs-from-scratch/tree/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch04/05_mla) 和 [KV sharing](https://github.com/rasbt/LLMs-from-scratch/tree/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch04/10_kv-sharing)。以下推导和小实验为本书原创；上游的随机模型生成和 GPU 时间不是本书实测。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "811666f49caa8c2517ee0a0d5451542f335e4433d2ead3746e6221349150312b",
      "source_line_count": 82,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-207",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/10_swa_dsa.md",
      "title": "第 10 章　局部与稀疏注意力：SWA 和 DSA",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "SWA 窗口与稀疏选择",
      "recommended_medium": "可见位置/实际计算量双视图",
      "reason": "拖动当前位置和窗口高亮可见键，区分 mask 稀疏与内核少算；DSA 分数仅用教学固定值。",
      "reuse_family": "attention-visibility",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 5,
            "text": "## 10.1 SWA 的窗口到底包含谁"
          },
          {
            "line": 17,
            "text": "## 10.2 持久缓存可以只留 W，当前计算却未必只用 W"
          },
          {
            "line": 29,
            "text": "## 10.3 DSA：先索引，再做注意力"
          },
          {
            "line": 43,
            "text": "## 10.4 稀疏选择不等于稀疏执行，也不等于自动学会选择"
          },
          {
            "line": 49,
            "text": "## 10.5 配套脚本与验证路径"
          },
          {
            "line": 57,
            "text": "## 10.6 补充练习与答案"
          },
          {
            "line": 66,
            "text": "### 参考资料与未验证边界"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "第 09 章主要减少历史的存储方式。本章减少一次 query 实际读取的历史位置：SWA 按距离选择，DSA 按学习到的分数选择。必须同时检查两件事：输出是否只使用允许的位置，以及程序是否真的少做了计算。给稠密矩阵涂上一层 mask，只完成前一件事。",
            "truncated": false
          },
          {
            "line": 11,
            "text": "例如 $W=3,t=4$，可见位置是 2、3、4，数量为 3。源码 `MultiHeadAttentionWithSWA` 先形成差值 $\\Delta_{t,j}=t-j$，再以 `(diff < 0) | (diff >= W)` 标记禁止位置。第一项挡未来，第二项挡过远历史。只写因果条件并没有滑窗；写成 `diff > W` 则多放进一个 token。见 [固定 SWA 源码](https://github.com/rasbt/LLMs-from-scratch/blob/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch04/06_swa/gpt_with_kv_swa.py)。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "c2a07b4ea253946a109ebcb359928983b33af256ccc37f88f7c625ef39cefcdb",
      "source_line_count": 72,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-208",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/11_gated_deltanet.md",
      "title": "第 11 章　Gated DeltaNet：从矩阵状态理解递推",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "Delta rule 的矩阵记忆更新",
      "recommended_medium": "二维 key/value 状态步进器",
      "reason": "用小矩阵逐次写入和读取，展示误差修正；不标成 softmax attention 的等价版本。",
      "reuse_family": "recurrent-memory",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 11.1 一个 key 对应一行读出方向"
          },
          {
            "line": 13,
            "text": "## 11.2 Delta rule 用预测误差修正记忆"
          },
          {
            "line": 31,
            "text": "## 11.3 源码每一步的形状与顺序"
          },
          {
            "line": 41,
            "text": "## 11.4 为什么可以分块，为什么仍不能称为任意 softmax 的等价替代"
          },
          {
            "line": 55,
            "text": "## 11.5 存储与计算的边界"
          },
          {
            "line": 61,
            "text": "## 11.6 真实验证与补充练习"
          },
          {
            "line": 69,
            "text": "### 参考资料"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "如果不保存每个历史 token，而只保留一个固定大小的矩阵，生成时的状态就不会随着句子长度一直增长。问题是：新信息怎样写进矩阵，旧信息怎样被读取或修正？Gated DeltaNet 给出的不是 softmax 注意力的另一种写法，而是另一种记忆更新规则。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本章沿固定提交的 [Gated DeltaNet README 与估算脚本](https://github.com/rasbt/LLMs-from-scratch/tree/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch04/08_deltanet) 展开。这个目录把核心模块放在 README 的代码块中，独立 Python 文件是内存估算绘图；不要把文件数少误解为实现内容少。完整含卷积的 Qwen3.5 模块在第 20 章继续解释。[Gated Delta Networks 原论文](https://arxiv.org/abs/2412.06464) 是递推规则的研究来源。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "637673dd030323d5e784e2ea301a823a9a0461143b0e78be976ecd781ed78d43",
      "source_line_count": 73,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-209",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/12_moe.md",
      "title": "第 12 章　MoE：路由、专家与负载",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "MoE 路由与专家负载",
      "recommended_medium": "固定路由分数的 token 分派图",
      "reason": "改变 top-k 和偏斜分数后看各专家收到多少 token，分开总参数、激活参数和常驻内存。",
      "reuse_family": "moe-routing",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 12.1 路由器决定“谁来计算”，专家决定“计算什么”"
          },
          {
            "line": 23,
            "text": "## 12.2 为什么先展平，再按专家收集 token"
          },
          {
            "line": 33,
            "text": "## 12.3 总参数、激活参数与内存是三笔账"
          },
          {
            "line": 41,
            "text": "## 12.4 负载集中为什么会成为问题"
          },
          {
            "line": 49,
            "text": "## 12.5 接回主干、验证与练习"
          },
          {
            "line": 62,
            "text": "### 参考资料"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "一个前馈层的参数很多，但每个 token 是否都需要用到全部参数？混合专家模型（Mixture of Experts，MoE）把一层前馈换成多个专家，由路由器选择少数专家参与当前 token 的计算。它通常增加总参数，而不是把整个模型变小。收益来自每个 token 激活的专家较少，代价来自权重常驻、分派开销和负载不均。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本章解释固定提交 [ch04/07_moe](https://github.com/rasbt/LLMs-from-scratch/tree/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch04/07_moe) 的代码。这里的注意力仍是带 KV cache 的普通 MHA，MoE 换掉的是前馈模块。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "d368bffab7bdc4b2b6ef98e958fb21becc7e3a5df49a1251d1cf2b21ee50ec0b",
      "source_line_count": 67,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-210",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/13-pretraining-loss.md",
      "title": "第 13 章　预训练目标、交叉熵与验证集",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "交叉熵、标签配对与分母",
      "recommended_medium": "概率/目标高亮与损失归约器",
      "reason": "复用概率和目标对齐组件，展示每个有效位置贡献而非只看一个平均 loss。",
      "reuse_family": "probability-loss",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 13.1 交叉熵取的是目标 ID 对应的概率"
          },
          {
            "line": 26,
            "text": "## 13.2 flatten 不应该打乱位置与标签配对"
          },
          {
            "line": 34,
            "text": "## 13.3 perplexity 依赖分词与计量单位"
          },
          {
            "line": 40,
            "text": "## 13.4 验证集要在造重叠窗口之前分开"
          },
          {
            "line": 46,
            "text": "## 13.5 loader 平均要明确分母"
          },
          {
            "line": 54,
            "text": "## 13.6 训练、评价、采样三段的模式切换"
          },
          {
            "line": 62,
            "text": "## 13.7 AdamW 更新与短实验的目的"
          },
          {
            "line": 72,
            "text": "## 13.8 如何读上游训练脚本和测试"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "模型已经输出 `[B,T,V]` 的 logits，接下来要把“正确续写的分数不够高”变成可优化的标量。训练循环本身只有几步，真正容易出错的是：哪个位置预测哪个 token、损失平均了哪些项目、验证数据是否泄漏，以及图上的下降究竟说明什么。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本章对应上游 ch05 主 notebook 的训练部分、`gpt_train.py` 及相关练习。生成策略放在第 14 章，保存恢复与官方权重映射放在第 15 章，避免把三种不同责任混在一个长脚本里。[固定训练脚本](https://github.com/rasbt/LLMs-from-scratch/blob/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch05/01_main-chapter-code/gpt_train.py)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "bcfad0d21261dcb24dffd4bb82356851c2e24e051051645171a76fb0beea7ed8",
      "source_line_count": 94,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-211",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/14-sampling-generation.md",
      "title": "第 14 章　采样、温度、top-k 与生成边界",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "温度、top-k 和生成停止",
      "recommended_medium": "共享采样分布组件",
      "reason": "以自造 logits 重算分布，展示停止规则与上下文限制，避免把随机文字当质量证据。",
      "reuse_family": "sampling",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 14.1 贪心选择不需要 softmax"
          },
          {
            "line": 13,
            "text": "## 14.2 温度改变概率比值"
          },
          {
            "line": 27,
            "text": "## 14.3 top-k 删除支持集之外的候选"
          },
          {
            "line": 35,
            "text": "## 14.4 采样频数只能近似概率"
          },
          {
            "line": 43,
            "text": "## 14.5 一个生成循环应有明确的停止协议"
          },
          {
            "line": 51,
            "text": "## 14.6 上下文截断与 KV cache 不能混用旧假设"
          },
          {
            "line": 57,
            "text": "## 14.7 复现采样时应记录的不止 seed"
          },
          {
            "line": 63,
            "text": "## 14.8 习题与复查"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "模型给的是候选 token 的分布，生成程序才决定选哪个、是否继续以及怎样截取上下文。降低温度不会更新权重，top-k 也不会让模型学到新知识。它们改变的是同一组 logits 被使用的方式。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本章对应上游 ch05 的温度、频数、top-k、完整 generate 函数和生成练习；真实小实验只使用自造 logits，不把随机小模型输出写成自然语言质量证据。[上游生成脚本](https://github.com/rasbt/LLMs-from-scratch/blob/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch05/01_main-chapter-code/gpt_generate.py)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "e09ba5a4c62a6cba46428af94e6dd7a7f2dbe8bbf6d6d2a74145b2567651a8d3",
      "source_line_count": 77,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-212",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/15-weight-loading.md",
      "title": "第 15 章　权重格式、参数映射与低内存加载",
      "priority": "P2",
      "treatment": "static",
      "concept": "权重映射、共享关系与加载峰值",
      "recommended_medium": "参数映射和加载对象生命周期图",
      "reason": "真实加载正确性需数值校验，静态图可解释为什么一度有两份权重。",
      "reuse_family": "weight-mapping",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 15.1 先区分推理恢复与训练恢复"
          },
          {
            "line": 15,
            "text": "## 15.2 官方 GPT-2 的名字怎样映射到教学模型"
          },
          {
            "line": 37,
            "text": "## 15.3 assign 会替换参数对象"
          },
          {
            "line": 45,
            "text": "## 15.4 三种替代文件入口"
          },
          {
            "line": 55,
            "text": "## 15.5 下载器的成功条件不能只看文件存在"
          },
          {
            "line": 63,
            "text": "## 15.6 加载峰值为何可能接近两份权重"
          },
          {
            "line": 71,
            "text": "## 15.7 低内存加载不能漏掉 buffer"
          },
          {
            "line": 79,
            "text": "## 15.8 mmap 与 assign 的小型实测"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "下载一个权重文件和得到正确模型之间，还隔着结构、名字、形状、布局、dtype、设备及共享关系。只检查 `load_state_dict` 没有报错，不能证明 tokenizer 与模型兼容；只检查每层形状相同，也不能发现 Q、K 被交换或方阵忘记转置。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本章覆盖 ch05 主代码的保存恢复、TensorFlow GPT-2 导入、三种替代加载 notebook，以及低内存 state_dict notebook。所有实际运行只读写本书自己创建的小检查点，不下载预训练权重。[替代加载](https://github.com/rasbt/LLMs-from-scratch/tree/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch05/02_alternative_weight_loading)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "fa4d37076aa7df95bb16275d399a46524672fb51e02395e6ea519d8245a2d9d8",
      "source_line_count": 116,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-213",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/16-corpus-gutenberg.md",
      "title": "第 16 章　语料整理与 Gutenberg 训练工程",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "语料清理、切分与恢复工程",
      "recommended_medium": "产物检查、来源记录与本地小管道",
      "reason": "现有可运行流程比新增交互更能证明文件边界和去重正确。",
      "reuse_family": "data-engineering",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 16.1 语料清单应先于合并大文件"
          },
          {
            "line": 13,
            "text": "## 16.2 上游清理脚本逐步做了什么"
          },
          {
            "line": 21,
            "text": "## 16.3 分片大小要把分隔符也算进去"
          },
          {
            "line": 29,
            "text": "## 16.4 去重应发生在划分审计中"
          },
          {
            "line": 37,
            "text": "## 16.5 按文件训练怎样管理进度"
          },
          {
            "line": 45,
            "text": "## 16.6 检查点频率代码值得逐字符核对"
          },
          {
            "line": 53,
            "text": "## 16.7 ETA 与日志也有计量假设"
          },
          {
            "line": 59,
            "text": "## 16.8 CLI 与测试入口的边界"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "把训练文本从一篇变成一千篇，困难不只是循环多跑几次。文件编码、重复文档、边界标记、分片大小、顺序、验证集和中断恢复都会影响训练。数据管道的错误往往仍会输出一个下降的 loss，因此要先检查数据产物，再启动训练。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "上游 Gutenberg bonus 提供整理脚本和按文件训练脚本。本书只分析公开代码，并用六条自造记录验证小型离线管道；没有下载整套 Gutenberg 语料，也没有据此训练模型。[固定版本目录](https://github.com/rasbt/LLMs-from-scratch/tree/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch05/03_bonus_pretraining_on_gutenberg)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "76a042e06ec373012884a41884130f8d33e4f0bb1d93fb49e254303190aa59e7",
      "source_line_count": 83,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-214",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/17-schedules-clipping-muon.md",
      "title": "第 17 章　学习率、梯度裁剪、超参数与 Muon",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "学习率与梯度裁剪边界",
      "recommended_medium": "日程曲线和小向量裁剪图",
      "reason": "选定一个小向量显示整组等比缩放，再核对日程端点；Muon 比较留给真实实验。",
      "reuse_family": "learning-rate",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 17.1 先约定 step 表示什么"
          },
          {
            "line": 23,
            "text": "## 17.2 为什么先升后降"
          },
          {
            "line": 33,
            "text": "## 17.3 梯度裁剪缩放整组向量"
          },
          {
            "line": 47,
            "text": "## 17.4 日程和裁剪的边界要一起测"
          },
          {
            "line": 55,
            "text": "## 17.5 网格搜索的乘法预算"
          },
          {
            "line": 69,
            "text": "## 17.6 搜索脚本的失败路径也属于实验"
          },
          {
            "line": 77,
            "text": "## 17.7 Muon 改变的是矩阵更新方向"
          },
          {
            "line": 85,
            "text": "## 17.8 两个优化器必须不重不漏地分参数"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "一个训练循环能下降，不代表它已经适合更长训练或更大模型。学习率决定每步更新幅度，裁剪限制异常梯度，超参数搜索决定怎样比较实验，优化器则规定如何利用梯度历史。这些机制最好逐项引入，每加一项都先检查边界。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "上游 ch05 的 scheduler 目录指向 Appendix D，另有网格搜索脚本和 Muon 双优化器示例。本章覆盖这些路径，不把短文本上的一次结果当作优化器排名。[Appendix D](https://github.com/rasbt/LLMs-from-scratch/blob/cdbd33e6d57a71e20ea31434841095fac50cecd8/appendix-D/01_main-chapter-code/appendix-D.ipynb)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "3413a36a578fc5c6445560ef4a6a143a3527f5e9936b4ed4542d438e6c85df3f",
      "source_line_count": 109,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-215",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/18-performance-devices.md",
      "title": "第 18 章　训练性能及 CPU、MPS 数值差异",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "设备、精度和计时条件",
      "recommended_medium": "真实 CPU 检查与未运行范围",
      "reason": "没有对应 GPU/MPS 结果就不画加速滑块，保留复测步骤和数值边界。",
      "reuse_family": "benchmark-evidence",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 18.1 先建立可比较的基线"
          },
          {
            "line": 15,
            "text": "## 18.2 减少中间张量和调用开销"
          },
          {
            "line": 21,
            "text": "## 18.3 TF32、bfloat16 与 autocast 不是同一件事"
          },
          {
            "line": 29,
            "text": "## 18.4 pinned memory 在主机，不是预留显存"
          },
          {
            "line": 37,
            "text": "## 18.5 编译收益需要排除首次成本"
          },
          {
            "line": 45,
            "text": "## 18.6 词表对齐增加的行不是合法文本 token"
          },
          {
            "line": 51,
            "text": "## 18.7 计时边界能让同一个数字有不同含义"
          },
          {
            "line": 59,
            "text": "## 18.8 DDP 同步参数梯度，不会自动汇总所有指标"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "优化一个实现时，应该同时回答两个问题：它少花了哪些时间或内存，仍然在计算什么。把模型转为 bfloat16、替换注意力内核、增加批量或扩大词表，都可能提高吞吐，却也可能改变数值、目标函数或训练预算。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "本章覆盖上游 `00_orig.py`、`01_opt_single_gpu.py`、`02_opt_multi_gpu_ddp.py`，以及 CPU/MPS 随机采样 notebook。只运行了 CPU 数学和随机数检查，没有执行 CUDA、MPS、torch.compile 性能或真实 DDP。[性能脚本目录](https://github.com/rasbt/LLMs-from-scratch/tree/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch05/10_llm-training-speed)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "9aff9cf84395bac4817b05eb2e1c9f822924ee4c91fab2314a8754722b64fe78",
      "source_line_count": 96,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-216",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/19_gpt_to_llama.md",
      "title": "第 19 章　从 GPT 到 Llama：RoPE、RMSNorm 与门控前馈",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "RoPE 二维旋转与相对位移",
      "recommended_medium": "可拖动位置的向量旋转图",
      "reason": "固定向量改变两个位置，观察点积依赖相对位移；明确代数性质不保证长上下文能力。",
      "reuse_family": "rope",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 19.1 RMSNorm 保留均值，只调节整体幅度"
          },
          {
            "line": 17,
            "text": "## 19.2 SwiGLU 的两个分支分别做什么"
          },
          {
            "line": 27,
            "text": "## 19.3 RoPE 把位置变成二维旋转"
          },
          {
            "line": 45,
            "text": "## 19.4 Llama 3 的 GQA 与长上下文频率"
          },
          {
            "line": 53,
            "text": "## 19.5 分词器、对话边界与权重布局是一整套协议"
          },
          {
            "line": 65,
            "text": "## 19.6 主干外的辅助步骤与源码差异"
          },
          {
            "line": 71,
            "text": "## 19.7 真实检查、练习与答案"
          },
          {
            "line": 79,
            "text": "## 19.8 上游测试怎样把错误定位到零件"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "“把 GPT 改成 Llama”容易被误解成改几个配置数值。真正需要改变的是模型函数：怎样归一化、怎样表达位置、怎样组合前馈支路，以及如何把 checkpoint 的参数放到正确位置。形状对得上只是第一步，数值约定对得上才可能加载成功。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "固定源码 [ch05/07_gpt_to_llama](https://github.com/rasbt/LLMs-from-scratch/tree/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch05/07_gpt_to_llama) 包括 GPT→Llama 2、Llama 2→Llama 3/3.1/3.2 和独立 Llama 3.2 三份 notebook。转换指的是逐步修改实现，不是把 GPT 权重无损变成 Llama 权重。本章不下载任何 pretrained checkpoint。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "1291f80e5240ed1c0b3b75973df28b2513252c9c8df13cdb9e35f4a15696f0fd",
      "source_line_count": 91,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-217",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/20_qwen3_qwen35.md",
      "title": "第 20 章　Qwen3 与 Qwen3.5：同一个名字下，两种记忆接口",
      "priority": "P2",
      "treatment": "static",
      "concept": "全局注意力与递归混合缓存",
      "recommended_medium": "按层状态类型和位置计数图",
      "reason": "模型版本接口复杂，静态状态表更可靠；分别链接 KV 与递归记忆演示。",
      "reuse_family": "model-state-contracts",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 20.1 Qwen3：Q/K 归一化发生在每个头内部"
          },
          {
            "line": 17,
            "text": "## 20.2 Qwen3 MoE：注意力不变，前馈变为按 token 分派"
          },
          {
            "line": 25,
            "text": "## 20.3 Qwen3 缓存：位置计数和 KV 容器缺一不可"
          },
          {
            "line": 33,
            "text": "## 20.4 Qwen3.5 的全局层：局部旋转与额外输出门"
          },
          {
            "line": 45,
            "text": "## 20.5 线性层：卷积记忆加矩阵记忆"
          },
          {
            "line": 59,
            "text": "## 20.6 chunk 与 recurrent helper 如何相互校验"
          },
          {
            "line": 69,
            "text": "## 20.7 混合缓存的一个重要限制"
          },
          {
            "line": 75,
            "text": "## 20.8 加载、真实结果与练习"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "Qwen3 的密集版和 MoE 版主要在前馈模块上分岔；Qwen3.5 的教学实现又引入全局注意力与 Gated DeltaNet 的混合。因此“换 checkpoint 名称”不足以完成模型替换。投影宽度、归一化参数含义、位置旋转范围、缓存种类都必须一起核对。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "来源为固定提交 [ch05/11_qwen3](https://github.com/rasbt/LLMs-from-scratch/tree/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch05/11_qwen3) 的四份 notebook，以及 [ch05/16_qwen3.5](https://github.com/rasbt/LLMs-from-scratch/tree/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch05/16_qwen3.5) 的两份 notebook 与 helper。这里限定为源码中的文本模型，不涵盖整个系列的多模态组件。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "4e27ed954a04d7953d8c78cfaa20fc23bc730d695f65a8931f6625a4d7f3c90b",
      "source_line_count": 101,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-218",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/21_gemma3_gemma4.md",
      "title": "第 21 章　Gemma 3、Gemma 4：局部层、共享层与数值约定",
      "priority": "P2",
      "treatment": "static",
      "concept": "局部/全局层与 KV 共享生产者",
      "recommended_medium": "按层消费者/生产者依赖图",
      "reason": "共享路径和截断时机需要精确核对，静态图有助于发现取错层或丢失 prompt。",
      "reuse_family": "model-state-contracts",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 21.1 Gemma 3：残差分支前后都要归一化"
          },
          {
            "line": 17,
            "text": "## 21.2 局部/全局层使用不同的位置表和 mask"
          },
          {
            "line": 25,
            "text": "## 21.3 Gemma 4：局部头和全局头可以不同宽"
          },
          {
            "line": 33,
            "text": "## 21.4 proportional RoPE 保留不旋转的子空间"
          },
          {
            "line": 39,
            "text": "## 21.5 KV 共享必须寻找同一种层的生产者"
          },
          {
            "line": 49,
            "text": "## 21.6 逐层输入：每一层都有自己的 token 侧信息"
          },
          {
            "line": 63,
            "text": "## 21.7 加载器和后半段 notebook 的职责"
          },
          {
            "line": 69,
            "text": "## 21.8 实测与补充题"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "Gemma 的教学实现很适合练习“同样叫 RMSNorm、RoPE、GQA，为什么不能交换零件”。Gemma 3 的零中心 norm 参数、局部/全局双套位置频率，以及 Gemma 4 的跨层 KV 共享和逐层输入，都影响真实计算路径。本章按数据流解释，不把模型名当成配置标签。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "固定来源是 [Gemma 3 两份 notebook](https://github.com/rasbt/LLMs-from-scratch/tree/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch05/12_gemma3) 与 [Gemma 4 E2B/E4B 两份 notebook](https://github.com/rasbt/LLMs-from-scratch/tree/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch05/17_gemma4)。范围是文本前向与生成，不包括视觉、音频模块或全系列配置。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "d65dbe7a2e27f06771dba0ef8987eb0e224b5c5eecd8a9c23d44b8e593cac034",
      "source_line_count": 99,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-219",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/22_olmo_tiny_aya_interfaces.md",
      "title": "第 22 章　OLMo 3、Tiny Aya 与可替换模型接口",
      "priority": "P2",
      "treatment": "static",
      "concept": "模型接口与位置、状态语义",
      "recommended_medium": "接口契约和架构差异对照",
      "reason": "可替换不等于权重兼容，图表保留具体差异，比统一交互外壳更诚实。",
      "reuse_family": "model-state-contracts",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 22.1 OLMo 3 把 norm 放在分支输出"
          },
          {
            "line": 19,
            "text": "## 22.2 YaRN 不是统一放慢所有频率"
          },
          {
            "line": 31,
            "text": "## 22.3 Tiny Aya：无 bias 的 LayerNorm 仍然减均值"
          },
          {
            "line": 43,
            "text": "## 22.4 Tiny Aya 的位置配对与全局层例外"
          },
          {
            "line": 53,
            "text": "## 22.5 可替换接口：先约定张量，再约定状态"
          },
          {
            "line": 61,
            "text": "## 22.6 用 Llama/Qwen3 重走第五章训练"
          },
          {
            "line": 71,
            "text": "## 22.7 真实检查与补充练习"
          },
          {
            "line": 79,
            "text": "## 22.8 缓存差异与逐层参考测试"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "训练代码通常只要求模型把 token ID 变成 logits。因此我们可以保留同一个训练循环，替换内部架构。但“接口兼容”不等于“权重兼容”，也不等于换一个类名就能保留位置、分词和缓存语义。本章用 OLMo 3 与 Tiny Aya 的具体差异说明这条边界，再把它接回第五章的训练流程。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "固定来源：[OLMo 3](https://github.com/rasbt/LLMs-from-scratch/tree/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch05/13_olmo3)、[Tiny Aya](https://github.com/rasbt/LLMs-from-scratch/tree/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch05/15_tiny-aya)、[用其他模型重走训练章节](https://github.com/rasbt/LLMs-from-scratch/tree/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch05/14_ch05_with_other_llms)。每个模型都有完整前向与 KV-cache 版本；训练目录另有 Llama 3.2 和 Qwen3 notebook。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "42d18fdbeb483efaed6ae558e3c53b9a6d706c9d8701af3f393924cdd7ac5e61",
      "source_line_count": 93,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-220",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/23_extend_tokenizer.md",
      "title": "第 23 章　扩展分词器：新 ID 还需要新的模型行",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "新增 token 与 embedding/输出头扩展",
      "recommended_medium": "词表行数和 softmax 分母演示",
      "reason": "加一行后显示旧 logits 即使不变概率也会变，能说明扩词表不只是新增一个 ID。",
      "reuse_family": "vocabulary-extension",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 23.1 特殊 token 与普通 BPE 合并项不同"
          },
          {
            "line": 17,
            "text": "## 23.2 输入 embedding 为什么必须增加行"
          },
          {
            "line": 25,
            "text": "## 23.3 输出头也必须扩展，但旧概率不会保持不变"
          },
          {
            "line": 35,
            "text": "## 23.4 Weight tying 必须共享对象，不只是值相同"
          },
          {
            "line": 41,
            "text": "## 23.5 优化器、数据与 checkpoint 都要一起迁移"
          },
          {
            "line": 49,
            "text": "## 23.6 真实小实验、源单元与练习"
          },
          {
            "line": 59,
            "text": "## 23.7 不下载词表的真实 tiktoken 检查"
          },
          {
            "line": 63,
            "text": "### 参考资料与未运行边界"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "给分词器加一个特殊 token，程序可能立刻能把它编码成一个整数。但模型看到这个整数以后，第一步就会去 embedding 表查相应的行。如果表没有扩大，结果是越界；如果表扩大了却没训练新行，结果只是“可以计算”，还谈不上“理解了新符号”。本章把分词器、输入表、输出表和优化器连起来处理。",
            "truncated": false
          },
          {
            "line": 5,
            "text": "固定来源是 [extend-tiktoken.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch05/09_extending-tokenizers/extend-tiktoken.ipynb)。源码演示 GPT-2 词表增加两个特殊项，本书不下载 GPT-2 权重，改用本地小张量检验兼容性。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "b77ca6575a957b81a6bb3a57be50cb1739ab89058b5445ef71cc19b9cfaae556",
      "source_line_count": 67,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-221",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/24_classification.md",
      "title": "第24章　文本分类：一个输出位置，决定模型看见多少内容",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "分类位置、padding 与可见上下文",
      "recommended_medium": "输出位置选择与因果可见图",
      "reason": "选择第一个、最后有效和最后补齐位置，显示分类头实际读到了哪些 token。",
      "reuse_family": "attention-visibility",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 5,
            "text": "## 24.1 从逐词预测改成整段判断"
          },
          {
            "line": 25,
            "text": "## 24.2 数据集先确定长度，再划清训练与评估"
          },
          {
            "line": 33,
            "text": "## 24.3 冻结不是切断整条计算图"
          },
          {
            "line": 54,
            "text": "## 24.4 附加实验怎样构成有意义的对照"
          },
          {
            "line": 64,
            "text": "## 24.5 IMDb：先问表示是否值得付出成本"
          },
          {
            "line": 72,
            "text": "## 24.6 保存、恢复与检查"
          },
          {
            "line": 78,
            "text": "### 练习与答案"
          },
          {
            "line": 84,
            "text": "### 来源"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "把一封短信分成两类，看起来只需把 GPT 的词表输出换成两个数字。难处藏在后面：这两个数字取自哪个位置？补齐后的最后一列算不算文本结尾？冻结了参数以后，优化器真的不会再动它吗？本章沿着这些问题，把分类微调拆成可检查的步骤。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "设输入 token 为 X，形状为 B×T，其中 B 是批量大小，T 是补齐长度。GPT 最后一层产生 $H\\in\\mathbb R^{B\\times T\\times d}$，d 为隐藏维度。语言模型的输出矩阵原来有 V 行，每行对应一个词表项；分类器改用 $W_c\\in\\mathbb R^{C\\times d}$ 和偏置 $b_c\\in\\mathbb R^C$，得到 $Z=HW_c^\\top+b_c$，形状为 B×T×C。垃圾信息分类中 C=2。替换输出头不会自动把时间轴 T 消掉，还必须决定从哪一行 H 提取整段信息。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "bf80afdc30cb1995131ffc39469b15c7854f1ea114632c516faadf573ba5cf7f",
      "source_line_count": 86,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-222",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/25_instruction_tuning.md",
      "title": "第25章　指令微调：先把“该预测什么”排对",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "指令模板、shift 与两种 mask",
      "recommended_medium": "变长样本装箱/监督查看器",
      "reason": "逐格标明 prompt、回答、padding、attention mask 与 loss mask，能抓出一格偏移和全被截断。",
      "reuse_family": "supervision-mask",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 5,
            "text": "## 25.1 模板是模型输入的一部分"
          },
          {
            "line": 13,
            "text": "## 25.2 从三条变长序列到一个 batch"
          },
          {
            "line": 30,
            "text": "## 25.3 两种掩码不能互相代替"
          },
          {
            "line": 50,
            "text": "## 25.4 截断可能把监督全部截没"
          },
          {
            "line": 56,
            "text": "## 25.5 训练与生成的闭环"
          },
          {
            "line": 64,
            "text": "### 练习与答案"
          },
          {
            "line": 72,
            "text": "### 来源"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "指令微调仍然预测下一个 token。变化在训练文本的组织：模型看见任务说明、可选输入，再学习接着写回答。模板、移位和掩码中任何一处错一格，都可能产生持续下降的 loss，却训练了另一个任务。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "上游 `format_input` 把 `instruction` 与非空 `input` 拼成统一格式，`InstructionDataset` 再追加响应分隔符和 `output`，一次性分词并缓存 token。这里的字符串不仅供人阅读，换行、空格和分隔符都会影响 token 序列。推理时必须保留训练时的提示前缀；把输出答案拼进推理输入，则会让评估看到参考答案。[来源：ch07 主源码](https://github.com/rasbt/LLMs-from-scratch/blob/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch07/01_main-chapter-code/gpt_instruction_finetuning.py)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "dd16014a3082241b48cfb71b72c1eeeb149523abf8b48bbf180fc49038c0dd4b",
      "source_line_count": 74,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-223",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/26_instruction_data.md",
      "title": "第26章　指令数据：先防止自己给自己打高分",
      "priority": "P2",
      "treatment": "static",
      "concept": "相似样本分组与评估泄漏",
      "recommended_medium": "样本关系和 split 分组图",
      "reason": "近似重复仍需人工判断，静态关系图更能保留“不确定候选”这一性质。",
      "reuse_family": "data-leakage",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 5,
            "text": "## 26.1 近似重复是候选关系，不是事实判决"
          },
          {
            "line": 20,
            "text": "## 26.2 先分组，再切分"
          },
          {
            "line": 30,
            "text": "## 26.3 被动语态生成与反思修订"
          },
          {
            "line": 40,
            "text": "## 26.4 模型评分该记录什么"
          },
          {
            "line": 55,
            "text": "## 26.5 相关不等于校准正确"
          },
          {
            "line": 61,
            "text": "### 练习与答案"
          },
          {
            "line": 67,
            "text": "### 来源"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "数据集里出现两种问法相近的问题，不一定是坏事；同一问题的改写分别进入训练集和测试集，却可能让评估显得过于容易。本章把去重、生成、修订和评分连成一条可审计的数据链。上游同时提供 OpenAI API 与本地 Ollama 路线；本书只静态阅读这些代码，并运行无外部服务的人工数据检查。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "`preprocess_text` 小写化并删除标点，随后 `TfidfVectorizer` 对字符1至3元组编码。这样 “cat” 会贡献单字符、双字符和三字符特征。TF-IDF 降低频繁特征的区分力，余弦相似度 $s(a,b)=\\frac{a^\\top b}{\\|a\\|_2\\|b\\|_2}$ 衡量方向接近程度。若有 N 条文本，完整相似度矩阵为 N×N，空间随 N² 增长；扩到大型语料时应分块或先检索候选，不能只增加内存碰碰运气。[来源：find-near-duplicates.py](https://github.com/rasbt/LLMs-from-scratch/blob/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch07/02_dataset-utilities/find-near-duplicates.py)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "8711621dac4c87ce96a83ff22f77d1427a2d3cc4aed83be04728fedf9ec0b5d1",
      "source_line_count": 69,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-224",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/27_dpo.md",
      "title": "第27章　DPO：比较两个回答，也要说明比较的基准",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "DPO 四组对数概率与参考差",
      "recommended_medium": "明确公式的偏好损失演算器",
      "reason": "逐项改变策略/参考模型分数和 beta，显示相对差；默认数据均为教学标量，不声称训练效果。",
      "reuse_family": "preference-loss",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 5,
            "text": "## 27.1 偏好对不是两个独立分类样本"
          },
          {
            "line": 11,
            "text": "## 27.2 从受约束奖励到概率比"
          },
          {
            "line": 33,
            "text": "## 27.3 为什么要用稳定的 logsigmoid"
          },
          {
            "line": 48,
            "text": "## 27.4 从 token logits 取得回答分数"
          },
          {
            "line": 66,
            "text": "## 27.5 参考模型、训练与日志"
          },
          {
            "line": 74,
            "text": "### 练习与答案"
          },
          {
            "line": 80,
            "text": "### 来源"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "监督微调提供一个目标回答，偏好数据提供一个排序：同一提示下，回答 y⁺ 比 y⁻ 更合适。DPO 把这种排序变成可微目标。要写对它，必须先分清四组对数概率、序列归约方式，以及参考模型为什么保持不变。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "数据条目包含 instruction、input、chosen、rejected。两段回答共享提示 x，否则偏好差异可能只是题目难度差异。`create-preference-data-ollama.ipynb` 的 `generate_model_responses` 请求本地模型产生偏好材料，`format_input` 仍维持指令格式。自动生成的 chosen 标签只表达构造流程的偏好假设，不等于已经有人工共识。[来源：偏好数据生成](https://github.com/rasbt/LLMs-from-scratch/blob/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch07/04_preference-tuning-with-dpo/create-preference-data-ollama.ipynb)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "ed953437dd9fc2b8031866c713d76e4dfee97a7e99f6e385838536fb6f982dbc",
      "source_line_count": 82,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-225",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/28_lora.md",
      "title": "第28章　LoRA：冻结大矩阵，学习一个可合并的增量",
      "priority": "P1",
      "treatment": "interactive",
      "concept": "LoRA 矩阵朝向、低秩增量与合并",
      "recommended_medium": "小矩阵乘法和参数账本",
      "reason": "改 rank 并展示 BA 与 W 的形状及参数量，可验收合并前后输出一致，不宣称内存只剩低秩参数。",
      "reuse_family": "lora",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 5,
            "text": "## 28.1 先统一矩阵朝向"
          },
          {
            "line": 17,
            "text": "## 28.2 缩放约定必须跟代码走"
          },
          {
            "line": 29,
            "text": "## 28.3 零初始化其中一边，为什么还能学"
          },
          {
            "line": 39,
            "text": "## 28.4 替换线性层与参数管理"
          },
          {
            "line": 47,
            "text": "## 28.5 合并与反向合并"
          },
          {
            "line": 62,
            "text": "### 练习与答案"
          },
          {
            "line": 68,
            "text": "### 来源"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "如果每个任务都更新一整套模型，保存与切换任务都很昂贵。LoRA 把每个选中线性层的更新限制为两个小矩阵的乘积。它减少的是可训练更新的自由度，并不让原模型在前向时消失。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "PyTorch 线性层保存 $W\\in\\mathbb R^{d_{\\mathrm{out}}\\times d_{\\mathrm{in}}}$，输入 $X\\in\\mathbb R^{N\\times d_{\\mathrm{in}}}$，输出为 $XW^\\top+b$。这里 N 可以是 batch与时间位置合并后的数量，偏置 $b\\in\\mathbb R^{d_{\\mathrm{out}}}$。按上游代码的行向量约定，LoRA 参数 $A\\in\\mathbb R^{d_{\\mathrm{in}}\\times r}$、$B\\in\\mathbb R^{r\\times d_{\\mathrm{out}}}$，输出改为",
            "truncated": false
          }
        ]
      },
      "source_sha256": "bb9799f52807c8900d6f407ee5bc33d4d6ae30876ec09b1736318a82d86368e3",
      "source_line_count": 70,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-226",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/29_interfaces.md",
      "title": "第29章　三种界面，同一条推理链路",
      "priority": "P2",
      "treatment": "static",
      "concept": "加载、编码、推理和显示接口",
      "recommended_medium": "三种任务共用/差异路线图",
      "reason": "界面本身已有示例，重点应保留权重和输入契约，避免再搭一个无模型聊天框。",
      "reuse_family": "application-contracts",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 5,
            "text": "## 29.1 四个入口对应三类任务"
          },
          {
            "line": 19,
            "text": "## 29.2 加载成功不是推理准备完成"
          },
          {
            "line": 27,
            "text": "## 29.3 上游是一次返回，流式是补充接口"
          },
          {
            "line": 35,
            "text": "## 29.4 上下文预算属于接口，不应留给崩溃处理"
          },
          {
            "line": 43,
            "text": "### 练习与答案"
          },
          {
            "line": 49,
            "text": "### 来源"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "把模型接到聊天框，并不会让预训练模型自动学会遵循指令。上游的三个UI目录分别演示文本续写、短信分类、指令回答。共同部分是加载、编码、推理和显示；差异在权重结构、输入格式和输出解释。先把这些差异写成接口约定，再考虑流式显示。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "ch05包含 `app_orig.py` 与 `app_own.py`，前者加载OpenAI GPT-2权重，后者加载第5章自行训练得到的 `model.pth`。两者都调用generate，把输入与续写一起解码。它们属于一种任务的两个权重来源，不能因为都是124M结构就交换state_dict：自训示例上下文为256、qkv_bias=False；原始权重配置上下文为1024、qkv_bias=True。[源码：预训练UI](https://github.com/rasbt/LLMs-from-scratch/tree/cdbd33e6d57a71e20ea31434841095fac50cecd8/ch05/06_user_interface)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "0c73ebf29662acaf14c24db5ee518413dcc9ead91f74055ba27ffe6d31164b52",
      "source_line_count": 51,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-227",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/30_package_reproducibility.md",
      "title": "第30章　包接口与测试：怎样知道自己运行的是同一份模型",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "包身份、测试契约与可复现环境",
      "recommended_medium": "版本记录和真实测试入口",
      "reason": "同名代码的差异需实际导入和断言，互动不能替代来源核对。",
      "reuse_family": "reproduction",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 5,
            "text": "## 30.1 模块名相同，不代表实现细节永远相同"
          },
          {
            "line": 13,
            "text": "## 30.2 包额外承担的边界"
          },
          {
            "line": 19,
            "text": "## 30.3 测试应该说明它证明了什么"
          },
          {
            "line": 27,
            "text": "## 30.4 环境检查与持续集成"
          },
          {
            "line": 37,
            "text": "## 30.5 这份交付怎样复核"
          },
          {
            "line": 52,
            "text": "### 练习与答案"
          },
          {
            "line": 58,
            "text": "### 来源"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "Notebook适合逐步观察，包适合稳定导入。两者共存时，最常见的错误是修改了一份代码，却从另一份代码导入同名函数。本章沿着包接口、测试契约和环境记录，建立可追溯的运行方式。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "固定仓库的 `pkg/llms_from_scratch` 将章节代码整理为可导入模块。ch02提供滑窗数据集；ch03提供注意力的逐步版本和PyTorch优化版本；ch04提供基础GPT与Fast变体；ch05提供生成、训练、权重转换和下载；ch06是分类；ch07是指令数据和评估；appendix_a/d/e分别承接自动微分入门、训练改进和LoRA。[包README](https://github.com/rasbt/LLMs-from-scratch/blob/cdbd33e6d57a71e20ea31434841095fac50cecd8/pkg/llms_from_scratch/README.md)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "62b168081870f5304e8730d04daeb5844bbd3427276a1e936e6777c7d38a35fe",
      "source_line_count": 60,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-228",
      "book": "llm/llms-from-scratch",
      "book_title": "LLMs From Scratch 原理与实践",
      "path": "books/llm/llms-from-scratch/chapters/31_exercises_and_boundaries.md",
      "title": "第31章　习题闭环与延伸：知道哪里已经验证，哪里还没有",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "习题、验证范围和延伸边界",
      "recommended_medium": "习题索引与完成标准",
      "reason": "作为收束章节，重点是检验前面机制，不再增加新的交互负担。",
      "reuse_family": "reproduction",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 5,
            "text": "## 31.1 原题、公开解答与本书补充题的区别"
          },
          {
            "line": 28,
            "text": "## 31.2 用一组题检查自己是否真的能修改代码"
          },
          {
            "line": 42,
            "text": "## 31.3 独立推理仓库的边界"
          },
          {
            "line": 50,
            "text": "## 31.4 参考文献怎么用，而不只是放在书尾"
          },
          {
            "line": 58,
            "text": "## 31.5 什么叫完成这次复现"
          }
        ],
        "intro_excerpts": [
          {
            "line": 3,
            "text": "这本书的终点不是把仓库文件都列一遍，而是让读者能从一个结果追到数据、计算、参数和验证条件。最后一章把公开习题解答与前面章节重新接起来，并说明独立推理仓库为什么不能被算作本仓库的完整实现覆盖。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "上游appendix-C只提供各章解答Notebook入口，appendix-B说明没有代码；它们不提供一份可据此逐字转载的完整出版书。这里概括公开解答所演示的操作，不复制原书题面或原图。本书每章“补充题”是为自己的解释设计的检查；公开解答中保存的大模型输出和耗时属于上游记录，未在本书环境重新测量。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "5a464195a07020677886b52fcc31c24c5222436a7599b9ef850d51f7bb56f7c5",
      "source_line_count": 62,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-229",
      "book": "llm/happy-llm",
      "book_title": "Happy LLM 原理与实践",
      "path": "books/llm/happy-llm/chapters/01_nlp_representations.md",
      "title": "第 01 章　先把语言任务写成可检查的输入和输出",
      "priority": "P2",
      "treatment": "static",
      "concept": "语言任务与表示中丢失的信息",
      "recommended_medium": "同一句话的任务/表示对照图",
      "reason": "多个任务先建立清楚的输入输出，静态例子即可区分词袋、顺序和 embedding。",
      "reuse_family": "nlp-representations",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 01.1 一句中文可以对应九种不同的问题"
          },
          {
            "line": 17,
            "text": "## 01.2 从规则到统计：改变的是知识从哪里来"
          },
          {
            "line": 25,
            "text": "## 01.3 one-hot、词袋和 embedding 不能混为一谈"
          },
          {
            "line": 35,
            "text": "## 01.4 语言模型把一句话分成一串条件概率"
          },
          {
            "line": 49,
            "text": "## 01.5 练习：先找信息在哪一步丢失"
          },
          {
            "line": 55,
            "text": "## 01.6 来源、改编与继续阅读"
          }
        ],
        "intro_excerpts": [
          {
            "line": 9,
            "text": "看到“林青周五从杭州出发，去苏州参加会议”，人会自然补全背景；程序却需要先知道要交付什么。中文分词要给出词边界，子词切分要把文本变成词表能表示的片段，词性标注要给这些单位分配语法标签。三者的单位和目标不同。“杭州”被 tokenizer 分成两个字，不代表分词错误；它可能根本没有执行中文分词任务。[来源：Happy-LLM 第一章 §1.1、§1.3，固定版本见章末。]",
            "truncated": false
          },
          {
            "line": 11,
            "text": "文本分类给整段话一个类别，例如“行程”；多标签分类则可能同时给出“工作”和“交通”。实体识别要指出字符区间及类型，例如“林青”是人物，“周五”是时间。关系抽取还要连接对象，例如（林青，出发地，杭州）。只识别出杭州和苏州，尚未解决谁从哪里去哪里的问题。在真实标注中，应该保留实体起止位置，不能仅返回字符串，否则同名实体重复出现时便无法定位。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "fef655792d16bd4de30cd4855199a76b5e39077b5ad29933453d725fdf437286",
      "source_line_count": 57,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-230",
      "book": "llm/happy-llm",
      "book_title": "Happy LLM 原理与实践",
      "path": "books/llm/happy-llm/chapters/02_attention_encoder_decoder.md",
      "title": "第 02 章　注意力怎样把信息搬到需要它的位置",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "注意力的信息流与因果性",
      "recommended_medium": "共享注意力矩阵检查器",
      "reason": "用同一输入切换 encoder/decoder 可见关系，依然保留具体模型训练边界。",
      "reuse_family": "attention",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 02.1 先算一次加权平均，再解释 Q、K、V"
          },
          {
            "line": 32,
            "text": "## 02.2 自注意力、掩码和多头是三件不同的事"
          },
          {
            "line": 42,
            "text": "## 02.3 Encoder-Decoder 的两条输入流"
          },
          {
            "line": 52,
            "text": "## 02.4 没有位置，注意力不知道谁先谁后"
          },
          {
            "line": 63,
            "text": "## 02.5 逐模块阅读上游 transformer.py，辨认演示与可训练系统"
          },
          {
            "line": 79,
            "text": "## 02.6 验证因果性，要改变未来而不是只看 mask"
          },
          {
            "line": 87,
            "text": "## 02.7 来源与修改说明"
          },
          {
            "line": 91,
            "text": "## 02.8 文档片段的阅读位置"
          }
        ],
        "intro_excerpts": [
          {
            "line": 9,
            "text": "源章先用前馈、卷积和循环网络说明为什么需要另一种信息传递方式。前馈网络把输入一次映射到输出；普通 MLP 若直接吃固定长度的拼接向量，位置与长度就被写进输入尺寸。卷积使用共享局部卷积核，同一种局部模式可以在不同位置被检测，多层或扩张卷积能扩大感受野。循环网络则让隐藏状态随序列逐步更新，$h_t=f(h_{t-1},x_t)$，用同一套参数处理不同长度。这里 $x_t$ 是第 t 个输入，$h_t$ 是读完该位置后的状态。三者都能用于文本，适用性不能仅由网络名字判断。",
            "truncated": false
          },
          {
            "line": 18,
            "text": "LSTM 的门控改善了长期信息的保存，不保证所有远距离依赖都容易学；RNN 推理也不必把整条历史原文全部保存在内存里，递推状态可以流式更新。注意力的研究历史跨越多个领域，不能把源文“必定最先来自计算机视觉”的叙述当作无争议结论。NLP 中，[Bahdanau 等的机器翻译工作](https://arxiv.org/abs/1409.0473) 已将可学习对齐用于循环 encoder-decoder；Transformer 进一步以注意力和逐位置前馈层构成主要计算路径。这些比较说明设计动机，不证明任何一种结构在所有任务上最好。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "4cd3bbbe18348b2d2959e4e470d2853c47ccce94c1a86afaf10c1547f3a87884",
      "source_line_count": 95,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-231",
      "book": "llm/happy-llm",
      "book_title": "Happy LLM 原理与实践",
      "path": "books/llm/happy-llm/chapters/03_pretraining_objectives.md",
      "title": "第 03 章　预训练目标决定模型能看见什么",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "掩码预测、跨度去噪与因果目标",
      "recommended_medium": "同文三种标签/可见性视图",
      "reason": "切换目标后显示哪些位置有监督、能看哪里，帮助理解自监督标签来自原文。",
      "reuse_family": "supervision-mask",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 03.1 同一段文字，三种监督信号"
          },
          {
            "line": 15,
            "text": "## 03.2 BERT：先恢复缺失，再把表示交给下游"
          },
          {
            "line": 23,
            "text": "## 03.3 RoBERTa 和 ALBERT 分别在改什么"
          },
          {
            "line": 31,
            "text": "## 03.4 T5 把任务接口统一为文本到文本"
          },
          {
            "line": 37,
            "text": "## 03.5 GPT、LLaMA 与 GLM：生成路径相近，配方不相同"
          },
          {
            "line": 45,
            "text": "## 03.6 用标签和可见性检查一次训练样本"
          },
          {
            "line": 53,
            "text": "## 03.7 来源和阅读顺序"
          }
        ],
        "intro_excerpts": [
          {
            "line": 9,
            "text": "“今天下午下雨”可以生成三类训练样本。把“下午”遮住再要求恢复，是掩码预测；把一个连续片段换成特殊标记再生成缺失片段，是跨度去噪；给出“今天下午下”预测“雨”，是因果语言建模。三种任务都能从未人工标注的文本自动构造标签，因此常称为自监督学习。“无监督语料”不表示优化器没有标签，而是标签来自文本本身。[源教程第三章 §3.1—§3.3。]",
            "truncated": false
          },
          {
            "line": 11,
            "text": "设原序列为 $x=(x_1,\\ldots,x_T)$，随机选中的掩码位置集合为 $S$，损坏后的序列为 $\\tilde x$。掩码语言模型损失可以写为 $L_{MLM}=-\\sum_{t\\in S}\\log p_\\theta(x_t\\mid\\tilde x)$。因果损失则是 $L_{CLM}=-\\sum_t\\log p_\\theta(x_t\\mid x_{<t})$。区别在条件，不只是两个缩写：前者可以看右侧未损坏上下文，后者不行；前者通常只监督选中位置，后者可以监督每个有效预测位置。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "db97c5d43abd7e4988bc8dca9331b7caca9cc7233e2d5427b2ce1be7a92db083",
      "source_line_count": 55,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-232",
      "book": "llm/happy-llm",
      "book_title": "Happy LLM 原理与实践",
      "path": "books/llm/happy-llm/chapters/04_training_capabilities_evidence.md",
      "title": "第 04 章　训练规模很大，结论仍要落到证据上",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "能力结论、训练阶段与证据条件",
      "recommended_medium": "带条件的案例和阶段说明",
      "reason": "能力不能由参数滑块或演示题外推，保留证据边界比交互更重要。",
      "reuse_family": "model-evidence",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 04.1 “大模型”不是一条统一的参数门槛"
          },
          {
            "line": 13,
            "text": "## 04.2 能完成一道题，不等于具备不受条件限制的能力"
          },
          {
            "line": 21,
            "text": "## 04.3 多语言、长上下文、多模态和幻觉的条件"
          },
          {
            "line": 27,
            "text": "## 04.4 预训练、SFT、偏好优化分别在改变什么"
          },
          {
            "line": 35,
            "text": "## 04.5 计算量估算不是采购清单"
          },
          {
            "line": 45,
            "text": "## 04.6 数据配比同样是实验变量"
          },
          {
            "line": 53,
            "text": "## 04.7 来源与证据边界"
          },
          {
            "line": 57,
            "text": "## 04.8 PPO-RLHF 的四个角色，以及原章公式需要修正的地方"
          }
        ],
        "intro_excerpts": [
          {
            "line": 9,
            "text": "源教程第四章把大语言模型放在预训练语言模型的发展线上，讨论上下文学习、指令遵循、逐步推理和多模态扩展。理解这些能力前，先固定一个边界：LLM 没有跨研究、产品和时间都一致的参数下限。模型大、数据多、目标合适，可以提高某些任务表现；不能由参数量直接推出它可靠、会推理或理解任何语言。",
            "truncated": false
          },
          {
            "line": 11,
            "text": "历史表格也不能当产品目录使用。源章列举的发布时间、机构归属和“开源”类别存在简化，开权重、开放代码、开放数据与满足开源许可不是同一回事。本书保留这段的教学目的，即展示研究路线迅速分化，不复刻可能误导的厂商时间线，也不据此给出当前产品排名。评估具体模型应记录准确名称、版本、权重许可、上下文配置和测试日期。[源教程 §4.1.1；源固定版本见章末。]",
            "truncated": false
          }
        ]
      },
      "source_sha256": "1d7251a718ecaf4d465dd6fe8de2cc6c1dc7ba8a427977c84ec4049465de8ead",
      "source_line_count": 71,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-233",
      "book": "llm/happy-llm",
      "book_title": "Happy LLM 原理与实践",
      "path": "books/llm/happy-llm/chapters/05_rmsnorm_rope_gqa_swiglu.md",
      "title": "第 05 章　把一个小型 LLaMA 风格解码器拆开",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "RoPE、GQA 与模型尺寸约束",
      "recommended_medium": "复用旋转图及缓存账本入口",
      "reason": "只复用 RoPE/GQA 局部解释，其他模块保留正文；不能把拼装成功当权重兼容。",
      "reuse_family": "rope",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 05.1 先固定尺寸，再开始写层"
          },
          {
            "line": 13,
            "text": "## 05.2 RMSNorm 调整长度，不减去均值"
          },
          {
            "line": 32,
            "text": "## 05.3 RoPE 为什么能把相对位移放进点积"
          },
          {
            "line": 47,
            "text": "## 05.4 GQA 减少 K/V 头，并没有删除 Q 头"
          },
          {
            "line": 55,
            "text": "## 05.5 SwiGLU 用一个分支控制另一个分支"
          },
          {
            "line": 63,
            "text": "## 05.6 词表头、共享权重和输出协议"
          },
          {
            "line": 71,
            "text": "## 05.7 本书实现与检查"
          },
          {
            "line": 81,
            "text": "## 05.8 归属与参考"
          }
        ],
        "intro_excerpts": [
          {
            "line": 9,
            "text": "上游第五章 §5.1 从 `ModelConfig` 开始是合理的：没有尺寸约定，模块能否接起来全靠猜。本书用 $B$ 表示 batch，$T$ 表示长度，$d$ 表示模型宽度，$H_q$ 表示查询头数，$H_{kv}$ 表示键值头数，$d_h=d/H_q$ 表示每头宽度。RoPE 的相邻坐标成对旋转，因此 $d_h$ 必须为偶数；分组查询注意力要求 $H_q$ 能被 $H_{kv}$ 整除。输入 ID 为 $B\\times T$，embedding 后为 $B\\times T\\times d$，每层输出仍为这个形状。[固定 `k_model.py` 的 `ModelConfig`、`Attention`，见章末。]",
            "truncated": false
          },
          {
            "line": 11,
            "text": "配置还包括层数、词表大小、中间层宽度、宽度对齐倍数、归一化 epsilon、最大长度、dropout 和 pad ID。上游默认宽度 768、12 层、16 个查询头、8 个键值头和词表 6144 是一份教学配置，不是 LLaMA 2 官方通用配置。`model_type=\"Tiny-K\"` 用于框架识别；继承 `PretrainedConfig` 也不会自动保证所有生成与训练接口都符合 Transformers 约定。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "5893f414431ec880ecac92da31cb1ab937b152a1017c137b0b1035da70cdf150",
      "source_line_count": 91,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-234",
      "book": "llm/happy-llm",
      "book_title": "Happy LLM 原理与实践",
      "path": "books/llm/happy-llm/chapters/06_tokenizer_data_labels.md",
      "title": "第 06 章　Tokenizer 和标签决定模型实际学到什么",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "tokenizer、右移标签与 SFT mask",
      "recommended_medium": "共享分词和监督位置演示",
      "reason": "选择一个短对话显示角色边界及有效目标，字符 BPE 仅作为机制补充。",
      "reuse_family": "supervision-mask",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 06.1 切分粒度是在词表、长度与覆盖率之间取舍"
          },
          {
            "line": 15,
            "text": "## 06.2 一个 tokenizer 文件里不止词表"
          },
          {
            "line": 27,
            "text": "## 06.3 原始数据转换必须可重复，也必须保留对话角色"
          },
          {
            "line": 35,
            "text": "## 06.4 按字节索引 JSONL，按 token 右移标签"
          },
          {
            "line": 43,
            "text": "## 06.5 SFT mask 必须跟着消息边界走"
          },
          {
            "line": 51,
            "text": "## 06.6 练习与答案"
          },
          {
            "line": 57,
            "text": "## 06.7 来源与运行范围"
          },
          {
            "line": 61,
            "text": "## 06.8 固定 tokenizer 资产的静态核对"
          }
        ],
        "intro_excerpts": [
          {
            "line": 9,
            "text": "整词 tokenizer 给常见词一个 ID，序列短，但未知词和词表大小会成为问题。字符 tokenizer 的词表较小，遇到新组合容易表示，却让序列变长。子词在两者之间：常见片段合并，罕见词拆开。分词器不需要理解词源；BPE 恰好切出像词根的片段，也不能据此断言它学会了形态学。[源教程第五章 §5.2.1—§5.2.3。]",
            "truncated": false
          },
          {
            "line": 11,
            "text": "BPE 从基础符号序列开始，统计相邻对频率，把最常见的一对合并成新符号，再重复。假设语料是 `低 温`、`低 温`、`低 压`，`低 温` 的频率为 2，大于 `低 压` 的 1，因此可先合并“低温”。必须保留合并顺序；编码时按已学规则切分，而不是在每条新输入上重新统计。频率并列时的规则也要固定，否则词表可能不同。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "13b06a33868d2fe7dcc31216241f6370c03b75f654ef6b692c8c83172b3515fd",
      "source_line_count": 63,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-235",
      "book": "llm/happy-llm",
      "book_title": "Happy LLM 原理与实践",
      "path": "books/llm/happy-llm/chapters/07_training_export_sampling.md",
      "title": "第 07 章　训练闭环：从损失到可重载的模型",
      "priority": "P2",
      "treatment": "static",
      "concept": "训练顺序、导出和采样责任",
      "recommended_medium": "训练/恢复/生成阶段图",
      "reason": "现有真实 CPU 闭环是主证据；把先 unscale 后 clip 等顺序清楚画出即可。",
      "reuse_family": "training-state",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 07.1 一步训练有明确的先后顺序"
          },
          {
            "line": 15,
            "text": "## 07.2 学习率和精度选项应当真的控制执行路径"
          },
          {
            "line": 25,
            "text": "## 07.3 权重文件和训练检查点用途不同"
          },
          {
            "line": 33,
            "text": "## 07.4 生成规则决定下一步怎样选 token"
          },
          {
            "line": 45,
            "text": "## 07.5 TextGenerator 把前述接口接成演示"
          },
          {
            "line": 51,
            "text": "## 07.6 完整 CPU 实验：它学会了循环，也暴露了局限"
          },
          {
            "line": 67,
            "text": "## 07.7 来源与许可"
          },
          {
            "line": 71,
            "text": "## 07.8 隔离测试怎样复现上游缺陷"
          }
        ],
        "intro_excerpts": [
          {
            "line": 9,
            "text": "数据给出输入 X、右移目标 Y 和有效目标 mask。模型返回每个位置的损失，先只对有效目标取平均，再反向传播；梯度累积完成后，取消混合精度缩放、裁剪梯度、更新优化器，最后清空梯度。混淆顺序会改变结果：缩放后的梯度直接裁剪会改变阈值含义；更新前清空梯度则什么也学不到。[源教程 §5.3.4—§5.3.5；`ddp_pretrain.py` 与 `ddp_sft_full.py` 的 `train_epoch`。]",
            "truncated": false
          },
          {
            "line": 11,
            "text": "梯度累积常把每个 micro-batch 的均值损失除以累积次数 K，再连续反向 K 次。如果每个 micro-batch 有效 token 数不同，这得到的是 micro-batch 均值的平均，不等于整个大 batch 的 token 加权均值。若希望严格等价，需按各批有效 token 数加权。源脚本还只在 `(step+1)%K==0` 更新，epoch 末尾不足 K 个批次的梯度没有及时提交；下一轮也没有明确清零，可能跨 epoch 留存，最后一轮则可能直接丢失。解决时必须同时处理尾批大小与归一化，而不只是加一个 `optimizer.step()`。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "d6f102aba9c0af651a71db4568f705da4a19ee590616f78a065abea3c9ba4d98",
      "source_line_count": 75,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-236",
      "book": "llm/happy-llm",
      "book_title": "Happy LLM 原理与实践",
      "path": "books/llm/happy-llm/chapters/08_transformers_training.md",
      "title": "第 08 章　使用 Transformers，仍然要核对模型和数据的约定",
      "priority": "P2",
      "treatment": "static",
      "concept": "配置构建、权重加载与标签契约",
      "recommended_medium": "两条初始化路径和处理链",
      "reason": "API 名称易混淆，静态对照能说明哪些路径继承参数，保留实际脚本检查。",
      "reuse_family": "framework-contracts",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 08.1 从配置构建和从权重加载，差了一整段训练历史"
          },
          {
            "line": 15,
            "text": "## 08.2 tokenize、拼接、切块和 shift 的边界"
          },
          {
            "line": 23,
            "text": "## 08.3 Trainer 入口里有哪些实际动作"
          },
          {
            "line": 31,
            "text": "## 08.4 SFT preprocess 的标签错误为何严重"
          },
          {
            "line": 41,
            "text": "## 08.5 DeepSpeed JSON 与 shell 配置逐项读"
          },
          {
            "line": 51,
            "text": "## 08.6 三个 notebook 的每段在做什么"
          },
          {
            "line": 57,
            "text": "## 08.7 练习、答案与来源"
          }
        ],
        "intro_excerpts": [
          {
            "line": 9,
            "text": "`AutoConfig.from_pretrained` 读取结构配置；`AutoModelForCausalLM.from_config` 按配置随机初始化；`from_pretrained` 则加载已有权重。名字里都有 pretrained，不能据此认为第一条路径也继承了知识。源第六章以 Qwen2.5-1.5B 配置演示从零训练，主脚本也允许传入已有模型继续训练；这两条路径的预算与结果含义完全不同。[源 §6.1.1—§6.1.2，`pretrain.main`。]",
            "truncated": false
          },
          {
            "line": 11,
            "text": "Transformers 管模型接口与加载，Datasets 管数据映射与缓存，Trainer 管训练循环，DeepSpeed 管部分分布式和内存优化。框架能减少重复工程，却不能自动修正错误标签、数据泄漏或不匹配的 tokenizer。源码使用 `trust_remote_code=True` 时还可能执行模型仓库代码；本书只静态阅读，不下载权重，也不执行远程模型代码。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "92fc4436ed649c670c4f87b118ad7582d6c14678c33fe12e0ad3ebe5e60896d6",
      "source_line_count": 63,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-237",
      "book": "llm/happy-llm",
      "book_title": "Happy LLM 原理与实践",
      "path": "books/llm/happy-llm/chapters/09_lora_qlora_preference.md",
      "title": "第 09 章　少训练一些参数，究竟省了什么",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "LoRA 的可训练参数与 QLoRA 内存边界",
      "recommended_medium": "共享低秩矩阵和分项账本",
      "reason": "复用 LoRA 组件演算 rank；4-bit 存储与实际计算精度分开说明，不生成整体节省百分比。",
      "reuse_family": "lora",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 09.1 参数高效微调有不同的插入位置"
          },
          {
            "line": 13,
            "text": "## 09.2 低秩更新怎样进入线性层"
          },
          {
            "line": 25,
            "text": "## 09.3 逐分支阅读源 LoRA 示例"
          },
          {
            "line": 33,
            "text": "## 09.4 PEFT 接入并不替你选择损失"
          },
          {
            "line": 39,
            "text": "## 09.5 QLoRA 的 4 bit 不是用整数直接完成所有计算"
          },
          {
            "line": 47,
            "text": "## 09.6 偏好对齐：上游明确未完成，先学接口和目标"
          },
          {
            "line": 57,
            "text": "## 09.7 练习、答案与来源"
          },
          {
            "line": 65,
            "text": "## 09.8 不能直接运行的 LoRA 片段"
          }
        ],
        "intro_excerpts": [
          {
            "line": 9,
            "text": "全参数微调允许所有权重变化，适应空间大，但梯度与优化器状态昂贵。Adapter 在原层之间插入较小网络；prefix/prompt tuning 学习输入或中间层前缀；LoRA 给已有线性变换加低秩更新。它们冻结大部分基座参数，却仍需让激活和梯度信号通过相应网络。可训练参数少不意味着训练内存只剩这些参数，也不保证每种任务都比全参数微调好。[源第六章 §6.3.1。]",
            "truncated": false
          },
          {
            "line": 11,
            "text": "源教程关于 LoRA 不适合学习知识的说法过强。低秩限制会影响适应能力，但是否能学习特定知识要看数据、秩、目标模块、基座能力和评测；不能把失败视为定理。另一方面，如果目标是从随机初始化训练整个语言模型，冻结随机基座只学少量 adapter 通常不是这里要教的从零预训练方案。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "80c47d6df158fdbf6ee0cc1887bb33a83d817069244acacfa0a96a95b730d401",
      "source_line_count": 77,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-238",
      "book": "llm/happy-llm",
      "book_title": "Happy LLM 原理与实践",
      "path": "books/llm/happy-llm/chapters/10_evaluation.md",
      "title": "第 10 章　评测要回答一个明确的问题",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "评测问题、样本和提取规则",
      "recommended_medium": "评估条件和错误例子",
      "reason": "平均分需回到数据和规则，互动排行榜会淡化比较条件。",
      "reuse_family": "evaluation-design",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 10.1 分数之前先写实验条件"
          },
          {
            "line": 13,
            "text": "## 10.2 源教程列出的数据集，各自在测什么"
          },
          {
            "line": 23,
            "text": "## 10.3 三种榜单机制，不是一把尺子"
          },
          {
            "line": 29,
            "text": "## 10.4 从预测到分数，中间有一层容易出错的代码"
          },
          {
            "line": 37,
            "text": "## 10.5 数据泄漏可以在训练前，也可以在评测时发生"
          },
          {
            "line": 43,
            "text": "## 10.6 可运行的小评测与练习"
          },
          {
            "line": 51,
            "text": "## 10.7 来源说明"
          }
        ],
        "intro_excerpts": [
          {
            "line": 9,
            "text": "如果两个模型使用不同提示、不同采样次数、不同工具和不同答案提取规则，分数差就不能简单归因于模型本身。一个可复核结果要绑定模型版本、tokenizer、数据版本、split、提示、示例、最大输出长度、随机种子、采样策略、工具权限与评分程序。服务端会变化的模型还要记录日期和可取得的版本标识。[源第七章 §7.1。]",
            "truncated": false
          },
          {
            "line": 11,
            "text": "单个平均分也会隐藏差异：模型可能在常见类型上提高，却在高代价错误上退步。准确率、稳定性、拒答、延迟和资源成本是不同维度。针对具体应用，应先确定哪些错误不可接受，再决定用什么测试，而不是先找最高榜单分数再寻找使用理由。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "8da9572f0c2fb18609146794686667b19c606314ad2eae16ae7ac8b2ed48bcc6",
      "source_line_count": 53,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-239",
      "book": "llm/happy-llm",
      "book_title": "Happy LLM 原理与实践",
      "path": "books/llm/happy-llm/chapters/11_rag.md",
      "title": "第 11 章　RAG 先找证据，再检查回答有没有用对证据",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "检索、证据选择与回答支持",
      "recommended_medium": "内置文档的检索路径演示",
      "reason": "用固定可查看的向量/词项与片段展示召回和漏条件，明确是离线教学数据而非语义模型实测。",
      "reuse_family": "retrieval-evidence",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 11.1 检索和生成是两个可以分别失败的系统"
          },
          {
            "line": 13,
            "text": "## 11.2 文档读取的损失不能留到生成阶段补救"
          },
          {
            "line": 23,
            "text": "## 11.3 embedding 和余弦相似度分别承担什么"
          },
          {
            "line": 35,
            "text": "## 11.4 VectorStore 是线性扫描，不是已经优化的向量数据库"
          },
          {
            "line": 43,
            "text": "## 11.5 生成接口要保护来源边界"
          },
          {
            "line": 51,
            "text": "## 11.6 离线替身只验证协议，不验证语义模型"
          },
          {
            "line": 61,
            "text": "## 11.7 来源与署名"
          }
        ],
        "intro_excerpts": [
          {
            "line": 9,
            "text": "模型不知道一份新文档，不一定要先重新训练。RAG 把问题交给检索器，找出相关片段，再把片段与问题交给生成器。知识更新主要发生在索引和资料库中，权重可以不变。但“检索到了文档”不会自动变成“回答真实”：来源可能过时、片段可能缺条件、模型可能误读，资料还可能包含恶意指令。[源第七章 §7.2.1—§7.2.2 Step 1；RAG 原研究见章末。]",
            "truncated": false
          },
          {
            "line": 11,
            "text": "一个最小系统要保留文档来源、chunk ID、内容、向量和索引版本。问题向量与文档向量必须来自兼容的 embedding 模型与维度。只存向量数组和文本字符串，短期能跑，更新模型或资料后却很难追溯错误。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "9795b0636cb4dd4bf4d7134fb9def68f94f8f9c05f6160514e89aec0362747a4",
      "source_line_count": 63,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-240",
      "book": "llm/happy-llm",
      "book_title": "Happy LLM 原理与实践",
      "path": "books/llm/happy-llm/chapters/12_agent_tools_state.md",
      "title": "第 12 章　Agent 是一个受约束的执行循环",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "工具提议、校验、执行和反馈",
      "recommended_medium": "离线受限 Agent 状态机",
      "reason": "只提供加法等内置无副作用动作，展示拒绝参数与预算耗尽；绝不执行自由代码或真实账号动作。",
      "reuse_family": "agent-state",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 7,
            "text": "## 12.1 会提议工具，不等于有权执行工具"
          },
          {
            "line": 15,
            "text": "## 12.2 函数 schema 是契约的说明，不是运行时验证器"
          },
          {
            "line": 23,
            "text": "## 12.3 Agent 类把调用 ID 连接成一条因果链"
          },
          {
            "line": 33,
            "text": "## 12.4 每个示例工具都应有边界"
          },
          {
            "line": 43,
            "text": "## 12.5 命令行与网页演示的状态风险不同"
          },
          {
            "line": 49,
            "text": "## 12.6 一个真正离线、可以失败的协议实验"
          },
          {
            "line": 57,
            "text": "## 12.7 练习与答案"
          },
          {
            "line": 63,
            "text": "## 12.8 来源与修改说明"
          }
        ],
        "intro_excerpts": [
          {
            "line": 9,
            "text": "语言模型可以生成回答，也可以生成一个结构化请求：“调用 add，参数 a=2、b=3”。宿主程序解析请求、检查参数和权限、执行真实函数，再把结果作为工具消息交回模型。Agent 的可执行能力来自这条外部循环，而不是文本里说“我已经完成”就完成。[源第七章 §7.3.1。]",
            "truncated": false
          },
          {
            "line": 11,
            "text": "任务导向系统常有固定流程；规划型系统允许模型根据反馈选择下一步；多 Agent 系统让多个角色协作；探索学习型系统可能根据交互更新策略。这些是设计侧重点，可以重叠，并非互斥分类。保存聊天历史是记忆，权重梯度更新是学习，二者不能混说。模型自我反思的文字也不等于已经验证错误或改变了参数。[源 §7.3.2。]",
            "truncated": false
          }
        ]
      },
      "source_sha256": "5192afd995bd589c86f4eff32da5d7439d620f23e7056552de92515950001775",
      "source_line_count": 65,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-241",
      "book": "llm/happy-llm",
      "book_title": "Happy LLM 原理与实践",
      "path": "books/llm/happy-llm/chapters/13_grpo.md",
      "title": "第13章 GRPO：从一组答案中产生学习信号",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "组内奖励、优势与策略比率",
      "recommended_medium": "组内奖励归一化演算器",
      "reason": "调整四个奖励后看均值和相对优势，演示全相同奖励的边界；不声称 RL 收益。",
      "reuse_family": "group-advantages",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 13.1 先把“答对”与“应该更新多少”分开"
          },
          {
            "line": 15,
            "text": "## 13.2 策略比率到底在比较什么"
          },
          {
            "line": 31,
            "text": "## 13.3 奖励器是任务规格的一部分"
          },
          {
            "line": 39,
            "text": "## 13.4 把一条回答放进训练数组"
          },
          {
            "line": 54,
            "text": "## 13.5 从数据到权重的控制流"
          },
          {
            "line": 60,
            "text": "## 13.6 离线验证与练习"
          },
          {
            "line": 71,
            "text": "### 来源与改编说明"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "假设同一道算术题采样四次，奖励为 [1, 0, 1, 0]。奖励器知道哪条答案正确，却不知道某条轨迹相对于当前模型有多出色。如果所有答案都正确，继续同样强度地奖励每个 token，并不能说明模型该在组内偏向谁。GRPO 用同一问题的一组采样作为比较对象，不再额外训练一个逐状态估值的 critic。省掉 critic 不等于省掉 rollout；组大小越大，采样成本仍会增长。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "在固定问题 $q$ 下，第 $i$ 条回答记为 $y_i=(y_{i,1},\\ldots,y_{i,T_i})$，长度为 $T_i$，奖励 $r_i$ 是标量。组大小 $G\\ge 2$，均值为 $\\bar r=\\frac{1}{G}\\sum_{i=1}^{G}r_i$。**本仓库的优势是 $A_i=r_i-\\bar r$，没有除以标准差。** 因而上例得到 $[0.5,-0.5,0.5,-0.5]$。这要与原始 GRPO 文献中常见的标准化形式 $(r_i-\\bar r)/(\\operatorname{std}(r)+\\varepsilon)$ 区分；更换这一行会改变奖励尺度、梯度尺度及难题的权重，不能只把它当数值稳定小修补。[固定实现](https://github.com/datawhalechina/happy-llm/blob/1e0b3a1e6f8936b8ba4bbd0673e8e2227d196c96/docs/chapter8/grpo/01-demo-sync.py)",
            "truncated": false
          }
        ]
      },
      "source_sha256": "6a69cefa177cedf41d0d98bd386e95b76ff2439b2b219c35b82e3ba5f9a4a8fd",
      "source_line_count": 73,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-242",
      "book": "llm/happy-llm",
      "book_title": "Happy LLM 原理与实践",
      "path": "books/llm/happy-llm/chapters/14_opd.md",
      "title": "第14章 OPD：让教师评价学生走过的路径",
      "priority": "P2",
      "treatment": "static",
      "concept": "学生轨迹与教师逐 token 评分",
      "recommended_medium": "生成者/评分者双轨对齐图",
      "reason": "重点是轨迹来自谁以及 token 对齐，静态图足够；单 token 估计不应被标成总 KL。",
      "reuse_family": "distillation",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 14.1 谁负责生成，决定了蒸馏覆盖什么"
          },
          {
            "line": 13,
            "text": "## 14.2 为什么单个 token 的“KL”可以为负"
          },
          {
            "line": 29,
            "text": "## 14.3 教师分数与学生 token 的对齐"
          },
          {
            "line": 37,
            "text": "## 14.4 同步与异步：省等待，不省一致性检查"
          },
          {
            "line": 51,
            "text": "## 14.5 数据、下载和日志也会改变复现"
          },
          {
            "line": 59,
            "text": "## 14.6 练习与答案"
          },
          {
            "line": 68,
            "text": "### 来源与改编说明"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "监督蒸馏可以先让教师写答案，再教学生模仿。但学生部署时可能走进教师示范没出现过的中间状态。On-Policy Distillation（OPD）反过来先让学生生成，再让教师对这条学生轨迹逐 token 打分。教师没有重新写一份更好答案；它回答的是“在学生实际走到的上下文中，我会给这个 token 多大概率”。[固定同步实现](https://github.com/datawhalechina/happy-llm/blob/1e0b3a1e6f8936b8ba4bbd0673e8e2227d196c96/docs/chapter8/opd/01-demo-sync.py)",
            "truncated": false
          },
          {
            "line": 7,
            "text": "仓库使用 DeepMath 的 question 作为 prompt pool，不使用参考推理来计算交叉熵；学生是 LoRA training client，教师是只打分的 sampling client。模型名默认值只是这个提交的配置，不保证将来服务仍供应相同型号。最重要的前提是双方 tokenizer/token ID 兼容：同一个整数在双方必须表示同一个 token，聊天模板与上下文语义也要一致。词表大小相同并不足以保证兼容。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "855efb3174e90dc78a5701ce5065dc0eb7841034b01bb30db11cb71507994a5e",
      "source_line_count": 70,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-243",
      "book": "llm/happy-llm",
      "book_title": "Happy LLM 原理与实践",
      "path": "books/llm/happy-llm/chapters/15_search_r1.md",
      "title": "第15章 Search-R1：把搜索结果放回可训练的轨迹",
      "priority": "P2",
      "treatment": "static",
      "concept": "搜索动作、外部观察与训练 mask",
      "recommended_medium": "带角色的工具轨迹图",
      "reason": "固定轨迹能说明观察不属于策略输出，不在教材中调用真实搜索接口。",
      "reuse_family": "tool-trajectory",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 15.1 一次搜索为什么不是普通文本续写"
          },
          {
            "line": 9,
            "text": "## 15.2 协议先决定合法动作，再决定奖励"
          },
          {
            "line": 17,
            "text": "## 15.3 保存 token，比重新渲染一份漂亮对话更重要"
          },
          {
            "line": 29,
            "text": "## 15.4 多轮状态机怎样终止"
          },
          {
            "line": 39,
            "text": "## 15.5 搜索后端是实验环境，不是可随便替换的细节"
          },
          {
            "line": 47,
            "text": "## 15.6 训练装箱：为什么按最长长度算"
          },
          {
            "line": 63,
            "text": "## 15.7 固定评测集仍需固定工具"
          },
          {
            "line": 71,
            "text": "## 15.8 练习与答案"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "问“《小王子》的作者出生在哪个国家”，模型可以直接作答，也可以先搜作者，再搜出生地。强化学习在这里学习的是：何时需要证据、怎样写查询、看到结果后是否继续搜索以及何时结束。一次完整轨迹由模型动作与外部观察交替组成。观察影响下一次决策，但不是模型自己生成的动作，因此不能把它的 token 一并当作策略要模仿的答案。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "上游借用 Search-R1 的研究方向，具体脚本使用 PyTRIO 与可切换搜索后端；它不是原论文所有训练组件的逐行复刻。[Search-R1 原论文](https://arxiv.org/abs/2503.09516)给出搜索交织推理的研究背景；以下实现细节以[固定源码](https://github.com/datawhalechina/happy-llm/tree/1e0b3a1e6f8936b8ba4bbd0673e8e2227d196c96/docs/chapter8/search-r1)为准。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "1d3b43eb9bc0140da96f3a150bc1fe107f068f840634476c5e742b88bd6da83a",
      "source_line_count": 80,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-244",
      "book": "llm/happy-llm",
      "book_title": "Happy LLM 原理与实践",
      "path": "books/llm/happy-llm/chapters/16_retool.md",
      "title": "第16章 ReTool：工具执行反馈、奖励与隔离边界",
      "priority": "P2",
      "treatment": "static",
      "concept": "工具反馈与沙箱隔离边界",
      "recommended_medium": "执行协议及失败回路图",
      "reason": "安全边界比互动运行重要；不能把普通子进程当安全沙箱，也不提供任意代码输入。",
      "reuse_family": "tool-safety",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 16.1 把计算交给工具，也把工具失败放进学习过程"
          },
          {
            "line": 9,
            "text": "## 16.2 工具调用与终局答案是不同类型"
          },
          {
            "line": 17,
            "text": "## 16.3 从代码动作到新的上下文"
          },
          {
            "line": 31,
            "text": "## 16.4 源码的 LocalPythonSandbox 能做什么，不能做什么"
          },
          {
            "line": 49,
            "text": "## 16.5 数据准备、训练与公平评测"
          },
          {
            "line": 65,
            "text": "## 16.6 离线反例、练习与答案"
          },
          {
            "line": 76,
            "text": "### 来源与改编说明"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "模型生成一段 Python 计算式，解释器返回数值或异常，模型再决定怎样修改推理。这种交织比“先完整推理，最后调用计算器”更灵活：中途观察可以改变后续动作。代价是轨迹更长、执行成本更高，而且模型生成的程序不可信。[ReTool 原论文](https://arxiv.org/abs/2504.11536)讨论这种强化学习方向；本章核对的是[Happy-LLM 固定实现](https://github.com/datawhalechina/happy-llm/tree/1e0b3a1e6f8936b8ba4bbd0673e8e2227d196c96/docs/chapter8/retool)。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "本书不执行模型生成代码。离线 fixture 只把请求映射到预写结果，足以检查解析、消息拼接、奖励和预算；它不能证明真实解释器隔离安全，也不能证明模型学会使用工具。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "a16f28174e05edac817ea445f32607b45706d3645fd70be9f36244ecf16e8174",
      "source_line_count": 78,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-245",
      "book": "llm/happy-llm",
      "book_title": "Happy LLM 原理与实践",
      "path": "books/llm/happy-llm/chapters/17_small_model_finetuning.md",
      "title": "第 17 章　小模型微调：先把一项任务做可靠",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "小模型任务、数据协议与抽取验收",
      "recommended_medium": "字段证据、测试集与训练检查",
      "reason": "可靠抽取依赖真实数据验证；不要收集个人投诉信息来驱动演示。",
      "reuse_family": "fine-tuning-evidence",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 17.1 问题足够具体，小模型才有可检验的优势"
          },
          {
            "line": 21,
            "text": "## 17.2 数据集的四个字段其实定义了一个协议"
          },
          {
            "line": 31,
            "text": "## 17.3 模板、可见性和监督位置是三件事"
          },
          {
            "line": 49,
            "text": "## 17.4 LoRA 参数账本可以从层尺寸逐项算出来"
          },
          {
            "line": 70,
            "text": "## 17.5 训练器要接通，也要能解释每一步"
          },
          {
            "line": 80,
            "text": "## 17.6 推理成功不等于抽取可靠"
          },
          {
            "line": 88,
            "text": "## 17.7 本章的真实检查与练习"
          },
          {
            "line": 101,
            "text": "## 17.8 来源、归属与覆盖说明"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "假设系统每天收到大量投诉文本，需要输出姓名、地址、邮箱和投诉内容。模型不必写文章，也不必解数学题；它需要识别四个字段，保留原文证据，并输出可解析的 JSON。这种任务适合讨论小模型微调，因为我们能明确判断错误，而不是只凭“回答看起来不错”选择模型。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "本章伴读 Happy-LLM 的 [小模型微调补充教程](https://github.com/datawhalechina/happy-llm/blob/1e0b3a1e6f8936b8ba4bbd0673e8e2227d196c96/Extra-Chapter/why-fine-tune-small-large-language-models/readme.md) 与同目录笔记本。上游使用合成投诉数据和 Qwen3-0.6B，展示 LoRA 微调链路。本章解释这条链路，并补上数据契约、损失检查和评估边界。笔记本共 34 个单元，索引为 0—33；其代码单元没有保存执行输出。本章不会把 README 中的示例回答当作本次复现实验。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "384e856806e3eff3ebba068ede00e85b65b852065aa2a0f00fe88886b89f3837",
      "source_line_count": 105,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-246",
      "book": "llm/happy-llm",
      "book_title": "Happy LLM 原理与实践",
      "path": "books/llm/happy-llm/chapters/18_architecture_text_contracts.md",
      "title": "第 18 章　Transformer 设计复盘：从文本到注意力，哪里最容易接错",
      "priority": "P2",
      "treatment": "static",
      "concept": "文本到注意力的整链契约",
      "recommended_medium": "带错误点的接口总图",
      "reason": "作为设计复盘可链接已有 BPE、mask 和窗口演示，不再创建第四套分词器。",
      "reuse_family": "text-contracts",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 18.1 先沿着张量走一遍"
          },
          {
            "line": 15,
            "text": "## 18.2 Encoder、Decoder 与注意力各自做什么"
          },
          {
            "line": 42,
            "text": "## 18.3 两种 mask 与标签移位必须分别核对"
          },
          {
            "line": 61,
            "text": "## 18.4 位置编码的代数性质不等于长文本能力保证"
          },
          {
            "line": 87,
            "text": "## 18.5 第一张词向量图能证明什么"
          },
          {
            "line": 95,
            "text": "## 18.6 三种正则分词器，保留的信息并不一样"
          },
          {
            "line": 105,
            "text": "## 18.7 “加进词表”不等于“识别成一个特殊 token”"
          },
          {
            "line": 113,
            "text": "## 18.8 手写 BPE 的合并过程与失败边界"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "一段文本要经过分词、整数编号、嵌入查表、位置处理，才成为 Transformer 的输入。每一步都可能运行成功，却悄悄改变任务：空格被删除，特殊标记被拆开，窗口末尾缺少目标，或者把隐藏状态误认为词表概率。本章把 Happy-LLM 的 [架构补充](https://github.com/datawhalechina/happy-llm/blob/1e0b3a1e6f8936b8ba4bbd0673e8e2227d196c96/Extra-Chapter/transformer-architecture/readme.md) 与 [文本处理补充](https://github.com/datawhalechina/happy-llm/blob/1e0b3a1e6f8936b8ba4bbd0673e8e2227d196c96/Extra-Chapter/text-data-processing/readme.md) 放在一起，重点解释各层之间必须满足的约定。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "设批大小为 $B$，源长度为 $S$，目标长度为 $T$，隐藏维度为 $d$，词表大小为 $V$。文本 ID 是整数张量 $[B,S]$，embedding 表 $E\\in\\mathbb R^{V\\times d}$ 查出 $[B,S,d]$。编码器得到同形状的 memory。解码器接收目标侧 $[B,T,d]$，结合 memory，输出 $[B,T,d]$。还需一个 $d\\to V$ 的输出头，才能得到 $[B,T,V]$ 的 logits；softmax 才得到词表概率。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "52028862db656a1337816ac8baa67b48d81f6356d9ba785c0bc4ef3a23ff75e8",
      "source_line_count": 166,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-247",
      "book": "llm/happy-llm",
      "book_title": "Happy LLM 原理与实践",
      "path": "books/llm/happy-llm/chapters/19_visual_language_concatenation.md",
      "title": "第 19 章　拼接式视觉语言模型：图像真的进入语言模型了吗",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "视觉表示接入与梯度通路",
      "recommended_medium": "虚构 patch 到文本位置的连接图",
      "reason": "切断/接通图像分支并标出依赖和梯度路径，说明 loss 下降不证明看图；不伪造图像理解结果。",
      "reuse_family": "multimodal-wiring",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 19.1 一条下降的 loss 曲线可能掩盖整条断路"
          },
          {
            "line": 11,
            "text": "## 19.2 拼接的是表示，不能只换一个模型名字"
          },
          {
            "line": 37,
            "text": "## 19.3 Tokenizer、模板和嵌入替换必须使用同一套 ID"
          },
          {
            "line": 47,
            "text": "## 19.4 替换 backbone、输出头和配置，是同一个手术"
          },
          {
            "line": 59,
            "text": "## 19.5 冻结参数，不是切断梯度"
          },
          {
            "line": 78,
            "text": "## 19.6 数据整理函数做了哪些取舍"
          },
          {
            "line": 92,
            "text": "## 19.7 训练配置与断点恢复如何解释"
          },
          {
            "line": 104,
            "text": "## 19.8 怎样读成功、失败和还没完成的结果"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "一个模型能流畅回答“图里有什么动物”，不代表它看过图。它可能只根据问题猜一个常见动物；甚至在训练 loss 很低时，图像分支仍然没有接通。Happy-LLM 的 [拼接微调补充教程](https://github.com/datawhalechina/happy-llm/blob/1e0b3a1e6f8936b8ba4bbd0673e8e2227d196c96/Extra-Chapter/vlm-concatenation-finetune/README.md) 记录了这样一次错误：外层模型的 image token ID 已修改，内层模型仍使用旧 ID，导致语言模型没有收到预期图像特征。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "这个案例比单纯的成功演示更值得学习。本章的目标是解释怎样把视觉编码器与语言模型接在一起，怎样证明接口正确，以及哪些实验仍然不足以证明视觉能力。来源作者为情感机器实验室陈少宏；上游感谢 zhihuazhao-bit 和 KMnO4-zx 的代码与文章审阅。本章保留这些归属，用原创推导和 CPU 小张量验证补充原文，不复述作者的个人经历为本书实验。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "0318abba9f3f905f68e4262eea674c8cf8ed99265f398ced4af7e0d7b0b365ad",
      "source_line_count": 129,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-248",
      "book": "llm/happy-llm",
      "book_title": "Happy LLM 原理与实践",
      "path": "books/llm/happy-llm/chapters/20_thinking_budget.md",
      "title": "第20章 思考预算与生成：更多 token 应该花在哪里",
      "priority": "P2",
      "treatment": "interactive",
      "concept": "思考、工具、答案 token 预算",
      "recommended_medium": "生成预算拆分和截断查看器",
      "reason": "调整各段配额显示总开销与停止位置，明确更多 token 是否有效仍需评测。",
      "reuse_family": "generation-budget",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 20.1 先定义预算，再讨论效果"
          },
          {
            "line": 9,
            "text": "## 20.2 把一条回答的长度分成三部分"
          },
          {
            "line": 17,
            "text": "## 20.3 原预算循环为什么需要边界测试"
          },
          {
            "line": 31,
            "text": "## 20.4 贪婪、采样与束搜索分别优化什么"
          },
          {
            "line": 54,
            "text": "## 20.5 怎样证明额外思考值得付费"
          },
          {
            "line": 65,
            "text": "### 来源与改编说明"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "“多思考一会儿”在程序里至少可以指四件事：允许单条回答更长；同时采样更多答案；提前停止；在模型想结束时追加提示让它继续检查。这些做法增加的成本和带来的统计机会不同，不能统称为“用了相同思考预算”。s1 的 budget forcing 提供一种延长或限制测试时生成的思路；它不是无需训练就保证任何模型更聪明的定理。[s1 原论文](https://arxiv.org/abs/2501.19393)",
            "truncated": false
          },
          {
            "line": 7,
            "text": "本仓库 Extra-Chapter 的 s1.py 使用本地 Qwen3-14B 路径与 vLLM，开启 thinking 模板，在 `</think>` 停止后追加 `Wait!`，尝试继续推理，再显式补结束标记进入最终答案阶段。这是作者的机制演示，不等于复现 s1 的全部数据筛选、微调和评测。readme、images 与 output 下的文本是上游叙述/运行记录，不是本书 CPU 环境生成的证据。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "4becf66bbe7e1bbd14ff54e396d4e6815c96e6f9100ef4b64cd862ab71fa2989",
      "source_line_count": 67,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-249",
      "book": "llm/happy-llm",
      "book_title": "Happy LLM 原理与实践",
      "path": "books/llm/happy-llm/chapters/21_cddrs.md",
      "title": "第21章 CDDRS：把“找相似内容”变成可审计的知识检索",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "知识检索、适用条件与审查责任",
      "recommended_medium": "来源到断言的证据核对流程",
      "reason": "真实工程合规需专业判断，交互评分会让启发式被误当可信度或合规结论。",
      "reuse_family": "evidence-judgment",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 21.1 检索辅助审查，还需要人判断适用条件"
          },
          {
            "line": 9,
            "text": "## 21.2 基础接口：为什么要把模型调用包起来"
          },
          {
            "line": 17,
            "text": "## 21.3 动态语义分块的目标与实现缝隙"
          },
          {
            "line": 27,
            "text": "## 21.4 知识分数怎样算，为什么不能把名字当保证"
          },
          {
            "line": 57,
            "text": "## 21.5 重排序与“查询增强”的实际接口"
          },
          {
            "line": 65,
            "text": "## 21.6 分析、修订和假置信度"
          },
          {
            "line": 71,
            "text": "## 21.7 如何做有意义的离线实验"
          },
          {
            "line": 82,
            "text": "### 来源与改编说明"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "建筑文档可能同时涉及材料等级、尺寸、施工步骤与适用范围。向量相似度擅长找主题相近的段落，却可能忽略一个数值或限定条件。CDDRS 示例组合语义分块、关键信息提取、知识评分、重排序、错误分析和修订建议。本章解释其程序，不提供真实工程合规意见；示例中的 C25、25mm 等值只是待审查文本，不能从例子推断它们普遍合规。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "上游作者与论文信息见[固定 readme](https://github.com/datawhalechina/happy-llm/blob/1e0b3a1e6f8936b8ba4bbd0673e8e2227d196c96/Extra-Chapter/CDDRS/readme.md)。论文题为 Generative knowledge-guided review system for construction disclosure documents，作者 Hongru Xiao、Jiankun Zhuang、Bin Yang、Jiale Han、Yantao Yu、Songning Lai，DOI为10.1016/j.aei.2025.103618。此处依据仓库提供的教学实现阅读，不声称复现论文实验或确认现行建筑规范。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "ff1fbe713bef29f3bf4569276929ddb8d704c16cbb82af54d63a60fd513da4da",
      "source_line_count": 84,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    },
    {
      "id": "ch-250",
      "book": "llm/happy-llm",
      "book_title": "Happy LLM 原理与实践",
      "path": "books/llm/happy-llm/chapters/22_reproduction.md",
      "title": "第22章 分章环境与复现：把“能读懂”变成“知道验证了什么”",
      "priority": "P3",
      "treatment": "retain_prose",
      "concept": "分章环境、静态/离线/远端验证",
      "recommended_medium": "环境表和复现入口",
      "reason": "清楚区分验证层次是收束目标，不能用统一绿色状态替代未执行的实验。",
      "reuse_family": "reproduction",
      "implementation_status": "editorial_recommendation_only",
      "evidence": {
        "review_depth": "selected_headings_and_intro_scan",
        "review_note": "扫读标题、前段小节及导言/开篇正文，用于媒介适配初筛；并非逐行技术审校。",
        "heading_examples": [
          {
            "line": 3,
            "text": "## 22.1 同一个项目，不等于同一个运行环境"
          },
          {
            "line": 20,
            "text": "## 22.2 三个层次的验证，别互相替代"
          },
          {
            "line": 28,
            "text": "## 22.3 可移植地运行本书的小实验"
          },
          {
            "line": 43,
            "text": "## 22.4 文件、函数与单元的覆盖怎样查"
          },
          {
            "line": 49,
            "text": "## 22.5 导航、反馈与许可也是工程的一部分"
          },
          {
            "line": 61,
            "text": "## 22.6 从错误出发安排下一次实验"
          },
          {
            "line": 72,
            "text": "### 来源与改编说明"
          }
        ],
        "intro_excerpts": [
          {
            "line": 5,
            "text": "Happy-LLM 同时包含从头实现、框架微调、RAG/Agent和远端RL客户端。一次安装所有依赖，往往让尚未开始的章节也参与版本冲突。上游《学习与环境准备》建议第2–7章优先独立Python3.10/3.11环境，第8章使用Python3.13；这是固定提交的建议，不是本书已经安装和验证过这些全套环境的声明。",
            "truncated": false
          },
          {
            "line": 7,
            "text": "第8章requirements固定pytrio==0.2.6、swanlab==0.9.2，另有多个最低版本约束，以及deepseek-search的固定Git提交。固定部分依赖不等于完整lockfile：未固定的依赖和传递依赖仍可变化。尤其numpy、torch、matplotlib等下界可能与某个现有Python环境不兼容，应先做解析与平台检查，不要反复升级一个共享环境。",
            "truncated": false
          }
        ]
      },
      "source_sha256": "62e2001e538cc9cfe1a76a1b174aa64c2709dad2b5ab7d43f8a67acf09ea9662",
      "source_line_count": 74,
      "existing_static_mermaid_count": 0,
      "inline_demo_tags_found": []
    }
  ]
}
