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

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

Potentially problematic release.


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

Files changed (166) hide show
  1. hanzo_mcp/__init__.py +1 -3
  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 +2 -6
  17. hanzo_mcp/tools/__init__.py +26 -27
  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 +22 -15
  21. hanzo_mcp/tools/agent/claude_desktop_auth.py +3 -9
  22. hanzo_mcp/tools/agent/cli_agent_base.py +7 -24
  23. hanzo_mcp/tools/agent/cli_tools.py +75 -74
  24. hanzo_mcp/tools/agent/code_auth.py +1 -3
  25. hanzo_mcp/tools/agent/code_auth_tool.py +2 -6
  26. hanzo_mcp/tools/agent/critic_tool.py +8 -24
  27. hanzo_mcp/tools/agent/iching_tool.py +12 -36
  28. hanzo_mcp/tools/agent/network_tool.py +7 -18
  29. hanzo_mcp/tools/agent/prompt.py +1 -5
  30. hanzo_mcp/tools/agent/review_tool.py +10 -25
  31. hanzo_mcp/tools/agent/swarm_alias.py +1 -3
  32. hanzo_mcp/tools/agent/unified_cli_tools.py +38 -38
  33. hanzo_mcp/tools/common/batch_tool.py +15 -45
  34. hanzo_mcp/tools/common/config_tool.py +9 -28
  35. hanzo_mcp/tools/common/context.py +1 -3
  36. hanzo_mcp/tools/common/critic_tool.py +1 -3
  37. hanzo_mcp/tools/common/decorators.py +2 -6
  38. hanzo_mcp/tools/common/enhanced_base.py +2 -6
  39. hanzo_mcp/tools/common/fastmcp_pagination.py +4 -12
  40. hanzo_mcp/tools/common/forgiving_edit.py +9 -28
  41. hanzo_mcp/tools/common/mode.py +1 -5
  42. hanzo_mcp/tools/common/paginated_base.py +3 -11
  43. hanzo_mcp/tools/common/paginated_response.py +10 -30
  44. hanzo_mcp/tools/common/pagination.py +3 -9
  45. hanzo_mcp/tools/common/path_utils.py +34 -0
  46. hanzo_mcp/tools/common/permissions.py +14 -13
  47. hanzo_mcp/tools/common/personality.py +983 -701
  48. hanzo_mcp/tools/common/plugin_loader.py +3 -15
  49. hanzo_mcp/tools/common/stats.py +6 -18
  50. hanzo_mcp/tools/common/thinking_tool.py +1 -3
  51. hanzo_mcp/tools/common/tool_disable.py +2 -6
  52. hanzo_mcp/tools/common/tool_list.py +2 -6
  53. hanzo_mcp/tools/common/validation.py +1 -3
  54. hanzo_mcp/tools/compiler/__init__.py +8 -0
  55. hanzo_mcp/tools/compiler/sandboxed_compiler.py +681 -0
  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/environment/__init__.py +8 -0
  73. hanzo_mcp/tools/environment/environment_detector.py +594 -0
  74. hanzo_mcp/tools/filesystem/__init__.py +28 -26
  75. hanzo_mcp/tools/filesystem/ast_multi_edit.py +14 -43
  76. hanzo_mcp/tools/filesystem/ast_tool.py +3 -0
  77. hanzo_mcp/tools/filesystem/base.py +20 -12
  78. hanzo_mcp/tools/filesystem/content_replace.py +7 -12
  79. hanzo_mcp/tools/filesystem/diff.py +2 -10
  80. hanzo_mcp/tools/filesystem/directory_tree.py +285 -51
  81. hanzo_mcp/tools/filesystem/edit.py +10 -18
  82. hanzo_mcp/tools/filesystem/find.py +312 -179
  83. hanzo_mcp/tools/filesystem/git_search.py +12 -24
  84. hanzo_mcp/tools/filesystem/multi_edit.py +10 -18
  85. hanzo_mcp/tools/filesystem/read.py +14 -30
  86. hanzo_mcp/tools/filesystem/rules_tool.py +9 -17
  87. hanzo_mcp/tools/filesystem/search.py +1160 -0
  88. hanzo_mcp/tools/filesystem/watch.py +2 -4
  89. hanzo_mcp/tools/filesystem/write.py +7 -10
  90. hanzo_mcp/tools/framework/__init__.py +8 -0
  91. hanzo_mcp/tools/framework/framework_modes.py +714 -0
  92. hanzo_mcp/tools/jupyter/base.py +6 -20
  93. hanzo_mcp/tools/jupyter/jupyter.py +4 -12
  94. hanzo_mcp/tools/llm/consensus_tool.py +8 -24
  95. hanzo_mcp/tools/llm/llm_manage.py +2 -6
  96. hanzo_mcp/tools/llm/llm_tool.py +17 -58
  97. hanzo_mcp/tools/llm/llm_unified.py +18 -59
  98. hanzo_mcp/tools/llm/provider_tools.py +1 -3
  99. hanzo_mcp/tools/lsp/lsp_tool.py +621 -481
  100. hanzo_mcp/tools/mcp/mcp_add.py +1 -3
  101. hanzo_mcp/tools/mcp/mcp_stats.py +1 -3
  102. hanzo_mcp/tools/mcp/mcp_tool.py +9 -23
  103. hanzo_mcp/tools/memory/__init__.py +10 -27
  104. hanzo_mcp/tools/memory/conversation_memory.py +636 -0
  105. hanzo_mcp/tools/memory/knowledge_tools.py +7 -25
  106. hanzo_mcp/tools/memory/memory_tools.py +6 -18
  107. hanzo_mcp/tools/search/find_tool.py +12 -34
  108. hanzo_mcp/tools/search/unified_search.py +24 -78
  109. hanzo_mcp/tools/shell/__init__.py +16 -4
  110. hanzo_mcp/tools/shell/auto_background.py +2 -6
  111. hanzo_mcp/tools/shell/base.py +1 -5
  112. hanzo_mcp/tools/shell/base_process.py +5 -7
  113. hanzo_mcp/tools/shell/bash_session.py +7 -24
  114. hanzo_mcp/tools/shell/bash_session_executor.py +5 -15
  115. hanzo_mcp/tools/shell/bash_tool.py +3 -7
  116. hanzo_mcp/tools/shell/command_executor.py +26 -79
  117. hanzo_mcp/tools/shell/logs.py +4 -16
  118. hanzo_mcp/tools/shell/npx.py +2 -8
  119. hanzo_mcp/tools/shell/npx_tool.py +1 -3
  120. hanzo_mcp/tools/shell/pkill.py +4 -12
  121. hanzo_mcp/tools/shell/process_tool.py +2 -8
  122. hanzo_mcp/tools/shell/processes.py +5 -17
  123. hanzo_mcp/tools/shell/run_background.py +1 -3
  124. hanzo_mcp/tools/shell/run_command.py +1 -3
  125. hanzo_mcp/tools/shell/run_command_windows.py +1 -3
  126. hanzo_mcp/tools/shell/run_tool.py +56 -0
  127. hanzo_mcp/tools/shell/session_manager.py +2 -6
  128. hanzo_mcp/tools/shell/session_storage.py +2 -6
  129. hanzo_mcp/tools/shell/streaming_command.py +7 -23
  130. hanzo_mcp/tools/shell/uvx.py +4 -14
  131. hanzo_mcp/tools/shell/uvx_background.py +2 -6
  132. hanzo_mcp/tools/shell/uvx_tool.py +1 -3
  133. hanzo_mcp/tools/shell/zsh_tool.py +12 -20
  134. hanzo_mcp/tools/todo/todo.py +1 -3
  135. hanzo_mcp/tools/vector/__init__.py +97 -50
  136. hanzo_mcp/tools/vector/ast_analyzer.py +6 -20
  137. hanzo_mcp/tools/vector/git_ingester.py +10 -30
  138. hanzo_mcp/tools/vector/index_tool.py +3 -9
  139. hanzo_mcp/tools/vector/infinity_store.py +7 -27
  140. hanzo_mcp/tools/vector/mock_infinity.py +1 -3
  141. hanzo_mcp/tools/vector/node_tool.py +538 -0
  142. hanzo_mcp/tools/vector/project_manager.py +4 -12
  143. hanzo_mcp/tools/vector/unified_vector.py +384 -0
  144. hanzo_mcp/tools/vector/vector.py +2 -6
  145. hanzo_mcp/tools/vector/vector_index.py +8 -8
  146. hanzo_mcp/tools/vector/vector_search.py +7 -21
  147. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.9.0.dist-info}/METADATA +2 -2
  148. hanzo_mcp-0.9.0.dist-info/RECORD +191 -0
  149. hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +0 -645
  150. hanzo_mcp/tools/agent/swarm_tool.py +0 -718
  151. hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +0 -577
  152. hanzo_mcp/tools/filesystem/batch_search.py +0 -900
  153. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +0 -350
  154. hanzo_mcp/tools/filesystem/find_files.py +0 -369
  155. hanzo_mcp/tools/filesystem/grep.py +0 -467
  156. hanzo_mcp/tools/filesystem/search_tool.py +0 -767
  157. hanzo_mcp/tools/filesystem/symbols_tool.py +0 -515
  158. hanzo_mcp/tools/filesystem/tree.py +0 -270
  159. hanzo_mcp/tools/jupyter/notebook_edit.py +0 -317
  160. hanzo_mcp/tools/jupyter/notebook_read.py +0 -147
  161. hanzo_mcp/tools/todo/todo_read.py +0 -143
  162. hanzo_mcp/tools/todo/todo_write.py +0 -374
  163. hanzo_mcp-0.8.11.dist-info/RECORD +0 -193
  164. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.9.0.dist-info}/WHEEL +0 -0
  165. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.9.0.dist-info}/entry_points.txt +0 -0
  166. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.9.0.dist-info}/top_level.txt +0 -0
