phantomdev-detector 0.1.1

A suite of tools for preserving coding signatures and humanizing AI agent outputs.
Documentation
//! PhantomDev Detection Engine
//!
//! This crate provides AI detection capabilities using local models
//! and cloud API fallbacks.
//!
//! Built with ❤️ by John Varghese (J0X)
//! GitHub: https://github.com/John-Varghese-EH
//! LinkedIn: https://linkedin.com/in/John--Varghese

use phantomdev_core::{CodeBlock, DetectionResult, Detector, Pattern, PatternType, StealthScore};
use std::sync::Arc;

/// Main detector implementation
pub struct PhantomDetector {
    #[allow(dead_code)]
    config: DetectorConfig,
    local_model: Option<Arc<dyn LocalModel>>,
    cloud_client: Option<Arc<dyn CloudClient>>,
}

/// Detector configuration
#[derive(Clone)]
pub struct DetectorConfig {
    /// Whether to use local models
    pub use_local: bool,
    /// Whether to use cloud API as fallback
    pub use_cloud_fallback: bool,
    /// AI probability threshold
    pub threshold: f32,
}

impl Default for DetectorConfig {
    fn default() -> Self {
        Self {
            use_local: true,
            use_cloud_fallback: true,
            threshold: 0.15,
        }
    }
}

impl PhantomDetector {
    /// Create a new detector with default configuration
    pub fn new() -> anyhow::Result<Self> {
        Self::with_config(DetectorConfig::default())
    }

    /// Create a new detector with custom configuration
    pub fn with_config(config: DetectorConfig) -> anyhow::Result<Self> {
        let local_model: Option<Arc<dyn LocalModel>> = if config.use_local {
            Some(Arc::new(RobertaDetector::new()?))
        } else {
            None
        };

        let cloud_client: Option<Arc<dyn CloudClient>> = if config.use_cloud_fallback {
            Some(Arc::new(CloudDetector::new()?))
        } else {
            None
        };

        Ok(Self {
            config,
            local_model,
            cloud_client,
        })
    }

    /// Detect using local model
    fn detect_local(&self, code: &CodeBlock) -> anyhow::Result<Option<DetectionResult>> {
        if let Some(model) = &self.local_model {
            Ok(Some(model.detect(code)?))
        } else {
            Ok(None)
        }
    }

    /// Detect using cloud API
    async fn detect_cloud(&self, code: &CodeBlock) -> anyhow::Result<Option<DetectionResult>> {
        if let Some(client) = &self.cloud_client {
            Ok(Some(client.detect(code).await?))
        } else {
            Ok(None)
        }
    }

    /// Detect patterns in code
    fn detect_patterns(&self, code: &CodeBlock) -> Vec<Pattern> {
        let mut patterns = Vec::new();

        // Check for excessive emojis
        let emoji_count = code.content.chars().filter(|c| is_emoji(*c)).count();
        if emoji_count > 3 {
            patterns.push(Pattern {
                pattern_type: PatternType::ExcessiveEmojis,
                confidence: (emoji_count as f32 / 10.0).min(1.0),
                location: None,
            });
        }

        // Check for watermarks
        if code.content.contains("TODO") || code.content.contains("FIXME") {
            patterns.push(Pattern {
                pattern_type: PatternType::Watermark,
                confidence: 0.3,
                location: None,
            });
        }

        // Check for uniform comments
        let comment_lines: Vec<_> = code.content
            .lines()
            .filter(|l| l.trim().starts_with("//") || l.trim().starts_with("#"))
            .collect();
        if comment_lines.len() > 5 {
            patterns.push(Pattern {
                pattern_type: PatternType::UniformComments,
                confidence: 0.4,
                location: None,
            });
        }

        patterns
    }
}

impl Detector for PhantomDetector {
    fn detect(&self, code: &CodeBlock) -> anyhow::Result<DetectionResult> {
        // Try local detection first
        let mut result = if let Some(local_result) = self.detect_local(code)? {
            local_result
        } else {
            // Fall back to cloud detection
            let runtime = tokio::runtime::Runtime::new()?;
            if let Some(cloud_result) = runtime.block_on(self.detect_cloud(code))? {
                cloud_result
            } else {
                // Default result if no detection available
                DetectionResult {
                    score: StealthScore::new(0.5, 0.5, 0.5),
                    patterns: Vec::new(),
                    sections: Vec::new(),
                }
            }
        };

        // Add pattern detection
        let patterns = self.detect_patterns(code);
        result.patterns.extend(patterns);

        Ok(result)
    }
}

/// Trait for local ML models
pub trait LocalModel: Send + Sync {
    fn detect(&self, code: &CodeBlock) -> anyhow::Result<DetectionResult>;
}

/// Trait for cloud API clients
#[async_trait::async_trait]
pub trait CloudClient: Send + Sync {
    async fn detect(&self, code: &CodeBlock) -> anyhow::Result<DetectionResult>;
}

/// RoBERTa-based local detector
pub struct RobertaDetector {
    // Model placeholder - will be implemented with candle
}

impl RobertaDetector {
    pub fn new() -> anyhow::Result<Self> {
        // TODO: Initialize candle model
        Ok(Self {})
    }
}

impl LocalModel for RobertaDetector {
    fn detect(&self, _code: &CodeBlock) -> anyhow::Result<DetectionResult> {
        // TODO: Implement actual detection with RoBERTa model
        // For now, return a placeholder result
        Ok(DetectionResult {
            score: StealthScore::new(0.5, 0.5, 0.5),
            patterns: Vec::new(),
            sections: Vec::new(),
        })
    }
}

/// Cloud-based detector
pub struct CloudDetector {
    // API client placeholder
}

impl CloudDetector {
    pub fn new() -> anyhow::Result<Self> {
        // TODO: Initialize API client
        Ok(Self {})
    }
}

#[async_trait::async_trait]
impl CloudClient for CloudDetector {
    async fn detect(&self, _code: &CodeBlock) -> anyhow::Result<DetectionResult> {
        // TODO: Implement actual cloud API detection
        // For now, return a placeholder result
        Ok(DetectionResult {
            score: StealthScore::new(0.5, 0.5, 0.5),
            patterns: Vec::new(),
            sections: Vec::new(),
        })
    }
}

/// Check if a character is an emoji
fn is_emoji(c: char) -> bool {
    // Basic emoji detection - can be improved
    let as_u32 = c as u32;
    (0x1F600..=0x1F64F).contains(&as_u32) // Emoticons
        || (0x1F300..=0x1F5FF).contains(&as_u32) // Misc Symbols and Pictographs
        || (0x1F680..=0x1F6FF).contains(&as_u32) // Transport and Map
        || (0x1F1E0..=0x1F1FF).contains(&as_u32) // Flags
        || (0x2600..=0x26FF).contains(&as_u32) // Misc symbols
        || (0x2700..=0x27BF).contains(&as_u32) // Dingbats
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_detector_creation() {
        let detector = PhantomDetector::new().unwrap();
        assert!(detector.local_model.is_some());
    }

    #[test]
    fn test_pattern_detection() {
        let detector = PhantomDetector::new().unwrap();
        let code = CodeBlock {
            path: "test.rs".into(),
            language: phantomdev_core::Language::Rust,
            content: "// TODO: fix this 🎉\n// Another comment 🚀\n// Yet another 🎯".to_string(),
            line_range: (1, 3),
        };
        let patterns = detector.detect_patterns(&code);
        assert!(!patterns.is_empty());
    }
}