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

@@ -28,7 +28,9 @@ from .commands.search import (
28
28
  )
29
29
  from .commands.status import status_app
30
30
  from .commands.watch import app as watch_app
31
+ from .commands.reset import reset_app, health_main
31
32
  from .didyoumean import create_enhanced_typer, add_common_suggestions
33
+ from .suggestions import get_contextual_suggestions, ContextualSuggestionProvider
32
34
  from .output import print_error, setup_logging
33
35
 
34
36
  # Install rich traceback handler
@@ -45,9 +47,11 @@ app = create_enhanced_typer(
45
47
  rich_markup_mode="rich",
46
48
  )
47
49
 
48
- # Add install command directly (not as subcommand app)
50
+ # Import command functions for direct registration and aliases
49
51
  from .commands.install import main as install_main, demo as install_demo
50
52
  from .commands.status import main as status_main
53
+ from .commands.index import main as index_main
54
+ # Note: config doesn't have a main function, it uses subcommands via config_app
51
55
  app.command("install", help="🚀 Install mcp-vector-search in projects")(install_main)
52
56
  app.command("demo", help="🎬 Run installation demo with sample project")(install_demo)
53
57
  app.command("status", help="📊 Show project status and statistics")(status_main)
@@ -62,6 +66,7 @@ app.add_typer(config_app, name="config", help="Manage project configuration")
62
66
  app.add_typer(watch_app, name="watch", help="Watch for file changes and update index")
63
67
  app.add_typer(auto_index_app, name="auto-index", help="Manage automatic indexing")
64
68
  app.add_typer(mcp_app, name="mcp", help="Manage Claude Code MCP integration")
69
+ app.add_typer(reset_app, name="reset", help="Reset and recovery operations")
65
70
 
66
71
  # Add search command - simplified syntax as default
67
72
  app.command("search", help="Search code semantically")(search_main)
@@ -69,13 +74,34 @@ app.command("search", help="Search code semantically")(search_main)
69
74
  # Keep old nested structure for backward compatibility
70
75
  app.add_typer(search_app, name="search-legacy", help="Legacy search commands", hidden=True)
71
76
  app.add_typer(status_app, name="status-legacy", help="Legacy status commands", hidden=True)
77
+
78
+ # Add command aliases for better user experience
72
79
  app.command("find", help="Search code semantically (alias for search)")(search_main)
80
+ app.command("f", help="Search code semantically (short alias)", hidden=True)(search_main) # Hidden short alias
81
+ app.command("s", help="Search code semantically (short alias)", hidden=True)(search_main) # Hidden short alias
82
+ app.command("query", help="Search code semantically (alias for search)", hidden=True)(search_main) # Hidden alias
83
+
84
+ # Index aliases
85
+ app.command("i", help="Index codebase (short alias)", hidden=True)(index_main) # Hidden short alias
86
+ app.command("build", help="Index codebase (alias for index)", hidden=True)(index_main) # Hidden alias
87
+ app.command("scan", help="Index codebase (alias for index)", hidden=True)(index_main) # Hidden alias
88
+
89
+ # Status aliases
90
+ app.command("st", help="Show status (short alias)", hidden=True)(status_main) # Hidden short alias
91
+ app.command("info", help="Show project information (alias for status)", hidden=True)(status_main) # Hidden alias
92
+
93
+ # Config aliases - Since config uses subcommands, these will be handled by the enhanced typer error resolution
94
+ # app.command("c", help="Manage configuration (short alias)", hidden=True) # Will be handled by typo resolution
95
+ # app.command("cfg", help="Manage configuration (alias for config)", hidden=True) # Will be handled by typo resolution
96
+
97
+ # Specialized search commands
73
98
  app.command("search-similar", help="Find code similar to a specific file or function")(
74
99
  search_similar_cmd
75
100
  )
76
101
  app.command("search-context", help="Search for code based on contextual description")(
77
102
  search_context_cmd
78
103
  )
104
+ app.command("health", help="Check index health and optionally repair")(health_main)
79
105
 
