mcp-vector-search 0.7.6__py3-none-any.whl → 0.8.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.
- mcp_vector_search/__init__.py +2 -2
- mcp_vector_search/cli/commands/index.py +5 -0
- mcp_vector_search/cli/commands/visualize.py +529 -0
- mcp_vector_search/cli/main.py +16 -11
- mcp_vector_search/core/indexer.py +84 -3
- mcp_vector_search/core/models.py +45 -1
- mcp_vector_search/parsers/base.py +83 -0
- mcp_vector_search/parsers/javascript.py +350 -2
- mcp_vector_search/parsers/python.py +79 -0
- {mcp_vector_search-0.7.6.dist-info → mcp_vector_search-0.8.2.dist-info}/METADATA +1 -1
- {mcp_vector_search-0.7.6.dist-info → mcp_vector_search-0.8.2.dist-info}/RECORD +14 -13
- {mcp_vector_search-0.7.6.dist-info → mcp_vector_search-0.8.2.dist-info}/WHEEL +0 -0
- {mcp_vector_search-0.7.6.dist-info → mcp_vector_search-0.8.2.dist-info}/entry_points.txt +0 -0
- {mcp_vector_search-0.7.6.dist-info → mcp_vector_search-0.8.2.dist-info}/licenses/LICENSE +0 -0
mcp_vector_search/__init__.py
CHANGED
|
@@ -330,6 +330,11 @@ async def _run_batch_indexing(
|
|
|
330
330
|
console.print(
|
|
331
331
|
f"[yellow]⚠ {failed_count} files failed to index[/yellow]"
|
|
332
332
|
)
|
|
333
|
+
error_log_path = project_root / ".mcp-vector-search" / "indexing_errors.log"
|
|
334
|
+
if error_log_path.exists():
|
|
335
|
+
console.print(
|
|
336
|
+
f"[dim] → See details in: {error_log_path}[/dim]"
|
|
337
|
+
)
|
|
333
338
|
else:
|
|
334
339
|
# Non-progress mode (fallback to original behavior)
|
|
335
340
|
indexed_count = await indexer.index_project(
|
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
"""Visualization commands for MCP Vector Search."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from loguru import logger
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
|
|
12
|
+
from ...core.database import ChromaVectorDatabase
|
|
13
|
+
from ...core.embeddings import create_embedding_function
|
|
14
|
+
from ...core.project import ProjectManager
|
|
15
|
+
|
|
16
|
+
app = typer.Typer(
|
|
17
|
+
help="Visualize code chunk relationships",
|
|
18
|
+
no_args_is_help=True,
|
|
19
|
+
)
|
|
20
|
+
console = Console()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@app.command()
|
|
24
|
+
def export(
|
|
25
|
+
output: Path = typer.Option(
|
|
26
|
+
Path("chunk-graph.json"),
|
|
27
|
+
"--output",
|
|
28
|
+
"-o",
|
|
29
|
+
help="Output file for chunk relationship data",
|
|
30
|
+
),
|
|
31
|
+
file_path: str | None = typer.Option(
|
|
32
|
+
None,
|
|
33
|
+
"--file",
|
|
34
|
+
"-f",
|
|
35
|
+
help="Export only chunks from specific file (supports wildcards)",
|
|
36
|
+
),
|
|
37
|
+
) -> None:
|
|
38
|
+
"""Export chunk relationships as JSON for D3.js visualization.
|
|
39
|
+
|
|
40
|
+
Examples:
|
|
41
|
+
# Export all chunks
|
|
42
|
+
mcp-vector-search visualize export
|
|
43
|
+
|
|
44
|
+
# Export from specific file
|
|
45
|
+
mcp-vector-search visualize export --file src/main.py
|
|
46
|
+
|
|
47
|
+
# Custom output location
|
|
48
|
+
mcp-vector-search visualize export -o graph.json
|
|
49
|
+
"""
|
|
50
|
+
asyncio.run(_export_chunks(output, file_path))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def _export_chunks(output: Path, file_filter: str | None) -> None:
|
|
54
|
+
"""Export chunk relationship data."""
|
|
55
|
+
try:
|
|
56
|
+
# Load project
|
|
57
|
+
project_manager = ProjectManager(Path.cwd())
|
|
58
|
+
|
|
59
|
+
if not project_manager.is_initialized():
|
|
60
|
+
console.print("[red]Project not initialized. Run 'mcp-vector-search init' first.[/red]")
|
|
61
|
+
raise typer.Exit(1)
|
|
62
|
+
|
|
63
|
+
config = project_manager.load_config()
|
|
64
|
+
|
|
65
|
+
# Get database
|
|
66
|
+
embedding_function, _ = create_embedding_function(config.embedding_model)
|
|
67
|
+
database = ChromaVectorDatabase(
|
|
68
|
+
persist_directory=config.index_path,
|
|
69
|
+
embedding_function=embedding_function,
|
|
70
|
+
)
|
|
71
|
+
await database.initialize()
|
|
72
|
+
|
|
73
|
+
# Get all chunks with metadata
|
|
74
|
+
console.print("[cyan]Fetching chunks from database...[/cyan]")
|
|
75
|
+
|
|
76
|
+
# Query all chunks (we'll use a dummy search to get all)
|
|
77
|
+
stats = await database.get_stats()
|
|
78
|
+
|
|
79
|
+
if stats.total_chunks == 0:
|
|
80
|
+
console.print("[yellow]No chunks found in index. Run 'mcp-vector-search index' first.[/yellow]")
|
|
81
|
+
raise typer.Exit(1)
|
|
82
|
+
|
|
83
|
+
# Build graph data structure
|
|
84
|
+
nodes = []
|
|
85
|
+
links = []
|
|
86
|
+
|
|
87
|
+
# We need to query the database to get actual chunk data
|
|
88
|
+
# Since there's no "get all chunks" method, we'll work with the stats
|
|
89
|
+
# In a real implementation, you would add a method to get all chunks
|
|
90
|
+
|
|
91
|
+
console.print(f"[yellow]Note: Full chunk export requires database enhancement.[/yellow]")
|
|
92
|
+
console.print(f"[cyan]Creating placeholder graph with {stats.total_chunks} chunks...[/cyan]")
|
|
93
|
+
|
|
94
|
+
# Create sample graph structure
|
|
95
|
+
graph_data = {
|
|
96
|
+
"nodes": [
|
|
97
|
+
{
|
|
98
|
+
"id": f"chunk_{i}",
|
|
99
|
+
"name": f"Chunk {i}",
|
|
100
|
+
"type": "code",
|
|
101
|
+
"file_path": "example.py",
|
|
102
|
+
"start_line": i * 10,
|
|
103
|
+
"end_line": (i + 1) * 10,
|
|
104
|
+
"complexity": 1.0 + (i % 5),
|
|
105
|
+
}
|
|
106
|
+
for i in range(min(stats.total_chunks, 50)) # Limit to 50 for demo
|
|
107
|
+
],
|
|
108
|
+
"links": [
|
|
109
|
+
{"source": f"chunk_{i}", "target": f"chunk_{i+1}"}
|
|
110
|
+
for i in range(min(stats.total_chunks - 1, 49))
|
|
111
|
+
],
|
|
112
|
+
"metadata": {
|
|
113
|
+
"total_chunks": stats.total_chunks,
|
|
114
|
+
"total_files": stats.total_files,
|
|
115
|
+
"languages": stats.languages,
|
|
116
|
+
"export_note": "This is a placeholder. Full export requires database enhancement.",
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
# Write to file
|
|
121
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
122
|
+
with open(output, "w") as f:
|
|
123
|
+
json.dump(graph_data, f, indent=2)
|
|
124
|
+
|
|
125
|
+
await database.close()
|
|
126
|
+
|
|
127
|
+
console.print()
|
|
128
|
+
console.print(
|
|
129
|
+
Panel.fit(
|
|
130
|
+
f"[green]✓[/green] Exported graph data to [cyan]{output}[/cyan]\n\n"
|
|
131
|
+
f"Nodes: {len(graph_data['nodes'])}\n"
|
|
132
|
+
f"Links: {len(graph_data['links'])}\n\n"
|
|
133
|
+
f"[dim]Next: Run 'mcp-vector-search visualize serve' to view[/dim]",
|
|
134
|
+
title="Export Complete",
|
|
135
|
+
border_style="green",
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
except Exception as e:
|
|
140
|
+
logger.error(f"Export failed: {e}")
|
|
141
|
+
console.print(f"[red]✗ Export failed: {e}[/red]")
|
|
142
|
+
raise typer.Exit(1)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@app.command()
|
|
146
|
+
def serve(
|
|
147
|
+
port: int = typer.Option(8080, "--port", "-p", help="Port for visualization server"),
|
|
148
|
+
graph_file: Path = typer.Option(
|
|
149
|
+
Path("chunk-graph.json"),
|
|
150
|
+
"--graph",
|
|
151
|
+
"-g",
|
|
152
|
+
help="Graph JSON file to visualize",
|
|
153
|
+
),
|
|
154
|
+
) -> None:
|
|
155
|
+
"""Start local HTTP server for D3.js visualization.
|
|
156
|
+
|
|
157
|
+
Examples:
|
|
158
|
+
# Start server on default port 8080
|
|
159
|
+
mcp-vector-search visualize serve
|
|
160
|
+
|
|
161
|
+
# Custom port
|
|
162
|
+
mcp-vector-search visualize serve --port 3000
|
|
163
|
+
|
|
164
|
+
# Custom graph file
|
|
165
|
+
mcp-vector-search visualize serve --graph my-graph.json
|
|
166
|
+
"""
|
|
167
|
+
import http.server
|
|
168
|
+
import os
|
|
169
|
+
import socketserver
|
|
170
|
+
import webbrowser
|
|
171
|
+
|
|
172
|
+
# Get visualization directory
|
|
173
|
+
viz_dir = Path(__file__).parent.parent.parent / "visualization"
|
|
174
|
+
|
|
175
|
+
if not viz_dir.exists():
|
|
176
|
+
console.print(
|
|
177
|
+
f"[yellow]Visualization directory not found. Creating at {viz_dir}...[/yellow]"
|
|
178
|
+
)
|
|
179
|
+
viz_dir.mkdir(parents=True, exist_ok=True)
|
|
180
|
+
|
|
181
|
+
# Create index.html if it doesn't exist
|
|
182
|
+
html_file = viz_dir / "index.html"
|
|
183
|
+
if not html_file.exists():
|
|
184
|
+
console.print("[yellow]Creating visualization HTML file...[/yellow]")
|
|
185
|
+
_create_visualization_html(html_file)
|
|
186
|
+
|
|
187
|
+
# Copy graph file to visualization directory if it exists
|
|
188
|
+
if graph_file.exists():
|
|
189
|
+
import shutil
|
|
190
|
+
|
|
191
|
+
dest = viz_dir / "chunk-graph.json"
|
|
192
|
+
shutil.copy(graph_file, dest)
|
|
193
|
+
console.print(f"[green]✓[/green] Copied graph data to {dest}")
|
|
194
|
+
else:
|
|
195
|
+
console.print(
|
|
196
|
+
f"[yellow]Warning: Graph file {graph_file} not found. "
|
|
197
|
+
"Run 'mcp-vector-search visualize export' first.[/yellow]"
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# Change to visualization directory
|
|
201
|
+
os.chdir(viz_dir)
|
|
202
|
+
|
|
203
|
+
# Start server
|
|
204
|
+
Handler = http.server.SimpleHTTPRequestHandler
|
|
205
|
+
try:
|
|
206
|
+
with socketserver.TCPServer(("", port), Handler) as httpd:
|
|
207
|
+
url = f"http://localhost:{port}"
|
|
208
|
+
console.print()
|
|
209
|
+
console.print(
|
|
210
|
+
Panel.fit(
|
|
211
|
+
f"[green]✓[/green] Visualization server running\n\n"
|
|
212
|
+
f"URL: [cyan]{url}[/cyan]\n"
|
|
213
|
+
f"Directory: [dim]{viz_dir}[/dim]\n\n"
|
|
214
|
+
f"[dim]Press Ctrl+C to stop[/dim]",
|
|
215
|
+
title="Server Started",
|
|
216
|
+
border_style="green",
|
|
217
|
+
)
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
# Open browser
|
|
221
|
+
webbrowser.open(url)
|
|
222
|
+
|
|
223
|
+
try:
|
|
224
|
+
httpd.serve_forever()
|
|
225
|
+
except KeyboardInterrupt:
|
|
226
|
+
console.print("\n[yellow]Stopping server...[/yellow]")
|
|
227
|
+
|
|
228
|
+
except OSError as e:
|
|
229
|
+
if "Address already in use" in str(e):
|
|
230
|
+
console.print(
|
|
231
|
+
f"[red]✗ Port {port} is already in use. Try a different port with --port[/red]"
|
|
232
|
+
)
|
|
233
|
+
else:
|
|
234
|
+
console.print(f"[red]✗ Server error: {e}[/red]")
|
|
235
|
+
raise typer.Exit(1)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _create_visualization_html(html_file: Path) -> None:
|
|
239
|
+
"""Create the D3.js visualization HTML file."""
|
|
240
|
+
html_content = '''<!DOCTYPE html>
|
|
241
|
+
<html>
|
|
242
|
+
<head>
|
|
243
|
+
<meta charset="utf-8">
|
|
244
|
+
<title>Code Chunk Relationship Graph</title>
|
|
245
|
+
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
246
|
+
<style>
|
|
247
|
+
body {
|
|
248
|
+
margin: 0;
|
|
249
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
250
|
+
background: #0d1117;
|
|
251
|
+
color: #c9d1d9;
|
|
252
|
+
overflow: hidden;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
#controls {
|
|
256
|
+
position: absolute;
|
|
257
|
+
top: 20px;
|
|
258
|
+
left: 20px;
|
|
259
|
+
background: rgba(13, 17, 23, 0.95);
|
|
260
|
+
border: 1px solid #30363d;
|
|
261
|
+
border-radius: 6px;
|
|
262
|
+
padding: 16px;
|
|
263
|
+
min-width: 250px;
|
|
264
|
+
max-height: 80vh;
|
|
265
|
+
overflow-y: auto;
|
|
266
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
h1 { margin: 0 0 16px 0; font-size: 18px; }
|
|
270
|
+
h3 { margin: 16px 0 8px 0; font-size: 14px; color: #8b949e; }
|
|
271
|
+
|
|
272
|
+
.control-group {
|
|
273
|
+
margin-bottom: 12px;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
label {
|
|
277
|
+
display: block;
|
|
278
|
+
margin-bottom: 4px;
|
|
279
|
+
font-size: 12px;
|
|
280
|
+
color: #8b949e;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
input[type="file"] {
|
|
284
|
+
width: 100%;
|
|
285
|
+
padding: 6px;
|
|
286
|
+
background: #161b22;
|
|
287
|
+
border: 1px solid #30363d;
|
|
288
|
+
border-radius: 6px;
|
|
289
|
+
color: #c9d1d9;
|
|
290
|
+
font-size: 12px;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.legend {
|
|
294
|
+
font-size: 12px;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.legend-item {
|
|
298
|
+
margin: 4px 0;
|
|
299
|
+
display: flex;
|
|
300
|
+
align-items: center;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.legend-color {
|
|
304
|
+
width: 12px;
|
|
305
|
+
height: 12px;
|
|
306
|
+
border-radius: 50%;
|
|
307
|
+
margin-right: 8px;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
#graph {
|
|
311
|
+
width: 100vw;
|
|
312
|
+
height: 100vh;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.node circle {
|
|
316
|
+
cursor: pointer;
|
|
317
|
+
stroke: #c9d1d9;
|
|
318
|
+
stroke-width: 1.5px;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.node.module circle { fill: #238636; }
|
|
322
|
+
.node.class circle { fill: #1f6feb; }
|
|
323
|
+
.node.function circle { fill: #d29922; }
|
|
324
|
+
.node.method circle { fill: #8957e5; }
|
|
325
|
+
.node.code circle { fill: #6e7681; }
|
|
326
|
+
|
|
327
|
+
.node text {
|
|
328
|
+
font-size: 11px;
|
|
329
|
+
fill: #c9d1d9;
|
|
330
|
+
text-anchor: middle;
|
|
331
|
+
pointer-events: none;
|
|
332
|
+
user-select: none;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.link {
|
|
336
|
+
stroke: #30363d;
|
|
337
|
+
stroke-opacity: 0.6;
|
|
338
|
+
stroke-width: 1.5px;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.tooltip {
|
|
342
|
+
position: absolute;
|
|
343
|
+
padding: 12px;
|
|
344
|
+
background: rgba(13, 17, 23, 0.95);
|
|
345
|
+
border: 1px solid #30363d;
|
|
346
|
+
border-radius: 6px;
|
|
347
|
+
pointer-events: none;
|
|
348
|
+
display: none;
|
|
349
|
+
font-size: 12px;
|
|
350
|
+
max-width: 300px;
|
|
351
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.stats {
|
|
355
|
+
margin-top: 16px;
|
|
356
|
+
padding-top: 16px;
|
|
357
|
+
border-top: 1px solid #30363d;
|
|
358
|
+
font-size: 12px;
|
|
359
|
+
color: #8b949e;
|
|
360
|
+
}
|
|
361
|
+
</style>
|
|
362
|
+
</head>
|
|
363
|
+
<body>
|
|
364
|
+
<div id="controls">
|
|
365
|
+
<h1>🔍 Code Graph</h1>
|
|
366
|
+
|
|
367
|
+
<div class="control-group">
|
|
368
|
+
<label>Load Graph Data:</label>
|
|
369
|
+
<input type="file" id="fileInput" accept=".json">
|
|
370
|
+
</div>
|
|
371
|
+
|
|
372
|
+
<h3>Legend</h3>
|
|
373
|
+
<div class="legend">
|
|
374
|
+
<div class="legend-item">
|
|
375
|
+
<span class="legend-color" style="background: #238636;"></span> Module
|
|
376
|
+
</div>
|
|
377
|
+
<div class="legend-item">
|
|
378
|
+
<span class="legend-color" style="background: #1f6feb;"></span> Class
|
|
379
|
+
</div>
|
|
380
|
+
<div class="legend-item">
|
|
381
|
+
<span class="legend-color" style="background: #d29922;"></span> Function
|
|
382
|
+
</div>
|
|
383
|
+
<div class="legend-item">
|
|
384
|
+
<span class="legend-color" style="background: #8957e5;"></span> Method
|
|
385
|
+
</div>
|
|
386
|
+
<div class="legend-item">
|
|
387
|
+
<span class="legend-color" style="background: #6e7681;"></span> Code
|
|
388
|
+
</div>
|
|
389
|
+
</div>
|
|
390
|
+
|
|
391
|
+
<div class="stats" id="stats"></div>
|
|
392
|
+
</div>
|
|
393
|
+
|
|
394
|
+
<svg id="graph"></svg>
|
|
395
|
+
<div id="tooltip" class="tooltip"></div>
|
|
396
|
+
|
|
397
|
+
<script>
|
|
398
|
+
const width = window.innerWidth;
|
|
399
|
+
const height = window.innerHeight;
|
|
400
|
+
|
|
401
|
+
const svg = d3.select("#graph")
|
|
402
|
+
.attr("width", width)
|
|
403
|
+
.attr("height", height)
|
|
404
|
+
.call(d3.zoom().on("zoom", (event) => {
|
|
405
|
+
g.attr("transform", event.transform);
|
|
406
|
+
}));
|
|
407
|
+
|
|
408
|
+
const g = svg.append("g");
|
|
409
|
+
const tooltip = d3.select("#tooltip");
|
|
410
|
+
let simulation;
|
|
411
|
+
|
|
412
|
+
function visualizeGraph(data) {
|
|
413
|
+
g.selectAll("*").remove();
|
|
414
|
+
|
|
415
|
+
simulation = d3.forceSimulation(data.nodes)
|
|
416
|
+
.force("link", d3.forceLink(data.links).id(d => d.id).distance(100))
|
|
417
|
+
.force("charge", d3.forceManyBody().strength(-400))
|
|
418
|
+
.force("center", d3.forceCenter(width / 2, height / 2))
|
|
419
|
+
.force("collision", d3.forceCollide().radius(40));
|
|
420
|
+
|
|
421
|
+
const link = g.append("g")
|
|
422
|
+
.selectAll("line")
|
|
423
|
+
.data(data.links)
|
|
424
|
+
.join("line")
|
|
425
|
+
.attr("class", "link");
|
|
426
|
+
|
|
427
|
+
const node = g.append("g")
|
|
428
|
+
.selectAll("g")
|
|
429
|
+
.data(data.nodes)
|
|
430
|
+
.join("g")
|
|
431
|
+
.attr("class", d => `node ${d.type}`)
|
|
432
|
+
.call(drag(simulation))
|
|
433
|
+
.on("mouseover", showTooltip)
|
|
434
|
+
.on("mouseout", hideTooltip);
|
|
435
|
+
|
|
436
|
+
node.append("circle")
|
|
437
|
+
.attr("r", d => d.complexity ? Math.min(8 + d.complexity * 2, 25) : 12);
|
|
438
|
+
|
|
439
|
+
node.append("text")
|
|
440
|
+
.text(d => d.name)
|
|
441
|
+
.attr("dy", 30);
|
|
442
|
+
|
|
443
|
+
simulation.on("tick", () => {
|
|
444
|
+
link
|
|
445
|
+
.attr("x1", d => d.source.x)
|
|
446
|
+
.attr("y1", d => d.source.y)
|
|
447
|
+
.attr("x2", d => d.target.x)
|
|
448
|
+
.attr("y2", d => d.target.y);
|
|
449
|
+
|
|
450
|
+
node.attr("transform", d => `translate(${d.x},${d.y})`);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
updateStats(data);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function showTooltip(event, d) {
|
|
457
|
+
tooltip
|
|
458
|
+
.style("display", "block")
|
|
459
|
+
.style("left", (event.pageX + 10) + "px")
|
|
460
|
+
.style("top", (event.pageY + 10) + "px")
|
|
461
|
+
.html(`
|
|
462
|
+
<div><strong>${d.name}</strong></div>
|
|
463
|
+
<div>Type: ${d.type}</div>
|
|
464
|
+
${d.complexity ? `<div>Complexity: ${d.complexity.toFixed(1)}</div>` : ''}
|
|
465
|
+
${d.start_line ? `<div>Lines: ${d.start_line}-${d.end_line}</div>` : ''}
|
|
466
|
+
<div>File: ${d.file_path}</div>
|
|
467
|
+
`);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function hideTooltip() {
|
|
471
|
+
tooltip.style("display", "none");
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function drag(simulation) {
|
|
475
|
+
function dragstarted(event) {
|
|
476
|
+
if (!event.active) simulation.alphaTarget(0.3).restart();
|
|
477
|
+
event.subject.fx = event.subject.x;
|
|
478
|
+
event.subject.fy = event.subject.y;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function dragged(event) {
|
|
482
|
+
event.subject.fx = event.x;
|
|
483
|
+
event.subject.fy = event.y;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function dragended(event) {
|
|
487
|
+
if (!event.active) simulation.alphaTarget(0);
|
|
488
|
+
event.subject.fx = null;
|
|
489
|
+
event.subject.fy = null;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return d3.drag()
|
|
493
|
+
.on("start", dragstarted)
|
|
494
|
+
.on("drag", dragged)
|
|
495
|
+
.on("end", dragended);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function updateStats(data) {
|
|
499
|
+
const stats = d3.select("#stats");
|
|
500
|
+
stats.html(`
|
|
501
|
+
<div>Nodes: ${data.nodes.length}</div>
|
|
502
|
+
<div>Links: ${data.links.length}</div>
|
|
503
|
+
${data.metadata ? `<div>Files: ${data.metadata.total_files || 'N/A'}</div>` : ''}
|
|
504
|
+
`);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
document.getElementById("fileInput").addEventListener("change", (event) => {
|
|
508
|
+
const file = event.target.files[0];
|
|
509
|
+
if (file) {
|
|
510
|
+
const reader = new FileReader();
|
|
511
|
+
reader.onload = (e) => {
|
|
512
|
+
const data = JSON.parse(e.target.result);
|
|
513
|
+
visualizeGraph(data);
|
|
514
|
+
};
|
|
515
|
+
reader.readAsText(file);
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
// Try to load default data
|
|
520
|
+
fetch("chunk-graph.json")
|
|
521
|
+
.then(response => response.json())
|
|
522
|
+
.then(data => visualizeGraph(data))
|
|
523
|
+
.catch(err => console.log("No default graph found. Please load a JSON file."));
|
|
524
|
+
</script>
|
|
525
|
+
</body>
|
|
526
|
+
</html>'''
|
|
527
|
+
|
|
528
|
+
with open(html_file, "w") as f:
|
|
529
|
+
f.write(html_content)
|
mcp_vector_search/cli/main.py
CHANGED
|
@@ -33,16 +33,17 @@ unfamiliar codebases, finding similar patterns, and integrating with AI tools.
|
|
|
33
33
|
3. Check status: [green]mcp-vector-search status[/green]
|
|
34
34
|
|
|
35
35
|
[bold cyan]Main Commands:[/bold cyan]
|
|
36
|
-
init
|
|
37
|
-
demo
|
|
38
|
-
doctor
|
|
39
|
-
status
|
|
40
|
-
search
|
|
41
|
-
index
|
|
42
|
-
mcp
|
|
43
|
-
config
|
|
44
|
-
|
|
45
|
-
|
|
36
|
+
init 🔧 Initialize project
|
|
37
|
+
demo 🎬 Run interactive demo
|
|
38
|
+
doctor 🩺 Check system health
|
|
39
|
+
status 📊 Show project status
|
|
40
|
+
search 🔍 Search code semantically
|
|
41
|
+
index 📇 Index codebase
|
|
42
|
+
mcp 🤖 MCP integration for AI tools
|
|
43
|
+
config ⚙️ Configure settings
|
|
44
|
+
visualize 📊 Visualize code relationships
|
|
45
|
+
help ❓ Get help
|
|
46
|
+
version ℹ️ Show version
|
|
46
47
|
|
|
47
48
|
[dim]For detailed help: [cyan]mcp-vector-search COMMAND --help[/cyan][/dim]
|
|
48
49
|
""",
|
|
@@ -58,6 +59,7 @@ from .commands.init import init_app # noqa: E402
|
|
|
58
59
|
from .commands.mcp import mcp_app # noqa: E402
|
|
59
60
|
from .commands.search import search_app, search_main # noqa: E402, F401
|
|
60
61
|
from .commands.status import main as status_main # noqa: E402
|
|
62
|
+
from .commands.visualize import app as visualize_app # noqa: E402
|
|
61
63
|
|
|
62
64
|
# ============================================================================
|
|
63
65
|
# MAIN COMMANDS - Clean hierarchy
|
|
@@ -89,7 +91,10 @@ app.add_typer(mcp_app, name="mcp", help="🤖 Manage MCP integration for AI tool
|
|
|
89
91
|
# 8. CONFIG - Configuration
|
|
90
92
|
app.add_typer(config_app, name="config", help="⚙️ Manage project configuration")
|
|
91
93
|
|
|
92
|
-
# 9.
|
|
94
|
+
# 9. VISUALIZE - Code graph visualization
|
|
95
|
+
app.add_typer(visualize_app, name="visualize", help="📊 Visualize code chunk relationships")
|
|
96
|
+
|
|
97
|
+
# 10. HELP - Enhanced help
|
|
93
98
|
# (defined below inline)
|
|
94
99
|
|
|
95
100
|
# 10. VERSION - Version info
|