@a9i5k4/dsh-auto-memory 3.0.1 → 3.1.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 (134) hide show
  1. package/README.md +13 -8
  2. package/README.zh-CN.md +13 -8
  3. package/docs/HANDBOOK.md +92 -52
  4. package/docs/USER-GUIDE.en.md +9 -9
  5. package/docs/USER-GUIDE.zh-CN.md +9 -9
  6. package/docs/screenshots/promo/promo-0-banner-v4.png +0 -0
  7. package/docs/screenshots/promo/promo-1b-auto-recall.png +0 -0
  8. package/lib/activation-host.js +6 -1
  9. package/lib/client.js +1376 -255
  10. package/lib/config-io.js +59 -6
  11. package/lib/context-host.js +17 -2
  12. package/lib/episodic-store.js +90 -16
  13. package/lib/evidence-store.js +27 -1
  14. package/lib/fact-store.js +463 -41
  15. package/lib/hub-io.js +217 -0
  16. package/lib/index.js +904 -87
  17. package/lib/intent-clean-safe.js +1 -1
  18. package/lib/jsonl-tail-cursor.js +75 -0
  19. package/lib/m7-index-sync-host.js +4 -6
  20. package/lib/memory-hub.js +62 -7
  21. package/lib/migrate-pack.js +351 -0
  22. package/lib/note-status.js +9 -1
  23. package/lib/procedure-store.js +252 -31
  24. package/lib/procedure-switch.js +38 -0
  25. package/lib/python-setup.js +109 -27
  26. package/lib/python-sidecar-client.js +285 -8
  27. package/lib/recall-stats.js +242 -0
  28. package/lib/rules-layer.js +106 -15
  29. package/lib/semantic-js.js +25 -7
  30. package/lib/shadow-host.js +41 -2
  31. package/package.json +6 -2
  32. package/docs/internal/ACCEPT-35-LIVE.md +0 -143
  33. package/docs/internal/ACCEPTANCE-20260914.md +0 -90
  34. package/docs/internal/ARCH-REVIEW-BRIEF.md +0 -411
  35. package/docs/internal/ARCH-REVIEW-REQUEST.md +0 -201
  36. package/docs/internal/ARCH-REVIEW-ROUND2.md +0 -169
  37. package/docs/internal/ARCH-REVIEW-ROUND3.md +0 -206
  38. package/docs/internal/ARCHITECTURE-FOR-ZCODE-20260920.md +0 -397
  39. package/docs/internal/ART-DIRECTION-DEEPSEEK-20260920.md +0 -351
  40. package/docs/internal/ART-DIRECTION-WIREFRAME.md +0 -191
  41. package/docs/internal/ART-DIRECTION-WIREFRAME.md.bak-superseded +0 -181
  42. package/docs/internal/AUDIT-WB-GRAPH-FULL-20260916.md +0 -314
  43. package/docs/internal/BATTLE-PLAN-20260917.md +0 -871
  44. package/docs/internal/CONCURRENCY-INVESTIGATION-20260917.md +0 -192
  45. package/docs/internal/CROSS-SESSION-SEARCH-PATH-DECISION.md +0 -72
  46. package/docs/internal/CROSS-SESSION-SEARCH-RESEARCH.md +0 -131
  47. package/docs/internal/CUA-VISION-FIX-NOTES.md +0 -78
  48. package/docs/internal/DECISIONS-20260914-SESSION.md +0 -269
  49. package/docs/internal/DESIGN-OVERHAUL-PRE-RESEARCH.md +0 -292
  50. package/docs/internal/DESIGN-P1-STATE-COMMIT-20260915.md +0 -219
  51. package/docs/internal/DIRECTION-CHECK-WB-GRAPH-20260916.md +0 -132
  52. package/docs/internal/FEATURE-INVENTORY.md +0 -531
  53. package/docs/internal/FEEDBACK-TO-DSHAPI-RELAY.md +0 -13
  54. package/docs/internal/G-SERIES-EXECUTION-20260917.md +0 -248
  55. package/docs/internal/G3-DESIGN-20260918.md +0 -82
  56. package/docs/internal/G3-DISK-FORMAT-GAP-20260919.md +0 -92
  57. package/docs/internal/GH-DISCUSSION-5732-COMMENT.md +0 -74
  58. package/docs/internal/GPT-ACCEPTANCE-PROMPT-20260916.md +0 -352
  59. package/docs/internal/GPT-REVIEW-PROMPT.md +0 -216
  60. package/docs/internal/GROUP-DIGEST-SETUP.md +0 -62
  61. package/docs/internal/GROUP-LISTENER-SETUP.md +0 -49
  62. package/docs/internal/GROUP-WEBHOOK-SETUP.md +0 -93
  63. package/docs/internal/HANDOFF-TO-ZCODE-20260920.md +0 -309
  64. package/docs/internal/HANDOFF-TO-ZCODE.md +0 -168
  65. package/docs/internal/HERMES-DATA-VERIFICATION-20260919.md +0 -120
  66. package/docs/internal/HERMES-LEGACY-STATUS-20260919.md +0 -74
  67. package/docs/internal/ISSUE-55-58-VERIFICATION-20260918.md +0 -175
  68. package/docs/internal/ISSUE10-FIX-EXECUTION-20260919.md +0 -389
  69. package/docs/internal/ISSUE10-PLAN-20260919.md +0 -254
  70. package/docs/internal/ISSUE10B-FORENSICS-20260919.md +0 -468
  71. package/docs/internal/ISSUE9-PURGE-AND-R1-PLAIN-20260919.md +0 -150
  72. package/docs/internal/ISSUE9-RESIDUAL-FORENSICS-20260919.md +0 -114
  73. package/docs/internal/KICKOFF-P0.md +0 -254
  74. package/docs/internal/LESSON-TO-CANDIDATE-STATUS-20260919.md +0 -79
  75. package/docs/internal/MASTER-PLAN-3.0.md +0 -411
  76. package/docs/internal/MEMORY-GOVERNANCE-20260917.md +0 -309
  77. package/docs/internal/MEMORY-MUTATION-AND-INDEX-DESIGN.md +0 -85
  78. package/docs/internal/MERGE-CONFLICT-SCAN-20260914.md +0 -222
  79. package/docs/internal/NEXT-VERSION-TODO.md +0 -95
  80. package/docs/internal/OFFICIAL-DISCUSSION-DRAFT.md +0 -80
  81. package/docs/internal/PENDING-FIXES-20260916.md +0 -289
  82. package/docs/internal/PRE-FRONTEND-CHECKLIST-20260919.md +0 -705
  83. package/docs/internal/PRE-FRONTEND-CHECKLIST-20260919.md.bak-s10 +0 -649
  84. package/docs/internal/PROCEDURAL-MEMORY-AND-APPROVAL-DESIGN-20260918.md +0 -225
  85. package/docs/internal/PROGRESS-20260917.md +0 -93
  86. package/docs/internal/PROMPT-GAP-AUDIT-20260920.md +0 -128
  87. package/docs/internal/R1-DEGRADE-AUDIT-20260918.md +0 -163
  88. package/docs/internal/R1-READABILITY-FORENSICS-20260919.md +0 -127
  89. package/docs/internal/R2-EVIDENCE-DEEP-AUDIT-20260918.md +0 -140
  90. package/docs/internal/R3-DEGRADE-LEDGER-DESIGN-20260918.md +0 -138
  91. package/docs/internal/R4-RECALL-QUOTA-PLAN-20260918.md +0 -218
  92. package/docs/internal/RAG-KARPATHY-PROGRAM.md +0 -229
  93. package/docs/internal/RELEASE-PROCESS.md +0 -99
  94. package/docs/internal/REPORT-P0-NIGHTLY.md +0 -212
  95. package/docs/internal/REPORT-P5-ACCEPTANCE.md +0 -31
  96. package/docs/internal/REPORT-WB-GRAPH-NIGHTLY.md +0 -153
  97. package/docs/internal/RESUME-20260918.md +0 -171
  98. package/docs/internal/RESUME-20260919.md +0 -104
  99. package/docs/internal/REVIEW-WB-GRAPH-SELF.md +0 -81
  100. package/docs/internal/RHINELAB-TO-DEEPSEEK-FEASIBILITY.md +0 -198
  101. package/docs/internal/ROADMAP-20260917-WEEK.md +0 -439
  102. package/docs/internal/ROADMAP.md +0 -106
  103. package/docs/internal/RUN-P0-NIGHTLY.md +0 -227
  104. package/docs/internal/S10-CONSTRUCTION-HANDOFF-20260917.md +0 -185
  105. package/docs/internal/S10-GAP-INVENTORY-20260917.md +0 -239
  106. package/docs/internal/S10-GAPS-PLAIN-20260917.md +0 -125
  107. package/docs/internal/SEMANTIC-ARCHITECTURE-SPEC.md +0 -360
  108. package/docs/internal/SESSION-FILE-REPAIR-PROTOCOL.md +0 -90
  109. package/docs/internal/SUBAGENT-REPORT-ROUTING-PRE-RESEARCH.md +0 -261
  110. package/docs/internal/T6-EXECUTION-20260920.md +0 -130
  111. package/docs/internal/TELEMETRY-EFFECT-REPORT-DESIGN-20260918.md +0 -146
  112. package/docs/internal/THESIS-GAP-ANALYSIS-20260918.md +0 -89
  113. package/docs/internal/THESIS-OUTLINE-20260918.md +0 -147
  114. package/docs/internal/THREE-LAYER-CONTRACT.md +0 -219
  115. package/docs/internal/TODO-BACKLOG.md +0 -263
  116. package/docs/internal/TODO-GRAPH.html +0 -715
  117. package/docs/internal/TODO-GRAPH.html.bak-20260914-v2 +0 -493
  118. package/docs/internal/TODO-GRAPH.html.bak-20260915-alsfix +0 -710
  119. package/docs/internal/TODO-GRAPH.html.bak-20260915-p1 +0 -710
  120. package/docs/internal/TODO-GRAPH.html.bak-20260915-p6a-rev +0 -703
  121. package/docs/internal/TODO-GRAPH.html.bak-20260915-wshint +0 -710
  122. package/docs/internal/TODO-GRAPH.html.bak-20260916-batch +0 -715
  123. package/docs/internal/UPSTREAM-ISSUE-PR-TRIAGE-20260919.md +0 -297
  124. package/docs/internal/UPSTREAM-ISSUES-3RD-AUDIT-20260920.md +0 -104
  125. package/docs/internal/WB-FORMAT-CONVENTION.md +0 -112
  126. package/docs/internal/WB-GRAPH-DECISIONS-20260914.md +0 -71
  127. package/docs/internal/WB-GRAPH-INTEGRATION-PLAN.md +0 -386
  128. package/docs/internal/WB-GRAPH-RESEARCH-BRIEF.md +0 -118
  129. package/docs/internal/WB-GRAPH-RESEARCH-EXTERNAL.md +0 -228
  130. package/docs/internal/WB-GRAPH-RESEARCH-LOCAL.md +0 -190
  131. package/docs/internal/reviews/CLAIM-VERIFICATION-20260914.md +0 -56
  132. package/docs/internal/reviews/PLAN-gpt6astra-round2-20260914.md +0 -787
  133. package/docs/internal/reviews/REVIEW-gpt6astra-20260914.md +0 -112
  134. package/docs/internal/reviews/ROUND3-REVIEW-INTEGRATION-20260914.md +0 -230
