Skip to main content

phantomdev_detector/
lib.rs

1//! PhantomDev Detection Engine
2//!
3//! This crate provides AI detection capabilities using local models
4//! and cloud API fallbacks.
5//!
6//! Built with ❤️ by John Varghese (J0X)
7//! GitHub: https://github.com/John-Varghese-EH
8//! LinkedIn: https://linkedin.com/in/John--Varghese
9
10use phantomdev_core::{CodeBlock, DetectionResult, Detector, Pattern, PatternType, StealthScore};
11use std::sync::Arc;
12
13/// Main detector implementation
14pub struct PhantomDetector {
15    config: DetectorConfig,
16    local_model: Option<Arc<dyn LocalModel>>,
17    cloud_client: Option<Arc<dyn CloudClient>>,
18}
19
20/// Detector configuration
21#[derive(Clone)]
22pub struct DetectorConfig {
23    /// Whether to use local models
24    pub use_local: bool,
25    /// Whether to use cloud API as fallback
26    pub use_cloud_fallback: bool,
27    /// AI probability threshold
28    pub threshold: f32,
29}
30
31impl Default for DetectorConfig {
32    fn default() -> Self {
33        Self {
34            use_local: true,
35            use_cloud_fallback: true,
36            threshold: 0.15,
37        }
38    }
39}
40
41impl PhantomDetector {
42    /// Create a new detector with default configuration
43    pub fn new() -> anyhow::Result<Self> {
44        Self::with_config(DetectorConfig::default())
45    }
46
47    /// Create a new detector with custom configuration
48    pub fn with_config(config: DetectorConfig) -> anyhow::Result<Self> {
49        let local_model: Option<Arc<dyn LocalModel>> = if config.use_local {
50            Some(Arc::new(RobertaDetector::new()?))
51        } else {
52            None
53        };
54
55        let cloud_client: Option<Arc<dyn CloudClient>> = if config.use_cloud_fallback {
56            Some(Arc::new(CloudDetector::new()?))
57        } else {
58            None
59        };
60
61        Ok(Self {
62            config,
63            local_model,
64            cloud_client,
65        })
66    }
67
68    /// Detect using local model
69    fn detect_local(&self, code: &CodeBlock) -> anyhow::Result<Option<DetectionResult>> {
70        if let Some(model) = &self.local_model {
71            Ok(Some(model.detect(code)?))
72        } else {
73            Ok(None)
74        }
75    }
76
77    /// Detect using cloud API
78    async fn detect_cloud(&self, code: &CodeBlock) -> anyhow::Result<Option<DetectionResult>> {
79        if let Some(client) = &self.cloud_client {
80            Ok(Some(client.detect(code).await?))
81        } else {
82            Ok(None)
83        }
84    }
85
86    /// Detect patterns in code
87    fn detect_patterns(&self, code: &CodeBlock) -> Vec<Pattern> {
88        let mut patterns = Vec::new();
89
90        // Check for excessive emojis
91        let emoji_count = code.content.chars().filter(|c| is_emoji(*c)).count();
92        if emoji_count > 3 {
93            patterns.push(Pattern {
94                pattern_type: PatternType::ExcessiveEmojis,
95                confidence: (emoji_count as f32 / 10.0).min(1.0),
96                location: None,
97            });
98        }
99
100        // Check for watermarks
101        if code.content.contains("TODO") || code.content.contains("FIXME") {
102            patterns.push(Pattern {
103                pattern_type: PatternType::Watermark,
104                confidence: 0.3,
105                location: None,
106            });
107        }
108
109        // Check for uniform comments
110        let comment_lines: Vec<_> = code.content
111            .lines()
112            .filter(|l| l.trim().starts_with("//") || l.trim().starts_with("#"))
113            .collect();
114        if comment_lines.len() > 5 {
115            patterns.push(Pattern {
116                pattern_type: PatternType::UniformComments,
117                confidence: 0.4,
118                location: None,
119            });
120        }
121
122        patterns
123    }
124}
125
126impl Detector for PhantomDetector {
127    fn detect(&self, code: &CodeBlock) -> anyhow::Result<DetectionResult> {
128        // Try local detection first
129        let mut result = if let Some(local_result) = self.detect_local(code)? {
130            local_result
131        } else {
132            // Fall back to cloud detection
133            let runtime = tokio::runtime::Runtime::new()?;
134            if let Some(cloud_result) = runtime.block_on(self.detect_cloud(code))? {
135                cloud_result
136            } else {
137                // Default result if no detection available
138                DetectionResult {
139                    score: StealthScore::new(0.5, 0.5, 0.5),
140                    patterns: Vec::new(),
141                    sections: Vec::new(),
142                }
143            }
144        };
145
146        // Add pattern detection
147        let patterns = self.detect_patterns(code);
148        result.patterns.extend(patterns);
149
150        Ok(result)
151    }
152}
153
154/// Trait for local ML models
155pub trait LocalModel: Send + Sync {
156    fn detect(&self, code: &CodeBlock) -> anyhow::Result<DetectionResult>;
157}
158
159/// Trait for cloud API clients
160#[async_trait::async_trait]
161pub trait CloudClient: Send + Sync {
162    async fn detect(&self, code: &CodeBlock) -> anyhow::Result<DetectionResult>;
163}
164
165/// RoBERTa-based local detector
166pub struct RobertaDetector {
167    // Model placeholder - will be implemented with candle
168}
169
170impl RobertaDetector {
171    pub fn new() -> anyhow::Result<Self> {
172        // TODO: Initialize candle model
173        Ok(Self {})
174    }
175}
176
177impl LocalModel for RobertaDetector {
178    fn detect(&self, _code: &CodeBlock) -> anyhow::Result<DetectionResult> {
179        // TODO: Implement actual detection with RoBERTa model
180        // For now, return a placeholder result
181        Ok(DetectionResult {
182            score: StealthScore::new(0.5, 0.5, 0.5),
183            patterns: Vec::new(),
184            sections: Vec::new(),
185        })
186    }
187}
188
189/// Cloud-based detector
190pub struct CloudDetector {
191    // API client placeholder
192}
193
194impl CloudDetector {
195    pub fn new() -> anyhow::Result<Self> {
196        // TODO: Initialize API client
197        Ok(Self {})
198    }
199}
200
201#[async_trait::async_trait]
202impl CloudClient for CloudDetector {
203    async fn detect(&self, _code: &CodeBlock) -> anyhow::Result<DetectionResult> {
204        // TODO: Implement actual cloud API detection
205        // For now, return a placeholder result
206        Ok(DetectionResult {
207            score: StealthScore::new(0.5, 0.5, 0.5),
208            patterns: Vec::new(),
209            sections: Vec::new(),
210        })
211    }
212}
213
214/// Check if a character is an emoji
215fn is_emoji(c: char) -> bool {
216    // Basic emoji detection - can be improved
217    let as_u32 = c as u32;
218    (0x1F600..=0x1F64F).contains(&as_u32) // Emoticons
219        || (0x1F300..=0x1F5FF).contains(&as_u32) // Misc Symbols and Pictographs
220        || (0x1F680..=0x1F6FF).contains(&as_u32) // Transport and Map
221        || (0x1F1E0..=0x1F1FF).contains(&as_u32) // Flags
222        || (0x2600..=0x26FF).contains(&as_u32) // Misc symbols
223        || (0x2700..=0x27BF).contains(&as_u32) // Dingbats
224}
225
226#[cfg(test)]
227mod tests {
228    use super::*;
229
230    #[test]
231    fn test_detector_creation() {
232        let detector = PhantomDetector::new().unwrap();
233        assert!(detector.local_model.is_some());
234    }
235
236    #[test]
237    fn test_pattern_detection() {
238        let detector = PhantomDetector::new().unwrap();
239        let code = CodeBlock {
240            path: "test.rs".into(),
241            language: phantomdev_core::Language::Rust,
242            content: "// TODO: fix this 🎉\n// Another comment 🚀\n// Yet another 🎯".to_string(),
243            line_range: (1, 3),
244        };
245        let patterns = detector.detect_patterns(&code);
246        assert!(!patterns.is_empty());
247    }
248}