ricecoder_agents/agents/
web.rs

1//! Web Development Agent
2//!
3//! This module provides a specialized agent for web development tasks,
4//! including frontend framework recommendations, styling guidance, build tool
5//! recommendations, testing strategies, performance optimization, and deployment guidance.
6
7use crate::agents::Agent;
8use crate::domain::{DomainAgent, DomainCapability, DomainKnowledge, TechRecommendation};
9use crate::error::Result;
10use crate::models::{AgentInput, AgentOutput, Finding, Severity, TaskType};
11use async_trait::async_trait;
12use uuid::Uuid;
13
14/// Web Development Agent
15///
16/// A specialized agent for web development that provides recommendations for:
17/// - Frontend frameworks (React, Vue, Angular)
18/// - Styling solutions (CSS, Tailwind CSS, styled-components)
19/// - Build tools (Vite, Webpack)
20/// - Testing frameworks (Jest, Vitest, Playwright)
21/// - Performance optimization
22/// - Deployment patterns
23///
24/// # Examples
25///
26/// ```ignore
27/// use ricecoder_agents::agents::WebAgent;
28///
29/// let agent = WebAgent::new();
30/// assert_eq!(agent.id(), "web-agent");
31/// assert_eq!(agent.domain(), "web");
32/// ```
33#[derive(Debug, Clone)]
34pub struct WebAgent {
35    domain_agent: DomainAgent,
36}
37
38impl WebAgent {
39    /// Create a new Web Development Agent
40    ///
41    /// Initializes the agent with web-specific capabilities and knowledge.
42    ///
43    /// # Returns
44    ///
45    /// A new `WebAgent` instance
46    pub fn new() -> Self {
47        let capabilities = vec![
48            DomainCapability {
49                name: "Frontend Framework Selection".to_string(),
50                description: "Recommend frontend frameworks based on project needs".to_string(),
51                technologies: vec![
52                    "React".to_string(),
53                    "Vue".to_string(),
54                    "Angular".to_string(),
55                ],
56                patterns: vec![],
57            },
58            DomainCapability {
59                name: "Styling Guidance".to_string(),
60                description: "Provide expertise on CSS, Tailwind CSS, styled-components"
61                    .to_string(),
62                technologies: vec![
63                    "CSS".to_string(),
64                    "Tailwind CSS".to_string(),
65                    "styled-components".to_string(),
66                ],
67                patterns: vec![],
68            },
69            DomainCapability {
70                name: "Build Configuration".to_string(),
71                description: "Guidance on Vite or Webpack configuration".to_string(),
72                technologies: vec!["Vite".to_string(), "Webpack".to_string()],
73                patterns: vec![],
74            },
75            DomainCapability {
76                name: "Testing Strategy".to_string(),
77                description: "Recommendations for web testing frameworks".to_string(),
78                technologies: vec![
79                    "Jest".to_string(),
80                    "Vitest".to_string(),
81                    "Playwright".to_string(),
82                ],
83                patterns: vec![],
84            },
85            DomainCapability {
86                name: "Performance Optimization".to_string(),
87                description: "Specific optimization recommendations for web applications"
88                    .to_string(),
89                technologies: vec![
90                    "React".to_string(),
91                    "Vue".to_string(),
92                    "Angular".to_string(),
93                ],
94                patterns: vec![],
95            },
96            DomainCapability {
97                name: "Deployment Guidance".to_string(),
98                description: "Recommendations for web deployment patterns and platforms"
99                    .to_string(),
100                technologies: vec![
101                    "Vercel".to_string(),
102                    "Netlify".to_string(),
103                    "AWS".to_string(),
104                    "Docker".to_string(),
105                ],
106                patterns: vec![],
107            },
108        ];
109
110        let knowledge = DomainKnowledge {
111            best_practices: vec![],
112            technology_recommendations: vec![
113                TechRecommendation {
114                    technology: "React".to_string(),
115                    domain: "web".to_string(),
116                    use_cases: vec![
117                        "Single Page Applications".to_string(),
118                        "Complex UIs".to_string(),
119                        "Real-time applications".to_string(),
120                    ],
121                    pros: vec![
122                        "Large ecosystem".to_string(),
123                        "Strong community".to_string(),
124                        "Excellent tooling".to_string(),
125                    ],
126                    cons: vec![
127                        "Steep learning curve".to_string(),
128                        "JSX syntax".to_string(),
129                        "Frequent updates".to_string(),
130                    ],
131                    alternatives: vec!["Vue".to_string(), "Angular".to_string()],
132                },
133                TechRecommendation {
134                    technology: "Vue".to_string(),
135                    domain: "web".to_string(),
136                    use_cases: vec![
137                        "Progressive enhancement".to_string(),
138                        "Single Page Applications".to_string(),
139                        "Rapid prototyping".to_string(),
140                    ],
141                    pros: vec![
142                        "Easy to learn".to_string(),
143                        "Excellent documentation".to_string(),
144                        "Flexible".to_string(),
145                    ],
146                    cons: vec![
147                        "Smaller ecosystem".to_string(),
148                        "Less enterprise adoption".to_string(),
149                    ],
150                    alternatives: vec!["React".to_string(), "Angular".to_string()],
151                },
152                TechRecommendation {
153                    technology: "Angular".to_string(),
154                    domain: "web".to_string(),
155                    use_cases: vec![
156                        "Large-scale applications".to_string(),
157                        "Enterprise projects".to_string(),
158                        "Complex applications".to_string(),
159                    ],
160                    pros: vec![
161                        "Full-featured framework".to_string(),
162                        "Strong typing with TypeScript".to_string(),
163                        "Enterprise support".to_string(),
164                    ],
165                    cons: vec![
166                        "Steep learning curve".to_string(),
167                        "Verbose syntax".to_string(),
168                        "Heavier bundle size".to_string(),
169                    ],
170                    alternatives: vec!["React".to_string(), "Vue".to_string()],
171                },
172                TechRecommendation {
173                    technology: "Vite".to_string(),
174                    domain: "web".to_string(),
175                    use_cases: vec![
176                        "Modern web development".to_string(),
177                        "Fast development experience".to_string(),
178                    ],
179                    pros: vec![
180                        "Lightning fast HMR".to_string(),
181                        "Optimized build".to_string(),
182                        "Native ES modules".to_string(),
183                    ],
184                    cons: vec![
185                        "Newer tool".to_string(),
186                        "Less mature than Webpack".to_string(),
187                    ],
188                    alternatives: vec!["Webpack".to_string(), "Parcel".to_string()],
189                },
190                TechRecommendation {
191                    technology: "Webpack".to_string(),
192                    domain: "web".to_string(),
193                    use_cases: vec![
194                        "Complex bundling scenarios".to_string(),
195                        "Legacy projects".to_string(),
196                    ],
197                    pros: vec![
198                        "Mature and stable".to_string(),
199                        "Highly configurable".to_string(),
200                        "Large ecosystem".to_string(),
201                    ],
202                    cons: vec![
203                        "Complex configuration".to_string(),
204                        "Slower build times".to_string(),
205                    ],
206                    alternatives: vec!["Vite".to_string(), "Parcel".to_string()],
207                },
208                TechRecommendation {
209                    technology: "Jest".to_string(),
210                    domain: "web".to_string(),
211                    use_cases: vec![
212                        "Unit testing".to_string(),
213                        "Integration testing".to_string(),
214                    ],
215                    pros: vec![
216                        "Zero configuration".to_string(),
217                        "Great documentation".to_string(),
218                        "Snapshot testing".to_string(),
219                    ],
220                    cons: vec![
221                        "Slower than alternatives".to_string(),
222                        "Higher memory usage".to_string(),
223                    ],
224                    alternatives: vec!["Vitest".to_string(), "Mocha".to_string()],
225                },
226                TechRecommendation {
227                    technology: "Vitest".to_string(),
228                    domain: "web".to_string(),
229                    use_cases: vec![
230                        "Unit testing".to_string(),
231                        "Fast test execution".to_string(),
232                    ],
233                    pros: vec![
234                        "Lightning fast".to_string(),
235                        "Vite integration".to_string(),
236                        "Jest-compatible API".to_string(),
237                    ],
238                    cons: vec![
239                        "Newer tool".to_string(),
240                        "Smaller ecosystem".to_string(),
241                    ],
242                    alternatives: vec!["Jest".to_string(), "Mocha".to_string()],
243                },
244                TechRecommendation {
245                    technology: "Playwright".to_string(),
246                    domain: "web".to_string(),
247                    use_cases: vec![
248                        "End-to-end testing".to_string(),
249                        "Cross-browser testing".to_string(),
250                    ],
251                    pros: vec![
252                        "Cross-browser support".to_string(),
253                        "Reliable automation".to_string(),
254                        "Great debugging tools".to_string(),
255                    ],
256                    cons: vec![
257                        "Heavier than Cypress".to_string(),
258                        "Steeper learning curve".to_string(),
259                    ],
260                    alternatives: vec!["Cypress".to_string(), "Selenium".to_string()],
261                },
262                TechRecommendation {
263                    technology: "Tailwind CSS".to_string(),
264                    domain: "web".to_string(),
265                    use_cases: vec![
266                        "Rapid UI development".to_string(),
267                        "Utility-first styling".to_string(),
268                    ],
269                    pros: vec![
270                        "Rapid development".to_string(),
271                        "Consistent design".to_string(),
272                        "Small bundle size".to_string(),
273                    ],
274                    cons: vec![
275                        "Learning curve".to_string(),
276                        "HTML markup verbosity".to_string(),
277                    ],
278                    alternatives: vec!["Bootstrap".to_string(), "styled-components".to_string()],
279                },
280                TechRecommendation {
281                    technology: "styled-components".to_string(),
282                    domain: "web".to_string(),
283                    use_cases: vec![
284                        "Component-scoped styling".to_string(),
285                        "Dynamic styling".to_string(),
286                    ],
287                    pros: vec![
288                        "Component scoping".to_string(),
289                        "Dynamic styling".to_string(),
290                        "No class name conflicts".to_string(),
291                    ],
292                    cons: vec![
293                        "Runtime overhead".to_string(),
294                        "Larger bundle size".to_string(),
295                    ],
296                    alternatives: vec!["Tailwind CSS".to_string(), "CSS Modules".to_string()],
297                },
298            ],
299            patterns: vec![],
300            anti_patterns: vec![],
301        };
302
303        let domain_agent = DomainAgent {
304            id: "web-agent".to_string(),
305            domain: "web".to_string(),
306            capabilities,
307            knowledge,
308        };
309
310        WebAgent { domain_agent }
311    }
312
313    /// Get the domain of this agent
314    pub fn domain(&self) -> &str {
315        &self.domain_agent.domain
316    }
317
318    /// Get the capabilities of this agent
319    pub fn capabilities(&self) -> &[DomainCapability] {
320        &self.domain_agent.capabilities
321    }
322
323    /// Get the knowledge of this agent
324    pub fn knowledge(&self) -> &DomainKnowledge {
325        &self.domain_agent.knowledge
326    }
327
328    /// Get the underlying domain agent
329    pub fn domain_agent(&self) -> &DomainAgent {
330        &self.domain_agent
331    }
332}
333
334impl Default for WebAgent {
335    fn default() -> Self {
336        Self::new()
337    }
338}
339
340#[async_trait]
341impl Agent for WebAgent {
342    fn id(&self) -> &str {
343        &self.domain_agent.id
344    }
345
346    fn name(&self) -> &str {
347        "Web Development Agent"
348    }
349
350    fn description(&self) -> &str {
351        "Specialized agent for web development with expertise in frontend frameworks, styling, build tools, testing, performance optimization, and deployment"
352    }
353
354    fn supports(&self, task_type: TaskType) -> bool {
355        // Web agent supports code review and other web-related tasks
356        matches!(
357            task_type,
358            TaskType::CodeReview
359                | TaskType::Refactoring
360                | TaskType::Documentation
361                | TaskType::SecurityAnalysis
362        )
363    }
364
365    async fn execute(&self, input: AgentInput) -> Result<AgentOutput> {
366        // For now, return a basic output
367        // This will be enhanced with actual web-specific logic
368        let mut output = AgentOutput::default();
369
370        // Add findings based on the task type
371        match input.task.task_type {
372            TaskType::CodeReview => {
373                output.findings.push(Finding {
374                    id: Uuid::new_v4().to_string(),
375                    severity: Severity::Info,
376                    category: "web-best-practices".to_string(),
377                    message: "Web development best practices applied".to_string(),
378                    location: None,
379                    suggestion: None,
380                });
381            }
382            _ => {}
383        }
384
385        Ok(output)
386    }
387}
388
389#[cfg(test)]
390mod tests {
391    use super::*;
392
393    #[test]
394    fn test_web_agent_creation() {
395        let agent = WebAgent::new();
396        assert_eq!(agent.id(), "web-agent");
397        assert_eq!(agent.domain(), "web");
398        assert_eq!(agent.name(), "Web Development Agent");
399    }
400
401    #[test]
402    fn test_web_agent_capabilities() {
403        let agent = WebAgent::new();
404        let capabilities = agent.capabilities();
405        assert_eq!(capabilities.len(), 6);
406
407        // Check first capability
408        assert_eq!(
409            capabilities[0].name,
410            "Frontend Framework Selection"
411        );
412        assert_eq!(capabilities[0].technologies.len(), 3);
413        assert!(capabilities[0]
414            .technologies
415            .contains(&"React".to_string()));
416        assert!(capabilities[0]
417            .technologies
418            .contains(&"Vue".to_string()));
419        assert!(capabilities[0]
420            .technologies
421            .contains(&"Angular".to_string()));
422    }
423
424    #[test]
425    fn test_web_agent_knowledge() {
426        let agent = WebAgent::new();
427        let knowledge = agent.knowledge();
428        assert!(!knowledge.technology_recommendations.is_empty());
429        assert_eq!(knowledge.technology_recommendations.len(), 10);
430    }
431
432    #[test]
433    fn test_web_agent_technology_recommendations() {
434        let agent = WebAgent::new();
435        let knowledge = agent.knowledge();
436
437        // Check React recommendation
438        let react_rec = knowledge
439            .technology_recommendations
440            .iter()
441            .find(|r| r.technology == "React")
442            .expect("React recommendation not found");
443
444        assert_eq!(react_rec.domain, "web");
445        assert!(!react_rec.use_cases.is_empty());
446        assert!(!react_rec.pros.is_empty());
447        assert!(!react_rec.cons.is_empty());
448        assert!(!react_rec.alternatives.is_empty());
449    }
450
451    #[test]
452    fn test_web_agent_supports_task_types() {
453        let agent = WebAgent::new();
454        assert!(agent.supports(TaskType::CodeReview));
455        assert!(agent.supports(TaskType::Refactoring));
456        assert!(agent.supports(TaskType::Documentation));
457        assert!(agent.supports(TaskType::SecurityAnalysis));
458        assert!(!agent.supports(TaskType::TestGeneration));
459    }
460
461    #[test]
462    fn test_web_agent_default() {
463        let agent1 = WebAgent::new();
464        let agent2 = WebAgent::default();
465        assert_eq!(agent1.id(), agent2.id());
466        assert_eq!(agent1.domain(), agent2.domain());
467    }
468
469    #[test]
470    fn test_web_agent_clone() {
471        let agent1 = WebAgent::new();
472        let agent2 = agent1.clone();
473        assert_eq!(agent1.id(), agent2.id());
474        assert_eq!(agent1.domain(), agent2.domain());
475        assert_eq!(agent1.capabilities().len(), agent2.capabilities().len());
476    }
477
478    #[test]
479    fn test_web_agent_all_frameworks_present() {
480        let agent = WebAgent::new();
481        let knowledge = agent.knowledge();
482
483        let frameworks = vec!["React", "Vue", "Angular"];
484        for framework in frameworks {
485            let found = knowledge
486                .technology_recommendations
487                .iter()
488                .any(|r| r.technology == framework);
489            assert!(found, "Framework {} not found in recommendations", framework);
490        }
491    }
492
493    #[test]
494    fn test_web_agent_all_build_tools_present() {
495        let agent = WebAgent::new();
496        let knowledge = agent.knowledge();
497
498        let build_tools = vec!["Vite", "Webpack"];
499        for tool in build_tools {
500            let found = knowledge
501                .technology_recommendations
502                .iter()
503                .any(|r| r.technology == tool);
504            assert!(found, "Build tool {} not found in recommendations", tool);
505        }
506    }
507
508    #[test]
509    fn test_web_agent_all_testing_frameworks_present() {
510        let agent = WebAgent::new();
511        let knowledge = agent.knowledge();
512
513        let testing_frameworks = vec!["Jest", "Vitest", "Playwright"];
514        for framework in testing_frameworks {
515            let found = knowledge
516                .technology_recommendations
517                .iter()
518                .any(|r| r.technology == framework);
519            assert!(
520                found,
521                "Testing framework {} not found in recommendations",
522                framework
523            );
524        }
525    }
526
527    #[test]
528    fn test_web_agent_styling_solutions_present() {
529        let agent = WebAgent::new();
530        let knowledge = agent.knowledge();
531
532        let styling_solutions = vec!["Tailwind CSS", "styled-components"];
533        for solution in styling_solutions {
534            let found = knowledge
535                .technology_recommendations
536                .iter()
537                .any(|r| r.technology == solution);
538            assert!(
539                found,
540                "Styling solution {} not found in recommendations",
541                solution
542            );
543        }
544    }
545
546    #[tokio::test]
547    async fn test_web_agent_execute() {
548        use crate::models::{
549            AgentConfig, AgentTask, ProjectContext, TaskOptions, TaskScope, TaskTarget,
550        };
551        use std::path::PathBuf;
552
553        let agent = WebAgent::new();
554        let input = AgentInput {
555            task: AgentTask {
556                id: "task-1".to_string(),
557                task_type: TaskType::CodeReview,
558                target: TaskTarget {
559                    files: vec![PathBuf::from("test.tsx")],
560                    scope: TaskScope::File,
561                },
562                options: TaskOptions::default(),
563            },
564            context: ProjectContext {
565                name: "web-project".to_string(),
566                root: PathBuf::from("/tmp/web-project"),
567            },
568            config: AgentConfig::default(),
569        };
570
571        let result = agent.execute(input).await;
572        assert!(result.is_ok());
573
574        let output = result.unwrap();
575        assert!(!output.findings.is_empty());
576    }
577
578    #[test]
579    fn test_web_agent_react_alternatives() {
580        let agent = WebAgent::new();
581        let knowledge = agent.knowledge();
582
583        let react_rec = knowledge
584            .technology_recommendations
585            .iter()
586            .find(|r| r.technology == "React")
587            .expect("React recommendation not found");
588
589        assert!(react_rec.alternatives.contains(&"Vue".to_string()));
590        assert!(react_rec.alternatives.contains(&"Angular".to_string()));
591    }
592
593    #[test]
594    fn test_web_agent_vite_alternatives() {
595        let agent = WebAgent::new();
596        let knowledge = agent.knowledge();
597
598        let vite_rec = knowledge
599            .technology_recommendations
600            .iter()
601            .find(|r| r.technology == "Vite")
602            .expect("Vite recommendation not found");
603
604        assert!(vite_rec.alternatives.contains(&"Webpack".to_string()));
605        assert!(vite_rec.alternatives.contains(&"Parcel".to_string()));
606    }
607
608    #[test]
609    fn test_web_agent_jest_alternatives() {
610        let agent = WebAgent::new();
611        let knowledge = agent.knowledge();
612
613        let jest_rec = knowledge
614            .technology_recommendations
615            .iter()
616            .find(|r| r.technology == "Jest")
617            .expect("Jest recommendation not found");
618
619        assert!(jest_rec.alternatives.contains(&"Vitest".to_string()));
620        assert!(jest_rec.alternatives.contains(&"Mocha".to_string()));
621    }
622}