ts-knowledge-graph 0.1.1 → 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 (212) hide show
  1. package/README.md +104 -43
  2. package/contribs/web_visualisation/README.md +83 -0
  3. package/contribs/web_visualisation/web/css/style.css +219 -0
  4. package/contribs/web_visualisation/web/data/.gitignore +3 -0
  5. package/contribs/web_visualisation/web/data/kind_descriptions.js +38 -0
  6. package/contribs/web_visualisation/web/index.html +74 -0
  7. package/contribs/web_visualisation/web/js/app.js +910 -0
  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 +0 -9
  19. package/dist/cli.d.ts.map +1 -1
  20. package/dist/cli.js +32 -208
  21. package/dist/cli.js.map +1 -1
  22. package/dist/commands/benchmark_command.d.ts +11 -0
  23. package/dist/commands/benchmark_command.d.ts.map +1 -0
  24. package/dist/commands/benchmark_command.js +91 -0
  25. package/dist/commands/benchmark_command.js.map +1 -0
  26. package/dist/commands/blast_radius_command.d.ts +5 -0
  27. package/dist/commands/blast_radius_command.d.ts.map +1 -0
  28. package/dist/commands/blast_radius_command.js +18 -0
  29. package/dist/commands/blast_radius_command.js.map +1 -0
  30. package/dist/commands/calls_command.d.ts +5 -0
  31. package/dist/commands/calls_command.d.ts.map +1 -0
  32. package/dist/commands/calls_command.js +7 -0
  33. package/dist/commands/calls_command.js.map +1 -0
  34. package/dist/commands/command_helpers.d.ts +15 -0
  35. package/dist/commands/command_helpers.d.ts.map +1 -0
  36. package/dist/commands/command_helpers.js +61 -0
  37. package/dist/commands/command_helpers.js.map +1 -0
  38. package/dist/commands/cost_command.d.ts +13 -0
  39. package/dist/commands/cost_command.d.ts.map +1 -0
  40. package/dist/commands/cost_command.js +122 -0
  41. package/dist/commands/cost_command.js.map +1 -0
  42. package/dist/commands/dead_exports_command.d.ts +5 -0
  43. package/dist/commands/dead_exports_command.d.ts.map +1 -0
  44. package/dist/commands/dead_exports_command.js +7 -0
  45. package/dist/commands/dead_exports_command.js.map +1 -0
  46. package/dist/commands/enrich_command.d.ts +7 -0
  47. package/dist/commands/enrich_command.d.ts.map +1 -0
  48. package/dist/commands/enrich_command.js +62 -0
  49. package/dist/commands/enrich_command.js.map +1 -0
  50. package/dist/commands/extract_command.d.ts +8 -0
  51. package/dist/commands/extract_command.d.ts.map +1 -0
  52. package/dist/commands/extract_command.js +49 -0
  53. package/dist/commands/extract_command.js.map +1 -0
  54. package/dist/commands/find_command.d.ts +5 -0
  55. package/dist/commands/find_command.d.ts.map +1 -0
  56. package/dist/commands/find_command.js +7 -0
  57. package/dist/commands/find_command.js.map +1 -0
  58. package/dist/commands/hotspots_command.d.ts +7 -0
  59. package/dist/commands/hotspots_command.d.ts.map +1 -0
  60. package/dist/commands/hotspots_command.js +67 -0
  61. package/dist/commands/hotspots_command.js.map +1 -0
  62. package/dist/commands/install_command.d.ts +15 -0
  63. package/dist/commands/install_command.d.ts.map +1 -0
  64. package/dist/commands/install_command.js +41 -0
  65. package/dist/commands/install_command.js.map +1 -0
  66. package/dist/commands/load_command.d.ts +6 -0
  67. package/dist/commands/load_command.d.ts.map +1 -0
  68. package/dist/commands/load_command.js +30 -0
  69. package/dist/commands/load_command.js.map +1 -0
  70. package/dist/commands/neighbors_command.d.ts +5 -0
  71. package/dist/commands/neighbors_command.d.ts.map +1 -0
  72. package/dist/commands/neighbors_command.js +17 -0
  73. package/dist/commands/neighbors_command.js.map +1 -0
  74. package/dist/commands/references_command.d.ts +5 -0
  75. package/dist/commands/references_command.d.ts.map +1 -0
  76. package/dist/commands/references_command.js +17 -0
  77. package/dist/commands/references_command.js.map +1 -0
  78. package/dist/commands/verify_command.d.ts +8 -0
  79. package/dist/commands/verify_command.d.ts.map +1 -0
  80. package/dist/commands/verify_command.js +57 -0
  81. package/dist/commands/verify_command.js.map +1 -0
  82. package/dist/commands/web_command.d.ts +46 -0
  83. package/dist/commands/web_command.d.ts.map +1 -0
  84. package/dist/commands/web_command.js +226 -0
  85. package/dist/commands/web_command.js.map +1 -0
  86. package/dist/commands/who_calls_command.d.ts +5 -0
  87. package/dist/commands/who_calls_command.d.ts.map +1 -0
  88. package/dist/commands/who_calls_command.js +7 -0
  89. package/dist/commands/who_calls_command.js.map +1 -0
  90. package/dist/enrich/cpu_profile.d.ts +127 -0
  91. package/dist/enrich/cpu_profile.d.ts.map +1 -0
  92. package/dist/enrich/cpu_profile.js +97 -0
  93. package/dist/enrich/cpu_profile.js.map +1 -0
  94. package/dist/enrich/runtime_enricher.d.ts +56 -0
  95. package/dist/enrich/runtime_enricher.d.ts.map +1 -0
  96. package/dist/enrich/runtime_enricher.js +80 -0
  97. package/dist/enrich/runtime_enricher.js.map +1 -0
  98. package/dist/enrich/runtime_join.d.ts +100 -0
  99. package/dist/enrich/runtime_join.d.ts.map +1 -0
  100. package/dist/enrich/runtime_join.js +227 -0
  101. package/dist/enrich/runtime_join.js.map +1 -0
  102. package/dist/extract/api_extractor.d.ts +24 -0
  103. package/dist/extract/api_extractor.d.ts.map +1 -0
  104. package/dist/extract/api_extractor.js +71 -0
  105. package/dist/extract/api_extractor.js.map +1 -0
  106. package/dist/extract/config_extractor.d.ts +22 -0
  107. package/dist/extract/config_extractor.d.ts.map +1 -0
  108. package/dist/extract/config_extractor.js +61 -0
  109. package/dist/extract/config_extractor.js.map +1 -0
  110. package/dist/extract/endpoint_extractor.d.ts +36 -0
  111. package/dist/extract/endpoint_extractor.d.ts.map +1 -0
  112. package/dist/extract/endpoint_extractor.js +117 -0
  113. package/dist/extract/endpoint_extractor.js.map +1 -0
  114. package/dist/extract/{graph-builder.d.ts → graph_builder.d.ts} +9 -1
  115. package/dist/extract/graph_builder.d.ts.map +1 -0
  116. package/dist/extract/graph_builder.js +61 -0
  117. package/dist/extract/graph_builder.js.map +1 -0
  118. package/dist/extract/node_id.d.ts +24 -0
  119. package/dist/extract/node_id.d.ts.map +1 -0
  120. package/dist/extract/node_id.js +44 -0
  121. package/dist/extract/node_id.js.map +1 -0
  122. package/dist/extract/{project-loader.d.ts → project_loader.d.ts} +1 -1
  123. package/dist/extract/project_loader.d.ts.map +1 -0
  124. package/dist/extract/{project-loader.js → project_loader.js} +1 -1
  125. package/dist/extract/{project-loader.js.map → project_loader.js.map} +1 -1
  126. package/dist/extract/scope_resolver.d.ts +22 -0
  127. package/dist/extract/scope_resolver.d.ts.map +1 -0
  128. package/dist/extract/scope_resolver.js +53 -0
  129. package/dist/extract/scope_resolver.js.map +1 -0
  130. package/dist/extract/semantic_extractor.d.ts +47 -0
  131. package/dist/extract/semantic_extractor.d.ts.map +1 -0
  132. package/dist/extract/{semantic-extractor.js → semantic_extractor.js} +98 -4
  133. package/dist/extract/semantic_extractor.js.map +1 -0
  134. package/dist/extract/{structural-extractor.d.ts → structural_extractor.d.ts} +7 -1
  135. package/dist/extract/{structural-extractor.d.ts.map → structural_extractor.d.ts.map} +1 -1
  136. package/dist/extract/{structural-extractor.js → structural_extractor.js} +24 -14
  137. package/dist/extract/structural_extractor.js.map +1 -0
  138. package/dist/project_root.d.ts +7 -0
  139. package/dist/project_root.d.ts.map +1 -0
  140. package/dist/project_root.js +9 -0
  141. package/dist/project_root.js.map +1 -0
  142. package/dist/query/graph_query.d.ts +262 -0
  143. package/dist/query/graph_query.d.ts.map +1 -0
  144. package/dist/query/graph_query.js +604 -0
  145. package/dist/query/graph_query.js.map +1 -0
  146. package/dist/schema/edge.d.ts +40 -5
  147. package/dist/schema/edge.d.ts.map +1 -1
  148. package/dist/schema/edge.js +70 -0
  149. package/dist/schema/edge.js.map +1 -1
  150. package/dist/schema/node.d.ts +20 -5
  151. package/dist/schema/node.d.ts.map +1 -1
  152. package/dist/schema/node.js +36 -0
  153. package/dist/schema/node.js.map +1 -1
  154. package/dist/schema/runtime_manifest.d.ts +36 -0
  155. package/dist/schema/runtime_manifest.d.ts.map +1 -0
  156. package/dist/schema/runtime_manifest.js +23 -0
  157. package/dist/schema/runtime_manifest.js.map +1 -0
  158. package/dist/store/{jsonl-reader.d.ts → jsonl_reader.d.ts} +1 -1
  159. package/dist/store/{jsonl-reader.d.ts.map → jsonl_reader.d.ts.map} +1 -1
  160. package/dist/store/{jsonl-reader.js → jsonl_reader.js} +1 -1
  161. package/dist/store/{jsonl-reader.js.map → jsonl_reader.js.map} +1 -1
  162. package/dist/store/{jsonl-store.d.ts → jsonl_store.d.ts} +1 -1
  163. package/dist/store/{jsonl-store.d.ts.map → jsonl_store.d.ts.map} +1 -1
  164. package/dist/store/{jsonl-store.js → jsonl_store.js} +1 -1
  165. package/dist/store/{jsonl-store.js.map → jsonl_store.js.map} +1 -1
  166. package/dist/store/kuzu_store.d.ts +66 -0
  167. package/dist/store/kuzu_store.d.ts.map +1 -0
  168. package/dist/store/kuzu_store.js +156 -0
  169. package/dist/store/kuzu_store.js.map +1 -0
  170. package/dist/verify/project_verifier.d.ts +85 -0
  171. package/dist/verify/project_verifier.d.ts.map +1 -0
  172. package/dist/verify/project_verifier.js +138 -0
  173. package/dist/verify/project_verifier.js.map +1 -0
  174. package/dotclaude_folder/skills/code-graph-query/SKILL.md +91 -0
  175. package/package.json +88 -5
  176. package/.env-sample +0 -34
  177. package/dist/agent/agent-tools.d.ts +0 -13
  178. package/dist/agent/agent-tools.d.ts.map +0 -1
  179. package/dist/agent/agent-tools.js +0 -153
  180. package/dist/agent/agent-tools.js.map +0 -1
  181. package/dist/agent/code-editor.d.ts +0 -18
  182. package/dist/agent/code-editor.d.ts.map +0 -1
  183. package/dist/agent/code-editor.js +0 -43
  184. package/dist/agent/code-editor.js.map +0 -1
  185. package/dist/agent/optimizer-agent.d.ts +0 -30
  186. package/dist/agent/optimizer-agent.d.ts.map +0 -1
  187. package/dist/agent/optimizer-agent.js +0 -97
  188. package/dist/agent/optimizer-agent.js.map +0 -1
  189. package/dist/agent/verifier.d.ts +0 -9
  190. package/dist/agent/verifier.d.ts.map +0 -1
  191. package/dist/agent/verifier.js +0 -19
  192. package/dist/agent/verifier.js.map +0 -1
  193. package/dist/extract/graph-builder.d.ts.map +0 -1
  194. package/dist/extract/graph-builder.js +0 -39
  195. package/dist/extract/graph-builder.js.map +0 -1
  196. package/dist/extract/node-id.d.ts +0 -8
  197. package/dist/extract/node-id.d.ts.map +0 -1
  198. package/dist/extract/node-id.js +0 -22
  199. package/dist/extract/node-id.js.map +0 -1
  200. package/dist/extract/project-loader.d.ts.map +0 -1
  201. package/dist/extract/semantic-extractor.d.ts +0 -22
  202. package/dist/extract/semantic-extractor.d.ts.map +0 -1
  203. package/dist/extract/semantic-extractor.js.map +0 -1
  204. package/dist/extract/structural-extractor.js.map +0 -1
  205. package/dist/query/graph-query.d.ts +0 -28
  206. package/dist/query/graph-query.d.ts.map +0 -1
  207. package/dist/query/graph-query.js +0 -93
  208. package/dist/query/graph-query.js.map +0 -1
  209. package/dist/store/kuzu-store.d.ts +0 -14
  210. package/dist/store/kuzu-store.d.ts.map +0 -1
  211. package/dist/store/kuzu-store.js +0 -52
  212. package/dist/store/kuzu-store.js.map +0 -1
