# 前端重构前置清单 · dsh-auto-memory（2026-09-19）

> **用途**：回答「重构前端之前，还需要做哪些动作」。
> **权威上游**：`BATTLE-PLAN-20260917.md` §7/§8/§9/§10 · `RESUME-20260919.md` §2 · `DESIGN-OVERHAUL-PRE-RESEARCH.md` §1。
> **本文件是排期视图**，不新增决策；与上游冲突时以 BATTLE-PLAN 为准。

---

## 0. 划线依据（一句话）

**前端重构会冻结整个后端。** `DESIGN-OVERHAUL-PRE-RESEARCH.md` §1.1 的冻结清单：

| 冻结项 | 范围 |
|---|---|
| 记忆引擎 | `lib/index.js` 的记忆/检索/注入/水位/接续全部逻辑；`lib/*-pre.js` 除 `client.js` 外**全部模块** |
| HTTP 契约 | 46 条路由的路径 / 方法 / 请求响应结构 |
| 配置语义 | `DEFAULT_CONFIG` 85 键的名称 / 类型 / 默认值 / 联动规则 |
| prompt 层 | `DEFAULT_PROMPT_LAYERS`、注入文案、缓存友好性 |
| Python 引擎 | `python/`、worker 协议、安装向导后端步骤 |
| 工具面 | 14 个 `memory_*` 工具的名字与参数 |
| 测试 | `tests/smoke/` 既有断言（**只能新增，不能放宽**） |

⇒ **所有后端改动只有两个窗口**：① 前端开工前 ② 前端完工后。**本清单只管窗口 ①。**

---

## 0.5 ★★★ 前端开工闸门（2026-09-19 用户强制要求）

> **用户原话**：「**你做前端之前一定要停下来问我**，咱们先把前端之前的事情了了再说。」
> 「前端是一个非常大而且非常重要的项目，**不能一起做**」「**值得专项规划**」

**⇒ 硬性闸门（agent 必须遵守）**：

1. **本清单 ①–④、⑨、⑩ 全部完工后，必须停下来**，**不得自行开始前端重构**；
2. 停下来时，须与用户**专门讨论并固化成独立的前端专项文档**，讨论内容至少包括：
   - 浮层/浮窗重构（含「白板看板是否改成浮窗」）
   - 白板看板重构
   - **主页重做**（用户明确其为**项目量很大的工程**，要做成「比较花哨的东西」，**需专项、耗时较长**）
   - 首次启动页重构 + sponsor 展示
   - 审批界面可读性（§4）
   - §8 六个拍板点（**全部推迟到此时讨论**，见下）
3. **⑧ 六个拍板点：全部推迟**到前端专项规划时再定（用户 2026-09-19 裁定）：
   | # | 事项 | 用户态度 |
   |---|---|---|
   | ⑧-1 首页托管 | **推迟** —— 「这个不太清楚，你刚才说的这些**专有名词**……等到要说的时候，**再给我说一下，然后停下来让我决定**」 |
   | ⑧-2 设计主张 | **推迟** —— 接受两种语境，但「**前端是需要再思考的一个大模块**」，做前端前要重看粗规划、讨论出细规划 |
   | ⑧-3 浮层面板 | **推迟** —— 「先别着急做呢，等真正做前端重构时再仔细聊、仔细出规划」 |
   | ⑧-4 出厂默认 | **倾向于改为出厂 `true`**（理由：本批加了很多功能、向导页要重做、还要加 sponsor 内容）——**但统一到前端规划时定** |
   | ⑧-5 docs 出包 | ✅ **已拍板：保持现状** |
   | ⑧-6 排期节奏 | 按 P0–P6 推进，**但做前端前必须停下来**，固化成专门的前端文档 |

**★ ⑧-1 的专有名词（用户要求届时解释）**：
- **htmlpreview**：用 GitHub 的 htmlpreview 服务直接渲染仓库里的 HTML（现在首页的做法，零配置）
- **GitHub Pages / `gh-pages` 分支**：GitHub 官方的静态站点托管（更正式，但需在仓库 Settings 里开一次）
- ⇒ 两者的差别＝「零配置的临时预览」vs「正式的站点托管」，届时再定。

---

## 1. 硬前置（用户已明确裁定「前端之前」）

| # | 事项 | 用户原话 | 状态 |
|---|---|---|---|
| **①** | **修 issue #55–#58**（合作方 Minervaowl7 提的 4 条） | 「先把这 4 个记录等到**前端重构之前**一起改」 | ✅ **完工 2026-09-19**（套件 16/16、变异 5/5 真红、回归 PASS 123） |

**4 条归属与修法**（4/4 已核真，报告 `ISSUE-55-58-VERIFICATION-20260918.md`）：

| issue | 文件 | 里程碑归属 | 缺陷 | 风险 |
|---|---|---|---|---|
| #55 | `storage-manage-pre.js:119` | M10 存储管理 | `readSidecarPrev` 引用未定义的 `docStore`（只在 `:135`/`:169` 局部定义）⇒ 恒抛 ReferenceError 被 `:127` 吞 ⇒ 恒返 null ⇒ **「修复」按钮把 fresh 证据全翻 stale（功能反向）** | 极低 |
| #56 | `evidence-store-pre.js:119` | M5-2 Evidence Store | `_appended.add(id)` 在写盘**之前**；写失败无 delete ⇒ 一次瞬时失败后同 id 重试恒被拒 = **证据静默丢失** | 低 |
| #57 | `episodic-store-pre.js:292` | M8 三店 | `current = data.current \|\| null` **零形状校验**（对比 `:222`）⇒ `current={}` 时每次 consolidate 抛 TypeError ⇒ **巩固链路静默停摆** | 低 |
| #58 | `activation-host-pre.js` | M6 投递 | ① `disposeRuntime`/`disposeSession` **全仓零调用方** ② `:352` 写入键 `sessionId\|ws:workspaceKey` 与 `:510` 删除键 `String(runtimeKey)` **错配** ⇒ 即使接线也是空操作 | 中 |

**执行纪律**：4 条**当前零测试覆盖** ⇒ 每条**必须先写「能失败」的套件**；#58 修完须用**变异演示**确认键格式真的对齐（正是「改了一处、另一处还是旧键」的典型）。

**共同特征（值得记住的判据）**：4 条**没有一条**会被现有 smoke 抓到，也**没有一条**在正常使用中报错——3 条是 **fail-soft 的 catch 吞掉了本该炸的错误**，1 条是「删除键写错 ⇒ 删除静默失败」。

---

## 2. 应赶在冻结前落地的后端改动

> 这些**都改 `lib/`**，前端开工即进冻结门内，只能等前端完工。

