git-slop 0.12.0

A deterministic repository token-defragmenter for humans and AI agents.
Documentation
fn print_text(value: &str) {
    if value.is_empty() {
        return;
    }
    print!("{value}");
    if !value.ends_with('\n') {
        println!();
    }
}

fn safe_terminal(value: &str) -> String {
    crate::text::visible_controls(value)
}

fn relative_display(path: &Path, root: &Path) -> String {
    path.strip_prefix(root)
        .unwrap_or(path)
        .to_string_lossy()
        .replace('\\', "/")
}

fn default_report_path(repo_root: &Path) -> PathBuf {
    config::latest_dir(repo_root).join("report.json")
}

fn load_report_at(path: &Path) -> Result<Option<Value>> {
    if !path.exists() {
        return Ok(None);
    }
    report::load_report(path).map(Some)
}

fn load_default_report(
    repo_root: &Path,
    explicit_report: Option<&Path>,
) -> Result<Option<(Value, PathBuf)>> {
    let path = explicit_report
        .map(Path::to_path_buf)
        .unwrap_or_else(|| default_report_path(repo_root));
    Ok(load_report_at(&path)?.map(|report| (report, path)))
}

fn report_or_missing(repo_root: &Path, explicit_report: Option<&Path>) -> Result<(Value, PathBuf)> {
    let fallback = explicit_report
        .map(Path::to_path_buf)
        .unwrap_or_else(|| default_report_path(repo_root));
    let loaded = load_default_report(repo_root, explicit_report).map_err(|error| {
        ClassifiedError::new(ErrorKind::Contract, "report_invalid", format!("{error:#}"))
            .at("/report")
            .with_details(json!({"path": fallback}))
    })?;
    loaded.ok_or_else(|| {
        ClassifiedError::new(
            ErrorKind::Contract,
            "report_not_found",
            format!(
                "Report not found: {}\nRun `git slop find` to generate it.",
                fallback.display()
            ),
        )
        .at("/report")
        .with_details(json!({"path": fallback}))
        .into()
    })
}

fn lexical_normalize(path: &Path) -> PathBuf {
    let mut result = PathBuf::new();
    for component in path.components() {
        match component {
            Component::CurDir => {}
            Component::ParentDir => {
                if !result.pop() {
                    result.push(component.as_os_str());
                }
            }
            _ => result.push(component.as_os_str()),
        }
    }
    result
}

fn selector_path(repo_root: &Path, input: &str) -> String {
    let candidate = if Path::new(input).is_absolute() {
        lexical_normalize(Path::new(input))
    } else {
        lexical_normalize(&repo_root.join(input))
    };
    candidate
        .strip_prefix(repo_root)
        .ok()
        .map(|path| path.to_string_lossy().replace('\\', "/"))
        .unwrap_or_else(|| {
            let trimmed = input.trim();
            if trimmed.is_empty() {
                ".".to_string()
            } else {
                trimmed.to_string()
            }
        })
}

fn usage_error(error: impl std::fmt::Display) -> Result<i32> {
    Err(ClassifiedError::new(ErrorKind::Contract, "invalid_argument", error).into())
}

fn argument_error(
    pointer: &str,
    flag: &str,
    error: impl std::fmt::Display,
    actual: impl serde::Serialize,
) -> Result<i32> {
    Err(ClassifiedError::new(ErrorKind::Contract, "invalid_argument", error)
        .at(pointer)
        .with_details(json!({"flag": flag, "actual": actual}))
        .into())
}

fn ensure_prompt_pack_target(path: &Path) -> Result<()> {
    if path.exists() && !path.is_dir() {
        Err(ClassifiedError::new(
            ErrorKind::Contract,
            "prompt_pack_collision",
            format!("Prompt pack path is not a directory: {}", path.display()),
        )
        .at("/prompt_pack")
        .with_details(json!({"path": path}))
        .into())
    } else {
        Ok(())
    }
}

fn run_init(repo_root: &Path, args: InitArgs) -> Result<i32> {
    let result = config::initialize(repo_root, args.force)?;
    println!(
        "Initialized {} ({}).",
        relative_display(&config::config_path(repo_root), repo_root),
        result.config
    );
    println!(
        "Initialized {} ({}).",
        relative_display(&config::slop_dir(repo_root).join(".gitignore"), repo_root),
        result.gitignore
    );
    println!("Ensured .slop/latest/, .slop/runs/, and .slop/cache/ exist.");
    Ok(0)
}