mcp-vector-search 0.0.3__py3-none-any.whl → 0.4.11__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.

Files changed (49) hide show
  1. mcp_vector_search/__init__.py +3 -2
  2. mcp_vector_search/cli/commands/auto_index.py +397 -0
  3. mcp_vector_search/cli/commands/config.py +88 -40
  4. mcp_vector_search/cli/commands/index.py +198 -52
  5. mcp_vector_search/cli/commands/init.py +472 -58
  6. mcp_vector_search/cli/commands/install.py +284 -0
  7. mcp_vector_search/cli/commands/mcp.py +495 -0
  8. mcp_vector_search/cli/commands/search.py +241 -87
  9. mcp_vector_search/cli/commands/status.py +184 -58
  10. mcp_vector_search/cli/commands/watch.py +34 -35
  11. mcp_vector_search/cli/didyoumean.py +184 -0
  12. mcp_vector_search/cli/export.py +320 -0
  13. mcp_vector_search/cli/history.py +292 -0
  14. mcp_vector_search/cli/interactive.py +342 -0
  15. mcp_vector_search/cli/main.py +163 -26
  16. mcp_vector_search/cli/output.py +63 -45
  17. mcp_vector_search/config/defaults.py +50 -36
  18. mcp_vector_search/config/settings.py +49 -35
  19. mcp_vector_search/core/auto_indexer.py +298 -0
  20. mcp_vector_search/core/connection_pool.py +322 -0
  21. mcp_vector_search/core/database.py +335 -25
  22. mcp_vector_search/core/embeddings.py +73 -29
  23. mcp_vector_search/core/exceptions.py +19 -2
  24. mcp_vector_search/core/factory.py +310 -0
  25. mcp_vector_search/core/git_hooks.py +345 -0
  26. mcp_vector_search/core/indexer.py +237 -73
  27. mcp_vector_search/core/models.py +21 -19
  28. mcp_vector_search/core/project.py +73 -58
  29. mcp_vector_search/core/scheduler.py +330 -0
  30. mcp_vector_search/core/search.py +574 -86
  31. mcp_vector_search/core/watcher.py +48 -46
  32. mcp_vector_search/mcp/__init__.py +4 -0
  33. mcp_vector_search/mcp/__main__.py +25 -0
  34. mcp_vector_search/mcp/server.py +701 -0
  35. mcp_vector_search/parsers/base.py +30 -31
  36. mcp_vector_search/parsers/javascript.py +74 -48
  37. mcp_vector_search/parsers/python.py +57 -49
  38. mcp_vector_search/parsers/registry.py +47 -32
  39. mcp_vector_search/parsers/text.py +179 -0
  40. mcp_vector_search/utils/__init__.py +40 -0
  41. mcp_vector_search/utils/gitignore.py +229 -0
  42. mcp_vector_search/utils/timing.py +334 -0
  43. mcp_vector_search/utils/version.py +47 -0
  44. {mcp_vector_search-0.0.3.dist-info → mcp_vector_search-0.4.11.dist-info}/METADATA +173 -7
  45. mcp_vector_search-0.4.11.dist-info/RECORD +54 -0
  46. mcp_vector_search-0.0.3.dist-info/RECORD +0 -35
  47. {mcp_vector_search-0.0.3.dist-info → mcp_vector_search-0.4.11.dist-info}/WHEEL +0 -0
  48. {mcp_vector_search-0.0.3.dist-info → mcp_vector_search-0.4.11.dist-info}/entry_points.txt +0 -0
  49. {mcp_vector_search-0.0.3.dist-info → mcp_vector_search-0.4.11.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,345 @@
1
+ """Git hooks for automatic reindexing."""
2
+
3
+ import os
4
+ import subprocess
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ from loguru import logger
9
+
10
+
11
+ class GitHookManager:
12
+ """Manages Git hooks for automatic reindexing."""
13
+
14
+ def __init__(self, project_root: Path):
15
+ """Initialize Git hook manager.
16
+
17
+ Args:
18
+ project_root: Project root directory
19
+ """
20
+ self.project_root = project_root
21
+ self.git_dir = project_root / ".git"
22
+ self.hooks_dir = self.git_dir / "hooks"
23
+
24
+ def is_git_repo(self) -> bool:
25
+ """Check if project is a Git repository."""
26
+ return self.git_dir.exists() and self.git_dir.is_dir()
27
+
28
+ def install_hooks(self, hook_types: list[str] | None = None) -> bool:
29
+ """Install Git hooks for automatic reindexing.
30
+
31
+ Args:
32
+ hook_types: List of hook types to install (default: ['post-commit', 'post-merge'])
33
+
34
+ Returns:
35
+ True if hooks were installed successfully
36
+ """
37
+ if not self.is_git_repo():
38
+ logger.error("Not a Git repository")
39
+ return False
40
+
41
+ if hook_types is None:
42
+ hook_types = ["post-commit", "post-merge", "post-checkout"]
43
+
44
+ success = True
45
+ for hook_type in hook_types:
46
+ if not self._install_hook(hook_type):
47
+ success = False
48
+
49
+ return success
50
+
51
+ def uninstall_hooks(self, hook_types: list[str] | None = None) -> bool:
52
+ """Uninstall Git hooks.
53
+
54
+ Args:
55
+ hook_types: List of hook types to uninstall (default: all MCP hooks)
56
+
57
+ Returns:
58
+ True if hooks were uninstalled successfully
59
+ """
60
+ if hook_types is None:
61
+ hook_types = ["post-commit", "post-merge", "post-checkout"]
62
+
63
+ success = True
64
+ for hook_type in hook_types:
65
+ if not self._uninstall_hook(hook_type):
66
+ success = False
67
+
68
+ return success
69
+
70
+ def _install_hook(self, hook_type: str) -> bool:
71
+ """Install a specific Git hook."""
72
+ try:
73
+ hook_file = self.hooks_dir / hook_type
74
+
75
+ # Create hooks directory if it doesn't exist
76
+ self.hooks_dir.mkdir(exist_ok=True)
77
+
78
+ # Generate hook script
79
+ hook_script = self._generate_hook_script(hook_type)
80
+
81
+ if hook_file.exists():
82
+ # If hook already exists, try to integrate with it
83
+ return self._integrate_with_existing_hook(hook_file, hook_script)
84
+ else:
85
+ # Create new hook
86
+ hook_file.write_text(hook_script)
87
+ hook_file.chmod(0o755) # Make executable
88
+ logger.info(f"Installed {hook_type} hook")
89
+ return True
90
+
91
+ except Exception as e:
92
+ logger.error(f"Failed to install {hook_type} hook: {e}")
93
+ return False
94
+
95
+ def _uninstall_hook(self, hook_type: str) -> bool:
96
+ """Uninstall a specific Git hook."""
97
+ try:
98
+ hook_file = self.hooks_dir / hook_type
99
+
100
+ if not hook_file.exists():
101
+ return True # Already uninstalled
102
+
103
+ content = hook_file.read_text()
104
+
105
+ # Check if this is our hook or integrated
106
+ if "# MCP Vector Search Hook" in content:
107
+ if (
108
+ content.strip().startswith("#!/bin/bash")
109
+ and "# MCP Vector Search Hook" in content
110
+ ):
111
+ # This is our hook, remove it
112
+ hook_file.unlink()
113
+ logger.info(f"Uninstalled {hook_type} hook")
114
+ else:
115
+ # This is integrated, remove our part
116
+ return self._remove_from_existing_hook(hook_file)
117
+
118
+ return True
119
+
120
+ except Exception as e:
121
+ logger.error(f"Failed to uninstall {hook_type} hook: {e}")
122
+ return False
123
+
124
+ def _generate_hook_script(self, hook_type: str) -> str:
125
+ """Generate Git hook script."""
126
+ python_path = sys.executable
127
+ project_root = str(self.project_root)
128
+
129
+ script = f'''#!/bin/bash
130
+ # MCP Vector Search Hook - {hook_type}
131
+ # Auto-generated - do not edit manually
132
+
133
+ # Check if mcp-vector-search is available
134
+ if ! command -v mcp-vector-search &> /dev/null; then
135
+ # Try using Python directly
136
+ if [ -f "{python_path}" ]; then
137
+ PYTHON_CMD="{python_path}"
138
+ else
139
+ PYTHON_CMD="python3"
140
+ fi
141
+
142
+ # Try to run via Python module
143
+ if $PYTHON_CMD -m mcp_vector_search --help &> /dev/null; then
144
+ MCP_CMD="$PYTHON_CMD -m mcp_vector_search"
145
+ else
146
+ # Silently exit if not available
147
+ exit 0
148
+ fi
149
+ else
150
+ MCP_CMD="mcp-vector-search"
151
+ fi
152
+
153
+ # Change to project directory
154
+ cd "{project_root}" || exit 0
155
+
156
+ # Run auto-indexing check
157
+ $MCP_CMD auto-index check --auto-reindex --max-files 10 &> /dev/null || true
158
+
159
+ # Exit successfully (don't block Git operations)
160
+ exit 0
161
+ '''
162
+ return script
163
+
164
+ def _integrate_with_existing_hook(self, hook_file: Path, our_script: str) -> bool:
165
+ """Integrate our hook with an existing hook."""
166
+ try:
167
+ existing_content = hook_file.read_text()
168
+
169
+ # Check if our hook is already integrated
170
+ if "# MCP Vector Search Hook" in existing_content:
171
+ logger.info(f"Hook {hook_file.name} already integrated")
172
+ return True
173
+
174
+ # Add our hook to the end
175
+ integrated_content = existing_content.rstrip() + "\n\n" + our_script
176
+
177
+ # Backup original
178
+ backup_file = hook_file.with_suffix(hook_file.suffix + ".backup")
179
+ backup_file.write_text(existing_content)
180
+
181
+ # Write integrated version
182
+ hook_file.write_text(integrated_content)
183
+
184
+ logger.info(f"Integrated with existing {hook_file.name} hook")
185
+ return True
186
+
187
+ except Exception as e:
188
+ logger.error(f"Failed to integrate with existing hook: {e}")
189
+ return False
190
+
191
+ def _remove_from_existing_hook(self, hook_file: Path) -> bool:
192
+ """Remove our hook from an existing integrated hook."""
193
+ try:
194
+ content = hook_file.read_text()
195
+
196
+ # Find and remove our section
197
+ lines = content.split("\n")
198
+ new_lines = []
199
+ skip_section = False
200
+
201
+ for line in lines:
202
+ if "# MCP Vector Search Hook" in line:
203
+ skip_section = True
204
+ continue
205
+ elif skip_section and line.strip() == "":
206
+ # End of our section
207
+ skip_section = False
208
+ continue
209
+ elif not skip_section:
210
+ new_lines.append(line)
211
+
212
+ # Write back the cleaned content
213
+ hook_file.write_text("\n".join(new_lines))
214
+
215
+ logger.info(f"Removed MCP hook from {hook_file.name}")
216
+ return True
217
+
218
+ except Exception as e:
219
+ logger.error(f"Failed to remove from existing hook: {e}")
220
+ return False
221
+
222
+ def get_hook_status(self) -> dict:
223
+ """Get status of Git hooks."""
224
+ if not self.is_git_repo():
225
+ return {"is_git_repo": False}
226
+
227
+ hook_types = ["post-commit", "post-merge", "post-checkout"]
228
+ status = {
229
+ "is_git_repo": True,
230
+ "hooks_dir_exists": self.hooks_dir.exists(),
231
+ "hooks": {},
232
+ }
233
+
234
+ for hook_type in hook_types:
235
+ hook_file = self.hooks_dir / hook_type
236
+ hook_status = {
237
+ "exists": hook_file.exists(),
238
+ "executable": False,
239
+ "has_mcp_hook": False,
240
+ "is_mcp_only": False,
241
+ }
242
+
243
+ if hook_file.exists():
244
+ try:
245
+ hook_status["executable"] = os.access(hook_file, os.X_OK)
246
+ content = hook_file.read_text()
247
+ hook_status["has_mcp_hook"] = "# MCP Vector Search Hook" in content
248
+ hook_status["is_mcp_only"] = (
249
+ hook_status["has_mcp_hook"]
250
+ and content.strip().startswith("#!/bin/bash")
251
+ and content.count("# MCP Vector Search Hook") == 1
252
+ )
253
+ except Exception:
254
+ pass
255
+
256
+ status["hooks"][hook_type] = hook_status
257
+
258
+ return status
259
+
260
+
261
+ class GitChangeDetector:
262
+ """Detects changed files from Git operations."""
263
+
264
+ @staticmethod
265
+ def get_changed_files_since_commit(
266
+ commit_hash: str, project_root: Path
267
+ ) -> set[Path]:
268
+ """Get files changed since a specific commit.
269
+
270
+ Args:
271
+ commit_hash: Git commit hash
272
+ project_root: Project root directory
273
+
274
+ Returns:
275
+ Set of changed file paths
276
+ """
277
+ try:
278
+ result = subprocess.run(
279
+ ["git", "diff", "--name-only", commit_hash, "HEAD"],
280
+ cwd=project_root,
281
+ capture_output=True,
282
+ text=True,
283
+ check=True,
284
+ )
285
+
286
+ changed_files = set()
287
+ for line in result.stdout.strip().split("\n"):
288
+ if line:
289
+ file_path = project_root / line
290
+ if file_path.exists():
291
+ changed_files.add(file_path)
292
+
293
+ return changed_files
294
+
295
+ except subprocess.CalledProcessError:
296
+ return set()
297
+
298
+ @staticmethod
299
+ def get_changed_files_in_last_commit(project_root: Path) -> set[Path]:
300
+ """Get files changed in the last commit.
301
+
302
+ Args:
303
+ project_root: Project root directory
304
+
305
+ Returns:
306
+ Set of changed file paths
307
+ """
308
+ try:
309
+ result = subprocess.run(
310
+ ["git", "diff", "--name-only", "HEAD~1", "HEAD"],
311
+ cwd=project_root,
312
+ capture_output=True,
313
+ text=True,
314
+ check=True,
315
+ )
316
+
317
+ changed_files = set()
318
+ for line in result.stdout.strip().split("\n"):
319
+ if line:
320
+ file_path = project_root / line
321
+ if file_path.exists():
322
+ changed_files.add(file_path)
323
+
324
+ return changed_files
325
+
326
+ except subprocess.CalledProcessError:
327
+ return set()
328
+
329
+ @staticmethod
330
+ def should_trigger_reindex(
331
+ changed_files: set[Path], file_extensions: list[str]
332
+ ) -> bool:
333
+ """Check if changed files should trigger reindexing.
334
+
335
+ Args:
336
+ changed_files: Set of changed file paths
337
+ file_extensions: List of file extensions to monitor
338
+
339
+ Returns:
340
+ True if reindexing should be triggered
341
+ """
342
+ for file_path in changed_files:
343
+ if file_path.suffix in file_extensions:
344
+ return True
345
+ return False