package/README.md CHANGED
@@ -1,9 +1,15 @@
1
- # open_ts_optim_ai
1
+ # ts_knowledge_graph
2
2
 
3
3
  Parse TypeScript source code into a **knowledge graph**, then use that graph as
4
4
  the substrate for an autonomous AI agent that finds and applies code
5
5
  optimizations.
6
6
 
7
+ ## Documentation
8
+
9
+ Full documentation lives in [`./docs`](docs/INDEX.md). The
10
+ [documentation index](docs/INDEX.md) describes every guide and command — start
11
+ there, or jump straight to [Getting Started](docs/GETTING_STARTED.md).
12
+
7
13
  ## Why a graph
8
14
 
9
15
  An optimization agent constantly needs to reason about *blast radius*:
@@ -19,7 +25,9 @@ Compiler API) rather than a syntax-only parser.
19
25
  ## Graph model
20
26
 
21
27
  **Nodes** — `Module`, `Class`, `Interface`, `TypeAlias`, `Enum`, `Function`,
22
- `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).
23
31
 
24
32
  **Edges**
25
33
 
@@ -28,9 +36,18 @@ Compiler API) rather than a syntax-only parser.
28
36
  | Structural | `CONTAINS`, `IMPORTS`, `EXPORTS` |
29
37
  | Type | `EXTENDS`, `IMPLEMENTS`, `USES_TYPE`, `RETURNS`, `PARAM_TYPE` |
30
38
  | Behavioral | `CALLS`, `INSTANTIATES`, `OVERRIDES`, `READS`, `WRITES` |
39
+ | System-level | `READS_CONFIG`, `CALLS_EXTERNAL`, `HANDLES` |
40
+
41
+ The structural layer — plus the always-on config and outbound-HTTP surfaces
42
+ (`ConfigFlag` / `READS_CONFIG`, `ExternalAPI` / `CALLS_EXTERNAL`) — is cheap and
43
+ needs no symbol resolution. The type, behavioral, and endpoint (`Endpoint` /
44
+ `HANDLES`) layers require symbol resolution and are emitted with `--semantic`.
31
45
 
32
- The structural layer is cheap and always emitted. The type + behavioral layers
33
- require symbol resolution and are emitted with `--semantic`.
46
+ `ConfigFlag` nodes come from `process.env.X` reads; `ExternalAPI` nodes from
47
+ `fetch(...)` call sites (one per host); `Endpoint` nodes from route registrations
48
+ like `app.get('/users', handler)`, each with a `HANDLES` edge to the handler
49
+ function. These are the system-level kinds tracked in
50
+ [#31](https://github.com/jeromeetienne/ts_knowledge_graph/issues/31).
34
51
 
35
52
  ## Usage
36
53
 
@@ -63,6 +80,9 @@ npm run dev -- blast-radius <id> --depth 10 # transitive callers (impact set)
63
80
  npm run dev -- references <id> # everything that references a symbol/type
64
81
  npm run dev -- dead-exports # exported symbols with no inbound refs
65
82
  npm run dev -- neighbors <id> # one-hop neighbourhood (in + out)
83
+ npm run dev -- hotspots --by self-time # rank nodes by optimization leverage
84
+ npm run dev -- cost # inclusive cost + share-of-total (causal)
85
+ npm run dev -- cost <id> # where one node's cost goes / who causes it
66
86
  ```
