mcp-vector-search 0.12.6__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.
Files changed (92) hide show
  1. mcp_vector_search/__init__.py +3 -3
  2. mcp_vector_search/analysis/__init__.py +111 -0
  3. mcp_vector_search/analysis/baseline/__init__.py +68 -0
  4. mcp_vector_search/analysis/baseline/comparator.py +462 -0
  5. mcp_vector_search/analysis/baseline/manager.py +621 -0
  6. mcp_vector_search/analysis/collectors/__init__.py +74 -0
  7. mcp_vector_search/analysis/collectors/base.py +164 -0
  8. mcp_vector_search/analysis/collectors/cohesion.py +463 -0
  9. mcp_vector_search/analysis/collectors/complexity.py +743 -0
  10. mcp_vector_search/analysis/collectors/coupling.py +1162 -0
  11. mcp_vector_search/analysis/collectors/halstead.py +514 -0
  12. mcp_vector_search/analysis/collectors/smells.py +325 -0
  13. mcp_vector_search/analysis/debt.py +516 -0
  14. mcp_vector_search/analysis/interpretation.py +685 -0
  15. mcp_vector_search/analysis/metrics.py +414 -0
  16. mcp_vector_search/analysis/reporters/__init__.py +7 -0
  17. mcp_vector_search/analysis/reporters/console.py +646 -0
  18. mcp_vector_search/analysis/reporters/markdown.py +480 -0
  19. mcp_vector_search/analysis/reporters/sarif.py +377 -0
  20. mcp_vector_search/analysis/storage/__init__.py +93 -0
  21. mcp_vector_search/analysis/storage/metrics_store.py +762 -0
  22. mcp_vector_search/analysis/storage/schema.py +245 -0
  23. mcp_vector_search/analysis/storage/trend_tracker.py +560 -0
  24. mcp_vector_search/analysis/trends.py +308 -0
  25. mcp_vector_search/analysis/visualizer/__init__.py +90 -0
  26. mcp_vector_search/analysis/visualizer/d3_data.py +534 -0
  27. mcp_vector_search/analysis/visualizer/exporter.py +484 -0
  28. mcp_vector_search/analysis/visualizer/html_report.py +2895 -0
  29. mcp_vector_search/analysis/visualizer/schemas.py +525 -0
  30. mcp_vector_search/cli/commands/analyze.py +1062 -0
  31. mcp_vector_search/cli/commands/chat.py +1455 -0
  32. mcp_vector_search/cli/commands/index.py +621 -5
  33. mcp_vector_search/cli/commands/index_background.py +467 -0
  34. mcp_vector_search/cli/commands/init.py +13 -0
  35. mcp_vector_search/cli/commands/install.py +597 -335
  36. mcp_vector_search/cli/commands/install_old.py +8 -4
  37. mcp_vector_search/cli/commands/mcp.py +78 -6
  38. mcp_vector_search/cli/commands/reset.py +68 -26
  39. mcp_vector_search/cli/commands/search.py +224 -8
  40. mcp_vector_search/cli/commands/setup.py +1184 -0
  41. mcp_vector_search/cli/commands/status.py +339 -5
  42. mcp_vector_search/cli/commands/uninstall.py +276 -357
  43. mcp_vector_search/cli/commands/visualize/__init__.py +39 -0
  44. mcp_vector_search/cli/commands/visualize/cli.py +292 -0
  45. mcp_vector_search/cli/commands/visualize/exporters/__init__.py +12 -0
  46. mcp_vector_search/cli/commands/visualize/exporters/html_exporter.py +33 -0
  47. mcp_vector_search/cli/commands/visualize/exporters/json_exporter.py +33 -0
  48. mcp_vector_search/cli/commands/visualize/graph_builder.py +647 -0
  49. mcp_vector_search/cli/commands/visualize/layout_engine.py +469 -0
  50. mcp_vector_search/cli/commands/visualize/server.py +600 -0
  51. mcp_vector_search/cli/commands/visualize/state_manager.py +428 -0
  52. mcp_vector_search/cli/commands/visualize/templates/__init__.py +16 -0
  53. mcp_vector_search/cli/commands/visualize/templates/base.py +234 -0
  54. mcp_vector_search/cli/commands/visualize/templates/scripts.py +4542 -0
  55. mcp_vector_search/cli/commands/visualize/templates/styles.py +2522 -0
  56. mcp_vector_search/cli/didyoumean.py +27 -2
  57. mcp_vector_search/cli/main.py +127 -160
  58. mcp_vector_search/cli/output.py +158 -13
  59. mcp_vector_search/config/__init__.py +4 -0
  60. mcp_vector_search/config/default_thresholds.yaml +52 -0
  61. mcp_vector_search/config/settings.py +12 -0
  62. mcp_vector_search/config/thresholds.py +273 -0
  63. mcp_vector_search/core/__init__.py +16 -0
  64. mcp_vector_search/core/auto_indexer.py +3 -3
  65. mcp_vector_search/core/boilerplate.py +186 -0
  66. mcp_vector_search/core/config_utils.py +394 -0
  67. mcp_vector_search/core/database.py +406 -94
  68. mcp_vector_search/core/embeddings.py +24 -0
  69. mcp_vector_search/core/exceptions.py +11 -0
  70. mcp_vector_search/core/git.py +380 -0
  71. mcp_vector_search/core/git_hooks.py +4 -4
  72. mcp_vector_search/core/indexer.py +632 -54
  73. mcp_vector_search/core/llm_client.py +756 -0
  74. mcp_vector_search/core/models.py +91 -1
  75. mcp_vector_search/core/project.py +17 -0
  76. mcp_vector_search/core/relationships.py +473 -0
  77. mcp_vector_search/core/scheduler.py +11 -11
  78. mcp_vector_search/core/search.py +179 -29
  79. mcp_vector_search/mcp/server.py +819 -9
  80. mcp_vector_search/parsers/python.py +285 -5
  81. mcp_vector_search/utils/__init__.py +2 -0
  82. mcp_vector_search/utils/gitignore.py +0 -3
  83. mcp_vector_search/utils/gitignore_updater.py +212 -0
  84. mcp_vector_search/utils/monorepo.py +66 -4
  85. mcp_vector_search/utils/timing.py +10 -6
  86. {mcp_vector_search-0.12.6.dist-info → mcp_vector_search-1.1.22.dist-info}/METADATA +184 -53
  87. mcp_vector_search-1.1.22.dist-info/RECORD +120 -0
  88. {mcp_vector_search-0.12.6.dist-info → mcp_vector_search-1.1.22.dist-info}/WHEEL +1 -1
  89. {mcp_vector_search-0.12.6.dist-info → mcp_vector_search-1.1.22.dist-info}/entry_points.txt +1 -0
  90. mcp_vector_search/cli/commands/visualize.py +0 -1467
  91. mcp_vector_search-0.12.6.dist-info/RECORD +0 -68
  92. {mcp_vector_search-0.12.6.dist-info → mcp_vector_search-1.1.22.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,52 @@
1
+ # Code Quality Threshold Configuration
2
+ # Customize these values to match your project's standards
3
+
4
+ complexity:
5
+ # Cognitive complexity grade thresholds
6
+ cognitive_a: 5 # Grade A: 0-5 (excellent)
7
+ cognitive_b: 10 # Grade B: 6-10 (good)
8
+ cognitive_c: 20 # Grade C: 11-20 (acceptable)
9
+ cognitive_d: 30 # Grade D: 21-30 (needs improvement)
10
+ # Grade F: 31+ (refactor recommended)
11
+
12
+ # Cyclomatic complexity thresholds
13
+ cyclomatic_low: 4 # Low: 1-4
14
+ cyclomatic_moderate: 10 # Moderate: 5-10
15
+ cyclomatic_high: 20 # High: 11-20
16
+ # Very high: 21+
17
+
18
+ # Nesting depth thresholds
19
+ nesting_warning: 3 # Warning level
20
+ nesting_error: 5 # Error level
21
+
22
+ # Parameter count thresholds
23
+ parameters_warning: 4 # Warning level
24
+ parameters_error: 7 # Error level
25
+
26
+ # Method count thresholds (per class)
27
+ methods_warning: 10 # Warning level
28
+ methods_error: 20 # Error level
29
+
30
+ smells:
31
+ # Long method (lines of code)
32
+ long_method_lines: 50
33
+
34
+ # Too many parameters
35
+ too_many_parameters: 5
36
+
37
+ # Deep nesting
38
+ deep_nesting_depth: 4
39
+
40
+ # High complexity
41
+ high_complexity: 15
42
+
43
+ # God class (too many methods)
44
+ god_class_methods: 20
45
+
46
+ # Feature envy (external calls)
47
+ feature_envy_external_calls: 5
48
+
49
+ # Quality gate settings
50
+ fail_on_f_grade: true # Fail quality gate on F grade
51
+ fail_on_smell_count: 10 # Fail if more than N smells total
52
+ warn_on_d_grade: true # Warn on D grade
@@ -49,6 +49,18 @@ class ProjectConfig(BaseSettings):
49
49
  default=True,
50
50
  description="Respect .gitignore patterns when indexing files",
51
51
  )