| # | 事项 | 为什么必须在前端之前 | 现状 |
|---|---|---|---|
| **②** | **落地 R4-A**（I5 修正：三态一律返回 + 标记） | 改 `index.js` 检索侧三处 + `l0-extract-pre.js` | ✅ **完工 2026-09-19**（探针 38/38 + 端到端 15/15、变异 5/5 真红、回归 PASS 123） |
| **③** | **Hermes 遗留修复** | 打开 `procedurePromotionEnabled` 的前提 | ✅ **完工 2026-09-19**（③-b 一并解决）：清洗器改为**结构判据**，真机 4 类漏网全部清空；新套件 36/36 + 变异 6/6 真红。见下行 ③-b |
| **③-b** | **H-3 清洗器覆盖不全**（③ 的实施本体） | `intent-clean-safe-pre.js` 原用**行首字面量白名单** | ✅ **完工 2026-09-19**：改为**形态侦测**（F1 结构化转储 + F2 声明头词族 + 位置约束），并新增 `BE_STATEMENT_RE` 覆盖**被截断到 40 字符**的形态。真机实测：4 类信封全清空、3 条真人原话全保留；套件 36/36、变异 **6/6 真红**、SHA256 逐字节还原。见 §③ 落地要点 |
| **④** | **教训 → 观察型候选** | procedure 层通路；用户裁定「教训肯定得进 C，**不自动晋升**但**可被搜索到**」 | ✅ **完工 2026-09-19**：新增 `lessonCandidateFromRetractedPre()` 生产者（`memory-hub-pre.js`），`retracted` 教训 → `observationOnly:true` 候选 → **`promote()` 短路 `observation-only`**（证据拉满 99/99 仍 keep）；可 `query()` 检索到。套件 30/30、变异 **6/6 真红**、SHA256 还原。见 §④ 落地要点 |
| **⑤** | **R4-C 层次保底配额**（配额问题） | 改 `recall-fusion-pre.js` **排序** | ⏸ **用户 2026-09-19 裁定：移到「前端之后」再决定**（原暂缓理由：「先保持不变，等到什么时候观察有个结果了再说」）<br>⇒ **不属于本清单窗口 ①**，见 §5「前端之后」 |
| **⑥** | **G3 状态写入** | 改 `memory_note_pre` 写入路径（**只挂结论层**，`memory_log_pre` 零改动） | ✅ **完工 2026-09-19**：磁盘形态（`note-status-pre.js` + `note-status-apply-pre.js`）+ 写侧参数 + **读侧接线**全部落地（端到端 25/25、接线锁 26/26、变异 7/7 真红、回归 PASS 125） |
| **⑨** | **技能队列注入污染修复** | `intent-clean-safe-pre.js` 的 `EXACT_ENVELOPE_RE` 是**字面量白名单**，`③` 改为形态侦测后**仍有新形态穿透**（skill 目录 JSON / 运行时信封中段），垃圾经 `crossFeed()` 落成 observed 候选 | ⚠️ **待开工**（2026-09-19 真机发现，用户截图确认；见 §3.5） |
| **⑩** | **长期记忆可读性（R1）** | 用户原话「表述还是不太好，看不懂这个记忆到底是什么回事儿」；**此前被 §8 归口约定挡在执行序外**（见 §3.6 归因） | ✅ **取证已完成**（`R1-READABILITY-FORENSICS-20260919.md`）· **问题 1/2/3 已答** · **问题 4（修法）待拍板**（A/B/C/D，agent 倾向 B+C 先行）；**排在前端之前做完** |
| **⑪** | **skill 导出层（procedure → SKILL.md）** | 用户举 WorkBuddy 例："把流程自动撰写成 skill 和一些可执行的程序，下次遇到同样情况就可以调用它" | ✅ **设计已拍板 2026-09-19**（见 §3.7「★ 拍板结果」）；⚠️ **待开工**——**依赖 ⑨ 先清污染** |

### ② 的落地要点（避免撞既有断言）

- 契约文档 `THREE-LAYER-CONTRACT.md:183` 的 I5 须改写（原为「检索结果与注入内容**两处都过滤**」⇒ 检索侧改为「标注」）
- ⚠️ **会撞 `smoke-test-three-layer-pre.mjs:188` 的字面量正则** `/if \(!isCurrentPre\(it\)\) continue/` ⇒ 该断言须同步更新**并注明原因**
- **注入侧 `isCurrentPre` 保持不变**（常驻目录仅 800 token，装过时条目会挤掉现行结论）

### ②与⑥的关系

**同一个决策的两面**：② 是**读侧**（检索到作废条目怎么办），⑥ 是**写侧**（什么条件下标 `superseded`）。建议同批做。

---

## 3. 需要用户拍板的（前端开工前必须给答案）

| # | 事项 | 阻塞什么 | 出处 |
|---|---|---|---|
| **⑦** | **S1.3 literature 理念**：(a) 同一功能多处可挂载 / (b) 设置内切换「功能归属面板」 | **决定前端容器架构形态**——BATTLE-PLAN §7 **唯一还开着的设计类拍板项** | §7 第 2 项 |
| **⑧** | **前端重构 6 个拍板点** | 首页托管 / 设计主张 / 浮层是否降级 / 出厂默认 / docs 出包 / 排期节奏 | `DESIGN-OVERHAUL-PRE-RESEARCH.md` §8 |

> **§7 其余项状态**（2026-09-19 核对）：
> - 第 1/3/4/5/6/7/8/9 项 **已全部拍板**
> - **第 11 项（M6 `act.skill` 定位）已撤销** —— 用户澄清 skill 是**相对独立的系统**，晋升成功后由**本插件语义召回**正常注入 ⇒ `act.skill` 段是**实现本体，保持不变**，无需改码
> - ⇒ §7 实际只剩 **第 2 项（= 上表 ⑦）** 与 **第 10 项（= 上表 ③）**

---

## 3.5 ★ ⑨ 技能队列注入污染（2026-09-19 真机发现）

**发现方式**：用户截图「记忆中枢 → 技能审批队列 (8)」，其中**至少 3 条不是技能**：

| 截图里的 title | 真实身份 |
|---|---|
| `{"path":"D:\personal_issue\dsh-vision` | DSH **skill 目录的 JSON 元数据** |
| `Current DSH file policy: danger-full-acc` | 系统注入样板 |
| `Current runtime context. This snapshot s` | 系统注入样板 |

（均被 `lib/memory-hub-pre.js` 的 `slice(0,40)` 截断，与截图长度吻合 ⇒ 确认是 `p.title`。）

**根因链（行号级）**：
```
episodic-store-pre.js:140   stripRuntimeIntentPre(userText)   ← 第一次洗
  ↓
memory-hub-pre.js:152       stripRuntimeIntentPre(ep.intent)  ← 第二次洗
  ↓
intent-clean-safe-pre.js:34 EXACT_ENVELOPE_RE = /^(?:current runtime context\.|current dsh file policy:)/i
                            ← 仍是【字面量白名单】，且要求【行首】
  ↓ 穿透（两个原因：① skill JSON 不在白名单 ② 信封出现在文本【中段】而非行首）
memory-hub-pre.js:153       if (ep.success && … && ep.actions.length) → observe()
  ↓
procedure-store：observationOnly 候选 → 进「技能审批队列」
```

**★ 关键结论**：`③`（H-3 清洗器改造）**并没有彻底解决**这类问题。
- `③` 把**判据**从「行首白名单」升级为「形态侦测（F1 结构化转储 / F2 声明头词族）」——**这是对的**；
- 但本节实测的穿透发生在 `intent-clean-safe-pre.js:34` **另一处**（`EXACT_ENVELOPE_RE`），且 `③` 的位置约束（"只在尚未出现真人文本的信封区内生效"）**恰好让它放过中段信封**。

⇒ **⑨ 的修法方向**：把 `:34` 的 `EXACT_ENVELOPE_RE` 一并并入 `③` 的形态侦测体系；
并复核「位置约束」是否过宽（中段信封是否应清）。

**重要性**：**⑨ 是 ⑪ 的前置** —— 队列里现在是脏数据，直接导出会产出垃圾 skill。

### ★ ⑨ 修复已落地（2026-09-19）

**改动**：`lib/intent-clean-safe-pre.js`（备份 `*.bak-20260919-B5`）

**新增两条判据（与 F1 同属「任意位置生效」，不走 F2 的位置约束）**：

