mcp-vector-search 0.12.0__py3-none-any.whl → 0.12.2__py3-none-any.whl

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.

Potentially problematic release.


This version of mcp-vector-search might be problematic. Click here for more details.

Files changed (51) hide show
  1. mcp_vector_search/__init__.py +2 -2
  2. mcp_vector_search/cli/commands/index.py +15 -24
  3. mcp_vector_search/cli/commands/install.py +502 -523
  4. mcp_vector_search/cli/commands/install_old.py +696 -0
  5. mcp_vector_search/cli/commands/status.py +7 -5
  6. mcp_vector_search/cli/commands/uninstall.py +485 -0
  7. mcp_vector_search/cli/commands/visualize.py +417 -121
  8. mcp_vector_search/cli/didyoumean.py +10 -0
  9. mcp_vector_search/cli/main.py +39 -21
  10. mcp_vector_search/core/connection_pool.py +49 -11
  11. mcp_vector_search/core/database.py +7 -9
  12. mcp_vector_search/core/directory_index.py +26 -11
  13. mcp_vector_search/core/indexer.py +89 -29
  14. mcp_vector_search/core/models.py +4 -1
  15. mcp_vector_search/core/project.py +16 -5
  16. mcp_vector_search/parsers/base.py +54 -18
  17. mcp_vector_search/parsers/javascript.py +41 -20
  18. mcp_vector_search/parsers/python.py +19 -11
  19. mcp_vector_search/parsers/registry.py +3 -2
  20. mcp_vector_search/utils/gitignore.py +3 -1
  21. mcp_vector_search/visualization/favicon-v1-1024.png +0 -0
  22. mcp_vector_search/visualization/favicon-v1-128.png +0 -0
  23. mcp_vector_search/visualization/favicon-v1-16.png +0 -0
  24. mcp_vector_search/visualization/favicon-v1-256.png +0 -0
  25. mcp_vector_search/visualization/favicon-v1-32.png +0 -0
  26. mcp_vector_search/visualization/favicon-v1-512.png +0 -0
  27. mcp_vector_search/visualization/favicon-v1-64.png +0 -0
  28. mcp_vector_search/visualization/favicon-v1.ico +0 -0
  29. mcp_vector_search/visualization/favicon-v2-1024.png +0 -0
  30. mcp_vector_search/visualization/favicon-v2-128.png +0 -0
  31. mcp_vector_search/visualization/favicon-v2-16.png +0 -0
  32. mcp_vector_search/visualization/favicon-v2-256.png +0 -0
  33. mcp_vector_search/visualization/favicon-v2-32.png +0 -0
  34. mcp_vector_search/visualization/favicon-v2-512.png +0 -0
  35. mcp_vector_search/visualization/favicon-v2-64.png +0 -0
  36. mcp_vector_search/visualization/favicon-v2.ico +0 -0
  37. mcp_vector_search/visualization/favicon-v3-1024.png +0 -0
  38. mcp_vector_search/visualization/favicon-v3-128.png +0 -0
  39. mcp_vector_search/visualization/favicon-v3-16.png +0 -0
  40. mcp_vector_search/visualization/favicon-v3-256.png +0 -0
  41. mcp_vector_search/visualization/favicon-v3-32.png +0 -0
  42. mcp_vector_search/visualization/favicon-v3-512.png +0 -0
  43. mcp_vector_search/visualization/favicon-v3-64.png +0 -0
  44. mcp_vector_search/visualization/favicon-v3.ico +0 -0
  45. mcp_vector_search/visualization/index.html +522 -172
  46. {mcp_vector_search-0.12.0.dist-info → mcp_vector_search-0.12.2.dist-info}/METADATA +148 -25
  47. mcp_vector_search-0.12.2.dist-info/RECORD +93 -0
  48. mcp_vector_search-0.12.0.dist-info/RECORD +0 -67
  49. {mcp_vector_search-0.12.0.dist-info → mcp_vector_search-0.12.2.dist-info}/WHEEL +0 -0
  50. {mcp_vector_search-0.12.0.dist-info → mcp_vector_search-0.12.2.dist-info}/entry_points.txt +0 -0
  51. {mcp_vector_search-0.12.0.dist-info → mcp_vector_search-0.12.2.dist-info}/licenses/LICENSE +0 -0
@@ -2,10 +2,15 @@
2
2
  <html>
3
3
  <head>
4
4
  <meta charset="utf-8">
5
- <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
6
- <meta http-equiv="Pragma" content="no-cache">
7
- <meta http-equiv="Expires" content="0">
8
5
  <title>Code Chunk Relationship Graph</title>
6
+
7
+ <!-- Favicon -->
8
+ <link rel="icon" type="image/x-icon" href="favicon-v1.ico">
9
+ <link rel="icon" type="image/png" sizes="16x16" href="favicon-v1-16.png">
10
+ <link rel="icon" type="image/png" sizes="32x32" href="favicon-v1-32.png">
11
+ <link rel="icon" type="image/png" sizes="64x64" href="favicon-v1-64.png">
12
+ <link rel="apple-touch-icon" sizes="180x180" href="favicon-v1-512.png">
13
+
9
14
  <script src="https://d3js.org/d3.v7.min.js"></script>
