@modusensus/dsh-mneme 0.14.0-beta.1 → 0.14.0-beta.2

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 (543) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +830 -830
  3. package/assets/icon.svg +3 -3
  4. package/bin/cli.mjs +964 -964
  5. package/bin/dsh-mneme-choro.mjs +262 -262
  6. package/bin/dsh-mneme-mcp.mjs +510 -510
  7. package/bin/dsh-mneme-serve.mjs +151 -151
  8. package/cordis.patch.yml +21 -21
  9. package/docs/CONFIGURATION.md +407 -404
  10. package/docs/CONTINUITY.md +139 -139
  11. package/docs/DAEMON.md +57 -57
  12. package/docs/DIRECTORIES.md +114 -114
  13. package/docs/DISTILL.md +397 -371
  14. package/docs/DSH-GO.md +97 -97
  15. package/docs/ENTITIES.md +247 -247
  16. package/docs/FORGET.md +77 -77
  17. package/docs/KB.md +110 -110
  18. package/docs/LOCAL_MODEL.md +222 -222
  19. package/docs/SEMANTIC.md +256 -256
  20. package/docs/SHARE.md +552 -552
  21. package/docs/SLEEP.md +202 -202
  22. package/docs/STORAGE.md +122 -122
  23. package/docs/WORKFLOWS.md +366 -366
  24. package/lib/api-standalone.js +1007 -1007
  25. package/lib/api.js +3600 -3600
  26. package/lib/attachment-text.js +122 -122
  27. package/lib/bootstrap-http.js +105 -105
  28. package/lib/bootstrap.js +215 -215
  29. package/lib/candidates.js +454 -454
  30. package/lib/capabilities.js +819 -819
  31. package/lib/choro-command.js +111 -111
  32. package/lib/client.js +14420 -14322
  33. package/lib/commands.js +95 -95
  34. package/lib/config-keys.js +144 -144
  35. package/lib/config.js +1361 -1344
  36. package/lib/content-hash.js +54 -54
  37. package/lib/continuity.js +258 -258
  38. package/lib/date-hint.js +32 -0
  39. package/lib/diary/auto.js +190 -190
  40. package/lib/diary/compose.js +440 -440
  41. package/lib/diary/dir.js +59 -59
  42. package/lib/diary/export.js +97 -97
  43. package/lib/diary/index.js +26 -26
  44. package/lib/diary/library.js +189 -189
  45. package/lib/diary/qq-inbox.js +142 -142
  46. package/lib/diary/rollover.js +61 -61
  47. package/lib/diary/session-provider.js +122 -122
  48. package/lib/document-index.js +94 -94
  49. package/lib/document.js +322 -322
  50. package/lib/dream/clustering.js +123 -123
  51. package/lib/dream/decisions.js +945 -945
  52. package/lib/dream/narratives.js +44 -44
  53. package/lib/dream/sleep.js +2027 -2027
  54. package/lib/dream/triage.js +139 -139
  55. package/lib/dream.js +1879 -1879
  56. package/lib/dsh-go/host.js +162 -162
  57. package/lib/dsh-go/panel.js +2091 -2091
  58. package/lib/dsh-go/text.js +342 -342
  59. package/lib/dsh-go/views/common.js +68 -68
  60. package/lib/dsh-go/views/entity.js +48 -48
  61. package/lib/dsh-go/views/memory.js +198 -198
  62. package/lib/dsh-go/views/root.js +30 -30
  63. package/lib/dsh-go/views/settings.js +398 -398
  64. package/lib/dsh-go/views/status.js +51 -51
  65. package/lib/embedding.js +342 -342
  66. package/lib/entities/extractor.js +285 -285
  67. package/lib/fence.js +24 -24
  68. package/lib/graph/anchoring.js +184 -184
  69. package/lib/guide.js +74 -74
  70. package/lib/heat.js +144 -144
  71. package/lib/hot-memory.js +119 -119
  72. package/lib/image-describe.js +158 -0
  73. package/lib/index.js +852 -852
  74. package/lib/inject.js +656 -651
  75. package/lib/kb/index.js +309 -309
  76. package/lib/kb/routes.js +219 -219
  77. package/lib/kb/tools.js +148 -148
  78. package/lib/lang.js +607 -595
  79. package/lib/local-embedder.js +358 -358
  80. package/lib/maintenance.js +162 -162
  81. package/lib/memory-source.js +9 -9
  82. package/lib/mirror.js +330 -330
  83. package/lib/organize.js +456 -456
  84. package/lib/peak-hours.js +172 -172
  85. package/lib/pipeline-activities.js +178 -178
  86. package/lib/pipeline-attribution.js +165 -165
  87. package/lib/pipeline-command.js +279 -279
  88. package/lib/pipeline-confirm.js +142 -142
  89. package/lib/pipeline-exec.js +174 -174
  90. package/lib/pipeline-nudge-auto.js +129 -129
  91. package/lib/pipeline-nudge.js +1623 -1623
  92. package/lib/pipeline-observations.js +181 -181
  93. package/lib/pipeline-observe.js +133 -133
  94. package/lib/pipeline-runner.js +361 -361
  95. package/lib/pipeline-runs.js +442 -442
  96. package/lib/pipeline.js +716 -716
  97. package/lib/placement.js +13 -13
  98. package/lib/pre-inject-gate.js +354 -354
  99. package/lib/profile-extract.js +164 -164
  100. package/lib/profile-facts.js +356 -356
  101. package/lib/provenance-http.js +251 -251
  102. package/lib/quality-filter.js +162 -162
  103. package/lib/recall-stats.js +190 -190
  104. package/lib/reclaim-http.js +150 -150
  105. package/lib/reranker.js +269 -269
  106. package/lib/runtime/adopt.js +176 -176
  107. package/lib/runtime/closure.js +172 -172
  108. package/lib/runtime/download.js +247 -247
  109. package/lib/runtime/layout.js +265 -265
  110. package/lib/runtime/loader.js +252 -252
  111. package/lib/runtime/model-fetch.js +361 -361
  112. package/lib/runtime/provision.js +237 -237
  113. package/lib/runtime/tarball.js +130 -130
  114. package/lib/runtime/verify.js +223 -223
  115. package/lib/safe-slice.js +18 -18
  116. package/lib/scope-state.js +277 -277
  117. package/lib/scope.js +187 -187
  118. package/lib/search/adaptive.js +60 -60
  119. package/lib/search/bm25.js +96 -96
  120. package/lib/semantic.js +572 -572
  121. package/lib/sensitive-scan.js +209 -209
  122. package/lib/serve.js +228 -228
  123. package/lib/service.js +3268 -3268
  124. package/lib/session-gate.js +91 -91
  125. package/lib/session-usage.js +350 -350
  126. package/lib/settings.js +827 -817
  127. package/lib/share-http.js +281 -281
  128. package/lib/share-import.js +234 -234
  129. package/lib/share.js +335 -335
  130. package/lib/store.js +5895 -5895
  131. package/lib/summarize.js +1388 -1343
  132. package/lib/tools.js +1370 -1356
  133. package/lib/user-settings.js +230 -230
  134. package/lib/vector-index.js +180 -180
  135. package/lib/version-check.js +122 -122
  136. package/lib/wiki/claims.js +267 -267
  137. package/lib/wiki/cli-hint.js +90 -90
  138. package/lib/wiki/inject.js +164 -164
  139. package/lib/wiki/partition.js +200 -200
  140. package/lib/wiki/plan.js +214 -214
  141. package/lib/wiki/queries.js +640 -640
  142. package/lib/wiki/render.js +261 -261
  143. package/lib/wiki/scan.js +371 -371
  144. package/lib/wiki/t0.js +500 -500
  145. package/lib/wiki/t1-jobs.js +232 -232
  146. package/lib/wiki/t1.js +609 -609
  147. package/lib/write-admission.js +404 -404
  148. package/locale/en.json +6 -6
  149. package/locale/zh.json +6 -6
  150. package/package.json +1 -1
  151. package/runtime-manifest.json +526 -526
  152. package/scripts/benchmark-embed.js +201 -201
  153. package/scripts/benchmark-recall.js +249 -249
  154. package/scripts/benchmark-rerank.js +166 -166
  155. package/scripts/build-runtime-manifest.mjs +224 -224
  156. package/scripts/changelog-prep.mjs +91 -91
  157. package/scripts/check-sync.js +44 -44
  158. package/scripts/e2e-dsh.js +220 -220
  159. package/scripts/mneme-runtime.mjs +277 -277
  160. package/scripts/publish-gate.mjs +197 -197
  161. package/scripts/release-docs.mjs +210 -210
  162. package/scripts/release-notes.mjs +164 -164
  163. package/scripts/stress-dsh.js +255 -255
  164. package/scripts/sync-lib.js +47 -47
  165. package/scripts/sync-test-badge.mjs +69 -69
  166. package/scripts/test-count-sync.mjs +48 -48
  167. package/src/api-standalone.js +1007 -1007
  168. package/src/api.js +3600 -3600
  169. package/src/attachment-text.js +122 -122
  170. package/src/bootstrap-http.js +105 -105
  171. package/src/bootstrap.js +215 -215
  172. package/src/candidates.js +454 -454
  173. package/src/capabilities.js +819 -819
  174. package/src/choro-command.js +111 -111
  175. package/src/commands.js +95 -95
  176. package/src/config-keys.js +144 -144
  177. package/src/config.js +1361 -1344
  178. package/src/content-hash.js +54 -54
  179. package/src/continuity.js +258 -258
  180. package/src/date-hint.js +32 -0
  181. package/src/diary/auto.js +190 -190
  182. package/src/diary/compose.js +440 -440
  183. package/src/diary/dir.js +59 -59
  184. package/src/diary/export.js +97 -97
  185. package/src/diary/index.js +26 -26
  186. package/src/diary/library.js +189 -189
  187. package/src/diary/qq-inbox.js +142 -142
  188. package/src/diary/rollover.js +61 -61
  189. package/src/diary/session-provider.js +122 -122
  190. package/src/document-index.js +94 -94
  191. package/src/document.js +322 -322
  192. package/src/dream/clustering.js +123 -123
  193. package/src/dream/decisions.js +945 -945
  194. package/src/dream/narratives.js +44 -44
  195. package/src/dream/sleep.js +2027 -2027
  196. package/src/dream/triage.js +139 -139
  197. package/src/dream.js +1879 -1879
  198. package/src/dsh-go/host.js +162 -162
  199. package/src/dsh-go/panel.js +2091 -2091
  200. package/src/dsh-go/text.js +342 -342
  201. package/src/dsh-go/views/common.js +68 -68
  202. package/src/dsh-go/views/entity.js +48 -48
  203. package/src/dsh-go/views/memory.js +198 -198
  204. package/src/dsh-go/views/root.js +30 -30
  205. package/src/dsh-go/views/settings.js +398 -398
  206. package/src/dsh-go/views/status.js +51 -51
  207. package/src/embedding.js +342 -342
  208. package/src/entities/extractor.js +285 -285
  209. package/src/fence.js +24 -24
  210. package/src/graph/anchoring.js +184 -184
  211. package/src/guide.js +74 -74
  212. package/src/heat.js +144 -144
  213. package/src/hot-memory.js +119 -119
  214. package/src/image-describe.js +158 -0
  215. package/src/index.js +852 -852
  216. package/src/inject.js +656 -651
  217. package/src/kb/index.js +309 -309
  218. package/src/kb/routes.js +219 -219
  219. package/src/kb/tools.js +148 -148
  220. package/src/lang.js +607 -595
  221. package/src/local-embedder.js +358 -358
  222. package/src/maintenance.js +162 -162
  223. package/src/memory-source.js +9 -9
  224. package/src/mirror.js +330 -330
  225. package/src/organize.js +456 -456
  226. package/src/peak-hours.js +172 -172
  227. package/src/pipeline-activities.js +178 -178
  228. package/src/pipeline-attribution.js +165 -165
  229. package/src/pipeline-command.js +279 -279
  230. package/src/pipeline-confirm.js +142 -142
  231. package/src/pipeline-exec.js +174 -174
  232. package/src/pipeline-nudge-auto.js +129 -129
  233. package/src/pipeline-nudge.js +1623 -1623
  234. package/src/pipeline-observations.js +181 -181
  235. package/src/pipeline-observe.js +133 -133
  236. package/src/pipeline-runner.js +361 -361
  237. package/src/pipeline-runs.js +442 -442
  238. package/src/pipeline.js +716 -716
  239. package/src/placement.js +13 -13
  240. package/src/pre-inject-gate.js +354 -354
  241. package/src/profile-extract.js +164 -164
  242. package/src/profile-facts.js +356 -356
  243. package/src/provenance-http.js +251 -251
  244. package/src/quality-filter.js +162 -162
  245. package/src/recall-stats.js +190 -190
  246. package/src/reclaim-http.js +150 -150
  247. package/src/reranker.js +269 -269
  248. package/src/runtime/adopt.js +176 -176
  249. package/src/runtime/closure.js +172 -172
  250. package/src/runtime/download.js +247 -247
  251. package/src/runtime/layout.js +265 -265
  252. package/src/runtime/loader.js +252 -252
  253. package/src/runtime/model-fetch.js +361 -361
  254. package/src/runtime/provision.js +237 -237
  255. package/src/runtime/tarball.js +130 -130
  256. package/src/runtime/verify.js +223 -223
  257. package/src/safe-slice.js +18 -18
  258. package/src/scope-state.js +277 -277
  259. package/src/scope.js +187 -187
  260. package/src/search/adaptive.js +60 -60
  261. package/src/search/bm25.js +96 -96
  262. package/src/semantic.js +572 -572
  263. package/src/sensitive-scan.js +209 -209
  264. package/src/serve.js +228 -228
  265. package/src/service.js +3268 -3268
  266. package/src/session-gate.js +91 -91
  267. package/src/session-usage.js +350 -350
  268. package/src/settings.js +827 -817
  269. package/src/share-http.js +281 -281
  270. package/src/share-import.js +234 -234
  271. package/src/share.js +335 -335
  272. package/src/store.js +5895 -5895
  273. package/src/summarize.js +1388 -1343
  274. package/src/tools.js +1370 -1356
  275. package/src/user-settings.js +230 -230
  276. package/src/vector-index.js +180 -180
  277. package/src/version-check.js +122 -122
  278. package/src/wiki/claims.js +267 -267
  279. package/src/wiki/cli-hint.js +90 -90
  280. package/src/wiki/inject.js +164 -164
  281. package/src/wiki/partition.js +200 -200
  282. package/src/wiki/plan.js +214 -214
  283. package/src/wiki/queries.js +640 -640
  284. package/src/wiki/render.js +261 -261
  285. package/src/wiki/scan.js +371 -371
  286. package/src/wiki/t0.js +500 -500
  287. package/src/wiki/t1-jobs.js +232 -232
  288. package/src/wiki/t1.js +609 -609
  289. package/src/write-admission.js +404 -404
  290. package/test/adaptive-inject-budget.test.js +85 -85
  291. package/test/api-hardening.test.js +173 -173
  292. package/test/api-nudge-route.test.js +259 -259
  293. package/test/api-routes-gaps.test.js +1092 -1092
  294. package/test/api.test.js +3007 -2998
  295. package/test/attachment-text.test.js +139 -139
  296. package/test/audit.test.js +448 -448
  297. package/test/baseline-persist.test.js +248 -248
  298. package/test/benchmark.test.js +94 -94
  299. package/test/bm25-corpus-cache.test.js +145 -145
  300. package/test/bootstrap.test.js +228 -228
  301. package/test/candidates.test.js +345 -345
  302. package/test/capabilities.test.js +577 -577
  303. package/test/capacity-warning.test.js +262 -262
  304. package/test/changelog-prep.test.js +125 -125
  305. package/test/choro-command.test.js +218 -218
  306. package/test/cli-enums.test.js +37 -37
  307. package/test/cli-flag-value.test.js +86 -86
  308. package/test/cli-receive.test.js +390 -390
  309. package/test/cli-share.test.js +437 -437
  310. package/test/client-share.test.js +225 -225
  311. package/test/client.test.js +4595 -4425
  312. package/test/clustering.test.js +100 -100
  313. package/test/commands.test.js +106 -106
  314. package/test/config-keys-routes.test.js +233 -233
  315. package/test/config.test.js +153 -153
  316. package/test/conflict-freeze.test.js +344 -344
  317. package/test/conflict-ledger-enforce.test.js +452 -452
  318. package/test/conflict-ledger.test.js +242 -242
  319. package/test/conflict-reviewed.test.js +79 -79
  320. package/test/content-hash.test.js +204 -204
  321. package/test/continuity.test.js +452 -452
  322. package/test/contrast.test.js +432 -432
  323. package/test/date-hint.test.js +156 -0
  324. package/test/dedupe-candidate-window.test.js +180 -180
  325. package/test/dependency-overrides.test.js +78 -78
  326. package/test/diary-library.test.js +187 -187
  327. package/test/diary.test.js +576 -576
  328. package/test/differentiate-feedback.test.js +248 -248
  329. package/test/distill-tx-embed-gap.test.js +110 -110
  330. package/test/docs-config-keys.test.js +65 -65
  331. package/test/document-dir.test.js +233 -233
  332. package/test/document.test.js +703 -703
  333. package/test/dream-archive-guardrail.test.js +116 -116
  334. package/test/dream-baseline-clamp.test.js +174 -174
  335. package/test/dream-candidate-hybrid.test.js +281 -281
  336. package/test/dream-coverage-degrade.test.js +86 -86
  337. package/test/dream-decision-evidence.test.js +314 -314
  338. package/test/dream-failure-backoff.test.js +173 -173
  339. package/test/dream-fence.test.js +207 -207
  340. package/test/dream-maxtokens-fallback.test.js +132 -132
  341. package/test/dream-merge-guardrail.test.js +219 -219
  342. package/test/dream-peak-hours.test.js +194 -194
  343. package/test/dream.test.js +1528 -1528
  344. package/test/dsh-go-host.test.js +134 -134
  345. package/test/dsh-go-panel-actions.test.js +854 -854
  346. package/test/dsh-go-panel.test.js +251 -251
  347. package/test/embed-ready-poll.test.js +94 -94
  348. package/test/embed-text-reasons.test.js +112 -112
  349. package/test/embedding-cache-f32.test.js +276 -276
  350. package/test/embedding-ready.test.js +226 -226
  351. package/test/entities.test.js +711 -711
  352. package/test/entity-recall.test.js +179 -179
  353. package/test/epistemic.test.js +325 -325
  354. package/test/failure-query.test.js +147 -147
  355. package/test/failure-retention.test.js +105 -105
  356. package/test/fnew-0112.test.js +311 -311
  357. package/test/fnew-03.test.js +422 -422
  358. package/test/forget-corrections.test.js +70 -70
  359. package/test/graph-anchoring.test.js +184 -184
  360. package/test/graph-api.test.js +333 -333
  361. package/test/graph-boosted-relations.test.js +189 -189
  362. package/test/graph-inject-budget.test.js +103 -103
  363. package/test/graph-multipath.test.js +269 -269
  364. package/test/graph-passive-confirm.test.js +91 -91
  365. package/test/graph-weight-evolution.test.js +104 -104
  366. package/test/heat.test.js +175 -175
  367. package/test/helpers/dream-mock.js +86 -86
  368. package/test/helpers/peer-worker.mjs +25 -25
  369. package/test/helpers/runtime-source.js +60 -60
  370. package/test/helpers/tar-builder.js +80 -80
  371. package/test/helpers/write-admission-samples.js +312 -312
  372. package/test/hot-memory.test.js +259 -259
  373. package/test/image-description.test.js +271 -0
  374. package/test/image-memory.test.js +170 -170
  375. package/test/inject-content-cap.test.js +91 -91
  376. package/test/inject-first-round-bm25.test.js +89 -89
  377. package/test/inject-heat.test.js +77 -77
  378. package/test/inject-parent-gate.test.js +134 -109
  379. package/test/inject-pin-explicit.test.js +300 -300
  380. package/test/inject-pin-vector-crowd.test.js +225 -225
  381. package/test/inject-pools.test.js +195 -195
  382. package/test/inject-recall.test.js +172 -172
  383. package/test/inject-rotation.test.js +161 -161
  384. package/test/inject.test.js +553 -553
  385. package/test/issue-617-step1-query-and-fusion.test.js +361 -361
  386. package/test/kb-api-wiring.test.js +128 -128
  387. package/test/kb-collections.test.js +272 -272
  388. package/test/kb-routes.test.js +379 -379
  389. package/test/lang.test.js +98 -98
  390. package/test/lastrunat-persist.test.js +101 -101
  391. package/test/lib-smoke.test.js +116 -116
  392. package/test/llm-audit.test.js +815 -815
  393. package/test/llm-message-source.test.js +207 -207
  394. package/test/local-embedder.test.js +292 -292
  395. package/test/maintenance.test.js +501 -501
  396. package/test/mcp-stdio.test.js +385 -385
  397. package/test/mint-source.test.js +311 -311
  398. package/test/mirror-dirty.test.js +424 -424
  399. package/test/mirror-edit-digest.test.js +187 -187
  400. package/test/mirror-generation.test.js +499 -499
  401. package/test/mirror-types.test.js +208 -208
  402. package/test/mirror.test.js +249 -249
  403. package/test/narrative-bars.test.js +162 -162
  404. package/test/organize-tool.test.js +232 -232
  405. package/test/organize.test.js +418 -418
  406. package/test/panel-chart-scale.test.js +90 -90
  407. package/test/panel-tokens.test.js +245 -245
  408. package/test/peak-hours-observability.test.js +70 -70
  409. package/test/peer-blockers.test.js +193 -193
  410. package/test/persona-addressing.test.js +83 -83
  411. package/test/persona-api.test.js +172 -172
  412. package/test/persona-by-session.test.js +568 -568
  413. package/test/persona-flag.test.js +36 -36
  414. package/test/persona-inject.test.js +145 -145
  415. package/test/persona-settings.test.js +198 -198
  416. package/test/pipeline-activities.test.js +155 -155
  417. package/test/pipeline-attribution.test.js +175 -175
  418. package/test/pipeline-command.test.js +243 -243
  419. package/test/pipeline-confirm.test.js +148 -148
  420. package/test/pipeline-exec.test.js +172 -172
  421. package/test/pipeline-nudge-auto.test.js +248 -248
  422. package/test/pipeline-nudge.test.js +1278 -1278
  423. package/test/pipeline-observations.test.js +157 -157
  424. package/test/pipeline-observe.test.js +127 -127
  425. package/test/pipeline-r1r2.test.js +278 -278
  426. package/test/pipeline-runner.test.js +468 -468
  427. package/test/pipeline-runs.test.js +767 -767
  428. package/test/pipeline.test.js +636 -636
  429. package/test/placement.test.js +36 -36
  430. package/test/policy-epoch.test.js +259 -259
  431. package/test/pre-inject-gate.test.js +444 -444
  432. package/test/profile-extract.test.js +269 -269
  433. package/test/profile-facts.test.js +492 -492
  434. package/test/prompt-stance.test.js +128 -128
  435. package/test/provenance-http.test.js +367 -367
  436. package/test/publish-gate.test.js +272 -272
  437. package/test/quality-backfill.test.js +290 -290
  438. package/test/quality-filter.test.js +295 -295
  439. package/test/readme-claims.test.js +113 -113
  440. package/test/reasoning-effort.test.js +752 -752
  441. package/test/recall-anti-update.test.js +82 -82
  442. package/test/recall-evals.test.js +253 -253
  443. package/test/recall-layer.test.js +326 -326
  444. package/test/recall-retention.test.js +128 -128
  445. package/test/recall-stats.test.js +274 -274
  446. package/test/receipt-chain.test.js +451 -451
  447. package/test/reclaim-http.test.js +209 -209
  448. package/test/reflection-update-gate.test.js +129 -129
  449. package/test/reflection.test.js +226 -226
  450. package/test/reindex-backfill.test.js +475 -475
  451. package/test/reindex-scheduler.test.js +369 -369
  452. package/test/release-docs.test.js +156 -156
  453. package/test/release-notes.test.js +265 -265
  454. package/test/release-workflow.test.js +93 -93
  455. package/test/reranker-single-logit.test.js +73 -73
  456. package/test/reranker.test.js +197 -197
  457. package/test/resident-status.test.js +83 -83
  458. package/test/route-policy.test.js +113 -113
  459. package/test/runtime-adopt.test.js +206 -206
  460. package/test/runtime-cli.test.js +89 -89
  461. package/test/runtime-closure.test.js +193 -193
  462. package/test/runtime-download.test.js +302 -302
  463. package/test/runtime-layout.test.js +163 -163
  464. package/test/runtime-loader.test.js +255 -255
  465. package/test/runtime-manifest.test.js +159 -159
  466. package/test/runtime-model-fetch.test.js +348 -348
  467. package/test/runtime-provision.test.js +101 -101
  468. package/test/runtime-tarball.test.js +81 -81
  469. package/test/runtime-verify.test.js +319 -319
  470. package/test/scope-candidates.test.js +491 -491
  471. package/test/scope-distill-stamp.test.js +134 -134
  472. package/test/scope-explicit.test.js +452 -452
  473. package/test/scope-inject-soft.test.js +106 -106
  474. package/test/scope-optional-service.test.js +110 -110
  475. package/test/scope-retrieval.test.js +197 -197
  476. package/test/scope-state.test.js +610 -610
  477. package/test/scope-storage.test.js +255 -255
  478. package/test/scope-strict.test.js +367 -367
  479. package/test/search-fusion.test.js +90 -90
  480. package/test/security-doc.test.js +188 -188
  481. package/test/semantic.test.js +124 -124
  482. package/test/sensitive-scan.test.js +251 -251
  483. package/test/serve-bin.test.js +121 -121
  484. package/test/serve.test.js +258 -258
  485. package/test/service-search.test.js +174 -174
  486. package/test/service.test.js +410 -410
  487. package/test/session-gate.test.js +264 -264
  488. package/test/session-usage.test.js +317 -317
  489. package/test/settings.test.js +273 -273
  490. package/test/share-foreign-rows.test.js +144 -144
  491. package/test/share-http.test.js +144 -144
  492. package/test/share-import.test.js +577 -577
  493. package/test/share-imported-guard.test.js +311 -311
  494. package/test/share-imported-identity.test.js +111 -111
  495. package/test/share-pack.test.js +511 -511
  496. package/test/share-receive-http.test.js +756 -756
  497. package/test/share-routes.test.js +337 -337
  498. package/test/share-store.test.js +133 -133
  499. package/test/share-validate.test.js +101 -101
  500. package/test/skip-invalid-restore.test.js +262 -262
  501. package/test/sleep-action-set.test.js +441 -441
  502. package/test/sleep-catchup.test.js +315 -315
  503. package/test/sleep-failure-backoff.test.js +156 -156
  504. package/test/sleep-heat.test.js +174 -174
  505. package/test/sleep-scheduler-rearm.test.js +89 -89
  506. package/test/sleep.test.js +1455 -1455
  507. package/test/standalone-api.test.js +1344 -1344
  508. package/test/store-vector-cache.test.js +98 -98
  509. package/test/store-write-path-columns.test.js +191 -191
  510. package/test/store.test.js +744 -744
  511. package/test/stress.test.js +209 -209
  512. package/test/summarize-occurred-at.test.js +127 -127
  513. package/test/summarize-parse-diagnostics.test.js +75 -75
  514. package/test/summarize-reasoning-effort.test.js +189 -189
  515. package/test/summarize.test.js +2251 -2251
  516. package/test/supersede-state-tracking.test.js +394 -394
  517. package/test/test-count-sync.test.js +71 -71
  518. package/test/tool-state-ignore.test.js +65 -65
  519. package/test/tools-dto-schema-matrix.test.js +398 -398
  520. package/test/tools-memory-get-schema.test.js +65 -65
  521. package/test/tools.test.js +585 -585
  522. package/test/updated-at-semantics.test.js +113 -113
  523. package/test/user-settings-preview.test.js +127 -127
  524. package/test/user-settings-segments.test.js +527 -527
  525. package/test/vector-index.test.js +334 -334
  526. package/test/version-check.test.js +152 -152
  527. package/test/webServer-optional.test.js +82 -82
  528. package/test/wiki-api.test.js +734 -734
  529. package/test/wiki-claims.test.js +194 -194
  530. package/test/wiki-cli-hint.test.js +118 -118
  531. package/test/wiki-inject.test.js +265 -265
  532. package/test/wiki-partition.test.js +112 -112
  533. package/test/wiki-plan.test.js +80 -80
  534. package/test/wiki-provenance.test.js +202 -202
  535. package/test/wiki-queries.test.js +398 -398
  536. package/test/wiki-scan.test.js +208 -208
  537. package/test/wiki-store.test.js +190 -190
  538. package/test/wiki-t0.test.js +456 -456
  539. package/test/wiki-t1-jobs.test.js +125 -125
  540. package/test/wiki-t1.test.js +460 -460
  541. package/test/wiki-tools.test.js +176 -176
  542. package/test/workflow-guards.test.js +155 -155
  543. package/test/write-admission.test.js +668 -668
