repopilot 0.19.0

Local-first CLI for reviewing Git changes, security boundaries, and blast radius before merge.
Documentation
use super::*;
use crate::findings::types::{Confidence, Evidence, FindingCategory, Severity};
use crate::risk::{RiskAssessment, RiskPriority};
use std::path::PathBuf;

fn finding(rule_id: &str, category: FindingCategory, severity: Severity) -> Finding {
    let mut finding = Finding {
        rule_id: rule_id.to_string(),
        category,
        severity,
        confidence: Confidence::High,
        ..Default::default()
    };
    finding.populate_rule_metadata();
    finding
}

fn finding_with_path(
    rule_id: &str,
    category: FindingCategory,
    severity: Severity,
    path: &str,
) -> Finding {
    let mut finding = finding(rule_id, category, severity);
    finding.evidence = vec![Evidence {
        path: PathBuf::from(path),
        line_start: 1,
        line_end: None,
        snippet: "process.exit(1);".to_string(),
    }];
    finding
}

fn finding_with_priority(
    rule_id: &str,
    category: FindingCategory,
    severity: Severity,
    priority: RiskPriority,
) -> Finding {
    let mut finding = finding(rule_id, category, severity);
    finding.risk = RiskAssessment {
        priority,
        ..RiskAssessment::default()
    };
    finding
}

#[test]
fn default_profile_hides_testing_gaps_by_intent() {
    let finding = finding(
        "testing.source-without-test",
        FindingCategory::Testing,
        Severity::Low,
    );

    let decision = classify_visibility(&finding);

    assert_eq!(decision.intent, FindingIntent::TestingGap);
    assert!(!decision.visible_by_default);
}

#[test]
fn default_profile_hides_high_maintainability_signals() {
    let finding = finding(
        "architecture.large-file",
        FindingCategory::Architecture,
        Severity::High,
    );

    let decision = classify_visibility(&finding);

    assert_eq!(decision.intent, FindingIntent::Maintainability);
    assert!(!decision.visible_by_default);
}

#[test]
fn default_profile_hides_deep_relative_imports() {
    let finding = finding(
        "architecture.deep-relative-imports",
        FindingCategory::Architecture,
        Severity::High,
    );

    let decision = classify_visibility(&finding);

    assert_eq!(decision.intent, FindingIntent::Maintainability);
    assert!(!decision.visible_by_default);
}

#[test]
fn default_profile_hides_low_confidence_barrel_file_risk() {
    let finding = finding(
        "architecture.barrel-file-risk",
        FindingCategory::Architecture,
        Severity::Low,
    );

    let decision = classify_visibility(&finding);

    assert_eq!(finding.confidence, Confidence::Low);
    assert_eq!(decision.intent, FindingIntent::Maintainability);
    assert!(!decision.visible_by_default);
}

#[test]
fn default_profile_hides_deep_directory_nesting() {
    let finding = finding(
        "architecture.deep-directory-nesting",
        FindingCategory::Architecture,
        Severity::Low,
    );

    let decision = classify_visibility(&finding);

    assert_eq!(decision.intent, FindingIntent::Maintainability);
    assert!(!decision.visible_by_default);
}

#[test]
fn default_profile_hides_too_many_modules_even_at_high_priority() {
    let finding = finding_with_priority(
        "architecture.too-many-modules",
        FindingCategory::Architecture,
        Severity::Medium,
        RiskPriority::P1,
    );

    let decision = classify_visibility(&finding);

    assert_eq!(decision.intent, FindingIntent::Maintainability);
    assert!(!decision.visible_by_default);
    assert_eq!(
        decision.reason,
        "experimental rules are strict-mode suggestions by default"
    );
}

#[test]
fn default_profile_hides_high_priority_broad_maintainability_heuristics() {
    let finding = finding_with_priority(
        "code-quality.long-function",
        FindingCategory::CodeQuality,
        Severity::Medium,
        RiskPriority::P1,
    );

    let decision = classify_visibility(&finding);

    assert_eq!(decision.intent, FindingIntent::Maintainability);
    assert!(!decision.visible_by_default);
}