package/lib/hub-io.js ADDED
@@ -0,0 +1,217 @@
1
+ /**
2
+ * M8 记忆中枢(Memory Hub)持久化 IO —— **带健康度记账的 io 适配器**(#110,2026-09-22)。
3
+ *
4
+ * ── 背景(为什么需要这个模块)──
5
+ * hub 三店(episodes / facts / procedures)的 `io` 由 index.js 内联的 `hubIo()` 提供,
6
+ * 旧实现三个方法各自 `catch (_) {}` 把异常**吞在适配器这一层**:
7
+ *
8
+ * 1. 上层三店在 A-8 里写好的 `try { io.save(snapshot()) } catch (e) { … ok:false … }`
9
+ * **永远走不到 catch 分支** ⇒ store 照样返回 `{ ok:true, persisted:true }`,
10
+ * `persistFailures` 恒为 0、`lastPersistError` 恒为 null ⇒ 三条线全绿而磁盘没写上。
11
+ * 2. 用户侧表现为「记忆看着存上了,重启清零」,日志、计数、面板三处都拿不到信号。
12
+ *
13
+ * 现在把这段逻辑从 index.js 提出来,语义收紧为三条:
14
+ * · `save` / `clear` 失败**照原样抛出**(把「写不进去就是写不进去」交还调用方,
15
+ * 让 A-8 既有的 try/catch 真正生效),**同时**记一次健康度;
16
+ * · `load` 保持「无文件 / 损坏 → 返回 null(空启动)」的既有控制流不变,只追加可观测性;
17
+ * · 每次失败都写进 health(errno → 中文人话 + 累计计数 + 时间戳),由
18
+ * `hubIoHealthSnapshotPre()` 投影给 debugInfo / 诊断面板。
19
+ *
20
+ * 边界(与既有纪律一致):
21
+ * · 只做记账与转发,**不改变** 落盘格式、原子性策略(tmp + rename)与任何调用方契约;
22
+ * · 不引入时钟依赖以外的副作用;`onError` 回调抛错不得影响主路径(自身 try/catch 包住);
23
+ * · 不做 unlink/copy 回退:调用方各自保留自己的原子性策略(同 `fs-retry.js` 的边界声明)。
24
+ */
25
+
26
+ import { mkdirSync, writeFileSync, renameSync, readFileSync, rmSync } from 'node:fs'
27
+ import path from 'node:path'
28
+
29
+ /** errno → 人话。口径:暴露给前端/工具的原因必须人能看懂,不能只甩机器码。 */
30
+ export const HUB_IO_ERRNO_MESSAGES_V1 = Object.freeze({
31
+ EACCES: '权限被拒绝(目标目录不可写)',
32
+ EPERM: '操作被系统拒绝(权限或安全策略拦截,Windows 下也常见于文件被占用)',
33
+ ENOSPC: '磁盘空间不足',
34
+ EROFS: '目标位于只读位置',
35
+ EBUSY: '文件被其它进程占用',
36
+ ENOENT: '路径不存在(父目录缺失)',
37
+ EISDIR: '该名字被一个目录占着,不是文件',
38
+ ENOTDIR: '路径中间有一段不是目录',
39
+ EEXIST: '临时文件已存在',
40
+ ENAMETOOLONG: '路径过长',
41
+ EMFILE: '进程打开的文件过多',
42
+ EIO: '底层读写错误(磁盘或驱动)',
43
+ })
44
+
45
+ /** 把任意异常翻成一句人话(带 errno 便于排障;无 code 时回落 message)。 */
46
+ export function explainHubIoErrorPre(e) {
47
+ const code = e && e.code ? String(e.code) : ''
48
+ const mapped = code ? HUB_IO_ERRNO_MESSAGES_V1[code] : ''
49
+ if (mapped) return mapped + '(' + code + ')'
50
+ if (code) return '文件系统错误 ' + code
51
+ return String((e && e.message) || e || '未知错误')
52
+ }
53
+
54
+ /** 新建一份健康度台账(纯内存,零 IO)。 */
55
+ export function createHubIoHealthPre() {
56
+ return { errors: 0, lastError: null, lastErrorAt: 0, saves: 0, loads: 0, clears: 0, byFile: {} }
57
+ }
58
+
59
+ /**
60
+ * 记一次失败,并调用 `onError(key, message)`(节流由调用方决定,本模块不持有定时器)。
61
+ * @returns {string} 人话原因
62
+ */
63
+ function notePre(health, name, op, e, onError) {
64
+ const now = Date.now()
65
+ const human = explainHubIoErrorPre(e)
66
+ health.errors += 1
67
+ health.lastError = name + ' ' + op + ' 失败:' + human
68
+ health.lastErrorAt = now
69
+ const key = name + ':' + op
70
+ const rec = health.byFile[key] || { count: 0, lastError: null, lastErrorAt: 0 }
71
+ rec.count += 1
72
+ rec.lastError = human
73
+ rec.lastErrorAt = now
74
+ health.byFile[key] = rec
75
+ if (typeof onError === 'function') {
76
+ try { onError(key, 'M8 hub 持久化失败 —— ' + health.lastError + '(累计 ' + health.errors + ' 次)') } catch (_) { /* 记账不得影响主路径 */ }
77
+ }
78
+ return human
79
+ }
80
+
81
+ /**
82
+ * 造一个 hub io 适配器工厂。
83
+ * @param {{dir:string, health?:object, onError?:Function, fsApi?:object}} opts
84
+ * `fsApi` 注入点仅供测试(故障注入),缺省用 node:fs 同步 API。
85
+ * @returns {(name:string) => {save:Function, load:Function, clear:Function}}
86
+ */
87
+ export function createHubIoPre(opts = {}) {
88
+ const dir = String(opts.dir || '')
89
+ const health = opts.health || createHubIoHealthPre()
90
+ const onError = opts.onError
91
+ const api = opts.fsApi || null
92
+ const mk = (api && api.mkdirSync) || mkdirSync
93
+ const wf = (api && api.writeFileSync) || writeFileSync
94
+ const rn = (api && api.renameSync) || renameSync
95
+ const rf = (api && api.readFileSync) || readFileSync
96
+ const rm = (api && api.rmSync) || rmSync
97
+
98
+ // ★#110(2026-09-22):**批内合并落盘**(消除写放大)。
99
+ // 背景:hub 一次喂数会连续写同一份快照 N 次(每行判据 upsert 一次 ⇒ 一次整份写盘)。
100
+ // 做法:批内只记「最后一次的整份数据」,批末统一原子落盘。**语义无损**——每份快照都是全量,
101
+ // 最后一次即最终状态;store 的内存态始终最新,落盘只是它的投影。
102
+ // 明确声明的代价:批未落盘时进程被杀,本批持久化会丢。这批是「机器切出来的流程观察行」,
103
+ // 源头 judgement-shadow 文件仍在、可重放,**不涉及用户数据**。
104
+ // 失败**不静默**:逐文件记 health(含 errno 人话)并走 onError;返回值把 ok/written/errors 交出去。
105
+ let batchDepth = 0
106
+ const pendingWrites = new Map() // filePath → { name, data }
107
+
108
+ function atomicWrite(file, name, data) {
109
+ mk(dir, { recursive: true })
110
+ const tmp = file + '.tmp'
111
+ wf(tmp, JSON.stringify(data), 'utf8')
112
+ rn(tmp, file)
113
+ }
114
+ function flushPendingPre() {
115
+ let written = 0
116
+ let ok = true
117
+ for (const [file, rec] of pendingWrites) {
118
+ try { atomicWrite(file, rec.name, rec.data); written++ } catch (e) { ok = false }
119
+ }
120
+ pendingWrites.clear()
121
+ return { ok, written, errors: health.errors }
122
+ }
123
+
124
+ const factory = (name) => {
125
+ const file = path.join(dir, String(name))
126
+ return {
127
+ /**
128
+ * 原子写(tmp + rename)。失败**记健康度后原样抛出** —— 上层 A-8 的 try/catch 依赖这一点。
129
+ * 批内(beginBatch 之后 endBatch 之前)改为**只登记不落盘**,批末一次写。
130
+ */
131
+ save(data) {
132
+ health.saves += 1
133
+ if (batchDepth > 0) { pendingWrites.set(file, { name, data }); return }
134
+ try {
135
+ atomicWrite(file, name, data)
136
+ } catch (e) {
137
+ notePre(health, name, 'save', e, onError)
138
+ throw e
139
+ }
140
+ },
141
+ /**
142
+ * 读取。保持既有语义:无文件 / 损坏一律返回 null(空启动,fail closed 幂等恢复);
143
+ * 只有「非 ENOENT 的读取失败」与「JSON 解析失败」才记健康度。
144
+ */
145
+ load() {
146
+ health.loads += 1
147
+ let raw
148
+ try {
149
+ raw = rf(file, 'utf8')
150
+ } catch (e) {
151
+ if (!(e && e.code === 'ENOENT')) notePre(health, name, 'load', e, onError)
152
+ return null
153
+ }
154
+ try {
155
+ return JSON.parse(raw)
156
+ } catch (e) {
157
+ notePre(health, name, 'parse', e, onError)
158
+ return null
159
+ }
160
+ },
161
+ /** 删除。失败**记健康度后原样抛出**(残留快照会在下次 load 时"复活"已清空的数据)。 */
162
+ clear() {
163
+ health.clears += 1
164
+ // 批内 clear 必须先取消该文件的待写,否则批末会把刚删掉的快照又写回来。
165
+ pendingWrites.delete(file)
166
+ try { rm(file, { force: true }) } catch (e) { notePre(health, name, 'clear', e, onError); throw e }
167
+ },
168
+ }
169
+ }
170
+ // ── 批控制(挂在工厂函数上,调用方:`hubIo.beginBatch()` / `hubIo.endBatch()`)──
171
+ factory.beginBatch = () => { batchDepth += 1; return batchDepth }
172
+ factory.endBatch = () => {
173
+ if (batchDepth > 0) batchDepth -= 1
174
+ if (batchDepth > 0) return { ok: true, written: 0, errors: health.errors, deferred: true }
175
+ return flushPendingPre()
176
+ }
177
+ factory.flushBatch = () => flushPendingPre()
178
+ factory.batchPending = () => pendingWrites.size
179
+ return factory
180
+ }
181
+
182
+ /**
183
+ * 健康度只读投影(供 debugInfo / 诊断面板)。
184
+ * 纪律:只出计数 + 人话原因 + 时间戳,**无路径、无原文**;任何异常都不得打断诊断。
185
+ */
186
+ export function hubIoHealthSnapshotPre(health) {
187
+ const empty = { errors: 0, lastError: null, lastErrorAt: null, saves: 0, loads: 0, clears: 0, byFile: {}, verdict: 'ok', summary: '三层记忆已正常落盘(本轮无写入失败)' }
188
+ try {
189
+ if (!health || typeof health !== 'object') return empty
190
+ const byFile = {}
191
+ const src = (health.byFile && typeof health.byFile === 'object') ? health.byFile : {}
192
+ for (const k of Object.keys(src)) {
193
+ const r = src[k] || {}
194
+ byFile[k] = {
195
+ count: Number(r.count) || 0,
196
+ lastError: r.lastError ? String(r.lastError) : null,
197
+ lastErrorAt: Number(r.lastErrorAt) || null,
198
+ }
199
+ }
200
+ const errors = Number(health.errors) || 0
201
+ return {
202
+ errors,
203
+ lastError: health.lastError ? String(health.lastError) : null,
204
+ lastErrorAt: Number(health.lastErrorAt) || null,
205
+ saves: Number(health.saves) || 0,
206
+ loads: Number(health.loads) || 0,
207
+ clears: Number(health.clears) || 0,
208
+ byFile,
209
+ verdict: errors === 0 ? 'ok' : 'io-error',
210
+ summary: errors === 0
211
+ ? '三层记忆已正常落盘(本轮无写入失败)'
212
+ : '三层记忆有 ' + errors + ' 次落盘/读取失败,最近一次:' + String(health.lastError || '') + '。记忆可能只在内存里,重启会丢。',
213
+ }
214
+ } catch (_) {
215
+ return empty
216
+ }
217
+ }