mcp-vector-search 0.4.12__py3-none-any.whl → 0.4.14__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
@@ -86,7 +112,15 @@ def interactive_search(
86
112
  None, "--project-root", "-p", help="Project root directory"
87
113
  ),
88
114
  ) -> None:
89
- """Start an interactive search session with filtering and refinement."""
115
+ """Start an interactive search session with filtering and refinement.
116
+
117
+ The interactive mode provides a rich terminal interface for searching your codebase
118
+ with real-time filtering, query refinement, and result navigation.
119
+
120
+ Examples:
121
+ mcp-vector-search interactive
122
+ mcp-vector-search interactive --project-root /path/to/project
123
+ """
90
124
  import asyncio
91
125
 
92
126
  from .interactive import start_interactive_search
@@ -111,7 +145,15 @@ def show_history(
111
145
  None, "--project-root", "-p", help="Project root directory"
112
146
  ),
113
147
  ) -> None:
114
- """Show search history."""
148
+ """Show search history.
149
+
150
+ Displays your recent search queries with timestamps and result counts.
151
+ Use this to revisit previous searches or track your search patterns.
152
+
153
+ Examples:
154
+ mcp-vector-search history
155
+ mcp-vector-search history --limit 50
156
+ """
115
157
  from .history import show_search_history
116
158
 
117
159
  root = project_root or ctx.obj.get("project_root") or Path.cwd()
@@ -125,7 +167,14 @@ def show_favorites_cmd(
125
167
  None, "--project-root", "-p", help="Project root directory"
126
168
  ),
127
169
  ) -> None:
128
- """Show favorite queries."""
170
+ """Show favorite queries.
171
+
172
+ Displays your saved favorite search queries. Favorites allow you to quickly
173
+ access frequently used searches without typing them again.
174
+
175
+ Examples:
176
+ mcp-vector-search favorites
177
+ """
129
178
  from .history import show_favorites
130
179
 
131
180
  root = project_root or ctx.obj.get("project_root") or Path.cwd()
@@ -141,7 +190,15 @@ def add_favorite(
141
190
  None, "--project-root", "-p", help="Project root directory"
142
191
  ),
143
192
  ) -> None:
144
- """Add a query to favorites."""
193
+ """Add a query to favorites.
194
+
195
+ Save a search query to your favorites list for quick access later.
196
+ Optionally include a description to help remember what the query is for.
197
+
198
+ Examples:
199
+ mcp-vector-search add-favorite "authentication functions"
200
+ mcp-vector-search add-favorite "error handling" --desc "Error handling patterns"
201
+ """
145
202
  from .history import SearchHistory
146
203
 
147
204
  root = project_root or ctx.obj.get("project_root") or Path.cwd()
@@ -157,7 +214,13 @@ def remove_favorite(
157
214
  None, "--project-root", "-p", help="Project root directory"
158
215
  ),
159
216
  ) -> None:
160
- """Remove a query from favorites."""
217
+ """Remove a query from favorites.
218
+
219
+ Remove a previously saved favorite query from your favorites list.
220
+
221
+ Examples:
222
+ mcp-vector-search remove-favorite "authentication functions"
223
+ """
161
224
  from .history import SearchHistory
162
225
 
163
226
  root = project_root or ctx.obj.get("project_root") or Path.cwd()
