git-slop 0.16.4

A deterministic repository token-defragmenter for humans and AI agents.
Documentation
use std::fs;
use std::path::PathBuf;
use std::process::Command;

use assert_cmd::cargo::cargo_bin_cmd;
use predicates::prelude::*;
use serde_json::{Value, json};
use tempfile::{NamedTempFile, TempDir};

fn manifest_dir() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}

fn fixture(path: &str) -> PathBuf {
    manifest_dir().join("tests/fixtures/reports").join(path)
}

fn write_report(report: &Value) -> NamedTempFile {
    let file = NamedTempFile::new().expect("temporary report");
    serde_json::to_writer_pretty(file.as_file(), report).expect("write report");
    file
}

fn assert_close(actual: f64, expected: f64) {
    assert!(
        (actual - expected).abs() <= 0.000_001,
        "expected {expected}, got {actual}"
    );
}

fn assert_stdout_matches_golden(output: &std::process::Output, golden: &std::path::Path) {
    assert!(
        output.status.success(),
        "command failed: {}",
        String::from_utf8_lossy(&output.stderr)
    );
    let actual = std::str::from_utf8(&output.stdout).expect("command stdout is UTF-8");
    if std::env::var_os("UPDATE_GIT_SLOP_GOLDENS").is_some() {
        fs::write(golden, actual).expect("update text golden");
    }
    let expected = fs::read_to_string(golden).expect("read text golden");
    assert_eq!(actual.replace("\r\n", "\n"), expected.replace("\r\n", "\n"));
}

fn round6(value: f64) -> f64 {
    (value * 1_000_000.0).round() / 1_000_000.0
}

fn git(repository: &TempDir, args: &[&str]) {
    let output = Command::new("git")
        .current_dir(repository.path())
        .args(args)
        .output()
        .expect("run git");
    assert!(
        output.status.success(),
        "git {} failed: {}",
        args.join(" "),
        String::from_utf8_lossy(&output.stderr)
    );
}

fn committed_repository() -> TempDir {
    let repository = TempDir::new().expect("temporary repository");
    git(&repository, &["init"]);
    git(&repository, &["config", "user.name", "Git Slop Test"]);
    git(
        &repository,
        &["config", "user.email", "git-slop@example.invalid"],
    );
    fs::create_dir(repository.path().join("src")).expect("source directory");
    fs::write(
        repository.path().join("src/lib.rs"),
        "pub fn repository_health() -> &'static str {\n    \"healthy\"\n}\n",
    )
    .expect("source file");
    fs::write(
        repository.path().join("src/main.rs"),
        "fn main() {\n    println!(\"healthy\");\n}\n",
    )
    .expect("source file");
    fs::write(
        repository.path().join("README.md"),
        "# Fixture\n\nA small committed repository.\n",
    )
    .expect("readme");
    git(&repository, &["add", "."]);
    git(
        &repository,
        &[
            "-c",
            "commit.gpgsign=false",
            "commit",
            "-m",
            "Create fixture",
        ],
    );
    repository
}

fn health_report() -> Value {
    json!({
        "schema_version": 4,
        "generated_at": "2026-07-30T09:48:24Z",
        "repo": {
            "repo_name": "example",
            "branch": "main",
            "head_commit": "0123456789abcdef",
            "git_remote_url": "https://github.com/example/example.git",
            "worktree_clean": true
        },
        "diagnostics": {
            "analysis": {"analysis_status": "complete"}
        },
        "collection_metadata": {
            "files": {"total": 3, "returned": 3, "limit": null, "truncated": false},
            "folders": {"total": 0, "returned": 0, "limit": null, "truncated": false}
        },
        "config": {
            "tokenization": {
                "context_bands": {
                    "compact_max_tokens": 3072,
                    "healthy_max_tokens": 8000,
                    "warning_max_tokens": 10000
                }
            },
            "health": {
                "folder_bands": {
                    "compact_max_direct_tokens": 31999,
                    "healthy_max_direct_tokens": 128000,
                    "warning_max_direct_tokens": 256000,
                    "warning_max_direct_files": 17,
                    "refactor_required_max_direct_files": 37
                }
            }
        },
        "stats": {},
        "summary": {},
        "overlays": {},
        "health": {},
        "files": [{
            "path": "src/a,b%file.rs",
            "profile": "agent_context",
            "classification": "source",
            "language": "Rust",
            "lines": 100,
            "code_lines": 80,
            "comment_lines": 10,
            "blank_lines": 10,
            "tokens": 12000,
            "analysis_status": "analyzed",
            "context_band": "critical",
            "slop_band": "critical",
            "slop_score": 88.0,
            "reason_codes": ["critical_token_cost"]
        }, {
            "path": "src/second.rs",
            "profile": "agent_context",
            "classification": "source",
            "language": "Rust",
            "lines": 50,
            "code_lines": 40,
            "comment_lines": 5,
            "blank_lines": 5,
            "tokens": 9000,
            "analysis_status": "analyzed",
            "context_band": "warning",
            "slop_band": "high",
            "slop_score": 70.0,
            "reason_codes": ["high_token_cost"]
        }, {
            "path": "src/watchlist.rs",
            "profile": "agent_context",
            "classification": "source",
            "language": "Rust",
            "lines": 30,
            "code_lines": 24,
            "comment_lines": 3,
            "blank_lines": 3,
            "tokens": 6000,
            "analysis_status": "analyzed",
            "context_band": "healthy",
            "slop_band": "moderate",
            "slop_score": 60.0,
            "reason_codes": []
        }],
        "folders": [],
        "action_queue": []
    })
}

include!("cli_runtime/group_1.rs");
include!("cli_runtime/group_2.rs");
include!("cli_runtime/group_3.rs");