@@ -423,17 +423,13 @@ class IChing:
423
423
  lines += "1"
424
424
  return Hexagram(lines)
425
425
 
426
- def select_principles(
427
- self, hexagram: Hexagram, challenge: str
428
- ) -> List[HanzoPrinciple]:
426
+ def select_principles(self, hexagram: Hexagram, challenge: str) -> List[HanzoPrinciple]:
429
427
  """Select relevant Hanzo principles based on hexagram and challenge."""
430
428
  # Use hexagram pattern to deterministically but creatively select principles
431
429
  selected = []
432
430
 
433
431
  # Primary principle based on hexagram pattern
434
- primary_index = sum(
435
- int(bit) * (2**i) for i, bit in enumerate(hexagram.lines)
436
- ) % len(self.principles)
432
+ primary_index = sum(int(bit) * (2**i) for i, bit in enumerate(hexagram.lines)) % len(self.principles)
437
433
  selected.append(self.principles[primary_index])
438
434
 
439
435
  # Supporting principles based on challenge keywords
@@ -458,9 +454,7 @@ class IChing:
458
454
  # Add complementary principle based on changing lines
459
455
  changing_lines = hexagram.get_changing_lines()
460
456
  if changing_lines:
461
- complement_index = (primary_index + sum(changing_lines)) % len(
462
- self.principles
463
- )
457
+ complement_index = (primary_index + sum(changing_lines)) % len(self.principles)
464
458
  selected.append(self.principles[complement_index])
