WIP
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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.
|
|
||||||
"""
|
|
||||||
169
systemprompts/tracer_prompt.py
Normal file
169
systemprompts/tracer_prompt.py
Normal 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.
|
||||||
|
"""
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
420
tests/test_tracer.py
Normal 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
|
||||||
@@ -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",
|
||||||
]
|
]
|
||||||
|
|||||||
125
tools/models.py
125
tools/models.py
@@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
456
tools/tracer.py
Normal 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"""
|
||||||
Reference in New Issue
Block a user