diff --git a/crates/ctxgraph-cli/src/commands/query.rs b/crates/ctxgraph-cli/src/commands/query.rs index 295545e..91cec32 100644 --- a/crates/ctxgraph-cli/src/commands/query.rs +++ b/crates/ctxgraph-cli/src/commands/query.rs @@ -9,7 +9,10 @@ pub fn run( _source: Option, ) -> ctxgraph::Result<()> { let graph = open_graph()?; - let results = graph.search(&text, limit)?; + + let safe_query = super::query_universal::sanitize_query(&text); + + let results = graph.search(&safe_query, limit)?; if results.is_empty() { println!("No results found for '{text}'"); diff --git a/crates/ctxgraph-cli/src/commands/query_universal.rs b/crates/ctxgraph-cli/src/commands/query_universal.rs index efdfa43..7b49c41 100644 --- a/crates/ctxgraph-cli/src/commands/query_universal.rs +++ b/crates/ctxgraph-cli/src/commands/query_universal.rs @@ -170,20 +170,7 @@ pub fn run(query_text: String, limit: usize) -> ctxgraph::Result<()> { } // ── Stage 3: fallback fused search ─────────────────────────── - // FTS5 doesn't like punctuation. Strip everything except word chars. - let safe_query: String = query_text - .chars() - .map(|c| { - if c.is_alphanumeric() || c == ' ' || c == '_' { - c - } else { - ' ' - } - }) - .collect::() - .split_whitespace() - .collect::>() - .join(" "); + let safe_query = sanitize_query(&query_text); println!("\n FALLBACK fused search results:"); let entity_hits = graph.search_entities(&safe_query, limit.min(10))?; @@ -300,20 +287,7 @@ fn split_verb_entity( } fn exact_entity_lookup(graph: &ctxgraph::Graph, name: &str) -> ctxgraph::Result> { - // Strip punctuation for FTS5 compatibility - let safe: String = name - .chars() - .map(|c| { - if c.is_alphanumeric() || c == ' ' || c == '_' { - c - } else { - ' ' - } - }) - .collect::() - .split_whitespace() - .collect::>() - .join(" "); + let safe = sanitize_query(name); if safe.is_empty() { return Ok(None); } @@ -328,6 +302,22 @@ fn exact_entity_lookup(graph: &ctxgraph::Graph, name: &str) -> ctxgraph::Result< Ok(None) } +/// Sanitize a query string for FTS5 by stripping non-alphanumeric characters. +pub fn sanitize_query(text: &str) -> String { + text.chars() + .map(|c| { + if c.is_alphanumeric() || c == ' ' || c == '_' { + c + } else { + ' ' + } + }) + .collect::() + .split_whitespace() + .collect::>() + .join(" ") +} + fn is_pure_stopwords(s: &str) -> bool { const STOP: &[&str] = &[ "what", "who", "where", "when", "why", "how", "the", "a", "an", "is", "are", "was", "were",