hanzo-mcp 0.8.11__py3-none-any.whl → 0.8.14__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of hanzo-mcp might be problematic. Click here for more details.

Files changed (154) hide show
  1. hanzo_mcp/__init__.py +2 -4
  2. hanzo_mcp/analytics/posthog_analytics.py +3 -9
  3. hanzo_mcp/bridge.py +9 -25
  4. hanzo_mcp/cli.py +6 -15
  5. hanzo_mcp/cli_enhanced.py +5 -14
  6. hanzo_mcp/cli_plugin.py +3 -9
  7. hanzo_mcp/config/settings.py +6 -20
  8. hanzo_mcp/config/tool_config.py +1 -3
  9. hanzo_mcp/core/base_agent.py +88 -88
  10. hanzo_mcp/core/model_registry.py +238 -210
  11. hanzo_mcp/dev_server.py +5 -15
  12. hanzo_mcp/prompts/__init__.py +2 -6
  13. hanzo_mcp/prompts/project_todo_reminder.py +3 -9
  14. hanzo_mcp/prompts/tool_explorer.py +1 -3
  15. hanzo_mcp/prompts/utils.py +7 -21
  16. hanzo_mcp/server.py +13 -6
  17. hanzo_mcp/tools/__init__.py +10 -24
  18. hanzo_mcp/tools/agent/__init__.py +2 -1
  19. hanzo_mcp/tools/agent/agent.py +10 -30
  20. hanzo_mcp/tools/agent/agent_tool.py +5 -15
  21. hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +14 -41
  22. hanzo_mcp/tools/agent/claude_desktop_auth.py +3 -9
  23. hanzo_mcp/tools/agent/cli_agent_base.py +7 -24
  24. hanzo_mcp/tools/agent/cli_tools.py +75 -74
  25. hanzo_mcp/tools/agent/code_auth.py +1 -3
  26. hanzo_mcp/tools/agent/code_auth_tool.py +2 -6
  27. hanzo_mcp/tools/agent/critic_tool.py +8 -24
  28. hanzo_mcp/tools/agent/iching_tool.py +12 -36
  29. hanzo_mcp/tools/agent/network_tool.py +7 -18
  30. hanzo_mcp/tools/agent/prompt.py +1 -5
  31. hanzo_mcp/tools/agent/review_tool.py +10 -25
  32. hanzo_mcp/tools/agent/swarm_alias.py +1 -3
  33. hanzo_mcp/tools/agent/swarm_tool.py +9 -29
  34. hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +11 -39
  35. hanzo_mcp/tools/agent/unified_cli_tools.py +38 -38
  36. hanzo_mcp/tools/common/batch_tool.py +15 -45
  37. hanzo_mcp/tools/common/config_tool.py +9 -28
  38. hanzo_mcp/tools/common/context.py +1 -3
  39. hanzo_mcp/tools/common/critic_tool.py +1 -3
  40. hanzo_mcp/tools/common/decorators.py +2 -6
  41. hanzo_mcp/tools/common/enhanced_base.py +2 -6
  42. hanzo_mcp/tools/common/fastmcp_pagination.py +4 -12
  43. hanzo_mcp/tools/common/forgiving_edit.py +9 -28
  44. hanzo_mcp/tools/common/mode.py +1 -5
  45. hanzo_mcp/tools/common/paginated_base.py +3 -11
  46. hanzo_mcp/tools/common/paginated_response.py +10 -30
  47. hanzo_mcp/tools/common/pagination.py +3 -9
  48. hanzo_mcp/tools/common/permissions.py +38 -11
  49. hanzo_mcp/tools/common/personality.py +9 -34
  50. hanzo_mcp/tools/common/plugin_loader.py +3 -15
  51. hanzo_mcp/tools/common/stats.py +6 -18
  52. hanzo_mcp/tools/common/thinking_tool.py +1 -3
  53. hanzo_mcp/tools/common/tool_disable.py +2 -6
  54. hanzo_mcp/tools/common/tool_list.py +2 -6
  55. hanzo_mcp/tools/common/validation.py +1 -3
  56. hanzo_mcp/tools/config/config_tool.py +7 -13
  57. hanzo_mcp/tools/config/index_config.py +1 -3
  58. hanzo_mcp/tools/config/mode_tool.py +5 -15
  59. hanzo_mcp/tools/database/database_manager.py +3 -9
  60. hanzo_mcp/tools/database/graph.py +1 -3
  61. hanzo_mcp/tools/database/graph_add.py +3 -9
  62. hanzo_mcp/tools/database/graph_query.py +11 -34
  63. hanzo_mcp/tools/database/graph_remove.py +3 -9
  64. hanzo_mcp/tools/database/graph_search.py +6 -20
  65. hanzo_mcp/tools/database/graph_stats.py +11 -33
  66. hanzo_mcp/tools/database/sql.py +4 -12
  67. hanzo_mcp/tools/database/sql_query.py +6 -10
  68. hanzo_mcp/tools/database/sql_search.py +2 -6
  69. hanzo_mcp/tools/database/sql_stats.py +5 -15
  70. hanzo_mcp/tools/editor/neovim_command.py +1 -3
  71. hanzo_mcp/tools/editor/neovim_edit.py +2 -2
  72. hanzo_mcp/tools/editor/neovim_session.py +7 -13
  73. hanzo_mcp/tools/filesystem/__init__.py +2 -3
  74. hanzo_mcp/tools/filesystem/ast_multi_edit.py +14 -43
  75. hanzo_mcp/tools/filesystem/base.py +4 -12
  76. hanzo_mcp/tools/filesystem/batch_search.py +35 -115
  77. hanzo_mcp/tools/filesystem/content_replace.py +4 -12
  78. hanzo_mcp/tools/filesystem/diff.py +2 -10
  79. hanzo_mcp/tools/filesystem/directory_tree.py +9 -27
  80. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +5 -15
  81. hanzo_mcp/tools/filesystem/edit.py +6 -18
  82. hanzo_mcp/tools/filesystem/find.py +3 -9
  83. hanzo_mcp/tools/filesystem/find_files.py +2 -6
  84. hanzo_mcp/tools/filesystem/git_search.py +9 -24
  85. hanzo_mcp/tools/filesystem/grep.py +9 -27
  86. hanzo_mcp/tools/filesystem/multi_edit.py +6 -18
  87. hanzo_mcp/tools/filesystem/read.py +8 -26
  88. hanzo_mcp/tools/filesystem/rules_tool.py +6 -17
  89. hanzo_mcp/tools/filesystem/search_tool.py +18 -62
  90. hanzo_mcp/tools/filesystem/symbols_tool.py +5 -15
  91. hanzo_mcp/tools/filesystem/tree.py +1 -3
  92. hanzo_mcp/tools/filesystem/watch.py +1 -3
  93. hanzo_mcp/tools/filesystem/write.py +1 -3
  94. hanzo_mcp/tools/jupyter/base.py +6 -20
  95. hanzo_mcp/tools/jupyter/jupyter.py +4 -12
  96. hanzo_mcp/tools/jupyter/notebook_edit.py +11 -35
  97. hanzo_mcp/tools/jupyter/notebook_read.py +2 -6
  98. hanzo_mcp/tools/llm/consensus_tool.py +8 -24
  99. hanzo_mcp/tools/llm/llm_manage.py +2 -6
  100. hanzo_mcp/tools/llm/llm_tool.py +17 -58
  101. hanzo_mcp/tools/llm/llm_unified.py +18 -59
  102. hanzo_mcp/tools/llm/provider_tools.py +1 -3
  103. hanzo_mcp/tools/lsp/lsp_tool.py +5 -17
  104. hanzo_mcp/tools/mcp/mcp_add.py +1 -3
  105. hanzo_mcp/tools/mcp/mcp_stats.py +1 -3
  106. hanzo_mcp/tools/mcp/mcp_tool.py +9 -23
  107. hanzo_mcp/tools/memory/__init__.py +10 -27
  108. hanzo_mcp/tools/memory/knowledge_tools.py +7 -25
  109. hanzo_mcp/tools/memory/memory_tools.py +6 -18
  110. hanzo_mcp/tools/search/find_tool.py +10 -32
  111. hanzo_mcp/tools/search/unified_search.py +24 -78
  112. hanzo_mcp/tools/shell/__init__.py +2 -2
  113. hanzo_mcp/tools/shell/auto_background.py +2 -6
  114. hanzo_mcp/tools/shell/base.py +1 -5
  115. hanzo_mcp/tools/shell/base_process.py +5 -7
  116. hanzo_mcp/tools/shell/bash_session.py +7 -24
  117. hanzo_mcp/tools/shell/bash_session_executor.py +5 -15
  118. hanzo_mcp/tools/shell/bash_tool.py +3 -7
  119. hanzo_mcp/tools/shell/command_executor.py +33 -86
  120. hanzo_mcp/tools/shell/logs.py +4 -16
  121. hanzo_mcp/tools/shell/npx.py +2 -8
  122. hanzo_mcp/tools/shell/npx_tool.py +1 -3
  123. hanzo_mcp/tools/shell/pkill.py +4 -12
  124. hanzo_mcp/tools/shell/process_tool.py +2 -8
  125. hanzo_mcp/tools/shell/processes.py +5 -17
  126. hanzo_mcp/tools/shell/run_background.py +1 -3
  127. hanzo_mcp/tools/shell/run_command.py +1 -3
  128. hanzo_mcp/tools/shell/run_command_windows.py +1 -3
  129. hanzo_mcp/tools/shell/session_manager.py +2 -6
  130. hanzo_mcp/tools/shell/session_storage.py +2 -6
  131. hanzo_mcp/tools/shell/streaming_command.py +7 -23
  132. hanzo_mcp/tools/shell/uvx.py +4 -14
  133. hanzo_mcp/tools/shell/uvx_background.py +2 -6
  134. hanzo_mcp/tools/shell/uvx_tool.py +1 -3
  135. hanzo_mcp/tools/shell/zsh_tool.py +12 -20
  136. hanzo_mcp/tools/todo/todo.py +1 -3
  137. hanzo_mcp/tools/todo/todo_read.py +3 -9
  138. hanzo_mcp/tools/todo/todo_write.py +6 -18
  139. hanzo_mcp/tools/vector/__init__.py +3 -9
  140. hanzo_mcp/tools/vector/ast_analyzer.py +6 -20
  141. hanzo_mcp/tools/vector/git_ingester.py +10 -30
  142. hanzo_mcp/tools/vector/index_tool.py +3 -9
  143. hanzo_mcp/tools/vector/infinity_store.py +7 -27
  144. hanzo_mcp/tools/vector/mock_infinity.py +1 -3
  145. hanzo_mcp/tools/vector/project_manager.py +4 -12
  146. hanzo_mcp/tools/vector/vector.py +2 -6
  147. hanzo_mcp/tools/vector/vector_index.py +8 -8
  148. hanzo_mcp/tools/vector/vector_search.py +7 -21
  149. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.14.dist-info}/METADATA +2 -2
  150. hanzo_mcp-0.8.14.dist-info/RECORD +193 -0
  151. hanzo_mcp-0.8.11.dist-info/RECORD +0 -193
  152. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.14.dist-info}/WHEEL +0 -0
  153. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.14.dist-info}/entry_points.txt +0 -0
  154. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.14.dist-info}/top_level.txt +0 -0