465
459
 
466
460
  # Ensure uniqueness and limit to 3-5 principles
@@ -473,9 +467,7 @@ class IChing:
473
467
 
474
468
  return unique_selected[:5]
475
469
 
476
- def generate_guidance(
477
- self, hexagram: Hexagram, principles: List[HanzoPrinciple], challenge: str
478
- ) -> str:
470
+ def generate_guidance(self, hexagram: Hexagram, principles: List[HanzoPrinciple], challenge: str) -> str:
479
471
  """Generate creative guidance combining I Ching wisdom and Hanzo principles."""
480
472
  guidance = f"☯️ I CHING GUIDANCE FOR ENGINEERING CHALLENGE ☯️\n\n"
481
473
  guidance += f"**Your Challenge:** {challenge}\n\n"
@@ -498,21 +490,15 @@ class IChing:
498
490
  if "Creative" in hexagram.title:
499
491
  guidance += "• This is a time for bold innovation. Don't hold back on ambitious ideas.\n"
500
492
  elif "Receptive" in hexagram.title:
501
- guidance += (
502
- "• Listen deeply to user needs and system constraints before acting.\n"
503
- )
493
+ guidance += "• Listen deeply to user needs and system constraints before acting.\n"
504
494
  elif "Difficulty" in hexagram.title:
505
- guidance += (
506
- "• Challenges are teachers. Each obstacle reveals the path forward.\n"
507
- )
495
+ guidance += "• Challenges are teachers. Each obstacle reveals the path forward.\n"
508
496
  elif "Waiting" in hexagram.title:
509
497
  guidance += "• Strategic patience required. Prepare thoroughly before implementation.\n"
510
498
  elif "Conflict" in hexagram.title:
511
499
  guidance += "• Technical disagreements? Seek data-driven resolution.\n"
512
500
  elif "Peace" in hexagram.title:
513
- guidance += (
514
- "• Harmony achieved. Now build sustainably on this foundation.\n"
515
- )
501
+ guidance += "• Harmony achieved. Now build sustainably on this foundation.\n"
516
502
 
517
503
  # Principle-specific actionable advice
