hanzo-mcp 0.8.8__py3-none-any.whl → 0.8.13__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 +1 -3
  2. hanzo_mcp/analytics/posthog_analytics.py +4 -17
  3. hanzo_mcp/bridge.py +9 -25
  4. hanzo_mcp/cli.py +8 -17
  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 +2 -4
  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 +6 -7
  17. hanzo_mcp/tools/__init__.py +13 -29
  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 +6 -17
  21. hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +15 -42
  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 +76 -75
  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 +16 -41
  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 +3 -9
  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 +7 -19
  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_session.py +7 -13
  72. hanzo_mcp/tools/filesystem/__init__.py +2 -3
  73. hanzo_mcp/tools/filesystem/ast_multi_edit.py +14 -43
  74. hanzo_mcp/tools/filesystem/base.py +4 -12
  75. hanzo_mcp/tools/filesystem/batch_search.py +35 -115
  76. hanzo_mcp/tools/filesystem/content_replace.py +4 -12
  77. hanzo_mcp/tools/filesystem/diff.py +2 -10
  78. hanzo_mcp/tools/filesystem/directory_tree.py +9 -27
  79. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +5 -15
  80. hanzo_mcp/tools/filesystem/edit.py +6 -18
  81. hanzo_mcp/tools/filesystem/find.py +3 -9
  82. hanzo_mcp/tools/filesystem/find_files.py +2 -6
  83. hanzo_mcp/tools/filesystem/git_search.py +9 -24
  84. hanzo_mcp/tools/filesystem/grep.py +9 -27
  85. hanzo_mcp/tools/filesystem/multi_edit.py +6 -18
  86. hanzo_mcp/tools/filesystem/read.py +8 -26
  87. hanzo_mcp/tools/filesystem/rules_tool.py +6 -17
  88. hanzo_mcp/tools/filesystem/search_tool.py +18 -62
  89. hanzo_mcp/tools/filesystem/symbols_tool.py +5 -15
  90. hanzo_mcp/tools/filesystem/tree.py +1 -3
  91. hanzo_mcp/tools/filesystem/watch.py +1 -3
  92. hanzo_mcp/tools/filesystem/write.py +1 -3
  93. hanzo_mcp/tools/jupyter/base.py +6 -20
  94. hanzo_mcp/tools/jupyter/jupyter.py +4 -12
  95. hanzo_mcp/tools/jupyter/notebook_edit.py +11 -35
  96. hanzo_mcp/tools/jupyter/notebook_read.py +2 -6
  97. hanzo_mcp/tools/llm/consensus_tool.py +8 -24
  98. hanzo_mcp/tools/llm/llm_manage.py +2 -6
  99. hanzo_mcp/tools/llm/llm_tool.py +17 -58
  100. hanzo_mcp/tools/llm/llm_unified.py +18 -59
  101. hanzo_mcp/tools/llm/provider_tools.py +1 -3
  102. hanzo_mcp/tools/lsp/lsp_tool.py +5 -17
  103. hanzo_mcp/tools/mcp/mcp_add.py +3 -5
  104. hanzo_mcp/tools/mcp/mcp_remove.py +1 -1
  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 +33 -40
  108. hanzo_mcp/tools/memory/knowledge_tools.py +7 -25
  109. hanzo_mcp/tools/memory/memory_tools.py +7 -19
  110. hanzo_mcp/tools/search/find_tool.py +10 -32
  111. hanzo_mcp/tools/search/unified_search.py +27 -81
  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 +26 -79
  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 +11 -30
  144. hanzo_mcp/tools/vector/mock_infinity.py +159 -0
  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.8.dist-info → hanzo_mcp-0.8.13.dist-info}/METADATA +2 -2
  150. hanzo_mcp-0.8.13.dist-info/RECORD +193 -0
  151. hanzo_mcp-0.8.8.dist-info/RECORD +0 -192
  152. {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.8.13.dist-info}/WHEEL +0 -0
  153. {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.8.13.dist-info}/entry_points.txt +0 -0
  154. {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.8.13.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 = {}
@@ -11,9 +11,10 @@ try:
11
11
 
12
12
  INFINITY_AVAILABLE = True
13
13
  except ImportError:
14
- # infinity_embedded not available, functionality will be limited
15
- INFINITY_AVAILABLE = False
16
- infinity_embedded = None # type: ignore
14
+ # Use mock implementation when infinity_embedded is not available
15
+ from . import mock_infinity as infinity_embedded
16
+
17
+ INFINITY_AVAILABLE = True # Mock is always available
17
18
 
18
19
  from .ast_analyzer import Symbol, FileAST, ASTAnalyzer, create_symbol_embedding_text
19
20
 
@@ -78,9 +79,7 @@ class InfinityVectorStore:
78
79
  dimension: Vector dimension (must match embedding model)
79
80
  """
80
81
  if not INFINITY_AVAILABLE:
81
- raise ImportError(
82
- "infinity_embedded is required for vector store functionality"
83
- )
82
+ raise ImportError("infinity_embedded is required for vector store functionality")
84
83
 
85
84
  # Set up data path
86
85
  if data_path:
@@ -171,17 +170,13 @@ class InfinityVectorStore:
171
170
  "source_file": {"type": "varchar"},
172
171
  "target_file": {"type": "varchar"},
173
172
  "symbol_name": {"type": "varchar"},
174
- "reference_type": {
175
- "type": "varchar"
176
- }, # import, call, inheritance, etc.
173
+ "reference_type": {"type": "varchar"}, # import, call, inheritance, etc.
177
174
  "line_number": {"type": "integer"},
178
175
  "metadata": {"type": "varchar"}, # JSON string
179
176
  },
180
177
  )
181
178
 
182
- def _generate_doc_id(
183
- self, content: str, file_path: str = "", chunk_index: int = 0
184
- ) -> str:
179
+ def _generate_doc_id(self, content: str, file_path: str = "", chunk_index: int = 0) -> str:
185
180
  """Generate a unique document ID."""
186
181
  content_hash = hashlib.sha256(content.encode()).hexdigest()[:16]
187
182
  path_hash = hashlib.sha256(file_path.encode()).hexdigest()[:8]
@@ -533,11 +528,7 @@ class InfinityVectorStore:
533
528
  FileAST object if file found, None otherwise
534
529
  """
535
530
  try:
536
- results = (
537
- self.ast_table.output(["*"])
538
- .filter(f"file_path = '{file_path}'")
539
- .to_pl()
540
- )
531
+ results = self.ast_table.output(["*"]).filter(f"file_path = '{file_path}'").to_pl()
541
532
 
542
533
  if len(results) == 0:
543
534
  return None
@@ -576,11 +567,7 @@ class InfinityVectorStore:
576
567
  List of reference information
577
568
  """
578
569
  try:
579
- results = (
580
- self.references_table.output(["*"])
581
- .filter(f"target_file = '{file_path}'")
582
- .to_pl()
583
- )
570
+ results = self.references_table.output(["*"]).filter(f"target_file = '{file_path}'").to_pl()
584
571
 
585
572
  references = []
586
573
  for row in results.iter_rows(named=True):
@@ -695,11 +682,7 @@ class InfinityVectorStore:
695
682
  """
696
683
  try:
697
684
  # Get count first
698
- results = (
699
- self.documents_table.output(["id"])
700
- .filter(f"file_path = '{file_path}'")
701
- .to_pl()
702
- )
685
+ results = self.documents_table.output(["id"]).filter(f"file_path = '{file_path}'").to_pl()
703
686
  count = len(results)
704
687
 
705
688
  # Delete all documents for this file
@@ -725,9 +708,7 @@ class InfinityVectorStore:
725
708
  metadata = json.loads(row["metadata"])
726
709
  files[file_path] = {
727
710
  "file_path": file_path,
728
- "file_name": metadata.get(
729
- "file_name", Path(file_path).name
730
- ),
711
+ "file_name": metadata.get("file_name", Path(file_path).name),
731
712
  "file_size": metadata.get("file_size", 0),
732
713
  "total_chunks": metadata.get("total_chunks", 1),
733
714
  }