@modusensus/dsh-mneme 0.14.0-beta.1 → 0.14.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +830 -830
- package/assets/icon.svg +3 -3
- package/bin/cli.mjs +964 -964
- package/bin/dsh-mneme-choro.mjs +262 -262
- package/bin/dsh-mneme-mcp.mjs +510 -510
- package/bin/dsh-mneme-serve.mjs +151 -151
- package/cordis.patch.yml +21 -21
- package/docs/CONFIGURATION.md +407 -404
- package/docs/CONTINUITY.md +139 -139
- package/docs/DAEMON.md +57 -57
- package/docs/DIRECTORIES.md +114 -114
- package/docs/DISTILL.md +397 -371
- package/docs/DSH-GO.md +97 -97
- package/docs/ENTITIES.md +247 -247
- package/docs/FORGET.md +77 -77
- package/docs/KB.md +110 -110
- package/docs/LOCAL_MODEL.md +222 -222
- package/docs/SEMANTIC.md +256 -256
- package/docs/SHARE.md +552 -552
- package/docs/SLEEP.md +202 -202
- package/docs/STORAGE.md +122 -122
- package/docs/WORKFLOWS.md +366 -366
- package/lib/api-standalone.js +1007 -1007
- package/lib/api.js +3600 -3600
- package/lib/attachment-text.js +122 -122
- package/lib/bootstrap-http.js +105 -105
- package/lib/bootstrap.js +215 -215
- package/lib/candidates.js +454 -454
- package/lib/capabilities.js +819 -819
- package/lib/choro-command.js +111 -111
- package/lib/client.js +14420 -14322
- package/lib/commands.js +95 -95
- package/lib/config-keys.js +144 -144
- package/lib/config.js +1361 -1344
- package/lib/content-hash.js +54 -54
- package/lib/continuity.js +258 -258
- package/lib/date-hint.js +32 -0
- package/lib/diary/auto.js +190 -190
- package/lib/diary/compose.js +440 -440
- package/lib/diary/dir.js +59 -59
- package/lib/diary/export.js +97 -97
- package/lib/diary/index.js +26 -26
- package/lib/diary/library.js +189 -189
- package/lib/diary/qq-inbox.js +142 -142
- package/lib/diary/rollover.js +61 -61
- package/lib/diary/session-provider.js +122 -122
- package/lib/document-index.js +94 -94
- package/lib/document.js +322 -322
- package/lib/dream/clustering.js +123 -123
- package/lib/dream/decisions.js +945 -945
- package/lib/dream/narratives.js +44 -44
- package/lib/dream/sleep.js +2027 -2027
- package/lib/dream/triage.js +139 -139
- package/lib/dream.js +1879 -1879
- package/lib/dsh-go/host.js +162 -162
- package/lib/dsh-go/panel.js +2091 -2091
- package/lib/dsh-go/text.js +342 -342
- package/lib/dsh-go/views/common.js +68 -68
- package/lib/dsh-go/views/entity.js +48 -48
- package/lib/dsh-go/views/memory.js +198 -198
- package/lib/dsh-go/views/root.js +30 -30
- package/lib/dsh-go/views/settings.js +398 -398
- package/lib/dsh-go/views/status.js +51 -51
- package/lib/embedding.js +342 -342
- package/lib/entities/extractor.js +285 -285
- package/lib/fence.js +24 -24
- package/lib/graph/anchoring.js +184 -184
- package/lib/guide.js +74 -74
- package/lib/heat.js +144 -144
- package/lib/hot-memory.js +119 -119
- package/lib/image-describe.js +158 -0
- package/lib/index.js +852 -852
- package/lib/inject.js +656 -651
- package/lib/kb/index.js +309 -309
- package/lib/kb/routes.js +219 -219
- package/lib/kb/tools.js +148 -148
- package/lib/lang.js +607 -595
- package/lib/local-embedder.js +358 -358
- package/lib/maintenance.js +162 -162
- package/lib/memory-source.js +9 -9
- package/lib/mirror.js +330 -330
- package/lib/organize.js +456 -456
- package/lib/peak-hours.js +172 -172
- package/lib/pipeline-activities.js +178 -178
- package/lib/pipeline-attribution.js +165 -165
- package/lib/pipeline-command.js +279 -279
- package/lib/pipeline-confirm.js +142 -142
- package/lib/pipeline-exec.js +174 -174
- package/lib/pipeline-nudge-auto.js +129 -129
- package/lib/pipeline-nudge.js +1623 -1623
- package/lib/pipeline-observations.js +181 -181
- package/lib/pipeline-observe.js +133 -133
- package/lib/pipeline-runner.js +361 -361
- package/lib/pipeline-runs.js +442 -442
- package/lib/pipeline.js +716 -716
- package/lib/placement.js +13 -13
- package/lib/pre-inject-gate.js +354 -354
- package/lib/profile-extract.js +164 -164
- package/lib/profile-facts.js +356 -356
- package/lib/provenance-http.js +251 -251
- package/lib/quality-filter.js +162 -162
- package/lib/recall-stats.js +190 -190
- package/lib/reclaim-http.js +150 -150
- package/lib/reranker.js +269 -269
- package/lib/runtime/adopt.js +176 -176
- package/lib/runtime/closure.js +172 -172
- package/lib/runtime/download.js +247 -247
- package/lib/runtime/layout.js +265 -265
- package/lib/runtime/loader.js +252 -252
- package/lib/runtime/model-fetch.js +361 -361
- package/lib/runtime/provision.js +237 -237
- package/lib/runtime/tarball.js +130 -130
- package/lib/runtime/verify.js +223 -223
- package/lib/safe-slice.js +18 -18
- package/lib/scope-state.js +277 -277
- package/lib/scope.js +187 -187
- package/lib/search/adaptive.js +60 -60
- package/lib/search/bm25.js +96 -96
- package/lib/semantic.js +572 -572
- package/lib/sensitive-scan.js +209 -209
- package/lib/serve.js +228 -228
- package/lib/service.js +3268 -3268
- package/lib/session-gate.js +91 -91
- package/lib/session-usage.js +350 -350
- package/lib/settings.js +827 -817
- package/lib/share-http.js +281 -281
- package/lib/share-import.js +234 -234
- package/lib/share.js +335 -335
- package/lib/store.js +5895 -5895
- package/lib/summarize.js +1388 -1343
- package/lib/tools.js +1370 -1356
- package/lib/user-settings.js +230 -230
- package/lib/vector-index.js +180 -180
- package/lib/version-check.js +122 -122
- package/lib/wiki/claims.js +267 -267
- package/lib/wiki/cli-hint.js +90 -90
- package/lib/wiki/inject.js +164 -164
- package/lib/wiki/partition.js +200 -200
- package/lib/wiki/plan.js +214 -214
- package/lib/wiki/queries.js +640 -640
- package/lib/wiki/render.js +261 -261
- package/lib/wiki/scan.js +371 -371
- package/lib/wiki/t0.js +500 -500
- package/lib/wiki/t1-jobs.js +232 -232
- package/lib/wiki/t1.js +609 -609
- package/lib/write-admission.js +404 -404
- package/locale/en.json +6 -6
- package/locale/zh.json +6 -6
- package/package.json +1 -1
- package/runtime-manifest.json +526 -526
- package/scripts/benchmark-embed.js +201 -201
- package/scripts/benchmark-recall.js +249 -249
- package/scripts/benchmark-rerank.js +166 -166
- package/scripts/build-runtime-manifest.mjs +224 -224
- package/scripts/changelog-prep.mjs +91 -91
- package/scripts/check-sync.js +44 -44
- package/scripts/e2e-dsh.js +220 -220
- package/scripts/mneme-runtime.mjs +277 -277
- package/scripts/publish-gate.mjs +197 -197
- package/scripts/release-docs.mjs +210 -210
- package/scripts/release-notes.mjs +164 -164
- package/scripts/stress-dsh.js +255 -255
- package/scripts/sync-lib.js +47 -47
- package/scripts/sync-test-badge.mjs +69 -69
- package/scripts/test-count-sync.mjs +48 -48
- package/src/api-standalone.js +1007 -1007
- package/src/api.js +3600 -3600
- package/src/attachment-text.js +122 -122
- package/src/bootstrap-http.js +105 -105
- package/src/bootstrap.js +215 -215
- package/src/candidates.js +454 -454
- package/src/capabilities.js +819 -819
- package/src/choro-command.js +111 -111
- package/src/commands.js +95 -95
- package/src/config-keys.js +144 -144
- package/src/config.js +1361 -1344
- package/src/content-hash.js +54 -54
- package/src/continuity.js +258 -258
- package/src/date-hint.js +32 -0
- package/src/diary/auto.js +190 -190
- package/src/diary/compose.js +440 -440
- package/src/diary/dir.js +59 -59
- package/src/diary/export.js +97 -97
- package/src/diary/index.js +26 -26
- package/src/diary/library.js +189 -189
- package/src/diary/qq-inbox.js +142 -142
- package/src/diary/rollover.js +61 -61
- package/src/diary/session-provider.js +122 -122
- package/src/document-index.js +94 -94
- package/src/document.js +322 -322
- package/src/dream/clustering.js +123 -123
- package/src/dream/decisions.js +945 -945
- package/src/dream/narratives.js +44 -44
- package/src/dream/sleep.js +2027 -2027
- package/src/dream/triage.js +139 -139
- package/src/dream.js +1879 -1879
- package/src/dsh-go/host.js +162 -162
- package/src/dsh-go/panel.js +2091 -2091
- package/src/dsh-go/text.js +342 -342
- package/src/dsh-go/views/common.js +68 -68
- package/src/dsh-go/views/entity.js +48 -48
- package/src/dsh-go/views/memory.js +198 -198
- package/src/dsh-go/views/root.js +30 -30
- package/src/dsh-go/views/settings.js +398 -398
- package/src/dsh-go/views/status.js +51 -51
- package/src/embedding.js +342 -342
- package/src/entities/extractor.js +285 -285
- package/src/fence.js +24 -24
- package/src/graph/anchoring.js +184 -184
- package/src/guide.js +74 -74
- package/src/heat.js +144 -144
- package/src/hot-memory.js +119 -119
- package/src/image-describe.js +158 -0
- package/src/index.js +852 -852
- package/src/inject.js +656 -651
- package/src/kb/index.js +309 -309
- package/src/kb/routes.js +219 -219
- package/src/kb/tools.js +148 -148
- package/src/lang.js +607 -595
- package/src/local-embedder.js +358 -358
- package/src/maintenance.js +162 -162
- package/src/memory-source.js +9 -9
- package/src/mirror.js +330 -330
- package/src/organize.js +456 -456
- package/src/peak-hours.js +172 -172
- package/src/pipeline-activities.js +178 -178
- package/src/pipeline-attribution.js +165 -165
- package/src/pipeline-command.js +279 -279
- package/src/pipeline-confirm.js +142 -142
- package/src/pipeline-exec.js +174 -174
- package/src/pipeline-nudge-auto.js +129 -129
- package/src/pipeline-nudge.js +1623 -1623
- package/src/pipeline-observations.js +181 -181
- package/src/pipeline-observe.js +133 -133
- package/src/pipeline-runner.js +361 -361
- package/src/pipeline-runs.js +442 -442
- package/src/pipeline.js +716 -716
- package/src/placement.js +13 -13
- package/src/pre-inject-gate.js +354 -354
- package/src/profile-extract.js +164 -164
- package/src/profile-facts.js +356 -356
- package/src/provenance-http.js +251 -251
- package/src/quality-filter.js +162 -162
- package/src/recall-stats.js +190 -190
- package/src/reclaim-http.js +150 -150
- package/src/reranker.js +269 -269
- package/src/runtime/adopt.js +176 -176
- package/src/runtime/closure.js +172 -172
- package/src/runtime/download.js +247 -247
- package/src/runtime/layout.js +265 -265
- package/src/runtime/loader.js +252 -252
- package/src/runtime/model-fetch.js +361 -361
- package/src/runtime/provision.js +237 -237
- package/src/runtime/tarball.js +130 -130
- package/src/runtime/verify.js +223 -223
- package/src/safe-slice.js +18 -18
- package/src/scope-state.js +277 -277
- package/src/scope.js +187 -187
- package/src/search/adaptive.js +60 -60
- package/src/search/bm25.js +96 -96
- package/src/semantic.js +572 -572
- package/src/sensitive-scan.js +209 -209
- package/src/serve.js +228 -228
- package/src/service.js +3268 -3268
- package/src/session-gate.js +91 -91
- package/src/session-usage.js +350 -350
- package/src/settings.js +827 -817
- package/src/share-http.js +281 -281
- package/src/share-import.js +234 -234
- package/src/share.js +335 -335
- package/src/store.js +5895 -5895
- package/src/summarize.js +1388 -1343
- package/src/tools.js +1370 -1356
- package/src/user-settings.js +230 -230
- package/src/vector-index.js +180 -180
- package/src/version-check.js +122 -122
- package/src/wiki/claims.js +267 -267
- package/src/wiki/cli-hint.js +90 -90
- package/src/wiki/inject.js +164 -164
- package/src/wiki/partition.js +200 -200
- package/src/wiki/plan.js +214 -214
- package/src/wiki/queries.js +640 -640
- package/src/wiki/render.js +261 -261
- package/src/wiki/scan.js +371 -371
- package/src/wiki/t0.js +500 -500
- package/src/wiki/t1-jobs.js +232 -232
- package/src/wiki/t1.js +609 -609
- package/src/write-admission.js +404 -404
- package/test/adaptive-inject-budget.test.js +85 -85
- package/test/api-hardening.test.js +173 -173
- package/test/api-nudge-route.test.js +259 -259
- package/test/api-routes-gaps.test.js +1092 -1092
- package/test/api.test.js +3007 -2998
- package/test/attachment-text.test.js +139 -139
- package/test/audit.test.js +448 -448
- package/test/baseline-persist.test.js +248 -248
- package/test/benchmark.test.js +94 -94
- package/test/bm25-corpus-cache.test.js +145 -145
- package/test/bootstrap.test.js +228 -228
- package/test/candidates.test.js +345 -345
- package/test/capabilities.test.js +577 -577
- package/test/capacity-warning.test.js +262 -262
- package/test/changelog-prep.test.js +125 -125
- package/test/choro-command.test.js +218 -218
- package/test/cli-enums.test.js +37 -37
- package/test/cli-flag-value.test.js +86 -86
- package/test/cli-receive.test.js +390 -390
- package/test/cli-share.test.js +437 -437
- package/test/client-share.test.js +225 -225
- package/test/client.test.js +4595 -4425
- package/test/clustering.test.js +100 -100
- package/test/commands.test.js +106 -106
- package/test/config-keys-routes.test.js +233 -233
- package/test/config.test.js +153 -153
- package/test/conflict-freeze.test.js +344 -344
- package/test/conflict-ledger-enforce.test.js +452 -452
- package/test/conflict-ledger.test.js +242 -242
- package/test/conflict-reviewed.test.js +79 -79
- package/test/content-hash.test.js +204 -204
- package/test/continuity.test.js +452 -452
- package/test/contrast.test.js +432 -432
- package/test/date-hint.test.js +156 -0
- package/test/dedupe-candidate-window.test.js +180 -180
- package/test/dependency-overrides.test.js +78 -78
- package/test/diary-library.test.js +187 -187
- package/test/diary.test.js +576 -576
- package/test/differentiate-feedback.test.js +248 -248
- package/test/distill-tx-embed-gap.test.js +110 -110
- package/test/docs-config-keys.test.js +65 -65
- package/test/document-dir.test.js +233 -233
- package/test/document.test.js +703 -703
- package/test/dream-archive-guardrail.test.js +116 -116
- package/test/dream-baseline-clamp.test.js +174 -174
- package/test/dream-candidate-hybrid.test.js +281 -281
- package/test/dream-coverage-degrade.test.js +86 -86
- package/test/dream-decision-evidence.test.js +314 -314
- package/test/dream-failure-backoff.test.js +173 -173
- package/test/dream-fence.test.js +207 -207
- package/test/dream-maxtokens-fallback.test.js +132 -132
- package/test/dream-merge-guardrail.test.js +219 -219
- package/test/dream-peak-hours.test.js +194 -194
- package/test/dream.test.js +1528 -1528
- package/test/dsh-go-host.test.js +134 -134
- package/test/dsh-go-panel-actions.test.js +854 -854
- package/test/dsh-go-panel.test.js +251 -251
- package/test/embed-ready-poll.test.js +94 -94
- package/test/embed-text-reasons.test.js +112 -112
- package/test/embedding-cache-f32.test.js +276 -276
- package/test/embedding-ready.test.js +226 -226
- package/test/entities.test.js +711 -711
- package/test/entity-recall.test.js +179 -179
- package/test/epistemic.test.js +325 -325
- package/test/failure-query.test.js +147 -147
- package/test/failure-retention.test.js +105 -105
- package/test/fnew-0112.test.js +311 -311
- package/test/fnew-03.test.js +422 -422
- package/test/forget-corrections.test.js +70 -70
- package/test/graph-anchoring.test.js +184 -184
- package/test/graph-api.test.js +333 -333
- package/test/graph-boosted-relations.test.js +189 -189
- package/test/graph-inject-budget.test.js +103 -103
- package/test/graph-multipath.test.js +269 -269
- package/test/graph-passive-confirm.test.js +91 -91
- package/test/graph-weight-evolution.test.js +104 -104
- package/test/heat.test.js +175 -175
- package/test/helpers/dream-mock.js +86 -86
- package/test/helpers/peer-worker.mjs +25 -25
- package/test/helpers/runtime-source.js +60 -60
- package/test/helpers/tar-builder.js +80 -80
- package/test/helpers/write-admission-samples.js +312 -312
- package/test/hot-memory.test.js +259 -259
- package/test/image-description.test.js +271 -0
- package/test/image-memory.test.js +170 -170
- package/test/inject-content-cap.test.js +91 -91
- package/test/inject-first-round-bm25.test.js +89 -89
- package/test/inject-heat.test.js +77 -77
- package/test/inject-parent-gate.test.js +134 -109
- package/test/inject-pin-explicit.test.js +300 -300
- package/test/inject-pin-vector-crowd.test.js +225 -225
- package/test/inject-pools.test.js +195 -195
- package/test/inject-recall.test.js +172 -172
- package/test/inject-rotation.test.js +161 -161
- package/test/inject.test.js +553 -553
- package/test/issue-617-step1-query-and-fusion.test.js +361 -361
- package/test/kb-api-wiring.test.js +128 -128
- package/test/kb-collections.test.js +272 -272
- package/test/kb-routes.test.js +379 -379
- package/test/lang.test.js +98 -98
- package/test/lastrunat-persist.test.js +101 -101
- package/test/lib-smoke.test.js +116 -116
- package/test/llm-audit.test.js +815 -815
- package/test/llm-message-source.test.js +207 -207
- package/test/local-embedder.test.js +292 -292
- package/test/maintenance.test.js +501 -501
- package/test/mcp-stdio.test.js +385 -385
- package/test/mint-source.test.js +311 -311
- package/test/mirror-dirty.test.js +424 -424
- package/test/mirror-edit-digest.test.js +187 -187
- package/test/mirror-generation.test.js +499 -499
- package/test/mirror-types.test.js +208 -208
- package/test/mirror.test.js +249 -249
- package/test/narrative-bars.test.js +162 -162
- package/test/organize-tool.test.js +232 -232
- package/test/organize.test.js +418 -418
- package/test/panel-chart-scale.test.js +90 -90
- package/test/panel-tokens.test.js +245 -245
- package/test/peak-hours-observability.test.js +70 -70
- package/test/peer-blockers.test.js +193 -193
- package/test/persona-addressing.test.js +83 -83
- package/test/persona-api.test.js +172 -172
- package/test/persona-by-session.test.js +568 -568
- package/test/persona-flag.test.js +36 -36
- package/test/persona-inject.test.js +145 -145
- package/test/persona-settings.test.js +198 -198
- package/test/pipeline-activities.test.js +155 -155
- package/test/pipeline-attribution.test.js +175 -175
- package/test/pipeline-command.test.js +243 -243
- package/test/pipeline-confirm.test.js +148 -148
- package/test/pipeline-exec.test.js +172 -172
- package/test/pipeline-nudge-auto.test.js +248 -248
- package/test/pipeline-nudge.test.js +1278 -1278
- package/test/pipeline-observations.test.js +157 -157
- package/test/pipeline-observe.test.js +127 -127
- package/test/pipeline-r1r2.test.js +278 -278
- package/test/pipeline-runner.test.js +468 -468
- package/test/pipeline-runs.test.js +767 -767
- package/test/pipeline.test.js +636 -636
- package/test/placement.test.js +36 -36
- package/test/policy-epoch.test.js +259 -259
- package/test/pre-inject-gate.test.js +444 -444
- package/test/profile-extract.test.js +269 -269
- package/test/profile-facts.test.js +492 -492
- package/test/prompt-stance.test.js +128 -128
- package/test/provenance-http.test.js +367 -367
- package/test/publish-gate.test.js +272 -272
- package/test/quality-backfill.test.js +290 -290
- package/test/quality-filter.test.js +295 -295
- package/test/readme-claims.test.js +113 -113
- package/test/reasoning-effort.test.js +752 -752
- package/test/recall-anti-update.test.js +82 -82
- package/test/recall-evals.test.js +253 -253
- package/test/recall-layer.test.js +326 -326
- package/test/recall-retention.test.js +128 -128
- package/test/recall-stats.test.js +274 -274
- package/test/receipt-chain.test.js +451 -451
- package/test/reclaim-http.test.js +209 -209
- package/test/reflection-update-gate.test.js +129 -129
- package/test/reflection.test.js +226 -226
- package/test/reindex-backfill.test.js +475 -475
- package/test/reindex-scheduler.test.js +369 -369
- package/test/release-docs.test.js +156 -156
- package/test/release-notes.test.js +265 -265
- package/test/release-workflow.test.js +93 -93
- package/test/reranker-single-logit.test.js +73 -73
- package/test/reranker.test.js +197 -197
- package/test/resident-status.test.js +83 -83
- package/test/route-policy.test.js +113 -113
- package/test/runtime-adopt.test.js +206 -206
- package/test/runtime-cli.test.js +89 -89
- package/test/runtime-closure.test.js +193 -193
- package/test/runtime-download.test.js +302 -302
- package/test/runtime-layout.test.js +163 -163
- package/test/runtime-loader.test.js +255 -255
- package/test/runtime-manifest.test.js +159 -159
- package/test/runtime-model-fetch.test.js +348 -348
- package/test/runtime-provision.test.js +101 -101
- package/test/runtime-tarball.test.js +81 -81
- package/test/runtime-verify.test.js +319 -319
- package/test/scope-candidates.test.js +491 -491
- package/test/scope-distill-stamp.test.js +134 -134
- package/test/scope-explicit.test.js +452 -452
- package/test/scope-inject-soft.test.js +106 -106
- package/test/scope-optional-service.test.js +110 -110
- package/test/scope-retrieval.test.js +197 -197
- package/test/scope-state.test.js +610 -610
- package/test/scope-storage.test.js +255 -255
- package/test/scope-strict.test.js +367 -367
- package/test/search-fusion.test.js +90 -90
- package/test/security-doc.test.js +188 -188
- package/test/semantic.test.js +124 -124
- package/test/sensitive-scan.test.js +251 -251
- package/test/serve-bin.test.js +121 -121
- package/test/serve.test.js +258 -258
- package/test/service-search.test.js +174 -174
- package/test/service.test.js +410 -410
- package/test/session-gate.test.js +264 -264
- package/test/session-usage.test.js +317 -317
- package/test/settings.test.js +273 -273
- package/test/share-foreign-rows.test.js +144 -144
- package/test/share-http.test.js +144 -144
- package/test/share-import.test.js +577 -577
- package/test/share-imported-guard.test.js +311 -311
- package/test/share-imported-identity.test.js +111 -111
- package/test/share-pack.test.js +511 -511
- package/test/share-receive-http.test.js +756 -756
- package/test/share-routes.test.js +337 -337
- package/test/share-store.test.js +133 -133
- package/test/share-validate.test.js +101 -101
- package/test/skip-invalid-restore.test.js +262 -262
- package/test/sleep-action-set.test.js +441 -441
- package/test/sleep-catchup.test.js +315 -315
- package/test/sleep-failure-backoff.test.js +156 -156
- package/test/sleep-heat.test.js +174 -174
- package/test/sleep-scheduler-rearm.test.js +89 -89
- package/test/sleep.test.js +1455 -1455
- package/test/standalone-api.test.js +1344 -1344
- package/test/store-vector-cache.test.js +98 -98
- package/test/store-write-path-columns.test.js +191 -191
- package/test/store.test.js +744 -744
- package/test/stress.test.js +209 -209
- package/test/summarize-occurred-at.test.js +127 -127
- package/test/summarize-parse-diagnostics.test.js +75 -75
- package/test/summarize-reasoning-effort.test.js +189 -189
- package/test/summarize.test.js +2251 -2251
- package/test/supersede-state-tracking.test.js +394 -394
- package/test/test-count-sync.test.js +71 -71
- package/test/tool-state-ignore.test.js +65 -65
- package/test/tools-dto-schema-matrix.test.js +398 -398
- package/test/tools-memory-get-schema.test.js +65 -65
- package/test/tools.test.js +585 -585
- package/test/updated-at-semantics.test.js +113 -113
- package/test/user-settings-preview.test.js +127 -127
- package/test/user-settings-segments.test.js +527 -527
- package/test/vector-index.test.js +334 -334
- package/test/version-check.test.js +152 -152
- package/test/webServer-optional.test.js +82 -82
- package/test/wiki-api.test.js +734 -734
- package/test/wiki-claims.test.js +194 -194
- package/test/wiki-cli-hint.test.js +118 -118
- package/test/wiki-inject.test.js +265 -265
- package/test/wiki-partition.test.js +112 -112
- package/test/wiki-plan.test.js +80 -80
- package/test/wiki-provenance.test.js +202 -202
- package/test/wiki-queries.test.js +398 -398
- package/test/wiki-scan.test.js +208 -208
- package/test/wiki-store.test.js +190 -190
- package/test/wiki-t0.test.js +456 -456
- package/test/wiki-t1-jobs.test.js +125 -125
- package/test/wiki-t1.test.js +460 -460
- package/test/wiki-tools.test.js +176 -176
- package/test/workflow-guards.test.js +155 -155
- package/test/write-admission.test.js +668 -668
|
@@ -1,181 +1,181 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* P3 的两个产出:观察旁挂(丙方案)与 tool 不符检出。
|
|
3
|
-
*
|
|
4
|
-
* ## 背景(一句话)
|
|
5
|
-
*
|
|
6
|
-
* 自动归属被实测否决(14.2%,第三方复核 74% 变体唯一),所以写账由人确认;
|
|
7
|
-
* 而人确认时说「这一步做了」,可与本次**实际观察到的业务动作**对不上——
|
|
8
|
-
* 「声明是 npm test,这次一次 npm test 都没看到」。这个矛盾是步骤表过时的信号,
|
|
9
|
-
* 就是 P3 要捕的东西(设计见 mneme-research/mo-foundations/10 号)。
|
|
10
|
-
*
|
|
11
|
-
* ## 丙方案:观察为什么不进账本、要旁挂
|
|
12
|
-
*
|
|
13
|
-
* 账本 `.runs.jsonl` 是**已发布的数据契约**(v0.9.0 发出去的形状),为 P3 加字段
|
|
14
|
-
* 是「动已发布契约」,代价与风险都不必要。所以观察走旁挂文件:
|
|
15
|
-
*
|
|
16
|
-
* <memoryDir>/observations/<项目键>/<工作流id>.jsonl
|
|
17
|
-
*
|
|
18
|
-
* 这在这个仓库有**三次同构先例**:candidates / proposals / 本目录。
|
|
19
|
-
* 共同纪律:**项目键分桶、只增不改(append-only)、不进仓库、坏行跳过但计数**。
|
|
20
|
-
*
|
|
21
|
-
* ## 🔴 隐私
|
|
22
|
-
*
|
|
23
|
-
* 观察清单的每一项都来自 `pipeline-activities` 的产物——那一层已经**剥掉了位置参数**
|
|
24
|
-
* (完整命令行含绝对路径,路径含本机用户名)。本模块**只存 `{key, count, flags}`**,
|
|
25
|
-
* 拒收任何带命令原文的形状。这是第二道闸:即使上游改了,这里也不放行。
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
import { existsSync, mkdirSync, readFileSync, appendFileSync, writeFileSync } from "node:fs";
|
|
29
|
-
import { dirname, join } from "node:path";
|
|
30
|
-
import { homedir } from "node:os";
|
|
31
|
-
|
|
32
|
-
/** 展开 `~`;观察旁挂跟 candidates/proposals 一样是本地状态(candidates.js 的 expandHome 同构)。
|
|
33
|
-
* config.memoryDir 的默认值是 "~/.dsh/memory",不展开的话 readdir 在带 "~" 的字面路径上
|
|
34
|
-
* 永远落空——旁挂静默读不到,fix-step-tool 提议少一类(CodeRabbit #458 F2)。 */
|
|
35
|
-
function expandHome(p) {
|
|
36
|
-
const s = String(p || "");
|
|
37
|
-
if (s === "~") return homedir();
|
|
38
|
-
if (s.startsWith("~/") || s.startsWith("~\\")) return join(homedir(), s.slice(2));
|
|
39
|
-
return s;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** 观察旁挂的目录:`<memoryDir>/observations/<项目键>/`。与 candidates/proposals 同构。 */
|
|
43
|
-
export function observationDir({ memoryDir, projectKey } = {}) {
|
|
44
|
-
if (!projectKey || String(projectKey).trim() === "") return null;
|
|
45
|
-
const base = expandHome(memoryDir && String(memoryDir).trim() ? memoryDir : "~/.dsh/memory");
|
|
46
|
-
return join(base, "observations", projectKey);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** 观察行的形状闸:只认 {key, count}(flags 可选且必须是旗标数组)。多一个字段都拒。 */
|
|
50
|
-
export function validateObservationLine(raw) {
|
|
51
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return { ok: false, reason: "not-object" };
|
|
52
|
-
if (typeof raw.key !== "string" || raw.key.trim() === "") return { ok: false, reason: "bad-key" };
|
|
53
|
-
if (!Number.isInteger(raw.count) || raw.count < 1) return { ok: false, reason: "bad-count" };
|
|
54
|
-
if (raw.flags !== undefined && !Array.isArray(raw.flags)) return { ok: false, reason: "bad-flags" };
|
|
55
|
-
if (raw.command !== undefined || raw.args !== undefined || raw.segments !== undefined) {
|
|
56
|
-
// 🔴 命令原文(或其片段)不许出现在观察行里——那是位置参数泄露的口子
|
|
57
|
-
return { ok: false, reason: "forbidden-field" };
|
|
58
|
-
}
|
|
59
|
-
return { ok: true };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* 落盘一次确认时的观察清单(append-only,幂等键 = confirmAt)。
|
|
64
|
-
*
|
|
65
|
-
* 返回 `{ok, file, lines}`;传 `dryRun: true` 时只校验不写(测试与预览用)。
|
|
66
|
-
*/
|
|
67
|
-
export function saveObservations({ dir, pipelineId, confirmAt, activities, dryRun = false } = {}) {
|
|
68
|
-
if (!dir) return { ok: false, reason: "no-dir" };
|
|
69
|
-
if (!pipelineId || typeof pipelineId !== "string") return { ok: false, reason: "no-pipeline-id" };
|
|
70
|
-
if (typeof confirmAt !== "string" || confirmAt.trim() === "") return { ok: false, reason: "no-confirm-at" };
|
|
71
|
-
if (!Array.isArray(activities)) return { ok: false, reason: "bad-activities" };
|
|
72
|
-
|
|
73
|
-
const valid = [];
|
|
74
|
-
for (const a of activities) {
|
|
75
|
-
// 🔴 先验**原始输入**再构造:多带 command/args/segments 的行要**大声拒**,
|
|
76
|
-
// 不是悄悄剥掉——静默剥离会把上游的形状 bug 藏进一次"成功"的写入里。
|
|
77
|
-
const v0 = validateObservationLine(a);
|
|
78
|
-
if (!v0.ok) return { ok: false, reason: "invalid-line:" + v0.reason, file: null, lines: 0 };
|
|
79
|
-
const line = { key: a.key, count: a.count, ...(Array.isArray(a.flags) && a.flags.length ? { flags: a.flags } : {}) };
|
|
80
|
-
valid.push(line);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const record = { contract: "mneme.observations/v1", pipelineId, confirmAt, activities: valid };
|
|
84
|
-
const file = join(dir, `${pipelineId}.jsonl`);
|
|
85
|
-
|
|
86
|
-
// 幂等:同一 confirmAt 已写过就不重复(confirm 保留待确认状态重答的场景会重放)
|
|
87
|
-
if (existsSync(file)) {
|
|
88
|
-
const seen = readFileSync(file, "utf8").split("\n").filter((l) => l.trim());
|
|
89
|
-
for (const l of seen) {
|
|
90
|
-
try { if (JSON.parse(l).confirmAt === confirmAt) return { ok: true, file, lines: 0, duplicate: true }; }
|
|
91
|
-
catch { /* 坏行:与读侧同一纪律,跳过但不出声 */ }
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (dryRun) return { ok: true, file, lines: valid.length, dryRun: true };
|
|
96
|
-
mkdirSync(dir, { recursive: true });
|
|
97
|
-
appendFileSync(file, JSON.stringify(record) + "\n", "utf8");
|
|
98
|
-
return { ok: true, file, lines: valid.length };
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/** 读一个工作流攒下的全部观察(按 confirmAt 升序,坏行跳过并计数)。 */
|
|
102
|
-
export function loadObservations(dir, pipelineId) {
|
|
103
|
-
if (!dir || !pipelineId) return { records: [], unreadable: 0 };
|
|
104
|
-
const file = join(dir, `${pipelineId}.jsonl`);
|
|
105
|
-
if (!existsSync(file)) return { records: [], unreadable: 0 };
|
|
106
|
-
const records = [];
|
|
107
|
-
let unreadable = 0;
|
|
108
|
-
for (const l of readFileSync(file, "utf8").split("\n")) {
|
|
109
|
-
if (!l.trim()) continue;
|
|
110
|
-
try {
|
|
111
|
-
const r = JSON.parse(l);
|
|
112
|
-
if (r?.contract === "mneme.observations/v1" && Array.isArray(r.activities)) records.push(r);
|
|
113
|
-
else unreadable += 1;
|
|
114
|
-
} catch { unreadable += 1; }
|
|
115
|
-
}
|
|
116
|
-
return { records, unreadable };
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* 批量读多个工作流的观察,压成 readPipelineDoc 要的形状:
|
|
121
|
-
* { [pipelineId]: { latest: { activities } } }
|
|
122
|
-
*
|
|
123
|
-
* `latest` 取最后一条记录(confirmAt 升序写入,末条即最近一次确认)。
|
|
124
|
-
* 没有观察的 id 不出现在结果里——读侧用 `?? null` 降级,不制造空壳。
|
|
125
|
-
*/
|
|
126
|
-
export function loadObservationsById(dir, pipelineIds) {
|
|
127
|
-
const out = {};
|
|
128
|
-
for (const id of pipelineIds || []) {
|
|
129
|
-
if (typeof id !== "string" || id.trim() === "") continue;
|
|
130
|
-
const { records, unreadable } = loadObservations(dir, id);
|
|
131
|
-
if (records.length === 0) {
|
|
132
|
-
// 有文件但全坏也如实记一条空 latest?——不。空 = 没有观察,与坏文件同形处理,
|
|
133
|
-
// unreadable 的计数只在单工作流读取时暴露,批量视图里降级为「没有」。
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
const last = records[records.length - 1];
|
|
137
|
-
out[id] = { latest: { activities: last.activities }, unreadable };
|
|
138
|
-
}
|
|
139
|
-
return out;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* 🔴 核心判据:从「人确认的执行集」与「本次观察到的动作」里算出 tool 不符。
|
|
144
|
-
*
|
|
145
|
-
* - `pipeline.steps[].tool`:人手写的声明(可能是 `git push`,或 `a / b` 多锚点)
|
|
146
|
-
* - `confirmedDone`:人说了 done 的 stepId 集合
|
|
147
|
-
* - `activities`:本次观察到的业务动作(`{key, count}`)——**只认 key,不认命令原文**
|
|
148
|
-
*
|
|
149
|
-
* 返回不符清单。**unsure 与没答不进判据**(那不是「做了但没看到」,是「没确认」)。
|
|
150
|
-
* `declared` 原样带出,不含路径(tool 是作者手写的,且没有经过本模块之外的清洗——
|
|
151
|
-
* 若作者把路径写进 tool,那是步骤表自己的内容,应由步骤表校验层管,不在这里扩大职责)。
|
|
152
|
-
*/
|
|
153
|
-
export function toolMismatches({ pipeline, confirmedDone, activities } = {}) {
|
|
154
|
-
if (!pipeline || !Array.isArray(pipeline.steps)) return { ok: false, reason: "no-pipeline", items: [] };
|
|
155
|
-
if (!(confirmedDone instanceof Set)) return { ok: false, reason: "bad-confirmed", items: [] };
|
|
156
|
-
if (!Array.isArray(activities)) return { ok: false, reason: "bad-activities", items: [] };
|
|
157
|
-
|
|
158
|
-
const seen = new Set(activities.map((a) => String(a?.key ?? "").toLowerCase()).filter(Boolean));
|
|
159
|
-
const items = [];
|
|
160
|
-
for (const step of pipeline.steps) {
|
|
161
|
-
if (!confirmedDone.has(step.id)) continue;
|
|
162
|
-
const declared = typeof step.tool === "string" ? step.tool.trim() : "";
|
|
163
|
-
if (declared === "") continue; // 纯人判步骤(无 tool 声明)无从矛盾
|
|
164
|
-
const anchors = declared.split("/").map((t) => t.trim().toLowerCase()).filter(Boolean);
|
|
165
|
-
// 多锚点(`a / b`)任一出现即算对上——它们是作者声明的同义做法
|
|
166
|
-
const hit = anchors.some((a) => seen.has(a));
|
|
167
|
-
if (!hit) {
|
|
168
|
-
items.push({
|
|
169
|
-
stepId: step.id,
|
|
170
|
-
title: step.title,
|
|
171
|
-
declared,
|
|
172
|
-
observedTop: activities
|
|
173
|
-
.slice()
|
|
174
|
-
.sort((a, b) => (b.count ?? 0) - (a.count ?? 0))
|
|
175
|
-
.slice(0, 3)
|
|
176
|
-
.map((a) => `${a.key} ×${a.count}`)
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
return { ok: true, items };
|
|
181
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* P3 的两个产出:观察旁挂(丙方案)与 tool 不符检出。
|
|
3
|
+
*
|
|
4
|
+
* ## 背景(一句话)
|
|
5
|
+
*
|
|
6
|
+
* 自动归属被实测否决(14.2%,第三方复核 74% 变体唯一),所以写账由人确认;
|
|
7
|
+
* 而人确认时说「这一步做了」,可与本次**实际观察到的业务动作**对不上——
|
|
8
|
+
* 「声明是 npm test,这次一次 npm test 都没看到」。这个矛盾是步骤表过时的信号,
|
|
9
|
+
* 就是 P3 要捕的东西(设计见 mneme-research/mo-foundations/10 号)。
|
|
10
|
+
*
|
|
11
|
+
* ## 丙方案:观察为什么不进账本、要旁挂
|
|
12
|
+
*
|
|
13
|
+
* 账本 `.runs.jsonl` 是**已发布的数据契约**(v0.9.0 发出去的形状),为 P3 加字段
|
|
14
|
+
* 是「动已发布契约」,代价与风险都不必要。所以观察走旁挂文件:
|
|
15
|
+
*
|
|
16
|
+
* <memoryDir>/observations/<项目键>/<工作流id>.jsonl
|
|
17
|
+
*
|
|
18
|
+
* 这在这个仓库有**三次同构先例**:candidates / proposals / 本目录。
|
|
19
|
+
* 共同纪律:**项目键分桶、只增不改(append-only)、不进仓库、坏行跳过但计数**。
|
|
20
|
+
*
|
|
21
|
+
* ## 🔴 隐私
|
|
22
|
+
*
|
|
23
|
+
* 观察清单的每一项都来自 `pipeline-activities` 的产物——那一层已经**剥掉了位置参数**
|
|
24
|
+
* (完整命令行含绝对路径,路径含本机用户名)。本模块**只存 `{key, count, flags}`**,
|
|
25
|
+
* 拒收任何带命令原文的形状。这是第二道闸:即使上游改了,这里也不放行。
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { existsSync, mkdirSync, readFileSync, appendFileSync, writeFileSync } from "node:fs";
|
|
29
|
+
import { dirname, join } from "node:path";
|
|
30
|
+
import { homedir } from "node:os";
|
|
31
|
+
|
|
32
|
+
/** 展开 `~`;观察旁挂跟 candidates/proposals 一样是本地状态(candidates.js 的 expandHome 同构)。
|
|
33
|
+
* config.memoryDir 的默认值是 "~/.dsh/memory",不展开的话 readdir 在带 "~" 的字面路径上
|
|
34
|
+
* 永远落空——旁挂静默读不到,fix-step-tool 提议少一类(CodeRabbit #458 F2)。 */
|
|
35
|
+
function expandHome(p) {
|
|
36
|
+
const s = String(p || "");
|
|
37
|
+
if (s === "~") return homedir();
|
|
38
|
+
if (s.startsWith("~/") || s.startsWith("~\\")) return join(homedir(), s.slice(2));
|
|
39
|
+
return s;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** 观察旁挂的目录:`<memoryDir>/observations/<项目键>/`。与 candidates/proposals 同构。 */
|
|
43
|
+
export function observationDir({ memoryDir, projectKey } = {}) {
|
|
44
|
+
if (!projectKey || String(projectKey).trim() === "") return null;
|
|
45
|
+
const base = expandHome(memoryDir && String(memoryDir).trim() ? memoryDir : "~/.dsh/memory");
|
|
46
|
+
return join(base, "observations", projectKey);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 观察行的形状闸:只认 {key, count}(flags 可选且必须是旗标数组)。多一个字段都拒。 */
|
|
50
|
+
export function validateObservationLine(raw) {
|
|
51
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return { ok: false, reason: "not-object" };
|
|
52
|
+
if (typeof raw.key !== "string" || raw.key.trim() === "") return { ok: false, reason: "bad-key" };
|
|
53
|
+
if (!Number.isInteger(raw.count) || raw.count < 1) return { ok: false, reason: "bad-count" };
|
|
54
|
+
if (raw.flags !== undefined && !Array.isArray(raw.flags)) return { ok: false, reason: "bad-flags" };
|
|
55
|
+
if (raw.command !== undefined || raw.args !== undefined || raw.segments !== undefined) {
|
|
56
|
+
// 🔴 命令原文(或其片段)不许出现在观察行里——那是位置参数泄露的口子
|
|
57
|
+
return { ok: false, reason: "forbidden-field" };
|
|
58
|
+
}
|
|
59
|
+
return { ok: true };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 落盘一次确认时的观察清单(append-only,幂等键 = confirmAt)。
|
|
64
|
+
*
|
|
65
|
+
* 返回 `{ok, file, lines}`;传 `dryRun: true` 时只校验不写(测试与预览用)。
|
|
66
|
+
*/
|
|
67
|
+
export function saveObservations({ dir, pipelineId, confirmAt, activities, dryRun = false } = {}) {
|
|
68
|
+
if (!dir) return { ok: false, reason: "no-dir" };
|
|
69
|
+
if (!pipelineId || typeof pipelineId !== "string") return { ok: false, reason: "no-pipeline-id" };
|
|
70
|
+
if (typeof confirmAt !== "string" || confirmAt.trim() === "") return { ok: false, reason: "no-confirm-at" };
|
|
71
|
+
if (!Array.isArray(activities)) return { ok: false, reason: "bad-activities" };
|
|
72
|
+
|
|
73
|
+
const valid = [];
|
|
74
|
+
for (const a of activities) {
|
|
75
|
+
// 🔴 先验**原始输入**再构造:多带 command/args/segments 的行要**大声拒**,
|
|
76
|
+
// 不是悄悄剥掉——静默剥离会把上游的形状 bug 藏进一次"成功"的写入里。
|
|
77
|
+
const v0 = validateObservationLine(a);
|
|
78
|
+
if (!v0.ok) return { ok: false, reason: "invalid-line:" + v0.reason, file: null, lines: 0 };
|
|
79
|
+
const line = { key: a.key, count: a.count, ...(Array.isArray(a.flags) && a.flags.length ? { flags: a.flags } : {}) };
|
|
80
|
+
valid.push(line);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const record = { contract: "mneme.observations/v1", pipelineId, confirmAt, activities: valid };
|
|
84
|
+
const file = join(dir, `${pipelineId}.jsonl`);
|
|
85
|
+
|
|
86
|
+
// 幂等:同一 confirmAt 已写过就不重复(confirm 保留待确认状态重答的场景会重放)
|
|
87
|
+
if (existsSync(file)) {
|
|
88
|
+
const seen = readFileSync(file, "utf8").split("\n").filter((l) => l.trim());
|
|
89
|
+
for (const l of seen) {
|
|
90
|
+
try { if (JSON.parse(l).confirmAt === confirmAt) return { ok: true, file, lines: 0, duplicate: true }; }
|
|
91
|
+
catch { /* 坏行:与读侧同一纪律,跳过但不出声 */ }
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (dryRun) return { ok: true, file, lines: valid.length, dryRun: true };
|
|
96
|
+
mkdirSync(dir, { recursive: true });
|
|
97
|
+
appendFileSync(file, JSON.stringify(record) + "\n", "utf8");
|
|
98
|
+
return { ok: true, file, lines: valid.length };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** 读一个工作流攒下的全部观察(按 confirmAt 升序,坏行跳过并计数)。 */
|
|
102
|
+
export function loadObservations(dir, pipelineId) {
|
|
103
|
+
if (!dir || !pipelineId) return { records: [], unreadable: 0 };
|
|
104
|
+
const file = join(dir, `${pipelineId}.jsonl`);
|
|
105
|
+
if (!existsSync(file)) return { records: [], unreadable: 0 };
|
|
106
|
+
const records = [];
|
|
107
|
+
let unreadable = 0;
|
|
108
|
+
for (const l of readFileSync(file, "utf8").split("\n")) {
|
|
109
|
+
if (!l.trim()) continue;
|
|
110
|
+
try {
|
|
111
|
+
const r = JSON.parse(l);
|
|
112
|
+
if (r?.contract === "mneme.observations/v1" && Array.isArray(r.activities)) records.push(r);
|
|
113
|
+
else unreadable += 1;
|
|
114
|
+
} catch { unreadable += 1; }
|
|
115
|
+
}
|
|
116
|
+
return { records, unreadable };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 批量读多个工作流的观察,压成 readPipelineDoc 要的形状:
|
|
121
|
+
* { [pipelineId]: { latest: { activities } } }
|
|
122
|
+
*
|
|
123
|
+
* `latest` 取最后一条记录(confirmAt 升序写入,末条即最近一次确认)。
|
|
124
|
+
* 没有观察的 id 不出现在结果里——读侧用 `?? null` 降级,不制造空壳。
|
|
125
|
+
*/
|
|
126
|
+
export function loadObservationsById(dir, pipelineIds) {
|
|
127
|
+
const out = {};
|
|
128
|
+
for (const id of pipelineIds || []) {
|
|
129
|
+
if (typeof id !== "string" || id.trim() === "") continue;
|
|
130
|
+
const { records, unreadable } = loadObservations(dir, id);
|
|
131
|
+
if (records.length === 0) {
|
|
132
|
+
// 有文件但全坏也如实记一条空 latest?——不。空 = 没有观察,与坏文件同形处理,
|
|
133
|
+
// unreadable 的计数只在单工作流读取时暴露,批量视图里降级为「没有」。
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const last = records[records.length - 1];
|
|
137
|
+
out[id] = { latest: { activities: last.activities }, unreadable };
|
|
138
|
+
}
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 🔴 核心判据:从「人确认的执行集」与「本次观察到的动作」里算出 tool 不符。
|
|
144
|
+
*
|
|
145
|
+
* - `pipeline.steps[].tool`:人手写的声明(可能是 `git push`,或 `a / b` 多锚点)
|
|
146
|
+
* - `confirmedDone`:人说了 done 的 stepId 集合
|
|
147
|
+
* - `activities`:本次观察到的业务动作(`{key, count}`)——**只认 key,不认命令原文**
|
|
148
|
+
*
|
|
149
|
+
* 返回不符清单。**unsure 与没答不进判据**(那不是「做了但没看到」,是「没确认」)。
|
|
150
|
+
* `declared` 原样带出,不含路径(tool 是作者手写的,且没有经过本模块之外的清洗——
|
|
151
|
+
* 若作者把路径写进 tool,那是步骤表自己的内容,应由步骤表校验层管,不在这里扩大职责)。
|
|
152
|
+
*/
|
|
153
|
+
export function toolMismatches({ pipeline, confirmedDone, activities } = {}) {
|
|
154
|
+
if (!pipeline || !Array.isArray(pipeline.steps)) return { ok: false, reason: "no-pipeline", items: [] };
|
|
155
|
+
if (!(confirmedDone instanceof Set)) return { ok: false, reason: "bad-confirmed", items: [] };
|
|
156
|
+
if (!Array.isArray(activities)) return { ok: false, reason: "bad-activities", items: [] };
|
|
157
|
+
|
|
158
|
+
const seen = new Set(activities.map((a) => String(a?.key ?? "").toLowerCase()).filter(Boolean));
|
|
159
|
+
const items = [];
|
|
160
|
+
for (const step of pipeline.steps) {
|
|
161
|
+
if (!confirmedDone.has(step.id)) continue;
|
|
162
|
+
const declared = typeof step.tool === "string" ? step.tool.trim() : "";
|
|
163
|
+
if (declared === "") continue; // 纯人判步骤(无 tool 声明)无从矛盾
|
|
164
|
+
const anchors = declared.split("/").map((t) => t.trim().toLowerCase()).filter(Boolean);
|
|
165
|
+
// 多锚点(`a / b`)任一出现即算对上——它们是作者声明的同义做法
|
|
166
|
+
const hit = anchors.some((a) => seen.has(a));
|
|
167
|
+
if (!hit) {
|
|
168
|
+
items.push({
|
|
169
|
+
stepId: step.id,
|
|
170
|
+
title: step.title,
|
|
171
|
+
declared,
|
|
172
|
+
observedTop: activities
|
|
173
|
+
.slice()
|
|
174
|
+
.sort((a, b) => (b.count ?? 0) - (a.count ?? 0))
|
|
175
|
+
.slice(0, 3)
|
|
176
|
+
.map((a) => `${a.key} ×${a.count}`)
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return { ok: true, items };
|
|
181
|
+
}
|
package/lib/pipeline-observe.js
CHANGED
|
@@ -1,133 +1,133 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 把宿主的会话事件,翻成归属规则认得的**观察**。
|
|
3
|
-
*
|
|
4
|
-
* ## 为什么需要这一层(接线前实测出来的事实)
|
|
5
|
-
*
|
|
6
|
-
* 会话日志里的工具调用长这样:`name:"pwsh"`,`arguments:'{"command":"npm test"}'`。
|
|
7
|
-
* 而工作流里人写的 `tool` 是**命令行**:`"npm test"`、`"git push <tag>"`。
|
|
8
|
-
* **两者不重叠**——直接拿 `name` 去比,一条都对不上(实测 400 个会话里
|
|
9
|
-
* `pwsh` 1399 次、`read` 1709 次、`glob` 183 次,而工作流写的是 npm/git/gh 命令)。
|
|
10
|
-
*
|
|
11
|
-
* 唯一能对上的是:**命令跑在哪个工具里**。所以这一层的职责就是把它抽出来——
|
|
12
|
-
* `{name:"pwsh", arguments:{command:"npm test"}}` → `{command:"npm test"}`。
|
|
13
|
-
*
|
|
14
|
-
* ## 为什么只抽「命令执行器」,其余一律不给观察
|
|
15
|
-
*
|
|
16
|
-
* 真实工作流 19 步的 `tool` 全是 shell 命令(git / gh / npm / node)。而 `read`、`write`、
|
|
17
|
-
* `glob`、`grep` 这些是**代理的日常动作**,不是任何工作流的一步。硬把
|
|
18
|
-
* `read C:\…\file.js` 拼成一个「命令行」去参与匹配,只会制造噪声——而噪声的代价是
|
|
19
|
-
* 把某一步凭空染色。所以认不出来命令执行器,就**不产观察**(返回 null),
|
|
20
|
-
* 由上游记进 skipped 桶:**「不是工作流的动作」与「认不出是哪一步」是两件事。**
|
|
21
|
-
*
|
|
22
|
-
* ## 成败的三态
|
|
23
|
-
*
|
|
24
|
-
* `tool/result` 带 `message.isError`。只认 true/false 两个值,取不到时留 `null`
|
|
25
|
-
* ——把「不知道」折叠成「失败」会给这一步打上不该有的 refuted 标记。
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
/** 命令执行器:只有跑在这些工具里的调用,才可能是工作流写成的那种命令行。 */
|
|
29
|
-
const COMMAND_TOOLS = new Set(["pwsh", "bash", "sh", "shell", "cmd", "run_command", "terminal"]);
|
|
30
|
-
|
|
31
|
-
/** 参数里哪个字段是命令行。按工具名给,取不到就退回常见字段名。 */
|
|
32
|
-
const COMMAND_KEYS = ["command", "cmd", "script", "commandLine"];
|
|
33
|
-
|
|
34
|
-
function parseArgs(raw) {
|
|
35
|
-
if (raw === null || raw === undefined) return null;
|
|
36
|
-
if (typeof raw === "object" && !Array.isArray(raw)) return raw;
|
|
37
|
-
if (typeof raw !== "string") return null;
|
|
38
|
-
try {
|
|
39
|
-
const parsed = JSON.parse(raw);
|
|
40
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
41
|
-
} catch {
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* 这次工具调用是不是一条工作流命令。是就给出命令行,不是给 `null`。
|
|
48
|
-
*
|
|
49
|
-
* 返回 `{tool, command}`;`command` 已去掉首尾空白(比较由归属规则负责)。
|
|
50
|
-
*/
|
|
51
|
-
export function commandFromToolCall(name, args) {
|
|
52
|
-
const tool = typeof name === "string" ? name.trim() : "";
|
|
53
|
-
if (tool === "" || !COMMAND_TOOLS.has(tool.toLowerCase())) return null;
|
|
54
|
-
const parsed = parseArgs(args);
|
|
55
|
-
if (!parsed) return null;
|
|
56
|
-
for (const key of COMMAND_KEYS) {
|
|
57
|
-
const value = parsed[key];
|
|
58
|
-
if (typeof value === "string" && value.trim() !== "") {
|
|
59
|
-
return { tool, command: value.trim() };
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
// 是命令执行器但参数里没有命令行(形状变了):不猜,给 null。
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** `tool/result` 的成败:只认布尔,拿不到留 null(「不知道」不许折叠成「失败」)。 */
|
|
67
|
-
export function outcomeOfToolResult(event) {
|
|
68
|
-
const message = event?.data?.message;
|
|
69
|
-
if (!message || typeof message !== "object") return null;
|
|
70
|
-
if (message.isError === true) return false;
|
|
71
|
-
if (message.isError === false) return true;
|
|
72
|
-
// 旧形状把 isError 挂在 content 的 tool-result 块上;两种都不认时留 null。
|
|
73
|
-
const blocks = Array.isArray(message.content) ? message.content : [];
|
|
74
|
-
const legacy = blocks.find((b) => b && b.type === "tool-result");
|
|
75
|
-
if (legacy && typeof legacy.isError === "boolean") return legacy.isError !== true;
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* 把一串会话事件配对成观察列表(纯函数,便于测试与批处理)。
|
|
81
|
-
*
|
|
82
|
-
* 配对靠 `callId`:`tool/call` 先到、`tool/result` 后到。**只有拿到结果的调用才产观察**
|
|
83
|
-
* ——结果决定了成败,而「调了但没结果」既不是成功也不是失败(可能被中断),
|
|
84
|
-
* 拿它当一次成功使用是编造。
|
|
85
|
-
*
|
|
86
|
-
* 另外两条纪律:
|
|
87
|
-
* - 认不出命令执行器的调用**直接丢**(进 `notCommands` 计数),不产观察
|
|
88
|
-
* - 有调用没结果的,如实进 `dangling`,不产观察也不假装失败
|
|
89
|
-
*/
|
|
90
|
-
export function pairObservations(events) {
|
|
91
|
-
const pending = new Map(); // callId -> {tool, command, at}
|
|
92
|
-
const observations = [];
|
|
93
|
-
const dangling = [];
|
|
94
|
-
let notCommands = 0;
|
|
95
|
-
|
|
96
|
-
for (const event of events || []) {
|
|
97
|
-
const type = event?.type;
|
|
98
|
-
if (type === "tool/call") {
|
|
99
|
-
const data = event.data ?? {};
|
|
100
|
-
const extracted = commandFromToolCall(data.name, data.arguments);
|
|
101
|
-
if (!extracted) {
|
|
102
|
-
notCommands += 1;
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
const callId = data.callId ?? null;
|
|
106
|
-
const entry = { ...extracted, at: event.at ?? null };
|
|
107
|
-
if (callId === null) {
|
|
108
|
-
// 没有 callId 就无法配对結果;不硬凑,如实记下。
|
|
109
|
-
dangling.push({ ...entry, why: "调用没有 callId,无法与结果配对" });
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
pending.set(callId, entry);
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (type === "tool/result") {
|
|
117
|
-
const callId = event?.data?.callId ?? event?.callId ?? null;
|
|
118
|
-
if (callId === null || !pending.has(callId)) continue;
|
|
119
|
-
const entry = pending.get(callId);
|
|
120
|
-
pending.delete(callId);
|
|
121
|
-
observations.push({
|
|
122
|
-
tool: entry.tool,
|
|
123
|
-
command: entry.command,
|
|
124
|
-
ok: outcomeOfToolResult(event),
|
|
125
|
-
at: entry.at
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
for (const entry of pending.values()) dangling.push({ ...entry, why: "调用没有等到结果" });
|
|
131
|
-
|
|
132
|
-
return { observations, dangling, notCommands };
|
|
133
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* 把宿主的会话事件,翻成归属规则认得的**观察**。
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么需要这一层(接线前实测出来的事实)
|
|
5
|
+
*
|
|
6
|
+
* 会话日志里的工具调用长这样:`name:"pwsh"`,`arguments:'{"command":"npm test"}'`。
|
|
7
|
+
* 而工作流里人写的 `tool` 是**命令行**:`"npm test"`、`"git push <tag>"`。
|
|
8
|
+
* **两者不重叠**——直接拿 `name` 去比,一条都对不上(实测 400 个会话里
|
|
9
|
+
* `pwsh` 1399 次、`read` 1709 次、`glob` 183 次,而工作流写的是 npm/git/gh 命令)。
|
|
10
|
+
*
|
|
11
|
+
* 唯一能对上的是:**命令跑在哪个工具里**。所以这一层的职责就是把它抽出来——
|
|
12
|
+
* `{name:"pwsh", arguments:{command:"npm test"}}` → `{command:"npm test"}`。
|
|
13
|
+
*
|
|
14
|
+
* ## 为什么只抽「命令执行器」,其余一律不给观察
|
|
15
|
+
*
|
|
16
|
+
* 真实工作流 19 步的 `tool` 全是 shell 命令(git / gh / npm / node)。而 `read`、`write`、
|
|
17
|
+
* `glob`、`grep` 这些是**代理的日常动作**,不是任何工作流的一步。硬把
|
|
18
|
+
* `read C:\…\file.js` 拼成一个「命令行」去参与匹配,只会制造噪声——而噪声的代价是
|
|
19
|
+
* 把某一步凭空染色。所以认不出来命令执行器,就**不产观察**(返回 null),
|
|
20
|
+
* 由上游记进 skipped 桶:**「不是工作流的动作」与「认不出是哪一步」是两件事。**
|
|
21
|
+
*
|
|
22
|
+
* ## 成败的三态
|
|
23
|
+
*
|
|
24
|
+
* `tool/result` 带 `message.isError`。只认 true/false 两个值,取不到时留 `null`
|
|
25
|
+
* ——把「不知道」折叠成「失败」会给这一步打上不该有的 refuted 标记。
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/** 命令执行器:只有跑在这些工具里的调用,才可能是工作流写成的那种命令行。 */
|
|
29
|
+
const COMMAND_TOOLS = new Set(["pwsh", "bash", "sh", "shell", "cmd", "run_command", "terminal"]);
|
|
30
|
+
|
|
31
|
+
/** 参数里哪个字段是命令行。按工具名给,取不到就退回常见字段名。 */
|
|
32
|
+
const COMMAND_KEYS = ["command", "cmd", "script", "commandLine"];
|
|
33
|
+
|
|
34
|
+
function parseArgs(raw) {
|
|
35
|
+
if (raw === null || raw === undefined) return null;
|
|
36
|
+
if (typeof raw === "object" && !Array.isArray(raw)) return raw;
|
|
37
|
+
if (typeof raw !== "string") return null;
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(raw);
|
|
40
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 这次工具调用是不是一条工作流命令。是就给出命令行,不是给 `null`。
|
|
48
|
+
*
|
|
49
|
+
* 返回 `{tool, command}`;`command` 已去掉首尾空白(比较由归属规则负责)。
|
|
50
|
+
*/
|
|
51
|
+
export function commandFromToolCall(name, args) {
|
|
52
|
+
const tool = typeof name === "string" ? name.trim() : "";
|
|
53
|
+
if (tool === "" || !COMMAND_TOOLS.has(tool.toLowerCase())) return null;
|
|
54
|
+
const parsed = parseArgs(args);
|
|
55
|
+
if (!parsed) return null;
|
|
56
|
+
for (const key of COMMAND_KEYS) {
|
|
57
|
+
const value = parsed[key];
|
|
58
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
59
|
+
return { tool, command: value.trim() };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// 是命令执行器但参数里没有命令行(形状变了):不猜,给 null。
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** `tool/result` 的成败:只认布尔,拿不到留 null(「不知道」不许折叠成「失败」)。 */
|
|
67
|
+
export function outcomeOfToolResult(event) {
|
|
68
|
+
const message = event?.data?.message;
|
|
69
|
+
if (!message || typeof message !== "object") return null;
|
|
70
|
+
if (message.isError === true) return false;
|
|
71
|
+
if (message.isError === false) return true;
|
|
72
|
+
// 旧形状把 isError 挂在 content 的 tool-result 块上;两种都不认时留 null。
|
|
73
|
+
const blocks = Array.isArray(message.content) ? message.content : [];
|
|
74
|
+
const legacy = blocks.find((b) => b && b.type === "tool-result");
|
|
75
|
+
if (legacy && typeof legacy.isError === "boolean") return legacy.isError !== true;
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 把一串会话事件配对成观察列表(纯函数,便于测试与批处理)。
|
|
81
|
+
*
|
|
82
|
+
* 配对靠 `callId`:`tool/call` 先到、`tool/result` 后到。**只有拿到结果的调用才产观察**
|
|
83
|
+
* ——结果决定了成败,而「调了但没结果」既不是成功也不是失败(可能被中断),
|
|
84
|
+
* 拿它当一次成功使用是编造。
|
|
85
|
+
*
|
|
86
|
+
* 另外两条纪律:
|
|
87
|
+
* - 认不出命令执行器的调用**直接丢**(进 `notCommands` 计数),不产观察
|
|
88
|
+
* - 有调用没结果的,如实进 `dangling`,不产观察也不假装失败
|
|
89
|
+
*/
|
|
90
|
+
export function pairObservations(events) {
|
|
91
|
+
const pending = new Map(); // callId -> {tool, command, at}
|
|
92
|
+
const observations = [];
|
|
93
|
+
const dangling = [];
|
|
94
|
+
let notCommands = 0;
|
|
95
|
+
|
|
96
|
+
for (const event of events || []) {
|
|
97
|
+
const type = event?.type;
|
|
98
|
+
if (type === "tool/call") {
|
|
99
|
+
const data = event.data ?? {};
|
|
100
|
+
const extracted = commandFromToolCall(data.name, data.arguments);
|
|
101
|
+
if (!extracted) {
|
|
102
|
+
notCommands += 1;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const callId = data.callId ?? null;
|
|
106
|
+
const entry = { ...extracted, at: event.at ?? null };
|
|
107
|
+
if (callId === null) {
|
|
108
|
+
// 没有 callId 就无法配对結果;不硬凑,如实记下。
|
|
109
|
+
dangling.push({ ...entry, why: "调用没有 callId,无法与结果配对" });
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
pending.set(callId, entry);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (type === "tool/result") {
|
|
117
|
+
const callId = event?.data?.callId ?? event?.callId ?? null;
|
|
118
|
+
if (callId === null || !pending.has(callId)) continue;
|
|
119
|
+
const entry = pending.get(callId);
|
|
120
|
+
pending.delete(callId);
|
|
121
|
+
observations.push({
|
|
122
|
+
tool: entry.tool,
|
|
123
|
+
command: entry.command,
|
|
124
|
+
ok: outcomeOfToolResult(event),
|
|
125
|
+
at: entry.at
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for (const entry of pending.values()) dangling.push({ ...entry, why: "调用没有等到结果" });
|
|
131
|
+
|
|
132
|
+
return { observations, dangling, notCommands };
|
|
133
|
+
}
|