rho-coding-agent 2.27.0

A fast Rust agent harness with a small footprint and opinionated defaults
use pretty_assertions::assert_eq;
use serde_json::json;

use super::{search_entries, ToolCatalogEntry};

fn entry(name: &str, description: &str) -> ToolCatalogEntry {
    ToolCatalogEntry {
        name: name.into(),
        description: description.into(),
        parameters: json!({}),
        returns: None,
    }
}

// Covers: model-written multi-term and operator-prefixed queries find MCP tools
// (a `+memorywhale remember` query returned [] and the parent gave up on MCP).
// Owner: codemode discovery search shared by tool_search and search_tools.
#[test]
fn search_matches_any_term_and_ranks_name_hits_first() {
    let catalog = [
        entry("bash", "Run shell commands."),
        entry("mcp__memorywhale__remember", "Save a lesson to memory."),
        entry(
            "mcp__memorywhale__search_memory",
            "Full-text search over memory.",
        ),
    ];
    let cases: [(&str, Vec<&str>); 7] = [
        (
            "+memorywhale remember",
            vec![
                "mcp__memorywhale__remember",
                "mcp__memorywhale__search_memory",
            ],
        ),
        (
            "search MEMORY",
            vec![
                "mcp__memorywhale__search_memory",
                "mcp__memorywhale__remember",
            ],
        ),
        (
            "mcp__memorywhale__remember",
            vec!["mcp__memorywhale__remember"],
        ),
        (
            "  ",
            vec![
                "bash",
                "mcp__memorywhale__remember",
                "mcp__memorywhale__search_memory",
            ],
        ),
        // A name hit outranks a description hit.
        ("shell remember", vec!["mcp__memorywhale__remember", "bash"]),
        // Filler words must not match every description containing `a`/`to`.
        ("post a message to slack", vec![]),
        ("github", vec![]),
    ];
    for (query, expected) in cases {
        for limit in [0, 1, catalog.len(), usize::MAX] {
            let names: Vec<&str> = search_entries(catalog.iter(), query, limit)
                .into_iter()
                .map(|hit| hit.name.as_str())
                .collect();
            assert_eq!(
                (query, limit, names.as_slice()),
                (query, limit, &expected[..expected.len().min(limit)])
            );
        }
    }
}