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.
Files changed (531) hide show
  1. package/README.md +95 -26
  2. package/dist/_shared/graph/types.d.ts +81 -2
  3. package/dist/_shared/graph/types.d.ts.map +1 -1
  4. package/dist/_shared/integrations/circuit-breaker.d.ts.map +1 -1
  5. package/dist/_shared/integrations/circuit-breaker.js.map +1 -1
  6. package/dist/_shared/integrations/resilient-fetch.d.ts.map +1 -1
  7. package/dist/_shared/integrations/resilient-fetch.js.map +1 -1
  8. package/dist/_shared/language-detection.d.ts.map +1 -1
  9. package/dist/_shared/language-detection.js +2 -0
  10. package/dist/_shared/language-detection.js.map +1 -1
  11. package/dist/_shared/languages.d.ts +2 -1
  12. package/dist/_shared/languages.d.ts.map +1 -1
  13. package/dist/_shared/languages.js +1 -0
  14. package/dist/_shared/languages.js.map +1 -1
  15. package/dist/_shared/lbug/schema-constants.d.ts +2 -2
  16. package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
  17. package/dist/_shared/lbug/schema-constants.js +4 -0
  18. package/dist/_shared/lbug/schema-constants.js.map +1 -1
  19. package/dist/_shared/scope-resolution/evidence-weights.d.ts.map +1 -1
  20. package/dist/_shared/scope-resolution/finalize-algorithm.js +1 -0
  21. package/dist/_shared/scope-resolution/finalize-algorithm.js.map +1 -1
  22. package/dist/_shared/scope-resolution/language-classification.d.ts +3 -0
  23. package/dist/_shared/scope-resolution/language-classification.d.ts.map +1 -1
  24. package/dist/_shared/scope-resolution/language-classification.js +4 -0
  25. package/dist/_shared/scope-resolution/language-classification.js.map +1 -1
  26. package/dist/_shared/scope-resolution/reference-site.d.ts +10 -0
  27. package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -1
  28. package/dist/_shared/scope-resolution/registries/field-registry.js.map +1 -1
  29. package/dist/_shared/scope-resolution/registries/method-registry.js.map +1 -1
  30. package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
  31. package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
  32. package/dist/_shared/scope-resolution/types.d.ts +15 -0
  33. package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
  34. package/dist/cli/ai-context.d.ts +4 -0
  35. package/dist/cli/ai-context.js +90 -14
  36. package/dist/cli/analyze-config.js +14 -0
  37. package/dist/cli/analyze-options.d.ts +14 -0
  38. package/dist/cli/analyze-watch.d.ts +20 -0
  39. package/dist/cli/analyze-watch.js +434 -0
  40. package/dist/cli/analyze.js +27 -9
  41. package/dist/cli/auto-sync.d.ts +1 -0
  42. package/dist/cli/auto-sync.js +107 -0
  43. package/dist/cli/command-banner.d.ts +15 -0
  44. package/dist/cli/command-banner.js +42 -0
  45. package/dist/cli/detect-changes-format.js +21 -0
  46. package/dist/cli/doctor.d.ts +7 -0
  47. package/dist/cli/doctor.js +14 -0
  48. package/dist/cli/group.js +7 -1
  49. package/dist/cli/help-i18n.js +4 -0
  50. package/dist/cli/i18n/en.d.ts +18 -3
  51. package/dist/cli/i18n/en.js +18 -3
  52. package/dist/cli/i18n/resources.d.ts +33 -3
  53. package/dist/cli/i18n/zh-CN.d.ts +15 -0
  54. package/dist/cli/i18n/zh-CN.js +18 -3
  55. package/dist/cli/index.js +34 -7
  56. package/dist/cli/mcp.d.ts +15 -0
  57. package/dist/cli/mcp.js +44 -0
  58. package/dist/cli/optional-grammars.d.ts +3 -2
  59. package/dist/cli/optional-grammars.js +19 -3
  60. package/dist/cli/setup.js +41 -15
  61. package/dist/cli/update-notice.d.ts +34 -0
  62. package/dist/cli/update-notice.js +91 -0
  63. package/dist/cli/update.d.ts +21 -0
  64. package/dist/cli/update.js +93 -0
  65. package/dist/cli/watch.d.ts +1 -20
  66. package/dist/cli/watch.js +5 -371
  67. package/dist/core/analysis-feature-registry.d.ts +2 -0
  68. package/dist/core/analysis-feature-registry.js +15 -0
  69. package/dist/core/analyzer-identity.js +21 -3
  70. package/dist/core/auto-sync/analysis-worker-launch.d.ts +30 -0
  71. package/dist/core/auto-sync/analysis-worker-launch.js +166 -0
  72. package/dist/core/auto-sync/config.d.ts +33 -0
  73. package/dist/core/auto-sync/config.js +320 -0
  74. package/dist/core/auto-sync/index.d.ts +6 -0
  75. package/dist/core/auto-sync/index.js +6 -0
  76. package/dist/core/auto-sync/path-security.d.ts +9 -0
  77. package/dist/core/auto-sync/path-security.js +251 -0
  78. package/dist/core/auto-sync/repo.d.ts +1 -0
  79. package/dist/core/auto-sync/repo.js +6 -0
  80. package/dist/core/auto-sync/runner.d.ts +44 -0
  81. package/dist/core/auto-sync/runner.js +428 -0
  82. package/dist/core/auto-sync/starter.d.ts +50 -0
  83. package/dist/core/auto-sync/starter.js +497 -0
  84. package/dist/core/auto-sync/state.d.ts +37 -0
  85. package/dist/core/auto-sync/state.js +119 -0
  86. package/dist/core/group/config-parser.js +8 -1
  87. package/dist/core/group/cross-impact.d.ts +3 -0
  88. package/dist/core/group/cross-impact.js +36 -4
  89. package/dist/core/group/extractors/http-patterns/java.js +22 -8
  90. package/dist/core/group/extractors/http-patterns/kotlin.js +86 -31
  91. package/dist/core/group/extractors/http-patterns/node.js +145 -8
  92. package/dist/core/group/extractors/http-patterns/python.js +11 -7
  93. package/dist/core/group/extractors/http-route-extractor.js +63 -17
  94. package/dist/core/group/extractors/java-workspace-extractor.js +244 -31
  95. package/dist/core/group/group-lock.js +5 -6
  96. package/dist/core/group/normalization.d.ts +38 -1
  97. package/dist/core/group/normalization.js +59 -3
  98. package/dist/core/group/service.d.ts +11 -0
  99. package/dist/core/group/service.js +13 -1
  100. package/dist/core/group/storage.js +2 -2
  101. package/dist/core/group/sync.d.ts +24 -1
  102. package/dist/core/group/sync.js +46 -8
  103. package/dist/core/group/types.d.ts +13 -0
  104. package/dist/core/incremental/derived-writeback.d.ts +36 -0
  105. package/dist/core/incremental/derived-writeback.js +68 -0
  106. package/dist/core/incremental/spring-config-drift.d.ts +12 -0
  107. package/dist/core/incremental/spring-config-drift.js +46 -0
  108. package/dist/core/incremental/subgraph-extract.d.ts +7 -4
  109. package/dist/core/incremental/subgraph-extract.js +30 -5
  110. package/dist/core/ingestion/asyncapi/document.d.ts +226 -0
  111. package/dist/core/ingestion/asyncapi/document.js +758 -0
  112. package/dist/core/ingestion/asyncapi/protocol.d.ts +79 -0
  113. package/dist/core/ingestion/asyncapi/protocol.js +203 -0
  114. package/dist/core/ingestion/call-extractors/configs/zig.d.ts +2 -0
  115. package/dist/core/ingestion/call-extractors/configs/zig.js +4 -0
  116. package/dist/core/ingestion/call-extractors/zig-static-gating.d.ts +100 -0
  117. package/dist/core/ingestion/call-extractors/zig-static-gating.js +319 -0
  118. package/dist/core/ingestion/call-processor.js +3 -1
  119. package/dist/core/ingestion/class-extractors/configs/zig.d.ts +2 -0
  120. package/dist/core/ingestion/class-extractors/configs/zig.js +32 -0
  121. package/dist/core/ingestion/class-extractors/generic.js +4 -1
  122. package/dist/core/ingestion/class-types.d.ts +6 -2
  123. package/dist/core/ingestion/destination-key.d.ts +59 -0
  124. package/dist/core/ingestion/destination-key.js +61 -0
  125. package/dist/core/ingestion/entry-point-scoring.d.ts +6 -2
  126. package/dist/core/ingestion/entry-point-scoring.js +8 -44
  127. package/dist/core/ingestion/export-detection.d.ts +37 -0
  128. package/dist/core/ingestion/export-detection.js +60 -0
  129. package/dist/core/ingestion/field-extractors/configs/zig.d.ts +8 -0
  130. package/dist/core/ingestion/field-extractors/configs/zig.js +49 -0
  131. package/dist/core/ingestion/field-extractors/generic.d.ts +1 -1
  132. package/dist/core/ingestion/field-extractors/generic.js +12 -1
  133. package/dist/core/ingestion/frameworks/spring/actuator-runtime.d.ts +26 -0
  134. package/dist/core/ingestion/frameworks/spring/actuator-runtime.js +843 -0
  135. package/dist/core/ingestion/frameworks/spring/analysis-features.d.ts +5 -0
  136. package/dist/core/ingestion/frameworks/spring/analysis-features.js +9 -0
  137. package/dist/core/ingestion/frameworks/spring/annotation-arguments.js +8 -1
  138. package/dist/core/ingestion/frameworks/spring/argument-facts.d.ts +80 -0
  139. package/dist/core/ingestion/frameworks/spring/argument-facts.js +85 -0
  140. package/dist/core/ingestion/frameworks/spring/config-bindings.d.ts +1 -0
  141. package/dist/core/ingestion/frameworks/spring/config-bindings.js +2 -1
  142. package/dist/core/ingestion/frameworks/spring/destinations.d.ts +539 -0
  143. package/dist/core/ingestion/frameworks/spring/destinations.js +840 -0
  144. package/dist/core/ingestion/frameworks/spring/dynamic-lookups.d.ts +22 -0
  145. package/dist/core/ingestion/frameworks/spring/dynamic-lookups.js +124 -0
  146. package/dist/core/ingestion/frameworks/spring/message-producers.d.ts +78 -0
  147. package/dist/core/ingestion/frameworks/spring/message-producers.js +90 -0
  148. package/dist/core/ingestion/frameworks/spring/non-http-handlers.d.ts +29 -0
  149. package/dist/core/ingestion/frameworks/spring/vendor-prefixes.d.ts +7 -0
  150. package/dist/core/ingestion/frameworks/spring/vendor-prefixes.js +22 -0
  151. package/dist/core/ingestion/import-resolvers/configs/zig.d.ts +13 -0
  152. package/dist/core/ingestion/import-resolvers/configs/zig.js +26 -0
  153. package/dist/core/ingestion/import-resolvers/zig.d.ts +34 -0
  154. package/dist/core/ingestion/import-resolvers/zig.js +193 -0
  155. package/dist/core/ingestion/language-config.d.ts +177 -0
  156. package/dist/core/ingestion/language-config.js +719 -0
  157. package/dist/core/ingestion/language-provider.d.ts +178 -1
  158. package/dist/core/ingestion/language-provider.js +22 -0
  159. package/dist/core/ingestion/languages/csharp/razor-view-components.d.ts +62 -0
  160. package/dist/core/ingestion/languages/csharp/razor-view-components.js +954 -0
  161. package/dist/core/ingestion/languages/csharp/resolution-config.d.ts +3 -0
  162. package/dist/core/ingestion/languages/csharp/resolution-config.js +6 -1
  163. package/dist/core/ingestion/languages/csharp/scope-resolver.js +8 -0
  164. package/dist/core/ingestion/languages/index.d.ts +1 -0
  165. package/dist/core/ingestion/languages/index.js +2 -0
  166. package/dist/core/ingestion/languages/java/analysis-features.d.ts +1 -1
  167. package/dist/core/ingestion/languages/java/analysis-features.js +14 -6
  168. package/dist/core/ingestion/languages/java/capture-side-channel.d.ts +8 -0
  169. package/dist/core/ingestion/languages/java/capture-side-channel.js +34 -0
  170. package/dist/core/ingestion/languages/java/captures.js +22 -1
  171. package/dist/core/ingestion/languages/java/lombok-synthesizer.d.ts +42 -0
  172. package/dist/core/ingestion/languages/java/lombok-synthesizer.js +439 -0
  173. package/dist/core/ingestion/languages/java/scope-resolver.js +2 -0
  174. package/dist/core/ingestion/languages/java/spring-actuator.d.ts +2 -0
  175. package/dist/core/ingestion/languages/java/spring-actuator.js +62 -0
  176. package/dist/core/ingestion/languages/java/spring-di.d.ts +17 -1
  177. package/dist/core/ingestion/languages/java/spring-di.js +44 -2
  178. package/dist/core/ingestion/languages/java/spring-dynamic-lookup.d.ts +8 -0
  179. package/dist/core/ingestion/languages/java/spring-dynamic-lookup.js +62 -0
  180. package/dist/core/ingestion/languages/java/spring-message-producers.d.ts +20 -0
  181. package/dist/core/ingestion/languages/java/spring-message-producers.js +83 -0
  182. package/dist/core/ingestion/languages/java/spring-non-http-handlers.d.ts +11 -1
  183. package/dist/core/ingestion/languages/java/spring-non-http-handlers.js +13 -3
  184. package/dist/core/ingestion/languages/java.js +18 -8
  185. package/dist/core/ingestion/languages/jvm/accessor-synthesis.d.ts +99 -0
  186. package/dist/core/ingestion/languages/jvm/accessor-synthesis.js +173 -0
  187. package/dist/core/ingestion/languages/jvm/beanspec.d.ts +17 -0
  188. package/dist/core/ingestion/languages/jvm/beanspec.js +42 -0
  189. package/dist/core/ingestion/languages/kotlin/capture-side-channel.d.ts +15 -0
  190. package/dist/core/ingestion/languages/kotlin/capture-side-channel.js +50 -0
  191. package/dist/core/ingestion/languages/kotlin/captures.js +24 -1
  192. package/dist/core/ingestion/languages/kotlin/lombok-synthesizer.d.ts +26 -0
  193. package/dist/core/ingestion/languages/kotlin/lombok-synthesizer.js +417 -0
  194. package/dist/core/ingestion/languages/kotlin/scope-resolver.js +4 -0
  195. package/dist/core/ingestion/languages/kotlin/spring-actuator.d.ts +4 -0
  196. package/dist/core/ingestion/languages/kotlin/spring-actuator.js +172 -0
  197. package/dist/core/ingestion/languages/kotlin/spring-config-bindings.d.ts +18 -0
  198. package/dist/core/ingestion/languages/kotlin/spring-config-bindings.js +411 -0
  199. package/dist/core/ingestion/languages/kotlin/spring-di.d.ts +51 -1
  200. package/dist/core/ingestion/languages/kotlin/spring-di.js +86 -6
  201. package/dist/core/ingestion/languages/kotlin/spring-dynamic-lookup.d.ts +8 -0
  202. package/dist/core/ingestion/languages/kotlin/spring-dynamic-lookup.js +77 -0
  203. package/dist/core/ingestion/languages/kotlin/spring-message-producers.d.ts +17 -0
  204. package/dist/core/ingestion/languages/kotlin/spring-message-producers.js +119 -0
  205. package/dist/core/ingestion/languages/kotlin/spring-non-http-handlers.d.ts +16 -0
  206. package/dist/core/ingestion/languages/kotlin/spring-non-http-handlers.js +77 -2
  207. package/dist/core/ingestion/languages/kotlin.js +24 -0
  208. package/dist/core/ingestion/languages/python.js +2 -0
  209. package/dist/core/ingestion/languages/rust/query.js +8 -8
  210. package/dist/core/ingestion/languages/zig/captures.d.ts +260 -0
  211. package/dist/core/ingestion/languages/zig/captures.js +2070 -0
  212. package/dist/core/ingestion/languages/zig/index.d.ts +4 -0
  213. package/dist/core/ingestion/languages/zig/index.js +4 -0
  214. package/dist/core/ingestion/languages/zig/interpret.d.ts +36 -0
  215. package/dist/core/ingestion/languages/zig/interpret.js +176 -0
  216. package/dist/core/ingestion/languages/zig/query.d.ts +3 -0
  217. package/dist/core/ingestion/languages/zig/query.js +426 -0
  218. package/dist/core/ingestion/languages/zig/range-binding.d.ts +37 -0
  219. package/dist/core/ingestion/languages/zig/range-binding.js +373 -0
  220. package/dist/core/ingestion/languages/zig/scope-resolver.d.ts +12 -0
  221. package/dist/core/ingestion/languages/zig/scope-resolver.js +58 -0
  222. package/dist/core/ingestion/languages/zig/simple-hooks.d.ts +25 -0
  223. package/dist/core/ingestion/languages/zig/simple-hooks.js +143 -0
  224. package/dist/core/ingestion/languages/zig.d.ts +32 -0
  225. package/dist/core/ingestion/languages/zig.js +130 -0
  226. package/dist/core/ingestion/method-extractors/configs/zig.d.ts +2 -0
  227. package/dist/core/ingestion/method-extractors/configs/zig.js +120 -0
  228. package/dist/core/ingestion/method-extractors/generic.js +16 -4
  229. package/dist/core/ingestion/method-types.d.ts +10 -4
  230. package/dist/core/ingestion/model/field-registry.d.ts +3 -3
  231. package/dist/core/ingestion/model/field-registry.js +11 -7
  232. package/dist/core/ingestion/model/method-registry.d.ts +3 -3
  233. package/dist/core/ingestion/model/method-registry.js +13 -8
  234. package/dist/core/ingestion/model/registration-table.js +3 -0
  235. package/dist/core/ingestion/model/type-registry.js +8 -4
  236. package/dist/core/ingestion/pipeline-phases/di.js +47 -14
  237. package/dist/core/ingestion/pipeline-phases/index.d.ts +1 -0
  238. package/dist/core/ingestion/pipeline-phases/index.js +1 -0
  239. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +7 -1
  240. package/dist/core/ingestion/pipeline-phases/parse-impl.js +79 -91
  241. package/dist/core/ingestion/pipeline-phases/parse.d.ts +17 -0
  242. package/dist/core/ingestion/pipeline-phases/processes.d.ts +2 -1
  243. package/dist/core/ingestion/pipeline-phases/processes.js +39 -4
  244. package/dist/core/ingestion/pipeline-phases/routes.js +27 -18
  245. package/dist/core/ingestion/pipeline-phases/runner.d.ts +4 -1
  246. package/dist/core/ingestion/pipeline-phases/runner.js +34 -14
  247. package/dist/core/ingestion/pipeline-phases/scan.js +82 -1
  248. package/dist/core/ingestion/pipeline-phases/spring-auto-configuration.d.ts +2 -0
  249. package/dist/core/ingestion/pipeline-phases/spring-auto-configuration.js +8 -0
  250. package/dist/core/ingestion/pipeline-phases/spring-destinations.d.ts +123 -0
  251. package/dist/core/ingestion/pipeline-phases/spring-destinations.js +688 -0
  252. package/dist/core/ingestion/pipeline.d.ts +30 -1
  253. package/dist/core/ingestion/pipeline.js +52 -14
  254. package/dist/core/ingestion/resolve-references.js +5 -0
  255. package/dist/core/ingestion/route-extractors/constant-resolver.d.ts +16 -0
  256. package/dist/core/ingestion/route-extractors/constant-resolver.js +10 -0
  257. package/dist/core/ingestion/route-extractors/java-const-resolver.d.ts +27 -1
  258. package/dist/core/ingestion/route-extractors/java-const-resolver.js +161 -13
  259. package/dist/core/ingestion/route-extractors/kotlin-const-resolver.d.ts +12 -18
  260. package/dist/core/ingestion/route-extractors/kotlin-const-resolver.js +214 -33
  261. package/dist/core/ingestion/route-extractors/kotlin-spring.d.ts +13 -0
  262. package/dist/core/ingestion/route-extractors/kotlin-spring.js +259 -0
  263. package/dist/core/ingestion/route-extractors/python-decorator-handler.d.ts +6 -0
  264. package/dist/core/ingestion/route-extractors/python-decorator-handler.js +16 -0
  265. package/dist/core/ingestion/route-extractors/spring-shared.d.ts +30 -1
  266. package/dist/core/ingestion/route-extractors/spring-shared.js +77 -9
  267. package/dist/core/ingestion/route-extractors/spring.js +9 -5
  268. package/dist/core/ingestion/scope-extractor.js +8 -0
  269. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +62 -0
  270. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +4 -0
  271. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +2 -0
  272. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +28 -0
  273. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +11 -2
  274. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +23 -2
  275. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +24 -3
  276. package/dist/core/ingestion/scope-resolution/passes/callable-value-flow.d.ts +11 -1
  277. package/dist/core/ingestion/scope-resolution/passes/callable-value-flow.js +26 -6
  278. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.d.ts +11 -0
  279. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +29 -3
  280. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +8 -1
  281. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +35 -9
  282. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +1 -4
  283. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +277 -9
  284. package/dist/core/ingestion/scope-resolution/pipeline/registry.js +2 -0
  285. package/dist/core/ingestion/scope-resolution/pipeline/run.js +7 -3
  286. package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +43 -9
  287. package/dist/core/ingestion/scope-resolution/scope/walkers.js +71 -10
  288. package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -0
  289. package/dist/core/ingestion/tree-sitter-queries.js +161 -0
  290. package/dist/core/ingestion/type-extractors/zig.d.ts +2 -0
  291. package/dist/core/ingestion/type-extractors/zig.js +17 -0
  292. package/dist/core/ingestion/utils/ast-helpers.d.ts +40 -1
  293. package/dist/core/ingestion/utils/ast-helpers.js +124 -6
  294. package/dist/core/ingestion/utils/callable-flow-captures.d.ts +9 -0
  295. package/dist/core/ingestion/utils/callable-flow-captures.js +75 -14
  296. package/dist/core/ingestion/utils/test-file-path.d.ts +28 -0
  297. package/dist/core/ingestion/utils/test-file-path.js +87 -0
  298. package/dist/core/ingestion/variable-extractors/configs/zig.d.ts +2 -0
  299. package/dist/core/ingestion/variable-extractors/configs/zig.js +71 -0
  300. package/dist/core/ingestion/workers/parse-worker.js +85 -14
  301. package/dist/core/install-context.d.ts +4 -0
  302. package/dist/core/install-context.js +152 -0
  303. package/dist/core/lbug/csv-generator.d.ts +10 -1
  304. package/dist/core/lbug/csv-generator.js +92 -22
  305. package/dist/core/lbug/graph-emit-sink.d.ts +3 -2
  306. package/dist/core/lbug/graph-emit-sink.js +4 -2
  307. package/dist/core/lbug/lbug-adapter.d.ts +100 -5
  308. package/dist/core/lbug/lbug-adapter.js +236 -24
  309. package/dist/core/lbug/schema.d.ts +6 -5
  310. package/dist/core/lbug/schema.js +84 -22
  311. package/dist/core/net/url-guard.d.ts +6 -0
  312. package/dist/core/net/url-guard.js +147 -0
  313. package/dist/core/package-version.d.ts +5 -0
  314. package/dist/core/package-version.js +10 -0
  315. package/dist/core/run-analyze.d.ts +21 -4
  316. package/dist/core/run-analyze.js +426 -117
  317. package/dist/core/search/fts-indexes.d.ts +19 -1
  318. package/dist/core/search/fts-indexes.js +28 -1
  319. package/dist/core/tree-sitter/parser-loader.js +16 -0
  320. package/dist/core/update-cache.d.ts +39 -0
  321. package/dist/core/update-cache.js +141 -0
  322. package/dist/core/update-check.d.ts +31 -0
  323. package/dist/core/update-check.js +274 -0
  324. package/dist/mcp/local/local-backend.d.ts +27 -13
  325. package/dist/mcp/local/local-backend.js +148 -60
  326. package/dist/mcp/repository-policy.d.ts +5 -1
  327. package/dist/mcp/repository-policy.js +48 -4
  328. package/dist/mcp/resources.js +10 -1
  329. package/dist/mcp/server.js +8 -9
  330. package/dist/mcp/tools.js +32 -22
  331. package/dist/server/analyze-launch.d.ts +2 -0
  332. package/dist/server/analyze-launch.js +2 -0
  333. package/dist/server/analyze-worker-protocol.d.ts +6 -1
  334. package/dist/server/analyze-worker.d.ts +1 -0
  335. package/dist/server/analyze-worker.js +38 -20
  336. package/dist/server/api.d.ts +5 -1
  337. package/dist/server/api.js +105 -88
  338. package/dist/server/git-clone.d.ts +40 -16
  339. package/dist/server/git-clone.js +253 -199
  340. package/dist/server/grep-params.d.ts +18 -0
  341. package/dist/server/grep-params.js +83 -0
  342. package/dist/server/grep-scan.d.ts +23 -0
  343. package/dist/server/grep-scan.js +107 -0
  344. package/dist/server/grep-worker.d.ts +1 -0
  345. package/dist/server/grep-worker.js +12 -0
  346. package/dist/server/mcp-http.d.ts +8 -0
  347. package/dist/server/mcp-http.js +16 -1
  348. package/dist/server/update-controller.d.ts +29 -0
  349. package/dist/server/update-controller.js +82 -0
  350. package/dist/storage/file-lock.d.ts +15 -0
  351. package/dist/storage/file-lock.js +155 -0
  352. package/dist/storage/fs-atomic.d.ts +24 -0
  353. package/dist/storage/fs-atomic.js +86 -2
  354. package/dist/storage/global-dir.d.ts +2 -0
  355. package/dist/storage/global-dir.js +6 -0
  356. package/dist/storage/parse-cache.d.ts +45 -7
  357. package/dist/storage/parse-cache.js +189 -42
  358. package/dist/storage/parsedfile-store.d.ts +41 -60
  359. package/dist/storage/parsedfile-store.js +247 -171
  360. package/dist/storage/repo-manager.d.ts +10 -4
  361. package/dist/storage/repo-manager.js +35 -25
  362. package/dist/storage/repo-meta.d.ts +34 -0
  363. package/dist/storage/v8-sidecar.d.ts +48 -0
  364. package/dist/storage/v8-sidecar.js +347 -0
  365. package/dist/types/pipeline.d.ts +14 -0
  366. package/dist/utils/process-identity.d.ts +2 -0
  367. package/dist/utils/process-identity.js +35 -0
  368. package/package.json +10 -4
  369. package/scripts/assert-web-assets.mjs +35 -0
  370. package/scripts/build-web.js +72 -0
  371. package/scripts/build.js +31 -28
  372. package/scripts/cross-platform-tests.ts +5 -0
  373. package/skills/gitnexus-cli.md +5 -1
  374. package/web/assets/ProcessFlowModal-DAeX2BHW.js +24 -0
  375. package/web/assets/{abnfDiagram-QNN2D4NM-Cq2djWWe.js → abnfDiagram-PAR5IXJI-J3OdeHLl.js} +1 -1
  376. package/web/assets/{agent-BWAXPjC5.js → agent-BMRqXorA.js} +6 -4
  377. package/web/assets/architecture-7GRP2DOG-M7G3SWCO-G3sWyX6M.js +1 -0
  378. package/web/assets/architectureDiagram-2PNWDENW-ysKcRlGT.js +36 -0
  379. package/web/assets/blockDiagram-PUWGUBOA-oLXTa1lY.js +129 -0
  380. package/web/assets/c4Diagram-3NLNHZP5-D3iu4eC0.js +38 -0
  381. package/web/assets/{chunk-2SREHG4O-Dip42xOq.js → chunk-2NMSUPJH-B24w-wng.js} +1 -1
  382. package/web/assets/{chunk-VJFVEZTB-CX66F2ep.js → chunk-32MCUW2P-ByUyt0Sa.js} +3 -3
  383. package/web/assets/{chunk-4RFN2BYJ-DHHWuC-n.js → chunk-3NPNU5GP-DocZ07Sg.js} +1 -1
  384. package/web/assets/{chunk-XNMVGMAZ-Cg_LnPtj.js → chunk-3SMGFRGC-CAu4JRLv.js} +1 -1
  385. package/web/assets/{chunk-W44A43WB-BN8EQc_u.js → chunk-4CAJECRU-DRYMwG9z.js} +1 -1
  386. package/web/assets/chunk-4OADUZ5Q-CxrSvOwZ.js +10 -0
  387. package/web/assets/{chunk-DMV4VAQV-Bn6WXDSX.js → chunk-5IQCK5RY-CCMhl4so.js} +2 -2
  388. package/web/assets/{chunk-RRFMTAIC-CMN0wcYA.js → chunk-5MVFYWFU-Db9KvrYO.js} +1 -1
  389. package/web/assets/chunk-CLS4B6BI-Uvx5QNsu.js +168 -0
  390. package/web/assets/chunk-DZP67EKU-B3Mt380j.js +70 -0
  391. package/web/assets/{chunk-6L755F7B-CMhLBxi2.js → chunk-EICP7K5X-CqwE9hui.js} +1 -1
  392. package/web/assets/{chunk-BI6VK774-D0oaWk8V.js → chunk-GDY2UAB4-CtQQR0a8.js} +1 -1
  393. package/web/assets/chunk-H7VHZCWX-CE1nAmt6.js +1 -0
  394. package/web/assets/chunk-IJBDOHL6-BUKtY6oF.js +1 -0
  395. package/web/assets/{chunk-OIYT25JQ-CQ6KOZGX.js → chunk-JFFVMJIA-0fJenqia.js} +1 -1
  396. package/web/assets/{chunk-HNC4WDU7-DoU3KnA7.js → chunk-JZGDI5JK-BeTMSSkJ.js} +1 -1
  397. package/web/assets/{chunk-EQFTRU2I-1ix7TzW9.js → chunk-KD6SPIPN-Qp1Pflah.js} +1 -1
  398. package/web/assets/chunk-NF366YI6-CpSpya3n.js +231 -0
  399. package/web/assets/chunk-NUDPKBEF-CU1-BQIW.js +34 -0
  400. package/web/assets/{chunk-SATU7PGQ-B1zhgOvc.js → chunk-OJINMHTS-zOCQe4eI.js} +1 -1
  401. package/web/assets/{chunk-E5QJAATJ-dh-5CtIV.js → chunk-Q6NGPIAH-yyBomb63.js} +1 -1
  402. package/web/assets/chunk-QJSWEUOL-D_rf9gPD.js +128 -0
  403. package/web/assets/chunk-R7JDMUKV-CRoqKpD6.js +1 -0
  404. package/web/assets/{chunk-VE5CLXGZ-C1vB7wpY.js → chunk-RCXAM2GK-CUqrWQDV.js} +1 -1
  405. package/web/assets/{chunk-66DQ2XMT-CM0xjFAN.js → chunk-RT4MAWR3-zXy9-Lvt.js} +1 -1
  406. package/web/assets/{chunk-TZUXEDM2-B7p9AcTw.js → chunk-SFMINX4W-J2UrvMpD.js} +1 -1
  407. package/web/assets/{chunk-LORZLZWG-B57uGw2N.js → chunk-SLDQ463B-CHY6e_bQ.js} +2 -2
  408. package/web/assets/{chunk-FWYVLQTC-BgDItRzJ.js → chunk-TCEHX7R3-CXPDEVbm.js} +1 -1
  409. package/web/assets/{chunk-224SPVON-B8gxtXpy.js → chunk-VQC2EELJ-Dc8fIy4R.js} +1 -1
  410. package/web/assets/chunk-VRZKL3EH-DB3KixTQ.js +1 -0
  411. package/web/assets/{chunk-A7VWPJGB-B4O3oQdQ.js → chunk-VTHDQ7BV-b7FsPQZO.js} +1 -1
  412. package/web/assets/chunk-VY2OSTJR-Dkgc3Brg.js +2 -0
  413. package/web/assets/chunk-WUBWJARI-7FwILJ3k.js +2 -0
  414. package/web/assets/{chunk-D6VWDJW2-BxcBdZ9c.js → chunk-YBMQ5JGJ-BSnyWZmd.js} +1 -1
  415. package/web/assets/chunk-YDMLL4PJ-IHhMngTz.js +2 -0
  416. package/web/assets/chunk-Z6V44ARX-BjCEsF0Q.js +62 -0
  417. package/web/assets/chunk-ZJJ7VQJP-Dz089b-K.js +321 -0
  418. package/web/assets/chunk-ZNLK7HRR-B0iFpmW4.js +1 -0
  419. package/web/assets/chunk-ZQN7UHIL-Cy7KAjQt.js +1 -0
  420. package/web/assets/{chunk-L5GCOVLC-CE2w8Nm_.js → chunk-ZS6VBONO-DBjJO0w9.js} +2 -2
  421. package/web/assets/{chunk-NV3KIAZN-5yvcpMiu.js → chunk-ZUNWM646-DNRJyNPe.js} +1 -1
  422. package/web/assets/classDiagram-FGAMPOII-C5Ibyode.js +1 -0
  423. package/web/assets/classDiagram-v2-AI5RJEK4-C5Ibyode.js +1 -0
  424. package/web/assets/{cose-bilkent-RZA4XIPR-BHpK4Crp.js → cose-bilkent-Q72C2OJ3-DMeFXnK8.js} +1 -1
  425. package/web/assets/cynefin-OW5HDTMX-6OCTWOGG-vlqakYcO.js +1 -0
  426. package/web/assets/{cynefinDiagram-TR7LZUCG-C3Iar9lZ.js → cynefinDiagram-2OOWUY4M-BY6DZkTt.js} +1 -1
  427. package/web/assets/dagre-MPVFI544-DWO70fy_.js +4 -0
  428. package/web/assets/diagram-AE4NWXFW-f7JRVRUL.js +24 -0
  429. package/web/assets/{diagram-L7DVE3PN-GIRyVUtt.js → diagram-BUJID5UG-BXZv1rCS.js} +2 -2
  430. package/web/assets/{diagram-JCD7PQ7C-DC5-9FRr.js → diagram-C44CZ5NI-CRNfvjCq.js} +1 -1
  431. package/web/assets/diagram-EYOLEXXS-DZOjKQVE.js +30 -0
  432. package/web/assets/diagram-P7B6PWOJ-D3SQmw3_.js +3 -0
  433. package/web/assets/{ebnfDiagram-DRHRQJWO-D8_CI4ZF.js → ebnfDiagram-FTPZ3PV6-jKWpIDN2.js} +1 -1
  434. package/web/assets/erDiagram-H3ERR26V-BqJntEuJ.js +99 -0
  435. package/web/assets/eventmodeling-NTZA5JFV-WKSWQHFS-Bq4FqtBL.js +1 -0
  436. package/web/assets/flowDiagram-YHGXBVSY-Dnlk2bEu.js +1 -0
  437. package/web/assets/ganttDiagram-RWFTB7ED-Bl6Y1K5T.js +292 -0
  438. package/web/assets/gitGraph-4MIJSDKK-JH2HKYUO-B3yc4GkH.js +1 -0
  439. package/web/assets/gitGraphDiagram-MAR6GKOP-CkNvsrdw.js +106 -0
  440. package/web/assets/{index-CJDpyH-n.css → index-C2_aej58.css} +1 -1
  441. package/web/assets/{index-DDRie6_8.js → index-Dj3vQOHK.js} +51 -51
  442. package/web/assets/info-A6RAGUB7-3TX3OFWW-BM3N5_Dv.js +1 -0
  443. package/web/assets/infoDiagram-LV547AFH-COkFutAf.js +2 -0
  444. package/web/assets/{ishikawaDiagram-4JDSWWDM-DmqWtjkl.js → ishikawaDiagram-PUJQGNDS-Bb8FylSR.js} +2 -2
  445. package/web/assets/{journeyDiagram-QUS3FAKT-XAzVzrQW.js → journeyDiagram-EW5QJT4W-BzoHUBhM.js} +4 -4
  446. package/web/assets/{kanban-definition-D4L7SQKK-YurRnmYn.js → kanban-definition-JSQERAZB-Uy2Pi0yQ.js} +4 -4
  447. package/web/assets/katex-CZ4GXH2S-C0Fcou7U.js +257 -0
  448. package/web/assets/{mindmap-definition-66QP5TD6-ChrWWnil.js → mindmap-definition-VBJCLQLM-B-CnOnt_.js} +3 -3
  449. package/web/assets/packet-AYTQ26CC-MKII6P3Y-Bd52ZE3V.js +1 -0
  450. package/web/assets/{pegDiagram-YKIO5C6U-QGeQLEq2.js → pegDiagram-7WILOQTJ-D9sneKXg.js} +1 -1
  451. package/web/assets/pie-WAS4IAKB-6B4OPMJ5-DW5Xy69Z.js +1 -0
  452. package/web/assets/pieDiagram-EFA7CBSB-Dx2NQofL.js +39 -0
  453. package/web/assets/{quadrantDiagram-VO5QLCXA-B7098wSc.js → quadrantDiagram-WARVYA7P-DPKL9_0A.js} +3 -3
  454. package/web/assets/radar-RG4KPBEZ-UPTR3XUL-CkzibT2h.js +1 -0
  455. package/web/assets/railroad-74A4TZTK-MEFUPFMS-D9zF-mDN.js +1 -0
  456. package/web/assets/railroad-abnf-HS5TGJTU-R5YG2S5D-ruacjUbX.js +1 -0
  457. package/web/assets/railroad-ebnf-LZEXJU2U-G5QEX2L6-S1zc5OiO.js +1 -0
  458. package/web/assets/railroad-peg-WCYAUIDC-H5I2ZRY2-Cbcr0XzY.js +1 -0
  459. package/web/assets/{railroadDiagram-M77LTMSB-AXu4vxk_.js → railroadDiagram-5NIQVNLQ-DsweTo14.js} +1 -1
  460. package/web/assets/{requirementDiagram-RASO5HTO-B-l8InpK.js → requirementDiagram-SXREGOKZ-CXwdNSVR.js} +4 -4
  461. package/web/assets/{sankeyDiagram-F32LOJ6L-Kd7zjJgX.js → sankeyDiagram-VV74M2VP-CA6jtP9P.js} +3 -3
  462. package/web/assets/{sequenceDiagram-URATNSBD-BlcdY9DS.js → sequenceDiagram-52ZFIFDL-k_5rEbzm.js} +15 -15
  463. package/web/assets/src-Df5C1Nz4.js +1 -0
  464. package/web/assets/stateDiagram-3QAKPIVI-BejHTf31.js +1 -0
  465. package/web/assets/stateDiagram-v2-HN2P4RDZ-BrfoRuOc.js +1 -0
  466. package/web/assets/swimlanes-QLVP42QD-CQSVO5D6.js +1 -0
  467. package/web/assets/swimlanesDiagram-JKPDBFCN-DrWB9kcD.js +8 -0
  468. package/web/assets/{timeline-definition-WJXWRH4I-DUfp1R3E.js → timeline-definition-GS45XAXW-C0Ks8rPP.js} +4 -4
  469. package/web/assets/treeView-Q6P3EWNA-ZRJGANNQ-DmKOCo6L.js +1 -0
  470. package/web/assets/treemap-WGGIJYW6-K64VN5AS-DO3xY92U.js +1 -0
  471. package/web/assets/{vennDiagram-K3VCMQLI-Bj-FN-XS.js → vennDiagram-QIQU2ZRJ-rgPiS837.js} +3 -3
  472. package/web/assets/wardley-WFR3VGLG-D67OWCCH-DiDPBNft.js +1 -0
  473. package/web/assets/wardleyDiagram-U6YNM66N-DRVr7SPi.js +78 -0
  474. package/web/assets/{xychartDiagram-5VRUVLCW-CK_rbSWE.js → xychartDiagram-XJAZEDNM-COtlZzYb.js} +3 -3
  475. package/web/index.html +19 -17
  476. package/web/assets/ProcessFlowModal-Badh2Pbg.js +0 -24
  477. package/web/assets/architecture-TIHT7OUA-V6N4J3SY-fTePvLoe.js +0 -1
  478. package/web/assets/architectureDiagram-OT7VKSJ7-mKEXxkeF.js +0 -36
  479. package/web/assets/blockDiagram-54WLDVWM-Dg_hrRWE.js +0 -132
  480. package/web/assets/c4Diagram-X2LASM7V-CnhoT4aV.js +0 -10
  481. package/web/assets/chunk-2AEHWXPW-CV4r6-1I.js +0 -127
  482. package/web/assets/chunk-5VCL7Z4A-CQ-2dYWj.js +0 -1
  483. package/web/assets/chunk-6BELYETK-ChsYtfHW.js +0 -1
  484. package/web/assets/chunk-7CWYLC5S-DRgGkkpF.js +0 -2
  485. package/web/assets/chunk-7FYTHRHK-CsmYIswB.js +0 -32
  486. package/web/assets/chunk-KRXBNO2N-C-UNuKKf.js +0 -1
  487. package/web/assets/chunk-LAHSG3N2-Bz4hcn0N.js +0 -321
  488. package/web/assets/chunk-LIEV3EAG-pyKGssV3.js +0 -1
  489. package/web/assets/chunk-NLANEA3F-XpH_NPA5.js +0 -62
  490. package/web/assets/chunk-PE7DX7ZZ-CCvRVj43.js +0 -10
  491. package/web/assets/chunk-Q67WD55A-BWCH2SVz.js +0 -156
  492. package/web/assets/chunk-SZD42YQK-ijyKKVr1.js +0 -70
  493. package/web/assets/chunk-TFPGFQPA-Dj6a-0pC.js +0 -231
  494. package/web/assets/chunk-TGVD4F4B-BMc31Gj2.js +0 -2
  495. package/web/assets/chunk-UAT7B5JY-DW-A_fpI.js +0 -1
  496. package/web/assets/chunk-VY5UBI4V-yxPQAWxw.js +0 -1
  497. package/web/assets/chunk-WJBAP47W-BckNkuEN.js +0 -1
  498. package/web/assets/classDiagram-VPOSVPXH-BjOiXA_C.js +0 -1
  499. package/web/assets/classDiagram-v2-5VFNUXQA-BjOiXA_C.js +0 -1
  500. package/web/assets/cynefin-VYW2F7L2-2VAHYKR6-DAsF6GZv.js +0 -1
  501. package/web/assets/dagre-K64A6Z3X-CUwi79mA.js +0 -4
  502. package/web/assets/diagram-2TLGJV2N-C2F5Nfym.js +0 -30
  503. package/web/assets/diagram-KFBZAVDW-Dfmf_4ti.js +0 -3
  504. package/web/assets/diagram-YP3FGXHI-CyNFYtkd.js +0 -24
  505. package/web/assets/erDiagram-SZLJLQMB-CohQjznC.js +0 -85
  506. package/web/assets/eventmodeling-45OFAUF4-AOUJWSLX-BvTc6iQi.js +0 -1
  507. package/web/assets/flowDiagram-BWE6NHOH-B6RnEvD9.js +0 -1
  508. package/web/assets/ganttDiagram-2QTCJSNI-Cks_mEcF.js +0 -292
  509. package/web/assets/gitGraph-TEB2WS4Q-U5VWYIBC-Dy7CCSZ5.js +0 -1
  510. package/web/assets/gitGraphDiagram-VVZAVV5G-CG41cIVc.js +0 -106
  511. package/web/assets/info-DKCQHKI2-DX6KST54-BLJg1ikX.js +0 -1
  512. package/web/assets/infoDiagram-FY4PZKRE-BJ3vPVwW.js +0 -2
  513. package/web/assets/katex-C5OPUE3Q-Bnbavtmu.js +0 -257
  514. package/web/assets/packet-7NZHBO7P-5WG45QPS-KUxcuvaA.js +0 -1
  515. package/web/assets/pie-RZYD4A2V-2OCZ2VTD-DPs08kJY.js +0 -1
  516. package/web/assets/pieDiagram-YPY3TKG5-BuT3MYzm.js +0 -39
  517. package/web/assets/radar-I7S5WNFK-EPLSA35B-w5F0K3a7.js +0 -1
  518. package/web/assets/railroad-3IZDKUUU-PWFQV6Y6-Bu668yqY.js +0 -1
  519. package/web/assets/railroad-abnf-AHOZXSZD-JYYJX3BS-B6ghpETS.js +0 -1
  520. package/web/assets/railroad-ebnf-EBAXGLYW-RBZ4ONAP-D-XmHh3M.js +0 -1
  521. package/web/assets/railroad-peg-LSFZ7HO6-KHNPXGPG-mhkrhzEn.js +0 -1
  522. package/web/assets/src-C5sx_gMp.js +0 -1
  523. package/web/assets/stateDiagram-DODFXNG5-DTdHc-fF.js +0 -1
  524. package/web/assets/stateDiagram-v2-JNMM5JRA-DioNLDDo.js +0 -1
  525. package/web/assets/swimlanes-OSWOYLSK-CA0ofdwY.js +0 -2
  526. package/web/assets/swimlanesDiagram-RHGX2RDE-PkwUdckC.js +0 -8
  527. package/web/assets/treeView-QDETBFTQ-EHYTSQNO-C5lpBgPm.js +0 -1
  528. package/web/assets/treemap-6X3UGDF4-CCWCF7EK-BB0ZyO-B.js +0 -1
  529. package/web/assets/wardley-OPB4EBWU-6OWERDVT-CEA3Nq0j.js +0 -1
  530. package/web/assets/wardleyDiagram-XK6FUAWV-CVV0dHFr.js +0 -78
  531. /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
+ }