gitnexus 1.6.9 → 1.6.10-aptos.0
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/README.md +211 -53
- package/dist/_shared/graph/types.d.ts +67 -3
- package/dist/_shared/graph/types.d.ts.map +1 -1
- package/dist/_shared/index.d.ts +1 -0
- package/dist/_shared/index.d.ts.map +1 -1
- package/dist/_shared/index.js.map +1 -1
- package/dist/_shared/integrations/resilient-fetch.d.ts +3 -1
- package/dist/_shared/integrations/resilient-fetch.d.ts.map +1 -1
- package/dist/_shared/integrations/resilient-fetch.js +4 -3
- package/dist/_shared/integrations/resilient-fetch.js.map +1 -1
- package/dist/_shared/language-detection.d.ts.map +1 -1
- package/dist/_shared/language-detection.js +2 -0
- package/dist/_shared/language-detection.js.map +1 -1
- package/dist/_shared/languages.d.ts +2 -0
- package/dist/_shared/languages.d.ts.map +1 -1
- package/dist/_shared/languages.js +2 -0
- package/dist/_shared/languages.js.map +1 -1
- package/dist/_shared/lbug/schema-constants.d.ts +2 -2
- package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
- package/dist/_shared/lbug/schema-constants.js +8 -0
- package/dist/_shared/lbug/schema-constants.js.map +1 -1
- package/dist/_shared/pipeline.d.ts +1 -1
- package/dist/_shared/pipeline.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/callable-flow-site.d.ts +110 -0
- package/dist/_shared/scope-resolution/callable-flow-site.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/callable-flow-site.js +11 -0
- package/dist/_shared/scope-resolution/callable-flow-site.js.map +1 -0
- package/dist/_shared/scope-resolution/finalize-algorithm.d.ts +1 -1
- package/dist/_shared/scope-resolution/finalize-algorithm.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/finalize-algorithm.js +1 -1
- package/dist/_shared/scope-resolution/finalize-algorithm.js.map +1 -1
- package/dist/_shared/scope-resolution/language-classification.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/language-classification.js +1 -0
- package/dist/_shared/scope-resolution/language-classification.js.map +1 -1
- package/dist/_shared/scope-resolution/parsed-file.d.ts +7 -0
- package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/reference-site.d.ts +9 -1
- package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/types.d.ts +20 -3
- package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
- package/dist/cli/ai-context.d.ts +1 -1
- package/dist/cli/ai-context.js +25 -44
- package/dist/cli/analyze.d.ts +5 -2
- package/dist/cli/analyze.js +197 -11
- package/dist/cli/clean.js +17 -3
- package/dist/cli/cli-message.d.ts +1 -1
- package/dist/cli/doctor.d.ts +18 -0
- package/dist/cli/doctor.js +96 -5
- package/dist/cli/editor-targets.d.ts +17 -2
- package/dist/cli/editor-targets.js +44 -0
- package/dist/cli/embeddings.d.ts +12 -0
- package/dist/cli/embeddings.js +49 -0
- package/dist/cli/eval-server.d.ts +23 -1
- package/dist/cli/eval-server.js +126 -8
- package/dist/cli/help-i18n.js +6 -0
- package/dist/cli/i18n/en.d.ts +18 -8
- package/dist/cli/i18n/en.js +18 -8
- package/dist/cli/i18n/resources.d.ts +28 -8
- package/dist/cli/i18n/zh-CN.d.ts +10 -0
- package/dist/cli/i18n/zh-CN.js +18 -8
- package/dist/cli/index.js +21 -7
- package/dist/cli/lazy-action.d.ts +7 -0
- package/dist/cli/lazy-action.js +27 -0
- package/dist/cli/mcp.js +6 -3
- package/dist/cli/move-availability.d.ts +5 -0
- package/dist/cli/move-availability.js +11 -0
- package/dist/cli/setup.d.ts +8 -0
- package/dist/cli/setup.js +230 -32
- package/dist/cli/skill-gen.js +38 -18
- package/dist/cli/standard-skills.d.ts +76 -0
- package/dist/cli/standard-skills.js +45 -0
- package/dist/cli/status.d.ts +4 -1
- package/dist/cli/status.js +57 -4
- package/dist/cli/tool.js +11 -0
- package/dist/cli/uninstall.js +77 -18
- package/dist/cli/wiki.d.ts +1 -0
- package/dist/cli/wiki.js +7 -1
- package/dist/config/ignore-service.d.ts +2 -0
- package/dist/config/ignore-service.js +27 -0
- package/dist/core/analysis-features.d.ts +21 -0
- package/dist/core/analysis-features.js +59 -0
- package/dist/core/analyzer-identity.d.ts +74 -0
- package/dist/core/analyzer-identity.js +1820 -0
- package/dist/core/augmentation/engine.js +13 -8
- package/dist/core/embeddings/embedder.d.ts +3 -2
- package/dist/core/embeddings/embedder.js +24 -7
- package/dist/core/embeddings/embedding-identity.d.ts +11 -0
- package/dist/core/embeddings/embedding-identity.js +24 -0
- package/dist/core/embeddings/embedding-pipeline.d.ts +36 -1
- package/dist/core/embeddings/embedding-pipeline.js +100 -9
- package/dist/core/embeddings/http-client.d.ts +33 -3
- package/dist/core/embeddings/http-client.js +237 -25
- package/dist/core/embeddings/node-module-compat.d.ts +25 -0
- package/dist/core/embeddings/node-module-compat.js +25 -0
- package/dist/core/embeddings/onnxruntime-common-resolver.js +8 -5
- package/dist/core/embeddings/onnxruntime-node-resolver.js +33 -7
- package/dist/core/embeddings/runtime-install.d.ts +119 -0
- package/dist/core/embeddings/runtime-install.js +372 -0
- package/dist/core/embeddings/runtime-support.d.ts +48 -17
- package/dist/core/embeddings/runtime-support.js +96 -0
- package/dist/core/git-staleness.js +1 -1
- package/dist/core/group/bridge-db.js +2 -0
- package/dist/core/group/extractors/http-patterns/python.js +246 -108
- package/dist/core/group/extractors/manifest-extractor.js +4 -0
- package/dist/core/incremental/escalation-gate.d.ts +38 -0
- package/dist/core/incremental/escalation-gate.js +48 -0
- package/dist/core/incremental/shadow-candidates.d.ts +3 -3
- package/dist/core/incremental/shadow-candidates.js +3 -3
- package/dist/core/incremental/subgraph-extract.d.ts +2 -2
- package/dist/core/incremental/subgraph-extract.js +2 -2
- package/dist/core/index-freshness.d.ts +5 -0
- package/dist/core/index-freshness.js +13 -0
- package/dist/core/ingestion/class-extractors/configs/jvm.js +29 -0
- package/dist/core/ingestion/cobol/cobol-preprocessor.js +1 -1
- package/dist/core/ingestion/cobol/jcl-processor.js +9 -8
- package/dist/core/ingestion/cobol-processor.js +27 -26
- package/dist/core/ingestion/community-processor.d.ts +51 -1
- package/dist/core/ingestion/community-processor.js +323 -64
- package/dist/core/ingestion/emit-references.js +7 -2
- package/dist/core/ingestion/filesystem-walker.js +4 -0
- package/dist/core/ingestion/frameworks/spring/analysis-features.d.ts +3 -0
- package/dist/core/ingestion/frameworks/spring/analysis-features.js +7 -0
- package/dist/core/ingestion/frameworks/spring/bean-candidates.d.ts +30 -0
- package/dist/core/ingestion/frameworks/spring/bean-candidates.js +182 -0
- package/dist/core/ingestion/frameworks/spring/bean-catalog.d.ts +12 -0
- package/dist/core/ingestion/frameworks/spring/bean-catalog.js +26 -0
- package/dist/core/ingestion/frameworks/spring/config-bindings.d.ts +24 -0
- package/dist/core/ingestion/frameworks/spring/config-bindings.js +115 -0
- package/dist/core/ingestion/languages/c/captures.js +56 -0
- package/dist/core/ingestion/languages/c/query.js +17 -0
- package/dist/core/ingestion/languages/c/scope-resolver.js +6 -0
- package/dist/core/ingestion/languages/cobol/captures.js +93 -0
- package/dist/core/ingestion/languages/cobol/scope-resolver.js +7 -0
- package/dist/core/ingestion/languages/cpp/captures.js +365 -75
- package/dist/core/ingestion/languages/cpp/scope-resolver.js +14 -0
- package/dist/core/ingestion/languages/csharp/captures.js +25 -0
- package/dist/core/ingestion/languages/dart/captures.js +90 -2
- package/dist/core/ingestion/languages/dart/extension-type-preprocess.d.ts +7 -0
- package/dist/core/ingestion/languages/dart/extension-type-preprocess.js +30 -0
- package/dist/core/ingestion/languages/dart.js +2 -0
- package/dist/core/ingestion/languages/go/captures.js +34 -0
- package/dist/core/ingestion/languages/index.d.ts +1 -0
- package/dist/core/ingestion/languages/index.js +2 -0
- package/dist/core/ingestion/languages/java/analysis-features.d.ts +3 -0
- package/dist/core/ingestion/languages/java/analysis-features.js +13 -0
- package/dist/core/ingestion/languages/java/capture-side-channel.d.ts +22 -0
- package/dist/core/ingestion/languages/java/capture-side-channel.js +58 -0
- package/dist/core/ingestion/languages/java/captures.d.ts +4 -3
- package/dist/core/ingestion/languages/java/captures.js +193 -17
- package/dist/core/ingestion/languages/java/package-facts.d.ts +5 -0
- package/dist/core/ingestion/languages/java/package-facts.js +9 -0
- package/dist/core/ingestion/languages/java/package-siblings.d.ts +3 -16
- package/dist/core/ingestion/languages/java/package-siblings.js +8 -147
- package/dist/core/ingestion/languages/java/query.js +23 -0
- package/dist/core/ingestion/languages/java/scope-resolver.js +23 -0
- package/dist/core/ingestion/languages/java/spring-bean-metadata.d.ts +2 -0
- package/dist/core/ingestion/languages/java/spring-bean-metadata.js +8 -0
- package/dist/core/ingestion/languages/java/spring-config-bindings.d.ts +23 -0
- package/dist/core/ingestion/languages/java/spring-config-bindings.js +215 -0
- package/dist/core/ingestion/languages/java.js +3 -0
- package/dist/core/ingestion/languages/javascript/captures.js +16 -0
- package/dist/core/ingestion/languages/javascript/query.js +21 -0
- package/dist/core/ingestion/languages/jvm/package-facts.d.ts +35 -0
- package/dist/core/ingestion/languages/jvm/package-facts.js +40 -0
- package/dist/core/ingestion/languages/jvm/package-siblings.d.ts +14 -0
- package/dist/core/ingestion/languages/jvm/package-siblings.js +129 -0
- package/dist/core/ingestion/languages/kotlin/capture-side-channel.d.ts +17 -3
- package/dist/core/ingestion/languages/kotlin/capture-side-channel.js +42 -8
- package/dist/core/ingestion/languages/kotlin/captures.js +59 -0
- package/dist/core/ingestion/languages/kotlin/package-facts.d.ts +5 -0
- package/dist/core/ingestion/languages/kotlin/package-facts.js +9 -0
- package/dist/core/ingestion/languages/kotlin/package-siblings.d.ts +4 -0
- package/dist/core/ingestion/languages/kotlin/package-siblings.js +8 -0
- package/dist/core/ingestion/languages/kotlin/query.js +19 -0
- package/dist/core/ingestion/languages/kotlin/scope-resolver.js +9 -1
- package/dist/core/ingestion/languages/kotlin/spring-bean-metadata.d.ts +2 -0
- package/dist/core/ingestion/languages/kotlin/spring-bean-metadata.js +8 -0
- package/dist/core/ingestion/languages/kotlin.js +5 -6
- package/dist/core/ingestion/languages/move.d.ts +1 -0
- package/dist/core/ingestion/languages/move.js +27 -0
- package/dist/core/ingestion/languages/php/captures.js +40 -0
- package/dist/core/ingestion/languages/php/import-decomposer.js +12 -0
- package/dist/core/ingestion/languages/php/import-target.d.ts +2 -1
- package/dist/core/ingestion/languages/php/import-target.js +110 -2
- package/dist/core/ingestion/languages/php/interpret.js +10 -0
- package/dist/core/ingestion/languages/php/scope-resolver.js +1 -1
- package/dist/core/ingestion/languages/python/captures.d.ts +3 -4
- package/dist/core/ingestion/languages/python/captures.js +24 -5
- package/dist/core/ingestion/languages/python/interpret.js +4 -0
- package/dist/core/ingestion/languages/python/receiver-binding.d.ts +15 -2
- package/dist/core/ingestion/languages/python/receiver-binding.js +96 -2
- package/dist/core/ingestion/languages/python/simple-hooks.d.ts +5 -5
- package/dist/core/ingestion/languages/python/simple-hooks.js +15 -5
- package/dist/core/ingestion/languages/ruby/captures.js +42 -0
- package/dist/core/ingestion/languages/rust/captures.js +13 -0
- package/dist/core/ingestion/languages/rust/interpret.js +18 -0
- package/dist/core/ingestion/languages/rust/query.js +9 -0
- package/dist/core/ingestion/languages/rust/range-binding.js +6 -2
- package/dist/core/ingestion/languages/swift/captures.js +12 -0
- package/dist/core/ingestion/languages/typescript/captures.js +21 -0
- package/dist/core/ingestion/languages/typescript/nuxt-auto-imports.js +2 -1
- package/dist/core/ingestion/languages/typescript/query.js +37 -0
- package/dist/core/ingestion/markdown-processor.js +3 -2
- package/dist/core/ingestion/model/registration-table.js +1 -0
- package/dist/core/ingestion/parsing-processor.d.ts +3 -1
- package/dist/core/ingestion/parsing-processor.js +36 -1
- package/dist/core/ingestion/pipeline-phases/index.d.ts +2 -0
- package/dist/core/ingestion/pipeline-phases/index.js +2 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +56 -2
- package/dist/core/ingestion/pipeline-phases/parse.d.ts +0 -2
- package/dist/core/ingestion/pipeline-phases/parse.js +12 -3
- package/dist/core/ingestion/pipeline-phases/processes.d.ts +8 -0
- package/dist/core/ingestion/pipeline-phases/processes.js +11 -1
- package/dist/core/ingestion/pipeline-phases/spring-config.d.ts +37 -0
- package/dist/core/ingestion/pipeline-phases/spring-config.js +433 -0
- package/dist/core/ingestion/pipeline-phases/standalone-ingest.d.ts +10 -0
- package/dist/core/ingestion/pipeline-phases/standalone-ingest.js +6 -0
- package/dist/core/ingestion/pipeline.d.ts +4 -2
- package/dist/core/ingestion/pipeline.js +5 -2
- package/dist/core/ingestion/resolve-references.js +12 -0
- package/dist/core/ingestion/route-extractors/constant-resolver.d.ts +83 -0
- package/dist/core/ingestion/route-extractors/constant-resolver.js +131 -0
- package/dist/core/ingestion/route-extractors/python-const-resolver.d.ts +75 -0
- package/dist/core/ingestion/route-extractors/python-const-resolver.js +299 -0
- package/dist/core/ingestion/scope-extractor.js +306 -43
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +71 -2
- package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.d.ts +7 -7
- package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.js +8 -7
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +3 -1
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +6 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +1 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +4 -1
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +7 -2
- package/dist/core/ingestion/scope-resolution/passes/callable-value-flow.d.ts +47 -0
- package/dist/core/ingestion/scope-resolution/passes/callable-value-flow.js +1138 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +16 -4
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +97 -1
- package/dist/core/ingestion/scope-resolution/passes/property-dispatch.d.ts +60 -0
- package/dist/core/ingestion/scope-resolution/passes/property-dispatch.js +111 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +4 -3
- package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +5 -0
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +25 -38
- package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +6 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +147 -35
- package/dist/core/ingestion/scope-resolution/resolution-outcome.d.ts +1 -1
- package/dist/core/ingestion/scope-resolution/scope/walkers.js +73 -44
- package/dist/core/ingestion/taint/typescript-model.js +34 -2
- package/dist/core/ingestion/tree-sitter-queries.d.ts +2 -2
- package/dist/core/ingestion/tree-sitter-queries.js +23 -2
- package/dist/core/ingestion/type-extractors/jvm.js +17 -2
- package/dist/core/ingestion/utils/ast-helpers.d.ts +1 -0
- package/dist/core/ingestion/utils/ast-helpers.js +153 -2
- package/dist/core/ingestion/utils/callable-flow-captures.d.ts +94 -0
- package/dist/core/ingestion/utils/callable-flow-captures.js +878 -0
- package/dist/core/ingestion/utils/line-base.d.ts +20 -0
- package/dist/core/ingestion/utils/line-base.js +20 -0
- package/dist/core/ingestion/utils/symbol-labels.d.ts +21 -0
- package/dist/core/ingestion/utils/symbol-labels.js +45 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +34 -0
- package/dist/core/ingestion/workers/parse-worker.js +40 -5
- package/dist/core/ingestion/workers/result-merge.js +4 -0
- package/dist/core/ingestion/workers/worker-pool.d.ts +28 -0
- package/dist/core/ingestion/workers/worker-pool.js +128 -28
- package/dist/core/lbug/csv-generator.d.ts +15 -0
- package/dist/core/lbug/csv-generator.js +124 -27
- package/dist/core/lbug/cypher-escape.d.ts +20 -0
- package/dist/core/lbug/cypher-escape.js +20 -0
- package/dist/core/lbug/extension-load-error.d.ts +67 -0
- package/dist/core/lbug/extension-load-error.js +326 -0
- package/dist/core/lbug/extension-loader.d.ts +17 -2
- package/dist/core/lbug/extension-loader.js +38 -13
- package/dist/core/lbug/lbug-adapter.d.ts +203 -2
- package/dist/core/lbug/lbug-adapter.js +527 -86
- package/dist/core/lbug/lbug-config.d.ts +56 -2
- package/dist/core/lbug/lbug-config.js +267 -7
- package/dist/core/lbug/move-columns.d.ts +7 -0
- package/dist/core/lbug/move-columns.js +86 -0
- package/dist/core/lbug/native-check.d.ts +40 -0
- package/dist/core/lbug/native-check.js +90 -0
- package/dist/core/lbug/node-table-layout.d.ts +29 -0
- package/dist/core/lbug/node-table-layout.js +119 -0
- package/dist/core/lbug/pool-adapter.d.ts +23 -1
- package/dist/core/lbug/pool-adapter.js +115 -8
- package/dist/core/lbug/schema.d.ts +4 -3
- package/dist/core/lbug/schema.js +16 -16
- package/dist/core/lbug/sidecar-recovery.d.ts +172 -0
- package/dist/core/lbug/sidecar-recovery.js +418 -16
- package/dist/core/lbug/wal-checkpoint-driver.js +3 -0
- package/dist/core/move/artifact-download.d.ts +19 -0
- package/dist/core/move/artifact-download.js +179 -0
- package/dist/core/move/compiler-facts.d.ts +120 -0
- package/dist/core/move/compiler-facts.js +9 -0
- package/dist/core/move/consistency.d.ts +28 -0
- package/dist/core/move/consistency.js +98 -0
- package/dist/core/move/constants.d.ts +98 -0
- package/dist/core/move/constants.js +136 -0
- package/dist/core/move/discovery.d.ts +10 -0
- package/dist/core/move/discovery.js +22 -0
- package/dist/core/move/entry-points.d.ts +14 -0
- package/dist/core/move/entry-points.js +40 -0
- package/dist/core/move/facts-mapper.d.ts +75 -0
- package/dist/core/move/facts-mapper.js +534 -0
- package/dist/core/move/install-lock.d.ts +14 -0
- package/dist/core/move/install-lock.js +102 -0
- package/dist/core/move/install.d.ts +39 -0
- package/dist/core/move/install.js +391 -0
- package/dist/core/move/mcp-client.d.ts +115 -0
- package/dist/core/move/mcp-client.js +436 -0
- package/dist/core/move/move-ingest.d.ts +49 -0
- package/dist/core/move/move-ingest.js +320 -0
- package/dist/core/move/provision.d.ts +19 -0
- package/dist/core/move/provision.js +91 -0
- package/dist/core/move/release.d.ts +6 -0
- package/dist/core/move/release.js +10 -0
- package/dist/core/move/symbol-id.d.ts +35 -0
- package/dist/core/move/symbol-id.js +65 -0
- package/dist/core/move/type-parser.d.ts +11 -0
- package/dist/core/move/type-parser.js +38 -0
- package/dist/core/platform/capabilities.d.ts +0 -1
- package/dist/core/platform/capabilities.js +39 -8
- package/dist/core/run-analyze.d.ts +3 -3
- package/dist/core/run-analyze.js +1001 -184
- package/dist/core/search/fts-indexes.d.ts +31 -0
- package/dist/core/search/fts-indexes.js +73 -0
- package/dist/core/search/fts-schema.js +2 -0
- package/dist/core/tree-sitter/safe-parse.d.ts +8 -3
- package/dist/core/tree-sitter/safe-parse.js +21 -7
- package/dist/core/wiki/graph-queries.js +6 -5
- package/dist/core/wiki/llm-client.d.ts +8 -2
- package/dist/core/wiki/llm-client.js +41 -5
- package/dist/mcp/core/embedder.js +16 -2
- package/dist/mcp/http-transport.d.ts +5 -0
- package/dist/mcp/http-transport.js +19 -5
- package/dist/mcp/local/bean-metadata.d.ts +2 -0
- package/dist/mcp/local/bean-metadata.js +25 -0
- package/dist/mcp/local/line-display.d.ts +22 -0
- package/dist/mcp/local/line-display.js +3 -0
- package/dist/mcp/local/local-backend.d.ts +19 -1
- package/dist/mcp/local/local-backend.js +402 -155
- package/dist/mcp/local/pdg-impact.d.ts +5 -3
- package/dist/mcp/local/pdg-impact.js +5 -2
- package/dist/mcp/output-budget.d.ts +5 -0
- package/dist/mcp/output-budget.js +52 -0
- package/dist/mcp/read-only-policy.d.ts +12 -0
- package/dist/mcp/read-only-policy.js +102 -0
- package/dist/mcp/repository-policy.d.ts +34 -0
- package/dist/mcp/repository-policy.js +328 -0
- package/dist/mcp/resources.js +32 -5
- package/dist/mcp/server.d.ts +5 -2
- package/dist/mcp/server.js +29 -9
- package/dist/mcp/tools.d.ts +0 -8
- package/dist/mcp/tools.js +102 -5
- package/dist/server/analyze-job.d.ts +3 -0
- package/dist/server/analyze-job.js +17 -0
- package/dist/server/analyze-launch.d.ts +6 -0
- package/dist/server/analyze-launch.js +82 -6
- package/dist/server/analyze-worker-core.d.ts +2 -1
- package/dist/server/analyze-worker-core.js +5 -2
- package/dist/server/analyze-worker.js +29 -6
- package/dist/server/api.d.ts +30 -0
- package/dist/server/api.js +120 -34
- package/dist/server/mcp-http.d.ts +1 -1
- package/dist/server/mcp-http.js +4 -2
- package/dist/server/upload-ingest.js +2 -1
- package/dist/server/validation.js +2 -1
- package/dist/storage/git.d.ts +31 -0
- package/dist/storage/git.js +111 -1
- package/dist/storage/parse-cache.js +7 -1
- package/dist/storage/parsedfile-store.d.ts +8 -0
- package/dist/storage/parsedfile-store.js +165 -1
- package/dist/storage/repo-manager.d.ts +194 -2
- package/dist/storage/repo-manager.js +56 -1
- package/hooks/antigravity/gitnexus-antigravity-hook.cjs +50 -3
- package/hooks/claude/gitnexus-hook.cjs +59 -9
- package/hooks/claude/pre-tool-use.sh +0 -0
- package/hooks/claude/session-start.sh +0 -0
- package/package.json +21 -15
- package/scripts/cross-platform-tests.ts +56 -0
- package/scripts/ensure-fts.ts +43 -0
- package/scripts/install-duckdb-extension.mjs +85 -24
- package/scripts/run-cross-platform.ts +61 -6
- package/scripts/shard-arg.ts +30 -0
- package/scripts/sync-plugin-manifests.mjs +192 -0
- package/skills/gitnexus-lfg/README.md +55 -0
- package/skills/gitnexus-lfg/SKILL.md +86 -0
- package/skills/gitnexus-plan/README.md +142 -0
- package/skills/gitnexus-plan/SKILL.md +348 -0
- package/skills/gitnexus-plan/references/context-ledger.md +137 -0
- package/skills/gitnexus-plan/references/context-pack.md +126 -0
- package/skills/gitnexus-plan/references/evidence-provenance.md +272 -0
- package/skills/gitnexus-plan/references/pdg-slice.md +109 -0
- package/skills/gitnexus-plan/references/plan-template.md +201 -0
- package/skills/gitnexus-plan/scripts/evidence-provenance.mjs +2084 -0
- package/skills/gitnexus-review/SKILL.md +273 -0
- package/skills/gitnexus-review/ci-personas/ci-adversarial-lens.md +42 -0
- package/skills/gitnexus-review/ci-personas/ci-blast-radius-lens.md +39 -0
- package/skills/gitnexus-review/ci-personas/ci-correctness-lens.md +37 -0
- package/skills/gitnexus-review/ci-personas/ci-coverage-lens.md +40 -0
- package/skills/gitnexus-review/ci-personas/ci-critic-lens.md +42 -0
- package/skills/gitnexus-review/ci-personas/ci-security-lens.md +39 -0
- package/skills/gitnexus-work/README.md +71 -0
- package/skills/gitnexus-work/SKILL.md +269 -0
- package/skills/gitnexus-work/references/evidence-provenance.md +272 -0
- package/skills/gitnexus-work/scripts/evidence-provenance.mjs +2084 -0
- package/web/assets/ProcessFlowModal-B_bfBySy.js +22 -0
- package/web/assets/__vite-browser-external-bMKyYQkO.js +1 -0
- package/web/assets/agent-Bqxn-abU.js +626 -0
- package/web/assets/architecture-7EHR7CIX-6QZW5X65-BVWFvYz2.js +1 -0
- package/web/assets/architectureDiagram-UL44E2DR-CmG3uPWf.js +36 -0
- package/web/assets/{blockDiagram-7IZFK4PR-BkWhpzWB.js → blockDiagram-7IZFK4PR-DBefcjof.js} +7 -7
- package/web/assets/{c4Diagram-Y2BXMSZH-DtBvpzWD.js → c4Diagram-Y2BXMSZH-EjWnjEJl.js} +6 -6
- package/web/assets/chunk-2T2R6R2M-Bb6Sy3_j.js +4 -0
- package/web/assets/chunk-2UTLFMKG-CXlhF3Pt.js +1 -0
- package/web/assets/chunk-3SSMPTDK-rPsRM1x0.js +321 -0
- package/web/assets/chunk-4R4BOZG6-Be_F0JoX.js +159 -0
- package/web/assets/{chunk-6764PJDD-DpIS7hxD.js → chunk-6764PJDD-C7xxk063.js} +1 -1
- package/web/assets/chunk-67TQ5CYL-CA7OhIEf.js +124 -0
- package/web/assets/chunk-7FYTHRHK-CsmYIswB.js +32 -0
- package/web/assets/chunk-7J6CGLKN-BlKwgXKs.js +10 -0
- package/web/assets/chunk-7W6UQGC5-pyKGssV3.js +1 -0
- package/web/assets/chunk-C62D2QBJ-3gdL8EKc.js +1 -0
- package/web/assets/chunk-CEXFNPSA-CjwtMDkc.js +1 -0
- package/web/assets/{chunk-INKRHTLW-Nu4ri9P-.js → chunk-INKRHTLW-Ckl3_KkO.js} +13 -13
- package/web/assets/chunk-J5EP6P6S-L19oz55R.js +1 -0
- package/web/assets/{chunk-KGFNY3KK-JGMJvKCK.js → chunk-KGFNY3KK-BZeP-55u.js} +17 -17
- package/web/assets/chunk-KGYTTC2M-zm6n242I.js +1 -0
- package/web/assets/{chunk-KRXBNO2N-BW5o0KUN.js → chunk-KRXBNO2N-C-UNuKKf.js} +1 -1
- package/web/assets/{chunk-LCXTWHL2-CfLLCywY.js → chunk-LCXTWHL2-C162wE5z.js} +5 -5
- package/web/assets/chunk-LII3EMHJ-BL6lKJ_P.js +1 -0
- package/web/assets/chunk-LRIF4GLE-n59lhprL.js +1 -0
- package/web/assets/chunk-QA3QBVWF-DVXPOIxe.js +2 -0
- package/web/assets/chunk-RERM46MO-B5dAdWpP.js +1 -0
- package/web/assets/chunk-RG4AUYOV-ZNgyLFq6.js +206 -0
- package/web/assets/chunk-RKZBBQEN-MyIQbGgD.js +1 -0
- package/web/assets/chunk-RLI5ZMPA-CkXZ0y0U.js +1 -0
- package/web/assets/chunk-T2UQINTJ-BEyXhZWe.js +1 -0
- package/web/assets/chunk-UP6H54XL-BG-n7qDp.js +1 -0
- package/web/assets/chunk-UXSXWOXI-CW1_r5cS.js +1 -0
- package/web/assets/chunk-UY5QBCOK-ByqTVLvh.js +1 -0
- package/web/assets/chunk-VU6ZFW4Y-BLeasdkU.js +2 -0
- package/web/assets/{chunk-W44A43WB-JqMLIpR0.js → chunk-W44A43WB-BN8EQc_u.js} +1 -1
- package/web/assets/chunk-ZXARS5L4-C3v-4Fsw.js +1 -0
- package/web/assets/classDiagram-KGZ6W3CR-olpO3fMe.js +1 -0
- package/web/assets/classDiagram-v2-72OJOZXJ-olpO3fMe.js +1 -0
- package/web/assets/cose-bilkent-UX7MHV2Q-BmnZ7Mvj.js +1 -0
- package/web/assets/dagre-ND4H6XIP-CY4iIF7v.js +4 -0
- package/web/assets/diagram-3NCE3AQN-CO6QpzEE.js +43 -0
- package/web/assets/diagram-GF46GFSD-B2atbzmU.js +24 -0
- package/web/assets/diagram-HNR7UZ2L-DVRltWbL.js +3 -0
- package/web/assets/diagram-QXG6HAR7-DR6AHD2h.js +24 -0
- package/web/assets/diagram-WEQXMOUZ-DkpoMwTL.js +10 -0
- package/web/assets/erDiagram-L5TCEMPS-n8g_90bc.js +85 -0
- package/web/assets/eventmodeling-FCH6USID-MREXMVOE-CgFBM7e-.js +1 -0
- package/web/assets/flowDiagram-H6V6AXG4-SGMDiCkB.js +162 -0
- package/web/assets/ganttDiagram-JCBTUEKG-Cb3UBqRS.js +292 -0
- package/web/assets/gitGraph-WXDBUCRP-R675I2BI-DCJv7SII.js +1 -0
- package/web/assets/gitGraphDiagram-S2ZK5IYY-Bs9CR7tq.js +106 -0
- package/web/assets/index-CX_fADmQ.css +2 -0
- package/web/assets/index-D1Z3dGGz.js +575 -0
- package/web/assets/info-J43DQDTF-KCYPFFUO-BfNy-9Nd.js +1 -0
- package/web/assets/infoDiagram-3YFTVSEB-T368on4D.js +2 -0
- package/web/assets/{ishikawaDiagram-BNXS4ZKH-DfYpxnE5.js → ishikawaDiagram-BNXS4ZKH-XpjJRU9h.js} +6 -6
- package/web/assets/journeyDiagram-M6C3CM3L-C6wjooas.js +139 -0
- package/web/assets/kanban-definition-75IXJCU3-5_UVY79g.js +89 -0
- package/web/assets/{katex-K3KEBU37-CTc5BslQ.js → katex-K3KEBU37-qRQBy-EZ.js} +2 -2
- package/web/assets/{mindmap-definition-2TDM6QVE-DypvbOMQ.js → mindmap-definition-2TDM6QVE-8xDLzk2e.js} +29 -29
- package/web/assets/node-Ct_zna8U.js +8 -0
- package/web/assets/packet-YPE3B663-LP52Z2RK-D4qkeG9k.js +1 -0
- package/web/assets/pie-LRSECV5Y-TCRJHUBD-B_yMKyQa.js +1 -0
- package/web/assets/pieDiagram-CU6KROY3-Bv4LpAbr.js +30 -0
- package/web/assets/preload-helper-Czpn1I53.js +1 -0
- package/web/assets/{quadrantDiagram-VICAPDV7-yXSZ2lLz.js → quadrantDiagram-VICAPDV7-DR54jOeR.js} +6 -6
- package/web/assets/radar-GUYGQ44K-RDLRG3WG-DeD6yN_0.js +1 -0
- package/web/assets/requirementDiagram-JXO7QTGE-PEkg7sKC.js +84 -0
- package/web/assets/rolldown-runtime-QTnfLwEv.js +1 -0
- package/web/assets/sankeyDiagram-URQDO5SZ-DDWEtjcs.js +40 -0
- package/web/assets/sdk-CzHHH8tD.js +37 -0
- package/web/assets/sequenceDiagram-VS2MUI6T-Bv-uwANx.js +162 -0
- package/web/assets/src-JV30wyV8.js +1 -0
- package/web/assets/stateDiagram-7D4R322I-DVNjmkEq.js +1 -0
- package/web/assets/stateDiagram-v2-36443NZ5-CyN4Fh7_.js +1 -0
- package/web/assets/timeline-definition-O6YCAMPW-Qmsuo7qS.js +120 -0
- package/web/assets/treeView-BLDUP644-QA4HXRO3-CDuSYnd4.js +1 -0
- package/web/assets/treemap-LRROVOQU-LLAWBHMP-imazucyJ.js +1 -0
- package/web/assets/vennDiagram-MWXL3ELB-BD1iK1SX.js +34 -0
- package/web/assets/wardley-L42UT6IY-5TKZOOLJ-DqrT9tB8.js +1 -0
- package/web/assets/wardleyDiagram-CUQ6CDDI-D6TzLVob.js +78 -0
- package/web/assets/{xychartDiagram-N2JHSOCM-BOLm0eNm.js → xychartDiagram-N2JHSOCM-CoZ_Tits.js} +6 -6
- package/web/index.html +18 -10
- package/skills/gitnexus-pr-review.md +0 -163
- package/web/assets/agent-Dli_x7bU.js +0 -660
- package/web/assets/architecture-7EHR7CIX-6QZW5X65-DxJw65fT.js +0 -1
- package/web/assets/architectureDiagram-UL44E2DR-dEkqUZN9.js +0 -36
- package/web/assets/chunk-3SSMPTDK-B4_etUhr.js +0 -321
- package/web/assets/chunk-67TQ5CYL-4sNPJU1q.js +0 -124
- package/web/assets/chunk-7W6UQGC5-KyEG0HQg.js +0 -1
- package/web/assets/chunk-KGYTTC2M-CWC_c3H9.js +0 -161
- package/web/assets/chunk-LII3EMHJ-D-jm-dLa.js +0 -1
- package/web/assets/chunk-QA3QBVWF-BgRW3SXd.js +0 -2
- package/web/assets/chunk-RG4AUYOV-CqWzWsV3.js +0 -206
- package/web/assets/chunk-T2UQINTJ-DSBqkzH7.js +0 -1
- package/web/assets/chunk-UY5QBCOK-DyF0vyGd.js +0 -1
- package/web/assets/chunk-ZXARS5L4-CxN8oiwI.js +0 -1
- package/web/assets/classDiagram-KGZ6W3CR-CMne4tG9.js +0 -1
- package/web/assets/classDiagram-v2-72OJOZXJ-CMne4tG9.js +0 -1
- package/web/assets/cose-bilkent-UX7MHV2Q-D71wNYRJ.js +0 -1
- package/web/assets/dagre-ND4H6XIP-Co5rRx9X.js +0 -4
- package/web/assets/diagram-3NCE3AQN-DBp4O00j.js +0 -43
- package/web/assets/diagram-GF46GFSD-6N_8yS0i.js +0 -24
- package/web/assets/diagram-HNR7UZ2L-DFCfd5LI.js +0 -3
- package/web/assets/diagram-QXG6HAR7-Di093rFg.js +0 -24
- package/web/assets/diagram-WEQXMOUZ-DZWi4SUN.js +0 -10
- package/web/assets/erDiagram-L5TCEMPS-8X1y6R4Y.js +0 -85
- package/web/assets/eventmodeling-FCH6USID-MREXMVOE-BQm9QzEa.js +0 -1
- package/web/assets/flowDiagram-H6V6AXG4-CMpY8Ufn.js +0 -162
- package/web/assets/ganttDiagram-JCBTUEKG-D3iL2Aet.js +0 -292
- package/web/assets/gitGraph-WXDBUCRP-R675I2BI-Cr_Bm2Nb.js +0 -1
- package/web/assets/gitGraphDiagram-S2ZK5IYY-CPIDgxGm.js +0 -106
- package/web/assets/index-KctnI3Vq.js +0 -635
- package/web/assets/index-_lgn7hs5.css +0 -2
- package/web/assets/info-J43DQDTF-KCYPFFUO-Cs4p2oyk.js +0 -1
- package/web/assets/infoDiagram-3YFTVSEB-BaLMZkUg.js +0 -2
- package/web/assets/journeyDiagram-M6C3CM3L-CvArszLo.js +0 -139
- package/web/assets/kanban-definition-75IXJCU3-CpfVLLf9.js +0 -89
- package/web/assets/packet-YPE3B663-LP52Z2RK-CKCrztD2.js +0 -1
- package/web/assets/pie-LRSECV5Y-TCRJHUBD-BB7pHqoD.js +0 -1
- package/web/assets/pieDiagram-CU6KROY3-s27EFWgP.js +0 -30
- package/web/assets/radar-GUYGQ44K-RDLRG3WG-DhoTOcuK.js +0 -1
- package/web/assets/requirementDiagram-JXO7QTGE-CWhqInuf.js +0 -84
- package/web/assets/sankeyDiagram-URQDO5SZ-1qEjlFIB.js +0 -40
- package/web/assets/sequenceDiagram-VS2MUI6T-RyTlZ5cR.js +0 -162
- package/web/assets/stateDiagram-7D4R322I-DTjVfX4A.js +0 -1
- package/web/assets/stateDiagram-v2-36443NZ5-xY9_k9_Q.js +0 -1
- package/web/assets/timeline-definition-O6YCAMPW-DrrqLYAf.js +0 -120
- package/web/assets/treeView-BLDUP644-QA4HXRO3-ODehyGKL.js +0 -1
- package/web/assets/treemap-LRROVOQU-LLAWBHMP-BfpgC-7o.js +0 -1
- package/web/assets/vennDiagram-MWXL3ELB-B0RkWAim.js +0 -34
- package/web/assets/wardley-L42UT6IY-5TKZOOLJ-CNeL6VHE.js +0 -1
- package/web/assets/wardleyDiagram-CUQ6CDDI-boUxiQ5F.js +0 -78
|
@@ -6,15 +6,17 @@ import { finished } from 'stream/promises';
|
|
|
6
6
|
import path from 'path';
|
|
7
7
|
import lbug from '@ladybugdb/core';
|
|
8
8
|
import { closeQueryResults } from './query-result-utils.js';
|
|
9
|
+
import { escapeCypherString } from './cypher-escape.js';
|
|
9
10
|
import { withConnLock } from './conn-lock.js';
|
|
10
11
|
import { isWalDriverActive } from './wal-driver-state.js';
|
|
11
12
|
import { NODE_TABLES, REL_TABLE_NAME, SCHEMA_QUERIES, EMBEDDING_TABLE_NAME, CREATE_VECTOR_INDEX_QUERY, STALE_HASH_SENTINEL, } from './schema.js';
|
|
12
13
|
import { streamAllCSVsToDisk } from './csv-generator.js';
|
|
13
14
|
import { getNodeLabel as deriveNodeLabel } from './rel-pair-routing.js';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
15
|
+
import { EMBEDDABLE_LABELS } from '../embeddings/types.js';
|
|
16
|
+
import { getNodeTableColumnNames } from './node-table-layout.js';
|
|
17
|
+
import { extensionManager, resolveAnalyzeInstallPolicy, } from './extension-loader.js';
|
|
18
|
+
import { classifyDeleteAllError, closeLbugConnection, HANDLE_RELEASE_PROBE_ATTEMPTS, HANDLE_RELEASE_PROBE_DELAY_MS, isDbBusyError, isOpenRetryExhausted, isWalCorruptionError, openLbugConnection, sleep, toNativeSafePath, resolveNativeSafeStorageDir, WAL_RECOVERY_SUGGESTION, waitForWindowsHandleRelease, } from './lbug-config.js';
|
|
19
|
+
import { finalizeLbugSidecarsAfterClose, guardWalQuarantine, isMissingShadowSidecarError, isReadOnlyShadowReplayError, lbugLockRemediation, preflightLbugSidecars, quarantineWalForMissingShadow, renameFailureMessage, shadowSidecarRecoveryMessage, } from './sidecar-recovery.js';
|
|
18
20
|
import { logger } from '../logger.js';
|
|
19
21
|
/**
|
|
20
22
|
* Split a relationship CSV into per-label-pair files on disk.
|
|
@@ -409,23 +411,14 @@ const queryAndDrain = async (targetConn, cypher) => {
|
|
|
409
411
|
};
|
|
410
412
|
const READ_ONLY_SHADOW_REPLAY_PROBE = 'MATCH (n) RETURN n LIMIT 1';
|
|
411
413
|
/**
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
*
|
|
415
|
-
*
|
|
416
|
-
*
|
|
417
|
-
* Throws shadowSidecarRecoveryMessage immediately when the WAL is large,
|
|
418
|
-
* preserving the uncheckpointed pages for explicit operator recovery.
|
|
419
|
-
* Returns silently when the WAL is absent, tiny, or in any other state
|
|
420
|
-
* where the existing recovery path is safe to proceed.
|
|
414
|
+
* Serve-side entry to the shared WAL-quarantine safety gate. Refuses (throws)
|
|
415
|
+
* when the `.shadow` is present on disk or the orphan WAL is too large to
|
|
416
|
+
* safely discard; returns silently otherwise. The policy itself lives in
|
|
417
|
+
* `guardWalQuarantine` (sidecar-recovery.ts) so serve and the MCP pool share
|
|
418
|
+
* one source of truth (PR #1747 review D2; issue #2382 review, Finding B).
|
|
421
419
|
*/
|
|
422
420
|
const refuseLargeWalQuarantine = async (dbPath, mode, triggeringErr) => {
|
|
423
|
-
|
|
424
|
-
if (state.kind === 'orphan-wal') {
|
|
425
|
-
logger.warn(`GitNexus: refusing to quarantine large WAL (${state.walBytes} bytes) at ${dbPath}.wal during ${mode} recovery; ` +
|
|
426
|
-
'manual recovery required — run `gitnexus analyze --force <repo-path> --index-only`.');
|
|
427
|
-
throw new Error(shadowSidecarRecoveryMessage(dbPath, triggeringErr));
|
|
428
|
-
}
|
|
421
|
+
await guardWalQuarantine(dbPath, mode, triggeringErr, logger);
|
|
429
422
|
};
|
|
430
423
|
const reopenReadOnlyAfterMissingShadow = async (dbPath, err) => {
|
|
431
424
|
await refuseLargeWalQuarantine(dbPath, 'read-only', err);
|
|
@@ -673,7 +666,8 @@ const doInitLbug = async (dbPath, readOnly = false) => {
|
|
|
673
666
|
const realPath = await fs.realpath(dbPath);
|
|
674
667
|
const parentDir = path.dirname(dbPath);
|
|
675
668
|
const realParent = await fs.realpath(parentDir);
|
|
676
|
-
|
|
669
|
+
const safePrefix = realParent.endsWith(path.sep) ? realParent : realParent + path.sep;
|
|
670
|
+
if (!realPath.startsWith(safePrefix) && realPath !== realParent) {
|
|
677
671
|
throw new Error(`Refusing to delete ${dbPath}: resolved path ${realPath} is outside storage directory`);
|
|
678
672
|
}
|
|
679
673
|
// Old-style directory database or empty leftover - remove it
|
|
@@ -1091,8 +1085,49 @@ const BACKTICK_TABLES = new Set([
|
|
|
1091
1085
|
const escapeTableName = (table) => {
|
|
1092
1086
|
return BACKTICK_TABLES.has(table) ? `\`${table}\`` : table;
|
|
1093
1087
|
};
|
|
1094
|
-
/**
|
|
1095
|
-
|
|
1088
|
+
/**
|
|
1089
|
+
* Format one JS value as a Cypher literal for the adapter's string-built
|
|
1090
|
+
* statements: NULL/undefined → `NULL`, numbers pass through unquoted,
|
|
1091
|
+
* everything else becomes a single-quoted string literal escaped via
|
|
1092
|
+
* {@link escapeCypherString} (backslashes first, then quotes).
|
|
1093
|
+
*
|
|
1094
|
+
* Replaces three per-function closures that used SQL-style `''` doubling —
|
|
1095
|
+
* LadybugDB REJECTS doubling, so every value containing a quote made the
|
|
1096
|
+
* whole statement a parser error, invisible wherever the call site swallowed
|
|
1097
|
+
* per-row failures (#2409 escaping sweep, completed for tri-review
|
|
1098
|
+
* 4669518496 P2-2). Those closures also rewrote literal `\n`/`\r` into
|
|
1099
|
+
* two-character escape sequences; raw LF/CR are legal inside LadybugDB
|
|
1100
|
+
* single-quoted literals (live-probed on @ladybugdb/core 0.18.0), so the
|
|
1101
|
+
* replaces are gone and content now round-trips byte-identical.
|
|
1102
|
+
*/
|
|
1103
|
+
const formatCypherValue = (v) => {
|
|
1104
|
+
if (v === null || v === undefined)
|
|
1105
|
+
return 'NULL';
|
|
1106
|
+
if (typeof v === 'number')
|
|
1107
|
+
return String(v);
|
|
1108
|
+
return `'${escapeCypherString(String(v))}'`;
|
|
1109
|
+
};
|
|
1110
|
+
const formatCypherStringArray = (value) => {
|
|
1111
|
+
const items = Array.isArray(value)
|
|
1112
|
+
? value.filter((item) => typeof item === 'string')
|
|
1113
|
+
: [];
|
|
1114
|
+
return `[${items.map(formatCypherValue).join(', ')}]`;
|
|
1115
|
+
};
|
|
1116
|
+
/**
|
|
1117
|
+
* Fallback: insert relationships one-by-one if COPY fails.
|
|
1118
|
+
*
|
|
1119
|
+
* Exported for the quoted-id round-trip tests in
|
|
1120
|
+
* `test/integration/lbug-core-adapter.test.ts` (the `DELETE_FILES_CHUNK_SIZE`
|
|
1121
|
+
* exported-for-tests precedent); production callers stay in this module.
|
|
1122
|
+
* Bails silently when the adapter singleton is closed.
|
|
1123
|
+
*
|
|
1124
|
+
* KNOWN PRE-EXISTING NARROWING (distinct from the `''` escaping bug, NOT
|
|
1125
|
+
* fixed here): the row regex below matches CSV fields with `[^"]*`, so an id
|
|
1126
|
+
* containing a double quote (CSV-escaped as `""`) never matches and the edge
|
|
1127
|
+
* is skipped. Tracked as part of the quote-in-id divergence documented in
|
|
1128
|
+
* `rel-pair-routing.ts`.
|
|
1129
|
+
*/
|
|
1130
|
+
export const fallbackRelationshipInserts = async (validRelLines, validTables, getNodeLabel) => {
|
|
1096
1131
|
if (!conn)
|
|
1097
1132
|
return;
|
|
1098
1133
|
const escapeLabel = (label) => {
|
|
@@ -1111,11 +1146,10 @@ const fallbackRelationshipInserts = async (validRelLines, validTables, getNodeLa
|
|
|
1111
1146
|
continue;
|
|
1112
1147
|
const confidence = parseFloat(confidenceStr) || 1.0;
|
|
1113
1148
|
const step = parseInt(stepStr) || 0;
|
|
1114
|
-
const esc = (s) => s.replace(/'/g, "''").replace(/\\/g, '\\\\').replace(/\n/g, '\\n').replace(/\r/g, '\\r');
|
|
1115
1149
|
await queryAndDrain(conn, `
|
|
1116
|
-
MATCH (a:${escapeLabel(fromLabel)} {id:
|
|
1117
|
-
(b:${escapeLabel(toLabel)} {id:
|
|
1118
|
-
CREATE (a)-[:${REL_TABLE_NAME} {type:
|
|
1150
|
+
MATCH (a:${escapeLabel(fromLabel)} {id: ${formatCypherValue(fromId)} }),
|
|
1151
|
+
(b:${escapeLabel(toLabel)} {id: ${formatCypherValue(toId)} })
|
|
1152
|
+
CREATE (a)-[:${REL_TABLE_NAME} {type: ${formatCypherValue(relType)}, confidence: ${confidence}, reason: ${formatCypherValue(reason)}, step: ${step}}]->(b)
|
|
1119
1153
|
`);
|
|
1120
1154
|
}
|
|
1121
1155
|
catch {
|
|
@@ -1131,8 +1165,13 @@ const TABLES_WITH_EXPORTED = new Set([
|
|
|
1131
1165
|
'Method',
|
|
1132
1166
|
'CodeElement',
|
|
1133
1167
|
]);
|
|
1168
|
+
const copyColumns = (columns) => columns.join(', ');
|
|
1134
1169
|
export const getCopyQuery = (table, filePath) => {
|
|
1135
1170
|
const t = escapeTableName(table);
|
|
1171
|
+
const layoutColumns = getNodeTableColumnNames(table);
|
|
1172
|
+
if (layoutColumns) {
|
|
1173
|
+
return `COPY ${t}(${copyColumns(layoutColumns)}) FROM "${filePath}" ${COPY_CSV_OPTS}`;
|
|
1174
|
+
}
|
|
1136
1175
|
if (table === 'File') {
|
|
1137
1176
|
return `COPY ${t}(id, name, filePath, content) FROM "${filePath}" ${COPY_CSV_OPTS}`;
|
|
1138
1177
|
}
|
|
@@ -1160,6 +1199,9 @@ export const getCopyQuery = (table, filePath) => {
|
|
|
1160
1199
|
// `calleeIds` is its SOUND parallel (space-joined resolved callee ids, #2227).
|
|
1161
1200
|
return `COPY ${t}(id, filePath, startLine, endLine, text, callees, calleeIds) FROM "${filePath}" ${COPY_CSV_OPTS}`;
|
|
1162
1201
|
}
|
|
1202
|
+
if (table === 'Class') {
|
|
1203
|
+
return `COPY ${t}(id, name, filePath, startLine, endLine, isExported, content, description, frameworkAnnotations) FROM "${filePath}" ${COPY_CSV_OPTS}`;
|
|
1204
|
+
}
|
|
1163
1205
|
if (table === 'Method') {
|
|
1164
1206
|
return `COPY ${t}(id, name, filePath, startLine, endLine, isExported, content, description, parameterCount, returnType) FROM "${filePath}" ${COPY_CSV_OPTS}`;
|
|
1165
1207
|
}
|
|
@@ -1186,52 +1228,53 @@ export const insertNodeToLbug = async (label, properties, dbPath) => {
|
|
|
1186
1228
|
throw new Error('LadybugDB not initialized. Provide dbPath or call initLbug first.');
|
|
1187
1229
|
}
|
|
1188
1230
|
try {
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
if (typeof v === 'number')
|
|
1193
|
-
return String(v);
|
|
1194
|
-
// Escape backslashes first (for Windows paths), then single quotes
|
|
1195
|
-
return `'${String(v).replace(/\\/g, '\\\\').replace(/'/g, "''").replace(/\n/g, '\\n').replace(/\r/g, '\\r')}'`;
|
|
1196
|
-
};
|
|
1231
|
+
// Values go through the module-scope formatCypherValue — the old local
|
|
1232
|
+
// closure used `''` doubling, which LadybugDB rejects (#2409 escaping
|
|
1233
|
+
// sweep, tri-review 4669518496 P2-2).
|
|
1197
1234
|
// Build INSERT query based on node type
|
|
1198
1235
|
const t = escapeTableName(label);
|
|
1199
1236
|
let query;
|
|
1200
1237
|
if (label === 'File') {
|
|
1201
|
-
query = `CREATE (n:File {id: ${
|
|
1238
|
+
query = `CREATE (n:File {id: ${formatCypherValue(properties.id)}, name: ${formatCypherValue(properties.name)}, filePath: ${formatCypherValue(properties.filePath)}, content: ${formatCypherValue(properties.content || '')}})`;
|
|
1202
1239
|
}
|
|
1203
1240
|
else if (label === 'Folder') {
|
|
1204
|
-
query = `CREATE (n:Folder {id: ${
|
|
1241
|
+
query = `CREATE (n:Folder {id: ${formatCypherValue(properties.id)}, name: ${formatCypherValue(properties.name)}, filePath: ${formatCypherValue(properties.filePath)}})`;
|
|
1205
1242
|
}
|
|
1206
1243
|
else if (label === 'Section') {
|
|
1207
1244
|
const descPart = properties.description
|
|
1208
|
-
? `, description: ${
|
|
1245
|
+
? `, description: ${formatCypherValue(properties.description)}`
|
|
1209
1246
|
: '';
|
|
1210
|
-
query = `CREATE (n:Section {id: ${
|
|
1247
|
+
query = `CREATE (n:Section {id: ${formatCypherValue(properties.id)}, name: ${formatCypherValue(properties.name)}, filePath: ${formatCypherValue(properties.filePath)}, startLine: ${properties.startLine || 0}, endLine: ${properties.endLine || 0}, level: ${properties.level || 1}, content: ${formatCypherValue(properties.content || '')}${descPart}})`;
|
|
1211
1248
|
}
|
|
1212
1249
|
else if (label === 'BasicBlock') {
|
|
1213
1250
|
// Taint/PDG substrate (issue #2080) — no name column. `calleeIds` (#2227)
|
|
1214
1251
|
// is the sound resolved-id parallel to the leaf-name `callees` set.
|
|
1215
|
-
query = `CREATE (n:BasicBlock {id: ${
|
|
1252
|
+
query = `CREATE (n:BasicBlock {id: ${formatCypherValue(properties.id)}, filePath: ${formatCypherValue(properties.filePath)}, startLine: ${properties.startLine || 0}, endLine: ${properties.endLine || 0}, text: ${formatCypherValue(properties.text || '')}, callees: ${formatCypherValue(properties.callees || '')}, calleeIds: ${formatCypherValue(properties.calleeIds || '')}})`;
|
|
1253
|
+
}
|
|
1254
|
+
else if (label === 'Class') {
|
|
1255
|
+
const descPart = properties.description
|
|
1256
|
+
? `, description: ${formatCypherValue(properties.description)}`
|
|
1257
|
+
: '';
|
|
1258
|
+
query = `CREATE (n:Class {id: ${formatCypherValue(properties.id)}, name: ${formatCypherValue(properties.name)}, filePath: ${formatCypherValue(properties.filePath)}, startLine: ${properties.startLine || 0}, endLine: ${properties.endLine || 0}, isExported: ${!!properties.isExported}, content: ${formatCypherValue(properties.content || '')}${descPart}, frameworkAnnotations: ${formatCypherStringArray(properties.frameworkAnnotations)}})`;
|
|
1216
1259
|
}
|
|
1217
1260
|
else if (TABLES_WITH_EXPORTED.has(label)) {
|
|
1218
1261
|
const descPart = properties.description
|
|
1219
|
-
? `, description: ${
|
|
1262
|
+
? `, description: ${formatCypherValue(properties.description)}`
|
|
1220
1263
|
: '';
|
|
1221
|
-
query = `CREATE (n:${t} {id: ${
|
|
1264
|
+
query = `CREATE (n:${t} {id: ${formatCypherValue(properties.id)}, name: ${formatCypherValue(properties.name)}, filePath: ${formatCypherValue(properties.filePath)}, startLine: ${properties.startLine || 0}, endLine: ${properties.endLine || 0}, isExported: ${!!properties.isExported}, content: ${formatCypherValue(properties.content || '')}${descPart}})`;
|
|
1222
1265
|
}
|
|
1223
1266
|
else if (label === 'Property') {
|
|
1224
1267
|
const descPart = properties.description
|
|
1225
|
-
? `, description: ${
|
|
1268
|
+
? `, description: ${formatCypherValue(properties.description)}`
|
|
1226
1269
|
: '';
|
|
1227
|
-
query = `CREATE (n:${t} {id: ${
|
|
1270
|
+
query = `CREATE (n:${t} {id: ${formatCypherValue(properties.id)}, name: ${formatCypherValue(properties.name)}, filePath: ${formatCypherValue(properties.filePath)}, startLine: ${properties.startLine || 0}, endLine: ${properties.endLine || 0}, content: ${formatCypherValue(properties.content || '')}${descPart}, declaredType: ${formatCypherValue(properties.declaredType || '')}})`;
|
|
1228
1271
|
}
|
|
1229
1272
|
else {
|
|
1230
1273
|
// Multi-language tables (Struct, Impl, Trait, Macro, etc.) — no isExported
|
|
1231
1274
|
const descPart = properties.description
|
|
1232
|
-
? `, description: ${
|
|
1275
|
+
? `, description: ${formatCypherValue(properties.description)}`
|
|
1233
1276
|
: '';
|
|
1234
|
-
query = `CREATE (n:${t} {id: ${
|
|
1277
|
+
query = `CREATE (n:${t} {id: ${formatCypherValue(properties.id)}, name: ${formatCypherValue(properties.name)}, filePath: ${formatCypherValue(properties.filePath)}, startLine: ${properties.startLine || 0}, endLine: ${properties.endLine || 0}, content: ${formatCypherValue(properties.content || '')}${descPart}})`;
|
|
1235
1278
|
}
|
|
1236
1279
|
// Use per-query connection if dbPath provided (avoids lock conflicts)
|
|
1237
1280
|
if (targetDbPath) {
|
|
@@ -1266,14 +1309,10 @@ export const insertNodeToLbug = async (label, properties, dbPath) => {
|
|
|
1266
1309
|
export const batchInsertNodesToLbug = async (nodes, dbPath) => {
|
|
1267
1310
|
if (nodes.length === 0)
|
|
1268
1311
|
return { inserted: 0, failed: 0 };
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
return String(v);
|
|
1274
|
-
// Escape backslashes first (for Windows paths), then single quotes, then newlines
|
|
1275
|
-
return `'${String(v).replace(/\\/g, '\\\\').replace(/'/g, "''").replace(/\n/g, '\\n').replace(/\r/g, '\\r')}'`;
|
|
1276
|
-
};
|
|
1312
|
+
// Values go through the module-scope formatCypherValue — the old local
|
|
1313
|
+
// closure used `''` doubling, which LadybugDB rejects; the per-node catch
|
|
1314
|
+
// below counted every quoted value as a silent `failed` (#2409 escaping
|
|
1315
|
+
// sweep, tri-review 4669518496 P2-2).
|
|
1277
1316
|
// Open a single connection for all inserts
|
|
1278
1317
|
const tempHandle = await openLbugConnection(lbug, dbPath);
|
|
1279
1318
|
const tempConn = tempHandle.conn;
|
|
@@ -1286,39 +1325,45 @@ export const batchInsertNodesToLbug = async (nodes, dbPath) => {
|
|
|
1286
1325
|
// Use MERGE instead of CREATE for upsert behavior (handles duplicates gracefully)
|
|
1287
1326
|
const t = escapeTableName(label);
|
|
1288
1327
|
if (label === 'File') {
|
|
1289
|
-
query = `MERGE (n:File {id: ${
|
|
1328
|
+
query = `MERGE (n:File {id: ${formatCypherValue(properties.id)}}) SET n.name = ${formatCypherValue(properties.name)}, n.filePath = ${formatCypherValue(properties.filePath)}, n.content = ${formatCypherValue(properties.content || '')}`;
|
|
1290
1329
|
}
|
|
1291
1330
|
else if (label === 'Folder') {
|
|
1292
|
-
query = `MERGE (n:Folder {id: ${
|
|
1331
|
+
query = `MERGE (n:Folder {id: ${formatCypherValue(properties.id)}}) SET n.name = ${formatCypherValue(properties.name)}, n.filePath = ${formatCypherValue(properties.filePath)}`;
|
|
1293
1332
|
}
|
|
1294
1333
|
else if (label === 'Section') {
|
|
1295
1334
|
const descPart = properties.description
|
|
1296
|
-
? `, n.description = ${
|
|
1335
|
+
? `, n.description = ${formatCypherValue(properties.description)}`
|
|
1297
1336
|
: '';
|
|
1298
|
-
query = `MERGE (n:Section {id: ${
|
|
1337
|
+
query = `MERGE (n:Section {id: ${formatCypherValue(properties.id)}}) SET n.name = ${formatCypherValue(properties.name)}, n.filePath = ${formatCypherValue(properties.filePath)}, n.startLine = ${properties.startLine || 0}, n.endLine = ${properties.endLine || 0}, n.level = ${properties.level || 1}, n.content = ${formatCypherValue(properties.content || '')}${descPart}`;
|
|
1299
1338
|
}
|
|
1300
1339
|
else if (label === 'BasicBlock') {
|
|
1301
1340
|
// Taint/PDG substrate (issue #2080) — no name column. `calleeIds`
|
|
1302
1341
|
// (#2227) is the sound resolved-id parallel to the `callees` set.
|
|
1303
|
-
query = `MERGE (n:BasicBlock {id: ${
|
|
1342
|
+
query = `MERGE (n:BasicBlock {id: ${formatCypherValue(properties.id)}}) SET n.filePath = ${formatCypherValue(properties.filePath)}, n.startLine = ${properties.startLine || 0}, n.endLine = ${properties.endLine || 0}, n.text = ${formatCypherValue(properties.text || '')}, n.callees = ${formatCypherValue(properties.callees || '')}, n.calleeIds = ${formatCypherValue(properties.calleeIds || '')}`;
|
|
1343
|
+
}
|
|
1344
|
+
else if (label === 'Class') {
|
|
1345
|
+
const descPart = properties.description
|
|
1346
|
+
? `, n.description = ${formatCypherValue(properties.description)}`
|
|
1347
|
+
: '';
|
|
1348
|
+
query = `MERGE (n:Class {id: ${formatCypherValue(properties.id)}}) SET n.name = ${formatCypherValue(properties.name)}, n.filePath = ${formatCypherValue(properties.filePath)}, n.startLine = ${properties.startLine || 0}, n.endLine = ${properties.endLine || 0}, n.isExported = ${!!properties.isExported}, n.content = ${formatCypherValue(properties.content || '')}${descPart}, n.frameworkAnnotations = ${formatCypherStringArray(properties.frameworkAnnotations)}`;
|
|
1304
1349
|
}
|
|
1305
1350
|
else if (TABLES_WITH_EXPORTED.has(label)) {
|
|
1306
1351
|
const descPart = properties.description
|
|
1307
|
-
? `, n.description = ${
|
|
1352
|
+
? `, n.description = ${formatCypherValue(properties.description)}`
|
|
1308
1353
|
: '';
|
|
1309
|
-
query = `MERGE (n:${t} {id: ${
|
|
1354
|
+
query = `MERGE (n:${t} {id: ${formatCypherValue(properties.id)}}) SET n.name = ${formatCypherValue(properties.name)}, n.filePath = ${formatCypherValue(properties.filePath)}, n.startLine = ${properties.startLine || 0}, n.endLine = ${properties.endLine || 0}, n.isExported = ${!!properties.isExported}, n.content = ${formatCypherValue(properties.content || '')}${descPart}`;
|
|
1310
1355
|
}
|
|
1311
1356
|
else if (label === 'Property') {
|
|
1312
1357
|
const descPart = properties.description
|
|
1313
|
-
? `, n.description = ${
|
|
1358
|
+
? `, n.description = ${formatCypherValue(properties.description)}`
|
|
1314
1359
|
: '';
|
|
1315
|
-
query = `MERGE (n:${t} {id: ${
|
|
1360
|
+
query = `MERGE (n:${t} {id: ${formatCypherValue(properties.id)}}) SET n.name = ${formatCypherValue(properties.name)}, n.filePath = ${formatCypherValue(properties.filePath)}, n.startLine = ${properties.startLine || 0}, n.endLine = ${properties.endLine || 0}, n.content = ${formatCypherValue(properties.content || '')}${descPart}, n.declaredType = ${formatCypherValue(properties.declaredType || '')}`;
|
|
1316
1361
|
}
|
|
1317
1362
|
else {
|
|
1318
1363
|
const descPart = properties.description
|
|
1319
|
-
? `, n.description = ${
|
|
1364
|
+
? `, n.description = ${formatCypherValue(properties.description)}`
|
|
1320
1365
|
: '';
|
|
1321
|
-
query = `MERGE (n:${t} {id: ${
|
|
1366
|
+
query = `MERGE (n:${t} {id: ${formatCypherValue(properties.id)}}) SET n.name = ${formatCypherValue(properties.name)}, n.filePath = ${formatCypherValue(properties.filePath)}, n.startLine = ${properties.startLine || 0}, n.endLine = ${properties.endLine || 0}, n.content = ${formatCypherValue(properties.content || '')}${descPart}`;
|
|
1322
1367
|
}
|
|
1323
1368
|
await queryAndDrain(tempConn, query);
|
|
1324
1369
|
inserted++;
|
|
@@ -1749,7 +1794,142 @@ export const closeLbug = async () => {
|
|
|
1749
1794
|
vectorIndexEnsured = false;
|
|
1750
1795
|
ensuredFTSIndexes.clear();
|
|
1751
1796
|
};
|
|
1797
|
+
/**
|
|
1798
|
+
* Thrown by {@link wipeLbugDbFiles} when a data-bearing member of the
|
|
1799
|
+
* LadybugDB file family is still present after the bounded
|
|
1800
|
+
* remove-and-verify retries (#2409, tri-review 4669518496 P2-4), and by
|
|
1801
|
+
* run-analyze's dirty-recovery block when the crashed run's sidecars can
|
|
1802
|
+
* neither be parked nor removed (this shipping review, FIX 1 — same lock
|
|
1803
|
+
* class, same remediation, and the CLI already renders this type).
|
|
1804
|
+
*
|
|
1805
|
+
* Classify by TYPE (`err instanceof LbugWipeError`) — the repo norm from
|
|
1806
|
+
* #2385 — never by message text. The MESSAGE is nonetheless fully
|
|
1807
|
+
* self-contained (headline + blocked paths + remediation) because
|
|
1808
|
+
* `gitnexus serve` forwards only `err.message` over worker IPC
|
|
1809
|
+
* (analyze-worker-core.ts), so the serve surface has nothing but this
|
|
1810
|
+
* string to show the user. The holder framing deliberately covers the
|
|
1811
|
+
* own-process case (FIX 2, finder A): the blocking handle is often a
|
|
1812
|
+
* lingering one from THIS process's just-closed DB or a transient AV scan
|
|
1813
|
+
* — not necessarily another process — so an immediate re-run often
|
|
1814
|
+
* succeeds.
|
|
1815
|
+
*/
|
|
1816
|
+
export class LbugWipeError extends Error {
|
|
1817
|
+
/** Paths still present (or unverifiable) after all retries. */
|
|
1818
|
+
survivors;
|
|
1819
|
+
constructor(survivors, options) {
|
|
1820
|
+
super(`${options?.headline ??
|
|
1821
|
+
`Failed to remove the LadybugDB index files — still present after ` +
|
|
1822
|
+
`${HANDLE_RELEASE_PROBE_ATTEMPTS} attempts:`}\n` +
|
|
1823
|
+
survivors.map((p) => ` - ${p}`).join('\n') +
|
|
1824
|
+
`\nThe blocking handle may be another process, a lingering handle from this ` +
|
|
1825
|
+
`process's just-closed database, or an antivirus scan — an immediate re-run ` +
|
|
1826
|
+
`often succeeds. If it persists, ${lbugLockRemediation('re-run the analyze')}.`);
|
|
1827
|
+
this.name = 'LbugWipeError';
|
|
1828
|
+
this.survivors = survivors;
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
/**
|
|
1832
|
+
* Remove the LadybugDB file family and VERIFY each member is really gone.
|
|
1833
|
+
*
|
|
1834
|
+
* Owns the canonical 4-file family list — `<lbugPath>`, `.wal`, `.shadow`,
|
|
1835
|
+
* `.lock` — so run-analyze's two wipe sites (full rebuild + the #2409
|
|
1836
|
+
* escalation valve) can never drift apart. `.shadow` is included because a
|
|
1837
|
+
* checkpoint-in-flight crash leaves a shadow sidecar, and a stale shadow next
|
|
1838
|
+
* to a freshly created DB file is replay poison on the next open (#2409).
|
|
1839
|
+
*
|
|
1840
|
+
* Verification contract (tri-review 4669518496 P2-4 — the old inline loops
|
|
1841
|
+
* swallowed rm failures and let `initLbug` reopen a still-populated DB the
|
|
1842
|
+
* run believed it wiped): after `fs.rm({ recursive, force })`, each path is
|
|
1843
|
+
* probed and counts as GONE only when the probe rejects with **ENOENT**. A
|
|
1844
|
+
* resolving probe, or a rejection in the EPERM/EBUSY/EACCES class (Windows
|
|
1845
|
+
* delete-pending / handle-release lag — see HANDLE_RELEASE_LOCK_CODES in
|
|
1846
|
+
* lbug-config.ts), or any other code means the path is not verifiably gone:
|
|
1847
|
+
* it is retried on the shared handle-release budget
|
|
1848
|
+
* (HANDLE_RELEASE_PROBE_ATTEMPTS × linear HANDLE_RELEASE_PROBE_DELAY_MS,
|
|
1849
|
+
* lbug-config.ts — the previous private mirror constants were
|
|
1850
|
+
* documentation-coupled copies) and then handled by CLASS (this shipping
|
|
1851
|
+
* review, FIX 2):
|
|
1852
|
+
*
|
|
1853
|
+
* - DATA-BEARING members (`<lbugPath>`, `.wal`, `.shadow`) — a survivor
|
|
1854
|
+
* means the reopen would resurrect rows this run believes wiped: throw
|
|
1855
|
+
* a typed {@link LbugWipeError}.
|
|
1856
|
+
* - `.lock` — contentless: `initLbug` recreates it, and a genuinely held
|
|
1857
|
+
* lock surfaces as initLbug's own lock-busy classification (a better
|
|
1858
|
+
* error than this one). A `.lock`-only survivor (an AV-held
|
|
1859
|
+
* delete-pending handle outlasting the budget previously failed a
|
|
1860
|
+
* perfectly sound rebuild) logs a warning and CONTINUES.
|
|
1861
|
+
*
|
|
1862
|
+
* Linux unlinked-but-open (name gone, holder keeps the old inode) probes
|
|
1863
|
+
* ENOENT and is accepted by design — both production wipe sites run after a
|
|
1864
|
+
* real `closeLbug()`.
|
|
1865
|
+
*
|
|
1866
|
+
* Deliberately OUT of this contract: `cleanupOldKuzuFiles`
|
|
1867
|
+
* (repo-manager.ts) sweeps the LEGACY kuzu-era file family during storage
|
|
1868
|
+
* migration — different family, best-effort by design; and
|
|
1869
|
+
* `sweepStaleSidecars` (lbug-config.ts) is a test-fixture-gated open-retry
|
|
1870
|
+
* fallback that must never delete production files. Neither wipes the live
|
|
1871
|
+
* DB the run is about to recreate, so neither needs (or may share) the
|
|
1872
|
+
* loud-failure contract here.
|
|
1873
|
+
*/
|
|
1874
|
+
export const wipeLbugDbFiles = async (lbugPath) => {
|
|
1875
|
+
const lockPath = `${lbugPath}.lock`;
|
|
1876
|
+
const family = [lbugPath, `${lbugPath}.wal`, `${lbugPath}.shadow`, lockPath];
|
|
1877
|
+
let survivors = [];
|
|
1878
|
+
for (let attempt = 1; attempt <= HANDLE_RELEASE_PROBE_ATTEMPTS; attempt++) {
|
|
1879
|
+
survivors = [];
|
|
1880
|
+
for (const f of family) {
|
|
1881
|
+
try {
|
|
1882
|
+
await fs.rm(f, { recursive: true, force: true });
|
|
1883
|
+
}
|
|
1884
|
+
catch {
|
|
1885
|
+
// `force: true` swallows ENOENT, so a rejection is a real failure —
|
|
1886
|
+
// but the ENOENT-probe below stays authoritative either way (another
|
|
1887
|
+
// process may have removed the path between the rm and the probe).
|
|
1888
|
+
}
|
|
1889
|
+
const gone = await fs.access(f).then(() => false, // still present
|
|
1890
|
+
(err) => err?.code === 'ENOENT');
|
|
1891
|
+
if (!gone)
|
|
1892
|
+
survivors.push(f);
|
|
1893
|
+
}
|
|
1894
|
+
if (survivors.length === 0)
|
|
1895
|
+
return;
|
|
1896
|
+
if (attempt < HANDLE_RELEASE_PROBE_ATTEMPTS) {
|
|
1897
|
+
await sleep(HANDLE_RELEASE_PROBE_DELAY_MS * attempt);
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
// Class split (FIX 2): the contentless `.lock` never fails the wipe.
|
|
1901
|
+
const dataSurvivors = survivors.filter((f) => f !== lockPath);
|
|
1902
|
+
if (survivors.includes(lockPath)) {
|
|
1903
|
+
logger.warn(`GitNexus: ${lockPath} is still present after the wipe retries — continuing: the ` +
|
|
1904
|
+
'lock file is contentless and initLbug recreates it; a genuinely held lock will ' +
|
|
1905
|
+
"surface as the reopen's own lock-busy error.");
|
|
1906
|
+
}
|
|
1907
|
+
if (dataSurvivors.length > 0) {
|
|
1908
|
+
throw new LbugWipeError(dataSurvivors);
|
|
1909
|
+
}
|
|
1910
|
+
};
|
|
1752
1911
|
export const isLbugReady = () => conn !== null && db !== null;
|
|
1912
|
+
/**
|
|
1913
|
+
* Multi-label alternation over exactly the labels that can own embedding
|
|
1914
|
+
* rows: EMBEDDABLE_LABELS plus File, which embedding-pipeline.ts embeds as
|
|
1915
|
+
* the zero-symbol fallback for text-only repositories (#2454). Reserved
|
|
1916
|
+
* keywords are backtick-escaped via {@link escapeTableName}. Probed on
|
|
1917
|
+
* @ladybugdb/core 0.18.0 (this shipping review, FIX 4): the full multi-label
|
|
1918
|
+
* alternation parses, executes, and deletes exactly the joined rows —
|
|
1919
|
+
* replacing the unlabeled `MATCH (n)` that scanned EVERY node table per
|
|
1920
|
+
* chunk (BasicBlock-dominated under `--pdg`) when only embeddable labels
|
|
1921
|
+
* can match an embedding row. Including File is free for code repositories:
|
|
1922
|
+
* they never hold File embedding rows, so the extra label joins nothing.
|
|
1923
|
+
*/
|
|
1924
|
+
const embeddableLabelMatch = () => ['File', ...EMBEDDABLE_LABELS].map((l) => escapeTableName(l)).join('|');
|
|
1925
|
+
// LADYBUGDB-CONTRACT: matches @ladybugdb/core ^0.18.0 native binder text,
|
|
1926
|
+
// probe-recorded: `Binder exception: Table CodeEmbedding does not exist.`
|
|
1927
|
+
// When bumping LadybugDB, re-validate — `git grep "LADYBUGDB-CONTRACT"`
|
|
1928
|
+
// enumerates every version-coupled spot.
|
|
1929
|
+
const isMissingEmbeddingTableError = (err) => {
|
|
1930
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1931
|
+
return msg.includes(`Table ${EMBEDDING_TABLE_NAME} does not exist`);
|
|
1932
|
+
};
|
|
1753
1933
|
/**
|
|
1754
1934
|
* Delete all nodes (and their relationships) for a specific file from LadybugDB
|
|
1755
1935
|
* @param filePath - The file path to delete nodes for
|
|
@@ -1772,7 +1952,34 @@ export const deleteNodesForFile = async (filePath, dbPath) => {
|
|
|
1772
1952
|
}
|
|
1773
1953
|
try {
|
|
1774
1954
|
let deletedNodes = 0;
|
|
1775
|
-
const escapedPath = filePath
|
|
1955
|
+
const escapedPath = escapeCypherString(filePath);
|
|
1956
|
+
// Delete the file's embedding rows FIRST, while their owning nodes are
|
|
1957
|
+
// still present: node ids are label-first — generateId = `${label}:${name}`
|
|
1958
|
+
// (src/lib/utils.ts) with qualified names that embed the file path — so
|
|
1959
|
+
// the old `e.nodeId STARTS WITH '<filePath>'` shape never matched a row
|
|
1960
|
+
// (tri-review 4669518496 P2-1). Join through the nodes on exact id
|
|
1961
|
+
// equality instead, scoped to the embeddable labels (FIX 4 — see
|
|
1962
|
+
// embeddableLabelMatch); ordering is load-bearing — after the DETACH
|
|
1963
|
+
// DELETE loop below the join would match nothing.
|
|
1964
|
+
try {
|
|
1965
|
+
await queryAndDrain(targetConn, `MATCH (n:${embeddableLabelMatch()}) WHERE n.filePath = '${escapedPath}' ` +
|
|
1966
|
+
`MATCH (e:${EMBEDDING_TABLE_NAME}) WHERE e.nodeId = n.id DELETE e`);
|
|
1967
|
+
}
|
|
1968
|
+
catch (err) {
|
|
1969
|
+
// Deliberately legacy-permissive (pinned contract:
|
|
1970
|
+
// lbug-conn-serialization U5 and lbug-core-adapter expect this variant
|
|
1971
|
+
// to resolve `{deletedNodes: 0}` even on a bogus dbPath): the singular
|
|
1972
|
+
// variant swallows per-statement failures wholesale — its per-table
|
|
1973
|
+
// loop below does the same — so a partial rethrow here would be
|
|
1974
|
+
// incoherent with the rest of the function. The STRICT
|
|
1975
|
+
// rethrow-except-missing-table policy lives in deleteNodesForFiles,
|
|
1976
|
+
// the #2409 incremental writeback path (FIX 4). The one case worth a
|
|
1977
|
+
// diagnostic is the missing embedding table.
|
|
1978
|
+
if (isMissingEmbeddingTableError(err)) {
|
|
1979
|
+
logger.warn({ err }, `deleteNodesForFile: ${EMBEDDING_TABLE_NAME} table does not exist — ` +
|
|
1980
|
+
'skipping embedding-row deletes for this DB.');
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1776
1983
|
// Delete nodes from each table that has filePath
|
|
1777
1984
|
// DETACH DELETE removes the node and all its relationships
|
|
1778
1985
|
for (const tableName of NODE_TABLES) {
|
|
@@ -1802,13 +2009,6 @@ export const deleteNodesForFile = async (filePath, dbPath) => {
|
|
|
1802
2009
|
// Some tables may not support this query, skip
|
|
1803
2010
|
}
|
|
1804
2011
|
}
|
|
1805
|
-
// Also delete any embeddings for nodes in this file
|
|
1806
|
-
try {
|
|
1807
|
-
await queryAndDrain(targetConn, `MATCH (e:${EMBEDDING_TABLE_NAME}) WHERE e.nodeId STARTS WITH '${escapedPath}' DELETE e`);
|
|
1808
|
-
}
|
|
1809
|
-
catch {
|
|
1810
|
-
// Embedding table may not exist or nodeId format may differ
|
|
1811
|
-
}
|
|
1812
2012
|
return { deletedNodes };
|
|
1813
2013
|
}
|
|
1814
2014
|
finally {
|
|
@@ -1817,6 +2017,91 @@ export const deleteNodesForFile = async (filePath, dbPath) => {
|
|
|
1817
2017
|
await closeLbugConnection(tempHandle);
|
|
1818
2018
|
}
|
|
1819
2019
|
};
|
|
2020
|
+
/**
|
|
2021
|
+
* Chunk size for {@link deleteNodesForFiles}. 200 paths keeps each
|
|
2022
|
+
* statement ~13KB (well inside parser limits) while a ~700-file write set
|
|
2023
|
+
* still collapses from ~13,000 statements to 124: 31 statements per chunk
|
|
2024
|
+
* (1 CodeEmbedding join-delete + 30 filePath-bearing node tables — the
|
|
2025
|
+
* 32-table NODE_TABLES roster minus Community/Process) × 4 chunks. The
|
|
2026
|
+
* original "~40" claim under-counted the per-chunk statement fan-out
|
|
2027
|
+
* (tri-review 4669518496 accuracy sweep).
|
|
2028
|
+
*/
|
|
2029
|
+
export const DELETE_FILES_CHUNK_SIZE = 200;
|
|
2030
|
+
/**
|
|
2031
|
+
* Batched variant of {@link deleteNodesForFile} for the incremental
|
|
2032
|
+
* writeback (#2409). One `DETACH DELETE … WHERE n.filePath IN […]` per
|
|
2033
|
+
* node table per chunk of paths, instead of a count + delete per table
|
|
2034
|
+
* per FILE. The per-file loop issued ~13,000 single-row write
|
|
2035
|
+
* transactions on a ~700-file write set — a WAL-append storm that made
|
|
2036
|
+
* the incremental path slower than a full rebuild and is the write
|
|
2037
|
+
* pattern behind the native mid-writeback deaths reported in #2409.
|
|
2038
|
+
*
|
|
2039
|
+
* NO general error swallowing: a zero-match chunk is a no-op success by
|
|
2040
|
+
* construction (every node table except Community/Process has a filePath
|
|
2041
|
+
* column), so anything thrown here is a real engine failure the caller
|
|
2042
|
+
* must see — silently skipping was exactly how #2409 hid its root cause.
|
|
2043
|
+
* The single tolerated exception (FIX 4) is the missing-embedding-table
|
|
2044
|
+
* binder error on the embedding join-delete: a DB created without
|
|
2045
|
+
* EMBEDDING_SCHEMA cannot own embedding rows, so skipping that one
|
|
2046
|
+
* statement is sound, while failing would brick every incremental run on
|
|
2047
|
+
* such a DB until `--force`. Statement count per chunk is unchanged by the
|
|
2048
|
+
* multi-label join: 1 embedding join-delete + 30 node-table deletes = 31
|
|
2049
|
+
* (the rejected per-label fallback shape would have been 19 + 30 = 49).
|
|
2050
|
+
* Singleton-connection only: the analyze writeback owns the write lock,
|
|
2051
|
+
* and `queryAndDrain` routes through `withConnLock` for it (the WAL
|
|
2052
|
+
* checkpoint driver is live during this).
|
|
2053
|
+
*/
|
|
2054
|
+
export const deleteNodesForFiles = async (filePaths, options = {}) => {
|
|
2055
|
+
if (!conn) {
|
|
2056
|
+
throw new Error('LadybugDB not initialized. Call initLbug first.');
|
|
2057
|
+
}
|
|
2058
|
+
const targetConn = conn;
|
|
2059
|
+
let warnedMissingEmbeddingTable = false;
|
|
2060
|
+
for (let i = 0; i < filePaths.length; i += DELETE_FILES_CHUNK_SIZE) {
|
|
2061
|
+
const chunk = filePaths.slice(i, i + DELETE_FILES_CHUNK_SIZE);
|
|
2062
|
+
const listLiteral = `[${chunk.map((p) => `'${escapeCypherString(p)}'`).join(', ')}]`;
|
|
2063
|
+
// Embedding rows key on their OWNING NODE's id: generateId builds
|
|
2064
|
+
// label-first ids — `${label}:${name}` (src/lib/utils.ts) with qualified
|
|
2065
|
+
// names that embed the file path (e.g. `Function:src/f.ts:fn0:1`) — so
|
|
2066
|
+
// the previous bare-path `e.nodeId STARTS WITH '<filePath>'` OR-chain
|
|
2067
|
+
// could never match anything (tri-review 4669518496 P2-1: the embedding
|
|
2068
|
+
// delete was a no-op). Join through the nodes instead: one multi-label
|
|
2069
|
+
// MATCH over exactly the embeddable labels (FIX 4, probe-proven on
|
|
2070
|
+
// 0.18.0 — see embeddableLabelMatch; the old unlabeled `MATCH (n)`
|
|
2071
|
+
// scanned every node table per chunk, BasicBlock-dominated under
|
|
2072
|
+
// `--pdg`, when only embeddable labels can own rows), and
|
|
2073
|
+
// `e.nodeId = n.id` equality is exact — no `File:a.ts` / `File:a.tsx`
|
|
2074
|
+
// prefix collisions. ORDER IS LOAD-BEARING: this must run BEFORE the
|
|
2075
|
+
// DETACH DELETE loop below — once the nodes are gone the join matches
|
|
2076
|
+
// nothing (empirically verified against @ladybugdb/core 0.18.0).
|
|
2077
|
+
try {
|
|
2078
|
+
await queryAndDrain(targetConn, `MATCH (n:${embeddableLabelMatch()}) WHERE n.filePath IN ${listLiteral} ` +
|
|
2079
|
+
`MATCH (e:${EMBEDDING_TABLE_NAME}) WHERE e.nodeId = n.id DELETE e`);
|
|
2080
|
+
}
|
|
2081
|
+
catch (err) {
|
|
2082
|
+
// Tolerate exactly the missing-embedding-table binder error: a
|
|
2083
|
+
// build-variant DB without EMBEDDING_SCHEMA would otherwise brick
|
|
2084
|
+
// every incremental run until `--force` (FIX 4). The no-swallow
|
|
2085
|
+
// policy stays for every real failure — anything else rethrows.
|
|
2086
|
+
if (!isMissingEmbeddingTableError(err))
|
|
2087
|
+
throw err;
|
|
2088
|
+
if (!warnedMissingEmbeddingTable) {
|
|
2089
|
+
warnedMissingEmbeddingTable = true;
|
|
2090
|
+
logger.warn({ err }, `deleteNodesForFiles: ${EMBEDDING_TABLE_NAME} table does not exist — ` +
|
|
2091
|
+
'skipping embedding-row deletes for this writeback.');
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
for (const tableName of NODE_TABLES) {
|
|
2095
|
+
// Community/Process are graph-wide (no filePath); the orchestrator
|
|
2096
|
+
// drops them wholesale via deleteAllCommunitiesAndProcesses.
|
|
2097
|
+
if (tableName === 'Community' || tableName === 'Process')
|
|
2098
|
+
continue;
|
|
2099
|
+
const tn = escapeTableName(tableName);
|
|
2100
|
+
await queryAndDrain(targetConn, `MATCH (n:${tn}) WHERE n.filePath IN ${listLiteral} DETACH DELETE n`);
|
|
2101
|
+
}
|
|
2102
|
+
options.onChunk?.(Math.min(i + DELETE_FILES_CHUNK_SIZE, filePaths.length), filePaths.length);
|
|
2103
|
+
}
|
|
2104
|
+
};
|
|
1820
2105
|
export const getEmbeddingTableName = () => EMBEDDING_TABLE_NAME;
|
|
1821
2106
|
/**
|
|
1822
2107
|
* Return the distinct repo-relative paths of files that import
|
|
@@ -1838,7 +2123,7 @@ export const queryImporters = async (targetFilePath) => {
|
|
|
1838
2123
|
if (!c) {
|
|
1839
2124
|
throw new Error('LadybugDB not initialized. Call initLbug first.');
|
|
1840
2125
|
}
|
|
1841
|
-
const escaped = targetFilePath
|
|
2126
|
+
const escaped = escapeCypherString(targetFilePath);
|
|
1842
2127
|
const cypher = `
|
|
1843
2128
|
MATCH (a)-[r:${REL_TABLE_NAME}]->(b)
|
|
1844
2129
|
WHERE r.type = 'IMPORTS' AND b.filePath = '${escaped}'
|
|
@@ -1870,6 +2155,70 @@ export const queryImporters = async (targetFilePath) => {
|
|
|
1870
2155
|
}
|
|
1871
2156
|
});
|
|
1872
2157
|
};
|
|
2158
|
+
/**
|
|
2159
|
+
* Batched variant of {@link queryImporters} for the incremental importer
|
|
2160
|
+
* BFS (#2409): distinct importers of ANY of the target paths, one query per
|
|
2161
|
+
* chunk per BFS depth instead of one query per frontier FILE (a ~700-file
|
|
2162
|
+
* frontier was ~700 sequential round-trips, each taking the connection lock
|
|
2163
|
+
* against the live WAL checkpoint driver — ~5.6s of the writeback measured).
|
|
2164
|
+
*
|
|
2165
|
+
* Same contract as the singular form: reads the pre-pipeline DB state and
|
|
2166
|
+
* swallows per-chunk query failures into a smaller result (correctness
|
|
2167
|
+
* degrades on that branch — under-expansion means possibly-stale edges —
|
|
2168
|
+
* but the DB stays writable and the writeback proceeds). Unlike the singular
|
|
2169
|
+
* form the degradation is not silent (tri-review 4669518496 P2-5): every
|
|
2170
|
+
* dropped chunk is logged and reported through `options.onChunkFailure`, so
|
|
2171
|
+
* the orchestrator can count it into the #2410 crash diagnostics
|
|
2172
|
+
* (`incrementalInProgress.droppedImporterChunks`).
|
|
2173
|
+
*/
|
|
2174
|
+
export const queryImportersBatch = async (targetFilePaths, options = {}) => {
|
|
2175
|
+
const c = conn;
|
|
2176
|
+
if (!c) {
|
|
2177
|
+
throw new Error('LadybugDB not initialized. Call initLbug first.');
|
|
2178
|
+
}
|
|
2179
|
+
const importers = new Set();
|
|
2180
|
+
for (let i = 0; i < targetFilePaths.length; i += DELETE_FILES_CHUNK_SIZE) {
|
|
2181
|
+
// `i` only ever advances in whole chunk strides, so this is exact.
|
|
2182
|
+
const chunkIndex = i / DELETE_FILES_CHUNK_SIZE;
|
|
2183
|
+
const chunk = targetFilePaths.slice(i, i + DELETE_FILES_CHUNK_SIZE);
|
|
2184
|
+
const listLiteral = `[${chunk.map((p) => `'${escapeCypherString(p)}'`).join(', ')}]`;
|
|
2185
|
+
const cypher = `
|
|
2186
|
+
MATCH (a)-[r:${REL_TABLE_NAME}]->(b)
|
|
2187
|
+
WHERE r.type = 'IMPORTS' AND b.filePath IN ${listLiteral}
|
|
2188
|
+
RETURN DISTINCT a.filePath AS importer
|
|
2189
|
+
`;
|
|
2190
|
+
await withConnLock(async () => {
|
|
2191
|
+
let queryResult;
|
|
2192
|
+
try {
|
|
2193
|
+
queryResult = await c.query(cypher);
|
|
2194
|
+
const result = Array.isArray(queryResult) ? queryResult[0] : queryResult;
|
|
2195
|
+
const rows = await result.getAll();
|
|
2196
|
+
for (const row of rows) {
|
|
2197
|
+
const v = row.importer;
|
|
2198
|
+
if (typeof v === 'string' && v.length > 0)
|
|
2199
|
+
importers.add(v);
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
catch (err) {
|
|
2203
|
+
// Degrade-don't-fail, mirroring queryImporters — but LOUDLY
|
|
2204
|
+
// (tri-review 4669518496 P2-5): a dropped chunk means every importer
|
|
2205
|
+
// it would have surfaced keeps possibly-stale edges this run, and the
|
|
2206
|
+
// old bare `catch {}` left no trace of that anywhere. pino idiom:
|
|
2207
|
+
// `err` key — `error` serializes to `{}`.
|
|
2208
|
+
logger.warn({ err }, `Incremental importer BFS: dropped chunk ${chunkIndex} (${chunk.length} target path(s)) — ` +
|
|
2209
|
+
'importer expansion degrades for this run; affected importers may keep stale edges until the next full rebuild.');
|
|
2210
|
+
options.onChunkFailure?.(chunkIndex, chunk.length, err);
|
|
2211
|
+
}
|
|
2212
|
+
finally {
|
|
2213
|
+
if (queryResult)
|
|
2214
|
+
await closeQueryResults(queryResult);
|
|
2215
|
+
}
|
|
2216
|
+
});
|
|
2217
|
+
}
|
|
2218
|
+
// Cypher without ORDER BY is unordered — sort so downstream chunking and
|
|
2219
|
+
// logs are stable run-to-run (matches diffFileHashes' sorted outputs).
|
|
2220
|
+
return [...importers].sort();
|
|
2221
|
+
};
|
|
1873
2222
|
/**
|
|
1874
2223
|
* Drop every Community and Process node (and their MEMBER_OF /
|
|
1875
2224
|
* STEP_IN_PROCESS edges via DETACH DELETE). Used at the start of an
|
|
@@ -2052,16 +2401,16 @@ export const loadVectorExtension = async (targetConn, opts = {}) => {
|
|
|
2052
2401
|
const useModuleState = targetConn === undefined;
|
|
2053
2402
|
if (useModuleState && vectorExtensionLoaded)
|
|
2054
2403
|
return true;
|
|
2055
|
-
//
|
|
2056
|
-
//
|
|
2057
|
-
//
|
|
2058
|
-
//
|
|
2059
|
-
//
|
|
2060
|
-
//
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2404
|
+
// No platform gate. Windows was hard-refused here for years on the strength
|
|
2405
|
+
// of an early-era report that in-process INSTALL VECTOR could SIGSEGV
|
|
2406
|
+
// (#1365) — but the extension server ships win_amd64 VECTOR artifacts for
|
|
2407
|
+
// every 0.18.x extension version (probed live: v0.18.0 and v0.18.1 both
|
|
2408
|
+
// serve a real PE32+ DLL; the pinned 0.18.2 core resolves its extension
|
|
2409
|
+
// directory to 0.18.1, strace-verified), and INSTALL now runs in a spawned
|
|
2410
|
+
// child process (installDuckDbExtensionOutOfProcess), so even a crashing
|
|
2411
|
+
// installer kills only the child and degrades to `false` here. LOAD of a
|
|
2412
|
+
// present extension file is an ordinary in-process load whose failures
|
|
2413
|
+
// surface as catchable errors, exactly like FTS.
|
|
2065
2414
|
const c = targetConn ?? conn;
|
|
2066
2415
|
if (!c) {
|
|
2067
2416
|
throw new Error('LadybugDB not initialized. Call initLbug first.');
|
|
@@ -2155,6 +2504,74 @@ export const createVectorIndex = async () => {
|
|
|
2155
2504
|
throw e;
|
|
2156
2505
|
}
|
|
2157
2506
|
};
|
|
2507
|
+
/**
|
|
2508
|
+
* Make DML against {@link EMBEDDING_TABLE_NAME} legal on the writable
|
|
2509
|
+
* connection when it can be, and report whether it is.
|
|
2510
|
+
*
|
|
2511
|
+
* LadybugDB refuses EVERY mutation of a table carrying an HNSW index while
|
|
2512
|
+
* the VECTOR extension is not loaded on that connection: `DELETE` fails with
|
|
2513
|
+
* "Trying to delete from an index on table CodeEmbedding but its extension is
|
|
2514
|
+
* not loaded", `CREATE` with the matching "insert into an index" variant,
|
|
2515
|
+
* `DROP TABLE` is refused while the index references it, and `SET` — even on
|
|
2516
|
+
* a NON-indexed property — segfaults the process outright. Probed against
|
|
2517
|
+
* @ladybugdb/core 0.18.2 (the lockfile-pinned version) and 0.18.0 — every
|
|
2518
|
+
* result identical on both (#2623).
|
|
2519
|
+
*
|
|
2520
|
+
* Dropping the index is NOT an available recovery: `CALL DROP_VECTOR_INDEX`
|
|
2521
|
+
* is itself a VECTOR-extension function and resolves to "Catalog exception:
|
|
2522
|
+
* function DROP_VECTOR_INDEX is not defined" in exactly the state it would
|
|
2523
|
+
* need to rescue. Loading the extension is the only in-place repair, which is
|
|
2524
|
+
* why this returns a verdict instead of attempting a fixup.
|
|
2525
|
+
*
|
|
2526
|
+
* `true` = embedding-row DML is safe: either VECTOR is now loaded, or the
|
|
2527
|
+
* table carries no index to trip over. `false` = genuinely blocked (index
|
|
2528
|
+
* present, extension unloadable); the analyze orchestrator answers that by
|
|
2529
|
+
* escalating to the wipe-and-rebuild write plan instead of failing
|
|
2530
|
+
* mid-writeback.
|
|
2531
|
+
*
|
|
2532
|
+
* Cheap by construction: one local `SHOW_INDEXES` read settles the common
|
|
2533
|
+
* "this repo never built an embedding index" case without touching the
|
|
2534
|
+
* extension machinery at all, so a VECTOR-less machine is not charged a
|
|
2535
|
+
* bounded INSTALL attempt on every incremental analyze. `SHOW_INDEXES` is
|
|
2536
|
+
* readable WITHOUT the extension and reports `extension_loaded` per index, so
|
|
2537
|
+
* no error-string sniffing is needed; it runs through the unprepared
|
|
2538
|
+
* `conn.query()` path like every other `CALL` procedure here (#2114).
|
|
2539
|
+
*/
|
|
2540
|
+
export const ensureEmbeddingRowDmlSafe = async () => {
|
|
2541
|
+
const targetConn = conn;
|
|
2542
|
+
if (!targetConn) {
|
|
2543
|
+
throw new Error('LadybugDB not initialized. Call initLbug first.');
|
|
2544
|
+
}
|
|
2545
|
+
// Catalog FIRST. The overwhelmingly common case on a repo that never enabled
|
|
2546
|
+
// embeddings is "no index at all", and that is provable with one local read
|
|
2547
|
+
// — no extension needed. Loading first would make every incremental analyze
|
|
2548
|
+
// on a VECTOR-less machine pay a bounded out-of-process INSTALL attempt (the
|
|
2549
|
+
// `auto` policy) plus an "extension unavailable" warning, for a repo that
|
|
2550
|
+
// can never hit this hazard.
|
|
2551
|
+
let indexRows;
|
|
2552
|
+
try {
|
|
2553
|
+
indexRows = await withConnLock(async () => readQueryRows(await targetConn.query('CALL SHOW_INDEXES() RETURN *')));
|
|
2554
|
+
}
|
|
2555
|
+
catch (err) {
|
|
2556
|
+
// Fall through to the load attempt: unable to prove the index is absent,
|
|
2557
|
+
// so the extension is the only thing that can make DML safe.
|
|
2558
|
+
logger.warn({ err }, `Could not read the index catalog to check for a ${EMBEDDING_TABLE_NAME} vector index; ` +
|
|
2559
|
+
'falling back to loading the VECTOR extension.');
|
|
2560
|
+
}
|
|
2561
|
+
// Any non-HASH index on the embedding table gates DML. Keyed on index TYPE,
|
|
2562
|
+
// not name, so an index built under a different name still counts; the
|
|
2563
|
+
// implicit primary-key HASH index is engine-internal and never gates.
|
|
2564
|
+
const indexGatesDml = indexRows === undefined ||
|
|
2565
|
+
indexRows.some((row) => {
|
|
2566
|
+
const table = row?.table_name ?? row?.[0];
|
|
2567
|
+
if (table !== EMBEDDING_TABLE_NAME)
|
|
2568
|
+
return false;
|
|
2569
|
+
return (row?.index_type ?? row?.[2]) !== 'HASH';
|
|
2570
|
+
});
|
|
2571
|
+
if (!indexGatesDml)
|
|
2572
|
+
return true;
|
|
2573
|
+
return await loadVectorExtension(undefined, { policy: resolveAnalyzeInstallPolicy() });
|
|
2574
|
+
};
|
|
2158
2575
|
/**
|
|
2159
2576
|
* Lazy-create an FTS index, caching the fact in-process.
|
|
2160
2577
|
*
|
|
@@ -2232,7 +2649,28 @@ export const queryFTS = async (tableName, indexName, query, limit = 20, conjunct
|
|
|
2232
2649
|
}
|
|
2233
2650
|
};
|
|
2234
2651
|
/**
|
|
2235
|
-
*
|
|
2652
|
+
* True for the two benign "nothing to drop" `DROP_FTS_INDEX` failures —
|
|
2653
|
+
* both catalog/binder exceptions, LadybugDB's classes for "this name isn't
|
|
2654
|
+
* bound to anything right now" (probe-verified end-to-end through
|
|
2655
|
+
* `dropFTSIndex`'s real `conn.query()` path against @ladybugdb/core
|
|
2656
|
+
* 0.18.x): the named index was never created (`Binder exception: Table <T>
|
|
2657
|
+
* doesn't have an index with name <name>.`), or the FTS extension/function
|
|
2658
|
+
* isn't registered at all (`Catalog exception: function DROP_FTS_INDEX is
|
|
2659
|
+
* not defined...`). A real engine failure — e.g. the `Runtime exception:
|
|
2660
|
+
* FTS index '<name>' is inconsistent: ...` class from #2589 — is a
|
|
2661
|
+
* DIFFERENT exception class (an execution-time failure, not a catalog/bind
|
|
2662
|
+
* lookup miss), so this returns false for it. Anchored to the START of the
|
|
2663
|
+
* message (not a bare substring search): every probed LadybugDB error leads
|
|
2664
|
+
* with its exception class, and anchoring means a future message that merely
|
|
2665
|
+
* mentions "Binder exception" or "Catalog exception" further in in the body
|
|
2666
|
+
* of an otherwise-genuine failure can't be misclassified as benign. Pure
|
|
2667
|
+
* string logic so it is unit-testable without a native LadybugDB connection.
|
|
2668
|
+
*/
|
|
2669
|
+
export const isBenignDropFtsIndexError = (message) => message.startsWith('Binder exception:') || message.startsWith('Catalog exception:');
|
|
2670
|
+
/**
|
|
2671
|
+
* Drop an FTS index. Tolerates only {@link isBenignDropFtsIndexError} —
|
|
2672
|
+
* anything else rethrows instead of being silently masked, which previously
|
|
2673
|
+
* let a corrupted index persist across analyze runs undetected.
|
|
2236
2674
|
*/
|
|
2237
2675
|
export const dropFTSIndex = async (tableName, indexName) => {
|
|
2238
2676
|
if (!conn) {
|
|
@@ -2241,8 +2679,11 @@ export const dropFTSIndex = async (tableName, indexName) => {
|
|
|
2241
2679
|
try {
|
|
2242
2680
|
await queryAndDrain(conn, `CALL DROP_FTS_INDEX('${tableName}', '${indexName}')`);
|
|
2243
2681
|
}
|
|
2244
|
-
catch {
|
|
2245
|
-
|
|
2682
|
+
catch (e) {
|
|
2683
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
2684
|
+
if (!isBenignDropFtsIndexError(msg)) {
|
|
2685
|
+
throw e;
|
|
2686
|
+
}
|
|
2246
2687
|
}
|
|
2247
2688
|
finally {
|
|
2248
2689
|
ensuredFTSIndexes.delete(ftsIndexKey(tableName, indexName));
|