@grafema/util 0.3.0-beta

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 (324) hide show
  1. package/LICENSE +190 -0
  2. package/dist/api/GraphAPI.d.ts +87 -0
  3. package/dist/api/GraphAPI.d.ts.map +1 -0
  4. package/dist/api/GraphAPI.js +212 -0
  5. package/dist/api/GraphAPI.js.map +1 -0
  6. package/dist/api/GuaranteeAPI.d.ts +147 -0
  7. package/dist/api/GuaranteeAPI.d.ts.map +1 -0
  8. package/dist/api/GuaranteeAPI.js +290 -0
  9. package/dist/api/GuaranteeAPI.js.map +1 -0
  10. package/dist/config/ConfigLoader.d.ts +214 -0
  11. package/dist/config/ConfigLoader.d.ts.map +1 -0
  12. package/dist/config/ConfigLoader.js +441 -0
  13. package/dist/config/ConfigLoader.js.map +1 -0
  14. package/dist/config/index.d.ts +6 -0
  15. package/dist/config/index.d.ts.map +1 -0
  16. package/dist/config/index.js +5 -0
  17. package/dist/config/index.js.map +1 -0
  18. package/dist/core/CoverageAnalyzer.d.ts +65 -0
  19. package/dist/core/CoverageAnalyzer.d.ts.map +1 -0
  20. package/dist/core/CoverageAnalyzer.js +199 -0
  21. package/dist/core/CoverageAnalyzer.js.map +1 -0
  22. package/dist/core/FileExplainer.d.ts +101 -0
  23. package/dist/core/FileExplainer.d.ts.map +1 -0
  24. package/dist/core/FileExplainer.js +140 -0
  25. package/dist/core/FileExplainer.js.map +1 -0
  26. package/dist/core/FileOverview.d.ts +124 -0
  27. package/dist/core/FileOverview.d.ts.map +1 -0
  28. package/dist/core/FileOverview.js +279 -0
  29. package/dist/core/FileOverview.js.map +1 -0
  30. package/dist/core/GrafemaUri.d.ts +66 -0
  31. package/dist/core/GrafemaUri.d.ts.map +1 -0
  32. package/dist/core/GrafemaUri.js +191 -0
  33. package/dist/core/GrafemaUri.js.map +1 -0
  34. package/dist/core/GraphBackend.d.ts +158 -0
  35. package/dist/core/GraphBackend.d.ts.map +1 -0
  36. package/dist/core/GraphBackend.js +85 -0
  37. package/dist/core/GraphBackend.js.map +1 -0
  38. package/dist/core/GraphFreshnessChecker.d.ts +33 -0
  39. package/dist/core/GraphFreshnessChecker.d.ts.map +1 -0
  40. package/dist/core/GraphFreshnessChecker.js +104 -0
  41. package/dist/core/GraphFreshnessChecker.js.map +1 -0
  42. package/dist/core/GuaranteeManager.d.ts +254 -0
  43. package/dist/core/GuaranteeManager.d.ts.map +1 -0
  44. package/dist/core/GuaranteeManager.js +447 -0
  45. package/dist/core/GuaranteeManager.js.map +1 -0
  46. package/dist/core/HashUtils.d.ts +24 -0
  47. package/dist/core/HashUtils.d.ts.map +1 -0
  48. package/dist/core/HashUtils.js +46 -0
  49. package/dist/core/HashUtils.js.map +1 -0
  50. package/dist/core/IncrementalReanalyzer.d.ts +33 -0
  51. package/dist/core/IncrementalReanalyzer.d.ts.map +1 -0
  52. package/dist/core/IncrementalReanalyzer.js +67 -0
  53. package/dist/core/IncrementalReanalyzer.js.map +1 -0
  54. package/dist/core/ResourceRegistry.d.ts +17 -0
  55. package/dist/core/ResourceRegistry.d.ts.map +1 -0
  56. package/dist/core/ResourceRegistry.js +32 -0
  57. package/dist/core/ResourceRegistry.js.map +1 -0
  58. package/dist/core/SemanticId.d.ts +159 -0
  59. package/dist/core/SemanticId.d.ts.map +1 -0
  60. package/dist/core/SemanticId.js +291 -0
  61. package/dist/core/SemanticId.js.map +1 -0
  62. package/dist/core/VersionManager.d.ts +166 -0
  63. package/dist/core/VersionManager.d.ts.map +1 -0
  64. package/dist/core/VersionManager.js +239 -0
  65. package/dist/core/VersionManager.js.map +1 -0
  66. package/dist/core/brandNodeInternal.d.ts +14 -0
  67. package/dist/core/brandNodeInternal.d.ts.map +1 -0
  68. package/dist/core/brandNodeInternal.js +4 -0
  69. package/dist/core/brandNodeInternal.js.map +1 -0
  70. package/dist/core/nodes/GuaranteeNode.d.ts +76 -0
  71. package/dist/core/nodes/GuaranteeNode.d.ts.map +1 -0
  72. package/dist/core/nodes/GuaranteeNode.js +118 -0
  73. package/dist/core/nodes/GuaranteeNode.js.map +1 -0
  74. package/dist/core/nodes/IssueNode.d.ts +73 -0
  75. package/dist/core/nodes/IssueNode.d.ts.map +1 -0
  76. package/dist/core/nodes/IssueNode.js +130 -0
  77. package/dist/core/nodes/IssueNode.js.map +1 -0
  78. package/dist/core/nodes/NodeKind.d.ts +104 -0
  79. package/dist/core/nodes/NodeKind.d.ts.map +1 -0
  80. package/dist/core/nodes/NodeKind.js +166 -0
  81. package/dist/core/nodes/NodeKind.js.map +1 -0
  82. package/dist/diagnostics/DiagnosticCollector.d.ts +103 -0
  83. package/dist/diagnostics/DiagnosticCollector.d.ts.map +1 -0
  84. package/dist/diagnostics/DiagnosticCollector.js +133 -0
  85. package/dist/diagnostics/DiagnosticCollector.js.map +1 -0
  86. package/dist/diagnostics/DiagnosticReporter.d.ts +122 -0
  87. package/dist/diagnostics/DiagnosticReporter.d.ts.map +1 -0
  88. package/dist/diagnostics/DiagnosticReporter.js +300 -0
  89. package/dist/diagnostics/DiagnosticReporter.js.map +1 -0
  90. package/dist/diagnostics/DiagnosticWriter.d.ts +31 -0
  91. package/dist/diagnostics/DiagnosticWriter.d.ts.map +1 -0
  92. package/dist/diagnostics/DiagnosticWriter.js +44 -0
  93. package/dist/diagnostics/DiagnosticWriter.js.map +1 -0
  94. package/dist/diagnostics/categories.d.ts +57 -0
  95. package/dist/diagnostics/categories.d.ts.map +1 -0
  96. package/dist/diagnostics/categories.js +71 -0
  97. package/dist/diagnostics/categories.js.map +1 -0
  98. package/dist/diagnostics/index.d.ts +17 -0
  99. package/dist/diagnostics/index.d.ts.map +1 -0
  100. package/dist/diagnostics/index.js +15 -0
  101. package/dist/diagnostics/index.js.map +1 -0
  102. package/dist/errors/GrafemaError.d.ts +200 -0
  103. package/dist/errors/GrafemaError.d.ts.map +1 -0
  104. package/dist/errors/GrafemaError.js +209 -0
  105. package/dist/errors/GrafemaError.js.map +1 -0
  106. package/dist/index.d.ts +75 -0
  107. package/dist/index.d.ts.map +1 -0
  108. package/dist/index.js +76 -0
  109. package/dist/index.js.map +1 -0
  110. package/dist/instructions/index.d.ts +8 -0
  111. package/dist/instructions/index.d.ts.map +1 -0
  112. package/dist/instructions/index.js +20 -0
  113. package/dist/instructions/index.js.map +1 -0
  114. package/dist/instructions/onboarding.md +133 -0
  115. package/dist/knowledge/KnowledgeBase.d.ts +113 -0
  116. package/dist/knowledge/KnowledgeBase.d.ts.map +1 -0
  117. package/dist/knowledge/KnowledgeBase.js +420 -0
  118. package/dist/knowledge/KnowledgeBase.js.map +1 -0
  119. package/dist/knowledge/SemanticAddressResolver.d.ts +59 -0
  120. package/dist/knowledge/SemanticAddressResolver.d.ts.map +1 -0
  121. package/dist/knowledge/SemanticAddressResolver.js +160 -0
  122. package/dist/knowledge/SemanticAddressResolver.js.map +1 -0
  123. package/dist/knowledge/git-ingest.d.ts +58 -0
  124. package/dist/knowledge/git-ingest.d.ts.map +1 -0
  125. package/dist/knowledge/git-ingest.js +301 -0
  126. package/dist/knowledge/git-ingest.js.map +1 -0
  127. package/dist/knowledge/git-queries.d.ts +86 -0
  128. package/dist/knowledge/git-queries.d.ts.map +1 -0
  129. package/dist/knowledge/git-queries.js +177 -0
  130. package/dist/knowledge/git-queries.js.map +1 -0
  131. package/dist/knowledge/index.d.ts +14 -0
  132. package/dist/knowledge/index.d.ts.map +1 -0
  133. package/dist/knowledge/index.js +10 -0
  134. package/dist/knowledge/index.js.map +1 -0
  135. package/dist/knowledge/parser.d.ts +39 -0
  136. package/dist/knowledge/parser.d.ts.map +1 -0
  137. package/dist/knowledge/parser.js +292 -0
  138. package/dist/knowledge/parser.js.map +1 -0
  139. package/dist/knowledge/types.d.ts +133 -0
  140. package/dist/knowledge/types.d.ts.map +1 -0
  141. package/dist/knowledge/types.js +8 -0
  142. package/dist/knowledge/types.js.map +1 -0
  143. package/dist/logging/Logger.d.ts +98 -0
  144. package/dist/logging/Logger.d.ts.map +1 -0
  145. package/dist/logging/Logger.js +274 -0
  146. package/dist/logging/Logger.js.map +1 -0
  147. package/dist/notation/archetypes.d.ts +36 -0
  148. package/dist/notation/archetypes.d.ts.map +1 -0
  149. package/dist/notation/archetypes.js +173 -0
  150. package/dist/notation/archetypes.js.map +1 -0
  151. package/dist/notation/fold.d.ts +25 -0
  152. package/dist/notation/fold.d.ts.map +1 -0
  153. package/dist/notation/fold.js +598 -0
  154. package/dist/notation/fold.js.map +1 -0
  155. package/dist/notation/index.d.ts +18 -0
  156. package/dist/notation/index.d.ts.map +1 -0
  157. package/dist/notation/index.js +16 -0
  158. package/dist/notation/index.js.map +1 -0
  159. package/dist/notation/lodExtractor.d.ts +32 -0
  160. package/dist/notation/lodExtractor.d.ts.map +1 -0
  161. package/dist/notation/lodExtractor.js +149 -0
  162. package/dist/notation/lodExtractor.js.map +1 -0
  163. package/dist/notation/nameShortener.d.ts +22 -0
  164. package/dist/notation/nameShortener.d.ts.map +1 -0
  165. package/dist/notation/nameShortener.js +24 -0
  166. package/dist/notation/nameShortener.js.map +1 -0
  167. package/dist/notation/perspectives.d.ts +11 -0
  168. package/dist/notation/perspectives.d.ts.map +1 -0
  169. package/dist/notation/perspectives.js +16 -0
  170. package/dist/notation/perspectives.js.map +1 -0
  171. package/dist/notation/renderer.d.ts +31 -0
  172. package/dist/notation/renderer.d.ts.map +1 -0
  173. package/dist/notation/renderer.js +315 -0
  174. package/dist/notation/renderer.js.map +1 -0
  175. package/dist/notation/traceRenderer.d.ts +39 -0
  176. package/dist/notation/traceRenderer.d.ts.map +1 -0
  177. package/dist/notation/traceRenderer.js +358 -0
  178. package/dist/notation/traceRenderer.js.map +1 -0
  179. package/dist/notation/types.d.ts +66 -0
  180. package/dist/notation/types.d.ts.map +1 -0
  181. package/dist/notation/types.js +10 -0
  182. package/dist/notation/types.js.map +1 -0
  183. package/dist/queries/NodeContext.d.ts +81 -0
  184. package/dist/queries/NodeContext.d.ts.map +1 -0
  185. package/dist/queries/NodeContext.js +196 -0
  186. package/dist/queries/NodeContext.js.map +1 -0
  187. package/dist/queries/findCallsInFunction.d.ts +62 -0
  188. package/dist/queries/findCallsInFunction.d.ts.map +1 -0
  189. package/dist/queries/findCallsInFunction.js +169 -0
  190. package/dist/queries/findCallsInFunction.js.map +1 -0
  191. package/dist/queries/findContainingFunction.d.ts +57 -0
  192. package/dist/queries/findContainingFunction.d.ts.map +1 -0
  193. package/dist/queries/findContainingFunction.js +91 -0
  194. package/dist/queries/findContainingFunction.js.map +1 -0
  195. package/dist/queries/index.d.ts +18 -0
  196. package/dist/queries/index.d.ts.map +1 -0
  197. package/dist/queries/index.js +14 -0
  198. package/dist/queries/index.js.map +1 -0
  199. package/dist/queries/traceDataflow.d.ts +65 -0
  200. package/dist/queries/traceDataflow.d.ts.map +1 -0
  201. package/dist/queries/traceDataflow.js +754 -0
  202. package/dist/queries/traceDataflow.js.map +1 -0
  203. package/dist/queries/traceValues.d.ts +70 -0
  204. package/dist/queries/traceValues.d.ts.map +1 -0
  205. package/dist/queries/traceValues.js +373 -0
  206. package/dist/queries/traceValues.js.map +1 -0
  207. package/dist/queries/types.d.ts +166 -0
  208. package/dist/queries/types.d.ts.map +1 -0
  209. package/dist/queries/types.js +10 -0
  210. package/dist/queries/types.js.map +1 -0
  211. package/dist/schema/GraphSchemaExtractor.d.ts +53 -0
  212. package/dist/schema/GraphSchemaExtractor.d.ts.map +1 -0
  213. package/dist/schema/GraphSchemaExtractor.js +125 -0
  214. package/dist/schema/GraphSchemaExtractor.js.map +1 -0
  215. package/dist/schema/InterfaceSchemaExtractor.d.ts +73 -0
  216. package/dist/schema/InterfaceSchemaExtractor.d.ts.map +1 -0
  217. package/dist/schema/InterfaceSchemaExtractor.js +113 -0
  218. package/dist/schema/InterfaceSchemaExtractor.js.map +1 -0
  219. package/dist/schema/index.d.ts +5 -0
  220. package/dist/schema/index.d.ts.map +1 -0
  221. package/dist/schema/index.js +3 -0
  222. package/dist/schema/index.js.map +1 -0
  223. package/dist/storage/backends/RFDBServerBackend.d.ts +356 -0
  224. package/dist/storage/backends/RFDBServerBackend.d.ts.map +1 -0
  225. package/dist/storage/backends/RFDBServerBackend.js +748 -0
  226. package/dist/storage/backends/RFDBServerBackend.js.map +1 -0
  227. package/dist/storage/backends/typeValidation.d.ts +47 -0
  228. package/dist/storage/backends/typeValidation.d.ts.map +1 -0
  229. package/dist/storage/backends/typeValidation.js +141 -0
  230. package/dist/storage/backends/typeValidation.js.map +1 -0
  231. package/dist/utils/findRfdbBinary.d.ts +67 -0
  232. package/dist/utils/findRfdbBinary.d.ts.map +1 -0
  233. package/dist/utils/findRfdbBinary.js +261 -0
  234. package/dist/utils/findRfdbBinary.js.map +1 -0
  235. package/dist/utils/lazyDownload.d.ts +43 -0
  236. package/dist/utils/lazyDownload.d.ts.map +1 -0
  237. package/dist/utils/lazyDownload.js +175 -0
  238. package/dist/utils/lazyDownload.js.map +1 -0
  239. package/dist/utils/moduleResolution.d.ts +134 -0
  240. package/dist/utils/moduleResolution.d.ts.map +1 -0
  241. package/dist/utils/moduleResolution.js +189 -0
  242. package/dist/utils/moduleResolution.js.map +1 -0
  243. package/dist/utils/resolveNodeFile.d.ts +13 -0
  244. package/dist/utils/resolveNodeFile.d.ts.map +1 -0
  245. package/dist/utils/resolveNodeFile.js +18 -0
  246. package/dist/utils/resolveNodeFile.js.map +1 -0
  247. package/dist/utils/startRfdbServer.d.ts +63 -0
  248. package/dist/utils/startRfdbServer.d.ts.map +1 -0
  249. package/dist/utils/startRfdbServer.js +142 -0
  250. package/dist/utils/startRfdbServer.js.map +1 -0
  251. package/dist/validation/PathValidator.d.ts +80 -0
  252. package/dist/validation/PathValidator.d.ts.map +1 -0
  253. package/dist/validation/PathValidator.js +252 -0
  254. package/dist/validation/PathValidator.js.map +1 -0
  255. package/dist/version.d.ts +11 -0
  256. package/dist/version.d.ts.map +1 -0
  257. package/dist/version.js +26 -0
  258. package/dist/version.js.map +1 -0
  259. package/package.json +50 -0
  260. package/src/api/GraphAPI.ts +307 -0
  261. package/src/api/GuaranteeAPI.ts +402 -0
  262. package/src/config/ConfigLoader.ts +653 -0
  263. package/src/config/index.ts +13 -0
  264. package/src/core/CoverageAnalyzer.ts +243 -0
  265. package/src/core/FileExplainer.ts +179 -0
  266. package/src/core/FileOverview.ts +397 -0
  267. package/src/core/GrafemaUri.ts +216 -0
  268. package/src/core/GraphBackend.ts +266 -0
  269. package/src/core/GraphFreshnessChecker.ts +145 -0
  270. package/src/core/GuaranteeManager.ts +684 -0
  271. package/src/core/HashUtils.ts +48 -0
  272. package/src/core/IncrementalReanalyzer.ts +106 -0
  273. package/src/core/ResourceRegistry.ts +39 -0
  274. package/src/core/SemanticId.ts +423 -0
  275. package/src/core/VersionManager.ts +405 -0
  276. package/src/core/brandNodeInternal.ts +16 -0
  277. package/src/core/nodes/GuaranteeNode.ts +162 -0
  278. package/src/core/nodes/IssueNode.ts +177 -0
  279. package/src/core/nodes/NodeKind.ts +192 -0
  280. package/src/diagnostics/DiagnosticCollector.ts +170 -0
  281. package/src/diagnostics/DiagnosticReporter.ts +395 -0
  282. package/src/diagnostics/DiagnosticWriter.ts +50 -0
  283. package/src/diagnostics/categories.ts +104 -0
  284. package/src/diagnostics/index.ts +30 -0
  285. package/src/errors/GrafemaError.ts +297 -0
  286. package/src/index.ts +261 -0
  287. package/src/instructions/index.ts +21 -0
  288. package/src/instructions/onboarding.md +133 -0
  289. package/src/knowledge/KnowledgeBase.ts +486 -0
  290. package/src/knowledge/SemanticAddressResolver.ts +191 -0
  291. package/src/knowledge/git-ingest.ts +402 -0
  292. package/src/knowledge/git-queries.ts +269 -0
  293. package/src/knowledge/index.ts +29 -0
  294. package/src/knowledge/parser.ts +294 -0
  295. package/src/knowledge/types.ts +146 -0
  296. package/src/logging/Logger.ts +303 -0
  297. package/src/notation/archetypes.ts +189 -0
  298. package/src/notation/fold.ts +696 -0
  299. package/src/notation/index.ts +27 -0
  300. package/src/notation/lodExtractor.ts +177 -0
  301. package/src/notation/nameShortener.ts +24 -0
  302. package/src/notation/perspectives.ts +18 -0
  303. package/src/notation/renderer.ts +394 -0
  304. package/src/notation/traceRenderer.ts +458 -0
  305. package/src/notation/types.ts +79 -0
  306. package/src/queries/NodeContext.ts +280 -0
  307. package/src/queries/findCallsInFunction.ts +249 -0
  308. package/src/queries/findContainingFunction.ts +124 -0
  309. package/src/queries/index.ts +44 -0
  310. package/src/queries/traceDataflow.ts +838 -0
  311. package/src/queries/traceValues.ts +531 -0
  312. package/src/queries/types.ts +191 -0
  313. package/src/schema/GraphSchemaExtractor.ts +177 -0
  314. package/src/schema/InterfaceSchemaExtractor.ts +173 -0
  315. package/src/schema/index.ts +5 -0
  316. package/src/storage/backends/RFDBServerBackend.ts +895 -0
  317. package/src/storage/backends/typeValidation.ts +154 -0
  318. package/src/utils/findRfdbBinary.ts +288 -0
  319. package/src/utils/lazyDownload.ts +206 -0
  320. package/src/utils/moduleResolution.ts +271 -0
  321. package/src/utils/resolveNodeFile.ts +18 -0
  322. package/src/utils/startRfdbServer.ts +197 -0
  323. package/src/validation/PathValidator.ts +334 -0
  324. package/src/version.ts +28 -0
