repopilot 0.17.0

Local-first CLI for reviewing Git changes, security boundaries, and blast radius before merge.
Documentation
#[test]
fn renders_header_with_risk_high() {
    let findings = vec![make_finding(
        "security.secret",
        "Hardcoded secret",
        Severity::Critical,
        FindingCategory::Security,
        "src/auth.rs",
        42,
    )];
    let summary = make_summary(findings);
    let output = render(&summary, &AiContextRenderOptions::default());
    assert!(output.contains("# RepoPilot AI Context — my-project"));
    assert!(output.contains("🔴 HIGH"));
    assert!(output.contains("1 critical"));
}


#[test]
fn renders_no_header_when_flag_set() {
    let summary = make_summary(vec![]);
    let opts = AiContextRenderOptions {
        no_header: true,
        ..Default::default()
    };
    let output = render(&summary, &opts);
    assert!(!output.contains("# RepoPilot AI Context"));
}


#[test]
fn focus_filters_to_security_only() {
    let findings = vec![
        make_finding(
            "security.secret",
            "Hardcoded secret",
            Severity::Critical,
            FindingCategory::Security,
            "src/auth.rs",
            1,
        ),
        make_finding(
            "architecture.large-file",
            "Large file",
            Severity::Medium,
            FindingCategory::Architecture,
            "src/big.rs",
            1,
        ),
    ];
    let summary = make_summary(findings);
    let opts = AiContextRenderOptions {
        focus: Some(AiFocusCategory::Security),
        ..Default::default()
    };
    let output = render(&summary, &opts);
    assert!(output.contains("Security"));
    assert!(!output.contains("Architecture"));
}


#[test]
fn focus_quality_includes_testing_and_code_quality() {
    let findings = vec![
        make_finding(
            "code-quality.todo",
            "TODO marker",
            Severity::Low,
            FindingCategory::CodeQuality,
            "src/a.rs",
            1,
        ),
        make_finding(
            "testing.missing-tests",
            "Missing tests",
            Severity::Medium,
            FindingCategory::Testing,
            "src/b.rs",
            1,
        ),
        make_finding(
            "security.secret",
            "Secret",
            Severity::Critical,
            FindingCategory::Security,
            "src/c.rs",
            1,
        ),
    ];
    let summary = make_summary(findings);
    let opts = AiContextRenderOptions {
        focus: Some(AiFocusCategory::Quality),
        ..Default::default()
    };
    let output = render(&summary, &opts);
    assert!(output.contains("Code Quality") || output.contains("Testing"));
    assert!(!output.contains("Security"));
}


#[test]
fn finding_entries_include_context_confidence_and_fix() {
    let findings = vec![make_finding(
        "code-quality.long-function",
        "Long React component",
        Severity::Low,
        FindingCategory::CodeQuality,
        "src/Profile.tsx",
        12,
    )];
    let summary = make_summary(findings);
    let opts = AiContextRenderOptions {
        no_header: true,
        ..Default::default()
    };

    let output = render(&summary, &opts);

    assert!(output.contains("> **Confidence:** MEDIUM"));
    assert!(output.contains("> **Context:** Description for Long React component"));
    assert!(output.contains("> **Fix:**"));
}


#[test]
fn small_budget_renders_truncation_notice() {
    let findings: Vec<Finding> = (0..8)
        .map(|i| {
            make_finding(
                "security.secret",
                &format!("Hardcoded secret {i}"),
                Severity::High,
                FindingCategory::Security,
                &format!("src/auth_{i}.rs"),
                i + 1,
            )
        })
        .collect();
    let summary = make_summary(findings);
    let opts = AiContextRenderOptions {
        budget_tokens: 20,
        no_header: true,
        ..Default::default()
    };
    let output = render(&summary, &opts);
    assert!(output.contains("Output truncated to stay within token budget"));
}