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,840 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recognized listener annotations, keyed by SIMPLE name.
|
|
3
|
+
*
|
|
4
|
+
* Simple names, not fully-qualified ones, because a pipeline phase runs after
|
|
5
|
+
* scope resolution has finished and no longer has the import tables that
|
|
6
|
+
* `createSpringAnnotationNameResolver` needs. The capture layer already gates
|
|
7
|
+
* on simple names for the same reason (`CAPTURE_RELEVANT_SIMPLE_NAMES` in
|
|
8
|
+
* `non-http-handlers.ts`), so nothing reaches this map that was not already
|
|
9
|
+
* admitted on that basis; matching on the FQN here would only reject facts the
|
|
10
|
+
* capture had already accepted, never admit more.
|
|
11
|
+
*
|
|
12
|
+
* DELIBERATELY ABSENT: `@MessageMapping` and `@SubscribeMapping`. Both are
|
|
13
|
+
* recognized by `non-http-handlers.ts` as message handlers, and both are
|
|
14
|
+
* WebSocket/STOMP routes — an application-level destination inside a
|
|
15
|
+
* server-managed session, not an address on a broker. Modelling `/topic/prices`
|
|
16
|
+
* as a `Destination` would put a STOMP path in the same namespace as a Kafka
|
|
17
|
+
* topic and let the cross-service joiner match them.
|
|
18
|
+
*/
|
|
19
|
+
const CONSUMER_ANNOTATIONS = new Map([
|
|
20
|
+
[
|
|
21
|
+
'KafkaListener',
|
|
22
|
+
{
|
|
23
|
+
broker: 'kafka',
|
|
24
|
+
addressArgs: ['topics'],
|
|
25
|
+
positionalIsAddress: false,
|
|
26
|
+
patternArgs: ['topicPattern'],
|
|
27
|
+
unsupportedArgs: ['topicPartitions'],
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
[
|
|
31
|
+
'PulsarListener',
|
|
32
|
+
{
|
|
33
|
+
broker: 'pulsar',
|
|
34
|
+
addressArgs: ['topics'],
|
|
35
|
+
positionalIsAddress: false,
|
|
36
|
+
patternArgs: ['topicPattern'],
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
[
|
|
40
|
+
'RabbitListener',
|
|
41
|
+
{
|
|
42
|
+
broker: 'rabbit',
|
|
43
|
+
addressArgs: ['queues'],
|
|
44
|
+
positionalIsAddress: false,
|
|
45
|
+
unsupportedArgs: ['bindings', 'queuesToDeclare'],
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
[
|
|
49
|
+
'JmsListener',
|
|
50
|
+
{ broker: 'jms', addressArgs: ['destination'], positionalIsAddress: false },
|
|
51
|
+
],
|
|
52
|
+
[
|
|
53
|
+
'ServiceActivator',
|
|
54
|
+
{ broker: 'integration', addressArgs: ['inputChannel'], positionalIsAddress: false },
|
|
55
|
+
],
|
|
56
|
+
['SqsListener', { broker: 'sqs', addressArgs: ['value'], positionalIsAddress: true }],
|
|
57
|
+
[
|
|
58
|
+
'StreamListener',
|
|
59
|
+
{ broker: 'stream', addressArgs: ['value'], positionalIsAddress: true },
|
|
60
|
+
],
|
|
61
|
+
]);
|
|
62
|
+
/**
|
|
63
|
+
* Plural container annotations (`@KafkaListeners`, `@RabbitListeners`, …) wrap
|
|
64
|
+
* repeated listeners. Their single argument is a list of nested annotations,
|
|
65
|
+
* whose own arguments the capture does not descend into, so there is nothing
|
|
66
|
+
* here to read.
|
|
67
|
+
*
|
|
68
|
+
* They are recognized rather than ignored so the loss is COUNTED. A repository
|
|
69
|
+
* that declares its listeners this way really does lose those destinations, and
|
|
70
|
+
* returning an empty selection would make it indistinguishable from a
|
|
71
|
+
* repository with no listeners at all — the module header promises that every
|
|
72
|
+
* path which declines to produce an address records why, and an empty
|
|
73
|
+
* `refusals` array records nothing. The broker comes from the container's own
|
|
74
|
+
* name, which is the one thing the annotation does state.
|
|
75
|
+
*/
|
|
76
|
+
const CONSUMER_CONTAINER_ANNOTATIONS = new Map([
|
|
77
|
+
['KafkaListeners', 'kafka'],
|
|
78
|
+
['RabbitListeners', 'rabbit'],
|
|
79
|
+
['JmsListeners', 'jms'],
|
|
80
|
+
['PulsarListeners', 'pulsar'],
|
|
81
|
+
]);
|
|
82
|
+
function simpleName(name) {
|
|
83
|
+
const separator = name.lastIndexOf('.');
|
|
84
|
+
return separator === -1 ? name : name.slice(separator + 1);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Choose the destination-bearing arguments of one listener annotation.
|
|
88
|
+
*
|
|
89
|
+
* Returns `null` when the annotation is not a broker listener at all — that is
|
|
90
|
+
* not a refusal, there was nothing to refuse. A recognized annotation always
|
|
91
|
+
* returns a selection, even when every path in it declined, so the caller can
|
|
92
|
+
* count what was seen against what resolved.
|
|
93
|
+
*/
|
|
94
|
+
export function selectConsumerDestinationArguments(annotationName, args) {
|
|
95
|
+
const name = simpleName(annotationName);
|
|
96
|
+
const containerBroker = CONSUMER_CONTAINER_ANNOTATIONS.get(name);
|
|
97
|
+
if (containerBroker !== undefined) {
|
|
98
|
+
return {
|
|
99
|
+
candidates: [],
|
|
100
|
+
refusals: [
|
|
101
|
+
{
|
|
102
|
+
role: 'consumer',
|
|
103
|
+
source: name,
|
|
104
|
+
broker: containerBroker,
|
|
105
|
+
reason: 'repeated-listener-container',
|
|
106
|
+
...(args === undefined || args[0] === undefined ? {} : { rawText: args[0].text }),
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const rule = CONSUMER_ANNOTATIONS.get(name);
|
|
112
|
+
if (rule === undefined)
|
|
113
|
+
return null;
|
|
114
|
+
const refusals = [];
|
|
115
|
+
const refuse = (reason, extra = {}) => {
|
|
116
|
+
refusals.push({ role: 'consumer', source: name, broker: rule.broker, reason, ...extra });
|
|
117
|
+
};
|
|
118
|
+
// ABSENT arguments are a capture limitation: the annotation was recognized
|
|
119
|
+
// but its argument list was never read (see
|
|
120
|
+
// `SpringNonHttpHandlerAnnotationFact.args`). An empty ARRAY is a different
|
|
121
|
+
// fact entirely — an argument list WAS read and it was empty, so the source
|
|
122
|
+
// really does declare a listener that names no destination. Capture keeps the
|
|
123
|
+
// two apart on purpose, the producer side of this module already does, and
|
|
124
|
+
// merging them here would file a source-level gap under a tooling gap and
|
|
125
|
+
// corrupt the one breakdown this feature is measured on.
|
|
126
|
+
if (args === undefined) {
|
|
127
|
+
refuse('annotation-arguments-unavailable');
|
|
128
|
+
return { candidates: [], refusals };
|
|
129
|
+
}
|
|
130
|
+
if (args.length === 0) {
|
|
131
|
+
refuse('annotation-arguments-empty');
|
|
132
|
+
return { candidates: [], refusals };
|
|
133
|
+
}
|
|
134
|
+
const candidates = [];
|
|
135
|
+
let sawDestinationArgument = false;
|
|
136
|
+
for (const [argIndex, arg] of args.entries()) {
|
|
137
|
+
const argName = arg.name;
|
|
138
|
+
if (argName === undefined) {
|
|
139
|
+
// Positional. Only the annotations whose `value` element IS the
|
|
140
|
+
// destination accept it; on the others a positional argument is a
|
|
141
|
+
// different element entirely and gets no guess.
|
|
142
|
+
if (!rule.positionalIsAddress)
|
|
143
|
+
continue;
|
|
144
|
+
sawDestinationArgument = true;
|
|
145
|
+
pushElements(candidates, refusals, {
|
|
146
|
+
role: 'consumer',
|
|
147
|
+
source: name,
|
|
148
|
+
broker: rule.broker,
|
|
149
|
+
argIndex,
|
|
150
|
+
rawText: arg.text,
|
|
151
|
+
});
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (rule.patternArgs?.includes(argName)) {
|
|
155
|
+
sawDestinationArgument = true;
|
|
156
|
+
refuse('topic-pattern', { rawText: arg.text, argIndex, argName });
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (rule.unsupportedArgs?.includes(argName)) {
|
|
160
|
+
sawDestinationArgument = true;
|
|
161
|
+
refuse('unsupported-annotation-argument', { rawText: arg.text, argIndex, argName });
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (!rule.addressArgs.includes(argName))
|
|
165
|
+
continue;
|
|
166
|
+
sawDestinationArgument = true;
|
|
167
|
+
pushElements(candidates, refusals, {
|
|
168
|
+
role: 'consumer',
|
|
169
|
+
source: name,
|
|
170
|
+
broker: rule.broker,
|
|
171
|
+
argIndex,
|
|
172
|
+
argName,
|
|
173
|
+
rawText: arg.text,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
// A listener whose arguments were read and named `groupId` and
|
|
177
|
+
// `containerFactory` but no destination is a real, countable gap — most often
|
|
178
|
+
// a form this module has not learned. It must not be silent.
|
|
179
|
+
if (!sawDestinationArgument)
|
|
180
|
+
refuse('no-destination-argument');
|
|
181
|
+
return { candidates, refusals };
|
|
182
|
+
}
|
|
183
|
+
// ── Producer side: which call argument names the destination ───────────────
|
|
184
|
+
/**
|
|
185
|
+
* Parameter names that carry a destination, per template, for calls that pass
|
|
186
|
+
* their arguments BY NAME.
|
|
187
|
+
*
|
|
188
|
+
* Kotlin call sites may name arguments, and a named argument list is in source
|
|
189
|
+
* order, not parameter order — `send(data = payload, topic = "orders")` is
|
|
190
|
+
* legal and puts the payload in slot 0. Reading slot 0 there publishes the
|
|
191
|
+
* PAYLOAD as an address. The name is captured
|
|
192
|
+
* ({@link SpringArgumentFact.name}), so the honest rule is to use it: select by
|
|
193
|
+
* name when there is one, and refuse when the names present say nothing this
|
|
194
|
+
* module recognizes. Selecting by position while ignoring a name that
|
|
195
|
+
* contradicts it is the one option that is never defensible.
|
|
196
|
+
*
|
|
197
|
+
* `exchange` is listed for rabbit but is NOT an address — it is the companion
|
|
198
|
+
* provenance the routing key carries (see the arity notes below).
|
|
199
|
+
*/
|
|
200
|
+
const PRODUCER_DESTINATION_PARAMETERS = {
|
|
201
|
+
kafka: ['topic'],
|
|
202
|
+
// `RabbitTemplate.convertAndSend(String exchange, String routingKey, Object message, …)`.
|
|
203
|
+
rabbit: ['routingKey'],
|
|
204
|
+
// `JmsTemplate.convertAndSend(Destination destination, …)` and the
|
|
205
|
+
// `String destinationName` overloads.
|
|
206
|
+
jms: ['destination', 'destinationName'],
|
|
207
|
+
// `StreamBridge.send(String bindingName, Object data, …)`.
|
|
208
|
+
'stream-bridge': ['bindingName'],
|
|
209
|
+
};
|
|
210
|
+
/** Rabbit's exchange parameter, carried as provenance rather than as an address. */
|
|
211
|
+
const RABBIT_EXCHANGE_PARAMETER = 'exchange';
|
|
212
|
+
/**
|
|
213
|
+
* Choose the destination-bearing arguments of one messaging-template publish.
|
|
214
|
+
*
|
|
215
|
+
* A NAME beats a position, arity decides where it can decide, and shape decides
|
|
216
|
+
* where it cannot.
|
|
217
|
+
*
|
|
218
|
+
* When any argument is passed by name, {@link PRODUCER_DESTINATION_PARAMETERS}
|
|
219
|
+
* decides — position is not consulted at all, because a named argument list
|
|
220
|
+
* need not be in parameter order. When the slot this module would have read
|
|
221
|
+
* positionally is itself named with something it does not recognize, that is a
|
|
222
|
+
* contradiction and the publish is refused rather than read.
|
|
223
|
+
*
|
|
224
|
+
* `KafkaTemplate.send` and `StreamBridge.send` put the destination first in
|
|
225
|
+
* every multi-argument positional overload they have, so once such a call has
|
|
226
|
+
* two or more arguments its slot 0 is the destination and nothing further needs
|
|
227
|
+
* deciding. Those slots use the PERMISSIVE gate ({@link isAddressShaped}): a
|
|
228
|
+
* bare identifier is let through to the cascade, which refuses it by name if no
|
|
229
|
+
* constant folds. That keeps `unresolved-constant` — a thing we tried to
|
|
230
|
+
* resolve — distinct from `producer-argument-not-address-shaped`, a thing we
|
|
231
|
+
* declined to read at all.
|
|
232
|
+
*
|
|
233
|
+
* The `convertAndSend` families are different. Both admit trailing
|
|
234
|
+
* `MessagePostProcessor` and `CorrelationData` parameters, and arity does not
|
|
235
|
+
* separate the overloads in EITHER direction:
|
|
236
|
+
*
|
|
237
|
+
* jms (destination, message) 2 vs (message, postProcessor) 2
|
|
238
|
+
* rabbit (routingKey, message) 2 vs (message, postProcessor) 2
|
|
239
|
+
* rabbit (exchange, routingKey, message) 3 vs (routingKey, message, pp) 3
|
|
240
|
+
* vs (routingKey, message, correlation) 3
|
|
241
|
+
* rabbit (exchange, routingKey, message, pp) 4 vs (routingKey, message, pp, corr) 4
|
|
242
|
+
*
|
|
243
|
+
* So the tie is broken by the STRICT gate ({@link isConfidentAddressShape}) — a
|
|
244
|
+
* string literal, a qualified reference, or a screaming-snake constant, all of
|
|
245
|
+
* which a payload variable is not. A lowercase bare identifier is NOT confident
|
|
246
|
+
* evidence, so `convertAndSend(topic, payload)` is refused rather than read:
|
|
247
|
+
* the same spelling is how a payload variable looks, and nothing in the syntax
|
|
248
|
+
* separates them. That refusal is the deliberate cost. A refusal is counted and
|
|
249
|
+
* recoverable; a wrong address enters reports as a fact.
|
|
250
|
+
*
|
|
251
|
+
* There is NO positional fallback at rabbit arity 3+. An earlier revision fell
|
|
252
|
+
* back to accepting slot 0 when slot 1 was not confident, which turned the
|
|
253
|
+
* ordinary `convertAndSend(EXCHANGE, routingKey, event)` — routing key in a
|
|
254
|
+
* variable — into a destination whose address was the EXCHANGE NAME. That is
|
|
255
|
+
* the worst possible outcome: an address that looks entirely plausible and can
|
|
256
|
+
* join a `@RabbitListener(queues = "orders")` that has nothing to do with it.
|
|
257
|
+
*
|
|
258
|
+
* ── THE ONE AMBIGUITY, REFUSED RATHER THAN GUESSED ───────────────────────
|
|
259
|
+
*
|
|
260
|
+
* `convertAndSend("orders.rk", "body", correlationData)` fits two overloads at
|
|
261
|
+
* once and they disagree about which slot is the address:
|
|
262
|
+
*
|
|
263
|
+
* (exchange, routingKey, message) → the address is `"body"`
|
|
264
|
+
* (routingKey, message, correlationData) → the address is `"orders.rk"`
|
|
265
|
+
*
|
|
266
|
+
* Both are real, both are spelled (String, String, ref), and no rule over the
|
|
267
|
+
* syntax separates them. Picking either one publishes the OTHER reading's
|
|
268
|
+
* payload as an address, where a consumer of a queue named after that text
|
|
269
|
+
* joins a publisher that never wrote to it. So neither is picked: the call is
|
|
270
|
+
* refused as `ambiguous-producer-overload` and yields no candidate and no edge.
|
|
271
|
+
*
|
|
272
|
+
* The refusal is narrow on purpose, because over-refusing here costs the
|
|
273
|
+
* ordinary case, and a suppression that eats correct results is the more
|
|
274
|
+
* expensive mistake. It fires ONLY on a STRING LITERAL in slot 1, at the
|
|
275
|
+
* arities where a competing overload exists:
|
|
276
|
+
*
|
|
277
|
+
* - A literal is no evidence at all. An address and a payload are BOTH
|
|
278
|
+
* ordinarily written as literals, so the spelling distinguishes nothing.
|
|
279
|
+
* - A CONSTANT or QUALIFIED reference is evidence, which is the whole premise
|
|
280
|
+
* of {@link isConfidentAddressShape}: `ORDERS_ROUTING_KEY` and
|
|
281
|
+
* `Topics.ORDERS_KEY` are how a configured NAME is written, not how a
|
|
282
|
+
* payload computed at the call site is. Those keep resolving.
|
|
283
|
+
* - Arity 5 has no competing overload at all —
|
|
284
|
+
* `(exchange, routingKey, message, pp, correlationData)` is the only
|
|
285
|
+
* five-argument form — so slot 1 there is the routing key whatever it is
|
|
286
|
+
* spelled like, and the refusal must not reach it.
|
|
287
|
+
* - A NAMED argument settles the reading outright, and the name-beats-position
|
|
288
|
+
* pre-pass above has already returned by then.
|
|
289
|
+
*/
|
|
290
|
+
export function selectProducerDestinationArguments(fact) {
|
|
291
|
+
const broker = fact.template === 'stream-bridge' ? 'stream' : fact.template;
|
|
292
|
+
const source = fact.template;
|
|
293
|
+
const refusals = [];
|
|
294
|
+
const refuse = (reason, extra = {}) => {
|
|
295
|
+
refusals.push({ role: 'producer', source, broker, reason, ...extra });
|
|
296
|
+
};
|
|
297
|
+
const args = fact.args;
|
|
298
|
+
if (args === undefined) {
|
|
299
|
+
refuse('producer-arguments-unavailable');
|
|
300
|
+
return { candidates: [], refusals };
|
|
301
|
+
}
|
|
302
|
+
if (args.length === 0) {
|
|
303
|
+
refuse('producer-arity-unrecognized');
|
|
304
|
+
return { candidates: [], refusals };
|
|
305
|
+
}
|
|
306
|
+
const candidates = [];
|
|
307
|
+
const accept = (argIndex, exchange) => {
|
|
308
|
+
const arg = args[argIndex];
|
|
309
|
+
pushElements(candidates, refusals, {
|
|
310
|
+
role: 'producer',
|
|
311
|
+
source,
|
|
312
|
+
broker,
|
|
313
|
+
argIndex,
|
|
314
|
+
...(arg.name === undefined ? {} : { argName: arg.name }),
|
|
315
|
+
rawText: arg.text,
|
|
316
|
+
...(exchange === undefined ? {} : { exchange }),
|
|
317
|
+
});
|
|
318
|
+
};
|
|
319
|
+
/**
|
|
320
|
+
* Accept a slot chosen by POSITION.
|
|
321
|
+
*
|
|
322
|
+
* Refuses when the argument in that slot carries a name — a named list need
|
|
323
|
+
* not be in parameter order, so a name in the destination slot that is not a
|
|
324
|
+
* destination parameter contradicts the position, and reading the position
|
|
325
|
+
* anyway is how `send(data = "payload", topic = "orders")` published the
|
|
326
|
+
* payload. The name-matching pre-pass above has already had its chance.
|
|
327
|
+
*/
|
|
328
|
+
const acceptPositional = (argIndex, exchange) => {
|
|
329
|
+
const arg = args[argIndex];
|
|
330
|
+
if (arg.name !== undefined) {
|
|
331
|
+
refuse('producer-named-argument-unrecognized', {
|
|
332
|
+
rawText: arg.text,
|
|
333
|
+
argIndex,
|
|
334
|
+
argName: arg.name,
|
|
335
|
+
});
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
accept(argIndex, exchange);
|
|
339
|
+
};
|
|
340
|
+
const textAt = (index) => args[index].text;
|
|
341
|
+
const confident = (index) => index < args.length && isConfidentAddressShape(textAt(index));
|
|
342
|
+
const refuseShape = (index) => {
|
|
343
|
+
refuse('producer-argument-not-address-shaped', { rawText: textAt(index), argIndex: index });
|
|
344
|
+
};
|
|
345
|
+
// ── A name beats a position ─────────────────────────────────────────────
|
|
346
|
+
// When an argument names a destination parameter, that argument IS the
|
|
347
|
+
// destination wherever it sits in the list. Only when no name matches does
|
|
348
|
+
// the positional reasoning below run, and `acceptPositional` then refuses if
|
|
349
|
+
// the slot it lands on turns out to be named after something else.
|
|
350
|
+
const destinationNames = PRODUCER_DESTINATION_PARAMETERS[fact.template];
|
|
351
|
+
const namedIndex = args.findIndex((arg) => arg.name !== undefined && destinationNames.includes(arg.name));
|
|
352
|
+
if (namedIndex !== -1) {
|
|
353
|
+
const exchangeIndex = fact.template === 'rabbit'
|
|
354
|
+
? args.findIndex((arg) => arg.name === RABBIT_EXCHANGE_PARAMETER)
|
|
355
|
+
: -1;
|
|
356
|
+
accept(namedIndex, exchangeIndex === -1 ? undefined : unquoteForProvenance(textAt(exchangeIndex)));
|
|
357
|
+
return { candidates, refusals };
|
|
358
|
+
}
|
|
359
|
+
if (fact.template === 'rabbit') {
|
|
360
|
+
// `convertAndSend` overloads, by what occupies the leading slots:
|
|
361
|
+
// (message) → default exchange, no address
|
|
362
|
+
// (routingKey, message) → arg0 is the routing key
|
|
363
|
+
// (message, postProcessor) → NO address, same arity
|
|
364
|
+
// (exchange, routingKey, message) → arg0 + arg1
|
|
365
|
+
// (routingKey, message, postProcessor) → arg0 only, same arity
|
|
366
|
+
// (routingKey, message, correlationData) → arg0 only, same arity
|
|
367
|
+
// (exchange, routingKey, message, pp) → arg0 + arg1
|
|
368
|
+
// (routingKey, message, pp, correlationData) → arg0 only, same arity
|
|
369
|
+
// (exchange, routingKey, message, pp, corr) → arg0 + arg1
|
|
370
|
+
if (args.length === 1) {
|
|
371
|
+
refuse('rabbit-default-exchange', { rawText: textAt(0), argIndex: 0 });
|
|
372
|
+
return { candidates, refusals };
|
|
373
|
+
}
|
|
374
|
+
if (args.length === 2) {
|
|
375
|
+
// (routingKey, message) versus (message, postProcessor).
|
|
376
|
+
if (confident(0)) {
|
|
377
|
+
acceptPositional(0);
|
|
378
|
+
return { candidates, refusals };
|
|
379
|
+
}
|
|
380
|
+
refuseShape(0);
|
|
381
|
+
return { candidates, refusals };
|
|
382
|
+
}
|
|
383
|
+
// Three arguments and up. Arity separates almost nothing here — three and
|
|
384
|
+
// four both admit an exchange form and a routing-key form — so the ONLY
|
|
385
|
+
// acceptance is confident evidence in slot 1, and there is no positional
|
|
386
|
+
// fallback. `convertAndSend(EXCHANGE, routingKey, event)` fails that test
|
|
387
|
+
// and is refused; the discarded fallback published `EXCHANGE` as the
|
|
388
|
+
// address, which is a wrong answer wearing the costume of a right one.
|
|
389
|
+
//
|
|
390
|
+
// And confident evidence in slot 1 is not enough when the evidence is a
|
|
391
|
+
// STRING LITERAL: under the competing overload that same literal is the
|
|
392
|
+
// String PAYLOAD, and the two readings are spelled identically. See the
|
|
393
|
+
// ambiguity section in this function's doc comment for why this is a
|
|
394
|
+
// refusal rather than a choice, and for each of the three cases it must not
|
|
395
|
+
// touch — a constant in slot 1 (spelling that IS evidence), arity 5 (no
|
|
396
|
+
// competing overload exists), and a named argument (already returned
|
|
397
|
+
// above, and its name settles the reading).
|
|
398
|
+
const competingOverload = args.length === 3 || args.length === 4;
|
|
399
|
+
if (competingOverload &&
|
|
400
|
+
args[1]?.name === undefined &&
|
|
401
|
+
parseSpringStringLiteral(textAt(1)) !== null) {
|
|
402
|
+
refuse('ambiguous-producer-overload', { rawText: textAt(1), argIndex: 1 });
|
|
403
|
+
return { candidates, refusals };
|
|
404
|
+
}
|
|
405
|
+
if (confident(1)) {
|
|
406
|
+
// The ADDRESS is the routing key. The exchange rides along as provenance
|
|
407
|
+
// on the edge rather than becoming part of the address: composing
|
|
408
|
+
// `exchange/routingKey` would invent a spelling no consumer ever writes,
|
|
409
|
+
// and a `@RabbitListener` names a QUEUE, so the two sides do not join on
|
|
410
|
+
// the exchange anyway. Which queue an exchange/key pair reaches is decided
|
|
411
|
+
// by bindings this index does not read.
|
|
412
|
+
acceptPositional(1, unquoteForProvenance(textAt(0)));
|
|
413
|
+
return { candidates, refusals };
|
|
414
|
+
}
|
|
415
|
+
refuseShape(1);
|
|
416
|
+
return { candidates, refusals };
|
|
417
|
+
}
|
|
418
|
+
// kafka `send(topic, …)`, jms `convertAndSend(destination, message, …)` and
|
|
419
|
+
// stream-bridge `send(binding, …)` all put the destination first and all
|
|
420
|
+
// require at least one further argument for the payload. A single-argument
|
|
421
|
+
// call is therefore one of the payload-only overloads —
|
|
422
|
+
// `send(ProducerRecord)`, `send(Message<?>)`, `convertAndSend(Object)` — which
|
|
423
|
+
// carries its destination inside an object this module does not open.
|
|
424
|
+
if (args.length < 2) {
|
|
425
|
+
refuse('producer-arity-unrecognized', { rawText: textAt(0), argIndex: 0 });
|
|
426
|
+
return { candidates, refusals };
|
|
427
|
+
}
|
|
428
|
+
// Two-argument `convertAndSend` is the one JMS arity that collides with the
|
|
429
|
+
// post-processor overload, so only there does slot 0 need confident evidence.
|
|
430
|
+
const strict = fact.template === 'jms' && args.length === 2;
|
|
431
|
+
if (strict ? !confident(0) : !isAddressShaped(textAt(0))) {
|
|
432
|
+
refuseShape(0);
|
|
433
|
+
return { candidates, refusals };
|
|
434
|
+
}
|
|
435
|
+
acceptPositional(0);
|
|
436
|
+
return { candidates, refusals };
|
|
437
|
+
}
|
|
438
|
+
// ── Array / literal / placeholder text handling ────────────────────────────
|
|
439
|
+
/**
|
|
440
|
+
* Split an array-valued destination argument into its elements.
|
|
441
|
+
*
|
|
442
|
+
* Both languages hand this module ONE unsplit string per argument: capture
|
|
443
|
+
* records an argument's source text, and `topics = {"a", "b"}` is a single
|
|
444
|
+
* argument whose text happens to be a list. So the list is parsed here, in the
|
|
445
|
+
* three spellings the two languages use — Java `{…}`, Kotlin `[…]`, and Kotlin
|
|
446
|
+
* `arrayOf(…)`.
|
|
447
|
+
*
|
|
448
|
+
* Anything else comes back as a single element, unchanged: a scalar argument,
|
|
449
|
+
* and equally an expression that merely starts with a brace. The split tracks
|
|
450
|
+
* nesting and string literals, so a comma inside a literal or inside a nested
|
|
451
|
+
* call does not split the list.
|
|
452
|
+
*
|
|
453
|
+
* Returns `[]` for an empty list, which the caller must distinguish from a
|
|
454
|
+
* one-element list — `topics = {}` names no destination at all.
|
|
455
|
+
*/
|
|
456
|
+
export function splitSpringDestinationList(text) {
|
|
457
|
+
const trimmed = text.trim();
|
|
458
|
+
let inner = null;
|
|
459
|
+
if (trimmed.startsWith('{') && trimmed.endsWith('}'))
|
|
460
|
+
inner = trimmed.slice(1, -1);
|
|
461
|
+
else if (trimmed.startsWith('[') && trimmed.endsWith(']'))
|
|
462
|
+
inner = trimmed.slice(1, -1);
|
|
463
|
+
else if (/^arrayOf\s*\(/.test(trimmed) && trimmed.endsWith(')')) {
|
|
464
|
+
inner = trimmed.slice(trimmed.indexOf('(') + 1, -1);
|
|
465
|
+
}
|
|
466
|
+
if (inner === null)
|
|
467
|
+
return [trimmed];
|
|
468
|
+
if (inner.trim() === '')
|
|
469
|
+
return [];
|
|
470
|
+
const elements = [];
|
|
471
|
+
let current = '';
|
|
472
|
+
let depth = 0;
|
|
473
|
+
let quote = null;
|
|
474
|
+
for (let index = 0; index < inner.length; index += 1) {
|
|
475
|
+
const char = inner[index];
|
|
476
|
+
if (quote === '"""') {
|
|
477
|
+
current += char;
|
|
478
|
+
if (inner.startsWith('"""', index)) {
|
|
479
|
+
current += '""';
|
|
480
|
+
index += 2;
|
|
481
|
+
quote = null;
|
|
482
|
+
}
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
if (quote !== null) {
|
|
486
|
+
if (char === '\\' && index + 1 < inner.length) {
|
|
487
|
+
current += inner.slice(index, index + 2);
|
|
488
|
+
index += 1;
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
current += char;
|
|
492
|
+
if (char === quote)
|
|
493
|
+
quote = null;
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
if (inner.startsWith('"""', index)) {
|
|
497
|
+
current += '"""';
|
|
498
|
+
index += 2;
|
|
499
|
+
quote = '"""';
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
if (char === '"' || char === "'") {
|
|
503
|
+
quote = char;
|
|
504
|
+
current += char;
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
if (char === '(' || char === '[' || char === '{')
|
|
508
|
+
depth += 1;
|
|
509
|
+
else if (char === ')' || char === ']' || char === '}')
|
|
510
|
+
depth -= 1;
|
|
511
|
+
if (char === ',' && depth === 0) {
|
|
512
|
+
elements.push(current.trim());
|
|
513
|
+
current = '';
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
current += char;
|
|
517
|
+
}
|
|
518
|
+
elements.push(current.trim());
|
|
519
|
+
return elements.filter((element) => element !== '');
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* ONE string literal, whole.
|
|
523
|
+
*
|
|
524
|
+
* The triple-quoted alternative excludes `"""` from its body rather than
|
|
525
|
+
* matching greedily: `"""a""" + """b"""` is a concatenation, not a literal, and
|
|
526
|
+
* a greedy body swallowed the operator and folded it to the single address
|
|
527
|
+
* `a""" + """b`. Excluding the terminator makes the whole-string anchor fail
|
|
528
|
+
* there, so the text falls through to the constant test and is refused as
|
|
529
|
+
* `not-a-literal-or-constant`, which is what it is.
|
|
530
|
+
*/
|
|
531
|
+
const STRING_LITERAL = /^(?:"""((?:(?!""")[\s\S])*)"""|"((?:[^"\\]|\\[\s\S])*)"|'((?:[^'\\]|\\[\s\S])*)')$/;
|
|
532
|
+
/**
|
|
533
|
+
* Unquote a string literal to its value, or `null` when the text is not a
|
|
534
|
+
* single literal.
|
|
535
|
+
*
|
|
536
|
+
* Escapes are undone only for the sequences that can appear inside a
|
|
537
|
+
* destination: `\"`, `\\`, and Kotlin's `\$`. That last one matters more than it
|
|
538
|
+
* looks — a Spring placeholder written in Kotlin MUST escape the dollar
|
|
539
|
+
* (`"\${app.topic}"`) or the compiler reads it as a string template, so without
|
|
540
|
+
* undoing it every Kotlin placeholder would fail the `${` test below and be
|
|
541
|
+
* misfiled as a plain literal address named `\${app.topic}`.
|
|
542
|
+
*
|
|
543
|
+
* The unescaping is also why {@link hasUnescapedStringInterpolation} has to run
|
|
544
|
+
* against the RAW text: once `\$` has become `$`, the escaped placeholder and
|
|
545
|
+
* the runtime template are the same string.
|
|
546
|
+
*/
|
|
547
|
+
export function parseSpringStringLiteral(text) {
|
|
548
|
+
const match = STRING_LITERAL.exec(text.trim());
|
|
549
|
+
if (match === null)
|
|
550
|
+
return null;
|
|
551
|
+
const raw = match[1] ?? match[2] ?? match[3] ?? '';
|
|
552
|
+
return raw.replace(/\\(["'\\$nrt])/g, (_all, escaped) => {
|
|
553
|
+
if (escaped === 'n')
|
|
554
|
+
return '\n';
|
|
555
|
+
if (escaped === 'r')
|
|
556
|
+
return '\r';
|
|
557
|
+
if (escaped === 't')
|
|
558
|
+
return '\t';
|
|
559
|
+
return escaped;
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
/** `$` followed by a brace or an identifier start — the two template forms. */
|
|
563
|
+
const INTERPOLATION_START = /^[{A-Za-z_]/;
|
|
564
|
+
/**
|
|
565
|
+
* Whether a string literal contains an UNESCAPED interpolation, for a language
|
|
566
|
+
* whose literals interpolate.
|
|
567
|
+
*
|
|
568
|
+
* Only meaningful for such a language; Java never calls it. In Kotlin:
|
|
569
|
+
*
|
|
570
|
+
* "orders-$env" template — the value is decided at runtime
|
|
571
|
+
* "orders-${env}" template — NOT a Spring placeholder
|
|
572
|
+
* "\${app.topic}" escaped — this is how a Spring placeholder is written
|
|
573
|
+
* """orders-$env""" template — raw strings interpolate and cannot escape
|
|
574
|
+
*
|
|
575
|
+
* Reads the RAW literal text on purpose: {@link parseSpringStringLiteral}
|
|
576
|
+
* resolves `\$` to `$`, after which the second and third rows above are the
|
|
577
|
+
* same string and the distinction is gone. A raw (`"""`) literal has no
|
|
578
|
+
* backslash escapes at all — `${'$'}` is the only way to write a dollar there —
|
|
579
|
+
* so every `$` in one is an interpolation.
|
|
580
|
+
*/
|
|
581
|
+
export function hasUnescapedStringInterpolation(text) {
|
|
582
|
+
const trimmed = text.trim();
|
|
583
|
+
const match = STRING_LITERAL.exec(trimmed);
|
|
584
|
+
if (match === null)
|
|
585
|
+
return false;
|
|
586
|
+
const raw = match[1] ?? match[2] ?? match[3] ?? '';
|
|
587
|
+
const escapable = match[1] === undefined;
|
|
588
|
+
for (let index = 0; index < raw.length; index += 1) {
|
|
589
|
+
const char = raw[index];
|
|
590
|
+
if (escapable && char === '\\') {
|
|
591
|
+
index += 1;
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
if (char === '$' && INTERPOLATION_START.test(raw.slice(index + 1)))
|
|
595
|
+
return true;
|
|
596
|
+
}
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
599
|
+
/** `#{...}` — a SpEL expression the container evaluates at runtime. */
|
|
600
|
+
function containsSpelExpression(value) {
|
|
601
|
+
return value.includes('#{');
|
|
602
|
+
}
|
|
603
|
+
/** A dotted or bare identifier — the only non-literal shape read as a constant. */
|
|
604
|
+
const CONSTANT_REFERENCE = /^[A-Za-z_$][A-Za-z0-9_$]*(?:\s*\.\s*[A-Za-z_$][A-Za-z0-9_$]*)*$/;
|
|
605
|
+
/**
|
|
606
|
+
* PERMISSIVE gate — true when the text could name an address: a string literal,
|
|
607
|
+
* or any reference a constant resolver could plausibly fold. Says nothing about
|
|
608
|
+
* whether that reference actually resolves; the cascade decides that and
|
|
609
|
+
* records `unresolved-constant` when it does not.
|
|
610
|
+
*
|
|
611
|
+
* Used where the overload set already fixes which slot holds the destination.
|
|
612
|
+
*/
|
|
613
|
+
export function isAddressShaped(text) {
|
|
614
|
+
const trimmed = text.trim();
|
|
615
|
+
if (parseSpringStringLiteral(trimmed) !== null)
|
|
616
|
+
return true;
|
|
617
|
+
return CONSTANT_REFERENCE.test(trimmed);
|
|
618
|
+
}
|
|
619
|
+
/** A reference whose spelling is evidence in itself: qualified (`Topics.ORDERS`)
|
|
620
|
+
* or a screaming-snake constant (`ORDERS_TOPIC`). `this.x` is excluded — the
|
|
621
|
+
* qualifier says nothing about the member. */
|
|
622
|
+
const CONFIDENT_REFERENCE = /^(?!this\s*\.)[A-Za-z_$][A-Za-z0-9_$]*\s*\.\s*[A-Za-z0-9_$.\s]+$/;
|
|
623
|
+
const SCREAMING_SNAKE = /^[A-Z][A-Z0-9_$]*$/;
|
|
624
|
+
/**
|
|
625
|
+
* STRICT gate — true only when the spelling is confident evidence of an
|
|
626
|
+
* address, not merely compatible with one.
|
|
627
|
+
*
|
|
628
|
+
* The difference from {@link isAddressShaped} is the lowercase bare identifier.
|
|
629
|
+
* `convertAndSend(topic, payload)` and `convertAndSend(message, processor)` are
|
|
630
|
+
* the same syntax; only a human reading the names can tell which slot is the
|
|
631
|
+
* destination, and a name is not something this module is willing to rank as
|
|
632
|
+
* evidence. So a bare `topic` fails here and the publish is refused, while
|
|
633
|
+
* `"orders"`, `Topics.ORDERS` and `ORDERS_TOPIC` pass.
|
|
634
|
+
*
|
|
635
|
+
* Used ONLY at the arities where a trailing `MessagePostProcessor` overload
|
|
636
|
+
* collides with the destination-carrying one. Everywhere else the permissive
|
|
637
|
+
* gate applies, so this stricter rule costs nothing outside the ambiguity.
|
|
638
|
+
*/
|
|
639
|
+
export function isConfidentAddressShape(text) {
|
|
640
|
+
const trimmed = text.trim();
|
|
641
|
+
if (parseSpringStringLiteral(trimmed) !== null)
|
|
642
|
+
return true;
|
|
643
|
+
if (!CONSTANT_REFERENCE.test(trimmed))
|
|
644
|
+
return false;
|
|
645
|
+
return CONFIDENT_REFERENCE.test(trimmed) || SCREAMING_SNAKE.test(trimmed);
|
|
646
|
+
}
|
|
647
|
+
/** Best-effort display form for provenance text; never used as an identity. */
|
|
648
|
+
function unquoteForProvenance(text) {
|
|
649
|
+
return parseSpringStringLiteral(text) ?? text.trim();
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Read the FIRST Spring property placeholder out of an already-unquoted value.
|
|
653
|
+
*
|
|
654
|
+
* NOTHING IS SUBSTITUTED, and that is the rule, not an omission.
|
|
655
|
+
*
|
|
656
|
+
* `${key}` cannot resolve: the value lives in a configuration file this index
|
|
657
|
+
* deliberately does not read into the graph (values may hold credentials — see
|
|
658
|
+
* `pipeline-phases/spring-config.ts`). The KEY comes back instead, so the
|
|
659
|
+
* caller can link the node to the `Property` nodes for that key without ever
|
|
660
|
+
* learning its value.
|
|
661
|
+
*
|
|
662
|
+
* `${key:default}` cannot resolve EITHER, which is a correction to this
|
|
663
|
+
* module's original rule. The default is written in the source, so reading it
|
|
664
|
+
* is legitimate and it is returned — but it is provenance, never an identity.
|
|
665
|
+
* A default holds only while the key is not overridden, and whether it is
|
|
666
|
+
* overridden is a fact about configuration VALUES, which are absent from this
|
|
667
|
+
* graph by design. Substituting it made `${a.topic:events}` and
|
|
668
|
+
* `${b.topic:events}` one node and reported a producer/consumer pair between
|
|
669
|
+
* two services that shared nothing but a copy-pasted fallback. The same
|
|
670
|
+
* reasoning applies to any placeholder-derived value: a value the configuration
|
|
671
|
+
* can override is not an identity.
|
|
672
|
+
*
|
|
673
|
+
* Only the first placeholder is read because there is nothing to do with the
|
|
674
|
+
* rest — the value is already unresolvable, and the first key is the one a
|
|
675
|
+
* reader would look up. A nested default (`${a:${b}}`) needs no special case
|
|
676
|
+
* under this rule: `a` is the key and `${b}` is the default text, both reported
|
|
677
|
+
* as written.
|
|
678
|
+
*/
|
|
679
|
+
export function resolveSpringPlaceholders(value) {
|
|
680
|
+
const start = value.indexOf('${');
|
|
681
|
+
if (start === -1)
|
|
682
|
+
return { plain: true };
|
|
683
|
+
let depth = 1;
|
|
684
|
+
let cursor = start + 2;
|
|
685
|
+
while (cursor < value.length && depth > 0) {
|
|
686
|
+
if (value.startsWith('${', cursor)) {
|
|
687
|
+
depth += 1;
|
|
688
|
+
cursor += 2;
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
if (value[cursor] === '}')
|
|
692
|
+
depth -= 1;
|
|
693
|
+
cursor += 1;
|
|
694
|
+
}
|
|
695
|
+
// An unterminated `${` is not a placeholder this module can read. Treating
|
|
696
|
+
// the tail as a literal would mint an address containing `${`; treating it as
|
|
697
|
+
// a key at least names the thing the author was reaching for.
|
|
698
|
+
if (depth > 0)
|
|
699
|
+
return { plain: false, key: value.slice(start + 2).trim() };
|
|
700
|
+
const body = value.slice(start + 2, cursor - 1);
|
|
701
|
+
const separator = body.indexOf(':');
|
|
702
|
+
// Spring splits on the FIRST colon, so `${a:b:c}` defaults to `b:c`.
|
|
703
|
+
if (separator === -1)
|
|
704
|
+
return { plain: false, key: body.trim() };
|
|
705
|
+
return {
|
|
706
|
+
plain: false,
|
|
707
|
+
key: body.slice(0, separator).trim(),
|
|
708
|
+
defaultValue: body.slice(separator + 1),
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
// ── The cascade ────────────────────────────────────────────────────────────
|
|
712
|
+
/**
|
|
713
|
+
* Resolve one candidate to an address, or to a named refusal.
|
|
714
|
+
*
|
|
715
|
+
* Four steps, in this order, each of which may decline:
|
|
716
|
+
*
|
|
717
|
+
* 1. literal — `"orders.v1"`, including one element of an array form.
|
|
718
|
+
* 2. constant — `Topics.ORDERS`, through the supplied constant resolver.
|
|
719
|
+
* 3. configuration — neither `${app.topic}` nor `${app.topic:orders}`
|
|
720
|
+
* resolves; the key, and the default text when there is
|
|
721
|
+
* one, are reported instead.
|
|
722
|
+
* 4. specification — the deferred seam; see {@link SpringDestinationResolvers}.
|
|
723
|
+
*
|
|
724
|
+
* Steps 1 and 2 both feed step 3: a literal may be a placeholder, and so may
|
|
725
|
+
* the value a constant folds to (`static final String TOPIC = "${app.topic}"`
|
|
726
|
+
* is an ordinary way to write one). Skipping step 3 after step 2 would file
|
|
727
|
+
* that constant's placeholder text as a resolved address — the exact false
|
|
728
|
+
* identity this module exists to prevent, arrived at one step later.
|
|
729
|
+
*
|
|
730
|
+
* Two classes of text are rejected BEFORE step 3, because they are not
|
|
731
|
+
* addresses in any configuration: a SpEL expression, which the container
|
|
732
|
+
* evaluates against live beans, and an unescaped string-template interpolation
|
|
733
|
+
* in a language that interpolates. Order matters between them and the
|
|
734
|
+
* placeholder rule — `"#{'${app.topics}'.split(',')}"` contains a `${` and
|
|
735
|
+
* would otherwise be filed under a configuration key that is not really what
|
|
736
|
+
* it is.
|
|
737
|
+
*
|
|
738
|
+
* WHITESPACE. An address is kept exactly as the source wrote it, `" orders "`
|
|
739
|
+
* included, so `" orders "` is its own node and does not join `"orders"`. That
|
|
740
|
+
* is a missing connection rather than a false one, which is the trade this
|
|
741
|
+
* module makes everywhere. The emptiness test below trims, because a
|
|
742
|
+
* whitespace-only address addresses nothing — the two rules disagree on
|
|
743
|
+
* purpose, and this is the statement of it.
|
|
744
|
+
*/
|
|
745
|
+
export function resolveSpringDestination(candidate, resolvers = {}) {
|
|
746
|
+
const specification = () => {
|
|
747
|
+
const resolved = resolvers.specification?.(candidate);
|
|
748
|
+
if (resolved === undefined || resolved === null || resolved === '')
|
|
749
|
+
return null;
|
|
750
|
+
return { kind: 'resolved', address: resolved, via: 'specification' };
|
|
751
|
+
};
|
|
752
|
+
const literal = parseSpringStringLiteral(candidate.rawText);
|
|
753
|
+
if (literal !== null) {
|
|
754
|
+
// The raw spelling, not the unquoted value: unquoting has already turned
|
|
755
|
+
// `\$` into `$` and the escaped placeholder into the runtime template.
|
|
756
|
+
if (resolvers.interpolatesStringLiterals === true &&
|
|
757
|
+
hasUnescapedStringInterpolation(candidate.rawText)) {
|
|
758
|
+
return specification() ?? { kind: 'unresolved', reason: 'unescaped-interpolation' };
|
|
759
|
+
}
|
|
760
|
+
return finish(literal, 'literal', specification);
|
|
761
|
+
}
|
|
762
|
+
const trimmed = candidate.rawText.trim();
|
|
763
|
+
if (CONSTANT_REFERENCE.test(trimmed)) {
|
|
764
|
+
const folded = resolvers.constant?.(trimmed.replace(/\s*\.\s*/g, '.')) ?? null;
|
|
765
|
+
if (folded === null)
|
|
766
|
+
return specification() ?? { kind: 'unresolved', reason: 'unresolved-constant' };
|
|
767
|
+
// A folded value has already lost its escapes, so an interpolating language
|
|
768
|
+
// cannot tell `"\${app.topic}"` from `"${app.topic}"` here the way the
|
|
769
|
+
// literal branch can. Both are unresolved either way, so the cost is a
|
|
770
|
+
// reason filed under `unescaped-interpolation` that might have belonged
|
|
771
|
+
// under `unresolved-config-key` — never a false address.
|
|
772
|
+
//
|
|
773
|
+
// A LIVE PATH, not a guard for the future. Kotlin both interpolates and
|
|
774
|
+
// supplies a constant fold — `languages/kotlin.ts` declares
|
|
775
|
+
// `extractModuleConstants` and `foldRoutePathOperands`, and
|
|
776
|
+
// `spring-destinations.ts` hands the fold to this cascade — so a Kotlin
|
|
777
|
+
// constant reaching this branch is an ordinary occurrence and the misfiled
|
|
778
|
+
// reason above is a cost actually paid. Fixing it means teaching the fold
|
|
779
|
+
// to report whether the value it returned was escaped at its declaration,
|
|
780
|
+
// which the shared `ModuleConstants` shape does not carry.
|
|
781
|
+
if (resolvers.interpolatesStringLiterals === true && /\$[{A-Za-z_]/.test(folded)) {
|
|
782
|
+
return specification() ?? { kind: 'unresolved', reason: 'unescaped-interpolation' };
|
|
783
|
+
}
|
|
784
|
+
return finish(folded, 'constant', specification);
|
|
785
|
+
}
|
|
786
|
+
return specification() ?? { kind: 'unresolved', reason: 'not-a-literal-or-constant' };
|
|
787
|
+
}
|
|
788
|
+
function finish(value, via, specification) {
|
|
789
|
+
const decline = (reason, extra = {}) => specification() ?? {
|
|
790
|
+
kind: 'unresolved',
|
|
791
|
+
reason,
|
|
792
|
+
...(extra.configKey === undefined ? {} : { configKey: extra.configKey }),
|
|
793
|
+
...(extra.configDefault === undefined ? {} : { configDefault: extra.configDefault }),
|
|
794
|
+
};
|
|
795
|
+
// Before the placeholder rule: a SpEL expression may CONTAIN a `${…}`, and
|
|
796
|
+
// calling that a configuration key would name the wrong diagnosis.
|
|
797
|
+
if (containsSpelExpression(value))
|
|
798
|
+
return decline('spel-expression');
|
|
799
|
+
const placeholders = resolveSpringPlaceholders(value);
|
|
800
|
+
if (!placeholders.plain) {
|
|
801
|
+
const key = placeholders.key ?? '';
|
|
802
|
+
if (key === '')
|
|
803
|
+
return decline('empty-config-key');
|
|
804
|
+
if (placeholders.defaultValue !== undefined) {
|
|
805
|
+
return decline('overridable-config-default', {
|
|
806
|
+
configKey: key,
|
|
807
|
+
configDefault: placeholders.defaultValue,
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
return decline('unresolved-config-key', { configKey: key });
|
|
811
|
+
}
|
|
812
|
+
if (value.trim() === '') {
|
|
813
|
+
return decline(via === 'literal' ? 'empty-literal-address' : 'empty-constant-address');
|
|
814
|
+
}
|
|
815
|
+
return { kind: 'resolved', address: value, via };
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Expand one accepted argument into per-element candidates.
|
|
819
|
+
*
|
|
820
|
+
* An empty list is a refusal rather than zero silent candidates: `topics = {}`
|
|
821
|
+
* is a listener that names nothing, which is a finding, not an absence.
|
|
822
|
+
*/
|
|
823
|
+
function pushElements(candidates, refusals, base) {
|
|
824
|
+
const elements = splitSpringDestinationList(base.rawText);
|
|
825
|
+
if (elements.length === 0) {
|
|
826
|
+
refusals.push({
|
|
827
|
+
role: base.role,
|
|
828
|
+
source: base.source,
|
|
829
|
+
broker: base.broker,
|
|
830
|
+
reason: 'empty-destination-list',
|
|
831
|
+
rawText: base.rawText,
|
|
832
|
+
argIndex: base.argIndex,
|
|
833
|
+
...(base.argName === undefined ? {} : { argName: base.argName }),
|
|
834
|
+
});
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
for (const [elementIndex, element] of elements.entries()) {
|
|
838
|
+
candidates.push({ ...base, rawText: element, elementIndex });
|
|
839
|
+
}
|
|
840
|
+
}
|