diff --git a/poc-memory/src/agents/daemon.rs b/poc-memory/src/agents/daemon.rs index d824ae2..f2da04a 100644 --- a/poc-memory/src/agents/daemon.rs +++ b/poc-memory/src/agents/daemon.rs @@ -1690,7 +1690,7 @@ After=default.target [Service] Type=simple -ExecStart={exe} daemon +ExecStart={exe} agent daemon Restart=on-failure RestartSec=30 Environment=HOME={home} @@ -1748,7 +1748,7 @@ After=default.target [Service] Type=simple -ExecStart={exe} daemon +ExecStart={exe} agent daemon Restart=on-failure RestartSec=10 Environment=HOME={home} diff --git a/poc-memory/src/bin/memory-search.rs b/poc-memory/src/bin/memory-search.rs index 74c2eb2..e90f46d 100644 --- a/poc-memory/src/bin/memory-search.rs +++ b/poc-memory/src/bin/memory-search.rs @@ -126,7 +126,7 @@ fn main() { if debug { println!("[memory-search] loading full context"); } // Load full memory context, chunk it, print first chunk, save rest - if let Ok(output) = Command::new("poc-memory").args(["load-context"]).output() { + if let Ok(output) = Command::new("poc-memory").args(["admin", "load-context"]).output() { if output.status.success() { let ctx = String::from_utf8_lossy(&output.stdout).to_string(); if !ctx.trim().is_empty() { diff --git a/poc-memory/src/bin/poc-hook.rs b/poc-memory/src/bin/poc-hook.rs index 4d1ad77..1e5389c 100644 --- a/poc-memory/src/bin/poc-hook.rs +++ b/poc-memory/src/bin/poc-hook.rs @@ -115,7 +115,7 @@ fn check_context(transcript: &PathBuf, rate_limit: bool) { "\ CONTEXT WARNING: Compaction approaching ({usage} tokens). Write a journal entry NOW. -Use `poc-memory journal-write \"entry text\"` to save a dated entry covering: +Use `poc-memory journal write \"entry text\"` to save a dated entry covering: - What you're working on and current state (done / in progress / blocked) - Key things learned this session (patterns, debugging insights) - Anything half-finished that needs pickup diff --git a/poc-memory/src/lib.rs b/poc-memory/src/lib.rs index 037a998..411a75a 100644 --- a/poc-memory/src/lib.rs +++ b/poc-memory/src/lib.rs @@ -13,7 +13,6 @@ pub mod similarity; pub mod spectral; pub mod lookups; pub mod query; -pub mod migrate; pub mod transcript; pub mod neuro; pub mod counters; diff --git a/poc-memory/src/main.rs b/poc-memory/src/main.rs index ccfd83f..7fe2e30 100644 --- a/poc-memory/src/main.rs +++ b/poc-memory/src/main.rs @@ -58,6 +58,8 @@ struct Cli { #[derive(Subcommand)] enum Command { + // ── Core (daily use) ────────────────────────────────────────────── + /// Search memory (AND logic across terms) /// /// Pipeline: -p spread -p spectral,k=20 @@ -84,24 +86,40 @@ enum Command { #[arg(long)] content: bool, }, - /// Scan markdown files, index all memory units - Init, - /// Migrate from old weights.json system - Migrate, - /// Report graph metrics (CC, communities, small-world) - Health, - /// Run consistency checks and repair - Fsck, - /// Find and merge duplicate nodes (same key, multiple UUIDs) - Dedup { - /// Apply the merge (default: dry run) + /// Output a node's content to stdout + Render { + /// Node key + key: Vec, + }, + /// Upsert node content from stdin + Write { + /// Node key + key: Vec, + }, + /// Show all stored versions of a node + History { + /// Show full content for every version #[arg(long)] - apply: bool, + full: bool, + /// Node key + key: Vec, + }, + /// Show most recent writes to the node log + Tail { + /// Number of entries (default: 20) + #[arg(default_value_t = 20)] + n: usize, + /// Show full content + #[arg(long)] + full: bool, }, /// Summary of memory state Status, - /// Show graph structure overview - Graph, + /// Query the memory graph + Query { + /// Query expression (e.g. "degree > 15 | sort degree | limit 10") + expr: Vec, + }, /// Mark a memory as useful (boosts weight) Used { /// Node key @@ -119,61 +137,103 @@ enum Command { /// Gap description description: Vec, }, - /// Cap node degree by pruning weak auto edges - #[command(name = "cap-degree")] - CapDegree { - /// Maximum degree (default: 50) - #[arg(default_value_t = 50)] - max_degree: usize, + + // ── Node operations ─────────────────────────────────────────────── + + /// Node operations (delete, rename, list) + #[command(subcommand)] + Node(NodeCmd), + + // ── Journal ─────────────────────────────────────────────────────── + + /// Journal operations (write, tail, enrich) + #[command(subcommand)] + Journal(JournalCmd), + + // ── Graph ───────────────────────────────────────────────────────── + + /// Graph operations (link, audit, spectral) + #[command(subcommand, name = "graph")] + GraphCmd(GraphCmd), + + // ── Agents ──────────────────────────────────────────────────────── + + /// Agent and daemon operations + #[command(subcommand)] + Agent(AgentCmd), + + // ── Admin ───────────────────────────────────────────────────────── + + /// Admin operations (fsck, health, import, export) + #[command(subcommand)] + Admin(AdminCmd), +} + +#[derive(Subcommand)] +enum NodeCmd { + /// Soft-delete a node + Delete { + /// Node key + key: Vec, }, - /// Link orphan nodes to similar neighbors - #[command(name = "link-orphans")] - LinkOrphans { - /// Minimum degree to consider orphan (default: 2) - #[arg(default_value_t = 2)] - min_degree: usize, - /// Links per orphan (default: 3) - #[arg(default_value_t = 3)] - links_per: usize, - /// Similarity threshold (default: 0.15) - #[arg(default_value_t = 0.15)] - sim_threshold: f32, + /// Rename a node key + Rename { + /// Old key + old_key: String, + /// New key + new_key: String, }, - /// Run agent consolidation on priority nodes - #[command(name = "consolidate-batch")] - ConsolidateBatch { - /// Number of nodes to consolidate - #[arg(long, default_value_t = 5)] - count: usize, - /// Generate replay agent prompt automatically + /// List all node keys (one per line, optional glob) + #[command(name = "list")] + List { + /// Glob pattern to filter keys + pattern: Option, + }, + /// List all edges (tsv: source target strength type) + Edges, + /// Dump entire store as JSON + #[command(name = "dump")] + Dump, +} + +#[derive(Subcommand)] +enum JournalCmd { + /// Write a journal entry to the store + Write { + /// Entry text + text: Vec, + }, + /// Show recent journal/digest entries + Tail { + /// Number of entries to show (default: 20) + #[arg(default_value_t = 20)] + n: usize, + /// Show full content #[arg(long)] - auto: bool, - /// Generate prompt for a specific agent (replay, linker, separator, transfer, health) - #[arg(long)] - agent: Option, + full: bool, + /// Digest level: 0/journal, 1/daily, 2/weekly, 3/monthly + #[arg(long, default_value_t = 0)] + level: u8, }, - /// Show recent retrieval log - Log, - /// Show current parameters - Params, + /// Enrich journal entry with conversation links + Enrich { + /// Path to JSONL transcript + jsonl_path: String, + /// Journal entry text to enrich + entry_text: String, + /// Grep line number for source location + #[arg(default_value_t = 0)] + grep_line: usize, + }, +} + +#[derive(Subcommand)] +enum GraphCmd { /// Show neighbors of a node Link { /// Node key key: Vec, }, - /// Show spaced repetition replay queue - #[command(name = "replay-queue")] - ReplayQueue { - /// Number of items to show - #[arg(long, default_value_t = 10)] - count: usize, - }, - /// Detect potentially confusable memory pairs - Interference { - /// Similarity threshold (default: 0.4) - #[arg(long, default_value_t = 0.4)] - threshold: f32, - }, /// Add a link between two nodes #[command(name = "link-add")] LinkAdd { @@ -192,12 +252,26 @@ enum Command { /// Target node key target: String, }, - /// Analyze metrics, plan agent allocation - #[command(name = "consolidate-session")] - ConsolidateSession, - /// Autonomous: plan → agents → apply → digests → links - #[command(name = "consolidate-full")] - ConsolidateFull, + /// Walk every link, send to Sonnet for quality review + #[command(name = "link-audit")] + LinkAudit { + /// Apply changes (default: dry run) + #[arg(long)] + apply: bool, + }, + /// Link orphan nodes to similar neighbors + #[command(name = "link-orphans")] + LinkOrphans { + /// Minimum degree to consider orphan (default: 2) + #[arg(default_value_t = 2)] + min_degree: usize, + /// Links per orphan (default: 3) + #[arg(default_value_t = 3)] + links_per: usize, + /// Similarity threshold (default: 0.15) + #[arg(default_value_t = 0.15)] + sim_threshold: f32, + }, /// Close triangles: link similar neighbors of hubs #[command(name = "triangle-close")] TriangleClose { @@ -211,55 +285,12 @@ enum Command { #[arg(default_value_t = 10)] max_per_hub: usize, }, - /// Brief metrics check (for cron/notifications) - #[command(name = "daily-check")] - DailyCheck, - /// Import pending agent results into the graph - #[command(name = "apply-agent")] - ApplyAgent { - /// Process all files without moving to done/ - #[arg(long)] - all: bool, - }, - /// Generate episodic digests (daily, weekly, monthly, auto) - Digest { - /// Digest type: daily, weekly, monthly, auto - #[command(subcommand)] - level: DigestLevel, - }, - /// Parse and apply links from digest nodes - #[command(name = "digest-links")] - DigestLinks { - /// Apply the links (default: dry run) - #[arg(long)] - apply: bool, - }, - /// Enrich journal entry with conversation links - #[command(name = "journal-enrich")] - JournalEnrich { - /// Path to JSONL transcript - jsonl_path: String, - /// Journal entry text to enrich - entry_text: String, - /// Grep line number for source location - #[arg(default_value_t = 0)] - grep_line: usize, - }, - /// Mine conversation for experiential moments to journal - #[command(name = "experience-mine")] - ExperienceMine { - /// Path to JSONL transcript (default: most recent) - jsonl_path: Option, - }, - /// Extract and apply actions from consolidation reports - #[command(name = "apply-consolidation")] - ApplyConsolidation { - /// Apply actions (default: dry run) - #[arg(long)] - apply: bool, - /// Read from specific report file - #[arg(long)] - report: Option, + /// Cap node degree by pruning weak auto edges + #[command(name = "cap-degree")] + CapDegree { + /// Maximum degree (default: 50) + #[arg(default_value_t = 50)] + max_degree: usize, }, /// Redistribute hub links to section-level children Differentiate { @@ -269,18 +300,19 @@ enum Command { #[arg(long)] apply: bool, }, - /// Walk every link, send to Sonnet for quality review - #[command(name = "link-audit")] - LinkAudit { - /// Apply changes (default: dry run) - #[arg(long)] - apply: bool, - }, /// Walk temporal links: semantic ↔ episodic ↔ conversation Trace { /// Node key key: Vec, }, + /// Detect potentially confusable memory pairs + Interference { + /// Similarity threshold (default: 0.4) + #[arg(long, default_value_t = 0.4)] + threshold: f32, + }, + /// Show graph structure overview + Overview, /// Spectral decomposition of the memory graph Spectral { /// Number of eigenvectors (default: 30) @@ -317,117 +349,10 @@ enum Command { #[arg(default_value_t = 20)] n: usize, }, - /// List all node keys (one per line, optional glob) - #[command(name = "list-keys")] - ListKeys { - /// Glob pattern to filter keys - pattern: Option, - }, - /// List all edges (tsv: source target strength type) - #[command(name = "list-edges")] - ListEdges, - /// Dump entire store as JSON - #[command(name = "dump-json")] - DumpJson, - /// Soft-delete a node - #[command(name = "node-delete")] - NodeDelete { - /// Node key - key: Vec, - }, - /// Rename a node key - #[command(name = "node-rename")] - NodeRename { - /// Old key - old_key: String, - /// New key - new_key: String, - }, - /// Populate created_at for nodes missing timestamps - #[command(name = "journal-ts-migrate")] - JournalTsMigrate, - /// Output session-start context from the store - #[command(name = "load-context")] - LoadContext { - /// Show word count statistics instead of content - #[arg(long)] - stats: bool, - }, - /// Output a node's content to stdout - Render { - /// Node key - key: Vec, - }, - /// Show all stored versions of a node - History { - /// Show full content for every version - #[arg(long)] - full: bool, - /// Node key - key: Vec, - }, - /// Show most recent writes to the node log - Tail { - /// Number of entries (default: 20) - #[arg(default_value_t = 20)] - n: usize, - /// Show full content - #[arg(long)] - full: bool, - }, - /// Upsert node content from stdin - Write { - /// Node key - key: Vec, - }, - /// Import markdown file(s) into the store - Import { - /// File paths - files: Vec, - }, - /// Export store nodes to markdown file(s) - Export { - /// File keys to export (or --all) - files: Vec, - /// Export all file-level nodes - #[arg(long)] - all: bool, - }, - /// Write a journal entry to the store - #[command(name = "journal-write")] - JournalWrite { - /// Entry text - text: Vec, - }, - /// Show recent journal/digest entries - #[command(name = "journal-tail")] - JournalTail { - /// Number of entries to show (default: 20) - #[arg(default_value_t = 20)] - n: usize, - /// Show full content - #[arg(long)] - full: bool, - /// Digest level: 0/journal, 1/daily, 2/weekly, 3/monthly - #[arg(long, default_value_t = 0)] - level: u8, - }, - /// Query the memory graph - Query { - /// Query expression (e.g. "degree > 15 | sort degree | limit 10") - expr: Vec, - }, - /// Bump daily lookup counter for keys - #[command(name = "lookup-bump")] - LookupBump { - /// Node keys - keys: Vec, - }, - /// Show daily lookup counts - Lookups { - /// Date (default: today) - date: Option, - }, +} + +#[derive(Subcommand)] +enum AgentCmd { /// Background job daemon Daemon { /// Subcommand: status, log, install @@ -451,6 +376,61 @@ enum Command { #[arg(long, default_value_t = 4)] max_depth: i32, }, + /// Run agent consolidation on priority nodes + #[command(name = "consolidate-batch")] + ConsolidateBatch { + /// Number of nodes to consolidate + #[arg(long, default_value_t = 5)] + count: usize, + /// Generate replay agent prompt automatically + #[arg(long)] + auto: bool, + /// Generate prompt for a specific agent (replay, linker, separator, transfer, health) + #[arg(long)] + agent: Option, + }, + /// Analyze metrics, plan agent allocation + #[command(name = "consolidate-session")] + ConsolidateSession, + /// Autonomous: plan → agents → apply → digests → links + #[command(name = "consolidate-full")] + ConsolidateFull, + /// Import pending agent results into the graph + #[command(name = "apply-agent")] + ApplyAgent { + /// Process all files without moving to done/ + #[arg(long)] + all: bool, + }, + /// Extract and apply actions from consolidation reports + #[command(name = "apply-consolidation")] + ApplyConsolidation { + /// Apply actions (default: dry run) + #[arg(long)] + apply: bool, + /// Read from specific report file + #[arg(long)] + report: Option, + }, + /// Generate episodic digests (daily, weekly, monthly, auto) + Digest { + /// Digest type: daily, weekly, monthly, auto + #[command(subcommand)] + level: DigestLevel, + }, + /// Parse and apply links from digest nodes + #[command(name = "digest-links")] + DigestLinks { + /// Apply the links (default: dry run) + #[arg(long)] + apply: bool, + }, + /// Mine conversation for experiential moments to journal + #[command(name = "experience-mine")] + ExperienceMine { + /// Path to JSONL transcript (default: most recent) + jsonl_path: Option, + }, /// Extract atomic facts from conversation transcripts #[command(name = "fact-mine")] FactMine { @@ -475,6 +455,67 @@ enum Command { /// Path to JSONL transcript path: String, }, + /// Show spaced repetition replay queue + #[command(name = "replay-queue")] + ReplayQueue { + /// Number of items to show + #[arg(long, default_value_t = 10)] + count: usize, + }, +} + +#[derive(Subcommand)] +enum AdminCmd { + /// Scan markdown files, index all memory units + Init, + /// Report graph metrics (CC, communities, small-world) + Health, + /// Run consistency checks and repair + Fsck, + /// Find and merge duplicate nodes (same key, multiple UUIDs) + Dedup { + /// Apply the merge (default: dry run) + #[arg(long)] + apply: bool, + }, + /// Brief metrics check (for cron/notifications) + #[command(name = "daily-check")] + DailyCheck, + /// Import markdown file(s) into the store + Import { + /// File paths + files: Vec, + }, + /// Export store nodes to markdown file(s) + Export { + /// File keys to export (or --all) + files: Vec, + /// Export all file-level nodes + #[arg(long)] + all: bool, + }, + /// Output session-start context from the store + #[command(name = "load-context")] + LoadContext { + /// Show word count statistics instead of content + #[arg(long)] + stats: bool, + }, + /// Show recent retrieval log + Log, + /// Show current parameters + Params, + /// Bump daily lookup counter for keys + #[command(name = "lookup-bump")] + LookupBump { + /// Node keys + keys: Vec, + }, + /// Show daily lookup counts + Lookups { + /// Date (default: today) + date: Option, + }, } #[derive(Subcommand)] @@ -502,105 +543,98 @@ fn main() { let cli = Cli::parse(); let result = match cli.command { + // Core Command::Search { query, pipeline, expand, full, debug, fuzzy, content } => cmd_search(&query, &pipeline, expand, full, debug, fuzzy, content), - Command::Init => cmd_init(), - Command::Migrate => cmd_migrate(), - Command::Health => cmd_health(), - Command::Fsck => cmd_fsck(), - Command::Dedup { apply } => cmd_dedup(apply), - Command::Status => cmd_status(), - Command::Graph => cmd_graph(), - Command::Used { key } => cmd_used(&key), - Command::Wrong { key, context } - => cmd_wrong(&key, &context), - Command::Gap { description } - => cmd_gap(&description), - Command::CapDegree { max_degree } - => cmd_cap_degree(max_degree), - Command::LinkOrphans { min_degree, links_per, sim_threshold } - => cmd_link_orphans(min_degree, links_per, sim_threshold), - Command::ConsolidateBatch { count, auto, agent } - => cmd_consolidate_batch(count, auto, agent), - Command::Log => cmd_log(), - Command::Params => cmd_params(), - Command::Link { key } => cmd_link(&key), - Command::ReplayQueue { count } - => cmd_replay_queue(count), - Command::Interference { threshold } - => cmd_interference(threshold), - Command::LinkAdd { source, target, reason } - => cmd_link_add(&source, &target, &reason), - Command::LinkImpact { source, target } - => cmd_link_impact(&source, &target), - Command::ConsolidateSession => cmd_consolidate_session(), - Command::ConsolidateFull => cmd_consolidate_full(), - Command::TriangleClose { min_degree, sim_threshold, max_per_hub } - => cmd_triangle_close(min_degree, sim_threshold, max_per_hub), - Command::DailyCheck => cmd_daily_check(), - Command::ApplyAgent { all } - => cmd_apply_agent(all), - Command::Digest { level } => cmd_digest(level), - Command::DigestLinks { apply } - => cmd_digest_links(apply), - Command::JournalEnrich { jsonl_path, entry_text, grep_line } - => cmd_journal_enrich(&jsonl_path, &entry_text, grep_line), - Command::ExperienceMine { jsonl_path } - => cmd_experience_mine(jsonl_path), - Command::ApplyConsolidation { apply, report } - => cmd_apply_consolidation(apply, report.as_deref()), - Command::Differentiate { key, apply } - => cmd_differentiate(key.as_deref(), apply), - Command::LinkAudit { apply } - => cmd_link_audit(apply), - Command::Trace { key } => cmd_trace(&key), - Command::Spectral { k } => cmd_spectral(k), - Command::SpectralSave { k } => cmd_spectral_save(k), - Command::SpectralNeighbors { key, n } - => cmd_spectral_neighbors(&key, n), - Command::SpectralPositions { n } - => cmd_spectral_positions(n), - Command::SpectralSuggest { n } - => cmd_spectral_suggest(n), - Command::ListKeys { pattern } - => cmd_list_keys(pattern.as_deref()), - Command::ListEdges => cmd_list_edges(), - Command::DumpJson => cmd_dump_json(), - Command::NodeDelete { key } - => cmd_node_delete(&key), - Command::NodeRename { old_key, new_key } - => cmd_node_rename(&old_key, &new_key), - Command::JournalTsMigrate => cmd_journal_ts_migrate(), - Command::LoadContext { stats } - => cmd_load_context(stats), Command::Render { key } => cmd_render(&key), - Command::History { full, key } - => cmd_history(&key, full), - Command::Tail { n, full } - => cmd_tail(n, full), Command::Write { key } => cmd_write(&key), - Command::Import { files } - => cmd_import(&files), - Command::Export { files, all } - => cmd_export(&files, all), - Command::JournalWrite { text } - => cmd_journal_write(&text), - Command::JournalTail { n, full, level } - => cmd_journal_tail(n, full, level), - Command::Query { expr } - => cmd_query(&expr), - Command::LookupBump { keys } - => cmd_lookup_bump(&keys), - Command::Lookups { date } - => cmd_lookups(date.as_deref()), - Command::Daemon { sub, args } - => cmd_daemon(sub.as_deref(), &args), - Command::KnowledgeLoop { max_cycles, batch_size, window, max_depth } - => cmd_knowledge_loop(max_cycles, batch_size, window, max_depth), - Command::FactMine { path, batch, dry_run, output, min_messages } - => cmd_fact_mine(&path, batch, dry_run, output.as_deref(), min_messages), - Command::FactMineStore { path } - => cmd_fact_mine_store(&path), + Command::History { full, key } => cmd_history(&key, full), + Command::Tail { n, full } => cmd_tail(n, full), + Command::Status => cmd_status(), + Command::Query { expr } => cmd_query(&expr), + Command::Used { key } => cmd_used(&key), + Command::Wrong { key, context } => cmd_wrong(&key, &context), + Command::Gap { description } => cmd_gap(&description), + + // Node + Command::Node(sub) => match sub { + NodeCmd::Delete { key } => cmd_node_delete(&key), + NodeCmd::Rename { old_key, new_key } => cmd_node_rename(&old_key, &new_key), + NodeCmd::List { pattern } => cmd_list_keys(pattern.as_deref()), + NodeCmd::Edges => cmd_list_edges(), + NodeCmd::Dump => cmd_dump_json(), + }, + + // Journal + Command::Journal(sub) => match sub { + JournalCmd::Write { text } => cmd_journal_write(&text), + JournalCmd::Tail { n, full, level } => cmd_journal_tail(n, full, level), + JournalCmd::Enrich { jsonl_path, entry_text, grep_line } + => cmd_journal_enrich(&jsonl_path, &entry_text, grep_line), + }, + + // Graph + Command::GraphCmd(sub) => match sub { + GraphCmd::Link { key } => cmd_link(&key), + GraphCmd::LinkAdd { source, target, reason } + => cmd_link_add(&source, &target, &reason), + GraphCmd::LinkImpact { source, target } + => cmd_link_impact(&source, &target), + GraphCmd::LinkAudit { apply } => cmd_link_audit(apply), + GraphCmd::LinkOrphans { min_degree, links_per, sim_threshold } + => cmd_link_orphans(min_degree, links_per, sim_threshold), + GraphCmd::TriangleClose { min_degree, sim_threshold, max_per_hub } + => cmd_triangle_close(min_degree, sim_threshold, max_per_hub), + GraphCmd::CapDegree { max_degree } => cmd_cap_degree(max_degree), + GraphCmd::Differentiate { key, apply } + => cmd_differentiate(key.as_deref(), apply), + GraphCmd::Trace { key } => cmd_trace(&key), + GraphCmd::Interference { threshold } => cmd_interference(threshold), + GraphCmd::Overview => cmd_graph(), + GraphCmd::Spectral { k } => cmd_spectral(k), + GraphCmd::SpectralSave { k } => cmd_spectral_save(k), + GraphCmd::SpectralNeighbors { key, n } + => cmd_spectral_neighbors(&key, n), + GraphCmd::SpectralPositions { n } => cmd_spectral_positions(n), + GraphCmd::SpectralSuggest { n } => cmd_spectral_suggest(n), + }, + + // Agent + Command::Agent(sub) => match sub { + AgentCmd::Daemon { sub, args } => cmd_daemon(sub.as_deref(), &args), + AgentCmd::KnowledgeLoop { max_cycles, batch_size, window, max_depth } + => cmd_knowledge_loop(max_cycles, batch_size, window, max_depth), + AgentCmd::ConsolidateBatch { count, auto, agent } + => cmd_consolidate_batch(count, auto, agent), + AgentCmd::ConsolidateSession => cmd_consolidate_session(), + AgentCmd::ConsolidateFull => cmd_consolidate_full(), + AgentCmd::ApplyAgent { all } => cmd_apply_agent(all), + AgentCmd::ApplyConsolidation { apply, report } + => cmd_apply_consolidation(apply, report.as_deref()), + AgentCmd::Digest { level } => cmd_digest(level), + AgentCmd::DigestLinks { apply } => cmd_digest_links(apply), + AgentCmd::ExperienceMine { jsonl_path } => cmd_experience_mine(jsonl_path), + AgentCmd::FactMine { path, batch, dry_run, output, min_messages } + => cmd_fact_mine(&path, batch, dry_run, output.as_deref(), min_messages), + AgentCmd::FactMineStore { path } => cmd_fact_mine_store(&path), + AgentCmd::ReplayQueue { count } => cmd_replay_queue(count), + }, + + // Admin + Command::Admin(sub) => match sub { + AdminCmd::Init => cmd_init(), + AdminCmd::Health => cmd_health(), + AdminCmd::Fsck => cmd_fsck(), + AdminCmd::Dedup { apply } => cmd_dedup(apply), + AdminCmd::DailyCheck => cmd_daily_check(), + AdminCmd::Import { files } => cmd_import(&files), + AdminCmd::Export { files, all } => cmd_export(&files, all), + AdminCmd::LoadContext { stats } => cmd_load_context(stats), + AdminCmd::Log => cmd_log(), + AdminCmd::Params => cmd_params(), + AdminCmd::LookupBump { keys } => cmd_lookup_bump(&keys), + AdminCmd::Lookups { date } => cmd_lookups(date.as_deref()), + }, }; if let Err(e) = result { @@ -807,10 +841,6 @@ fn install_default_file(data_dir: &std::path::Path, name: &str, content: &str) - Ok(()) } -fn cmd_migrate() -> Result<(), String> { - migrate::migrate() -} - fn cmd_fsck() -> Result<(), String> { let mut store = store::Store::load()?; @@ -1871,57 +1901,6 @@ fn cmd_node_rename(old_key: &str, new_key: &str) -> Result<(), String> { Ok(()) } -fn cmd_journal_ts_migrate() -> Result<(), String> { - use chrono::{NaiveDateTime, TimeZone, Local}; - - let mut store = store::Store::load()?; - let re = regex::Regex::new(r"j-(\d{4}-\d{2}-\d{2})[t-](\d{2})-(\d{2})").unwrap(); - - let valid_range = 978_307_200i64..=4_102_444_800i64; - - let to_update: Vec<_> = store.nodes.values() - .filter(|n| !valid_range.contains(&n.created_at)) - .map(|n| n.key.clone()) - .collect(); - - let mut updated = 0usize; - - for key in &to_update { - if let Some(caps) = re.captures(key) { - let date_str = format!("{} {}:{}", &caps[1], &caps[2], &caps[3]); - if let Ok(ndt) = NaiveDateTime::parse_from_str(&date_str, "%Y-%m-%d %H:%M") { - if let Some(dt) = Local.from_local_datetime(&ndt).earliest() { - if let Some(node) = store.nodes.get_mut(key) { - node.created_at = dt.timestamp(); - node.version += 1; - } - updated += 1; - continue; - } - } - } - if let Some(node) = store.nodes.get_mut(key) { - node.created_at = node.timestamp; - node.version += 1; - updated += 1; - } - } - - let nodes_to_write: Vec<_> = to_update.iter() - .filter_map(|k| store.nodes.get(k)) - .filter(|n| valid_range.contains(&n.created_at)) - .cloned() - .collect(); - - if !nodes_to_write.is_empty() { - store.append_nodes(&nodes_to_write)?; - store.save()?; - } - - println!("journal-ts-migrate: updated {}/{}", updated, to_update.len()); - Ok(()) -} - fn get_group_content(group: &config::ContextGroup, store: &store::Store, cfg: &config::Config) -> Vec<(String, String)> { match group.source { config::ContextSource::Journal => {