mcp-vector-search 0.9.3__py3-none-any.whl → 0.12.0__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.
- mcp_vector_search/__init__.py +2 -2
- mcp_vector_search/cli/commands/index.py +31 -0
- mcp_vector_search/cli/commands/visualize.py +358 -20
- mcp_vector_search/core/database.py +55 -20
- mcp_vector_search/core/directory_index.py +303 -0
- mcp_vector_search/core/indexer.py +67 -0
- mcp_vector_search/core/models.py +58 -0
- mcp_vector_search/utils/gitignore.py +14 -4
- mcp_vector_search/visualization/index.html +658 -0
- {mcp_vector_search-0.9.3.dist-info → mcp_vector_search-0.12.0.dist-info}/METADATA +1 -1
- {mcp_vector_search-0.9.3.dist-info → mcp_vector_search-0.12.0.dist-info}/RECORD +14 -12
- {mcp_vector_search-0.9.3.dist-info → mcp_vector_search-0.12.0.dist-info}/WHEEL +0 -0
- {mcp_vector_search-0.9.3.dist-info → mcp_vector_search-0.12.0.dist-info}/entry_points.txt +0 -0
- {mcp_vector_search-0.9.3.dist-info → mcp_vector_search-0.12.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
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
|
+
<title>Code Chunk Relationship Graph</title>
|
|
9
|
+
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
10
|
+
<style>
|
|
11
|
+
body {
|
|
12
|
+
margin: 0;
|
|
13
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
14
|
+
background: #0d1117;
|
|
15
|
+
color: #c9d1d9;
|
|
16
|
+
overflow: hidden;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
#controls {
|
|
20
|
+
position: absolute;
|
|
21
|
+
top: 20px;
|
|
22
|
+
left: 20px;
|
|
23
|
+
background: rgba(13, 17, 23, 0.95);
|
|
24
|
+
border: 1px solid #30363d;
|
|
25
|
+
border-radius: 6px;
|
|
26
|
+
padding: 16px;
|
|
27
|
+
min-width: 250px;
|
|
28
|
+
max-height: 80vh;
|
|
29
|
+
overflow-y: auto;
|
|
30
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
h1 { margin: 0 0 16px 0; font-size: 18px; }
|
|
34
|
+
h3 { margin: 16px 0 8px 0; font-size: 14px; color: #8b949e; }
|
|
35
|
+
|
|
36
|
+
.control-group {
|
|
37
|
+
margin-bottom: 12px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
label {
|
|
41
|
+
display: block;
|
|
42
|
+
margin-bottom: 4px;
|
|
43
|
+
font-size: 12px;
|
|
44
|
+
color: #8b949e;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
input[type="file"] {
|
|
48
|
+
width: 100%;
|
|
49
|
+
padding: 6px;
|
|
50
|
+
background: #161b22;
|
|
51
|
+
border: 1px solid #30363d;
|
|
52
|
+
border-radius: 6px;
|
|
53
|
+
color: #c9d1d9;
|
|
54
|
+
font-size: 12px;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.legend {
|
|
58
|
+
font-size: 12px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.legend-item {
|
|
62
|
+
margin: 4px 0;
|
|
63
|
+
display: flex;
|
|
64
|
+
align-items: center;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.legend-color {
|
|
68
|
+
width: 12px;
|
|
69
|
+
height: 12px;
|
|
70
|
+
border-radius: 50%;
|
|
71
|
+
margin-right: 8px;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#graph {
|
|
75
|
+
width: 100vw;
|
|
76
|
+
height: 100vh;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.node circle {
|
|
80
|
+
cursor: pointer;
|
|
81
|
+
stroke: #c9d1d9;
|
|
82
|
+
stroke-width: 1.5px;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.node.directory circle { fill: #ffa657; }
|
|
86
|
+
.node.file circle { fill: #58a6ff; }
|
|
87
|
+
.node.module circle { fill: #238636; }
|
|
88
|
+
.node.class circle { fill: #1f6feb; }
|
|
89
|
+
.node.function circle { fill: #d29922; }
|
|
90
|
+
.node.method circle { fill: #8957e5; }
|
|
91
|
+
.node.imports circle { fill: #6e7681; }
|
|
92
|
+
.node.text circle { fill: #6e7681; }
|
|
93
|
+
.node.code circle { fill: #6e7681; }
|
|
94
|
+
.node.subproject circle { fill: #da3633; stroke-width: 3px; }
|
|
95
|
+
|
|
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;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.node text {
|
|
106
|
+
font-size: 11px;
|
|
107
|
+
fill: #c9d1d9;
|
|
108
|
+
text-anchor: middle;
|
|
109
|
+
pointer-events: none;
|
|
110
|
+
user-select: none;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.link {
|
|
114
|
+
stroke: #58a6ff;
|
|
115
|
+
stroke-opacity: 0.4;
|
|
116
|
+
stroke-width: 2px;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.link.dependency {
|
|
120
|
+
stroke: #d29922;
|
|
121
|
+
stroke-opacity: 0.8;
|
|
122
|
+
stroke-width: 2px;
|
|
123
|
+
stroke-dasharray: 5,5;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.tooltip {
|
|
127
|
+
position: absolute;
|
|
128
|
+
padding: 12px;
|
|
129
|
+
background: rgba(13, 17, 23, 0.95);
|
|
130
|
+
border: 1px solid #30363d;
|
|
131
|
+
border-radius: 6px;
|
|
132
|
+
pointer-events: none;
|
|
133
|
+
display: none;
|
|
134
|
+
font-size: 12px;
|
|
135
|
+
max-width: 300px;
|
|
136
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.stats {
|
|
140
|
+
margin-top: 16px;
|
|
141
|
+
padding-top: 16px;
|
|
142
|
+
border-top: 1px solid #30363d;
|
|
143
|
+
font-size: 12px;
|
|
144
|
+
color: #8b949e;
|
|
145
|
+
}
|
|
146
|
+
|
|
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;
|
|
157
|
+
overflow-y: auto;
|
|
158
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
|
159
|
+
display: none;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
#code-viewer.visible {
|
|
163
|
+
display: block;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
#code-viewer .header {
|
|
167
|
+
margin-bottom: 12px;
|
|
168
|
+
padding-bottom: 12px;
|
|
169
|
+
border-bottom: 1px solid #30363d;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
#code-viewer .title {
|
|
173
|
+
font-size: 14px;
|
|
174
|
+
font-weight: bold;
|
|
175
|
+
color: #58a6ff;
|
|
176
|
+
margin-bottom: 4px;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
#code-viewer .meta {
|
|
180
|
+
font-size: 11px;
|
|
181
|
+
color: #8b949e;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
#code-viewer .close-btn {
|
|
185
|
+
float: right;
|
|
186
|
+
cursor: pointer;
|
|
187
|
+
color: #8b949e;
|
|
188
|
+
font-size: 20px;
|
|
189
|
+
line-height: 1;
|
|
190
|
+
margin-top: -4px;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
#code-viewer .close-btn:hover {
|
|
194
|
+
color: #c9d1d9;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
#code-viewer pre {
|
|
198
|
+
margin: 0;
|
|
199
|
+
padding: 12px;
|
|
200
|
+
background: #0d1117;
|
|
201
|
+
border: 1px solid #30363d;
|
|
202
|
+
border-radius: 6px;
|
|
203
|
+
overflow-x: auto;
|
|
204
|
+
font-size: 11px;
|
|
205
|
+
line-height: 1.5;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
#code-viewer code {
|
|
209
|
+
color: #c9d1d9;
|
|
210
|
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.node.highlighted circle {
|
|
214
|
+
stroke: #f0e68c;
|
|
215
|
+
stroke-width: 3px;
|
|
216
|
+
filter: drop-shadow(0 0 8px #f0e68c);
|
|
217
|
+
}
|
|
218
|
+
</style>
|
|
219
|
+
</head>
|
|
220
|
+
<body>
|
|
221
|
+
<div id="controls">
|
|
222
|
+
<h1>🔍 Code Graph</h1>
|
|
223
|
+
|
|
224
|
+
<div class="control-group" id="loading">
|
|
225
|
+
<label>⏳ Loading graph data...</label>
|
|
226
|
+
</div>
|
|
227
|
+
|
|
228
|
+
<h3>Legend</h3>
|
|
229
|
+
<div class="legend">
|
|
230
|
+
<div class="legend-item">
|
|
231
|
+
📁 <span class="legend-color" style="background: #ffa657;"></span> Directory
|
|
232
|
+
</div>
|
|
233
|
+
<div class="legend-item">
|
|
234
|
+
📄 <span class="legend-color" style="background: #58a6ff;"></span> File
|
|
235
|
+
</div>
|
|
236
|
+
<div class="legend-item">
|
|
237
|
+
C <span class="legend-color" style="background: #1f6feb;"></span> Class
|
|
238
|
+
</div>
|
|
239
|
+
<div class="legend-item">
|
|
240
|
+
ƒ <span class="legend-color" style="background: #d29922;"></span> Function
|
|
241
|
+
</div>
|
|
242
|
+
<div class="legend-item">
|
|
243
|
+
m <span class="legend-color" style="background: #8957e5;"></span> Method
|
|
244
|
+
</div>
|
|
245
|
+
<div class="legend-item">
|
|
246
|
+
M <span class="legend-color" style="background: #238636;"></span> Module
|
|
247
|
+
</div>
|
|
248
|
+
<div class="legend-item">
|
|
249
|
+
• <span class="legend-color" style="background: #6e7681;"></span> Text/Other
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
|
|
253
|
+
<div id="subprojects-legend" style="display: none;">
|
|
254
|
+
<h3>Subprojects</h3>
|
|
255
|
+
<div class="legend" id="subprojects-list"></div>
|
|
256
|
+
</div>
|
|
257
|
+
|
|
258
|
+
<div class="stats" id="stats"></div>
|
|
259
|
+
</div>
|
|
260
|
+
|
|
261
|
+
<svg id="graph"></svg>
|
|
262
|
+
<div id="tooltip" class="tooltip"></div>
|
|
263
|
+
|
|
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>
|
|
269
|
+
</div>
|
|
270
|
+
<pre><code id="viewer-code"></code></pre>
|
|
271
|
+
</div>
|
|
272
|
+
|
|
273
|
+
<script>
|
|
274
|
+
const width = window.innerWidth;
|
|
275
|
+
const height = window.innerHeight;
|
|
276
|
+
|
|
277
|
+
const svg = d3.select("#graph")
|
|
278
|
+
.attr("width", width)
|
|
279
|
+
.attr("height", height)
|
|
280
|
+
.call(d3.zoom().on("zoom", (event) => {
|
|
281
|
+
g.attr("transform", event.transform);
|
|
282
|
+
}));
|
|
283
|
+
|
|
284
|
+
const g = svg.append("g");
|
|
285
|
+
const tooltip = d3.select("#tooltip");
|
|
286
|
+
let simulation;
|
|
287
|
+
let allNodes = [];
|
|
288
|
+
let allLinks = [];
|
|
289
|
+
let allLinksOriginal = []; // Keep original link structure before D3 modifies it
|
|
290
|
+
let visibleNodes = new Set();
|
|
291
|
+
let collapsedNodes = new Set();
|
|
292
|
+
let highlightedNode = null;
|
|
293
|
+
|
|
294
|
+
function visualizeGraph(data) {
|
|
295
|
+
g.selectAll("*").remove();
|
|
296
|
+
|
|
297
|
+
allNodes = data.nodes;
|
|
298
|
+
allLinks = data.links;
|
|
299
|
+
// Deep copy links before D3 modifies them
|
|
300
|
+
allLinksOriginal = JSON.parse(JSON.stringify(data.links));
|
|
301
|
+
|
|
302
|
+
// Find root nodes - start with only top-level nodes
|
|
303
|
+
let rootNodes;
|
|
304
|
+
if (data.metadata && data.metadata.is_monorepo) {
|
|
305
|
+
// In monorepos, subproject nodes are roots
|
|
306
|
+
rootNodes = allNodes.filter(n => n.type === 'subproject');
|
|
307
|
+
} else {
|
|
308
|
+
// Regular projects: prefer directories, fallback to files
|
|
309
|
+
const dirNodes = allNodes.filter(n => n.type === 'directory');
|
|
310
|
+
const fileNodes = allNodes.filter(n => n.type === 'file');
|
|
311
|
+
|
|
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;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Start with only root nodes visible, all collapsed
|
|
327
|
+
visibleNodes = new Set(rootNodes.map(n => n.id));
|
|
328
|
+
collapsedNodes = new Set(rootNodes.map(n => n.id));
|
|
329
|
+
|
|
330
|
+
renderGraph();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function renderGraph() {
|
|
334
|
+
const visibleNodesList = allNodes.filter(n => visibleNodes.has(n.id));
|
|
335
|
+
const visibleLinks = allLinks.filter(l =>
|
|
336
|
+
visibleNodes.has(l.source.id || l.source) &&
|
|
337
|
+
visibleNodes.has(l.target.id || l.target)
|
|
338
|
+
);
|
|
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
|
+
};
|
|
360
|
+
|
|
361
|
+
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))
|
|
374
|
+
.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);
|
|
382
|
+
|
|
383
|
+
g.selectAll("*").remove();
|
|
384
|
+
|
|
385
|
+
const link = g.append("g")
|
|
386
|
+
.selectAll("line")
|
|
387
|
+
.data(visibleLinks)
|
|
388
|
+
.join("line")
|
|
389
|
+
.attr("class", d => d.type === "dependency" ? "link dependency" : "link");
|
|
390
|
+
|
|
391
|
+
const node = g.append("g")
|
|
392
|
+
.selectAll("g")
|
|
393
|
+
.data(visibleNodesList)
|
|
394
|
+
.join("g")
|
|
395
|
+
.attr("class", d => {
|
|
396
|
+
let classes = `node ${d.type}`;
|
|
397
|
+
if (highlightedNode && d.id === highlightedNode.id) {
|
|
398
|
+
classes += ' highlighted';
|
|
399
|
+
}
|
|
400
|
+
return classes;
|
|
401
|
+
})
|
|
402
|
+
.call(drag(simulation))
|
|
403
|
+
.on("click", handleNodeClick)
|
|
404
|
+
.on("mouseover", showTooltip)
|
|
405
|
+
.on("mouseout", hideTooltip);
|
|
406
|
+
|
|
407
|
+
// Add circles
|
|
408
|
+
node.append("circle")
|
|
409
|
+
.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;
|
|
414
|
+
})
|
|
415
|
+
.style("fill", d => d.color || null);
|
|
416
|
+
|
|
417
|
+
// Add icons based on node type
|
|
418
|
+
node.append("text")
|
|
419
|
+
.attr("class", "node-icon")
|
|
420
|
+
.attr("dy", 15)
|
|
421
|
+
.text(d => {
|
|
422
|
+
if (d.type === 'directory') return '📁';
|
|
423
|
+
if (d.type === 'file') return '📄';
|
|
424
|
+
if (d.type === 'class') return 'C';
|
|
425
|
+
if (d.type === 'function') return 'ƒ';
|
|
426
|
+
if (d.type === 'method') return 'm';
|
|
427
|
+
if (d.type === 'module') return 'M';
|
|
428
|
+
if (d.type === 'imports') return '⇄';
|
|
429
|
+
return '•';
|
|
430
|
+
});
|
|
431
|
+
|
|
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
|
|
445
|
+
node.append("text")
|
|
446
|
+
.text(d => d.name)
|
|
447
|
+
.attr("dy", 30);
|
|
448
|
+
|
|
449
|
+
simulation.on("tick", () => {
|
|
450
|
+
link
|
|
451
|
+
.attr("x1", d => d.source.x)
|
|
452
|
+
.attr("y1", d => d.source.y)
|
|
453
|
+
.attr("x2", d => d.target.x)
|
|
454
|
+
.attr("y2", d => d.target.y);
|
|
455
|
+
|
|
456
|
+
node.attr("transform", d => `translate(${d.x},${d.y})`);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
updateStats({nodes: visibleNodesList, links: visibleLinks, metadata: {total_files: allNodes.length}});
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function hasChildren(node) {
|
|
463
|
+
// Use original links (not modified by D3)
|
|
464
|
+
return allLinksOriginal.some(l => l.source === node.id);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function handleNodeClick(event, d) {
|
|
468
|
+
event.stopPropagation();
|
|
469
|
+
|
|
470
|
+
// If node has children, toggle expansion
|
|
471
|
+
if (hasChildren(d)) {
|
|
472
|
+
if (collapsedNodes.has(d.id)) {
|
|
473
|
+
expandNode(d);
|
|
474
|
+
} else {
|
|
475
|
+
collapseNode(d);
|
|
476
|
+
}
|
|
477
|
+
renderGraph();
|
|
478
|
+
} else {
|
|
479
|
+
// Leaf node - show code viewer
|
|
480
|
+
showCodeViewer(d);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function expandNode(node) {
|
|
485
|
+
collapsedNodes.delete(node.id);
|
|
486
|
+
|
|
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))
|
|
491
|
+
.filter(n => n);
|
|
492
|
+
|
|
493
|
+
children.forEach(child => {
|
|
494
|
+
visibleNodes.add(child.id);
|
|
495
|
+
collapsedNodes.add(child.id); // Children start collapsed
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function collapseNode(node) {
|
|
500
|
+
collapsedNodes.add(node.id);
|
|
501
|
+
|
|
502
|
+
// Hide all descendants recursively using original links
|
|
503
|
+
function hideDescendants(parentId) {
|
|
504
|
+
const children = allLinksOriginal
|
|
505
|
+
.filter(l => l.source === parentId)
|
|
506
|
+
.map(l => l.target);
|
|
507
|
+
|
|
508
|
+
children.forEach(childId => {
|
|
509
|
+
visibleNodes.delete(childId);
|
|
510
|
+
collapsedNodes.delete(childId);
|
|
511
|
+
hideDescendants(childId);
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
hideDescendants(node.id);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function showTooltip(event, d) {
|
|
519
|
+
tooltip
|
|
520
|
+
.style("display", "block")
|
|
521
|
+
.style("left", (event.pageX + 10) + "px")
|
|
522
|
+
.style("top", (event.pageY + 10) + "px")
|
|
523
|
+
.html(`
|
|
524
|
+
<div><strong>${d.name}</strong></div>
|
|
525
|
+
<div>Type: ${d.type}</div>
|
|
526
|
+
${d.complexity ? `<div>Complexity: ${d.complexity.toFixed(1)}</div>` : ''}
|
|
527
|
+
${d.start_line ? `<div>Lines: ${d.start_line}-${d.end_line}</div>` : ''}
|
|
528
|
+
<div>File: ${d.file_path}</div>
|
|
529
|
+
`);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function hideTooltip() {
|
|
533
|
+
tooltip.style("display", "none");
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function drag(simulation) {
|
|
537
|
+
function dragstarted(event) {
|
|
538
|
+
if (!event.active) simulation.alphaTarget(0.3).restart();
|
|
539
|
+
event.subject.fx = event.subject.x;
|
|
540
|
+
event.subject.fy = event.subject.y;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function dragged(event) {
|
|
544
|
+
event.subject.fx = event.x;
|
|
545
|
+
event.subject.fy = event.y;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function dragended(event) {
|
|
549
|
+
if (!event.active) simulation.alphaTarget(0);
|
|
550
|
+
event.subject.fx = null;
|
|
551
|
+
event.subject.fy = null;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return d3.drag()
|
|
555
|
+
.on("start", dragstarted)
|
|
556
|
+
.on("drag", dragged)
|
|
557
|
+
.on("end", dragended);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function updateStats(data) {
|
|
561
|
+
const stats = d3.select("#stats");
|
|
562
|
+
stats.html(`
|
|
563
|
+
<div>Nodes: ${data.nodes.length}</div>
|
|
564
|
+
<div>Links: ${data.links.length}</div>
|
|
565
|
+
${data.metadata ? `<div>Files: ${data.metadata.total_files || 'N/A'}</div>` : ''}
|
|
566
|
+
${data.metadata && data.metadata.is_monorepo ? `<div>Monorepo: ${data.metadata.subprojects.length} subprojects</div>` : ''}
|
|
567
|
+
`);
|
|
568
|
+
|
|
569
|
+
// Show subproject legend if monorepo
|
|
570
|
+
if (data.metadata && data.metadata.is_monorepo && data.metadata.subprojects.length > 0) {
|
|
571
|
+
const subprojectsLegend = d3.select("#subprojects-legend");
|
|
572
|
+
const subprojectsList = d3.select("#subprojects-list");
|
|
573
|
+
|
|
574
|
+
subprojectsLegend.style("display", "block");
|
|
575
|
+
|
|
576
|
+
// Get subproject nodes with colors
|
|
577
|
+
const subprojectNodes = allNodes.filter(n => n.type === 'subproject');
|
|
578
|
+
|
|
579
|
+
subprojectsList.html(
|
|
580
|
+
subprojectNodes.map(sp =>
|
|
581
|
+
`<div class="legend-item">
|
|
582
|
+
<span class="legend-color" style="background: ${sp.color};"></span> ${sp.name}
|
|
583
|
+
</div>`
|
|
584
|
+
).join('')
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function showCodeViewer(node) {
|
|
590
|
+
// Highlight the node
|
|
591
|
+
highlightedNode = node;
|
|
592
|
+
renderGraph();
|
|
593
|
+
|
|
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;
|
|
601
|
+
|
|
602
|
+
let metaText = `${node.type} • ${node.file_path}`;
|
|
603
|
+
if (node.start_line) {
|
|
604
|
+
metaText += ` • Lines ${node.start_line}-${node.end_line}`;
|
|
605
|
+
}
|
|
606
|
+
if (node.language) {
|
|
607
|
+
metaText += ` • ${node.language}`;
|
|
608
|
+
}
|
|
609
|
+
meta.textContent = metaText;
|
|
610
|
+
|
|
611
|
+
// Show content if available
|
|
612
|
+
if (node.content) {
|
|
613
|
+
code.textContent = node.content;
|
|
614
|
+
} else if (node.docstring) {
|
|
615
|
+
code.textContent = `// Docstring:
|
|
616
|
+
${node.docstring}`;
|
|
617
|
+
} else {
|
|
618
|
+
code.textContent = '// No content available';
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
viewer.classList.add('visible');
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function closeCodeViewer() {
|
|
625
|
+
const viewer = document.getElementById('code-viewer');
|
|
626
|
+
viewer.classList.remove('visible');
|
|
627
|
+
|
|
628
|
+
// Remove highlight
|
|
629
|
+
highlightedNode = null;
|
|
630
|
+
renderGraph();
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// Auto-load graph data on page load
|
|
634
|
+
window.addEventListener('DOMContentLoaded', () => {
|
|
635
|
+
const loadingEl = document.getElementById('loading');
|
|
636
|
+
|
|
637
|
+
fetch(`chunk-graph.json?t=${Date.now()}`)
|
|
638
|
+
.then(response => {
|
|
639
|
+
if (!response.ok) {
|
|
640
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
641
|
+
}
|
|
642
|
+
return response.json();
|
|
643
|
+
})
|
|
644
|
+
.then(data => {
|
|
645
|
+
loadingEl.innerHTML = '<label style="color: #238636;">✓ Graph loaded successfully</label>';
|
|
646
|
+
setTimeout(() => loadingEl.style.display = 'none', 2000);
|
|
647
|
+
visualizeGraph(data);
|
|
648
|
+
})
|
|
649
|
+
.catch(err => {
|
|
650
|
+
loadingEl.innerHTML = `<label style="color: #f85149;">✗ Failed to load graph data</label><br>` +
|
|
651
|
+
`<small style="color: #8b949e;">${err.message}</small><br>` +
|
|
652
|
+
`<small style="color: #8b949e;">Run: mcp-vector-search visualize export</small>`;
|
|
653
|
+
console.error("Failed to load graph:", err);
|
|
654
|
+
});
|
|
655
|
+
});
|
|
656
|
+
</script>
|
|
657
|
+
</body>
|
|
658
|
+
</html>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcp-vector-search
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.0
|
|
4
4
|
Summary: CLI-first semantic code search with MCP integration
|
|
5
5
|
Project-URL: Homepage, https://github.com/bobmatnyc/mcp-vector-search
|
|
6
6
|
Project-URL: Documentation, https://mcp-vector-search.readthedocs.io
|