github_mcp/tools/
search_tool.rs1use rusqlite::Connection;
4
5use crate::data::store::search_endpoints;
6use crate::services::embedding_service::embed;
7
8fn warn_if_embeddings_incomplete(conn: &Connection) -> bool {
15 let counts: rusqlite::Result<(i64, i64)> = conn.query_row(
16 "SELECT (SELECT COUNT(*) FROM endpoints), (SELECT COUNT(*) FROM semantic_endpoints)",
17 [],
18 |row| Ok((row.get(0)?, row.get(1)?)),
19 );
20 let incomplete = matches!(&counts, Ok((endpoints_count, semantic_count)) if semantic_count < endpoints_count);
21 if let Ok((endpoints_count, semantic_count)) = counts
22 && incomplete
23 {
24 tracing::warn!(
25 endpoints_count,
26 semantic_count,
27 "semantic_endpoints is missing {} row(s) compared to endpoints — search results \
28 may be incomplete; re-run populate-embeddings for this store",
29 endpoints_count - semantic_count
30 );
31 }
32 incomplete
33}
34
35pub fn search_operations(
39 conn: &Connection,
40 query: &str,
41 limit: usize,
42) -> anyhow::Result<serde_json::Value> {
43 warn_if_embeddings_incomplete(conn);
44 let query_embedding = embed(query)?;
45 let results = search_endpoints(conn, &query_embedding, limit)?;
46 Ok(serde_json::to_value(results)?)
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52
53 #[test]
54 fn incomplete_embedding_catalog_diagnostic_handles_every_count_shape() {
55 let conn = Connection::open_in_memory().unwrap();
56 conn.execute_batch(
57 "CREATE TABLE endpoints (operation_id TEXT); \
58 CREATE TABLE semantic_endpoints (operation_id TEXT); \
59 INSERT INTO endpoints VALUES ('missing');",
60 )
61 .unwrap();
62 assert!(warn_if_embeddings_incomplete(&conn));
63 conn.execute("INSERT INTO semantic_endpoints VALUES ('missing')", [])
64 .unwrap();
65 assert!(!warn_if_embeddings_incomplete(&conn));
66 }
67}