@tienne/gestalt 0.9.1 → 0.10.0

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 (149) hide show
  1. package/CLAUDE.md +75 -0
  2. package/dist/package.json +2 -1
  3. package/dist/skills/blast-radius/SKILL.md +134 -0
  4. package/dist/skills/build-graph/SKILL.md +100 -0
  5. package/dist/skills/diff-radius/SKILL.md +125 -0
  6. package/dist/skills/setup/SKILL.md +150 -0
  7. package/dist/src/cli/commands/graph-visualize.d.ts +11 -0
  8. package/dist/src/cli/commands/graph-visualize.d.ts.map +1 -0
  9. package/dist/src/cli/commands/graph-visualize.js +26 -0
  10. package/dist/src/cli/commands/graph-visualize.js.map +1 -0
  11. package/dist/src/cli/commands/init.d.ts +5 -0
  12. package/dist/src/cli/commands/init.d.ts.map +1 -0
  13. package/dist/src/cli/commands/init.js +79 -0
  14. package/dist/src/cli/commands/init.js.map +1 -0
  15. package/dist/src/cli/index.d.ts.map +1 -1
  16. package/dist/src/cli/index.js +23 -0
  17. package/dist/src/cli/index.js.map +1 -1
  18. package/dist/src/code-graph/blast-radius.d.ts +9 -0
  19. package/dist/src/code-graph/blast-radius.d.ts.map +1 -0
  20. package/dist/src/code-graph/blast-radius.js +139 -0
  21. package/dist/src/code-graph/blast-radius.js.map +1 -0
  22. package/dist/src/code-graph/engine.d.ts +46 -0
  23. package/dist/src/code-graph/engine.d.ts.map +1 -0
  24. package/dist/src/code-graph/engine.js +301 -0
  25. package/dist/src/code-graph/engine.js.map +1 -0
  26. package/dist/src/code-graph/git-hook.d.ts +22 -0
  27. package/dist/src/code-graph/git-hook.d.ts.map +1 -0
  28. package/dist/src/code-graph/git-hook.js +100 -0
  29. package/dist/src/code-graph/git-hook.js.map +1 -0
  30. package/dist/src/code-graph/index.d.ts +7 -0
  31. package/dist/src/code-graph/index.d.ts.map +1 -0
  32. package/dist/src/code-graph/index.js +6 -0
  33. package/dist/src/code-graph/index.js.map +1 -0
  34. package/dist/src/code-graph/plugins/go.d.ts +3 -0
  35. package/dist/src/code-graph/plugins/go.d.ts.map +1 -0
  36. package/dist/src/code-graph/plugins/go.js +126 -0
  37. package/dist/src/code-graph/plugins/go.js.map +1 -0
  38. package/dist/src/code-graph/plugins/index.d.ts +16 -0
  39. package/dist/src/code-graph/plugins/index.d.ts.map +1 -0
  40. package/dist/src/code-graph/plugins/index.js +36 -0
  41. package/dist/src/code-graph/plugins/index.js.map +1 -0
  42. package/dist/src/code-graph/plugins/java.d.ts +3 -0
  43. package/dist/src/code-graph/plugins/java.d.ts.map +1 -0
  44. package/dist/src/code-graph/plugins/java.js +108 -0
  45. package/dist/src/code-graph/plugins/java.js.map +1 -0
  46. package/dist/src/code-graph/plugins/kotlin.d.ts +3 -0
  47. package/dist/src/code-graph/plugins/kotlin.d.ts.map +1 -0
  48. package/dist/src/code-graph/plugins/kotlin.js +94 -0
  49. package/dist/src/code-graph/plugins/kotlin.js.map +1 -0
  50. package/dist/src/code-graph/plugins/objc.d.ts +3 -0
  51. package/dist/src/code-graph/plugins/objc.d.ts.map +1 -0
  52. package/dist/src/code-graph/plugins/objc.js +129 -0
  53. package/dist/src/code-graph/plugins/objc.js.map +1 -0
  54. package/dist/src/code-graph/plugins/python.d.ts +3 -0
  55. package/dist/src/code-graph/plugins/python.d.ts.map +1 -0
  56. package/dist/src/code-graph/plugins/python.js +116 -0
  57. package/dist/src/code-graph/plugins/python.js.map +1 -0
  58. package/dist/src/code-graph/plugins/rust.d.ts +3 -0
  59. package/dist/src/code-graph/plugins/rust.d.ts.map +1 -0
  60. package/dist/src/code-graph/plugins/rust.js +108 -0
  61. package/dist/src/code-graph/plugins/rust.js.map +1 -0
  62. package/dist/src/code-graph/plugins/swift.d.ts +3 -0
  63. package/dist/src/code-graph/plugins/swift.d.ts.map +1 -0
  64. package/dist/src/code-graph/plugins/swift.js +106 -0
  65. package/dist/src/code-graph/plugins/swift.js.map +1 -0
  66. package/dist/src/code-graph/plugins/typescript.d.ts +3 -0
  67. package/dist/src/code-graph/plugins/typescript.d.ts.map +1 -0
  68. package/dist/src/code-graph/plugins/typescript.js +209 -0
  69. package/dist/src/code-graph/plugins/typescript.js.map +1 -0
  70. package/dist/src/code-graph/storage.d.ts +19 -0
  71. package/dist/src/code-graph/storage.d.ts.map +1 -0
  72. package/dist/src/code-graph/storage.js +182 -0
  73. package/dist/src/code-graph/storage.js.map +1 -0
  74. package/dist/src/code-graph/types.d.ts +93 -0
  75. package/dist/src/code-graph/types.d.ts.map +1 -0
  76. package/dist/src/code-graph/types.js +17 -0
  77. package/dist/src/code-graph/types.js.map +1 -0
  78. package/dist/src/core/config.d.ts +1 -0
  79. package/dist/src/core/config.d.ts.map +1 -1
  80. package/dist/src/core/config.js +1 -1
  81. package/dist/src/core/config.js.map +1 -1
  82. package/dist/src/core/types.d.ts +1 -0
  83. package/dist/src/core/types.d.ts.map +1 -1
  84. package/dist/src/execute/passthrough-engine.d.ts +4 -1
  85. package/dist/src/execute/passthrough-engine.d.ts.map +1 -1
  86. package/dist/src/execute/passthrough-engine.js +54 -4
  87. package/dist/src/execute/passthrough-engine.js.map +1 -1
  88. package/dist/src/execute/prompts.d.ts +2 -2
  89. package/dist/src/execute/prompts.d.ts.map +1 -1
  90. package/dist/src/execute/prompts.js +7 -3
  91. package/dist/src/execute/prompts.js.map +1 -1
  92. package/dist/src/execute/repository.d.ts.map +1 -1
  93. package/dist/src/execute/repository.js +1 -0
  94. package/dist/src/execute/repository.js.map +1 -1
  95. package/dist/src/execute/session.d.ts +3 -1
  96. package/dist/src/execute/session.d.ts.map +1 -1
  97. package/dist/src/execute/session.js +3 -1
  98. package/dist/src/execute/session.js.map +1 -1
  99. package/dist/src/graph-viz/engine.d.ts +20 -0
  100. package/dist/src/graph-viz/engine.d.ts.map +1 -0
  101. package/dist/src/graph-viz/engine.js +81 -0
  102. package/dist/src/graph-viz/engine.js.map +1 -0
  103. package/dist/src/graph-viz/html-generator.d.ts +7 -0
  104. package/dist/src/graph-viz/html-generator.d.ts.map +1 -0
  105. package/dist/src/graph-viz/html-generator.js +699 -0
  106. package/dist/src/graph-viz/html-generator.js.map +1 -0
  107. package/dist/src/graph-viz/index.d.ts +6 -0
  108. package/dist/src/graph-viz/index.d.ts.map +1 -0
  109. package/dist/src/graph-viz/index.js +5 -0
  110. package/dist/src/graph-viz/index.js.map +1 -0
  111. package/dist/src/graph-viz/port-finder.d.ts +2 -0
  112. package/dist/src/graph-viz/port-finder.d.ts.map +1 -0
  113. package/dist/src/graph-viz/port-finder.js +26 -0
  114. package/dist/src/graph-viz/port-finder.js.map +1 -0
  115. package/dist/src/graph-viz/server.d.ts +26 -0
  116. package/dist/src/graph-viz/server.d.ts.map +1 -0
  117. package/dist/src/graph-viz/server.js +104 -0
  118. package/dist/src/graph-viz/server.js.map +1 -0
  119. package/dist/src/graph-viz/types.d.ts +11 -0
  120. package/dist/src/graph-viz/types.d.ts.map +1 -0
  121. package/dist/src/graph-viz/types.js +2 -0
  122. package/dist/src/graph-viz/types.js.map +1 -0
  123. package/dist/src/mcp/schemas.d.ts +57 -0
  124. package/dist/src/mcp/schemas.d.ts.map +1 -1
  125. package/dist/src/mcp/schemas.js +28 -0
  126. package/dist/src/mcp/schemas.js.map +1 -1
  127. package/dist/src/mcp/server.d.ts.map +1 -1
  128. package/dist/src/mcp/server.js +28 -1
  129. package/dist/src/mcp/server.js.map +1 -1
  130. package/dist/src/mcp/tools/code-graph-passthrough.d.ts +16 -0
  131. package/dist/src/mcp/tools/code-graph-passthrough.d.ts.map +1 -0
  132. package/dist/src/mcp/tools/code-graph-passthrough.js +65 -0
  133. package/dist/src/mcp/tools/code-graph-passthrough.js.map +1 -0
  134. package/dist/src/mcp/tools/execute-passthrough.d.ts.map +1 -1
  135. package/dist/src/mcp/tools/execute-passthrough.js +1 -1
  136. package/dist/src/mcp/tools/execute-passthrough.js.map +1 -1
  137. package/dist/src/mcp/tools/graph-visualize-passthrough.d.ts +13 -0
  138. package/dist/src/mcp/tools/graph-visualize-passthrough.d.ts.map +1 -0
  139. package/dist/src/mcp/tools/graph-visualize-passthrough.js +26 -0
  140. package/dist/src/mcp/tools/graph-visualize-passthrough.js.map +1 -0
  141. package/dist/src/mcp/tools/index.d.ts +1 -0
  142. package/dist/src/mcp/tools/index.d.ts.map +1 -1
  143. package/dist/src/mcp/tools/index.js +1 -0
  144. package/dist/src/mcp/tools/index.js.map +1 -1
  145. package/package.json +2 -1
  146. package/skills/blast-radius/SKILL.md +134 -0
  147. package/skills/build-graph/SKILL.md +100 -0
  148. package/skills/diff-radius/SKILL.md +125 -0
  149. package/skills/setup/SKILL.md +150 -0
