repopilot 0.7.0

Local-first CLI for repository audit, architecture risk detection, baseline tracking, and CI-friendly code review.
Documentation
use repopilot::findings::types::{Evidence, Finding, FindingCategory, Severity};
use repopilot::frameworks::ReactNativeArchitectureProfile;
use repopilot::output::{OutputFormat, render_scan_summary};
use repopilot::scan::types::{LanguageSummary, ScanSummary};
use std::path::PathBuf;

#[test]
fn renders_markdown_scan_summary() {
    let summary = ScanSummary {
        root_path: PathBuf::from("demo-project"),
        files_count: 2,
        directories_count: 1,
        lines_of_code: 10,
        skipped_files_count: 0,
        skipped_bytes: 0,
        languages: vec![
            LanguageSummary {
                name: "Rust".to_string(),
                files_count: 1,
            },
            LanguageSummary {
                name: "TypeScript".to_string(),
                files_count: 1,
            },
        ],
        findings: vec![Finding {
            id: "code-marker.todo.src/main.rs:7".to_string(),
            rule_id: "code-marker.todo".to_string(),
            title: "TODO marker found".to_string(),
            description: "A TODO marker was found in the codebase and should be reviewed."
                .to_string(),
            category: FindingCategory::CodeQuality,
            severity: Severity::Low,
            evidence: vec![Evidence {
                path: PathBuf::from("src/main.rs"),
                line_start: 7,
                line_end: None,
                snippet: "// TODO: improve architecture".to_string(),
            }],
            workspace_package: None,
            docs_url: None,
        }],
        detected_frameworks: vec![],
        framework_projects: vec![],
        react_native: None,
        coupling_graph: None,
        scan_duration_us: 0,
    };

    let output = render_scan_summary(&summary, OutputFormat::Markdown)
        .expect("failed to render markdown summary");

    assert!(output.contains("# RepoPilot Scan Report"));
    assert!(output.contains("## Summary"));
    assert!(output.contains("## Languages"));
    assert!(output.contains("## Findings"));
    assert!(output.contains("| LOW | `code-marker.todo` | TODO marker found |"));
    assert!(output.contains("`src/main.rs:7`"));
    assert!(output.contains("- **Path:** `demo-project`"));
    assert!(output.contains("- **Files analyzed:** 2"));
    assert!(output.contains("| Rust | 1 |"));
    assert!(output.contains("| TypeScript | 1 |"));
    assert!(output.contains("| TODO | `src/main.rs` | 7 | // TODO: improve architecture |"));
}

#[test]
fn renders_empty_markdown_sections() {
    let summary = ScanSummary {
        root_path: PathBuf::from("empty-project"),
        files_count: 0,
        directories_count: 0,
        lines_of_code: 0,
        skipped_files_count: 0,
        skipped_bytes: 0,
        languages: vec![],
        findings: vec![],
        detected_frameworks: vec![],
        framework_projects: vec![],
        react_native: None,
        coupling_graph: None,
        scan_duration_us: 0,
    };

    let output = render_scan_summary(&summary, OutputFormat::Markdown)
        .expect("failed to render markdown summary");

    assert!(output.contains("No languages detected."));
    assert!(output.contains("No findings found."));
    assert!(output.contains("No TODO/FIXME/HACK markers found."));
    // Non-RN project must not render the React Native architecture section
    assert!(!output.contains("### React Native"));
}

#[test]
fn renders_react_native_architecture_section_when_profile_present() {
    let summary = ScanSummary {
        root_path: PathBuf::from("rn-project"),
        files_count: 5,
        directories_count: 2,
        lines_of_code: 100,
        skipped_files_count: 0,
        skipped_bytes: 0,
        languages: vec![],
        findings: vec![],
        detected_frameworks: vec![],
        framework_projects: vec![],
        react_native: Some(ReactNativeArchitectureProfile {
            detected: true,
            react_native_version: Some("0.73.0".to_string()),
            has_ios: true,
            has_android: true,
            has_metro_config: false,
            has_react_native_config: false,
            has_codegen_config: false,
            android_new_arch_enabled: Some(false),
            ios_new_arch_enabled: None,
            hermes_enabled: Some(true),
            android_gradle_properties_found: true,
            ios_podfile_found: false,
            ..ReactNativeArchitectureProfile::default()
        }),
        coupling_graph: None,
        scan_duration_us: 0,
    };

    let output = render_scan_summary(&summary, OutputFormat::Markdown)
        .expect("failed to render markdown summary");

    assert!(output.contains("### React Native"));
    assert!(output.contains("0.73.0"));
    assert!(output.contains("**iOS:** detected"));
    assert!(output.contains("**Android:** detected"));
    assert!(output.contains("**Android New Architecture:** disabled"));
    assert!(output.contains("**iOS New Architecture:** unknown"));
    assert!(output.contains("**Hermes:** enabled"));
    assert!(output.contains("**Codegen config:** missing"));
}

#[test]
fn react_native_section_absent_when_profile_is_none() {
    let summary = ScanSummary {
        root_path: PathBuf::from("web-project"),
        react_native: None,
        ..ScanSummary::default()
    };

    let output = render_scan_summary(&summary, OutputFormat::Markdown)
        .expect("failed to render markdown summary");

    assert!(!output.contains("### React Native"));
}