# 施工交接 · S10 四缺口全量改（2026-09-17 02:3x）

> **给谁看**：压缩后的下一个窗口（我），以及用户本人。
> **状态**：**未开工**。用户已拍板「四个缺口全量改」，但决定**先推送一次文档更新，明天再改代码**。
> **上游依据**：`ROADMAP-20260917-WEEK.md` §3.5（技术版，带行号）· `S10-GAPS-PLAIN-20260917.md`（大白话版）。

---

## 一、用户已拍板的决定（不要重开议题）

| # | 决定 | 原话/口径 |
|---|---|---|
| 1 | **四个缺口全量改**（不是"标注未实现"，是真做） | 「我决定了，四个缺口都全量改」 |
| 2 | **缺口 4 的第⑤类（矛盾检测）走「主代理每轮收尾写记忆时顺带做」** | 「不是 subagent 的自动沉淀，是每轮对话的收尾写记忆的时候可以更新白板状态」 |
| 3 | 该时机下 **LLM 介入是合法的** | 用户自己推断，**已核实成立**（见 §四） |
| 4 | **先推送一次文档更新，明天再改代码** | 「我压缩之后先推送一次更新，明天再改剩下的」 |
| 5 | 图片等前端重构后再改 | 前轮已定 |

---

## 二、四个缺口 + 现状（全部已代码核实）

### 缺口 1（P0）· 锚点写入被 `boardMode` 门控，默认不生效

- 默认值 `boardMode: 'legacy'` —— `lib/index.js:219`
- 锚点写入被门控：`:1988`（PLAN）、`:2032`（账本）；sidecar 直接早退 `:2076`
- **⇒ 默认配置下 S10.1「页面即语料」整条链不启动**

**改法（用户已认可方向）**：**把锚点写入与 `boardMode` 解耦**。
理由：锚点是**记录格式**，看板是**显示样式**，本不该耦合。**不要改默认值**（会牵动老用户）。
⚠️ 实施注意：解耦后需确认 sidecar 写入口径 —— `writeSidecarEntryPre` 的早退（`:2076`）与锚点写入是两件事，**要分别判断**是否都该解耦。

### 缺口 2（P0）· 白板进「注入」但不进「检索」

- 注入 ✅ `index.js:4701` `add('whiteboard', s.planText, s.planPath)` → Tier-0 目录（五来源之一，有保底配额）
- 检索 ❌ `pushL0` 四来源（`:5505-5508`）与 `semSources` 四来源（`:5704-5707`）**均无白板**
- **⇒ 白板每轮被注入，但 `memory_recall` 搜不到**

**改法**：两处各补一条白板来源。`p.planPath` 变量**已存在**（`:4701` 在用）。
⚠️ 注意配额：`tier0-catalog-pre.js` 的 `floorLayers=['whiteboard','user']` 是 Tier-0 侧的；**L0 检索侧是否需要同类保护要一并判断**，否则白板可能又被 `project` 挤掉。

### 缺口 3 · 索引派生（S10.2）——**不是没做，是没输出成契约那一行**

契约要的那行（`WB-FORMAT-CONVENTION.md:44`）：
```
- [卡片标题](<页面路径>#mem_<32hex>) — 一句话摘要 · layer=whiteboard · status=current · 2026-09-14
```

**已有的家底（关键！别重复造）**：
- `rebuildSidecarIndexPre`（`wb-sidecar-pre.js:363`）**真在派生索引**，产出 `entries` + `by_tag` + `by_cue` + `versions`
- 每条 entry 已有 `id`（`mem_`+sha256 前 32 位，`:78-83`）、`source`（页面路径）、`title`、`section`、`preview`（前 120 字）、`tags`、`cues`、`kind`、`mtime`、`ts`
- **锚点 id 与 sidecar id 同源**：`applyAnchorsPre:501` 调的就是 `wbEntryIdPre`
- `renderCatalogLinePre`（`tier0-catalog-pre.js:316`）已在渲染 `标题 · 摘要 · layer · status · 日期`

