ts-knowledge-graph 0.1.2 → 0.1.4

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 (255) hide show
  1. package/README.md +74 -34
  2. package/contribs/web_visualisation/README.md +28 -0
  3. package/contribs/web_visualisation/web/css/style.css +104 -0
  4. package/contribs/web_visualisation/web/data/.gitignore +1 -0
  5. package/contribs/web_visualisation/web/data/kind_descriptions.js +38 -0
  6. package/contribs/web_visualisation/web/index.html +20 -4
  7. package/contribs/web_visualisation/web/js/app.js +581 -35
  8. package/contribs/web_visualisation/web/tsconfig.json +18 -0
  9. package/contribs/web_visualisation/web/types/app_globals.d.ts +146 -0
  10. package/dist/benchmark/benchmark_stats.d.ts +41 -0
  11. package/dist/benchmark/benchmark_stats.d.ts.map +1 -0
  12. package/dist/benchmark/benchmark_stats.js +61 -0
  13. package/dist/benchmark/benchmark_stats.js.map +1 -0
  14. package/dist/benchmark/node_benchmark.d.ts +78 -0
  15. package/dist/benchmark/node_benchmark.d.ts.map +1 -0
  16. package/dist/benchmark/node_benchmark.js +112 -0
  17. package/dist/benchmark/node_benchmark.js.map +1 -0
  18. package/dist/cli.d.ts.map +1 -1
  19. package/dist/cli.js +10 -2
  20. package/dist/cli.js.map +1 -1
  21. package/dist/commands/benchmark_command.d.ts +11 -0
  22. package/dist/commands/benchmark_command.d.ts.map +1 -0
  23. package/dist/commands/benchmark_command.js +91 -0
  24. package/dist/commands/benchmark_command.js.map +1 -0
  25. package/dist/commands/blast_radius_command.js +1 -1
  26. package/dist/commands/blast_radius_command.js.map +1 -1
  27. package/dist/commands/cost_command.d.ts +13 -0
  28. package/dist/commands/cost_command.d.ts.map +1 -0
  29. package/dist/commands/cost_command.js +122 -0
  30. package/dist/commands/cost_command.js.map +1 -0
  31. package/dist/commands/{load.d.ts → enrich_command.d.ts} +3 -2
  32. package/dist/commands/enrich_command.d.ts.map +1 -0
  33. package/dist/commands/enrich_command.js +62 -0
  34. package/dist/commands/enrich_command.js.map +1 -0
  35. package/dist/commands/hotspots_command.d.ts +7 -0
  36. package/dist/commands/hotspots_command.d.ts.map +1 -0
  37. package/dist/commands/hotspots_command.js +67 -0
  38. package/dist/commands/hotspots_command.js.map +1 -0
  39. package/dist/commands/install_command.d.ts +2 -3
  40. package/dist/commands/install_command.d.ts.map +1 -1
  41. package/dist/commands/install_command.js +7 -8
  42. package/dist/commands/install_command.js.map +1 -1
  43. package/dist/commands/load_command.d.ts.map +1 -1
  44. package/dist/commands/load_command.js +2 -0
  45. package/dist/commands/load_command.js.map +1 -1
  46. package/dist/commands/verify_command.d.ts +8 -0
  47. package/dist/commands/verify_command.d.ts.map +1 -0
  48. package/dist/commands/verify_command.js +57 -0
  49. package/dist/commands/verify_command.js.map +1 -0
  50. package/dist/commands/web_command.d.ts +27 -0
  51. package/dist/commands/web_command.d.ts.map +1 -1
  52. package/dist/commands/web_command.js +109 -3
  53. package/dist/commands/web_command.js.map +1 -1
  54. package/dist/enrich/cpu_profile.d.ts +127 -0
  55. package/dist/enrich/cpu_profile.d.ts.map +1 -0
  56. package/dist/enrich/cpu_profile.js +97 -0
  57. package/dist/enrich/cpu_profile.js.map +1 -0
  58. package/dist/enrich/runtime_enricher.d.ts +56 -0
  59. package/dist/enrich/runtime_enricher.d.ts.map +1 -0
  60. package/dist/enrich/runtime_enricher.js +80 -0
  61. package/dist/enrich/runtime_enricher.js.map +1 -0
  62. package/dist/enrich/runtime_join.d.ts +100 -0
  63. package/dist/enrich/runtime_join.d.ts.map +1 -0
  64. package/dist/enrich/runtime_join.js +227 -0
  65. package/dist/enrich/runtime_join.js.map +1 -0
  66. package/dist/extract/api_extractor.d.ts +24 -0
  67. package/dist/extract/api_extractor.d.ts.map +1 -0
  68. package/dist/extract/api_extractor.js +71 -0
  69. package/dist/extract/api_extractor.js.map +1 -0
  70. package/dist/extract/config_extractor.d.ts +22 -0
  71. package/dist/extract/config_extractor.d.ts.map +1 -0
  72. package/dist/extract/config_extractor.js +61 -0
  73. package/dist/extract/config_extractor.js.map +1 -0
  74. package/dist/extract/endpoint_extractor.d.ts +36 -0
  75. package/dist/extract/endpoint_extractor.d.ts.map +1 -0
  76. package/dist/extract/endpoint_extractor.js +117 -0
  77. package/dist/extract/endpoint_extractor.js.map +1 -0
  78. package/dist/extract/graph_builder.d.ts +8 -0
  79. package/dist/extract/graph_builder.d.ts.map +1 -1
  80. package/dist/extract/graph_builder.js +23 -1
  81. package/dist/extract/graph_builder.js.map +1 -1
  82. package/dist/extract/node_id.d.ts +16 -0
  83. package/dist/extract/node_id.d.ts.map +1 -1
  84. package/dist/extract/node_id.js +22 -0
  85. package/dist/extract/node_id.js.map +1 -1
  86. package/dist/extract/scope_resolver.d.ts +22 -0
  87. package/dist/extract/scope_resolver.d.ts.map +1 -0
  88. package/dist/extract/scope_resolver.js +53 -0
  89. package/dist/extract/scope_resolver.js.map +1 -0
  90. package/dist/extract/semantic_extractor.d.ts +25 -0
  91. package/dist/extract/semantic_extractor.d.ts.map +1 -1
  92. package/dist/extract/semantic_extractor.js +96 -2
  93. package/dist/extract/semantic_extractor.js.map +1 -1
  94. package/dist/extract/structural_extractor.d.ts +6 -0
  95. package/dist/extract/structural_extractor.d.ts.map +1 -1
  96. package/dist/extract/structural_extractor.js +22 -12
  97. package/dist/extract/structural_extractor.js.map +1 -1
  98. package/dist/project_root.d.ts +7 -0
  99. package/dist/project_root.d.ts.map +1 -0
  100. package/dist/project_root.js +9 -0
  101. package/dist/project_root.js.map +1 -0
  102. package/dist/query/graph_query.d.ts +234 -0
  103. package/dist/query/graph_query.d.ts.map +1 -1
  104. package/dist/query/graph_query.js +522 -11
  105. package/dist/query/graph_query.js.map +1 -1
  106. package/dist/schema/edge.d.ts +40 -5
  107. package/dist/schema/edge.d.ts.map +1 -1
  108. package/dist/schema/edge.js +70 -0
  109. package/dist/schema/edge.js.map +1 -1
  110. package/dist/schema/node.d.ts +20 -5
  111. package/dist/schema/node.d.ts.map +1 -1
  112. package/dist/schema/node.js +36 -0
  113. package/dist/schema/node.js.map +1 -1
  114. package/dist/schema/runtime_manifest.d.ts +36 -0
  115. package/dist/schema/runtime_manifest.d.ts.map +1 -0
  116. package/dist/schema/runtime_manifest.js +23 -0
  117. package/dist/schema/runtime_manifest.js.map +1 -0
  118. package/dist/store/kuzu_store.d.ts +46 -0
  119. package/dist/store/kuzu_store.d.ts.map +1 -1
  120. package/dist/store/kuzu_store.js +95 -5
  121. package/dist/store/kuzu_store.js.map +1 -1
  122. package/dist/verify/project_verifier.d.ts +85 -0
  123. package/dist/verify/project_verifier.d.ts.map +1 -0
  124. package/dist/verify/project_verifier.js +138 -0
  125. package/dist/verify/project_verifier.js.map +1 -0
  126. package/{skills/ts-knowledge-graph → dotclaude_folder/skills/code-graph-query}/SKILL.md +2 -2
  127. package/package.json +86 -7
  128. package/.env-sample +0 -34
  129. package/dist/agent/agent-tools.d.ts +0 -13
  130. package/dist/agent/agent-tools.d.ts.map +0 -1
  131. package/dist/agent/agent-tools.js +0 -153
  132. package/dist/agent/agent-tools.js.map +0 -1
  133. package/dist/agent/agent_tools.d.ts +0 -13
  134. package/dist/agent/agent_tools.d.ts.map +0 -1
  135. package/dist/agent/agent_tools.js +0 -153
  136. package/dist/agent/agent_tools.js.map +0 -1
  137. package/dist/agent/code-editor.d.ts +0 -18
  138. package/dist/agent/code-editor.d.ts.map +0 -1
  139. package/dist/agent/code-editor.js +0 -43
  140. package/dist/agent/code-editor.js.map +0 -1
  141. package/dist/agent/code_editor.d.ts +0 -18
  142. package/dist/agent/code_editor.d.ts.map +0 -1
  143. package/dist/agent/code_editor.js +0 -43
  144. package/dist/agent/code_editor.js.map +0 -1
  145. package/dist/agent/optimizer-agent.d.ts +0 -30
  146. package/dist/agent/optimizer-agent.d.ts.map +0 -1
  147. package/dist/agent/optimizer-agent.js +0 -97
  148. package/dist/agent/optimizer-agent.js.map +0 -1
  149. package/dist/agent/optimizer_agent.d.ts +0 -30
  150. package/dist/agent/optimizer_agent.d.ts.map +0 -1
  151. package/dist/agent/optimizer_agent.js +0 -97
  152. package/dist/agent/optimizer_agent.js.map +0 -1
  153. package/dist/agent/verifier.d.ts +0 -9
  154. package/dist/agent/verifier.d.ts.map +0 -1
  155. package/dist/agent/verifier.js +0 -19
  156. package/dist/agent/verifier.js.map +0 -1
  157. package/dist/commands/blast-radius.d.ts +0 -5
  158. package/dist/commands/blast-radius.d.ts.map +0 -1
  159. package/dist/commands/blast-radius.js +0 -18
  160. package/dist/commands/blast-radius.js.map +0 -1
  161. package/dist/commands/blast_radius.d.ts +0 -5
  162. package/dist/commands/blast_radius.d.ts.map +0 -1
  163. package/dist/commands/blast_radius.js +0 -18
  164. package/dist/commands/blast_radius.js.map +0 -1
  165. package/dist/commands/calls.d.ts +0 -5
  166. package/dist/commands/calls.d.ts.map +0 -1
  167. package/dist/commands/calls.js +0 -7
  168. package/dist/commands/calls.js.map +0 -1
  169. package/dist/commands/command-helpers.d.ts +0 -15
  170. package/dist/commands/command-helpers.d.ts.map +0 -1
  171. package/dist/commands/command-helpers.js +0 -61
  172. package/dist/commands/command-helpers.js.map +0 -1
  173. package/dist/commands/dead-exports.d.ts +0 -5
  174. package/dist/commands/dead-exports.d.ts.map +0 -1
  175. package/dist/commands/dead-exports.js +0 -7
  176. package/dist/commands/dead-exports.js.map +0 -1
  177. package/dist/commands/dead_exports.d.ts +0 -5
  178. package/dist/commands/dead_exports.d.ts.map +0 -1
  179. package/dist/commands/dead_exports.js +0 -7
  180. package/dist/commands/dead_exports.js.map +0 -1
  181. package/dist/commands/extract.d.ts +0 -8
  182. package/dist/commands/extract.d.ts.map +0 -1
  183. package/dist/commands/extract.js +0 -49
  184. package/dist/commands/extract.js.map +0 -1
  185. package/dist/commands/find.d.ts +0 -5
  186. package/dist/commands/find.d.ts.map +0 -1
  187. package/dist/commands/find.js +0 -7
  188. package/dist/commands/find.js.map +0 -1
  189. package/dist/commands/load.d.ts.map +0 -1
  190. package/dist/commands/load.js +0 -28
  191. package/dist/commands/load.js.map +0 -1
  192. package/dist/commands/neighbors.d.ts +0 -5
  193. package/dist/commands/neighbors.d.ts.map +0 -1
  194. package/dist/commands/neighbors.js +0 -17
  195. package/dist/commands/neighbors.js.map +0 -1
  196. package/dist/commands/optimize.d.ts +0 -6
  197. package/dist/commands/optimize.d.ts.map +0 -1
  198. package/dist/commands/optimize.js +0 -59
  199. package/dist/commands/optimize.js.map +0 -1
  200. package/dist/commands/optimize_command.d.ts +0 -6
  201. package/dist/commands/optimize_command.d.ts.map +0 -1
  202. package/dist/commands/optimize_command.js +0 -59
  203. package/dist/commands/optimize_command.js.map +0 -1
  204. package/dist/commands/references.d.ts +0 -5
  205. package/dist/commands/references.d.ts.map +0 -1
  206. package/dist/commands/references.js +0 -17
  207. package/dist/commands/references.js.map +0 -1
  208. package/dist/commands/web.d.ts +0 -19
  209. package/dist/commands/web.d.ts.map +0 -1
  210. package/dist/commands/web.js +0 -120
  211. package/dist/commands/web.js.map +0 -1
  212. package/dist/commands/who-calls.d.ts +0 -5
  213. package/dist/commands/who-calls.d.ts.map +0 -1
  214. package/dist/commands/who-calls.js +0 -7
  215. package/dist/commands/who-calls.js.map +0 -1
  216. package/dist/commands/who_calls.d.ts +0 -5
  217. package/dist/commands/who_calls.d.ts.map +0 -1
  218. package/dist/commands/who_calls.js +0 -7
  219. package/dist/commands/who_calls.js.map +0 -1
  220. package/dist/extract/graph-builder.d.ts +0 -16
  221. package/dist/extract/graph-builder.d.ts.map +0 -1
  222. package/dist/extract/graph-builder.js +0 -39
  223. package/dist/extract/graph-builder.js.map +0 -1
  224. package/dist/extract/node-id.d.ts +0 -8
  225. package/dist/extract/node-id.d.ts.map +0 -1
  226. package/dist/extract/node-id.js +0 -22
  227. package/dist/extract/node-id.js.map +0 -1
  228. package/dist/extract/project-loader.d.ts +0 -5
  229. package/dist/extract/project-loader.d.ts.map +0 -1
  230. package/dist/extract/project-loader.js +0 -19
  231. package/dist/extract/project-loader.js.map +0 -1
  232. package/dist/extract/semantic-extractor.d.ts +0 -22
  233. package/dist/extract/semantic-extractor.d.ts.map +0 -1
  234. package/dist/extract/semantic-extractor.js +0 -254
  235. package/dist/extract/semantic-extractor.js.map +0 -1
  236. package/dist/extract/structural-extractor.d.ts +0 -18
  237. package/dist/extract/structural-extractor.d.ts.map +0 -1
  238. package/dist/extract/structural-extractor.js +0 -97
  239. package/dist/extract/structural-extractor.js.map +0 -1
  240. package/dist/query/graph-query.d.ts +0 -28
  241. package/dist/query/graph-query.d.ts.map +0 -1
  242. package/dist/query/graph-query.js +0 -93
  243. package/dist/query/graph-query.js.map +0 -1
  244. package/dist/store/jsonl-reader.d.ts +0 -11
  245. package/dist/store/jsonl-reader.d.ts.map +0 -1
  246. package/dist/store/jsonl-reader.js +0 -19
  247. package/dist/store/jsonl-reader.js.map +0 -1
  248. package/dist/store/jsonl-store.d.ts +0 -7
  249. package/dist/store/jsonl-store.d.ts.map +0 -1
  250. package/dist/store/jsonl-store.js +0 -13
  251. package/dist/store/jsonl-store.js.map +0 -1
  252. package/dist/store/kuzu-store.d.ts +0 -14
  253. package/dist/store/kuzu-store.d.ts.map +0 -1
  254. package/dist/store/kuzu-store.js +0 -52
  255. package/dist/store/kuzu-store.js.map +0 -1
