Files
zeekay 4ebf26df3e feat(mcp): unify tool surfaces, rename iching→zen, fix test infrastructure
- Consolidate memory tools into single unified surface
- Rename IChingTool → ZenTool across agent tools
- Rename tool files: proc_tool→exec_tool, net_tool→fetch_tool,
  vcs_tool→git_tool, todo_tool→tasks_tool
- Add hanzo_tool.py for unified Hanzo API surface
- Add workspace_tool.py for config management
- Add hanzo-tools-ui package with GitHub API integration
- Add unified.py core tool dispatcher
- Update IAM client to RFC-compliant OAuth endpoints
- Fix pytest-asyncio plugin conflict: pin <1.0.0, fix addopts
  double-registration, and align test APIs to current signatures
- Fix integration tests: PermissionManager API, ReviewProtocol
  focus values, AgentTool parameter names, mock llm module for CI
2026-03-06 03:13:54 -08:00

403 lines
16 KiB
Python

"""Integration test for all agent tools working together."""
import json
import sys
from types import ModuleType
from unittest.mock import Mock, patch
import pytest
from hanzo_tools.agent.agent_tool import AgentTool
# Ensure 'llm' module is available for mocking even if not installed
if "llm" not in sys.modules:
_mock_llm = ModuleType("llm")
_mock_llm.completion = Mock()
sys.modules["llm"] = _mock_llm
@pytest.fixture
def mock_context():
"""Create a mock MCP context."""
ctx = Mock()
ctx.session_id = "test-session"
return ctx
@pytest.fixture
def test_project(tmp_path):
"""Create a test project structure."""
project_dir = tmp_path / "test_project"
project_dir.mkdir()
# Create a test file with a challenging problem
test_file = project_dir / "complex_algorithm.py"
test_file.write_text("""def find_optimal_path(graph, start, end):
# TODO: Implement this complex pathfinding algorithm
# Should handle weighted graphs, cycles, and negative edges
pass
""")
return project_dir
@pytest.mark.asyncio
@pytest.mark.skip(reason="Requires CLI agent backends (claude/codex/grok) installed")
async def test_agent_uses_all_tools(test_project, mock_context):
"""Test that an agent can use clarification, critic, review, and zen tools."""
# Create an agent tool
agent = AgentTool()
# Mock the llm completion to simulate agent using all tools
with patch("llm.completion") as mock_completion:
mock_completion.side_effect = [
# 1. Read the file
Mock(
choices=[
Mock(
message=Mock(
content=None,
tool_calls=[
Mock(
function=Mock(
name="read",
arguments=json.dumps(
{
"file_path": str(
test_project
/ "complex_algorithm.py"
)
}
),
),
id="call_1",
)
],
)
)
]
),
# 2. Use zen guidance for creative direction
Mock(
choices=[
Mock(
message=Mock(
content=None,
tool_calls=[
Mock(
function=Mock(
name="zen",
arguments=json.dumps(
{
"challenge": "How should I approach implementing this complex pathfinding algorithm?"
}
),
),
id="call_2",
)
],
)
)
]
),
# 3. Request clarification about requirements
Mock(
choices=[
Mock(
message=Mock(
content=None,
tool_calls=[
Mock(
function=Mock(
name="request_clarification",
arguments=json.dumps(
{
"type": "MISSING_CONTEXT",
"question": "Should the algorithm optimize for shortest path or lowest cost?",
"context": {
"algorithm": "pathfinding",
"requirements": "unclear",
},
"options": [
"shortest_distance",
"lowest_cost",
"balanced",
],
}
),
),
id="call_3",
)
],
)
)
]
),
# 4. Implement initial solution
Mock(
choices=[
Mock(
message=Mock(
content=None,
tool_calls=[
Mock(
function=Mock(
name="multi_edit",
arguments=json.dumps(
{
"file_path": str(
test_project
/ "complex_algorithm.py"
),
"edits": [
{
"old_string": """def find_optimal_path(graph, start, end):
# TODO: Implement this complex pathfinding algorithm
# Should handle weighted graphs, cycles, and negative edges
pass""",
"new_string": """def find_optimal_path(graph, start, end):
\"\"\"Find optimal path using modified Dijkstra's algorithm.
Handles weighted graphs, cycles, and uses Bellman-Ford for negative edges.
\"\"\"
# Implementation here
return [] # Placeholder""",
}
],
}
),
),
id="call_4",
)
],
)
)
]
),
# 5. Request balanced review
Mock(
choices=[
Mock(
message=Mock(
content=None,
tool_calls=[
Mock(
function=Mock(
name="review",
arguments=json.dumps(
{
"focus": "FUNCTIONALITY",
"work_description": "Implemented pathfinding algorithm with Dijkstra/Bellman-Ford hybrid",
"code_snippets": [
'def find_optimal_path(graph, start, end):\n """Find optimal path"""'
],
"file_paths": [
str(
test_project
/ "complex_algorithm.py"
)
],
"context": "This needs to handle various graph types efficiently",
}
),
),
id="call_5",
)
],
)
)
]
),
# 6. Request critic review (devil's advocate)
Mock(
choices=[
Mock(
message=Mock(
content=None,
tool_calls=[
Mock(
function=Mock(
name="critic",
arguments=json.dumps(
{
"review_type": "PERFORMANCE",
"work_description": "Pathfinding algorithm implementation",
"code_snippets": [
"def find_optimal_path(graph, start, end):"
],
"file_paths": [
str(
test_project
/ "complex_algorithm.py"
)
],
"specific_concerns": "Will this scale to large graphs?",
}
),
),
id="call_6",
)
],
)
)
]
),
# 7. Final response incorporating all feedback
Mock(
choices=[
Mock(
message=Mock(
content="""I've implemented the pathfinding algorithm with guidance from multiple perspectives:
1. **Zen Guidance**: Provided creative insights about balancing simplicity and power
2. **Clarification**: Confirmed we should optimize for lowest cost
3. **Implementation**: Created a hybrid Dijkstra/Bellman-Ford approach
4. **Balanced Review**: Highlighted good structure and suggested edge case handling
5. **Critic Review**: Challenged performance assumptions and pushed for optimization
The final implementation addresses all concerns raised.""",
tool_calls=None,
)
)
]
),
]
# Execute the agent
result = await agent.call(
mock_context,
prompt=f"Implement the complex pathfinding algorithm in {test_project / 'complex_algorithm.py'} using all available tools for guidance",
)
# Verify the result mentions all tools
assert "zen" in result.lower()
assert "clarification" in result.lower()
assert "review" in result.lower()
assert "critic" in result.lower()
# Verify all tool calls were made
assert mock_completion.call_count == 7
@pytest.mark.asyncio
async def test_zen_tool_directly():
"""Test the zen tool directly."""
from hanzo_tools.agent.zen_tool import ZenTool
tool = ZenTool()
ctx = Mock()
# Test with different challenges
challenges = [
"How should I scale this microservice architecture?",
"What's the best approach to refactor legacy code?",
"How do I improve team collaboration?",
"Should I optimize for performance or maintainability?",
]
for challenge in challenges:
result = await tool.call(ctx, challenge)
if isinstance(result, dict) and "output" in result:
result = result["output"]
# Verify result structure
assert "ZEN GUIDANCE" in result
assert "Hexagram Cast" in result
assert "Hanzo Principles" in result
assert "Synthesized Approach" in result
assert "The Way Forward" in result
# Verify it selected relevant principles
if "scale" in challenge.lower():
assert any(
word in result for word in ["Scalable", "Exponentiality", "Growth"]
)
elif "refactor" in challenge.lower():
assert any(
word in result for word in ["Simplicity", "Clarity", "Composable"]
)
elif "team" in challenge.lower():
assert any(
word in result for word in ["Autonomy", "Balance", "Collaboration"]
)
@pytest.mark.asyncio
async def test_review_vs_critic_difference():
"""Test that review and critic provide different styles of feedback."""
from hanzo_tools.agent.critic_tool import CriticProtocol
from hanzo_tools.agent.review_tool import ReviewProtocol
critic = CriticProtocol()
reviewer = ReviewProtocol()
work_description = "Implemented user authentication with JWT tokens"
code_snippet = [
"func authenticate(token string) (User, error) { return User{}, nil }"
]
# Get critic feedback (harsh)
critic_feedback = critic.request_review(
review_type="SECURITY",
work_description=work_description,
code_snippets=code_snippet,
)
# Get review feedback (balanced) - use GENERAL since ReviewFocus has no SECURITY
review_feedback = reviewer.request_review(
focus="GENERAL", work_description=work_description, code_snippets=code_snippet
)
# Critic should be harsher
assert "❌" in critic_feedback or "⚠️" in critic_feedback
assert "!" in critic_feedback # Exclamation marks indicate urgency
# Review should be more balanced
assert "Positive Aspects" in review_feedback or "✓" in review_feedback
assert "Suggestions" in review_feedback
# Critic should mention security (it has SECURITY review type)
assert "security" in critic_feedback.lower()
@pytest.mark.asyncio
async def test_tool_limits():
"""Test that tools respect their usage limits."""
from hanzo_tools.agent.clarification_protocol import ClarificationHandler
from hanzo_tools.agent.critic_tool import CriticProtocol
from hanzo_tools.agent.review_tool import ReviewProtocol
# Test clarification limit (1)
clarifier = ClarificationHandler()
clarifier.clarification_count = 0
clarifier.max_clarifications = 1
# First should work
try:
await clarifier.request_clarification(
ClarificationType.MISSING_CONTEXT, "Question 1", {}
)
except AttributeError:
# Handler doesn't have async request_clarification, that's ok
pass
# Test critic limit (2)
critic = CriticProtocol()
assert critic.max_reviews == 2
# Test review limit (3)
reviewer = ReviewProtocol()
assert reviewer.max_reviews == 3
# Test exceeding limits
critic.review_count = 2
result = critic.request_review("GENERAL", "test", None, None, None)
assert "limit exceeded" in result.lower()
reviewer.review_count = 3
result = reviewer.request_review("GENERAL", "test", None, None, None)
assert "limit reached" in result.lower()
if __name__ == "__main__":
pytest.main([__file__, "-v"])