gitnexus 1.6.11-rc.8 → 1.6.11
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 +95 -26
- package/dist/_shared/graph/types.d.ts +81 -2
- package/dist/_shared/graph/types.d.ts.map +1 -1
- package/dist/_shared/integrations/circuit-breaker.d.ts.map +1 -1
- package/dist/_shared/integrations/circuit-breaker.js.map +1 -1
- package/dist/_shared/integrations/resilient-fetch.d.ts.map +1 -1
- 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 -1
- package/dist/_shared/languages.d.ts.map +1 -1
- package/dist/_shared/languages.js +1 -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 +4 -0
- package/dist/_shared/lbug/schema-constants.js.map +1 -1
- package/dist/_shared/scope-resolution/evidence-weights.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/finalize-algorithm.js +1 -0
- package/dist/_shared/scope-resolution/finalize-algorithm.js.map +1 -1
- package/dist/_shared/scope-resolution/language-classification.d.ts +3 -0
- package/dist/_shared/scope-resolution/language-classification.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/language-classification.js +4 -0
- package/dist/_shared/scope-resolution/language-classification.js.map +1 -1
- package/dist/_shared/scope-resolution/reference-site.d.ts +10 -0
- package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/registries/field-registry.js.map +1 -1
- package/dist/_shared/scope-resolution/registries/method-registry.js.map +1 -1
- package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
- package/dist/_shared/scope-resolution/types.d.ts +15 -0
- package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
- package/dist/cli/ai-context.d.ts +4 -0
- package/dist/cli/ai-context.js +90 -14
- package/dist/cli/analyze-config.js +14 -0
- package/dist/cli/analyze-options.d.ts +14 -0
- package/dist/cli/analyze-watch.d.ts +20 -0
- package/dist/cli/analyze-watch.js +434 -0
- package/dist/cli/analyze.js +27 -9
- package/dist/cli/auto-sync.d.ts +1 -0
- package/dist/cli/auto-sync.js +107 -0
- package/dist/cli/command-banner.d.ts +15 -0
- package/dist/cli/command-banner.js +42 -0
- package/dist/cli/detect-changes-format.js +21 -0
- package/dist/cli/doctor.d.ts +7 -0
- package/dist/cli/doctor.js +14 -0
- package/dist/cli/group.js +7 -1
- package/dist/cli/help-i18n.js +4 -0
- package/dist/cli/i18n/en.d.ts +18 -3
- package/dist/cli/i18n/en.js +18 -3
- package/dist/cli/i18n/resources.d.ts +33 -3
- package/dist/cli/i18n/zh-CN.d.ts +15 -0
- package/dist/cli/i18n/zh-CN.js +18 -3
- package/dist/cli/index.js +34 -7
- package/dist/cli/mcp.d.ts +15 -0
- package/dist/cli/mcp.js +44 -0
- package/dist/cli/optional-grammars.d.ts +3 -2
- package/dist/cli/optional-grammars.js +19 -3
- package/dist/cli/setup.js +41 -15
- package/dist/cli/update-notice.d.ts +34 -0
- package/dist/cli/update-notice.js +91 -0
- package/dist/cli/update.d.ts +21 -0
- package/dist/cli/update.js +93 -0
- package/dist/cli/watch.d.ts +1 -20
- package/dist/cli/watch.js +5 -371
- package/dist/core/analysis-feature-registry.d.ts +2 -0
- package/dist/core/analysis-feature-registry.js +15 -0
- package/dist/core/analyzer-identity.js +21 -3
- package/dist/core/auto-sync/analysis-worker-launch.d.ts +30 -0
- package/dist/core/auto-sync/analysis-worker-launch.js +166 -0
- package/dist/core/auto-sync/config.d.ts +33 -0
- package/dist/core/auto-sync/config.js +320 -0
- package/dist/core/auto-sync/index.d.ts +6 -0
- package/dist/core/auto-sync/index.js +6 -0
- package/dist/core/auto-sync/path-security.d.ts +9 -0
- package/dist/core/auto-sync/path-security.js +251 -0
- package/dist/core/auto-sync/repo.d.ts +1 -0
- package/dist/core/auto-sync/repo.js +6 -0
- package/dist/core/auto-sync/runner.d.ts +44 -0
- package/dist/core/auto-sync/runner.js +428 -0
- package/dist/core/auto-sync/starter.d.ts +50 -0
- package/dist/core/auto-sync/starter.js +497 -0
- package/dist/core/auto-sync/state.d.ts +37 -0
- package/dist/core/auto-sync/state.js +119 -0
- package/dist/core/group/config-parser.js +8 -1
- package/dist/core/group/cross-impact.d.ts +3 -0
- package/dist/core/group/cross-impact.js +36 -4
- package/dist/core/group/extractors/http-patterns/java.js +22 -8
- package/dist/core/group/extractors/http-patterns/kotlin.js +86 -31
- package/dist/core/group/extractors/http-patterns/node.js +145 -8
- package/dist/core/group/extractors/http-patterns/python.js +11 -7
- package/dist/core/group/extractors/http-route-extractor.js +63 -17
- package/dist/core/group/extractors/java-workspace-extractor.js +244 -31
- package/dist/core/group/group-lock.js +5 -6
- package/dist/core/group/normalization.d.ts +38 -1
- package/dist/core/group/normalization.js +59 -3
- package/dist/core/group/service.d.ts +11 -0
- package/dist/core/group/service.js +13 -1
- package/dist/core/group/storage.js +2 -2
- package/dist/core/group/sync.d.ts +24 -1
- package/dist/core/group/sync.js +46 -8
- package/dist/core/group/types.d.ts +13 -0
- package/dist/core/incremental/derived-writeback.d.ts +36 -0
- package/dist/core/incremental/derived-writeback.js +68 -0
- package/dist/core/incremental/spring-config-drift.d.ts +12 -0
- package/dist/core/incremental/spring-config-drift.js +46 -0
- package/dist/core/incremental/subgraph-extract.d.ts +7 -4
- package/dist/core/incremental/subgraph-extract.js +30 -5
- package/dist/core/ingestion/asyncapi/document.d.ts +226 -0
- package/dist/core/ingestion/asyncapi/document.js +758 -0
- package/dist/core/ingestion/asyncapi/protocol.d.ts +79 -0
- package/dist/core/ingestion/asyncapi/protocol.js +203 -0
- package/dist/core/ingestion/call-extractors/configs/zig.d.ts +2 -0
- package/dist/core/ingestion/call-extractors/configs/zig.js +4 -0
- package/dist/core/ingestion/call-extractors/zig-static-gating.d.ts +100 -0
- package/dist/core/ingestion/call-extractors/zig-static-gating.js +319 -0
- package/dist/core/ingestion/call-processor.js +3 -1
- package/dist/core/ingestion/class-extractors/configs/zig.d.ts +2 -0
- package/dist/core/ingestion/class-extractors/configs/zig.js +32 -0
- package/dist/core/ingestion/class-extractors/generic.js +4 -1
- package/dist/core/ingestion/class-types.d.ts +6 -2
- package/dist/core/ingestion/destination-key.d.ts +59 -0
- package/dist/core/ingestion/destination-key.js +61 -0
- package/dist/core/ingestion/entry-point-scoring.d.ts +6 -2
- package/dist/core/ingestion/entry-point-scoring.js +8 -44
- package/dist/core/ingestion/export-detection.d.ts +37 -0
- package/dist/core/ingestion/export-detection.js +60 -0
- package/dist/core/ingestion/field-extractors/configs/zig.d.ts +8 -0
- package/dist/core/ingestion/field-extractors/configs/zig.js +49 -0
- package/dist/core/ingestion/field-extractors/generic.d.ts +1 -1
- package/dist/core/ingestion/field-extractors/generic.js +12 -1
- package/dist/core/ingestion/frameworks/spring/actuator-runtime.d.ts +26 -0
- package/dist/core/ingestion/frameworks/spring/actuator-runtime.js +843 -0
- package/dist/core/ingestion/frameworks/spring/analysis-features.d.ts +5 -0
- package/dist/core/ingestion/frameworks/spring/analysis-features.js +9 -0
- package/dist/core/ingestion/frameworks/spring/annotation-arguments.js +8 -1
- package/dist/core/ingestion/frameworks/spring/argument-facts.d.ts +80 -0
- package/dist/core/ingestion/frameworks/spring/argument-facts.js +85 -0
- package/dist/core/ingestion/frameworks/spring/config-bindings.d.ts +1 -0
- package/dist/core/ingestion/frameworks/spring/config-bindings.js +2 -1
- package/dist/core/ingestion/frameworks/spring/destinations.d.ts +539 -0
- package/dist/core/ingestion/frameworks/spring/destinations.js +840 -0
- package/dist/core/ingestion/frameworks/spring/dynamic-lookups.d.ts +22 -0
- package/dist/core/ingestion/frameworks/spring/dynamic-lookups.js +124 -0
- package/dist/core/ingestion/frameworks/spring/message-producers.d.ts +78 -0
- package/dist/core/ingestion/frameworks/spring/message-producers.js +90 -0
- package/dist/core/ingestion/frameworks/spring/non-http-handlers.d.ts +29 -0
- package/dist/core/ingestion/frameworks/spring/vendor-prefixes.d.ts +7 -0
- package/dist/core/ingestion/frameworks/spring/vendor-prefixes.js +22 -0
- package/dist/core/ingestion/import-resolvers/configs/zig.d.ts +13 -0
- package/dist/core/ingestion/import-resolvers/configs/zig.js +26 -0
- package/dist/core/ingestion/import-resolvers/zig.d.ts +34 -0
- package/dist/core/ingestion/import-resolvers/zig.js +193 -0
- package/dist/core/ingestion/language-config.d.ts +177 -0
- package/dist/core/ingestion/language-config.js +719 -0
- package/dist/core/ingestion/language-provider.d.ts +178 -1
- package/dist/core/ingestion/language-provider.js +22 -0
- package/dist/core/ingestion/languages/csharp/razor-view-components.d.ts +62 -0
- package/dist/core/ingestion/languages/csharp/razor-view-components.js +954 -0
- package/dist/core/ingestion/languages/csharp/resolution-config.d.ts +3 -0
- package/dist/core/ingestion/languages/csharp/resolution-config.js +6 -1
- package/dist/core/ingestion/languages/csharp/scope-resolver.js +8 -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 +1 -1
- package/dist/core/ingestion/languages/java/analysis-features.js +14 -6
- package/dist/core/ingestion/languages/java/capture-side-channel.d.ts +8 -0
- package/dist/core/ingestion/languages/java/capture-side-channel.js +34 -0
- package/dist/core/ingestion/languages/java/captures.js +22 -1
- package/dist/core/ingestion/languages/java/lombok-synthesizer.d.ts +42 -0
- package/dist/core/ingestion/languages/java/lombok-synthesizer.js +439 -0
- package/dist/core/ingestion/languages/java/scope-resolver.js +2 -0
- package/dist/core/ingestion/languages/java/spring-actuator.d.ts +2 -0
- package/dist/core/ingestion/languages/java/spring-actuator.js +62 -0
- package/dist/core/ingestion/languages/java/spring-di.d.ts +17 -1
- package/dist/core/ingestion/languages/java/spring-di.js +44 -2
- package/dist/core/ingestion/languages/java/spring-dynamic-lookup.d.ts +8 -0
- package/dist/core/ingestion/languages/java/spring-dynamic-lookup.js +62 -0
- package/dist/core/ingestion/languages/java/spring-message-producers.d.ts +20 -0
- package/dist/core/ingestion/languages/java/spring-message-producers.js +83 -0
- package/dist/core/ingestion/languages/java/spring-non-http-handlers.d.ts +11 -1
- package/dist/core/ingestion/languages/java/spring-non-http-handlers.js +13 -3
- package/dist/core/ingestion/languages/java.js +18 -8
- package/dist/core/ingestion/languages/jvm/accessor-synthesis.d.ts +99 -0
- package/dist/core/ingestion/languages/jvm/accessor-synthesis.js +173 -0
- package/dist/core/ingestion/languages/jvm/beanspec.d.ts +17 -0
- package/dist/core/ingestion/languages/jvm/beanspec.js +42 -0
- package/dist/core/ingestion/languages/kotlin/capture-side-channel.d.ts +15 -0
- package/dist/core/ingestion/languages/kotlin/capture-side-channel.js +50 -0
- package/dist/core/ingestion/languages/kotlin/captures.js +24 -1
- package/dist/core/ingestion/languages/kotlin/lombok-synthesizer.d.ts +26 -0
- package/dist/core/ingestion/languages/kotlin/lombok-synthesizer.js +417 -0
- package/dist/core/ingestion/languages/kotlin/scope-resolver.js +4 -0
- package/dist/core/ingestion/languages/kotlin/spring-actuator.d.ts +4 -0
- package/dist/core/ingestion/languages/kotlin/spring-actuator.js +172 -0
- package/dist/core/ingestion/languages/kotlin/spring-config-bindings.d.ts +18 -0
- package/dist/core/ingestion/languages/kotlin/spring-config-bindings.js +411 -0
- package/dist/core/ingestion/languages/kotlin/spring-di.d.ts +51 -1
- package/dist/core/ingestion/languages/kotlin/spring-di.js +86 -6
- package/dist/core/ingestion/languages/kotlin/spring-dynamic-lookup.d.ts +8 -0
- package/dist/core/ingestion/languages/kotlin/spring-dynamic-lookup.js +77 -0
- package/dist/core/ingestion/languages/kotlin/spring-message-producers.d.ts +17 -0
- package/dist/core/ingestion/languages/kotlin/spring-message-producers.js +119 -0
- package/dist/core/ingestion/languages/kotlin/spring-non-http-handlers.d.ts +16 -0
- package/dist/core/ingestion/languages/kotlin/spring-non-http-handlers.js +77 -2
- package/dist/core/ingestion/languages/kotlin.js +24 -0
- package/dist/core/ingestion/languages/python.js +2 -0
- package/dist/core/ingestion/languages/rust/query.js +8 -8
- package/dist/core/ingestion/languages/zig/captures.d.ts +260 -0
- package/dist/core/ingestion/languages/zig/captures.js +2070 -0
- package/dist/core/ingestion/languages/zig/index.d.ts +4 -0
- package/dist/core/ingestion/languages/zig/index.js +4 -0
- package/dist/core/ingestion/languages/zig/interpret.d.ts +36 -0
- package/dist/core/ingestion/languages/zig/interpret.js +176 -0
- package/dist/core/ingestion/languages/zig/query.d.ts +3 -0
- package/dist/core/ingestion/languages/zig/query.js +426 -0
- package/dist/core/ingestion/languages/zig/range-binding.d.ts +37 -0
- package/dist/core/ingestion/languages/zig/range-binding.js +373 -0
- package/dist/core/ingestion/languages/zig/scope-resolver.d.ts +12 -0
- package/dist/core/ingestion/languages/zig/scope-resolver.js +58 -0
- package/dist/core/ingestion/languages/zig/simple-hooks.d.ts +25 -0
- package/dist/core/ingestion/languages/zig/simple-hooks.js +143 -0
- package/dist/core/ingestion/languages/zig.d.ts +32 -0
- package/dist/core/ingestion/languages/zig.js +130 -0
- package/dist/core/ingestion/method-extractors/configs/zig.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/zig.js +120 -0
- package/dist/core/ingestion/method-extractors/generic.js +16 -4
- package/dist/core/ingestion/method-types.d.ts +10 -4
- package/dist/core/ingestion/model/field-registry.d.ts +3 -3
- package/dist/core/ingestion/model/field-registry.js +11 -7
- package/dist/core/ingestion/model/method-registry.d.ts +3 -3
- package/dist/core/ingestion/model/method-registry.js +13 -8
- package/dist/core/ingestion/model/registration-table.js +3 -0
- package/dist/core/ingestion/model/type-registry.js +8 -4
- package/dist/core/ingestion/pipeline-phases/di.js +47 -14
- package/dist/core/ingestion/pipeline-phases/index.d.ts +1 -0
- package/dist/core/ingestion/pipeline-phases/index.js +1 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +7 -1
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +79 -91
- package/dist/core/ingestion/pipeline-phases/parse.d.ts +17 -0
- package/dist/core/ingestion/pipeline-phases/processes.d.ts +2 -1
- package/dist/core/ingestion/pipeline-phases/processes.js +39 -4
- package/dist/core/ingestion/pipeline-phases/routes.js +27 -18
- package/dist/core/ingestion/pipeline-phases/runner.d.ts +4 -1
- package/dist/core/ingestion/pipeline-phases/runner.js +34 -14
- package/dist/core/ingestion/pipeline-phases/scan.js +82 -1
- package/dist/core/ingestion/pipeline-phases/spring-auto-configuration.d.ts +2 -0
- package/dist/core/ingestion/pipeline-phases/spring-auto-configuration.js +8 -0
- package/dist/core/ingestion/pipeline-phases/spring-destinations.d.ts +123 -0
- package/dist/core/ingestion/pipeline-phases/spring-destinations.js +688 -0
- package/dist/core/ingestion/pipeline.d.ts +30 -1
- package/dist/core/ingestion/pipeline.js +52 -14
- package/dist/core/ingestion/resolve-references.js +5 -0
- package/dist/core/ingestion/route-extractors/constant-resolver.d.ts +16 -0
- package/dist/core/ingestion/route-extractors/constant-resolver.js +10 -0
- package/dist/core/ingestion/route-extractors/java-const-resolver.d.ts +27 -1
- package/dist/core/ingestion/route-extractors/java-const-resolver.js +161 -13
- package/dist/core/ingestion/route-extractors/kotlin-const-resolver.d.ts +12 -18
- package/dist/core/ingestion/route-extractors/kotlin-const-resolver.js +214 -33
- package/dist/core/ingestion/route-extractors/kotlin-spring.d.ts +13 -0
- package/dist/core/ingestion/route-extractors/kotlin-spring.js +259 -0
- package/dist/core/ingestion/route-extractors/python-decorator-handler.d.ts +6 -0
- package/dist/core/ingestion/route-extractors/python-decorator-handler.js +16 -0
- package/dist/core/ingestion/route-extractors/spring-shared.d.ts +30 -1
- package/dist/core/ingestion/route-extractors/spring-shared.js +77 -9
- package/dist/core/ingestion/route-extractors/spring.js +9 -5
- package/dist/core/ingestion/scope-extractor.js +8 -0
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +62 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +4 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +2 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +28 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +11 -2
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +23 -2
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +24 -3
- package/dist/core/ingestion/scope-resolution/passes/callable-value-flow.d.ts +11 -1
- package/dist/core/ingestion/scope-resolution/passes/callable-value-flow.js +26 -6
- package/dist/core/ingestion/scope-resolution/passes/compound-receiver.d.ts +11 -0
- package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +29 -3
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +8 -1
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +35 -9
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +1 -4
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +277 -9
- package/dist/core/ingestion/scope-resolution/pipeline/registry.js +2 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +7 -3
- package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +43 -9
- package/dist/core/ingestion/scope-resolution/scope/walkers.js +71 -10
- package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -0
- package/dist/core/ingestion/tree-sitter-queries.js +161 -0
- package/dist/core/ingestion/type-extractors/zig.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/zig.js +17 -0
- package/dist/core/ingestion/utils/ast-helpers.d.ts +40 -1
- package/dist/core/ingestion/utils/ast-helpers.js +124 -6
- package/dist/core/ingestion/utils/callable-flow-captures.d.ts +9 -0
- package/dist/core/ingestion/utils/callable-flow-captures.js +75 -14
- package/dist/core/ingestion/utils/test-file-path.d.ts +28 -0
- package/dist/core/ingestion/utils/test-file-path.js +87 -0
- package/dist/core/ingestion/variable-extractors/configs/zig.d.ts +2 -0
- package/dist/core/ingestion/variable-extractors/configs/zig.js +71 -0
- package/dist/core/ingestion/workers/parse-worker.js +85 -14
- package/dist/core/install-context.d.ts +4 -0
- package/dist/core/install-context.js +152 -0
- package/dist/core/lbug/csv-generator.d.ts +10 -1
- package/dist/core/lbug/csv-generator.js +92 -22
- package/dist/core/lbug/graph-emit-sink.d.ts +3 -2
- package/dist/core/lbug/graph-emit-sink.js +4 -2
- package/dist/core/lbug/lbug-adapter.d.ts +100 -5
- package/dist/core/lbug/lbug-adapter.js +236 -24
- package/dist/core/lbug/schema.d.ts +6 -5
- package/dist/core/lbug/schema.js +84 -22
- package/dist/core/net/url-guard.d.ts +6 -0
- package/dist/core/net/url-guard.js +147 -0
- package/dist/core/package-version.d.ts +5 -0
- package/dist/core/package-version.js +10 -0
- package/dist/core/run-analyze.d.ts +21 -4
- package/dist/core/run-analyze.js +426 -117
- package/dist/core/search/fts-indexes.d.ts +19 -1
- package/dist/core/search/fts-indexes.js +28 -1
- package/dist/core/tree-sitter/parser-loader.js +16 -0
- package/dist/core/update-cache.d.ts +39 -0
- package/dist/core/update-cache.js +141 -0
- package/dist/core/update-check.d.ts +31 -0
- package/dist/core/update-check.js +274 -0
- package/dist/mcp/local/local-backend.d.ts +27 -13
- package/dist/mcp/local/local-backend.js +148 -60
- package/dist/mcp/repository-policy.d.ts +5 -1
- package/dist/mcp/repository-policy.js +48 -4
- package/dist/mcp/resources.js +10 -1
- package/dist/mcp/server.js +8 -9
- package/dist/mcp/tools.js +32 -22
- package/dist/server/analyze-launch.d.ts +2 -0
- package/dist/server/analyze-launch.js +2 -0
- package/dist/server/analyze-worker-protocol.d.ts +6 -1
- package/dist/server/analyze-worker.d.ts +1 -0
- package/dist/server/analyze-worker.js +38 -20
- package/dist/server/api.d.ts +5 -1
- package/dist/server/api.js +105 -88
- package/dist/server/git-clone.d.ts +40 -16
- package/dist/server/git-clone.js +253 -199
- package/dist/server/grep-params.d.ts +18 -0
- package/dist/server/grep-params.js +83 -0
- package/dist/server/grep-scan.d.ts +23 -0
- package/dist/server/grep-scan.js +107 -0
- package/dist/server/grep-worker.d.ts +1 -0
- package/dist/server/grep-worker.js +12 -0
- package/dist/server/mcp-http.d.ts +8 -0
- package/dist/server/mcp-http.js +16 -1
- package/dist/server/update-controller.d.ts +29 -0
- package/dist/server/update-controller.js +82 -0
- package/dist/storage/file-lock.d.ts +15 -0
- package/dist/storage/file-lock.js +155 -0
- package/dist/storage/fs-atomic.d.ts +24 -0
- package/dist/storage/fs-atomic.js +86 -2
- package/dist/storage/global-dir.d.ts +2 -0
- package/dist/storage/global-dir.js +6 -0
- package/dist/storage/parse-cache.d.ts +45 -7
- package/dist/storage/parse-cache.js +189 -42
- package/dist/storage/parsedfile-store.d.ts +41 -60
- package/dist/storage/parsedfile-store.js +247 -171
- package/dist/storage/repo-manager.d.ts +10 -4
- package/dist/storage/repo-manager.js +35 -25
- package/dist/storage/repo-meta.d.ts +34 -0
- package/dist/storage/v8-sidecar.d.ts +48 -0
- package/dist/storage/v8-sidecar.js +347 -0
- package/dist/types/pipeline.d.ts +14 -0
- package/dist/utils/process-identity.d.ts +2 -0
- package/dist/utils/process-identity.js +35 -0
- package/package.json +10 -4
- package/scripts/assert-web-assets.mjs +35 -0
- package/scripts/build-web.js +72 -0
- package/scripts/build.js +31 -28
- package/scripts/cross-platform-tests.ts +5 -0
- package/skills/gitnexus-cli.md +5 -1
- package/web/assets/ProcessFlowModal-DAeX2BHW.js +24 -0
- package/web/assets/{abnfDiagram-QNN2D4NM-Cq2djWWe.js → abnfDiagram-PAR5IXJI-J3OdeHLl.js} +1 -1
- package/web/assets/{agent-BWAXPjC5.js → agent-BMRqXorA.js} +6 -4
- package/web/assets/architecture-7GRP2DOG-M7G3SWCO-G3sWyX6M.js +1 -0
- package/web/assets/architectureDiagram-2PNWDENW-ysKcRlGT.js +36 -0
- package/web/assets/blockDiagram-PUWGUBOA-oLXTa1lY.js +129 -0
- package/web/assets/c4Diagram-3NLNHZP5-D3iu4eC0.js +38 -0
- package/web/assets/{chunk-2SREHG4O-Dip42xOq.js → chunk-2NMSUPJH-B24w-wng.js} +1 -1
- package/web/assets/{chunk-VJFVEZTB-CX66F2ep.js → chunk-32MCUW2P-ByUyt0Sa.js} +3 -3
- package/web/assets/{chunk-4RFN2BYJ-DHHWuC-n.js → chunk-3NPNU5GP-DocZ07Sg.js} +1 -1
- package/web/assets/{chunk-XNMVGMAZ-Cg_LnPtj.js → chunk-3SMGFRGC-CAu4JRLv.js} +1 -1
- package/web/assets/{chunk-W44A43WB-BN8EQc_u.js → chunk-4CAJECRU-DRYMwG9z.js} +1 -1
- package/web/assets/chunk-4OADUZ5Q-CxrSvOwZ.js +10 -0
- package/web/assets/{chunk-DMV4VAQV-Bn6WXDSX.js → chunk-5IQCK5RY-CCMhl4so.js} +2 -2
- package/web/assets/{chunk-RRFMTAIC-CMN0wcYA.js → chunk-5MVFYWFU-Db9KvrYO.js} +1 -1
- package/web/assets/chunk-CLS4B6BI-Uvx5QNsu.js +168 -0
- package/web/assets/chunk-DZP67EKU-B3Mt380j.js +70 -0
- package/web/assets/{chunk-6L755F7B-CMhLBxi2.js → chunk-EICP7K5X-CqwE9hui.js} +1 -1
- package/web/assets/{chunk-BI6VK774-D0oaWk8V.js → chunk-GDY2UAB4-CtQQR0a8.js} +1 -1
- package/web/assets/chunk-H7VHZCWX-CE1nAmt6.js +1 -0
- package/web/assets/chunk-IJBDOHL6-BUKtY6oF.js +1 -0
- package/web/assets/{chunk-OIYT25JQ-CQ6KOZGX.js → chunk-JFFVMJIA-0fJenqia.js} +1 -1
- package/web/assets/{chunk-HNC4WDU7-DoU3KnA7.js → chunk-JZGDI5JK-BeTMSSkJ.js} +1 -1
- package/web/assets/{chunk-EQFTRU2I-1ix7TzW9.js → chunk-KD6SPIPN-Qp1Pflah.js} +1 -1
- package/web/assets/chunk-NF366YI6-CpSpya3n.js +231 -0
- package/web/assets/chunk-NUDPKBEF-CU1-BQIW.js +34 -0
- package/web/assets/{chunk-SATU7PGQ-B1zhgOvc.js → chunk-OJINMHTS-zOCQe4eI.js} +1 -1
- package/web/assets/{chunk-E5QJAATJ-dh-5CtIV.js → chunk-Q6NGPIAH-yyBomb63.js} +1 -1
- package/web/assets/chunk-QJSWEUOL-D_rf9gPD.js +128 -0
- package/web/assets/chunk-R7JDMUKV-CRoqKpD6.js +1 -0
- package/web/assets/{chunk-VE5CLXGZ-C1vB7wpY.js → chunk-RCXAM2GK-CUqrWQDV.js} +1 -1
- package/web/assets/{chunk-66DQ2XMT-CM0xjFAN.js → chunk-RT4MAWR3-zXy9-Lvt.js} +1 -1
- package/web/assets/{chunk-TZUXEDM2-B7p9AcTw.js → chunk-SFMINX4W-J2UrvMpD.js} +1 -1
- package/web/assets/{chunk-LORZLZWG-B57uGw2N.js → chunk-SLDQ463B-CHY6e_bQ.js} +2 -2
- package/web/assets/{chunk-FWYVLQTC-BgDItRzJ.js → chunk-TCEHX7R3-CXPDEVbm.js} +1 -1
- package/web/assets/{chunk-224SPVON-B8gxtXpy.js → chunk-VQC2EELJ-Dc8fIy4R.js} +1 -1
- package/web/assets/chunk-VRZKL3EH-DB3KixTQ.js +1 -0
- package/web/assets/{chunk-A7VWPJGB-B4O3oQdQ.js → chunk-VTHDQ7BV-b7FsPQZO.js} +1 -1
- package/web/assets/chunk-VY2OSTJR-Dkgc3Brg.js +2 -0
- package/web/assets/chunk-WUBWJARI-7FwILJ3k.js +2 -0
- package/web/assets/{chunk-D6VWDJW2-BxcBdZ9c.js → chunk-YBMQ5JGJ-BSnyWZmd.js} +1 -1
- package/web/assets/chunk-YDMLL4PJ-IHhMngTz.js +2 -0
- package/web/assets/chunk-Z6V44ARX-BjCEsF0Q.js +62 -0
- package/web/assets/chunk-ZJJ7VQJP-Dz089b-K.js +321 -0
- package/web/assets/chunk-ZNLK7HRR-B0iFpmW4.js +1 -0
- package/web/assets/chunk-ZQN7UHIL-Cy7KAjQt.js +1 -0
- package/web/assets/{chunk-L5GCOVLC-CE2w8Nm_.js → chunk-ZS6VBONO-DBjJO0w9.js} +2 -2
- package/web/assets/{chunk-NV3KIAZN-5yvcpMiu.js → chunk-ZUNWM646-DNRJyNPe.js} +1 -1
- package/web/assets/classDiagram-FGAMPOII-C5Ibyode.js +1 -0
- package/web/assets/classDiagram-v2-AI5RJEK4-C5Ibyode.js +1 -0
- package/web/assets/{cose-bilkent-RZA4XIPR-BHpK4Crp.js → cose-bilkent-Q72C2OJ3-DMeFXnK8.js} +1 -1
- package/web/assets/cynefin-OW5HDTMX-6OCTWOGG-vlqakYcO.js +1 -0
- package/web/assets/{cynefinDiagram-TR7LZUCG-C3Iar9lZ.js → cynefinDiagram-2OOWUY4M-BY6DZkTt.js} +1 -1
- package/web/assets/dagre-MPVFI544-DWO70fy_.js +4 -0
- package/web/assets/diagram-AE4NWXFW-f7JRVRUL.js +24 -0
- package/web/assets/{diagram-L7DVE3PN-GIRyVUtt.js → diagram-BUJID5UG-BXZv1rCS.js} +2 -2
- package/web/assets/{diagram-JCD7PQ7C-DC5-9FRr.js → diagram-C44CZ5NI-CRNfvjCq.js} +1 -1
- package/web/assets/diagram-EYOLEXXS-DZOjKQVE.js +30 -0
- package/web/assets/diagram-P7B6PWOJ-D3SQmw3_.js +3 -0
- package/web/assets/{ebnfDiagram-DRHRQJWO-D8_CI4ZF.js → ebnfDiagram-FTPZ3PV6-jKWpIDN2.js} +1 -1
- package/web/assets/erDiagram-H3ERR26V-BqJntEuJ.js +99 -0
- package/web/assets/eventmodeling-NTZA5JFV-WKSWQHFS-Bq4FqtBL.js +1 -0
- package/web/assets/flowDiagram-YHGXBVSY-Dnlk2bEu.js +1 -0
- package/web/assets/ganttDiagram-RWFTB7ED-Bl6Y1K5T.js +292 -0
- package/web/assets/gitGraph-4MIJSDKK-JH2HKYUO-B3yc4GkH.js +1 -0
- package/web/assets/gitGraphDiagram-MAR6GKOP-CkNvsrdw.js +106 -0
- package/web/assets/{index-CJDpyH-n.css → index-C2_aej58.css} +1 -1
- package/web/assets/{index-DDRie6_8.js → index-Dj3vQOHK.js} +51 -51
- package/web/assets/info-A6RAGUB7-3TX3OFWW-BM3N5_Dv.js +1 -0
- package/web/assets/infoDiagram-LV547AFH-COkFutAf.js +2 -0
- package/web/assets/{ishikawaDiagram-4JDSWWDM-DmqWtjkl.js → ishikawaDiagram-PUJQGNDS-Bb8FylSR.js} +2 -2
- package/web/assets/{journeyDiagram-QUS3FAKT-XAzVzrQW.js → journeyDiagram-EW5QJT4W-BzoHUBhM.js} +4 -4
- package/web/assets/{kanban-definition-D4L7SQKK-YurRnmYn.js → kanban-definition-JSQERAZB-Uy2Pi0yQ.js} +4 -4
- package/web/assets/katex-CZ4GXH2S-C0Fcou7U.js +257 -0
- package/web/assets/{mindmap-definition-66QP5TD6-ChrWWnil.js → mindmap-definition-VBJCLQLM-B-CnOnt_.js} +3 -3
- package/web/assets/packet-AYTQ26CC-MKII6P3Y-Bd52ZE3V.js +1 -0
- package/web/assets/{pegDiagram-YKIO5C6U-QGeQLEq2.js → pegDiagram-7WILOQTJ-D9sneKXg.js} +1 -1
- package/web/assets/pie-WAS4IAKB-6B4OPMJ5-DW5Xy69Z.js +1 -0
- package/web/assets/pieDiagram-EFA7CBSB-Dx2NQofL.js +39 -0
- package/web/assets/{quadrantDiagram-VO5QLCXA-B7098wSc.js → quadrantDiagram-WARVYA7P-DPKL9_0A.js} +3 -3
- package/web/assets/radar-RG4KPBEZ-UPTR3XUL-CkzibT2h.js +1 -0
- package/web/assets/railroad-74A4TZTK-MEFUPFMS-D9zF-mDN.js +1 -0
- package/web/assets/railroad-abnf-HS5TGJTU-R5YG2S5D-ruacjUbX.js +1 -0
- package/web/assets/railroad-ebnf-LZEXJU2U-G5QEX2L6-S1zc5OiO.js +1 -0
- package/web/assets/railroad-peg-WCYAUIDC-H5I2ZRY2-Cbcr0XzY.js +1 -0
- package/web/assets/{railroadDiagram-M77LTMSB-AXu4vxk_.js → railroadDiagram-5NIQVNLQ-DsweTo14.js} +1 -1
- package/web/assets/{requirementDiagram-RASO5HTO-B-l8InpK.js → requirementDiagram-SXREGOKZ-CXwdNSVR.js} +4 -4
- package/web/assets/{sankeyDiagram-F32LOJ6L-Kd7zjJgX.js → sankeyDiagram-VV74M2VP-CA6jtP9P.js} +3 -3
- package/web/assets/{sequenceDiagram-URATNSBD-BlcdY9DS.js → sequenceDiagram-52ZFIFDL-k_5rEbzm.js} +15 -15
- package/web/assets/src-Df5C1Nz4.js +1 -0
- package/web/assets/stateDiagram-3QAKPIVI-BejHTf31.js +1 -0
- package/web/assets/stateDiagram-v2-HN2P4RDZ-BrfoRuOc.js +1 -0
- package/web/assets/swimlanes-QLVP42QD-CQSVO5D6.js +1 -0
- package/web/assets/swimlanesDiagram-JKPDBFCN-DrWB9kcD.js +8 -0
- package/web/assets/{timeline-definition-WJXWRH4I-DUfp1R3E.js → timeline-definition-GS45XAXW-C0Ks8rPP.js} +4 -4
- package/web/assets/treeView-Q6P3EWNA-ZRJGANNQ-DmKOCo6L.js +1 -0
- package/web/assets/treemap-WGGIJYW6-K64VN5AS-DO3xY92U.js +1 -0
- package/web/assets/{vennDiagram-K3VCMQLI-Bj-FN-XS.js → vennDiagram-QIQU2ZRJ-rgPiS837.js} +3 -3
- package/web/assets/wardley-WFR3VGLG-D67OWCCH-DiDPBNft.js +1 -0
- package/web/assets/wardleyDiagram-U6YNM66N-DRVr7SPi.js +78 -0
- package/web/assets/{xychartDiagram-5VRUVLCW-CK_rbSWE.js → xychartDiagram-XJAZEDNM-COtlZzYb.js} +3 -3
- package/web/index.html +19 -17
- package/web/assets/ProcessFlowModal-Badh2Pbg.js +0 -24
- package/web/assets/architecture-TIHT7OUA-V6N4J3SY-fTePvLoe.js +0 -1
- package/web/assets/architectureDiagram-OT7VKSJ7-mKEXxkeF.js +0 -36
- package/web/assets/blockDiagram-54WLDVWM-Dg_hrRWE.js +0 -132
- package/web/assets/c4Diagram-X2LASM7V-CnhoT4aV.js +0 -10
- package/web/assets/chunk-2AEHWXPW-CV4r6-1I.js +0 -127
- package/web/assets/chunk-5VCL7Z4A-CQ-2dYWj.js +0 -1
- package/web/assets/chunk-6BELYETK-ChsYtfHW.js +0 -1
- package/web/assets/chunk-7CWYLC5S-DRgGkkpF.js +0 -2
- package/web/assets/chunk-7FYTHRHK-CsmYIswB.js +0 -32
- package/web/assets/chunk-KRXBNO2N-C-UNuKKf.js +0 -1
- package/web/assets/chunk-LAHSG3N2-Bz4hcn0N.js +0 -321
- package/web/assets/chunk-LIEV3EAG-pyKGssV3.js +0 -1
- package/web/assets/chunk-NLANEA3F-XpH_NPA5.js +0 -62
- package/web/assets/chunk-PE7DX7ZZ-CCvRVj43.js +0 -10
- package/web/assets/chunk-Q67WD55A-BWCH2SVz.js +0 -156
- package/web/assets/chunk-SZD42YQK-ijyKKVr1.js +0 -70
- package/web/assets/chunk-TFPGFQPA-Dj6a-0pC.js +0 -231
- package/web/assets/chunk-TGVD4F4B-BMc31Gj2.js +0 -2
- package/web/assets/chunk-UAT7B5JY-DW-A_fpI.js +0 -1
- package/web/assets/chunk-VY5UBI4V-yxPQAWxw.js +0 -1
- package/web/assets/chunk-WJBAP47W-BckNkuEN.js +0 -1
- package/web/assets/classDiagram-VPOSVPXH-BjOiXA_C.js +0 -1
- package/web/assets/classDiagram-v2-5VFNUXQA-BjOiXA_C.js +0 -1
- package/web/assets/cynefin-VYW2F7L2-2VAHYKR6-DAsF6GZv.js +0 -1
- package/web/assets/dagre-K64A6Z3X-CUwi79mA.js +0 -4
- package/web/assets/diagram-2TLGJV2N-C2F5Nfym.js +0 -30
- package/web/assets/diagram-KFBZAVDW-Dfmf_4ti.js +0 -3
- package/web/assets/diagram-YP3FGXHI-CyNFYtkd.js +0 -24
- package/web/assets/erDiagram-SZLJLQMB-CohQjznC.js +0 -85
- package/web/assets/eventmodeling-45OFAUF4-AOUJWSLX-BvTc6iQi.js +0 -1
- package/web/assets/flowDiagram-BWE6NHOH-B6RnEvD9.js +0 -1
- package/web/assets/ganttDiagram-2QTCJSNI-Cks_mEcF.js +0 -292
- package/web/assets/gitGraph-TEB2WS4Q-U5VWYIBC-Dy7CCSZ5.js +0 -1
- package/web/assets/gitGraphDiagram-VVZAVV5G-CG41cIVc.js +0 -106
- package/web/assets/info-DKCQHKI2-DX6KST54-BLJg1ikX.js +0 -1
- package/web/assets/infoDiagram-FY4PZKRE-BJ3vPVwW.js +0 -2
- package/web/assets/katex-C5OPUE3Q-Bnbavtmu.js +0 -257
- package/web/assets/packet-7NZHBO7P-5WG45QPS-KUxcuvaA.js +0 -1
- package/web/assets/pie-RZYD4A2V-2OCZ2VTD-DPs08kJY.js +0 -1
- package/web/assets/pieDiagram-YPY3TKG5-BuT3MYzm.js +0 -39
- package/web/assets/radar-I7S5WNFK-EPLSA35B-w5F0K3a7.js +0 -1
- package/web/assets/railroad-3IZDKUUU-PWFQV6Y6-Bu668yqY.js +0 -1
- package/web/assets/railroad-abnf-AHOZXSZD-JYYJX3BS-B6ghpETS.js +0 -1
- package/web/assets/railroad-ebnf-EBAXGLYW-RBZ4ONAP-D-XmHh3M.js +0 -1
- package/web/assets/railroad-peg-LSFZ7HO6-KHNPXGPG-mhkrhzEn.js +0 -1
- package/web/assets/src-C5sx_gMp.js +0 -1
- package/web/assets/stateDiagram-DODFXNG5-DTdHc-fF.js +0 -1
- package/web/assets/stateDiagram-v2-JNMM5JRA-DioNLDDo.js +0 -1
- package/web/assets/swimlanes-OSWOYLSK-CA0ofdwY.js +0 -2
- package/web/assets/swimlanesDiagram-RHGX2RDE-PkwUdckC.js +0 -8
- package/web/assets/treeView-QDETBFTQ-EHYTSQNO-C5lpBgPm.js +0 -1
- package/web/assets/treemap-6X3UGDF4-CCWCF7EK-BB0ZyO-B.js +0 -1
- package/web/assets/wardley-OPB4EBWU-6OWERDVT-CEA3Nq0j.js +0 -1
- package/web/assets/wardleyDiagram-XK6FUAWV-CVV0dHFr.js +0 -78
- /package/web/assets/{sizeCapture-5GORAGP4-BEqQzsxs.js → sizeCapture-FGA5AGYP-BEqQzsxs.js} +0 -0
|
@@ -0,0 +1,688 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase: springDestinations
|
|
3
|
+
*
|
|
4
|
+
* Materializes Spring async messaging as graph structure: a `Destination` node
|
|
5
|
+
* per broker address, `CONSUMES_FROM` from every `@KafkaListener`-family
|
|
6
|
+
* handler, and `PUBLISHES_TO` from every messaging-template publish. The
|
|
7
|
+
* inbound and outbound facts are captured during parse and survive the parse
|
|
8
|
+
* cache; until now nothing read them.
|
|
9
|
+
*
|
|
10
|
+
* Since `asyncApiSpecPath`, the phase has a SECOND source that is not Spring
|
|
11
|
+
* and not source code at all: AsyncAPI documents read off disk
|
|
12
|
+
* (`ingestion/asyncapi/document.ts`). They mint the same `Destination` nodes on
|
|
13
|
+
* the same key, so both sources meet on one node. The reader is deliberately
|
|
14
|
+
* framework-neutral and lives outside `frameworks/spring/`; only the emit is
|
|
15
|
+
* hosted here, because this is where the node's keying rule is enforced and
|
|
16
|
+
* splitting that rule across two phases is how it drifts. The phase name is
|
|
17
|
+
* accurate about its origin rather than its current contents.
|
|
18
|
+
*
|
|
19
|
+
* Shaped after `Route` + `HANDLES_ROUTE` in `routes.ts` — a framework overlay
|
|
20
|
+
* node keyed by what it names, with the callable pointing at it, down to the
|
|
21
|
+
* detail that the key pairs the address with the one dimension that can make
|
|
22
|
+
* two same-named things different places: the method for a Route, the broker
|
|
23
|
+
* here.
|
|
24
|
+
*
|
|
25
|
+
* ── THE KEYING RULE, WHICH IS THE POINT OF THE PHASE ─────────────────────
|
|
26
|
+
*
|
|
27
|
+
* A `Destination` connects two services precisely because both sides mint the
|
|
28
|
+
* SAME node id from the SAME address on the SAME broker. That is the whole
|
|
29
|
+
* value, and it is also the whole hazard: an address that could not be resolved
|
|
30
|
+
* must never be allowed to key a node.
|
|
31
|
+
*
|
|
32
|
+
* resolved id = generateId('Destination', `<broker> <address>`) `address` present
|
|
33
|
+
* unresolved id = generateId('Destination', <site>) `address` ABSENT
|
|
34
|
+
*
|
|
35
|
+
* Two unrelated services that each merely write `@KafkaListener(topics =
|
|
36
|
+
* "${app.topic}")` have said nothing whatever about each other. Keyed on the
|
|
37
|
+
* placeholder text they would land on one node and READ AS CONNECTED, in a
|
|
38
|
+
* report, as a fact. A missing edge is visible as a gap; a false one is not.
|
|
39
|
+
*
|
|
40
|
+
* A status property would not have prevented this, and neither would a second
|
|
41
|
+
* label: the id is what merges the nodes, and both sides would still compute
|
|
42
|
+
* the same id. Only the KEY prevents it, so an unresolved destination is keyed
|
|
43
|
+
* by its source LOCATION — a value no second file can produce.
|
|
44
|
+
*
|
|
45
|
+
* The same rule governs the `address` PROPERTY, which is the join key a
|
|
46
|
+
* cross-repository pass would match on. It is written only when resolved. An
|
|
47
|
+
* absent property cannot match another absent property, so the structural
|
|
48
|
+
* guarantee survives being read back out of the database. The unresolved
|
|
49
|
+
* spelling is kept in `name`, for a human reading the node.
|
|
50
|
+
*
|
|
51
|
+
* The BROKER is part of the connecting key rather than a reason to withdraw
|
|
52
|
+
* one — see {@link destinationNodeKey}, which owns that argument and the
|
|
53
|
+
* evidence for it. Two brokers claiming one address is therefore an ordinary
|
|
54
|
+
* two-node situation here, exactly like `GET /x` and `POST /x`, and this phase
|
|
55
|
+
* needs no vocabulary for it: nothing is being taken away, so there is nothing
|
|
56
|
+
* to diagnose.
|
|
57
|
+
*
|
|
58
|
+
* `name` must never be used to join two destinations, and nothing does — but
|
|
59
|
+
* the stronger claim that nothing reads it at all would be false. `Destination`
|
|
60
|
+
* is in `VALID_NODE_LABELS`, and `mcp/local/local-backend.ts` resolves a
|
|
61
|
+
* symbol with an unlabeled `WHERE n.name = $symName`, so a destination can be
|
|
62
|
+
* returned by name like any other node. That is a lookup, not a join: it
|
|
63
|
+
* matches a caller-supplied string against one node, never one destination
|
|
64
|
+
* against another, so it cannot manufacture the connection this phase exists to
|
|
65
|
+
* prevent.
|
|
66
|
+
*
|
|
67
|
+
* @deps parse, scopeResolution, springConfig
|
|
68
|
+
* @reads Spring messaging capture facts, Method/Function nodes, Property nodes,
|
|
69
|
+
* AsyncAPI documents under `options.asyncApiSpecPath` (filesystem)
|
|
70
|
+
* @writes Destination nodes; synthetic File nodes for out-of-tree documents;
|
|
71
|
+
* CONSUMES_FROM / PUBLISHES_TO / USES edges
|
|
72
|
+
*/
|
|
73
|
+
import path from 'node:path';
|
|
74
|
+
import { generateId } from '../../../lib/utils.js';
|
|
75
|
+
import { readAsyncApiDocuments } from '../asyncapi/document.js';
|
|
76
|
+
import { logger } from '../../logger.js';
|
|
77
|
+
import { SPRING_CONFIG_DESCRIPTION } from '../frameworks/spring/config-bindings.js';
|
|
78
|
+
import { parseSpringStringLiteral, resolveSpringDestination, selectConsumerDestinationArguments, selectProducerDestinationArguments, } from '../frameworks/spring/destinations.js';
|
|
79
|
+
import { destinationNodeKey } from '../destination-key.js';
|
|
80
|
+
import { getProviderForFile } from '../languages/index.js';
|
|
81
|
+
import { isDev } from '../utils/env.js';
|
|
82
|
+
import { getPhaseOutput } from './types.js';
|
|
83
|
+
/**
|
|
84
|
+
* Exact-range index of callable nodes, mirroring the bridge in
|
|
85
|
+
* `non-http-handlers.ts`.
|
|
86
|
+
*
|
|
87
|
+
* A duplicate range maps to `null` rather than to one of its nodes: two
|
|
88
|
+
* callables sharing a span means the index cannot say which one publishes, and
|
|
89
|
+
* attributing the publish to an arbitrary one of them is the failure this
|
|
90
|
+
* phase is least able to detect afterwards. The File-level edge below is the
|
|
91
|
+
* fallback, so a `null` here costs precision, not the fact.
|
|
92
|
+
*/
|
|
93
|
+
function callableOwnersByRange(graph) {
|
|
94
|
+
const owners = new Map();
|
|
95
|
+
for (const node of graph.iterNodes()) {
|
|
96
|
+
if ((node.label !== 'Method' && node.label !== 'Function') ||
|
|
97
|
+
typeof node.properties.filePath !== 'string') {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const key = `${node.properties.filePath}\0${node.properties.startLine}\0${node.properties.endLine}`;
|
|
101
|
+
owners.set(key, owners.has(key) ? null : node);
|
|
102
|
+
}
|
|
103
|
+
return owners;
|
|
104
|
+
}
|
|
105
|
+
/** Capture ranges are 1-based; graph nodes carry 0-based lines. */
|
|
106
|
+
function ownerKey(filePath, range) {
|
|
107
|
+
return `${filePath}\0${range.startLine - 1}\0${range.endLine - 1}`;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Spring configuration `Property` nodes grouped by KEY.
|
|
111
|
+
*
|
|
112
|
+
* Deliberately a multimap. `spring-config.ts` keys a Property node per FILE
|
|
113
|
+
* (`spring-config:<file>:<key>`), so one key declared in `application.yml` and
|
|
114
|
+
* again in `application-prod.yml` is TWO nodes. Linking to only the first would
|
|
115
|
+
* silently pin a destination to an arbitrary profile. An EMPTY match set is
|
|
116
|
+
* normal, not an error — a key may be supplied by an environment variable or a
|
|
117
|
+
* config server and never appear in a checked-in file at all.
|
|
118
|
+
*/
|
|
119
|
+
function springConfigPropertiesByKey(graph) {
|
|
120
|
+
const byKey = new Map();
|
|
121
|
+
for (const node of graph.iterNodes()) {
|
|
122
|
+
if (node.label !== 'Property')
|
|
123
|
+
continue;
|
|
124
|
+
const description = node.properties.description;
|
|
125
|
+
if (typeof description !== 'string' || !description.startsWith(SPRING_CONFIG_DESCRIPTION)) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const key = node.properties.name;
|
|
129
|
+
if (typeof key !== 'string' || key === '')
|
|
130
|
+
continue;
|
|
131
|
+
const existing = byKey.get(key);
|
|
132
|
+
if (existing === undefined)
|
|
133
|
+
byKey.set(key, [node.id]);
|
|
134
|
+
else
|
|
135
|
+
existing.push(node.id);
|
|
136
|
+
}
|
|
137
|
+
return byKey;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Fold a constant reference against the harvested repo constants, using the
|
|
141
|
+
* owning provider's own fold when it declares one.
|
|
142
|
+
*
|
|
143
|
+
* Only providers that declare `extractModuleConstants` contribute to the table,
|
|
144
|
+
* so a language that harvests nothing simply resolves nothing here and the
|
|
145
|
+
* cascade records `unresolved-constant`. That is a countable gap, not a wrong
|
|
146
|
+
* answer.
|
|
147
|
+
*/
|
|
148
|
+
function makeConstantResolver(filePath, repo) {
|
|
149
|
+
if (repo.size === 0)
|
|
150
|
+
return undefined;
|
|
151
|
+
const fold = getProviderForFile(filePath)?.foldRoutePathOperands;
|
|
152
|
+
if (fold === undefined)
|
|
153
|
+
return undefined;
|
|
154
|
+
return (name) => fold(filePath, [{ kind: 'ref', name }], repo);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Identity for a destination node.
|
|
158
|
+
*
|
|
159
|
+
* The connecting key is `(broker, address)` and nothing else — not the file,
|
|
160
|
+
* not the site. That is what lets a publisher in one module and a subscriber in
|
|
161
|
+
* another meet on one node, which is the entire point, and it is minted by the
|
|
162
|
+
* framework-neutral {@link destinationNodeKey} so a non-Spring producer can mint
|
|
163
|
+
* the same identity without importing anything Spring.
|
|
164
|
+
*
|
|
165
|
+
* The broker is IN that key rather than a reason to withhold one. The argument
|
|
166
|
+
* for it, including the inferred-broker objection and why the previous rule was
|
|
167
|
+
* worse, lives on `destinationNodeKey` — one copy, next to the code that
|
|
168
|
+
* decides it.
|
|
169
|
+
*
|
|
170
|
+
* The site key has to identify the site EXACTLY. It carries the file path, so
|
|
171
|
+
* no second file can ever produce it — that is the cross-repository guarantee,
|
|
172
|
+
* and nothing added below can weaken it. Everything else in the key is there to
|
|
173
|
+
* keep two sites inside ONE file apart, which is the same false identity at a
|
|
174
|
+
* smaller scale:
|
|
175
|
+
*
|
|
176
|
+
* - the owner SCOPE ID, because two callables can start on the same line and
|
|
177
|
+
* because `ownerRange` is optional on a handler fact — keyed on the line
|
|
178
|
+
* alone, a file whose handlers carried no range collapsed every consumer in
|
|
179
|
+
* it onto line 0;
|
|
180
|
+
* - the full owner RANGE, which separates two sites the scope id cannot (a
|
|
181
|
+
* scope id is stable, but two callables that share one are still two
|
|
182
|
+
* callables);
|
|
183
|
+
* - the raw TEXT plus argument and element position, because two publishes to
|
|
184
|
+
* two different placeholders inside one method share the owner entirely.
|
|
185
|
+
*
|
|
186
|
+
* The residual: two publishes with identical text at identical argument
|
|
187
|
+
* positions inside ONE callable share a node. They are indistinguishable to
|
|
188
|
+
* this phase — a producer fact carries the owner's range, not the call's — and
|
|
189
|
+
* merging two publishes of the same unreadable address from one method is the
|
|
190
|
+
* one collapse that asserts nothing false about anybody.
|
|
191
|
+
*/
|
|
192
|
+
function destinationNodeId(site) {
|
|
193
|
+
if (site.resolution.kind === 'resolved') {
|
|
194
|
+
return generateId('Destination', destinationNodeKey(site.candidate.broker, site.resolution.address));
|
|
195
|
+
}
|
|
196
|
+
const { candidate, ownerRange } = site;
|
|
197
|
+
const position = ownerRange === undefined
|
|
198
|
+
? 'no-range'
|
|
199
|
+
: `${ownerRange.startLine}:${ownerRange.startCol}:${ownerRange.endLine}:${ownerRange.endCol}`;
|
|
200
|
+
return generateId('Destination', [
|
|
201
|
+
site.filePath,
|
|
202
|
+
site.ownerScopeId,
|
|
203
|
+
position,
|
|
204
|
+
candidate.role,
|
|
205
|
+
candidate.source,
|
|
206
|
+
candidate.argIndex,
|
|
207
|
+
candidate.elementIndex,
|
|
208
|
+
candidate.rawText,
|
|
209
|
+
].join(':'));
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Display spelling for a destination's `name`.
|
|
213
|
+
*
|
|
214
|
+
* A resolved destination's `name` is its address, bare. An unresolved one keeps
|
|
215
|
+
* the source text — but unquoted, so the two are spelled the same way. Keeping
|
|
216
|
+
* the quotes on one and not the other made the same value read differently
|
|
217
|
+
* depending on whether it resolved, for no gain to the human the property
|
|
218
|
+
* exists for.
|
|
219
|
+
*/
|
|
220
|
+
function destinationDisplayName(rawText) {
|
|
221
|
+
return parseSpringStringLiteral(rawText) ?? rawText.trim();
|
|
222
|
+
}
|
|
223
|
+
function edgeReason(candidate) {
|
|
224
|
+
const argument = candidate.argName ?? `arg${candidate.argIndex}`;
|
|
225
|
+
const element = `${argument}[${candidate.elementIndex}]`;
|
|
226
|
+
const exchange = candidate.exchange === undefined ? '' : ` exchange=${candidate.exchange}`;
|
|
227
|
+
return `spring-${candidate.source}:${element}${exchange}`;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Pseudo-path prefix for a document that is not a file of this repository.
|
|
231
|
+
*
|
|
232
|
+
* An edge needs a source node, and the emit below refuses to attach one to a
|
|
233
|
+
* `File` that does not exist — so a document supplied from outside the working
|
|
234
|
+
* tree needs an identity minted for it. The same answer
|
|
235
|
+
* `frameworks/spring/actuator-runtime.ts` gives for Actuator snapshots
|
|
236
|
+
* (`spring-actuator:<endpoint>`): a prefixed pseudo-path that a real
|
|
237
|
+
* repo-relative path is not expected to take.
|
|
238
|
+
*
|
|
239
|
+
* That is a CONVENTION, not a guarantee, and the difference is worth stating
|
|
240
|
+
* because the neighbouring prefix states it too strongly. A colon is a legal
|
|
241
|
+
* POSIX filename character, so a committed file literally named
|
|
242
|
+
* `asyncapi:orders.yaml` would share this identity — costing one merged node
|
|
243
|
+
* and a misattributed edge, never a wrong address. Windows cannot express the
|
|
244
|
+
* collision at all. It is accepted on the same terms the Actuator prefix
|
|
245
|
+
* already is rather than escaped, because an escape would have to be applied to
|
|
246
|
+
* both prefixes at once to be worth anything.
|
|
247
|
+
*/
|
|
248
|
+
const DOCUMENT_FILE_PREFIX = 'asyncapi:';
|
|
249
|
+
/**
|
|
250
|
+
* The `File` node an AsyncAPI operation's edge hangs off.
|
|
251
|
+
*
|
|
252
|
+
* A document COMMITTED to the repository already has a real `File` node, and
|
|
253
|
+
* using it is strictly better: the edge lands on something the reader can open,
|
|
254
|
+
* and the ordinary per-file writeback keeps it honest. Only a document from
|
|
255
|
+
* outside the tree gets a synthetic node.
|
|
256
|
+
*/
|
|
257
|
+
function documentFileNodeId(ctx, configuredRoot, documentPath) {
|
|
258
|
+
const repoRelative = path.relative(ctx.repoPath, documentPath);
|
|
259
|
+
if (repoRelative !== '' && !repoRelative.startsWith('..') && !path.isAbsolute(repoRelative)) {
|
|
260
|
+
const realId = generateId('File', repoRelative.split(path.sep).join('/'));
|
|
261
|
+
if (ctx.graph.getNode(realId) !== undefined)
|
|
262
|
+
return realId;
|
|
263
|
+
}
|
|
264
|
+
// Relative to the CONFIGURED root, not to the filesystem root: an absolute
|
|
265
|
+
// path would put a machine's directory layout into the graph, and two
|
|
266
|
+
// machines indexing the same documents would then disagree about their ids.
|
|
267
|
+
const relative = path.relative(configuredRoot, documentPath);
|
|
268
|
+
const label = relative === '' || relative.startsWith('..')
|
|
269
|
+
? path.basename(documentPath)
|
|
270
|
+
: relative.split(path.sep).join('/');
|
|
271
|
+
const filePath = `${DOCUMENT_FILE_PREFIX}${label}`;
|
|
272
|
+
const id = generateId('File', filePath);
|
|
273
|
+
if (ctx.graph.getNode(id) === undefined) {
|
|
274
|
+
ctx.graph.addNode({
|
|
275
|
+
id,
|
|
276
|
+
label: 'File',
|
|
277
|
+
properties: { name: path.basename(documentPath), filePath },
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
return id;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Mint destinations stated by AsyncAPI documents, with no claim about code.
|
|
284
|
+
*
|
|
285
|
+
* ── WHY THIS DOES NOT TRY TO FIND THE HANDLER ─────────────────────────────
|
|
286
|
+
*
|
|
287
|
+
* A document says an address is sent to or received from; it does not say by
|
|
288
|
+
* which method. Guessing that mapping is a real temptation and a bad trade: the
|
|
289
|
+
* addresses in one document partition by (broker, action) into buckets that
|
|
290
|
+
* usually hold more than one operation, so any assignment beyond a bucket of
|
|
291
|
+
* size one is a heuristic — and a wrong one silently attaches a real address to
|
|
292
|
+
* the wrong handler, which is a false connection dressed as a resolved one.
|
|
293
|
+
*
|
|
294
|
+
* So this claims only what the document actually states: that THIS SERVICE
|
|
295
|
+
* talks to that address on that broker in that direction. The edge therefore
|
|
296
|
+
* starts at the document, not at a callable. That is a weaker statement than a
|
|
297
|
+
* source-derived edge and it is worth having anyway, because it is available in
|
|
298
|
+
* cases where the source cannot supply one at all — a listener registered
|
|
299
|
+
* programmatically, a broker this codebase has no patterns for, or a language
|
|
300
|
+
* whose messaging idiom nobody has taught it yet.
|
|
301
|
+
*
|
|
302
|
+
* The node itself is the ordinary resolved `Destination`: same key, same
|
|
303
|
+
* `address` property, so a document and a source site that name one address on
|
|
304
|
+
* one broker land on ONE node and the two halves of a conversation meet. That
|
|
305
|
+
* is the whole point, and it is why this mints nothing of its own invention.
|
|
306
|
+
*/
|
|
307
|
+
async function emitSpecDestinations(ctx, specPath) {
|
|
308
|
+
const read = await readAsyncApiDocuments(ctx.repoPath, specPath);
|
|
309
|
+
const configuredRoot = path.resolve(ctx.repoPath, specPath);
|
|
310
|
+
let destinations = 0;
|
|
311
|
+
let edges = 0;
|
|
312
|
+
for (const operation of read.operations) {
|
|
313
|
+
const nodeId = generateId('Destination', destinationNodeKey(operation.broker, operation.address));
|
|
314
|
+
// Runs AFTER the source pass, so a site that resolved this address already
|
|
315
|
+
// owns the node and keeps its own `resolution` provenance. First writer
|
|
316
|
+
// wins and the order is fixed, so the property is deterministic rather than
|
|
317
|
+
// a race — and `literal` is the more informative of the two answers anyway.
|
|
318
|
+
if (ctx.graph.getNode(nodeId) === undefined) {
|
|
319
|
+
ctx.graph.addNode({
|
|
320
|
+
id: nodeId,
|
|
321
|
+
label: 'Destination',
|
|
322
|
+
properties: {
|
|
323
|
+
name: operation.address,
|
|
324
|
+
// Empty for the same reason every connecting destination carries it
|
|
325
|
+
// empty: the node is shared, and stamping it with the document's path
|
|
326
|
+
// would make it collateral damage of that path's next writeback.
|
|
327
|
+
filePath: '',
|
|
328
|
+
address: operation.address,
|
|
329
|
+
// NOT `'specification'`, though the address did come from one. That
|
|
330
|
+
// value belongs to `SpringDestinationVia` and means "a CODE
|
|
331
|
+
// CANDIDATE was resolved through the step-4 resolver hook" — a
|
|
332
|
+
// different fact with a code site behind it. Reusing it would make a
|
|
333
|
+
// query that groups destinations by provenance unable to separate an
|
|
334
|
+
// address a document merely states from one a document was used to
|
|
335
|
+
// resolve, and the second of those is a claim about source that this
|
|
336
|
+
// node is not making.
|
|
337
|
+
resolution: 'asyncapi-document',
|
|
338
|
+
broker: operation.broker,
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
destinations += 1;
|
|
342
|
+
}
|
|
343
|
+
const sourceId = documentFileNodeId(ctx, configuredRoot, operation.documentPath);
|
|
344
|
+
const type = operation.action === 'receive' ? 'CONSUMES_FROM' : 'PUBLISHES_TO';
|
|
345
|
+
const reason = `asyncapi:${operation.operationId}`;
|
|
346
|
+
ctx.graph.addRelationship({
|
|
347
|
+
id: generateId(type, `${sourceId}->${nodeId}:${reason}`),
|
|
348
|
+
sourceId,
|
|
349
|
+
targetId: nodeId,
|
|
350
|
+
type,
|
|
351
|
+
confidence: 1.0,
|
|
352
|
+
reason,
|
|
353
|
+
});
|
|
354
|
+
edges += 1;
|
|
355
|
+
}
|
|
356
|
+
const stats = {
|
|
357
|
+
symlinksSkipped: read.symlinksSkipped,
|
|
358
|
+
truncated: read.truncated,
|
|
359
|
+
scanned: read.documentsScanned,
|
|
360
|
+
accepted: read.documentsAccepted,
|
|
361
|
+
operations: read.operations.length,
|
|
362
|
+
destinations,
|
|
363
|
+
edges,
|
|
364
|
+
refusalsByReason: read.refusals,
|
|
365
|
+
};
|
|
366
|
+
// Unconditional, and not `isDev`-gated like the summary below it. The tally
|
|
367
|
+
// above is justified on the grounds that an operator must be able to tell a
|
|
368
|
+
// mistyped directory from a repository with no documents — and that
|
|
369
|
+
// justification is only true if the operator can SEE it. A configured path
|
|
370
|
+
// that produced nothing is the one outcome where silence and success look
|
|
371
|
+
// identical from outside, which is why `spring-auto-configuration.ts` warns
|
|
372
|
+
// unconditionally for the same class of input.
|
|
373
|
+
if (stats.accepted === 0 || stats.truncated) {
|
|
374
|
+
// Repo-relative when the path is inside the repository, bare name when it
|
|
375
|
+
// is not. The same change refuses to persist this path to index metadata on
|
|
376
|
+
// the grounds that it would record an operator's directory layout; applying
|
|
377
|
+
// that reasoning to metadata and not to logs would be holding one rule in
|
|
378
|
+
// two places.
|
|
379
|
+
const resolved = path.resolve(ctx.repoPath, specPath);
|
|
380
|
+
const relative = path.relative(ctx.repoPath, resolved);
|
|
381
|
+
const reportedPath = relative !== '' && !relative.startsWith('..') && !path.isAbsolute(relative)
|
|
382
|
+
? relative.split(path.sep).join('/')
|
|
383
|
+
: path.basename(resolved);
|
|
384
|
+
logger.warn({
|
|
385
|
+
asyncApiSpecPath: reportedPath,
|
|
386
|
+
...stats,
|
|
387
|
+
}, stats.accepted === 0
|
|
388
|
+
? '⚠️ No AsyncAPI document under the configured path yielded a destination.'
|
|
389
|
+
: '⚠️ AsyncAPI document reading hit a bound; the destinations below are a floor, not a total.');
|
|
390
|
+
}
|
|
391
|
+
return stats;
|
|
392
|
+
}
|
|
393
|
+
export const springDestinationsPhase = {
|
|
394
|
+
name: 'springDestinations',
|
|
395
|
+
// `parse` supplies the file list and the harvested constants; `scopeResolution`
|
|
396
|
+
// must have run so the Method/Function nodes exist AND so each provider's
|
|
397
|
+
// `applyCaptureSideChannel` has restored the messaging facts onto the main
|
|
398
|
+
// thread; `springConfig` must have run so the Property nodes a `${key}`
|
|
399
|
+
// placeholder links to are already in the graph.
|
|
400
|
+
deps: ['parse', 'scopeResolution', 'springConfig'],
|
|
401
|
+
async execute(ctx, deps) {
|
|
402
|
+
// `allPaths`, NOT `parsedFiles`. On any run with a storage path — which is
|
|
403
|
+
// every run of the CLI — worker-produced ParsedFiles are flushed to a disk
|
|
404
|
+
// store and `ParseOutput.parsedFiles` comes back EMPTY, with
|
|
405
|
+
// scope-resolution streaming them back per language. Iterating it therefore
|
|
406
|
+
// found nothing in production while every in-process test passed, because a
|
|
407
|
+
// direct pipeline call has no storage path and keeps them in memory.
|
|
408
|
+
//
|
|
409
|
+
// The fact stores are keyed by file path and are populated by the same
|
|
410
|
+
// streaming pass, so the path list is the right cursor for them anyway: it
|
|
411
|
+
// does not care how the ParsedFile got to scope resolution.
|
|
412
|
+
const { allPaths, moduleConstants } = getPhaseOutput(deps, 'parse');
|
|
413
|
+
const refusalsByReason = {};
|
|
414
|
+
const countRefusal = (reason) => {
|
|
415
|
+
refusalsByReason[reason] = (refusalsByReason[reason] ?? 0) + 1;
|
|
416
|
+
};
|
|
417
|
+
// ── Gather: facts → candidates → resolutions ──────────────────────────
|
|
418
|
+
const sites = [];
|
|
419
|
+
for (const filePath of allPaths) {
|
|
420
|
+
const provider = getProviderForFile(filePath);
|
|
421
|
+
const facts = provider?.getSpringMessagingFacts?.(filePath);
|
|
422
|
+
if (facts === undefined)
|
|
423
|
+
continue;
|
|
424
|
+
if (facts.handlers.length === 0 && facts.producers.length === 0)
|
|
425
|
+
continue;
|
|
426
|
+
// Built lazily and reused for the whole file: the fold state behind it is
|
|
427
|
+
// per-call, but resolving the provider and checking the table is not free
|
|
428
|
+
// and every candidate in the file wants the same closure.
|
|
429
|
+
const constant = makeConstantResolver(filePath, moduleConstants);
|
|
430
|
+
// The owning language's capability, not its name. In an interpolating
|
|
431
|
+
// language `"orders-$env"` is a runtime template rather than an address
|
|
432
|
+
// and `"${app.topic}"` is a template rather than a Spring placeholder;
|
|
433
|
+
// the resolver needs to know which regime it is in, and this phase must
|
|
434
|
+
// not learn which language that is (AGENTS.md — shared ingestion code
|
|
435
|
+
// plugs language behaviour in through provider hooks).
|
|
436
|
+
const interpolatesStringLiterals = provider?.interpolatesStringLiterals === true;
|
|
437
|
+
const record = (selection, ownerScopeId, ownerRange) => {
|
|
438
|
+
for (const refusal of selection.refusals)
|
|
439
|
+
countRefusal(refusal.reason);
|
|
440
|
+
for (const candidate of selection.candidates) {
|
|
441
|
+
const resolution = resolveSpringDestination(candidate, {
|
|
442
|
+
constant,
|
|
443
|
+
interpolatesStringLiterals,
|
|
444
|
+
});
|
|
445
|
+
if (resolution.kind === 'unresolved')
|
|
446
|
+
countRefusal(resolution.reason);
|
|
447
|
+
sites.push({
|
|
448
|
+
filePath,
|
|
449
|
+
...(ownerRange === undefined ? {} : { ownerRange }),
|
|
450
|
+
ownerScopeId,
|
|
451
|
+
candidate,
|
|
452
|
+
resolution,
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
for (const handler of facts.handlers) {
|
|
457
|
+
for (const annotation of handler.annotations) {
|
|
458
|
+
// A Kotlin use-site target describes a generated property element, not
|
|
459
|
+
// the callable, so its arguments are not this handler's.
|
|
460
|
+
if (annotation.useSiteTarget !== undefined)
|
|
461
|
+
continue;
|
|
462
|
+
const selection = selectConsumerDestinationArguments(annotation.name, annotation.args);
|
|
463
|
+
if (selection === null)
|
|
464
|
+
continue;
|
|
465
|
+
record(selection, String(handler.ownerScopeId), handler.ownerRange);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
for (const producer of facts.producers) {
|
|
469
|
+
record(selectProducerDestinationArguments(producer), String(producer.ownerScopeId), producer.ownerRange);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
// Documents are read whether or not the source pass found anything, and
|
|
473
|
+
// that is the point of the closure rather than a straight call here: a
|
|
474
|
+
// repository whose messaging is invisible to the source patterns — a broker
|
|
475
|
+
// with no rules, a listener registered programmatically — is exactly the
|
|
476
|
+
// case a published document exists to cover, and an early return keyed on
|
|
477
|
+
// source sites would skip the documents precisely there.
|
|
478
|
+
//
|
|
479
|
+
// Always invoked AFTER the source emit, so a site that resolved an address
|
|
480
|
+
// owns its node first and keeps its own provenance.
|
|
481
|
+
const specPath = ctx.options?.asyncApiSpecPath;
|
|
482
|
+
const readSpecifications = async () => specPath === undefined ? undefined : emitSpecDestinations(ctx, specPath);
|
|
483
|
+
if (sites.length === 0) {
|
|
484
|
+
const specDocuments = await readSpecifications();
|
|
485
|
+
return {
|
|
486
|
+
resolvedDestinations: 0,
|
|
487
|
+
unresolvedDestinations: 0,
|
|
488
|
+
edges: 0,
|
|
489
|
+
refusalsByReason,
|
|
490
|
+
configKeyLinks: 0,
|
|
491
|
+
...(specDocuments === undefined ? {} : { specDocuments }),
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
// ── Emit ──────────────────────────────────────────────────────────────
|
|
495
|
+
//
|
|
496
|
+
// A single pass. There used to be a preliminary one that looked for an
|
|
497
|
+
// address claimed by two brokers so the emit could withdraw it from both —
|
|
498
|
+
// the disagreement had to be known before the first node was minted,
|
|
499
|
+
// because re-keying a node after it has grown an edge is the kind of work
|
|
500
|
+
// that is easy to get half-right. With the broker in the key there is
|
|
501
|
+
// nothing to decide up front: each site's identity is a function of that
|
|
502
|
+
// site alone, so no other site can change it and no lookahead is needed.
|
|
503
|
+
const owners = callableOwnersByRange(ctx.graph);
|
|
504
|
+
const configProperties = springConfigPropertiesByKey(ctx.graph);
|
|
505
|
+
const linkedConfigKeys = new Set();
|
|
506
|
+
let resolvedDestinations = 0;
|
|
507
|
+
let unresolvedDestinations = 0;
|
|
508
|
+
let edges = 0;
|
|
509
|
+
let configKeyLinks = 0;
|
|
510
|
+
for (const site of sites) {
|
|
511
|
+
const { candidate, resolution } = site;
|
|
512
|
+
// The one predicate the rest of the loop is written against: may this
|
|
513
|
+
// site's node be keyed by its address, and therefore meet another site on
|
|
514
|
+
// it? Exactly when the address resolved. Otherwise the node gets its
|
|
515
|
+
// location-based key, no `address` property, and its own file.
|
|
516
|
+
const connects = resolution.kind === 'resolved';
|
|
517
|
+
const nodeId = destinationNodeId(site);
|
|
518
|
+
const isNew = ctx.graph.getNode(nodeId) === undefined;
|
|
519
|
+
if (isNew) {
|
|
520
|
+
if (connects)
|
|
521
|
+
resolvedDestinations += 1;
|
|
522
|
+
else
|
|
523
|
+
unresolvedDestinations += 1;
|
|
524
|
+
ctx.graph.addNode({
|
|
525
|
+
id: nodeId,
|
|
526
|
+
label: 'Destination',
|
|
527
|
+
properties: {
|
|
528
|
+
// For a resolved address this equals `address`. For an unresolved
|
|
529
|
+
// one it is the UNRESOLVED SPELLING, unquoted, kept so a human
|
|
530
|
+
// reading the node sees what the source actually said. Either way
|
|
531
|
+
// it is kept out of `address` unless the node connects, so nothing
|
|
532
|
+
// joins on it.
|
|
533
|
+
//
|
|
534
|
+
// Note that `name` is the ADDRESS, not the node key: two nodes on
|
|
535
|
+
// one spelling over two brokers share a `name` and differ by id.
|
|
536
|
+
// That is deliberate — `name` is for a human, and telling them the
|
|
537
|
+
// topic is called `kafka orders` would be a lie.
|
|
538
|
+
name: resolution.kind === 'resolved'
|
|
539
|
+
? resolution.address
|
|
540
|
+
: destinationDisplayName(candidate.rawText),
|
|
541
|
+
// A CONNECTING destination carries NO location, and that is load
|
|
542
|
+
// bearing rather than cosmetic.
|
|
543
|
+
//
|
|
544
|
+
// It is shared by every site that names the address, so no single
|
|
545
|
+
// file identifies it — but more importantly, the incremental
|
|
546
|
+
// writeback deletes by location: `deleteNodesForFiles` issues
|
|
547
|
+
// `MATCH (n:<table>) WHERE n.filePath IN [...] DETACH DELETE n` for
|
|
548
|
+
// every changed file. Stamping the first-seen file here would make
|
|
549
|
+
// a shared destination collateral damage whenever THAT file
|
|
550
|
+
// changed, and DETACH DELETE would take its edges from every OTHER
|
|
551
|
+
// file with it. Those files are not in the write set, so their
|
|
552
|
+
// edges would never be rebuilt: a publisher and a subscriber that
|
|
553
|
+
// genuinely agree on an address would silently stop being
|
|
554
|
+
// connected, depending on which of them the indexer happened to
|
|
555
|
+
// walk first. Omitting the property makes the `IN` predicate unable
|
|
556
|
+
// to match, so the node survives the writeback and every referrer
|
|
557
|
+
// keeps its edge.
|
|
558
|
+
//
|
|
559
|
+
// The cost used to be the opposite error — a destination whose
|
|
560
|
+
// last referrer was deleted lingered as an edgeless orphan until a
|
|
561
|
+
// full rebuild — and that is no longer paid. Because the per-file
|
|
562
|
+
// predicate can neither remove such a node nor admit a newly
|
|
563
|
+
// introduced one, the whole layer is instead delete-alled
|
|
564
|
+
// (`deleteAllDestinations`) and re-included graph-wide
|
|
565
|
+
// (`isGraphWideNode`) on every incremental writeback. Both halves
|
|
566
|
+
// move together: the delete without the re-include drops the layer,
|
|
567
|
+
// and the re-include without the delete duplicates every edge.
|
|
568
|
+
//
|
|
569
|
+
// A NON-CONNECTING destination is the opposite case — it belongs to
|
|
570
|
+
// exactly one site, its id already says so, and it SHOULD be
|
|
571
|
+
// deleted and re-created with its file.
|
|
572
|
+
// `''`, not absent: `NodeProperties.filePath` is required, and the
|
|
573
|
+
// empty string is the established spelling for a node with no file
|
|
574
|
+
// (`pipeline-phases/communities.ts` does the same). It is equally
|
|
575
|
+
// unmatchable by the `IN` predicate and the CSV writes it as an
|
|
576
|
+
// empty field, which COPY loads as NULL.
|
|
577
|
+
...(connects
|
|
578
|
+
? { filePath: '' }
|
|
579
|
+
: {
|
|
580
|
+
filePath: site.filePath,
|
|
581
|
+
...(site.ownerRange === undefined
|
|
582
|
+
? {}
|
|
583
|
+
: {
|
|
584
|
+
startLine: site.ownerRange.startLine - 1,
|
|
585
|
+
endLine: site.ownerRange.endLine - 1,
|
|
586
|
+
}),
|
|
587
|
+
}),
|
|
588
|
+
// `address` IS THE JOIN KEY and is written ONLY when the node
|
|
589
|
+
// connects. See the module header: an absent property cannot match
|
|
590
|
+
// another absent property, so the structural guarantee survives
|
|
591
|
+
// being read back out of the database.
|
|
592
|
+
//
|
|
593
|
+
// `resolution` always says how the node got here — the provenance
|
|
594
|
+
// of a real address, or the named refusal that stopped one. Every
|
|
595
|
+
// value in the column now comes from the resolver's own closed
|
|
596
|
+
// vocabulary, because the phase no longer has a verdict of its own
|
|
597
|
+
// to record: nothing is withdrawn here.
|
|
598
|
+
...(resolution.kind === 'resolved'
|
|
599
|
+
? { address: resolution.address, resolution: resolution.via }
|
|
600
|
+
: { resolution: resolution.reason }),
|
|
601
|
+
...(resolution.kind === 'unresolved' && resolution.configKey !== undefined
|
|
602
|
+
? { configKey: resolution.configKey }
|
|
603
|
+
: {}),
|
|
604
|
+
// The `${key:default}` default text. Kept because the source wrote
|
|
605
|
+
// it and throwing it away would make an overridable default
|
|
606
|
+
// indistinguishable from a bare `${key}`; NOT an address and never
|
|
607
|
+
// part of the id, because configuration can override it and this
|
|
608
|
+
// graph cannot see whether it did.
|
|
609
|
+
...(resolution.kind === 'unresolved' && resolution.configDefault !== undefined
|
|
610
|
+
? { configDefault: resolution.configDefault }
|
|
611
|
+
: {}),
|
|
612
|
+
broker: candidate.broker,
|
|
613
|
+
},
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
// Link a placeholder's KEY to the configuration entries that could supply
|
|
617
|
+
// it — `${key}` and `${key:default}` alike, since a default changes
|
|
618
|
+
// nothing about where the real value comes from. This is PROVENANCE, not
|
|
619
|
+
// resolution: the node stays unresolved and keeps its location-based id
|
|
620
|
+
// even when Property nodes are found, because the VALUE is still not in
|
|
621
|
+
// the graph and letting a Property sighting upgrade the node would
|
|
622
|
+
// reintroduce the false connection the keying rule exists to prevent.
|
|
623
|
+
// `${}` names no key at all and reports none, so nothing is ever looked
|
|
624
|
+
// up under the empty string.
|
|
625
|
+
if (resolution.kind === 'unresolved' && resolution.configKey !== undefined) {
|
|
626
|
+
for (const propertyId of configProperties.get(resolution.configKey) ?? []) {
|
|
627
|
+
const linkId = `${nodeId}->${propertyId}`;
|
|
628
|
+
if (linkedConfigKeys.has(linkId))
|
|
629
|
+
continue;
|
|
630
|
+
linkedConfigKeys.add(linkId);
|
|
631
|
+
ctx.graph.addRelationship({
|
|
632
|
+
id: generateId('USES', linkId),
|
|
633
|
+
sourceId: nodeId,
|
|
634
|
+
targetId: propertyId,
|
|
635
|
+
type: 'USES',
|
|
636
|
+
confidence: 1.0,
|
|
637
|
+
reason: `spring-destination:config-key:${resolution.configKey}`,
|
|
638
|
+
});
|
|
639
|
+
configKeyLinks += 1;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
// One edge per address. An array-valued `topics` really does subscribe to
|
|
643
|
+
// several places, and each gets its own edge rather than a group node, so
|
|
644
|
+
// "who reads from `a`" stays one hop. `reason` carries which argument and
|
|
645
|
+
// which element it came from.
|
|
646
|
+
const type = candidate.role === 'consumer' ? 'CONSUMES_FROM' : 'PUBLISHES_TO';
|
|
647
|
+
const owner = site.ownerRange === undefined
|
|
648
|
+
? undefined
|
|
649
|
+
: (owners.get(ownerKey(site.filePath, site.ownerRange)) ?? undefined);
|
|
650
|
+
// Prefer the callable; fall back to its File when the owner is unknown or
|
|
651
|
+
// ambiguous. Unlike `routes.ts` this does NOT emit both — there is no
|
|
652
|
+
// legacy File-level consumer to keep working here, and a second edge per
|
|
653
|
+
// publish would double the async surface of the graph for no query.
|
|
654
|
+
const sourceId = owner?.id ?? generateId('File', site.filePath);
|
|
655
|
+
if (owner === undefined && ctx.graph.getNode(sourceId) === undefined)
|
|
656
|
+
continue;
|
|
657
|
+
const reason = edgeReason(candidate);
|
|
658
|
+
ctx.graph.addRelationship({
|
|
659
|
+
id: generateId(type, `${sourceId}->${nodeId}:${reason}`),
|
|
660
|
+
sourceId,
|
|
661
|
+
targetId: nodeId,
|
|
662
|
+
type,
|
|
663
|
+
confidence: 1.0,
|
|
664
|
+
reason,
|
|
665
|
+
});
|
|
666
|
+
edges += 1;
|
|
667
|
+
}
|
|
668
|
+
const specDocuments = await readSpecifications();
|
|
669
|
+
if (isDev) {
|
|
670
|
+
const fromSpec = specDocuments === undefined
|
|
671
|
+
? ''
|
|
672
|
+
: `, +${specDocuments.destinations} from ${specDocuments.accepted} document(s)`;
|
|
673
|
+
// The breakdown, not just the totals. The unresolved FRACTION is the
|
|
674
|
+
// number this feature is judged on, and a bare count of unresolved
|
|
675
|
+
// destinations says how big the gap is without saying what would close
|
|
676
|
+
// it — which is the only question an operator can act on.
|
|
677
|
+
logger.info({ refusalsByReason, ...(specDocuments === undefined ? {} : { specDocuments }) }, `📮 Spring destinations: ${resolvedDestinations} resolved, ${unresolvedDestinations} unresolved, ${edges} edges${fromSpec}`);
|
|
678
|
+
}
|
|
679
|
+
return {
|
|
680
|
+
resolvedDestinations,
|
|
681
|
+
unresolvedDestinations,
|
|
682
|
+
edges,
|
|
683
|
+
refusalsByReason,
|
|
684
|
+
configKeyLinks,
|
|
685
|
+
...(specDocuments === undefined ? {} : { specDocuments }),
|
|
686
|
+
};
|
|
687
|
+
},
|
|
688
|
+
};
|