@a9i5k4/dsh-auto-memory 3.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +19 -7
  2. package/README.zh-CN.md +19 -7
  3. package/docs/FRONTEND-CO-CREATION.md +191 -0
  4. package/docs/GM53-HOMEPAGE-PROMPT.md +323 -0
  5. package/docs/HOMEPAGE-CONTENT-FOR-GM53.md +299 -0
  6. package/docs/PROMO-PROMPT-3.0.md +100 -0
  7. package/docs/USER-GUIDE.en.md +2 -2
  8. package/docs/USER-GUIDE.zh-CN.md +2 -2
  9. package/docs/WHITEPAPER.md +207 -0
  10. package/docs/internal/ARCHITECTURE-FOR-ZCODE-20260920.md +397 -0
  11. package/docs/internal/ART-DIRECTION-DEEPSEEK-20260920.md +351 -0
  12. package/docs/internal/ART-DIRECTION-WIREFRAME.md +191 -181
  13. package/docs/internal/ART-DIRECTION-WIREFRAME.md.bak-superseded +181 -0
  14. package/docs/internal/BATTLE-PLAN-20260917.md +871 -0
  15. package/docs/internal/FEATURE-INVENTORY.md +531 -0
  16. package/docs/internal/G-SERIES-EXECUTION-20260917.md +248 -0
  17. package/docs/internal/G3-DESIGN-20260918.md +82 -0
  18. package/docs/internal/G3-DISK-FORMAT-GAP-20260919.md +92 -0
  19. package/docs/internal/HANDOFF-TO-ZCODE-20260920.md +309 -0
  20. package/docs/internal/HERMES-DATA-VERIFICATION-20260919.md +120 -0
  21. package/docs/internal/HERMES-LEGACY-STATUS-20260919.md +74 -0
  22. package/docs/internal/ISSUE-55-58-VERIFICATION-20260918.md +175 -0
  23. package/docs/internal/ISSUE10-FIX-EXECUTION-20260919.md +389 -0
  24. package/docs/internal/ISSUE10-PLAN-20260919.md +254 -0
  25. package/docs/internal/ISSUE10B-FORENSICS-20260919.md +468 -0
  26. package/docs/internal/ISSUE9-PURGE-AND-R1-PLAIN-20260919.md +150 -0
  27. package/docs/internal/ISSUE9-RESIDUAL-FORENSICS-20260919.md +114 -0
  28. package/docs/internal/LESSON-TO-CANDIDATE-STATUS-20260919.md +79 -0
  29. package/docs/internal/MEMORY-GOVERNANCE-20260917.md +309 -0
  30. package/docs/internal/PRE-FRONTEND-CHECKLIST-20260919.md +705 -0
  31. package/docs/internal/PRE-FRONTEND-CHECKLIST-20260919.md.bak-s10 +649 -0
  32. package/docs/internal/PROCEDURAL-MEMORY-AND-APPROVAL-DESIGN-20260918.md +225 -0
  33. package/docs/internal/PROGRESS-20260917.md +93 -0
  34. package/docs/internal/PROMPT-GAP-AUDIT-20260920.md +128 -0
  35. package/docs/internal/R1-DEGRADE-AUDIT-20260918.md +163 -0
  36. package/docs/internal/R1-READABILITY-FORENSICS-20260919.md +127 -0
  37. package/docs/internal/R2-EVIDENCE-DEEP-AUDIT-20260918.md +140 -0
  38. package/docs/internal/R3-DEGRADE-LEDGER-DESIGN-20260918.md +138 -0
  39. package/docs/internal/R4-RECALL-QUOTA-PLAN-20260918.md +218 -0
  40. package/docs/internal/RESUME-20260918.md +171 -0
  41. package/docs/internal/RESUME-20260919.md +104 -0
  42. package/docs/internal/RHINELAB-TO-DEEPSEEK-FEASIBILITY.md +198 -0
  43. package/docs/internal/ROADMAP-20260917-WEEK.md +134 -0
  44. package/docs/internal/S10-CONSTRUCTION-HANDOFF-20260917.md +13 -3
  45. package/docs/internal/S10-GAP-INVENTORY-20260917.md +239 -0
  46. package/docs/internal/T6-EXECUTION-20260920.md +130 -0
  47. package/docs/internal/TELEMETRY-EFFECT-REPORT-DESIGN-20260918.md +146 -0
  48. package/docs/internal/THESIS-GAP-ANALYSIS-20260918.md +89 -0
  49. package/docs/internal/THESIS-OUTLINE-20260918.md +147 -0
  50. package/docs/internal/THREE-LAYER-CONTRACT.md +10 -1
  51. package/docs/internal/UPSTREAM-ISSUE-PR-TRIAGE-20260919.md +297 -0
  52. package/docs/internal/UPSTREAM-ISSUES-3RD-AUDIT-20260920.md +104 -0
  53. package/docs/screenshots/promo/promo-0-banner-v3.png +0 -0
  54. package/lib/activation-host.js +63 -9
  55. package/lib/board-mode.js +1 -1
  56. package/lib/client.js +892 -27
  57. package/lib/config-io.js +156 -0
  58. package/lib/context-bridge.js +3 -0
  59. package/lib/context-host.js +16 -9
  60. package/lib/degrade.js +385 -0
  61. package/lib/dsh-home.js +143 -0
  62. package/lib/episodic-store.js +52 -2
  63. package/lib/evidence-store.js +8 -1
  64. package/lib/fact-store.js +21 -2
  65. package/lib/index-sync.js +13 -1
  66. package/lib/index.js +1507 -158
  67. package/lib/intent-clean-safe.js +258 -40
  68. package/lib/l0-extract.js +231 -16
  69. package/lib/m4-corpus.js +8 -2
  70. package/lib/m7-index-sync-host.js +8 -1
  71. package/lib/memory-envelope.js +6 -1
  72. package/lib/memory-hub.js +127 -12
  73. package/lib/memory-index.js +4 -2
  74. package/lib/note-status-apply.js +118 -0
  75. package/lib/note-status.js +196 -0
  76. package/lib/procedure-store.js +84 -3
  77. package/lib/python-sidecar-client.js +29 -3
  78. package/lib/recall-fusion.js +83 -12
  79. package/lib/rules-edit.js +159 -0
  80. package/lib/semantic-decide.js +41 -8
  81. package/lib/semantic-js.js +51 -6
  82. package/lib/shadow-host.js +3 -5
  83. package/lib/skill-export-host.js +153 -0
  84. package/lib/skill-export.js +239 -0
  85. package/lib/storage-manage.js +6 -0
  86. package/lib/temporal-parse.js +191 -159
  87. package/lib/tier0-catalog.js +45 -3
  88. package/lib/wb-contract.js +198 -2
  89. package/lib/wb-sidecar.js +54 -3
  90. package/package.json +1 -1
