agcodex_core/subagents/built_in/
debugger.rs1use 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#[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum DebugDepth {
47 Surface, Standard, Deep, }
51
52impl Default for DebuggerAgent {
53 fn default() -> Self {
54 Self::new()
55 }
56}
57
58impl DebuggerAgent {
59 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 pub const fn with_depth(mut self, depth: DebugDepth) -> Self {
91 self.debug_depth = depth;
92 self
93 }
94
95 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 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 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 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 if let Ok(AgentToolResult::CallGraph(graph)) =
169 ast_tools.execute(AgentToolOp::AnalyzeCallGraph {
170 entry_point: entry_point.to_string(),
171 })
172 {
173 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 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 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 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 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 let error_findings = self.analyze_error_patterns(ast_tools, file).await;
300 all_findings.extend(error_findings);
301 }
302 DebugDepth::Standard => {
303 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 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 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}