oh-my-second-brain 0.2.0 → 0.6.1
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/.claude-plugin/marketplace.json +41 -0
- package/{adapters/claude-code/.claude-plugin → .claude-plugin}/plugin.json +7 -8
- package/{adapters/codex/.codex-plugin → .codex-plugin}/plugin.json +4 -4
- package/ACKNOWLEDGMENTS.md +162 -0
- package/CHANGELOG-assets.md +23 -0
- package/CHANGELOG-cli.md +24 -0
- package/CHANGELOG-kernel.md +48 -0
- package/CHANGELOG-mcp.md +36 -0
- package/CHANGELOG-vendors.md +26 -0
- package/CHANGELOG.md +51 -7
- package/README.ko.md +2 -2
- package/README.md +16 -16
- package/{adapters/claude-code → assets/claude}/CLAUDE.md +4 -4
- package/{adapters → assets}/codex/AGENTS.md +3 -3
- package/{adapters → assets}/codex/rules/oms.md +5 -5
- package/{adapters → assets}/hermes/README.md +1 -1
- package/{adapters → assets}/hermes/SOUL.md +3 -3
- package/{adapters/hermes/manifest.json → assets/hermes-manifest.json} +3 -4
- package/assets/skills/distill/SKILL.md +20 -0
- package/assets/skills/doctor/SKILL.md +23 -0
- package/assets/skills/link/SKILL.md +24 -0
- package/assets/skills/search/SKILL.md +25 -0
- package/assets/skills/status/SKILL.md +22 -0
- package/assets/skills/write/SKILL.md +30 -0
- package/core/ontology/schemas/README.md +20 -14
- package/dist/assets/shared-skills.d.ts +4 -0
- package/dist/assets/shared-skills.js +8 -0
- package/dist/assets/shared-skills.js.map +1 -0
- package/dist/cli/args.d.ts +3 -1
- package/dist/cli/args.js +14 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/audit.js +3 -3
- package/dist/cli/audit.js.map +1 -1
- package/dist/cli/claude-install-plan.js +2 -2
- package/dist/cli/claude-install-plan.js.map +1 -1
- package/dist/cli/doctor-lint.js +8 -8
- package/dist/cli/doctor-lint.js.map +1 -1
- package/dist/cli/global-writeback.js +1 -1
- package/dist/cli/global-writeback.js.map +1 -1
- package/dist/cli/host-commands.d.ts +1 -1
- package/dist/cli/host-commands.js +24 -5
- package/dist/cli/host-commands.js.map +1 -1
- package/dist/cli/link-command.d.ts +1 -1
- package/dist/cli/link-command.js +2 -2
- package/dist/cli/link-command.js.map +1 -1
- package/dist/cli/linkify.js +16 -71
- package/dist/cli/linkify.js.map +1 -1
- package/dist/cli/oms.d.ts +1 -1
- package/dist/cli/oms.js +45 -11
- package/dist/cli/oms.js.map +1 -1
- package/dist/cli/semantic-args.d.ts +1 -1
- package/dist/cli/semantic-args.js +102 -3
- package/dist/cli/semantic-args.js.map +1 -1
- package/dist/cli/semantic-http.js +3 -3
- package/dist/cli/semantic-http.js.map +1 -1
- package/dist/cli/semantic-usage.js +1 -1
- package/dist/cli/semantic-usage.js.map +1 -1
- package/dist/cli/semantic.js +31 -12
- package/dist/cli/semantic.js.map +1 -1
- package/dist/cli/setup-command.d.ts +17 -0
- package/dist/cli/setup-command.js +89 -145
- package/dist/cli/setup-command.js.map +1 -1
- package/dist/cli/update-notice.d.ts +1 -1
- package/dist/cli/update-notice.js +2 -2
- package/dist/cli/update-notice.js.map +1 -1
- package/dist/cli/usage.d.ts +1 -1
- package/dist/cli/usage.js +2 -2
- package/dist/cli/usage.js.map +1 -1
- package/dist/{capture → kernel/capture}/safe.d.ts +11 -1
- package/dist/{capture → kernel/capture}/safe.js +3 -3
- package/dist/kernel/capture/safe.js.map +1 -0
- package/dist/kernel/contracts/index.d.ts +144 -0
- package/dist/kernel/contracts/index.js +401 -0
- package/dist/kernel/contracts/index.js.map +1 -0
- package/dist/kernel/conventions/frontmatter.js.map +1 -0
- package/dist/kernel/conventions/lint.js.map +1 -0
- package/dist/kernel/conventions/report.js.map +1 -0
- package/dist/{conventions → kernel/conventions}/validate.d.ts +1 -1
- package/dist/kernel/conventions/validate.js.map +1 -0
- package/dist/kernel/conventions/vault-walk.js.map +1 -0
- package/dist/{conventions → kernel/conventions}/write-contract.d.ts +1 -1
- package/dist/kernel/conventions/write-contract.js.map +1 -0
- package/dist/kernel/conventions/write-protocol.js.map +1 -0
- package/dist/kernel/doctor/service.d.ts +71 -0
- package/dist/kernel/doctor/service.js +70 -0
- package/dist/kernel/doctor/service.js.map +1 -0
- package/dist/{engine → kernel/engine}/assemble.d.ts +46 -1
- package/dist/kernel/engine/assemble.js +483 -0
- package/dist/kernel/engine/assemble.js.map +1 -0
- package/dist/kernel/engine/axes/store.d.ts +90 -0
- package/dist/kernel/engine/axes/store.js +400 -0
- package/dist/kernel/engine/axes/store.js.map +1 -0
- package/dist/{engine → kernel/engine}/conventions/vault-lint.d.ts +1 -1
- package/dist/{engine → kernel/engine}/conventions/vault-lint.js +1 -1
- package/dist/kernel/engine/conventions/vault-lint.js.map +1 -0
- package/dist/kernel/engine/embed/chunker.js.map +1 -0
- package/dist/kernel/engine/embed/deferred.js.map +1 -0
- package/dist/{engine → kernel/engine}/embed/hash-stub.test-helper.d.ts +1 -1
- package/dist/{engine → kernel/engine}/embed/hash-stub.test-helper.js +1 -1
- package/dist/kernel/engine/embed/hash-stub.test-helper.js.map +1 -0
- package/dist/kernel/engine/embed/identity.d.ts +21 -0
- package/dist/kernel/engine/embed/identity.js +47 -0
- package/dist/kernel/engine/embed/identity.js.map +1 -0
- package/dist/kernel/engine/embed/model.d.ts +134 -0
- package/dist/kernel/engine/embed/model.js +302 -0
- package/dist/kernel/engine/embed/model.js.map +1 -0
- package/dist/{engine → kernel/engine}/embed/provider.d.ts +36 -5
- package/dist/kernel/engine/embed/provider.js +403 -0
- package/dist/kernel/engine/embed/provider.js.map +1 -0
- package/dist/{engine → kernel/engine}/embed/store.d.ts +13 -1
- package/dist/kernel/engine/embed/store.js +730 -0
- package/dist/kernel/engine/embed/store.js.map +1 -0
- package/dist/kernel/engine/embed/sync.d.ts +100 -0
- package/dist/kernel/engine/embed/sync.js +753 -0
- package/dist/kernel/engine/embed/sync.js.map +1 -0
- package/dist/{engine → kernel/engine}/graph/builder.d.ts +9 -5
- package/dist/{engine → kernel/engine}/graph/builder.js +213 -79
- package/dist/kernel/engine/graph/builder.js.map +1 -0
- package/dist/kernel/engine/graph/explore.js.map +1 -0
- package/dist/{engine → kernel/engine}/graph/node.d.ts +49 -1
- package/dist/kernel/engine/graph/node.js +315 -0
- package/dist/kernel/engine/graph/node.js.map +1 -0
- package/dist/kernel/engine/graph/resolver.js.map +1 -0
- package/dist/kernel/engine/graph/traverse.js.map +1 -0
- package/dist/kernel/engine/linkify/apply.js.map +1 -0
- package/dist/kernel/engine/linkify/josa.js.map +1 -0
- package/dist/kernel/engine/linkify/mask.js.map +1 -0
- package/dist/kernel/engine/linkify/suggest.js.map +1 -0
- package/dist/kernel/engine/linkify/types.js.map +1 -0
- package/dist/{engine → kernel/engine}/mcp/facade.d.ts +27 -1
- package/dist/kernel/engine/mcp/facade.js +1144 -0
- package/dist/kernel/engine/mcp/facade.js.map +1 -0
- package/dist/kernel/engine/mcp/op-mappers.js.map +1 -0
- package/dist/{engine → kernel/engine}/mcp/query-mapper.d.ts +27 -2
- package/dist/kernel/engine/mcp/query-mapper.js +247 -0
- package/dist/kernel/engine/mcp/query-mapper.js.map +1 -0
- package/dist/{engine → kernel/engine}/mcp/types.d.ts +69 -2
- package/dist/kernel/engine/mcp/types.js.map +1 -0
- package/dist/{engine → kernel/engine}/retrieval/dispatcher.d.ts +23 -1
- package/dist/{engine → kernel/engine}/retrieval/dispatcher.js +92 -14
- package/dist/kernel/engine/retrieval/dispatcher.js.map +1 -0
- package/dist/{engine → kernel/engine}/retrieval/index.d.ts +4 -1
- package/dist/{engine → kernel/engine}/retrieval/index.js +6 -3
- package/dist/kernel/engine/retrieval/index.js.map +1 -0
- package/dist/kernel/engine/retrieval/reranker.d.ts +124 -0
- package/dist/kernel/engine/retrieval/reranker.js +204 -0
- package/dist/kernel/engine/retrieval/reranker.js.map +1 -0
- package/dist/{engine → kernel/engine}/retrieval/rrf.d.ts +10 -2
- package/dist/kernel/engine/retrieval/rrf.js +77 -0
- package/dist/kernel/engine/retrieval/rrf.js.map +1 -0
- package/dist/{engine → kernel/engine}/tracer.d.ts +13 -0
- package/dist/{engine → kernel/engine}/tracer.js +63 -6
- package/dist/kernel/engine/tracer.js.map +1 -0
- package/dist/{engine → kernel/engine}/types.d.ts +4 -4
- package/dist/kernel/engine/types.js.map +1 -0
- package/dist/{graph → kernel/graph}/cache.d.ts +1 -1
- package/dist/{graph → kernel/graph}/cache.js +165 -21
- package/dist/kernel/graph/cache.js.map +1 -0
- package/dist/kernel/graph/explore.js.map +1 -0
- package/dist/{harness → kernel/harness}/surface-registry.d.ts +0 -1
- package/dist/kernel/harness/surface-registry.js +170 -0
- package/dist/kernel/harness/surface-registry.js.map +1 -0
- package/dist/{harness → kernel/harness}/validation.js +67 -4
- package/dist/kernel/harness/validation.js.map +1 -0
- package/dist/kernel/index.d.ts +13 -0
- package/dist/kernel/index.js +14 -0
- package/dist/kernel/index.js.map +1 -0
- package/dist/{install → kernel/install}/adapter-source.d.ts +10 -1
- package/dist/kernel/install/adapter-source.js +76 -0
- package/dist/kernel/install/adapter-source.js.map +1 -0
- package/dist/kernel/install/common.js.map +1 -0
- package/dist/{install → kernel/install}/hosts.d.ts +3 -3
- package/dist/{install → kernel/install}/hosts.js +3 -22
- package/dist/kernel/install/hosts.js.map +1 -0
- package/dist/{core/ontology → kernel/install}/types.js.map +1 -1
- package/dist/{link → kernel/link}/convention-note.js +2 -2
- package/dist/kernel/link/convention-note.js.map +1 -0
- package/dist/kernel/link/global-config.js.map +1 -0
- package/dist/kernel/link/link.js.map +1 -0
- package/dist/kernel/link/workflow.d.ts +55 -0
- package/dist/kernel/link/workflow.js +107 -0
- package/dist/kernel/link/workflow.js.map +1 -0
- package/dist/kernel/measurement/no-default-contract.d.ts +17 -0
- package/dist/kernel/measurement/no-default-contract.js +84 -0
- package/dist/kernel/measurement/no-default-contract.js.map +1 -0
- package/dist/{ontology → kernel/ontology}/active.d.ts +1 -1
- package/dist/{ontology → kernel/ontology}/active.js +2 -2
- package/dist/kernel/ontology/active.js.map +1 -0
- package/dist/kernel/ontology/loader.js.map +1 -0
- package/dist/kernel/ontology/resolver.js.map +1 -0
- package/dist/{install → kernel/ontology}/types.js.map +1 -1
- package/dist/kernel/plugin/index.d.ts +1 -0
- package/dist/kernel/plugin/index.js +2 -0
- package/dist/kernel/plugin/index.js.map +1 -0
- package/dist/{core → kernel}/runtime/assets.d.ts +2 -4
- package/dist/{core → kernel}/runtime/assets.js +1 -3
- package/dist/kernel/runtime/assets.js.map +1 -0
- package/dist/kernel/search/morning-test-fixtures.js.map +1 -0
- package/dist/{retrieve → kernel/search}/morning.d.ts +0 -1
- package/dist/kernel/search/morning.js.map +1 -0
- package/dist/kernel/search/semantic-contract.js.map +1 -0
- package/dist/kernel/searchbackend/engine-search-backend.d.ts +28 -0
- package/dist/kernel/searchbackend/engine-search-backend.js +153 -0
- package/dist/kernel/searchbackend/engine-search-backend.js.map +1 -0
- package/dist/kernel/searchbackend/index.d.ts +2 -0
- package/dist/kernel/searchbackend/index.js +2 -0
- package/dist/kernel/searchbackend/index.js.map +1 -0
- package/dist/kernel/searchbackend/search-backend.d.ts +57 -0
- package/dist/kernel/searchbackend/search-backend.js +110 -0
- package/dist/kernel/searchbackend/search-backend.js.map +1 -0
- package/dist/{mcp → kernel/semantic}/semantic-engine.d.ts +3 -2
- package/dist/kernel/semantic/semantic-engine.js +78 -0
- package/dist/kernel/semantic/semantic-engine.js.map +1 -0
- package/dist/{mcp → kernel/semantic}/semantic-retrieve-args.d.ts +20 -3
- package/dist/{mcp → kernel/semantic}/semantic-retrieve-args.js +81 -15
- package/dist/kernel/semantic/semantic-retrieve-args.js.map +1 -0
- package/dist/{mcp → kernel/semantic}/semantic-retrieve-schema.d.ts +0 -24
- package/dist/{mcp → kernel/semantic}/semantic-retrieve-schema.js +0 -8
- package/dist/kernel/semantic/semantic-retrieve-schema.js.map +1 -0
- package/dist/{mcp → kernel/semantic}/semantic-retrieve.js +5 -9
- package/dist/kernel/semantic/semantic-retrieve.js.map +1 -0
- package/dist/kernel/semantic/semantic-schemas.js.map +1 -0
- package/dist/{mcp → kernel/semantic}/semantic-tool-schemas.js +0 -28
- package/dist/kernel/semantic/semantic-tool-schemas.js.map +1 -0
- package/dist/{setup → kernel/setup}/axis.d.ts +1 -1
- package/dist/{setup → kernel/setup}/axis.js +1 -1
- package/dist/kernel/setup/axis.js.map +1 -0
- package/dist/{cli/setup-documents.d.ts → kernel/setup/documents.d.ts} +1 -1
- package/dist/{cli/setup-documents.js → kernel/setup/documents.js} +15 -49
- package/dist/kernel/setup/documents.js.map +1 -0
- package/dist/kernel/setup/service.d.ts +44 -0
- package/dist/kernel/setup/service.js +97 -0
- package/dist/kernel/setup/service.js.map +1 -0
- package/dist/{update → kernel/update}/update.d.ts +7 -0
- package/dist/{update → kernel/update}/update.js +106 -8
- package/dist/kernel/update/update.js.map +1 -0
- package/dist/mcp/engine-morning-backend.d.ts +4 -4
- package/dist/mcp/engine-morning-backend.js +4 -18
- package/dist/mcp/engine-morning-backend.js.map +1 -1
- package/dist/mcp/link-tools.d.ts +9 -56
- package/dist/mcp/link-tools.js +5 -129
- package/dist/mcp/link-tools.js.map +1 -1
- package/dist/mcp/server.d.ts +9 -2
- package/dist/mcp/server.js +373 -324
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/update-notice.d.ts +1 -1
- package/dist/mcp/update-notice.js +1 -1
- package/dist/mcp/update-notice.js.map +1 -1
- package/dist/{install → vendors/claude}/claude-hooks.d.ts +1 -1
- package/dist/{install → vendors/claude}/claude-hooks.js +1 -1
- package/dist/vendors/claude/claude-hooks.js.map +1 -0
- package/dist/{install → vendors/claude}/claude-marketplace.d.ts +3 -3
- package/dist/{install → vendors/claude}/claude-marketplace.js +6 -9
- package/dist/vendors/claude/claude-marketplace.js.map +1 -0
- package/dist/{install → vendors/claude}/claude.d.ts +2 -2
- package/dist/{install → vendors/claude}/claude.js +2 -2
- package/dist/vendors/claude/claude.js.map +1 -0
- package/dist/{hook → vendors/claude/hook}/post-tool-use.js +5 -5
- package/dist/vendors/claude/hook/post-tool-use.js.map +1 -0
- package/dist/vendors/claude/hook/pre-tool-use.js.map +1 -0
- package/dist/vendors/claude/hook/stdin.js.map +1 -0
- package/dist/vendors/codex/codex.d.ts +10 -0
- package/dist/{install → vendors/codex}/codex.js +19 -37
- package/dist/vendors/codex/codex.js.map +1 -0
- package/dist/{install → vendors/hermes}/hermes.d.ts +2 -2
- package/dist/{install → vendors/hermes}/hermes.js +15 -7
- package/dist/vendors/hermes/hermes.js.map +1 -0
- package/docs/adapters.md +15 -0
- package/docs/install.md +10 -11
- package/package.json +23 -10
- package/adapters/README.md +0 -99
- package/adapters/claude-code/skills/compile/SKILL.md +0 -27
- package/adapters/claude-code/skills/define/SKILL.md +0 -51
- package/adapters/claude-code/skills/distill/SKILL.md +0 -28
- package/adapters/claude-code/skills/doctor/SKILL.md +0 -58
- package/adapters/claude-code/skills/link/SKILL.md +0 -87
- package/adapters/claude-code/skills/retrieve/SKILL.md +0 -99
- package/adapters/claude-code/skills/setup/SKILL.md +0 -78
- package/adapters/claude-code/skills/uninstall/SKILL.md +0 -34
- package/adapters/claude-code/skills/update/SKILL.md +0 -45
- package/adapters/claude-code/skills/vault-decision-record/SKILL.md +0 -52
- package/adapters/claude-code/skills/vault-lint/SKILL.md +0 -47
- package/adapters/claude-code/skills/vault-scaffold/SKILL.md +0 -47
- package/adapters/claude-code/skills/wiki/SKILL.md +0 -82
- package/adapters/claude-code/skills/write/SKILL.md +0 -16
- package/adapters/codex/skills/oms-compile/SKILL.md +0 -16
- package/adapters/codex/skills/oms-distill/SKILL.md +0 -14
- package/adapters/codex/skills/oms-doctor/SKILL.md +0 -14
- package/adapters/codex/skills/oms-install/SKILL.md +0 -19
- package/adapters/codex/skills/oms-link/SKILL.md +0 -35
- package/adapters/codex/skills/oms-retrieve/SKILL.md +0 -32
- package/adapters/codex/skills/oms-setup/SKILL.md +0 -34
- package/adapters/codex/skills/oms-uninstall/SKILL.md +0 -20
- package/adapters/codex/skills/oms-update/SKILL.md +0 -22
- package/adapters/codex/skills/oms-vault-lint/SKILL.md +0 -19
- package/adapters/codex/skills/oms-wiki/SKILL.md +0 -39
- package/adapters/codex/skills/oms-write/SKILL.md +0 -10
- package/adapters/hermes/skills/compile/SKILL.md +0 -13
- package/adapters/hermes/skills/distill/SKILL.md +0 -10
- package/adapters/hermes/skills/doctor/SKILL.md +0 -14
- package/adapters/hermes/skills/install/SKILL.md +0 -19
- package/adapters/hermes/skills/link/SKILL.md +0 -18
- package/adapters/hermes/skills/retrieve/SKILL.md +0 -32
- package/adapters/hermes/skills/setup/SKILL.md +0 -37
- package/adapters/hermes/skills/uninstall/SKILL.md +0 -20
- package/adapters/hermes/skills/update/SKILL.md +0 -22
- package/adapters/hermes/skills/wiki/SKILL.md +0 -14
- package/adapters/hermes/skills/write/SKILL.md +0 -10
- package/core/agents/librarian.md +0 -44
- package/core/agents/retriever.md +0 -62
- package/core/skills/compile/CHANGELOG.md +0 -24
- package/core/skills/compile/SKILL.md +0 -57
- package/core/skills/define/SKILL.md +0 -59
- package/core/skills/distill/CHANGELOG.md +0 -39
- package/core/skills/distill/SKILL.md +0 -62
- package/core/skills/doctor/SKILL.md +0 -49
- package/core/skills/link/SKILL.md +0 -130
- package/core/skills/retrieve/SKILL.md +0 -78
- package/core/skills/setup/CHANGELOG.md +0 -49
- package/core/skills/setup/SKILL.md +0 -246
- package/core/skills/uninstall/SKILL.md +0 -34
- package/core/skills/vault-decision-record/CHANGELOG.md +0 -24
- package/core/skills/vault-decision-record/SKILL.md +0 -93
- package/core/skills/vault-lint/CHANGELOG.md +0 -35
- package/core/skills/vault-lint/SKILL.md +0 -58
- package/core/skills/vault-scaffold/CHANGELOG.md +0 -21
- package/core/skills/vault-scaffold/SKILL.md +0 -72
- package/core/skills/wiki/CHANGELOG.md +0 -23
- package/core/skills/wiki/SKILL.md +0 -159
- package/core/skills/write/SKILL.md +0 -15
- package/dist/capture/safe.js.map +0 -1
- package/dist/cli/setup-documents.js.map +0 -1
- package/dist/conventions/frontmatter.js.map +0 -1
- package/dist/conventions/lint.js.map +0 -1
- package/dist/conventions/report.js.map +0 -1
- package/dist/conventions/validate.js.map +0 -1
- package/dist/conventions/vault-walk.js.map +0 -1
- package/dist/conventions/write-contract.js.map +0 -1
- package/dist/conventions/write-protocol.js.map +0 -1
- package/dist/core/ontology/loader.js.map +0 -1
- package/dist/core/ontology/resolver.js.map +0 -1
- package/dist/core/runtime/assets.js.map +0 -1
- package/dist/engine/assemble.js +0 -162
- package/dist/engine/assemble.js.map +0 -1
- package/dist/engine/conventions/vault-lint.js.map +0 -1
- package/dist/engine/embed/chunker.js.map +0 -1
- package/dist/engine/embed/deferred.js.map +0 -1
- package/dist/engine/embed/hash-stub.test-helper.js.map +0 -1
- package/dist/engine/embed/identity.d.ts +0 -11
- package/dist/engine/embed/identity.js +0 -15
- package/dist/engine/embed/identity.js.map +0 -1
- package/dist/engine/embed/provider.js +0 -266
- package/dist/engine/embed/provider.js.map +0 -1
- package/dist/engine/embed/store.js +0 -416
- package/dist/engine/embed/store.js.map +0 -1
- package/dist/engine/embed/sync.d.ts +0 -45
- package/dist/engine/embed/sync.js +0 -321
- package/dist/engine/embed/sync.js.map +0 -1
- package/dist/engine/graph/builder.js.map +0 -1
- package/dist/engine/graph/explore.js.map +0 -1
- package/dist/engine/graph/node.js +0 -110
- package/dist/engine/graph/node.js.map +0 -1
- package/dist/engine/graph/resolver.js.map +0 -1
- package/dist/engine/graph/traverse.js.map +0 -1
- package/dist/engine/linkify/apply.js.map +0 -1
- package/dist/engine/linkify/josa.js.map +0 -1
- package/dist/engine/linkify/mask.js.map +0 -1
- package/dist/engine/linkify/suggest.js.map +0 -1
- package/dist/engine/linkify/types.js.map +0 -1
- package/dist/engine/mcp/facade.js +0 -628
- package/dist/engine/mcp/facade.js.map +0 -1
- package/dist/engine/mcp/op-mappers.js.map +0 -1
- package/dist/engine/mcp/query-mapper.js +0 -102
- package/dist/engine/mcp/query-mapper.js.map +0 -1
- package/dist/engine/mcp/types.js.map +0 -1
- package/dist/engine/retrieval/dispatcher.js.map +0 -1
- package/dist/engine/retrieval/index.js.map +0 -1
- package/dist/engine/retrieval/reranker.d.ts +0 -46
- package/dist/engine/retrieval/reranker.js +0 -29
- package/dist/engine/retrieval/reranker.js.map +0 -1
- package/dist/engine/retrieval/rrf.js +0 -54
- package/dist/engine/retrieval/rrf.js.map +0 -1
- package/dist/engine/tracer.js.map +0 -1
- package/dist/engine/types.js.map +0 -1
- package/dist/graph/cache.js.map +0 -1
- package/dist/graph/explore.js.map +0 -1
- package/dist/harness/surface-registry.js +0 -397
- package/dist/harness/surface-registry.js.map +0 -1
- package/dist/harness/validation.js.map +0 -1
- package/dist/hook/post-tool-use.js.map +0 -1
- package/dist/hook/pre-tool-use.js.map +0 -1
- package/dist/hook/stdin.js.map +0 -1
- package/dist/index.d.ts +0 -12
- package/dist/index.js +0 -13
- package/dist/index.js.map +0 -1
- package/dist/install/adapter-source.js +0 -45
- package/dist/install/adapter-source.js.map +0 -1
- package/dist/install/claude-hooks.js.map +0 -1
- package/dist/install/claude-marketplace.js.map +0 -1
- package/dist/install/claude.js.map +0 -1
- package/dist/install/codex.d.ts +0 -24
- package/dist/install/codex.js.map +0 -1
- package/dist/install/common.js.map +0 -1
- package/dist/install/hermes.js.map +0 -1
- package/dist/install/hosts.js.map +0 -1
- package/dist/link/convention-note.js.map +0 -1
- package/dist/link/global-config.js.map +0 -1
- package/dist/link/link.js.map +0 -1
- package/dist/mcp/semantic-engine.js +0 -52
- package/dist/mcp/semantic-engine.js.map +0 -1
- package/dist/mcp/semantic-retrieve-args.js.map +0 -1
- package/dist/mcp/semantic-retrieve-schema.js.map +0 -1
- package/dist/mcp/semantic-retrieve.js.map +0 -1
- package/dist/mcp/semantic-schemas.js.map +0 -1
- package/dist/mcp/semantic-tool-schemas.js.map +0 -1
- package/dist/ontology/active.js.map +0 -1
- package/dist/retrieve/morning-test-fixtures.js.map +0 -1
- package/dist/retrieve/morning.js.map +0 -1
- package/dist/retrieve/semantic-contract.js.map +0 -1
- package/dist/setup/axis.js.map +0 -1
- package/dist/update/update.js.map +0 -1
- package/docs/release.md +0 -188
- /package/{adapters/codex/.mcp.json → .mcp.codex.json} +0 -0
- /package/{adapters/claude-code/.mcp.json → .mcp.json} +0 -0
- /package/{adapters/claude-code → assets/claude}/hooks/oms-guard.mjs +0 -0
- /package/{adapters/claude-code → assets/claude}/hooks/oms-post-guard.mjs +0 -0
- /package/dist/{conventions → kernel/conventions}/frontmatter.d.ts +0 -0
- /package/dist/{conventions → kernel/conventions}/frontmatter.js +0 -0
- /package/dist/{conventions → kernel/conventions}/lint.d.ts +0 -0
- /package/dist/{conventions → kernel/conventions}/lint.js +0 -0
- /package/dist/{conventions → kernel/conventions}/report.d.ts +0 -0
- /package/dist/{conventions → kernel/conventions}/report.js +0 -0
- /package/dist/{conventions → kernel/conventions}/validate.js +0 -0
- /package/dist/{conventions → kernel/conventions}/vault-walk.d.ts +0 -0
- /package/dist/{conventions → kernel/conventions}/vault-walk.js +0 -0
- /package/dist/{conventions → kernel/conventions}/write-contract.js +0 -0
- /package/dist/{conventions → kernel/conventions}/write-protocol.d.ts +0 -0
- /package/dist/{conventions → kernel/conventions}/write-protocol.js +0 -0
- /package/dist/{engine → kernel/engine}/embed/chunker.d.ts +0 -0
- /package/dist/{engine → kernel/engine}/embed/chunker.js +0 -0
- /package/dist/{engine → kernel/engine}/embed/deferred.d.ts +0 -0
- /package/dist/{engine → kernel/engine}/embed/deferred.js +0 -0
- /package/dist/{engine → kernel/engine}/graph/explore.d.ts +0 -0
- /package/dist/{engine → kernel/engine}/graph/explore.js +0 -0
- /package/dist/{engine → kernel/engine}/graph/resolver.d.ts +0 -0
- /package/dist/{engine → kernel/engine}/graph/resolver.js +0 -0
- /package/dist/{engine → kernel/engine}/graph/traverse.d.ts +0 -0
- /package/dist/{engine → kernel/engine}/graph/traverse.js +0 -0
- /package/dist/{engine → kernel/engine}/linkify/apply.d.ts +0 -0
- /package/dist/{engine → kernel/engine}/linkify/apply.js +0 -0
- /package/dist/{engine → kernel/engine}/linkify/josa.d.ts +0 -0
- /package/dist/{engine → kernel/engine}/linkify/josa.js +0 -0
- /package/dist/{engine → kernel/engine}/linkify/mask.d.ts +0 -0
- /package/dist/{engine → kernel/engine}/linkify/mask.js +0 -0
- /package/dist/{engine → kernel/engine}/linkify/suggest.d.ts +0 -0
- /package/dist/{engine → kernel/engine}/linkify/suggest.js +0 -0
- /package/dist/{engine → kernel/engine}/linkify/types.d.ts +0 -0
- /package/dist/{engine → kernel/engine}/linkify/types.js +0 -0
- /package/dist/{engine → kernel/engine}/mcp/op-mappers.d.ts +0 -0
- /package/dist/{engine → kernel/engine}/mcp/op-mappers.js +0 -0
- /package/dist/{engine → kernel/engine}/mcp/types.js +0 -0
- /package/dist/{engine → kernel/engine}/types.js +0 -0
- /package/dist/{graph → kernel/graph}/explore.d.ts +0 -0
- /package/dist/{graph → kernel/graph}/explore.js +0 -0
- /package/dist/{harness → kernel/harness}/validation.d.ts +0 -0
- /package/dist/{install → kernel/install}/common.d.ts +0 -0
- /package/dist/{install → kernel/install}/common.js +0 -0
- /package/dist/{install → kernel/install}/types.d.ts +0 -0
- /package/dist/{core/ontology → kernel/install}/types.js +0 -0
- /package/dist/{link → kernel/link}/convention-note.d.ts +0 -0
- /package/dist/{link → kernel/link}/global-config.d.ts +0 -0
- /package/dist/{link → kernel/link}/global-config.js +0 -0
- /package/dist/{link → kernel/link}/link.d.ts +0 -0
- /package/dist/{link → kernel/link}/link.js +0 -0
- /package/dist/{core → kernel}/ontology/loader.d.ts +0 -0
- /package/dist/{core → kernel}/ontology/loader.js +0 -0
- /package/dist/{core → kernel}/ontology/resolver.d.ts +0 -0
- /package/dist/{core → kernel}/ontology/resolver.js +0 -0
- /package/dist/{core → kernel}/ontology/types.d.ts +0 -0
- /package/dist/{install → kernel/ontology}/types.js +0 -0
- /package/dist/{retrieve → kernel/search}/morning-test-fixtures.d.ts +0 -0
- /package/dist/{retrieve → kernel/search}/morning-test-fixtures.js +0 -0
- /package/dist/{retrieve → kernel/search}/morning.js +0 -0
- /package/dist/{retrieve → kernel/search}/semantic-contract.d.ts +0 -0
- /package/dist/{retrieve → kernel/search}/semantic-contract.js +0 -0
- /package/dist/{mcp → kernel/semantic}/semantic-retrieve.d.ts +0 -0
- /package/dist/{mcp → kernel/semantic}/semantic-schemas.d.ts +0 -0
- /package/dist/{mcp → kernel/semantic}/semantic-schemas.js +0 -0
- /package/dist/{mcp → kernel/semantic}/semantic-tool-schemas.d.ts +0 -0
- /package/dist/{hook → vendors/claude/hook}/post-tool-use.d.ts +0 -0
- /package/dist/{hook → vendors/claude/hook}/pre-tool-use.d.ts +0 -0
- /package/dist/{hook → vendors/claude/hook}/pre-tool-use.js +0 -0
- /package/dist/{hook → vendors/claude/hook}/stdin.d.ts +0 -0
- /package/dist/{hook → vendors/claude/hook}/stdin.js +0 -0
|
@@ -0,0 +1,1144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* McpEngineAdapter — engine-side adapter facade for live MCP semantic ops.
|
|
3
|
+
*
|
|
4
|
+
* Receives DispatcherDeps + the vault root as INJECTED dependencies; never
|
|
5
|
+
* instantiates VectorStore, EmbeddingProvider, or any other backend. Backend
|
|
6
|
+
* construction is the assemble step (assemble.ts), which passes the real
|
|
7
|
+
* EngineStore + EmbeddingProvider via DispatcherDeps and the vault path here.
|
|
8
|
+
*
|
|
9
|
+
* This class owns the translation between MCP op inputs/outputs and the
|
|
10
|
+
* engine's retrieval / sync / graph contracts:
|
|
11
|
+
* - semantic_query → dispatch() over the RRF pipeline
|
|
12
|
+
* - sync_embeddings → syncEngineStore() (vault scan → chunk → embed → upsert)
|
|
13
|
+
* - semantic_status / collections / contexts → caps probe of DispatcherDeps
|
|
14
|
+
* - semantic_cleanup → orphan diff (store paths − live vault paths)
|
|
15
|
+
* - graph_build / status → builder.ts edge graph + node index, cached on disk
|
|
16
|
+
* - retrieve_by_axis → node-index axis filter + lexical score
|
|
17
|
+
*
|
|
18
|
+
* Live oms_retrieve_context is handled by retrieveMorningContext(), not this class.
|
|
19
|
+
*
|
|
20
|
+
* R18: NO import from src/search.
|
|
21
|
+
*/
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
24
|
+
import { parse as parseYaml } from "yaml";
|
|
25
|
+
import { resolveBundledAssetPaths } from "../../runtime/assets.js";
|
|
26
|
+
import { loadContract } from "../../contracts/index.js";
|
|
27
|
+
import { retrieve } from "../retrieval/index.js";
|
|
28
|
+
import { acquireEngineStoreWriterLock, syncEngineStore, walkMarkdown, } from "../embed/sync.js";
|
|
29
|
+
import { openEngineStore } from "../embed/store.js";
|
|
30
|
+
import { buildGraph, saveCachedGraph, loadCachedGraphMeta, buildNodeIndex, saveNodeIndex, loadNodeIndex, nodeSourceSignature, } from "../graph/builder.js";
|
|
31
|
+
import { filterNodesByAxis, filterNodesByQueryAxes, queryFacets, searchScore, } from "../graph/node.js";
|
|
32
|
+
import { axisStorePath, collectVaultAxisObservations, openAxisStore, } from "../axes/store.js";
|
|
33
|
+
import { normalizeQueryOptions, retrievalResultsToQueryResult, queryResultUnavailable, } from "./query-mapper.js";
|
|
34
|
+
import { engineSyncResultToMcp, syncResultUnavailable, capsToEngineStatusResult, engineStatusResultToMcp, statusResultUnavailable, engineStatusToCollectionResult, engineStatusToContextResult, cleanupResultUnavailable, graphBuildOptionsToEngineArgs, engineGraphBuildResultToMcp, engineGraphBuildToStatusResult, } from "./op-mappers.js";
|
|
35
|
+
function positiveInt(value) {
|
|
36
|
+
if (!value || !/^\d+$/u.test(value))
|
|
37
|
+
return undefined;
|
|
38
|
+
const parsed = Number(value);
|
|
39
|
+
return parsed > 0 ? parsed : undefined;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Strip a resource scheme to a vault-relative path.
|
|
43
|
+
*
|
|
44
|
+
* `oms://<collection>/<path>` is URL-decoded; plain targets get backslashes and
|
|
45
|
+
* a leading "./" normalized away. The `qmd://` branch was removed with ADR-009:
|
|
46
|
+
* qmd compatibility is no longer a product contract, and keeping an input
|
|
47
|
+
* tolerance for a retired scheme is a compatibility layer with no caller.
|
|
48
|
+
*/
|
|
49
|
+
function normalizeDocScheme(target) {
|
|
50
|
+
if (target.startsWith("oms://")) {
|
|
51
|
+
const rest = target.slice("oms://".length);
|
|
52
|
+
const slash = rest.indexOf("/");
|
|
53
|
+
return slash >= 0 ? decodeURIComponent(rest.slice(slash + 1)) : "";
|
|
54
|
+
}
|
|
55
|
+
return target.replace(/\\/g, "/").replace(/^\.?\//u, "");
|
|
56
|
+
}
|
|
57
|
+
/** Split a trailing line range off a path, supporting both colon forms. */
|
|
58
|
+
function splitDocRange(value) {
|
|
59
|
+
const parts = value.split(":");
|
|
60
|
+
// src/search colon form: "file:FROM:COUNT" (>= 3 parts, last two positive ints).
|
|
61
|
+
if (parts.length >= 3) {
|
|
62
|
+
const count = positiveInt(parts.at(-1));
|
|
63
|
+
const from = positiveInt(parts.at(-2));
|
|
64
|
+
if (from !== undefined && count !== undefined) {
|
|
65
|
+
return { filePath: parts.slice(0, -2).join(":"), fromLine: from, lineCount: count };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const colonIdx = value.lastIndexOf(":");
|
|
69
|
+
if (colonIdx > 0) {
|
|
70
|
+
const rangePart = value.slice(colonIdx + 1);
|
|
71
|
+
const filePart = value.slice(0, colonIdx);
|
|
72
|
+
const dash = /^(\d+)-(\d+)$/u.exec(rangePart);
|
|
73
|
+
if (dash) {
|
|
74
|
+
const from = parseInt(dash[1], 10);
|
|
75
|
+
const to = parseInt(dash[2], 10);
|
|
76
|
+
return { filePath: filePart, fromLine: from, lineCount: Math.max(0, to - from + 1) };
|
|
77
|
+
}
|
|
78
|
+
if (/^\d+$/u.test(rangePart)) {
|
|
79
|
+
return { filePath: filePart, fromLine: parseInt(rangePart, 10), lineCount: 1 };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return { filePath: value };
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Parse target forms: "#docid", "file.md", "file.md:N", "file.md:N-M",
|
|
86
|
+
* "file.md:FROM:COUNT", "dir/*.md" (glob), and oms:// resource URIs.
|
|
87
|
+
*/
|
|
88
|
+
function parseDocTarget(target) {
|
|
89
|
+
if (target.startsWith("#")) {
|
|
90
|
+
return { filePath: target.slice(1), isDocid: true, isGlob: false };
|
|
91
|
+
}
|
|
92
|
+
const scheme = normalizeDocScheme(target);
|
|
93
|
+
const { filePath, fromLine, lineCount } = splitDocRange(scheme);
|
|
94
|
+
return { filePath, fromLine, lineCount, isDocid: false, isGlob: filePath.includes("*") };
|
|
95
|
+
}
|
|
96
|
+
/** Mirror src/search globRegex: "*" → one path segment, "**" → any depth. */
|
|
97
|
+
function docGlobToRegex(pattern) {
|
|
98
|
+
let source = "";
|
|
99
|
+
for (let index = 0; index < pattern.length; index++) {
|
|
100
|
+
const char = pattern[index] ?? "";
|
|
101
|
+
const next = pattern[index + 1] ?? "";
|
|
102
|
+
if (char === "*" && next === "*") {
|
|
103
|
+
source += ".*";
|
|
104
|
+
index++;
|
|
105
|
+
}
|
|
106
|
+
else if (char === "*") {
|
|
107
|
+
source += "[^/]*";
|
|
108
|
+
}
|
|
109
|
+
else if ("|\\{}()[]^$+?.".includes(char)) {
|
|
110
|
+
source += `\\${char}`;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
source += char;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return new RegExp(`^${source}$`, "u");
|
|
117
|
+
}
|
|
118
|
+
/** Filesystem glob over the vault's markdown files; sorted for determinism. */
|
|
119
|
+
async function globVaultDocs(vault, pattern) {
|
|
120
|
+
const regex = docGlobToRegex(pattern);
|
|
121
|
+
const matches = [];
|
|
122
|
+
for await (const rel of walkMarkdown(vault, vault)) {
|
|
123
|
+
if (regex.test(rel))
|
|
124
|
+
matches.push(rel);
|
|
125
|
+
}
|
|
126
|
+
return matches.sort((a, b) => a.localeCompare(b));
|
|
127
|
+
}
|
|
128
|
+
/** Return true if resolving filePath inside vaultRoot would escape the vault. */
|
|
129
|
+
function isUnsafeVaultPath(filePath, vaultRoot) {
|
|
130
|
+
if (path.isAbsolute(filePath))
|
|
131
|
+
return true;
|
|
132
|
+
const root = path.resolve(vaultRoot);
|
|
133
|
+
const resolved = path.resolve(vaultRoot, filePath);
|
|
134
|
+
return resolved !== root && !resolved.startsWith(root + path.sep);
|
|
135
|
+
}
|
|
136
|
+
/** Slice and optionally number lines. lineNumbers format: "N\tline". */
|
|
137
|
+
function sliceDocLines(content, opts) {
|
|
138
|
+
const lines = content.split(/\r?\n/u);
|
|
139
|
+
const start = Math.max(0, (opts.fromLine ?? 1) - 1);
|
|
140
|
+
const count = opts.lineCount ?? opts.lineLimit ?? lines.length;
|
|
141
|
+
const selected = lines.slice(start, start + Math.max(0, count));
|
|
142
|
+
return selected
|
|
143
|
+
.map((line, index) => (opts.lineNumbers === true ? `${start + index + 1}\t${line}` : line))
|
|
144
|
+
.join("\n");
|
|
145
|
+
}
|
|
146
|
+
/** Cheaply extract a title from the first 20 lines (# H1 or frontmatter title:). */
|
|
147
|
+
function extractDocTitle(content) {
|
|
148
|
+
for (const line of content.split(/\r?\n/u).slice(0, 20)) {
|
|
149
|
+
const h1 = /^#\s+(.+)$/u.exec(line);
|
|
150
|
+
if (h1)
|
|
151
|
+
return h1[1].trim();
|
|
152
|
+
const fm = /^title:\s*(.+)$/u.exec(line);
|
|
153
|
+
if (fm)
|
|
154
|
+
return fm[1].trim();
|
|
155
|
+
}
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* First body lines (YAML frontmatter stripped, heading markers removed) as a
|
|
160
|
+
* single-line preview, capped to maxChars. The engine's retrieval is
|
|
161
|
+
* document-level (no chunk offsets), so this is an honest doc-head preview, not
|
|
162
|
+
* a match-centered excerpt.
|
|
163
|
+
*/
|
|
164
|
+
function docHeadSnippet(content, maxChars = 200) {
|
|
165
|
+
let body = content;
|
|
166
|
+
const fm = /^---\r?\n[\s\S]*?\r?\n---\r?\n?/u.exec(body);
|
|
167
|
+
if (fm && fm.index === 0)
|
|
168
|
+
body = body.slice(fm[0].length);
|
|
169
|
+
const preview = body
|
|
170
|
+
.split(/\r?\n/u)
|
|
171
|
+
.map((line) => line.replace(/^#{1,6}\s+/u, "").trim())
|
|
172
|
+
.filter((line) => line.length > 0)
|
|
173
|
+
.join(" ");
|
|
174
|
+
return preview.length > maxChars ? `${preview.slice(0, maxChars).trimEnd()}…` : preview;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Best-effort title + doc-head snippet for ranked hits. Retrieval returns
|
|
178
|
+
* document-level paths only, so the snippet is the note's opening body (not a
|
|
179
|
+
* passage match) — practical parity with the src/search hit preview without
|
|
180
|
+
* faking relevance. Out-of-vault paths or read failures degrade to the bare hit
|
|
181
|
+
* (empty snippet, no title), never throwing.
|
|
182
|
+
*/
|
|
183
|
+
function enrichQueryHits(result, vault) {
|
|
184
|
+
if (!result.available || result.hits.length === 0)
|
|
185
|
+
return result;
|
|
186
|
+
const hits = result.hits.map((hit) => {
|
|
187
|
+
if (isUnsafeVaultPath(hit.path, vault))
|
|
188
|
+
return hit;
|
|
189
|
+
let raw;
|
|
190
|
+
try {
|
|
191
|
+
raw = readFileSync(path.join(vault, hit.path), "utf-8");
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return hit;
|
|
195
|
+
}
|
|
196
|
+
const title = extractDocTitle(raw);
|
|
197
|
+
return { ...hit, snippet: docHeadSnippet(raw), ...(title !== undefined ? { title } : {}) };
|
|
198
|
+
});
|
|
199
|
+
return { ...result, available: true, hits };
|
|
200
|
+
}
|
|
201
|
+
/** Read folder intent declarations without creating or updating vault state. */
|
|
202
|
+
function folderIntents(vault) {
|
|
203
|
+
const candidates = [
|
|
204
|
+
path.join(vault, ".oms", "taxonomy.yaml"),
|
|
205
|
+
path.join(vault, "taxonomy.yaml"),
|
|
206
|
+
];
|
|
207
|
+
try {
|
|
208
|
+
candidates.push(path.join(resolveBundledAssetPaths().ontologyDir, "taxonomy.yaml"));
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// A package without bundled ontology still serves axis results; intents
|
|
212
|
+
// remain optional metadata.
|
|
213
|
+
}
|
|
214
|
+
for (const candidate of candidates) {
|
|
215
|
+
try {
|
|
216
|
+
const parsed = parseYaml(readFileSync(candidate, "utf-8"));
|
|
217
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
218
|
+
continue;
|
|
219
|
+
const folders = parsed["folders"];
|
|
220
|
+
if (!folders || typeof folders !== "object" || Array.isArray(folders))
|
|
221
|
+
continue;
|
|
222
|
+
const intents = new Map();
|
|
223
|
+
for (const [folder, value] of Object.entries(folders)) {
|
|
224
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
225
|
+
continue;
|
|
226
|
+
const intent = value["intent"];
|
|
227
|
+
if (typeof intent === "string" && intent.trim())
|
|
228
|
+
intents.set(folder, intent.trim());
|
|
229
|
+
}
|
|
230
|
+
return intents;
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
// A missing optional taxonomy is not an error; a malformed one simply
|
|
234
|
+
// contributes no intent and never causes a write.
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return new Map();
|
|
238
|
+
}
|
|
239
|
+
function facetIntent(facet, intents, queryIntent) {
|
|
240
|
+
if (facet.axis === "folder")
|
|
241
|
+
return intents.get(facet.value) ?? `Folder axis: ${facet.value}`;
|
|
242
|
+
if (queryIntent !== undefined && queryIntent.trim().length > 0)
|
|
243
|
+
return queryIntent.trim();
|
|
244
|
+
if (facet.axis === "field")
|
|
245
|
+
return `Frontmatter field axis: ${facet.key ?? "unknown"}`;
|
|
246
|
+
return `Link axis: ${facet.value}`;
|
|
247
|
+
}
|
|
248
|
+
function isLexOnlySubQueries(subQueries) {
|
|
249
|
+
return subQueries.length > 0 && subQueries.every((subQuery) => subQuery.type === "lex");
|
|
250
|
+
}
|
|
251
|
+
/** Return true when the caller explicitly requested an embedding channel. */
|
|
252
|
+
function hasExplicitEmbeddingSearch(opts) {
|
|
253
|
+
if (opts.mode === "vsearch")
|
|
254
|
+
return true;
|
|
255
|
+
if (typeof opts.vec === "string" && opts.vec.trim().length > 0)
|
|
256
|
+
return true;
|
|
257
|
+
if (typeof opts.hyde === "string" && opts.hyde.trim().length > 0)
|
|
258
|
+
return true;
|
|
259
|
+
return (opts.searches ?? []).some((search) => search.type === "vec" || search.type === "hyde");
|
|
260
|
+
}
|
|
261
|
+
const UNBOUNDED_CANDIDATE_LIMIT = Number.MAX_SAFE_INTEGER;
|
|
262
|
+
/**
|
|
263
|
+
* Collection aggregation asks each child query for every candidate and applies
|
|
264
|
+
* one global page after merging. A direct query without a limit still keeps
|
|
265
|
+
* the public ten-hit default.
|
|
266
|
+
*/
|
|
267
|
+
function resultPageLimit(opts) {
|
|
268
|
+
return opts.limit ?? (opts.collectionPath === undefined ? undefined : UNBOUNDED_CANDIDATE_LIMIT);
|
|
269
|
+
}
|
|
270
|
+
/** Read the dedicated EAV snapshot without creating `.oms` state. */
|
|
271
|
+
async function loadAxisSnapshot(vault) {
|
|
272
|
+
const dbPath = axisStorePath(vault);
|
|
273
|
+
if (!existsSync(dbPath))
|
|
274
|
+
return null;
|
|
275
|
+
const store = openAxisStore(dbPath, { readOnly: true });
|
|
276
|
+
try {
|
|
277
|
+
const storedSignature = store.sourceSignature();
|
|
278
|
+
if (storedSignature === null || storedSignature !== await nodeSourceSignature(vault))
|
|
279
|
+
return null;
|
|
280
|
+
return store.list();
|
|
281
|
+
}
|
|
282
|
+
finally {
|
|
283
|
+
store.close();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function observationValue(value) {
|
|
287
|
+
if (value instanceof Date)
|
|
288
|
+
return value.toISOString().toLowerCase();
|
|
289
|
+
return String(value).trim().toLowerCase();
|
|
290
|
+
}
|
|
291
|
+
function contractAxisType(type) {
|
|
292
|
+
switch (type) {
|
|
293
|
+
case "number":
|
|
294
|
+
return "number";
|
|
295
|
+
case "boolean":
|
|
296
|
+
case "checkbox":
|
|
297
|
+
return "boolean";
|
|
298
|
+
case "date":
|
|
299
|
+
case "datetime":
|
|
300
|
+
return "date";
|
|
301
|
+
case "text":
|
|
302
|
+
case "string":
|
|
303
|
+
case "select":
|
|
304
|
+
case "list":
|
|
305
|
+
case "multitext":
|
|
306
|
+
case "multi":
|
|
307
|
+
case "tags":
|
|
308
|
+
case "aliases":
|
|
309
|
+
case "file":
|
|
310
|
+
return "string";
|
|
311
|
+
default:
|
|
312
|
+
return undefined;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Compare live frontmatter/EAV observations with the tracked JSON contract.
|
|
317
|
+
* Missing fields are allowed (required-field validation owns that concern);
|
|
318
|
+
* an observed undeclared key, type, or closed-vocabulary value is drift.
|
|
319
|
+
*/
|
|
320
|
+
function hasContractObservationDrift(contract, nodes, observations) {
|
|
321
|
+
if (contract === null)
|
|
322
|
+
return false;
|
|
323
|
+
const declared = new Set();
|
|
324
|
+
const types = new Map();
|
|
325
|
+
const allowed = new Map();
|
|
326
|
+
const addAllowed = (key, values) => {
|
|
327
|
+
if (values === undefined)
|
|
328
|
+
return;
|
|
329
|
+
const normalizedKey = key.trim().toLowerCase();
|
|
330
|
+
const target = allowed.get(normalizedKey) ?? new Set();
|
|
331
|
+
for (const item of values) {
|
|
332
|
+
const value = typeof item === "string"
|
|
333
|
+
? item
|
|
334
|
+
: item && typeof item === "object" && typeof item.value === "string"
|
|
335
|
+
? item.value
|
|
336
|
+
: undefined;
|
|
337
|
+
if (value !== undefined)
|
|
338
|
+
target.add(observationValue(value));
|
|
339
|
+
}
|
|
340
|
+
allowed.set(normalizedKey, target);
|
|
341
|
+
};
|
|
342
|
+
for (const axis of contract.axes) {
|
|
343
|
+
if (axis.kind !== "field")
|
|
344
|
+
continue;
|
|
345
|
+
const key = axis.key.trim().toLowerCase();
|
|
346
|
+
if (key === "concept")
|
|
347
|
+
continue;
|
|
348
|
+
declared.add(key);
|
|
349
|
+
types.set(key, axis.type);
|
|
350
|
+
addAllowed(key, axis.allowedValues);
|
|
351
|
+
}
|
|
352
|
+
for (const [key, type] of Object.entries(contract.types)) {
|
|
353
|
+
const normalizedKey = key.trim().toLowerCase();
|
|
354
|
+
if (normalizedKey === "concept")
|
|
355
|
+
continue;
|
|
356
|
+
declared.add(normalizedKey);
|
|
357
|
+
types.set(normalizedKey, type);
|
|
358
|
+
}
|
|
359
|
+
for (const [key, values] of Object.entries(contract.allowedValues)) {
|
|
360
|
+
const normalizedKey = key.trim().toLowerCase();
|
|
361
|
+
if (normalizedKey === "concept")
|
|
362
|
+
continue;
|
|
363
|
+
declared.add(normalizedKey);
|
|
364
|
+
addAllowed(normalizedKey, values);
|
|
365
|
+
}
|
|
366
|
+
const observedKeys = new Set();
|
|
367
|
+
const observedValues = [];
|
|
368
|
+
// Node axes are the live source of truth even when no EAV snapshot exists.
|
|
369
|
+
for (const node of nodes) {
|
|
370
|
+
for (const [key, rawValues] of Object.entries(node.axes)) {
|
|
371
|
+
const normalizedKey = key.trim().toLowerCase();
|
|
372
|
+
if (normalizedKey === "concept")
|
|
373
|
+
continue;
|
|
374
|
+
observedKeys.add(normalizedKey);
|
|
375
|
+
const values = Array.isArray(rawValues) ? rawValues : [rawValues];
|
|
376
|
+
for (const value of values) {
|
|
377
|
+
// String values can also represent YAML dates after node-cache
|
|
378
|
+
// serialisation, so only infer unambiguous primitive types here.
|
|
379
|
+
const valueType = typeof value === "number"
|
|
380
|
+
? "number"
|
|
381
|
+
: typeof value === "boolean"
|
|
382
|
+
? "boolean"
|
|
383
|
+
: undefined;
|
|
384
|
+
observedValues.push({ key: normalizedKey, value, valueType });
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
for (const row of observations ?? []) {
|
|
389
|
+
const normalizedKey = row.axisKey.trim().toLowerCase();
|
|
390
|
+
if (row.axisKind !== "field" || normalizedKey === "concept")
|
|
391
|
+
continue;
|
|
392
|
+
observedKeys.add(normalizedKey);
|
|
393
|
+
observedValues.push({
|
|
394
|
+
key: normalizedKey,
|
|
395
|
+
value: row.value,
|
|
396
|
+
valueType: row.valueType,
|
|
397
|
+
normalizedValue: row.normalizedValue,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
for (const observed of observedValues) {
|
|
401
|
+
if (!declared.has(observed.key))
|
|
402
|
+
return true;
|
|
403
|
+
const expectedType = contractAxisType(types.get(observed.key) ?? "");
|
|
404
|
+
if (expectedType !== undefined && observed.valueType !== undefined && expectedType !== observed.valueType) {
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
407
|
+
const values = allowed.get(observed.key);
|
|
408
|
+
if (values !== undefined && !values.has(observed.normalizedValue ?? observationValue(observed.value))) {
|
|
409
|
+
return true;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
for (const key of observedKeys) {
|
|
413
|
+
if (!declared.has(key))
|
|
414
|
+
return true;
|
|
415
|
+
}
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
async function contractObservationDrift(vault, nodes, observations) {
|
|
419
|
+
return hasContractObservationDrift(await loadContract(vault), nodes, observations);
|
|
420
|
+
}
|
|
421
|
+
/** Overlay typed EAV values onto the lexical node snapshot. */
|
|
422
|
+
function applyAxisSnapshot(nodes, observations) {
|
|
423
|
+
const byNote = new Map();
|
|
424
|
+
for (const observation of observations) {
|
|
425
|
+
const rows = byNote.get(observation.notePath) ?? [];
|
|
426
|
+
rows.push(observation);
|
|
427
|
+
byNote.set(observation.notePath, rows);
|
|
428
|
+
}
|
|
429
|
+
return nodes.map((node) => {
|
|
430
|
+
const rows = byNote.get(node.path);
|
|
431
|
+
if (rows === undefined)
|
|
432
|
+
return node;
|
|
433
|
+
const fields = {};
|
|
434
|
+
const links = [];
|
|
435
|
+
let folder = node.folder;
|
|
436
|
+
for (const row of rows) {
|
|
437
|
+
const value = row.value instanceof Date ? row.value.toISOString() : row.value;
|
|
438
|
+
if (row.axisKind === "folder" && typeof value === "string") {
|
|
439
|
+
folder = value;
|
|
440
|
+
}
|
|
441
|
+
else if (row.axisKind === "link" && typeof value === "string") {
|
|
442
|
+
links.push(value);
|
|
443
|
+
}
|
|
444
|
+
else if (row.axisKind === "field") {
|
|
445
|
+
const values = fields[row.axisKey] ?? [];
|
|
446
|
+
values.push(value);
|
|
447
|
+
fields[row.axisKey] = values;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return {
|
|
451
|
+
...node,
|
|
452
|
+
folder,
|
|
453
|
+
// The node DTO keeps its legacy string-array declaration, while this
|
|
454
|
+
// runtime overlay deliberately carries typed EAV scalars.
|
|
455
|
+
axes: fields,
|
|
456
|
+
// An existing snapshot is authoritative: an empty link/field set must
|
|
457
|
+
// clear stale values from an older JSON node-index cache.
|
|
458
|
+
wikilinks: [...new Set(links)],
|
|
459
|
+
};
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
/** Contract field names are the allow-list for public field-axis queries. */
|
|
463
|
+
async function loadContractFieldKeys(vault) {
|
|
464
|
+
const contract = await loadContract(vault);
|
|
465
|
+
if (contract === null)
|
|
466
|
+
return undefined;
|
|
467
|
+
const keys = new Set();
|
|
468
|
+
for (const axis of contract.axes) {
|
|
469
|
+
if (axis.kind === "field")
|
|
470
|
+
keys.add(axis.key.trim().toLowerCase());
|
|
471
|
+
}
|
|
472
|
+
for (const key of Object.keys(contract.types))
|
|
473
|
+
keys.add(key.trim().toLowerCase());
|
|
474
|
+
return keys;
|
|
475
|
+
}
|
|
476
|
+
function validateKnownFieldAxes(axes, knownFields) {
|
|
477
|
+
if (axes === undefined || knownFields === undefined || axes.field === undefined)
|
|
478
|
+
return;
|
|
479
|
+
if (axes.field === null || typeof axes.field !== "object" || Array.isArray(axes.field)) {
|
|
480
|
+
throw new Error("Field axis must be an object mapping field names to values.");
|
|
481
|
+
}
|
|
482
|
+
for (const key of Object.keys(axes.field)) {
|
|
483
|
+
if (key.trim().toLocaleLowerCase() === "concept") {
|
|
484
|
+
throw new Error('Unknown query field axis "concept".');
|
|
485
|
+
}
|
|
486
|
+
if (!knownFields.has(key.trim().toLowerCase())) {
|
|
487
|
+
throw new Error(`Unknown field axis "${key}" (not declared by the JSON contract).`);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
// ---------------------------------------------------------------------------
|
|
492
|
+
// Adapter facade
|
|
493
|
+
// ---------------------------------------------------------------------------
|
|
494
|
+
/**
|
|
495
|
+
* Engine-side adapter for the 10 MCP ops.
|
|
496
|
+
*
|
|
497
|
+
* Construct with injected DispatcherDeps + the vault root; the assemble step
|
|
498
|
+
* wires the real EngineStore + EmbeddingProvider into those deps.
|
|
499
|
+
*/
|
|
500
|
+
export class McpEngineAdapter {
|
|
501
|
+
deps;
|
|
502
|
+
vaultPath;
|
|
503
|
+
config;
|
|
504
|
+
reranker;
|
|
505
|
+
implicitLexicalSync;
|
|
506
|
+
persistLexicalSync;
|
|
507
|
+
/**
|
|
508
|
+
* @param deps - Injected backend dependencies (store + embed required).
|
|
509
|
+
* @param vaultPath - Absolute vault root, used by graph / sync / cleanup /
|
|
510
|
+
* retrieve ops that are vault-scoped. Required so tsc
|
|
511
|
+
* enforces it at every construction site (RISK-4).
|
|
512
|
+
* @param config - Optional embedding identity config (provider/model).
|
|
513
|
+
* This is used to enforce mismatch policy and to thread
|
|
514
|
+
* identity into syncEmbeddings when the call omits it.
|
|
515
|
+
*/
|
|
516
|
+
constructor(deps, vaultPath, config, reranker,
|
|
517
|
+
/** Explicit assembly policy; read-only engines suppress this refresh. */
|
|
518
|
+
implicitLexicalSync = false, persistLexicalSync = true) {
|
|
519
|
+
this.deps = deps;
|
|
520
|
+
this.vaultPath = vaultPath;
|
|
521
|
+
this.config = config;
|
|
522
|
+
this.reranker = reranker;
|
|
523
|
+
this.implicitLexicalSync = implicitLexicalSync;
|
|
524
|
+
this.persistLexicalSync = persistLexicalSync;
|
|
525
|
+
}
|
|
526
|
+
// -------------------------------------------------------------------------
|
|
527
|
+
// Cache-path + node-index helpers
|
|
528
|
+
// -------------------------------------------------------------------------
|
|
529
|
+
graphCachePath(vault) {
|
|
530
|
+
return path.join(vault, ".oms", "cache", "engine", "graph.json");
|
|
531
|
+
}
|
|
532
|
+
nodeCachePath(vault) {
|
|
533
|
+
return path.join(vault, ".oms", "cache", "engine", "node-index.json");
|
|
534
|
+
}
|
|
535
|
+
/** Load the node index from cache, building it live on a cache miss. */
|
|
536
|
+
async loadOrBuildNodes(vault = this.vaultPath) {
|
|
537
|
+
// A direct adapter caller may supply a store-backed query without a
|
|
538
|
+
// filesystem vault (for example an MCP transport test). Node enrichment is
|
|
539
|
+
// optional in that case; never turn a valid retrieval result into an ENOENT.
|
|
540
|
+
if (!existsSync(vault))
|
|
541
|
+
return [];
|
|
542
|
+
const cached = await loadNodeIndex(this.nodeCachePath(vault), await nodeSourceSignature(vault));
|
|
543
|
+
if (cached !== null)
|
|
544
|
+
return cached;
|
|
545
|
+
return buildNodeIndex({ vaultPath: vault });
|
|
546
|
+
}
|
|
547
|
+
// -------------------------------------------------------------------------
|
|
548
|
+
// 1. oms_semantic_query (centerpiece)
|
|
549
|
+
// -------------------------------------------------------------------------
|
|
550
|
+
/**
|
|
551
|
+
* Evaluate an overview/axis query directly from the read-only node index.
|
|
552
|
+
* This path is intentionally model-free and does not open the embedding
|
|
553
|
+
* store, so a query issued outside a vault or against an empty vault cannot
|
|
554
|
+
* create `.oms/` state as a side effect.
|
|
555
|
+
*/
|
|
556
|
+
async queryNodeAxes(opts, subQueries) {
|
|
557
|
+
const vault = opts.vault ?? this.vaultPath;
|
|
558
|
+
const baseNodes = await this.loadOrBuildNodes(vault);
|
|
559
|
+
const snapshot = await loadAxisSnapshot(vault);
|
|
560
|
+
const drift = await contractObservationDrift(vault, baseNodes, snapshot);
|
|
561
|
+
const observedFields = snapshot === null
|
|
562
|
+
? undefined
|
|
563
|
+
: new Set(snapshot.filter((row) => row.axisKind === "field").map((row) => row.axisKey.toLowerCase()));
|
|
564
|
+
const liveObservedFields = new Set(baseNodes.flatMap((node) => Object.keys(node.axes).map((key) => key.toLowerCase())));
|
|
565
|
+
const knownFields = await loadContractFieldKeys(vault)
|
|
566
|
+
?? (observedFields === undefined ? liveObservedFields : new Set([...observedFields, ...liveObservedFields]));
|
|
567
|
+
validateKnownFieldAxes(opts.axes, knownFields);
|
|
568
|
+
const nodes = snapshot === null ? baseNodes : applyAxisSnapshot(baseNodes, snapshot);
|
|
569
|
+
const axisFiltered = opts.axes === undefined
|
|
570
|
+
? nodes
|
|
571
|
+
: filterNodesByQueryAxes(nodes, opts.axes);
|
|
572
|
+
const collection = opts.collectionPath;
|
|
573
|
+
const filtered = collection === undefined
|
|
574
|
+
? axisFiltered
|
|
575
|
+
: axisFiltered.filter((node) => node.path === collection || node.path.startsWith(`${collection}/`));
|
|
576
|
+
const query = subQueries.find((subQuery) => subQuery.type === "lex")?.query
|
|
577
|
+
?? (subQueries.length === 0 ? opts.query ?? "" : "");
|
|
578
|
+
const scored = filtered
|
|
579
|
+
.map((node) => {
|
|
580
|
+
const score = searchScore(node, query);
|
|
581
|
+
return {
|
|
582
|
+
docPath: node.path,
|
|
583
|
+
score,
|
|
584
|
+
...(query.trim().length > 0 ? { perTypeScores: { lex: score } } : {}),
|
|
585
|
+
};
|
|
586
|
+
})
|
|
587
|
+
// An axis constraint narrows the corpus; it is not lexical evidence.
|
|
588
|
+
// Once a lexical query is supplied, do not return axis-only zero-score
|
|
589
|
+
// documents as query hits.
|
|
590
|
+
.filter((result) => query.trim().length === 0 || result.score > 0)
|
|
591
|
+
.sort((left, right) => right.score - left.score || left.docPath.localeCompare(right.docPath));
|
|
592
|
+
const facetNodes = opts.minScore === undefined
|
|
593
|
+
? filtered
|
|
594
|
+
: filtered.filter((node) => searchScore(node, query) >= opts.minScore);
|
|
595
|
+
const intents = folderIntents(vault);
|
|
596
|
+
const facets = queryFacets(facetNodes).map((facet) => ({
|
|
597
|
+
...facet,
|
|
598
|
+
intent: facetIntent(facet, intents, opts.intent),
|
|
599
|
+
}));
|
|
600
|
+
const candidateLimit = opts.candidateLimit ?? UNBOUNDED_CANDIDATE_LIMIT;
|
|
601
|
+
const candidates = scored.slice(0, candidateLimit);
|
|
602
|
+
const shouldRerank = opts.rerank === true && opts.noRerank !== true;
|
|
603
|
+
if (shouldRerank && this.reranker === undefined) {
|
|
604
|
+
return queryResultUnavailable("reranking requires a configured Reranker; pass one to assembleEngine() or createOMSMcpServer().");
|
|
605
|
+
}
|
|
606
|
+
if (shouldRerank && query.trim().length === 0) {
|
|
607
|
+
return queryResultUnavailable("reranking requires a non-empty natural-language query.");
|
|
608
|
+
}
|
|
609
|
+
const reranker = this.reranker;
|
|
610
|
+
const ranked = shouldRerank
|
|
611
|
+
? await reranker.rerank(query, candidates.map((result) => ({
|
|
612
|
+
docPath: result.docPath,
|
|
613
|
+
chunkOrdinal: 0,
|
|
614
|
+
score: result.score,
|
|
615
|
+
}))).then((reranked) => {
|
|
616
|
+
const candidateByPath = new Map(candidates.map((candidate) => [candidate.docPath, candidate]));
|
|
617
|
+
return reranked.flatMap((hit) => {
|
|
618
|
+
const candidate = candidateByPath.get(hit.docPath);
|
|
619
|
+
return candidate === undefined
|
|
620
|
+
? []
|
|
621
|
+
: [{ ...candidate, score: hit.score, perTypeScores: { lex: hit.score } }];
|
|
622
|
+
});
|
|
623
|
+
})
|
|
624
|
+
: candidates;
|
|
625
|
+
const hasNonLexSearch = subQueries.some((search) => search.type !== "lex");
|
|
626
|
+
const result = retrievalResultsToQueryResult(ranked, {
|
|
627
|
+
minScore: opts.minScore,
|
|
628
|
+
limit: resultPageLimit(opts),
|
|
629
|
+
cursor: opts.cursor,
|
|
630
|
+
intent: opts.intent,
|
|
631
|
+
facetValues: facets,
|
|
632
|
+
// Axis queries are evaluated by the model-free node matcher. A vector
|
|
633
|
+
// request is reported as approximated rather than falsely claiming vec
|
|
634
|
+
// evidence in the receipt.
|
|
635
|
+
usedChannels: query.trim().length > 0 ? ["lex"] : [],
|
|
636
|
+
approximated: hasNonLexSearch,
|
|
637
|
+
drift,
|
|
638
|
+
});
|
|
639
|
+
return enrichQueryHits(result, vault);
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Execute a semantic query and return MCP-shaped results.
|
|
643
|
+
* Maps opts → TypedSubQuery[] → dispatch() → McpSemanticQueryResult.
|
|
644
|
+
*/
|
|
645
|
+
async semanticQuery(opts) {
|
|
646
|
+
let normalized;
|
|
647
|
+
try {
|
|
648
|
+
normalized = normalizeQueryOptions(opts);
|
|
649
|
+
}
|
|
650
|
+
catch (err) {
|
|
651
|
+
return queryResultUnavailable(err instanceof Error ? err.message : String(err));
|
|
652
|
+
}
|
|
653
|
+
const { subQueries } = normalized;
|
|
654
|
+
if (opts.axes !== undefined && hasExplicitEmbeddingSearch(opts)) {
|
|
655
|
+
return queryResultUnavailable("Axis queries support lexical matching only; explicit vector/HyDE retrieval requires " +
|
|
656
|
+
"OMS_EMBEDDING_PROVIDER + OMS_EMBEDDING_MODEL.");
|
|
657
|
+
}
|
|
658
|
+
if (opts.axes !== undefined || normalized.overview) {
|
|
659
|
+
try {
|
|
660
|
+
return await this.queryNodeAxes(opts, subQueries);
|
|
661
|
+
}
|
|
662
|
+
catch (err) {
|
|
663
|
+
return queryResultUnavailable(err instanceof Error ? err.message : String(err));
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
try {
|
|
667
|
+
// A core engine has a real lexical store but deliberately no embedding
|
|
668
|
+
// provider. The default query mode is hybrid for vector-capable engines;
|
|
669
|
+
// on a model-less engine, retain the useful lexical half unless the
|
|
670
|
+
// caller explicitly asked for vec/HyDE (which must fail loudly).
|
|
671
|
+
const vecAvailable = typeof this.deps.store.capabilities === "function"
|
|
672
|
+
? this.deps.store.capabilities().vecAvailable
|
|
673
|
+
: true;
|
|
674
|
+
const modelLessFallback = !vecAvailable &&
|
|
675
|
+
!hasExplicitEmbeddingSearch(opts) &&
|
|
676
|
+
subQueries.some((subQuery) => subQuery.type === "vec");
|
|
677
|
+
const effectiveSubQueries = modelLessFallback
|
|
678
|
+
? subQueries.filter((subQuery) => subQuery.type === "lex")
|
|
679
|
+
: subQueries;
|
|
680
|
+
if (this.implicitLexicalSync && isLexOnlySubQueries(effectiveSubQueries)) {
|
|
681
|
+
const syncResult = await syncEngineStore({
|
|
682
|
+
vault: opts.vault ?? this.vaultPath,
|
|
683
|
+
collection: opts.collection,
|
|
684
|
+
embed: false,
|
|
685
|
+
store: this.deps.store,
|
|
686
|
+
persist: this.persistLexicalSync,
|
|
687
|
+
});
|
|
688
|
+
if (!syncResult.available) {
|
|
689
|
+
return queryResultUnavailable(syncResult.reason ?? "Lexical index sync unavailable");
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
// Without an explicit candidate limit, retrieve the complete ranked
|
|
693
|
+
// stream so totalCount and offset cursors remain accurate on pages past
|
|
694
|
+
// the first 50 results. A caller-supplied candidateLimit remains an
|
|
695
|
+
// intentional cap (not an implementation truncation).
|
|
696
|
+
const k = opts.candidateLimit ?? UNBOUNDED_CANDIDATE_LIMIT;
|
|
697
|
+
const shouldRerank = opts.rerank === true && opts.noRerank !== true;
|
|
698
|
+
if (shouldRerank && this.reranker === undefined) {
|
|
699
|
+
return queryResultUnavailable("reranking requires a configured Reranker; pass one to assembleEngine() or createOMSMcpServer().");
|
|
700
|
+
}
|
|
701
|
+
if (shouldRerank && !opts.query?.trim()) {
|
|
702
|
+
return queryResultUnavailable("reranking requires a non-empty natural-language query.");
|
|
703
|
+
}
|
|
704
|
+
const results = await retrieve({
|
|
705
|
+
subQueries: [...effectiveSubQueries],
|
|
706
|
+
deps: this.deps,
|
|
707
|
+
k,
|
|
708
|
+
collection: opts.collectionPath,
|
|
709
|
+
query: opts.query,
|
|
710
|
+
reranker: shouldRerank ? this.reranker : undefined,
|
|
711
|
+
});
|
|
712
|
+
let facetValues;
|
|
713
|
+
const vault = opts.vault ?? this.vaultPath;
|
|
714
|
+
const baseNodes = await this.loadOrBuildNodes(vault);
|
|
715
|
+
const snapshot = await loadAxisSnapshot(vault);
|
|
716
|
+
const nodes = snapshot === null ? baseNodes : applyAxisSnapshot(baseNodes, snapshot);
|
|
717
|
+
const drift = await contractObservationDrift(vault, baseNodes, snapshot);
|
|
718
|
+
const scoped = opts.collectionPath === undefined
|
|
719
|
+
? nodes
|
|
720
|
+
: nodes.filter((node) => node.path === opts.collectionPath || node.path.startsWith(`${opts.collectionPath}/`));
|
|
721
|
+
const intents = folderIntents(vault);
|
|
722
|
+
facetValues = queryFacets(scoped).map((facet) => ({
|
|
723
|
+
...facet,
|
|
724
|
+
intent: facetIntent(facet, intents, opts.intent),
|
|
725
|
+
}));
|
|
726
|
+
const requestedChannels = [...new Set(effectiveSubQueries.map((search) => search.type))]
|
|
727
|
+
.filter((type) => type === "lex" || type === "vec" || type === "hyde");
|
|
728
|
+
const mapped = retrievalResultsToQueryResult(results, {
|
|
729
|
+
...opts,
|
|
730
|
+
limit: resultPageLimit(opts),
|
|
731
|
+
facetValues,
|
|
732
|
+
usedChannels: requestedChannels,
|
|
733
|
+
approximated: modelLessFallback ||
|
|
734
|
+
effectiveSubQueries.some((search) => search.type !== "lex" && search.type !== "vec"),
|
|
735
|
+
drift,
|
|
736
|
+
});
|
|
737
|
+
// Fill title + doc-head snippet from disk so engine hits reach practical
|
|
738
|
+
// parity with the src/search preview (the pure mapper stays text-free).
|
|
739
|
+
return enrichQueryHits(mapped, opts.vault ?? this.vaultPath);
|
|
740
|
+
}
|
|
741
|
+
catch (err) {
|
|
742
|
+
return queryResultUnavailable(err instanceof Error ? err.message : String(err));
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
// -------------------------------------------------------------------------
|
|
746
|
+
// 2. oms_sync_embeddings
|
|
747
|
+
// -------------------------------------------------------------------------
|
|
748
|
+
/**
|
|
749
|
+
* Sync the vault into the engine store: scan → chunk → embed → upsert.
|
|
750
|
+
*
|
|
751
|
+
* Delegates to syncEngineStore() (embed/sync.ts), which performs SHA-256
|
|
752
|
+
* incremental diffing so unchanged chunks are skipped. The mandatory
|
|
753
|
+
* `status` field is synthesized from deps.embed + the run counters.
|
|
754
|
+
*
|
|
755
|
+
* Note: syncEngineStore opens its own provider+store internally; the GGUF
|
|
756
|
+
* pool deduplicates by loadPromise per process so this is safe (RISK-1).
|
|
757
|
+
*/
|
|
758
|
+
async syncEmbeddings(opts) {
|
|
759
|
+
const activeDbPath = this.config?.dbPath ??
|
|
760
|
+
path.join(path.resolve(opts.vault), ".oms", "engine-store.sqlite");
|
|
761
|
+
let swapHandleClosed = false;
|
|
762
|
+
try {
|
|
763
|
+
const syncResult = await syncEngineStore({
|
|
764
|
+
vault: opts.vault,
|
|
765
|
+
collection: opts.collection,
|
|
766
|
+
collectionPath: opts.collectionPath,
|
|
767
|
+
// Embedding identity is owned by the assemble-time canonical config
|
|
768
|
+
// (OMS_EMBEDDING_PROVIDER / OMS_EMBEDDING_MODEL) threaded into the
|
|
769
|
+
// adapter; the MCP call no longer carries modelPath. syncEngineStore
|
|
770
|
+
// resolves the real provider and fails fast (ADR-007) if it is missing.
|
|
771
|
+
embeddingProvider: this.config?.embeddingProvider,
|
|
772
|
+
embeddingModel: this.config?.embeddingModel,
|
|
773
|
+
embeddingDescriptor: this.config?.embeddingDescriptor,
|
|
774
|
+
embeddingDimensions: this.config?.embeddingDimensions,
|
|
775
|
+
embeddingContext: this.config?.embeddingContext,
|
|
776
|
+
embeddingContextLength: this.config?.embeddingContextLength,
|
|
777
|
+
embeddingContextTokens: this.config?.embeddingContextTokens,
|
|
778
|
+
embeddingMrlDim: this.config?.embeddingMrlDim,
|
|
779
|
+
embeddingNormalization: this.config?.embeddingNormalization,
|
|
780
|
+
embeddingPrefixScheme: this.config?.embeddingPrefixScheme,
|
|
781
|
+
dbPath: this.config?.dbPath,
|
|
782
|
+
embed: opts.embed ?? true,
|
|
783
|
+
force: opts.force ?? false,
|
|
784
|
+
onGenerationSwapPrepare: () => {
|
|
785
|
+
// The adapter's store outlives one sync call. Close it before the
|
|
786
|
+
// active WAL/SHM sidecars are removed, not after rename.
|
|
787
|
+
swapHandleClosed = true;
|
|
788
|
+
this.deps.store.close();
|
|
789
|
+
},
|
|
790
|
+
onGenerationSwapComplete: () => {
|
|
791
|
+
if (!swapHandleClosed)
|
|
792
|
+
return;
|
|
793
|
+
const reboundStore = openEngineStore(activeDbPath, this.deps.embed.dimensions);
|
|
794
|
+
this.deps.store = reboundStore;
|
|
795
|
+
this.config?.onStoreRebind?.(reboundStore);
|
|
796
|
+
swapHandleClosed = false;
|
|
797
|
+
},
|
|
798
|
+
});
|
|
799
|
+
if (!syncResult.available) {
|
|
800
|
+
return syncResultUnavailable(syncResult.reason ?? "sync unavailable", opts);
|
|
801
|
+
}
|
|
802
|
+
const engineResult = {
|
|
803
|
+
upserted: syncResult.added + syncResult.updated,
|
|
804
|
+
skipped: syncResult.skipped,
|
|
805
|
+
errors: 0,
|
|
806
|
+
};
|
|
807
|
+
const statusSnapshot = {
|
|
808
|
+
available: true,
|
|
809
|
+
storage: opts.storage ?? "oms-native-json",
|
|
810
|
+
models: { embedding: this.deps.embed.model },
|
|
811
|
+
index: {
|
|
812
|
+
documents: {
|
|
813
|
+
total: syncResult.scanned,
|
|
814
|
+
vectors: syncResult.added + syncResult.updated,
|
|
815
|
+
},
|
|
816
|
+
},
|
|
817
|
+
};
|
|
818
|
+
return engineSyncResultToMcp(engineResult, opts, statusSnapshot);
|
|
819
|
+
}
|
|
820
|
+
catch (err) {
|
|
821
|
+
return syncResultUnavailable(err instanceof Error ? err.message : String(err), opts);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
// -------------------------------------------------------------------------
|
|
825
|
+
// 3. oms_semantic_status
|
|
826
|
+
// -------------------------------------------------------------------------
|
|
827
|
+
/** Return status derived from the injected embed + store capabilities. */
|
|
828
|
+
semanticStatus(_opts) {
|
|
829
|
+
try {
|
|
830
|
+
const engineResult = capsToEngineStatusResult(this.deps.embed, this.deps.store);
|
|
831
|
+
return engineStatusResultToMcp(engineResult);
|
|
832
|
+
}
|
|
833
|
+
catch (err) {
|
|
834
|
+
return statusResultUnavailable(err instanceof Error ? err.message : String(err));
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
// -------------------------------------------------------------------------
|
|
838
|
+
// 4. oms_semantic_collections
|
|
839
|
+
// -------------------------------------------------------------------------
|
|
840
|
+
/** List embedding collections visible through the injected store. */
|
|
841
|
+
listCollections(_opts) {
|
|
842
|
+
try {
|
|
843
|
+
const engineResult = capsToEngineStatusResult(this.deps.embed, this.deps.store);
|
|
844
|
+
return engineStatusToCollectionResult(engineResult);
|
|
845
|
+
}
|
|
846
|
+
catch (err) {
|
|
847
|
+
return {
|
|
848
|
+
available: false,
|
|
849
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
850
|
+
collections: [],
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
// -------------------------------------------------------------------------
|
|
855
|
+
// 5. oms_semantic_contexts
|
|
856
|
+
// -------------------------------------------------------------------------
|
|
857
|
+
/** List semantic contexts from the injected store. */
|
|
858
|
+
listContexts(_opts) {
|
|
859
|
+
try {
|
|
860
|
+
const engineResult = capsToEngineStatusResult(this.deps.embed, this.deps.store);
|
|
861
|
+
return engineStatusToContextResult(engineResult);
|
|
862
|
+
}
|
|
863
|
+
catch (err) {
|
|
864
|
+
return {
|
|
865
|
+
available: false,
|
|
866
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
867
|
+
contexts: [],
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
// -------------------------------------------------------------------------
|
|
872
|
+
// 6. oms_semantic_cleanup
|
|
873
|
+
// -------------------------------------------------------------------------
|
|
874
|
+
/**
|
|
875
|
+
* Remove orphaned documents from the store: any stored doc_path that no
|
|
876
|
+
* longer exists in the live vault is cleared (meta + vec + FTS).
|
|
877
|
+
*/
|
|
878
|
+
async cleanup(_opts) {
|
|
879
|
+
let releaseLock;
|
|
880
|
+
try {
|
|
881
|
+
const dbPath = this.config?.dbPath ??
|
|
882
|
+
path.join(path.resolve(this.vaultPath), ".oms", "engine-store.sqlite");
|
|
883
|
+
releaseLock = acquireEngineStoreWriterLock(dbPath);
|
|
884
|
+
const store = this.deps.store;
|
|
885
|
+
const livePaths = new Set();
|
|
886
|
+
for await (const rel of walkMarkdown(this.vaultPath, this.vaultPath)) {
|
|
887
|
+
livePaths.add(rel);
|
|
888
|
+
}
|
|
889
|
+
const storePaths = store.listDocPaths();
|
|
890
|
+
let removed = 0;
|
|
891
|
+
for (const docPath of storePaths) {
|
|
892
|
+
if (!livePaths.has(docPath)) {
|
|
893
|
+
store.clearDocument(docPath);
|
|
894
|
+
removed++;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
return {
|
|
898
|
+
available: true,
|
|
899
|
+
storage: "oms-native-json",
|
|
900
|
+
removedDocuments: removed,
|
|
901
|
+
remainingDocuments: storePaths.length - removed,
|
|
902
|
+
collections: 1,
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
catch (err) {
|
|
906
|
+
return cleanupResultUnavailable(err instanceof Error ? err.message : String(err));
|
|
907
|
+
}
|
|
908
|
+
finally {
|
|
909
|
+
releaseLock?.();
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
// -------------------------------------------------------------------------
|
|
913
|
+
// 7. oms_graph_build
|
|
914
|
+
// -------------------------------------------------------------------------
|
|
915
|
+
/**
|
|
916
|
+
* Build the edge graph + node index and persist both to .oms/cache/engine/.
|
|
917
|
+
* On dryRun, report stats from the existing cache without rebuilding.
|
|
918
|
+
*/
|
|
919
|
+
async graphBuild(opts, vaultPath) {
|
|
920
|
+
const args = graphBuildOptionsToEngineArgs(opts, vaultPath);
|
|
921
|
+
const graphCachePath = this.graphCachePath(args.vaultPath);
|
|
922
|
+
if (args.dryRun) {
|
|
923
|
+
const meta = await loadCachedGraphMeta(graphCachePath);
|
|
924
|
+
if (meta !== null) {
|
|
925
|
+
const noteSet = new Set(meta.edges.flatMap((e) => [e.from, e.to]));
|
|
926
|
+
return engineGraphBuildResultToMcp({
|
|
927
|
+
notes: noteSet.size,
|
|
928
|
+
edges: meta.edges.length,
|
|
929
|
+
generatedAt: meta.generatedAt,
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
return engineGraphBuildResultToMcp({
|
|
933
|
+
notes: 0,
|
|
934
|
+
edges: 0,
|
|
935
|
+
generatedAt: new Date().toISOString(),
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
const edges = await buildGraph({ vaultPath: args.vaultPath });
|
|
939
|
+
await saveCachedGraph(graphCachePath, edges);
|
|
940
|
+
const nodes = await buildNodeIndex({ vaultPath: args.vaultPath });
|
|
941
|
+
const sourceSignature = await nodeSourceSignature(args.vaultPath);
|
|
942
|
+
await saveNodeIndex(this.nodeCachePath(args.vaultPath), nodes, sourceSignature);
|
|
943
|
+
// Graph build is the explicit writable refresh boundary for the typed
|
|
944
|
+
// EAV snapshot. Reconcile it atomically with the current markdown set.
|
|
945
|
+
const axisStore = await collectVaultAxisObservations(args.vaultPath);
|
|
946
|
+
axisStore.close();
|
|
947
|
+
const noteSet = new Set(edges.flatMap((e) => [e.from, e.to]));
|
|
948
|
+
return engineGraphBuildResultToMcp({
|
|
949
|
+
notes: noteSet.size,
|
|
950
|
+
edges: edges.length,
|
|
951
|
+
generatedAt: new Date().toISOString(),
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
// -------------------------------------------------------------------------
|
|
955
|
+
// 8a. oms_graph_status
|
|
956
|
+
// -------------------------------------------------------------------------
|
|
957
|
+
/** Report graph cache status (notes / edges / generatedAt) from disk. */
|
|
958
|
+
async graphStatus(vaultPath) {
|
|
959
|
+
const meta = await loadCachedGraphMeta(this.graphCachePath(vaultPath));
|
|
960
|
+
if (meta === null)
|
|
961
|
+
return engineGraphBuildToStatusResult(null);
|
|
962
|
+
const noteSet = new Set(meta.edges.flatMap((e) => [e.from, e.to]));
|
|
963
|
+
return engineGraphBuildToStatusResult({
|
|
964
|
+
notes: noteSet.size,
|
|
965
|
+
edges: meta.edges.length,
|
|
966
|
+
generatedAt: meta.generatedAt,
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
// -------------------------------------------------------------------------
|
|
970
|
+
// 8b. oms_retrieve_by_axis
|
|
971
|
+
// -------------------------------------------------------------------------
|
|
972
|
+
/**
|
|
973
|
+
* Filter the node index by axis (concept / folder / property / value /
|
|
974
|
+
* wikilink), rank by lexical overlap with the optional query, return hits.
|
|
975
|
+
* Axis metadata (concept / folder / axes / wikilinks) is JSON-encoded into
|
|
976
|
+
* the hit's `context` field for callers that need it (RISK-6).
|
|
977
|
+
*/
|
|
978
|
+
async retrieveByAxis(filters) {
|
|
979
|
+
try {
|
|
980
|
+
const baseNodes = await this.loadOrBuildNodes();
|
|
981
|
+
const snapshot = await loadAxisSnapshot(this.vaultPath);
|
|
982
|
+
const drift = await contractObservationDrift(this.vaultPath, baseNodes, snapshot);
|
|
983
|
+
const nodes = snapshot === null ? baseNodes : applyAxisSnapshot(baseNodes, snapshot);
|
|
984
|
+
const limit = Math.max(1, filters.limit ?? 10);
|
|
985
|
+
const filtered = filterNodesByAxis(nodes, {
|
|
986
|
+
concept: filters.concept,
|
|
987
|
+
folder: filters.folder,
|
|
988
|
+
property: filters.property,
|
|
989
|
+
value: filters.value,
|
|
990
|
+
wikilink: filters.wikilink,
|
|
991
|
+
});
|
|
992
|
+
const query = filters.query ?? "";
|
|
993
|
+
const scored = filtered
|
|
994
|
+
.map((node) => ({ node, score: searchScore(node, query) }))
|
|
995
|
+
.sort((a, b) => b.score - a.score || a.node.path.localeCompare(b.node.path))
|
|
996
|
+
.slice(0, limit);
|
|
997
|
+
const hits = scored.map(({ node, score }) => ({
|
|
998
|
+
docid: node.path,
|
|
999
|
+
score,
|
|
1000
|
+
uri: node.path,
|
|
1001
|
+
path: node.path,
|
|
1002
|
+
snippet: node.bodyPreview,
|
|
1003
|
+
context: JSON.stringify({
|
|
1004
|
+
concept: node.concept,
|
|
1005
|
+
folder: node.folder,
|
|
1006
|
+
axes: node.axes,
|
|
1007
|
+
wikilinks: node.wikilinks,
|
|
1008
|
+
}),
|
|
1009
|
+
evidence: { lexical: true, vector: false },
|
|
1010
|
+
}));
|
|
1011
|
+
const intents = folderIntents(this.vaultPath);
|
|
1012
|
+
const facets = queryFacets(filtered).map((facet) => ({
|
|
1013
|
+
...facet,
|
|
1014
|
+
intent: facetIntent(facet, intents, undefined),
|
|
1015
|
+
}));
|
|
1016
|
+
return {
|
|
1017
|
+
available: true,
|
|
1018
|
+
hits,
|
|
1019
|
+
totalCount: filtered.length,
|
|
1020
|
+
facets,
|
|
1021
|
+
cursor: scored.length < filtered.length ? String(scored.length) : null,
|
|
1022
|
+
receipt: { usedChannels: ["lex"], approximated: false, drift },
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
catch (err) {
|
|
1026
|
+
return queryResultUnavailable(err instanceof Error ? err.message : String(err));
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
// -------------------------------------------------------------------------
|
|
1030
|
+
// 9. oms_get — single-document file-based hydration (GAP-9)
|
|
1031
|
+
// -------------------------------------------------------------------------
|
|
1032
|
+
/**
|
|
1033
|
+
* Hydrate one document from disk by real vault-relative path (ADR-008).
|
|
1034
|
+
* Supports "file.md", "file.md:N" (single line), "file.md:N-M" (range),
|
|
1035
|
+
* and "#docid" (resolved via store.listDocPaths). No embedding model needed.
|
|
1036
|
+
*/
|
|
1037
|
+
async getDocument(opts) {
|
|
1038
|
+
const vault = opts.vault ?? this.vaultPath;
|
|
1039
|
+
const parsed = parseDocTarget(opts.target);
|
|
1040
|
+
let resolvedPath = parsed.filePath;
|
|
1041
|
+
if (parsed.isDocid) {
|
|
1042
|
+
const store = this.deps.store;
|
|
1043
|
+
const matched = store.listDocPaths().find((p) => p === parsed.filePath);
|
|
1044
|
+
if (!matched) {
|
|
1045
|
+
return { available: false, reason: `No OMS document matched "${opts.target}".`, documents: [] };
|
|
1046
|
+
}
|
|
1047
|
+
resolvedPath = matched;
|
|
1048
|
+
}
|
|
1049
|
+
else if (parsed.isGlob) {
|
|
1050
|
+
const [matched] = await globVaultDocs(vault, parsed.filePath);
|
|
1051
|
+
if (!matched) {
|
|
1052
|
+
return { available: false, reason: `No OMS document matched "${opts.target}".`, documents: [] };
|
|
1053
|
+
}
|
|
1054
|
+
resolvedPath = matched;
|
|
1055
|
+
}
|
|
1056
|
+
if (isUnsafeVaultPath(resolvedPath, vault)) {
|
|
1057
|
+
return { available: false, reason: "OMS semantic document target must stay inside the vault.", documents: [] };
|
|
1058
|
+
}
|
|
1059
|
+
let raw;
|
|
1060
|
+
try {
|
|
1061
|
+
raw = readFileSync(path.join(vault, resolvedPath), "utf-8");
|
|
1062
|
+
}
|
|
1063
|
+
catch {
|
|
1064
|
+
return { available: false, reason: `No OMS document matched "${opts.target}".`, documents: [] };
|
|
1065
|
+
}
|
|
1066
|
+
const content = sliceDocLines(raw, {
|
|
1067
|
+
fromLine: opts.fromLine ?? parsed.fromLine,
|
|
1068
|
+
lineCount: opts.lineCount ?? parsed.lineCount,
|
|
1069
|
+
lineNumbers: opts.lineNumbers,
|
|
1070
|
+
});
|
|
1071
|
+
const doc = {
|
|
1072
|
+
target: opts.target,
|
|
1073
|
+
path: opts.fullPath === true ? path.join(vault, resolvedPath) : resolvedPath,
|
|
1074
|
+
content,
|
|
1075
|
+
docid: resolvedPath,
|
|
1076
|
+
title: extractDocTitle(raw),
|
|
1077
|
+
};
|
|
1078
|
+
return { available: true, documents: [doc] };
|
|
1079
|
+
}
|
|
1080
|
+
// -------------------------------------------------------------------------
|
|
1081
|
+
// 10. oms_multi_get — batch file-based hydration (GAP-9)
|
|
1082
|
+
// -------------------------------------------------------------------------
|
|
1083
|
+
/**
|
|
1084
|
+
* Hydrate multiple documents from disk. De-dups by resolved path. Honors
|
|
1085
|
+
* lineLimit per doc and stops early when accumulated bytes would exceed
|
|
1086
|
+
* maxBytes (returns available:true with partial results, mirroring src/search).
|
|
1087
|
+
*/
|
|
1088
|
+
async multiGetDocuments(opts) {
|
|
1089
|
+
const vault = opts.vault ?? this.vaultPath;
|
|
1090
|
+
const documents = [];
|
|
1091
|
+
const seen = new Set();
|
|
1092
|
+
let usedBytes = 0;
|
|
1093
|
+
for (const rawTarget of opts.targets) {
|
|
1094
|
+
const parsed = parseDocTarget(rawTarget);
|
|
1095
|
+
let resolvedPaths;
|
|
1096
|
+
if (parsed.isDocid) {
|
|
1097
|
+
const store = this.deps.store;
|
|
1098
|
+
const matched = store.listDocPaths().find((p) => p === parsed.filePath);
|
|
1099
|
+
resolvedPaths = matched ? [matched] : [];
|
|
1100
|
+
}
|
|
1101
|
+
else if (parsed.isGlob) {
|
|
1102
|
+
resolvedPaths = await globVaultDocs(vault, parsed.filePath);
|
|
1103
|
+
}
|
|
1104
|
+
else {
|
|
1105
|
+
resolvedPaths = [parsed.filePath];
|
|
1106
|
+
}
|
|
1107
|
+
for (const resolvedPath of resolvedPaths) {
|
|
1108
|
+
if (isUnsafeVaultPath(resolvedPath, vault)) {
|
|
1109
|
+
return { available: false, reason: "OMS semantic document target must stay inside the vault.", documents: [] };
|
|
1110
|
+
}
|
|
1111
|
+
if (seen.has(resolvedPath))
|
|
1112
|
+
continue;
|
|
1113
|
+
let raw;
|
|
1114
|
+
try {
|
|
1115
|
+
raw = readFileSync(path.join(vault, resolvedPath), "utf-8");
|
|
1116
|
+
}
|
|
1117
|
+
catch {
|
|
1118
|
+
continue;
|
|
1119
|
+
}
|
|
1120
|
+
const content = sliceDocLines(raw, {
|
|
1121
|
+
fromLine: parsed.fromLine,
|
|
1122
|
+
lineCount: parsed.lineCount,
|
|
1123
|
+
lineLimit: opts.lineLimit,
|
|
1124
|
+
lineNumbers: opts.lineNumbers,
|
|
1125
|
+
});
|
|
1126
|
+
const nextBytes = Buffer.byteLength(content, "utf-8");
|
|
1127
|
+
if (opts.maxBytes && usedBytes + nextBytes > opts.maxBytes) {
|
|
1128
|
+
return { available: true, documents };
|
|
1129
|
+
}
|
|
1130
|
+
seen.add(resolvedPath);
|
|
1131
|
+
usedBytes += nextBytes;
|
|
1132
|
+
documents.push({
|
|
1133
|
+
target: rawTarget,
|
|
1134
|
+
path: opts.fullPath === true ? path.join(vault, resolvedPath) : resolvedPath,
|
|
1135
|
+
content,
|
|
1136
|
+
docid: resolvedPath,
|
|
1137
|
+
title: extractDocTitle(raw),
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
return { available: true, documents };
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
//# sourceMappingURL=facade.js.map
|