sparrow-cli 0.9.3

A local-first Rust agent cockpit — route, run, replay, rewind
use serde::Serialize;

#[derive(Debug, Clone, Serialize)]
pub struct CommitPlan {
    pub message: String,
    pub staged_stat: String,
    pub secret_findings: Vec<String>,
}

pub fn build_commit_plan(
    message: Option<String>,
    staged_stat: String,
    staged_diff: &str,
) -> CommitPlan {
    let secret_findings = scan_diff_for_secret_patterns(staged_diff);
    let message = message.unwrap_or_else(|| generated_message(&staged_stat));
    CommitPlan {
        message,
        staged_stat,
        secret_findings,
    }
}

pub fn scan_diff_for_secret_patterns(diff: &str) -> Vec<String> {
    let mut findings = Vec::new();
    for (idx, line) in diff.lines().enumerate() {
        if !line.starts_with('+') || line.starts_with("+++") {
            continue;
        }
        let lower = line.to_ascii_lowercase();
        let suspicious = lower.contains("api_key")
            || lower.contains("secret")
            || lower.contains("token")
            || line.contains("sk-")
            || line.contains("ghp_")
            || line.contains("xoxb-")
            || line.contains("AKIA");
        if suspicious {
            findings.push(format!("line {}: {}", idx + 1, line.trim()));
        }
    }
    findings
}

fn generated_message(staged_stat: &str) -> String {
    if staged_stat.contains("docs/") || staged_stat.contains("README") {
        "docs: update Sparrow release materials".into()
    } else if staged_stat.contains("Cargo.toml") || staged_stat.contains("Cargo.lock") {
        "chore: update Sparrow workspace configuration".into()
    } else if staged_stat.contains("console.html") {
        "feat: improve Sparrow console experience".into()
    } else {
        "chore: update Sparrow".into()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn commit_plan_detects_secret_like_added_lines() {
        let plan = build_commit_plan(
            None,
            "Cargo.toml | 2 +".into(),
            "+OPENAI_API_KEY=sk-test\n context\n",
        );
        assert_eq!(
            plan.message,
            "chore: update Sparrow workspace configuration"
        );
        assert_eq!(plan.secret_findings.len(), 1);
    }
}