harness-glob 0.1.1

Glob tool for AI agent harnesses — bash-style pattern matching with .gitignore enumeration, alias pushback, NOT_FOUND siblings, and auto-split absolute-path patterns
Documentation
use crate::constants::FUZZY_SUGGESTION_LIMIT;
use std::path::Path;

pub fn suggest_siblings(missing_path: &str) -> Vec<String> {
    let p = Path::new(missing_path);
    let parent = match p.parent() {
        Some(pp) => pp,
        None => return Vec::new(),
    };
    let base = p
        .file_name()
        .and_then(|n| n.to_str())
        .unwrap_or("")
        .to_lowercase();
    let entries = match std::fs::read_dir(parent) {
        Ok(e) => e,
        Err(_) => return Vec::new(),
    };
    let mut scored: Vec<(i64, String)> = entries
        .filter_map(|e| e.ok())
        .filter_map(|e| e.file_name().to_str().map(|n| n.to_string()))
        .filter_map(|name| {
            let s = similarity(&base, &name.to_lowercase());
            if s > 0 {
                Some((s, parent.join(&name).to_string_lossy().into_owned()))
            } else {
                None
            }
        })
        .collect();
    scored.sort_by(|a, b| b.0.cmp(&a.0));
    scored
        .into_iter()
        .take(FUZZY_SUGGESTION_LIMIT)
        .map(|(_, p)| p)
        .collect()
}

fn similarity(a: &str, b: &str) -> i64 {
    if a == b {
        return 1000;
    }
    if a.is_empty() || b.is_empty() {
        return 0;
    }
    if a.contains(b) || b.contains(a) {
        return 500;
    }
    let prefix = common_prefix(a, b);
    if prefix >= 3 {
        return 200 + prefix as i64;
    }
    let a_len = a.len() as i64;
    let b_len = b.len() as i64;
    if prefix >= 2 && (a_len - b_len).abs() <= 2 {
        return 100 + prefix as i64;
    }
    let a_ext = ext_of(a);
    let b_ext = ext_of(b);
    if !a_ext.is_empty() && a_ext == b_ext {
        return 10;
    }
    0
}

fn common_prefix(a: &str, b: &str) -> usize {
    a.bytes().zip(b.bytes()).take_while(|(x, y)| x == y).count()
}

fn ext_of(name: &str) -> &str {
    match name.rfind('.') {
        Some(i) if i > 0 => &name[i..],
        _ => "",
    }
}