@@ -135,9 +135,7 @@ For long-running servers, use uvx_background instead.
135
135
 
136
136
  try:
137
137
  # Run installation
138
- install_result = subprocess.run(
139
- install_cmd, shell=True, capture_output=True, text=True, timeout=60
140
- )
138
+ install_result = subprocess.run(install_cmd, shell=True, capture_output=True, text=True, timeout=60)
141
139
 
142
140
  if install_result.returncode == 0:
143
141
  await tool_ctx.info("uvx installed successfully!")
@@ -146,9 +144,7 @@ For long-running servers, use uvx_background instead.
146
144
  import os
147
145
 
148
146
  home = os.path.expanduser("~")
149
- os.environ["PATH"] = (
150
- f"{home}/.cargo/bin:{os.environ.get('PATH', '')}"
151
- )
147
+ os.environ["PATH"] = f"{home}/.cargo/bin:{os.environ.get('PATH', '')}"
152
148
 
153
149
  # Check again
154
150
  if not shutil.which("uvx"):
@@ -197,9 +193,7 @@ curl -LsSf https://astral.sh/uv/install.sh | sh"""
197
193
 
198
194
  try:
199
195
  # Execute command
200
- result = subprocess.run(
201
- cmd, capture_output=True, text=True, timeout=timeout, check=True
202
- )
196
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, check=True)
203
197
 
