From aff872e10106b5eae518c014adeeb4b34e63a08f Mon Sep 17 00:00:00 2001 From: ProofOfConcept Date: Sun, 12 Apr 2026 22:44:59 -0400 Subject: [PATCH] cmd_search: thin wrapper around memory_search RPC Remove term matching, pipeline stages, mmap/store paths. Just pass keys to memory_search and print result. For anything fancy, use memory_query. -165 lines. Co-Authored-By: Proof of Concept --- src/cli/misc.rs | 147 +++--------------------------------------------- src/main.rs | 30 ++-------- 2 files changed, 12 insertions(+), 165 deletions(-) diff --git a/src/cli/misc.rs b/src/cli/misc.rs index 0217843..f48204a 100644 --- a/src/cli/misc.rs +++ b/src/cli/misc.rs @@ -1,146 +1,15 @@ // cli/misc.rs — misc subcommand handlers -pub fn cmd_search(terms: &[String], pipeline_args: &[String], expand: bool, full: bool, debug: bool, fuzzy: bool, content: bool) -> Result<(), String> { - use std::collections::BTreeMap; - use crate::search::{Stage, Algorithm, AlgoStage}; - - // When running inside an agent session, exclude already-surfaced nodes - let seen = crate::session::HookSession::from_env() - .map(|s| s.seen()) - .unwrap_or_default(); - - // Build pipeline: if args provided, parse them; otherwise default to spread - let stages: Vec = if pipeline_args.is_empty() { - vec![Stage::Algorithm(AlgoStage { algo: Algorithm::Spread, params: std::collections::HashMap::new() })] - } else { - // Join args with | and parse as unified query - let pipeline_str = format!("all | {}", pipeline_args.join(" | ")); - crate::query_parser::parse_stages(&pipeline_str)? - }; - - // Check if pipeline needs full Store (has filters/transforms/generators) - let needs_store = stages.iter().any(|s| !matches!(s, Stage::Algorithm(_))); - // Check if pipeline starts with a generator (doesn't need seed terms) - let has_generator = stages.first().map(|s| matches!(s, Stage::Generator(_))).unwrap_or(false); - - if terms.is_empty() && !has_generator { - return Err("search requires terms or a generator stage (e.g. 'all')".into()); +pub fn cmd_search(keys: &[String]) -> Result<(), String> { + if keys.is_empty() { + return Err("search requires seed keys".into()); } - - let query: String = terms.join(" "); - - if debug { - let names: Vec = stages.iter().map(|s| format!("{}", s)).collect(); - println!("[search] pipeline: {}", names.join(" → ")); - } - - let max_results = if expand { 15 } else { 5 }; - - if needs_store { - // Full Store path — needed for filter/transform/generator stages - let store = crate::store::Store::load()?; - let graph = store.build_graph(); - - let seeds = if has_generator { - vec![] // generator will produce its own result set - } else { - let terms_map: BTreeMap = query.split_whitespace() - .map(|t| (t.to_lowercase(), 1.0)) - .collect(); - let (seeds, _) = crate::search::match_seeds_opts(&terms_map, &store, fuzzy, content); - seeds - }; - - let raw = crate::search::run_query(&stages, seeds, &graph, &store, debug, max_results); - - let raw: Vec<_> = raw.into_iter() - .filter(|(key, _)| !seen.contains(key)) - .collect(); - - if raw.is_empty() { - eprintln!("No results"); - return Ok(()); - } - - for (i, (key, score)) in raw.iter().enumerate().take(max_results) { - let weight = store.nodes.get(key).map(|n| n.weight).unwrap_or(0.0); - println!("{:2}. [{:.2}/{:.2}] {}", i + 1, score, weight, key); - if full - && let Some(node) = store.nodes.get(key) { - println!(); - for line in node.content.lines() { - println!(" {}", line); - } - println!(); - } - } - } else { - // Fast MmapView path — algorithm-only pipeline - use crate::store::StoreView; - let view = crate::store::AnyView::load()?; - let graph = crate::graph::build_graph_fast(&view); - - let terms_map: BTreeMap = query.split_whitespace() - .map(|t| (t.to_lowercase(), 1.0)) - .collect(); - let (seeds, direct_hits) = crate::search::match_seeds_opts(&terms_map, &view, fuzzy, content); - - if seeds.is_empty() { - eprintln!("No results for '{}'", query); - return Ok(()); - } - - if debug { - println!("[search] {} seeds from query '{}'", seeds.len(), query); - } - - // Extract AlgoStages from the unified stages - let algo_stages: Vec<&crate::search::AlgoStage> = stages.iter() - .filter_map(|s| match s { - crate::search::Stage::Algorithm(a) => Some(a), - _ => None, - }) - .collect(); - let algo_owned: Vec = algo_stages.into_iter().cloned().collect(); - - let raw = crate::search::run_pipeline(&algo_owned, seeds, &graph, &view, debug, max_results); - - let results: Vec = raw.into_iter() - .filter(|(key, _)| !seen.contains(key)) - .map(|(key, activation)| { - let is_direct = direct_hits.contains(&key); - crate::search::SearchResult { key, activation, is_direct, snippet: None } - }) - .collect(); - - if results.is_empty() { - eprintln!("No results for '{}'", query); - return Ok(()); - } - - // Log retrieval - crate::store::Store::log_retrieval_static(&query, - &results.iter().map(|r| r.key.clone()).collect::>()); - - let bump_keys: Vec<&str> = results.iter().take(max_results).map(|r| r.key.as_str()).collect(); - let _ = crate::lookups::bump_many(&bump_keys); - - for (i, r) in results.iter().enumerate().take(max_results) { - let marker = if r.is_direct { "→" } else { " " }; - let weight = view.node_weight(&r.key); - println!("{}{:2}. [{:.2}/{:.2}] {}", marker, i + 1, r.activation, weight, r.key); - if full - && let Some(content) = view.node_content(&r.key) { - println!(); - for line in content.lines() { - println!(" {}", line); - } - println!(); - } - } - } - + let result = crate::mcp_server::memory_rpc( + "memory_search", + serde_json::json!({"keys": keys}), + ).map_err(|e| e.to_string())?; + print!("{}", result); Ok(()) } diff --git a/src/main.rs b/src/main.rs index 6a9f427..1da3fa9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,31 +35,10 @@ struct Cli { enum Command { // ── Core (daily use) ────────────────────────────────────────────── - /// Search memory (AND logic across terms) - /// - /// Pipeline: -p spread -p spectral,k=20 - /// Default pipeline: spread + /// Search memory via spreading activation from seed keys Search { - /// Search terms - query: Vec, - /// Algorithm pipeline stages (repeatable) - #[arg(short, long = "pipeline")] - pipeline: Vec, - /// Show more results - #[arg(long)] - expand: bool, - /// Show node content, not just keys - #[arg(long)] - full: bool, - /// Show debug output for each pipeline stage - #[arg(long)] - debug: bool, - /// Also match key components (e.g. "irc" matches "irc-access") - #[arg(long)] - fuzzy: bool, - /// Also search node content (slow, use when graph search misses) - #[arg(long)] - content: bool, + /// Seed node keys + keys: Vec, }, /// Output a node's content to stdout Render { @@ -462,8 +441,7 @@ trait Run { impl Run for Command { fn run(self) -> Result<(), String> { match self { - Self::Search { query, pipeline, expand, full, debug, fuzzy, content } - => cli::misc::cmd_search(&query, &pipeline, expand, full, debug, fuzzy, content), + Self::Search { keys } => cli::misc::cmd_search(&keys), Self::Render { key } => cli::node::cmd_render(&key), Self::Write { key } => cli::node::cmd_write(&key), Self::Edit { key } => cli::node::cmd_edit(&key),