hanzo-mcp 0.7.7__py3-none-any.whl → 0.8.1__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 +6 -0
- hanzo_mcp/__main__.py +1 -1
- hanzo_mcp/analytics/__init__.py +2 -2
- hanzo_mcp/analytics/posthog_analytics.py +76 -82
- hanzo_mcp/cli.py +31 -36
- hanzo_mcp/cli_enhanced.py +94 -72
- hanzo_mcp/cli_plugin.py +27 -17
- hanzo_mcp/config/__init__.py +2 -2
- hanzo_mcp/config/settings.py +112 -88
- hanzo_mcp/config/tool_config.py +32 -34
- hanzo_mcp/dev_server.py +66 -67
- hanzo_mcp/prompts/__init__.py +94 -12
- hanzo_mcp/prompts/enhanced_prompts.py +809 -0
- hanzo_mcp/prompts/example_custom_prompt.py +6 -5
- hanzo_mcp/prompts/project_todo_reminder.py +0 -1
- hanzo_mcp/prompts/tool_explorer.py +10 -7
- hanzo_mcp/server.py +17 -21
- hanzo_mcp/server_enhanced.py +15 -22
- hanzo_mcp/tools/__init__.py +56 -28
- hanzo_mcp/tools/agent/__init__.py +16 -19
- hanzo_mcp/tools/agent/agent.py +82 -65
- hanzo_mcp/tools/agent/agent_tool.py +152 -122
- hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +66 -62
- hanzo_mcp/tools/agent/clarification_protocol.py +55 -50
- hanzo_mcp/tools/agent/clarification_tool.py +11 -10
- hanzo_mcp/tools/agent/claude_cli_tool.py +21 -20
- hanzo_mcp/tools/agent/claude_desktop_auth.py +130 -144
- hanzo_mcp/tools/agent/cli_agent_base.py +59 -53
- hanzo_mcp/tools/agent/code_auth.py +102 -107
- hanzo_mcp/tools/agent/code_auth_tool.py +28 -27
- hanzo_mcp/tools/agent/codex_cli_tool.py +20 -19
- hanzo_mcp/tools/agent/critic_tool.py +86 -73
- hanzo_mcp/tools/agent/gemini_cli_tool.py +21 -20
- hanzo_mcp/tools/agent/grok_cli_tool.py +21 -20
- hanzo_mcp/tools/agent/iching_tool.py +404 -139
- hanzo_mcp/tools/agent/network_tool.py +89 -73
- hanzo_mcp/tools/agent/prompt.py +2 -1
- hanzo_mcp/tools/agent/review_tool.py +101 -98
- hanzo_mcp/tools/agent/swarm_alias.py +87 -0
- hanzo_mcp/tools/agent/swarm_tool.py +246 -161
- hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +134 -92
- hanzo_mcp/tools/agent/tool_adapter.py +21 -11
- hanzo_mcp/tools/common/__init__.py +1 -1
- hanzo_mcp/tools/common/base.py +3 -5
- hanzo_mcp/tools/common/batch_tool.py +46 -39
- hanzo_mcp/tools/common/config_tool.py +120 -84
- hanzo_mcp/tools/common/context.py +1 -5
- hanzo_mcp/tools/common/context_fix.py +5 -3
- hanzo_mcp/tools/common/critic_tool.py +4 -8
- hanzo_mcp/tools/common/decorators.py +58 -56
- hanzo_mcp/tools/common/enhanced_base.py +29 -32
- hanzo_mcp/tools/common/fastmcp_pagination.py +91 -94
- hanzo_mcp/tools/common/forgiving_edit.py +91 -87
- hanzo_mcp/tools/common/mode.py +15 -17
- hanzo_mcp/tools/common/mode_loader.py +27 -24
- hanzo_mcp/tools/common/paginated_base.py +61 -53
- hanzo_mcp/tools/common/paginated_response.py +72 -79
- hanzo_mcp/tools/common/pagination.py +50 -53
- hanzo_mcp/tools/common/permissions.py +4 -4
- hanzo_mcp/tools/common/personality.py +186 -138
- hanzo_mcp/tools/common/plugin_loader.py +54 -54
- hanzo_mcp/tools/common/stats.py +65 -47
- hanzo_mcp/tools/common/test_helpers.py +31 -0
- hanzo_mcp/tools/common/thinking_tool.py +4 -8
- hanzo_mcp/tools/common/tool_disable.py +17 -12
- hanzo_mcp/tools/common/tool_enable.py +13 -14
- hanzo_mcp/tools/common/tool_list.py +36 -28
- hanzo_mcp/tools/common/truncate.py +23 -23
- hanzo_mcp/tools/config/__init__.py +4 -4
- hanzo_mcp/tools/config/config_tool.py +42 -29
- hanzo_mcp/tools/config/index_config.py +37 -34
- hanzo_mcp/tools/config/mode_tool.py +175 -55
- hanzo_mcp/tools/database/__init__.py +15 -12
- hanzo_mcp/tools/database/database_manager.py +77 -75
- hanzo_mcp/tools/database/graph.py +137 -91
- hanzo_mcp/tools/database/graph_add.py +30 -18
- hanzo_mcp/tools/database/graph_query.py +178 -102
- hanzo_mcp/tools/database/graph_remove.py +33 -28
- hanzo_mcp/tools/database/graph_search.py +97 -75
- hanzo_mcp/tools/database/graph_stats.py +91 -59
- hanzo_mcp/tools/database/sql.py +107 -79
- hanzo_mcp/tools/database/sql_query.py +30 -24
- hanzo_mcp/tools/database/sql_search.py +29 -25
- hanzo_mcp/tools/database/sql_stats.py +47 -35
- hanzo_mcp/tools/editor/neovim_command.py +25 -28
- hanzo_mcp/tools/editor/neovim_edit.py +21 -23
- hanzo_mcp/tools/editor/neovim_session.py +60 -54
- hanzo_mcp/tools/filesystem/__init__.py +31 -30
- hanzo_mcp/tools/filesystem/ast_multi_edit.py +329 -249
- hanzo_mcp/tools/filesystem/ast_tool.py +4 -4
- hanzo_mcp/tools/filesystem/base.py +1 -1
- hanzo_mcp/tools/filesystem/batch_search.py +316 -224
- hanzo_mcp/tools/filesystem/content_replace.py +4 -4
- hanzo_mcp/tools/filesystem/diff.py +71 -59
- hanzo_mcp/tools/filesystem/directory_tree.py +7 -7
- hanzo_mcp/tools/filesystem/directory_tree_paginated.py +49 -37
- hanzo_mcp/tools/filesystem/edit.py +4 -4
- hanzo_mcp/tools/filesystem/find.py +173 -80
- hanzo_mcp/tools/filesystem/find_files.py +73 -52
- hanzo_mcp/tools/filesystem/git_search.py +157 -104
- hanzo_mcp/tools/filesystem/grep.py +8 -8
- hanzo_mcp/tools/filesystem/multi_edit.py +4 -8
- hanzo_mcp/tools/filesystem/read.py +12 -10
- hanzo_mcp/tools/filesystem/rules_tool.py +59 -43
- hanzo_mcp/tools/filesystem/search_tool.py +263 -207
- hanzo_mcp/tools/filesystem/symbols_tool.py +94 -54
- hanzo_mcp/tools/filesystem/tree.py +35 -33
- hanzo_mcp/tools/filesystem/unix_aliases.py +13 -18
- hanzo_mcp/tools/filesystem/watch.py +37 -36
- hanzo_mcp/tools/filesystem/write.py +4 -8
- hanzo_mcp/tools/jupyter/__init__.py +4 -4
- hanzo_mcp/tools/jupyter/base.py +4 -5
- hanzo_mcp/tools/jupyter/jupyter.py +67 -47
- hanzo_mcp/tools/jupyter/notebook_edit.py +4 -4
- hanzo_mcp/tools/jupyter/notebook_read.py +4 -7
- hanzo_mcp/tools/llm/__init__.py +5 -7
- hanzo_mcp/tools/llm/consensus_tool.py +72 -52
- hanzo_mcp/tools/llm/llm_manage.py +101 -60
- hanzo_mcp/tools/llm/llm_tool.py +226 -166
- hanzo_mcp/tools/llm/provider_tools.py +25 -26
- hanzo_mcp/tools/lsp/__init__.py +1 -1
- hanzo_mcp/tools/lsp/lsp_tool.py +228 -143
- hanzo_mcp/tools/mcp/__init__.py +2 -3
- hanzo_mcp/tools/mcp/mcp_add.py +27 -25
- hanzo_mcp/tools/mcp/mcp_remove.py +7 -8
- hanzo_mcp/tools/mcp/mcp_stats.py +23 -22
- hanzo_mcp/tools/mcp/mcp_tool.py +129 -98
- hanzo_mcp/tools/memory/__init__.py +39 -21
- hanzo_mcp/tools/memory/knowledge_tools.py +124 -99
- hanzo_mcp/tools/memory/memory_tools.py +90 -108
- hanzo_mcp/tools/search/__init__.py +7 -2
- hanzo_mcp/tools/search/find_tool.py +297 -212
- hanzo_mcp/tools/search/unified_search.py +366 -314
- hanzo_mcp/tools/shell/__init__.py +8 -7
- hanzo_mcp/tools/shell/auto_background.py +56 -49
- hanzo_mcp/tools/shell/base.py +1 -1
- hanzo_mcp/tools/shell/base_process.py +75 -75
- hanzo_mcp/tools/shell/bash_session.py +2 -2
- hanzo_mcp/tools/shell/bash_session_executor.py +4 -4
- hanzo_mcp/tools/shell/bash_tool.py +24 -31
- hanzo_mcp/tools/shell/command_executor.py +12 -12
- hanzo_mcp/tools/shell/logs.py +43 -33
- hanzo_mcp/tools/shell/npx.py +13 -13
- hanzo_mcp/tools/shell/npx_background.py +24 -21
- hanzo_mcp/tools/shell/npx_tool.py +18 -22
- hanzo_mcp/tools/shell/open.py +19 -21
- hanzo_mcp/tools/shell/pkill.py +31 -26
- hanzo_mcp/tools/shell/process_tool.py +32 -32
- hanzo_mcp/tools/shell/processes.py +57 -58
- hanzo_mcp/tools/shell/run_background.py +24 -25
- hanzo_mcp/tools/shell/run_command.py +5 -5
- hanzo_mcp/tools/shell/run_command_windows.py +5 -5
- hanzo_mcp/tools/shell/session_storage.py +3 -3
- hanzo_mcp/tools/shell/streaming_command.py +141 -126
- hanzo_mcp/tools/shell/uvx.py +24 -25
- hanzo_mcp/tools/shell/uvx_background.py +35 -33
- hanzo_mcp/tools/shell/uvx_tool.py +18 -22
- hanzo_mcp/tools/todo/__init__.py +6 -2
- hanzo_mcp/tools/todo/todo.py +50 -37
- hanzo_mcp/tools/todo/todo_read.py +5 -8
- hanzo_mcp/tools/todo/todo_write.py +5 -7
- hanzo_mcp/tools/vector/__init__.py +40 -28
- hanzo_mcp/tools/vector/ast_analyzer.py +176 -143
- hanzo_mcp/tools/vector/git_ingester.py +170 -179
- hanzo_mcp/tools/vector/index_tool.py +96 -44
- hanzo_mcp/tools/vector/infinity_store.py +283 -228
- hanzo_mcp/tools/vector/mock_infinity.py +39 -40
- hanzo_mcp/tools/vector/project_manager.py +88 -78
- hanzo_mcp/tools/vector/vector.py +59 -42
- hanzo_mcp/tools/vector/vector_index.py +30 -27
- hanzo_mcp/tools/vector/vector_search.py +64 -45
- hanzo_mcp/types.py +6 -4
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/METADATA +1 -1
- hanzo_mcp-0.8.1.dist-info/RECORD +185 -0
- hanzo_mcp-0.7.7.dist-info/RECORD +0 -182
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/top_level.txt +0 -0
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
"""AST analysis and symbol extraction for code understanding."""
|
|
2
2
|
|
|
3
3
|
import ast
|
|
4
|
-
import json
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from typing import Dict, List, Any, Optional, Set, Tuple
|
|
7
|
-
from dataclasses import dataclass, asdict
|
|
8
4
|
import hashlib
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from dataclasses import asdict, dataclass
|
|
9
8
|
|
|
10
9
|
try:
|
|
11
|
-
import tree_sitter_python as tspython
|
|
12
10
|
import tree_sitter
|
|
11
|
+
import tree_sitter_python as tspython
|
|
12
|
+
|
|
13
13
|
TREE_SITTER_AVAILABLE = True
|
|
14
14
|
except ImportError:
|
|
15
15
|
TREE_SITTER_AVAILABLE = False
|
|
@@ -18,6 +18,7 @@ except ImportError:
|
|
|
18
18
|
@dataclass
|
|
19
19
|
class Symbol:
|
|
20
20
|
"""Represents a code symbol (function, class, variable, etc.)."""
|
|
21
|
+
|
|
21
22
|
name: str
|
|
22
23
|
type: str # function, class, variable, import, etc.
|
|
23
24
|
file_path: str
|
|
@@ -30,24 +31,25 @@ class Symbol:
|
|
|
30
31
|
docstring: Optional[str] = None
|
|
31
32
|
signature: Optional[str] = None
|
|
32
33
|
references: List[str] = None # Files that reference this symbol
|
|
33
|
-
|
|
34
|
+
|
|
34
35
|
def __post_init__(self):
|
|
35
36
|
if self.references is None:
|
|
36
37
|
self.references = []
|
|
37
38
|
|
|
38
39
|
|
|
39
|
-
@dataclass
|
|
40
|
+
@dataclass
|
|
40
41
|
class ASTNode:
|
|
41
42
|
"""Represents an AST node with metadata."""
|
|
43
|
+
|
|
42
44
|
type: str
|
|
43
45
|
name: Optional[str]
|
|
44
46
|
line_start: int
|
|
45
47
|
line_end: int
|
|
46
48
|
column_start: int
|
|
47
49
|
column_end: int
|
|
48
|
-
children: List[
|
|
50
|
+
children: List["ASTNode"] = None
|
|
49
51
|
parent: Optional[str] = None
|
|
50
|
-
|
|
52
|
+
|
|
51
53
|
def __post_init__(self):
|
|
52
54
|
if self.children is None:
|
|
53
55
|
self.children = []
|
|
@@ -56,6 +58,7 @@ class ASTNode:
|
|
|
56
58
|
@dataclass
|
|
57
59
|
class FileAST:
|
|
58
60
|
"""Complete AST representation of a file."""
|
|
61
|
+
|
|
59
62
|
file_path: str
|
|
60
63
|
file_hash: str
|
|
61
64
|
language: str
|
|
@@ -64,7 +67,7 @@ class FileAST:
|
|
|
64
67
|
imports: List[str]
|
|
65
68
|
exports: List[str]
|
|
66
69
|
dependencies: List[str] # Files this file depends on
|
|
67
|
-
|
|
70
|
+
|
|
68
71
|
def to_dict(self) -> Dict[str, Any]:
|
|
69
72
|
"""Convert to dictionary for storage."""
|
|
70
73
|
return {
|
|
@@ -81,182 +84,198 @@ class FileAST:
|
|
|
81
84
|
|
|
82
85
|
class ASTAnalyzer:
|
|
83
86
|
"""Analyzes code files and extracts AST information and symbols."""
|
|
84
|
-
|
|
87
|
+
|
|
85
88
|
def __init__(self):
|
|
86
89
|
"""Initialize the AST analyzer."""
|
|
87
90
|
self.parsers = {}
|
|
88
91
|
self._setup_parsers()
|
|
89
|
-
|
|
92
|
+
|
|
90
93
|
def _setup_parsers(self):
|
|
91
94
|
"""Set up tree-sitter parsers for different languages."""
|
|
92
95
|
if TREE_SITTER_AVAILABLE:
|
|
93
96
|
try:
|
|
94
97
|
# Python parser
|
|
95
|
-
self.parsers[
|
|
98
|
+
self.parsers["python"] = tree_sitter.Language(tspython.language())
|
|
96
99
|
except Exception as e:
|
|
97
100
|
import logging
|
|
101
|
+
|
|
98
102
|
logger = logging.getLogger(__name__)
|
|
99
103
|
logger.warning(f"Could not initialize Python parser: {e}")
|
|
100
|
-
|
|
104
|
+
|
|
101
105
|
def analyze_file(self, file_path: str) -> Optional[FileAST]:
|
|
102
106
|
"""Analyze a file and extract AST information and symbols.
|
|
103
|
-
|
|
107
|
+
|
|
104
108
|
Args:
|
|
105
109
|
file_path: Path to the file to analyze
|
|
106
|
-
|
|
110
|
+
|
|
107
111
|
Returns:
|
|
108
112
|
FileAST object with extracted information, or None if analysis fails
|
|
109
113
|
"""
|
|
110
114
|
path = Path(file_path)
|
|
111
115
|
if not path.exists():
|
|
112
116
|
return None
|
|
113
|
-
|
|
117
|
+
|
|
114
118
|
# Determine language
|
|
115
119
|
language = self._detect_language(path)
|
|
116
120
|
if not language:
|
|
117
121
|
return None
|
|
118
|
-
|
|
122
|
+
|
|
119
123
|
try:
|
|
120
124
|
# Read file content
|
|
121
|
-
content = path.read_text(encoding=
|
|
125
|
+
content = path.read_text(encoding="utf-8")
|
|
122
126
|
file_hash = hashlib.sha256(content.encode()).hexdigest()
|
|
123
|
-
|
|
127
|
+
|
|
124
128
|
# Extract symbols and AST
|
|
125
|
-
if language ==
|
|
129
|
+
if language == "python":
|
|
126
130
|
return self._analyze_python_file(file_path, content, file_hash)
|
|
127
131
|
else:
|
|
128
132
|
# Generic analysis for other languages
|
|
129
|
-
return self._analyze_generic_file(
|
|
130
|
-
|
|
133
|
+
return self._analyze_generic_file(
|
|
134
|
+
file_path, content, file_hash, language
|
|
135
|
+
)
|
|
136
|
+
|
|
131
137
|
except Exception as e:
|
|
132
138
|
import logging
|
|
139
|
+
|
|
133
140
|
logger = logging.getLogger(__name__)
|
|
134
141
|
logger.error(f"Error analyzing file {file_path}: {e}")
|
|
135
142
|
return None
|
|
136
|
-
|
|
143
|
+
|
|
137
144
|
def _detect_language(self, path: Path) -> Optional[str]:
|
|
138
145
|
"""Detect programming language from file extension."""
|
|
139
146
|
extension = path.suffix.lower()
|
|
140
|
-
|
|
147
|
+
|
|
141
148
|
language_map = {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
149
|
+
".py": "python",
|
|
150
|
+
".js": "javascript",
|
|
151
|
+
".ts": "typescript",
|
|
152
|
+
".jsx": "javascript",
|
|
153
|
+
".tsx": "typescript",
|
|
154
|
+
".java": "java",
|
|
155
|
+
".cpp": "cpp",
|
|
156
|
+
".c": "c",
|
|
157
|
+
".h": "c",
|
|
158
|
+
".hpp": "cpp",
|
|
159
|
+
".rs": "rust",
|
|
160
|
+
".go": "go",
|
|
161
|
+
".rb": "ruby",
|
|
162
|
+
".php": "php",
|
|
163
|
+
".cs": "csharp",
|
|
164
|
+
".swift": "swift",
|
|
165
|
+
".kt": "kotlin",
|
|
166
|
+
".scala": "scala",
|
|
167
|
+
".clj": "clojure",
|
|
168
|
+
".hs": "haskell",
|
|
169
|
+
".ml": "ocaml",
|
|
170
|
+
".elm": "elm",
|
|
171
|
+
".dart": "dart",
|
|
172
|
+
".lua": "lua",
|
|
173
|
+
".r": "r",
|
|
174
|
+
".m": "objective-c",
|
|
175
|
+
".mm": "objective-cpp",
|
|
169
176
|
}
|
|
170
|
-
|
|
177
|
+
|
|
171
178
|
return language_map.get(extension)
|
|
172
|
-
|
|
173
|
-
def _analyze_python_file(
|
|
179
|
+
|
|
180
|
+
def _analyze_python_file(
|
|
181
|
+
self, file_path: str, content: str, file_hash: str
|
|
182
|
+
) -> FileAST:
|
|
174
183
|
"""Analyze Python file using both AST and tree-sitter."""
|
|
175
184
|
symbols = []
|
|
176
185
|
ast_nodes = []
|
|
177
186
|
imports = []
|
|
178
187
|
exports = []
|
|
179
188
|
dependencies = []
|
|
180
|
-
|
|
189
|
+
|
|
181
190
|
try:
|
|
182
191
|
# Parse with Python AST
|
|
183
192
|
tree = ast.parse(content)
|
|
184
|
-
|
|
193
|
+
|
|
185
194
|
# Extract symbols using AST visitor
|
|
186
195
|
visitor = PythonSymbolExtractor(file_path)
|
|
187
196
|
visitor.visit(tree)
|
|
188
|
-
|
|
197
|
+
|
|
189
198
|
symbols.extend(visitor.symbols)
|
|
190
199
|
imports.extend(visitor.imports)
|
|
191
200
|
exports.extend(visitor.exports)
|
|
192
201
|
dependencies.extend(visitor.dependencies)
|
|
193
|
-
|
|
202
|
+
|
|
194
203
|
# If tree-sitter is available, get more detailed AST
|
|
195
|
-
if TREE_SITTER_AVAILABLE and
|
|
196
|
-
parser = tree_sitter.Parser(self.parsers[
|
|
204
|
+
if TREE_SITTER_AVAILABLE and "python" in self.parsers:
|
|
205
|
+
parser = tree_sitter.Parser(self.parsers["python"])
|
|
197
206
|
ts_tree = parser.parse(content.encode())
|
|
198
207
|
ast_nodes = self._extract_tree_sitter_nodes(ts_tree.root_node, content)
|
|
199
|
-
|
|
208
|
+
|
|
200
209
|
except SyntaxError as e:
|
|
201
210
|
import logging
|
|
211
|
+
|
|
202
212
|
logger = logging.getLogger(__name__)
|
|
203
213
|
logger.error(f"Syntax error in {file_path}: {e}")
|
|
204
214
|
except Exception as e:
|
|
205
215
|
import logging
|
|
216
|
+
|
|
206
217
|
logger = logging.getLogger(__name__)
|
|
207
218
|
logger.error(f"Error parsing Python file {file_path}: {e}")
|
|
208
|
-
|
|
219
|
+
|
|
209
220
|
return FileAST(
|
|
210
221
|
file_path=file_path,
|
|
211
222
|
file_hash=file_hash,
|
|
212
|
-
language=
|
|
223
|
+
language="python",
|
|
213
224
|
symbols=symbols,
|
|
214
225
|
ast_nodes=ast_nodes,
|
|
215
226
|
imports=imports,
|
|
216
227
|
exports=exports,
|
|
217
228
|
dependencies=dependencies,
|
|
218
229
|
)
|
|
219
|
-
|
|
220
|
-
def _analyze_generic_file(
|
|
230
|
+
|
|
231
|
+
def _analyze_generic_file(
|
|
232
|
+
self, file_path: str, content: str, file_hash: str, language: str
|
|
233
|
+
) -> FileAST:
|
|
221
234
|
"""Generic analysis for non-Python files."""
|
|
222
235
|
# For now, just basic line-based analysis
|
|
223
236
|
# Could be enhanced with language-specific parsers
|
|
224
|
-
|
|
237
|
+
|
|
225
238
|
symbols = []
|
|
226
239
|
ast_nodes = []
|
|
227
240
|
imports = []
|
|
228
241
|
exports = []
|
|
229
242
|
dependencies = []
|
|
230
|
-
|
|
243
|
+
|
|
231
244
|
# Basic pattern matching for common constructs
|
|
232
|
-
lines = content.split(
|
|
245
|
+
lines = content.split("\n")
|
|
233
246
|
for i, line in enumerate(lines, 1):
|
|
234
247
|
line = line.strip()
|
|
235
|
-
|
|
248
|
+
|
|
236
249
|
# Basic function detection (works for many C-style languages)
|
|
237
|
-
if language in [
|
|
238
|
-
if
|
|
250
|
+
if language in ["javascript", "typescript", "java", "cpp", "c"]:
|
|
251
|
+
if (
|
|
252
|
+
"function " in line
|
|
253
|
+
or line.startswith("def ")
|
|
254
|
+
or " function(" in line
|
|
255
|
+
):
|
|
239
256
|
# Extract function name
|
|
240
257
|
parts = line.split()
|
|
241
258
|
for j, part in enumerate(parts):
|
|
242
|
-
if part ==
|
|
243
|
-
func_name = parts[j + 1].split(
|
|
244
|
-
symbols.append(
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
259
|
+
if part == "function" and j + 1 < len(parts):
|
|
260
|
+
func_name = parts[j + 1].split("(")[0]
|
|
261
|
+
symbols.append(
|
|
262
|
+
Symbol(
|
|
263
|
+
name=func_name,
|
|
264
|
+
type="function",
|
|
265
|
+
file_path=file_path,
|
|
266
|
+
line_start=i,
|
|
267
|
+
line_end=i,
|
|
268
|
+
column_start=0,
|
|
269
|
+
column_end=len(line),
|
|
270
|
+
scope="global",
|
|
271
|
+
)
|
|
272
|
+
)
|
|
254
273
|
break
|
|
255
|
-
|
|
274
|
+
|
|
256
275
|
# Basic import detection
|
|
257
|
-
if
|
|
276
|
+
if "import " in line or "#include " in line or "require(" in line:
|
|
258
277
|
imports.append(line)
|
|
259
|
-
|
|
278
|
+
|
|
260
279
|
return FileAST(
|
|
261
280
|
file_path=file_path,
|
|
262
281
|
file_hash=file_hash,
|
|
@@ -267,23 +286,27 @@ class ASTAnalyzer:
|
|
|
267
286
|
exports=exports,
|
|
268
287
|
dependencies=dependencies,
|
|
269
288
|
)
|
|
270
|
-
|
|
289
|
+
|
|
271
290
|
def _extract_tree_sitter_nodes(self, node, content: str) -> List[ASTNode]:
|
|
272
291
|
"""Extract AST nodes from tree-sitter parse tree."""
|
|
273
292
|
nodes = []
|
|
274
|
-
|
|
293
|
+
|
|
275
294
|
def traverse(ts_node, parent_name=None):
|
|
276
295
|
node_name = None
|
|
277
|
-
|
|
296
|
+
|
|
278
297
|
# Try to extract node name for named nodes
|
|
279
|
-
if ts_node.type in [
|
|
298
|
+
if ts_node.type in [
|
|
299
|
+
"function_definition",
|
|
300
|
+
"class_definition",
|
|
301
|
+
"identifier",
|
|
302
|
+
]:
|
|
280
303
|
for child in ts_node.children:
|
|
281
|
-
if child.type ==
|
|
304
|
+
if child.type == "identifier":
|
|
282
305
|
start_byte = child.start_byte
|
|
283
306
|
end_byte = child.end_byte
|
|
284
307
|
node_name = content[start_byte:end_byte]
|
|
285
308
|
break
|
|
286
|
-
|
|
309
|
+
|
|
287
310
|
ast_node = ASTNode(
|
|
288
311
|
type=ts_node.type,
|
|
289
312
|
name=node_name,
|
|
@@ -293,148 +316,158 @@ class ASTAnalyzer:
|
|
|
293
316
|
column_end=ts_node.end_point[1],
|
|
294
317
|
parent=parent_name,
|
|
295
318
|
)
|
|
296
|
-
|
|
319
|
+
|
|
297
320
|
nodes.append(ast_node)
|
|
298
|
-
|
|
321
|
+
|
|
299
322
|
# Recursively process children
|
|
300
323
|
for child in ts_node.children:
|
|
301
324
|
traverse(child, node_name or parent_name)
|
|
302
|
-
|
|
325
|
+
|
|
303
326
|
traverse(node)
|
|
304
327
|
return nodes
|
|
305
328
|
|
|
306
329
|
|
|
307
330
|
class PythonSymbolExtractor(ast.NodeVisitor):
|
|
308
331
|
"""AST visitor for extracting Python symbols."""
|
|
309
|
-
|
|
332
|
+
|
|
310
333
|
def __init__(self, file_path: str):
|
|
311
334
|
self.file_path = file_path
|
|
312
335
|
self.symbols = []
|
|
313
336
|
self.imports = []
|
|
314
337
|
self.exports = []
|
|
315
338
|
self.dependencies = []
|
|
316
|
-
self.scope_stack = [
|
|
317
|
-
|
|
339
|
+
self.scope_stack = ["global"]
|
|
340
|
+
|
|
318
341
|
def visit_FunctionDef(self, node):
|
|
319
342
|
"""Visit function definitions."""
|
|
320
|
-
scope =
|
|
343
|
+
scope = ".".join(self.scope_stack)
|
|
321
344
|
parent = self.scope_stack[-1] if len(self.scope_stack) > 1 else None
|
|
322
|
-
|
|
345
|
+
|
|
323
346
|
# Extract docstring
|
|
324
347
|
docstring = None
|
|
325
|
-
if (
|
|
326
|
-
|
|
327
|
-
isinstance(node.body[0]
|
|
348
|
+
if (
|
|
349
|
+
node.body
|
|
350
|
+
and isinstance(node.body[0], ast.Expr)
|
|
351
|
+
and isinstance(node.body[0].value, ast.Constant)
|
|
352
|
+
and isinstance(node.body[0].value.value, str)
|
|
353
|
+
):
|
|
328
354
|
docstring = node.body[0].value.value
|
|
329
|
-
|
|
355
|
+
|
|
330
356
|
# Create function signature
|
|
331
357
|
args = [arg.arg for arg in node.args.args]
|
|
332
358
|
signature = f"{node.name}({', '.join(args)})"
|
|
333
|
-
|
|
359
|
+
|
|
334
360
|
symbol = Symbol(
|
|
335
361
|
name=node.name,
|
|
336
|
-
type=
|
|
362
|
+
type="function",
|
|
337
363
|
file_path=self.file_path,
|
|
338
364
|
line_start=node.lineno,
|
|
339
365
|
line_end=node.end_lineno or node.lineno,
|
|
340
366
|
column_start=node.col_offset,
|
|
341
367
|
column_end=node.end_col_offset or 0,
|
|
342
368
|
scope=scope,
|
|
343
|
-
parent=parent if parent !=
|
|
369
|
+
parent=parent if parent != "global" else None,
|
|
344
370
|
docstring=docstring,
|
|
345
371
|
signature=signature,
|
|
346
372
|
)
|
|
347
|
-
|
|
373
|
+
|
|
348
374
|
self.symbols.append(symbol)
|
|
349
|
-
|
|
375
|
+
|
|
350
376
|
# Enter function scope
|
|
351
377
|
self.scope_stack.append(node.name)
|
|
352
378
|
self.generic_visit(node)
|
|
353
379
|
self.scope_stack.pop()
|
|
354
|
-
|
|
380
|
+
|
|
355
381
|
def visit_AsyncFunctionDef(self, node):
|
|
356
382
|
"""Visit async function definitions."""
|
|
357
383
|
self.visit_FunctionDef(node) # Same logic
|
|
358
|
-
|
|
384
|
+
|
|
359
385
|
def visit_ClassDef(self, node):
|
|
360
386
|
"""Visit class definitions."""
|
|
361
|
-
scope =
|
|
387
|
+
scope = ".".join(self.scope_stack)
|
|
362
388
|
parent = self.scope_stack[-1] if len(self.scope_stack) > 1 else None
|
|
363
|
-
|
|
389
|
+
|
|
364
390
|
# Extract docstring
|
|
365
391
|
docstring = None
|
|
366
|
-
if (
|
|
367
|
-
|
|
368
|
-
isinstance(node.body[0]
|
|
392
|
+
if (
|
|
393
|
+
node.body
|
|
394
|
+
and isinstance(node.body[0], ast.Expr)
|
|
395
|
+
and isinstance(node.body[0].value, ast.Constant)
|
|
396
|
+
and isinstance(node.body[0].value.value, str)
|
|
397
|
+
):
|
|
369
398
|
docstring = node.body[0].value.value
|
|
370
|
-
|
|
399
|
+
|
|
371
400
|
# Extract base classes
|
|
372
401
|
bases = [self._get_name(base) for base in node.bases]
|
|
373
|
-
signature =
|
|
374
|
-
|
|
402
|
+
signature = (
|
|
403
|
+
f"class {node.name}({', '.join(bases)})" if bases else f"class {node.name}"
|
|
404
|
+
)
|
|
405
|
+
|
|
375
406
|
symbol = Symbol(
|
|
376
407
|
name=node.name,
|
|
377
|
-
type=
|
|
408
|
+
type="class",
|
|
378
409
|
file_path=self.file_path,
|
|
379
410
|
line_start=node.lineno,
|
|
380
411
|
line_end=node.end_lineno or node.lineno,
|
|
381
412
|
column_start=node.col_offset,
|
|
382
413
|
column_end=node.end_col_offset or 0,
|
|
383
414
|
scope=scope,
|
|
384
|
-
parent=parent if parent !=
|
|
415
|
+
parent=parent if parent != "global" else None,
|
|
385
416
|
docstring=docstring,
|
|
386
417
|
signature=signature,
|
|
387
418
|
)
|
|
388
|
-
|
|
419
|
+
|
|
389
420
|
self.symbols.append(symbol)
|
|
390
|
-
|
|
421
|
+
|
|
391
422
|
# Enter class scope
|
|
392
423
|
self.scope_stack.append(node.name)
|
|
393
424
|
self.generic_visit(node)
|
|
394
425
|
self.scope_stack.pop()
|
|
395
|
-
|
|
426
|
+
|
|
396
427
|
def visit_Import(self, node):
|
|
397
428
|
"""Visit import statements."""
|
|
398
429
|
for alias in node.names:
|
|
399
430
|
import_name = alias.name
|
|
400
431
|
self.imports.append(import_name)
|
|
401
|
-
if
|
|
432
|
+
if "." not in import_name: # Top-level module
|
|
402
433
|
self.dependencies.append(import_name)
|
|
403
|
-
|
|
434
|
+
|
|
404
435
|
def visit_ImportFrom(self, node):
|
|
405
436
|
"""Visit from...import statements."""
|
|
406
437
|
if node.module:
|
|
407
438
|
self.imports.append(node.module)
|
|
408
|
-
if
|
|
439
|
+
if "." not in node.module: # Top-level module
|
|
409
440
|
self.dependencies.append(node.module)
|
|
410
|
-
|
|
441
|
+
|
|
411
442
|
for alias in node.names:
|
|
412
|
-
if alias.name !=
|
|
413
|
-
import_item =
|
|
443
|
+
if alias.name != "*":
|
|
444
|
+
import_item = (
|
|
445
|
+
f"{node.module}.{alias.name}" if node.module else alias.name
|
|
446
|
+
)
|
|
414
447
|
self.imports.append(import_item)
|
|
415
|
-
|
|
448
|
+
|
|
416
449
|
def visit_Assign(self, node):
|
|
417
450
|
"""Visit variable assignments."""
|
|
418
|
-
scope =
|
|
451
|
+
scope = ".".join(self.scope_stack)
|
|
419
452
|
parent = self.scope_stack[-1] if len(self.scope_stack) > 1 else None
|
|
420
|
-
|
|
453
|
+
|
|
421
454
|
for target in node.targets:
|
|
422
455
|
if isinstance(target, ast.Name):
|
|
423
456
|
symbol = Symbol(
|
|
424
457
|
name=target.id,
|
|
425
|
-
type=
|
|
458
|
+
type="variable",
|
|
426
459
|
file_path=self.file_path,
|
|
427
460
|
line_start=node.lineno,
|
|
428
461
|
line_end=node.end_lineno or node.lineno,
|
|
429
462
|
column_start=node.col_offset,
|
|
430
463
|
column_end=node.end_col_offset or 0,
|
|
431
464
|
scope=scope,
|
|
432
|
-
parent=parent if parent !=
|
|
465
|
+
parent=parent if parent != "global" else None,
|
|
433
466
|
)
|
|
434
467
|
self.symbols.append(symbol)
|
|
435
|
-
|
|
468
|
+
|
|
436
469
|
self.generic_visit(node)
|
|
437
|
-
|
|
470
|
+
|
|
438
471
|
def _get_name(self, node):
|
|
439
472
|
"""Extract name from AST node."""
|
|
440
473
|
if isinstance(node, ast.Name):
|
|
@@ -454,14 +487,14 @@ def create_symbol_embedding_text(symbol: Symbol) -> str:
|
|
|
454
487
|
f"Type: {symbol.type}",
|
|
455
488
|
f"Scope: {symbol.scope}",
|
|
456
489
|
]
|
|
457
|
-
|
|
490
|
+
|
|
458
491
|
if symbol.parent:
|
|
459
492
|
parts.append(f"Parent: {symbol.parent}")
|
|
460
|
-
|
|
493
|
+
|
|
461
494
|
if symbol.signature:
|
|
462
495
|
parts.append(f"Signature: {symbol.signature}")
|
|
463
|
-
|
|
496
|
+
|
|
464
497
|
if symbol.docstring:
|
|
465
498
|
parts.append(f"Documentation: {symbol.docstring}")
|
|
466
|
-
|
|
467
|
-
return " | ".join(parts)
|
|
499
|
+
|
|
500
|
+
return " | ".join(parts)
|