hanzo-mcp 0.8.8__py3-none-any.whl → 0.9.0__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 -3
- hanzo_mcp/analytics/posthog_analytics.py +4 -17
- hanzo_mcp/bridge.py +9 -25
- hanzo_mcp/cli.py +8 -17
- hanzo_mcp/cli_enhanced.py +5 -14
- hanzo_mcp/cli_plugin.py +3 -9
- hanzo_mcp/config/settings.py +6 -20
- hanzo_mcp/config/tool_config.py +2 -4
- hanzo_mcp/core/base_agent.py +88 -88
- hanzo_mcp/core/model_registry.py +238 -210
- hanzo_mcp/dev_server.py +5 -15
- hanzo_mcp/prompts/__init__.py +2 -6
- hanzo_mcp/prompts/project_todo_reminder.py +3 -9
- hanzo_mcp/prompts/tool_explorer.py +1 -3
- hanzo_mcp/prompts/utils.py +7 -21
- hanzo_mcp/server.py +6 -7
- hanzo_mcp/tools/__init__.py +29 -32
- hanzo_mcp/tools/agent/__init__.py +2 -1
- hanzo_mcp/tools/agent/agent.py +10 -30
- hanzo_mcp/tools/agent/agent_tool.py +23 -17
- hanzo_mcp/tools/agent/claude_desktop_auth.py +3 -9
- hanzo_mcp/tools/agent/cli_agent_base.py +7 -24
- hanzo_mcp/tools/agent/cli_tools.py +76 -75
- hanzo_mcp/tools/agent/code_auth.py +1 -3
- hanzo_mcp/tools/agent/code_auth_tool.py +2 -6
- hanzo_mcp/tools/agent/critic_tool.py +8 -24
- hanzo_mcp/tools/agent/iching_tool.py +12 -36
- hanzo_mcp/tools/agent/network_tool.py +7 -18
- hanzo_mcp/tools/agent/prompt.py +1 -5
- hanzo_mcp/tools/agent/review_tool.py +10 -25
- hanzo_mcp/tools/agent/swarm_alias.py +1 -3
- hanzo_mcp/tools/agent/unified_cli_tools.py +38 -38
- hanzo_mcp/tools/common/batch_tool.py +15 -45
- hanzo_mcp/tools/common/config_tool.py +9 -28
- hanzo_mcp/tools/common/context.py +1 -3
- hanzo_mcp/tools/common/critic_tool.py +1 -3
- hanzo_mcp/tools/common/decorators.py +2 -6
- hanzo_mcp/tools/common/enhanced_base.py +2 -6
- hanzo_mcp/tools/common/fastmcp_pagination.py +4 -12
- hanzo_mcp/tools/common/forgiving_edit.py +9 -28
- hanzo_mcp/tools/common/mode.py +1 -5
- hanzo_mcp/tools/common/paginated_base.py +3 -11
- hanzo_mcp/tools/common/paginated_response.py +10 -30
- hanzo_mcp/tools/common/pagination.py +3 -9
- hanzo_mcp/tools/common/path_utils.py +34 -0
- hanzo_mcp/tools/common/permissions.py +14 -13
- hanzo_mcp/tools/common/personality.py +983 -701
- hanzo_mcp/tools/common/plugin_loader.py +3 -15
- hanzo_mcp/tools/common/stats.py +7 -19
- hanzo_mcp/tools/common/thinking_tool.py +1 -3
- hanzo_mcp/tools/common/tool_disable.py +2 -6
- hanzo_mcp/tools/common/tool_list.py +2 -6
- hanzo_mcp/tools/common/validation.py +1 -3
- hanzo_mcp/tools/compiler/__init__.py +8 -0
- hanzo_mcp/tools/compiler/sandboxed_compiler.py +681 -0
- hanzo_mcp/tools/config/config_tool.py +7 -13
- hanzo_mcp/tools/config/index_config.py +1 -3
- hanzo_mcp/tools/config/mode_tool.py +5 -15
- hanzo_mcp/tools/database/database_manager.py +3 -9
- hanzo_mcp/tools/database/graph.py +1 -3
- hanzo_mcp/tools/database/graph_add.py +3 -9
- hanzo_mcp/tools/database/graph_query.py +11 -34
- hanzo_mcp/tools/database/graph_remove.py +3 -9
- hanzo_mcp/tools/database/graph_search.py +6 -20
- hanzo_mcp/tools/database/graph_stats.py +11 -33
- hanzo_mcp/tools/database/sql.py +4 -12
- hanzo_mcp/tools/database/sql_query.py +6 -10
- hanzo_mcp/tools/database/sql_search.py +2 -6
- hanzo_mcp/tools/database/sql_stats.py +5 -15
- hanzo_mcp/tools/editor/neovim_command.py +1 -3
- hanzo_mcp/tools/editor/neovim_session.py +7 -13
- hanzo_mcp/tools/environment/__init__.py +8 -0
- hanzo_mcp/tools/environment/environment_detector.py +594 -0
- hanzo_mcp/tools/filesystem/__init__.py +28 -26
- hanzo_mcp/tools/filesystem/ast_multi_edit.py +14 -43
- hanzo_mcp/tools/filesystem/ast_tool.py +3 -0
- hanzo_mcp/tools/filesystem/base.py +20 -12
- hanzo_mcp/tools/filesystem/content_replace.py +7 -12
- hanzo_mcp/tools/filesystem/diff.py +2 -10
- hanzo_mcp/tools/filesystem/directory_tree.py +285 -51
- hanzo_mcp/tools/filesystem/edit.py +10 -18
- hanzo_mcp/tools/filesystem/find.py +312 -179
- hanzo_mcp/tools/filesystem/git_search.py +12 -24
- hanzo_mcp/tools/filesystem/multi_edit.py +10 -18
- hanzo_mcp/tools/filesystem/read.py +14 -30
- hanzo_mcp/tools/filesystem/rules_tool.py +9 -17
- hanzo_mcp/tools/filesystem/search.py +1160 -0
- hanzo_mcp/tools/filesystem/watch.py +2 -4
- hanzo_mcp/tools/filesystem/write.py +7 -10
- hanzo_mcp/tools/framework/__init__.py +8 -0
- hanzo_mcp/tools/framework/framework_modes.py +714 -0
- hanzo_mcp/tools/jupyter/base.py +6 -20
- hanzo_mcp/tools/jupyter/jupyter.py +4 -12
- hanzo_mcp/tools/llm/consensus_tool.py +8 -24
- hanzo_mcp/tools/llm/llm_manage.py +2 -6
- hanzo_mcp/tools/llm/llm_tool.py +17 -58
- hanzo_mcp/tools/llm/llm_unified.py +18 -59
- hanzo_mcp/tools/llm/provider_tools.py +1 -3
- hanzo_mcp/tools/lsp/lsp_tool.py +621 -481
- hanzo_mcp/tools/mcp/mcp_add.py +3 -5
- hanzo_mcp/tools/mcp/mcp_remove.py +1 -1
- hanzo_mcp/tools/mcp/mcp_stats.py +1 -3
- hanzo_mcp/tools/mcp/mcp_tool.py +9 -23
- hanzo_mcp/tools/memory/__init__.py +33 -40
- hanzo_mcp/tools/memory/conversation_memory.py +636 -0
- hanzo_mcp/tools/memory/knowledge_tools.py +7 -25
- hanzo_mcp/tools/memory/memory_tools.py +7 -19
- hanzo_mcp/tools/search/find_tool.py +12 -34
- hanzo_mcp/tools/search/unified_search.py +27 -81
- hanzo_mcp/tools/shell/__init__.py +16 -4
- hanzo_mcp/tools/shell/auto_background.py +2 -6
- hanzo_mcp/tools/shell/base.py +1 -5
- hanzo_mcp/tools/shell/base_process.py +5 -7
- hanzo_mcp/tools/shell/bash_session.py +7 -24
- hanzo_mcp/tools/shell/bash_session_executor.py +5 -15
- hanzo_mcp/tools/shell/bash_tool.py +3 -7
- hanzo_mcp/tools/shell/command_executor.py +26 -79
- hanzo_mcp/tools/shell/logs.py +4 -16
- hanzo_mcp/tools/shell/npx.py +2 -8
- hanzo_mcp/tools/shell/npx_tool.py +1 -3
- hanzo_mcp/tools/shell/pkill.py +4 -12
- hanzo_mcp/tools/shell/process_tool.py +2 -8
- hanzo_mcp/tools/shell/processes.py +5 -17
- hanzo_mcp/tools/shell/run_background.py +1 -3
- hanzo_mcp/tools/shell/run_command.py +1 -3
- hanzo_mcp/tools/shell/run_command_windows.py +1 -3
- hanzo_mcp/tools/shell/run_tool.py +56 -0
- hanzo_mcp/tools/shell/session_manager.py +2 -6
- hanzo_mcp/tools/shell/session_storage.py +2 -6
- hanzo_mcp/tools/shell/streaming_command.py +7 -23
- hanzo_mcp/tools/shell/uvx.py +4 -14
- hanzo_mcp/tools/shell/uvx_background.py +2 -6
- hanzo_mcp/tools/shell/uvx_tool.py +1 -3
- hanzo_mcp/tools/shell/zsh_tool.py +12 -20
- hanzo_mcp/tools/todo/todo.py +1 -3
- hanzo_mcp/tools/vector/__init__.py +97 -50
- hanzo_mcp/tools/vector/ast_analyzer.py +6 -20
- hanzo_mcp/tools/vector/git_ingester.py +10 -30
- hanzo_mcp/tools/vector/index_tool.py +3 -9
- hanzo_mcp/tools/vector/infinity_store.py +11 -30
- hanzo_mcp/tools/vector/mock_infinity.py +159 -0
- hanzo_mcp/tools/vector/node_tool.py +538 -0
- hanzo_mcp/tools/vector/project_manager.py +4 -12
- hanzo_mcp/tools/vector/unified_vector.py +384 -0
- hanzo_mcp/tools/vector/vector.py +2 -6
- hanzo_mcp/tools/vector/vector_index.py +8 -8
- hanzo_mcp/tools/vector/vector_search.py +7 -21
- {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.9.0.dist-info}/METADATA +2 -2
- hanzo_mcp-0.9.0.dist-info/RECORD +191 -0
- hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +0 -645
- hanzo_mcp/tools/agent/swarm_tool.py +0 -723
- hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +0 -577
- hanzo_mcp/tools/filesystem/batch_search.py +0 -900
- hanzo_mcp/tools/filesystem/directory_tree_paginated.py +0 -350
- hanzo_mcp/tools/filesystem/find_files.py +0 -369
- hanzo_mcp/tools/filesystem/grep.py +0 -467
- hanzo_mcp/tools/filesystem/search_tool.py +0 -767
- hanzo_mcp/tools/filesystem/symbols_tool.py +0 -515
- hanzo_mcp/tools/filesystem/tree.py +0 -270
- hanzo_mcp/tools/jupyter/notebook_edit.py +0 -317
- hanzo_mcp/tools/jupyter/notebook_read.py +0 -147
- hanzo_mcp/tools/todo/todo_read.py +0 -143
- hanzo_mcp/tools/todo/todo_write.py +0 -374
- hanzo_mcp-0.8.8.dist-info/RECORD +0 -192
- {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.9.0.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.9.0.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.9.0.dist-info}/top_level.txt +0 -0
|
@@ -1,515 +0,0 @@
|
|
|
1
|
-
"""Unified symbols tool implementation.
|
|
2
|
-
|
|
3
|
-
This module provides the SymbolsTool for searching, indexing, and querying code symbols
|
|
4
|
-
using tree-sitter AST parsing. It can find function definitions, class declarations,
|
|
5
|
-
and other code structures with full context.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import os
|
|
9
|
-
from typing import (
|
|
10
|
-
Any,
|
|
11
|
-
Dict,
|
|
12
|
-
List,
|
|
13
|
-
Unpack,
|
|
14
|
-
Optional,
|
|
15
|
-
Annotated,
|
|
16
|
-
TypedDict,
|
|
17
|
-
final,
|
|
18
|
-
override,
|
|
19
|
-
)
|
|
20
|
-
from pathlib import Path
|
|
21
|
-
|
|
22
|
-
from pydantic import Field
|
|
23
|
-
from grep_ast.grep_ast import TreeContext
|
|
24
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
25
|
-
|
|
26
|
-
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
|
|
27
|
-
|
|
28
|
-
# Parameter types
|
|
29
|
-
Action = Annotated[
|
|
30
|
-
str,
|
|
31
|
-
Field(
|
|
32
|
-
description="Action: search (default), ast, index, query, list",
|
|
33
|
-
default="search",
|
|
34
|
-
),
|
|
35
|
-
]
|
|
36
|
-
|
|
37
|
-
Pattern = Annotated[
|
|
38
|
-
Optional[str],
|
|
39
|
-
Field(
|
|
40
|
-
description="Pattern to search for in code",
|
|
41
|
-
default=None,
|
|
42
|
-
),
|
|
43
|
-
]
|
|
44
|
-
|
|
45
|
-
SearchPath = Annotated[
|
|
46
|
-
str,
|
|
47
|
-
Field(
|
|
48
|
-
description="Path to search/index (file or directory)",
|
|
49
|
-
default=".",
|
|
50
|
-
),
|
|
51
|
-
]
|
|
52
|
-
|
|
53
|
-
SymbolType = Annotated[
|
|
54
|
-
Optional[str],
|
|
55
|
-
Field(
|
|
56
|
-
description="Symbol type: function, class, method, variable",
|
|
57
|
-
default=None,
|
|
58
|
-
),
|
|
59
|
-
]
|
|
60
|
-
|
|
61
|
-
IgnoreCase = Annotated[
|
|
62
|
-
bool,
|
|
63
|
-
Field(
|
|
64
|
-
description="Ignore case when matching",
|
|
65
|
-
default=False,
|
|
66
|
-
),
|
|
67
|
-
]
|
|
68
|
-
|
|
69
|
-
ShowContext = Annotated[
|
|
70
|
-
bool,
|
|
71
|
-
Field(
|
|
72
|
-
description="Show AST context around matches",
|
|
73
|
-
default=True,
|
|
74
|
-
),
|
|
75
|
-
]
|
|
76
|
-
|
|
77
|
-
Limit = Annotated[
|
|
78
|
-
int,
|
|
79
|
-
Field(
|
|
80
|
-
description="Maximum results to return",
|
|
81
|
-
default=50,
|
|
82
|
-
),
|
|
83
|
-
]
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
class SymbolsParams(TypedDict, total=False):
|
|
87
|
-
"""Parameters for symbols tool."""
|
|
88
|
-
|
|
89
|
-
action: str
|
|
90
|
-
pattern: Optional[str]
|
|
91
|
-
path: str
|
|
92
|
-
symbol_type: Optional[str]
|
|
93
|
-
ignore_case: bool
|
|
94
|
-
show_context: bool
|
|
95
|
-
limit: int
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
@final
|
|
99
|
-
class SymbolsTool(FilesystemBaseTool):
|
|
100
|
-
"""Tool for code symbol operations using tree-sitter."""
|
|
101
|
-
|
|
102
|
-
def __init__(self, permission_manager):
|
|
103
|
-
"""Initialize the symbols tool."""
|
|
104
|
-
super().__init__(permission_manager)
|
|
105
|
-
self._symbol_cache = {} # Cache for indexed symbols
|
|
106
|
-
|
|
107
|
-
@property
|
|
108
|
-
@override
|
|
109
|
-
def name(self) -> str:
|
|
110
|
-
"""Get the tool name."""
|
|
111
|
-
return "symbols"
|
|
112
|
-
|
|
113
|
-
@property
|
|
114
|
-
@override
|
|
115
|
-
def description(self) -> str:
|
|
116
|
-
"""Get the tool description."""
|
|
117
|
-
return """Code symbols search with tree-sitter AST. Actions: search (default), ast, index, query, list.
|
|
118
|
-
|
|
119
|
-
Usage:
|
|
120
|
-
symbols "function_name"
|
|
121
|
-
symbols --action ast --pattern "TODO" --path ./src
|
|
122
|
-
symbols --action query --symbol-type function --path ./src
|
|
123
|
-
symbols --action index --path ./project
|
|
124
|
-
symbols --action list --path ./src --symbol-type class
|
|
125
|
-
|
|
126
|
-
Finds code structures (functions, classes, methods) with full context."""
|
|
127
|
-
|
|
128
|
-
@override
|
|
129
|
-
async def call(
|
|
130
|
-
self,
|
|
131
|
-
ctx: MCPContext,
|
|
132
|
-
**params: Unpack[SymbolsParams],
|
|
133
|
-
) -> str:
|
|
134
|
-
"""Execute symbols operation."""
|
|
135
|
-
tool_ctx = self.create_tool_context(ctx)
|
|
136
|
-
self.set_tool_context_info(tool_ctx)
|
|
137
|
-
|
|
138
|
-
# Extract action
|
|
139
|
-
action = params.get("action", "search")
|
|
140
|
-
|
|
141
|
-
# Route to appropriate handler
|
|
142
|
-
if action == "search":
|
|
143
|
-
return await self._handle_search(params, tool_ctx)
|
|
144
|
-
elif (
|
|
145
|
-
action == "ast" or action == "grep_ast"
|
|
146
|
-
): # Support both for backward compatibility
|
|
147
|
-
return await self._handle_ast(params, tool_ctx)
|
|
148
|
-
elif action == "index":
|
|
149
|
-
return await self._handle_index(params, tool_ctx)
|
|
150
|
-
elif action == "query":
|
|
151
|
-
return await self._handle_query(params, tool_ctx)
|
|
152
|
-
elif action == "list":
|
|
153
|
-
return await self._handle_list(params, tool_ctx)
|
|
154
|
-
else:
|
|
155
|
-
return f"Error: Unknown action '{action}'. Valid actions: search, ast, index, query, list"
|
|
156
|
-
|
|
157
|
-
async def _handle_search(self, params: Dict[str, Any], tool_ctx) -> str:
|
|
158
|
-
"""Search for pattern in code with AST context."""
|
|
159
|
-
pattern = params.get("pattern")
|
|
160
|
-
if not pattern:
|
|
161
|
-
return "Error: pattern required for search action"
|
|
162
|
-
|
|
163
|
-
path = params.get("path", ".")
|
|
164
|
-
ignore_case = params.get("ignore_case", False)
|
|
165
|
-
show_context = params.get("show_context", True)
|
|
166
|
-
limit = params.get("limit", 50)
|
|
167
|
-
|
|
168
|
-
# Validate path
|
|
169
|
-
path_validation = self.validate_path(path)
|
|
170
|
-
if not path_validation.is_valid:
|
|
171
|
-
await tool_ctx.error(f"Invalid path: {path_validation.error_message}")
|
|
172
|
-
return f"Error: Invalid path: {path_validation.error_message}"
|
|
173
|
-
|
|
174
|
-
# Check permissions
|
|
175
|
-
is_allowed, error_message = await self.check_path_allowed(path, tool_ctx)
|
|
176
|
-
if not is_allowed:
|
|
177
|
-
return error_message
|
|
178
|
-
|
|
179
|
-
# Check existence
|
|
180
|
-
is_exists, error_message = await self.check_path_exists(path, tool_ctx)
|
|
181
|
-
if not is_exists:
|
|
182
|
-
return error_message
|
|
183
|
-
|
|
184
|
-
await tool_ctx.info(f"Searching for '{pattern}' in {path}")
|
|
185
|
-
|
|
186
|
-
# Get files to process
|
|
187
|
-
files_to_process = self._get_source_files(path)
|
|
188
|
-
if not files_to_process:
|
|
189
|
-
return f"No source code files found in {path}"
|
|
190
|
-
|
|
191
|
-
# Process files
|
|
192
|
-
results = []
|
|
193
|
-
match_count = 0
|
|
194
|
-
|
|
195
|
-
for file_path in files_to_process:
|
|
196
|
-
if match_count >= limit:
|
|
197
|
-
break
|
|
198
|
-
|
|
199
|
-
try:
|
|
200
|
-
with open(file_path, "r", encoding="utf-8") as f:
|
|
201
|
-
code = f.read()
|
|
202
|
-
|
|
203
|
-
tc = TreeContext(
|
|
204
|
-
file_path,
|
|
205
|
-
code,
|
|
206
|
-
color=False,
|
|
207
|
-
verbose=False,
|
|
208
|
-
line_number=True,
|
|
209
|
-
)
|
|
210
|
-
|
|
211
|
-
# Find matches
|
|
212
|
-
loi = tc.grep(pattern, ignore_case)
|
|
213
|
-
|
|
214
|
-
if loi:
|
|
215
|
-
if show_context:
|
|
216
|
-
tc.add_lines_of_interest(loi)
|
|
217
|
-
tc.add_context()
|
|
218
|
-
output = tc.format()
|
|
219
|
-
else:
|
|
220
|
-
# Just show matching lines
|
|
221
|
-
output = "\n".join(
|
|
222
|
-
[f"{line}: {code.splitlines()[line - 1]}" for line in loi]
|
|
223
|
-
)
|
|
224
|
-
|
|
225
|
-
results.append(f"\n{file_path}:\n{output}\n")
|
|
226
|
-
match_count += len(loi)
|
|
227
|
-
|
|
228
|
-
except Exception as e:
|
|
229
|
-
await tool_ctx.warning(f"Could not parse {file_path}: {str(e)}")
|
|
230
|
-
|
|
231
|
-
if not results:
|
|
232
|
-
return f"No matches found for '{pattern}' in {path}"
|
|
233
|
-
|
|
234
|
-
output = [f"=== Symbol Search Results for '{pattern}' ==="]
|
|
235
|
-
output.append(f"Found {match_count} matches in {len(results)} files\n")
|
|
236
|
-
output.extend(results)
|
|
237
|
-
|
|
238
|
-
if match_count >= limit:
|
|
239
|
-
output.append(f"\n(Results limited to {limit} matches)")
|
|
240
|
-
|
|
241
|
-
return "\n".join(output)
|
|
242
|
-
|
|
243
|
-
async def _handle_ast(self, params: Dict[str, Any], tool_ctx) -> str:
|
|
244
|
-
"""AST-aware grep - shows code structure context around matches."""
|
|
245
|
-
pattern = params.get("pattern")
|
|
246
|
-
if not pattern:
|
|
247
|
-
return "Error: pattern required for ast action"
|
|
248
|
-
|
|
249
|
-
path = params.get("path", ".")
|
|
250
|
-
ignore_case = params.get("ignore_case", False)
|
|
251
|
-
show_context = params.get("show_context", True)
|
|
252
|
-
limit = params.get("limit", 50)
|
|
253
|
-
|
|
254
|
-
# Validate path
|
|
255
|
-
path_validation = self.validate_path(path)
|
|
256
|
-
if not path_validation.is_valid:
|
|
257
|
-
await tool_ctx.error(f"Invalid path: {path_validation.error_message}")
|
|
258
|
-
return f"Error: Invalid path: {path_validation.error_message}"
|
|
259
|
-
|
|
260
|
-
# Check permissions
|
|
261
|
-
is_allowed, error_message = await self.check_path_allowed(path, tool_ctx)
|
|
262
|
-
if not is_allowed:
|
|
263
|
-
return error_message
|
|
264
|
-
|
|
265
|
-
# Check existence
|
|
266
|
-
is_exists, error_message = await self.check_path_exists(path, tool_ctx)
|
|
267
|
-
if not is_exists:
|
|
268
|
-
return error_message
|
|
269
|
-
|
|
270
|
-
await tool_ctx.info(f"Running AST-aware grep for '{pattern}' in {path}")
|
|
271
|
-
|
|
272
|
-
# Get files to process
|
|
273
|
-
files_to_process = self._get_source_files(path)
|
|
274
|
-
if not files_to_process:
|
|
275
|
-
return f"No source code files found in {path}"
|
|
276
|
-
|
|
277
|
-
# Process files
|
|
278
|
-
results = []
|
|
279
|
-
match_count = 0
|
|
280
|
-
|
|
281
|
-
for file_path in files_to_process:
|
|
282
|
-
if match_count >= limit:
|
|
283
|
-
break
|
|
284
|
-
|
|
285
|
-
try:
|
|
286
|
-
with open(file_path, "r", encoding="utf-8") as f:
|
|
287
|
-
code = f.read()
|
|
288
|
-
|
|
289
|
-
# Create TreeContext for AST parsing
|
|
290
|
-
tc = TreeContext(
|
|
291
|
-
file_path,
|
|
292
|
-
code,
|
|
293
|
-
color=False,
|
|
294
|
-
verbose=False,
|
|
295
|
-
line_number=True,
|
|
296
|
-
)
|
|
297
|
-
|
|
298
|
-
# Find matches with case sensitivity option
|
|
299
|
-
if ignore_case:
|
|
300
|
-
loi = tc.grep(pattern, ignore_case=True)
|
|
301
|
-
else:
|
|
302
|
-
loi = tc.grep(pattern, ignore_case=False)
|
|
303
|
-
|
|
304
|
-
if loi:
|
|
305
|
-
# Always show AST context for grep_ast
|
|
306
|
-
tc.add_lines_of_interest(loi)
|
|
307
|
-
tc.add_context()
|
|
308
|
-
|
|
309
|
-
# Get the formatted output with structure
|
|
310
|
-
output = tc.format()
|
|
311
|
-
|
|
312
|
-
# Add section separator and file info
|
|
313
|
-
results.append(f"\n{'=' * 60}")
|
|
314
|
-
results.append(f"File: {file_path}")
|
|
315
|
-
results.append(f"Matches: {len(loi)}")
|
|
316
|
-
results.append(f"{'=' * 60}\n")
|
|
317
|
-
results.append(output)
|
|
318
|
-
|
|
319
|
-
match_count += len(loi)
|
|
320
|
-
|
|
321
|
-
except Exception as e:
|
|
322
|
-
await tool_ctx.warning(f"Could not parse {file_path}: {str(e)}")
|
|
323
|
-
|
|
324
|
-
if not results:
|
|
325
|
-
return f"No matches found for '{pattern}' in {path}"
|
|
326
|
-
|
|
327
|
-
output = [f"=== AST-aware Grep Results for '{pattern}' ==="]
|
|
328
|
-
output.append(
|
|
329
|
-
f"Total matches: {match_count} in {len([r for r in results if '===' in str(r)]) // 4} files\n"
|
|
330
|
-
)
|
|
331
|
-
output.extend(results)
|
|
332
|
-
|
|
333
|
-
if match_count >= limit:
|
|
334
|
-
output.append(f"\n(Results limited to {limit} matches)")
|
|
335
|
-
|
|
336
|
-
return "\n".join(output)
|
|
337
|
-
|
|
338
|
-
async def _handle_index(self, params: Dict[str, Any], tool_ctx) -> str:
|
|
339
|
-
"""Index symbols in a codebase."""
|
|
340
|
-
path = params.get("path", ".")
|
|
341
|
-
|
|
342
|
-
# Validate path
|
|
343
|
-
is_allowed, error_message = await self.check_path_allowed(path, tool_ctx)
|
|
344
|
-
if not is_allowed:
|
|
345
|
-
return error_message
|
|
346
|
-
|
|
347
|
-
await tool_ctx.info(f"Indexing symbols in {path}...")
|
|
348
|
-
|
|
349
|
-
files_to_process = self._get_source_files(path)
|
|
350
|
-
if not files_to_process:
|
|
351
|
-
return f"No source code files found in {path}"
|
|
352
|
-
|
|
353
|
-
# Clear cache for this path
|
|
354
|
-
self._symbol_cache[path] = {
|
|
355
|
-
"functions": [],
|
|
356
|
-
"classes": [],
|
|
357
|
-
"methods": [],
|
|
358
|
-
"variables": [],
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
indexed_count = 0
|
|
362
|
-
symbol_count = 0
|
|
363
|
-
|
|
364
|
-
for file_path in files_to_process:
|
|
365
|
-
try:
|
|
366
|
-
with open(file_path, "r", encoding="utf-8") as f:
|
|
367
|
-
code = f.read()
|
|
368
|
-
|
|
369
|
-
tc = TreeContext(file_path, code, color=False, verbose=False)
|
|
370
|
-
|
|
371
|
-
# Extract symbols (simplified - would need proper tree-sitter queries)
|
|
372
|
-
# This is a placeholder for actual symbol extraction
|
|
373
|
-
symbols = self._extract_symbols(tc, file_path)
|
|
374
|
-
|
|
375
|
-
for symbol_type, syms in symbols.items():
|
|
376
|
-
self._symbol_cache[path][symbol_type].extend(syms)
|
|
377
|
-
symbol_count += len(syms)
|
|
378
|
-
|
|
379
|
-
indexed_count += 1
|
|
380
|
-
|
|
381
|
-
except Exception as e:
|
|
382
|
-
await tool_ctx.warning(f"Could not index {file_path}: {str(e)}")
|
|
383
|
-
|
|
384
|
-
output = [f"=== Symbol Indexing Complete ==="]
|
|
385
|
-
output.append(f"Indexed {indexed_count} files")
|
|
386
|
-
output.append(f"Found {symbol_count} total symbols:")
|
|
387
|
-
|
|
388
|
-
for symbol_type, symbols in self._symbol_cache[path].items():
|
|
389
|
-
if symbols:
|
|
390
|
-
output.append(f" {symbol_type}: {len(symbols)}")
|
|
391
|
-
|
|
392
|
-
return "\n".join(output)
|
|
393
|
-
|
|
394
|
-
async def _handle_query(self, params: Dict[str, Any], tool_ctx) -> str:
|
|
395
|
-
"""Query indexed symbols."""
|
|
396
|
-
path = params.get("path", ".")
|
|
397
|
-
symbol_type = params.get("symbol_type")
|
|
398
|
-
pattern = params.get("pattern")
|
|
399
|
-
limit = params.get("limit", 50)
|
|
400
|
-
|
|
401
|
-
# Check if we have indexed this path
|
|
402
|
-
if path not in self._symbol_cache:
|
|
403
|
-
return f"No symbols indexed for {path}. Run 'symbols --action index --path {path}' first."
|
|
404
|
-
|
|
405
|
-
symbols = self._symbol_cache[path]
|
|
406
|
-
results = []
|
|
407
|
-
|
|
408
|
-
# Filter by type if specified
|
|
409
|
-
if symbol_type:
|
|
410
|
-
if symbol_type in symbols:
|
|
411
|
-
candidates = symbols[symbol_type]
|
|
412
|
-
else:
|
|
413
|
-
return f"Unknown symbol type: {symbol_type}. Valid types: {', '.join(symbols.keys())}"
|
|
414
|
-
else:
|
|
415
|
-
# Combine all symbol types
|
|
416
|
-
candidates = []
|
|
417
|
-
for syms in symbols.values():
|
|
418
|
-
candidates.extend(syms)
|
|
419
|
-
|
|
420
|
-
# Filter by pattern if specified
|
|
421
|
-
if pattern:
|
|
422
|
-
filtered = []
|
|
423
|
-
for sym in candidates:
|
|
424
|
-
if pattern.lower() in sym["name"].lower():
|
|
425
|
-
filtered.append(sym)
|
|
426
|
-
candidates = filtered
|
|
427
|
-
|
|
428
|
-
# Limit results
|
|
429
|
-
candidates = candidates[:limit]
|
|
430
|
-
|
|
431
|
-
if not candidates:
|
|
432
|
-
return "No symbols found matching criteria"
|
|
433
|
-
|
|
434
|
-
output = [f"=== Symbol Query Results ==="]
|
|
435
|
-
output.append(f"Found {len(candidates)} symbols\n")
|
|
436
|
-
|
|
437
|
-
for sym in candidates:
|
|
438
|
-
output.append(f"{sym['type']}: {sym['name']}")
|
|
439
|
-
output.append(f" File: {sym['file']}:{sym['line']}")
|
|
440
|
-
if sym.get("signature"):
|
|
441
|
-
output.append(f" Signature: {sym['signature']}")
|
|
442
|
-
output.append("")
|
|
443
|
-
|
|
444
|
-
return "\n".join(output)
|
|
445
|
-
|
|
446
|
-
async def _handle_list(self, params: Dict[str, Any], tool_ctx) -> str:
|
|
447
|
-
"""List all symbols in a path."""
|
|
448
|
-
# Similar to query but shows all symbols
|
|
449
|
-
params["pattern"] = None
|
|
450
|
-
return await self._handle_query(params, tool_ctx)
|
|
451
|
-
|
|
452
|
-
def _get_source_files(self, path: str) -> List[str]:
|
|
453
|
-
"""Get all source code files in a path."""
|
|
454
|
-
path_obj = Path(path)
|
|
455
|
-
files_to_process = []
|
|
456
|
-
|
|
457
|
-
# Common source file extensions
|
|
458
|
-
extensions = {
|
|
459
|
-
".py",
|
|
460
|
-
".js",
|
|
461
|
-
".ts",
|
|
462
|
-
".jsx",
|
|
463
|
-
".tsx",
|
|
464
|
-
".java",
|
|
465
|
-
".cpp",
|
|
466
|
-
".c",
|
|
467
|
-
".h",
|
|
468
|
-
".hpp",
|
|
469
|
-
".cs",
|
|
470
|
-
".rb",
|
|
471
|
-
".go",
|
|
472
|
-
".rs",
|
|
473
|
-
".swift",
|
|
474
|
-
".kt",
|
|
475
|
-
".scala",
|
|
476
|
-
".php",
|
|
477
|
-
".lua",
|
|
478
|
-
".r",
|
|
479
|
-
".jl",
|
|
480
|
-
".ex",
|
|
481
|
-
".exs",
|
|
482
|
-
".clj",
|
|
483
|
-
".cljs",
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
if path_obj.is_file():
|
|
487
|
-
if path_obj.suffix in extensions:
|
|
488
|
-
files_to_process.append(str(path_obj))
|
|
489
|
-
elif path_obj.is_dir():
|
|
490
|
-
for root, _, files in os.walk(path_obj):
|
|
491
|
-
for file in files:
|
|
492
|
-
file_path = Path(root) / file
|
|
493
|
-
if file_path.suffix in extensions and self.is_path_allowed(
|
|
494
|
-
str(file_path)
|
|
495
|
-
):
|
|
496
|
-
files_to_process.append(str(file_path))
|
|
497
|
-
|
|
498
|
-
return files_to_process
|
|
499
|
-
|
|
500
|
-
def _extract_symbols(
|
|
501
|
-
self, tc: TreeContext, file_path: str
|
|
502
|
-
) -> Dict[str, List[Dict[str, Any]]]:
|
|
503
|
-
"""Extract symbols from a TreeContext (placeholder implementation)."""
|
|
504
|
-
# This would need proper tree-sitter queries to extract symbols
|
|
505
|
-
# For now, return empty structure
|
|
506
|
-
return {
|
|
507
|
-
"functions": [],
|
|
508
|
-
"classes": [],
|
|
509
|
-
"methods": [],
|
|
510
|
-
"variables": [],
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
def register(self, mcp_server) -> None:
|
|
514
|
-
"""Register this tool with the MCP server."""
|
|
515
|
-
pass
|