Implement TracePath tool for static call path analysis

Add comprehensive TracePath tool that predicts and explains full call paths
and control flow without executing code. Features include:

**Core Functionality:**
- Static call path prediction with confidence levels (🟢🟡🔴)
- Multi-language support (Python, JavaScript, TypeScript, C#, Java)
- Value-driven flow analysis based on parameter combinations
- Side effects identification (database, network, filesystem)
- Polymorphism and dynamic dispatch analysis
- Entry point parsing for multiple syntax patterns

**Technical Implementation:**
- Hybrid AI-first architecture (Phase 1: pure AI, Phase 2: AST enhancement)
- Export formats: Markdown, JSON, PlantUML
- Confidence threshold filtering for speculative branches
- Integration with existing tool ecosystem and conversation threading
- Comprehensive error handling and token management

**Files Added:**
- tools/tracepath.py - Main tool implementation
- systemprompts/tracepath_prompt.py - System prompt for analysis
- tests/test_tracepath.py - Comprehensive unit tests (32 tests)

**Files Modified:**
- server.py - Tool registration
- tools/__init__.py - Tool exports
- systemprompts/__init__.py - Prompt exports

**Quality Assurance:**
- All 449 unit tests pass including 32 new TracePath tests
- Full linting and formatting compliance
- Follows established project patterns and conventions
- Multi-model validation with O3 and Gemini Pro insights

**Usage Examples:**
- "Use zen tracepath to analyze BookingManager::finalizeInvoice(invoiceId: 123)"
- "Trace payment.process_payment() with confidence levels and side effects"

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Fahad
2025-06-15 14:07:23 +04:00
parent 6304b7af6b
commit 3bc7956239
6 changed files with 1173 additions and 0 deletions

View File

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

View File

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

View File

@@ -0,0 +1,155 @@
"""
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.
"""

410
tests/test_tracepath.py Normal file
View File

@@ -0,0 +1,410 @@
"""
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")

View File

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

602
tools/tracepath.py Normal file
View File

@@ -0,0 +1,602 @@
"""
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}"