mcp-vector-search 0.12.6__py3-none-any.whl → 1.0.3__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.
Files changed (65) hide show
  1. mcp_vector_search/__init__.py +2 -2
  2. mcp_vector_search/analysis/__init__.py +64 -0
  3. mcp_vector_search/analysis/collectors/__init__.py +39 -0
  4. mcp_vector_search/analysis/collectors/base.py +164 -0
  5. mcp_vector_search/analysis/collectors/complexity.py +743 -0
  6. mcp_vector_search/analysis/metrics.py +341 -0
  7. mcp_vector_search/analysis/reporters/__init__.py +5 -0
  8. mcp_vector_search/analysis/reporters/console.py +222 -0
  9. mcp_vector_search/cli/commands/analyze.py +408 -0
  10. mcp_vector_search/cli/commands/chat.py +1262 -0
  11. mcp_vector_search/cli/commands/index.py +21 -3
  12. mcp_vector_search/cli/commands/init.py +13 -0
  13. mcp_vector_search/cli/commands/install.py +597 -335
  14. mcp_vector_search/cli/commands/install_old.py +8 -4
  15. mcp_vector_search/cli/commands/mcp.py +78 -6
  16. mcp_vector_search/cli/commands/reset.py +68 -26
  17. mcp_vector_search/cli/commands/search.py +30 -7
  18. mcp_vector_search/cli/commands/setup.py +1133 -0
  19. mcp_vector_search/cli/commands/status.py +37 -2
  20. mcp_vector_search/cli/commands/uninstall.py +276 -357
  21. mcp_vector_search/cli/commands/visualize/__init__.py +39 -0
  22. mcp_vector_search/cli/commands/visualize/cli.py +276 -0
  23. mcp_vector_search/cli/commands/visualize/exporters/__init__.py +12 -0
  24. mcp_vector_search/cli/commands/visualize/exporters/html_exporter.py +33 -0
  25. mcp_vector_search/cli/commands/visualize/exporters/json_exporter.py +29 -0
  26. mcp_vector_search/cli/commands/visualize/graph_builder.py +714 -0
  27. mcp_vector_search/cli/commands/visualize/layout_engine.py +469 -0
  28. mcp_vector_search/cli/commands/visualize/server.py +311 -0
  29. mcp_vector_search/cli/commands/visualize/state_manager.py +428 -0
  30. mcp_vector_search/cli/commands/visualize/templates/__init__.py +16 -0
  31. mcp_vector_search/cli/commands/visualize/templates/base.py +180 -0
  32. mcp_vector_search/cli/commands/visualize/templates/scripts.py +2507 -0
  33. mcp_vector_search/cli/commands/visualize/templates/styles.py +1313 -0
  34. mcp_vector_search/cli/commands/visualize.py.original +2536 -0
  35. mcp_vector_search/cli/didyoumean.py +22 -2
  36. mcp_vector_search/cli/main.py +115 -159
  37. mcp_vector_search/cli/output.py +24 -8
  38. mcp_vector_search/config/__init__.py +4 -0
  39. mcp_vector_search/config/default_thresholds.yaml +52 -0
  40. mcp_vector_search/config/settings.py +12 -0
  41. mcp_vector_search/config/thresholds.py +185 -0
  42. mcp_vector_search/core/auto_indexer.py +3 -3
  43. mcp_vector_search/core/boilerplate.py +186 -0
  44. mcp_vector_search/core/config_utils.py +394 -0
  45. mcp_vector_search/core/database.py +369 -94
  46. mcp_vector_search/core/exceptions.py +11 -0
  47. mcp_vector_search/core/git_hooks.py +4 -4
  48. mcp_vector_search/core/indexer.py +221 -4
  49. mcp_vector_search/core/llm_client.py +751 -0
  50. mcp_vector_search/core/models.py +3 -0
  51. mcp_vector_search/core/project.py +17 -0
  52. mcp_vector_search/core/scheduler.py +11 -11
  53. mcp_vector_search/core/search.py +179 -29
  54. mcp_vector_search/mcp/server.py +24 -5
  55. mcp_vector_search/utils/__init__.py +2 -0
  56. mcp_vector_search/utils/gitignore_updater.py +212 -0
  57. mcp_vector_search/utils/monorepo.py +66 -4
  58. mcp_vector_search/utils/timing.py +10 -6
  59. {mcp_vector_search-0.12.6.dist-info → mcp_vector_search-1.0.3.dist-info}/METADATA +182 -52
  60. mcp_vector_search-1.0.3.dist-info/RECORD +97 -0
  61. {mcp_vector_search-0.12.6.dist-info → mcp_vector_search-1.0.3.dist-info}/WHEEL +1 -1
  62. {mcp_vector_search-0.12.6.dist-info → mcp_vector_search-1.0.3.dist-info}/entry_points.txt +1 -0
  63. mcp_vector_search/cli/commands/visualize.py +0 -1467
  64. mcp_vector_search-0.12.6.dist-info/RECORD +0 -68
  65. {mcp_vector_search-0.12.6.dist-info → mcp_vector_search-1.0.3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,185 @@
1
+ """Threshold configuration for code quality metrics."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ import yaml
10
+
11
+
12
+ @dataclass
13
+ class ComplexityThresholds:
14
+ """Thresholds for complexity metrics."""
15
+
16
+ # Cognitive complexity thresholds for grades
17
+ cognitive_a: int = 5 # A grade: 0-5
18
+ cognitive_b: int = 10 # B grade: 6-10
19
+ cognitive_c: int = 20 # C grade: 11-20
20
+ cognitive_d: int = 30 # D grade: 21-30
21
+ # F grade: 31+
22
+
23
+ # Cyclomatic complexity thresholds
24
+ cyclomatic_low: int = 4 # Low complexity
25
+ cyclomatic_moderate: int = 10 # Moderate
26
+ cyclomatic_high: int = 20 # High (needs attention)
27
+ # Very high: 21+
28
+
29
+ # Nesting depth thresholds
30
+ nesting_warning: int = 3 # Warning level
31
+ nesting_error: int = 5 # Error level
32
+
33
+ # Parameter count thresholds
34
+ parameters_warning: int = 4 # Warning level
35
+ parameters_error: int = 7 # Error level
36
+
37
+ # Method count thresholds
38
+ methods_warning: int = 10 # Warning level
39
+ methods_error: int = 20 # Error level
40
+
41
+
42
+ @dataclass
43
+ class SmellThresholds:
44
+ """Thresholds for code smell detection."""
45
+
46
+ # Long method threshold (lines of code)
47
+ long_method_lines: int = 50
48
+
49
+ # Too many parameters
50
+ too_many_parameters: int = 5
51
+
52
+ # Deep nesting
53
+ deep_nesting_depth: int = 4
54
+
55
+ # High complexity
56
+ high_complexity: int = 15
57
+
58
+ # God class (too many methods)
59
+ god_class_methods: int = 20
60
+
61
+ # Feature envy (placeholder for future)
62
+ feature_envy_external_calls: int = 5
63
+
64
+
65
+ @dataclass
66
+ class ThresholdConfig:
67
+ """Complete threshold configuration."""
68
+
69
+ complexity: ComplexityThresholds = field(default_factory=ComplexityThresholds)
70
+ smells: SmellThresholds = field(default_factory=SmellThresholds)
71
+
72
+ # Quality gate settings
73
+ fail_on_f_grade: bool = True
74
+ fail_on_smell_count: int = 10 # Fail if more than N smells
75
+ warn_on_d_grade: bool = True
76
+
77
+ @classmethod
78
+ def load(cls, path: Path) -> ThresholdConfig:
79
+ """Load configuration from YAML file.
80
+
81
+ Args:
82
+ path: Path to YAML configuration file
83
+
84
+ Returns:
85
+ ThresholdConfig instance
86
+ """
87
+ if not path.exists():
88
+ return cls()
89
+
90
+ with open(path) as f:
91
+ data = yaml.safe_load(f) or {}
92
+
93
+ return cls.from_dict(data)
94
+
95
+ @classmethod
96
+ def from_dict(cls, data: dict[str, Any]) -> ThresholdConfig:
97
+ """Create config from dictionary.
98
+
99
+ Args:
100
+ data: Configuration dictionary
101
+
102
+ Returns:
103
+ ThresholdConfig instance
104
+ """
105
+ complexity_data = data.get("complexity", {})
106
+ smells_data = data.get("smells", {})
107
+
108
+ return cls(
109
+ complexity=(
110
+ ComplexityThresholds(**complexity_data)
111
+ if complexity_data
112
+ else ComplexityThresholds()
113
+ ),
114
+ smells=(
115
+ SmellThresholds(**smells_data) if smells_data else SmellThresholds()
116
+ ),
117
+ fail_on_f_grade=data.get("fail_on_f_grade", True),
118
+ fail_on_smell_count=data.get("fail_on_smell_count", 10),
119
+ warn_on_d_grade=data.get("warn_on_d_grade", True),
120
+ )
121
+
122
+ def to_dict(self) -> dict[str, Any]:
123
+ """Convert to dictionary for serialization.
124
+
125
+ Returns:
126
+ Dictionary representation
127
+ """
128
+ return {
129
+ "complexity": {
130
+ "cognitive_a": self.complexity.cognitive_a,
131
+ "cognitive_b": self.complexity.cognitive_b,
132
+ "cognitive_c": self.complexity.cognitive_c,
133
+ "cognitive_d": self.complexity.cognitive_d,
134
+ "cyclomatic_low": self.complexity.cyclomatic_low,
135
+ "cyclomatic_moderate": self.complexity.cyclomatic_moderate,
136
+ "cyclomatic_high": self.complexity.cyclomatic_high,
137
+ "nesting_warning": self.complexity.nesting_warning,
138
+ "nesting_error": self.complexity.nesting_error,
139
+ "parameters_warning": self.complexity.parameters_warning,
140
+ "parameters_error": self.complexity.parameters_error,
141
+ "methods_warning": self.complexity.methods_warning,
142
+ "methods_error": self.complexity.methods_error,
143
+ },
144
+ "smells": {
145
+ "long_method_lines": self.smells.long_method_lines,
146
+ "too_many_parameters": self.smells.too_many_parameters,
147
+ "deep_nesting_depth": self.smells.deep_nesting_depth,
148
+ "high_complexity": self.smells.high_complexity,
149
+ "god_class_methods": self.smells.god_class_methods,
150
+ "feature_envy_external_calls": self.smells.feature_envy_external_calls,
151
+ },
152
+ "fail_on_f_grade": self.fail_on_f_grade,
153
+ "fail_on_smell_count": self.fail_on_smell_count,
154
+ "warn_on_d_grade": self.warn_on_d_grade,
155
+ }
156
+
157
+ def save(self, path: Path) -> None:
158
+ """Save configuration to YAML file.
159
+
160
+ Args:
161
+ path: Path to save configuration
162
+ """
163
+ path.parent.mkdir(parents=True, exist_ok=True)
164
+ with open(path, "w") as f:
165
+ yaml.dump(self.to_dict(), f, default_flow_style=False, sort_keys=False)
166
+
167
+ def get_grade(self, cognitive_complexity: int) -> str:
168
+ """Get complexity grade based on cognitive complexity.
169
+
170
+ Args:
171
+ cognitive_complexity: Cognitive complexity value
172
+
173
+ Returns:
174
+ Grade from A to F
175
+ """
176
+ if cognitive_complexity <= self.complexity.cognitive_a:
177
+ return "A"
178
+ elif cognitive_complexity <= self.complexity.cognitive_b:
179
+ return "B"
180
+ elif cognitive_complexity <= self.complexity.cognitive_c:
181
+ return "C"
182
+ elif cognitive_complexity <= self.complexity.cognitive_d:
183
+ return "D"
184
+ else:
185
+ return "F"
@@ -192,9 +192,9 @@ class AutoIndexer:
192
192
  "staleness_seconds": staleness_seconds,
193
193
  "is_stale": staleness_seconds > self.staleness_threshold,
194
194
  "newest_file_time": newest_file_time,
195
- "oldest_index_time": oldest_index_time
196
- if oldest_index_time != float("inf")
197
- else 0,
195
+ "oldest_index_time": (
196
+ oldest_index_time if oldest_index_time != float("inf") else 0
197
+ ),
198
198
  }
199
199
 
200
200
  except Exception as e:
@@ -0,0 +1,186 @@
1
+ """Boilerplate filtering for semantic search results.
2
+
3
+ This module provides language-specific filtering to penalize common boilerplate
4
+ code patterns (constructors, lifecycle methods, etc.) in search results while
5
+ still preserving them when explicitly queried.
6
+ """
7
+
8
+ from typing import Final
9
+
10
+ # Language-specific boilerplate function/method names
11
+ # Using frozensets for O(1) lookup performance
12
+ _PYTHON_BOILERPLATE: Final[frozenset[str]] = frozenset(
13
+ {
14
+ "__init__",
15
+ "__str__",
16
+ "__repr__",
17
+ "__eq__",
18
+ "__hash__",
19
+ "__len__",
20
+ "__iter__",
21
+ "__next__",
22
+ "__enter__",
23
+ "__exit__",
24
+ "main",
25
+ "setUp",
26
+ "tearDown",
27
+ "setUpClass",
28
+ "tearDownClass",
29
+ }
30
+ )
31
+
32
+ _JAVASCRIPT_TYPESCRIPT_BOILERPLATE: Final[frozenset[str]] = frozenset(
33
+ {
34
+ "constructor",
35
+ "render",
36
+ "componentDidMount",
37
+ "componentWillUnmount",
38
+ "componentDidUpdate",
39
+ "useState",
40
+ "useEffect",
41
+ "index",
42
+ "main",
43
+ "default",
44
+ }
45
+ )
46
+
47
+ _DART_BOILERPLATE: Final[frozenset[str]] = frozenset(
48
+ {
49
+ "build",
50
+ "dispose",
51
+ "initState",
52
+ "didChangeDependencies",
53
+ "main",
54
+ "createState",
55
+ }
56
+ )
57
+
58
+ _PHP_BOILERPLATE: Final[frozenset[str]] = frozenset(
59
+ {
60
+ "__construct",
61
+ "__destruct",
62
+ "__toString",
63
+ "__get",
64
+ "__set",
65
+ "__call",
66
+ "__callStatic",
67
+ "index",
68
+ "main",
69
+ }
70
+ )
71
+
72
+ _RUBY_BOILERPLATE: Final[frozenset[str]] = frozenset(
73
+ {
74
+ "initialize",
75
+ "to_s",
76
+ "to_h",
77
+ "to_a",
78
+ "inspect",
79
+ "main",
80
+ "setup",
81
+ "teardown",
82
+ }
83
+ )
84
+
85
+
86
+ class BoilerplateFilter:
87
+ """Filter for identifying and penalizing boilerplate code patterns.
88
+
89
+ This filter applies language-specific penalties to common boilerplate
90
+ patterns (constructors, lifecycle methods, etc.) to improve search
91
+ result relevance. It avoids filtering when the user explicitly searches
92
+ for a boilerplate pattern.
93
+
94
+ Example:
95
+ filter = BoilerplateFilter()
96
+
97
+ # Returns penalty for __init__ in Python
98
+ penalty = filter.get_penalty("__init__", "python", "search classes")
99
+
100
+ # Returns 0.0 when explicitly searching for __init__
101
+ penalty = filter.get_penalty("__init__", "python", "find __init__ methods")
102
+ """
103
+
104
+ # Default penalty for boilerplate patterns
105
+ DEFAULT_PENALTY: Final[float] = -0.15
106
+
107
+ # Mapping of language identifiers to boilerplate sets
108
+ _LANGUAGE_BOILERPLATE: Final[dict[str, frozenset[str]]] = {
109
+ "python": _PYTHON_BOILERPLATE,
110
+ "javascript": _JAVASCRIPT_TYPESCRIPT_BOILERPLATE,
111
+ "typescript": _JAVASCRIPT_TYPESCRIPT_BOILERPLATE,
112
+ "jsx": _JAVASCRIPT_TYPESCRIPT_BOILERPLATE,
113
+ "tsx": _JAVASCRIPT_TYPESCRIPT_BOILERPLATE,
114
+ "dart": _DART_BOILERPLATE,
115
+ "php": _PHP_BOILERPLATE,
116
+ "ruby": _RUBY_BOILERPLATE,
117
+ }
118
+
119
+ def is_boilerplate(self, name: str, language: str, query: str) -> bool:
120
+ """Check if a function/method name is considered boilerplate.
121
+
122
+ Args:
123
+ name: Function or method name to check
124
+ language: Programming language (e.g., "python", "javascript")
125
+ query: Original search query
126
+
127
+ Returns:
128
+ True if the name is boilerplate AND not explicitly in the query
129
+
130
+ Example:
131
+ >>> filter = BoilerplateFilter()
132
+ >>> filter.is_boilerplate("__init__", "python", "find classes")
133
+ True
134
+ >>> filter.is_boilerplate("__init__", "python", "show __init__ methods")
135
+ False
136
+ """
137
+ if not name:
138
+ return False
139
+
140
+ # Don't filter if user explicitly searched for this boilerplate
141
+ query_lower = query.lower()
142
+ name_lower = name.lower()
143
+ if name_lower in query_lower:
144
+ return False
145
+
146
+ # Check if name is in language's boilerplate set
147
+ language_lower = language.lower()
148
+ boilerplate_set = self._LANGUAGE_BOILERPLATE.get(language_lower)
149
+
150
+ if boilerplate_set is None:
151
+ # Unknown language - no filtering
152
+ return False
153
+
154
+ # Exact match (case-sensitive for languages like Python)
155
+ return name in boilerplate_set
156
+
157
+ def get_penalty(
158
+ self,
159
+ name: str,
160
+ language: str,
161
+ query: str = "",
162
+ penalty: float | None = None,
163
+ ) -> float:
164
+ """Calculate penalty for a potentially boilerplate name.
165
+
166
+ Args:
167
+ name: Function or method name to check
168
+ language: Programming language (e.g., "python", "javascript")
169
+ query: Original search query (default: "")
170
+ penalty: Custom penalty value (default: uses DEFAULT_PENALTY)
171
+
172
+ Returns:
173
+ Penalty value (negative float) if boilerplate, 0.0 otherwise
174
+
175
+ Example:
176
+ >>> filter = BoilerplateFilter()
177
+ >>> filter.get_penalty("__init__", "python", "search classes")
178
+ -0.15
179
+ >>> filter.get_penalty("__init__", "python", "find __init__")
180
+ 0.0
181
+ >>> filter.get_penalty("custom_method", "python", "search")
182
+ 0.0
183
+ """
184
+ if self.is_boilerplate(name, language, query):
185
+ return penalty if penalty is not None else self.DEFAULT_PENALTY
186
+ return 0.0