agcodex_core/subagents/
agents.rs

1//! Core subagent implementations for AGCodex
2//!
3//! This module provides specialized AI assistants for task-specific workflows:
4//! - CodeReviewerAgent: AST-based code review and quality analysis
5//! - RefactorerAgent: Systematic code refactoring with risk assessment
6//! - DebuggerAgent: Deep debugging analysis and execution path tracing
7//! - TestWriterAgent: Comprehensive test generation and coverage analysis
8//! - PerformanceAgent: Performance optimization and bottleneck detection
9//! - SecurityAgent: Security vulnerability detection and OWASP analysis
10//! - DocsAgent: Documentation generation and API reference creation
11
12use crate::code_tools::ast_agent_tools::ASTAgentTools;
13use crate::code_tools::ast_agent_tools::AgentToolOp;
14use crate::code_tools::ast_agent_tools::AgentToolResult;
15use crate::code_tools::ast_agent_tools::DocumentationTarget;
16use crate::code_tools::ast_agent_tools::ImprovementFocus;
17use crate::code_tools::ast_agent_tools::Location;
18use crate::code_tools::ast_agent_tools::PatternType;
19use crate::code_tools::search::SearchScope;
20use crate::subagents::SubagentContext;
21use crate::subagents::SubagentError;
22use crate::subagents::SubagentResult;
23use serde::Deserialize;
24use serde::Serialize;
25use std::collections::HashMap;
26use std::future::Future;
27use std::path::PathBuf;
28use std::pin::Pin;
29use std::sync::Arc;
30use std::sync::atomic::AtomicBool;
31use std::sync::atomic::Ordering;
32use std::time::Duration;
33use std::time::SystemTime;
34use walkdir::WalkDir;
35
36/// Result of agent execution with structured findings
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct AgentResult {
39    /// Agent that produced this result
40    pub agent_name: String,
41    /// Execution status
42    pub status: AgentStatus,
43    /// Primary findings
44    pub findings: Vec<Finding>,
45    /// Files analyzed
46    pub analyzed_files: Vec<PathBuf>,
47    /// Files modified (if any)
48    pub modified_files: Vec<PathBuf>,
49    /// Execution time
50    pub execution_time: Duration,
51    /// Summary report
52    pub summary: String,
53    /// Metrics and statistics
54    pub metrics: HashMap<String, serde_json::Value>,
55}
56
57/// Agent execution status with progress tracking
58#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
59pub enum AgentStatus {
60    Initializing,
61    Analyzing,
62    Processing,
63    Generating,
64    Completed,
65    Failed(String),
66    Cancelled,
67}
68
69/// Individual finding from agent analysis
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct Finding {
72    /// Finding type/category
73    pub category: String,
74    /// Severity level (Critical, High, Medium, Low, Info)
75    pub severity: Severity,
76    /// Finding title
77    pub title: String,
78    /// Detailed description
79    pub description: String,
80    /// Location in code (if applicable)
81    pub location: Option<Location>,
82    /// Suggested fix or improvement
83    pub suggestion: Option<String>,
84    /// Additional metadata
85    pub metadata: HashMap<String, serde_json::Value>,
86}
87
88/// Severity levels for findings
89#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
90pub enum Severity {
91    Info,
92    Low,
93    Medium,
94    High,
95    Critical,
96}
97
98/// Core trait for all subagents
99pub trait Subagent: Send + Sync + std::fmt::Debug {
100    /// Agent name identifier
101    fn name(&self) -> &str;
102
103    /// Agent description
104    fn description(&self) -> &str;
105
106    /// Execute the agent with given context
107    /// Returns a boxed future to make the trait object-safe
108    fn execute<'a>(
109        &'a self,
110        context: &'a SubagentContext,
111        ast_tools: &'a mut ASTAgentTools,
112        cancel_flag: Arc<AtomicBool>,
113    ) -> Pin<Box<dyn Future<Output = SubagentResult<AgentResult>> + Send + 'a>>;
114
115    /// Get agent capabilities and tool requirements
116    fn capabilities(&self) -> Vec<String>;
117
118    /// Check if agent can handle the given file types
119    fn supports_file_type(&self, file_path: &std::path::Path) -> bool;
120
121    /// Get expected execution time range
122    fn execution_time_estimate(&self) -> Duration {
123        Duration::from_secs(60) // Default 1 minute
124    }
125}
126
127/// Code review agent for AST-based quality analysis
128#[derive(Debug)]
129pub struct CodeReviewerAgent {
130    config: ReviewConfig,
131}
132
133#[derive(Debug, Clone)]
134pub struct ReviewConfig {
135    pub focus_areas: Vec<ReviewFocus>,
136    pub severity_threshold: Severity,
137    pub include_suggestions: bool,
138    pub check_security: bool,
139    pub check_performance: bool,
140    pub check_maintainability: bool,
141}
142
143#[derive(Debug, Clone)]
144pub enum ReviewFocus {
145    Security,
146    Performance,
147    Maintainability,
148    CodeStyle,
149    Documentation,
150    Testing,
151}
152
153impl Default for CodeReviewerAgent {
154    fn default() -> Self {
155        Self::new()
156    }
157}
158
159impl CodeReviewerAgent {
160    pub fn new() -> Self {
161        Self {
162            config: ReviewConfig {
163                focus_areas: vec![
164                    ReviewFocus::Security,
165                    ReviewFocus::Performance,
166                    ReviewFocus::Maintainability,
167                ],
168                severity_threshold: Severity::Low,
169                include_suggestions: true,
170                check_security: true,
171                check_performance: true,
172                check_maintainability: true,
173            },
174        }
175    }
176
177    pub const fn with_config(config: ReviewConfig) -> Self {
178        Self { config }
179    }
180
181    async fn analyze_functions(
182        &self,
183        ast_tools: &mut ASTAgentTools,
184        file: &PathBuf,
185    ) -> SubagentResult<Vec<Finding>> {
186        let mut findings = Vec::new();
187
188        // Extract functions from file
189        let result = ast_tools.execute(AgentToolOp::ExtractFunctions {
190            file: file.clone(),
191            language: self.detect_language(file),
192        })?;
193
194        if let AgentToolResult::Functions(functions) = result {
195            for func in functions {
196                // Check complexity
197                if let Ok(AgentToolResult::Complexity(complexity)) =
198                    ast_tools.execute(AgentToolOp::CalculateComplexity {
199                        function: func.name.clone(),
200                    })
201                    && complexity.cyclomatic_complexity > 10
202                {
203                    findings.push(Finding {
204                        category: "complexity".to_string(),
205                        severity: if complexity.cyclomatic_complexity > 20 {
206                            Severity::High
207                        } else {
208                            Severity::Medium
209                        },
210                        title: format!("High cyclomatic complexity in {}", func.name),
211                        description: format!(
212                            "Function '{}' has cyclomatic complexity of {}, consider refactoring",
213                            func.name, complexity.cyclomatic_complexity
214                        ),
215                        location: Some(Location {
216                            file: file.clone(),
217                            line: func.start_line,
218                            column: 0,
219                            byte_offset: 0,
220                        }),
221                        suggestion: Some(
222                            "Break down into smaller functions or reduce conditional complexity"
223                                .to_string(),
224                        ),
225                        metadata: HashMap::from([(
226                            "complexity".to_string(),
227                            serde_json::Value::Number(serde_json::Number::from(
228                                complexity.cyclomatic_complexity,
229                            )),
230                        )]),
231                    });
232                }
233
234                // Check function length
235                let line_count = func.end_line - func.start_line + 1;
236                if line_count > 50 {
237                    findings.push(Finding {
238                        category: "maintainability".to_string(),
239                        severity: if line_count > 100 {
240                            Severity::High
241                        } else {
242                            Severity::Medium
243                        },
244                        title: format!("Long function: {}", func.name),
245                        description: format!(
246                            "Function '{}' is {} lines long, consider breaking it down",
247                            func.name, line_count
248                        ),
249                        location: Some(Location {
250                            file: file.clone(),
251                            line: func.start_line,
252                            column: 0,
253                            byte_offset: 0,
254                        }),
255                        suggestion: Some("Extract smaller, focused functions".to_string()),
256                        metadata: HashMap::from([(
257                            "line_count".to_string(),
258                            serde_json::Value::Number(serde_json::Number::from(line_count)),
259                        )]),
260                    });
261                }
262
263                // Check parameter count
264                if func.parameters.len() > 5 {
265                    findings.push(Finding {
266                        category: "design".to_string(),
267                        severity: Severity::Medium,
268                        title: format!("Too many parameters in {}", func.name),
269                        description: format!(
270                            "Function '{}' has {} parameters, consider using a parameter object",
271                            func.name,
272                            func.parameters.len()
273                        ),
274                        location: Some(Location {
275                            file: file.clone(),
276                            line: func.start_line,
277                            column: 0,
278                            byte_offset: 0,
279                        }),
280                        suggestion: Some(
281                            "Group related parameters into a struct or use builder pattern"
282                                .to_string(),
283                        ),
284                        metadata: HashMap::from([(
285                            "parameter_count".to_string(),
286                            serde_json::Value::Number(serde_json::Number::from(
287                                func.parameters.len(),
288                            )),
289                        )]),
290                    });
291                }
292            }
293        }
294
295        Ok(findings)
296    }
297
298    async fn detect_security_issues(
299        &self,
300        ast_tools: &mut ASTAgentTools,
301    ) -> SubagentResult<Vec<Finding>> {
302        let mut findings = Vec::new();
303
304        // Detect security anti-patterns
305        let result = ast_tools.execute(AgentToolOp::DetectPatterns {
306            pattern: PatternType::AntiPattern("sql_injection".to_string()),
307        })?;
308
309        if let AgentToolResult::Patterns(patterns) = result {
310            for pattern in patterns {
311                findings.push(Finding {
312                    category: "security".to_string(),
313                    severity: Severity::Critical,
314                    title: "Potential SQL injection vulnerability".to_string(),
315                    description: "Direct string concatenation for SQL queries detected".to_string(),
316                    location: Some(pattern.location),
317                    suggestion: Some(
318                        "Use parameterized queries or prepared statements".to_string(),
319                    ),
320                    metadata: HashMap::new(),
321                });
322            }
323        }
324
325        Ok(findings)
326    }
327
328    async fn find_dead_code(&self, ast_tools: &mut ASTAgentTools) -> SubagentResult<Vec<Finding>> {
329        let mut findings = Vec::new();
330
331        let result = ast_tools.execute(AgentToolOp::FindDeadCode {
332            scope: SearchScope::Workspace,
333        })?;
334
335        if let AgentToolResult::DeadCode(dead_items) = result {
336            for item in dead_items {
337                findings.push(Finding {
338                    category: "maintainability".to_string(),
339                    severity: Severity::Low,
340                    title: format!("Dead code detected: {}", item.symbol),
341                    description: format!("Unused {:?} '{}' found", item.kind, item.symbol),
342                    location: Some(item.location),
343                    suggestion: Some("Remove unused code to improve maintainability".to_string()),
344                    metadata: HashMap::from([(
345                        "item_type".to_string(),
346                        serde_json::Value::String(format!("{:?}", item.kind)),
347                    )]),
348                });
349            }
350        }
351
352        Ok(findings)
353    }
354
355    fn detect_language(&self, file: &PathBuf) -> String {
356        match file.extension().and_then(|ext| ext.to_str()) {
357            Some("rs") => "rust".to_string(),
358            Some("py") => "python".to_string(),
359            Some("js") | Some("jsx") => "javascript".to_string(),
360            Some("ts") | Some("tsx") => "typescript".to_string(),
361            Some("go") => "go".to_string(),
362            Some("java") => "java".to_string(),
363            Some("cpp") | Some("cc") | Some("cxx") => "cpp".to_string(),
364            Some("c") => "c".to_string(),
365            Some("cs") => "csharp".to_string(),
366            _ => "text".to_string(),
367        }
368    }
369}
370
371impl Subagent for CodeReviewerAgent {
372    fn name(&self) -> &str {
373        "code-reviewer"
374    }
375
376    fn description(&self) -> &str {
377        "AST-based code review for quality, security, and maintainability analysis"
378    }
379
380    fn execute<'a>(
381        &'a self,
382        context: &'a SubagentContext,
383        ast_tools: &'a mut ASTAgentTools,
384        cancel_flag: Arc<AtomicBool>,
385    ) -> Pin<Box<dyn Future<Output = SubagentResult<AgentResult>> + Send + 'a>> {
386        Box::pin(async move {
387            let start_time = SystemTime::now();
388            let mut all_findings = Vec::new();
389            let mut analyzed_files = Vec::new();
390
391            // Get files to analyze
392            let files_to_analyze = self.get_files_to_analyze(&context.working_directory)?;
393
394            for file in &files_to_analyze {
395                if cancel_flag.load(Ordering::Relaxed) {
396                    return Ok(AgentResult {
397                        agent_name: self.name().to_string(),
398                        status: AgentStatus::Cancelled,
399                        findings: all_findings,
400                        analyzed_files,
401                        modified_files: vec![],
402                        execution_time: start_time.elapsed().unwrap_or_default(),
403                        summary: "Analysis cancelled by user".to_string(),
404                        metrics: HashMap::new(),
405                    });
406                }
407
408                analyzed_files.push(file.clone());
409
410                // Analyze functions
411                let mut findings = self.analyze_functions(ast_tools, file).await?;
412                all_findings.append(&mut findings);
413            }
414
415            // Global analysis
416            if self.config.check_security {
417                let mut security_findings = self.detect_security_issues(ast_tools).await?;
418                all_findings.append(&mut security_findings);
419            }
420
421            // Find dead code
422            let mut dead_code_findings = self.find_dead_code(ast_tools).await?;
423            all_findings.append(&mut dead_code_findings);
424
425            // Generate summary
426            let summary = self.generate_summary(&all_findings);
427            let metrics = self.calculate_metrics(&all_findings, &analyzed_files);
428
429            Ok(AgentResult {
430                agent_name: self.name().to_string(),
431                status: AgentStatus::Completed,
432                findings: all_findings,
433                analyzed_files,
434                modified_files: vec![],
435                execution_time: start_time.elapsed().unwrap_or_default(),
436                summary,
437                metrics,
438            })
439        })
440    }
441
442    fn capabilities(&self) -> Vec<String> {
443        vec![
444            "extract_functions".to_string(),
445            "detect_patterns".to_string(),
446            "find_dead_code".to_string(),
447            "calculate_complexity".to_string(),
448        ]
449    }
450
451    fn supports_file_type(&self, file_path: &std::path::Path) -> bool {
452        matches!(
453            file_path.extension().and_then(|ext| ext.to_str()),
454            Some("rs")
455                | Some("py")
456                | Some("js")
457                | Some("ts")
458                | Some("jsx")
459                | Some("tsx")
460                | Some("go")
461                | Some("java")
462                | Some("cpp")
463                | Some("c")
464                | Some("cs")
465        )
466    }
467
468    fn execution_time_estimate(&self) -> Duration {
469        Duration::from_secs(120) // 2 minutes for thorough review
470    }
471}
472
473impl CodeReviewerAgent {
474    fn get_files_to_analyze(&self, working_dir: &PathBuf) -> SubagentResult<Vec<PathBuf>> {
475        let mut files = Vec::new();
476
477        // Walk directory and collect source files
478        for entry in WalkDir::new(working_dir).follow_links(false).max_depth(10) {
479            let entry =
480                entry.map_err(|e| SubagentError::Io(std::io::Error::other(e.to_string())))?;
481
482            if entry.file_type().is_file() && self.supports_file_type(entry.path()) {
483                files.push(entry.path().to_path_buf());
484            }
485        }
486
487        Ok(files)
488    }
489
490    fn generate_summary(&self, findings: &[Finding]) -> String {
491        let critical_count = findings
492            .iter()
493            .filter(|f| f.severity == Severity::Critical)
494            .count();
495        let high_count = findings
496            .iter()
497            .filter(|f| f.severity == Severity::High)
498            .count();
499        let medium_count = findings
500            .iter()
501            .filter(|f| f.severity == Severity::Medium)
502            .count();
503        let low_count = findings
504            .iter()
505            .filter(|f| f.severity == Severity::Low)
506            .count();
507
508        format!(
509            "Code review completed. Found {} issues: {} critical, {} high, {} medium, {} low severity.",
510            findings.len(),
511            critical_count,
512            high_count,
513            medium_count,
514            low_count
515        )
516    }
517
518    fn calculate_metrics(
519        &self,
520        findings: &[Finding],
521        analyzed_files: &[PathBuf],
522    ) -> HashMap<String, serde_json::Value> {
523        let mut metrics = HashMap::new();
524
525        metrics.insert(
526            "total_findings".to_string(),
527            serde_json::Value::Number(serde_json::Number::from(findings.len())),
528        );
529        metrics.insert(
530            "files_analyzed".to_string(),
531            serde_json::Value::Number(serde_json::Number::from(analyzed_files.len())),
532        );
533
534        // Count by severity
535        for severity in [
536            Severity::Critical,
537            Severity::High,
538            Severity::Medium,
539            Severity::Low,
540            Severity::Info,
541        ] {
542            let count = findings.iter().filter(|f| f.severity == severity).count();
543            let key = format!("{:?}_count", severity).to_lowercase();
544            metrics.insert(
545                key,
546                serde_json::Value::Number(serde_json::Number::from(count)),
547            );
548        }
549
550        // Count by category
551        let mut categories = HashMap::new();
552        for finding in findings {
553            *categories.entry(finding.category.clone()).or_insert(0) += 1;
554        }
555
556        for (category, count) in categories {
557            metrics.insert(
558                format!("{}_issues", category),
559                serde_json::Value::Number(serde_json::Number::from(count)),
560            );
561        }
562
563        metrics
564    }
565}
566
567/// Systematic refactoring agent
568#[derive(Debug)]
569pub struct RefactorerAgent {
570    _config: RefactorConfig,
571}
572
573#[derive(Debug, Clone)]
574pub struct RefactorConfig {
575    pub risk_threshold: RiskLevel,
576    pub operations: Vec<RefactorOperation>,
577    pub backup_enabled: bool,
578}
579
580#[derive(Debug, Clone, Copy, PartialEq, Eq)]
581pub enum RiskLevel {
582    Low,
583    Medium,
584    High,
585}
586
587#[derive(Debug, Clone)]
588pub enum RefactorOperation {
589    ExtractMethod,
590    InlineVariable,
591    RenameSymbol,
592    RemoveDuplication,
593}
594
595impl Default for RefactorerAgent {
596    fn default() -> Self {
597        Self::new()
598    }
599}
600
601impl RefactorerAgent {
602    pub fn new() -> Self {
603        Self {
604            _config: RefactorConfig {
605                risk_threshold: RiskLevel::Medium,
606                operations: vec![
607                    RefactorOperation::ExtractMethod,
608                    RefactorOperation::RemoveDuplication,
609                ],
610                backup_enabled: true,
611            },
612        }
613    }
614}
615
616impl Subagent for RefactorerAgent {
617    fn name(&self) -> &str {
618        "refactorer"
619    }
620
621    fn description(&self) -> &str {
622        "Systematic code refactoring with risk assessment and automated improvements"
623    }
624
625    fn execute<'a>(
626        &'a self,
627        _context: &'a SubagentContext,
628        ast_tools: &'a mut ASTAgentTools,
629        _cancel_flag: Arc<AtomicBool>,
630    ) -> Pin<Box<dyn Future<Output = SubagentResult<AgentResult>> + Send + 'a>> {
631        Box::pin(async move {
632            let start_time = SystemTime::now();
633            let mut findings = Vec::new();
634
635            // Detect code duplication
636            let result = ast_tools.execute(AgentToolOp::DetectDuplication { threshold: 0.8 })?;
637
638            if let AgentToolResult::Duplications(duplications) = result {
639                for dup_group in duplications {
640                    findings.push(Finding {
641                        category: "duplication".to_string(),
642                        severity: if dup_group.locations.len() > 3 {
643                            Severity::High
644                        } else {
645                            Severity::Medium
646                        },
647                        title: "Code duplication detected".to_string(),
648                        description: format!(
649                            "Found {} instances of duplicated code",
650                            dup_group.locations.len()
651                        ),
652                        location: dup_group.locations.first().cloned(),
653                        suggestion: Some(
654                            "Extract common functionality into a shared function or method"
655                                .to_string(),
656                        ),
657                        metadata: HashMap::from([
658                            (
659                                "duplication_count".to_string(),
660                                serde_json::Value::Number(serde_json::Number::from(
661                                    dup_group.locations.len(),
662                                )),
663                            ),
664                            (
665                                "similarity_score".to_string(),
666                                serde_json::Value::Number(
667                                    serde_json::Number::from_f64(dup_group.similarity as f64)
668                                        .unwrap(),
669                                ),
670                            ),
671                        ]),
672                    });
673                }
674            }
675
676            Ok(AgentResult {
677                agent_name: self.name().to_string(),
678                status: AgentStatus::Completed,
679                findings,
680                analyzed_files: vec![],
681                modified_files: vec![],
682                execution_time: start_time.elapsed().unwrap_or_default(),
683                summary: "Refactoring analysis completed".to_string(),
684                metrics: HashMap::new(),
685            })
686        })
687    }
688
689    fn capabilities(&self) -> Vec<String> {
690        vec![
691            "detect_duplication".to_string(),
692            "extract_method".to_string(),
693            "inline_variable".to_string(),
694            "refactor_rename".to_string(),
695        ]
696    }
697
698    fn supports_file_type(&self, file_path: &std::path::Path) -> bool {
699        // Supports same file types as CodeReviewerAgent
700        matches!(
701            file_path.extension().and_then(|ext| ext.to_str()),
702            Some("rs")
703                | Some("py")
704                | Some("js")
705                | Some("ts")
706                | Some("jsx")
707                | Some("tsx")
708                | Some("go")
709                | Some("java")
710                | Some("cpp")
711                | Some("c")
712                | Some("cs")
713        )
714    }
715}
716
717/// Deep debugging analysis agent
718#[derive(Debug)]
719pub struct DebuggerAgent;
720
721impl Default for DebuggerAgent {
722    fn default() -> Self {
723        Self::new()
724    }
725}
726
727impl DebuggerAgent {
728    pub const fn new() -> Self {
729        Self
730    }
731}
732
733impl Subagent for DebuggerAgent {
734    fn name(&self) -> &str {
735        "debugger"
736    }
737
738    fn description(&self) -> &str {
739        "Deep debugging analysis with execution path tracing and error source identification"
740    }
741
742    fn execute<'a>(
743        &'a self,
744        context: &'a SubagentContext,
745        ast_tools: &'a mut ASTAgentTools,
746        _cancel_flag: Arc<AtomicBool>,
747    ) -> Pin<Box<dyn Future<Output = SubagentResult<AgentResult>> + Send + 'a>> {
748        Box::pin(async move {
749            let start_time = SystemTime::now();
750            let mut findings = Vec::new();
751
752            // Analyze call graph to identify potential issue sources
753            if let Some(entry_point) = context.parameters.get("entry_point") {
754                let result = ast_tools.execute(AgentToolOp::AnalyzeCallGraph {
755                    entry_point: entry_point.clone(),
756                })?;
757
758                if let AgentToolResult::CallGraph(call_graph) = result {
759                    // Analyze call graph for potential issues
760                    for (_name, node) in call_graph.nodes {
761                        // Simple heuristic: if there are no edges pointing to this node
762                        let has_callers = call_graph
763                            .edges
764                            .iter()
765                            .any(|edge| edge.callee == node.function_name);
766                        if !has_callers {
767                            findings.push(Finding {
768                                category: "unreachable".to_string(),
769                                severity: Severity::Medium,
770                                title: format!(
771                                    "Potentially unreachable function: {}",
772                                    node.function_name
773                                ),
774                                description: "Function with no callers detected".to_string(),
775                                location: Some(node.location),
776                                suggestion: Some(
777                                    "Verify if this function is used externally or can be removed"
778                                        .to_string(),
779                                ),
780                                metadata: HashMap::new(),
781                            });
782                        }
783                    }
784                }
785            }
786
787            Ok(AgentResult {
788                agent_name: self.name().to_string(),
789                status: AgentStatus::Completed,
790                findings,
791                analyzed_files: vec![],
792                modified_files: vec![],
793                execution_time: start_time.elapsed().unwrap_or_default(),
794                summary: "Debugging analysis completed".to_string(),
795                metrics: HashMap::new(),
796            })
797        })
798    }
799
800    fn capabilities(&self) -> Vec<String> {
801        vec![
802            "analyze_call_graph".to_string(),
803            "find_references".to_string(),
804            "find_definition".to_string(),
805        ]
806    }
807
808    fn supports_file_type(&self, file_path: &std::path::Path) -> bool {
809        matches!(
810            file_path.extension().and_then(|ext| ext.to_str()),
811            Some("rs")
812                | Some("py")
813                | Some("js")
814                | Some("ts")
815                | Some("jsx")
816                | Some("tsx")
817                | Some("go")
818                | Some("java")
819                | Some("cpp")
820                | Some("c")
821                | Some("cs")
822        )
823    }
824}
825
826/// Test generation and coverage analysis agent
827#[derive(Debug)]
828pub struct TestWriterAgent;
829
830impl Default for TestWriterAgent {
831    fn default() -> Self {
832        Self::new()
833    }
834}
835
836impl TestWriterAgent {
837    pub const fn new() -> Self {
838        Self
839    }
840}
841
842impl Subagent for TestWriterAgent {
843    fn name(&self) -> &str {
844        "test-writer"
845    }
846
847    fn description(&self) -> &str {
848        "Comprehensive test generation and coverage analysis"
849    }
850
851    fn execute<'a>(
852        &'a self,
853        context: &'a SubagentContext,
854        ast_tools: &'a mut ASTAgentTools,
855        _cancel_flag: Arc<AtomicBool>,
856    ) -> Pin<Box<dyn Future<Output = SubagentResult<AgentResult>> + Send + 'a>> {
857        Box::pin(async move {
858            let start_time = SystemTime::now();
859            let mut findings = Vec::new();
860
861            // Find functions that need tests
862            let files = self.get_source_files(&context.working_directory)?;
863
864            for file in &files {
865                let result = ast_tools.execute(AgentToolOp::ExtractFunctions {
866                    file: file.clone(),
867                    language: self.detect_language(file),
868                })?;
869
870                if let AgentToolResult::Functions(functions) = result {
871                    for func in functions {
872                        // Simple heuristic: not a test function and is exported
873                        let is_test_function =
874                            func.name.contains("test") || func.name.starts_with("test_");
875                        if !is_test_function && func.is_exported {
876                            findings.push(Finding {
877                                category: "testing".to_string(),
878                                severity: Severity::Medium,
879                                title: format!("Missing tests for function: {}", func.name),
880                                description: format!(
881                                    "Public function '{}' lacks test coverage",
882                                    func.name
883                                ),
884                                location: Some(Location {
885                                    file: file.clone(),
886                                    line: func.start_line,
887                                    column: 0,
888                                    byte_offset: 0,
889                                }),
890                                suggestion: Some(
891                                    "Add unit tests to verify function behavior".to_string(),
892                                ),
893                                metadata: HashMap::from([
894                                    (
895                                        "function_name".to_string(),
896                                        serde_json::Value::String(func.name),
897                                    ),
898                                    (
899                                        "parameter_count".to_string(),
900                                        serde_json::Value::Number(serde_json::Number::from(
901                                            func.parameters.len(),
902                                        )),
903                                    ),
904                                ]),
905                            });
906                        }
907                    }
908                }
909            }
910
911            let findings_count = findings.len();
912            Ok(AgentResult {
913                agent_name: self.name().to_string(),
914                status: AgentStatus::Completed,
915                findings,
916                analyzed_files: files,
917                modified_files: vec![],
918                execution_time: start_time.elapsed().unwrap_or_default(),
919                summary: format!(
920                    "Test analysis completed. Found {} functions needing tests.",
921                    findings_count
922                ),
923                metrics: HashMap::new(),
924            })
925        })
926    }
927
928    fn capabilities(&self) -> Vec<String> {
929        vec![
930            "extract_functions".to_string(),
931            "generate_tests".to_string(),
932        ]
933    }
934
935    fn supports_file_type(&self, file_path: &std::path::Path) -> bool {
936        matches!(
937            file_path.extension().and_then(|ext| ext.to_str()),
938            Some("rs")
939                | Some("py")
940                | Some("js")
941                | Some("ts")
942                | Some("jsx")
943                | Some("tsx")
944                | Some("go")
945                | Some("java")
946                | Some("cpp")
947                | Some("c")
948                | Some("cs")
949        )
950    }
951}
952
953impl TestWriterAgent {
954    fn get_source_files(&self, working_dir: &PathBuf) -> SubagentResult<Vec<PathBuf>> {
955        let mut files = Vec::new();
956
957        for entry in WalkDir::new(working_dir).follow_links(false).max_depth(8) {
958            let entry =
959                entry.map_err(|e| SubagentError::Io(std::io::Error::other(e.to_string())))?;
960
961            if entry.file_type().is_file() && self.supports_file_type(entry.path()) {
962                // Skip test files
963                let path_str = entry.path().to_string_lossy();
964                if !path_str.contains("test") && !path_str.contains("spec") {
965                    files.push(entry.path().to_path_buf());
966                }
967            }
968        }
969
970        Ok(files)
971    }
972
973    fn detect_language(&self, file: &PathBuf) -> String {
974        match file.extension().and_then(|ext| ext.to_str()) {
975            Some("rs") => "rust".to_string(),
976            Some("py") => "python".to_string(),
977            Some("js") | Some("jsx") => "javascript".to_string(),
978            Some("ts") | Some("tsx") => "typescript".to_string(),
979            Some("go") => "go".to_string(),
980            Some("java") => "java".to_string(),
981            _ => "text".to_string(),
982        }
983    }
984}
985
986/// Performance optimization agent
987#[derive(Debug)]
988pub struct PerformanceAgent;
989
990impl Default for PerformanceAgent {
991    fn default() -> Self {
992        Self::new()
993    }
994}
995
996impl PerformanceAgent {
997    pub const fn new() -> Self {
998        Self
999    }
1000}
1001
1002impl Subagent for PerformanceAgent {
1003    fn name(&self) -> &str {
1004        "performance"
1005    }
1006
1007    fn description(&self) -> &str {
1008        "Performance optimization with bottleneck detection and algorithmic analysis"
1009    }
1010
1011    fn execute<'a>(
1012        &'a self,
1013        context: &'a SubagentContext,
1014        ast_tools: &'a mut ASTAgentTools,
1015        _cancel_flag: Arc<AtomicBool>,
1016    ) -> Pin<Box<dyn Future<Output = SubagentResult<AgentResult>> + Send + 'a>> {
1017        Box::pin(async move {
1018            let start_time = SystemTime::now();
1019            let mut findings = Vec::new();
1020
1021            // Detect performance improvement opportunities
1022            // For now, use a placeholder file - this would be enhanced to iterate over relevant files
1023            let target_file = context.working_directory.join("src").join("main.rs");
1024            if target_file.exists() {
1025                let result = ast_tools.execute(AgentToolOp::SuggestImprovements {
1026                    file: target_file,
1027                    focus: ImprovementFocus::Performance,
1028                })?;
1029
1030                if let AgentToolResult::Improvements(improvements) = result {
1031                    for improvement in improvements {
1032                        let severity = Severity::Medium;
1033
1034                        findings.push(Finding {
1035                            category: "performance".to_string(),
1036                            severity,
1037                            title: format!("Performance improvement: {:?}", improvement.category),
1038                            description: improvement.description,
1039                            location: Some(improvement.location),
1040                            suggestion: improvement.suggested_change,
1041                            metadata: HashMap::from([
1042                                (
1043                                    "category".to_string(),
1044                                    serde_json::Value::String(format!(
1045                                        "{:?}",
1046                                        improvement.category
1047                                    )),
1048                                ),
1049                                (
1050                                    "impact".to_string(),
1051                                    serde_json::Value::String(format!("{:?}", improvement.impact)),
1052                                ),
1053                            ]),
1054                        });
1055                    }
1056                }
1057            }
1058
1059            let findings_count = findings.len();
1060            Ok(AgentResult {
1061                agent_name: self.name().to_string(),
1062                status: AgentStatus::Completed,
1063                findings,
1064                analyzed_files: vec![],
1065                modified_files: vec![],
1066                execution_time: start_time.elapsed().unwrap_or_default(),
1067                summary: format!(
1068                    "Performance analysis completed. Found {} optimization opportunities.",
1069                    findings_count
1070                ),
1071                metrics: HashMap::new(),
1072            })
1073        })
1074    }
1075
1076    fn capabilities(&self) -> Vec<String> {
1077        vec![
1078            "suggest_improvements".to_string(),
1079            "calculate_complexity".to_string(),
1080            "analyze_call_graph".to_string(),
1081        ]
1082    }
1083
1084    fn supports_file_type(&self, file_path: &std::path::Path) -> bool {
1085        matches!(
1086            file_path.extension().and_then(|ext| ext.to_str()),
1087            Some("rs")
1088                | Some("py")
1089                | Some("js")
1090                | Some("ts")
1091                | Some("jsx")
1092                | Some("tsx")
1093                | Some("go")
1094                | Some("java")
1095                | Some("cpp")
1096                | Some("c")
1097                | Some("cs")
1098        )
1099    }
1100}
1101
1102/// Security vulnerability detection agent
1103#[derive(Debug)]
1104pub struct SecurityAgent;
1105
1106impl Default for SecurityAgent {
1107    fn default() -> Self {
1108        Self::new()
1109    }
1110}
1111
1112impl SecurityAgent {
1113    pub const fn new() -> Self {
1114        Self
1115    }
1116}
1117
1118impl Subagent for SecurityAgent {
1119    fn name(&self) -> &str {
1120        "security"
1121    }
1122
1123    fn description(&self) -> &str {
1124        "Security vulnerability detection with OWASP Top 10 analysis"
1125    }
1126
1127    fn execute<'a>(
1128        &'a self,
1129        _context: &'a SubagentContext,
1130        ast_tools: &'a mut ASTAgentTools,
1131        _cancel_flag: Arc<AtomicBool>,
1132    ) -> Pin<Box<dyn Future<Output = SubagentResult<AgentResult>> + Send + 'a>> {
1133        Box::pin(async move {
1134            let start_time = SystemTime::now();
1135            let mut findings = Vec::new();
1136
1137            // Check for various security patterns
1138            let security_patterns = vec![
1139                "sql_injection",
1140                "xss_vulnerability",
1141                "path_traversal",
1142                "hardcoded_secrets",
1143                "weak_crypto",
1144            ];
1145
1146            for pattern in security_patterns {
1147                let result = ast_tools.execute(AgentToolOp::DetectPatterns {
1148                    pattern: PatternType::AntiPattern(pattern.to_string()),
1149                })?;
1150
1151                if let AgentToolResult::Patterns(patterns) = result {
1152                    for detected_pattern in patterns {
1153                        let (severity, description, suggestion) = match pattern {
1154                            "sql_injection" => (
1155                                Severity::Critical,
1156                                "Potential SQL injection vulnerability detected",
1157                                "Use parameterized queries or prepared statements",
1158                            ),
1159                            "xss_vulnerability" => (
1160                                Severity::High,
1161                                "Potential XSS vulnerability detected",
1162                                "Sanitize and validate all user inputs",
1163                            ),
1164                            "path_traversal" => (
1165                                Severity::High,
1166                                "Potential path traversal vulnerability",
1167                                "Validate and sanitize file paths",
1168                            ),
1169                            "hardcoded_secrets" => (
1170                                Severity::Critical,
1171                                "Hardcoded secret or credential detected",
1172                                "Use environment variables or secure credential storage",
1173                            ),
1174                            "weak_crypto" => (
1175                                Severity::Medium,
1176                                "Weak cryptographic algorithm detected",
1177                                "Use modern, secure cryptographic algorithms",
1178                            ),
1179                            _ => (
1180                                Severity::Medium,
1181                                "Security issue detected",
1182                                "Review and address security concern",
1183                            ),
1184                        };
1185
1186                        findings.push(Finding {
1187                            category: "security".to_string(),
1188                            severity,
1189                            title: format!("Security: {}", pattern.replace('_', " ")),
1190                            description: description.to_string(),
1191                            location: Some(detected_pattern.location),
1192                            suggestion: Some(suggestion.to_string()),
1193                            metadata: HashMap::from([(
1194                                "vulnerability_type".to_string(),
1195                                serde_json::Value::String(pattern.to_string()),
1196                            )]),
1197                        });
1198                    }
1199                }
1200            }
1201
1202            let findings_count = findings.len();
1203            Ok(AgentResult {
1204                agent_name: self.name().to_string(),
1205                status: AgentStatus::Completed,
1206                findings,
1207                analyzed_files: vec![],
1208                modified_files: vec![],
1209                execution_time: start_time.elapsed().unwrap_or_default(),
1210                summary: format!(
1211                    "Security analysis completed. Found {} potential vulnerabilities.",
1212                    findings_count
1213                ),
1214                metrics: HashMap::new(),
1215            })
1216        })
1217    }
1218
1219    fn capabilities(&self) -> Vec<String> {
1220        vec!["detect_patterns".to_string(), "security_scan".to_string()]
1221    }
1222
1223    fn supports_file_type(&self, file_path: &std::path::Path) -> bool {
1224        matches!(
1225            file_path.extension().and_then(|ext| ext.to_str()),
1226            Some("rs")
1227                | Some("py")
1228                | Some("js")
1229                | Some("ts")
1230                | Some("jsx")
1231                | Some("tsx")
1232                | Some("go")
1233                | Some("java")
1234                | Some("cpp")
1235                | Some("c")
1236                | Some("cs")
1237                | Some("php")
1238        )
1239    }
1240}
1241
1242/// Documentation generation agent
1243#[derive(Debug)]
1244pub struct DocsAgent;
1245
1246impl Default for DocsAgent {
1247    fn default() -> Self {
1248        Self::new()
1249    }
1250}
1251
1252impl DocsAgent {
1253    pub const fn new() -> Self {
1254        Self
1255    }
1256}
1257
1258impl Subagent for DocsAgent {
1259    fn name(&self) -> &str {
1260        "docs"
1261    }
1262
1263    fn description(&self) -> &str {
1264        "Documentation generation and API reference creation"
1265    }
1266
1267    fn execute<'a>(
1268        &'a self,
1269        context: &'a SubagentContext,
1270        ast_tools: &'a mut ASTAgentTools,
1271        _cancel_flag: Arc<AtomicBool>,
1272    ) -> Pin<Box<dyn Future<Output = SubagentResult<AgentResult>> + Send + 'a>> {
1273        Box::pin(async move {
1274            let start_time = SystemTime::now();
1275            let mut findings = Vec::new();
1276
1277            // Generate documentation for modules/files
1278            if let Some(target_file) = context.parameters.get("target") {
1279                let target_path = PathBuf::from(target_file);
1280
1281                let result = ast_tools.execute(AgentToolOp::GenerateDocumentation {
1282                    target: DocumentationTarget::File(target_path),
1283                })?;
1284
1285                if let AgentToolResult::Documentation(doc_content) = result {
1286                    findings.push(Finding {
1287                        category: "documentation".to_string(),
1288                        severity: Severity::Info,
1289                        title: "Generated documentation".to_string(),
1290                        description: "API documentation has been generated".to_string(),
1291                        location: None,
1292                        suggestion: None,
1293                        metadata: HashMap::from([(
1294                            "doc_length".to_string(),
1295                            serde_json::Value::Number(serde_json::Number::from(doc_content.len())),
1296                        )]),
1297                    });
1298                }
1299            }
1300
1301            Ok(AgentResult {
1302                agent_name: self.name().to_string(),
1303                status: AgentStatus::Completed,
1304                findings,
1305                analyzed_files: vec![],
1306                modified_files: vec![],
1307                execution_time: start_time.elapsed().unwrap_or_default(),
1308                summary: "Documentation generation completed".to_string(),
1309                metrics: HashMap::new(),
1310            })
1311        })
1312    }
1313
1314    fn capabilities(&self) -> Vec<String> {
1315        vec![
1316            "generate_documentation".to_string(),
1317            "extract_functions".to_string(),
1318        ]
1319    }
1320
1321    fn supports_file_type(&self, file_path: &std::path::Path) -> bool {
1322        matches!(
1323            file_path.extension().and_then(|ext| ext.to_str()),
1324            Some("rs")
1325                | Some("py")
1326                | Some("js")
1327                | Some("ts")
1328                | Some("jsx")
1329                | Some("tsx")
1330                | Some("go")
1331                | Some("java")
1332                | Some("cpp")
1333                | Some("c")
1334                | Some("cs")
1335        )
1336    }
1337}
1338
1339/// Agent registry for managing all available agents
1340pub struct AgentRegistry {
1341    agents: HashMap<String, Arc<dyn Subagent>>,
1342}
1343
1344impl AgentRegistry {
1345    pub fn new() -> Self {
1346        let mut registry = Self {
1347            agents: HashMap::new(),
1348        };
1349
1350        // Register all core agents
1351        registry.register(Arc::new(CodeReviewerAgent::new()));
1352        registry.register(Arc::new(RefactorerAgent::new()));
1353        registry.register(Arc::new(DebuggerAgent::new()));
1354        registry.register(Arc::new(TestWriterAgent::new()));
1355        registry.register(Arc::new(PerformanceAgent::new()));
1356        registry.register(Arc::new(SecurityAgent::new()));
1357        registry.register(Arc::new(DocsAgent::new()));
1358
1359        registry
1360    }
1361
1362    pub fn register(&mut self, agent: Arc<dyn Subagent>) {
1363        self.agents.insert(agent.name().to_string(), agent);
1364    }
1365
1366    pub fn get_agent(&self, name: &str) -> Option<Arc<dyn Subagent>> {
1367        self.agents.get(name).cloned()
1368    }
1369
1370    pub fn list_agents(&self) -> Vec<&str> {
1371        self.agents.keys().map(|s| s.as_str()).collect()
1372    }
1373
1374    pub fn get_agents_for_file(&self, file_path: &std::path::Path) -> Vec<String> {
1375        self.agents
1376            .iter()
1377            .filter(|(_, agent)| agent.supports_file_type(file_path))
1378            .map(|(name, _)| name.clone())
1379            .collect()
1380    }
1381}
1382
1383impl Default for AgentRegistry {
1384    fn default() -> Self {
1385        Self::new()
1386    }
1387}
1388
1389#[cfg(test)]
1390mod tests {
1391    use super::*;
1392
1393    #[test]
1394    fn test_agent_registry() {
1395        let registry = AgentRegistry::new();
1396
1397        assert!(registry.get_agent("code-reviewer").is_some());
1398        assert!(registry.get_agent("refactorer").is_some());
1399        assert!(registry.get_agent("debugger").is_some());
1400        assert!(registry.get_agent("test-writer").is_some());
1401        assert!(registry.get_agent("performance").is_some());
1402        assert!(registry.get_agent("security").is_some());
1403        assert!(registry.get_agent("docs").is_some());
1404
1405        assert_eq!(registry.list_agents().len(), 7);
1406    }
1407
1408    #[test]
1409    fn test_file_type_support() {
1410        let agent = CodeReviewerAgent::new();
1411
1412        assert!(agent.supports_file_type(&PathBuf::from("test.rs")));
1413        assert!(agent.supports_file_type(&PathBuf::from("test.py")));
1414        assert!(agent.supports_file_type(&PathBuf::from("test.js")));
1415        assert!(!agent.supports_file_type(&PathBuf::from("test.txt")));
1416    }
1417
1418    #[test]
1419    fn test_finding_severity_ordering() {
1420        assert!(Severity::Critical > Severity::High);
1421        assert!(Severity::High > Severity::Medium);
1422        assert!(Severity::Medium > Severity::Low);
1423        assert!(Severity::Low > Severity::Info);
1424    }
1425
1426    #[test]
1427    fn test_agent_result_creation() {
1428        let result = AgentResult {
1429            agent_name: "test-agent".to_string(),
1430            status: AgentStatus::Completed,
1431            findings: vec![],
1432            analyzed_files: vec![],
1433            modified_files: vec![],
1434            execution_time: Duration::from_secs(30),
1435            summary: "Test completed".to_string(),
1436            metrics: HashMap::new(),
1437        };
1438
1439        assert_eq!(result.agent_name, "test-agent");
1440        assert_eq!(result.status, AgentStatus::Completed);
1441        assert_eq!(result.execution_time, Duration::from_secs(30));
1442    }
1443}