| 代号 | 判据 | 覆盖 |
|---|---|---|
| **F3** | `PLUGIN_TAIL_MARKER_RE` —— 召回块标记**族**（`[Retrieved memory ref…` 允许截断 / `Verify against…` / `If a reference hints…` / `Source: mem_<32hex>` / `Reason: fv2 lane=` / `Score: N (rank N/N)`） | **本插件自产的召回块**（自污染闭环） |
| **F4** | `looksEncodingCorruptedPre` —— U+FFFD ≥ 3 | 编码损坏行 |

**★ 关键修正：`^` 锚定 = 不误伤引用句**
`/^\[?Retrieved memory ref|…/i` 只匹配**行首**，故「帮我看看 [Retrieved memory reference] 这个标记是干嘛的」**不会被删**（套件 [4] 组 3/3 守住）。

**★ 为什么 F3/F4 必须「任意位置生效」**：召回块常出现在 `episode.intent` 的**中段**（前面已有真人文本）。若沿用 F2 的「仅信封区」约束会**再次漏网** —— 这正是 ⑤ 的漏网根因之一。套件 [6] 组专门锁这条。

**验收**（全部实跑）：
- `tests/smoke/smoke-test-batch9-20260919-pre.mjs` —— **33/33**
  （含：真机 11 条 title 逐条 / 真人文本不误伤 / F3 六种尾部行 / F3 反引用不误伤 / F4 边界 1-2 个 U+FFFD 不误伤 / **位置无关性** / 既有 F1/F2 未破 / 判据导出 / 接线锁 / 反证 F3 有独立 return 分支）
- `artifacts/_mutate-batch9.mjs` —— **3/3 真红 + SHA256 逐字节还原 + 末态绿**
- `artifacts/_probe-9-dirty.mjs` **9/9**、`artifacts/_probe-9-newform.mjs` **0 漏网**

### ★ ⑨ 存量脏数据：**已清理完毕**（2026-09-19 20:15，用户重启后执行）

> 完整记录见 `docs/internal/ISSUE9-PURGE-AND-R1-PLAIN-20260919.md`。此处只留结论。

**① 数字修正：不是 5 条，是 8 条。**
用**旧清洗器（改前备份 B5）**与**新清洗器（生效中 C6）**对同一份真机数据逐条对跑：旧判 **5** 条、新判 **8** 条。多出的 3 条恰是本次修复的靶子——`proc_pre_77d5…`（中文乱码）、`proc_pre_bc7b…` 与 `proc_pre_79f2…`（**插件自己的召回块标记**）。
⇒ **数字从 5 变 8 不是数据变脏了，是判据变准了** —— 反向印证 ⑨ 修复有效。

**② 差点白做（关键发现，踩坑记录）。**

| 位置 | 代码 | 后果 |
|---|---|---|
| `procedure-store-pre.js:406` | `dispose()` → `io.save(snapshot())`（**无条件**） | 宿主关停时把**内存整份快照写回盘** |
| `procedure-store-pre.js:411` | `persist()` → 同款 `io.save` | 任何一次 persist 都会整份覆盖 |

宿主内存持有的是**启动时 restore 的 12 条**；证据：清理前盘上 `savedAt` = **12:04:51 UTC**，恰等于用户重启时刻。
⇒ **若直接编辑 `procedures.json` 删行，用户下次重启时旧实例 `dispose()` 会把 12 条原样写回，清理当场作废。**

**③ 实际做法（走宿主自己的通路，不动源码）。**

```
POST http://127.0.0.1:3080/api/dsh-auto-memory-pre/memory-hub
     { action: 'deprecate', procedureId: 'proc_pre_…' }
```

判据**不手工列 id**，复用生产清洗器 `stripRuntimeIntentPre()` 现场判定 ⇒ 「清掉的」与「新代码会拦的」严格一致。

**④ 实测结果（落盘已验证，非「接线正确」纸面结论）。**

| 指标 | 清理前 | 清理后 |
|---|---|---|
| 审批面 `pipeline`（会进注入路径） | **9** | **3** |
| 脏条目中处于 `observed`（可注入）者 | **6** | **0** |
| 盘上文件 bytes | 9,540 | **9,930** |
| 盘上文件 mtime | 12:04:51Z | **12:15:29Z** |

弃用 **6 条 / 失败 0 条**。注入只取 `stage === 'active'`（`renderChecklists` → `activeProcedures()`）⇒ **注入路径已 100% 干净**。剩余 3 条 observed 全为真人文本。

**⑤ 为什么选「弃用」而非「物理删除」**：`deprecated` 是这套 store 的**终态而非删除**（设计如此，`applyAutomaticTransitions` 亦只归档不删），可逆且留 `deprecateReason`；物理删除需新增 store 方法 = 动源码，风险收益不成比例。
**备份**：`artifacts/procedures.json.bak-20260919-201223`（SHA256 `B51CFAC5…`，与清理前源文件逐字节相同）。

---

## 3.6 ★★ ⑩ 长期记忆可读性（R1）：**不是跳过，是被归口约定挡住**

**用户 2026-09-19 追问**：「这个长期记忆的优化应该是放在作战记忆里做过的，不知道为什么做完以后还是这样的，难道是跳过去了？」

**答：没有跳过，而是从一开始就被写进了「不进执行序」的那一节。**

### 证据链

| 位置 | 内容 |
|---|---|
| `BATTLE-PLAN-20260917.md:780-783` | `## 8. 远期调研清单（Long-term research backlog）`<br>「**归口约定**：本节条目**不进 L/M/S 执行顺序**，不参与层级冻结；**只在用户明确点名或排期时才启动**。」 |
| 同上 §8 第 4/5 条 | 第 4 条 = 教训/攻关 → skill 式固化（= 今天的 **⑪**）<br>第 5 条 = **审批界面可读性**（「看不懂只能盲确认」） |
| `PRE-FRONTEND-CHECKLIST §4` | 审批界面可读性**被归入「前端重构本身」**（不属于「之前」） |

⇒ **R1 从未进入任何一批的执行序**，所以「做完之后还是这样」是**预期结果，不是遗漏**。

### ★ 但这里有真问题（用户的抱怨成立）

1. **R1 只写进了项目笔记正文，没进 Tier-0 常驻目录** ⇒ 每轮注入看不到它
   （本轮系统提示自身就有 `[降级] log 层有 7 条候选但 0 条进目录（配额/预算裁剪）` 的同类现象）。
2. **归口约定的副作用**：把一个**用户明确提出的痛点**放进「只在点名时才启动」的清单 ⇒ 实际上等于**无限期搁置**。
   用户在 2026-09-18 就说过，到 09-19 才被重新问起 —— **这正是约定本身该被质疑的地方**。

### ★★ 取证已完成（2026-09-19）→ 报告：`docs/internal/R1-READABILITY-FORENSICS-20260919.md`

**方法**：`artifacts/_probe-r1-readability.mjs`（可重跑、零依赖），对两份**真机**记忆文件做**无预设**的六类缺陷量化。

**结论（三句话）**：
1. **主因在「写入/整理侧」，不在「渲染侧」** —— 空行、重复日期标题、缺主语都是**文件本身**的属性。
2. **但渲染侧确有一处独立且真实的结构性缺陷**：注入文本保留对人有**零信息量**的 `<!-- memory:mem_<32hex> -->` 锚点注释，且**每条前各带一个 `## <日期>`** ⇒ 同一天被切成 N 段。
3. **⇒ 修法是两个方向各自独立的改法，不是一个** —— 这正是**问题 3（归因）**要分清的，**已分清**。

**关键数据（真机）**：