204
198
  output = []
205
199
  if result.stdout:
@@ -207,11 +201,7 @@ curl -LsSf https://astral.sh/uv/install.sh | sh"""
207
201
  if result.stderr:
208
202
  output.append(f"\nSTDERR:\n{result.stderr}")
209
203
 
210
- return (
211
- "\n".join(output)
212
- if output
213
- else "Command completed successfully with no output."
214
- )
204
+ return "\n".join(output) if output else "Command completed successfully with no output."
215
205
 
216
206
  except subprocess.TimeoutExpired:
217
207
  return f"Error: Command timed out after {timeout} seconds. Use uvx_background for long-running processes."
@@ -156,9 +156,7 @@ Use 'processes' to list running processes and 'pkill' to stop them.
156
156
 
157
157
  try:
158
158
  # Run installation
159
- install_result = subprocess.run(
160
- install_cmd, shell=True, capture_output=True, text=True, timeout=60
161
- )
159
+ install_result = subprocess.run(install_cmd, shell=True, capture_output=True, text=True, timeout=60)
162
160
 
163
161
  if install_result.returncode == 0:
164
162
  await tool_ctx.info("uvx installed successfully!")
@@ -167,9 +165,7 @@ Use 'processes' to list running processes and 'pkill' to stop them.
167
165
  import os
168
166
 
169
167
  home = os.path.expanduser("~")
170
- os.environ["PATH"] = (
171
- f"{home}/.cargo/bin:{os.environ.get('PATH', '')}"
172
- )
168
+ os.environ["PATH"] = f"{home}/.cargo/bin:{os.environ.get('PATH', '')}"
173
169
 
174
170
  # Check again
175
171
  if not shutil.which("uvx"):
@@ -86,9 +86,7 @@ uvx jupyter lab --port 8888 # Auto-backgrounds if needed"""
86
86
  cwd: Optional[str] = None,
87
87
  python: Optional[str] = None,
88
88
  ) -> str:
89
- return await tool_self.run(
90
- ctx, package=package, args=args, cwd=cwd, python=python
91
- )
89
+ return await tool_self.run(ctx, package=package, args=args, cwd=cwd, python=python)
92
90
 
93
91
  async def call(self, ctx: MCPContext, **params) -> str:
