hanzo-mcp 0.7.7__py3-none-any.whl → 0.8.0__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (178) hide show
  1. hanzo_mcp/__init__.py +6 -0
  2. hanzo_mcp/__main__.py +1 -1
  3. hanzo_mcp/analytics/__init__.py +2 -2
  4. hanzo_mcp/analytics/posthog_analytics.py +76 -82
  5. hanzo_mcp/cli.py +31 -36
  6. hanzo_mcp/cli_enhanced.py +94 -72
  7. hanzo_mcp/cli_plugin.py +27 -17
  8. hanzo_mcp/config/__init__.py +2 -2
  9. hanzo_mcp/config/settings.py +112 -88
  10. hanzo_mcp/config/tool_config.py +32 -34
  11. hanzo_mcp/dev_server.py +66 -67
  12. hanzo_mcp/prompts/__init__.py +94 -12
  13. hanzo_mcp/prompts/enhanced_prompts.py +809 -0
  14. hanzo_mcp/prompts/example_custom_prompt.py +6 -5
  15. hanzo_mcp/prompts/project_todo_reminder.py +0 -1
  16. hanzo_mcp/prompts/tool_explorer.py +10 -7
  17. hanzo_mcp/server.py +17 -21
  18. hanzo_mcp/server_enhanced.py +15 -22
  19. hanzo_mcp/tools/__init__.py +56 -28
  20. hanzo_mcp/tools/agent/__init__.py +16 -19
  21. hanzo_mcp/tools/agent/agent.py +82 -65
  22. hanzo_mcp/tools/agent/agent_tool.py +152 -122
  23. hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +66 -62
  24. hanzo_mcp/tools/agent/clarification_protocol.py +55 -50
  25. hanzo_mcp/tools/agent/clarification_tool.py +11 -10
  26. hanzo_mcp/tools/agent/claude_cli_tool.py +21 -20
  27. hanzo_mcp/tools/agent/claude_desktop_auth.py +130 -144
  28. hanzo_mcp/tools/agent/cli_agent_base.py +59 -53
  29. hanzo_mcp/tools/agent/code_auth.py +102 -107
  30. hanzo_mcp/tools/agent/code_auth_tool.py +28 -27
  31. hanzo_mcp/tools/agent/codex_cli_tool.py +20 -19
  32. hanzo_mcp/tools/agent/critic_tool.py +86 -73
  33. hanzo_mcp/tools/agent/gemini_cli_tool.py +21 -20
  34. hanzo_mcp/tools/agent/grok_cli_tool.py +21 -20
  35. hanzo_mcp/tools/agent/iching_tool.py +404 -139
  36. hanzo_mcp/tools/agent/network_tool.py +89 -73
  37. hanzo_mcp/tools/agent/prompt.py +2 -1
  38. hanzo_mcp/tools/agent/review_tool.py +101 -98
  39. hanzo_mcp/tools/agent/swarm_alias.py +87 -0
  40. hanzo_mcp/tools/agent/swarm_tool.py +246 -161
  41. hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +134 -92
  42. hanzo_mcp/tools/agent/tool_adapter.py +21 -11
  43. hanzo_mcp/tools/common/__init__.py +1 -1
  44. hanzo_mcp/tools/common/base.py +3 -5
  45. hanzo_mcp/tools/common/batch_tool.py +46 -39
  46. hanzo_mcp/tools/common/config_tool.py +120 -84
  47. hanzo_mcp/tools/common/context.py +1 -5
  48. hanzo_mcp/tools/common/context_fix.py +5 -3
  49. hanzo_mcp/tools/common/critic_tool.py +4 -8
  50. hanzo_mcp/tools/common/decorators.py +58 -56
  51. hanzo_mcp/tools/common/enhanced_base.py +29 -32
  52. hanzo_mcp/tools/common/fastmcp_pagination.py +91 -94
  53. hanzo_mcp/tools/common/forgiving_edit.py +91 -87
  54. hanzo_mcp/tools/common/mode.py +15 -17
  55. hanzo_mcp/tools/common/mode_loader.py +27 -24
  56. hanzo_mcp/tools/common/paginated_base.py +61 -53
  57. hanzo_mcp/tools/common/paginated_response.py +72 -79
  58. hanzo_mcp/tools/common/pagination.py +50 -53
  59. hanzo_mcp/tools/common/permissions.py +4 -4
  60. hanzo_mcp/tools/common/personality.py +186 -138
  61. hanzo_mcp/tools/common/plugin_loader.py +54 -54
  62. hanzo_mcp/tools/common/stats.py +65 -47
  63. hanzo_mcp/tools/common/test_helpers.py +31 -0
  64. hanzo_mcp/tools/common/thinking_tool.py +4 -8
  65. hanzo_mcp/tools/common/tool_disable.py +17 -12
  66. hanzo_mcp/tools/common/tool_enable.py +13 -14
  67. hanzo_mcp/tools/common/tool_list.py +36 -28
  68. hanzo_mcp/tools/common/truncate.py +23 -23
  69. hanzo_mcp/tools/config/__init__.py +4 -4
  70. hanzo_mcp/tools/config/config_tool.py +42 -29
  71. hanzo_mcp/tools/config/index_config.py +37 -34
  72. hanzo_mcp/tools/config/mode_tool.py +175 -55
  73. hanzo_mcp/tools/database/__init__.py +15 -12
  74. hanzo_mcp/tools/database/database_manager.py +77 -75
  75. hanzo_mcp/tools/database/graph.py +137 -91
  76. hanzo_mcp/tools/database/graph_add.py +30 -18
  77. hanzo_mcp/tools/database/graph_query.py +178 -102
  78. hanzo_mcp/tools/database/graph_remove.py +33 -28
  79. hanzo_mcp/tools/database/graph_search.py +97 -75
  80. hanzo_mcp/tools/database/graph_stats.py +91 -59
  81. hanzo_mcp/tools/database/sql.py +107 -79
  82. hanzo_mcp/tools/database/sql_query.py +30 -24
  83. hanzo_mcp/tools/database/sql_search.py +29 -25
  84. hanzo_mcp/tools/database/sql_stats.py +47 -35
  85. hanzo_mcp/tools/editor/neovim_command.py +25 -28
  86. hanzo_mcp/tools/editor/neovim_edit.py +21 -23
  87. hanzo_mcp/tools/editor/neovim_session.py +60 -54
  88. hanzo_mcp/tools/filesystem/__init__.py +31 -30
  89. hanzo_mcp/tools/filesystem/ast_multi_edit.py +329 -249
  90. hanzo_mcp/tools/filesystem/ast_tool.py +4 -4
  91. hanzo_mcp/tools/filesystem/base.py +1 -1
  92. hanzo_mcp/tools/filesystem/batch_search.py +316 -224
  93. hanzo_mcp/tools/filesystem/content_replace.py +4 -4
  94. hanzo_mcp/tools/filesystem/diff.py +71 -59
  95. hanzo_mcp/tools/filesystem/directory_tree.py +7 -7
  96. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +49 -37
  97. hanzo_mcp/tools/filesystem/edit.py +4 -4
  98. hanzo_mcp/tools/filesystem/find.py +173 -80
  99. hanzo_mcp/tools/filesystem/find_files.py +73 -52
  100. hanzo_mcp/tools/filesystem/git_search.py +157 -104
  101. hanzo_mcp/tools/filesystem/grep.py +8 -8
  102. hanzo_mcp/tools/filesystem/multi_edit.py +4 -8
  103. hanzo_mcp/tools/filesystem/read.py +12 -10
  104. hanzo_mcp/tools/filesystem/rules_tool.py +59 -43
  105. hanzo_mcp/tools/filesystem/search_tool.py +263 -207
  106. hanzo_mcp/tools/filesystem/symbols_tool.py +94 -54
  107. hanzo_mcp/tools/filesystem/tree.py +35 -33
  108. hanzo_mcp/tools/filesystem/unix_aliases.py +13 -18
  109. hanzo_mcp/tools/filesystem/watch.py +37 -36
  110. hanzo_mcp/tools/filesystem/write.py +4 -8
  111. hanzo_mcp/tools/jupyter/__init__.py +4 -4
  112. hanzo_mcp/tools/jupyter/base.py +4 -5
  113. hanzo_mcp/tools/jupyter/jupyter.py +67 -47
  114. hanzo_mcp/tools/jupyter/notebook_edit.py +4 -4
  115. hanzo_mcp/tools/jupyter/notebook_read.py +4 -7
  116. hanzo_mcp/tools/llm/__init__.py +5 -7
  117. hanzo_mcp/tools/llm/consensus_tool.py +72 -52
  118. hanzo_mcp/tools/llm/llm_manage.py +101 -60
  119. hanzo_mcp/tools/llm/llm_tool.py +226 -166
  120. hanzo_mcp/tools/llm/provider_tools.py +25 -26
  121. hanzo_mcp/tools/lsp/__init__.py +1 -1
  122. hanzo_mcp/tools/lsp/lsp_tool.py +228 -143
  123. hanzo_mcp/tools/mcp/__init__.py +2 -3
  124. hanzo_mcp/tools/mcp/mcp_add.py +27 -25
  125. hanzo_mcp/tools/mcp/mcp_remove.py +7 -8
  126. hanzo_mcp/tools/mcp/mcp_stats.py +23 -22
  127. hanzo_mcp/tools/mcp/mcp_tool.py +129 -98
  128. hanzo_mcp/tools/memory/__init__.py +39 -21
  129. hanzo_mcp/tools/memory/knowledge_tools.py +124 -99
  130. hanzo_mcp/tools/memory/memory_tools.py +90 -108
  131. hanzo_mcp/tools/search/__init__.py +7 -2
  132. hanzo_mcp/tools/search/find_tool.py +297 -212
  133. hanzo_mcp/tools/search/unified_search.py +366 -314
  134. hanzo_mcp/tools/shell/__init__.py +8 -7
  135. hanzo_mcp/tools/shell/auto_background.py +56 -49
  136. hanzo_mcp/tools/shell/base.py +1 -1
  137. hanzo_mcp/tools/shell/base_process.py +75 -75
  138. hanzo_mcp/tools/shell/bash_session.py +2 -2
  139. hanzo_mcp/tools/shell/bash_session_executor.py +4 -4
  140. hanzo_mcp/tools/shell/bash_tool.py +24 -31
  141. hanzo_mcp/tools/shell/command_executor.py +12 -12
  142. hanzo_mcp/tools/shell/logs.py +43 -33
  143. hanzo_mcp/tools/shell/npx.py +13 -13
  144. hanzo_mcp/tools/shell/npx_background.py +24 -21
  145. hanzo_mcp/tools/shell/npx_tool.py +18 -22
  146. hanzo_mcp/tools/shell/open.py +19 -21
  147. hanzo_mcp/tools/shell/pkill.py +31 -26
  148. hanzo_mcp/tools/shell/process_tool.py +32 -32
  149. hanzo_mcp/tools/shell/processes.py +57 -58
  150. hanzo_mcp/tools/shell/run_background.py +24 -25
  151. hanzo_mcp/tools/shell/run_command.py +5 -5
  152. hanzo_mcp/tools/shell/run_command_windows.py +5 -5
  153. hanzo_mcp/tools/shell/session_storage.py +3 -3
  154. hanzo_mcp/tools/shell/streaming_command.py +141 -126
  155. hanzo_mcp/tools/shell/uvx.py +24 -25
  156. hanzo_mcp/tools/shell/uvx_background.py +35 -33
  157. hanzo_mcp/tools/shell/uvx_tool.py +18 -22
  158. hanzo_mcp/tools/todo/__init__.py +6 -2
  159. hanzo_mcp/tools/todo/todo.py +50 -37
  160. hanzo_mcp/tools/todo/todo_read.py +5 -8
  161. hanzo_mcp/tools/todo/todo_write.py +5 -7
  162. hanzo_mcp/tools/vector/__init__.py +40 -28
  163. hanzo_mcp/tools/vector/ast_analyzer.py +176 -143
  164. hanzo_mcp/tools/vector/git_ingester.py +170 -179
  165. hanzo_mcp/tools/vector/index_tool.py +96 -44
  166. hanzo_mcp/tools/vector/infinity_store.py +283 -228
  167. hanzo_mcp/tools/vector/mock_infinity.py +39 -40
  168. hanzo_mcp/tools/vector/project_manager.py +88 -78
  169. hanzo_mcp/tools/vector/vector.py +59 -42
  170. hanzo_mcp/tools/vector/vector_index.py +30 -27
  171. hanzo_mcp/tools/vector/vector_search.py +64 -45
  172. hanzo_mcp/types.py +6 -4
  173. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.dist-info}/METADATA +1 -1
  174. hanzo_mcp-0.8.0.dist-info/RECORD +185 -0
  175. hanzo_mcp-0.7.7.dist-info/RECORD +0 -182
  176. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.dist-info}/WHEEL +0 -0
  177. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.dist-info}/entry_points.txt +0 -0
  178. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.dist-info}/top_level.txt +0 -0
