""" Debug Issue tool - Root cause analysis and debugging assistance with systematic investigation """ import json import logging from typing import TYPE_CHECKING, Any, Optional from pydantic import Field if TYPE_CHECKING: from tools.models import ToolModelCategory from config import TEMPERATURE_ANALYTICAL from systemprompts import DEBUG_ISSUE_PROMPT from .base import BaseTool, ToolRequest logger = logging.getLogger(__name__) # Field descriptions for the investigation steps DEBUG_INVESTIGATION_FIELD_DESCRIPTIONS = { "step": ( "Your current investigation step. For the first step, describe the issue/error to investigate. " "For subsequent steps, describe what you're investigating, what code you're examining, " "what patterns you're looking for, or what hypothesis you're testing." ), "step_number": "Current step number in the investigation sequence (starts at 1)", "total_steps": "Current estimate of total investigation steps needed (can be adjusted as investigation progresses)", "next_step_required": "Whether another investigation step is required", "findings": ( "Current findings from this investigation step. Include code patterns discovered, " "potential causes identified, hypotheses formed, or evidence gathered." ), "files_checked": ( "List of files you've examined so far in the investigation (cumulative list). " "Include all files you've looked at, even if they turned out to be irrelevant." ), "relevant_files": ( "List of files that are definitely related to the issue (subset of files_checked). " "Only include files that contain code directly related to the problem." ), "relevant_methods": ( "List of specific methods/functions that are involved in the issue. " "Format: 'ClassName.methodName' or 'functionName'" ), "hypothesis": ( "Your current working hypothesis about the root cause. This can be updated/revised " "as the investigation progresses." ), "confidence": "Your confidence level in the current hypothesis: 'low', 'medium', or 'high'", "backtrack_from_step": "If you need to revise a previous finding, which step number to backtrack from", "continuation_id": "Thread continuation ID for multi-turn investigation sessions", "images": ( "Optional images showing error screens, UI issues, logs displays, or visual debugging information " "that help understand the issue (must be FULL absolute paths - DO NOT SHORTEN)" ), } # Field descriptions for the final debug request DEBUG_FIELD_DESCRIPTIONS = { "initial_issue": "The original issue description that started the investigation", "investigation_summary": "Complete summary of the systematic investigation performed", "findings": "Consolidated findings from all investigation steps", "files": "Essential files identified during investigation (must be FULL absolute paths - DO NOT SHORTEN)", "error_context": "Stack trace, logs, or error context discovered during investigation", "relevant_methods": "List of methods/functions identified as involved in the issue", "hypothesis": "Final hypothesis about the root cause after investigation", "images": "Optional images showing error screens, UI issues, or visual debugging information", } class DebugInvestigationRequest(ToolRequest): """Request model for debug investigation steps""" # Required fields for each investigation step step: str = Field(..., description=DEBUG_INVESTIGATION_FIELD_DESCRIPTIONS["step"]) step_number: int = Field(..., description=DEBUG_INVESTIGATION_FIELD_DESCRIPTIONS["step_number"]) total_steps: int = Field(..., description=DEBUG_INVESTIGATION_FIELD_DESCRIPTIONS["total_steps"]) next_step_required: bool = Field(..., description=DEBUG_INVESTIGATION_FIELD_DESCRIPTIONS["next_step_required"]) # Investigation tracking fields findings: str = Field(..., description=DEBUG_INVESTIGATION_FIELD_DESCRIPTIONS["findings"]) files_checked: list[str] = Field( default_factory=list, description=DEBUG_INVESTIGATION_FIELD_DESCRIPTIONS["files_checked"] ) relevant_files: list[str] = Field( default_factory=list, description=DEBUG_INVESTIGATION_FIELD_DESCRIPTIONS["relevant_files"] ) relevant_methods: list[str] = Field( default_factory=list, description=DEBUG_INVESTIGATION_FIELD_DESCRIPTIONS["relevant_methods"] ) hypothesis: Optional[str] = Field(None, description=DEBUG_INVESTIGATION_FIELD_DESCRIPTIONS["hypothesis"]) confidence: Optional[str] = Field("low", description=DEBUG_INVESTIGATION_FIELD_DESCRIPTIONS["confidence"]) # Optional backtracking field backtrack_from_step: Optional[int] = Field( None, description=DEBUG_INVESTIGATION_FIELD_DESCRIPTIONS["backtrack_from_step"] ) # Optional continuation field continuation_id: Optional[str] = Field(None, description=DEBUG_INVESTIGATION_FIELD_DESCRIPTIONS["continuation_id"]) # Optional images for visual debugging images: Optional[list[str]] = Field(default=None, description=DEBUG_INVESTIGATION_FIELD_DESCRIPTIONS["images"]) # Override inherited fields to exclude them model: Optional[str] = Field(default=None, exclude=True) temperature: Optional[float] = Field(default=None, exclude=True) thinking_mode: Optional[str] = Field(default=None, exclude=True) use_websearch: Optional[bool] = Field(default=None, exclude=True) class DebugIssueTool(BaseTool): """Advanced debugging tool with systematic self-investigation""" def __init__(self): super().__init__() self.investigation_history = [] self.consolidated_findings = { "files_checked": set(), "relevant_files": set(), "relevant_methods": set(), "findings": [], "hypotheses": [], "images": [], } def get_name(self) -> str: return "debug" def get_description(self) -> str: return ( "DEBUG & ROOT CAUSE ANALYSIS - Systematic self-investigation followed by expert analysis. " "This tool guides you through a step-by-step investigation process where you:\n\n" "1. Start with step 1: describe the issue to investigate\n" "2. Continue with investigation steps: examine code, trace errors, test hypotheses\n" "3. Track findings, relevant files, and methods throughout\n" "4. Update hypotheses as understanding evolves\n" "5. Backtrack and revise findings when needed\n" "6. Once investigation is complete, receive expert analysis\n\n" "The tool enforces systematic investigation methodology:\n" "- Methodical code examination and evidence collection\n" "- Hypothesis formation and validation\n" "- File and method tracking for context\n" "- Confidence assessment and revision capabilities\n\n" "Perfect for: complex bugs, mysterious errors, performance issues, " "race conditions, memory leaks, integration problems." ) def get_input_schema(self) -> dict[str, Any]: schema = { "type": "object", "properties": { # Investigation step fields "step": { "type": "string", "description": DEBUG_INVESTIGATION_FIELD_DESCRIPTIONS["step"], }, "step_number": { "type": "integer", "description": DEBUG_INVESTIGATION_FIELD_DESCRIPTIONS["step_number"], "minimum": 1, }, "total_steps": { "type": "integer", "description": DEBUG_INVESTIGATION_FIELD_DESCRIPTIONS["total_steps"], "minimum": 1, }, "next_step_required": { "type": "boolean", "description": DEBUG_INVESTIGATION_FIELD_DESCRIPTIONS["next_step_required"], }, "findings": { "type": "string", "description": DEBUG_INVESTIGATION_FIELD_DESCRIPTIONS["findings"], }, "files_checked": { "type": "array", "items": {"type": "string"}, "description": DEBUG_INVESTIGATION_FIELD_DESCRIPTIONS["files_checked"], }, "relevant_files": { "type": "array", "items": {"type": "string"}, "description": DEBUG_INVESTIGATION_FIELD_DESCRIPTIONS["relevant_files"], }, "relevant_methods": { "type": "array", "items": {"type": "string"}, "description": DEBUG_INVESTIGATION_FIELD_DESCRIPTIONS["relevant_methods"], }, "hypothesis": { "type": "string", "description": DEBUG_INVESTIGATION_FIELD_DESCRIPTIONS["hypothesis"], }, "confidence": { "type": "string", "enum": ["low", "medium", "high"], "description": DEBUG_INVESTIGATION_FIELD_DESCRIPTIONS["confidence"], }, "backtrack_from_step": { "type": "integer", "description": DEBUG_INVESTIGATION_FIELD_DESCRIPTIONS["backtrack_from_step"], "minimum": 1, }, "continuation_id": { "type": "string", "description": DEBUG_INVESTIGATION_FIELD_DESCRIPTIONS["continuation_id"], }, "images": { "type": "array", "items": {"type": "string"}, "description": DEBUG_INVESTIGATION_FIELD_DESCRIPTIONS["images"], }, }, # Required fields for investigation "required": ["step", "step_number", "total_steps", "next_step_required", "findings"], } return schema def get_system_prompt(self) -> str: return DEBUG_ISSUE_PROMPT def get_default_temperature(self) -> float: return TEMPERATURE_ANALYTICAL def get_model_category(self) -> "ToolModelCategory": """Debug requires deep analysis and reasoning""" from tools.models import ToolModelCategory return ToolModelCategory.EXTENDED_REASONING def get_request_model(self): return DebugInvestigationRequest def requires_model(self) -> bool: """ Debug tool manages its own model interactions. It doesn't need model during investigation steps, only for final analysis. """ return False async def execute(self, arguments: dict[str, Any]) -> list: """ Override execute to implement self-investigation pattern. Investigation Flow: 1. Claude calls debug with investigation steps 2. Tool tracks findings, files, methods progressively 3. Once investigation is complete, tool calls AI model for expert analysis 4. Returns structured response combining investigation + expert analysis """ from mcp.types import TextContent from utils.conversation_memory import add_turn, create_thread try: # Validate request request = DebugInvestigationRequest(**arguments) # Adjust total steps if needed if request.step_number > request.total_steps: request.total_steps = request.step_number # Handle continuation continuation_id = request.continuation_id # Create thread for first step if not continuation_id and request.step_number == 1: continuation_id = create_thread("debug", arguments) # Store initial issue description self.initial_issue = request.step # Handle backtracking first if requested if request.backtrack_from_step: # Remove findings after the backtrack point self.investigation_history = [ s for s in self.investigation_history if s["step_number"] < request.backtrack_from_step ] # Reprocess consolidated findings to match truncated history self._reprocess_consolidated_findings() # Log if step number needs correction expected_step_number = len(self.investigation_history) + 1 if request.step_number != expected_step_number: logger.debug( f"Step number adjusted from {request.step_number} to {expected_step_number} after backtracking" ) # Process investigation step step_data = { "step": request.step, "step_number": request.step_number, "findings": request.findings, "files_checked": request.files_checked, "relevant_files": request.relevant_files, "relevant_methods": request.relevant_methods, "hypothesis": request.hypothesis, "confidence": request.confidence, "images": request.images, } # Store in history self.investigation_history.append(step_data) # Update consolidated findings self.consolidated_findings["files_checked"].update(request.files_checked) self.consolidated_findings["relevant_files"].update(request.relevant_files) self.consolidated_findings["relevant_methods"].update(request.relevant_methods) self.consolidated_findings["findings"].append(f"Step {request.step_number}: {request.findings}") if request.hypothesis: self.consolidated_findings["hypotheses"].append( {"step": request.step_number, "hypothesis": request.hypothesis, "confidence": request.confidence} ) if request.images: self.consolidated_findings["images"].extend(request.images) # Build response response_data = { "status": "investigation_in_progress", "step_number": request.step_number, "total_steps": request.total_steps, "next_step_required": request.next_step_required, "investigation_status": { "files_checked": len(self.consolidated_findings["files_checked"]), "relevant_files": len(self.consolidated_findings["relevant_files"]), "relevant_methods": len(self.consolidated_findings["relevant_methods"]), "hypotheses_formed": len(self.consolidated_findings["hypotheses"]), "images_collected": len(set(self.consolidated_findings["images"])), "current_confidence": request.confidence, }, "output": { "instructions": "Continue systematic investigation. Present findings clearly and proceed to next step if required.", "format": "systematic_investigation", }, } if continuation_id: response_data["continuation_id"] = continuation_id # If investigation is complete, call the AI model for expert analysis if not request.next_step_required: response_data["status"] = "calling_expert_analysis" response_data["investigation_complete"] = True # Prepare consolidated investigation summary investigation_summary = self._prepare_investigation_summary() # Call the AI model with full context expert_analysis = await self._call_expert_analysis( initial_issue=getattr(self, "initial_issue", request.step), investigation_summary=investigation_summary, relevant_files=list(self.consolidated_findings["relevant_files"]), relevant_methods=list(self.consolidated_findings["relevant_methods"]), final_hypothesis=request.hypothesis, error_context=self._extract_error_context(), images=list(set(self.consolidated_findings["images"])), # Unique images model_info=arguments.get("_model_context"), model_override=arguments.get("model"), # Pass model selection from final step ) # Combine investigation and expert analysis response_data["expert_analysis"] = expert_analysis response_data["complete_investigation"] = { "initial_issue": getattr(self, "initial_issue", request.step), "steps_taken": len(self.investigation_history), "files_examined": list(self.consolidated_findings["files_checked"]), "relevant_files": list(self.consolidated_findings["relevant_files"]), "relevant_methods": list(self.consolidated_findings["relevant_methods"]), "investigation_summary": investigation_summary, } response_data["next_steps"] = ( "Investigation complete with expert analysis. Present the findings, hypotheses, " "and recommended fixes to the user. Focus on the most likely root cause and " "provide actionable implementation guidance." ) else: response_data["next_steps"] = ( f"Continue investigation with step {request.step_number + 1}. " f"Focus on: examining relevant code, testing hypotheses, gathering evidence." ) # Store in conversation memory if continuation_id: add_turn( thread_id=continuation_id, role="assistant", content=json.dumps(response_data, indent=2), tool_name="debug", files=list(self.consolidated_findings["relevant_files"]), images=request.images, ) return [TextContent(type="text", text=json.dumps(response_data, indent=2))] except Exception as e: logger.error(f"Error in debug investigation: {e}", exc_info=True) error_data = { "status": "investigation_failed", "error": str(e), "step_number": arguments.get("step_number", 0), } return [TextContent(type="text", text=json.dumps(error_data, indent=2))] def _reprocess_consolidated_findings(self): """Reprocess consolidated findings after backtracking""" self.consolidated_findings = { "files_checked": set(), "relevant_files": set(), "relevant_methods": set(), "findings": [], "hypotheses": [], "images": [], } for step in self.investigation_history: self.consolidated_findings["files_checked"].update(step.get("files_checked", [])) self.consolidated_findings["relevant_files"].update(step.get("relevant_files", [])) self.consolidated_findings["relevant_methods"].update(step.get("relevant_methods", [])) self.consolidated_findings["findings"].append(f"Step {step['step_number']}: {step['findings']}") if step.get("hypothesis"): self.consolidated_findings["hypotheses"].append( { "step": step["step_number"], "hypothesis": step["hypothesis"], "confidence": step.get("confidence", "low"), } ) if step.get("images"): self.consolidated_findings["images"].extend(step["images"]) def _prepare_investigation_summary(self) -> str: """Prepare a comprehensive summary of the investigation""" summary_parts = [ "=== SYSTEMATIC INVESTIGATION SUMMARY ===", f"Total steps: {len(self.investigation_history)}", f"Files examined: {len(self.consolidated_findings['files_checked'])}", f"Relevant files identified: {len(self.consolidated_findings['relevant_files'])}", f"Methods/functions involved: {len(self.consolidated_findings['relevant_methods'])}", "", "=== INVESTIGATION PROGRESSION ===", ] for finding in self.consolidated_findings["findings"]: summary_parts.append(finding) if self.consolidated_findings["hypotheses"]: summary_parts.extend( [ "", "=== HYPOTHESIS EVOLUTION ===", ] ) for hyp in self.consolidated_findings["hypotheses"]: summary_parts.append(f"Step {hyp['step']} ({hyp['confidence']} confidence): {hyp['hypothesis']}") return "\n".join(summary_parts) def _extract_error_context(self) -> Optional[str]: """Extract error context from investigation findings""" error_patterns = ["error", "exception", "stack trace", "traceback", "failure"] error_context_parts = [] for finding in self.consolidated_findings["findings"]: if any(pattern in finding.lower() for pattern in error_patterns): error_context_parts.append(finding) return "\n".join(error_context_parts) if error_context_parts else None async def _call_expert_analysis( self, initial_issue: str, investigation_summary: str, relevant_files: list[str], relevant_methods: list[str], final_hypothesis: Optional[str], error_context: Optional[str], images: list[str], model_info: Optional[Any] = None, model_override: Optional[str] = None, ) -> dict: """Call AI model for expert analysis of the investigation""" # Prepare the debug prompt with all investigation context prompt_parts = [ f"=== ISSUE DESCRIPTION ===\n{initial_issue}\n=== END DESCRIPTION ===", f"\n=== CLAUDE'S INVESTIGATION FINDINGS ===\n{investigation_summary}\n=== END FINDINGS ===", ] if error_context: prompt_parts.append(f"\n=== ERROR CONTEXT/STACK TRACE ===\n{error_context}\n=== END CONTEXT ===") if relevant_methods: prompt_parts.append( "\n=== RELEVANT METHODS/FUNCTIONS ===\n" + "\n".join(f"- {method}" for method in relevant_methods) + "\n=== END METHODS ===" ) if final_hypothesis: prompt_parts.append(f"\n=== FINAL HYPOTHESIS ===\n{final_hypothesis}\n=== END HYPOTHESIS ===") if images: prompt_parts.append( "\n=== VISUAL DEBUGGING INFORMATION ===\n" + "\n".join(f"- {img}" for img in images) + "\n=== END VISUAL INFORMATION ===" ) # Add file content if we have relevant files if relevant_files: file_content, _ = self._prepare_file_content_for_prompt(relevant_files, None, "Essential debugging files") if file_content: prompt_parts.append( f"\n=== ESSENTIAL FILES FOR DEBUGGING ===\n{file_content}\n=== END ESSENTIAL FILES ===" ) full_prompt = "\n".join(prompt_parts) # Get appropriate model and provider from config import DEFAULT_MODEL from providers.registry import ModelProviderRegistry model_name = model_override or DEFAULT_MODEL # Use override if provided provider = ModelProviderRegistry.get_provider_for_model(model_name) if not provider: return {"error": f"No provider available for model {model_name}", "status": "provider_error"} # Generate AI response try: full_analysis_prompt = f"{self.get_system_prompt()}\n\n{full_prompt}\n\nPlease debug this issue following the structured format in the system prompt." # Prepare generation kwargs generation_kwargs = { "prompt": full_analysis_prompt, "model_name": model_name, "system_prompt": "", # Already included in prompt "temperature": self.get_default_temperature(), "thinking_mode": "high", # High thinking for debug analysis } # Add images if available if images: generation_kwargs["images"] = images model_response = provider.generate_content(**generation_kwargs) if model_response.content: # Try to parse as JSON try: analysis_result = json.loads(model_response.content.strip()) return analysis_result except json.JSONDecodeError: # Return as text if not valid JSON return { "status": "analysis_complete", "raw_analysis": model_response.content, "parse_error": "Response was not valid JSON", } else: return {"error": "No response from model", "status": "empty_response"} except Exception as e: logger.error(f"Error calling expert analysis: {e}", exc_info=True) return {"error": str(e), "status": "analysis_error"} # Stub implementations for base class requirements async def prepare_prompt(self, request) -> str: return "" # Not used - execute() is overridden def format_response(self, response: str, request, model_info: dict = None) -> str: return response # Not used - execute() is overridden