94
92
  """Call the tool with arguments."""
@@ -29,9 +29,7 @@ class ZshTool(BaseScriptTool):
29
29
  env: Optional[dict[str, str]] = None,
30
30
  timeout: Optional[int] = None,
31
31
  ) -> str:
32
- return await tool_self.run(
33
- ctx, command=command, cwd=cwd, env=env, timeout=timeout
34
- )
32
+ return await tool_self.run(ctx, command=command, cwd=cwd, env=env, timeout=timeout)
35
33
 
36
34
  async def call(self, ctx: MCPContext, **params) -> str:
37
35
  """Call the tool with arguments."""
@@ -79,12 +77,12 @@ zsh "npm run dev" --cwd ./frontend # Auto-backgrounds if needed"""
79
77
  return path
80
78
  # Fall back to bash if no zsh found
81
79
  return "bash"
82
-
80
+
83
81
  # On Unix-like systems, check for zsh
84
82
  zsh_path = shutil.which("zsh")
85
83
  if zsh_path:
86
84
  return zsh_path
87
-
85
+
88
86
  # Fall back to bash if zsh not found
89
87
  return "bash"
90
88
 
@@ -124,14 +122,12 @@ zsh "npm run dev" --cwd ./frontend # Auto-backgrounds if needed"""
124
122
  # Check if zsh is available
125
123
  if not shutil.which("zsh") and platform.system() != "Windows":
126
124
  return "Error: Zsh is not installed. Please install zsh first."
127
-
125
+
128
126
  # Prepare working directory
129
127
  work_dir = Path(cwd).resolve() if cwd else Path.cwd()
130
128
 
131
129
  # Use execute_sync which has auto-backgrounding
132
- output = await self.execute_sync(
133
- command, cwd=work_dir, env=env, timeout=timeout
134
- )
130
+ output = await self.execute_sync(command, cwd=work_dir, env=env, timeout=timeout)
135
131
  return output if output else "Command completed successfully (no output)"
136
132
 
137
133
 
@@ -152,12 +148,12 @@ class ShellTool(BaseScriptTool):
152
148
  # Also check if .zshrc exists
153
149
  if (Path.home() / ".zshrc").exists():
154
150
  return "zsh"
155
-
151
+
156
152
  # Check for user's preferred shell
157
153
  user_shell = os.environ.get("SHELL", "")
158
154
  if user_shell and Path(user_shell).exists():
159
155
  return user_shell
160
-
156
+
161
157
  # Default to bash
162
158
  return "bash"
163
159
 
@@ -173,9 +169,7 @@ class ShellTool(BaseScriptTool):
173
169
  env: Optional[dict[str, str]] = None,
174
170
  timeout: Optional[int] = None,
175
171
  ) -> str:
176
- return await tool_self.run(
177
- ctx, command=command, cwd=cwd, env=env, timeout=timeout
178
- )
172
+ return await tool_self.run(ctx, command=command, cwd=cwd, env=env, timeout=timeout)
179
173
 
180
174
  async def call(self, ctx: MCPContext, **params) -> str:
181
175
  """Call the tool with arguments."""
@@ -249,12 +243,10 @@ shell "npm run dev" --cwd ./frontend # Auto-backgrounds if needed"""
249
243
 
250
244
  # Add shell info to output if verbose
251
245
  shell_name = os.path.basename(self._best_shell)
252
-
246
+
253
247
  # Use execute_sync which has auto-backgrounding
254
- output = await self.execute_sync(
255
- command, cwd=work_dir, env=env, timeout=timeout
256
- )
257
-
248
+ output = await self.execute_sync(command, cwd=work_dir, env=env, timeout=timeout)
249
+
258
250
  if output:
259
251
  return output
260
252
  else:
@@ -263,4 +255,4 @@ shell "npm run dev" --cwd ./frontend # Auto-backgrounds if needed"""
263
255
 
264
256
  # Create tool instances
265
257
  zsh_tool = ZshTool()
266
- shell_tool = ShellTool() # Smart shell that prefers zsh
258
+ shell_tool = ShellTool() # Smart shell that prefers zsh
@@ -161,9 +161,7 @@ todo --filter in_progress
161
161
  priority_icon = {"high": "🔴", "medium": "🟡", "low": "🟢"}.get(
162
162
  todo.get("priority", "medium"), "⚪"
163
163
  )
164
- output.append(
165
- f"{priority_icon} [{todo['id'][:8]}] {todo['content']}"
166
- )
164
+ output.append(f"{priority_icon} [{todo['id'][:8]}] {todo['content']}")
167
165
 
168
166
  # Summary