80
106
 
81
107
  # Add interactive search command
@@ -208,6 +234,8 @@ def main(
208
234
  if project_root:
209
235
  logger.info(f"Using project root: {project_root}")
210
236
 
237
+ # Note: Contextual help moved to a separate command to avoid interfering with didyoumean
238
+
211
239
 
212
240
  @app.command()
213
241
  def version() -> None:
@@ -220,7 +248,7 @@ def version() -> None:
220
248
 
221
249
 
222
250
  def handle_command_error(ctx, param, value):
223
- """Handle command errors with suggestions."""
251
+ """Handle command errors with enhanced suggestions."""
224
252
  if ctx.resilient_parsing:
225
253
  return
226
254
 
@@ -235,10 +263,34 @@ def handle_command_error(ctx, param, value):
235
263
  match = re.search(r"No such command '([^']+)'", str(e))
236
264
  if match:
237
265
  command_name = match.group(1)
266
+
267
+ # Use both the original suggestions and contextual suggestions
238
268
  add_common_suggestions(ctx, command_name)
269
+
270
+ # Add contextual suggestions based on project state
271
+ try:
272
+ project_root = ctx.obj.get("project_root") if ctx.obj else None
273
+ get_contextual_suggestions(project_root, command_name)
274
+ except Exception:
275
+ # If contextual suggestions fail, don't break the error flow
276
+ pass
239
277
  raise
240
278
 
241
279
 
280
+
281
+
282
+ @app.command()
283
+ def help_contextual() -> None:
284
+ """Show contextual help and suggestions based on project state."""
285
+ try:
286
+ project_root = Path.cwd()
287
+ console.print(f"[bold blue]mcp-vector-search[/bold blue] version [green]{__version__}[/green]")
288
+ console.print("[dim]CLI-first semantic code search with MCP integration[/dim]")
289
+ get_contextual_suggestions(project_root)
290
+ except Exception as e:
291
+ console.print("\n[dim]Use [bold]mcp-vector-search --help[/bold] for more information.[/dim]")
292
+
293
+
242
294
  @app.command()
243
295
  def doctor() -> None:
244
296
  """Check system dependencies and configuration."""
@@ -261,5 +313,54 @@ def doctor() -> None:
261
313
 
262
314
 
263
315
 
316
+ def cli_with_suggestions():
317
+ """CLI wrapper that catches errors and provides suggestions."""
318
+ import sys
319
+ import click
320
+
321
+ try:
322
+ # Call the app with standalone_mode=False to get exceptions instead of sys.exit
323
+ app(standalone_mode=False)
324
+ except click.UsageError as e:
325
+ # Check if it's a "No such command" error
326
+ if "No such command" in str(e):
327
+ # Extract the command name from the error
328
+ import re
329
+ match = re.search(r"No such command '([^']+)'", str(e))
330
+ if match:
331
+ command_name = match.group(1)
332
+
333
+ # Show enhanced suggestions
334
+ from rich.console import Console
335
+ console = Console(stderr=True)
336
+ console.print(f"\\n[red]Error:[/red] {e}")
337
+
338
+ # Show enhanced suggestions
339
+ add_common_suggestions(None, command_name)
340
+
341
+ # Show contextual suggestions too
342
+ try:
343
+ project_root = Path.cwd()
344
+ get_contextual_suggestions(project_root, command_name)
345
+ except Exception:
346
+ pass
347
+
348
+ sys.exit(2) # Exit with error code
349
+
350
+ # For other usage errors, show the default message and exit
351
+ click.echo(f"Error: {e}", err=True)
352
+ sys.exit(2)
353
+ except click.Abort:
354
+ # User interrupted (Ctrl+C)
355
+ sys.exit(1)
356
+ except SystemExit as e:
357
+ # Re-raise system exits
358
+ raise
359
+ except Exception as e:
360
+ # For other exceptions, show error and exit
361
+ click.echo(f"Unexpected error: {e}", err=True)
362
+ sys.exit(1)
363
+
364
+
264
365
  if __name__ == "__main__":
265
- app()
366
+ cli_with_suggestions()
@@ -0,0 +1,325 @@
1
+ """Contextual suggestion system for better user experience."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Dict, List, Optional, Set, Tuple
6
+ from rich.console import Console
7
+
8
+ from ..core.project import ProjectManager
9
+ from ..core.exceptions import ProjectNotFoundError
10
+
11
+
12
+ class ContextualSuggestionProvider:
13
+ """Provides context-aware suggestions based on project state and user workflow."""
14
+
15
+ def __init__(self, project_root: Optional[Path] = None):
16
+ """Initialize the suggestion provider.
17
+
18
+ Args:
19
+ project_root: Root directory of the project (defaults to current directory)
20
+ """
21
+ self.project_root = project_root or Path.cwd()
22
+ self.console = Console(stderr=True)
23
+
24
+ def get_project_state(self) -> Dict[str, bool]:
25
+ """Analyze the current project state.
26
+
27
+ Returns:
28
+ Dictionary with boolean flags indicating project state
29
+ """
30
+ state = {
31
+ 'is_initialized': False,
32
+ 'has_index': False,
33
+ 'has_config': False,
34
+ 'has_recent_changes': False,
35
+ 'is_git_repo': False,
36
+ 'has_mcp_config': False
37
+ }
38
+
39
+ try:
40
+ # Check if project is initialized
41
+ config_dir = self.project_root / '.mcp-vector-search'
42
+ state['is_initialized'] = config_dir.exists()
43
+
44
+ if state['is_initialized']:
45
+ # Check for config
46
+ config_file = config_dir / 'config.json'
47
+ state['has_config'] = config_file.exists()
48
+
49
+ # Check for index
50
+ index_dir = config_dir / 'chroma_db'
51
+ state['has_index'] = index_dir.exists() and any(index_dir.iterdir())
52
+
53
+ # Check if it's a git repo
54
+ git_dir = self.project_root / '.git'
55
+ state['is_git_repo'] = git_dir.exists()
56
+
57
+ # Check for MCP configuration (Claude Desktop config)
58
+ home = Path.home()
59
+ claude_config = home / 'Library' / 'Application Support' / 'Claude' / 'claude_desktop_config.json'
60
+ if claude_config.exists():
61
+ try:
62
+ with open(claude_config) as f:
63
+ config_data = json.load(f)
64
+ mcp_servers = config_data.get('mcpServers', {})
65
+ state['has_mcp_config'] = 'mcp-vector-search' in mcp_servers
66
+ except (json.JSONDecodeError, IOError):
67
+ pass
68
+
69
+ # TODO: Check for recent file changes (would need file system monitoring)
70
+ # For now, we'll assume false
71
+ state['has_recent_changes'] = False
72
+
73
+ except Exception:
74
+ # If we can't determine state, provide conservative defaults
75
+ pass
76
+
77
+ return state
78
+
79
+ def get_workflow_suggestions(self, failed_command: str) -> List[Dict[str, str]]:
80
+ """Get workflow-based suggestions for a failed command.
81
+
82
+ Args:
83
+ failed_command: The command that failed
84
+
85
+ Returns:
86
+ List of suggestion dictionaries with 'command', 'reason', and 'priority'
87
+ """
88
+ suggestions = []
89
+ state = self.get_project_state()
90
+
91
+ # High priority suggestions based on project state
92
+ if not state['is_initialized']:
93
+ suggestions.append({
94
+ 'command': 'init',
95
+ 'reason': 'Project is not initialized for vector search',
96
+ 'priority': 'high',
97
+ 'description': 'Set up the project configuration and create necessary directories'
98
+ })
99
+ elif not state['has_index']:
100
+ suggestions.append({
101
+ 'command': 'index',
102
+ 'reason': 'No search index found - create one to enable searching',
103
+ 'priority': 'high',
104
+ 'description': 'Build the vector index for your codebase'
105
+ })
106
+
107
+ # Context-specific suggestions based on the failed command
108
+ if failed_command.lower() in ['search', 'find', 'query', 's', 'f']:
109
+ if not state['has_index']:
110
+ suggestions.append({
111
+ 'command': 'index',
112
+ 'reason': 'Cannot search without an index',
113
+ 'priority': 'high',
114
+ 'description': 'Build the search index first'
115
+ })
116
+ else:
117
+ suggestions.extend([
118
+ {
119
+ 'command': 'search',
120
+ 'reason': 'Correct command for semantic code search',
121
+ 'priority': 'high',
122
+ 'description': 'Search your codebase semantically'
123
+ },
124
+ {
125
+ 'command': 'interactive',
126
+ 'reason': 'Try interactive search for better experience',
127
+ 'priority': 'medium',
128
+ 'description': 'Start an interactive search session'
129
+ }
130
+ ])
131
+
132
+ elif failed_command.lower() in ['index', 'build', 'scan', 'i', 'b']:
133
+ suggestions.append({
134
+ 'command': 'index',
135
+ 'reason': 'Correct command for building search index',
136
+ 'priority': 'high',
137
+ 'description': 'Index your codebase for semantic search'
138
+ })
139
+
140
+ elif failed_command.lower() in ['status', 'info', 'stat', 'st']:
141
+ suggestions.append({
142
+ 'command': 'status',
143
+ 'reason': 'Show project status and statistics',
144
+ 'priority': 'high',
145
+ 'description': 'Display current project information'
146
+ })
147
+
148
+ elif failed_command.lower() in ['config', 'configure', 'settings', 'c']:
149
+ suggestions.append({
150
+ 'command': 'config',
151
+ 'reason': 'Manage project configuration',
152
+ 'priority': 'high',
153
+ 'description': 'View or modify project settings'
154
+ })
155
+
156
+ # MCP-related suggestions
157
+ if not state['has_mcp_config'] and failed_command.lower() in ['mcp', 'claude', 'server']:
158
+ suggestions.append({
159
+ 'command': 'init-mcp',
160
+ 'reason': 'Set up Claude Code MCP integration',
161
+ 'priority': 'medium',
162
+ 'description': 'Configure MCP server for Claude Code integration'
163
+ })
164
+
165
+ # Remove duplicates while preserving order
166
+ seen = set()
167
+ unique_suggestions = []
168
+ for suggestion in suggestions:
169
+ key = suggestion['command']
170
+ if key not in seen:
171
+ seen.add(key)
172
+ unique_suggestions.append(suggestion)
173
+
174
+ return unique_suggestions
175
+
176
+ def get_next_steps(self) -> List[Dict[str, str]]:
177
+ """Get suggested next steps based on current project state.
178
+
179
+ Returns:
180
+ List of suggested next step dictionaries
181
+ """
182
+ state = self.get_project_state()
183
+ next_steps = []
184
+
185
+ if not state['is_initialized']:
186
+ next_steps.append({
187
+ 'command': 'init',
188
+ 'description': 'Initialize the project for semantic search',
189
+ 'priority': 'high'
190
+ })
191
+ elif not state['has_index']:
192
+ next_steps.append({
193
+ 'command': 'index',
194
+ 'description': 'Build the search index for your codebase',
195
+ 'priority': 'high'
196
+ })
197
+ else:
198
+ # Project is ready for use
199
+ next_steps.extend([
200
+ {
201
+ 'command': 'search "your query here"',
202
+ 'description': 'Search your codebase semantically',
203
+ 'priority': 'high'
204
+ },
205
+ {
206
+ 'command': 'status',
207
+ 'description': 'Check project statistics and index health',
208
+ 'priority': 'medium'
209
+ }
210
+ ])
211
+
212
+ if state['has_recent_changes']:
213
+ next_steps.insert(0, {
214
+ 'command': 'index --force',
215
+ 'description': 'Update the index with recent changes',
216
+ 'priority': 'high'
217
+ })
218
+
219
+ if not state['has_mcp_config'] and state['is_initialized']:
220
+ next_steps.append({
221
+ 'command': 'init-mcp',
222
+ 'description': 'Set up Claude Code integration',
223
+ 'priority': 'low'
224
+ })
225
+
226
+ return next_steps
227
+
228
+ def show_contextual_help(self, failed_command: Optional[str] = None) -> None:
229
+ """Show contextual help and suggestions.
230
+
231
+ Args:
232
+ failed_command: The command that failed (if any)
233
+ """
234
+ if failed_command:
235
+ self.console.print(f"\n[yellow]Command '{failed_command}' not recognized.[/yellow]")
236
+
237
+ suggestions = self.get_workflow_suggestions(failed_command)
238
+ if suggestions:
239
+ self.console.print("\n[bold]Based on your project state, you might want to try:[/bold]")
240
+
241
+ for i, suggestion in enumerate(suggestions[:3], 1): # Show top 3
242
+ priority_color = {
243
+ 'high': 'red',
244
+ 'medium': 'yellow',
245
+ 'low': 'dim'
246
+ }.get(suggestion['priority'], 'white')
247
+
248
+ self.console.print(
249
+ f" [{priority_color}]{i}.[/{priority_color}] "
250
+ f"[bold cyan]mcp-vector-search {suggestion['command']}[/bold cyan]"
251
+ )
252
+ self.console.print(f" {suggestion['description']}")
253
+ if suggestion.get('reason'):
254
+ self.console.print(f" [dim]({suggestion['reason']})[/dim]")
255
+ else:
256
+ # Show general next steps
257
+ next_steps = self.get_next_steps()
258
+ if next_steps:
259
+ self.console.print("\n[bold]Suggested next steps:[/bold]")
260
+
261
+ for i, step in enumerate(next_steps[:3], 1):
262
+ priority_color = {
263
+ 'high': 'green',
264
+ 'medium': 'yellow',
265
+ 'low': 'dim'
266
+ }.get(step['priority'], 'white')
267
+
268
+ self.console.print(
269
+ f" [{priority_color}]{i}.[/{priority_color}] "
270
+ f"[bold cyan]mcp-vector-search {step['command']}[/bold cyan]"
271
+ )
272
+ self.console.print(f" {step['description']}")
273
+
274
+ def get_command_completion_suggestions(self, partial_command: str) -> List[str]:
275
+ """Get command completion suggestions for a partial command.
276
+
277
+ Args:
278
+ partial_command: Partial command string
279
+
280
+ Returns:
281
+ List of possible command completions
282
+ """
283
+ all_commands = [
284
+ 'search', 'index', 'status', 'config', 'init', 'mcp',
285
+ 'doctor', 'version', 'watch', 'auto-index', 'history',
286
+ 'interactive', 'demo', 'install', 'reset', 'health'
287
+ ]
288
+
289
+ # Add common aliases and shortcuts
290
+ all_commands.extend(['s', 'i', 'st', 'c', 'f', 'find'])
291
+
292
+ partial_lower = partial_command.lower()
293
+ matches = [
294
+ cmd for cmd in all_commands
295
+ if cmd.startswith(partial_lower)
296
+ ]
297
+
298
+ return sorted(matches)
299
+
300
+
301
+ def get_contextual_suggestions(project_root: Optional[Path] = None,
302
+ failed_command: Optional[str] = None) -> None:
303
+ """Get and display contextual suggestions.
304
+
305
+ Args:
306
+ project_root: Root directory of the project
307
+ failed_command: The command that failed
308
+ """
309
+ provider = ContextualSuggestionProvider(project_root)
310
+ provider.show_contextual_help(failed_command)
311
+
312
+
313
+ def suggest_workflow_commands(project_root: Optional[Path] = None) -> List[str]:
314
+ """Get workflow command suggestions for the current project state.
315
+
316
+ Args:
317
+ project_root: Root directory of the project
318
+
319
+ Returns:
320
+ List of suggested commands in priority order
321
+ """
322
+ provider = ContextualSuggestionProvider(project_root)
323
+ next_steps = provider.get_next_steps()
324
+
325
+ return [step['command'] for step in next_steps]