gitnexus 1.6.8-rc.9 → 1.6.8
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 +3 -3
- package/dist/_shared/graph/types.d.ts +31 -1
- package/dist/_shared/graph/types.d.ts.map +1 -1
- package/dist/_shared/language-detection.d.ts.map +1 -1
- package/dist/_shared/language-detection.js +11 -1
- package/dist/_shared/language-detection.js.map +1 -1
- package/dist/_shared/lbug/schema-constants.d.ts +1 -1
- package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
- package/dist/_shared/lbug/schema-constants.js +6 -0
- package/dist/_shared/lbug/schema-constants.js.map +1 -1
- package/dist/_shared/scope-resolution/parsed-file.d.ts +21 -0
- package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/symbol-definition.d.ts +6 -0
- package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
- package/dist/cli/ai-context.d.ts +31 -1
- package/dist/cli/ai-context.js +18 -16
- package/dist/cli/analyze-config.js +11 -0
- package/dist/cli/analyze.d.ts +14 -0
- package/dist/cli/analyze.js +98 -0
- package/dist/cli/embedding-dims.d.ts +33 -0
- package/dist/cli/embedding-dims.js +39 -0
- package/dist/cli/eval-server.d.ts +8 -0
- package/dist/cli/eval-server.js +242 -2
- package/dist/cli/help-i18n.js +19 -0
- package/dist/cli/i18n/en.d.ts +15 -1
- package/dist/cli/i18n/en.js +15 -1
- package/dist/cli/i18n/resources.d.ts +29 -1
- package/dist/cli/i18n/zh-CN.d.ts +14 -0
- package/dist/cli/i18n/zh-CN.js +15 -1
- package/dist/cli/index.js +89 -1
- package/dist/cli/mcp.d.ts +6 -1
- package/dist/cli/mcp.js +26 -1
- package/dist/cli/optional-grammars.d.ts +6 -8
- package/dist/cli/optional-grammars.js +8 -11
- package/dist/cli/setup.d.ts +3 -1
- package/dist/cli/setup.js +67 -17
- package/dist/cli/skill-gen.js +2 -1
- package/dist/cli/tool.d.ts +19 -1
- package/dist/cli/tool.js +96 -3
- package/dist/core/embeddings/hf-env.d.ts +0 -66
- package/dist/core/embeddings/http-client.d.ts +7 -0
- package/dist/core/embeddings/http-client.js +5 -3
- package/dist/core/embeddings/server-mapping.d.ts +1 -1
- package/dist/core/embeddings/server-mapping.js +7 -3
- package/dist/core/graph/import-cycles.d.ts +10 -0
- package/dist/core/graph/import-cycles.js +103 -0
- package/dist/core/group/cross-impact.d.ts +2 -1
- package/dist/core/group/cross-impact.js +5 -1
- package/dist/core/group/extractors/grpc-patterns/proto.js +10 -6
- package/dist/core/group/extractors/http-patterns/kotlin.js +7 -7
- package/dist/core/group/extractors/http-route-extractor.d.ts +24 -0
- package/dist/core/group/extractors/http-route-extractor.js +22 -4
- package/dist/core/group/extractors/include-extractor.js +22 -13
- package/dist/core/group/sync.d.ts +27 -1
- package/dist/core/group/sync.js +0 -0
- package/dist/core/incremental/subgraph-extract.js +18 -1
- package/dist/core/ingestion/cfg/cfg-builder.d.ts +114 -0
- package/dist/core/ingestion/cfg/cfg-builder.js +196 -0
- package/dist/core/ingestion/cfg/collect.d.ts +51 -0
- package/dist/core/ingestion/cfg/collect.js +77 -0
- package/dist/core/ingestion/cfg/control-dependence.d.ts +58 -0
- package/dist/core/ingestion/cfg/control-dependence.js +182 -0
- package/dist/core/ingestion/cfg/control-flow-context.d.ts +106 -0
- package/dist/core/ingestion/cfg/control-flow-context.js +124 -0
- package/dist/core/ingestion/cfg/emit.d.ts +293 -0
- package/dist/core/ingestion/cfg/emit.js +646 -0
- package/dist/core/ingestion/cfg/post-dominators.d.ts +79 -0
- package/dist/core/ingestion/cfg/post-dominators.js +176 -0
- package/dist/core/ingestion/cfg/reaching-def-reason-codec.d.ts +106 -0
- package/dist/core/ingestion/cfg/reaching-def-reason-codec.js +133 -0
- package/dist/core/ingestion/cfg/reaching-defs-graph.d.ts +18 -0
- package/dist/core/ingestion/cfg/reaching-defs-graph.js +312 -0
- package/dist/core/ingestion/cfg/reaching-defs.d.ts +162 -0
- package/dist/core/ingestion/cfg/reaching-defs.js +785 -0
- package/dist/core/ingestion/cfg/synthetic-escape.d.ts +124 -0
- package/dist/core/ingestion/cfg/synthetic-escape.js +300 -0
- package/dist/core/ingestion/cfg/traversal-result.d.ts +20 -0
- package/dist/core/ingestion/cfg/traversal-result.js +2 -0
- package/dist/core/ingestion/cfg/types.d.ts +274 -0
- package/dist/core/ingestion/cfg/types.js +13 -0
- package/dist/core/ingestion/cfg/visitors/c-cpp-harvest.d.ts +111 -0
- package/dist/core/ingestion/cfg/visitors/c-cpp-harvest.js +541 -0
- package/dist/core/ingestion/cfg/visitors/c-cpp.d.ts +74 -0
- package/dist/core/ingestion/cfg/visitors/c-cpp.js +584 -0
- package/dist/core/ingestion/cfg/visitors/call-site-harvest.d.ts +195 -0
- package/dist/core/ingestion/cfg/visitors/call-site-harvest.js +350 -0
- package/dist/core/ingestion/cfg/visitors/csharp-harvest.d.ts +124 -0
- package/dist/core/ingestion/cfg/visitors/csharp-harvest.js +589 -0
- package/dist/core/ingestion/cfg/visitors/csharp.d.ts +87 -0
- package/dist/core/ingestion/cfg/visitors/csharp.js +867 -0
- package/dist/core/ingestion/cfg/visitors/dart-harvest.d.ts +278 -0
- package/dist/core/ingestion/cfg/visitors/dart-harvest.js +870 -0
- package/dist/core/ingestion/cfg/visitors/dart.d.ts +119 -0
- package/dist/core/ingestion/cfg/visitors/dart.js +836 -0
- package/dist/core/ingestion/cfg/visitors/go-harvest.d.ts +160 -0
- package/dist/core/ingestion/cfg/visitors/go-harvest.js +621 -0
- package/dist/core/ingestion/cfg/visitors/go.d.ts +108 -0
- package/dist/core/ingestion/cfg/visitors/go.js +638 -0
- package/dist/core/ingestion/cfg/visitors/java-harvest.d.ts +108 -0
- package/dist/core/ingestion/cfg/visitors/java-harvest.js +513 -0
- package/dist/core/ingestion/cfg/visitors/java.d.ts +101 -0
- package/dist/core/ingestion/cfg/visitors/java.js +812 -0
- package/dist/core/ingestion/cfg/visitors/kotlin-harvest.d.ts +253 -0
- package/dist/core/ingestion/cfg/visitors/kotlin-harvest.js +719 -0
- package/dist/core/ingestion/cfg/visitors/kotlin.d.ts +121 -0
- package/dist/core/ingestion/cfg/visitors/kotlin.js +809 -0
- package/dist/core/ingestion/cfg/visitors/php-harvest.d.ts +172 -0
- package/dist/core/ingestion/cfg/visitors/php-harvest.js +626 -0
- package/dist/core/ingestion/cfg/visitors/php.d.ts +96 -0
- package/dist/core/ingestion/cfg/visitors/php.js +721 -0
- package/dist/core/ingestion/cfg/visitors/python-harvest.d.ts +212 -0
- package/dist/core/ingestion/cfg/visitors/python-harvest.js +739 -0
- package/dist/core/ingestion/cfg/visitors/python.d.ts +103 -0
- package/dist/core/ingestion/cfg/visitors/python.js +558 -0
- package/dist/core/ingestion/cfg/visitors/ruby-harvest.d.ts +223 -0
- package/dist/core/ingestion/cfg/visitors/ruby-harvest.js +587 -0
- package/dist/core/ingestion/cfg/visitors/ruby.d.ts +112 -0
- package/dist/core/ingestion/cfg/visitors/ruby.js +756 -0
- package/dist/core/ingestion/cfg/visitors/rust-harvest.d.ts +324 -0
- package/dist/core/ingestion/cfg/visitors/rust-harvest.js +873 -0
- package/dist/core/ingestion/cfg/visitors/rust.d.ts +95 -0
- package/dist/core/ingestion/cfg/visitors/rust.js +558 -0
- package/dist/core/ingestion/cfg/visitors/scope-tree-harvest.d.ts +87 -0
- package/dist/core/ingestion/cfg/visitors/scope-tree-harvest.js +116 -0
- package/dist/core/ingestion/cfg/visitors/swift-harvest.d.ts +255 -0
- package/dist/core/ingestion/cfg/visitors/swift-harvest.js +679 -0
- package/dist/core/ingestion/cfg/visitors/swift.d.ts +116 -0
- package/dist/core/ingestion/cfg/visitors/swift.js +787 -0
- package/dist/core/ingestion/cfg/visitors/typescript-harvest.d.ts +149 -0
- package/dist/core/ingestion/cfg/visitors/typescript-harvest.js +1056 -0
- package/dist/core/ingestion/cfg/visitors/typescript.d.ts +66 -0
- package/dist/core/ingestion/cfg/visitors/typescript.js +583 -0
- package/dist/core/ingestion/import-resolvers/utils.js +2 -0
- package/dist/core/ingestion/language-provider.d.ts +11 -0
- package/dist/core/ingestion/languages/c-cpp.js +6 -1
- package/dist/core/ingestion/languages/cobol.js +4 -0
- package/dist/core/ingestion/languages/cpp/arity-metadata.js +41 -2
- package/dist/core/ingestion/languages/cpp/captures.js +50 -1
- package/dist/core/ingestion/languages/cpp/conversion-rank.d.ts +2 -0
- package/dist/core/ingestion/languages/cpp/conversion-rank.js +89 -0
- package/dist/core/ingestion/languages/cpp/header-scan.d.ts +1 -1
- package/dist/core/ingestion/languages/cpp/header-scan.js +2 -2
- package/dist/core/ingestion/languages/cpp/inline-namespaces.js +7 -2
- package/dist/core/ingestion/languages/cpp/member-lookup.js +2 -1
- package/dist/core/ingestion/languages/cpp/query.js +23 -0
- package/dist/core/ingestion/languages/cpp/scope-resolver.js +2 -1
- package/dist/core/ingestion/languages/csharp.js +2 -0
- package/dist/core/ingestion/languages/dart.js +2 -0
- package/dist/core/ingestion/languages/go.js +2 -0
- package/dist/core/ingestion/languages/java.js +3 -0
- package/dist/core/ingestion/languages/kotlin/query.js +3 -2
- package/dist/core/ingestion/languages/kotlin.js +3 -0
- package/dist/core/ingestion/languages/php.js +2 -0
- package/dist/core/ingestion/languages/python.js +2 -0
- package/dist/core/ingestion/languages/ruby.js +2 -0
- package/dist/core/ingestion/languages/rust.js +2 -0
- package/dist/core/ingestion/languages/swift.js +2 -0
- package/dist/core/ingestion/languages/typescript.js +5 -0
- package/dist/core/ingestion/languages/vue.js +6 -0
- package/dist/core/ingestion/method-extractors/configs/c-cpp.js +6 -13
- package/dist/core/ingestion/method-extractors/generic.js +1 -0
- package/dist/core/ingestion/method-types.d.ts +2 -0
- package/dist/core/ingestion/model/symbol-table.d.ts +1 -0
- package/dist/core/ingestion/model/symbol-table.js +1 -0
- package/dist/core/ingestion/mro-processor.js +109 -0
- package/dist/core/ingestion/parsing-processor.js +23 -0
- package/dist/core/ingestion/pipeline-phases/call-summaries.d.ts +26 -0
- package/dist/core/ingestion/pipeline-phases/call-summaries.js +46 -0
- 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.d.ts +0 -21
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +18 -1
- package/dist/core/ingestion/pipeline-phases/routes.d.ts +10 -0
- package/dist/core/ingestion/pipeline-phases/routes.js +30 -1
- package/dist/core/ingestion/pipeline-phases/taint-summaries.d.ts +30 -0
- package/dist/core/ingestion/pipeline-phases/taint-summaries.js +81 -0
- package/dist/core/ingestion/pipeline.d.ts +92 -10
- package/dist/core/ingestion/pipeline.js +11 -2
- package/dist/core/ingestion/scope-extractor.js +12 -2
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +9 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.d.ts +75 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.js +67 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +12 -2
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +14 -2
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +6 -1
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +13 -1
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +9 -1
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +28 -2
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +4 -0
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +9 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +6 -1
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +174 -40
- package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +24 -0
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +276 -190
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +43 -3
- package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +75 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +349 -1
- package/dist/core/ingestion/scope-resolution/resolution-outcome.d.ts +1 -1
- package/dist/core/ingestion/taint/call-summary-codec.d.ts +75 -0
- package/dist/core/ingestion/taint/call-summary-codec.js +128 -0
- package/dist/core/ingestion/taint/call-summary-emit.d.ts +43 -0
- package/dist/core/ingestion/taint/call-summary-emit.js +70 -0
- package/dist/core/ingestion/taint/call-summary-harvest.d.ts +77 -0
- package/dist/core/ingestion/taint/call-summary-harvest.js +179 -0
- package/dist/core/ingestion/taint/call-summary-model.d.ts +47 -0
- package/dist/core/ingestion/taint/call-summary-model.js +25 -0
- package/dist/core/ingestion/taint/emit.d.ts +125 -0
- package/dist/core/ingestion/taint/emit.js +208 -0
- package/dist/core/ingestion/taint/interproc-emit.d.ts +50 -0
- package/dist/core/ingestion/taint/interproc-emit.js +88 -0
- package/dist/core/ingestion/taint/interproc-solver.d.ts +110 -0
- package/dist/core/ingestion/taint/interproc-solver.js +312 -0
- package/dist/core/ingestion/taint/match.d.ts +153 -0
- package/dist/core/ingestion/taint/match.js +278 -0
- package/dist/core/ingestion/taint/path-codec.d.ts +134 -0
- package/dist/core/ingestion/taint/path-codec.js +190 -0
- package/dist/core/ingestion/taint/propagate.d.ts +216 -0
- package/dist/core/ingestion/taint/propagate.js +659 -0
- package/dist/core/ingestion/taint/site-safety.d.ts +29 -0
- package/dist/core/ingestion/taint/site-safety.js +98 -0
- package/dist/core/ingestion/taint/source-sink-config.d.ts +107 -23
- package/dist/core/ingestion/taint/source-sink-config.js +35 -12
- package/dist/core/ingestion/taint/source-sink-registry.d.ts +6 -4
- package/dist/core/ingestion/taint/source-sink-registry.js +6 -4
- package/dist/core/ingestion/taint/summary-harvest-driver.d.ts +80 -0
- package/dist/core/ingestion/taint/summary-harvest-driver.js +179 -0
- package/dist/core/ingestion/taint/summary-harvest.d.ts +89 -0
- package/dist/core/ingestion/taint/summary-harvest.js +537 -0
- package/dist/core/ingestion/taint/summary-model.d.ts +200 -0
- package/dist/core/ingestion/taint/summary-model.js +86 -0
- package/dist/core/ingestion/taint/typescript-model.d.ts +38 -0
- package/dist/core/ingestion/taint/typescript-model.js +102 -0
- package/dist/core/ingestion/type-extractors/c-cpp.js +21 -0
- package/dist/core/ingestion/utils/env.d.ts +7 -0
- package/dist/core/ingestion/utils/env.js +12 -0
- package/dist/core/ingestion/utils/method-props.js +1 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +13 -0
- package/dist/core/ingestion/workers/parse-worker.js +76 -17
- package/dist/core/ingestion/workers/worker-pool.d.ts +9 -40
- package/dist/core/ingestion/workers/worker-pool.js +15 -2
- package/dist/core/lbug/csv-generator.d.ts +51 -3
- package/dist/core/lbug/csv-generator.js +369 -255
- package/dist/core/lbug/lbug-adapter.d.ts +58 -18
- package/dist/core/lbug/lbug-adapter.js +346 -84
- package/dist/core/lbug/lbug-config.d.ts +19 -0
- package/dist/core/lbug/lbug-config.js +29 -0
- package/dist/core/lbug/pdg-emit-sink.d.ts +127 -0
- package/dist/core/lbug/pdg-emit-sink.js +358 -0
- package/dist/core/lbug/pool-adapter.d.ts +35 -0
- package/dist/core/lbug/pool-adapter.js +90 -1
- package/dist/core/lbug/rel-pair-routing.d.ts +54 -0
- package/dist/core/lbug/rel-pair-routing.js +140 -0
- package/dist/core/lbug/schema.d.ts +2 -2
- package/dist/core/lbug/schema.js +3 -0
- package/dist/core/logger.d.ts +0 -5
- package/dist/core/run-analyze.d.ts +96 -0
- package/dist/core/run-analyze.js +207 -3
- package/dist/core/tree-sitter/parser-loader.js +5 -4
- package/dist/core/tree-sitter/safe-parse.d.ts +0 -5
- package/dist/core/tree-sitter/vendored-grammars.d.ts +39 -0
- package/dist/core/tree-sitter/vendored-grammars.js +57 -0
- package/dist/core/wiki/generator.js +3 -1
- package/dist/core/wiki/graph-queries.d.ts +6 -0
- package/dist/core/wiki/graph-queries.js +9 -1
- package/dist/mcp/http-transport.d.ts +134 -0
- package/dist/mcp/http-transport.js +503 -0
- package/dist/mcp/local/local-backend.d.ts +110 -24
- package/dist/mcp/local/local-backend.js +1225 -19
- package/dist/mcp/local/pdg-impact.d.ts +384 -0
- package/dist/mcp/local/pdg-impact.js +1972 -0
- package/dist/mcp/resources.js +13 -1
- package/dist/mcp/tools.d.ts +12 -0
- package/dist/mcp/tools.js +214 -7
- package/dist/server/analyze-launch.d.ts +29 -0
- package/dist/server/analyze-launch.js +137 -0
- package/dist/server/analyze-upload.d.ts +33 -0
- package/dist/server/analyze-upload.js +123 -0
- package/dist/server/analyze-worker.d.ts +17 -1
- package/dist/server/api.d.ts +17 -0
- package/dist/server/api.js +132 -168
- package/dist/server/git-clone.d.ts +43 -2
- package/dist/server/git-clone.js +211 -16
- package/dist/server/mcp-http.d.ts +5 -6
- package/dist/server/mcp-http.js +9 -82
- package/dist/server/middleware.d.ts +40 -0
- package/dist/server/middleware.js +92 -0
- package/dist/server/private-ip.d.ts +1 -0
- package/dist/server/private-ip.js +15 -0
- package/dist/server/upload-ingest.d.ts +56 -0
- package/dist/server/upload-ingest.js +276 -0
- package/dist/server/upload-paths.d.ts +37 -0
- package/dist/server/upload-paths.js +57 -0
- package/dist/server/upload-sweep.d.ts +20 -0
- package/dist/server/upload-sweep.js +57 -0
- package/dist/storage/git.js +5 -8
- package/dist/storage/parse-cache.d.ts +22 -1
- package/dist/storage/parse-cache.js +41 -10
- package/dist/storage/repo-manager.d.ts +121 -10
- package/dist/storage/repo-manager.js +43 -5
- package/dist/types/pipeline.d.ts +9 -0
- package/hooks/antigravity/gitnexus-antigravity-hook.cjs +110 -10
- package/hooks/claude/gitnexus-hook.cjs +110 -13
- package/hooks/claude/hook-db-lock-probe.cjs +513 -26
- package/package.json +8 -2
- package/scripts/assert-publish-grammar-coverage.cjs +31 -0
- package/scripts/build-tree-sitter-grammars.cjs +16 -10
- package/scripts/cross-platform-tests.ts +1 -0
- package/skills/gitnexus-debugging.md +16 -4
- package/skills/gitnexus-exploring.md +3 -3
- package/skills/gitnexus-guide.md +33 -0
- package/skills/gitnexus-pdg-query.md +89 -0
- package/skills/gitnexus-refactoring.md +1 -1
- package/skills/gitnexus-taint-analysis.md +178 -0
- package/vendor/tree-sitter-c/package.json +1 -1
- package/vendor/tree-sitter-dart/package.json +1 -1
- package/vendor/tree-sitter-kotlin/package.json +1 -1
- package/vendor/tree-sitter-proto/package.json +1 -1
- package/vendor/tree-sitter-swift/package.json +1 -1
- package/web/assets/{agent-CKPMqImC.js → agent-D3Xd7zZE.js} +123 -123
- package/web/assets/architecture-7EHR7CIX-6QZW5X65-DxJw65fT.js +1 -0
- package/web/assets/architectureDiagram-UL44E2DR-ChH9xW-N.js +36 -0
- package/web/assets/{blockDiagram-7IZFK4PR-B4niSLvR.js → blockDiagram-7IZFK4PR-BkWhpzWB.js} +1 -1
- package/web/assets/{c4Diagram-Y2BXMSZH-DIzEXalR.js → c4Diagram-Y2BXMSZH-DtBvpzWD.js} +1 -1
- package/web/assets/{chunk-3SSMPTDK-BzGeeheI.js → chunk-3SSMPTDK-B4_etUhr.js} +2 -2
- package/web/assets/{chunk-5IMINLNL-CnTLsXIV.js → chunk-5IMINLNL-DOdTeQri.js} +1 -1
- package/web/assets/{chunk-5VCL7Z4A-w1BCx98Z.js → chunk-5VCL7Z4A-CQ-2dYWj.js} +1 -1
- package/web/assets/chunk-6764PJDD-DpIS7hxD.js +1 -0
- package/web/assets/{chunk-67TQ5CYL-CChC2cAv.js → chunk-67TQ5CYL-4sNPJU1q.js} +3 -3
- package/web/assets/{chunk-7W6UQGC5-CgFyfZ6k.js → chunk-7W6UQGC5-KyEG0HQg.js} +1 -1
- package/web/assets/{chunk-AZZRMDJM-5E9ARdDQ.js → chunk-AZZRMDJM-y-xJBuMz.js} +1 -1
- package/web/assets/{chunk-INKRHTLW-ELpfuk9h.js → chunk-INKRHTLW-Nu4ri9P-.js} +1 -1
- package/web/assets/{chunk-JQRUD6KW-Dcq_Qnyy.js → chunk-JQRUD6KW-Dbva2Z17.js} +1 -1
- package/web/assets/{chunk-KGFNY3KK-W-VR57xb.js → chunk-KGFNY3KK-JGMJvKCK.js} +1 -1
- package/web/assets/chunk-KGYTTC2M-CWC_c3H9.js +161 -0
- package/web/assets/{chunk-KNLZD3CH-BuZBOcso.js → chunk-KNLZD3CH-BGlP6X9R.js} +1 -1
- package/web/assets/{chunk-KRXBNO2N-DH2NPM7I.js → chunk-KRXBNO2N-BW5o0KUN.js} +1 -1
- package/web/assets/{chunk-LCXTWHL2-Dfmux4m1.js → chunk-LCXTWHL2-zoomRtWX.js} +1 -1
- package/web/assets/{chunk-LII3EMHJ-3X33tCpU.js → chunk-LII3EMHJ-D-jm-dLa.js} +1 -1
- package/web/assets/{chunk-QA3QBVWF-D5Ha3GgB.js → chunk-QA3QBVWF-BgRW3SXd.js} +1 -1
- package/web/assets/{chunk-RG4AUYOV-CAkzcoRj.js → chunk-RG4AUYOV-DD0B2N2B.js} +1 -1
- package/web/assets/{chunk-T2UQINTJ-kGYG7ppP.js → chunk-T2UQINTJ-DSBqkzH7.js} +1 -1
- package/web/assets/chunk-T5OCTHI4-DdZvN-9i.js +1 -0
- package/web/assets/{chunk-UY5QBCOK-Du-c56gs.js → chunk-UY5QBCOK-DyF0vyGd.js} +1 -1
- package/web/assets/{chunk-W44A43WB-lrCpuupA.js → chunk-W44A43WB-JqMLIpR0.js} +1 -1
- package/web/assets/{chunk-ZXARS5L4-CSZRiyOf.js → chunk-ZXARS5L4-CxN8oiwI.js} +1 -1
- package/web/assets/classDiagram-KGZ6W3CR-DJm9BgF-.js +1 -0
- package/web/assets/classDiagram-v2-72OJOZXJ-DJm9BgF-.js +1 -0
- package/web/assets/{context-builder-_HS0v2ma.js → context-builder-BHiFUA8O.js} +10 -2
- package/web/assets/{cose-bilkent-UX7MHV2Q-BHv2bbtl.js → cose-bilkent-UX7MHV2Q-D71wNYRJ.js} +1 -1
- package/web/assets/dagre-ND4H6XIP-BIkxmhcu.js +4 -0
- package/web/assets/diagram-3NCE3AQN-BTa6iH0T.js +43 -0
- package/web/assets/diagram-GF46GFSD-DZDV0vBj.js +24 -0
- package/web/assets/{diagram-HNR7UZ2L-fUWreZzx.js → diagram-HNR7UZ2L-DFCfd5LI.js} +2 -2
- package/web/assets/diagram-QXG6HAR7-BzM--Jj0.js +24 -0
- package/web/assets/diagram-WEQXMOUZ-DFFwz0k4.js +10 -0
- package/web/assets/{erDiagram-L5TCEMPS-QN2eEP1e.js → erDiagram-L5TCEMPS-CfHMJJqo.js} +1 -1
- package/web/assets/eventmodeling-FCH6USID-MREXMVOE-BQm9QzEa.js +1 -0
- package/web/assets/{flowDiagram-H6V6AXG4-CXbXImlN.js → flowDiagram-H6V6AXG4-Be805QiJ.js} +3 -3
- package/web/assets/{ganttDiagram-JCBTUEKG-BHQssTz6.js → ganttDiagram-JCBTUEKG-D3iL2Aet.js} +1 -1
- package/web/assets/gitGraph-WXDBUCRP-R675I2BI-Cr_Bm2Nb.js +1 -0
- package/web/assets/gitGraphDiagram-S2ZK5IYY-CPIDgxGm.js +106 -0
- package/web/assets/index-BvYev1UJ.js +635 -0
- package/web/assets/index-_lgn7hs5.css +2 -0
- package/web/assets/info-J43DQDTF-KCYPFFUO-Cs4p2oyk.js +1 -0
- package/web/assets/infoDiagram-3YFTVSEB-Y7BKSoke.js +2 -0
- package/web/assets/{ishikawaDiagram-BNXS4ZKH-6LKOvBfp.js → ishikawaDiagram-BNXS4ZKH-CT4SHFuI.js} +1 -1
- package/web/assets/{journeyDiagram-M6C3CM3L-DNPYCpvq.js → journeyDiagram-M6C3CM3L-CvArszLo.js} +1 -1
- package/web/assets/{kanban-definition-75IXJCU3-DgDi9oJT.js → kanban-definition-75IXJCU3-C6yDxr7N.js} +3 -3
- package/web/assets/{katex-K3KEBU37-CrVLz8l7.js → katex-K3KEBU37-CTc5BslQ.js} +1 -1
- package/web/assets/{mindmap-definition-2TDM6QVE-CwR5sBB-.js → mindmap-definition-2TDM6QVE-BOc2WEne.js} +1 -1
- package/web/assets/packet-YPE3B663-LP52Z2RK-CKCrztD2.js +1 -0
- package/web/assets/pie-LRSECV5Y-TCRJHUBD-BB7pHqoD.js +1 -0
- package/web/assets/{pieDiagram-CU6KROY3-By8g6f6B.js → pieDiagram-CU6KROY3-Cvc0f_Pd.js} +1 -1
- package/web/assets/{quadrantDiagram-VICAPDV7-bAm6Hi_5.js → quadrantDiagram-VICAPDV7-yXSZ2lLz.js} +1 -1
- package/web/assets/radar-GUYGQ44K-RDLRG3WG-DhoTOcuK.js +1 -0
- package/web/assets/{requirementDiagram-JXO7QTGE-7oDcJ1_J.js → requirementDiagram-JXO7QTGE-BRcZvHs4.js} +1 -1
- package/web/assets/{sankeyDiagram-URQDO5SZ-LFJz53u5.js → sankeyDiagram-URQDO5SZ-1qEjlFIB.js} +1 -1
- package/web/assets/{sequenceDiagram-VS2MUI6T-FySeKCUy.js → sequenceDiagram-VS2MUI6T-PcSCuvrB.js} +3 -3
- package/web/assets/stateDiagram-7D4R322I-D6qYSV1E.js +1 -0
- package/web/assets/stateDiagram-v2-36443NZ5-Bg-ImOSX.js +1 -0
- package/web/assets/{timeline-definition-O6YCAMPW-Ds2CnVZK.js → timeline-definition-O6YCAMPW-QWkh01xw.js} +1 -1
- package/web/assets/treeView-BLDUP644-QA4HXRO3-ODehyGKL.js +1 -0
- package/web/assets/treemap-LRROVOQU-LLAWBHMP-BfpgC-7o.js +1 -0
- package/web/assets/{vennDiagram-MWXL3ELB-DUIEwXWp.js → vennDiagram-MWXL3ELB-ak4DfwuM.js} +3 -3
- package/web/assets/wardley-L42UT6IY-5TKZOOLJ-CNeL6VHE.js +1 -0
- package/web/assets/{wardleyDiagram-CUQ6CDDI-DEiFPQih.js → wardleyDiagram-CUQ6CDDI-CTzR4Loq.js} +1 -1
- package/web/assets/{xychartDiagram-N2JHSOCM-BUbayhST.js → xychartDiagram-N2JHSOCM-DA1BEaQz.js} +1 -1
- package/web/index.html +13 -15
- package/scripts/materialize-vendor-grammars.cjs +0 -97
- package/web/assets/architecture-7EHR7CIX-6QZW5X65-BETj2x4g.js +0 -1
- package/web/assets/architectureDiagram-UL44E2DR-D1EXI0zA.js +0 -36
- package/web/assets/chunk-2T2R6R2M-n6s9JZqv.js +0 -4
- package/web/assets/chunk-2UTLFMKG-CMBB1TMN.js +0 -1
- package/web/assets/chunk-4R4BOZG6-mKhL59ul.js +0 -159
- package/web/assets/chunk-6764PJDD-ChwMM2z4.js +0 -1
- package/web/assets/chunk-7J6CGLKN-DT-b53FT.js +0 -10
- package/web/assets/chunk-C62D2QBJ-CDAWj26E.js +0 -1
- package/web/assets/chunk-CEXFNPSA-D68Tk6ls.js +0 -1
- package/web/assets/chunk-CilyBKbf.js +0 -1
- package/web/assets/chunk-J5EP6P6S-DxWW0yvu.js +0 -1
- package/web/assets/chunk-KGYTTC2M-C6PHeuay.js +0 -1
- package/web/assets/chunk-RERM46MO-DQNbXtfw.js +0 -1
- package/web/assets/chunk-RKZBBQEN-BmTPLSyv.js +0 -1
- package/web/assets/chunk-RLI5ZMPA-DExu2DOK.js +0 -1
- package/web/assets/chunk-T5OCTHI4--9wWpVws.js +0 -1
- package/web/assets/chunk-UP6H54XL-DsKdC6jC.js +0 -1
- package/web/assets/chunk-UXSXWOXI-DR81EqLr.js +0 -1
- package/web/assets/classDiagram-KGZ6W3CR-Bv93af_b.js +0 -1
- package/web/assets/classDiagram-v2-72OJOZXJ-CLfEqHUa.js +0 -1
- package/web/assets/dagre-ND4H6XIP-DsIr9MOv.js +0 -4
- package/web/assets/diagram-3NCE3AQN-BqAtKUpW.js +0 -43
- package/web/assets/diagram-GF46GFSD-CaBG6n6o.js +0 -24
- package/web/assets/diagram-QXG6HAR7-CZ-O3rcV.js +0 -24
- package/web/assets/diagram-WEQXMOUZ-P4lSL4GH.js +0 -10
- package/web/assets/eventmodeling-FCH6USID-MREXMVOE-B3myHUst.js +0 -1
- package/web/assets/gitGraph-WXDBUCRP-R675I2BI-DF3rtf4A.js +0 -1
- package/web/assets/gitGraphDiagram-S2ZK5IYY-CCwVLT__.js +0 -106
- package/web/assets/index-BKWA-m7o.css +0 -2
- package/web/assets/index-CG6q8eTs.js +0 -626
- package/web/assets/info-J43DQDTF-KCYPFFUO-R4XV_akJ.js +0 -1
- package/web/assets/infoDiagram-3YFTVSEB-DmLICZx1.js +0 -2
- package/web/assets/packet-YPE3B663-LP52Z2RK-CgQPdNZa.js +0 -1
- package/web/assets/pie-LRSECV5Y-TCRJHUBD-BzWOwc_c.js +0 -1
- package/web/assets/radar-GUYGQ44K-RDLRG3WG-B7KG0FN7.js +0 -1
- package/web/assets/stateDiagram-7D4R322I-CB2nABwH.js +0 -1
- package/web/assets/stateDiagram-v2-36443NZ5-COBGd2RL.js +0 -1
- package/web/assets/treeView-BLDUP644-QA4HXRO3-CRSxEwLO.js +0 -1
- package/web/assets/treemap-LRROVOQU-LLAWBHMP-a0PfKoVn.js +0 -1
- package/web/assets/wardley-L42UT6IY-5TKZOOLJ-BKQ6e5mC.js +0 -1
- /package/web/assets/{chunk-AQ6EADP3-CCFje6lL.js → chunk-AQ6EADP3-CZhslHi-.js} +0 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streaming PDG graph-emit sink (issue #2202).
|
|
3
|
+
*
|
|
4
|
+
* The PDG emit loop (`scope-resolution/pipeline/run.ts`, the `--pdg` block)
|
|
5
|
+
* materializes BasicBlock nodes + intra-file PDG edges (CFG / REACHING_DEF /
|
|
6
|
+
* CDG / POST_DOMINATE / TAINTED / SANITIZES) into the in-memory
|
|
7
|
+
* `KnowledgeGraph`. At full-kernel scale that layer dominates peak RSS
|
|
8
|
+
* (~7 GB at 511K BasicBlocks; ~100 GB extrapolated to the full kernel → OOM).
|
|
9
|
+
*
|
|
10
|
+
* `PdgEmitSink` is a write-routing façade over the real graph: the emit
|
|
11
|
+
* functions are write-only and compute every edge endpoint by deterministic id
|
|
12
|
+
* (audited — no read-back), so the sink can route BasicBlock node rows and PDG
|
|
13
|
+
* edge rows straight to bounded CSV-on-disk writers and **never store them**.
|
|
14
|
+
* The graph's resident size stops growing with the PDG layer → peak RSS becomes
|
|
15
|
+
* O(chunk buffer), not O(graph). Everything else (structural nodes/edges, the
|
|
16
|
+
* whole-program M4 TAINT_PATH edges) is delegated to the real graph unchanged.
|
|
17
|
+
*
|
|
18
|
+
* Why synchronous writers? The whole PDG emit (`runScopeResolution` and its
|
|
19
|
+
* per-file loop) is synchronous — there is no `await` point to drain an async
|
|
20
|
+
* stream, so a `BufferedCSVWriter` (Node `WriteStream`) would accumulate
|
|
21
|
+
* unwritten chunks in process memory across millions of rows, defeating the RSS
|
|
22
|
+
* bound. `fs.writeSync` goes straight to the OS; resident memory is bounded to
|
|
23
|
+
* one `chunkRows` buffer. This mirrors the sync-shard pattern in
|
|
24
|
+
* `storage/parsedfile-store.ts`.
|
|
25
|
+
*
|
|
26
|
+
* Byte-identity (issue acceptance): the sink reuses the SAME shared row
|
|
27
|
+
* builders (`buildBasicBlockRow`, `buildRelRow`) and label derivation
|
|
28
|
+
* (`getNodeLabel`) as `streamAllCSVsToDisk`, so the streamed CSV line SET is
|
|
29
|
+
* identical to the whole-graph emit's, and the bulk COPY loads the same rows →
|
|
30
|
+
* the persisted graph is SET-identical and DB-identical. The guarantee is
|
|
31
|
+
* set-level, not byte-level on the CSV file: the sink streams rows in emit
|
|
32
|
+
* order and does NOT re-sort them under `GITNEXUS_SORT_GRAPH_OUTPUT`, so a
|
|
33
|
+
* streamed CSV file is not necessarily byte-for-byte equal to the sorted
|
|
34
|
+
* whole-graph CSV — but the row set, and therefore the DB outcome, is. (The
|
|
35
|
+
* streamed CSVs are deleted right after the COPY, so their on-disk byte order
|
|
36
|
+
* is never observed.) Cross-pass dedup is done upstream, per FILE, in the emit
|
|
37
|
+
* loop (`run.ts` skips a file whose PDG already streamed) rather than in the
|
|
38
|
+
* sink, because a file can be PDG-emitted in more than one language pass (a
|
|
39
|
+
* `.ts` module imported by a `.vue` SFC is emitted in both the TypeScript and
|
|
40
|
+
* Vue context passes over the same worker-built CFG) and a sink-level per-id
|
|
41
|
+
* dedup set would retain every id → O(total ids) memory, defeating the
|
|
42
|
+
* O(chunk) RSS bound (#2202 review #1). The differential fingerprint test
|
|
43
|
+
* (issue #2202 U6) and the Vue+TS cross-pass integration test guard the set.
|
|
44
|
+
*/
|
|
45
|
+
import type { GraphNode, GraphRelationship, RelationshipType } from '../../_shared/index.js';
|
|
46
|
+
import type { KnowledgeGraph } from '../graph/types.js';
|
|
47
|
+
import { type NodeTableName } from './schema.js';
|
|
48
|
+
/** Default streamed-write buffer (rows). Matches the whole-graph emit's
|
|
49
|
+
* `FLUSH_EVERY` order of magnitude; overridable via `GITNEXUS_PDG_EMIT_CHUNK_SIZE`. */
|
|
50
|
+
export declare const DEFAULT_PDG_EMIT_CHUNK_ROWS = 500;
|
|
51
|
+
/**
|
|
52
|
+
* COPY manifest produced by {@link PdgEmitSink.finalize}. Shaped to merge
|
|
53
|
+
* directly into `StreamedCSVResult` so `loadGraphToLbug` COPYs the streamed
|
|
54
|
+
* PDG CSVs through the same per-table / per-pair loops as the structural CSVs.
|
|
55
|
+
* Paths are absolute, so persistence needs no dir recomputation.
|
|
56
|
+
*/
|
|
57
|
+
export interface PdgEmitManifest {
|
|
58
|
+
/** Node-table CSVs (only `BasicBlock` today). */
|
|
59
|
+
readonly nodeFiles: Map<NodeTableName, {
|
|
60
|
+
csvPath: string;
|
|
61
|
+
rows: number;
|
|
62
|
+
}>;
|
|
63
|
+
/** pairKey (`From|To`) → per-pair edge CSV. */
|
|
64
|
+
readonly relsByPair: Map<string, {
|
|
65
|
+
csvPath: string;
|
|
66
|
+
rows: number;
|
|
67
|
+
}>;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Write-routing graph façade. Construct one per analyze run, thread it into the
|
|
71
|
+
* per-language `runScopeResolution` calls in place of the real graph during the
|
|
72
|
+
* `--pdg` emit, then {@link finalize} once after the last language.
|
|
73
|
+
*/
|
|
74
|
+
export declare class PdgEmitSink implements KnowledgeGraph {
|
|
75
|
+
private readonly real;
|
|
76
|
+
private readonly pdgCsvDir;
|
|
77
|
+
private readonly chunkRows;
|
|
78
|
+
private readonly validTables;
|
|
79
|
+
private bbWriter;
|
|
80
|
+
/** pairKey (`From|To`) → writer. PDG edges are all `BasicBlock|BasicBlock`,
|
|
81
|
+
* but the map keeps the sink general and the manifest pair-keyed. */
|
|
82
|
+
private readonly relWriters;
|
|
83
|
+
private finalized;
|
|
84
|
+
/**
|
|
85
|
+
* First writer-construction failure (a `fs.openSync` throwing on e.g. EMFILE
|
|
86
|
+
* — out of file descriptors). The failure happens inside the `SyncCsvWriter`
|
|
87
|
+
* constructor before a writer object exists to carry poison, so it is held
|
|
88
|
+
* here at the sink level and folded into the {@link finalize} error check.
|
|
89
|
+
* Like an in-flight write fault, an open failure mid-emit would otherwise be
|
|
90
|
+
* swallowed by the emit loop's per-file try/catch and silently drop the rest
|
|
91
|
+
* of that file's rows (#2202 review #4/#6).
|
|
92
|
+
*/
|
|
93
|
+
private openFailure;
|
|
94
|
+
constructor(real: KnowledgeGraph, pdgCsvDir: string, chunkRows?: number);
|
|
95
|
+
addNode(node: GraphNode): void;
|
|
96
|
+
addRelationship(relationship: GraphRelationship): void;
|
|
97
|
+
/** Flush + close every streamed writer and return the COPY manifest. Every
|
|
98
|
+
* fd is closed even when a writer is poisoned (its `close` never throws); any
|
|
99
|
+
* IO fault — an in-flight write that poisoned a writer, a final-flush failure,
|
|
100
|
+
* or a writer-open failure (EMFILE) — is surfaced loudly here so a disk-full
|
|
101
|
+
* / out-of-fds run never hands a truncated CSV to the bulk COPY (#2202 review
|
|
102
|
+
* #4). The emit loop's per-file try/catch swallows the synchronous throw, so
|
|
103
|
+
* this poison check is the backstop that turns a silent partial manifest into
|
|
104
|
+
* a hard failure. */
|
|
105
|
+
finalize(): PdgEmitManifest;
|
|
106
|
+
/**
|
|
107
|
+
* Best-effort fd release for the error path — when a language pass throws
|
|
108
|
+
* before {@link finalize} runs, the caller's `finally` calls this so the
|
|
109
|
+
* BasicBlock + per-pair fds never leak. Idempotent with finalize via the
|
|
110
|
+
* `finalized` flag; close errors are swallowed because the run is already
|
|
111
|
+
* failing.
|
|
112
|
+
*/
|
|
113
|
+
close(): void;
|
|
114
|
+
get nodes(): GraphNode[];
|
|
115
|
+
get relationships(): GraphRelationship[];
|
|
116
|
+
iterNodes(): IterableIterator<GraphNode>;
|
|
117
|
+
iterRelationships(): IterableIterator<GraphRelationship>;
|
|
118
|
+
iterRelationshipsByType(type: RelationshipType): IterableIterator<GraphRelationship>;
|
|
119
|
+
forEachNode(fn: (node: GraphNode) => void): void;
|
|
120
|
+
forEachRelationship(fn: (rel: GraphRelationship) => void): void;
|
|
121
|
+
getNode(id: string): GraphNode | undefined;
|
|
122
|
+
get nodeCount(): number;
|
|
123
|
+
get relationshipCount(): number;
|
|
124
|
+
removeNode(nodeId: string): boolean;
|
|
125
|
+
removeNodesByFile(filePath: string): number;
|
|
126
|
+
removeRelationship(relationshipId: string): boolean;
|
|
127
|
+
}
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streaming PDG graph-emit sink (issue #2202).
|
|
3
|
+
*
|
|
4
|
+
* The PDG emit loop (`scope-resolution/pipeline/run.ts`, the `--pdg` block)
|
|
5
|
+
* materializes BasicBlock nodes + intra-file PDG edges (CFG / REACHING_DEF /
|
|
6
|
+
* CDG / POST_DOMINATE / TAINTED / SANITIZES) into the in-memory
|
|
7
|
+
* `KnowledgeGraph`. At full-kernel scale that layer dominates peak RSS
|
|
8
|
+
* (~7 GB at 511K BasicBlocks; ~100 GB extrapolated to the full kernel → OOM).
|
|
9
|
+
*
|
|
10
|
+
* `PdgEmitSink` is a write-routing façade over the real graph: the emit
|
|
11
|
+
* functions are write-only and compute every edge endpoint by deterministic id
|
|
12
|
+
* (audited — no read-back), so the sink can route BasicBlock node rows and PDG
|
|
13
|
+
* edge rows straight to bounded CSV-on-disk writers and **never store them**.
|
|
14
|
+
* The graph's resident size stops growing with the PDG layer → peak RSS becomes
|
|
15
|
+
* O(chunk buffer), not O(graph). Everything else (structural nodes/edges, the
|
|
16
|
+
* whole-program M4 TAINT_PATH edges) is delegated to the real graph unchanged.
|
|
17
|
+
*
|
|
18
|
+
* Why synchronous writers? The whole PDG emit (`runScopeResolution` and its
|
|
19
|
+
* per-file loop) is synchronous — there is no `await` point to drain an async
|
|
20
|
+
* stream, so a `BufferedCSVWriter` (Node `WriteStream`) would accumulate
|
|
21
|
+
* unwritten chunks in process memory across millions of rows, defeating the RSS
|
|
22
|
+
* bound. `fs.writeSync` goes straight to the OS; resident memory is bounded to
|
|
23
|
+
* one `chunkRows` buffer. This mirrors the sync-shard pattern in
|
|
24
|
+
* `storage/parsedfile-store.ts`.
|
|
25
|
+
*
|
|
26
|
+
* Byte-identity (issue acceptance): the sink reuses the SAME shared row
|
|
27
|
+
* builders (`buildBasicBlockRow`, `buildRelRow`) and label derivation
|
|
28
|
+
* (`getNodeLabel`) as `streamAllCSVsToDisk`, so the streamed CSV line SET is
|
|
29
|
+
* identical to the whole-graph emit's, and the bulk COPY loads the same rows →
|
|
30
|
+
* the persisted graph is SET-identical and DB-identical. The guarantee is
|
|
31
|
+
* set-level, not byte-level on the CSV file: the sink streams rows in emit
|
|
32
|
+
* order and does NOT re-sort them under `GITNEXUS_SORT_GRAPH_OUTPUT`, so a
|
|
33
|
+
* streamed CSV file is not necessarily byte-for-byte equal to the sorted
|
|
34
|
+
* whole-graph CSV — but the row set, and therefore the DB outcome, is. (The
|
|
35
|
+
* streamed CSVs are deleted right after the COPY, so their on-disk byte order
|
|
36
|
+
* is never observed.) Cross-pass dedup is done upstream, per FILE, in the emit
|
|
37
|
+
* loop (`run.ts` skips a file whose PDG already streamed) rather than in the
|
|
38
|
+
* sink, because a file can be PDG-emitted in more than one language pass (a
|
|
39
|
+
* `.ts` module imported by a `.vue` SFC is emitted in both the TypeScript and
|
|
40
|
+
* Vue context passes over the same worker-built CFG) and a sink-level per-id
|
|
41
|
+
* dedup set would retain every id → O(total ids) memory, defeating the
|
|
42
|
+
* O(chunk) RSS bound (#2202 review #1). The differential fingerprint test
|
|
43
|
+
* (issue #2202 U6) and the Vue+TS cross-pass integration test guard the set.
|
|
44
|
+
*/
|
|
45
|
+
import fs from 'fs';
|
|
46
|
+
import path from 'path';
|
|
47
|
+
import { BASICBLOCK_CSV_HEADER, REL_CSV_HEADER, buildBasicBlockRow, buildRelRow, } from './csv-generator.js';
|
|
48
|
+
import { getNodeLabel } from './rel-pair-routing.js';
|
|
49
|
+
import { NODE_TABLES } from './schema.js';
|
|
50
|
+
/**
|
|
51
|
+
* PDG edge types streamed per-file (all intra-block BasicBlock→BasicBlock).
|
|
52
|
+
* `TAINT_PATH` is intentionally excluded — it is the whole-program M4 edge
|
|
53
|
+
* (Function→Function), computed in a separate post-resolution phase over the
|
|
54
|
+
* complete CALLS graph, and stays in the in-memory graph (it is small and is
|
|
55
|
+
* persisted by the normal whole-graph emit).
|
|
56
|
+
*/
|
|
57
|
+
const PDG_EDGE_TYPES = new Set([
|
|
58
|
+
'CFG',
|
|
59
|
+
'REACHING_DEF',
|
|
60
|
+
'CDG',
|
|
61
|
+
'POST_DOMINATE',
|
|
62
|
+
'TAINTED',
|
|
63
|
+
'SANITIZES',
|
|
64
|
+
]);
|
|
65
|
+
/** Default streamed-write buffer (rows). Matches the whole-graph emit's
|
|
66
|
+
* `FLUSH_EVERY` order of magnitude; overridable via `GITNEXUS_PDG_EMIT_CHUNK_SIZE`. */
|
|
67
|
+
export const DEFAULT_PDG_EMIT_CHUNK_ROWS = 500;
|
|
68
|
+
/**
|
|
69
|
+
* Synchronous buffered CSV writer. Buffers up to `chunkRows` rows, then issues
|
|
70
|
+
* one `fs.writeSync` straight to the OS (no in-process stream buffer). Header
|
|
71
|
+
* is written into the buffer at construction and is NOT counted in `rows`
|
|
72
|
+
* (matching `BufferedCSVWriter` semantics, so manifest row counts line up).
|
|
73
|
+
*/
|
|
74
|
+
class SyncCsvWriter {
|
|
75
|
+
csvPath;
|
|
76
|
+
fd;
|
|
77
|
+
buf = [];
|
|
78
|
+
chunkRows;
|
|
79
|
+
rows = 0;
|
|
80
|
+
/**
|
|
81
|
+
* First IO error this writer hit (a `fs.writeSync` short-write loop throwing
|
|
82
|
+
* on e.g. disk-full). Once poisoned the writer refuses further rows and
|
|
83
|
+
* skips its final flush; the sink surfaces it from {@link PdgEmitSink.finalize}
|
|
84
|
+
* so a truncated CSV is never handed to the bulk COPY (#2202 review #4). A
|
|
85
|
+
* streamed-write failure is an IO fault, not the CFG-logic error that the
|
|
86
|
+
* emit loop's per-file try/catch is built to swallow — poisoning routes it
|
|
87
|
+
* past that catch to a loud failure.
|
|
88
|
+
*/
|
|
89
|
+
poison = undefined;
|
|
90
|
+
constructor(csvPath, header, chunkRows) {
|
|
91
|
+
this.csvPath = csvPath;
|
|
92
|
+
// Guard a 0/negative buffer: the flush modulo would never fire and `buf`
|
|
93
|
+
// would grow unbounded, defeating the whole point of streaming.
|
|
94
|
+
this.chunkRows = Math.max(1, chunkRows);
|
|
95
|
+
// Exclusive create (O_EXCL): the streamed-CSV dir is wiped + recreated fresh
|
|
96
|
+
// by the PdgEmitSink constructor before any writer opens a file, so the path
|
|
97
|
+
// never pre-exists — 'wx' both matches that invariant and refuses to follow
|
|
98
|
+
// a pre-planted symlink at the path (CWE-377 / CodeQL js/insecure-temporary-file).
|
|
99
|
+
this.fd = fs.openSync(csvPath, 'wx');
|
|
100
|
+
this.buf.push(header);
|
|
101
|
+
}
|
|
102
|
+
addRow(row) {
|
|
103
|
+
// A poisoned writer is dead — stop buffering so memory can't grow on a
|
|
104
|
+
// writer whose fd is already in a bad state; finalize will report the fault.
|
|
105
|
+
if (this.poison !== undefined)
|
|
106
|
+
return;
|
|
107
|
+
this.buf.push(row);
|
|
108
|
+
this.rows++;
|
|
109
|
+
// Flush on DATA-row count, not buffer length: the header occupies buf[0]
|
|
110
|
+
// until the first flush, so a `buf.length >= chunkRows` test would fire one
|
|
111
|
+
// row early on the first chunk. Counting rows makes every flush exactly
|
|
112
|
+
// `chunkRows` rows.
|
|
113
|
+
if (this.rows % this.chunkRows === 0)
|
|
114
|
+
this.flushOrPoison();
|
|
115
|
+
}
|
|
116
|
+
/** Flush, recording (and re-throwing) any IO error as poison. Re-throwing
|
|
117
|
+
* lets the immediate caller log the per-file failure; the persisted `poison`
|
|
118
|
+
* is the backstop that makes finalize fail loudly even when that throw is
|
|
119
|
+
* swallowed by the emit loop's CFG try/catch. */
|
|
120
|
+
flushOrPoison() {
|
|
121
|
+
try {
|
|
122
|
+
this.flush();
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
this.poison ??= e;
|
|
126
|
+
throw e;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
flush() {
|
|
130
|
+
if (this.buf.length === 0)
|
|
131
|
+
return;
|
|
132
|
+
const data = Buffer.from(this.buf.join('\n') + '\n', 'utf8');
|
|
133
|
+
// fs.writeSync can return a short byte count; loop until the whole buffer
|
|
134
|
+
// lands so a partial write never truncates a CSV row mid-field.
|
|
135
|
+
let offset = 0;
|
|
136
|
+
while (offset < data.length) {
|
|
137
|
+
offset += fs.writeSync(this.fd, data, offset, data.length - offset);
|
|
138
|
+
}
|
|
139
|
+
this.buf.length = 0;
|
|
140
|
+
}
|
|
141
|
+
/** Flush remaining rows (unless already poisoned) and close the fd. Never
|
|
142
|
+
* throws: a final-flush IO error is recorded as poison and the fd is still
|
|
143
|
+
* closed, so a write error neither leaks an fd nor escapes here — the sink
|
|
144
|
+
* reads {@link poison} after closing every writer and fails loudly then. */
|
|
145
|
+
close() {
|
|
146
|
+
try {
|
|
147
|
+
if (this.poison === undefined)
|
|
148
|
+
this.flush();
|
|
149
|
+
}
|
|
150
|
+
catch (e) {
|
|
151
|
+
this.poison ??= e;
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
try {
|
|
155
|
+
fs.closeSync(this.fd);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
/* fd may already be invalid after an IO fault — nothing to recover */
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Write-routing graph façade. Construct one per analyze run, thread it into the
|
|
165
|
+
* per-language `runScopeResolution` calls in place of the real graph during the
|
|
166
|
+
* `--pdg` emit, then {@link finalize} once after the last language.
|
|
167
|
+
*/
|
|
168
|
+
export class PdgEmitSink {
|
|
169
|
+
real;
|
|
170
|
+
pdgCsvDir;
|
|
171
|
+
chunkRows;
|
|
172
|
+
validTables;
|
|
173
|
+
bbWriter;
|
|
174
|
+
/** pairKey (`From|To`) → writer. PDG edges are all `BasicBlock|BasicBlock`,
|
|
175
|
+
* but the map keeps the sink general and the manifest pair-keyed. */
|
|
176
|
+
relWriters = new Map();
|
|
177
|
+
finalized = false;
|
|
178
|
+
/**
|
|
179
|
+
* First writer-construction failure (a `fs.openSync` throwing on e.g. EMFILE
|
|
180
|
+
* — out of file descriptors). The failure happens inside the `SyncCsvWriter`
|
|
181
|
+
* constructor before a writer object exists to carry poison, so it is held
|
|
182
|
+
* here at the sink level and folded into the {@link finalize} error check.
|
|
183
|
+
* Like an in-flight write fault, an open failure mid-emit would otherwise be
|
|
184
|
+
* swallowed by the emit loop's per-file try/catch and silently drop the rest
|
|
185
|
+
* of that file's rows (#2202 review #4/#6).
|
|
186
|
+
*/
|
|
187
|
+
openFailure = undefined;
|
|
188
|
+
// NOTE on dedup: the same file can be PDG-emitted in more than one language
|
|
189
|
+
// pass (e.g. a `.ts` module imported by a `.vue` SFC is emitted in both the
|
|
190
|
+
// TypeScript pass and the Vue context pass over the same worker-built
|
|
191
|
+
// `cfgSideChannel`). The in-memory graph dedups that by id (first-writer-wins);
|
|
192
|
+
// this sink does NOT — to keep peak memory O(write buffer) rather than
|
|
193
|
+
// O(total ids), cross-pass dedup is done upstream, per FILE, in the emit loop
|
|
194
|
+
// (`run.ts` skips a file whose PDG already streamed via `pdgEmittedFiles`).
|
|
195
|
+
// The sink therefore receives each id exactly once and is a faithful
|
|
196
|
+
// pass-through; it must not be fed duplicate ids.
|
|
197
|
+
constructor(real, pdgCsvDir, chunkRows = DEFAULT_PDG_EMIT_CHUNK_ROWS) {
|
|
198
|
+
this.real = real;
|
|
199
|
+
this.pdgCsvDir = pdgCsvDir;
|
|
200
|
+
this.chunkRows = chunkRows;
|
|
201
|
+
this.validTables = new Set(NODE_TABLES);
|
|
202
|
+
// Clear any streamed CSVs left by a previous (possibly crashed) run so a
|
|
203
|
+
// later COPY never picks up stale rows.
|
|
204
|
+
fs.rmSync(pdgCsvDir, { recursive: true, force: true });
|
|
205
|
+
fs.mkdirSync(pdgCsvDir, { recursive: true });
|
|
206
|
+
}
|
|
207
|
+
// ── routed writes ──────────────────────────────────────────────────────────
|
|
208
|
+
addNode(node) {
|
|
209
|
+
if (node.label === 'BasicBlock') {
|
|
210
|
+
if (this.bbWriter === undefined) {
|
|
211
|
+
try {
|
|
212
|
+
this.bbWriter = new SyncCsvWriter(path.join(this.pdgCsvDir, 'basicblock.csv'), BASICBLOCK_CSV_HEADER, this.chunkRows);
|
|
213
|
+
}
|
|
214
|
+
catch (e) {
|
|
215
|
+
this.openFailure ??= e;
|
|
216
|
+
throw e;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
this.bbWriter.addRow(buildBasicBlockRow(node));
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
this.real.addNode(node);
|
|
223
|
+
}
|
|
224
|
+
addRelationship(relationship) {
|
|
225
|
+
if (PDG_EDGE_TYPES.has(relationship.type)) {
|
|
226
|
+
const fromLabel = getNodeLabel(relationship.sourceId);
|
|
227
|
+
const toLabel = getNodeLabel(relationship.targetId);
|
|
228
|
+
// Skip edges whose endpoint labels are not valid node tables — mirrors
|
|
229
|
+
// `RelPairRouter` exactly so the streamed set matches the whole-graph set.
|
|
230
|
+
if (!this.validTables.has(fromLabel) || !this.validTables.has(toLabel))
|
|
231
|
+
return;
|
|
232
|
+
const pairKey = `${fromLabel}|${toLabel}`;
|
|
233
|
+
let writer = this.relWriters.get(pairKey);
|
|
234
|
+
if (writer === undefined) {
|
|
235
|
+
try {
|
|
236
|
+
writer = new SyncCsvWriter(path.join(this.pdgCsvDir, `rel_${fromLabel}_${toLabel}.csv`), REL_CSV_HEADER, this.chunkRows);
|
|
237
|
+
}
|
|
238
|
+
catch (e) {
|
|
239
|
+
this.openFailure ??= e;
|
|
240
|
+
throw e;
|
|
241
|
+
}
|
|
242
|
+
this.relWriters.set(pairKey, writer);
|
|
243
|
+
}
|
|
244
|
+
writer.addRow(buildRelRow(relationship));
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
this.real.addRelationship(relationship);
|
|
248
|
+
}
|
|
249
|
+
/** Flush + close every streamed writer and return the COPY manifest. Every
|
|
250
|
+
* fd is closed even when a writer is poisoned (its `close` never throws); any
|
|
251
|
+
* IO fault — an in-flight write that poisoned a writer, a final-flush failure,
|
|
252
|
+
* or a writer-open failure (EMFILE) — is surfaced loudly here so a disk-full
|
|
253
|
+
* / out-of-fds run never hands a truncated CSV to the bulk COPY (#2202 review
|
|
254
|
+
* #4). The emit loop's per-file try/catch swallows the synchronous throw, so
|
|
255
|
+
* this poison check is the backstop that turns a silent partial manifest into
|
|
256
|
+
* a hard failure. */
|
|
257
|
+
finalize() {
|
|
258
|
+
if (this.finalized)
|
|
259
|
+
throw new Error('PdgEmitSink.finalize() called twice');
|
|
260
|
+
this.finalized = true;
|
|
261
|
+
const errors = [];
|
|
262
|
+
if (this.openFailure !== undefined)
|
|
263
|
+
errors.push(this.openFailure);
|
|
264
|
+
const nodeFiles = new Map();
|
|
265
|
+
if (this.bbWriter !== undefined) {
|
|
266
|
+
this.bbWriter.close();
|
|
267
|
+
if (this.bbWriter.poison !== undefined)
|
|
268
|
+
errors.push(this.bbWriter.poison);
|
|
269
|
+
nodeFiles.set('BasicBlock', {
|
|
270
|
+
csvPath: this.bbWriter.csvPath,
|
|
271
|
+
rows: this.bbWriter.rows,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
const relsByPair = new Map();
|
|
275
|
+
for (const [pairKey, writer] of this.relWriters) {
|
|
276
|
+
writer.close();
|
|
277
|
+
if (writer.poison !== undefined)
|
|
278
|
+
errors.push(writer.poison);
|
|
279
|
+
relsByPair.set(pairKey, { csvPath: writer.csvPath, rows: writer.rows });
|
|
280
|
+
}
|
|
281
|
+
if (errors.length > 0) {
|
|
282
|
+
const first = errors[0];
|
|
283
|
+
throw new Error(`PdgEmitSink: ${errors.length} streamed CSV writer(s) hit an IO error ` +
|
|
284
|
+
`(disk-full / out-of-fds) during the emit — the persisted graph would ` +
|
|
285
|
+
`be truncated, so the run is failed rather than COPYing a partial CSV: ${first instanceof Error ? first.message : String(first)}`);
|
|
286
|
+
}
|
|
287
|
+
return { nodeFiles, relsByPair };
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Best-effort fd release for the error path — when a language pass throws
|
|
291
|
+
* before {@link finalize} runs, the caller's `finally` calls this so the
|
|
292
|
+
* BasicBlock + per-pair fds never leak. Idempotent with finalize via the
|
|
293
|
+
* `finalized` flag; close errors are swallowed because the run is already
|
|
294
|
+
* failing.
|
|
295
|
+
*/
|
|
296
|
+
close() {
|
|
297
|
+
if (this.finalized)
|
|
298
|
+
return;
|
|
299
|
+
this.finalized = true;
|
|
300
|
+
try {
|
|
301
|
+
this.bbWriter?.close();
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
/* best-effort */
|
|
305
|
+
}
|
|
306
|
+
for (const writer of this.relWriters.values()) {
|
|
307
|
+
try {
|
|
308
|
+
writer.close();
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
/* best-effort */
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// ── delegated reads / non-PDG mutations ─────────────────────────────────────
|
|
316
|
+
// The PDG emit functions never call these on the routed graph, but the
|
|
317
|
+
// façade implements the full KnowledgeGraph surface so it is a drop-in for
|
|
318
|
+
// the emit target and any non-PDG write transparently reaches the real graph.
|
|
319
|
+
get nodes() {
|
|
320
|
+
return this.real.nodes;
|
|
321
|
+
}
|
|
322
|
+
get relationships() {
|
|
323
|
+
return this.real.relationships;
|
|
324
|
+
}
|
|
325
|
+
iterNodes() {
|
|
326
|
+
return this.real.iterNodes();
|
|
327
|
+
}
|
|
328
|
+
iterRelationships() {
|
|
329
|
+
return this.real.iterRelationships();
|
|
330
|
+
}
|
|
331
|
+
iterRelationshipsByType(type) {
|
|
332
|
+
return this.real.iterRelationshipsByType(type);
|
|
333
|
+
}
|
|
334
|
+
forEachNode(fn) {
|
|
335
|
+
this.real.forEachNode(fn);
|
|
336
|
+
}
|
|
337
|
+
forEachRelationship(fn) {
|
|
338
|
+
this.real.forEachRelationship(fn);
|
|
339
|
+
}
|
|
340
|
+
getNode(id) {
|
|
341
|
+
return this.real.getNode(id);
|
|
342
|
+
}
|
|
343
|
+
get nodeCount() {
|
|
344
|
+
return this.real.nodeCount;
|
|
345
|
+
}
|
|
346
|
+
get relationshipCount() {
|
|
347
|
+
return this.real.relationshipCount;
|
|
348
|
+
}
|
|
349
|
+
removeNode(nodeId) {
|
|
350
|
+
return this.real.removeNode(nodeId);
|
|
351
|
+
}
|
|
352
|
+
removeNodesByFile(filePath) {
|
|
353
|
+
return this.real.removeNodesByFile(filePath);
|
|
354
|
+
}
|
|
355
|
+
removeRelationship(relationshipId) {
|
|
356
|
+
return this.real.removeRelationship(relationshipId);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
@@ -37,6 +37,41 @@ export { realStdoutWrite, realStderrWrite, setActiveStdoutWrite } from '../../mc
|
|
|
37
37
|
* Call this during long-running operations to prevent the connection from being closed.
|
|
38
38
|
*/
|
|
39
39
|
export declare const touchRepo: (repoId: string) => void;
|
|
40
|
+
/**
|
|
41
|
+
* Acquire one eviction-exemption lease on a repo (LRU + idle timeout) by
|
|
42
|
+
* incrementing its reference count. The repoId must match the key passed to
|
|
43
|
+
* initLbug (e.g. group sync leases by handle.id — the same id it inits with).
|
|
44
|
+
* Leasing a repoId before it enters the pool is allowed and protects the entry
|
|
45
|
+
* once it is created, but the lease does NOT survive a teardown: closeOne
|
|
46
|
+
* force-clears the count, so a later re-init of the same repoId starts
|
|
47
|
+
* unpinned. Each pinRepo MUST be balanced by exactly one release (the repo
|
|
48
|
+
* stays exempt until the last lease is released). See the pinnedRepos docstring
|
|
49
|
+
* for the full contract.
|
|
50
|
+
*
|
|
51
|
+
* Returns a `release` disposer (mirroring addPoolCloseListener) that releases
|
|
52
|
+
* THIS lease exactly once — calling it twice is a no-op, so it can never
|
|
53
|
+
* over-decrement a sibling holder's count. Prefer the disposer
|
|
54
|
+
* (`const release = pinRepo(id); try { … } finally { release(); }`) so the
|
|
55
|
+
* pin/release pair is leak-proof; unpinRepo remains available for callers that
|
|
56
|
+
* pair explicitly.
|
|
57
|
+
*/
|
|
58
|
+
export declare const pinRepo: (repoId: string) => (() => void);
|
|
59
|
+
/**
|
|
60
|
+
* Release one eviction-exemption lease on a repo. The repo becomes eligible for
|
|
61
|
+
* automatic eviction again only once its count reaches 0 (the key is deleted).
|
|
62
|
+
* Idempotent at the floor: releasing a repo with no active lease is a no-op (no
|
|
63
|
+
* negative counts). Does NOT close the repo's pool.
|
|
64
|
+
*/
|
|
65
|
+
export declare const unpinRepo: (repoId: string) => void;
|
|
66
|
+
/**
|
|
67
|
+
* Maximum number of repos a bounded multi-repo operation (e.g. group sync's
|
|
68
|
+
* windowed manifest resolution) should hold resident at once. Equals
|
|
69
|
+
* MAX_POOL_SIZE today, but exposed under an intent-named accessor so callers
|
|
70
|
+
* size their working set against "max repos a bounded op should hold" rather
|
|
71
|
+
* than coupling to the LRU eviction-cap constant, which may be tuned
|
|
72
|
+
* independently.
|
|
73
|
+
*/
|
|
74
|
+
export declare const getMaxResidentRepos: () => number;
|
|
40
75
|
/**
|
|
41
76
|
* Silence stdout by replacing process.stdout.write with a no-op.
|
|
42
77
|
* Uses a reference counter so nested silence/restore pairs are safe.
|
|
@@ -39,6 +39,30 @@ const MAX_POOL_SIZE = 5;
|
|
|
39
39
|
const IDLE_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
|
|
40
40
|
/** Max connections per repo (caps concurrent queries per repo) */
|
|
41
41
|
const MAX_CONNS_PER_REPO = 8;
|
|
42
|
+
/**
|
|
43
|
+
* Repos exempt from AUTOMATIC eviction (LRU + idle timeout) until explicitly
|
|
44
|
+
* unpinned. Used by bounded multi-repo operations like `group sync`, which
|
|
45
|
+
* initializes one pool per repo and then resolves cross-repo manifest/workspace
|
|
46
|
+
* links against ALL of those pools after the init loop. Without pinning, a
|
|
47
|
+
* group larger than MAX_POOL_SIZE would LRU-evict the earliest repos before
|
|
48
|
+
* resolution runs, leaving the deferred executor closures pointing at dead pool
|
|
49
|
+
* entries (issue #2189).
|
|
50
|
+
*
|
|
51
|
+
* Pins are REFERENCE-COUNTED: the map holds repoId → active lease count. This
|
|
52
|
+
* lets overlapping holders (two windows of one sync, or two concurrent
|
|
53
|
+
* `group sync` calls sharing a repo) coexist safely — the repo stays exempt
|
|
54
|
+
* until the LAST holder releases. A boolean Set could not represent "two
|
|
55
|
+
* holders," so the first release would wrongly clear a pin another holder still
|
|
56
|
+
* needs (PR #2191 review, Finding 1).
|
|
57
|
+
*
|
|
58
|
+
* Pins block only automatic eviction (LRU + idle). Explicit teardown
|
|
59
|
+
* (closeOne / closeLbug) always closes the entry and force-clears its count —
|
|
60
|
+
* teardown is authoritative. A present key always means count ≥ 1. While every
|
|
61
|
+
* pooled repo is pinned, evictLRU finds no eligible victim and the pool may
|
|
62
|
+
* transiently exceed MAX_POOL_SIZE — the same soft-cap behavior that already
|
|
63
|
+
* occurs when every entry is checked out.
|
|
64
|
+
*/
|
|
65
|
+
const pinnedRepos = new Map();
|
|
42
66
|
// Behavior-neutral RSS tracing for the FTS evict→reload memory repro
|
|
43
67
|
// (gitnexus/scripts/bench/fts-evict-reload-rss.mjs). Two invariants keep it safe
|
|
44
68
|
// in the pool init/close hot path: it writes ONLY to stderr (stdout is the MCP
|
|
@@ -77,6 +101,8 @@ function ensureIdleTimer() {
|
|
|
77
101
|
idleTimer = setInterval(() => {
|
|
78
102
|
const now = Date.now();
|
|
79
103
|
for (const [repoId, entry] of pool) {
|
|
104
|
+
if (pinnedRepos.has(repoId))
|
|
105
|
+
continue;
|
|
80
106
|
if (now - entry.lastUsed > IDLE_TIMEOUT_MS && entry.checkedOut === 0) {
|
|
81
107
|
closeOne(repoId);
|
|
82
108
|
}
|
|
@@ -97,7 +123,64 @@ export const touchRepo = (repoId) => {
|
|
|
97
123
|
}
|
|
98
124
|
};
|
|
99
125
|
/**
|
|
100
|
-
*
|
|
126
|
+
* Acquire one eviction-exemption lease on a repo (LRU + idle timeout) by
|
|
127
|
+
* incrementing its reference count. The repoId must match the key passed to
|
|
128
|
+
* initLbug (e.g. group sync leases by handle.id — the same id it inits with).
|
|
129
|
+
* Leasing a repoId before it enters the pool is allowed and protects the entry
|
|
130
|
+
* once it is created, but the lease does NOT survive a teardown: closeOne
|
|
131
|
+
* force-clears the count, so a later re-init of the same repoId starts
|
|
132
|
+
* unpinned. Each pinRepo MUST be balanced by exactly one release (the repo
|
|
133
|
+
* stays exempt until the last lease is released). See the pinnedRepos docstring
|
|
134
|
+
* for the full contract.
|
|
135
|
+
*
|
|
136
|
+
* Returns a `release` disposer (mirroring addPoolCloseListener) that releases
|
|
137
|
+
* THIS lease exactly once — calling it twice is a no-op, so it can never
|
|
138
|
+
* over-decrement a sibling holder's count. Prefer the disposer
|
|
139
|
+
* (`const release = pinRepo(id); try { … } finally { release(); }`) so the
|
|
140
|
+
* pin/release pair is leak-proof; unpinRepo remains available for callers that
|
|
141
|
+
* pair explicitly.
|
|
142
|
+
*/
|
|
143
|
+
export const pinRepo = (repoId) => {
|
|
144
|
+
pinnedRepos.set(repoId, (pinnedRepos.get(repoId) ?? 0) + 1);
|
|
145
|
+
let released = false;
|
|
146
|
+
return () => {
|
|
147
|
+
if (released)
|
|
148
|
+
return;
|
|
149
|
+
released = true;
|
|
150
|
+
unpinRepo(repoId);
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* Release one eviction-exemption lease on a repo. The repo becomes eligible for
|
|
155
|
+
* automatic eviction again only once its count reaches 0 (the key is deleted).
|
|
156
|
+
* Idempotent at the floor: releasing a repo with no active lease is a no-op (no
|
|
157
|
+
* negative counts). Does NOT close the repo's pool.
|
|
158
|
+
*/
|
|
159
|
+
export const unpinRepo = (repoId) => {
|
|
160
|
+
const count = pinnedRepos.get(repoId);
|
|
161
|
+
if (count === undefined)
|
|
162
|
+
return;
|
|
163
|
+
if (count <= 1) {
|
|
164
|
+
pinnedRepos.delete(repoId);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
pinnedRepos.set(repoId, count - 1);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* Maximum number of repos a bounded multi-repo operation (e.g. group sync's
|
|
172
|
+
* windowed manifest resolution) should hold resident at once. Equals
|
|
173
|
+
* MAX_POOL_SIZE today, but exposed under an intent-named accessor so callers
|
|
174
|
+
* size their working set against "max repos a bounded op should hold" rather
|
|
175
|
+
* than coupling to the LRU eviction-cap constant, which may be tuned
|
|
176
|
+
* independently.
|
|
177
|
+
*/
|
|
178
|
+
export const getMaxResidentRepos = () => MAX_POOL_SIZE;
|
|
179
|
+
/**
|
|
180
|
+
* Evict the least-recently-used repo if pool is at capacity.
|
|
181
|
+
* Pinned repos are never chosen as the eviction victim — when every eligible
|
|
182
|
+
* entry is pinned, no eviction occurs and the pool transiently exceeds
|
|
183
|
+
* MAX_POOL_SIZE (see the pinnedRepos docstring).
|
|
101
184
|
*/
|
|
102
185
|
function evictLRU() {
|
|
103
186
|
if (pool.size < MAX_POOL_SIZE)
|
|
@@ -105,6 +188,8 @@ function evictLRU() {
|
|
|
105
188
|
let oldestId = null;
|
|
106
189
|
let oldestTime = Infinity;
|
|
107
190
|
for (const [id, entry] of pool) {
|
|
191
|
+
if (pinnedRepos.has(id))
|
|
192
|
+
continue;
|
|
108
193
|
if (entry.checkedOut === 0 && entry.lastUsed < oldestTime) {
|
|
109
194
|
oldestTime = entry.lastUsed;
|
|
110
195
|
oldestId = id;
|
|
@@ -167,6 +252,10 @@ function closeOne(repoId) {
|
|
|
167
252
|
}
|
|
168
253
|
}
|
|
169
254
|
pool.delete(repoId);
|
|
255
|
+
// Clear any eviction pin — the entry is gone, so the pin is meaningless and
|
|
256
|
+
// would otherwise leak across operations in a long-lived process. Teardown
|
|
257
|
+
// is authoritative: an explicit close always wins over a pin.
|
|
258
|
+
pinnedRepos.delete(repoId);
|
|
170
259
|
// Notify listeners AFTER the pool entry is gone so any cache-invalidation
|
|
171
260
|
// they perform is consistent with `isLbugReady(repoId) === false`.
|
|
172
261
|
for (const listener of poolCloseListeners) {
|