169
167
  output.append(
@@ -14,9 +14,7 @@ from hanzo_mcp.tools.todo.base import TodoStorage, TodoBaseTool
14
14
 
15
15
  SessionId = Annotated[
16
16
  str | int | float,
17
- Field(
18
- description="Unique identifier for the Claude Desktop session (generate using timestamp command)"
19
- ),
17
+ Field(description="Unique identifier for the Claude Desktop session (generate using timestamp command)"),
20
18
  ]
21
19
 
22
20
 
@@ -109,13 +107,9 @@ Usage:
109
107
 
110
108
  # Log status
111
109
  if todos:
112
- await tool_ctx.info(
113
- f"Found {len(todos)} todos for session {session_id}"
114
- )
110
+ await tool_ctx.info(f"Found {len(todos)} todos for session {session_id}")
115
111
  else:
116
- await tool_ctx.info(
117
- f"No todos found for session {session_id} (returning empty list)"
118
- )
112
+ await tool_ctx.info(f"No todos found for session {session_id} (returning empty list)")
119
113
 
120
114
  # Return todos as JSON string
121
115
  result = json.dumps(todos, indent=2)
@@ -309,9 +309,7 @@ When in doubt, use this tool. Being proactive with task management demonstrates
309
309
 
310
310
  # Log storage stats
311
311
  session_count = TodoStorage.get_session_count()
312
- await tool_ctx.info(
313
- f"Successfully stored todos. Total active sessions: {session_count}"
314
- )
312
+ await tool_ctx.info(f"Successfully stored todos. Total active sessions: {session_count}")
315
313
 
316
314
  # Provide feedback about the todos
317
315
  if todos:
@@ -328,23 +326,15 @@ When in doubt, use this tool. Being proactive with task management demonstrates
328
326
  # Create summary
329
327
  summary_parts = []
330
328
  if status_counts:
331
- status_summary = ", ".join(
332
- [f"{count} {status}" for status, count in status_counts.items()]
333
- )
329
+ status_summary = ", ".join([f"{count} {status}" for status, count in status_counts.items()])
334
330
  summary_parts.append(f"Status: {status_summary}")
335
331
 
336
332
  if priority_counts:
337
- priority_summary = ", ".join(
338
- [
339
- f"{count} {priority}"
340
- for priority, count in priority_counts.items()
341
- ]
342
- )
333
+ priority_summary = ", ".join([f"{count} {priority}" for priority, count in priority_counts.items()])
343
334
  summary_parts.append(f"Priority: {priority_summary}")
344
335
 
345
- summary = (
346
- f"Successfully stored {len(todos)} todos for session {session_id}.\n"
347
- + "; ".join(summary_parts)
336
+ summary = f"Successfully stored {len(todos)} todos for session {session_id}.\n" + "; ".join(
337
+ summary_parts
348
338
  )
349
339
 
350
340
  return summary
@@ -368,7 +358,5 @@ When in doubt, use this tool. Being proactive with task management demonstrates
368
358
  tool_self = self # Create a reference to self for use in the closure
369
359
 
370
360
  @mcp_server.tool(name=self.name, description=self.description)
371
- async def todo_write(
372
- session_id: SessionId, todos: Todos, ctx: MCPContext
373
- ) -> str:
361
+ async def todo_write(session_id: SessionId, todos: Todos, ctx: MCPContext) -> str:
374
362
  return await tool_self.call(ctx, session_id=session_id, todos=todos)
@@ -56,9 +56,7 @@ try:
56
56
  store_config = vector_config.copy()
57
57
  project_manager = ProjectVectorManager(
58
58
  global_db_path=store_config.get("data_path"),
59
- embedding_model=store_config.get(
60
- "embedding_model", "text-embedding-3-small"
61
- ),
59
+ embedding_model=store_config.get("embedding_model", "text-embedding-3-small"),
62
60
  dimension=store_config.get("dimension", 1536),
63
61
  )
64
62
 
@@ -68,9 +66,7 @@ try:
68
66
  import logging
69
67
 
70
68
  logger = logging.getLogger(__name__)
71
- logger.info(
72
- f"Detected {len(detected_projects)} projects with LLM.md files"
73
- )
69
+ logger.info(f"Detected {len(detected_projects)} projects with LLM.md files")
74
70
 
75
71
  # Register individual tools if enabled
76
72
  if tool_enabled.get("index", True):
@@ -97,9 +93,7 @@ except ImportError:
97
93
  import logging
98
94
 
99
95
  logger = logging.getLogger(__name__)
100
- logger.warning(
101
- "Vector tools not available. Install infinity-embedded: pip install infinity-embedded"
102
- )
96
+ logger.warning("Vector tools not available. Install infinity-embedded: pip install infinity-embedded")
103
97
  return []
104
98
 
105
99
 
@@ -130,9 +130,7 @@ class ASTAnalyzer:
130
130
  return self._analyze_python_file(file_path, content, file_hash)
131
131
  else:
132
132
  # Generic analysis for other languages
133
- return self._analyze_generic_file(
134
- file_path, content, file_hash, language
135
- )
133
+ return self._analyze_generic_file(file_path, content, file_hash, language)
136
134
 
137
135
  except Exception as e:
138
136
  import logging
@@ -177,9 +175,7 @@ class ASTAnalyzer:
177
175
 
178
176
  return language_map.get(extension)
179
177
 
180
- def _analyze_python_file(
181
- self, file_path: str, content: str, file_hash: str
182
- ) -> FileAST:
178
+ def _analyze_python_file(self, file_path: str, content: str, file_hash: str) -> FileAST:
183
179
  """Analyze Python file using both AST and tree-sitter."""
184
180
  symbols = []
185
181
  ast_nodes = []
@@ -228,9 +224,7 @@ class ASTAnalyzer:
228
224
  dependencies=dependencies,
229
225
  )
230
226
 
231
- def _analyze_generic_file(
232
- self, file_path: str, content: str, file_hash: str, language: str
233
- ) -> FileAST:
227
+ def _analyze_generic_file(self, file_path: str, content: str, file_hash: str, language: str) -> FileAST:
234
228
  """Generic analysis for non-Python files."""
235
229
  # For now, just basic line-based analysis
236
230
  # Could be enhanced with language-specific parsers
@@ -248,11 +242,7 @@ class ASTAnalyzer:
248
242
 
249
243
  # Basic function detection (works for many C-style languages)
250
244
  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
- ):
245
+ if "function " in line or line.startswith("def ") or " function(" in line:
256
246
  # Extract function name
