mcp-vector-search 1.0.3__py3-none-any.whl → 1.1.22__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.
- mcp_vector_search/__init__.py +3 -3
- mcp_vector_search/analysis/__init__.py +48 -1
- mcp_vector_search/analysis/baseline/__init__.py +68 -0
- mcp_vector_search/analysis/baseline/comparator.py +462 -0
- mcp_vector_search/analysis/baseline/manager.py +621 -0
- mcp_vector_search/analysis/collectors/__init__.py +35 -0
- mcp_vector_search/analysis/collectors/cohesion.py +463 -0
- mcp_vector_search/analysis/collectors/coupling.py +1162 -0
- mcp_vector_search/analysis/collectors/halstead.py +514 -0
- mcp_vector_search/analysis/collectors/smells.py +325 -0
- mcp_vector_search/analysis/debt.py +516 -0
- mcp_vector_search/analysis/interpretation.py +685 -0
- mcp_vector_search/analysis/metrics.py +74 -1
- mcp_vector_search/analysis/reporters/__init__.py +3 -1
- mcp_vector_search/analysis/reporters/console.py +424 -0
- mcp_vector_search/analysis/reporters/markdown.py +480 -0
- mcp_vector_search/analysis/reporters/sarif.py +377 -0
- mcp_vector_search/analysis/storage/__init__.py +93 -0
- mcp_vector_search/analysis/storage/metrics_store.py +762 -0
- mcp_vector_search/analysis/storage/schema.py +245 -0
- mcp_vector_search/analysis/storage/trend_tracker.py +560 -0
- mcp_vector_search/analysis/trends.py +308 -0
- mcp_vector_search/analysis/visualizer/__init__.py +90 -0
- mcp_vector_search/analysis/visualizer/d3_data.py +534 -0
- mcp_vector_search/analysis/visualizer/exporter.py +484 -0
- mcp_vector_search/analysis/visualizer/html_report.py +2895 -0
- mcp_vector_search/analysis/visualizer/schemas.py +525 -0
- mcp_vector_search/cli/commands/analyze.py +665 -11
- mcp_vector_search/cli/commands/chat.py +193 -0
- mcp_vector_search/cli/commands/index.py +600 -2
- mcp_vector_search/cli/commands/index_background.py +467 -0
- mcp_vector_search/cli/commands/search.py +194 -1
- mcp_vector_search/cli/commands/setup.py +64 -13
- mcp_vector_search/cli/commands/status.py +302 -3
- mcp_vector_search/cli/commands/visualize/cli.py +26 -10
- mcp_vector_search/cli/commands/visualize/exporters/json_exporter.py +8 -4
- mcp_vector_search/cli/commands/visualize/graph_builder.py +167 -234
- mcp_vector_search/cli/commands/visualize/server.py +304 -15
- mcp_vector_search/cli/commands/visualize/templates/base.py +60 -6
- mcp_vector_search/cli/commands/visualize/templates/scripts.py +2100 -65
- mcp_vector_search/cli/commands/visualize/templates/styles.py +1297 -88
- mcp_vector_search/cli/didyoumean.py +5 -0
- mcp_vector_search/cli/main.py +16 -5
- mcp_vector_search/cli/output.py +134 -5
- mcp_vector_search/config/thresholds.py +89 -1
- mcp_vector_search/core/__init__.py +16 -0
- mcp_vector_search/core/database.py +39 -2
- mcp_vector_search/core/embeddings.py +24 -0
- mcp_vector_search/core/git.py +380 -0
- mcp_vector_search/core/indexer.py +445 -84
- mcp_vector_search/core/llm_client.py +9 -4
- mcp_vector_search/core/models.py +88 -1
- mcp_vector_search/core/relationships.py +473 -0
- mcp_vector_search/core/search.py +1 -1
- mcp_vector_search/mcp/server.py +795 -4
- mcp_vector_search/parsers/python.py +285 -5
- mcp_vector_search/utils/gitignore.py +0 -3
- {mcp_vector_search-1.0.3.dist-info → mcp_vector_search-1.1.22.dist-info}/METADATA +3 -2
- {mcp_vector_search-1.0.3.dist-info → mcp_vector_search-1.1.22.dist-info}/RECORD +62 -39
- mcp_vector_search/cli/commands/visualize.py.original +0 -2536
- {mcp_vector_search-1.0.3.dist-info → mcp_vector_search-1.1.22.dist-info}/WHEEL +0 -0
- {mcp_vector_search-1.0.3.dist-info → mcp_vector_search-1.1.22.dist-info}/entry_points.txt +0 -0
- {mcp_vector_search-1.0.3.dist-info → mcp_vector_search-1.1.22.dist-info}/licenses/LICENSE +0 -0
|
@@ -136,6 +136,41 @@ def search_main(
|
|
|
136
136
|
help="Custom export file path",
|
|
137
137
|
rich_help_panel="💾 Export Options",
|
|
138
138
|
),
|
|
139
|
+
max_complexity: int | None = typer.Option(
|
|
140
|
+
None,
|
|
141
|
+
"--max-complexity",
|
|
142
|
+
help="Filter results with cognitive complexity greater than N",
|
|
143
|
+
min=1,
|
|
144
|
+
rich_help_panel="🎯 Quality Filters",
|
|
145
|
+
),
|
|
146
|
+
no_smells: bool = typer.Option(
|
|
147
|
+
False,
|
|
148
|
+
"--no-smells",
|
|
149
|
+
help="Exclude results with code smells",
|
|
150
|
+
rich_help_panel="🎯 Quality Filters",
|
|
151
|
+
),
|
|
152
|
+
grade: str | None = typer.Option(
|
|
153
|
+
None,
|
|
154
|
+
"--grade",
|
|
155
|
+
help="Filter by complexity grade (e.g., 'A,B,C' or 'A-C')",
|
|
156
|
+
rich_help_panel="🎯 Quality Filters",
|
|
157
|
+
),
|
|
158
|
+
min_quality: int | None = typer.Option(
|
|
159
|
+
None,
|
|
160
|
+
"--min-quality",
|
|
161
|
+
help="Filter by minimum quality score (0-100)",
|
|
162
|
+
min=0,
|
|
163
|
+
max=100,
|
|
164
|
+
rich_help_panel="🎯 Quality Filters",
|
|
165
|
+
),
|
|
166
|
+
quality_weight: float = typer.Option(
|
|
167
|
+
0.3,
|
|
168
|
+
"--quality-weight",
|
|
169
|
+
help="Weight for quality ranking (0.0=pure relevance, 1.0=pure quality, default=0.3)",
|
|
170
|
+
min=0.0,
|
|
171
|
+
max=1.0,
|
|
172
|
+
rich_help_panel="🎯 Quality Filters",
|
|
173
|
+
),
|
|
139
174
|
) -> None:
|
|
140
175
|
"""🔍 Search your codebase semantically.
|
|
141
176
|
|
|
@@ -166,6 +201,25 @@ def search_main(
|
|
|
166
201
|
[green]Context-based search:[/green]
|
|
167
202
|
$ mcp-vector-search search "implement rate limiting" --context --focus security
|
|
168
203
|
|
|
204
|
+
[bold cyan]Quality Filters:[/bold cyan]
|
|
205
|
+
|
|
206
|
+
[green]Filter by complexity:[/green]
|
|
207
|
+
$ mcp-vector-search search "authentication" --max-complexity 15
|
|
208
|
+
|
|
209
|
+
[green]Exclude code smells:[/green]
|
|
210
|
+
$ mcp-vector-search search "login" --no-smells
|
|
211
|
+
|
|
212
|
+
[green]Filter by grade:[/green]
|
|
213
|
+
$ mcp-vector-search search "api" --grade A,B
|
|
214
|
+
|
|
215
|
+
[green]Minimum quality score:[/green]
|
|
216
|
+
$ mcp-vector-search search "handler" --min-quality 80
|
|
217
|
+
|
|
218
|
+
[green]Quality-aware ranking:[/green]
|
|
219
|
+
$ mcp-vector-search search "auth" --quality-weight 0.5 # Balance relevance and quality
|
|
220
|
+
$ mcp-vector-search search "api" --quality-weight 0.0 # Pure semantic search
|
|
221
|
+
$ mcp-vector-search search "util" --quality-weight 1.0 # Pure quality ranking
|
|
222
|
+
|
|
169
223
|
[bold cyan]Export Results:[/bold cyan]
|
|
170
224
|
|
|
171
225
|
[green]Export to JSON:[/green]
|
|
@@ -242,6 +296,11 @@ def search_main(
|
|
|
242
296
|
json_output=json_output,
|
|
243
297
|
export_format=export_format,
|
|
244
298
|
export_path=export_path,
|
|
299
|
+
max_complexity=max_complexity,
|
|
300
|
+
no_smells=no_smells,
|
|
301
|
+
grade=grade,
|
|
302
|
+
min_quality=min_quality,
|
|
303
|
+
quality_weight=quality_weight,
|
|
245
304
|
)
|
|
246
305
|
)
|
|
247
306
|
|
|
@@ -251,6 +310,52 @@ def search_main(
|
|
|
251
310
|
raise typer.Exit(1)
|
|
252
311
|
|
|
253
312
|
|
|
313
|
+
def _parse_grade_filter(grade_str: str) -> set[str]:
|
|
314
|
+
"""Parse grade filter string into set of allowed grades.
|
|
315
|
+
|
|
316
|
+
Supports formats:
|
|
317
|
+
- Comma-separated: "A,B,C"
|
|
318
|
+
- Range: "A-C" (expands to A, B, C)
|
|
319
|
+
- Mixed: "A,C-D" (expands to A, C, D)
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
grade_str: Grade filter string
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
Set of allowed grade letters
|
|
326
|
+
"""
|
|
327
|
+
allowed_grades = set()
|
|
328
|
+
grade_order = ["A", "B", "C", "D", "F"]
|
|
329
|
+
|
|
330
|
+
# Split by comma
|
|
331
|
+
parts = [part.strip().upper() for part in grade_str.split(",")]
|
|
332
|
+
|
|
333
|
+
for part in parts:
|
|
334
|
+
if "-" in part:
|
|
335
|
+
# Range format (e.g., "A-C")
|
|
336
|
+
start, end = part.split("-", 1)
|
|
337
|
+
start = start.strip()
|
|
338
|
+
end = end.strip()
|
|
339
|
+
|
|
340
|
+
if start in grade_order and end in grade_order:
|
|
341
|
+
start_idx = grade_order.index(start)
|
|
342
|
+
end_idx = grade_order.index(end)
|
|
343
|
+
|
|
344
|
+
# Handle reverse ranges (C-A becomes A-C)
|
|
345
|
+
if start_idx > end_idx:
|
|
346
|
+
start_idx, end_idx = end_idx, start_idx
|
|
347
|
+
|
|
348
|
+
# Add all grades in range
|
|
349
|
+
for grade in grade_order[start_idx : end_idx + 1]:
|
|
350
|
+
allowed_grades.add(grade)
|
|
351
|
+
else:
|
|
352
|
+
# Single grade
|
|
353
|
+
if part in grade_order:
|
|
354
|
+
allowed_grades.add(part)
|
|
355
|
+
|
|
356
|
+
return allowed_grades
|
|
357
|
+
|
|
358
|
+
|
|
254
359
|
async def run_search(
|
|
255
360
|
project_root: Path,
|
|
256
361
|
query: str,
|
|
@@ -264,8 +369,13 @@ async def run_search(
|
|
|
264
369
|
json_output: bool = False,
|
|
265
370
|
export_format: str | None = None,
|
|
266
371
|
export_path: Path | None = None,
|
|
372
|
+
max_complexity: int | None = None,
|
|
373
|
+
no_smells: bool = False,
|
|
374
|
+
grade: str | None = None,
|
|
375
|
+
min_quality: int | None = None,
|
|
376
|
+
quality_weight: float = 0.3,
|
|
267
377
|
) -> None:
|
|
268
|
-
"""Run semantic search."""
|
|
378
|
+
"""Run semantic search with optional quality filters and quality-aware ranking."""
|
|
269
379
|
# Load project configuration
|
|
270
380
|
project_manager = ProjectManager(project_root)
|
|
271
381
|
|
|
@@ -371,6 +481,88 @@ async def run_search(
|
|
|
371
481
|
f"File pattern '{files}' filtered results to {len(results)} matches"
|
|
372
482
|
)
|
|
373
483
|
|
|
484
|
+
# Apply quality filters if specified
|
|
485
|
+
if any([max_complexity, no_smells, grade, min_quality]) and results:
|
|
486
|
+
filtered_results = []
|
|
487
|
+
for result in results:
|
|
488
|
+
# Parse quality metrics from result metadata
|
|
489
|
+
cognitive_complexity = getattr(result, "cognitive_complexity", None)
|
|
490
|
+
complexity_grade = getattr(result, "complexity_grade", None)
|
|
491
|
+
smell_count = getattr(result, "smell_count", None)
|
|
492
|
+
quality_score = getattr(result, "quality_score", None)
|
|
493
|
+
|
|
494
|
+
# Filter by max complexity
|
|
495
|
+
if max_complexity is not None and cognitive_complexity is not None:
|
|
496
|
+
if cognitive_complexity > max_complexity:
|
|
497
|
+
continue
|
|
498
|
+
|
|
499
|
+
# Filter by code smells
|
|
500
|
+
if no_smells and smell_count is not None:
|
|
501
|
+
if smell_count > 0:
|
|
502
|
+
continue
|
|
503
|
+
|
|
504
|
+
# Filter by grade
|
|
505
|
+
if grade and complexity_grade:
|
|
506
|
+
allowed_grades = _parse_grade_filter(grade)
|
|
507
|
+
if complexity_grade not in allowed_grades:
|
|
508
|
+
continue
|
|
509
|
+
|
|
510
|
+
# Filter by minimum quality score
|
|
511
|
+
if min_quality is not None and quality_score is not None:
|
|
512
|
+
if quality_score < min_quality:
|
|
513
|
+
continue
|
|
514
|
+
|
|
515
|
+
filtered_results.append(result)
|
|
516
|
+
|
|
517
|
+
initial_count = len(results)
|
|
518
|
+
results = filtered_results
|
|
519
|
+
logger.debug(
|
|
520
|
+
f"Quality filters reduced results from {initial_count} to {len(results)}"
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
# Apply quality-aware ranking if quality_weight > 0 and results have quality metrics
|
|
524
|
+
if quality_weight > 0.0 and results:
|
|
525
|
+
# Calculate quality scores for results that don't have them
|
|
526
|
+
for result in results:
|
|
527
|
+
if result.quality_score is None:
|
|
528
|
+
# Calculate quality score using the formula
|
|
529
|
+
calculated_score = result.calculate_quality_score()
|
|
530
|
+
if calculated_score is not None:
|
|
531
|
+
result.quality_score = calculated_score
|
|
532
|
+
|
|
533
|
+
# Re-rank results based on combined score
|
|
534
|
+
# Store original similarity score for display
|
|
535
|
+
for result in results:
|
|
536
|
+
# Store original relevance score
|
|
537
|
+
if not hasattr(result, "_original_similarity"):
|
|
538
|
+
result._original_similarity = result.similarity_score
|
|
539
|
+
|
|
540
|
+
# Calculate combined score
|
|
541
|
+
if result.quality_score is not None:
|
|
542
|
+
# Normalize quality score to 0-1 range (it's 0-100)
|
|
543
|
+
normalized_quality = result.quality_score / 100.0
|
|
544
|
+
|
|
545
|
+
# Combined score: (1-W) × relevance + W × quality
|
|
546
|
+
combined_score = (
|
|
547
|
+
(1.0 - quality_weight) * result.similarity_score
|
|
548
|
+
+ quality_weight * normalized_quality
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
# Update similarity_score with combined score for sorting
|
|
552
|
+
result.similarity_score = combined_score
|
|
553
|
+
# If no quality score, keep original similarity_score
|
|
554
|
+
|
|
555
|
+
# Re-sort by combined score
|
|
556
|
+
results.sort(key=lambda r: r.similarity_score, reverse=True)
|
|
557
|
+
|
|
558
|
+
# Update ranks
|
|
559
|
+
for i, result in enumerate(results):
|
|
560
|
+
result.rank = i + 1
|
|
561
|
+
|
|
562
|
+
logger.debug(
|
|
563
|
+
f"Quality-aware ranking applied with weight {quality_weight:.2f}"
|
|
564
|
+
)
|
|
565
|
+
|
|
374
566
|
# Handle export if requested
|
|
375
567
|
if export_format:
|
|
376
568
|
from ..export import SearchResultExporter, get_export_path
|
|
@@ -424,6 +616,7 @@ async def run_search(
|
|
|
424
616
|
results=results,
|
|
425
617
|
query=query,
|
|
426
618
|
show_content=show_content,
|
|
619
|
+
quality_weight=quality_weight,
|
|
427
620
|
)
|
|
428
621
|
|
|
429
622
|
# Add contextual tips based on results
|
|
@@ -31,6 +31,9 @@ from pathlib import Path
|
|
|
31
31
|
|
|
32
32
|
import typer
|
|
33
33
|
from loguru import logger
|
|
34
|
+
|
|
35
|
+
# Import Platform enum to filter excluded platforms
|
|
36
|
+
from py_mcp_installer import Platform
|
|
34
37
|
from rich.console import Console
|
|
35
38
|
from rich.panel import Panel
|
|
36
39
|
|
|
@@ -53,6 +56,9 @@ from ..output import (
|
|
|
53
56
|
# Import functions from refactored install module
|
|
54
57
|
from .install import _install_to_platform, detect_all_platforms
|
|
55
58
|
|
|
59
|
+
# Platforms to exclude from auto-setup (user can still manually install)
|
|
60
|
+
EXCLUDED_PLATFORMS_FROM_SETUP = {Platform.CLAUDE_DESKTOP}
|
|
61
|
+
|
|
56
62
|
# Create console for rich output
|
|
57
63
|
console = Console()
|
|
58
64
|
|
|
@@ -148,9 +154,10 @@ def register_with_claude_cli(
|
|
|
148
154
|
|
|
149
155
|
# Build the add command using mcp-vector-search CLI
|
|
150
156
|
# This works for all installation methods: pipx, homebrew, and uv
|
|
157
|
+
# Claude Code sets CWD to the project directory, so no path needed
|
|
151
158
|
# claude mcp add --transport stdio mcp-vector-search \
|
|
152
159
|
# --env MCP_ENABLE_FILE_WATCHING=true \
|
|
153
|
-
# -- mcp-vector-search mcp
|
|
160
|
+
# -- mcp-vector-search mcp
|
|
154
161
|
cmd = [
|
|
155
162
|
"claude",
|
|
156
163
|
"mcp",
|
|
@@ -163,7 +170,6 @@ def register_with_claude_cli(
|
|
|
163
170
|
"--",
|
|
164
171
|
"mcp-vector-search",
|
|
165
172
|
"mcp",
|
|
166
|
-
str(project_root.absolute()),
|
|
167
173
|
]
|
|
168
174
|
|
|
169
175
|
if verbose:
|
|
@@ -959,15 +965,35 @@ async def _run_smart_setup(
|
|
|
959
965
|
detected_platforms_list = detect_all_platforms()
|
|
960
966
|
|
|
961
967
|
if detected_platforms_list:
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
968
|
+
# Filter out excluded platforms for display
|
|
969
|
+
configurable_platforms = [
|
|
970
|
+
p
|
|
971
|
+
for p in detected_platforms_list
|
|
972
|
+
if p.platform not in EXCLUDED_PLATFORMS_FROM_SETUP
|
|
973
|
+
]
|
|
974
|
+
excluded_platforms = [
|
|
975
|
+
p
|
|
976
|
+
for p in detected_platforms_list
|
|
977
|
+
if p.platform in EXCLUDED_PLATFORMS_FROM_SETUP
|
|
978
|
+
]
|
|
979
|
+
|
|
980
|
+
if configurable_platforms:
|
|
981
|
+
platform_names = [p.platform.value for p in configurable_platforms]
|
|
982
|
+
print_success(
|
|
983
|
+
f" ✅ Found {len(platform_names)} platform(s): {', '.join(platform_names)}"
|
|
984
|
+
)
|
|
985
|
+
if verbose:
|
|
986
|
+
for platform_info in configurable_platforms:
|
|
987
|
+
print_info(
|
|
988
|
+
f" {platform_info.platform.value}: {platform_info.config_path}"
|
|
989
|
+
)
|
|
990
|
+
|
|
991
|
+
# Note excluded platforms
|
|
992
|
+
if excluded_platforms:
|
|
993
|
+
excluded_names = [p.platform.value for p in excluded_platforms]
|
|
994
|
+
print_info(
|
|
995
|
+
f" ℹ️ Skipping: {', '.join(excluded_names)} (use 'install mcp --platform' for manual install)"
|
|
996
|
+
)
|
|
971
997
|
else:
|
|
972
998
|
print_info(" No MCP platforms detected (will configure Claude Code)")
|
|
973
999
|
|
|
@@ -1012,7 +1038,27 @@ async def _run_smart_setup(
|
|
|
1012
1038
|
# ===========================================================================
|
|
1013
1039
|
# Phase 4: Indexing
|
|
1014
1040
|
# ===========================================================================
|
|
1015
|
-
if
|
|
1041
|
+
# Determine if indexing is needed:
|
|
1042
|
+
# 1. Not already initialized (new setup)
|
|
1043
|
+
# 2. Force flag is set
|
|
1044
|
+
# 3. Index database doesn't exist
|
|
1045
|
+
# 4. Index exists but is empty
|
|
1046
|
+
# 5. Files have changed (incremental indexing will handle this)
|
|
1047
|
+
needs_indexing = not already_initialized or force
|
|
1048
|
+
|
|
1049
|
+
if already_initialized and not force:
|
|
1050
|
+
# Check if index exists and has content
|
|
1051
|
+
index_db_path = project_root / ".mcp-vector-search" / "chroma.sqlite3"
|
|
1052
|
+
if not index_db_path.exists():
|
|
1053
|
+
print_info(" Index database not found, will create...")
|
|
1054
|
+
needs_indexing = True
|
|
1055
|
+
else:
|
|
1056
|
+
# Check if index is empty or files have changed
|
|
1057
|
+
# Run incremental indexing to catch any changes
|
|
1058
|
+
print_info(" Checking for file changes...")
|
|
1059
|
+
needs_indexing = True # Always run incremental to catch changes
|
|
1060
|
+
|
|
1061
|
+
if needs_indexing:
|
|
1016
1062
|
console.print("\n[bold blue]🔍 Indexing codebase...[/bold blue]")
|
|
1017
1063
|
|
|
1018
1064
|
from .index import run_indexing
|
|
@@ -1045,7 +1091,12 @@ async def _run_smart_setup(
|
|
|
1045
1091
|
print_info(" ✅ Claude CLI detected, using native integration")
|
|
1046
1092
|
|
|
1047
1093
|
# Use detected platforms or default to empty list
|
|
1048
|
-
|
|
1094
|
+
# Filter out excluded platforms (e.g., Claude Desktop) - exclusion already noted in Phase 1
|
|
1095
|
+
platforms_to_configure = [
|
|
1096
|
+
p
|
|
1097
|
+
for p in (detected_platforms_list or [])
|
|
1098
|
+
if p.platform not in EXCLUDED_PLATFORMS_FROM_SETUP
|
|
1099
|
+
]
|
|
1049
1100
|
|
|
1050
1101
|
# Configure all detected platforms using new library
|
|
1051
1102
|
for platform_info in platforms_to_configure:
|