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
@@ -0,0 +1,98 @@
1
+ import { RUNTIME_MANIFEST_KEY } from '../schema/runtime_manifest.js';
2
+ import { CpuProfile } from './cpu_profile.js';
3
+ import { RuntimeJoin } from './runtime_join.js';
4
+ /** The telemetry source tag written into `metadata.runtime.source`. */
5
+ export const RUNTIME_SOURCE_CPU_PROFILE = 'v8-cpuprofile';
6
+ /** Namespaced key under which runtime metrics are stored on a node's metadata. */
7
+ export const RUNTIME_METADATA_KEY = 'runtime';
8
+ /** Edge kind under which the runtime call graph extracted from the profile is stored. */
9
+ export const RUNTIME_CALL_EDGE_KIND = 'CALLS_RUNTIME';
10
+ export class RuntimeEnricher {
11
+ /**
12
+ * Ingests a V8 CPU profile and attaches `metadata.runtime` (self time and
13
+ * sample count) onto the graph nodes whose ranges enclose the profiled
14
+ * frames. Existing metadata is preserved; only the `runtime` key is written,
15
+ * so re-running with the same profile is idempotent. Unmatched frames are
16
+ * counted and returned in the report rather than dropped silently.
17
+ */
18
+ static async enrich(store, profileText, options) {
19
+ const profile = CpuProfile.parse(profileText);
20
+ const frames = CpuProfile.aggregate(profile);
21
+ const nodes = await store.readNodes();
22
+ const result = RuntimeJoin.join(nodes, frames, { root: options.root });
23
+ const nodeById = new Map(nodes.map((node) => [node.id, node]));
24
+ const updates = [];
25
+ const hotspots = [];
26
+ for (const [id, attribution] of result.attributions) {
27
+ const node = nodeById.get(id);
28
+ if (node === undefined) {
29
+ continue;
30
+ }
31
+ const metrics = RuntimeEnricher.toMetrics(attribution);
32
+ updates.push({
33
+ id,
34
+ metadata: { ...node.metadata, [RUNTIME_METADATA_KEY]: metrics },
35
+ });
36
+ hotspots.push({
37
+ id,
38
+ name: node.name,
39
+ kind: node.kind,
40
+ filePath: node.filePath,
41
+ selfMs: metrics.selfMs,
42
+ samples: metrics.samples,
43
+ });
44
+ }
45
+ await store.writeNodeMetadata(updates);
46
+ const manifest = {
47
+ source: RUNTIME_SOURCE_CPU_PROFILE,
48
+ totalSamples: CpuProfile.totalSamples(profile),
49
+ matchedSamples: result.matchedSamples,
50
+ totalSelfMicros: result.matchedSelfMicros + result.droppedSelfMicros,
51
+ matchedSelfMicros: result.matchedSelfMicros,
52
+ };
53
+ await store.writeGraphMeta(RUNTIME_MANIFEST_KEY, manifest);
54
+ const callEdges = CpuProfile.callEdges(profile);
55
+ const edgeResult = RuntimeJoin.joinCallEdges(nodes, callEdges, { root: options.root });
56
+ await store.clearEdgesByKind(RUNTIME_CALL_EDGE_KIND);
57
+ await store.writeEdges(edgeResult.edges.map((edge) => RuntimeEnricher.toCallEdge(edge)));
58
+ hotspots.sort((a, b) => b.selfMs - a.selfMs || b.samples - a.samples);
59
+ return {
60
+ totalSamples: CpuProfile.totalSamples(profile),
61
+ matchedNodes: updates.length,
62
+ matchedFrames: result.matchedFrames,
63
+ matchedSamples: result.matchedSamples,
64
+ matchedSelfMs: RuntimeEnricher.microsToMs(result.matchedSelfMicros),
65
+ matchedByName: result.matchedByName,
66
+ matchedByRange: result.matchedByRange,
67
+ droppedFrames: result.droppedFrames,
68
+ droppedSamples: result.droppedSamples,
69
+ runtimeEdges: edgeResult.matchedEdges,
70
+ droppedCallEdges: edgeResult.droppedEdges,
71
+ dropped: result.dropped,
72
+ hotspots,
73
+ };
74
+ }
75
+ static toMetrics(attribution) {
76
+ return {
77
+ source: RUNTIME_SOURCE_CPU_PROFILE,
78
+ samples: attribution.samples,
79
+ selfMicros: attribution.selfMicros,
80
+ selfMs: RuntimeEnricher.microsToMs(attribution.selfMicros),
81
+ };
82
+ }
83
+ /** Builds a `CALLS_RUNTIME` graph edge from a resolved runtime call edge, weighted by its sample count. */
84
+ static toCallEdge(edge) {
85
+ return {
86
+ id: `${RUNTIME_CALL_EDGE_KIND}:${edge.from}->${edge.to}`,
87
+ kind: RUNTIME_CALL_EDGE_KIND,
88
+ from: edge.from,
89
+ to: edge.to,
90
+ metadata: { source: RUNTIME_SOURCE_CPU_PROFILE, samples: edge.samples },
91
+ };
92
+ }
93
+ /** Converts microseconds to milliseconds, rounded to microsecond precision. */
94
+ static microsToMs(micros) {
95
+ return Math.round(micros) / 1000;
96
+ }
97
+ }
98
+ //# sourceMappingURL=runtime_enricher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime_enricher.js","sourceRoot":"","sources":["../../src/enrich/runtime_enricher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,124 @@
1
+ import { FrameSample, RuntimeCallEdge } from './cpu_profile.js';
2
+ /** A graph node reduced to the fields the join needs. */
3
+ export type RuntimeTargetNode = {
4
+ id: string;
5
+ kind: string;
6
+ name: string;
7
+ filePath: string;
8
+ startLine: number;
9
+ endLine: number;
10
+ };
11
+ /** Self time and sample count attributed to one graph node. */
12
+ export type RuntimeAttribution = {
13
+ samples: number;
14
+ selfMicros: number;
15
+ };
16
+ /** How a frame was attached to its node — by symbol name or by line range. */
17
+ export type MatchVia = 'name' | 'range';
18
+ /**
19
+ * A group of profile frames that could not be attached to a single graph node,
20
+ * aggregated by a human-readable label so coverage gaps are reportable rather
21
+ * than silently lost. `ambiguous` means the frame's name matched several nodes
22
+ * in the file and the (transpiled) line could not break the tie.
23
+ */
24
+ export type DroppedFrameGroup = {
25
+ label: string;
26
+ reason: 'no-file' | 'no-node' | 'ambiguous';
27
+ samples: number;
28
+ selfMicros: number;
29
+ };
30
+ export type RuntimeJoinResult = {
31
+ attributions: Map<string, RuntimeAttribution>;
32
+ matchedFrames: number;
33
+ matchedSamples: number;
34
+ matchedSelfMicros: number;
35
+ matchedByName: number;
36
+ matchedByRange: number;
37
+ droppedFrames: number;
38
+ droppedSamples: number;
39
+ droppedSelfMicros: number;
40
+ dropped: DroppedFrameGroup[];
41
+ };
42
+ export type RuntimeJoinOptions = {
43
+ /** Project root the profile's absolute frame urls are resolved against. */
44
+ root: string;
45
+ };
46
+ /** A runtime call edge resolved onto graph node ids, weighted by the samples that flowed through it. */
47
+ export type RuntimeEdge = {
48
+ from: string;
49
+ to: string;
50
+ samples: number;
51
+ };
52
+ /** The outcome of joining the profile's call-tree edges onto graph nodes. */
53
+ export type RuntimeCallJoinResult = {
54
+ edges: RuntimeEdge[];
55
+ matchedEdges: number;
56
+ droppedEdges: number;
57
+ droppedSamples: number;
58
+ };
59
+ export declare class RuntimeJoin {
60
+ /**
61
+ * Joins profile frames onto graph nodes with a hybrid key that survives
62
+ * transpilation.
63
+ *
64
+ * For each frame the url is resolved to a graph `filePath`, then within that
65
+ * file:
66
+ * 1. **By name** — a frame's `functionName` matched against node names. A
67
+ * unique match wins outright; this is the path that works when a
68
+ * transpiler (tsx/esbuild) has collapsed line numbers, since the function
69
+ * name is still intact.
70
+ * 2. **By range** — when the name is absent or matches nothing, the innermost
71
+ * node whose `[startLine, endLine]` encloses the frame line. This is the
72
+ * precise path for line-preserving runs and also disambiguates a name that
73
+ * matched several nodes.
74
+ *
75
+ * Frames resolving to no in-project file, to a file with no matching node, or
76
+ * to an unbreakable name tie are counted and reported as dropped — never
77
+ * attached.
78
+ */
79
+ static join(nodes: RuntimeTargetNode[], frames: FrameSample[], options: RuntimeJoinOptions): RuntimeJoinResult;
80
+ /**
81
+ * Joins the profile's call-tree edges onto graph node ids, reusing the same
82
+ * per-frame resolution as {@link join} (name match, then enclosing range) for
83
+ * both endpoints. Edges whose caller or callee resolves to nothing, and
84
+ * self-edges (recursion landing on one node), are dropped and counted; the
85
+ * survivors are aggregated by node pair, summing the samples that flowed through
86
+ * each call.
87
+ */
88
+ static joinCallEdges(nodes: RuntimeTargetNode[], callEdges: RuntimeCallEdge[], options: RuntimeJoinOptions): RuntimeCallJoinResult;
89
+ /** Resolves a single frame to its graph node — file path, then name-or-range within that file — or undefined. */
90
+ private static resolveFrame;
91
+ private static indexByFile;
92
+ /**
93
+ * Resolves a frame to a node within its file, preferring a name match and
94
+ * falling back to an enclosing-range match.
95
+ */
96
+ private static resolveNode;
97
+ /**
98
+ * Nodes in the file whose name equals the frame's function name (or its final
99
+ * dotted segment, so `Widget.render` matches a method named `render`). V8
100
+ * synthetic frames such as `(anonymous)` and `(root)` never match.
101
+ */
102
+ private static nameMatches;
103
+ private static isMeaningfulName;
104
+ private static candidateNames;
105
+ /**
106
+ * Picks the innermost node whose range encloses `line`. Ties on range width
107
+ * break toward the later-starting node, which is the more deeply nested
108
+ * declaration. A non-positive line (V8 synthetic frame, or a transpiler that
109
+ * collapsed line numbers) encloses nothing.
110
+ */
111
+ private static enclosingNode;
112
+ /**
113
+ * Maps a profile frame url to a graph `filePath`. Node-internal and empty
114
+ * urls (`node:`, `(root)`, `(idle)`) resolve to nothing. A url inside `root`
115
+ * resolves by relative path; otherwise a unique path-suffix match is used so
116
+ * a graph extracted under a different absolute prefix still attaches.
117
+ */
118
+ private static resolveFilePath;
119
+ private static urlToAbsolute;
120
+ private static toPosix;
121
+ private static recordDrop;
122
+ private static dropLabel;
123
+ }
124
+ //# sourceMappingURL=runtime_join.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime_join.d.ts","sourceRoot":"","sources":["../../src/enrich/runtime_join.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,270 @@
1
+ import { isAbsolute, relative, sep } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ /**
4
+ * Node kinds a CPU frame can plausibly name. Restricting name matching to these
5
+ * avoids attaching an executing frame to a same-named type or enum member.
6
+ */
7
+ const NAME_MATCH_KINDS = new Set(['Function', 'Method', 'Property', 'Variable', 'Class']);
8
+ export class RuntimeJoin {
9
+ /**
10
+ * Joins profile frames onto graph nodes with a hybrid key that survives
11
+ * transpilation.
12
+ *
13
+ * For each frame the url is resolved to a graph `filePath`, then within that
14
+ * file:
15
+ * 1. **By name** — a frame's `functionName` matched against node names. A
16
+ * unique match wins outright; this is the path that works when a
17
+ * transpiler (tsx/esbuild) has collapsed line numbers, since the function
18
+ * name is still intact.
19
+ * 2. **By range** — when the name is absent or matches nothing, the innermost
20
+ * node whose `[startLine, endLine]` encloses the frame line. This is the
21
+ * precise path for line-preserving runs and also disambiguates a name that
22
+ * matched several nodes.
23
+ *
24
+ * Frames resolving to no in-project file, to a file with no matching node, or
25
+ * to an unbreakable name tie are counted and reported as dropped — never
26
+ * attached.
27
+ */
28
+ static join(nodes, frames, options) {
29
+ const byFile = RuntimeJoin.indexByFile(nodes);
30
+ const filePaths = [...byFile.keys()];
31
+ const attributions = new Map();
32
+ const dropped = new Map();
33
+ let matchedFrames = 0;
34
+ let matchedSamples = 0;
35
+ let matchedSelfMicros = 0;
36
+ let matchedByName = 0;
37
+ let matchedByRange = 0;
38
+ let droppedFrames = 0;
39
+ let droppedSamples = 0;
40
+ let droppedSelfMicros = 0;
41
+ for (const frame of frames) {
42
+ const filePath = RuntimeJoin.resolveFilePath(frame.url, options.root, byFile, filePaths);
43
+ if (filePath === undefined) {
44
+ RuntimeJoin.recordDrop(dropped, frame, 'no-file');
45
+ droppedFrames += 1;
46
+ droppedSamples += frame.samples;
47
+ droppedSelfMicros += frame.selfMicros;
48
+ continue;
49
+ }
50
+ const resolution = RuntimeJoin.resolveNode(byFile.get(filePath) ?? [], frame);
51
+ if (resolution.node === undefined) {
52
+ RuntimeJoin.recordDrop(dropped, frame, resolution.reason);
53
+ droppedFrames += 1;
54
+ droppedSamples += frame.samples;
55
+ droppedSelfMicros += frame.selfMicros;
56
+ continue;
57
+ }
58
+ const current = attributions.get(resolution.node.id) ?? { samples: 0, selfMicros: 0 };
59
+ current.samples += frame.samples;
60
+ current.selfMicros += frame.selfMicros;
61
+ attributions.set(resolution.node.id, current);
62
+ matchedFrames += 1;
63
+ matchedSamples += frame.samples;
64
+ matchedSelfMicros += frame.selfMicros;
65
+ if (resolution.via === 'name') {
66
+ matchedByName += 1;
67
+ }
68
+ else {
69
+ matchedByRange += 1;
70
+ }
71
+ }
72
+ return {
73
+ attributions,
74
+ matchedFrames,
75
+ matchedSamples,
76
+ matchedSelfMicros,
77
+ matchedByName,
78
+ matchedByRange,
79
+ droppedFrames,
80
+ droppedSamples,
81
+ droppedSelfMicros,
82
+ dropped: [...dropped.values()].sort((a, b) => b.selfMicros - a.selfMicros),
83
+ };
84
+ }
85
+ /**
86
+ * Joins the profile's call-tree edges onto graph node ids, reusing the same
87
+ * per-frame resolution as {@link join} (name match, then enclosing range) for
88
+ * both endpoints. Edges whose caller or callee resolves to nothing, and
89
+ * self-edges (recursion landing on one node), are dropped and counted; the
90
+ * survivors are aggregated by node pair, summing the samples that flowed through
91
+ * each call.
92
+ */
93
+ static joinCallEdges(nodes, callEdges, options) {
94
+ const byFile = RuntimeJoin.indexByFile(nodes);
95
+ const filePaths = [...byFile.keys()];
96
+ const aggregated = new Map();
97
+ let matchedEdges = 0;
98
+ let droppedEdges = 0;
99
+ let droppedSamples = 0;
100
+ for (const edge of callEdges) {
101
+ const from = RuntimeJoin.resolveFrame(edge.caller, byFile, filePaths, options.root);
102
+ const to = RuntimeJoin.resolveFrame(edge.callee, byFile, filePaths, options.root);
103
+ if (from === undefined || to === undefined || from.id === to.id) {
104
+ droppedEdges += 1;
105
+ droppedSamples += edge.samples;
106
+ continue;
107
+ }
108
+ const key = `${from.id}->${to.id}`;
109
+ const existing = aggregated.get(key);
110
+ if (existing === undefined) {
111
+ aggregated.set(key, { from: from.id, to: to.id, samples: edge.samples });
112
+ }
113
+ else {
114
+ existing.samples += edge.samples;
115
+ }
116
+ matchedEdges += 1;
117
+ }
118
+ return { edges: [...aggregated.values()], matchedEdges, droppedEdges, droppedSamples };
119
+ }
120
+ /** Resolves a single frame to its graph node — file path, then name-or-range within that file — or undefined. */
121
+ static resolveFrame(frame, byFile, filePaths, root) {
122
+ const filePath = RuntimeJoin.resolveFilePath(frame.url, root, byFile, filePaths);
123
+ if (filePath === undefined) {
124
+ return undefined;
125
+ }
126
+ return RuntimeJoin.resolveNode(byFile.get(filePath) ?? [], frame).node;
127
+ }
128
+ static indexByFile(nodes) {
129
+ const byFile = new Map();
130
+ for (const node of nodes) {
131
+ const bucket = byFile.get(node.filePath);
132
+ if (bucket === undefined) {
133
+ byFile.set(node.filePath, [node]);
134
+ }
135
+ else {
136
+ bucket.push(node);
137
+ }
138
+ }
139
+ return byFile;
140
+ }
141
+ /**
142
+ * Resolves a frame to a node within its file, preferring a name match and
143
+ * falling back to an enclosing-range match.
144
+ */
145
+ static resolveNode(fileNodes, frame) {
146
+ const nameMatches = RuntimeJoin.nameMatches(fileNodes, frame.functionName);
147
+ if (nameMatches.length === 1) {
148
+ return { node: nameMatches[0], via: 'name' };
149
+ }
150
+ if (nameMatches.length > 1) {
151
+ const disambiguated = RuntimeJoin.enclosingNode(nameMatches, frame.line);
152
+ if (disambiguated !== undefined) {
153
+ return { node: disambiguated, via: 'name' };
154
+ }
155
+ return { node: undefined, reason: 'ambiguous' };
156
+ }
157
+ const enclosed = RuntimeJoin.enclosingNode(fileNodes, frame.line);
158
+ if (enclosed !== undefined) {
159
+ return { node: enclosed, via: 'range' };
160
+ }
161
+ return { node: undefined, reason: 'no-node' };
162
+ }
163
+ /**
164
+ * Nodes in the file whose name equals the frame's function name (or its final
165
+ * dotted segment, so `Widget.render` matches a method named `render`). V8
166
+ * synthetic frames such as `(anonymous)` and `(root)` never match.
167
+ */
168
+ static nameMatches(fileNodes, functionName) {
169
+ if (RuntimeJoin.isMeaningfulName(functionName) === false) {
170
+ return [];
171
+ }
172
+ const candidates = RuntimeJoin.candidateNames(functionName);
173
+ return fileNodes.filter((node) => NAME_MATCH_KINDS.has(node.kind) && candidates.includes(node.name));
174
+ }
175
+ static isMeaningfulName(functionName) {
176
+ return functionName.length > 0 && functionName.startsWith('(') === false;
177
+ }
178
+ static candidateNames(functionName) {
179
+ const dot = functionName.lastIndexOf('.');
180
+ if (dot >= 0 && dot < functionName.length - 1) {
181
+ return [functionName, functionName.slice(dot + 1)];
182
+ }
183
+ return [functionName];
184
+ }
185
+ /**
186
+ * Picks the innermost node whose range encloses `line`. Ties on range width
187
+ * break toward the later-starting node, which is the more deeply nested
188
+ * declaration. A non-positive line (V8 synthetic frame, or a transpiler that
189
+ * collapsed line numbers) encloses nothing.
190
+ */
191
+ static enclosingNode(nodes, line) {
192
+ if (line < 1) {
193
+ return undefined;
194
+ }
195
+ let best;
196
+ let bestSpan = Number.POSITIVE_INFINITY;
197
+ for (const node of nodes) {
198
+ if (node.startLine < 1 || node.endLine < node.startLine) {
199
+ continue;
200
+ }
201
+ if (line < node.startLine || line > node.endLine) {
202
+ continue;
203
+ }
204
+ const span = node.endLine - node.startLine;
205
+ if (span < bestSpan || (span === bestSpan && best !== undefined && node.startLine > best.startLine)) {
206
+ best = node;
207
+ bestSpan = span;
208
+ }
209
+ }
210
+ return best;
211
+ }
212
+ /**
213
+ * Maps a profile frame url to a graph `filePath`. Node-internal and empty
214
+ * urls (`node:`, `(root)`, `(idle)`) resolve to nothing. A url inside `root`
215
+ * resolves by relative path; otherwise a unique path-suffix match is used so
216
+ * a graph extracted under a different absolute prefix still attaches.
217
+ */
218
+ static resolveFilePath(url, root, byFile, filePaths) {
219
+ if (url.length === 0 || url.startsWith('node:')) {
220
+ return undefined;
221
+ }
222
+ const absolute = RuntimeJoin.urlToAbsolute(url);
223
+ if (absolute === undefined) {
224
+ return byFile.has(url) ? url : undefined;
225
+ }
226
+ const normalizedAbsolute = RuntimeJoin.toPosix(absolute);
227
+ const candidate = RuntimeJoin.toPosix(relative(root, absolute));
228
+ if (candidate.length > 0 && candidate.startsWith('..') === false && isAbsolute(candidate) === false && byFile.has(candidate)) {
229
+ return candidate;
230
+ }
231
+ let suffixMatch;
232
+ for (const filePath of filePaths) {
233
+ if (normalizedAbsolute === filePath || normalizedAbsolute.endsWith(`/${filePath}`)) {
234
+ if (suffixMatch !== undefined && suffixMatch !== filePath) {
235
+ return undefined;
236
+ }
237
+ suffixMatch = filePath;
238
+ }
239
+ }
240
+ return suffixMatch;
241
+ }
242
+ static urlToAbsolute(url) {
243
+ if (url.startsWith('file://')) {
244
+ return fileURLToPath(url);
245
+ }
246
+ if (isAbsolute(url)) {
247
+ return url;
248
+ }
249
+ return undefined;
250
+ }
251
+ static toPosix(value) {
252
+ return sep === '/' ? value : value.split(sep).join('/');
253
+ }
254
+ static recordDrop(dropped, frame, reason) {
255
+ const label = RuntimeJoin.dropLabel(frame, reason);
256
+ const key = `${reason}:${label}`;
257
+ const group = dropped.get(key) ?? { label, reason, samples: 0, selfMicros: 0 };
258
+ group.samples += frame.samples;
259
+ group.selfMicros += frame.selfMicros;
260
+ dropped.set(key, group);
261
+ }
262
+ static dropLabel(frame, reason) {
263
+ const name = frame.functionName.length > 0 ? frame.functionName : '(anonymous)';
264
+ if (reason === 'no-file') {
265
+ return frame.url.length === 0 ? name : `${name} ${frame.url}`;
266
+ }
267
+ return `${name} ${frame.url}:${frame.line}`;
268
+ }
269
+ }
270
+ //# sourceMappingURL=runtime_join.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime_join.js","sourceRoot":"","sources":["../../src/enrich/runtime_join.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { SourceFile } from 'ts-morph';
2
+ import { Extraction } from './structural_extractor.js';
3
+ /**
4
+ * Detects outbound HTTP calls via `fetch(...)` and emits an `ExternalAPI` node for
5
+ * the called host plus a `CALLS_EXTERNAL` edge from the calling declaration. The
6
+ * host is taken from a static URL argument (`fetch('https://api.example.com/…')`);
7
+ * a call whose target is not statically a URL is attributed to a single generic
8
+ * `(dynamic)` node, so the outbound-I/O call site is still surfaced. Calls to the
9
+ * same host from one scope collapse to a single counted edge.
10
+ *
11
+ * Detection is purely syntactic (no symbol resolution), so it runs in the
12
+ * structural pass; a project that never calls `fetch` is unchanged. Other HTTP
13
+ * clients (axios, got, …) are out of scope for now — fetch first (#31 Part 2b).
14
+ */
15
+ export declare class ApiExtractor {
16
+ static extract(sourceFile: SourceFile, rootPath: string): Extraction;
17
+ /** `fetch(...)` — the bare global, or `window` / `globalThis` / `self`.fetch(...). */
18
+ private static isFetchCall;
19
+ /** The host of a static URL argument, or {@link DYNAMIC_TARGET} when not statically a URL. */
20
+ private static targetHost;
21
+ /** The literal text of a string / no-substitution-template argument, else undefined. */
22
+ private static staticUrl;
23
+ }
24
+ //# sourceMappingURL=api_extractor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api_extractor.d.ts","sourceRoot":"","sources":["../../src/extract/api_extractor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,UAAU,EAAc,MAAM,UAAU,CAAC;AAKxD,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAQvD;;;;;;;;;;;GAWG;AACH,qBAAa,YAAY;IACxB,MAAM,CAAC,OAAO,CAAC,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU;IAmBpE,sFAAsF;IACtF,OAAO,CAAC,MAAM,CAAC,WAAW;IAY1B,8FAA8F;IAC9F,OAAO,CAAC,MAAM,CAAC,UAAU;IAYzB,wFAAwF;IACxF,OAAO,CAAC,MAAM,CAAC,SAAS;CAOxB"}
@@ -0,0 +1,71 @@
1
+ import { Node, SyntaxKind } from 'ts-morph';
2
+ import { NodeId } from './node_id.js';
3
+ import { ScopeResolver } from './scope_resolver.js';
4
+ /** Receivers on which a `.fetch(...)` call is the WHATWG global rather than a user method. */
5
+ const FETCH_GLOBALS = new Set(['window', 'globalThis', 'self']);
6
+ /** Host used when a fetch target is not a static URL (a variable, a built string, a relative path). */
7
+ const DYNAMIC_TARGET = '(dynamic)';
8
+ /**
9
+ * Detects outbound HTTP calls via `fetch(...)` and emits an `ExternalAPI` node for
10
+ * the called host plus a `CALLS_EXTERNAL` edge from the calling declaration. The
11
+ * host is taken from a static URL argument (`fetch('https://api.example.com/…')`);
12
+ * a call whose target is not statically a URL is attributed to a single generic
13
+ * `(dynamic)` node, so the outbound-I/O call site is still surfaced. Calls to the
14
+ * same host from one scope collapse to a single counted edge.
15
+ *
16
+ * Detection is purely syntactic (no symbol resolution), so it runs in the
17
+ * structural pass; a project that never calls `fetch` is unchanged. Other HTTP
18
+ * clients (axios, got, …) are out of scope for now — fetch first (#31 Part 2b).
19
+ */
20
+ export class ApiExtractor {
21
+ static extract(sourceFile, rootPath) {
22
+ const nodes = [];
23
+ const edges = [];
24
+ const moduleId = NodeId.forModule(sourceFile.getFilePath(), rootPath);
25
+ for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
26
+ if (ApiExtractor.isFetchCall(call.getExpression()) === false) {
27
+ continue;
28
+ }
29
+ const host = ApiExtractor.targetHost(call.getArguments()[0]);
30
+ const apiId = NodeId.forExternalApi(host);
31
+ nodes.push({ id: apiId, kind: 'ExternalAPI', name: host, filePath: host });
32
+ const scopeId = ScopeResolver.enclosingId(call, moduleId, rootPath);
33
+ edges.push({ id: `CALLS_EXTERNAL:${scopeId}->${apiId}`, kind: 'CALLS_EXTERNAL', from: scopeId, to: apiId });
34
+ }
35
+ return { nodes, edges };
36
+ }
37
+ /** `fetch(...)` — the bare global, or `window` / `globalThis` / `self`.fetch(...). */
38
+ static isFetchCall(callee) {
39
+ if (Node.isIdentifier(callee) === true) {
40
+ return callee.getText() === 'fetch';
41
+ }
42
+ const access = callee.asKind(SyntaxKind.PropertyAccessExpression);
43
+ if (access === undefined || access.getName() !== 'fetch') {
44
+ return false;
45
+ }
46
+ const target = access.getExpression();
47
+ return Node.isIdentifier(target) === true && FETCH_GLOBALS.has(target.getText()) === true;
48
+ }
49
+ /** The host of a static URL argument, or {@link DYNAMIC_TARGET} when not statically a URL. */
50
+ static targetHost(argument) {
51
+ const url = ApiExtractor.staticUrl(argument);
52
+ if (url === undefined) {
53
+ return DYNAMIC_TARGET;
54
+ }
55
+ try {
56
+ return new URL(url).host || DYNAMIC_TARGET;
57
+ }
58
+ catch {
59
+ return DYNAMIC_TARGET;
60
+ }
61
+ }
62
+ /** The literal text of a string / no-substitution-template argument, else undefined. */
63
+ static staticUrl(argument) {
64
+ const string = argument?.asKind(SyntaxKind.StringLiteral);
65
+ if (string !== undefined) {
66
+ return string.getLiteralText();
67
+ }
68
+ return argument?.asKind(SyntaxKind.NoSubstitutionTemplateLiteral)?.getLiteralText();
69
+ }
70
+ }
71
+ //# sourceMappingURL=api_extractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api_extractor.js","sourceRoot":"","sources":["../../src/extract/api_extractor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAc,UAAU,EAAE,MAAM,UAAU,CAAC;AAGxD,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGpD,8FAA8F;AAC9F,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;AAEhE,uGAAuG;AACvG,MAAM,cAAc,GAAG,WAAW,CAAC;AAEnC;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,YAAY;IACxB,MAAM,CAAC,OAAO,CAAC,UAAsB,EAAE,QAAgB;QACtD,MAAM,KAAK,GAAgB,EAAE,CAAC;QAC9B,MAAM,KAAK,GAAgB,EAAE,CAAC;QAC9B,MAAM,QAAQ,GAAG,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,WAAW,EAAE,EAAE,QAAQ,CAAC,CAAC;QAEtE,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,oBAAoB,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;YAC/E,IAAI,YAAY,CAAC,WAAW,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,KAAK,KAAK,EAAE,CAAC;gBAC9D,SAAS;YACV,CAAC;YACD,MAAM,IAAI,GAAG,YAAY,CAAC,UAAU,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAC7D,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;YAC1C,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;YAC3E,MAAM,OAAO,GAAG,aAAa,CAAC,WAAW,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;YACpE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,kBAAkB,OAAO,KAAK,KAAK,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QAC7G,CAAC;QAED,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IACzB,CAAC;IAED,sFAAsF;IAC9E,MAAM,CAAC,WAAW,CAAC,MAAY;QACtC,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;YACxC,OAAO,MAAM,CAAC,OAAO,EAAE,KAAK,OAAO,CAAC;QACrC,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,wBAAwB,CAAC,CAAC;QAClE,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,OAAO,EAAE,KAAK,OAAO,EAAE,CAAC;YAC1D,OAAO,KAAK,CAAC;QACd,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,CAAC,aAAa,EAAE,CAAC;QACtC,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,IAAI,IAAI,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,KAAK,IAAI,CAAC;IAC3F,CAAC;IAED,8FAA8F;IACtF,MAAM,CAAC,UAAU,CAAC,QAA0B;QACnD,MAAM,GAAG,GAAG,YAAY,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAC7C,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACvB,OAAO,cAAc,CAAC;QACvB,CAAC;QACD,IAAI,CAAC;YACJ,OAAO,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,cAAc,CAAC;QAC5C,CAAC;QAAC,MAAM,CAAC;YACR,OAAO,cAAc,CAAC;QACvB,CAAC;IACF,CAAC;IAED,wFAAwF;IAChF,MAAM,CAAC,SAAS,CAAC,QAA0B;QAClD,MAAM,MAAM,GAAG,QAAQ,EAAE,MAAM,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;QAC1D,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YAC1B,OAAO,MAAM,CAAC,cAAc,EAAE,CAAC;QAChC,CAAC;QACD,OAAO,QAAQ,EAAE,MAAM,CAAC,UAAU,CAAC,6BAA6B,CAAC,EAAE,cAAc,EAAE,CAAC;IACrF,CAAC;CACD"}
@@ -0,0 +1,22 @@
1
+ import { SourceFile } from 'ts-morph';
2
+ import { Extraction } from './structural_extractor.js';
3
+ /**
4
+ * Detects configuration reads — `process.env.NAME` and `process.env['NAME']` —
5
+ * and emits one `ConfigFlag` node per distinct variable plus a `READS_CONFIG`
6
+ * edge from the enclosing declaration that performs the read. A variable read in
7
+ * several places collapses to one node (keyed by name) with one counted edge per
8
+ * reading scope.
9
+ *
10
+ * The detection is purely syntactic (no symbol resolution), so it runs in the
11
+ * structural pass and is always emitted. A project that never touches
12
+ * `process.env` produces no config nodes or edges, leaving its graph unchanged.
13
+ */
14
+ export declare class ConfigExtractor {
15
+ static extract(sourceFile: SourceFile, rootPath: string): Extraction;
16
+ private static emit;
17
+ /** Whether a node is the `process.env` member access (`process` identifier, `env` name). */
18
+ private static isProcessEnv;
19
+ /** The literal key of `process.env['NAME']`, or undefined when the key is computed. */
20
+ private static stringArgument;
21
+ }
22
+ //# sourceMappingURL=config_extractor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config_extractor.d.ts","sourceRoot":"","sources":["../../src/extract/config_extractor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,UAAU,EAAc,MAAM,UAAU,CAAC;AAKxD,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,qBAAa,eAAe;IAC3B,MAAM,CAAC,OAAO,CAAC,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU;IAuBpE,OAAO,CAAC,MAAM,CAAC,IAAI;IAiBnB,4FAA4F;IAC5F,OAAO,CAAC,MAAM,CAAC,YAAY;IAS3B,uFAAuF;IACvF,OAAO,CAAC,MAAM,CAAC,cAAc;CAK7B"}