@@ -0,0 +1,754 @@
1
+ /**
2
+ * Dataflow BFS Tracing
3
+ *
4
+ * Full BFS-based dataflow analysis with 100% reachability.
5
+ * Forward: 8 heuristics for complete data flow propagation.
6
+ * Backward: structural descent + PA matching + THROWS propagation.
7
+ *
8
+ * Shared by MCP handler and CLI trace command.
9
+ *
10
+ * @module queries/traceDataflow
11
+ */
12
+ // === CONSTANTS ===
13
+ const MUTATION_METHODS = new Set([
14
+ 'push', 'unshift', 'splice', 'set', 'add', 'append', 'insert', 'enqueue', 'prepend',
15
+ ]);
16
+ const STRUCTURAL_EDGE_TYPES = ['HAS_PROPERTY', 'HAS_ELEMENT', 'HAS_CONSEQUENT', 'HAS_ALTERNATE'];
17
+ // === SHARED HELPERS ===
18
+ /** Resolve a node ID through REFERENCE → READS_FROM to reach the declaration. */
19
+ async function resolveRef(db, nodeId) {
20
+ const node = await db.getNode(nodeId);
21
+ if (!node)
22
+ return null;
23
+ if (node.type === 'REFERENCE') {
24
+ const edges = await db.getOutgoingEdges(nodeId, ['READS_FROM']);
25
+ return edges.length > 0 ? edges[0].dst : null;
26
+ }
27
+ return nodeId;
28
+ }
29
+ /** Extract .index from edge metadata (PASSES_ARGUMENT/RECEIVES_ARGUMENT). */
30
+ function edgeIndex(edge) {
31
+ if (edge.metadata && typeof edge.metadata.index === 'number')
32
+ return edge.metadata.index;
33
+ if (typeof edge.index === 'number')
34
+ return edge.index;
35
+ return undefined;
36
+ }
37
+ /** Check if two argument-index values match (undefined = wildcard). */
38
+ function indexMatch(paIdx, raIdx) {
39
+ if (paIdx === undefined || raIdx === undefined)
40
+ return true;
41
+ return paIdx === raIdx;
42
+ }
43
+ /** Resolve a PROPERTY_ACCESS receiver chain: walk READS_FROM to find base var + dot-path. */
44
+ async function resolveReceiverChain(db, paId) {
45
+ const pathParts = [];
46
+ let cur = paId;
47
+ const seen = new Set();
48
+ while (cur && !seen.has(cur)) {
49
+ seen.add(cur);
50
+ const rf = await db.getOutgoingEdges(cur, ['READS_FROM']);
51
+ if (!rf.length)
52
+ break;
53
+ const t = await db.getNode(rf[0].dst);
54
+ if (!t)
55
+ break;
56
+ cur = rf[0].dst;
57
+ if (t.type === 'PROPERTY_ACCESS') {
58
+ if (t.name)
59
+ pathParts.push(t.name);
60
+ continue;
61
+ }
62
+ if (t.type === 'REFERENCE') {
63
+ const re = await db.getOutgoingEdges(t.id, ['READS_FROM']);
64
+ if (re.length) {
65
+ const r = await db.getNode(re[0].dst);
66
+ if (r && (r.type === 'CONSTANT' || r.type === 'VARIABLE')) {
67
+ return { base: r.id, path: pathParts.join('.') };
68
+ }
69
+ }
70
+ return null;
71
+ }
72
+ if (t.type === 'CONSTANT' || t.type === 'VARIABLE') {
73
+ return { base: t.id, path: pathParts.join('.') };
74
+ }
75
+ break;
76
+ }
77
+ return null;
78
+ }
79
+ /** Resolve dynamic callee: follow ASSIGNED_FROM/READS_FROM chains to find the actual FUNCTION. */
80
+ async function resolveDynamicCallee(db, nodeId) {
81
+ const seen = new Set();
82
+ let cur = nodeId;
83
+ while (cur && !seen.has(cur)) {
84
+ seen.add(cur);
85
+ const n = await db.getNode(cur);
86
+ if (!n)
87
+ return null;
88
+ if (n.type === 'FUNCTION')
89
+ return cur;
90
+ if (n.type === 'REFERENCE') {
91
+ const rf = await db.getOutgoingEdges(cur, ['READS_FROM']);
92
+ if (rf.length) {
93
+ cur = rf[0].dst;
94
+ continue;
95
+ }
96
+ return null;
97
+ }
98
+ if (n.type === 'PARAMETER' || n.type === 'VARIABLE' || n.type === 'CONSTANT') {
99
+ const afs = await db.getOutgoingEdges(cur, ['ASSIGNED_FROM']);
100
+ for (const af of afs) {
101
+ const afN = await db.getNode(af.dst);
102
+ if (afN?.type === 'FUNCTION')
103
+ return af.dst;
104
+ if (afN?.type === 'REFERENCE') {
105
+ const result = await resolveDynamicCallee(db, af.dst);
106
+ if (result)
107
+ return result;
108
+ }
109
+ }
110
+ if (n.type === 'PARAMETER') {
111
+ const raIn = await db.getIncomingEdges(cur, ['RECEIVES_ARGUMENT']);
112
+ for (const ra of raIn) {
113
+ const fnId = ra.src;
114
+ const raIdx = edgeIndex(ra);
115
+ for (const ci of await db.getIncomingEdges(fnId, ['CALLS'])) {
116
+ const pas = await db.getOutgoingEdges(ci.src, ['PASSES_ARGUMENT']);
117
+ for (const pa of pas) {
118
+ if (!indexMatch(raIdx, edgeIndex(pa)))
119
+ continue;
120
+ const paN = await db.getNode(pa.dst);
121
+ if (paN?.type === 'FUNCTION')
122
+ return pa.dst;
123
+ if (paN?.type === 'REFERENCE') {
124
+ const result = await resolveDynamicCallee(db, pa.dst);
125
+ if (result)
126
+ return result;
127
+ }
128
+ }
129
+ }
130
+ }
131
+ }
132
+ return null;
133
+ }
134
+ if (n.type === 'PROPERTY_ACCESS') {
135
+ const rf = await db.getOutgoingEdges(cur, ['READS_FROM']);
136
+ if (rf.length) {
137
+ cur = rf[0].dst;
138
+ continue;
139
+ }
140
+ return null;
141
+ }
142
+ return null;
143
+ }
144
+ return null;
145
+ }
146
+ // === FORWARD BFS ===
147
+ /**
148
+ * Forward BFS dataflow trace.
149
+ * Starting from a declaration node, finds all nodes where data flows TO.
150
+ * Returns all reached nodes (including the start node at index 0).
151
+ */
152
+ export async function traceForwardBFS(db, startId, maxIterations) {
153
+ const visited = new Set();
154
+ const queue = [];
155
+ const reachedNodes = [];
156
+ function enq(id) {
157
+ if (id && !visited.has(id)) {
158
+ visited.add(id);
159
+ queue.push(id);
160
+ }
161
+ }
162
+ // Guard sets for helper functions
163
+ const processedCalls = new Set();
164
+ const processedFns = new Set();
165
+ const climbProcessed = new Set();
166
+ // Lazy indexes
167
+ let paReadByName = null;
168
+ let catchParamIds = null;
169
+ /** Lazy index: "file::receiverName" → CALL node IDs for method calls on that receiver. */
170
+ let callsByReceiver = null;
171
+ async function getPAReadByName() {
172
+ if (paReadByName)
173
+ return paReadByName;
174
+ paReadByName = new Map();
175
+ for await (const n of db.queryNodes({ type: 'PROPERTY_ACCESS' })) {
176
+ const wt = await db.getOutgoingEdges(n.id, ['WRITES_TO']);
177
+ if (wt.length === 0 && n.name) {
178
+ const chain = await resolveReceiverChain(db, n.id);
179
+ if (chain) {
180
+ let list = paReadByName.get(n.name);
181
+ if (!list) {
182
+ list = [];
183
+ paReadByName.set(n.name, list);
184
+ }
185
+ list.push({ id: n.id, base: chain.base, path: chain.path });
186
+ }
187
+ }
188
+ }
189
+ return paReadByName;
190
+ }
191
+ async function getCatchParams() {
192
+ if (catchParamIds)
193
+ return catchParamIds;
194
+ catchParamIds = [];
195
+ for await (const p of db.queryNodes({ type: 'PARAMETER' })) {
196
+ const decls = await db.getIncomingEdges(p.id, ['DECLARES']);
197
+ for (const d of decls) {
198
+ const scope = await db.getNode(d.src);
199
+ if (scope?.name === 'catch') {
200
+ catchParamIds.push(p.id);
201
+ break;
202
+ }
203
+ }
204
+ }
205
+ return catchParamIds;
206
+ }
207
+ /**
208
+ * Build index of CALL nodes keyed by "file::receiverName".
209
+ * Used to find method calls on a declaration when CALL→DERIVED_FROM→PA→READS_FROM→REF
210
+ * edges are missing. CALL names encode the receiver: "db.getNode" → receiver "db".
211
+ */
212
+ async function getCallsByReceiver() {
213
+ if (callsByReceiver)
214
+ return callsByReceiver;
215
+ callsByReceiver = new Map();
216
+ for await (const c of db.queryNodes({ type: 'CALL' })) {
217
+ if (c.name?.includes('.') && c.file) {
218
+ const dotIdx = c.name.indexOf('.');
219
+ const receiver = c.name.substring(0, dotIdx);
220
+ // Skip computed receivers like "<obj>.method"
221
+ if (receiver.startsWith('<'))
222
+ continue;
223
+ const key = `${c.file}::${receiver}`;
224
+ let list = callsByReceiver.get(key);
225
+ if (!list) {
226
+ list = [];
227
+ callsByReceiver.set(key, list);
228
+ }
229
+ list.push(c.id);
230
+ }
231
+ }
232
+ return callsByReceiver;
233
+ }
234
+ // --- Helper: enqueue call result consumers ---
235
+ async function enqueueCallConsumers(callId) {
236
+ if (processedCalls.has(callId))
237
+ return;
238
+ processedCalls.add(callId);
239
+ for (const af of await db.getIncomingEdges(callId, ['ASSIGNED_FROM']))
240
+ enq(af.src);
241
+ // Structural climb
242
+ for (const se of await db.getIncomingEdges(callId, [...STRUCTURAL_EDGE_TYPES])) {
243
+ await enqueueClimb(se.src, 3);
244
+ }
245
+ // CALL result passed as arg to another CALL
246
+ for (const pa of await db.getIncomingEdges(callId, ['PASSES_ARGUMENT'])) {
247
+ for (const ce of await db.getOutgoingEdges(pa.src, ['CALLS'])) {
248
+ for (const ra of await db.getOutgoingEdges(ce.dst, ['RECEIVES_ARGUMENT']))
249
+ enq(ra.dst);
250
+ }
251
+ // Mutation on call result
252
+ for (const df of await db.getOutgoingEdges(pa.src, ['DERIVED_FROM'])) {
253
+ const dfN = await db.getNode(df.dst);
254
+ if (dfN?.type === 'PROPERTY_ACCESS' && dfN.name && MUTATION_METHODS.has(dfN.name)) {
255
+ for (const rf of await db.getOutgoingEdges(df.dst, ['READS_FROM'])) {
256
+ enq(await resolveRef(db, rf.dst));
257
+ }
258
+ }
259
+ }
260
+ // Outer call result
261
+ await enqueueCallConsumers(pa.src);
262
+ }
263
+ // Chained calls: another CALL uses this as callee (fn()())
264
+ for (const df of await db.getIncomingEdges(callId, ['DERIVED_FROM'])) {
265
+ const dfN = await db.getNode(df.src);
266
+ if (dfN?.type === 'CALL')
267
+ await enqueueCallConsumers(df.src);
268
+ }
269
+ // CALL result returned by FUNCTION
270
+ for (const ret of await db.getIncomingEdges(callId, ['RETURNS'])) {
271
+ await enqueueFnCallers(ret.src);
272
+ }
273
+ // CALL result read via PA (call().value)
274
+ for (const rf of await db.getIncomingEdges(callId, ['READS_FROM'])) {
275
+ const rfN = await db.getNode(rf.src);
276
+ if (rfN?.type === 'PROPERTY_ACCESS')
277
+ enq(rf.src);
278
+ }
279
+ // Dynamic callee resolution
280
+ const callsEdges = await db.getOutgoingEdges(callId, ['CALLS']);
281
+ let hasStaticCallee = false;
282
+ for (const ce of callsEdges) {
283
+ const ceN = await db.getNode(ce.dst);
284
+ if (ceN?.type === 'FUNCTION') {
285
+ hasStaticCallee = true;
286
+ break;
287
+ }
288
+ }
289
+ if (!hasStaticCallee) {
290
+ for (const df of await db.getOutgoingEdges(callId, ['DERIVED_FROM'])) {
291
+ const resolved = await resolveDynamicCallee(db, df.dst);
292
+ if (resolved) {
293
+ for (const ra of await db.getOutgoingEdges(resolved, ['RECEIVES_ARGUMENT']))
294
+ enq(ra.dst);
295
+ }
296
+ }
297
+ for (const ce of callsEdges) {
298
+ const ceN = await db.getNode(ce.dst);
299
+ if (ceN?.type === 'PARAMETER') {
300
+ const resolved = await resolveDynamicCallee(db, ce.dst);
301
+ if (resolved) {
302
+ for (const ra of await db.getOutgoingEdges(resolved, ['RECEIVES_ARGUMENT']))
303
+ enq(ra.dst);
304
+ }
305
+ }
306
+ }
307
+ }
308
+ // Callback injection: if CALL passes a FUNCTION as argument, trace into callback params
309
+ for (const pa of await db.getOutgoingEdges(callId, ['PASSES_ARGUMENT'])) {
310
+ const paN = await db.getNode(pa.dst);
311
+ if (paN?.type === 'FUNCTION') {
312
+ for (const ra of await db.getOutgoingEdges(pa.dst, ['RECEIVES_ARGUMENT']))
313
+ enq(ra.dst);
314
+ }
315
+ }
316
+ }
317
+ // --- Helper: enqueue callers of a function ---
318
+ async function enqueueFnCallers(fnId) {
319
+ if (processedFns.has(fnId))
320
+ return;
321
+ processedFns.add(fnId);
322
+ for (const ci of await db.getIncomingEdges(fnId, ['CALLS'])) {
323
+ await enqueueCallConsumers(ci.src);
324
+ }
325
+ for (const fpa of await db.getIncomingEdges(fnId, ['PASSES_ARGUMENT'])) {
326
+ await enqueueCallConsumers(fpa.src);
327
+ }
328
+ for (const faf of await db.getIncomingEdges(fnId, ['ASSIGNED_FROM'])) {
329
+ for (const vc of await db.getIncomingEdges(faf.src, ['CALLS'])) {
330
+ await enqueueCallConsumers(vc.src);
331
+ }
332
+ }
333
+ for (const df of await db.getIncomingEdges(fnId, ['DERIVED_FROM'])) {
334
+ const dfN = await db.getNode(df.src);
335
+ if (dfN?.type === 'CALL')
336
+ await enqueueCallConsumers(df.src);
337
+ }
338
+ }
339
+ // --- Helper: climb structural containers ---
340
+ async function enqueueClimb(nodeId, maxClimb) {
341
+ if (maxClimb <= 0 || climbProcessed.has(nodeId))
342
+ return;
343
+ climbProcessed.add(nodeId);
344
+ enq(nodeId);
345
+ for (const af of await db.getIncomingEdges(nodeId, ['ASSIGNED_FROM']))
346
+ enq(af.src);
347
+ for (const ret of await db.getIncomingEdges(nodeId, ['RETURNS']))
348
+ await enqueueFnCallers(ret.src);
349
+ for (const se of await db.getIncomingEdges(nodeId, [...STRUCTURAL_EDGE_TYPES, 'PASSES_ARGUMENT'])) {
350
+ await enqueueClimb(se.src, maxClimb - 1);
351
+ }
352
+ }
353
+ // --- Helper: follow PASSES_ARGUMENT to callee params + mutation + call result ---
354
+ async function followPassesArgument(pa) {
355
+ const callId = pa.src;
356
+ const paIdx = edgeIndex(pa);
357
+ // A. To params (with index matching)
358
+ for (const ce of await db.getOutgoingEdges(callId, ['CALLS'])) {
359
+ const raEdges = await db.getOutgoingEdges(ce.dst, ['RECEIVES_ARGUMENT']);
360
+ for (const ra of raEdges) {
361
+ if (!indexMatch(paIdx, edgeIndex(ra)))
362
+ continue;
363
+ enq(ra.dst);
364
+ }
365
+ }
366
+ // B. Mutation detection
367
+ for (const df of await db.getOutgoingEdges(callId, ['DERIVED_FROM'])) {
368
+ const dfN = await db.getNode(df.dst);
369
+ if (dfN?.type === 'PROPERTY_ACCESS' && dfN.name && MUTATION_METHODS.has(dfN.name)) {
370
+ for (const rf of await db.getOutgoingEdges(df.dst, ['READS_FROM'])) {
371
+ enq(await resolveRef(db, rf.dst));
372
+ }
373
+ }
374
+ }
375
+ // C. Call result consumers
376
+ await enqueueCallConsumers(callId);
377
+ }
378
+ // === Main BFS loop ===
379
+ enq(startId);
380
+ let iterations = 0;
381
+ while (queue.length > 0) {
382
+ const declId = queue.shift();
383
+ iterations++;
384
+ if (iterations > maxIterations)
385
+ break;
386
+ const node = await db.getNode(declId);
387
+ if (!node)
388
+ continue;
389
+ reachedNodes.push(node);
390
+ // --- 1. Follow refs that read this declaration ---
391
+ const refsToDecl = await db.getIncomingEdges(declId, ['READS_FROM']);
392
+ for (const refEdge of refsToDecl) {
393
+ const refId = refEdge.src;
394
+ // 1a. ASSIGNED_FROM incoming on ref
395
+ for (const af of await db.getIncomingEdges(refId, ['ASSIGNED_FROM']))
396
+ enq(af.src);
397
+ // 1b. WRITES_TO incoming on ref
398
+ for (const wt of await db.getIncomingEdges(refId, ['WRITES_TO'])) {
399
+ enq(await resolveRef(db, wt.src));
400
+ }
401
+ // 1c. PASSES_ARGUMENT incoming on ref
402
+ for (const pa of await db.getIncomingEdges(refId, ['PASSES_ARGUMENT'])) {
403
+ await followPassesArgument(pa);
404
+ }
405
+ // 1d. Structural climb
406
+ for (const se of await db.getIncomingEdges(refId, [...STRUCTURAL_EDGE_TYPES])) {
407
+ await enqueueClimb(se.src, 4);
408
+ }
409
+ // 1e. DERIVED_FROM incoming on ref: EXPRESSION/CALL uses this ref
410
+ for (const df of await db.getIncomingEdges(refId, ['DERIVED_FROM'])) {
411
+ const dfN = await db.getNode(df.src);
412
+ if (dfN) {
413
+ if (dfN.type === 'EXPRESSION')
414
+ enq(df.src);
415
+ else if (dfN.type === 'CALL')
416
+ await enqueueCallConsumers(df.src);
417
+ }
418
+ }
419
+ // 1f. RETURNS / YIELDS — enqueue function callers + function itself
420
+ for (const ret of await db.getIncomingEdges(refId, ['RETURNS'])) {
421
+ await enqueueFnCallers(ret.src);
422
+ enq(ret.src);
423
+ }
424
+ for (const y of await db.getIncomingEdges(refId, ['YIELDS'])) {
425
+ await enqueueFnCallers(y.src);
426
+ enq(y.src);
427
+ }
428
+ // 1g. THROWS → all catch PARAMETERs
429
+ const throwsEdges = await db.getIncomingEdges(refId, ['THROWS']);
430
+ if (throwsEdges.length > 0) {
431
+ const cps = await getCatchParams();
432
+ for (const cpId of cps)
433
+ enq(cpId);
434
+ }
435
+ // 1h. ITERATES_OVER incoming on ref
436
+ for (const io of await db.getIncomingEdges(refId, ['ITERATES_OVER']))
437
+ enq(io.src);
438
+ // 1i. PA reads from ref (receiver chain)
439
+ for (const paRead of await db.getIncomingEdges(refId, ['READS_FROM'])) {
440
+ const paN = await db.getNode(paRead.src);
441
+ if (paN?.type === 'PROPERTY_ACCESS')
442
+ enq(paRead.src);
443
+ }
444
+ // 1j. DERIVED_FROM incoming (CALL consumers)
445
+ for (const df of await db.getIncomingEdges(refId, ['DERIVED_FROM'])) {
446
+ const dfN = await db.getNode(df.src);
447
+ if (dfN?.type === 'CALL')
448
+ await enqueueCallConsumers(df.src);
449
+ }
450
+ }
451
+ // --- 2. Direct edges on declaration ---
452
+ for (const hel of await db.getIncomingEdges(declId, ['HAS_ELEMENT']))
453
+ enq(hel.src);
454
+ for (const hp of await db.getIncomingEdges(declId, ['HAS_PROPERTY']))
455
+ enq(hp.src);
456
+ for (const ret of await db.getIncomingEdges(declId, ['RETURNS']))
457
+ await enqueueFnCallers(ret.src);
458
+ for (const y of await db.getIncomingEdges(declId, ['YIELDS']))
459
+ await enqueueFnCallers(y.src);
460
+ for (const io of await db.getIncomingEdges(declId, ['ITERATES_OVER']))
461
+ enq(io.src);
462
+ for (const af of await db.getIncomingEdges(declId, ['ASSIGNED_FROM']))
463
+ enq(af.src);
464
+ // WRITES_TO incoming on declaration
465
+ for (const wt of await db.getIncomingEdges(declId, ['WRITES_TO'])) {
466
+ enq(await resolveRef(db, wt.src));
467
+ }
468
+ // PASSES_ARGUMENT incoming on declaration
469
+ for (const pa of await db.getIncomingEdges(declId, ['PASSES_ARGUMENT'])) {
470
+ await followPassesArgument(pa);
471
+ }
472
+ // --- 2b. Method call receiver heuristic ---
473
+ // When graph lacks CALL→DERIVED_FROM→PA→READS_FROM→REF edges for method calls,
474
+ // use CALL naming convention ("db.getNode" → receiver "db") to find method calls
475
+ // on this declaration and trace their consumers.
476
+ if ((node.type === 'CONSTANT' || node.type === 'VARIABLE' || node.type === 'PARAMETER') && node.name && node.file) {
477
+ const idx = await getCallsByReceiver();
478
+ const key = `${node.file}::${node.name}`;
479
+ const methodCalls = idx.get(key);
480
+ if (methodCalls) {
481
+ for (const callId of methodCalls)
482
+ enq(callId);
483
+ }
484
+ }
485
+ // --- 3. PA write→read propagation via receiver chain matching ---
486
+ if (node.type === 'PROPERTY_ACCESS') {
487
+ const myChain = await resolveReceiverChain(db, declId);
488
+ if (myChain) {
489
+ const wt = await db.getOutgoingEdges(declId, ['WRITES_TO']);
490
+ if (wt.length > 0 && node.name) {
491
+ const idx = await getPAReadByName();
492
+ const readers = idx.get(node.name) || [];
493
+ for (const r of readers) {
494
+ if (r.base === myChain.base && r.path === myChain.path)
495
+ enq(r.id);
496
+ }
497
+ }
498
+ }
499
+ }
500
+ // --- 4. DERIVED_FROM incoming on declaration ---
501
+ for (const df of await db.getIncomingEdges(declId, ['DERIVED_FROM'])) {
502
+ const dfN = await db.getNode(df.src);
503
+ if (dfN?.type === 'CALL')
504
+ await enqueueCallConsumers(df.src);
505
+ else if (dfN?.type === 'EXPRESSION')
506
+ enq(df.src);
507
+ }
508
+ // --- 5. FUNCTION type → enqueueFnCallers ---
509
+ if (node.type === 'FUNCTION')
510
+ await enqueueFnCallers(declId);
511
+ // --- 6. CALL type → enqueueCallConsumers ---
512
+ if (node.type === 'CALL')
513
+ await enqueueCallConsumers(declId);
514
+ }
515
+ return reachedNodes;
516
+ }
517
+ // === BACKWARD BFS ===
518
+ /**
519
+ * Backward BFS dataflow trace.
520
+ * Starting from a declaration node, finds all nodes where data comes FROM.
521
+ * Returns all reached nodes (including the start node at index 0).
522
+ */
523
+ export async function traceBackwardBFS(db, startId, maxIterations) {
524
+ const visited = new Set();
525
+ const queue = [];
526
+ const reachedNodes = [];
527
+ function enq(id) {
528
+ if (id && !visited.has(id)) {
529
+ visited.add(id);
530
+ queue.push(id);
531
+ }
532
+ }
533
+ // Lazy PA writer index
534
+ let paWriterByName = null;
535
+ async function getPAWriterByName() {
536
+ if (paWriterByName)
537
+ return paWriterByName;
538
+ paWriterByName = new Map();
539
+ for await (const n of db.queryNodes({ type: 'PROPERTY_ACCESS' })) {
540
+ const wt = await db.getOutgoingEdges(n.id, ['WRITES_TO']);
541
+ if (wt.length > 0 && n.name) {
542
+ const chain = await resolveReceiverChain(db, n.id);
543
+ if (chain) {
544
+ let list = paWriterByName.get(n.name);
545
+ if (!list) {
546
+ list = [];
547
+ paWriterByName.set(n.name, list);
548
+ }
549
+ list.push({ id: n.id, base: chain.base, path: chain.path });
550
+ }
551
+ }
552
+ }
553
+ return paWriterByName;
554
+ }
555
+ enq(startId);
556
+ let iterations = 0;
557
+ while (queue.length > 0) {
558
+ const nodeId = queue.shift();
559
+ iterations++;
560
+ if (iterations > maxIterations)
561
+ break;
562
+ const node = await db.getNode(nodeId);
563
+ if (!node)
564
+ continue;
565
+ reachedNodes.push(node);
566
+ // 1. ASSIGNED_FROM outgoing → resolve ref → enqueue source
567
+ for (const af of await db.getOutgoingEdges(nodeId, ['ASSIGNED_FROM'])) {
568
+ const src = await resolveRef(db, af.dst);
569
+ enq(src);
570
+ const afNode = await db.getNode(af.dst);
571
+ if (afNode && !['CONSTANT', 'VARIABLE', 'PARAMETER', 'REFERENCE'].includes(afNode.type)) {
572
+ for (const ch of await db.getOutgoingEdges(af.dst, [...STRUCTURAL_EDGE_TYPES])) {
573
+ const resolved = await resolveRef(db, ch.dst);
574
+ enq(resolved);
575
+ }
576
+ }
577
+ }
578
+ // 2. Refs that write to this node → trace back to what was written
579
+ const refsToNode = await db.getIncomingEdges(nodeId, ['READS_FROM']);
580
+ for (const refEdge of refsToNode) {
581
+ const wtEdges = await db.getOutgoingEdges(refEdge.src, ['WRITES_TO']);
582
+ for (const wt of wtEdges) {
583
+ const resolved = await resolveRef(db, wt.dst);
584
+ enq(resolved);
585
+ }
586
+ }
587
+ // 3. PARAMETER: incoming RECEIVES_ARGUMENT → FUNCTION → incoming CALLS → CALL → PASSES_ARGUMENT
588
+ if (node.type === 'PARAMETER') {
589
+ const raEdges = await db.getIncomingEdges(nodeId, ['RECEIVES_ARGUMENT']);
590
+ for (const ra of raEdges) {
591
+ const fnId = ra.src;
592
+ const raIdx = edgeIndex(ra);
593
+ const callsIn = await db.getIncomingEdges(fnId, ['CALLS']);
594
+ for (const callEdge of callsIn) {
595
+ const paEdges = await db.getOutgoingEdges(callEdge.src, ['PASSES_ARGUMENT']);
596
+ for (const pa of paEdges) {
597
+ if (!indexMatch(raIdx, edgeIndex(pa)))
598
+ continue;
599
+ const argSrc = await resolveRef(db, pa.dst);
600
+ enq(argSrc);
601
+ }
602
+ }
603
+ }
604
+ }
605
+ // 4. Structural descent: outgoing HAS_ELEMENT, HAS_PROPERTY → descend, resolveRef
606
+ for (const ch of await db.getOutgoingEdges(nodeId, ['HAS_ELEMENT', 'HAS_PROPERTY'])) {
607
+ const resolved = await resolveRef(db, ch.dst);
608
+ enq(resolved);
609
+ }
610
+ // 5. CALL node: trace arguments and receiver chain
611
+ if (node.type === 'CALL') {
612
+ for (const pa of await db.getOutgoingEdges(nodeId, ['PASSES_ARGUMENT'])) {
613
+ const argSrc = await resolveRef(db, pa.dst);
614
+ enq(argSrc);
615
+ }
616
+ for (const df of await db.getOutgoingEdges(nodeId, ['DERIVED_FROM'])) {
617
+ const dfN = await db.getNode(df.dst);
618
+ if (dfN?.type === 'PROPERTY_ACCESS') {
619
+ for (const rf of await db.getOutgoingEdges(df.dst, ['READS_FROM'])) {
620
+ const resolved = await resolveRef(db, rf.dst);
621
+ enq(resolved);
622
+ }
623
+ }
624
+ }
625
+ // 5b. Receiver heuristic: when CALL→DERIVED_FROM→PA→READS_FROM chain is missing,
626
+ // parse receiver name from CALL name ("db.getNode" → "db") and find its declaration.
627
+ if (node.name?.includes('.')) {
628
+ const dotIdx = node.name.indexOf('.');
629
+ const receiverName = node.name.substring(0, dotIdx);
630
+ if (!receiverName.startsWith('<')) {
631
+ for (const declType of ['CONSTANT', 'VARIABLE', 'PARAMETER']) {
632
+ for await (const decl of db.queryNodes({ type: declType, name: receiverName })) {
633
+ if (decl.file === node.file)
634
+ enq(decl.id);
635
+ }
636
+ }
637
+ }
638
+ }
639
+ }
640
+ // 6. PA node: READS_FROM → resolveRef (the receiver's data source)
641
+ if (node.type === 'PROPERTY_ACCESS') {
642
+ for (const rf of await db.getOutgoingEdges(nodeId, ['READS_FROM'])) {
643
+ const resolved = await resolveRef(db, rf.dst);
644
+ enq(resolved);
645
+ }
646
+ }
647
+ // 7. ITERATES_OVER outgoing → resolveRef → enqueue (the iterable)
648
+ for (const io of await db.getOutgoingEdges(nodeId, ['ITERATES_OVER'])) {
649
+ const resolved = await resolveRef(db, io.dst);
650
+ enq(resolved);
651
+ }
652
+ // 8. Property read→write propagation: if this is a PA reader, find matching writers
653
+ if (node.type === 'PROPERTY_ACCESS' && node.name) {
654
+ const wt = await db.getOutgoingEdges(nodeId, ['WRITES_TO']);
655
+ if (wt.length === 0) {
656
+ const myChain = await resolveReceiverChain(db, nodeId);
657
+ if (myChain) {
658
+ const idx = await getPAWriterByName();
659
+ const writers = idx.get(node.name) || [];
660
+ for (const w of writers) {
661
+ if (w.base === myChain.base && w.path === myChain.path)
662
+ enq(w.id);
663
+ }
664
+ }
665
+ }
666
+ }
667
+ // 9. THROWS: if this is a catch PARAMETER, find thrown values
668
+ if (node.type === 'PARAMETER') {
669
+ const decls = await db.getIncomingEdges(nodeId, ['DECLARES']);
670
+ let isCatch = false;
671
+ for (const d of decls) {
672
+ const scope = await db.getNode(d.src);
673
+ if (scope?.name === 'catch') {
674
+ isCatch = true;
675
+ break;
676
+ }
677
+ }
678
+ if (isCatch) {
679
+ for await (const fn of db.queryNodes({ type: 'FUNCTION' })) {
680
+ for (const t of await db.getOutgoingEdges(fn.id, ['THROWS'])) {
681
+ const resolved = await resolveRef(db, t.dst);
682
+ enq(resolved);
683
+ }
684
+ }
685
+ for await (const mod of db.queryNodes({ type: 'MODULE' })) {
686
+ for (const t of await db.getOutgoingEdges(mod.id, ['THROWS'])) {
687
+ const resolved = await resolveRef(db, t.dst);
688
+ enq(resolved);
689
+ }
690
+ }
691
+ }
692
+ }
693
+ // 10. DERIVED_FROM outgoing: trace expression operands
694
+ for (const df of await db.getOutgoingEdges(nodeId, ['DERIVED_FROM'])) {
695
+ const resolved = await resolveRef(db, df.dst);
696
+ enq(resolved);
697
+ }
698
+ // 11. RETURNS incoming → if someone returns this node, trace the function's callers' arguments
699
+ for (const ret of await db.getIncomingEdges(nodeId, ['RETURNS'])) {
700
+ const callsIn = await db.getIncomingEdges(ret.src, ['CALLS']);
701
+ for (const callEdge of callsIn) {
702
+ for (const pa of await db.getOutgoingEdges(callEdge.src, ['PASSES_ARGUMENT'])) {
703
+ const argSrc = await resolveRef(db, pa.dst);
704
+ enq(argSrc);
705
+ }
706
+ }
707
+ }
708
+ }
709
+ return reachedNodes;
710
+ }
711
+ // === HIGH-LEVEL API ===
712
+ /**
713
+ * Trace dataflow from a starting node.
714
+ * Handles REFERENCE resolution, runs BFS in requested direction(s).
715
+ * Returns results with start node excluded from reached lists.
716
+ */
717
+ export async function traceDataflow(db, sourceId, options = {}) {
718
+ const { direction = 'forward', maxDepth = 10, limit } = options;
719
+ const maxIterations = Math.min((maxDepth || 10) * 100, 5000);
720
+ // Resolve REFERENCE to declaration
721
+ let startId = sourceId;
722
+ const sourceNode = await db.getNode(sourceId);
723
+ if (sourceNode?.type === 'REFERENCE') {
724
+ const edges = await db.getOutgoingEdges(sourceId, ['READS_FROM']);
725
+ if (edges.length > 0)
726
+ startId = edges[0].dst;
727
+ }
728
+ const resolvedStart = await db.getNode(startId);
729
+ if (!resolvedStart)
730
+ return [];
731
+ const results = [];
732
+ if (direction === 'forward' || direction === 'both') {
733
+ const nodes = await traceForwardBFS(db, startId, maxIterations);
734
+ const reached = nodes.slice(1); // skip start node
735
+ results.push({
736
+ direction: 'forward',
737
+ startNode: resolvedStart,
738
+ reached: limit ? reached.slice(0, limit) : reached,
739
+ totalReached: reached.length,
740
+ });
741
+ }
742
+ if (direction === 'backward' || direction === 'both') {
743
+ const nodes = await traceBackwardBFS(db, startId, maxIterations);
744
+ const reached = nodes.slice(1);
745
+ results.push({
746
+ direction: 'backward',
747
+ startNode: resolvedStart,
748
+ reached: limit ? reached.slice(0, limit) : reached,
749
+ totalReached: reached.length,
750
+ });
751
+ }
752
+ return results;
753
+ }
754
+ //# sourceMappingURL=traceDataflow.js.map