kill cmd_graph, cmd_organize
This commit is contained in:
parent
11f2d5b169
commit
1f6bfb5915
3 changed files with 2 additions and 139 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
// cli/admin.rs — admin subcommand handlers
|
// cli/admin.rs — admin subcommand handlers
|
||||||
|
|
||||||
use crate::store;
|
use crate::store;
|
||||||
|
|
||||||
fn install_default_file(data_dir: &std::path::Path, name: &str, content: &str) -> Result<(), String> {
|
fn install_default_file(data_dir: &std::path::Path, name: &str, content: &str) -> Result<(), String> {
|
||||||
let path = data_dir.join(name);
|
let path = data_dir.join(name);
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
|
|
@ -11,7 +12,6 @@ fn install_default_file(data_dir: &std::path::Path, name: &str, content: &str) -
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn cmd_init() -> Result<(), String> {
|
pub fn cmd_init() -> Result<(), String> {
|
||||||
let cfg = crate::config::get();
|
let cfg = crate::config::get();
|
||||||
|
|
||||||
|
|
|
||||||
120
src/cli/graph.rs
120
src/cli/graph.rs
|
|
@ -4,19 +4,7 @@
|
||||||
// link, link-add, link-impact, link-audit, cap-degree,
|
// link, link-add, link-impact, link-audit, cap-degree,
|
||||||
// normalize-strengths, trace, spectral-*, organize, communities.
|
// normalize-strengths, trace, spectral-*, organize, communities.
|
||||||
|
|
||||||
use crate::{store, graph};
|
use crate::store;
|
||||||
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(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cmd_cap_degree(max_deg: usize) -> Result<(), String> {
|
pub fn cmd_cap_degree(max_deg: usize) -> Result<(), String> {
|
||||||
let mut store = store::Store::load()?;
|
let mut store = store::Store::load()?;
|
||||||
|
|
@ -91,111 +79,6 @@ pub fn cmd_trace(key: &[String]) -> Result<(), String> {
|
||||||
Ok(())
|
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).
|
/// Show communities sorted by isolation (most isolated first).
|
||||||
/// Useful for finding poorly-integrated knowledge clusters that need
|
/// Useful for finding poorly-integrated knowledge clusters that need
|
||||||
/// organize agents aimed at them.
|
/// organize agents aimed at them.
|
||||||
|
|
@ -207,4 +90,3 @@ pub fn cmd_communities(top_n: usize, min_size: usize) -> Result<(), String> {
|
||||||
print!("{}", result);
|
print!("{}", result);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
19
src/main.rs
19
src/main.rs
|
|
@ -270,22 +270,6 @@ enum GraphCmd {
|
||||||
#[arg(long, default_value_t = 2)]
|
#[arg(long, default_value_t = 2)]
|
||||||
min_size: usize,
|
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)]
|
#[derive(Subcommand)]
|
||||||
|
|
@ -480,9 +464,6 @@ impl Run for GraphCmd {
|
||||||
Self::NormalizeStrengths { apply } => cli::graph::cmd_normalize_strengths(apply),
|
Self::NormalizeStrengths { apply } => cli::graph::cmd_normalize_strengths(apply),
|
||||||
Self::Trace { key } => cli::graph::cmd_trace(&key),
|
Self::Trace { key } => cli::graph::cmd_trace(&key),
|
||||||
Self::Communities { top_n, min_size } => cli::graph::cmd_communities(top_n, min_size),
|
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),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue