use crate::classification::{EditCategory, RiskAssessment, RiskFactor, CriticalType, SafeType, ExperimentalType, ImpactScope, ConfidenceLevel};
use crate::edit_control::{ModifiableEdit, ApprovalState};
use crate::agents::gpt4_agent::ProposedEdit;
use crate::memory::SymbolicContext;
use crate::ops::{default_operator_registry, SomaOperator};
use anyhow::{Result, anyhow};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
pub struct EditClassificationSystem {
cognitive_operators: HashMap<String, Box<dyn SomaOperator + Send>>,
config: ClassificationConfig,
patterns: PatternDatabase,
scoring_weights: ScoringWeights,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClassificationConfig {
pub use_cognitive_analysis: bool,
pub auto_classification_threshold: f64,
pub max_batch_size: usize,
pub verbose_logging: bool,
pub risk_strictness: f64,
}
impl Default for ClassificationConfig {
fn default() -> Self {
Self {
use_cognitive_analysis: true,
auto_classification_threshold: 0.8,
max_batch_size: 100,
verbose_logging: false,
risk_strictness: 0.7,
}
}
}
#[derive(Debug, Clone)]
pub struct PatternDatabase {
file_patterns: HashMap<String, f64>,
content_patterns: HashMap<String, EditCategory>,
security_patterns: Vec<String>,
performance_patterns: Vec<String>,
}
impl Default for PatternDatabase {
fn default() -> Self {
let mut file_patterns = HashMap::new();
file_patterns.insert(".rs".to_string(), 0.4); file_patterns.insert(".toml".to_string(), 0.6); file_patterns.insert(".md".to_string(), 0.1); file_patterns.insert(".json".to_string(), 0.5); file_patterns.insert(".yaml".to_string(), 0.5); file_patterns.insert(".yml".to_string(), 0.5);
let mut content_patterns = HashMap::new();
content_patterns.insert("TODO".to_string(), EditCategory::Experimental {
subcategory: ExperimentalType::NewAlgorithm,
validation_requirements: vec!["Review incomplete implementation".to_string()]
});
content_patterns.insert("FIXME".to_string(), EditCategory::Critical {
subcategory: CriticalType::SecurityFix,
impact_scope: ImpactScope::SingleFunction
});
content_patterns.insert("//".to_string(), EditCategory::Safe {
subcategory: SafeType::CommentAddition,
confidence_level: ConfidenceLevel::High
});
let security_patterns = vec![
"unsafe".to_string(),
"password".to_string(),
"secret".to_string(),
"token".to_string(),
"key".to_string(),
"auth".to_string(),
"crypto".to_string(),
"decrypt".to_string(),
"encrypt".to_string(),
];
let performance_patterns = vec![
"loop".to_string(),
"recursive".to_string(),
"alloc".to_string(),
"clone".to_string(),
"mutex".to_string(),
"lock".to_string(),
"thread".to_string(),
"async".to_string(),
];
Self {
file_patterns,
content_patterns,
security_patterns,
performance_patterns,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScoringWeights {
pub risk_weight: f64,
pub complexity_weight: f64,
pub impact_weight: f64,
pub confidence_weight: f64,
pub cognitive_weight: f64,
}
impl Default for ScoringWeights {
fn default() -> Self {
Self {
risk_weight: 0.3,
complexity_weight: 0.25,
impact_weight: 0.2,
confidence_weight: 0.15,
cognitive_weight: 0.1,
}
}
}
#[derive(Debug, Clone)]
pub struct ClassifiedEdit {
pub edit: ModifiableEdit,
pub category: EditCategory,
pub risk_assessment: RiskAssessment,
pub priority_score: f64,
pub classification_confidence: f64,
pub cognitive_analysis: Option<SymbolicContext>,
pub recommendation: ClassificationRecommendation,
pub reasoning: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ClassificationRecommendation {
AutoApprove { confidence: f64 },
RequireReview { concerns: Vec<String> },
Escalate { target_level: String, reason: String },
Reject { reason: String },
RequestInfo { questions: Vec<String> },
}
#[derive(Debug, Clone, Default)]
pub struct FilterCriteria {
pub categories: Option<Vec<EditCategory>>,
pub min_priority: Option<f64>,
pub max_risk: Option<f64>,
pub approval_states: Option<Vec<ApprovalState>>,
pub min_confidence: Option<f64>,
}
impl EditClassificationSystem {
pub fn new() -> Self {
Self::with_config(ClassificationConfig::default())
}
pub fn with_config(config: ClassificationConfig) -> Self {
let cognitive_operators = if config.use_cognitive_analysis {
default_operator_registry()
} else {
HashMap::new()
};
Self {
cognitive_operators,
config,
patterns: PatternDatabase::default(),
scoring_weights: ScoringWeights::default(),
}
}
pub fn classify_edit(&self, edit: &ModifiableEdit) -> Result<ClassifiedEdit> {
if self.config.verbose_logging {
println!("🔍 Classifying edit: {}", edit.base_edit.file);
}
let category = self.determine_category(edit)?;
let risk_assessment = self.assess_risk(edit, &category)?;
let cognitive_analysis = if self.config.use_cognitive_analysis {
self.run_cognitive_analysis(edit, &category)?
} else {
None
};
let priority_score = self.calculate_priority_score(edit, &category, &risk_assessment, &cognitive_analysis)?;
let classification_confidence = self.calculate_classification_confidence(edit, &category, &risk_assessment)?;
let recommendation = self.generate_recommendation(&category, &risk_assessment, classification_confidence)?;
let reasoning = self.generate_reasoning(edit, &category, &risk_assessment, &cognitive_analysis)?;
Ok(ClassifiedEdit {
edit: edit.clone(),
category,
risk_assessment,
priority_score,
classification_confidence,
cognitive_analysis,
recommendation,
reasoning,
})
}
pub fn classify_batch(&self, edits: &[ModifiableEdit]) -> Result<Vec<ClassifiedEdit>> {
if edits.len() > self.config.max_batch_size {
return Err(anyhow!("Batch size {} exceeds maximum {}", edits.len(), self.config.max_batch_size));
}
let mut classified_edits = Vec::new();
for edit in edits {
let classified = self.classify_edit(edit)?;
classified_edits.push(classified);
}
classified_edits.sort_by(|a, b| b.priority_score.partial_cmp(&a.priority_score).unwrap_or(std::cmp::Ordering::Equal));
if self.config.verbose_logging {
println!("✅ Classified {} edits, sorted by priority", classified_edits.len());
}
Ok(classified_edits)
}
pub fn filter_edits(&self, edits: &[ClassifiedEdit], criteria: &FilterCriteria) -> Vec<&ClassifiedEdit> {
edits.iter().filter(|edit| {
if let Some(ref categories) = criteria.categories {
if !categories.contains(&edit.category) {
return false;
}
}
if let Some(min_priority) = criteria.min_priority {
if edit.priority_score < min_priority {
return false;
}
}
if let Some(max_risk) = criteria.max_risk {
if edit.risk_assessment.overall_score > max_risk {
return false;
}
}
if let Some(ref approval_states) = criteria.approval_states {
if !approval_states.contains(&edit.edit.approval_state()) {
return false;
}
}
if let Some(min_confidence) = criteria.min_confidence {
if edit.classification_confidence < min_confidence {
return false;
}
}
true
}).collect()
}
pub fn get_classification_stats(&self, edits: &[ClassifiedEdit]) -> ClassificationStats {
let total_count = edits.len();
let mut category_counts = HashMap::new();
let mut total_risk = 0.0;
let mut total_priority = 0.0;
let mut total_confidence = 0.0;
for edit in edits {
let category_key = std::mem::discriminant(&edit.category);
*category_counts.entry(category_key).or_insert(0) += 1;
total_risk += edit.risk_assessment.overall_score;
total_priority += edit.priority_score;
total_confidence += edit.classification_confidence;
}
ClassificationStats {
total_count,
category_distribution: category_counts,
average_risk: if total_count > 0 { total_risk / total_count as f64 } else { 0.0 },
average_priority: if total_count > 0 { total_priority / total_count as f64 } else { 0.0 },
average_confidence: if total_count > 0 { total_confidence / total_count as f64 } else { 0.0 },
}
}
fn determine_category(&self, edit: &ModifiableEdit) -> Result<EditCategory> {
let file_path = &edit.base_edit.file;
let extension = file_path.extension()
.and_then(|ext| ext.to_str())
.map(|ext| format!(".{}", ext))
.unwrap_or_default();
if let Ok(content) = std::fs::read_to_string(file_path) {
for (pattern, category) in &self.patterns.content_patterns {
if content.contains(pattern) {
return Ok(category.clone());
}
}
for pattern in &self.patterns.security_patterns {
if content.to_lowercase().contains(&pattern.to_lowercase()) {
return Ok(EditCategory::Critical {
subcategory: CriticalType::SecurityFix,
impact_scope: ImpactScope::SingleFile,
});
}
}
for pattern in &self.patterns.performance_patterns {
if content.to_lowercase().contains(&pattern.to_lowercase()) {
return Ok(EditCategory::Experimental {
subcategory: ExperimentalType::PerformanceOptimization,
validation_requirements: vec!["Performance testing required".to_string()],
});
}
}
}
match extension.as_str() {
".md" => Ok(EditCategory::Safe {
subcategory: SafeType::Documentation,
confidence_level: ConfidenceLevel::High,
}),
".toml" | ".json" | ".yaml" | ".yml" => Ok(EditCategory::Critical {
subcategory: CriticalType::DataCorruption,
impact_scope: ImpactScope::SystemWide,
}),
_ => Ok(EditCategory::Safe {
subcategory: SafeType::VariableRename,
confidence_level: ConfidenceLevel::Medium,
}),
}
}
fn assess_risk(&self, edit: &ModifiableEdit, category: &EditCategory) -> Result<RiskAssessment> {
let file_path = edit.target_path();
let extension = file_path.extension()
.and_then(|ext| ext.to_str())
.map(|ext| format!(".{}", ext))
.unwrap_or_default();
let file_risk = self.patterns.file_patterns.get(&extension).copied().unwrap_or(0.3);
let category_risk = match category {
EditCategory::Critical { .. } => 0.9,
EditCategory::Experimental { .. } => 0.7,
EditCategory::Safe { .. } => 0.2,
EditCategory::Cosmetic { .. } => 0.1,
};
let overall_score = (file_risk + category_risk) / 2.0 * self.config.risk_strictness;
Ok(RiskAssessment::builder()
.with_risk_factor(RiskFactor::FileImpact, file_risk)
.with_risk_factor(RiskFactor::ChangeComplexity, category_risk)
.with_explanation(format!("Risk assessment: file={:.2}, category={:.2}, overall={:.2}",
file_risk, category_risk, overall_score))
.with_mitigation("Review changes carefully".to_string())
.build())
}
fn run_cognitive_analysis(&self, edit: &ModifiableEdit, category: &EditCategory) -> Result<Option<SymbolicContext>> {
let mut context = SymbolicContext::new();
context.set("edit_file", &edit.target_path().display().to_string());
context.set("edit_category", &format!("{:?}", category));
context.set("system_performance", "optimal");
if let Some(introspect_op) = self.cognitive_operators.get("introspect") {
if let Ok(result) = introspect_op.execute(&context) {
return Ok(Some(result));
}
}
Ok(None)
}
fn calculate_priority_score(&self, _edit: &ModifiableEdit, category: &EditCategory, risk: &RiskAssessment, cognitive: &Option<SymbolicContext>) -> Result<f64> {
let risk_score = risk.overall_score * self.scoring_weights.risk_weight;
let complexity_score = (category.priority() as f64 / 4.0) * self.scoring_weights.complexity_weight;
let impact_score = 0.5 * self.scoring_weights.impact_weight; let confidence_score = 0.8 * self.scoring_weights.confidence_weight;
let cognitive_score = if let Some(ref _cognitive_ctx) = cognitive {
0.7 * self.scoring_weights.cognitive_weight
} else {
0.5 * self.scoring_weights.cognitive_weight
};
Ok(risk_score + complexity_score + impact_score + confidence_score + cognitive_score)
}
fn calculate_classification_confidence(&self, _edit: &ModifiableEdit, category: &EditCategory, risk: &RiskAssessment) -> Result<f64> {
let category_confidence = match category {
EditCategory::Critical { .. } => 0.9,
EditCategory::Safe { .. } => 0.8,
EditCategory::Experimental { .. } => 0.6,
EditCategory::Cosmetic { .. } => 0.95,
};
let risk_confidence = 1.0 - (risk.overall_score * 0.3);
Ok((category_confidence + risk_confidence) / 2.0)
}
fn generate_recommendation(&self, category: &EditCategory, risk: &RiskAssessment, confidence: f64) -> Result<ClassificationRecommendation> {
if risk.overall_score > 0.8 {
return Ok(ClassificationRecommendation::RequireReview {
concerns: vec!["High risk detected".to_string()],
});
}
if confidence > self.config.auto_classification_threshold {
return Ok(ClassificationRecommendation::AutoApprove { confidence });
}
match category {
EditCategory::Critical { .. } => Ok(ClassificationRecommendation::Escalate {
target_level: "Senior".to_string(),
reason: "Critical change requires senior review".to_string(),
}),
EditCategory::Experimental { .. } => Ok(ClassificationRecommendation::RequireReview {
concerns: vec!["Experimental change needs validation".to_string()],
}),
_ => Ok(ClassificationRecommendation::AutoApprove { confidence }),
}
}
fn generate_reasoning(&self, edit: &ModifiableEdit, category: &EditCategory, risk: &RiskAssessment, cognitive: &Option<SymbolicContext>) -> Result<String> {
let mut reasoning = String::new();
reasoning.push_str(&format!("File: {}\n", edit.target_path().display()));
reasoning.push_str(&format!("Category: {:?}\n", category));
reasoning.push_str(&format!("Risk Score: {:.2}\n", risk.overall_score));
reasoning.push_str(&format!("Risk Explanation: {}\n", risk.explanation));
if let Some(ref _cognitive_ctx) = cognitive {
reasoning.push_str("Cognitive analysis: Enhanced decision support enabled\n");
}
Ok(reasoning)
}
}
#[derive(Debug, Clone)]
pub struct ClassificationStats {
pub total_count: usize,
pub category_distribution: HashMap<std::mem::Discriminant<EditCategory>, usize>,
pub average_risk: f64,
pub average_priority: f64,
pub average_confidence: f64,
}
impl Default for EditClassificationSystem {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::edit_control::ProposedEdit;
use std::path::PathBuf;
fn create_test_edit(path: &str, content: &str) -> ModifiableEdit {
let proposed = ProposedEdit::new(
PathBuf::from(path),
PathBuf::from(path),
content.to_string(),
"Test edit".to_string(),
);
ModifiableEdit::from_proposed_edit(proposed)
}
#[test]
fn test_classification_system_creation() {
let system = EditClassificationSystem::new();
assert!(system.config.use_cognitive_analysis);
assert_eq!(system.config.auto_classification_threshold, 0.8);
}
#[test]
fn test_edit_classification() {
let system = EditClassificationSystem::new();
let edit = create_test_edit("test.md", "# Test Documentation");
let classified = system.classify_edit(&edit).unwrap();
assert!(matches!(classified.category, EditCategory::Safe { .. }));
assert!(classified.priority_score > 0.0);
assert!(classified.classification_confidence > 0.0);
}
#[test]
fn test_security_pattern_detection() {
let system = EditClassificationSystem::new();
let edit = create_test_edit("security.rs", "let password = \"secret123\";");
let classified = system.classify_edit(&edit).unwrap();
assert!(matches!(classified.category, EditCategory::Critical { .. }));
assert!(classified.risk_assessment.overall_score > 0.5);
}
#[test]
fn test_batch_classification() {
let system = EditClassificationSystem::new();
let edits = vec![
create_test_edit("file1.md", "Documentation"),
create_test_edit("file2.rs", "fn main() {}"),
];
let classified = system.classify_batch(&edits).unwrap();
assert_eq!(classified.len(), 2);
assert!(classified[0].priority_score >= classified[1].priority_score);
}
#[test]
fn test_filtering() {
let system = EditClassificationSystem::new();
let edit = create_test_edit("test.md", "Documentation");
let classified = system.classify_edit(&edit).unwrap();
let mut criteria = FilterCriteria::default();
criteria.min_priority = Some(0.0);
let filtered = system.filter_edits(&[classified], &criteria);
assert_eq!(filtered.len(), 1);
}
#[test]
fn test_classification_stats() {
let system = EditClassificationSystem::new();
let edits = vec![
create_test_edit("file1.md", "Documentation"),
create_test_edit("file2.rs", "fn main() {}"),
];
let classified = system.classify_batch(&edits).unwrap();
let stats = system.get_classification_stats(&classified);
assert_eq!(stats.total_count, 2);
assert!(stats.average_risk > 0.0);
assert!(stats.average_priority > 0.0);
assert!(stats.average_confidence > 0.0);
}
#[test]
fn test_recommendation_generation() {
let system = EditClassificationSystem::new();
let category = EditCategory::Safe {
subcategory: SafeType::Documentation,
confidence_level: ConfidenceLevel::High,
};
let risk = RiskAssessment::new(0.1, "Low risk".to_string());
let recommendation = system.generate_recommendation(&category, &risk, 0.9).unwrap();
assert!(matches!(recommendation, ClassificationRecommendation::AutoApprove { .. }));
}
#[test]
fn test_cognitive_integration() {
let mut config = ClassificationConfig::default();
config.use_cognitive_analysis = true;
let system = EditClassificationSystem::with_config(config);
let edit = create_test_edit("test.rs", "fn main() {}");
let classified = system.classify_edit(&edit).unwrap();
assert!(classified.cognitive_analysis.is_some());
}
#[test]
fn test_pattern_database() {
let patterns = PatternDatabase::default();
assert!(patterns.file_patterns.contains_key(".rs"));
assert!(patterns.file_patterns.contains_key(".md"));
assert!(!patterns.security_patterns.is_empty());
assert!(!patterns.performance_patterns.is_empty());
}
}