ricecoder_agents/domain/
knowledge.rs

1//! Knowledge base for domain-specific expertise
2
3use crate::domain::error::{DomainError, DomainResult};
4use crate::domain::models::{BestPractice, Pattern, TechRecommendation, AntiPattern};
5use std::collections::HashMap;
6use std::sync::{Arc, RwLock};
7
8/// Knowledge base for domain expertise
9///
10/// This struct stores and retrieves domain-specific knowledge,
11/// including best practices, technology recommendations, patterns, and anti-patterns.
12///
13/// # Examples
14///
15/// ```ignore
16/// use ricecoder_agents::domain::KnowledgeBase;
17///
18/// let kb = KnowledgeBase::new();
19/// let recommendations = kb.get_recommendations("web", "framework")?;
20/// ```
21#[derive(Debug, Clone)]
22pub struct KnowledgeBase {
23    best_practices: Arc<RwLock<HashMap<String, Vec<BestPractice>>>>,
24    tech_recommendations: Arc<RwLock<HashMap<String, Vec<TechRecommendation>>>>,
25    patterns: Arc<RwLock<HashMap<String, Vec<Pattern>>>>,
26    anti_patterns: Arc<RwLock<HashMap<String, Vec<AntiPattern>>>>,
27}
28
29impl KnowledgeBase {
30    /// Create a new knowledge base
31    pub fn new() -> Self {
32        Self {
33            best_practices: Arc::new(RwLock::new(HashMap::new())),
34            tech_recommendations: Arc::new(RwLock::new(HashMap::new())),
35            patterns: Arc::new(RwLock::new(HashMap::new())),
36            anti_patterns: Arc::new(RwLock::new(HashMap::new())),
37        }
38    }
39
40    /// Add a best practice
41    ///
42    /// # Arguments
43    ///
44    /// * `domain` - Domain identifier
45    /// * `practice` - Best practice to add
46    pub fn add_best_practice(&self, domain: &str, practice: BestPractice) -> DomainResult<()> {
47        let mut practices = self.best_practices.write().map_err(|e| {
48            DomainError::internal(format!("Failed to acquire write lock: {}", e))
49        })?;
50
51        practices
52            .entry(domain.to_string())
53            .or_insert_with(Vec::new)
54            .push(practice);
55
56        Ok(())
57    }
58
59    /// Get best practices for a domain
60    ///
61    /// # Arguments
62    ///
63    /// * `domain` - Domain identifier
64    ///
65    /// # Returns
66    ///
67    /// Returns a vector of best practices for the domain
68    pub fn get_best_practices(&self, domain: &str) -> DomainResult<Vec<BestPractice>> {
69        let practices = self.best_practices.read().map_err(|e| {
70            DomainError::internal(format!("Failed to acquire read lock: {}", e))
71        })?;
72
73        Ok(practices
74            .get(domain)
75            .cloned()
76            .unwrap_or_default())
77    }
78
79    /// Add a technology recommendation
80    ///
81    /// # Arguments
82    ///
83    /// * `domain` - Domain identifier
84    /// * `recommendation` - Technology recommendation to add
85    pub fn add_tech_recommendation(
86        &self,
87        domain: &str,
88        recommendation: TechRecommendation,
89    ) -> DomainResult<()> {
90        let mut recommendations = self.tech_recommendations.write().map_err(|e| {
91            DomainError::internal(format!("Failed to acquire write lock: {}", e))
92        })?;
93
94        recommendations
95            .entry(domain.to_string())
96            .or_insert_with(Vec::new)
97            .push(recommendation);
98
99        Ok(())
100    }
101
102    /// Get technology recommendations for a domain
103    ///
104    /// # Arguments
105    ///
106    /// * `domain` - Domain identifier
107    ///
108    /// # Returns
109    ///
110    /// Returns a vector of technology recommendations for the domain
111    pub fn get_tech_recommendations(&self, domain: &str) -> DomainResult<Vec<TechRecommendation>> {
112        let recommendations = self.tech_recommendations.read().map_err(|e| {
113            DomainError::internal(format!("Failed to acquire read lock: {}", e))
114        })?;
115
116        Ok(recommendations
117            .get(domain)
118            .cloned()
119            .unwrap_or_default())
120    }
121
122    /// Get technology recommendation by technology name
123    ///
124    /// # Arguments
125    ///
126    /// * `domain` - Domain identifier
127    /// * `technology` - Technology name
128    ///
129    /// # Returns
130    ///
131    /// Returns the technology recommendation if found
132    pub fn get_tech_recommendation(
133        &self,
134        domain: &str,
135        technology: &str,
136    ) -> DomainResult<TechRecommendation> {
137        let recommendations = self.get_tech_recommendations(domain)?;
138
139        recommendations
140            .into_iter()
141            .find(|r| r.technology == technology)
142            .ok_or_else(|| DomainError::knowledge_not_found(technology))
143    }
144
145    /// Add a pattern
146    ///
147    /// # Arguments
148    ///
149    /// * `domain` - Domain identifier
150    /// * `pattern` - Pattern to add
151    pub fn add_pattern(&self, domain: &str, pattern: Pattern) -> DomainResult<()> {
152        let mut patterns = self.patterns.write().map_err(|e| {
153            DomainError::internal(format!("Failed to acquire write lock: {}", e))
154        })?;
155
156        patterns
157            .entry(domain.to_string())
158            .or_insert_with(Vec::new)
159            .push(pattern);
160
161        Ok(())
162    }
163
164    /// Get patterns for a domain
165    ///
166    /// # Arguments
167    ///
168    /// * `domain` - Domain identifier
169    ///
170    /// # Returns
171    ///
172    /// Returns a vector of patterns for the domain
173    pub fn get_patterns(&self, domain: &str) -> DomainResult<Vec<Pattern>> {
174        let patterns = self.patterns.read().map_err(|e| {
175            DomainError::internal(format!("Failed to acquire read lock: {}", e))
176        })?;
177
178        Ok(patterns
179            .get(domain)
180            .cloned()
181            .unwrap_or_default())
182    }
183
184    /// Add an anti-pattern
185    ///
186    /// # Arguments
187    ///
188    /// * `domain` - Domain identifier
189    /// * `anti_pattern` - Anti-pattern to add
190    pub fn add_anti_pattern(&self, domain: &str, anti_pattern: AntiPattern) -> DomainResult<()> {
191        let mut anti_patterns = self.anti_patterns.write().map_err(|e| {
192            DomainError::internal(format!("Failed to acquire write lock: {}", e))
193        })?;
194
195        anti_patterns
196            .entry(domain.to_string())
197            .or_insert_with(Vec::new)
198            .push(anti_pattern);
199
200        Ok(())
201    }
202
203    /// Get anti-patterns for a domain
204    ///
205    /// # Arguments
206    ///
207    /// * `domain` - Domain identifier
208    ///
209    /// # Returns
210    ///
211    /// Returns a vector of anti-patterns for the domain
212    pub fn get_anti_patterns(&self, domain: &str) -> DomainResult<Vec<AntiPattern>> {
213        let anti_patterns = self.anti_patterns.read().map_err(|e| {
214            DomainError::internal(format!("Failed to acquire read lock: {}", e))
215        })?;
216
217        Ok(anti_patterns
218            .get(domain)
219            .cloned()
220            .unwrap_or_default())
221    }
222
223    /// Clear all knowledge for a domain
224    ///
225    /// # Arguments
226    ///
227    /// * `domain` - Domain identifier
228    pub fn clear_domain(&self, domain: &str) -> DomainResult<()> {
229        let mut practices = self.best_practices.write().map_err(|e| {
230            DomainError::internal(format!("Failed to acquire write lock: {}", e))
231        })?;
232        practices.remove(domain);
233
234        let mut recommendations = self.tech_recommendations.write().map_err(|e| {
235            DomainError::internal(format!("Failed to acquire write lock: {}", e))
236        })?;
237        recommendations.remove(domain);
238
239        let mut patterns = self.patterns.write().map_err(|e| {
240            DomainError::internal(format!("Failed to acquire write lock: {}", e))
241        })?;
242        patterns.remove(domain);
243
244        let mut anti_patterns = self.anti_patterns.write().map_err(|e| {
245            DomainError::internal(format!("Failed to acquire write lock: {}", e))
246        })?;
247        anti_patterns.remove(domain);
248
249        Ok(())
250    }
251
252    /// Clear all knowledge
253    pub fn clear_all(&self) -> DomainResult<()> {
254        let mut practices = self.best_practices.write().map_err(|e| {
255            DomainError::internal(format!("Failed to acquire write lock: {}", e))
256        })?;
257        practices.clear();
258
259        let mut recommendations = self.tech_recommendations.write().map_err(|e| {
260            DomainError::internal(format!("Failed to acquire write lock: {}", e))
261        })?;
262        recommendations.clear();
263
264        let mut patterns = self.patterns.write().map_err(|e| {
265            DomainError::internal(format!("Failed to acquire write lock: {}", e))
266        })?;
267        patterns.clear();
268
269        let mut anti_patterns = self.anti_patterns.write().map_err(|e| {
270            DomainError::internal(format!("Failed to acquire write lock: {}", e))
271        })?;
272        anti_patterns.clear();
273
274        Ok(())
275    }
276}
277
278impl Default for KnowledgeBase {
279    fn default() -> Self {
280        Self::new()
281    }
282}
283
284#[cfg(test)]
285mod tests {
286    use super::*;
287
288    fn create_test_practice(domain: &str) -> BestPractice {
289        BestPractice {
290            title: "Test Practice".to_string(),
291            description: "A test practice".to_string(),
292            domain: domain.to_string(),
293            technologies: vec!["Tech1".to_string()],
294            implementation: "Implementation".to_string(),
295        }
296    }
297
298    fn create_test_recommendation(domain: &str) -> TechRecommendation {
299        TechRecommendation {
300            technology: "React".to_string(),
301            domain: domain.to_string(),
302            use_cases: vec!["SPAs".to_string()],
303            pros: vec!["Ecosystem".to_string()],
304            cons: vec!["Learning curve".to_string()],
305            alternatives: vec!["Vue".to_string()],
306        }
307    }
308
309    fn create_test_pattern(domain: &str) -> Pattern {
310        Pattern {
311            name: "Test Pattern".to_string(),
312            description: "A test pattern".to_string(),
313            domain: domain.to_string(),
314            technologies: vec!["Tech1".to_string()],
315            use_cases: vec!["Use case 1".to_string()],
316        }
317    }
318
319    fn create_test_anti_pattern(domain: &str) -> AntiPattern {
320        AntiPattern {
321            name: "Test Anti-pattern".to_string(),
322            description: "A test anti-pattern".to_string(),
323            domain: domain.to_string(),
324            why_avoid: "Reason".to_string(),
325            better_alternative: "Alternative".to_string(),
326        }
327    }
328
329    #[test]
330    fn test_knowledge_base_creation() {
331        let kb = KnowledgeBase::new();
332        assert!(kb.get_best_practices("web").unwrap().is_empty());
333    }
334
335    #[test]
336    fn test_add_best_practice() {
337        let kb = KnowledgeBase::new();
338        let practice = create_test_practice("web");
339
340        kb.add_best_practice("web", practice).unwrap();
341        let practices = kb.get_best_practices("web").unwrap();
342
343        assert_eq!(practices.len(), 1);
344        assert_eq!(practices[0].title, "Test Practice");
345    }
346
347    #[test]
348    fn test_add_tech_recommendation() {
349        let kb = KnowledgeBase::new();
350        let recommendation = create_test_recommendation("web");
351
352        kb.add_tech_recommendation("web", recommendation).unwrap();
353        let recommendations = kb.get_tech_recommendations("web").unwrap();
354
355        assert_eq!(recommendations.len(), 1);
356        assert_eq!(recommendations[0].technology, "React");
357    }
358
359    #[test]
360    fn test_get_tech_recommendation() {
361        let kb = KnowledgeBase::new();
362        let recommendation = create_test_recommendation("web");
363
364        kb.add_tech_recommendation("web", recommendation).unwrap();
365        let retrieved = kb.get_tech_recommendation("web", "React").unwrap();
366
367        assert_eq!(retrieved.technology, "React");
368    }
369
370    #[test]
371    fn test_get_nonexistent_tech_recommendation() {
372        let kb = KnowledgeBase::new();
373        let result = kb.get_tech_recommendation("web", "NonExistent");
374
375        assert!(result.is_err());
376    }
377
378    #[test]
379    fn test_add_pattern() {
380        let kb = KnowledgeBase::new();
381        let pattern = create_test_pattern("web");
382
383        kb.add_pattern("web", pattern).unwrap();
384        let patterns = kb.get_patterns("web").unwrap();
385
386        assert_eq!(patterns.len(), 1);
387        assert_eq!(patterns[0].name, "Test Pattern");
388    }
389
390    #[test]
391    fn test_add_anti_pattern() {
392        let kb = KnowledgeBase::new();
393        let anti_pattern = create_test_anti_pattern("web");
394
395        kb.add_anti_pattern("web", anti_pattern).unwrap();
396        let anti_patterns = kb.get_anti_patterns("web").unwrap();
397
398        assert_eq!(anti_patterns.len(), 1);
399        assert_eq!(anti_patterns[0].name, "Test Anti-pattern");
400    }
401
402    #[test]
403    fn test_clear_domain() {
404        let kb = KnowledgeBase::new();
405
406        kb.add_best_practice("web", create_test_practice("web")).unwrap();
407        kb.add_tech_recommendation("web", create_test_recommendation("web")).unwrap();
408
409        kb.clear_domain("web").unwrap();
410
411        assert!(kb.get_best_practices("web").unwrap().is_empty());
412        assert!(kb.get_tech_recommendations("web").unwrap().is_empty());
413    }
414
415    #[test]
416    fn test_clear_all() {
417        let kb = KnowledgeBase::new();
418
419        kb.add_best_practice("web", create_test_practice("web")).unwrap();
420        kb.add_best_practice("backend", create_test_practice("backend")).unwrap();
421
422        kb.clear_all().unwrap();
423
424        assert!(kb.get_best_practices("web").unwrap().is_empty());
425        assert!(kb.get_best_practices("backend").unwrap().is_empty());
426    }
427
428    #[test]
429    fn test_multiple_domains() {
430        let kb = KnowledgeBase::new();
431
432        kb.add_best_practice("web", create_test_practice("web")).unwrap();
433        kb.add_best_practice("backend", create_test_practice("backend")).unwrap();
434
435        let web_practices = kb.get_best_practices("web").unwrap();
436        let backend_practices = kb.get_best_practices("backend").unwrap();
437
438        assert_eq!(web_practices.len(), 1);
439        assert_eq!(backend_practices.len(), 1);
440    }
441
442    #[test]
443    fn test_default_knowledge_base() {
444        let kb = KnowledgeBase::default();
445        assert!(kb.get_best_practices("web").unwrap().is_empty());
446    }
447
448    #[test]
449    fn test_multiple_practices_per_domain() {
450        let kb = KnowledgeBase::new();
451
452        kb.add_best_practice("web", create_test_practice("web")).unwrap();
453        let mut practice2 = create_test_practice("web");
454        practice2.title = "Practice 2".to_string();
455        kb.add_best_practice("web", practice2).unwrap();
456
457        let practices = kb.get_best_practices("web").unwrap();
458        assert_eq!(practices.len(), 2);
459    }
460
461    #[test]
462    fn test_multiple_recommendations_per_domain() {
463        let kb = KnowledgeBase::new();
464
465        kb.add_tech_recommendation("web", create_test_recommendation("web")).unwrap();
466        let mut rec2 = create_test_recommendation("web");
467        rec2.technology = "Vue".to_string();
468        kb.add_tech_recommendation("web", rec2).unwrap();
469
470        let recommendations = kb.get_tech_recommendations("web").unwrap();
471        assert_eq!(recommendations.len(), 2);
472    }
473}