@@ -3,10 +3,10 @@
3
3
  This tool manages API keys and accounts for Claude Code and other AI coding tools.
4
4
  """
5
5
 
6
- from typing import Annotated, Optional, TypedDict, Unpack, final, override
6
+ from typing import Unpack, Optional, TypedDict, final, override
7
+
7
8
  from mcp.server import FastMCP
8
9
  from mcp.server.fastmcp import Context as MCPContext
9
- from pydantic import Field
10
10
 
11
11
  from hanzo_mcp.tools.common.base import BaseTool
12
12
  from hanzo_mcp.tools.common.context import create_tool_context
@@ -15,6 +15,7 @@ from hanzo_mcp.tools.agent.code_auth import CodeAuthManager
15
15
 
16
16
  class CodeAuthParams(TypedDict, total=False):
17
17
  """Parameters for code auth tool."""
18
+
18
19
  action: str
19
20
  account: Optional[str]
20
21
  provider: Optional[str]
@@ -28,13 +29,13 @@ class CodeAuthParams(TypedDict, total=False):
28
29
  @final
29
30
  class CodeAuthTool(BaseTool):
30
31
  """Tool for managing Claude Code authentication and API keys."""
31
-
32
+
32
33
  @property
33
34
  @override
34
35
  def name(self) -> str:
35
36
  """Get the tool name."""
36
37
  return "code_auth"
37
-
38
+
38
39
  @property
39
40
  @override
40
41
  def description(self) -> str:
@@ -60,11 +61,11 @@ code_auth switch --account personal
60
61
  code_auth agent --agent_id swarm_1 --parent_account work
61
62
 
62
63
  Providers: claude, openai, azure, deepseek, google, groq"""