10
15
  <style>
11
16
  body {
@@ -80,26 +85,43 @@
80
85
  cursor: pointer;
81
86
  stroke: #c9d1d9;
82
87
  stroke-width: 1.5px;
88
+ transition: all 0.2s ease;
89
+ }
90
+
91
+ .node circle:hover,
92
+ .node rect:hover {
93
+ stroke-width: 3px !important;
94
+ filter: brightness(1.2);
95
+ cursor: pointer;
83
96
  }
84
97
 
85
- .node.directory circle { fill: #ffa657; }
86
- .node.file circle { fill: #58a6ff; }
87
98
  .node.module circle { fill: #238636; }
88
99
  .node.class circle { fill: #1f6feb; }
89
100
  .node.function circle { fill: #d29922; }
90
101
  .node.method circle { fill: #8957e5; }
91
- .node.imports circle { fill: #6e7681; }
92
- .node.text circle { fill: #6e7681; }
93
102
  .node.code circle { fill: #6e7681; }
103
+ .node.file circle {
104
+ fill: none;
105
+ stroke: #58a6ff;
106
+ stroke-width: 2px;
107
+ stroke-dasharray: 5,3;
108
+ opacity: 0.6;
109
+ }
110
+ .node.directory circle {
111
+ fill: none;
112
+ stroke: #79c0ff;
113
+ stroke-width: 2px;
114
+ stroke-dasharray: 3,3;
115
+ opacity: 0.5;
116
+ }
94
117
  .node.subproject circle { fill: #da3633; stroke-width: 3px; }
95
118
 
96
- .node-icon {
97
- font-size: 35px !important;
98
- text-anchor: middle;
99
- pointer-events: none;
100
- user-select: none;
101
- fill: #0d1117;
102
- font-weight: bold;
119
+ /* Non-code document nodes - squares */
120
+ .node.docstring rect { fill: #8b949e; }
121
+ .node.comment rect { fill: #6e7681; }
122
+ .node rect {
123
+ stroke: #c9d1d9;
124
+ stroke-width: 1.5px;
103
125
  }
104
126
 
105
127
  .node text {
@@ -110,10 +132,19 @@
110
132
  user-select: none;
111
133
  }
112
134
 
135
+ .node-icon {
136
+ font-size: 35px !important;
137
+ text-anchor: middle;
138
+ pointer-events: none;
139
+ user-select: none;
140
+ fill: #0d1117;
141
+ font-weight: bold;
142
+ }
143
+
113
144
  .link {
114
- stroke: #58a6ff;
115
- stroke-opacity: 0.4;
116
- stroke-width: 2px;
145
+ stroke: #30363d;
146
+ stroke-opacity: 0.6;
147
+ stroke-width: 1.5px;
117
148
  }
118
149
 
119
150
  .link.dependency {
@@ -144,73 +175,141 @@
144
175
  color: #8b949e;
145
176
  }
146
177
 
147
- #code-viewer {
148
- position: absolute;
149
- top: 20px;
150
- right: 20px;
151
- width: 500px;
152
- max-height: 80vh;
153
- background: rgba(13, 17, 23, 0.95);
154
- border: 1px solid #30363d;
155
- border-radius: 6px;
156
- padding: 16px;
178
+ #content-pane {
179
+ position: fixed;
180
+ top: 0;
181
+ right: 0;
182
+ width: 600px;
183
+ height: 100vh;
184
+ background: rgba(13, 17, 23, 0.98);
185
+ border-left: 1px solid #30363d;
157
186
  overflow-y: auto;
158
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
159
- display: none;
187
+ box-shadow: -4px 0 24px rgba(0, 0, 0, 0.5);
188
+ transform: translateX(100%);
189
+ transition: transform 0.3s ease-in-out;
190
+ z-index: 1000;
160
191
  }
161
192
 
162
- #code-viewer.visible {
163
- display: block;
193
+ #content-pane.visible {
194
+ transform: translateX(0);
164
195
  }
165
196
 
166
- #code-viewer .header {
167
- margin-bottom: 12px;
168
- padding-bottom: 12px;
197
+ #content-pane .pane-header {
198
+ position: sticky;
199
+ top: 0;
200
+ background: rgba(13, 17, 23, 0.98);
201
+ padding: 20px;
169
202
  border-bottom: 1px solid #30363d;
203
+ z-index: 1;
170
204
  }
171
205
 
172
- #code-viewer .title {
173
- font-size: 14px;
206
+ #content-pane .pane-title {
207
+ font-size: 16px;
174
208
  font-weight: bold;
175
209
  color: #58a6ff;
176
- margin-bottom: 4px;
210
+ margin-bottom: 8px;
211
+ padding-right: 30px;
177
212
  }
178
213
 
179
- #code-viewer .meta {
180
- font-size: 11px;
214
+ #content-pane .pane-meta {
215
+ font-size: 12px;
181
216
  color: #8b949e;
182
217
  }
183
218
 
184
- #code-viewer .close-btn {
185
- float: right;
219
+ #content-pane .collapse-btn {
220
+ position: absolute;
221
+ top: 20px;
222
+ right: 20px;
186
223
  cursor: pointer;
187
224
  color: #8b949e;
188
- font-size: 20px;
225
+ font-size: 24px;
189
226
  line-height: 1;
190
- margin-top: -4px;
227
+ background: none;
228
+ border: none;
229
+ padding: 0;
230
+ transition: color 0.2s;
191
231
  }
192
232
 
193
- #code-viewer .close-btn:hover {
233
+ #content-pane .collapse-btn:hover {
194
234
  color: #c9d1d9;
195
235
  }
196
236
 
197
- #code-viewer pre {
237
+ #content-pane .pane-content {
238
+ padding: 20px;
239
+ }
240
+
241
+ #content-pane pre {
198
242
  margin: 0;
199
- padding: 12px;
243
+ padding: 16px;
200
244
  background: #0d1117;
201
245
  border: 1px solid #30363d;
202
246
  border-radius: 6px;
203
247
  overflow-x: auto;
204
- font-size: 11px;
205
- line-height: 1.5;
248
+ font-size: 12px;
249
+ line-height: 1.6;
206
250
  }
207
251
 
208
- #code-viewer code {
252
+ #content-pane code {
209
253
  color: #c9d1d9;
210
254
  font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
211
255
  }
212
256
 
213
- .node.highlighted circle {
257
+ #content-pane .directory-list {
258
+ list-style: none;
259
+ padding: 0;
260
+ margin: 0;
261
+ }
262
+
263
+ #content-pane .directory-list li {
264
+ padding: 8px 12px;
265
+ margin: 4px 0;
266
+ background: #161b22;
267
+ border: 1px solid #30363d;
268
+ border-radius: 4px;
269
+ font-size: 12px;
270
+ display: flex;
271
+ align-items: center;
272
+ }
273
+
274
+ #content-pane .directory-list .item-icon {
275
+ margin-right: 8px;
276
+ font-size: 14px;
277
+ }
278
+
279
+ #content-pane .directory-list .item-type {
280
+ margin-left: auto;
281
+ padding-left: 12px;
282
+ font-size: 10px;
283
+ color: #8b949e;
284
+ }
285
+
286
+ #content-pane .import-details {
287
+ background: #161b22;
288
+ border: 1px solid #30363d;
289
+ border-radius: 6px;
290
+ padding: 16px;
291
+ }
292
+
293
+ #content-pane .import-details .import-statement {
294
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
295
+ font-size: 12px;
296
+ color: #79c0ff;
297
+ margin-bottom: 12px;
298
+ }
299
+
300
+ #content-pane .import-details .detail-row {
301
+ font-size: 11px;
302
+ color: #8b949e;
303
+ margin: 4px 0;
304
+ }
305
+
306
+ #content-pane .import-details .detail-label {
307
+ color: #c9d1d9;
308
+ font-weight: 600;
309
+ }
310
+
311
+ .node.highlighted circle,
312
+ .node.highlighted rect {
214
313
  stroke: #f0e68c;
215
314
  stroke-width: 3px;
216
315
  filter: drop-shadow(0 0 8px #f0e68c);
@@ -228,25 +327,37 @@
228
327
  <h3>Legend</h3>
229
328
  <div class="legend">
230
329
  <div class="legend-item">
231
- 📁 <span class="legend-color" style="background: #ffa657;"></span> Directory
330
+ <span class="legend-color" style="background: #da3633;"></span> Subproject
331
+ </div>
332
+ <div class="legend-item">
333
+ <span class="legend-color" style="border: 2px dashed #79c0ff; border-radius: 50%; background: transparent;"></span> Directory
334
+ </div>
335
+ <div class="legend-item">
336
+ <span class="legend-color" style="border: 2px dashed #58a6ff; border-radius: 50%; background: transparent;"></span> File
337
+ </div>
338
+ <div class="legend-item">
339
+ <span class="legend-color" style="background: #238636;"></span> Module
232
340
  </div>
233
341
  <div class="legend-item">
234
- 📄 <span class="legend-color" style="background: #58a6ff;"></span> File
342
+ <span class="legend-color" style="background: #1f6feb;"></span> Class
235
343
  </div>
236
344
  <div class="legend-item">
237
- C <span class="legend-color" style="background: #1f6feb;"></span> Class
345
+ <span class="legend-color" style="background: #d29922;"></span> Function
238
346
  </div>
239
347
  <div class="legend-item">
240
- ƒ <span class="legend-color" style="background: #d29922;"></span> Function
348
+ <span class="legend-color" style="background: #8957e5;"></span> Method
241
349
  </div>
242
350
  <div class="legend-item">
243
- m <span class="legend-color" style="background: #8957e5;"></span> Method
351
+ <span class="legend-color" style="background: #6e7681;"></span> Code
352
+ </div>
353
+ <div class="legend-item" style="font-style: italic; color: #79c0ff;">
354
+ <span class="legend-color" style="background: #6e7681;"></span> Import
244
355
  </div>
245
356
  <div class="legend-item">
246
- M <span class="legend-color" style="background: #238636;"></span> Module
357
+ <span class="legend-color" style="background: #8b949e; border-radius: 2px;"></span> Docstring ▢
247
358
  </div>
248
359
  <div class="legend-item">
249
- <span class="legend-color" style="background: #6e7681;"></span> Text/Other
360
+ <span class="legend-color" style="background: #6e7681; border-radius: 2px;"></span> Comment ▢
250
361
  </div>
251
362
  </div>
252
363
 
@@ -261,13 +372,13 @@
261
372
  <svg id="graph"></svg>
262
373
  <div id="tooltip" class="tooltip"></div>
263
374
 
264
- <div id="code-viewer">
265
- <div class="header">
266
- <span class="close-btn" onclick="closeCodeViewer()">×</span>
267
- <div class="title" id="viewer-title"></div>
268
- <div class="meta" id="viewer-meta"></div>
375
+ <div id="content-pane">
376
+ <div class="pane-header">
377
+ <button class="collapse-btn" onclick="closeContentPane()">×</button>
378
+ <div class="pane-title" id="pane-title"></div>
379
+ <div class="pane-meta" id="pane-meta"></div>
269
380
  </div>
270
- <pre><code id="viewer-code"></code></pre>
381
+ <div class="pane-content" id="pane-content"></div>
271
382
  </div>
272
383
 
273
384
  <script>
@@ -286,7 +397,6 @@
286
397
  let simulation;
287
398
  let allNodes = [];
288
399
  let allLinks = [];
289
- let allLinksOriginal = []; // Keep original link structure before D3 modifies it
290
400
  let visibleNodes = new Set();
291
401
  let collapsedNodes = new Set();
292
402
  let highlightedNode = null;
@@ -296,8 +406,6 @@
296
406
 
297
407
  allNodes = data.nodes;
298
408
  allLinks = data.links;
299
- // Deep copy links before D3 modifies them
300
- allLinksOriginal = JSON.parse(JSON.stringify(data.links));
301
409
 
302
410
  // Find root nodes - start with only top-level nodes
303
411
  let rootNodes;
@@ -305,21 +413,36 @@
305
413
  // In monorepos, subproject nodes are roots
306
414
  rootNodes = allNodes.filter(n => n.type === 'subproject');
307
415
  } else {
308
- // Regular projects: prefer directories, fallback to files
416
+ // Regular projects: Find root nodes by graph structure
309
417
  const dirNodes = allNodes.filter(n => n.type === 'directory');
310
418
  const fileNodes = allNodes.filter(n => n.type === 'file');
311
419
 
312
- if (dirNodes.length > 0) {
313
- // Show root-level directories (minimum depth)
314
- const minDirDepth = Math.min(...dirNodes.map(n => n.depth || 0));
315
- rootNodes = dirNodes.filter(n => (n.depth || 0) === minDirDepth);
316
- } else if (fileNodes.length > 0) {
317
- // No directories, show root-level files
318
- const minFileDepth = Math.min(...fileNodes.map(n => n.depth || 0));
319
- rootNodes = fileNodes.filter(n => (n.depth || 0) === minFileDepth);
320
- } else {
321
- // No directories or files, show all nodes
322
- rootNodes = allNodes;
420
+ // Find minimum depth for directories and files (for fallback)
421
+ let minDirDepth = Infinity;
422
+ let minFileDepth = Infinity;
423
+
424
+ dirNodes.forEach(d => {
425
+ if (d.depth < minDirDepth) minDirDepth = d.depth;
426
+ });
427
+
428
+ fileNodes.forEach(f => {
429
+ if (f.depth < minFileDepth) minFileDepth = f.depth;
430
+ });
431
+
432
+ // CORRECT: Find root nodes by checking graph structure
433
+ // Root nodes are nodes with no incoming links (no parent)
434
+ const targetIds = new Set(allLinks.map(link => link.target));
435
+ rootNodes = allNodes.filter(node => !targetIds.has(node.id));
436
+
437
+ console.log(`Root nodes found via links: ${rootNodes.length}`, rootNodes.map(n => n.name));
438
+
439
+ // Fallback only if we got 0 root nodes (shouldn't happen with correct data)
440
+ if (rootNodes.length === 0) {
441
+ console.warn('No root nodes found via links, falling back to depth-based detection');
442
+ rootNodes = [
443
+ ...dirNodes.filter(n => n.depth === minDirDepth),
444
+ ...fileNodes.filter(n => n.depth === minFileDepth)
445
+ ];
323
446
  }
324
447
  }
325
448
 
@@ -327,58 +450,35 @@
327
450
  visibleNodes = new Set(rootNodes.map(n => n.id));
328
451
  collapsedNodes = new Set(rootNodes.map(n => n.id));
329
452
 
453
+ console.log('=== INITIAL STATE ===');
454
+ console.log(`Total nodes: ${allNodes.length}`);
455
+ console.log(`Total links: ${allLinks.length}`);
456
+ console.log(`Root nodes: ${rootNodes.length}`, rootNodes.map(n => n.name));
457
+ console.log(`Initial visibleNodes:`, Array.from(visibleNodes));
458
+ console.log(`Initial collapsedNodes:`, Array.from(collapsedNodes));
459
+
330
460
  renderGraph();
331
461
  }
332
462
 
333
463
  function renderGraph() {
464
+ console.log('=== renderGraph() called ===');
465
+ console.log(`visibleNodes.size: ${visibleNodes.size}`);
466
+ console.log(`collapsedNodes.size: ${collapsedNodes.size}`);
467
+
334
468
  const visibleNodesList = allNodes.filter(n => visibleNodes.has(n.id));
469
+ console.log(`Rendering ${visibleNodesList.length} visible nodes`);
470
+
335
471
  const visibleLinks = allLinks.filter(l =>
336
472
  visibleNodes.has(l.source.id || l.source) &&
337
473
  visibleNodes.has(l.target.id || l.target)
338
474
  );
339
-
340
- // Dynamic collision radius based on node type
341
- const getNodeRadius = (d) => {
342
- if (d.type === 'directory') return 35;
343
- if (d.type === 'file') return 32;
344
- if (d.type === 'subproject') return 38;
345
- return d.complexity ? Math.min(20 + d.complexity * 2, 35) : 25;
346
- };
347
-
348
- // Dynamic link distance based on node types
349
- const getLinkDistance = (link) => {
350
- const source = typeof link.source === 'object' ? link.source : visibleNodesList.find(n => n.id === link.source);
351
- const target = typeof link.target === 'object' ? link.target : visibleNodesList.find(n => n.id === link.target);
352
-
353
- if (!source || !target) return 150;
354
-
355
- // Longer distances for directory relationships
356
- if (source.type === 'directory' || target.type === 'directory') return 200;
357
- if (source.type === 'file' || target.type === 'file') return 150;
358
- return 100;
359
- };
475
+ console.log(`Rendering ${visibleLinks.length} visible links`);
360
476
 
361
477
  simulation = d3.forceSimulation(visibleNodesList)
362
- .force("link", d3.forceLink(visibleLinks)
363
- .id(d => d.id)
364
- .distance(getLinkDistance)
365
- .strength(0.5))
366
- .force("charge", d3.forceManyBody()
367
- .strength(d => {
368
- // Stronger repulsion for directories and files
369
- if (d.type === 'directory') return -800;
370
- if (d.type === 'file') return -600;
371
- return -400;
372
- })
373
- .distanceMax(500))
478
+ .force("link", d3.forceLink(visibleLinks).id(d => d.id).distance(100))
479
+ .force("charge", d3.forceManyBody().strength(-400))
374
480
  .force("center", d3.forceCenter(width / 2, height / 2))
375
- .force("collision", d3.forceCollide()
376
- .radius(getNodeRadius)
377
- .strength(0.9))
378
- .force("x", d3.forceX(width / 2).strength(0.05))
379
- .force("y", d3.forceY(height / 2).strength(0.05))
380
- .alphaDecay(0.02)
381
- .velocityDecay(0.3);
481
+ .force("collision", d3.forceCollide().radius(40));
382
482
 
383
483
  g.selectAll("*").remove();
384
484
 
@@ -404,16 +504,59 @@
404
504
  .on("mouseover", showTooltip)
405
505
  .on("mouseout", hideTooltip);
406
506
 
407
- // Add circles
408
- node.append("circle")
507
+ // Add shapes based on node type (circles for code, squares for docs)
508
+ const isDocNode = d => ['docstring', 'comment'].includes(d.type);
509
+
510
+ // Add circles for code nodes
511
+ node.filter(d => !isDocNode(d))
512
+ .append("circle")
409
513
  .attr("r", d => {
410
- if (d.type === 'directory') return 25;
411
- if (d.type === 'file') return 22;
412
- if (d.type === 'subproject') return 28;
413
- return d.complexity ? Math.min(12 + d.complexity * 2, 25) : 15;
514
+ if (d.type === 'subproject') return 20;
515
+ if (d.type === 'directory') return 40; // Largest for directory containers
516
+ if (d.type === 'file') return 30; // Larger transparent circle for files
517
+ return d.complexity ? Math.min(8 + d.complexity * 2, 25) : 12;
518
+ })
519
+ .attr("stroke", d => hasChildren(d) ? "#ffffff" : "none")
520
+ .attr("stroke-width", d => hasChildren(d) ? 3 : 0)
521
+ .style("fill", d => d.color || null); // Use custom color if available
522
+
523
+ // Add rectangles for document nodes
524
+ node.filter(d => isDocNode(d))
525
+ .append("rect")
526
+ .attr("width", d => {
527
+ const size = d.complexity ? Math.min(8 + d.complexity * 2, 25) : 12;
528
+ return size * 2;
529
+ })
530
+ .attr("height", d => {
531
+ const size = d.complexity ? Math.min(8 + d.complexity * 2, 25) : 12;
532
+ return size * 2;
414
533
  })
534
+ .attr("x", d => {
535
+ const size = d.complexity ? Math.min(8 + d.complexity * 2, 25) : 12;
536
+ return -size;
537
+ })
538
+ .attr("y", d => {
539
+ const size = d.complexity ? Math.min(8 + d.complexity * 2, 25) : 12;
540
+ return -size;
541
+ })
542
+ .attr("rx", 2) // Rounded corners
543
+ .attr("ry", 2)
544
+ .attr("stroke", d => hasChildren(d) ? "#ffffff" : "none")
545
+ .attr("stroke-width", d => hasChildren(d) ? 3 : 0)
415
546
  .style("fill", d => d.color || null);
416
547
 
548
+ // Add expand/collapse indicator
549
+ node.filter(d => hasChildren(d))
550
+ .append("text")
551
+ .attr("class", "expand-indicator")
552
+ .attr("text-anchor", "middle")
553
+ .attr("dy", 5)
554
+ .style("font-size", "16px")
555
+ .style("font-weight", "bold")
556
+ .style("fill", "#ffffff")
557
+ .style("pointer-events", "none")
558
+ .text(d => collapsedNodes.has(d.id) ? "+" : "−");
559
+
417
560
  // Add icons based on node type
418
561
  node.append("text")
419
562
  .attr("class", "node-icon")
@@ -426,24 +569,27 @@
426
569
  if (d.type === 'method') return 'm';
427
570
  if (d.type === 'module') return 'M';
428
571
  if (d.type === 'imports') return '⇄';
572
+ if (d.type === 'docstring') return '📝';
573
+ if (d.type === 'comment') return '💬';
574
+ if (d.type === 'subproject') return '📦';
429
575
  return '•';
430
576
  });
