@modusensus/dsh-mneme 0.13.0 → 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 -828
  3. package/assets/icon.svg +3 -3
  4. package/bin/cli.mjs +964 -964
  5. package/bin/dsh-mneme-choro.mjs +262 -254
  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 -402
  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 -3394
  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 -13436
  33. package/lib/commands.js +95 -95
  34. package/lib/config-keys.js +144 -144
  35. package/lib/config.js +1361 -1317
  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 -907
  52. package/lib/dream/narratives.js +44 -44
  53. package/lib/dream/sleep.js +2027 -1887
  54. package/lib/dream/triage.js +139 -0
  55. package/lib/dream.js +1879 -1764
  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 -107
  72. package/lib/image-describe.js +158 -0
  73. package/lib/index.js +852 -822
  74. package/lib/inject.js +656 -639
  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 -584
  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 -3196
  124. package/lib/session-gate.js +91 -91
  125. package/lib/session-usage.js +350 -350
  126. package/lib/settings.js +827 -802
  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 -5662
  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 -0
  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 -474
  142. package/lib/wiki/render.js +261 -187
  143. package/lib/wiki/scan.js +371 -371
  144. package/lib/wiki/t0.js +500 -463
  145. package/lib/wiki/t1-jobs.js +232 -232
  146. package/lib/wiki/t1.js +609 -502
  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 +15 -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 -204
  162. package/scripts/release-notes.mjs +164 -161
  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 -3394
  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 -1317
  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 -907
  194. package/src/dream/narratives.js +44 -44
  195. package/src/dream/sleep.js +2027 -1887
  196. package/src/dream/triage.js +139 -0
  197. package/src/dream.js +1879 -1764
  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 -107
  214. package/src/image-describe.js +158 -0
  215. package/src/index.js +852 -822
  216. package/src/inject.js +656 -639
  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 -584
  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 -3196
  266. package/src/session-gate.js +91 -91
  267. package/src/session-usage.js +350 -350
  268. package/src/settings.js +827 -802
  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 -5662
  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 -0
  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 -474
  284. package/src/wiki/render.js +261 -187
  285. package/src/wiki/scan.js +371 -371
  286. package/src/wiki/t0.js +500 -463
  287. package/src/wiki/t1-jobs.js +232 -232
  288. package/src/wiki/t1.js +609 -502
  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 -2988
  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 -4184
  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 -0
  318. package/test/conflict-ledger.test.js +242 -0
  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 -320
  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 -0
  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 -209
  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 -211
  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 -106
  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 -494
  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 -0
  407. package/test/panel-tokens.test.js +245 -0
  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 -136
  453. package/test/release-notes.test.js +265 -240
  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 -178
  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 -1418
  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 -627
  529. package/test/wiki-claims.test.js +194 -0
  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 -0
  535. package/test/wiki-queries.test.js +398 -277
  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 -380
  539. package/test/wiki-t1-jobs.test.js +125 -125
  540. package/test/wiki-t1.test.js +460 -235
  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
