phantomdev_detector/
lib.rs1use phantomdev_core::{CodeBlock, DetectionResult, Detector, Pattern, PatternType, StealthScore};
11use std::sync::Arc;
12
13pub struct PhantomDetector {
15 #[allow(dead_code)]
16 config: DetectorConfig,
17 local_model: Option<Arc<dyn LocalModel>>,
18 cloud_client: Option<Arc<dyn CloudClient>>,
19}
20
21#[derive(Clone)]
23pub struct DetectorConfig {
24 pub use_local: bool,
26 pub use_cloud_fallback: bool,
28 pub threshold: f32,
30}
31
32impl Default for DetectorConfig {
33 fn default() -> Self {
34 Self {
35 use_local: true,
36 use_cloud_fallback: true,
37 threshold: 0.15,
38 }
39 }
40}
41
42impl PhantomDetector {
43 pub fn new() -> anyhow::Result<Self> {
45 Self::with_config(DetectorConfig::default())
46 }
47
48 pub fn with_config(config: DetectorConfig) -> anyhow::Result<Self> {
50 let local_model: Option<Arc<dyn LocalModel>> = if config.use_local {
51 Some(Arc::new(RobertaDetector::new()?))
52 } else {
53 None
54 };
55
56 let cloud_client: Option<Arc<dyn CloudClient>> = if config.use_cloud_fallback {
57 Some(Arc::new(CloudDetector::new()?))
58 } else {
59 None
60 };
61
62 Ok(Self {
63 config,
64 local_model,
65 cloud_client,
66 })
67 }
68
69 fn detect_local(&self, code: &CodeBlock) -> anyhow::Result<Option<DetectionResult>> {
71 if let Some(model) = &self.local_model {
72 Ok(Some(model.detect(code)?))
73 } else {
74 Ok(None)
75 }
76 }
77
78 async fn detect_cloud(&self, code: &CodeBlock) -> anyhow::Result<Option<DetectionResult>> {
80 if let Some(client) = &self.cloud_client {
81 Ok(Some(client.detect(code).await?))
82 } else {
83 Ok(None)
84 }
85 }
86
87 fn detect_patterns(&self, code: &CodeBlock) -> Vec<Pattern> {
89 let mut patterns = Vec::new();
90
91 let emoji_count = code.content.chars().filter(|c| is_emoji(*c)).count();
93 if emoji_count > 3 {
94 patterns.push(Pattern {
95 pattern_type: PatternType::ExcessiveEmojis,
96 confidence: (emoji_count as f32 / 10.0).min(1.0),
97 location: None,
98 });
99 }
100
101 if code.content.contains("TODO") || code.content.contains("FIXME") {
103 patterns.push(Pattern {
104 pattern_type: PatternType::Watermark,
105 confidence: 0.3,
106 location: None,
107 });
108 }
109
110 let comment_lines: Vec<_> = code.content
112 .lines()
113 .filter(|l| l.trim().starts_with("//") || l.trim().starts_with("#"))
114 .collect();
115 if comment_lines.len() > 5 {
116 patterns.push(Pattern {
117 pattern_type: PatternType::UniformComments,
118 confidence: 0.4,
119 location: None,
120 });
121 }
122
123 patterns
124 }
125}
126
127impl Detector for PhantomDetector {
128 fn detect(&self, code: &CodeBlock) -> anyhow::Result<DetectionResult> {
129 let mut result = if let Some(local_result) = self.detect_local(code)? {
131 local_result
132 } else {
133 let runtime = tokio::runtime::Runtime::new()?;
135 if let Some(cloud_result) = runtime.block_on(self.detect_cloud(code))? {
136 cloud_result
137 } else {
138 DetectionResult {
140 score: StealthScore::new(0.5, 0.5, 0.5),
141 patterns: Vec::new(),
142 sections: Vec::new(),
143 }
144 }
145 };
146
147 let patterns = self.detect_patterns(code);
149 result.patterns.extend(patterns);
150
151 Ok(result)
152 }
153}
154
155pub trait LocalModel: Send + Sync {
157 fn detect(&self, code: &CodeBlock) -> anyhow::Result<DetectionResult>;
158}
159
160#[async_trait::async_trait]
162pub trait CloudClient: Send + Sync {
163 async fn detect(&self, code: &CodeBlock) -> anyhow::Result<DetectionResult>;
164}
165
166pub struct RobertaDetector {
168 }
170
171impl RobertaDetector {
172 pub fn new() -> anyhow::Result<Self> {
173 Ok(Self {})
175 }
176}
177
178impl LocalModel for RobertaDetector {
179 fn detect(&self, _code: &CodeBlock) -> anyhow::Result<DetectionResult> {
180 Ok(DetectionResult {
183 score: StealthScore::new(0.5, 0.5, 0.5),
184 patterns: Vec::new(),
185 sections: Vec::new(),
186 })
187 }
188}
189
190pub struct CloudDetector {
192 }
194
195impl CloudDetector {
196 pub fn new() -> anyhow::Result<Self> {
197 Ok(Self {})
199 }
200}
201
202#[async_trait::async_trait]
203impl CloudClient for CloudDetector {
204 async fn detect(&self, _code: &CodeBlock) -> anyhow::Result<DetectionResult> {
205 Ok(DetectionResult {
208 score: StealthScore::new(0.5, 0.5, 0.5),
209 patterns: Vec::new(),
210 sections: Vec::new(),
211 })
212 }
213}
214
215fn is_emoji(c: char) -> bool {
217 let as_u32 = c as u32;
219 (0x1F600..=0x1F64F).contains(&as_u32) || (0x1F300..=0x1F5FF).contains(&as_u32) || (0x1F680..=0x1F6FF).contains(&as_u32) || (0x1F1E0..=0x1F1FF).contains(&as_u32) || (0x2600..=0x26FF).contains(&as_u32) || (0x2700..=0x27BF).contains(&as_u32) }
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230
231 #[test]
232 fn test_detector_creation() {
233 let detector = PhantomDetector::new().unwrap();
234 assert!(detector.local_model.is_some());
235 }
236
237 #[test]
238 fn test_pattern_detection() {
239 let detector = PhantomDetector::new().unwrap();
240 let code = CodeBlock {
241 path: "test.rs".into(),
242 language: phantomdev_core::Language::Rust,
243 content: "// TODO: fix this 🎉\n// Another comment 🚀\n// Yet another 🎯".to_string(),
244 line_range: (1, 3),
245 };
246 let patterns = detector.detect_patterns(&code);
247 assert!(!patterns.is_empty());
248 }
249}