git-slop 0.16.4

A deterministic repository token-defragmenter for humans and AI agents.
Documentation
fn sha256(bytes: impl AsRef<[u8]>) -> String {
    hex::encode(Sha256::digest(bytes.as_ref()))
}

fn canonical_digest(value: &Value) -> Result<String> {
    Ok(sha256(serde_json::to_vec(value)?))
}

fn context_digest(value: &Value) -> Result<String> {
    let mut normalized = value.clone();
    if let Some(object) = normalized.as_object_mut() {
        object.remove("context_digest");
    }
    if let Some(limits) = normalized.get_mut("limits").and_then(Value::as_object_mut) {
        limits.remove("estimated_context_tokens");
    }
    canonical_digest(&normalized)
}

fn push_strings(value: Option<&Value>, target: &mut BTreeSet<String>) {
    if let Some(values) = value.and_then(Value::as_array) {
        for value in values.iter().filter_map(Value::as_str) {
            target.insert(value.to_string());
        }
    }
}

fn plan_payloads(
    report: &Value,
    selector: &AdviceSelector,
    max_slices: usize,
) -> Result<Vec<Value>> {
    match selector {
        AdviceSelector::Path(path) => Ok(vec![plan_payload(
            report,
            PlanSelector::Path(path.clone()),
            max_slices,
        )?]),
        AdviceSelector::Relationship(id) => Ok(vec![plan_payload(
            report,
            PlanSelector::Relationship(id.clone()),
            max_slices,
        )?]),
        AdviceSelector::Cluster(id) => Ok(vec![plan_payload(
            report,
            PlanSelector::Cluster(id.clone()),
            max_slices,
        )?]),
        AdviceSelector::Top(count) => {
            if *count == 0 || *count > 20 {
                bail!("--top must be between 1 and 20");
            }
            let explanations = explain_payload(report, Some(ExplainSelector::Top(*count)))?;
            let mut paths = explanations
                .get("items")
                .and_then(Value::as_array)
                .into_iter()
                .flatten()
                .filter_map(|item| item.pointer("/target/path").and_then(Value::as_str))
                .map(str::to_string)
                .collect::<Vec<_>>();
            if paths.len() < *count {
                for path in report
                    .pointer("/health/refactor_candidates")
                    .and_then(Value::as_array)
                    .into_iter()
                    .flatten()
                    .filter_map(|item| item.get("path").and_then(Value::as_str))
                {
                    if !paths.iter().any(|existing| existing == path) {
                        paths.push(path.to_string());
                    }
                    if paths.len() == *count {
                        break;
                    }
                }
            }
            paths
                .into_iter()
                .map(|path| plan_payload(report, PlanSelector::Path(path.to_string()), 1))
                .collect()
        }
    }
}

