ts-knowledge-graph 0.1.2 → 0.1.6

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 (335) hide show
  1. package/README.md +99 -41
  2. package/contribs/webview/README.md +83 -0
  3. package/contribs/webview/web/css/style.css +310 -0
  4. package/contribs/webview/web/index.html +109 -0
  5. package/contribs/webview/web/js/app.js +1249 -0
  6. package/contribs/webview/web/js_autogenerated/.gitignore +3 -0
  7. package/contribs/webview/web/js_autogenerated/kind_descriptions.js +39 -0
  8. package/contribs/webview/web/types/app_globals.d.ts +154 -0
  9. package/dist/benchmark/benchmark_stats.d.ts +41 -0
  10. package/dist/benchmark/benchmark_stats.d.ts.map +1 -0
  11. package/dist/benchmark/benchmark_stats.js +61 -0
  12. package/dist/benchmark/benchmark_stats.js.map +1 -0
  13. package/dist/benchmark/node_benchmark.d.ts +78 -0
  14. package/dist/benchmark/node_benchmark.d.ts.map +1 -0
  15. package/dist/benchmark/node_benchmark.js +112 -0
  16. package/dist/benchmark/node_benchmark.js.map +1 -0
  17. package/dist/cli.d.ts.map +1 -1
  18. package/dist/cli.js +16 -4
  19. package/dist/cli.js.map +1 -1
  20. package/dist/cluster/cluster_weights.d.ts +20 -0
  21. package/dist/cluster/cluster_weights.d.ts.map +1 -0
  22. package/dist/cluster/cluster_weights.js +32 -0
  23. package/dist/cluster/cluster_weights.js.map +1 -0
  24. package/dist/cluster/community_detector.d.ts +61 -0
  25. package/dist/cluster/community_detector.d.ts.map +1 -0
  26. package/dist/cluster/community_detector.js +120 -0
  27. package/dist/cluster/community_detector.js.map +1 -0
  28. package/dist/cluster/community_labeler.d.ts +84 -0
  29. package/dist/cluster/community_labeler.d.ts.map +1 -0
  30. package/dist/cluster/community_labeler.js +194 -0
  31. package/dist/cluster/community_labeler.js.map +1 -0
  32. package/dist/cluster/graph_clusterer.d.ts +47 -0
  33. package/dist/cluster/graph_clusterer.d.ts.map +1 -0
  34. package/dist/cluster/graph_clusterer.js +126 -0
  35. package/dist/cluster/graph_clusterer.js.map +1 -0
  36. package/dist/commands/benchmark_command.d.ts +11 -0
  37. package/dist/commands/benchmark_command.d.ts.map +1 -0
  38. package/dist/commands/benchmark_command.js +94 -0
  39. package/dist/commands/benchmark_command.js.map +1 -0
  40. package/dist/commands/blast_radius_command.d.ts.map +1 -1
  41. package/dist/commands/blast_radius_command.js +7 -6
  42. package/dist/commands/blast_radius_command.js.map +1 -1
  43. package/dist/commands/cluster_command.d.ts +7 -0
  44. package/dist/commands/cluster_command.d.ts.map +1 -0
  45. package/dist/commands/cluster_command.js +55 -0
  46. package/dist/commands/cluster_command.js.map +1 -0
  47. package/dist/commands/command_helpers.d.ts +9 -4
  48. package/dist/commands/command_helpers.d.ts.map +1 -1
  49. package/dist/commands/command_helpers.js +13 -8
  50. package/dist/commands/command_helpers.js.map +1 -1
  51. package/dist/commands/cost_command.d.ts +13 -0
  52. package/dist/commands/cost_command.d.ts.map +1 -0
  53. package/dist/commands/cost_command.js +139 -0
  54. package/dist/commands/cost_command.js.map +1 -0
  55. package/dist/commands/{load.d.ts → enrich_command.d.ts} +3 -2
  56. package/dist/commands/enrich_command.d.ts.map +1 -0
  57. package/dist/commands/enrich_command.js +64 -0
  58. package/dist/commands/enrich_command.js.map +1 -0
  59. package/dist/commands/extract_command.d.ts.map +1 -1
  60. package/dist/commands/extract_command.js +12 -6
  61. package/dist/commands/extract_command.js.map +1 -1
  62. package/dist/commands/hotspots_command.d.ts +7 -0
  63. package/dist/commands/hotspots_command.d.ts.map +1 -0
  64. package/dist/commands/hotspots_command.js +68 -0
  65. package/dist/commands/hotspots_command.js.map +1 -0
  66. package/dist/commands/install_command.d.ts +15 -6
  67. package/dist/commands/install_command.d.ts.map +1 -1
  68. package/dist/commands/install_command.js +62 -25
  69. package/dist/commands/install_command.js.map +1 -1
  70. package/dist/commands/load_command.d.ts.map +1 -1
  71. package/dist/commands/load_command.js +20 -13
  72. package/dist/commands/load_command.js.map +1 -1
  73. package/dist/commands/neighbors_command.d.ts.map +1 -1
  74. package/dist/commands/neighbors_command.js +6 -5
  75. package/dist/commands/neighbors_command.js.map +1 -1
  76. package/dist/commands/references_command.d.ts.map +1 -1
  77. package/dist/commands/references_command.js +6 -5
  78. package/dist/commands/references_command.js.map +1 -1
  79. package/dist/commands/report_command.d.ts +16 -0
  80. package/dist/commands/report_command.d.ts.map +1 -0
  81. package/dist/commands/report_command.js +115 -0
  82. package/dist/commands/report_command.js.map +1 -0
  83. package/dist/commands/verify_command.d.ts +8 -0
  84. package/dist/commands/verify_command.d.ts.map +1 -0
  85. package/dist/commands/verify_command.js +57 -0
  86. package/dist/commands/verify_command.js.map +1 -0
  87. package/dist/commands/web_command.d.ts +27 -0
  88. package/dist/commands/web_command.d.ts.map +1 -1
  89. package/dist/commands/web_command.js +109 -3
  90. package/dist/commands/web_command.js.map +1 -1
  91. package/dist/commands/webview_command.d.ts +36 -0
  92. package/dist/commands/webview_command.d.ts.map +1 -0
  93. package/dist/commands/webview_command.js +186 -0
  94. package/dist/commands/webview_command.js.map +1 -0
  95. package/dist/enrich/cpu_profile.d.ts +160 -0
  96. package/dist/enrich/cpu_profile.d.ts.map +1 -0
  97. package/dist/enrich/cpu_profile.js +185 -0
  98. package/dist/enrich/cpu_profile.js.map +1 -0
  99. package/dist/enrich/runtime_enricher.d.ts +64 -0
  100. package/dist/enrich/runtime_enricher.d.ts.map +1 -0
  101. package/dist/enrich/runtime_enricher.js +98 -0
  102. package/dist/enrich/runtime_enricher.js.map +1 -0
  103. package/dist/enrich/runtime_join.d.ts +124 -0
  104. package/dist/enrich/runtime_join.d.ts.map +1 -0
  105. package/dist/enrich/runtime_join.js +270 -0
  106. package/dist/enrich/runtime_join.js.map +1 -0
  107. package/dist/extract/api_extractor.d.ts +24 -0
  108. package/dist/extract/api_extractor.d.ts.map +1 -0
  109. package/dist/extract/api_extractor.js +71 -0
  110. package/dist/extract/api_extractor.js.map +1 -0
  111. package/dist/extract/config_extractor.d.ts +22 -0
  112. package/dist/extract/config_extractor.d.ts.map +1 -0
  113. package/dist/extract/config_extractor.js +61 -0
  114. package/dist/extract/config_extractor.js.map +1 -0
  115. package/dist/extract/endpoint_extractor.d.ts +36 -0
  116. package/dist/extract/endpoint_extractor.d.ts.map +1 -0
  117. package/dist/extract/endpoint_extractor.js +117 -0
  118. package/dist/extract/endpoint_extractor.js.map +1 -0
  119. package/dist/extract/git_source.d.ts +23 -0
  120. package/dist/extract/git_source.d.ts.map +1 -0
  121. package/dist/extract/git_source.js +75 -0
  122. package/dist/extract/git_source.js.map +1 -0
  123. package/dist/extract/graph_builder.d.ts +8 -0
  124. package/dist/extract/graph_builder.d.ts.map +1 -1
  125. package/dist/extract/graph_builder.js +23 -1
  126. package/dist/extract/graph_builder.js.map +1 -1
  127. package/dist/extract/node_id.d.ts +16 -0
  128. package/dist/extract/node_id.d.ts.map +1 -1
  129. package/dist/extract/node_id.js +22 -0
  130. package/dist/extract/node_id.js.map +1 -1
  131. package/dist/extract/scope_resolver.d.ts +22 -0
  132. package/dist/extract/scope_resolver.d.ts.map +1 -0
  133. package/dist/extract/scope_resolver.js +53 -0
  134. package/dist/extract/scope_resolver.js.map +1 -0
  135. package/dist/extract/semantic_extractor.d.ts +25 -0
  136. package/dist/extract/semantic_extractor.d.ts.map +1 -1
  137. package/dist/extract/semantic_extractor.js +96 -2
  138. package/dist/extract/semantic_extractor.js.map +1 -1
  139. package/dist/extract/structural_extractor.d.ts +6 -0
  140. package/dist/extract/structural_extractor.d.ts.map +1 -1
  141. package/dist/extract/structural_extractor.js +22 -12
  142. package/dist/extract/structural_extractor.js.map +1 -1
  143. package/dist/project_root.d.ts +7 -0
  144. package/dist/project_root.d.ts.map +1 -0
  145. package/dist/project_root.js +9 -0
  146. package/dist/project_root.js.map +1 -0
  147. package/dist/query/graph_query.d.ts +269 -0
  148. package/dist/query/graph_query.d.ts.map +1 -1
  149. package/dist/query/graph_query.js +585 -11
  150. package/dist/query/graph_query.js.map +1 -1
  151. package/dist/report/graph_report.d.ts +51 -0
  152. package/dist/report/graph_report.d.ts.map +1 -0
  153. package/dist/report/graph_report.js +312 -0
  154. package/dist/report/graph_report.js.map +1 -0
  155. package/dist/report/pdf_renderer.d.ts +22 -0
  156. package/dist/report/pdf_renderer.d.ts.map +1 -0
  157. package/dist/report/pdf_renderer.js +54 -0
  158. package/dist/report/pdf_renderer.js.map +1 -0
  159. package/dist/report/report_data.d.ts +128 -0
  160. package/dist/report/report_data.d.ts.map +1 -0
  161. package/dist/report/report_data.js +191 -0
  162. package/dist/report/report_data.js.map +1 -0
  163. package/dist/schema/edge.d.ts +40 -5
  164. package/dist/schema/edge.d.ts.map +1 -1
  165. package/dist/schema/edge.js +73 -0
  166. package/dist/schema/edge.js.map +1 -1
  167. package/dist/schema/node.d.ts +20 -5
  168. package/dist/schema/node.d.ts.map +1 -1
  169. package/dist/schema/node.js +36 -0
  170. package/dist/schema/node.js.map +1 -1
  171. package/dist/schema/runtime_manifest.d.ts +36 -0
  172. package/dist/schema/runtime_manifest.d.ts.map +1 -0
  173. package/dist/schema/runtime_manifest.js +23 -0
  174. package/dist/schema/runtime_manifest.js.map +1 -0
  175. package/dist/schema/source_manifest.d.ts +30 -0
  176. package/dist/schema/source_manifest.d.ts.map +1 -0
  177. package/dist/schema/source_manifest.js +21 -0
  178. package/dist/schema/source_manifest.js.map +1 -0
  179. package/dist/store/jsonl_reader.d.ts +4 -0
  180. package/dist/store/jsonl_reader.d.ts.map +1 -1
  181. package/dist/store/jsonl_reader.js +13 -1
  182. package/dist/store/jsonl_reader.js.map +1 -1
  183. package/dist/store/jsonl_store.d.ts +2 -1
  184. package/dist/store/jsonl_store.d.ts.map +1 -1
  185. package/dist/store/jsonl_store.js +4 -1
  186. package/dist/store/jsonl_store.js.map +1 -1
  187. package/dist/store/kuzu_store.d.ts +59 -0
  188. package/dist/store/kuzu_store.d.ts.map +1 -1
  189. package/dist/store/kuzu_store.js +124 -5
  190. package/dist/store/kuzu_store.js.map +1 -1
  191. package/dist/store/output_folder.d.ts +43 -0
  192. package/dist/store/output_folder.d.ts.map +1 -0
  193. package/dist/store/output_folder.js +61 -0
  194. package/dist/store/output_folder.js.map +1 -0
  195. package/dist/verify/project_verifier.d.ts +85 -0
  196. package/dist/verify/project_verifier.d.ts.map +1 -0
  197. package/dist/verify/project_verifier.js +138 -0
  198. package/dist/verify/project_verifier.js.map +1 -0
  199. package/dotclaude_folder/commands/code-graph-interview.md +123 -0
  200. package/dotclaude_folder/commands/code-graph-optimize.md +65 -0
  201. package/{skills/ts-knowledge-graph → dotclaude_folder/skills/code-graph-query}/SKILL.md +6 -6
  202. package/package.json +99 -10
  203. package/.env-sample +0 -34
  204. package/contribs/web_visualisation/README.md +0 -55
  205. package/contribs/web_visualisation/web/css/style.css +0 -115
  206. package/contribs/web_visualisation/web/data/.gitignore +0 -2
  207. package/contribs/web_visualisation/web/index.html +0 -58
  208. package/contribs/web_visualisation/web/js/app.js +0 -364
  209. package/dist/agent/agent-tools.d.ts +0 -13
  210. package/dist/agent/agent-tools.d.ts.map +0 -1
  211. package/dist/agent/agent-tools.js +0 -153
  212. package/dist/agent/agent-tools.js.map +0 -1
  213. package/dist/agent/agent_tools.d.ts +0 -13
  214. package/dist/agent/agent_tools.d.ts.map +0 -1
  215. package/dist/agent/agent_tools.js +0 -153
  216. package/dist/agent/agent_tools.js.map +0 -1
  217. package/dist/agent/code-editor.d.ts +0 -18
  218. package/dist/agent/code-editor.d.ts.map +0 -1
  219. package/dist/agent/code-editor.js +0 -43
  220. package/dist/agent/code-editor.js.map +0 -1
  221. package/dist/agent/code_editor.d.ts +0 -18
  222. package/dist/agent/code_editor.d.ts.map +0 -1
  223. package/dist/agent/code_editor.js +0 -43
  224. package/dist/agent/code_editor.js.map +0 -1
  225. package/dist/agent/optimizer-agent.d.ts +0 -30
  226. package/dist/agent/optimizer-agent.d.ts.map +0 -1
  227. package/dist/agent/optimizer-agent.js +0 -97
  228. package/dist/agent/optimizer-agent.js.map +0 -1
  229. package/dist/agent/optimizer_agent.d.ts +0 -30
  230. package/dist/agent/optimizer_agent.d.ts.map +0 -1
  231. package/dist/agent/optimizer_agent.js +0 -97
  232. package/dist/agent/optimizer_agent.js.map +0 -1
  233. package/dist/agent/verifier.d.ts +0 -9
  234. package/dist/agent/verifier.d.ts.map +0 -1
  235. package/dist/agent/verifier.js +0 -19
  236. package/dist/agent/verifier.js.map +0 -1
  237. package/dist/commands/blast-radius.d.ts +0 -5
  238. package/dist/commands/blast-radius.d.ts.map +0 -1
  239. package/dist/commands/blast-radius.js +0 -18
  240. package/dist/commands/blast-radius.js.map +0 -1
  241. package/dist/commands/blast_radius.d.ts +0 -5
  242. package/dist/commands/blast_radius.d.ts.map +0 -1
  243. package/dist/commands/blast_radius.js +0 -18
  244. package/dist/commands/blast_radius.js.map +0 -1
  245. package/dist/commands/calls.d.ts +0 -5
  246. package/dist/commands/calls.d.ts.map +0 -1
  247. package/dist/commands/calls.js +0 -7
  248. package/dist/commands/calls.js.map +0 -1
  249. package/dist/commands/command-helpers.d.ts +0 -15
  250. package/dist/commands/command-helpers.d.ts.map +0 -1
  251. package/dist/commands/command-helpers.js +0 -61
  252. package/dist/commands/command-helpers.js.map +0 -1
  253. package/dist/commands/dead-exports.d.ts +0 -5
  254. package/dist/commands/dead-exports.d.ts.map +0 -1
  255. package/dist/commands/dead-exports.js +0 -7
  256. package/dist/commands/dead-exports.js.map +0 -1
  257. package/dist/commands/dead_exports.d.ts +0 -5
  258. package/dist/commands/dead_exports.d.ts.map +0 -1
  259. package/dist/commands/dead_exports.js +0 -7
  260. package/dist/commands/dead_exports.js.map +0 -1
  261. package/dist/commands/extract.d.ts +0 -8
  262. package/dist/commands/extract.d.ts.map +0 -1
  263. package/dist/commands/extract.js +0 -49
  264. package/dist/commands/extract.js.map +0 -1
  265. package/dist/commands/find.d.ts +0 -5
  266. package/dist/commands/find.d.ts.map +0 -1
  267. package/dist/commands/find.js +0 -7
  268. package/dist/commands/find.js.map +0 -1
  269. package/dist/commands/load.d.ts.map +0 -1
  270. package/dist/commands/load.js +0 -28
  271. package/dist/commands/load.js.map +0 -1
  272. package/dist/commands/neighbors.d.ts +0 -5
  273. package/dist/commands/neighbors.d.ts.map +0 -1
  274. package/dist/commands/neighbors.js +0 -17
  275. package/dist/commands/neighbors.js.map +0 -1
  276. package/dist/commands/optimize.d.ts +0 -6
  277. package/dist/commands/optimize.d.ts.map +0 -1
  278. package/dist/commands/optimize.js +0 -59
  279. package/dist/commands/optimize.js.map +0 -1
  280. package/dist/commands/optimize_command.d.ts +0 -6
  281. package/dist/commands/optimize_command.d.ts.map +0 -1
  282. package/dist/commands/optimize_command.js +0 -59
  283. package/dist/commands/optimize_command.js.map +0 -1
  284. package/dist/commands/references.d.ts +0 -5
  285. package/dist/commands/references.d.ts.map +0 -1
  286. package/dist/commands/references.js +0 -17
  287. package/dist/commands/references.js.map +0 -1
  288. package/dist/commands/web.d.ts +0 -19
  289. package/dist/commands/web.d.ts.map +0 -1
  290. package/dist/commands/web.js +0 -120
  291. package/dist/commands/web.js.map +0 -1
  292. package/dist/commands/who-calls.d.ts +0 -5
  293. package/dist/commands/who-calls.d.ts.map +0 -1
  294. package/dist/commands/who-calls.js +0 -7
  295. package/dist/commands/who-calls.js.map +0 -1
  296. package/dist/commands/who_calls.d.ts +0 -5
  297. package/dist/commands/who_calls.d.ts.map +0 -1
  298. package/dist/commands/who_calls.js +0 -7
  299. package/dist/commands/who_calls.js.map +0 -1
  300. package/dist/extract/graph-builder.d.ts +0 -16
  301. package/dist/extract/graph-builder.d.ts.map +0 -1
  302. package/dist/extract/graph-builder.js +0 -39
  303. package/dist/extract/graph-builder.js.map +0 -1
  304. package/dist/extract/node-id.d.ts +0 -8
  305. package/dist/extract/node-id.d.ts.map +0 -1
  306. package/dist/extract/node-id.js +0 -22
  307. package/dist/extract/node-id.js.map +0 -1
  308. package/dist/extract/project-loader.d.ts +0 -5
  309. package/dist/extract/project-loader.d.ts.map +0 -1
  310. package/dist/extract/project-loader.js +0 -19
  311. package/dist/extract/project-loader.js.map +0 -1
  312. package/dist/extract/semantic-extractor.d.ts +0 -22
  313. package/dist/extract/semantic-extractor.d.ts.map +0 -1
  314. package/dist/extract/semantic-extractor.js +0 -254
  315. package/dist/extract/semantic-extractor.js.map +0 -1
  316. package/dist/extract/structural-extractor.d.ts +0 -18
  317. package/dist/extract/structural-extractor.d.ts.map +0 -1
  318. package/dist/extract/structural-extractor.js +0 -97
  319. package/dist/extract/structural-extractor.js.map +0 -1
  320. package/dist/query/graph-query.d.ts +0 -28
  321. package/dist/query/graph-query.d.ts.map +0 -1
  322. package/dist/query/graph-query.js +0 -93
  323. package/dist/query/graph-query.js.map +0 -1
  324. package/dist/store/jsonl-reader.d.ts +0 -11
  325. package/dist/store/jsonl-reader.d.ts.map +0 -1
  326. package/dist/store/jsonl-reader.js +0 -19
  327. package/dist/store/jsonl-reader.js.map +0 -1
  328. package/dist/store/jsonl-store.d.ts +0 -7
  329. package/dist/store/jsonl-store.d.ts.map +0 -1
  330. package/dist/store/jsonl-store.js +0 -13
  331. package/dist/store/jsonl-store.js.map +0 -1
  332. package/dist/store/kuzu-store.d.ts +0 -14
  333. package/dist/store/kuzu-store.d.ts.map +0 -1
  334. package/dist/store/kuzu-store.js +0 -52
  335. package/dist/store/kuzu-store.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"graph_query.d.ts","sourceRoot":"","sources":["../../src/query/graph_query.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEnD,MAAM,MAAM,SAAS,GAAG;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,IAAI,GAAG,KAAK,CAAC;CACxB,CAAC;AAOF,qBAAa,UAAU;IACtB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAY;gBAEtB,KAAK,EAAE,SAAS;IAItB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAW1C,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAWvC,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAW5D,WAAW,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;IAiBnC,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAW9C,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAiBhD,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAYjD,OAAO,CAAC,MAAM,CAAC,MAAM;IAIrB,OAAO,CAAC,MAAM,CAAC,KAAK;IAUpB,OAAO,CAAC,MAAM,CAAC,UAAU;IAIzB,OAAO,CAAC,MAAM,CAAC,UAAU;CAUzB"}