@@ -0,0 +1,468 @@
1
+ # ⑩-b 记忆文件侧隐患 · 取证报告(2026-09-19)
2
+
3
+ > **两份只读子代理普查 + 主代理逐条复核的合并成果。**
4
+ > 子代理 A = M8 回写通路(`480e1420`)· 子代理 B = `MEMORY.md` 全部写入者普查(`88b6427e`)。
5
+ > **主代理对每条关键结论做了独立复核**,并纠正了子代理报告中的**三处错误**(见 §5)。
6
+ > 本报告不含修复建议 —— 修法由用户拍板后另立文档。
7
+
8
+ ---
9
+
10
+ ## 0. 一句话结论
11
+
12
+ > **`MEMORY.md` 这一份纯文本字节,被 20 条写入通路、3 条消费管线共用;而它们之间没有任何一层做联合把关。**
13
+ > 其中 **12 条写入通路「清洗门 × 事务校验」双零**,**3 条还是全自动无人值守**。
14
+ > 最危险的那条(M8 事实回写)**已经实际把脏数据写进过正文**——物证就在归档文件里。
15
+
16
+ ---
17
+
18
+ ## 1. 背景:三条消费管线(全部已代码核实)
19
+
20
+ `MEMORY.md` 不是普通文档,它同时喂三条**互不知情**的管线:
21
+
22
+ | # | 管线 | 读什么 | 依据 |
23
+ |---|---|---|---|
24
+ | ① | **注入上下文** | 整篇文本 → `systemPrompt` | `lib/index.js:5498-5499` |
25
+ | ② | **L0 摘要索引** | 按 `<!-- memory:mem_<32hex> -->` 锚点**切条** | `lib/l0-extract-pre.js:34` · `:322-343` |
26
+ | ③ | **语义语料** | 按锚点划**字节区间**算 `recordDigest` | `lib/memory-anchor-pre.js:258` · `lib/m4-corpus-pre.js:96/107` |
27
+
28
+ **三者的共同前提 = 文件字节结构与锚点标记的完整性。**
29
+ 但 §2 会看到:**没有任何一条写入通路为这三条管线做联合把关。**
30
+
31
+ **语料侧的两级静默丢弃**(③):
32
+
33
+ ```js
34
+ // lib/m4-corpus-pre.js:95-96 文件级先拦 —— 跳过整个文件
35
+ const fileDigest = sha256Hex(buf)
36
+ if (fileDigest !== sc.fileDigest) { dropped.push({reason:'stale-source'}); continue }
37
+
38
+ // lib/m4-corpus-pre.js:107 记录级再拦 —— 单条丢弃
39
+ if (sha256Hex(body.subarray(r.byteStart, r.byteEnd)) !== r.recordDigest) {
40
+ dropped.push({ reason: 'record-stale', memoryId: r.memoryId }); continue }
41
+ ```
42
+
43
+ **留痕判定(关键)**:`dropped[]` **技术上有数组**,但 **无日志、无 `degrade` 台账、不抛错**,
44
+ 只被 `storage-manage-pre.js:81-96 scanHealth` 归类后**在 GUI 存储页展示**。
45
+ ⇒ **用户不主动打开存储页,就完全看不到**;而调用方拿到的是 **`res.ok === true`**。
46
+
47
+ ---
48
+
49
+ ## 2. 写入者全景(20 条,全部收敛到三个原语)
50
+
51
+ `p = await this.resolvePaths(agent)`;`notesPath = {projectDir}/MEMORY.md`、`userFile = {userDir}/MEMORY.md`。
52
+ 两者共用同一套 `appendText` / `writeFull`。
53
+
54
+ | # | 调用点 | 触发 | 方式 | 清洗门 |
55
+ |---|---|---|---|---|
56
+ | 1 | `index.js:9773` | `memory_note_pre`(append) | 追加 | ✅ `sanitizeForWrite` |
57
+ | 2 | `index.js:9771` | `memory_note_pre`(replace) | 整体替换 | ✅ |
58
+ | 3 | `index.js:9811` | `memory_user_pre`(append) | 追加 | ✅ |
59
+ | 4 | `index.js:9809` | `memory_user_pre`(replace) | 整体替换 | ✅ |
60
+ | 5 | `index.js:7519` | **自动沉淀 note(每轮)** | 追加 | ❌ |
61
+ | 6 | `index.js:7529` | **自动沉淀 user(每轮)** | 追加 | ❌ |
62
+ | 7 | `index.js:7614` | `memory_consolidate_pre` | 追加 | ❌ |
63
+ | 8 | `index.js:7622` | 同上 | 追加 | ❌ |
64
+ | 9 | `index.js:7719` | `memory_maintain_pre` 蒸馏成功 | 追加 | ❌ |
65
+ | 10 | `index.js:7730` | **蒸馏失败保底(内联归档日志全文)** | 追加 | ❌ |
66
+ | 11 | `index.js:8141` | GUI 接入外部记忆 → user | 追加 | ❌ |
67
+ | 12 | `index.js:8146` | 同上 → project | 追加 | ❌ |
68
+ | 13 | `index.js:8183` | GUI 移除已接入内容 | 整体替换 | ❌ |
69
+ | 14 | **`index.js:8982`** | **`hubFlushTick`(30 分钟定时器)** | 追加 | ❌ |
70
+ | 15 | `index.js:5844` | `applyNoteStatusPre`(supersede/restore) | 整体替换 | ❌ |
71
+ | 16 | `index.js:4861` | `ensureBudget` → `compactLegacyLayer` | 整体替换 | ❌ |
72
+ | 17 | `index.js:4991` | `compactAnchoredLayer`(仅 anchor 模式) | 整体替换(**真事务**) | ❌ |
73
+ | 18 | `index.js:10773` | HTTP `API.note` | 追加 | ✅ |
74
+ | 19 | `storage-manage-pre.js:154` | GUI「修复 stale」(只重建 sidecar) | 不动正文 | — |
75
+ | 20 | `storage-manage-pre.js:202` | GUI「删除记忆」 | 整体替换(**真事务**) | — |
76
+
77
+ **旁证**:`index.js:7713` 写归档目录 · `index.js:4964` `writeFullRaw` 写 `notes-archived.md`
78
+ (`:4908` 注释明写「writeFullRaw 绕开 anchor 事务,归档不参与解析/索引」)。
79
+
80
+ ### 2.1 清洗门只有 5 处接线
81
+
82
+ `sanitizeForWrite`(`index.js:8313-8337`,含 mojibake / stutter / raw-json / base64 / 重复行 / 空 六类判据)
83
+ **只接了 5 处**:`:9706`(log)· `:9736`(handoff/plan)· `:9759`(note)· `:9797`(user)· `:10768`(HTTP note)。
84
+
85
+ **其余 15 条直接写。** 其中「清洗 × 事务」双零者 **12 条**。
86
+
87
+ ### 2.2 ★ 全自动无人值守的只有 3 条(这才是要害)
88
+
89
+ | 通路 | 频率 | 为什么危险 |
90
+ |---|---|---|
91
+ | **`index.js:8982` `hubFlushTick`** | **30 分钟定时器** + 启动 90 秒 | 门 `memoryHubEnabled` **出厂 true**(`index.js:518`)⇒ 用户不关就一定跑 |
92
+ | `index.js:7519` 自动沉淀 note | **每轮对话结束** | 正文来自 subagent 标签解析(`:7490-7506`) |
93
+ | `index.js:7529` 自动沉淀 user | **每轮对话结束** | 同上 |
94
+
95
+ ---
96
+
97
+ ## 3. 三个致命的结构性问题
98
+
99
+ ### 3.1 ★ `stripAnchorLines` 的「一处收口」承诺**不成立**
100
+
101
+ ```js
102
+ // lib/index.js:5746-5754
103
+ const store = this.docStore // ← anchor 关闭时 = null
104
+ if (store) {
105
+ // issue #54 配套(一处收口,覆盖全部调用方):**整行合法 anchor marker** 只应由写入原语自己生成。
106
+ // …,那行 marker 会变成本文档的结构锚点 ⇒ 幻影记录(身份属于旧文档、却挂在本文档上)。
107
+ const safeText = stripAnchorLines(...) // ← 只有 store 存在才执行
108
+ const r = await store.append(p, safeText)
109
+ ...
110
+ }
111
+ // 关闭分支直接到这里 —— stripAnchorLines 从未执行
112
+ const existing = await this.readTextSafe(p)
113
+ await writeFile(p, body, 'utf8') // 裸写
114
+ ```
115
+
116
+ ⇒ **注释声称「覆盖全部调用方」,但它在 `if (store)` 内部。**
117
+ 对新装用户(`memoryAnchorEnabled` 出厂 false)**这句承诺完全失效** ——
118
+ `:7730` 这类「把另一文档原文当正文追加」的通路,会把源文件的 marker 行**原样**带进 `MEMORY.md`,成为**幻影锚点**。
119
+
120
+ ### 3.2 ★ `reuse` / `occ` 的「保住旧 memoryId」机制是**死代码**
121
+
122
+ **主代理实测**:全仓 `planMigration` 只命中 **2 次**(`memory-anchor-pre.js:364` / `memory-anchor.js:327`),
123
+ **两处都是定义,`index.js` 零引用**;`applyPlan`(`memory-writer-pre.js:545`)同样零调用。
124
+
125
+ 机制本身(若被接线)是对的:
126
+ ```js
127
+ // lib/memory-anchor-pre.js:384-398 复用键 = 记录内容 digest + '#' + 出现序号
128
+ // 复用三条件全满足才复用:①调用方传 existingPlan ②sourceFile 相同 ③expectedFileDigest 整份字节一致
129
+ ```
130
+ 但**当前任何真实写入都走不到它** ⇒ 新 id 只能来自 `newMemoryId()`(`memory-anchor-pre.js:40`,纯随机 UUID)。
131
+
132
+ ⇒ **「就地改文件会换一批新 id」不但是真的,而且没有任何机制能保住旧 id。**
133
+
134
+ ### 3.3 ★ `sanitizeReservedSyntax` 会把回填的 marker **去功能化**
135
+
136
+ ```js
137
+ // lib/index.js:8343
138
+ return String(text||'').replace(/<!-- memory:/g, '<!--memory:')
139
+ ```
140
+ ⇒ 任何经 `writeFull` 回填的 marker 都会变成**失效形式**,
141
+ 于是 `renderReplace`(`memory-writer-pre.js:168-183`)把全文当 `legacy` 块 ⇒ **该文件全部条目重新分配 id**。
142
+
143
+ **这三条合起来说明**:**就地重排/整篇替换 `MEMORY.md` 永远是错的** ——
144
+ 它必然导致 id 全换、证据链(`seen/read/cite`、`supersededBy`、白板锚点)全断。
145
+
146
+ ## 4. M8 回写通路(`hubFlushTick`)—— 已引爆的那条
147
+
148
+ ### 4.1 它是干什么的
149
+ M8 记忆中枢的**治理式回写出口**:把 hub 里已固化的 fact(confirmed / 未撤销 / 未过期 / 置信≥0.6)
150
+ 渲染成 `## <主语>(M8 固化)` 段落,`appendText` 追加进 `MEMORY.md`,使其进注入面与语义语料。
151
+ **设计意图原文**(`index.js:8885-8886`):
152
+ ```
153
+ // 写回:P1④ 主闭环——hub.facts 中 confirmed/未过期/未撤销/置信≥0.6 的事实,每日限额
154
+ // 治理式写入 notesPath/userFile(autoConsolidate 同款 appendText 原子事务)→ 进 M7 语料。
155
+ ```
156
+
157
+ ### 4.2 怎么干的
158
+ | 环节 | 行号 |
159
+ |---|---|
160
+ | feed 60s / flush 30min / boot 90s | `:8992` / `:8993` / `:8996` |
161
+ | unref(不阻止进程退出) | `:8995` / `:8997` |
162
+ | disposer(**只 clear 两个 interval,漏了 boot**) | `:8999` |
163
+ | 开关门控(**关掉不写**) | `:8958` |
164
+ | 日期翻转清零 count | `:8962` |
165
+ | 每日上限 8 | `:8963-8964` |
166
+ | 取数(**无参**) | `:8965` |
167
+ | **写入格式** | `:8981` |
168
+ | 写入 | `:8982` |
169
+ | 失败 | `:8987` `catch (_) {}` |
170
+ | 状态落盘(**在 try 外,无条件**) | `:8989` |
171
+
172
+ ### 4.3 ★ 已引爆:物证(主代理亲自复核)
173
+
174
+ | 证据 | 内容 |
175
+ |---|---|
176
+ | `archive/notes-archived.md:493-495` | `## DSH ������(M8 固化)` / `- has three modes:shadow ֻ��¼ …` / `- 来源:记忆中枢治理固化(confidence=0.92)` |
177
+ | 同文件 `:519-521` / `:684-686` / `:3168-3170` | 另 3 段,**均带 `(M8 固化)`** |
178
+ | `MEMORY.md.bak-20260917-G2` | 正文**曾有** `## Approval prompts are disabled(M8 固化)` ⇒ 证明当时确实在正文 |
179
+ | 格式唯一性 | `(confidence=` 全仓只在 `:8981` 出现 ⇒ 只能由本通路产生 |
180
+ | 锚点 | 每段带 `<!-- memory:mem_<32hex> -->` ⇒ 走 `appendText` 正经事务 |
181
+
182
+ **`count: 0` 的正确解释**:`index.js:8962` 按**记忆日**清零(日界 450 分钟)
183
+ ⇒ `date:2026-09-19 / count:0` 只表示「**今天还没写**」,**不是**「历史没写」。
184
+
185
+ ### 4.4 六道过滤 —— 全是结构性,没有一道内容卫生
186
+ | # | 条件 | 行号 | 挡不住什么 |
187
+ |---|---|---|---|
188
+ | ① | `!fact.revoked` | `:8970` | 从未被撤销的脏事实 |
189
+ | ② | 未被 `flushed` 标记 | `:8970` | (标记本身有问题,见 4.5) |
190
+ | ③ | `ttl` 未过期 | `:8971` | **`ttl:0` = 永不过期**(`fact-store-pre.js:60`)⇒ 全放行 |
191
+ | ④ | `confidence < 0.6` 拒绝 | `:8972` | **`confidence: null` 直接放行**(`typeof null !== 'number'`)⇒ 实测 3 条 null 全过 |
192
+ | ⑤ | `subj` 非空且不以 `mem_` 开头 | `:8973-8974` | **U+FFFD / 运行时信封 / 超长 / 含换行** |
193
+ | ⑥ | `!cur.includes(subj)` | `:8980` | 子串误命中 |
194
+
195
+ ### 4.5 ★ 五个失败模式(要害)
196
+ 1. **脏 subject/object 原样落地** —— `:8981` 对 `predicate`/`object` **连 `.trim()` 都没有**;
197
+ **换行未归一化** ⇒ 一条 fact 可**伪造 `## ` 标题**,并被 `compactLegacyLayer`(`:4796` `^##\s+(.+)$`)当独立段落搬运。
198
+ 2. **失败静默** —— `catch (_) {}`(`:8987`)吞异常,而 `hubFlushSave()`(`:8989`)**无条件执行** ⇒ 失败照写 flush-state。
199
+ **无 diag、无 degrade 台账**(对照 `:5853` note-status 路径**有** `_degradePre.record`)。
200
+ 3. **`flushed` 与 `count` 不自洽** —— `:8980` 的「已在正文」分支**只置标记、不 `count++`**;
201
+ 且判定用 **子串**(短主语易误命中);且该标记**永久生效**(`:8962` 只清 count 不清 flushed)。
202
+ 4. **`flushed` 永不清理** ⇒ 实际语义是「**生命周期总量 8 条**」而非「每日 8 条」;且**归档后永不重写**(单向)。
203
+ 5. **`hubFlushSave` 写失败静默吞**(`:8895`)⇒ 重启后 `flushed` 回退 ⇒ **已写过的会被再写一遍**(正文出现两个同名段落)。
204
+
205
+ ### 4.6 门控与并发
206
+ - **开关门控彻底**(关掉不写),**但定时器无条件创建**、关掉后仍每 30min 空转;
207
+ **`hubBootTimer` 未纳入 disposer**(`:8999`)。
208
+ - **无重入保护**(`:8993` `void` 无 in-flight 标志);
209
+ `MEMORY.md` 不撕裂靠 docStore `_queue`(`memory-writer-pre.js:345-352/506`),
210
+ 但 **`flush-state.json` 是裸 `writeFileSync`,无锁、无 tmp+rename**(`:8895`)。
211
+
212
+ ### 4.7 ★ 可观察性:失败完全不可见
213
+ - 成功有 `diag`(`:8986`)→ `~/.dsh/dsh-auto-memory-pre-diagnose.log`
214
+ - **失败零留痕**
215
+ - `memory-hub-pre.js:192-220 overview()` **不含 flush 字段** ⇒ **前端面板看不到「写了但没成功」**
216
+
217
+ ---
218
+
219
+ ## 5. ★ 主代理对子代理报告的三处纠正
220
+
221
+ > **纪律**:子代理结论不得直接采信,须主代理独立复核。
222
+
223
+ ### 纠正 1:B 报告「默认配置下一条都不走事务」—— **对用户本机不成立**
224
+
225
+ B 的推理链是对的(`memoryAnchorEnabled` 出厂 `false` ⇒ `docStore` getter `return null` ⇒ 裸写),
226
+ 但**用户的实际配置不是默认值**:
227
+
228
+ | 项 | 出厂默认 | **用户实际** | 依据 |
229
+ |---|---|---|---|
230
+ | `memoryAnchorEnabled` | `false`(`index.js:307`) | **`true`** | `~/.dsh/dsh-auto-memory-pre.json` |
231
+ | `memoryHubEnabled` | `true`(`index.js:518`) | `true` | 同上 |
232
+ | `~/.dsh/memory/index-pre/files` | 关时不创建 | **存在,102 个 sidecar** | 只在该开关 true 时创建(`:5737`) |
233
+
234
+ ⇒ **用户本机一直开着 anchor 事务层**;B 的「默认裸写」结论只对新装用户成立。
235
+ **但这不改变 B 的核心价值** —— §3.1(`stripAnchorLines` 承诺不成立)与 §2.2(3 条全自动零防线)依然成立,
236
+ 且对**新用户**(出厂配置)风险更高。
237
+
238
+ ### 纠正 2:`reuse`/`occ` 是死代码 —— **B 对,且比它说的更绝对**
239
+ 主代理实测:`planMigration` 全仓 **2 次命中,都是定义**,`index.js` 零引用。
240
+ ⇒ **没有任何机制能保住旧 `memoryId`**。
241
+
242
+ ### 纠正 3:`sanitizeReservedSyntax` 的去功能化 —— **B 对,且它强化了「就地改文件必然错」**
243
+ `:8343` 会把回填的 `<!-- memory:` 改成 `<!--memory:`(失效形式)
244
+ ⇒ 整篇替换必然让全文变 `legacy` 块 ⇒ **全部条目重新分配 id**。
245
+
246
+ ---
247
+
248
+ ## 6. 综合判定:风险排序
249
+
250
+ | 排序 | 通路 | 理由 |
251
+ |---|---|---|
252
+ | **★★★** | **`index.js:8982` `hubFlushTick`** | ①全自动高频(30min,门出厂 true)②清洗×校验双空 ③**脏源结构性**(主语取自记忆文件字节 `:8943`←`m4-corpus-pre.js:118` ⇒ **脏数据自我放大**)④失败静默 ⑤**已实际引爆(§4.3)** |
253
+ | ★★ | `index.js:7519` / `:7529` 自动沉淀 | 每轮对话结束触发,无清洗门 |
254
+ | ★ | `index.js:7730` 蒸馏保底 | 内联**归档日志全文**,无清洗门;但受定时+AI失败双条件限制 |
255
+
256
+ ### ★ 最关键的一条:**「自我放大回路」**
257
+ ```
258
+ 记忆文件字节 → m4-corpus-pre.js:118 取 text → index.js:8943 作 fact.subject
259
+ ↓
260
+ hubFlushTick:8982 写回 MEMORY.md
261
+ ↓
262
+ (若脏)再次成为语料来源 → 再固化 → 再写回
263
+ ```
264
+ ⇒ **脏数据不只是「落盘」,它会自己繁殖。**
265
+
266
+ ---
267
+
268
+ ## 7. 取证边界
269
+ - 子代理 A 未读 `~/.dsh/dsh-auto-memory-pre-diagnose.log` ⇒ 「哪一次 compaction 归档的」为**推断**。
270
+ - 本报告全部结论均附 `文件:行号`;主代理已复核 §3.1 / §3.2 / §3.3 / §4.3 / §5 纠正 1。
271
+ - **两份子代理报告全程只读**,未修改/创建/删除任何文件,未派生下级代理。
272
+
273
+ ---
274
+
275
+ ## 8. ★ 修复方案(待用户拍板)
276
+
277
+ ### 8.0 先判紧急度(诚实评估,不夸大)
278
+
279
+ **急性事故已经发生完毕**——4 条脏 fact 已写入、已被归档,**没有正在持续流血**。
280
+ **但慢性暴露仍然存在**,理由是:
281
+ - `count` 每日清零(`:8962`)、`flushed` 永不清零 ⇒ 实际语义是「**每天最多 8 条新 fact,每条一生只写一次**」
282
+ - ⇒ **每个记忆日仍有最多 8 次脏写入机会**
283
+ - 且脏源**是持续产生的**:`hubFeedTick`(60s)从 `judgement-shadow.jsonl` 取数,
284
+ 主语来自 `rec.heading || rec.text`(`:8943`),而 `rec.text` 来自**记忆文件字节**(`m4-corpus-pre.js:118`)
285
+
286
+ ⇒ **判定:中高优先,不是「立刻停机」级别,但应在进入前端前修完**(与用户既定节奏一致)。
287
+
288
+ ### 8.1 关键洞察:⑩-a 的修法**就是** ⑩-b 的主要止血手段
289
+
290
+ **两条问题同源**:
291
+ ```
292
+ episode.intent(可能脏)
293
+ ├─→ crossFeed procedure 分支 ✅ 已清洗(⑨ 修的)
294
+ └─→ crossFeed fact 分支 ❌ 未清洗 ──→ facts.json 变脏
295
+ ├─→ ⑩-a 前端面板看不懂
296
+ └─→ hubFlushTick 写进 MEMORY.md ──→ ⑩-b 三管线污染
297
+ ```
298
+ ⇒ **给 fact 通路补上清洗器,同时解决 ⑩-a 与 ⑩-b 的脏源**。这是最高性价比的一刀。
299
+
300
+ ### 8.2 分三层(T0 止血 / T1 根因 / T2 加固)
301
+
302
+ #### T0 · 立即止血(不改 host 代码)
303
+ | 动作 | 说明 |
304
+ |---|---|
305
+ | **T0-1** | 备份 `facts.json` + `flush-state.json`(**只备份,不动**) |
306
+ | **T0-2** | 记录当前脏条目 id,作为 T1 完成后的验收基线 |
307
+ | **T0-3** | **不关 `memoryHubEnabled`** —— 那会连 procedure 晋升一起停,代价远大于收益 |
308
+
309
+ > **为什么不直接清 `facts.json`**:`fact-store-pre.js` **没有按 id 删除的方法**(导出全集见 §中),
310
+ > 唯一的 `revokeBySource` 需先删来源记忆/episode(副作用过大);
311
+ > 且宿主内存持副本、`dispose()` 会回写 ⇒ **直接改盘会被回滚**(⑨ 已踩过)。
312
+
313
+ #### T1 · 根因修复(改 `lib/`,改动小、收益最高)
314
+ | # | 改动 | 位置 | 效果 |
315
+ |---|---|---|---|
316
+ | **T1-1** | `crossFeed` **fact 分支**补 `stripRuntimeIntentPre`(清洗 `subject`/`object`) | `memory-hub-pre.js:166-174` | **断源**(⑩-a 根因) |
317
+ | **T1-2** | `factCandidateFromRow()` 补清洗 | `memory-hub-pre.js:249-265` | 断源(第二条 fact 入口) |
318
+ | **T1-3** | `hubFlushTick` **写入前加内容卫生门**:`subject`/`object` 过清洗器;脏则 **skip + 留痕** | `index.js:8975-8987` | **即使脏 fact 已存在也写不出去**(⑩-b 止血) |
319
+ | **T1-4** | 换行归一化:`subject`/`object`/`predicate` 去 `\r\n` | `index.js:8981` | 堵「伪造 `## ` 标题」 |
320
+ | **T1-5** | 失败留痕:`catch` 内补 `diag` + `_degradePre.record`(对照 `:5853` 既有做法) | `index.js:8987` | 「写了但没成功」可见 |
321
+
322
+ #### T2 · 结构加固(改 `lib/`,中等)
323
+ | # | 改动 | 位置 |
324
+ |---|---|---|
325
+ | **T2-1** | `flushed` 与 `count` 自洽:`cur.includes(subj)` 分支也 `count++`;并把「子串判定」改为更严的判据 | `index.js:8980` |
326
+ | **T2-2** | `hubBootTimer` 纳入 disposer(`:8999` 补 `clearTimeout`) | `index.js:8999` |
327
+ | **T2-3** | `flush-state.json` 改原子写(tmp + rename,与 `hubIo` 同款) | `index.js:8895` |
328
+ | **T2-4** | 加 in-flight 标志防重入 | `index.js:8993` |
329
+
330
+ #### T3 · 系统性收口(**最大收益,也最大改动** —— 建议单独立项)
331
+ | # | 改动 | 说明 |
332
+ |---|---|---|
333
+ | **T3-1** | **把 `sanitizeForWrite` 从 5 个调用点下沉到写入原语层**(`appendText` / `writeFull` 内部) | 一处收口 ⇒ **20 条通路全部受保护**,不再依赖调用方自觉 |
334
+ | **T3-2** | `m4-corpus-pre.js` 的 `dropped[]` 补 `degrade` 台账 | 让「静默丢出语义语料」变得可见 |
335
+ | **T3-3** | `overview()` 暴露 flush 状态(`count`/`lastFlush`/失败数) | 前端面板可诊断 |
336
+
337
+ > **T3-1 是「治本」**:本报告的核心结论是「20 条通路没有统一契约」,
338
+ > 而 T1/T2 都是**逐条补**。只有把卫生门下沉到原语层,才能让**将来新增的写入者**自动受保护。
339
+ > **但它的改动面最大、回归风险最高,建议单独一批、单独验收。**
340
+
341
+ ### 8.3 建议执行顺序
342
+ ```
343
+ T0(备份,10 分钟)
344
+ ↓
345
+ T1-1 / T1-2(断源,最优先 —— 同时修 ⑩-a)
346
+ ↓
347
+ T1-3 / T1-4 / T1-5(hubFlushTick 加固)
348
+ ↓
349
+ 新套件 + 变异演示 + 全量回归
350
+ ↓
351
+ T2(结构加固,可与 T1 合并一批)
352
+ ↓
353
+ T3(系统性收口)—— 建议单独立项,或并入前端后的重构
354
+ ```
355
+
356
+ ### 8.4 验收标准(可自动化)
357
+ 1. `facts.json` 中 U+FFFD 计数 = 0
358
+ 2. `facts.json` 中命中 `/Current DSH file policy|Approval prompts are disabled|Current runtime context|Retrieved memory ref/i` = 0
359
+ 3. 新套件:`crossFeed` 走 fact 时脏 intent **不落库**
360
+ 4. 新套件:`hubFlushTick` 遇到脏 fact **不写入 + 留痕**
361
+ 5. 变异演示真红(去掉任一新加的清洗调用 ⇒ 断言变红)
362
+ 6. 全量回归 **0 失败**
363
+
364
+ ### 8.5 风险与回退
365
+ - **T1/T2 风险:低-中**。均为**前置检查/留痕**性质,不改变既有成功路径的行为。
366
+ - **回退**:每项改动独立可回退;改前备份(本仓既定纪律)。
367
+ - **不碰**:不改 `MEMORY.md` 任何字节、不改锚点、不动事务层语义。
368
+
369
+ ## 9. ★★ 用户拍板(2026-09-19,**已定案,压缩后照此执行**)
370
+
371
+ | # | 议题 | **用户裁定** |
372
+ |---|---|---|
373
+ | 1 | **开工时点** | **先压缩上下文,再开工** |
374
+ | 2 | **存量 3 条脏 fact** | **路 C —— 靠卫生门堵住**(不改数据) |
375
+ | 3 | **T3 系统性收口** | **单独立项,前端之前做** |
376
+
377
+ ### 9.1 由此确定的执行序(压缩后按此开工)
378
+
379
+ ```
380
+ 【第 0 步】等用户压缩上下文
381
+ ↓
382
+ 【第 1 步】T0 备份(10 分钟,不改代码)
383
+ · 备份 ~/.dsh/memory/hub-pre/facts.json
384
+ · 备份 ~/.dsh/memory/hub-pre/flush-state.json
385
+ · 记录当前脏条目 id 作为验收基线
386
+ · ⚠️ 只备份,绝不改盘(宿主内存持副本,dispose() 会回写)
387
+ ↓
388
+ 【第 2 步】T1-0 ★ ⑨ 漏网修复(**2026-09-19 22:40 追加,必须先做**)
389
+ · lib/intent-clean-safe-pre.js:73 PLUGIN_TAIL_MARKER_RE 改「前缀即判据」
390
+ 现状(过严):^Source:\s*mem_[0-9a-f]{32} ← 被 slice(0,40) 截断即漏
391
+ 改为(前缀族):^Source:\s*\S ← 去掉 mem_<32hex> 约束
392
+ 新增: ^Reference:\s*\S ← F3 目前完全没有这一条
393
+ · ⚠️ 必须同时守住误伤边界(⑨ 套件 [4] 组口径):`^` 行首锚定必须保留,
394
+ 且要求后面跟非空内容(`\S`),否则会吞掉正文里的普通 `Source:`
395
+ · 取证见 docs/internal/ISSUE9-RESIDUAL-FORENSICS-20260919.md
396
+ ↓
397
+ 【第 3 步】T1 根因修复(⑩-a 与 ⑩-b 同源,一批做完)
398
+ T1-1 lib/memory-hub-pre.js:166-174 crossFeed fact 分支补 stripRuntimeIntentPre
399
+ T1-2 lib/memory-hub-pre.js:249-265 factCandidateFromRow 补清洗
400
+ T1-3 lib/index.js:8975-8987 hubFlushTick 加内容卫生门(脏则 skip + 留痕)
401
+ T1-4 lib/index.js:8981 换行归一化(防伪造 ## 标题)
402
+ T1-5 lib/index.js:8987 失败留痕(diag + _degradePre.record)
403
+ ↓
404
+ 【第 4 步】T2 结构加固(同批)
405
+ T2-1 index.js:8980 flushed 与 count 自洽(含改掉子串判定)
406
+ T2-2 index.js:8999 hubBootTimer 纳入 disposer
407
+ T2-3 index.js:8895 flush-state.json 改原子写(tmp + rename)
408
+ T2-4 index.js:8993 加 in-flight 标志防重入
409
+ ↓
410
+ 【第 5 步】收尾(本仓既定纪律,缺一不可)
411
+ 备份 → 改 → node --check → 新套件(先看它红)→ 变异演示(确认真红)
412
+ → 还原(SHA256 逐字节一致)→ 全量回归(0 失败)→ 记录
413
+ ↓
414
+ 【第 6 步】告知用户重启宿主(agent 绝不碰 3080)
415
+ ↓
416
+ 【第 7 步】T3 系统性收口 —— 单独立项,**在前端之前**做
417
+ 把 sanitizeForWrite 从 5 个调用点下沉到写入原语层
418
+ (appendText / writeFull 内部)⇒ 20 条通路全部自动受保护
419
+ + `overview()` 回传 `reasonCodes`(前端 R2 的前置依赖)
420
+ ```
421
+
422
+ > **★ 为什么 T1-0 必须放在最前**:⑨ 的漏网**正在持续产生新脏数据**(实测 22:40 已从 3 条涨到 5 条),
423
+ > 而 T1-1/T1-3 只挡 fact 那条路,**挡不住 procedure 这条**。不先修 F3,等于一边修一边漏。
424
+
425
+ ### 9.2 存量 3 条脏 fact 的处置(路 C,已定)
426
+
427
+ **不改数据。** 依赖 T1-3 的内容卫生门:
428
+ - 脏 fact 一旦命中清洗器判据 ⇒ `hubFlushTick` **直接 skip**,永远写不出去
429
+ - 面板乱码由 **T1-1/T1-2 的清洗器**从源头解决(新 fact 不再脏)
430
+ - **补充事实(降低紧迫性)**:`flushed` 4 条全为 `true` 且**永不清零**
431
+ ⇒ 这 4 条**事实上已经不会再被写入**,路 C 属于「补一道保险」而非「抢救」
432
+
433
+ ### 9.3 `hubFlushTick` 卫生门的具体判据(T1-3 实现要点)
434
+
435
+ 复用 `lib/intent-clean-safe-pre.js` 的 `stripRuntimeIntentPre()`——**与 ⑨ 同源**,一次覆盖两类脏:
436
+ - **F3**:召回块 / 运行时信封尾部标记族
437
+ - **F4**:`looksEncodingCorruptedPre`(U+FFFD ≥ 3)
438
+
439
+ **行为约定**:
440
+ ```
441
+ 对 fact.subject / fact.object 各自过 stripRuntimeIntentPre
442
+ ├─ 清洗后仍非空 且 与原文等价 → 正常写入(走原路径)
443
+ ├─ 清洗后为空 → skip + 留痕(记 degrade)
444
+ └─ 清洗后与原文不同(= 有脏) → skip + 留痕(**不静默改写后再写**,
445
+ 避免「悄悄改用户数据」的语义歧义)
446
+ ```
447
+ > **为什么「有脏就 skip」而不是「清洗后照写」**:本仓既有纪律是
448
+ > 「**fail-soft 必须留痕、不得静默改写**」。清洗后照写等于**静默修改数据**,
449
+ > 与 T1-1/T1-2 的「源头断供」在语义上重复,却在诊断上丢失了「曾经有过脏 fact」这一事实。
450
+
451
+ ### 9.4 验收标准(可自动化,压缩后直接照用)
452
+ 1. `facts.json` 中 U+FFFD 计数 = 0
453
+ 2. `facts.json` 中命中 `/Current DSH file policy|Approval prompts are disabled|Current runtime context|Retrieved memory ref/i` = 0
454
+ 3. 新套件:`crossFeed` 走 fact 时脏 intent **不落库**
455
+ 4. 新套件:`hubFlushTick` 遇脏 fact **不写入 + 有留痕**
456
+ 5. 变异演示真红(去掉任一新加的清洗调用 ⇒ 断言必须变红)
457
+ 6. 全量回归 **0 失败**
458
+
459
+ ### 9.5 铁律(压缩后务必遵守)
460
+ - **不碰 `MEMORY.md` 任何字节**:不改锚点、不改排版、不整篇替换
461
+ (依据 §3.1/§3.2/§3.3:就地改文件必然导致 id 全换、证据链全断)
462
+ - **不直接改 `hub-pre/*.json`**:宿主内存持副本,`dispose()` 会整份回写覆盖(⑨ 已踩过)
463
+ - **不关 `memoryHubEnabled`**:那会连 procedure 晋升一起停,代价远大于收益
464
+ - **agent 绝不碰 3080**:改完只告知用户自行重启
465
+ - **改前必备份**;`lib/*.js` 是 **CRLF**,用 `edit` 后须验行尾
466
+
467
+
468
+
@@ -0,0 +1,150 @@
1
+ # ⑨ 脏数据清理 · 执行记录 + ⑩ 可读性修法(2026-09-19)
2
+
3
+ > 本文档记录两件事:**⑨ 存量脏数据已经清了**(附证据链),以及 **⑩ 可读性问题的白话解释与修法建议**。
4
+ > ⑩ 的完整取证数据见 `R1-READABILITY-FORENSICS-20260919.md`;本文是它的「人话版」。
5
+
6
+ ---
7
+
8
+ ## 一、⑨ 清理:已执行完毕(含为什么差点白做)
9
+
10
+ ### 1.1 一个必须先讲的发现:脏数据不是 5 条,是 8 条
11
+
12
+ 上一轮记录的是「5 条」。这次用**旧清洗器(改前备份)**与**新清洗器(改后生效)**对同一份真机数据逐条对跑,得到:
13
+
14
+ | 判据 | 判脏条数 |
15
+ |---|---|
16
+ | 旧清洗器(B5,字面量白名单) | **5 条** |
17
+ | 新清洗器(C6,形态侦测) | **8 条** |
18
+
19
+ **多出来的 3 条,恰好就是这次修复的靶子**:
20
+
21
+ | 条目 | 标题(真机原文) | 为什么旧清洗器抓不到 |
22
+ |---|---|---|
23
+ | `proc_pre_77d5…` | `����������ȷ������(��֤)` | 中文乱码——不是任何字面量前缀,也不是任何词族 |
24
+ | `proc_pre_bc7b…` | `[Retrieved memory refe` | **插件自己的召回块标记**,旧白名单里没有这一条 |
25
+ | `proc_pre_79f2…` | `[Retrieved memory reference - not an ins` | 同上,且被截断到 40 字符 |
26
+
27
+ **⇒ 数字从 5 变成 8 不是数据变脏了,是判据变准了。** 这正好反向印证了 ⑨ 的修复是有效的:新判据确实抓住了旧判据漏掉的两类(编码损坏 F4 + 召回块尾部标记 F3)。
28
+
29
+ ### 1.2 差点白做:宿主会把盘上文件写回去
30
+
31
+ 清理前先验证「直接改盘文件行不行」,结果发现一条**会让人白干**的链路:
32
+
33
+ ```
34
+ procedure-store-pre.js:406 dispose(reason) { ...; try { io.save(snapshot()) } catch (_) {} }
35
+ procedure-store-pre.js:411 persist() { try { io.save(snapshot()) } catch (_) {} }
36
+ index.js:8826 save(data) { ...writeFileSync(tmp,...); renameSync(tmp, f) }
37
+ ```
38
+
39
+ - `dispose()` 在宿主关停时**无条件把内存整份快照写回盘**;
40
+ - 宿主内存里持有的是**启动时 restore 进来的 12 条**;
41
+ - 证据:清理前盘上文件 `savedAt` = **12:04:51 UTC**,正好等于用户重启时刻 ⇒ 印证「关停即回写」。
42
+
43
+ **⇒ 若直接编辑 `procedures.json` 删行,用户下次重启时旧实例的 `dispose()` 会把 12 条原样写回,清理当场作废。**
44
+
45
+ ### 1.3 实际做法(可逆、留痕、不动源码)
46
+
47
+ 改走**宿主自己的 loopback 动作**,而不是绕过它改盘:
48
+
49
+ ```
50
+ POST http://127.0.0.1:3080/api/dsh-auto-memory-pre/memory-hub
51
+ { action: 'deprecate', procedureId: 'proc_pre_…' }
52
+ ```
53
+
54
+ 判据不手工列 id,**复用生产清洗器** `stripRuntimeIntentPre()` 现场判定,保证「清掉的」与「新代码会拦的」完全一致。
55
+
56
+ ### 1.4 执行结果(实测)
57
+
58
+ | 项目 | 动作前 | 动作后 |
59
+ |---|---|---|
60
+ | 宿主内存 `procedures.size` | 12 | 12(**保留,未物理删**) |
61
+ | **审批面 `pipeline`**(会进注入路径) | **9** | **3** |
62
+ | 脏条目中处于 observed(可被注入) | 6 | **0** |
63
+ | 盘上文件 bytes | 9,540 | 9,930 |
64
+ | 盘上文件 mtime | 12:04:51Z | **12:15:29Z** |
65
+
66
+ **弃用 6 条,失败 0 条。** 现在 8 条脏数据**全部**处于 `deprecated`,而注入只取 `stage === 'active'`(`renderChecklists` → `activeProcedures()`)⇒ **注入路径已 100% 干净**。
67
+
68
+ 留下的 3 条 observed 全是真人文本:「让我自己去试吧。现在是什么情况?」「开始实施」「开始实施,实施完给我准确的报告保证我能看懂」。
69
+
70
+ **为什么选「弃用」而不是「物理删除」**:`deprecated` 是这套 store 的**终态而非删除**(设计如此,`applyAutomaticTransitions` 也只归档不删),且 `deprecate` 可逆、留 `deprecateReason`。物理删除需要新增 store 方法 = 动源码,风险与收益不成比例。
71
+
72
+ **备份**:`artifacts/procedures.json.bak-20260919-201223`(SHA256 `B51CFAC5…`,与清理前源文件逐字节相同)。
73
+
74
+ ---
75
+
76
+ ## 二、⑩ 可读性:用大白话说清楚是什么情况
77
+
78
+ ### 2.1 问题一句话
79
+
80
+ **你的记忆文件,一条条内容大多是对的,但它们是「写给已经知道背景的人看的」——打开一看,先是一大片空白,然后同一个日期重复出现十九次,中间夹着对人有零信息量的机器标记。**
81
+
82
+ ### 2.2 拆成三个真实症状
83
+
84
+ **症状 A:打开就是空白(最直观)**
85
+ 项目笔记文件**最前面整整 50 行是空的**。这不是排版习惯,是**容量整理留下的坑**——文件内容被 AI 折叠变短后,原来的行号槽位没回收,留成了一片空白。用户级那份前面有 3 行空白。
86
+
87
+ **症状 B:同一天被切成十几段**
88
+ 每写一条记忆,都会新起一个 `## 2026-09-18` 这样的日期小标题。结果:项目笔记里 `2026-09-18` 出现了 **19 次**,用户级里 `2026-08-19` 出现 5 次。翻起来就像**同一章被撕成了十九张纸**,每张纸上只写一条。
89
+
90
+ **症状 C:条目不说「这是什么」**
91
+ 比如这两条:
92
+ ```
93
+ M1 契约行渲染设计定案:不新建独立文件…
94
+ `lib/tier0-catalog-pre.js`:新增 TIER0_ANCHOR_ID_RE = /^mem_[0-9a-f]…
95
+ ```
96
+ 两条**都对**,但读者得先知道「M1 是什么」「这个文件在项目里干什么」才读得懂。**缺的不是信息,是入口**。这类条目在项目笔记里占 **15%**。另外还有 **30%** 属于「过短或没有完整句」,包括「无」「本轮无新增跨项目通用规则」这种**空转条目**——它们不承载信息却占版面。
97
+
98
+ ### 2.3 一个容易搞错的归因:到底是谁的锅
99
+
100
+ **主因在「写入/整理侧」(文件本身的问题)**:空行、重复日期标题、缺主语,都是**文件内容**的属性——文件打开就长这样,不是注入时变形的。
101
+
102
+ **但渲染侧另有一处独立的缺陷**:注入给模型的文本里,每条前面都带着一行 `<!-- memory:mem_cbd99fb3… -->`。这是给**机器**用的锚点 ID,**对人零信息量**,却每条占一行;再加上每条各带一个日期标题 ⇒ 双重稀释。这是**独立于写入侧的第二个病**。
103
+
104
+ **⇒ 所以修法不是一个,是两个方向各自独立的改法。**
105
+
106
+ ### 2.4 怎么改好一点(四选,agent 倾向 B+C 先行)
107
+
108
+ | # | 方案 | 改哪里 | 成本 | 效果 |
109
+ |---|---|---|---|---|
110
+ | **A** | **写入侧自解释模板**:强制新条目首句是完整句、说清「这是什么/在哪个项目用」 | 写入提示 + 整理器 | 低 | **只对新条目生效**,旧条目不变 |
111
+ | **B** | **渲染侧呈现**:隐藏锚点注释、同日合并标题、清理空行 | 注入渲染 | 低 | **立刻改善已存在的全部条目** |
112
+ | **C** | **整理侧清理**:压空行、合并同日段、删空转条目 | 整理器 | 中 | 一次性修好存量;**需防误删** |
113
+ | **D** | 检索侧逐条自动摘要 | 检索侧 | 高 | 效果好,但要调模型,**成本最高** |
114
+
115
+ **建议顺序:B + C 先做,A 随后。**
116
+
117
+ 理由:**B 和 C 不改变任何记忆的「内容」,只改「呈现与排版」**——风险最低、可随时回退,而且**立刻见效于你已经积累的全部条目**(不用等新条目慢慢变好)。A 属于**写入语义变更**(改变模型写记忆的行为),按本仓纪律必须你拍板,所以放后面。
118
+
119
+ **可以自动验收的四条标准**(探针已实现):
120
+ 1. 开头空行 = 0
121
+ 2. 空行占比 < 15%
122
+ 3. 同日重复 `## ` 标题 = 0(或渲染时合并)
123
+ 4. 空转条目(「无」「无新增」)占比 = 0
124
+
125
+ ---
126
+
127
+ ## 三、待办:GM5.3 模型学术+功能评审(已登记,**在所有功能完工并发布版本之后**再做)
128
+
129
+ **缘起**:用户 2026-09-19 提出——手头的 GM5.3 token 额度,正好用来给这个插件做一次**整体送审**。因为目前很多管线与思路**靠人脑管理**,不够严谨,可能有功能漏掉或不科学。
130
+
131
+ **评审范围(用户明确要求)**:
132
+ - **功能层面**:确保可用性与高效性——**哪些功能是漏掉的**
133
+ - **学术层面**:方法论严谨性——**哪些设计不科学**
134
+
135
+ **具体切入点(用户指定)**:以 **MemEye** 论文为缘起。该论文评估的核心之一是「大模型如何在记忆中**维护现有记忆**,并**区分记忆的变化与修改**」——**这正是本项目的薄弱项**,可纳入科学考量。
136
+
137
+ **已知最相关的三篇文献(本轮检索所得,供评审时取用)**:
138
+ | 论文 | 与本书的关系 |
139
+ |---|---|
140
+ | **MemEye**(arXiv:2605.15128) | 用户指定缘起;记忆评估框架,含「演化式综合」维度 |
141
+ | **Supersede**(arXiv:2606.27472) | **正面命中薄弱项**:专门诊断 LLM 的「记忆更新缺口」——事实变化后能否用新值、丢弃过期值。实测 frontier 模型把全上下文换成自维护记忆,准确率从 92% 掉到 77%(McNemar p<0.005);且**不是记忆太小的问题**(上下文增长 24 倍,准确率 68%→28%,给更多记忆也不恢复) |
142
+ | **Reliable Post-Retrieval Assembly**(arXiv:2606.01435) | 指出「检索后装配」是独立可靠性边界:把**证据抽取**与**策略执行**分开,比换更强的执行器贡献大得多(单跳 +10.8pp 平均、262K 时 +21pp) |
143
+
144
+ **执行前置条件(用户指定)**:① 所有功能完工 ② **已发版本**。然后再做学术性严谨提升。
145
+ **附带收益**:正好可以据此产出**白皮书**。
146
+
147
+ ---
148
+
149
+ **本轮产物**:`artifacts/_probe-9-clean.mjs`(脏数据识别)· `artifacts/_probe-9-delta.mjs`(新旧判据对照)· `artifacts/_probe-hub-memory.mjs`(宿主内存 vs 盘上)· `artifacts/_purge-9-step1-deprecate.mjs`(清理执行)
150
+ **备份**:`artifacts/procedures.json.bak-20260919-201223`