Ingest: - Introduce IngestDisplay struct with show_progress/show_text booleans to decouple progress bars from text output. Replaces the robot_mode bool parameter with explicit display control, enabling sync to show progress without duplicating summary text (progress_only mode). - Use resolve_project() for --project filtering instead of LIKE queries, providing proper error messages for ambiguous or missing projects. List: - Add colored_cell() helper that checks console::colors_enabled() before applying comfy-table foreground colors, bridging the gap between the console and comfy-table crates for --color flag support. - Use resolve_project() for project filtering (exact ID match). - Improve since filter to return explicit errors instead of silently ignoring invalid values. - Improve format_relative_time for proper singular/plural forms. Search: - Validate --after/--updated-after with explicit error messages. - Handle optional title field (Option<String>) in HydratedRow. Show: - Use resolve_project() for project disambiguation. Sync: - Thread robot_mode via SyncOptions for IngestDisplay selection. - Use IngestDisplay::progress_only() in interactive sync mode. GenerateDocs: - Use resolve_project() for --project filtering. Co-Authored-By: Claude (us.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com>
132 lines
4.0 KiB
Rust
132 lines
4.0 KiB
Rust
//! Sync command: unified orchestrator for ingest -> generate-docs -> embed.
|
|
|
|
use console::style;
|
|
use serde::Serialize;
|
|
use tracing::{info, warn};
|
|
|
|
use crate::Config;
|
|
use crate::core::error::Result;
|
|
|
|
use super::embed::run_embed;
|
|
use super::generate_docs::run_generate_docs;
|
|
use super::ingest::{IngestDisplay, run_ingest};
|
|
|
|
/// Options for the sync command.
|
|
#[derive(Debug, Default)]
|
|
pub struct SyncOptions {
|
|
pub full: bool,
|
|
pub force: bool,
|
|
pub no_embed: bool,
|
|
pub no_docs: bool,
|
|
pub robot_mode: bool,
|
|
}
|
|
|
|
/// Result of the sync command.
|
|
#[derive(Debug, Default, Serialize)]
|
|
pub struct SyncResult {
|
|
pub issues_updated: usize,
|
|
pub mrs_updated: usize,
|
|
pub discussions_fetched: usize,
|
|
pub documents_regenerated: usize,
|
|
pub documents_embedded: usize,
|
|
}
|
|
|
|
/// Run the full sync pipeline: ingest -> generate-docs -> embed.
|
|
pub async fn run_sync(config: &Config, options: SyncOptions) -> Result<SyncResult> {
|
|
let mut result = SyncResult::default();
|
|
|
|
let ingest_display = if options.robot_mode {
|
|
IngestDisplay::silent()
|
|
} else {
|
|
IngestDisplay::progress_only()
|
|
};
|
|
|
|
// Stage 1: Ingest issues
|
|
info!("Sync stage 1/4: ingesting issues");
|
|
let issues_result = run_ingest(config, "issues", None, options.force, options.full, ingest_display).await?;
|
|
result.issues_updated = issues_result.issues_upserted;
|
|
result.discussions_fetched += issues_result.discussions_fetched;
|
|
|
|
// Stage 2: Ingest MRs
|
|
info!("Sync stage 2/4: ingesting merge requests");
|
|
let mrs_result = run_ingest(config, "mrs", None, options.force, options.full, ingest_display).await?;
|
|
result.mrs_updated = mrs_result.mrs_upserted;
|
|
result.discussions_fetched += mrs_result.discussions_fetched;
|
|
|
|
// Stage 3: Generate documents (unless --no-docs)
|
|
if options.no_docs {
|
|
info!("Sync stage 3/4: skipping document generation (--no-docs)");
|
|
} else {
|
|
info!("Sync stage 3/4: generating documents");
|
|
let docs_result = run_generate_docs(config, false, None)?;
|
|
result.documents_regenerated = docs_result.regenerated;
|
|
}
|
|
|
|
// Stage 4: Embed documents (unless --no-embed)
|
|
if options.no_embed {
|
|
info!("Sync stage 4/4: skipping embedding (--no-embed)");
|
|
} else {
|
|
info!("Sync stage 4/4: embedding documents");
|
|
match run_embed(config, false).await {
|
|
Ok(embed_result) => {
|
|
result.documents_embedded = embed_result.embedded;
|
|
}
|
|
Err(e) => {
|
|
// Graceful degradation: Ollama down is a warning, not an error
|
|
warn!(error = %e, "Embedding stage failed (Ollama may be unavailable), continuing");
|
|
}
|
|
}
|
|
}
|
|
|
|
info!(
|
|
issues = result.issues_updated,
|
|
mrs = result.mrs_updated,
|
|
discussions = result.discussions_fetched,
|
|
docs = result.documents_regenerated,
|
|
embedded = result.documents_embedded,
|
|
"Sync pipeline complete"
|
|
);
|
|
|
|
Ok(result)
|
|
}
|
|
|
|
/// Print human-readable sync summary.
|
|
pub fn print_sync(result: &SyncResult, elapsed: std::time::Duration) {
|
|
println!(
|
|
"{} Sync complete:",
|
|
style("done").green().bold(),
|
|
);
|
|
println!(" Issues updated: {}", result.issues_updated);
|
|
println!(" MRs updated: {}", result.mrs_updated);
|
|
println!(" Discussions fetched: {}", result.discussions_fetched);
|
|
println!(" Documents regenerated: {}", result.documents_regenerated);
|
|
println!(" Documents embedded: {}", result.documents_embedded);
|
|
println!(
|
|
" Elapsed: {:.1}s",
|
|
elapsed.as_secs_f64()
|
|
);
|
|
}
|
|
|
|
/// JSON output for sync.
|
|
#[derive(Serialize)]
|
|
struct SyncJsonOutput<'a> {
|
|
ok: bool,
|
|
data: &'a SyncResult,
|
|
meta: SyncMeta,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct SyncMeta {
|
|
elapsed_ms: u64,
|
|
}
|
|
|
|
/// Print JSON robot-mode sync output.
|
|
pub fn print_sync_json(result: &SyncResult, elapsed_ms: u64) {
|
|
let output = SyncJsonOutput {
|
|
ok: true,
|
|
data: result,
|
|
meta: SyncMeta { elapsed_ms },
|
|
};
|
|
println!("{}", serde_json::to_string(&output).unwrap());
|
|
}
|