518
504
  principle_actions = {
@@ -534,9 +520,7 @@ class IChing:
534
520
  changing_lines = hexagram.get_changing_lines()
535
521
  if changing_lines:
536
522
  guidance += f"\n**Lines in Transition:** {', '.join(str(i + 1) for i in changing_lines)}\n"
537
- guidance += (
538
- "• Change is imminent in these areas. Prepare for transformation.\n"
539
- )
523
+ guidance += "• Change is imminent in these areas. Prepare for transformation.\n"
540
524
 
541
525
  # Final synthesis
542
526
  guidance += "\n**The Way Forward:**\n"
@@ -547,9 +531,7 @@ class IChing:
547
531
 
548
532
  return guidance
549
533
 
550
- def _synthesize_action_plan(
551
- self, hexagram: Hexagram, principles: List[HanzoPrinciple], challenge: str
552
- ) -> str:
534
+ def _synthesize_action_plan(self, hexagram: Hexagram, principles: List[HanzoPrinciple], challenge: str) -> str:
553
535
  """Create a specific action plan based on the reading."""
554
536
  plan = ""
555
537
 
@@ -558,21 +540,15 @@ class IChing:
558
540
  plan += "1. **Diagnose systematically** - Use empirical debugging, not guesswork\n"
559
541
  plan += "2. **Fix root cause** - Address the source, not just symptoms\n"
560
542
  plan += "3. **Prevent recurrence** - Add tests and monitoring\n"
561
- elif any(
562
- word in challenge.lower() for word in ["scale", "performance", "slow"]
563
- ):
543
+ elif any(word in challenge.lower() for word in ["scale", "performance", "slow"]):
564
544
  plan += "1. **Measure first** - Profile to find actual bottlenecks\n"
565
545
  plan += "2. **Parallelize** - Use concurrency where possible\n"
566
546
  plan += "3. **Simplify** - Remove complexity before optimizing\n"
567
- elif any(
568
- word in challenge.lower() for word in ["design", "architect", "structure"]
569
- ):
547
+ elif any(word in challenge.lower() for word in ["design", "architect", "structure"]):
570
548
  plan += "1. **Start simple** - MVP first, elaborate later\n"
571
549
  plan += "2. **Stay flexible** - Design for change\n"
572
550
  plan += "3. **Think holistically** - Consider entire system\n"
573
- elif any(
574
- word in challenge.lower() for word in ["team", "collaborate", "people"]
575
- ):
551
+ elif any(word in challenge.lower() for word in ["team", "collaborate", "people"]):
576
552
  plan += "1. **Enable autonomy** - Trust your team\n"
577
553
  plan += "2. **Maintain balance** - Sustainable pace wins\n"
578
554
  plan += "3. **Share knowledge** - Elevate everyone\n"
@@ -77,9 +77,7 @@ class NetworkTool(BaseTool):
77
77
  """
78
78
  self.permission_manager = permission_manager
79
79
  self.default_mode = default_mode
80
- self.cluster_endpoint = cluster_endpoint or os.environ.get(
81
- "HANZO_CLUSTER_ENDPOINT", "http://localhost:8000"
82
- )
80
+ self.cluster_endpoint = cluster_endpoint or os.environ.get("HANZO_CLUSTER_ENDPOINT", "http://localhost:8000")
83
81
  self._cluster = None
84
82
 
85
83
  async def _ensure_cluster(self):
@@ -160,9 +158,7 @@ class NetworkTool(BaseTool):
160
158
  results["results"].append(
161
159
  {
162
160
  "agent": "local-cluster",
163
- "response": local_result.get("choices", [{}])[0].get(
164
- "text", ""
165
- ),
161
+ "response": local_result.get("choices", [{}])[0].get("text", ""),
166
162
  "local": True,
167
163
  }
168
164
  )
@@ -180,6 +176,7 @@ class NetworkTool(BaseTool):
180
176
  # Agent-based execution with concurrency
181
177
  if not results["success"] or mode in ["distributed", "hybrid"]:
182
178
  from hanzo_mcp.tools.agent.agent_tool import AgentTool
179
+
183
180
  agent = AgentTool(permission_manager=self.permission_manager, model=model_pref)
184
181
  concurrency = max(1, len(agents_list)) if agents_list else 5 if routing == "parallel" else 1
185
182
  agent_params = {"prompts": task, "concurrency": concurrency}
@@ -206,25 +203,17 @@ class NetworkTool(BaseTool):
206
203
  async def network_handler(
207
204
  ctx: MCPContext,
208
205
  task: Annotated[str, Field(description="Task to execute on the network")],
209
- agents: Annotated[
210
- Optional[List[str]], Field(description="Specific agents to use")
211
- ] = None,
206
+ agents: Annotated[Optional[List[str]], Field(description="Specific agents to use")] = None,
212
207
  mode: Annotated[
213
208
  Optional[str],
214
209
  Field(description="Execution mode: local, distributed, or hybrid"),
215
210
  ] = None,
216
- model: Annotated[
217
- Optional[str], Field(description="Model preference")
218
- ] = None,
211
+ model: Annotated[Optional[str], Field(description="Model preference")] = None,
219
212
  routing: Annotated[
220
213
  Optional[str],
221
- Field(
222
- description="Routing strategy: sequential, parallel, or consensus"
223
- ),
224
- ] = None,
225
- require_local: Annotated[
226
- Optional[bool], Field(description="Require local-only execution")
214
+ Field(description="Routing strategy: sequential, parallel, or consensus"),
227
215
  ] = None,
216
+ require_local: Annotated[Optional[bool], Field(description="Require local-only execution")] = None,
228
217
  ) -> str:
229
218
  """Dispatch work to agent networks."""
230
219
  params = NetworkToolParams(
@@ -139,11 +139,7 @@ def get_default_model(model_override: str | None = None) -> str:
139
139
  model = os.environ.get("AGENT_MODEL", "claude-3-5-sonnet-20241022")
140
140
 
141
141
  # Special cases for tests
142
- if (
143
- model.startswith("test-model")
144
- or "TEST_MODE" in os.environ
145
- and model == "claude-3-5-sonnet-20241022"
146
- ):
142
+ if model.startswith("test-model") or "TEST_MODE" in os.environ and model == "claude-3-5-sonnet-20241022":
147
143
  return model
148
144
 
149
145
  provider = os.environ.get("AGENT_PROVIDER", "anthropic")
@@ -83,9 +83,7 @@ review(
83
83
  file_paths: Optional[List[str]] = None,
84
84
  context: Optional[str] = None,
85
85
  ) -> str:
86
- return await tool_self.call(
87
- ctx, focus, work_description, code_snippets, file_paths, context
88
- )
86
+ return await tool_self.call(ctx, focus, work_description, code_snippets, file_paths, context)
89
87
 
90
88
 
91
89
  class BalancedReviewer:
@@ -210,23 +208,18 @@ class BalancedReviewer:
210
208
 
211
209
  if code_snippets:
212
210
  total_lines = sum(snippet.count("\n") + 1 for snippet in code_snippets)
213
- avg_line_length = sum(
214
- len(line) for snippet in code_snippets for line in snippet.split("\n")
215
- ) / max(total_lines, 1)
211
+ avg_line_length = sum(len(line) for snippet in code_snippets for line in snippet.split("\n")) / max(
212
+ total_lines, 1
213
+ )
216
214
 
217
215
  if avg_line_length < 80:
218
216
  response += "✓ Line lengths are reasonable\n"
219
217
  else:
220
- response += (
221
- "• Some lines might be too long, consider breaking them up\n"
222
- )
218
+ response += "• Some lines might be too long, consider breaking them up\n"
223
219
 
224
220
  # Check naming
225
221
  has_good_names = any(
226
- any(
227
- word in snippet
228
- for word in ["Add", "Get", "Set", "Create", "Update", "Delete"]
229
- )
222
+ any(word in snippet for word in ["Add", "Get", "Set", "Create", "Update", "Delete"])
230
223
  for snippet in code_snippets
231
224
  )
232
225
  if has_good_names:
@@ -264,10 +257,7 @@ class BalancedReviewer:
264
257
  # Check for modularity in code
265
258
  if code_snippets:
266
259
  function_count = sum(
267
- snippet.count("func ")
268
- + snippet.count("def ")
269
- + snippet.count("function ")
270
- for snippet in code_snippets
260
+ snippet.count("func ") + snippet.count("def ") + snippet.count("function ") for snippet in code_snippets
271
261
  )
272
262
  if function_count > 0:
273
263
  response += "✓ Code is broken into functions/methods\n"
@@ -334,8 +324,7 @@ class BalancedReviewer:
334
324
  has_comments = False
335
325
  if code_snippets:
336
326
  has_comments = any(
337
- "//" in snippet or "/*" in snippet or "#" in snippet or '"""' in snippet
338
- for snippet in code_snippets
327
+ "//" in snippet or "/*" in snippet or "#" in snippet or '"""' in snippet for snippet in code_snippets
339
328
  )