63
-
64
+
64
65
  def __init__(self):
65
66
  """Initialize the code auth tool."""
66
67
  self.auth_manager = CodeAuthManager()
67
-
68
+
68
69
  @override
69
70
  async def call(
70
71
  self,
@@ -72,19 +73,19 @@ Providers: claude, openai, azure, deepseek, google, groq"""
72
73
  **params: Unpack[CodeAuthParams],
73
74
  ) -> str:
74
75
  """Execute the code auth tool.
75
-
76
+
76
77
  Args:
77
78
  ctx: MCP context
78
79
  **params: Tool parameters
79
-
80
+
80
81
  Returns:
81
82
  Result message
82
83
  """
83
84
  tool_ctx = create_tool_context(ctx)
84
85
  await tool_ctx.set_tool_info(self.name)
85
-
86
+
86
87
  action = params.get("action", "status")
87
-
88
+
88
89
  if action == "status":
89
90
  current = self.auth_manager.get_active_account()
90
91
  if current:
@@ -92,12 +93,12 @@ Providers: claude, openai, azure, deepseek, google, groq"""
92
93
  if info:
93
94
  return f"Logged in as: {current} ({info['provider']})"
94
95
  return "Not logged in"
95
-
96
+
96
97
  elif action == "list":
97
98
  accounts = self.auth_manager.list_accounts()
98
99
  if not accounts:
99
100
  return "No accounts configured"
100
-
101
+
101
102
  current = self.auth_manager.get_active_account()
102
103
  lines = ["Configured accounts:"]
103
104
  for account in accounts:
@@ -105,52 +106,52 @@ Providers: claude, openai, azure, deepseek, google, groq"""
105
106
  marker = " (active)" if account == current else ""
106
107
  lines.append(f" - {account}: {info['provider']}{marker}")
107
108
  return "\n".join(lines)
108
-
109
+
109
110
  elif action == "create":
110
111
  account = params.get("account")
111
112
  if not account:
112
113
  return "Error: account name required"
113
-
114
+
114
115
  provider = params.get("provider", "claude")
115
116
  api_key = params.get("api_key")
116
117
  model = params.get("model")
117
118
  description = params.get("description")
118
-
119
+
119
120
  success, msg = self.auth_manager.create_account(
120
121
  account, provider, api_key, model, description
121
122
  )
122
123
  return msg
123
-
124
+
124
125
  elif action == "login":
125
126
  account = params.get("account", "default")
126
127
  success, msg = self.auth_manager.login(account)
127
128
  return msg
128
-
129
+
129
130
  elif action == "logout":
130
131
  success, msg = self.auth_manager.logout()
131
132
  return msg
132
-
133
+
133
134
  elif action == "switch":
134
135
  account = params.get("account")
135
136
  if not account:
136
137
  return "Error: account name required"
137
-
138
+
138
139
  success, msg = self.auth_manager.switch_account(account)
139
140
  return msg
140
-
141
+
141
142
  elif action == "agent":
142
143
  agent_id = params.get("agent_id")
143
144
  if not agent_id:
144
145
  return "Error: agent_id required"
145
-
146
+
146
147
  provider = params.get("provider", "claude")
147
148
  parent_account = params.get("parent_account")
148
-
149
+
149
150
  # Try to create agent account
150
151
  success, result = self.auth_manager.create_agent_account(
151
152
  agent_id, provider, parent_account
152
153
  )
153
-
154
+
154
155
  if success:
155
156
  # Get credentials
156
157
  creds = self.auth_manager.get_agent_credentials(agent_id)
@@ -160,15 +161,15 @@ Providers: claude, openai, azure, deepseek, google, groq"""
160
161
  return f"Agent account created but no credentials: {result}"
161
162
  else:
162
163
  return f"Failed to create agent account: {result}"
163
-
164
+
164
165
  else:
165
166
  return f"Unknown action: {action}. Use: status, list, create, login, logout, switch, agent"
166
-
167
+
167
168
  @override
168
169
  def register(self, mcp_server: FastMCP) -> None:
169
170
  """Register this tool with the MCP server."""
170
171
  tool_self = self
171
-
172
+
172
173
  @mcp_server.tool(name=self.name, description=self.description)
173
174
  async def code_auth(
174
175
  ctx: MCPContext,
@@ -191,4 +192,4 @@ Providers: claude, openai, azure, deepseek, google, groq"""
191
192
  description=description,
192
193
  agent_id=agent_id,
193
194
  parent_account=parent_account,
194
- )
195
+ )
@@ -4,26 +4,27 @@ This tool provides integration with OpenAI's CLI (openai command),
4
4
  allowing programmatic execution of GPT-4 and other models for code tasks.
5
5
  """
6
6
 
7
- from typing import List, Optional, override, final
7
+ from typing import List, Optional, final, override
8
+
8
9
  from mcp.server import FastMCP
9
10
  from mcp.server.fastmcp import Context as MCPContext
10
11
 
11
- from hanzo_mcp.tools.agent.cli_agent_base import CLIAgentBase
12
12
  from hanzo_mcp.tools.common.permissions import PermissionManager
13
+ from hanzo_mcp.tools.agent.cli_agent_base import CLIAgentBase
13
14
 
14
15
 
15
16
  @final
16
17
  class CodexCLITool(CLIAgentBase):
17
18
  """Tool for executing OpenAI CLI (formerly Codex)."""
18
-
19
+
19
20
  def __init__(
20
21
  self,
21
22
  permission_manager: PermissionManager,
22
23
  model: Optional[str] = None,
23
- **kwargs
24
+ **kwargs,
24
25
  ):
25
26
  """Initialize Codex CLI tool.
26
-
27
+
27
28
  Args:
28
29
  permission_manager: Permission manager for access control
29
30
  model: Optional model override (defaults to gpt-4o)
@@ -35,15 +36,15 @@ class CodexCLITool(CLIAgentBase):
35
36
  provider_name="OpenAI",
36
37
  default_model=model or "gpt-4o",
37
38
  env_vars=["OPENAI_API_KEY"],
38
- **kwargs
39
+ **kwargs,
39
40
  )
40
-
41
+
41
42
  @property
42
43
  @override
43
44
  def name(self) -> str:
44
45
  """Get the tool name."""
45
46
  return "codex_cli"
46
-
47
+
47
48
  @property
48
49
  @override
49
50
  def description(self) -> str:
@@ -68,42 +69,42 @@ Requirements:
68
69
  - OpenAI CLI must be installed: pip install openai
69
70
  - OPENAI_API_KEY environment variable
70
71
  """
71
-
72
+
72
73
  @override
73
74
  def get_cli_args(self, prompt: str, **kwargs) -> List[str]:
74
75
  """Get CLI arguments for OpenAI.
75
-
76
+
76
77
  Args:
77
78
  prompt: The prompt to send
78
79
  **kwargs: Additional arguments (model, temperature, etc.)
79
-
80
+
80
81
  Returns:
81
82
  List of command arguments
82
83
  """
83
84
  args = ["api", "chat.completions.create"]
84
-
85
+
85
86
  # Add model
86
87
  model = kwargs.get("model", self.default_model)
87
88
  args.extend(["-m", model])
88
-
89
+
89
90
  # Add temperature if specified
90
91
  if "temperature" in kwargs:
91
92
  args.extend(["--temperature", str(kwargs["temperature"])])
92
-
93
+
93
94
  # Add max tokens if specified
94
95
  if "max_tokens" in kwargs:
95
96
  args.extend(["--max-tokens", str(kwargs["max_tokens"])])
96
-
97
+
97
98
  # Add the prompt as a message
98
99
  args.extend(["-g", prompt])
99
-
100
+
100
101
  return args
101
-
102
+
102
103
  @override
103
104
  def register(self, mcp_server: FastMCP) -> None:
104
105
  """Register this tool with the MCP server."""
105
106
  tool_self = self
106
-
107
+
107
108
  @mcp_server.tool(name=self.name, description=self.description)
108
109
  async def codex_cli(
109
110
  ctx: MCPContext,
@@ -120,4 +121,4 @@ Requirements:
120
121
  temperature=temperature,
121
122
  max_tokens=max_tokens,
122
123
  working_dir=working_dir,
123
- )
124
+ )
@@ -1,16 +1,17 @@
1
1
  """Critic tool for agents to request critical review from main loop."""
2
2
 
3
- import json
4
- from typing import Any, Dict, List, Optional, override
5
3
  from enum import Enum
4
+ from typing import List, Optional, override
6
5
 
7
- from hanzo_mcp.tools.common.base import BaseTool
8
- from mcp.server.fastmcp import Context as MCPContext
9
6
  from mcp.server import FastMCP
7
+ from mcp.server.fastmcp import Context as MCPContext
8
+
9
+ from hanzo_mcp.tools.common.base import BaseTool
10
10
 
11
11
 
12
12
  class ReviewType(Enum):
13
13
  """Types of review requests."""
14
+
14
15
  CODE_QUALITY = "code_quality"
15
16
  CORRECTNESS = "correctness"
16
17
  PERFORMANCE = "performance"
@@ -22,9 +23,9 @@ class ReviewType(Enum):
22
23
 
23
24
  class CriticTool(BaseTool):
24
25
  """Tool for agents to request critical review from the main loop."""
25
-
26
+
26
27
  name = "critic"
27
-
28
+
28
29
  @property
29
30
  @override
30
31
  def description(self) -> str:
@@ -55,7 +56,7 @@ critic(
55
56
  file_paths=["/path/to/atomic.go", "/path/to/network.go"],
56
57
  specific_concerns="Are the imports in the correct format and location?"
57
58
  )"""
58
-
59
+
59
60
  async def call(
60
61
  self,
61
62
  ctx: MCPContext,
@@ -63,16 +64,16 @@ critic(
63
64
  work_description: str,
64
65
  code_snippets: Optional[List[str]] = None,
65
66
  file_paths: Optional[List[str]] = None,
66
- specific_concerns: Optional[str] = None
67
+ specific_concerns: Optional[str] = None,
67
68
  ) -> str:
68
69
  """This is a placeholder - actual implementation happens in AgentTool."""
69
70
  # This tool is handled specially in the agent execution
70
71
  return f"Critic review requested for: {work_description}"
71
-
72
+
72
73
  def register(self, server: FastMCP) -> None:
73
74
  """Register the tool with the MCP server."""
74
75
  tool_self = self
75
-
76
+
76
77
  @server.tool(name=self.name, description=self.description)
77
78
  async def critic(
78
79
  ctx: MCPContext,
@@ -80,7 +81,7 @@ critic(
80
81
  work_description: str,
81
82
  code_snippets: Optional[List[str]] = None,
82
83
  file_paths: Optional[List[str]] = None,
83
- specific_concerns: Optional[str] = None
84
+ specific_concerns: Optional[str] = None,
84
85
  ) -> str:
85
86
  return await tool_self.call(
86
87
  ctx,
@@ -88,13 +89,13 @@ critic(
88
89
  work_description,
89
90
  code_snippets,
90
91
  file_paths,
91
- specific_concerns
92
+ specific_concerns,
92
93
  )
93
94
 
94
95
 
95
96
  class AutoCritic:
96
97
  """Automated critic that provides harsh but constructive feedback."""
97
-
98
+
98
99
  def __init__(self):
99
100
  self.review_patterns = {
100
101
  ReviewType.CODE_QUALITY: self._review_code_quality,
@@ -105,82 +106,96 @@ class AutoCritic:
105
106
  ReviewType.BEST_PRACTICES: self._review_best_practices,
106
107
  ReviewType.GENERAL: self._review_general,
107
108
  }
108
-
109
+
109
110
  def review(
110
111
  self,
111
112
  review_type: ReviewType,
112
113
  work_description: str,
113
114
  code_snippets: Optional[List[str]] = None,
114
115
  file_paths: Optional[List[str]] = None,
115
- specific_concerns: Optional[str] = None
116
+ specific_concerns: Optional[str] = None,
116
117
  ) -> str:
117
118
  """Perform automated critical review."""
118
119
  review_func = self.review_patterns.get(review_type, self._review_general)
119
- return review_func(work_description, code_snippets, file_paths, specific_concerns)
120
-
120
+ return review_func(
121
+ work_description, code_snippets, file_paths, specific_concerns
122
+ )
123
+
121
124
  def _review_code_quality(
122
125
  self,
123
126
  work_description: str,
124
127
  code_snippets: Optional[List[str]],
125
128
  file_paths: Optional[List[str]],
126
- specific_concerns: Optional[str]
129
+ specific_concerns: Optional[str],
127
130
  ) -> str:
128
131
  """Review code quality aspects."""
129
132
  issues = []
130
133
  suggestions = []
131
-
134
+
132
135
  # Check for common code quality issues
133
136
  if code_snippets:
134
137
  for snippet in code_snippets:
135
138
  # Check for proper error handling
136
139
  if "error" in snippet.lower() and "if err" not in snippet:
137
- issues.append("āŒ Missing error handling - always check errors in Go")
138
-
140
+ issues.append(
141
+ "āŒ Missing error handling - always check errors in Go"
142
+ )
143
+
139
144
  # Check for magic numbers
140
145
  if any(char.isdigit() for char in snippet) and "const" not in snippet:
141
- suggestions.append("šŸ’” Consider extracting magic numbers to named constants")
142
-
146
+ suggestions.append(
147
+ "šŸ’” Consider extracting magic numbers to named constants"
148
+ )
149
+
143
150
  # Check for proper imports
144
151
  if "import" in snippet:
145
152
  if '"fmt"' in snippet and snippet.count("fmt.") == 0:
146
153
  issues.append("āŒ Unused import 'fmt' - remove unused imports")
147
154
  if not snippet.strip().endswith(")") and "import (" in snippet:
148
155
  issues.append("āŒ Import block not properly closed")
149
-
156
+
150
157
  # General quality checks
151
158
  if "fix" in work_description.lower():
152
159
  suggestions.append("šŸ’” Ensure you've tested the fix thoroughly")
153
160
  suggestions.append("šŸ’” Consider edge cases and error scenarios")
154
-
161
+
155
162
  if file_paths and len(file_paths) > 5:
156
- suggestions.append("šŸ’” Large number of files modified - consider breaking into smaller PRs")
157
-
163
+ suggestions.append(
164
+ "šŸ’” Large number of files modified - consider breaking into smaller PRs"
165
+ )
166
+
158
167
  # Build response
159
168
  response = "šŸ” CODE QUALITY REVIEW:\n\n"
160
-
169
+
161
170
  if issues:
162
171
  response += "Issues Found:\n" + "\n".join(issues) + "\n\n"
163
172
  else:
164
173
  response += "āœ… No major code quality issues detected.\n\n"
165
-
174
+
166
175
  if suggestions:
167
- response += "Suggestions for Improvement:\n" + "\n".join(suggestions) + "\n\n"
168
-
176
+ response += (
177
+ "Suggestions for Improvement:\n" + "\n".join(suggestions) + "\n\n"
178
+ )
179
+
169
180
  if specific_concerns:
170
181
  response += f"Regarding your concern: '{specific_concerns}'\n"
171
182
  if "import" in specific_concerns.lower():
172
183
  response += "→ Imports look properly formatted. Ensure they're in the standard order: stdlib, external, internal.\n"
173
-
174
- response += "\nOverall: " + ("āš ļø Address the issues before proceeding." if issues else "āœ… Good work, but always room for improvement!")
175
-
184
+
185
+ response += "\nOverall: " + (
186
+ "āš ļø Address the issues before proceeding."
187
+ if issues
188
+ else "āœ… Good work, but always room for improvement!"
189
+ )
190
+
176
191
  return response
177
-
192
+
178
193
  def _review_correctness(
179
194
  self,
180
195
  work_description: str,
181
196
  code_snippets: Optional[List[str]],
182
197
  file_paths: Optional[List[str]],
183
- specific_concerns: Optional[str]
198
+ specific_concerns: Optional[str],
184
199
  ) -> str:
185
200
  """Review correctness aspects."""
186
201
  return """šŸ” CORRECTNESS REVIEW:
@@ -197,13 +212,13 @@ Specific Checks:
197
212
  - If refactoring: Confirm behavior is preserved
198
213
 
199
214
  āš ļø Remember: Working code > elegant code. Make sure it works first!"""
200
-
215
+
201
216
  def _review_performance(
202
217
  self,
203
218
  work_description: str,
204
219
  code_snippets: Optional[List[str]],
205
220
  file_paths: Optional[List[str]],
206
- specific_concerns: Optional[str]
221
+ specific_concerns: Optional[str],
207
222
  ) -> str:
208
223
  """Review performance aspects."""
209
224
  return """šŸ” PERFORMANCE REVIEW:
@@ -220,13 +235,13 @@ For file operations:
220
235
  - Avoid reading entire files into memory if possible
221
236
 
222
237
  šŸ’” Remember: Premature optimization is evil, but obvious inefficiencies should be fixed."""
223
-
238
+
224
239
  def _review_security(
225
240
  self,
226
241
  work_description: str,
227
242
  code_snippets: Optional[List[str]],
228
243
  file_paths: Optional[List[str]],
229
- specific_concerns: Optional[str]
244
+ specific_concerns: Optional[str],
230
245
  ) -> str:
231
246
  """Review security aspects."""
232
247
  return """šŸ” SECURITY REVIEW:
@@ -240,41 +255,43 @@ Security Checklist:
240
255
  šŸ” Sensitive data is not logged
241
256
 
242
257
  āš ļø If in doubt, err on the side of caution!"""
243
-
258
+
244
259
  def _review_completeness(
245
260
  self,
246
261
  work_description: str,
247
262
  code_snippets: Optional[List[str]],
248
263
  file_paths: Optional[List[str]],
249
- specific_concerns: Optional[str]
264
+ specific_concerns: Optional[str],
250
265
  ) -> str:
251
266
  """Review completeness aspects."""
252
267
  tasks_mentioned = work_description.lower()
253
-
268
+
254
269
  response = "šŸ” COMPLETENESS REVIEW:\n\n"
255
-
270
+
256
271
  if "fix" in tasks_mentioned and "test" not in tasks_mentioned:
257
- response += "āŒ No mention of tests - have you verified the fix with tests?\n"
258
-
272
+ response += (
273
+ "āŒ No mention of tests - have you verified the fix with tests?\n"
274
+ )
275
+
259
276
  if "import" in tasks_mentioned:
260
277
  response += "āœ“ Import fixes mentioned\n"
261
278
  response += "ā“ Have you checked for other files with similar issues?\n"
262
279
  response += "ā“ Are all undefined symbols now resolved?\n"
263
-
280
+
264
281
  if file_paths:
265
282
  response += f"\nāœ“ Modified {len(file_paths)} files\n"
266
283
  response += "ā“ Are there any related files that also need updates?\n"
267
-
284
+
268
285
  response += "\nšŸ’” Completeness means not just fixing the immediate issue, but considering the broader impact."
269
-
286
+
270
287
  return response
271
-
288
+
272
289
  def _review_best_practices(
273
290
  self,
274
291
  work_description: str,
275
292
  code_snippets: Optional[List[str]],
276
293
  file_paths: Optional[List[str]],
277
- specific_concerns: Optional[str]
294
+ specific_concerns: Optional[str],
278
295
  ) -> str:
279
296
  """Review best practices."""
280
297
  return """šŸ” BEST PRACTICES REVIEW:
@@ -293,21 +310,21 @@ General Best Practices:
293
310
  āœ“ Changes are minimal and focused
294
311
 
295
312
  šŸ’” Good code is code that others (including future you) can understand and modify."""
296
-
313
+
297
314
  def _review_general(
298
315
  self,
299
316
  work_description: str,
300
317
  code_snippets: Optional[List[str]],
301
318
  file_paths: Optional[List[str]],
302
- specific_concerns: Optional[str]
319
+ specific_concerns: Optional[str],
303
320
  ) -> str:
304
321
  """General review covering multiple aspects."""
305
322
  response = "šŸ” GENERAL CRITICAL REVIEW:\n\n"
306
323
  response += f"Work Description: {work_description}\n\n"
307
-
324
+
308
325
  # Quick assessment
309
326
  response += "Quick Assessment:\n"
310
-
327
+
311
328
  if "fix" in work_description.lower():
312
329
  response += "- Type: Bug fix / Error resolution\n"
313
330
  response += "- Critical: Ensure the fix is complete and tested\n"
@@ -317,60 +334,56 @@ General Best Practices:
317
334
  elif "refactor" in work_description.lower():
318
335
  response += "- Type: Code refactoring\n"
319
336
  response += "- Critical: Ensure behavior is preserved\n"
320
-
337
+
321
338
  if file_paths:
322
339
  response += f"- Scope: {len(file_paths)} files affected\n"
323
340
  if len(file_paths) > 10:
324
341
  response += "- āš ļø Large scope - consider breaking down\n"
325
-
342
+
326
343
  response += "\nCritical Questions:\n"
327
344
  response += "1. Is this the minimal change needed?\n"
328
345
  response += "2. Have you considered all edge cases?\n"
329
346
  response += "3. Will this work in production?\n"
330
347
  response += "4. Is there a simpler solution?\n"
331
-
348
+
332
349
  if specific_concerns:
333
350
  response += f"\nYour Concern: {specific_concerns}\n"
334
351
  response += "→ Valid concern. Double-check this area carefully.\n"
335
-
352
+
336
353
  response += "\nšŸŽÆ Bottom Line: Good work needs critical thinking. Question everything, verify everything."
337
-
354
+
338
355
  return response
339
356
 
340
357
 
341
358
  class CriticProtocol:
342
359
  """Protocol for critic interactions."""
343
-
360
+
344
361
  def __init__(self):
345
362
  self.auto_critic = AutoCritic()
346
363
  self.review_count = 0
347
364
  self.max_reviews = 2 # Allow up to 2 reviews per task
348
-
365
+
349
366
  def request_review(
350
367
  self,
351
368
  review_type: str,
352
369
  work_description: str,
353
370
  code_snippets: Optional[List[str]] = None,
354
371
  file_paths: Optional[List[str]] = None,
355
- specific_concerns: Optional[str] = None
372
+ specific_concerns: Optional[str] = None,
356
373
  ) -> str:
357
374
  """Request a critical review."""
358
375
  if self.review_count >= self.max_reviews:
359
376
  return "āŒ Review limit exceeded. Time to move forward with what you have."
360
-
377
+
361
378
  self.review_count += 1
362
-
379
+
363
380
  try:
364
381
  review_enum = ReviewType[review_type.upper()]
365
382
  except KeyError:
366
383
  review_enum = ReviewType.GENERAL
367
-
384
+
368
385
  review = self.auto_critic.review(
369
- review_enum,
370
- work_description,
371
- code_snippets,
372
- file_paths,
373
- specific_concerns
386
+ review_enum, work_description, code_snippets, file_paths, specific_concerns
374
387
  )
375
-
376
- return f"Review {self.review_count}/{self.max_reviews}:\n\n{review}"
388
+
389
+ return f"Review {self.review_count}/{self.max_reviews}:\n\n{review}"