package/src/document.js CHANGED
@@ -1,322 +1,322 @@
1
- // dsh-mneme/src/document.js
2
- // document 型记忆注册(issue #230,口径基线 = #164 设计稿评审线):agent 产
3
- // 长文档的指针行铸造口。写入权分离——全文归 agent(管线对文件零读零写零改),
4
- // 管线只做四件事:注册校验(文件存在 + 路径合法 + evidence 求交)、摘要 +
5
- // doc_path 落库(库里只存这三样)、C2 vector 档比对去重、supersede 记账
6
- // (出新版 = 新摘要行 supersede 旧行,旧文件不删,content_history 可追溯)。
7
- //
8
- // 独立成模块(AGENTS.md 尺寸约定,同 recallStats 先例):service.js 已过
9
- // 2000 行参考线,这里只依赖注入 service 内部件,barrel 出口在 service.js,
10
- // 调用方零改动。防绕过三层兜底:service 两条业务守卫(saveWithDedupe /
11
- // updateMemory)+ 存储层唯一铸造口(store.saveDocument,通用 save/update/
12
- // CAS 整类拒绝 document)——注册是唯一铸造口。
13
-
14
- import { statSync } from "node:fs";
15
- import { homedir } from "node:os";
16
- import { isAbsolute, resolve, sep } from "node:path";
17
- import { scopeKeyOf } from "./scope.js";
18
- import { cosineSimilarity } from "./dream/clustering.js";
19
- import { isImportedRow } from "./memory-source.js";
20
-
21
- // C2 vector 档阈值(#164 拍板 0.92,与 findSessionDuplicate 的 vector 档同源)。
22
- // 刻意不做 24h 时间窗:那个窗是会话去重的「近邻界」(findSessionDuplicate 注释),
23
- // 不是相似度语义本身;document 版本更新天然跨天/跨周,套窗会把一周后重注册的
24
- // 新版判成「无近重复」,留下两行活跃摘要漂移——supersede 探测必须全时段。
25
- const DOCUMENT_MIN_SIM = 0.92;
26
- const CANDIDATE_LIMIT = 200;
27
- const DOCUMENT_DIR_NAME = "documents";
28
-
29
- /**
30
- * 解析 managed 文档目录(#296 第二批)。`configured` 为空 = 跟随 memoryDir 的
31
- * `<memoryDir>/documents/`;`~` / `~/` / `~\` 展开到用户 home;相对路径落在
32
- * memoryDir 下(本键自己的规则:memoryDir 的相对路径原样留着走 cwd 语义);
33
- * 绝对路径归一化后原样用。纯函数,路径解析集中在 index.js 调用一次。
34
- */
35
- export function resolveDocumentDir(memoryDir, configured = "") {
36
- const raw = String(configured ?? "").trim();
37
- if (!raw) return resolve(memoryDir, DOCUMENT_DIR_NAME);
38
- if (raw === "~" || raw.startsWith("~/") || raw.startsWith("~\\")) {
39
- return resolve(homedir(), raw.slice(2));
40
- }
41
- return isAbsolute(raw) ? resolve(raw) : resolve(memoryDir, raw);
42
- }
43
-
44
- /**
45
- * 这个路径是不是落在 managed 目录里(`documentDir` 内 = mneme 管的树,`index.md`
46
- * 与镜像 md 同权;之外 = 只登记指针行、正文一个字节都不碰)。比较前两边都归一化,
47
- * Windows 上大小写不敏感(文件系统就是),否则 `C:\a\documents\x.md` 与
48
- * `c:\A\Documents\x.md` 会被判成两类。
49
- */
50
- export function isManagedDocumentPath(dir, path) {
51
- if (!dir || !path) return false;
52
- const norm = (p) => (process.platform === "win32" ? String(p).toLowerCase() : String(p));
53
- const base = norm(resolve(dir));
54
- const target = norm(resolve(path));
55
- const prefix = base.endsWith(sep) ? base : base + sep;
56
- return target === base || target.startsWith(prefix);
57
- }
58
-
59
- // 追加到被取代行正文的指针注记。刻意不带语言分支:id 是跨语言稳定键(用户
60
- // 对 tag/章节名建议用英文同理——指针性内容不翻译);可追溯链 = 注记里的新行
61
- // id + 旧行 content_history 里的旧摘要(source=superseded)。
62
- const supersededByPointer = (id) => `\n\n[superseded by ${id}]`;
63
-
64
- // 升格吸收不碰的类型(#275 拍板 5 的边界):吸收的对象是「原子条」,而下面这两种
65
- // 行各有自己的生命周期,被归档会各自留下第二行——
66
- // document:本体是磁盘上的文件。归档它等于替用户撤下外档;而且 supersede 探测
67
- // 只看活跃行(store.list 默认排除归档),同一 path 下次注册会在旧行还挂着
68
- // 「新版本」语义时再铸一行,指针注记与 content_history 记账也走不到。
69
- // summary:dream 总览与叙述按 source 身份去重、并按注入档位常驻。归档它会让下一
70
- // 次做梦把它当不存在(候选集同样只取活跃行),于是库里多出一行同源总览。
71
- // 口径是保守方向的:拿不准的行一律不吸收——少做一步没有代价,收错一步是内容事故。
72
- const ABSORB_EXEMPT_TYPES = new Set(["document", "summary"]);
73
-
74
- /**
75
- * Build the document registrar. Injected deps are service.js closure members
76
- * (store / embedQuery / transaction / finalize) so this module stays free of
77
- * service-internal wiring; `finalize` is the single write epilogue (mirror
78
- * sync + notify + re-embed) so callers never duplicate it.
79
- *
80
- * `pinnedTypes` 是注入的 #249 逐字保真池(service.js 的 PINNED_MEMORY_TYPES):
81
- * 这里不 import 它在 service.js 里,是因为 service.js 反向 import 本模块——
82
- * 方向反了会成环。
83
- *
84
- * @returns {(payload: object, opts?: object) => Promise<object>}
85
- * `{ action: "created"|"superseded", memory, superseded?, evidence_kept,
86
- * evidence_dropped, evidence_archived, evidence_skipped, degraded }`
87
- * @throws flag 关 / 路径非法或文件缺失 / title 或 summary 为空 / evidence 全部
88
- * 捏造 / 仅 vector 近重复(疑似重复外档,交 agent 裁决而不是静默二选一)。
89
- */
90
- export function createDocumentRegistrar({ store, config, embedQuery, pushContentHistory, transaction, finalize, pinnedTypes }) {
91
- return async function registerDocument(payload, { hiddenEvidenceIds = [], archiveEvidence = true } = {}) {
92
- // opt-in 总闸(#230 对齐 #228 形态):关 = 注册入口整体不存在,错误信息
93
- // 指回配置键,agent 能准确转告用户去开。
94
- if (config?.documentMemoryEnabled !== true) {
95
- throw new Error("document memory is disabled (documentMemoryEnabled)");
96
- }
97
-
98
- // --- 1. 路径合法 + 文件存在 --------------------------------------------
99
- // 只认展开 ~ 后的绝对路径:相对路径依赖进程 cwd,注册时的 cwd 与读取时
100
- // 的 cwd 不保证一致,指针必须自带完整定位。校验存在 + 常规文件 + 非空——
101
- // 空文件/被截断成 0 字节是「文字结构意外破坏」的最早信号,注册时拦住
102
- // 而不是落一条死指针(回退路径:agent 修好文件后重注册,同 path/title
103
- // 自动 supersede 旧行)。
104
- const rawPath = String(payload?.path ?? "").trim();
105
- if (!rawPath) throw new Error("registerDocument: path is required");
106
- // 只展开 ~ / ~/(~user 形式拒收为普通相对路径走 isAbsolute 拦截——不猜测
107
- // 其他用户的 home,错展开比报错更糟)。
108
- const isTilde = rawPath === "~" || rawPath.startsWith("~/") || rawPath.startsWith("~\\");
109
- const absolute = isTilde ? resolve(homedir(), rawPath.slice(2)) : rawPath;
110
- if (!isAbsolute(absolute)) {
111
- throw new Error(`registerDocument: path must be absolute (got "${rawPath}")`);
112
- }
113
- // 所有绝对路径统一 resolve 归一化(. / .. / 重复分隔符)——归一化不做的话,
114
- // D:\a\.\b.md 与 D:\a\b.md 会被判成两个路径,同文件绕过 same-path
115
- // supersede,留下双活跃指针。realpathSync 刻意不用:符号链接合并会改写
116
- // agent 提交的路径字面量,指针的可读性比追符号链更重要。
117
- const expanded = resolve(absolute);
118
- let stat = null;
119
- try { stat = statSync(expanded); } catch { /* missing/unreadable → null */ }
120
- if (!stat?.isFile() || stat.size <= 0) {
121
- throw new Error(`registerDocument: document file not found or empty: ${expanded}`);
122
- }
123
-
124
- // --- 2. evidence 求交(sleep.js 扫描集求交同款语义)---------------------
125
- // 引用的原子条必须真实存在且未归档(归档行已被降级,不再为新鲜 document
126
- // 背书)。红线 4 宽容形态:合法子集落库 + evidence_degraded 系统标记;
127
- // 提供了 evidence 但全军覆没 = 捏造信号,整单拒绝,agent 修正后重试。
128
- const wanted = [...new Set(
129
- (Array.isArray(payload?.evidence) ? payload.evidence : [])
130
- .map((id) => String(id ?? "").trim())
131
- .filter(Boolean)
132
- )];
133
- // strictScope(tools 层传入,#170 复核项 4 同款):调用者 scope 看不见的
134
- // 行按「不存在」处理——不得为跨 scope id 建立引用(存在性泄漏),与
135
- // unknown/archived 同落 dropped,evidence_kept/dropped 计数保持诚实。
136
- const hidden = new Set((Array.isArray(hiddenEvidenceIds) ? hiddenEvidenceIds : []).map((id) => String(id)));
137
- const kept = [];
138
- const dropped = [];
139
- // 存在但已归档的引用另记一份:它可能是被上一版文档升格吸收走的行(见下方
140
- // supersede 目标探测后的「认回」),不能一律按「不可用」处理。
141
- const archivedRefs = new Set();
142
- for (const id of wanted) {
143
- const row = hidden.has(id) ? null : store.getById(id);
144
- if (row?.archived) archivedRefs.add(id);
145
- (row && !row.archived ? kept : dropped).push(id);
146
- }
147
-
148
- const title = String(payload?.title ?? "").trim();
149
- if (!title) throw new Error("registerDocument: title is required");
150
- const summary = String(payload?.summary ?? "").trim();
151
- if (!summary) throw new Error("registerDocument: summary is required");
152
-
153
- // --- 3. supersede 目标探测(三层)---------------------------------------
154
- // 同 doc_path / 同标题 = agent 显式的「出新版」意图 → supersede;仅 vector
155
- // 近重复而路径标题都不同 = 疑似重复外档 → 拒绝并指路。C2×C1 硬线:比对
156
- // 不得越过矛盾裁决替 agent 二选一——改既有行还是归档后重注册,判断权留在
157
- // agent 手里。去重键带 scope(scopeKeyOf):跨作用域永不互判(防泄漏,与
158
- // saveWithDedupe 的 scopeMatches 同口径)。
159
- const scopeMatches = (m) =>
160
- scopeKeyOf(m.agent_scope) === scopeKeyOf(payload?.agent_scope) &&
161
- scopeKeyOf(m.workspace_scope) === scopeKeyOf(payload?.workspace_scope) &&
162
- scopeKeyOf(m.sensitivity) === scopeKeyOf(payload?.sensitivity);
163
- const samePath = (a, b) => {
164
- if (!a || !b) return false;
165
- // Windows 文件系统大小写不敏感:比较前归一,否则 C:\a.md 与 c:\A.md
166
- // 会被判成两个版本。
167
- const norm = (p) => (process.platform === "win32" ? String(p).toLowerCase() : String(p));
168
- return norm(a) === norm(b);
169
- };
170
- // 精确层(同路径/同标题)必须全量扫描、不允许窗口截断:>200 行活跃
171
- // document 时,窗口外的同路径旧版漏检会留下双活跃版本——supersede 判定
172
- // 是正确性要求,不是性能优化对象。vector 档比对保留 CANDIDATE_LIMIT
173
- // (getEmbeddings + 两两 cosine 才是有界对象)。
174
- const scopeMatched = store
175
- .list({ type: "document", limit: null })
176
- .filter((m) => scopeMatches(m));
177
- const explicitTarget = scopeMatched.find((m) => samePath(m.doc_path, expanded))
178
- ?? scopeMatched.find((m) => m.title.trim() === title);
179
- // 升格吸收过的引用认回(#275 拍板 5 的配套):evidence 行在文档注册成功后就翻了
180
- // archived,于是「同一份文档出新版、evidence 照旧」这一最常规的路径会整批落在
181
- // dropped——那会被下面的捏造判据误报成「证据都是编的」。口径:已归档行仍可为
182
- // **吸收它的那份文档**背书(loser.evidence 就是它吸收走的名单),不为别的文档
183
- // 背书;捏造与跨 scope 照旧拒绝。
184
- if (explicitTarget) {
185
- const own = new Set(explicitTarget.evidence ?? []);
186
- for (const id of archivedRefs) {
187
- if (!own.has(id)) continue;
188
- const at = dropped.indexOf(id);
189
- if (at >= 0) dropped.splice(at, 1);
190
- kept.push(id);
191
- }
192
- }
193
- if (wanted.length > 0 && kept.length === 0) {
194
- throw new Error(
195
- `registerDocument: all ${wanted.length} evidence ids are unknown, archived or out of scope — fabricated evidence is rejected`
196
- );
197
- }
198
- if (!explicitTarget) {
199
- // vector 档:embedder 不可用/向量缺失一律跳过——去重是增强不是写入依赖
200
- // (findSessionDuplicate 同原则)。probe 用 title+summary,与行向量
201
- // (同两段拼接 embed)同构。
202
- const candidates = scopeMatched.slice(0, CANDIDATE_LIMIT);
203
- try {
204
- const vecs = store.getEmbeddings(candidates.map((m) => m.id));
205
- if (vecs.size) {
206
- const probe = await embedQuery([title, summary].filter(Boolean).join("\n"));
207
- if (probe) {
208
- let best = null;
209
- for (const m of candidates) {
210
- const v = vecs.get(m.id);
211
- if (!v) continue; // 无向量的既有行不参与比对(无信号 = 不判定)
212
- const sim = cosineSimilarity(probe, v);
213
- if (sim >= DOCUMENT_MIN_SIM && (!best || sim > best.sim)) best = { m, sim };
214
- }
215
- if (best) {
216
- throw new Error(
217
- `registerDocument: summary is a near duplicate (C2 >= ${DOCUMENT_MIN_SIM}, sim ${best.sim.toFixed(3)}) ` +
218
- `of document row ${best.m.id} "${best.m.title}" — update that row via memory_update, or archive it ` +
219
- `before registering a separate document`
220
- );
221
- }
222
- }
223
- }
224
- } catch (e) {
225
- // 近重复拒绝是业务结果,原样上抛;embedder 故障吞掉回落(只做路径/标题层)。
226
- if (e instanceof Error && e.message.startsWith("registerDocument:")) throw e;
227
- }
228
- }
229
-
230
- // --- 4. 落库 + supersede 记账(同事务)----------------------------------
231
- // 新摘要行:库里只存 summary + doc_path + evidence(+ scope 标注),旧文件
232
- // 零触碰。supersede:旧行 content_history 存旧摘要(source=superseded)、
233
- // 正文追加指向新行的注记、归档(降可及性可复活,无物理删除)。
234
- const userTags = (Array.isArray(payload?.tags) ? payload.tags : [])
235
- .map((t) => String(t ?? "").trim())
236
- .filter(Boolean);
237
- const tags = [...new Set([...userTags, ...(dropped.length ? ["evidence_degraded"] : [])])];
238
- const result = transaction(() => {
239
- // saveDocument 是存储层唯一 document 铸造口(通用 save/update/CAS 拒绝
240
- // document——CodeRabbit 复核 #8/#882):registerDocument 传入的 doc_path
241
- // 已过归一化与文件校验。
242
- const created = store.saveDocument({
243
- type: "document",
244
- title,
245
- content: summary,
246
- tags,
247
- importance: Number.isInteger(payload?.importance)
248
- ? Math.min(5, Math.max(1, payload.importance))
249
- : 3,
250
- source: payload?.source ?? "tool",
251
- evidence: kept,
252
- doc_path: expanded,
253
- // sensitivity 进 supersede 匹配键(scopeKeyOf 第三维):不落库的话,
254
- // 首注册带 sensitivity、重注册同键会在匹配时判成两 scope,旧版漏检。
255
- ...(payload?.sensitivity !== undefined ? { sensitivity: payload.sensitivity } : {}),
256
- ...(payload?.agent_scope !== undefined
257
- ? { agent_scope: payload.agent_scope, agent_scope_source: payload.agent_scope_source }
258
- : {}),
259
- ...(payload?.workspace_scope !== undefined
260
- ? { workspace_scope: payload.workspace_scope, workspace_scope_source: payload.workspace_scope_source }
261
- : {})
262
- });
263
- let superseded;
264
- const loser = explicitTarget ? store.getById(explicitTarget.id) : null;
265
- if (loser && !loser.archived) {
266
- // 这里不挂 isImportedRow:loser 只会是 document 行(scopeMatched 全来自
267
- // store.list({ type: "document" })),而 document 行永远不可能是外来行——
268
- // 分享包格式排除 document,存储层的 document 只由 saveDocument 从这里铸造。
269
- // 加谓词是空转,还会让下一个读者误以为 document 行有外来形态。
270
- store.update(loser.id, {
271
- content: `${loser.content}${supersededByPointer(created.id)}`,
272
- content_history: pushContentHistory(loser, "superseded")
273
- });
274
- store.setArchived(loser.id, true);
275
- superseded = store.getById(loser.id);
276
- }
277
- // #275 拍板 5(#230 验收口径的补丁):升格吸收的 evidence 行随之退出活跃面——
278
- // 一次吸收 20 条,库里就是「21 行不是 1 行」。四条口径:①与本行同一事务(要么
279
- // 都成、要么都不成);②pinned 永不自动归档——升格不能绕过它,而 pinned 有**两个**
280
- // 判据:显式钉住的行(pinned_memories,#593 起为唯一正源)与 #249 的逐字保真池
281
- // 类型(constraint / preference);③自有生命周期的类型(document / summary,见
282
- // ABSORB_EXEMPT_TYPES)不吸收,吸收的是原子条;④只翻标志位、内容与审计全留
283
- // (可恢复),opt-out 走 archiveEvidence。
284
- // pinnedTypes 拿不到就不做这一步:宁可少做,也不能把保真池当普通行收走。
285
- // 外来行不吸收(spec §15 一,车道 C8):这条旁路不经任何 service.all() 行池,
286
- // 车道 B 的九池静态锁看不见它;证据 id 由调用方给,循环里的 setArchived 是
287
- // 机器发起的批量归档——正是「本库模型不改写别人的记忆」要禁的动作类。
288
- // 引用本身照旧落库(文档合法地为它背书),只是不替别人撤下活跃面,
289
- // 跳过条数走 evidence_skipped 报出来,不做静默跳过。
290
- let evidenceArchived = 0;
291
- let evidenceSkipped = 0;
292
- if (archiveEvidence && pinnedTypes && typeof pinnedTypes.has === "function") {
293
- for (const id of kept) {
294
- // 事务内重读:期间被别的进程归档/遗忘的行不重复计数,也不误伤 pinned。
295
- const row = store.getById(id);
296
- if (!row || row.archived || row.forgotten) continue;
297
- if (isImportedRow(row)) { evidenceSkipped += 1; continue; }
298
- // #593:显式 pin(pinned_memories)与类型保真池是**两个**判据。上一版只问
299
- // pinnedTypes.has(row.type),等于把「被钉住的 pattern / decision」当普通行
300
- // 收走——用户钉过的东西在文档升格时被静默归档,正是「钉着 ≠ 会注入」的
301
- // 反面:这里连「钉着」都没保住。store.isPinned 缺席时退回类型口径(旧行为)。
302
- const explicitlyPinned = typeof store.isPinned === "function" && store.isPinned(row.id);
303
- if (explicitlyPinned || pinnedTypes.has(row.type) || ABSORB_EXEMPT_TYPES.has(row.type)) continue;
304
- store.setArchived(id, true);
305
- evidenceArchived += 1;
306
- }
307
- }
308
- return { created, superseded, evidenceArchived, evidenceSkipped };
309
- });
310
- finalize(result.superseded ? [result.created, result.superseded] : [result.created]);
311
- return {
312
- action: result.superseded ? "superseded" : "created",
313
- memory: result.created,
314
- ...(result.superseded ? { superseded: result.superseded } : {}),
315
- evidence_kept: kept.length,
316
- evidence_dropped: dropped.length,
317
- evidence_archived: result.evidenceArchived,
318
- evidence_skipped: result.evidenceSkipped,
319
- degraded: dropped.length > 0
320
- };
321
- };
322
- }
1
+ // dsh-mneme/src/document.js
2
+ // document 型记忆注册(issue #230,口径基线 = #164 设计稿评审线):agent 产
3
+ // 长文档的指针行铸造口。写入权分离——全文归 agent(管线对文件零读零写零改),
4
+ // 管线只做四件事:注册校验(文件存在 + 路径合法 + evidence 求交)、摘要 +
5
+ // doc_path 落库(库里只存这三样)、C2 vector 档比对去重、supersede 记账
6
+ // (出新版 = 新摘要行 supersede 旧行,旧文件不删,content_history 可追溯)。
7
+ //
8
+ // 独立成模块(AGENTS.md 尺寸约定,同 recallStats 先例):service.js 已过
9
+ // 2000 行参考线,这里只依赖注入 service 内部件,barrel 出口在 service.js,
10
+ // 调用方零改动。防绕过三层兜底:service 两条业务守卫(saveWithDedupe /
11
+ // updateMemory)+ 存储层唯一铸造口(store.saveDocument,通用 save/update/
12
+ // CAS 整类拒绝 document)——注册是唯一铸造口。
13
+
14
+ import { statSync } from "node:fs";
15
+ import { homedir } from "node:os";
16
+ import { isAbsolute, resolve, sep } from "node:path";
17
+ import { scopeKeyOf } from "./scope.js";
18
+ import { cosineSimilarity } from "./dream/clustering.js";
19
+ import { isImportedRow } from "./memory-source.js";
20
+
21
+ // C2 vector 档阈值(#164 拍板 0.92,与 findSessionDuplicate 的 vector 档同源)。
22
+ // 刻意不做 24h 时间窗:那个窗是会话去重的「近邻界」(findSessionDuplicate 注释),
23
+ // 不是相似度语义本身;document 版本更新天然跨天/跨周,套窗会把一周后重注册的
24
+ // 新版判成「无近重复」,留下两行活跃摘要漂移——supersede 探测必须全时段。
25
+ const DOCUMENT_MIN_SIM = 0.92;
26
+ const CANDIDATE_LIMIT = 200;
27
+ const DOCUMENT_DIR_NAME = "documents";
28
+
29
+ /**
30
+ * 解析 managed 文档目录(#296 第二批)。`configured` 为空 = 跟随 memoryDir 的
31
+ * `<memoryDir>/documents/`;`~` / `~/` / `~\` 展开到用户 home;相对路径落在
32
+ * memoryDir 下(本键自己的规则:memoryDir 的相对路径原样留着走 cwd 语义);
33
+ * 绝对路径归一化后原样用。纯函数,路径解析集中在 index.js 调用一次。
34
+ */
35
+ export function resolveDocumentDir(memoryDir, configured = "") {
36
+ const raw = String(configured ?? "").trim();
37
+ if (!raw) return resolve(memoryDir, DOCUMENT_DIR_NAME);
38
+ if (raw === "~" || raw.startsWith("~/") || raw.startsWith("~\\")) {
39
+ return resolve(homedir(), raw.slice(2));
40
+ }
41
+ return isAbsolute(raw) ? resolve(raw) : resolve(memoryDir, raw);
42
+ }
43
+
44
+ /**
45
+ * 这个路径是不是落在 managed 目录里(`documentDir` 内 = mneme 管的树,`index.md`
46
+ * 与镜像 md 同权;之外 = 只登记指针行、正文一个字节都不碰)。比较前两边都归一化,
47
+ * Windows 上大小写不敏感(文件系统就是),否则 `C:\a\documents\x.md` 与
48
+ * `c:\A\Documents\x.md` 会被判成两类。
49
+ */
50
+ export function isManagedDocumentPath(dir, path) {
51
+ if (!dir || !path) return false;
52
+ const norm = (p) => (process.platform === "win32" ? String(p).toLowerCase() : String(p));
53
+ const base = norm(resolve(dir));
54
+ const target = norm(resolve(path));
55
+ const prefix = base.endsWith(sep) ? base : base + sep;
56
+ return target === base || target.startsWith(prefix);
57
+ }
58
+
59
+ // 追加到被取代行正文的指针注记。刻意不带语言分支:id 是跨语言稳定键(用户
60
+ // 对 tag/章节名建议用英文同理——指针性内容不翻译);可追溯链 = 注记里的新行
61
+ // id + 旧行 content_history 里的旧摘要(source=superseded)。
62
+ const supersededByPointer = (id) => `\n\n[superseded by ${id}]`;
63
+
64
+ // 升格吸收不碰的类型(#275 拍板 5 的边界):吸收的对象是「原子条」,而下面这两种
65
+ // 行各有自己的生命周期,被归档会各自留下第二行——
66
+ // document:本体是磁盘上的文件。归档它等于替用户撤下外档;而且 supersede 探测
67
+ // 只看活跃行(store.list 默认排除归档),同一 path 下次注册会在旧行还挂着
68
+ // 「新版本」语义时再铸一行,指针注记与 content_history 记账也走不到。
69
+ // summary:dream 总览与叙述按 source 身份去重、并按注入档位常驻。归档它会让下一
70
+ // 次做梦把它当不存在(候选集同样只取活跃行),于是库里多出一行同源总览。
71
+ // 口径是保守方向的:拿不准的行一律不吸收——少做一步没有代价,收错一步是内容事故。
72
+ const ABSORB_EXEMPT_TYPES = new Set(["document", "summary"]);
73
+
74
+ /**
75
+ * Build the document registrar. Injected deps are service.js closure members
76
+ * (store / embedQuery / transaction / finalize) so this module stays free of
77
+ * service-internal wiring; `finalize` is the single write epilogue (mirror
78
+ * sync + notify + re-embed) so callers never duplicate it.
79
+ *
80
+ * `pinnedTypes` 是注入的 #249 逐字保真池(service.js 的 PINNED_MEMORY_TYPES):
81
+ * 这里不 import 它在 service.js 里,是因为 service.js 反向 import 本模块——
82
+ * 方向反了会成环。
83
+ *
84
+ * @returns {(payload: object, opts?: object) => Promise<object>}
85
+ * `{ action: "created"|"superseded", memory, superseded?, evidence_kept,
86
+ * evidence_dropped, evidence_archived, evidence_skipped, degraded }`
87
+ * @throws flag 关 / 路径非法或文件缺失 / title 或 summary 为空 / evidence 全部
88
+ * 捏造 / 仅 vector 近重复(疑似重复外档,交 agent 裁决而不是静默二选一)。
89
+ */
90
+ export function createDocumentRegistrar({ store, config, embedQuery, pushContentHistory, transaction, finalize, pinnedTypes }) {
91
+ return async function registerDocument(payload, { hiddenEvidenceIds = [], archiveEvidence = true } = {}) {
92
+ // opt-in 总闸(#230 对齐 #228 形态):关 = 注册入口整体不存在,错误信息
93
+ // 指回配置键,agent 能准确转告用户去开。
94
+ if (config?.documentMemoryEnabled !== true) {
95
+ throw new Error("document memory is disabled (documentMemoryEnabled)");
96
+ }
97
+
98
+ // --- 1. 路径合法 + 文件存在 --------------------------------------------
99
+ // 只认展开 ~ 后的绝对路径:相对路径依赖进程 cwd,注册时的 cwd 与读取时
100
+ // 的 cwd 不保证一致,指针必须自带完整定位。校验存在 + 常规文件 + 非空——
101
+ // 空文件/被截断成 0 字节是「文字结构意外破坏」的最早信号,注册时拦住
102
+ // 而不是落一条死指针(回退路径:agent 修好文件后重注册,同 path/title
103
+ // 自动 supersede 旧行)。
104
+ const rawPath = String(payload?.path ?? "").trim();
105
+ if (!rawPath) throw new Error("registerDocument: path is required");
106
+ // 只展开 ~ / ~/(~user 形式拒收为普通相对路径走 isAbsolute 拦截——不猜测
107
+ // 其他用户的 home,错展开比报错更糟)。
108
+ const isTilde = rawPath === "~" || rawPath.startsWith("~/") || rawPath.startsWith("~\\");
109
+ const absolute = isTilde ? resolve(homedir(), rawPath.slice(2)) : rawPath;
110
+ if (!isAbsolute(absolute)) {
111
+ throw new Error(`registerDocument: path must be absolute (got "${rawPath}")`);
112
+ }
113
+ // 所有绝对路径统一 resolve 归一化(. / .. / 重复分隔符)——归一化不做的话,
114
+ // D:\a\.\b.md 与 D:\a\b.md 会被判成两个路径,同文件绕过 same-path
115
+ // supersede,留下双活跃指针。realpathSync 刻意不用:符号链接合并会改写
116
+ // agent 提交的路径字面量,指针的可读性比追符号链更重要。
117
+ const expanded = resolve(absolute);
118
+ let stat = null;
119
+ try { stat = statSync(expanded); } catch { /* missing/unreadable → null */ }
120
+ if (!stat?.isFile() || stat.size <= 0) {
121
+ throw new Error(`registerDocument: document file not found or empty: ${expanded}`);
122
+ }
123
+
124
+ // --- 2. evidence 求交(sleep.js 扫描集求交同款语义)---------------------
125
+ // 引用的原子条必须真实存在且未归档(归档行已被降级,不再为新鲜 document
126
+ // 背书)。红线 4 宽容形态:合法子集落库 + evidence_degraded 系统标记;
127
+ // 提供了 evidence 但全军覆没 = 捏造信号,整单拒绝,agent 修正后重试。
128
+ const wanted = [...new Set(
129
+ (Array.isArray(payload?.evidence) ? payload.evidence : [])
130
+ .map((id) => String(id ?? "").trim())
131
+ .filter(Boolean)
132
+ )];
133
+ // strictScope(tools 层传入,#170 复核项 4 同款):调用者 scope 看不见的
134
+ // 行按「不存在」处理——不得为跨 scope id 建立引用(存在性泄漏),与
135
+ // unknown/archived 同落 dropped,evidence_kept/dropped 计数保持诚实。
136
+ const hidden = new Set((Array.isArray(hiddenEvidenceIds) ? hiddenEvidenceIds : []).map((id) => String(id)));
137
+ const kept = [];
138
+ const dropped = [];
139
+ // 存在但已归档的引用另记一份:它可能是被上一版文档升格吸收走的行(见下方
140
+ // supersede 目标探测后的「认回」),不能一律按「不可用」处理。
141
+ const archivedRefs = new Set();
142
+ for (const id of wanted) {
143
+ const row = hidden.has(id) ? null : store.getById(id);
144
+ if (row?.archived) archivedRefs.add(id);
145
+ (row && !row.archived ? kept : dropped).push(id);
146
+ }
147
+
148
+ const title = String(payload?.title ?? "").trim();
149
+ if (!title) throw new Error("registerDocument: title is required");
150
+ const summary = String(payload?.summary ?? "").trim();
151
+ if (!summary) throw new Error("registerDocument: summary is required");
152
+
153
+ // --- 3. supersede 目标探测(三层)---------------------------------------
154
+ // 同 doc_path / 同标题 = agent 显式的「出新版」意图 → supersede;仅 vector
155
+ // 近重复而路径标题都不同 = 疑似重复外档 → 拒绝并指路。C2×C1 硬线:比对
156
+ // 不得越过矛盾裁决替 agent 二选一——改既有行还是归档后重注册,判断权留在
157
+ // agent 手里。去重键带 scope(scopeKeyOf):跨作用域永不互判(防泄漏,与
158
+ // saveWithDedupe 的 scopeMatches 同口径)。
159
+ const scopeMatches = (m) =>
160
+ scopeKeyOf(m.agent_scope) === scopeKeyOf(payload?.agent_scope) &&
161
+ scopeKeyOf(m.workspace_scope) === scopeKeyOf(payload?.workspace_scope) &&
162
+ scopeKeyOf(m.sensitivity) === scopeKeyOf(payload?.sensitivity);
163
+ const samePath = (a, b) => {
164
+ if (!a || !b) return false;
165
+ // Windows 文件系统大小写不敏感:比较前归一,否则 C:\a.md 与 c:\A.md
166
+ // 会被判成两个版本。
167
+ const norm = (p) => (process.platform === "win32" ? String(p).toLowerCase() : String(p));
168
+ return norm(a) === norm(b);
169
+ };
170
+ // 精确层(同路径/同标题)必须全量扫描、不允许窗口截断:>200 行活跃
171
+ // document 时,窗口外的同路径旧版漏检会留下双活跃版本——supersede 判定
172
+ // 是正确性要求,不是性能优化对象。vector 档比对保留 CANDIDATE_LIMIT
173
+ // (getEmbeddings + 两两 cosine 才是有界对象)。
174
+ const scopeMatched = store
175
+ .list({ type: "document", limit: null })
176
+ .filter((m) => scopeMatches(m));
177
+ const explicitTarget = scopeMatched.find((m) => samePath(m.doc_path, expanded))
178
+ ?? scopeMatched.find((m) => m.title.trim() === title);
179
+ // 升格吸收过的引用认回(#275 拍板 5 的配套):evidence 行在文档注册成功后就翻了
180
+ // archived,于是「同一份文档出新版、evidence 照旧」这一最常规的路径会整批落在
181
+ // dropped——那会被下面的捏造判据误报成「证据都是编的」。口径:已归档行仍可为
182
+ // **吸收它的那份文档**背书(loser.evidence 就是它吸收走的名单),不为别的文档
183
+ // 背书;捏造与跨 scope 照旧拒绝。
184
+ if (explicitTarget) {
185
+ const own = new Set(explicitTarget.evidence ?? []);
186
+ for (const id of archivedRefs) {
187
+ if (!own.has(id)) continue;
188
+ const at = dropped.indexOf(id);
189
+ if (at >= 0) dropped.splice(at, 1);
190
+ kept.push(id);
191
+ }
192
+ }
193
+ if (wanted.length > 0 && kept.length === 0) {
194
+ throw new Error(
195
+ `registerDocument: all ${wanted.length} evidence ids are unknown, archived or out of scope — fabricated evidence is rejected`
196
+ );
197
+ }
198
+ if (!explicitTarget) {
199
+ // vector 档:embedder 不可用/向量缺失一律跳过——去重是增强不是写入依赖
200
+ // (findSessionDuplicate 同原则)。probe 用 title+summary,与行向量
201
+ // (同两段拼接 embed)同构。
202
+ const candidates = scopeMatched.slice(0, CANDIDATE_LIMIT);
203
+ try {
204
+ const vecs = store.getEmbeddings(candidates.map((m) => m.id));
205
+ if (vecs.size) {
206
+ const probe = await embedQuery([title, summary].filter(Boolean).join("\n"));
207
+ if (probe) {
208
+ let best = null;
209
+ for (const m of candidates) {
210
+ const v = vecs.get(m.id);
211
+ if (!v) continue; // 无向量的既有行不参与比对(无信号 = 不判定)
212
+ const sim = cosineSimilarity(probe, v);
213
+ if (sim >= DOCUMENT_MIN_SIM && (!best || sim > best.sim)) best = { m, sim };
214
+ }
215
+ if (best) {
216
+ throw new Error(
217
+ `registerDocument: summary is a near duplicate (C2 >= ${DOCUMENT_MIN_SIM}, sim ${best.sim.toFixed(3)}) ` +
218
+ `of document row ${best.m.id} "${best.m.title}" — update that row via memory_update, or archive it ` +
219
+ `before registering a separate document`
220
+ );
221
+ }
222
+ }
223
+ }
224
+ } catch (e) {
225
+ // 近重复拒绝是业务结果,原样上抛;embedder 故障吞掉回落(只做路径/标题层)。
226
+ if (e instanceof Error && e.message.startsWith("registerDocument:")) throw e;
227
+ }
228
+ }
229
+
230
+ // --- 4. 落库 + supersede 记账(同事务)----------------------------------
231
+ // 新摘要行:库里只存 summary + doc_path + evidence(+ scope 标注),旧文件
232
+ // 零触碰。supersede:旧行 content_history 存旧摘要(source=superseded)、
233
+ // 正文追加指向新行的注记、归档(降可及性可复活,无物理删除)。
234
+ const userTags = (Array.isArray(payload?.tags) ? payload.tags : [])
235
+ .map((t) => String(t ?? "").trim())
236
+ .filter(Boolean);
237
+ const tags = [...new Set([...userTags, ...(dropped.length ? ["evidence_degraded"] : [])])];
238
+ const result = transaction(() => {
239
+ // saveDocument 是存储层唯一 document 铸造口(通用 save/update/CAS 拒绝
240
+ // document——CodeRabbit 复核 #8/#882):registerDocument 传入的 doc_path
241
+ // 已过归一化与文件校验。
242
+ const created = store.saveDocument({
243
+ type: "document",
244
+ title,
245
+ content: summary,
246
+ tags,
247
+ importance: Number.isInteger(payload?.importance)
248
+ ? Math.min(5, Math.max(1, payload.importance))
249
+ : 3,
250
+ source: payload?.source ?? "tool",
251
+ evidence: kept,
252
+ doc_path: expanded,
253
+ // sensitivity 进 supersede 匹配键(scopeKeyOf 第三维):不落库的话,
254
+ // 首注册带 sensitivity、重注册同键会在匹配时判成两 scope,旧版漏检。
255
+ ...(payload?.sensitivity !== undefined ? { sensitivity: payload.sensitivity } : {}),
256
+ ...(payload?.agent_scope !== undefined
257
+ ? { agent_scope: payload.agent_scope, agent_scope_source: payload.agent_scope_source }
258
+ : {}),
259
+ ...(payload?.workspace_scope !== undefined
260
+ ? { workspace_scope: payload.workspace_scope, workspace_scope_source: payload.workspace_scope_source }
261
+ : {})
262
+ });
263
+ let superseded;
264
+ const loser = explicitTarget ? store.getById(explicitTarget.id) : null;
265
+ if (loser && !loser.archived) {
266
+ // 这里不挂 isImportedRow:loser 只会是 document 行(scopeMatched 全来自
267
+ // store.list({ type: "document" })),而 document 行永远不可能是外来行——
268
+ // 分享包格式排除 document,存储层的 document 只由 saveDocument 从这里铸造。
269
+ // 加谓词是空转,还会让下一个读者误以为 document 行有外来形态。
270
+ store.update(loser.id, {
271
+ content: `${loser.content}${supersededByPointer(created.id)}`,
272
+ content_history: pushContentHistory(loser, "superseded")
273
+ });
274
+ store.setArchived(loser.id, true);
275
+ superseded = store.getById(loser.id);
276
+ }
277
+ // #275 拍板 5(#230 验收口径的补丁):升格吸收的 evidence 行随之退出活跃面——
278
+ // 一次吸收 20 条,库里就是「21 行不是 1 行」。四条口径:①与本行同一事务(要么
279
+ // 都成、要么都不成);②pinned 永不自动归档——升格不能绕过它,而 pinned 有**两个**
280
+ // 判据:显式钉住的行(pinned_memories,#593 起为唯一正源)与 #249 的逐字保真池
281
+ // 类型(constraint / preference);③自有生命周期的类型(document / summary,见
282
+ // ABSORB_EXEMPT_TYPES)不吸收,吸收的是原子条;④只翻标志位、内容与审计全留
283
+ // (可恢复),opt-out 走 archiveEvidence。
284
+ // pinnedTypes 拿不到就不做这一步:宁可少做,也不能把保真池当普通行收走。
285
+ // 外来行不吸收(spec §15 一,车道 C8):这条旁路不经任何 service.all() 行池,
286
+ // 车道 B 的九池静态锁看不见它;证据 id 由调用方给,循环里的 setArchived 是
287
+ // 机器发起的批量归档——正是「本库模型不改写别人的记忆」要禁的动作类。
288
+ // 引用本身照旧落库(文档合法地为它背书),只是不替别人撤下活跃面,
289
+ // 跳过条数走 evidence_skipped 报出来,不做静默跳过。
290
+ let evidenceArchived = 0;
291
+ let evidenceSkipped = 0;
292
+ if (archiveEvidence && pinnedTypes && typeof pinnedTypes.has === "function") {
293
+ for (const id of kept) {
294
+ // 事务内重读:期间被别的进程归档/遗忘的行不重复计数,也不误伤 pinned。
295
+ const row = store.getById(id);
296
+ if (!row || row.archived || row.forgotten) continue;
297
+ if (isImportedRow(row)) { evidenceSkipped += 1; continue; }
298
+ // #593:显式 pin(pinned_memories)与类型保真池是**两个**判据。上一版只问
299
+ // pinnedTypes.has(row.type),等于把「被钉住的 pattern / decision」当普通行
300
+ // 收走——用户钉过的东西在文档升格时被静默归档,正是「钉着 ≠ 会注入」的
301
+ // 反面:这里连「钉着」都没保住。store.isPinned 缺席时退回类型口径(旧行为)。
302
+ const explicitlyPinned = typeof store.isPinned === "function" && store.isPinned(row.id);
303
+ if (explicitlyPinned || pinnedTypes.has(row.type) || ABSORB_EXEMPT_TYPES.has(row.type)) continue;
304
+ store.setArchived(id, true);
305
+ evidenceArchived += 1;
306
+ }
307
+ }
308
+ return { created, superseded, evidenceArchived, evidenceSkipped };
309
+ });
310
+ finalize(result.superseded ? [result.created, result.superseded] : [result.created]);
311
+ return {
312
+ action: result.superseded ? "superseded" : "created",
313
+ memory: result.created,
314
+ ...(result.superseded ? { superseded: result.superseded } : {}),
315
+ evidence_kept: kept.length,
316
+ evidence_dropped: dropped.length,
317
+ evidence_archived: result.evidenceArchived,
318
+ evidence_skipped: result.evidenceSkipped,
319
+ degraded: dropped.length > 0
320
+ };
321
+ };
322
+ }