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,688 @@
1
+ /**
2
+ * Phase: springDestinations
3
+ *
4
+ * Materializes Spring async messaging as graph structure: a `Destination` node
5
+ * per broker address, `CONSUMES_FROM` from every `@KafkaListener`-family
6
+ * handler, and `PUBLISHES_TO` from every messaging-template publish. The
7
+ * inbound and outbound facts are captured during parse and survive the parse
8
+ * cache; until now nothing read them.
9
+ *
10
+ * Since `asyncApiSpecPath`, the phase has a SECOND source that is not Spring
11
+ * and not source code at all: AsyncAPI documents read off disk
12
+ * (`ingestion/asyncapi/document.ts`). They mint the same `Destination` nodes on
13
+ * the same key, so both sources meet on one node. The reader is deliberately
14
+ * framework-neutral and lives outside `frameworks/spring/`; only the emit is
15
+ * hosted here, because this is where the node's keying rule is enforced and
16
+ * splitting that rule across two phases is how it drifts. The phase name is
17
+ * accurate about its origin rather than its current contents.
18
+ *
19
+ * Shaped after `Route` + `HANDLES_ROUTE` in `routes.ts` — a framework overlay
20
+ * node keyed by what it names, with the callable pointing at it, down to the
21
+ * detail that the key pairs the address with the one dimension that can make
22
+ * two same-named things different places: the method for a Route, the broker
23
+ * here.
24
+ *
25
+ * ── THE KEYING RULE, WHICH IS THE POINT OF THE PHASE ─────────────────────
26
+ *
27
+ * A `Destination` connects two services precisely because both sides mint the
28
+ * SAME node id from the SAME address on the SAME broker. That is the whole
29
+ * value, and it is also the whole hazard: an address that could not be resolved
30
+ * must never be allowed to key a node.
31
+ *
32
+ * resolved id = generateId('Destination', `<broker> <address>`) `address` present
33
+ * unresolved id = generateId('Destination', <site>) `address` ABSENT
34
+ *
35
+ * Two unrelated services that each merely write `@KafkaListener(topics =
36
+ * "${app.topic}")` have said nothing whatever about each other. Keyed on the
37
+ * placeholder text they would land on one node and READ AS CONNECTED, in a
38
+ * report, as a fact. A missing edge is visible as a gap; a false one is not.
39
+ *
40
+ * A status property would not have prevented this, and neither would a second
41
+ * label: the id is what merges the nodes, and both sides would still compute
42
+ * the same id. Only the KEY prevents it, so an unresolved destination is keyed
43
+ * by its source LOCATION — a value no second file can produce.
44
+ *
45
+ * The same rule governs the `address` PROPERTY, which is the join key a
46
+ * cross-repository pass would match on. It is written only when resolved. An
47
+ * absent property cannot match another absent property, so the structural
48
+ * guarantee survives being read back out of the database. The unresolved
49
+ * spelling is kept in `name`, for a human reading the node.
50
+ *
51
+ * The BROKER is part of the connecting key rather than a reason to withdraw
52
+ * one — see {@link destinationNodeKey}, which owns that argument and the
53
+ * evidence for it. Two brokers claiming one address is therefore an ordinary
54
+ * two-node situation here, exactly like `GET /x` and `POST /x`, and this phase
55
+ * needs no vocabulary for it: nothing is being taken away, so there is nothing
56
+ * to diagnose.
57
+ *
58
+ * `name` must never be used to join two destinations, and nothing does — but
59
+ * the stronger claim that nothing reads it at all would be false. `Destination`
60
+ * is in `VALID_NODE_LABELS`, and `mcp/local/local-backend.ts` resolves a
61
+ * symbol with an unlabeled `WHERE n.name = $symName`, so a destination can be
62
+ * returned by name like any other node. That is a lookup, not a join: it
63
+ * matches a caller-supplied string against one node, never one destination
64
+ * against another, so it cannot manufacture the connection this phase exists to
65
+ * prevent.
66
+ *
67
+ * @deps parse, scopeResolution, springConfig
68
+ * @reads Spring messaging capture facts, Method/Function nodes, Property nodes,
69
+ * AsyncAPI documents under `options.asyncApiSpecPath` (filesystem)
70
+ * @writes Destination nodes; synthetic File nodes for out-of-tree documents;
71
+ * CONSUMES_FROM / PUBLISHES_TO / USES edges
72
+ */
73
+ import path from 'node:path';
74
+ import { generateId } from '../../../lib/utils.js';
75
+ import { readAsyncApiDocuments } from '../asyncapi/document.js';
76
+ import { logger } from '../../logger.js';
77
+ import { SPRING_CONFIG_DESCRIPTION } from '../frameworks/spring/config-bindings.js';
78
+ import { parseSpringStringLiteral, resolveSpringDestination, selectConsumerDestinationArguments, selectProducerDestinationArguments, } from '../frameworks/spring/destinations.js';
79
+ import { destinationNodeKey } from '../destination-key.js';
80
+ import { getProviderForFile } from '../languages/index.js';
81
+ import { isDev } from '../utils/env.js';
82
+ import { getPhaseOutput } from './types.js';
83
+ /**
84
+ * Exact-range index of callable nodes, mirroring the bridge in
85
+ * `non-http-handlers.ts`.
86
+ *
87
+ * A duplicate range maps to `null` rather than to one of its nodes: two
88
+ * callables sharing a span means the index cannot say which one publishes, and
89
+ * attributing the publish to an arbitrary one of them is the failure this
90
+ * phase is least able to detect afterwards. The File-level edge below is the
91
+ * fallback, so a `null` here costs precision, not the fact.
92
+ */
93
+ function callableOwnersByRange(graph) {
94
+ const owners = new Map();
95
+ for (const node of graph.iterNodes()) {
96
+ if ((node.label !== 'Method' && node.label !== 'Function') ||
97
+ typeof node.properties.filePath !== 'string') {
98
+ continue;
99
+ }
100
+ const key = `${node.properties.filePath}\0${node.properties.startLine}\0${node.properties.endLine}`;
101
+ owners.set(key, owners.has(key) ? null : node);
102
+ }
103
+ return owners;
104
+ }
105
+ /** Capture ranges are 1-based; graph nodes carry 0-based lines. */
106
+ function ownerKey(filePath, range) {
107
+ return `${filePath}\0${range.startLine - 1}\0${range.endLine - 1}`;
108
+ }
109
+ /**
110
+ * Spring configuration `Property` nodes grouped by KEY.
111
+ *
112
+ * Deliberately a multimap. `spring-config.ts` keys a Property node per FILE
113
+ * (`spring-config:<file>:<key>`), so one key declared in `application.yml` and
114
+ * again in `application-prod.yml` is TWO nodes. Linking to only the first would
115
+ * silently pin a destination to an arbitrary profile. An EMPTY match set is
116
+ * normal, not an error — a key may be supplied by an environment variable or a
117
+ * config server and never appear in a checked-in file at all.
118
+ */
119
+ function springConfigPropertiesByKey(graph) {
120
+ const byKey = new Map();
121
+ for (const node of graph.iterNodes()) {
122
+ if (node.label !== 'Property')
123
+ continue;
124
+ const description = node.properties.description;
125
+ if (typeof description !== 'string' || !description.startsWith(SPRING_CONFIG_DESCRIPTION)) {
126
+ continue;
127
+ }
128
+ const key = node.properties.name;
129
+ if (typeof key !== 'string' || key === '')
130
+ continue;
131
+ const existing = byKey.get(key);
132
+ if (existing === undefined)
133
+ byKey.set(key, [node.id]);
134
+ else
135
+ existing.push(node.id);
136
+ }
137
+ return byKey;
138
+ }
139
+ /**
140
+ * Fold a constant reference against the harvested repo constants, using the
141
+ * owning provider's own fold when it declares one.
142
+ *
143
+ * Only providers that declare `extractModuleConstants` contribute to the table,
144
+ * so a language that harvests nothing simply resolves nothing here and the
145
+ * cascade records `unresolved-constant`. That is a countable gap, not a wrong
146
+ * answer.
147
+ */
148
+ function makeConstantResolver(filePath, repo) {
149
+ if (repo.size === 0)
150
+ return undefined;
151
+ const fold = getProviderForFile(filePath)?.foldRoutePathOperands;
152
+ if (fold === undefined)
153
+ return undefined;
154
+ return (name) => fold(filePath, [{ kind: 'ref', name }], repo);
155
+ }
156
+ /**
157
+ * Identity for a destination node.
158
+ *
159
+ * The connecting key is `(broker, address)` and nothing else — not the file,
160
+ * not the site. That is what lets a publisher in one module and a subscriber in
161
+ * another meet on one node, which is the entire point, and it is minted by the
162
+ * framework-neutral {@link destinationNodeKey} so a non-Spring producer can mint
163
+ * the same identity without importing anything Spring.
164
+ *
165
+ * The broker is IN that key rather than a reason to withhold one. The argument
166
+ * for it, including the inferred-broker objection and why the previous rule was
167
+ * worse, lives on `destinationNodeKey` — one copy, next to the code that
168
+ * decides it.
169
+ *
170
+ * The site key has to identify the site EXACTLY. It carries the file path, so
171
+ * no second file can ever produce it — that is the cross-repository guarantee,
172
+ * and nothing added below can weaken it. Everything else in the key is there to
173
+ * keep two sites inside ONE file apart, which is the same false identity at a
174
+ * smaller scale:
175
+ *
176
+ * - the owner SCOPE ID, because two callables can start on the same line and
177
+ * because `ownerRange` is optional on a handler fact — keyed on the line
178
+ * alone, a file whose handlers carried no range collapsed every consumer in
179
+ * it onto line 0;
180
+ * - the full owner RANGE, which separates two sites the scope id cannot (a
181
+ * scope id is stable, but two callables that share one are still two
182
+ * callables);
183
+ * - the raw TEXT plus argument and element position, because two publishes to
184
+ * two different placeholders inside one method share the owner entirely.
185
+ *
186
+ * The residual: two publishes with identical text at identical argument
187
+ * positions inside ONE callable share a node. They are indistinguishable to
188
+ * this phase — a producer fact carries the owner's range, not the call's — and
189
+ * merging two publishes of the same unreadable address from one method is the
190
+ * one collapse that asserts nothing false about anybody.
191
+ */
192
+ function destinationNodeId(site) {
193
+ if (site.resolution.kind === 'resolved') {
194
+ return generateId('Destination', destinationNodeKey(site.candidate.broker, site.resolution.address));
195
+ }
196
+ const { candidate, ownerRange } = site;
197
+ const position = ownerRange === undefined
198
+ ? 'no-range'
199
+ : `${ownerRange.startLine}:${ownerRange.startCol}:${ownerRange.endLine}:${ownerRange.endCol}`;
200
+ return generateId('Destination', [
201
+ site.filePath,
202
+ site.ownerScopeId,
203
+ position,
204
+ candidate.role,
205
+ candidate.source,
206
+ candidate.argIndex,
207
+ candidate.elementIndex,
208
+ candidate.rawText,
209
+ ].join(':'));
210
+ }
211
+ /**
212
+ * Display spelling for a destination's `name`.
213
+ *
214
+ * A resolved destination's `name` is its address, bare. An unresolved one keeps
215
+ * the source text — but unquoted, so the two are spelled the same way. Keeping
216
+ * the quotes on one and not the other made the same value read differently
217
+ * depending on whether it resolved, for no gain to the human the property
218
+ * exists for.
219
+ */
220
+ function destinationDisplayName(rawText) {
221
+ return parseSpringStringLiteral(rawText) ?? rawText.trim();
222
+ }
223
+ function edgeReason(candidate) {
224
+ const argument = candidate.argName ?? `arg${candidate.argIndex}`;
225
+ const element = `${argument}[${candidate.elementIndex}]`;
226
+ const exchange = candidate.exchange === undefined ? '' : ` exchange=${candidate.exchange}`;
227
+ return `spring-${candidate.source}:${element}${exchange}`;
228
+ }
229
+ /**
230
+ * Pseudo-path prefix for a document that is not a file of this repository.
231
+ *
232
+ * An edge needs a source node, and the emit below refuses to attach one to a
233
+ * `File` that does not exist — so a document supplied from outside the working
234
+ * tree needs an identity minted for it. The same answer
235
+ * `frameworks/spring/actuator-runtime.ts` gives for Actuator snapshots
236
+ * (`spring-actuator:<endpoint>`): a prefixed pseudo-path that a real
237
+ * repo-relative path is not expected to take.
238
+ *
239
+ * That is a CONVENTION, not a guarantee, and the difference is worth stating
240
+ * because the neighbouring prefix states it too strongly. A colon is a legal
241
+ * POSIX filename character, so a committed file literally named
242
+ * `asyncapi:orders.yaml` would share this identity — costing one merged node
243
+ * and a misattributed edge, never a wrong address. Windows cannot express the
244
+ * collision at all. It is accepted on the same terms the Actuator prefix
245
+ * already is rather than escaped, because an escape would have to be applied to
246
+ * both prefixes at once to be worth anything.
247
+ */
248
+ const DOCUMENT_FILE_PREFIX = 'asyncapi:';
249
+ /**
250
+ * The `File` node an AsyncAPI operation's edge hangs off.
251
+ *
252
+ * A document COMMITTED to the repository already has a real `File` node, and
253
+ * using it is strictly better: the edge lands on something the reader can open,
254
+ * and the ordinary per-file writeback keeps it honest. Only a document from
255
+ * outside the tree gets a synthetic node.
256
+ */
257
+ function documentFileNodeId(ctx, configuredRoot, documentPath) {
258
+ const repoRelative = path.relative(ctx.repoPath, documentPath);
259
+ if (repoRelative !== '' && !repoRelative.startsWith('..') && !path.isAbsolute(repoRelative)) {
260
+ const realId = generateId('File', repoRelative.split(path.sep).join('/'));
261
+ if (ctx.graph.getNode(realId) !== undefined)
262
+ return realId;
263
+ }
264
+ // Relative to the CONFIGURED root, not to the filesystem root: an absolute
265
+ // path would put a machine's directory layout into the graph, and two
266
+ // machines indexing the same documents would then disagree about their ids.
267
+ const relative = path.relative(configuredRoot, documentPath);
268
+ const label = relative === '' || relative.startsWith('..')
269
+ ? path.basename(documentPath)
270
+ : relative.split(path.sep).join('/');
271
+ const filePath = `${DOCUMENT_FILE_PREFIX}${label}`;
272
+ const id = generateId('File', filePath);
273
+ if (ctx.graph.getNode(id) === undefined) {
274
+ ctx.graph.addNode({
275
+ id,
276
+ label: 'File',
277
+ properties: { name: path.basename(documentPath), filePath },
278
+ });
279
+ }
280
+ return id;
281
+ }
282
+ /**
283
+ * Mint destinations stated by AsyncAPI documents, with no claim about code.
284
+ *
285
+ * ── WHY THIS DOES NOT TRY TO FIND THE HANDLER ─────────────────────────────
286
+ *
287
+ * A document says an address is sent to or received from; it does not say by
288
+ * which method. Guessing that mapping is a real temptation and a bad trade: the
289
+ * addresses in one document partition by (broker, action) into buckets that
290
+ * usually hold more than one operation, so any assignment beyond a bucket of
291
+ * size one is a heuristic — and a wrong one silently attaches a real address to
292
+ * the wrong handler, which is a false connection dressed as a resolved one.
293
+ *
294
+ * So this claims only what the document actually states: that THIS SERVICE
295
+ * talks to that address on that broker in that direction. The edge therefore
296
+ * starts at the document, not at a callable. That is a weaker statement than a
297
+ * source-derived edge and it is worth having anyway, because it is available in
298
+ * cases where the source cannot supply one at all — a listener registered
299
+ * programmatically, a broker this codebase has no patterns for, or a language
300
+ * whose messaging idiom nobody has taught it yet.
301
+ *
302
+ * The node itself is the ordinary resolved `Destination`: same key, same
303
+ * `address` property, so a document and a source site that name one address on
304
+ * one broker land on ONE node and the two halves of a conversation meet. That
305
+ * is the whole point, and it is why this mints nothing of its own invention.
306
+ */
307
+ async function emitSpecDestinations(ctx, specPath) {
308
+ const read = await readAsyncApiDocuments(ctx.repoPath, specPath);
309
+ const configuredRoot = path.resolve(ctx.repoPath, specPath);
310
+ let destinations = 0;
311
+ let edges = 0;
312
+ for (const operation of read.operations) {
313
+ const nodeId = generateId('Destination', destinationNodeKey(operation.broker, operation.address));
314
+ // Runs AFTER the source pass, so a site that resolved this address already
315
+ // owns the node and keeps its own `resolution` provenance. First writer
316
+ // wins and the order is fixed, so the property is deterministic rather than
317
+ // a race — and `literal` is the more informative of the two answers anyway.
318
+ if (ctx.graph.getNode(nodeId) === undefined) {
319
+ ctx.graph.addNode({
320
+ id: nodeId,
321
+ label: 'Destination',
322
+ properties: {
323
+ name: operation.address,
324
+ // Empty for the same reason every connecting destination carries it
325
+ // empty: the node is shared, and stamping it with the document's path
326
+ // would make it collateral damage of that path's next writeback.
327
+ filePath: '',
328
+ address: operation.address,
329
+ // NOT `'specification'`, though the address did come from one. That
330
+ // value belongs to `SpringDestinationVia` and means "a CODE
331
+ // CANDIDATE was resolved through the step-4 resolver hook" — a
332
+ // different fact with a code site behind it. Reusing it would make a
333
+ // query that groups destinations by provenance unable to separate an
334
+ // address a document merely states from one a document was used to
335
+ // resolve, and the second of those is a claim about source that this
336
+ // node is not making.
337
+ resolution: 'asyncapi-document',
338
+ broker: operation.broker,
339
+ },
340
+ });
341
+ destinations += 1;
342
+ }
343
+ const sourceId = documentFileNodeId(ctx, configuredRoot, operation.documentPath);
344
+ const type = operation.action === 'receive' ? 'CONSUMES_FROM' : 'PUBLISHES_TO';
345
+ const reason = `asyncapi:${operation.operationId}`;
346
+ ctx.graph.addRelationship({
347
+ id: generateId(type, `${sourceId}->${nodeId}:${reason}`),
348
+ sourceId,
349
+ targetId: nodeId,
350
+ type,
351
+ confidence: 1.0,
352
+ reason,
353
+ });
354
+ edges += 1;
355
+ }
356
+ const stats = {
357
+ symlinksSkipped: read.symlinksSkipped,
358
+ truncated: read.truncated,
359
+ scanned: read.documentsScanned,
360
+ accepted: read.documentsAccepted,
361
+ operations: read.operations.length,
362
+ destinations,
363
+ edges,
364
+ refusalsByReason: read.refusals,
365
+ };
366
+ // Unconditional, and not `isDev`-gated like the summary below it. The tally
367
+ // above is justified on the grounds that an operator must be able to tell a
368
+ // mistyped directory from a repository with no documents — and that
369
+ // justification is only true if the operator can SEE it. A configured path
370
+ // that produced nothing is the one outcome where silence and success look
371
+ // identical from outside, which is why `spring-auto-configuration.ts` warns
372
+ // unconditionally for the same class of input.
373
+ if (stats.accepted === 0 || stats.truncated) {
374
+ // Repo-relative when the path is inside the repository, bare name when it
375
+ // is not. The same change refuses to persist this path to index metadata on
376
+ // the grounds that it would record an operator's directory layout; applying
377
+ // that reasoning to metadata and not to logs would be holding one rule in
378
+ // two places.
379
+ const resolved = path.resolve(ctx.repoPath, specPath);
380
+ const relative = path.relative(ctx.repoPath, resolved);
381
+ const reportedPath = relative !== '' && !relative.startsWith('..') && !path.isAbsolute(relative)
382
+ ? relative.split(path.sep).join('/')
383
+ : path.basename(resolved);
384
+ logger.warn({
385
+ asyncApiSpecPath: reportedPath,
386
+ ...stats,
387
+ }, stats.accepted === 0
388
+ ? '⚠️ No AsyncAPI document under the configured path yielded a destination.'
389
+ : '⚠️ AsyncAPI document reading hit a bound; the destinations below are a floor, not a total.');
390
+ }
391
+ return stats;
392
+ }
393
+ export const springDestinationsPhase = {
394
+ name: 'springDestinations',
395
+ // `parse` supplies the file list and the harvested constants; `scopeResolution`
396
+ // must have run so the Method/Function nodes exist AND so each provider's
397
+ // `applyCaptureSideChannel` has restored the messaging facts onto the main
398
+ // thread; `springConfig` must have run so the Property nodes a `${key}`
399
+ // placeholder links to are already in the graph.
400
+ deps: ['parse', 'scopeResolution', 'springConfig'],
401
+ async execute(ctx, deps) {
402
+ // `allPaths`, NOT `parsedFiles`. On any run with a storage path — which is
403
+ // every run of the CLI — worker-produced ParsedFiles are flushed to a disk
404
+ // store and `ParseOutput.parsedFiles` comes back EMPTY, with
405
+ // scope-resolution streaming them back per language. Iterating it therefore
406
+ // found nothing in production while every in-process test passed, because a
407
+ // direct pipeline call has no storage path and keeps them in memory.
408
+ //
409
+ // The fact stores are keyed by file path and are populated by the same
410
+ // streaming pass, so the path list is the right cursor for them anyway: it
411
+ // does not care how the ParsedFile got to scope resolution.
412
+ const { allPaths, moduleConstants } = getPhaseOutput(deps, 'parse');
413
+ const refusalsByReason = {};
414
+ const countRefusal = (reason) => {
415
+ refusalsByReason[reason] = (refusalsByReason[reason] ?? 0) + 1;
416
+ };
417
+ // ── Gather: facts → candidates → resolutions ──────────────────────────
418
+ const sites = [];
419
+ for (const filePath of allPaths) {
420
+ const provider = getProviderForFile(filePath);
421
+ const facts = provider?.getSpringMessagingFacts?.(filePath);
422
+ if (facts === undefined)
423
+ continue;
424
+ if (facts.handlers.length === 0 && facts.producers.length === 0)
425
+ continue;
426
+ // Built lazily and reused for the whole file: the fold state behind it is
427
+ // per-call, but resolving the provider and checking the table is not free
428
+ // and every candidate in the file wants the same closure.
429
+ const constant = makeConstantResolver(filePath, moduleConstants);
430
+ // The owning language's capability, not its name. In an interpolating
431
+ // language `"orders-$env"` is a runtime template rather than an address
432
+ // and `"${app.topic}"` is a template rather than a Spring placeholder;
433
+ // the resolver needs to know which regime it is in, and this phase must
434
+ // not learn which language that is (AGENTS.md — shared ingestion code
435
+ // plugs language behaviour in through provider hooks).
436
+ const interpolatesStringLiterals = provider?.interpolatesStringLiterals === true;
437
+ const record = (selection, ownerScopeId, ownerRange) => {
438
+ for (const refusal of selection.refusals)
439
+ countRefusal(refusal.reason);
440
+ for (const candidate of selection.candidates) {
441
+ const resolution = resolveSpringDestination(candidate, {
442
+ constant,
443
+ interpolatesStringLiterals,
444
+ });
445
+ if (resolution.kind === 'unresolved')
446
+ countRefusal(resolution.reason);
447
+ sites.push({
448
+ filePath,
449
+ ...(ownerRange === undefined ? {} : { ownerRange }),
450
+ ownerScopeId,
451
+ candidate,
452
+ resolution,
453
+ });
454
+ }
455
+ };
456
+ for (const handler of facts.handlers) {
457
+ for (const annotation of handler.annotations) {
458
+ // A Kotlin use-site target describes a generated property element, not
459
+ // the callable, so its arguments are not this handler's.
460
+ if (annotation.useSiteTarget !== undefined)
461
+ continue;
462
+ const selection = selectConsumerDestinationArguments(annotation.name, annotation.args);
463
+ if (selection === null)
464
+ continue;
465
+ record(selection, String(handler.ownerScopeId), handler.ownerRange);
466
+ }
467
+ }
468
+ for (const producer of facts.producers) {
469
+ record(selectProducerDestinationArguments(producer), String(producer.ownerScopeId), producer.ownerRange);
470
+ }
471
+ }
472
+ // Documents are read whether or not the source pass found anything, and
473
+ // that is the point of the closure rather than a straight call here: a
474
+ // repository whose messaging is invisible to the source patterns — a broker
475
+ // with no rules, a listener registered programmatically — is exactly the
476
+ // case a published document exists to cover, and an early return keyed on
477
+ // source sites would skip the documents precisely there.
478
+ //
479
+ // Always invoked AFTER the source emit, so a site that resolved an address
480
+ // owns its node first and keeps its own provenance.
481
+ const specPath = ctx.options?.asyncApiSpecPath;
482
+ const readSpecifications = async () => specPath === undefined ? undefined : emitSpecDestinations(ctx, specPath);
483
+ if (sites.length === 0) {
484
+ const specDocuments = await readSpecifications();
485
+ return {
486
+ resolvedDestinations: 0,
487
+ unresolvedDestinations: 0,
488
+ edges: 0,
489
+ refusalsByReason,
490
+ configKeyLinks: 0,
491
+ ...(specDocuments === undefined ? {} : { specDocuments }),
492
+ };
493
+ }
494
+ // ── Emit ──────────────────────────────────────────────────────────────
495
+ //
496
+ // A single pass. There used to be a preliminary one that looked for an
497
+ // address claimed by two brokers so the emit could withdraw it from both —
498
+ // the disagreement had to be known before the first node was minted,
499
+ // because re-keying a node after it has grown an edge is the kind of work
500
+ // that is easy to get half-right. With the broker in the key there is
501
+ // nothing to decide up front: each site's identity is a function of that
502
+ // site alone, so no other site can change it and no lookahead is needed.
503
+ const owners = callableOwnersByRange(ctx.graph);
504
+ const configProperties = springConfigPropertiesByKey(ctx.graph);
505
+ const linkedConfigKeys = new Set();
506
+ let resolvedDestinations = 0;
507
+ let unresolvedDestinations = 0;
508
+ let edges = 0;
509
+ let configKeyLinks = 0;
510
+ for (const site of sites) {
511
+ const { candidate, resolution } = site;
512
+ // The one predicate the rest of the loop is written against: may this
513
+ // site's node be keyed by its address, and therefore meet another site on
514
+ // it? Exactly when the address resolved. Otherwise the node gets its
515
+ // location-based key, no `address` property, and its own file.
516
+ const connects = resolution.kind === 'resolved';
517
+ const nodeId = destinationNodeId(site);
518
+ const isNew = ctx.graph.getNode(nodeId) === undefined;
519
+ if (isNew) {
520
+ if (connects)
521
+ resolvedDestinations += 1;
522
+ else
523
+ unresolvedDestinations += 1;
524
+ ctx.graph.addNode({
525
+ id: nodeId,
526
+ label: 'Destination',
527
+ properties: {
528
+ // For a resolved address this equals `address`. For an unresolved
529
+ // one it is the UNRESOLVED SPELLING, unquoted, kept so a human
530
+ // reading the node sees what the source actually said. Either way
531
+ // it is kept out of `address` unless the node connects, so nothing
532
+ // joins on it.
533
+ //
534
+ // Note that `name` is the ADDRESS, not the node key: two nodes on
535
+ // one spelling over two brokers share a `name` and differ by id.
536
+ // That is deliberate — `name` is for a human, and telling them the
537
+ // topic is called `kafka orders` would be a lie.
538
+ name: resolution.kind === 'resolved'
539
+ ? resolution.address
540
+ : destinationDisplayName(candidate.rawText),
541
+ // A CONNECTING destination carries NO location, and that is load
542
+ // bearing rather than cosmetic.
543
+ //
544
+ // It is shared by every site that names the address, so no single
545
+ // file identifies it — but more importantly, the incremental
546
+ // writeback deletes by location: `deleteNodesForFiles` issues
547
+ // `MATCH (n:<table>) WHERE n.filePath IN [...] DETACH DELETE n` for
548
+ // every changed file. Stamping the first-seen file here would make
549
+ // a shared destination collateral damage whenever THAT file
550
+ // changed, and DETACH DELETE would take its edges from every OTHER
551
+ // file with it. Those files are not in the write set, so their
552
+ // edges would never be rebuilt: a publisher and a subscriber that
553
+ // genuinely agree on an address would silently stop being
554
+ // connected, depending on which of them the indexer happened to
555
+ // walk first. Omitting the property makes the `IN` predicate unable
556
+ // to match, so the node survives the writeback and every referrer
557
+ // keeps its edge.
558
+ //
559
+ // The cost used to be the opposite error — a destination whose
560
+ // last referrer was deleted lingered as an edgeless orphan until a
561
+ // full rebuild — and that is no longer paid. Because the per-file
562
+ // predicate can neither remove such a node nor admit a newly
563
+ // introduced one, the whole layer is instead delete-alled
564
+ // (`deleteAllDestinations`) and re-included graph-wide
565
+ // (`isGraphWideNode`) on every incremental writeback. Both halves
566
+ // move together: the delete without the re-include drops the layer,
567
+ // and the re-include without the delete duplicates every edge.
568
+ //
569
+ // A NON-CONNECTING destination is the opposite case — it belongs to
570
+ // exactly one site, its id already says so, and it SHOULD be
571
+ // deleted and re-created with its file.
572
+ // `''`, not absent: `NodeProperties.filePath` is required, and the
573
+ // empty string is the established spelling for a node with no file
574
+ // (`pipeline-phases/communities.ts` does the same). It is equally
575
+ // unmatchable by the `IN` predicate and the CSV writes it as an
576
+ // empty field, which COPY loads as NULL.
577
+ ...(connects
578
+ ? { filePath: '' }
579
+ : {
580
+ filePath: site.filePath,
581
+ ...(site.ownerRange === undefined
582
+ ? {}
583
+ : {
584
+ startLine: site.ownerRange.startLine - 1,
585
+ endLine: site.ownerRange.endLine - 1,
586
+ }),
587
+ }),
588
+ // `address` IS THE JOIN KEY and is written ONLY when the node
589
+ // connects. See the module header: an absent property cannot match
590
+ // another absent property, so the structural guarantee survives
591
+ // being read back out of the database.
592
+ //
593
+ // `resolution` always says how the node got here — the provenance
594
+ // of a real address, or the named refusal that stopped one. Every
595
+ // value in the column now comes from the resolver's own closed
596
+ // vocabulary, because the phase no longer has a verdict of its own
597
+ // to record: nothing is withdrawn here.
598
+ ...(resolution.kind === 'resolved'
599
+ ? { address: resolution.address, resolution: resolution.via }
600
+ : { resolution: resolution.reason }),
601
+ ...(resolution.kind === 'unresolved' && resolution.configKey !== undefined
602
+ ? { configKey: resolution.configKey }
603
+ : {}),
604
+ // The `${key:default}` default text. Kept because the source wrote
605
+ // it and throwing it away would make an overridable default
606
+ // indistinguishable from a bare `${key}`; NOT an address and never
607
+ // part of the id, because configuration can override it and this
608
+ // graph cannot see whether it did.
609
+ ...(resolution.kind === 'unresolved' && resolution.configDefault !== undefined
610
+ ? { configDefault: resolution.configDefault }
611
+ : {}),
612
+ broker: candidate.broker,
613
+ },
614
+ });
615
+ }
616
+ // Link a placeholder's KEY to the configuration entries that could supply
617
+ // it — `${key}` and `${key:default}` alike, since a default changes
618
+ // nothing about where the real value comes from. This is PROVENANCE, not
619
+ // resolution: the node stays unresolved and keeps its location-based id
620
+ // even when Property nodes are found, because the VALUE is still not in
621
+ // the graph and letting a Property sighting upgrade the node would
622
+ // reintroduce the false connection the keying rule exists to prevent.
623
+ // `${}` names no key at all and reports none, so nothing is ever looked
624
+ // up under the empty string.
625
+ if (resolution.kind === 'unresolved' && resolution.configKey !== undefined) {
626
+ for (const propertyId of configProperties.get(resolution.configKey) ?? []) {
627
+ const linkId = `${nodeId}->${propertyId}`;
628
+ if (linkedConfigKeys.has(linkId))
629
+ continue;
630
+ linkedConfigKeys.add(linkId);
631
+ ctx.graph.addRelationship({
632
+ id: generateId('USES', linkId),
633
+ sourceId: nodeId,
634
+ targetId: propertyId,
635
+ type: 'USES',
636
+ confidence: 1.0,
637
+ reason: `spring-destination:config-key:${resolution.configKey}`,
638
+ });
639
+ configKeyLinks += 1;
640
+ }
641
+ }
642
+ // One edge per address. An array-valued `topics` really does subscribe to
643
+ // several places, and each gets its own edge rather than a group node, so
644
+ // "who reads from `a`" stays one hop. `reason` carries which argument and
645
+ // which element it came from.
646
+ const type = candidate.role === 'consumer' ? 'CONSUMES_FROM' : 'PUBLISHES_TO';
647
+ const owner = site.ownerRange === undefined
648
+ ? undefined
649
+ : (owners.get(ownerKey(site.filePath, site.ownerRange)) ?? undefined);
650
+ // Prefer the callable; fall back to its File when the owner is unknown or
651
+ // ambiguous. Unlike `routes.ts` this does NOT emit both — there is no
652
+ // legacy File-level consumer to keep working here, and a second edge per
653
+ // publish would double the async surface of the graph for no query.
654
+ const sourceId = owner?.id ?? generateId('File', site.filePath);
655
+ if (owner === undefined && ctx.graph.getNode(sourceId) === undefined)
656
+ continue;
657
+ const reason = edgeReason(candidate);
658
+ ctx.graph.addRelationship({
659
+ id: generateId(type, `${sourceId}->${nodeId}:${reason}`),
660
+ sourceId,
661
+ targetId: nodeId,
662
+ type,
663
+ confidence: 1.0,
664
+ reason,
665
+ });
666
+ edges += 1;
667
+ }
668
+ const specDocuments = await readSpecifications();
669
+ if (isDev) {
670
+ const fromSpec = specDocuments === undefined
671
+ ? ''
672
+ : `, +${specDocuments.destinations} from ${specDocuments.accepted} document(s)`;
673
+ // The breakdown, not just the totals. The unresolved FRACTION is the
674
+ // number this feature is judged on, and a bare count of unresolved
675
+ // destinations says how big the gap is without saying what would close
676
+ // it — which is the only question an operator can act on.
677
+ logger.info({ refusalsByReason, ...(specDocuments === undefined ? {} : { specDocuments }) }, `📮 Spring destinations: ${resolvedDestinations} resolved, ${unresolvedDestinations} unresolved, ${edges} edges${fromSpec}`);
678
+ }
679
+ return {
680
+ resolvedDestinations,
681
+ unresolvedDestinations,
682
+ edges,
683
+ refusalsByReason,
684
+ configKeyLinks,
685
+ ...(specDocuments === undefined ? {} : { specDocuments }),
686
+ };
687
+ },
688
+ };