340
329
 
341
330
  if has_comments:
@@ -372,9 +361,7 @@ class BalancedReviewer:
372
361
  # Analyze file structure
373
362
  if file_paths:
374
363
  # Check for separation of concerns
375
- has_separation = (
376
- len(set(str(p).split("/")[-2] for p in file_paths if "/" in str(p))) > 1
377
- )
364
+ has_separation = len(set(str(p).split("/")[-2] for p in file_paths if "/" in str(p))) > 1
378
365
  if has_separation:
379
366
  response += "✓ Changes span multiple modules (good separation)\n"
380
367
  else:
@@ -426,9 +413,7 @@ class ReviewProtocol:
426
413
  except KeyError:
427
414
  focus_enum = ReviewFocus.GENERAL
428
415
 
429
- review = self.reviewer.review(
430
- focus_enum, work_description, code_snippets, file_paths, context
431
- )
416
+ review = self.reviewer.review(focus_enum, work_description, code_snippets, file_paths, context)
432
417
 
433
418
  header = f"Review {self.review_count}/{self.max_reviews} (Focus: {focus_enum.value}):\n\n"
434
419
  footer = "\n\n💡 This is a balanced review - consider both strengths and suggestions."
@@ -61,9 +61,7 @@ For new code, prefer using 'network' directly."""
61
61
  **kwargs: Additional arguments passed to NetworkTool
62
62
  """
63
63
  # Just pass through to NetworkTool
64
- super().__init__(
65
- permission_manager=permission_manager, default_mode=default_mode, **kwargs
66
- )
64
+ super().__init__(permission_manager=permission_manager, default_mode=default_mode, **kwargs)
67
65
 
68
66
  async def call(self, **kwargs) -> str:
69
67
  """Execute swarm via network tool.
@@ -21,10 +21,10 @@ from ...core.model_registry import registry
21
21
 
22
22
  class UnifiedCLITool(BaseTool, CLIAgent):
23
23
  """Unified CLI tool that combines BaseTool and CLIAgent functionality.
24
-
24
+
25
25
  MRO: BaseTool first for proper method resolution order.
26
26
  """
27
-
27
+
28
28
  def __init__(
29
29
  self,
30
30
  name: str,
@@ -34,7 +34,7 @@ class UnifiedCLITool(BaseTool, CLIAgent):
34
34
  permission_manager: Optional[PermissionManager] = None,
35
35
  ):
36
36
  """Initialize unified CLI tool.
37
-
37
+
38
38
  Args:
39
39
  name: Tool name
40
40
  description: Tool description
