From b8db8754beb87b58ec81f832339f389d8b3635d2 Mon Sep 17 00:00:00 2001 From: Kent Overstreet Date: Mon, 13 Apr 2026 18:05:04 -0400 Subject: [PATCH] Convert store and CLI to anyhow::Result for cleaner error handling Replace Result<_, String> with anyhow::Result throughout: - hippocampus/store module (persist, ops, types, view, mod) - CLI modules (admin, agent, graph, journal, node) - Run trait in main.rs Use .context() and .with_context() instead of .map_err(|e| format!(...)) patterns. Add bail!() for early error returns. Add access_local() helper in hippocampus/mod.rs that returns Result>> for direct local store access. Fix store access patterns to properly lock Arc> before accessing fields in mind/unconscious.rs, mind/mod.rs, subconscious/learn.rs, and hippocampus/memory.rs. Co-Authored-By: Proof of Concept --- src/bin/merge-logs.rs | 30 +++---- src/cli/admin.rs | 65 +++++++------- src/cli/agent.rs | 19 ++-- src/cli/graph.rs | 46 +++++----- src/cli/journal.rs | 23 +++-- src/cli/node.rs | 76 +++++++--------- src/hippocampus/memory.rs | 3 +- src/hippocampus/mod.rs | 29 +++--- src/hippocampus/store/mod.rs | 23 ++--- src/hippocampus/store/ops.rs | 39 ++++---- src/hippocampus/store/persist.rs | 149 ++++++++++++++++--------------- src/hippocampus/store/types.rs | 15 ++-- src/hippocampus/store/view.rs | 3 +- src/main.rs | 22 ++--- src/mind/mod.rs | 5 +- src/mind/unconscious.rs | 9 +- src/subconscious/learn.rs | 21 +++-- 17 files changed, 282 insertions(+), 295 deletions(-) diff --git a/src/bin/merge-logs.rs b/src/bin/merge-logs.rs index d883fa2..bc02c2b 100644 --- a/src/bin/merge-logs.rs +++ b/src/bin/merge-logs.rs @@ -22,6 +22,7 @@ use std::fs; use std::io::{BufReader, BufWriter}; use std::path::Path; +use anyhow::{bail, Context, Result}; use capnp::message; use capnp::serialize; @@ -29,17 +30,17 @@ use consciousness::memory_capnp; use consciousness::store::Node; /// Read all node entries from a capnp log file, preserving order. -fn read_all_entries(path: &Path) -> Result, String> { +fn read_all_entries(path: &Path) -> Result> { let file = fs::File::open(path) - .map_err(|e| format!("open {}: {}", path.display(), e))?; + .with_context(|| format!("open {}", path.display()))?; let mut reader = BufReader::new(file); let mut entries = Vec::new(); while let Ok(msg) = serialize::read_message(&mut reader, message::ReaderOptions::new()) { let log = msg.get_root::() - .map_err(|e| format!("read log from {}: {}", path.display(), e))?; + .with_context(|| format!("read log from {}", path.display()))?; for node_reader in log.get_nodes() - .map_err(|e| format!("get nodes from {}: {}", path.display(), e))? { + .with_context(|| format!("get nodes from {}", path.display()))? { let node = Node::from_capnp_migrate(node_reader)?; entries.push(node); } @@ -49,9 +50,9 @@ fn read_all_entries(path: &Path) -> Result, String> { } /// Write node entries to a new capnp log file in chunks. -fn write_entries(path: &Path, entries: &[Node]) -> Result<(), String> { +fn write_entries(path: &Path, entries: &[Node]) -> Result<()> { let file = fs::File::create(path) - .map_err(|e| format!("create {}: {}", path.display(), e))?; + .with_context(|| format!("create {}", path.display()))?; let mut writer = BufWriter::new(file); for chunk in entries.chunks(100) { @@ -64,13 +65,13 @@ fn write_entries(path: &Path, entries: &[Node]) -> Result<(), String> { } } serialize::write_message(&mut writer, &msg) - .map_err(|e| format!("write: {}", e))?; + .context("write message")?; } Ok(()) } -fn main() -> Result<(), String> { +fn main() -> Result<()> { let args: Vec = std::env::args().collect(); if args.len() != 4 { eprintln!("Usage: merge-logs "); @@ -87,19 +88,18 @@ fn main() -> Result<(), String> { // Validate inputs exist if !old_path.exists() { - return Err(format!("old log not found: {}", old_path.display())); + bail!("old log not found: {}", old_path.display()); } if !current_path.exists() { - return Err(format!("current log not found: {}", current_path.display())); + bail!("current log not found: {}", current_path.display()); } // Create output directory (must not already contain nodes.capnp) fs::create_dir_all(output_dir) - .map_err(|e| format!("create output dir: {}", e))?; + .context("create output dir")?; let output_path = output_dir.join("nodes.capnp"); if output_path.exists() { - return Err(format!("output already exists: {} — refusing to overwrite", - output_path.display())); + bail!("output already exists: {} — refusing to overwrite", output_path.display()); } eprintln!("Reading old log: {} ...", old_path.display()); @@ -190,8 +190,8 @@ fn main() -> Result<(), String> { eprintln!(" Replay produces {} live nodes", final_nodes.len()); if verify_entries.len() != merged.len() { - return Err(format!("Verification failed: wrote {} but read back {}", - merged.len(), verify_entries.len())); + bail!("Verification failed: wrote {} but read back {}", + merged.len(), verify_entries.len()); } eprintln!(); diff --git a/src/cli/admin.rs b/src/cli/admin.rs index b06099b..2f5cb3a 100644 --- a/src/cli/admin.rs +++ b/src/cli/admin.rs @@ -1,24 +1,23 @@ // cli/admin.rs — admin subcommand handlers +use anyhow::{Context, Result}; use crate::hippocampus as memory; -use crate::store; +use crate::hippocampus::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<()> { let path = data_dir.join(name); if !path.exists() { - std::fs::write(&path, content) - .map_err(|e| format!("write {}: {}", name, e))?; + std::fs::write(&path, content)?; println!("Created {}", path.display()); } Ok(()) } -pub fn cmd_init() -> Result<(), String> { +pub async fn cmd_init() -> Result<()> { let cfg = crate::config::get(); // Ensure data directory exists - std::fs::create_dir_all(&cfg.data_dir) - .map_err(|e| format!("create data_dir: {}", e))?; + std::fs::create_dir_all(&cfg.data_dir)?; // Install filesystem files (not store nodes) install_default_file(&cfg.data_dir, "instructions.md", @@ -27,17 +26,17 @@ pub fn cmd_init() -> Result<(), String> { include_str!("../../defaults/on-consciousness.md"))?; // Initialize store and seed default identity node if empty - let mut store = store::Store::load()?; - let count = store.init_from_markdown()?; + let arc = memory::access_local()?; + let mut store = arc.lock().await; + let count = store.init_from_markdown().map_err(|e| anyhow::anyhow!("{}", e))?; for key in &cfg.core_nodes { if !store.nodes.contains_key(key) && key == "identity" { let default = include_str!("../../defaults/identity.md"); - store.upsert(key, default) - .map_err(|e| format!("seed {}: {}", key, e))?; + store.upsert(key, default).map_err(|e| anyhow::anyhow!("{}", e))?; println!("Seeded {} in store", key); } } - store.save()?; + store.save().map_err(|e| anyhow::anyhow!("{}", e))?; println!("Indexed {} memory units", count); // Create config if none exists @@ -49,11 +48,9 @@ pub fn cmd_init() -> Result<(), String> { }); if !config_path.exists() { let config_dir = config_path.parent().unwrap(); - std::fs::create_dir_all(config_dir) - .map_err(|e| format!("create config dir: {}", e))?; + std::fs::create_dir_all(config_dir)?; let example = include_str!("../../config.example.jsonl"); - std::fs::write(&config_path, example) - .map_err(|e| format!("write config: {}", e))?; + std::fs::write(&config_path, example)?; println!("Created config at {} — edit with your name and context groups", config_path.display()); } @@ -62,7 +59,7 @@ pub fn cmd_init() -> Result<(), String> { Ok(()) } -pub fn cmd_fsck() -> Result<(), String> { +pub fn cmd_fsck() -> Result<()> { let mut store = store::Store::load()?; // Check cache vs log consistency @@ -96,7 +93,7 @@ pub fn cmd_fsck() -> Result<(), String> { if cache_issues > 0 { eprintln!("{} cache inconsistencies found — rebuilding from logs", cache_issues); store = log_store; - store.save().map_err(|e| format!("rebuild save: {}", e))?; + store.save().context("rebuild save")?; } // Check node-key consistency @@ -153,10 +150,11 @@ pub fn cmd_fsck() -> Result<(), String> { Ok(()) } -pub fn cmd_dedup(apply: bool) -> Result<(), String> { +pub async fn cmd_dedup(apply: bool) -> Result<()> { use std::collections::{HashMap, HashSet}; - let mut store = store::Store::load()?; + let arc = memory::access_local()?; + let mut store = arc.lock().await; let duplicates = store.find_duplicates()?; if duplicates.is_empty() { @@ -329,30 +327,31 @@ pub fn cmd_dedup(apply: bool) -> Result<(), String> { Ok(()) } -pub async fn cmd_health() -> Result<(), String> { +pub async fn cmd_health() -> Result<()> { let result = memory::graph_health(None).await - .map_err(|e| e.to_string())?; + ?; print!("{}", result); Ok(()) } -pub async fn cmd_topology() -> Result<(), String> { +pub async fn cmd_topology() -> Result<()> { let result = memory::graph_topology(None).await - .map_err(|e| e.to_string())?; + ?; print!("{}", result); Ok(()) } -pub fn cmd_daily_check() -> Result<(), String> { - let store = store::Store::load()?; +pub async fn cmd_daily_check() -> Result<()> { + let arc = memory::access_local()?; + let store = arc.lock().await; let report = crate::neuro::daily_check(&store); print!("{}", report); Ok(()) } -pub fn cmd_import(files: &[String]) -> Result<(), String> { +pub fn cmd_import(files: &[String]) -> Result<()> { if files.is_empty() { - return Err("import requires at least one file path".into()); + anyhow::bail!("import requires at least one file path"); } let mut store = store::Store::load()?; @@ -383,7 +382,7 @@ pub fn cmd_import(files: &[String]) -> Result<(), String> { Ok(()) } -pub fn cmd_export(files: &[String], export_all: bool) -> Result<(), String> { +pub fn cmd_export(files: &[String], export_all: bool) -> Result<()> { let store = store::Store::load()?; let targets: Vec = if export_all { @@ -394,7 +393,7 @@ pub fn cmd_export(files: &[String], export_all: bool) -> Result<(), String> { files.sort(); files } else if files.is_empty() { - return Err("export requires file keys or --all".into()); + anyhow::bail!("export requires file keys or --all"); } else { files.iter().map(|a| { a.strip_suffix(".md").unwrap_or(a).to_string() @@ -408,7 +407,7 @@ pub fn cmd_export(files: &[String], export_all: bool) -> Result<(), String> { Some(content) => { let out_path = mem_dir.join(format!("{}.md", file_key)); std::fs::write(&out_path, &content) - .map_err(|e| format!("write {}: {}", out_path.display(), e))?; + .with_context(|| format!("write {}", out_path.display()))?; let section_count = content.matches("