@angri450/zvec-grep 0.2.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 +201 -0
- package/README.md +319 -0
- package/README_CN.md +297 -0
- package/dist/authorization/index.d.ts +7 -0
- package/dist/authorization/index.js +7 -0
- package/dist/authorization/index.js.map +1 -0
- package/dist/authorization/manager.d.ts +8 -0
- package/dist/authorization/manager.js +19 -0
- package/dist/authorization/manager.js.map +1 -0
- package/dist/authorization/operation.d.ts +7 -0
- package/dist/authorization/operation.js +50 -0
- package/dist/authorization/operation.js.map +1 -0
- package/dist/authorization/planner.d.ts +21 -0
- package/dist/authorization/planner.js +97 -0
- package/dist/authorization/planner.js.map +1 -0
- package/dist/authorization/prompt.d.ts +12 -0
- package/dist/authorization/prompt.js +63 -0
- package/dist/authorization/prompt.js.map +1 -0
- package/dist/authorization/store.d.ts +20 -0
- package/dist/authorization/store.js +251 -0
- package/dist/authorization/store.js.map +1 -0
- package/dist/authorization/target.d.ts +15 -0
- package/dist/authorization/target.js +49 -0
- package/dist/authorization/target.js.map +1 -0
- package/dist/authorization/types.d.ts +61 -0
- package/dist/authorization/types.js +2 -0
- package/dist/authorization/types.js.map +1 -0
- package/dist/cli/args.d.ts +6 -0
- package/dist/cli/args.js +1255 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/auth.d.ts +21 -0
- package/dist/cli/auth.js +209 -0
- package/dist/cli/auth.js.map +1 -0
- package/dist/cli/commands.d.ts +4 -0
- package/dist/cli/commands.js +801 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/errors.d.ts +9 -0
- package/dist/cli/errors.js +131 -0
- package/dist/cli/errors.js.map +1 -0
- package/dist/cli/format/context.d.ts +9 -0
- package/dist/cli/format/context.js +786 -0
- package/dist/cli/format/context.js.map +1 -0
- package/dist/cli/format/debug.d.ts +4 -0
- package/dist/cli/format/debug.js +60 -0
- package/dist/cli/format/debug.js.map +1 -0
- package/dist/cli/format/highlight.d.ts +3 -0
- package/dist/cli/format/highlight.js +106 -0
- package/dist/cli/format/highlight.js.map +1 -0
- package/dist/cli/format/progress.d.ts +13 -0
- package/dist/cli/format/progress.js +290 -0
- package/dist/cli/format/progress.js.map +1 -0
- package/dist/cli/format/range.d.ts +3 -0
- package/dist/cli/format/range.js +24 -0
- package/dist/cli/format/range.js.map +1 -0
- package/dist/cli/format/status.d.ts +83 -0
- package/dist/cli/format/status.js +530 -0
- package/dist/cli/format/status.js.map +1 -0
- package/dist/cli/help.d.ts +1 -0
- package/dist/cli/help.js +546 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +40 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/install.d.ts +4 -0
- package/dist/cli/install.js +1735 -0
- package/dist/cli/install.js.map +1 -0
- package/dist/cli/managed-rg.d.ts +10 -0
- package/dist/cli/managed-rg.js +337 -0
- package/dist/cli/managed-rg.js.map +1 -0
- package/dist/cli/server-search.d.ts +3 -0
- package/dist/cli/server-search.js +12 -0
- package/dist/cli/server-search.js.map +1 -0
- package/dist/cli/types.d.ts +87 -0
- package/dist/cli/types.js +10 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/cli/version.d.ts +1 -0
- package/dist/cli/version.js +13 -0
- package/dist/cli/version.js.map +1 -0
- package/dist/client/daemon-client.d.ts +31 -0
- package/dist/client/daemon-client.js +203 -0
- package/dist/client/daemon-client.js.map +1 -0
- package/dist/client/mode-router.d.ts +9 -0
- package/dist/client/mode-router.js +28 -0
- package/dist/client/mode-router.js.map +1 -0
- package/dist/client/search-policy.d.ts +11 -0
- package/dist/client/search-policy.js +15 -0
- package/dist/client/search-policy.js.map +1 -0
- package/dist/daemon/backend.d.ts +78 -0
- package/dist/daemon/backend.js +1118 -0
- package/dist/daemon/backend.js.map +1 -0
- package/dist/daemon/change-set.d.ts +30 -0
- package/dist/daemon/change-set.js +163 -0
- package/dist/daemon/change-set.js.map +1 -0
- package/dist/daemon/config.d.ts +27 -0
- package/dist/daemon/config.js +107 -0
- package/dist/daemon/config.js.map +1 -0
- package/dist/daemon/errors.d.ts +5 -0
- package/dist/daemon/errors.js +11 -0
- package/dist/daemon/errors.js.map +1 -0
- package/dist/daemon/http-server.d.ts +30 -0
- package/dist/daemon/http-server.js +307 -0
- package/dist/daemon/http-server.js.map +1 -0
- package/dist/daemon/index-coordinator.d.ts +22 -0
- package/dist/daemon/index-coordinator.js +55 -0
- package/dist/daemon/index-coordinator.js.map +1 -0
- package/dist/daemon/job-scheduler.d.ts +85 -0
- package/dist/daemon/job-scheduler.js +492 -0
- package/dist/daemon/job-scheduler.js.map +1 -0
- package/dist/daemon/logger.d.ts +10 -0
- package/dist/daemon/logger.js +101 -0
- package/dist/daemon/logger.js.map +1 -0
- package/dist/daemon/model-pool.d.ts +48 -0
- package/dist/daemon/model-pool.js +190 -0
- package/dist/daemon/model-pool.js.map +1 -0
- package/dist/daemon/root-lease.d.ts +14 -0
- package/dist/daemon/root-lease.js +197 -0
- package/dist/daemon/root-lease.js.map +1 -0
- package/dist/daemon/root-runtime.d.ts +80 -0
- package/dist/daemon/root-runtime.js +340 -0
- package/dist/daemon/root-runtime.js.map +1 -0
- package/dist/daemon/runtime-manager.d.ts +47 -0
- package/dist/daemon/runtime-manager.js +252 -0
- package/dist/daemon/runtime-manager.js.map +1 -0
- package/dist/daemon/runtime.d.ts +12 -0
- package/dist/daemon/runtime.js +105 -0
- package/dist/daemon/runtime.js.map +1 -0
- package/dist/daemon/server-controller.d.ts +38 -0
- package/dist/daemon/server-controller.js +327 -0
- package/dist/daemon/server-controller.js.map +1 -0
- package/dist/daemon/watch-manager.d.ts +62 -0
- package/dist/daemon/watch-manager.js +415 -0
- package/dist/daemon/watch-manager.js.map +1 -0
- package/dist/daemon/workspace-read-session-cache.d.ts +31 -0
- package/dist/daemon/workspace-read-session-cache.js +115 -0
- package/dist/daemon/workspace-read-session-cache.js.map +1 -0
- package/dist/engine/code-formats.d.ts +3 -0
- package/dist/engine/code-formats.js +14 -0
- package/dist/engine/code-formats.js.map +1 -0
- package/dist/engine/config.d.ts +57 -0
- package/dist/engine/config.js +351 -0
- package/dist/engine/config.js.map +1 -0
- package/dist/engine/errors.d.ts +20 -0
- package/dist/engine/errors.js +51 -0
- package/dist/engine/errors.js.map +1 -0
- package/dist/engine/extraction/code/adapter.d.ts +19 -0
- package/dist/engine/extraction/code/adapter.js +24 -0
- package/dist/engine/extraction/code/adapter.js.map +1 -0
- package/dist/engine/extraction/code/extractor.d.ts +13 -0
- package/dist/engine/extraction/code/extractor.js +696 -0
- package/dist/engine/extraction/code/extractor.js.map +1 -0
- package/dist/engine/extraction/code/families/c-family.d.ts +2 -0
- package/dist/engine/extraction/code/families/c-family.js +122 -0
- package/dist/engine/extraction/code/families/c-family.js.map +1 -0
- package/dist/engine/extraction/code/families/js-ts.d.ts +12 -0
- package/dist/engine/extraction/code/families/js-ts.js +118 -0
- package/dist/engine/extraction/code/families/js-ts.js.map +1 -0
- package/dist/engine/extraction/code/families/metadata.d.ts +8 -0
- package/dist/engine/extraction/code/families/metadata.js +86 -0
- package/dist/engine/extraction/code/families/metadata.js.map +1 -0
- package/dist/engine/extraction/code/families/name-field.d.ts +3 -0
- package/dist/engine/extraction/code/families/name-field.js +22 -0
- package/dist/engine/extraction/code/families/name-field.js.map +1 -0
- package/dist/engine/extraction/code/languages/c.d.ts +1 -0
- package/dist/engine/extraction/code/languages/c.js +12 -0
- package/dist/engine/extraction/code/languages/c.js.map +1 -0
- package/dist/engine/extraction/code/languages/cpp.d.ts +1 -0
- package/dist/engine/extraction/code/languages/cpp.js +18 -0
- package/dist/engine/extraction/code/languages/cpp.js.map +1 -0
- package/dist/engine/extraction/code/languages/go.d.ts +2 -0
- package/dist/engine/extraction/code/languages/go.js +70 -0
- package/dist/engine/extraction/code/languages/go.js.map +1 -0
- package/dist/engine/extraction/code/languages/java.d.ts +1 -0
- package/dist/engine/extraction/code/languages/java.js +17 -0
- package/dist/engine/extraction/code/languages/java.js.map +1 -0
- package/dist/engine/extraction/code/languages/javascript.d.ts +2 -0
- package/dist/engine/extraction/code/languages/javascript.js +23 -0
- package/dist/engine/extraction/code/languages/javascript.js.map +1 -0
- package/dist/engine/extraction/code/languages/python.d.ts +2 -0
- package/dist/engine/extraction/code/languages/python.js +52 -0
- package/dist/engine/extraction/code/languages/python.js.map +1 -0
- package/dist/engine/extraction/code/languages/rust.d.ts +2 -0
- package/dist/engine/extraction/code/languages/rust.js +25 -0
- package/dist/engine/extraction/code/languages/rust.js.map +1 -0
- package/dist/engine/extraction/code/languages/typescript.d.ts +2 -0
- package/dist/engine/extraction/code/languages/typescript.js +37 -0
- package/dist/engine/extraction/code/languages/typescript.js.map +1 -0
- package/dist/engine/extraction/code/tree-sitter/grammar.d.ts +4 -0
- package/dist/engine/extraction/code/tree-sitter/grammar.js +49 -0
- package/dist/engine/extraction/code/tree-sitter/grammar.js.map +1 -0
- package/dist/engine/extraction/code/tree-sitter/nodes.d.ts +3 -0
- package/dist/engine/extraction/code/tree-sitter/nodes.js +27 -0
- package/dist/engine/extraction/code/tree-sitter/nodes.js.map +1 -0
- package/dist/engine/extraction/code/tree-sitter/parser.d.ts +2 -0
- package/dist/engine/extraction/code/tree-sitter/parser.js +31 -0
- package/dist/engine/extraction/code/tree-sitter/parser.js.map +1 -0
- package/dist/engine/extraction/ids.d.ts +1 -0
- package/dist/engine/extraction/ids.js +5 -0
- package/dist/engine/extraction/ids.js.map +1 -0
- package/dist/engine/extraction/image/extractor.d.ts +5 -0
- package/dist/engine/extraction/image/extractor.js +35 -0
- package/dist/engine/extraction/image/extractor.js.map +1 -0
- package/dist/engine/extraction/index.d.ts +4 -0
- package/dist/engine/extraction/index.js +3 -0
- package/dist/engine/extraction/index.js.map +1 -0
- package/dist/engine/extraction/markdown/extractor.d.ts +7 -0
- package/dist/engine/extraction/markdown/extractor.js +362 -0
- package/dist/engine/extraction/markdown/extractor.js.map +1 -0
- package/dist/engine/extraction/runtime.d.ts +9 -0
- package/dist/engine/extraction/runtime.js +37 -0
- package/dist/engine/extraction/runtime.js.map +1 -0
- package/dist/engine/extraction/source.d.ts +18 -0
- package/dist/engine/extraction/source.js +22 -0
- package/dist/engine/extraction/source.js.map +1 -0
- package/dist/engine/extraction/text/extractor.d.ts +7 -0
- package/dist/engine/extraction/text/extractor.js +168 -0
- package/dist/engine/extraction/text/extractor.js.map +1 -0
- package/dist/engine/extraction/types.d.ts +4 -0
- package/dist/engine/extraction/types.js +2 -0
- package/dist/engine/extraction/types.js.map +1 -0
- package/dist/engine/extraction/vector-content.d.ts +5 -0
- package/dist/engine/extraction/vector-content.js +83 -0
- package/dist/engine/extraction/vector-content.js.map +1 -0
- package/dist/engine/file-size-policy.d.ts +6 -0
- package/dist/engine/file-size-policy.js +20 -0
- package/dist/engine/file-size-policy.js.map +1 -0
- package/dist/engine/file-type.d.ts +15 -0
- package/dist/engine/file-type.js +152 -0
- package/dist/engine/file-type.js.map +1 -0
- package/dist/engine/index-status.d.ts +9 -0
- package/dist/engine/index-status.js +38 -0
- package/dist/engine/index-status.js.map +1 -0
- package/dist/engine/manifest.d.ts +13 -0
- package/dist/engine/manifest.js +124 -0
- package/dist/engine/manifest.js.map +1 -0
- package/dist/engine/models/artifact-cache-lock.d.ts +16 -0
- package/dist/engine/models/artifact-cache-lock.js +228 -0
- package/dist/engine/models/artifact-cache-lock.js.map +1 -0
- package/dist/engine/models/artifact-downloader.d.ts +77 -0
- package/dist/engine/models/artifact-downloader.js +766 -0
- package/dist/engine/models/artifact-downloader.js.map +1 -0
- package/dist/engine/models/backends/llama-cpp.d.ts +100 -0
- package/dist/engine/models/backends/llama-cpp.js +567 -0
- package/dist/engine/models/backends/llama-cpp.js.map +1 -0
- package/dist/engine/models/backends/model2vec-runtime.d.ts +63 -0
- package/dist/engine/models/backends/model2vec-runtime.js +112 -0
- package/dist/engine/models/backends/model2vec-runtime.js.map +1 -0
- package/dist/engine/models/backends/model2vec-tokenizer.d.ts +2 -0
- package/dist/engine/models/backends/model2vec-tokenizer.js +42 -0
- package/dist/engine/models/backends/model2vec-tokenizer.js.map +1 -0
- package/dist/engine/models/backends/model2vec-worker-pool.d.ts +25 -0
- package/dist/engine/models/backends/model2vec-worker-pool.js +264 -0
- package/dist/engine/models/backends/model2vec-worker-pool.js.map +1 -0
- package/dist/engine/models/backends/model2vec-worker.d.ts +1 -0
- package/dist/engine/models/backends/model2vec-worker.js +41 -0
- package/dist/engine/models/backends/model2vec-worker.js.map +1 -0
- package/dist/engine/models/backends/model2vec.d.ts +38 -0
- package/dist/engine/models/backends/model2vec.js +344 -0
- package/dist/engine/models/backends/model2vec.js.map +1 -0
- package/dist/engine/models/backends/qwen.d.ts +44 -0
- package/dist/engine/models/backends/qwen.js +411 -0
- package/dist/engine/models/backends/qwen.js.map +1 -0
- package/dist/engine/models/backends/transformers-js.d.ts +90 -0
- package/dist/engine/models/backends/transformers-js.js +411 -0
- package/dist/engine/models/backends/transformers-js.js.map +1 -0
- package/dist/engine/models/catalog.d.ts +490 -0
- package/dist/engine/models/catalog.js +522 -0
- package/dist/engine/models/catalog.js.map +1 -0
- package/dist/engine/models/download-progress.d.ts +16 -0
- package/dist/engine/models/download-progress.js +47 -0
- package/dist/engine/models/download-progress.js.map +1 -0
- package/dist/engine/models/embeddings.d.ts +73 -0
- package/dist/engine/models/embeddings.js +125 -0
- package/dist/engine/models/embeddings.js.map +1 -0
- package/dist/engine/models/factory.d.ts +2 -0
- package/dist/engine/models/factory.js +51 -0
- package/dist/engine/models/factory.js.map +1 -0
- package/dist/engine/models/index.d.ts +5 -0
- package/dist/engine/models/index.js +6 -0
- package/dist/engine/models/index.js.map +1 -0
- package/dist/engine/models/local-embedding-parallelism.d.ts +15 -0
- package/dist/engine/models/local-embedding-parallelism.js +63 -0
- package/dist/engine/models/local-embedding-parallelism.js.map +1 -0
- package/dist/engine/models/local-embedding-queue.d.ts +12 -0
- package/dist/engine/models/local-embedding-queue.js +65 -0
- package/dist/engine/models/local-embedding-queue.js.map +1 -0
- package/dist/engine/models/ranking.d.ts +19 -0
- package/dist/engine/models/ranking.js +2 -0
- package/dist/engine/models/ranking.js.map +1 -0
- package/dist/engine/models/resolution.d.ts +8 -0
- package/dist/engine/models/resolution.js +22 -0
- package/dist/engine/models/resolution.js.map +1 -0
- package/dist/engine/pipeline/indexing/index.d.ts +14 -0
- package/dist/engine/pipeline/indexing/index.js +1219 -0
- package/dist/engine/pipeline/indexing/index.js.map +1 -0
- package/dist/engine/pipeline/indexing/input-budget.d.ts +2 -0
- package/dist/engine/pipeline/indexing/input-budget.js +51 -0
- package/dist/engine/pipeline/indexing/input-budget.js.map +1 -0
- package/dist/engine/pipeline/indexing/root-paths.d.ts +7 -0
- package/dist/engine/pipeline/indexing/root-paths.js +148 -0
- package/dist/engine/pipeline/indexing/root-paths.js.map +1 -0
- package/dist/engine/pipeline/indexing/scanner/index.d.ts +14 -0
- package/dist/engine/pipeline/indexing/scanner/index.js +776 -0
- package/dist/engine/pipeline/indexing/scanner/index.js.map +1 -0
- package/dist/engine/pipeline/search/index.d.ts +12 -0
- package/dist/engine/pipeline/search/index.js +811 -0
- package/dist/engine/pipeline/search/index.js.map +1 -0
- package/dist/engine/service/index.d.ts +3 -0
- package/dist/engine/service/index.js +2 -0
- package/dist/engine/service/index.js.map +1 -0
- package/dist/engine/service/lexical.d.ts +28 -0
- package/dist/engine/service/lexical.js +446 -0
- package/dist/engine/service/lexical.js.map +1 -0
- package/dist/engine/service/root.d.ts +15 -0
- package/dist/engine/service/root.js +57 -0
- package/dist/engine/service/root.js.map +1 -0
- package/dist/engine/service/structure-enrichment.d.ts +8 -0
- package/dist/engine/service/structure-enrichment.js +178 -0
- package/dist/engine/service/structure-enrichment.js.map +1 -0
- package/dist/engine/service/types.d.ts +212 -0
- package/dist/engine/service/types.js +2 -0
- package/dist/engine/service/types.js.map +1 -0
- package/dist/engine/service/workspace-index.d.ts +26 -0
- package/dist/engine/service/workspace-index.js +157 -0
- package/dist/engine/service/workspace-index.js.map +1 -0
- package/dist/engine/service/zvec-grep.d.ts +15 -0
- package/dist/engine/service/zvec-grep.js +1520 -0
- package/dist/engine/service/zvec-grep.js.map +1 -0
- package/dist/engine/storage/index.d.ts +53 -0
- package/dist/engine/storage/index.js +3 -0
- package/dist/engine/storage/index.js.map +1 -0
- package/dist/engine/storage/layout.d.ts +10 -0
- package/dist/engine/storage/layout.js +29 -0
- package/dist/engine/storage/layout.js.map +1 -0
- package/dist/engine/storage/zvec.d.ts +4 -0
- package/dist/engine/storage/zvec.js +976 -0
- package/dist/engine/storage/zvec.js.map +1 -0
- package/dist/engine/types.d.ts +303 -0
- package/dist/engine/types.js +14 -0
- package/dist/engine/types.js.map +1 -0
- package/dist/engine/utils/atomic-file.d.ts +3 -0
- package/dist/engine/utils/atomic-file.js +39 -0
- package/dist/engine/utils/atomic-file.js.map +1 -0
- package/dist/engine/utils/daemon-lease.d.ts +17 -0
- package/dist/engine/utils/daemon-lease.js +175 -0
- package/dist/engine/utils/daemon-lease.js.map +1 -0
- package/dist/engine/utils/file-selection.d.ts +10 -0
- package/dist/engine/utils/file-selection.js +157 -0
- package/dist/engine/utils/file-selection.js.map +1 -0
- package/dist/engine/utils/glob.d.ts +9 -0
- package/dist/engine/utils/glob.js +208 -0
- package/dist/engine/utils/glob.js.map +1 -0
- package/dist/engine/utils/hash.d.ts +2 -0
- package/dist/engine/utils/hash.js +8 -0
- package/dist/engine/utils/hash.js.map +1 -0
- package/dist/engine/utils/json.d.ts +8 -0
- package/dist/engine/utils/json.js +83 -0
- package/dist/engine/utils/json.js.map +1 -0
- package/dist/engine/utils/lock.d.ts +19 -0
- package/dist/engine/utils/lock.js +254 -0
- package/dist/engine/utils/lock.js.map +1 -0
- package/dist/engine/utils/path.d.ts +4 -0
- package/dist/engine/utils/path.js +18 -0
- package/dist/engine/utils/path.js.map +1 -0
- package/dist/engine/utils/timing.d.ts +19 -0
- package/dist/engine/utils/timing.js +87 -0
- package/dist/engine/utils/timing.js.map +1 -0
- package/dist/index-progress.d.ts +10 -0
- package/dist/index-progress.js +159 -0
- package/dist/index-progress.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/http-transport.d.ts +27 -0
- package/dist/mcp/http-transport.js +182 -0
- package/dist/mcp/http-transport.js.map +1 -0
- package/dist/mcp/input-normalization.d.ts +38 -0
- package/dist/mcp/input-normalization.js +104 -0
- package/dist/mcp/input-normalization.js.map +1 -0
- package/dist/mcp/progress-heartbeat.d.ts +17 -0
- package/dist/mcp/progress-heartbeat.js +46 -0
- package/dist/mcp/progress-heartbeat.js.map +1 -0
- package/dist/mcp/request-metadata.d.ts +2 -0
- package/dist/mcp/request-metadata.js +11 -0
- package/dist/mcp/request-metadata.js.map +1 -0
- package/dist/mcp/request-state.d.ts +40 -0
- package/dist/mcp/request-state.js +210 -0
- package/dist/mcp/request-state.js.map +1 -0
- package/dist/mcp/result-format.d.ts +13 -0
- package/dist/mcp/result-format.js +75 -0
- package/dist/mcp/result-format.js.map +1 -0
- package/dist/mcp/schemas.d.ts +668 -0
- package/dist/mcp/schemas.js +512 -0
- package/dist/mcp/schemas.js.map +1 -0
- package/dist/mcp/stdio-bridge.d.ts +15 -0
- package/dist/mcp/stdio-bridge.js +190 -0
- package/dist/mcp/stdio-bridge.js.map +1 -0
- package/dist/mcp/tools.d.ts +153 -0
- package/dist/mcp/tools.js +488 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/mcp/toolset.d.ts +5 -0
- package/dist/mcp/toolset.js +12 -0
- package/dist/mcp/toolset.js.map +1 -0
- package/dist/observability/trace-context.d.ts +11 -0
- package/dist/observability/trace-context.js +138 -0
- package/dist/observability/trace-context.js.map +1 -0
- package/dist/prompts/zvec-grep-guidance.d.ts +2 -0
- package/dist/prompts/zvec-grep-guidance.js +9 -0
- package/dist/prompts/zvec-grep-guidance.js.map +1 -0
- package/docs/01-agents.md +217 -0
- package/docs/02-cli.md +259 -0
- package/docs/03-mcp.md +185 -0
- package/docs/04-pipeline.md +234 -0
- package/docs/05-architecture.md +86 -0
- package/docs/06-server.md +279 -0
- package/docs/07-embedding.md +227 -0
- package/docs/08-roadmap.md +85 -0
- package/docs/README.md +53 -0
- package/package.json +87 -0
|
@@ -0,0 +1,1520 @@
|
|
|
1
|
+
import { readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { createHash, createHmac, randomBytes, randomUUID } from "node:crypto";
|
|
3
|
+
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
4
|
+
import { globalConfigPath, readGlobalConfig, resolveEmbeddingRuntimeOptions, } from "../config.js";
|
|
5
|
+
import { isWorkspaceIndexed, WorkspaceIndex } from "./workspace-index.js";
|
|
6
|
+
import { workspaceIndexDetail, detail, EngineError, errorDetails, } from "../errors.js";
|
|
7
|
+
import { createEmbeddingModel, EmbeddingPurpose, getEmbeddingModelCatalogEntry, normalizeLocalEmbeddingConcurrency, resolveEmbeddingReference, resolveIndexEmbeddingConcurrencyOverride, } from "../models/index.js";
|
|
8
|
+
import { CURRENT_INDEX_VERSION } from "../types.js";
|
|
9
|
+
import { indexStatusNeedsRefresh } from "../index-status.js";
|
|
10
|
+
import { workspaceIndexInfoFromManifest, CURRENT_MANIFEST_VERSION, readWorkspaceManifest, writeWorkspaceManifest, } from "../manifest.js";
|
|
11
|
+
import { validateRootPaths } from "../pipeline/indexing/root-paths.js";
|
|
12
|
+
import { findNearestWorkspace, hasWorkspaceIndex, resetWorkspaceIndex, resolveZvecGrepRoot, workspaceHome, workspaceIndexLocation, } from "./root.js";
|
|
13
|
+
import { runRgSearch } from "./lexical.js";
|
|
14
|
+
import { defaultHome, normalizePath } from "../utils/path.js";
|
|
15
|
+
import { acquireReadWriteLock, assertNoWriteLock, } from "../utils/lock.js";
|
|
16
|
+
import { assertDaemonWriteAllowed } from "../utils/daemon-lease.js";
|
|
17
|
+
import { TimingCollector } from "../utils/timing.js";
|
|
18
|
+
import { enrichLexicalItemsWithStructure } from "./structure-enrichment.js";
|
|
19
|
+
import { remoteEmbeddingAuthorizationGuard } from "../../authorization/operation.js";
|
|
20
|
+
import { RemoteEmbeddingAuthorizationStore } from "../../authorization/store.js";
|
|
21
|
+
const DEFAULT_CONTEXT_LIMIT = 10;
|
|
22
|
+
const DEFAULT_CONTEXT_TOTAL_LIMIT = 30;
|
|
23
|
+
const DEFAULT_LOCAL_EMBEDDING = "local/potion-code-16m-v2";
|
|
24
|
+
const PROVIDER_API_KEY_IDENTITY_SECRET = randomBytes(32);
|
|
25
|
+
const MAX_RECOVERED_EMBEDDING_MODELS = 4;
|
|
26
|
+
export async function createZvecGrep(options = {}) {
|
|
27
|
+
return new ZvecGrepService(options);
|
|
28
|
+
}
|
|
29
|
+
export function openWorkspaceReadSession(startRoot, embeddingModel) {
|
|
30
|
+
const start = resolveZvecGrepRoot(startRoot);
|
|
31
|
+
assertNearestWorkspaceHomeUnlocked(start, "daemon.context.open");
|
|
32
|
+
const nearest = findNearestWorkspaceIndex(start);
|
|
33
|
+
if (!nearest) {
|
|
34
|
+
throw workspaceIndexMissingError(start, "undecided");
|
|
35
|
+
}
|
|
36
|
+
const { location, info } = nearest;
|
|
37
|
+
if (info.indexPolicy === "disabled") {
|
|
38
|
+
throw workspaceIndexDisabledError(location.root);
|
|
39
|
+
}
|
|
40
|
+
if (!isWorkspaceIndexed(info) || !hasWorkspaceIndex(location)) {
|
|
41
|
+
throw workspaceIndexMissingError(location.root, info.indexPolicy ?? "enabled");
|
|
42
|
+
}
|
|
43
|
+
const workspaceIndex = new WorkspaceIndex(info, {
|
|
44
|
+
mode: "read",
|
|
45
|
+
embeddingModel,
|
|
46
|
+
});
|
|
47
|
+
let closed = false;
|
|
48
|
+
return {
|
|
49
|
+
root: location.root,
|
|
50
|
+
async context(options) {
|
|
51
|
+
if (closed) {
|
|
52
|
+
throw new EngineError("Workspace read session is already closed", {
|
|
53
|
+
code: "ZVEC_GREP.ENGINE.SERVICE.READ_SESSION_CLOSED",
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const timings = new TimingCollector();
|
|
57
|
+
const request = normalizeContextRequest(options);
|
|
58
|
+
const result = await timings.time("total", () => withHomeReadLock(location.home, "daemon.context", () => contextFromOpenWorkspaceIndex({
|
|
59
|
+
root: location.root,
|
|
60
|
+
request,
|
|
61
|
+
workspaceIndex,
|
|
62
|
+
options: { ...options, autoUpdate: false },
|
|
63
|
+
timings,
|
|
64
|
+
})));
|
|
65
|
+
return withContextTimings(result, timings);
|
|
66
|
+
},
|
|
67
|
+
async close() {
|
|
68
|
+
if (closed) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
workspaceIndex.close();
|
|
72
|
+
closed = true;
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export function createEmbeddingModelForIdentity(identity, options = {}, workspaceRuntime = {}) {
|
|
77
|
+
const reference = embeddingModelReference(identity);
|
|
78
|
+
return createServiceEmbeddingModel(reference, providerOptions(options, identity, readGlobalConfig(), workspaceRuntime, options.embeddingConcurrency), options);
|
|
79
|
+
}
|
|
80
|
+
export function embeddingModelPoolKeyForIdentity(identity, options = {}, workspaceRuntime = {}) {
|
|
81
|
+
const reference = embeddingModelReference(identity);
|
|
82
|
+
const fingerprint = providerOptionsFingerprint(providerOptions(options, identity, readGlobalConfig(), workspaceRuntime, options.embeddingConcurrency));
|
|
83
|
+
return [reference, fingerprint].join("/");
|
|
84
|
+
}
|
|
85
|
+
class ZvecGrepService {
|
|
86
|
+
options;
|
|
87
|
+
root;
|
|
88
|
+
embeddingModel;
|
|
89
|
+
recoveredEmbeddingModels = new Map();
|
|
90
|
+
retiredEmbeddingModels = new Set();
|
|
91
|
+
localEmbeddingVariants = new Map();
|
|
92
|
+
modelSelectionPromise = Promise.resolve();
|
|
93
|
+
modelDisposalPromise = Promise.resolve();
|
|
94
|
+
activeEmbeddingModelOperations = 0;
|
|
95
|
+
closed = false;
|
|
96
|
+
constructor(options) {
|
|
97
|
+
this.options = options;
|
|
98
|
+
this.root = resolveZvecGrepRoot(options.root);
|
|
99
|
+
this.embeddingModel = options.embeddingModel;
|
|
100
|
+
}
|
|
101
|
+
async index(options = {}) {
|
|
102
|
+
this.ensureOpen();
|
|
103
|
+
const root = resolveZvecGrepRoot(options.root ?? this.root);
|
|
104
|
+
const daemonWritePermit = assertDaemonWriteAllowed(root, this.options.daemonInstanceToken);
|
|
105
|
+
const location = workspaceIndexLocation(root);
|
|
106
|
+
try {
|
|
107
|
+
return await this.withEmbeddingModelOperation(() => withHomeWriteLock(location.home, options.rebuild ? "index.rebuild" : "index", async () => {
|
|
108
|
+
const existing = readWorkspaceManifest(location.home);
|
|
109
|
+
const existingRuntime = existing?.embeddingRuntime ?? {};
|
|
110
|
+
const { model: embeddingModel, concurrency: embeddingConcurrency } = await this.embeddingModelForIndex(existing, "index", existingRuntime, options.embeddingConcurrency);
|
|
111
|
+
const effectiveRuntime = effectiveEmbeddingRuntime(this.options, embeddingModel, runtimeForModelProvider(existing, embeddingModel, existingRuntime));
|
|
112
|
+
if (!options.rebuild) {
|
|
113
|
+
assertWorkspaceEndpointMatchesCurrentRuntime(existing, existingRuntime, effectiveRuntime, "zg --index --endpoint <url> --rebuild");
|
|
114
|
+
}
|
|
115
|
+
const rootPaths = resolveIndexRootPaths(existing, options.rootPaths, root, {
|
|
116
|
+
resetPaths: options.resetPaths === true,
|
|
117
|
+
includePaths: options.includePaths,
|
|
118
|
+
excludePaths: options.excludePaths,
|
|
119
|
+
globs: options.globs,
|
|
120
|
+
insensitiveGlobs: options.insensitiveGlobs,
|
|
121
|
+
fileTypes: options.fileTypes,
|
|
122
|
+
excludedFileTypes: options.excludedFileTypes,
|
|
123
|
+
hidden: options.hidden,
|
|
124
|
+
noIgnore: options.noIgnore,
|
|
125
|
+
ignoreFiles: options.ignoreFiles,
|
|
126
|
+
maxDepth: options.maxDepth,
|
|
127
|
+
maxFileSizeBytes: options.maxFileSizeBytes,
|
|
128
|
+
follow: options.follow,
|
|
129
|
+
});
|
|
130
|
+
if (options.rebuild || !isWorkspaceIndexed(existing)) {
|
|
131
|
+
resetWorkspaceIndex(location);
|
|
132
|
+
}
|
|
133
|
+
const existingAfterRebuild = options.rebuild ? null : existing;
|
|
134
|
+
if (isWorkspaceIndexed(existingAfterRebuild)) {
|
|
135
|
+
assertWorkspaceEmbeddingMatchesCurrentModel(existingAfterRebuild, embeddingModel, "zg --index --rebuild");
|
|
136
|
+
}
|
|
137
|
+
const embeddingRuntime = embeddingRuntimeAfterIndex(existing, existingRuntime, embeddingModel, effectiveRuntime, this.options);
|
|
138
|
+
const manifest = prepareWorkspaceManifest(location, existingAfterRebuild, rootPaths, embeddingModel, embeddingRuntime);
|
|
139
|
+
const workspaceIndex = new WorkspaceIndex(manifest, {
|
|
140
|
+
mode: "write",
|
|
141
|
+
embeddingModel,
|
|
142
|
+
});
|
|
143
|
+
writeWorkspaceManifest(location.home, manifest);
|
|
144
|
+
try {
|
|
145
|
+
const releaseWriterContext = options.onWriterContext?.((contextOptions) => this.contextFromWriterWorkspaceIndex(root, workspaceIndex, contextOptions));
|
|
146
|
+
try {
|
|
147
|
+
const result = await workspaceIndex.index({
|
|
148
|
+
rebuild: false,
|
|
149
|
+
embeddingConcurrency,
|
|
150
|
+
onProgress: options.onProgress,
|
|
151
|
+
changedPaths: options.changedPaths,
|
|
152
|
+
signal: options.signal,
|
|
153
|
+
});
|
|
154
|
+
writeWorkspaceManifest(location.home, {
|
|
155
|
+
...manifest,
|
|
156
|
+
embeddingRuntime,
|
|
157
|
+
updatedTime: Date.now(),
|
|
158
|
+
});
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
writeWorkspaceManifest(location.home, {
|
|
163
|
+
...manifest,
|
|
164
|
+
embeddingRuntime,
|
|
165
|
+
updatedTime: Date.now(),
|
|
166
|
+
});
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
finally {
|
|
170
|
+
await releaseWriterContext?.();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
workspaceIndex.close();
|
|
175
|
+
}
|
|
176
|
+
}));
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
daemonWritePermit?.release();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async dropIndex(options = {}) {
|
|
183
|
+
this.ensureOpen();
|
|
184
|
+
const root = resolveZvecGrepRoot(options.root ?? this.root);
|
|
185
|
+
const daemonWritePermit = assertDaemonWriteAllowed(root, this.options.daemonInstanceToken);
|
|
186
|
+
const location = workspaceIndexLocation(root);
|
|
187
|
+
try {
|
|
188
|
+
if (!readWorkspaceManifest(location.home)) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
return await withHomeWriteLock(location.home, "index.drop", async () => {
|
|
192
|
+
resetWorkspaceIndex(location);
|
|
193
|
+
return true;
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
finally {
|
|
197
|
+
daemonWritePermit?.release();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
async disableIndex(options = {}) {
|
|
201
|
+
this.ensureOpen();
|
|
202
|
+
const root = resolveZvecGrepRoot(options.root ?? this.root);
|
|
203
|
+
const daemonWritePermit = assertDaemonWriteAllowed(root, this.options.daemonInstanceToken);
|
|
204
|
+
const location = workspaceIndexLocation(root);
|
|
205
|
+
try {
|
|
206
|
+
await withHomeWriteLock(location.home, "index.disable", async () => {
|
|
207
|
+
const existing = readWorkspaceManifest(location.home);
|
|
208
|
+
const now = Date.now();
|
|
209
|
+
writeWorkspaceManifest(location.home, {
|
|
210
|
+
manifestVersion: CURRENT_MANIFEST_VERSION,
|
|
211
|
+
id: existing?.id ?? randomUUID(),
|
|
212
|
+
name: existing?.name ?? workspaceDisplayName(root),
|
|
213
|
+
path: location.home,
|
|
214
|
+
rootPaths: existing?.rootPaths ?? validateRootPaths([root]),
|
|
215
|
+
indexPolicy: "disabled",
|
|
216
|
+
embedding: null,
|
|
217
|
+
indexVersion: null,
|
|
218
|
+
createdTime: existing?.createdTime ?? now,
|
|
219
|
+
updatedTime: now,
|
|
220
|
+
embeddingRuntime: existing?.embeddingRuntime ?? {},
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
return this.info({ root });
|
|
224
|
+
}
|
|
225
|
+
finally {
|
|
226
|
+
daemonWritePermit?.release();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
async context(options) {
|
|
230
|
+
this.ensureOpen();
|
|
231
|
+
return await this.withEmbeddingModelOperation(async () => {
|
|
232
|
+
const timings = new TimingCollector();
|
|
233
|
+
const result = await timings.time("total", () => this.contextWithTimings(options, timings));
|
|
234
|
+
return withContextTimings(result, timings);
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
async contextWithTimings(options, timings) {
|
|
238
|
+
const request = normalizeContextRequest(options);
|
|
239
|
+
const startRoot = resolveZvecGrepRoot(options.root ?? this.root);
|
|
240
|
+
if (options.rg) {
|
|
241
|
+
return this.contextFromRg(startRoot, request, options, timings);
|
|
242
|
+
}
|
|
243
|
+
assertNearestWorkspaceHomeUnlocked(startRoot, "context");
|
|
244
|
+
const nearest = findNearestWorkspaceIndex(startRoot);
|
|
245
|
+
if (nearest) {
|
|
246
|
+
const { location, info } = nearest;
|
|
247
|
+
if (info.indexPolicy === "disabled") {
|
|
248
|
+
throw workspaceIndexDisabledError(location.root);
|
|
249
|
+
}
|
|
250
|
+
if (!isWorkspaceIndexed(info) || !hasWorkspaceIndex(location)) {
|
|
251
|
+
throw workspaceIndexMissingError(location.root, info.indexPolicy ?? "enabled");
|
|
252
|
+
}
|
|
253
|
+
if (options.autoUpdate !== false) {
|
|
254
|
+
await this.refreshWorkspaceIndexForContext(location, options, timings);
|
|
255
|
+
}
|
|
256
|
+
return await withHomeReadLock(location.home, "context", () => this.contextFromWorkspaceIndex(location, request, options, timings));
|
|
257
|
+
}
|
|
258
|
+
throw workspaceIndexMissingError(startRoot, "undecided");
|
|
259
|
+
}
|
|
260
|
+
async info(options = {}) {
|
|
261
|
+
this.ensureOpen();
|
|
262
|
+
const startRoot = resolveZvecGrepRoot(options.root ?? this.root);
|
|
263
|
+
assertNearestWorkspaceHomeUnlocked(startRoot, "info");
|
|
264
|
+
const nearest = findNearestWorkspaceIndex(startRoot);
|
|
265
|
+
if (!nearest) {
|
|
266
|
+
const location = workspaceIndexLocation(startRoot);
|
|
267
|
+
return {
|
|
268
|
+
root: startRoot,
|
|
269
|
+
indexed: false,
|
|
270
|
+
indexPolicy: "undecided",
|
|
271
|
+
home: location.home,
|
|
272
|
+
indexPath: location.indexPath,
|
|
273
|
+
source: "unindexed",
|
|
274
|
+
suggestion: "zg --index or zg --rg",
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
return await withHomeReadLock(nearest.location.home, "info", async () => {
|
|
278
|
+
const workspaceIndex = readWorkspaceManifest(nearest.location.home);
|
|
279
|
+
const indexed = workspaceIndex !== null &&
|
|
280
|
+
workspaceIndex.indexPolicy !== "disabled" &&
|
|
281
|
+
isWorkspaceIndexed(workspaceIndex) &&
|
|
282
|
+
hasWorkspaceIndex(nearest.location);
|
|
283
|
+
return {
|
|
284
|
+
root: nearest.location.root,
|
|
285
|
+
indexed,
|
|
286
|
+
indexPolicy: workspaceIndex?.indexPolicy ?? "undecided",
|
|
287
|
+
home: nearest.location.home,
|
|
288
|
+
indexPath: nearest.location.indexPath,
|
|
289
|
+
source: indexed ? "index" : "unindexed",
|
|
290
|
+
workspaceIndex: workspaceIndex
|
|
291
|
+
? workspaceIndexInfoFromManifest(workspaceIndex)
|
|
292
|
+
: undefined,
|
|
293
|
+
status: indexed && options.includeStatus !== false
|
|
294
|
+
? await workspaceIndexStatus(workspaceIndex, nearest.location)
|
|
295
|
+
: null,
|
|
296
|
+
suggestion: workspaceInfoSuggestion(workspaceIndex),
|
|
297
|
+
};
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
async close() {
|
|
301
|
+
this.closed = true;
|
|
302
|
+
await this.modelSelectionPromise;
|
|
303
|
+
await this.modelDisposalPromise;
|
|
304
|
+
const models = new Set([
|
|
305
|
+
...(this.embeddingModel &&
|
|
306
|
+
this.options.embeddingModelOwnership !== "borrowed"
|
|
307
|
+
? [this.embeddingModel]
|
|
308
|
+
: []),
|
|
309
|
+
...this.recoveredEmbeddingModels.values(),
|
|
310
|
+
...this.retiredEmbeddingModels,
|
|
311
|
+
]);
|
|
312
|
+
this.recoveredEmbeddingModels.clear();
|
|
313
|
+
this.retiredEmbeddingModels.clear();
|
|
314
|
+
this.localEmbeddingVariants.clear();
|
|
315
|
+
await disposeEmbeddingModels(models);
|
|
316
|
+
}
|
|
317
|
+
async contextFromWorkspaceIndex(location, request, options, timings) {
|
|
318
|
+
const info = readWorkspaceManifest(location.home);
|
|
319
|
+
if (!info) {
|
|
320
|
+
throw new EngineError("Workspace index manifest not found", {
|
|
321
|
+
code: "ZVEC_GREP.ENGINE.MANIFEST.NOT_FOUND",
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
const workspaceIndex = await this.openWorkspaceIndexForSearch(info, request, info.embeddingRuntime);
|
|
325
|
+
try {
|
|
326
|
+
return await contextFromOpenWorkspaceIndex({
|
|
327
|
+
root: location.root,
|
|
328
|
+
request,
|
|
329
|
+
workspaceIndex,
|
|
330
|
+
options,
|
|
331
|
+
timings,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
finally {
|
|
335
|
+
workspaceIndex.close();
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
async refreshWorkspaceIndexForContext(location, options, timings) {
|
|
339
|
+
const daemonWritePermit = assertDaemonWriteAllowed(location.root, this.options.daemonInstanceToken);
|
|
340
|
+
try {
|
|
341
|
+
const needsRefresh = await timings.time("status_scan", () => withHomeReadLock(location.home, "context.status", async () => {
|
|
342
|
+
const manifest = readWorkspaceManifest(location.home);
|
|
343
|
+
const status = manifest
|
|
344
|
+
? await workspaceIndexStatus(manifest, location)
|
|
345
|
+
: null;
|
|
346
|
+
return indexStatusNeedsRefresh(status);
|
|
347
|
+
}));
|
|
348
|
+
if (!needsRefresh) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
await timings.time("auto_update", () => withHomeWriteLock(location.home, "context.refresh", async () => {
|
|
352
|
+
const existing = readWorkspaceManifest(location.home);
|
|
353
|
+
if (!existing) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const stillNeedsRefresh = await timings.time("refresh_status_scan", () => workspaceIndexNeedsRefresh(location));
|
|
357
|
+
if (!stillNeedsRefresh) {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
const workspaceRuntime = existing.embeddingRuntime;
|
|
361
|
+
const { model: embeddingModel, concurrency: embeddingConcurrency } = await this.embeddingModelForIndex(existing, "context.refresh", workspaceRuntime, options.embeddingConcurrency);
|
|
362
|
+
assertWorkspaceEndpointMatchesCurrentRuntime(existing, workspaceRuntime, effectiveEmbeddingRuntime(this.options, embeddingModel, workspaceRuntime), "zg --index --endpoint <url> --rebuild");
|
|
363
|
+
assertWorkspaceEmbeddingMatchesCurrentModel(existing, embeddingModel, "zg --index --rebuild");
|
|
364
|
+
const workspaceIndex = new WorkspaceIndex(existing, {
|
|
365
|
+
mode: "write",
|
|
366
|
+
embeddingModel,
|
|
367
|
+
});
|
|
368
|
+
try {
|
|
369
|
+
const result = await workspaceIndex.index({
|
|
370
|
+
embeddingConcurrency,
|
|
371
|
+
onProgress: options.onAutoUpdateProgress,
|
|
372
|
+
});
|
|
373
|
+
timings.addEntries(result.timings, "auto_update_");
|
|
374
|
+
}
|
|
375
|
+
finally {
|
|
376
|
+
workspaceIndex.close();
|
|
377
|
+
}
|
|
378
|
+
}));
|
|
379
|
+
}
|
|
380
|
+
finally {
|
|
381
|
+
daemonWritePermit?.release();
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
async contextFromWriterWorkspaceIndex(root, workspaceIndex, options) {
|
|
385
|
+
return await this.withEmbeddingModelOperation(async () => {
|
|
386
|
+
const timings = new TimingCollector();
|
|
387
|
+
const request = normalizeContextRequest(options);
|
|
388
|
+
const result = await timings.time("total", () => contextFromOpenWorkspaceIndex({
|
|
389
|
+
root,
|
|
390
|
+
request,
|
|
391
|
+
workspaceIndex,
|
|
392
|
+
options: { ...options, autoUpdate: false },
|
|
393
|
+
timings,
|
|
394
|
+
}));
|
|
395
|
+
return withContextTimings(result, timings);
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
async contextFromRg(root, request, options, timings) {
|
|
399
|
+
let rgResult;
|
|
400
|
+
try {
|
|
401
|
+
rgResult = await timings.time("rg_search", () => runRgSearch({
|
|
402
|
+
root,
|
|
403
|
+
patterns: request.rgPatterns,
|
|
404
|
+
paths: options.rgPaths,
|
|
405
|
+
limit: options.limit,
|
|
406
|
+
includePaths: options.includePaths,
|
|
407
|
+
excludePaths: options.excludePaths,
|
|
408
|
+
globs: options.globs,
|
|
409
|
+
insensitiveGlobs: options.insensitiveGlobs,
|
|
410
|
+
fileTypes: options.fileTypes,
|
|
411
|
+
excludedFileTypes: options.excludedFileTypes,
|
|
412
|
+
hidden: options.hidden,
|
|
413
|
+
noIgnore: options.noIgnore,
|
|
414
|
+
ignoreFiles: options.ignoreFiles,
|
|
415
|
+
maxDepth: options.maxDepth,
|
|
416
|
+
maxFileSizeBytes: options.maxFileSizeBytes,
|
|
417
|
+
follow: options.follow,
|
|
418
|
+
modifiedAfter: options.modifiedAfter,
|
|
419
|
+
modifiedBefore: options.modifiedBefore,
|
|
420
|
+
rgOptions: options.rgOptions,
|
|
421
|
+
}));
|
|
422
|
+
}
|
|
423
|
+
catch (cause) {
|
|
424
|
+
throw new EngineError("Search failed", {
|
|
425
|
+
code: "ZVEC_GREP.ENGINE.SEARCH.FAILED",
|
|
426
|
+
context: errorDetails([detail("source", "rg"), detail("root", root)]),
|
|
427
|
+
cause,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
const structuralEnrichment = await timings.time("structure_enrichment", () => enrichLexicalItemsWithStructure(root, rgResult.items, undefined, options.maxFileSizeBytes));
|
|
431
|
+
const items = dedupeAndRerankContextItems(structuralEnrichment.items);
|
|
432
|
+
const emptyReason = items.length === 0 ? rgEmptyReason(rgResult.diagnostics) : undefined;
|
|
433
|
+
return {
|
|
434
|
+
query: request.displayQuery,
|
|
435
|
+
root,
|
|
436
|
+
source: "rg",
|
|
437
|
+
coverage: rgResult.diagnostics.truncated
|
|
438
|
+
? "rg_truncated"
|
|
439
|
+
: "rg_exhaustive",
|
|
440
|
+
items,
|
|
441
|
+
diagnostics: {
|
|
442
|
+
emptyReason,
|
|
443
|
+
rg: rgResult.diagnostics,
|
|
444
|
+
structure: structuralEnrichment.diagnostics,
|
|
445
|
+
},
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
async openWorkspaceIndexForSearch(info, request, workspaceRuntime) {
|
|
449
|
+
return new WorkspaceIndex(info, {
|
|
450
|
+
mode: "read",
|
|
451
|
+
embeddingModel: await this.embeddingModelForSearch(indexedEmbeddingSchema(info), request, workspaceRuntime, info),
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
async embeddingModelForSearch(schema, request, workspaceRuntime, info) {
|
|
455
|
+
if (!request.routes.some((route) => route.mode === "vector")) {
|
|
456
|
+
return undefined;
|
|
457
|
+
}
|
|
458
|
+
const identity = {
|
|
459
|
+
provider: schema.provider,
|
|
460
|
+
name: schema.model,
|
|
461
|
+
};
|
|
462
|
+
const effectiveRuntime = resolveEmbeddingRuntimeOptions(embeddingModelReference(identity), this.options, workspaceRuntime, readGlobalConfig());
|
|
463
|
+
assertSearchEndpointMatchesWorkspace(info, workspaceRuntime, effectiveRuntime);
|
|
464
|
+
if (this.embeddingModel) {
|
|
465
|
+
return this.embeddingModel;
|
|
466
|
+
}
|
|
467
|
+
return this.recoverEmbeddingModel(schema, workspaceRuntime);
|
|
468
|
+
}
|
|
469
|
+
async embeddingModelForIndex(existing, operation, workspaceRuntime = {}, embeddingConcurrency) {
|
|
470
|
+
if (isWorkspaceIndexed(existing) &&
|
|
471
|
+
!this.embeddingModel &&
|
|
472
|
+
!this.options.embedding) {
|
|
473
|
+
const concurrency = resolveIndexEmbeddingConcurrency({
|
|
474
|
+
provider: existing.embedding.provider,
|
|
475
|
+
name: existing.embedding.model,
|
|
476
|
+
}, embeddingConcurrency ?? this.options.embeddingConcurrency);
|
|
477
|
+
return {
|
|
478
|
+
model: await this.recoverEmbeddingModel(existing.embedding, workspaceRuntime, concurrency),
|
|
479
|
+
concurrency,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
const config = readGlobalConfig();
|
|
483
|
+
const reference = resolveEmbeddingReference({
|
|
484
|
+
explicit: this.options.embedding,
|
|
485
|
+
globalDefault: config.defaults?.embedding,
|
|
486
|
+
fallback: DEFAULT_LOCAL_EMBEDDING,
|
|
487
|
+
});
|
|
488
|
+
const referenceIdentity = reference
|
|
489
|
+
? parseEmbeddingModelReference(reference)
|
|
490
|
+
: undefined;
|
|
491
|
+
const selectedWorkspaceRuntime = isWorkspaceIndexed(existing) &&
|
|
492
|
+
referenceIdentity &&
|
|
493
|
+
existing.embedding.provider !== referenceIdentity.provider
|
|
494
|
+
? {}
|
|
495
|
+
: workspaceRuntime;
|
|
496
|
+
const identity = this.embeddingModel
|
|
497
|
+
? {
|
|
498
|
+
provider: this.embeddingModel.info.provider,
|
|
499
|
+
name: this.embeddingModel.info.name,
|
|
500
|
+
}
|
|
501
|
+
: referenceIdentity;
|
|
502
|
+
const concurrency = identity
|
|
503
|
+
? resolveIndexEmbeddingConcurrency(identity, embeddingConcurrency ?? this.options.embeddingConcurrency)
|
|
504
|
+
: undefined;
|
|
505
|
+
const model = this.embeddingModel ??
|
|
506
|
+
(reference
|
|
507
|
+
? await this.embeddingModelFromReference(reference, config, selectedWorkspaceRuntime, concurrency)
|
|
508
|
+
: undefined) ??
|
|
509
|
+
this.requireEmbeddingModel(operation);
|
|
510
|
+
return { model, concurrency };
|
|
511
|
+
}
|
|
512
|
+
async recoverEmbeddingModel(schema, workspaceRuntime = {}, embeddingConcurrency) {
|
|
513
|
+
const config = readGlobalConfig();
|
|
514
|
+
const identity = {
|
|
515
|
+
provider: schema.provider,
|
|
516
|
+
name: schema.model,
|
|
517
|
+
};
|
|
518
|
+
const reference = embeddingModelReference(identity);
|
|
519
|
+
const options = providerOptions(this.options, identity, config, workspaceRuntime, embeddingConcurrency);
|
|
520
|
+
const key = `${reference}/${providerOptionsFingerprint(options)}`;
|
|
521
|
+
return this.cachedEmbeddingModel(key, () => createServiceEmbeddingModel(reference, options, this.options), localEmbeddingVariant(reference, options));
|
|
522
|
+
}
|
|
523
|
+
async embeddingModelFromReference(reference, config = readGlobalConfig(), workspaceRuntime = {}, embeddingConcurrency) {
|
|
524
|
+
const identity = parseEmbeddingModelReference(reference);
|
|
525
|
+
const options = providerOptions(this.options, identity, config, workspaceRuntime, embeddingConcurrency);
|
|
526
|
+
const key = `configured/${reference}/${providerOptionsFingerprint(options)}`;
|
|
527
|
+
return this.cachedEmbeddingModel(key, () => createServiceEmbeddingModel(reference, options, this.options), localEmbeddingVariant(reference, options));
|
|
528
|
+
}
|
|
529
|
+
cachedEmbeddingModel(key, create, variant) {
|
|
530
|
+
// Serialize cache selection, not inference. A caller arriving during an
|
|
531
|
+
// idle model's disposal must wait before it can load another GPU model.
|
|
532
|
+
const selected = this.modelSelectionPromise.then(async () => {
|
|
533
|
+
this.ensureOpen();
|
|
534
|
+
if (variant && this.activeEmbeddingModelOperations <= 1) {
|
|
535
|
+
for (const [cachedKey, cachedVariant] of this.localEmbeddingVariants) {
|
|
536
|
+
if (cachedVariant.family === variant.family &&
|
|
537
|
+
cachedVariant.concurrency !== variant.concurrency) {
|
|
538
|
+
this.retireEmbeddingModel(cachedKey);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
if (this.activeEmbeddingModelOperations <= 1) {
|
|
543
|
+
// Only this operation remains, and its previous phase has finished.
|
|
544
|
+
// Other active operations may still hold retired models, so otherwise
|
|
545
|
+
// defer disposal until the final operation exits.
|
|
546
|
+
await this.disposeRetiredEmbeddingModels();
|
|
547
|
+
}
|
|
548
|
+
else {
|
|
549
|
+
await this.modelDisposalPromise;
|
|
550
|
+
}
|
|
551
|
+
this.ensureOpen();
|
|
552
|
+
const equivalentKey = variant
|
|
553
|
+
? [...this.localEmbeddingVariants].find(([, cachedVariant]) => cachedVariant.family === variant.family &&
|
|
554
|
+
cachedVariant.concurrency === variant.concurrency)?.[0]
|
|
555
|
+
: undefined;
|
|
556
|
+
const cachedKey = this.recoveredEmbeddingModels.has(key)
|
|
557
|
+
? key
|
|
558
|
+
: (equivalentKey ?? key);
|
|
559
|
+
const cached = this.recoveredEmbeddingModels.get(cachedKey);
|
|
560
|
+
if (cached) {
|
|
561
|
+
this.recoveredEmbeddingModels.delete(cachedKey);
|
|
562
|
+
this.recoveredEmbeddingModels.set(cachedKey, cached);
|
|
563
|
+
return cached;
|
|
564
|
+
}
|
|
565
|
+
const model = create();
|
|
566
|
+
this.recoveredEmbeddingModels.set(key, model);
|
|
567
|
+
if (variant)
|
|
568
|
+
this.localEmbeddingVariants.set(key, variant);
|
|
569
|
+
this.trimRecoveredEmbeddingModels();
|
|
570
|
+
return model;
|
|
571
|
+
});
|
|
572
|
+
this.modelSelectionPromise = selected.then(() => undefined, () => undefined);
|
|
573
|
+
return selected;
|
|
574
|
+
}
|
|
575
|
+
retireEmbeddingModel(key) {
|
|
576
|
+
const model = this.recoveredEmbeddingModels.get(key);
|
|
577
|
+
this.recoveredEmbeddingModels.delete(key);
|
|
578
|
+
this.localEmbeddingVariants.delete(key);
|
|
579
|
+
if (model)
|
|
580
|
+
this.retiredEmbeddingModels.add(model);
|
|
581
|
+
}
|
|
582
|
+
trimRecoveredEmbeddingModels() {
|
|
583
|
+
while (this.recoveredEmbeddingModels.size > MAX_RECOVERED_EMBEDDING_MODELS) {
|
|
584
|
+
const oldestKey = this.recoveredEmbeddingModels.keys().next().value;
|
|
585
|
+
if (oldestKey === undefined) {
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
this.retireEmbeddingModel(oldestKey);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
retireIdleEmbeddingVariants() {
|
|
592
|
+
const retainedFamilies = new Set();
|
|
593
|
+
for (const key of [...this.recoveredEmbeddingModels.keys()].reverse()) {
|
|
594
|
+
const variant = this.localEmbeddingVariants.get(key);
|
|
595
|
+
if (!variant)
|
|
596
|
+
continue;
|
|
597
|
+
if (retainedFamilies.has(variant.family)) {
|
|
598
|
+
this.retireEmbeddingModel(key);
|
|
599
|
+
}
|
|
600
|
+
else {
|
|
601
|
+
retainedFamilies.add(variant.family);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
async withEmbeddingModelOperation(operation) {
|
|
606
|
+
this.activeEmbeddingModelOperations += 1;
|
|
607
|
+
try {
|
|
608
|
+
return await operation();
|
|
609
|
+
}
|
|
610
|
+
finally {
|
|
611
|
+
this.activeEmbeddingModelOperations -= 1;
|
|
612
|
+
if (this.activeEmbeddingModelOperations === 0) {
|
|
613
|
+
this.retireIdleEmbeddingVariants();
|
|
614
|
+
await this.disposeRetiredEmbeddingModels();
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
disposeRetiredEmbeddingModels() {
|
|
619
|
+
const models = [...this.retiredEmbeddingModels];
|
|
620
|
+
this.retiredEmbeddingModels.clear();
|
|
621
|
+
const disposal = this.modelDisposalPromise.then(() => disposeEmbeddingModels(models));
|
|
622
|
+
// Report this batch's failures to its caller, but let later cleanup and
|
|
623
|
+
// model selection continue after every disposal in the batch settles.
|
|
624
|
+
this.modelDisposalPromise = disposal.then(() => undefined, () => undefined);
|
|
625
|
+
return disposal;
|
|
626
|
+
}
|
|
627
|
+
requireEmbeddingModel(operation) {
|
|
628
|
+
if (!this.embeddingModel) {
|
|
629
|
+
throw new EngineError("zvec-grep operation requires an embedding model", {
|
|
630
|
+
code: "ZVEC_GREP.ENGINE.SERVICE.EMBEDDING_MODEL_REQUIRED",
|
|
631
|
+
context: errorDetails([
|
|
632
|
+
detail("operation", operation),
|
|
633
|
+
detail("hint", `Pass "--embedding <model>", set ZVEC_GREP_EMBEDDING, or configure defaults.embedding in ${globalConfigPath()}. Existing indexes can run "zg --index" without --embedding to reuse the stored schema.`),
|
|
634
|
+
detail("examples", "local/potion-code-16m-v2, qwen/text-embedding-v4"),
|
|
635
|
+
]),
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
return this.embeddingModel;
|
|
639
|
+
}
|
|
640
|
+
ensureOpen() {
|
|
641
|
+
if (this.closed) {
|
|
642
|
+
throw new EngineError("zvec-grep service is already closed", {
|
|
643
|
+
code: "ZVEC_GREP.ENGINE.SERVICE.CLOSED",
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
async function disposeEmbeddingModels(models) {
|
|
649
|
+
const errors = [];
|
|
650
|
+
for (const model of models) {
|
|
651
|
+
try {
|
|
652
|
+
await model.dispose();
|
|
653
|
+
}
|
|
654
|
+
catch (error) {
|
|
655
|
+
errors.push(error);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
if (errors.length === 1)
|
|
659
|
+
throw errors[0];
|
|
660
|
+
if (errors.length > 1) {
|
|
661
|
+
throw new AggregateError(errors, "Failed to dispose embedding models");
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
async function contextFromOpenWorkspaceIndex(input) {
|
|
665
|
+
const searches = [];
|
|
666
|
+
const groups = input.options.fuse
|
|
667
|
+
? [
|
|
668
|
+
{
|
|
669
|
+
id: "Q1",
|
|
670
|
+
query: input.request.displayQuery,
|
|
671
|
+
role: input.request.groups.some((group) => group.role === "primary")
|
|
672
|
+
? "primary"
|
|
673
|
+
: "supplemental",
|
|
674
|
+
routes: input.request.routes,
|
|
675
|
+
},
|
|
676
|
+
]
|
|
677
|
+
: input.request.groups;
|
|
678
|
+
const limit = contextGroupLimit(input.options.limit, groups.length);
|
|
679
|
+
for (const group of groups) {
|
|
680
|
+
const search = await input.workspaceIndex.searchPlan({
|
|
681
|
+
routes: group.routes,
|
|
682
|
+
limit,
|
|
683
|
+
trace: input.options.trace,
|
|
684
|
+
preferSymbol: input.options.preferSymbol,
|
|
685
|
+
symbolTypes: input.options.symbolTypes,
|
|
686
|
+
includePaths: input.options.includePaths,
|
|
687
|
+
excludePaths: input.options.excludePaths,
|
|
688
|
+
globs: input.options.globs,
|
|
689
|
+
insensitiveGlobs: input.options.insensitiveGlobs,
|
|
690
|
+
fileTypes: input.options.fileTypes,
|
|
691
|
+
excludedFileTypes: input.options.excludedFileTypes,
|
|
692
|
+
modifiedAfter: input.options.modifiedAfter,
|
|
693
|
+
modifiedBefore: input.options.modifiedBefore,
|
|
694
|
+
});
|
|
695
|
+
input.timings.addEntries(search.timings);
|
|
696
|
+
searches.push(search);
|
|
697
|
+
}
|
|
698
|
+
const groupItems = searches.map((search, index) => searchPlanToContextItems(search, input.root, groups[index]));
|
|
699
|
+
const items = selectAndRankContextItems(groupItems.flat(), groups.filter((group) => group.role === "primary").map((group) => group.id));
|
|
700
|
+
return {
|
|
701
|
+
query: input.request.displayQuery,
|
|
702
|
+
root: input.root,
|
|
703
|
+
source: "index",
|
|
704
|
+
coverage: "ranked_sample",
|
|
705
|
+
workspaceIndex: {
|
|
706
|
+
id: input.workspaceIndex.info.id,
|
|
707
|
+
name: input.workspaceIndex.info.name,
|
|
708
|
+
path: input.workspaceIndex.info.path,
|
|
709
|
+
},
|
|
710
|
+
items,
|
|
711
|
+
groupResults: groups.map((group, index) => ({
|
|
712
|
+
id: group.id,
|
|
713
|
+
query: group.query,
|
|
714
|
+
role: group.role,
|
|
715
|
+
items: groupItems[index] ?? [],
|
|
716
|
+
})),
|
|
717
|
+
diagnostics: {
|
|
718
|
+
emptyReason: items.length === 0 ? "no_matches" : undefined,
|
|
719
|
+
index: {
|
|
720
|
+
hitsReturned: items.length,
|
|
721
|
+
queryGroups: groups.map((group) => ({
|
|
722
|
+
id: group.id,
|
|
723
|
+
query: group.query,
|
|
724
|
+
role: group.role,
|
|
725
|
+
})),
|
|
726
|
+
routes: searches.flatMap((search) => search.plan.routes),
|
|
727
|
+
},
|
|
728
|
+
},
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
async function withHomeReadLock(home, operation, task) {
|
|
732
|
+
const lock = acquireHomeLock(home, "read", operation);
|
|
733
|
+
try {
|
|
734
|
+
return await task();
|
|
735
|
+
}
|
|
736
|
+
finally {
|
|
737
|
+
lock.release();
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
async function withHomeWriteLock(home, operation, task) {
|
|
741
|
+
const lock = acquireHomeLock(home, "write", operation);
|
|
742
|
+
try {
|
|
743
|
+
return await task();
|
|
744
|
+
}
|
|
745
|
+
finally {
|
|
746
|
+
lock.release();
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
async function workspaceIndexNeedsRefresh(location) {
|
|
750
|
+
const manifest = readWorkspaceManifest(location.home);
|
|
751
|
+
const status = manifest
|
|
752
|
+
? await workspaceIndexStatus(manifest, location)
|
|
753
|
+
: null;
|
|
754
|
+
return indexStatusNeedsRefresh(status);
|
|
755
|
+
}
|
|
756
|
+
function acquireHomeLock(home, mode, operation) {
|
|
757
|
+
return acquireReadWriteLock(homeLockPath(home), mode, { operation });
|
|
758
|
+
}
|
|
759
|
+
function assertHomeUnlocked(home, operation) {
|
|
760
|
+
assertNoWriteLock(homeLockPath(home), operation);
|
|
761
|
+
}
|
|
762
|
+
function assertNearestWorkspaceHomeUnlocked(start, operation) {
|
|
763
|
+
let current = resolve(start);
|
|
764
|
+
while (true) {
|
|
765
|
+
assertHomeUnlocked(workspaceHome(current), operation);
|
|
766
|
+
const parent = dirname(current);
|
|
767
|
+
if (parent === current) {
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
current = parent;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
function homeLockPath(home) {
|
|
774
|
+
return join(home, "locks", "home");
|
|
775
|
+
}
|
|
776
|
+
function rgEmptyReason(diagnostics) {
|
|
777
|
+
return diagnostics.missingPaths &&
|
|
778
|
+
diagnostics.missingPaths.length > 0 &&
|
|
779
|
+
diagnostics.searchedPaths &&
|
|
780
|
+
diagnostics.searchedPaths.length === 0
|
|
781
|
+
? "no_searchable_files"
|
|
782
|
+
: "no_matches";
|
|
783
|
+
}
|
|
784
|
+
function withContextTimings(result, timings) {
|
|
785
|
+
const entries = timings.entries();
|
|
786
|
+
if (entries.length === 0) {
|
|
787
|
+
return result;
|
|
788
|
+
}
|
|
789
|
+
return {
|
|
790
|
+
...result,
|
|
791
|
+
diagnostics: {
|
|
792
|
+
...result.diagnostics,
|
|
793
|
+
timings: entries,
|
|
794
|
+
},
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
function findNearestWorkspaceIndex(start) {
|
|
798
|
+
const location = findNearestWorkspace(start);
|
|
799
|
+
if (!location)
|
|
800
|
+
return null;
|
|
801
|
+
const info = readWorkspaceManifest(location.home);
|
|
802
|
+
return info ? { location, info } : null;
|
|
803
|
+
}
|
|
804
|
+
function workspaceInfoSuggestion(workspaceIndex) {
|
|
805
|
+
if (!workspaceIndex) {
|
|
806
|
+
return "zg --index or zg --rg";
|
|
807
|
+
}
|
|
808
|
+
if (workspaceIndex.indexPolicy === "disabled") {
|
|
809
|
+
return "zg --rg";
|
|
810
|
+
}
|
|
811
|
+
if (!isWorkspaceIndexed(workspaceIndex)) {
|
|
812
|
+
return "zg --index";
|
|
813
|
+
}
|
|
814
|
+
return undefined;
|
|
815
|
+
}
|
|
816
|
+
function workspaceIndexMissingError(root, policy) {
|
|
817
|
+
return new EngineError("No zvec-grep index found for this workspace", {
|
|
818
|
+
code: "ZVEC_GREP.ENGINE.SERVICE.WORKSPACE_INDEX_NOT_FOUND",
|
|
819
|
+
context: errorDetails([
|
|
820
|
+
detail("root", root),
|
|
821
|
+
detail("policy", policy),
|
|
822
|
+
detail("hint", policy === "undecided"
|
|
823
|
+
? "Ask the user whether to build an index with zg --index, or use zg --rg for no-index search."
|
|
824
|
+
: "Run zg --index to build the enabled workspace index, or use zg --rg for no-index search."),
|
|
825
|
+
detail("agent_prompt", policy === "undecided"
|
|
826
|
+
? "Ask the user whether this workspace should be indexed. If yes, run zg --index --embedding <model> with appropriate -g/--glob and -t/--type filters; otherwise use zg --rg for immediate no-index search."
|
|
827
|
+
: "This workspace is marked index-enabled but has no built index. Ask before running zg --index if an embedding model or cost is involved; otherwise use zg --rg for immediate no-index search."),
|
|
828
|
+
]),
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
function workspaceIndexDisabledError(root) {
|
|
832
|
+
return new EngineError("The zvec-grep index is disabled for this workspace", {
|
|
833
|
+
code: "ZVEC_GREP.ENGINE.SERVICE.WORKSPACE_INDEX_DISABLED",
|
|
834
|
+
context: errorDetails([
|
|
835
|
+
detail("root", root),
|
|
836
|
+
detail("policy", "disabled"),
|
|
837
|
+
detail("hint", "Use zg --rg for no-index search. Run zg --index only if the user explicitly decides to index this workspace."),
|
|
838
|
+
detail("agent_action", "do_not_build_index"),
|
|
839
|
+
]),
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
function resolveIndexRootPaths(existing, requested, fallbackRoot, options) {
|
|
843
|
+
let rootPaths = requested
|
|
844
|
+
? inheritRequestedRootPathSettings(requested, existing?.rootPaths)
|
|
845
|
+
: (existing?.rootPaths ?? [fallbackRoot]);
|
|
846
|
+
if (options.resetPaths) {
|
|
847
|
+
rootPaths = rootPaths.map(resetRootPathFilters);
|
|
848
|
+
}
|
|
849
|
+
if (options.includePaths !== undefined ||
|
|
850
|
+
options.excludePaths !== undefined ||
|
|
851
|
+
options.globs !== undefined ||
|
|
852
|
+
options.insensitiveGlobs !== undefined ||
|
|
853
|
+
options.fileTypes !== undefined ||
|
|
854
|
+
options.excludedFileTypes !== undefined ||
|
|
855
|
+
options.hidden !== undefined ||
|
|
856
|
+
options.noIgnore !== undefined ||
|
|
857
|
+
options.ignoreFiles !== undefined ||
|
|
858
|
+
options.maxDepth !== undefined ||
|
|
859
|
+
options.maxFileSizeBytes !== undefined ||
|
|
860
|
+
options.follow !== undefined) {
|
|
861
|
+
rootPaths = rootPaths.map((rootPath) => applyRootPathOverrides(rootPath, options));
|
|
862
|
+
}
|
|
863
|
+
return rootPaths;
|
|
864
|
+
}
|
|
865
|
+
function inheritRequestedRootPathSettings(requested, existing) {
|
|
866
|
+
if (!existing?.length) {
|
|
867
|
+
return requested;
|
|
868
|
+
}
|
|
869
|
+
return requested.map((rootPath) => {
|
|
870
|
+
const absolutePath = normalizePath(typeof rootPath === "string" ? rootPath : rootPath.absolutePath);
|
|
871
|
+
const inherited = existing.find((candidate) => normalizePath(candidate.absolutePath) === absolutePath);
|
|
872
|
+
if (!inherited) {
|
|
873
|
+
return rootPath;
|
|
874
|
+
}
|
|
875
|
+
if (typeof rootPath === "string") {
|
|
876
|
+
return { ...inherited, absolutePath };
|
|
877
|
+
}
|
|
878
|
+
return {
|
|
879
|
+
...inherited,
|
|
880
|
+
...rootPath,
|
|
881
|
+
absolutePath,
|
|
882
|
+
include: rootPath.include ?? inherited.include,
|
|
883
|
+
exclude: rootPath.exclude ?? inherited.exclude,
|
|
884
|
+
globs: rootPath.globs ?? inherited.globs,
|
|
885
|
+
insensitiveGlobs: rootPath.insensitiveGlobs ?? inherited.insensitiveGlobs,
|
|
886
|
+
fileTypes: rootPath.fileTypes ?? inherited.fileTypes,
|
|
887
|
+
excludedFileTypes: rootPath.excludedFileTypes ?? inherited.excludedFileTypes,
|
|
888
|
+
hidden: rootPath.hidden ?? inherited.hidden,
|
|
889
|
+
noIgnore: rootPath.noIgnore ?? inherited.noIgnore,
|
|
890
|
+
ignoreFiles: rootPath.ignoreFiles ?? inherited.ignoreFiles,
|
|
891
|
+
maxDepth: rootPath.maxDepth ?? inherited.maxDepth,
|
|
892
|
+
maxFileSizeBytes: rootPath.maxFileSizeBytes ?? inherited.maxFileSizeBytes,
|
|
893
|
+
follow: rootPath.follow ?? inherited.follow,
|
|
894
|
+
};
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
function resetRootPathFilters(rootPath) {
|
|
898
|
+
if (typeof rootPath === "string") {
|
|
899
|
+
return rootPath;
|
|
900
|
+
}
|
|
901
|
+
return {
|
|
902
|
+
absolutePath: rootPath.absolutePath,
|
|
903
|
+
recursive: rootPath.recursive,
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
function applyRootPathOverrides(rootPath, options) {
|
|
907
|
+
const normalized = typeof rootPath === "string"
|
|
908
|
+
? {
|
|
909
|
+
absolutePath: rootPath,
|
|
910
|
+
recursive: true,
|
|
911
|
+
}
|
|
912
|
+
: rootPath;
|
|
913
|
+
return {
|
|
914
|
+
...normalized,
|
|
915
|
+
include: options.includePaths ?? normalized.include,
|
|
916
|
+
exclude: options.excludePaths ?? normalized.exclude,
|
|
917
|
+
globs: options.globs ?? normalized.globs,
|
|
918
|
+
insensitiveGlobs: options.insensitiveGlobs ?? normalized.insensitiveGlobs,
|
|
919
|
+
fileTypes: options.fileTypes ?? normalized.fileTypes,
|
|
920
|
+
excludedFileTypes: options.excludedFileTypes ?? normalized.excludedFileTypes,
|
|
921
|
+
hidden: options.hidden ?? normalized.hidden,
|
|
922
|
+
noIgnore: options.noIgnore ?? normalized.noIgnore,
|
|
923
|
+
ignoreFiles: options.ignoreFiles ?? normalized.ignoreFiles,
|
|
924
|
+
maxDepth: options.maxDepth ?? normalized.maxDepth,
|
|
925
|
+
maxFileSizeBytes: options.maxFileSizeBytes ?? normalized.maxFileSizeBytes,
|
|
926
|
+
follow: options.follow ?? normalized.follow,
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
function prepareWorkspaceManifest(location, existing, rootPaths, embeddingModel, embeddingRuntime) {
|
|
930
|
+
const now = Date.now();
|
|
931
|
+
return {
|
|
932
|
+
manifestVersion: CURRENT_MANIFEST_VERSION,
|
|
933
|
+
id: existing?.id ?? randomUUID(),
|
|
934
|
+
name: existing?.name ?? workspaceDisplayName(location.root),
|
|
935
|
+
path: location.home,
|
|
936
|
+
rootPaths: validateRootPaths(rootPaths),
|
|
937
|
+
indexPolicy: "enabled",
|
|
938
|
+
embedding: currentEmbeddingSchema(embeddingModel),
|
|
939
|
+
indexVersion: CURRENT_INDEX_VERSION,
|
|
940
|
+
createdTime: existing?.createdTime ?? now,
|
|
941
|
+
updatedTime: now,
|
|
942
|
+
embeddingRuntime,
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
function currentEmbeddingSchema(embeddingModel) {
|
|
946
|
+
return {
|
|
947
|
+
provider: embeddingModel.info.provider,
|
|
948
|
+
model: embeddingModel.info.name,
|
|
949
|
+
dimension: embeddingModel.info.dimension,
|
|
950
|
+
metric: embeddingModel.info.metric,
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
function workspaceDisplayName(root) {
|
|
954
|
+
return basename(normalizePath(root)) || "workspace";
|
|
955
|
+
}
|
|
956
|
+
async function workspaceIndexStatus(info, location) {
|
|
957
|
+
if (info.indexPolicy === "disabled" ||
|
|
958
|
+
!isWorkspaceIndexed(info) ||
|
|
959
|
+
!hasWorkspaceIndex(location)) {
|
|
960
|
+
return null;
|
|
961
|
+
}
|
|
962
|
+
const workspaceIndex = new WorkspaceIndex(info, { mode: "read" });
|
|
963
|
+
try {
|
|
964
|
+
return await workspaceIndex.status();
|
|
965
|
+
}
|
|
966
|
+
finally {
|
|
967
|
+
workspaceIndex.close();
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
function indexedEmbeddingSchema(info) {
|
|
971
|
+
if (isWorkspaceIndexed(info)) {
|
|
972
|
+
return info.embedding;
|
|
973
|
+
}
|
|
974
|
+
throw new EngineError("zvec-grep index has not been built", {
|
|
975
|
+
code: "ZVEC_GREP.ENGINE.SERVICE.INDEX_MISSING",
|
|
976
|
+
context: errorDetails([
|
|
977
|
+
workspaceIndexDetail(info.name),
|
|
978
|
+
detail("hint", "Run zg --index to build this index."),
|
|
979
|
+
]),
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
/** Resolve index-stage batch concurrency; query model creation never reads it. */
|
|
983
|
+
export function resolveIndexEmbeddingConcurrency(identity, requested) {
|
|
984
|
+
const backend = getEmbeddingModelCatalogEntry(embeddingModelReference(identity))?.backend;
|
|
985
|
+
return resolveIndexEmbeddingConcurrencyOverride({
|
|
986
|
+
embeddingConcurrency: requested,
|
|
987
|
+
legacyLlama: backend === "llama-cpp",
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
function providerOptions(options, identity, config = readGlobalConfig(), workspaceRuntime = {}, embeddingConcurrency) {
|
|
991
|
+
const reference = embeddingModelReference(identity);
|
|
992
|
+
const backend = getEmbeddingModelCatalogEntry(reference)?.backend;
|
|
993
|
+
const parallelism = backend === "llama-cpp" || backend === "transformers-js"
|
|
994
|
+
? normalizeLocalEmbeddingConcurrency(embeddingConcurrency)
|
|
995
|
+
: undefined;
|
|
996
|
+
const runtime = resolveEmbeddingRuntimeOptions(reference, options, workspaceRuntime, config);
|
|
997
|
+
return {
|
|
998
|
+
apiKey: runtime.apiKey,
|
|
999
|
+
endpoint: runtime.endpoint,
|
|
1000
|
+
modelCacheDir: options.modelCacheDir ??
|
|
1001
|
+
config.defaults?.modelCacheDir ??
|
|
1002
|
+
process.env.ZVEC_GREP_MODEL_CACHE ??
|
|
1003
|
+
join(options.home ?? defaultHome(), "models"),
|
|
1004
|
+
device: runtime.device,
|
|
1005
|
+
...(parallelism !== undefined ? { embeddingConcurrency: parallelism } : {}),
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
function localEmbeddingVariant(reference, options) {
|
|
1009
|
+
const backend = getEmbeddingModelCatalogEntry(reference)?.backend;
|
|
1010
|
+
if (backend !== "llama-cpp" && backend !== "transformers-js") {
|
|
1011
|
+
return undefined;
|
|
1012
|
+
}
|
|
1013
|
+
return {
|
|
1014
|
+
family: `${reference}/${providerOptionsFingerprint({
|
|
1015
|
+
...options,
|
|
1016
|
+
embeddingConcurrency: undefined,
|
|
1017
|
+
})}`,
|
|
1018
|
+
concurrency: options.embeddingConcurrency ??
|
|
1019
|
+
(backend === "transformers-js" ||
|
|
1020
|
+
options.device === "cpu" ||
|
|
1021
|
+
options.device === undefined
|
|
1022
|
+
? 1
|
|
1023
|
+
: undefined),
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
function createServiceEmbeddingModel(reference, modelOptions, serviceOptions) {
|
|
1027
|
+
const model = createEmbeddingModel(reference, modelOptions);
|
|
1028
|
+
if (model.info.provider !== "qwen") {
|
|
1029
|
+
return model;
|
|
1030
|
+
}
|
|
1031
|
+
const endpoint = model.info.endpoint;
|
|
1032
|
+
if (endpoint === undefined) {
|
|
1033
|
+
throw new EngineError("Remote embedding model did not provide an endpoint", {
|
|
1034
|
+
code: "ZVEC_GREP.ENGINE.MODELS.REMOTE_ENDPOINT_NOT_IMPLEMENTED",
|
|
1035
|
+
context: `model=${model.info.reference}`,
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
const authorize = remoteEmbeddingAuthorizationGuard({
|
|
1039
|
+
store: new RemoteEmbeddingAuthorizationStore({
|
|
1040
|
+
signingKeyPath: serviceOptions.authorizationSigningKeyPath,
|
|
1041
|
+
}),
|
|
1042
|
+
});
|
|
1043
|
+
const authorizedModel = {
|
|
1044
|
+
info: model.info,
|
|
1045
|
+
async embed(contents, embedOptions) {
|
|
1046
|
+
await authorize({
|
|
1047
|
+
provider: model.info.provider,
|
|
1048
|
+
model: model.info.name,
|
|
1049
|
+
endpoint,
|
|
1050
|
+
purpose: embedOptions?.purpose ?? EmbeddingPurpose.Document,
|
|
1051
|
+
contentKinds: contents.map((content) => content.kind),
|
|
1052
|
+
contentCount: contents.length,
|
|
1053
|
+
});
|
|
1054
|
+
return await model.embed(contents, embedOptions);
|
|
1055
|
+
},
|
|
1056
|
+
dispose: () => model.dispose(),
|
|
1057
|
+
};
|
|
1058
|
+
return authorizedModel;
|
|
1059
|
+
}
|
|
1060
|
+
function parseEmbeddingModelReference(reference) {
|
|
1061
|
+
const separator = reference.indexOf("/");
|
|
1062
|
+
if (separator <= 0 || separator === reference.length - 1) {
|
|
1063
|
+
throw new EngineError("Embedding model reference is invalid", {
|
|
1064
|
+
code: "ZVEC_GREP.ENGINE.MODELS.EMBEDDING_INVALID_REFERENCE",
|
|
1065
|
+
context: `reference=${reference}`,
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
return {
|
|
1069
|
+
provider: reference.slice(0, separator),
|
|
1070
|
+
name: reference.slice(separator + 1),
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
function embeddingModelReference(identity) {
|
|
1074
|
+
return `${identity.provider}/${identity.name}`;
|
|
1075
|
+
}
|
|
1076
|
+
function providerOptionsFingerprint(options) {
|
|
1077
|
+
const publicOptionsFingerprint = createHash("sha256")
|
|
1078
|
+
.update(JSON.stringify([
|
|
1079
|
+
options.endpoint,
|
|
1080
|
+
options.modelCacheDir,
|
|
1081
|
+
options.device,
|
|
1082
|
+
options.embeddingConcurrency,
|
|
1083
|
+
]))
|
|
1084
|
+
.digest("hex");
|
|
1085
|
+
return `${providerApiKeyIdentity(options.apiKey)}:${publicOptionsFingerprint}`;
|
|
1086
|
+
}
|
|
1087
|
+
function providerApiKeyIdentity(apiKey) {
|
|
1088
|
+
return createHmac("sha256", PROVIDER_API_KEY_IDENTITY_SECRET)
|
|
1089
|
+
.update(apiKey)
|
|
1090
|
+
.digest("hex");
|
|
1091
|
+
}
|
|
1092
|
+
function effectiveEmbeddingRuntime(options, model, workspaceRuntime) {
|
|
1093
|
+
const resolved = resolveEmbeddingRuntimeOptions(model.info.reference, options, workspaceRuntime, readGlobalConfig());
|
|
1094
|
+
return {
|
|
1095
|
+
apiKey: resolved.apiKey,
|
|
1096
|
+
...((model.info.endpoint ?? resolved.endpoint)
|
|
1097
|
+
? { endpoint: model.info.endpoint ?? resolved.endpoint }
|
|
1098
|
+
: {}),
|
|
1099
|
+
...(model.info.provider === "local"
|
|
1100
|
+
? { device: resolved.device ?? "auto" }
|
|
1101
|
+
: {}),
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
function runtimeForModelProvider(existing, model, workspaceRuntime) {
|
|
1105
|
+
return isWorkspaceIndexed(existing) &&
|
|
1106
|
+
existing.embedding.provider !== model.info.provider
|
|
1107
|
+
? {}
|
|
1108
|
+
: workspaceRuntime;
|
|
1109
|
+
}
|
|
1110
|
+
function embeddingRuntimeAfterIndex(existing, existingRuntime, model, effectiveRuntime, explicit) {
|
|
1111
|
+
const sameProvider = isWorkspaceIndexed(existing) &&
|
|
1112
|
+
existing.embedding.provider === model.info.provider;
|
|
1113
|
+
const apiKey = model.info.provider === "local"
|
|
1114
|
+
? undefined
|
|
1115
|
+
: (explicit.apiKey ??
|
|
1116
|
+
(sameProvider ? existingRuntime.apiKey : undefined));
|
|
1117
|
+
return {
|
|
1118
|
+
...(apiKey !== undefined ? { apiKey } : {}),
|
|
1119
|
+
...(model.info.provider !== "local" &&
|
|
1120
|
+
effectiveRuntime.endpoint !== undefined
|
|
1121
|
+
? { endpoint: effectiveRuntime.endpoint }
|
|
1122
|
+
: {}),
|
|
1123
|
+
...(model.info.provider === "local"
|
|
1124
|
+
? { device: effectiveRuntime.device ?? "auto" }
|
|
1125
|
+
: {}),
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
function assertWorkspaceEndpointMatchesCurrentRuntime(info, workspaceRuntime, effectiveRuntime, rebuildCommand) {
|
|
1129
|
+
if (!isWorkspaceIndexed(info) ||
|
|
1130
|
+
workspaceRuntime.endpoint === effectiveRuntime.endpoint) {
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
throw new EngineError("Existing zvec-grep index uses a different embedding endpoint", {
|
|
1134
|
+
code: "ZVEC_GREP.ENGINE.SERVICE.EMBEDDING_ENDPOINT_CHANGE_REQUIRES_REBUILD",
|
|
1135
|
+
context: errorDetails([
|
|
1136
|
+
workspaceIndexDetail(info.name),
|
|
1137
|
+
detail("hint", `Run "${rebuildCommand}" to rebuild this index with the requested endpoint.`),
|
|
1138
|
+
]),
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
function assertSearchEndpointMatchesWorkspace(info, workspaceRuntime, effectiveRuntime) {
|
|
1142
|
+
if (workspaceRuntime.endpoint === effectiveRuntime.endpoint) {
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
throw new EngineError("Search cannot override the embedding endpoint of an existing index", {
|
|
1146
|
+
code: "ZVEC_GREP.ENGINE.SERVICE.SEARCH_ENDPOINT_CHANGE_REQUIRES_REBUILD",
|
|
1147
|
+
context: errorDetails([
|
|
1148
|
+
workspaceIndexDetail(info.name),
|
|
1149
|
+
detail("hint", 'Run "zg --index --endpoint <url> --rebuild" before searching with this endpoint.'),
|
|
1150
|
+
]),
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
function assertWorkspaceEmbeddingMatchesCurrentModel(info, model, rebuildCommand) {
|
|
1154
|
+
if (!isWorkspaceIndexed(info)) {
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
const expected = info.embedding;
|
|
1158
|
+
const changed = expected.provider !== model.info.provider ||
|
|
1159
|
+
expected.model !== model.info.name ||
|
|
1160
|
+
expected.dimension !== model.info.dimension ||
|
|
1161
|
+
expected.metric !== model.info.metric;
|
|
1162
|
+
if (!changed) {
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
throw new EngineError("Existing zvec-grep index uses a different embedding model", {
|
|
1166
|
+
code: "ZVEC_GREP.ENGINE.SERVICE.EMBEDDING_SCHEMA_CHANGE_REQUIRES_REBUILD",
|
|
1167
|
+
context: errorDetails([
|
|
1168
|
+
workspaceIndexDetail(info.name),
|
|
1169
|
+
detail("existing", `${expected.provider}/${expected.model}`),
|
|
1170
|
+
detail("requested", model.info.reference),
|
|
1171
|
+
detail("hint", `Run "${rebuildCommand}" to rebuild this index with the requested embedding model.`),
|
|
1172
|
+
]),
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
function contextGroupLimit(limit, groupCount) {
|
|
1176
|
+
if (limit !== undefined) {
|
|
1177
|
+
return limit;
|
|
1178
|
+
}
|
|
1179
|
+
const safeGroupCount = Math.max(1, groupCount);
|
|
1180
|
+
if (safeGroupCount <= 3) {
|
|
1181
|
+
return DEFAULT_CONTEXT_LIMIT;
|
|
1182
|
+
}
|
|
1183
|
+
return Math.max(1, Math.ceil(DEFAULT_CONTEXT_TOTAL_LIMIT / safeGroupCount));
|
|
1184
|
+
}
|
|
1185
|
+
function normalizeContextRequest(options) {
|
|
1186
|
+
const primaryQueries = normalizePrimaryQueries(options.query, options.queries);
|
|
1187
|
+
const extraRoutes = normalizeContextRoutes(options.routes ?? []);
|
|
1188
|
+
const hasPatternFiles = options.rg === true && (options.rgOptions?.patternFiles?.length ?? 0) > 0;
|
|
1189
|
+
if (primaryQueries.length === 0 &&
|
|
1190
|
+
extraRoutes.length === 0 &&
|
|
1191
|
+
!hasPatternFiles) {
|
|
1192
|
+
throw new EngineError("zvec-grep context requires a non-empty query or route", {
|
|
1193
|
+
code: "ZVEC_GREP.ENGINE.SERVICE.EMPTY_QUERY",
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
const groups = contextGroups(primaryQueries, extraRoutes);
|
|
1197
|
+
const routes = groups.flatMap((group) => group.routes);
|
|
1198
|
+
const rgPatterns = [
|
|
1199
|
+
...primaryQueries,
|
|
1200
|
+
...extraRoutes.map((route) => route.query),
|
|
1201
|
+
];
|
|
1202
|
+
const displayQuery = primaryQueries.length > 0
|
|
1203
|
+
? primaryQueries.join(" | ")
|
|
1204
|
+
: extraRoutes.length > 0
|
|
1205
|
+
? extraRoutes.map((route) => route.query).join(" | ")
|
|
1206
|
+
: options
|
|
1207
|
+
.rgOptions.patternFiles.map((path) => `@${path}`)
|
|
1208
|
+
.join(" | ");
|
|
1209
|
+
return {
|
|
1210
|
+
displayQuery,
|
|
1211
|
+
rgPatterns,
|
|
1212
|
+
routes,
|
|
1213
|
+
groups,
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
function normalizePrimaryQueries(query, queries) {
|
|
1217
|
+
return [query, ...(queries ?? [])].flatMap((value) => {
|
|
1218
|
+
const normalized = normalizeOptionalQuery(value);
|
|
1219
|
+
return normalized ? [normalized] : [];
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
function normalizeOptionalQuery(query) {
|
|
1223
|
+
const normalized = query?.trim() ?? "";
|
|
1224
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
1225
|
+
}
|
|
1226
|
+
function normalizeContextRoutes(routes) {
|
|
1227
|
+
return routes.map((route, index) => {
|
|
1228
|
+
if (route.mode !== "fts" && route.mode !== "vector") {
|
|
1229
|
+
throw new EngineError("zvec-grep context route has an unsupported mode", {
|
|
1230
|
+
code: "ZVEC_GREP.ENGINE.SERVICE.INVALID_ROUTE_MODE",
|
|
1231
|
+
context: `routeIndex=${index} mode=${String(route.mode)}`,
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
const query = route.query.trim();
|
|
1235
|
+
if (query.length === 0) {
|
|
1236
|
+
throw new EngineError("zvec-grep context route requires a non-empty query", {
|
|
1237
|
+
code: "ZVEC_GREP.ENGINE.SERVICE.EMPTY_ROUTE_QUERY",
|
|
1238
|
+
context: `routeIndex=${index} mode=${route.mode}`,
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
return {
|
|
1242
|
+
mode: route.mode,
|
|
1243
|
+
query,
|
|
1244
|
+
};
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
function contextGroups(primaryQueries, extraRoutes) {
|
|
1248
|
+
return [
|
|
1249
|
+
...primaryQueries.map((query, index) => ({
|
|
1250
|
+
id: `Q${index + 1}`,
|
|
1251
|
+
query,
|
|
1252
|
+
role: "primary",
|
|
1253
|
+
routes: [
|
|
1254
|
+
{ mode: "fts", query },
|
|
1255
|
+
{ mode: "vector", query },
|
|
1256
|
+
],
|
|
1257
|
+
})),
|
|
1258
|
+
...extraRoutes.map((route, index) => ({
|
|
1259
|
+
id: `Q${primaryQueries.length + index + 1}`,
|
|
1260
|
+
query: route.query,
|
|
1261
|
+
role: "supplemental",
|
|
1262
|
+
routes: [route],
|
|
1263
|
+
})),
|
|
1264
|
+
];
|
|
1265
|
+
}
|
|
1266
|
+
function searchPlanToContextItems(result, root, group) {
|
|
1267
|
+
return result.hits.map((hit) => {
|
|
1268
|
+
const target = contextItemTarget(hit);
|
|
1269
|
+
return {
|
|
1270
|
+
kind: "indexed_entity",
|
|
1271
|
+
rank: hit.rank,
|
|
1272
|
+
file: {
|
|
1273
|
+
absolutePath: hit.file.absolutePath,
|
|
1274
|
+
relativePath: hit.file.relativePath || relative(root, hit.file.absolutePath) || ".",
|
|
1275
|
+
rootPath: hit.file.rootPath,
|
|
1276
|
+
},
|
|
1277
|
+
range: hit.entity.range,
|
|
1278
|
+
excerptRange: target.excerptRange,
|
|
1279
|
+
content: contentToText(target.content),
|
|
1280
|
+
contentRole: target.contentRole,
|
|
1281
|
+
outline: target.outline,
|
|
1282
|
+
status: fileFreshnessStatus(hit.file),
|
|
1283
|
+
score: hit.score,
|
|
1284
|
+
matchedBy: hit.matchedBy,
|
|
1285
|
+
metadata: hit.entity.metadata,
|
|
1286
|
+
entityId: hit.entity.id,
|
|
1287
|
+
trace: hit.trace,
|
|
1288
|
+
queryGroups: [
|
|
1289
|
+
{
|
|
1290
|
+
id: group.id,
|
|
1291
|
+
query: group.query,
|
|
1292
|
+
role: group.role,
|
|
1293
|
+
rank: hit.rank,
|
|
1294
|
+
matchedBy: hit.matchedBy,
|
|
1295
|
+
},
|
|
1296
|
+
],
|
|
1297
|
+
};
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1300
|
+
const DEFAULT_CONTEXT_PRIORITY_LIMIT = 6;
|
|
1301
|
+
const CONTEXT_GROUP_RRF_K = 60;
|
|
1302
|
+
function selectAndRankContextItems(items, coverageGroupIds) {
|
|
1303
|
+
const deduped = new Map();
|
|
1304
|
+
for (const item of items) {
|
|
1305
|
+
const key = contextItemDedupeKey(item);
|
|
1306
|
+
const existing = deduped.get(key);
|
|
1307
|
+
if (existing) {
|
|
1308
|
+
existing.queryGroups = mergeContextQueryGroupMatches(existing.queryGroups ?? [], item.queryGroups ?? []);
|
|
1309
|
+
existing.matchedBy = mergedContextMatchedBy(existing.queryGroups);
|
|
1310
|
+
continue;
|
|
1311
|
+
}
|
|
1312
|
+
deduped.set(key, { ...item });
|
|
1313
|
+
}
|
|
1314
|
+
const candidates = [...deduped.values()];
|
|
1315
|
+
const globallyRanked = [...candidates].sort(compareContextGlobalRank);
|
|
1316
|
+
const selected = new Set();
|
|
1317
|
+
const prioritized = [];
|
|
1318
|
+
for (const groupId of coverageGroupIds) {
|
|
1319
|
+
if (prioritized.length >= DEFAULT_CONTEXT_PRIORITY_LIMIT) {
|
|
1320
|
+
break;
|
|
1321
|
+
}
|
|
1322
|
+
const candidate = candidates
|
|
1323
|
+
.filter((item) => !selected.has(contextItemDedupeKey(item)))
|
|
1324
|
+
.filter((item) => contextQueryGroupMatch(item, groupId) !== undefined)
|
|
1325
|
+
.sort((left, right) => compareContextGroupRank(left, right, groupId))[0];
|
|
1326
|
+
if (!candidate) {
|
|
1327
|
+
continue;
|
|
1328
|
+
}
|
|
1329
|
+
selected.add(contextItemDedupeKey(candidate));
|
|
1330
|
+
prioritized.push({
|
|
1331
|
+
...candidate,
|
|
1332
|
+
selectionReason: "coverage",
|
|
1333
|
+
coverageGroup: groupId,
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
for (const candidate of globallyRanked) {
|
|
1337
|
+
if (prioritized.length >= DEFAULT_CONTEXT_PRIORITY_LIMIT) {
|
|
1338
|
+
break;
|
|
1339
|
+
}
|
|
1340
|
+
const key = contextItemDedupeKey(candidate);
|
|
1341
|
+
if (selected.has(key)) {
|
|
1342
|
+
continue;
|
|
1343
|
+
}
|
|
1344
|
+
selected.add(key);
|
|
1345
|
+
prioritized.push({
|
|
1346
|
+
...candidate,
|
|
1347
|
+
selectionReason: "global_fill",
|
|
1348
|
+
coverageGroup: undefined,
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
const unprioritized = globallyRanked.filter((item) => !selected.has(contextItemDedupeKey(item)));
|
|
1352
|
+
return [...prioritized, ...unprioritized].map((item, index) => ({
|
|
1353
|
+
...item,
|
|
1354
|
+
rank: index + 1,
|
|
1355
|
+
}));
|
|
1356
|
+
}
|
|
1357
|
+
function dedupeAndRerankContextItems(items) {
|
|
1358
|
+
const seen = new Set();
|
|
1359
|
+
const deduped = [];
|
|
1360
|
+
for (const item of items) {
|
|
1361
|
+
const key = contextItemDedupeKey(item);
|
|
1362
|
+
if (seen.has(key)) {
|
|
1363
|
+
continue;
|
|
1364
|
+
}
|
|
1365
|
+
seen.add(key);
|
|
1366
|
+
deduped.push({ ...item, rank: deduped.length + 1 });
|
|
1367
|
+
}
|
|
1368
|
+
return deduped;
|
|
1369
|
+
}
|
|
1370
|
+
function mergeContextQueryGroupMatches(left, right) {
|
|
1371
|
+
const matches = new Map(left.map((match) => [match.id, match]));
|
|
1372
|
+
for (const match of right) {
|
|
1373
|
+
const existing = matches.get(match.id);
|
|
1374
|
+
if (!existing || match.rank < existing.rank) {
|
|
1375
|
+
matches.set(match.id, match);
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
return [...matches.values()].sort((a, b) => contextGroupNumber(a.id) - contextGroupNumber(b.id));
|
|
1379
|
+
}
|
|
1380
|
+
function mergedContextMatchedBy(matches) {
|
|
1381
|
+
const hasFts = matches.some((match) => match.matchedBy === "fts" || match.matchedBy === "fts+vector");
|
|
1382
|
+
const hasVector = matches.some((match) => match.matchedBy === "vector" || match.matchedBy === "fts+vector");
|
|
1383
|
+
return hasFts && hasVector ? "fts+vector" : hasFts ? "fts" : "vector";
|
|
1384
|
+
}
|
|
1385
|
+
function compareContextGlobalRank(left, right) {
|
|
1386
|
+
return (contextGlobalRrfScore(right) - contextGlobalRrfScore(left) ||
|
|
1387
|
+
contextBestGroupRank(left) - contextBestGroupRank(right) ||
|
|
1388
|
+
contextFirstGroupNumber(left) - contextFirstGroupNumber(right) ||
|
|
1389
|
+
contextItemDedupeKey(left).localeCompare(contextItemDedupeKey(right)));
|
|
1390
|
+
}
|
|
1391
|
+
function contextGlobalRrfScore(item) {
|
|
1392
|
+
return (item.queryGroups ?? []).reduce((score, match) => score + 1 / (CONTEXT_GROUP_RRF_K + match.rank), 0);
|
|
1393
|
+
}
|
|
1394
|
+
function compareContextGroupRank(left, right, groupId) {
|
|
1395
|
+
return (contextQueryGroupMatch(left, groupId).rank -
|
|
1396
|
+
contextQueryGroupMatch(right, groupId).rank ||
|
|
1397
|
+
compareContextGlobalRank(left, right));
|
|
1398
|
+
}
|
|
1399
|
+
function contextQueryGroupMatch(item, groupId) {
|
|
1400
|
+
return item.queryGroups?.find((match) => match.id === groupId);
|
|
1401
|
+
}
|
|
1402
|
+
function contextBestGroupRank(item) {
|
|
1403
|
+
return Math.min(...(item.queryGroups ?? []).map((match) => match.rank));
|
|
1404
|
+
}
|
|
1405
|
+
function contextFirstGroupNumber(item) {
|
|
1406
|
+
return Math.min(...(item.queryGroups ?? []).map((match) => contextGroupNumber(match.id)));
|
|
1407
|
+
}
|
|
1408
|
+
function contextGroupNumber(id) {
|
|
1409
|
+
const value = Number.parseInt(id.slice(1), 10);
|
|
1410
|
+
return Number.isFinite(value) ? value : Number.MAX_SAFE_INTEGER;
|
|
1411
|
+
}
|
|
1412
|
+
function contextItemDedupeKey(item) {
|
|
1413
|
+
if (item.entityId) {
|
|
1414
|
+
return `entity:${item.entityId}`;
|
|
1415
|
+
}
|
|
1416
|
+
return ["range", item.file.absolutePath, JSON.stringify(item.range)].join(":");
|
|
1417
|
+
}
|
|
1418
|
+
function contextItemTarget(hit) {
|
|
1419
|
+
const evidence = hit.evidence.find((item) => !item.isEntity);
|
|
1420
|
+
const hasSeparateEvidence = evidence && !sameDisplayedContent(hit.entity, evidence);
|
|
1421
|
+
const content = hasSeparateEvidence ? evidence.content : hit.entity.content;
|
|
1422
|
+
const contentRole = hasSeparateEvidence || entityContentLooksLikeSource(hit.entity)
|
|
1423
|
+
? "source"
|
|
1424
|
+
: "outline";
|
|
1425
|
+
const excerptRange = hasSeparateEvidence ? evidence.range : undefined;
|
|
1426
|
+
const outline = contentRole === "source" ? contextItemOutline(hit, evidence) : undefined;
|
|
1427
|
+
return {
|
|
1428
|
+
content,
|
|
1429
|
+
contentRole,
|
|
1430
|
+
excerptRange,
|
|
1431
|
+
outline,
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
function entityContentLooksLikeSource(entity) {
|
|
1435
|
+
if (entity.content.kind !== "text" || entity.range.kind !== "text") {
|
|
1436
|
+
return true;
|
|
1437
|
+
}
|
|
1438
|
+
const expectedLines = entity.range.endLine - entity.range.startLine + 1;
|
|
1439
|
+
const actualLines = entity.content.text.split(/\r?\n/).length;
|
|
1440
|
+
return actualLines >= expectedLines;
|
|
1441
|
+
}
|
|
1442
|
+
function contextItemOutline(hit, evidence) {
|
|
1443
|
+
if (hit.entity.content.kind !== "text") {
|
|
1444
|
+
return undefined;
|
|
1445
|
+
}
|
|
1446
|
+
const outline = hit.entity.content.text.trim();
|
|
1447
|
+
if (outline.length === 0) {
|
|
1448
|
+
return undefined;
|
|
1449
|
+
}
|
|
1450
|
+
if (!evidence) {
|
|
1451
|
+
return undefined;
|
|
1452
|
+
}
|
|
1453
|
+
if (evidence && sameDisplayedContent(hit.entity, evidence)) {
|
|
1454
|
+
return undefined;
|
|
1455
|
+
}
|
|
1456
|
+
if (!isUsefulOutline(hit.entity.metadata, outline)) {
|
|
1457
|
+
return undefined;
|
|
1458
|
+
}
|
|
1459
|
+
if (evidence?.content.kind === "text" &&
|
|
1460
|
+
evidence.content.text.trim() === outline) {
|
|
1461
|
+
return undefined;
|
|
1462
|
+
}
|
|
1463
|
+
return outline;
|
|
1464
|
+
}
|
|
1465
|
+
function isUsefulOutline(metadata, outline) {
|
|
1466
|
+
if (!metadata || metadata.kind !== "code") {
|
|
1467
|
+
return false;
|
|
1468
|
+
}
|
|
1469
|
+
return (metadata.symbolType === "class" ||
|
|
1470
|
+
metadata.symbolType === "interface" ||
|
|
1471
|
+
metadata.symbolType === "module" ||
|
|
1472
|
+
outline.includes("\ncalls:"));
|
|
1473
|
+
}
|
|
1474
|
+
function sameDisplayedContent(entity, evidence) {
|
|
1475
|
+
return (JSON.stringify(entity.range) === JSON.stringify(evidence.range) &&
|
|
1476
|
+
contentEquals(entity.content, evidence.content));
|
|
1477
|
+
}
|
|
1478
|
+
function contentEquals(left, right) {
|
|
1479
|
+
if (left.kind !== right.kind) {
|
|
1480
|
+
return false;
|
|
1481
|
+
}
|
|
1482
|
+
if (left.kind === "image") {
|
|
1483
|
+
return (right.kind === "image" &&
|
|
1484
|
+
left.format === right.format &&
|
|
1485
|
+
left.data.byteLength === right.data.byteLength);
|
|
1486
|
+
}
|
|
1487
|
+
return right.kind === "text" && left.text === right.text;
|
|
1488
|
+
}
|
|
1489
|
+
function fileFreshnessStatus(file) {
|
|
1490
|
+
if (!file.indexStatus?.indexedTime) {
|
|
1491
|
+
return "possibly_stale";
|
|
1492
|
+
}
|
|
1493
|
+
try {
|
|
1494
|
+
const info = statSync(file.absolutePath, { throwIfNoEntry: false });
|
|
1495
|
+
if (!info || !info.isFile()) {
|
|
1496
|
+
return "possibly_stale";
|
|
1497
|
+
}
|
|
1498
|
+
if (file.indexStatus.indexedTime >= info.mtimeMs) {
|
|
1499
|
+
return "fresh";
|
|
1500
|
+
}
|
|
1501
|
+
if (file.contentHash &&
|
|
1502
|
+
sha256File(file.absolutePath) === file.contentHash) {
|
|
1503
|
+
return "fresh";
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
catch {
|
|
1507
|
+
return "possibly_stale";
|
|
1508
|
+
}
|
|
1509
|
+
return "possibly_stale";
|
|
1510
|
+
}
|
|
1511
|
+
function sha256File(path) {
|
|
1512
|
+
return createHash("sha256").update(readFileSync(path)).digest("hex");
|
|
1513
|
+
}
|
|
1514
|
+
function contentToText(content) {
|
|
1515
|
+
if (content.kind === "text") {
|
|
1516
|
+
return content.text;
|
|
1517
|
+
}
|
|
1518
|
+
return `[image:${content.format} bytes=${content.data.byteLength}]`;
|
|
1519
|
+
}
|
|
1520
|
+
//# sourceMappingURL=zvec-grep.js.map
|