repopilot 0.18.0

Local-first CLI for reviewing Git changes, security boundaries, and blast radius before merge.
Documentation
#[test]
fn old_arch_flagged_when_no_app_json() {
    let dir = tempdir().unwrap();
    let findings = ReactNativeOldArchAudit.audit(&facts_for(dir.path()), &ScanConfig::default());
    assert_eq!(findings.len(), 1);
    assert_eq!(
        findings[0].rule_id,
        "framework.react-native.old-architecture"
    );
}


#[test]
fn old_arch_not_flagged_when_enabled() {
    let dir = tempdir().unwrap();
    write!(
        std::fs::File::create(dir.path().join("app.json")).unwrap(),
        r#"{{"expo": {{"newArchEnabled": true}}}}"#
    )
    .unwrap();
    let mut facts = facts_for(dir.path());
    facts.react_native = Some(ReactNativeArchitectureProfile {
        detected: true,
        has_expo_config: true,
        expo_new_arch_enabled: Some(true),
        ..ReactNativeArchitectureProfile::default()
    });
    let findings = ReactNativeOldArchAudit.audit(&facts, &ScanConfig::default());
    assert!(findings.is_empty());
}


#[test]
fn old_arch_not_flagged_when_enabled_in_rn_config() {
    let dir = tempdir().unwrap();
    writeln!(
        std::fs::File::create(dir.path().join("react-native.config.js")).unwrap(),
        "module.exports = {{ newArchEnabled: true }};"
    )
    .unwrap();
    let mut facts = facts_for(dir.path());
    facts.react_native = Some(ReactNativeArchitectureProfile {
        detected: true,
        expo_new_arch_enabled: Some(true),
        ..ReactNativeArchitectureProfile::default()
    });
    let findings = ReactNativeOldArchAudit.audit(&facts, &ScanConfig::default());
    assert!(findings.is_empty());
}


#[test]
fn architecture_mismatch_is_flagged() {
    let dir = tempdir().unwrap();
    let mut facts = facts_for(dir.path());
    facts.react_native = Some(ReactNativeArchitectureProfile {
        detected: true,
        android_new_arch_enabled: Some(false),
        ios_new_arch_enabled: Some(true),
        architecture_mismatch: true,
        ..ReactNativeArchitectureProfile::default()
    });

    let findings = ReactNativeArchitectureMismatchAudit.audit(&facts, &ScanConfig::default());

    assert_eq!(findings.len(), 1);
    assert_eq!(
        findings[0].rule_id,
        "framework.react-native.architecture-mismatch"
    );
    assert_eq!(findings[0].severity, Severity::High);
}


#[test]
fn hermes_mismatch_is_flagged() {
    let dir = tempdir().unwrap();
    let mut facts = facts_for(dir.path());
    facts.react_native = Some(ReactNativeArchitectureProfile {
        detected: true,
        android_hermes_enabled: Some(true),
        ios_hermes_enabled: Some(false),
        hermes_mismatch: true,
        ..ReactNativeArchitectureProfile::default()
    });

    let findings = HermesMismatchAudit.audit(&facts, &ScanConfig::default());

    assert_eq!(findings.len(), 1);
    assert_eq!(
        findings[0].rule_id,
        "framework.react-native.hermes-mismatch"
    );
    assert_eq!(findings[0].severity, Severity::Medium);
}


#[test]
fn codegen_missing_is_flagged_when_turbo_module_signal_exists() {
    let dir = tempdir().unwrap();
    let mut facts = facts_for(dir.path());
    facts.react_native = Some(ReactNativeArchitectureProfile {
        detected: true,
        has_codegen_config: false,
        ..ReactNativeArchitectureProfile::default()
    });
    facts.files.push(jsx_file(
            &dir,
            "NativeLocalStorage.ts",
            "import type { TurboModule } from 'react-native';\nexport interface Spec extends TurboModule {}\n",
        ));

    let findings = ReactNativeCodegenMissingAudit.audit(&facts, &ScanConfig::default());

    assert_eq!(findings.len(), 1);
    assert_eq!(
        findings[0].rule_id,
        "framework.react-native.codegen-missing"
    );
}