repopilot 0.15.0

Local-first CLI for repository audit, architecture risk detection, baseline tracking, and CI-friendly code review.
Documentation
use repopilot::analysis::{ArchitectureClassifier, FileRole, LanguageFamily, ModuleKind};
use repopilot::scan::config::ScanConfig;
use repopilot::scan::facts::FileFacts;
use std::collections::BTreeMap;
use std::path::PathBuf;

#[test]
fn test_default_module_mappings() {
    let config = ScanConfig::default();
    let classifier = ArchitectureClassifier::new(&config.module_mappings);

    let test_cases = vec![
        (
            "src/features/billing/domain/service.rs",
            ModuleKind::Domain,
            FileRole::Production,
        ),
        (
            "src/ui/components/Button.tsx",
            ModuleKind::Ui,
            FileRole::Production,
        ),
        (
            "src/infra/db/client.go",
            ModuleKind::Infrastructure,
            FileRole::Production,
        ),
        (
            "src/shared/utils/date.ts",
            ModuleKind::Shared,
            FileRole::Production,
        ),
        (
            "tests/integration/test_helper.py",
            ModuleKind::Unknown,
            FileRole::Test,
        ),
        (
            "src/cli/commands/scan.rs",
            ModuleKind::Cli,
            FileRole::Production,
        ),
        (
            "docs/index.md",
            ModuleKind::Unknown,
            FileRole::Documentation,
        ),
        (
            "tests/fixtures/sample.json",
            ModuleKind::Unknown,
            FileRole::Fixture,
        ),
        (
            "src/generated/types.ts",
            ModuleKind::Unknown,
            FileRole::Generated,
        ),
        ("cargo.toml", ModuleKind::Unknown, FileRole::Config),
    ];

    for (path_str, expected_module, expected_role) in test_cases {
        let file = FileFacts {
            path: PathBuf::from(path_str),
            language: None,
            non_empty_lines: 10,
            branch_count: 0,
            imports: vec![],
            content: None,
            has_inline_tests: false,
        };

        let context = classifier.classify(&file);
        assert_eq!(
            context.module_kind, expected_module,
            "Path '{}' classified as {:?}, expected {:?}",
            path_str, context.module_kind, expected_module
        );
        assert_eq!(
            context.file_role, expected_role,
            "Path '{}' classified as {:?}, expected {:?}",
            path_str, context.file_role, expected_role
        );
    }
}

#[test]
fn test_custom_module_mappings() {
    let mut custom_mappings = BTreeMap::new();
    custom_mappings.insert("domain".to_string(), vec!["**/core/models/**".to_string()]);
    custom_mappings.insert("ui".to_string(), vec!["**/views/**".to_string()]);

    let classifier = ArchitectureClassifier::new(&custom_mappings);

    let domain_file = FileFacts {
        path: PathBuf::from("src/core/models/user.rs"),
        language: None,
        non_empty_lines: 10,
        branch_count: 0,
        imports: vec![],
        content: None,
        has_inline_tests: false,
    };
    let context = classifier.classify(&domain_file);
    assert_eq!(context.module_kind, ModuleKind::Domain);

    let ui_file = FileFacts {
        path: PathBuf::from("src/views/MainScreen.tsx"),
        language: None,
        non_empty_lines: 10,
        branch_count: 0,
        imports: vec![],
        content: None,
        has_inline_tests: false,
    };
    let context = classifier.classify(&ui_file);
    assert_eq!(context.module_kind, ModuleKind::Ui);

    let unknown_file = FileFacts {
        path: PathBuf::from("src/features/billing/domain/service.rs"),
        language: None,
        non_empty_lines: 10,
        branch_count: 0,
        imports: vec![],
        content: None,
        has_inline_tests: false,
    };
    let context = classifier.classify(&unknown_file);
    assert_eq!(context.module_kind, ModuleKind::Unknown);
}

#[test]
fn test_language_families() {
    let config = ScanConfig::default();
    let classifier = ArchitectureClassifier::new(&config.module_mappings);

    let test_cases = vec![
        ("src/main.rs", "Rust", LanguageFamily::CurlyBrace),
        ("src/index.ts", "TypeScript", LanguageFamily::CurlyBrace),
        ("src/app.py", "Python", LanguageFamily::Python),
        ("src/main.go", "Go", LanguageFamily::Go),
        ("script.sh", "Shell", LanguageFamily::Shell),
        ("config.json", "JSON", LanguageFamily::Markup),
        ("readme.md", "Markdown", LanguageFamily::Markup),
    ];

    for (path_str, language_str, expected_family) in test_cases {
        let file = FileFacts {
            path: PathBuf::from(path_str),
            language: Some(language_str.to_string()),
            non_empty_lines: 10,
            branch_count: 0,
            imports: vec![],
            content: None,
            has_inline_tests: false,
        };

        let context = classifier.classify(&file);
        assert_eq!(
            context.language_family, expected_family,
            "Language '{}' classified as {:?}, expected {:?}",
            language_str, context.language_family, expected_family
        );
    }
}