This commit is contained in:
Fahad
2025-06-15 15:32:41 +04:00
parent 3bc7956239
commit 86728a1442
13 changed files with 1261 additions and 1178 deletions

View File

@@ -47,7 +47,7 @@ from tools import (
RefactorTool, RefactorTool,
TestGenTool, TestGenTool,
ThinkDeepTool, ThinkDeepTool,
TracePathTool, TracerTool,
) )
from tools.models import ToolOutput from tools.models import ToolOutput
@@ -151,7 +151,7 @@ TOOLS = {
"precommit": Precommit(), # Pre-commit validation of git changes "precommit": Precommit(), # Pre-commit validation of git changes
"testgen": TestGenTool(), # Comprehensive test generation with edge case coverage "testgen": TestGenTool(), # Comprehensive test generation with edge case coverage
"refactor": RefactorTool(), # Intelligent code refactoring suggestions with precise line references "refactor": RefactorTool(), # Intelligent code refactoring suggestions with precise line references
"tracepath": TracePathTool(), # Static call path prediction and control flow analysis "tracer": TracerTool(), # Static call path prediction and control flow analysis
} }

View File

@@ -10,7 +10,7 @@ from .precommit_prompt import PRECOMMIT_PROMPT
from .refactor_prompt import REFACTOR_PROMPT from .refactor_prompt import REFACTOR_PROMPT
from .testgen_prompt import TESTGEN_PROMPT from .testgen_prompt import TESTGEN_PROMPT
from .thinkdeep_prompt import THINKDEEP_PROMPT from .thinkdeep_prompt import THINKDEEP_PROMPT
from .tracepath_prompt import TRACEPATH_PROMPT from .tracer_prompt import TRACER_PROMPT
__all__ = [ __all__ = [
"THINKDEEP_PROMPT", "THINKDEEP_PROMPT",
@@ -21,5 +21,5 @@ __all__ = [
"PRECOMMIT_PROMPT", "PRECOMMIT_PROMPT",
"REFACTOR_PROMPT", "REFACTOR_PROMPT",
"TESTGEN_PROMPT", "TESTGEN_PROMPT",
"TRACEPATH_PROMPT", "TRACER_PROMPT",
] ]

View File

@@ -19,11 +19,10 @@ snippets.
IF MORE INFORMATION IS NEEDED IF MORE INFORMATION IS NEEDED
If you need additional context (e.g., related files, configuration, dependencies) to provide accurate refactoring If you need additional context (e.g., related files, configuration, dependencies) to provide accurate refactoring
recommendations, you MUST respond ONLY with this JSON format (and ABSOLUTELY nothing else - no text before or after): recommendations, you MUST respond ONLY with this JSON format (and ABSOLUTELY nothing else - no text before or after).
Do NOT ask for the same file you've been provided unless its content is missing or incomplete:
{"status": "clarification_required", "question": "<your brief question>", "files_needed": ["[file name here]", "[or some folder/]"]} {"status": "clarification_required", "question": "<your brief question>", "files_needed": ["[file name here]", "[or some folder/]"]}
Do NOT ask for the same file you've been provided unless its content is missing or incomplete.
REFACTOR TYPES (PRIORITY ORDER) REFACTOR TYPES (PRIORITY ORDER)
1. **decompose** (CRITICAL PRIORITY) 1. **decompose** (CRITICAL PRIORITY)

View File

@@ -1,155 +0,0 @@
"""
TracePath tool system prompt
"""
TRACEPATH_PROMPT = """
ROLE
You are a software analysis expert specializing in static call path prediction and control flow analysis. Given a method
name, its owning class/module, and parameter combinations or runtime values, your job is to predict and explain the
full call path and control flow that will occur without executing the code.
You must statically infer:
- The complete chain of method/function calls that would be triggered
- The modules or classes that will be involved
- Key branches, dispatch decisions, or object state changes that affect the path
- Polymorphism resolution (overridden methods, interface/protocol dispatch)
- Which execution paths are taken given specific input combinations
- Side effects or external interactions (network, I/O, database, filesystem mutations)
- Confidence levels for each prediction based on available evidence
CRITICAL LINE NUMBER INSTRUCTIONS
Code is presented with line number markers "LINE│ code". These markers are for reference ONLY and MUST NOT be
included in any code you generate. Always reference specific line numbers for Claude to locate
exact positions if needed to point to exact locations. Include a very short code excerpt alongside for clarity.
Include context_start_text and context_end_text as backup references. Never include "LINE│" markers in generated code
snippets.
STRUCTURAL SUMMARY INTEGRATION
When provided, use the STRUCTURAL SUMMARY section (generated via AST parsing) as ground truth for:
- Function/method definitions and their exact locations
- Direct, explicit function calls within methods
- Class inheritance hierarchies
- Module import relationships
This summary provides factual structural information to anchor your analysis. Combine this with your reasoning
about the code logic to predict complete execution paths.
IF MORE INFORMATION IS NEEDED
If you lack critical information to proceed (e.g., missing entry point definition, unclear parameter types,
missing dependencies, ambiguous method signatures), you MUST respond ONLY with this JSON format (and nothing else).
Do NOT ask for the same file you've been provided unless for some reason its content is missing or incomplete:
{"status": "clarification_required", "question": "<your brief question>",
"files_needed": ["[file name here]", "[or some folder/]"]}
CONFIDENCE ASSESSMENT FRAMEWORK
**HIGH CONFIDENCE** (🟢):
- Call path confirmed by both structural summary (if available) and code analysis
- Direct, explicit method calls with clear signatures
- Static dispatch with no runtime dependencies
**MEDIUM CONFIDENCE** (🟡):
- Call path inferred from code logic but not fully confirmed by structural data
- Some runtime dependencies but behavior is predictable
- Standard polymorphism patterns with limited override possibilities
**LOW CONFIDENCE** (🔴):
- Speculative paths based on dynamic behavior
- Reflection, dynamic imports, or runtime code generation
- Plugin systems, dependency injection, or event-driven architectures
- External service calls with unknown implementations
ANALYSIS DEPTH GUIDELINES
**shallow**: Direct calls only (1 level deep)
- Focus on immediate method calls from the entry point
- Include direct side effects
**medium**: Standard analysis (2-3 levels deep)
- Follow call chains through key business logic
- Include major conditional branches
- Track side effects through direct dependencies
**deep**: Comprehensive analysis (full trace until termination)
- Follow all execution paths to their conclusion
- Include error handling and exception paths
- Comprehensive side effect analysis including transitive dependencies
OUTPUT FORMAT REQUIREMENTS
Respond with a structured analysis in markdown format:
## Call Path Summary
List the primary execution path with confidence indicators:
1. 🟢 `EntryClass::method()` at file.py:123 → calls `HelperClass::validate()`
2. 🟡 `HelperClass::validate()` at helper.py:45 → conditionally calls `Logger::log()`
3. 🔴 `Logger::log()` at logger.py:78 → dynamic plugin dispatch (uncertain)
## Value-Driven Flow Analysis
For each provided parameter combination, explain how values affect execution:
**Scenario 1**: `payment_method="credit_card", amount=100.00`
- Path: ValidationService → CreditCardProcessor → PaymentGateway.charge()
- Key decision at payment.py:156: routes to Stripe integration
**Scenario 2**: `payment_method="paypal", amount=100.00`
- Path: ValidationService → PayPalProcessor → PayPal.API.process()
- Key decision at payment.py:162: routes to PayPal SDK
## Branching Analysis
Identify key conditional logic that affects call paths:
- **payment.py:156**: `if payment_method == "credit_card"` → determines processor selection
- **validation.py:89**: `if amount > LIMIT` → triggers additional verification
- **logger.py:23**: `if config.DEBUG` → enables detailed logging
## Side Effects & External Dependencies
### Database Interactions
- **payment_transactions.save()** at models.py:234 → inserts payment record
- **user_audit.log_action()** at audit.py:67 → logs user activity
### Network Calls
- **PaymentGateway.charge()** → HTTPS POST to payment processor
- **notifications.send_email()** → SMTP request to email service
### Filesystem Operations
- **Logger::write_to_file()** at logger.py:145 → appends to payment.log
## Polymorphism Resolution
Explain how interface/inheritance affects call dispatch:
- `PaymentProcessor` interface → resolves to `StripeProcessor` or `PayPalProcessor` based on method parameter
- Virtual method `validate()` → overridden in `CreditCardValidator` vs `PayPalValidator`
## Uncertain Calls & Limitations
Explicitly identify areas where static analysis cannot provide definitive answers:
- 🔴 **Dynamic plugin loading** at plugin.py:89: Cannot predict which plugins are loaded at runtime
- 🔴 **Reflection-based calls** at service.py:123: Method names constructed dynamically
- 🔴 **External service behavior**: Payment gateway response handling depends on runtime conditions
## Code Anchors
Key file:line references for implementation:
- Entry point: `BookingManager::finalizeInvoice` at booking.py:45
- Critical branch: Payment method selection at payment.py:156
- Side effect origin: Database save at models.py:234
- Error handling: Exception catch at booking.py:78
RULES & CONSTRAINTS
1. Do not invent code that is not in the project - only analyze what is provided
2. Stay within project boundaries unless dependencies are clearly visible in imports
3. If dynamic behavior depends on runtime state you cannot infer, state so clearly in Uncertain Calls
4. If overloaded or overridden methods exist, explain how resolution happens based on the provided context
5. Provide specific file:line references for all significant calls and decisions
6. Use confidence indicators (🟢🟡🔴) consistently throughout the analysis
7. Focus on the specific entry point and parameters provided - avoid general code analysis
GOAL
Help engineers reason about multi-class call paths without running the code, reducing trial-and-error debugging
or test scaffolding needed to understand complex logic flow. Provide actionable insights for understanding
code behavior, impact analysis, and debugging assistance.
"""

View File

@@ -0,0 +1,169 @@
"""
Tracer tool system prompt
"""
TRACER_PROMPT = """
ROLE
You are a principal software analysis engine. You examine source code across a multi-language repository and statically analyze the behavior of a method, function, or class.
Your task is to return either a full **execution flow trace** (`precision`) or a **bidirectional dependency map** (`dependencies`) based solely on code — never speculation.
You must respond in strict JSON that Claude (the receiving model) can use to visualize, query, and validate.
CRITICAL: You MUST respond ONLY in valid JSON format. NO explanations, introductions, or text outside JSON structure.
Claude cannot parse your response if you include any non-JSON content.
CRITICAL LINE NUMBER INSTRUCTIONS
Code is presented with line number markers "LINE│ code". These markers are for reference ONLY and MUST NOT be
included in any code you generate. Always reference specific line numbers for Claude to locate exact positions.
Include context_start_text and context_end_text as backup references. Never include "LINE│" markers in generated code
snippets.
TRACE MODES
1. **precision** Follow the actual code path from a given method across functions, classes, and modules.
Resolve method calls, branching, type dispatch, and potential side effects. If parameters are provided, use them to resolve branching; if not, flag ambiguous paths.
2. **dependencies** Analyze all dependencies flowing into and out from the method/class, including method calls, state usage, class-level imports, and inheritance.
Show both **incoming** (what uses this) and **outgoing** (what it uses) connections.
INPUT FORMAT
You will receive:
- Method/class name
- Code with File Names
- Optional parameters (used only in precision mode)
IF MORE INFORMATION IS NEEDED OR CONTEXT IS MISSING
If you cannot analyze accurately, respond ONLY with this JSON (and ABSOLUTELY nothing else - no text before or after).
Do NOT ask for the same file you've been provided unless its content is missing or incomplete:
{"status": "clarification_required", "question": "<your brief question>", "files_needed": ["[file name here]", "[or some folder/]"]}
OUTPUT FORMAT
Respond ONLY with the following JSON format depending on the trace mode.
MODE: precision
EXPECTED OUTPUT:
{
"status": "trace_complete",
"trace_type": "precision",
"entry_point": {
"file": "/absolute/path/to/file.ext",
"class_or_struct": "ClassOrModuleName",
"method": "methodName",
"signature": "func methodName(param1: Type1, param2: Type2) -> ReturnType",
"parameters": {
"param1": "value_or_type",
"param2": "value_or_type"
}
},
"call_path": [
{
"from": {
"file": "/file/path",
"class": "ClassName",
"method": "methodName",
"line": 42
},
"to": {
"file": "/file/path",
"class": "ClassName",
"method": "calledMethod",
"line": 123
},
"reason": "direct call / protocol dispatch / conditional branch",
"condition": "if param.isEnabled", // null if unconditional
"ambiguous": false
}
],
"branching_points": [
{
"file": "/file/path",
"method": "methodName",
"line": 77,
"condition": "if user.role == .admin",
"branches": ["audit()", "restrict()"],
"ambiguous": true
}
],
"side_effects": [
{
"type": "database|network|filesystem|state|log|ui|external",
"description": "calls remote endpoint / modifies user record",
"file": "/file/path",
"method": "methodName",
"line": 88
}
],
"unresolved": [
{
"reason": "param.userRole not provided",
"affected_file": "/file/path",
"line": 77
}
]
}
MODE: dependencies
EXPECTED OUTPUT:
{
"status": "trace_complete",
"trace_type": "dependencies",
"target": {
"file": "/absolute/path/to/file.ext",
"class_or_struct": "ClassOrModuleName",
"method": "methodName",
"signature": "func methodName(param1: Type1, param2: Type2) -> ReturnType"
},
"incoming_dependencies": [
{
"from_file": "/file/path",
"from_class": "CallingClass",
"from_method": "callerMethod",
"line": 101,
"type": "direct_call|protocol_impl|event_handler|override|reflection"
}
],
"outgoing_dependencies": [
{
"to_file": "/file/path",
"to_class": "DependencyClass",
"to_method": "calledMethod",
"line": 57,
"type": "method_call|instantiates|uses_constant|reads_property|writes_property|network|db|log"
}
],
"type_dependencies": [
{
"dependency_type": "extends|implements|conforms_to|uses_generic|imports",
"source_file": "/file/path",
"source_entity": "ClassOrStruct",
"target": "TargetProtocolOrClass"
}
],
"state_access": [
{
"file": "/file/path",
"method": "methodName",
"access_type": "reads|writes|mutates|injects",
"state_entity": "user.balance"
}
]
}
RULES
- All data must come from the actual codebase. No invented paths or method guesses.
- If parameters are missing in precision mode, include all possible branches and mark them "ambiguous": true.
- Use full file paths, class names, method names, and line numbers exactly as they appear.
- Use the "reason" field to explain why the call or dependency exists.
- In dependencies mode, the incoming_dependencies list may be empty if nothing in the repo currently calls the target.
GOAL
Enable Claude and the user to clearly visualize how a method:
- Flows across the system (in precision mode)
- Connects with other classes and modules (in dependencies mode)
FINAL REMINDER: CRITICAL OUTPUT FORMAT ENFORCEMENT
Your response MUST start with "{" and end with "}". NO other text is allowed.
If you include ANY text outside the JSON structure, Claude will be unable to parse your response and the tool will fail.
DO NOT provide explanations, introductions, conclusions, or reasoning outside the JSON.
ALL information must be contained within the JSON structure itself.
"""

View File

@@ -28,10 +28,11 @@ class TestServerTools:
assert "precommit" in tool_names assert "precommit" in tool_names
assert "testgen" in tool_names assert "testgen" in tool_names
assert "refactor" in tool_names assert "refactor" in tool_names
assert "tracer" in tool_names
assert "version" in tool_names assert "version" in tool_names
# Should have exactly 9 tools (including refactor) # Should have exactly 10 tools (including refactor and tracer)
assert len(tools) == 9 assert len(tools) == 10
# Check descriptions are verbose # Check descriptions are verbose
for tool in tools: for tool in tools:

View File

@@ -321,3 +321,83 @@ class TestAbsolutePathValidation:
response = json.loads(result[0].text) response = json.loads(result[0].text)
assert response["status"] == "success" assert response["status"] == "success"
assert "Analysis complete" in response["content"] assert "Analysis complete" in response["content"]
class TestSpecialStatusModels:
"""Test SPECIAL_STATUS_MODELS registry and structured response handling"""
def test_trace_complete_status_in_registry(self):
"""Test that trace_complete status is properly registered"""
from tools.models import SPECIAL_STATUS_MODELS, TraceComplete
assert "trace_complete" in SPECIAL_STATUS_MODELS
assert SPECIAL_STATUS_MODELS["trace_complete"] == TraceComplete
def test_trace_complete_model_validation(self):
"""Test TraceComplete model validation"""
from tools.models import TraceComplete
# Test precision mode
precision_data = {
"status": "trace_complete",
"trace_type": "precision",
"entry_point": {
"file": "/path/to/file.py",
"class_or_struct": "MyClass",
"method": "myMethod",
"signature": "def myMethod(self, param1: str) -> bool",
"parameters": {"param1": "test"},
},
"call_path": [
{
"from": {"file": "/path/to/file.py", "class": "MyClass", "method": "myMethod", "line": 10},
"to": {"file": "/path/to/other.py", "class": "OtherClass", "method": "otherMethod", "line": 20},
"reason": "direct call",
"condition": None,
"ambiguous": False,
}
],
}
model = TraceComplete(**precision_data)
assert model.status == "trace_complete"
assert model.trace_type == "precision"
assert model.entry_point.file == "/path/to/file.py"
assert len(model.call_path) == 1
# Test dependencies mode
dependencies_data = {
"status": "trace_complete",
"trace_type": "dependencies",
"target": {
"file": "/path/to/file.py",
"class_or_struct": "MyClass",
"method": "myMethod",
"signature": "def myMethod(self, param1: str) -> bool",
},
"incoming_dependencies": [
{
"from_file": "/path/to/caller.py",
"from_class": "CallerClass",
"from_method": "callerMethod",
"line": 15,
"type": "direct_call",
}
],
"outgoing_dependencies": [
{
"to_file": "/path/to/dependency.py",
"to_class": "DepClass",
"to_method": "depMethod",
"line": 25,
"type": "method_call",
}
],
}
model = TraceComplete(**dependencies_data)
assert model.status == "trace_complete"
assert model.trace_type == "dependencies"
assert model.target.file == "/path/to/file.py"
assert len(model.incoming_dependencies) == 1
assert len(model.outgoing_dependencies) == 1

View File

@@ -1,410 +0,0 @@
"""
Tests for the tracepath tool functionality
"""
from unittest.mock import Mock, patch
import pytest
from tools.models import ToolModelCategory
from tools.tracepath import TracePathRequest, TracePathTool
class TestTracePathTool:
"""Test suite for the TracePath tool"""
@pytest.fixture
def tracepath_tool(self):
"""Create a tracepath tool instance for testing"""
return TracePathTool()
@pytest.fixture
def mock_model_response(self):
"""Create a mock model response for call path analysis"""
def _create_response(content=None):
if content is None:
content = """## Call Path Summary
1. 🟢 `BookingManager::finalizeInvoice()` at booking.py:45 → calls `PaymentProcessor.process()`
2. 🟢 `PaymentProcessor::process()` at payment.py:123 → calls `validation.validate_payment()`
3. 🟡 `validation.validate_payment()` at validation.py:67 → conditionally calls `Logger.log()`
## Value-Driven Flow Analysis
**Scenario 1**: `invoice_id=123, payment_method="credit_card"`
- Path: BookingManager → PaymentProcessor → CreditCardValidator → StripeGateway
- Key decision at payment.py:156: routes to Stripe integration
## Side Effects & External Dependencies
### Database Interactions
- **Transaction.save()** at models.py:234 → inserts payment record
### Network Calls
- **StripeGateway.charge()** → HTTPS POST to Stripe API
## Code Anchors
- Entry point: `BookingManager::finalizeInvoice` at booking.py:45
- Critical branch: Payment method selection at payment.py:156
"""
return Mock(
content=content,
usage={"input_tokens": 150, "output_tokens": 300, "total_tokens": 450},
model_name="test-model",
metadata={"finish_reason": "STOP"},
)
return _create_response
def test_get_name(self, tracepath_tool):
"""Test that the tool returns the correct name"""
assert tracepath_tool.get_name() == "tracepath"
def test_get_description(self, tracepath_tool):
"""Test that the tool returns a comprehensive description"""
description = tracepath_tool.get_description()
assert "STATIC CALL PATH ANALYSIS" in description
assert "control flow" in description
assert "confidence levels" in description
assert "polymorphism" in description
assert "side effects" in description
def test_get_input_schema(self, tracepath_tool):
"""Test that the input schema includes all required fields"""
schema = tracepath_tool.get_input_schema()
assert schema["type"] == "object"
assert "entry_point" in schema["properties"]
assert "files" in schema["properties"]
# Check required fields
required_fields = schema["required"]
assert "entry_point" in required_fields
assert "files" in required_fields
# Check optional parameters
assert "parameters" in schema["properties"]
assert "analysis_depth" in schema["properties"]
assert "language" in schema["properties"]
assert "confidence_threshold" in schema["properties"]
# Check enum values for analysis_depth
depth_enum = schema["properties"]["analysis_depth"]["enum"]
expected_depths = ["shallow", "medium", "deep"]
assert all(depth in depth_enum for depth in expected_depths)
# Check enum values for language
language_enum = schema["properties"]["language"]["enum"]
expected_languages = ["python", "javascript", "typescript", "csharp", "java"]
assert all(lang in language_enum for lang in expected_languages)
def test_get_model_category(self, tracepath_tool):
"""Test that the tool uses extended reasoning category"""
category = tracepath_tool.get_model_category()
assert category == ToolModelCategory.EXTENDED_REASONING
def test_request_model_validation(self):
"""Test request model validation"""
# Valid request
request = TracePathRequest(
entry_point="BookingManager::finalizeInvoice",
files=["/test/booking.py", "/test/payment.py"],
parameters={"invoice_id": 123, "payment_method": "credit_card"},
analysis_depth="medium",
)
assert request.entry_point == "BookingManager::finalizeInvoice"
assert len(request.files) == 2
assert request.analysis_depth == "medium"
assert request.confidence_threshold == 0.7 # default value
# Test validation with invalid confidence threshold
with pytest.raises(ValueError):
TracePathRequest(
entry_point="test::method", files=["/test/file.py"], confidence_threshold=1.5 # Invalid: > 1.0
)
# Invalid request (missing required fields)
with pytest.raises(ValueError):
TracePathRequest(files=["/test/file.py"]) # Missing entry_point
def test_language_detection_python(self, tracepath_tool):
"""Test language detection for Python files"""
files = ["/test/booking.py", "/test/payment.py", "/test/utils.py"]
language = tracepath_tool.detect_primary_language(files)
assert language == "python"
def test_language_detection_javascript(self, tracepath_tool):
"""Test language detection for JavaScript files"""
files = ["/test/app.js", "/test/component.jsx", "/test/utils.js"]
language = tracepath_tool.detect_primary_language(files)
assert language == "javascript"
def test_language_detection_typescript(self, tracepath_tool):
"""Test language detection for TypeScript files"""
files = ["/test/app.ts", "/test/component.tsx", "/test/utils.ts"]
language = tracepath_tool.detect_primary_language(files)
assert language == "typescript"
def test_language_detection_csharp(self, tracepath_tool):
"""Test language detection for C# files"""
files = ["/test/BookingService.cs", "/test/PaymentProcessor.cs"]
language = tracepath_tool.detect_primary_language(files)
assert language == "csharp"
def test_language_detection_java(self, tracepath_tool):
"""Test language detection for Java files"""
files = ["/test/BookingManager.java", "/test/PaymentService.java"]
language = tracepath_tool.detect_primary_language(files)
assert language == "java"
def test_language_detection_mixed(self, tracepath_tool):
"""Test language detection for mixed language files"""
files = ["/test/app.py", "/test/service.js", "/test/model.java"]
language = tracepath_tool.detect_primary_language(files)
assert language == "mixed"
def test_language_detection_unknown(self, tracepath_tool):
"""Test language detection for unknown extensions"""
files = ["/test/config.xml", "/test/readme.txt"]
language = tracepath_tool.detect_primary_language(files)
assert language == "unknown"
def test_parse_entry_point_class_method_double_colon(self, tracepath_tool):
"""Test parsing entry point with double colon syntax"""
result = tracepath_tool.parse_entry_point("BookingManager::finalizeInvoice", "python")
assert result["raw"] == "BookingManager::finalizeInvoice"
assert result["class_or_module"] == "BookingManager"
assert result["method_or_function"] == "finalizeInvoice"
assert result["type"] == "method"
def test_parse_entry_point_module_function_dot(self, tracepath_tool):
"""Test parsing entry point with dot syntax"""
result = tracepath_tool.parse_entry_point("utils.validate_input", "python")
assert result["raw"] == "utils.validate_input"
assert result["class_or_module"] == "utils"
assert result["method_or_function"] == "validate_input"
assert result["type"] == "function"
def test_parse_entry_point_nested_module(self, tracepath_tool):
"""Test parsing entry point with nested module syntax"""
result = tracepath_tool.parse_entry_point("payment.services.process_payment", "python")
assert result["raw"] == "payment.services.process_payment"
assert result["class_or_module"] == "payment.services"
assert result["method_or_function"] == "process_payment"
assert result["type"] == "function"
def test_parse_entry_point_function_only(self, tracepath_tool):
"""Test parsing entry point with function name only"""
result = tracepath_tool.parse_entry_point("validate_payment", "python")
assert result["raw"] == "validate_payment"
assert result["class_or_module"] == ""
assert result["method_or_function"] == "validate_payment"
assert result["type"] == "function"
def test_parse_entry_point_camelcase_class(self, tracepath_tool):
"""Test parsing entry point with CamelCase class (method detection)"""
result = tracepath_tool.parse_entry_point("PaymentProcessor.process", "java")
assert result["raw"] == "PaymentProcessor.process"
assert result["class_or_module"] == "PaymentProcessor"
assert result["method_or_function"] == "process"
assert result["type"] == "method" # CamelCase suggests class method
@pytest.mark.asyncio
async def test_generate_structural_summary_phase1(self, tracepath_tool):
"""Test structural summary generation (Phase 1 returns empty)"""
files = ["/test/booking.py", "/test/payment.py"]
summary = await tracepath_tool._generate_structural_summary(files, "python")
# Phase 1 implementation should return empty string
assert summary == ""
@pytest.mark.asyncio
async def test_prepare_prompt_basic(self, tracepath_tool):
"""Test basic prompt preparation"""
request = TracePathRequest(
entry_point="BookingManager::finalizeInvoice",
files=["/test/booking.py"],
parameters={"invoice_id": 123},
analysis_depth="medium",
)
# Mock file content preparation
with patch.object(tracepath_tool, "_prepare_file_content_for_prompt") as mock_prep:
mock_prep.return_value = "def finalizeInvoice(self, invoice_id):\n pass"
with patch.object(tracepath_tool, "_validate_token_limit"):
prompt = await tracepath_tool.prepare_prompt(request)
assert "ANALYSIS REQUEST" in prompt
assert "BookingManager::finalizeInvoice" in prompt
assert "medium" in prompt
assert "CODE TO ANALYZE" in prompt
@pytest.mark.asyncio
async def test_prepare_prompt_with_parameters(self, tracepath_tool):
"""Test prompt preparation with parameter values"""
request = TracePathRequest(
entry_point="payment.process_payment",
files=["/test/payment.py"],
parameters={"amount": 100.50, "method": "credit_card"},
analysis_depth="deep",
include_db=True,
include_network=True,
include_fs=False,
)
with patch.object(tracepath_tool, "_prepare_file_content_for_prompt") as mock_prep:
mock_prep.return_value = "def process_payment(amount, method):\n pass"
with patch.object(tracepath_tool, "_validate_token_limit"):
prompt = await tracepath_tool.prepare_prompt(request)
assert "Parameter Values: {'amount': 100.5, 'method': 'credit_card'}" in prompt
assert "Analysis Depth: deep" in prompt
assert "Include Side Effects: database, network" in prompt
@pytest.mark.asyncio
async def test_prepare_prompt_with_context(self, tracepath_tool):
"""Test prompt preparation with additional context"""
request = TracePathRequest(
entry_point="UserService::authenticate",
files=["/test/auth.py"],
context="Focus on security implications and potential vulnerabilities",
focus_areas=["security", "error_handling"],
)
with patch.object(tracepath_tool, "_prepare_file_content_for_prompt") as mock_prep:
mock_prep.return_value = "def authenticate(self, username, password):\n pass"
with patch.object(tracepath_tool, "_validate_token_limit"):
prompt = await tracepath_tool.prepare_prompt(request)
assert "Additional Context: Focus on security implications" in prompt
assert "Focus Areas: security, error_handling" in prompt
def test_format_response_markdown(self, tracepath_tool):
"""Test response formatting for markdown output"""
request = TracePathRequest(
entry_point="BookingManager::finalizeInvoice", files=["/test/booking.py"], export_format="markdown"
)
response = "## Call Path Summary\n1. BookingManager::finalizeInvoice..."
model_info = {"model_response": Mock(friendly_name="Gemini Pro")}
formatted = tracepath_tool.format_response(response, request, model_info)
assert response in formatted
assert "Analysis Complete" in formatted
assert "Gemini Pro" in formatted
assert "confidence assessments" in formatted
def test_format_response_json(self, tracepath_tool):
"""Test response formatting for JSON output"""
request = TracePathRequest(entry_point="payment.process", files=["/test/payment.py"], export_format="json")
response = '{"call_path": [...], "confidence": "high"}'
formatted = tracepath_tool.format_response(response, request)
assert response in formatted
assert "structured JSON analysis" in formatted
assert "confidence levels" in formatted
def test_format_response_plantuml(self, tracepath_tool):
"""Test response formatting for PlantUML output"""
request = TracePathRequest(entry_point="service.execute", files=["/test/service.py"], export_format="plantuml")
response = "@startuml\nBooking -> Payment\n@enduml"
formatted = tracepath_tool.format_response(response, request)
assert response in formatted
assert "PlantUML diagram" in formatted
assert "Render the PlantUML" in formatted
def test_get_default_temperature(self, tracepath_tool):
"""Test that the tool uses analytical temperature"""
from config import TEMPERATURE_ANALYTICAL
assert tracepath_tool.get_default_temperature() == TEMPERATURE_ANALYTICAL
def test_wants_line_numbers_by_default(self, tracepath_tool):
"""Test that line numbers are enabled by default"""
# The base class should enable line numbers by default for precise references
# We test that this isn't overridden to disable them
assert hasattr(tracepath_tool, "wants_line_numbers_by_default")
def test_side_effects_configuration(self):
"""Test side effects boolean configuration"""
request = TracePathRequest(
entry_point="test.function",
files=["/test/file.py"],
include_db=True,
include_network=False,
include_fs=True,
)
assert request.include_db is True
assert request.include_network is False
assert request.include_fs is True
def test_confidence_threshold_bounds(self):
"""Test confidence threshold validation bounds"""
# Valid thresholds
request1 = TracePathRequest(entry_point="test.function", files=["/test/file.py"], confidence_threshold=0.0)
assert request1.confidence_threshold == 0.0
request2 = TracePathRequest(entry_point="test.function", files=["/test/file.py"], confidence_threshold=1.0)
assert request2.confidence_threshold == 1.0
# Invalid thresholds should raise ValidationError
with pytest.raises(ValueError):
TracePathRequest(entry_point="test.function", files=["/test/file.py"], confidence_threshold=-0.1)
with pytest.raises(ValueError):
TracePathRequest(entry_point="test.function", files=["/test/file.py"], confidence_threshold=1.1)
def test_signature_parameter(self):
"""Test signature parameter for overload resolution"""
request = TracePathRequest(
entry_point="Calculator.add",
files=["/test/calc.cs"],
signature="public int Add(int a, int b)",
language="csharp",
)
assert request.signature == "public int Add(int a, int b)"
assert request.language == "csharp"
@pytest.mark.asyncio
async def test_prepare_prompt_with_language_override(self, tracepath_tool):
"""Test prompt preparation with language override"""
request = TracePathRequest(
entry_point="Calculator::Add",
files=["/test/calc.py"], # Python extension
language="csharp", # Override to C#
)
with patch.object(tracepath_tool, "_prepare_file_content_for_prompt") as mock_prep:
mock_prep.return_value = "public class Calculator { }"
with patch.object(tracepath_tool, "_validate_token_limit"):
prompt = await tracepath_tool.prepare_prompt(request)
assert "Language: csharp" in prompt # Should use override, not detected
def test_export_format_options(self):
"""Test all export format options"""
formats = ["markdown", "json", "plantuml"]
for fmt in formats:
request = TracePathRequest(entry_point="test.function", files=["/test/file.py"], export_format=fmt)
assert request.export_format == fmt
# Invalid format should raise ValidationError
with pytest.raises(ValueError):
TracePathRequest(entry_point="test.function", files=["/test/file.py"], export_format="invalid_format")

420
tests/test_tracer.py Normal file
View File

@@ -0,0 +1,420 @@
"""
Tests for the tracer tool functionality
"""
from unittest.mock import Mock, patch
import pytest
from tools.models import ToolModelCategory
from tools.tracer import TracerRequest, TracerTool
class TestTracerTool:
"""Test suite for the Tracer tool"""
@pytest.fixture
def tracer_tool(self):
"""Create a tracer tool instance for testing"""
return TracerTool()
@pytest.fixture
def mock_model_response(self):
"""Create a mock model response for call path analysis"""
def _create_response(content=None):
if content is None:
content = """## Call Path Summary
1. 🟢 `BookingManager::finalizeInvoice()` at booking.py:45 → calls `PaymentProcessor.process()`
2. 🟢 `PaymentProcessor::process()` at payment.py:123 → calls `validation.validate_payment()`
3. 🟡 `validation.validate_payment()` at validation.py:67 → conditionally calls `Logger.log()`
## Value-Driven Flow Analysis
**Scenario 1**: `invoice_id=123, payment_method="credit_card"`
- Path: BookingManager → PaymentProcessor → CreditCardValidator → StripeGateway
- Key decision at payment.py:156: routes to Stripe integration
## Side Effects & External Dependencies
### Database Interactions
- **Transaction.save()** at models.py:234 → inserts payment record
### Network Calls
- **StripeGateway.charge()** → HTTPS POST to Stripe API
## Code Anchors
- Entry point: `BookingManager::finalizeInvoice` at booking.py:45
- Critical branch: Payment method selection at payment.py:156
"""
return Mock(
content=content,
usage={"input_tokens": 150, "output_tokens": 300, "total_tokens": 450},
model_name="test-model",
metadata={"finish_reason": "STOP"},
)
return _create_response
def test_get_name(self, tracer_tool):
"""Test that the tool returns the correct name"""
assert tracer_tool.get_name() == "tracer"
def test_get_description(self, tracer_tool):
"""Test that the tool returns a comprehensive description"""
description = tracer_tool.get_description()
assert "STATIC CODE ANALYSIS" in description
assert "execution flow" in description
assert "dependency mappings" in description
assert "precision" in description
assert "dependencies" in description
def test_get_input_schema(self, tracer_tool):
"""Test that the input schema includes all required fields"""
schema = tracer_tool.get_input_schema()
assert schema["type"] == "object"
assert "prompt" in schema["properties"]
assert "files" in schema["properties"]
assert "trace_mode" in schema["properties"]
# Check required fields
required_fields = schema["required"]
assert "prompt" in required_fields
assert "files" in required_fields
assert "trace_mode" in required_fields
# Check enum values for trace_mode
trace_mode_enum = schema["properties"]["trace_mode"]["enum"]
assert "precision" in trace_mode_enum
assert "dependencies" in trace_mode_enum
def test_get_model_category(self, tracer_tool):
"""Test that the tool uses extended reasoning category"""
category = tracer_tool.get_model_category()
assert category == ToolModelCategory.EXTENDED_REASONING
def test_request_model_validation(self):
"""Test request model validation"""
# Valid request
request = TracerRequest(
prompt="Trace BookingManager::finalizeInvoice method with invoice_id=123",
files=["/test/booking.py", "/test/payment.py"],
trace_mode="precision",
)
assert request.prompt == "Trace BookingManager::finalizeInvoice method with invoice_id=123"
assert len(request.files) == 2
assert request.trace_mode == "precision"
# Invalid request (missing required fields)
with pytest.raises(ValueError):
TracerRequest(files=["/test/file.py"]) # Missing prompt and trace_mode
# Invalid trace_mode value
with pytest.raises(ValueError):
TracerRequest(prompt="Test", files=["/test/file.py"], trace_mode="invalid_type")
def test_language_detection_python(self, tracer_tool):
"""Test language detection for Python files"""
files = ["/test/booking.py", "/test/payment.py", "/test/utils.py"]
language = tracer_tool.detect_primary_language(files)
assert language == "python"
def test_language_detection_javascript(self, tracer_tool):
"""Test language detection for JavaScript files"""
files = ["/test/app.js", "/test/component.jsx", "/test/utils.js"]
language = tracer_tool.detect_primary_language(files)
assert language == "javascript"
def test_language_detection_typescript(self, tracer_tool):
"""Test language detection for TypeScript files"""
files = ["/test/app.ts", "/test/component.tsx", "/test/utils.ts"]
language = tracer_tool.detect_primary_language(files)
assert language == "typescript"
def test_language_detection_csharp(self, tracer_tool):
"""Test language detection for C# files"""
files = ["/test/BookingService.cs", "/test/PaymentProcessor.cs"]
language = tracer_tool.detect_primary_language(files)
assert language == "csharp"
def test_language_detection_java(self, tracer_tool):
"""Test language detection for Java files"""
files = ["/test/BookingManager.java", "/test/PaymentService.java"]
language = tracer_tool.detect_primary_language(files)
assert language == "java"
def test_language_detection_mixed(self, tracer_tool):
"""Test language detection for mixed language files"""
files = ["/test/app.py", "/test/service.js", "/test/model.java"]
language = tracer_tool.detect_primary_language(files)
assert language == "mixed"
def test_language_detection_unknown(self, tracer_tool):
"""Test language detection for unknown extensions"""
files = ["/test/config.xml", "/test/readme.txt"]
language = tracer_tool.detect_primary_language(files)
assert language == "unknown"
# Removed parse_entry_point tests as method no longer exists in simplified interface
@pytest.mark.asyncio
async def test_prepare_prompt_basic(self, tracer_tool):
"""Test basic prompt preparation"""
request = TracerRequest(
prompt="Trace BookingManager::finalizeInvoice method with invoice_id=123",
files=["/test/booking.py"],
trace_mode="precision",
)
# Mock file content preparation
with patch.object(tracer_tool, "_prepare_file_content_for_prompt") as mock_prep:
mock_prep.return_value = "def finalizeInvoice(self, invoice_id):\n pass"
with patch.object(tracer_tool, "check_prompt_size") as mock_check:
mock_check.return_value = None
prompt = await tracer_tool.prepare_prompt(request)
assert "ANALYSIS REQUEST" in prompt
assert "Trace BookingManager::finalizeInvoice method" in prompt
assert "precision" in prompt
assert "CODE TO ANALYZE" in prompt
@pytest.mark.asyncio
async def test_prepare_prompt_with_dependencies(self, tracer_tool):
"""Test prompt preparation with dependencies type"""
request = TracerRequest(
prompt="Analyze dependencies for payment.process_payment function with amount=100.50",
files=["/test/payment.py"],
trace_mode="dependencies",
)
with patch.object(tracer_tool, "_prepare_file_content_for_prompt") as mock_prep:
mock_prep.return_value = "def process_payment(amount, method):\n pass"
with patch.object(tracer_tool, "check_prompt_size") as mock_check:
mock_check.return_value = None
prompt = await tracer_tool.prepare_prompt(request)
assert "Analyze dependencies for payment.process_payment" in prompt
assert "Trace Mode: dependencies" in prompt
@pytest.mark.asyncio
async def test_prepare_prompt_with_security_context(self, tracer_tool):
"""Test prompt preparation with security context"""
request = TracerRequest(
prompt="Trace UserService::authenticate method focusing on security implications and potential vulnerabilities",
files=["/test/auth.py"],
trace_mode="precision",
)
with patch.object(tracer_tool, "_prepare_file_content_for_prompt") as mock_prep:
mock_prep.return_value = "def authenticate(self, username, password):\n pass"
with patch.object(tracer_tool, "check_prompt_size") as mock_check:
mock_check.return_value = None
prompt = await tracer_tool.prepare_prompt(request)
assert "security implications and potential vulnerabilities" in prompt
assert "Trace Mode: precision" in prompt
def test_format_response_precision(self, tracer_tool):
"""Test response formatting for precision trace"""
request = TracerRequest(
prompt="Trace BookingManager::finalizeInvoice method", files=["/test/booking.py"], trace_mode="precision"
)
response = '{"status": "trace_complete", "trace_type": "precision"}'
model_info = {"model_response": Mock(friendly_name="Gemini Pro")}
formatted = tracer_tool.format_response(response, request, model_info)
assert response in formatted
assert "Analysis Complete" in formatted
assert "Gemini Pro" in formatted
assert "precision analysis" in formatted
assert "CALL FLOW DIAGRAM" in formatted
assert "BRANCHING & SIDE EFFECT TABLE" in formatted
def test_format_response_dependencies(self, tracer_tool):
"""Test response formatting for dependencies trace"""
request = TracerRequest(
prompt="Analyze dependencies for payment.process function",
files=["/test/payment.py"],
trace_mode="dependencies",
)
response = '{"status": "trace_complete", "trace_type": "dependencies"}'
formatted = tracer_tool.format_response(response, request)
assert response in formatted
assert "dependencies analysis" in formatted
assert "DEPENDENCY FLOW GRAPH" in formatted
assert "DEPENDENCY TABLE" in formatted
# Removed PlantUML test as export_format is no longer a parameter
def test_get_default_temperature(self, tracer_tool):
"""Test that the tool uses analytical temperature"""
from config import TEMPERATURE_ANALYTICAL
assert tracer_tool.get_default_temperature() == TEMPERATURE_ANALYTICAL
def test_wants_line_numbers_by_default(self, tracer_tool):
"""Test that line numbers are enabled by default"""
# The base class should enable line numbers by default for precise references
# We test that this isn't overridden to disable them
assert hasattr(tracer_tool, "wants_line_numbers_by_default")
def test_trace_mode_validation(self):
"""Test trace mode validation"""
# Valid trace modes
request1 = TracerRequest(prompt="Test precision", files=["/test/file.py"], trace_mode="precision")
assert request1.trace_mode == "precision"
request2 = TracerRequest(prompt="Test dependencies", files=["/test/file.py"], trace_mode="dependencies")
assert request2.trace_mode == "dependencies"
# Invalid trace mode should raise ValidationError
with pytest.raises(ValueError):
TracerRequest(prompt="Test", files=["/test/file.py"], trace_mode="invalid_type")
def test_get_rendering_instructions(self, tracer_tool):
"""Test the main rendering instructions dispatcher method"""
# Test precision mode
precision_instructions = tracer_tool._get_rendering_instructions("precision")
assert "MANDATORY RENDERING INSTRUCTIONS FOR PRECISION TRACE" in precision_instructions
assert "CALL FLOW DIAGRAM" in precision_instructions
assert "BRANCHING & SIDE EFFECT TABLE" in precision_instructions
# Test dependencies mode
dependencies_instructions = tracer_tool._get_rendering_instructions("dependencies")
assert "MANDATORY RENDERING INSTRUCTIONS FOR DEPENDENCIES TRACE" in dependencies_instructions
assert "DEPENDENCY FLOW GRAPH" in dependencies_instructions
assert "DEPENDENCY TABLE" in dependencies_instructions
def test_get_precision_rendering_instructions(self, tracer_tool):
"""Test precision mode rendering instructions"""
instructions = tracer_tool._get_precision_rendering_instructions()
# Check for required sections
assert "MANDATORY RENDERING INSTRUCTIONS FOR PRECISION TRACE" in instructions
assert "1. CALL FLOW DIAGRAM (TOP-DOWN)" in instructions
assert "2. BRANCHING & SIDE EFFECT TABLE" in instructions
# Check for specific formatting requirements
assert "[Class::Method] (file: /path, line: ##)" in instructions
assert "Chain each call using ↓ or → for readability" in instructions
assert "If ambiguous, mark with `⚠️ ambiguous branch`" in instructions
assert "Side Effects:" in instructions
assert "[database] description (File.ext:##)" in instructions
# Check for critical rules
assert "CRITICAL RULES:" in instructions
assert "Use exact filenames, class names, and line numbers from JSON" in instructions
assert "DO NOT invent function names or examples" in instructions
def test_get_dependencies_rendering_instructions(self, tracer_tool):
"""Test dependencies mode rendering instructions"""
instructions = tracer_tool._get_dependencies_rendering_instructions()
# Check for required sections
assert "MANDATORY RENDERING INSTRUCTIONS FOR DEPENDENCIES TRACE" in instructions
assert "1. DEPENDENCY FLOW GRAPH" in instructions
assert "2. DEPENDENCY TABLE" in instructions
# Check for specific formatting requirements
assert "Called by:" in instructions
assert "[CallerClass::callerMethod] ← /path/file.ext:##" in instructions
assert "Calls:" in instructions
assert "[Logger::logAction] → /utils/log.ext:##" in instructions
assert "Type Dependencies:" in instructions
assert "State Access:" in instructions
# Check for arrow rules
assert "`←` for incoming (who calls this)" in instructions
assert "`→` for outgoing (what this calls)" in instructions
# Check for dependency table format
assert "| Type | From/To | Method | File | Line |" in instructions
assert "| direct_call | From: CallerClass | callerMethod |" in instructions
# Check for critical rules
assert "CRITICAL RULES:" in instructions
assert "Use exact filenames, class names, and line numbers from JSON" in instructions
assert "Show directional dependencies with proper arrows" in instructions
def test_format_response_uses_private_methods(self, tracer_tool):
"""Test that format_response correctly uses the refactored private methods"""
# Test precision mode
precision_request = TracerRequest(prompt="Test precision", files=["/test/file.py"], trace_mode="precision")
precision_response = tracer_tool.format_response('{"test": "response"}', precision_request)
# Should contain precision-specific instructions
assert "CALL FLOW DIAGRAM" in precision_response
assert "BRANCHING & SIDE EFFECT TABLE" in precision_response
assert "precision analysis" in precision_response
# Test dependencies mode
dependencies_request = TracerRequest(
prompt="Test dependencies", files=["/test/file.py"], trace_mode="dependencies"
)
dependencies_response = tracer_tool.format_response('{"test": "response"}', dependencies_request)
# Should contain dependencies-specific instructions
assert "DEPENDENCY FLOW GRAPH" in dependencies_response
assert "DEPENDENCY TABLE" in dependencies_response
assert "dependencies analysis" in dependencies_response
def test_rendering_instructions_consistency(self, tracer_tool):
"""Test that private methods return consistent instructions"""
# Get instructions through both paths
precision_direct = tracer_tool._get_precision_rendering_instructions()
precision_via_dispatcher = tracer_tool._get_rendering_instructions("precision")
dependencies_direct = tracer_tool._get_dependencies_rendering_instructions()
dependencies_via_dispatcher = tracer_tool._get_rendering_instructions("dependencies")
# Should be identical
assert precision_direct == precision_via_dispatcher
assert dependencies_direct == dependencies_via_dispatcher
def test_rendering_instructions_completeness(self, tracer_tool):
"""Test that rendering instructions contain all required elements"""
precision_instructions = tracer_tool._get_precision_rendering_instructions()
dependencies_instructions = tracer_tool._get_dependencies_rendering_instructions()
# Both should have mandatory sections
for instructions in [precision_instructions, dependencies_instructions]:
assert "MANDATORY RENDERING INSTRUCTIONS" in instructions
assert "You MUST render" in instructions
assert "exactly two views" in instructions
assert "CRITICAL RULES:" in instructions
assert "ALWAYS render both views unless data is missing" in instructions
assert "Use exact filenames, class names, and line numbers from JSON" in instructions
assert "DO NOT invent function names or examples" in instructions
def test_rendering_instructions_mode_specific_content(self, tracer_tool):
"""Test that each mode has unique content"""
precision_instructions = tracer_tool._get_precision_rendering_instructions()
dependencies_instructions = tracer_tool._get_dependencies_rendering_instructions()
# Precision-specific content should not be in dependencies
assert "CALL FLOW DIAGRAM" in precision_instructions
assert "CALL FLOW DIAGRAM" not in dependencies_instructions
assert "BRANCHING & SIDE EFFECT TABLE" in precision_instructions
assert "BRANCHING & SIDE EFFECT TABLE" not in dependencies_instructions
# Dependencies-specific content should not be in precision
assert "DEPENDENCY FLOW GRAPH" in dependencies_instructions
assert "DEPENDENCY FLOW GRAPH" not in precision_instructions
assert "DEPENDENCY TABLE" in dependencies_instructions
assert "DEPENDENCY TABLE" not in precision_instructions
# Mode-specific symbols and patterns
assert "" in precision_instructions # Flow arrows
assert "" in dependencies_instructions # Incoming arrow
assert "" in dependencies_instructions # Outgoing arrow
assert "Side Effects:" in precision_instructions
assert "Called by:" in dependencies_instructions

View File

@@ -10,7 +10,7 @@ from .precommit import Precommit
from .refactor import RefactorTool from .refactor import RefactorTool
from .testgen import TestGenTool from .testgen import TestGenTool
from .thinkdeep import ThinkDeepTool from .thinkdeep import ThinkDeepTool
from .tracepath import TracePathTool from .tracer import TracerTool
__all__ = [ __all__ = [
"ThinkDeepTool", "ThinkDeepTool",
@@ -21,5 +21,5 @@ __all__ = [
"Precommit", "Precommit",
"RefactorTool", "RefactorTool",
"TestGenTool", "TestGenTool",
"TracePathTool", "TracerTool",
] ]

View File

@@ -41,6 +41,7 @@ class ToolOutput(BaseModel):
"test_sample_needed", "test_sample_needed",
"more_tests_required", "more_tests_required",
"refactor_analysis_complete", "refactor_analysis_complete",
"trace_complete",
"resend_prompt", "resend_prompt",
"continuation_available", "continuation_available",
] = "success" ] = "success"
@@ -150,6 +151,129 @@ class ResendPromptRequest(BaseModel):
metadata: dict[str, Any] = Field(default_factory=dict) metadata: dict[str, Any] = Field(default_factory=dict)
class TraceEntryPoint(BaseModel):
"""Entry point information for trace analysis"""
file: str = Field(..., description="Absolute path to the file")
class_or_struct: str = Field(..., description="Class or module name")
method: str = Field(..., description="Method or function name")
signature: str = Field(..., description="Full method signature")
parameters: Optional[dict[str, Any]] = Field(default_factory=dict, description="Parameter values used in analysis")
class TraceTarget(BaseModel):
"""Target information for dependency analysis"""
file: str = Field(..., description="Absolute path to the file")
class_or_struct: str = Field(..., description="Class or module name")
method: str = Field(..., description="Method or function name")
signature: str = Field(..., description="Full method signature")
class CallPathStep(BaseModel):
"""A single step in the call path trace"""
from_info: dict[str, Any] = Field(..., description="Source location information", alias="from")
to: dict[str, Any] = Field(..., description="Target location information")
reason: str = Field(..., description="Reason for the call or dependency")
condition: Optional[str] = Field(None, description="Conditional logic if applicable")
ambiguous: bool = Field(False, description="Whether this call is ambiguous")
class BranchingPoint(BaseModel):
"""A branching point in the execution flow"""
file: str = Field(..., description="File containing the branching point")
method: str = Field(..., description="Method containing the branching point")
line: int = Field(..., description="Line number of the branching point")
condition: str = Field(..., description="Branching condition")
branches: list[str] = Field(..., description="Possible execution branches")
ambiguous: bool = Field(False, description="Whether the branching is ambiguous")
class SideEffect(BaseModel):
"""A side effect detected in the trace"""
type: str = Field(..., description="Type of side effect")
description: str = Field(..., description="Description of the side effect")
file: str = Field(..., description="File where the side effect occurs")
method: str = Field(..., description="Method where the side effect occurs")
line: int = Field(..., description="Line number of the side effect")
class UnresolvedDependency(BaseModel):
"""An unresolved dependency in the trace"""
reason: str = Field(..., description="Reason why the dependency is unresolved")
affected_file: str = Field(..., description="File affected by the unresolved dependency")
line: int = Field(..., description="Line number of the unresolved dependency")
class IncomingDependency(BaseModel):
"""An incoming dependency (what calls this target)"""
from_file: str = Field(..., description="Source file of the dependency")
from_class: str = Field(..., description="Source class of the dependency")
from_method: str = Field(..., description="Source method of the dependency")
line: int = Field(..., description="Line number of the dependency")
type: str = Field(..., description="Type of dependency")
class OutgoingDependency(BaseModel):
"""An outgoing dependency (what this target calls)"""
to_file: str = Field(..., description="Target file of the dependency")
to_class: str = Field(..., description="Target class of the dependency")
to_method: str = Field(..., description="Target method of the dependency")
line: int = Field(..., description="Line number of the dependency")
type: str = Field(..., description="Type of dependency")
class TypeDependency(BaseModel):
"""A type-level dependency (inheritance, imports, etc.)"""
dependency_type: str = Field(..., description="Type of dependency")
source_file: str = Field(..., description="Source file of the dependency")
source_entity: str = Field(..., description="Source entity (class, module)")
target: str = Field(..., description="Target entity")
class StateAccess(BaseModel):
"""State access information"""
file: str = Field(..., description="File where state is accessed")
method: str = Field(..., description="Method accessing the state")
access_type: str = Field(..., description="Type of access (reads, writes, etc.)")
state_entity: str = Field(..., description="State entity being accessed")
class TraceComplete(BaseModel):
"""Complete trace analysis response"""
status: Literal["trace_complete"] = "trace_complete"
trace_type: Literal["precision", "dependencies"] = Field(..., description="Type of trace performed")
# Precision mode fields
entry_point: Optional[TraceEntryPoint] = Field(None, description="Entry point for precision trace")
call_path: Optional[list[CallPathStep]] = Field(default_factory=list, description="Call path for precision trace")
branching_points: Optional[list[BranchingPoint]] = Field(default_factory=list, description="Branching points")
side_effects: Optional[list[SideEffect]] = Field(default_factory=list, description="Side effects detected")
unresolved: Optional[list[UnresolvedDependency]] = Field(
default_factory=list, description="Unresolved dependencies"
)
# Dependencies mode fields
target: Optional[TraceTarget] = Field(None, description="Target for dependency analysis")
incoming_dependencies: Optional[list[IncomingDependency]] = Field(
default_factory=list, description="Incoming dependencies"
)
outgoing_dependencies: Optional[list[OutgoingDependency]] = Field(
default_factory=list, description="Outgoing dependencies"
)
type_dependencies: Optional[list[TypeDependency]] = Field(default_factory=list, description="Type dependencies")
state_access: Optional[list[StateAccess]] = Field(default_factory=list, description="State access information")
# Registry mapping status strings to their corresponding Pydantic models # Registry mapping status strings to their corresponding Pydantic models
SPECIAL_STATUS_MODELS = { SPECIAL_STATUS_MODELS = {
"clarification_required": ClarificationRequest, "clarification_required": ClarificationRequest,
@@ -158,6 +282,7 @@ SPECIAL_STATUS_MODELS = {
"test_sample_needed": TestSampleNeeded, "test_sample_needed": TestSampleNeeded,
"more_tests_required": MoreTestsRequired, "more_tests_required": MoreTestsRequired,
"refactor_analysis_complete": RefactorAnalysisComplete, "refactor_analysis_complete": RefactorAnalysisComplete,
"trace_complete": TraceComplete,
"resend_prompt": ResendPromptRequest, "resend_prompt": ResendPromptRequest,
} }

View File

@@ -1,602 +0,0 @@
"""
TracePath tool - Static call path prediction and control flow analysis
This tool analyzes code to predict and explain full call paths and control flow without executing code.
Given a method name, its owning class/module, and parameter combinations or runtime values, it predicts
the complete chain of method/function calls that would be triggered.
Key Features:
- Static call path prediction with confidence levels
- Polymorphism and dynamic dispatch analysis
- Value-driven flow analysis based on parameter combinations
- Side effects identification (database, network, filesystem)
- Branching analysis for conditional logic
- Hybrid AI-first approach with optional AST preprocessing for enhanced accuracy
"""
import logging
import os
import re
from typing import Any, Literal, Optional
from pydantic import Field
from config import TEMPERATURE_ANALYTICAL
from systemprompts import TRACEPATH_PROMPT
from .base import BaseTool, ToolRequest
logger = logging.getLogger(__name__)
class TracePathRequest(ToolRequest):
"""
Request model for the tracepath tool.
This model defines all parameters for customizing the call path analysis process.
"""
entry_point: str = Field(
...,
description="Method/function to trace (e.g., 'BookingManager::finalizeInvoice', 'utils.validate_input')",
)
files: list[str] = Field(
...,
description="Code files or directories to analyze (must be absolute paths)",
)
parameters: Optional[dict[str, Any]] = Field(
None,
description="Parameter values to analyze - format: {param_name: value_or_type}",
)
context: Optional[str] = Field(
None,
description="Additional context about analysis goals or specific scenarios to focus on",
)
analysis_depth: Literal["shallow", "medium", "deep"] = Field(
"medium",
description="Analysis depth: shallow (direct calls), medium (2-3 levels), deep (full trace)",
)
language: Optional[str] = Field(
None,
description="Override auto-detection: python, javascript, typescript, csharp, java",
)
signature: Optional[str] = Field(
None,
description="Fully-qualified signature for overload resolution in languages like C#/Java",
)
confidence_threshold: Optional[float] = Field(
0.7,
description="Filter speculative branches (0-1, default 0.7)",
ge=0.0,
le=1.0,
)
include_db: bool = Field(
True,
description="Include database interactions in side effects analysis",
)
include_network: bool = Field(
True,
description="Include network calls in side effects analysis",
)
include_fs: bool = Field(
True,
description="Include filesystem operations in side effects analysis",
)
export_format: Literal["markdown", "json", "plantuml"] = Field(
"markdown",
description="Output format for the analysis results",
)
focus_areas: Optional[list[str]] = Field(
None,
description="Specific aspects to focus on (e.g., 'performance', 'security', 'error_handling')",
)
class TracePathTool(BaseTool):
"""
TracePath tool implementation.
This tool analyzes code to predict static call paths and control flow without execution.
Uses a hybrid AI-first approach with optional AST preprocessing for enhanced accuracy.
"""
def get_name(self) -> str:
return "tracepath"
def get_description(self) -> str:
return (
"STATIC CALL PATH ANALYSIS - Predicts and explains full call paths and control flow without executing code. "
"Given a method/function name and parameter values, traces the complete execution path including "
"conditional branches, polymorphism resolution, and side effects. "
"Perfect for: understanding complex code flows, impact analysis, debugging assistance, architecture review. "
"Provides confidence levels for predictions and identifies uncertain calls due to dynamic behavior. "
"Choose thinking_mode based on code complexity: 'low' for simple functions, "
"'medium' for standard analysis (default), 'high' for complex systems, "
"'max' for legacy codebases requiring deep analysis. "
"Note: If you're not currently using a top-tier model such as Opus 4 or above, these tools can provide enhanced capabilities."
)
def get_input_schema(self) -> dict[str, Any]:
schema = {
"type": "object",
"properties": {
"entry_point": {
"type": "string",
"description": "Method/function to trace (e.g., 'BookingManager::finalizeInvoice', 'utils.validate_input')",
},
"files": {
"type": "array",
"items": {"type": "string"},
"description": "Code files or directories to analyze (must be absolute paths)",
},
"model": self.get_model_field_schema(),
"parameters": {
"type": "object",
"description": "Parameter values to analyze - format: {param_name: value_or_type}",
},
"context": {
"type": "string",
"description": "Additional context about analysis goals or specific scenarios to focus on",
},
"analysis_depth": {
"type": "string",
"enum": ["shallow", "medium", "deep"],
"default": "medium",
"description": "Analysis depth: shallow (direct calls), medium (2-3 levels), deep (full trace)",
},
"language": {
"type": "string",
"enum": ["python", "javascript", "typescript", "csharp", "java"],
"description": "Override auto-detection for programming language",
},
"signature": {
"type": "string",
"description": "Fully-qualified signature for overload resolution",
},
"confidence_threshold": {
"type": "number",
"minimum": 0.0,
"maximum": 1.0,
"default": 0.7,
"description": "Filter speculative branches (0-1)",
},
"include_db": {
"type": "boolean",
"default": True,
"description": "Include database interactions in analysis",
},
"include_network": {
"type": "boolean",
"default": True,
"description": "Include network calls in analysis",
},
"include_fs": {
"type": "boolean",
"default": True,
"description": "Include filesystem operations in analysis",
},
"export_format": {
"type": "string",
"enum": ["markdown", "json", "plantuml"],
"default": "markdown",
"description": "Output format for analysis results",
},
"focus_areas": {
"type": "array",
"items": {"type": "string"},
"description": "Specific aspects to focus on",
},
"temperature": {
"type": "number",
"description": "Temperature (0-1, default 0.2 for analytical precision)",
"minimum": 0,
"maximum": 1,
},
"thinking_mode": {
"type": "string",
"enum": ["minimal", "low", "medium", "high", "max"],
"description": "Thinking depth: minimal (0.5% of model max), low (8%), medium (33%), high (67%), max (100% of model max)",
},
"use_websearch": {
"type": "boolean",
"description": "Enable web search for framework documentation and patterns",
"default": True,
},
"continuation_id": {
"type": "string",
"description": "Thread continuation ID for multi-turn conversations across tools",
},
},
"required": ["entry_point", "files"] + (["model"] if self.is_effective_auto_mode() else []),
}
return schema
def get_system_prompt(self) -> str:
return TRACEPATH_PROMPT
def get_default_temperature(self) -> float:
return TEMPERATURE_ANALYTICAL
# Line numbers are enabled by default for precise code references
def get_model_category(self):
"""TracePath requires extended reasoning for complex flow analysis"""
from tools.models import ToolModelCategory
return ToolModelCategory.EXTENDED_REASONING
def get_request_model(self):
return TracePathRequest
def detect_primary_language(self, file_paths: list[str]) -> str:
"""
Detect the primary programming language from file extensions.
Args:
file_paths: List of file paths to analyze
Returns:
str: Detected language or "mixed" if multiple languages found
"""
# Language detection based on file extensions
language_extensions = {
"python": {".py", ".pyx", ".pyi"},
"javascript": {".js", ".jsx", ".mjs", ".cjs"},
"typescript": {".ts", ".tsx", ".mts", ".cts"},
"java": {".java"},
"csharp": {".cs"},
"cpp": {".cpp", ".cc", ".cxx", ".c", ".h", ".hpp"},
"go": {".go"},
"rust": {".rs"},
"swift": {".swift"},
"kotlin": {".kt", ".kts"},
"ruby": {".rb"},
"php": {".php"},
"scala": {".scala"},
}
# Count files by language
language_counts = {}
for file_path in file_paths:
extension = os.path.splitext(file_path.lower())[1]
for lang, exts in language_extensions.items():
if extension in exts:
language_counts[lang] = language_counts.get(lang, 0) + 1
break
if not language_counts:
return "unknown"
# Return most common language, or "mixed" if multiple languages
max_count = max(language_counts.values())
dominant_languages = [lang for lang, count in language_counts.items() if count == max_count]
if len(dominant_languages) == 1:
return dominant_languages[0]
else:
return "mixed"
def parse_entry_point(self, entry_point: str, language: str) -> dict[str, str]:
"""
Parse entry point string to extract class/module and method/function information.
Args:
entry_point: Entry point string (e.g., "BookingManager::finalizeInvoice", "utils.validate_input")
language: Detected or specified programming language
Returns:
dict: Parsed entry point information
"""
result = {
"raw": entry_point,
"class_or_module": "",
"method_or_function": "",
"type": "unknown",
}
# Common patterns across languages
patterns = {
# Class::method (C++, PHP style)
"class_method_double_colon": r"^([A-Za-z_][A-Za-z0-9_]*?)::([A-Za-z_][A-Za-z0-9_]*?)$",
# Module.function or Class.method (Python, JavaScript, etc.)
"module_function_dot": r"^([A-Za-z_][A-Za-z0-9_]*?)\.([A-Za-z_][A-Za-z0-9_]*?)$",
# Nested module.submodule.function
"nested_module_dot": r"^([A-Za-z_][A-Za-z0-9_.]*?)\.([A-Za-z_][A-Za-z0-9_]*?)$",
# Just function name
"function_only": r"^([A-Za-z_][A-Za-z0-9_]*?)$",
}
# Try patterns in order of specificity
for pattern_name, pattern in patterns.items():
match = re.match(pattern, entry_point.strip())
if match:
if pattern_name == "function_only":
result["method_or_function"] = match.group(1)
result["type"] = "function"
else:
result["class_or_module"] = match.group(1)
result["method_or_function"] = match.group(2)
# Determine if it's a class method or module function based on naming conventions
if pattern_name == "class_method_double_colon":
result["type"] = "method"
elif result["class_or_module"][0].isupper():
result["type"] = "method" # Likely class method (CamelCase)
else:
result["type"] = "function" # Likely module function (snake_case)
break
logger.debug(f"[TRACEPATH] Parsed entry point '{entry_point}' as: {result}")
return result
async def _generate_structural_summary(self, files: list[str], language: str) -> str:
"""
Generate structural summary of the code using AST parsing.
Phase 1: Returns empty string (pure AI-driven approach)
Phase 2: Will contain language-specific AST parsing logic
Args:
files: List of file paths to analyze
language: Detected programming language
Returns:
str: Structural summary or empty string for Phase 1
"""
# Phase 1 implementation: Pure AI-driven approach
# Phase 2 will add AST parsing for enhanced context
if language == "python":
# Placeholder for Python AST parsing using built-in 'ast' module
# Will extract class definitions, method signatures, and direct calls
pass
elif language in ["javascript", "typescript"]:
# Placeholder for JavaScript/TypeScript parsing using acorn or TS compiler API
pass
elif language == "csharp":
# Placeholder for C# parsing using Microsoft Roslyn SDK
pass
elif language == "java":
# Placeholder for Java parsing (future implementation)
pass
# For Phase 1, return empty to rely on pure LLM analysis
logger.debug(f"[TRACEPATH] Phase 1: No structural summary generated for {language}")
return ""
async def prepare_prompt(self, request: TracePathRequest) -> str:
"""
Prepare the complete prompt for call path analysis.
This method combines:
- System prompt with analysis instructions
- User context and entry point information
- File contents with line numbers
- Structural summary (Phase 2)
- Analysis parameters and constraints
Args:
request: The validated tracepath request
Returns:
str: Complete prompt for the model
Raises:
ValueError: If the prompt exceeds token limits
"""
logger.info(
f"[TRACEPATH] Preparing prompt for entry point '{request.entry_point}' with {len(request.files)} files"
)
logger.debug(f"[TRACEPATH] Analysis depth: {request.analysis_depth}, Export format: {request.export_format}")
# Check for prompt.txt in files
prompt_content, updated_files = self.handle_prompt_file(request.files)
# If prompt.txt was found, incorporate it into the context
if prompt_content:
logger.debug("[TRACEPATH] Found prompt.txt file, incorporating content")
if request.context:
request.context = prompt_content + "\n\n" + request.context
else:
request.context = prompt_content
# Update request files list
if updated_files is not None:
logger.debug(f"[TRACEPATH] Updated files list after prompt.txt processing: {len(updated_files)} files")
request.files = updated_files
# Check user input size at MCP transport boundary (before adding internal content)
if request.context:
size_check = self.check_prompt_size(request.context)
if size_check:
from tools.models import ToolOutput
raise ValueError(f"MCP_SIZE_CHECK:{ToolOutput(**size_check).model_dump_json()}")
# Detect or use specified language
if request.language:
primary_language = request.language
logger.debug(f"[TRACEPATH] Using specified language: {primary_language}")
else:
primary_language = self.detect_primary_language(request.files)
logger.debug(f"[TRACEPATH] Detected primary language: {primary_language}")
# Parse entry point
entry_point_info = self.parse_entry_point(request.entry_point, primary_language)
logger.debug(f"[TRACEPATH] Entry point parsed as: {entry_point_info}")
# Generate structural summary (Phase 1: returns empty, Phase 2: AST analysis)
continuation_id = getattr(request, "continuation_id", None)
structural_summary = await self._generate_structural_summary(request.files, primary_language)
# Use centralized file processing logic for main code files (with line numbers enabled)
logger.debug(f"[TRACEPATH] Preparing {len(request.files)} code files for analysis")
code_content = self._prepare_file_content_for_prompt(request.files, continuation_id, "Code to analyze")
if code_content:
from utils.token_utils import estimate_tokens
code_tokens = estimate_tokens(code_content)
logger.info(f"[TRACEPATH] Code files embedded successfully: {code_tokens:,} tokens")
else:
logger.warning("[TRACEPATH] No code content after file processing")
# Build the complete prompt
prompt_parts = []
# Add system prompt
prompt_parts.append(self.get_system_prompt())
# Add structural summary if available (Phase 2)
if structural_summary:
prompt_parts.append("\n=== STRUCTURAL SUMMARY ===")
prompt_parts.append(structural_summary)
prompt_parts.append("=== END STRUCTURAL SUMMARY ===")
# Add user context and analysis parameters
prompt_parts.append("\n=== ANALYSIS REQUEST ===")
prompt_parts.append(f"Entry Point: {request.entry_point}")
if entry_point_info["type"] != "unknown":
prompt_parts.append(
f"Parsed as: {entry_point_info['type']} '{entry_point_info['method_or_function']}' in {entry_point_info['class_or_module'] or 'global scope'}"
)
prompt_parts.append(f"Language: {primary_language}")
prompt_parts.append(f"Analysis Depth: {request.analysis_depth}")
prompt_parts.append(f"Confidence Threshold: {request.confidence_threshold}")
if request.signature:
prompt_parts.append(f"Method Signature: {request.signature}")
if request.parameters:
prompt_parts.append(f"Parameter Values: {request.parameters}")
# Side effects configuration
side_effects = []
if request.include_db:
side_effects.append("database")
if request.include_network:
side_effects.append("network")
if request.include_fs:
side_effects.append("filesystem")
if side_effects:
prompt_parts.append(f"Include Side Effects: {', '.join(side_effects)}")
if request.focus_areas:
prompt_parts.append(f"Focus Areas: {', '.join(request.focus_areas)}")
if request.context:
prompt_parts.append(f"Additional Context: {request.context}")
prompt_parts.append(f"Export Format: {request.export_format}")
prompt_parts.append("=== END REQUEST ===")
# Add web search instruction if enabled
websearch_instruction = self.get_websearch_instruction(
request.use_websearch,
f"""When analyzing call paths for {primary_language} code, consider if searches for these would help:
- Framework-specific call patterns and lifecycle methods
- Language-specific dispatch mechanisms and polymorphism
- Common side-effect patterns for libraries used in the code
- Documentation for external APIs and services called
- Known design patterns that affect call flow""",
)
if websearch_instruction:
prompt_parts.append(websearch_instruction)
# Add main code to analyze
prompt_parts.append("\n=== CODE TO ANALYZE ===")
prompt_parts.append(code_content)
prompt_parts.append("=== END CODE ===")
# Add analysis instructions
analysis_instructions = [
f"\nPlease perform a {request.analysis_depth} static call path analysis for the entry point '{request.entry_point}'."
]
if request.parameters:
analysis_instructions.append(
"Pay special attention to how the provided parameter values affect the execution flow."
)
if request.confidence_threshold < 1.0:
analysis_instructions.append(
f"Filter out speculative paths with confidence below {request.confidence_threshold}."
)
analysis_instructions.append(f"Format the output as {request.export_format}.")
prompt_parts.extend(analysis_instructions)
full_prompt = "\n".join(prompt_parts)
# Log final prompt statistics
from utils.token_utils import estimate_tokens
total_tokens = estimate_tokens(full_prompt)
logger.info(f"[TRACEPATH] Complete prompt prepared: {total_tokens:,} tokens, {len(full_prompt):,} characters")
return full_prompt
def format_response(self, response: str, request: TracePathRequest, model_info: Optional[dict] = None) -> str:
"""
Format the call path analysis response.
The base tool handles structured response validation via SPECIAL_STATUS_MODELS,
so this method focuses on providing clear guidance for next steps.
Args:
response: The raw analysis from the model
request: The original request for context
model_info: Optional dict with model metadata
Returns:
str: The response with additional guidance
"""
logger.debug(f"[TRACEPATH] Formatting response for entry point '{request.entry_point}'")
# Get the friendly model name
model_name = "the model"
if model_info and model_info.get("model_response"):
model_name = model_info["model_response"].friendly_name or "the model"
# Add contextual footer based on analysis depth and format
if request.export_format == "json":
footer = f"""
---
**Analysis Complete**: {model_name} has provided a structured JSON analysis of the call path for `{request.entry_point}`.
**Next Steps**:
- Review the confidence levels for each predicted call
- Investigate any uncertain calls marked with low confidence
- Use this analysis for impact assessment, debugging, or architecture review
- For deeper analysis, increase analysis_depth to 'deep' or provide additional context files
"""
elif request.export_format == "plantuml":
footer = f"""
---
**Analysis Complete**: {model_name} has generated a PlantUML diagram showing the call path for `{request.entry_point}`.
**Next Steps**:
- Render the PlantUML diagram to visualize the call flow
- Review branching points and conditional logic
- Verify the predicted paths against your understanding of the code
- Use this for documentation or architectural discussions
"""
else: # markdown
footer = f"""
---
**Analysis Complete**: {model_name} has traced the execution path for `{request.entry_point}` at {request.analysis_depth} depth.
**Next Steps**:
- Review the call path summary and confidence assessments
- Pay attention to uncertain calls that may require runtime verification
- Use the code anchors (file:line references) to navigate to critical decision points
- Consider this analysis for debugging, impact assessment, or refactoring decisions
"""
return f"{response}{footer}"

456
tools/tracer.py Normal file
View File

@@ -0,0 +1,456 @@
"""
Tracer tool - Static call path prediction and control flow analysis
This tool analyzes code to predict and explain full call paths and control flow without executing code.
Given a method name, its owning class/module, and parameter combinations or runtime values, it predicts
the complete chain of method/function calls that would be triggered.
Key Features:
- Static call path prediction with confidence levels
- Polymorphism and dynamic dispatch analysis
- Value-driven flow analysis based on parameter combinations
- Side effects identification (database, network, filesystem)
- Branching analysis for conditional logic
- Hybrid AI-first approach with optional AST preprocessing for enhanced accuracy
"""
import logging
import os
from typing import Any, Literal, Optional
from pydantic import Field
from config import TEMPERATURE_ANALYTICAL
from systemprompts import TRACER_PROMPT
from .base import BaseTool, ToolRequest
logger = logging.getLogger(__name__)
class TracerRequest(ToolRequest):
"""
Request model for the tracer tool.
This model defines the simplified parameters for static code analysis.
"""
prompt: str = Field(
...,
description="Description of what to trace including method/function name and class/file context (e.g., 'Trace BookingManager::finalizeInvoice method' or 'Analyze dependencies for validate_input function in utils module')",
)
files: list[str] = Field(
...,
description="Code files or directories to analyze (must be absolute paths)",
)
trace_mode: Literal["precision", "dependencies"] = Field(
...,
description="Trace mode: 'precision' (follows actual code execution path from entry point) or 'dependencies' (analyzes bidirectional dependency mapping showing what calls this target and what it calls)",
)
class TracerTool(BaseTool):
"""
Tracer tool implementation.
This tool analyzes code to predict static call paths and control flow without execution.
Uses a hybrid AI-first approach with optional AST preprocessing for enhanced accuracy.
"""
def get_name(self) -> str:
return "tracer"
def get_description(self) -> str:
return (
"STATIC CODE ANALYSIS - Analyzes code to provide either execution flow traces or dependency mappings without executing code. "
"Type 'precision': Follows the actual code path from a specified method/function, resolving calls, branching, and side effects. "
"Type 'dependencies': Analyzes bidirectional dependencies showing what calls the target and what it calls, including imports and inheritance. "
"Perfect for: understanding complex code flows, impact analysis, debugging assistance, architecture review. "
"Responds in structured JSON format for easy parsing and visualization. "
"Choose thinking_mode based on code complexity: 'medium' for standard analysis (default), "
"'high' for complex systems, 'max' for legacy codebases requiring deep analysis. "
"Note: If you're not currently using a top-tier model such as Opus 4 or above, these tools can provide enhanced capabilities."
)
def get_input_schema(self) -> dict[str, Any]:
schema = {
"type": "object",
"properties": {
"prompt": {
"type": "string",
"description": "Description of what to trace including method/function name and class/file context (e.g., 'Trace BookingManager::finalizeInvoice method' or 'Analyze dependencies for validate_input function in utils module')",
},
"files": {
"type": "array",
"items": {"type": "string"},
"description": "Code files or directories to analyze (must be absolute paths)",
},
"trace_mode": {
"type": "string",
"enum": ["precision", "dependencies"],
"description": "Trace mode: 'precision' (follows actual code execution path from entry point) or 'dependencies' (analyzes bidirectional dependency mapping showing what calls this target and what it calls)",
},
"model": self.get_model_field_schema(),
"temperature": {
"type": "number",
"description": "Temperature (0-1, default 0.2 for analytical precision)",
"minimum": 0,
"maximum": 1,
},
"thinking_mode": {
"type": "string",
"enum": ["minimal", "low", "medium", "high", "max"],
"description": "Thinking depth: minimal (0.5% of model max), low (8%), medium (33%), high (67%), max (100% of model max)",
},
"use_websearch": {
"type": "boolean",
"description": "Enable web search for framework documentation and patterns",
"default": True,
},
"continuation_id": {
"type": "string",
"description": "Thread continuation ID for multi-turn conversations across tools",
},
},
"required": ["prompt", "files", "trace_mode"] + (["model"] if self.is_effective_auto_mode() else []),
}
return schema
def get_system_prompt(self) -> str:
return TRACER_PROMPT
def get_default_temperature(self) -> float:
return TEMPERATURE_ANALYTICAL
# Line numbers are enabled by default for precise code references
def get_model_category(self):
"""Tracer requires extended reasoning for complex flow analysis"""
from tools.models import ToolModelCategory
return ToolModelCategory.EXTENDED_REASONING
def get_request_model(self):
return TracerRequest
def detect_primary_language(self, file_paths: list[str]) -> str:
"""
Detect the primary programming language from file extensions.
Args:
file_paths: List of file paths to analyze
Returns:
str: Detected language or "mixed" if multiple languages found
"""
# Language detection based on file extensions
language_extensions = {
"python": {".py", ".pyx", ".pyi"},
"javascript": {".js", ".jsx", ".mjs", ".cjs"},
"typescript": {".ts", ".tsx", ".mts", ".cts"},
"java": {".java"},
"csharp": {".cs"},
"cpp": {".cpp", ".cc", ".cxx", ".c", ".h", ".hpp"},
"go": {".go"},
"rust": {".rs"},
"swift": {".swift"},
"kotlin": {".kt", ".kts"},
"ruby": {".rb"},
"php": {".php"},
"scala": {".scala"},
}
# Count files by language
language_counts = {}
for file_path in file_paths:
extension = os.path.splitext(file_path.lower())[1]
for lang, exts in language_extensions.items():
if extension in exts:
language_counts[lang] = language_counts.get(lang, 0) + 1
break
if not language_counts:
return "unknown"
# Return most common language, or "mixed" if multiple languages
max_count = max(language_counts.values())
dominant_languages = [lang for lang, count in language_counts.items() if count == max_count]
if len(dominant_languages) == 1:
return dominant_languages[0]
else:
return "mixed"
async def prepare_prompt(self, request: TracerRequest) -> str:
"""
Prepare the complete prompt for code analysis.
This method combines:
- System prompt with analysis instructions
- User request and trace type
- File contents with line numbers
- Analysis parameters
Args:
request: The validated tracer request
Returns:
str: Complete prompt for the model
Raises:
ValueError: If the prompt exceeds token limits
"""
logger.info(
f"[TRACER] Preparing prompt for {request.trace_mode} trace analysis with {len(request.files)} files"
)
logger.debug(f"[TRACER] User request: {request.prompt[:100]}...")
# Check for prompt.txt in files
prompt_content, updated_files = self.handle_prompt_file(request.files)
# If prompt.txt was found, incorporate it into the request prompt
if prompt_content:
logger.debug("[TRACER] Found prompt.txt file, incorporating content")
request.prompt = prompt_content + "\n\n" + request.prompt
# Update request files list
if updated_files is not None:
logger.debug(f"[TRACER] Updated files list after prompt.txt processing: {len(updated_files)} files")
request.files = updated_files
# Check user input size at MCP transport boundary (before adding internal content)
size_check = self.check_prompt_size(request.prompt)
if size_check:
from tools.models import ToolOutput
raise ValueError(f"MCP_SIZE_CHECK:{ToolOutput(**size_check).model_dump_json()}")
# Detect primary language
primary_language = self.detect_primary_language(request.files)
logger.debug(f"[TRACER] Detected primary language: {primary_language}")
# Use centralized file processing logic for main code files (with line numbers enabled)
continuation_id = getattr(request, "continuation_id", None)
logger.debug(f"[TRACER] Preparing {len(request.files)} code files for analysis")
code_content = self._prepare_file_content_for_prompt(request.files, continuation_id, "Code to analyze")
if code_content:
from utils.token_utils import estimate_tokens
code_tokens = estimate_tokens(code_content)
logger.info(f"[TRACER] Code files embedded successfully: {code_tokens:,} tokens")
else:
logger.warning("[TRACER] No code content after file processing")
# Build the complete prompt
prompt_parts = []
# Add system prompt
prompt_parts.append(self.get_system_prompt())
# Add user request and analysis parameters
prompt_parts.append("\n=== ANALYSIS REQUEST ===")
prompt_parts.append(f"User Request: {request.prompt}")
prompt_parts.append(f"Trace Mode: {request.trace_mode}")
prompt_parts.append(f"Language: {primary_language}")
prompt_parts.append("=== END REQUEST ===")
# Add web search instruction if enabled
websearch_instruction = self.get_websearch_instruction(
getattr(request, "use_websearch", True),
f"""When analyzing code for {primary_language}, consider if searches for these would help:
- Framework-specific call patterns and lifecycle methods
- Language-specific dispatch mechanisms and polymorphism
- Common side-effect patterns for libraries used in the code
- Documentation for external APIs and services called
- Known design patterns that affect call flow""",
)
if websearch_instruction:
prompt_parts.append(websearch_instruction)
# Add main code to analyze
prompt_parts.append("\n=== CODE TO ANALYZE ===")
prompt_parts.append(code_content)
prompt_parts.append("=== END CODE ===")
# Add analysis instructions
prompt_parts.append(f"\nPlease perform a {request.trace_mode} trace analysis based on the user request.")
full_prompt = "\n".join(prompt_parts)
# Log final prompt statistics
from utils.token_utils import estimate_tokens
total_tokens = estimate_tokens(full_prompt)
logger.info(f"[TRACER] Complete prompt prepared: {total_tokens:,} tokens, {len(full_prompt):,} characters")
return full_prompt
def format_response(self, response: str, request: TracerRequest, model_info: Optional[dict] = None) -> str:
"""
Format the code analysis response with mode-specific rendering instructions.
The base tool handles structured response validation via SPECIAL_STATUS_MODELS,
so this method focuses on providing clear rendering instructions for Claude.
Args:
response: The raw analysis from the model
request: The original request for context
model_info: Optional dict with model metadata
Returns:
str: The response with mode-specific rendering instructions
"""
logger.debug(f"[TRACER] Formatting response for {request.trace_mode} trace analysis")
# Get the friendly model name
model_name = "the model"
if model_info and model_info.get("model_response"):
model_name = model_info["model_response"].friendly_name or "the model"
# Base tool will handle trace_complete JSON responses via SPECIAL_STATUS_MODELS
# No need for manual JSON parsing here
# Generate mode-specific rendering instructions
rendering_instructions = self._get_rendering_instructions(request.trace_mode)
# Create the complete response with rendering instructions
footer = f"""
---
**Analysis Complete**: {model_name} has completed a {request.trace_mode} analysis as requested.
{rendering_instructions}
**GENERAL REQUIREMENTS:**
- Follow the rendering instructions EXACTLY as specified above
- Use only the data provided in the JSON response
- Maintain exact formatting for readability
- Include file paths and line numbers as provided
- Do not add explanations or commentary outside the specified format"""
return f"{response}{footer}"
def _get_rendering_instructions(self, trace_mode: str) -> str:
"""
Get mode-specific rendering instructions for Claude.
Args:
trace_mode: Either "precision" or "dependencies"
Returns:
str: Complete rendering instructions for the specified mode
"""
if trace_mode == "precision":
return self._get_precision_rendering_instructions()
else: # dependencies mode
return self._get_dependencies_rendering_instructions()
def _get_precision_rendering_instructions(self) -> str:
"""Get rendering instructions for precision trace mode."""
return """
## MANDATORY RENDERING INSTRUCTIONS FOR PRECISION TRACE
You MUST render the trace analysis in exactly two views:
### 1. CALL FLOW DIAGRAM (TOP-DOWN)
Use this exact format:
```
[Class::Method] (file: /path, line: ##)
[Class::CalledMethod] (file: /path, line: ##)
...
```
**Rules:**
- Chain each call using ↓ or → for readability
- Include file name and line number per method
- If the call is conditional, append `? if condition`
- If ambiguous, mark with `⚠️ ambiguous branch`
- Indent nested calls appropriately
### 2. BRANCHING & SIDE EFFECT TABLE
Render exactly this table format:
| Location | Condition | Branches | Ambiguous |
|----------|-----------|----------|-----------|
| /file/path:## | if condition | method1(), method2() | ✅/❌ |
**Side Effects section:**
```
Side Effects:
- [database] description (File.ext:##)
- [network] description (File.ext:##)
- [filesystem] description (File.ext:##)
```
**CRITICAL RULES:**
- ALWAYS render both views unless data is missing
- Use exact filenames, class names, and line numbers from JSON
- DO NOT invent function names or examples
- Mark ambiguous branches with ⚠️ or ✅
- If sections are empty, omit them cleanly"""
def _get_dependencies_rendering_instructions(self) -> str:
"""Get rendering instructions for dependencies trace mode."""
return """
## MANDATORY RENDERING INSTRUCTIONS FOR DEPENDENCIES TRACE
You MUST render the trace analysis in exactly two views:
### 1. DEPENDENCY FLOW GRAPH
Use this exact format:
**Incoming:**
```
Called by:
- [CallerClass::callerMethod] ← /path/file.ext:##
- [ServiceImpl::run] ← /path/file.ext:##
```
**Outgoing:**
```
Calls:
- [Logger::logAction] → /utils/log.ext:##
- [PaymentClient::send] → /clients/pay.ext:##
```
**Type Dependencies:**
```
- conforms_to: ProtocolName
- implements: InterfaceName
- imports: ModuleName, LibraryName
```
**State Access:**
```
- reads: property.name (line ##)
- writes: object.field (line ##)
```
**Arrow Rules:**
- `←` for incoming (who calls this)
- `→` for outgoing (what this calls)
### 2. DEPENDENCY TABLE
Render exactly this table format:
| Type | From/To | Method | File | Line |
|------|---------|--------|------|------|
| direct_call | From: CallerClass | callerMethod | /path/file.ext | ## |
| method_call | To: TargetClass | targetMethod | /path/file.ext | ## |
| uses_property | To: ObjectClass | .propertyName | /path/file.ext | ## |
| conforms_to | Self: ThisClass | — | /path/file.ext | — |
**CRITICAL RULES:**
- ALWAYS render both views unless data is missing
- Use exact filenames, class names, and line numbers from JSON
- DO NOT invent function names or examples
- If sections (state access, type dependencies) are empty, omit them cleanly
- Show directional dependencies with proper arrows"""