@@ -1,225 +1,225 @@
1
- // 车道 D 的面板静态锁。为什么是静态锁:lib/client.js 由宿主 __ModuleLoader__ 加载,
2
- // 本仓库没有可跑的宿主壳,渲染测试要拖进一份 React 测试栈才够得着那几行真代码。
3
- // 锁的是「形状」而不是「长得像」:每一条都对应一个**会伤人的回归形态**,
4
- // 注释里写清它防的是哪一种(AGENTS.md 纪律 8 对测试同样适用)。
5
- import test from "node:test";
6
- import assert from "node:assert/strict";
7
- import { readFileSync } from "node:fs";
8
- import { dirname, join } from "node:path";
9
- import { fileURLToPath } from "node:url";
10
-
11
- const here = dirname(fileURLToPath(import.meta.url));
12
- const clientSource = readFileSync(join(here, "..", "lib/client.js"), "utf8");
13
- const shareSource = readFileSync(join(here, "..", "src/share.js"), "utf8");
14
-
15
- // 分享弹层最要紧的一条:**看见 report 之前不给下载**(spec §6 的支点)。
16
- // 防的回归形态:有人为了「方便」把下载按钮改成常驻,或从别处(如列表工具栏)再开一条
17
- // 直接落盘的口子——那一步停顿就没了,随手全库外发重新变成一次点击。
18
- test("the share dialog gates the download on the report", () => {
19
- assert.ok(clientSource.includes('apiFetch("/api/dsh-mneme/share"'),
20
- "面板必须发这一枪(不许另造 fetch 绕过 apiFetch 的 token 注入)");
21
- assert.ok(clientSource.includes("const canDownload = !!rep && rep.included > 0;"),
22
- "下载按钮的唯一门:有报告且有内容。included 为 0 时对面吃的是 empty-pack(400),不该给一个必败的文件");
23
- assert.ok(clientSource.includes("canDownload && h(\"div\""),
24
- "下载那一行必须挂在 canDownload 上,而不是常驻(按钮本身在这样一个 div 里)");
25
- // 413 与 200 是两份形状:413 没有 memories,只有 report + 字节数 + hint。
26
- assert.ok(clientSource.includes("if (res.status === 413)"),
27
- "超水位要走 tooLarge 那一支,不能落到通用失败");
28
- assert.ok(clientSource.includes("new Blob([JSON.stringify(pack)]"),
29
- "落盘的必须是服务端给的那一份字节:重拼字段或 pretty-print 都会让「量过的包」与「发出去的包」不是同一坨");
30
- });
31
-
32
- // docs/SHARE.md「车道 D 三笔账」之一:included 只数实际发出的条数,够格进包的是
33
- // included + deduped。防的回归:有人把「合格条数」的标签挪到 included 上,读数是假的。
34
- test("the report separates what shipped from what qualified", () => {
35
- assert.ok(clientSource.includes("report.included + report.deduped"),
36
- "折叠掉的那几条必须加回去,两个数同屏");
37
- assert.ok(clientSource.includes('t("memory.explorer.shareDeduped")'),
38
- "deduped 那一行要真的有渲染点,不是只在字典里躺着");
39
- });
40
-
41
- // 跨文件锁:面板的六个跳过原因与八个可分享类型都只许是服务端那份的投影。
42
- // 防的回归:share.js 加/改一个 reason 键或一类可分享类型,面板静默少一格
43
- // ——她的报告上就有一类跳过是看不见的。
44
- test("the panel's skip-reason and type sets mirror src/share.js", () => {
45
- const serverReasons = [...shareSource.matchAll(/reason:\s*"([a-z_]+)"/g)].map((m) => m[1]);
46
- const uniqueReasons = [...new Set(serverReasons)];
47
- const panelReasons = /const SHARE_SKIP_REASONS = \[([^\]]*)\]/.exec(clientSource)[1]
48
- .split(",").map((s) => s.trim().replace(/"/g, "")).filter(Boolean);
49
- assert.deepEqual(panelReasons, uniqueReasons,
50
- "跳过原因集必须与 share.js 的判序一致(顺序也一致:报告按判序展示)");
51
-
52
- const serverTypes = /SHARE_ALLOWED_TYPES = new Set\(\[([^\]]*)\]/.exec(shareSource)[1]
53
- .split(",").map((s) => s.trim().replace(/"/g, "")).filter(Boolean);
54
- const panelTypes = /const SHARE_TYPE_OPTIONS = \[([^\]]*)\]/.exec(clientSource)[1]
55
- .split(",").map((s) => s.trim().replace(/"/g, "")).filter(Boolean);
56
- assert.deepEqual(panelTypes.slice().sort(), serverTypes.slice().sort(),
57
- "类型下拉只能列真能进包的那几类");
58
- });
59
-
60
- // 别名的尺:服务端 importedTagFor 把整串(含 from-)截到 IMPORTED_TAG_MAX。
61
- // 防的回归:服务端改这个常量而面板还拦着旧长度,她打的字在对面被静默吃掉。
62
- test("the alias input matches the server's tag budget", () => {
63
- const max = Number(/IMPORTED_TAG_MAX = (\d+)/.exec(shareSource)[1]);
64
- assert.ok(clientSource.includes(`maxLength: ${max - "from-".length}`),
65
- "输入框长度必须等于服务端留给别名的字符数");
66
- });
67
-
68
- // 文件名以服务端 Content-Disposition 为准(拼法只住在 src/share-http.js 的 shareFilename)。
69
- // 防的回归:面板自己抄一份「日期 + pack_id 前 8 位」的拼法,两处日后分叉。
70
- test("the pack filename comes from the response header, with a pack_id fallback", () => {
71
- assert.ok(clientSource.includes('res.headers.get("content-disposition")'));
72
- assert.ok(clientSource.includes("const dispositionName = (value) =>"));
73
- // 回落那一格只许出现一次前缀串,且不带日期拼法。
74
- assert.equal(clientSource.split("dsh-mneme-share-").length - 1, 1,
75
- "面板只许有一份包名前缀串(回落用),不许再拼第二套日期命名");
76
- assert.equal(/pack_id[^;\n]{0,48}slice\(\s*0\s*,\s*8\s*\)/.test(clientSource), false,
77
- "不许在 pack_id 后面截 8 位——那是 src/share-http.js 的 shareFilename 唯一的拼法");
78
- });
79
-
80
- // docs/SHARE.md「车道 D 三笔账」之二:干跑与真导入的答复同形状,区别只有
81
- // withDryRun 加的 dry_run 那一格。防的回归形态:界面按「我有没有发 confirm」推断写了
82
- // 没有——同一件事写两处,改一处就出现「其实没写、却说写了」。
83
- test("the receive dialog reads dry_run instead of inferring from its own button", () => {
84
- assert.ok(clientSource.includes('apiFetch("/api/dsh-mneme/receive"'),
85
- "入库只许走这一枪(apiFetch 带 token 注入)");
86
- assert.ok(clientSource.includes("post({ pack })"), "预览那一发不带 confirm——缺省即干跑");
87
- assert.ok(clientSource.includes("post({ pack, confirm: true })"),
88
- "真写的那一发必须带 confirm 这个真值,其他形状服务端都不认");
89
- assert.ok(clientSource.includes('d.dry_run === false ? "memory.explorer.receiveDidCreate"'),
90
- "「写进去了」这句只能由答复自己说——钉的是那个三元式本身,不是六个字面量都在");
91
- assert.ok(clientSource.includes("result.dry_run !== false"),
92
- "确认之后仍回干跑时要单独说「没有写库」,不许沉默,也不许挂在预览那一支上");
93
- assert.ok(clientSource.includes('"memory.explorer.receiveStillDry"'),
94
- "那一格要有自己的文案");
95
- // 第二个旋钮一概不认:收侧只认 confirm(spec §5——reclaim 的 body 里就带 dryRun,
96
- // 手熟的人会顺手发来,把它读成「要写」等于替她点头)。
97
- // **切片而不是全文**:本文件里 reclaim/VACUUM 那一发合法地发 `dryRun: false, confirm: true`,
98
- // 那是 reclaim 的形状不是 receive 的。全文反锁会一上来就红;谁把它「修」成全文锁,
99
- // 就等于给 reclaim 那颗键定罪。所以按函数名切区段,并且**两个锚点都要验**:
100
- // 结束锚点丢了的时候 indexOf 返回 -1,切片会一路长到文件尾而不是塌成空——
101
- // 那时候这条锁看似更严,实际悄悄开始管整个文件(而 ReceiveDialog 之后今天没有 dryRun,
102
- // 它会空过)。两头都钉住,锚点漂移只会红得明白。
103
- const receiveStart = clientSource.indexOf("function ReceiveDialog(");
104
- const receiveEnd = clientSource.indexOf("// #386 第 5 条", receiveStart);
105
- assert.ok(receiveStart > 0 && receiveEnd > receiveStart,
106
- "ReceiveDialog 的起止锚点都要在,且顺序对——否则这条锁在管的东西和它说的不是一件");
107
- const receiveRegion = clientSource.slice(receiveStart, receiveEnd);
108
- assert.ok(receiveRegion.length > 800, "ReceiveDialog 的区段短得不像话,先修锚再谈锁");
109
- assert.equal(receiveRegion.includes("dryRun"), false,
110
- "收侧不许发 dryRun——唯一的入口是 confirm");
111
- });
112
-
113
- // 三笔账之三:title_conflicts 是 advisory 读数,只对「本轮真正会新建的那些」判过,
114
- // 重吃同一个包时恒空。防的回归:文案写成「现在没有冲突」。
115
- test("the receive preview keeps the advisory reading honest", () => {
116
- assert.ok(clientSource.includes("d.title_conflicts"), "撞名清单要显示");
117
- assert.ok(clientSource.includes('"memory.explorer.receiveConflicts"'));
118
- // 钉的是**文案**而不是注释:解释性的话必须能写「这不等于没有冲突」来提醒读者,
119
- // 连注释一起 ban 等于把警告本身禁掉(这一轮就撞上了:新注释里的「没有撞名」先红的是这条锁)。
120
- // 真正会伤人的是屏上那一句,所以只扫非注释行。
121
- const copyLines = clientSource.split("\n").filter((line) => !/^\s*\/\//.test(line)).join("\n");
122
- assert.equal(/现在没有冲突|没有撞名|无冲突|no conflicts|no title clashes/i.test(copyLines), false,
123
- "不许出现「没有冲突」这类绝对断言(清单为空只说明本轮没有新的要报)");
124
- // 先预览才允许确认:干跑是必经的一站,不是可选装饰。
125
- assert.ok(clientSource.includes("disabled: busy || !preview"),
126
- "确认按钮在拿到预览之前必须 disabled");
127
- // advisory 读数的分母要说出来,否则「清单为空」与「什么也没比」在屏上长一个样。
128
- // 注意它是**包内条数**而不是「比过了几条」,所以文案只许说「包内 N 条」。
129
- assert.ok(clientSource.includes("d.conflicts_checked"),
130
- "要把服务端给的包内条数显示出来");
131
- assert.ok(clientSource.includes('t("memory.explorer.receivePackCount")'),
132
- "那一行要有自己的渲染点,不是只在字典里躺着");
133
- });
134
-
135
- // D3 评审裁定补的锁:两种「按了没反应」的形态。
136
- // a) 200 但正文读不出——曾经一路 return null,调用方把它读成「屏上已有提示」,
137
- // 于是确认那一发悄悄没走完,而上一块的「将新建 N 条」还留在屏上;
138
- // b) 预览还在飞的时候又能选一个文件——飞回来的数字说的是前一个包,确认写的却是后一个。
139
- test("the receive dialog never goes silent, and never lets a superseded preview drive the write", () => {
140
- assert.ok(clientSource.includes('if (!d || typeof d !== "object") {'),
141
- "200 但没有可读正文也要 setErr,不许 return null 装成「已经处理过了」");
142
- assert.ok(clientSource.includes("disabled: busy, onClick: () => fileRef.current?.click()"),
143
- "预览/确认在飞的时候不许再选文件(那一块数字属于上一个包)");
144
- assert.ok(clientSource.includes('typeof parsed !== "object"'),
145
- "解得出 JSON 但不是对象,是另一种失败,得有它自己的一句话");
146
- assert.ok(clientSource.includes('e.target.value = ""'),
147
- "选完清空控件值:否则改过的同一个文件再选一次不发 change,面板拿着旧包预览");
148
- });
149
-
150
- // 外来区三件:筛选、来源徽标、整包退出。锁的是「每件事都挂在一个服务端真值上」,
151
- // 而不是挂在一个界面自己造出来的判断上。
152
- test("the foreign filter is a browse-state server filter and stays inert while searching", () => {
153
- assert.ok(clientSource.includes('foreignOnly ? "&source=imported" : ""'),
154
- "浏览态交给服务端筛:分页与 total 同口径,本地筛会切掉尾巴");
155
- // **搜索态不做本地过滤**(本轮扫描改掉的裁定,不是漏做):/search 那张 DTO 的 source 是
156
- // 召回渠道(keyword/vector/bm25,src/service.js:939),行的 provenance 被盖掉,「这条是不是
157
- // 外来的」在这张 DTO 上压根读不出来。宁可让 chip 在搜索态明确失效并说清原因,也不要按一个
158
- // 假信号筛——test/share-foreign-rows.test.js 的第三条用例锁着这个事实。
159
- assert.ok(clientSource.includes("disabled: !!query.trim() && !foreignOnly"),
160
- "有搜索词时不许**开启**外来筛选,但任何时候都许关掉——只给 disabled 会把她锁在一个她解不开的状态里");
161
- assert.ok(clientSource.includes('"memory.explorer.filterForeignSearchHint"'),
162
- "失效要有自己的文案,不能只靠 disabled 让人猜");
163
- assert.equal(clientSource.includes('(!foreignOnly || m.source'), false,
164
- "不许退回「搜索态本地按 source 筛」那条假信号");
165
- // 服务端不吃 source 这件事,不许用「给 search 的 URL 也拼一个键」的方式绕过去:
166
- // 那条路由压根不读它(src/api.js 的 search handler 里没有 source 这一档),拼上去
167
- // 只会造出一个「看起来筛过了、其实没有」的形状。全文件只许有一处拼这个参数。
168
- assert.equal(clientSource.split("&source=imported").length - 1, 1,
169
- "只有浏览态那一条 URL 会拼 source=imported");
170
- });
171
-
172
- // 来源徽标与撤销都按服务端给的真值门控:徽标认 source + from- 标签,撤销认 pack_id。
173
- test("the foreign badge and the withdraw action gate on server truth, not on guesswork", () => {
174
- assert.ok(clientSource.includes('m.source === "imported"'),
175
- "徽标先认 source,再认标签:人手工打的 from-* 标签不算外来行");
176
- assert.ok(clientSource.includes('startsWith("from-")'),
177
- "别名读数就是 importedTagFor 打的那条标签,面板不另找地方读别名");
178
- assert.ok(clientSource.includes("memory.share_pack_id &&"),
179
- "撤销按钮按包号自门控:/search 的 DTO 不带这一格,那种行按不了(已知代价,docs/SHARE.md 记着)");
180
- assert.ok(clientSource.includes('apiFetch("/api/dsh-mneme/receive/undo"'),
181
- "整包退出只许走这一枪");
182
- assert.ok(clientSource.includes("{ packId: memory.share_pack_id, confirm: true }"),
183
- "删除那一发要带 confirm 真值,且 packId 只从行上取");
184
- // 两段式的**第一段**也要有锁:只钉 confirm 那一发等于没钉——把三元式删掉、每次都带
185
- // confirm:true,十把锁一把都不会红,而「整包物删」变成一次点击就发生。
186
- assert.ok(clientSource.includes(": { packId: memory.share_pack_id })"),
187
- "清点那一发必须是不带 confirm 的形状");
188
- assert.ok(clientSource.includes("onClick: () => undoPack(false)"),
189
- "第一颗键走的是干跑那一支");
190
- assert.ok(clientSource.includes("d.dry_run === false"),
191
- "撤成功的读数同样只由答复的 dry_run 说");
192
- assert.ok(clientSource.includes("undoPreview.sample"),
193
- "删除前的确认要带前缀样例(答复的 sample 最多 20 条,deleted 才是全量)");
194
- // 撤是**物删**(docs/SHARE.md:归档行会占住 UNIQUE 位,撤了导不回来),文案不许写「归档」。
195
- assert.equal(/memory\.explorer\.undoPack[A-Za-z]*"\s*:\s*"[^"]*(归档|archive)/i.test(clientSource), false,
196
- "撤销的文案不许出现「归档」——它是物理删除");
197
- // 删除那一发的成功判据**只能是答复的 dry_run**。`confirm && d.dry_run === false` 那种合取
198
- // 读的是「我刚才点的是哪一颗」:确认回来一个 dry_run:true 时会把预览重画一遍当作没发生——
199
- // 接收弹层那一侧修过同一个形状,撤销这条更硬的路不许退回去。
200
- assert.ok(clientSource.includes("if (d.dry_run === false) onUndone(d.deleted ?? 0)"),
201
- "撤销的成功判据只看答复");
202
- assert.equal(clientSource.includes("confirm && d.dry_run === false"), false,
203
- "不许再出现按按钮形状推断的那种合取");
204
- assert.ok(clientSource.includes('t("memory.explorer.undoPackGone")'),
205
- "包已不在本库时要说那一句,而不是给一颗「确认删除 0 条」的键");
206
- // 同一形状只许有一份:抽屉的退出清单与接收弹层的撞名清单都是 {type,title} 两列。
207
- assert.ok(clientSource.includes("TypeRowList({ t, rows: undoPreview.sample })"),
208
- "清单形状走公共组件,不许第三份手写 .map");
209
- assert.ok(clientSource.includes('foreignOnly && !!query.trim() && h("div"'),
210
- "「搜索时不按」要有一行看得见的说明,不能只挂在 disabled 的 title 上");
211
- });
212
-
213
- // 外来判据是这条车道唯一一处「面板自己写的字面量」:lib/client.js 是独立 bundle,
214
- // 不能 import src/memory-source.js,所以用静态镜像把它钉回服务端那一份。
215
- // 服务端改了那个值而这里没跟,面板的徽标与筛选会安静地筛不到东西——正是最难发现的一种坏。
216
- test("the panel's foreign-ness literal mirrors the server's single definition", () => {
217
- const sourceModule = readFileSync(join(here, "..", "src/memory-source.js"), "utf8");
218
- const imported = /IMPORTED_SOURCE = "([^"]+)"/.exec(sourceModule);
219
- assert.ok(imported, "src/memory-source.js 的导出形状变了,先修这条锁的锚");
220
- const value = imported[1];
221
- assert.ok(clientSource.includes(`m.source === "${value}"`),
222
- "徽标读的行来源必须与服务端定义的那个值同字面");
223
- assert.ok(clientSource.includes(`&source=${value}`),
224
- "浏览态那条 URL 拼的来源值必须是同一个");
225
- });
1
+ // 车道 D 的面板静态锁。为什么是静态锁:lib/client.js 由宿主 __ModuleLoader__ 加载,
2
+ // 本仓库没有可跑的宿主壳,渲染测试要拖进一份 React 测试栈才够得着那几行真代码。
3
+ // 锁的是「形状」而不是「长得像」:每一条都对应一个**会伤人的回归形态**,
4
+ // 注释里写清它防的是哪一种(AGENTS.md 纪律 8 对测试同样适用)。
5
+ import test from "node:test";
6
+ import assert from "node:assert/strict";
7
+ import { readFileSync } from "node:fs";
8
+ import { dirname, join } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+
11
+ const here = dirname(fileURLToPath(import.meta.url));
12
+ const clientSource = readFileSync(join(here, "..", "lib/client.js"), "utf8");
13
+ const shareSource = readFileSync(join(here, "..", "src/share.js"), "utf8");
14
+
15
+ // 分享弹层最要紧的一条:**看见 report 之前不给下载**(spec §6 的支点)。
16
+ // 防的回归形态:有人为了「方便」把下载按钮改成常驻,或从别处(如列表工具栏)再开一条
17
+ // 直接落盘的口子——那一步停顿就没了,随手全库外发重新变成一次点击。
18
+ test("the share dialog gates the download on the report", () => {
19
+ assert.ok(clientSource.includes('apiFetch("/api/dsh-mneme/share"'),
20
+ "面板必须发这一枪(不许另造 fetch 绕过 apiFetch 的 token 注入)");
21
+ assert.ok(clientSource.includes("const canDownload = !!rep && rep.included > 0;"),
22
+ "下载按钮的唯一门:有报告且有内容。included 为 0 时对面吃的是 empty-pack(400),不该给一个必败的文件");
23
+ assert.ok(clientSource.includes("canDownload && h(\"div\""),
24
+ "下载那一行必须挂在 canDownload 上,而不是常驻(按钮本身在这样一个 div 里)");
25
+ // 413 与 200 是两份形状:413 没有 memories,只有 report + 字节数 + hint。
26
+ assert.ok(clientSource.includes("if (res.status === 413)"),
27
+ "超水位要走 tooLarge 那一支,不能落到通用失败");
28
+ assert.ok(clientSource.includes("new Blob([JSON.stringify(pack)]"),
29
+ "落盘的必须是服务端给的那一份字节:重拼字段或 pretty-print 都会让「量过的包」与「发出去的包」不是同一坨");
30
+ });
31
+
32
+ // docs/SHARE.md「车道 D 三笔账」之一:included 只数实际发出的条数,够格进包的是
33
+ // included + deduped。防的回归:有人把「合格条数」的标签挪到 included 上,读数是假的。
34
+ test("the report separates what shipped from what qualified", () => {
35
+ assert.ok(clientSource.includes("report.included + report.deduped"),
36
+ "折叠掉的那几条必须加回去,两个数同屏");
37
+ assert.ok(clientSource.includes('t("memory.explorer.shareDeduped")'),
38
+ "deduped 那一行要真的有渲染点,不是只在字典里躺着");
39
+ });
40
+
41
+ // 跨文件锁:面板的六个跳过原因与八个可分享类型都只许是服务端那份的投影。
42
+ // 防的回归:share.js 加/改一个 reason 键或一类可分享类型,面板静默少一格
43
+ // ——她的报告上就有一类跳过是看不见的。
44
+ test("the panel's skip-reason and type sets mirror src/share.js", () => {
45
+ const serverReasons = [...shareSource.matchAll(/reason:\s*"([a-z_]+)"/g)].map((m) => m[1]);
46
+ const uniqueReasons = [...new Set(serverReasons)];
47
+ const panelReasons = /const SHARE_SKIP_REASONS = \[([^\]]*)\]/.exec(clientSource)[1]
48
+ .split(",").map((s) => s.trim().replace(/"/g, "")).filter(Boolean);
49
+ assert.deepEqual(panelReasons, uniqueReasons,
50
+ "跳过原因集必须与 share.js 的判序一致(顺序也一致:报告按判序展示)");
51
+
52
+ const serverTypes = /SHARE_ALLOWED_TYPES = new Set\(\[([^\]]*)\]/.exec(shareSource)[1]
53
+ .split(",").map((s) => s.trim().replace(/"/g, "")).filter(Boolean);
54
+ const panelTypes = /const SHARE_TYPE_OPTIONS = \[([^\]]*)\]/.exec(clientSource)[1]
55
+ .split(",").map((s) => s.trim().replace(/"/g, "")).filter(Boolean);
56
+ assert.deepEqual(panelTypes.slice().sort(), serverTypes.slice().sort(),
57
+ "类型下拉只能列真能进包的那几类");
58
+ });
59
+
60
+ // 别名的尺:服务端 importedTagFor 把整串(含 from-)截到 IMPORTED_TAG_MAX。
61
+ // 防的回归:服务端改这个常量而面板还拦着旧长度,她打的字在对面被静默吃掉。
62
+ test("the alias input matches the server's tag budget", () => {
63
+ const max = Number(/IMPORTED_TAG_MAX = (\d+)/.exec(shareSource)[1]);
64
+ assert.ok(clientSource.includes(`maxLength: ${max - "from-".length}`),
65
+ "输入框长度必须等于服务端留给别名的字符数");
66
+ });
67
+
68
+ // 文件名以服务端 Content-Disposition 为准(拼法只住在 src/share-http.js 的 shareFilename)。
69
+ // 防的回归:面板自己抄一份「日期 + pack_id 前 8 位」的拼法,两处日后分叉。
70
+ test("the pack filename comes from the response header, with a pack_id fallback", () => {
71
+ assert.ok(clientSource.includes('res.headers.get("content-disposition")'));
72
+ assert.ok(clientSource.includes("const dispositionName = (value) =>"));
73
+ // 回落那一格只许出现一次前缀串,且不带日期拼法。
74
+ assert.equal(clientSource.split("dsh-mneme-share-").length - 1, 1,
75
+ "面板只许有一份包名前缀串(回落用),不许再拼第二套日期命名");
76
+ assert.equal(/pack_id[^;\n]{0,48}slice\(\s*0\s*,\s*8\s*\)/.test(clientSource), false,
77
+ "不许在 pack_id 后面截 8 位——那是 src/share-http.js 的 shareFilename 唯一的拼法");
78
+ });
79
+
80
+ // docs/SHARE.md「车道 D 三笔账」之二:干跑与真导入的答复同形状,区别只有
81
+ // withDryRun 加的 dry_run 那一格。防的回归形态:界面按「我有没有发 confirm」推断写了
82
+ // 没有——同一件事写两处,改一处就出现「其实没写、却说写了」。
83
+ test("the receive dialog reads dry_run instead of inferring from its own button", () => {
84
+ assert.ok(clientSource.includes('apiFetch("/api/dsh-mneme/receive"'),
85
+ "入库只许走这一枪(apiFetch 带 token 注入)");
86
+ assert.ok(clientSource.includes("post({ pack })"), "预览那一发不带 confirm——缺省即干跑");
87
+ assert.ok(clientSource.includes("post({ pack, confirm: true })"),
88
+ "真写的那一发必须带 confirm 这个真值,其他形状服务端都不认");
89
+ assert.ok(clientSource.includes('d.dry_run === false ? "memory.explorer.receiveDidCreate"'),
90
+ "「写进去了」这句只能由答复自己说——钉的是那个三元式本身,不是六个字面量都在");
91
+ assert.ok(clientSource.includes("result.dry_run !== false"),
92
+ "确认之后仍回干跑时要单独说「没有写库」,不许沉默,也不许挂在预览那一支上");
93
+ assert.ok(clientSource.includes('"memory.explorer.receiveStillDry"'),
94
+ "那一格要有自己的文案");
95
+ // 第二个旋钮一概不认:收侧只认 confirm(spec §5——reclaim 的 body 里就带 dryRun,
96
+ // 手熟的人会顺手发来,把它读成「要写」等于替她点头)。
97
+ // **切片而不是全文**:本文件里 reclaim/VACUUM 那一发合法地发 `dryRun: false, confirm: true`,
98
+ // 那是 reclaim 的形状不是 receive 的。全文反锁会一上来就红;谁把它「修」成全文锁,
99
+ // 就等于给 reclaim 那颗键定罪。所以按函数名切区段,并且**两个锚点都要验**:
100
+ // 结束锚点丢了的时候 indexOf 返回 -1,切片会一路长到文件尾而不是塌成空——
101
+ // 那时候这条锁看似更严,实际悄悄开始管整个文件(而 ReceiveDialog 之后今天没有 dryRun,
102
+ // 它会空过)。两头都钉住,锚点漂移只会红得明白。
103
+ const receiveStart = clientSource.indexOf("function ReceiveDialog(");
104
+ const receiveEnd = clientSource.indexOf("// #386 第 5 条", receiveStart);
105
+ assert.ok(receiveStart > 0 && receiveEnd > receiveStart,
106
+ "ReceiveDialog 的起止锚点都要在,且顺序对——否则这条锁在管的东西和它说的不是一件");
107
+ const receiveRegion = clientSource.slice(receiveStart, receiveEnd);
108
+ assert.ok(receiveRegion.length > 800, "ReceiveDialog 的区段短得不像话,先修锚再谈锁");
109
+ assert.equal(receiveRegion.includes("dryRun"), false,
110
+ "收侧不许发 dryRun——唯一的入口是 confirm");
111
+ });
112
+
113
+ // 三笔账之三:title_conflicts 是 advisory 读数,只对「本轮真正会新建的那些」判过,
114
+ // 重吃同一个包时恒空。防的回归:文案写成「现在没有冲突」。
115
+ test("the receive preview keeps the advisory reading honest", () => {
116
+ assert.ok(clientSource.includes("d.title_conflicts"), "撞名清单要显示");
117
+ assert.ok(clientSource.includes('"memory.explorer.receiveConflicts"'));
118
+ // 钉的是**文案**而不是注释:解释性的话必须能写「这不等于没有冲突」来提醒读者,
119
+ // 连注释一起 ban 等于把警告本身禁掉(这一轮就撞上了:新注释里的「没有撞名」先红的是这条锁)。
120
+ // 真正会伤人的是屏上那一句,所以只扫非注释行。
121
+ const copyLines = clientSource.split("\n").filter((line) => !/^\s*\/\//.test(line)).join("\n");
122
+ assert.equal(/现在没有冲突|没有撞名|无冲突|no conflicts|no title clashes/i.test(copyLines), false,
123
+ "不许出现「没有冲突」这类绝对断言(清单为空只说明本轮没有新的要报)");
124
+ // 先预览才允许确认:干跑是必经的一站,不是可选装饰。
125
+ assert.ok(clientSource.includes("disabled: busy || !preview"),
126
+ "确认按钮在拿到预览之前必须 disabled");
127
+ // advisory 读数的分母要说出来,否则「清单为空」与「什么也没比」在屏上长一个样。
128
+ // 注意它是**包内条数**而不是「比过了几条」,所以文案只许说「包内 N 条」。
129
+ assert.ok(clientSource.includes("d.conflicts_checked"),
130
+ "要把服务端给的包内条数显示出来");
131
+ assert.ok(clientSource.includes('t("memory.explorer.receivePackCount")'),
132
+ "那一行要有自己的渲染点,不是只在字典里躺着");
133
+ });
134
+
135
+ // D3 评审裁定补的锁:两种「按了没反应」的形态。
136
+ // a) 200 但正文读不出——曾经一路 return null,调用方把它读成「屏上已有提示」,
137
+ // 于是确认那一发悄悄没走完,而上一块的「将新建 N 条」还留在屏上;
138
+ // b) 预览还在飞的时候又能选一个文件——飞回来的数字说的是前一个包,确认写的却是后一个。
139
+ test("the receive dialog never goes silent, and never lets a superseded preview drive the write", () => {
140
+ assert.ok(clientSource.includes('if (!d || typeof d !== "object") {'),
141
+ "200 但没有可读正文也要 setErr,不许 return null 装成「已经处理过了」");
142
+ assert.ok(clientSource.includes("disabled: busy, onClick: () => fileRef.current?.click()"),
143
+ "预览/确认在飞的时候不许再选文件(那一块数字属于上一个包)");
144
+ assert.ok(clientSource.includes('typeof parsed !== "object"'),
145
+ "解得出 JSON 但不是对象,是另一种失败,得有它自己的一句话");
146
+ assert.ok(clientSource.includes('e.target.value = ""'),
147
+ "选完清空控件值:否则改过的同一个文件再选一次不发 change,面板拿着旧包预览");
148
+ });
149
+
150
+ // 外来区三件:筛选、来源徽标、整包退出。锁的是「每件事都挂在一个服务端真值上」,
151
+ // 而不是挂在一个界面自己造出来的判断上。
152
+ test("the foreign filter is a browse-state server filter and stays inert while searching", () => {
153
+ assert.ok(clientSource.includes('foreignOnly ? "&source=imported" : ""'),
154
+ "浏览态交给服务端筛:分页与 total 同口径,本地筛会切掉尾巴");
155
+ // **搜索态不做本地过滤**(本轮扫描改掉的裁定,不是漏做):/search 那张 DTO 的 source 是
156
+ // 召回渠道(keyword/vector/bm25,src/service.js:939),行的 provenance 被盖掉,「这条是不是
157
+ // 外来的」在这张 DTO 上压根读不出来。宁可让 chip 在搜索态明确失效并说清原因,也不要按一个
158
+ // 假信号筛——test/share-foreign-rows.test.js 的第三条用例锁着这个事实。
159
+ assert.ok(clientSource.includes("disabled: !!query.trim() && !foreignOnly"),
160
+ "有搜索词时不许**开启**外来筛选,但任何时候都许关掉——只给 disabled 会把她锁在一个她解不开的状态里");
161
+ assert.ok(clientSource.includes('"memory.explorer.filterForeignSearchHint"'),
162
+ "失效要有自己的文案,不能只靠 disabled 让人猜");
163
+ assert.equal(clientSource.includes('(!foreignOnly || m.source'), false,
164
+ "不许退回「搜索态本地按 source 筛」那条假信号");
165
+ // 服务端不吃 source 这件事,不许用「给 search 的 URL 也拼一个键」的方式绕过去:
166
+ // 那条路由压根不读它(src/api.js 的 search handler 里没有 source 这一档),拼上去
167
+ // 只会造出一个「看起来筛过了、其实没有」的形状。全文件只许有一处拼这个参数。
168
+ assert.equal(clientSource.split("&source=imported").length - 1, 1,
169
+ "只有浏览态那一条 URL 会拼 source=imported");
170
+ });
171
+
172
+ // 来源徽标与撤销都按服务端给的真值门控:徽标认 source + from- 标签,撤销认 pack_id。
173
+ test("the foreign badge and the withdraw action gate on server truth, not on guesswork", () => {
174
+ assert.ok(clientSource.includes('m.source === "imported"'),
175
+ "徽标先认 source,再认标签:人手工打的 from-* 标签不算外来行");
176
+ assert.ok(clientSource.includes('startsWith("from-")'),
177
+ "别名读数就是 importedTagFor 打的那条标签,面板不另找地方读别名");
178
+ assert.ok(clientSource.includes("memory.share_pack_id &&"),
179
+ "撤销按钮按包号自门控:/search 的 DTO 不带这一格,那种行按不了(已知代价,docs/SHARE.md 记着)");
180
+ assert.ok(clientSource.includes('apiFetch("/api/dsh-mneme/receive/undo"'),
181
+ "整包退出只许走这一枪");
182
+ assert.ok(clientSource.includes("{ packId: memory.share_pack_id, confirm: true }"),
183
+ "删除那一发要带 confirm 真值,且 packId 只从行上取");
184
+ // 两段式的**第一段**也要有锁:只钉 confirm 那一发等于没钉——把三元式删掉、每次都带
185
+ // confirm:true,十把锁一把都不会红,而「整包物删」变成一次点击就发生。
186
+ assert.ok(clientSource.includes(": { packId: memory.share_pack_id })"),
187
+ "清点那一发必须是不带 confirm 的形状");
188
+ assert.ok(clientSource.includes("onClick: () => undoPack(false)"),
189
+ "第一颗键走的是干跑那一支");
190
+ assert.ok(clientSource.includes("d.dry_run === false"),
191
+ "撤成功的读数同样只由答复的 dry_run 说");
192
+ assert.ok(clientSource.includes("undoPreview.sample"),
193
+ "删除前的确认要带前缀样例(答复的 sample 最多 20 条,deleted 才是全量)");
194
+ // 撤是**物删**(docs/SHARE.md:归档行会占住 UNIQUE 位,撤了导不回来),文案不许写「归档」。
195
+ assert.equal(/memory\.explorer\.undoPack[A-Za-z]*"\s*:\s*"[^"]*(归档|archive)/i.test(clientSource), false,
196
+ "撤销的文案不许出现「归档」——它是物理删除");
197
+ // 删除那一发的成功判据**只能是答复的 dry_run**。`confirm && d.dry_run === false` 那种合取
198
+ // 读的是「我刚才点的是哪一颗」:确认回来一个 dry_run:true 时会把预览重画一遍当作没发生——
199
+ // 接收弹层那一侧修过同一个形状,撤销这条更硬的路不许退回去。
200
+ assert.ok(clientSource.includes("if (d.dry_run === false) onUndone(d.deleted ?? 0)"),
201
+ "撤销的成功判据只看答复");
202
+ assert.equal(clientSource.includes("confirm && d.dry_run === false"), false,
203
+ "不许再出现按按钮形状推断的那种合取");
204
+ assert.ok(clientSource.includes('t("memory.explorer.undoPackGone")'),
205
+ "包已不在本库时要说那一句,而不是给一颗「确认删除 0 条」的键");
206
+ // 同一形状只许有一份:抽屉的退出清单与接收弹层的撞名清单都是 {type,title} 两列。
207
+ assert.ok(clientSource.includes("TypeRowList({ t, rows: undoPreview.sample })"),
208
+ "清单形状走公共组件,不许第三份手写 .map");
209
+ assert.ok(clientSource.includes('foreignOnly && !!query.trim() && h("div"'),
210
+ "「搜索时不按」要有一行看得见的说明,不能只挂在 disabled 的 title 上");
211
+ });
212
+
213
+ // 外来判据是这条车道唯一一处「面板自己写的字面量」:lib/client.js 是独立 bundle,
214
+ // 不能 import src/memory-source.js,所以用静态镜像把它钉回服务端那一份。
215
+ // 服务端改了那个值而这里没跟,面板的徽标与筛选会安静地筛不到东西——正是最难发现的一种坏。
216
+ test("the panel's foreign-ness literal mirrors the server's single definition", () => {
217
+ const sourceModule = readFileSync(join(here, "..", "src/memory-source.js"), "utf8");
218
+ const imported = /IMPORTED_SOURCE = "([^"]+)"/.exec(sourceModule);
219
+ assert.ok(imported, "src/memory-source.js 的导出形状变了,先修这条锁的锚");
220
+ const value = imported[1];
221
+ assert.ok(clientSource.includes(`m.source === "${value}"`),
222
+ "徽标读的行来源必须与服务端定义的那个值同字面");
223
+ assert.ok(clientSource.includes(`&source=${value}`),
224
+ "浏览态那条 URL 拼的来源值必须是同一个");
225
+ });