fn build_candidates(plans: &[Value]) -> Result<Vec<Value>> {
    let mut candidates = Vec::new();
    for plan in plans {
        let selector = plan.get("selector").cloned().unwrap_or(Value::Null);
        for slice in plan
            .get("proposed_slices")
            .and_then(Value::as_array)
            .into_iter()
            .flatten()
        {
            let source_id = slice
                .get("id")
                .and_then(Value::as_str)
                .unwrap_or("unnamed-plan-slice");
            let candidate_digest = canonical_digest(&json!({
                "selector": selector,
                "source_plan_slice": slice,
            }))?;
            let candidate_id = format!("candidate-{}", &candidate_digest[..16]);
            let boundaries = slice.get("boundaries").unwrap_or(&Value::Null);
            let evidence = slice.get("evidence").unwrap_or(&Value::Null);
            let outcome = slice.get("expected_outcome").unwrap_or(&Value::Null);
            let verification = slice.get("verification").unwrap_or(&Value::Null);
            let compact_targets = verification
                .get("concrete_targets")
                .and_then(Value::as_array)
                .into_iter()
                .flatten()
                .map(|target| {
                    let bounded_paths = |key: &str| {
                        target
                            .get(key)
                            .and_then(Value::as_array)
                            .into_iter()
                            .flatten()
                            .take(1)
                            .cloned()
                            .collect::<Vec<_>>()
                    };
                    json!({
                        "path": target.get("path").cloned().unwrap_or(Value::Null),
                        "nearby_tests": bounded_paths("nearby_tests"),
                        "nearby_verification": bounded_paths("nearby_verification"),
                    })
                })
                .collect::<Vec<_>>();
            let assumptions = slice
                .get("assumptions")
                .and_then(Value::as_array)
                .into_iter()
                .flatten()
                .filter(|assumption| {
                    !matches!(
                        assumption.as_str(),
                        Some(
                            "The cited detector report is the source of truth for scope and ranking."
                                | "A human reviews the proposed slice before any repository mutation."
                        )
                    )
                })
                .cloned()
                .collect::<Vec<_>>();
            let relationship_support = evidence
                .get("relationship_support")
                .and_then(Value::as_object)
                .map(|support| {
                    let supported = support
                        .get("supported_ids")
                        .and_then(Value::as_array)
                        .is_some_and(|ids| !ids.is_empty());
                    let context_only = support
                        .get("context_only_ids")
                        .and_then(Value::as_array)
                        .is_some_and(|ids| !ids.is_empty());
                    match (supported, context_only) {
                        (true, true) => "mixed",
                        (true, false) => "supported",
                        (false, true) => "context_only",
                        (false, false) => "unavailable",
                    }
                });
            let intervention_supported = evidence
                .pointer("/anchor/intervention_supported")
                .and_then(Value::as_bool)
                .unwrap_or(false);
            let disposition = if intervention_supported {
                "implementable"
            } else {
                "investigate"
            };
            let implementation_sequence = if intervention_supported {
                json!(["baseline", "change", "verify", "compare"])
            } else {
                json!(["baseline", "investigate", "establish_evidence", "verify"])
            };
            candidates.push(json!({
                "id": candidate_id,
                "source_plan_slice_id": source_id,
                "disposition": disposition,
                "observed_facts": {
                    "scope_paths": slice.get("scope_paths").cloned().unwrap_or_else(|| json!([])),
                    "out_of_scope_paths": slice.get("out_of_scope_paths").cloned().unwrap_or_else(|| json!([])),
                    "boundaries": {
                        "maximum_existing_paths": boundaries.pointer("/existing_path_cap/maximum").cloned().unwrap_or(Value::Null),
                        "maximum_new_paths": boundaries.pointer("/new_path_cap/maximum").cloned().unwrap_or(Value::Null),
                    },
                    "evidence": {
                        "anchor": evidence.get("anchor").cloned().unwrap_or(Value::Null),
                        "intervention_supported": intervention_supported,
                        "finding_ids": evidence.get("finding_ids").cloned().unwrap_or_else(|| json!([])),
                        "relationship_ids": evidence.get("relationship_ids").cloned().unwrap_or_else(|| json!([])),
                        "cluster_ids": evidence.get("cluster_ids").cloned().unwrap_or_else(|| json!([])),
                        "relationship_support": relationship_support,
                    },
                    "verification": {
                        "classes": verification.get("classes").cloned().unwrap_or_else(|| json!([])),
                        "concrete_targets": compact_targets,
                        "discovered_commands": verification.get("discovered_commands").cloned().unwrap_or_else(|| json!([])),
                        "required_checks": verification.get("required_checks").cloned().unwrap_or_else(|| json!([])),
                    },
                    "expected_outcome": {
                        "required": outcome.get("required").cloned().unwrap_or_else(|| json!([])),
                        "target_slop_band": outcome.get("target_slop_band").cloned().unwrap_or(Value::Null),
                        "target_top_slop_score": outcome.get("target_top_slop_score").cloned().unwrap_or(Value::Null),
                    },
                },
                "interpretation": {
                    "title": slice.get("title").cloned().unwrap_or(Value::Null),
                    "objective": slice.get("objective").cloned().unwrap_or_else(|| if intervention_supported {
                        json!("Apply and verify the bounded slice.")
                    } else {
                        json!("Investigate the bounded slice without mutating source until intervention evidence is established.")
                    }),
                    "rationale": slice.get("rationale").cloned().unwrap_or(Value::Null),
                    "assumptions": assumptions,
                    "abandonment_condition": slice.get("abandonment_condition").cloned().unwrap_or_else(|| json!("Abstain if evidence is insufficient.")),
                    "rollback": slice.get("rollback").cloned().unwrap_or_else(|| json!("Revert the bounded change.")),
                },
                "implementation_sequence": implementation_sequence,
            }));
        }
    }
    candidates.sort_by(|left, right| {
        left.get("id")
            .and_then(Value::as_str)
            .cmp(&right.get("id").and_then(Value::as_str))
    });
    let mut seen = BTreeSet::new();
    candidates.retain(|candidate| {
        candidate
            .get("id")
            .and_then(Value::as_str)
            .is_some_and(|id| seen.insert(id.to_string()))
    });
    if candidates.is_empty() {
        bail!("the selected report evidence produced no deterministic plan candidates");
    }
    Ok(candidates)
}

fn collect_candidate_paths(candidates: &[Value]) -> (BTreeSet<String>, BTreeSet<String>) {
    let mut sources = BTreeSet::new();
    let mut tests = BTreeSet::new();
    for candidate in candidates {
        let facts = candidate.get("observed_facts").unwrap_or(&Value::Null);
        push_strings(facts.get("scope_paths"), &mut sources);
        if let Some(targets) = facts
            .pointer("/verification/concrete_targets")
            .and_then(Value::as_array)
        {
            for target in targets {
                if let Some(path) = target.get("path").and_then(Value::as_str) {
                    sources.insert(path.to_string());
                }
                push_strings(target.get("nearby_tests"), &mut tests);
                push_strings(target.get("nearby_verification"), &mut tests);
            }
        }
    }
    (sources, tests)
}

fn guidance_candidates(source_paths: &BTreeSet<String>) -> Vec<String> {
    let mut guidance = Vec::new();
    for source in source_paths {
        let mut parent = Path::new(source).parent();
        while let Some(directory) = parent {
            if directory.as_os_str().is_empty() {
                break;
            }
            let path = directory
                .join("AGENTS.md")
                .to_string_lossy()
                .replace('\\', "/");
            if !guidance.contains(&path) {
                guidance.push(path);
            }
            parent = directory.parent();
        }
    }
    for path in [
        "AGENTS.md",
        "CONTRIBUTING.md",
        "README.md",
        "docs/architecture.md",
    ] {
        if !guidance.iter().any(|candidate| candidate == path) {
            guidance.push(path.to_string());
        }
    }
    guidance
}