pmat 3.11.0

PMAT - Zero-config AI context generation and code quality toolkit (CLI, MCP, HTTP)
/// Equivalent mutant detector
#[derive(Debug)]
pub struct EquivalentMutantDetector {
    /// Known equivalence patterns
    equivalence_patterns: HashMap<String, f64>,

    /// Pattern confidence scores
    pattern_confidence: HashMap<String, f64>,

    /// Is the detector trained?
    trained: bool,

    /// Training samples count
    training_samples: usize,
}

impl EquivalentMutantDetector {
    /// Create new detector
    pub fn new() -> Self {
        Self {
            equivalence_patterns: HashMap::new(),
            pattern_confidence: HashMap::new(),
            trained: false,
            training_samples: 0,
        }
    }

    /// Train the detector on labeled data
    pub fn train(&mut self, training_data: &[EquivalenceTrainingData]) -> Result<()> {
        if training_data.is_empty() {
            anyhow::bail!("Training data cannot be empty");
        }

        // Phase 1: Pattern-based detection
        for sample in training_data {
            let features =
                EquivalenceFeatures::from_mutant_pair(&sample.mutant, &sample.original_source);

            for pattern in &features.operator_patterns {
                let entry = self
                    .equivalence_patterns
                    .entry(pattern.clone())
                    .or_insert(0.0);

                if sample.is_equivalent {
                    *entry += 1.0;
                }

                // Track pattern confidence
                let confidence = if sample.verified_manually { 1.0 } else { 0.7 };
                self.pattern_confidence.insert(pattern.clone(), confidence);
            }
        }

        // Normalize pattern scores
        let total = training_data.len() as f64;
        for score in self.equivalence_patterns.values_mut() {
            *score /= total;
        }

        self.trained = true;
        self.training_samples = training_data.len();

        Ok(())
    }

    /// Update detector with new patterns
    pub fn update(&mut self, new_data: &[EquivalenceTrainingData]) -> Result<()> {
        if !self.trained {
            return self.train(new_data);
        }

        self.training_samples += new_data.len();

        for sample in new_data {
            let features =
                EquivalenceFeatures::from_mutant_pair(&sample.mutant, &sample.original_source);

            for pattern in &features.operator_patterns {
                let current = self
                    .equivalence_patterns
                    .get(pattern)
                    .copied()
                    .unwrap_or(0.5);

                let alpha = 0.3; // Learning rate
                let new_score = if sample.is_equivalent {
                    current * (1.0 - alpha) + alpha
                } else {
                    current * (1.0 - alpha)
                };

                self.equivalence_patterns.insert(pattern.clone(), new_score);
            }
        }

        Ok(())
    }

    /// Detect if a mutant is equivalent to the original
    pub fn detect_equivalent(&self, mutant: &Mutant, original: &str) -> Result<EquivalenceResult> {
        if !self.trained {
            anyhow::bail!("Detector not trained");
        }

        let features = EquivalenceFeatures::from_mutant_pair(mutant, original);

        // Check for known equivalence patterns
        let patterns = features.operator_patterns.clone();

        let (is_equivalent, confidence, reason) = if features.has_identity_ops {
            // Identity operations
            (
                true,
                0.9,
                "Contains identity operation (e.g., +0, *1, -0, /1)".to_string(),
            )
        } else if features.has_tautology {
            // Boolean tautology
            (
                true,
                0.85,
                "Contains boolean tautology (e.g., x || true → true)".to_string(),
            )
        } else if features.has_commutative {
            // Commutative swap
            (
                true,
                0.8,
                "commutative operation swap (e.g., a + b → b + a)".to_string(),
            )
        } else {
            // Pattern-based detection
            let mut total_score = 0.0;
            let mut pattern_count = 0;

            for pattern in &features.operator_patterns {
                if let Some(&score) = self.equivalence_patterns.get(pattern) {
                    total_score += score;
                    pattern_count += 1;
                }
            }

            if pattern_count > 0 {
                let avg_score = total_score / pattern_count as f64;
                (
                    avg_score > 0.6,
                    avg_score,
                    format!("Pattern-based detection (score: {:.2})", avg_score),
                )
            } else {
                (
                    false,
                    0.5,
                    "No known equivalence patterns detected".to_string(),
                )
            }
        };

        Ok(EquivalenceResult {
            is_equivalent,
            confidence,
            reason,
            patterns,
        })
    }

