repopilot 0.10.0

Local-first CLI for repository audit, architecture risk detection, baseline tracking, and CI-friendly code review.
Documentation
use repopilot::findings::types::Severity;
use repopilot::scan::config::ScanConfig;
use repopilot::scan::scanner::scan_path_with_config;
use std::fs;
use tempfile::tempdir;

#[test]
fn polyglot_scan_uses_knowledge_engine_context_without_flagging_paradigms() {
    let temp = tempdir().expect("temp dir");
    let root = temp.path();

    write(
        root.join("src/domain/user.rs"),
        "pub fn user_name() -> String { panic!(\"invalid domain state\") }\n",
    );
    write(
        root.join("src/main.rs"),
        "fn main() { let config = load_config().unwrap(); }\nfn load_config() -> Result<(), ()> { Ok(()) }\n",
    );
    write(
        root.join("src/users.ts"),
        "const names = users.filter(user => user.active).map(user => user.name);\n",
    );
    write(
        root.join("app/views.py"),
        "from fastapi import FastAPI\napp = FastAPI()\n",
    );

    let summary = scan_path_with_config(
        root,
        &ScanConfig {
            detect_missing_tests: false,
            ..ScanConfig::default()
        },
    )
    .expect("scan");

    assert!(
        summary
            .languages
            .iter()
            .any(|language| language.name == "Rust")
    );
    assert!(
        summary
            .languages
            .iter()
            .any(|language| language.name == "TypeScript")
    );
    assert!(
        summary
            .languages
            .iter()
            .any(|language| language.name == "Python")
    );

    let rust_findings: Vec<_> = summary
        .findings
        .iter()
        .filter(|finding| finding.rule_id == "language.rust.panic-risk")
        .collect();

    assert_eq!(rust_findings.len(), 2);
    assert!(
        rust_findings
            .iter()
            .any(|finding| finding.severity == Severity::High)
    );
    assert!(
        rust_findings
            .iter()
            .any(|finding| finding.severity == Severity::Low)
    );
    assert!(
        summary
            .findings
            .iter()
            .all(|finding| !finding.title.to_lowercase().contains("functional"))
    );
}

#[test]
fn node_cli_boundary_downgrades_process_exit() {
    let temp = tempdir().expect("temp dir");
    let root = temp.path();

    write(root.join("src/main.js"), "process.exit(1);\n");

    let summary = scan_path_with_config(
        root,
        &ScanConfig {
            detect_missing_tests: false,
            ..ScanConfig::default()
        },
    )
    .expect("scan");

    let finding = summary
        .findings
        .iter()
        .find(|finding| finding.rule_id == "language.javascript.runtime-exit-risk")
        .expect("process exit finding");

    assert_eq!(finding.severity, Severity::Low);
}

#[test]
fn generated_files_suppress_language_risk_noise() {
    let temp = tempdir().expect("temp dir");
    let root = temp.path();

    write(
        root.join("src/generated/client.go"),
        "package generated\nfunc Parse() { panic(\"generated\") }\n",
    );

    let summary = scan_path_with_config(
        root,
        &ScanConfig {
            detect_missing_tests: false,
            ..ScanConfig::default()
        },
    )
    .expect("scan");

    assert!(
        summary
            .findings
            .iter()
            .all(|finding| finding.rule_id != "language.go.panic-exit-risk")
    );
}

#[test]
fn no_audit_uses_hardcoded_language_allowlist_for_applicability() {
    let root = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    let source_files = [
        "src/audits/architecture/large_file.rs",
        "src/audits/code_quality/complexity.rs",
        "src/audits/code_quality/long_function/mod.rs",
    ];

    for relative_path in source_files {
        let content = fs::read_to_string(root.join(relative_path)).expect("read source file");
        for forbidden in ["fn is_supported", "fn is_code_language", "fn is_code_file"] {
            assert!(
                !content.contains(forbidden),
                "{relative_path} still contains local applicability helper `{forbidden}`"
            );
        }
    }
}

fn write(path: std::path::PathBuf, content: &str) {
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent).expect("create parent dir");
    }
    fs::write(path, content).expect("write fixture");
}