From 41fcec58f0f88d9679e171e56c967099ee4427d3 Mon Sep 17 00:00:00 2001 From: ProofOfConcept Date: Thu, 26 Mar 2026 18:00:23 -0400 Subject: [PATCH] main: replace giant match block with Run trait dispatch Each subcommand enum (Command, NodeCmd, JournalCmd, GraphCmd, CursorCmd, DaemonCmd, AgentCmd, AdminCmd) now implements a Run trait. main() becomes `cli.command.run()`. Standalone dispatch functions (cmd_cursor, cmd_daemon, cmd_experience_mine) inlined into their enum's Run impl. No functional changes. Co-Authored-By: Kent Overstreet --- src/main.rs | 385 +++++++++++++++++++++++++++------------------------- 1 file changed, 198 insertions(+), 187 deletions(-) diff --git a/src/main.rs b/src/main.rs index 423eb69..f18e433 100644 --- a/src/main.rs +++ b/src/main.rs @@ -767,6 +767,203 @@ fn print_help() { } } +// ── Dispatch ───────────────────────────────────────────────────────── + +trait Run { + fn run(self) -> Result<(), String>; +} + +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::Render { key } => cli::node::cmd_render(&key), + Self::Write { key } => cli::node::cmd_write(&key), + Self::Edit { key } => cli::node::cmd_edit(&key), + Self::History { full, key } => cli::node::cmd_history(&key, full), + Self::Tail { n, full } => cli::journal::cmd_tail(n, full), + Self::Status => cli::misc::cmd_status(), + Self::Query { expr } => cli::misc::cmd_query(&expr), + Self::Used { key } => cli::node::cmd_used(&key), + Self::Wrong { key, context } => cli::node::cmd_wrong(&key, &context), + Self::NotRelevant { key } => cli::node::cmd_not_relevant(&key), + Self::NotUseful { key } => cli::node::cmd_not_useful(&key), + Self::WeightSet { key, weight } => cli::node::cmd_weight_set(&key, weight), + Self::Gap { description } => cli::node::cmd_gap(&description), + Self::Node(sub) => sub.run(), + Self::Journal(sub) => sub.run(), + Self::GraphCmd(sub) => sub.run(), + Self::Cursor(sub) => sub.run(), + Self::Agent(sub) => sub.run(), + Self::Admin(sub) => sub.run(), + } + } +} + +impl Run for NodeCmd { + fn run(self) -> Result<(), String> { + match self { + Self::Delete { key } => cli::node::cmd_node_delete(&key), + Self::Rename { old_key, new_key } => cli::node::cmd_node_rename(&old_key, &new_key), + Self::List { pattern } => cli::node::cmd_list_keys(pattern.as_deref()), + Self::Edges => cli::node::cmd_list_edges(), + Self::Dump => cli::node::cmd_dump_json(), + } + } +} + +impl Run for JournalCmd { + fn run(self) -> Result<(), String> { + match self { + Self::Write { text } => cli::journal::cmd_journal_write(&text), + Self::Tail { n, full, level } => cli::journal::cmd_journal_tail(n, full, level), + Self::Enrich { jsonl_path, entry_text, grep_line } + => cli::agent::cmd_journal_enrich(&jsonl_path, &entry_text, grep_line), + } + } +} + +impl Run for GraphCmd { + fn run(self) -> Result<(), String> { + match self { + Self::Link { key } => cli::graph::cmd_link(&key), + Self::LinkAdd { source, target, reason } + => cli::graph::cmd_link_add(&source, &target, &reason), + Self::LinkSet { source, target, strength } + => cli::graph::cmd_link_set(&source, &target, strength), + Self::LinkImpact { source, target } => cli::graph::cmd_link_impact(&source, &target), + Self::LinkAudit { apply } => cli::graph::cmd_link_audit(apply), + Self::LinkOrphans { min_degree, links_per, sim_threshold } + => cli::graph::cmd_link_orphans(min_degree, links_per, sim_threshold), + Self::TriangleClose { min_degree, sim_threshold, max_per_hub } + => cli::graph::cmd_triangle_close(min_degree, sim_threshold, max_per_hub), + Self::CapDegree { max_degree } => cli::graph::cmd_cap_degree(max_degree), + Self::NormalizeStrengths { apply } => cli::graph::cmd_normalize_strengths(apply), + Self::Differentiate { key, apply } => cli::graph::cmd_differentiate(key.as_deref(), apply), + Self::Trace { key } => cli::graph::cmd_trace(&key), + Self::Interference { threshold } => cli::graph::cmd_interference(threshold), + Self::Communities { top_n, min_size } => cli::graph::cmd_communities(top_n, min_size), + Self::Overview => cli::graph::cmd_graph(), + Self::Spectral { k } => cli::graph::cmd_spectral(k), + Self::SpectralSave { k } => cli::graph::cmd_spectral_save(k), + Self::SpectralNeighbors { key, n } => cli::graph::cmd_spectral_neighbors(&key, n), + Self::SpectralPositions { n } => cli::graph::cmd_spectral_positions(n), + Self::SpectralSuggest { n } => cli::graph::cmd_spectral_suggest(n), + Self::Organize { term, threshold, key_only, anchor } + => cli::graph::cmd_organize(&term, threshold, key_only, anchor), + } + } +} + +impl Run for CursorCmd { + fn run(self) -> Result<(), String> { + match self { + Self::Show => { + let store = store::Store::load()?; + cursor::show(&store) + } + Self::Set { key } => { + if key.is_empty() { return Err("cursor set requires a key".into()); } + let key = key.join(" "); + let store = store::Store::load()?; + let bare = store::strip_md_suffix(&key); + if !store.nodes.contains_key(&bare) { + return Err(format!("Node not found: {}", bare)); + } + cursor::set(&bare)?; + cursor::show(&store) + } + Self::Forward => { let s = store::Store::load()?; cursor::move_temporal(&s, true) } + Self::Back => { let s = store::Store::load()?; cursor::move_temporal(&s, false) } + Self::Up => { let s = store::Store::load()?; cursor::move_up(&s) } + Self::Down => { let s = store::Store::load()?; cursor::move_down(&s) } + Self::Clear => cursor::clear(), + } + } +} + +impl Run for DaemonCmd { + fn run(self) -> Result<(), String> { + match self { + Self::Start => daemon::run_daemon(), + Self::Status => daemon::show_status(), + Self::Log { job, task, lines } => { + if let Some(ref task_name) = task { + daemon::show_task_log(task_name, lines) + } else { + daemon::show_log(job.as_deref(), lines) + } + } + Self::Install => daemon::install_service(), + Self::Consolidate => daemon::rpc_consolidate(), + Self::Run { agent, count } => daemon::rpc_run_agent(&agent, count), + Self::Tui => tui::run_tui(), + Self::ReloadConfig => { + match daemon::send_rpc_pub("reload-config") { + Some(resp) => { eprintln!("{}", resp.trim()); Ok(()) } + None => Err("daemon not running".into()), + } + } + } + } +} + +impl Run for AgentCmd { + fn run(self) -> Result<(), String> { + match self { + Self::Daemon(sub) => sub.run(), + Self::KnowledgeLoop { max_cycles, batch_size, window, max_depth } + => cli::agent::cmd_knowledge_loop(max_cycles, batch_size, window, max_depth), + Self::ConsolidateBatch { count, auto, agent } + => cli::agent::cmd_consolidate_batch(count, auto, agent), + Self::ConsolidateSession => cli::agent::cmd_consolidate_session(), + Self::ConsolidateFull => cli::agent::cmd_consolidate_full(), + Self::ApplyAgent { all } => cmd_apply_agent(all), + Self::ApplyConsolidation { apply, report } + => cli::agent::cmd_apply_consolidation(apply, report.as_deref()), + Self::Digest { level } => cmd_digest(level), + Self::DigestLinks { apply } => cli::agent::cmd_digest_links(apply), + Self::ExperienceMine { .. } + => Err("experience-mine has been removed — use the observation agent instead.".into()), + Self::FactMine { path, batch, dry_run, output, min_messages } + => cli::agent::cmd_fact_mine(&path, batch, dry_run, output.as_deref(), min_messages), + Self::FactMineStore { path } => cli::agent::cmd_fact_mine_store(&path), + Self::Run { agent, count, target, query, dry_run, local, state_dir } + => cli::agent::cmd_run_agent(&agent, count, &target, query.as_deref(), dry_run, local, state_dir.as_deref()), + Self::ReplayQueue { count } => cli::agent::cmd_replay_queue(count), + Self::Evaluate { matchups, model, dry_run } + => cli::agent::cmd_evaluate_agents(matchups, &model, dry_run), + } + } +} + +impl Run for AdminCmd { + fn run(self) -> Result<(), String> { + match self { + Self::Init => cli::admin::cmd_init(), + Self::Health => cli::admin::cmd_health(), + Self::Fsck => cli::admin::cmd_fsck(), + Self::Dedup { apply } => cli::admin::cmd_dedup(apply), + Self::BulkRename { from, to, apply } => cli::admin::cmd_bulk_rename(&from, &to, apply), + Self::DailyCheck => cli::admin::cmd_daily_check(), + Self::Import { files } => cli::admin::cmd_import(&files), + Self::Export { files, all } => cli::admin::cmd_export(&files, all), + Self::LoadContext { stats } => cli::misc::cmd_load_context(stats), + Self::Log => cli::misc::cmd_log(), + Self::Params => cli::misc::cmd_params(), + Self::LookupBump { keys } => cli::node::cmd_lookup_bump(&keys), + Self::Lookups { date } => cli::node::cmd_lookups(date.as_deref()), + Self::MigrateTranscriptProgress => { + let mut store = store::Store::load()?; + let count = store.migrate_transcript_progress()?; + println!("Migrated {} transcript segment markers", count); + Ok(()) + } + } + } +} + fn main() { // Handle --help ourselves for expanded subcommand display let args: Vec = std::env::args().collect(); @@ -777,126 +974,7 @@ fn main() { let cli = Cli::parse(); - let result = match cli.command { - // Core - Command::Search { query, pipeline, expand, full, debug, fuzzy, content } - => cli::misc::cmd_search(&query, &pipeline, expand, full, debug, fuzzy, content), - Command::Render { key } => cli::node::cmd_render(&key), - Command::Write { key } => cli::node::cmd_write(&key), - Command::Edit { key } => cli::node::cmd_edit(&key), - Command::History { full, key } => cli::node::cmd_history(&key, full), - Command::Tail { n, full } => cli::journal::cmd_tail(n, full), - Command::Status => cli::misc::cmd_status(), - Command::Query { expr } => cli::misc::cmd_query(&expr), - Command::Used { key } => cli::node::cmd_used(&key), - Command::Wrong { key, context } => cli::node::cmd_wrong(&key, &context), - Command::NotRelevant { key } => cli::node::cmd_not_relevant(&key), - Command::NotUseful { key } => cli::node::cmd_not_useful(&key), - Command::WeightSet { key, weight } => cli::node::cmd_weight_set(&key, weight), - Command::Gap { description } => cli::node::cmd_gap(&description), - - // Node - Command::Node(sub) => match sub { - NodeCmd::Delete { key } => cli::node::cmd_node_delete(&key), - NodeCmd::Rename { old_key, new_key } => cli::node::cmd_node_rename(&old_key, &new_key), - NodeCmd::List { pattern } => cli::node::cmd_list_keys(pattern.as_deref()), - NodeCmd::Edges => cli::node::cmd_list_edges(), - NodeCmd::Dump => cli::node::cmd_dump_json(), - }, - - // Journal - Command::Journal(sub) => match sub { - JournalCmd::Write { text } => cli::journal::cmd_journal_write(&text), - JournalCmd::Tail { n, full, level } => cli::journal::cmd_journal_tail(n, full, level), - JournalCmd::Enrich { jsonl_path, entry_text, grep_line } - => cli::agent::cmd_journal_enrich(&jsonl_path, &entry_text, grep_line), - }, - - // Graph - Command::GraphCmd(sub) => match sub { - GraphCmd::Link { key } => cli::graph::cmd_link(&key), - GraphCmd::LinkAdd { source, target, reason } - => cli::graph::cmd_link_add(&source, &target, &reason), - GraphCmd::LinkSet { source, target, strength } - => cli::graph::cmd_link_set(&source, &target, strength), - GraphCmd::LinkImpact { source, target } - => cli::graph::cmd_link_impact(&source, &target), - GraphCmd::LinkAudit { apply } => cli::graph::cmd_link_audit(apply), - GraphCmd::LinkOrphans { min_degree, links_per, sim_threshold } - => cli::graph::cmd_link_orphans(min_degree, links_per, sim_threshold), - GraphCmd::TriangleClose { min_degree, sim_threshold, max_per_hub } - => cli::graph::cmd_triangle_close(min_degree, sim_threshold, max_per_hub), - GraphCmd::CapDegree { max_degree } => cli::graph::cmd_cap_degree(max_degree), - GraphCmd::NormalizeStrengths { apply } => cli::graph::cmd_normalize_strengths(apply), - GraphCmd::Differentiate { key, apply } - => cli::graph::cmd_differentiate(key.as_deref(), apply), - GraphCmd::Trace { key } => cli::graph::cmd_trace(&key), - GraphCmd::Interference { threshold } => cli::graph::cmd_interference(threshold), - GraphCmd::Communities { top_n, min_size } => cli::graph::cmd_communities(top_n, min_size), - GraphCmd::Overview => cli::graph::cmd_graph(), - GraphCmd::Spectral { k } => cli::graph::cmd_spectral(k), - GraphCmd::SpectralSave { k } => cli::graph::cmd_spectral_save(k), - GraphCmd::SpectralNeighbors { key, n } - => cli::graph::cmd_spectral_neighbors(&key, n), - GraphCmd::SpectralPositions { n } => cli::graph::cmd_spectral_positions(n), - GraphCmd::SpectralSuggest { n } => cli::graph::cmd_spectral_suggest(n), - GraphCmd::Organize { term, threshold, key_only, anchor } - => cli::graph::cmd_organize(&term, threshold, key_only, anchor), - }, - - // Cursor - Command::Cursor(sub) => cmd_cursor(sub), - - // Agent - Command::Agent(sub) => match sub { - AgentCmd::Daemon(sub) => cmd_daemon(sub), - AgentCmd::KnowledgeLoop { max_cycles, batch_size, window, max_depth } - => cli::agent::cmd_knowledge_loop(max_cycles, batch_size, window, max_depth), - AgentCmd::ConsolidateBatch { count, auto, agent } - => cli::agent::cmd_consolidate_batch(count, auto, agent), - AgentCmd::ConsolidateSession => cli::agent::cmd_consolidate_session(), - AgentCmd::ConsolidateFull => cli::agent::cmd_consolidate_full(), - AgentCmd::ApplyAgent { all } => cmd_apply_agent(all), - AgentCmd::ApplyConsolidation { apply, report } - => cli::agent::cmd_apply_consolidation(apply, report.as_deref()), - AgentCmd::Digest { level } => cmd_digest(level), - AgentCmd::DigestLinks { apply } => cli::agent::cmd_digest_links(apply), - AgentCmd::ExperienceMine { jsonl_path } => cmd_experience_mine(jsonl_path), - AgentCmd::FactMine { path, batch, dry_run, output, min_messages } - => cli::agent::cmd_fact_mine(&path, batch, dry_run, output.as_deref(), min_messages), - AgentCmd::FactMineStore { path } => cli::agent::cmd_fact_mine_store(&path), - AgentCmd::Run { agent, count, target, query, dry_run, local, state_dir } - => cli::agent::cmd_run_agent(&agent, count, &target, query.as_deref(), dry_run, local, state_dir.as_deref()), - AgentCmd::ReplayQueue { count } => cli::agent::cmd_replay_queue(count), - AgentCmd::Evaluate { matchups, model, dry_run } - => cli::agent::cmd_evaluate_agents(matchups, &model, dry_run), - }, - - // Admin - Command::Admin(sub) => match sub { - AdminCmd::Init => cli::admin::cmd_init(), - AdminCmd::Health => cli::admin::cmd_health(), - AdminCmd::Fsck => cli::admin::cmd_fsck(), - AdminCmd::Dedup { apply } => cli::admin::cmd_dedup(apply), - AdminCmd::BulkRename { from, to, apply } => cli::admin::cmd_bulk_rename(&from, &to, apply), - AdminCmd::DailyCheck => cli::admin::cmd_daily_check(), - AdminCmd::Import { files } => cli::admin::cmd_import(&files), - AdminCmd::Export { files, all } => cli::admin::cmd_export(&files, all), - AdminCmd::LoadContext { stats } => cli::misc::cmd_load_context(stats), - AdminCmd::Log => cli::misc::cmd_log(), - AdminCmd::Params => cli::misc::cmd_params(), - AdminCmd::LookupBump { keys } => cli::node::cmd_lookup_bump(&keys), - AdminCmd::Lookups { date } => cli::node::cmd_lookups(date.as_deref()), - AdminCmd::MigrateTranscriptProgress => (|| -> Result<(), String> { - let mut store = store::Store::load()?; - let count = store.migrate_transcript_progress()?; - println!("Migrated {} transcript segment markers", count); - Ok(()) - })() - }, - }; - - if let Err(e) = result { + if let Err(e) = cli.command.run() { eprintln!("Error: {}", e); process::exit(1); } @@ -1065,70 +1143,3 @@ fn cmd_digest(level: DigestLevel) -> Result<(), String> { } } -fn cmd_experience_mine(_jsonl_path: Option) -> Result<(), String> { - Err("experience-mine has been removed — use the observation agent instead.".into()) -} - -fn cmd_daemon(sub: DaemonCmd) -> Result<(), String> { - match sub { - DaemonCmd::Start => daemon::run_daemon(), - DaemonCmd::Status => daemon::show_status(), - DaemonCmd::Log { job, task, lines } => { - if let Some(ref task_name) = task { - daemon::show_task_log(task_name, lines) - } else { - daemon::show_log(job.as_deref(), lines) - } - } - DaemonCmd::Install => daemon::install_service(), - DaemonCmd::Consolidate => daemon::rpc_consolidate(), - DaemonCmd::Run { agent, count } => daemon::rpc_run_agent(&agent, count), - DaemonCmd::Tui => tui::run_tui(), - DaemonCmd::ReloadConfig => { - match daemon::send_rpc_pub("reload-config") { - Some(resp) => { eprintln!("{}", resp.trim()); Ok(()) } - None => Err("daemon not running".into()), - } - } - } -} - -fn cmd_cursor(sub: CursorCmd) -> Result<(), String> { - match sub { - CursorCmd::Show => { - let store = crate::store::Store::load()?; - cursor::show(&store) - } - CursorCmd::Set { key } => { - if key.is_empty() { - return Err("cursor set requires a key".into()); - } - let key = key.join(" "); - let store = crate::store::Store::load()?; - let bare = crate::store::strip_md_suffix(&key); - if !store.nodes.contains_key(&bare) { - return Err(format!("Node not found: {}", bare)); - } - cursor::set(&bare)?; - cursor::show(&store) - } - CursorCmd::Forward => { - let store = crate::store::Store::load()?; - cursor::move_temporal(&store, true) - } - CursorCmd::Back => { - let store = crate::store::Store::load()?; - cursor::move_temporal(&store, false) - } - CursorCmd::Up => { - let store = crate::store::Store::load()?; - cursor::move_up(&store) - } - CursorCmd::Down => { - let store = crate::store::Store::load()?; - cursor::move_down(&store) - } - CursorCmd::Clear => cursor::clear(), - } -} -