    /// Detect with human-readable explanation
    pub fn detect_with_explanation(
        &self,
        mutant: &Mutant,
        original: &str,
    ) -> Result<(EquivalenceResult, String)> {
        let result = self.detect_equivalent(mutant, original)?;

        let explanation = if result.is_equivalent {
            format!(
                "Mutant is EQUIVALENT to original (confidence: {:.1}%). Reason: {}",
                result.confidence * 100.0,
                result.reason
            )
        } else {
            format!(
                "Mutant is NOT EQUIVALENT to original (confidence: {:.1}%). Reason: {}",
                result.confidence * 100.0,
                result.reason
            )
        };

        Ok((result, explanation))
    }

    /// Filter out equivalent mutants from a list
    pub fn filter_equivalents(
        &self,
        mutants: &[Mutant],
        original_sources: &[(&str, &str)],
    ) -> Result<Vec<(Mutant, EquivalenceResult)>> {
        let mut non_equivalents = Vec::new();

        for (i, mutant) in mutants.iter().enumerate() {
            // Find matching original source (simplified for Phase 1)
            let original = if i < original_sources.len() {
                original_sources[i].1
            } else {
                ""
            };

            let result = self.detect_equivalent(mutant, original)?;

            if !result.is_equivalent {
                non_equivalents.push((mutant.clone(), result));
            }
        }

        Ok(non_equivalents)
    }

    /// Get accuracy estimate based on training data
    pub fn get_accuracy_estimate(&self) -> f64 {
        if !self.trained {
            return 0.0;
        }

        // Simple estimate based on pattern coverage
        let pattern_count = self.equivalence_patterns.len();
        (pattern_count as f64 / (pattern_count as f64 + 10.0)).min(0.95)
    }

    /// Check if detector is trained
    pub fn is_trained(&self) -> bool {
        self.trained
    }

    /// Save detector to file
    pub fn save(&self, path: &Path) -> Result<()> {
        let serialized = bincode::serialize(self)?;
        std::fs::write(path, serialized)?;
        Ok(())
    }

    /// Load detector from file
    pub fn load(path: &Path) -> Result<Self> {
        let data = std::fs::read(path)?;
        let detector = bincode::deserialize(&data)?;
        Ok(detector)
    }
}

impl Default for EquivalentMutantDetector {
    fn default() -> Self {
        Self::new()
    }
}

// Serialization support
impl Serialize for EquivalentMutantDetector {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        use serde::ser::SerializeStruct;
        let mut state = serializer.serialize_struct("EquivalentMutantDetector", 4)?;
        state.serialize_field("equivalence_patterns", &self.equivalence_patterns)?;
        state.serialize_field("pattern_confidence", &self.pattern_confidence)?;
        state.serialize_field("trained", &self.trained)?;
        state.serialize_field("training_samples", &self.training_samples)?;
        state.end()
    }
}

impl<'de> Deserialize<'de> for EquivalentMutantDetector {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        #[derive(Deserialize)]
        struct DetectorData {
            equivalence_patterns: HashMap<String, f64>,
            pattern_confidence: HashMap<String, f64>,
            trained: bool,
            training_samples: usize,
        }

        let data = DetectorData::deserialize(deserializer)?;
        Ok(Self {
            equivalence_patterns: data.equivalence_patterns,
            pattern_confidence: data.pattern_confidence,
            trained: data.trained,
            training_samples: data.training_samples,
        })
    }
}