agcodex_core/subagents/built_in/
debugger.rs

1//! Debugger Agent - Deep debugging and root cause analysis
2//!
3//! This agent performs comprehensive debugging analysis:
4//! - Trace execution paths
5//! - Identify error patterns
6//! - Analyze stack traces
7//! - Find race conditions
8//! - Detect memory leaks
9
10use crate::code_tools::ast_agent_tools::ASTAgentTools;
11use crate::code_tools::ast_agent_tools::AgentToolOp;
12use crate::code_tools::ast_agent_tools::AgentToolResult;
13use crate::modes::OperatingMode;
14use crate::subagents::AgentResult;
15use crate::subagents::AgentStatus;
16use crate::subagents::Finding;
17use crate::subagents::Severity;
18use crate::subagents::Subagent;
19use crate::subagents::SubagentContext;
20use crate::subagents::SubagentError;
21use crate::subagents::SubagentResult;
22use std::collections::HashMap;
23use std::future::Future;
24use std::path::Path;
25use std::path::PathBuf;
26use std::pin::Pin;
27use std::sync::Arc;
28use std::sync::atomic::AtomicBool;
29use std::sync::atomic::Ordering;
30use std::time::Duration;
31use std::time::SystemTime;
32
33/// Debugger Agent implementation
34#[derive(Debug)]
35pub struct DebuggerAgent {
36    name: String,
37    description: String,
38    _mode_override: Option<OperatingMode>,
39    _tool_permissions: Vec<String>,
40    _prompt_template: String,
41    debug_depth: DebugDepth,
42}
43
44/// Debug analysis depth
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum DebugDepth {
47    Surface,  // Quick error check
48    Standard, // Normal debugging
49    Deep,     // Comprehensive analysis with execution tracing
50}
51
52impl Default for DebuggerAgent {
53    fn default() -> Self {
54        Self::new()
55    }
56}
57
58impl DebuggerAgent {
59    /// Create a new debugger agent
60    pub fn new() -> Self {
61        Self {
62            name: "debugger".to_string(),
63            description: "Deep debugging analysis and root cause identification".to_string(),
64            _mode_override: Some(OperatingMode::Review),
65            _tool_permissions: vec![
66                "search".to_string(),
67                "tree".to_string(),
68                "grep".to_string(),
69                "bash".to_string(),
70                "think".to_string(),
71            ],
72            _prompt_template: r#"
73You are an expert debugger specializing in:
74- Root cause analysis
75- Execution path tracing
76- Error pattern recognition
77- Race condition detection
78- Memory leak identification
79- Performance bottleneck analysis
80
81Focus on finding the root cause, not just symptoms.
82Provide clear reproduction steps and fixes.
83"#
84            .to_string(),
85            debug_depth: DebugDepth::Standard,
86        }
87    }
88
89    /// Set debug depth
90    pub const fn with_depth(mut self, depth: DebugDepth) -> Self {
91        self.debug_depth = depth;
92        self
93    }
94
95    /// Analyze error patterns
96    async fn analyze_error_patterns(
97        &self,
98        ast_tools: &mut ASTAgentTools,
99        file: &Path,
100    ) -> Vec<Finding> {
101        let mut findings = Vec::new();
102
103        // Check for unhandled errors
104        if let Ok(AgentToolResult::Patterns(patterns)) =
105            ast_tools.execute(AgentToolOp::FindPatterns {
106                pattern_type: crate::code_tools::ast_agent_tools::PatternType::UnhandledError,
107                scope: crate::code_tools::search::SearchScope::Files(vec![file.to_path_buf()]),
108            })
109        {
110            for pattern in patterns {
111                findings.push(Finding {
112                    category: "error-handling".to_string(),
113                    severity: Severity::High,
114                    title: "Unhandled Error Detected".to_string(),
115                    description: format!(
116                        "Found unhandled error at {}:{}. This could cause crashes or data loss.",
117                        pattern.location.file.display(),
118                        pattern.location.line
119                    ),
120                    location: Some(pattern.location),
121                    suggestion: Some("Add proper error handling with recovery logic".to_string()),
122                    metadata: HashMap::from([(
123                        "debug_type".to_string(),
124                        serde_json::json!("unhandled_error"),
125                    )]),
126                });
127            }
128        }
129
130        // Check for race conditions
131        if let Ok(AgentToolResult::Patterns(patterns)) =
132            ast_tools.execute(AgentToolOp::FindPatterns {
133                pattern_type: crate::code_tools::ast_agent_tools::PatternType::RaceCondition,
134                scope: crate::code_tools::search::SearchScope::Files(vec![file.to_path_buf()]),
135            })
136        {
137            for pattern in patterns {
138                findings.push(Finding {
139                    category: "concurrency".to_string(),
140                    severity: Severity::Critical,
141                    title: "Potential Race Condition".to_string(),
142                    description: format!(
143                        "Found potential race condition at {}:{}. Shared state accessed without synchronization.",
144                        pattern.location.file.display(),
145                        pattern.location.line
146                    ),
147                    location: Some(pattern.location),
148                    suggestion: Some("Use proper synchronization primitives (mutex, channels, etc.)".to_string()),
149                    metadata: HashMap::from([
150                        ("debug_type".to_string(), serde_json::json!("race_condition")),
151                    ]),
152                });
153            }
154        }
155
156        findings
157    }
158
159    /// Trace execution paths
160    async fn trace_execution_paths(
161        &self,
162        ast_tools: &mut ASTAgentTools,
163        entry_point: &str,
164    ) -> Vec<Finding> {
165        let mut findings = Vec::new();
166
167        // Analyze call graph from entry point
168        if let Ok(AgentToolResult::CallGraph(graph)) =
169            ast_tools.execute(AgentToolOp::AnalyzeCallGraph {
170                entry_point: entry_point.to_string(),
171            })
172        {
173            // Check for infinite recursion
174            for cycle in graph.cycles {
175                findings.push(Finding {
176                    category: "logic-error".to_string(),
177                    severity: Severity::Critical,
178                    title: "Infinite Recursion Detected".to_string(),
179                    description: format!("Found recursive cycle: {}", cycle.join(" -> ")),
180                    location: None,
181                    suggestion: Some("Add base case or termination condition".to_string()),
182                    metadata: HashMap::from([
183                        (
184                            "debug_type".to_string(),
185                            serde_json::json!("infinite_recursion"),
186                        ),
187                        ("cycle".to_string(), serde_json::json!(cycle)),
188                    ]),
189                });
190            }
191
192            // Check for unreachable code
193            for unreachable in graph.unreachable_functions {
194                findings.push(Finding {
195                    category: "dead-code".to_string(),
196                    severity: Severity::Low,
197                    title: format!("Unreachable Code: {}", unreachable),
198                    description: format!(
199                        "Function '{}' is never called from any entry point",
200                        unreachable
201                    ),
202                    location: None,
203                    suggestion: Some("Remove dead code or add proper entry points".to_string()),
204                    metadata: HashMap::from([
205                        (
206                            "debug_type".to_string(),
207                            serde_json::json!("unreachable_code"),
208                        ),
209                        ("function".to_string(), serde_json::json!(unreachable)),
210                    ]),
211                });
212            }
213        }
214
215        findings
216    }
217
218    /// Analyze memory issues
219    async fn analyze_memory_issues(
220        &self,
221        ast_tools: &mut ASTAgentTools,
222        file: &Path,
223    ) -> Vec<Finding> {
224        let mut findings = Vec::new();
225
226        // Check for memory leaks
227        if let Ok(AgentToolResult::Patterns(patterns)) =
228            ast_tools.execute(AgentToolOp::FindPatterns {
229                pattern_type: crate::code_tools::ast_agent_tools::PatternType::MemoryLeak,
230                scope: crate::code_tools::search::SearchScope::Files(vec![file.to_path_buf()]),
231            })
232        {
233            for pattern in patterns {
234                findings.push(Finding {
235                    category: "memory".to_string(),
236                    severity: Severity::High,
237                    title: "Potential Memory Leak".to_string(),
238                    description: format!(
239                        "Found potential memory leak at {}:{}. Resource allocated but not freed.",
240                        pattern.location.file.display(),
241                        pattern.location.line
242                    ),
243                    location: Some(pattern.location),
244                    suggestion: Some(
245                        "Ensure all allocated resources are properly freed".to_string(),
246                    ),
247                    metadata: HashMap::from([(
248                        "debug_type".to_string(),
249                        serde_json::json!("memory_leak"),
250                    )]),
251                });
252            }
253        }
254
255        findings
256    }
257}
258
259impl Subagent for DebuggerAgent {
260    fn name(&self) -> &str {
261        &self.name
262    }
263
264    fn description(&self) -> &str {
265        &self.description
266    }
267
268    fn execute<'a>(
269        &'a self,
270        context: &'a SubagentContext,
271        ast_tools: &'a mut ASTAgentTools,
272        cancel_flag: Arc<AtomicBool>,
273    ) -> Pin<Box<dyn Future<Output = SubagentResult<AgentResult>> + Send + 'a>> {
274        Box::pin(async move {
275            let start_time = SystemTime::now();
276            let mut all_findings = Vec::new();
277            let mut analyzed_files = Vec::new();
278
279            // Get debug targets from context
280            let files = self.get_debug_targets(context)?;
281            let entry_point = context
282                .parameters
283                .get("entry_point")
284                .map(|s| s.as_str())
285                .unwrap_or("main");
286
287            for file in &files {
288                if cancel_flag.load(Ordering::Acquire) {
289                    return Err(SubagentError::ExecutionFailed(
290                        "Debugging cancelled".to_string(),
291                    ));
292                }
293
294                analyzed_files.push(file.clone());
295
296                match self.debug_depth {
297                    DebugDepth::Surface => {
298                        // Quick error pattern check
299                        let error_findings = self.analyze_error_patterns(ast_tools, file).await;
300                        all_findings.extend(error_findings);
301                    }
302                    DebugDepth::Standard => {
303                        // Standard debugging
304                        let error_findings = self.analyze_error_patterns(ast_tools, file).await;
305                        let memory_findings = self.analyze_memory_issues(ast_tools, file).await;
306                        all_findings.extend(error_findings);
307                        all_findings.extend(memory_findings);
308                    }
309                    DebugDepth::Deep => {
310                        // Deep analysis with execution tracing
311                        let error_findings = self.analyze_error_patterns(ast_tools, file).await;
312                        let memory_findings = self.analyze_memory_issues(ast_tools, file).await;
313                        let trace_findings =
314                            self.trace_execution_paths(ast_tools, entry_point).await;
315                        all_findings.extend(error_findings);
316                        all_findings.extend(memory_findings);
317                        all_findings.extend(trace_findings);
318                    }
319                }
320            }
321
322            let critical_count = all_findings
323                .iter()
324                .filter(|f| f.severity == Severity::Critical)
325                .count();
326            let high_count = all_findings
327                .iter()
328                .filter(|f| f.severity == Severity::High)
329                .count();
330
331            let summary = format!(
332                "Debug analysis completed: {} files analyzed, {} issues found (Critical: {}, High: {})",
333                analyzed_files.len(),
334                all_findings.len(),
335                critical_count,
336                high_count
337            );
338
339            // Store the length before moving all_findings
340            let issues_found = all_findings.len();
341
342            let execution_time = SystemTime::now()
343                .duration_since(start_time)
344                .unwrap_or_else(|_| Duration::from_secs(0));
345
346            Ok(AgentResult {
347                agent_name: self.name.clone(),
348                status: AgentStatus::Completed,
349                findings: all_findings,
350                analyzed_files,
351                modified_files: Vec::new(),
352                execution_time,
353                summary,
354                metrics: HashMap::from([
355                    ("issues_found".to_string(), serde_json::json!(issues_found)),
356                    (
357                        "critical_issues".to_string(),
358                        serde_json::json!(critical_count),
359                    ),
360                    (
361                        "debug_depth".to_string(),
362                        serde_json::json!(format!("{:?}", self.debug_depth)),
363                    ),
364                ]),
365            })
366        })
367    }
368
369    fn capabilities(&self) -> Vec<String> {
370        vec![
371            "error-analysis".to_string(),
372            "execution-tracing".to_string(),
373            "race-detection".to_string(),
374            "memory-analysis".to_string(),
375            "root-cause-analysis".to_string(),
376        ]
377    }
378
379    fn supports_file_type(&self, file_path: &Path) -> bool {
380        let supported = ["rs", "py", "js", "ts", "go", "java", "cpp", "c"];
381        file_path
382            .extension()
383            .and_then(|ext| ext.to_str())
384            .map(|ext| supported.contains(&ext))
385            .unwrap_or(false)
386    }
387
388    fn execution_time_estimate(&self) -> Duration {
389        match self.debug_depth {
390            DebugDepth::Surface => Duration::from_secs(30),
391            DebugDepth::Standard => Duration::from_secs(60),
392            DebugDepth::Deep => Duration::from_secs(120),
393        }
394    }
395}
396
397impl DebuggerAgent {
398    fn get_debug_targets(&self, context: &SubagentContext) -> Result<Vec<PathBuf>, SubagentError> {
399        if let Some(files) = context.parameters.get("files") {
400            Ok(files.split(',').map(|s| PathBuf::from(s.trim())).collect())
401        } else {
402            Ok(vec![context.working_directory.clone()])
403        }
404    }
405}