git-slop 0.16.4

A deterministic repository token-defragmenter for humans and AI agents.
Documentation
fn execute(repo_root: &Path, command: Command) -> Result<i32> {
    match command {
        Command::Init(args) => run_init(repo_root, args),
        Command::Find(args) => run_find(repo_root, args),
        Command::Show(args) => run_show(repo_root, args),
        Command::Explain(args) => run_explain(repo_root, args),
        Command::Plan(args) => run_plan(repo_root, args),
        Command::Policy(args) => run_policy(repo_root, args),
        Command::Advise(args) => run_advise(repo_root, *args),
        Command::Check(args) => run_check(repo_root, args),
        Command::Compare(args) => run_compare(repo_root, args),
        Command::Baseline(args) => run_baseline(repo_root, args),
        Command::Report(args) => run_report(repo_root, args),
        Command::Sarif(args) => run_sarif(repo_root, args),
        Command::Health(args) => run_health(repo_root, args),
        Command::Config(args) => run_config(repo_root, args),
        Command::Doctor(args) => run_doctor(repo_root, args),
        Command::List(args) => run_list(repo_root, args),
        Command::Prune(args) => run_prune(repo_root, args),
        Command::Cache(args) => run_cache(repo_root, args),
        Command::Completions(args) => run_completions(args),
        Command::Man(args) => run_man(args),
        Command::Reference(args) => run_reference(args),
        Command::Html(args) => run_html(repo_root, args),
        Command::Version => {
            println!("{PROJECT_NAME} {VERSION}");
            Ok(0)
        }
        Command::BuildInfo(args) => {
            match args.format {
                BuildInfoFormat::Json => {
                    println!("{}", serde_json::to_string_pretty(&build_info::current())?)
                }
            }
            Ok(0)
        }
        Command::Schema(args) => {
            let rendered = match args.contract {
                SchemaContract::Report => render_json(&report::schema())?,
                SchemaContract::Config => render_json(&config::schema())?,
                contract => {
                    let source = match contract {
                        SchemaContract::Compare => include_str!("../../schemas/compare-1.json"),
                        SchemaContract::Init => include_str!("../../schemas/init-1.json"),
                        SchemaContract::Explain => include_str!("../../schemas/explain-2.json"),
                        SchemaContract::Plan => include_str!("../../schemas/plan-2.json"),
                        SchemaContract::Sarif => include_str!("../../schemas/sarif-1.json"),
                        SchemaContract::Health => include_str!("../../schemas/health-1.json"),
                        SchemaContract::Check => include_str!("../../schemas/check-1.json"),
                        SchemaContract::Doctor => include_str!("../../schemas/doctor-1.json"),
                        SchemaContract::BuildInfo => include_str!("../../schemas/build-info-2.json"),
                        SchemaContract::ReleaseManifest => {
                            include_str!("../../schemas/release-manifest-3.json")
                        }
                        SchemaContract::List => include_str!("../../schemas/list-1.json"),
                        SchemaContract::Show => include_str!("../../schemas/show-1.json"),
                        SchemaContract::PromptManifest => {
                            include_str!("../../schemas/prompt-manifest-1.json")
                        }
                        SchemaContract::Error => include_str!("../../schemas/error-1.json"),
                        SchemaContract::FindEstimate => {
                            include_str!("../../schemas/find-estimate-1.json")
                        }
                        SchemaContract::CacheStatus => {
                            include_str!("../../schemas/cache-status-1.json")
                        }
                        SchemaContract::CachePrune => {
                            include_str!("../../schemas/cache-prune-1.json")
                        }
                        SchemaContract::Baseline => include_str!("../../schemas/baseline-1.json"),
                        SchemaContract::Prune => include_str!("../../schemas/prune-1.json"),
                        SchemaContract::CompareNdjson => {
                            include_str!("../../schemas/compare-ndjson-1.json")
                        }
                        SchemaContract::PolicyPack => {
                            include_str!("../../schemas/policy-pack-1.json")
                        }
                        SchemaContract::PolicyLock => {
                            include_str!("../../schemas/policy-lock-1.json")
                        }
                        SchemaContract::AdviceInput => {
                            include_str!("../../schemas/advice-input-1.json")
                        }
                        SchemaContract::AdviceResponse => {
                            include_str!("../../schemas/advice-response-1.json")
                        }
                        SchemaContract::Advice => include_str!("../../schemas/advice-1.json"),
                        SchemaContract::AdvisorCorpus => {
                            include_str!("../../schemas/advisor-corpus-1.json")
                        }
                        SchemaContract::AdvisorRatings => {
                            include_str!("../../schemas/advisor-ratings-1.json")
                        }
                        SchemaContract::AdvisorRatingsV2 => {
                            include_str!("../../schemas/advisor-ratings-2.json")
                        }
                        SchemaContract::AdvisorReviewArtifact => {
                            include_str!("../../schemas/advisor-review-artifact-1.json")
                        }
                        SchemaContract::AdvisorReviewManifest => {
                            include_str!("../../schemas/advisor-review-manifest-1.json")
                        }
                        SchemaContract::AdvisorOperationReceipt => {
                            include_str!("../../schemas/advisor-operation-receipt-1.json")
                        }
                        SchemaContract::AdvisorThresholds => {
                            include_str!("../../schemas/advisor-thresholds-1.json")
                        }
                        SchemaContract::AdvisorBenchmark => {
                            include_str!("../../schemas/advisor-benchmark-1.json")
                        }
                        SchemaContract::AdvisorCapacity => {
                            include_str!("../../schemas/advisor-capacity-1.json")
                        }
                        SchemaContract::Report | SchemaContract::Config => unreachable!(),
                    };
                    let value: Value = serde_json::from_str(source)?;
                    render_json(&value)?
                }
            };
            write_generated_output(args.output.as_deref(), rendered.as_bytes())?;
            Ok(0)
        }
    }
}