| 指标 | 用户级 | 项目 |
|---|---|---|
| 体量 | 13,160 字符 / 290 行 | 23,693 字符 / 444 行 |
| 开头连续空行 | 3 | **50** ← ★ 打开即空白 |
| 空行占比 | 32% | **37%** |
| 纯日期标题重复 | **15 种**（`2026-08-19`×5…） | **2 种**（`2026-09-18`×**19**） |
| 缺主语条目 | 0% | **15%** |
| 过短/无完整句 | 25% | **30%** |

**典型样本（逐字真机）**：`M1 契约行渲染设计定案：…` / `` `lib/tier0-catalog-pre.js`：新增… `` / `isRetrievablePre 只对未知值 fail-closed` —— 这些条目**都对**，但读者需先知道「M1 是什么」；**缺的不是信息，是入口**。空转条目：「本轮无新增跨项目通用规则。」「无」「无」。

**问题 1（取证）✅ 已答** · **问题 2（判据）✅ 已给出**（探针已实现，可直接接测试）：
开头空行=0 · 空行占比<15% · 同日重复 `## `=0 · 缺主语占比<5% · 空转条目=0
**问题 3（归因）✅ 已答**：**写入/整理侧为主 + 渲染侧有一处独立缺陷，两者修法不同**。
**问题 4（修法）⚠️ 待用户拍板**：

| # | 方案 | 改哪里 | 成本 | 影响面 |
|---|---|---|---|---|
| **A** | 写入侧**自解释模板**（首句完整句 + 适用范围） | 写侧提示 + 整理器 | 低 | 新条目变好；旧条目不变 |
| **B** | **渲染侧**：隐藏锚点注释、同日合并标题 | `tier0-catalog-pre.js` / 注入渲染 | 低 | **立刻改善已存在条目** |
| **C** | **整理侧**：压空行、合并同日段、删空转条目 | 整理器 | 中 | 一次性改善存量；需防误删 |
| **D** | 检索侧逐条摘要句 | 检索侧 | 高 | 需模型调用 |

**agent 倾向**：**B + C 先行**，**A 随后**（防新增）。理由：A 属**写入语义变更**，按本仓纪律**须用户拍板**。

> ### ⚠️ 2026-09-19 20:30 重大修正：上面那句「不动记忆内容、风险最低」**是错的**
>
> **用户追问**：「这不是给人看的，要嵌合语义模型，改了语义会不会变」——**问对了**。
> 经代码核实，`MEMORY.md` **不是纯文本**，它同时喂**三条管线**：
>
> | # | 消费方 | 依据 |
> |---|---|---|
> | ① | 注入上下文（整篇） | `index.js:5498-5499` |
> | ② | L0 摘要（按锚点切条） | `l0-extract-pre.js:34` |
> | ③ | **语义语料**（锚点划字节区间 → `recordDigest`） | `memory-anchor-pre.js:168/248`；复核 `m4-corpus-pre.js:107` |
>
> ⇒ **改正文的排版会动 `byteStart/byteEnd` ⇒ `recordDigest` 失配 ⇒ 该文件被静默踢出语义语料**（`m4-corpus-pre.js:96` 文件级先比 `fileDigest`，不等直接跳过整个文件）。
> 且 `memoryId` 是**随机生成**的（`memory-anchor-pre.js:40`）⇒ **就地改文件会换一批新 id**，已有证据链/`supersededBy`/白板锚点全部对不上。
>
> **⇒ B/C 不是「低风险排版优化」，而是「动数据身份」。** 风险等级须整体上调。
> **真正安全的是「追加式写入」**（既有 `memory-writer` 事务路径），不是就地重排。
>
> ### ★★ ⑩ 已拆分为两半（**这是恢复上下文的关键**）
>
> | | **⑩-a** | **⑩-b** |
> |---|---|---|
> | 是什么 | **前端「记忆中枢」面板看不懂** | **记忆文件侧隐患**（M8 回写雷 + 排版/语义耦合） |
> | 是否用户本意 | ✅ **这就是 ⑩ 的原意**（用户以面板截图澄清） | 后续深挖出的 |
> | 涉及语义吗 | **不涉及**（代码核实） | **涉及** |
> | 涉及 `MEMORY.md` 吗 | **不涉及** | **涉及** |
> | 状态 | **待修（先做）** | **取证中**（两个只读子代理并行） |
>
> **★ ⑩-a 的直接成因（新发现）**：`memory-hub-pre.js` 的 `crossFeed()` **两条通路不对称**——
> 走 procedure（约 `:152`）调 `stripRuntimeIntentPre(ep.intent)` ✅（⑨ 已修）；
> 走 fact（约 `:166`）**完全没清洗**，直接 `ep.intent.slice(0,30)` ❌。
> 同理 `factCandidateFromRow()`（约 `:249-265`）也不调清洗器。
> ⇒ **⑨ 只修了「技能」那条路，「事实」这条路整套漏掉** ⇒ 这是 `facts.json` 变脏的直接成因。
> 实测：`facts.json` **4 条中 3 条脏**（1 条含 22 个 U+FFFD；2 条内嵌运行时信封）。
>
> **★ ⑩-b 的雷**：`index.js:8956-8991 hubFlushTick`（每 30 分钟）会把 facts **写回 `MEMORY.md`** ⇒
> 脏 fact 有通路爬进注入面与语义语料。**实测尚未引爆**（`flushed=true` 但 `count:0`，两份 `MEMORY.md` 里 `（M8 固化）` 计数均为 0）。
>
> **⇒ 完整规划（含修复方案、执行序、验收标准）见 `docs/internal/ISSUE10-PLAN-20260919.md`** —— 上下文压缩后**先读那份**。

**与 ⑪ 的复用**：若 A 落地，⑪ 的 `SKILL.md` 导出模板可直接复用同一套「自解释条目」判据（有复用价值，但**互不阻塞**）。

---

## 3.7 ★ ⑪ skill 导出层（procedure → SKILL.md）

**用户原话**：「我在 WorkBuddy 里把这个（30 多份问卷）做出来以后，我会帮你把这个流程自动撰写成了一份 skill 和一些可执行的程序，然后下次遇到同样的情况就可以调用它了。**现在这种情况能做出来吗**」

**答：能做，地基已具备，缺的只有「导出层」。**

### 已具备（`procedure-store-pre.js` + `memory-hub-pre.js`）

- 四阶段生命周期 `observed → candidate → validated → active`
- 晋升闸门（`procedureMinSessions` / `procedureMinSuccess`）+ 证据累积（`evidence.success/sessions`）
- 风险分级（`riskLevel`，`procedureHighRiskApproval`）+ 置顶 / 弃用（`pin` / `deprecate`）
- 检索可查

### 缺的只有导出

**DSH skill 发现路径（已核实，`dsh-skill-filesystem/lib/index.js`）**：
```
<projectRoot>/.dsh/skills/<name>/SKILL.md
<projectRoot>/.agents/skills/<name>/SKILL.md
<DSH_HOME>/skills/<name>/SKILL.md
~/.agents/skills/<name>/SKILL.md
```
目录束形态 = `<name>/SKILL.md`；另有扁平 `.md` 形态。

⇒ **写一个导出器**，把 `active` procedure 渲染成 `SKILL.md` 写进上述目录，**DSH 下次启动即自动发现并注入**。

**★ 与既有架构裁定的关系（重要）**：用户 2026-09-18 已裁定「**skill 注入归官方流程**，插件不重复注入、不占插件预算」。
导出层**完全符合**这条：插件只**生产文件**，注入交给 DSH 官方 ⇒ **不冲突、不需改裁定**。

### 待用户拍板

