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 <poc@bcachefs.org>
This commit is contained in:
ProofOfConcept 2026-04-12 22:44:59 -04:00
parent 8b59becbab
commit aff872e101
2 changed files with 12 additions and 165 deletions

View file

@ -1,146 +1,15 @@
// cli/misc.rs — misc subcommand handlers // 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> { pub fn cmd_search(keys: &[String]) -> Result<(), String> {
use std::collections::BTreeMap; if keys.is_empty() {
use crate::search::{Stage, Algorithm, AlgoStage}; return Err("search requires seed keys".into());
// 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<Stage> = 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());
} }
let result = crate::mcp_server::memory_rpc(
let query: String = terms.join(" "); "memory_search",
serde_json::json!({"keys": keys}),
if debug { ).map_err(|e| e.to_string())?;
let names: Vec<String> = stages.iter().map(|s| format!("{}", s)).collect(); print!("{}", result);
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<String, f64> = 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<String, f64> = 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<crate::search::AlgoStage> = algo_stages.into_iter().cloned().collect();
let raw = crate::search::run_pipeline(&algo_owned, seeds, &graph, &view, debug, max_results);
let results: Vec<crate::search::SearchResult> = 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::<Vec<_>>());
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!();
}
}
}
Ok(()) Ok(())
} }

View file

@ -35,31 +35,10 @@ struct Cli {
enum Command { enum Command {
// ── Core (daily use) ────────────────────────────────────────────── // ── Core (daily use) ──────────────────────────────────────────────
/// Search memory (AND logic across terms) /// Search memory via spreading activation from seed keys
///
/// Pipeline: -p spread -p spectral,k=20
/// Default pipeline: spread
Search { Search {
/// Search terms /// Seed node keys
query: Vec<String>, keys: Vec<String>,
/// Algorithm pipeline stages (repeatable)
#[arg(short, long = "pipeline")]
pipeline: Vec<String>,
/// 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,
}, },
/// Output a node's content to stdout /// Output a node's content to stdout
Render { Render {
@ -462,8 +441,7 @@ trait Run {
impl Run for Command { impl Run for Command {
fn run(self) -> Result<(), String> { fn run(self) -> Result<(), String> {
match self { match self {
Self::Search { query, pipeline, expand, full, debug, fuzzy, content } Self::Search { keys } => cli::misc::cmd_search(&keys),
=> cli::misc::cmd_search(&query, &pipeline, expand, full, debug, fuzzy, content),
Self::Render { key } => cli::node::cmd_render(&key), Self::Render { key } => cli::node::cmd_render(&key),
Self::Write { key } => cli::node::cmd_write(&key), Self::Write { key } => cli::node::cmd_write(&key),
Self::Edit { key } => cli::node::cmd_edit(&key), Self::Edit { key } => cli::node::cmd_edit(&key),