git-slop 0.16.4

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

use assert_cmd::cargo::cargo_bin_cmd;
use predicates::prelude::*;
use serde_json::Value;
use tempfile::TempDir;

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

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

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

fn read_json(path: &Path) -> Value {
    serde_json::from_slice(&fs::read(path).expect("read JSON file")).expect("parse JSON file")
}

fn assert_prompt_pack_safety(pack: &Path) -> Value {
    assert!(pack.join("prompt.md").is_file(), "missing prompt.md");
    assert!(pack.join("README.md").is_file(), "missing README.md");
    let manifest = read_json(&pack.join("manifest.json"));
    assert_eq!(manifest["schema_version"], 1);
    for name in ["context.json", "prompt.md", "README.md"] {
        assert_eq!(manifest["files"][name].as_str().map(str::len), Some(64));
    }

    let context = read_json(&pack.join("context.json"));
    assert_eq!(context["prompt_pack_version"], 1);
    assert_eq!(context["report_excerpt"]["schema_version"], 5);
    assert_eq!(
        context["provenance"]["report_sha256"]
            .as_str()
            .map(str::len),
        Some(64)
    );
    assert_eq!(context["repository_context"]["included"], false);
    assert_eq!(context["repository_context"]["planning_usable"], false);
    assert_eq!(context["repository_context"]["execution_ready"], false);

    let boundary = context["boundary"].as_str().expect("prompt-pack boundary");
    assert!(boundary.contains("advisory only"));
    assert!(boundary.contains("must not rescore detector truth"));
    assert!(boundary.contains("mutate code, GitHub, or report data"));

    let prompt = fs::read_to_string(pack.join("prompt.md")).expect("read prompt.md");
    assert!(prompt.contains("Use only the facts in context.json"));
    assert!(prompt.contains("do not rescore detector truth"));
    assert!(prompt.contains("instead of inventing context"));

    let readme = fs::read_to_string(pack.join("README.md")).expect("read README.md");
    assert!(readme.contains("must not mutate code, GitHub, or detector truth"));
    assert!(readme.contains("must not rescore detector truth"));

    context
}

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