1. **「可执行程序」的形态**：只写 `SKILL.md`（步骤说明）？还是同时导出 `.mjs` 脚本？
   （涉及安全边界：自动生成的脚本被后续会话执行 = 需要明确授权模型。）
2. **导出时机**：手动点「导出」按钮 / `active` 后自动导出 / 定期批量？
3. **导出目标目录**：项目级（`.dsh/skills`）还是用户级（`~/.agents/skills`）？

### ★ 实现已完成（2026-09-19）

**新增两文件（分层：纯渲染 / IO 分离）**：
- `lib/skill-export-pre.js` —— **纯渲染层**（无 IO，可直测）：`SKILL_USAGE_NOTICE_PRE_V1`（约束条款常量）/ `SKILL_NOTICE_ANCHORS_PRE_V1`（五锚点）/ `skillDirNamePre` / `renderSkillMarkdownPre` / `validateSkillMarkdownPre`
- `lib/skill-export-host-pre.js` —— **IO 层**：`resolveSkillsRootPre` / `projectNameFromPre` / `collectProgramsPre` / `exportSkillForPre` / `listExportedSkillsPre`

**三条设计纪律**：
1. **落盘走 `临时文件 + rename`** ⇒ 防半截 `SKILL.md` 被 DSH 扫到；
2. **防误覆盖**：目标目录已存在且**不含自检标记** `mem-skill-export-pre-v1` ⇒ 拒绝（`dir-occupied-by-foreign`），保护用户手写技能；
3. **不自动执行任何导出程序**（程序是参考资料，不是入口）。

**SKILL.md 内含四条显式约束**（⑪-1 硬要求）：①附带程序仅供参考、不是可直接调用的工具 ②**场景根本不同时 → 只做迁移，不要直接运行** ③**跨项目使用须先核对**，项目不一致一律只作参考 ④高风险步骤需人工确认。

**验收**：`smoke-test-batch10-20260919-pre.mjs` **57/57** · `_mutate-batch10.mjs` **6/6 真红 + 双文件 SHA256 还原 + 末态绿** · 全量回归 **PASS 132 / FAIL 0 / TIMEOUT 0**。

**★★ 两个「套件缺陷」教训（非代码缺陷，值得长期记住）**：
1. **判据同义反复**：`programs-not-constrained` 原用 `/不要直接运行/`，**而顶层约束条款正文里就含这四字** ⇒ 该判据**永远为真**（等于没判）。修法 = 锚定**程序块独有形态** `**不要直接运行**。`
2. **断言崩溃伪装成假绿**：导出失败时 `e1.file` 为 `undefined`，`existsSync(undefined)` **抛异常 → 整套件崩溃**，外面看像假绿。修法 = **显式守卫**（失败也必须是**可断言的红**）。
3. **「等价变异」不算假绿**：原 M5「跳过 host 校验」是等价变异（渲染层已保证条款齐全，host 校验只是**冗余防线**）⇒ **变异若不变产物，换变异而不是改代码**。

**⚠️ 需用户重启宿主**（本轮改了 `index.js` + 新增两模块）。

---

## 3.8 ★ ⑪ 的原始背景（保留）

### ★ 拍板结果（2026-09-19 用户）

| # | 问题 | **用户裁定** |
|---|---|---|
| **⑪-1** | 导出形态 | **`SKILL.md` + 附上过程中用到的程序**（如 `.py` 或其它有用程序）；**且 `SKILL.md` 里必须明写：这些程序只是「参考性」的 —— 如果当前做的事情与之前**根本不同**，可以用它来**迁移**，**不能直接运行**。** |
| **⑪-2** | 导出时机 | **晋升为 `active` 后自动导出** |
| **⑪-3** | 导出目录 | **用户级**（软件层面，可跨项目迁移）；**但导出物必须明确标注「适用于哪个项目」** —— **项目不一样时只作参考，不能直接用** |

**⇒ ⑪ 的核心安全设计（由 ⑪-1/⑪-3 共同确定）**：
1. 导出物**带项目来源标注**（project provenance）；
2. `SKILL.md` 内含**显式使用约束条款**：「参考性，非同场景须迁移、不可直接运行」；
3. **不自动执行任何导出的程序** —— 程序是**参考资料**，不是可调用入口。

---

## 4. 前端重构本身（**不属于「之前」**，列出以免混淆）

| 组 | 内容 | 出处 |
|---|---|---|
| **S 层** | S1 容器结构（看板升级为容器 + 记忆窗格作为其中一个功能页，旧位置保留共存）· S2 视觉对齐 DeepSeek Flow（V1–V8，含 V5 连线分支标签） | BATTLE-PLAN §4 |
| **★★ 审批界面可读性（用户 2026-09-19 强化）** | 「看不懂只能盲确认」⇒ **把这套东西「讲成人话」**，见下方 ★ 专段 | BATTLE-PLAN §8 第 5 条 + 用户本轮补充 |
| **★★ R7「用户级硬性约束」可视编辑（用户 2026-09-20 明确要求）** | 让用户能**自己增 / 删 / 改**每条硬性约束——它每轮无条件注入、不走语义层，所以必须可手动更正，见下方 ★★ 专段 | 用户 2026-09-20 原话 |
| **首次启动页重构 + 赞助商/中转站展示** | 用户要求「等做前端的时候再提醒我」 | BATTLE-PLAN §8 第 6 条 |
| **设计基座 → 收口（P0–P6）** | 令牌层 + 组件原语 · IA 重排（12 页签 → 原生插槽）· 设置台重构 · 冷启动闭环 · 文档体系 · 首页 + 发布链 · 收口发版 | `DESIGN-OVERHAUL-PRE-RESEARCH.md` §5 |
| **配图** | 22 张界面图 + 7 张宣传图**全部重拍**（顺序：UI 定稿 → 重拍 → 改文） | 同上 §2.4 / §7.5 |

### ★★ 审批界面可读性（用户 2026-09-19 以截图补充，**必须在旧前端基础上体现**）

**用户原话**：
> 「最后你在旧前端的基础上也要体现出来，要让人读懂。比如说 procedure memory、skill 的内容，以及晋升的原因等等。
> 比如说，你看这图片上他技能的名字和描述，都是很难让人看懂的。**这个也要在前端里更好地表示。**」

**截图实测问题**（`lib/client.js:2573-2586` 渲染的「技能审批队列」）：

| 现状 | 问题 |
|---|---|
| 每条只显示 `title` + `[stage]` + `· 观察` + 📌 + ⚠ + `evidence` 计数 + 三个按钮 | **看不出「这是什么技能」「为什么能晋升 / 为什么不能」** |
| `title` 直接取 `procedureIntent.slice(0, 40)`（`memory-hub-pre.js:155`） | 是**截断的原始 intent**，不是人话摘要 |
| `stage` 显示英文枚举 `observed/candidate/validated/active` | **未给中文解释** |
| **晋升判据完全不可见** | `promote()` 的 `reasonCodes`（如 `diversity-below-3` / `success-below-2` / `observation-only` / `no-success-criteria`）**只写进 diag 日志，从未回传前端** |
| `evidence` 只显示数字 | 看不出「见过几次、被引用几次、成功几次」的含义 |

**⇒ 前端改造要求（登记，前端阶段实现）**：

