git-slop 0.10.0

A deterministic repository token-defragmenter for humans and AI agents.
Documentation
use super::rollup::{build_health_rollup_from_values, distribution, finding_for_file};
use super::*;
use crate::model::FileAnalysis;
use serde_json::{Value, json};

#[test]
fn percentile_interpolates_and_concentration_is_bounded() {
    let stats = distribution(&[100, 200, 300, 400]);
    assert_eq!(stats["p50"], 250.0);
    assert_eq!(stats["p90"], 370.0);
    assert_eq!(stats["max"], 400);
    assert_eq!(stats["top_10_share"], 1.0);
}

#[test]
fn github_blob_links_support_https_and_ssh_remotes() {
    for remote in [
        "https://github.com/coreycoto/git-slop.git",
        "git@github.com:coreycoto/git-slop.git",
    ] {
        let report = json!({
            "repo": {
                "git_remote_url": remote,
                "head_commit": "abc123"
            }
        });
        assert_eq!(
            github_blob_url(&report, "src/a file.rs").as_deref(),
            Some("https://github.com/coreycoto/git-slop/blob/abc123/src/a%20file.rs")
        );
    }
}

#[test]
fn health_numbers_use_stable_human_formatting() {
    assert_eq!(super::render::format_number(1_234.0), "1,234");
    assert_eq!(super::render::format_number(1_234.5), "1,234.50");
    assert_eq!(super::render::format_score(1_234.5), "1,234.5");
    assert_eq!(super::render::format_percent(0.123_55), "12.4%");
    assert_eq!(
        super::render::format_finding_reason(
            "10001 tokens exceed the configured fail threshold",
            10_001,
        ),
        "10,001 tokens exceed the configured fail threshold"
    );
    assert_eq!(
        super::render::folder_next_command("."),
        "git-slop explain --path ."
    );
}

#[test]
fn folder_risks_explain_direct_triggers_and_rank_one_agent_descendant() {
    let report: Value = serde_json::from_str(include_str!(
        "../../tests/fixtures/reports/health_folder_guidance_report.json"
    ))
    .expect("folder guidance fixture");

    let rendered = render_health_from_report(&report).expect("health renders");

    assert!(rendered.contains("Context/load band"));
    assert!(rendered.contains("Maintenance pressure"));
    assert!(rendered.contains("Review severity"));
    assert!(rendered.contains("direct files"));
    assert!(rendered.contains("direct tokens"));
    for path in ["src/files-only/", "src/tokens-only/", "src/both/"] {
        assert!(
            rendered.contains(&format!("git-slop explain --path {path}")),
            "missing next command for {path}"
        );
    }
    assert!(rendered.contains("src/files-only/nested/winner.rs"));
    assert!(!rendered.contains("src/files-only/a.rs"));
    assert!(rendered.contains("src/files-only/generated.json"));
    assert!(rendered.contains("score 1,234.5"));
    assert!(rendered.contains("% of parent"));
}

#[test]
fn report_only_renderer_is_compatible_with_minimal_schema_four() {
    let report = json!({
        "schema_version": 4,
        "generated_at": "2026-07-30T10:00:00Z",
        "repo": {
            "repo_name": "fixture",
            "branch": "main",
            "head_commit": "abc123",
            "git_remote_url": "https://github.com/example/fixture.git"
        },
        "config": {
            "tokenization": {"context_bands": {
                "compact_max_tokens": 3072,
                "healthy_max_tokens": 8000,
                "warning_max_tokens": 10000
            }}
        },
        "stats": {},
        "files": [{
            "path": "src/lib.rs",
            "profile": "agent_context",
            "language": "Rust",
            "classification": "source",
            "tokens": 9001,
            "context_band": "warning",
            "slop_band": "high",
            "slop_score": 70.0,
            "reason_codes": ["high_token_cost"]
        }],
        "folders": []
    });
    let rendered = render_health_from_report(&report).expect("health renders");
    assert!(rendered.starts_with("# Repository Health"));
    assert!(rendered.contains("src/lib.rs"));
    assert!(rendered.contains("Actionable Findings"));
    assert!(rendered.contains("blob/abc123/src/lib.rs"));
}

