From 115774c023ec6421b4db4e95fba061e3b03d57c7 Mon Sep 17 00:00:00 2001 From: Harsha Vardhan <80092815+harshav167@users.noreply.github.com> Date: Mon, 12 Jan 2026 17:29:09 +1100 Subject: [PATCH 1/2] feat: Add OpenCode support for log discovery and parsing Add support for OpenCode AI coding assistant: - Add OpenCode variant to AiTool enum with path detection - Add XDG-compliant search patterns (~/.local/share/opencode, ~/.config/opencode, etc.) - Add subdirectory patterns for storage, log, snapshot, tool-output, prompt-history.jsonl - Create dedicated OpenCode parser for session/message/part JSON files OpenCode storage structure parsed: - storage/session/{projectID}/{sessionID}.json - Session metadata - storage/message/{sessionID}/{messageID}.json - Messages - storage/part/{messageID}/{partID}.json - Message parts - prompt-history.jsonl - Prompt history (JSONL format) --- src/discovery.rs | 15 ++ src/models.rs | 13 +- src/parsers/mod.rs | 1 + src/parsers/opencode.rs | 442 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 470 insertions(+), 1 deletion(-) create mode 100644 src/parsers/opencode.rs diff --git a/src/discovery.rs b/src/discovery.rs index db90e7d..911d3eb 100644 --- a/src/discovery.rs +++ b/src/discovery.rs @@ -33,6 +33,11 @@ impl LogDiscovery { // Known AI tool directories let search_patterns = vec![ + // OpenCode (XDG-compliant paths) + ".local/share/opencode", // Data: storage/, log/, snapshot/, tool-output/ + ".config/opencode", // Config: opencode.json + ".local/state/opencode", // State: prompt-history.jsonl, kv.json + ".cache/opencode", // Cache // Claude Code ".claude", "Library/Application Support/Claude", @@ -224,6 +229,16 @@ impl LogDiscovery { ("commandEmbeddings.json", LogType::Cache), // Copilot embeddings ("settingEmbeddings.json", LogType::Cache), // Copilot embeddings ("toolEmbeddingsCache.bin", LogType::Cache), // Copilot embeddings + // OpenCode-specific directories (XDG data dir: ~/.local/share/opencode/) + ("storage", LogType::Session), // Session/message/part JSON files + ("log", LogType::Debug), // Application logs + ("snapshot", LogType::FileHistory), // Snapshots + ("tool-output", LogType::Debug), // Tool outputs + // OpenCode state files (XDG state dir: ~/.local/state/opencode/) + ("prompt-history.jsonl", LogType::History), // Prompt history + ("kv.json", LogType::Cache), // Key-value store + ("model.json", LogType::Cache), // Model preferences + ("frecency.jsonl", LogType::Cache), // Frecency data ]; for (subdir_name, log_type) in subdirs { diff --git a/src/models.rs b/src/models.rs index 0c471d2..906e865 100644 --- a/src/models.rs +++ b/src/models.rs @@ -6,6 +6,7 @@ use std::path::{Path, PathBuf}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum AiTool { ClaudeCode, + OpenCode, Cline, Cursor, Kiro, @@ -30,7 +31,16 @@ impl AiTool { pub fn from_path(path: &Path) -> Option { let path_str = path.to_string_lossy().to_lowercase(); - if path_str.contains(".claude") { + // Check OpenCode first (XDG paths: ~/.local/share/opencode, ~/.config/opencode, etc.) + // Must check before .claude since some paths might overlap + // Match both /opencode/ and /opencode (end of path) + if path_str.contains("/opencode/") + || path_str.contains("\\opencode\\") + || path_str.ends_with("/opencode") + || path_str.ends_with("\\opencode") + { + Some(AiTool::OpenCode) + } else if path_str.contains(".claude") { Some(AiTool::ClaudeCode) } else if path_str.contains("cline") { Some(AiTool::Cline) @@ -74,6 +84,7 @@ impl AiTool { pub fn name(&self) -> &str { match self { AiTool::ClaudeCode => "Claude Code", + AiTool::OpenCode => "OpenCode", AiTool::Cline => "Cline", AiTool::Cursor => "Cursor", AiTool::Kiro => "Kiro", diff --git a/src/parsers/mod.rs b/src/parsers/mod.rs index ba93ff6..d7de2d5 100644 --- a/src/parsers/mod.rs +++ b/src/parsers/mod.rs @@ -4,6 +4,7 @@ pub mod claude; pub mod cline; pub mod cursor; pub mod generic; +pub mod opencode; use crate::models::*; use anyhow::Result; diff --git a/src/parsers/opencode.rs b/src/parsers/opencode.rs new file mode 100644 index 0000000..d763f7c --- /dev/null +++ b/src/parsers/opencode.rs @@ -0,0 +1,442 @@ +// OpenCode log parser +// OpenCode uses XDG-compliant paths: +// - Data: ~/.local/share/opencode/ (storage/, log/, snapshot/, tool-output/) +// - Config: ~/.config/opencode/ (opencode.json) +// - State: ~/.local/state/opencode/ (prompt-history.jsonl, kv.json) +// - Cache: ~/.cache/opencode/ + +use super::{EntryCategory, LogEntry, LogLevel, LogMetadata, LogParser, ParsedLog}; +use crate::models::AiTool; +use anyhow::Result; +use chrono::{DateTime, Utc}; +use serde_json::Value; +use std::fs; +use std::io::{BufRead, BufReader}; +use std::path::Path; +use walkdir::WalkDir; + +pub struct OpenCodeParser; + +impl LogParser for OpenCodeParser { + fn can_parse(&self, path: &Path) -> bool { + let path_str = path.to_string_lossy().to_lowercase(); + path_str.contains("/opencode/") || path_str.contains("\\opencode\\") + } + + fn parse(&self, path: &Path) -> Result { + let mut entries = Vec::new(); + let mut oldest: Option> = None; + let mut newest: Option> = None; + + // Handle different OpenCode file types + if path.is_file() { + let filename = path.file_name().and_then(|n| n.to_str()).unwrap_or(""); + + match filename { + // Prompt history (JSONL format) + "prompt-history.jsonl" => { + parse_jsonl_file(path, &mut entries, &mut oldest, &mut newest)?; + } + // Session/message/part JSON files + _ if filename.ends_with(".json") => { + if let Ok(content) = fs::read_to_string(path) { + if let Ok(json) = serde_json::from_str::(&content) { + if let Some(entry) = parse_opencode_json(&json) { + if let Some(ts) = entry.timestamp { + oldest = Some(oldest.map_or(ts, |o| o.min(ts))); + newest = Some(newest.map_or(ts, |n| n.max(ts))); + } + entries.push(entry); + } + } + } + } + _ => {} + } + } else if path.is_dir() { + // Scan directory for session/message/part files + let path_str = path.to_string_lossy(); + + if path_str.contains("storage") { + // Parse storage directory structure + parse_storage_directory(path, &mut entries, &mut oldest, &mut newest)?; + } else if path_str.contains("log") { + // Parse log directory + parse_log_directory(path, &mut entries, &mut oldest, &mut newest)?; + } + } + + let file_size = if path.is_file() { + fs::metadata(path).map(|m| m.len()).unwrap_or(0) + } else { + calculate_dir_size(path) + }; + + let entry_count = entries.len(); + + Ok(ParsedLog { + tool: AiTool::OpenCode, + entries, + metadata: LogMetadata { + file_size, + entry_count, + date_range: (oldest, newest), + }, + }) + } +} + +fn parse_jsonl_file( + path: &Path, + entries: &mut Vec, + oldest: &mut Option>, + newest: &mut Option>, +) -> Result<()> { + let file = fs::File::open(path)?; + let reader = BufReader::new(file); + + for line in reader.lines() { + let line = line?; + if line.trim().is_empty() { + continue; + } + + if let Ok(json) = serde_json::from_str::(&line) { + if let Some(entry) = parse_prompt_history_entry(&json) { + if let Some(ts) = entry.timestamp { + *oldest = Some(oldest.map_or(ts, |o| o.min(ts))); + *newest = Some(newest.map_or(ts, |n| n.max(ts))); + } + entries.push(entry); + } + } + } + + Ok(()) +} + +fn parse_prompt_history_entry(json: &Value) -> Option { + // OpenCode prompt history format: {"prompt": "...", "timestamp": "..."} + let timestamp = json + .get("timestamp") + .or_else(|| json.get("time")) + .or_else(|| json.get("ts")) + .and_then(|v| { + if let Some(s) = v.as_str() { + DateTime::parse_from_rfc3339(s) + .ok() + .map(|dt| dt.with_timezone(&Utc)) + } else if let Some(ms) = v.as_i64() { + DateTime::from_timestamp_millis(ms).map(|dt| dt.with_timezone(&Utc)) + } else { + None + } + }); + + let message = json + .get("prompt") + .or_else(|| json.get("text")) + .or_else(|| json.get("content")) + .and_then(|v| v.as_str()) + .unwrap_or("") + .to_string(); + + Some(LogEntry { + timestamp, + level: LogLevel::Info, + message, + category: EntryCategory::UserPrompt, + }) +} + +fn parse_opencode_json(json: &Value) -> Option { + // Parse OpenCode session/message/part JSON files + // Session format: {id, projectID, time: {created, initialized}, summary: {additions, deletions}} + // Message format: {id, role, content, timestamp, ...} + // Part format: {id, type, content, ...} + + let timestamp = json + .get("timestamp") + .or_else(|| json.get("time").and_then(|t| t.get("created"))) + .and_then(|v| { + if let Some(s) = v.as_str() { + DateTime::parse_from_rfc3339(s) + .ok() + .map(|dt| dt.with_timezone(&Utc)) + } else if let Some(ms) = v.as_i64() { + DateTime::from_timestamp_millis(ms).map(|dt| dt.with_timezone(&Utc)) + } else { + None + } + }); + + // Determine category based on role or type + let role = json.get("role").and_then(|v| v.as_str()).unwrap_or(""); + let msg_type = json.get("type").and_then(|v| v.as_str()).unwrap_or(""); + + let category = if role == "user" || msg_type == "user" { + EntryCategory::UserPrompt + } else if role == "assistant" || msg_type == "assistant" { + EntryCategory::AssistantResponse + } else if msg_type == "tool" || json.get("tool_calls").is_some() { + EntryCategory::ToolUse + } else if json.get("error").is_some() { + EntryCategory::Error + } else { + EntryCategory::SystemEvent + }; + + let message = json + .get("content") + .or_else(|| json.get("message")) + .or_else(|| json.get("text")) + .and_then(|v| v.as_str()) + .unwrap_or("") + .to_string(); + + let level = if json.get("error").is_some() { + LogLevel::Error + } else { + LogLevel::Info + }; + + Some(LogEntry { + timestamp, + level, + message, + category, + }) +} + +fn parse_storage_directory( + path: &Path, + entries: &mut Vec, + oldest: &mut Option>, + newest: &mut Option>, +) -> Result<()> { + // OpenCode storage structure: + // storage/ + // session/{projectID}/{sessionID}.json + // message/{sessionID}/{messageID}.json + // part/{messageID}/{partID}.json + // project/{projectID}.json + // todo/*.json + + for entry in WalkDir::new(path) + .follow_links(false) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|e| e.file_type().is_file()) + { + let file_path = entry.path(); + if file_path.extension().and_then(|e| e.to_str()) == Some("json") { + if let Ok(content) = fs::read_to_string(file_path) { + if let Ok(json) = serde_json::from_str::(&content) { + if let Some(log_entry) = parse_opencode_json(&json) { + if let Some(ts) = log_entry.timestamp { + *oldest = Some(oldest.map_or(ts, |o| o.min(ts))); + *newest = Some(newest.map_or(ts, |n| n.max(ts))); + } + entries.push(log_entry); + } + } + } + } + } + + Ok(()) +} + +fn parse_log_directory( + path: &Path, + entries: &mut Vec, + oldest: &mut Option>, + newest: &mut Option>, +) -> Result<()> { + // Parse log files in the log directory + for entry in WalkDir::new(path) + .follow_links(false) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|e| e.file_type().is_file()) + { + let file_path = entry.path(); + if let Some(ext) = file_path.extension().and_then(|e| e.to_str()) { + if ext == "log" || ext == "jsonl" { + if let Ok(file) = fs::File::open(file_path) { + let reader = BufReader::new(file); + for line in reader.lines().take(1000) { + // Limit to first 1000 lines + if let Ok(line) = line { + if line.trim().is_empty() { + continue; + } + + // Try to parse as JSON first + if let Ok(json) = serde_json::from_str::(&line) { + if let Some(log_entry) = parse_log_json_entry(&json) { + if let Some(ts) = log_entry.timestamp { + *oldest = Some(oldest.map_or(ts, |o| o.min(ts))); + *newest = Some(newest.map_or(ts, |n| n.max(ts))); + } + entries.push(log_entry); + } + } + } + } + } + } + } + } + + Ok(()) +} + +fn parse_log_json_entry(json: &Value) -> Option { + let timestamp = json + .get("time") + .or_else(|| json.get("timestamp")) + .or_else(|| json.get("ts")) + .and_then(|v| { + if let Some(s) = v.as_str() { + DateTime::parse_from_rfc3339(s) + .ok() + .map(|dt| dt.with_timezone(&Utc)) + } else if let Some(ms) = v.as_i64() { + DateTime::from_timestamp_millis(ms).map(|dt| dt.with_timezone(&Utc)) + } else { + None + } + }); + + let level_str = json + .get("level") + .or_else(|| json.get("severity")) + .and_then(|v| v.as_str()) + .unwrap_or("info") + .to_lowercase(); + + let level = match level_str.as_str() { + "error" | "err" | "fatal" => LogLevel::Error, + "warn" | "warning" => LogLevel::Warn, + "debug" | "trace" => LogLevel::Debug, + _ => LogLevel::Info, + }; + + let message = json + .get("msg") + .or_else(|| json.get("message")) + .or_else(|| json.get("text")) + .and_then(|v| v.as_str()) + .unwrap_or("") + .to_string(); + + let category = if message.to_lowercase().contains("error") { + EntryCategory::Error + } else { + EntryCategory::SystemEvent + }; + + Some(LogEntry { + timestamp, + level, + message, + category, + }) +} + +fn calculate_dir_size(path: &Path) -> u64 { + WalkDir::new(path) + .follow_links(false) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|e| e.file_type().is_file()) + .filter_map(|e| e.metadata().ok()) + .map(|m| m.len()) + .sum() +} + +/// Analyze OpenCode logs and return statistics +pub fn analyze_opencode_logs(opencode_dir: &Path) -> Result { + let mut analysis = OpenCodeAnalysis::default(); + + // Check storage directory + let storage_dir = opencode_dir.join("storage"); + if storage_dir.exists() { + // Count sessions + let session_dir = storage_dir.join("session"); + if session_dir.exists() { + for project_entry in fs::read_dir(&session_dir)?.filter_map(|e| e.ok()) { + if project_entry.path().is_dir() { + analysis.project_count += 1; + analysis.session_count += fs::read_dir(project_entry.path())? + .filter_map(|e| e.ok()) + .count(); + } + } + } + + // Count messages + let message_dir = storage_dir.join("message"); + if message_dir.exists() { + for session_entry in fs::read_dir(&message_dir)?.filter_map(|e| e.ok()) { + if session_entry.path().is_dir() { + analysis.message_count += fs::read_dir(session_entry.path())? + .filter_map(|e| e.ok()) + .count(); + } + } + } + + // Count parts (message parts) + let part_dir = storage_dir.join("part"); + if part_dir.exists() { + for msg_entry in fs::read_dir(&part_dir)?.filter_map(|e| e.ok()) { + if msg_entry.path().is_dir() { + analysis.part_count += fs::read_dir(msg_entry.path())? + .filter_map(|e| e.ok()) + .count(); + } + } + } + + // Count todos + let todo_dir = storage_dir.join("todo"); + if todo_dir.exists() { + analysis.todo_count = fs::read_dir(&todo_dir)?.filter_map(|e| e.ok()).count(); + } + } + + // Check log directory + let log_dir = opencode_dir.join("log"); + if log_dir.exists() { + analysis.log_file_count = fs::read_dir(&log_dir)?.filter_map(|e| e.ok()).count(); + } + + // Check snapshot directory + let snapshot_dir = opencode_dir.join("snapshot"); + if snapshot_dir.exists() { + analysis.snapshot_count = fs::read_dir(&snapshot_dir)?.filter_map(|e| e.ok()).count(); + } + + // Estimate tokens from message files + let parser = OpenCodeParser; + if let Ok(parsed) = parser.parse(&storage_dir) { + let total_chars: usize = parsed.entries.iter().map(|e| e.message.len()).sum(); + analysis.estimated_tokens = (total_chars / 4) as u64; // 1 token ≈ 4 chars + } + + Ok(analysis) +} + +#[derive(Debug, Clone, Default)] +pub struct OpenCodeAnalysis { + pub project_count: usize, + pub session_count: usize, + pub message_count: usize, + pub part_count: usize, + pub todo_count: usize, + pub log_file_count: usize, + pub snapshot_count: usize, + pub estimated_tokens: u64, +} From b3217f0e7e4e015001fe7bc5643ccb68b14dac4d Mon Sep 17 00:00:00 2001 From: Harsha Vardhan <80092815+harshav167@users.noreply.github.com> Date: Mon, 12 Jan 2026 17:31:22 +1100 Subject: [PATCH 2/2] style: Apply rustfmt formatting across codebase Run cargo fmt to enforce consistent code style: - Alphabetize imports - Wrap long lines (100 char limit) - Format array elements - Format method chains --- src/ascii_charts.rs | 517 +++++++--- src/behavior_analyzer.rs | 1731 ++++++++++++++++++++++------------ src/deobfuscator.rs | 78 +- src/main.rs | 227 +++-- src/proxy.rs | 42 +- src/search/index_builder.rs | 31 +- src/search/metadata.rs | 14 +- src/search/mod.rs | 12 +- src/search/query_executor.rs | 25 +- src/sourcemap_unbundler.rs | 20 +- src/structure_recovery.rs | 540 +++++++---- src/traffic.rs | 5 +- src/tui_traffic.rs | 108 ++- src/unbundler_cli.rs | 56 +- 14 files changed, 2296 insertions(+), 1110 deletions(-) diff --git a/src/ascii_charts.rs b/src/ascii_charts.rs index 58be35f..4ab9b05 100644 --- a/src/ascii_charts.rs +++ b/src/ascii_charts.rs @@ -205,7 +205,8 @@ impl LineChart { if let Some(py) = prev_y { if i > 0 { let prev_point = &series.data[i - 1]; - let prev_time_offset = (prev_point.timestamp - min_time).num_seconds() as f64; + let prev_time_offset = + (prev_point.timestamp - min_time).num_seconds() as f64; let prev_x = ((prev_time_offset / time_range) * (chart_width - 1) as f64) as usize; let prev_x = prev_x.min(chart_width - 1); @@ -266,11 +267,7 @@ impl LineChart { } // X-axis - output.push_str(&format!( - "{:>width$} └", - "", - width = self.y_label_width - )); + output.push_str(&format!("{:>width$} └", "", width = self.y_label_width)); output.push_str(&"─".repeat(chart_width)); output.push('\n'); @@ -627,7 +624,8 @@ impl StatsPanel { } pub fn add(&mut self, label: &str, value: &str) { - self.metrics.push((label.to_string(), value.to_string(), None)); + self.metrics + .push((label.to_string(), value.to_string(), None)); } pub fn add_with_trend(&mut self, label: &str, value: &str, trend: &str) { @@ -863,7 +861,10 @@ impl ActivityHeatmap { // Month headers output.push_str(" "); - let months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan"]; + let months = [ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + "Jan", + ]; let weeks_per_month = self.weeks / 12; for month in &months[..13] { output.push_str(month); @@ -1013,11 +1014,20 @@ impl FunFact { } let fact = if multiplier >= 100.0 { - format!("You've used ~{}x more tokens than {}", multiplier as u64, best_match.0) + format!( + "You've used ~{}x more tokens than {}", + multiplier as u64, best_match.0 + ) } else if multiplier >= 10.0 { - format!("You've used ~{:.0}x more tokens than {}", multiplier, best_match.0) + format!( + "You've used ~{:.0}x more tokens than {}", + multiplier, best_match.0 + ) } else if multiplier >= 1.0 { - format!("You've used ~{:.1}x the tokens of {}", multiplier, best_match.0) + format!( + "You've used ~{:.1}x the tokens of {}", + multiplier, best_match.0 + ) } else { format!("You've used {} tokens (keep going!)", format_tokens(tokens)) }; @@ -1263,11 +1273,7 @@ impl ProgressBar { } else { format!( " {} {} {} {}/{}\n", - status, - self.label, - colored_bar, - self.current as u64, - self.target as u64 + status, self.label, colored_bar, self.current as u64, self.target as u64 ) } } @@ -1375,8 +1381,19 @@ impl CalendarView { let mut output = String::new(); let month_names = [ - "", "January", "February", "March", "April", "May", "June", - "July", "August", "September", "October", "November", "December", + "", + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", ]; output.push_str(&format!( @@ -1699,7 +1716,8 @@ impl Gauge { let pct = ((self.value - self.min) / (self.max - self.min)).clamp(0.0, 1.0); // Determine color based on thresholds - let color = self.thresholds + let color = self + .thresholds .iter() .find(|(t, _)| pct <= *t) .map(|(_, c)| *c) @@ -1743,7 +1761,7 @@ impl Gauge { pub struct DonutChart { pub title: String, pub segments: Vec<(String, f64, String)>, // (label, value, color) - pub size: usize, // 1=small, 2=medium, 3=large + pub size: usize, // 1=small, 2=medium, 3=large } impl DonutChart { @@ -1756,7 +1774,8 @@ impl DonutChart { } pub fn add(&mut self, label: &str, value: f64, color: &str) { - self.segments.push((label.to_string(), value, color.to_string())); + self.segments + .push((label.to_string(), value, color.to_string())); } pub fn render(&self) -> String { @@ -1789,7 +1808,11 @@ impl DonutChart { // Legend with percentages for (label, value, color) in &self.segments { - let pct = if total > 0.0 { value / total * 100.0 } else { 0.0 }; + let pct = if total > 0.0 { + value / total * 100.0 + } else { + 0.0 + }; let bullet = colorize_bullet(color); output.push_str(&format!( " {} {} ({:.1}%) - {}\n", @@ -1819,11 +1842,7 @@ impl BulletChart { label: label.to_string(), value, target, - ranges: vec![ - (0.33, "red"), - (0.66, "yellow"), - (1.0, "green"), - ], + ranges: vec![(0.33, "red"), (0.66, "yellow"), (1.0, "green")], width: 40, } } @@ -1841,7 +1860,8 @@ impl BulletChart { // Draw background ranges for i in 0..self.width { let pos_pct = i as f64 / self.width as f64; - let range_color = self.ranges + let range_color = self + .ranges .iter() .find(|(t, _)| pos_pct <= *t) .map(|(_, c)| *c) @@ -2087,7 +2107,8 @@ impl WaterfallChart { return output; } - let max_abs = self.items + let max_abs = self + .items .iter() .map(|(_, v, _)| v.abs()) .fold(0.0f64, f64::max); @@ -2224,7 +2245,8 @@ impl MatrixHeatmap { return output; } - let max_val = self.data + let max_val = self + .data .iter() .flat_map(|row| row.iter()) .cloned() @@ -2241,8 +2263,16 @@ impl MatrixHeatmap { // Data rows for (row_idx, row) in self.data.iter().enumerate() { - let row_label = self.row_labels.get(row_idx).map(|s| s.as_str()).unwrap_or(""); - output.push_str(&format!(" {:>width$} ", row_label, width = row_label_width)); + let row_label = self + .row_labels + .get(row_idx) + .map(|s| s.as_str()) + .unwrap_or(""); + output.push_str(&format!( + " {:>width$} ", + row_label, + width = row_label_width + )); for val in row { let intensity = if max_val > 0.0 { val / max_val } else { 0.0 }; @@ -2302,7 +2332,8 @@ impl GanttChart { } pub fn add(&mut self, name: &str, start: u32, duration: u32, color: &str) { - self.tasks.push((name.to_string(), start, duration, color.to_string())); + self.tasks + .push((name.to_string(), start, duration, color.to_string())); } pub fn render(&self) -> String { @@ -2314,7 +2345,12 @@ impl GanttChart { return output; } - let max_name_len = self.tasks.iter().map(|(n, _, _, _)| n.len()).max().unwrap_or(10); + let max_name_len = self + .tasks + .iter() + .map(|(n, _, _, _)| n.len()) + .max() + .unwrap_or(10); let chart_width = 40; let scale = chart_width as f64 / self.total_units as f64; @@ -2368,7 +2404,8 @@ impl MiniDashboard { } pub fn add_metric(&mut self, label: &str, value: &str, change: Option) { - self.metrics.push((label.to_string(), value.to_string(), change)); + self.metrics + .push((label.to_string(), value.to_string(), change)); } pub fn add_sparkline(&mut self, label: &str, data: Vec) { @@ -2495,7 +2532,9 @@ impl AsciiBanner { ('5', ["█████", "█ ", "████ ", " █", "████ "]), ('!', [" █ ", " █ ", " █ ", " ", " █ "]), (' ', [" ", " ", " ", " ", " "]), - ].into_iter().collect(); + ] + .into_iter() + .collect(); for row in 0..5 { output.push_str(" "); @@ -2552,23 +2591,39 @@ impl MetricCard { let width = 24; output.push_str(&format!(" ┌{}┐\n", "─".repeat(width))); - output.push_str(&format!(" │ {:width$}│\n", self.label.dimmed(), width = width - 1)); + output.push_str(&format!( + " │ {:width$}│\n", + self.label.dimmed(), + width = width - 1 + )); let value_str = colorize_text(&self.value, &self.color).bold().to_string(); - let trend_str = self.trend.map(|t| { - if t > 0.0 { - format!(" {}", format!("↑{:.0}%", t).green()) - } else if t < 0.0 { - format!(" {}", format!("↓{:.0}%", t.abs()).red()) - } else { - String::new() - } - }).unwrap_or_default(); + let trend_str = self + .trend + .map(|t| { + if t > 0.0 { + format!(" {}", format!("↑{:.0}%", t).green()) + } else if t < 0.0 { + format!(" {}", format!("↓{:.0}%", t.abs()).red()) + } else { + String::new() + } + }) + .unwrap_or_default(); - output.push_str(&format!(" │ {}{:>width$}│\n", value_str, trend_str, width = width - self.value.len() - trend_str.len() - 1)); + output.push_str(&format!( + " │ {}{:>width$}│\n", + value_str, + trend_str, + width = width - self.value.len() - trend_str.len() - 1 + )); if let Some(ref sub) = self.subtitle { - output.push_str(&format!(" │ {:width$}│\n", sub.dimmed(), width = width - 1)); + output.push_str(&format!( + " │ {:width$}│\n", + sub.dimmed(), + width = width - 1 + )); } output.push_str(&format!(" └{}┘\n", "─".repeat(width))); @@ -2585,10 +2640,10 @@ impl MetricCard { /// Shows frustration vs flow state based on retry counts, error frequency, etc. pub struct MoodRing { pub title: String, - pub frustration_score: f64, // 0-100 - pub flow_score: f64, // 0-100 - pub energy_score: f64, // 0-100 - pub focus_score: f64, // 0-100 + pub frustration_score: f64, // 0-100 + pub flow_score: f64, // 0-100 + pub energy_score: f64, // 0-100 + pub focus_score: f64, // 0-100 } impl MoodRing { @@ -2621,7 +2676,10 @@ impl MoodRing { (self.frustration_score, "Frustration", "red", "Struggling"), ]; - let dominant = moods.iter().max_by(|a, b| a.0.partial_cmp(&b.0).unwrap()).unwrap(); + let dominant = moods + .iter() + .max_by(|a, b| a.0.partial_cmp(&b.0).unwrap()) + .unwrap(); // ASCII mood ring let ring_color = match dominant.2 { @@ -2635,12 +2693,21 @@ impl MoodRing { // Draw concentric ring visualization output.push_str(" ╭─────────────╮\n"); output.push_str(" ╭─┘ └─╮\n"); - output.push_str(&format!(" │ {} {} │\n", ring_color, ring_color)); + output.push_str(&format!( + " │ {} {} │\n", + ring_color, ring_color + )); output.push_str(&format!(" │ │\n")); output.push_str(&format!(" │ {:^13} │\n", dominant.1.bold())); - output.push_str(&format!(" │ {:^13} │\n", format!("{:.0}%", dominant.0))); + output.push_str(&format!( + " │ {:^13} │\n", + format!("{:.0}%", dominant.0) + )); output.push_str(&format!(" │ │\n")); - output.push_str(&format!(" │ {} {} │\n", ring_color, ring_color)); + output.push_str(&format!( + " │ {} {} │\n", + ring_color, ring_color + )); output.push_str(" ╰─╮ ╭─╯\n"); output.push_str(" ╰─────────────╯\n\n"); @@ -2648,22 +2715,42 @@ impl MoodRing { output.push_str(&format!(" {} ", "Flow".green())); let flow_bar = "█".repeat((self.flow_score / 5.0) as usize); let flow_empty = "░".repeat(20 - (self.flow_score / 5.0) as usize); - output.push_str(&format!("{}{} {:.0}%\n", flow_bar.green(), flow_empty, self.flow_score)); + output.push_str(&format!( + "{}{} {:.0}%\n", + flow_bar.green(), + flow_empty, + self.flow_score + )); output.push_str(&format!(" {} ", "Focus".cyan())); let focus_bar = "█".repeat((self.focus_score / 5.0) as usize); let focus_empty = "░".repeat(20 - (self.focus_score / 5.0) as usize); - output.push_str(&format!("{}{} {:.0}%\n", focus_bar.cyan(), focus_empty, self.focus_score)); + output.push_str(&format!( + "{}{} {:.0}%\n", + focus_bar.cyan(), + focus_empty, + self.focus_score + )); output.push_str(&format!(" {} ", "Energy".yellow())); let energy_bar = "█".repeat((self.energy_score / 5.0) as usize); let energy_empty = "░".repeat(20 - (self.energy_score / 5.0) as usize); - output.push_str(&format!("{}{} {:.0}%\n", energy_bar.yellow(), energy_empty, self.energy_score)); + output.push_str(&format!( + "{}{} {:.0}%\n", + energy_bar.yellow(), + energy_empty, + self.energy_score + )); output.push_str(&format!(" {} ", "Stress".red())); let frust_bar = "█".repeat((self.frustration_score / 5.0) as usize); let frust_empty = "░".repeat(20 - (self.frustration_score / 5.0) as usize); - output.push_str(&format!("{}{} {:.0}%\n", frust_bar.red(), frust_empty, self.frustration_score)); + output.push_str(&format!( + "{}{} {:.0}%\n", + frust_bar.red(), + frust_empty, + self.frustration_score + )); output.push_str(&format!("\n 💭 {}\n", dominant.3)); @@ -2690,7 +2777,8 @@ impl CodePulse { pub fn set_data(&mut self, data: Vec) { // Calculate heart rate from peaks - let peaks = data.windows(3) + let peaks = data + .windows(3) .filter(|w| w[1] > w[0] && w[1] > w[2]) .count(); self.heart_rate = (peaks as f64 * 10.0) as u32; @@ -2732,9 +2820,19 @@ impl CodePulse { // Create ECG spike pattern let char = if (normalized - row_pos).abs() < 0.1 { - if normalized > 0.7 { "╱" } else if normalized > 0.3 { "─" } else { "╲" } + if normalized > 0.7 { + "╱" + } else if normalized > 0.3 { + "─" + } else { + "╲" + } } else if val >= threshold { - if row == height - 1 { "▄" } else { " " } + if row == height - 1 { + "▄" + } else { + " " + } } else { " " }; @@ -2795,7 +2893,8 @@ impl TreeMap { } pub fn add(&mut self, name: &str, value: f64, color: &str) { - self.items.push((name.to_string(), value, color.to_string())); + self.items + .push((name.to_string(), value, color.to_string())); } pub fn render(&self) -> String { @@ -2826,7 +2925,9 @@ impl TreeMap { for (name, value, color) in &sorted { let cells = ((value / total) * total_cells as f64) as usize; - if cells == 0 { continue; } + if cells == 0 { + continue; + } let (rect_w, rect_h) = if horizontal { let w = remaining_width; @@ -2872,7 +2973,13 @@ impl TreeMap { for (name, value, color) in &sorted { let pct = value / total * 100.0; let marker = colorize_text("██", color); - output.push_str(&format!(" {} {} ({:.1}%) - {}\n", marker, name, pct, format_number(*value))); + output.push_str(&format!( + " {} {} ({:.1}%) - {}\n", + marker, + name, + pct, + format_number(*value) + )); } output @@ -2917,13 +3024,16 @@ impl SankeyFlow { targets.dedup(); let max_flow: f64 = self.flows.iter().map(|(_, _, a)| *a).fold(0.0, f64::max); - let max_name = sources.iter().chain(targets.iter()).map(|s| s.len()).max().unwrap_or(10); + let max_name = sources + .iter() + .chain(targets.iter()) + .map(|s| s.len()) + .max() + .unwrap_or(10); // Draw flows for source in &sources { - let source_flows: Vec<_> = self.flows.iter() - .filter(|(s, _, _)| s == *source) - .collect(); + let source_flows: Vec<_> = self.flows.iter().filter(|(s, _, _)| s == *source).collect(); let total: f64 = source_flows.iter().map(|(_, _, a)| *a).sum(); let bar_width = ((total / max_flow) * 20.0) as usize; @@ -2935,7 +3045,8 @@ impl SankeyFlow { for (i, (_, target, amount)) in source_flows.iter().enumerate() { let flow_width = ((*amount / max_flow) * 15.0) as usize; let flow_char = if i == 0 { "┬" } else { "├" }; - output.push_str(&format!("{}{}─▶ {} ({})\n", + output.push_str(&format!( + "{}{}─▶ {} ({})\n", flow_char, "─".repeat(flow_width), target.green(), @@ -2994,7 +3105,9 @@ impl AsciiWordCloud { for (i, (word, freq)) in sorted.iter().take(30).enumerate() { let scale = (*freq as f64 / max_freq).sqrt(); let styled_word = if scale > 0.8 { - colorize_text(&word.to_uppercase(), colors[i % colors.len()]).bold().to_string() + colorize_text(&word.to_uppercase(), colors[i % colors.len()]) + .bold() + .to_string() } else if scale > 0.5 { colorize_text(word, colors[i % colors.len()]).to_string() } else { @@ -3063,7 +3176,9 @@ impl BubbleMatrix { return output; } - let max_val = self.values.iter() + let max_val = self + .values + .iter() .flat_map(|row| row.iter()) .cloned() .fold(0.0_f64, f64::max) @@ -3083,7 +3198,11 @@ impl BubbleMatrix { for (row_idx, row_label) in self.row_labels.iter().enumerate() { output.push_str(&format!("{:>width$} ", row_label, width = max_row_label)); for col_idx in 0..self.col_labels.len() { - let val = self.values.get(row_idx).and_then(|r| r.get(col_idx)).unwrap_or(&0.0); + let val = self + .values + .get(row_idx) + .and_then(|r| r.get(col_idx)) + .unwrap_or(&0.0); let normalized = val / max_val; let bubble_idx = ((normalized * 5.0) as usize).min(5); let bubble = bubbles[bubble_idx]; @@ -3105,7 +3224,11 @@ impl BubbleMatrix { } // Legend - output.push_str(&format!("\n Size: {} min {} max\n", "·".dimmed(), "◉".red())); + output.push_str(&format!( + "\n Size: {} min {} max\n", + "·".dimmed(), + "◉".red() + )); output } @@ -3127,7 +3250,8 @@ impl PolarArea { } pub fn add_segment(&mut self, label: &str, value: f64, color: &str) { - self.segments.push((label.to_string(), value, color.to_string())); + self.segments + .push((label.to_string(), value, color.to_string())); } pub fn render(&self) -> String { @@ -3139,7 +3263,12 @@ impl PolarArea { return output; } - let max_val = self.segments.iter().map(|(_, v, _)| *v).fold(0.0_f64, f64::max).max(1.0); + let max_val = self + .segments + .iter() + .map(|(_, v, _)| *v) + .fold(0.0_f64, f64::max) + .max(1.0); let radius = 8; // ASCII polar plot with concentric circles @@ -3253,8 +3382,16 @@ impl TimelineStory { _ => "📌", }; - let connector = if i == self.events.len() - 1 { "└" } else { "├" }; - let line = if i == self.events.len() - 1 { " " } else { "│" }; + let connector = if i == self.events.len() - 1 { + "└" + } else { + "├" + }; + let line = if i == self.events.len() - 1 { + " " + } else { + "│" + }; output.push_str(&format!(" {} {} {}\n", date.dimmed(), icon, title.bold())); output.push_str(&format!(" {}──┤\n", connector)); @@ -3267,7 +3404,9 @@ impl TimelineStory { output.push_str(&format!(" {} {}\n", line, current_line)); current_line = word.to_string(); } else { - if !current_line.is_empty() { current_line.push(' '); } + if !current_line.is_empty() { + current_line.push(' '); + } current_line.push_str(word); } } @@ -3310,7 +3449,9 @@ impl HexGrid { let mut output = String::new(); output.push_str(&format!(" {}\n\n", self.title.bold())); - let max_val = self.data.iter() + let max_val = self + .data + .iter() .flat_map(|row| row.iter()) .cloned() .fold(0.0_f64, f64::max) @@ -3328,7 +3469,11 @@ impl HexGrid { for &val in row { let intensity = val / max_val; - let hex = if intensity > 0.1 { hex_chars[1] } else { hex_chars[0] }; + let hex = if intensity > 0.1 { + hex_chars[1] + } else { + hex_chars[0] + }; let colored = if intensity > 0.8 { hex.red() @@ -3348,7 +3493,11 @@ impl HexGrid { output.push('\n'); } - output.push_str(&format!("\n Intensity: {} low {} high\n", "⬡".dimmed(), "⬢".red())); + output.push_str(&format!( + "\n Intensity: {} low {} high\n", + "⬡".dimmed(), + "⬢".red() + )); output } @@ -3370,7 +3519,8 @@ impl FlowState { } pub fn add_period(&mut self, time_label: &str, duration_mins: f64, was_flow: bool) { - self.periods.push((time_label.to_string(), duration_mins, was_flow)); + self.periods + .push((time_label.to_string(), duration_mins, was_flow)); } pub fn render(&self) -> String { @@ -3383,7 +3533,9 @@ impl FlowState { } let total_time: f64 = self.periods.iter().map(|(_, d, _)| d).sum(); - let flow_time: f64 = self.periods.iter() + let flow_time: f64 = self + .periods + .iter() .filter(|(_, _, f)| *f) .map(|(_, d, _)| d) .sum(); @@ -3415,19 +3567,31 @@ impl FlowState { "Fragmented - consider time blocking".red() }; - output.push_str(&format!(" {} Flow time: {:.0} mins ({:.1}%)\n", "█".green(), flow_time, flow_pct)); - output.push_str(&format!(" {} Interrupted: {:.0} mins ({:.1}%)\n", "░".red(), total_time - flow_time, 100.0 - flow_pct)); + output.push_str(&format!( + " {} Flow time: {:.0} mins ({:.1}%)\n", + "█".green(), + flow_time, + flow_pct + )); + output.push_str(&format!( + " {} Interrupted: {:.0} mins ({:.1}%)\n", + "░".red(), + total_time - flow_time, + 100.0 - flow_pct + )); output.push_str(&format!("\n 🎯 {}\n", flow_score)); // Flow periods breakdown output.push_str("\n Flow Sessions:\n"); - let flow_sessions: Vec<_> = self.periods.iter() - .filter(|(_, _, f)| *f) - .collect(); + let flow_sessions: Vec<_> = self.periods.iter().filter(|(_, _, f)| *f).collect(); let mut longest = 0.0_f64; for (time, duration, _) in flow_sessions.iter().take(5) { - output.push_str(&format!(" {} - {:.0} mins of focus\n", time.cyan(), duration)); + output.push_str(&format!( + " {} - {:.0} mins of focus\n", + time.cyan(), + duration + )); longest = longest.max(*duration); } @@ -3459,7 +3623,12 @@ impl AchievementBadges { } pub fn add_badge(&mut self, name: &str, icon: &str, unlocked: bool, progress: f64) { - self.badges.push((name.to_string(), icon.to_string(), unlocked, progress.clamp(0.0, 100.0))); + self.badges.push(( + name.to_string(), + icon.to_string(), + unlocked, + progress.clamp(0.0, 100.0), + )); } pub fn render(&self) -> String { @@ -3467,19 +3636,26 @@ impl AchievementBadges { output.push_str(&format!(" {}\n\n", self.title.bold())); let unlocked_count = self.badges.iter().filter(|(_, _, u, _)| *u).count(); - output.push_str(&format!(" 🏆 {} / {} Unlocked\n\n", unlocked_count, self.badges.len())); + output.push_str(&format!( + " 🏆 {} / {} Unlocked\n\n", + unlocked_count, + self.badges.len() + )); for (name, icon, unlocked, progress) in &self.badges { if *unlocked { - output.push_str(&format!(" {} {} {}\n", icon, name.green().bold(), "✓".green())); + output.push_str(&format!( + " {} {} {}\n", + icon, + name.green().bold(), + "✓".green() + )); } else { let bar_width = 10; let filled = (progress / 100.0 * bar_width as f64) as usize; - let bar = format!("{}{}", - "█".repeat(filled), - "░".repeat(bar_width - filled) - ); - output.push_str(&format!(" {} {} {} {:.0}%\n", + let bar = format!("{}{}", "█".repeat(filled), "░".repeat(bar_width - filled)); + output.push_str(&format!( + " {} {} {} {:.0}%\n", icon.dimmed(), name.dimmed(), bar.dimmed(), @@ -3510,7 +3686,7 @@ impl SkillTree { pub fn add_branch(&mut self, name: &str, skills: Vec<(&str, u8)>) { self.branches.push(( name.to_string(), - skills.iter().map(|(s, l)| (s.to_string(), *l)).collect() + skills.iter().map(|(s, l)| (s.to_string(), *l)).collect(), )); } @@ -3521,7 +3697,10 @@ impl SkillTree { let level_chars = ["○", "◔", "◑", "◕", "●", "★"]; for (branch_name, skills) in &self.branches { - output.push_str(&format!(" ┌─ {} ─────────────────────\n", branch_name.cyan().bold())); + output.push_str(&format!( + " ┌─ {} ─────────────────────\n", + branch_name.cyan().bold() + )); for (i, (skill, level)) in skills.iter().enumerate() { let connector = if i == skills.len() - 1 { "└" } else { "├" }; @@ -3537,17 +3716,16 @@ impl SkillTree { _ => level_char.dimmed().to_string(), }; - output.push_str(&format!(" {}── {} {} (Lv.{})\n", - connector, - colored_char, - skill, - level + output.push_str(&format!( + " {}── {} {} (Lv.{})\n", + connector, colored_char, skill, level )); } output.push('\n'); } - output.push_str(&format!(" Legend: {} None {} Beginner {} Intermediate {} Advanced {} Expert {} Master\n", + output.push_str(&format!( + " Legend: {} None {} Beginner {} Intermediate {} Advanced {} Expert {} Master\n", level_chars[0].dimmed(), level_chars[1].dimmed(), level_chars[2].blue(), @@ -3576,7 +3754,8 @@ impl CommitGraph { } pub fn add_commit(&mut self, date: &str, message: &str, additions: u32) { - self.commits.push((date.to_string(), message.to_string(), additions)); + self.commits + .push((date.to_string(), message.to_string(), additions)); } pub fn render(&self) -> String { @@ -3612,7 +3791,8 @@ impl CommitGraph { let branch = if is_last { "└" } else { "├" }; let line = if is_last { " " } else { "│" }; - output.push_str(&format!(" {} {} {} +{}\n", + output.push_str(&format!( + " {} {} {} +{}\n", branch, colored_node, date.dimmed(), @@ -3676,7 +3856,8 @@ impl ProductivityScore { // Level display let level_stars = "★".repeat((self.level as usize).min(5)); let empty_stars = "☆".repeat(5 - (self.level as usize).min(5)); - output.push_str(&format!(" Level {} {}{}\n\n", + output.push_str(&format!( + " Level {} {}{}\n\n", self.level.to_string().yellow().bold(), level_stars.yellow(), empty_stars.dimmed() @@ -3685,18 +3866,23 @@ impl ProductivityScore { // Score display output.push_str(&format!(" ┌─────────────────────────────────┐\n")); output.push_str(&format!(" │ PRODUCTIVITY SCORE │\n")); - output.push_str(&format!(" │ {:^27} │\n", format!("{}", self.total_score).green().bold())); + output.push_str(&format!( + " │ {:^27} │\n", + format!("{}", self.total_score).green().bold() + )); output.push_str(&format!(" └─────────────────────────────────┘\n\n")); // XP bar let xp_pct = (self.xp_current as f64 / self.xp_next_level as f64 * 100.0).min(100.0); let bar_width = 25; let filled = (xp_pct / 100.0 * bar_width as f64) as usize; - output.push_str(&format!(" XP: {}/{}\n", + output.push_str(&format!( + " XP: {}/{}\n", self.xp_current.to_string().cyan(), self.xp_next_level )); - output.push_str(&format!(" [{}{}] {:.0}%\n\n", + output.push_str(&format!( + " [{}{}] {:.0}%\n\n", "█".repeat(filled).cyan(), "░".repeat(bar_width - filled), xp_pct @@ -3725,9 +3911,9 @@ impl ProductivityScore { /// Shows current energy/focus level with depletion tracking pub struct EnergyMeter { pub title: String, - pub current_energy: f64, // 0-100 + pub current_energy: f64, // 0-100 pub max_energy: f64, - pub drain_rate: f64, // per hour + pub drain_rate: f64, // per hour pub recharge_events: Vec<(String, f64)>, } @@ -3773,7 +3959,8 @@ impl EnergyMeter { }; output.push_str(" ┌──────────────────────┬┐\n"); - output.push_str(&format!(" │{}{}│█\n", + output.push_str(&format!( + " │{}{}│█\n", colorize_text(&fill_char.repeat(filled), color), " ".repeat(battery_width - filled) )); @@ -3788,7 +3975,10 @@ impl EnergyMeter { format!("{:.0}%", pct).red() }; - output.push_str(&format!(" Energy: {} ({:.0}/{:.0})\n", energy_display, self.current_energy, self.max_energy)); + output.push_str(&format!( + " Energy: {} ({:.0}/{:.0})\n", + energy_display, self.current_energy, self.max_energy + )); output.push_str(&format!(" Drain rate: {:.1}/hour\n", self.drain_rate)); let hours_left = if self.drain_rate > 0.0 { @@ -3842,7 +4032,8 @@ impl LearningCurve { } pub fn add_point(&mut self, date: &str, proficiency: f64) { - self.data_points.push((date.to_string(), proficiency.clamp(0.0, 100.0))); + self.data_points + .push((date.to_string(), proficiency.clamp(0.0, 100.0))); } pub fn render(&self) -> String { @@ -3860,10 +4051,12 @@ impl LearningCurve { // Resample if needed let data: Vec = if self.data_points.len() > width { - (0..width).map(|i| { - let idx = i * self.data_points.len() / width; - self.data_points[idx].1 - }).collect() + (0..width) + .map(|i| { + let idx = i * self.data_points.len() / width; + self.data_points[idx].1 + }) + .collect() } else { self.data_points.iter().map(|(_, v)| *v).collect() }; @@ -3911,11 +4104,20 @@ impl LearningCurve { let last = self.data_points.last().map(|(_, v)| *v).unwrap_or(0.0); let improvement = last - first; - output.push_str(&format!("\n Start: {:.0}% → Current: {:.0}%\n", first, last)); + output.push_str(&format!( + "\n Start: {:.0}% → Current: {:.0}%\n", + first, last + )); if improvement > 0.0 { - output.push_str(&format!(" 📈 Improvement: {}\n", format!("+{:.0}%", improvement).green())); + output.push_str(&format!( + " 📈 Improvement: {}\n", + format!("+{:.0}%", improvement).green() + )); } else if improvement < 0.0 { - output.push_str(&format!(" 📉 Change: {}\n", format!("{:.0}%", improvement).red())); + output.push_str(&format!( + " 📉 Change: {}\n", + format!("{:.0}%", improvement).red() + )); } output @@ -3938,7 +4140,8 @@ impl SessionStack { } pub fn add_session(&mut self, project: &str, hours: f64, status: &str) { - self.sessions.push((project.to_string(), hours, status.to_string())); + self.sessions + .push((project.to_string(), hours, status.to_string())); } pub fn render(&self) -> String { @@ -3950,7 +4153,12 @@ impl SessionStack { return output; } - let max_hours = self.sessions.iter().map(|(_, h, _)| *h).fold(0.0_f64, f64::max).max(1.0); + let max_hours = self + .sessions + .iter() + .map(|(_, h, _)| *h) + .fold(0.0_f64, f64::max) + .max(1.0); let total_hours: f64 = self.sessions.iter().map(|(_, h, _)| *h).sum(); // Draw stacked blocks @@ -3969,19 +4177,25 @@ impl SessionStack { let bar = "█".repeat(block_width.max(1)); let padding = width - 4 - block_width.max(1); - output.push_str(&format!(" │ {}{}{} │\n", + output.push_str(&format!( + " │ {}{}{} │\n", bar.cyan(), " ".repeat(padding), status_icon )); - output.push_str(&format!(" │ {:width$} │\n", + output.push_str(&format!( + " │ {:width$} │\n", format!("{} ({:.1}h)", project, hours), width = width - 4 )); } output.push_str(&format!(" └{}┘\n", "─".repeat(width))); - output.push_str(&format!("\n Total: {:.1}h across {} sessions\n", total_hours, self.sessions.len())); + output.push_str(&format!( + "\n Total: {:.1}h across {} sessions\n", + total_hours, + self.sessions.len() + )); output } @@ -3992,7 +4206,7 @@ impl SessionStack { pub struct MilestoneRoad { pub title: String, pub milestones: Vec<(String, bool, Option)>, // (name, reached, date) - pub current_progress: f64, // 0-100 between milestones + pub current_progress: f64, // 0-100 between milestones } impl MilestoneRoad { @@ -4005,7 +4219,8 @@ impl MilestoneRoad { } pub fn add_milestone(&mut self, name: &str, reached: bool, date: Option<&str>) { - self.milestones.push((name.to_string(), reached, date.map(|s| s.to_string()))); + self.milestones + .push((name.to_string(), reached, date.map(|s| s.to_string()))); } pub fn set_progress(&mut self, progress: f64) { @@ -4022,10 +4237,18 @@ impl MilestoneRoad { } let reached_count = self.milestones.iter().filter(|(_, r, _)| *r).count(); - output.push_str(&format!(" Progress: {} / {} milestones\n\n", reached_count, self.milestones.len())); + output.push_str(&format!( + " Progress: {} / {} milestones\n\n", + reached_count, + self.milestones.len() + )); // Find current position - let current_idx = self.milestones.iter().position(|(_, r, _)| !*r).unwrap_or(self.milestones.len()); + let current_idx = self + .milestones + .iter() + .position(|(_, r, _)| !*r) + .unwrap_or(self.milestones.len()); for (i, (name, reached, date)) in self.milestones.iter().enumerate() { let is_current = i == current_idx; @@ -4059,7 +4282,10 @@ impl MilestoneRoad { name.dimmed().to_string() }; - let date_str = date.as_ref().map(|d| format!(" ({})", d.dimmed())).unwrap_or_default(); + let date_str = date + .as_ref() + .map(|d| format!(" ({})", d.dimmed())) + .unwrap_or_default(); output.push_str(&format!(" {} {}{}\n", marker, name_str, date_str)); @@ -4104,7 +4330,8 @@ impl ComparisonRadar { } pub fn add_item(&mut self, name: &str, values: Vec, color: &str) { - self.items.push((name.to_string(), values, color.to_string())); + self.items + .push((name.to_string(), values, color.to_string())); } pub fn render(&self) -> String { @@ -4197,7 +4424,8 @@ impl StreakFlame { } else { "white" }; - output.push_str(&format!(" {}{}\n", + output.push_str(&format!( + " {}{}\n", colorize_text(left, color), colorize_text(right, color) )); @@ -4208,11 +4436,20 @@ impl StreakFlame { // Streak count let streak_display = if self.current_streak >= self.best_streak && self.current_streak > 0 { - format!("{} 🏆 NEW RECORD!", self.current_streak).yellow().bold().to_string() + format!("{} 🏆 NEW RECORD!", self.current_streak) + .yellow() + .bold() + .to_string() } else if self.current_streak > 20 { - format!("{} days", self.current_streak).red().bold().to_string() + format!("{} days", self.current_streak) + .red() + .bold() + .to_string() } else if self.current_streak > 7 { - format!("{} days", self.current_streak).yellow().bold().to_string() + format!("{} days", self.current_streak) + .yellow() + .bold() + .to_string() } else if self.current_streak > 0 { format!("{} days", self.current_streak).green().to_string() } else { diff --git a/src/behavior_analyzer.rs b/src/behavior_analyzer.rs index 337a449..d6c12df 100644 --- a/src/behavior_analyzer.rs +++ b/src/behavior_analyzer.rs @@ -1,6 +1,6 @@ +use regex::Regex; /// Behavioral Analysis - Understands what code DOES, not what functions it uses use std::collections::HashMap; -use regex::Regex; #[derive(Debug, Clone)] pub struct BehaviorProfile { @@ -152,96 +152,96 @@ pub enum CodeCategory { LanguageParser, // React Component Library (from iteration 5) - InputComponentLibrary, // Input (1153) - SelectComponentLibrary, // Select (1128) - FormComponentLibrary, // Form (953) - TabNavigationSystem, // Tab (737) - ProgressIndicatorSystem, // Progress (424) - AlertNotificationSystem, // Alert (220) - ButtonComponentLibrary, // Button (211) - DialogComponentLibrary, // Dialog (200) - MenuComponentLibrary, // Menu (136) + InputComponentLibrary, // Input (1153) + SelectComponentLibrary, // Select (1128) + FormComponentLibrary, // Form (953) + TabNavigationSystem, // Tab (737) + ProgressIndicatorSystem, // Progress (424) + AlertNotificationSystem, // Alert (220) + ButtonComponentLibrary, // Button (211) + DialogComponentLibrary, // Dialog (200) + MenuComponentLibrary, // Menu (136) // State Management (from iteration 5) - ActionDispatcher, // action (1810), dispatch (179) - StateSelector, // selector (168) - StoreManagerCore, // store (410) + ActionDispatcher, // action (1810), dispatch (179) + StateSelector, // selector (168) + StoreManagerCore, // store (410) // Network/API Layer (from iteration 5) - HTTPRequestManager, // http (4176), request (2449) - HTTPResponseHandler, // response (1451) - EndpointRegistry, // endpoint (1055) - APIClientLibrary, // api (1080) - FetchAPIWrapper, // fetch (709) + HTTPRequestManager, // http (4176), request (2449) + HTTPResponseHandler, // response (1451) + EndpointRegistry, // endpoint (1055) + APIClientLibrary, // api (1080) + FetchAPIWrapper, // fetch (709) // Editor Features (from iteration 5) - DiffViewerComponent, // diff (601) - MergeConflictResolver, // merge (635), conflict (62) - CompactOperationManager, // compact (353) - TeleportNavigator, // teleport (112) + DiffViewerComponent, // diff (601) + MergeConflictResolver, // merge (635), conflict (62) + CompactOperationManager, // compact (353) + TeleportNavigator, // teleport (112) // Service Architecture (from iteration 5) - PlaneServiceCoordinator, // PlaneService (188) - CredentialsProviderSystem, // CredentialsProvider (112) + PlaneServiceCoordinator, // PlaneService (188) + CredentialsProviderSystem, // CredentialsProvider (112) // Error Types (from iteration 6) - TypeErrorHandler, // TypeError (1457) - ParameterErrorHandler, // ParameterError (350) - ProviderErrorHandler, // ProviderError (156) - RangeErrorHandler, // RangeError (144) - ServiceExceptionHandler, // ServiceException (112) + TypeErrorHandler, // TypeError (1457) + ParameterErrorHandler, // ParameterError (350) + ProviderErrorHandler, // ProviderError (156) + RangeErrorHandler, // RangeError (144) + ServiceExceptionHandler, // ServiceException (112) // Additional Error Types (iteration 8) - AbortErrorHandler, // AbortError (88) - SyntaxErrorHandler, // SyntaxError (82) - TimeoutErrorHandler, // TimeoutError (76) - UnknownErrorHandler, // UnknownError (62) - QueryErrorHandler, // QueryError (62) - ReferenceErrorHandler, // ReferenceError (59) - ParseErrorHandler, // ParseError (58) - ResponseErrorHandler, // ResponseError (54) - RequestErrorHandler, // RequestError (54) - InternalErrorHandler, // InternalError (54) - TokenExceptionHandler, // TokenException (48) - ServerExceptionHandler, // ServerException (44) - AxiosErrorHandler, // AxiosError (44) - ParserErrorHandler, // ParserError (42) - AuthErrorHandler, // AuthError (42) + AbortErrorHandler, // AbortError (88) + SyntaxErrorHandler, // SyntaxError (82) + TimeoutErrorHandler, // TimeoutError (76) + UnknownErrorHandler, // UnknownError (62) + QueryErrorHandler, // QueryError (62) + ReferenceErrorHandler, // ReferenceError (59) + ParseErrorHandler, // ParseError (58) + ResponseErrorHandler, // ResponseError (54) + RequestErrorHandler, // RequestError (54) + InternalErrorHandler, // InternalError (54) + TokenExceptionHandler, // TokenException (48) + ServerExceptionHandler, // ServerException (44) + AxiosErrorHandler, // AxiosError (44) + ParserErrorHandler, // ParserError (42) + AuthErrorHandler, // AuthError (42) // Content-Based Categories (Iteration 13 - defeats filename obfuscation!) - EllipticCurveCrypto, // EC, KeyPair, Signature, HmacDRBG - NodeErrorFactory, // createErrorType, NodeError, codes[] - BitwiseCryptoOps, // ushrn, bitLength, nh (crypto bit operations) - JavaScriptSyntaxHighlighter, // JSX tags, keyword highlighting, className - ReactDevToolsProfiler, // __REACT_DEVTOOLS_GLOBAL_HOOK__, performance profiling - SyncFileIO, // sync file operations - ImageProcessor, // image handling and manipulation - RegexEngine, // regex compilation and execution - TimestampManager, // timestamp creation and formatting - APIErrorHandler, // API-specific error handling - ErrorRecoverySystem, // error recovery strategies - FallbackErrorHandler, // fallback error handling - PromiseErrorHandler, // promise rejection handling + EllipticCurveCrypto, // EC, KeyPair, Signature, HmacDRBG + NodeErrorFactory, // createErrorType, NodeError, codes[] + BitwiseCryptoOps, // ushrn, bitLength, nh (crypto bit operations) + JavaScriptSyntaxHighlighter, // JSX tags, keyword highlighting, className + ReactDevToolsProfiler, // __REACT_DEVTOOLS_GLOBAL_HOOK__, performance profiling + SyncFileIO, // sync file operations + ImageProcessor, // image handling and manipulation + RegexEngine, // regex compilation and execution + TimestampManager, // timestamp creation and formatting + APIErrorHandler, // API-specific error handling + ErrorRecoverySystem, // error recovery strategies + FallbackErrorHandler, // fallback error handling + PromiseErrorHandler, // promise rejection handling // Iteration 14: Additional obfuscated utilities discovered! - DateFnsLibrary, // weekStartsOn, Date.UTC, getFullYear, locale-aware dates - DebounceThrottle, // leading, trailing, maxWait, setTimeout/clearTimeout - JSONTokenizer, // brace matching, token types, JSON.parse - StartupProfiler, // performance marks, startup timing, profiling reports - ObjectInspector, // property introspection, __proto__, constructor.name - ElmSyntaxHighlighter, // Elm language: infix/infixl/infixr, port keyword - LodashTypeChecker, // [object Array], [object Function], type introspection + DateFnsLibrary, // weekStartsOn, Date.UTC, getFullYear, locale-aware dates + DebounceThrottle, // leading, trailing, maxWait, setTimeout/clearTimeout + JSONTokenizer, // brace matching, token types, JSON.parse + StartupProfiler, // performance marks, startup timing, profiling reports + ObjectInspector, // property introspection, __proto__, constructor.name + ElmSyntaxHighlighter, // Elm language: infix/infixl/infixr, port keyword + LodashTypeChecker, // [object Array], [object Function], type introspection // Iteration 20: Deep obfuscation - entire directories with misleading names! - RxJSOperators, // .subscribe(), createOperatorSubscriber, Observable, Subject - OpenTelemetryEncoding, // hrTimeToNanos, hexToBinary, encodeAsLongBits, createInstrumentationScope - ZlibCompression, // Z_MIN_WINDOWBITS, Z_DEFAULT_CHUNK, deflate, inflate, gzip - InstallationDetection, // Homebrew, winget, process.execPath, npm config get prefix - AnthropicAPIClient, // api.anthropic.com, CLAUDE_CODE_USE_BEDROCK, session logs - LodashCoreLibrary, // Heavy typeof checks, Object.keys, type coercion, minified patterns + RxJSOperators, // .subscribe(), createOperatorSubscriber, Observable, Subject + OpenTelemetryEncoding, // hrTimeToNanos, hexToBinary, encodeAsLongBits, createInstrumentationScope + ZlibCompression, // Z_MIN_WINDOWBITS, Z_DEFAULT_CHUNK, deflate, inflate, gzip + InstallationDetection, // Homebrew, winget, process.execPath, npm config get prefix + AnthropicAPIClient, // api.anthropic.com, CLAUDE_CODE_USE_BEDROCK, session logs + LodashCoreLibrary, // Heavy typeof checks, Object.keys, type coercion, minified patterns // Iteration 24: Crypto library wrappers and entry points - CryptoLibraryWrappers, // browserify-crypto, ASN.1, elliptic lib, hash lib entry points + CryptoLibraryWrappers, // browserify-crypto, ASN.1, elliptic lib, hash lib entry points Unknown, } @@ -337,7 +337,8 @@ impl BehaviorAnalyzer { fn analyze_react(&self, scores: &mut HashMap, evidence: &mut Vec) { let create_element_count = self.code.matches("createElement").count(); if create_element_count > 5 { - *scores.entry(CodeCategory::ReactComponent).or_insert(0.0) += (create_element_count as f64 * 0.1).min(3.0); + *scores.entry(CodeCategory::ReactComponent).or_insert(0.0) += + (create_element_count as f64 * 0.1).min(3.0); evidence.push(format!("React ({} createElement)", create_element_count)); } } @@ -345,59 +346,85 @@ impl BehaviorAnalyzer { fn analyze_process(&self, scores: &mut HashMap, evidence: &mut Vec) { let process_count = self.code.matches("process.").count(); if process_count > 5 { - *scores.entry(CodeCategory::ProcessManager).or_insert(0.0) += (process_count as f64 * 0.1).min(2.0); + *scores.entry(CodeCategory::ProcessManager).or_insert(0.0) += + (process_count as f64 * 0.1).min(2.0); evidence.push("Process management".to_string()); } } - fn analyze_filesystem(&self, scores: &mut HashMap, evidence: &mut Vec) { + fn analyze_filesystem( + &self, + scores: &mut HashMap, + evidence: &mut Vec, + ) { let fs_count = self.code.matches("Sync(").count(); if fs_count > 3 { - *scores.entry(CodeCategory::FileSystemSync).or_insert(0.0) += (fs_count as f64 * 0.2).min(2.0); + *scores.entry(CodeCategory::FileSystemSync).or_insert(0.0) += + (fs_count as f64 * 0.2).min(2.0); evidence.push("File system operations".to_string()); } } - fn analyze_string_ops(&self, scores: &mut HashMap, evidence: &mut Vec) { + fn analyze_string_ops( + &self, + scores: &mut HashMap, + evidence: &mut Vec, + ) { let str_ops = self.code.matches(".join(").count() + self.code.matches(".slice(").count(); if str_ops > 10 { - *scores.entry(CodeCategory::StringManipulator).or_insert(0.0) += (str_ops as f64 * 0.05).min(2.0); + *scores.entry(CodeCategory::StringManipulator).or_insert(0.0) += + (str_ops as f64 * 0.05).min(2.0); evidence.push("String operations".to_string()); } } - fn analyze_data_parsing(&self, scores: &mut HashMap, evidence: &mut Vec) { + fn analyze_data_parsing( + &self, + scores: &mut HashMap, + evidence: &mut Vec, + ) { let json_count = self.code.matches("JSON.").count(); if json_count > 2 { - *scores.entry(CodeCategory::JSONParser).or_insert(0.0) += (json_count as f64 * 0.3).min(2.0); + *scores.entry(CodeCategory::JSONParser).or_insert(0.0) += + (json_count as f64 * 0.3).min(2.0); evidence.push("JSON parsing".to_string()); } } fn analyze_timing(&self, scores: &mut HashMap, evidence: &mut Vec) { - let timer_count = self.code.matches("setTimeout").count() + self.code.matches("setInterval").count(); + let timer_count = + self.code.matches("setTimeout").count() + self.code.matches("setInterval").count(); if timer_count > 0 { *scores.entry(CodeCategory::TimerScheduler).or_insert(0.0) += timer_count as f64 * 0.4; evidence.push("Timer scheduling".to_string()); } } - fn analyze_claude_specific(&self, scores: &mut HashMap, evidence: &mut Vec) { + fn analyze_claude_specific( + &self, + scores: &mut HashMap, + evidence: &mut Vec, + ) { // Claude Protocol Headers if self.code.contains("Claude-Escapes") || self.code.contains("Claude-Steers") { - *scores.entry(CodeCategory::ClaudeProtocolHandler).or_insert(0.0) += 2.5; + *scores + .entry(CodeCategory::ClaudeProtocolHandler) + .or_insert(0.0) += 2.5; evidence.push("Claude protocol headers".to_string()); } // Sandbox Management (highest specificity) - if self.code.contains("CLAUDE_CODE_BUBBLEWRAP") || self.code.contains("BASH_SANDBOX_SHOW_INDICATOR") { + if self.code.contains("CLAUDE_CODE_BUBBLEWRAP") + || self.code.contains("BASH_SANDBOX_SHOW_INDICATOR") + { *scores.entry(CodeCategory::SandboxManager).or_insert(0.0) += 10.0; evidence.push("Sandbox management (BUBBLEWRAP)".to_string()); } // API Key Vault - if self.code.contains("API_KEY_FILE_DESCRIPTOR") || - (self.code.contains("API_KEY_HELPER") && self.code.contains("TTL_MS")) { + if self.code.contains("API_KEY_FILE_DESCRIPTOR") + || (self.code.contains("API_KEY_HELPER") && self.code.contains("TTL_MS")) + { *scores.entry(CodeCategory::APIKeyVault).or_insert(0.0) += 10.0; evidence.push("API Key Vault".to_string()); } @@ -409,26 +436,38 @@ impl BehaviorAnalyzer { } // Command Injection Guard - if self.code.contains("DISABLE_COMMAND_INJECTION_CHECK") || self.code.contains("ADDITIONAL_PROTECTION") { - *scores.entry(CodeCategory::CommandInjectionGuard).or_insert(0.0) += 2.5; + if self.code.contains("DISABLE_COMMAND_INJECTION_CHECK") + || self.code.contains("ADDITIONAL_PROTECTION") + { + *scores + .entry(CodeCategory::CommandInjectionGuard) + .or_insert(0.0) += 2.5; evidence.push("Command injection guard".to_string()); } // Agent Telemetry (specific tengu_ events) if self.code.contains("tengu_agent_") { - *scores.entry(CodeCategory::AgentTelemetryRecorder).or_insert(0.0) += 2.5; + *scores + .entry(CodeCategory::AgentTelemetryRecorder) + .or_insert(0.0) += 2.5; evidence.push("Agent telemetry (tengu_agent_)".to_string()); } // API Monitoring if self.code.contains("tengu_api_error") || self.code.contains("tengu_api_retry") { - *scores.entry(CodeCategory::APIMonitoringDashboard).or_insert(0.0) += 2.5; + *scores + .entry(CodeCategory::APIMonitoringDashboard) + .or_insert(0.0) += 2.5; evidence.push("API monitoring (tengu_api_)".to_string()); } // Feedback Collection - if self.code.contains("tengu_accept_feedback") || self.code.contains("tengu_accept_submitted") { - *scores.entry(CodeCategory::FeedbackCollectionSystem).or_insert(0.0) += 2.5; + if self.code.contains("tengu_accept_feedback") + || self.code.contains("tengu_accept_submitted") + { + *scores + .entry(CodeCategory::FeedbackCollectionSystem) + .or_insert(0.0) += 2.5; evidence.push("Feedback collection".to_string()); } @@ -439,16 +478,22 @@ impl BehaviorAnalyzer { } // Keyboard Shortcuts (HIGHEST specificity - data structure pattern) - if (self.code.contains("bindings:") || self.code.contains("bindings={")) && - (self.code.contains("ctrl+") || self.code.contains("meta+")) { - *scores.entry(CodeCategory::KeyboardShortcutManager).or_insert(0.0) += 10.0; + if (self.code.contains("bindings:") || self.code.contains("bindings={")) + && (self.code.contains("ctrl+") || self.code.contains("meta+")) + { + *scores + .entry(CodeCategory::KeyboardShortcutManager) + .or_insert(0.0) += 10.0; evidence.push("Keyboard shortcut bindings".to_string()); } // Command Palette - if (self.code.contains("\"app:") || self.code.contains("\"chat:")) && - self.code.contains("\"escape") { - *scores.entry(CodeCategory::CommandPaletteHandler).or_insert(0.0) += 2.5; + if (self.code.contains("\"app:") || self.code.contains("\"chat:")) + && self.code.contains("\"escape") + { + *scores + .entry(CodeCategory::CommandPaletteHandler) + .or_insert(0.0) += 2.5; evidence.push("Command palette (app:*, chat:*)".to_string()); } @@ -459,33 +504,45 @@ impl BehaviorAnalyzer { } // Modal Dialog (escape + confirmation patterns) - if self.code.contains("escape:") && - (self.code.contains("confirm:") || self.code.contains("Confirmation")) { - *scores.entry(CodeCategory::ModalDialogController).or_insert(0.0) += 2.0; + if self.code.contains("escape:") + && (self.code.contains("confirm:") || self.code.contains("Confirmation")) + { + *scores + .entry(CodeCategory::ModalDialogController) + .or_insert(0.0) += 2.0; evidence.push("Modal dialog controller".to_string()); } // Generic Claude telemetry (fallback) if self.code.contains("tengu_") { - *scores.entry(CodeCategory::ClaudeTelemetryRecorder).or_insert(0.0) += 1.0; + *scores + .entry(CodeCategory::ClaudeTelemetryRecorder) + .or_insert(0.0) += 1.0; evidence.push("Claude telemetry (generic)".to_string()); } // Proxy Manager - if self.code.contains("HOST_HTTP_PROXY_PORT") || self.code.contains("HOST_SOCKS_PROXY_PORT") { + if self.code.contains("HOST_HTTP_PROXY_PORT") || self.code.contains("HOST_SOCKS_PROXY_PORT") + { *scores.entry(CodeCategory::ProxyManager).or_insert(0.0) += 10.0; evidence.push("Proxy manager (HTTP/SOCKS)".to_string()); } // Telemetry Controller if self.code.contains("ENABLE_TELEMETRY") || self.code.contains("OTEL_FLUSH_TIMEOUT_MS") { - *scores.entry(CodeCategory::TelemetryController).or_insert(0.0) += 2.5; + *scores + .entry(CodeCategory::TelemetryController) + .or_insert(0.0) += 2.5; evidence.push("Telemetry controller".to_string()); } // Token Budget Manager - if self.code.contains("FILE_READ_MAX_OUTPUT_TOKENS") || self.code.contains("MAX_OUTPUT_TOKENS") { - *scores.entry(CodeCategory::TokenBudgetManager).or_insert(0.0) += 10.0; + if self.code.contains("FILE_READ_MAX_OUTPUT_TOKENS") + || self.code.contains("MAX_OUTPUT_TOKENS") + { + *scores + .entry(CodeCategory::TokenBudgetManager) + .or_insert(0.0) += 10.0; evidence.push("Token budget manager".to_string()); } @@ -497,13 +554,19 @@ impl BehaviorAnalyzer { // Tool Concurrency Limiter if self.code.contains("MAX_TOOL_USE_CONCURRENCY") { - *scores.entry(CodeCategory::ToolConcurrencyLimiter).or_insert(0.0) += 10.0; + *scores + .entry(CodeCategory::ToolConcurrencyLimiter) + .or_insert(0.0) += 10.0; evidence.push("Tool concurrency limiter".to_string()); } // Prompt Suggestion Engine - if self.code.contains("ENABLE_PROMPT_SUGGESTION") || self.code.contains("tengu_prompt_suggestion") { - *scores.entry(CodeCategory::PromptSuggestionEngine).or_insert(0.0) += 2.5; + if self.code.contains("ENABLE_PROMPT_SUGGESTION") + || self.code.contains("tengu_prompt_suggestion") + { + *scores + .entry(CodeCategory::PromptSuggestionEngine) + .or_insert(0.0) += 2.5; evidence.push("Prompt suggestion engine".to_string()); } @@ -515,13 +578,19 @@ impl BehaviorAnalyzer { // Bash Security Monitor (52 occurrences!) - HIGHEST SPECIFICITY if self.code.contains("tengu_bash_security_check_triggered") { - *scores.entry(CodeCategory::BashSecurityMonitor).or_insert(0.0) += 10.0; + *scores + .entry(CodeCategory::BashSecurityMonitor) + .or_insert(0.0) += 10.0; evidence.push("Bash security monitor".to_string()); } // GitHub Integration Tracker - if self.code.contains("tengu_install_github_app") || self.code.contains("tengu_setup_github_actions") { - *scores.entry(CodeCategory::GitHubIntegrationTracker).or_insert(0.0) += 2.5; + if self.code.contains("tengu_install_github_app") + || self.code.contains("tengu_setup_github_actions") + { + *scores + .entry(CodeCategory::GitHubIntegrationTracker) + .or_insert(0.0) += 2.5; evidence.push("GitHub integration tracker".to_string()); } @@ -533,19 +602,25 @@ impl BehaviorAnalyzer { // MCP Operation Tracker if self.code.contains("tengu_mcp_") { - *scores.entry(CodeCategory::MCPOperationTracker).or_insert(0.0) += 2.5; + *scores + .entry(CodeCategory::MCPOperationTracker) + .or_insert(0.0) += 2.5; evidence.push("MCP operation tracker".to_string()); } // Tool Use Monitor - if self.code.contains("tengu_tool_use_error") || self.code.contains("tengu_tool_use_success") { + if self.code.contains("tengu_tool_use_error") + || self.code.contains("tengu_tool_use_success") + { *scores.entry(CodeCategory::ToolUseMonitor).or_insert(0.0) += 2.5; evidence.push("Tool use monitor".to_string()); } // Version Lock Tracker if self.code.contains("tengu_version_lock_") { - *scores.entry(CodeCategory::VersionLockTracker).or_insert(0.0) += 2.5; + *scores + .entry(CodeCategory::VersionLockTracker) + .or_insert(0.0) += 2.5; evidence.push("Version lock tracker".to_string()); } @@ -564,147 +639,201 @@ impl BehaviorAnalyzer { // Generic environment loader (fallback) let claude_env_count = self.code.matches("CLAUDE_CODE_").count(); if claude_env_count > 3 { - *scores.entry(CodeCategory::ClaudeEnvironmentLoader).or_insert(0.0) += (claude_env_count as f64 * 0.2).min(1.5); + *scores + .entry(CodeCategory::ClaudeEnvironmentLoader) + .or_insert(0.0) += (claude_env_count as f64 * 0.2).min(1.5); evidence.push(format!("Claude environment ({} vars)", claude_env_count)); } } /// Analyze React UI components (Iteration 5 discoveries) - fn analyze_react_components(&self, scores: &mut HashMap, evidence: &mut Vec) { + fn analyze_react_components( + &self, + scores: &mut HashMap, + evidence: &mut Vec, + ) { // Input components (1153 occurrences!) let input_count = self.code.matches("Input").count(); if input_count >= 5 { - *scores.entry(CodeCategory::InputComponentLibrary).or_insert(0.0) += (input_count as f64 * 0.3).min(10.0); + *scores + .entry(CodeCategory::InputComponentLibrary) + .or_insert(0.0) += (input_count as f64 * 0.3).min(10.0); evidence.push(format!("Input component library ({} refs)", input_count)); } // Select components (1128 occurrences!) let select_count = self.code.matches("Select").count(); if select_count >= 5 { - *scores.entry(CodeCategory::SelectComponentLibrary).or_insert(0.0) += (select_count as f64 * 0.3).min(10.0); + *scores + .entry(CodeCategory::SelectComponentLibrary) + .or_insert(0.0) += (select_count as f64 * 0.3).min(10.0); evidence.push(format!("Select component library ({} refs)", select_count)); } // Form components (953 occurrences!) let form_count = self.code.matches("Form").count(); if form_count >= 5 { - *scores.entry(CodeCategory::FormComponentLibrary).or_insert(0.0) += (form_count as f64 * 0.3).min(10.0); + *scores + .entry(CodeCategory::FormComponentLibrary) + .or_insert(0.0) += (form_count as f64 * 0.3).min(10.0); evidence.push(format!("Form component library ({} refs)", form_count)); } // Tab navigation (737 occurrences!) let tab_count = self.code.matches("Tab").count(); if tab_count >= 5 { - *scores.entry(CodeCategory::TabNavigationSystem).or_insert(0.0) += (tab_count as f64 * 0.3).min(8.0); + *scores + .entry(CodeCategory::TabNavigationSystem) + .or_insert(0.0) += (tab_count as f64 * 0.3).min(8.0); evidence.push(format!("Tab navigation ({} refs)", tab_count)); } // Progress indicators (424 occurrences!) let progress_count = self.code.matches("Progress").count(); if progress_count >= 5 { - *scores.entry(CodeCategory::ProgressIndicatorSystem).or_insert(0.0) += (progress_count as f64 * 0.3).min(8.0); + *scores + .entry(CodeCategory::ProgressIndicatorSystem) + .or_insert(0.0) += (progress_count as f64 * 0.3).min(8.0); evidence.push(format!("Progress indicators ({} refs)", progress_count)); } // Alert/notification system (220 occurrences!) let alert_count = self.code.matches("Alert").count(); if alert_count >= 3 { - *scores.entry(CodeCategory::AlertNotificationSystem).or_insert(0.0) += (alert_count as f64 * 0.4).min(8.0); + *scores + .entry(CodeCategory::AlertNotificationSystem) + .or_insert(0.0) += (alert_count as f64 * 0.4).min(8.0); evidence.push(format!("Alert system ({} refs)", alert_count)); } // Button library (211 occurrences!) let button_count = self.code.matches("Button").count(); if button_count >= 3 { - *scores.entry(CodeCategory::ButtonComponentLibrary).or_insert(0.0) += (button_count as f64 * 0.4).min(8.0); + *scores + .entry(CodeCategory::ButtonComponentLibrary) + .or_insert(0.0) += (button_count as f64 * 0.4).min(8.0); evidence.push(format!("Button library ({} refs)", button_count)); } // Dialog components (200 occurrences!) let dialog_count = self.code.matches("Dialog").count(); if dialog_count >= 3 { - *scores.entry(CodeCategory::DialogComponentLibrary).or_insert(0.0) += (dialog_count as f64 * 0.4).min(8.0); + *scores + .entry(CodeCategory::DialogComponentLibrary) + .or_insert(0.0) += (dialog_count as f64 * 0.4).min(8.0); evidence.push(format!("Dialog components ({} refs)", dialog_count)); } // Menu components (136 occurrences!) let menu_count = self.code.matches("Menu").count(); if menu_count >= 3 { - *scores.entry(CodeCategory::MenuComponentLibrary).or_insert(0.0) += (menu_count as f64 * 0.4).min(7.0); + *scores + .entry(CodeCategory::MenuComponentLibrary) + .or_insert(0.0) += (menu_count as f64 * 0.4).min(7.0); evidence.push(format!("Menu components ({} refs)", menu_count)); } } /// Analyze network/API layer (Iteration 5 discoveries) - fn analyze_network_layer(&self, scores: &mut HashMap, evidence: &mut Vec) { + fn analyze_network_layer( + &self, + scores: &mut HashMap, + evidence: &mut Vec, + ) { // HTTP request management (4176 http, 2449 request!) let http_count = self.code.matches("http").count(); let request_count = self.code.matches("request").count(); if http_count >= 10 || request_count >= 10 { - *scores.entry(CodeCategory::HTTPRequestManager).or_insert(0.0) += ((http_count + request_count) as f64 * 0.05).min(10.0); - evidence.push(format!("HTTP request manager ({} http, {} request)", http_count, request_count)); + *scores + .entry(CodeCategory::HTTPRequestManager) + .or_insert(0.0) += ((http_count + request_count) as f64 * 0.05).min(10.0); + evidence.push(format!( + "HTTP request manager ({} http, {} request)", + http_count, request_count + )); } // HTTP response handling (1451 response!) let response_count = self.code.matches("response").count(); if response_count >= 10 { - *scores.entry(CodeCategory::HTTPResponseHandler).or_insert(0.0) += (response_count as f64 * 0.1).min(10.0); + *scores + .entry(CodeCategory::HTTPResponseHandler) + .or_insert(0.0) += (response_count as f64 * 0.1).min(10.0); evidence.push(format!("HTTP response handler ({} refs)", response_count)); } // Endpoint registry (1055 endpoint!) let endpoint_count = self.code.matches("endpoint").count(); if endpoint_count >= 5 { - *scores.entry(CodeCategory::EndpointRegistry).or_insert(0.0) += (endpoint_count as f64 * 0.2).min(9.0); + *scores.entry(CodeCategory::EndpointRegistry).or_insert(0.0) += + (endpoint_count as f64 * 0.2).min(9.0); evidence.push(format!("Endpoint registry ({} refs)", endpoint_count)); } // API client library (1080 api!) let api_count = self.code.matches("api").count(); if api_count >= 10 { - *scores.entry(CodeCategory::APIClientLibrary).or_insert(0.0) += (api_count as f64 * 0.1).min(9.0); + *scores.entry(CodeCategory::APIClientLibrary).or_insert(0.0) += + (api_count as f64 * 0.1).min(9.0); evidence.push(format!("API client library ({} refs)", api_count)); } // Fetch API wrapper (709 fetch!) let fetch_count = self.code.matches("fetch").count(); if fetch_count >= 5 { - *scores.entry(CodeCategory::FetchAPIWrapper).or_insert(0.0) += (fetch_count as f64 * 0.2).min(8.0); + *scores.entry(CodeCategory::FetchAPIWrapper).or_insert(0.0) += + (fetch_count as f64 * 0.2).min(8.0); evidence.push(format!("Fetch API wrapper ({} refs)", fetch_count)); } } /// Analyze state management patterns (Iteration 5 discoveries) - fn analyze_state_patterns(&self, scores: &mut HashMap, evidence: &mut Vec) { + fn analyze_state_patterns( + &self, + scores: &mut HashMap, + evidence: &mut Vec, + ) { // Action dispatcher (1810 action, 179 dispatch!) let action_count = self.code.matches("action").count(); let dispatch_count = self.code.matches("dispatch").count(); if action_count >= 10 || dispatch_count >= 5 { - *scores.entry(CodeCategory::ActionDispatcher).or_insert(0.0) += ((action_count + dispatch_count * 2) as f64 * 0.05).min(10.0); - evidence.push(format!("Action dispatcher ({} actions, {} dispatch)", action_count, dispatch_count)); + *scores.entry(CodeCategory::ActionDispatcher).or_insert(0.0) += + ((action_count + dispatch_count * 2) as f64 * 0.05).min(10.0); + evidence.push(format!( + "Action dispatcher ({} actions, {} dispatch)", + action_count, dispatch_count + )); } // State selector (168 selector!) let selector_count = self.code.matches("selector").count(); if selector_count >= 5 { - *scores.entry(CodeCategory::StateSelector).or_insert(0.0) += (selector_count as f64 * 0.3).min(8.0); + *scores.entry(CodeCategory::StateSelector).or_insert(0.0) += + (selector_count as f64 * 0.3).min(8.0); evidence.push(format!("State selector ({} refs)", selector_count)); } // Store manager (410 store!) let store_count = self.code.matches("store").count(); if store_count >= 5 { - *scores.entry(CodeCategory::StoreManagerCore).or_insert(0.0) += (store_count as f64 * 0.2).min(8.0); + *scores.entry(CodeCategory::StoreManagerCore).or_insert(0.0) += + (store_count as f64 * 0.2).min(8.0); evidence.push(format!("Store manager ({} refs)", store_count)); } } /// Analyze editor features (Iteration 5 discoveries) - fn analyze_editor_features(&self, scores: &mut HashMap, evidence: &mut Vec) { + fn analyze_editor_features( + &self, + scores: &mut HashMap, + evidence: &mut Vec, + ) { // Diff viewer (601 diff!) let diff_count = self.code.matches("diff").count(); if diff_count >= 5 { - *scores.entry(CodeCategory::DiffViewerComponent).or_insert(0.0) += (diff_count as f64 * 0.3).min(9.0); + *scores + .entry(CodeCategory::DiffViewerComponent) + .or_insert(0.0) += (diff_count as f64 * 0.3).min(9.0); evidence.push(format!("Diff viewer ({} refs)", diff_count)); } @@ -712,77 +841,114 @@ impl BehaviorAnalyzer { let merge_count = self.code.matches("merge").count(); let conflict_count = self.code.matches("conflict").count(); if merge_count >= 5 || conflict_count >= 3 { - *scores.entry(CodeCategory::MergeConflictResolver).or_insert(0.0) += ((merge_count + conflict_count * 2) as f64 * 0.1).min(9.0); - evidence.push(format!("Merge resolver ({} merge, {} conflict)", merge_count, conflict_count)); + *scores + .entry(CodeCategory::MergeConflictResolver) + .or_insert(0.0) += ((merge_count + conflict_count * 2) as f64 * 0.1).min(9.0); + evidence.push(format!( + "Merge resolver ({} merge, {} conflict)", + merge_count, conflict_count + )); } // Compact operation (353 compact!) let compact_count = self.code.matches("compact").count(); if compact_count >= 5 { - *scores.entry(CodeCategory::CompactOperationManager).or_insert(0.0) += (compact_count as f64 * 0.3).min(8.0); + *scores + .entry(CodeCategory::CompactOperationManager) + .or_insert(0.0) += (compact_count as f64 * 0.3).min(8.0); evidence.push(format!("Compact operations ({} refs)", compact_count)); } // Teleport navigator (112 teleport!) let teleport_count = self.code.matches("teleport").count(); if teleport_count >= 3 { - *scores.entry(CodeCategory::TeleportNavigator).or_insert(0.0) += (teleport_count as f64 * 0.5).min(8.0); + *scores.entry(CodeCategory::TeleportNavigator).or_insert(0.0) += + (teleport_count as f64 * 0.5).min(8.0); evidence.push(format!("Teleport navigator ({} refs)", teleport_count)); } } /// Analyze service architecture (Iteration 5 discoveries) - fn analyze_services(&self, scores: &mut HashMap, evidence: &mut Vec) { + fn analyze_services( + &self, + scores: &mut HashMap, + evidence: &mut Vec, + ) { // Plane service (188 PlaneService!) let plane_count = self.code.matches("PlaneService").count(); if plane_count >= 3 { - *scores.entry(CodeCategory::PlaneServiceCoordinator).or_insert(0.0) += (plane_count as f64 * 0.5).min(9.0); + *scores + .entry(CodeCategory::PlaneServiceCoordinator) + .or_insert(0.0) += (plane_count as f64 * 0.5).min(9.0); evidence.push(format!("Plane service ({} refs)", plane_count)); } // Credentials provider (112 CredentialsProvider!) let creds_count = self.code.matches("CredentialsProvider").count(); if creds_count >= 3 { - *scores.entry(CodeCategory::CredentialsProviderSystem).or_insert(0.0) += (creds_count as f64 * 0.5).min(9.0); + *scores + .entry(CodeCategory::CredentialsProviderSystem) + .or_insert(0.0) += (creds_count as f64 * 0.5).min(9.0); evidence.push(format!("Credentials provider ({} refs)", creds_count)); } } /// Analyze specific error types (Iteration 6 discoveries) - fn analyze_error_types(&self, scores: &mut HashMap, evidence: &mut Vec) { + fn analyze_error_types( + &self, + scores: &mut HashMap, + evidence: &mut Vec, + ) { // TypeError (1457 occurrences!) let type_error_count = self.code.matches("TypeError").count(); if type_error_count >= 3 { - *scores.entry(CodeCategory::TypeErrorHandler).or_insert(0.0) += (type_error_count as f64 * 0.5).min(10.0); + *scores.entry(CodeCategory::TypeErrorHandler).or_insert(0.0) += + (type_error_count as f64 * 0.5).min(10.0); evidence.push(format!("TypeError handler ({} errors)", type_error_count)); } // ParameterError (350 occurrences!) let param_error_count = self.code.matches("ParameterError").count(); if param_error_count >= 2 { - *scores.entry(CodeCategory::ParameterErrorHandler).or_insert(0.0) += (param_error_count as f64 * 0.7).min(9.0); - evidence.push(format!("ParameterError handler ({} errors)", param_error_count)); + *scores + .entry(CodeCategory::ParameterErrorHandler) + .or_insert(0.0) += (param_error_count as f64 * 0.7).min(9.0); + evidence.push(format!( + "ParameterError handler ({} errors)", + param_error_count + )); } // ProviderError (156 occurrences!) let provider_error_count = self.code.matches("ProviderError").count(); if provider_error_count >= 2 { - *scores.entry(CodeCategory::ProviderErrorHandler).or_insert(0.0) += (provider_error_count as f64 * 0.8).min(9.0); - evidence.push(format!("ProviderError handler ({} errors)", provider_error_count)); + *scores + .entry(CodeCategory::ProviderErrorHandler) + .or_insert(0.0) += (provider_error_count as f64 * 0.8).min(9.0); + evidence.push(format!( + "ProviderError handler ({} errors)", + provider_error_count + )); } // RangeError (144 occurrences!) let range_error_count = self.code.matches("RangeError").count(); if range_error_count >= 2 { - *scores.entry(CodeCategory::RangeErrorHandler).or_insert(0.0) += (range_error_count as f64 * 0.8).min(8.0); + *scores.entry(CodeCategory::RangeErrorHandler).or_insert(0.0) += + (range_error_count as f64 * 0.8).min(8.0); evidence.push(format!("RangeError handler ({} errors)", range_error_count)); } // ServiceException (112 occurrences!) let service_exc_count = self.code.matches("ServiceException").count(); if service_exc_count >= 2 { - *scores.entry(CodeCategory::ServiceExceptionHandler).or_insert(0.0) += (service_exc_count as f64 * 0.8).min(8.0); - evidence.push(format!("ServiceException handler ({} errors)", service_exc_count)); + *scores + .entry(CodeCategory::ServiceExceptionHandler) + .or_insert(0.0) += (service_exc_count as f64 * 0.8).min(8.0); + evidence.push(format!( + "ServiceException handler ({} errors)", + service_exc_count + )); } // Iteration 8: Additional error types @@ -790,112 +956,171 @@ impl BehaviorAnalyzer { // AbortError (88 occurrences!) let abort_error_count = self.code.matches("AbortError").count(); if abort_error_count >= 2 { - *scores.entry(CodeCategory::AbortErrorHandler).or_insert(0.0) += (abort_error_count as f64 * 0.8).min(8.0); + *scores.entry(CodeCategory::AbortErrorHandler).or_insert(0.0) += + (abort_error_count as f64 * 0.8).min(8.0); evidence.push(format!("AbortError handler ({} errors)", abort_error_count)); } // SyntaxError (82 occurrences!) let syntax_error_count = self.code.matches("SyntaxError").count(); if syntax_error_count >= 2 { - *scores.entry(CodeCategory::SyntaxErrorHandler).or_insert(0.0) += (syntax_error_count as f64 * 0.8).min(8.0); - evidence.push(format!("SyntaxError handler ({} errors)", syntax_error_count)); + *scores + .entry(CodeCategory::SyntaxErrorHandler) + .or_insert(0.0) += (syntax_error_count as f64 * 0.8).min(8.0); + evidence.push(format!( + "SyntaxError handler ({} errors)", + syntax_error_count + )); } // TimeoutError (76 occurrences!) let timeout_error_count = self.code.matches("TimeoutError").count(); if timeout_error_count >= 2 { - *scores.entry(CodeCategory::TimeoutErrorHandler).or_insert(0.0) += (timeout_error_count as f64 * 0.8).min(8.0); - evidence.push(format!("TimeoutError handler ({} errors)", timeout_error_count)); + *scores + .entry(CodeCategory::TimeoutErrorHandler) + .or_insert(0.0) += (timeout_error_count as f64 * 0.8).min(8.0); + evidence.push(format!( + "TimeoutError handler ({} errors)", + timeout_error_count + )); } // UnknownError (62 occurrences!) let unknown_error_count = self.code.matches("UnknownError").count(); if unknown_error_count >= 2 { - *scores.entry(CodeCategory::UnknownErrorHandler).or_insert(0.0) += (unknown_error_count as f64 * 0.7).min(7.0); - evidence.push(format!("UnknownError handler ({} errors)", unknown_error_count)); + *scores + .entry(CodeCategory::UnknownErrorHandler) + .or_insert(0.0) += (unknown_error_count as f64 * 0.7).min(7.0); + evidence.push(format!( + "UnknownError handler ({} errors)", + unknown_error_count + )); } // QueryError (62 occurrences!) let query_error_count = self.code.matches("QueryError").count(); if query_error_count >= 2 { - *scores.entry(CodeCategory::QueryErrorHandler).or_insert(0.0) += (query_error_count as f64 * 0.7).min(7.0); + *scores.entry(CodeCategory::QueryErrorHandler).or_insert(0.0) += + (query_error_count as f64 * 0.7).min(7.0); evidence.push(format!("QueryError handler ({} errors)", query_error_count)); } // ReferenceError (59 occurrences!) let ref_error_count = self.code.matches("ReferenceError").count(); if ref_error_count >= 2 { - *scores.entry(CodeCategory::ReferenceErrorHandler).or_insert(0.0) += (ref_error_count as f64 * 0.7).min(7.0); - evidence.push(format!("ReferenceError handler ({} errors)", ref_error_count)); + *scores + .entry(CodeCategory::ReferenceErrorHandler) + .or_insert(0.0) += (ref_error_count as f64 * 0.7).min(7.0); + evidence.push(format!( + "ReferenceError handler ({} errors)", + ref_error_count + )); } // ParseError (58 occurrences!) let parse_error_count = self.code.matches("ParseError").count(); if parse_error_count >= 2 { - *scores.entry(CodeCategory::ParseErrorHandler).or_insert(0.0) += (parse_error_count as f64 * 0.7).min(7.0); + *scores.entry(CodeCategory::ParseErrorHandler).or_insert(0.0) += + (parse_error_count as f64 * 0.7).min(7.0); evidence.push(format!("ParseError handler ({} errors)", parse_error_count)); } // ResponseError (54 occurrences!) let response_error_count = self.code.matches("ResponseError").count(); if response_error_count >= 2 { - *scores.entry(CodeCategory::ResponseErrorHandler).or_insert(0.0) += (response_error_count as f64 * 0.7).min(7.0); - evidence.push(format!("ResponseError handler ({} errors)", response_error_count)); + *scores + .entry(CodeCategory::ResponseErrorHandler) + .or_insert(0.0) += (response_error_count as f64 * 0.7).min(7.0); + evidence.push(format!( + "ResponseError handler ({} errors)", + response_error_count + )); } // RequestError (54 occurrences!) let request_error_count = self.code.matches("RequestError").count(); if request_error_count >= 2 { - *scores.entry(CodeCategory::RequestErrorHandler).or_insert(0.0) += (request_error_count as f64 * 0.7).min(7.0); - evidence.push(format!("RequestError handler ({} errors)", request_error_count)); + *scores + .entry(CodeCategory::RequestErrorHandler) + .or_insert(0.0) += (request_error_count as f64 * 0.7).min(7.0); + evidence.push(format!( + "RequestError handler ({} errors)", + request_error_count + )); } // InternalError (54 occurrences!) let internal_error_count = self.code.matches("InternalError").count(); if internal_error_count >= 2 { - *scores.entry(CodeCategory::InternalErrorHandler).or_insert(0.0) += (internal_error_count as f64 * 0.7).min(7.0); - evidence.push(format!("InternalError handler ({} errors)", internal_error_count)); + *scores + .entry(CodeCategory::InternalErrorHandler) + .or_insert(0.0) += (internal_error_count as f64 * 0.7).min(7.0); + evidence.push(format!( + "InternalError handler ({} errors)", + internal_error_count + )); } // TokenException (48 occurrences!) let token_exc_count = self.code.matches("TokenException").count(); if token_exc_count >= 2 { - *scores.entry(CodeCategory::TokenExceptionHandler).or_insert(0.0) += (token_exc_count as f64 * 0.7).min(7.0); - evidence.push(format!("TokenException handler ({} errors)", token_exc_count)); + *scores + .entry(CodeCategory::TokenExceptionHandler) + .or_insert(0.0) += (token_exc_count as f64 * 0.7).min(7.0); + evidence.push(format!( + "TokenException handler ({} errors)", + token_exc_count + )); } // ServerException (44 occurrences!) let server_exc_count = self.code.matches("ServerException").count(); if server_exc_count >= 2 { - *scores.entry(CodeCategory::ServerExceptionHandler).or_insert(0.0) += (server_exc_count as f64 * 0.7).min(7.0); - evidence.push(format!("ServerException handler ({} errors)", server_exc_count)); + *scores + .entry(CodeCategory::ServerExceptionHandler) + .or_insert(0.0) += (server_exc_count as f64 * 0.7).min(7.0); + evidence.push(format!( + "ServerException handler ({} errors)", + server_exc_count + )); } // AxiosError (44 occurrences!) let axios_error_count = self.code.matches("AxiosError").count(); if axios_error_count >= 2 { - *scores.entry(CodeCategory::AxiosErrorHandler).or_insert(0.0) += (axios_error_count as f64 * 0.7).min(7.0); + *scores.entry(CodeCategory::AxiosErrorHandler).or_insert(0.0) += + (axios_error_count as f64 * 0.7).min(7.0); evidence.push(format!("AxiosError handler ({} errors)", axios_error_count)); } // ParserError (42 occurrences!) let parser_error_count = self.code.matches("ParserError").count(); if parser_error_count >= 2 { - *scores.entry(CodeCategory::ParserErrorHandler).or_insert(0.0) += (parser_error_count as f64 * 0.7).min(7.0); - evidence.push(format!("ParserError handler ({} errors)", parser_error_count)); + *scores + .entry(CodeCategory::ParserErrorHandler) + .or_insert(0.0) += (parser_error_count as f64 * 0.7).min(7.0); + evidence.push(format!( + "ParserError handler ({} errors)", + parser_error_count + )); } // AuthError (42 occurrences!) let auth_error_count = self.code.matches("AuthError").count(); if auth_error_count >= 2 { - *scores.entry(CodeCategory::AuthErrorHandler).or_insert(0.0) += (auth_error_count as f64 * 0.7).min(7.0); + *scores.entry(CodeCategory::AuthErrorHandler).or_insert(0.0) += + (auth_error_count as f64 * 0.7).min(7.0); evidence.push(format!("AuthError handler ({} errors)", auth_error_count)); } } /// ITERATION 13: Detect content-based patterns that defeat filename obfuscation /// These patterns prioritize code content over misleading source map names - fn analyze_content_based_categories(&self, scores: &mut HashMap, evidence: &mut Vec) { + fn analyze_content_based_categories( + &self, + scores: &mut HashMap, + evidence: &mut Vec, + ) { // 1. Elliptic Curve Cryptography (HmacDRBG, EC, KeyPair, Signature) let has_hmac_drbg = self.code.contains("HmacDRBG"); let has_keypair = self.code.contains("KeyPair") || self.code.contains("keyPair"); @@ -913,8 +1138,13 @@ impl BehaviorAnalyzer { if has_hmac_drbg && has_keypair && has_signature { crypto_score += 0.4; } - *scores.entry(CodeCategory::EllipticCurveCrypto).or_insert(0.0) += crypto_score; - evidence.push(format!("Elliptic Curve Cryptography (score: {:.1})", crypto_score)); + *scores + .entry(CodeCategory::EllipticCurveCrypto) + .or_insert(0.0) += crypto_score; + evidence.push(format!( + "Elliptic Curve Cryptography (score: {:.1})", + crypto_score + )); } // 2. Node Error Factory (createErrorType, NodeError, codes[]) @@ -930,7 +1160,10 @@ impl BehaviorAnalyzer { error_factory_score += 0.5; } *scores.entry(CodeCategory::NodeErrorFactory).or_insert(0.0) += error_factory_score; - evidence.push(format!("Node.js Error Factory (score: {:.1})", error_factory_score)); + evidence.push(format!( + "Node.js Error Factory (score: {:.1})", + error_factory_score + )); } // 3. Bitwise Crypto Operations (ushrn, bitLength, nh) @@ -946,13 +1179,17 @@ impl BehaviorAnalyzer { // 4. JavaScript Syntax Highlighter (JSX, className, keyword highlighting) let has_jsx_tags = self.code.contains("begin:\"<>\"") || self.code.contains("end:\"\""); - let has_classname = self.code.contains("className:\"number\"") || self.code.contains("className:\"string\""); + let has_classname = self.code.contains("className:\"number\"") + || self.code.contains("className:\"string\""); let has_keyword_literal = self.code.contains("keyword:") && self.code.contains("literal:"); let has_builtin = self.code.contains("built_in:"); let has_uri_encode = self.code.contains("decodeURI") && self.code.contains("encodeURI"); - if (has_jsx_tags && has_classname) || (has_keyword_literal && has_builtin) || has_uri_encode { - *scores.entry(CodeCategory::JavaScriptSyntaxHighlighter).or_insert(0.0) += 10.0; + if (has_jsx_tags && has_classname) || (has_keyword_literal && has_builtin) || has_uri_encode + { + *scores + .entry(CodeCategory::JavaScriptSyntaxHighlighter) + .or_insert(0.0) += 10.0; evidence.push("JavaScript Syntax Highlighter (JSX, keywords, className)".to_string()); } @@ -961,25 +1198,35 @@ impl BehaviorAnalyzer { let has_get_internal_ranges = self.code.contains("getInternalModuleRanges"); let has_display_name_fiber = self.code.contains("getDisplayNameForFiber"); let has_is_profiling = self.code.contains("getIsProfiling"); - let has_profiler_version = self.code.contains("--profiler-v") || self.code.contains("--react-version"); + let has_profiler_version = + self.code.contains("--profiler-v") || self.code.contains("--react-version"); - if has_devtools_hook || (has_display_name_fiber && has_is_profiling) || has_profiler_version { + if has_devtools_hook || (has_display_name_fiber && has_is_profiling) || has_profiler_version + { let mut devtools_score = 10.0; // __REACT_DEVTOOLS_GLOBAL_HOOK__ is EXTREMELY unique if has_devtools_hook { devtools_score += 0.7; } - *scores.entry(CodeCategory::ReactDevToolsProfiler).or_insert(0.0) += devtools_score; - evidence.push(format!("React DevTools Profiler (score: {:.1})", devtools_score)); + *scores + .entry(CodeCategory::ReactDevToolsProfiler) + .or_insert(0.0) += devtools_score; + evidence.push(format!( + "React DevTools Profiler (score: {:.1})", + devtools_score + )); } // 6. API Error Handler (API-specific error patterns) - let api_error_patterns = self.code.matches("api_error").count() + - self.code.matches("ApiError").count() + - self.code.matches("API_ERROR").count(); + let api_error_patterns = self.code.matches("api_error").count() + + self.code.matches("ApiError").count() + + self.code.matches("API_ERROR").count(); if api_error_patterns >= 3 { *scores.entry(CodeCategory::APIErrorHandler).or_insert(0.0) += 8.0; - evidence.push(format!("API Error Handler ({} patterns)", api_error_patterns)); + evidence.push(format!( + "API Error Handler ({} patterns)", + api_error_patterns + )); } // 7. Error Recovery System (recovery strategies, retry logic) @@ -988,25 +1235,31 @@ impl BehaviorAnalyzer { let has_fallback = self.code.contains("fallback") || self.code.contains("Fallback"); if (has_recovery && has_retry) || (has_recovery && has_fallback) { - *scores.entry(CodeCategory::ErrorRecoverySystem).or_insert(0.0) += 8.0; + *scores + .entry(CodeCategory::ErrorRecoverySystem) + .or_insert(0.0) += 8.0; evidence.push("Error Recovery System (recovery, retry, fallback)".to_string()); } // 8. Fallback Error Handler let fallback_count = self.code.matches("fallback").count(); if fallback_count >= 5 { - *scores.entry(CodeCategory::FallbackErrorHandler).or_insert(0.0) += 8.0; + *scores + .entry(CodeCategory::FallbackErrorHandler) + .or_insert(0.0) += 8.0; evidence.push(format!("Fallback Error Handler ({} refs)", fallback_count)); } // 9. Promise Error Handler (unhandledRejection, promise rejection) let has_unhandled_rejection = self.code.contains("unhandledRejection"); - let has_promise_reject = self.code.contains("PromiseRejection") || - self.code.contains("promise.reject"); + let has_promise_reject = + self.code.contains("PromiseRejection") || self.code.contains("promise.reject"); let promise_error_count = self.code.matches("promiseError").count(); if has_unhandled_rejection || has_promise_reject || promise_error_count >= 3 { - *scores.entry(CodeCategory::PromiseErrorHandler).or_insert(0.0) += 8.0; + *scores + .entry(CodeCategory::PromiseErrorHandler) + .or_insert(0.0) += 8.0; evidence.push("Promise Error Handler (unhandledRejection)".to_string()); } @@ -1033,9 +1286,9 @@ impl BehaviorAnalyzer { // 12. Sync File I/O (sync file operations) let sync_ops = self.code.matches("Sync").count(); - let has_fs_sync = self.code.contains("readFileSync") || - self.code.contains("writeFileSync") || - self.code.contains("existsSync"); + let has_fs_sync = self.code.contains("readFileSync") + || self.code.contains("writeFileSync") + || self.code.contains("existsSync"); if has_fs_sync && sync_ops >= 3 { *scores.entry(CodeCategory::SyncFileIO).or_insert(0.0) += 8.0; @@ -1045,7 +1298,8 @@ impl BehaviorAnalyzer { // 13. Image Processor (image handling) let has_image = self.code.matches("image").count() + self.code.matches("Image").count(); let has_canvas = self.code.contains("canvas") || self.code.contains("Canvas"); - let has_png_jpg = self.code.contains("png") || self.code.contains("jpg") || self.code.contains("jpeg"); + let has_png_jpg = + self.code.contains("png") || self.code.contains("jpg") || self.code.contains("jpeg"); if has_image >= 5 || (has_canvas && has_png_jpg) { *scores.entry(CodeCategory::ImageProcessor).or_insert(0.0) += 7.0; @@ -1061,7 +1315,10 @@ impl BehaviorAnalyzer { let has_locale_options = self.code.contains("locale?.options"); let has_set_hours_zero = self.code.contains("setHours(0,0,0,0)"); - if (has_week_starts_on && has_get_full_year) || (has_date_utc && has_set_hours_zero) || has_locale_options { + if (has_week_starts_on && has_get_full_year) + || (has_date_utc && has_set_hours_zero) + || has_locale_options + { *scores.entry(CodeCategory::DateFnsLibrary).or_insert(0.0) += 10.0; evidence.push("Date-fns Library (weekStartsOn, locale-aware dates)".to_string()); } @@ -1071,7 +1328,8 @@ impl BehaviorAnalyzer { let has_trailing = self.code.contains("trailing"); let has_max_wait = self.code.contains("maxWait"); let has_clear_timeout = self.code.contains("clearTimeout"); - let debounce_count = self.code.matches("debounce").count() + self.code.matches("throttle").count(); + let debounce_count = + self.code.matches("debounce").count() + self.code.matches("throttle").count(); if (has_leading && has_trailing && has_max_wait) || debounce_count >= 3 { *scores.entry(CodeCategory::DebounceThrottle).or_insert(0.0) += 10.0; @@ -1079,8 +1337,10 @@ impl BehaviorAnalyzer { } // 16. JSON Tokenizer (brace/bracket matching for parsing) - let has_type_brace = self.code.contains("type:\"brace\"") || self.code.contains("type:'brace'"); - let has_type_paren = self.code.contains("type:\"paren\"") || self.code.contains("type:'paren'"); + let has_type_brace = + self.code.contains("type:\"brace\"") || self.code.contains("type:'brace'"); + let has_type_paren = + self.code.contains("type:\"paren\"") || self.code.contains("type:'paren'"); let has_json_parse = self.code.contains("JSON.parse"); let bracket_count = self.code.matches("{type:").count(); @@ -1091,12 +1351,15 @@ impl BehaviorAnalyzer { // 17. Startup Profiler (performance timing and reporting) let has_startup_time = self.code.contains("startup") && self.code.contains("startTime"); - let has_perf_marks = self.code.contains("getEntriesByType(\"mark\")") || - self.code.contains("getEntriesByType('mark')"); + let has_perf_marks = self.code.contains("getEntriesByType(\"mark\")") + || self.code.contains("getEntriesByType('mark')"); let has_profiling = self.code.contains("profiling") || self.code.contains("Performance"); let has_startup_perf = self.code.contains("startup-perf"); - if has_startup_perf || (has_startup_time && has_perf_marks) || (has_perf_marks && has_profiling) { + if has_startup_perf + || (has_startup_time && has_perf_marks) + || (has_perf_marks && has_profiling) + { let mut profiler_score = 10.0; // "startup-perf" path is very specific if has_startup_perf { @@ -1107,38 +1370,41 @@ impl BehaviorAnalyzer { } // 18. Object Inspector (property introspection and serialization) - let has_get_own_property = self.code.contains("getOwnPropertyDescriptor") || - self.code.contains("getOwnPropertyNames"); + let has_get_own_property = self.code.contains("getOwnPropertyDescriptor") + || self.code.contains("getOwnPropertyNames"); let has_proto_check = self.code.contains("__proto__"); - let has_constructor_name = self.code.contains("constructor?.name") || - self.code.contains("constructor.name"); + let has_constructor_name = + self.code.contains("constructor?.name") || self.code.contains("constructor.name"); let has_to_iso_string = self.code.contains("toISOString()"); - if (has_get_own_property && has_proto_check) || - (has_constructor_name && has_to_iso_string && has_proto_check) { + if (has_get_own_property && has_proto_check) + || (has_constructor_name && has_to_iso_string && has_proto_check) + { *scores.entry(CodeCategory::ObjectInspector).or_insert(0.0) += 10.0; evidence.push("Object Inspector (property introspection, __proto__)".to_string()); } // 19. Elm Syntax Highlighter (Elm language highlighting) - let has_infix_keywords = self.code.contains("infix infixl infixr") || - self.code.contains("infixl") || - self.code.contains("infixr"); + let has_infix_keywords = self.code.contains("infix infixl infixr") + || self.code.contains("infixl") + || self.code.contains("infixr"); let has_port_keyword = self.code.contains("port") && self.code.contains("keywords"); - let has_illegal_semicolon = self.code.contains("illegal:/;/") || - self.code.contains("illegal: /;/"); + let has_illegal_semicolon = + self.code.contains("illegal:/;/") || self.code.contains("illegal: /;/"); if (has_infix_keywords && has_port_keyword) || has_illegal_semicolon { let mut elm_score = 10.0; // Uniqueness bonus: illegal:/;/ is VERY Elm-specific if has_illegal_semicolon { - elm_score += 0.5; // Tie-breaker bonus + elm_score += 0.5; // Tie-breaker bonus } // Multiple signals bonus if has_infix_keywords && has_port_keyword && has_illegal_semicolon { - elm_score += 0.3; // Extra confidence + elm_score += 0.3; // Extra confidence } - *scores.entry(CodeCategory::ElmSyntaxHighlighter).or_insert(0.0) += elm_score; + *scores + .entry(CodeCategory::ElmSyntaxHighlighter) + .or_insert(0.0) += elm_score; evidence.push(format!("Elm Syntax Highlighter (score: {:.1})", elm_score)); } @@ -1151,14 +1417,18 @@ impl BehaviorAnalyzer { if object_type_count >= 5 || (has_object_array && has_object_function) || has_max_safe_int { *scores.entry(CodeCategory::LodashTypeChecker).or_insert(0.0) += 10.0; - evidence.push(format!("Lodash Type Checker ({} type tags)", object_type_count)); + evidence.push(format!( + "Lodash Type Checker ({} type tags)", + object_type_count + )); } // 21. RxJS Operators (.subscribe, createOperatorSubscriber, Observable) - let has_subscribe = self.code.contains(".subscribe(") || self.code.contains(".unsubscribe()"); + let has_subscribe = + self.code.contains(".subscribe(") || self.code.contains(".unsubscribe()"); let has_observable = self.code.contains("Observable") || self.code.contains("Subject"); - let has_operator = self.code.contains("createOperatorSubscriber") || - self.code.contains(".operate(function"); + let has_operator = self.code.contains("createOperatorSubscriber") + || self.code.contains(".operate(function"); let subscribe_count = self.code.matches("subscribe").count(); if (has_subscribe && has_observable) || subscribe_count >= 3 { @@ -1177,13 +1447,20 @@ impl BehaviorAnalyzer { // 22. OpenTelemetry Encoding (hrTimeToNanos, hexToBinary, encodeAsLongBits) let has_hex_to_binary = self.code.contains("hexToBinary"); - let has_hr_time = self.code.contains("hrTimeToNanos") || self.code.contains("encodeAsLongBits"); - let has_instrumentation = self.code.contains("createInstrumentationScope") || - self.code.contains("createResource"); - let has_to_any_value = self.code.contains("toAnyValue") || self.code.contains("toAttributes"); - let has_bigint_encoding = self.code.contains("BigInt.asUintN") && self.code.contains("low:") && self.code.contains("high:"); - - if (has_hex_to_binary && has_hr_time) || has_instrumentation || (has_bigint_encoding && has_to_any_value) { + let has_hr_time = + self.code.contains("hrTimeToNanos") || self.code.contains("encodeAsLongBits"); + let has_instrumentation = self.code.contains("createInstrumentationScope") + || self.code.contains("createResource"); + let has_to_any_value = + self.code.contains("toAnyValue") || self.code.contains("toAttributes"); + let has_bigint_encoding = self.code.contains("BigInt.asUintN") + && self.code.contains("low:") + && self.code.contains("high:"); + + if (has_hex_to_binary && has_hr_time) + || has_instrumentation + || (has_bigint_encoding && has_to_any_value) + { let mut otel_score = 10.0; // hrTimeToNanos is extremely specific to OpenTelemetry if has_hr_time { @@ -1193,25 +1470,42 @@ impl BehaviorAnalyzer { if has_hex_to_binary && has_hr_time && has_instrumentation { otel_score += 0.3; } - *scores.entry(CodeCategory::OpenTelemetryEncoding).or_insert(0.0) += otel_score; + *scores + .entry(CodeCategory::OpenTelemetryEncoding) + .or_insert(0.0) += otel_score; evidence.push(format!("OpenTelemetry Encoding (score: {:.1})", otel_score)); } // 23. Zlib Compression (Z_ constants, deflate/inflate) - let has_z_constants = self.code.contains("Z_MIN_WINDOWBITS") || - self.code.contains("Z_DEFAULT_CHUNK") || - self.code.contains("Z_MAX_LEVEL"); + let has_z_constants = self.code.contains("Z_MIN_WINDOWBITS") + || self.code.contains("Z_DEFAULT_CHUNK") + || self.code.contains("Z_MAX_LEVEL"); let has_deflate = self.code.contains("deflate") || self.code.contains("inflate"); let has_gzip = self.code.contains("gzip") || self.code.contains("gunzip"); // PRECISION FIX: Only count ACTUAL zlib constants, not just "Z_" anywhere let zlib_specific_constants = [ - "Z_NO_FLUSH", "Z_PARTIAL_FLUSH", "Z_SYNC_FLUSH", "Z_FULL_FLUSH", - "Z_FINISH", "Z_BLOCK", "Z_TREES", "Z_OK", "Z_STREAM_END", - "Z_NEED_DICT", "Z_ERRNO", "Z_STREAM_ERROR", "Z_DATA_ERROR", - "Z_BEST_SPEED", "Z_BEST_COMPRESSION", "Z_DEFAULT_COMPRESSION", - "Z_FILTERED", "Z_HUFFMAN_ONLY", "Z_DEFLATED" + "Z_NO_FLUSH", + "Z_PARTIAL_FLUSH", + "Z_SYNC_FLUSH", + "Z_FULL_FLUSH", + "Z_FINISH", + "Z_BLOCK", + "Z_TREES", + "Z_OK", + "Z_STREAM_END", + "Z_NEED_DICT", + "Z_ERRNO", + "Z_STREAM_ERROR", + "Z_DATA_ERROR", + "Z_BEST_SPEED", + "Z_BEST_COMPRESSION", + "Z_DEFAULT_COMPRESSION", + "Z_FILTERED", + "Z_HUFFMAN_ONLY", + "Z_DEFLATED", ]; - let z_count = zlib_specific_constants.iter() + let z_count = zlib_specific_constants + .iter() .filter(|c| self.code.contains(*c)) .count(); @@ -1234,9 +1528,9 @@ impl BehaviorAnalyzer { let has_winget = self.code.contains("winget") || self.code.contains("Microsoft/WinGet"); let has_npm_prefix = self.code.contains("npm config get prefix"); let has_exec_path = self.code.contains("process.execPath"); - let has_install_paths = self.code.contains("node_modules") || - self.code.contains("/opt/homebrew/") || - self.code.contains(".nvm/versions"); + let has_install_paths = self.code.contains("node_modules") + || self.code.contains("/opt/homebrew/") + || self.code.contains(".nvm/versions"); if (has_homebrew && has_exec_path) || (has_winget && has_exec_path) || has_npm_prefix { let mut install_score = 10.0; @@ -1248,18 +1542,24 @@ impl BehaviorAnalyzer { if has_install_paths && has_exec_path { install_score += 0.3; } - *scores.entry(CodeCategory::InstallationDetection).or_insert(0.0) += install_score; - evidence.push(format!("Installation Detection (score: {:.1})", install_score)); + *scores + .entry(CodeCategory::InstallationDetection) + .or_insert(0.0) += install_score; + evidence.push(format!( + "Installation Detection (score: {:.1})", + install_score + )); } // 25. Anthropic API Client (api.anthropic.com, CLAUDE_CODE_USE_BEDROCK) let has_api_anthropic = self.code.contains("api.anthropic.com"); - let has_bedrock = self.code.contains("CLAUDE_CODE_USE_BEDROCK") || - self.code.contains("CLAUDE_CODE_USE_VERTEX") || - self.code.contains("CLAUDE_CODE_USE_FOUNDRY"); - let has_session_logs = self.code.contains("Error fetching session logs") || - self.code.contains("session_get_fail_status"); - let has_connectivity = self.code.contains("isConnected") && self.code.contains("EHOSTUNREACH"); + let has_bedrock = self.code.contains("CLAUDE_CODE_USE_BEDROCK") + || self.code.contains("CLAUDE_CODE_USE_VERTEX") + || self.code.contains("CLAUDE_CODE_USE_FOUNDRY"); + let has_session_logs = self.code.contains("Error fetching session logs") + || self.code.contains("session_get_fail_status"); + let has_connectivity = + self.code.contains("isConnected") && self.code.contains("EHOSTUNREACH"); if has_api_anthropic || (has_bedrock && has_connectivity) { let mut api_score = 10.0; @@ -1271,26 +1571,29 @@ impl BehaviorAnalyzer { if has_bedrock && has_session_logs { api_score += 0.2; } - *scores.entry(CodeCategory::AnthropicAPIClient).or_insert(0.0) += api_score; + *scores + .entry(CodeCategory::AnthropicAPIClient) + .or_insert(0.0) += api_score; evidence.push(format!("Anthropic API Client (score: {:.1})", api_score)); } // 26. Lodash Core Library (heavy typeof checks, Object.keys, type coercion) let typeof_count = self.code.matches("typeof").count(); - let has_object_keys = self.code.contains("Object.keys") || - self.code.contains("Object.getOwnPropertySymbols"); - let has_value_of = self.code.contains("T.valueOf==") || - self.code.contains("$.valueOf()") || - self.code.contains("S.valueOf"); - let has_type_coercion = self.code.contains("T===0?T:0") || - self.code.contains("S===S?") || - self.code.contains("T===T?T:0"); + let has_object_keys = + self.code.contains("Object.keys") || self.code.contains("Object.getOwnPropertySymbols"); + let has_value_of = self.code.contains("T.valueOf==") + || self.code.contains("$.valueOf()") + || self.code.contains("S.valueOf"); + let has_type_coercion = self.code.contains("T===0?T:0") + || self.code.contains("S===S?") + || self.code.contains("T===T?T:0"); let minified_function_count = self.code.matches("function").count(); - let single_char_params = self.code.matches("(T,S)").count() + - self.code.matches("(T)").count(); + let single_char_params = + self.code.matches("(T,S)").count() + self.code.matches("(T)").count(); - if (typeof_count >= 5 && has_object_keys && has_type_coercion) || - (minified_function_count >= 10 && single_char_params >= 5) { + if (typeof_count >= 5 && has_object_keys && has_type_coercion) + || (minified_function_count >= 10 && single_char_params >= 5) + { let mut lodash_score = 10.0; // valueOf + type coercion is very Lodash-specific if has_value_of && has_type_coercion { @@ -1307,114 +1610,144 @@ impl BehaviorAnalyzer { // 27. Crypto Library Wrappers (Iteration 24: browserify-crypto, ASN.1, elliptic, hash libs) // These are thin wrappers that re-export crypto functions let has_crypto_require = self.code.contains("require(\"crypto\")"); - let has_crypto_exports = self.code.contains("createCipher") || - self.code.contains("createCipheriv") || - self.code.contains("createHash") || - self.code.contains("createHmac") || - self.code.contains("createSign") || - self.code.contains("createVerify") || - self.code.contains("DiffieHellman") || - self.code.contains("createECDH"); + let has_crypto_exports = self.code.contains("createCipher") + || self.code.contains("createCipheriv") + || self.code.contains("createHash") + || self.code.contains("createHmac") + || self.code.contains("createSign") + || self.code.contains("createVerify") + || self.code.contains("DiffieHellman") + || self.code.contains("createECDH"); // Browserify crypto wrappers - let has_browserify_crypto = (self.code.contains("browserify") || - self.code.contains("Browserify")) && - (self.code.contains("Cipher") || - self.code.contains("Hash") || - self.code.contains("Sign")); + let has_browserify_crypto = (self.code.contains("browserify") + || self.code.contains("Browserify")) + && (self.code.contains("Cipher") + || self.code.contains("Hash") + || self.code.contains("Sign")); // ASN.1 library structure (asn1.encoders, asn1.decoders, asn1.bignum) - let has_asn1 = self.code.contains("asn1.") && - (self.code.contains(".encoders") || - self.code.contains(".decoders") || - self.code.contains(".bignum") || - self.code.contains(".define")); + let has_asn1 = self.code.contains("asn1.") + && (self.code.contains(".encoders") + || self.code.contains(".decoders") + || self.code.contains(".bignum") + || self.code.contains(".define")); // Elliptic curve library entry point - let has_elliptic_lib = self.code.contains("elliptic.") && - (self.code.contains(".curve") || - self.code.contains(".ec") || - self.code.contains(".eddsa") || - self.code.contains(".utils")); + let has_elliptic_lib = self.code.contains("elliptic.") + && (self.code.contains(".curve") + || self.code.contains(".ec") + || self.code.contains(".eddsa") + || self.code.contains(".utils")); // Hash library entry point (hash.sha1, hash.sha256, hash.ripemd160) - let has_hash_lib = self.code.contains("hash.") && - (self.code.contains(".sha") || - self.code.contains(".ripemd") || - self.code.contains(".hmac")); + let has_hash_lib = self.code.contains("hash.") + && (self.code.contains(".sha") + || self.code.contains(".ripemd") + || self.code.contains(".hmac")); // OID mappings for crypto algorithms (2.16.840.1.101.3.4.1.X = aes-X) - let has_crypto_oids = self.code.contains("2.16.840.1.101") && - (self.code.contains("aes-") || - self.code.contains("des-") || - self.code.contains("rsa-")); + let has_crypto_oids = self.code.contains("2.16.840.1.101") + && (self.code.contains("aes-") + || self.code.contains("des-") + || self.code.contains("rsa-")); // Module re-export pattern: exports.X = crypto.X or exports.X = require_X() - let has_crypto_reexport = (self.code.contains("exports.") || - self.code.contains("module.exports")) && - (self.code.contains("Cipher") || - self.code.contains("Hash") || - self.code.contains("Hmac") || - self.code.contains("Sign") || - self.code.contains("Verify") || - self.code.contains("ECDH")); - - if (has_crypto_require && has_crypto_exports) || - has_browserify_crypto || - has_asn1 || - has_elliptic_lib || - has_hash_lib || - has_crypto_oids || - (has_crypto_reexport && has_crypto_exports) { + let has_crypto_reexport = (self.code.contains("exports.") + || self.code.contains("module.exports")) + && (self.code.contains("Cipher") + || self.code.contains("Hash") + || self.code.contains("Hmac") + || self.code.contains("Sign") + || self.code.contains("Verify") + || self.code.contains("ECDH")); + + if (has_crypto_require && has_crypto_exports) + || has_browserify_crypto + || has_asn1 + || has_elliptic_lib + || has_hash_lib + || has_crypto_oids + || (has_crypto_reexport && has_crypto_exports) + { let mut crypto_score = 10.0; // Uniqueness bonuses for highly specific patterns if has_asn1 || has_elliptic_lib || has_hash_lib { - crypto_score += 0.6; // Library entry points are very specific + crypto_score += 0.6; // Library entry points are very specific } if has_crypto_oids { - crypto_score += 0.5; // OID mappings are extremely unique + crypto_score += 0.5; // OID mappings are extremely unique } if has_browserify_crypto { - crypto_score += 0.4; // Browserify wrappers are specific + crypto_score += 0.4; // Browserify wrappers are specific } - *scores.entry(CodeCategory::CryptoLibraryWrappers).or_insert(0.0) += crypto_score; - evidence.push(format!("Crypto Library Wrappers (score: {:.1})", crypto_score)); + *scores + .entry(CodeCategory::CryptoLibraryWrappers) + .or_insert(0.0) += crypto_score; + evidence.push(format!( + "Crypto Library Wrappers (score: {:.1})", + crypto_score + )); } } /// Detect syntax highlighters and language parsers (HIGH PRIORITY - prevents false positives) - fn analyze_syntax_highlighter(&self, scores: &mut HashMap, evidence: &mut Vec) { + fn analyze_syntax_highlighter( + &self, + scores: &mut HashMap, + evidence: &mut Vec, + ) { let language_indicators = [ - ("name:", "keywords:", "aliases:"), // Syntax highlighter structure + ("name:", "keywords:", "aliases:"), // Syntax highlighter structure ("grammar", "tokenizer", "syntax"), ("lexer", "parser", "AST"), ("language:", "mode:", "syntax:"), ]; // Check for language definition patterns - let has_language_structure = language_indicators.iter() - .any(|(p1, p2, p3)| { - [*p1, *p2, *p3].iter() - .filter(|p| self.code.contains(**p)) - .count() >= 2 - }); + let has_language_structure = language_indicators.iter().any(|(p1, p2, p3)| { + [*p1, *p2, *p3] + .iter() + .filter(|p| self.code.contains(**p)) + .count() + >= 2 + }); // Check for specific language names in keyword lists let language_keywords = [ - "JavaScript", "TypeScript", "Python", "Ruby", "Java", "C++", - "XQuery", "XPath", "XML", "SQL", "HTML", "CSS", "JSON", - "keyword:", "comment:", "string:", "number:", "operator:", + "JavaScript", + "TypeScript", + "Python", + "Ruby", + "Java", + "C++", + "XQuery", + "XPath", + "XML", + "SQL", + "HTML", + "CSS", + "JSON", + "keyword:", + "comment:", + "string:", + "number:", + "operator:", ]; - let keyword_count = language_keywords.iter() + let keyword_count = language_keywords + .iter() .filter(|k| self.code.contains(*k)) .count(); if has_language_structure && keyword_count >= 3 { - *scores.entry(CodeCategory::SyntaxHighlighter).or_insert(0.0) += 3.0; // High score to override others - evidence.push(format!("Syntax highlighter (detected {} language keywords)", keyword_count)); + *scores.entry(CodeCategory::SyntaxHighlighter).or_insert(0.0) += 3.0; // High score to override others + evidence.push(format!( + "Syntax highlighter (detected {} language keywords)", + keyword_count + )); } else if has_language_structure { *scores.entry(CodeCategory::LanguageParser).or_insert(0.0) += 2.5; evidence.push("Language parser structure".to_string()); @@ -1432,18 +1765,30 @@ impl BehaviorAnalyzer { } /// Detect state machines (multiple conditional branches on state) - fn analyze_state_machine(&self, scores: &mut HashMap, evidence: &mut Vec) { + fn analyze_state_machine( + &self, + scores: &mut HashMap, + evidence: &mut Vec, + ) { // Pattern: if (state === X) ... else if (state === Y) ... else ... - let state_pattern = Regex::new(r#"if\s*\([^)]*\.(state|mode|status|phase)\s*===?\s*["']([^"']+)["']\)"#).unwrap(); + let state_pattern = + Regex::new(r#"if\s*\([^)]*\.(state|mode|status|phase)\s*===?\s*["']([^"']+)["']\)"#) + .unwrap(); let matches: Vec<_> = state_pattern.captures_iter(&self.code).collect(); if matches.len() >= 3 { - let states: Vec = matches.iter() + let states: Vec = matches + .iter() .filter_map(|m| m.get(2).map(|s| s.as_str().to_string())) .collect(); - *scores.entry(CodeCategory::StateManagement).or_insert(0.0) += matches.len() as f64 * 0.3; - evidence.push(format!("State machine with {} states: {:?}", states.len(), states)); + *scores.entry(CodeCategory::StateManagement).or_insert(0.0) += + matches.len() as f64 * 0.3; + evidence.push(format!( + "State machine with {} states: {:?}", + states.len(), + states + )); } // Pattern: switch(type) with multiple cases @@ -1451,26 +1796,40 @@ impl BehaviorAnalyzer { let case_pattern = Regex::new(r#"case\s+["']([^"']+)["']:"#).unwrap(); if switch_pattern.is_match(&self.code) { - let cases: Vec<_> = case_pattern.captures_iter(&self.code) + let cases: Vec<_> = case_pattern + .captures_iter(&self.code) .filter_map(|m| m.get(1).map(|s| s.as_str().to_string())) .collect(); if cases.len() >= 3 { - *scores.entry(CodeCategory::EventHandling).or_insert(0.0) += cases.len() as f64 * 0.4; + *scores.entry(CodeCategory::EventHandling).or_insert(0.0) += + cases.len() as f64 * 0.4; evidence.push(format!("Event handler with {} event types", cases.len())); } } } /// Detect validation logic (error types, schema checks) - fn analyze_validation(&self, scores: &mut HashMap, evidence: &mut Vec) { + fn analyze_validation( + &self, + scores: &mut HashMap, + evidence: &mut Vec, + ) { let validation_errors = [ - "invalid_type", "invalid_value", "too_small", "too_big", - "invalid_format", "invalid_email", "invalid_url", - "required", "optional", "nullable", + "invalid_type", + "invalid_value", + "too_small", + "too_big", + "invalid_format", + "invalid_email", + "invalid_url", + "required", + "optional", + "nullable", ]; - let matches = validation_errors.iter() + let matches = validation_errors + .iter() .filter(|e| self.code.contains(*e)) .count(); @@ -1480,76 +1839,116 @@ impl BehaviorAnalyzer { } // Detect zod/yup/joi patterns - if self.code.contains("z.object") || self.code.contains("yup.object") || self.code.contains("Joi.object") { + if self.code.contains("z.object") + || self.code.contains("yup.object") + || self.code.contains("Joi.object") + { *scores.entry(CodeCategory::DataValidation).or_insert(0.0) += 1.0; evidence.push("Schema validator (zod/yup/joi)".to_string()); } } /// Detect authentication flows - now detects DOMAIN-SPECIFIC patterns - fn analyze_authentication(&self, scores: &mut HashMap, evidence: &mut Vec) { + fn analyze_authentication( + &self, + scores: &mut HashMap, + evidence: &mut Vec, + ) { // AWS IAM/Cognito patterns - if (self.code.contains("AccessKeyId") || self.code.contains("SecretAccessKey")) && - (self.code.contains("SessionToken") || self.code.contains("credential")) { - *scores.entry(CodeCategory::AWSCredentialProvider).or_insert(0.0) += 2.0; - evidence.push("AWS IAM Credentials (AccessKeyId + SecretAccessKey + SessionToken)".to_string()); - } - if self.code.contains("Cognito") && (self.code.contains("Identity") || self.code.contains("Pool")) { - *scores.entry(CodeCategory::AWSCredentialProvider).or_insert(0.0) += 1.5; + if (self.code.contains("AccessKeyId") || self.code.contains("SecretAccessKey")) + && (self.code.contains("SessionToken") || self.code.contains("credential")) + { + *scores + .entry(CodeCategory::AWSCredentialProvider) + .or_insert(0.0) += 2.0; + evidence.push( + "AWS IAM Credentials (AccessKeyId + SecretAccessKey + SessionToken)".to_string(), + ); + } + if self.code.contains("Cognito") + && (self.code.contains("Identity") || self.code.contains("Pool")) + { + *scores + .entry(CodeCategory::AWSCredentialProvider) + .or_insert(0.0) += 1.5; evidence.push("AWS Cognito Identity".to_string()); } // API Key authentication - if (self.code.contains("apiKey") || self.code.contains("api_key")) && - (self.code.contains("header") || self.code.contains("Authorization")) { - *scores.entry(CodeCategory::AWSCredentialProvider).or_insert(0.0) += 1.5; + if (self.code.contains("apiKey") || self.code.contains("api_key")) + && (self.code.contains("header") || self.code.contains("Authorization")) + { + *scores + .entry(CodeCategory::AWSCredentialProvider) + .or_insert(0.0) += 1.5; evidence.push("API Key Authentication".to_string()); } // OAuth flows if self.code.contains("OAuth") && self.code.contains("callback") { - *scores.entry(CodeCategory::AWSCredentialProvider).or_insert(0.0) += 2.0; + *scores + .entry(CodeCategory::AWSCredentialProvider) + .or_insert(0.0) += 2.0; evidence.push("OAuth Callback Flow".to_string()); } else if self.code.contains("OAuth") { - *scores.entry(CodeCategory::AWSCredentialProvider).or_insert(0.0) += 1.2; + *scores + .entry(CodeCategory::AWSCredentialProvider) + .or_insert(0.0) += 1.2; evidence.push("OAuth Flow".to_string()); } // JWT patterns if self.code.contains("JWT") && self.code.contains("refresh") { - *scores.entry(CodeCategory::AWSCredentialProvider).or_insert(0.0) += 1.8; + *scores + .entry(CodeCategory::AWSCredentialProvider) + .or_insert(0.0) += 1.8; evidence.push("JWT Refresh Token".to_string()); } else if self.code.contains("JWT") || self.code.contains("jsonwebtoken") { - *scores.entry(CodeCategory::AWSCredentialProvider).or_insert(0.0) += 1.2; + *scores + .entry(CodeCategory::AWSCredentialProvider) + .or_insert(0.0) += 1.2; evidence.push("JWT Token".to_string()); } // Session-based auth if self.code.contains("session") && self.code.contains("cookie") { - *scores.entry(CodeCategory::AWSCredentialProvider).or_insert(0.0) += 1.5; + *scores + .entry(CodeCategory::AWSCredentialProvider) + .or_insert(0.0) += 1.5; evidence.push("Session Cookie Authentication".to_string()); } // SAML if self.code.contains("SAML") { - *scores.entry(CodeCategory::AWSCredentialProvider).or_insert(0.0) += 1.5; + *scores + .entry(CodeCategory::AWSCredentialProvider) + .or_insert(0.0) += 1.5; evidence.push("SAML Authentication".to_string()); } // MFA/2FA - if (self.code.contains("MFA") || self.code.contains("2FA") || self.code.contains("twoFactor")) && - (self.code.contains("verify") || self.code.contains("code")) { - *scores.entry(CodeCategory::AWSCredentialProvider).or_insert(0.0) += 1.5; + if (self.code.contains("MFA") + || self.code.contains("2FA") + || self.code.contains("twoFactor")) + && (self.code.contains("verify") || self.code.contains("code")) + { + *scores + .entry(CodeCategory::AWSCredentialProvider) + .or_insert(0.0) += 1.5; evidence.push("Multi-Factor Authentication".to_string()); } // Social login patterns if self.code.contains("google") && self.code.contains("sign") { - *scores.entry(CodeCategory::AWSCredentialProvider).or_insert(0.0) += 1.0; + *scores + .entry(CodeCategory::AWSCredentialProvider) + .or_insert(0.0) += 1.0; evidence.push("Google Sign-In".to_string()); } if self.code.contains("facebook") && self.code.contains("login") { - *scores.entry(CodeCategory::AWSCredentialProvider).or_insert(0.0) += 1.0; + *scores + .entry(CodeCategory::AWSCredentialProvider) + .or_insert(0.0) += 1.0; evidence.push("Facebook Login".to_string()); } @@ -1561,19 +1960,26 @@ impl BehaviorAnalyzer { ]; for (p1, p2, p3) in &auth_patterns { - let count = [p1, p2, p3].iter() + let count = [p1, p2, p3] + .iter() .filter(|p| self.code.to_lowercase().contains(&p.to_lowercase())) .count(); if count >= 2 { - *scores.entry(CodeCategory::AWSCredentialProvider).or_insert(0.0) += count as f64 * 0.4; + *scores + .entry(CodeCategory::AWSCredentialProvider) + .or_insert(0.0) += count as f64 * 0.4; evidence.push(format!("Generic auth pattern: {}/{}/{}", p1, p2, p3)); } } } /// Detect error handling patterns - fn analyze_error_handling(&self, scores: &mut HashMap, evidence: &mut Vec) { + fn analyze_error_handling( + &self, + scores: &mut HashMap, + evidence: &mut Vec, + ) { // ITERATION 25 PRECISION FIX: Require multiple signals to avoid false positives // Found: 87% false positive rate with old thresholds! // Files with incidental error throwing (React hooks, parsers) were misclassified @@ -1581,29 +1987,29 @@ impl BehaviorAnalyzer { let try_count = self.code.matches("try{").count(); let catch_count = self.code.matches("catch(").count(); let throw_count = self.code.matches("throw ").count(); - let error_new_count = self.code.matches("new Error(").count() + - self.code.matches("new TypeError(").count() + - self.code.matches("new RangeError(").count(); + let error_new_count = self.code.matches("new Error(").count() + + self.code.matches("new TypeError(").count() + + self.code.matches("new RangeError(").count(); // Signal 1: Substantial error handling (try/catch blocks) - let has_error_handling = try_count >= 4 && catch_count >= 4; // Raised from 2 to 4 + let has_error_handling = try_count >= 4 && catch_count >= 4; // Raised from 2 to 4 // Signal 2: Substantial error throwing (indicates error generation, not just control flow) - let has_error_throwing = throw_count >= 8; // Raised from 3 to 8 + let has_error_throwing = throw_count >= 8; // Raised from 3 to 8 // Signal 3: Error recovery patterns - let has_error_recovery = self.code.contains("retry") || - self.code.contains("fallback") || - self.code.contains("recover"); + let has_error_recovery = self.code.contains("retry") + || self.code.contains("fallback") + || self.code.contains("recover"); // Signal 4: Error construction (creating custom errors) let has_error_construction = error_new_count >= 3; // Signal 5: Error handler function names - let has_error_handler_names = self.code.contains("handleError") || - self.code.contains("errorHandler") || - self.code.contains("onError") || - self.code.contains("catchError"); + let has_error_handler_names = self.code.contains("handleError") + || self.code.contains("errorHandler") + || self.code.contains("onError") + || self.code.contains("catchError"); // Count how many signals are present let signal_count = [ @@ -1612,7 +2018,10 @@ impl BehaviorAnalyzer { has_error_recovery, has_error_construction, has_error_handler_names, - ].iter().filter(|&&x| x).count(); + ] + .iter() + .filter(|&&x| x) + .count(); // REQUIRE at least 2 signals for error handling to be primary purpose if signal_count >= 2 { @@ -1629,13 +2038,16 @@ impl BehaviorAnalyzer { } if has_error_recovery { - error_score += 1.5; // Increased bonus for recovery patterns + error_score += 1.5; // Increased bonus for recovery patterns evidence.push("Error recovery (retry/fallback)".to_string()); } if has_error_construction { error_score += error_new_count as f64 * 0.4; - evidence.push(format!("Error construction: {} new Error()", error_new_count)); + evidence.push(format!( + "Error construction: {} new Error()", + error_new_count + )); } if has_error_handler_names { @@ -1654,10 +2066,15 @@ impl BehaviorAnalyzer { } /// Detect telemetry/analytics - fn analyze_telemetry(&self, scores: &mut HashMap, evidence: &mut Vec) { + fn analyze_telemetry( + &self, + scores: &mut HashMap, + evidence: &mut Vec, + ) { let telemetry_patterns = ["tengu_", "track", "recordEvent", "analytics", "metric"]; - let matches = telemetry_patterns.iter() + let matches = telemetry_patterns + .iter() .filter(|p| self.code.contains(*p)) .count(); @@ -1668,13 +2085,23 @@ impl BehaviorAnalyzer { } /// Detect permission/access control - fn analyze_permissions(&self, scores: &mut HashMap, evidence: &mut Vec) { + fn analyze_permissions( + &self, + scores: &mut HashMap, + evidence: &mut Vec, + ) { let permission_keywords = [ - "MUST NOT", "read-only", "permission", "access control", - "allowedPaths", "forbidden", "authorized", + "MUST NOT", + "read-only", + "permission", + "access control", + "allowedPaths", + "forbidden", + "authorized", ]; - let matches = permission_keywords.iter() + let matches = permission_keywords + .iter() .filter(|k| self.code.contains(*k)) .count(); @@ -1685,13 +2112,24 @@ impl BehaviorAnalyzer { } /// Detect protocol handling (headers, encoding, parsing) - fn analyze_protocol(&self, scores: &mut HashMap, evidence: &mut Vec) { + fn analyze_protocol( + &self, + scores: &mut HashMap, + evidence: &mut Vec, + ) { let protocol_patterns = [ - "Claude-", "X-", "Content-Type", "Authorization", - "header", "encode", "decode", "parse", + "Claude-", + "X-", + "Content-Type", + "Authorization", + "header", + "encode", + "decode", + "parse", ]; - let matches = protocol_patterns.iter() + let matches = protocol_patterns + .iter() .filter(|p| self.code.contains(*p)) .count(); @@ -1710,7 +2148,10 @@ impl BehaviorAnalyzer { "AWS IAM Credential Provider".to_string() } else if evidence.iter().any(|e| e.contains("AWS Cognito")) { "AWS Cognito Identity Provider".to_string() - } else if evidence.iter().any(|e| e.contains("API Key Authentication")) { + } else if evidence + .iter() + .any(|e| e.contains("API Key Authentication")) + { "API Key Auth Handler".to_string() } else if evidence.iter().any(|e| e.contains("OAuth Callback")) { "OAuth Callback Handler".to_string() @@ -1735,7 +2176,7 @@ impl BehaviorAnalyzer { } else { "Auth Flow Controller".to_string() } - }, + } CodeCategory::StateManagement => { let state_context = self.extract_state_context(); if state_context.contains("plan") || state_context.contains("Plan") { @@ -1757,25 +2198,31 @@ impl BehaviorAnalyzer { } else { format!("State Machine ({})", state_context) } - }, + } CodeCategory::EventHandling => { let event_types = self.extract_event_types(); if event_types.iter().any(|s| s == "file" || s == "directory") { "FileSystem Event Dispatcher".to_string() - } else if event_types.iter().any(|s| s == "click" || s == "mouse" || s == "keyboard") { + } else if event_types + .iter() + .any(|s| s == "click" || s == "mouse" || s == "keyboard") + { "UI Interaction Handler".to_string() } else if event_types.iter().any(|s| s == "message" || s == "data") { "Message Event Router".to_string() } else if event_types.iter().any(|s| s == "tool" || s == "function") { "Tool Event Orchestrator".to_string() - } else if event_types.iter().any(|s| s.contains("error") || s.contains("fail")) { + } else if event_types + .iter() + .any(|s| s.contains("error") || s.contains("fail")) + { "Error Event Handler".to_string() } else if !event_types.is_empty() { format!("Event Handler ({})", event_types.join(", ")) } else { "Generic Event Dispatcher".to_string() } - }, + } CodeCategory::DataValidation => { let validation_targets = self.extract_validation_targets(); if validation_targets.iter().any(|s| s == "email") { @@ -1788,7 +2235,9 @@ impl BehaviorAnalyzer { "Password Strength Validator".to_string() } else if validation_targets.iter().any(|s| s == "phone") { "Phone Number Validator".to_string() - } else if validation_targets.iter().any(|s| s == "schema") && self.code.contains("z.object") { + } else if validation_targets.iter().any(|s| s == "schema") + && self.code.contains("z.object") + { "Zod Schema Validator".to_string() } else if validation_targets.len() > 3 { "Multi-Field Input Validator".to_string() @@ -1797,7 +2246,7 @@ impl BehaviorAnalyzer { } else { "Input Validator".to_string() } - }, + } CodeCategory::ErrorHandler => { if self.code.contains("retry") && self.code.contains("exponential") { "Exponential Backoff Retry Handler".to_string() @@ -1812,7 +2261,7 @@ impl BehaviorAnalyzer { } else { "Error Handler".to_string() } - }, + } CodeCategory::TelemetryRecorder => { if evidence.iter().any(|e| e.contains("tengu_")) { "Telemetry Event Recorder".to_string() @@ -1825,7 +2274,7 @@ impl BehaviorAnalyzer { } else { "Analytics Tracker".to_string() } - }, + } CodeCategory::PermissionControl => { if self.code.contains("plan") || self.code.contains("Plan") { "Plan Mode Permission Enforcer".to_string() @@ -1838,7 +2287,7 @@ impl BehaviorAnalyzer { } else { "Access Control Manager".to_string() } - }, + } CodeCategory::ProtocolHandler => { if self.code.contains("Claude-") { "Claude Protocol Header Parser".to_string() @@ -1851,7 +2300,7 @@ impl BehaviorAnalyzer { } else { "Protocol Handler".to_string() } - }, + } CodeCategory::WorkflowOrchestration => { if self.code.contains("step") && self.code.contains("pipeline") { "Pipeline Orchestrator".to_string() @@ -1864,7 +2313,7 @@ impl BehaviorAnalyzer { } else { "Workflow Orchestrator".to_string() } - }, + } CodeCategory::ApiClient => { if self.code.contains("fetch") && self.code.contains("retry") { "Resilient API Client".to_string() @@ -1877,7 +2326,7 @@ impl BehaviorAnalyzer { } else { "API Client".to_string() } - }, + } CodeCategory::MessageRouter => { if self.code.contains("RabbitMQ") || self.code.contains("AMQP") { "RabbitMQ Message Router".to_string() @@ -1890,7 +2339,7 @@ impl BehaviorAnalyzer { } else { "Message Router".to_string() } - }, + } CodeCategory::ResourceManagement => { if self.code.contains("pool") && self.code.contains("connection") { "Connection Pool Manager".to_string() @@ -1903,7 +2352,7 @@ impl BehaviorAnalyzer { } else { "Resource Manager".to_string() } - }, + } CodeCategory::ConfigurationLoader => { if self.code.contains(".env") || self.code.contains("dotenv") { "Environment Config Loader".to_string() @@ -1916,7 +2365,7 @@ impl BehaviorAnalyzer { } else { "Configuration Loader".to_string() } - }, + } CodeCategory::LoggingSystem => { if self.code.contains("winston") { "Winston Logger".to_string() @@ -1929,7 +2378,7 @@ impl BehaviorAnalyzer { } else { "Logging System".to_string() } - }, + } CodeCategory::CacheManager => { if self.code.contains("Redis") { "Redis Cache Manager".to_string() @@ -1942,7 +2391,7 @@ impl BehaviorAnalyzer { } else { "Cache Manager".to_string() } - }, + } CodeCategory::SyntaxHighlighter => { // Detect specific language if self.code.contains("XQuery") || self.code.contains("XPath") { @@ -1960,7 +2409,7 @@ impl BehaviorAnalyzer { } else { "Syntax Highlighter".to_string() } - }, + } CodeCategory::LanguageParser => { if self.code.contains("AST") { "AST Parser".to_string() @@ -1969,7 +2418,7 @@ impl BehaviorAnalyzer { } else { "Language Parser".to_string() } - }, + } CodeCategory::ReactComponent => "React Component".to_string(), CodeCategory::ReactHooks => "React Hooks".to_string(), CodeCategory::ReactEventHandler => "React Event Handler".to_string(), @@ -2096,7 +2545,9 @@ impl BehaviorAnalyzer { CodeCategory::EllipticCurveCrypto => "Elliptic Curve Cryptography".to_string(), CodeCategory::NodeErrorFactory => "Node.js Error Factory".to_string(), CodeCategory::BitwiseCryptoOps => "Bitwise Crypto Operations".to_string(), - CodeCategory::JavaScriptSyntaxHighlighter => "JavaScript Syntax Highlighter".to_string(), + CodeCategory::JavaScriptSyntaxHighlighter => { + "JavaScript Syntax Highlighter".to_string() + } CodeCategory::ReactDevToolsProfiler => "React DevTools Profiler".to_string(), CodeCategory::SyncFileIO => "Synchronous File I/O".to_string(), CodeCategory::ImageProcessor => "Image Processor".to_string(), @@ -2142,356 +2593,473 @@ impl BehaviorAnalyzer { let path = match profile.category { CodeCategory::AWSCredentialProvider => { format!("src/auth/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::ErrorHandler => { format!("src/errors/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::DataValidation => { format!("src/validation/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::StateManagement => { format!("src/state/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::EventHandling => { format!("src/events/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::TelemetryRecorder => { format!("src/telemetry/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::PermissionControl => { format!("src/permissions/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::ProtocolHandler => { format!("src/protocol/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::SyntaxHighlighter => { format!("src/languages/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::LanguageParser => { format!("src/parsers/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::WorkflowOrchestration => { format!("src/workflows/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::ApiClient => { format!("src/api/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::MessageRouter => { format!("src/messaging/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::ResourceManagement => { format!("src/resources/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::ConfigurationLoader => { format!("src/config/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::LoggingSystem => { format!("src/logging/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::CacheManager => { format!("src/cache/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::SandboxManager => { format!("src/sandbox/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::APIKeyVault => { format!("src/security/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::IDEConnector => { format!("src/integrations/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::CommandInjectionGuard => { format!("src/security/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::AgentTelemetryRecorder => { format!("src/telemetry/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::APIMonitoringDashboard => { format!("src/monitoring/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::FeedbackCollectionSystem => { format!("src/feedback/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::SearchAnalytics => { format!("src/analytics/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::KeyboardShortcutManager => { format!("src/keyboard/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::CommandPaletteHandler => { format!("src/commands/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::ModalDialogController => { format!("src/ui/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::SentryIntegration => { format!("src/monitoring/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::BashSecurityMonitor => { format!("src/security/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::ProxyManager => { format!("src/network/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::TelemetryController => { format!("src/telemetry/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::TokenBudgetManager => { format!("src/resources/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::RetryPolicy => { format!("src/resilience/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::ToolConcurrencyLimiter => { format!("src/concurrency/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::PromptSuggestionEngine => { format!("src/ai/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::SDKCheckpointing => { format!("src/state/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::GitHubIntegrationTracker => { format!("src/integrations/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::PlanModeAnalytics => { format!("src/analytics/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::MCPOperationTracker => { format!("src/mcp/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::ToolUseMonitor => { format!("src/monitoring/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::VersionLockTracker => { format!("src/versioning/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::OAuthFlowTracker => { format!("src/auth/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::TreeSitterLoader => { format!("src/parsers/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } // Iteration 5-6 new categories CodeCategory::InputComponentLibrary => { - format!("src/components/input/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/components/input/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::SelectComponentLibrary => { - format!("src/components/select/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/components/select/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::FormComponentLibrary => { - format!("src/components/form/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/components/form/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::TabNavigationSystem => { - format!("src/components/tabs/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/components/tabs/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::ProgressIndicatorSystem => { - format!("src/components/progress/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/components/progress/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::AlertNotificationSystem => { - format!("src/components/alert/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/components/alert/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::ButtonComponentLibrary => { - format!("src/components/button/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/components/button/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::DialogComponentLibrary => { - format!("src/components/dialog/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/components/dialog/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::MenuComponentLibrary => { - format!("src/components/menu/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/components/menu/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::ActionDispatcher => { format!("src/state/actions/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::StateSelector => { - format!("src/state/selectors/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/state/selectors/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::StoreManagerCore => { format!("src/state/store/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::HTTPRequestManager => { format!("src/network/http/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::HTTPResponseHandler => { - format!("src/network/response/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/network/response/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::EndpointRegistry => { - format!("src/network/endpoints/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/network/endpoints/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::APIClientLibrary => { format!("src/network/api/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::FetchAPIWrapper => { format!("src/network/fetch/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::DiffViewerComponent => { format!("src/editor/diff/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::MergeConflictResolver => { format!("src/editor/merge/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::CompactOperationManager => { - format!("src/editor/compact/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/editor/compact/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::TeleportNavigator => { - format!("src/editor/teleport/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/editor/teleport/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::PlaneServiceCoordinator => { - format!("src/services/plane/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/services/plane/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::CredentialsProviderSystem => { - format!("src/services/credentials/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/services/credentials/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::TypeErrorHandler => { format!("src/errors/type/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::ParameterErrorHandler => { - format!("src/errors/parameter/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/errors/parameter/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::ProviderErrorHandler => { - format!("src/errors/provider/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/errors/provider/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::RangeErrorHandler => { format!("src/errors/range/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::ServiceExceptionHandler => { - format!("src/errors/service/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/errors/service/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::AbortErrorHandler => { format!("src/errors/abort/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::SyntaxErrorHandler => { format!("src/errors/syntax/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::TimeoutErrorHandler => { - format!("src/errors/timeout/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/errors/timeout/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::UnknownErrorHandler => { - format!("src/errors/unknown/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/errors/unknown/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::QueryErrorHandler => { format!("src/errors/query/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::ReferenceErrorHandler => { - format!("src/errors/reference/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/errors/reference/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::ParseErrorHandler => { format!("src/errors/parse/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::ResponseErrorHandler => { - format!("src/errors/response/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/errors/response/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::RequestErrorHandler => { - format!("src/errors/request/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/errors/request/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::InternalErrorHandler => { - format!("src/errors/internal/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/errors/internal/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::TokenExceptionHandler => { format!("src/errors/token/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::ServerExceptionHandler => { format!("src/errors/server/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::AxiosErrorHandler => { format!("src/errors/axios/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::ParserErrorHandler => { format!("src/errors/parser/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::AuthErrorHandler => { format!("src/errors/auth/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } // Iteration 13: Content-based categories (defeats filename obfuscation) CodeCategory::EllipticCurveCrypto => { - format!("src/crypto/elliptic_curve/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/crypto/elliptic_curve/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::NodeErrorFactory => { format!("src/errors/node/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::BitwiseCryptoOps => { - format!("src/crypto/bitwise/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/crypto/bitwise/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::JavaScriptSyntaxHighlighter => { - format!("src/languages/javascript/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/languages/javascript/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::ReactDevToolsProfiler => { - format!("src/devtools/react/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/devtools/react/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::SyncFileIO => { format!("src/fs/sync/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::ImageProcessor => { format!("src/media/images/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::RegexEngine => { - format!("src/validation/regex/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/validation/regex/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::TimestampManager => { - format!("src/time/timestamps/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/time/timestamps/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::APIErrorHandler => { format!("src/errors/api/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::ErrorRecoverySystem => { - format!("src/errors/recovery/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/errors/recovery/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::FallbackErrorHandler => { - format!("src/errors/fallback/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/errors/fallback/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::PromiseErrorHandler => { format!("src/async/errors/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } // Iteration 14: Additional obfuscated utilities CodeCategory::DateFnsLibrary => { format!("src/time/datefns/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::DebounceThrottle => { format!("src/utils/timers/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::JSONTokenizer => { format!("src/parsers/json/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::StartupProfiler => { - format!("src/profiling/startup/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/profiling/startup/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::ObjectInspector => { format!("src/introspection/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::ElmSyntaxHighlighter => { format!("src/languages/elm/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::LodashTypeChecker => { format!("src/utils/lodash/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } // Iteration 20: Deep obfuscation path mappings CodeCategory::RxJSOperators => { format!("src/reactive/rxjs/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::OpenTelemetryEncoding => { - format!("src/telemetry/opentelemetry/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/telemetry/opentelemetry/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::ZlibCompression => { - format!("src/compression/zlib/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/compression/zlib/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::InstallationDetection => { - format!("src/installer/detection/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/installer/detection/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } CodeCategory::AnthropicAPIClient => { format!("src/api/anthropic/{}.ts.AI", self.slugify(&profile.purpose)) - }, + } CodeCategory::LodashCoreLibrary => { - format!("src/libraries/lodash/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/libraries/lodash/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } // Iteration 24: Crypto library wrappers CodeCategory::CryptoLibraryWrappers => { - format!("src/crypto/wrappers/{}.ts.AI", self.slugify(&profile.purpose)) - }, + format!( + "src/crypto/wrappers/{}.ts.AI", + self.slugify(&profile.purpose) + ) + } _ => return None, }; @@ -2509,8 +3077,10 @@ impl BehaviorAnalyzer { /// Extract specific state names from state machine code fn extract_state_context(&self) -> String { - let state_pattern = Regex::new(r#"(state|mode|status|phase)\s*===?\s*["']([^"']+)["']"#).unwrap(); - let states: Vec = state_pattern.captures_iter(&self.code) + let state_pattern = + Regex::new(r#"(state|mode|status|phase)\s*===?\s*["']([^"']+)["']"#).unwrap(); + let states: Vec = state_pattern + .captures_iter(&self.code) .filter_map(|m| m.get(2).map(|s| s.as_str().to_string())) .take(3) .collect(); @@ -2525,7 +3095,8 @@ impl BehaviorAnalyzer { /// Extract event type names from switch/case statements fn extract_event_types(&self) -> Vec { let case_pattern = Regex::new(r#"case\s+["']([a-z_]+)["']:"#).unwrap(); - case_pattern.captures_iter(&self.code) + case_pattern + .captures_iter(&self.code) .filter_map(|m| m.get(1).map(|s| s.as_str().to_string())) .filter(|s| !s.contains("invalid") && !s.contains("error")) .take(3) @@ -2573,7 +3144,8 @@ mod tests { if (state.mode === "executing") return executeMode(); if (state.mode === "reviewing") return reviewMode(); } - "#.to_string(); + "# + .to_string(); let analyzer = BehaviorAnalyzer::new(code); let profile = analyzer.analyze(); @@ -2590,7 +3162,8 @@ mod tests { age: z.number().min(0).max(150), }); // Returns: invalid_type, invalid_format, too_small, too_big - "#.to_string(); + "# + .to_string(); let analyzer = BehaviorAnalyzer::new(code); let profile = analyzer.analyze(); diff --git a/src/deobfuscator.rs b/src/deobfuscator.rs index e320134..59689ac 100644 --- a/src/deobfuscator.rs +++ b/src/deobfuscator.rs @@ -1,8 +1,8 @@ +use regex::Regex; /// JavaScript Deobfuscator - Rename variables using heuristics use std::collections::{HashMap, HashSet}; use std::fs; use std::path::Path; -use regex::Regex; /// Variable usage context for determining meaningful names #[derive(Debug, Clone)] @@ -112,7 +112,10 @@ impl JSDeobfuscator { let result = self.apply_renames(); if self.verbose { - println!("[+] Deobfuscation complete! Renamed {} variables", self.renamed_vars.len()); + println!( + "[+] Deobfuscation complete! Renamed {} variables", + self.renamed_vars.len() + ); } result } @@ -122,13 +125,13 @@ impl JSDeobfuscator { // Find all single-letter, dollar sign, and short minified variable names // Matches: a, x, H, $, a1, x2, H0, _a, _H, GaD (3 chars with caps) let patterns = vec![ - r"\b([a-z])\b", // Single lowercase: a, x - r"\b([A-Z])\b", // Single uppercase: H, A, L - r"\$", // Dollar sign: $ - r"\b([a-z][0-9])\b", // Lowercase + digit: a1, x2 - r"\b([A-Z][0-9])\b", // Uppercase + digit: H0, A1 - r"\b(_[a-zA-Z])\b", // Underscore + letter: _a, _H - r"\b([A-Z][a-z]+[A-Z][a-z]*)\b", // CamelCase minified: GaD, KaD, UaD + r"\b([a-z])\b", // Single lowercase: a, x + r"\b([A-Z])\b", // Single uppercase: H, A, L + r"\$", // Dollar sign: $ + r"\b([a-z][0-9])\b", // Lowercase + digit: a1, x2 + r"\b([A-Z][0-9])\b", // Uppercase + digit: H0, A1 + r"\b(_[a-zA-Z])\b", // Underscore + letter: _a, _H + r"\b([A-Z][a-z]+[A-Z][a-z]*)\b", // CamelCase minified: GaD, KaD, UaD ]; for pattern in patterns { @@ -183,7 +186,10 @@ impl JSDeobfuscator { ctx.has_query_selector = true; ctx.is_dom_element = true; } - if self.code.contains(&format!("{}.addEventListener(", var_name)) { + if self + .code + .contains(&format!("{}.addEventListener(", var_name)) + { ctx.has_add_event_listener = true; ctx.is_dom_element = true; } @@ -198,20 +204,23 @@ impl JSDeobfuscator { if self.code.contains(&format!("return {}", var_name)) { ctx.used_in_return = true; } - if self.code.contains(&format!("if({})", var_name)) || - self.code.contains(&format!("if ({})", var_name)) { + if self.code.contains(&format!("if({})", var_name)) + || self.code.contains(&format!("if ({})", var_name)) + { ctx.used_in_condition = true; } // Check for error patterns - if self.code.contains(&format!("catch({})", var_name)) || - self.code.contains(&format!("catch ({})", var_name)) { + if self.code.contains(&format!("catch({})", var_name)) + || self.code.contains(&format!("catch ({})", var_name)) + { ctx.is_error = true; } // Check for event patterns - if self.code.contains(&format!("function({}", var_name)) && - (self.code.contains("event") || self.code.contains("Event")) { + if self.code.contains(&format!("function({}", var_name)) + && (self.code.contains("event") || self.code.contains("Event")) + { ctx.is_event = true; } @@ -233,7 +242,8 @@ impl JSDeobfuscator { } // Check if it's a callback (function passed as argument) - let callback_re = Regex::new(&format!(r"\w+\([^)]*function\s*\([^)]*{}\s*\)", var_name)).unwrap(); + let callback_re = + Regex::new(&format!(r"\w+\([^)]*function\s*\([^)]*{}\s*\)", var_name)).unwrap(); if callback_re.is_match(&self.code) { ctx.is_callback = true; } @@ -350,13 +360,27 @@ impl JSDeobfuscator { fn get_context_hints(&self, ctx: &VariableContext) -> String { let mut hints = Vec::new(); - if ctx.is_array_like { hints.push("array"); } - if ctx.is_promise { hints.push("promise"); } - if ctx.is_dom_element { hints.push("DOM"); } - if ctx.is_error { hints.push("error"); } - if ctx.is_callback { hints.push("callback"); } - if ctx.has_map_call { hints.push("mapped"); } - if ctx.has_then_call { hints.push("async"); } + if ctx.is_array_like { + hints.push("array"); + } + if ctx.is_promise { + hints.push("promise"); + } + if ctx.is_dom_element { + hints.push("DOM"); + } + if ctx.is_error { + hints.push("error"); + } + if ctx.is_callback { + hints.push("callback"); + } + if ctx.has_map_call { + hints.push("mapped"); + } + if ctx.has_then_call { + hints.push("async"); + } if hints.is_empty() { "inferred".to_string() @@ -379,7 +403,8 @@ mod tests { a.map(x => x * 2); return a; } - "#.to_string(); + "# + .to_string(); let mut deobf = JSDeobfuscator::new(code); deobf.deobfuscate(); @@ -393,7 +418,8 @@ mod tests { let code = r#" var p = fetch(url); p.then(r => r.json()); - "#.to_string(); + "# + .to_string(); let mut deobf = JSDeobfuscator::new(code); deobf.deobfuscate(); diff --git a/src/main.rs b/src/main.rs index 8bc69fa..0a6a47e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,6 +27,7 @@ mod viral_insights; mod work_hours_analyzer; mod workflow_correlation; // mod infographics; // Temporarily disabled due to compilation errors +mod ascii_charts; mod cache; mod claude_config; mod daemon; @@ -38,16 +39,15 @@ mod extractors; mod git_infographics; mod html_report; mod llm_chat; +mod proxy; mod report_analyzer; +mod search; mod timeline; mod timeline_png; -mod tui; -mod ultra_deep; -mod ascii_charts; -mod search; mod traffic; -mod proxy; +mod tui; mod tui_traffic; +mod ultra_deep; use analysis::Analyzer; use backup::BackupManager; @@ -2320,12 +2320,28 @@ async fn main() -> Result<()> { use chrono::Utc; use colored::Colorize; - println!("\n{}", "═══════════════════════════════════════════════════════════════".cyan()); - println!("{}", " 🎨 vibedev ASCII Charts Demo - Beautiful Terminal Visualizations".cyan().bold()); - println!("{}", "═══════════════════════════════════════════════════════════════".cyan()); + println!( + "\n{}", + "═══════════════════════════════════════════════════════════════".cyan() + ); + println!( + "{}", + " 🎨 vibedev ASCII Charts Demo - Beautiful Terminal Visualizations" + .cyan() + .bold() + ); + println!( + "{}", + "═══════════════════════════════════════════════════════════════".cyan() + ); // 1. Activity Heatmap (GitHub-style) - println!("\n{}", "━━━ Activity Heatmap (GitHub-style Contribution Graph) ━━━".yellow().bold()); + println!( + "\n{}", + "━━━ Activity Heatmap (GitHub-style Contribution Graph) ━━━" + .yellow() + .bold() + ); let mut heatmap = ActivityHeatmap::new().with_weeks(52); // Simulate activity data for week in 40..52 { @@ -2357,10 +2373,7 @@ async fn main() -> Result<()> { ("Current streak", "19 days"), ("Longest streak", "20 days"), ]); - stats_card.add_row(vec![ - ("Active days", "52/74"), - ("Peak hour", "8:00-9:00"), - ]); + stats_card.add_row(vec![("Active days", "52/74"), ("Peak hour", "8:00-9:00")]); print!("{}", stats_card.render()); // 3. Fun Fact @@ -2395,12 +2408,16 @@ async fn main() -> Result<()> { print!("{}", bar_chart.render()); // 7. Histogram - println!("\n{}", "━━━ Histogram (Session Duration) ━━━".yellow().bold()); + println!( + "\n{}", + "━━━ Histogram (Session Duration) ━━━".yellow().bold() + ); let session_durations: Vec = vec![ - 0.5, 1.0, 1.2, 1.5, 2.0, 2.1, 2.3, 2.5, 2.8, 3.0, - 3.2, 3.5, 4.0, 4.5, 5.0, 6.0, 7.0, 8.0, 10.0, 12.0, + 0.5, 1.0, 1.2, 1.5, 2.0, 2.1, 2.3, 2.5, 2.8, 3.0, 3.2, 3.5, 4.0, 4.5, 5.0, 6.0, + 7.0, 8.0, 10.0, 12.0, ]; - let histogram = Histogram::from_values("Session Duration (hours)", &session_durations, 6); + let histogram = + Histogram::from_values("Session Duration (hours)", &session_durations, 6); print!("{}", histogram.render()); // 8. Leaderboard @@ -2415,9 +2432,18 @@ async fn main() -> Result<()> { // 9. Progress Bars println!("\n{}", "━━━ Progress Bars (Goals) ━━━".yellow().bold()); - println!("{}", ProgressBar::new("Daily token goal", 45_000.0, 50_000.0).render()); - println!("{}", ProgressBar::new("Weekly sessions", 28.0, 35.0).render()); - println!("{}", ProgressBar::new("Monthly commits", 156.0, 100.0).render()); + println!( + "{}", + ProgressBar::new("Daily token goal", 45_000.0, 50_000.0).render() + ); + println!( + "{}", + ProgressBar::new("Weekly sessions", 28.0, 35.0).render() + ); + println!( + "{}", + ProgressBar::new("Monthly commits", 156.0, 100.0).render() + ); // 10. Calendar View println!("\n{}", "━━━ Calendar View ━━━".yellow().bold()); @@ -2445,8 +2471,9 @@ async fn main() -> Result<()> { // 12. Comparison Chart println!("\n{}", "━━━ Comparison Chart ━━━".yellow().bold()); - let comparison = ComparisonChart::new("Input tokens", 5_800_000.0, "Output tokens", 40_300_000.0) - .with_unit("tokens"); + let comparison = + ComparisonChart::new("Input tokens", 5_800_000.0, "Output tokens", 40_300_000.0) + .with_unit("tokens"); print!("{}", comparison.render()); // 13. Sparkline @@ -2478,9 +2505,15 @@ async fn main() -> Result<()> { // NEW VISUALIZATIONS - Part 2 // ═══════════════════════════════════════════════════════════════ - println!("\n{}", "═══════════════════════════════════════════════════════════════".magenta()); + println!( + "\n{}", + "═══════════════════════════════════════════════════════════════".magenta() + ); println!("{}", " 🎯 NEW VISUALIZATIONS".magenta().bold()); - println!("{}", "═══════════════════════════════════════════════════════════════".magenta()); + println!( + "{}", + "═══════════════════════════════════════════════════════════════".magenta() + ); // 15. Gauge println!("\n{}", "━━━ Gauge ━━━".yellow().bold()); @@ -2510,10 +2543,13 @@ async fn main() -> Result<()> { print!("{}", funnel.render()); // 19. Box Plot - println!("\n{}", "━━━ Box Plot (Session Duration) ━━━".yellow().bold()); + println!( + "\n{}", + "━━━ Box Plot (Session Duration) ━━━".yellow().bold() + ); let session_data: Vec = vec![ - 0.5, 1.0, 1.2, 1.5, 2.0, 2.1, 2.3, 2.5, 2.8, 3.0, - 3.2, 3.5, 4.0, 4.5, 5.0, 6.0, 7.0, 8.0, 10.0, 12.0, + 0.5, 1.0, 1.2, 1.5, 2.0, 2.1, 2.3, 2.5, 2.8, 3.0, 3.2, 3.5, 4.0, 4.5, 5.0, 6.0, + 7.0, 8.0, 10.0, 12.0, ]; let boxplot = BoxPlot::new("Session Duration (hrs)", &session_data); print!("{}", boxplot.render()); @@ -2589,9 +2625,18 @@ async fn main() -> Result<()> { print!("{}", card1.render()); print!("{}", card2.render()); - println!("\n{}", "═══════════════════════════════════════════════════════════════".cyan()); - println!("{}", " 🧠 CREATIVE INSIGHT VISUALIZATIONS".magenta().bold()); - println!("{}", "═══════════════════════════════════════════════════════════════".cyan()); + println!( + "\n{}", + "═══════════════════════════════════════════════════════════════".cyan() + ); + println!( + "{}", + " 🧠 CREATIVE INSIGHT VISUALIZATIONS".magenta().bold() + ); + println!( + "{}", + "═══════════════════════════════════════════════════════════════".cyan() + ); // 27. Mood Ring println!("\n{}", "━━━ Mood Ring ━━━".yellow().bold()); @@ -2603,9 +2648,8 @@ async fn main() -> Result<()> { println!("\n{}", "━━━ Code Pulse (ECG) ━━━".yellow().bold()); let mut pulse = ascii_charts::CodePulse::new("Coding Activity Rhythm"); pulse.set_data(vec![ - 0.2, 0.3, 0.8, 0.4, 0.2, 0.1, 0.3, 0.9, 0.5, 0.2, - 0.1, 0.4, 0.7, 0.3, 0.2, 0.5, 0.95, 0.6, 0.3, 0.1, - 0.2, 0.3, 0.6, 0.85, 0.4, 0.2, 0.1, 0.3, 0.7, 0.4, + 0.2, 0.3, 0.8, 0.4, 0.2, 0.1, 0.3, 0.9, 0.5, 0.2, 0.1, 0.4, 0.7, 0.3, 0.2, 0.5, + 0.95, 0.6, 0.3, 0.1, 0.2, 0.3, 0.6, 0.85, 0.4, 0.2, 0.1, 0.3, 0.7, 0.4, ]); print!("{}", pulse.render()); @@ -2707,13 +2751,13 @@ async fn main() -> Result<()> { "Jan 02", "ASCII charts added", "Implemented 26 beautiful visualization types. Major feature addition!", - "feature" + "feature", ); story.add_event( "Jan 08", "Flow state achieved", "4-hour deep work session. Highest productivity day so far.", - "achievement" + "achievement", ); print!("{}", story.render()); @@ -2746,9 +2790,15 @@ async fn main() -> Result<()> { flow.add_period("3:20 PM", 80.0, true); print!("{}", flow.render()); - println!("\n{}", "═══════════════════════════════════════════════════════════════".cyan()); + println!( + "\n{}", + "═══════════════════════════════════════════════════════════════".cyan() + ); println!("{}", " 🎮 GAMIFIED VISUALIZATIONS".magenta().bold()); - println!("{}", "═══════════════════════════════════════════════════════════════".cyan()); + println!( + "{}", + "═══════════════════════════════════════════════════════════════".cyan() + ); // 37. Achievement Badges println!("\n{}", "━━━ Achievement Badges ━━━".yellow().bold()); @@ -2764,32 +2814,37 @@ async fn main() -> Result<()> { // 38. Skill Tree println!("\n{}", "━━━ Skill Tree ━━━".yellow().bold()); let mut tree = ascii_charts::SkillTree::new("Technology Skill Tree"); - tree.add_branch("Frontend", vec![ - ("HTML/CSS", 5), - ("JavaScript", 4), - ("React", 4), - ("TypeScript", 3), - ]); - tree.add_branch("Backend", vec![ - ("Rust", 4), - ("Node.js", 3), - ("Databases", 3), - ("APIs", 4), - ]); - tree.add_branch("DevOps", vec![ - ("Git", 5), - ("Docker", 2), - ("CI/CD", 2), - ]); + tree.add_branch( + "Frontend", + vec![ + ("HTML/CSS", 5), + ("JavaScript", 4), + ("React", 4), + ("TypeScript", 3), + ], + ); + tree.add_branch( + "Backend", + vec![("Rust", 4), ("Node.js", 3), ("Databases", 3), ("APIs", 4)], + ); + tree.add_branch("DevOps", vec![("Git", 5), ("Docker", 2), ("CI/CD", 2)]); print!("{}", tree.render()); // 39. Commit Graph println!("\n{}", "━━━ Commit Graph ━━━".yellow().bold()); let mut commits = ascii_charts::CommitGraph::new("Recent Activity"); - commits.add_commit("Jan 08", "feat: Add 10 creative insight visualizations", 1020); + commits.add_commit( + "Jan 08", + "feat: Add 10 creative insight visualizations", + 1020, + ); commits.add_commit("Jan 07", "fix: Resolve GanttChart padding bug", 45); commits.add_commit("Jan 06", "feat: Add 12 new ASCII visualization types", 920); - commits.add_commit("Jan 05", "refactor: Improve chart rendering performance", 230); + commits.add_commit( + "Jan 05", + "refactor: Improve chart rendering performance", + 230, + ); commits.add_commit("Jan 04", "docs: Update CLAUDE.md with new features", 150); print!("{}", commits.render()); @@ -2861,17 +2916,31 @@ async fn main() -> Result<()> { flame.set_history(vec![5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]); print!("{}", flame.render()); - println!("\n{}", "═══════════════════════════════════════════════════════════════".cyan()); - println!("{}", " 46 visualization types available! Use in TUI: 'vibedev tui'".dimmed()); - println!("{}", "═══════════════════════════════════════════════════════════════".cyan()); + println!( + "\n{}", + "═══════════════════════════════════════════════════════════════".cyan() + ); + println!( + "{}", + " 46 visualization types available! Use in TUI: 'vibedev tui'".dimmed() + ); + println!( + "{}", + "═══════════════════════════════════════════════════════════════".cyan() + ); Ok(()) } - Commands::Monitor { port, export, init_ca, setup } => { + Commands::Monitor { + port, + export, + init_ca, + setup, + } => { use proxy::{MitmProxy, ProxyConfig, ProxyEvent}; - use traffic::TrafficLog; use tokio::sync::mpsc; + use traffic::TrafficLog; if setup { proxy::print_setup_instructions(); @@ -2891,7 +2960,10 @@ async fn main() -> Result<()> { if init_ca { proxy.init_ca()?; - println!("CA certificate initialized at: {:?}", proxy::get_ca_cert_path()); + println!( + "CA certificate initialized at: {:?}", + proxy::get_ca_cert_path() + ); proxy::print_setup_instructions(); return Ok(()); } @@ -2905,9 +2977,7 @@ async fn main() -> Result<()> { println!("Or manually: export HTTPS_PROXY=http://127.0.0.1:{}", port); // Run proxy and TUI concurrently - let proxy_handle = tokio::spawn(async move { - proxy.run().await - }); + let proxy_handle = tokio::spawn(async move { proxy.run().await }); // Run TUI tui_traffic::run_traffic_monitor(traffic_log, event_rx).await?; @@ -2923,7 +2993,12 @@ async fn main() -> Result<()> { Ok(()) } - Commands::Patch { claude_path, restore, status, binary } => { + Commands::Patch { + claude_path, + restore, + status, + binary, + } => { use std::os::unix::fs::PermissionsExt; // Find Claude binary @@ -2982,7 +3057,7 @@ async fn main() -> Result<()> { if binary { // Binary patch: replace API URL directly in the binary const ORIGINAL_URL: &[u8] = b"https://api.anthropic.com"; - const PATCHED_URL: &[u8] = b"http://127.0.0.1:1338/api"; // Same length: 26 chars + const PATCHED_URL: &[u8] = b"http://127.0.0.1:1338/api"; // Same length: 26 chars if !real_binary.exists() { anyhow::bail!("Claude binary not found at {:?}", real_binary); @@ -2995,7 +3070,8 @@ async fn main() -> Result<()> { // Count occurrences let mut count = 0; let mut pos = 0; - while let Some(idx) = binary_data[pos..].windows(ORIGINAL_URL.len()) + while let Some(idx) = binary_data[pos..] + .windows(ORIGINAL_URL.len()) .position(|w| w == ORIGINAL_URL) { count += 1; @@ -3022,7 +3098,8 @@ async fn main() -> Result<()> { // Replace all occurrences let mut patched = 0; pos = 0; - while let Some(idx) = binary_data[pos..].windows(ORIGINAL_URL.len()) + while let Some(idx) = binary_data[pos..] + .windows(ORIGINAL_URL.len()) .position(|w| w == ORIGINAL_URL) { let abs_pos = pos + idx; @@ -3053,7 +3130,8 @@ async fn main() -> Result<()> { // Create wrapper script let ca_path = proxy::get_ca_cert_path(); - let wrapper_content = format!(r#"#!/bin/bash + let wrapper_content = format!( + r#"#!/bin/bash # Claude wrapper - routes through claudev monitor # Restore with: claudev patch --restore @@ -3063,13 +3141,18 @@ export SSL_CERT_FILE={} export NODE_TLS_REJECT_UNAUTHORIZED=0 exec {} "$@" -"#, ca_path.display(), real_binary.display()); +"#, + ca_path.display(), + real_binary.display() + ); std::fs::write(&claude_bin, wrapper_content)?; std::fs::set_permissions(&claude_bin, std::fs::Permissions::from_mode(0o755))?; println!("Created wrapper script at {:?}", claude_bin); - println!("\nNow run 'claudev monitor' in another terminal, then use 'claude' as normal."); + println!( + "\nNow run 'claudev monitor' in another terminal, then use 'claude' as normal." + ); println!("To restore: claudev patch --restore"); Ok(()) diff --git a/src/proxy.rs b/src/proxy.rs index d1f1cc6..9e9ed6b 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -5,7 +5,9 @@ use crate::traffic::TrafficLog; use anyhow::Result; -use rcgen::{Certificate, CertificateParams, DistinguishedName, DnType, KeyPair, PKCS_ECDSA_P256_SHA256}; +use rcgen::{ + Certificate, CertificateParams, DistinguishedName, DnType, KeyPair, PKCS_ECDSA_P256_SHA256, +}; use std::fs; use std::net::SocketAddr; use std::path::PathBuf; @@ -16,10 +18,25 @@ use tokio::sync::mpsc; /// Event sent from proxy to TUI #[derive(Debug, Clone)] pub enum ProxyEvent { - RequestStarted { id: u64, model: String, stream: bool }, - RequestCompleted { id: u64, tokens_in: u64, tokens_out: u64, latency_ms: u64 }, - RequestFailed { id: u64, error: String }, - StreamChunk { id: u64, text: String }, + RequestStarted { + id: u64, + model: String, + stream: bool, + }, + RequestCompleted { + id: u64, + tokens_in: u64, + tokens_out: u64, + latency_ms: u64, + }, + RequestFailed { + id: u64, + error: String, + }, + StreamChunk { + id: u64, + text: String, + }, } /// MITM Proxy configuration @@ -171,10 +188,7 @@ async fn handle_connection( } /// Handle CONNECT method for HTTPS proxying -async fn handle_connect( - mut client_stream: TcpStream, - request: &str, -) -> Result<()> { +async fn handle_connect(mut client_stream: TcpStream, request: &str) -> Result<()> { // Parse CONNECT host:port let parts: Vec<&str> = request.split_whitespace().collect(); if parts.len() < 2 { @@ -183,7 +197,10 @@ async fn handle_connect( let host_port = parts[1]; let (host, port) = if let Some(idx) = host_port.rfind(':') { - (&host_port[..idx], host_port[idx + 1..].parse().unwrap_or(443)) + ( + &host_port[..idx], + host_port[idx + 1..].parse().unwrap_or(443), + ) } else { (host_port, 443) }; @@ -228,10 +245,7 @@ async fn tunnel_streams(mut client: TcpStream, mut server: TcpStream) -> Result< } /// Handle regular HTTP proxy request -async fn handle_http( - mut client_stream: TcpStream, - initial_data: &[u8], -) -> Result<()> { +async fn handle_http(mut client_stream: TcpStream, initial_data: &[u8]) -> Result<()> { // Parse the HTTP request let request_str = String::from_utf8_lossy(initial_data); let lines: Vec<&str> = request_str.lines().collect(); diff --git a/src/search/index_builder.rs b/src/search/index_builder.rs index b6af6f8..dc99108 100644 --- a/src/search/index_builder.rs +++ b/src/search/index_builder.rs @@ -10,8 +10,8 @@ use tantivy::schema::*; use tantivy::{doc, Index, IndexWriter}; use crate::discovery::LogDiscovery; -use crate::models::{DiscoveryFindings, LogLocation}; use crate::models::{format_bytes, LogType}; +use crate::models::{DiscoveryFindings, LogLocation}; use crate::parsers::claude::ClaudeParser; use crate::parsers::cline::ClineParser; use crate::parsers::cursor::CursorParser; @@ -192,9 +192,7 @@ impl IndexBuilder { let pb = ProgressBar::new(total_files as u64); pb.set_style( ProgressStyle::default_bar() - .template( - " [{bar:40.cyan/blue}] {pos}/{len} files ({msg}) ETA: {eta}", - ) + .template(" [{bar:40.cyan/blue}] {pos}/{len} files ({msg}) ETA: {eta}") .unwrap() .progress_chars("█▓▒░ "), ); @@ -225,15 +223,13 @@ impl IndexBuilder { for location in locations { // Try each parser - let parsed = parsers - .iter() - .find_map(|parser| { - if parser.can_parse(&location.path) { - parser.parse(&location.path).ok() - } else { - None - } - }); + let parsed = parsers.iter().find_map(|parser| { + if parser.can_parse(&location.path) { + parser.parse(&location.path).ok() + } else { + None + } + }); if let Some(parsed_log) = parsed { // Index entries in batches @@ -284,9 +280,7 @@ impl IndexBuilder { } // Commit index - writer - .commit() - .context("Failed to commit index")?; + writer.commit().context("Failed to commit index")?; let total_docs = doc_counter.load(Ordering::SeqCst); let index_size = self.get_index_size()?; @@ -302,9 +296,8 @@ impl IndexBuilder { /// Deletes documents by file paths (for incremental updates) fn delete_docs_by_paths(&mut self, locations: &[LogLocation]) -> Result<()> { - let mut writer: IndexWriter = self - .index - .writer(MEMORY_BUDGET_MB * 1_024 * 1_024)?; + let mut writer: IndexWriter = + self.index.writer(MEMORY_BUDGET_MB * 1_024 * 1_024)?; let file_path_field = self.schema.get_field(FIELD_FILE_PATH)?; diff --git a/src/search/metadata.rs b/src/search/metadata.rs index dd53218..49e184f 100644 --- a/src/search/metadata.rs +++ b/src/search/metadata.rs @@ -4,7 +4,7 @@ use md5::{Digest, Md5}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fs::{self, File}; -use std::io::{Read, BufReader}; +use std::io::{BufReader, Read}; use std::path::{Path, PathBuf}; use crate::models::LogLocation; @@ -82,9 +82,7 @@ impl IndexMetadata { /// Finds a location by path pub fn find_location(&self, path: &Path) -> Option<&LocationMetadata> { - self.indexed_locations - .iter() - .find(|loc| loc.path == path) + self.indexed_locations.iter().find(|loc| loc.path == path) } /// Updates or adds a location @@ -121,16 +119,14 @@ impl LocationMetadata { /// Creates metadata from a LogLocation pub fn from_log_location(location: &LogLocation) -> Result { let hash = compute_location_hash(&location.path)?; - let last_modified = fs::metadata(&location.path)? - .modified()? - .into(); + let last_modified = fs::metadata(&location.path)?.modified()?.into(); Ok(Self { path: location.path.clone(), hash, size_bytes: location.size_bytes, last_modified, - doc_count: 0, // Will be updated during indexing + doc_count: 0, // Will be updated during indexing }) } @@ -207,8 +203,8 @@ pub fn detect_changes( #[cfg(test)] mod tests { use super::*; - use tempfile::tempdir; use std::io::Write; + use tempfile::tempdir; #[test] fn test_metadata_new() { diff --git a/src/search/mod.rs b/src/search/mod.rs index 5f1fcc4..37a48aa 100644 --- a/src/search/mod.rs +++ b/src/search/mod.rs @@ -1,14 +1,14 @@ // Search indexing and query modules using Tantivy -pub mod schema; -pub mod metadata; +pub mod cli; pub mod index_builder; +pub mod metadata; pub mod query_executor; -pub mod cli; +pub mod schema; // Re-exports -pub use schema::{build_schema, LogEntryDocument}; -pub use metadata::{IndexMetadata, LocationMetadata}; +pub use cli::handle_search; pub use index_builder::IndexBuilder; +pub use metadata::{IndexMetadata, LocationMetadata}; pub use query_executor::QueryExecutor; -pub use cli::handle_search; +pub use schema::{build_schema, LogEntryDocument}; diff --git a/src/search/query_executor.rs b/src/search/query_executor.rs index 1f66aba..2e689dd 100644 --- a/src/search/query_executor.rs +++ b/src/search/query_executor.rs @@ -185,31 +185,46 @@ impl QueryExecutor { if let Some(ref tool) = query.tool { let tool_field = self.schema.get_field(FIELD_TOOL)?; let term = Term::from_field_text(tool_field, tool); - subqueries.push((Occur::Must, Box::new(TermQuery::new(term, IndexRecordOption::Basic)))); + subqueries.push(( + Occur::Must, + Box::new(TermQuery::new(term, IndexRecordOption::Basic)), + )); } if let Some(ref log_type) = query.log_type { let log_type_field = self.schema.get_field(FIELD_LOG_TYPE)?; let term = Term::from_field_text(log_type_field, log_type); - subqueries.push((Occur::Must, Box::new(TermQuery::new(term, IndexRecordOption::Basic)))); + subqueries.push(( + Occur::Must, + Box::new(TermQuery::new(term, IndexRecordOption::Basic)), + )); } if let Some(ref category) = query.category { let category_field = self.schema.get_field(FIELD_CATEGORY)?; let term = Term::from_field_text(category_field, category); - subqueries.push((Occur::Must, Box::new(TermQuery::new(term, IndexRecordOption::Basic)))); + subqueries.push(( + Occur::Must, + Box::new(TermQuery::new(term, IndexRecordOption::Basic)), + )); } if let Some(ref level) = query.level { let level_field = self.schema.get_field(FIELD_LEVEL)?; let term = Term::from_field_text(level_field, level); - subqueries.push((Occur::Must, Box::new(TermQuery::new(term, IndexRecordOption::Basic)))); + subqueries.push(( + Occur::Must, + Box::new(TermQuery::new(term, IndexRecordOption::Basic)), + )); } if let Some(ref project) = query.project { let project_field = self.schema.get_field(FIELD_PROJECT)?; let term = Term::from_field_text(project_field, project); - subqueries.push((Occur::Must, Box::new(TermQuery::new(term, IndexRecordOption::Basic)))); + subqueries.push(( + Occur::Must, + Box::new(TermQuery::new(term, IndexRecordOption::Basic)), + )); } // Date range filter (simplified - would need proper range query in production) diff --git a/src/sourcemap_unbundler.rs b/src/sourcemap_unbundler.rs index e933b24..255928c 100644 --- a/src/sourcemap_unbundler.rs +++ b/src/sourcemap_unbundler.rs @@ -1,10 +1,10 @@ +use regex::Regex; +use serde::{Deserialize, Serialize}; /// Source Map-Aware Unbundler - Reconstructs original files from bundled code use std::collections::HashMap; use std::fs::{self, File}; use std::io::Write; use std::path::{Path, PathBuf}; -use serde::{Deserialize, Serialize}; -use regex::Regex; /// Source Map v3 format #[derive(Debug, Deserialize, Serialize)] @@ -187,13 +187,16 @@ impl SourceMapExtractor { } /// Create a mapping index for name recovery - pub fn create_name_index(&self) -> Result>, Box> { + pub fn create_name_index( + &self, + ) -> Result>, Box> { let source_maps = self.extract_source_maps()?; let mut name_index: HashMap> = HashMap::new(); for map in source_maps { for source in &map.sources { - name_index.entry(source.clone()) + name_index + .entry(source.clone()) .or_insert_with(Vec::new) .extend(map.names.clone()); } @@ -221,7 +224,8 @@ impl SourceMapExtractor { has_content_count += 1; } - if idx < 5 { // Show details for first 5 + if idx < 5 { + // Show details for first 5 println!("\nSource Map #{}:", idx + 1); println!(" Version: {}", map.version); println!(" Sources: {}", map.sources.len()); @@ -243,7 +247,11 @@ impl SourceMapExtractor { println!("\n=== Summary ==="); println!("Total unique source files: {}", all_sources.len()); println!("Total unique names: {}", all_names.len()); - println!("Maps with embedded content: {}/{}", has_content_count, source_maps.len()); + println!( + "Maps with embedded content: {}/{}", + has_content_count, + source_maps.len() + ); Ok(()) } diff --git a/src/structure_recovery.rs b/src/structure_recovery.rs index 0e0b740..dd0588a 100644 --- a/src/structure_recovery.rs +++ b/src/structure_recovery.rs @@ -1,13 +1,13 @@ +use regex::Regex; /// Project Structure Recovery from Source Maps use std::collections::{HashMap, HashSet}; use std::fs::{self, File}; use std::io::Write; use std::path::{Path, PathBuf}; -use regex::Regex; -use crate::sourcemap_unbundler::{SourceMap, SourceMapExtractor}; -use crate::deobfuscator::JSDeobfuscator; use crate::behavior_analyzer::BehaviorAnalyzer; +use crate::deobfuscator::JSDeobfuscator; +use crate::sourcemap_unbundler::{SourceMap, SourceMapExtractor}; pub struct StructureRecovery { binary_path: PathBuf, @@ -40,7 +40,10 @@ impl StructureRecovery { println!("[*] Step 3: Distributing code to files using heuristics..."); self.distribute_code(&file_paths, output_dir)?; - println!("[+] Recovery complete! Reconstructed {} files", file_paths.len()); + println!( + "[+] Recovery complete! Reconstructed {} files", + file_paths.len() + ); Ok(()) } @@ -160,8 +163,8 @@ impl StructureRecovery { // Optionally deobfuscate the code (skip for node_modules to save time) let final_code = if self.deobfuscate && !is_dependency { - let mut deobfuscator = JSDeobfuscator::new(module_code.clone()) - .with_verbosity(false); + let mut deobfuscator = + JSDeobfuscator::new(module_code.clone()).with_verbosity(false); deobfuscator.deobfuscate() } else { module_code @@ -174,13 +177,19 @@ impl StructureRecovery { println!(" [+] Application code files: {}", app_code_count); println!(" [+] Node modules files: {}", node_modules_count); if ai_generated_names > 0 { - println!(" [+] AI-generated semantic filenames: {} (marked with .AI suffix)", ai_generated_names); + println!( + " [+] AI-generated semantic filenames: {} (marked with .AI suffix)", + ai_generated_names + ); } // Generate package.json if dependencies were detected if !detected_dependencies.is_empty() { self.generate_package_json(output_dir, &detected_dependencies)?; - println!(" [+] Generated package.json with {} dependencies", detected_dependencies.len()); + println!( + " [+] Generated package.json with {} dependencies", + detected_dependencies.len() + ); } // Create README explaining the recovery @@ -194,12 +203,23 @@ impl StructureRecovery { let mut modules = Vec::new(); // Pattern 1: __commonJS wrapped modules - let commonjs_re = Regex::new(r"var\s+require_(\w+)\s*=\s*__commonJS\(\(([^)]+)\)\s*=>\s*\{([^}]+(?:\{[^}]*\})*[^}]*)\}\)")?; + let commonjs_re = Regex::new( + r"var\s+require_(\w+)\s*=\s*__commonJS\(\(([^)]+)\)\s*=>\s*\{([^}]+(?:\{[^}]*\})*[^}]*)\}\)", + )?; for caps in commonjs_re.captures_iter(code) { - let name = caps.get(1).map(|m| m.as_str().to_string()).unwrap_or_default(); - let params = caps.get(2).map(|m| m.as_str().to_string()).unwrap_or_default(); - let body = caps.get(3).map(|m| m.as_str().to_string()).unwrap_or_default(); + let name = caps + .get(1) + .map(|m| m.as_str().to_string()) + .unwrap_or_default(); + let params = caps + .get(2) + .map(|m| m.as_str().to_string()) + .unwrap_or_default(); + let body = caps + .get(3) + .map(|m| m.as_str().to_string()) + .unwrap_or_default(); modules.push(CodeModule { name: name.clone(), @@ -210,7 +230,8 @@ impl StructureRecovery { } // Pattern 2: ES6 modules with export - let export_re = Regex::new(r"export\s+(?:default\s+)?(?:class|function|const|let|var)\s+(\w+)")?; + let export_re = + Regex::new(r"export\s+(?:default\s+)?(?:class|function|const|let|var)\s+(\w+)")?; let mut current_pos = 0; for caps in export_re.captures_iter(code) { @@ -233,7 +254,9 @@ impl StructureRecovery { } // Pattern 3: React components - let react_re = Regex::new(r"function\s+([A-Z]\w+)\s*\([^)]*\)\s*\{[^}]*(?:return\s+.*?<|React\.createElement)")?; + let react_re = Regex::new( + r"function\s+([A-Z]\w+)\s*\([^)]*\)\s*\{[^}]*(?:return\s+.*?<|React\.createElement)", + )?; for caps in react_re.captures_iter(code) { if let Some(name_match) = caps.get(1) { @@ -268,14 +291,16 @@ impl StructureRecovery { for module in modules { // Try exact match first if file_paths.contains(&module.inferred_path) { - assignments.entry(module.inferred_path.clone()) + assignments + .entry(module.inferred_path.clone()) .or_insert_with(String::new) .push_str(&format!("\n// Module: {}\n{}\n", module.name, module.code)); continue; } // Try fuzzy matching - let best_match = file_paths.iter() + let best_match = file_paths + .iter() .filter(|p| self.path_similarity(&module.inferred_path, p) > 0.5) .max_by(|a, b| { self.path_similarity(&module.inferred_path, a) @@ -284,17 +309,19 @@ impl StructureRecovery { }); if let Some(matched_path) = best_match { - assignments.entry(matched_path.clone()) + assignments + .entry(matched_path.clone()) .or_insert_with(String::new) .push_str(&format!("\n// Module: {}\n{}\n", module.name, module.code)); } else { // Create a new file for unmatched modules - use semantic naming if possible - let mut fallback_path = if let Some(semantic_path) = self.infer_semantic_filename(&module.code) { - semantic_path - } else { - // Fall back to generic name with .AI suffix to mark for manual review - format!("recovered/{}.js.AI", module.name) - }; + let mut fallback_path = + if let Some(semantic_path) = self.infer_semantic_filename(&module.code) { + semantic_path + } else { + // Fall back to generic name with .AI suffix to mark for manual review + format!("recovered/{}.js.AI", module.name) + }; // Handle duplicates by adding counter let base_path = fallback_path.clone(); @@ -333,7 +360,13 @@ impl StructureRecovery { } // Check for hooks (React) - if name.starts_with("use") && name.chars().nth(3).map(|c| c.is_uppercase()).unwrap_or(false) { + if name.starts_with("use") + && name + .chars() + .nth(3) + .map(|c| c.is_uppercase()) + .unwrap_or(false) + { return format!("src/hooks/{}.ts", name); } @@ -363,198 +396,307 @@ impl StructureRecovery { // Fallback to pattern matching (old approach) let patterns = [ // Purpose: API Key & Secret Sanitization (found: AKIA, AWS key, AIza patterns) - (vec!["AKIA", "AWS key", "AIza", "[REDACTED", "sanitize"], - "src/security/apiKeySanitizer.ts.AI"), - + ( + vec!["AKIA", "AWS key", "AIza", "[REDACTED", "sanitize"], + "src/security/apiKeySanitizer.ts.AI", + ), // Purpose: Protocol Header Management (found: Claude-Escapes, Claude-Steers) - (vec!["Claude-Escapes", "Claude-Steers", "Claude-Permission", "Claude-Plan"], - "src/protocol/headerParser.ts.AI"), - + ( + vec![ + "Claude-Escapes", + "Claude-Steers", + "Claude-Permission", + "Claude-Plan", + ], + "src/protocol/headerParser.ts.AI", + ), // Purpose: Telemetry & Event Tracking (found: recordDroppedEvent, tengu_) - (vec!["recordDroppedEvent", "tengu_", "trackEvent", "analytics"], - "src/telemetry/eventRecorder.ts.AI"), - + ( + vec!["recordDroppedEvent", "tengu_", "trackEvent", "analytics"], + "src/telemetry/eventRecorder.ts.AI", + ), // Purpose: Input Validation (found: datetime, email validation patterns) - (vec!["email({message:", "datetime({offset:", "validate", "schema"], - "src/validation/inputValidator.ts.AI"), - + ( + vec!["email({message:", "datetime({offset:", "validate", "schema"], + "src/validation/inputValidator.ts.AI", + ), // Purpose: Plan Mode Workflow (found: "Plan mode is active", isSubAgent) - (vec!["Plan mode is active", "isSubAgent", "planFilePath", "workflow"], - "src/planning/planModeController.ts.AI"), - + ( + vec![ + "Plan mode is active", + "isSubAgent", + "planFilePath", + "workflow", + ], + "src/planning/planModeController.ts.AI", + ), // Purpose: Agent Lifecycle Management - (vec!["isSubAgent", "agentName", "agent lifecycle", "spawn agent"], - "src/agents/lifecycleManager.ts.AI"), - + ( + vec!["isSubAgent", "agentName", "agent lifecycle", "spawn agent"], + "src/agents/lifecycleManager.ts.AI", + ), // Purpose: Session & State Management - (vec!["SESSION_ACCESS_TOKEN", "session state", "snapshot", "checkpoint"], - "src/session/stateManager.ts.AI"), - + ( + vec![ + "SESSION_ACCESS_TOKEN", + "session state", + "snapshot", + "checkpoint", + ], + "src/session/stateManager.ts.AI", + ), // Purpose: File Permission & Access Control - (vec!["Read-only except", "permission", "access control", "allowedPaths"], - "src/permissions/accessController.ts.AI"), - + ( + vec![ + "Read-only except", + "permission", + "access control", + "allowedPaths", + ], + "src/permissions/accessController.ts.AI", + ), // Purpose: MCP Server Communication - (vec!["mcp serve", "MCP", "CLAUDE_CODE_ENTRYPOINT"], - "src/mcp/serverCommunication.ts.AI"), - + ( + vec!["mcp serve", "MCP", "CLAUDE_CODE_ENTRYPOINT"], + "src/mcp/serverCommunication.ts.AI", + ), // File Operations - Sync (discovered: 787 existsSync, 278 statSync) - (vec!["existsSync", "statSync", "readFileSync", "mkdirSync"], - "src/utils/fileSystemSync.ts.AI"), - (vec!["readdirSync", "unlinkSync", "rmdirSync", "writeFileSync"], - "src/utils/fileOps.ts.AI"), - + ( + vec!["existsSync", "statSync", "readFileSync", "mkdirSync"], + "src/utils/fileSystemSync.ts.AI", + ), + ( + vec!["readdirSync", "unlinkSync", "rmdirSync", "writeFileSync"], + "src/utils/fileOps.ts.AI", + ), // DOM Manipulation (discovered: 11184 createElement!) - (vec!["createElement", "appendChild", "removeChild", "textContent"], - "src/dom/domManipulator.ts.AI"), - (vec!["addEventListener", "removeEventListener", "querySelector"], - "src/dom/eventHandlers.ts.AI"), - + ( + vec!["createElement", "appendChild", "removeChild", "textContent"], + "src/dom/domManipulator.ts.AI", + ), + ( + vec!["addEventListener", "removeEventListener", "querySelector"], + "src/dom/eventHandlers.ts.AI", + ), // Data Parsing (discovered: 775 parseInt, 442 JSON.stringify) - (vec!["JSON.parse", "JSON.stringify", "parseInt", "parseFloat"], - "src/utils/dataParser.ts.AI"), - (vec!["encodeURIComponent", "decodeURIComponent", "atob", "btoa"], - "src/utils/encoding.ts.AI"), - + ( + vec!["JSON.parse", "JSON.stringify", "parseInt", "parseFloat"], + "src/utils/dataParser.ts.AI", + ), + ( + vec!["encodeURIComponent", "decodeURIComponent", "atob", "btoa"], + "src/utils/encoding.ts.AI", + ), // Error Handling (discovered: 8632 throw, 8520 Error()!) - (vec!["throw new", "Error(", "try{", "catch("], - "src/errors/errorHandler.ts.AI"), - (vec!["Promise.reject", "reject(", "finally"], - "src/errors/promiseErrors.ts.AI"), - + ( + vec!["throw new", "Error(", "try{", "catch("], + "src/errors/errorHandler.ts.AI", + ), + ( + vec!["Promise.reject", "reject(", "finally"], + "src/errors/promiseErrors.ts.AI", + ), // Async/Await (discovered: 6749 await, 3907 async) - (vec!["async ", "await ", "Promise.resolve"], - "src/async/asyncOps.ts.AI"), - + ( + vec!["async ", "await ", "Promise.resolve"], + "src/async/asyncOps.ts.AI", + ), // React Components & Hooks (discovered: 990 useState, 482 useEffect) - (vec!["useState", "useEffect", "useContext", "useMemo"], - "src/hooks/reactHooks.ts.AI"), - (vec!["React.createElement", "jsx", "Component"], - "src/components/ReactComponent.tsx.AI"), - + ( + vec!["useState", "useEffect", "useContext", "useMemo"], + "src/hooks/reactHooks.ts.AI", + ), + ( + vec!["React.createElement", "jsx", "Component"], + "src/components/ReactComponent.tsx.AI", + ), // Process Management (discovered: 3026 process., 224 process.exit) - (vec!["process.exit", "process.argv", "process.cwd", "process.env"], - "src/process/processManager.ts.AI"), - (vec!["child_process", "spawn", "exec", "fork"], - "src/process/childProcess.ts.AI"), - (vec!["process.stdin", "process.stdout", "process.stderr"], - "src/process/stdio.ts.AI"), - + ( + vec!["process.exit", "process.argv", "process.cwd", "process.env"], + "src/process/processManager.ts.AI", + ), + ( + vec!["child_process", "spawn", "exec", "fork"], + "src/process/childProcess.ts.AI", + ), + ( + vec!["process.stdin", "process.stdout", "process.stderr"], + "src/process/stdio.ts.AI", + ), // CLI & Terminal (discovered: 90 process.argv, 36 commander) - (vec!["process.argv", "commander", "yargs", "readline"], - "src/cli/cliParser.ts.AI"), - + ( + vec!["process.argv", "commander", "yargs", "readline"], + "src/cli/cliParser.ts.AI", + ), // Timing (discovered: 1000 Date.now, 660 setTimeout) - (vec!["setTimeout", "clearTimeout", "setInterval", "clearInterval"], - "src/utils/timers.ts.AI"), - (vec!["Date.now", "performance.now", "requestAnimationFrame"], - "src/utils/performance.ts.AI"), - + ( + vec!["setTimeout", "clearTimeout", "setInterval", "clearInterval"], + "src/utils/timers.ts.AI", + ), + ( + vec!["Date.now", "performance.now", "requestAnimationFrame"], + "src/utils/performance.ts.AI", + ), // String Operations (discovered: 2978 .join, 2498 .slice) - (vec![".join(", ".slice(", ".replace(", ".split("], - "src/utils/stringUtils.ts.AI"), - (vec!["RegExp", ".test(", ".match(", ".trim("], - "src/utils/regexUtils.ts.AI"), - + ( + vec![".join(", ".slice(", ".replace(", ".split("], + "src/utils/stringUtils.ts.AI", + ), + ( + vec!["RegExp", ".test(", ".match(", ".trim("], + "src/utils/regexUtils.ts.AI", + ), // Network & HTTP - (vec!["http.createServer", "https.request", "listen"], - "src/server/httpServer.ts.AI"), - (vec!["socket.on", "socket.emit", "io.on"], - "src/socket/socketHandler.ts.AI"), - (vec!["fetch(", ".then(", ".catch("], - "src/api/httpClient.ts.AI"), - + ( + vec!["http.createServer", "https.request", "listen"], + "src/server/httpServer.ts.AI", + ), + ( + vec!["socket.on", "socket.emit", "io.on"], + "src/socket/socketHandler.ts.AI", + ), + ( + vec!["fetch(", ".then(", ".catch("], + "src/api/httpClient.ts.AI", + ), // Crypto & Security - (vec!["crypto.createHash", "randomBytes", "encrypt", "decrypt"], - "src/security/cryptoUtils.ts.AI"), - + ( + vec!["crypto.createHash", "randomBytes", "encrypt", "decrypt"], + "src/security/cryptoUtils.ts.AI", + ), // Streams & Buffers - (vec!["stream.Readable", "pipe", "Transform"], - "src/streams/streamHandler.ts.AI"), - (vec!["Buffer.from", "Buffer.alloc"], - "src/utils/bufferUtils.ts.AI"), - + ( + vec!["stream.Readable", "pipe", "Transform"], + "src/streams/streamHandler.ts.AI", + ), + ( + vec!["Buffer.from", "Buffer.alloc"], + "src/utils/bufferUtils.ts.AI", + ), // Date/Time Operations (discovered: weekStartsOn, getFullYear, setFullYear) - (vec!["getFullYear", "setFullYear", "getMonth", "setMonth"], - "src/utils/dateUtils.ts.AI"), - (vec!["weekStartsOn", "getTime", "toISOString", "parseDate"], - "src/utils/timeUtils.ts.AI"), - + ( + vec!["getFullYear", "setFullYear", "getMonth", "setMonth"], + "src/utils/dateUtils.ts.AI", + ), + ( + vec!["weekStartsOn", "getTime", "toISOString", "parseDate"], + "src/utils/timeUtils.ts.AI", + ), // Image Processing (discovered: imagePasteIds, isLoading) - (vec!["imagePasteIds", "image", "canvas", "drawImage"], - "src/utils/imageHandler.ts.AI"), - (vec!["isLoading", "loading", "spinner", "loadingState"], - "src/components/LoadingState.tsx.AI"), - + ( + vec!["imagePasteIds", "image", "canvas", "drawImage"], + "src/utils/imageHandler.ts.AI", + ), + ( + vec!["isLoading", "loading", "spinner", "loadingState"], + "src/components/LoadingState.tsx.AI", + ), // Bit Operations (discovered: getLowestSetBit, compareTo) - (vec!["getLowestSetBit", "bitwise", "<<", ">>"], - "src/utils/bitOperations.ts.AI"), - + ( + vec!["getLowestSetBit", "bitwise", "<<", ">>"], + "src/utils/bitOperations.ts.AI", + ), // Object Utilities & Polyfills (discovered: prototype, hasOwnProperty, construct) - (vec!["prototype", "hasOwnProperty", "Object.keys", "Object.values"], - "src/utils/objectUtils.ts.AI"), - (vec!["polyfill", "__proto__", "defineProperty"], - "src/polyfills/objectPolyfill.ts.AI"), - + ( + vec![ + "prototype", + "hasOwnProperty", + "Object.keys", + "Object.values", + ], + "src/utils/objectUtils.ts.AI", + ), + ( + vec!["polyfill", "__proto__", "defineProperty"], + "src/polyfills/objectPolyfill.ts.AI", + ), // Database - (vec!["database", "db", "query", "connection", "pool"], - "src/database/dbConnection.js.AI"), - (vec!["mongoose", "schema", "model"], - "src/models/schema.js.AI"), - (vec!["sequelize", "findOne", "findAll"], - "src/models/queries.js.AI"), - + ( + vec!["database", "db", "query", "connection", "pool"], + "src/database/dbConnection.js.AI", + ), + ( + vec!["mongoose", "schema", "model"], + "src/models/schema.js.AI", + ), + ( + vec!["sequelize", "findOne", "findAll"], + "src/models/queries.js.AI", + ), // Validation & Middleware - (vec!["validate", "validator", "schema", "yup", "joi"], - "src/middleware/validation.js.AI"), - (vec!["middleware", "next()", "req", "res"], - "src/middleware/handler.js.AI"), - + ( + vec!["validate", "validator", "schema", "yup", "joi"], + "src/middleware/validation.js.AI", + ), + ( + vec!["middleware", "next()", "req", "res"], + "src/middleware/handler.js.AI", + ), // UI Components - (vec!["Button", "onClick", "className", "props"], - "src/components/Button.tsx.AI"), - (vec!["Form", "input", "onSubmit", "formData"], - "src/components/Form.tsx.AI"), - (vec!["Modal", "dialog", "isOpen", "onClose"], - "src/components/Modal.tsx.AI"), - + ( + vec!["Button", "onClick", "className", "props"], + "src/components/Button.tsx.AI", + ), + ( + vec!["Form", "input", "onSubmit", "formData"], + "src/components/Form.tsx.AI", + ), + ( + vec!["Modal", "dialog", "isOpen", "onClose"], + "src/components/Modal.tsx.AI", + ), // State Management - (vec!["useState", "useEffect", "useContext"], - "src/hooks/useCustomHook.ts.AI"), - (vec!["redux", "dispatch", "action", "reducer"], - "src/store/reducer.js.AI"), - (vec!["createSlice", "configureStore"], - "src/store/slice.js.AI"), - + ( + vec!["useState", "useEffect", "useContext"], + "src/hooks/useCustomHook.ts.AI", + ), + ( + vec!["redux", "dispatch", "action", "reducer"], + "src/store/reducer.js.AI", + ), + ( + vec!["createSlice", "configureStore"], + "src/store/slice.js.AI", + ), // Utils & Helpers - (vec!["format", "parse", "convert", "transform"], - "src/utils/formatter.js.AI"), - (vec!["logger", "log", "console", "debug"], - "src/utils/logger.js.AI"), - (vec!["config", "env", "process.env"], - "src/config/config.js.AI"), - + ( + vec!["format", "parse", "convert", "transform"], + "src/utils/formatter.js.AI", + ), + ( + vec!["logger", "log", "console", "debug"], + "src/utils/logger.js.AI", + ), + ( + vec!["config", "env", "process.env"], + "src/config/config.js.AI", + ), // Testing - (vec!["test", "expect", "describe", "it("], - "tests/spec.test.js.AI"), - (vec!["mock", "jest", "spy"], - "tests/mocks.js.AI"), - + ( + vec!["test", "expect", "describe", "it("], + "tests/spec.test.js.AI", + ), + (vec!["mock", "jest", "spy"], "tests/mocks.js.AI"), // Error Handling - (vec!["Error", "throw", "catch", "try"], - "src/errors/errorHandler.js.AI"), - + ( + vec!["Error", "throw", "catch", "try"], + "src/errors/errorHandler.js.AI", + ), // File Operations - (vec!["fs", "readFile", "writeFile", "path"], - "src/utils/fileHandler.js.AI"), - + ( + vec!["fs", "readFile", "writeFile", "path"], + "src/utils/fileHandler.js.AI", + ), // WebSocket & Real-time - (vec!["socket", "io", "emit", "on("], - "src/socket/socketHandler.js.AI"), + ( + vec!["socket", "io", "emit", "on("], + "src/socket/socketHandler.js.AI", + ), ]; for (keywords, path) in &patterns { - let matches = keywords.iter() + let matches = keywords + .iter() .filter(|k| code.to_lowercase().contains(&k.to_lowercase())) .count(); @@ -572,7 +714,8 @@ impl StructureRecovery { let parts1: Vec<&str> = path1.split('/').collect(); let parts2: Vec<&str> = path2.split('/').collect(); - let common_parts = parts1.iter() + let common_parts = parts1 + .iter() .zip(parts2.iter()) .filter(|(a, b)| a == b) .count(); @@ -615,9 +758,21 @@ impl StructureRecovery { // Check for common library names let common_libs = [ - "react", "lodash", "axios", "express", "moment", - "webpack", "babel", "typescript", "esbuild", "rollup", - "vue", "angular", "jquery", "underscore", "chalk", + "react", + "lodash", + "axios", + "express", + "moment", + "webpack", + "babel", + "typescript", + "esbuild", + "rollup", + "vue", + "angular", + "jquery", + "underscore", + "chalk", ]; let has_lib_reference = common_libs.iter().any(|lib| { @@ -650,7 +805,10 @@ impl StructureRecovery { // Try detecting from common library patterns in code let common_libs = [ - ("react", vec!["React.createElement", "useState", "useEffect", "Component"]), + ( + "react", + vec!["React.createElement", "useState", "useEffect", "Component"], + ), ("lodash", vec!["lodash", "_.map", "_.forEach"]), ("axios", vec!["axios", ".get(", ".post("]), ("express", vec!["express", "app.get", "app.post"]), @@ -689,7 +847,7 @@ impl StructureRecovery { deps_json.push_str(" }"); let package_json = format!( -r#"{{ + r#"{{ "name": "recovered-project", "version": "1.0.0", "description": "Recovered from bundled JavaScript", @@ -729,7 +887,7 @@ r#"{{ }; let readme_content = format!( -r#"# Recovered Project Structure + r#"# Recovered Project Structure This directory was reconstructed from source map fragments and heuristic analysis. @@ -788,7 +946,11 @@ These require **manual review** to: file_paths.len(), deobfuscation_note, self.format_directory_tree(file_paths), - if self.deobfuscate { "\n4. **Deobfuscation**: Variable names were renamed using pattern analysis" } else { "" }, + if self.deobfuscate { + "\n4. **Deobfuscation**: Variable names were renamed using pattern analysis" + } else { + "" + }, limitations_note, self.categorize_files(file_paths) ); @@ -804,7 +966,9 @@ These require **manual review** to: for path in paths { if let Some(dir) = Path::new(path).parent() { let dir_str = dir.to_string_lossy().to_string(); - dirs.entry(dir_str).or_insert_with(Vec::new).push(path.clone()); + dirs.entry(dir_str) + .or_insert_with(Vec::new) + .push(path.clone()); } } diff --git a/src/traffic.rs b/src/traffic.rs index ab33958..43268eb 100644 --- a/src/traffic.rs +++ b/src/traffic.rs @@ -291,10 +291,7 @@ impl TrafficLog { } // Update average latency - let total_latency: u64 = entries - .iter() - .filter_map(|e| e.latency_ms) - .sum(); + let total_latency: u64 = entries.iter().filter_map(|e| e.latency_ms).sum(); let count = entries.iter().filter(|e| e.latency_ms.is_some()).count(); if count > 0 { stats.avg_latency_ms = total_latency as f64 / count as f64; diff --git a/src/tui_traffic.rs b/src/tui_traffic.rs index ea5c254..db3cf38 100644 --- a/src/tui_traffic.rs +++ b/src/tui_traffic.rs @@ -29,10 +29,7 @@ pub struct TrafficMonitorApp { } impl TrafficMonitorApp { - pub fn new( - traffic_log: TrafficLog, - event_rx: mpsc::UnboundedReceiver, - ) -> Self { + pub fn new(traffic_log: TrafficLog, event_rx: mpsc::UnboundedReceiver) -> Self { Self { traffic_log, event_rx, @@ -46,7 +43,10 @@ impl TrafficMonitorApp { } /// Run the TUI event loop - pub async fn run(&mut self, terminal: &mut ratatui::Terminal) -> io::Result<()> { + pub async fn run( + &mut self, + terminal: &mut ratatui::Terminal, + ) -> io::Result<()> { loop { // Draw UI terminal.draw(|f| self.draw(f))?; @@ -79,7 +79,11 @@ impl TrafficMonitorApp { KeyCode::Char('p') | KeyCode::Char(' ') => self.paused = !self.paused, KeyCode::Tab => self.selected_tab = (self.selected_tab + 1) % 3, KeyCode::BackTab => { - self.selected_tab = if self.selected_tab == 0 { 2 } else { self.selected_tab - 1 }; + self.selected_tab = if self.selected_tab == 0 { + 2 + } else { + self.selected_tab - 1 + }; } KeyCode::Up | KeyCode::Char('k') => { self.scroll_offset = self.scroll_offset.saturating_sub(1); @@ -99,14 +103,26 @@ impl TrafficMonitorApp { ProxyEvent::RequestStarted { id, model, stream } => { format!("#{} {} stream={}", id, model, stream) } - ProxyEvent::RequestCompleted { id, tokens_in, tokens_out, latency_ms } => { - format!("#{} done: {}in/{}out {}ms", id, tokens_in, tokens_out, latency_ms) + ProxyEvent::RequestCompleted { + id, + tokens_in, + tokens_out, + latency_ms, + } => { + format!( + "#{} done: {}in/{}out {}ms", + id, tokens_in, tokens_out, latency_ms + ) } ProxyEvent::RequestFailed { id, error } => { format!("#{} ERROR: {}", id, error) } ProxyEvent::StreamChunk { id, text } => { - format!("#{} chunk: {}...", id, text.chars().take(50).collect::()) + format!( + "#{} chunk: {}...", + id, + text.chars().take(50).collect::() + ) } }; @@ -141,10 +157,18 @@ impl TrafficMonitorApp { fn draw_tabs(&self, f: &mut Frame, area: Rect) { let titles = vec!["Live", "Stats", "History"]; let tabs = Tabs::new(titles) - .block(Block::default().borders(Borders::ALL).title("Claude Traffic Monitor")) + .block( + Block::default() + .borders(Borders::ALL) + .title("Claude Traffic Monitor"), + ) .select(self.selected_tab) .style(Style::default().fg(Color::White)) - .highlight_style(Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)); + .highlight_style( + Style::default() + .fg(Color::Yellow) + .add_modifier(Modifier::BOLD), + ); f.render_widget(tabs, area); } @@ -191,7 +215,9 @@ impl TrafficMonitorApp { Line::from(""), Line::from(Span::styled( format!("Cost: ${:.4}", stats.total_cost_usd), - Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD), + Style::default() + .fg(Color::Yellow) + .add_modifier(Modifier::BOLD), )), Line::from(format!("Avg latency: {:.0}ms", stats.avg_latency_ms)), ]; @@ -219,15 +245,30 @@ impl TrafficMonitorApp { let token_text = vec![ Line::from(format!("Total tokens: {}", total_tokens)), - Line::from(format!(" Input: {} ({:.1}%)", stats.total_input_tokens, input_pct)), - Line::from(format!(" Output: {} ({:.1}%)", stats.total_output_tokens, 100 - input_pct)), + Line::from(format!( + " Input: {} ({:.1}%)", + stats.total_input_tokens, input_pct + )), + Line::from(format!( + " Output: {} ({:.1}%)", + stats.total_output_tokens, + 100 - input_pct + )), Line::from(""), - Line::from(format!("Cache read tokens: {}", stats.total_cache_read_tokens)), - Line::from(format!("Cache write tokens: {}", stats.total_cache_write_tokens)), + Line::from(format!( + "Cache read tokens: {}", + stats.total_cache_read_tokens + )), + Line::from(format!( + "Cache write tokens: {}", + stats.total_cache_write_tokens + )), Line::from(""), Line::from(Span::styled( format!("Estimated cost: ${:.6}", stats.total_cost_usd), - Style::default().fg(Color::Green).add_modifier(Modifier::BOLD), + Style::default() + .fg(Color::Green) + .add_modifier(Modifier::BOLD), )), ]; @@ -248,12 +289,21 @@ impl TrafficMonitorApp { .collect(); if model_rows.is_empty() { - model_rows.push(Row::new(vec![Cell::from("No requests yet"), Cell::from("-")])); + model_rows.push(Row::new(vec![ + Cell::from("No requests yet"), + Cell::from("-"), + ])); } - let model_table = Table::new(model_rows, [Constraint::Percentage(70), Constraint::Percentage(30)]) - .header(Row::new(vec!["Model", "Requests"]).style(Style::default().add_modifier(Modifier::BOLD))) - .block(Block::default().borders(Borders::ALL).title("Models Used")); + let model_table = Table::new( + model_rows, + [Constraint::Percentage(70), Constraint::Percentage(30)], + ) + .header( + Row::new(vec!["Model", "Requests"]) + .style(Style::default().add_modifier(Modifier::BOLD)), + ) + .block(Block::default().borders(Borders::ALL).title("Models Used")); f.render_widget(model_table, chunks[1]); } @@ -318,7 +368,11 @@ impl TrafficMonitorApp { Row::new(vec!["ID", "Time", "Model", "Status", "Tokens", "Latency"]) .style(Style::default().add_modifier(Modifier::BOLD)), ) - .block(Block::default().borders(Borders::ALL).title("Request History")); + .block( + Block::default() + .borders(Borders::ALL) + .title("Request History"), + ); f.render_widget(table, area); } @@ -331,7 +385,11 @@ impl TrafficMonitorApp { Span::styled( format!(" {} ", status), Style::default() - .fg(if self.paused { Color::Yellow } else { Color::Green }) + .fg(if self.paused { + Color::Yellow + } else { + Color::Green + }) .add_modifier(Modifier::BOLD), ), Span::raw(" | "), @@ -345,8 +403,8 @@ impl TrafficMonitorApp { Span::raw("q: quit p: pause Tab: switch ↑↓: scroll"), ]); - let status_widget = Paragraph::new(status_line) - .block(Block::default().borders(Borders::ALL)); + let status_widget = + Paragraph::new(status_line).block(Block::default().borders(Borders::ALL)); f.render_widget(status_widget, area); } } diff --git a/src/unbundler_cli.rs b/src/unbundler_cli.rs index c49c320..59476b8 100644 --- a/src/unbundler_cli.rs +++ b/src/unbundler_cli.rs @@ -1,9 +1,9 @@ +use clap::{Parser, Subcommand}; +use regex::Regex; /// JavaScript Unbundler - Extract and analyze bundled JS from binaries -use std::fs::{File, self}; +use std::fs::{self, File}; use std::io::Write; use std::path::PathBuf; -use regex::Regex; -use clap::{Parser, Subcommand}; mod sourcemap_unbundler; use sourcemap_unbundler::SourceMapExtractor; @@ -116,7 +116,10 @@ struct JsExtractor { impl JsExtractor { fn new(binary_path: PathBuf, min_length: usize) -> Self { - Self { binary_path, min_length } + Self { + binary_path, + min_length, + } } /// Extract all strings from binary using `strings` command @@ -155,7 +158,8 @@ impl JsExtractor { fn extract_javascript(&self) -> Result, Box> { let strings = self.extract_strings()?; - Ok(strings.into_iter() + Ok(strings + .into_iter() .filter(|s| self.is_javascript(s)) .collect()) } @@ -180,7 +184,11 @@ impl JsExtractor { } /// Save extracted code to files - fn save_to_files(&self, code: Vec, output_dir: &PathBuf) -> Result<(), Box> { + fn save_to_files( + &self, + code: Vec, + output_dir: &PathBuf, + ) -> Result<(), Box> { fs::create_dir_all(output_dir)?; // Save all JavaScript code @@ -191,7 +199,8 @@ impl JsExtractor { // Save individual functions/modules for (idx, line) in code.iter().enumerate() { - if line.len() > 1000 { // Large chunks likely to be modules + if line.len() > 1000 { + // Large chunks likely to be modules let mut file = File::create(output_dir.join(format!("module_{}.js", idx)))?; writeln!(file, "{}", line)?; } @@ -244,10 +253,7 @@ impl BundleAnalyzer { // Find source file references let file_re = Regex::new(r"[\w-]+\.(js|ts|jsx|tsx)").unwrap(); - let source_files: Vec<&str> = file_re - .find_iter(&full_code) - .map(|m| m.as_str()) - .collect(); + let source_files: Vec<&str> = file_re.find_iter(&full_code).map(|m| m.as_str()).collect(); if !source_files.is_empty() { println!("\nSource files referenced: {}", source_files.len()); @@ -323,7 +329,11 @@ fn main() -> Result<(), Box> { let cli = Cli::parse(); match cli.command { - Commands::Extract { binary, output, min_length } => { + Commands::Extract { + binary, + output, + min_length, + } => { println!("Extracting JavaScript from: {}", binary.display()); let extractor = JsExtractor::new(binary, min_length); let js_code = extractor.extract_javascript()?; @@ -339,7 +349,11 @@ fn main() -> Result<(), Box> { CodeBeautifier::beautify_file(input, output)?; } - Commands::SourceMaps { binary, report_only, output } => { + Commands::SourceMaps { + binary, + report_only, + output, + } => { let extractor = SourceMapExtractor::new(binary); if report_only { @@ -351,7 +365,11 @@ fn main() -> Result<(), Box> { } } - Commands::Deobfuscate { input, output, report } => { + Commands::Deobfuscate { + input, + output, + report, + } => { println!("Deobfuscating: {}", input.display()); let mut deobfuscator = JSDeobfuscator::from_file(&input)?; let deobfuscated = deobfuscator.deobfuscate(); @@ -364,14 +382,18 @@ fn main() -> Result<(), Box> { println!("[+] Saved deobfuscated code to: {}", output.display()); } - Commands::RecoverStructure { binary, extracted_code, output, deobfuscate } => { + Commands::RecoverStructure { + binary, + extracted_code, + output, + deobfuscate, + } => { println!("Recovering project structure from source maps..."); if deobfuscate { println!("[*] Deobfuscation enabled - variables will be renamed using heuristics"); } let code = fs::read_to_string(&extracted_code)?; - let recovery = StructureRecovery::new(binary, code) - .with_deobfuscation(deobfuscate); + let recovery = StructureRecovery::new(binary, code).with_deobfuscation(deobfuscate); recovery.recover_structure(&output)?; } }