| # | 要求 | 依据 |
|---|---|---|
| **R1** | **技能名要能读懂** —— 不得直接显示截断的 intent；应渲染**人话摘要**（可复用 ⑪ 导出层的自解释模板） | 用户截图 |
| **R2** | **晋升原因必须显式展示** —— `reasonCodes` 要**从 host 回传到 `pipeline` 投影**，并给**中文解释**（「需要 3 个不同会话使用过，当前 1 个」） | 用户原话「晋升的原因」 |
| **R3** | **stage 枚举给中文**（observed=已观察 / candidate=候选 / validated=已验证 / active=已启用） | BATTLE-PLAN §8 第 5 条 |
| **R4** | **预览「晋升后会注入什么」** —— 渲染 `renderChecklist()` 的真实文本 | 同上 |
| **R5** | **evidence 给出人话**（不是裸数字） | 用户原话「skill 的内容」 |
| **R6** | 保留可撤回 | 同上 |

**⚠️ 前置依赖（重要）**：**R2 需要改 host**（`memory-hub-pre.js` 的 `overview()` 目前**不投影 reasonCodes**，
`pipeline` 只有 `procedureId/title/stage/riskLevel/evidence/pinned/observationOnly` 七个字段）。
⇒ **R2 不是纯前端改动，需与 ⑩ 那批或前端准备期一并处理**。
**建议**：把它并入 **T3（系统性收口）** 或前端开工前的最后一小批 host 改动。

---

### ★★ R7「用户级硬性约束」可视编辑（用户 2026-09-20 明确要求 · **前端阶段必做**）

**用户原话**：
> 「`[规则 — 用户级硬性约束 · 必须遵守]` 这一类的东西必须可以让用户够得着，能够自行添加和减少这个硬性约束。
> 这方面应该是没有语义层介入的，每次都必须注入。所以是可以更改的。比如用户觉得哪一个约束过时了或者不行了，
> 或者 AI 自己加得不对，都可以手动去删改。**这个功能必须要落在前端**。」

#### 为什么这条必须做（用户判断成立，附代码证据）

| 事实 | 证据 |
|---|---|
| 这是**每轮无条件注入**的硬约束 | `lib/rules-layer-pre.js:238` `renderRulesSectionPre()` → 注入块标题 `[规则 — 用户级硬性约束 · 必须遵守]` / 引导语「凡与其它内容冲突，以本节为准」 |
| **真源是文件、不是数据库** | `rules-layer-pre.js:12` 明写：**用户级规则＝既有用户级记忆文件（`~/.dsh/memory/MEMORY.md`）** |
| **不走语义层**（所以必须可手改） | 该层是**逐条直读文件**渲染，无检索/打分/召回环节；故「过时条目」不会被自动淘汰，只能人工清理 |
| 前端**目前没有条目级编辑入口** | 现有设置只有**容量上限**（`fUserCap` → `userCapacityChars`）与「用户记忆目录」路径，**没有任何增删改单条规则的 UI** |

**⇒ 结论**：真源是纯文本 `MEMORY.md`，模型通过 `memory_user_pre` 往里追加。
**AI 可能加错**（把项目级规则写进用户级、把一次性偏好升格为硬约束、措辞失当），
**用户也可能觉得某条过时**——而它**每轮都会注入**，错一条就每轮都被误导。**手工可纠正 = 这个设计的必要配套。**

#### 前端要求（登记，前端阶段实现）

| # | 要求 | 依据 |
|---|---|---|
| **R7-1** | **条目级列表视图** —— 把 `~/.dsh/memory/MEMORY.md` 解析成**一条一行的可读列表**（不是原始 Markdown 大段文本） | 用户原话「让用户够得着」 |
| **R7-2** | **单条增 / 改 / 删** —— 三个动作均可，且**改动立即回显**（用户既有硬性偏好：开关类改动必须即时回显，写盘成功但界面无变化视为「功能坏了」） | 用户原话「自行添加和减少」+ 用户界面偏好 |
| **R7-3** | **标注每条来源与时间** —— 区分「用户自己写的」/「AI 加的」（`memory_user_pre` 落盘带日期标题），便于用户判断该不该删 | 用户原话「AI 自己加得不对」 |
| **R7-4** | **删除 = 真删，不做软删** —— 该层是**无注释的纯列表**，软删标记反而会被当成正文注入模型。删前**必须二次确认**（不可撤销） | 该层渲染逻辑（不认任何状态标记） |
| **R7-5** | **写入必须复用既有 writeFull 事务** —— 备份 + 校验 + fail-soft 留痕，**不得绕过**（本仓状态写入纪律） | `memory_user_pre` 既有通路 |
| **R7-6** | **预览注入效果** —— 编辑后展示「下一轮注入会变成什么样」（复用 R4 的思路） | 与 R4 同源 |
| **R7-7** | 容量上限提示 —— 现已有 `userCapacityChars`（默认 24000）；编辑界面应显示当前占用，超限时按既有规则（先折叠、后退回归档） | `userCapacityChars` 设置项 |

**⚠️ 与既有纪律的关系（重要）**：
- 本项**只做 UI 入口**，**不改注入语义、不改 `rules-layer-pre.js`、不改 `memory_user_pre`**——
  即**不新增事实来源**（导出物/UI 都是 `MEMORY.md` 的派生视图，不是新的真源）。
- **R7-4 的「真删」是刻意的**：与「检索侧三态放行」那套**不同层**——
  三态服务于**记忆条目（可追溯教训）**，而本层是**每轮生效的硬性指令**，
  留一条过时指令 = 每轮持续误导。**故本层不做 superseded/retracted，只做删改。**

### ★ P0 前置（开工第一件事，防数据丢失）

`git add` **48 份未入 git 的文档**（`USER-GUIDE.en.md`、`HANDBOOK.md`、`ROADMAP.md`、`STATUS-BOARD.md`、`docs/prompts/*` 36 份）——
**npm tarball 是其中相当一部分的唯一副本**。整理 docs 前不收编，会直接销毁文件。

---

## 5. 前端之后（现在不做，登记防遗忘）

| 项 | 说明 | 出处 |
|---|---|---|
| **⑤ R4-C 层次保底配额**（配额问题） | **用户 2026-09-19 裁定：移到「前端之后」再决定**（原暂缓理由「先保持不变，等到观察有个结果了再说」） | 本清单 §2 ⑤ |
| **遥测效果报告实现** | 设计已定稿；三条红线：插件永不主动联网 / 报告只含聚合数与计数（**不含记忆原文、路径、查询词、会话 id**）/ 发送永远是用户显式动作 | `TELEMETRY-EFFECT-REPORT-DESIGN-20260918.md` |
| **M-CM3 残余** | 会话语义通道 · 跨工作区白板语料 | `M-CM-STATE.md` |
| **M-CM4 精度升级** | 真实 token 计数（待 host 暴露） | 同上 |
| **A4b 跨会话 / 跨 Agent 检索** | 路径已选 C（分期版 A）；语料与成本已实测（约 600MB） | ROADMAP §A4b（P2-⑦ 待拍板） |
| **procedural memory 重构 B1–B4** | 晋升标准重做（长时攻关也晋升）· skill 互链 hand-off · 晋升物可读 · 项目→全局晋升开关 | ROADMAP §B（群反馈 3/5/8 号的共同前置） |
| **STATUS-BOARD §5 存量** | token 账本 · 时间检索臂 · 倒排索引 · 科学性 S1 基准/消融 · 召回 A/B · 鸿蒙适配 | `STATUS-BOARD.md` |
| **群反馈 #7 / #9** | 日历换开源方案 · webhook CI 安全收口 | TODO-BACKLOG §L |
| **★★ GM5.3 学术+功能整体评审** | **用户 2026-09-19 指定，条件触发**：所有功能完工 + **已发版本之后**，把插件整体送往 GM5.3 模型审一遍。**功能面**=可用性与高效性、有哪些功能漏掉；**学术面**=方法论严谨性、哪些设计不科学。缘起=用户读 **MemEye**（arXiv:2605.15128），其核心维度之一「大模型如何在记忆中**维护现有记忆**、**区分记忆的变化与修改**」被用户认定为**本项目薄弱项**。已定位三篇最相关文献（MemEye / **Supersede** arXiv:2606.27472 / **Reliable Post-Retrieval Assembly** arXiv:2606.01435）——详见 `ISSUE9-PURGE-AND-R1-PLAIN-20260919.md` §三。**附带收益：正好据此产出白皮书。** | 本清单 §5 + 该文档 §三 |

