@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.
- package/LICENSE +21 -21
- package/README.md +830 -830
- package/assets/icon.svg +3 -3
- package/bin/cli.mjs +964 -964
- package/bin/dsh-mneme-choro.mjs +262 -262
- package/bin/dsh-mneme-mcp.mjs +510 -510
- package/bin/dsh-mneme-serve.mjs +151 -151
- package/cordis.patch.yml +21 -21
- package/docs/CONFIGURATION.md +407 -404
- package/docs/CONTINUITY.md +139 -139
- package/docs/DAEMON.md +57 -57
- package/docs/DIRECTORIES.md +114 -114
- package/docs/DISTILL.md +397 -371
- package/docs/DSH-GO.md +97 -97
- package/docs/ENTITIES.md +247 -247
- package/docs/FORGET.md +77 -77
- package/docs/KB.md +110 -110
- package/docs/LOCAL_MODEL.md +222 -222
- package/docs/SEMANTIC.md +256 -256
- package/docs/SHARE.md +552 -552
- package/docs/SLEEP.md +202 -202
- package/docs/STORAGE.md +122 -122
- package/docs/WORKFLOWS.md +366 -366
- package/lib/api-standalone.js +1007 -1007
- package/lib/api.js +3600 -3600
- package/lib/attachment-text.js +122 -122
- package/lib/bootstrap-http.js +105 -105
- package/lib/bootstrap.js +215 -215
- package/lib/candidates.js +454 -454
- package/lib/capabilities.js +819 -819
- package/lib/choro-command.js +111 -111
- package/lib/client.js +14420 -14322
- package/lib/commands.js +95 -95
- package/lib/config-keys.js +144 -144
- package/lib/config.js +1361 -1344
- package/lib/content-hash.js +54 -54
- package/lib/continuity.js +258 -258
- package/lib/date-hint.js +32 -0
- package/lib/diary/auto.js +190 -190
- package/lib/diary/compose.js +440 -440
- package/lib/diary/dir.js +59 -59
- package/lib/diary/export.js +97 -97
- package/lib/diary/index.js +26 -26
- package/lib/diary/library.js +189 -189
- package/lib/diary/qq-inbox.js +142 -142
- package/lib/diary/rollover.js +61 -61
- package/lib/diary/session-provider.js +122 -122
- package/lib/document-index.js +94 -94
- package/lib/document.js +322 -322
- package/lib/dream/clustering.js +123 -123
- package/lib/dream/decisions.js +945 -945
- package/lib/dream/narratives.js +44 -44
- package/lib/dream/sleep.js +2027 -2027
- package/lib/dream/triage.js +139 -139
- package/lib/dream.js +1879 -1879
- package/lib/dsh-go/host.js +162 -162
- package/lib/dsh-go/panel.js +2091 -2091
- package/lib/dsh-go/text.js +342 -342
- package/lib/dsh-go/views/common.js +68 -68
- package/lib/dsh-go/views/entity.js +48 -48
- package/lib/dsh-go/views/memory.js +198 -198
- package/lib/dsh-go/views/root.js +30 -30
- package/lib/dsh-go/views/settings.js +398 -398
- package/lib/dsh-go/views/status.js +51 -51
- package/lib/embedding.js +342 -342
- package/lib/entities/extractor.js +285 -285
- package/lib/fence.js +24 -24
- package/lib/graph/anchoring.js +184 -184
- package/lib/guide.js +74 -74
- package/lib/heat.js +144 -144
- package/lib/hot-memory.js +119 -119
- package/lib/image-describe.js +158 -0
- package/lib/index.js +852 -852
- package/lib/inject.js +656 -651
- package/lib/kb/index.js +309 -309
- package/lib/kb/routes.js +219 -219
- package/lib/kb/tools.js +148 -148
- package/lib/lang.js +607 -595
- package/lib/local-embedder.js +358 -358
- package/lib/maintenance.js +162 -162
- package/lib/memory-source.js +9 -9
- package/lib/mirror.js +330 -330
- package/lib/organize.js +456 -456
- package/lib/peak-hours.js +172 -172
- package/lib/pipeline-activities.js +178 -178
- package/lib/pipeline-attribution.js +165 -165
- package/lib/pipeline-command.js +279 -279
- package/lib/pipeline-confirm.js +142 -142
- package/lib/pipeline-exec.js +174 -174
- package/lib/pipeline-nudge-auto.js +129 -129
- package/lib/pipeline-nudge.js +1623 -1623
- package/lib/pipeline-observations.js +181 -181
- package/lib/pipeline-observe.js +133 -133
- package/lib/pipeline-runner.js +361 -361
- package/lib/pipeline-runs.js +442 -442
- package/lib/pipeline.js +716 -716
- package/lib/placement.js +13 -13
- package/lib/pre-inject-gate.js +354 -354
- package/lib/profile-extract.js +164 -164
- package/lib/profile-facts.js +356 -356
- package/lib/provenance-http.js +251 -251
- package/lib/quality-filter.js +162 -162
- package/lib/recall-stats.js +190 -190
- package/lib/reclaim-http.js +150 -150
- package/lib/reranker.js +269 -269
- package/lib/runtime/adopt.js +176 -176
- package/lib/runtime/closure.js +172 -172
- package/lib/runtime/download.js +247 -247
- package/lib/runtime/layout.js +265 -265
- package/lib/runtime/loader.js +252 -252
- package/lib/runtime/model-fetch.js +361 -361
- package/lib/runtime/provision.js +237 -237
- package/lib/runtime/tarball.js +130 -130
- package/lib/runtime/verify.js +223 -223
- package/lib/safe-slice.js +18 -18
- package/lib/scope-state.js +277 -277
- package/lib/scope.js +187 -187
- package/lib/search/adaptive.js +60 -60
- package/lib/search/bm25.js +96 -96
- package/lib/semantic.js +572 -572
- package/lib/sensitive-scan.js +209 -209
- package/lib/serve.js +228 -228
- package/lib/service.js +3268 -3268
- package/lib/session-gate.js +91 -91
- package/lib/session-usage.js +350 -350
- package/lib/settings.js +827 -817
- package/lib/share-http.js +281 -281
- package/lib/share-import.js +234 -234
- package/lib/share.js +335 -335
- package/lib/store.js +5895 -5895
- package/lib/summarize.js +1388 -1343
- package/lib/tools.js +1370 -1356
- package/lib/user-settings.js +230 -230
- package/lib/vector-index.js +180 -180
- package/lib/version-check.js +122 -122
- package/lib/wiki/claims.js +267 -267
- package/lib/wiki/cli-hint.js +90 -90
- package/lib/wiki/inject.js +164 -164
- package/lib/wiki/partition.js +200 -200
- package/lib/wiki/plan.js +214 -214
- package/lib/wiki/queries.js +640 -640
- package/lib/wiki/render.js +261 -261
- package/lib/wiki/scan.js +371 -371
- package/lib/wiki/t0.js +500 -500
- package/lib/wiki/t1-jobs.js +232 -232
- package/lib/wiki/t1.js +609 -609
- package/lib/write-admission.js +404 -404
- package/locale/en.json +6 -6
- package/locale/zh.json +6 -6
- package/package.json +1 -1
- package/runtime-manifest.json +526 -526
- package/scripts/benchmark-embed.js +201 -201
- package/scripts/benchmark-recall.js +249 -249
- package/scripts/benchmark-rerank.js +166 -166
- package/scripts/build-runtime-manifest.mjs +224 -224
- package/scripts/changelog-prep.mjs +91 -91
- package/scripts/check-sync.js +44 -44
- package/scripts/e2e-dsh.js +220 -220
- package/scripts/mneme-runtime.mjs +277 -277
- package/scripts/publish-gate.mjs +197 -197
- package/scripts/release-docs.mjs +210 -210
- package/scripts/release-notes.mjs +164 -164
- package/scripts/stress-dsh.js +255 -255
- package/scripts/sync-lib.js +47 -47
- package/scripts/sync-test-badge.mjs +69 -69
- package/scripts/test-count-sync.mjs +48 -48
- package/src/api-standalone.js +1007 -1007
- package/src/api.js +3600 -3600
- package/src/attachment-text.js +122 -122
- package/src/bootstrap-http.js +105 -105
- package/src/bootstrap.js +215 -215
- package/src/candidates.js +454 -454
- package/src/capabilities.js +819 -819
- package/src/choro-command.js +111 -111
- package/src/commands.js +95 -95
- package/src/config-keys.js +144 -144
- package/src/config.js +1361 -1344
- package/src/content-hash.js +54 -54
- package/src/continuity.js +258 -258
- package/src/date-hint.js +32 -0
- package/src/diary/auto.js +190 -190
- package/src/diary/compose.js +440 -440
- package/src/diary/dir.js +59 -59
- package/src/diary/export.js +97 -97
- package/src/diary/index.js +26 -26
- package/src/diary/library.js +189 -189
- package/src/diary/qq-inbox.js +142 -142
- package/src/diary/rollover.js +61 -61
- package/src/diary/session-provider.js +122 -122
- package/src/document-index.js +94 -94
- package/src/document.js +322 -322
- package/src/dream/clustering.js +123 -123
- package/src/dream/decisions.js +945 -945
- package/src/dream/narratives.js +44 -44
- package/src/dream/sleep.js +2027 -2027
- package/src/dream/triage.js +139 -139
- package/src/dream.js +1879 -1879
- package/src/dsh-go/host.js +162 -162
- package/src/dsh-go/panel.js +2091 -2091
- package/src/dsh-go/text.js +342 -342
- package/src/dsh-go/views/common.js +68 -68
- package/src/dsh-go/views/entity.js +48 -48
- package/src/dsh-go/views/memory.js +198 -198
- package/src/dsh-go/views/root.js +30 -30
- package/src/dsh-go/views/settings.js +398 -398
- package/src/dsh-go/views/status.js +51 -51
- package/src/embedding.js +342 -342
- package/src/entities/extractor.js +285 -285
- package/src/fence.js +24 -24
- package/src/graph/anchoring.js +184 -184
- package/src/guide.js +74 -74
- package/src/heat.js +144 -144
- package/src/hot-memory.js +119 -119
- package/src/image-describe.js +158 -0
- package/src/index.js +852 -852
- package/src/inject.js +656 -651
- package/src/kb/index.js +309 -309
- package/src/kb/routes.js +219 -219
- package/src/kb/tools.js +148 -148
- package/src/lang.js +607 -595
- package/src/local-embedder.js +358 -358
- package/src/maintenance.js +162 -162
- package/src/memory-source.js +9 -9
- package/src/mirror.js +330 -330
- package/src/organize.js +456 -456
- package/src/peak-hours.js +172 -172
- package/src/pipeline-activities.js +178 -178
- package/src/pipeline-attribution.js +165 -165
- package/src/pipeline-command.js +279 -279
- package/src/pipeline-confirm.js +142 -142
- package/src/pipeline-exec.js +174 -174
- package/src/pipeline-nudge-auto.js +129 -129
- package/src/pipeline-nudge.js +1623 -1623
- package/src/pipeline-observations.js +181 -181
- package/src/pipeline-observe.js +133 -133
- package/src/pipeline-runner.js +361 -361
- package/src/pipeline-runs.js +442 -442
- package/src/pipeline.js +716 -716
- package/src/placement.js +13 -13
- package/src/pre-inject-gate.js +354 -354
- package/src/profile-extract.js +164 -164
- package/src/profile-facts.js +356 -356
- package/src/provenance-http.js +251 -251
- package/src/quality-filter.js +162 -162
- package/src/recall-stats.js +190 -190
- package/src/reclaim-http.js +150 -150
- package/src/reranker.js +269 -269
- package/src/runtime/adopt.js +176 -176
- package/src/runtime/closure.js +172 -172
- package/src/runtime/download.js +247 -247
- package/src/runtime/layout.js +265 -265
- package/src/runtime/loader.js +252 -252
- package/src/runtime/model-fetch.js +361 -361
- package/src/runtime/provision.js +237 -237
- package/src/runtime/tarball.js +130 -130
- package/src/runtime/verify.js +223 -223
- package/src/safe-slice.js +18 -18
- package/src/scope-state.js +277 -277
- package/src/scope.js +187 -187
- package/src/search/adaptive.js +60 -60
- package/src/search/bm25.js +96 -96
- package/src/semantic.js +572 -572
- package/src/sensitive-scan.js +209 -209
- package/src/serve.js +228 -228
- package/src/service.js +3268 -3268
- package/src/session-gate.js +91 -91
- package/src/session-usage.js +350 -350
- package/src/settings.js +827 -817
- package/src/share-http.js +281 -281
- package/src/share-import.js +234 -234
- package/src/share.js +335 -335
- package/src/store.js +5895 -5895
- package/src/summarize.js +1388 -1343
- package/src/tools.js +1370 -1356
- package/src/user-settings.js +230 -230
- package/src/vector-index.js +180 -180
- package/src/version-check.js +122 -122
- package/src/wiki/claims.js +267 -267
- package/src/wiki/cli-hint.js +90 -90
- package/src/wiki/inject.js +164 -164
- package/src/wiki/partition.js +200 -200
- package/src/wiki/plan.js +214 -214
- package/src/wiki/queries.js +640 -640
- package/src/wiki/render.js +261 -261
- package/src/wiki/scan.js +371 -371
- package/src/wiki/t0.js +500 -500
- package/src/wiki/t1-jobs.js +232 -232
- package/src/wiki/t1.js +609 -609
- package/src/write-admission.js +404 -404
- package/test/adaptive-inject-budget.test.js +85 -85
- package/test/api-hardening.test.js +173 -173
- package/test/api-nudge-route.test.js +259 -259
- package/test/api-routes-gaps.test.js +1092 -1092
- package/test/api.test.js +3007 -2998
- package/test/attachment-text.test.js +139 -139
- package/test/audit.test.js +448 -448
- package/test/baseline-persist.test.js +248 -248
- package/test/benchmark.test.js +94 -94
- package/test/bm25-corpus-cache.test.js +145 -145
- package/test/bootstrap.test.js +228 -228
- package/test/candidates.test.js +345 -345
- package/test/capabilities.test.js +577 -577
- package/test/capacity-warning.test.js +262 -262
- package/test/changelog-prep.test.js +125 -125
- package/test/choro-command.test.js +218 -218
- package/test/cli-enums.test.js +37 -37
- package/test/cli-flag-value.test.js +86 -86
- package/test/cli-receive.test.js +390 -390
- package/test/cli-share.test.js +437 -437
- package/test/client-share.test.js +225 -225
- package/test/client.test.js +4595 -4425
- package/test/clustering.test.js +100 -100
- package/test/commands.test.js +106 -106
- package/test/config-keys-routes.test.js +233 -233
- package/test/config.test.js +153 -153
- package/test/conflict-freeze.test.js +344 -344
- package/test/conflict-ledger-enforce.test.js +452 -452
- package/test/conflict-ledger.test.js +242 -242
- package/test/conflict-reviewed.test.js +79 -79
- package/test/content-hash.test.js +204 -204
- package/test/continuity.test.js +452 -452
- package/test/contrast.test.js +432 -432
- package/test/date-hint.test.js +156 -0
- package/test/dedupe-candidate-window.test.js +180 -180
- package/test/dependency-overrides.test.js +78 -78
- package/test/diary-library.test.js +187 -187
- package/test/diary.test.js +576 -576
- package/test/differentiate-feedback.test.js +248 -248
- package/test/distill-tx-embed-gap.test.js +110 -110
- package/test/docs-config-keys.test.js +65 -65
- package/test/document-dir.test.js +233 -233
- package/test/document.test.js +703 -703
- package/test/dream-archive-guardrail.test.js +116 -116
- package/test/dream-baseline-clamp.test.js +174 -174
- package/test/dream-candidate-hybrid.test.js +281 -281
- package/test/dream-coverage-degrade.test.js +86 -86
- package/test/dream-decision-evidence.test.js +314 -314
- package/test/dream-failure-backoff.test.js +173 -173
- package/test/dream-fence.test.js +207 -207
- package/test/dream-maxtokens-fallback.test.js +132 -132
- package/test/dream-merge-guardrail.test.js +219 -219
- package/test/dream-peak-hours.test.js +194 -194
- package/test/dream.test.js +1528 -1528
- package/test/dsh-go-host.test.js +134 -134
- package/test/dsh-go-panel-actions.test.js +854 -854
- package/test/dsh-go-panel.test.js +251 -251
- package/test/embed-ready-poll.test.js +94 -94
- package/test/embed-text-reasons.test.js +112 -112
- package/test/embedding-cache-f32.test.js +276 -276
- package/test/embedding-ready.test.js +226 -226
- package/test/entities.test.js +711 -711
- package/test/entity-recall.test.js +179 -179
- package/test/epistemic.test.js +325 -325
- package/test/failure-query.test.js +147 -147
- package/test/failure-retention.test.js +105 -105
- package/test/fnew-0112.test.js +311 -311
- package/test/fnew-03.test.js +422 -422
- package/test/forget-corrections.test.js +70 -70
- package/test/graph-anchoring.test.js +184 -184
- package/test/graph-api.test.js +333 -333
- package/test/graph-boosted-relations.test.js +189 -189
- package/test/graph-inject-budget.test.js +103 -103
- package/test/graph-multipath.test.js +269 -269
- package/test/graph-passive-confirm.test.js +91 -91
- package/test/graph-weight-evolution.test.js +104 -104
- package/test/heat.test.js +175 -175
- package/test/helpers/dream-mock.js +86 -86
- package/test/helpers/peer-worker.mjs +25 -25
- package/test/helpers/runtime-source.js +60 -60
- package/test/helpers/tar-builder.js +80 -80
- package/test/helpers/write-admission-samples.js +312 -312
- package/test/hot-memory.test.js +259 -259
- package/test/image-description.test.js +271 -0
- package/test/image-memory.test.js +170 -170
- package/test/inject-content-cap.test.js +91 -91
- package/test/inject-first-round-bm25.test.js +89 -89
- package/test/inject-heat.test.js +77 -77
- package/test/inject-parent-gate.test.js +134 -109
- package/test/inject-pin-explicit.test.js +300 -300
- package/test/inject-pin-vector-crowd.test.js +225 -225
- package/test/inject-pools.test.js +195 -195
- package/test/inject-recall.test.js +172 -172
- package/test/inject-rotation.test.js +161 -161
- package/test/inject.test.js +553 -553
- package/test/issue-617-step1-query-and-fusion.test.js +361 -361
- package/test/kb-api-wiring.test.js +128 -128
- package/test/kb-collections.test.js +272 -272
- package/test/kb-routes.test.js +379 -379
- package/test/lang.test.js +98 -98
- package/test/lastrunat-persist.test.js +101 -101
- package/test/lib-smoke.test.js +116 -116
- package/test/llm-audit.test.js +815 -815
- package/test/llm-message-source.test.js +207 -207
- package/test/local-embedder.test.js +292 -292
- package/test/maintenance.test.js +501 -501
- package/test/mcp-stdio.test.js +385 -385
- package/test/mint-source.test.js +311 -311
- package/test/mirror-dirty.test.js +424 -424
- package/test/mirror-edit-digest.test.js +187 -187
- package/test/mirror-generation.test.js +499 -499
- package/test/mirror-types.test.js +208 -208
- package/test/mirror.test.js +249 -249
- package/test/narrative-bars.test.js +162 -162
- package/test/organize-tool.test.js +232 -232
- package/test/organize.test.js +418 -418
- package/test/panel-chart-scale.test.js +90 -90
- package/test/panel-tokens.test.js +245 -245
- package/test/peak-hours-observability.test.js +70 -70
- package/test/peer-blockers.test.js +193 -193
- package/test/persona-addressing.test.js +83 -83
- package/test/persona-api.test.js +172 -172
- package/test/persona-by-session.test.js +568 -568
- package/test/persona-flag.test.js +36 -36
- package/test/persona-inject.test.js +145 -145
- package/test/persona-settings.test.js +198 -198
- package/test/pipeline-activities.test.js +155 -155
- package/test/pipeline-attribution.test.js +175 -175
- package/test/pipeline-command.test.js +243 -243
- package/test/pipeline-confirm.test.js +148 -148
- package/test/pipeline-exec.test.js +172 -172
- package/test/pipeline-nudge-auto.test.js +248 -248
- package/test/pipeline-nudge.test.js +1278 -1278
- package/test/pipeline-observations.test.js +157 -157
- package/test/pipeline-observe.test.js +127 -127
- package/test/pipeline-r1r2.test.js +278 -278
- package/test/pipeline-runner.test.js +468 -468
- package/test/pipeline-runs.test.js +767 -767
- package/test/pipeline.test.js +636 -636
- package/test/placement.test.js +36 -36
- package/test/policy-epoch.test.js +259 -259
- package/test/pre-inject-gate.test.js +444 -444
- package/test/profile-extract.test.js +269 -269
- package/test/profile-facts.test.js +492 -492
- package/test/prompt-stance.test.js +128 -128
- package/test/provenance-http.test.js +367 -367
- package/test/publish-gate.test.js +272 -272
- package/test/quality-backfill.test.js +290 -290
- package/test/quality-filter.test.js +295 -295
- package/test/readme-claims.test.js +113 -113
- package/test/reasoning-effort.test.js +752 -752
- package/test/recall-anti-update.test.js +82 -82
- package/test/recall-evals.test.js +253 -253
- package/test/recall-layer.test.js +326 -326
- package/test/recall-retention.test.js +128 -128
- package/test/recall-stats.test.js +274 -274
- package/test/receipt-chain.test.js +451 -451
- package/test/reclaim-http.test.js +209 -209
- package/test/reflection-update-gate.test.js +129 -129
- package/test/reflection.test.js +226 -226
- package/test/reindex-backfill.test.js +475 -475
- package/test/reindex-scheduler.test.js +369 -369
- package/test/release-docs.test.js +156 -156
- package/test/release-notes.test.js +265 -265
- package/test/release-workflow.test.js +93 -93
- package/test/reranker-single-logit.test.js +73 -73
- package/test/reranker.test.js +197 -197
- package/test/resident-status.test.js +83 -83
- package/test/route-policy.test.js +113 -113
- package/test/runtime-adopt.test.js +206 -206
- package/test/runtime-cli.test.js +89 -89
- package/test/runtime-closure.test.js +193 -193
- package/test/runtime-download.test.js +302 -302
- package/test/runtime-layout.test.js +163 -163
- package/test/runtime-loader.test.js +255 -255
- package/test/runtime-manifest.test.js +159 -159
- package/test/runtime-model-fetch.test.js +348 -348
- package/test/runtime-provision.test.js +101 -101
- package/test/runtime-tarball.test.js +81 -81
- package/test/runtime-verify.test.js +319 -319
- package/test/scope-candidates.test.js +491 -491
- package/test/scope-distill-stamp.test.js +134 -134
- package/test/scope-explicit.test.js +452 -452
- package/test/scope-inject-soft.test.js +106 -106
- package/test/scope-optional-service.test.js +110 -110
- package/test/scope-retrieval.test.js +197 -197
- package/test/scope-state.test.js +610 -610
- package/test/scope-storage.test.js +255 -255
- package/test/scope-strict.test.js +367 -367
- package/test/search-fusion.test.js +90 -90
- package/test/security-doc.test.js +188 -188
- package/test/semantic.test.js +124 -124
- package/test/sensitive-scan.test.js +251 -251
- package/test/serve-bin.test.js +121 -121
- package/test/serve.test.js +258 -258
- package/test/service-search.test.js +174 -174
- package/test/service.test.js +410 -410
- package/test/session-gate.test.js +264 -264
- package/test/session-usage.test.js +317 -317
- package/test/settings.test.js +273 -273
- package/test/share-foreign-rows.test.js +144 -144
- package/test/share-http.test.js +144 -144
- package/test/share-import.test.js +577 -577
- package/test/share-imported-guard.test.js +311 -311
- package/test/share-imported-identity.test.js +111 -111
- package/test/share-pack.test.js +511 -511
- package/test/share-receive-http.test.js +756 -756
- package/test/share-routes.test.js +337 -337
- package/test/share-store.test.js +133 -133
- package/test/share-validate.test.js +101 -101
- package/test/skip-invalid-restore.test.js +262 -262
- package/test/sleep-action-set.test.js +441 -441
- package/test/sleep-catchup.test.js +315 -315
- package/test/sleep-failure-backoff.test.js +156 -156
- package/test/sleep-heat.test.js +174 -174
- package/test/sleep-scheduler-rearm.test.js +89 -89
- package/test/sleep.test.js +1455 -1455
- package/test/standalone-api.test.js +1344 -1344
- package/test/store-vector-cache.test.js +98 -98
- package/test/store-write-path-columns.test.js +191 -191
- package/test/store.test.js +744 -744
- package/test/stress.test.js +209 -209
- package/test/summarize-occurred-at.test.js +127 -127
- package/test/summarize-parse-diagnostics.test.js +75 -75
- package/test/summarize-reasoning-effort.test.js +189 -189
- package/test/summarize.test.js +2251 -2251
- package/test/supersede-state-tracking.test.js +394 -394
- package/test/test-count-sync.test.js +71 -71
- package/test/tool-state-ignore.test.js +65 -65
- package/test/tools-dto-schema-matrix.test.js +398 -398
- package/test/tools-memory-get-schema.test.js +65 -65
- package/test/tools.test.js +585 -585
- package/test/updated-at-semantics.test.js +113 -113
- package/test/user-settings-preview.test.js +127 -127
- package/test/user-settings-segments.test.js +527 -527
- package/test/vector-index.test.js +334 -334
- package/test/version-check.test.js +152 -152
- package/test/webServer-optional.test.js +82 -82
- package/test/wiki-api.test.js +734 -734
- package/test/wiki-claims.test.js +194 -194
- package/test/wiki-cli-hint.test.js +118 -118
- package/test/wiki-inject.test.js +265 -265
- package/test/wiki-partition.test.js +112 -112
- package/test/wiki-plan.test.js +80 -80
- package/test/wiki-provenance.test.js +202 -202
- package/test/wiki-queries.test.js +398 -398
- package/test/wiki-scan.test.js +208 -208
- package/test/wiki-store.test.js +190 -190
- package/test/wiki-t0.test.js +456 -456
- package/test/wiki-t1-jobs.test.js +125 -125
- package/test/wiki-t1.test.js +460 -460
- package/test/wiki-tools.test.js +176 -176
- package/test/workflow-guards.test.js +155 -155
- package/test/write-admission.test.js +668 -668
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
import test from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
import { createStore } from "../src/store.js";
|
|
4
|
-
|
|
5
|
-
// 回归(issue #202):检索路径的固定成本。此前 searchVector 每次调用
|
|
6
|
-
// SELECT * 全表 + 逐行 JSON.parse(993 × 512 维 ≈ 48ms,余弦本身只占 3%),
|
|
7
|
-
// all() 拖着 embedding 列(5k 行 ≈ 9.4MB)且 ORDER BY 无索引走临时 B 树。
|
|
8
|
-
// 本批修复:updated_at 索引入 schema;all() 显式列清单排除 embedding;
|
|
9
|
-
// searchVector 只取 id 列 + 解析缓存(setEmbedding 为单一失效点)+ Top-N 回表。
|
|
10
|
-
|
|
11
|
-
function make() {
|
|
12
|
-
const store = createStore(":memory:");
|
|
13
|
-
return store;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
test("#202: updated_at index is created on open (existing DBs included)", () => {
|
|
17
|
-
const store = make();
|
|
18
|
-
const row = store.db.prepare(
|
|
19
|
-
"SELECT name FROM sqlite_master WHERE type='index' AND name='idx_memories_updated'"
|
|
20
|
-
).get();
|
|
21
|
-
assert.ok(row, "idx_memories_updated exists after open");
|
|
22
|
-
store.close();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test("#202: all() returns full rows (minus embedding) ordered by updated_at desc", () => {
|
|
26
|
-
const store = make();
|
|
27
|
-
store.save({ type: "project", title: "旧", content: "c1", importance: 3 });
|
|
28
|
-
store.save({ type: "decision", title: "新", content: "c2", importance: 3 });
|
|
29
|
-
const rows = store.all();
|
|
30
|
-
assert.equal(rows.length, 2);
|
|
31
|
-
assert.equal(rows[0].title, "新", "most recently updated first");
|
|
32
|
-
for (const row of rows) {
|
|
33
|
-
assert.equal(row.content?.length > 0, true, "content present");
|
|
34
|
-
assert.equal("embedding" in row, false, "embedding never leaks into row objects");
|
|
35
|
-
}
|
|
36
|
-
store.close();
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test("#202: searchVector cache invalidates on setEmbedding rewrite", () => {
|
|
40
|
-
const store = make();
|
|
41
|
-
const { id } = store.save({ type: "project", title: "A", content: "a" });
|
|
42
|
-
store.setEmbedding(id, [1, 0, 0]);
|
|
43
|
-
|
|
44
|
-
// 第一次检索:缓存 v1([1,0,0] 方向命中)
|
|
45
|
-
const hitV1 = store.searchVector([1, 0, 0], { limit: 5 });
|
|
46
|
-
assert.equal(hitV1[0]?.id, id, "v1 direction matches");
|
|
47
|
-
|
|
48
|
-
// 重写为正交向量后,旧方向必须不再命中、新方向命中——缓存失效点生效。
|
|
49
|
-
// (正交余弦=0,threshold=0 会连 0 分一起入选,所以用 0.5 的阈值区分。)
|
|
50
|
-
store.setEmbedding(id, [0, 1, 0]);
|
|
51
|
-
const hitStale = store.searchVector([1, 0, 0], { limit: 5, threshold: 0.5 });
|
|
52
|
-
assert.equal(hitStale.some((r) => r.id === id), false, "stale vector must not match after rewrite");
|
|
53
|
-
const hitV2 = store.searchVector([0, 1, 0], { limit: 5 });
|
|
54
|
-
assert.equal(hitV2[0]?.id, id, "new vector matches after rewrite");
|
|
55
|
-
store.close();
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test("#202: setEmbedding(null) drops the row from vector search", () => {
|
|
59
|
-
const store = make();
|
|
60
|
-
const { id } = store.save({ type: "project", title: "A", content: "a" });
|
|
61
|
-
store.setEmbedding(id, [1, 0, 0]);
|
|
62
|
-
assert.equal(store.searchVector([1, 0, 0], { limit: 5 }).length, 1);
|
|
63
|
-
store.setEmbedding(id, null);
|
|
64
|
-
assert.equal(store.searchVector([1, 0, 0], { limit: 5 }).length, 0, "cleared embedding leaves the search set");
|
|
65
|
-
store.close();
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("#202: searchVector returns full memory rows with scores, sorted desc", () => {
|
|
69
|
-
const store = make();
|
|
70
|
-
const a = store.save({ type: "project", title: "标题A", content: "内容A", importance: 4 });
|
|
71
|
-
const b = store.save({ type: "decision", title: "标题B", content: "内容B", importance: 2 });
|
|
72
|
-
store.setEmbedding(a.id, [1, 0, 0]);
|
|
73
|
-
store.setEmbedding(b.id, [0.6, 0.8, 0]);
|
|
74
|
-
|
|
75
|
-
const hits = store.searchVector([1, 0, 0], { limit: 5 });
|
|
76
|
-
assert.equal(hits.length, 2);
|
|
77
|
-
assert.equal(hits[0].id, a.id, "closest vector first");
|
|
78
|
-
assert.ok(hits[0].score > hits[1].score, "descending scores");
|
|
79
|
-
for (const hit of hits) {
|
|
80
|
-
assert.ok(hit.title && hit.content, "full fields refetched for top hits");
|
|
81
|
-
assert.equal(typeof hit.score, "number");
|
|
82
|
-
assert.equal(hit.importance > 0, true, "non-score fields intact");
|
|
83
|
-
}
|
|
84
|
-
store.close();
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test("#202: getParsedEmbedding / getEmbeddings reflect rewrites (dedup path cache)", () => {
|
|
88
|
-
const store = make();
|
|
89
|
-
const { id } = store.save({ type: "project", title: "A", content: "a" });
|
|
90
|
-
store.setEmbedding(id, [1, 2, 3]);
|
|
91
|
-
assert.deepEqual(store.getParsedEmbedding(id), [1, 2, 3]);
|
|
92
|
-
assert.deepEqual(store.getEmbeddings([id]).get(id), [1, 2, 3]);
|
|
93
|
-
|
|
94
|
-
store.setEmbedding(id, [4, 5, 6]);
|
|
95
|
-
assert.deepEqual(store.getParsedEmbedding(id), [4, 5, 6], "cache-first read sees the rewrite");
|
|
96
|
-
assert.deepEqual(store.getEmbeddings([id]).get(id), [4, 5, 6]);
|
|
97
|
-
store.close();
|
|
98
|
-
});
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { createStore } from "../src/store.js";
|
|
4
|
+
|
|
5
|
+
// 回归(issue #202):检索路径的固定成本。此前 searchVector 每次调用
|
|
6
|
+
// SELECT * 全表 + 逐行 JSON.parse(993 × 512 维 ≈ 48ms,余弦本身只占 3%),
|
|
7
|
+
// all() 拖着 embedding 列(5k 行 ≈ 9.4MB)且 ORDER BY 无索引走临时 B 树。
|
|
8
|
+
// 本批修复:updated_at 索引入 schema;all() 显式列清单排除 embedding;
|
|
9
|
+
// searchVector 只取 id 列 + 解析缓存(setEmbedding 为单一失效点)+ Top-N 回表。
|
|
10
|
+
|
|
11
|
+
function make() {
|
|
12
|
+
const store = createStore(":memory:");
|
|
13
|
+
return store;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
test("#202: updated_at index is created on open (existing DBs included)", () => {
|
|
17
|
+
const store = make();
|
|
18
|
+
const row = store.db.prepare(
|
|
19
|
+
"SELECT name FROM sqlite_master WHERE type='index' AND name='idx_memories_updated'"
|
|
20
|
+
).get();
|
|
21
|
+
assert.ok(row, "idx_memories_updated exists after open");
|
|
22
|
+
store.close();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("#202: all() returns full rows (minus embedding) ordered by updated_at desc", () => {
|
|
26
|
+
const store = make();
|
|
27
|
+
store.save({ type: "project", title: "旧", content: "c1", importance: 3 });
|
|
28
|
+
store.save({ type: "decision", title: "新", content: "c2", importance: 3 });
|
|
29
|
+
const rows = store.all();
|
|
30
|
+
assert.equal(rows.length, 2);
|
|
31
|
+
assert.equal(rows[0].title, "新", "most recently updated first");
|
|
32
|
+
for (const row of rows) {
|
|
33
|
+
assert.equal(row.content?.length > 0, true, "content present");
|
|
34
|
+
assert.equal("embedding" in row, false, "embedding never leaks into row objects");
|
|
35
|
+
}
|
|
36
|
+
store.close();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("#202: searchVector cache invalidates on setEmbedding rewrite", () => {
|
|
40
|
+
const store = make();
|
|
41
|
+
const { id } = store.save({ type: "project", title: "A", content: "a" });
|
|
42
|
+
store.setEmbedding(id, [1, 0, 0]);
|
|
43
|
+
|
|
44
|
+
// 第一次检索:缓存 v1([1,0,0] 方向命中)
|
|
45
|
+
const hitV1 = store.searchVector([1, 0, 0], { limit: 5 });
|
|
46
|
+
assert.equal(hitV1[0]?.id, id, "v1 direction matches");
|
|
47
|
+
|
|
48
|
+
// 重写为正交向量后,旧方向必须不再命中、新方向命中——缓存失效点生效。
|
|
49
|
+
// (正交余弦=0,threshold=0 会连 0 分一起入选,所以用 0.5 的阈值区分。)
|
|
50
|
+
store.setEmbedding(id, [0, 1, 0]);
|
|
51
|
+
const hitStale = store.searchVector([1, 0, 0], { limit: 5, threshold: 0.5 });
|
|
52
|
+
assert.equal(hitStale.some((r) => r.id === id), false, "stale vector must not match after rewrite");
|
|
53
|
+
const hitV2 = store.searchVector([0, 1, 0], { limit: 5 });
|
|
54
|
+
assert.equal(hitV2[0]?.id, id, "new vector matches after rewrite");
|
|
55
|
+
store.close();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("#202: setEmbedding(null) drops the row from vector search", () => {
|
|
59
|
+
const store = make();
|
|
60
|
+
const { id } = store.save({ type: "project", title: "A", content: "a" });
|
|
61
|
+
store.setEmbedding(id, [1, 0, 0]);
|
|
62
|
+
assert.equal(store.searchVector([1, 0, 0], { limit: 5 }).length, 1);
|
|
63
|
+
store.setEmbedding(id, null);
|
|
64
|
+
assert.equal(store.searchVector([1, 0, 0], { limit: 5 }).length, 0, "cleared embedding leaves the search set");
|
|
65
|
+
store.close();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("#202: searchVector returns full memory rows with scores, sorted desc", () => {
|
|
69
|
+
const store = make();
|
|
70
|
+
const a = store.save({ type: "project", title: "标题A", content: "内容A", importance: 4 });
|
|
71
|
+
const b = store.save({ type: "decision", title: "标题B", content: "内容B", importance: 2 });
|
|
72
|
+
store.setEmbedding(a.id, [1, 0, 0]);
|
|
73
|
+
store.setEmbedding(b.id, [0.6, 0.8, 0]);
|
|
74
|
+
|
|
75
|
+
const hits = store.searchVector([1, 0, 0], { limit: 5 });
|
|
76
|
+
assert.equal(hits.length, 2);
|
|
77
|
+
assert.equal(hits[0].id, a.id, "closest vector first");
|
|
78
|
+
assert.ok(hits[0].score > hits[1].score, "descending scores");
|
|
79
|
+
for (const hit of hits) {
|
|
80
|
+
assert.ok(hit.title && hit.content, "full fields refetched for top hits");
|
|
81
|
+
assert.equal(typeof hit.score, "number");
|
|
82
|
+
assert.equal(hit.importance > 0, true, "non-score fields intact");
|
|
83
|
+
}
|
|
84
|
+
store.close();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("#202: getParsedEmbedding / getEmbeddings reflect rewrites (dedup path cache)", () => {
|
|
88
|
+
const store = make();
|
|
89
|
+
const { id } = store.save({ type: "project", title: "A", content: "a" });
|
|
90
|
+
store.setEmbedding(id, [1, 2, 3]);
|
|
91
|
+
assert.deepEqual(store.getParsedEmbedding(id), [1, 2, 3]);
|
|
92
|
+
assert.deepEqual(store.getEmbeddings([id]).get(id), [1, 2, 3]);
|
|
93
|
+
|
|
94
|
+
store.setEmbedding(id, [4, 5, 6]);
|
|
95
|
+
assert.deepEqual(store.getParsedEmbedding(id), [4, 5, 6], "cache-first read sees the rewrite");
|
|
96
|
+
assert.deepEqual(store.getEmbeddings([id]).get(id), [4, 5, 6]);
|
|
97
|
+
store.close();
|
|
98
|
+
});
|
|
@@ -1,191 +1,191 @@
|
|
|
1
|
-
import test from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
import { mkdtempSync, readFileSync, writeFileSync, existsSync, rmSync } from "node:fs";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
|
-
import { join } from "node:path";
|
|
6
|
-
import { createStore } from "../src/store.js";
|
|
7
|
-
import { createService } from "../src/service.js";
|
|
8
|
-
import { createMirror } from "../src/mirror.js";
|
|
9
|
-
|
|
10
|
-
// 回归(issue #430):写路径上的全表物化。此前每一次业务写都要走两处
|
|
11
|
-
// all() + 逐行 toRow(27 字段 + 三次 JSON.parse):
|
|
12
|
-
// 1. 质量过滤的近重复判定只要最近 20 条 content,却先物化整库再 slice(0,20)
|
|
13
|
-
// (5k 行实测 100ms 级 → 本批换成单列有界查询后 0.06ms 级);
|
|
14
|
-
// 2. syncMirror 先物化整库再逐行 filter 掉 archived/forgotten/document。
|
|
15
|
-
// 本批修复:store 新增单列有界读 recentContents();list() 两条分支的 SELECT *
|
|
16
|
-
// 换成显式列清单(memoryColumns,排除 embedding);syncMirror 改走 list 的无界
|
|
17
|
-
// 档 + SQL 侧过滤。下面四条锁不测墙钟(CI 矩阵 ubuntu+windows × node 22/24 并行,
|
|
18
|
-
// 计时断言不稳),锁的是「谁被物化」这个形状本身。
|
|
19
|
-
|
|
20
|
-
function make(over = {}) {
|
|
21
|
-
const store = createStore(":memory:");
|
|
22
|
-
return { store, config: { memoryQualityFilter: { enabled: true }, ...over } };
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
test("#430: recentContents mirrors the all() prefix it replaced", () => {
|
|
26
|
-
const { store } = make();
|
|
27
|
-
for (let i = 0; i < 25; i += 1) {
|
|
28
|
-
store.save({ type: "project", title: `t${i}`, content: `c${i}`, importance: 3 });
|
|
29
|
-
}
|
|
30
|
-
// 旧写法:all().slice(0, 20).map((m) => m.content ?? "")。集合锁与逐位锁都要:
|
|
31
|
-
// 一旦把 ORDER BY 丢掉(或反向),只要 LIMIT 不低于表行数,取到的行一个不少、
|
|
32
|
-
// 只是顺序全反过来,集合锁抓不到这类回归(变异检验证实,见 PR 讨论)。
|
|
33
|
-
// 逐位比较在此安全:store.js 的 per-instance 单调时间戳守卫保证同一实例内
|
|
34
|
-
// updated_at 严格递增,故这几行的先后关系是确定的。
|
|
35
|
-
const expected = store.all().slice(0, 20).map((m) => m.content ?? "");
|
|
36
|
-
const actual = store.recentContents(20);
|
|
37
|
-
assert.equal(actual.length, 20);
|
|
38
|
-
assert.deepEqual(actual, expected, "same 20 contents in the same order as the old prefix");
|
|
39
|
-
assert.deepEqual([...actual].sort(), [...expected].sort(), "same 20 contents as the old prefix (order-agnostic backstop)");
|
|
40
|
-
assert.equal(store.recentContents().length, 20, "default limit is 20");
|
|
41
|
-
assert.equal(store.recentContents(3).length, 3);
|
|
42
|
-
// sanitizePage 口径:0 / 负数 / 非整数落回默认值,而不是 SQLite 里 LIMIT 无界
|
|
43
|
-
assert.equal(store.recentContents(0).length, 20, "limit 0 falls back to the default, never unbounded");
|
|
44
|
-
assert.equal(store.recentContents(-1).length, 20);
|
|
45
|
-
assert.equal(store.recentContents(undefined).length, 20);
|
|
46
|
-
// 口径与 all() 一致:不过滤 archived(近重复判定要连归档内容一起看)
|
|
47
|
-
const { id } = store.save({ type: "project", title: "旧", content: "归档正文", importance: 3 });
|
|
48
|
-
store.setArchived(id, true);
|
|
49
|
-
assert.ok(store.recentContents(20).includes("归档正文"), "archived rows still take part in near-duplicate detection");
|
|
50
|
-
// 空库不炸
|
|
51
|
-
const fresh = createStore(":memory:");
|
|
52
|
-
assert.deepEqual(fresh.recentContents(20), []);
|
|
53
|
-
store.close();
|
|
54
|
-
fresh.close();
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test("#430: list() projects columns and never leaks embedding", () => {
|
|
58
|
-
const { store } = make();
|
|
59
|
-
const { id } = store.save({ type: "project", title: "带向量", content: "正文", importance: 3 });
|
|
60
|
-
store.setEmbedding(id, [1, 0, 0]);
|
|
61
|
-
for (const opts of [{ limit: 50 }, { limit: null }, { order: "chrono", limit: 10 }, { type: "project", limit: null }]) {
|
|
62
|
-
const rows = store.list(opts);
|
|
63
|
-
assert.ok(rows.length > 0, `list(${JSON.stringify(opts)}) returned rows`);
|
|
64
|
-
for (const row of rows) {
|
|
65
|
-
assert.equal("embedding" in row, false, `embedding never leaks into list rows (${JSON.stringify(opts)})`);
|
|
66
|
-
assert.equal(typeof row.content, "string", "content still projected");
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
store.close();
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test("#430: the write path reads explicit, bounded columns (no SELECT *, no embedding)", () => {
|
|
73
|
-
const { store } = make();
|
|
74
|
-
store.save({ type: "project", title: "带向量", content: "正文", importance: 3 });
|
|
75
|
-
// 语句级探针(不测墙钟):SELECT * 会把全表最重的 embedding 列拖进内存再被
|
|
76
|
-
// toRow 丢掉,且无界档实测 5k 行 263ms → 117ms。这里直接看发出去的 SQL——
|
|
77
|
-
// 将来谁把列投影改回 SELECT *(或手工把 embedding 加回清单),本锁立刻转红,
|
|
78
|
-
// 而「行里没有 embedding」那条锁抓不到这种回归(toRow 本就不输出向量)。
|
|
79
|
-
const seen = [];
|
|
80
|
-
const orig = store.db.prepare.bind(store.db);
|
|
81
|
-
store.db.prepare = (sql) => { seen.push(String(sql)); return orig(sql); };
|
|
82
|
-
const listSql = seen.length;
|
|
83
|
-
store.list({ limit: 50 });
|
|
84
|
-
store.list({ limit: null });
|
|
85
|
-
store.list({ type: "project", limit: 10, order: "chrono" });
|
|
86
|
-
const probed = seen.length - listSql;
|
|
87
|
-
assert.ok(probed >= 3, `expected three distinct list queries, saw ${probed}`);
|
|
88
|
-
for (const sql of seen) {
|
|
89
|
-
assert.ok(!/SELECT\s+\*/i.test(sql), `write path must project explicit columns: ${sql}`);
|
|
90
|
-
assert.ok(!/\bembedding\b/.test(sql), `write path must not read the embedding column: ${sql}`);
|
|
91
|
-
}
|
|
92
|
-
// 质量过滤那条单列读同样要探:它一旦退回 SELECT *,多读的正是全表最重的列。
|
|
93
|
-
seen.length = 0;
|
|
94
|
-
store.recentContents(20);
|
|
95
|
-
assert.equal(seen.length, 1, "recentContents issues exactly one query");
|
|
96
|
-
assert.ok(!/SELECT\s+\*/i.test(seen[0]), `recentContents must read one column: ${seen[0]}`);
|
|
97
|
-
assert.ok(!/\bembedding\b/.test(seen[0]), "recentContents must not read the embedding column");
|
|
98
|
-
assert.match(seen[0], /\bLIMIT\b/i, `recentContents must stay bounded: ${seen[0]}`);
|
|
99
|
-
store.close();
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test("#430: quality filter reads recent contents without materializing the table", () => {
|
|
103
|
-
const { store, config } = make();
|
|
104
|
-
// 抛错探针(先例 audit.test.js:182、mirror-dirty.test.js:383):写路径一旦再退回
|
|
105
|
-
// all(),本用例立刻失败,而不是等到某个 profile 上再被观测到。
|
|
106
|
-
store.all = () => { throw new Error("write path must not materialize the whole table"); };
|
|
107
|
-
const service = createService({ store, mirror: null, config });
|
|
108
|
-
const base = "今天下午去操场跑步三圈然后回来洗澡";
|
|
109
|
-
const first = service.saveWithDedupe({ type: "history", title: "记录A", content: base });
|
|
110
|
-
assert.equal(first.action, "created");
|
|
111
|
-
const second = service.saveWithDedupe({ type: "history", title: "记录B", content: `${base}${base}${base}` });
|
|
112
|
-
const row = store.getById(second.memory.id);
|
|
113
|
-
assert.equal(row.archived, true, "near-duplicate still archived");
|
|
114
|
-
assert.ok(row.tags.includes("low_quality"), "low_quality tag still added");
|
|
115
|
-
assert.ok(row.quality_score < 30, `score still below the archive threshold: ${row.quality_score}`);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test("#430: syncMirror covers the active set without materializing it", () => {
|
|
119
|
-
const dir = mkdtempSync(join(tmpdir(), "dsh-mneme-430-"));
|
|
120
|
-
try {
|
|
121
|
-
const { store, config } = make();
|
|
122
|
-
const mirror = createMirror(dir);
|
|
123
|
-
// 先按正常路径写一次,把镜像文件建起来
|
|
124
|
-
const seed = createService({ store, mirror, config });
|
|
125
|
-
const a = store.save({ type: "project", title: "活跃项目", content: "项目正文", importance: 3 });
|
|
126
|
-
store.save({ type: "preference", title: "偏好", content: "偏好正文", importance: 3 });
|
|
127
|
-
const hidden = store.save({ type: "project", title: "归档项目", content: "归档正文", importance: 3 });
|
|
128
|
-
store.setArchived(hidden.id, true);
|
|
129
|
-
const held = store.save({ type: "project", title: "遗忘项目", content: "遗忘正文", importance: 3 });
|
|
130
|
-
store.setForget(held.id, true);
|
|
131
|
-
seed.saveWithDedupe({ type: "project", title: "触发同步", content: "第一次触发同步的正文,长度足够不触发短内容规则", importance: 3 });
|
|
132
|
-
|
|
133
|
-
// 写路径不得再出现全表物化
|
|
134
|
-
store.all = () => { throw new Error("syncMirror must not materialize the whole table"); };
|
|
135
|
-
const svc = createService({ store, mirror, config });
|
|
136
|
-
const res = svc.saveWithDedupe({ type: "project", title: "第二次触发", content: "第二次触发同步的正文,内容与上一句不同以免被判近重复", importance: 3 });
|
|
137
|
-
assert.equal(res.action, "created");
|
|
138
|
-
assert.equal(res.memory.archived, false, "quality filter left it active (mirror only renders active rows)");
|
|
139
|
-
|
|
140
|
-
const text = readFileSync(join(dir, "projects.md"), "utf8");
|
|
141
|
-
assert.ok(text.includes(a.id), "active row still rendered");
|
|
142
|
-
assert.ok(text.includes(res.memory.id), "new row still rendered");
|
|
143
|
-
assert.ok(!text.includes("归档项目"), "archived row stays out");
|
|
144
|
-
assert.ok(!text.includes("遗忘项目"), "forgotten row stays out");
|
|
145
|
-
assert.ok(readFileSync(join(dir, "preferences.md"), "utf8").includes("偏好正文"), "sibling type rendered");
|
|
146
|
-
store.close();
|
|
147
|
-
} finally {
|
|
148
|
-
rmSync(dir, { recursive: true, force: true });
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
test("#430: the document gate is decided in SQL, before anything is rendered", () => {
|
|
153
|
-
const dir = mkdtempSync(join(tmpdir(), "dsh-mneme-430-doc-"));
|
|
154
|
-
try {
|
|
155
|
-
const { store, config } = make({ memoryQualityFilter: { enabled: true }, documentMemoryEnabled: false });
|
|
156
|
-
const mirror = createMirror(dir);
|
|
157
|
-
const svc = createService({ store, mirror, config });
|
|
158
|
-
// 真 document 行:doc_path 只能经 store.saveDocument 铸造(通用 save 整类拒绝)。
|
|
159
|
-
// 这条用例必须让库里真的有 document 行——上一版用例库里一行都没有,
|
|
160
|
-
// 「闸关时不渲染 document」就无法与「根本没见过 document」区分(变异检验漏网)。
|
|
161
|
-
const doc = store.saveDocument({
|
|
162
|
-
type: "document",
|
|
163
|
-
title: "指针文档",
|
|
164
|
-
content: "文档摘要正文,长度足够",
|
|
165
|
-
doc_path: "docs/pointer.md",
|
|
166
|
-
importance: 3
|
|
167
|
-
});
|
|
168
|
-
svc.saveWithDedupe({ type: "project", title: "项目", content: "项目正文,长度足够绕开短内容归档规则", importance: 3 });
|
|
169
|
-
const docPath = mirror.filePath("document");
|
|
170
|
-
// 闸关(#296 第二批默认):有真指针行也不许渲染出 documents.md
|
|
171
|
-
assert.equal(existsSync(docPath), false, "documents.md stays absent while the gate is off");
|
|
172
|
-
// 陈旧视图必须被删掉:不留一个「看着还在、其实已关」的视图
|
|
173
|
-
writeFileSync(docPath, "陈旧视图", "utf8");
|
|
174
|
-
svc.saveWithDedupe({ type: "project", title: "再写一条", content: "再写一条正文,与上一条正文互不相似", importance: 3 });
|
|
175
|
-
assert.equal(existsSync(docPath), false, "stale documents.md pruned while the gate is off");
|
|
176
|
-
assert.equal(existsSync(join(dir, "index.md")), false, "index.md pruned while the gate is off");
|
|
177
|
-
assert.ok(readFileSync(join(dir, "projects.md"), "utf8").includes("项目正文"), "sibling type still rendered");
|
|
178
|
-
// 反向:闸开时同一份指针行确实会渲染(证明上面那条断言有判别力,而不是
|
|
179
|
-
// 因为 document 行从没进入过渲染集),关回去再必须删掉。
|
|
180
|
-
config.documentMemoryEnabled = true;
|
|
181
|
-
svc.saveWithDedupe({ type: "project", title: "闸开", content: "闸开后的正文,与前面几条互不相似", importance: 3 });
|
|
182
|
-
assert.ok(existsSync(docPath), "documents.md rendered while the gate is on");
|
|
183
|
-
assert.ok(readFileSync(docPath, "utf8").includes(doc.id), "pointer row is what gets rendered");
|
|
184
|
-
config.documentMemoryEnabled = false;
|
|
185
|
-
svc.saveWithDedupe({ type: "project", title: "闸关回去", content: "闸关回去后的正文,同样互不相似", importance: 3 });
|
|
186
|
-
assert.equal(existsSync(docPath), false, "documents.md pruned again once the gate flips back off");
|
|
187
|
-
store.close();
|
|
188
|
-
} finally {
|
|
189
|
-
rmSync(dir, { recursive: true, force: true });
|
|
190
|
-
}
|
|
191
|
-
});
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtempSync, readFileSync, writeFileSync, existsSync, rmSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { createStore } from "../src/store.js";
|
|
7
|
+
import { createService } from "../src/service.js";
|
|
8
|
+
import { createMirror } from "../src/mirror.js";
|
|
9
|
+
|
|
10
|
+
// 回归(issue #430):写路径上的全表物化。此前每一次业务写都要走两处
|
|
11
|
+
// all() + 逐行 toRow(27 字段 + 三次 JSON.parse):
|
|
12
|
+
// 1. 质量过滤的近重复判定只要最近 20 条 content,却先物化整库再 slice(0,20)
|
|
13
|
+
// (5k 行实测 100ms 级 → 本批换成单列有界查询后 0.06ms 级);
|
|
14
|
+
// 2. syncMirror 先物化整库再逐行 filter 掉 archived/forgotten/document。
|
|
15
|
+
// 本批修复:store 新增单列有界读 recentContents();list() 两条分支的 SELECT *
|
|
16
|
+
// 换成显式列清单(memoryColumns,排除 embedding);syncMirror 改走 list 的无界
|
|
17
|
+
// 档 + SQL 侧过滤。下面四条锁不测墙钟(CI 矩阵 ubuntu+windows × node 22/24 并行,
|
|
18
|
+
// 计时断言不稳),锁的是「谁被物化」这个形状本身。
|
|
19
|
+
|
|
20
|
+
function make(over = {}) {
|
|
21
|
+
const store = createStore(":memory:");
|
|
22
|
+
return { store, config: { memoryQualityFilter: { enabled: true }, ...over } };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
test("#430: recentContents mirrors the all() prefix it replaced", () => {
|
|
26
|
+
const { store } = make();
|
|
27
|
+
for (let i = 0; i < 25; i += 1) {
|
|
28
|
+
store.save({ type: "project", title: `t${i}`, content: `c${i}`, importance: 3 });
|
|
29
|
+
}
|
|
30
|
+
// 旧写法:all().slice(0, 20).map((m) => m.content ?? "")。集合锁与逐位锁都要:
|
|
31
|
+
// 一旦把 ORDER BY 丢掉(或反向),只要 LIMIT 不低于表行数,取到的行一个不少、
|
|
32
|
+
// 只是顺序全反过来,集合锁抓不到这类回归(变异检验证实,见 PR 讨论)。
|
|
33
|
+
// 逐位比较在此安全:store.js 的 per-instance 单调时间戳守卫保证同一实例内
|
|
34
|
+
// updated_at 严格递增,故这几行的先后关系是确定的。
|
|
35
|
+
const expected = store.all().slice(0, 20).map((m) => m.content ?? "");
|
|
36
|
+
const actual = store.recentContents(20);
|
|
37
|
+
assert.equal(actual.length, 20);
|
|
38
|
+
assert.deepEqual(actual, expected, "same 20 contents in the same order as the old prefix");
|
|
39
|
+
assert.deepEqual([...actual].sort(), [...expected].sort(), "same 20 contents as the old prefix (order-agnostic backstop)");
|
|
40
|
+
assert.equal(store.recentContents().length, 20, "default limit is 20");
|
|
41
|
+
assert.equal(store.recentContents(3).length, 3);
|
|
42
|
+
// sanitizePage 口径:0 / 负数 / 非整数落回默认值,而不是 SQLite 里 LIMIT 无界
|
|
43
|
+
assert.equal(store.recentContents(0).length, 20, "limit 0 falls back to the default, never unbounded");
|
|
44
|
+
assert.equal(store.recentContents(-1).length, 20);
|
|
45
|
+
assert.equal(store.recentContents(undefined).length, 20);
|
|
46
|
+
// 口径与 all() 一致:不过滤 archived(近重复判定要连归档内容一起看)
|
|
47
|
+
const { id } = store.save({ type: "project", title: "旧", content: "归档正文", importance: 3 });
|
|
48
|
+
store.setArchived(id, true);
|
|
49
|
+
assert.ok(store.recentContents(20).includes("归档正文"), "archived rows still take part in near-duplicate detection");
|
|
50
|
+
// 空库不炸
|
|
51
|
+
const fresh = createStore(":memory:");
|
|
52
|
+
assert.deepEqual(fresh.recentContents(20), []);
|
|
53
|
+
store.close();
|
|
54
|
+
fresh.close();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("#430: list() projects columns and never leaks embedding", () => {
|
|
58
|
+
const { store } = make();
|
|
59
|
+
const { id } = store.save({ type: "project", title: "带向量", content: "正文", importance: 3 });
|
|
60
|
+
store.setEmbedding(id, [1, 0, 0]);
|
|
61
|
+
for (const opts of [{ limit: 50 }, { limit: null }, { order: "chrono", limit: 10 }, { type: "project", limit: null }]) {
|
|
62
|
+
const rows = store.list(opts);
|
|
63
|
+
assert.ok(rows.length > 0, `list(${JSON.stringify(opts)}) returned rows`);
|
|
64
|
+
for (const row of rows) {
|
|
65
|
+
assert.equal("embedding" in row, false, `embedding never leaks into list rows (${JSON.stringify(opts)})`);
|
|
66
|
+
assert.equal(typeof row.content, "string", "content still projected");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
store.close();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("#430: the write path reads explicit, bounded columns (no SELECT *, no embedding)", () => {
|
|
73
|
+
const { store } = make();
|
|
74
|
+
store.save({ type: "project", title: "带向量", content: "正文", importance: 3 });
|
|
75
|
+
// 语句级探针(不测墙钟):SELECT * 会把全表最重的 embedding 列拖进内存再被
|
|
76
|
+
// toRow 丢掉,且无界档实测 5k 行 263ms → 117ms。这里直接看发出去的 SQL——
|
|
77
|
+
// 将来谁把列投影改回 SELECT *(或手工把 embedding 加回清单),本锁立刻转红,
|
|
78
|
+
// 而「行里没有 embedding」那条锁抓不到这种回归(toRow 本就不输出向量)。
|
|
79
|
+
const seen = [];
|
|
80
|
+
const orig = store.db.prepare.bind(store.db);
|
|
81
|
+
store.db.prepare = (sql) => { seen.push(String(sql)); return orig(sql); };
|
|
82
|
+
const listSql = seen.length;
|
|
83
|
+
store.list({ limit: 50 });
|
|
84
|
+
store.list({ limit: null });
|
|
85
|
+
store.list({ type: "project", limit: 10, order: "chrono" });
|
|
86
|
+
const probed = seen.length - listSql;
|
|
87
|
+
assert.ok(probed >= 3, `expected three distinct list queries, saw ${probed}`);
|
|
88
|
+
for (const sql of seen) {
|
|
89
|
+
assert.ok(!/SELECT\s+\*/i.test(sql), `write path must project explicit columns: ${sql}`);
|
|
90
|
+
assert.ok(!/\bembedding\b/.test(sql), `write path must not read the embedding column: ${sql}`);
|
|
91
|
+
}
|
|
92
|
+
// 质量过滤那条单列读同样要探:它一旦退回 SELECT *,多读的正是全表最重的列。
|
|
93
|
+
seen.length = 0;
|
|
94
|
+
store.recentContents(20);
|
|
95
|
+
assert.equal(seen.length, 1, "recentContents issues exactly one query");
|
|
96
|
+
assert.ok(!/SELECT\s+\*/i.test(seen[0]), `recentContents must read one column: ${seen[0]}`);
|
|
97
|
+
assert.ok(!/\bembedding\b/.test(seen[0]), "recentContents must not read the embedding column");
|
|
98
|
+
assert.match(seen[0], /\bLIMIT\b/i, `recentContents must stay bounded: ${seen[0]}`);
|
|
99
|
+
store.close();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("#430: quality filter reads recent contents without materializing the table", () => {
|
|
103
|
+
const { store, config } = make();
|
|
104
|
+
// 抛错探针(先例 audit.test.js:182、mirror-dirty.test.js:383):写路径一旦再退回
|
|
105
|
+
// all(),本用例立刻失败,而不是等到某个 profile 上再被观测到。
|
|
106
|
+
store.all = () => { throw new Error("write path must not materialize the whole table"); };
|
|
107
|
+
const service = createService({ store, mirror: null, config });
|
|
108
|
+
const base = "今天下午去操场跑步三圈然后回来洗澡";
|
|
109
|
+
const first = service.saveWithDedupe({ type: "history", title: "记录A", content: base });
|
|
110
|
+
assert.equal(first.action, "created");
|
|
111
|
+
const second = service.saveWithDedupe({ type: "history", title: "记录B", content: `${base}${base}${base}` });
|
|
112
|
+
const row = store.getById(second.memory.id);
|
|
113
|
+
assert.equal(row.archived, true, "near-duplicate still archived");
|
|
114
|
+
assert.ok(row.tags.includes("low_quality"), "low_quality tag still added");
|
|
115
|
+
assert.ok(row.quality_score < 30, `score still below the archive threshold: ${row.quality_score}`);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("#430: syncMirror covers the active set without materializing it", () => {
|
|
119
|
+
const dir = mkdtempSync(join(tmpdir(), "dsh-mneme-430-"));
|
|
120
|
+
try {
|
|
121
|
+
const { store, config } = make();
|
|
122
|
+
const mirror = createMirror(dir);
|
|
123
|
+
// 先按正常路径写一次,把镜像文件建起来
|
|
124
|
+
const seed = createService({ store, mirror, config });
|
|
125
|
+
const a = store.save({ type: "project", title: "活跃项目", content: "项目正文", importance: 3 });
|
|
126
|
+
store.save({ type: "preference", title: "偏好", content: "偏好正文", importance: 3 });
|
|
127
|
+
const hidden = store.save({ type: "project", title: "归档项目", content: "归档正文", importance: 3 });
|
|
128
|
+
store.setArchived(hidden.id, true);
|
|
129
|
+
const held = store.save({ type: "project", title: "遗忘项目", content: "遗忘正文", importance: 3 });
|
|
130
|
+
store.setForget(held.id, true);
|
|
131
|
+
seed.saveWithDedupe({ type: "project", title: "触发同步", content: "第一次触发同步的正文,长度足够不触发短内容规则", importance: 3 });
|
|
132
|
+
|
|
133
|
+
// 写路径不得再出现全表物化
|
|
134
|
+
store.all = () => { throw new Error("syncMirror must not materialize the whole table"); };
|
|
135
|
+
const svc = createService({ store, mirror, config });
|
|
136
|
+
const res = svc.saveWithDedupe({ type: "project", title: "第二次触发", content: "第二次触发同步的正文,内容与上一句不同以免被判近重复", importance: 3 });
|
|
137
|
+
assert.equal(res.action, "created");
|
|
138
|
+
assert.equal(res.memory.archived, false, "quality filter left it active (mirror only renders active rows)");
|
|
139
|
+
|
|
140
|
+
const text = readFileSync(join(dir, "projects.md"), "utf8");
|
|
141
|
+
assert.ok(text.includes(a.id), "active row still rendered");
|
|
142
|
+
assert.ok(text.includes(res.memory.id), "new row still rendered");
|
|
143
|
+
assert.ok(!text.includes("归档项目"), "archived row stays out");
|
|
144
|
+
assert.ok(!text.includes("遗忘项目"), "forgotten row stays out");
|
|
145
|
+
assert.ok(readFileSync(join(dir, "preferences.md"), "utf8").includes("偏好正文"), "sibling type rendered");
|
|
146
|
+
store.close();
|
|
147
|
+
} finally {
|
|
148
|
+
rmSync(dir, { recursive: true, force: true });
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("#430: the document gate is decided in SQL, before anything is rendered", () => {
|
|
153
|
+
const dir = mkdtempSync(join(tmpdir(), "dsh-mneme-430-doc-"));
|
|
154
|
+
try {
|
|
155
|
+
const { store, config } = make({ memoryQualityFilter: { enabled: true }, documentMemoryEnabled: false });
|
|
156
|
+
const mirror = createMirror(dir);
|
|
157
|
+
const svc = createService({ store, mirror, config });
|
|
158
|
+
// 真 document 行:doc_path 只能经 store.saveDocument 铸造(通用 save 整类拒绝)。
|
|
159
|
+
// 这条用例必须让库里真的有 document 行——上一版用例库里一行都没有,
|
|
160
|
+
// 「闸关时不渲染 document」就无法与「根本没见过 document」区分(变异检验漏网)。
|
|
161
|
+
const doc = store.saveDocument({
|
|
162
|
+
type: "document",
|
|
163
|
+
title: "指针文档",
|
|
164
|
+
content: "文档摘要正文,长度足够",
|
|
165
|
+
doc_path: "docs/pointer.md",
|
|
166
|
+
importance: 3
|
|
167
|
+
});
|
|
168
|
+
svc.saveWithDedupe({ type: "project", title: "项目", content: "项目正文,长度足够绕开短内容归档规则", importance: 3 });
|
|
169
|
+
const docPath = mirror.filePath("document");
|
|
170
|
+
// 闸关(#296 第二批默认):有真指针行也不许渲染出 documents.md
|
|
171
|
+
assert.equal(existsSync(docPath), false, "documents.md stays absent while the gate is off");
|
|
172
|
+
// 陈旧视图必须被删掉:不留一个「看着还在、其实已关」的视图
|
|
173
|
+
writeFileSync(docPath, "陈旧视图", "utf8");
|
|
174
|
+
svc.saveWithDedupe({ type: "project", title: "再写一条", content: "再写一条正文,与上一条正文互不相似", importance: 3 });
|
|
175
|
+
assert.equal(existsSync(docPath), false, "stale documents.md pruned while the gate is off");
|
|
176
|
+
assert.equal(existsSync(join(dir, "index.md")), false, "index.md pruned while the gate is off");
|
|
177
|
+
assert.ok(readFileSync(join(dir, "projects.md"), "utf8").includes("项目正文"), "sibling type still rendered");
|
|
178
|
+
// 反向:闸开时同一份指针行确实会渲染(证明上面那条断言有判别力,而不是
|
|
179
|
+
// 因为 document 行从没进入过渲染集),关回去再必须删掉。
|
|
180
|
+
config.documentMemoryEnabled = true;
|
|
181
|
+
svc.saveWithDedupe({ type: "project", title: "闸开", content: "闸开后的正文,与前面几条互不相似", importance: 3 });
|
|
182
|
+
assert.ok(existsSync(docPath), "documents.md rendered while the gate is on");
|
|
183
|
+
assert.ok(readFileSync(docPath, "utf8").includes(doc.id), "pointer row is what gets rendered");
|
|
184
|
+
config.documentMemoryEnabled = false;
|
|
185
|
+
svc.saveWithDedupe({ type: "project", title: "闸关回去", content: "闸关回去后的正文,同样互不相似", importance: 3 });
|
|
186
|
+
assert.equal(existsSync(docPath), false, "documents.md pruned again once the gate flips back off");
|
|
187
|
+
store.close();
|
|
188
|
+
} finally {
|
|
189
|
+
rmSync(dir, { recursive: true, force: true });
|
|
190
|
+
}
|
|
191
|
+
});
|