@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.
- package/LICENSE +21 -21
- package/README.md +830 -828
- package/assets/icon.svg +3 -3
- package/bin/cli.mjs +964 -964
- package/bin/dsh-mneme-choro.mjs +262 -254
- 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 -402
- 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 -3394
- 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 -13436
- package/lib/commands.js +95 -95
- package/lib/config-keys.js +144 -144
- package/lib/config.js +1361 -1317
- 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 -907
- package/lib/dream/narratives.js +44 -44
- package/lib/dream/sleep.js +2027 -1887
- package/lib/dream/triage.js +139 -0
- package/lib/dream.js +1879 -1764
- 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 -107
- package/lib/image-describe.js +158 -0
- package/lib/index.js +852 -822
- package/lib/inject.js +656 -639
- 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 -584
- 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 -3196
- package/lib/session-gate.js +91 -91
- package/lib/session-usage.js +350 -350
- package/lib/settings.js +827 -802
- 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 -5662
- 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 -0
- 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 -474
- package/lib/wiki/render.js +261 -187
- package/lib/wiki/scan.js +371 -371
- package/lib/wiki/t0.js +500 -463
- package/lib/wiki/t1-jobs.js +232 -232
- package/lib/wiki/t1.js +609 -502
- package/lib/write-admission.js +404 -404
- package/locale/en.json +6 -6
- package/locale/zh.json +6 -6
- package/package.json +15 -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 -204
- package/scripts/release-notes.mjs +164 -161
- 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 -3394
- 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 -1317
- 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 -907
- package/src/dream/narratives.js +44 -44
- package/src/dream/sleep.js +2027 -1887
- package/src/dream/triage.js +139 -0
- package/src/dream.js +1879 -1764
- 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 -107
- package/src/image-describe.js +158 -0
- package/src/index.js +852 -822
- package/src/inject.js +656 -639
- 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 -584
- 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 -3196
- package/src/session-gate.js +91 -91
- package/src/session-usage.js +350 -350
- package/src/settings.js +827 -802
- 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 -5662
- 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 -0
- 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 -474
- package/src/wiki/render.js +261 -187
- package/src/wiki/scan.js +371 -371
- package/src/wiki/t0.js +500 -463
- package/src/wiki/t1-jobs.js +232 -232
- package/src/wiki/t1.js +609 -502
- 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 -2988
- 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 -4184
- 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 -0
- package/test/conflict-ledger.test.js +242 -0
- 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 -320
- 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 -0
- 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 -209
- 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 -211
- 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 -106
- 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 -494
- 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 -0
- package/test/panel-tokens.test.js +245 -0
- 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 -136
- package/test/release-notes.test.js +265 -240
- 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 -178
- 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 -1418
- 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 -627
- package/test/wiki-claims.test.js +194 -0
- 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 -0
- package/test/wiki-queries.test.js +398 -277
- package/test/wiki-scan.test.js +208 -208
- package/test/wiki-store.test.js +190 -190
- package/test/wiki-t0.test.js +456 -380
- package/test/wiki-t1-jobs.test.js +125 -125
- package/test/wiki-t1.test.js +460 -235
- 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,70 +1,70 @@
|
|
|
1
|
-
// #532 选择性遗忘 × 事实更正的结构性回归锁。
|
|
2
|
-
//
|
|
3
|
-
// 现象(议题主楼):更正两个事实 → 只忘一个 → 保留的那个答回更正前的旧值。
|
|
4
|
-
// 根因是三段契约的接缝(docs/FORGET.md §1):更正由蒸馏落成新行(三元组去重不
|
|
5
|
-
// 命中、dream 冲突裁决阈值门控未运行)→ 旧行与新行之间没有持久失效关系 →
|
|
6
|
-
// memory_forget 按行抑制,合流在同一行里的其他事实连带退出可见面,旧值行成了
|
|
7
|
-
// 唯一幸存载体。
|
|
8
|
-
//
|
|
9
|
-
// 本文件锁两件事:
|
|
10
|
-
// ① 存储层合成轨迹(零 LLM):两行并存、遗忘新行、旧行原样幸存——这是现象的
|
|
11
|
-
// 存储层等价物。**机制修复落地时 ⑤ 的断言应当翻转**:更正行写入后旧行要么
|
|
12
|
-
// 被记上失效关系、要么退出活跃面;注释里写明翻转条件,防止它被当成「永远
|
|
13
|
-
// 该绿」的现状锁读。
|
|
14
|
-
// ② memory_forget 的工具描述必须带行粒度警示:模型无从得知「请求的语义粒度
|
|
15
|
-
// (一个事实)」与「工具的能力粒度(一行)」之间的落差,除非描述告诉它。
|
|
16
|
-
import test from "node:test";
|
|
17
|
-
import assert from "node:assert/strict";
|
|
18
|
-
import { createStore } from "../src/store.js";
|
|
19
|
-
import { createService } from "../src/service.js";
|
|
20
|
-
import { createTools } from "../src/tools.js";
|
|
21
|
-
|
|
22
|
-
test("#532 合成轨迹:更正落新行、遗忘新行、旧行原样幸存(机制修复落地时 ⑤ 翻转)", () => {
|
|
23
|
-
const store = createStore(":memory:");
|
|
24
|
-
const service = createService({ store, mirror: null, config: {} });
|
|
25
|
-
try {
|
|
26
|
-
// ① 原始陈述(对话 A 的蒸馏产物:颜色 + 时间合流一行)
|
|
27
|
-
const r1 = service.saveWithDedupe({
|
|
28
|
-
type: "preference", title: "纸模工具的收纳与整理",
|
|
29
|
-
content: "纸模工具放在雾蓝色收纳盒,每周三晚上八点整理一次", importance: 3
|
|
30
|
-
});
|
|
31
|
-
// ② 更正(对话 B:蒸馏器另起标题 → (type,title,scope) 三元组不命中 → 新行)
|
|
32
|
-
const r2 = service.saveWithDedupe({
|
|
33
|
-
type: "preference", title: "收纳盒颜色与整理时间的更正",
|
|
34
|
-
content: "收纳盒换成浅橙色,整理时间改为每周六上午九点", importance: 3
|
|
35
|
-
});
|
|
36
|
-
assert.notEqual(r1.memory.id, r2.memory.id, "标题不同 → 去重不命中,更正落成新行");
|
|
37
|
-
// ③ 结构断言:两行同时活跃,二者之间没有任何失效链接(本车道基线未含 #275
|
|
38
|
-
// 的 capacityStats,直接查列)
|
|
39
|
-
const activeCount = () => store.db.prepare("SELECT COUNT(*) AS n FROM memories WHERE forgotten = 0 AND archived = 0").get().n;
|
|
40
|
-
assert.equal(activeCount(), 2, "更正不淘汰旧行(dream 未运行的阈值下无失效关系)");
|
|
41
|
-
// ④ 「忘掉颜色」=按行遗忘新值行(模型在可见行里看到的颜色载体)
|
|
42
|
-
service.setForget(r2.memory.id, true);
|
|
43
|
-
// ⑤ 现象复现:新值行退出可见面后,旧值行仍是唯一幸存载体——这正是 #532
|
|
44
|
-
// 「保留的更正事实答回旧值」的存储层等价物。机制修复(写侧更正失效关系 /
|
|
45
|
-
// dream 触发前移,见 docs/FORGET.md §3)落地时翻转:旧行应被记上失效关系
|
|
46
|
-
// 或退出活跃面,断言改为不存在「幸存的旧值行」。
|
|
47
|
-
assert.equal(store.getById(r1.memory.id).forgotten, false, "旧行从未被任何机制波及(toRow 把列归一成 boolean)");
|
|
48
|
-
assert.match(store.getById(r1.memory.id).content, /每周三晚上八点/, "旧值仍在召回面");
|
|
49
|
-
// 被抑制的行可恢复(接口契约:行粒度遗忘不是删除)——恢复后两行回到并存
|
|
50
|
-
// 状态:失效关系的缺位不因遗忘/恢复而改变。
|
|
51
|
-
service.setForget(r2.memory.id, false);
|
|
52
|
-
assert.equal(activeCount(), 2);
|
|
53
|
-
} finally {
|
|
54
|
-
store.close();
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test("memory_forget 的工具描述必须带行粒度警示(#532 对话 5 的模型误用直接对策)", () => {
|
|
59
|
-
const registered = [];
|
|
60
|
-
const ctx = { tools: { register(def) { registered.push(def); return () => {}; } } };
|
|
61
|
-
createTools(ctx, { getById: () => undefined }, {}, undefined);
|
|
62
|
-
const forget = registered.find((d) => d.name === "memory_forget");
|
|
63
|
-
assert.ok(forget, "memory_forget 必须注册");
|
|
64
|
-
// 锁「语义」不锁逐字:描述必须让模型知道抑制按行、合流事实连带退出、
|
|
65
|
-
// 请求只针对一个事实时先看行内容或拆分。防的回归是有人把描述改回
|
|
66
|
-
// 「只说单条抑制」,模型在「忘掉 X」请求下重新对合流行整条下手。
|
|
67
|
-
const desc = String(forget.description || "");
|
|
68
|
-
assert.match(desc, /per ROW/i, "描述必须写明抑制粒度是行");
|
|
69
|
-
assert.match(desc, /memory_update|inspect|say in your reply/i, "描述必须给出合流行下的替代动作");
|
|
70
|
-
});
|
|
1
|
+
// #532 选择性遗忘 × 事实更正的结构性回归锁。
|
|
2
|
+
//
|
|
3
|
+
// 现象(议题主楼):更正两个事实 → 只忘一个 → 保留的那个答回更正前的旧值。
|
|
4
|
+
// 根因是三段契约的接缝(docs/FORGET.md §1):更正由蒸馏落成新行(三元组去重不
|
|
5
|
+
// 命中、dream 冲突裁决阈值门控未运行)→ 旧行与新行之间没有持久失效关系 →
|
|
6
|
+
// memory_forget 按行抑制,合流在同一行里的其他事实连带退出可见面,旧值行成了
|
|
7
|
+
// 唯一幸存载体。
|
|
8
|
+
//
|
|
9
|
+
// 本文件锁两件事:
|
|
10
|
+
// ① 存储层合成轨迹(零 LLM):两行并存、遗忘新行、旧行原样幸存——这是现象的
|
|
11
|
+
// 存储层等价物。**机制修复落地时 ⑤ 的断言应当翻转**:更正行写入后旧行要么
|
|
12
|
+
// 被记上失效关系、要么退出活跃面;注释里写明翻转条件,防止它被当成「永远
|
|
13
|
+
// 该绿」的现状锁读。
|
|
14
|
+
// ② memory_forget 的工具描述必须带行粒度警示:模型无从得知「请求的语义粒度
|
|
15
|
+
// (一个事实)」与「工具的能力粒度(一行)」之间的落差,除非描述告诉它。
|
|
16
|
+
import test from "node:test";
|
|
17
|
+
import assert from "node:assert/strict";
|
|
18
|
+
import { createStore } from "../src/store.js";
|
|
19
|
+
import { createService } from "../src/service.js";
|
|
20
|
+
import { createTools } from "../src/tools.js";
|
|
21
|
+
|
|
22
|
+
test("#532 合成轨迹:更正落新行、遗忘新行、旧行原样幸存(机制修复落地时 ⑤ 翻转)", () => {
|
|
23
|
+
const store = createStore(":memory:");
|
|
24
|
+
const service = createService({ store, mirror: null, config: {} });
|
|
25
|
+
try {
|
|
26
|
+
// ① 原始陈述(对话 A 的蒸馏产物:颜色 + 时间合流一行)
|
|
27
|
+
const r1 = service.saveWithDedupe({
|
|
28
|
+
type: "preference", title: "纸模工具的收纳与整理",
|
|
29
|
+
content: "纸模工具放在雾蓝色收纳盒,每周三晚上八点整理一次", importance: 3
|
|
30
|
+
});
|
|
31
|
+
// ② 更正(对话 B:蒸馏器另起标题 → (type,title,scope) 三元组不命中 → 新行)
|
|
32
|
+
const r2 = service.saveWithDedupe({
|
|
33
|
+
type: "preference", title: "收纳盒颜色与整理时间的更正",
|
|
34
|
+
content: "收纳盒换成浅橙色,整理时间改为每周六上午九点", importance: 3
|
|
35
|
+
});
|
|
36
|
+
assert.notEqual(r1.memory.id, r2.memory.id, "标题不同 → 去重不命中,更正落成新行");
|
|
37
|
+
// ③ 结构断言:两行同时活跃,二者之间没有任何失效链接(本车道基线未含 #275
|
|
38
|
+
// 的 capacityStats,直接查列)
|
|
39
|
+
const activeCount = () => store.db.prepare("SELECT COUNT(*) AS n FROM memories WHERE forgotten = 0 AND archived = 0").get().n;
|
|
40
|
+
assert.equal(activeCount(), 2, "更正不淘汰旧行(dream 未运行的阈值下无失效关系)");
|
|
41
|
+
// ④ 「忘掉颜色」=按行遗忘新值行(模型在可见行里看到的颜色载体)
|
|
42
|
+
service.setForget(r2.memory.id, true);
|
|
43
|
+
// ⑤ 现象复现:新值行退出可见面后,旧值行仍是唯一幸存载体——这正是 #532
|
|
44
|
+
// 「保留的更正事实答回旧值」的存储层等价物。机制修复(写侧更正失效关系 /
|
|
45
|
+
// dream 触发前移,见 docs/FORGET.md §3)落地时翻转:旧行应被记上失效关系
|
|
46
|
+
// 或退出活跃面,断言改为不存在「幸存的旧值行」。
|
|
47
|
+
assert.equal(store.getById(r1.memory.id).forgotten, false, "旧行从未被任何机制波及(toRow 把列归一成 boolean)");
|
|
48
|
+
assert.match(store.getById(r1.memory.id).content, /每周三晚上八点/, "旧值仍在召回面");
|
|
49
|
+
// 被抑制的行可恢复(接口契约:行粒度遗忘不是删除)——恢复后两行回到并存
|
|
50
|
+
// 状态:失效关系的缺位不因遗忘/恢复而改变。
|
|
51
|
+
service.setForget(r2.memory.id, false);
|
|
52
|
+
assert.equal(activeCount(), 2);
|
|
53
|
+
} finally {
|
|
54
|
+
store.close();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("memory_forget 的工具描述必须带行粒度警示(#532 对话 5 的模型误用直接对策)", () => {
|
|
59
|
+
const registered = [];
|
|
60
|
+
const ctx = { tools: { register(def) { registered.push(def); return () => {}; } } };
|
|
61
|
+
createTools(ctx, { getById: () => undefined }, {}, undefined);
|
|
62
|
+
const forget = registered.find((d) => d.name === "memory_forget");
|
|
63
|
+
assert.ok(forget, "memory_forget 必须注册");
|
|
64
|
+
// 锁「语义」不锁逐字:描述必须让模型知道抑制按行、合流事实连带退出、
|
|
65
|
+
// 请求只针对一个事实时先看行内容或拆分。防的回归是有人把描述改回
|
|
66
|
+
// 「只说单条抑制」,模型在「忘掉 X」请求下重新对合流行整条下手。
|
|
67
|
+
const desc = String(forget.description || "");
|
|
68
|
+
assert.match(desc, /per ROW/i, "描述必须写明抑制粒度是行");
|
|
69
|
+
assert.match(desc, /memory_update|inspect|say in your reply/i, "描述必须给出合流行下的替代动作");
|
|
70
|
+
});
|
|
@@ -1,185 +1,185 @@
|
|
|
1
|
-
// issue #24 块1 图谱锚定层(graphAnchoringEnabled)测试。
|
|
2
|
-
// 覆盖:默认关=单跳轴行为逐字节不变 / 开=级联扩散邻居挂联记忆进融合池 /
|
|
3
|
-
// 邻居分 = hop 配额 × tier(attr 1.0 > relation 0.9)/ 种子直达分不被邻居覆盖 /
|
|
4
|
-
// 级联深度上界 / graphSeedCap 收敛邻居总量 / 空关系表降级 / lightMode 关闭配置。
|
|
5
|
-
import test from "node:test";
|
|
6
|
-
import assert from "node:assert/strict";
|
|
7
|
-
import { createStore } from "../src/store.js";
|
|
8
|
-
import { createService } from "../src/service.js";
|
|
9
|
-
import { HOP_QUOTA } from "../src/graph/anchoring.js";
|
|
10
|
-
|
|
11
|
-
function makeService(config = {}) {
|
|
12
|
-
const store = createStore(":memory:");
|
|
13
|
-
const service = createService({ store, mirror: null, config });
|
|
14
|
-
return { store, service };
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function putMemory(store, service, { type = "project", title, content, importance = 3 }) {
|
|
18
|
-
return service.saveWithDedupe({ type, title, content, importance }).memory;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function linkAttr(store, service, { entityName, memory, key = "k", value = "v", type = "technology" }) {
|
|
22
|
-
let entity = store.findEntityByName(entityName);
|
|
23
|
-
if (!entity) entity = store.createEntity({ name: entityName, type });
|
|
24
|
-
store.saveAttr({ entity_id: entity.id, attr_key: key, attr_value: value, memory_id: memory.id });
|
|
25
|
-
return entity;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function linkRelation(store, { from, to, relType = "related", memoryId, type = "technology" }) {
|
|
29
|
-
const a = store.findEntityByName(from) ?? store.createEntity({ name: from, type });
|
|
30
|
-
const b = store.findEntityByName(to) ?? store.createEntity({ name: to, type });
|
|
31
|
-
store.saveRelation({ from_entity: a.id, to_entity: b.id, relation_type: relType, memory_id: memoryId });
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// ============================================================ 默认关(回归红线)
|
|
35
|
-
|
|
36
|
-
test("graphAnchoringEnabled=false (default): neighbor-linked memory NOT recalled, seed-only preserved", async () => {
|
|
37
|
-
const { store, service } = makeService({ entityRecallEnabled: true });
|
|
38
|
-
// 种子实体 PostgreSQL 挂一条记忆;关系边连到 PgBouncer(不挂记忆)。
|
|
39
|
-
const seedMem = putMemory(store, service, { title: "数据库迁移", content: "主库切换完成" });
|
|
40
|
-
linkAttr(store, service, { entityName: "PostgreSQL", memory: seedMem, key: "version", value: "16" });
|
|
41
|
-
// 邻居实体 + 它自己挂的记忆:必须不被默认关猜中。
|
|
42
|
-
const neighborMem = putMemory(store, service, { title: "连接池笔记", content: "PgBouncer 池调优记录" });
|
|
43
|
-
const neigh = linkAttr(store, service, { entityName: "PgBouncer", memory: neighborMem, key: "mode", value: "pool" });
|
|
44
|
-
// 关系:PostgreSQL —uses→ PgBouncer(种子 → 邻居),不直接挂 seedMem。
|
|
45
|
-
const a = store.findEntityByName("PostgreSQL");
|
|
46
|
-
linkRelation(store, { from: a.name, to: neigh.name, relType: "uses", memoryId: seedMem.id });
|
|
47
|
-
|
|
48
|
-
const rows = await service.searchMemories("PostgreSQL 现状", { mode: "auto" });
|
|
49
|
-
const ids = rows.map((r) => r.id);
|
|
50
|
-
assert.ok(ids.includes(seedMem.id), "seed-linked memory present");
|
|
51
|
-
assert.ok(!ids.includes(neighborMem.id), "neighbor memory absent when anchoring off");
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
// ============================================================ 开:级联扩散
|
|
55
|
-
|
|
56
|
-
test("graphAnchoringEnabled=true: neighbor-linked memory joins via cascade, seed keeps full tier score", async () => {
|
|
57
|
-
const { store, service } = makeService({ entityRecallEnabled: true, graphAnchoringEnabled: true });
|
|
58
|
-
const seedMem = putMemory(store, service, { title: "数据库迁移", content: "主库切换完成" });
|
|
59
|
-
linkAttr(store, service, { entityName: "PostgreSQL", memory: seedMem, key: "version", value: "16" });
|
|
60
|
-
const neighborMem = putMemory(store, service, { title: "连接池笔记", content: "PgBouncer 池调优记录" });
|
|
61
|
-
const neigh = linkAttr(store, service, { entityName: "PgBouncer", memory: neighborMem, key: "mode", value: "pool" });
|
|
62
|
-
const a = store.findEntityByName("PostgreSQL");
|
|
63
|
-
linkRelation(store, { from: a.name, to: neigh.name, relType: "uses", memoryId: seedMem.id });
|
|
64
|
-
|
|
65
|
-
// hybrid blend 走加权合算,便于精确断言 entity 轴分(blend: + we·score)。
|
|
66
|
-
const rows = await service.searchMemories("PostgreSQL 现状", { mode: "hybrid" });
|
|
67
|
-
const ids = rows.map((r) => r.id);
|
|
68
|
-
assert.ok(ids.includes(seedMem.id), "seed memory present");
|
|
69
|
-
assert.ok(ids.includes(neighborMem.id), "neighbor memory present via cascade");
|
|
70
|
-
// 种子 attr 1.0 × we(0.3) = 0.3;邻居 attr 1.0 × HOP_QUOTA[1](0.5) × we = 0.15。
|
|
71
|
-
const seedRow = rows.find((r) => r.id === seedMem.id);
|
|
72
|
-
const nbRow = rows.find((r) => r.id === neighborMem.id);
|
|
73
|
-
assert.ok(Math.abs(seedRow.score - 0.3) < 1e-9, "seed keeps full tier score");
|
|
74
|
-
assert.ok(Math.abs(nbRow.score - 0.3 * HOP_QUOTA[1]) < 1e-9, "neighbor hop-weighted");
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test("cascade respects hop-quota ordering: 1-hop neighbors score above 2-hop", async () => {
|
|
78
|
-
const { store, service } = makeService({ entityRecallEnabled: true, graphAnchoringEnabled: true });
|
|
79
|
-
const seedMem = putMemory(store, service, { title: "根服务", content: "服务 A 上线" });
|
|
80
|
-
linkAttr(store, service, { entityName: "ServiceA", memory: seedMem, key: "env", value: "prod" });
|
|
81
|
-
const hop1Mem = putMemory(store, service, { title: "链路一", content: "ServiceB 依赖记录" });
|
|
82
|
-
const hop2Mem = putMemory(store, service, { title: "链路二", content: "ServiceC 间接关联" });
|
|
83
|
-
const b = linkAttr(store, service, { entityName: "ServiceB", memory: hop1Mem, key: "tier", value: "edge" });
|
|
84
|
-
const c = linkAttr(store, service, { entityName: "ServiceC", memory: hop2Mem, key: "tier", value: "leaf" });
|
|
85
|
-
const a = store.findEntityByName("ServiceA");
|
|
86
|
-
// A—B 关系(hop1);B—C 关系(hop2)。
|
|
87
|
-
linkRelation(store, { from: a.name, to: b.name, relType: "depends", memoryId: hop1Mem.id });
|
|
88
|
-
linkRelation(store, { from: b.name, to: c.name, relType: "depends", memoryId: hop2Mem.id });
|
|
89
|
-
|
|
90
|
-
const rows = await service.searchMemories("ServiceA", { mode: "auto" });
|
|
91
|
-
const ids = rows.map((r) => r.id);
|
|
92
|
-
assert.ok(ids.includes(hop1Mem.id) && ids.includes(hop2Mem.id), "both hops recalled");
|
|
93
|
-
const h1 = rows.find((r) => r.id === hop1Mem.id);
|
|
94
|
-
const h2 = rows.find((r) => r.id === hop2Mem.id);
|
|
95
|
-
assert.ok(h1.score > h2.score, `1-hop (${h1.score}) scores above 2-hop (${h2.score})`);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test("empty relation table degrades cleanly (still returns seed memories)", async () => {
|
|
99
|
-
const { store, service } = makeService({ entityRecallEnabled: true, graphAnchoringEnabled: true });
|
|
100
|
-
const seedMem = putMemory(store, service, { title: "独立条", content: "单独存在" });
|
|
101
|
-
linkAttr(store, service, { entityName: "Standalone", memory: seedMem, key: "k", value: "v" });
|
|
102
|
-
const rows = await service.searchMemories("Standalone", { mode: "auto" });
|
|
103
|
-
assert.ok(rows.some((r) => r.id === seedMem.id), "seed memory survives with no relations");
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// ============================================================ 配置与配额
|
|
107
|
-
|
|
108
|
-
test("graphSeedCap bounds neighbor expansion (small cap cuts deeper hops)", async () => {
|
|
109
|
-
const { store, service } = makeService({
|
|
110
|
-
entityRecallEnabled: true, graphAnchoringEnabled: true, graphSeedCap: 1, graphCascadeDepth: 3
|
|
111
|
-
});
|
|
112
|
-
const seedMem = putMemory(store, service, { title: "根", content: "入口" });
|
|
113
|
-
const hop1Mem = putMemory(store, service, { title: "一层", content: "近邻" });
|
|
114
|
-
const hop2Mem = putMemory(store, service, { title: "二层", content: "远邻" });
|
|
115
|
-
const b = linkAttr(store, service, { entityName: "NodeB", memory: hop1Mem, key: "k", value: "v" });
|
|
116
|
-
const c = linkAttr(store, service, { entityName: "NodeC", memory: hop2Mem, key: "k", value: "v" });
|
|
117
|
-
const a = store.findEntityByName("入口占位"); // 无;用 NodeA
|
|
118
|
-
const nodeA = store.createEntity({ name: "NodeA", type: "technology" });
|
|
119
|
-
store.saveRelation({ from_entity: nodeA.id, to_entity: b.id, relation_type: "x", memory_id: seedMem.id });
|
|
120
|
-
store.saveRelation({ from_entity: b.id, to_entity: c.id, relation_type: "x", memory_id: hop1Mem.id });
|
|
121
|
-
|
|
122
|
-
const rows = await service.searchMemories("NodeA", { mode: "auto" });
|
|
123
|
-
const ids = rows.map((r) => r.id).sort();
|
|
124
|
-
// cap=1 → 配额 floor(1*0.5)=0 → max(1,…) 兜底只进 1 个 1-hop;2-hop 进不来。
|
|
125
|
-
assert.ok(ids.includes(hop1Mem.id), "1-hop fits within cap");
|
|
126
|
-
assert.ok(!ids.includes(hop2Mem.id), "2-hop cut by small cap");
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test("the anchoring pure module is self-checked", () => {
|
|
130
|
-
// 模块自带 _selfCheck 已在模块加载时断言;这里再锚定导出契约,防重构断链。
|
|
131
|
-
assert.ok(typeof HOP_QUOTA[1] === "number" && HOP_QUOTA[1] > HOP_QUOTA[2]);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
// ---- #24 复审回归(review findings 逐条落锁)--------------------------------
|
|
135
|
-
|
|
136
|
-
// 关档逐字节复用 #219 单跳轴:graphSeedCap 只在锚定开启后生效。种子裁剪若漏进
|
|
137
|
-
// 关档路径,cap=1 时第二个命中实体整支挂联记忆都会消失——这条专门钉死它。
|
|
138
|
-
test("anchoring off: graphSeedCap does not cap the legacy single-hop axis", async () => {
|
|
139
|
-
const { store, service } = makeService({ entityRecallEnabled: true, graphSeedCap: 1 });
|
|
140
|
-
const memA = putMemory(store, service, { title: "迁移记录", content: "主库切换完成" });
|
|
141
|
-
const memB = putMemory(store, service, { title: "池化记录", content: "连接池调优" });
|
|
142
|
-
linkAttr(store, service, { entityName: "Alpha", memory: memA, key: "k", value: "v" });
|
|
143
|
-
linkAttr(store, service, { entityName: "Beta", memory: memB, key: "k", value: "v" });
|
|
144
|
-
|
|
145
|
-
const rows = await service.searchMemories("Alpha Beta", { mode: "auto" });
|
|
146
|
-
const ids = rows.map((r) => r.id);
|
|
147
|
-
assert.ok(ids.includes(memA.id), "first seed-linked memory present");
|
|
148
|
-
assert.ok(ids.includes(memB.id), "second seed-linked memory must survive when anchoring is off");
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
// 级联要真走出 >1 跳:邻接若只带种子那一层,2-hop 实体挂联的记忆根本不进池。
|
|
152
|
-
// 本地图上 2-hop 记忆只经 AnchorC 可达(两条关系行的 memory_id 都留空,堵死
|
|
153
|
-
// 「1-hop 关系行旁路」),所以这条能红着抓回归。
|
|
154
|
-
test("cascade reaches 2 hops (lazy per-node adjacency, deeper hop weighted lower)", async () => {
|
|
155
|
-
const { store, service } = makeService({
|
|
156
|
-
entityRecallEnabled: true, graphAnchoringEnabled: true, graphCascadeDepth: 2
|
|
157
|
-
});
|
|
158
|
-
const seedMem = putMemory(store, service, { title: "根服务", content: "入口记录" });
|
|
159
|
-
const hop2Mem = putMemory(store, service, { title: "远环", content: "间接可达" });
|
|
160
|
-
const a = linkAttr(store, service, { entityName: "AnchorA", memory: seedMem, key: "k", value: "v" });
|
|
161
|
-
const c = linkAttr(store, service, { entityName: "AnchorC", memory: hop2Mem, key: "k", value: "v" });
|
|
162
|
-
const b = store.createEntity({ name: "AnchorB", type: "technology" });
|
|
163
|
-
store.saveRelation({ from_entity: a.id, to_entity: b.id, relation_type: "x" });
|
|
164
|
-
store.saveRelation({ from_entity: b.id, to_entity: c.id, relation_type: "x" });
|
|
165
|
-
|
|
166
|
-
const rows = await service.searchMemories("AnchorA", { mode: "hybrid" });
|
|
167
|
-
const ids = rows.map((r) => r.id);
|
|
168
|
-
assert.ok(ids.includes(seedMem.id), "seed memory present");
|
|
169
|
-
assert.ok(ids.includes(hop2Mem.id), "2-hop linked memory reached via cascade");
|
|
170
|
-
const h2 = rows.find((r) => r.id === hop2Mem.id);
|
|
171
|
-
assert.ok(Math.abs(h2.score - 0.3 * HOP_QUOTA[2]) < 1e-9, `2-hop keeps hop-quota weighting, got ${h2.score}`);
|
|
172
|
-
store.close();
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
// 邻接查询两个 IN 组都要绑定:关系方向写成 X→种子 时,种子同样应看到 X 是它的
|
|
176
|
-
// 邻居(只绑第一组时这类行整批消失,级联方向性丢失)。
|
|
177
|
-
test("getEntityNeighbors sees both directions of a relation", () => {
|
|
178
|
-
const store = createStore(":memory:");
|
|
179
|
-
const a = store.createEntity({ name: "Seed", type: "technology" });
|
|
180
|
-
const b = store.createEntity({ name: "Other", type: "technology" });
|
|
181
|
-
store.saveRelation({ from_entity: b.id, to_entity: a.id, relation_type: "x" });
|
|
182
|
-
const nb = store.getEntityNeighbors([a.id]);
|
|
183
|
-
assert.deepEqual(nb.get(a.id), [b.id], "seed must see the reverse-direction neighbor");
|
|
184
|
-
store.close();
|
|
1
|
+
// issue #24 块1 图谱锚定层(graphAnchoringEnabled)测试。
|
|
2
|
+
// 覆盖:默认关=单跳轴行为逐字节不变 / 开=级联扩散邻居挂联记忆进融合池 /
|
|
3
|
+
// 邻居分 = hop 配额 × tier(attr 1.0 > relation 0.9)/ 种子直达分不被邻居覆盖 /
|
|
4
|
+
// 级联深度上界 / graphSeedCap 收敛邻居总量 / 空关系表降级 / lightMode 关闭配置。
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
import assert from "node:assert/strict";
|
|
7
|
+
import { createStore } from "../src/store.js";
|
|
8
|
+
import { createService } from "../src/service.js";
|
|
9
|
+
import { HOP_QUOTA } from "../src/graph/anchoring.js";
|
|
10
|
+
|
|
11
|
+
function makeService(config = {}) {
|
|
12
|
+
const store = createStore(":memory:");
|
|
13
|
+
const service = createService({ store, mirror: null, config });
|
|
14
|
+
return { store, service };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function putMemory(store, service, { type = "project", title, content, importance = 3 }) {
|
|
18
|
+
return service.saveWithDedupe({ type, title, content, importance }).memory;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function linkAttr(store, service, { entityName, memory, key = "k", value = "v", type = "technology" }) {
|
|
22
|
+
let entity = store.findEntityByName(entityName);
|
|
23
|
+
if (!entity) entity = store.createEntity({ name: entityName, type });
|
|
24
|
+
store.saveAttr({ entity_id: entity.id, attr_key: key, attr_value: value, memory_id: memory.id });
|
|
25
|
+
return entity;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function linkRelation(store, { from, to, relType = "related", memoryId, type = "technology" }) {
|
|
29
|
+
const a = store.findEntityByName(from) ?? store.createEntity({ name: from, type });
|
|
30
|
+
const b = store.findEntityByName(to) ?? store.createEntity({ name: to, type });
|
|
31
|
+
store.saveRelation({ from_entity: a.id, to_entity: b.id, relation_type: relType, memory_id: memoryId });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ============================================================ 默认关(回归红线)
|
|
35
|
+
|
|
36
|
+
test("graphAnchoringEnabled=false (default): neighbor-linked memory NOT recalled, seed-only preserved", async () => {
|
|
37
|
+
const { store, service } = makeService({ entityRecallEnabled: true });
|
|
38
|
+
// 种子实体 PostgreSQL 挂一条记忆;关系边连到 PgBouncer(不挂记忆)。
|
|
39
|
+
const seedMem = putMemory(store, service, { title: "数据库迁移", content: "主库切换完成" });
|
|
40
|
+
linkAttr(store, service, { entityName: "PostgreSQL", memory: seedMem, key: "version", value: "16" });
|
|
41
|
+
// 邻居实体 + 它自己挂的记忆:必须不被默认关猜中。
|
|
42
|
+
const neighborMem = putMemory(store, service, { title: "连接池笔记", content: "PgBouncer 池调优记录" });
|
|
43
|
+
const neigh = linkAttr(store, service, { entityName: "PgBouncer", memory: neighborMem, key: "mode", value: "pool" });
|
|
44
|
+
// 关系:PostgreSQL —uses→ PgBouncer(种子 → 邻居),不直接挂 seedMem。
|
|
45
|
+
const a = store.findEntityByName("PostgreSQL");
|
|
46
|
+
linkRelation(store, { from: a.name, to: neigh.name, relType: "uses", memoryId: seedMem.id });
|
|
47
|
+
|
|
48
|
+
const rows = await service.searchMemories("PostgreSQL 现状", { mode: "auto" });
|
|
49
|
+
const ids = rows.map((r) => r.id);
|
|
50
|
+
assert.ok(ids.includes(seedMem.id), "seed-linked memory present");
|
|
51
|
+
assert.ok(!ids.includes(neighborMem.id), "neighbor memory absent when anchoring off");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// ============================================================ 开:级联扩散
|
|
55
|
+
|
|
56
|
+
test("graphAnchoringEnabled=true: neighbor-linked memory joins via cascade, seed keeps full tier score", async () => {
|
|
57
|
+
const { store, service } = makeService({ entityRecallEnabled: true, graphAnchoringEnabled: true });
|
|
58
|
+
const seedMem = putMemory(store, service, { title: "数据库迁移", content: "主库切换完成" });
|
|
59
|
+
linkAttr(store, service, { entityName: "PostgreSQL", memory: seedMem, key: "version", value: "16" });
|
|
60
|
+
const neighborMem = putMemory(store, service, { title: "连接池笔记", content: "PgBouncer 池调优记录" });
|
|
61
|
+
const neigh = linkAttr(store, service, { entityName: "PgBouncer", memory: neighborMem, key: "mode", value: "pool" });
|
|
62
|
+
const a = store.findEntityByName("PostgreSQL");
|
|
63
|
+
linkRelation(store, { from: a.name, to: neigh.name, relType: "uses", memoryId: seedMem.id });
|
|
64
|
+
|
|
65
|
+
// hybrid blend 走加权合算,便于精确断言 entity 轴分(blend: + we·score)。
|
|
66
|
+
const rows = await service.searchMemories("PostgreSQL 现状", { mode: "hybrid" });
|
|
67
|
+
const ids = rows.map((r) => r.id);
|
|
68
|
+
assert.ok(ids.includes(seedMem.id), "seed memory present");
|
|
69
|
+
assert.ok(ids.includes(neighborMem.id), "neighbor memory present via cascade");
|
|
70
|
+
// 种子 attr 1.0 × we(0.3) = 0.3;邻居 attr 1.0 × HOP_QUOTA[1](0.5) × we = 0.15。
|
|
71
|
+
const seedRow = rows.find((r) => r.id === seedMem.id);
|
|
72
|
+
const nbRow = rows.find((r) => r.id === neighborMem.id);
|
|
73
|
+
assert.ok(Math.abs(seedRow.score - 0.3) < 1e-9, "seed keeps full tier score");
|
|
74
|
+
assert.ok(Math.abs(nbRow.score - 0.3 * HOP_QUOTA[1]) < 1e-9, "neighbor hop-weighted");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("cascade respects hop-quota ordering: 1-hop neighbors score above 2-hop", async () => {
|
|
78
|
+
const { store, service } = makeService({ entityRecallEnabled: true, graphAnchoringEnabled: true });
|
|
79
|
+
const seedMem = putMemory(store, service, { title: "根服务", content: "服务 A 上线" });
|
|
80
|
+
linkAttr(store, service, { entityName: "ServiceA", memory: seedMem, key: "env", value: "prod" });
|
|
81
|
+
const hop1Mem = putMemory(store, service, { title: "链路一", content: "ServiceB 依赖记录" });
|
|
82
|
+
const hop2Mem = putMemory(store, service, { title: "链路二", content: "ServiceC 间接关联" });
|
|
83
|
+
const b = linkAttr(store, service, { entityName: "ServiceB", memory: hop1Mem, key: "tier", value: "edge" });
|
|
84
|
+
const c = linkAttr(store, service, { entityName: "ServiceC", memory: hop2Mem, key: "tier", value: "leaf" });
|
|
85
|
+
const a = store.findEntityByName("ServiceA");
|
|
86
|
+
// A—B 关系(hop1);B—C 关系(hop2)。
|
|
87
|
+
linkRelation(store, { from: a.name, to: b.name, relType: "depends", memoryId: hop1Mem.id });
|
|
88
|
+
linkRelation(store, { from: b.name, to: c.name, relType: "depends", memoryId: hop2Mem.id });
|
|
89
|
+
|
|
90
|
+
const rows = await service.searchMemories("ServiceA", { mode: "auto" });
|
|
91
|
+
const ids = rows.map((r) => r.id);
|
|
92
|
+
assert.ok(ids.includes(hop1Mem.id) && ids.includes(hop2Mem.id), "both hops recalled");
|
|
93
|
+
const h1 = rows.find((r) => r.id === hop1Mem.id);
|
|
94
|
+
const h2 = rows.find((r) => r.id === hop2Mem.id);
|
|
95
|
+
assert.ok(h1.score > h2.score, `1-hop (${h1.score}) scores above 2-hop (${h2.score})`);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("empty relation table degrades cleanly (still returns seed memories)", async () => {
|
|
99
|
+
const { store, service } = makeService({ entityRecallEnabled: true, graphAnchoringEnabled: true });
|
|
100
|
+
const seedMem = putMemory(store, service, { title: "独立条", content: "单独存在" });
|
|
101
|
+
linkAttr(store, service, { entityName: "Standalone", memory: seedMem, key: "k", value: "v" });
|
|
102
|
+
const rows = await service.searchMemories("Standalone", { mode: "auto" });
|
|
103
|
+
assert.ok(rows.some((r) => r.id === seedMem.id), "seed memory survives with no relations");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// ============================================================ 配置与配额
|
|
107
|
+
|
|
108
|
+
test("graphSeedCap bounds neighbor expansion (small cap cuts deeper hops)", async () => {
|
|
109
|
+
const { store, service } = makeService({
|
|
110
|
+
entityRecallEnabled: true, graphAnchoringEnabled: true, graphSeedCap: 1, graphCascadeDepth: 3
|
|
111
|
+
});
|
|
112
|
+
const seedMem = putMemory(store, service, { title: "根", content: "入口" });
|
|
113
|
+
const hop1Mem = putMemory(store, service, { title: "一层", content: "近邻" });
|
|
114
|
+
const hop2Mem = putMemory(store, service, { title: "二层", content: "远邻" });
|
|
115
|
+
const b = linkAttr(store, service, { entityName: "NodeB", memory: hop1Mem, key: "k", value: "v" });
|
|
116
|
+
const c = linkAttr(store, service, { entityName: "NodeC", memory: hop2Mem, key: "k", value: "v" });
|
|
117
|
+
const a = store.findEntityByName("入口占位"); // 无;用 NodeA
|
|
118
|
+
const nodeA = store.createEntity({ name: "NodeA", type: "technology" });
|
|
119
|
+
store.saveRelation({ from_entity: nodeA.id, to_entity: b.id, relation_type: "x", memory_id: seedMem.id });
|
|
120
|
+
store.saveRelation({ from_entity: b.id, to_entity: c.id, relation_type: "x", memory_id: hop1Mem.id });
|
|
121
|
+
|
|
122
|
+
const rows = await service.searchMemories("NodeA", { mode: "auto" });
|
|
123
|
+
const ids = rows.map((r) => r.id).sort();
|
|
124
|
+
// cap=1 → 配额 floor(1*0.5)=0 → max(1,…) 兜底只进 1 个 1-hop;2-hop 进不来。
|
|
125
|
+
assert.ok(ids.includes(hop1Mem.id), "1-hop fits within cap");
|
|
126
|
+
assert.ok(!ids.includes(hop2Mem.id), "2-hop cut by small cap");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("the anchoring pure module is self-checked", () => {
|
|
130
|
+
// 模块自带 _selfCheck 已在模块加载时断言;这里再锚定导出契约,防重构断链。
|
|
131
|
+
assert.ok(typeof HOP_QUOTA[1] === "number" && HOP_QUOTA[1] > HOP_QUOTA[2]);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// ---- #24 复审回归(review findings 逐条落锁)--------------------------------
|
|
135
|
+
|
|
136
|
+
// 关档逐字节复用 #219 单跳轴:graphSeedCap 只在锚定开启后生效。种子裁剪若漏进
|
|
137
|
+
// 关档路径,cap=1 时第二个命中实体整支挂联记忆都会消失——这条专门钉死它。
|
|
138
|
+
test("anchoring off: graphSeedCap does not cap the legacy single-hop axis", async () => {
|
|
139
|
+
const { store, service } = makeService({ entityRecallEnabled: true, graphSeedCap: 1 });
|
|
140
|
+
const memA = putMemory(store, service, { title: "迁移记录", content: "主库切换完成" });
|
|
141
|
+
const memB = putMemory(store, service, { title: "池化记录", content: "连接池调优" });
|
|
142
|
+
linkAttr(store, service, { entityName: "Alpha", memory: memA, key: "k", value: "v" });
|
|
143
|
+
linkAttr(store, service, { entityName: "Beta", memory: memB, key: "k", value: "v" });
|
|
144
|
+
|
|
145
|
+
const rows = await service.searchMemories("Alpha Beta", { mode: "auto" });
|
|
146
|
+
const ids = rows.map((r) => r.id);
|
|
147
|
+
assert.ok(ids.includes(memA.id), "first seed-linked memory present");
|
|
148
|
+
assert.ok(ids.includes(memB.id), "second seed-linked memory must survive when anchoring is off");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// 级联要真走出 >1 跳:邻接若只带种子那一层,2-hop 实体挂联的记忆根本不进池。
|
|
152
|
+
// 本地图上 2-hop 记忆只经 AnchorC 可达(两条关系行的 memory_id 都留空,堵死
|
|
153
|
+
// 「1-hop 关系行旁路」),所以这条能红着抓回归。
|
|
154
|
+
test("cascade reaches 2 hops (lazy per-node adjacency, deeper hop weighted lower)", async () => {
|
|
155
|
+
const { store, service } = makeService({
|
|
156
|
+
entityRecallEnabled: true, graphAnchoringEnabled: true, graphCascadeDepth: 2
|
|
157
|
+
});
|
|
158
|
+
const seedMem = putMemory(store, service, { title: "根服务", content: "入口记录" });
|
|
159
|
+
const hop2Mem = putMemory(store, service, { title: "远环", content: "间接可达" });
|
|
160
|
+
const a = linkAttr(store, service, { entityName: "AnchorA", memory: seedMem, key: "k", value: "v" });
|
|
161
|
+
const c = linkAttr(store, service, { entityName: "AnchorC", memory: hop2Mem, key: "k", value: "v" });
|
|
162
|
+
const b = store.createEntity({ name: "AnchorB", type: "technology" });
|
|
163
|
+
store.saveRelation({ from_entity: a.id, to_entity: b.id, relation_type: "x" });
|
|
164
|
+
store.saveRelation({ from_entity: b.id, to_entity: c.id, relation_type: "x" });
|
|
165
|
+
|
|
166
|
+
const rows = await service.searchMemories("AnchorA", { mode: "hybrid" });
|
|
167
|
+
const ids = rows.map((r) => r.id);
|
|
168
|
+
assert.ok(ids.includes(seedMem.id), "seed memory present");
|
|
169
|
+
assert.ok(ids.includes(hop2Mem.id), "2-hop linked memory reached via cascade");
|
|
170
|
+
const h2 = rows.find((r) => r.id === hop2Mem.id);
|
|
171
|
+
assert.ok(Math.abs(h2.score - 0.3 * HOP_QUOTA[2]) < 1e-9, `2-hop keeps hop-quota weighting, got ${h2.score}`);
|
|
172
|
+
store.close();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// 邻接查询两个 IN 组都要绑定:关系方向写成 X→种子 时,种子同样应看到 X 是它的
|
|
176
|
+
// 邻居(只绑第一组时这类行整批消失,级联方向性丢失)。
|
|
177
|
+
test("getEntityNeighbors sees both directions of a relation", () => {
|
|
178
|
+
const store = createStore(":memory:");
|
|
179
|
+
const a = store.createEntity({ name: "Seed", type: "technology" });
|
|
180
|
+
const b = store.createEntity({ name: "Other", type: "technology" });
|
|
181
|
+
store.saveRelation({ from_entity: b.id, to_entity: a.id, relation_type: "x" });
|
|
182
|
+
const nb = store.getEntityNeighbors([a.id]);
|
|
183
|
+
assert.deepEqual(nb.get(a.id), [b.id], "seed must see the reverse-direction neighbor");
|
|
184
|
+
store.close();
|
|
185
185
|
});
|