sniff-cli 0.1.0

An exhaustive LLM-backed slop finder for codebases
Documentation
use crate::llm::LLMClient;
use crate::slop_reason::{self, ReasonKind};
use crate::types::FileRecord;
use crate::types::FindingTier;
use std::path::Path;

pub(super) fn is_generic_file_name(file_path: &str, client: &LLMClient) -> bool {
    let stem = Path::new(file_path)
        .file_stem()
        .and_then(|s| s.to_str())
        .unwrap_or("")
        .to_lowercase();
    if stem.is_empty() {
        return false;
    }
    if stem == "llm" {
        return false;
    }

    client
        .config()
        .generic_file_names
        .iter()
        .any(|name| name.to_lowercase() == stem)
}

pub(super) fn normalize_vague_filename_verdict(
    file: &FileRecord,
    client: &LLMClient,
    reason: &str,
) -> Option<FindingTier> {
    if !slop_reason::is(reason, ReasonKind::FilenameVague) {
        return None;
    }

    if is_generic_file_name(&file.file_path, client) {
        return Some(FindingTier::KindaSlop);
    }

    Some(FindingTier::Clean)
}

#[cfg(test)]
mod tests {
    use super::{is_generic_file_name, normalize_vague_filename_verdict};
    use crate::config::{LLMConfig, ResolvedConfig, ThresholdsConfig};
    use crate::llm::LLMClient;
    use crate::types::{FileRecord, FindingTier, MethodRecord};

    fn client_with_generic_llm_name() -> LLMClient {
        let config = ResolvedConfig {
            thresholds: ThresholdsConfig::default(),
            ignore: vec![],
            generic_names: vec![],
            generic_file_names: vec!["llm".to_string()],
            model: "test-model".to_string(),
            llm: LLMConfig {
                system_context: String::new(),
                endpoint: "http://127.0.0.1:9".to_string(),
            },
        };
        LLMClient::new(config, Some("test-key".to_string()))
    }

    fn llm_file() -> FileRecord {
        FileRecord {
            file_path: "src/llm.rs".to_string(),
            source: "pub fn build_payload() {}".to_string(),
            language: "rust".to_string(),
            methods: vec![MethodRecord {
                name: "build_payload".to_string(),
                file_path: "src/llm.rs".to_string(),
                source: "pub fn build_payload() {}".to_string(),
                loc: 2,
                param_count: 0,
                start_line: 1,
                end_line: 2,
                is_exported: false,
                language: "rust".to_string(),
                nesting_depth: 0,
                references: vec![],
                real_ref_count: 0,
            }],
        }
    }

    #[test]
    fn llm_filename_is_not_generic_even_if_config_lists_it() {
        let client = client_with_generic_llm_name();
        assert!(!is_generic_file_name("src/llm.rs", &client));
    }

    #[test]
    fn llm_filename_vagueness_normalizes_to_clean() {
        let client = client_with_generic_llm_name();
        let verdict =
            normalize_vague_filename_verdict(&llm_file(), &client, "filename is vague 'llm'");

        assert_eq!(verdict, Some(FindingTier::Clean));
    }
}