use crate::config::Config;
use crate::session;
use std::path::Path;
pub fn detect_lang() -> String {
if let Ok(out) = std::process::Command::new("defaults")
.args(["read", "-g", "AppleLanguages"])
.output()
{
let s = String::from_utf8_lossy(&out.stdout);
if let Some(lang) = parse_apple_languages(&s) {
return lang;
}
}
for var in &["LC_ALL", "LANG", "LANGUAGE"] {
if let Ok(val) = std::env::var(var) {
if !val.is_empty() && val != "C" && val != "C.UTF-8" && val != "POSIX" {
return parse_posix_locale(&val);
}
}
}
"en".to_string()
}
fn parse_apple_languages(s: &str) -> Option<String> {
for line in s.lines() {
let trimmed = line.trim().trim_matches(',').trim_matches('"');
if trimmed.is_empty() || trimmed == "(" || trimmed == ")" {
continue;
}
return Some(normalize_lang(trimmed));
}
None
}
fn parse_posix_locale(s: &str) -> String {
let base = s.split('.').next().unwrap_or(s);
normalize_lang(&base.replace('_', "-"))
}
fn normalize_lang(s: &str) -> String {
let lower = s.to_lowercase();
if lower.starts_with("pt") {
"pt-BR".to_string()
} else {
"en".to_string()
}
}
fn write_lang_to_config(config_path: &str, lang: &str) {
let existing = std::fs::read_to_string(config_path).unwrap_or_default();
if existing.contains("lang =") || existing.contains("lang=") {
return; }
if lang == "en" {
return; }
let appended = format!("{}\nlang = {}\n", existing.trim_end(), lang);
let _ = std::fs::write(config_path, appended);
}
#[derive(Debug, Clone)]
pub struct CalibrationProfile {
pub max_lines: usize,
pub dedup_min: usize,
pub summarize_threshold_lines: usize,
pub auto_compress_md: bool,
pub ultra_trigger_pct: f32,
pub adaptive_intensity: bool,
pub read_max_lines: usize,
pub grep_max_results: usize,
}
impl CalibrationProfile {
fn aggressive() -> Self {
Self {
max_lines: 120,
dedup_min: 2,
summarize_threshold_lines: 300,
auto_compress_md: true,
ultra_trigger_pct: 0.70,
adaptive_intensity: true,
read_max_lines: 300,
grep_max_results: 100,
}
}
fn balanced() -> Self {
Self {
max_lines: 200,
dedup_min: 3,
summarize_threshold_lines: 500,
auto_compress_md: true,
ultra_trigger_pct: 0.80,
adaptive_intensity: true,
read_max_lines: 0,
grep_max_results: 0,
}
}
fn conservative() -> Self {
Self {
max_lines: 350,
dedup_min: 5,
summarize_threshold_lines: 800,
auto_compress_md: false,
ultra_trigger_pct: 0.90,
adaptive_intensity: true,
read_max_lines: 0,
grep_max_results: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct BenchmarkAnalysis {
pub avg_reduction_pct: f64,
pub quality_pass_count: usize,
pub total_scenarios: usize,
}
pub fn select_profile(analysis: &BenchmarkAnalysis) -> CalibrationProfile {
let all_pass = analysis.quality_pass_count == analysis.total_scenarios;
if analysis.avg_reduction_pct > 70.0 && all_pass {
CalibrationProfile::aggressive()
} else if analysis.avg_reduction_pct > 50.0 {
CalibrationProfile::balanced()
} else {
CalibrationProfile::conservative()
}
}
pub fn profile_to_config(profile: &CalibrationProfile) -> String {
let mut lines = Vec::new();
lines.push("# squeez config — auto-generated by `squeez calibrate`".to_string());
lines.push(format!("max_lines = {}", profile.max_lines));
lines.push(format!("dedup_min = {}", profile.dedup_min));
lines.push(format!(
"summarize_threshold_lines = {}",
profile.summarize_threshold_lines
));
lines.push(format!("auto_compress_md = {}", profile.auto_compress_md));
lines.push(format!("ultra_trigger_pct = {:.2}", profile.ultra_trigger_pct));
lines.push(format!("adaptive_intensity = {}", profile.adaptive_intensity));
if profile.read_max_lines > 0 {
lines.push(format!("read_max_lines = {}", profile.read_max_lines));
}
if profile.grep_max_results > 0 {
lines.push(format!("grep_max_results = {}", profile.grep_max_results));
}
lines.join("\n") + "\n"
}
pub fn write_config(profile: &CalibrationProfile) -> Result<String, String> {
let base = std::env::var("SQUEEZ_DIR").unwrap_or_else(|_| {
format!("{}/.claude/squeez", session::home_dir())
});
let config_path = format!("{}/config.ini", base);
let path = Path::new(&config_path);
if path.exists() {
let bak = format!("{}/config.ini.bak", base);
let _ = std::fs::copy(path, &bak);
}
let content = profile_to_config(profile);
std::fs::write(path, &content).map_err(|e| format!("write config: {}", e))?;
Ok(config_path)
}
pub fn run(args: &[String]) -> i32 {
let force_aggressive = args.iter().any(|a| a == "--force-aggressive");
if force_aggressive {
eprintln!("squeez calibrate: applying aggressive profile (--force-aggressive)...");
let lang = detect_lang();
eprintln!(" Language detected: {}", lang);
let profile = CalibrationProfile::aggressive();
match write_config(&profile) {
Ok(path) => {
write_lang_to_config(&path, &lang);
eprintln!(" Config written: {}", path);
return 0;
}
Err(e) => { eprintln!(" Error: {}", e); return 1; }
}
}
eprintln!("squeez calibrate: analyzing compression characteristics...");
let analysis = quick_analysis();
let profile = select_profile(&analysis);
let tier = if analysis.avg_reduction_pct > 70.0
&& analysis.quality_pass_count == analysis.total_scenarios
{
"aggressive"
} else if analysis.avg_reduction_pct > 50.0 {
"balanced"
} else {
"conservative"
};
eprintln!(
" Profile: {} (avg reduction: {:.1}%, quality: {}/{})",
tier,
analysis.avg_reduction_pct,
analysis.quality_pass_count,
analysis.total_scenarios,
);
let lang = detect_lang();
eprintln!(" Language detected: {}", lang);
match write_config(&profile) {
Ok(path) => {
write_lang_to_config(&path, &lang);
eprintln!(" Config written: {}", path);
0
}
Err(e) => {
eprintln!(" Error: {}", e);
1
}
}
}
fn quick_analysis() -> BenchmarkAnalysis {
let cfg = Config::load();
let sample = generate_test_output();
let lines: Vec<String> = sample.lines().map(String::from).collect();
let original_len = lines.len();
let compressed = crate::filter::compress("git status", lines, &cfg);
let compressed_len = compressed.len();
let reduction = if original_len > 0 {
((original_len - compressed_len) as f64 / original_len as f64) * 100.0
} else {
0.0
};
let quality_pass = compressed.iter().any(|l| l.contains("modified:"))
|| compressed.iter().any(|l| l.contains("Changes"));
BenchmarkAnalysis {
avg_reduction_pct: reduction,
quality_pass_count: if quality_pass { 1 } else { 0 },
total_scenarios: 1,
}
}
fn generate_test_output() -> String {
let mut lines = Vec::new();
lines.push("On branch main".to_string());
lines.push("Your branch is up to date with 'origin/main'.".to_string());
lines.push("".to_string());
lines.push("Changes not staged for commit:".to_string());
lines.push(" (use \"git add <file>...\" to update what will be committed)".to_string());
for i in 0..30 {
lines.push(format!("\tmodified: src/file_{}.rs", i));
}
lines.push("".to_string());
lines.push("Untracked files:".to_string());
for i in 0..20 {
lines.push(format!("\tnew_file_{}.rs", i));
}
lines.join("\n")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn aggressive_profile_selection() {
let analysis = BenchmarkAnalysis {
avg_reduction_pct: 75.0,
quality_pass_count: 10,
total_scenarios: 10,
};
let p = select_profile(&analysis);
assert_eq!(p.max_lines, 120);
assert_eq!(p.dedup_min, 2);
}
#[test]
fn balanced_profile_selection() {
let analysis = BenchmarkAnalysis {
avg_reduction_pct: 60.0,
quality_pass_count: 8,
total_scenarios: 10,
};
let p = select_profile(&analysis);
assert_eq!(p.max_lines, 200);
}
#[test]
fn conservative_profile_selection() {
let analysis = BenchmarkAnalysis {
avg_reduction_pct: 25.0,
quality_pass_count: 5,
total_scenarios: 10,
};
let p = select_profile(&analysis);
assert_eq!(p.max_lines, 350);
assert_eq!(p.dedup_min, 5);
}
#[test]
fn config_generation() {
let p = CalibrationProfile::aggressive();
let ini = profile_to_config(&p);
assert!(ini.contains("max_lines = 120"));
assert!(ini.contains("dedup_min = 2"));
assert!(ini.contains("read_max_lines = 300"));
}
#[test]
fn balanced_config_omits_zero_budgets() {
let p = CalibrationProfile::balanced();
let ini = profile_to_config(&p);
assert!(!ini.contains("read_max_lines"));
assert!(!ini.contains("grep_max_results"));
}
}