@@ -45,47 +45,47 @@ class UnifiedCLITool(BaseTool, CLIAgent):
45
45
  # Initialize CLIAgent with config
46
46
  config = AgentConfig(model=default_model)
47
47
  CLIAgent.__init__(self, config)
48
-
48
+
49
49
  # Store tool metadata
50
50
  self._name = name
51
51
  self._description = description
52
52
  self._cli_command = cli_command
53
53
  self.permission_manager = permission_manager
54
-
54
+
55
55
  @property
56
56
  def name(self) -> str:
57
57
  return self._name
58
-
58
+
59
59
  @property
60
60
  def description(self) -> str:
61
61
  return self._description
62
-
62
+
63
63
  @property
64
64
  def cli_command(self) -> str:
65
65
  return self._cli_command
66
-
66
+
67
67
  def build_command(self, prompt: str, **kwargs: Any) -> List[str]:
68
68
  """Build the CLI command with model-specific formatting.
69
-
69
+
70
70
  Args:
71
71
  prompt: The prompt
72
72
  **kwargs: Additional parameters
73
-
73
+
74
74
  Returns:
75
75
  Command arguments list
76
76
  """
77
77
  command = [self.cli_command]
78
-
78
+
79
79
  # Get model config from registry
80
80
  model_config = registry.get(self.config.model)
81
-
81
+
82
82
  # Handle different CLI tool formats
83
83
  if self.cli_command == "claude":
84
84
  if model_config:
85
85
  command.extend(["--model", model_config.full_name])
86
86
  # Claude takes prompt via stdin
87
87
  return command
88
-
88
+
89
89
  elif self.cli_command == "openai":
90
90
  # OpenAI CLI format
91
91
  command.extend(["api", "chat.completions.create"])
@@ -93,14 +93,14 @@ class UnifiedCLITool(BaseTool, CLIAgent):
93
93
  command.extend(["-m", model_config.full_name])
94
94
  command.extend(["-g", "user", prompt])
95
95
  return command
96
-
96
+
97
97
  elif self.cli_command in ["gemini", "grok"]:
98
98
  # Simple format: command --model MODEL prompt
99
99
  if model_config:
100
100
  command.extend(["--model", model_config.full_name])
101
101
  command.append(prompt)
102
102
  return command
103
-
103
+
104
104
  elif self.cli_command == "openhands":
105
105
  # OpenHands format
106
106
  command.extend(["run", prompt])
@@ -109,7 +109,7 @@ class UnifiedCLITool(BaseTool, CLIAgent):
109
109
  if self.config.working_dir:
110
110
  command.extend(["--workspace", str(self.config.working_dir)])
111
111
  return command
112
-
112
+
113
113
  elif self.cli_command == "hanzo":
114
114
  # Hanzo dev format
115
115
  command.append("dev")
@@ -117,13 +117,13 @@ class UnifiedCLITool(BaseTool, CLIAgent):
117
117
  command.extend(["--model", model_config.full_name])
118
118
  command.extend(["--prompt", prompt])
119
119
  return command
120
-
120
+
121
121
  elif self.cli_command == "cline":
122
122
  # Cline format
123
123
  command.append(prompt)
124
124
  command.append("--no-interactive")
125
125
  return command
126
-
126
+
127
127
  elif self.cli_command == "aider":
128
128
  # Aider format
129
129
  if model_config:
@@ -131,24 +131,24 @@ class UnifiedCLITool(BaseTool, CLIAgent):
131
131
  command.extend(["--message", prompt])
132
132
  command.extend(["--yes", "--no-stream"])
133
133
  return command
134
-
134
+
135
135
  elif self.cli_command == "ollama":
136
136
  # Ollama format for local models
137
137
  command.extend(["run", self.config.model.replace("ollama/", "")])
138
138
  command.append(prompt)
139
139
  return command
140
-
140
+
141
141
  # Default format
142
142
  command.append(prompt)
143
143
  return command
144
-
144
+
145
145
  async def call(self, ctx: Context[Any, Any, Any], **params: Any) -> str:
146
146
  """Execute the CLI tool via MCP interface.
147
-
147
+
148
148
  Args:
149
149
  ctx: MCP context
150
150
  **params: Tool parameters
151
-
151
+
152
152
  Returns:
153
153
  Execution result
154
154
  """
@@ -159,23 +159,23 @@ class UnifiedCLITool(BaseTool, CLIAgent):
159
159
  self.config.working_dir = Path(params["working_dir"])
160
160
  if params.get("timeout"):
161
161
  self.config.timeout = params["timeout"]
162
-
162
+
163
163
  # Execute using base agent
164
164
  result = await self.execute(
165
165
  params.get("prompt", ""),
166
166
  context=ctx,
167
167
  )
168
-
168
+
169
169
  return result.content
170
-
170
+
171
171
  def register(self, mcp_server: FastMCP) -> None:
172
172
  """Register this tool with the MCP server.
173
-
173
+
174
174
  Args:
175
175
  mcp_server: The FastMCP server instance
176
176
  """
