sniff-cli 0.1.1

An exhaustive LLM-backed slop finder for codebases
Documentation
fn string_from_array(items: &[serde_json::Value]) -> Option<String> {
    let parts: Vec<String> = items
        .iter()
        .filter_map(string_from_value)
        .filter(|part| !part.is_empty())
        .collect();
    if parts.is_empty() {
        None
    } else {
        Some(parts.join(""))
    }
}

fn string_from_object(map: &serde_json::Map<String, serde_json::Value>) -> Option<String> {
    ["text", "content", "value", "output"]
        .iter()
        .find_map(|key| {
            map.get(*key)
                .and_then(string_from_value)
                .filter(|part| !part.is_empty())
        })
}

fn string_from_value(value: &serde_json::Value) -> Option<String> {
    match value {
        serde_json::Value::String(s) => Some(s.clone()),
        serde_json::Value::Array(items) => string_from_array(items),
        serde_json::Value::Object(map) => string_from_object(map),
        _ => None,
    }
}

pub(super) fn openai_content(data: &serde_json::Value) -> String {
    let candidates = [
        "/choices/0/message/content",
        "/choices/0/text",
        "/choices/0/message/output",
        "/output_text",
        "/content",
        "/message/content",
    ];

    for candidate in candidates {
        if let Some(value) = data.pointer(candidate)
            && let Some(text) = string_from_value(value)
            && !text.trim().is_empty()
        {
            return text;
        }
    }

    String::new()
}

pub(super) fn anthropic_content(data: &serde_json::Value) -> String {
    let candidates = ["/content", "/choices/0/message/content", "/output_text"];

    for candidate in candidates {
        if let Some(value) = data.pointer(candidate)
            && let Some(text) = string_from_value(value)
            && !text.trim().is_empty()
        {
            return text;
        }
    }

    String::new()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn openai_content_handles_string_and_array_shapes() {
        let data = serde_json::json!({
            "choices": [
                {
                    "message": {
                        "content": [
                            { "type": "text", "text": "{\"smelly\":false}" }
                        ]
                    }
                }
            ]
        });

        assert_eq!(openai_content(&data), "{\"smelly\":false}");
    }

    #[test]
    fn anthropic_content_handles_block_arrays() {
        let data = serde_json::json!({
            "content": [
                { "type": "text", "text": "{\"smelly\":false}" }
            ]
        });

        assert_eq!(anthropic_content(&data), "{\"smelly\":false}");
    }

    #[test]
    fn anthropic_content_ignores_thinking_blocks() {
        let data = serde_json::json!({
            "content": [
                {
                    "type": "thinking",
                    "thinking": "I will think out loud here."
                },
                {
                    "type": "text",
                    "text": "{\"smelly\":false,\"tier\":\"clean\",\"evidence\":\"\",\"reason\":\"clean\"}"
                }
            ]
        });

        assert_eq!(
            anthropic_content(&data),
            "{\"smelly\":false,\"tier\":\"clean\",\"evidence\":\"\",\"reason\":\"clean\"}"
        );
    }
}