gitnexus 1.6.8-rc.9 → 1.6.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (430) hide show
  1. package/README.md +3 -3
  2. package/dist/_shared/graph/types.d.ts +31 -1
  3. package/dist/_shared/graph/types.d.ts.map +1 -1
  4. package/dist/_shared/language-detection.d.ts.map +1 -1
  5. package/dist/_shared/language-detection.js +11 -1
  6. package/dist/_shared/language-detection.js.map +1 -1
  7. package/dist/_shared/lbug/schema-constants.d.ts +1 -1
  8. package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
  9. package/dist/_shared/lbug/schema-constants.js +6 -0
  10. package/dist/_shared/lbug/schema-constants.js.map +1 -1
  11. package/dist/_shared/scope-resolution/parsed-file.d.ts +21 -0
  12. package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -1
  13. package/dist/_shared/scope-resolution/symbol-definition.d.ts +6 -0
  14. package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
  15. package/dist/cli/ai-context.d.ts +31 -1
  16. package/dist/cli/ai-context.js +18 -16
  17. package/dist/cli/analyze-config.js +11 -0
  18. package/dist/cli/analyze.d.ts +14 -0
  19. package/dist/cli/analyze.js +98 -0
  20. package/dist/cli/embedding-dims.d.ts +33 -0
  21. package/dist/cli/embedding-dims.js +39 -0
  22. package/dist/cli/eval-server.d.ts +8 -0
  23. package/dist/cli/eval-server.js +242 -2
  24. package/dist/cli/help-i18n.js +19 -0
  25. package/dist/cli/i18n/en.d.ts +15 -1
  26. package/dist/cli/i18n/en.js +15 -1
  27. package/dist/cli/i18n/resources.d.ts +29 -1
  28. package/dist/cli/i18n/zh-CN.d.ts +14 -0
  29. package/dist/cli/i18n/zh-CN.js +15 -1
  30. package/dist/cli/index.js +89 -1
  31. package/dist/cli/mcp.d.ts +6 -1
  32. package/dist/cli/mcp.js +26 -1
  33. package/dist/cli/optional-grammars.d.ts +6 -8
  34. package/dist/cli/optional-grammars.js +8 -11
  35. package/dist/cli/setup.d.ts +3 -1
  36. package/dist/cli/setup.js +67 -17
  37. package/dist/cli/skill-gen.js +2 -1
  38. package/dist/cli/tool.d.ts +19 -1
  39. package/dist/cli/tool.js +96 -3
  40. package/dist/core/embeddings/hf-env.d.ts +0 -66
  41. package/dist/core/embeddings/http-client.d.ts +7 -0
  42. package/dist/core/embeddings/http-client.js +5 -3
  43. package/dist/core/embeddings/server-mapping.d.ts +1 -1
  44. package/dist/core/embeddings/server-mapping.js +7 -3
  45. package/dist/core/graph/import-cycles.d.ts +10 -0
  46. package/dist/core/graph/import-cycles.js +103 -0
  47. package/dist/core/group/cross-impact.d.ts +2 -1
  48. package/dist/core/group/cross-impact.js +5 -1
  49. package/dist/core/group/extractors/grpc-patterns/proto.js +10 -6
  50. package/dist/core/group/extractors/http-patterns/kotlin.js +7 -7
  51. package/dist/core/group/extractors/http-route-extractor.d.ts +24 -0
  52. package/dist/core/group/extractors/http-route-extractor.js +22 -4
  53. package/dist/core/group/extractors/include-extractor.js +22 -13
  54. package/dist/core/group/sync.d.ts +27 -1
  55. package/dist/core/group/sync.js +0 -0
  56. package/dist/core/incremental/subgraph-extract.js +18 -1
  57. package/dist/core/ingestion/cfg/cfg-builder.d.ts +114 -0
  58. package/dist/core/ingestion/cfg/cfg-builder.js +196 -0
  59. package/dist/core/ingestion/cfg/collect.d.ts +51 -0
  60. package/dist/core/ingestion/cfg/collect.js +77 -0
  61. package/dist/core/ingestion/cfg/control-dependence.d.ts +58 -0
  62. package/dist/core/ingestion/cfg/control-dependence.js +182 -0
  63. package/dist/core/ingestion/cfg/control-flow-context.d.ts +106 -0
  64. package/dist/core/ingestion/cfg/control-flow-context.js +124 -0
  65. package/dist/core/ingestion/cfg/emit.d.ts +293 -0
  66. package/dist/core/ingestion/cfg/emit.js +646 -0
  67. package/dist/core/ingestion/cfg/post-dominators.d.ts +79 -0
  68. package/dist/core/ingestion/cfg/post-dominators.js +176 -0
  69. package/dist/core/ingestion/cfg/reaching-def-reason-codec.d.ts +106 -0
  70. package/dist/core/ingestion/cfg/reaching-def-reason-codec.js +133 -0
  71. package/dist/core/ingestion/cfg/reaching-defs-graph.d.ts +18 -0
  72. package/dist/core/ingestion/cfg/reaching-defs-graph.js +312 -0
  73. package/dist/core/ingestion/cfg/reaching-defs.d.ts +162 -0
  74. package/dist/core/ingestion/cfg/reaching-defs.js +785 -0
  75. package/dist/core/ingestion/cfg/synthetic-escape.d.ts +124 -0
  76. package/dist/core/ingestion/cfg/synthetic-escape.js +300 -0
  77. package/dist/core/ingestion/cfg/traversal-result.d.ts +20 -0
  78. package/dist/core/ingestion/cfg/traversal-result.js +2 -0
  79. package/dist/core/ingestion/cfg/types.d.ts +274 -0
  80. package/dist/core/ingestion/cfg/types.js +13 -0
  81. package/dist/core/ingestion/cfg/visitors/c-cpp-harvest.d.ts +111 -0
  82. package/dist/core/ingestion/cfg/visitors/c-cpp-harvest.js +541 -0
  83. package/dist/core/ingestion/cfg/visitors/c-cpp.d.ts +74 -0
  84. package/dist/core/ingestion/cfg/visitors/c-cpp.js +584 -0
  85. package/dist/core/ingestion/cfg/visitors/call-site-harvest.d.ts +195 -0
  86. package/dist/core/ingestion/cfg/visitors/call-site-harvest.js +350 -0
  87. package/dist/core/ingestion/cfg/visitors/csharp-harvest.d.ts +124 -0
  88. package/dist/core/ingestion/cfg/visitors/csharp-harvest.js +589 -0
  89. package/dist/core/ingestion/cfg/visitors/csharp.d.ts +87 -0
  90. package/dist/core/ingestion/cfg/visitors/csharp.js +867 -0
  91. package/dist/core/ingestion/cfg/visitors/dart-harvest.d.ts +278 -0
  92. package/dist/core/ingestion/cfg/visitors/dart-harvest.js +870 -0
  93. package/dist/core/ingestion/cfg/visitors/dart.d.ts +119 -0
  94. package/dist/core/ingestion/cfg/visitors/dart.js +836 -0
  95. package/dist/core/ingestion/cfg/visitors/go-harvest.d.ts +160 -0
  96. package/dist/core/ingestion/cfg/visitors/go-harvest.js +621 -0
  97. package/dist/core/ingestion/cfg/visitors/go.d.ts +108 -0
  98. package/dist/core/ingestion/cfg/visitors/go.js +638 -0
  99. package/dist/core/ingestion/cfg/visitors/java-harvest.d.ts +108 -0
  100. package/dist/core/ingestion/cfg/visitors/java-harvest.js +513 -0
  101. package/dist/core/ingestion/cfg/visitors/java.d.ts +101 -0
  102. package/dist/core/ingestion/cfg/visitors/java.js +812 -0
  103. package/dist/core/ingestion/cfg/visitors/kotlin-harvest.d.ts +253 -0
  104. package/dist/core/ingestion/cfg/visitors/kotlin-harvest.js +719 -0
  105. package/dist/core/ingestion/cfg/visitors/kotlin.d.ts +121 -0
  106. package/dist/core/ingestion/cfg/visitors/kotlin.js +809 -0
  107. package/dist/core/ingestion/cfg/visitors/php-harvest.d.ts +172 -0
  108. package/dist/core/ingestion/cfg/visitors/php-harvest.js +626 -0
  109. package/dist/core/ingestion/cfg/visitors/php.d.ts +96 -0
  110. package/dist/core/ingestion/cfg/visitors/php.js +721 -0
  111. package/dist/core/ingestion/cfg/visitors/python-harvest.d.ts +212 -0
  112. package/dist/core/ingestion/cfg/visitors/python-harvest.js +739 -0
  113. package/dist/core/ingestion/cfg/visitors/python.d.ts +103 -0
  114. package/dist/core/ingestion/cfg/visitors/python.js +558 -0
  115. package/dist/core/ingestion/cfg/visitors/ruby-harvest.d.ts +223 -0
  116. package/dist/core/ingestion/cfg/visitors/ruby-harvest.js +587 -0
  117. package/dist/core/ingestion/cfg/visitors/ruby.d.ts +112 -0
  118. package/dist/core/ingestion/cfg/visitors/ruby.js +756 -0
  119. package/dist/core/ingestion/cfg/visitors/rust-harvest.d.ts +324 -0
  120. package/dist/core/ingestion/cfg/visitors/rust-harvest.js +873 -0
  121. package/dist/core/ingestion/cfg/visitors/rust.d.ts +95 -0
  122. package/dist/core/ingestion/cfg/visitors/rust.js +558 -0
  123. package/dist/core/ingestion/cfg/visitors/scope-tree-harvest.d.ts +87 -0
  124. package/dist/core/ingestion/cfg/visitors/scope-tree-harvest.js +116 -0
  125. package/dist/core/ingestion/cfg/visitors/swift-harvest.d.ts +255 -0
  126. package/dist/core/ingestion/cfg/visitors/swift-harvest.js +679 -0
  127. package/dist/core/ingestion/cfg/visitors/swift.d.ts +116 -0
  128. package/dist/core/ingestion/cfg/visitors/swift.js +787 -0
  129. package/dist/core/ingestion/cfg/visitors/typescript-harvest.d.ts +149 -0
  130. package/dist/core/ingestion/cfg/visitors/typescript-harvest.js +1056 -0
  131. package/dist/core/ingestion/cfg/visitors/typescript.d.ts +66 -0
  132. package/dist/core/ingestion/cfg/visitors/typescript.js +583 -0
  133. package/dist/core/ingestion/import-resolvers/utils.js +2 -0
  134. package/dist/core/ingestion/language-provider.d.ts +11 -0
  135. package/dist/core/ingestion/languages/c-cpp.js +6 -1
  136. package/dist/core/ingestion/languages/cobol.js +4 -0
  137. package/dist/core/ingestion/languages/cpp/arity-metadata.js +41 -2
  138. package/dist/core/ingestion/languages/cpp/captures.js +50 -1
  139. package/dist/core/ingestion/languages/cpp/conversion-rank.d.ts +2 -0
  140. package/dist/core/ingestion/languages/cpp/conversion-rank.js +89 -0
  141. package/dist/core/ingestion/languages/cpp/header-scan.d.ts +1 -1
  142. package/dist/core/ingestion/languages/cpp/header-scan.js +2 -2
  143. package/dist/core/ingestion/languages/cpp/inline-namespaces.js +7 -2
  144. package/dist/core/ingestion/languages/cpp/member-lookup.js +2 -1
  145. package/dist/core/ingestion/languages/cpp/query.js +23 -0
  146. package/dist/core/ingestion/languages/cpp/scope-resolver.js +2 -1
  147. package/dist/core/ingestion/languages/csharp.js +2 -0
  148. package/dist/core/ingestion/languages/dart.js +2 -0
  149. package/dist/core/ingestion/languages/go.js +2 -0
  150. package/dist/core/ingestion/languages/java.js +3 -0
  151. package/dist/core/ingestion/languages/kotlin/query.js +3 -2
  152. package/dist/core/ingestion/languages/kotlin.js +3 -0
  153. package/dist/core/ingestion/languages/php.js +2 -0
  154. package/dist/core/ingestion/languages/python.js +2 -0
  155. package/dist/core/ingestion/languages/ruby.js +2 -0
  156. package/dist/core/ingestion/languages/rust.js +2 -0
  157. package/dist/core/ingestion/languages/swift.js +2 -0
  158. package/dist/core/ingestion/languages/typescript.js +5 -0
  159. package/dist/core/ingestion/languages/vue.js +6 -0
  160. package/dist/core/ingestion/method-extractors/configs/c-cpp.js +6 -13
  161. package/dist/core/ingestion/method-extractors/generic.js +1 -0
  162. package/dist/core/ingestion/method-types.d.ts +2 -0
  163. package/dist/core/ingestion/model/symbol-table.d.ts +1 -0
  164. package/dist/core/ingestion/model/symbol-table.js +1 -0
  165. package/dist/core/ingestion/mro-processor.js +109 -0
  166. package/dist/core/ingestion/parsing-processor.js +23 -0
  167. package/dist/core/ingestion/pipeline-phases/call-summaries.d.ts +26 -0
  168. package/dist/core/ingestion/pipeline-phases/call-summaries.js +46 -0
  169. package/dist/core/ingestion/pipeline-phases/index.d.ts +2 -0
  170. package/dist/core/ingestion/pipeline-phases/index.js +2 -0
  171. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +0 -21
  172. package/dist/core/ingestion/pipeline-phases/parse-impl.js +18 -1
  173. package/dist/core/ingestion/pipeline-phases/routes.d.ts +10 -0
  174. package/dist/core/ingestion/pipeline-phases/routes.js +30 -1
  175. package/dist/core/ingestion/pipeline-phases/taint-summaries.d.ts +30 -0
  176. package/dist/core/ingestion/pipeline-phases/taint-summaries.js +81 -0
  177. package/dist/core/ingestion/pipeline.d.ts +92 -10
  178. package/dist/core/ingestion/pipeline.js +11 -2
  179. package/dist/core/ingestion/scope-extractor.js +12 -2
  180. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +9 -0
  181. package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.d.ts +75 -0
  182. package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.js +67 -0
  183. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +12 -2
  184. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +14 -2
  185. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +6 -1
  186. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +13 -1
  187. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +9 -1
  188. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +28 -2
  189. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +4 -0
  190. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +9 -0
  191. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +6 -1
  192. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +174 -40
  193. package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +24 -0
  194. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +276 -190
  195. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +43 -3
  196. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +75 -0
  197. package/dist/core/ingestion/scope-resolution/pipeline/run.js +349 -1
  198. package/dist/core/ingestion/scope-resolution/resolution-outcome.d.ts +1 -1
  199. package/dist/core/ingestion/taint/call-summary-codec.d.ts +75 -0
  200. package/dist/core/ingestion/taint/call-summary-codec.js +128 -0
  201. package/dist/core/ingestion/taint/call-summary-emit.d.ts +43 -0
  202. package/dist/core/ingestion/taint/call-summary-emit.js +70 -0
  203. package/dist/core/ingestion/taint/call-summary-harvest.d.ts +77 -0
  204. package/dist/core/ingestion/taint/call-summary-harvest.js +179 -0
  205. package/dist/core/ingestion/taint/call-summary-model.d.ts +47 -0
  206. package/dist/core/ingestion/taint/call-summary-model.js +25 -0
  207. package/dist/core/ingestion/taint/emit.d.ts +125 -0
  208. package/dist/core/ingestion/taint/emit.js +208 -0
  209. package/dist/core/ingestion/taint/interproc-emit.d.ts +50 -0
  210. package/dist/core/ingestion/taint/interproc-emit.js +88 -0
  211. package/dist/core/ingestion/taint/interproc-solver.d.ts +110 -0
  212. package/dist/core/ingestion/taint/interproc-solver.js +312 -0
  213. package/dist/core/ingestion/taint/match.d.ts +153 -0
  214. package/dist/core/ingestion/taint/match.js +278 -0
  215. package/dist/core/ingestion/taint/path-codec.d.ts +134 -0
  216. package/dist/core/ingestion/taint/path-codec.js +190 -0
  217. package/dist/core/ingestion/taint/propagate.d.ts +216 -0
  218. package/dist/core/ingestion/taint/propagate.js +659 -0
  219. package/dist/core/ingestion/taint/site-safety.d.ts +29 -0
  220. package/dist/core/ingestion/taint/site-safety.js +98 -0
  221. package/dist/core/ingestion/taint/source-sink-config.d.ts +107 -23
  222. package/dist/core/ingestion/taint/source-sink-config.js +35 -12
  223. package/dist/core/ingestion/taint/source-sink-registry.d.ts +6 -4
  224. package/dist/core/ingestion/taint/source-sink-registry.js +6 -4
  225. package/dist/core/ingestion/taint/summary-harvest-driver.d.ts +80 -0
  226. package/dist/core/ingestion/taint/summary-harvest-driver.js +179 -0
  227. package/dist/core/ingestion/taint/summary-harvest.d.ts +89 -0
  228. package/dist/core/ingestion/taint/summary-harvest.js +537 -0
  229. package/dist/core/ingestion/taint/summary-model.d.ts +200 -0
  230. package/dist/core/ingestion/taint/summary-model.js +86 -0
  231. package/dist/core/ingestion/taint/typescript-model.d.ts +38 -0
  232. package/dist/core/ingestion/taint/typescript-model.js +102 -0
  233. package/dist/core/ingestion/type-extractors/c-cpp.js +21 -0
  234. package/dist/core/ingestion/utils/env.d.ts +7 -0
  235. package/dist/core/ingestion/utils/env.js +12 -0
  236. package/dist/core/ingestion/utils/method-props.js +1 -0
  237. package/dist/core/ingestion/workers/parse-worker.d.ts +13 -0
  238. package/dist/core/ingestion/workers/parse-worker.js +76 -17
  239. package/dist/core/ingestion/workers/worker-pool.d.ts +9 -40
  240. package/dist/core/ingestion/workers/worker-pool.js +15 -2
  241. package/dist/core/lbug/csv-generator.d.ts +51 -3
  242. package/dist/core/lbug/csv-generator.js +369 -255
  243. package/dist/core/lbug/lbug-adapter.d.ts +58 -18
  244. package/dist/core/lbug/lbug-adapter.js +346 -84
  245. package/dist/core/lbug/lbug-config.d.ts +19 -0
  246. package/dist/core/lbug/lbug-config.js +29 -0
  247. package/dist/core/lbug/pdg-emit-sink.d.ts +127 -0
  248. package/dist/core/lbug/pdg-emit-sink.js +358 -0
  249. package/dist/core/lbug/pool-adapter.d.ts +35 -0
  250. package/dist/core/lbug/pool-adapter.js +90 -1
  251. package/dist/core/lbug/rel-pair-routing.d.ts +54 -0
  252. package/dist/core/lbug/rel-pair-routing.js +140 -0
  253. package/dist/core/lbug/schema.d.ts +2 -2
  254. package/dist/core/lbug/schema.js +3 -0
  255. package/dist/core/logger.d.ts +0 -5
  256. package/dist/core/run-analyze.d.ts +96 -0
  257. package/dist/core/run-analyze.js +207 -3
  258. package/dist/core/tree-sitter/parser-loader.js +5 -4
  259. package/dist/core/tree-sitter/safe-parse.d.ts +0 -5
  260. package/dist/core/tree-sitter/vendored-grammars.d.ts +39 -0
  261. package/dist/core/tree-sitter/vendored-grammars.js +57 -0
  262. package/dist/core/wiki/generator.js +3 -1
  263. package/dist/core/wiki/graph-queries.d.ts +6 -0
  264. package/dist/core/wiki/graph-queries.js +9 -1
  265. package/dist/mcp/http-transport.d.ts +134 -0
  266. package/dist/mcp/http-transport.js +503 -0
  267. package/dist/mcp/local/local-backend.d.ts +110 -24
  268. package/dist/mcp/local/local-backend.js +1225 -19
  269. package/dist/mcp/local/pdg-impact.d.ts +384 -0
  270. package/dist/mcp/local/pdg-impact.js +1972 -0
  271. package/dist/mcp/resources.js +13 -1
  272. package/dist/mcp/tools.d.ts +12 -0
  273. package/dist/mcp/tools.js +214 -7
  274. package/dist/server/analyze-launch.d.ts +29 -0
  275. package/dist/server/analyze-launch.js +137 -0
  276. package/dist/server/analyze-upload.d.ts +33 -0
  277. package/dist/server/analyze-upload.js +123 -0
  278. package/dist/server/analyze-worker.d.ts +17 -1
  279. package/dist/server/api.d.ts +17 -0
  280. package/dist/server/api.js +132 -168
  281. package/dist/server/git-clone.d.ts +43 -2
  282. package/dist/server/git-clone.js +211 -16
  283. package/dist/server/mcp-http.d.ts +5 -6
  284. package/dist/server/mcp-http.js +9 -82
  285. package/dist/server/middleware.d.ts +40 -0
  286. package/dist/server/middleware.js +92 -0
  287. package/dist/server/private-ip.d.ts +1 -0
  288. package/dist/server/private-ip.js +15 -0
  289. package/dist/server/upload-ingest.d.ts +56 -0
  290. package/dist/server/upload-ingest.js +276 -0
  291. package/dist/server/upload-paths.d.ts +37 -0
  292. package/dist/server/upload-paths.js +57 -0
  293. package/dist/server/upload-sweep.d.ts +20 -0
  294. package/dist/server/upload-sweep.js +57 -0
  295. package/dist/storage/git.js +5 -8
  296. package/dist/storage/parse-cache.d.ts +22 -1
  297. package/dist/storage/parse-cache.js +41 -10
  298. package/dist/storage/repo-manager.d.ts +121 -10
  299. package/dist/storage/repo-manager.js +43 -5
  300. package/dist/types/pipeline.d.ts +9 -0
  301. package/hooks/antigravity/gitnexus-antigravity-hook.cjs +110 -10
  302. package/hooks/claude/gitnexus-hook.cjs +110 -13
  303. package/hooks/claude/hook-db-lock-probe.cjs +513 -26
  304. package/package.json +8 -2
  305. package/scripts/assert-publish-grammar-coverage.cjs +31 -0
  306. package/scripts/build-tree-sitter-grammars.cjs +16 -10
  307. package/scripts/cross-platform-tests.ts +1 -0
  308. package/skills/gitnexus-debugging.md +16 -4
  309. package/skills/gitnexus-exploring.md +3 -3
  310. package/skills/gitnexus-guide.md +33 -0
  311. package/skills/gitnexus-pdg-query.md +89 -0
  312. package/skills/gitnexus-refactoring.md +1 -1
  313. package/skills/gitnexus-taint-analysis.md +178 -0
  314. package/vendor/tree-sitter-c/package.json +1 -1
  315. package/vendor/tree-sitter-dart/package.json +1 -1
  316. package/vendor/tree-sitter-kotlin/package.json +1 -1
  317. package/vendor/tree-sitter-proto/package.json +1 -1
  318. package/vendor/tree-sitter-swift/package.json +1 -1
  319. package/web/assets/{agent-CKPMqImC.js → agent-D3Xd7zZE.js} +123 -123
  320. package/web/assets/architecture-7EHR7CIX-6QZW5X65-DxJw65fT.js +1 -0
  321. package/web/assets/architectureDiagram-UL44E2DR-ChH9xW-N.js +36 -0
  322. package/web/assets/{blockDiagram-7IZFK4PR-B4niSLvR.js → blockDiagram-7IZFK4PR-BkWhpzWB.js} +1 -1
  323. package/web/assets/{c4Diagram-Y2BXMSZH-DIzEXalR.js → c4Diagram-Y2BXMSZH-DtBvpzWD.js} +1 -1
  324. package/web/assets/{chunk-3SSMPTDK-BzGeeheI.js → chunk-3SSMPTDK-B4_etUhr.js} +2 -2
  325. package/web/assets/{chunk-5IMINLNL-CnTLsXIV.js → chunk-5IMINLNL-DOdTeQri.js} +1 -1
  326. package/web/assets/{chunk-5VCL7Z4A-w1BCx98Z.js → chunk-5VCL7Z4A-CQ-2dYWj.js} +1 -1
  327. package/web/assets/chunk-6764PJDD-DpIS7hxD.js +1 -0
  328. package/web/assets/{chunk-67TQ5CYL-CChC2cAv.js → chunk-67TQ5CYL-4sNPJU1q.js} +3 -3
  329. package/web/assets/{chunk-7W6UQGC5-CgFyfZ6k.js → chunk-7W6UQGC5-KyEG0HQg.js} +1 -1
  330. package/web/assets/{chunk-AZZRMDJM-5E9ARdDQ.js → chunk-AZZRMDJM-y-xJBuMz.js} +1 -1
  331. package/web/assets/{chunk-INKRHTLW-ELpfuk9h.js → chunk-INKRHTLW-Nu4ri9P-.js} +1 -1
  332. package/web/assets/{chunk-JQRUD6KW-Dcq_Qnyy.js → chunk-JQRUD6KW-Dbva2Z17.js} +1 -1
  333. package/web/assets/{chunk-KGFNY3KK-W-VR57xb.js → chunk-KGFNY3KK-JGMJvKCK.js} +1 -1
  334. package/web/assets/chunk-KGYTTC2M-CWC_c3H9.js +161 -0
  335. package/web/assets/{chunk-KNLZD3CH-BuZBOcso.js → chunk-KNLZD3CH-BGlP6X9R.js} +1 -1
  336. package/web/assets/{chunk-KRXBNO2N-DH2NPM7I.js → chunk-KRXBNO2N-BW5o0KUN.js} +1 -1
  337. package/web/assets/{chunk-LCXTWHL2-Dfmux4m1.js → chunk-LCXTWHL2-zoomRtWX.js} +1 -1
  338. package/web/assets/{chunk-LII3EMHJ-3X33tCpU.js → chunk-LII3EMHJ-D-jm-dLa.js} +1 -1
  339. package/web/assets/{chunk-QA3QBVWF-D5Ha3GgB.js → chunk-QA3QBVWF-BgRW3SXd.js} +1 -1
  340. package/web/assets/{chunk-RG4AUYOV-CAkzcoRj.js → chunk-RG4AUYOV-DD0B2N2B.js} +1 -1
  341. package/web/assets/{chunk-T2UQINTJ-kGYG7ppP.js → chunk-T2UQINTJ-DSBqkzH7.js} +1 -1
  342. package/web/assets/chunk-T5OCTHI4-DdZvN-9i.js +1 -0
  343. package/web/assets/{chunk-UY5QBCOK-Du-c56gs.js → chunk-UY5QBCOK-DyF0vyGd.js} +1 -1
  344. package/web/assets/{chunk-W44A43WB-lrCpuupA.js → chunk-W44A43WB-JqMLIpR0.js} +1 -1
  345. package/web/assets/{chunk-ZXARS5L4-CSZRiyOf.js → chunk-ZXARS5L4-CxN8oiwI.js} +1 -1
  346. package/web/assets/classDiagram-KGZ6W3CR-DJm9BgF-.js +1 -0
  347. package/web/assets/classDiagram-v2-72OJOZXJ-DJm9BgF-.js +1 -0
  348. package/web/assets/{context-builder-_HS0v2ma.js → context-builder-BHiFUA8O.js} +10 -2
  349. package/web/assets/{cose-bilkent-UX7MHV2Q-BHv2bbtl.js → cose-bilkent-UX7MHV2Q-D71wNYRJ.js} +1 -1
  350. package/web/assets/dagre-ND4H6XIP-BIkxmhcu.js +4 -0
  351. package/web/assets/diagram-3NCE3AQN-BTa6iH0T.js +43 -0
  352. package/web/assets/diagram-GF46GFSD-DZDV0vBj.js +24 -0
  353. package/web/assets/{diagram-HNR7UZ2L-fUWreZzx.js → diagram-HNR7UZ2L-DFCfd5LI.js} +2 -2
  354. package/web/assets/diagram-QXG6HAR7-BzM--Jj0.js +24 -0
  355. package/web/assets/diagram-WEQXMOUZ-DFFwz0k4.js +10 -0
  356. package/web/assets/{erDiagram-L5TCEMPS-QN2eEP1e.js → erDiagram-L5TCEMPS-CfHMJJqo.js} +1 -1
  357. package/web/assets/eventmodeling-FCH6USID-MREXMVOE-BQm9QzEa.js +1 -0
  358. package/web/assets/{flowDiagram-H6V6AXG4-CXbXImlN.js → flowDiagram-H6V6AXG4-Be805QiJ.js} +3 -3
  359. package/web/assets/{ganttDiagram-JCBTUEKG-BHQssTz6.js → ganttDiagram-JCBTUEKG-D3iL2Aet.js} +1 -1
  360. package/web/assets/gitGraph-WXDBUCRP-R675I2BI-Cr_Bm2Nb.js +1 -0
  361. package/web/assets/gitGraphDiagram-S2ZK5IYY-CPIDgxGm.js +106 -0
  362. package/web/assets/index-BvYev1UJ.js +635 -0
  363. package/web/assets/index-_lgn7hs5.css +2 -0
  364. package/web/assets/info-J43DQDTF-KCYPFFUO-Cs4p2oyk.js +1 -0
  365. package/web/assets/infoDiagram-3YFTVSEB-Y7BKSoke.js +2 -0
  366. package/web/assets/{ishikawaDiagram-BNXS4ZKH-6LKOvBfp.js → ishikawaDiagram-BNXS4ZKH-CT4SHFuI.js} +1 -1
  367. package/web/assets/{journeyDiagram-M6C3CM3L-DNPYCpvq.js → journeyDiagram-M6C3CM3L-CvArszLo.js} +1 -1
  368. package/web/assets/{kanban-definition-75IXJCU3-DgDi9oJT.js → kanban-definition-75IXJCU3-C6yDxr7N.js} +3 -3
  369. package/web/assets/{katex-K3KEBU37-CrVLz8l7.js → katex-K3KEBU37-CTc5BslQ.js} +1 -1
  370. package/web/assets/{mindmap-definition-2TDM6QVE-CwR5sBB-.js → mindmap-definition-2TDM6QVE-BOc2WEne.js} +1 -1
  371. package/web/assets/packet-YPE3B663-LP52Z2RK-CKCrztD2.js +1 -0
  372. package/web/assets/pie-LRSECV5Y-TCRJHUBD-BB7pHqoD.js +1 -0
  373. package/web/assets/{pieDiagram-CU6KROY3-By8g6f6B.js → pieDiagram-CU6KROY3-Cvc0f_Pd.js} +1 -1
  374. package/web/assets/{quadrantDiagram-VICAPDV7-bAm6Hi_5.js → quadrantDiagram-VICAPDV7-yXSZ2lLz.js} +1 -1
  375. package/web/assets/radar-GUYGQ44K-RDLRG3WG-DhoTOcuK.js +1 -0
  376. package/web/assets/{requirementDiagram-JXO7QTGE-7oDcJ1_J.js → requirementDiagram-JXO7QTGE-BRcZvHs4.js} +1 -1
  377. package/web/assets/{sankeyDiagram-URQDO5SZ-LFJz53u5.js → sankeyDiagram-URQDO5SZ-1qEjlFIB.js} +1 -1
  378. package/web/assets/{sequenceDiagram-VS2MUI6T-FySeKCUy.js → sequenceDiagram-VS2MUI6T-PcSCuvrB.js} +3 -3
  379. package/web/assets/stateDiagram-7D4R322I-D6qYSV1E.js +1 -0
  380. package/web/assets/stateDiagram-v2-36443NZ5-Bg-ImOSX.js +1 -0
  381. package/web/assets/{timeline-definition-O6YCAMPW-Ds2CnVZK.js → timeline-definition-O6YCAMPW-QWkh01xw.js} +1 -1
  382. package/web/assets/treeView-BLDUP644-QA4HXRO3-ODehyGKL.js +1 -0
  383. package/web/assets/treemap-LRROVOQU-LLAWBHMP-BfpgC-7o.js +1 -0
  384. package/web/assets/{vennDiagram-MWXL3ELB-DUIEwXWp.js → vennDiagram-MWXL3ELB-ak4DfwuM.js} +3 -3
  385. package/web/assets/wardley-L42UT6IY-5TKZOOLJ-CNeL6VHE.js +1 -0
  386. package/web/assets/{wardleyDiagram-CUQ6CDDI-DEiFPQih.js → wardleyDiagram-CUQ6CDDI-CTzR4Loq.js} +1 -1
  387. package/web/assets/{xychartDiagram-N2JHSOCM-BUbayhST.js → xychartDiagram-N2JHSOCM-DA1BEaQz.js} +1 -1
  388. package/web/index.html +13 -15
  389. package/scripts/materialize-vendor-grammars.cjs +0 -97
  390. package/web/assets/architecture-7EHR7CIX-6QZW5X65-BETj2x4g.js +0 -1
  391. package/web/assets/architectureDiagram-UL44E2DR-D1EXI0zA.js +0 -36
  392. package/web/assets/chunk-2T2R6R2M-n6s9JZqv.js +0 -4
  393. package/web/assets/chunk-2UTLFMKG-CMBB1TMN.js +0 -1
  394. package/web/assets/chunk-4R4BOZG6-mKhL59ul.js +0 -159
  395. package/web/assets/chunk-6764PJDD-ChwMM2z4.js +0 -1
  396. package/web/assets/chunk-7J6CGLKN-DT-b53FT.js +0 -10
  397. package/web/assets/chunk-C62D2QBJ-CDAWj26E.js +0 -1
  398. package/web/assets/chunk-CEXFNPSA-D68Tk6ls.js +0 -1
  399. package/web/assets/chunk-CilyBKbf.js +0 -1
  400. package/web/assets/chunk-J5EP6P6S-DxWW0yvu.js +0 -1
  401. package/web/assets/chunk-KGYTTC2M-C6PHeuay.js +0 -1
  402. package/web/assets/chunk-RERM46MO-DQNbXtfw.js +0 -1
  403. package/web/assets/chunk-RKZBBQEN-BmTPLSyv.js +0 -1
  404. package/web/assets/chunk-RLI5ZMPA-DExu2DOK.js +0 -1
  405. package/web/assets/chunk-T5OCTHI4--9wWpVws.js +0 -1
  406. package/web/assets/chunk-UP6H54XL-DsKdC6jC.js +0 -1
  407. package/web/assets/chunk-UXSXWOXI-DR81EqLr.js +0 -1
  408. package/web/assets/classDiagram-KGZ6W3CR-Bv93af_b.js +0 -1
  409. package/web/assets/classDiagram-v2-72OJOZXJ-CLfEqHUa.js +0 -1
  410. package/web/assets/dagre-ND4H6XIP-DsIr9MOv.js +0 -4
  411. package/web/assets/diagram-3NCE3AQN-BqAtKUpW.js +0 -43
  412. package/web/assets/diagram-GF46GFSD-CaBG6n6o.js +0 -24
  413. package/web/assets/diagram-QXG6HAR7-CZ-O3rcV.js +0 -24
  414. package/web/assets/diagram-WEQXMOUZ-P4lSL4GH.js +0 -10
  415. package/web/assets/eventmodeling-FCH6USID-MREXMVOE-B3myHUst.js +0 -1
  416. package/web/assets/gitGraph-WXDBUCRP-R675I2BI-DF3rtf4A.js +0 -1
  417. package/web/assets/gitGraphDiagram-S2ZK5IYY-CCwVLT__.js +0 -106
  418. package/web/assets/index-BKWA-m7o.css +0 -2
  419. package/web/assets/index-CG6q8eTs.js +0 -626
  420. package/web/assets/info-J43DQDTF-KCYPFFUO-R4XV_akJ.js +0 -1
  421. package/web/assets/infoDiagram-3YFTVSEB-DmLICZx1.js +0 -2
  422. package/web/assets/packet-YPE3B663-LP52Z2RK-CgQPdNZa.js +0 -1
  423. package/web/assets/pie-LRSECV5Y-TCRJHUBD-BzWOwc_c.js +0 -1
  424. package/web/assets/radar-GUYGQ44K-RDLRG3WG-B7KG0FN7.js +0 -1
  425. package/web/assets/stateDiagram-7D4R322I-CB2nABwH.js +0 -1
  426. package/web/assets/stateDiagram-v2-36443NZ5-COBGd2RL.js +0 -1
  427. package/web/assets/treeView-BLDUP644-QA4HXRO3-CRSxEwLO.js +0 -1
  428. package/web/assets/treemap-LRROVOQU-LLAWBHMP-a0PfKoVn.js +0 -1
  429. package/web/assets/wardley-L42UT6IY-5TKZOOLJ-BKQ6e5mC.js +0 -1
  430. /package/web/assets/{chunk-AQ6EADP3-CCFje6lL.js → chunk-AQ6EADP3-CZhslHi-.js} +0 -0