177
177
  tool_self = self
178
-
178
+
179
179
  @mcp_server.tool(name=self.name, description=self.description)
180
180
  async def tool_wrapper(
181
181
  prompt: str,
@@ -195,15 +195,15 @@ class UnifiedCLITool(BaseTool, CLIAgent):
195
195
 
196
196
  def create_cli_tools(permission_manager: Optional[PermissionManager] = None) -> Dict[str, UnifiedCLITool]:
197
197
  """Create all CLI tools with unified implementation.
198
-
198
+
199
199
  Args:
200
200
  permission_manager: Permission manager for access control
201
-
201
+
202
202
  Returns:
203
203
  Dictionary of tool name to tool instance
204
204
  """
205
205
  tools = {}
206
-
206
+
207
207
  # Define all tools with their configurations
208
208
  tool_configs = [
209
209
  ("claude", "Execute Claude CLI for AI assistance", "claude", "claude"),
@@ -217,7 +217,7 @@ def create_cli_tools(permission_manager: Optional[PermissionManager] = None) ->
217
217
  ("cline", "Execute Cline for autonomous coding", "cline", "claude"),
218
218
  ("aider", "Execute Aider for AI pair programming", "aider", "gpt-4-turbo"),
219
219
  ]
220
-
220
+
221
221
  for name, description, cli_command, default_model in tool_configs:
222
222
  tools[name] = UnifiedCLITool(
223
223
  name=name,
@@ -226,7 +226,7 @@ def create_cli_tools(permission_manager: Optional[PermissionManager] = None) ->
226
226
  default_model=default_model,
227
227
  permission_manager=permission_manager,
228
228
  )
229
-
229
+
230
230
  return tools
231
231
 
232
232
 
@@ -235,20 +235,20 @@ def register_cli_tools(
235
235
  permission_manager: Optional[PermissionManager] = None,
236
236
  ) -> List[BaseTool]:
237
237
  """Register all CLI tools with the MCP server.
238
-
238
+
239
239
  Args:
240
240
  mcp_server: The FastMCP server instance
241
241
  permission_manager: Permission manager for access control
242
-
242
+
243
243
  Returns:
244
244
  List of registered CLI tools
245
245
  """
246
246
  tools = create_cli_tools(permission_manager)
247
-
247
+
248
248
  # Register each tool
249
249
  for tool in tools.values():
250
250
  tool.register(mcp_server)
251
-
251
+
252
252
  return list(tools.values())
253
253
 
254
254
 
@@ -256,4 +256,4 @@ __all__ = [
256
256
  "UnifiedCLITool",
257
257
  "create_cli_tools",
258
258
  "register_cli_tools",
259
- ]
259
+ ]
@@ -177,30 +177,22 @@ Not available: think,write,edit,multi_edit,notebook_edit
177
177
 
178
178
  # Validate required parameters
179
179
  if not description:
180
- await tool_ctx.error(
181
- "Parameter 'description' is required but was None or empty"
182
- )
180
+ await tool_ctx.error("Parameter 'description' is required but was None or empty")
183
181
  return "Error: Parameter 'description' is required but was None or empty"
184
182
 
185
183
  if not invocations:
186
- await tool_ctx.error(
187
- "Parameter 'invocations' is required but was None or empty"
188
- )
184
+ await tool_ctx.error("Parameter 'invocations' is required but was None or empty")
189
185
  return "Error: Parameter 'invocations' is required but was None or empty"
190
186
 
191
187
  if not isinstance(invocations, list) or len(invocations) == 0:
192
188
  await tool_ctx.error("Parameter 'invocations' must be a non-empty list")
193
189
  return "Error: Parameter 'invocations' must be a non-empty list"
194
190
 
195
- await tool_ctx.info(
196
- f"Executing batch operation: {description} ({len(invocations)} invocations)"
197
- )
191
+ await tool_ctx.info(f"Executing batch operation: {description} ({len(invocations)} invocations)")
198
192
 
199
193
  # Execute all tool invocations in parallel
200
194
  tasks: list[asyncio.Future[dict[str, Any]]] = []
201
- invocation_map: dict[asyncio.Future[dict[str, Any]], dict[str, Any]] = (
202
- {}
203
- ) # Map task Future to invocation
195
+ invocation_map: dict[asyncio.Future[dict[str, Any]], dict[str, Any]] = {} # Map task Future to invocation
204
196
 
205
197
  for i, invocation in enumerate(invocations):
206
198
  # Extract tool name and input from invocation
@@ -213,9 +205,7 @@ Not available: think,write,edit,multi_edit,notebook_edit
213
205
  await tool_ctx.error(error_message)
214
206
  # Add direct result for this invocation
215
207
  tasks.append(asyncio.Future())
216
- tasks[-1].set_result(
217
- {"invocation": invocation, "result": f"Error: {error_message}"}
218
- )
208
+ tasks[-1].set_result({"invocation": invocation, "result": f"Error: {error_message}"})
219
209
  invocation_map[tasks[-1]] = invocation
220
210
  continue
221
211
 
