hanzo-mcp 0.7.7__py3-none-any.whl → 0.8.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.

Files changed (178) hide show
  1. hanzo_mcp/__init__.py +6 -0
  2. hanzo_mcp/__main__.py +1 -1
  3. hanzo_mcp/analytics/__init__.py +2 -2
  4. hanzo_mcp/analytics/posthog_analytics.py +76 -82
  5. hanzo_mcp/cli.py +31 -36
  6. hanzo_mcp/cli_enhanced.py +94 -72
  7. hanzo_mcp/cli_plugin.py +27 -17
  8. hanzo_mcp/config/__init__.py +2 -2
  9. hanzo_mcp/config/settings.py +112 -88
  10. hanzo_mcp/config/tool_config.py +32 -34
  11. hanzo_mcp/dev_server.py +66 -67
  12. hanzo_mcp/prompts/__init__.py +94 -12
  13. hanzo_mcp/prompts/enhanced_prompts.py +809 -0
  14. hanzo_mcp/prompts/example_custom_prompt.py +6 -5
  15. hanzo_mcp/prompts/project_todo_reminder.py +0 -1
  16. hanzo_mcp/prompts/tool_explorer.py +10 -7
  17. hanzo_mcp/server.py +17 -21
  18. hanzo_mcp/server_enhanced.py +15 -22
  19. hanzo_mcp/tools/__init__.py +56 -28
  20. hanzo_mcp/tools/agent/__init__.py +16 -19
  21. hanzo_mcp/tools/agent/agent.py +82 -65
  22. hanzo_mcp/tools/agent/agent_tool.py +152 -122
  23. hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +66 -62
  24. hanzo_mcp/tools/agent/clarification_protocol.py +55 -50
  25. hanzo_mcp/tools/agent/clarification_tool.py +11 -10
  26. hanzo_mcp/tools/agent/claude_cli_tool.py +21 -20
  27. hanzo_mcp/tools/agent/claude_desktop_auth.py +130 -144
  28. hanzo_mcp/tools/agent/cli_agent_base.py +59 -53
  29. hanzo_mcp/tools/agent/code_auth.py +102 -107
  30. hanzo_mcp/tools/agent/code_auth_tool.py +28 -27
  31. hanzo_mcp/tools/agent/codex_cli_tool.py +20 -19
  32. hanzo_mcp/tools/agent/critic_tool.py +86 -73
  33. hanzo_mcp/tools/agent/gemini_cli_tool.py +21 -20
  34. hanzo_mcp/tools/agent/grok_cli_tool.py +21 -20
  35. hanzo_mcp/tools/agent/iching_tool.py +404 -139
  36. hanzo_mcp/tools/agent/network_tool.py +89 -73
  37. hanzo_mcp/tools/agent/prompt.py +2 -1
  38. hanzo_mcp/tools/agent/review_tool.py +101 -98
  39. hanzo_mcp/tools/agent/swarm_alias.py +87 -0
  40. hanzo_mcp/tools/agent/swarm_tool.py +246 -161
  41. hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +134 -92
  42. hanzo_mcp/tools/agent/tool_adapter.py +21 -11
  43. hanzo_mcp/tools/common/__init__.py +1 -1
  44. hanzo_mcp/tools/common/base.py +3 -5
  45. hanzo_mcp/tools/common/batch_tool.py +46 -39
  46. hanzo_mcp/tools/common/config_tool.py +120 -84
  47. hanzo_mcp/tools/common/context.py +1 -5
  48. hanzo_mcp/tools/common/context_fix.py +5 -3
  49. hanzo_mcp/tools/common/critic_tool.py +4 -8
  50. hanzo_mcp/tools/common/decorators.py +58 -56
  51. hanzo_mcp/tools/common/enhanced_base.py +29 -32
  52. hanzo_mcp/tools/common/fastmcp_pagination.py +91 -94
  53. hanzo_mcp/tools/common/forgiving_edit.py +91 -87
  54. hanzo_mcp/tools/common/mode.py +15 -17
  55. hanzo_mcp/tools/common/mode_loader.py +27 -24
  56. hanzo_mcp/tools/common/paginated_base.py +61 -53
  57. hanzo_mcp/tools/common/paginated_response.py +72 -79
  58. hanzo_mcp/tools/common/pagination.py +50 -53
  59. hanzo_mcp/tools/common/permissions.py +4 -4
  60. hanzo_mcp/tools/common/personality.py +186 -138
  61. hanzo_mcp/tools/common/plugin_loader.py +54 -54
  62. hanzo_mcp/tools/common/stats.py +65 -47
  63. hanzo_mcp/tools/common/test_helpers.py +31 -0
  64. hanzo_mcp/tools/common/thinking_tool.py +4 -8
  65. hanzo_mcp/tools/common/tool_disable.py +17 -12
  66. hanzo_mcp/tools/common/tool_enable.py +13 -14
  67. hanzo_mcp/tools/common/tool_list.py +36 -28
  68. hanzo_mcp/tools/common/truncate.py +23 -23
  69. hanzo_mcp/tools/config/__init__.py +4 -4
  70. hanzo_mcp/tools/config/config_tool.py +42 -29
  71. hanzo_mcp/tools/config/index_config.py +37 -34
  72. hanzo_mcp/tools/config/mode_tool.py +175 -55
  73. hanzo_mcp/tools/database/__init__.py +15 -12
  74. hanzo_mcp/tools/database/database_manager.py +77 -75
  75. hanzo_mcp/tools/database/graph.py +137 -91
  76. hanzo_mcp/tools/database/graph_add.py +30 -18
  77. hanzo_mcp/tools/database/graph_query.py +178 -102
  78. hanzo_mcp/tools/database/graph_remove.py +33 -28
  79. hanzo_mcp/tools/database/graph_search.py +97 -75
  80. hanzo_mcp/tools/database/graph_stats.py +91 -59
  81. hanzo_mcp/tools/database/sql.py +107 -79
  82. hanzo_mcp/tools/database/sql_query.py +30 -24
  83. hanzo_mcp/tools/database/sql_search.py +29 -25
  84. hanzo_mcp/tools/database/sql_stats.py +47 -35
  85. hanzo_mcp/tools/editor/neovim_command.py +25 -28
  86. hanzo_mcp/tools/editor/neovim_edit.py +21 -23
  87. hanzo_mcp/tools/editor/neovim_session.py +60 -54
  88. hanzo_mcp/tools/filesystem/__init__.py +31 -30
  89. hanzo_mcp/tools/filesystem/ast_multi_edit.py +329 -249
  90. hanzo_mcp/tools/filesystem/ast_tool.py +4 -4
  91. hanzo_mcp/tools/filesystem/base.py +1 -1
  92. hanzo_mcp/tools/filesystem/batch_search.py +316 -224
  93. hanzo_mcp/tools/filesystem/content_replace.py +4 -4
  94. hanzo_mcp/tools/filesystem/diff.py +71 -59
  95. hanzo_mcp/tools/filesystem/directory_tree.py +7 -7
  96. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +49 -37
  97. hanzo_mcp/tools/filesystem/edit.py +4 -4
  98. hanzo_mcp/tools/filesystem/find.py +173 -80
  99. hanzo_mcp/tools/filesystem/find_files.py +73 -52
  100. hanzo_mcp/tools/filesystem/git_search.py +157 -104
  101. hanzo_mcp/tools/filesystem/grep.py +8 -8
  102. hanzo_mcp/tools/filesystem/multi_edit.py +4 -8
  103. hanzo_mcp/tools/filesystem/read.py +12 -10
  104. hanzo_mcp/tools/filesystem/rules_tool.py +59 -43
  105. hanzo_mcp/tools/filesystem/search_tool.py +263 -207
  106. hanzo_mcp/tools/filesystem/symbols_tool.py +94 -54
  107. hanzo_mcp/tools/filesystem/tree.py +35 -33
  108. hanzo_mcp/tools/filesystem/unix_aliases.py +13 -18
  109. hanzo_mcp/tools/filesystem/watch.py +37 -36
  110. hanzo_mcp/tools/filesystem/write.py +4 -8
  111. hanzo_mcp/tools/jupyter/__init__.py +4 -4
  112. hanzo_mcp/tools/jupyter/base.py +4 -5
  113. hanzo_mcp/tools/jupyter/jupyter.py +67 -47
  114. hanzo_mcp/tools/jupyter/notebook_edit.py +4 -4
  115. hanzo_mcp/tools/jupyter/notebook_read.py +4 -7
  116. hanzo_mcp/tools/llm/__init__.py +5 -7
  117. hanzo_mcp/tools/llm/consensus_tool.py +72 -52
  118. hanzo_mcp/tools/llm/llm_manage.py +101 -60
  119. hanzo_mcp/tools/llm/llm_tool.py +226 -166
  120. hanzo_mcp/tools/llm/provider_tools.py +25 -26
  121. hanzo_mcp/tools/lsp/__init__.py +1 -1
  122. hanzo_mcp/tools/lsp/lsp_tool.py +228 -143
  123. hanzo_mcp/tools/mcp/__init__.py +2 -3
  124. hanzo_mcp/tools/mcp/mcp_add.py +27 -25
  125. hanzo_mcp/tools/mcp/mcp_remove.py +7 -8
  126. hanzo_mcp/tools/mcp/mcp_stats.py +23 -22
  127. hanzo_mcp/tools/mcp/mcp_tool.py +129 -98
  128. hanzo_mcp/tools/memory/__init__.py +39 -21
  129. hanzo_mcp/tools/memory/knowledge_tools.py +124 -99
  130. hanzo_mcp/tools/memory/memory_tools.py +90 -108
  131. hanzo_mcp/tools/search/__init__.py +7 -2
  132. hanzo_mcp/tools/search/find_tool.py +297 -212
  133. hanzo_mcp/tools/search/unified_search.py +366 -314
  134. hanzo_mcp/tools/shell/__init__.py +8 -7
  135. hanzo_mcp/tools/shell/auto_background.py +56 -49
  136. hanzo_mcp/tools/shell/base.py +1 -1
  137. hanzo_mcp/tools/shell/base_process.py +75 -75
  138. hanzo_mcp/tools/shell/bash_session.py +2 -2
  139. hanzo_mcp/tools/shell/bash_session_executor.py +4 -4
  140. hanzo_mcp/tools/shell/bash_tool.py +24 -31
  141. hanzo_mcp/tools/shell/command_executor.py +12 -12
  142. hanzo_mcp/tools/shell/logs.py +43 -33
  143. hanzo_mcp/tools/shell/npx.py +13 -13
  144. hanzo_mcp/tools/shell/npx_background.py +24 -21
  145. hanzo_mcp/tools/shell/npx_tool.py +18 -22
  146. hanzo_mcp/tools/shell/open.py +19 -21
  147. hanzo_mcp/tools/shell/pkill.py +31 -26
  148. hanzo_mcp/tools/shell/process_tool.py +32 -32
  149. hanzo_mcp/tools/shell/processes.py +57 -58
  150. hanzo_mcp/tools/shell/run_background.py +24 -25
  151. hanzo_mcp/tools/shell/run_command.py +5 -5
  152. hanzo_mcp/tools/shell/run_command_windows.py +5 -5
  153. hanzo_mcp/tools/shell/session_storage.py +3 -3
  154. hanzo_mcp/tools/shell/streaming_command.py +141 -126
  155. hanzo_mcp/tools/shell/uvx.py +24 -25
  156. hanzo_mcp/tools/shell/uvx_background.py +35 -33
  157. hanzo_mcp/tools/shell/uvx_tool.py +18 -22
  158. hanzo_mcp/tools/todo/__init__.py +6 -2
  159. hanzo_mcp/tools/todo/todo.py +50 -37
  160. hanzo_mcp/tools/todo/todo_read.py +5 -8
  161. hanzo_mcp/tools/todo/todo_write.py +5 -7
  162. hanzo_mcp/tools/vector/__init__.py +40 -28
  163. hanzo_mcp/tools/vector/ast_analyzer.py +176 -143
  164. hanzo_mcp/tools/vector/git_ingester.py +170 -179
  165. hanzo_mcp/tools/vector/index_tool.py +96 -44
  166. hanzo_mcp/tools/vector/infinity_store.py +283 -228
  167. hanzo_mcp/tools/vector/mock_infinity.py +39 -40
  168. hanzo_mcp/tools/vector/project_manager.py +88 -78
  169. hanzo_mcp/tools/vector/vector.py +59 -42
  170. hanzo_mcp/tools/vector/vector_index.py +30 -27
  171. hanzo_mcp/tools/vector/vector_search.py +64 -45
  172. hanzo_mcp/types.py +6 -4
  173. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.dist-info}/METADATA +1 -1
  174. hanzo_mcp-0.8.0.dist-info/RECORD +185 -0
  175. hanzo_mcp-0.7.7.dist-info/RECORD +0 -182
  176. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.dist-info}/WHEEL +0 -0
  177. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.dist-info}/entry_points.txt +0 -0
  178. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.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['ASTNode'] = None
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['python'] = tree_sitter.Language(tspython.language())
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='utf-8')
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 == 'python':
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(file_path, content, file_hash, language)
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
- '.py': 'python',
143
- '.js': 'javascript',
144
- '.ts': 'typescript',
145
- '.jsx': 'javascript',
146
- '.tsx': 'typescript',
147
- '.java': 'java',
148
- '.cpp': 'cpp',
149
- '.c': 'c',
150
- '.h': 'c',
151
- '.hpp': 'cpp',
152
- '.rs': 'rust',
153
- '.go': 'go',
154
- '.rb': 'ruby',
155
- '.php': 'php',
156
- '.cs': 'csharp',
157
- '.swift': 'swift',
158
- '.kt': 'kotlin',
159
- '.scala': 'scala',
160
- '.clj': 'clojure',
161
- '.hs': 'haskell',
162
- '.ml': 'ocaml',
163
- '.elm': 'elm',
164
- '.dart': 'dart',
165
- '.lua': 'lua',
166
- '.r': 'r',
167
- '.m': 'objective-c',
168
- '.mm': 'objective-cpp',
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(self, file_path: str, content: str, file_hash: str) -> FileAST:
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 'python' in self.parsers:
196
- parser = tree_sitter.Parser(self.parsers['python'])
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='python',
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(self, file_path: str, content: str, file_hash: str, language: str) -> FileAST:
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('\n')
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 ['javascript', 'typescript', 'java', 'cpp', 'c']:
238
- if 'function ' in line or line.startswith('def ') or ' function(' in line:
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 == 'function' and j + 1 < len(parts):
243
- func_name = parts[j + 1].split('(')[0]
244
- symbols.append(Symbol(
245
- name=func_name,
246
- type='function',
247
- file_path=file_path,
248
- line_start=i,
249
- line_end=i,
250
- column_start=0,
251
- column_end=len(line),
252
- scope='global',
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 'import ' in line or '#include ' in line or 'require(' in line:
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 ['function_definition', 'class_definition', 'identifier']:
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 == 'identifier':
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 = ['global']
317
-
339
+ self.scope_stack = ["global"]
340
+
318
341
  def visit_FunctionDef(self, node):
319
342
  """Visit function definitions."""
320
- scope = '.'.join(self.scope_stack)
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 (node.body and isinstance(node.body[0], ast.Expr) and
326
- isinstance(node.body[0].value, ast.Constant) and
327
- isinstance(node.body[0].value.value, str)):
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='function',
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 != 'global' else None,
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 = '.'.join(self.scope_stack)
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 (node.body and isinstance(node.body[0], ast.Expr) and
367
- isinstance(node.body[0].value, ast.Constant) and
368
- isinstance(node.body[0].value.value, str)):
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 = f"class {node.name}({', '.join(bases)})" if bases else f"class {node.name}"
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='class',
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 != 'global' else None,
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 '.' not in import_name: # Top-level module
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 '.' not in node.module: # Top-level module
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 = f"{node.module}.{alias.name}" if node.module else alias.name
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 = '.'.join(self.scope_stack)
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='variable',
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 != 'global' else None,
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)