refactor(cli): migrate all command modules from console::style to Theme
Replace all console::style() calls in command modules with the centralized Theme API and render:: utility functions. This ensures consistent color behavior across the entire CLI, proper NO_COLOR/--color never support via the LoreRenderer singleton, and eliminates duplicated formatting code. Changes per module: - count.rs: Theme for table headers, render::format_number replacing local duplicate. Removed local format_number implementation. - doctor.rs: Theme::success/warning/error for check status symbols and messages. Unicode escapes for check/warning/cross symbols. - drift.rs: Theme::bold/error/success for drift detection headers and status messages. - embed.rs: Compact output format — headline with count, zero-suppressed detail lines, 'nothing to embed' short-circuit for no-op runs. - generate_docs.rs: Same compact pattern — headline + detail + hint for next step. No-op short-circuit when regenerated==0. - ingest.rs: Theme for project summaries, sync status, dry-run preview. All console::style -> Theme replacements. - list.rs: Replace comfy-table with render::LoreTable for issue/MR listing. Remove local colored_cell, colored_cell_hex, format_relative_time, truncate_with_ellipsis, and format_labels (all moved to render.rs). - list_tests.rs: Update test assertions to use render:: functions. - search.rs: Add render_snippet() for FTS5 <mark> tag highlighting via Theme::bold().underline(). Compact result layout with type badges. - show.rs: Theme for entity detail views, delegate format_date and wrap_text to render module. - stats.rs: Section-based layout using render::section_divider. Compact middle-dot format for document counts. Color-coded embedding coverage percentage (green >=95%, yellow >=50%, red <50%). - sync.rs: Compact sync summary — headline with counts and elapsed time, zero-suppressed detail lines, visually prominent error-only section. - sync_status.rs: Theme for run history headers, removed local format_number duplicate. - timeline.rs: Theme for headers/footers, render:: for date/truncate, standard format! padding replacing console::pad_str. - who.rs: Theme for all expert/workload/active/overlap/review output modes, render:: for relative time and truncation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use console::style;
|
||||
use crate::cli::render::{self, Theme};
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use serde::Serialize;
|
||||
use std::sync::Arc;
|
||||
@@ -240,7 +240,7 @@ pub async fn run_sync(
|
||||
embed_bar.finish_and_clear();
|
||||
spinner.finish_and_clear();
|
||||
if !options.robot_mode {
|
||||
eprintln!(" {} Embedding skipped ({})", style("warn").yellow(), e);
|
||||
eprintln!(" {} Embedding skipped ({})", Theme::warning().render("warn"), e);
|
||||
}
|
||||
warn!(error = %e, "Embedding stage failed (Ollama may be unavailable), continuing");
|
||||
}
|
||||
@@ -273,37 +273,58 @@ pub fn print_sync(
|
||||
elapsed: std::time::Duration,
|
||||
metrics: Option<&MetricsLayer>,
|
||||
) {
|
||||
println!("{} Sync complete:", style("done").green().bold(),);
|
||||
println!(" Issues updated: {}", result.issues_updated);
|
||||
println!(" MRs updated: {}", result.mrs_updated);
|
||||
// Headline: what happened, how long
|
||||
println!(
|
||||
" Discussions fetched: {}",
|
||||
result.discussions_fetched
|
||||
"\n {} {} issues and {} MRs in {:.1}s",
|
||||
Theme::success().bold().render("Synced"),
|
||||
Theme::bold().render(&result.issues_updated.to_string()),
|
||||
Theme::bold().render(&result.mrs_updated.to_string()),
|
||||
elapsed.as_secs_f64()
|
||||
);
|
||||
if result.mr_diffs_fetched > 0 || result.mr_diffs_failed > 0 {
|
||||
println!(" MR diffs fetched: {}", result.mr_diffs_fetched);
|
||||
if result.mr_diffs_failed > 0 {
|
||||
println!(" MR diffs failed: {}", result.mr_diffs_failed);
|
||||
}
|
||||
|
||||
// Detail: supporting counts, compact middle-dot format, zero-suppressed
|
||||
let mut details: Vec<String> = Vec::new();
|
||||
if result.discussions_fetched > 0 {
|
||||
details.push(format!("{} discussions", result.discussions_fetched));
|
||||
}
|
||||
if result.resource_events_fetched > 0 || result.resource_events_failed > 0 {
|
||||
println!(
|
||||
" Resource events fetched: {}",
|
||||
result.resource_events_fetched
|
||||
);
|
||||
if result.resource_events_failed > 0 {
|
||||
println!(
|
||||
" Resource events failed: {}",
|
||||
result.resource_events_failed
|
||||
);
|
||||
}
|
||||
if result.resource_events_fetched > 0 {
|
||||
details.push(format!("{} events", result.resource_events_fetched));
|
||||
}
|
||||
println!(
|
||||
" Documents regenerated: {}",
|
||||
result.documents_regenerated
|
||||
);
|
||||
println!(" Documents embedded: {}", result.documents_embedded);
|
||||
println!(" Elapsed: {:.1}s", elapsed.as_secs_f64());
|
||||
if result.mr_diffs_fetched > 0 {
|
||||
details.push(format!("{} diffs", result.mr_diffs_fetched));
|
||||
}
|
||||
if !details.is_empty() {
|
||||
println!(" {}", Theme::dim().render(&details.join(" \u{b7} ")));
|
||||
}
|
||||
|
||||
// Documents: regeneration + embedding as a second detail line
|
||||
let mut doc_parts: Vec<String> = Vec::new();
|
||||
if result.documents_regenerated > 0 {
|
||||
doc_parts.push(format!("{} docs regenerated", result.documents_regenerated));
|
||||
}
|
||||
if result.documents_embedded > 0 {
|
||||
doc_parts.push(format!("{} embedded", result.documents_embedded));
|
||||
}
|
||||
if !doc_parts.is_empty() {
|
||||
println!(" {}", Theme::dim().render(&doc_parts.join(" \u{b7} ")));
|
||||
}
|
||||
|
||||
// Errors: visually prominent, only if non-zero
|
||||
let mut errors: Vec<String> = Vec::new();
|
||||
if result.resource_events_failed > 0 {
|
||||
errors.push(format!("{} event failures", result.resource_events_failed));
|
||||
}
|
||||
if result.mr_diffs_failed > 0 {
|
||||
errors.push(format!("{} diff failures", result.mr_diffs_failed));
|
||||
}
|
||||
if result.status_enrichment_errors > 0 {
|
||||
errors.push(format!("{} status errors", result.status_enrichment_errors));
|
||||
}
|
||||
if !errors.is_empty() {
|
||||
println!(" {}", Theme::error().render(&errors.join(" \u{b7} ")));
|
||||
}
|
||||
|
||||
println!();
|
||||
|
||||
if let Some(metrics) = metrics {
|
||||
let stages = metrics.extract_timings();
|
||||
@@ -313,9 +334,12 @@ pub fn print_sync(
|
||||
}
|
||||
}
|
||||
|
||||
fn section(title: &str) {
|
||||
println!("{}", render::section_divider(title));
|
||||
}
|
||||
|
||||
fn print_timing_summary(stages: &[StageTiming]) {
|
||||
println!();
|
||||
println!("{}", style("Stage timing:").dim());
|
||||
section("Timing");
|
||||
for stage in stages {
|
||||
for sub in &stage.sub_stages {
|
||||
print_stage_line(sub, 1);
|
||||
@@ -331,29 +355,25 @@ fn print_stage_line(stage: &StageTiming, depth: usize) {
|
||||
stage.name.clone()
|
||||
};
|
||||
let pad_width = 30_usize.saturating_sub(indent.len() + name.len());
|
||||
let dots = ".".repeat(pad_width.max(2));
|
||||
let dots = Theme::dim().render(&".".repeat(pad_width.max(2)));
|
||||
|
||||
let mut suffix = String::new();
|
||||
let time_str = Theme::bold().render(&format!("{:.1}s", stage.elapsed_ms as f64 / 1000.0));
|
||||
|
||||
let mut parts: Vec<String> = Vec::new();
|
||||
if stage.items_processed > 0 {
|
||||
suffix.push_str(&format!("{} items", stage.items_processed));
|
||||
parts.push(format!("{} items", stage.items_processed));
|
||||
}
|
||||
if stage.errors > 0 {
|
||||
if !suffix.is_empty() {
|
||||
suffix.push_str(", ");
|
||||
}
|
||||
suffix.push_str(&format!("{} errors", stage.errors));
|
||||
parts.push(Theme::error().render(&format!("{} errors", stage.errors)));
|
||||
}
|
||||
if stage.rate_limit_hits > 0 {
|
||||
if !suffix.is_empty() {
|
||||
suffix.push_str(", ");
|
||||
}
|
||||
suffix.push_str(&format!("{} rate limits", stage.rate_limit_hits));
|
||||
parts.push(Theme::warning().render(&format!("{} rate limits", stage.rate_limit_hits)));
|
||||
}
|
||||
|
||||
let time_str = format!("{:.1}s", stage.elapsed_ms as f64 / 1000.0);
|
||||
if suffix.is_empty() {
|
||||
if parts.is_empty() {
|
||||
println!("{indent}{name} {dots} {time_str}");
|
||||
} else {
|
||||
let suffix = parts.join(" \u{b7} ");
|
||||
println!("{indent}{name} {dots} {time_str} ({suffix})");
|
||||
}
|
||||
|
||||
@@ -423,87 +443,52 @@ async fn run_sync_dry_run(config: &Config, options: &SyncOptions) -> Result<Sync
|
||||
|
||||
pub fn print_sync_dry_run(result: &SyncDryRunResult) {
|
||||
println!(
|
||||
"{} {}",
|
||||
style("Sync Dry Run Preview").cyan().bold(),
|
||||
style("(no changes will be made)").yellow()
|
||||
"\n {} {}",
|
||||
Theme::info().bold().render("Dry run"),
|
||||
Theme::dim().render("(no changes will be made)")
|
||||
);
|
||||
println!();
|
||||
|
||||
println!("{}", style("Stage 1: Issues Ingestion").white().bold());
|
||||
println!(
|
||||
" Sync mode: {}",
|
||||
if result.issues_preview.sync_mode == "full" {
|
||||
style("full").yellow()
|
||||
} else {
|
||||
style("incremental").green()
|
||||
}
|
||||
);
|
||||
println!(" Projects: {}", result.issues_preview.projects.len());
|
||||
for project in &result.issues_preview.projects {
|
||||
let sync_status = if !project.has_cursor {
|
||||
style("initial sync").yellow()
|
||||
} else {
|
||||
style("incremental").green()
|
||||
};
|
||||
println!(
|
||||
" {} ({}) - {} existing",
|
||||
&project.path, sync_status, project.existing_count
|
||||
);
|
||||
}
|
||||
println!();
|
||||
|
||||
println!(
|
||||
"{}",
|
||||
style("Stage 2: Merge Requests Ingestion").white().bold()
|
||||
);
|
||||
println!(
|
||||
" Sync mode: {}",
|
||||
if result.mrs_preview.sync_mode == "full" {
|
||||
style("full").yellow()
|
||||
} else {
|
||||
style("incremental").green()
|
||||
}
|
||||
);
|
||||
println!(" Projects: {}", result.mrs_preview.projects.len());
|
||||
for project in &result.mrs_preview.projects {
|
||||
let sync_status = if !project.has_cursor {
|
||||
style("initial sync").yellow()
|
||||
} else {
|
||||
style("incremental").green()
|
||||
};
|
||||
println!(
|
||||
" {} ({}) - {} existing",
|
||||
&project.path, sync_status, project.existing_count
|
||||
);
|
||||
}
|
||||
println!();
|
||||
print_dry_run_entity("Issues", &result.issues_preview);
|
||||
print_dry_run_entity("Merge Requests", &result.mrs_preview);
|
||||
|
||||
// Pipeline stages
|
||||
section("Pipeline");
|
||||
let mut stages: Vec<String> = Vec::new();
|
||||
if result.would_generate_docs {
|
||||
println!(
|
||||
"{} {}",
|
||||
style("Stage 3: Document Generation").white().bold(),
|
||||
style("(would run)").green()
|
||||
);
|
||||
stages.push("generate-docs".to_string());
|
||||
} else {
|
||||
println!(
|
||||
"{} {}",
|
||||
style("Stage 3: Document Generation").white().bold(),
|
||||
style("(skipped)").dim()
|
||||
);
|
||||
stages.push(Theme::dim().render("generate-docs (skip)"));
|
||||
}
|
||||
|
||||
if result.would_embed {
|
||||
println!(
|
||||
"{} {}",
|
||||
style("Stage 4: Embedding").white().bold(),
|
||||
style("(would run)").green()
|
||||
);
|
||||
stages.push("embed".to_string());
|
||||
} else {
|
||||
println!(
|
||||
"{} {}",
|
||||
style("Stage 4: Embedding").white().bold(),
|
||||
style("(skipped)").dim()
|
||||
);
|
||||
stages.push(Theme::dim().render("embed (skip)"));
|
||||
}
|
||||
println!(" {}", stages.join(" \u{b7} "));
|
||||
}
|
||||
|
||||
fn print_dry_run_entity(label: &str, preview: &DryRunPreview) {
|
||||
section(label);
|
||||
let mode = if preview.sync_mode == "full" {
|
||||
Theme::warning().render("full")
|
||||
} else {
|
||||
Theme::success().render("incremental")
|
||||
};
|
||||
println!(" {} \u{b7} {} projects", mode, preview.projects.len());
|
||||
for project in &preview.projects {
|
||||
let sync_status = if !project.has_cursor {
|
||||
Theme::warning().render("initial sync")
|
||||
} else {
|
||||
Theme::success().render("incremental")
|
||||
};
|
||||
if project.existing_count > 0 {
|
||||
println!(
|
||||
" {} \u{b7} {} \u{b7} {} existing",
|
||||
&project.path, sync_status, project.existing_count
|
||||
);
|
||||
} else {
|
||||
println!(" {} \u{b7} {}", &project.path, sync_status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user