phantomdev_detector/
lib.rs1use phantomdev_core::{CodeBlock, DetectionResult, Detector, Pattern, PatternType, StealthScore};
11use std::sync::Arc;
12
13pub struct PhantomDetector {
15 config: DetectorConfig,
16 local_model: Option<Arc<dyn LocalModel>>,
17 cloud_client: Option<Arc<dyn CloudClient>>,
18}
19
20#[derive(Clone)]
22pub struct DetectorConfig {
23 pub use_local: bool,
25 pub use_cloud_fallback: bool,
27 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 pub fn new() -> anyhow::Result<Self> {
44 Self::with_config(DetectorConfig::default())
45 }
46
47 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 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 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 fn detect_patterns(&self, code: &CodeBlock) -> Vec<Pattern> {
88 let mut patterns = Vec::new();
89
90 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 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 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 let mut result = if let Some(local_result) = self.detect_local(code)? {
130 local_result
131 } else {
132 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 DetectionResult {
139 score: StealthScore::new(0.5, 0.5, 0.5),
140 patterns: Vec::new(),
141 sections: Vec::new(),
142 }
143 }
144 };
145
146 let patterns = self.detect_patterns(code);
148 result.patterns.extend(patterns);
149
150 Ok(result)
151 }
152}
153
154pub trait LocalModel: Send + Sync {
156 fn detect(&self, code: &CodeBlock) -> anyhow::Result<DetectionResult>;
157}
158
159#[async_trait::async_trait]
161pub trait CloudClient: Send + Sync {
162 async fn detect(&self, code: &CodeBlock) -> anyhow::Result<DetectionResult>;
163}
164
165pub struct RobertaDetector {
167 }
169
170impl RobertaDetector {
171 pub fn new() -> anyhow::Result<Self> {
172 Ok(Self {})
174 }
175}
176
177impl LocalModel for RobertaDetector {
178 fn detect(&self, _code: &CodeBlock) -> anyhow::Result<DetectionResult> {
179 Ok(DetectionResult {
182 score: StealthScore::new(0.5, 0.5, 0.5),
183 patterns: Vec::new(),
184 sections: Vec::new(),
185 })
186 }
187}
188
189pub struct CloudDetector {
191 }
193
194impl CloudDetector {
195 pub fn new() -> anyhow::Result<Self> {
196 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 Ok(DetectionResult {
207 score: StealthScore::new(0.5, 0.5, 0.5),
208 patterns: Vec::new(),
209 sections: Vec::new(),
210 })
211 }
212}
213
214fn is_emoji(c: char) -> bool {
216 let as_u32 = c as u32;
218 (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) }
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}