#[test]
fn default_profile_keeps_stable_import_graph_architecture_risks() {
    let finding = finding_with_priority(
        "architecture.excessive-fan-out",
        FindingCategory::Architecture,
        Severity::Medium,
        RiskPriority::P2,
    );

    let decision = classify_visibility(&finding);

    assert_eq!(decision.intent, FindingIntent::ActionableRisk);
    assert!(decision.visible_by_default);
}

#[test]
fn default_profile_keeps_manifest_backed_package_boundary_violations() {
    let mut finding = finding(
        "architecture.package-boundary-violation",
        FindingCategory::Architecture,
        Severity::Medium,
    );
    finding.evidence = vec![Evidence {
        path: PathBuf::from("packages/app/src/main.ts"),
        line_start: 3,
        line_end: None,
        snippet: "imports internal file: packages/core/src/private.ts".to_string(),
    }];

    let decision = classify_visibility(&finding);

    assert_eq!(finding.confidence, Confidence::High);
    assert_eq!(decision.intent, FindingIntent::ActionableRisk);
    assert!(decision.visible_by_default);
}

#[test]
fn default_profile_hides_configured_package_boundary_violations() {
    let mut finding = finding(
        "architecture.package-boundary-violation",
        FindingCategory::Architecture,
        Severity::Medium,
    );
    finding.confidence = Confidence::Medium;
    finding.evidence = vec![Evidence {
        path: PathBuf::from("packages/app/src/main.ts"),
        line_start: 3,
        line_end: None,
        snippet: "imports internal file: packages/core/src/private.ts".to_string(),
    }];

    let decision = classify_visibility(&finding);

    assert_eq!(decision.intent, FindingIntent::Informational);
    assert!(!decision.visible_by_default);
}

#[test]
fn default_profile_keeps_validated_secret_candidates() {
    let finding = finding(
        "security.secret-candidate",
        FindingCategory::Security,
        Severity::High,
    );

    let decision = classify_visibility(&finding);

    assert_eq!(decision.intent, FindingIntent::SecurityRisk);
    assert!(decision.visible_by_default);
}

#[test]
fn default_profile_keeps_stable_security_findings() {
    let finding = finding(
        "security.env-file-committed",
        FindingCategory::Security,
        Severity::Critical,
    );

    let decision = classify_visibility(&finding);

    assert_eq!(decision.intent, FindingIntent::SecurityRisk);
    assert!(decision.visible_by_default);
}

#[test]
fn default_profile_hides_framework_style_rules() {
    let finding = finding(
        "framework.react-native.old-architecture",
        FindingCategory::Framework,
        Severity::Medium,
    );

    let decision = classify_visibility(&finding);

    assert_eq!(decision.intent, FindingIntent::Maintainability);
    assert!(!decision.visible_by_default);
}

#[test]
fn default_profile_keeps_manifest_backed_framework_risks() {
    let finding = finding(
        "framework.react-native.old-react-navigation",
        FindingCategory::Framework,
        Severity::Medium,
    );

    let decision = classify_visibility(&finding);

    assert_eq!(decision.intent, FindingIntent::ActionableRisk);
    assert!(decision.visible_by_default);
}

#[test]
fn default_profile_hides_script_process_exit() {
    let finding = finding_with_path(
        "language.javascript.runtime-exit-risk",
        FindingCategory::CodeQuality,
        Severity::High,
        "scripts/verify-release.mjs",
    );

    let decision = classify_visibility(&finding);

    assert_eq!(decision.intent, FindingIntent::RuntimeRisk);
    assert!(!decision.visible_by_default);
}

#[test]
fn default_profile_hides_windows_script_process_exit() {
    let finding = finding_with_path(
        "language.javascript.runtime-exit-risk",
        FindingCategory::CodeQuality,
        Severity::High,
        r"tools\verify-release.mjs",
    );

    let decision = classify_visibility(&finding);

    assert_eq!(decision.intent, FindingIntent::RuntimeRisk);
    assert!(!decision.visible_by_default);
}

#[test]
fn default_profile_keeps_source_process_exit() {
    let finding = finding_with_path(
        "language.javascript.runtime-exit-risk",
        FindingCategory::CodeQuality,
        Severity::High,
        "src/runtime/server.ts",
    );

    let decision = classify_visibility(&finding);

    assert_eq!(decision.intent, FindingIntent::RuntimeRisk);
    assert!(decision.visible_by_default);
}