From aebbe6b7951995474ec80107eda5fbc9ad3a052c Mon Sep 17 00:00:00 2001 From: Taylor Eernisse Date: Tue, 3 Feb 2026 09:35:22 -0500 Subject: [PATCH] feat(cli): Wire --full flag for embed, add sync stage spinners - Add --full / --no-full flag pair to EmbedArgs with overrides_with semantics matching the existing flag pattern. When active, atomically DELETEs all embedding_metadata and embeddings before re-embedding. - Thread the full flag through run_embed -> run_sync so that 'lore sync --full' triggers a complete re-embed alongside the full re-ingest it already performed. - Add indicatif spinners to sync stages with dynamic stage numbering that adjusts when --no-docs or --no-embed skip stages. Spinners are hidden in robot mode. - Update robot-docs manifest to advertise the new --full flag on the embed command. - Replace hardcoded schema version 9 in health check with the LATEST_SCHEMA_VERSION constant from db.rs. Co-Authored-By: Claude Opus 4.5 --- src/cli/commands/embed.rs | 15 +++++++-- src/cli/commands/sync.rs | 68 ++++++++++++++++++++++++++++++++------- src/cli/mod.rs | 7 ++++ src/main.rs | 10 +++--- 4 files changed, 82 insertions(+), 18 deletions(-) diff --git a/src/cli/commands/embed.rs b/src/cli/commands/embed.rs index 7a99fe7..32ccfd5 100644 --- a/src/cli/commands/embed.rs +++ b/src/cli/commands/embed.rs @@ -21,6 +21,7 @@ pub struct EmbedCommandResult { /// Run the embed command. pub async fn run_embed( config: &Config, + full: bool, retry_failed: bool, ) -> Result { let db_path = get_db_path(config.storage.db_path.as_deref()); @@ -37,8 +38,18 @@ pub async fn run_embed( // Health check — fail fast if Ollama is down or model missing client.health_check().await?; - // If retry_failed, clear errors so they become pending again - if retry_failed { + if full { + // Clear ALL embeddings and metadata atomically for a complete re-embed. + // Wrapped in a transaction so a crash between the two DELETEs can't + // leave orphaned data. + conn.execute_batch( + "BEGIN; + DELETE FROM embedding_metadata; + DELETE FROM embeddings; + COMMIT;", + )?; + } else if retry_failed { + // Clear errors so they become pending again conn.execute( "UPDATE embedding_metadata SET last_error = NULL, attempt_count = 0 WHERE last_error IS NOT NULL", diff --git a/src/cli/commands/sync.rs b/src/cli/commands/sync.rs index ae8b737..bd0007c 100644 --- a/src/cli/commands/sync.rs +++ b/src/cli/commands/sync.rs @@ -1,6 +1,7 @@ //! Sync command: unified orchestrator for ingest -> generate-docs -> embed. use console::style; +use indicatif::{ProgressBar, ProgressStyle}; use serde::Serialize; use tracing::{info, warn}; @@ -31,6 +32,22 @@ pub struct SyncResult { pub documents_embedded: usize, } +/// Create a styled spinner for a sync stage. +fn stage_spinner(stage: u8, total: u8, msg: &str, robot_mode: bool) -> ProgressBar { + if robot_mode { + return ProgressBar::hidden(); + } + let pb = ProgressBar::new_spinner(); + pb.set_style( + ProgressStyle::default_spinner() + .template("{spinner:.blue} {msg}") + .expect("valid template"), + ); + pb.enable_steady_tick(std::time::Duration::from_millis(80)); + pb.set_message(format!("[{stage}/{total}] {msg}")); + pb +} + /// Run the full sync pipeline: ingest -> generate-docs -> embed. pub async fn run_sync(config: &Config, options: SyncOptions) -> Result { let mut result = SyncResult::default(); @@ -41,41 +58,70 @@ pub async fn run_sync(config: &Config, options: SyncOptions) -> Result { result.documents_embedded = embed_result.embedded; + spinner.finish_and_clear(); } Err(e) => { // Graceful degradation: Ollama down is a warning, not an error + spinner.finish_and_clear(); + if !options.robot_mode { + eprintln!( + " {} Embedding skipped ({})", + style("warn").yellow(), + e + ); + } warn!(error = %e, "Embedding stage failed (Ollama may be unavailable), continuing"); } } + } else { + info!("Sync: skipping embedding (--no-embed)"); } info!( diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 25f4190..0b162af 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -483,6 +483,13 @@ pub struct SyncArgs { /// Arguments for `lore embed` #[derive(Parser)] pub struct EmbedArgs { + /// Re-embed all documents (clears existing embeddings first) + #[arg(long, overrides_with = "no_full")] + pub full: bool, + + #[arg(long = "no-full", hide = true, overrides_with = "full")] + pub no_full: bool, + /// Retry previously failed embeddings #[arg(long, overrides_with = "no_retry_failed")] pub retry_failed: bool, diff --git a/src/main.rs b/src/main.rs index 0a1b34a..c6f090e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,7 +26,7 @@ use lore::cli::{ Cli, Commands, CountArgs, EmbedArgs, GenerateDocsArgs, IngestArgs, IssuesArgs, MrsArgs, SearchArgs, StatsArgs, SyncArgs, }; -use lore::core::db::{create_connection, get_schema_version, run_migrations}; +use lore::core::db::{create_connection, get_schema_version, run_migrations, LATEST_SCHEMA_VERSION}; use lore::core::error::{LoreError, RobotErrorOutput}; use lore::core::paths::get_config_path; use lore::core::paths::get_db_path; @@ -1112,8 +1112,9 @@ async fn handle_embed( robot_mode: bool, ) -> Result<(), Box> { let config = Config::load(config_override)?; + let full = args.full && !args.no_full; let retry_failed = args.retry_failed && !args.no_retry_failed; - let result = run_embed(&config, retry_failed).await?; + let result = run_embed(&config, full, retry_failed).await?; if robot_mode { print_embed_json(&result); } else { @@ -1183,8 +1184,7 @@ async fn handle_health( match create_connection(&db_path) { Ok(conn) => { let version = get_schema_version(&conn); - let latest = 9; // Number of embedded migrations - (true, version, version >= latest) + (true, version, version >= LATEST_SCHEMA_VERSION) } Err(_) => (true, 0, false), } @@ -1340,7 +1340,7 @@ fn handle_robot_docs(robot_mode: bool) -> Result<(), Box> }, "embed": { "description": "Generate vector embeddings for documents via Ollama", - "flags": ["--retry-failed"], + "flags": ["--full", "--retry-failed"], "example": "lore --robot embed" }, "migrate": {