fn command_requires_repository(command: &Command) -> bool {
    match command {
        Command::Completions(_)
        | Command::Man(_)
        | Command::Reference(_)
        | Command::Version
        | Command::BuildInfo(_)
        | Command::Schema(_)
        | Command::Report(_)
        | Command::Config(ConfigArgs {
            command: ConfigCommand::Schema,
        }) => false,
        Command::Show(args) => args.report.is_none() || args.require_current,
        Command::Compare(args) => args.base_ref.is_some() || args.baseline.is_some(),
        Command::Baseline(_) => true,
        Command::Policy(_) => true,
        Command::Advise(_) => true,
        Command::Explain(args) => {
            args.report.is_none() || args.include_repository_context || args.require_current
        }
        Command::Plan(args) => {
            args.report.is_none() || args.include_repository_context || args.require_current
        }
        Command::Check(args) => args.report.is_none() || args.require_current,
        Command::Sarif(args) => args.report.is_none() || args.require_current,
        Command::Health(args) => args.report.is_none() || args.require_current,
        Command::Html(args) => args.report.is_none() || args.require_current,
        Command::List(args) => {
            let output = match &args.command {
                ListCommand::PolicyFailures(args)
                | ListCommand::Interventions(args)
                | ListCommand::Observations(args)
                | ListCommand::HealthFindings(args)
                | ListCommand::Findings(args) => &args.output,
                ListCommand::Relationships(args) => &args.output,
                ListCommand::Clusters(args) => &args.output,
                ListCommand::Profiles(args) => &args.output,
            };
            output.report.is_none() || output.require_current
        }
        _ => true,
    }
}