@@ -0,0 +1,100 @@
1
+ import { FrameSample } 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
+ export declare class RuntimeJoin {
47
+ /**
48
+ * Joins profile frames onto graph nodes with a hybrid key that survives
49
+ * transpilation.
50
+ *
51
+ * For each frame the url is resolved to a graph `filePath`, then within that
52
+ * file:
53
+ * 1. **By name** — a frame's `functionName` matched against node names. A
54
+ * unique match wins outright; this is the path that works when a
55
+ * transpiler (tsx/esbuild) has collapsed line numbers, since the function
56
+ * name is still intact.
57
+ * 2. **By range** — when the name is absent or matches nothing, the innermost
58
+ * node whose `[startLine, endLine]` encloses the frame line. This is the
59
+ * precise path for line-preserving runs and also disambiguates a name that
60
+ * matched several nodes.
61
+ *
62
+ * Frames resolving to no in-project file, to a file with no matching node, or
63
+ * to an unbreakable name tie are counted and reported as dropped — never
64
+ * attached.
65
+ */
66
+ static join(nodes: RuntimeTargetNode[], frames: FrameSample[], options: RuntimeJoinOptions): RuntimeJoinResult;
67
+ private static indexByFile;
68
+ /**
69
+ * Resolves a frame to a node within its file, preferring a name match and
70
+ * falling back to an enclosing-range match.
71
+ */
72
+ private static resolveNode;
73
+ /**
74
+ * Nodes in the file whose name equals the frame's function name (or its final
75
+ * dotted segment, so `Widget.render` matches a method named `render`). V8
76
+ * synthetic frames such as `(anonymous)` and `(root)` never match.
77
+ */
78
+ private static nameMatches;
79
+ private static isMeaningfulName;
80
+ private static candidateNames;
81
+ /**
82
+ * Picks the innermost node whose range encloses `line`. Ties on range width
83
+ * break toward the later-starting node, which is the more deeply nested
84
+ * declaration. A non-positive line (V8 synthetic frame, or a transpiler that
85
+ * collapsed line numbers) encloses nothing.
86
+ */
87
+ private static enclosingNode;
88
+ /**
89
+ * Maps a profile frame url to a graph `filePath`. Node-internal and empty
90
+ * urls (`node:`, `(root)`, `(idle)`) resolve to nothing. A url inside `root`
91
+ * resolves by relative path; otherwise a unique path-suffix match is used so
92
+ * a graph extracted under a different absolute prefix still attaches.
93
+ */
94
+ private static resolveFilePath;
95
+ private static urlToAbsolute;
96
+ private static toPosix;
97
+ private static recordDrop;
98
+ private static dropLabel;
99
+ }
100
+ //# 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":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,yDAAyD;AACzD,MAAM,MAAM,iBAAiB,GAAG;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,+DAA+D;AAC/D,MAAM,MAAM,kBAAkB,GAAG;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,8EAA8E;AAC9E,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAExC;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,CAAC;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC/B,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC9C,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,iBAAiB,EAAE,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAChC,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;CACb,CAAC;AAYF,qBAAa,WAAW;IACvB;;;;;;;;;;;;;;;;;;OAkBG;IACH,MAAM,CAAC,IAAI,CACV,KAAK,EAAE,iBAAiB,EAAE,EAC1B,MAAM,EAAE,WAAW,EAAE,EACrB,OAAO,EAAE,kBAAkB,GACzB,iBAAiB;IA4DpB,OAAO,CAAC,MAAM,CAAC,WAAW;IAa1B;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,WAAW;IAmB1B;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,WAAW;IAQ1B,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAI/B,OAAO,CAAC,MAAM,CAAC,cAAc;IAQ7B;;;;;OAKG;IACH,OAAO,CAAC,MAAM,CAAC,aAAa;IAsB5B;;;;;OAKG;IACH,OAAO,CAAC,MAAM,CAAC,eAAe;IAgC9B,OAAO,CAAC,MAAM,CAAC,aAAa;IAU5B,OAAO,CAAC,MAAM,CAAC,OAAO;IAItB,OAAO,CAAC,MAAM,CAAC,UAAU;IAazB,OAAO,CAAC,MAAM,CAAC,SAAS;CAOxB"}
@@ -0,0 +1,227 @@
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
+ static indexByFile(nodes) {
86
+ const byFile = new Map();
87
+ for (const node of nodes) {
88
+ const bucket = byFile.get(node.filePath);
89
+ if (bucket === undefined) {
90
+ byFile.set(node.filePath, [node]);
91
+ }
92
+ else {
93
+ bucket.push(node);
94
+ }
95
+ }
96
+ return byFile;
97
+ }
98
+ /**
99
+ * Resolves a frame to a node within its file, preferring a name match and
100
+ * falling back to an enclosing-range match.
101
+ */
102
+ static resolveNode(fileNodes, frame) {
103
+ const nameMatches = RuntimeJoin.nameMatches(fileNodes, frame.functionName);
104
+ if (nameMatches.length === 1) {
105
+ return { node: nameMatches[0], via: 'name' };
106
+ }
107
+ if (nameMatches.length > 1) {
108
+ const disambiguated = RuntimeJoin.enclosingNode(nameMatches, frame.line);
109
+ if (disambiguated !== undefined) {
110
+ return { node: disambiguated, via: 'name' };
111
+ }
112
+ return { node: undefined, reason: 'ambiguous' };
113
+ }
114
+ const enclosed = RuntimeJoin.enclosingNode(fileNodes, frame.line);
115
+ if (enclosed !== undefined) {
116
+ return { node: enclosed, via: 'range' };
117
+ }
118
+ return { node: undefined, reason: 'no-node' };
119
+ }
120
+ /**
121
+ * Nodes in the file whose name equals the frame's function name (or its final
122
+ * dotted segment, so `Widget.render` matches a method named `render`). V8
123
+ * synthetic frames such as `(anonymous)` and `(root)` never match.
124
+ */
125
+ static nameMatches(fileNodes, functionName) {
126
+ if (RuntimeJoin.isMeaningfulName(functionName) === false) {
127
+ return [];
128
+ }
129
+ const candidates = RuntimeJoin.candidateNames(functionName);
130
+ return fileNodes.filter((node) => NAME_MATCH_KINDS.has(node.kind) && candidates.includes(node.name));
131
+ }
132
+ static isMeaningfulName(functionName) {
133
+ return functionName.length > 0 && functionName.startsWith('(') === false;
134
+ }
135
+ static candidateNames(functionName) {
136
+ const dot = functionName.lastIndexOf('.');
137
+ if (dot >= 0 && dot < functionName.length - 1) {
138
+ return [functionName, functionName.slice(dot + 1)];
139
+ }
140
+ return [functionName];
141
+ }
142
+ /**
143
+ * Picks the innermost node whose range encloses `line`. Ties on range width
144
+ * break toward the later-starting node, which is the more deeply nested
145
+ * declaration. A non-positive line (V8 synthetic frame, or a transpiler that
146
+ * collapsed line numbers) encloses nothing.
147
+ */
148
+ static enclosingNode(nodes, line) {
149
+ if (line < 1) {
150
+ return undefined;
151
+ }
152
+ let best;
153
+ let bestSpan = Number.POSITIVE_INFINITY;
154
+ for (const node of nodes) {
155
+ if (node.startLine < 1 || node.endLine < node.startLine) {
156
+ continue;
157
+ }
158
+ if (line < node.startLine || line > node.endLine) {
159
+ continue;
160
+ }
161
+ const span = node.endLine - node.startLine;
162
+ if (span < bestSpan || (span === bestSpan && best !== undefined && node.startLine > best.startLine)) {
163
+ best = node;
164
+ bestSpan = span;
165
+ }
166
+ }
167
+ return best;
168
+ }
169
+ /**
170
+ * Maps a profile frame url to a graph `filePath`. Node-internal and empty
171
+ * urls (`node:`, `(root)`, `(idle)`) resolve to nothing. A url inside `root`
172
+ * resolves by relative path; otherwise a unique path-suffix match is used so
173
+ * a graph extracted under a different absolute prefix still attaches.
174
+ */
175
+ static resolveFilePath(url, root, byFile, filePaths) {
176
+ if (url.length === 0 || url.startsWith('node:')) {
177
+ return undefined;
178
+ }
179
+ const absolute = RuntimeJoin.urlToAbsolute(url);
180
+ if (absolute === undefined) {
181
+ return byFile.has(url) ? url : undefined;
182
+ }
183
+ const normalizedAbsolute = RuntimeJoin.toPosix(absolute);
184
+ const candidate = RuntimeJoin.toPosix(relative(root, absolute));
185
+ if (candidate.length > 0 && candidate.startsWith('..') === false && isAbsolute(candidate) === false && byFile.has(candidate)) {
186
+ return candidate;
187
+ }
188
+ let suffixMatch;
189
+ for (const filePath of filePaths) {
190
+ if (normalizedAbsolute === filePath || normalizedAbsolute.endsWith(`/${filePath}`)) {
191
+ if (suffixMatch !== undefined && suffixMatch !== filePath) {
192
+ return undefined;
193
+ }
194
+ suffixMatch = filePath;
195
+ }
196
+ }
197
+ return suffixMatch;
198
+ }
199
+ static urlToAbsolute(url) {
200
+ if (url.startsWith('file://')) {
201
+ return fileURLToPath(url);
202
+ }
203
+ if (isAbsolute(url)) {
204
+ return url;
205
+ }
206
+ return undefined;
207
+ }
208
+ static toPosix(value) {
209
+ return sep === '/' ? value : value.split(sep).join('/');
210
+ }
211
+ static recordDrop(dropped, frame, reason) {
212
+ const label = RuntimeJoin.dropLabel(frame, reason);
213
+ const key = `${reason}:${label}`;
214
+ const group = dropped.get(key) ?? { label, reason, samples: 0, selfMicros: 0 };
215
+ group.samples += frame.samples;
216
+ group.selfMicros += frame.selfMicros;
217
+ dropped.set(key, group);
218
+ }
219
+ static dropLabel(frame, reason) {
220
+ const name = frame.functionName.length > 0 ? frame.functionName : '(anonymous)';
221
+ if (reason === 'no-file') {
222
+ return frame.url.length === 0 ? name : `${name} ${frame.url}`;
223
+ }
224
+ return `${name} ${frame.url}:${frame.line}`;
225
+ }
226
+ }
227
+ //# 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"}
@@ -0,0 +1,61 @@
1
+ import { Node, SyntaxKind } from 'ts-morph';
2
+ import { NodeId } from './node_id.js';
3
+ import { ScopeResolver } from './scope_resolver.js';
4
+ /**
5
+ * Detects configuration reads — `process.env.NAME` and `process.env['NAME']` —
6
+ * and emits one `ConfigFlag` node per distinct variable plus a `READS_CONFIG`
7
+ * edge from the enclosing declaration that performs the read. A variable read in
8
+ * several places collapses to one node (keyed by name) with one counted edge per
9
+ * reading scope.
10
+ *
11
+ * The detection is purely syntactic (no symbol resolution), so it runs in the
12
+ * structural pass and is always emitted. A project that never touches
13
+ * `process.env` produces no config nodes or edges, leaving its graph unchanged.
14
+ */
15
+ export class ConfigExtractor {
16
+ static extract(sourceFile, rootPath) {
17
+ const nodes = [];
18
+ const edges = [];
19
+ const moduleId = NodeId.forModule(sourceFile.getFilePath(), rootPath);
20
+ for (const access of sourceFile.getDescendantsOfKind(SyntaxKind.PropertyAccessExpression)) {
21
+ if (ConfigExtractor.isProcessEnv(access.getExpression()) === true) {
22
+ ConfigExtractor.emit(access, access.getName(), moduleId, rootPath, nodes, edges);
23
+ }
24
+ }
25
+ for (const access of sourceFile.getDescendantsOfKind(SyntaxKind.ElementAccessExpression)) {
26
+ if (ConfigExtractor.isProcessEnv(access.getExpression()) === false) {
27
+ continue;
28
+ }
29
+ const name = ConfigExtractor.stringArgument(access);
30
+ if (name !== undefined) {
31
+ ConfigExtractor.emit(access, name, moduleId, rootPath, nodes, edges);
32
+ }
33
+ }
34
+ return { nodes, edges };
35
+ }
36
+ static emit(access, name, moduleId, rootPath, nodes, edges) {
37
+ if (name === '') {
38
+ return;
39
+ }
40
+ const flagId = NodeId.forConfigFlag(name);
41
+ nodes.push({ id: flagId, kind: 'ConfigFlag', name, filePath: 'process.env' });
42
+ const scopeId = ScopeResolver.enclosingId(access, moduleId, rootPath);
43
+ edges.push({ id: `READS_CONFIG:${scopeId}->${flagId}`, kind: 'READS_CONFIG', from: scopeId, to: flagId });
44
+ }
45
+ /** Whether a node is the `process.env` member access (`process` identifier, `env` name). */
46
+ static isProcessEnv(node) {
47
+ const access = node.asKind(SyntaxKind.PropertyAccessExpression);
48
+ if (access === undefined || access.getName() !== 'env') {
49
+ return false;
50
+ }
51
+ const target = access.getExpression();
52
+ return Node.isIdentifier(target) === true && target.getText() === 'process';
53
+ }
54
+ /** The literal key of `process.env['NAME']`, or undefined when the key is computed. */
55
+ static stringArgument(access) {
56
+ const element = access.asKind(SyntaxKind.ElementAccessExpression);
57
+ const literal = element?.getArgumentExpression()?.asKind(SyntaxKind.StringLiteral);
58
+ return literal?.getLiteralText();
59
+ }
60
+ }
61
+ //# sourceMappingURL=config_extractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config_extractor.js","sourceRoot":"","sources":["../../src/extract/config_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;;;;;;;;;;GAUG;AACH,MAAM,OAAO,eAAe;IAC3B,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,MAAM,IAAI,UAAU,CAAC,oBAAoB,CAAC,UAAU,CAAC,wBAAwB,CAAC,EAAE,CAAC;YAC3F,IAAI,eAAe,CAAC,YAAY,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC;gBACnE,eAAe,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;YAClF,CAAC;QACF,CAAC;QACD,KAAK,MAAM,MAAM,IAAI,UAAU,CAAC,oBAAoB,CAAC,UAAU,CAAC,uBAAuB,CAAC,EAAE,CAAC;YAC1F,IAAI,eAAe,CAAC,YAAY,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC,KAAK,KAAK,EAAE,CAAC;gBACpE,SAAS;YACV,CAAC;YACD,MAAM,IAAI,GAAG,eAAe,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;YACpD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBACxB,eAAe,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;YACtE,CAAC;QACF,CAAC;QAED,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IACzB,CAAC;IAEO,MAAM,CAAC,IAAI,CAClB,MAAY,EACZ,IAAY,EACZ,QAAgB,EAChB,QAAgB,EAChB,KAAkB,EAClB,KAAkB;QAElB,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;YACjB,OAAO;QACR,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QAC1C,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC,CAAC;QAC9E,MAAM,OAAO,GAAG,aAAa,CAAC,WAAW,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACtE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,gBAAgB,OAAO,KAAK,MAAM,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IAC3G,CAAC;IAED,4FAA4F;IACpF,MAAM,CAAC,YAAY,CAAC,IAAU;QACrC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,wBAAwB,CAAC,CAAC;QAChE,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,OAAO,EAAE,KAAK,KAAK,EAAE,CAAC;YACxD,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,MAAM,CAAC,OAAO,EAAE,KAAK,SAAS,CAAC;IAC7E,CAAC;IAED,uFAAuF;IAC/E,MAAM,CAAC,cAAc,CAAC,MAAY;QACzC,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,uBAAuB,CAAC,CAAC;QAClE,MAAM,OAAO,GAAG,OAAO,EAAE,qBAAqB,EAAE,EAAE,MAAM,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;QACnF,OAAO,OAAO,EAAE,cAAc,EAAE,CAAC;IAClC,CAAC;CACD"}
@@ -0,0 +1,36 @@
1
+ import { SourceFile } from 'ts-morph';
2
+ import { Extraction } from './structural_extractor.js';
3
+ /**
4
+ * Detects HTTP route registrations in the Express/Fastify style —
5
+ * `app.get('/path', handler)`, `router.post('/path', mw, handler)` — and emits an
6
+ * `Endpoint` node (keyed by method + path) plus a `HANDLES` edge from the endpoint
7
+ * to the function that handles it.
8
+ *
9
+ * A route is recognised syntactically: the callee is `<obj>.<verb>` for an HTTP
10
+ * verb, the first argument is a string-literal path, and the last argument is a
11
+ * handler — an inline function, or a name/member that resolves to an in-project
12
+ * callable. Resolving the named handler is why this runs in the semantic pass; an
13
+ * inline handler yields the `Endpoint` node alone (it has no declaration to point
14
+ * at).
15
+ *
16
+ * The match is a heuristic (it does not prove `obj` is an Express app), but the
17
+ * verb + string path + resolvable-handler combination is specific, so a project
18
+ * with no such call sites is unchanged. Other routers (Nest decorators, …) are out
19
+ * of scope for now — Express/Fastify first (#31 Part 3).
20
+ */
21
+ export declare class EndpointExtractor {
22
+ static extract(sourceFile: SourceFile, rootPath: string): Extraction;
23
+ private static extractRoute;
24
+ /**
25
+ * Classifies the last argument of a route call. An inline function is a valid
26
+ * handler with no node to point at; a name/member that resolves to an in-project
27
+ * callable is a valid handler whose emitted declaration becomes the `HANDLES`
28
+ * target; anything else means this is not a route.
29
+ */
30
+ private static classifyHandler;
31
+ /** Resolves a handler name/member to the in-project function, method, or function-valued variable it refers to. */
32
+ private static resolveCallable;
33
+ private static resolve;
34
+ private static inProject;
35
+ }
36
+ //# sourceMappingURL=endpoint_extractor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"endpoint_extractor.d.ts","sourceRoot":"","sources":["../../src/extract/endpoint_extractor.ts"],"names":[],"mappings":"AACA,OAAO,EAAwB,UAAU,EAAc,MAAM,UAAU,CAAC;AAKxE,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAQvD;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,iBAAiB;IAC7B,MAAM,CAAC,OAAO,CAAC,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU;IASpE,OAAO,CAAC,MAAM,CAAC,YAAY;IAoC3B;;;;;OAKG;IACH,OAAO,CAAC,MAAM,CAAC,eAAe;IAc9B,mHAAmH;IACnH,OAAO,CAAC,MAAM,CAAC,eAAe;IAkB9B,OAAO,CAAC,MAAM,CAAC,OAAO;IAMtB,OAAO,CAAC,MAAM,CAAC,SAAS;CAKxB"}