@@ -0,0 +1,699 @@
1
+ import { NodeKind, EdgeKind } from '../code-graph/types.js';
2
+ /**
3
+ * Generates a self-contained interactive HTML visualization for a code knowledge graph.
4
+ * Uses D3.js v7 force-directed layout with dark theme UI.
5
+ */
6
+ export function generateVisualizationHtml(nodes, edges) {
7
+ const nodesJson = JSON.stringify(nodes);
8
+ const edgesJson = JSON.stringify(edges);
9
+ // NodeKind / EdgeKind enum values serialized for use in inline JS
10
+ const nodeKindFile = NodeKind.File;
11
+ const nodeKindFunction = NodeKind.Function;
12
+ const nodeKindClass = NodeKind.Class;
13
+ const edgeKindImport = EdgeKind.IMPORTS_FROM;
14
+ const edgeKindCall = EdgeKind.CALLS;
15
+ return `<!DOCTYPE html>
16
+ <html lang="en">
17
+ <head>
18
+ <meta charset="UTF-8" />
19
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
20
+ <title>Code Knowledge Graph</title>
21
+ <script src="https://d3js.org/d3.v7.min.js"></script>
22
+ <style>
23
+ *, *::before, *::after {
24
+ box-sizing: border-box;
25
+ margin: 0;
26
+ padding: 0;
27
+ }
28
+
29
+ body {
30
+ background: #0d1117;
31
+ color: #e6edf3;
32
+ font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
33
+ display: flex;
34
+ height: 100vh;
35
+ overflow: hidden;
36
+ }
37
+
38
+ #graph-container {
39
+ flex: 1;
40
+ position: relative;
41
+ overflow: hidden;
42
+ }
43
+
44
+ svg {
45
+ width: 100%;
46
+ height: 100%;
47
+ display: block;
48
+ }
49
+
50
+ /* ─── Sidebar ─────────────────────────────────────────────── */
51
+ #sidebar {
52
+ width: 280px;
53
+ background: #161b22;
54
+ border-left: 1px solid #30363d;
55
+ display: flex;
56
+ flex-direction: column;
57
+ transition: transform 0.2s ease;
58
+ }
59
+
60
+ #sidebar-header {
61
+ padding: 16px 20px;
62
+ border-bottom: 1px solid #30363d;
63
+ display: flex;
64
+ align-items: center;
65
+ gap: 10px;
66
+ }
67
+
68
+ #sidebar-header h2 {
69
+ font-size: 13px;
70
+ font-weight: 600;
71
+ color: #8b949e;
72
+ text-transform: uppercase;
73
+ letter-spacing: 0.05em;
74
+ }
75
+
76
+ #sidebar-content {
77
+ flex: 1;
78
+ padding: 20px;
79
+ overflow-y: auto;
80
+ }
81
+
82
+ #node-placeholder {
83
+ color: #484f58;
84
+ font-size: 13px;
85
+ text-align: center;
86
+ margin-top: 40px;
87
+ line-height: 1.6;
88
+ }
89
+
90
+ #node-details {
91
+ display: none;
92
+ }
93
+
94
+ .detail-label {
95
+ font-size: 10px;
96
+ font-weight: 600;
97
+ color: #8b949e;
98
+ text-transform: uppercase;
99
+ letter-spacing: 0.07em;
100
+ margin-bottom: 4px;
101
+ }
102
+
103
+ .detail-value {
104
+ font-size: 13px;
105
+ color: #e6edf3;
106
+ word-break: break-all;
107
+ margin-bottom: 16px;
108
+ padding: 8px 10px;
109
+ background: #0d1117;
110
+ border-radius: 6px;
111
+ border: 1px solid #30363d;
112
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
113
+ }
114
+
115
+ .detail-badge {
116
+ display: inline-block;
117
+ padding: 3px 8px;
118
+ border-radius: 999px;
119
+ font-size: 11px;
120
+ font-weight: 600;
121
+ margin-bottom: 16px;
122
+ }
123
+
124
+ .badge-file { background: #1f4068; color: #58a6ff; border: 1px solid #388bfd40; }
125
+ .badge-function { background: #1a3a1a; color: #3fb950; border: 1px solid #3fb95040; }
126
+ .badge-class { background: #3d2000; color: #f0883e; border: 1px solid #f0883e40; }
127
+ .badge-type { background: #2e1f5e; color: #bc8cff; border: 1px solid #bc8cff40; }
128
+
129
+ #connections-section h3 {
130
+ font-size: 11px;
131
+ font-weight: 600;
132
+ color: #8b949e;
133
+ text-transform: uppercase;
134
+ letter-spacing: 0.07em;
135
+ margin-bottom: 8px;
136
+ }
137
+
138
+ .connection-item {
139
+ font-size: 12px;
140
+ color: #c9d1d9;
141
+ padding: 5px 8px;
142
+ border-radius: 4px;
143
+ margin-bottom: 3px;
144
+ background: #21262d;
145
+ white-space: nowrap;
146
+ overflow: hidden;
147
+ text-overflow: ellipsis;
148
+ cursor: pointer;
149
+ border: 1px solid transparent;
150
+ transition: border-color 0.15s;
151
+ }
152
+
153
+ .connection-item:hover {
154
+ border-color: #388bfd;
155
+ }
156
+
157
+ .connection-item .conn-kind {
158
+ font-size: 10px;
159
+ color: #8b949e;
160
+ margin-right: 4px;
161
+ }
162
+
163
+ /* ─── Stats Bar ───────────────────────────────────────────── */
164
+ #stats-bar {
165
+ position: absolute;
166
+ top: 12px;
167
+ left: 12px;
168
+ display: flex;
169
+ gap: 8px;
170
+ pointer-events: none;
171
+ }
172
+
173
+ .stat-chip {
174
+ background: #161b22cc;
175
+ border: 1px solid #30363d;
176
+ border-radius: 6px;
177
+ padding: 5px 10px;
178
+ font-size: 11px;
179
+ color: #8b949e;
180
+ backdrop-filter: blur(4px);
181
+ }
182
+
183
+ .stat-chip span {
184
+ color: #e6edf3;
185
+ font-weight: 600;
186
+ }
187
+
188
+ /* ─── Legend ─────────────────────────────────────────────── */
189
+ #legend {
190
+ position: absolute;
191
+ bottom: 16px;
192
+ left: 12px;
193
+ background: #161b22cc;
194
+ border: 1px solid #30363d;
195
+ border-radius: 8px;
196
+ padding: 10px 14px;
197
+ pointer-events: none;
198
+ backdrop-filter: blur(4px);
199
+ }
200
+
201
+ .legend-title {
202
+ font-size: 10px;
203
+ font-weight: 600;
204
+ color: #8b949e;
205
+ text-transform: uppercase;
206
+ letter-spacing: 0.07em;
207
+ margin-bottom: 8px;
208
+ }
209
+
210
+ .legend-item {
211
+ display: flex;
212
+ align-items: center;
213
+ gap: 7px;
214
+ margin-bottom: 5px;
215
+ font-size: 11px;
216
+ color: #8b949e;
217
+ }
218
+
219
+ .legend-dot {
220
+ width: 10px;
221
+ height: 10px;
222
+ border-radius: 50%;
223
+ flex-shrink: 0;
224
+ }
225
+
226
+ .legend-line {
227
+ width: 22px;
228
+ height: 2px;
229
+ flex-shrink: 0;
230
+ border-radius: 1px;
231
+ }
232
+
233
+ .legend-dashed {
234
+ background: repeating-linear-gradient(
235
+ 90deg,
236
+ #6e7681 0, #6e7681 4px,
237
+ transparent 4px, transparent 8px
238
+ );
239
+ }
240
+
241
+ /* ─── D3 Graph Styles ────────────────────────────────────── */
242
+ .node circle {
243
+ cursor: pointer;
244
+ transition: filter 0.15s;
245
+ }
246
+
247
+ .node circle:hover {
248
+ filter: brightness(1.3);
249
+ }
250
+
251
+ .node text {
252
+ pointer-events: none;
253
+ user-select: none;
254
+ font-size: 10px;
255
+ fill: #8b949e;
256
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
257
+ }
258
+
259
+ .link {
260
+ pointer-events: none;
261
+ }
262
+
263
+ .link.dimmed {
264
+ opacity: 0.08;
265
+ }
266
+
267
+ .node.dimmed circle {
268
+ opacity: 0.12;
269
+ }
270
+
271
+ .node.dimmed text {
272
+ opacity: 0.08;
273
+ }
274
+
275
+ .node.highlighted circle {
276
+ filter: brightness(1.4) drop-shadow(0 0 6px currentColor);
277
+ }
278
+
279
+ .node.highlighted text {
280
+ fill: #e6edf3;
281
+ }
282
+
283
+ /* ─── Controls ───────────────────────────────────────────── */
284
+ #controls {
285
+ position: absolute;
286
+ top: 12px;
287
+ right: 12px;
288
+ display: flex;
289
+ flex-direction: column;
290
+ gap: 4px;
291
+ }
292
+
293
+ .ctrl-btn {
294
+ width: 32px;
295
+ height: 32px;
296
+ background: #161b22cc;
297
+ border: 1px solid #30363d;
298
+ border-radius: 6px;
299
+ color: #8b949e;
300
+ font-size: 16px;
301
+ cursor: pointer;
302
+ display: flex;
303
+ align-items: center;
304
+ justify-content: center;
305
+ transition: background 0.15s, color 0.15s;
306
+ backdrop-filter: blur(4px);
307
+ }
308
+
309
+ .ctrl-btn:hover {
310
+ background: #21262d;
311
+ color: #e6edf3;
312
+ }
313
+ </style>
314
+ </head>
315
+ <body>
316
+
317
+ <div id="graph-container">
318
+ <svg id="svg"></svg>
319
+
320
+ <div id="stats-bar">
321
+ <div class="stat-chip">Nodes: <span id="stat-nodes">0</span></div>
322
+ <div class="stat-chip">Edges: <span id="stat-edges">0</span></div>
323
+ </div>
324
+
325
+ <div id="controls">
326
+ <button class="ctrl-btn" id="btn-zoom-in" title="Zoom in">+</button>
327
+ <button class="ctrl-btn" id="btn-zoom-out" title="Zoom out">−</button>
328
+ <button class="ctrl-btn" id="btn-reset" title="Reset view">⌖</button>
329
+ </div>
330
+
331
+ <div id="legend">
332
+ <div class="legend-title">Legend</div>
333
+ <div class="legend-item"><div class="legend-dot" style="background:#388bfd;width:14px;height:14px;"></div> File</div>
334
+ <div class="legend-item"><div class="legend-dot" style="background:#3fb950;"></div> Function</div>
335
+ <div class="legend-item"><div class="legend-dot" style="background:#f0883e;"></div> Class / Type</div>
336
+ <div class="legend-item"><div class="legend-line legend-dashed"></div> Import</div>
337
+ <div class="legend-item"><div class="legend-line" style="background:#6e7681;"></div> Call / Other</div>
338
+ </div>
339
+ </div>
340
+
341
+ <div id="sidebar">
342
+ <div id="sidebar-header">
343
+ <h2>Node Details</h2>
344
+ </div>
345
+ <div id="sidebar-content">
346
+ <div id="node-placeholder">Click a node to<br/>inspect its details</div>
347
+ <div id="node-details"></div>
348
+ </div>
349
+ </div>
350
+
351
+ <script>
352
+ (function () {
353
+ 'use strict';
354
+
355
+ // ─── Embedded Data ─────────────────────────────────────────────
356
+ const RAW_NODES = ${nodesJson};
357
+ const RAW_EDGES = ${edgesJson};
358
+
359
+ const NODE_KIND_FILE = ${JSON.stringify(nodeKindFile)};
360
+ const NODE_KIND_FUNCTION = ${JSON.stringify(nodeKindFunction)};
361
+ const NODE_KIND_CLASS = ${JSON.stringify(nodeKindClass)};
362
+ const EDGE_KIND_IMPORT = ${JSON.stringify(edgeKindImport)};
363
+ const EDGE_KIND_CALL = ${JSON.stringify(edgeKindCall)};
364
+
365
+ // ─── Stats ─────────────────────────────────────────────────────
366
+ document.getElementById('stat-nodes').textContent = RAW_NODES.length;
367
+ document.getElementById('stat-edges').textContent = RAW_EDGES.length;
368
+
369
+ // ─── Helpers ───────────────────────────────────────────────────
370
+ function nodeColor(kind) {
371
+ if (kind === NODE_KIND_FILE) return '#388bfd';
372
+ if (kind === NODE_KIND_FUNCTION) return '#3fb950';
373
+ if (kind === NODE_KIND_CLASS) return '#f0883e';
374
+ return '#bc8cff'; // Type, etc.
375
+ }
376
+
377
+ function nodeRadius(kind) {
378
+ return kind === NODE_KIND_FILE ? 9 : 5;
379
+ }
380
+
381
+ function shortName(n) {
382
+ if (n.kind === NODE_KIND_FILE) {
383
+ const parts = n.filePath.split('/');
384
+ return parts[parts.length - 1] || n.name;
385
+ }
386
+ return n.name.length > 22 ? n.name.slice(0, 20) + '…' : n.name;
387
+ }
388
+
389
+ function badgeClass(kind) {
390
+ if (kind === NODE_KIND_FILE) return 'badge-file';
391
+ if (kind === NODE_KIND_FUNCTION) return 'badge-function';
392
+ if (kind === NODE_KIND_CLASS) return 'badge-class';
393
+ return 'badge-type';
394
+ }
395
+
396
+ // ─── D3 Setup ──────────────────────────────────────────────────
397
+ const width = document.getElementById('graph-container').clientWidth;
398
+ const height = document.getElementById('graph-container').clientHeight;
399
+
400
+ const svg = d3.select('#svg')
401
+ .attr('width', width)
402
+ .attr('height', height);
403
+
404
+ const defs = svg.append('defs');
405
+
406
+ // Arrow marker for directed edges (call edges)
407
+ defs.append('marker')
408
+ .attr('id', 'arrow')
409
+ .attr('viewBox', '0 -4 8 8')
410
+ .attr('refX', 12)
411
+ .attr('refY', 0)
412
+ .attr('markerWidth', 5)
413
+ .attr('markerHeight', 5)
414
+ .attr('orient', 'auto')
415
+ .append('path')
416
+ .attr('d', 'M0,-4L8,0L0,4')
417
+ .attr('fill', '#4d5566');
418
+
419
+ const zoomLayer = svg.append('g').attr('class', 'zoom-layer');
420
+
421
+ const zoom = d3.zoom()
422
+ .scaleExtent([0.1, 8])
423
+ .on('zoom', (event) => zoomLayer.attr('transform', event.transform));
424
+
425
+ svg.call(zoom);
426
+
427
+ // ─── Build graph data ──────────────────────────────────────────
428
+ const nodeMap = new Map(RAW_NODES.map(n => [n.id, n]));
429
+
430
+ // Filter edges to only those whose source and target exist
431
+ const validEdges = RAW_EDGES.filter(e =>
432
+ nodeMap.has(e.sourceId) && nodeMap.has(e.targetId)
433
+ );
434
+
435
+ const simNodes = RAW_NODES.map(n => ({ ...n }));
436
+ const simEdges = validEdges.map(e => ({
437
+ ...e,
438
+ source: e.sourceId,
439
+ target: e.targetId,
440
+ }));
441
+
442
+ // ─── Force Simulation ──────────────────────────────────────────
443
+ const simulation = d3.forceSimulation(simNodes)
444
+ .force('link', d3.forceLink(simEdges)
445
+ .id(d => d.id)
446
+ .distance(d => {
447
+ if (d.kind === EDGE_KIND_IMPORT) return 90;
448
+ return 60;
449
+ })
450
+ .strength(0.4)
451
+ )
452
+ .force('charge', d3.forceManyBody()
453
+ .strength(d => d.kind === NODE_KIND_FILE ? -280 : -120)
454
+ )
455
+ .force('center', d3.forceCenter(width / 2, height / 2))
456
+ .force('collision', d3.forceCollide().radius(d => nodeRadius(d.kind) + 6))
457
+ .alphaDecay(0.02);
458
+
459
+ // ─── Render Links ─────────────────────────────────────────────
460
+ const linkGroup = zoomLayer.append('g').attr('class', 'links');
461
+ const link = linkGroup.selectAll('line')
462
+ .data(simEdges)
463
+ .join('line')
464
+ .attr('class', 'link')
465
+ .attr('stroke', d => d.kind === EDGE_KIND_CALL ? '#4d5566' : '#3a3f4b')
466
+ .attr('stroke-width', d => d.kind === EDGE_KIND_CALL ? 1.2 : 0.8)
467
+ .attr('stroke-dasharray', d => d.kind === EDGE_KIND_IMPORT ? '4 3' : null)
468
+ .attr('stroke-opacity', d => d.kind === EDGE_KIND_CALL ? 0.7 : 0.45)
469
+ .attr('marker-end', d => d.kind === EDGE_KIND_CALL ? 'url(#arrow)' : null);
470
+
471
+ // ─── Render Nodes ─────────────────────────────────────────────
472
+ const nodeGroup = zoomLayer.append('g').attr('class', 'nodes');
473
+ const node = nodeGroup.selectAll('g')
474
+ .data(simNodes)
475
+ .join('g')
476
+ .attr('class', 'node')
477
+ .call(
478
+ d3.drag()
479
+ .on('start', (event, d) => {
480
+ if (!event.active) simulation.alphaTarget(0.3).restart();
481
+ d.fx = d.x;
482
+ d.fy = d.y;
483
+ })
484
+ .on('drag', (event, d) => {
485
+ d.fx = event.x;
486
+ d.fy = event.y;
487
+ })
488
+ .on('end', (event, d) => {
489
+ if (!event.active) simulation.alphaTarget(0);
490
+ d.fx = null;
491
+ d.fy = null;
492
+ })
493
+ )
494
+ .on('click', onNodeClick);
495
+
496
+ node.append('circle')
497
+ .attr('r', d => nodeRadius(d.kind))
498
+ .attr('fill', d => nodeColor(d.kind))
499
+ .attr('stroke', '#0d1117')
500
+ .attr('stroke-width', 1.5);
501
+
502
+ node.append('text')
503
+ .attr('dy', d => nodeRadius(d.kind) + 11)
504
+ .attr('text-anchor', 'middle')
505
+ .text(d => shortName(d));
506
+
507
+ // ─── Tick ─────────────────────────────────────────────────────
508
+ simulation.on('tick', () => {
509
+ link
510
+ .attr('x1', d => d.source.x)
511
+ .attr('y1', d => d.source.y)
512
+ .attr('x2', d => d.target.x)
513
+ .attr('y2', d => d.target.y);
514
+
515
+ node.attr('transform', d => \`translate(\${d.x},\${d.y})\`);
516
+ });
517
+
518
+ // ─── Click Interaction ────────────────────────────────────────
519
+ let selectedId = null;
520
+
521
+ function onNodeClick(event, d) {
522
+ event.stopPropagation();
523
+
524
+ if (selectedId === d.id) {
525
+ // Deselect
526
+ selectedId = null;
527
+ resetHighlight();
528
+ showPlaceholder();
529
+ return;
530
+ }
531
+
532
+ selectedId = d.id;
533
+ highlightNode(d);
534
+ showDetails(d);
535
+ }
536
+
537
+ svg.on('click', () => {
538
+ selectedId = null;
539
+ resetHighlight();
540
+ showPlaceholder();
541
+ });
542
+
543
+ function highlightNode(d) {
544
+ // Find directly connected node IDs
545
+ const connectedIds = new Set([d.id]);
546
+ simEdges.forEach(e => {
547
+ if (e.source.id === d.id || e.sourceId === d.id) connectedIds.add(e.target.id || e.targetId);
548
+ if (e.target.id === d.id || e.targetId === d.id) connectedIds.add(e.source.id || e.sourceId);
549
+ });
550
+
551
+ node
552
+ .classed('dimmed', n => !connectedIds.has(n.id))
553
+ .classed('highlighted', n => connectedIds.has(n.id));
554
+
555
+ link
556
+ .classed('dimmed', e => {
557
+ const sid = e.source.id || e.sourceId;
558
+ const tid = e.target.id || e.targetId;
559
+ return !(connectedIds.has(sid) && connectedIds.has(tid));
560
+ });
561
+ }
562
+
563
+ function resetHighlight() {
564
+ node.classed('dimmed', false).classed('highlighted', false);
565
+ link.classed('dimmed', false);
566
+ }
567
+
568
+ // ─── Sidebar: Details ─────────────────────────────────────────
569
+ function showPlaceholder() {
570
+ document.getElementById('node-placeholder').style.display = '';
571
+ document.getElementById('node-details').style.display = 'none';
572
+ }
573
+
574
+ function showDetails(d) {
575
+ document.getElementById('node-placeholder').style.display = 'none';
576
+
577
+ // Gather connections
578
+ const outgoing = [];
579
+ const incoming = [];
580
+ simEdges.forEach(e => {
581
+ const sid = e.source.id || e.sourceId;
582
+ const tid = e.target.id || e.targetId;
583
+ if (sid === d.id) {
584
+ const target = nodeMap.get(tid);
585
+ if (target) outgoing.push({ node: target, kind: e.kind });
586
+ }
587
+ if (tid === d.id) {
588
+ const source = nodeMap.get(sid);
589
+ if (source) incoming.push({ node: source, kind: e.kind });
590
+ }
591
+ });
592
+
593
+ const detailsEl = document.getElementById('node-details');
594
+ detailsEl.style.display = 'block';
595
+ detailsEl.innerHTML = \`
596
+ <div class="detail-label">Kind</div>
597
+ <div class="detail-badge \${badgeClass(d.kind)}">\${d.kind}</div>
598
+
599
+ <div class="detail-label">Name</div>
600
+ <div class="detail-value">\${escHtml(d.name)}</div>
601
+
602
+ <div class="detail-label">File</div>
603
+ <div class="detail-value">\${escHtml(d.filePath)}</div>
604
+
605
+ \${d.lineStart != null ? \`
606
+ <div class="detail-label">Lines</div>
607
+ <div class="detail-value">\${d.lineStart}\${d.lineEnd != null ? '–' + d.lineEnd : ''}</div>
608
+ \` : ''}
609
+
610
+ \${outgoing.length > 0 ? \`
611
+ <div id="connections-section">
612
+ <h3>Outgoing (\${outgoing.length})</h3>
613
+ \${outgoing.slice(0, 12).map(c => \`
614
+ <div class="connection-item" data-id="\${escAttr(c.node.id)}" title="\${escAttr(c.node.name)}">
615
+ <span class="conn-kind">\${c.kind}</span>\${escHtml(shortName(c.node))}
616
+ </div>
617
+ \`).join('')}
618
+ </div>
619
+ \` : ''}
620
+
621
+ \${incoming.length > 0 ? \`
622
+ <div id="connections-section" style="margin-top:12px;">
623
+ <h3>Incoming (\${incoming.length})</h3>
624
+ \${incoming.slice(0, 12).map(c => \`
625
+ <div class="connection-item" data-id="\${escAttr(c.node.id)}" title="\${escAttr(c.node.name)}">
626
+ <span class="conn-kind">\${c.kind}</span>\${escHtml(shortName(c.node))}
627
+ </div>
628
+ \`).join('')}
629
+ </div>
630
+ \` : ''}
631
+ \`;
632
+
633
+ // Click on connection item → jump to that node
634
+ detailsEl.querySelectorAll('.connection-item').forEach(item => {
635
+ item.addEventListener('click', () => {
636
+ const id = item.getAttribute('data-id');
637
+ const target = simNodes.find(n => n.id === id);
638
+ if (target) {
639
+ selectedId = target.id;
640
+ highlightNode(target);
641
+ showDetails(target);
642
+ panToNode(target);
643
+ }
644
+ });
645
+ });
646
+ }
647
+
648
+ function panToNode(d) {
649
+ const t = d3.zoomTransform(svg.node());
650
+ const cx = width / 2 - t.k * d.x;
651
+ const cy = height / 2 - t.k * d.y;
652
+ svg.transition().duration(400).call(
653
+ zoom.transform,
654
+ d3.zoomIdentity.translate(cx, cy).scale(t.k)
655
+ );
656
+ }
657
+
658
+ // ─── Controls ────────────────────────────────────────────────
659
+ document.getElementById('btn-zoom-in').addEventListener('click', () => {
660
+ svg.transition().duration(200).call(zoom.scaleBy, 1.4);
661
+ });
662
+ document.getElementById('btn-zoom-out').addEventListener('click', () => {
663
+ svg.transition().duration(200).call(zoom.scaleBy, 1 / 1.4);
664
+ });
665
+ document.getElementById('btn-reset').addEventListener('click', () => {
666
+ svg.transition().duration(400).call(
667
+ zoom.transform,
668
+ d3.zoomIdentity.translate(width / 2, height / 2).scale(1)
669
+ .translate(-width / 2, -height / 2)
670
+ );
671
+ });
672
+
673
+ // ─── Escape helpers ──────────────────────────────────────────
674
+ function escHtml(str) {
675
+ return String(str)
676
+ .replace(/&/g, '&amp;')
677
+ .replace(/</g, '&lt;')
678
+ .replace(/>/g, '&gt;')
679
+ .replace(/"/g, '&quot;');
680
+ }
681
+
682
+ function escAttr(str) {
683
+ return String(str).replace(/"/g, '&quot;');
684
+ }
685
+
686
+ // ─── Window resize ───────────────────────────────────────────
687
+ window.addEventListener('resize', () => {
688
+ const w = document.getElementById('graph-container').clientWidth;
689
+ const h = document.getElementById('graph-container').clientHeight;
690
+ svg.attr('width', w).attr('height', h);
691
+ simulation.force('center', d3.forceCenter(w / 2, h / 2)).alpha(0.1).restart();
692
+ });
693
+
694
+ })();
695
+ </script>
696
+ </body>
697
+ </html>`;
698
+ }
699
+ //# sourceMappingURL=html-generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-generator.js","sourceRoot":"","sources":["../../../src/graph-viz/html-generator.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAE5D;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CAAC,KAAsB,EAAE,KAAsB;IACtF,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAExC,kEAAkE;IAClE,MAAM,YAAY,GAAG,QAAQ,CAAC,IAAI,CAAC;IACnC,MAAM,gBAAgB,GAAG,QAAQ,CAAC,QAAQ,CAAC;IAC3C,MAAM,aAAa,GAAG,QAAQ,CAAC,KAAK,CAAC;IACrC,MAAM,cAAc,GAAG,QAAQ,CAAC,YAAY,CAAC;IAC7C,MAAM,YAAY,GAAG,QAAQ,CAAC,KAAK,CAAC;IAEpC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBAqVa,SAAS;sBACT,SAAS;;+BAEA,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC;+BAC5B,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC;+BAChC,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC;+BAC7B,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC;+BAC9B,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA8UnD,CAAC;AACT,CAAC"}
@@ -0,0 +1,6 @@
1
+ export type { GraphVisualizationResult, GraphVizServerOptions } from './types.js';
2
+ export * from './port-finder.js';
3
+ export * from './html-generator.js';
4
+ export * from './server.js';
5
+ export * from './engine.js';
6
+ //# sourceMappingURL=index.d.ts.map