Skip to main content

phantomdev_core/
lib.rs

1//! PhantomDev Core Library
2//!
3//! This crate provides the foundational types, traits, and configuration
4//! for the PhantomDev adversarial stylometry framework.
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 serde::{Deserialize, Serialize};
11use std::path::PathBuf;
12
13/// Represents a block of code with metadata
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct CodeBlock {
16    /// The file path
17    pub path: PathBuf,
18    /// The programming language
19    pub language: Language,
20    /// The source code content
21    pub content: String,
22    /// Line numbers (start, end)
23    pub line_range: (usize, usize),
24}
25
26/// Supported programming languages
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
28pub enum Language {
29    Rust,
30    Python,
31    JavaScript,
32    TypeScript,
33    Go,
34    Cpp,
35    C,
36    Unknown,
37}
38
39impl Language {
40    pub fn from_extension(ext: &str) -> Self {
41        match ext.to_lowercase().as_str() {
42            "rs" => Language::Rust,
43            "py" => Language::Python,
44            "js" | "mjs" | "cjs" => Language::JavaScript,
45            "ts" | "mts" | "cts" => Language::TypeScript,
46            "go" => Language::Go,
47            "cpp" | "cc" | "cxx" => Language::Cpp,
48            "c" | "h" => Language::C,
49            _ => Language::Unknown,
50        }
51    }
52
53    pub fn extension(&self) -> Option<&'static str> {
54        match self {
55            Language::Rust => Some("rs"),
56            Language::Python => Some("py"),
57            Language::JavaScript => Some("js"),
58            Language::TypeScript => Some("ts"),
59            Language::Go => Some("go"),
60            Language::Cpp => Some("cpp"),
61            Language::C => Some("c"),
62            Language::Unknown => None,
63        }
64    }
65}
66
67/// Represents a commit message
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct CommitMessage {
70    /// The full commit message
71    pub message: String,
72    /// The subject line (first line)
73    pub subject: String,
74    /// The body (everything after the first line)
75    pub body: Option<String>,
76}
77
78/// Style profile extracted from repository analysis
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct StyleProfile {
81    /// Naming convention preference
82    pub naming_convention: NamingConvention,
83    /// Comment style preference
84    pub comment_style: CommentStyle,
85    /// Line length preference
86    pub max_line_length: Option<usize>,
87    /// Indentation style
88    pub indentation: IndentationStyle,
89    /// Commit message format
90    pub commit_format: CommitFormat,
91}
92
93/// Naming convention preferences
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub enum NamingConvention {
96    SnakeCase,
97    CamelCase,
98    PascalCase,
99    KebabCase,
100    Mixed,
101}
102
103/// Comment style preferences
104#[derive(Debug, Clone, Serialize, Deserialize)]
105pub enum CommentStyle {
106    None,
107    InlineOnly,
108    BlockOnly,
109    Both,
110}
111
112/// Indentation style
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub enum IndentationStyle {
115    Spaces(u8),
116    Tabs,
117}
118
119/// Commit message format
120#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct CommitFormat {
122    /// Whether to use conventional commits
123    pub conventional: bool,
124    /// Typical subject length
125    pub subject_length: Option<usize>,
126    /// Whether body is typically present
127    pub has_body: bool,
128}
129
130/// Stealth score for code
131#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct StealthScore {
133    /// Overall stealth score (0.0 = obviously AI, 1.0 = definitely human)
134    pub overall: f32,
135    /// AI detection probability (inverse of stealth)
136    pub ai_probability: f32,
137    /// Pattern-based score
138    pub pattern_score: f32,
139    /// Style match score
140    pub style_score: f32,
141}
142
143impl StealthScore {
144    pub fn new(ai_probability: f32, pattern_score: f32, style_score: f32) -> Self {
145        let overall = 1.0 - (ai_probability * 0.5 + pattern_score * 0.3 + style_score * 0.2);
146        Self {
147            overall: overall.max(0.0).min(1.0),
148            ai_probability,
149            pattern_score,
150            style_score,
151        }
152    }
153
154    /// Returns true if the code is likely AI-generated
155    pub fn is_likely_ai(&self, threshold: f32) -> bool {
156        self.ai_probability > threshold
157    }
158}
159
160/// Result of AI detection
161#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct DetectionResult {
163    /// The stealth score
164    pub score: StealthScore,
165    /// Detected patterns
166    pub patterns: Vec<Pattern>,
167    /// Per-section results
168    pub sections: Vec<SectionResult>,
169}
170
171/// A detected pattern
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct Pattern {
174    /// Pattern type
175    pub pattern_type: PatternType,
176    /// Confidence score
177    pub confidence: f32,
178    /// Location in the code
179    pub location: Option<(usize, usize)>,
180}
181
182/// Types of patterns that can be detected
183#[derive(Debug, Clone, Serialize, Deserialize)]
184pub enum PatternType {
185    /// Watermark patterns
186    Watermark,
187    /// Excessive emoji usage
188    ExcessiveEmojis,
189    /// Uniform comment style
190    UniformComments,
191    /// Predictable variable naming
192    PredictableNaming,
193    /// Lack of entropy
194    LowEntropy,
195    /// Other pattern
196    Other(String),
197}
198
199/// Result for a specific section of code
200#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct SectionResult {
202    /// Section identifier
203    pub section_id: String,
204    /// Stealth score for this section
205    pub score: StealthScore,
206    /// Patterns found in this section
207    pub patterns: Vec<Pattern>,
208}
209
210/// Trait for AI detectors
211pub trait Detector: Send + Sync {
212    /// Detect AI-generated content in a code block
213    fn detect(&self, code: &CodeBlock) -> anyhow::Result<DetectionResult>;
214
215    /// Batch detect multiple code blocks
216    fn detect_batch(&self, codes: &[CodeBlock]) -> anyhow::Result<Vec<DetectionResult>> {
217        codes.iter().map(|c| self.detect(c)).collect()
218    }
219}
220
221/// Trait for code humanizers
222pub trait Humanizer: Send + Sync {
223    /// Humanize a code block based on a style profile
224    fn humanize(&self, code: &CodeBlock, profile: &StyleProfile) -> anyhow::Result<String>;
225
226    /// Learn style from a repository
227    fn learn_style(&self, repo_path: &PathBuf) -> anyhow::Result<StyleProfile>;
228}
229
230/// Trait for jitter engines (temporal obfuscation)
231pub trait JitterEngine: Send + Sync {
232    /// Apply jitter to a commit operation
233    fn apply_jitter(&self, operation: JitterOperation) -> anyhow::Result<JitterSchedule>;
234
235    /// Get the current jitter schedule
236    fn current_schedule(&self) -> Option<JitterSchedule>;
237}
238
239/// Types of operations that can be jittered
240#[derive(Debug, Clone)]
241pub enum JitterOperation {
242    /// Stage files
243    Stage { files: Vec<PathBuf> },
244    /// Create a commit
245    Commit { message: String },
246    /// Push to remote
247    Push,
248}
249
250/// Schedule for a jittered operation
251#[derive(Debug, Clone)]
252pub struct JitterSchedule {
253    /// When to execute the operation
254    pub execute_at: chrono::DateTime<chrono::Utc>,
255    /// The operation to execute
256    pub operation: JitterOperation,
257}
258
259/// Configuration for PhantomDev
260#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct Config {
262    /// Detection settings
263    pub detection: DetectionConfig,
264    /// Humanization settings
265    pub humanization: HumanizationConfig,
266    /// Jitter settings
267    pub jitter: JitterConfig,
268    /// API settings
269    pub api: ApiConfig,
270}
271
272/// Detection configuration
273#[derive(Debug, Clone, Serialize, Deserialize)]
274pub struct DetectionConfig {
275    /// AI probability threshold
276    pub threshold: f32,
277    /// Whether to use local models
278    pub use_local: bool,
279    /// Whether to use cloud API as fallback
280    pub use_cloud_fallback: bool,
281}
282
283/// Humanization configuration
284#[derive(Debug, Clone, Serialize, Deserialize)]
285pub struct HumanizationConfig {
286    /// Whether to auto-humanize on commit
287    pub auto_humanize: bool,
288    /// Entropy level (0.0 - 1.0)
289    pub entropy_level: f32,
290}
291
292/// Jitter configuration
293#[derive(Debug, Clone, Serialize, Deserialize)]
294pub struct JitterConfig {
295    /// Whether jitter is enabled
296    pub enabled: bool,
297    /// Minimum delay in seconds
298    pub min_delay_secs: u64,
299    /// Maximum delay in seconds
300    pub max_delay_secs: u64,
301}
302
303/// API configuration
304#[derive(Debug, Clone, Serialize, Deserialize)]
305pub struct ApiConfig {
306    /// OpenRouter API key (optional)
307    pub openrouter_key: Option<String>,
308    /// Anthropic API key (optional)
309    pub anthropic_key: Option<String>,
310    /// API base URL
311    pub base_url: Option<String>,
312}
313
314impl Default for Config {
315    fn default() -> Self {
316        Self {
317            detection: DetectionConfig {
318                threshold: 0.15,
319                use_local: true,
320                use_cloud_fallback: true,
321            },
322            humanization: HumanizationConfig {
323                auto_humanize: false,
324                entropy_level: 0.5,
325            },
326            jitter: JitterConfig {
327                enabled: false,
328                min_delay_secs: 60,
329                max_delay_secs: 300,
330            },
331            api: ApiConfig {
332                openrouter_key: None,
333                anthropic_key: None,
334                base_url: None,
335            },
336        }
337    }
338}
339
340impl Config {
341    /// Load configuration from a file
342    pub fn load(path: &PathBuf) -> anyhow::Result<Self> {
343        let content = std::fs::read_to_string(path)?;
344        let config: Config = toml::from_str(&content)?;
345        Ok(config)
346    }
347
348    /// Save configuration to a file
349    pub fn save(&self, path: &PathBuf) -> anyhow::Result<()> {
350        let content = toml::to_string_pretty(self)?;
351        std::fs::write(path, content)?;
352        Ok(())
353    }
354
355    /// Load or create default configuration
356    pub fn load_or_default(path: &PathBuf) -> anyhow::Result<Self> {
357        if path.exists() {
358            Self::load(path)
359        } else {
360            let config = Self::default();
361            config.save(path)?;
362            Ok(config)
363        }
364    }
365}