52
+ openrouter_api_key: str | None = Field(
53
+ default=None,
54
+ description="OpenRouter API key for chat command (optional, can also use env var)",
55
+ )
56
+ openai_api_key: str | None = Field(
57
+ default=None,
58
+ description="OpenAI API key for chat command (optional, can also use env var)",
59
+ )
60
+ preferred_llm_provider: str | None = Field(
61
+ default=None,
62
+ description="Preferred LLM provider: 'openai' or 'openrouter' (auto-detect if not set)",
63
+ )
52
64
 
53
65
  @field_validator("project_root", "index_path", mode="before")
54
66
  @classmethod
@@ -0,0 +1,273 @@
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 and lines)
59
+ god_class_methods: int = 20
60
+ god_class_lines: int = 500
61
+
62
+ # Feature envy (placeholder for future)
63
+ feature_envy_external_calls: int = 5
64
+
65
+
66
+ @dataclass
67
+ class CouplingThresholds:
68
+ """Thresholds for coupling and instability metrics."""
69
+
70
+ # Efferent coupling (Ce) thresholds
71
+ efferent_low: int = 3 # Low coupling (0-3 dependencies)
72
+ efferent_moderate: int = 7 # Moderate coupling (4-7)
73
+ efferent_high: int = 12 # High coupling (8-12)
74
+ # Very high: 13+
75
+
76
+ # Afferent coupling (Ca) thresholds
77
+ afferent_low: int = 2 # Low coupling (0-2 dependents)
78
+ afferent_moderate: int = 5 # Moderate coupling (3-5)
79
+ afferent_high: int = 10 # High coupling (6-10)
80
+ # Very high: 11+
81
+
82
+ # Instability (I) thresholds for grades
83
+ instability_a: float = 0.2 # A grade: very stable (0.0-0.2)
84
+ instability_b: float = 0.4 # B grade: stable (0.2-0.4)
85
+ instability_c: float = 0.6 # C grade: balanced (0.4-0.6)
86
+ instability_d: float = 0.8 # D grade: unstable (0.6-0.8)
87
+ # F grade: very unstable (0.8-1.0)
88
+
89
+ # Category thresholds
90
+ stable_max: float = 0.3 # Stable category (0.0-0.3)
91
+ balanced_max: float = 0.7 # Balanced category (0.3-0.7)
92
+ # Unstable category: 0.7-1.0
93
+
94
+
95
+ @dataclass
96
+ class ThresholdConfig:
97
+ """Complete threshold configuration."""
98
+
99
+ complexity: ComplexityThresholds = field(default_factory=ComplexityThresholds)
100
+ smells: SmellThresholds = field(default_factory=SmellThresholds)
101
+ coupling: CouplingThresholds = field(default_factory=CouplingThresholds)
102
+
103
+ # Quality gate settings
104
+ fail_on_f_grade: bool = True
105
+ fail_on_smell_count: int = 10 # Fail if more than N smells
106
+ warn_on_d_grade: bool = True
107
+
108
+ @classmethod
109
+ def load(cls, path: Path) -> ThresholdConfig:
110
+ """Load configuration from YAML file.
111
+
112
+ Args:
113
+ path: Path to YAML configuration file
114
+
115
+ Returns:
116
+ ThresholdConfig instance
117
+ """
118
+ if not path.exists():
119
+ return cls()
120
+
121
+ with open(path) as f:
122
+ data = yaml.safe_load(f) or {}
123
+
124
+ return cls.from_dict(data)
125
+
126
+ @classmethod
127
+ def from_dict(cls, data: dict[str, Any]) -> ThresholdConfig:
128
+ """Create config from dictionary.
129
+
130
+ Args:
131
+ data: Configuration dictionary
132
+
133
+ Returns:
134
+ ThresholdConfig instance
135
+ """
136
+ complexity_data = data.get("complexity", {})
137
+ smells_data = data.get("smells", {})
138
+ coupling_data = data.get("coupling", {})
139
+
140
+ return cls(
141
+ complexity=(
142
+ ComplexityThresholds(**complexity_data)
143
+ if complexity_data
144
+ else ComplexityThresholds()
145
+ ),
146
+ smells=(
147
+ SmellThresholds(**smells_data) if smells_data else SmellThresholds()
148
+ ),
149
+ coupling=(
150
+ CouplingThresholds(**coupling_data)
151
+ if coupling_data
152
+ else CouplingThresholds()
153
+ ),
154
+ fail_on_f_grade=data.get("fail_on_f_grade", True),
155
+ fail_on_smell_count=data.get("fail_on_smell_count", 10),
156
+ warn_on_d_grade=data.get("warn_on_d_grade", True),
157
+ )
158
+
159
+ def to_dict(self) -> dict[str, Any]:
160
+ """Convert to dictionary for serialization.
161
+
162
+ Returns:
163
+ Dictionary representation
164
+ """
165
+ return {
166
+ "complexity": {
167
+ "cognitive_a": self.complexity.cognitive_a,
168
+ "cognitive_b": self.complexity.cognitive_b,
169
+ "cognitive_c": self.complexity.cognitive_c,
170
+ "cognitive_d": self.complexity.cognitive_d,
171
+ "cyclomatic_low": self.complexity.cyclomatic_low,
172
+ "cyclomatic_moderate": self.complexity.cyclomatic_moderate,
173
+ "cyclomatic_high": self.complexity.cyclomatic_high,
174
+ "nesting_warning": self.complexity.nesting_warning,
175
+ "nesting_error": self.complexity.nesting_error,
176
+ "parameters_warning": self.complexity.parameters_warning,
177
+ "parameters_error": self.complexity.parameters_error,
178
+ "methods_warning": self.complexity.methods_warning,
179
+ "methods_error": self.complexity.methods_error,
180
+ },
181
+ "smells": {
182
+ "long_method_lines": self.smells.long_method_lines,
183
+ "too_many_parameters": self.smells.too_many_parameters,
184
+ "deep_nesting_depth": self.smells.deep_nesting_depth,
185
+ "high_complexity": self.smells.high_complexity,
186
+ "god_class_methods": self.smells.god_class_methods,
187
+ "god_class_lines": self.smells.god_class_lines,
188
+ "feature_envy_external_calls": self.smells.feature_envy_external_calls,
189
+ },
190
+ "coupling": {
191
+ "efferent_low": self.coupling.efferent_low,
192
+ "efferent_moderate": self.coupling.efferent_moderate,
193
+ "efferent_high": self.coupling.efferent_high,
194
+ "afferent_low": self.coupling.afferent_low,
195
+ "afferent_moderate": self.coupling.afferent_moderate,
196
+ "afferent_high": self.coupling.afferent_high,
197
+ "instability_a": self.coupling.instability_a,
198
+ "instability_b": self.coupling.instability_b,
199
+ "instability_c": self.coupling.instability_c,
200
+ "instability_d": self.coupling.instability_d,
201
+ "stable_max": self.coupling.stable_max,
202
+ "balanced_max": self.coupling.balanced_max,
203
+ },
204
+ "fail_on_f_grade": self.fail_on_f_grade,
205
+ "fail_on_smell_count": self.fail_on_smell_count,
206
+ "warn_on_d_grade": self.warn_on_d_grade,
207
+ }
208
+
209
+ def save(self, path: Path) -> None:
210
+ """Save configuration to YAML file.
211
+
212
+ Args:
213
+ path: Path to save configuration
214
+ """
215
+ path.parent.mkdir(parents=True, exist_ok=True)
216
+ with open(path, "w") as f:
217
+ yaml.dump(self.to_dict(), f, default_flow_style=False, sort_keys=False)
218
+
219
+ def get_grade(self, cognitive_complexity: int) -> str:
220
+ """Get complexity grade based on cognitive complexity.
221
+
222
+ Args:
223
+ cognitive_complexity: Cognitive complexity value
224
+
225
+ Returns:
226
+ Grade from A to F
227
+ """
228
+ if cognitive_complexity <= self.complexity.cognitive_a:
229
+ return "A"
230
+ elif cognitive_complexity <= self.complexity.cognitive_b:
231
+ return "B"
232
+ elif cognitive_complexity <= self.complexity.cognitive_c:
233
+ return "C"
234
+ elif cognitive_complexity <= self.complexity.cognitive_d:
235
+ return "D"
236
+ else:
237
+ return "F"
238
+
239
+ def get_instability_grade(self, instability: float) -> str:
240
+ """Get instability grade based on instability value.
241
+
242
+ Args:
243
+ instability: Instability value (0.0-1.0)
244
+
245
+ Returns:
246
+ Grade from A to F
247
+ """
248
+ if instability <= self.coupling.instability_a:
249
+ return "A"
250
+ elif instability <= self.coupling.instability_b:
251
+ return "B"
252
+ elif instability <= self.coupling.instability_c:
253
+ return "C"
254
+ elif instability <= self.coupling.instability_d:
255
+ return "D"
256
+ else:
257
+ return "F"
258
+
259
+ def get_stability_category(self, instability: float) -> str:
260
+ """Get stability category based on instability value.
261
+
262
+ Args:
263
+ instability: Instability value (0.0-1.0)
264
+
265
+ Returns:
266
+ Category: "Stable", "Balanced", or "Unstable"
267
+ """
268
+ if instability <= self.coupling.stable_max:
269
+ return "Stable"
270
+ elif instability <= self.coupling.balanced_max:
271
+ return "Balanced"
272
+ else:
273
+ return "Unstable"
@@ -1 +1,17 @@
1
1
  """Core functionality for MCP Vector Search."""
2
+
3
+ from mcp_vector_search.core.git import (
4
+ GitError,
5
+ GitManager,
6
+ GitNotAvailableError,
7
+ GitNotRepoError,
8
+ GitReferenceError,
9
+ )
10
+
11
+ __all__ = [
12
+ "GitError",
13
+ "GitManager",
14
+ "GitNotAvailableError",
15
+ "GitNotRepoError",
16
+ "GitReferenceError",
17
+ ]
@@ -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