**缺的只有**：① 没拼成契约那一行的字面格式 ② entry 无 `status` 字段 ③ entry 无 `layer` 字段

**改法（已定的设计）**：**不做成独立文件**（那会新增状态源，违反 S10.4「不建状态机」）。
改为：**同一次派生、两种渲染** —— 让 `renderCatalogLinePre`（或新增同源渲染函数）在有页面路径时输出**带链接形态**。
⇒ 契约 §8 验收项「派生出 index 与 Tier-0 条目一致」**天然成立**，因为就是同一份数据。

### 缺口 4 · lint（S10.3）——五类，前四类纯函数、第五类走纪律

契约清单（`WB-FORMAT-CONVENTION.md:80-93`）：

| 类 | 内容 | 难度 | 做法 |
|---|---|---|---|
| ① | 孤立条目（无入站引用） | ★ 低 | 扫全部卡正文，找是否引用过其 id/标题 |
| ② | 陈旧（`status≠current`，或超阈值仍 current） | ★ 极低 | 状态 + 日期比较 |
| ③ | 被提及却无独立卡 | ★★ 中 | **判据模糊，需先定阈值/词典** |
| ④ | 缺交叉引用 | ★★ 中 | **"相关"判据要先定** |
| ⑤ | 矛盾检测 | ★★★ | **需 LLM → 走 §四 的纪律方案** |

**形态（硬约束）**：`lintWhiteboardPre(entries) → 问题清单`，**只读、只报告、绝不写盘**。
契约 §6 尾注：「任何自动修正都会把白板变成状态机（违反边界）」。

---

## 三、缺口 4⑤ 的纪律方案（用户的核心思路）

### 结论：**合法**，且比"造一条 LLM 通路"便宜一个量级

三条理由（已核实）：
1. **契约只禁"自动路径"**：§6 原文「需要 LLM 的一类（**必须手动触发，不进自动路径**）」。而「每轮收尾主代理写记忆」**不是自动路径** —— 是主代理在环的有意识判断，与人工触发同性质。
2. **工具通道现成**：`memory_note_pre(kind=plan)` 就是写白板的口子（`index.js:543` 的 `snapshotPlanTitle` 已告知模型"内容过时可用它重写"），且**过判据门 + 保护门**（`:1939`/`:2020`），丢卡会被拦。
3. **白板本就是"模型维护区"**：契约 §5 人机分区已规定模型写哪段。

### 但**必须先解决的前提**（实测）

**当前 `GUIDANCE` 对白板零覆盖** —— 逐字扫过：
```
白板 no   PLAN no   kind=plan no   handoff no   账本 no
```
`GUIDANCE`（`index.js:143`，**845 字符 ≈ 423 token**）是每轮注入的记忆纪律，**详列了各 memory_* 工具，唯独没提白板**。
⇒ **模型被明确要求"做记忆"，但从未被要求"维护白板"。这才是白板腐烂的真因 —— 不是模型偷懒，是纪律里没这条。**

### 所以要做的：「扩写 `GUIDANCE`」（用户说的"固化文档"）

**零新增管线** —— 它本来就每轮注入，加几十字符即可。

### 三条设计要点（**必须守住**）

**① 写成「条件触发」，不是「每轮都做」**
否则每轮改白板 → 噪声 + token 浪费。
措辞方向：「**当本轮产生与白板既有结论冲突的信息、或完成了白板上的目标时**，用 `memory_note_pre(kind=plan)` 更新」。

**② 必须写明「只报告、不自动删」**
契约 §6 尾注为硬纪律。否则模型会把"清理白板"理解成"删卡片" → 违反 §4 写入门（会被拒，但模型会反复试）。

**③ 白板关闭时不得有副作用**
`handoffEnabled=false` 时白板不读不写（`:3581`、`:3615` 已有解耦处理）。
纪律里须说明「白板未启用时跳过」，否则模型会去找不存在的 PLAN.md。

### 已纠正的数字误记