@@ -225,9 +215,7 @@ Not available: think,write,edit,multi_edit,notebook_edit
225
215
  await tool_ctx.error(error_message)
226
216
  # Add direct result for this invocation
227
217
  tasks.append(asyncio.Future())
228
- tasks[-1].set_result(
229
- {"invocation": invocation, "result": f"Error: {error_message}"}
230
- )
218
+ tasks[-1].set_result({"invocation": invocation, "result": f"Error: {error_message}"})
231
219
  invocation_map[tasks[-1]] = invocation
232
220
  continue
233
221
 
@@ -237,9 +225,7 @@ Not available: think,write,edit,multi_edit,notebook_edit
237
225
  await tool_ctx.info(f"Creating task for tool: {tool_name}")
238
226
 
239
227
  # Create coroutine for this tool execution
240
- async def execute_tool(
241
- tool_obj: BaseTool, tool_name: str, tool_input: dict[str, Any]
242
- ):
228
+ async def execute_tool(tool_obj: BaseTool, tool_name: str, tool_input: dict[str, Any]):
243
229
  try:
244
230
  await tool_ctx.info(f"Executing tool: {tool_name}")
245
231
  result = await tool_obj.call(ctx, **tool_input)
@@ -265,9 +251,7 @@ Not available: think,write,edit,multi_edit,notebook_edit
265
251
  await tool_ctx.error(error_message)
266
252
  # Add direct result for this invocation
267
253
  tasks.append(asyncio.Future())
268
- tasks[-1].set_result(
269
- {"invocation": invocation, "result": f"Error: {error_message}"}
270
- )
254
+ tasks[-1].set_result({"invocation": invocation, "result": f"Error: {error_message}"})
271
255
  invocation_map[tasks[-1]] = invocation
272
256
 
273
257
  # Wait for all tasks to complete
@@ -284,9 +268,7 @@ Not available: think,write,edit,multi_edit,notebook_edit
284
268
  tool_name: str = invocation.get("tool_name", "unknown")
285
269
  error_message = f"Unexpected error in tool '{tool_name}': {str(e)}"
286
270
  await tool_ctx.error(error_message)
287
- results.append(
288
- {"invocation": invocation, "result": f"Error: {error_message}"}
289
- )
271
+ results.append({"invocation": invocation, "result": f"Error: {error_message}"})
290
272
 
291
273
  # Extract cursor if provided
292
274
  cursor = params.get("cursor")
@@ -314,9 +296,7 @@ Not available: think,write,edit,multi_edit,notebook_edit
314
296
  )
315
297
 
316
298
  # Create paginated response with token awareness
317
- paginated_response = create_paginated_response(
318
- formatted_results, cursor=cursor, use_token_limit=True
319
- )
299
+ paginated_response = create_paginated_response(formatted_results, cursor=cursor, use_token_limit=True)
320
300
 
321
301
  # Convert paginated response to string format for MCP
322
302
  if isinstance(paginated_response, dict) and "items" in paginated_response:
@@ -326,13 +306,9 @@ Not available: think,write,edit,multi_edit,notebook_edit
326
306
  # Add header
327
307
  result_parts.append(f"=== Batch operation: {description} ===")
328
308
  result_parts.append(f"Total invocations: {len(invocations)}")
329
- result_parts.append(
330
- f"Showing results: {len(paginated_response['items'])} of {len(results)}"
331
- )
309
+ result_parts.append(f"Showing results: {len(paginated_response['items'])} of {len(results)}")
332
310
  if paginated_response.get("hasMore"):
333
- result_parts.append(
334
- f"More results available - use cursor: {paginated_response.get('nextCursor')}"
335
- )
311
+ result_parts.append(f"More results available - use cursor: {paginated_response.get('nextCursor')}")
336
312
  result_parts.append("")
337
313
 
338
314
  # Format each result
@@ -352,12 +328,8 @@ Not available: think,write,edit,multi_edit,notebook_edit
352
328
 
353
329
  # If there's a next cursor, we need to preserve it in the response
354
330
  # For now, append it as a note at the end
355
- if paginated_response.get("hasMore") and paginated_response.get(
356
- "nextCursor"
357
- ):
358
- formatted_output += (
359
- f"\n\n[To continue, use cursor: {paginated_response['nextCursor']}]"
360
- )
331
+ if paginated_response.get("hasMore") and paginated_response.get("nextCursor"):
332
+ formatted_output += f"\n\n[To continue, use cursor: {paginated_response['nextCursor']}]"
361
333
 
362
334
  await tool_ctx.info(
363
335
  f"Batch operation '{description}' completed with {len(paginated_response['items'])} results"
@@ -424,6 +396,4 @@ Not available: think,write,edit,multi_edit,notebook_edit
424
396
  cursor: Cursor,
425
397
  ctx: MCPContext,
426
398
  ) -> str:
427
- return await tool_self.call(
428
- ctx, description=description, invocations=invocations, cursor=cursor
429
- )
399
+ return await tool_self.call(ctx, description=description, invocations=invocations, cursor=cursor)