1use 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#[derive(Debug, Clone)]
34pub struct WebAgent {
35 domain_agent: DomainAgent,
36}
37
38impl WebAgent {
39 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 pub fn domain(&self) -> &str {
315 &self.domain_agent.domain
316 }
317
318 pub fn capabilities(&self) -> &[DomainCapability] {
320 &self.domain_agent.capabilities
321 }
322
323 pub fn knowledge(&self) -> &DomainKnowledge {
325 &self.domain_agent.knowledge
326 }
327
328 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 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 let mut output = AgentOutput::default();
369
370 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 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 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}