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
package/README.md CHANGED
@@ -25,7 +25,9 @@ Compiler API) rather than a syntax-only parser.
25
25
  ## Graph model
26
26
 
27
27
  **Nodes** — `Module`, `Class`, `Interface`, `TypeAlias`, `Enum`, `Function`,
28
- `Method`, `Property`, `Parameter`, `Variable`, `ExternalModule`.
28
+ `Method`, `Property`, `Parameter`, `Variable`, `ExternalModule`, and the
29
+ system-level `ConfigFlag` (environment variables), `ExternalAPI` (outbound HTTP
30
+ hosts), and `Endpoint` (HTTP routes).
29
31
 
30
32
  **Edges**
31
33
 
@@ -34,9 +36,22 @@ Compiler API) rather than a syntax-only parser.
34
36
  | Structural | `CONTAINS`, `IMPORTS`, `EXPORTS` |
35
37
  | Type | `EXTENDS`, `IMPLEMENTS`, `USES_TYPE`, `RETURNS`, `PARAM_TYPE` |
36
38
  | Behavioral | `CALLS`, `INSTANTIATES`, `OVERRIDES`, `READS`, `WRITES` |
37
-
38
- The structural layer is cheap and always emitted. The type + behavioral layers
39
- require symbol resolution and are emitted with `--semantic`.
39
+ | System-level | `READS_CONFIG`, `CALLS_EXTERNAL`, `HANDLES` |
40
+ | Runtime | `CALLS_RUNTIME` |
41
+
42
+ The structural layer — plus the always-on config and outbound-HTTP surfaces
43
+ (`ConfigFlag` / `READS_CONFIG`, `ExternalAPI` / `CALLS_EXTERNAL`) — is cheap and
44
+ needs no symbol resolution. The type, behavioral, and endpoint (`Endpoint` /
45
+ `HANDLES`) layers require symbol resolution and are emitted with `--semantic`. The
46
+ runtime layer (`CALLS_RUNTIME`) is reconstructed from a CPU profile's call tree by
47
+ [`enrich`](docs/commands/enrich.md), not parsed from source — the calls that
48
+ actually fired, dynamic dispatch included.
49
+
50
+ `ConfigFlag` nodes come from `process.env.X` reads; `ExternalAPI` nodes from
51
+ `fetch(...)` call sites (one per host); `Endpoint` nodes from route registrations
52
+ like `app.get('/users', handler)`, each with a `HANDLES` edge to the handler
53
+ function. These are the system-level kinds tracked in
54
+ [#31](https://github.com/jeromeetienne/ts_knowledge_graph/issues/31).
40
55
 
41
56
  ## Usage
42
57
 
@@ -50,9 +65,9 @@ npm run extract -- <path-to-project>
50
65
  npm run extract -- <path-to-project> --semantic
51
66
  ```
52
67
 
53
- Output is two JSONL files — `outputs/graph/nodes.jsonl` and
54
- `outputs/graph/edges.jsonl` (override with `--out`) one record per line, easy
55
- to inspect, diff, and load into any store.
68
+ Output is two JSONL files — `.ts_knowledge_graph/graph/nodes.jsonl` and
69
+ `.ts_knowledge_graph/graph/edges.jsonl` (override the base folder with `-o, --output-folder`)
70
+ — one record per line, easy to inspect, diff, and load into any store.
56
71
 
57
72
  ### Querying the graph
58
73
 
@@ -60,7 +75,7 @@ Load the JSONL into an embedded [Kùzu](https://kuzudb.com) database, then run t
60
75
  query tools:
61
76
 
62
77
  ```bash
63
- npm run dev -- load # reads ./outputs/graph, writes ./outputs/graph.kuzu
78
+ npm run dev -- load # reads ./.ts_knowledge_graph/graph, writes ./.ts_knowledge_graph/graph.kuzu
64
79
 
65
80
  npm run dev -- find <name> # resolve a name to node ids
66
81
  npm run dev -- who-calls <id> # direct callers of a symbol
@@ -69,6 +84,10 @@ npm run dev -- blast-radius <id> --depth 10 # transitive callers (impact set)
69
84
  npm run dev -- references <id> # everything that references a symbol/type
70
85
  npm run dev -- dead-exports # exported symbols with no inbound refs
71
86
  npm run dev -- neighbors <id> # one-hop neighbourhood (in + out)
87
+ npm run dev -- hotspots --by self-time # rank nodes by optimization leverage
88
+ npm run dev -- cost # inclusive cost + share-of-total (causal)
89
+ npm run dev -- cost <id> # where one node's cost goes / who causes it
90
+ npm run dev -- cluster # detect code communities (Leiden) -> metadata.community
72
91
  ```
73
92
 
74
93
  Every query command accepts `--json` to emit machine-readable output — this is
@@ -76,8 +95,8 @@ the shape the optimization agent consumes. Node ids come from `find` or another
76
95
  query's results; do not hand-write them.
77
96
 
78
97
  The query methods on `GraphQuery` (`whoCalls`, `blastRadius`, `deadExports`,
79
- `neighborhood`, …) are designed to map one-to-one onto agent tools: JSON in,
80
- JSON out.
98
+ `hotspots`, `costRanking`, `costAttribution`, `neighborhood`, …) are designed to
99
+ map one-to-one onto agent tools: JSON in, JSON out.
81
100
 
82
101
  For a task-oriented walk-through of these commands — using them by hand to
83
102
  answer impact, dead-code, and dependency questions — see the
@@ -93,36 +112,55 @@ answer impact, dead-code, and dependency questions — see the
93
112
 
94
113
  Serve the database as an interactive graph — pan/zoom, kind filters, symbol
95
114
  search, per-node edge listing (see
96
- [contribs/web_visualisation](contribs/web_visualisation)):
115
+ [contribs/webview](contribs/webview)):
97
116
 
98
117
  ```bash
99
- npm run web # reads ./outputs/graph.kuzu, serves http://localhost:4173
100
- npm run web -- --db ./outputs/graph.kuzu --port 8080
118
+ npm run webview # reads ./.ts_knowledge_graph/graph.kuzu, serves http://localhost:4173
119
+ npm run webview -- -o ./.ts_knowledge_graph --port 8080
101
120
  ```
102
121
 
103
122
  ### The optimization agent
104
123
 
105
124
  The end goal: an agent that uses the graph to find and apply optimizations,
106
- verifying each one before keeping it.
107
-
108
- ```bash
109
- cp .env-sample .env # pick a provider block, set key + model
110
- npm run dev -- optimize --db ./outputs/graph.kuzu
111
- npm run dev -- optimize "Inline the single-use helper X" --db ./outputs/graph.kuzu --model gpt-5.1
125
+ verifying each one before keeping it. It ships as a [Claude Code](https://claude.com/claude-code)
126
+ slash command, `/code-graph-optimize`, defined in
127
+ [`dotclaude_folder/commands/code-graph-optimize.md`](dotclaude_folder/commands/code-graph-optimize.md)
128
+ so the agent runtime is your Claude Code subscription, with no API key or
129
+ provider configuration to set up.
130
+
131
+ ```text
132
+ /code-graph-optimize
133
+ /code-graph-optimize Inline the single-use helper X
112
134
  ```
113
135
 
114
- The LLM layer sits on the **OpenAI-compatible chat-completions API**, so any
115
- provider exposing that surface works OpenAI, OpenRouter, Ollama, LM Studio,
116
- vLLM — configured entirely through `OPENAI_API_KEY`, `OPENAI_BASE_URL`, and
117
- `OPENAI_MODEL` (see [.env-sample](.env-sample)).
136
+ With no argument the command runs its default mission: find one genuinely dead
137
+ exported symbol, confirm it has zero inbound references, and remove it safely.
138
+
139
+ The command drives a find → confirm → edit → verify loop. It queries the graph
140
+ through this CLI (`dead-exports`, `references`, `who-calls`, `blast-radius`) to
141
+ gather context and confirm blast radius, makes exactly one edit, then runs
142
+ [`ts-knowledge-graph verify`](docs/commands/verify.md) — the type-check **and**
143
+ the test suite as a single gate. **If verify passes the edit stands; if it fails
144
+ the edit is reverted with `git restore`** and the change is abandoned or retried.
145
+ On a project with no test script verify degrades to type-check-only and the agent
146
+ says so, rather than implying the change was behaviourally verified. Run it on a
147
+ clean git tree so you can review (and `git checkout`) what it kept.
148
+
149
+ A companion command, `/code-graph-interview`
150
+ ([`code-graph-interview.md`](dotclaude_folder/commands/code-graph-interview.md)),
151
+ is read-only: it interviews you to scope a measurable optimization target and
152
+ grounds each candidate in the graph, producing tasks you can then hand to
153
+ `/code-graph-optimize`. Both commands, plus the `code-graph-query` skill, live
154
+ under [`dotclaude_folder/`](dotclaude_folder) and are mirrored into `.claude/`.
155
+
156
+ To install all of them into another project, run
157
+ [`install`](docs/commands/install.md) from that project — it copies every
158
+ bundled command and skill into the project's `.claude/` directory:
118
159
 
119
- The agent runs a tool-calling loop. Its tools are the read-only `GraphQuery`
120
- methods plus `read_file`; it gathers context, confirms blast radius, then calls
121
- `propose_optimization`. The harness **applies the edit, runs `tsc --noEmit`,
122
- and keeps it only if type-checking passes** — otherwise it reverts and hands
123
- the compiler errors back for another attempt. Edits are unique-match
124
- find/replace with in-memory backups; run on a clean git tree so you can review
125
- (and `git checkout`) what it kept.
160
+ ```bash
161
+ npx ts-knowledge-graph install # into ./.claude
162
+ npx ts-knowledge-graph install --force # overwrite previously installed copies
163
+ ```
126
164
 
127
165
  ## Architecture
128
166
 
@@ -141,14 +179,15 @@ src/
141
179
  kuzu_store.ts load the graph into embedded Kùzu, run Cypher
142
180
  query/
143
181
  graph_query.ts the agent's query tools (who-calls, blast-radius…)
144
- agent/
145
- agent_tools.ts graph queries + read_file + propose_optimization, as LLM tools
146
- code_editor.ts unique-match find/replace with in-memory backup + revert
147
- verifier.ts runs `tsc --noEmit`, returns pass/fail + output
148
- optimizer_agent.ts the LLM tool-calling loop (propose → verify → keep/revert)
149
- cli.ts extract / load / query / optimize commands
182
+ commands/ one file per CLI command (extract, load, query, web, install)
183
+ cli.ts wires the commands into the ts-knowledge-graph CLI
150
184
  ```
151
185
 
186
+ The optimization agent is not part of this `src/` tree — it is the
187
+ `/code-graph-optimize` Claude Code command under
188
+ [`dotclaude_folder/commands/`](dotclaude_folder/commands), which drives the same
189
+ queries through the CLI.
190
+
152
191
  Node ids are derived purely from the declaration (`kind:relPath#name@line`), so
153
192
  any extractor computes the same id for the same symbol without a shared
154
193
  registry — that is what lets the semantic layer link a call site to the exact
@@ -165,10 +204,29 @@ declaration node the structural layer emitted.
165
204
  contained member is referenced.
166
205
  - [x] **Value-reference (`READS`) edges** — value-identifier usage, so exported
167
206
  `const`s (e.g. schemas) are no longer false-positive dead exports.
168
- - [x] **Optimization agent** — an LLM tool-calling loop (OpenAI-compatible API,
169
- provider-agnostic) that proposes edits and keeps them only if `tsc --noEmit`
170
- passes (otherwise reverts).
171
- - [ ] **Test verification** — run the test suite alongside `tsc` in the verify
172
- step, so behavior-changing edits are caught, not just type errors.
207
+ - [x] **Runtime enrichment** — the [`enrich`](docs/commands/enrich.md) command
208
+ ingests a V8 CPU profile and attaches measured self time / sample count onto
209
+ nodes as `metadata.runtime`, joining frames to nodes by enclosing range.
210
+ - [x] **Hotspot / leverage ranking** — the [`hotspots`](docs/commands/hotspots.md)
211
+ command ranks nodes by optimization value (runtime self-time, fan-in,
212
+ call-count, or transitive blast radius), defaulting to measured self time when
213
+ enriched and degrading gracefully to static fan-in when not.
214
+ - [x] **Optimization agent** — the `/code-graph-optimize` Claude Code command,
215
+ which proposes one edit and keeps it only if [`verify`](docs/commands/verify.md)
216
+ (type-check **and** tests) passes (otherwise reverts with `git restore`).
217
+ - [x] **Test verification** — the [`verify`](docs/commands/verify.md) command runs
218
+ the project's `typecheck` and `test` scripts as one keep/revert gate, so
219
+ behavior-changing edits are caught, not just type errors. A project with no test
220
+ script degrades to type-check-only, reported honestly (`behaviorVerified: false`).
221
+ - [x] **Benchmark verification** — the [`benchmark`](docs/commands/benchmark.md)
222
+ command measures a target node's runtime metric (profile → enrich → cost) over
223
+ N runs and reports the median + spread, with an advisory baseline→after delta —
224
+ so an optimization is reported by its *measured* impact (e.g. −57% self-time on
225
+ `titleCase`) rather than a guess. Advisory by design, distinct from the hard
226
+ `verify` gate.
227
+ - [x] **Community detection** — the [`cluster`](docs/commands/cluster.md) command
228
+ runs the Leiden algorithm (CPM) over the weighted coupling graph and attaches a
229
+ module index onto nodes as `metadata.community`, with the internal-connectedness
230
+ guarantee Louvain lacks.
173
231
  - [ ] **Vector index** — embed per-node summaries for hybrid graph + semantic
174
232
  retrieval, so the agent can find candidates by meaning, not just by name.
@@ -0,0 +1,83 @@
1
+ # WebView
2
+
3
+ Interactive viewer for the knowledge graph — pan/zoom, color-coded node and
4
+ edge kinds with toggleable filters, symbol search, and a per-node detail panel
5
+ showing every incoming/outgoing edge.
6
+
7
+ **No server required.** The page (in [web/](web)) is fully static; only the
8
+ Cytoscape.js library comes from a CDN (so you need internet, or vendor the
9
+ file).
10
+
11
+ ## Commands
12
+
13
+ ```bash
14
+ npm run build # embed ../../.ts_knowledge_graph/graph/*.jsonl into web/js_autogenerated/graph_data.js
15
+ npm start # serve web/ on http://localhost:4173 (optional)
16
+ npm run open # open web/index.html directly (file://, macOS)
17
+ ```
18
+
19
+ ## Loading data — pick one
20
+
21
+ **A. Embed the data, then open the page** (works from `file://`, no server):
22
+
23
+ ```bash
24
+ # from the repo root, after `npm run extract -- . --semantic`
25
+ cd contribs/webview
26
+ npm run build
27
+ npm run open
28
+ ```
29
+
30
+ `scripts/build-data.ts` reads `../../.ts_knowledge_graph/graph/{nodes,edges}.jsonl` by
31
+ default; pass a different graph directory as the first argument
32
+ (`npx tsx scripts/build-data.ts /path/to/graph`).
33
+
34
+ **B. Drag & drop** — open `web/index.html` any way at all and drop
35
+ `.ts_knowledge_graph/graph/nodes.jsonl` + `.ts_knowledge_graph/graph/edges.jsonl` onto the page.
36
+
37
+ **C. Serve the repo root** — the page then auto-fetches
38
+ `../../../.ts_knowledge_graph/graph/*.jsonl`:
39
+
40
+ ```bash
41
+ # from the repo root
42
+ npx serve # or: python3 -m http.server
43
+ # open http://localhost:3000/contribs/webview/web/
44
+ ```
45
+
46
+ ## Reading the graph
47
+
48
+ - **Node size** scales with degree (how connected a symbol is).
49
+ - **Edge colors**: red `CALLS`, green/teal type edges (`USES_TYPE`, `RETURNS`,
50
+ `PARAM_TYPE`), yellow `READS`, violet heritage, gray structure
51
+ (`CONTAINS`, `IMPORTS`).
52
+ - **Edge width** scales with call-site count — how many times the call / import /
53
+ read occurs between the two symbols (shown as `×N` in the detail panel). Thick
54
+ edges are the hot connections.
55
+ - Uncheck noisy kinds (`CONTAINS`, `IMPORTS`) to see the behavioral core;
56
+ enable **hide isolated nodes** to drop whatever the filter disconnected.
57
+ - The **all** checkbox atop Node kinds / Edge kinds toggles every kind at once —
58
+ hide all, then re-check just the few you want to isolate (it shows a dash when
59
+ only some kinds are visible).
60
+ - Click a node to fade everything outside its neighborhood and list its edges
61
+ in the sidebar — the links navigate the graph.
62
+ - **Fold any sidebar section** by clicking its header (Runtime, Hotspots, Node
63
+ kinds, Edge kinds, …); the collapsed state is remembered across reloads.
64
+
65
+ ## Runtime hotspots
66
+
67
+ When the graph has been enriched (`ts-knowledge-graph enrich <profile.cpuprofile>`),
68
+ each measured symbol carries `metadata.runtime` (self-time + sample count). The
69
+ sidebar's **Runtime** panel surfaces it:
70
+
71
+ - **Coverage line** — how many nodes were measured and the total self-time, so a
72
+ partial profile reads as partial.
73
+ - **Heat map toggle** — re-encodes the graph by measured self-time: nodes are
74
+ **sized and heat-coloured** (cool → yellow → red) by how hot they are, instead
75
+ of by kind/degree. Toggle off to return to the structural view.
76
+ - **Hotspots list** — the top symbols ranked by self-time; click one to focus it.
77
+ - **Only measured nodes** — hides the un-enriched nodes so only the measured
78
+ subgraph remains, to focus on where the cost actually is.
79
+ - Selecting any node adds a **runtime** block (self-time, samples, source) to the
80
+ detail panel.
81
+
82
+ Un-measured nodes render at a neutral, dashed baseline — "no metric" means
83
+ *inlined or not sampled*, not "free".
@@ -0,0 +1,310 @@
1
+ /* Dark theme (default). Every colour is a variable so a single `data-theme`
2
+ attribute on <html> repaints the whole viewer; see [data-theme="light"] below
3
+ and the matching graph-canvas variables read by js/app.js (cyStyle). */
4
+ :root {
5
+ color-scheme: dark;
6
+ --bg: #0f172a;
7
+ --surface: #111c33;
8
+ --surface-raised: #1e293b;
9
+ --surface-hover: #334155;
10
+ --border: #1e293b;
11
+ --border-control: #334155;
12
+ --border-subtle: #475569;
13
+ --input-bg: #0f172a;
14
+ --text: #cbd5e1;
15
+ --heading: #f1f5f9;
16
+ --muted: #64748b;
17
+ --muted-strong: #94a3b8;
18
+ --text-on-control: #e2e8f0;
19
+ --accent: #4f8cff;
20
+ --link: #7db2ff;
21
+ --tooltip-bg: #0b1424;
22
+ --tooltip-text: #e2e8f0;
23
+ --overlay-bg: rgba(15, 23, 42, .85);
24
+ --shadow: 0 4px 14px rgba(0, 0, 0, .45);
25
+ --on-swatch: #0f172a;
26
+ --num: #f59e0b;
27
+ --unmeasured-fill: #243044;
28
+ --unmeasured-border: #475569;
29
+ /* Graph canvas colours, read from JS so the graph tracks the theme. */
30
+ --graph-label: #cbd5e1;
31
+ --graph-label-bg: #0f172a;
32
+ --graph-sel-border: #ffffff;
33
+ --graph-node-border: #334155;
34
+ --graph-node-border-width: 0;
35
+ }
36
+
37
+ [data-theme="light"] {
38
+ color-scheme: light;
39
+ --bg: #f8fafc;
40
+ --surface: #f1f5f9;
41
+ --surface-raised: #e2e8f0;
42
+ --surface-hover: #cbd5e1;
43
+ --border: #e2e8f0;
44
+ --border-control: #cbd5e1;
45
+ --border-subtle: #94a3b8;
46
+ --input-bg: #ffffff;
47
+ --text: #334155;
48
+ --heading: #0f172a;
49
+ --muted: #64748b;
50
+ --muted-strong: #475569;
51
+ --text-on-control: #1e293b;
52
+ --accent: #2563eb;
53
+ --link: #2563eb;
54
+ --tooltip-bg: #ffffff;
55
+ --tooltip-text: #1e293b;
56
+ --overlay-bg: rgba(226, 232, 240, .85);
57
+ --shadow: 0 4px 14px rgba(15, 23, 42, .18);
58
+ --on-swatch: #0f172a;
59
+ --num: #b45309;
60
+ --unmeasured-fill: #e2e8f0;
61
+ --unmeasured-border: #94a3b8;
62
+ --graph-label: #334155;
63
+ --graph-label-bg: #f8fafc;
64
+ --graph-sel-border: #0f172a;
65
+ --graph-node-border: #cbd5e1;
66
+ --graph-node-border-width: 1;
67
+ }
68
+
69
+ * { box-sizing: border-box; }
70
+
71
+ html, body {
72
+ margin: 0;
73
+ height: 100%;
74
+ font: 13px/1.45 -apple-system, 'Segoe UI', Roboto, sans-serif;
75
+ background: var(--bg);
76
+ color: var(--text);
77
+ }
78
+
79
+ body { display: flex; }
80
+
81
+ #sidebar {
82
+ width: 300px;
83
+ flex: none;
84
+ height: 100%;
85
+ overflow-y: auto;
86
+ padding: 14px;
87
+ background: var(--surface);
88
+ border-right: 1px solid var(--border);
89
+ }
90
+
91
+ .sidebar-head { display: flex; align-items: center; gap: 8px; margin: 0 0 4px; }
92
+ #sidebar h1 { font-size: 15px; margin: 0; flex: 1; color: var(--heading); }
93
+ #sidebar h2 { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); margin: 16px 0 6px; }
94
+ #sidebar section { margin-top: 8px; }
95
+
96
+ .theme-toggle {
97
+ flex: none;
98
+ width: 26px;
99
+ height: 26px;
100
+ padding: 0;
101
+ display: inline-flex;
102
+ align-items: center;
103
+ justify-content: center;
104
+ font-size: 14px;
105
+ line-height: 1;
106
+ border-radius: 6px;
107
+ background: var(--surface-raised);
108
+ color: var(--text-on-control);
109
+ border: 1px solid var(--border-control);
110
+ cursor: pointer;
111
+ }
112
+ .theme-toggle:hover { background: var(--surface-hover); }
113
+
114
+ #sidebar .foldable {
115
+ display: flex;
116
+ align-items: center;
117
+ gap: 7px;
118
+ line-height: 1;
119
+ cursor: pointer;
120
+ user-select: none;
121
+ }
122
+ #sidebar .foldable:hover { color: var(--muted-strong); }
123
+ #sidebar .foldable::before {
124
+ content: '';
125
+ flex: none;
126
+ width: 0;
127
+ height: 0;
128
+ border-left: 5px solid currentColor;
129
+ border-top: 4px solid transparent;
130
+ border-bottom: 4px solid transparent;
131
+ transition: transform .12s ease;
132
+ }
133
+ #sidebar .foldable:not(.collapsed)::before { transform: rotate(90deg); }
134
+ #sidebar .foldable.collapsed ~ * { display: none; }
135
+
136
+ #status { font-size: 11px; color: var(--muted); margin-bottom: 10px; }
137
+
138
+ #search {
139
+ width: 100%;
140
+ padding: 6px 8px;
141
+ border: 1px solid var(--border-control);
142
+ border-radius: 6px;
143
+ background: var(--input-bg);
144
+ color: var(--text-on-control);
145
+ }
146
+ #search:focus { outline: none; border-color: var(--accent); }
147
+
148
+ #search-results { margin-top: 4px; }
149
+ #search-results .hit {
150
+ padding: 3px 6px;
151
+ border-radius: 4px;
152
+ cursor: pointer;
153
+ font-size: 12px;
154
+ white-space: nowrap;
155
+ overflow: hidden;
156
+ text-overflow: ellipsis;
157
+ }
158
+ #search-results .hit:hover { background: var(--surface-raised); }
159
+ #search-results .hit .loc { color: var(--muted); font-size: 10px; }
160
+
161
+ .legend label {
162
+ display: flex;
163
+ align-items: center;
164
+ gap: 6px;
165
+ padding: 1px 0;
166
+ cursor: pointer;
167
+ user-select: none;
168
+ }
169
+ .legend .swatch {
170
+ width: 10px;
171
+ height: 10px;
172
+ border-radius: 3px;
173
+ flex: none;
174
+ }
175
+ .legend .count { margin-left: auto; color: var(--muted); font-size: 11px; }
176
+ .legend .help-badge {
177
+ flex: none;
178
+ width: 13px;
179
+ height: 13px;
180
+ border-radius: 50%;
181
+ border: 1px solid var(--border-subtle);
182
+ color: var(--muted-strong);
183
+ font-size: 9px;
184
+ font-weight: 600;
185
+ line-height: 11px;
186
+ text-align: center;
187
+ user-select: none;
188
+ opacity: 0.5;
189
+ }
190
+ .legend .help-badge:hover,
191
+ .legend .help-badge:focus {
192
+ background: var(--surface-hover);
193
+ color: var(--text-on-control);
194
+ border-color: var(--muted);
195
+ outline: none;
196
+ }
197
+ .legend label.master {
198
+ margin-bottom: 4px;
199
+ padding-bottom: 4px;
200
+ border-bottom: 1px solid var(--border);
201
+ color: var(--muted-strong);
202
+ }
203
+ .legend .swatch.spacer { background: transparent; }
204
+
205
+ .kind-tooltip {
206
+ position: fixed;
207
+ z-index: 1000;
208
+ max-width: 260px;
209
+ padding: 6px 9px;
210
+ border-radius: 6px;
211
+ background: var(--tooltip-bg);
212
+ border: 1px solid var(--border-control);
213
+ color: var(--tooltip-text);
214
+ font-size: 11px;
215
+ line-height: 1.4;
216
+ box-shadow: var(--shadow);
217
+ pointer-events: none;
218
+ }
219
+ .kind-tooltip[hidden] { display: none; }
220
+
221
+ .row { display: flex; align-items: center; gap: 6px; margin: 4px 0; }
222
+
223
+ select, button {
224
+ background: var(--surface-raised);
225
+ color: var(--text-on-control);
226
+ border: 1px solid var(--border-control);
227
+ border-radius: 6px;
228
+ padding: 4px 8px;
229
+ font: inherit;
230
+ cursor: pointer;
231
+ }
232
+ button:hover { background: var(--surface-hover); }
233
+ select { flex: 1; }
234
+ .colour-by label { font-size: 12px; color: var(--muted-strong); white-space: nowrap; }
235
+
236
+ #details-body { font-size: 12px; }
237
+ #details-body .id { color: var(--muted); font-size: 10px; word-break: break-all; }
238
+ #details-body .kind-tag {
239
+ display: inline-block;
240
+ padding: 1px 6px;
241
+ border-radius: 4px;
242
+ font-size: 10px;
243
+ color: var(--on-swatch);
244
+ font-weight: 600;
245
+ }
246
+ #details-body h3 { font-size: 11px; color: var(--muted); margin: 10px 0 3px; }
247
+ #details-body .edge-row { padding: 1px 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
248
+ #details-body .edge-kind { color: var(--muted); font-size: 10px; }
249
+ #details-body .edge-count { color: var(--num); font-size: 10px; font-variant-numeric: tabular-nums; }
250
+ #details-body a { color: var(--link); cursor: pointer; text-decoration: none; }
251
+ #details-body a:hover { text-decoration: underline; }
252
+ #details-body a.file-link { text-decoration: underline; word-break: break-all; }
253
+ #details-body a.file-link::after { content: '↗'; margin-left: 3px; font-size: 9px; opacity: 0.7; }
254
+
255
+ #runtime.empty .heat-legend,
256
+ #runtime.empty .heat-note,
257
+ #runtime.empty .hotspots-title,
258
+ #runtime.empty #hotspots,
259
+ #runtime.empty label.row { display: none; }
260
+
261
+ #communities.empty { display: none; }
262
+
263
+ #coverage { font-size: 11px; color: var(--muted-strong); margin: 2px 0 6px; }
264
+
265
+ #runtime label.row { font-size: 12px; }
266
+
267
+ .heat-legend { display: flex; align-items: center; gap: 6px; margin: 8px 0 4px; }
268
+ .heat-bar { flex: 1; height: 10px; border-radius: 3px; background: linear-gradient(90deg, #64748b, #fde047, #dc2626); }
269
+ .heat-legend .heat-min, .heat-legend .heat-max { font-size: 10px; color: var(--muted); }
270
+
271
+ .heat-note { display: flex; align-items: center; gap: 6px; font-size: 10px; color: var(--muted); }
272
+ .heat-swatch { width: 10px; height: 10px; border-radius: 3px; flex: none; display: inline-block; }
273
+ .heat-swatch.unmeasured { background: var(--unmeasured-fill); border: 1px dashed var(--unmeasured-border); }
274
+
275
+ .hotspots-title { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); margin: 12px 0 4px; }
276
+ #hotspots .hotspot {
277
+ display: flex;
278
+ align-items: center;
279
+ gap: 6px;
280
+ padding: 2px 4px;
281
+ border-radius: 4px;
282
+ cursor: pointer;
283
+ font-size: 12px;
284
+ }
285
+ #hotspots .hotspot:hover { background: var(--surface-raised); }
286
+ .hotspot-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
287
+ .hotspot-ms { margin-left: auto; flex: none; color: var(--num); font-size: 11px; font-variant-numeric: tabular-nums; }
288
+
289
+ #details-body .runtime-block { margin-top: 8px; }
290
+ #details-body .runtime-block .metric { display: flex; justify-content: space-between; gap: 8px; padding: 1px 0; }
291
+ #details-body .runtime-block .metric span { color: var(--muted); }
292
+
293
+ #cy { flex: 1; height: 100%; }
294
+
295
+ #dropzone {
296
+ position: fixed;
297
+ inset: 0;
298
+ display: flex;
299
+ align-items: center;
300
+ justify-content: center;
301
+ font-size: 18px;
302
+ color: var(--text-on-control);
303
+ background: var(--overlay-bg);
304
+ border: 3px dashed var(--accent);
305
+ pointer-events: none;
306
+ opacity: 0;
307
+ transition: opacity .15s;
308
+ }
309
+ #dropzone.active { opacity: 1; }
310
+ #dropzone code { color: var(--link); }