// cli/misc.rs — misc subcommand handlers use crate::store; use crate::store::StoreView; pub fn cmd_search(terms: &[String], pipeline_args: &[String], expand: bool, full: bool, debug: bool, fuzzy: bool, content: bool) -> Result<(), String> { use crate::store::StoreView; use std::collections::BTreeMap; // Parse pipeline stages (unified: algorithms, filters, transforms, generators) let stages: Vec = if pipeline_args.is_empty() { vec![crate::search::Stage::Algorithm(crate::search::AlgoStage::parse("spread").unwrap())] } else { pipeline_args.iter() .map(|a| crate::search::Stage::parse(a)) .collect::, _>>()? }; // Check if pipeline needs full Store (has filters/transforms/generators) let needs_store = stages.iter().any(|s| !matches!(s, crate::search::Stage::Algorithm(_))); // Check if pipeline starts with a generator (doesn't need seed terms) let has_generator = stages.first().map(|s| matches!(s, crate::search::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 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); 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 { if let Some(node) = store.nodes.get(key) { println!(); for line in node.content.lines() { println!(" {}", line); } println!(); } } } } else { // Fast MmapView path — algorithm-only pipeline 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() .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 { if let Some(content) = view.node_content(&r.key) { println!(); for line in content.lines() { println!(" {}", line); } println!(); } } } } Ok(()) } pub fn cmd_status() -> Result<(), String> { // If stdout is a tty and daemon is running, launch TUI if std::io::IsTerminal::is_terminal(&std::io::stdout()) { // Try TUI first — falls back if daemon not running match crate::tui::run_tui() { Ok(()) => return Ok(()), Err(_) => {} // fall through to text output } } let store = crate::store::Store::load()?; let g = store.build_graph(); let mut type_counts = std::collections::HashMap::new(); for node in store.nodes.values() { *type_counts.entry(format!("{:?}", node.node_type)).or_insert(0usize) += 1; } let mut types: Vec<_> = type_counts.iter().collect(); types.sort_by_key(|(_, c)| std::cmp::Reverse(**c)); println!("Nodes: {} Relations: {}", store.nodes.len(), store.relations.len()); print!("Types:"); for (t, c) in &types { let label = match t.as_str() { "Semantic" => "semantic", "EpisodicSession" | "EpisodicDaily" | "EpisodicWeekly" | "EpisodicMonthly" => "episodic", _ => t, }; print!(" {}={}", label, c); } println!(); println!("Graph edges: {} Communities: {}", g.edge_count(), g.community_count()); Ok(()) } pub fn cmd_log() -> Result<(), String> { let store = crate::store::Store::load()?; for event in store.retrieval_log.iter().rev().take(20) { println!("[{}] q=\"{}\" → {} results", event.timestamp, event.query, event.results.len()); for r in &event.results { println!(" {}", r); } } Ok(()) } pub fn cmd_params() -> Result<(), String> { let store = crate::store::Store::load()?; println!("decay_factor: {}", store.params.decay_factor); println!("use_boost: {}", store.params.use_boost); println!("prune_threshold: {}", store.params.prune_threshold); println!("edge_decay: {}", store.params.edge_decay); println!("max_hops: {}", store.params.max_hops); println!("min_activation: {}", store.params.min_activation); Ok(()) } pub fn cmd_query(expr: &[String]) -> Result<(), String> { if expr.is_empty() { return Err("query requires an expression (try: poc-memory query --help)".into()); } let query_str = expr.join(" "); let store = crate::store::Store::load()?; let graph = store.build_graph(); crate::query_parser::run_query(&store, &graph, &query_str) }