mirror of
https://github.com/hanzoai/python-sdk.git
synced 2026-10-27 04:20:09 +00:00
- Fix import sorting (I001) in all packages - Fix bare except (E722) in lsp_tool.py - Fix duplicate set item (B033) in refactor_tool.py
427 lines
16 KiB
Python
427 lines
16 KiB
Python
"""Review tool for agents to request balanced code review from main loop."""
|
|
|
|
from enum import Enum
|
|
from typing import List, Optional, override
|
|
|
|
from mcp.server import FastMCP
|
|
from mcp.server.fastmcp import Context as MCPContext
|
|
|
|
from hanzo_tools.core import BaseTool, auto_timeout
|
|
|
|
|
|
class ReviewFocus(Enum):
|
|
"""Types of review focus areas."""
|
|
|
|
GENERAL = "general"
|
|
FUNCTIONALITY = "functionality"
|
|
READABILITY = "readability"
|
|
MAINTAINABILITY = "maintainability"
|
|
TESTING = "testing"
|
|
DOCUMENTATION = "documentation"
|
|
ARCHITECTURE = "architecture"
|
|
|
|
|
|
class ReviewTool(BaseTool):
|
|
"""Tool for agents to request balanced code review from the main loop."""
|
|
|
|
name = "review"
|
|
|
|
@property
|
|
@override
|
|
def description(self) -> str:
|
|
"""Get the tool description."""
|
|
return """Request a balanced, constructive code review from the main loop.
|
|
|
|
Unlike the critic tool (which plays devil's advocate), this provides:
|
|
- Objective assessment of code quality
|
|
- Recognition of what's done well
|
|
- Constructive suggestions for improvement
|
|
- Focus on practical concerns
|
|
- No predetermined bias or harsh judgment
|
|
|
|
Parameters:
|
|
- focus: Review focus area (GENERAL, FUNCTIONALITY, READABILITY, MAINTAINABILITY, TESTING, DOCUMENTATION, ARCHITECTURE)
|
|
- work_description: Clear description of what you've implemented
|
|
- code_snippets: Optional code snippets to review (as a list of strings)
|
|
- file_paths: Optional list of file paths you've modified
|
|
- context: Optional additional context about the implementation
|
|
|
|
The review will be balanced, highlighting both strengths and areas for improvement.
|
|
|
|
Example:
|
|
review(
|
|
focus="FUNCTIONALITY",
|
|
work_description="Implemented auto-import feature for Go files",
|
|
code_snippets=["func AddImport(file string, importPath string) error { ... }"],
|
|
file_paths=["/path/to/import_handler.go"],
|
|
context="This will be used to automatically fix missing imports in Go files"
|
|
)"""
|
|
|
|
@auto_timeout("review")
|
|
async def call(
|
|
self,
|
|
ctx: MCPContext,
|
|
focus: str,
|
|
work_description: str,
|
|
code_snippets: Optional[List[str]] = None,
|
|
file_paths: Optional[List[str]] = None,
|
|
context: Optional[str] = None,
|
|
) -> str:
|
|
"""Delegate to AgentTool for actual implementation.
|
|
|
|
This method provides the interface, but the actual review logic
|
|
is handled by the AgentTool's execution framework.
|
|
"""
|
|
# This tool is handled specially in the agent execution
|
|
return f"Review requested for: {work_description}"
|
|
|
|
def register(self, server: FastMCP) -> None:
|
|
"""Register the tool with the MCP server."""
|
|
tool_self = self
|
|
|
|
@server.tool(name=self.name, description=self.description)
|
|
async def review(
|
|
ctx: MCPContext,
|
|
focus: str,
|
|
work_description: str,
|
|
code_snippets: Optional[List[str]] = None,
|
|
file_paths: Optional[List[str]] = None,
|
|
context: Optional[str] = None,
|
|
) -> str:
|
|
return await tool_self.call(ctx, focus, work_description, code_snippets, file_paths, context)
|
|
|
|
|
|
class BalancedReviewer:
|
|
"""Provides balanced, constructive code reviews."""
|
|
|
|
def __init__(self):
|
|
self.review_handlers = {
|
|
ReviewFocus.GENERAL: self._review_general,
|
|
ReviewFocus.FUNCTIONALITY: self._review_functionality,
|
|
ReviewFocus.READABILITY: self._review_readability,
|
|
ReviewFocus.MAINTAINABILITY: self._review_maintainability,
|
|
ReviewFocus.TESTING: self._review_testing,
|
|
ReviewFocus.DOCUMENTATION: self._review_documentation,
|
|
ReviewFocus.ARCHITECTURE: self._review_architecture,
|
|
}
|
|
|
|
def review(
|
|
self,
|
|
focus: ReviewFocus,
|
|
work_description: str,
|
|
code_snippets: Optional[List[str]] = None,
|
|
file_paths: Optional[List[str]] = None,
|
|
context: Optional[str] = None,
|
|
) -> str:
|
|
"""Perform a balanced code review."""
|
|
review_func = self.review_handlers.get(focus, self._review_general)
|
|
return review_func(work_description, code_snippets, file_paths, context)
|
|
|
|
def _review_general(
|
|
self,
|
|
work_description: str,
|
|
code_snippets: Optional[List[str]],
|
|
file_paths: Optional[List[str]],
|
|
context: Optional[str],
|
|
) -> str:
|
|
"""Provide a general balanced review."""
|
|
response = "📋 GENERAL CODE REVIEW:\n\n"
|
|
response += f"**Work Reviewed:** {work_description}\n\n"
|
|
|
|
# Positive observations
|
|
response += "**Positive Aspects:**\n"
|
|
if "fix" in work_description.lower():
|
|
response += "✓ Addressing identified issues proactively\n"
|
|
if "implement" in work_description.lower():
|
|
response += "✓ Adding new functionality to enhance the system\n"
|
|
if code_snippets:
|
|
response += "✓ Code structure appears organized\n"
|
|
if file_paths and len(file_paths) == 1:
|
|
response += "✓ Focused changes in a single file (good for reviewability)\n"
|
|
elif file_paths and len(file_paths) > 1:
|
|
response += "✓ Comprehensive approach across multiple files\n"
|
|
|
|
# Constructive suggestions
|
|
response += "\n**Suggestions for Consideration:**\n"
|
|
response += "• Ensure all edge cases are handled appropriately\n"
|
|
response += "• Consider adding unit tests if not already present\n"
|
|
response += "• Verify the changes integrate well with existing code\n"
|
|
|
|
if context:
|
|
response += f"\n**Context Consideration:**\n{context}\n"
|
|
response += "→ This context helps understand the implementation choices.\n"
|
|
|
|
# Summary
|
|
response += "\n**Summary:**\n"
|
|
response += "The implementation appears sound. Consider the suggestions above to further strengthen the code."
|
|
|
|
return response
|
|
|
|
def _review_functionality(
|
|
self,
|
|
work_description: str,
|
|
code_snippets: Optional[List[str]],
|
|
file_paths: Optional[List[str]],
|
|
context: Optional[str],
|
|
) -> str:
|
|
"""Review functionality aspects."""
|
|
response = "📋 FUNCTIONALITY REVIEW:\n\n"
|
|
response += f"**Implementation:** {work_description}\n\n"
|
|
|
|
response += "**Functional Assessment:**\n"
|
|
|
|
# Analyze code snippets if provided
|
|
if code_snippets:
|
|
for i, snippet in enumerate(code_snippets, 1):
|
|
response += f"\nCode Snippet {i}:\n"
|
|
|
|
# Check for function definitions
|
|
if "func " in snippet or "def " in snippet or "function " in snippet:
|
|
response += "✓ Function definition looks properly structured\n"
|
|
|
|
# Check for error handling
|
|
if "error" in snippet or "err" in snippet or "try" in snippet:
|
|
response += "✓ Error handling is present\n"
|
|
elif "return" in snippet:
|
|
response += "• Consider adding error handling if applicable\n"
|
|
|
|
# Check for input validation
|
|
if "if " in snippet or "check" in snippet.lower():
|
|
response += "✓ Input validation appears to be present\n"
|
|
|
|
response += "\n**Functional Considerations:**\n"
|
|
response += "• Does the implementation handle all expected inputs?\n"
|
|
response += "• Are return values meaningful and consistent?\n"
|
|
response += "• Is the functionality easily testable?\n"
|
|
response += "• Does it integrate well with existing features?\n"
|
|
|
|
response += "\n**Overall:** The functionality appears to meet the described requirements."
|
|
|
|
return response
|
|
|
|
def _review_readability(
|
|
self,
|
|
work_description: str,
|
|
code_snippets: Optional[List[str]],
|
|
file_paths: Optional[List[str]],
|
|
context: Optional[str],
|
|
) -> str:
|
|
"""Review code readability."""
|
|
response = "📋 READABILITY REVIEW:\n\n"
|
|
|
|
response += "**Readability Factors:**\n"
|
|
|
|
if code_snippets:
|
|
total_lines = sum(snippet.count("\n") + 1 for snippet in code_snippets)
|
|
avg_line_length = sum(len(line) for snippet in code_snippets for line in snippet.split("\n")) / max(
|
|
total_lines, 1
|
|
)
|
|
|
|
if avg_line_length < 80:
|
|
response += "✓ Line lengths are reasonable\n"
|
|
else:
|
|
response += "• Some lines might be too long, consider breaking them up\n"
|
|
|
|
# Check naming
|
|
has_good_names = any(
|
|
any(word in snippet for word in ["Add", "Get", "Set", "Create", "Update", "Delete"])
|
|
for snippet in code_snippets
|
|
)
|
|
if has_good_names:
|
|
response += "✓ Function/method names appear descriptive\n"
|
|
|
|
response += "\n**Readability Suggestions:**\n"
|
|
response += "• Use meaningful variable and function names\n"
|
|
response += "• Keep functions focused on a single responsibility\n"
|
|
response += "• Add comments for complex logic sections\n"
|
|
response += "• Maintain consistent indentation and formatting\n"
|
|
|
|
response += "\n**Overall:** Code readability appears acceptable with room for minor improvements."
|
|
|
|
return response
|
|
|
|
def _review_maintainability(
|
|
self,
|
|
work_description: str,
|
|
code_snippets: Optional[List[str]],
|
|
file_paths: Optional[List[str]],
|
|
context: Optional[str],
|
|
) -> str:
|
|
"""Review maintainability aspects."""
|
|
response = "📋 MAINTAINABILITY REVIEW:\n\n"
|
|
|
|
response += "**Maintainability Factors:**\n"
|
|
|
|
# Check file organization
|
|
if file_paths:
|
|
if len(file_paths) == 1:
|
|
response += "✓ Changes are localized to a single file\n"
|
|
else:
|
|
response += "✓ Changes are logically distributed across files\n"
|
|
|
|
# Check for modularity in code
|
|
if code_snippets:
|
|
function_count = sum(
|
|
snippet.count("func ") + snippet.count("def ") + snippet.count("function ") for snippet in code_snippets
|
|
)
|
|
if function_count > 0:
|
|
response += "✓ Code is broken into functions/methods\n"
|
|
|
|
response += "\n**Maintainability Considerations:**\n"
|
|
response += "• Is the code modular and reusable?\n"
|
|
response += "• Are dependencies clearly defined?\n"
|
|
response += "• Will future developers understand the intent?\n"
|
|
response += "• Is the code structured to allow easy updates?\n"
|
|
|
|
response += "\n**Recommendations:**\n"
|
|
response += "• Consider extracting common patterns into utilities\n"
|
|
response += "• Ensure consistent patterns across the codebase\n"
|
|
response += "• Document any non-obvious design decisions\n"
|
|
|
|
return response
|
|
|
|
def _review_testing(
|
|
self,
|
|
work_description: str,
|
|
code_snippets: Optional[List[str]],
|
|
file_paths: Optional[List[str]],
|
|
context: Optional[str],
|
|
) -> str:
|
|
"""Review testing aspects."""
|
|
response = "📋 TESTING REVIEW:\n\n"
|
|
|
|
has_test_files = any("test" in str(path).lower() for path in (file_paths or []))
|
|
|
|
if has_test_files:
|
|
response += "✓ Test files are included with the changes\n\n"
|
|
else:
|
|
response += "⚠️ No test files detected in the changes\n\n"
|
|
|
|
response += "**Testing Checklist:**\n"
|
|
response += "□ Unit tests for new functions\n"
|
|
response += "□ Integration tests for feature interactions\n"
|
|
response += "□ Edge case coverage\n"
|
|
response += "□ Error condition testing\n"
|
|
response += "□ Performance tests (if applicable)\n"
|
|
|
|
response += "\n**Testing Recommendations:**\n"
|
|
response += "• Write tests that document expected behavior\n"
|
|
response += "• Include both positive and negative test cases\n"
|
|
response += "• Ensure tests are maintainable and clear\n"
|
|
response += "• Aim for good coverage of critical paths\n"
|
|
|
|
if not has_test_files:
|
|
response += "\n💡 Consider adding tests to ensure reliability and prevent regressions."
|
|
|
|
return response
|
|
|
|
def _review_documentation(
|
|
self,
|
|
work_description: str,
|
|
code_snippets: Optional[List[str]],
|
|
file_paths: Optional[List[str]],
|
|
context: Optional[str],
|
|
) -> str:
|
|
"""Review documentation aspects."""
|
|
response = "📋 DOCUMENTATION REVIEW:\n\n"
|
|
|
|
# Check for documentation in code
|
|
has_comments = False
|
|
if code_snippets:
|
|
has_comments = any(
|
|
"//" in snippet or "/*" in snippet or "#" in snippet or '"""' in snippet for snippet in code_snippets
|
|
)
|
|
|
|
if has_comments:
|
|
response += "✓ Code includes some documentation\n"
|
|
else:
|
|
response += "• Consider adding documentation comments\n"
|
|
|
|
response += "\n**Documentation Guidelines:**\n"
|
|
response += "• Document the 'why' not just the 'what'\n"
|
|
response += "• Include examples for complex functions\n"
|
|
response += "• Document any assumptions or limitations\n"
|
|
response += "• Keep documentation up-to-date with code changes\n"
|
|
|
|
response += "\n**Recommended Documentation:**\n"
|
|
response += "• Function/method purpose and parameters\n"
|
|
response += "• Complex algorithm explanations\n"
|
|
response += "• API usage examples\n"
|
|
response += "• Configuration requirements\n"
|
|
|
|
return response
|
|
|
|
def _review_architecture(
|
|
self,
|
|
work_description: str,
|
|
code_snippets: Optional[List[str]],
|
|
file_paths: Optional[List[str]],
|
|
context: Optional[str],
|
|
) -> str:
|
|
"""Review architectural aspects."""
|
|
response = "📋 ARCHITECTURE REVIEW:\n\n"
|
|
|
|
response += "**Architectural Considerations:**\n"
|
|
|
|
# Analyze file structure
|
|
if file_paths:
|
|
# Check for separation of concerns
|
|
has_separation = len(set(str(p).split("/")[-2] for p in file_paths if "/" in str(p))) > 1
|
|
if has_separation:
|
|
response += "✓ Changes span multiple modules (good separation)\n"
|
|
else:
|
|
response += "✓ Changes are cohesive within a module\n"
|
|
|
|
response += "\n**Architectural Principles:**\n"
|
|
response += "• Single Responsibility - Each component has one clear purpose\n"
|
|
response += "• Open/Closed - Open for extension, closed for modification\n"
|
|
response += "• Dependency Inversion - Depend on abstractions, not concretions\n"
|
|
response += "• Interface Segregation - Keep interfaces focused and minimal\n"
|
|
|
|
response += "\n**Questions to Consider:**\n"
|
|
response += "• Does this fit well with the existing architecture?\n"
|
|
response += "• Are the right abstractions in place?\n"
|
|
response += "• Is the coupling between components appropriate?\n"
|
|
response += "• Will this scale as requirements grow?\n"
|
|
|
|
if context:
|
|
response += f"\n**Context Impact:**\n{context}\n"
|
|
response += "→ Ensure the architectural choices align with this context.\n"
|
|
|
|
return response
|
|
|
|
|
|
class ReviewProtocol:
|
|
"""Protocol for review interactions."""
|
|
|
|
def __init__(self):
|
|
self.reviewer = BalancedReviewer()
|
|
self.review_count = 0
|
|
self.max_reviews = 3 # Allow up to 3 reviews per task
|
|
|
|
def request_review(
|
|
self,
|
|
focus: str,
|
|
work_description: str,
|
|
code_snippets: Optional[List[str]] = None,
|
|
file_paths: Optional[List[str]] = None,
|
|
context: Optional[str] = None,
|
|
) -> str:
|
|
"""Request a balanced review."""
|
|
if self.review_count >= self.max_reviews:
|
|
return "📋 Review limit reached. You've received comprehensive feedback - time to finalize your implementation."
|
|
|
|
self.review_count += 1
|
|
|
|
try:
|
|
focus_enum = ReviewFocus[focus.upper()]
|
|
except KeyError:
|
|
focus_enum = ReviewFocus.GENERAL
|
|
|
|
review = self.reviewer.review(focus_enum, work_description, code_snippets, file_paths, context)
|
|
|
|
header = f"Review {self.review_count}/{self.max_reviews} (Focus: {focus_enum.value}):\n\n"
|
|
footer = "\n\n💡 This is a balanced review - consider both strengths and suggestions."
|
|
|
|
return header + review + footer
|