67
87
 
68
88
  Every query command accepts `--json` to emit machine-readable output — this is
@@ -70,8 +90,12 @@ the shape the optimization agent consumes. Node ids come from `find` or another
70
90
  query's results; do not hand-write them.
71
91
 
72
92
  The query methods on `GraphQuery` (`whoCalls`, `blastRadius`, `deadExports`,
73
- `neighborhood`, …) are designed to map one-to-one onto agent tools: JSON in,
74
- JSON out.
93
+ `hotspots`, `costRanking`, `costAttribution`, `neighborhood`, …) are designed to
94
+ map one-to-one onto agent tools: JSON in, JSON out.
95
+
96
+ For a task-oriented walk-through of these commands — using them by hand to
97
+ answer impact, dead-code, and dependency questions — see the
98
+ [Static Analysis guide](docs/STATIC_ANALYSIS.md).
75
99
 
76
100
  > **`dead-exports` accuracy:** it is member-aware (a class/interface counts as
77
101
  > live when any contained member is referenced) and considers `CALLS`,
@@ -79,29 +103,50 @@ JSON out.
79
103
  > and `READS` (value-identifier) edges. On this repository it reports exactly the
80
104
  > two genuinely-unused type aliases — no false positives.
81
105
 
82
- ### The optimization agent
106
+ ### Web visualisation
83
107
 