用户说的「默认 10000」**不是注入预算**：
- `SECTION_ORDER = 10000`（`index.js:137`）是**注入顺序** —— 末尾注入，让模型最后读到、遵循度高
- 真实注入预算是 `injectBudgetChars = 8000`（本机配置实测）
- **但用户直觉指对了位置**：那个"最后读到"的位置，正是放纪律最合适的地方

---

## 四、明天动手顺序（5 步，已排定）

| 步 | 缺口 | 难度 | 备注 |
|---|---|---|---|
| 1 | **缺口 2** 白板进检索 | ★ 很低 | 补两行，变量现成，收益最大 |
| 2 | **缺口 1** 锚点解耦 | ★★ 低 | 拿掉 `boardMode` 壳，**要跑回归** |
| 3 | **缺口 3** 索引派生 | ★ 低 | 纯函数，同源两渲染 |
| 4 | **缺口 4①②④** lint | ★★ 中低 | 只读纯函数；③ 判据待定 |
| 5 | **缺口 4⑤ + 白板纪律** | ★★ 低 | 扩写 `GUIDANCE`；依赖前几步（纪律要能引用索引/lint 判据） |

**顺序理由**：⑤ 放最后，因为它要在纪律里引用前面做出来的东西。

---

## 五、实施纪律（沿用本仓既有传统）

1. **改前备份**：`*.bak-YYYYMMDD-<tag>`
2. **改后 `node --check`**（`lib/index.js` 是 CRLF 大文件，注意 edit 的 `replace_all` 命中计数）
3. **跑全量回归**：基线 **PASS 105 / FAIL 0 / TIMEOUT 0（160.5s）**（`node tools/run-smoke.mjs`）
4. **写新套件**：每个缺口配可失败断言；**变异演示**要真红（把机制改回旧行为，测试必须失败）
5. **无 BOM**；大文件分块写
6. **代码留 pre 线**，未获明确同意不 commit/push
7. **host 代码改完只告知用户自行重启**，严禁 Stop/Start-Process

### ⚠️ 本轮要新增的纪律（前几轮踩出来的）
- **引用行号前必须回读确认**（本次曾把 `:2062` 误记 `:2361`）
- **workflow 脚本先自查变量定义与 `return`**（本次 7 个 agent 白跑）
- **契约文档措辞像"已完成说明"，引用前必须 grep 真实调用链** —— 有调用点才算已实现

---

## 六、本轮已完成、可安全推送的部分（纯文档）

| 文件 | 内容 |
|---|---|
| `README.md` / `README.zh-CN.md` | 顶部按钮行换 for-the-badge 徽章；新增 5 个技术段（三层下探 / Karpathy 模块 / 唤起度 / 三级部署 / 交接工程）；含「⚠️ 实现边界」如实标注 |
| `docs/USER-GUIDE.zh-CN.md` / `.en.md` | 顶部徽章 + 返回链接；版本号 `2.2.7+`→`3.0+`；新增 §13「3.0 底层重建」5 小节 |
| `docs/internal/ROADMAP-20260917-WEEK.md` | 新增 §3.5 缺口审计（技术版） |
| `docs/internal/S10-GAPS-PLAIN-20260917.md` | 缺口大白话版（本文档的上游） |
| 本文档 | 施工交接 |

**推送注意**：
- 当前 `package.json` 版本 = **2.5.2**，pre 线 HEAD = `746a4ae`，工作区 **247 项改动**（其中 201 未跟踪）
- 远端 `main` = `43c5492`（本轮礼节性 merge 后的位置）
- **推送策略已由用户裁定**：远端 merge 属礼节性，最终以**本机版本强制覆盖**
- 推送前须确认：**README 图片引用未动**（用户指示等前端重构）

---

## 七、未决 / 待用户拍板

1. **lint 第③类（被提及却无独立卡）的判据** —— 「什么算概念」「几次算反复」需用户定；判据不清会是误报源。
2. **lint 第④类（缺交叉引用）的"相关"判据** —— 同上。
3. **白板纪律的具体措辞** —— 待我出草案（明天第 5 步）。
