sniff-cli 0.1.0

An exhaustive LLM-backed slop finder for codebases
Documentation
use crate::config::ResolvedConfig;
use crate::languages;
use crate::roles::{FileRole, classify_file_role};
use ignore::DirEntry;
use std::path::Path;

fn is_test_file(path: &Path) -> bool {
    let name = path
        .file_name()
        .and_then(|n| n.to_str())
        .unwrap_or("")
        .to_lowercase();

    let has_test_segment = path.components().any(|c| {
        c.as_os_str()
            .to_string_lossy()
            .to_lowercase()
            .starts_with("test")
    });

    name.starts_with("test_")
        || name.ends_with("_test.go")
        || name.contains(".test.")
        || name.contains(".spec.")
        || has_test_segment
}

fn is_supported_file(path: &Path) -> bool {
    path.extension()
        .and_then(|e| e.to_str())
        .and_then(languages::get_adapter)
        .is_some()
}

fn is_skipped_surface(path: &Path) -> bool {
    let path_str = path.to_string_lossy().replace("\\\\?\\", "");
    matches!(
        classify_file_role(&path_str),
        FileRole::Docs | FileRole::Generated | FileRole::Fixture
    )
}

fn path_matches_ignored_component(path: &Path, ignored: &[String]) -> bool {
    ignored.iter().any(|ignored_name| {
        let ignored_name = ignored_name.to_lowercase();
        path.components()
            .any(|component| component.as_os_str().to_string_lossy().to_lowercase() == ignored_name)
    })
}

pub(crate) fn should_descend(entry: &DirEntry, ignored: &[String]) -> bool {
    !path_matches_ignored_component(entry.path(), ignored)
}

pub(crate) fn should_keep_file(path: &Path, config: &ResolvedConfig) -> bool {
    !path_matches_ignored_component(path, &config.ignore)
        && !is_test_file(path)
        && !is_skipped_surface(path)
        && is_supported_file(path)
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;
    use std::time::{SystemTime, UNIX_EPOCH};

    #[test]
    fn root_compatibility_shims_are_still_scanned() {
        let config = ResolvedConfig::default();
        assert!(should_keep_file(Path::new("src/llm.py"), &config));
    }

    #[test]
    fn ignored_directory_names_match_case_insensitively() {
        let unique = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_nanos();
        let root = std::env::temp_dir().join(format!("sniff-walker-case-test-{unique}"));
        let ignored_dir = root.join("NODE_MODULES");
        let src_dir = root.join("src");
        fs::create_dir_all(&ignored_dir).unwrap();
        fs::create_dir_all(&src_dir).unwrap();

        fs::write(
            ignored_dir.join("ignore.ts"),
            "export function ignored() {}\n",
        )
        .unwrap();
        fs::write(src_dir.join("keep.ts"), "export function kept() {}\n").unwrap();

        let files =
            crate::walker::walk(root.to_str().unwrap(), &ResolvedConfig::default()).unwrap();
        assert!(
            files.iter().any(|path| path.contains("keep.ts")),
            "expected visible file to remain visible: {files:?}"
        );
        assert!(
            !files.iter().any(|path| path.contains("NODE_MODULES")),
            "expected uppercase ignored directory to be pruned: {files:?}"
        );

        let _ = fs::remove_dir_all(&root);
    }
}