84
- The end goal: an agent that uses the graph to find and apply optimizations,
85
- verifying each one before keeping it.
108
+ Serve the database as an interactive graph pan/zoom, kind filters, symbol
109
+ search, per-node edge listing (see
110
+ [contribs/web_visualisation](contribs/web_visualisation)):
86
111
 
87
112
  ```bash
88
- cp .env-sample .env # pick a provider block, set key + model
89
- npm run dev -- optimize --db ./outputs/graph.kuzu
90
- npm run dev -- optimize "Inline the single-use helper X" --db ./outputs/graph.kuzu --model gpt-5.1
113
+ npm run web # reads ./outputs/graph.kuzu, serves http://localhost:4173
114
+ npm run web -- --db ./outputs/graph.kuzu --port 8080
91
115
  ```
92
116
 
93
- The LLM layer sits on the **OpenAI-compatible chat-completions API**, so any
94
- provider exposing that surface works — OpenAI, OpenRouter, Ollama, LM Studio,
95
- vLLM — configured entirely through `OPENAI_API_KEY`, `OPENAI_BASE_URL`, and
96
- `OPENAI_MODEL` (see [.env-sample](.env-sample)).
117
+ ### The optimization agent
97
118
 
98
- The agent runs a tool-calling loop. Its tools are the read-only `GraphQuery`
99
- methods plus `read_file`; it gathers context, confirms blast radius, then calls
100
- `propose_optimization`. The harness **applies the edit, runs `tsc --noEmit`,
101
- and keeps it only if type-checking passes** — otherwise it reverts and hands
102
- the compiler errors back for another attempt. Edits are unique-match
103
- find/replace with in-memory backups; run on a clean git tree so you can review
104
- (and `git checkout`) what it kept.
119
+ The end goal: an agent that uses the graph to find and apply optimizations,
120
+ verifying each one before keeping it. It ships as a [Claude Code](https://claude.com/claude-code)
121
+ slash command, `/code-graph-optimize`, defined in
122
+ [`dotclaude_folder/commands/code-graph-optimize.md`](dotclaude_folder/commands/code-graph-optimize.md)
123
+ — so the agent runtime is your Claude Code subscription, with no API key or
124
+ provider configuration to set up.
125
+
126
+ ```text
127
+ /code-graph-optimize
128
+ /code-graph-optimize Inline the single-use helper X
129
+ ```
130
+
131
+ With no argument the command runs its default mission: find one genuinely dead
132
+ exported symbol, confirm it has zero inbound references, and remove it safely.
133
+
134
+ The command drives a find → confirm → edit → verify loop. It queries the graph
135
+ through this CLI (`dead-exports`, `references`, `who-calls`, `blast-radius`) to
136
+ gather context and confirm blast radius, makes exactly one edit, then runs
137
+ [`ts-knowledge-graph verify`](docs/commands/verify.md) — the type-check **and**
138
+ the test suite as a single gate. **If verify passes the edit stands; if it fails
139
+ the edit is reverted with `git restore`** and the change is abandoned or retried.
140
+ On a project with no test script verify degrades to type-check-only and the agent
141
+ says so, rather than implying the change was behaviourally verified. Run it on a
142
+ clean git tree so you can review (and `git checkout`) what it kept.
143
+
144
+ A companion command, `/code-graph-interview`
145
+ ([`code-graph-interview.md`](dotclaude_folder/commands/code-graph-interview.md)),
146
+ is read-only: it interviews you to scope a measurable optimization target and
147
+ grounds each candidate in the graph, producing tasks you can then hand to
148
+ `/code-graph-optimize`. Both commands, plus the `code-graph-query` skill, live
149
+ under [`dotclaude_folder/`](dotclaude_folder) and are mirrored into `.claude/`.
105
150
 
106
151
  ## Architecture
107
152
 
@@ -109,25 +154,26 @@ find/replace with in-memory backups; run on a clean git tree so you can review
109
154
  src/
110
155
  schema/ Zod schemas for nodes and edges (the wire format)
111
156
  extract/
112
- project-loader.ts load a ts-morph Project from tsconfig
113
- node-id.ts deterministic, position-stable node ids
114
- structural-extractor.ts modules, declarations, imports, containment
115
- semantic-extractor.ts heritage, CALLS, INSTANTIATES, type edges
116
- graph-builder.ts orchestrates extraction, dedupes by id
157
+ project_loader.ts load a ts-morph Project from tsconfig
158
+ node_id.ts deterministic, position-stable node ids
159
+ structural_extractor.ts modules, declarations, imports, containment
160
+ semantic_extractor.ts heritage, CALLS, INSTANTIATES, type edges
161
+ graph_builder.ts orchestrates extraction, dedupes by id
117
162
  store/
118
- jsonl-store.ts serialize the graph to JSONL
119
- jsonl-reader.ts read + Zod-validate the JSONL back in
120
- kuzu-store.ts load the graph into embedded Kùzu, run Cypher
163
+ jsonl_store.ts serialize the graph to JSONL
164
+ jsonl_reader.ts read + Zod-validate the JSONL back in
165
+ kuzu_store.ts load the graph into embedded Kùzu, run Cypher
121
166
  query/
122
- graph-query.ts the agent's query tools (who-calls, blast-radius…)
123
- agent/
124
- agent-tools.ts graph queries + read_file + propose_optimization, as LLM tools
125
- code-editor.ts unique-match find/replace with in-memory backup + revert
126
- verifier.ts runs `tsc --noEmit`, returns pass/fail + output
127
- optimizer-agent.ts the LLM tool-calling loop (propose → verify → keep/revert)
128
- cli.ts extract / load / query / optimize commands
167
+ graph_query.ts the agent's query tools (who-calls, blast-radius…)
168
+ commands/ one file per CLI command (extract, load, query, web, install)
169
+ cli.ts wires the commands into the ts-knowledge-graph CLI
129
170
  ```
130
171
 
172
+ The optimization agent is not part of this `src/` tree — it is the
173
+ `/code-graph-optimize` Claude Code command under
174
+ [`dotclaude_folder/commands/`](dotclaude_folder/commands), which drives the same
175
+ queries through the CLI.
176
+
131
177
  Node ids are derived purely from the declaration (`kind:relPath#name@line`), so
132
178
  any extractor computes the same id for the same symbol without a shared
133
179
  registry — that is what lets the semantic layer link a call site to the exact
@@ -144,10 +190,25 @@ declaration node the structural layer emitted.
144
190
  contained member is referenced.
145
191
  - [x] **Value-reference (`READS`) edges** — value-identifier usage, so exported
146
192
  `const`s (e.g. schemas) are no longer false-positive dead exports.
147
- - [x] **Optimization agent** — an LLM tool-calling loop (OpenAI-compatible API,
148
- provider-agnostic) that proposes edits and keeps them only if `tsc --noEmit`
149
- passes (otherwise reverts).
150
- - [ ] **Test verification** — run the test suite alongside `tsc` in the verify
151
- step, so behavior-changing edits are caught, not just type errors.
193
+ - [x] **Runtime enrichment** — the [`enrich`](docs/commands/enrich.md) command
194
+ ingests a V8 CPU profile and attaches measured self time / sample count onto
195
+ nodes as `metadata.runtime`, joining frames to nodes by enclosing range.
196
+ - [x] **Hotspot / leverage ranking** — the [`hotspots`](docs/commands/hotspots.md)
197
+ command ranks nodes by optimization value (runtime self-time, fan-in,
198
+ call-count, or transitive blast radius), defaulting to measured self time when
199
+ enriched and degrading gracefully to static fan-in when not.
200
+ - [x] **Optimization agent** — the `/code-graph-optimize` Claude Code command,
201
+ which proposes one edit and keeps it only if [`verify`](docs/commands/verify.md)
202
+ (type-check **and** tests) passes (otherwise reverts with `git restore`).
203
+ - [x] **Test verification** — the [`verify`](docs/commands/verify.md) command runs
204
+ the project's `typecheck` and `test` scripts as one keep/revert gate, so
205
+ behavior-changing edits are caught, not just type errors. A project with no test
206
+ script degrades to type-check-only, reported honestly (`behaviorVerified: false`).
207
+ - [x] **Benchmark verification** — the [`benchmark`](docs/commands/benchmark.md)
208
+ command measures a target node's runtime metric (profile → enrich → cost) over
209
+ N runs and reports the median + spread, with an advisory baseline→after delta —
210
+ so an optimization is reported by its *measured* impact (e.g. −57% self-time on
211
+ `titleCase`) rather than a guess. Advisory by design, distinct from the hard
212
+ `verify` gate.
152
213
  - [ ] **Vector index** — embed per-node summaries for hybrid graph + semantic
153
214
  retrieval, so the agent can find candidates by meaning, not just by name.
@@ -0,0 +1,83 @@
1
+ # Web visualisation
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 ../../outputs/graph/*.jsonl into web/data/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/web_visualisation
26
+ npm run build
27
+ npm run open
28
+ ```
29
+
30
+ `scripts/build-data.ts` reads `../../outputs/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
+ `outputs/graph/nodes.jsonl` + `outputs/graph/edges.jsonl` onto the page.
36
+
37
+ **C. Serve the repo root** — the page then auto-fetches
38
+ `../../../outputs/graph/*.jsonl`:
39
+
40
+ ```bash
41
+ # from the repo root
42
+ npx serve # or: python3 -m http.server
43
+ # open http://localhost:3000/contribs/web_visualisation/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,219 @@
1
+ * { box-sizing: border-box; }
2
+
3
+ html, body {
4
+ margin: 0;
5
+ height: 100%;
6
+ font: 13px/1.45 -apple-system, 'Segoe UI', Roboto, sans-serif;
7
+ background: #0f172a;
8
+ color: #cbd5e1;
9
+ }
10
+
11
+ body { display: flex; }
12
+
13
+ #sidebar {
14
+ width: 300px;
15
+ flex: none;
16
+ height: 100%;
17
+ overflow-y: auto;
18
+ padding: 14px;
19
+ background: #111c33;
20
+ border-right: 1px solid #1e293b;
21
+ }
22
+
23
+ #sidebar h1 { font-size: 15px; margin: 0 0 4px; color: #f1f5f9; }
24
+ #sidebar h2 { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: #64748b; margin: 16px 0 6px; }
25
+ #sidebar section { margin-top: 8px; }
26
+
27
+ #sidebar .foldable {
28
+ display: flex;
29
+ align-items: center;
30
+ gap: 7px;
31
+ line-height: 1;
32
+ cursor: pointer;
33
+ user-select: none;
34
+ }
35
+ #sidebar .foldable:hover { color: #94a3b8; }
36
+ #sidebar .foldable::before {
37
+ content: '';
38
+ flex: none;
39
+ width: 0;
40
+ height: 0;
41
+ border-left: 5px solid currentColor;
42
+ border-top: 4px solid transparent;
43
+ border-bottom: 4px solid transparent;
44
+ transition: transform .12s ease;
45
+ }
46
+ #sidebar .foldable:not(.collapsed)::before { transform: rotate(90deg); }
47
+ #sidebar .foldable.collapsed ~ * { display: none; }
48
+
49
+ #status { font-size: 11px; color: #64748b; margin-bottom: 10px; }
50
+
51
+ #search {
52
+ width: 100%;
53
+ padding: 6px 8px;
54
+ border: 1px solid #334155;
55
+ border-radius: 6px;
56
+ background: #0f172a;
57
+ color: #e2e8f0;
58
+ }
59
+ #search:focus { outline: none; border-color: #4f8cff; }
60
+
61
+ #search-results { margin-top: 4px; }
62
+ #search-results .hit {
63
+ padding: 3px 6px;
64
+ border-radius: 4px;
65
+ cursor: pointer;
66
+ font-size: 12px;
67
+ white-space: nowrap;
68
+ overflow: hidden;
69
+ text-overflow: ellipsis;
70
+ }
71
+ #search-results .hit:hover { background: #1e293b; }
72
+ #search-results .hit .loc { color: #64748b; font-size: 10px; }
73
+
74
+ .legend label {
75
+ display: flex;
76
+ align-items: center;
77
+ gap: 6px;
78
+ padding: 1px 0;
79
+ cursor: pointer;
80
+ user-select: none;
81
+ }
82
+ .legend .swatch {
83
+ width: 10px;
84
+ height: 10px;
85
+ border-radius: 3px;
86
+ flex: none;
87
+ }
88
+ .legend .count { margin-left: auto; color: #64748b; font-size: 11px; }
89
+ .legend .help-badge {
90
+ flex: none;
91
+ width: 13px;
92
+ height: 13px;
93
+ border-radius: 50%;
94
+ border: 1px solid #475569;
95
+ color: #94a3b8;
96
+ font-size: 9px;
97
+ font-weight: 600;
98
+ line-height: 11px;
99
+ text-align: center;
100
+ user-select: none;
101
+ }
102
+ .legend .help-badge:hover,
103
+ .legend .help-badge:focus {
104
+ background: #334155;
105
+ color: #e2e8f0;
106
+ border-color: #64748b;
107
+ outline: none;
108
+ }
109
+ .legend label.master {
110
+ margin-bottom: 4px;
111
+ padding-bottom: 4px;
112
+ border-bottom: 1px solid #1e293b;
113
+ color: #94a3b8;
114
+ }
115
+ .legend .swatch.spacer { background: transparent; }
116
+
117
+ .kind-tooltip {
118
+ position: fixed;
119
+ z-index: 1000;
120
+ max-width: 260px;
121
+ padding: 6px 9px;
122
+ border-radius: 6px;
123
+ background: #0b1424;
124
+ border: 1px solid #334155;
125
+ color: #e2e8f0;
126
+ font-size: 11px;
127
+ line-height: 1.4;
128
+ box-shadow: 0 4px 14px rgba(0, 0, 0, .45);
129
+ pointer-events: none;
130
+ }
131
+ .kind-tooltip[hidden] { display: none; }
132
+
133
+ .row { display: flex; align-items: center; gap: 6px; margin: 4px 0; }
134
+
135
+ select, button {
136
+ background: #1e293b;
137
+ color: #e2e8f0;
138
+ border: 1px solid #334155;
139
+ border-radius: 6px;
140
+ padding: 4px 8px;
141
+ font: inherit;
142
+ cursor: pointer;
143
+ }
144
+ button:hover { background: #334155; }
145
+ select { flex: 1; }
146
+
147
+ #details-body { font-size: 12px; }
148
+ #details-body .id { color: #64748b; font-size: 10px; word-break: break-all; }
149
+ #details-body .kind-tag {
150
+ display: inline-block;
151
+ padding: 1px 6px;
152
+ border-radius: 4px;
153
+ font-size: 10px;
154
+ color: #0f172a;
155
+ font-weight: 600;
156
+ }
157
+ #details-body h3 { font-size: 11px; color: #64748b; margin: 10px 0 3px; }
158
+ #details-body .edge-row { padding: 1px 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
159
+ #details-body .edge-kind { color: #64748b; font-size: 10px; }
160
+ #details-body .edge-count { color: #f59e0b; font-size: 10px; font-variant-numeric: tabular-nums; }
161
+ #details-body a { color: #7db2ff; cursor: pointer; text-decoration: none; }
162
+ #details-body a:hover { text-decoration: underline; }
163
+ #details-body a.file-link { text-decoration: underline; word-break: break-all; }
164
+ #details-body a.file-link::after { content: '↗'; margin-left: 3px; font-size: 9px; opacity: 0.7; }
165
+
166
+ #runtime.empty .heat-legend,
167
+ #runtime.empty .heat-note,
168
+ #runtime.empty .hotspots-title,
169
+ #runtime.empty #hotspots,
170
+ #runtime.empty label.row { display: none; }
171
+
172
+ #coverage { font-size: 11px; color: #94a3b8; margin: 2px 0 6px; }
173
+
174
+ #runtime label.row { font-size: 12px; }
175
+
176
+ .heat-legend { display: flex; align-items: center; gap: 6px; margin: 8px 0 4px; }
177
+ .heat-bar { flex: 1; height: 10px; border-radius: 3px; background: linear-gradient(90deg, #64748b, #fde047, #dc2626); }
178
+ .heat-legend .heat-min, .heat-legend .heat-max { font-size: 10px; color: #64748b; }
179
+
180
+ .heat-note { display: flex; align-items: center; gap: 6px; font-size: 10px; color: #64748b; }
181
+ .heat-swatch { width: 10px; height: 10px; border-radius: 3px; flex: none; display: inline-block; }
182
+ .heat-swatch.unmeasured { background: #243044; border: 1px dashed #475569; }
183
+
184
+ .hotspots-title { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: #64748b; margin: 12px 0 4px; }
185
+ #hotspots .hotspot {
186
+ display: flex;
187
+ align-items: center;
188
+ gap: 6px;
189
+ padding: 2px 4px;
190
+ border-radius: 4px;
191
+ cursor: pointer;
192
+ font-size: 12px;
193
+ }
194
+ #hotspots .hotspot:hover { background: #1e293b; }
195
+ .hotspot-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
196
+ .hotspot-ms { margin-left: auto; flex: none; color: #f59e0b; font-size: 11px; font-variant-numeric: tabular-nums; }
197
+
198
+ #details-body .runtime-block { margin-top: 8px; }
199
+ #details-body .runtime-block .metric { display: flex; justify-content: space-between; gap: 8px; padding: 1px 0; }
200
+ #details-body .runtime-block .metric span { color: #64748b; }
201
+
202
+ #cy { flex: 1; height: 100%; }
203
+
204
+ #dropzone {
205
+ position: fixed;
206
+ inset: 0;
207
+ display: flex;
208
+ align-items: center;
209
+ justify-content: center;
210
+ font-size: 18px;
211
+ color: #e2e8f0;
212
+ background: rgba(15, 23, 42, .85);
213
+ border: 3px dashed #4f8cff;
214
+ pointer-events: none;
215
+ opacity: 0;
216
+ transition: opacity .15s;
217
+ }
218
+ #dropzone.active { opacity: 1; }
219
+ #dropzone code { color: #7db2ff; }
@@ -0,0 +1,3 @@
1
+ *
2
+ !.gitignore
3
+ !kind_descriptions.js
@@ -0,0 +1,38 @@
1
+ // @ts-nocheck
2
+ // Generated from src/schema by scripts/build-data.ts. Do not edit by hand.
3
+ window.KIND_DESCRIPTIONS = {
4
+ "nodes": {
5
+ "Module": "A source file in the codebase.",
6
+ "Class": "A class declaration.",
7
+ "Interface": "An interface declaration.",
8
+ "TypeAlias": "A type alias declaration.",
9
+ "Enum": "An enum declaration.",
10
+ "Function": "A standalone, module-level function.",
11
+ "Method": "A function that belongs to a class or interface.",
12
+ "Property": "A field declared on a class or interface.",
13
+ "Parameter": "A parameter of a function or method.",
14
+ "Variable": "A module- or block-level variable binding.",
15
+ "ExternalModule": "An imported third-party or Node.js module, recorded as one opaque node per import specifier.",
16
+ "ConfigFlag": "An environment-variable configuration flag, detected from process.env reads.",
17
+ "ExternalAPI": "An outbound HTTP host called through fetch(), with one node per host.",
18
+ "Endpoint": "An HTTP route registered by the app, such as app.get(\"/users\", handler)."
19
+ },
20
+ "edges": {
21
+ "CONTAINS": "Structural nesting: the source declares or encloses the target (a module contains a class, which contains a method).",
22
+ "IMPORTS": "The source module imports the target.",
23
+ "EXPORTS": "The source module exports the target symbol.",
24
+ "EXTENDS": "The source class or interface extends the target (inheritance).",
25
+ "IMPLEMENTS": "The source class implements the target interface.",
26
+ "USES_TYPE": "The source references the target in a type position.",
27
+ "RETURNS": "The target type appears in the source function or method return type.",
28
+ "PARAM_TYPE": "The target type appears in one of the source parameter types.",
29
+ "CALLS": "The source function or method calls the target.",
30
+ "INSTANTIATES": "The source constructs the target class with new.",
31
+ "OVERRIDES": "The source method overrides the base-class member it replaces.",
32
+ "READS": "The source reads the value of the target variable or property.",
33
+ "WRITES": "The source assigns to the target variable or property.",
34
+ "READS_CONFIG": "The source reads the target configuration flag (an environment variable).",
35
+ "CALLS_EXTERNAL": "The source makes an outbound HTTP call to the target external API.",
36
+ "HANDLES": "Links an HTTP endpoint to the function that handles it (route to handler)."
37
+ }
38
+ };
@@ -0,0 +1,74 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>open_ts_optim_ai — knowledge graph</title>
7
+ <link rel="stylesheet" href="./css/style.css">
8
+ </head>
9
+ <body>
10
+ <aside id="sidebar">
11
+ <h1>Knowledge graph</h1>
12
+ <div id="status">no data loaded</div>
13
+
14
+ <input id="search" type="search" placeholder="Search symbol or file… (Enter)" autocomplete="off">
15
+ <div id="search-results"></div>
16
+
17
+ <section id="runtime" class="empty">
18
+ <h2 class="foldable" data-fold="runtime">Runtime</h2>
19
+ <div id="coverage">no runtime data</div>
20
+ <label class="row"><input id="runtime-heat" type="checkbox"> heat map (size + colour by self-time)</label>
21
+ <label class="row"><input id="only-measured" type="checkbox"> only measured nodes</label>
22
+ <div class="heat-legend">
23
+ <span class="heat-min">cold</span>
24
+ <span class="heat-bar"></span>
25
+ <span class="heat-max">hot</span>
26
+ </div>
27
+ <div class="heat-note"><span class="heat-swatch unmeasured"></span> not measured</div>
28
+ <h3 class="hotspots-title foldable" data-fold="hotspots">Hotspots</h3>
29
+ <div id="hotspots"></div>
30
+ </section>
31
+
32
+ <section>
33
+ <h2 class="foldable" data-fold="node-kinds">Node kinds</h2>
34
+ <div id="node-kinds" class="legend"></div>
35
+ </section>
36
+
37
+ <section>
38
+ <h2 class="foldable" data-fold="edge-kinds">Edge kinds</h2>
39
+ <div id="edge-kinds" class="legend"></div>
40
+ </section>
41
+
42
+ <section>
43
+ <h2 class="foldable" data-fold="options">Options</h2>
44
+ <label class="row"><input id="hide-isolated" type="checkbox"> hide isolated nodes</label>
45
+ <div class="row">
46
+ <select id="layout-select">
47
+ <option value="cose">cose (force)</option>
48
+ <option value="concentric">concentric (by degree)</option>
49
+ <option value="breadthfirst">breadthfirst</option>
50
+ <option value="circle">circle</option>
51
+ <option value="grid">grid</option>
52
+ </select>
53
+ <button id="relayout">layout</button>
54
+ </div>
55
+ </section>
56
+
57
+ <section id="details" class="empty">
58
+ <h2 class="foldable" data-fold="selection">Selection</h2>
59
+ <div id="details-body">click a node</div>
60
+ </section>
61
+ </aside>
62
+
63
+ <main id="cy"></main>
64
+
65
+ <div id="dropzone" class="hidden-overlay">
66
+ <div>drop <code>nodes.jsonl</code> + <code>edges.jsonl</code> here</div>
67
+ </div>
68
+
69
+ <script src="./data/graph_data.js"></script>
70
+ <script src="./data/kind_descriptions.js"></script>
71
+ <script src="https://unpkg.com/cytoscape@3/dist/cytoscape.min.js"></script>
72
+ <script src="./js/app.js"></script>
73
+ </body>
74
+ </html>