use crate::classification::types::*;
use crate::edit_control::{ModifiableEdit, ApprovalState};
use crate::memory::SymbolicContext;
use anyhow::Result;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct EditClassificationSystem {
config: ClassificationConfig,
}
#[derive(Debug, Clone)]
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: false,
auto_classification_threshold: 0.8,
max_batch_size: 100,
verbose_logging: false,
risk_strictness: 1.0,
}
}
}
#[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)]
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>,
}
#[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 EditClassificationSystem {
pub fn new() -> Self {
Self {
config: ClassificationConfig::default(),
}
}
pub fn with_config(config: ClassificationConfig) -> Self {
Self { config }
}
pub fn classify_edit(&self, edit: &ModifiableEdit) -> Result<ClassifiedEdit> {
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::anyhow!("Batch size {} exceeds maximum {}", edits.len(), self.config.max_batch_size));
}
let mut classified: Vec<ClassifiedEdit> = edits
.iter()
.map(|edit| self.classify_edit(edit))
.collect::<Result<Vec<_>>>()?;
classified.sort_by(|a, b| b.priority_score.partial_cmp(&a.priority_score).unwrap_or(std::cmp::Ordering::Equal));
Ok(classified)
}
pub fn filter_edits<'a>(&self, edits: &'a [ClassifiedEdit], criteria: &FilterCriteria) -> Vec<&'a ClassifiedEdit> {
edits
.iter()
.filter(|edit| {
if let Some(categories) = &criteria.categories {
if !categories.contains(&edit.category) {
return false;
}
}
if let Some(max_risk) = criteria.max_risk {
if edit.risk_assessment.overall_score > max_risk {
return false;
}
}
if let Some(min_priority) = criteria.min_priority {
if edit.priority_score < min_priority {
return false;
}
}
if let Some(min_confidence) = criteria.min_confidence {
if edit.classification_confidence < min_confidence {
return false;
}
}
if let Some(approval_states) = &criteria.approval_states {
if !approval_states.contains(&edit.edit.approval_state) {
return false;
}
}
true
})
.collect()
}
pub fn get_classification_stats(&self, edits: &[ClassifiedEdit]) -> ClassificationStats {
let total_count = edits.len();
let mut category_distribution = HashMap::new();
for edit in edits {
let discriminant = std::mem::discriminant(&edit.category);
*category_distribution.entry(discriminant).or_insert(0) += 1;
}
let average_risk = if total_count > 0 {
edits.iter().map(|e| e.risk_assessment.overall_score).sum::<f64>() / total_count as f64
} else {
0.0
};
let average_priority = if total_count > 0 {
edits.iter().map(|e| e.priority_score).sum::<f64>() / total_count as f64
} else {
0.0
};
let average_confidence = if total_count > 0 {
edits.iter().map(|e| e.classification_confidence).sum::<f64>() / total_count as f64
} else {
0.0
};
ClassificationStats {
total_count,
category_distribution,
average_risk,
average_priority,
average_confidence,
}
}
fn determine_category(&self, edit: &ModifiableEdit) -> Result<EditCategory> {
let file_path = &edit.base_edit.file;
let content = &edit.base_edit.new_code;
let security_patterns = ["password", "secret", "token", "auth", "crypto", "unsafe"];
if security_patterns.iter().any(|pattern| content.contains(pattern)) {
return Ok(EditCategory::Critical {
subcategory: CriticalType::SecurityFix,
impact_scope: ImpactScope::SystemWide,
});
}
if file_path.ends_with(".md") || file_path.ends_with(".txt") || content.starts_with('#') {
return Ok(EditCategory::Safe {
subcategory: SafeType::Documentation,
confidence_level: ConfidenceLevel::High,
});
}
if content.contains("for") && content.contains("loop") || content.contains("allocate") {
return Ok(EditCategory::Experimental {
subcategory: ExperimentalType::PerformanceOptimization,
validation_requirements: vec!["Performance testing required".to_string()],
});
}
Ok(EditCategory::Safe {
subcategory: SafeType::VariableRename,
confidence_level: ConfidenceLevel::Medium,
})
}
fn assess_risk(&self, edit: &ModifiableEdit, category: &EditCategory) -> Result<RiskAssessment> {
let content = &edit.base_edit.new_code;
let category_risk = match category {
EditCategory::Critical { .. } => 0.9,
EditCategory::Experimental { .. } => 0.6,
EditCategory::Safe { .. } => 0.2,
EditCategory::Cosmetic { .. } => 0.1,
};
let mut additional_risk = 0.0;
if content.len() > 1000 {
additional_risk += 0.1; }
if content.contains("unsafe") {
additional_risk += 0.3; }
if content.contains("TODO") || content.contains("FIXME") {
additional_risk += 0.1; }
let sum = category_risk + additional_risk;
let overall_score = if sum > 1.0 { 1.0 } else { sum } * self.config.risk_strictness;
let explanation = format!(
"Risk assessment: category={:.2}, additional={:.2}, overall={:.2}",
category_risk, additional_risk, overall_score
);
Ok(RiskAssessment::builder()
.with_risk_factor(RiskFactor::ChangeComplexity, overall_score)
.with_explanation(explanation)
.with_mitigation("Review changes carefully and test thoroughly".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.base_edit.file);
context.set("edit_category", &format!("{:?}", category));
context.set("edit_size", &edit.base_edit.new_code.len().to_string());
context.set("classification_confidence", "0.8");
Ok(Some(context))
}
fn calculate_priority_score(&self, edit: &ModifiableEdit, category: &EditCategory, risk: &RiskAssessment, cognitive: &Option<SymbolicContext>) -> Result<f64> {
let risk_score = risk.overall_score * 0.4;
let complexity_score = (category.priority() as f64 / 4.0) * 0.3;
let size_ratio = edit.base_edit.new_code.len() as f64 / 10000.0;
let size_score = if size_ratio > 1.0 { 1.0 } else { size_ratio } * 0.2;
let cognitive_score = if cognitive.is_some() { 0.1 } else { 0.05 };
Ok(risk_score + complexity_score + size_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);
let content_confidence = if edit.base_edit.new_code.len() > 100 { 0.8 } else { 0.6 };
Ok((category_confidence + risk_confidence + content_confidence) / 3.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.base_edit.file));
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));
reasoning.push_str(&format!("Content Size: {} chars\n", edit.base_edit.new_code.len()));
if cognitive.is_some() {
reasoning.push_str("Cognitive analysis: Enhanced decision support enabled\n");
}
Ok(reasoning)
}
pub fn config(&self) -> &ClassificationConfig {
&self.config
}
}
impl Default for EditClassificationSystem {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::agents::gpt4_agent::ProposedEdit;
fn create_test_edit(file: &str, content: &str, reason: &str) -> ModifiableEdit {
let proposed = ProposedEdit {
file: file.to_string(),
line_range: (1, 5),
new_code: content.to_string(),
reason: reason.to_string(),
confidence: 0.8,
};
ModifiableEdit::from_proposed_edit(proposed)
}
#[test]
fn test_classification_system_creation() {
let system = EditClassificationSystem::new();
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", "Update docs");
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("auth.rs", "let password = \"secret123\";", "Add auth");
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", "Update docs"),
create_test_edit("file2.rs", "fn main() {}", "Add function"),
];
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", "Update docs");
let classified = system.classify_edit(&edit).unwrap();
let classified_vec = vec![classified];
let mut criteria = FilterCriteria::default();
criteria.min_priority = Some(0.0);
let filtered = system.filter_edits(&classified_vec, &criteria);
assert_eq!(filtered.len(), 1);
}
}