---

## 6. 需要用户做的动作

| # | 动作 | 说明 |
|---|---|---|
| ① | **重启宿主** | `lib/` 下 **12 个已修改 + 3 个未跟踪**（`index.js` / `l0-extract-pre.js` / `degrade-pre.js` / `note-status-{pre,apply-pre}.js` 等）**未提交**，不重启不生效（**agent 绝不碰 3080**） |
| ② | 拍板 ⑦ ⑧ **+ H-3** | 前端架构形态依赖 ⑦⑧；H-3（清洗器漏规则）是**有真机证据的功能缺陷**，建议一并裁定 |
| ~~③~~ | ~~清理 ⑨ 存量脏数据~~ | ✅ **已完成**（2026-09-19 20:15，用户重启后执行；8 条全部弃用，注入路径已干净 —— 见 §3.5） |

---

## 7. 每步固定收尾（不变）

```
备份 → 改码 → node --check → 写新套件 → 跑该套件(看红)
     → 变异演示(确认真红) → 还原(SHA256 逐字节) → 跑全量回归(0 失败) → 留痕
```

**当前全量回归基线**：`PASS 135 / FAIL 0 / TIMEOUT 0`（161.8s，`node tools\run-smoke.mjs`）
（曲线：… → ⑥接线 125 → **③ 信封结构判据 126** → **④ 教训候选 127** → PR 四修复同步 128
→ 本批 issue 修复 131 → **#72 132** → **#76 133** → **#74 134** → **#75 135**）

---

## 7.5 ★ ③ / ④ 落地要点（2026-09-19 R5 · 压缩后据此处恢复上下文）

### ③ 信封清洗器：从「枚举」到「形态侦测」

**改的文件**：`lib/intent-clean-safe-pre.js`（6445 B，备份 `*.bak-20260919-R5`）

**旧实现的缺陷**：`:17` 一条行首字面量白名单 `/^(?:current runtime context\.|current dsh file policy:)/i`
⇒ 只挡当期两个已知形态。真机 `~/.dsh/memory/hub-pre/procedures.json` 的 7 条 observed 里
**4 条 title 就是运行时信封**，实测漏网 4 类（含**被截断到 40 字符**的形态）。

**新判据（三条，全部不依赖具体措辞）**：
| 代号 | 判据 | 覆盖 |
|---|---|---|
| **F1** | 结构化转储：`{"` / `[{` / `["` 强开幕（**允许未闭合** —— 真机就是断的）+ 引号键值 | 工具回包 JSON |
| **F2a** | 声明头**词族** + 分隔符（`:：.。`）：`current/approval/sandbox/policy/runtime/session/permission/escalation/tools/files/memory` | 英文声明头 |
| **F2b** | 同词族 + **be 动词系表**（`is/are/was/were/has been/will be`） | **截断形态**（冒号都没了） |
| **F2c** | 中文声明头词族：`当前/批准/沙箱/策略/运行时/会话/权限/升级/工具/文件/记忆` | 中文信封 |
| **位置约束** | F2 只在「**尚未出现真人文本**」的信封区内生效；出现真人即关闭 | **防误伤正文**（`Policy: xxx` 不会被删） |

**真机实测结果**：4 类信封**全清空**（`Approval prompts…` 两种形态 / `{"path":…` / `中文当前运行时上下文`），
3 条真人原话**全保留**。

**验收**：`tests/smoke/smoke-test-r5-envelope-structural-pre.mjs` **36/36**；
`artifacts/_mutate-r5-envelope.mjs` **6/6 真红 + SHA256 逐字节还原**；
既有 `smoke-test-m84-intent-clean-pre.mjs` **37/37 未破**。

### ④ 教训 → 观察型候选：新增生产者

**改的文件**：`lib/memory-hub-pre.js`（备份 `*.bak-20260919-R5`）
**新增导出**：`lessonCandidateFromRetractedPre(row)`

**三条硬约束（缺一即与用户裁定冲突）**：
1. **必须 `observationOnly: true`** —— 「永不自动晋升」的**唯一结构保证**；
   `procedure-store.pre `promote()` 对它在 `:273` **短路**返回 `{decision:'keep', reasonCodes:['observation-only']}`。
2. **`sourceMemoryIds` 必须带被撤回条目的真实 `mem_<32hex>` id** —— 不得留空
   （注意：这与 `crossFeed()` 里 episode 通路的 `sourceMemoryIds: []` **有意留空不同**，
   那条是「episode 只提供线索、不得凭空造 provenance」；本通路的 id 是**真实存在**的）。
3. **title/reason 必须先过 `stripRuntimeIntentPre`** —— 否则信封再成教训标题（H-3 同款）。

**用户裁定原文**（2026-09-18 00:20）：
> 「**教训肯定得进 C 啊，它不自动晋升，但是可以形成候选，模型也可以通过搜索搜索到**。」

**验收**：`tests/smoke/smoke-test-r5-lesson-candidate-pre.mjs` **30/30**（含 L2 端到端「证据拉满 99/99 仍 keep」、
L3 `query()` 可检索到、L5 七类非法输入 fail-closed）；
`artifacts/_mutate-r5-lesson.mjs` **6/6 真红 + SHA256 还原**。

**★ 尚未接线**（刻意）：`lessonCandidateFromRetractedPre` 目前**零调用方**。
「谁在什么时机把 retracted 条目喂进来」属**调用策略**，需用户拍板（现状=纯函数就绪，可被随时接线）。

---

## 8. 推荐执行序

```
① issue #55–#58                                    ← ✅ 已完成
  ↓
② 落地 R4-A ＋ ⑥ G3 状态写入                        ← ✅ 已完成
  ↓
③ Hermes 遗留修复（信封形态侦测）                    ← ✅ 已完成
  ↓
④ 教训 → 观察型候选                                 ← ✅ 已完成（纯函数就绪，接线待拍板）
  ↓
⑨ 技能队列注入污染                                  ← ✅ 已完成（+ 存量脏数据已清）
  ↓
⑪ skill 导出层                                      ← ✅ 已完成
  ↓
⑩-a 前端「记忆中枢」面板可读性                       ← ✅ **已完成**（2026-09-19，T1-1/T1-2 fact 通路补清洗器）
  ↓
⑩-b 记忆文件侧隐患（M8 回写雷 + 排版/语义耦合）       ← ✅ **已完成**（T0/T1/T2 全批，回归 PASS 133）
  ↓
T3 系统性收口（20 条写入路径 × 3 消费方）             ← ★ **下一步**（用户裁定：单独立项，前端之前做）
  ↓
⑤ R4-C 层次保底配额                                 ← 已移到「前端之后」（用户裁定）
  ↓
⑦⑧ 用户拍板                                        ← 前端架构形态的最后输入
  ↓
前端重构开工（含 P0 收编 48 份未跟踪文档 + **§4 的 R1–R6 审批界面可读性** + **★R7 用户级硬性约束可视编辑**）
  ↓
