Skip to main content
Glama

In Memoria

learning.rs74.1 kB
//! Core learning algorithms for pattern discovery and analysis #[cfg(feature = "napi-bindings")] use napi_derive::napi; use crate::patterns::implementation::ImplementationPatternAnalyzer; use crate::patterns::naming::NamingPatternAnalyzer; use crate::patterns::prediction::ApproachPredictor; use crate::patterns::structural::StructuralPatternAnalyzer; use crate::patterns::types::{ Pattern, PatternAnalysisResult, PatternLearner as PatternLearnerTrait, }; use crate::types::{ParseError, SemanticConcept}; use serde_json::{from_str, Value}; use std::collections::{HashMap, HashSet}; use std::fs; use walkdir::WalkDir; /// Core learning engine that orchestrates pattern discovery across all domains #[cfg_attr(feature = "napi-bindings", napi)] pub struct PatternLearningEngine { naming_analyzer: NamingPatternAnalyzer, structural_analyzer: StructuralPatternAnalyzer, implementation_analyzer: ImplementationPatternAnalyzer, approach_predictor: ApproachPredictor, learned_patterns: HashMap<String, Pattern>, learning_metrics: LearningMetrics, confidence_threshold: f64, } #[derive(Debug, Clone)] pub struct LearningMetrics { pub total_patterns_learned: usize, pub confidence_distribution: HashMap<String, usize>, // confidence ranges pub pattern_type_counts: HashMap<String, usize>, pub learning_accuracy: f64, pub last_learning_timestamp: Option<String>, } #[derive(Debug, Clone)] struct LearningSession { session_id: String, patterns_discovered: Vec<Pattern>, analysis_duration_ms: u64, files_analyzed: usize, concepts_analyzed: usize, } #[derive(Debug, Clone)] pub struct PatternEvolution { pub pattern_id: String, pub confidence_history: Vec<(String, f64)>, // timestamp, confidence pub frequency_history: Vec<(String, u32)>, // timestamp, frequency pub evolution_trend: EvolutionTrend, } #[derive(Debug, Clone)] pub enum EvolutionTrend { Improving, // Confidence/frequency increasing Stable, // Confidence/frequency stable Declining, // Confidence/frequency decreasing Emerging, // New pattern Deprecated, // Pattern no longer seen } #[cfg_attr(feature = "napi-bindings", napi)] impl PatternLearningEngine { #[cfg_attr(feature = "napi-bindings", napi(constructor))] pub fn new() -> Self { PatternLearningEngine { naming_analyzer: NamingPatternAnalyzer::new(), structural_analyzer: StructuralPatternAnalyzer::new(), implementation_analyzer: ImplementationPatternAnalyzer::new(), approach_predictor: ApproachPredictor::new(), learned_patterns: HashMap::new(), learning_metrics: LearningMetrics { total_patterns_learned: 0, confidence_distribution: HashMap::new(), pattern_type_counts: HashMap::new(), learning_accuracy: 0.0, last_learning_timestamp: None, }, confidence_threshold: 0.5, } } /// Learn patterns from an entire codebase /// /// # Safety /// This function is marked unsafe for NAPI compatibility. It performs file system operations /// and pattern analysis that are inherently safe but marked unsafe for JavaScript interop. #[cfg_attr(feature = "napi-bindings", napi)] pub async unsafe fn learn_from_codebase( &mut self, path: String, ) -> Result<Vec<Pattern>, ParseError> { let session_start = std::time::Instant::now(); let mut session = LearningSession { session_id: format!( "session_{}", std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_secs() ), patterns_discovered: Vec::new(), analysis_duration_ms: 0, files_analyzed: 0, concepts_analyzed: 0, }; // Phase 1: Collect semantic concepts from the codebase let concepts = self.extract_semantic_concepts(&path).await?; session.concepts_analyzed = concepts.len(); // Count unique files analyzed let unique_files: std::collections::HashSet<_> = concepts.iter().map(|c| &c.file_path).collect(); session.files_analyzed = unique_files.len(); // Phase 2: Learn naming patterns let naming_patterns = self.learn_naming_patterns(&concepts, &path).await?; session.patterns_discovered.extend(naming_patterns); // Phase 3: Learn structural patterns let structural_patterns = self.learn_structural_patterns(&concepts, &path).await?; session.patterns_discovered.extend(structural_patterns); // Phase 4: Learn implementation patterns let implementation_patterns = self.learn_implementation_patterns(&concepts, &path).await?; session.patterns_discovered.extend(implementation_patterns); // Phase 5: Update approach predictor with new patterns self.approach_predictor .update_patterns(session.patterns_discovered.clone()); // Phase 6: Consolidate and validate patterns let patterns_for_validation = session.patterns_discovered.clone(); let validated_patterns = self.validate_and_consolidate_patterns(patterns_for_validation)?; // Phase 7: Update learning metrics session.analysis_duration_ms = session_start.elapsed().as_millis() as u64; self.update_learning_metrics(&validated_patterns, &session); // Store learned patterns for pattern in &validated_patterns { self.learned_patterns .insert(pattern.id.clone(), pattern.clone()); } Ok(validated_patterns) } /// Learn from file changes (incremental learning) /// /// # Safety /// This function is marked unsafe for NAPI compatibility. It performs pattern analysis /// operations that are inherently safe but marked unsafe for JavaScript interop. #[cfg_attr(feature = "napi-bindings", napi)] pub async unsafe fn learn_from_changes( &mut self, old_content: String, new_content: String, file_path: String, language: String, ) -> Result<Vec<Pattern>, ParseError> { let mut new_patterns = Vec::new(); // Learn naming pattern changes let naming_changes = self.naming_analyzer .learn_from_changes(&old_content, &new_content, &language)?; new_patterns.extend(naming_changes); // Learn structural changes (simplified - would need AST diff in practice) if self.has_structural_changes(&old_content, &new_content) { let structural_changes = self .learn_structural_changes(&old_content, &new_content, &file_path) .await?; new_patterns.extend(structural_changes); } // Update internal state for pattern in &new_patterns { self.learned_patterns .insert(pattern.id.clone(), pattern.clone()); } // Use helper methods for additional learning let change_type = self.detect_change_type(&old_content, &new_content); self.learn_from_change_type(&change_type).await?; self.learn_from_file_context(&file_path).await?; // Boost confidence for related patterns when we find successful patterns if !new_patterns.is_empty() { let primary_concept = self.extract_primary_concept(&new_patterns); self.boost_related_pattern_confidence(&primary_concept, 0.05) .await?; } // Update learning metrics self.update_incremental_metrics(&new_patterns); Ok(new_patterns) } /// Learn from analysis data (JSON format) /// /// # Safety /// This function is marked unsafe for NAPI compatibility. It performs data parsing and /// learning operations that are inherently safe but marked unsafe for JavaScript interop. #[cfg_attr(feature = "napi-bindings", napi)] pub async unsafe fn learn_from_analysis( &mut self, analysis_data: String, ) -> Result<bool, ParseError> { let data: Value = from_str(&analysis_data).map_err(|e| { ParseError::from_reason(format!("Failed to parse analysis data: {}", e)) })?; // Extract concepts from analysis data let concepts = self.parse_concepts_from_analysis(&data)?; // Also try to parse any existing patterns in the data if let Some(patterns_array) = data.get("patterns").and_then(|p| p.as_array()) { for pattern_json in patterns_array { if let Ok(pattern) = self.parse_pattern_from_json(pattern_json) { self.learned_patterns.insert(pattern.id.clone(), pattern); } } } if !concepts.is_empty() { // Learn patterns from the concepts let naming_patterns = self .naming_analyzer .analyze_concepts(&concepts, "mixed") .unwrap_or_default(); let mut implementation_patterns = self .implementation_analyzer .analyze_concepts(&concepts) .unwrap_or_default(); let structural_patterns = self .structural_analyzer .analyze_concept_structures(&concepts) .unwrap_or_default(); // Combine all patterns let mut all_patterns = naming_patterns; all_patterns.append(&mut implementation_patterns); all_patterns.extend(structural_patterns); // Store patterns that meet confidence threshold let mut learned_count = 0; for pattern in all_patterns { if pattern.confidence >= self.confidence_threshold { self.learned_patterns.insert(pattern.id.clone(), pattern); learned_count += 1; } } // Update predictor with historical approach data if available if let Some(approaches) = data.get("approaches") { if let Ok(approach_data) = serde_json::to_string(approaches) { let _ = self .approach_predictor .learn_from_approaches(&approach_data); } } // Update metrics self.learning_metrics.total_patterns_learned += learned_count; Ok(learned_count > 0) } else { Ok(false) } } /// Get comprehensive analysis of learned patterns #[cfg_attr(feature = "napi-bindings", napi)] pub fn analyze_patterns( &self, concepts: Vec<SemanticConcept>, ) -> Result<PatternAnalysisResult, ParseError> { let mut detected = Vec::new(); let mut violations = Vec::new(); let mut recommendations = Vec::new(); // Analyze with each specialized analyzer // Naming analysis let naming_violations = self.naming_analyzer.detect_violations(&concepts, "mixed"); violations.extend(naming_violations); let naming_recommendations = self.naming_analyzer.generate_recommendations("mixed"); recommendations.extend(naming_recommendations); // Structural analysis let structural_violations = self .structural_analyzer .detect_structural_violations(&concepts); violations.extend(structural_violations); let structural_recommendations = self .structural_analyzer .generate_structural_recommendations(&concepts); recommendations.extend(structural_recommendations); // Implementation analysis let implementation_violations = self.implementation_analyzer.detect_antipatterns(&concepts); violations.extend(implementation_violations); let implementation_recommendations = self .implementation_analyzer .generate_recommendations(&concepts); recommendations.extend(implementation_recommendations); // Detected patterns for pattern in self.learned_patterns.values() { detected.push(format!( "{}: {} (confidence: {:.2})", pattern.pattern_type, pattern.description, pattern.confidence )); } Ok(PatternAnalysisResult { detected, violations, recommendations, learned: Some(self.learned_patterns.values().cloned().collect()), }) } /// Predict best approach for a problem #[cfg_attr(feature = "napi-bindings", napi)] pub fn predict_approach( &self, problem_description: String, context: Option<String>, ) -> Result<crate::patterns::types::ApproachPrediction, ParseError> { self.approach_predictor .predict_approach(problem_description, context) } /// Get learning metrics and statistics pub fn get_learning_metrics(&self) -> &LearningMetrics { &self.learning_metrics } /// Set confidence threshold for pattern acceptance pub fn set_confidence_threshold(&mut self, threshold: f64) { self.confidence_threshold = threshold.clamp(0.0, 1.0); } /// Get pattern evolution data pub fn get_pattern_evolution(&self, pattern_id: &str) -> Option<PatternEvolution> { // This would track pattern changes over time // Simplified implementation for now if self.learned_patterns.contains_key(pattern_id) { Some(PatternEvolution { pattern_id: pattern_id.to_string(), confidence_history: Vec::new(), frequency_history: Vec::new(), evolution_trend: EvolutionTrend::Stable, }) } else { None } } /// Get all learned patterns (for legacy compatibility) pub fn get_learned_patterns(&self) -> Vec<Pattern> { self.learned_patterns.values().cloned().collect() } /// Insert a pattern (for external use and testing) pub fn insert_pattern(&mut self, id: String, pattern: Pattern) { self.learned_patterns.insert(id, pattern); } /// Get a specific pattern by ID pub fn get_pattern(&self, id: &str) -> Option<&Pattern> { self.learned_patterns.get(id) } /// Check if a pattern exists pub fn has_pattern(&self, id: &str) -> bool { self.learned_patterns.contains_key(id) } /// Updates patterns based on file changes (from original implementation) /// /// # Safety /// This function uses unsafe because it needs to interact with the Node.js runtime /// through N-API bindings. The caller must ensure the change data is valid JSON. #[cfg_attr(feature = "napi-bindings", napi)] pub async unsafe fn update_from_change( &mut self, change_data: String, ) -> Result<bool, ParseError> { self.update_from_change_internal(change_data).await } /// Internal implementation for updating patterns from file changes (from original implementation) pub async fn update_from_change_internal( &mut self, change_data: String, ) -> Result<bool, ParseError> { // Parse the change data JSON let change: Value = match from_str(&change_data) { Ok(data) => data, Err(e) => { eprintln!("Failed to parse change data: {}", e); return Ok(false); } }; let mut patterns_updated = false; // Extract change information let change_type = change .get("type") .and_then(|t| t.as_str()) .unwrap_or("unknown"); let file_path = change.get("path").and_then(|p| p.as_str()); let content = change.get("content").and_then(|c| c.as_str()); let language = change.get("language").and_then(|l| l.as_str()); // Update patterns based on change type match change_type { "add" | "create" => { patterns_updated |= self .handle_file_addition(file_path, content, language) .await?; } "modify" | "change" => { patterns_updated |= self .handle_file_modification(file_path, content, language) .await?; } "delete" | "remove" => { patterns_updated |= self.handle_file_deletion(file_path).await?; } "rename" | "move" => { patterns_updated |= self.handle_file_rename(file_path, &change).await?; } _ => { // Handle unknown change types by treating as modification patterns_updated |= self .handle_file_modification(file_path, content, language) .await?; } } // Learn from the overall change pattern patterns_updated |= self .learn_from_change_pattern(change_type, file_path, language) .await?; // Update usage statistics for related patterns if let (Some(path), Some(lang)) = (file_path, language) { patterns_updated |= self.update_language_usage_patterns(path, lang).await?; } Ok(patterns_updated) } /// Helper method to update pattern frequency (from original implementation) async fn update_pattern_frequency( &mut self, pattern_type: &str, increment: u32, ) -> Result<bool, ParseError> { if let Some(pattern) = self.learned_patterns.get_mut(pattern_type) { pattern.frequency += increment; // Adjust confidence based on increased usage pattern.confidence = (pattern.confidence + 0.05).min(0.95); Ok(true) } else { // Create a new pattern if it doesn't exist let new_pattern = Pattern { id: format!("learned_{}_{}", pattern_type, self.generate_pattern_id()), pattern_type: pattern_type.to_string(), description: format!("Pattern learned from analysis: {}", pattern_type), frequency: increment, confidence: 0.3, // Start with low confidence for new patterns examples: vec![], contexts: vec!["learned".to_string()], }; self.learned_patterns .insert(new_pattern.id.clone(), new_pattern); Ok(true) } } /// Helper method to boost confidence of patterns related to a concept (from original implementation) async fn boost_related_pattern_confidence( &mut self, concept: &str, boost: f64, ) -> Result<bool, ParseError> { let mut updated = false; for pattern in self.learned_patterns.values_mut() { // Check if pattern is related to the concept if pattern .description .to_lowercase() .contains(&concept.to_lowercase()) || pattern .pattern_type .to_lowercase() .contains(&concept.to_lowercase()) || pattern .contexts .iter() .any(|c| c.to_lowercase().contains(&concept.to_lowercase())) { pattern.confidence = (pattern.confidence + boost).min(0.95); updated = true; } } Ok(updated) } /// Helper method to learn from change type (from original implementation) async fn learn_from_change_type(&mut self, change_type: &str) -> Result<bool, ParseError> { let pattern_type = format!("change_{}", change_type); self.update_pattern_frequency(&pattern_type, 1).await } /// Helper method to learn from file context (from original implementation) async fn learn_from_file_context(&mut self, file_path: &str) -> Result<bool, ParseError> { let mut updated = false; // Learn from file extension if let Some(extension) = std::path::Path::new(file_path) .extension() .and_then(|s| s.to_str()) { let pattern_type = format!("file_type_{}", extension); updated |= self.update_pattern_frequency(&pattern_type, 1).await?; } // Learn from directory structure if let Some(parent) = std::path::Path::new(file_path).parent() { if let Some(dir_name) = parent.file_name().and_then(|s| s.to_str()) { let pattern_type = format!("directory_{}", dir_name); updated |= self.update_pattern_frequency(&pattern_type, 1).await?; } } Ok(updated) } /// Helper method to parse pattern from JSON (from original implementation) fn parse_pattern_from_json(&self, json: &Value) -> Result<Pattern, serde_json::Error> { // Extract pattern fields from JSON let id = json .get("id") .and_then(|v| v.as_str()) .unwrap_or(&format!("parsed_{}", self.generate_pattern_id())) .to_string(); let pattern_type = json .get("type") .or_else(|| json.get("patternType")) .and_then(|v| v.as_str()) .unwrap_or("unknown") .to_string(); let description = json .get("description") .and_then(|v| v.as_str()) .unwrap_or("Pattern learned from analysis") .to_string(); let frequency = json.get("frequency").and_then(|v| v.as_u64()).unwrap_or(1) as u32; let confidence = json .get("confidence") .and_then(|v| v.as_f64()) .unwrap_or(0.5); let contexts = json .get("contexts") .and_then(|v| v.as_array()) .map(|arr| { arr.iter() .filter_map(|v| v.as_str().map(|s| s.to_string())) .collect() }) .unwrap_or_else(|| vec!["analysis".to_string()]); // Parse examples if available let examples = json .get("examples") .and_then(|v| v.as_array()) .map(|arr| { arr.iter() .filter_map(|ex| self.parse_example_from_json(ex)) .collect() }) .unwrap_or_default(); Ok(Pattern { id, pattern_type, description, frequency, confidence, examples, contexts, }) } /// Helper method to parse example from JSON (from original implementation) fn parse_example_from_json( &self, json: &Value, ) -> Option<crate::patterns::types::PatternExample> { let code = json.get("code")?.as_str()?.to_string(); let file_path = json .get("filePath") .or_else(|| json.get("file_path")) .and_then(|v| v.as_str()) .unwrap_or("unknown") .to_string(); let line_range = if let Some(range) = json.get("lineRange").or_else(|| json.get("line_range")) { crate::types::LineRange { start: range.get("start").and_then(|v| v.as_u64()).unwrap_or(1) as u32, end: range.get("end").and_then(|v| v.as_u64()).unwrap_or(1) as u32, } } else { crate::types::LineRange { start: 1, end: 1 } }; Some(crate::patterns::types::PatternExample { code, file_path, line_range, }) } /// Generate unique pattern ID (from original implementation) fn generate_pattern_id(&self) -> String { std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_millis() .to_string() } fn detect_change_type(&self, old_content: &str, new_content: &str) -> String { if old_content.len() > new_content.len() { "deletion".to_string() } else if old_content.len() < new_content.len() { "addition".to_string() } else { "modification".to_string() } } fn extract_primary_concept(&self, patterns: &[Pattern]) -> String { patterns .first() .map(|p| p.pattern_type.clone()) .unwrap_or_else(|| "general".to_string()) } /// Private helper methods async fn extract_semantic_concepts( &self, path: &str, ) -> Result<Vec<SemanticConcept>, ParseError> { let mut concepts = Vec::new(); let mut file_count = 0; let start_time = std::time::Instant::now(); let timeout = std::time::Duration::from_secs(60); // 60 second timeout for entry in WalkDir::new(path) .max_depth(5) // Limit directory traversal depth .into_iter() .filter_map(|e| e.ok()) { // Check timeout if start_time.elapsed() > timeout { eprintln!( "Timeout reached during concept extraction after {} files", file_count ); break; } if entry.file_type().is_file() && file_count < 100 { // Reduced limit for performance let file_path = entry.path(); // Add proper file filtering if !self.should_analyze_file(file_path) { continue; } if let Some(extension) = file_path.extension().and_then(|s| s.to_str()) { if self.is_supported_extension(extension) { if let Ok(content) = fs::read_to_string(file_path) { let file_concepts = self.extract_concepts_from_file( &content, file_path.to_string_lossy().as_ref(), extension, )?; concepts.extend(file_concepts); file_count += 1; } } } } } Ok(concepts) } fn extract_concepts_from_file( &self, content: &str, file_path: &str, extension: &str, ) -> Result<Vec<SemanticConcept>, ParseError> { // This would use the semantic analyzer from the main codebase // For now, return a simplified extraction let mut concepts = Vec::new(); let language = match extension { "js" | "jsx" => "javascript", "ts" | "tsx" => "typescript", "rs" => "rust", "py" => "python", "java" => "java", _ => "unknown", }; // Simple regex-based concept extraction (in practice, would use tree-sitter) let lines: Vec<&str> = content.lines().collect(); for (line_num, line) in lines.iter().enumerate() { if let Some(concept) = self.extract_concept_from_line(line, file_path, line_num as u32 + 1, language) { concepts.push(concept); } } Ok(concepts) } fn extract_concept_from_line( &self, line: &str, file_path: &str, line_num: u32, language: &str, ) -> Option<SemanticConcept> { let trimmed = line.trim(); // Function detection patterns let function_patterns = match language { "javascript" | "typescript" => vec![ r"function\s+(\w+)", r"const\s+(\w+)\s*=.*=>", r"(\w+)\s*:\s*\([^)]*\)\s*=>", ], "rust" => vec![r"fn\s+(\w+)", r"pub\s+fn\s+(\w+)"], "python" => vec![r"def\s+(\w+)"], "java" => vec![r"public\s+.*\s+(\w+)\s*\(", r"private\s+.*\s+(\w+)\s*\("], _ => vec![], }; // Class detection patterns let class_patterns = match language { "javascript" | "typescript" => vec![r"class\s+(\w+)", r"interface\s+(\w+)"], "rust" => vec![r"struct\s+(\w+)", r"enum\s+(\w+)", r"trait\s+(\w+)"], "python" => vec![r"class\s+(\w+)"], "java" => vec![r"class\s+(\w+)", r"interface\s+(\w+)"], _ => vec![], }; // Check for function patterns for pattern in function_patterns { if let Ok(regex) = regex::Regex::new(pattern) { if let Some(captures) = regex.captures(trimmed) { if let Some(name) = captures.get(1) { return Some(SemanticConcept { id: format!("{}_{}", file_path, name.as_str()), name: name.as_str().to_string(), concept_type: "function".to_string(), confidence: 0.8, file_path: file_path.to_string(), line_range: crate::types::LineRange { start: line_num, end: line_num, }, relationships: HashMap::new(), metadata: HashMap::new(), }); } } } } // Check for class patterns for pattern in class_patterns { if let Ok(regex) = regex::Regex::new(pattern) { if let Some(captures) = regex.captures(trimmed) { if let Some(name) = captures.get(1) { return Some(SemanticConcept { id: format!("{}_{}", file_path, name.as_str()), name: name.as_str().to_string(), concept_type: "class".to_string(), confidence: 0.9, file_path: file_path.to_string(), line_range: crate::types::LineRange { start: line_num, end: line_num, }, relationships: HashMap::new(), metadata: HashMap::new(), }); } } } } None } fn is_supported_extension(&self, extension: &str) -> bool { matches!( extension.to_lowercase().as_str(), "js" | "jsx" | "ts" | "tsx" | "rs" | "py" | "java" | "cpp" | "c" | "cs" | "go" | "rb" | "php" ) } fn should_analyze_file(&self, file_path: &std::path::Path) -> bool { // Skip common non-source directories let path_str = file_path.to_string_lossy(); if path_str.contains("node_modules") || path_str.contains(".git") || path_str.contains("target") || path_str.contains("dist") || path_str.contains("build") || path_str.contains(".next") || path_str.contains("__pycache__") || path_str.contains("coverage") || path_str.contains(".vscode") || path_str.contains(".idea") { return false; } // Check if file extension is supported if let Some(extension) = file_path.extension().and_then(|s| s.to_str()) { self.is_supported_extension(extension) } else { false } } fn is_ignored_directory(&self, dir_name: &str) -> bool { matches!( dir_name, "node_modules" | ".git" | "target" | "dist" | "build" | ".next" | "__pycache__" | "coverage" | ".vscode" | ".idea" ) } async fn learn_naming_patterns( &mut self, concepts: &[SemanticConcept], _path: &str, ) -> Result<Vec<Pattern>, ParseError> { // Group concepts by language for better analysis let mut language_groups: HashMap<String, Vec<&SemanticConcept>> = HashMap::new(); for concept in concepts { let language = self.detect_language_from_path(&concept.file_path); language_groups.entry(language).or_default().push(concept); } let mut all_patterns = Vec::new(); for (language, group_concepts) in language_groups { let concept_refs: Vec<_> = group_concepts.into_iter().cloned().collect(); let patterns = self .naming_analyzer .analyze_concepts(&concept_refs, &language)?; all_patterns.extend(patterns); } Ok(all_patterns) } async fn learn_structural_patterns( &mut self, concepts: &[SemanticConcept], path: &str, ) -> Result<Vec<Pattern>, ParseError> { let mut patterns = Vec::new(); // Analyze directory structure (from backup implementation) let directory_structure = self.analyze_directory_structure(path)?; patterns.extend(directory_structure); // Learn from codebase structure let structure_patterns = self.structural_analyzer.analyze_codebase_structure(path)?; patterns.extend(structure_patterns); // Learn from concept relationships let concept_patterns = self .structural_analyzer .analyze_concept_structures(concepts)?; patterns.extend(concept_patterns); Ok(patterns) } fn analyze_directory_structure(&self, path: &str) -> Result<Vec<Pattern>, ParseError> { let mut patterns = Vec::new(); let mut directory_stats = std::collections::HashMap::new(); // Analyze directory structure with depth limit for entry in WalkDir::new(path) .max_depth(3) .into_iter() .filter_map(|e| e.ok()) { if entry.file_type().is_dir() { let dir_name = entry.file_name().to_string_lossy(); if !self.is_ignored_directory(&dir_name) { *directory_stats.entry(dir_name.to_string()).or_insert(0) += 1; } } } // Common patterns from backup let common_dirs = vec![ "src", "lib", "components", "utils", "services", "types", "models", "controllers", ]; let mut found_patterns = Vec::new(); for dir in common_dirs { if directory_stats.contains_key(dir) { found_patterns.push(dir.to_string()); } } if found_patterns.len() >= 2 { patterns.push(Pattern { id: format!("struct_dirs_{}", self.generate_pattern_id()), pattern_type: "structure_organized_directories".to_string(), description: format!( "Organized directory structure with: {}", found_patterns.join(", ") ), frequency: found_patterns.len() as u32, confidence: 0.8, examples: vec![], contexts: vec!["architecture".to_string(), "organization".to_string()], }); } Ok(patterns) } async fn learn_implementation_patterns( &mut self, concepts: &[SemanticConcept], path: &str, ) -> Result<Vec<Pattern>, ParseError> { let mut patterns = Vec::new(); // Learn from concepts let concept_patterns = self.implementation_analyzer.analyze_concepts(concepts)?; patterns.extend(concept_patterns); // Learn from code files let code_patterns = self.implementation_analyzer.analyze_code_files(path)?; patterns.extend(code_patterns); Ok(patterns) } fn validate_and_consolidate_patterns( &self, patterns: Vec<Pattern>, ) -> Result<Vec<Pattern>, ParseError> { let mut consolidated: HashMap<String, Pattern> = HashMap::new(); let mut pattern_groups: HashMap<String, Vec<Pattern>> = HashMap::new(); // Group similar patterns with quality thresholds for pattern in patterns { // Apply quality thresholds from old implementation let min_frequency = if pattern.pattern_type.contains("naming") { 3 } else { 2 }; if pattern.confidence >= self.confidence_threshold && pattern.frequency >= min_frequency { let group_key = format!( "{}_{}", pattern.pattern_type, self.normalize_description(&pattern.description) ); pattern_groups.entry(group_key).or_default().push(pattern); } } // Consolidate each group for (group_key, group_patterns) in pattern_groups { if group_patterns.len() == 1 { consolidated.insert(group_key, group_patterns.into_iter().next().unwrap()); } else { // Merge patterns in the group let merged = self.merge_similar_patterns(group_patterns); consolidated.insert(group_key, merged); } } Ok(consolidated.into_values().collect()) } fn normalize_description(&self, description: &str) -> String { description .to_lowercase() .chars() .filter(|c| c.is_alphanumeric() || c.is_whitespace()) .collect::<String>() .split_whitespace() .take(3) // Take first 3 words for grouping .collect::<Vec<&str>>() .join("_") } fn merge_similar_patterns(&self, patterns: Vec<Pattern>) -> Pattern { if patterns.is_empty() { panic!("Cannot merge empty pattern list"); } let first = &patterns[0]; let total_frequency: u32 = patterns.iter().map(|p| p.frequency).sum(); let avg_confidence: f64 = patterns.iter().map(|p| p.confidence).sum::<f64>() / patterns.len() as f64; let mut all_examples = Vec::new(); let mut all_contexts = HashSet::new(); for pattern in &patterns { all_examples.extend(pattern.examples.clone()); all_contexts.extend(pattern.contexts.clone()); } // Limit examples to avoid bloat all_examples.truncate(10); Pattern { id: first.id.clone(), pattern_type: first.pattern_type.clone(), description: format!( "{} (consolidated from {} instances)", first.description, patterns.len() ), frequency: total_frequency, confidence: avg_confidence, examples: all_examples, contexts: all_contexts.into_iter().collect(), } } fn update_learning_metrics(&mut self, patterns: &[Pattern], session: &LearningSession) { self.learning_metrics.total_patterns_learned += patterns.len(); // Log session information for debugging and analytics eprintln!( "Learning session {} completed: analyzed {} files, found {} patterns in {} concepts", session.session_id, session.files_analyzed, patterns.len(), session.concepts_analyzed ); // Update confidence distribution for pattern in patterns { let confidence_range = match pattern.confidence { c if c >= 0.9 => "high", c if c >= 0.7 => "medium-high", c if c >= 0.5 => "medium", c if c >= 0.3 => "low-medium", _ => "low", }; *self .learning_metrics .confidence_distribution .entry(confidence_range.to_string()) .or_insert(0) += 1; } // Update pattern type counts for pattern in patterns { *self .learning_metrics .pattern_type_counts .entry(pattern.pattern_type.clone()) .or_insert(0) += 1; } // Update timestamp self.learning_metrics.last_learning_timestamp = Some(chrono::Utc::now().to_rfc3339()); // Calculate learning accuracy (simplified) let high_confidence_patterns = patterns.iter().filter(|p| p.confidence >= 0.8).count(); self.learning_metrics.learning_accuracy = if !patterns.is_empty() { high_confidence_patterns as f64 / patterns.len() as f64 } else { 0.0 }; } fn update_incremental_metrics(&mut self, patterns: &[Pattern]) { // Update metrics for incremental learning self.learning_metrics.total_patterns_learned += patterns.len(); for pattern in patterns { *self .learning_metrics .pattern_type_counts .entry(pattern.pattern_type.clone()) .or_insert(0) += 1; } } fn detect_language_from_path(&self, path: &str) -> String { if let Some(extension) = std::path::Path::new(path) .extension() .and_then(|s| s.to_str()) { match extension.to_lowercase().as_str() { "js" | "jsx" => "javascript", "ts" | "tsx" => "typescript", "rs" => "rust", "py" => "python", "java" => "java", "cpp" | "cc" | "cxx" => "cpp", "c" => "c", "cs" => "csharp", "go" => "go", _ => "unknown", } .to_string() } else { "unknown".to_string() } } fn has_structural_changes(&self, old_content: &str, new_content: &str) -> bool { // Simple check for structural changes let old_lines = old_content.lines().count(); let new_lines = new_content.lines().count(); // Consider it a structural change if lines changed significantly let line_change_ratio = (old_lines as f64 - new_lines as f64).abs() / old_lines.max(1) as f64; line_change_ratio > 0.2 || new_content.contains("class ") != old_content.contains("class ") || new_content.contains("function ") != old_content.contains("function ") } async fn learn_structural_changes( &self, _old_content: &str, _new_content: &str, _file_path: &str, ) -> Result<Vec<Pattern>, ParseError> { // Simplified implementation - would need proper AST diffing Ok(Vec::new()) } fn parse_concepts_from_analysis( &self, data: &Value, ) -> Result<Vec<SemanticConcept>, ParseError> { let mut concepts = Vec::new(); if let Some(concepts_array) = data.get("concepts").and_then(|v| v.as_array()) { for concept_value in concepts_array { if let Ok(concept) = self.parse_concept_from_value(concept_value) { concepts.push(concept); } } } Ok(concepts) } fn parse_concept_from_value(&self, value: &Value) -> Result<SemanticConcept, ParseError> { let name = value .get("name") .and_then(|v| v.as_str()) .unwrap_or("unknown") .to_string(); let concept_type = value .get("type") .and_then(|v| v.as_str()) .unwrap_or("unknown") .to_string(); let file_path = value .get("file") .and_then(|v| v.as_str()) .unwrap_or("unknown") .to_string(); let confidence = value .get("confidence") .and_then(|v| v.as_f64()) .unwrap_or(0.5); Ok(SemanticConcept { id: format!("{}_{}", file_path, name), name, concept_type, confidence, file_path, line_range: crate::types::LineRange { start: 1, end: 1 }, relationships: HashMap::new(), metadata: HashMap::new(), }) } // File change handling methods (from original implementation) /// Handle file addition (from original implementation) async fn handle_file_addition( &mut self, file_path: Option<&str>, content: Option<&str>, language: Option<&str>, ) -> Result<bool, ParseError> { let mut updated = false; if let Some(path) = file_path { // Learn from file structure patterns if let Some(extension) = std::path::Path::new(path) .extension() .and_then(|s| s.to_str()) { let pattern_type = format!("file_creation_{}", extension); updated |= self.update_pattern_frequency(&pattern_type, 1).await?; } // Learn from directory patterns if let Some(parent) = std::path::Path::new(path).parent() { if let Some(dir_name) = parent.file_name().and_then(|s| s.to_str()) { let pattern_type = format!("directory_usage_{}", dir_name); updated |= self.update_pattern_frequency(&pattern_type, 1).await?; } } // Analyze content if available if let (Some(content_str), Some(lang)) = (content, language) { updated |= self .analyze_new_file_content(path, content_str, lang) .await?; } } Ok(updated) } /// Handle file modification (from original implementation) async fn handle_file_modification( &mut self, file_path: Option<&str>, content: Option<&str>, language: Option<&str>, ) -> Result<bool, ParseError> { let mut updated = false; if let (Some(path), Some(content_str)) = (file_path, content) { // Analyze patterns in the modified content updated |= self .analyze_content_patterns(path, content_str, language) .await?; // Update modification frequency for file type if let Some(extension) = std::path::Path::new(path) .extension() .and_then(|s| s.to_str()) { let pattern_type = format!("file_modification_{}", extension); updated |= self.update_pattern_frequency(&pattern_type, 1).await?; } // Learn from naming patterns in the content updated |= self .learn_naming_patterns_from_content(path, content_str) .await?; } Ok(updated) } /// Handle file deletion (from original implementation) async fn handle_file_deletion(&mut self, file_path: Option<&str>) -> Result<bool, ParseError> { let mut updated = false; if let Some(path) = file_path { // Update deletion patterns if let Some(extension) = std::path::Path::new(path) .extension() .and_then(|s| s.to_str()) { let pattern_type = format!("file_deletion_{}", extension); updated |= self.update_pattern_frequency(&pattern_type, 1).await?; } // Decrease confidence of patterns related to deleted files updated |= self.adjust_patterns_for_deleted_file(path).await?; } Ok(updated) } /// Handle file rename/move (from original implementation) async fn handle_file_rename( &mut self, file_path: Option<&str>, change: &Value, ) -> Result<bool, ParseError> { let mut updated = false; if let Some(old_path) = change.get("oldPath").and_then(|p| p.as_str()) { let new_path = file_path.unwrap_or("unknown"); // Learn from file movement patterns let old_dir = std::path::Path::new(old_path) .parent() .and_then(|p| p.file_name()) .and_then(|s| s.to_str()) .unwrap_or("root"); let new_dir = std::path::Path::new(new_path) .parent() .and_then(|p| p.file_name()) .and_then(|s| s.to_str()) .unwrap_or("root"); if old_dir != new_dir { let pattern_type = format!("file_movement_{}_{}", old_dir, new_dir); updated |= self.update_pattern_frequency(&pattern_type, 1).await?; } // Learn from renaming patterns let old_name = std::path::Path::new(old_path) .file_stem() .and_then(|s| s.to_str()) .unwrap_or("unknown"); let new_name = std::path::Path::new(new_path) .file_stem() .and_then(|s| s.to_str()) .unwrap_or("unknown"); if old_name != new_name { let pattern_type = "file_renaming".to_string(); updated |= self.update_pattern_frequency(&pattern_type, 1).await?; } } Ok(updated) } /// Learn from change patterns (from original implementation) async fn learn_from_change_pattern( &mut self, change_type: &str, _file_path: Option<&str>, language: Option<&str>, ) -> Result<bool, ParseError> { let mut updated = false; // Create pattern type based on change and context let base_pattern = format!("change_{}", change_type); updated |= self.update_pattern_frequency(&base_pattern, 1).await?; // Language-specific change patterns if let Some(lang) = language { let lang_pattern = format!("change_{}_{}", change_type, lang); updated |= self.update_pattern_frequency(&lang_pattern, 1).await?; } // Time-based patterns (hour of day, day of week) let now = std::time::SystemTime::now(); if let Ok(duration) = now.duration_since(std::time::UNIX_EPOCH) { let hour = (duration.as_secs() / 3600) % 24; let time_pattern = format!("change_time_hour_{}", hour); updated |= self.update_pattern_frequency(&time_pattern, 1).await?; } Ok(updated) } /// Update language usage patterns (from original implementation) async fn update_language_usage_patterns( &mut self, file_path: &str, language: &str, ) -> Result<bool, ParseError> { let mut updated = false; // Update overall language usage let lang_pattern = format!("language_usage_{}", language); updated |= self.update_pattern_frequency(&lang_pattern, 1).await?; // Update directory-language combinations if let Some(parent) = std::path::Path::new(file_path).parent() { if let Some(dir_name) = parent.file_name().and_then(|s| s.to_str()) { let dir_lang_pattern = format!("directory_language_{}_{}", dir_name, language); updated |= self.update_pattern_frequency(&dir_lang_pattern, 1).await?; } } Ok(updated) } /// Analyze new file content (from original implementation) async fn analyze_new_file_content( &mut self, file_path: &str, content: &str, language: &str, ) -> Result<bool, ParseError> { let mut updated = false; // Analyze initial file structure patterns let lines = content.lines().count(); if lines > 0 { let size_category = if lines < 50 { "small" } else if lines < 200 { "medium" } else { "large" }; let pattern_type = format!("new_file_size_{}_{}", size_category, language); updated |= self.update_pattern_frequency(&pattern_type, 1).await?; } // Look for common patterns in new files if content.contains("import ") || content.contains("from ") { let pattern_type = format!("new_file_with_imports_{}", language); updated |= self.update_pattern_frequency(&pattern_type, 1).await?; } if content.contains("export ") || content.contains("module.exports") { let pattern_type = format!("new_file_with_exports_{}", language); updated |= self.update_pattern_frequency(&pattern_type, 1).await?; } // Analyze naming patterns in new content updated |= self .learn_naming_patterns_from_content(file_path, content) .await?; Ok(updated) } /// Analyze content patterns (from original implementation) async fn analyze_content_patterns( &mut self, _file_path: &str, content: &str, language: Option<&str>, ) -> Result<bool, ParseError> { let mut updated = false; // Count different types of constructs let function_count = content.matches("function ").count() + content.matches(" => ").count(); let class_count = content.matches("class ").count(); let import_count = content.matches("import ").count(); if let Some(lang) = language { if function_count > 0 { let pattern_type = format!("file_with_functions_{}", lang); updated |= self .update_pattern_frequency(&pattern_type, function_count as u32) .await?; } if class_count > 0 { let pattern_type = format!("file_with_classes_{}", lang); updated |= self .update_pattern_frequency(&pattern_type, class_count as u32) .await?; } if import_count > 0 { let pattern_type = format!("file_with_imports_{}", lang); updated |= self .update_pattern_frequency(&pattern_type, import_count as u32) .await?; } } Ok(updated) } /// Learn naming patterns from content (from original implementation) async fn learn_naming_patterns_from_content( &mut self, _file_path: &str, content: &str, ) -> Result<bool, ParseError> { let mut updated = false; // Extract and classify identifiers let lines: Vec<&str> = content.lines().collect(); for line in lines { // Extract function names if let Some(function_names) = self.extract_function_names(line) { for name in function_names { let pattern_type = format!( "naming_function_{}", self.classify_naming_pattern(&name, "function") ); updated |= self.update_pattern_frequency(&pattern_type, 1).await?; } } // Extract class names if let Some(class_names) = self.extract_class_names(line) { for name in class_names { let pattern_type = format!( "naming_class_{}", self.classify_naming_pattern(&name, "class") ); updated |= self.update_pattern_frequency(&pattern_type, 1).await?; } } // Extract variable names if let Some(variable_names) = self.extract_variable_names(line) { for name in variable_names { let pattern_type = format!( "naming_variable_{}", self.classify_naming_pattern(&name, "variable") ); updated |= self.update_pattern_frequency(&pattern_type, 1).await?; } } } Ok(updated) } /// Adjust patterns for deleted files (from original implementation) async fn adjust_patterns_for_deleted_file( &mut self, file_path: &str, ) -> Result<bool, ParseError> { let mut updated = false; // Slightly decrease confidence for patterns that might be related to the deleted file if let Some(extension) = std::path::Path::new(file_path) .extension() .and_then(|s| s.to_str()) { // Find patterns related to this file type and decrease their confidence slightly for pattern in self.learned_patterns.values_mut() { if (pattern.pattern_type.contains(extension) || pattern.contexts.contains(&extension.to_string())) && pattern.confidence > 0.1 { pattern.confidence = (pattern.confidence - 0.02).max(0.1); updated = true; } } } Ok(updated) } // Naming analysis methods (from original implementation) fn extract_function_names(&self, line: &str) -> Option<Vec<String>> { let mut names = Vec::new(); // TypeScript/JavaScript function patterns if line.contains("function ") { if let Some(start) = line.find("function ") { let after_function = &line[start + 9..]; if let Some(end) = after_function.find('(') { let name = after_function[..end].trim(); if !name.is_empty() && self.is_valid_identifier(name) { names.push(name.to_string()); } } } } // Arrow function patterns if line.contains(" = ") && line.contains("=>") { if let Some(equal_pos) = line.find(" = ") { let before_equal = &line[..equal_pos]; if let Some(start) = before_equal.rfind(char::is_whitespace) { let name = before_equal[start..].trim(); if !name.is_empty() && self.is_valid_identifier(name) { names.push(name.to_string()); } } } } // Python function patterns if line.trim_start().starts_with("def ") { if let Some(start) = line.find("def ") { let after_def = &line[start + 4..]; if let Some(end) = after_def.find('(') { let name = after_def[..end].trim(); if !name.is_empty() && self.is_valid_identifier(name) { names.push(name.to_string()); } } } } if names.is_empty() { None } else { Some(names) } } fn extract_class_names(&self, line: &str) -> Option<Vec<String>> { let mut names = Vec::new(); if line.contains("class ") { if let Some(start) = line.find("class ") { let after_class = &line[start + 6..]; let end = after_class .find(char::is_whitespace) .or_else(|| after_class.find('{')) .or_else(|| after_class.find('(')) .unwrap_or(after_class.len()); let name = after_class[..end].trim(); if !name.is_empty() && self.is_valid_identifier(name) { names.push(name.to_string()); } } } if names.is_empty() { None } else { Some(names) } } fn extract_variable_names(&self, line: &str) -> Option<Vec<String>> { let mut names = Vec::new(); // TypeScript/JavaScript variable patterns let patterns = vec!["const ", "let ", "var "]; for pattern in patterns { if line.contains(pattern) { if let Some(start) = line.find(pattern) { let after_keyword = &line[start + pattern.len()..]; if let Some(equal_pos) = after_keyword.find('=') { let name = after_keyword[..equal_pos].trim(); if !name.is_empty() && self.is_valid_identifier(name) { names.push(name.to_string()); } } } } } if names.is_empty() { None } else { Some(names) } } fn is_valid_identifier(&self, name: &str) -> bool { !name.is_empty() && name.chars().next().is_some_and(|c| c.is_alphabetic()) && name.chars().all(|c| c.is_alphanumeric() || c == '_') } fn classify_naming_pattern(&self, name: &str, _context: &str) -> String { if self.is_camel_case(name) { "camelCase".to_string() } else if self.is_pascal_case(name) { "PascalCase".to_string() } else if self.is_snake_case(name) { "snake_case".to_string() } else if self.is_kebab_case(name) { "kebab-case".to_string() } else if self.is_upper_case(name) { "UPPER_CASE".to_string() } else { "mixed".to_string() } } fn is_camel_case(&self, name: &str) -> bool { name.chars().next().is_some_and(|c| c.is_lowercase()) && name.contains(char::is_uppercase) && !name.contains('_') && !name.contains('-') } fn is_pascal_case(&self, name: &str) -> bool { name.chars().next().is_some_and(|c| c.is_uppercase()) && !name.contains('_') && !name.contains('-') } fn is_snake_case(&self, name: &str) -> bool { name.chars().all(|c| c.is_lowercase() || c == '_') && name.contains('_') } fn is_kebab_case(&self, name: &str) -> bool { name.chars().all(|c| c.is_lowercase() || c == '-') && name.contains('-') } fn is_upper_case(&self, name: &str) -> bool { name.chars().all(|c| c.is_uppercase() || c == '_') } } impl PatternLearnerTrait for PatternLearningEngine { fn learn_from_data(&mut self, data: &str) -> Result<Vec<Pattern>, ParseError> { // Synchronous version of learning from data let runtime = tokio::runtime::Runtime::new() .map_err(|e| ParseError::from_reason(format!("Failed to create runtime: {}", e)))?; runtime.block_on(async { unsafe { self.learn_from_analysis(data.to_string()).await } })?; Ok(self.learned_patterns.values().cloned().collect()) } } impl Default for PatternLearningEngine { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use super::*; use std::fs; use tempfile::TempDir; fn create_test_concept(name: &str, concept_type: &str, file_path: &str) -> SemanticConcept { SemanticConcept { id: format!("test_{}", name), name: name.to_string(), concept_type: concept_type.to_string(), confidence: 0.8, file_path: file_path.to_string(), line_range: crate::types::LineRange { start: 1, end: 10 }, relationships: HashMap::new(), metadata: HashMap::new(), } } #[tokio::test] async fn test_pattern_learning_engine_creation() { let engine = PatternLearningEngine::new(); assert_eq!(engine.confidence_threshold, 0.5); assert_eq!(engine.learned_patterns.len(), 0); } #[tokio::test] async fn test_concept_extraction_from_line() { let engine = PatternLearningEngine::new(); let js_function = "function getUserName() {"; let concept = engine.extract_concept_from_line(js_function, "test.js", 1, "javascript"); assert!(concept.is_some()); if let Some(c) = concept { assert_eq!(c.name, "getUserName"); assert_eq!(c.concept_type, "function"); } let rust_struct = "pub struct User {"; let concept = engine.extract_concept_from_line(rust_struct, "test.rs", 1, "rust"); assert!(concept.is_some()); if let Some(c) = concept { assert_eq!(c.name, "User"); assert_eq!(c.concept_type, "class"); } } #[tokio::test] async fn test_language_detection() { let engine = PatternLearningEngine::new(); assert_eq!(engine.detect_language_from_path("test.js"), "javascript"); assert_eq!(engine.detect_language_from_path("test.ts"), "typescript"); assert_eq!(engine.detect_language_from_path("test.rs"), "rust"); assert_eq!(engine.detect_language_from_path("test.py"), "python"); assert_eq!(engine.detect_language_from_path("test.java"), "java"); assert_eq!(engine.detect_language_from_path("test.unknown"), "unknown"); } #[tokio::test] async fn test_supported_extensions() { let engine = PatternLearningEngine::new(); assert!(engine.is_supported_extension("js")); assert!(engine.is_supported_extension("ts")); assert!(engine.is_supported_extension("rs")); assert!(engine.is_supported_extension("py")); assert!(engine.is_supported_extension("java")); assert!(!engine.is_supported_extension("txt")); assert!(!engine.is_supported_extension("md")); } #[tokio::test] async fn test_structural_change_detection() { let engine = PatternLearningEngine::new(); let old_code = "function test() {\n return 42;\n}"; let new_code_minor = "function test() {\n return 43;\n}"; let new_code_major = "class Test {\n method() {\n return 42;\n }\n}"; assert!(!engine.has_structural_changes(old_code, new_code_minor)); assert!(engine.has_structural_changes(old_code, new_code_major)); } #[tokio::test] async fn test_pattern_consolidation() { let engine = PatternLearningEngine::new(); let patterns = vec![ Pattern { id: "pattern1".to_string(), pattern_type: "naming".to_string(), description: "camelCase pattern".to_string(), frequency: 5, confidence: 0.8, examples: vec![], contexts: vec!["javascript".to_string()], }, Pattern { id: "pattern2".to_string(), pattern_type: "naming".to_string(), description: "camelCase pattern for functions".to_string(), frequency: 3, confidence: 0.7, examples: vec![], contexts: vec!["javascript".to_string()], }, ]; let consolidated = engine.validate_and_consolidate_patterns(patterns).unwrap(); assert_eq!(consolidated.len(), 1); assert_eq!(consolidated[0].frequency, 8); // 5 + 3 } #[tokio::test] async fn test_confidence_threshold() { let mut engine = PatternLearningEngine::new(); engine.set_confidence_threshold(0.7); assert_eq!(engine.confidence_threshold, 0.7); engine.set_confidence_threshold(1.5); // Should clamp to 1.0 assert_eq!(engine.confidence_threshold, 1.0); engine.set_confidence_threshold(-0.1); // Should clamp to 0.0 assert_eq!(engine.confidence_threshold, 0.0); } #[tokio::test] async fn test_learning_metrics_update() { let mut engine = PatternLearningEngine::new(); let patterns = vec![ Pattern { id: "high_confidence".to_string(), pattern_type: "naming".to_string(), description: "High confidence pattern".to_string(), frequency: 5, confidence: 0.9, examples: vec![], contexts: vec![], }, Pattern { id: "low_confidence".to_string(), pattern_type: "structural".to_string(), description: "Low confidence pattern".to_string(), frequency: 2, confidence: 0.4, examples: vec![], contexts: vec![], }, ]; let session = LearningSession { session_id: "test".to_string(), patterns_discovered: patterns.clone(), analysis_duration_ms: 1000, files_analyzed: 10, concepts_analyzed: 50, }; engine.update_learning_metrics(&patterns, &session); assert_eq!(engine.learning_metrics.total_patterns_learned, 2); assert_eq!( engine.learning_metrics.pattern_type_counts.get("naming"), Some(&1) ); assert_eq!( engine .learning_metrics .pattern_type_counts .get("structural"), Some(&1) ); assert!(engine .learning_metrics .confidence_distribution .get("high") .is_some()); } #[tokio::test] async fn test_pattern_analysis() { let engine = PatternLearningEngine::new(); let concepts = vec![ create_test_concept("getUserName", "function", "test.js"), create_test_concept("UserService", "class", "UserService.js"), ]; let result = engine.analyze_patterns(concepts).unwrap(); assert!(result.detected.is_empty() || !result.detected.is_empty()); // Either is fine for this test // Violations and recommendations depend on the specific patterns detected } #[tokio::test] async fn test_learn_from_codebase() { let mut engine = PatternLearningEngine::new(); let temp_dir = TempDir::new().unwrap(); // Create a simple JavaScript file let js_content = r#" function getUserName(user) { return user.name; } class UserService { constructor() { this.users = []; } addUser(user) { this.users.push(user); } } "#; fs::write(temp_dir.path().join("test.js"), js_content).unwrap(); let patterns = unsafe { engine .learn_from_codebase(temp_dir.path().to_str().unwrap().to_string()) .await .unwrap() }; // Should have learned some patterns from the code assert!(!patterns.is_empty()); // Check that patterns were stored assert!(!engine.learned_patterns.is_empty()); } #[tokio::test] async fn test_learn_from_analysis_data() { let mut engine = PatternLearningEngine::new(); let analysis_data = r#"{ "concepts": [ { "name": "getUserData", "type": "function", "file": "user.js", "confidence": 0.9 }, { "name": "UserController", "type": "class", "file": "controller.js", "confidence": 0.8 } ] }"#; let result = unsafe { engine .learn_from_analysis(analysis_data.to_string()) .await .unwrap() }; assert!(result); // Should have learned something // Check that patterns were learned assert!(!engine.learned_patterns.is_empty()); } #[test] fn test_description_normalization() { let engine = PatternLearningEngine::new(); let desc1 = "CamelCase naming pattern for functions"; let desc2 = "camelCase naming pattern in JavaScript"; let norm1 = engine.normalize_description(desc1); let norm2 = engine.normalize_description(desc2); // Should normalize to similar keys for grouping assert_eq!(norm1, "camelcase_naming_pattern"); assert_eq!(norm2, "camelcase_naming_pattern"); } #[test] fn test_pattern_merge() { let engine = PatternLearningEngine::new(); let patterns = vec![ Pattern { id: "pattern1".to_string(), pattern_type: "naming".to_string(), description: "Pattern 1".to_string(), frequency: 5, confidence: 0.8, examples: vec![], contexts: vec!["js".to_string()], }, Pattern { id: "pattern2".to_string(), pattern_type: "naming".to_string(), description: "Pattern 2".to_string(), frequency: 3, confidence: 0.6, examples: vec![], contexts: vec!["ts".to_string()], }, ]; let merged = engine.merge_similar_patterns(patterns); assert_eq!(merged.frequency, 8); // 5 + 3 assert_eq!(merged.confidence, 0.7); // (0.8 + 0.6) / 2 assert_eq!(merged.contexts.len(), 2); } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/pi22by7/In-Memoria'

If you have feedback or need assistance with the MCP directory API, please join our Discord server