hanzo-mcp 0.5.1__py3-none-any.whl → 0.5.2__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 hanzo-mcp might be problematic. Click here for more details.
- hanzo_mcp/__init__.py +1 -1
- hanzo_mcp/tools/__init__.py +135 -4
- hanzo_mcp/tools/common/base.py +7 -2
- hanzo_mcp/tools/common/stats.py +261 -0
- hanzo_mcp/tools/common/tool_disable.py +144 -0
- hanzo_mcp/tools/common/tool_enable.py +182 -0
- hanzo_mcp/tools/common/tool_list.py +263 -0
- hanzo_mcp/tools/database/__init__.py +71 -0
- hanzo_mcp/tools/database/database_manager.py +246 -0
- hanzo_mcp/tools/database/graph_add.py +257 -0
- hanzo_mcp/tools/database/graph_query.py +536 -0
- hanzo_mcp/tools/database/graph_remove.py +267 -0
- hanzo_mcp/tools/database/graph_search.py +348 -0
- hanzo_mcp/tools/database/graph_stats.py +345 -0
- hanzo_mcp/tools/database/sql_query.py +229 -0
- hanzo_mcp/tools/database/sql_search.py +296 -0
- hanzo_mcp/tools/database/sql_stats.py +254 -0
- hanzo_mcp/tools/editor/__init__.py +11 -0
- hanzo_mcp/tools/editor/neovim_command.py +272 -0
- hanzo_mcp/tools/editor/neovim_edit.py +290 -0
- hanzo_mcp/tools/editor/neovim_session.py +356 -0
- hanzo_mcp/tools/filesystem/__init__.py +15 -5
- hanzo_mcp/tools/filesystem/{unified_search.py → batch_search.py} +254 -131
- hanzo_mcp/tools/filesystem/find_files.py +348 -0
- hanzo_mcp/tools/filesystem/git_search.py +505 -0
- hanzo_mcp/tools/llm/__init__.py +27 -0
- hanzo_mcp/tools/llm/consensus_tool.py +351 -0
- hanzo_mcp/tools/llm/llm_manage.py +413 -0
- hanzo_mcp/tools/llm/llm_tool.py +346 -0
- hanzo_mcp/tools/llm/provider_tools.py +412 -0
- hanzo_mcp/tools/mcp/__init__.py +11 -0
- hanzo_mcp/tools/mcp/mcp_add.py +263 -0
- hanzo_mcp/tools/mcp/mcp_remove.py +127 -0
- hanzo_mcp/tools/mcp/mcp_stats.py +165 -0
- hanzo_mcp/tools/shell/__init__.py +27 -7
- hanzo_mcp/tools/shell/logs.py +265 -0
- hanzo_mcp/tools/shell/npx.py +194 -0
- hanzo_mcp/tools/shell/npx_background.py +254 -0
- hanzo_mcp/tools/shell/pkill.py +262 -0
- hanzo_mcp/tools/shell/processes.py +279 -0
- hanzo_mcp/tools/shell/run_background.py +326 -0
- hanzo_mcp/tools/shell/uvx.py +187 -0
- hanzo_mcp/tools/shell/uvx_background.py +249 -0
- hanzo_mcp/tools/vector/__init__.py +5 -0
- hanzo_mcp/tools/vector/git_ingester.py +3 -0
- hanzo_mcp/tools/vector/index_tool.py +358 -0
- hanzo_mcp/tools/vector/infinity_store.py +98 -0
- hanzo_mcp/tools/vector/vector_search.py +11 -6
- {hanzo_mcp-0.5.1.dist-info → hanzo_mcp-0.5.2.dist-info}/METADATA +1 -1
- {hanzo_mcp-0.5.1.dist-info → hanzo_mcp-0.5.2.dist-info}/RECORD +54 -16
- {hanzo_mcp-0.5.1.dist-info → hanzo_mcp-0.5.2.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.5.1.dist-info → hanzo_mcp-0.5.2.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.5.1.dist-info → hanzo_mcp-0.5.2.dist-info}/licenses/LICENSE +0 -0
- {hanzo_mcp-0.5.1.dist-info → hanzo_mcp-0.5.2.dist-info}/top_level.txt +0 -0
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Batch search tool that runs multiple search queries in parallel.
|
|
2
2
|
|
|
3
|
-
This tool
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
This tool allows running multiple searches of different types concurrently:
|
|
4
|
+
- grep: Fast pattern/regex search
|
|
5
|
+
- grep_ast: AST-aware code search
|
|
6
|
+
- vector_search: Semantic similarity search
|
|
7
|
+
- git_search: Search through git history
|
|
8
|
+
|
|
9
|
+
Results are combined and ranked for comprehensive search coverage.
|
|
10
|
+
Perfect for complex research and refactoring tasks where you need
|
|
11
|
+
to find all occurrences across different dimensions.
|
|
8
12
|
"""
|
|
9
13
|
|
|
10
14
|
import asyncio
|
|
@@ -23,6 +27,7 @@ from typing_extensions import Annotated, TypedDict, Unpack, final, override
|
|
|
23
27
|
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
|
|
24
28
|
from hanzo_mcp.tools.filesystem.grep import Grep
|
|
25
29
|
from hanzo_mcp.tools.filesystem.grep_ast_tool import GrepAstTool
|
|
30
|
+
from hanzo_mcp.tools.filesystem.git_search import GitSearchTool
|
|
26
31
|
from hanzo_mcp.tools.vector.vector_search import VectorSearchTool
|
|
27
32
|
from hanzo_mcp.tools.vector.ast_analyzer import ASTAnalyzer, Symbol
|
|
28
33
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
@@ -35,6 +40,7 @@ class SearchType(Enum):
|
|
|
35
40
|
VECTOR = "vector"
|
|
36
41
|
AST = "ast"
|
|
37
42
|
SYMBOL = "symbol"
|
|
43
|
+
GIT = "git"
|
|
38
44
|
|
|
39
45
|
|
|
40
46
|
@dataclass
|
|
@@ -59,8 +65,8 @@ class SearchResult:
|
|
|
59
65
|
|
|
60
66
|
|
|
61
67
|
@dataclass
|
|
62
|
-
class
|
|
63
|
-
"""Container for all
|
|
68
|
+
class BatchSearchResults:
|
|
69
|
+
"""Container for all batch search results."""
|
|
64
70
|
query: str
|
|
65
71
|
total_results: int
|
|
66
72
|
results_by_type: Dict[SearchType, List[SearchResult]]
|
|
@@ -78,30 +84,34 @@ class UnifiedSearchResults:
|
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
|
|
81
|
-
|
|
87
|
+
Queries = Annotated[List[Dict[str, Any]], Field(description="List of search queries with types", min_items=1)]
|
|
82
88
|
SearchPath = Annotated[str, Field(description="Path to search in", default=".")]
|
|
83
89
|
Include = Annotated[str, Field(description="File pattern to include", default="*")]
|
|
84
|
-
MaxResults = Annotated[int, Field(description="Maximum results per
|
|
85
|
-
EnableVector = Annotated[bool, Field(description="Enable vector/semantic search", default=True)]
|
|
86
|
-
EnableAST = Annotated[bool, Field(description="Enable AST context search", default=True)]
|
|
87
|
-
EnableSymbol = Annotated[bool, Field(description="Enable symbol search", default=True)]
|
|
90
|
+
MaxResults = Annotated[int, Field(description="Maximum results per query", default=20)]
|
|
88
91
|
IncludeContext = Annotated[bool, Field(description="Include function/method context", default=True)]
|
|
92
|
+
CombineResults = Annotated[bool, Field(description="Combine and deduplicate results", default=True)]
|
|
89
93
|
|
|
90
94
|
|
|
91
|
-
class
|
|
92
|
-
"""Parameters for
|
|
93
|
-
|
|
94
|
-
|
|
95
|
+
class BatchSearchParams(TypedDict):
|
|
96
|
+
"""Parameters for batch search.
|
|
97
|
+
|
|
98
|
+
queries format: [
|
|
99
|
+
{"type": "grep", "pattern": "TODO"},
|
|
100
|
+
{"type": "vector_search", "query": "error handling"},
|
|
101
|
+
{"type": "grep_ast", "pattern": "def.*test"},
|
|
102
|
+
{"type": "git_search", "pattern": "bug fix", "search_type": "commits"}
|
|
103
|
+
]
|
|
104
|
+
"""
|
|
105
|
+
queries: Queries
|
|
106
|
+
path: SearchPath
|
|
95
107
|
include: Include
|
|
96
108
|
max_results: MaxResults
|
|
97
|
-
enable_vector: EnableVector
|
|
98
|
-
enable_ast: EnableAST
|
|
99
|
-
enable_symbol: EnableSymbol
|
|
100
109
|
include_context: IncludeContext
|
|
110
|
+
combine_results: CombineResults
|
|
101
111
|
|
|
102
112
|
|
|
103
113
|
@final
|
|
104
|
-
class
|
|
114
|
+
class BatchSearchTool(FilesystemBaseTool):
|
|
105
115
|
"""Unified search tool combining multiple search strategies."""
|
|
106
116
|
|
|
107
117
|
def __init__(self, permission_manager: PermissionManager,
|
|
@@ -113,6 +123,7 @@ class UnifiedSearchTool(FilesystemBaseTool):
|
|
|
113
123
|
# Initialize component search tools
|
|
114
124
|
self.grep_tool = Grep(permission_manager)
|
|
115
125
|
self.grep_ast_tool = GrepAstTool(permission_manager)
|
|
126
|
+
self.git_search_tool = GitSearchTool(permission_manager)
|
|
116
127
|
self.ast_analyzer = ASTAnalyzer()
|
|
117
128
|
|
|
118
129
|
# Vector search is optional
|
|
@@ -128,23 +139,27 @@ class UnifiedSearchTool(FilesystemBaseTool):
|
|
|
128
139
|
@override
|
|
129
140
|
def name(self) -> str:
|
|
130
141
|
"""Get the tool name."""
|
|
131
|
-
return "
|
|
142
|
+
return "batch_search"
|
|
132
143
|
|
|
133
144
|
@property
|
|
134
145
|
@override
|
|
135
146
|
def description(self) -> str:
|
|
136
147
|
"""Get the tool description."""
|
|
137
|
-
return """
|
|
148
|
+
return """Run multiple search queries in parallel across different search types.
|
|
149
|
+
|
|
150
|
+
Supports running concurrent searches:
|
|
151
|
+
- Multiple grep patterns
|
|
152
|
+
- Multiple vector queries
|
|
153
|
+
- Multiple AST searches
|
|
154
|
+
- Combined with git history search
|
|
138
155
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
4. Including symbol search for code definitions
|
|
144
|
-
5. Providing function/method body context when relevant
|
|
156
|
+
Examples:
|
|
157
|
+
- Search for 'config' in code + 'configuration' in docs + 'CONFIG' in constants
|
|
158
|
+
- Find all references to a function across code, comments, and git history
|
|
159
|
+
- Search for concept across different naming conventions
|
|
145
160
|
|
|
146
|
-
|
|
147
|
-
|
|
161
|
+
Results are intelligently combined, deduplicated, and ranked by relevance.
|
|
162
|
+
Perfect for comprehensive code analysis and refactoring tasks."""
|
|
148
163
|
|
|
149
164
|
def _detect_search_intent(self, pattern: str) -> Tuple[bool, bool, bool]:
|
|
150
165
|
"""Analyze pattern to determine which search types to enable.
|
|
@@ -505,22 +520,20 @@ Use this when you need comprehensive search results or aren't sure which search
|
|
|
505
520
|
return all_results
|
|
506
521
|
|
|
507
522
|
@override
|
|
508
|
-
async def call(self, ctx: MCPContext, **params: Unpack[
|
|
509
|
-
"""Execute
|
|
523
|
+
async def call(self, ctx: MCPContext, **params: Unpack[BatchSearchParams]) -> str:
|
|
524
|
+
"""Execute batch search with multiple queries in parallel."""
|
|
510
525
|
import time
|
|
511
526
|
start_time = time.time()
|
|
512
527
|
|
|
513
528
|
tool_ctx = self.create_tool_context(ctx)
|
|
514
529
|
|
|
515
530
|
# Extract parameters
|
|
516
|
-
|
|
531
|
+
queries = params["queries"]
|
|
517
532
|
path = params.get("path", ".")
|
|
518
533
|
include = params.get("include", "*")
|
|
519
534
|
max_results = params.get("max_results", 20)
|
|
520
|
-
enable_vector = params.get("enable_vector", True)
|
|
521
|
-
enable_ast = params.get("enable_ast", True)
|
|
522
|
-
enable_symbol = params.get("enable_symbol", True)
|
|
523
535
|
include_context = params.get("include_context", True)
|
|
536
|
+
combine_results = params.get("combine_results", True)
|
|
524
537
|
|
|
525
538
|
# Validate path
|
|
526
539
|
path_validation = self.validate_path(path)
|
|
@@ -537,127 +550,237 @@ Use this when you need comprehensive search results or aren't sure which search
|
|
|
537
550
|
if not exists:
|
|
538
551
|
return error_msg
|
|
539
552
|
|
|
540
|
-
|
|
541
|
-
should_vector, should_ast, should_symbol = self._detect_search_intent(pattern)
|
|
542
|
-
enable_vector = enable_vector and should_vector
|
|
543
|
-
enable_ast = enable_ast and should_ast
|
|
544
|
-
enable_symbol = enable_symbol and should_symbol
|
|
553
|
+
await tool_ctx.info(f"Starting batch search with {len(queries)} queries in {path}")
|
|
545
554
|
|
|
546
|
-
|
|
547
|
-
await tool_ctx.info(f"Enabled searches: grep=True vector={enable_vector} ast={enable_ast} symbol={enable_symbol}")
|
|
548
|
-
|
|
549
|
-
# Run searches in parallel for maximum efficiency
|
|
555
|
+
# Run all queries in parallel
|
|
550
556
|
search_tasks = []
|
|
557
|
+
query_info = [] # Track query info for results
|
|
551
558
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
559
|
+
for query in queries:
|
|
560
|
+
query_type = query.get("type", "grep")
|
|
561
|
+
query_info.append(query)
|
|
562
|
+
|
|
563
|
+
if query_type == "grep":
|
|
564
|
+
pattern = query.get("pattern")
|
|
565
|
+
if pattern:
|
|
566
|
+
search_tasks.append(
|
|
567
|
+
self._run_grep_search(pattern, path, include, tool_ctx, max_results)
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
elif query_type == "grep_ast":
|
|
571
|
+
pattern = query.get("pattern")
|
|
572
|
+
if pattern:
|
|
573
|
+
search_tasks.append(
|
|
574
|
+
self._run_ast_search(pattern, path, include, tool_ctx, max_results)
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
elif query_type == "vector_search" and self.vector_tool:
|
|
578
|
+
search_query = query.get("query") or query.get("pattern")
|
|
579
|
+
if search_query:
|
|
580
|
+
search_tasks.append(
|
|
581
|
+
self._run_vector_search(search_query, path, tool_ctx, max_results)
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
elif query_type == "git_search":
|
|
585
|
+
pattern = query.get("pattern")
|
|
586
|
+
search_type = query.get("search_type", "content")
|
|
587
|
+
if pattern:
|
|
588
|
+
search_tasks.append(
|
|
589
|
+
self._run_git_search(pattern, path, search_type, tool_ctx, max_results)
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
else:
|
|
593
|
+
await tool_ctx.warning(f"Unknown or unavailable search type: {query_type}")
|
|
571
594
|
|
|
572
595
|
# Execute all searches in parallel
|
|
573
596
|
search_results = await asyncio.gather(*search_tasks, return_exceptions=True)
|
|
574
597
|
|
|
575
|
-
#
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
if enable_ast:
|
|
581
|
-
search_types.append(SearchType.AST)
|
|
582
|
-
if enable_symbol:
|
|
583
|
-
search_types.append(SearchType.SYMBOL)
|
|
584
|
-
|
|
585
|
-
for i, result in enumerate(search_results):
|
|
598
|
+
# Collect all results
|
|
599
|
+
all_results = []
|
|
600
|
+
results_by_query = {}
|
|
601
|
+
|
|
602
|
+
for i, (query, result) in enumerate(zip(query_info, search_results)):
|
|
586
603
|
if isinstance(result, Exception):
|
|
587
|
-
await tool_ctx.error(f"
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
604
|
+
await tool_ctx.error(f"Query {i+1} failed: {str(result)}")
|
|
605
|
+
results_by_query[i] = []
|
|
606
|
+
else:
|
|
607
|
+
results_by_query[i] = result
|
|
608
|
+
all_results.extend(result)
|
|
592
609
|
|
|
593
|
-
#
|
|
594
|
-
if
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
610
|
+
# Combine and deduplicate results if requested
|
|
611
|
+
if combine_results:
|
|
612
|
+
combined_results = self._combine_results(all_results)
|
|
613
|
+
else:
|
|
614
|
+
combined_results = all_results
|
|
598
615
|
|
|
599
|
-
#
|
|
600
|
-
|
|
616
|
+
# Add context if requested
|
|
617
|
+
if include_context:
|
|
618
|
+
combined_results = await self._add_context_to_results(combined_results, tool_ctx)
|
|
601
619
|
|
|
602
620
|
end_time = time.time()
|
|
603
621
|
search_time_ms = (end_time - start_time) * 1000
|
|
604
622
|
|
|
605
|
-
#
|
|
606
|
-
|
|
607
|
-
|
|
623
|
+
# Sort by relevance score
|
|
624
|
+
combined_results.sort(key=lambda r: r.score, reverse=True)
|
|
625
|
+
|
|
626
|
+
# Limit total results
|
|
627
|
+
combined_results = combined_results[:max_results * 2] # Allow more when combining
|
|
628
|
+
|
|
629
|
+
# Create batch results object
|
|
630
|
+
batch_results = BatchSearchResults(
|
|
631
|
+
query=f"Batch search with {len(queries)} queries",
|
|
608
632
|
total_results=len(combined_results),
|
|
609
|
-
results_by_type=
|
|
610
|
-
|
|
633
|
+
results_by_type={
|
|
634
|
+
SearchType.GREP: [r for r in combined_results if r.search_type == SearchType.GREP],
|
|
635
|
+
SearchType.VECTOR: [r for r in combined_results if r.search_type == SearchType.VECTOR],
|
|
636
|
+
SearchType.AST: [r for r in combined_results if r.search_type == SearchType.AST],
|
|
637
|
+
SearchType.GIT: [r for r in combined_results if r.search_type == SearchType.GIT],
|
|
638
|
+
},
|
|
639
|
+
combined_results=combined_results,
|
|
611
640
|
search_time_ms=search_time_ms
|
|
612
641
|
)
|
|
613
642
|
|
|
614
643
|
# Format output
|
|
615
|
-
return self.
|
|
644
|
+
return self._format_batch_results(batch_results, query_info)
|
|
616
645
|
|
|
617
|
-
def
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
lines = [
|
|
623
|
-
f"Unified Search Results for '{results.query}' ({results.search_time_ms:.1f}ms)",
|
|
624
|
-
f"Found {results.total_results} total results across {len(results.results_by_type)} search types",
|
|
625
|
-
""
|
|
626
|
-
]
|
|
627
|
-
|
|
628
|
-
# Show summary by type
|
|
629
|
-
for search_type, type_results in results.results_by_type.items():
|
|
630
|
-
if type_results:
|
|
631
|
-
lines.append(f"• {search_type.value.title()}: {len(type_results)} results")
|
|
632
|
-
lines.append("")
|
|
646
|
+
async def _run_git_search(self, pattern: str, path: str, search_type: str,
|
|
647
|
+
tool_ctx, max_results: int) -> List[SearchResult]:
|
|
648
|
+
"""Run git search and convert results."""
|
|
649
|
+
await tool_ctx.info(f"Running git search for: {pattern} (type: {search_type})")
|
|
633
650
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
header += f" - {result.file_path}"
|
|
651
|
+
try:
|
|
652
|
+
# Use the git search tool
|
|
653
|
+
git_result = await self.git_search_tool.call(
|
|
654
|
+
tool_ctx.mcp_context,
|
|
655
|
+
pattern=pattern,
|
|
656
|
+
path=path,
|
|
657
|
+
search_type=search_type,
|
|
658
|
+
max_count=max_results
|
|
659
|
+
)
|
|
644
660
|
|
|
645
|
-
|
|
646
|
-
|
|
661
|
+
results = []
|
|
662
|
+
if "Found" in git_result:
|
|
663
|
+
# Parse git search results - simplified parser
|
|
664
|
+
lines = git_result.split('\n')
|
|
665
|
+
current_file = None
|
|
666
|
+
|
|
667
|
+
for line in lines:
|
|
668
|
+
if line.strip():
|
|
669
|
+
# Extract file path and content
|
|
670
|
+
if ':' in line:
|
|
671
|
+
parts = line.split(':', 2)
|
|
672
|
+
if len(parts) >= 2:
|
|
673
|
+
file_path = parts[0].strip()
|
|
674
|
+
content = parts[-1].strip() if len(parts) > 2 else line
|
|
675
|
+
|
|
676
|
+
result = SearchResult(
|
|
677
|
+
file_path=file_path,
|
|
678
|
+
line_number=None,
|
|
679
|
+
content=content,
|
|
680
|
+
search_type=SearchType.GIT,
|
|
681
|
+
score=0.8, # Git results are relevant
|
|
682
|
+
)
|
|
683
|
+
results.append(result)
|
|
684
|
+
|
|
685
|
+
if len(results) >= max_results:
|
|
686
|
+
break
|
|
647
687
|
|
|
648
|
-
|
|
649
|
-
|
|
688
|
+
await tool_ctx.info(f"Git search found {len(results)} results")
|
|
689
|
+
return results
|
|
690
|
+
|
|
691
|
+
except Exception as e:
|
|
692
|
+
await tool_ctx.error(f"Git search failed: {str(e)}")
|
|
693
|
+
return []
|
|
694
|
+
|
|
695
|
+
def _combine_results(self, results: List[SearchResult]) -> List[SearchResult]:
|
|
696
|
+
"""Combine and deduplicate search results."""
|
|
697
|
+
# Use file path and line number as key for deduplication
|
|
698
|
+
seen = {}
|
|
699
|
+
combined = []
|
|
700
|
+
|
|
701
|
+
for result in results:
|
|
702
|
+
key = (result.file_path, result.line_number)
|
|
650
703
|
|
|
651
|
-
|
|
704
|
+
if key not in seen:
|
|
705
|
+
seen[key] = result
|
|
706
|
+
combined.append(result)
|
|
707
|
+
else:
|
|
708
|
+
# If we've seen this location, keep the one with higher score
|
|
709
|
+
existing = seen[key]
|
|
710
|
+
if result.score > existing.score:
|
|
711
|
+
# Replace with higher scored result
|
|
712
|
+
idx = combined.index(existing)
|
|
713
|
+
combined[idx] = result
|
|
714
|
+
seen[key] = result
|
|
715
|
+
|
|
716
|
+
return combined
|
|
717
|
+
|
|
718
|
+
async def _add_context_to_results(self, results: List[SearchResult], tool_ctx) -> List[SearchResult]:
|
|
719
|
+
"""Add function/method context to results."""
|
|
720
|
+
# This is a simplified version - you could enhance with full AST context
|
|
721
|
+
return await self._add_function_context(results, tool_ctx)
|
|
722
|
+
|
|
723
|
+
def _format_batch_results(self, results: BatchSearchResults, query_info: List[Dict]) -> str:
|
|
724
|
+
"""Format batch search results for display."""
|
|
725
|
+
output = []
|
|
726
|
+
|
|
727
|
+
# Header
|
|
728
|
+
output.append(f"=== Batch Search Results ===")
|
|
729
|
+
output.append(f"Queries: {len(query_info)}")
|
|
730
|
+
output.append(f"Total results: {results.total_results}")
|
|
731
|
+
output.append(f"Search time: {results.search_time_ms:.1f}ms\n")
|
|
732
|
+
|
|
733
|
+
# Summary by type
|
|
734
|
+
output.append("Results by type:")
|
|
735
|
+
for search_type, type_results in results.results_by_type.items():
|
|
736
|
+
if type_results:
|
|
737
|
+
output.append(f" {search_type.value}: {len(type_results)} results")
|
|
738
|
+
output.append("")
|
|
739
|
+
|
|
740
|
+
# Query summary
|
|
741
|
+
output.append("Queries executed:")
|
|
742
|
+
for i, query in enumerate(query_info):
|
|
743
|
+
query_type = query.get("type", "grep")
|
|
744
|
+
pattern = query.get("pattern") or query.get("query", "")
|
|
745
|
+
output.append(f" {i+1}. {query_type}: {pattern}")
|
|
746
|
+
output.append("")
|
|
747
|
+
|
|
748
|
+
# Results
|
|
749
|
+
if results.combined_results:
|
|
750
|
+
output.append("=== Top Results ===\n")
|
|
652
751
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
752
|
+
# Group by file
|
|
753
|
+
results_by_file = {}
|
|
754
|
+
for result in results.combined_results[:50]: # Limit display
|
|
755
|
+
if result.file_path not in results_by_file:
|
|
756
|
+
results_by_file[result.file_path] = []
|
|
757
|
+
results_by_file[result.file_path].append(result)
|
|
657
758
|
|
|
658
|
-
|
|
759
|
+
# Display results by file
|
|
760
|
+
for file_path, file_results in results_by_file.items():
|
|
761
|
+
output.append(f"{file_path}")
|
|
762
|
+
output.append("-" * len(file_path))
|
|
763
|
+
|
|
764
|
+
# Sort by line number if available
|
|
765
|
+
file_results.sort(key=lambda r: r.line_number or 0)
|
|
766
|
+
|
|
767
|
+
for result in file_results:
|
|
768
|
+
score_str = f"[{result.search_type.value} {result.score:.2f}]"
|
|
769
|
+
|
|
770
|
+
if result.line_number:
|
|
771
|
+
output.append(f" {result.line_number:>4}: {score_str} {result.content}")
|
|
772
|
+
else:
|
|
773
|
+
output.append(f" {score_str} {result.content}")
|
|
774
|
+
|
|
775
|
+
if result.context:
|
|
776
|
+
output.append(f" Context: {result.context}")
|
|
777
|
+
|
|
778
|
+
output.append("")
|
|
779
|
+
else:
|
|
780
|
+
output.append("No results found.")
|
|
659
781
|
|
|
660
|
-
return "\n".join(
|
|
782
|
+
return "\n".join(output)
|
|
783
|
+
|
|
661
784
|
|
|
662
785
|
@override
|
|
663
786
|
def register(self, mcp_server: FastMCP) -> None:
|