257
247
  parts = line.split()
258
248
  for j, part in enumerate(parts):
@@ -399,9 +389,7 @@ class PythonSymbolExtractor(ast.NodeVisitor):
399
389
 
400
390
  # Extract base classes
401
391
  bases = [self._get_name(base) for base in node.bases]
402
- signature = (
403
- f"class {node.name}({', '.join(bases)})" if bases else f"class {node.name}"
404
- )
392
+ signature = f"class {node.name}({', '.join(bases)})" if bases else f"class {node.name}"
405
393
 
406
394
  symbol = Symbol(
407
395
  name=node.name,
@@ -441,9 +429,7 @@ class PythonSymbolExtractor(ast.NodeVisitor):
441
429
 
442
430
  for alias in node.names:
443
431
  if alias.name != "*":
444
- import_item = (
445
- f"{node.module}.{alias.name}" if node.module else alias.name
446
- )
432
+ import_item = f"{node.module}.{alias.name}" if node.module else alias.name
447
433
  self.imports.append(import_item)
448
434
 
449
435
  def visit_Assign(self, node):
@@ -153,16 +153,12 @@ class GitIngester:
153
153
  )
154
154
  return result.stdout.strip()
155
155
 
156
- def _get_repository_files(
157
- self, repo_path: Path, patterns: Optional[List[str]] = None
158
- ) -> List[Path]:
156
+ def _get_repository_files(self, repo_path: Path, patterns: Optional[List[str]] = None) -> List[Path]:
159
157
  """Get list of files in repository matching patterns."""
160
158
  # Use git ls-files to respect .gitignore
161
159
  cmd = ["git", "ls-files"]
162
160
 
163
- result = subprocess.run(
164
- cmd, cwd=repo_path, capture_output=True, text=True, check=True
165
- )
161
+ result = subprocess.run(cmd, cwd=repo_path, capture_output=True, text=True, check=True)
166
162
 
167
163
  files = []
168
164
  for line in result.stdout.strip().split("\n"):
@@ -178,9 +174,7 @@ class GitIngester:
178
174
 
179
175
  return files
180
176
 
181
- def _get_commit_history(
182
- self, repo_path: Path, branch: str = "HEAD", max_commits: int = 1000
183
- ) -> List[GitCommit]:
177
+ def _get_commit_history(self, repo_path: Path, branch: str = "HEAD", max_commits: int = 1000) -> List[GitCommit]:
184
178
  """Get commit history for the repository."""
185
179
  # Get commit list with basic info
