@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
package/lib/wiki/scan.js
CHANGED
|
@@ -1,371 +1,371 @@
|
|
|
1
|
-
// dsh-mneme/src/wiki/scan.js
|
|
2
|
-
// T0 的确定性输入面(spec §6 步 2–3):文件清单 + JS/TS import 图。
|
|
3
|
-
// 全程零模型、零网络,输出排序稳定——同一仓库状态两次扫描给出逐项相同的结构。
|
|
4
|
-
//
|
|
5
|
-
// 范围口径(决策 D9):v1 只做 JS/TS 生态(ESM 静态/动态、CJS require、Vue SFC 的
|
|
6
|
-
// <script>、package.json 的 workspaces 与 exports)。Java/Python 仓库退化为目录
|
|
7
|
-
// 启发式——这是 spec 拍板过的取舍(四语言全上等于四个生态各做 40%)。
|
|
8
|
-
//
|
|
9
|
-
// import 提取用正则不用 AST:T0 要的是「这个文件依赖谁」的图,不是逐语句语义;
|
|
10
|
-
// 正则在模板字符串/注释里偶有误报,代价是把一条边多画一次,而引入一个完整 parser
|
|
11
|
-
// 依赖(acorn 等)违背本包的依赖纪律。解析失败的单文件记 skipped 不中断(spec
|
|
12
|
-
// 降级表第三行)。
|
|
13
|
-
import { readdirSync, statSync, readFileSync } from "node:fs";
|
|
14
|
-
import { join, dirname, posix } from "node:path";
|
|
15
|
-
import { createHash } from "node:crypto";
|
|
16
|
-
|
|
17
|
-
// T0 关心的文件集:代码 + 能直接反映约定的配置/文档。清单列它们,图只由代码扩展名构成。
|
|
18
|
-
export const SCAN_EXTENSIONS = new Set([
|
|
19
|
-
".js", ".mjs", ".cjs", ".jsx", ".ts", ".tsx", ".vue",
|
|
20
|
-
".json", ".md", ".css", ".scss", ".less", ".html",
|
|
21
|
-
]);
|
|
22
|
-
|
|
23
|
-
const CODE_EXTENSIONS = new Set([".js", ".mjs", ".cjs", ".jsx", ".ts", ".tsx", ".vue"]);
|
|
24
|
-
|
|
25
|
-
// 默认排除(spec 步 2「跳过 node_modules、构建产物、二进制」):plan.yaml 的
|
|
26
|
-
// exclude 是追加而不是替代——用户排除是表达意图,这份是卫生底线。
|
|
27
|
-
//
|
|
28
|
-
// 点开头目录整类排除(V2 实测回填,见 docs/plans/2026-11-04-chorography-m0.md):
|
|
29
|
-
// 会话产物的落盘习惯就是把东西塞进 .qoder-credits / .superpowers / .shots / .zcode /
|
|
30
|
-
// .dsh-ops / .v2c 这类点目录,而里面常常是提问原文、调试快照与第三方工具缓存——
|
|
31
|
-
// baleen 上它们占 15 个模块里的 6 个,study-desk 上占 5 个里的 3 个。逐个点名厂商等于
|
|
32
|
-
// 把一台机器的工具链钉进发布包,换个工具又漏;「点开头即非仓库内容」这条更短的规则
|
|
33
|
-
// 与三处实测的分布一致,也不随工具换代失效。
|
|
34
|
-
// 代价如实写在这里:.vitepress/theme、.docusaurus 这类确实把真源码放在点目录里的仓库
|
|
35
|
-
// 会漏掉那部分,且**拉不回来**——扫描阶段的排除先于 plan.include 白名单生效。
|
|
36
|
-
// 只按目录名判,点开头的**文件**(.eslintrc.json 之类)照旧扫:那是仓库配置,不是产物。
|
|
37
|
-
export const DEFAULT_EXCLUDE_DIRS = Object.freeze([
|
|
38
|
-
"node_modules", "dist", "build", "out", "coverage",
|
|
39
|
-
]);
|
|
40
|
-
|
|
41
|
-
function isDefaultExcludedDir(name) {
|
|
42
|
-
return name.startsWith(".") || DEFAULT_EXCLUDE_DIRS.includes(name);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const BINARY_EXTENSIONS = new Set([
|
|
46
|
-
".png", ".jpg", ".jpeg", ".gif", ".ico", ".webp", ".avif", ".bmp", ".pdf",
|
|
47
|
-
".zip", ".gz", ".tar", ".7z", ".woff", ".woff2", ".ttf", ".otf", ".eot",
|
|
48
|
-
".mp4", ".mp3", ".webm", ".wav", ".ogg", ".mov", ".sqlite", ".db", ".wasm",
|
|
49
|
-
]);
|
|
50
|
-
|
|
51
|
-
// 噪声锁文件:体积大、内容是依赖锁,对「模块是什么」零贡献。
|
|
52
|
-
const NOISE_FILES = new Set(["package-lock.json", "pnpm-lock.yaml", "yarn.lock"]);
|
|
53
|
-
|
|
54
|
-
// Node 内置模块清单(够用的小集合;node: 前缀之外的老写法也拦住)。
|
|
55
|
-
const NODE_BUILTINS = new Set([
|
|
56
|
-
"assert", "buffer", "child_process", "cluster", "console", "constants",
|
|
57
|
-
"crypto", "dgram", "dns", "domain", "events", "fs", "http", "http2",
|
|
58
|
-
"https", "module", "net", "os", "path", "perf_hooks", "process", "punycode",
|
|
59
|
-
"querystring", "readline", "repl", "sqlite", "stream", "string_decoder",
|
|
60
|
-
"timers", "tls", "tty", "url", "util", "v8", "vm", "worker_threads", "zlib",
|
|
61
|
-
]);
|
|
62
|
-
|
|
63
|
-
/* ------------------------------ 文件清单 ------------------------------ */
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* 列出仓库内 T0 关心的文件。返回 POSIX 风格相对路径的排序数组。
|
|
67
|
-
* exclude 走极小 gitignore 子集:# 注释、空行、尾 `/` 目录、前导 `/` 锚定、
|
|
68
|
-
* `!` 反选、`*` / `**` / `?`。(spec §4:plan.exclude 是 gitignore 语法。)
|
|
69
|
-
*/
|
|
70
|
-
export function listFiles(repoRoot, { exclude = [] } = {}) {
|
|
71
|
-
const matcher = compileExclude(exclude);
|
|
72
|
-
const files = [];
|
|
73
|
-
walk(repoRoot, "", matcher, files);
|
|
74
|
-
return files.sort();
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function walk(absRoot, relDir, matcher, out) {
|
|
78
|
-
let entries;
|
|
79
|
-
try {
|
|
80
|
-
entries = readdirSync(join(absRoot, relDir), { withFileTypes: true });
|
|
81
|
-
} catch {
|
|
82
|
-
return; // 无权限/符号链接悬空:跳过整个目录,不中断
|
|
83
|
-
}
|
|
84
|
-
for (const entry of entries) {
|
|
85
|
-
const rel = relDir ? posix.join(relDir, entry.name) : entry.name;
|
|
86
|
-
if (entry.isDirectory()) {
|
|
87
|
-
if (isDefaultExcludedDir(entry.name)) continue;
|
|
88
|
-
if (matcher.excludedDir(rel)) continue;
|
|
89
|
-
walk(absRoot, rel, matcher, out);
|
|
90
|
-
} else if (entry.isFile()) {
|
|
91
|
-
const ext = posix.extname(entry.name).toLowerCase();
|
|
92
|
-
if (BINARY_EXTENSIONS.has(ext)) continue;
|
|
93
|
-
if (NOISE_FILES.has(entry.name)) continue;
|
|
94
|
-
if (!SCAN_EXTENSIONS.has(ext)) continue;
|
|
95
|
-
if (matcher.excludedFile(rel)) continue;
|
|
96
|
-
out.push(rel);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* gitignore 子集编译成谓词。规则(与真 gitignore 的差异在此声明):
|
|
103
|
-
* - 只实现 目录匹配(尾 /)、锚定(前导 /)、反选(!)、*、**、?;
|
|
104
|
-
* - 不含中段斜杠的模式按真 gitignore 语义匹配任意层级("*.md" 也命中子目录里的);
|
|
105
|
-
* - 中段 ** 跨段;不支持字符组与转义(模式原文进 receipt,用户能看见是哪条没生效)。
|
|
106
|
-
*/
|
|
107
|
-
export function compileExclude(patterns) {
|
|
108
|
-
const rules = patterns
|
|
109
|
-
.map((raw) => String(raw).trim())
|
|
110
|
-
.filter((raw) => raw !== "" && !raw.startsWith("#"))
|
|
111
|
-
.map((raw) => {
|
|
112
|
-
const negate = raw.startsWith("!");
|
|
113
|
-
const body = negate ? raw.slice(1) : raw;
|
|
114
|
-
const anchored = body.startsWith("/");
|
|
115
|
-
const dirOnly = body.endsWith("/");
|
|
116
|
-
const clean = body.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
117
|
-
const re = globToRegExp(clean, { anchored });
|
|
118
|
-
return { negate, dirOnly, re };
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
return {
|
|
122
|
-
excludedDir(rel) {
|
|
123
|
-
return matchRules(rules, rel, true) === true;
|
|
124
|
-
},
|
|
125
|
-
excludedFile(rel) {
|
|
126
|
-
return matchRules(rules, rel, false) === true;
|
|
127
|
-
},
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function matchRules(rules, rel, isDir) {
|
|
132
|
-
// gitignore 语义:后一条规则赢;父目录被排除时文件也应排除(前缀目录逐级试)。
|
|
133
|
-
const segments = rel.split("/");
|
|
134
|
-
for (let i = 1; i <= segments.length; i++) {
|
|
135
|
-
const prefix = segments.slice(0, i).join("/");
|
|
136
|
-
const last = i === segments.length;
|
|
137
|
-
for (const rule of rules) {
|
|
138
|
-
if (rule.dirOnly && !isDir && last) continue;
|
|
139
|
-
if (rule.re.test(prefix)) return !rule.negate ? true : false;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
return false;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function globToRegExp(glob, { anchored }) {
|
|
146
|
-
// 无中段斜杠的模式(含纯目录名)匹配任意层级:^ 前置 (?:[^/]+/)*,段边界靠
|
|
147
|
-
// 尾部 (?:/.*)? 的斜杠要求与 [^/]+ 的段内限定撑住——「temp」不得命中
|
|
148
|
-
// 「mytemp/x」这类子串(^ 是这条性质的前提)。
|
|
149
|
-
const prefix = `^${!anchored && !glob.includes("/") ? "(?:[^/]+/)*" : ""}`;
|
|
150
|
-
let re = "";
|
|
151
|
-
for (let i = 0; i < glob.length; i++) {
|
|
152
|
-
const ch = glob[i];
|
|
153
|
-
if (ch === "*") {
|
|
154
|
-
if (glob[i + 1] === "*") {
|
|
155
|
-
// ** :跨段(允许吃掉 / )或尾段(等同 *)。
|
|
156
|
-
i++;
|
|
157
|
-
if (glob[i + 1] === "/") { i++; re += "(?:[^/]+/)*"; }
|
|
158
|
-
else re += ".*";
|
|
159
|
-
} else {
|
|
160
|
-
re += "[^/]*";
|
|
161
|
-
}
|
|
162
|
-
} else if (ch === "?") {
|
|
163
|
-
re += "[^/]";
|
|
164
|
-
} else {
|
|
165
|
-
re += ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
return new RegExp(`${prefix}${re}(?:/.*)?$`);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* 正向 include 匹配(plan.include 白名单,gitignore 语法的肯定面):文件命中任意
|
|
173
|
-
* 一条模式即收入。与 compileExclude 的区别:没有反选语义,模式即意图。
|
|
174
|
-
*/
|
|
175
|
-
export function compileInclude(patterns) {
|
|
176
|
-
const rules = patterns
|
|
177
|
-
.map((raw) => String(raw).trim())
|
|
178
|
-
.filter((raw) => raw !== "" && !raw.startsWith("#"))
|
|
179
|
-
.map((raw) => {
|
|
180
|
-
const anchored = raw.startsWith("/");
|
|
181
|
-
const dirOnly = raw.endsWith("/");
|
|
182
|
-
const clean = raw.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
183
|
-
return { dirOnly, re: globToRegExp(clean, { anchored }) };
|
|
184
|
-
});
|
|
185
|
-
return {
|
|
186
|
-
matchesFile(rel) {
|
|
187
|
-
if (rules.length === 0) return true;
|
|
188
|
-
const segments = rel.split("/");
|
|
189
|
-
for (let i = 1; i <= segments.length; i++) {
|
|
190
|
-
const prefix = segments.slice(0, i).join("/");
|
|
191
|
-
for (const rule of rules) {
|
|
192
|
-
if (rule.re.test(prefix)) return true;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
return false;
|
|
196
|
-
},
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/* ------------------------------ import 图 ------------------------------ */
|
|
201
|
-
|
|
202
|
-
const IMPORT_PATTERNS = [
|
|
203
|
-
// import x from 'a';import type {x} from 'a'(静态带 from)
|
|
204
|
-
/\bimport\s+(?:type\s+)?[^;'"()]*?\bfrom\s*["']([^"']+)["']/g,
|
|
205
|
-
// import 'a'(副作用导入)
|
|
206
|
-
/\bimport\s*["']([^"']+)["']/g,
|
|
207
|
-
// import('a')(动态导入)
|
|
208
|
-
/\bimport\s*\(\s*["']([^"']+)["']\s*\)/g,
|
|
209
|
-
// export {x} from 'a';export * from 'a'(再导出)
|
|
210
|
-
/\bexport\s+(?:type\s+)?(?:\{[^}]*\}|\*)\s*from\s*["']([^"']+)["']/g,
|
|
211
|
-
// require('a')(CJS)
|
|
212
|
-
/\brequire\s*\(\s*["']([^"']+)["']\s*\)/g,
|
|
213
|
-
];
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* 解析文件清单里的 import 图。返回:
|
|
217
|
-
* edges [{from, to}] 内部边(to 是清单内文件的 POSIX 相对路径)
|
|
218
|
-
* external Map<pkg, Set<fromFile>>
|
|
219
|
-
* unresolved [{from, spec}] 相对/绝对路径但没解析到清单内文件
|
|
220
|
-
* skipped [{file, reason}] 读取/解析失败(不中断,spec 降级表)
|
|
221
|
-
*/
|
|
222
|
-
export function parseImportGraph(repoRoot, files) {
|
|
223
|
-
const fileSet = new Set(files);
|
|
224
|
-
const codeFiles = files.filter((f) => CODE_EXTENSIONS.has(posix.extname(f).toLowerCase()));
|
|
225
|
-
const edges = [];
|
|
226
|
-
const external = new Map();
|
|
227
|
-
const unresolved = [];
|
|
228
|
-
const skipped = [];
|
|
229
|
-
|
|
230
|
-
for (const rel of codeFiles) {
|
|
231
|
-
let text;
|
|
232
|
-
try {
|
|
233
|
-
text = readFileSync(join(repoRoot, rel), "utf8");
|
|
234
|
-
} catch (err) {
|
|
235
|
-
skipped.push({ file: rel, reason: `读取失败:${String(err?.message ?? err)}` });
|
|
236
|
-
continue;
|
|
237
|
-
}
|
|
238
|
-
// Vue SFC:只取 <script> 块;模板里的资源引用不是 import 图的一部分。
|
|
239
|
-
const sources = posix.extname(rel).toLowerCase() === ".vue" ? extractVueScripts(text) : [text];
|
|
240
|
-
const specs = new Set();
|
|
241
|
-
for (const src of sources) {
|
|
242
|
-
for (const re of IMPORT_PATTERNS) {
|
|
243
|
-
re.lastIndex = 0;
|
|
244
|
-
let m;
|
|
245
|
-
while ((m = re.exec(src)) !== null) specs.add(m[1]);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
for (const spec of specs) {
|
|
249
|
-
const resolved = resolveSpecifier(spec, rel, fileSet);
|
|
250
|
-
if (resolved === "external") {
|
|
251
|
-
const pkg = packageNameOf(spec);
|
|
252
|
-
if (!pkg) continue; // 空说明符等病态输入:忽略
|
|
253
|
-
if (isNodeBuiltin(pkg)) continue;
|
|
254
|
-
if (!external.has(pkg)) external.set(pkg, new Set());
|
|
255
|
-
external.get(pkg).add(rel);
|
|
256
|
-
} else if (resolved === "unresolved") {
|
|
257
|
-
unresolved.push({ from: rel, spec });
|
|
258
|
-
} else if (resolved !== null && resolved !== rel) {
|
|
259
|
-
edges.push({ from: rel, to: resolved });
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
return { edges, external, unresolved, skipped };
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function extractVueScripts(text) {
|
|
268
|
-
const out = [];
|
|
269
|
-
const re = /<script\b[^>]*>([\s\S]*?)<\/script>/gi;
|
|
270
|
-
let m;
|
|
271
|
-
while ((m = re.exec(text)) !== null) out.push(m[1]);
|
|
272
|
-
return out;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function packageNameOf(spec) {
|
|
276
|
-
if (spec.startsWith("@")) {
|
|
277
|
-
const parts = spec.split("/");
|
|
278
|
-
if (parts.length >= 2) return `${parts[0]}/${parts[1]}`;
|
|
279
|
-
return spec;
|
|
280
|
-
}
|
|
281
|
-
return spec.split("/")[0];
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
function isNodeBuiltin(pkg) {
|
|
285
|
-
if (pkg.startsWith("node:")) return true;
|
|
286
|
-
return NODE_BUILTINS.has(pkg);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
function resolveSpecifier(spec, fromFile, fileSet) {
|
|
290
|
-
let base;
|
|
291
|
-
if (spec.startsWith("./") || spec.startsWith("../")) {
|
|
292
|
-
base = posix.normalize(posix.join(posix.dirname(fromFile), spec));
|
|
293
|
-
} else if (spec.startsWith("/")) {
|
|
294
|
-
base = spec.replace(/^\/+/, ""); // 仓库根绝对引用(少数配置型写法)
|
|
295
|
-
} else {
|
|
296
|
-
return "external"; // 裸说明符:npm 包或别名;别名 v1 不解析(实现计划记录)
|
|
297
|
-
}
|
|
298
|
-
base = base.split("\\").join("/");
|
|
299
|
-
if (fileSet.has(base)) return base;
|
|
300
|
-
for (const ext of [".js", ".mjs", ".cjs", ".ts", ".tsx", ".jsx", ".vue", ".json"]) {
|
|
301
|
-
if (fileSet.has(base + ext)) return base + ext;
|
|
302
|
-
}
|
|
303
|
-
for (const ext of [".js", ".ts", ".tsx", ".jsx", ".mjs", ".vue"]) {
|
|
304
|
-
const idx = posix.join(base, "index" + ext);
|
|
305
|
-
if (fileSet.has(idx)) return idx;
|
|
306
|
-
}
|
|
307
|
-
return "unresolved";
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/** 整个仓库状态的指纹(文件清单 + 边集):state.json 的 moduleFingerprint 用它。 */
|
|
311
|
-
export function repoFingerprint(files, edges) {
|
|
312
|
-
const hash = createHash("sha256");
|
|
313
|
-
for (const f of files) hash.update(`f:${f}\n`);
|
|
314
|
-
for (const e of edges) hash.update(`e:${e.from}>${e.to}\n`);
|
|
315
|
-
return hash.digest("hex");
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/** 文件行数(骨架页「文件清单」表用)。读失败记 0,不中断。 */
|
|
319
|
-
export function countLines(repoRoot, rel) {
|
|
320
|
-
try {
|
|
321
|
-
const text = readFileSync(join(repoRoot, rel), "utf8");
|
|
322
|
-
const n = text.split("\n").length;
|
|
323
|
-
return text.endsWith("\n") ? n - 1 : n;
|
|
324
|
-
} catch {
|
|
325
|
-
return 0;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/** 读 package.json(根部),取 scripts / workspaces / exports。没有或坏 JSON 返回 null(不中断)。 */
|
|
330
|
-
export function readRootPackage(repoRoot) {
|
|
331
|
-
try {
|
|
332
|
-
const pkg = JSON.parse(readFileSync(join(repoRoot, "package.json"), "utf8"));
|
|
333
|
-
return {
|
|
334
|
-
name: typeof pkg.name === "string" ? pkg.name : null,
|
|
335
|
-
scripts: pkg.scripts && typeof pkg.scripts === "object" ? pkg.scripts : {},
|
|
336
|
-
workspaces: Array.isArray(pkg.workspaces) ? pkg.workspaces.map(String) : [],
|
|
337
|
-
entries: collectEntries(pkg),
|
|
338
|
-
};
|
|
339
|
-
} catch {
|
|
340
|
-
return null;
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
function collectEntries(pkg) {
|
|
345
|
-
const out = [];
|
|
346
|
-
if (typeof pkg.main === "string") out.push(pkg.main);
|
|
347
|
-
if (typeof pkg.bin === "string") out.push(pkg.bin);
|
|
348
|
-
if (pkg.bin && typeof pkg.bin === "object") out.push(...Object.values(pkg.bin).filter((v) => typeof v === "string"));
|
|
349
|
-
if (pkg.exports && typeof pkg.exports === "object") {
|
|
350
|
-
const walkExports = (node) => {
|
|
351
|
-
if (typeof node === "string") out.push(node);
|
|
352
|
-
else if (node && typeof node === "object") for (const v of Object.values(node)) walkExports(v);
|
|
353
|
-
};
|
|
354
|
-
for (const v of Object.values(pkg.exports)) walkExports(v);
|
|
355
|
-
}
|
|
356
|
-
return [...new Set(out.filter((v) => typeof v === "string" && !v.startsWith("#")))].map(
|
|
357
|
-
(v) => v.replace(/^\.\//, "")
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// 把 workspace glob 展开成已知目录前缀:v1 只认「目录名 + 尾部星段」的写法
|
|
362
|
-
// (pnpm/npm 工作区最常见的那一种),其余模式忽略并跳过。返回排序数组。
|
|
363
|
-
export function workspacePrefixes(workspaces) {
|
|
364
|
-
const prefixes = new Set();
|
|
365
|
-
for (const pattern of workspaces) {
|
|
366
|
-
if (typeof pattern !== "string") continue;
|
|
367
|
-
if (pattern.endsWith("/*")) prefixes.add(pattern.slice(0, -2));
|
|
368
|
-
else prefixes.add(pattern.replace(/\/+$/, ""));
|
|
369
|
-
}
|
|
370
|
-
return [...prefixes].sort();
|
|
371
|
-
}
|
|
1
|
+
// dsh-mneme/src/wiki/scan.js
|
|
2
|
+
// T0 的确定性输入面(spec §6 步 2–3):文件清单 + JS/TS import 图。
|
|
3
|
+
// 全程零模型、零网络,输出排序稳定——同一仓库状态两次扫描给出逐项相同的结构。
|
|
4
|
+
//
|
|
5
|
+
// 范围口径(决策 D9):v1 只做 JS/TS 生态(ESM 静态/动态、CJS require、Vue SFC 的
|
|
6
|
+
// <script>、package.json 的 workspaces 与 exports)。Java/Python 仓库退化为目录
|
|
7
|
+
// 启发式——这是 spec 拍板过的取舍(四语言全上等于四个生态各做 40%)。
|
|
8
|
+
//
|
|
9
|
+
// import 提取用正则不用 AST:T0 要的是「这个文件依赖谁」的图,不是逐语句语义;
|
|
10
|
+
// 正则在模板字符串/注释里偶有误报,代价是把一条边多画一次,而引入一个完整 parser
|
|
11
|
+
// 依赖(acorn 等)违背本包的依赖纪律。解析失败的单文件记 skipped 不中断(spec
|
|
12
|
+
// 降级表第三行)。
|
|
13
|
+
import { readdirSync, statSync, readFileSync } from "node:fs";
|
|
14
|
+
import { join, dirname, posix } from "node:path";
|
|
15
|
+
import { createHash } from "node:crypto";
|
|
16
|
+
|
|
17
|
+
// T0 关心的文件集:代码 + 能直接反映约定的配置/文档。清单列它们,图只由代码扩展名构成。
|
|
18
|
+
export const SCAN_EXTENSIONS = new Set([
|
|
19
|
+
".js", ".mjs", ".cjs", ".jsx", ".ts", ".tsx", ".vue",
|
|
20
|
+
".json", ".md", ".css", ".scss", ".less", ".html",
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
const CODE_EXTENSIONS = new Set([".js", ".mjs", ".cjs", ".jsx", ".ts", ".tsx", ".vue"]);
|
|
24
|
+
|
|
25
|
+
// 默认排除(spec 步 2「跳过 node_modules、构建产物、二进制」):plan.yaml 的
|
|
26
|
+
// exclude 是追加而不是替代——用户排除是表达意图,这份是卫生底线。
|
|
27
|
+
//
|
|
28
|
+
// 点开头目录整类排除(V2 实测回填,见 docs/plans/2026-11-04-chorography-m0.md):
|
|
29
|
+
// 会话产物的落盘习惯就是把东西塞进 .qoder-credits / .superpowers / .shots / .zcode /
|
|
30
|
+
// .dsh-ops / .v2c 这类点目录,而里面常常是提问原文、调试快照与第三方工具缓存——
|
|
31
|
+
// baleen 上它们占 15 个模块里的 6 个,study-desk 上占 5 个里的 3 个。逐个点名厂商等于
|
|
32
|
+
// 把一台机器的工具链钉进发布包,换个工具又漏;「点开头即非仓库内容」这条更短的规则
|
|
33
|
+
// 与三处实测的分布一致,也不随工具换代失效。
|
|
34
|
+
// 代价如实写在这里:.vitepress/theme、.docusaurus 这类确实把真源码放在点目录里的仓库
|
|
35
|
+
// 会漏掉那部分,且**拉不回来**——扫描阶段的排除先于 plan.include 白名单生效。
|
|
36
|
+
// 只按目录名判,点开头的**文件**(.eslintrc.json 之类)照旧扫:那是仓库配置,不是产物。
|
|
37
|
+
export const DEFAULT_EXCLUDE_DIRS = Object.freeze([
|
|
38
|
+
"node_modules", "dist", "build", "out", "coverage",
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
function isDefaultExcludedDir(name) {
|
|
42
|
+
return name.startsWith(".") || DEFAULT_EXCLUDE_DIRS.includes(name);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const BINARY_EXTENSIONS = new Set([
|
|
46
|
+
".png", ".jpg", ".jpeg", ".gif", ".ico", ".webp", ".avif", ".bmp", ".pdf",
|
|
47
|
+
".zip", ".gz", ".tar", ".7z", ".woff", ".woff2", ".ttf", ".otf", ".eot",
|
|
48
|
+
".mp4", ".mp3", ".webm", ".wav", ".ogg", ".mov", ".sqlite", ".db", ".wasm",
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
// 噪声锁文件:体积大、内容是依赖锁,对「模块是什么」零贡献。
|
|
52
|
+
const NOISE_FILES = new Set(["package-lock.json", "pnpm-lock.yaml", "yarn.lock"]);
|
|
53
|
+
|
|
54
|
+
// Node 内置模块清单(够用的小集合;node: 前缀之外的老写法也拦住)。
|
|
55
|
+
const NODE_BUILTINS = new Set([
|
|
56
|
+
"assert", "buffer", "child_process", "cluster", "console", "constants",
|
|
57
|
+
"crypto", "dgram", "dns", "domain", "events", "fs", "http", "http2",
|
|
58
|
+
"https", "module", "net", "os", "path", "perf_hooks", "process", "punycode",
|
|
59
|
+
"querystring", "readline", "repl", "sqlite", "stream", "string_decoder",
|
|
60
|
+
"timers", "tls", "tty", "url", "util", "v8", "vm", "worker_threads", "zlib",
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
/* ------------------------------ 文件清单 ------------------------------ */
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 列出仓库内 T0 关心的文件。返回 POSIX 风格相对路径的排序数组。
|
|
67
|
+
* exclude 走极小 gitignore 子集:# 注释、空行、尾 `/` 目录、前导 `/` 锚定、
|
|
68
|
+
* `!` 反选、`*` / `**` / `?`。(spec §4:plan.exclude 是 gitignore 语法。)
|
|
69
|
+
*/
|
|
70
|
+
export function listFiles(repoRoot, { exclude = [] } = {}) {
|
|
71
|
+
const matcher = compileExclude(exclude);
|
|
72
|
+
const files = [];
|
|
73
|
+
walk(repoRoot, "", matcher, files);
|
|
74
|
+
return files.sort();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function walk(absRoot, relDir, matcher, out) {
|
|
78
|
+
let entries;
|
|
79
|
+
try {
|
|
80
|
+
entries = readdirSync(join(absRoot, relDir), { withFileTypes: true });
|
|
81
|
+
} catch {
|
|
82
|
+
return; // 无权限/符号链接悬空:跳过整个目录,不中断
|
|
83
|
+
}
|
|
84
|
+
for (const entry of entries) {
|
|
85
|
+
const rel = relDir ? posix.join(relDir, entry.name) : entry.name;
|
|
86
|
+
if (entry.isDirectory()) {
|
|
87
|
+
if (isDefaultExcludedDir(entry.name)) continue;
|
|
88
|
+
if (matcher.excludedDir(rel)) continue;
|
|
89
|
+
walk(absRoot, rel, matcher, out);
|
|
90
|
+
} else if (entry.isFile()) {
|
|
91
|
+
const ext = posix.extname(entry.name).toLowerCase();
|
|
92
|
+
if (BINARY_EXTENSIONS.has(ext)) continue;
|
|
93
|
+
if (NOISE_FILES.has(entry.name)) continue;
|
|
94
|
+
if (!SCAN_EXTENSIONS.has(ext)) continue;
|
|
95
|
+
if (matcher.excludedFile(rel)) continue;
|
|
96
|
+
out.push(rel);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* gitignore 子集编译成谓词。规则(与真 gitignore 的差异在此声明):
|
|
103
|
+
* - 只实现 目录匹配(尾 /)、锚定(前导 /)、反选(!)、*、**、?;
|
|
104
|
+
* - 不含中段斜杠的模式按真 gitignore 语义匹配任意层级("*.md" 也命中子目录里的);
|
|
105
|
+
* - 中段 ** 跨段;不支持字符组与转义(模式原文进 receipt,用户能看见是哪条没生效)。
|
|
106
|
+
*/
|
|
107
|
+
export function compileExclude(patterns) {
|
|
108
|
+
const rules = patterns
|
|
109
|
+
.map((raw) => String(raw).trim())
|
|
110
|
+
.filter((raw) => raw !== "" && !raw.startsWith("#"))
|
|
111
|
+
.map((raw) => {
|
|
112
|
+
const negate = raw.startsWith("!");
|
|
113
|
+
const body = negate ? raw.slice(1) : raw;
|
|
114
|
+
const anchored = body.startsWith("/");
|
|
115
|
+
const dirOnly = body.endsWith("/");
|
|
116
|
+
const clean = body.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
117
|
+
const re = globToRegExp(clean, { anchored });
|
|
118
|
+
return { negate, dirOnly, re };
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
excludedDir(rel) {
|
|
123
|
+
return matchRules(rules, rel, true) === true;
|
|
124
|
+
},
|
|
125
|
+
excludedFile(rel) {
|
|
126
|
+
return matchRules(rules, rel, false) === true;
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function matchRules(rules, rel, isDir) {
|
|
132
|
+
// gitignore 语义:后一条规则赢;父目录被排除时文件也应排除(前缀目录逐级试)。
|
|
133
|
+
const segments = rel.split("/");
|
|
134
|
+
for (let i = 1; i <= segments.length; i++) {
|
|
135
|
+
const prefix = segments.slice(0, i).join("/");
|
|
136
|
+
const last = i === segments.length;
|
|
137
|
+
for (const rule of rules) {
|
|
138
|
+
if (rule.dirOnly && !isDir && last) continue;
|
|
139
|
+
if (rule.re.test(prefix)) return !rule.negate ? true : false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function globToRegExp(glob, { anchored }) {
|
|
146
|
+
// 无中段斜杠的模式(含纯目录名)匹配任意层级:^ 前置 (?:[^/]+/)*,段边界靠
|
|
147
|
+
// 尾部 (?:/.*)? 的斜杠要求与 [^/]+ 的段内限定撑住——「temp」不得命中
|
|
148
|
+
// 「mytemp/x」这类子串(^ 是这条性质的前提)。
|
|
149
|
+
const prefix = `^${!anchored && !glob.includes("/") ? "(?:[^/]+/)*" : ""}`;
|
|
150
|
+
let re = "";
|
|
151
|
+
for (let i = 0; i < glob.length; i++) {
|
|
152
|
+
const ch = glob[i];
|
|
153
|
+
if (ch === "*") {
|
|
154
|
+
if (glob[i + 1] === "*") {
|
|
155
|
+
// ** :跨段(允许吃掉 / )或尾段(等同 *)。
|
|
156
|
+
i++;
|
|
157
|
+
if (glob[i + 1] === "/") { i++; re += "(?:[^/]+/)*"; }
|
|
158
|
+
else re += ".*";
|
|
159
|
+
} else {
|
|
160
|
+
re += "[^/]*";
|
|
161
|
+
}
|
|
162
|
+
} else if (ch === "?") {
|
|
163
|
+
re += "[^/]";
|
|
164
|
+
} else {
|
|
165
|
+
re += ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return new RegExp(`${prefix}${re}(?:/.*)?$`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 正向 include 匹配(plan.include 白名单,gitignore 语法的肯定面):文件命中任意
|
|
173
|
+
* 一条模式即收入。与 compileExclude 的区别:没有反选语义,模式即意图。
|
|
174
|
+
*/
|
|
175
|
+
export function compileInclude(patterns) {
|
|
176
|
+
const rules = patterns
|
|
177
|
+
.map((raw) => String(raw).trim())
|
|
178
|
+
.filter((raw) => raw !== "" && !raw.startsWith("#"))
|
|
179
|
+
.map((raw) => {
|
|
180
|
+
const anchored = raw.startsWith("/");
|
|
181
|
+
const dirOnly = raw.endsWith("/");
|
|
182
|
+
const clean = raw.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
183
|
+
return { dirOnly, re: globToRegExp(clean, { anchored }) };
|
|
184
|
+
});
|
|
185
|
+
return {
|
|
186
|
+
matchesFile(rel) {
|
|
187
|
+
if (rules.length === 0) return true;
|
|
188
|
+
const segments = rel.split("/");
|
|
189
|
+
for (let i = 1; i <= segments.length; i++) {
|
|
190
|
+
const prefix = segments.slice(0, i).join("/");
|
|
191
|
+
for (const rule of rules) {
|
|
192
|
+
if (rule.re.test(prefix)) return true;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return false;
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* ------------------------------ import 图 ------------------------------ */
|
|
201
|
+
|
|
202
|
+
const IMPORT_PATTERNS = [
|
|
203
|
+
// import x from 'a';import type {x} from 'a'(静态带 from)
|
|
204
|
+
/\bimport\s+(?:type\s+)?[^;'"()]*?\bfrom\s*["']([^"']+)["']/g,
|
|
205
|
+
// import 'a'(副作用导入)
|
|
206
|
+
/\bimport\s*["']([^"']+)["']/g,
|
|
207
|
+
// import('a')(动态导入)
|
|
208
|
+
/\bimport\s*\(\s*["']([^"']+)["']\s*\)/g,
|
|
209
|
+
// export {x} from 'a';export * from 'a'(再导出)
|
|
210
|
+
/\bexport\s+(?:type\s+)?(?:\{[^}]*\}|\*)\s*from\s*["']([^"']+)["']/g,
|
|
211
|
+
// require('a')(CJS)
|
|
212
|
+
/\brequire\s*\(\s*["']([^"']+)["']\s*\)/g,
|
|
213
|
+
];
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* 解析文件清单里的 import 图。返回:
|
|
217
|
+
* edges [{from, to}] 内部边(to 是清单内文件的 POSIX 相对路径)
|
|
218
|
+
* external Map<pkg, Set<fromFile>>
|
|
219
|
+
* unresolved [{from, spec}] 相对/绝对路径但没解析到清单内文件
|
|
220
|
+
* skipped [{file, reason}] 读取/解析失败(不中断,spec 降级表)
|
|
221
|
+
*/
|
|
222
|
+
export function parseImportGraph(repoRoot, files) {
|
|
223
|
+
const fileSet = new Set(files);
|
|
224
|
+
const codeFiles = files.filter((f) => CODE_EXTENSIONS.has(posix.extname(f).toLowerCase()));
|
|
225
|
+
const edges = [];
|
|
226
|
+
const external = new Map();
|
|
227
|
+
const unresolved = [];
|
|
228
|
+
const skipped = [];
|
|
229
|
+
|
|
230
|
+
for (const rel of codeFiles) {
|
|
231
|
+
let text;
|
|
232
|
+
try {
|
|
233
|
+
text = readFileSync(join(repoRoot, rel), "utf8");
|
|
234
|
+
} catch (err) {
|
|
235
|
+
skipped.push({ file: rel, reason: `读取失败:${String(err?.message ?? err)}` });
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
// Vue SFC:只取 <script> 块;模板里的资源引用不是 import 图的一部分。
|
|
239
|
+
const sources = posix.extname(rel).toLowerCase() === ".vue" ? extractVueScripts(text) : [text];
|
|
240
|
+
const specs = new Set();
|
|
241
|
+
for (const src of sources) {
|
|
242
|
+
for (const re of IMPORT_PATTERNS) {
|
|
243
|
+
re.lastIndex = 0;
|
|
244
|
+
let m;
|
|
245
|
+
while ((m = re.exec(src)) !== null) specs.add(m[1]);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
for (const spec of specs) {
|
|
249
|
+
const resolved = resolveSpecifier(spec, rel, fileSet);
|
|
250
|
+
if (resolved === "external") {
|
|
251
|
+
const pkg = packageNameOf(spec);
|
|
252
|
+
if (!pkg) continue; // 空说明符等病态输入:忽略
|
|
253
|
+
if (isNodeBuiltin(pkg)) continue;
|
|
254
|
+
if (!external.has(pkg)) external.set(pkg, new Set());
|
|
255
|
+
external.get(pkg).add(rel);
|
|
256
|
+
} else if (resolved === "unresolved") {
|
|
257
|
+
unresolved.push({ from: rel, spec });
|
|
258
|
+
} else if (resolved !== null && resolved !== rel) {
|
|
259
|
+
edges.push({ from: rel, to: resolved });
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return { edges, external, unresolved, skipped };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function extractVueScripts(text) {
|
|
268
|
+
const out = [];
|
|
269
|
+
const re = /<script\b[^>]*>([\s\S]*?)<\/script>/gi;
|
|
270
|
+
let m;
|
|
271
|
+
while ((m = re.exec(text)) !== null) out.push(m[1]);
|
|
272
|
+
return out;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function packageNameOf(spec) {
|
|
276
|
+
if (spec.startsWith("@")) {
|
|
277
|
+
const parts = spec.split("/");
|
|
278
|
+
if (parts.length >= 2) return `${parts[0]}/${parts[1]}`;
|
|
279
|
+
return spec;
|
|
280
|
+
}
|
|
281
|
+
return spec.split("/")[0];
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function isNodeBuiltin(pkg) {
|
|
285
|
+
if (pkg.startsWith("node:")) return true;
|
|
286
|
+
return NODE_BUILTINS.has(pkg);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function resolveSpecifier(spec, fromFile, fileSet) {
|
|
290
|
+
let base;
|
|
291
|
+
if (spec.startsWith("./") || spec.startsWith("../")) {
|
|
292
|
+
base = posix.normalize(posix.join(posix.dirname(fromFile), spec));
|
|
293
|
+
} else if (spec.startsWith("/")) {
|
|
294
|
+
base = spec.replace(/^\/+/, ""); // 仓库根绝对引用(少数配置型写法)
|
|
295
|
+
} else {
|
|
296
|
+
return "external"; // 裸说明符:npm 包或别名;别名 v1 不解析(实现计划记录)
|
|
297
|
+
}
|
|
298
|
+
base = base.split("\\").join("/");
|
|
299
|
+
if (fileSet.has(base)) return base;
|
|
300
|
+
for (const ext of [".js", ".mjs", ".cjs", ".ts", ".tsx", ".jsx", ".vue", ".json"]) {
|
|
301
|
+
if (fileSet.has(base + ext)) return base + ext;
|
|
302
|
+
}
|
|
303
|
+
for (const ext of [".js", ".ts", ".tsx", ".jsx", ".mjs", ".vue"]) {
|
|
304
|
+
const idx = posix.join(base, "index" + ext);
|
|
305
|
+
if (fileSet.has(idx)) return idx;
|
|
306
|
+
}
|
|
307
|
+
return "unresolved";
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** 整个仓库状态的指纹(文件清单 + 边集):state.json 的 moduleFingerprint 用它。 */
|
|
311
|
+
export function repoFingerprint(files, edges) {
|
|
312
|
+
const hash = createHash("sha256");
|
|
313
|
+
for (const f of files) hash.update(`f:${f}\n`);
|
|
314
|
+
for (const e of edges) hash.update(`e:${e.from}>${e.to}\n`);
|
|
315
|
+
return hash.digest("hex");
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** 文件行数(骨架页「文件清单」表用)。读失败记 0,不中断。 */
|
|
319
|
+
export function countLines(repoRoot, rel) {
|
|
320
|
+
try {
|
|
321
|
+
const text = readFileSync(join(repoRoot, rel), "utf8");
|
|
322
|
+
const n = text.split("\n").length;
|
|
323
|
+
return text.endsWith("\n") ? n - 1 : n;
|
|
324
|
+
} catch {
|
|
325
|
+
return 0;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** 读 package.json(根部),取 scripts / workspaces / exports。没有或坏 JSON 返回 null(不中断)。 */
|
|
330
|
+
export function readRootPackage(repoRoot) {
|
|
331
|
+
try {
|
|
332
|
+
const pkg = JSON.parse(readFileSync(join(repoRoot, "package.json"), "utf8"));
|
|
333
|
+
return {
|
|
334
|
+
name: typeof pkg.name === "string" ? pkg.name : null,
|
|
335
|
+
scripts: pkg.scripts && typeof pkg.scripts === "object" ? pkg.scripts : {},
|
|
336
|
+
workspaces: Array.isArray(pkg.workspaces) ? pkg.workspaces.map(String) : [],
|
|
337
|
+
entries: collectEntries(pkg),
|
|
338
|
+
};
|
|
339
|
+
} catch {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function collectEntries(pkg) {
|
|
345
|
+
const out = [];
|
|
346
|
+
if (typeof pkg.main === "string") out.push(pkg.main);
|
|
347
|
+
if (typeof pkg.bin === "string") out.push(pkg.bin);
|
|
348
|
+
if (pkg.bin && typeof pkg.bin === "object") out.push(...Object.values(pkg.bin).filter((v) => typeof v === "string"));
|
|
349
|
+
if (pkg.exports && typeof pkg.exports === "object") {
|
|
350
|
+
const walkExports = (node) => {
|
|
351
|
+
if (typeof node === "string") out.push(node);
|
|
352
|
+
else if (node && typeof node === "object") for (const v of Object.values(node)) walkExports(v);
|
|
353
|
+
};
|
|
354
|
+
for (const v of Object.values(pkg.exports)) walkExports(v);
|
|
355
|
+
}
|
|
356
|
+
return [...new Set(out.filter((v) => typeof v === "string" && !v.startsWith("#")))].map(
|
|
357
|
+
(v) => v.replace(/^\.\//, "")
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// 把 workspace glob 展开成已知目录前缀:v1 只认「目录名 + 尾部星段」的写法
|
|
362
|
+
// (pnpm/npm 工作区最常见的那一种),其余模式忽略并跳过。返回排序数组。
|
|
363
|
+
export function workspacePrefixes(workspaces) {
|
|
364
|
+
const prefixes = new Set();
|
|
365
|
+
for (const pattern of workspaces) {
|
|
366
|
+
if (typeof pattern !== "string") continue;
|
|
367
|
+
if (pattern.endsWith("/*")) prefixes.add(pattern.slice(0, -2));
|
|
368
|
+
else prefixes.add(pattern.replace(/\/+$/, ""));
|
|
369
|
+
}
|
|
370
|
+
return [...prefixes].sort();
|
|
371
|
+
}
|