git-slop 0.16.4

A deterministic repository token-defragmenter for humans and AI agents.
Documentation
fn advice_directory_size(path: &Path) -> Result<u64> {
    let metadata = fs::symlink_metadata(path)?;
    if metadata.file_type().is_symlink() {
        bail!(
            "advice state must not contain symbolic links: {}",
            path.display()
        );
    }
    if metadata.is_file() {
        return Ok(metadata.len());
    }
    let mut total = 0_u64;
    for entry in fs::read_dir(path)? {
        total = total.saturating_add(advice_directory_size(&entry?.path())?);
    }
    Ok(total)
}
#[cfg(unix)]
fn advice_permissions_private(path: &Path) -> Result<bool> {
    use std::os::unix::fs::PermissionsExt;

    let metadata = fs::symlink_metadata(path)?;
    if metadata.file_type().is_symlink() || metadata.permissions().mode() & 0o077 != 0 {
        return Ok(false);
    }
    if metadata.is_dir() {
        for entry in fs::read_dir(path)? {
            if !advice_permissions_private(&entry?.path())? {
                return Ok(false);
            }
        }
    }
    Ok(true)
}

#[cfg(not(unix))]
fn advice_permissions_private(_path: &Path) -> Result<bool> {
    Ok(true)
}

pub fn state_status(state_root: &Path, current_report: Option<&Value>) -> Result<Value> {
    let root = state_root.join("advice");
    if !root.exists() {
        return Ok(json!({
            "status": "missing",
            "latest": "missing",
            "retained_runs": 0,
            "retained_bytes": 0,
            "private_permissions": true,
            "recovery_entries": 0,
            "retention_command": "git slop prune --dry-run"
        }));
    }
    if root
        .symlink_metadata()
        .is_ok_and(|metadata| metadata.file_type().is_symlink())
    {
        bail!("advice state root must not be a symbolic link");
    }
    let recovery_entries = fs::read_dir(&root)?
        .filter_map(Result::ok)
        .filter(|entry| {
            entry
                .file_name()
                .to_str()
                .is_some_and(|name| name == ".latest.backup" || name.starts_with(".advice-latest-"))
        })
        .count();
    let runs = root.join("runs");
    let retained = if runs.is_dir() {
        fs::read_dir(&runs)?
            .filter_map(Result::ok)
            .filter(|entry| {
                entry.file_type().is_ok_and(|kind| kind.is_dir())
                    && entry
                        .file_name()
                        .to_str()
                        .is_some_and(|name| !name.starts_with('.'))
            })
            .map(|entry| {
                let bytes = advice_directory_size(&entry.path())?;
                Ok(bytes)
            })
            .collect::<Result<Vec<_>>>()?
    } else {
        Vec::new()
    };
    let latest_path = root.join("latest/advice.json");
    let markdown_path = root.join("latest/advice.md");
    let latest = if !latest_path.exists() && !markdown_path.exists() {
        "missing"
    } else if !latest_path.is_file() || !markdown_path.is_file() {
        "invalid"
    } else {
        let bytes = super::io::read_bounded(
            &latest_path,
            super::io::MAX_ADVICE_ARTIFACT_BYTES,
            "latest advice artifact",
        );
        bytes
            .and_then(|bytes| serde_json::from_slice::<Value>(&bytes).map_err(Into::into))
            .and_then(|artifact| {
                validate_artifact_contract(&artifact)?;
                validate_artifact_semantics(&artifact)?;
                if let Some(report) = current_report {
                    let current = sha256(serde_json::to_vec(report)?);
                    if artifact
                        .pointer("/report/canonical_sha256")
                        .and_then(Value::as_str)
                        != Some(current.as_str())
                    {
                        return Ok("stale");
                    }
                }
                Ok("valid")
            })
            .unwrap_or("invalid")
    };
    let private_permissions = advice_permissions_private(&root)?;
    let status = if recovery_entries > 0 {
        "recovery_required"
    } else if !private_permissions {
        "insecure_permissions"
    } else {
        latest
    };
    Ok(json!({
        "status": status,
        "latest": latest,
        "retained_runs": retained.len(),
        "retained_bytes": retained.into_iter().sum::<u64>(),
        "private_permissions": private_permissions,
        "recovery_entries": recovery_entries,
        "retention_command": "git slop prune --dry-run"
    }))
}