pub fn run() -> i32 {
    let raw_args = std::env::args_os().collect::<Vec<_>>();
    if raw_args.len() == 1 {
        println!(
            "Git Slop finds repository surfaces that cost too much context.\n\n\
             QUICK START\n  git slop find                 Run a safe, cacheable first scan\n  \
             git slop health               Review the current health snapshot\n  \
             git slop list interventions   Review bounded maintenance candidates\n  \
             git slop explain --top 5      Understand why candidates surfaced\n\n\
             INSPECT\n  show · explain · plan · list · html · doctor\n\n\
             AUTOMATION\n  check · compare · baseline · sarif\n\n\
             ADOPTION AND STATE\n  init · config · cache · prune\n\n\
             POLICY-GUIDED ADVICE\n  policy · advise\n\n\
             GENERATE AND INFO\n  completions · man · reference · schema · version · build-info\n\n\
             Run `git slop --help` for every command and option."
        );
        return 0;
    }
    let requested_error_format = requested_error_format(&raw_args);
    let parser_command = parser_command_name(&raw_args);
    let cli = match Cli::try_parse_from(&raw_args) {
        Ok(cli) => cli,
        Err(error) => {
            let code = error.exit_code();
            if code == 0 || requested_error_format == ErrorFormat::Human {
                let _ = error.print();
            } else {
                let classified =
                    ClassifiedError::new(ErrorKind::Contract, "parser_error", error.to_string())
                        .at("/arguments")
                        .with_details(json!({"clap_kind": format!("{:?}", error.kind())}));
                render_runtime_error(
                    requested_error_format,
                    &classified,
                    parser_command.as_deref(),
                );
            }
            return code;
        }
    };
    let error_format = if cli.error_format == ErrorFormat::Human
        && matches!(cli.command, Command::Init(InitArgs { format: InitFormat::Json, .. }))
    {
        ErrorFormat::Json
    } else {
        cli.error_format
    };
    let command_name = cli.command.name();
    let repo_root = if cli.repo.is_some() || command_requires_repository(&cli.command) {
        match git::resolve_repo_root_from(cli.repo.as_deref()) {
            Ok(root) => root,
            Err(error) => {
                let requested_path = cli.repo.clone().or_else(|| std::env::current_dir().ok());
                let requested_display = requested_path.as_deref().map_or_else(
                    || "the current directory".to_string(),
                    |path| path.display().to_string(),
                );
                render_runtime_error(
                    error_format,
                    &ClassifiedError::new(
                        ErrorKind::Repository,
                        "repository_not_found",
                        format!(
                            "Not inside a Git repository: {requested_display}. Run this command from a repository, or pass `--repo <PATH>`."
                        ),
                    )
                    .at("/repo")
                    .with_details(json!({"path": requested_path, "cause": format!("{error:#}")})),
                    Some(command_name),
                );
                return 3;
            }
        }
    } else {
        PathBuf::new()
    };
    match execute(&repo_root, cli.command) {
        Ok(code) => code,
        Err(error) => {
            let classified = error.downcast_ref::<ClassifiedError>();
            let fallback;
            let classified = if let Some(classified) = classified {
                classified
            } else {
                fallback = if error.downcast_ref::<std::io::Error>().is_some() {
                    ClassifiedError::new(ErrorKind::Io, "io_failure", format!("{error:#}"))
                } else {
                    ClassifiedError::new(
                        ErrorKind::Repository,
                        "operation_failed",
                        format!("{error:#}"),
                    )
                };
                &fallback
            };
            render_runtime_error(error_format, classified, Some(command_name));
            classified.kind.exit_code()
        }
    }
}

fn requested_error_format(args: &[std::ffi::OsString]) -> ErrorFormat {
    let explicit = args.iter()
        .filter_map(|arg| arg.to_str())
        .enumerate()
        .find_map(|(index, arg)| {
            if arg == "--error-format" {
                args.get(index + 1).and_then(|value| value.to_str())
            } else {
                arg.strip_prefix("--error-format=")
            }
        })
        .is_some_and(|value| value == "json");
    let init_json = args.iter().filter_map(|arg| arg.to_str()).any(|arg| arg == "init")
        && args
            .iter()
            .filter_map(|arg| arg.to_str())
            .enumerate()
            .any(|(index, arg)| {
                arg == "--format=json"
                    || (arg == "--format"
                        && args.get(index + 1).and_then(|value| value.to_str()) == Some("json"))
            });
    if explicit || init_json {
        ErrorFormat::Json
    } else {
        ErrorFormat::Human
    }
}

fn parser_command_name(args: &[std::ffi::OsString]) -> Option<String> {
    const COMMANDS: &[&str] = &[
        "init",
        "find",
        "show",
        "explain",
        "plan",
        "policy",
        "advise",
        "check",
        "compare",
        "baseline",
        "report",
        "sarif",
        "health",
        "config",
        "doctor",
        "list",
        "prune",
        "cache",
        "completions",
        "man",
        "reference",
        "html",
        "version",
        "build-info",
        "schema",
    ];
    args.iter()
        .skip(1)
        .filter_map(|arg| arg.to_str())
        .find(|arg| COMMANDS.contains(arg))
        .map(str::to_string)
}

fn render_runtime_error(format: ErrorFormat, error: &ClassifiedError, command: Option<&str>) {
    match format {
        ErrorFormat::Human => eprintln!("{}", error.message),
        ErrorFormat::Json => eprintln!(
            "{}",
            serde_json::to_string(&json!({
                "schema_version": 1,
                "error": {
                    "kind": error.kind,
                    "code": error.code,
                    "pointer": error.pointer,
                    "message": error.message,
                    "details": error.details,
                    "command": command,
                    "exit_code": error.kind.exit_code()
                }
            }))
            .unwrap_or_else(|_| {
                "{\"schema_version\":1,\"error\":{\"code\":\"serialization_failed\"}}".to_string()
            })
        ),
    }
}