use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CodeBlock {
pub path: PathBuf,
pub language: Language,
pub content: String,
pub line_range: (usize, usize),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Language {
Rust,
Python,
JavaScript,
TypeScript,
Go,
Cpp,
C,
Unknown,
}
impl Language {
pub fn from_extension(ext: &str) -> Self {
match ext.to_lowercase().as_str() {
"rs" => Language::Rust,
"py" => Language::Python,
"js" | "mjs" | "cjs" => Language::JavaScript,
"ts" | "mts" | "cts" => Language::TypeScript,
"go" => Language::Go,
"cpp" | "cc" | "cxx" => Language::Cpp,
"c" | "h" => Language::C,
_ => Language::Unknown,
}
}
pub fn extension(&self) -> Option<&'static str> {
match self {
Language::Rust => Some("rs"),
Language::Python => Some("py"),
Language::JavaScript => Some("js"),
Language::TypeScript => Some("ts"),
Language::Go => Some("go"),
Language::Cpp => Some("cpp"),
Language::C => Some("c"),
Language::Unknown => None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitMessage {
pub message: String,
pub subject: String,
pub body: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StyleProfile {
pub naming_convention: NamingConvention,
pub comment_style: CommentStyle,
pub max_line_length: Option<usize>,
pub indentation: IndentationStyle,
pub commit_format: CommitFormat,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum NamingConvention {
SnakeCase,
CamelCase,
PascalCase,
KebabCase,
Mixed,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum CommentStyle {
None,
InlineOnly,
BlockOnly,
Both,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum IndentationStyle {
Spaces(u8),
Tabs,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitFormat {
pub conventional: bool,
pub subject_length: Option<usize>,
pub has_body: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StealthScore {
pub overall: f32,
pub ai_probability: f32,
pub pattern_score: f32,
pub style_score: f32,
}
impl StealthScore {
pub fn new(ai_probability: f32, pattern_score: f32, style_score: f32) -> Self {
let overall = 1.0 - (ai_probability * 0.5 + pattern_score * 0.3 + style_score * 0.2);
Self {
overall: overall.max(0.0).min(1.0),
ai_probability,
pattern_score,
style_score,
}
}
pub fn is_likely_ai(&self, threshold: f32) -> bool {
self.ai_probability > threshold
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DetectionResult {
pub score: StealthScore,
pub patterns: Vec<Pattern>,
pub sections: Vec<SectionResult>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Pattern {
pub pattern_type: PatternType,
pub confidence: f32,
pub location: Option<(usize, usize)>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PatternType {
Watermark,
ExcessiveEmojis,
UniformComments,
PredictableNaming,
LowEntropy,
Other(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SectionResult {
pub section_id: String,
pub score: StealthScore,
pub patterns: Vec<Pattern>,
}
pub trait Detector: Send + Sync {
fn detect(&self, code: &CodeBlock) -> anyhow::Result<DetectionResult>;
fn detect_batch(&self, codes: &[CodeBlock]) -> anyhow::Result<Vec<DetectionResult>> {
codes.iter().map(|c| self.detect(c)).collect()
}
}
pub trait Humanizer: Send + Sync {
fn humanize(&self, code: &CodeBlock, profile: &StyleProfile) -> anyhow::Result<String>;
fn learn_style(&self, repo_path: &PathBuf) -> anyhow::Result<StyleProfile>;
}
pub trait JitterEngine: Send + Sync {
fn apply_jitter(&self, operation: JitterOperation) -> anyhow::Result<JitterSchedule>;
fn current_schedule(&self) -> Option<JitterSchedule>;
}
#[derive(Debug, Clone)]
pub enum JitterOperation {
Stage { files: Vec<PathBuf> },
Commit { message: String },
Push,
}
#[derive(Debug, Clone)]
pub struct JitterSchedule {
pub execute_at: chrono::DateTime<chrono::Utc>,
pub operation: JitterOperation,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
pub detection: DetectionConfig,
pub humanization: HumanizationConfig,
pub jitter: JitterConfig,
pub api: ApiConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DetectionConfig {
pub threshold: f32,
pub use_local: bool,
pub use_cloud_fallback: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HumanizationConfig {
pub auto_humanize: bool,
pub entropy_level: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JitterConfig {
pub enabled: bool,
pub min_delay_secs: u64,
pub max_delay_secs: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiConfig {
pub openrouter_key: Option<String>,
pub anthropic_key: Option<String>,
pub base_url: Option<String>,
}
impl Default for Config {
fn default() -> Self {
Self {
detection: DetectionConfig {
threshold: 0.15,
use_local: true,
use_cloud_fallback: true,
},
humanization: HumanizationConfig {
auto_humanize: false,
entropy_level: 0.5,
},
jitter: JitterConfig {
enabled: false,
min_delay_secs: 60,
max_delay_secs: 300,
},
api: ApiConfig {
openrouter_key: None,
anthropic_key: None,
base_url: None,
},
}
}
}
impl Config {
pub fn load(path: &PathBuf) -> anyhow::Result<Self> {
let content = std::fs::read_to_string(path)?;
let config: Config = toml::from_str(&content)?;
Ok(config)
}
pub fn save(&self, path: &PathBuf) -> anyhow::Result<()> {
let content = toml::to_string_pretty(self)?;
std::fs::write(path, content)?;
Ok(())
}
pub fn load_or_default(path: &PathBuf) -> anyhow::Result<Self> {
if path.exists() {
Self::load(path)
} else {
let config = Self::default();
config.save(path)?;
Ok(config)
}
}
}