431
577
 
432
- // Add expand/collapse indicator for nodes with children
433
- node.filter(d => hasChildren(d))
434
- .append("text")
435
- .attr("class", "expand-indicator")
436
- .attr("text-anchor", "middle")
437
- .attr("dy", -20)
438
- .style("font-size", "14px")
439
- .style("font-weight", "bold")
440
- .style("fill", "#58a6ff")
441
- .style("pointer-events", "none")
442
- .text(d => collapsedNodes.has(d.id) ? "+" : "−");
443
-
444
- // Add labels
578
+ // Add labels (show actual import statement for import nodes)
445
579
  node.append("text")
446
- .text(d => d.name)
580
+ .text(d => {
581
+ // Import nodes have type === 'imports'
582
+ if (d.type === 'imports') {
583
+ if (d.content) {
584
+ // Extract first line of import statement
585
+ const importLine = d.content.split('\n')[0].trim();
586
+ // Truncate if too long (max 60 chars)
587
+ return importLine.length > 60 ? importLine.substring(0, 57) + '...' : importLine;
588
+ }
589
+ return d.name; // Fallback to name if no content
590
+ }
591
+ return d.name;
592
+ })
447
593
  .attr("dy", 30);
448
594
 
449
595
  simulation.on("tick", () => {
@@ -460,50 +606,87 @@
460
606
  }
461
607
 
462
608
  function hasChildren(node) {
463
- // Use original links (not modified by D3)
464
- return allLinksOriginal.some(l => l.source === node.id);
609
+ // Handle both pre-mutation (string IDs) and post-mutation (object references)
610
+ const result = allLinks.some(l => {
611
+ const sourceId = typeof l.source === 'object' ? l.source.id : l.source;
612
+ return sourceId === node.id;
613
+ });
614
+ console.log(`hasChildren(${node.name}):`, result,
615
+ `Checking ${allLinks.length} links for node ID: ${node.id}`);
616
+ return result;
465
617
  }
466
618
 
467
619
  function handleNodeClick(event, d) {
620
+ console.log('=== NODE CLICKED ===');
621
+ console.log('Node:', d.name, 'Type:', d.type, 'ID:', d.id);
622
+ console.log('Event:', event);
623
+
468
624
  event.stopPropagation();
469
625
 
470
- // If node has children, toggle expansion
471
- if (hasChildren(d)) {
472
- if (collapsedNodes.has(d.id)) {
626
+ // Always show content pane when clicking any node
627
+ showContentPane(d);
628
+
629
+ // If node has children, also toggle expansion
630
+ const nodeHasChildren = hasChildren(d);
631
+ console.log('Node has children:', nodeHasChildren);
632
+
633
+ if (nodeHasChildren) {
634
+ const isCollapsed = collapsedNodes.has(d.id);
635
+ console.log('Node is collapsed:', isCollapsed);
636
+
637
+ if (isCollapsed) {
638
+ console.log('Expanding node...');
473
639
  expandNode(d);
474
640
  } else {
641
+ console.log('Collapsing node...');
475
642
  collapseNode(d);
476
643
  }
644
+ console.log('Calling renderGraph()...');
477
645
  renderGraph();
646
+ console.log('renderGraph() complete');
478
647
  } else {
479
- // Leaf node - show code viewer
480
- showCodeViewer(d);
648
+ console.log('Node has no children, skipping expansion');
481
649
  }
482
650
  }
483
651
 
484
652
  function expandNode(node) {
653
+ console.log(`expandNode(${node.name}):`, 'Removing from collapsedNodes');
485
654
  collapsedNodes.delete(node.id);
486
655
 
487
- // Find direct children using original links (not modified by D3)
488
- const children = allLinksOriginal
489
- .filter(l => l.source === node.id)
490
- .map(l => allNodes.find(n => n.id === l.target))
656
+ // Find direct children
657
+ const childLinks = allLinks.filter(l => (l.source.id || l.source) === node.id);
658
+ console.log(` Found ${childLinks.length} child links for node ${node.id}`);
659
+
660
+ const children = childLinks
661
+ .map(l => {
662
+ const targetId = l.target.id || l.target;
663
+ const child = allNodes.find(n => n.id === targetId);
664
+ if (!child) {
665
+ console.warn(` Could not find child node with ID: ${targetId}`);
666
+ }
667
+ return child;
668
+ })
491
669
  .filter(n => n);
492
670
 
671
+ console.log(` Found ${children.length} child nodes:`, children.map(c => c.name));
672
+
493
673
  children.forEach(child => {
674
+ console.log(` Adding child to visibleNodes: ${child.name} (${child.id})`);
494
675
  visibleNodes.add(child.id);
495
676
  collapsedNodes.add(child.id); // Children start collapsed
496
677
  });
678
+
679
+ console.log(` visibleNodes now has ${visibleNodes.size} items`);
497
680
  }
498
681
 
499
682
  function collapseNode(node) {
500
683
  collapsedNodes.add(node.id);
501
684
 
502
- // Hide all descendants recursively using original links
685
+ // Hide all descendants recursively
503
686
  function hideDescendants(parentId) {
504
- const children = allLinksOriginal
505
- .filter(l => l.source === parentId)
506
- .map(l => l.target);
687
+ const children = allLinks
688
+ .filter(l => (l.source.id || l.source) === parentId)
689
+ .map(l => l.target.id || l.target);
507
690
 
508
691
  children.forEach(childId => {
509
692
  visibleNodes.delete(childId);
@@ -586,19 +769,30 @@
586
769
  }
587
770
  }
588
771
 
589
- function showCodeViewer(node) {
772
+ function showContentPane(node) {
590
773
  // Highlight the node
591
774
  highlightedNode = node;
592
775
  renderGraph();
593
776
 
594
- // Populate code viewer
595
- const viewer = document.getElementById('code-viewer');
596
- const title = document.getElementById('viewer-title');
597
- const meta = document.getElementById('viewer-meta');
598
- const code = document.getElementById('viewer-code');
599
-
600
- title.textContent = node.name;
777
+ // Populate content pane
778
+ const pane = document.getElementById('content-pane');
779
+ const title = document.getElementById('pane-title');
780
+ const meta = document.getElementById('pane-meta');
781
+ const content = document.getElementById('pane-content');
782
+
783
+ // Set title with actual import statement for import nodes
784
+ if (node.type === 'imports') {
785
+ if (node.content) {
786
+ const importLine = node.content.split('\n')[0].trim();
787
+ title.textContent = importLine;
788
+ } else {
789
+ title.textContent = `Import: ${node.name}`;
790
+ }
791
+ } else {
792
+ title.textContent = node.name;
793
+ }
601
794
 
795
+ // Set metadata
602
796
  let metaText = `${node.type} • ${node.file_path}`;
603
797
  if (node.start_line) {
604
798
  metaText += ` • Lines ${node.start_line}-${node.end_line}`;
@@ -608,22 +802,178 @@
608
802
  }
609
803
  meta.textContent = metaText;
610
804
 
611
- // Show content if available
805
+ // Display content based on node type
806
+ if (node.type === 'directory') {
807
+ showDirectoryContents(node, content);
808
+ } else if (node.type === 'file') {
809
+ showFileContents(node, content);
810
+ } else if (node.type === 'imports') {
811
+ // Import nodes show import details
812
+ showImportDetails(node, content);
813
+ } else {
814
+ // Class, function, method, code nodes
815
+ showCodeContent(node, content);
816
+ }
817
+
818
+ pane.classList.add('visible');
819
+ }
820
+
821
+ function showDirectoryContents(node, container) {
822
+ // Find all direct children of this directory
823
+ const children = allLinks
824
+ .filter(l => (l.source.id || l.source) === node.id)
825
+ .map(l => allNodes.find(n => n.id === (l.target.id || l.target)))
826
+ .filter(n => n);
827
+
828
+ if (children.length === 0) {
829
+ container.innerHTML = '<p style="color: #8b949e;">Empty directory</p>';
830
+ return;
831
+ }
832
+
833
+ // Group by type
834
+ const files = children.filter(n => n.type === 'file');
835
+ const subdirs = children.filter(n => n.type === 'directory');
836
+ const chunks = children.filter(n => n.type !== 'file' && n.type !== 'directory');
837
+
838
+ let html = '<ul class="directory-list">';
839
+
840
+ // Show subdirectories first
841
+ subdirs.forEach(child => {
842
+ html += `
843
+ <li>
844
+ <span class="item-icon">📁</span>
845
+ ${child.name}
846
+ <span class="item-type">directory</span>
847
+ </li>
848
+ `;
849
+ });
850
+
851
+ // Then files
852
+ files.forEach(child => {
853
+ html += `
854
+ <li>
855
+ <span class="item-icon">📄</span>
856
+ ${child.name}
857
+ <span class="item-type">file</span>
858
+ </li>
859
+ `;
860
+ });
861
+
862
+ // Then code chunks
863
+ chunks.forEach(child => {
864
+ const icon = child.type === 'class' ? '🔷' : child.type === 'function' ? '⚡' : '📝';
865
+ html += `
866
+ <li>
867
+ <span class="item-icon">${icon}</span>
868
+ ${child.name}
869
+ <span class="item-type">${child.type}</span>
870
+ </li>
871
+ `;
872
+ });
873
+
874
+ html += '</ul>';
875
+
876
+ // Add summary
877
+ const summary = `<p style="color: #8b949e; font-size: 11px; margin-top: 16px;">
878
+ Total: ${children.length} items (${subdirs.length} directories, ${files.length} files, ${chunks.length} code chunks)
879
+ </p>`;
880
+
881
+ container.innerHTML = html + summary;
882
+ }
883
+
884
+ function showFileContents(node, container) {
885
+ // Find all chunks in this file
886
+ const fileChunks = allLinks
887
+ .filter(l => (l.source.id || l.source) === node.id)
888
+ .map(l => allNodes.find(n => n.id === (l.target.id || l.target)))
889
+ .filter(n => n);
890
+
891
+ if (fileChunks.length === 0) {
892
+ container.innerHTML = '<p style="color: #8b949e;">No code chunks found in this file</p>';
893
+ return;
894
+ }
895
+
896
+ // Collect all content from chunks and sort by line number
897
+ const sortedChunks = fileChunks
898
+ .filter(c => c.content)
899
+ .sort((a, b) => a.start_line - b.start_line);
900
+
901
+ if (sortedChunks.length === 0) {
902
+ container.innerHTML = '<p style="color: #8b949e;">File content not available</p>';
903
+ return;
904
+ }
905
+
906
+ // Combine all chunks to show full file
907
+ const fullContent = sortedChunks.map(c => c.content).join('\n\n');
908
+
909
+ container.innerHTML = `
910
+ <p style="color: #8b949e; font-size: 11px; margin-bottom: 12px;">
911
+ Contains ${fileChunks.length} code chunks
912
+ </p>
913
+ <pre><code>${escapeHtml(fullContent)}</code></pre>
914
+ `;
915
+ }
916
+
917
+ function showImportDetails(node, container) {
918
+ // Import nodes (type === 'imports') - show import content prominently
919
+ const importHtml = `
920
+ <div class="import-details">
921
+ ${node.content ? `
922
+ <div style="margin-bottom: 16px;">
923
+ <div class="detail-label" style="margin-bottom: 8px;">Import Statement:</div>
924
+ <pre><code>${escapeHtml(node.content)}</code></pre>
925
+ </div>
926
+ ` : '<p style="color: #8b949e;">No import content available</p>'}
927
+ <div class="detail-row">
928
+ <span class="detail-label">File:</span> ${node.file_path}
929
+ </div>
930
+ ${node.start_line ? `
931
+ <div class="detail-row">
932
+ <span class="detail-label">Location:</span> Lines ${node.start_line}-${node.end_line}
933
+ </div>
934
+ ` : ''}
935
+ ${node.language ? `
936
+ <div class="detail-row">
937
+ <span class="detail-label">Language:</span> ${node.language}
938
+ </div>
939
+ ` : ''}
940
+ </div>
941
+ `;
942
+
943
+ container.innerHTML = importHtml;
944
+ }
945
+
946
+ function showCodeContent(node, container) {
947
+ // Show code for function, class, method, or code chunks
948
+ let html = '';
949
+
950
+ if (node.docstring) {
951
+ html += `
952
+ <div style="margin-bottom: 16px; padding: 12px; background: #161b22; border: 1px solid #30363d; border-radius: 6px;">
953
+ <div style="font-size: 11px; color: #8b949e; margin-bottom: 8px; font-weight: 600;">DOCSTRING</div>
954
+ <pre style="margin: 0; padding: 0; background: transparent; border: none;"><code>${escapeHtml(node.docstring)}</code></pre>
955
+ </div>
956
+ `;
957
+ }
958
+
612
959
  if (node.content) {
613
- code.textContent = node.content;
614
- } else if (node.docstring) {
615
- code.textContent = `// Docstring:
616
- ${node.docstring}`;
960
+ html += `<pre><code>${escapeHtml(node.content)}</code></pre>`;
617
961
  } else {
618
- code.textContent = '// No content available';
962
+ html += '<p style="color: #8b949e;">No content available</p>';
619
963
  }
620
964
 
621
- viewer.classList.add('visible');
965
+ container.innerHTML = html;
966
+ }
967
+
968
+ function escapeHtml(text) {
969
+ const div = document.createElement('div');
970
+ div.textContent = text;
971
+ return div.innerHTML;
622
972
  }
623
973
 
624
- function closeCodeViewer() {
625
- const viewer = document.getElementById('code-viewer');
626
- viewer.classList.remove('visible');
974
+ function closeContentPane() {
975
+ const pane = document.getElementById('content-pane');
976
+ pane.classList.remove('visible');
627
977
 
628
978
  // Remove highlight
629
979
  highlightedNode = null;
@@ -634,7 +984,7 @@ ${node.docstring}`;
634
984
  window.addEventListener('DOMContentLoaded', () => {
635
985
  const loadingEl = document.getElementById('loading');
636
986
 
637
- fetch(`chunk-graph.json?t=${Date.now()}`)
987
+ fetch("chunk-graph.json")
638
988
  .then(response => {
639
989
  if (!response.ok) {
640
990
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);