git-slop 0.16.4

A deterministic repository token-defragmenter for humans and AI agents.
Documentation
fn print_find_estimate(
    payload: &Value,
    normalized_scope: Option<&str>,
    format: DisplayFormat,
) -> Result<()> {
    match format {
        DisplayFormat::Json => print_text(&render_json(payload)?),
        DisplayFormat::Yaml => print_text(&serde_yaml::to_string(payload)?),
        DisplayFormat::Text => print_text(&render_find_estimate_text(payload, normalized_scope)),
    }
    Ok(())
}

fn render_find_estimate_text(payload: &Value, normalized_scope: Option<&str>) -> String {
    let estimate = &payload["estimate"];
    let mib = |key: &str| {
        estimate[key]
            .as_u64()
            .unwrap_or_default()
            .div_ceil(1024 * 1024)
    };
    [
        "Git Slop scan estimate".to_string(),
        format!(
            "- scope: {}",
            normalized_scope.unwrap_or("all tracked paths")
        ),
        format!("- tracked paths: {}", estimate["tracked_path_count"]),
        format!(
            "- peak memory: ~{} MiB ({}-{} MiB; {} MiB budget)",
            mib("estimated_peak_memory_bytes"),
            mib("estimated_peak_memory_low_bytes"),
            mib("estimated_peak_memory_high_bytes"),
            mib("memory_budget_bytes")
        ),
        format!(
            "- cache/report: ~{} MiB / ~{} MiB",
            mib("estimated_cache_bytes"),
            mib("estimated_report_bytes")
        ),
        format!(
            "- time: ~{}s cold / ~{}s warm",
            estimate["estimated_seconds_cold"], estimate["estimated_seconds_warm"]
        ),
        format!("- inodes: ~{}", estimate["estimated_inode_count"]),
        format!(
            "- cache assumptions: {}",
            estimate["cache_assumptions"]
                .as_array()
                .into_iter()
                .flatten()
                .filter_map(Value::as_str)
                .collect::<Vec<_>>()
                .join("; ")
        ),
        format!(
            "- confidence: {}",
            estimate["confidence"].as_str().unwrap_or("unknown")
        ),
        "Next: run `git slop find` when the estimate fits your budget.".to_string(),
    ]
    .join("\n")
}

#[cfg(test)]
mod estimate_render_tests {
    use super::*;

    #[test]
    fn text_receipt_rounds_bytes_up_and_keeps_scope_and_assumptions() {
        let payload = json!({
            "estimate": {
                "tracked_path_count": 12,
                "estimated_peak_memory_bytes": 1_048_577,
                "estimated_peak_memory_low_bytes": 1,
                "estimated_peak_memory_high_bytes": 2_097_153,
                "memory_budget_bytes": 4_194_304,
                "estimated_cache_bytes": 0,
                "estimated_report_bytes": 1_048_576,
                "estimated_seconds_cold": 8,
                "estimated_seconds_warm": 3,
                "estimated_inode_count": 42,
                "cache_assumptions": ["cold cache", "complete history"],
                "confidence": "conservative"
            }
        });
        let rendered = render_find_estimate_text(&payload, Some("src/core"));
        for expected in [
            "- scope: src/core",
            "- tracked paths: 12",
            "- peak memory: ~2 MiB (1-3 MiB; 4 MiB budget)",
            "- cache/report: ~0 MiB / ~1 MiB",
            "- cache assumptions: cold cache; complete history",
            "- confidence: conservative",
        ] {
            assert!(rendered.contains(expected), "missing {expected:?}\n{rendered}");
        }
    }

    #[test]
    fn text_receipt_has_safe_fallbacks_for_partial_diagnostics() {
        let rendered = render_find_estimate_text(&json!({"estimate": {}}), None);
        assert!(rendered.contains("- scope: all tracked paths"));
        assert!(rendered.contains("- peak memory: ~0 MiB (0-0 MiB; 0 MiB budget)"));
        assert!(rendered.contains("- confidence: unknown"));
    }
}