#[test]
fn markdown_renderer_never_emits_raw_control_characters_from_repository_fields() {
    let report = json!({
        "schema_version": 4,
        "generated_at": "2026-07-30T10:00:00Z",
        "repo": {
            "repo_name": "fixture",
            "branch": "main\n::stop-commands::token",
            "head_commit": "abc123"
        },
        "config": {},
        "stats": {},
        "files": [{
            "path": "src/file.rs\n::error title=forged::message",
            "profile": "agent_context",
            "language": "Rust",
            "classification": "source",
            "tokens": 12_000,
            "context_band": "critical",
            "slop_band": "critical",
            "slop_score": 90.0,
            "reason_codes": ["critical_token_cost"]
        }],
        "folders": []
    });

    let rendered = render_health_from_report(&report).expect("health renders");

    assert!(!rendered.contains("\n::error"));
    assert!(!rendered.contains("\n::stop-commands"));
    assert!(rendered.contains(r"\n::error"));
    assert!(rendered.contains(r"\n::stop-commands"));
}

#[test]
fn folder_health_includes_data_context_consistently() {
    let files = vec![
        json!({
            "path": "src/lib.rs",
            "profile": "agent_context",
            "language": "Rust",
            "tokens": 100,
            "context_band": "compact"
        }),
        json!({
            "path": "data/large.json",
            "profile": "data_context",
            "language": "JSON",
            "tokens": 1_000_000,
            "context_band": "refactor_required"
        }),
    ];
    let folders = vec![
        json!({
            "path": ".",
            "direct_file_count": 0,
            "direct_tokens": 0,
            "tokens": 1_000_100,
            "health_band": "refactor_required"
        }),
        json!({
            "path": "src",
            "direct_file_count": 1,
            "direct_tokens": 100,
            "tokens": 100,
            "health_band": "compact"
        }),
        json!({
            "path": "data",
            "direct_file_count": 1,
            "direct_tokens": 1_000_000,
            "tokens": 1_000_000,
            "health_band": "refactor_required"
        }),
    ];

    let rollup = build_health_rollup_from_values(&files, &folders, &Value::Null);

    assert_eq!(rollup.file_band_counts["compact"], 1);
    assert_eq!(rollup.file_band_counts["refactor_required"], 1);
    assert_eq!(rollup.folder_band_counts["compact"], 2);
    assert_eq!(rollup.folder_band_counts["refactor_required"], 1);
    assert_eq!(rollup.folder_distribution["count"], 3);
    assert_eq!(rollup.folder_distribution["total"], 1_000_100);
}

#[test]
fn finding_humanizes_stable_reason_codes() {
    let file = serde_json::to_value(FileAnalysis {
        path: "src/lib.rs".to_string(),
        bytes: 1,
        lines: 1,
        blank_lines: 0,
        code_lines: 1,
        comment_lines: 0,
        language: "Rust".to_string(),
        profile: "agent_context".to_string(),
        classification: "source".to_string(),
        has_inline_tests: false,
        tokens: 10_001,
        context_band: "critical".to_string(),
        context_pressure: 1.0,
        content_fingerprint: String::new(),
        structural_tokens: vec![],
        structural_token_count: 0,
        top_structural_terms: vec![],
        age_days: 1,
        revisions_window: 20,
        recency_weighted_commits: 1.0,
        added_window: 1,
        deleted_window: 0,
        churn_lines_window: 1,
        line_churn_window: 1,
        token_churn_window: 1,
        relative_churn_window: 1.0,
        late_churn_spike: 0.0,
        author_count_window: 1,
        author_entropy: 0.0,
        top_author_share: 1.0,
        days_since_non_bot_edit: Some(1),
        recent_maintainer_diversity: 1,
        age_pressure: 0.0,
        revision_norm: 1.0,
        relative_churn_norm: 1.0,
        churn_pressure: 1.0,
        slop_score: 80.0,
        slop_band: "critical".to_string(),
        reason_codes: vec!["critical_token_cost".to_string()],
        costs: json!({}),
        overlays: json!({}),
    })
    .expect("serialize");
    let finding = finding_for_file(&file, &Value::Null).expect("finding");
    assert_eq!(finding.severity, "error");
    assert_eq!(
        finding.reasons,
        vec!["exceeds the configured context budget"]
    );
}