1
+ {"version":3,"file":"graph_query.d.ts","sourceRoot":"","sources":["../../src/query/graph_query.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,19 @@
1
- const REFERENCE_EDGE_KINDS = "['CALLS', 'IMPLEMENTS', 'EXTENDS', 'USES_TYPE', 'RETURNS', 'PARAM_TYPE', 'INSTANTIATES', 'READS']";
2
- const RETURN_REF = (variable) => `${variable}.id AS id, ${variable}.kind AS kind, ${variable}.name AS name, ${variable}.filePath AS filePath, ${variable}.startLine AS startLine`;
1
+ import { REFERENCE_EDGE_KINDS } from '../schema/edge.js';
2
+ import { RUNTIME_MANIFEST_KEY, RuntimeManifestSchema } from '../schema/runtime_manifest.js';
3
+ /**
4
+ * The {@link REFERENCE_EDGE_KINDS} set rendered as a Cypher list literal — e.g.
5
+ * `['CALLS', 'EXTENDS', …]` — for interpolation into `kind IN …` predicates. The
6
+ * schema array is the single source of truth; this is its query-side projection.
7
+ */
8
+ const REFERENCE_EDGE_KINDS_CYPHER = `[${REFERENCE_EDGE_KINDS.map((kind) => `'${kind}'`).join(', ')}]`;
9
+ const RETURN_REF = (variable) => `${variable}.id AS id, ${variable}.kind AS kind, ${variable}.name AS name, ${variable}.filePath AS filePath, ${variable}.startLine AS startLine, ${variable}.metadata AS metadata`;
10
+ /**
11
+ * Kùzu's hard ceiling on the upper bound of a variable-length relationship: a
12
+ * pattern like `-[e:Edge*1..N]-` with `N > 30` is rejected by the binder at query
13
+ * time. {@link GraphQuery.clampDepth} caps `--depth` here so the bound it
14
+ * interpolates into {@link GraphQuery.blastRadius} is always one Kùzu accepts.
15
+ */
16
+ const KUZU_MAX_REL_BOUND = 30;
3
17
  export class GraphQuery {
4
18
  constructor(store) {
5
19
  this.store = store;
@@ -29,41 +43,576 @@ export class GraphQuery {
29
43
  const rows = await this.store.run(`MATCH (n:GraphNode)
30
44
  WHERE n.exported = true
31
45
  OPTIONAL MATCH (n)<-[selfRef:Edge]-(:GraphNode)
32
- WHERE selfRef.kind IN ${REFERENCE_EDGE_KINDS}
46
+ WHERE selfRef.kind IN ${REFERENCE_EDGE_KINDS_CYPHER}
33
47
  WITH n, count(selfRef) AS selfRefs
34
48
  OPTIONAL MATCH (n)-[c:Edge]->(member:GraphNode)<-[memberRef:Edge]-(:GraphNode)
35
- WHERE c.kind = 'CONTAINS' AND memberRef.kind IN ${REFERENCE_EDGE_KINDS}
49
+ WHERE c.kind = 'CONTAINS' AND memberRef.kind IN ${REFERENCE_EDGE_KINDS_CYPHER}
36
50
  WITH n, selfRefs, count(memberRef) AS memberRefs
37
51
  WHERE selfRefs = 0 AND memberRefs = 0
38
52
  RETURN ${RETURN_REF('n')}
39
53
  ORDER BY filePath, startLine`);
40
54
  return GraphQuery.toRefs(rows);
41
55
  }
56
+ /**
57
+ * Returns the call cycles in the graph: every strongly-connected component of
58
+ * the static `CALLS` graph with more than one member, as the list of its
59
+ * symbols, largest cycle first. A purely static structural signal — it needs no
60
+ * runtime data — that surfaces mutual-recursion tangles which are hard to spot
61
+ * by eye. Reuses the same SCC pass {@link GraphQuery.computeCostModel} relies on.
62
+ */
63
+ async strongCycles() {
64
+ const nodes = await this.store.readNodes();
65
+ const edges = await this.readCallEdges();
66
+ const indexOf = new Map();
67
+ nodes.forEach((node, index) => indexOf.set(node.id, index));
68
+ const successors = nodes.map(() => []);
69
+ for (const edge of edges) {
70
+ const from = indexOf.get(edge.fromId);
71
+ const to = indexOf.get(edge.toId);
72
+ if (from === undefined || to === undefined || from === to) {
73
+ continue;
74
+ }
75
+ successors[from].push(to);
76
+ }
77
+ const { componentOf, componentCount } = GraphQuery.stronglyConnectedComponents(successors);
78
+ const members = Array.from({ length: componentCount }, () => []);
79
+ componentOf.forEach((component, index) => members[component].push(index));
80
+ return members
81
+ .filter((group) => group.length > 1)
82
+ .map((group) => group.map((index) => GraphQuery.symbolOf(nodes[index])))
83
+ .sort((a, b) => b.length - a.length);
84
+ }
42
85
  async references(id) {
43
86
  const rows = await this.store.run(`MATCH (n:GraphNode {id: $id})<-[e:Edge]-(other:GraphNode)
44
- WHERE e.kind IN ${REFERENCE_EDGE_KINDS}
45
- RETURN ${RETURN_REF('other')}, e.kind AS edgeKind
87
+ WHERE e.kind IN ${REFERENCE_EDGE_KINDS_CYPHER}
88
+ RETURN ${RETURN_REF('other')}, e.kind AS edgeKind, e.metadata AS edgeMetadata
46
89
  ORDER BY edgeKind, filePath, startLine`, { id });
47
90
  return rows.map((row) => GraphQuery.toNeighbor(row, 'in'));
48
91
  }
49
92
  async neighborhood(id) {
50
93
  const outgoing = await this.store.run(`MATCH (center:GraphNode {id: $id})-[e:Edge]->(other:GraphNode)
51
- RETURN ${RETURN_REF('other')}, e.kind AS edgeKind`, { id });
94
+ RETURN ${RETURN_REF('other')}, e.kind AS edgeKind, e.metadata AS edgeMetadata`, { id });
52
95
  const incoming = await this.store.run(`MATCH (center:GraphNode {id: $id})<-[e:Edge]-(other:GraphNode)
53
- RETURN ${RETURN_REF('other')}, e.kind AS edgeKind`, { id });
96
+ RETURN ${RETURN_REF('other')}, e.kind AS edgeKind, e.metadata AS edgeMetadata`, { id });
54
97
  return [
55
98
  ...outgoing.map((row) => GraphQuery.toNeighbor(row, 'out')),
56
99
  ...incoming.map((row) => GraphQuery.toNeighbor(row, 'in')),
57
100
  ];
58
101
  }
102
+ /**
103
+ * Resolves a search pattern to nodes, matching either a substring of the node
104
+ * name or an exact (case-insensitive) node kind. The kind match makes the
105
+ * system-level kinds discoverable as a set — `find Endpoint`, `find ConfigFlag`,
106
+ * `find ExternalAPI` — since their names are values (a route path, a host) rather
107
+ * than the kind itself. `Module` nodes are always excluded.
108
+ */
59
109
  async find(pattern) {
60
110
  const rows = await this.store.run(`MATCH (n:GraphNode)
61
- WHERE n.kind <> 'Module' AND lower(n.name) CONTAINS lower($pattern)
111
+ WHERE n.kind <> 'Module' AND (lower(n.name) CONTAINS lower($pattern) OR lower(n.kind) = lower($pattern))
62
112
  RETURN ${RETURN_REF('n')}
63
113
  ORDER BY filePath, startLine
64
114
  LIMIT 50`, { pattern });
65
115
  return GraphQuery.toRefs(rows);
66
116
  }
117
+ /**
118
+ * Ranks nodes by optimization leverage, returning the top-N for a chosen
119
+ * metric alongside the context needed to read the result.
120
+ *
121
+ * Runtime metrics (`self-time`, `samples`) read `metadata.runtime`; static
122
+ * metrics (`callers`, `call-count`, `blast-radius`) are derived from the
123
+ * inbound `CALLS` graph. The whole graph is read once (nodes, and the call
124
+ * edges when a static metric is used) and ranked in memory rather than per
125
+ * node, so `blast-radius` does not fan out into one traversal per node.
126
+ *
127
+ * The default metric is `self-time` on an enriched graph and `callers`
128
+ * otherwise. Asking for a runtime metric on an un-enriched graph does not
129
+ * return empty: it falls back to `callers` and flags `fellBack` so the caller
130
+ * can say so. Nodes that score zero on the chosen metric are omitted — a
131
+ * symbol nothing calls is not a fan-in hotspot.
132
+ */
133
+ async hotspots(options = {}) {
134
+ const nodes = await this.store.readNodes();
135
+ const enriched = nodes.some((node) => GraphQuery.hasRuntime(node.metadata));
136
+ const requested = options.by ?? (enriched === true ? 'self-time' : 'callers');
137
+ const fellBack = GraphQuery.isRuntimeMetric(requested) === true && enriched === false;
138
+ const metric = fellBack === true ? 'callers' : requested;
139
+ const measuredOnly = options.measuredOnly === true;
140
+ const limit = GraphQuery.clampLimit(options.limit);
141
+ const scores = await this.scoreNodes(nodes, metric);
142
+ const candidates = measuredOnly === true
143
+ ? nodes.filter((node) => GraphQuery.hasRuntime(node.metadata))
144
+ : nodes;
145
+ const hotspots = candidates
146
+ .map((node) => ({ node, score: scores.get(node.id) ?? 0 }))
147
+ .filter((entry) => entry.score > 0)
148
+ .sort((a, b) => b.score - a.score
149
+ || a.node.filePath.localeCompare(b.node.filePath)
150
+ || a.node.startLine - b.node.startLine)
151
+ .slice(0, limit)
152
+ .map((entry) => GraphQuery.toHotspot(entry.node, entry.score, metric));
153
+ return { metric, requested, enriched, fellBack, measuredOnly, hotspots };
154
+ }
155
+ /**
156
+ * Ranks nodes by **inclusive cost** — self cost plus the cost attributed from
157
+ * everything a node transitively calls — and reports each node's share of the
158
+ * graph's total self cost. This is the causal counterpart to {@link hotspots}'
159
+ * `self-time` ranking: where hotspots asks "where is time spent?", this asks
160
+ * "who is *responsible* for the time spent?".
161
+ *
162
+ * Cost propagates along `CALLS` edges, each callee's inclusive cost partitioned
163
+ * among its callers in proportion to call-site `count`, so cost is conserved (a
164
+ * diamond is not double-counted) and `shareOfTotal` is a true fraction. Call
165
+ * cycles are collapsed and their members share the cycle's total. The whole
166
+ * graph is read once and propagated in memory.
167
+ *
168
+ * Cost is inherently a runtime quantity: on an un-enriched graph there is no
169
+ * self cost to propagate, so `nodes` is empty and `enriched` is false — there is
170
+ * no static fallback (unlike {@link hotspots}).
171
+ */
172
+ async costRanking(options = {}) {
173
+ const nodes = await this.store.readNodes();
174
+ const enriched = nodes.some((node) => GraphQuery.hasRuntime(node.metadata));
175
+ const metric = options.by ?? 'self-time';
176
+ const limit = GraphQuery.clampLimit(options.limit);
177
+ const flow = await this.resolveFlowEdges(options.edges ?? 'static');
178
+ const manifest = await this.readRuntimeManifest();
179
+ const model = GraphQuery.computeCostModel(nodes, flow.edges, metric);
180
+ const ranked = nodes
181
+ .map((node) => GraphQuery.toCostRef(node, model))
182
+ .filter((ref) => ref.inclusiveCost > 0)
183
+ .sort((a, b) => b.inclusiveCost - a.inclusiveCost
184
+ || a.filePath.localeCompare(b.filePath)
185
+ || a.startLine - b.startLine)
186
+ .slice(0, limit);
187
+ return {
188
+ metric,
189
+ edges: flow.flow,
190
+ fellBack: flow.fellBack,
191
+ enriched,
192
+ totalSelf: model.totalSelf,
193
+ measuredNodes: model.measuredNodes,
194
+ coverage: GraphQuery.coverageFor(manifest, metric),
195
+ nodes: ranked,
196
+ };
197
+ }
198
+ /**
199
+ * Breaks one node's cost down causally: where its inclusive cost goes
200
+ * (`callees`, each carrying the cost its subtree contributes) and who is
201
+ * responsible for it (`callers`, how the node's cost is attributed upward by
202
+ * call-count share). Both are derived from the same propagation
203
+ * {@link costRanking} uses. Returns `node: null` when the id resolves to no node.
204
+ */
205
+ async costAttribution(id, options = {}) {
206
+ const nodes = await this.store.readNodes();
207
+ const enriched = nodes.some((node) => GraphQuery.hasRuntime(node.metadata));
208
+ const metric = options.by ?? 'self-time';
209
+ const flow = await this.resolveFlowEdges(options.edges ?? 'static');
210
+ const manifest = await this.readRuntimeManifest();
211
+ const model = GraphQuery.computeCostModel(nodes, flow.edges, metric);
212
+ const nodeById = new Map(nodes.map((node) => [node.id, node]));
213
+ const coverage = GraphQuery.coverageFor(manifest, metric);
214
+ const focal = nodeById.get(id);
215
+ if (focal === undefined) {
216
+ return { metric, edges: flow.flow, fellBack: flow.fellBack, enriched, totalSelf: model.totalSelf, coverage, node: null, callees: [], callers: [] };
217
+ }
218
+ const focalRef = GraphQuery.toCostRef(focal, model);
219
+ const focalComponent = model.componentOf.get(id);
220
+ const focalInclusive = focalRef.inclusiveCost;
221
+ const focalInbound = model.externalInbound.get(id) ?? 0;
222
+ const callees = [];
223
+ for (const edge of model.outEdges.get(id) ?? []) {
224
+ if (model.componentOf.get(edge.toId) === focalComponent) {
225
+ continue;
226
+ }
227
+ const callee = nodeById.get(edge.toId);
228
+ if (callee === undefined) {
229
+ continue;
230
+ }
231
+ const inbound = model.externalInbound.get(edge.toId) ?? 0;
232
+ const calleeInclusive = model.inclusiveCost.get(edge.toId) ?? 0;
233
+ const amount = inbound > 0 ? calleeInclusive * edge.count / inbound : 0;
234
+ const share = focalInclusive > 0 ? amount / focalInclusive : 0;
235
+ callees.push({ ...GraphQuery.symbolOf(callee), amount, share, callCount: edge.count });
236
+ }
237
+ const callers = [];
238
+ for (const edge of model.inEdges.get(id) ?? []) {
239
+ if (model.componentOf.get(edge.fromId) === focalComponent) {
240
+ continue;
241
+ }
242
+ const caller = nodeById.get(edge.fromId);
243
+ if (caller === undefined) {
244
+ continue;
245
+ }
246
+ const amount = focalInbound > 0 ? focalInclusive * edge.count / focalInbound : 0;
247
+ const share = focalInbound > 0 ? edge.count / focalInbound : 0;
248
+ callers.push({ ...GraphQuery.symbolOf(caller), amount, share, callCount: edge.count });
249
+ }
250
+ callees.sort((a, b) => b.amount - a.amount || a.filePath.localeCompare(b.filePath) || a.startLine - b.startLine);
251
+ callers.sort((a, b) => b.amount - a.amount || a.filePath.localeCompare(b.filePath) || a.startLine - b.startLine);
252
+ return { metric, edges: flow.flow, fellBack: flow.fellBack, enriched, totalSelf: model.totalSelf, coverage, node: focalRef, callees, callers };
253
+ }
254
+ /**
255
+ * Reads the runtime ingest manifest `enrich` records at the graph level, or
256
+ * null when the graph carries none (never enriched, or enriched before manifests
257
+ * were recorded). A stored value that fails validation is treated as absent.
258
+ */
259
+ async readRuntimeManifest() {
260
+ const raw = await this.store.readGraphMeta(RUNTIME_MANIFEST_KEY);
261
+ if (raw === null) {
262
+ return null;
263
+ }
264
+ const parsed = RuntimeManifestSchema.safeParse(raw);
265
+ return parsed.success === true ? parsed.data : null;
266
+ }
267
+ /** Builds a node-id → score map for the chosen metric, reading call edges only when a static metric needs them. */
268
+ async scoreNodes(nodes, metric) {
269
+ if (metric === 'self-time' || metric === 'samples') {
270
+ const key = metric === 'self-time' ? 'selfMs' : 'samples';
271
+ return new Map(nodes.map((node) => [node.id, GraphQuery.runtimeValue(node.metadata, key)]));
272
+ }
273
+ const edges = await this.readCallEdges();
274
+ if (metric === 'call-count') {
275
+ return GraphQuery.sumInbound(edges, (edge) => edge.count);
276
+ }
277
+ if (metric === 'blast-radius') {
278
+ return GraphQuery.blastRadiusSizes(nodes, edges);
279
+ }
280
+ return GraphQuery.sumInbound(edges, () => 1);
281
+ }
282
+ /** Reads every `CALLS` edge with its call-site `count` decoded from edge metadata. */
283
+ async readCallEdges() {
284
+ const rows = await this.store.run(`MATCH (caller:GraphNode)-[e:Edge]->(callee:GraphNode)
285
+ WHERE e.kind = 'CALLS'
286
+ RETURN caller.id AS fromId, callee.id AS toId, e.metadata AS metadata`);
287
+ return rows.map((row) => ({
288
+ fromId: String(row.fromId),
289
+ toId: String(row.toId),
290
+ count: GraphQuery.callCount(row.metadata),
291
+ }));
292
+ }
293
+ /**
294
+ * Resolves the call graph cost propagates along. `runtime` reads the
295
+ * `CALLS_RUNTIME` edges `enrich` records, weighted by sample flow; when the
296
+ * graph has none (un-enriched, or a profile that captured no in-project calls)
297
+ * it falls back to the static `CALLS` graph and flags it.
298
+ */
299
+ async resolveFlowEdges(requested) {
300
+ if (requested === 'runtime') {
301
+ const runtime = await this.readRuntimeCallEdges();
302
+ if (runtime.length > 0) {
303
+ return { edges: runtime, flow: 'runtime', fellBack: false };
304
+ }
305
+ return { edges: await this.readCallEdges(), flow: 'static', fellBack: true };
306
+ }
307
+ return { edges: await this.readCallEdges(), flow: 'static', fellBack: false };
308
+ }
309
+ /** Reads every `CALLS_RUNTIME` edge with its runtime `samples` as the propagation weight. */
310
+ async readRuntimeCallEdges() {
311
+ const rows = await this.store.run(`MATCH (caller:GraphNode)-[e:Edge]->(callee:GraphNode)
312
+ WHERE e.kind = 'CALLS_RUNTIME'
313
+ RETURN caller.id AS fromId, callee.id AS toId, e.metadata AS metadata`);
314
+ return rows.map((row) => ({
315
+ fromId: String(row.fromId),
316
+ toId: String(row.toId),
317
+ count: GraphQuery.edgeSamples(row.metadata),
318
+ }));
319
+ }
320
+ /** Sums a per-edge weight onto each edge's target, yielding inbound fan-in (`weight = 1`) or call-count (`weight = count`). */
321
+ static sumInbound(edges, weight) {
322
+ const totals = new Map();
323
+ for (const edge of edges) {
324
+ totals.set(edge.toId, (totals.get(edge.toId) ?? 0) + weight(edge));
325
+ }
326
+ return totals;
327
+ }
328
+ /** Computes, for every node, the number of distinct nodes that transitively reach it through inbound `CALLS`. */
329
+ static blastRadiusSizes(nodes, edges) {
330
+ const callers = new Map();
331
+ for (const edge of edges) {
332
+ const bucket = callers.get(edge.toId);
333
+ if (bucket === undefined) {
334
+ callers.set(edge.toId, [edge.fromId]);
335
+ }
336
+ else {
337
+ bucket.push(edge.fromId);
338
+ }
339
+ }
340
+ const sizes = new Map();
341
+ for (const node of nodes) {
342
+ sizes.set(node.id, GraphQuery.reachableCount(node.id, callers));
343
+ }
344
+ return sizes;
345
+ }
346
+ /** Counts the distinct ancestors of `start` over the reverse-call adjacency, cycle-safe and excluding `start` itself. */
347
+ static reachableCount(start, callers) {
348
+ const visited = new Set();
349
+ const stack = [...(callers.get(start) ?? [])];
350
+ while (stack.length > 0) {
351
+ const id = stack.pop();
352
+ if (id === undefined || visited.has(id) === true) {
353
+ continue;
354
+ }
355
+ visited.add(id);
356
+ for (const next of callers.get(id) ?? []) {
357
+ if (visited.has(next) === false) {
358
+ stack.push(next);
359
+ }
360
+ }
361
+ }
362
+ visited.delete(start);
363
+ return visited.size;
364
+ }
365
+ /**
366
+ * Propagates self cost into inclusive cost over the whole graph in one pass.
367
+ *
368
+ * Self-edges and edges touching unknown nodes are dropped. Strongly-connected
369
+ * components (call cycles) are collapsed with Tarjan's algorithm, which numbers
370
+ * them in reverse-topological order (a callee's component before its caller's);
371
+ * processing components in that order lets each caller read its callees'
372
+ * already-computed inclusive cost. A callee's inclusive cost is partitioned
373
+ * among its callers by call-site `count` over the inbound weight from *outside*
374
+ * its component, so cost is conserved on the acyclic part and a cycle's members
375
+ * share the cycle's total.
376
+ */
377
+ static computeCostModel(nodes, edges, metric) {
378
+ const key = metric === 'self-time' ? 'selfMs' : 'samples';
379
+ const count = nodes.length;
380
+ const indexOf = new Map();
381
+ nodes.forEach((node, i) => indexOf.set(node.id, i));
382
+ const self = nodes.map((node) => GraphQuery.runtimeValue(node.metadata, key));
383
+ const successors = nodes.map(() => []);
384
+ const outEdges = nodes.map(() => []);
385
+ const inEdges = nodes.map(() => []);
386
+ for (const edge of edges) {
387
+ const from = indexOf.get(edge.fromId);
388
+ const to = indexOf.get(edge.toId);
389
+ if (from === undefined || to === undefined || from === to) {
390
+ continue;
391
+ }
392
+ successors[from].push(to);
393
+ outEdges[from].push({ to, count: edge.count });
394
+ inEdges[to].push({ from, count: edge.count });
395
+ }
396
+ const { componentOf, componentCount } = GraphQuery.stronglyConnectedComponents(successors);
397
+ const componentSize = new Array(componentCount).fill(0);
398
+ const selfByComponent = new Array(componentCount).fill(0);
399
+ const members = Array.from({ length: componentCount }, () => []);
400
+ for (let i = 0; i < count; i += 1) {
401
+ const comp = componentOf[i];
402
+ componentSize[comp] += 1;
403
+ selfByComponent[comp] += self[i];
404
+ members[comp].push(i);
405
+ }
406
+ const externalInbound = new Array(count).fill(0);
407
+ for (let to = 0; to < count; to += 1) {
408
+ for (const edge of inEdges[to]) {
409
+ if (componentOf[edge.from] !== componentOf[to]) {
410
+ externalInbound[to] += edge.count;
411
+ }
412
+ }
413
+ }
414
+ const inclusiveByComponent = new Array(componentCount).fill(0);
415
+ for (let comp = 0; comp < componentCount; comp += 1) {
416
+ let inclusive = selfByComponent[comp];
417
+ for (const member of members[comp]) {
418
+ for (const edge of outEdges[member]) {
419
+ const target = componentOf[edge.to];
420
+ if (target === comp) {
421
+ continue;
422
+ }
423
+ const inbound = externalInbound[edge.to];
424
+ if (inbound > 0) {
425
+ inclusive += inclusiveByComponent[target] * edge.count / inbound;
426
+ }
427
+ }
428
+ }
429
+ inclusiveByComponent[comp] = inclusive;
430
+ }
431
+ const model = {
432
+ totalSelf: 0,
433
+ measuredNodes: 0,
434
+ selfCost: new Map(),
435
+ inclusiveCost: new Map(),
436
+ componentOf: new Map(),
437
+ cycleSize: new Map(),
438
+ externalInbound: new Map(),
439
+ outEdges: new Map(),
440
+ inEdges: new Map(),
441
+ };
442
+ for (let i = 0; i < count; i += 1) {
443
+ const node = nodes[i];
444
+ const comp = componentOf[i];
445
+ model.totalSelf += self[i];
446
+ if (GraphQuery.hasRuntime(node.metadata) === true) {
447
+ model.measuredNodes += 1;
448
+ }
449
+ model.selfCost.set(node.id, self[i]);
450
+ model.inclusiveCost.set(node.id, inclusiveByComponent[comp]);
451
+ model.componentOf.set(node.id, comp);
452
+ model.cycleSize.set(node.id, componentSize[comp]);
453
+ model.externalInbound.set(node.id, externalInbound[i]);
454
+ model.outEdges.set(node.id, outEdges[i].map((edge) => ({ fromId: node.id, toId: nodes[edge.to].id, count: edge.count })));
455
+ model.inEdges.set(node.id, inEdges[i].map((edge) => ({ fromId: nodes[edge.from].id, toId: node.id, count: edge.count })));
456
+ }
457
+ return model;
458
+ }
459
+ /**
460
+ * Tarjan's strongly-connected components over the index-based successor lists,
461
+ * iterative so a deep call chain cannot overflow the stack. Components are
462
+ * numbered in the order they are finalized, which is reverse-topological: every
463
+ * cross-component edge runs from a higher-numbered component to a lower one.
464
+ */
465
+ static stronglyConnectedComponents(successors) {
466
+ const count = successors.length;
467
+ const index = new Array(count).fill(-1);
468
+ const lowlink = new Array(count).fill(0);
469
+ const onStack = new Array(count).fill(false);
470
+ const componentOf = new Array(count).fill(-1);
471
+ const sccStack = [];
472
+ const workNode = [];
473
+ const workNext = [];
474
+ let counter = 0;
475
+ let componentCount = 0;
476
+ for (let start = 0; start < count; start += 1) {
477
+ if (index[start] !== -1) {
478
+ continue;
479
+ }
480
+ workNode.push(start);
481
+ workNext.push(0);
482
+ index[start] = counter;
483
+ lowlink[start] = counter;
484
+ counter += 1;
485
+ sccStack.push(start);
486
+ onStack[start] = true;
487
+ while (workNode.length > 0) {
488
+ const node = workNode[workNode.length - 1];
489
+ const position = workNext[workNext.length - 1];
490
+ const succ = successors[node];
491
+ if (position < succ.length) {
492
+ workNext[workNext.length - 1] = position + 1;
493
+ const next = succ[position];
494
+ if (index[next] === -1) {
495
+ index[next] = counter;
496
+ lowlink[next] = counter;
497
+ counter += 1;
498
+ sccStack.push(next);
499
+ onStack[next] = true;
500
+ workNode.push(next);
501
+ workNext.push(0);
502
+ }
503
+ else if (onStack[next] === true && index[next] < lowlink[node]) {
504
+ lowlink[node] = index[next];
505
+ }
506
+ continue;
507
+ }
508
+ if (lowlink[node] === index[node]) {
509
+ let member = -1;
510
+ do {
511
+ const popped = sccStack.pop();
512
+ member = popped === undefined ? node : popped;
513
+ onStack[member] = false;
514
+ componentOf[member] = componentCount;
515
+ } while (member !== node);
516
+ componentCount += 1;
517
+ }
518
+ workNode.pop();
519
+ workNext.pop();
520
+ if (workNode.length > 0) {
521
+ const parent = workNode[workNode.length - 1];
522
+ if (lowlink[node] < lowlink[parent]) {
523
+ lowlink[parent] = lowlink[node];
524
+ }
525
+ }
526
+ }
527
+ }
528
+ return { componentOf, componentCount };
529
+ }
530
+ static toCostRef(node, model) {
531
+ const selfCost = model.selfCost.get(node.id) ?? 0;
532
+ const inclusiveCost = model.inclusiveCost.get(node.id) ?? 0;
533
+ const cycleSize = model.cycleSize.get(node.id) ?? 1;
534
+ return {
535
+ ...GraphQuery.symbolOf(node),
536
+ selfCost,
537
+ inclusiveCost,
538
+ shareOfTotal: model.totalSelf > 0 ? inclusiveCost / model.totalSelf : 0,
539
+ cyclic: cycleSize > 1,
540
+ cycleSize,
541
+ };
542
+ }
543
+ static symbolOf(node) {
544
+ return {
545
+ id: node.id,
546
+ kind: node.kind,
547
+ name: node.name,
548
+ filePath: node.filePath,
549
+ startLine: node.startLine,
550
+ metadata: node.metadata,
551
+ };
552
+ }
553
+ /**
554
+ * The fraction of profiled cost (in `metric`) the join attributed to graph
555
+ * nodes, from the manifest: matched ÷ total. Null when there is no manifest or
556
+ * the profile measured nothing in that metric.
557
+ */
558
+ static coverageFor(manifest, metric) {
559
+ if (manifest === null) {
560
+ return null;
561
+ }
562
+ if (metric === 'samples') {
563
+ return manifest.totalSamples > 0 ? manifest.matchedSamples / manifest.totalSamples : null;
564
+ }
565
+ return manifest.totalSelfMicros > 0 ? manifest.matchedSelfMicros / manifest.totalSelfMicros : null;
566
+ }
567
+ static toHotspot(node, score, metric) {
568
+ return {
569
+ id: node.id,
570
+ kind: node.kind,
571
+ name: node.name,
572
+ filePath: node.filePath,
573
+ startLine: node.startLine,
574
+ metadata: node.metadata,
575
+ score,
576
+ metric,
577
+ };
578
+ }
579
+ static isRuntimeMetric(metric) {
580
+ return metric === 'self-time' || metric === 'samples';
581
+ }
582
+ /** Reads a numeric metric out of `metadata.runtime`, defaulting to 0 when absent or non-numeric. */
583
+ static runtimeValue(metadata, key) {
584
+ const runtime = metadata.runtime;
585
+ if (typeof runtime !== 'object' || runtime === null) {
586
+ return 0;
587
+ }
588
+ const value = runtime[key];
589
+ return typeof value === 'number' ? value : 0;
590
+ }
591
+ static hasRuntime(metadata) {
592
+ const runtime = metadata.runtime;
593
+ return typeof runtime === 'object' && runtime !== null;
594
+ }
595
+ /** Decodes an edge's call-site `count`, defaulting to 1 (the minimum the builder records). */
596
+ static callCount(value) {
597
+ const metadata = GraphQuery.parseMetadata(value);
598
+ const count = metadata.count;
599
+ return typeof count === 'number' && count > 0 ? count : 1;
600
+ }
601
+ /** Decodes a runtime call edge's `samples` weight, defaulting to 1 when absent. */
602
+ static edgeSamples(value) {
603
+ const samples = GraphQuery.parseMetadata(value).samples;
604
+ return typeof samples === 'number' && samples >= 0 ? samples : 1;
605
+ }
606
+ static clampLimit(limit) {
607
+ if (limit === undefined || Number.isFinite(limit) === false) {
608
+ return 20;
609
+ }
610
+ const floored = Math.floor(limit);
611
+ if (floored < 1) {
612
+ return 20;
613
+ }
614
+ return floored > 1000 ? 1000 : floored;
615
+ }
67
616
  static toRefs(rows) {
68
617
  return rows.map((row) => GraphQuery.toRef(row));
69
618
  }
@@ -74,10 +623,35 @@ export class GraphQuery {
74
623
  name: String(row.name),
75
624
  filePath: String(row.filePath),
76
625
  startLine: Number(row.startLine),
626
+ metadata: GraphQuery.parseMetadata(row.metadata),
77
627
  };
78
628
  }
79
629
  static toNeighbor(row, direction) {
80
- return { ...GraphQuery.toRef(row), edgeKind: String(row.edgeKind), direction };
630
+ return {
631
+ ...GraphQuery.toRef(row),
632
+ edgeKind: String(row.edgeKind),
633
+ edgeMetadata: GraphQuery.parseMetadata(row.edgeMetadata),
634
+ direction,
635
+ };
636
+ }
637
+ /**
638
+ * Decodes the JSON `metadata` column back into a record. A missing, empty, or
639
+ * malformed value decodes to an empty object so callers always receive a record.
640
+ */
641
+ static parseMetadata(value) {
642
+ if (typeof value !== 'string' || value.length === 0) {
643
+ return {};
644
+ }
645
+ try {
646
+ const parsed = JSON.parse(value);
647
+ if (typeof parsed === 'object' && parsed !== null) {
648
+ return parsed;
649
+ }
650
+ return {};
651
+ }
652
+ catch {
653
+ return {};
654
+ }
81
655
  }
82
656
  static clampDepth(depth) {
83
657
  if (Number.isFinite(depth) === false) {
@@ -87,7 +661,7 @@ export class GraphQuery {
87
661
  if (floored < 1) {
88
662
  return 1;
89
663
  }
90
- return floored > 50 ? 50 : floored;
664
+ return floored > KUZU_MAX_REL_BOUND ? KUZU_MAX_REL_BOUND : floored;
91
665
  }
92
666
  }
93
667
  //# sourceMappingURL=graph_query.js.map