diff --git a/src/cli/admin.rs b/src/cli/admin.rs index ddbaad1..8e91439 100644 --- a/src/cli/admin.rs +++ b/src/cli/admin.rs @@ -1,6 +1,7 @@ // cli/admin.rs — admin subcommand handlers use crate::store; + fn install_default_file(data_dir: &std::path::Path, name: &str, content: &str) -> Result<(), String> { let path = data_dir.join(name); if !path.exists() { @@ -11,7 +12,6 @@ fn install_default_file(data_dir: &std::path::Path, name: &str, content: &str) - Ok(()) } - pub fn cmd_init() -> Result<(), String> { let cfg = crate::config::get(); diff --git a/src/cli/graph.rs b/src/cli/graph.rs index c23940c..99a3f96 100644 --- a/src/cli/graph.rs +++ b/src/cli/graph.rs @@ -4,19 +4,7 @@ // link, link-add, link-impact, link-audit, cap-degree, // normalize-strengths, trace, spectral-*, organize, communities. -use crate::{store, graph}; -use crate::store::StoreView; - -pub fn cmd_graph() -> Result<(), String> { - let store = store::Store::load()?; - let g = store.build_graph(); - println!("Graph: {} nodes, {} edges, {} communities", - g.nodes().len(), g.edge_count(), g.community_count()); - println!("σ={:.2} α={:.2} gini={:.3} cc={:.4}", - g.small_world_sigma(), g.degree_power_law_exponent(), - g.degree_gini(), g.avg_clustering_coefficient()); - Ok(()) -} +use crate::store; pub fn cmd_cap_degree(max_deg: usize) -> Result<(), String> { let mut store = store::Store::load()?; @@ -91,111 +79,6 @@ pub fn cmd_trace(key: &[String]) -> Result<(), String> { Ok(()) } -pub fn cmd_organize(term: &str, key_only: bool, create_anchor: bool) -> Result<(), String> { - let mut store = store::Store::load()?; - - // Step 1: find all non-deleted nodes matching the term - let term_lower = term.to_lowercase(); - let mut topic_nodes: Vec<(String, String)> = Vec::new(); // (key, content) - - let skip_prefixes = ["_", "deep-index#", "facts-", "irc-history#"]; - - for (key, node) in &store.nodes { - if node.deleted { continue; } - // Skip episodic/digest nodes — use NodeType, not key prefix - if node.node_type != crate::store::NodeType::Semantic { continue; } - let key_matches = key.to_lowercase().contains(&term_lower); - let content_matches = !key_only && node.content.to_lowercase().contains(&term_lower); - if !key_matches && !content_matches { continue; } - if skip_prefixes.iter().any(|p| key.starts_with(p)) { continue; } - topic_nodes.push((key.clone(), node.content.clone())); - } - - if topic_nodes.is_empty() { - println!("No topic nodes found matching '{}'", term); - return Ok(()); - } - - topic_nodes.sort_by(|a, b| a.0.cmp(&b.0)); - - println!("=== Organize: '{}' ===", term); - println!("Found {} topic nodes:\n", topic_nodes.len()); - for (key, content) in &topic_nodes { - let lines = content.lines().count(); - let words = content.split_whitespace().count(); - println!(" {:60} {:>4} lines {:>5} words", key, lines, words); - } - - // Step 2: check connectivity within cluster - let g = store.build_graph(); - println!("=== Connectivity ===\n"); - - // Pick hub by intra-cluster connectivity, not overall degree - let cluster_keys: std::collections::HashSet<&str> = topic_nodes.iter() - .filter(|(k,_)| store.nodes.contains_key(k.as_str())) - .map(|(k,_)| k.as_str()) - .collect(); - - let mut best_hub: Option<(&str, usize)> = None; - for key in &cluster_keys { - let intra_degree = g.neighbor_keys(key).iter() - .filter(|n| cluster_keys.contains(*n)) - .count(); - if best_hub.is_none() || intra_degree > best_hub.unwrap().1 { - best_hub = Some((key, intra_degree)); - } - } - - if let Some((hub, deg)) = best_hub { - println!(" Hub: {} (degree {})", hub, deg); - let hub_nbrs = g.neighbor_keys(hub); - - let mut unlinked = Vec::new(); - for (key, _) in &topic_nodes { - if key == hub { continue; } - if store.nodes.get(key.as_str()).is_none() { continue; } - if !hub_nbrs.contains(key.as_str()) { - unlinked.push(key.clone()); - } - } - - if unlinked.is_empty() { - println!(" All cluster nodes connected to hub ✓"); - } else { - println!(" NOT linked to hub:"); - for key in &unlinked { - println!(" {} → needs link to {}", key, hub); - } - } - } - - // Step 4: anchor node - if create_anchor { - println!("\n=== Anchor node ===\n"); - if store.nodes.contains_key(term) && !store.nodes[term].deleted { - println!(" Anchor '{}' already exists ✓", term); - } else { - let desc = format!("Anchor node for '{}' search term", term); - store.upsert(term, &desc)?; - let anchor_uuid = store.nodes.get(term).unwrap().uuid; - for (key, _) in &topic_nodes { - if store.nodes.get(key.as_str()).is_none() { continue; } - let target_uuid = store.nodes[key.as_str()].uuid; - let rel = store::new_relation( - anchor_uuid, target_uuid, - store::RelationType::Link, 0.8, - term, key, - ); - store.add_relation(rel)?; - } - println!(" Created anchor '{}' with {} links", term, topic_nodes.len()); - } - } - - store.save()?; - Ok(()) -} - /// Show communities sorted by isolation (most isolated first). /// Useful for finding poorly-integrated knowledge clusters that need /// organize agents aimed at them. @@ -207,4 +90,3 @@ pub fn cmd_communities(top_n: usize, min_size: usize) -> Result<(), String> { print!("{}", result); Ok(()) } - diff --git a/src/main.rs b/src/main.rs index c93732e..2b7d494 100644 --- a/src/main.rs +++ b/src/main.rs @@ -270,22 +270,6 @@ enum GraphCmd { #[arg(long, default_value_t = 2)] min_size: usize, }, - /// Show graph structure overview - Overview, - /// Diagnose duplicate/overlapping nodes for a topic cluster - Organize { - /// Search term (matches node keys; also content unless --key-only) - term: String, - /// Similarity threshold for pair reporting (default: 0.4) - #[arg(long, default_value_t = 0.4)] - threshold: f32, - /// Only match node keys, not content - #[arg(long)] - key_only: bool, - /// Create anchor node for the search term and link to cluster - #[arg(long)] - anchor: bool, - }, } #[derive(Subcommand)] @@ -480,9 +464,6 @@ impl Run for GraphCmd { Self::NormalizeStrengths { apply } => cli::graph::cmd_normalize_strengths(apply), Self::Trace { key } => cli::graph::cmd_trace(&key), Self::Communities { top_n, min_size } => cli::graph::cmd_communities(top_n, min_size), - Self::Overview => cli::graph::cmd_graph(), - Self::Organize { term, key_only, anchor, .. } - => cli::graph::cmd_organize(&term, key_only, anchor), } } }