186
180
  result = subprocess.run(
@@ -222,9 +216,7 @@ class GitIngester:
222
216
 
223
217
  return commits
224
218
 
225
- def _get_commit_files(
226
- self, repo_path: Path, commit_hash: str
227
- ) -> List[Dict[str, str]]:
219
+ def _get_commit_files(self, repo_path: Path, commit_hash: str) -> List[Dict[str, str]]:
228
220
  """Get list of files changed in a commit."""
229
221
  result = subprocess.run(
230
222
  ["git", "show", "--name-status", "--format=", commit_hash],
@@ -275,21 +267,15 @@ class GitIngester:
275
267
  if history:
276
268
  metadata["first_commit"] = history[-1]["hash"]
277
269
  metadata["last_commit"] = history[0]["hash"]
278
- metadata["last_modified"] = datetime.fromtimestamp(
279
- history[0]["timestamp"]
280
- ).isoformat()
270
+ metadata["last_modified"] = datetime.fromtimestamp(history[0]["timestamp"]).isoformat()
281
271
 
282
272
  # Add blame information if requested
283
273
  if include_blame:
284
274
  blame_data = self._get_file_blame(repo_path, relative_path)
285
- metadata["unique_authors"] = len(
286
- set(b["author"] for b in blame_data.values())
287
- )
275
+ metadata["unique_authors"] = len(set(b["author"] for b in blame_data.values()))
288
276
 
289
277
  # Index the file content
290
- doc_ids = self.vector_store.add_file(
291
- str(file_path), chunk_size=1000, chunk_overlap=200, metadata=metadata
292
- )
278
+ doc_ids = self.vector_store.add_file(str(file_path), chunk_size=1000, chunk_overlap=200, metadata=metadata)
293
279
  results["files_indexed"] += 1
294
280
 
295
281
  # Perform AST analysis for supported languages
@@ -309,9 +295,7 @@ class GitIngester:
309
295
  except Exception as e:
310
296
  logger.warning(f"AST analysis failed for {file_path}: {e}")
311
297
 
312
- def _get_file_history(
313
- self, repo_path: Path, file_path: Path
314
- ) -> List[Dict[str, Any]]:
298
+ def _get_file_history(self, repo_path: Path, file_path: Path) -> List[Dict[str, Any]]:
315
299
  """Get commit history for a specific file."""
316
300
  result = subprocess.run(
317
301
  [
@@ -346,9 +330,7 @@ class GitIngester:
346
330
 
347
331
  return history
348
332
 
349
- def _get_file_blame(
350
- self, repo_path: Path, file_path: Path
351
- ) -> Dict[int, Dict[str, Any]]:
333
+ def _get_file_blame(self, repo_path: Path, file_path: Path) -> Dict[int, Dict[str, Any]]:
352
334
  """Get blame information for a file."""
353
335
  result = subprocess.run(
354
336
  ["git", "blame", "--line-porcelain", "--", str(file_path)],
@@ -448,9 +430,7 @@ Message: {commit.message}
448
430
  text=True,
449
431
  )
450
432
 
451
- remote_url = (
452
- remote_result.stdout.strip() if remote_result.returncode == 0 else None
453
- )
433
+ remote_url = remote_result.stdout.strip() if remote_result.returncode == 0 else None
454
434
 
455
435
  # Create repository summary document
456
436
  repo_doc = f"""Repository: {repo_path.name}
@@ -151,13 +151,9 @@ Usage:
151
151
  if not force:
152
152
  stats = await vector_store.get_stats()
153
153
  if stats and stats.get("document_count", 0) > 0:
154
- await tool_ctx.info(
155
- "Project already indexed, use --force to re-index"
156
- )
154
+ await tool_ctx.info("Project already indexed, use --force to re-index")
157
155
  if show_stats:
158
- return self._format_stats(
159
- stats, abs_path, time.time() - start_time
160
- )
156
+ return self._format_stats(stats, abs_path, time.time() - start_time)
161
157
  return "Project is already indexed. Use --force to re-index."
162
158
 
163
159
  # Prepare file patterns
@@ -268,9 +264,7 @@ Usage:
268
264
  except Exception as e:
269
265
  errors.append(f"{file_path}: {str(e)}")
270
266
 
271
- await tool_ctx.info(
272
- f"Indexed {indexed_files} files ({total_size / 1024 / 1024:.1f} MB)"
273
- )
267
+ await tool_ctx.info(f"Indexed {indexed_files} files ({total_size / 1024 / 1024:.1f} MB)")
274
268
 
275
269
  # Index git history if requested
276
270
  git_stats = {}
@@ -79,9 +79,7 @@ class InfinityVectorStore:
79
79
  dimension: Vector dimension (must match embedding model)
80
80
  """
81
81
  if not INFINITY_AVAILABLE:
82
- raise ImportError(
83
- "infinity_embedded is required for vector store functionality"
84
- )
82
+ raise ImportError("infinity_embedded is required for vector store functionality")
85
83
 
86
84
  # Set up data path
87
85
  if data_path:
@@ -172,17 +170,13 @@ class InfinityVectorStore:
172
170
  "source_file": {"type": "varchar"},
173
171
  "target_file": {"type": "varchar"},
174
172
  "symbol_name": {"type": "varchar"},
175
- "reference_type": {
176
- "type": "varchar"
177
- }, # import, call, inheritance, etc.
173
+ "reference_type": {"type": "varchar"}, # import, call, inheritance, etc.
178
174
  "line_number": {"type": "integer"},
179
175
  "metadata": {"type": "varchar"}, # JSON string
180
176
  },
181
177
  )
182
178
 
183
- def _generate_doc_id(
184
- self, content: str, file_path: str = "", chunk_index: int = 0
185
- ) -> str:
179
+ def _generate_doc_id(self, content: str, file_path: str = "", chunk_index: int = 0) -> str:
186
180
  """Generate a unique document ID."""
187
181
  content_hash = hashlib.sha256(content.encode()).hexdigest()[:16]
188
182
  path_hash = hashlib.sha256(file_path.encode()).hexdigest()[:8]
@@ -534,11 +528,7 @@ class InfinityVectorStore:
534
528
  FileAST object if file found, None otherwise
535
529
  """
536
530
  try:
537
- results = (
538
- self.ast_table.output(["*"])
539
- .filter(f"file_path = '{file_path}'")
540
- .to_pl()
541
- )
531
+ results = self.ast_table.output(["*"]).filter(f"file_path = '{file_path}'").to_pl()
542
532
 
543
533
  if len(results) == 0:
544
534
  return None
@@ -577,11 +567,7 @@ class InfinityVectorStore:
577
567
  List of reference information
578
568
  """
579
569
  try:
580
- results = (
581
- self.references_table.output(["*"])
582
- .filter(f"target_file = '{file_path}'")
583
- .to_pl()
584
- )
570
+ results = self.references_table.output(["*"]).filter(f"target_file = '{file_path}'").to_pl()
585
571
 
586
572
  references = []
587
573
  for row in results.iter_rows(named=True):
@@ -696,11 +682,7 @@ class InfinityVectorStore:
696
682
  """
697
683
  try:
698
684
  # Get count first
699
- results = (
700
- self.documents_table.output(["id"])
701
- .filter(f"file_path = '{file_path}'")
702
- .to_pl()
703
- )
685
+ results = self.documents_table.output(["id"]).filter(f"file_path = '{file_path}'").to_pl()
704
686
  count = len(results)
705
687
 
706
688
  # Delete all documents for this file
@@ -726,9 +708,7 @@ class InfinityVectorStore:
726
708
  metadata = json.loads(row["metadata"])
727
709
  files[file_path] = {
728
710
  "file_path": file_path,
729
- "file_name": metadata.get(
730
- "file_name", Path(file_path).name
731
- ),
711
+ "file_name": metadata.get("file_name", Path(file_path).name),
732
712
  "file_size": metadata.get("file_size", 0),
733
713
  "total_chunks": metadata.get("total_chunks", 1),
734
714
  }
@@ -58,9 +58,7 @@ class MockQuery:
58
58
  self.filters.append(condition)
59
59
  return self
60
60
 
61
- def match_dense(
62
- self, column: str, vector: List[float], dtype: str, metric: str, limit: int
63
- ):
61
+ def match_dense(self, column: str, vector: List[float], dtype: str, metric: str, limit: int):
64
62
  """Add vector search."""
65
63
  self.vector_search = {
66
64
  "column": column,
@@ -153,9 +153,7 @@ class ProjectVectorManager:
153
153
 
154
154
  return None
155
155
 
156
- def get_vector_store(
157
- self, project_info: Optional[ProjectInfo] = None
158
- ) -> InfinityVectorStore:
156
+ def get_vector_store(self, project_info: Optional[ProjectInfo] = None) -> InfinityVectorStore:
159
157
  """Get vector store for a project or global store.
160
158
 
161
159
  Args:
@@ -178,9 +176,7 @@ class ProjectVectorManager:
178
176
 
179
177
  if project_key not in self.vector_stores:
180
178
  # Get index path based on configuration
181
- index_path = self.index_config.get_index_path(
182
- "vector", str(project_info.root_path)
183
- )
179
+ index_path = self.index_config.get_index_path("vector", str(project_info.root_path))
184
180
  index_path.mkdir(parents=True, exist_ok=True)
185
181
 
186
182
  self.vector_stores[project_key] = InfinityVectorStore(
@@ -270,9 +266,7 @@ class ProjectVectorManager:
270
266
  search_tasks.append(
271
267
  asyncio.get_event_loop().run_in_executor(
272
268
  self.executor,
273
- lambda: global_store.search(
274
- query, limit_per_project, score_threshold
275
- ),
269
+ lambda: global_store.search(query, limit_per_project, score_threshold),
276
270
  )
277
271
  )
278
272
  project_names.append("global")
@@ -287,9 +281,7 @@ class ProjectVectorManager:
287
281
  search_tasks.append(
288
282
  asyncio.get_event_loop().run_in_executor(
289
283
  self.executor,
290
- lambda vs=vector_store: vs.search(
291
- query, limit_per_project, score_threshold
292
- ),
284
+ lambda vs=vector_store: vs.search(query, limit_per_project, score_threshold),
293
285
  )
294
286
  )
295
287
  project_names.append(project_info.name)
@@ -249,9 +249,7 @@ vector --action clear --path ./old_code
249
249
  output.append(f"Chunks created: {stats.get('chunks_created', 0)}")
250
250
  if stats.get("git_commits"):
251
251
  output.append(f"Git commits indexed: {stats['git_commits']}")
252
- output.append(
253
- f"Total documents: {project.get_stats().get('total_documents', 0)}"
254
- )
252
+ output.append(f"Total documents: {project.get_stats().get('total_documents', 0)}")
255
253
 
256
254
  return "\n".join(output)
257
255
 
@@ -283,9 +281,7 @@ vector --action clear --path ./old_code
283
281
  if stats.get("projects"):
284
282
  output.append(f"\nProjects indexed: {len(stats['projects'])}")
285
283
  for proj in stats["projects"]:
286
- output.append(
287
- f" - {proj['name']}: {proj['documents']} docs, {proj['size_mb']:.1f} MB"
288
- )
284
+ output.append(f" - {proj['name']}: {proj['documents']} docs, {proj['size_mb']:.1f} MB")
289
285
 
290
286
  return "\n".join(output)
291
287