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:
parent
8b59becbab
commit
aff872e101
2 changed files with 12 additions and 165 deletions
147
src/cli/misc.rs
147
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<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());
|
||||
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<String> = 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<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!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let result = crate::mcp_server::memory_rpc(
|
||||
"memory_search",
|
||||
serde_json::json!({"keys": keys}),
|
||||
).map_err(|e| e.to_string())?;
|
||||
print!("{}", result);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
30
src/main.rs
30
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<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,
|
||||
/// Seed node keys
|
||||
keys: Vec<String>,
|
||||
},
|
||||
/// 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),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue