@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
package/src/wiki/queries.js
CHANGED
|
@@ -1,474 +1,640 @@
|
|
|
1
|
-
// dsh-mneme/src/wiki/queries.js
|
|
2
|
-
// chorography 的读侧(M1):仓库列表 / 模块图 / 页面正文 / 关键词检索。
|
|
3
|
-
// 收尾档追加:向量检索(P37–P39,惰性物化 + cosine)。
|
|
4
|
-
// 检索读侧零写路径的例外只有一处——向量物化写 choro_vectors(P37:检索时惰性
|
|
5
|
-
// 补嵌,host 侧才有 embedder;那是向量账本,不是页面真源)。
|
|
6
|
-
//
|
|
7
|
-
// 真源纪律(spec §4 / P12):页面正文永远读仓库磁盘的 MD(root_path + DB 里的
|
|
8
|
-
// rel_path),库里的表只是索引与状态账。slug 只从 DB 白名单解析,路径拼装零用户
|
|
9
|
-
// 输入——面板与工具传进来的 slug 查不到模块行就返回 null,绝不用它拼路径。
|
|
10
|
-
import { readFileSync, existsSync, realpathSync } from "node:fs";
|
|
11
|
-
import { join, resolve, sep } from "node:path";
|
|
12
|
-
// 过期判定复用既有三件真源(t0 的 gitHead、plan 的指纹、render 的模板版本),
|
|
13
|
-
// 面板侧不另造一套判据——两套判据迟早会对同一仓库给出两个答案。
|
|
14
|
-
import { gitHead } from "./t0.js";
|
|
15
|
-
import { loadPlanFromRepo } from "./plan.js";
|
|
16
|
-
import { TEMPLATE_VERSION } from "./render.js";
|
|
17
|
-
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
(SELECT COUNT(*) FROM
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
*
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
function
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
).
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
).
|
|
337
|
-
|
|
338
|
-
const rows
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
1
|
+
// dsh-mneme/src/wiki/queries.js
|
|
2
|
+
// chorography 的读侧(M1):仓库列表 / 模块图 / 页面正文 / 关键词检索。
|
|
3
|
+
// 收尾档追加:向量检索(P37–P39,惰性物化 + cosine)。
|
|
4
|
+
// 检索读侧零写路径的例外只有一处——向量物化写 choro_vectors(P37:检索时惰性
|
|
5
|
+
// 补嵌,host 侧才有 embedder;那是向量账本,不是页面真源)。
|
|
6
|
+
//
|
|
7
|
+
// 真源纪律(spec §4 / P12):页面正文永远读仓库磁盘的 MD(root_path + DB 里的
|
|
8
|
+
// rel_path),库里的表只是索引与状态账。slug 只从 DB 白名单解析,路径拼装零用户
|
|
9
|
+
// 输入——面板与工具传进来的 slug 查不到模块行就返回 null,绝不用它拼路径。
|
|
10
|
+
import { readFileSync, existsSync, realpathSync } from "node:fs";
|
|
11
|
+
import { join, resolve, sep } from "node:path";
|
|
12
|
+
// 过期判定复用既有三件真源(t0 的 gitHead、plan 的指纹、render 的模板版本),
|
|
13
|
+
// 面板侧不另造一套判据——两套判据迟早会对同一仓库给出两个答案。
|
|
14
|
+
import { gitHead } from "./t0.js";
|
|
15
|
+
import { loadPlanFromRepo } from "./plan.js";
|
|
16
|
+
import { TEMPLATE_VERSION, T1_SECTION_START } from "./render.js";
|
|
17
|
+
// claim 失效判定的纯函数(借 openwiki 的 Grounded Claims):解析 diff + 逐条比对
|
|
18
|
+
// 三列证据。放独立模块是因为它零 DB、零 IO(除 diffSince 那一次 git),可单测。
|
|
19
|
+
import { diffSince, summarizeClaims } from "./claims.js";
|
|
20
|
+
|
|
21
|
+
// 页正文去 frontmatter / CHORO 标记行后作为嵌入文本(P40:嵌页级全文)。
|
|
22
|
+
function pageEmbedText(content) {
|
|
23
|
+
const lines = String(content ?? "").split("\n");
|
|
24
|
+
let start = 0;
|
|
25
|
+
if (lines[0]?.trim() === "---") {
|
|
26
|
+
start = 1;
|
|
27
|
+
while (start < lines.length && lines[start].trim() !== "---") start++;
|
|
28
|
+
start++;
|
|
29
|
+
}
|
|
30
|
+
return lines
|
|
31
|
+
.slice(start)
|
|
32
|
+
.filter((l) => !l.startsWith("<!-- CHORO:T1:") && !l.startsWith("<!-- CHORO:HUMAN:"))
|
|
33
|
+
.join("\n")
|
|
34
|
+
.trim();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function cosine(a, b) {
|
|
38
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length || a.length === 0) return 0;
|
|
39
|
+
let dot = 0;
|
|
40
|
+
let na = 0;
|
|
41
|
+
let nb = 0;
|
|
42
|
+
for (let i = 0; i < a.length; i++) {
|
|
43
|
+
dot += a[i] * b[i];
|
|
44
|
+
na += a[i] * a[i];
|
|
45
|
+
nb += b[i] * b[i];
|
|
46
|
+
}
|
|
47
|
+
const denom = Math.sqrt(na) * Math.sqrt(nb);
|
|
48
|
+
return denom === 0 ? 0 : dot / denom;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function createChoroQueries({ store }) {
|
|
52
|
+
const db = store.db;
|
|
53
|
+
|
|
54
|
+
/** 已登记仓库(含模块/页面计数,面板仓库选择器与工具的 repo 解析共用)。 */
|
|
55
|
+
function listRepos() {
|
|
56
|
+
const rows = db.prepare(
|
|
57
|
+
`SELECT r.id, r.root_path, r.name, r.plan_fingerprint, r.git_baseline, r.updated_at,
|
|
58
|
+
(SELECT COUNT(*) FROM choro_modules m WHERE m.repo_id = r.id) AS module_count,
|
|
59
|
+
(SELECT COUNT(*) FROM choro_pages p JOIN choro_modules m2 ON p.module_id = m2.id
|
|
60
|
+
WHERE m2.repo_id = r.id) AS page_count,
|
|
61
|
+
(SELECT COUNT(*) FROM choro_claims c
|
|
62
|
+
JOIN choro_pages p2 ON c.page_id = p2.id
|
|
63
|
+
JOIN choro_modules m3 ON p2.module_id = m3.id
|
|
64
|
+
WHERE m3.repo_id = r.id) AS claim_count
|
|
65
|
+
FROM choro_repos r ORDER BY r.id`
|
|
66
|
+
).all();
|
|
67
|
+
return rows;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 面板仓库列表(契约 §3.2):在 listRepos 的行上补契约要求的字段。
|
|
72
|
+
*
|
|
73
|
+
* 为什么另开一个函数而不是改 listRepos 的返回形状:listRepos 的
|
|
74
|
+
* module_count / page_count 被工具层与查询层测试锁着(下划线命名),改名就是
|
|
75
|
+
* 破坏既有调用方;而契约要的是 modules / pages 这样的对外字段。两套名字并存
|
|
76
|
+
* 难看,但改名的连带面比难看贵得多——装饰层把它包一层,工具层照旧读原始行。
|
|
77
|
+
*
|
|
78
|
+
* 每个仓库都要 fork 一次 git 判过期:这是用户点开面板才走的路(不是每轮注入的
|
|
79
|
+
* 热路径),一次 rev-parse 几十毫秒在交互里不可感知。
|
|
80
|
+
*/
|
|
81
|
+
/**
|
|
82
|
+
* 契约字段的仓库对象(stale 由调用方决定是否重算)。
|
|
83
|
+
*
|
|
84
|
+
* 注意区分两个入口:repoFacts 每次都会 fork git 判过期;repoFromRow 是纯行投影,
|
|
85
|
+
* 给「已经知道过期原因、手上有判定结果」的调用方用(例如自动重跑之后要紧着按
|
|
86
|
+
* 新基线说话,不能再拿旧行算一遍)。混用会得到自相矛盾的回执。
|
|
87
|
+
*/
|
|
88
|
+
function repoFromRow(row) {
|
|
89
|
+
const rootPath = String(row.root_path ?? "");
|
|
90
|
+
return {
|
|
91
|
+
id: row.id,
|
|
92
|
+
name: row.name,
|
|
93
|
+
rootPath,
|
|
94
|
+
modules: Number(row.module_count ?? 0),
|
|
95
|
+
pages: Number(row.page_count ?? 0),
|
|
96
|
+
claims: Number(row.claim_count ?? 0),
|
|
97
|
+
// legacy = 这个仓库里有页面是用旧模板(或 M0 的 NULL)渲染的:重跑一次 T0 就
|
|
98
|
+
// 会把它们升级成当前模板,面板据此提示「建议重建」而不是让用户自己比对版本。
|
|
99
|
+
legacy: legacyPages(row.id) > 0,
|
|
100
|
+
// enrichedPages:已经富化过(页面里有 T1 语义段、库里有 claims)的页数。
|
|
101
|
+
// 自动刷新要看它:这类页重跑 T0 会退回骨架,所以那条路必须先问一句。
|
|
102
|
+
enrichedPages: enrichedPageCount(row.id),
|
|
103
|
+
lastRunAt: row.updated_at ?? null
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 已富化的页数。判据取库里的 status(T1 成功后会写 enriched),而不是读盘找
|
|
109
|
+
* T1 标记——这是条给面板列表用的轻查询,几百页也不该逐文件 readFileSync。
|
|
110
|
+
* 两边可能短暂不一致(例如有人手改了 MD),但「要不要提醒」这种提示用途,
|
|
111
|
+
* 库里的账本就是最合适的真源;真正判断「会丢什么」的仍是 T0 的 collectT1Loss(读盘)。
|
|
112
|
+
*/
|
|
113
|
+
function enrichedPageCount(repoId) {
|
|
114
|
+
const hit = db.prepare(
|
|
115
|
+
`SELECT COUNT(*) AS n FROM choro_pages p JOIN choro_modules m ON p.module_id = m.id
|
|
116
|
+
WHERE m.repo_id = ? AND p.status = 'enriched'`
|
|
117
|
+
).get(repoId);
|
|
118
|
+
return Number(hit?.n ?? 0);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function legacyPages(repoId) {
|
|
122
|
+
const hit = db.prepare(
|
|
123
|
+
`SELECT COUNT(*) AS n FROM choro_pages p JOIN choro_modules m ON p.module_id = m.id
|
|
124
|
+
WHERE m.repo_id = ? AND (p.template_version IS NULL OR p.template_version <> ?)`
|
|
125
|
+
).get(repoId, TEMPLATE_VERSION);
|
|
126
|
+
return Number(hit?.n ?? 0);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function repoFacts(row) {
|
|
130
|
+
const rootPath = String(row.root_path ?? "");
|
|
131
|
+
// git 优先:代码变了是更硬的「该重跑」信号,plan 变了才退而求其次——两者同时
|
|
132
|
+
// 成立时面板只说一句话,说「git」比说「plan」更贴近用户看到的现象(他刚改了码)。
|
|
133
|
+
const head = rootPath === "" ? null : gitHead(rootPath);
|
|
134
|
+
const baseline = String(row.git_baseline ?? "").trim();
|
|
135
|
+
let staleReason = null;
|
|
136
|
+
if (head && head !== baseline) {
|
|
137
|
+
staleReason = "git";
|
|
138
|
+
} else if (rootPath !== "") {
|
|
139
|
+
// 读不到 git(head 为 null)时不直接判「没过期」:plan.yaml 可能已经被改过,
|
|
140
|
+
// 那条判据不依赖 git,仍能给出答案。
|
|
141
|
+
let planFingerprintNow = null;
|
|
142
|
+
try { planFingerprintNow = loadPlanFromRepo(rootPath).fingerprint; } catch { planFingerprintNow = null; }
|
|
143
|
+
// plan.yaml 校验不过时 loadPlanFromRepo 会抛——那是「现在读不出结论」,不是
|
|
144
|
+
// 「过期了」,所以落 null 保持 stale=false,让重建按钮自己去报那条 YAML 错误。
|
|
145
|
+
if (planFingerprintNow && planFingerprintNow !== String(row.plan_fingerprint ?? "")) {
|
|
146
|
+
staleReason = "plan";
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
...repoFromRow(row),
|
|
151
|
+
stale: staleReason !== null,
|
|
152
|
+
staleReason
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** 单仓的原始 listRepos 行(自动重跑之后要拿推进过的基线,不能再用旧行)。 */
|
|
157
|
+
function listRepoRow(repoId) {
|
|
158
|
+
return listRepos().find((r) => Number(r.id) === Number(repoId)) ?? null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** 契约形状的仓库列表:listRepos 的原始行 → 对外字段。 */
|
|
162
|
+
function listRepoFacts() {
|
|
163
|
+
return listRepos().map((row) => repoFacts(row));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** 仓库的契约字段(按 id/name,给 /choro/page 这类已知单仓的读路径用)。 */
|
|
167
|
+
function repoFactsById(repoId) {
|
|
168
|
+
const row = listRepoRow(repoId);
|
|
169
|
+
return row ? repoFacts(row) : null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* 按绝对路径找已登记仓库(面板「选目录 → 生成」必需):T0 apply 落库时存的是
|
|
174
|
+
* realpath,所以这里也必须先 realpath 再比,否则 Windows 上盘符大小写/短名/符号
|
|
175
|
+
* 链接会让同一个目录被当成新仓库重复登记一次。
|
|
176
|
+
*/
|
|
177
|
+
function repoByRoot(rootPath) {
|
|
178
|
+
const raw = String(rootPath ?? "").trim();
|
|
179
|
+
if (raw === "") return null;
|
|
180
|
+
let normalized;
|
|
181
|
+
try { normalized = realpathSync(resolve(raw)); } catch { return null; }
|
|
182
|
+
return db.prepare("SELECT * FROM choro_repos WHERE root_path = ?").get(normalized) ?? null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** repo 解析:数字 id 或显示名(root_path 是本机私有路径,不作为对外键)。 */
|
|
186
|
+
function resolveRepo(ref) {
|
|
187
|
+
if (ref === undefined || ref === null || ref === "") {
|
|
188
|
+
const rows = listRepos();
|
|
189
|
+
if (rows.length === 1) return rows[0];
|
|
190
|
+
if (rows.length === 0) return null;
|
|
191
|
+
return { ambiguous: rows.map((r) => ({ id: r.id, name: r.name })) };
|
|
192
|
+
}
|
|
193
|
+
const byId = db.prepare("SELECT * FROM choro_repos WHERE id = ?").get(Number(ref));
|
|
194
|
+
if (byId) return byId;
|
|
195
|
+
const byName = db.prepare("SELECT * FROM choro_repos WHERE name = ?").get(String(ref));
|
|
196
|
+
return byName ?? null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** 模块表 + 跨模块边(choro_module_map 与面板左侧树共用同一份数据)。 */
|
|
200
|
+
function moduleMap(repoId) {
|
|
201
|
+
const modules = db.prepare(
|
|
202
|
+
`SELECT m.id, m.slug, m.title, m.parent_id, m.file_count, m.fingerprint, m.updated_at,
|
|
203
|
+
p.status AS page_status, p.rel_path AS page_rel_path
|
|
204
|
+
FROM choro_modules m
|
|
205
|
+
LEFT JOIN choro_pages p ON p.module_id = m.id
|
|
206
|
+
WHERE m.repo_id = ? ORDER BY m.slug`
|
|
207
|
+
).all(repoId);
|
|
208
|
+
const edges = db.prepare(
|
|
209
|
+
`SELECT from_slug, to_slug, weight FROM choro_edges WHERE repo_id = ?
|
|
210
|
+
ORDER BY from_slug, to_slug`
|
|
211
|
+
).all(repoId);
|
|
212
|
+
// 纯加法补一个 camelCase 的 relPath:面板从地志页导入知识点要拼「根路径 + 页相对
|
|
213
|
+
// 路径」,而这里历史上直出 SQL 行的 snake_case(page_rel_path)。C 线按契约读的是
|
|
214
|
+
// mod.relPath,字段一缺就恒 undefined,用户选完模块点导入只会看到「请先选模块」。
|
|
215
|
+
// 原字段一律保留——src/tools.js 的 render 与 choro-command 都还在吃 file_count /
|
|
216
|
+
// page_status,改形状会波及它们;outline() 里的同名映射是这件事的正确写法。
|
|
217
|
+
for (const m of modules) m.relPath = m.page_rel_path ?? null;
|
|
218
|
+
return { modules, edges };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* 读一页正文。slug 必须命中 DB 的模块行(P12:路径零用户输入);索引仍在但
|
|
223
|
+
* 文件缺失或不可读时带回相对路径,让调用方给出对应的恢复提示。
|
|
224
|
+
*/
|
|
225
|
+
function readPage(repoId, slug) {
|
|
226
|
+
const row = db.prepare(
|
|
227
|
+
`SELECT m.slug, m.title, m.file_count, m.parent_id, p.rel_path, p.status,
|
|
228
|
+
p.git_baseline, p.template_version, p.generated_at, p.human_block_count
|
|
229
|
+
FROM choro_modules m JOIN choro_pages p ON p.module_id = m.id
|
|
230
|
+
WHERE m.repo_id = ? AND m.slug = ?`
|
|
231
|
+
).get(repoId, String(slug));
|
|
232
|
+
if (!row) return null;
|
|
233
|
+
const repo = db.prepare("SELECT root_path, name FROM choro_repos WHERE id = ?").get(repoId);
|
|
234
|
+
const abs = join(repo.root_path, row.rel_path);
|
|
235
|
+
let content;
|
|
236
|
+
try {
|
|
237
|
+
content = readFileSync(abs, "utf8");
|
|
238
|
+
} catch (cause) {
|
|
239
|
+
const error = cause.code === "ENOENT" || cause.code === "ENOTDIR" ? "page-missing" : "page-unreadable";
|
|
240
|
+
return { error, slug: row.slug, relPath: row.rel_path };
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
slug: row.slug,
|
|
244
|
+
title: row.title,
|
|
245
|
+
fileCount: row.file_count,
|
|
246
|
+
status: row.status,
|
|
247
|
+
gitBaseline: row.git_baseline,
|
|
248
|
+
templateVersion: row.template_version,
|
|
249
|
+
generatedAt: row.generated_at,
|
|
250
|
+
humanBlockCount: row.human_block_count,
|
|
251
|
+
repoName: repo.name,
|
|
252
|
+
relPath: row.rel_path,
|
|
253
|
+
content,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* 关键词检索(P10):按 `## ` 分节做大小写不敏感子串匹配,命中返回页 slug、
|
|
259
|
+
* 节标题、起始行号与摘录。只扫 DB 里登记过的页面文件(不是全目录 glob——
|
|
260
|
+
* 划分变更后未重算的孤儿文件不该被搜出来)。需要提示检索不完整的调用方可接收
|
|
261
|
+
* 不可读的页 slug。q 为空返回空数组不报错。
|
|
262
|
+
*/
|
|
263
|
+
function search(repoId, q, { moduleSlug = null, limit = 20, onUnreadablePage } = {}) {
|
|
264
|
+
const needle = String(q ?? "").trim().toLowerCase();
|
|
265
|
+
if (needle === "") return [];
|
|
266
|
+
const cap = Math.max(1, Math.min(100, Number(limit) || 20));
|
|
267
|
+
const pages = db.prepare(
|
|
268
|
+
`SELECT m.slug, m.title AS module_title, p.rel_path
|
|
269
|
+
FROM choro_modules m JOIN choro_pages p ON p.module_id = m.id
|
|
270
|
+
WHERE m.repo_id = ? AND (? IS NULL OR m.slug = ?)
|
|
271
|
+
ORDER BY m.slug`
|
|
272
|
+
).all(repoId, moduleSlug, moduleSlug);
|
|
273
|
+
const repo = db.prepare("SELECT root_path FROM choro_repos WHERE id = ?").get(repoId);
|
|
274
|
+
const hits = [];
|
|
275
|
+
for (const page of pages) {
|
|
276
|
+
const abs = join(repo.root_path, page.rel_path);
|
|
277
|
+
let text;
|
|
278
|
+
try {
|
|
279
|
+
text = readFileSync(abs, "utf8");
|
|
280
|
+
} catch {
|
|
281
|
+
onUnreadablePage?.(page.slug);
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
const lines = text.split("\n");
|
|
285
|
+
// 分节:每个 `## ` 标题开启一节,节内所有行都属于该节;文件头(frontmatter
|
|
286
|
+
// 之后到第一个 `## `)算 "(页首)" 一节,保证对 title/摘要的命中也有落点。
|
|
287
|
+
let section = "(页首)";
|
|
288
|
+
let sectionStart = 1;
|
|
289
|
+
for (let i = 0; i < lines.length; i++) {
|
|
290
|
+
const line = lines[i];
|
|
291
|
+
if (i > 0 && /^## /.test(line)) {
|
|
292
|
+
section = line.replace(/^## /, "").trim();
|
|
293
|
+
sectionStart = i + 1;
|
|
294
|
+
}
|
|
295
|
+
if (line.toLowerCase().includes(needle)) {
|
|
296
|
+
hits.push({
|
|
297
|
+
slug: page.slug,
|
|
298
|
+
moduleTitle: page.module_title,
|
|
299
|
+
section,
|
|
300
|
+
line: i + 1,
|
|
301
|
+
sectionStart,
|
|
302
|
+
excerpt: line.trim().slice(0, 200),
|
|
303
|
+
});
|
|
304
|
+
if (hits.length >= cap) return hits;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return hits;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* 按会话工作目录找已登记仓库(M3 开局注入的唯一入口)。
|
|
313
|
+
*
|
|
314
|
+
* 三个刻意的取舍:
|
|
315
|
+
* 1. **祖先前缀匹配**而不是精确相等——真实会话的 cwd 常常是仓库子目录
|
|
316
|
+
* (`D:/repo/src`),精确相等会让绝大部分会话匹配不到,等于逼用户
|
|
317
|
+
* 「必须在仓库根开会话」这条暗规则。
|
|
318
|
+
* 2. 两侧都做 realpath 归一:Windows 上盘符大小写、8.3 短名、符号链接
|
|
319
|
+
* 会让同一个目录写出不同字符串;t0 落库时存的就是 realpath。
|
|
320
|
+
* 3. **最长前缀胜出**——仓库嵌套(子模块、`.worktrees/`)时取最深那层,
|
|
321
|
+
* 否则内层会话会被外层仓库抢走。
|
|
322
|
+
* cwd 不存在或不是目录时返回 null(不是抛错):注入在热路径上,失败只
|
|
323
|
+
* 意味着「本帧不注入」。
|
|
324
|
+
*/
|
|
325
|
+
function repoByCwd(cwd) {
|
|
326
|
+
const raw = String(cwd ?? "").trim();
|
|
327
|
+
if (raw === "") return null;
|
|
328
|
+
let normalized;
|
|
329
|
+
try {
|
|
330
|
+
normalized = realpathSync(resolve(raw));
|
|
331
|
+
} catch {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
const cmp = (p) => (process.platform === "win32" ? p.toLowerCase() : p);
|
|
335
|
+
const target = cmp(normalized);
|
|
336
|
+
const rows = db.prepare("SELECT * FROM choro_repos ORDER BY id").all();
|
|
337
|
+
let best = null;
|
|
338
|
+
for (const row of rows) {
|
|
339
|
+
const root = cmp(String(row.root_path ?? ""));
|
|
340
|
+
if (root === "") continue;
|
|
341
|
+
const hit = target === root || target.startsWith(root.endsWith(sep) ? root : root + sep);
|
|
342
|
+
if (!hit) continue;
|
|
343
|
+
if (!best || root.length > cmp(String(best.root_path)).length) best = row;
|
|
344
|
+
}
|
|
345
|
+
return best ?? null;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* 注入与 /choro 共用的最小提纲(M3):模块名 + 文件数 + 页状态 + 页相对路径。
|
|
350
|
+
* **不读页正文**——spec §7 要求「只注标题级不注正文」,正文走 readPage。
|
|
351
|
+
* parentSlug 单独解出来给渲染层做层级缩进(DB 里存的是 parent_id)。
|
|
352
|
+
*/
|
|
353
|
+
function outline(repoId) {
|
|
354
|
+
const repo = db.prepare(
|
|
355
|
+
"SELECT id, root_path, name, git_baseline, plan_fingerprint, updated_at FROM choro_repos WHERE id = ?"
|
|
356
|
+
).get(repoId);
|
|
357
|
+
if (!repo) return null;
|
|
358
|
+
const rows = db.prepare(
|
|
359
|
+
`SELECT m.id, m.slug, m.title, m.parent_id, m.file_count,
|
|
360
|
+
p.status AS page_status, p.rel_path AS page_rel_path, p.generated_at
|
|
361
|
+
FROM choro_modules m
|
|
362
|
+
LEFT JOIN choro_pages p ON p.module_id = m.id
|
|
363
|
+
WHERE m.repo_id = ? ORDER BY m.slug`
|
|
364
|
+
).all(repoId);
|
|
365
|
+
const slugById = new Map(rows.map((r) => [r.id, r.slug]));
|
|
366
|
+
let generatedAt = null;
|
|
367
|
+
const modules = rows.map((r) => {
|
|
368
|
+
if (r.generated_at && (generatedAt === null || r.generated_at > generatedAt)) generatedAt = r.generated_at;
|
|
369
|
+
return {
|
|
370
|
+
slug: r.slug,
|
|
371
|
+
title: r.title,
|
|
372
|
+
fileCount: r.file_count,
|
|
373
|
+
parentSlug: r.parent_id === null || r.parent_id === undefined ? null : slugById.get(r.parent_id) ?? null,
|
|
374
|
+
status: r.page_status ?? null,
|
|
375
|
+
relPath: r.page_rel_path ?? null,
|
|
376
|
+
};
|
|
377
|
+
});
|
|
378
|
+
return {
|
|
379
|
+
repo: {
|
|
380
|
+
id: repo.id,
|
|
381
|
+
name: repo.name,
|
|
382
|
+
rootPath: repo.root_path,
|
|
383
|
+
gitBaseline: repo.git_baseline,
|
|
384
|
+
updatedAt: repo.updated_at,
|
|
385
|
+
},
|
|
386
|
+
generatedAt: generatedAt ?? repo.updated_at ?? null,
|
|
387
|
+
modules,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Claim 粒度的失效清单(借 openwiki 的 Grounded Claims):把「仓库代码变过就整仓过期」
|
|
393
|
+
* 降成「这 2 页里的 5 条结论被改动碰过」。
|
|
394
|
+
*
|
|
395
|
+
* 为什么放读侧而不写成后台任务:判据是纯机械的(git diff 的 hunk 区间 × claims 的
|
|
396
|
+
* 三列证据),不需要模型,一次 diff 几十毫秒——面板打开时现算,永远比任何落库的
|
|
397
|
+
* 缓存新鲜。写路径一条不加,红线不动。
|
|
398
|
+
*
|
|
399
|
+
* **比对起点是每页自己的 verified_baseline,不是全仓 git_baseline。** claims 的行号
|
|
400
|
+
* 是那页被富化的那一刻记下的;全仓基线是 T0 的时点。若 T1 晚于 T0(常态),用全仓
|
|
401
|
+
* 基线起算就会把「T1 之前就发生、claim 早已算进去」的改动也算进 diff,误报 touched。
|
|
402
|
+
* 所以按 verified_baseline 分组、每组各跑一次 git diff(同一起点只跑一次)。
|
|
403
|
+
*
|
|
404
|
+
* 五种「说不清」要分开说,别混成一个空清单:
|
|
405
|
+
* - repo 不存在 → null(调用方 404)
|
|
406
|
+
* - git 读不到 HEAD → 全仓 anchorStale + reason "git-unreadable"
|
|
407
|
+
* - 某页基线认不出(浅克隆/force-push)→ 该页 anchorStale,**其余页照常判**
|
|
408
|
+
* - 某页没记过验证基线(老数据/拿不到 HEAD)→ 该页 baselineUnknown,一条不判
|
|
409
|
+
* - 起点 == HEAD → 那组真没变,clean
|
|
410
|
+
*/
|
|
411
|
+
function claimDrift(repoId) {
|
|
412
|
+
const repo = db.prepare("SELECT id, root_path, name, git_baseline FROM choro_repos WHERE id = ?").get(repoId);
|
|
413
|
+
if (!repo) return null;
|
|
414
|
+
const rootPath = String(repo.root_path ?? "");
|
|
415
|
+
const rows = db.prepare(
|
|
416
|
+
`SELECT c.id, c.statement, c.kind, c.source_path, c.start_line, c.end_line,
|
|
417
|
+
m.slug AS page_slug, m.title AS page_title, p.rel_path
|
|
418
|
+
FROM choro_claims c
|
|
419
|
+
JOIN choro_pages p ON c.page_id = p.id
|
|
420
|
+
JOIN choro_modules m ON p.module_id = m.id
|
|
421
|
+
WHERE m.repo_id = ? ORDER BY m.slug, c.id`
|
|
422
|
+
).all(repoId);
|
|
423
|
+
// 页面清单单算:hasT1 只能读盘(标记写在正文里,库里没有这一列);验证基线在库里。
|
|
424
|
+
const pageRows = db.prepare(
|
|
425
|
+
`SELECT m.slug, m.title, p.rel_path, p.verified_baseline, p.verified_at
|
|
426
|
+
FROM choro_pages p
|
|
427
|
+
JOIN choro_modules m ON p.module_id = m.id WHERE m.repo_id = ? ORDER BY m.slug`
|
|
428
|
+
).all(repoId);
|
|
429
|
+
const head = rootPath === "" ? null : gitHead(rootPath);
|
|
430
|
+
const baseline = String(repo.git_baseline ?? "").trim();
|
|
431
|
+
// 每组验证基线只跑一次 diff:富化往往成批完成,同一基线的页占多数。
|
|
432
|
+
const diffCache = new Map();
|
|
433
|
+
const diffFor = (verified) => {
|
|
434
|
+
if (head === null) return null;
|
|
435
|
+
const key = String(verified ?? "").trim();
|
|
436
|
+
if (key === "") return null; // 没记过起点 → 判不了,别拿别的页的起点凑
|
|
437
|
+
if (diffCache.has(key)) return diffCache.get(key);
|
|
438
|
+
let value = null;
|
|
439
|
+
if (key === head) value = { ok: true, files: new Map(), deleted: [] };
|
|
440
|
+
else {
|
|
441
|
+
const diff = diffSince(rootPath, key, head);
|
|
442
|
+
value = diff.ok ? diff : { ok: false, reason: diff.reason };
|
|
443
|
+
}
|
|
444
|
+
diffCache.set(key, value);
|
|
445
|
+
return value;
|
|
446
|
+
};
|
|
447
|
+
const pages = pageRows.map((p) => {
|
|
448
|
+
let hasT1 = false;
|
|
449
|
+
try {
|
|
450
|
+
hasT1 = readFileSync(join(rootPath, p.rel_path), "utf8").includes(T1_SECTION_START);
|
|
451
|
+
} catch { hasT1 = false; }
|
|
452
|
+
const verifiedBaseline = String(p.verified_baseline ?? "").trim() || null;
|
|
453
|
+
const verifiedAt = p.verified_at ?? null;
|
|
454
|
+
// 老数据(本功能之前富化的页)只有全仓基线可依。退到它是**保守**方向:它早于
|
|
455
|
+
// 那页被富化的时刻,diff 会把「T1 之前就发生」的改动也算进来,只会多报不会漏报。
|
|
456
|
+
// 记 baselineApprox 是为了面板能说清「这是退而求其次的起点」,不是当真的验证点。
|
|
457
|
+
//
|
|
458
|
+
// 但「验证过、却没记下基线」不是老数据,是**验证没留下凭据**(T1 那次富化写了
|
|
459
|
+
// verified_at 却没写 verified_baseline,比如中途失败)。这种情况退到全仓基线
|
|
460
|
+
// 就会把「富化之后才发生」的改动当成「富化之前就有的」而放过——方向是漏报,
|
|
461
|
+
// 与上面那条恰好相反。所以判成 baselineUnknown:不知道起点,就不装作知道。
|
|
462
|
+
const verifyWithoutBaseline = verifiedBaseline === null && verifiedAt !== null;
|
|
463
|
+
const effective = verifyWithoutBaseline ? null : (verifiedBaseline ?? (baseline || null));
|
|
464
|
+
const computed = diffFor(effective);
|
|
465
|
+
return {
|
|
466
|
+
slug: p.slug,
|
|
467
|
+
title: p.title,
|
|
468
|
+
hasT1,
|
|
469
|
+
verifiedBaseline,
|
|
470
|
+
verifiedAt,
|
|
471
|
+
diffBaseline: effective,
|
|
472
|
+
baselineApprox: !verifyWithoutBaseline && verifiedBaseline === null && effective !== null,
|
|
473
|
+
baselineUnknown: effective === null,
|
|
474
|
+
anchorStale: computed !== null && computed.ok === false,
|
|
475
|
+
};
|
|
476
|
+
});
|
|
477
|
+
const claims = rows.map((r) => ({
|
|
478
|
+
id: r.id,
|
|
479
|
+
statement: r.statement,
|
|
480
|
+
kind: r.kind,
|
|
481
|
+
sourcePath: r.source_path,
|
|
482
|
+
startLine: r.start_line,
|
|
483
|
+
endLine: r.end_line,
|
|
484
|
+
pageSlug: r.page_slug,
|
|
485
|
+
pageTitle: r.page_title,
|
|
486
|
+
}));
|
|
487
|
+
const base = {
|
|
488
|
+
repo: { id: repo.id, name: repo.name, rootPath, gitBaseline: baseline || null },
|
|
489
|
+
head: head ?? null,
|
|
490
|
+
baseline: baseline || null,
|
|
491
|
+
};
|
|
492
|
+
if (head === null) {
|
|
493
|
+
return { ...base, clean: false, anchorStale: true, reason: "git-unreadable", ...summarizeClaims(claims, null, { pages }) };
|
|
494
|
+
}
|
|
495
|
+
// 仓库级 anchorStale 保留原来的语义:**骨架**(T0 页的文件清单、模块划分)记下的
|
|
496
|
+
// 那次基线还认不认得出。它和逐页 claims 的起点是两件事,面板上也是两句话——
|
|
497
|
+
// 「骨架要重建」不等于「结论都过时」。两条都算,谁也不掩盖谁。
|
|
498
|
+
let repoStale = false;
|
|
499
|
+
let repoStaleReason = null;
|
|
500
|
+
if (baseline !== "" && baseline !== head) {
|
|
501
|
+
const repoDiff = diffSince(rootPath, baseline, head);
|
|
502
|
+
if (!repoDiff.ok) { repoStale = true; repoStaleReason = repoDiff.reason; }
|
|
503
|
+
}
|
|
504
|
+
const diffByPage = (slug) => {
|
|
505
|
+
const page = pages.find((p) => p.slug === slug);
|
|
506
|
+
if (!page) return null;
|
|
507
|
+
const computed = diffFor(page.diffBaseline);
|
|
508
|
+
return computed !== null && computed.ok ? { files: computed.files, deleted: computed.deleted } : null;
|
|
509
|
+
};
|
|
510
|
+
const summary = summarizeClaims(claims, null, { pages, diffByPage });
|
|
511
|
+
// clean 的判据:每条结论都真判过、且一条都没被碰到。
|
|
512
|
+
// 「有结论但起点认不出 / 没记过」不算 clean——那是不知道,不是没事(#607 的病根);
|
|
513
|
+
// 一条结论都没有时也没有「没事」可说(那是「还没富化」,不是「全部干净」)。
|
|
514
|
+
// 仓库基线 == HEAD → 整仓一条提交都没发生,任何 claims 都不可能被碰过。这一条既短路
|
|
515
|
+
// 「逐页基线没记」的老数据(缺列不是判不了),也覆盖「刚跑完 T0、还没有 conclusion」
|
|
516
|
+
// 的情形:没变就是没变,不必等富化完才敢说。
|
|
517
|
+
// 不是这个情形时才逐页看:有哪页判不了就不说 clean,一条结论都没有也不说 clean
|
|
518
|
+
// (那是「还没富化」,不是「全部干净」——面板上这两句不一样)。
|
|
519
|
+
const repoAtHead = baseline !== "" && baseline === head;
|
|
520
|
+
const undecidable = summary.totals.baselineUnknownPages + summary.totals.anchorStalePages > 0;
|
|
521
|
+
// inconsistentPages 不参与 repoAtHead 的短路:它说的不是「起点认不出」,而是
|
|
522
|
+
// **页面有语义正文、库里却一条 claims 都没有**(上次 T1 没写完的残局)。这种页的
|
|
523
|
+
// 证据根本无法核对,与仓库有没有新提交无关;只要还有这种页,就不能说「全部干净」,
|
|
524
|
+
// 否则面板会把一次没落库的富化说成没事。
|
|
525
|
+
const clean = summary.totals.atRiskClaims === 0
|
|
526
|
+
&& summary.totals.inconsistentPages === 0
|
|
527
|
+
&& (repoAtHead || (summary.totals.claims > 0 && !undecidable && !repoStale));
|
|
528
|
+
return {
|
|
529
|
+
...base,
|
|
530
|
+
clean,
|
|
531
|
+
anchorStale: repoStale,
|
|
532
|
+
reason: repoStale ? repoStaleReason : null,
|
|
533
|
+
...summary,
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// --- chorography 向量(收尾档 P37–P39)---------------------------------
|
|
538
|
+
// 惰性物化(P37):只在 host 侧检索路径调用(embedder 由 index.js 注入);
|
|
539
|
+
// CLI 环境没有 embedder → 向量永不物化,检索退关键词。staleness 两维:
|
|
540
|
+
// 页 generated_at 变了(内容变了)、行 dim ≠ 本次查询向量 dim(换 embedder
|
|
541
|
+
// 模型了,P38)→ 重嵌。dim 失配是全表性的,遇到一次就把整仓重嵌(几十页)。
|
|
542
|
+
async function embedPageVectors(repoId, embedder, { limit = 200, expectDim = null } = {}) {
|
|
543
|
+
const pages = db.prepare(
|
|
544
|
+
`SELECT p.id, p.generated_at, m.slug, m.title, p.rel_path
|
|
545
|
+
FROM choro_pages p JOIN choro_modules m ON p.module_id = m.id
|
|
546
|
+
WHERE m.repo_id = ? ORDER BY m.slug`
|
|
547
|
+
).all(repoId);
|
|
548
|
+
const repo = db.prepare("SELECT root_path FROM choro_repos WHERE id = ?").get(repoId);
|
|
549
|
+
const embedOne = embedder.embedSingle ? (t) => embedder.embedSingle(t) : (t) => embedder.embed(t);
|
|
550
|
+
const rows = store.getChoroPageVectors(repoId);
|
|
551
|
+
const bySlug = new Map(rows.map((r) => [r.slug, r]));
|
|
552
|
+
const result = { embedded: 0, skipped: 0, stale: 0, failed: [], dimMismatch: false };
|
|
553
|
+
// dim 失配的全量重嵌(P38):expectDim 来自查询向量(调用方先嵌查询再物化,
|
|
554
|
+
// 维度免费拿到)。expectDim 与任何现存行失配 → 本轮 fresh 检查失效,全量重嵌;
|
|
555
|
+
// 没给 expectDim(无查询场景)退化为只按 generated_at 判 staleness。
|
|
556
|
+
const dimDrift = expectDim !== null && rows.some((r) => r.dim !== expectDim);
|
|
557
|
+
if (dimDrift) result.dimMismatch = true;
|
|
558
|
+
for (const page of pages) {
|
|
559
|
+
if (result.embedded >= limit) break;
|
|
560
|
+
const row = bySlug.get(page.slug);
|
|
561
|
+
const fresh = !dimDrift && row && row.source_generated_at === page.generated_at && row.vector;
|
|
562
|
+
if (fresh) { result.skipped++; continue; }
|
|
563
|
+
if (row && row.vector) result.stale++;
|
|
564
|
+
const abs = join(repo.root_path, page.rel_path);
|
|
565
|
+
if (!existsSync(abs)) continue;
|
|
566
|
+
let text;
|
|
567
|
+
try {
|
|
568
|
+
text = pageEmbedText(readFileSync(abs, "utf8"));
|
|
569
|
+
} catch {
|
|
570
|
+
result.failed.push({ slug: page.slug, reason: "page-unreadable" });
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
if (text.trim() === "") { result.skipped++; continue; }
|
|
574
|
+
let vector;
|
|
575
|
+
try {
|
|
576
|
+
vector = await embedOne(text);
|
|
577
|
+
} catch (err) {
|
|
578
|
+
result.failed.push({ slug: page.slug, reason: String(err?.message ?? err).slice(0, 120) });
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
if (!Array.isArray(vector) || vector.length === 0) {
|
|
582
|
+
result.failed.push({ slug: page.slug, reason: "embedder-returned-no-vector" });
|
|
583
|
+
continue;
|
|
584
|
+
}
|
|
585
|
+
if (row && row.dim !== vector.length) result.dimMismatch = true;
|
|
586
|
+
store.saveChoroPageVector(page.id, {
|
|
587
|
+
vector,
|
|
588
|
+
dim: vector.length,
|
|
589
|
+
model: String(embedder.model ?? "choro"),
|
|
590
|
+
sourceGeneratedAt: page.generated_at,
|
|
591
|
+
createdAt: new Date().toISOString(),
|
|
592
|
+
});
|
|
593
|
+
result.embedded++;
|
|
594
|
+
}
|
|
595
|
+
return result;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/** 嵌查询文本(宿主侧)。embedder 缺席 / 不可用 → null。 */
|
|
599
|
+
async function embedQueryText(embedder, q) {
|
|
600
|
+
if (!embedder) return null;
|
|
601
|
+
const embedOne = embedder.embedSingle ? (t) => embedder.embedSingle(t) : (t) => embedder.embed(t);
|
|
602
|
+
try {
|
|
603
|
+
const v = await embedOne(String(q ?? ""));
|
|
604
|
+
return Array.isArray(v) && v.length > 0 ? v : null;
|
|
605
|
+
} catch {
|
|
606
|
+
return null;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/** 纯余弦 Top-N:向量已给定时零嵌入。行 dim 与查询失配的行过滤掉(P38)。 */
|
|
611
|
+
function semanticTopN(repoId, queryVector, { limit = 10 } = {}) {
|
|
612
|
+
if (!Array.isArray(queryVector) || queryVector.length === 0) return [];
|
|
613
|
+
const rows = store.getChoroPageVectors(repoId).filter((r) => r.vector && r.dim === queryVector.length);
|
|
614
|
+
return rows
|
|
615
|
+
.map((r) => ({
|
|
616
|
+
slug: r.slug,
|
|
617
|
+
title: r.title,
|
|
618
|
+
score: Number(cosine(queryVector, JSON.parse(r.vector)).toFixed(4)),
|
|
619
|
+
}))
|
|
620
|
+
.sort((a, b) => b.score - a.score)
|
|
621
|
+
.slice(0, Math.max(1, Math.min(50, Number(limit) || 10)));
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/** 便捷组合:嵌查询 + Top-N(P39:独立数组,不与 keyword hits 混排序)。
|
|
625
|
+
* 空查询 → [](空白查询的「语义」是噪声,面板逐键触发时尤其如此);
|
|
626
|
+
* embedder 缺席 → null(调用方退纯关键词)。 */
|
|
627
|
+
async function searchSemantic(repoId, embedder, q, { limit = 10 } = {}) {
|
|
628
|
+
if (!embedder) return null;
|
|
629
|
+
if (String(q ?? "").trim() === "") return [];
|
|
630
|
+
const queryVector = await embedQueryText(embedder, q);
|
|
631
|
+
if (!queryVector) return [];
|
|
632
|
+
return semanticTopN(repoId, queryVector, { limit });
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
return {
|
|
636
|
+
listRepos, listRepoFacts, repoFacts, repoFactsById, repoFromRow, listRepoRow, repoByRoot,
|
|
637
|
+
resolveRepo, moduleMap, readPage, search, repoByCwd, outline, claimDrift,
|
|
638
|
+
embedPageVectors, searchSemantic, embedQueryText, semanticTopN,
|
|
639
|
+
};
|
|
640
|
+
}
|