@@ -208,6 +271,8 @@ def main(
208
271
  if project_root:
209
272
  logger.info(f"Using project root: {project_root}")
210
273
 
274
+ # Note: Contextual help moved to a separate command to avoid interfering with didyoumean
275
+
211
276
 
212
277
  @app.command()
213
278
  def version() -> None:
@@ -220,7 +285,7 @@ def version() -> None:
220
285
 
221
286
 
222
287
  def handle_command_error(ctx, param, value):
223
- """Handle command errors with suggestions."""
288
+ """Handle command errors with enhanced suggestions."""
224
289
  if ctx.resilient_parsing:
225
290
  return
226
291
 
@@ -235,13 +300,44 @@ def handle_command_error(ctx, param, value):
235
300
  match = re.search(r"No such command '([^']+)'", str(e))
236
301
  if match:
237
302
  command_name = match.group(1)
303
+
304
+ # Use both the original suggestions and contextual suggestions
238
305
  add_common_suggestions(ctx, command_name)
306
+
307
+ # Add contextual suggestions based on project state
308
+ try:
309
+ project_root = ctx.obj.get("project_root") if ctx.obj else None
310
+ get_contextual_suggestions(project_root, command_name)
311
+ except Exception:
312
+ # If contextual suggestions fail, don't break the error flow
313
+ pass
239
314
  raise
240
315
 
241
316
 
317
+
318
+
319
+ @app.command()
320
+ def help_contextual() -> None:
321
+ """Show contextual help and suggestions based on project state."""
322
+ try:
323
+ project_root = Path.cwd()
324
+ console.print(f"[bold blue]mcp-vector-search[/bold blue] version [green]{__version__}[/green]")
325
+ console.print("[dim]CLI-first semantic code search with MCP integration[/dim]")
326
+ get_contextual_suggestions(project_root)
327
+ except Exception as e:
328
+ console.print("\n[dim]Use [bold]mcp-vector-search --help[/bold] for more information.[/dim]")
329
+
330
+
242
331
  @app.command()
243
332
  def doctor() -> None:
244
- """Check system dependencies and configuration."""
333
+ """Check system dependencies and configuration.
334
+
335
+ Runs diagnostic checks to ensure all required dependencies are installed
336
+ and properly configured. Use this command to troubleshoot installation issues.
337
+
338
+ Examples:
339
+ mcp-vector-search doctor
340
+ """
245
341
  from .commands.status import check_dependencies
246
342
 
247
343
  console.print("[bold blue]MCP Vector Search - System Check[/bold blue]\n")
@@ -261,5 +357,58 @@ def doctor() -> None:
261
357
 
262
358
 
263
359
 
360
+ def cli_with_suggestions():
361
+ """CLI wrapper that catches errors and provides suggestions."""
362
+ import sys
363
+ import click
364
+
365
+ try:
366
+ # Call the app with standalone_mode=False to get exceptions instead of sys.exit
367
+ app(standalone_mode=False)
368
+ except click.UsageError as e:
369
+ # Check if it's a "No such command" error
370
+ if "No such command" in str(e):
371
+ # Extract the command name from the error
372
+ import re
373
+ match = re.search(r"No such command '([^']+)'", str(e))
374
+ if match:
375
+ command_name = match.group(1)
376
+
377
+ # Show enhanced suggestions
378
+ from rich.console import Console
379
+ console = Console(stderr=True)
380
+ console.print(f"\\n[red]Error:[/red] {e}")
381
+
382
+ # Show enhanced suggestions
383
+ add_common_suggestions(None, command_name)
384
+
385
+ # Show contextual suggestions too
386
+ try:
387
+ project_root = Path.cwd()
388
+ get_contextual_suggestions(project_root, command_name)
389
+ except Exception:
390
+ pass
391
+
392
+ sys.exit(2) # Exit with error code
393
+
394
+ # For other usage errors, show the default message and exit
395
+ click.echo(f"Error: {e}", err=True)
396
+ sys.exit(2)
397
+ except click.Abort:
398
+ # User interrupted (Ctrl+C)
399
+ sys.exit(1)
400
+ except SystemExit as e:
401
+ # Re-raise system exits
402
+ raise
403
+ except Exception as e:
404
+ # For other exceptions, show error and exit if verbose logging is enabled
405
+ # Suppress internal framework errors in normal operation
406
+ if "--verbose" in sys.argv or "-v" in sys.argv:
407
+ click.echo(f"Unexpected error: {e}", err=True)
408
+ sys.exit(1)
409
+ # Otherwise, just exit silently to avoid confusing error messages
410
+ pass
411
+
412
+
264
413
  if __name__ == "__main__":
265
- app()
414
+ 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]