@@ -0,0 +1,646 @@
1
+ import { generateId } from '../../../lib/utils.js';
2
+ import { computeReachingDefs } from './reaching-defs.js';
3
+ import { computeControlDependence } from './control-dependence.js';
4
+ import { computePostDominators, isExitReachableFromAllBlocks, NO_IPDOM, } from './post-dominators.js';
5
+ import { augmentForPostDom } from './synthetic-escape.js';
6
+ import { DEFAULT_PDG_MAX_SITES_PER_STATEMENT } from './visitors/call-site-harvest.js';
7
+ import { calleeIdPosKey } from '../scope-resolution/graph-bridge/callee-id-sink.js';
8
+ import { encodeReachingDefReasonPairs } from './reaching-def-reason-codec.js';
9
+ /**
10
+ * Reserved token placed in `BasicBlock.callees` when a statement's call sites
11
+ * were truncated at {@link DEFAULT_PDG_MAX_SITES_PER_STATEMENT}: the recorded
12
+ * callee list is then INCOMPLETE, so over-cap callees are absent. `*` is not a
13
+ * valid identifier leaf, so it cannot collide with a real callee name. The
14
+ * impact bridge treats a slice containing this sentinel as "callees unknown" and
15
+ * keeps reach callgraph-equal (proven), rather than falsely labeling an
16
+ * absent-but-real callee `unproven-bridge`.
17
+ */
18
+ export const CALLEES_TRUNCATED_SENTINEL = '*';
19
+ /**
20
+ * Inner separator for the `BasicBlock.calleeIds` cell (resolved callee symbol
21
+ * ids). A TAB is used — NOT a space — because resolved ids embed `filePath` and
22
+ * C++ overload shape tags with multi-word primitive types (e.g. `unsigned char`,
23
+ * `long double`), so an id can legitimately contain a space; a space-joined cell
24
+ * then fragments on read and silently drops inter-procedural reach to that
25
+ * callee (#2227 tri-review). A tab cannot appear in a tree-sitter-derived id
26
+ * token (paths/identifiers/type tokens are tab-free) and round-trips intact
27
+ * through `escapeCSVField` (tab is in its preserved set) and the RFC-4180 COPY
28
+ * reader (every cell is quoted). Producer ({@link calleeIdsOfBlock}) and
29
+ * consumer (`splitCalleeIds`) import this single constant so they cannot drift.
30
+ * The sibling `callees` (leaf-name) cell stays space-joined — leaf names are
31
+ * bare identifiers and never contain a space.
32
+ */
33
+ export const CALLEE_ID_SEP = '\t';
34
+ /**
35
+ * Default per-function CFG edge cap. A pathological generated function could
36
+ * otherwise emit an unbounded edge set; the cap bounds graph growth and is
37
+ * overridable via `--pdg` options. `0` (in options) means no cap (unlimited
38
+ * — see the `cap` mapping in {@link emitFileCfgs}); `undefined` means this
39
+ * default.
40
+ */
41
+ export const DEFAULT_MAX_CFG_EDGES_PER_FUNCTION = 5000;
42
+ /**
43
+ * Default per-function REACHING_DEF edge cap (#2082 M2 KTD9). 4000 mirrors
44
+ * Joern's per-method `maxNumberOfDefinitions` — the closest production prior
45
+ * art — but truncates-and-warns instead of silently skipping the function.
46
+ * Counts (defBlock, useBlock, binding) DEDUPED edges, not statement-level
47
+ * facts. `0` ⇒ unlimited; `undefined` ⇒ this default.
48
+ */
49
+ export const DEFAULT_PDG_MAX_REACHING_DEF_EDGES_PER_FUNCTION = 4000;
50
+ /**
51
+ * Default per-function CDG edge cap (#2085 M5). CDG edge count is bounded by
52
+ * (blocks × control-nesting-depth) — comparable to the CFG edge count — so it
53
+ * reuses the CFG default of 5000. Counts DEDUPED (controller, dependent, label)
54
+ * edges (the pure {@link computeControlDependence} already dedups). `0` ⇒
55
+ * unlimited; `undefined` ⇒ this default. Folded into the `RepoMeta.pdg` stamp
56
+ * (U5) so introducing CDG forces a full writeback for pre-CDG `--pdg` indexes.
57
+ */
58
+ export const DEFAULT_PDG_MAX_CDG_EDGES_PER_FUNCTION = 5000;
59
+ /**
60
+ * Heap-safety ceiling on {@link computeControlDependence}'s pre-dedup
61
+ * materialization (#2188 review). The walk is O(edges × post-dom depth), and its
62
+ * `out` IS the deduped-edge quantity the per-function cap trims — so, UNLIKE
63
+ * REACHING_DEF's facts ceiling, this is deliberately NOT derived from the
64
+ * runtime edge cap (doing so would pre-truncate the very set the cap reports on,
65
+ * losing the exact dropped count). A fixed, generous multiple of the default
66
+ * edge cap: far above any real function — a catastrophe backstop only. When hit,
67
+ * the per-function cap reporting plus the `truncated` flag keep it observable
68
+ * (never a silent drop).
69
+ */
70
+ export const DEFAULT_PDG_MAX_CDG_MATERIALIZATION_PER_FUNCTION = 8 * DEFAULT_PDG_MAX_CDG_EDGES_PER_FUNCTION;
71
+ /**
72
+ * Env flag that additionally emits diagnostic `POST_DOMINATE` edges
73
+ * (block → its immediate post-dominator) alongside CDG (#2085 M5 KTD8). Off in
74
+ * every normal `--pdg` run — these are for inspecting the post-dom tree, not a
75
+ * queryable product surface. Accepts `1`/`true` (case-insensitive).
76
+ */
77
+ export const POST_DOMINATE_DEBUG_ENV = 'GITNEXUS_PDG_EMIT_POST_DOMINATE';
78
+ /**
79
+ * Fact-materialization headroom over the edge cap (#2082 M2 U3/F3): facts are
80
+ * O(defs×uses) BY SPEC in merge-heavy code, and the edge cap alone bounds the
81
+ * GRAPH, not the per-function memory spike of materializing facts before
82
+ * dedup. {@link emitFileReachingDefs} hands `edgeCap × this` to
83
+ * `computeReachingDefs` as `maxFacts` (unlimited when the edge cap is 0) —
84
+ * single source of truth; the DEFAULT constant below is derived, never the
85
+ * mechanism.
86
+ */
87
+ export const REACHING_DEF_FACTS_PER_EDGE_CAP = 4;
88
+ /** Derived emit-path fact limit at the default edge cap (bench/doc anchor). */
89
+ export const DEFAULT_PDG_MAX_REACHING_DEF_FACTS_PER_FUNCTION = REACHING_DEF_FACTS_PER_EDGE_CAP * DEFAULT_PDG_MAX_REACHING_DEF_EDGES_PER_FUNCTION;
90
+ /**
91
+ * Fixpoint-iteration budget for {@link computeReachingDefs}, as a multiple of
92
+ * the function's block count ({@link emitFileReachingDefs} passes
93
+ * `blocks.length × this` as `maxBlockVisits`). Iterative reaching-defs on a
94
+ * reducible CFG converges in O(loop-nesting-depth) passes, so a worklist
95
+ * re-visits each block a small multiple of times for real code; this budget
96
+ * tolerates a nesting depth far beyond any hand-written function (real code is
97
+ * ≤ ~15 deep) while truncating the pathological deep nest that otherwise drives
98
+ * the solver to O(blocks²) — measured at seconds + GB on a machine-generated
99
+ * 2000-line all-loops function whose fact count stays linear (so `maxFacts`
100
+ * never fires). Truncation degrades to a sound empty REACHING_DEF for that one
101
+ * function (status `truncated`), never wrong facts.
102
+ *
103
+ * As of #2201 this ceiling is an adversarial-only backstop that effectively
104
+ * never fires on real code: the production solver auto-selects the SSA-sparse
105
+ * path for the looping functions that would breach it, and the SSA path has no
106
+ * fixpoint iteration (it answers reaching queries from the def-use graph in one
107
+ * pass) so it computes the full facts where the dense worklist would have
108
+ * truncated. The budget is still consulted on the dense fallback path (small /
109
+ * loop-free functions, and throw-edge / unreachable-block functions the SSA path
110
+ * does not model). WTO / loop-aware iteration ordering was benchmarked and
111
+ * rejected (0% faster — the cost was dense-set propagation, not visitation
112
+ * order); SSA-sparse was the real fix. See reaching-defs.ts.
113
+ */
114
+ export const DEFAULT_PDG_MAX_REACHING_DEF_BLOCK_REVISITS = 64;
115
+ /**
116
+ * The single BasicBlock id template (module doc). Exported for the M3 taint
117
+ * emit path (taint/emit.ts), whose TAINTED/SANITIZES edges must address the
118
+ * SAME persisted block nodes — a re-derived copy of this template would
119
+ * silently dangle the moment either drifted.
120
+ */
121
+ export const basicBlockId = (filePath, functionStartLine, functionStartColumn, blockIndex) => `BasicBlock:${filePath}:${functionStartLine}:${functionStartColumn}:${blockIndex}`;
122
+ /**
123
+ * Whether an untrusted `cfgSideChannel` element is safe to feed to
124
+ * {@link emitFileCfgs}. Deliberately NOT full FunctionCfg validation — it
125
+ * checks exactly the fields whose corruption is SILENT given emit's
126
+ * mechanics: {@link basicBlockId} string-templates every id-anchor value
127
+ * (filePath, function start line/column, block index, edge endpoints) and
128
+ * the graph's addNode/addRelationship are no-throw Map inserts. Unchecked,
129
+ * a missing anchor field cross-wires same-`undefined`-id blocks across
130
+ * functions (addNode is first-writer-wins), and an edge endpoint that
131
+ * matches no block index becomes a dangling `BasicBlock:…:<n>` edge that
132
+ * detonates much later at DB bulk-load instead of throwing here — so
133
+ * endpoints are checked for MEMBERSHIP in the block-index set, not just
134
+ * integer-ness. Lives in this module so the guard evolves with the id
135
+ * templating it defends (#2099 F4; M2 fields that join the id path must
136
+ * join this check).
137
+ */
138
+ export const isEmitSafeCfg = (cfg) => {
139
+ if (typeof cfg?.filePath !== 'string' ||
140
+ !Number.isInteger(cfg.functionStartLine) ||
141
+ !Number.isInteger(cfg.functionStartColumn) ||
142
+ !Array.isArray(cfg.blocks) ||
143
+ !Array.isArray(cfg.edges)) {
144
+ return false;
145
+ }
146
+ // Contiguity (index === position), not just integer-ness: every consumer —
147
+ // this module's id templating AND the reaching-defs solver's
148
+ // position-indexed adjacency arrays — assumes blocks[i].index === i. A
149
+ // membership-only check would admit a compacted channel ({index:0},{index:5})
150
+ // whose edge 0→5 passes membership but indexes past the arrays downstream.
151
+ for (let i = 0; i < cfg.blocks.length; i++) {
152
+ if (cfg.blocks[i]?.index !== i)
153
+ return false;
154
+ }
155
+ const n = cfg.blocks.length;
156
+ // entry/exit must land on real blocks — the solver feeds entryIndex straight
157
+ // into its RPO walk, where an out-of-range index throws and (worse than this
158
+ // one element) costs the whole FILE's REACHING_DEF pass (tri-review P3).
159
+ if (!Number.isInteger(cfg.entryIndex) ||
160
+ cfg.entryIndex < 0 ||
161
+ cfg.entryIndex >= n ||
162
+ !Number.isInteger(cfg.exitIndex) ||
163
+ cfg.exitIndex < 0 ||
164
+ cfg.exitIndex >= n) {
165
+ return false;
166
+ }
167
+ return cfg.edges.every((e) => Number.isInteger(e?.from) &&
168
+ Number.isInteger(e?.to) &&
169
+ e.from >= 0 &&
170
+ e.from < n &&
171
+ e.to >= 0 &&
172
+ e.to < n);
173
+ };
174
+ /**
175
+ * Whether a structurally-valid CFG's M2 statement facts are safe to feed to
176
+ * the reaching-defs solver + REACHING_DEF id templating (#2082 U1/U4): the
177
+ * binding table's name/declLine/declColumn template into edge ids, and
178
+ * statement def/use indices must stay IN RANGE of the table (an escaping
179
+ * index would fabricate `undefined`-keyed ids). Deliberately SEPARATE from
180
+ * {@link isEmitSafeCfg}: malformed facts must cost only the function's
181
+ * REACHING_DEF projection — degrading to M1 behavior (CFG emitted, no facts)
182
+ * — never the BasicBlock/CFG layer itself.
183
+ */
184
+ export const hasEmitSafeFacts = (cfg) => {
185
+ const bindings = cfg.bindings;
186
+ if (bindings === undefined) {
187
+ // Pre-M2 channel — statements must be absent too.
188
+ return cfg.blocks.every((b) => b.statements === undefined);
189
+ }
190
+ if (!Array.isArray(bindings))
191
+ return false;
192
+ for (const b of bindings) {
193
+ if (typeof b?.name !== 'string' ||
194
+ !Number.isInteger(b.declLine) ||
195
+ !Number.isInteger(b.declColumn)) {
196
+ return false;
197
+ }
198
+ }
199
+ const bindingCount = bindings.length;
200
+ const inRange = (i) => Number.isInteger(i) && i >= 0 && i < bindingCount;
201
+ for (const b of cfg.blocks) {
202
+ const stmts = b.statements;
203
+ if (stmts === undefined)
204
+ continue;
205
+ if (!Array.isArray(stmts))
206
+ return false;
207
+ for (const s of stmts) {
208
+ if (!Number.isInteger(s?.line) || !Array.isArray(s.defs) || !Array.isArray(s.uses)) {
209
+ return false;
210
+ }
211
+ if (!s.defs.every(inRange) || !s.uses.every(inRange))
212
+ return false;
213
+ if (s.mayDefs !== undefined) {
214
+ if (!Array.isArray(s.mayDefs) || !s.mayDefs.every(inRange))
215
+ return false;
216
+ }
217
+ }
218
+ }
219
+ return true;
220
+ };
221
+ /**
222
+ * Emit BasicBlock nodes + CFG edges for every function CFG in `cfgs`.
223
+ *
224
+ * `maxEdgesPerFunction` caps edges per function. On overflow we stop emitting
225
+ * that function's remaining edges and call `onWarn` naming the dropped count —
226
+ * no silent truncation (KTD6/R6). Block nodes are always fully emitted (their
227
+ * count is bounded by the function's statement count); only edges are capped.
228
+ */
229
+ /**
230
+ * Space-joined, sorted, de-duplicated leaf callee names invoked directly in a
231
+ * block (`call`/`new` sites; the leaf of a dotted path — `child_process.exec` ⇒
232
+ * `exec`). This is the persisted substrate for statement-precise inter-procedural
233
+ * impact: a callee reached from a function is "proven" to be impacted by a
234
+ * changed statement iff its name appears in the callees of a block in that
235
+ * statement's dependence slice. `sites` is harvested only for TS/JS under `--pdg`
236
+ * (and absent on synthetic ENTRY/EXIT), so the field is empty elsewhere and the
237
+ * bridge degrades to the prior (callgraph-equal) behavior. Space-joined because
238
+ * leaf names are identifiers (no spaces) and the field is itself one CSV cell.
239
+ */
240
+ export function calleesOfBlock(block) {
241
+ const names = new Set();
242
+ for (const stmt of block.statements ?? []) {
243
+ // A statement whose recorded sites reached the per-statement cap may have
244
+ // dropped over-cap callees (the harvester stops at the cap). Flag the block
245
+ // callee-unknown so the impact bridge keeps it callgraph-equal rather than
246
+ // under-proving an absent-but-real callee.
247
+ if ((stmt.sites?.length ?? 0) >= DEFAULT_PDG_MAX_SITES_PER_STATEMENT) {
248
+ names.add(CALLEES_TRUNCATED_SENTINEL);
249
+ }
250
+ for (const site of stmt.sites ?? []) {
251
+ if (site.kind === 'member-read')
252
+ continue;
253
+ const callee = site.callee;
254
+ if (!callee)
255
+ continue;
256
+ const leaf = callee.slice(callee.lastIndexOf('.') + 1);
257
+ if (leaf)
258
+ names.add(leaf);
259
+ }
260
+ }
261
+ return [...names].sort().join(' ');
262
+ }
263
+ /**
264
+ * Tab-joined ({@link CALLEE_ID_SEP}), sorted, de-duplicated RESOLVED callee symbol ids invoked
265
+ * directly in a block — the SOUND parallel to {@link calleesOfBlock}'s leaf
266
+ * names (#2227 follow-up plan U3, KTD1/KTD2/KTD7). Each block site's call-site
267
+ * anchor `at` (U1) is joined by EXACT position to the per-file resolved-id map
268
+ * `fileMap` (U2's `(line,col) → Set<calleeId>`), so a callee reached from a
269
+ * function is proven impacted by a changed statement iff its resolved id — not
270
+ * just its leaf NAME — appears in a slice block's `calleeIds`. This eliminates
271
+ * the same-leaf-name collision (false-proven) and import-alias (false-unproven)
272
+ * the name predicate suffers on overloading languages.
273
+ *
274
+ * The site partitioning is inherited verbatim from {@link calleesOfBlock}: the
275
+ * SAME `member-read`-skip and the SAME per-statement site cap (R7) — a capped
276
+ * statement adds {@link CALLEES_TRUNCATED_SENTINEL} so the bridge keeps the
277
+ * block callee-unknown for ids too (callgraph-equal rather than under-proving).
278
+ * Because `at` is the SAME anchor the CALLS resolution keyed `atRange` on
279
+ * (KTD7), the join lands on exactly the sites the name harvest partitioned,
280
+ * including the nested-function exclusion (so a single-line inline closure's
281
+ * inner call never leaks its id into the outer block).
282
+ *
283
+ * `fileMap` is the resolved-id map for THIS file (`calleeIdAccumulator.get(
284
+ * filePath)` in run.ts). Absent (pdg off, or a file with no captured CALLS) ⇒
285
+ * `''` — the bridge then degrades to the leaf-name fallback (R3). A site whose
286
+ * `at` is absent (pre-U1 channel) or whose position is not in the map
287
+ * contributes no id (graceful, never throws).
288
+ */
289
+ export function calleeIdsOfBlock(block, fileMap) {
290
+ if (fileMap === undefined)
291
+ return '';
292
+ const ids = new Set();
293
+ for (const stmt of block.statements ?? []) {
294
+ // Mirror calleesOfBlock's cap signal: an over-cap statement dropped sites,
295
+ // so the id list is INCOMPLETE — flag the block callee-unknown (R7).
296
+ if ((stmt.sites?.length ?? 0) >= DEFAULT_PDG_MAX_SITES_PER_STATEMENT) {
297
+ ids.add(CALLEES_TRUNCATED_SENTINEL);
298
+ }
299
+ for (const site of stmt.sites ?? []) {
300
+ if (site.kind === 'member-read')
301
+ continue;
302
+ const at = site.at;
303
+ if (!at)
304
+ continue;
305
+ const resolved = fileMap.get(calleeIdPosKey(at[0], at[1]));
306
+ if (resolved === undefined)
307
+ continue;
308
+ for (const id of resolved)
309
+ ids.add(id);
310
+ }
311
+ }
312
+ return [...ids].sort().join(CALLEE_ID_SEP);
313
+ }
314
+ export function emitFileCfgs(graph, cfgs, maxEdgesPerFunction = DEFAULT_MAX_CFG_EDGES_PER_FUNCTION, onWarn, calleeIdMap) {
315
+ const result = { blocks: 0, edges: 0, droppedEdges: 0, cappedFunctions: 0 };
316
+ const cap = maxEdgesPerFunction > 0 ? maxEdgesPerFunction : Infinity;
317
+ for (const cfg of cfgs) {
318
+ const { filePath, functionStartLine, functionStartColumn } = cfg;
319
+ for (const b of cfg.blocks) {
320
+ graph.addNode({
321
+ id: basicBlockId(filePath, functionStartLine, functionStartColumn, b.index),
322
+ label: 'BasicBlock',
323
+ properties: {
324
+ name: '', // BasicBlock has no name column; identified by id + span
325
+ filePath,
326
+ startLine: b.startLine,
327
+ endLine: b.endLine,
328
+ text: b.text,
329
+ // Space-joined leaf callee names invoked in this block — the
330
+ // statement-precise inter-procedural reach substrate. Harvested from
331
+ // the per-statement `sites` (already on the side channel); dropping
332
+ // them here is what made the impact-mode bridge labeling degenerate.
333
+ callees: calleesOfBlock(b),
334
+ // Space-joined RESOLVED callee symbol ids — the SOUND parallel to
335
+ // `callees`, joined from the U2 map by each site's exact `at`
336
+ // position (#2227 follow-up U3). Absent map (pdg off / no captures)
337
+ // ⇒ `''`, and the bridge falls back to the leaf-name match (R3).
338
+ calleeIds: calleeIdsOfBlock(b, calleeIdMap),
339
+ },
340
+ });
341
+ result.blocks++;
342
+ }
343
+ let emittedForFn = 0;
344
+ for (const e of cfg.edges) {
345
+ if (emittedForFn >= cap) {
346
+ const dropped = cfg.edges.length - emittedForFn;
347
+ result.droppedEdges += dropped;
348
+ result.cappedFunctions++;
349
+ onWarn?.(`[cfg] ${filePath}:${functionStartLine}: per-function CFG edge cap ` +
350
+ `(${maxEdgesPerFunction}) reached — dropped ${dropped} of ${cfg.edges.length} edges`);
351
+ break;
352
+ }
353
+ const sourceId = basicBlockId(filePath, functionStartLine, functionStartColumn, e.from);
354
+ const targetId = basicBlockId(filePath, functionStartLine, functionStartColumn, e.to);
355
+ graph.addRelationship({
356
+ id: generateId('CFG', `${sourceId}->${targetId}:${e.kind}`),
357
+ type: 'CFG',
358
+ sourceId,
359
+ targetId,
360
+ confidence: 1.0,
361
+ reason: e.kind, // CfgEdgeKind (seq/cond-true/loop-back/…) — queryable
362
+ });
363
+ result.edges++;
364
+ emittedForFn++;
365
+ }
366
+ }
367
+ return result;
368
+ }
369
+ /**
370
+ * Stable identity for a binding inside edge ids (#2082 M2 KTD3/KTD9):
371
+ * `name:declLine:declCol` for declared bindings, `name@module` for synthetic
372
+ * ones. Distinct same-name bindings never share a key; identifier characters
373
+ * cannot contain the id separators. Exported for the M3 taint emit path —
374
+ * TAINTED/SANITIZES ids key bindings with the same discipline.
375
+ */
376
+ export const bindingKey = (b) => b.synthetic ? `${b.name}@module` : `${b.name}:${b.declLine}:${b.declColumn}`;
377
+ /**
378
+ * Compute reaching definitions per function and persist the bounded
379
+ * REACHING_DEF projection (#2082 M2 U4).
380
+ *
381
+ * Facts are DEDUPED to (defBlock, useBlock, binding) before budgeting — the
382
+ * persisted columns (`from,to,type,confidence,reason,step`; relationship ids
383
+ * are in-memory-only, the CodeRelation table has no id column) cannot
384
+ * distinguish finer rows, so statement-indexed ids would only manufacture
385
+ * byte-identical duplicate rows that burn budget. Statement granularity lives
386
+ * in the in-memory {@link computeReachingDefs} result, which the M3 taint
387
+ * engine recomputes on demand — the budget here governs only this projection
388
+ * and can never drop a taint fact.
389
+ *
390
+ * R7 (no silent truncation) covers BOTH layers: the per-function edge cap AND
391
+ * the solver's fact-materialization limit (which can fire without the edge
392
+ * cap ever being reached, since dedup is many-to-one) each produce one
393
+ * unconditional `onWarn`. The edge-cap warn names the top bindings by fact
394
+ * count — overflow is almost always one variable, which is exactly the datum
395
+ * M3 tuning wants.
396
+ */
397
+ export function emitFileReachingDefs(graph, cfgs, maxEdgesPerFunction = DEFAULT_PDG_MAX_REACHING_DEF_EDGES_PER_FUNCTION, onWarn,
398
+ // U12: a per-file memoized solver lets the RD-emit / harvest / taint passes
399
+ // share the SAME per-function fixpoint (this caller is its own cache bucket —
400
+ // it passes maxBlockVisits, the harvest/taint callers do not). Defaults to the
401
+ // plain solver so existing callers are unaffected.
402
+ solve = computeReachingDefs) {
403
+ const result = {
404
+ edges: 0,
405
+ droppedEdges: 0,
406
+ cappedFunctions: 0,
407
+ truncatedFunctions: 0,
408
+ malformedFactFunctions: 0,
409
+ facts: 0,
410
+ };
411
+ const cap = maxEdgesPerFunction > 0 ? maxEdgesPerFunction : Infinity;
412
+ const maxFacts = Number.isFinite(cap) ? cap * REACHING_DEF_FACTS_PER_EDGE_CAP : 0; // 0 ⇒ unlimited
413
+ for (const cfg of cfgs) {
414
+ // Graceful degradation: malformed M2 facts cost only this function's
415
+ // REACHING_DEF projection — its BasicBlock/CFG layer was already emitted.
416
+ if (!hasEmitSafeFacts(cfg)) {
417
+ result.malformedFactFunctions++;
418
+ onWarn?.(`[reaching-defs] ${cfg.filePath}:${cfg.functionStartLine}: malformed ` +
419
+ `statement facts (bad binding table or out-of-range fact indices) — ` +
420
+ `REACHING_DEF skipped for this function; its CFG is unaffected`);
421
+ continue;
422
+ }
423
+ const r = solve(cfg, {
424
+ maxFacts,
425
+ maxBlockVisits: cfg.blocks.length * DEFAULT_PDG_MAX_REACHING_DEF_BLOCK_REVISITS,
426
+ });
427
+ if (r.status === 'no-facts')
428
+ continue;
429
+ result.facts += r.facts.length;
430
+ const { filePath, functionStartLine, functionStartColumn } = cfg;
431
+ if (r.status === 'truncated') {
432
+ result.truncatedFunctions++;
433
+ onWarn?.(`[reaching-defs] ${filePath}:${functionStartLine}: fact materialization ` +
434
+ `limit (${maxFacts}) reached — facts beyond it were not computed; ` +
435
+ `the persisted REACHING_DEF projection for this function is sparse`);
436
+ }
437
+ else if (r.status === 'overflow') {
438
+ result.truncatedFunctions++;
439
+ onWarn?.(`[reaching-defs] ${filePath}:${functionStartLine}: a basic block exceeds ` +
440
+ `the def-key stride (≥2^21 coalesced statements — minified/generated ` +
441
+ `code) — REACHING_DEF skipped for this function (computing any facts ` +
442
+ `would risk wrong-block aliasing); its CFG is unaffected`);
443
+ continue;
444
+ }
445
+ // Dedup to (defBlock, useBlock, binding) — facts arrive sorted, so the
446
+ // deduped order (and therefore cap truncation) is deterministic. ONE edge per
447
+ // group (the edge COUNT is unchanged — substrate/bench safe), but the FU-B-2
448
+ // annotation AGGREGATES the FULL ordered list of (defLine, useLine) pairs for
449
+ // that group into the persisted `reason`. The first fact of a group (facts
450
+ // sort by def block, def stmt, use block, use stmt, binding) keeps the group's
451
+ // emit position; every subsequent fact of the SAME (block-pair, binding)
452
+ // appends its line pair to that group's list. Carrying the full list (not just
453
+ // the first pair) is what makes a SAME-BINDING reassignment chain recoverable:
454
+ // `acc = f(acc); acc = g(acc)` coalesces into one self-block whose
455
+ // `acc@N->acc@N+1` and `acc@N+1->acc@N+2` steps share the one group — a
456
+ // first-pair-only annotation could chain N->N+1 but never reach N+2. Dedup of
457
+ // exact-duplicate pairs within a group keeps the list compact (a `x = x + 1`
458
+ // self-fact never re-adds the same pair).
459
+ const groupIndex = new Map();
460
+ const deduped = [];
461
+ for (const f of r.facts) {
462
+ const key = `${f.def.blockIndex}:${f.use.blockIndex}:${f.bindingIdx}`;
463
+ const at = groupIndex.get(key);
464
+ const pair = { defLine: f.def.line, useLine: f.use.line };
465
+ if (at === undefined) {
466
+ groupIndex.set(key, deduped.length);
467
+ deduped.push({
468
+ defBlock: f.def.blockIndex,
469
+ useBlock: f.use.blockIndex,
470
+ bindingIdx: f.bindingIdx,
471
+ pairs: [pair],
472
+ });
473
+ continue;
474
+ }
475
+ const list = deduped[at].pairs;
476
+ // Skip an exact-duplicate (defLine, useLine) — a self-referential statement
477
+ // (`x = x + 1`) emits the same line pair more than once; the list only needs
478
+ // each distinct step once for the projection walk.
479
+ if (!list.some((p) => p.defLine === pair.defLine && p.useLine === pair.useLine)) {
480
+ list.push(pair);
481
+ }
482
+ }
483
+ let emittedForFn = 0;
484
+ for (const edge of deduped) {
485
+ if (emittedForFn >= cap) {
486
+ const dropped = deduped.length - emittedForFn;
487
+ result.droppedEdges += dropped;
488
+ result.cappedFunctions++;
489
+ // Tallied lazily — cap overflow is the rare path; the common uncapped
490
+ // case must not pay a per-fact counting pass.
491
+ const factsPerBinding = new Map();
492
+ for (const f of r.facts) {
493
+ factsPerBinding.set(f.bindingIdx, (factsPerBinding.get(f.bindingIdx) ?? 0) + 1);
494
+ }
495
+ const top = [...factsPerBinding.entries()]
496
+ .sort((a, b) => b[1] - a[1] || a[0] - b[0])
497
+ .slice(0, 2)
498
+ .map(([idx, count]) => `${r.bindings[idx]?.name ?? `#${idx}`}(${count} facts)`)
499
+ .join(', ');
500
+ onWarn?.(`[reaching-defs] ${filePath}:${functionStartLine}: per-function ` +
501
+ `REACHING_DEF edge cap (${maxEdgesPerFunction}) reached — dropped ` +
502
+ `${dropped} of ${deduped.length} edges; top bindings: ${top}`);
503
+ break;
504
+ }
505
+ const binding = r.bindings[edge.bindingIdx];
506
+ const sourceId = basicBlockId(filePath, functionStartLine, functionStartColumn, edge.defBlock);
507
+ const targetId = basicBlockId(filePath, functionStartLine, functionStartColumn, edge.useBlock);
508
+ graph.addRelationship({
509
+ // Single function anchor — the two block ids share it, so templating
510
+ // it once halves the id size (ids are in-memory-only but ~4000 of
511
+ // them per capped function is real transient heap).
512
+ id: generateId('REACHING_DEF', `${filePath}:${functionStartLine}:${functionStartColumn}:` +
513
+ `${edge.defBlock}->${edge.useBlock}:${bindingKey(binding)}`),
514
+ type: 'REACHING_DEF',
515
+ sourceId,
516
+ targetId,
517
+ confidence: 1.0,
518
+ // FU-B-2: the source-level binding name (M0/S1 verdict — name FIRST so
519
+ // `pdg_query` flows stays queryable) PLUS a compact versioned annotation
520
+ // carrying the FULL ordered list of def/use source LINE pairs for this
521
+ // (block-pair, binding) group. For a self-edge (defBlock === useBlock)
522
+ // this captures the intra-block def@L→use@L' chain — including a
523
+ // SAME-BINDING reassignment chain (`acc@24->acc@25->acc@26`) — that the
524
+ // block-granular projection lost; the statement projection (pdg-impact.ts)
525
+ // walks the list forward to fixpoint to recover the coalesced block's
526
+ // interior statements.
527
+ reason: encodeReachingDefReasonPairs(binding.name, edge.pairs),
528
+ });
529
+ result.edges++;
530
+ emittedForFn++;
531
+ }
532
+ }
533
+ return result;
534
+ }
535
+ /** Whether the POST_DOMINATE debug env flag is enabled (`1`/`true`). */
536
+ const postDominateDebugEnabled = () => {
537
+ const v = process.env[POST_DOMINATE_DEBUG_ENV];
538
+ return v === '1' || v?.toLowerCase() === 'true';
539
+ };
540
+ /**
541
+ * Compute control dependence per function and persist the bounded CDG
542
+ * projection (#2085 M5 U4). Mirrors {@link emitFileReachingDefs}: the pure
543
+ * {@link computeControlDependence} already dedups to (controller, dependent,
544
+ * label), so the per-function cap applies to deduped edges and overflow logs
545
+ * one unconditional `onWarn` naming the dropped count — no silent truncation
546
+ * (R6/R7). The branch label ('T'|'F') rides the `reason` column (KTD3),
547
+ * mirroring how CFG stores its edge kind.
548
+ *
549
+ * When {@link POST_DOMINATE_DEBUG_ENV} is set, also emits diagnostic
550
+ * `POST_DOMINATE` edges (block → its immediate post-dominator). These are NOT
551
+ * capped or counted against the CDG budget — they exist only for inspecting the
552
+ * post-dom tree and never appear in a normal run.
553
+ */
554
+ export function emitFileCdg(graph, cfgs, maxEdgesPerFunction = DEFAULT_PDG_MAX_CDG_EDGES_PER_FUNCTION, onWarn) {
555
+ const result = {
556
+ edges: 0,
557
+ droppedEdges: 0,
558
+ cappedFunctions: 0,
559
+ postDominateEdges: 0,
560
+ skippedUnsoundFunctions: 0,
561
+ };
562
+ const cap = maxEdgesPerFunction > 0 ? maxEdgesPerFunction : Infinity;
563
+ const emitPostDom = postDominateDebugEnabled();
564
+ for (const cfg of cfgs) {
565
+ const { filePath, functionStartLine, functionStartColumn } = cfg;
566
+ // Synthetic-escape pass (#2197 U1): restore EXIT reverse-reachability for a
567
+ // genuine exit-unreachable CYCLE (an unconditional `goto`-cycle / infinite
568
+ // loop) so the post-dom / CDG pass runs instead of being withheld. A no-op
569
+ // (returns `cfg` unchanged) for terminating functions and properly-escaped
570
+ // loops — those stay byte-identical. The synthetic edges are ANALYSIS-ONLY:
571
+ // they live on the returned shallow clone, never on the persisted `cfg`, so
572
+ // CFG / REACHING_DEF and the byte-identical-off golden are unaffected. Both
573
+ // the gate below AND the post-dom / CDG passes must see the augmented view
574
+ // (KTD7 — the Ferrante walk re-reads `cfg.edges`).
575
+ const view = augmentForPostDom(cfg);
576
+ // Sound post-dominance requires EXIT reachable from every entry-reachable
577
+ // block (#2188 review). The synthetic-escape pass recovers genuine cycles;
578
+ // anything STILL unreachable after it is a residual non-cycle anomaly (a
579
+ // dangling/dead-end block, a branch-less trapping spin, or a construction
580
+ // error) — NOT something we bridge (that would mask the bug). Skip CDG for
581
+ // it and surface the skip. CFG and REACHING_DEF (emitted elsewhere,
582
+ // independent of post-dominance) are kept.
583
+ if (!isExitReachableFromAllBlocks(view)) {
584
+ result.skippedUnsoundFunctions++;
585
+ onWarn?.(`[cdg] ${filePath}:${functionStartLine}: EXIT not reachable from all ` +
586
+ `blocks — CDG skipped for this function (CFG/REACHING_DEF unaffected)`);
587
+ continue;
588
+ }
589
+ // Compute the post-dom tree once and feed it to the control-dependence
590
+ // pass (avoids recomputing it) and to the optional POST_DOMINATE emit. The
591
+ // CDG edges reference BLOCK INDICES, which are identical in `view` and `cfg`
592
+ // (the augmentation only appends edges), so persisting them keyed off the
593
+ // original block ids is correct.
594
+ const tree = computePostDominators(view);
595
+ // Bound the pre-dedup materialization (heap parity with REACHING_DEF). The
596
+ // fixed ceiling is a catastrophe backstop; the per-function edge cap below
597
+ // remains the reporting authority. A ceiling hit is surfaced, not silent.
598
+ const { edges: cdgEdges, truncated } = computeControlDependence(view, tree, DEFAULT_PDG_MAX_CDG_MATERIALIZATION_PER_FUNCTION);
599
+ if (truncated) {
600
+ onWarn?.(`[cdg] ${filePath}:${functionStartLine}: control-dependence materialization ` +
601
+ `ceiling (${DEFAULT_PDG_MAX_CDG_MATERIALIZATION_PER_FUNCTION}) reached — ` +
602
+ `edge counts for this function are a floor`);
603
+ }
604
+ let emittedForFn = 0;
605
+ for (const edge of cdgEdges) {
606
+ if (emittedForFn >= cap) {
607
+ const dropped = cdgEdges.length - emittedForFn;
608
+ result.droppedEdges += dropped;
609
+ result.cappedFunctions++;
610
+ onWarn?.(`[cdg] ${filePath}:${functionStartLine}: per-function CDG edge cap ` +
611
+ `(${maxEdgesPerFunction}) reached — dropped ${dropped} of ${cdgEdges.length} edges`);
612
+ break;
613
+ }
614
+ const sourceId = basicBlockId(filePath, functionStartLine, functionStartColumn, edge.controllerBlock);
615
+ const targetId = basicBlockId(filePath, functionStartLine, functionStartColumn, edge.dependentBlock);
616
+ graph.addRelationship({
617
+ id: generateId('CDG', `${filePath}:${functionStartLine}:${functionStartColumn}:` +
618
+ `${edge.controllerBlock}->${edge.dependentBlock}:${edge.label}`),
619
+ type: 'CDG',
620
+ sourceId,
621
+ targetId,
622
+ confidence: 1.0,
623
+ reason: edge.label, // 'T' | 'F' — queryable, mirrors CFG's kind-in-reason
624
+ });
625
+ result.edges++;
626
+ emittedForFn++;
627
+ }
628
+ if (emitPostDom) {
629
+ for (let b = 0; b < tree.ipdom.length; b++) {
630
+ const ip = tree.ipdom[b];
631
+ if (ip === NO_IPDOM)
632
+ continue;
633
+ graph.addRelationship({
634
+ id: generateId('POST_DOMINATE', `${filePath}:${functionStartLine}:${functionStartColumn}:${b}->${ip}`),
635
+ type: 'POST_DOMINATE',
636
+ sourceId: basicBlockId(filePath, functionStartLine, functionStartColumn, b),
637
+ targetId: basicBlockId(filePath, functionStartLine, functionStartColumn, ip),
638
+ confidence: 1.0,
639
+ reason: '',
640
+ });
641
+ result.postDominateEdges++;
642
+ }
643
+ }
644
+ }
645
+ return result;
646
+ }