★ GLM 全量审核 + 白皮书                             ← 见 §5
```

**★ R7 是前端开工当批必做项**（用户 2026-09-20 明确要求「这个功能必须要落在前端」）：
让用户能**自己增 / 删 / 改**「`[规则 — 用户级硬性约束 · 必须遵守]`」这一类条目。
它是**每轮无条件注入、不走语义层**的硬约束（真源 = `~/.dsh/memory/MEMORY.md`，
渲染见 `lib/rules-layer-pre.js:238`），所以过时条目不会被自动淘汰，
**手工可纠正 = 这个设计的必要配套**。完整要求见 **§4 R7 专段（R7-1 ~ R7-7）**。
⚠️ 本项**只加 UI 入口**：不改注入语义、不改 `rules-layer-pre.js`、不改 `memory_user_pre`，
且写入必须复用既有 `writeFull` 事务（备份 + 校验 + fail-soft 留痕）。

**★ ⑩-a + ⑩-b 已于 2026-09-19 一批完工**（**两者同源**：`memory-hub-pre.js` 的 fact 通路从来没有过清洗器）。
完整执行记录见 **`docs/internal/ISSUE10-FIX-EXECUTION-20260919.md`**。验收：新套件 **73/73** ·
变异演示 **17/17 真红** + SHA256 零残留 · 全量回归 **PASS 133 / FAIL 0 / TIMEOUT 0**。

**⚠️ 待用户动作**：重启宿主（`lib/intent-clean-safe-pre.js` · `lib/memory-hub-pre.js` · `lib/index.js` 已改未提交）。
**⚠️ 未做**：不碰 `MEMORY.md` 任何字节 · 不改 `hub-pre/*.json`（存量脏数据按用户拍板走**路 C**）。

**当前在途项 = T3**（用户拍板「前端之前做」）。T3 与本批的关系：
本批只在**三个已知入口**设防（`crossFeed` fact 分支 / `factCandidateFromRow` / `hubFlushTick`）；
T3 要解决的是「把 `sanitizeForWrite` 下沉到写原语（`appendText`/`writeFull`）⇒ **20 条写入路径自动受保护**」。
另：**前端 R2 需 host 侧改动**（`overview()` 补 `reasonCodes` 投影）⇒ 建议并入 T3 一并做。

---

## 9. 为什么 ⑩-b 这类「雷」不会是个例（用户 2026-09-19 判断，已采纳）

⑩-b 不是孤例，而是**一类系统性风险的一次样本**：

> 本插件的「记忆」不是普通文件，而是 **注入面 + 检索面 + 语义面共用的同一份字节**。
> 任何一处写入都要同时满足三套约束，而**历史实现是分批长出来的** ——
> 于是「一处漏清洗」「一处绕过事务」「一处改了字节却不动 sidecar」这类雷会**零散地留着**，
> 且因为**大多是静默降级**（fail-soft 吞异常），**不爆的时候完全看不出来**。

⇒ 这正是**前端做完之后必须做「GLM 全量审核 + 白皮书」**的原因，两层价值：
1. **逐条修只能清掉已知的雷**；**全量审核才能回答「还有多少雷、都分布在哪一类通路」** —— 这是 ⑩-b 给不出、只有全量视角能给的东西。
2. **白皮书把「三面共用一份字节」这条约束显式成文**，让后续任何写入者（含 AI 自己）不再靠口口相传 —— **约束不落纸，就一定会再被违反一次。**

**⇒ 本项目的「完工」因此有两层含义**：功能层面的完工（前端做完即达成），
与**认知层面的完工**（全量审核 + 白皮书 —— 把「我们到底建了什么、它有哪些已知边界」讲清楚）。
**后者才是用户说的「理清思路」**，也是这个插件从「能跑」走向「可信」的分界线。

---

## 10. ★★ 前端之后一起做完的遗留项（用户 2026-09-20 明确指定「放在作战记录的最后面」）

> **用户原话**：「#85 和 #87 … #84 的有界 NDJSON 落盘没做 … 这个放在作战记录的最后面。
> 和这个后面一起做完。」

**背景**：这三项在 2026-09-20 的 GitHub 队列清零中被**如实标注为「关闭 ≠ 已解决」**并关闭 ——
关单只为清队列，代码本身**没有动**。留到这里是为了「前端做完之后」一次性收口，
避免在前端冻结期引入后端行为变更。

### 10.1 · #84 残留：有界 NDJSON 落盘 + diag 行补会话/观测 ID

| 项 | 状态 | 说明 |
|---|---|---|
| `volatileEvents` 丢弃计数 | ✅ **已做** | `VOLATILE_MAX_PRE_V1` + `volatileDropped`，超限 shift 留痕 |
| `unhandledRejection` 计数 | ✅ **已做** | `{count, firstAt, lastAt, lastLine}` 挂 `process._dshAutoMemoryRejectionStat`，日志带 `guard #N` |
| **有界 NDJSON 落盘** | ⬜ **未做** | 事件环仍是**内存内 ≤16 条**，进程退出即销毁 —— 「崩溃即销毁现场」这一条**没解决** |
| **diag 行补会话/观测 ID** | ⬜ **未做** | opt-in stderr 行目前缺 sessionId / observationId，多会话并行时无法归因 |

**为什么留到最后**：这两项都要**新增落盘文件**（NDJSON 落盘路径 + 轮转/上限策略），
属于 IO 面变化。前端冻结期动 IO 面会污染回归基线，故推迟。
**做的时候注意**：上限必须有界（否则又是一个「无限增长」的雷）；轮转失败必须 fail-soft 且留痕。

### 10.2 · #85 Python 侧车依赖供应链（**需要先定版本策略**）

**问题**：`pip` 安装 `transformers` / `onnxruntime` 时**零版本约束**，且镜像回退**静默无留痕**。

**为什么不能「随手加 `==` `」**（这是它被推迟的真正原因）：
1. Python 侧车是**发烧友主动安装的进阶项**（见「语义引擎铁律」：JS 端=默认形态，Python 端=可选进阶）——
   锁死版本会让**现有已装用户**在下次安装/重装时直接失败；
2. `transformers` 3.x → 4.x 是**破坏性升级**（issue #85 附带评估），锁到哪一版需要**实测**嵌入质量，
   不是纯工程决策；
3. 镜像静默回退要补留痕（fail-soft 必须留痕，本仓纪律）。

**⇒ 需要用户拍板的点**：锁小版本区间（`>=4,<5`）还是**精确锁**？是否允许用户覆盖？
**建议**：区间锁 + 安装日志显式记录实际解析到的版本 + 覆盖开关。

### 10.3 · #87 可复现的 recall-regression harness（**功能请求**）

**现状**：`python/bench` 目录**存在**（已实测），但被**排除在 npm 包之外** ⇒ 用户装到插件后
**无法复现 recall 回归**。issue #87 原文（by JIE42393）：「Ship a reproducible recall-regression harness」。

**不是缺陷，是功能请求**。两种可选形态：
- **(a) 打包进 tarball**：体积变大，但用户开箱可复现；
- **(b) 独立仓库/独立入口**：主包不变，提供一个显式的 harness 文档与获取方式。

**⇒ 需要用户拍板**：选 (a) 还是 (b)。

### 10.4 · 执行纪律（三条共用）

1. **不合并进前端批次** —— 前端是 UI 重构，这三项是后端/打包面，混做会让「回归红了定位不到是哪条引起」；
2. **各自独立回归**，基线清晰（沿用现有做法：每批独立跑全量 + 变异）；
3. **原 issue 可重开** —— 关闭时已在回复中明确写「需要时回这里重开」，处理前先去原 issue 取上下文。

