refactor(main): wire LoreRenderer init, migrate to Theme, improve UX polish
Wire the LoreRenderer singleton initialization into main.rs color mode
handling, replacing the console::style import with Theme throughout.
Key changes:
- Color initialization: LoreRenderer::init() called for all code paths
(NO_COLOR, --color never/always/auto, unknown mode fallback) alongside
the existing console::set_colors_enabled() calls. Both systems must
agree since some transitive code still uses console (e.g. dialoguer).
- Tracing: Replace .with_target(false) with .event_format(CompactHumanFormat)
for the stderr layer, producing the clean 'HH:MM:SS LEVEL message' format.
- Error handling: handle_error() now shows machine-actionable recovery
commands from gi_error.actions() below the hint, formatted with dim '$'
prefix and bold command text.
- Deprecation warnings: All 'lore list', 'lore show', 'lore auth-test',
'lore sync-status' warnings migrated to Theme::warning().
- Init wizard: All success/info/error messages migrated. Unicode check
marks use explicit \u{2713} escapes instead of literal symbols.
- Embed command: Added progress bar with indicatif for embedding stage,
showing position/total with steady tick. Elapsed time shown on completion.
- Generate-docs and ingest commands: Added 'Done in Xs' elapsed time and
next-step hints (run embed after generate-docs, run generate-docs after
ingest) for better workflow guidance.
- Sync output: Interrupt message and lock release migrated to Theme.
- Health command: Status labels and overall healthy/unhealthy styled.
- Robot-docs: Added drift command schema, updated sync flags to include
--no-file-changes, updated who flags with new options.
- Timeline --expand-mentions -> --no-mentions flag rename wired through
params and robot-docs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
223
src/main.rs
223
src/main.rs
@@ -1,5 +1,4 @@
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use console::style;
|
|
||||||
use dialoguer::{Confirm, Input};
|
use dialoguer::{Confirm, Input};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use strsim::jaro_winkler;
|
use strsim::jaro_winkler;
|
||||||
@@ -26,6 +25,7 @@ use lore::cli::commands::{
|
|||||||
run_list_issues, run_list_mrs, run_search, run_show_issue, run_show_mr, run_stats, run_sync,
|
run_list_issues, run_list_mrs, run_search, run_show_issue, run_show_mr, run_stats, run_sync,
|
||||||
run_sync_status, run_timeline, run_who,
|
run_sync_status, run_timeline, run_who,
|
||||||
};
|
};
|
||||||
|
use lore::cli::render::{ColorMode, LoreRenderer, Theme};
|
||||||
use lore::cli::robot::{RobotMeta, strip_schemas};
|
use lore::cli::robot::{RobotMeta, strip_schemas};
|
||||||
use lore::cli::{
|
use lore::cli::{
|
||||||
Cli, Commands, CountArgs, EmbedArgs, GenerateDocsArgs, IngestArgs, IssuesArgs, MrsArgs,
|
Cli, Commands, CountArgs, EmbedArgs, GenerateDocsArgs, IngestArgs, IssuesArgs, MrsArgs,
|
||||||
@@ -116,7 +116,7 @@ async fn main() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let stderr_layer = tracing_subscriber::fmt::layer()
|
let stderr_layer = tracing_subscriber::fmt::layer()
|
||||||
.with_target(false)
|
.event_format(logging::CompactHumanFormat)
|
||||||
.with_writer(lore::cli::progress::SuspendingWriter)
|
.with_writer(lore::cli::progress::SuspendingWriter)
|
||||||
.with_filter(stderr_filter);
|
.with_filter(stderr_filter);
|
||||||
|
|
||||||
@@ -146,13 +146,23 @@ async fn main() {
|
|||||||
|
|
||||||
// I1: Respect NO_COLOR convention (https://no-color.org/)
|
// I1: Respect NO_COLOR convention (https://no-color.org/)
|
||||||
if std::env::var("NO_COLOR").is_ok_and(|v| !v.is_empty()) {
|
if std::env::var("NO_COLOR").is_ok_and(|v| !v.is_empty()) {
|
||||||
|
LoreRenderer::init(ColorMode::Never);
|
||||||
console::set_colors_enabled(false);
|
console::set_colors_enabled(false);
|
||||||
} else {
|
} else {
|
||||||
match cli.color.as_str() {
|
match cli.color.as_str() {
|
||||||
"never" => console::set_colors_enabled(false),
|
"never" => {
|
||||||
"always" => console::set_colors_enabled(true),
|
LoreRenderer::init(ColorMode::Never);
|
||||||
"auto" => {}
|
console::set_colors_enabled(false);
|
||||||
|
}
|
||||||
|
"always" => {
|
||||||
|
LoreRenderer::init(ColorMode::Always);
|
||||||
|
console::set_colors_enabled(true);
|
||||||
|
}
|
||||||
|
"auto" => {
|
||||||
|
LoreRenderer::init(ColorMode::Auto);
|
||||||
|
}
|
||||||
other => {
|
other => {
|
||||||
|
LoreRenderer::init(ColorMode::Auto);
|
||||||
eprintln!("Warning: unknown color mode '{}', using auto", other);
|
eprintln!("Warning: unknown color mode '{}', using auto", other);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,8 +287,9 @@ async fn main() {
|
|||||||
} else {
|
} else {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"{}",
|
"{}",
|
||||||
style("warning: 'lore list' is deprecated, use 'lore issues' or 'lore mrs'")
|
Theme::warning().render(
|
||||||
.yellow()
|
"warning: 'lore list' is deprecated, use 'lore issues' or 'lore mrs'"
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
handle_list_compat(
|
handle_list_compat(
|
||||||
@@ -318,11 +329,10 @@ async fn main() {
|
|||||||
} else {
|
} else {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"{}",
|
"{}",
|
||||||
style(format!(
|
Theme::warning().render(&format!(
|
||||||
"warning: 'lore show' is deprecated, use 'lore {}s {}'",
|
"warning: 'lore show' is deprecated, use 'lore {}s {}'",
|
||||||
entity, iid
|
entity, iid
|
||||||
))
|
))
|
||||||
.yellow()
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
handle_show_compat(
|
handle_show_compat(
|
||||||
@@ -342,7 +352,8 @@ async fn main() {
|
|||||||
} else {
|
} else {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"{}",
|
"{}",
|
||||||
style("warning: 'lore auth-test' is deprecated, use 'lore auth'").yellow()
|
Theme::warning()
|
||||||
|
.render("warning: 'lore auth-test' is deprecated, use 'lore auth'")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
handle_auth_test(cli.config.as_deref(), robot_mode).await
|
handle_auth_test(cli.config.as_deref(), robot_mode).await
|
||||||
@@ -355,7 +366,8 @@ async fn main() {
|
|||||||
} else {
|
} else {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"{}",
|
"{}",
|
||||||
style("warning: 'lore sync-status' is deprecated, use 'lore status'").yellow()
|
Theme::warning()
|
||||||
|
.render("warning: 'lore sync-status' is deprecated, use 'lore status'")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
handle_sync_status_cmd(cli.config.as_deref(), robot_mode).await
|
handle_sync_status_cmd(cli.config.as_deref(), robot_mode).await
|
||||||
@@ -397,9 +409,20 @@ fn handle_error(e: Box<dyn std::error::Error>, robot_mode: bool) -> ! {
|
|||||||
);
|
);
|
||||||
std::process::exit(gi_error.exit_code());
|
std::process::exit(gi_error.exit_code());
|
||||||
} else {
|
} else {
|
||||||
eprintln!("{} {}", style("Error:").red(), gi_error);
|
eprintln!("{} {}", Theme::error().render("Error:"), gi_error);
|
||||||
if let Some(suggestion) = gi_error.suggestion() {
|
if let Some(suggestion) = gi_error.suggestion() {
|
||||||
eprintln!("{} {}", style("Hint:").yellow(), suggestion);
|
eprintln!("{} {}", Theme::warning().render("Hint:"), suggestion);
|
||||||
|
}
|
||||||
|
let actions = gi_error.actions();
|
||||||
|
if !actions.is_empty() {
|
||||||
|
eprintln!();
|
||||||
|
for action in &actions {
|
||||||
|
eprintln!(
|
||||||
|
" {} {}",
|
||||||
|
Theme::dim().render("$"),
|
||||||
|
Theme::bold().render(action)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
std::process::exit(gi_error.exit_code());
|
std::process::exit(gi_error.exit_code());
|
||||||
}
|
}
|
||||||
@@ -420,7 +443,7 @@ fn handle_error(e: Box<dyn std::error::Error>, robot_mode: bool) -> ! {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
eprintln!("{} {}", style("Error:").red(), e);
|
eprintln!("{} {}", Theme::error().render("Error:"), e);
|
||||||
}
|
}
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
@@ -459,7 +482,7 @@ fn emit_correction_warnings(result: &CorrectionResult, robot_mode: bool) {
|
|||||||
for c in &result.corrections {
|
for c in &result.corrections {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"{} {}",
|
"{} {}",
|
||||||
style("Auto-corrected:").yellow(),
|
Theme::warning().render("Auto-corrected:"),
|
||||||
autocorrect::format_teaching_note(c)
|
autocorrect::format_teaching_note(c)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -984,7 +1007,7 @@ async fn handle_ingest(
|
|||||||
if !robot_mode && !quiet {
|
if !robot_mode && !quiet {
|
||||||
println!(
|
println!(
|
||||||
"{}",
|
"{}",
|
||||||
style("Ingesting all content (issues + merge requests)...").blue()
|
Theme::info().render("Ingesting all content (issues + merge requests)...")
|
||||||
);
|
);
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
@@ -1027,7 +1050,7 @@ async fn handle_ingest(
|
|||||||
if !robot_mode {
|
if !robot_mode {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"{}",
|
"{}",
|
||||||
style("Interrupted by Ctrl+C. Partial data has been saved.").yellow()
|
Theme::warning().render("Interrupted by Ctrl+C. Partial data has been saved.")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -1037,6 +1060,12 @@ async fn handle_ingest(
|
|||||||
let total_items: usize = stages.iter().map(|s| s.items_processed).sum();
|
let total_items: usize = stages.iter().map(|s| s.items_processed).sum();
|
||||||
let total_errors: usize = stages.iter().map(|s| s.errors).sum();
|
let total_errors: usize = stages.iter().map(|s| s.errors).sum();
|
||||||
let _ = recorder.succeed(&recorder_conn, &stages, total_items, total_errors);
|
let _ = recorder.succeed(&recorder_conn, &stages, total_items, total_errors);
|
||||||
|
if !robot_mode && !quiet {
|
||||||
|
eprintln!(
|
||||||
|
"{}",
|
||||||
|
Theme::dim().render("Hint: Run 'lore generate-docs' to update searchable documents, then 'lore embed' for vectors.")
|
||||||
|
);
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -1311,11 +1340,10 @@ async fn handle_init(
|
|||||||
if non_interactive {
|
if non_interactive {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"{}",
|
"{}",
|
||||||
style(format!(
|
Theme::error().render(&format!(
|
||||||
"Config file exists at {}. Use --force to overwrite.",
|
"Config file exists at {}. Use --force to overwrite.",
|
||||||
config_path.display()
|
config_path.display()
|
||||||
))
|
))
|
||||||
.red()
|
|
||||||
);
|
);
|
||||||
std::process::exit(2);
|
std::process::exit(2);
|
||||||
}
|
}
|
||||||
@@ -1329,7 +1357,7 @@ async fn handle_init(
|
|||||||
.interact()?;
|
.interact()?;
|
||||||
|
|
||||||
if !confirm {
|
if !confirm {
|
||||||
println!("{}", style("Cancelled.").yellow());
|
println!("{}", Theme::warning().render("Cancelled."));
|
||||||
std::process::exit(2);
|
std::process::exit(2);
|
||||||
}
|
}
|
||||||
confirmed_overwrite = true;
|
confirmed_overwrite = true;
|
||||||
@@ -1408,7 +1436,7 @@ async fn handle_init(
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
println!("{}", style("\nValidating configuration...").blue());
|
println!("{}", Theme::info().render("Validating configuration..."));
|
||||||
|
|
||||||
let result = run_init(
|
let result = run_init(
|
||||||
InitInputs {
|
InitInputs {
|
||||||
@@ -1427,35 +1455,43 @@ async fn handle_init(
|
|||||||
|
|
||||||
println!(
|
println!(
|
||||||
"{}",
|
"{}",
|
||||||
style(format!(
|
Theme::success().render(&format!(
|
||||||
"\n✓ Authenticated as @{} ({})",
|
"\n\u{2713} Authenticated as @{} ({})",
|
||||||
result.user.username, result.user.name
|
result.user.username, result.user.name
|
||||||
))
|
))
|
||||||
.green()
|
|
||||||
);
|
);
|
||||||
|
|
||||||
for project in &result.projects {
|
for project in &result.projects {
|
||||||
println!(
|
println!(
|
||||||
"{}",
|
"{}",
|
||||||
style(format!("✓ {} ({})", project.path, project.name)).green()
|
Theme::success().render(&format!("\u{2713} {} ({})", project.path, project.name))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref dp) = result.default_project {
|
if let Some(ref dp) = result.default_project {
|
||||||
println!("{}", style(format!("✓ Default project: {dp}")).green());
|
println!(
|
||||||
|
"{}",
|
||||||
|
Theme::success().render(&format!("\u{2713} Default project: {dp}"))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"{}",
|
"{}",
|
||||||
style(format!("\n✓ Config written to {}", result.config_path)).green()
|
Theme::success().render(&format!(
|
||||||
|
"\n\u{2713} Config written to {}",
|
||||||
|
result.config_path
|
||||||
|
))
|
||||||
);
|
);
|
||||||
println!(
|
println!(
|
||||||
"{}",
|
"{}",
|
||||||
style(format!("✓ Database initialized at {}", result.data_dir)).green()
|
Theme::success().render(&format!(
|
||||||
|
"\u{2713} Database initialized at {}",
|
||||||
|
result.data_dir
|
||||||
|
))
|
||||||
);
|
);
|
||||||
println!(
|
println!(
|
||||||
"{}",
|
"{}",
|
||||||
style("\nSetup complete! Run 'lore doctor' to verify.").blue()
|
Theme::info().render("\nSetup complete! Run 'lore doctor' to verify.")
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -1518,9 +1554,9 @@ async fn handle_auth_test(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
eprintln!("{} {}", style("Error:").red(), e);
|
eprintln!("{} {}", Theme::error().render("Error:"), e);
|
||||||
if let Some(suggestion) = e.suggestion() {
|
if let Some(suggestion) = e.suggestion() {
|
||||||
eprintln!("{} {}", style("Hint:").yellow(), suggestion);
|
eprintln!("{} {}", Theme::warning().render("Hint:"), suggestion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::process::exit(e.exit_code());
|
std::process::exit(e.exit_code());
|
||||||
@@ -1647,7 +1683,7 @@ fn handle_backup(robot_mode: bool) -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
} else {
|
} else {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"{} The 'backup' command is not yet implemented.",
|
"{} The 'backup' command is not yet implemented.",
|
||||||
style("Error:").red()
|
Theme::error().render("Error:")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
@@ -1669,7 +1705,7 @@ fn handle_reset(robot_mode: bool) -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
} else {
|
} else {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"{} The 'reset' command is not yet implemented.",
|
"{} The 'reset' command is not yet implemented.",
|
||||||
style("Error:").red()
|
Theme::error().render("Error:")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
@@ -1728,11 +1764,11 @@ async fn handle_migrate(
|
|||||||
} else {
|
} else {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"{}",
|
"{}",
|
||||||
style(format!("Database not found at {}", db_path.display())).red()
|
Theme::error().render(&format!("Database not found at {}", db_path.display()))
|
||||||
);
|
);
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"{}",
|
"{}",
|
||||||
style("Run 'lore init' first to create the database.").yellow()
|
Theme::warning().render("Run 'lore init' first to create the database.")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
std::process::exit(10);
|
std::process::exit(10);
|
||||||
@@ -1744,7 +1780,7 @@ async fn handle_migrate(
|
|||||||
if !robot_mode {
|
if !robot_mode {
|
||||||
println!(
|
println!(
|
||||||
"{}",
|
"{}",
|
||||||
style(format!("Current schema version: {}", before_version)).blue()
|
Theme::info().render(&format!("Current schema version: {}", before_version))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1768,14 +1804,16 @@ async fn handle_migrate(
|
|||||||
} else if after_version > before_version {
|
} else if after_version > before_version {
|
||||||
println!(
|
println!(
|
||||||
"{}",
|
"{}",
|
||||||
style(format!(
|
Theme::success().render(&format!(
|
||||||
"Migrations applied: {} -> {}",
|
"Migrations applied: {} -> {}",
|
||||||
before_version, after_version
|
before_version, after_version
|
||||||
))
|
))
|
||||||
.green()
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
println!("{}", style("Database is already up to date.").green());
|
println!(
|
||||||
|
"{}",
|
||||||
|
Theme::success().render("Database is already up to date.")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -1813,7 +1851,7 @@ async fn handle_timeline(
|
|||||||
.map(String::from),
|
.map(String::from),
|
||||||
since: args.since,
|
since: args.since,
|
||||||
depth: args.depth,
|
depth: args.depth,
|
||||||
expand_mentions: args.expand_mentions,
|
no_mentions: args.no_mentions,
|
||||||
limit: args.limit,
|
limit: args.limit,
|
||||||
max_seeds: args.max_seeds,
|
max_seeds: args.max_seeds,
|
||||||
max_entities: args.max_entities,
|
max_entities: args.max_entities,
|
||||||
@@ -1828,7 +1866,7 @@ async fn handle_timeline(
|
|||||||
&result,
|
&result,
|
||||||
result.total_events_before_limit,
|
result.total_events_before_limit,
|
||||||
params.depth,
|
params.depth,
|
||||||
params.expand_mentions,
|
!params.no_mentions,
|
||||||
args.fields.as_deref(),
|
args.fields.as_deref(),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -1900,10 +1938,25 @@ async fn handle_generate_docs(
|
|||||||
|
|
||||||
let project = config.effective_project(args.project.as_deref());
|
let project = config.effective_project(args.project.as_deref());
|
||||||
let result = run_generate_docs(&config, args.full, project, None)?;
|
let result = run_generate_docs(&config, args.full, project, None)?;
|
||||||
|
let elapsed = start.elapsed();
|
||||||
if robot_mode {
|
if robot_mode {
|
||||||
print_generate_docs_json(&result, start.elapsed().as_millis() as u64);
|
print_generate_docs_json(&result, elapsed.as_millis() as u64);
|
||||||
} else {
|
} else {
|
||||||
print_generate_docs(&result);
|
print_generate_docs(&result);
|
||||||
|
if elapsed.as_secs() >= 1 {
|
||||||
|
eprintln!(
|
||||||
|
"{}",
|
||||||
|
Theme::dim().render(&format!(" Done in {:.1}s", elapsed.as_secs_f64()))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if result.regenerated > 0 {
|
||||||
|
eprintln!(
|
||||||
|
"{}",
|
||||||
|
Theme::dim().render(
|
||||||
|
"Hint: Run 'lore embed' to update vector embeddings for changed documents."
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1913,6 +1966,10 @@ async fn handle_embed(
|
|||||||
args: EmbedArgs,
|
args: EmbedArgs,
|
||||||
robot_mode: bool,
|
robot_mode: bool,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
let config = Config::load(config_override)?;
|
let config = Config::load(config_override)?;
|
||||||
let full = args.full && !args.no_full;
|
let full = args.full && !args.no_full;
|
||||||
@@ -1928,11 +1985,45 @@ async fn handle_embed(
|
|||||||
std::process::exit(130);
|
std::process::exit(130);
|
||||||
});
|
});
|
||||||
|
|
||||||
let result = run_embed(&config, full, retry_failed, None, &signal).await?;
|
let embed_bar = if robot_mode {
|
||||||
|
ProgressBar::hidden()
|
||||||
|
} else {
|
||||||
|
let b = lore::cli::progress::multi().add(ProgressBar::new(0));
|
||||||
|
b.set_style(
|
||||||
|
ProgressStyle::default_bar()
|
||||||
|
.template(" {spinner:.blue} Generating embeddings [{bar:30.cyan/dim}] {pos}/{len}")
|
||||||
|
.unwrap()
|
||||||
|
.progress_chars("=> "),
|
||||||
|
);
|
||||||
|
b
|
||||||
|
};
|
||||||
|
let bar_clone = embed_bar.clone();
|
||||||
|
let tick_started = Arc::new(AtomicBool::new(false));
|
||||||
|
let tick_clone = Arc::clone(&tick_started);
|
||||||
|
let progress_cb: Box<dyn Fn(usize, usize)> = Box::new(move |processed, total| {
|
||||||
|
if total > 0 {
|
||||||
|
if !tick_clone.swap(true, Ordering::Relaxed) {
|
||||||
|
bar_clone.enable_steady_tick(std::time::Duration::from_millis(100));
|
||||||
|
}
|
||||||
|
bar_clone.set_length(total as u64);
|
||||||
|
bar_clone.set_position(processed as u64);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = run_embed(&config, full, retry_failed, Some(progress_cb), &signal).await?;
|
||||||
|
embed_bar.finish_and_clear();
|
||||||
|
|
||||||
|
let elapsed = start.elapsed();
|
||||||
if robot_mode {
|
if robot_mode {
|
||||||
print_embed_json(&result, start.elapsed().as_millis() as u64);
|
print_embed_json(&result, elapsed.as_millis() as u64);
|
||||||
} else {
|
} else {
|
||||||
print_embed(&result);
|
print_embed(&result);
|
||||||
|
if elapsed.as_secs() >= 1 {
|
||||||
|
eprintln!(
|
||||||
|
"{}",
|
||||||
|
Theme::dim().render(&format!(" Done in {:.1}s", elapsed.as_secs_f64()))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1962,7 +2053,7 @@ async fn handle_sync_cmd(
|
|||||||
dry_run,
|
dry_run,
|
||||||
};
|
};
|
||||||
|
|
||||||
// For dry_run, skip recording and just show the preview
|
// For dry run, skip recording and just show the preview
|
||||||
if dry_run {
|
if dry_run {
|
||||||
let signal = ShutdownSignal::new();
|
let signal = ShutdownSignal::new();
|
||||||
run_sync(&config, options, None, &signal).await?;
|
run_sync(&config, options, None, &signal).await?;
|
||||||
@@ -2003,13 +2094,13 @@ async fn handle_sync_cmd(
|
|||||||
eprintln!();
|
eprintln!();
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"{}",
|
"{}",
|
||||||
console::style("Interrupted by Ctrl+C. Partial results:").yellow()
|
Theme::warning().render("Interrupted by Ctrl+C. Partial results:")
|
||||||
);
|
);
|
||||||
print_sync(&result, elapsed, Some(metrics));
|
print_sync(&result, elapsed, Some(metrics));
|
||||||
if released > 0 {
|
if released > 0 {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"{}",
|
"{}",
|
||||||
console::style(format!("Released {released} locked jobs")).dim()
|
Theme::dim().render(&format!("Released {released} locked jobs"))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2121,9 +2212,9 @@ async fn handle_health(
|
|||||||
} else {
|
} else {
|
||||||
let status = |ok: bool| {
|
let status = |ok: bool| {
|
||||||
if ok {
|
if ok {
|
||||||
style("pass").green()
|
Theme::success().render("pass")
|
||||||
} else {
|
} else {
|
||||||
style("FAIL").red()
|
Theme::error().render("FAIL")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
println!(
|
println!(
|
||||||
@@ -2135,13 +2226,13 @@ async fn handle_health(
|
|||||||
println!("Schema: {} (v{})", status(schema_current), schema_version);
|
println!("Schema: {} (v{})", status(schema_current), schema_version);
|
||||||
println!();
|
println!();
|
||||||
if healthy {
|
if healthy {
|
||||||
println!("{}", style("Healthy").green().bold());
|
println!("{}", Theme::success().bold().render("Healthy"));
|
||||||
} else {
|
} else {
|
||||||
println!(
|
println!(
|
||||||
"{}",
|
"{}",
|
||||||
style("Unhealthy - run 'lore doctor' for details")
|
Theme::error()
|
||||||
.red()
|
|
||||||
.bold()
|
.bold()
|
||||||
|
.render("Unhealthy - run 'lore doctor' for details")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2243,7 +2334,7 @@ fn handle_robot_docs(robot_mode: bool, brief: bool) -> Result<(), Box<dyn std::e
|
|||||||
},
|
},
|
||||||
"sync": {
|
"sync": {
|
||||||
"description": "Full sync pipeline: ingest -> generate-docs -> embed",
|
"description": "Full sync pipeline: ingest -> generate-docs -> embed",
|
||||||
"flags": ["--full", "--no-full", "--force", "--no-force", "--no-embed", "--no-docs", "--no-events", "--dry-run", "--no-dry-run"],
|
"flags": ["--full", "--no-full", "--force", "--no-force", "--no-embed", "--no-docs", "--no-events", "--no-file-changes", "--dry-run", "--no-dry-run"],
|
||||||
"example": "lore --robot sync",
|
"example": "lore --robot sync",
|
||||||
"response_schema": {
|
"response_schema": {
|
||||||
"ok": "bool",
|
"ok": "bool",
|
||||||
@@ -2382,7 +2473,7 @@ fn handle_robot_docs(robot_mode: bool, brief: bool) -> Result<(), Box<dyn std::e
|
|||||||
},
|
},
|
||||||
"timeline": {
|
"timeline": {
|
||||||
"description": "Chronological timeline of events matching a keyword query or entity reference",
|
"description": "Chronological timeline of events matching a keyword query or entity reference",
|
||||||
"flags": ["<QUERY>", "-p/--project", "--since <duration>", "--depth <n>", "--expand-mentions", "-n/--limit", "--fields <list>", "--max-seeds", "--max-entities", "--max-evidence"],
|
"flags": ["<QUERY>", "-p/--project", "--since <duration>", "--depth <n>", "--no-mentions", "-n/--limit", "--fields <list>", "--max-seeds", "--max-entities", "--max-evidence"],
|
||||||
"query_syntax": {
|
"query_syntax": {
|
||||||
"search": "Any text -> hybrid search seeding (FTS + vector)",
|
"search": "Any text -> hybrid search seeding (FTS + vector)",
|
||||||
"entity_direct": "issue:N, i:N, mr:N, m:N -> direct entity seeding (no search, no Ollama)"
|
"entity_direct": "issue:N, i:N, mr:N, m:N -> direct entity seeding (no search, no Ollama)"
|
||||||
@@ -2397,7 +2488,7 @@ fn handle_robot_docs(robot_mode: bool, brief: bool) -> Result<(), Box<dyn std::e
|
|||||||
},
|
},
|
||||||
"who": {
|
"who": {
|
||||||
"description": "People intelligence: experts, workload, active discussions, overlap, review patterns",
|
"description": "People intelligence: experts, workload, active discussions, overlap, review patterns",
|
||||||
"flags": ["<target>", "--path <path>", "--active", "--overlap <path>", "--reviews", "--since <duration>", "-p/--project", "-n/--limit", "--fields <list>"],
|
"flags": ["<target>", "--path <path>", "--active", "--overlap <path>", "--reviews", "--since <duration>", "-p/--project", "-n/--limit", "--fields <list>", "--detail", "--no-detail", "--as-of <date>", "--explain-score", "--include-bots", "--all-history"],
|
||||||
"modes": {
|
"modes": {
|
||||||
"expert": "lore who <file-path> -- Who knows about this area? (also: --path for root files)",
|
"expert": "lore who <file-path> -- Who knows about this area? (also: --path for root files)",
|
||||||
"workload": "lore who <username> -- What is someone working on?",
|
"workload": "lore who <username> -- What is someone working on?",
|
||||||
@@ -2423,6 +2514,16 @@ fn handle_robot_docs(robot_mode: bool, brief: bool) -> Result<(), Box<dyn std::e
|
|||||||
"active_minimal": ["entity_type", "iid", "title", "participants"]
|
"active_minimal": ["entity_type", "iid", "title", "participants"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"drift": {
|
||||||
|
"description": "Detect discussion divergence from original issue intent",
|
||||||
|
"flags": ["<entity_type: issues>", "<IID>", "--threshold <0.0-1.0>", "-p/--project <path>"],
|
||||||
|
"example": "lore --robot drift issues 42 --threshold 0.4",
|
||||||
|
"response_schema": {
|
||||||
|
"ok": "bool",
|
||||||
|
"data": {"entity_type": "string", "iid": "int", "title": "string", "threshold": "float", "divergent_discussions": "[{discussion_id:string, similarity:float, snippet:string}]"},
|
||||||
|
"meta": {"elapsed_ms": "int"}
|
||||||
|
}
|
||||||
|
},
|
||||||
"notes": {
|
"notes": {
|
||||||
"description": "List notes from discussions with rich filtering",
|
"description": "List notes from discussions with rich filtering",
|
||||||
"flags": ["--limit/-n <N>", "--author/-a <username>", "--note-type <type>", "--contains <text>", "--for-issue <iid>", "--for-mr <iid>", "-p/--project <path>", "--since <period>", "--until <period>", "--path <filepath>", "--resolution <any|unresolved|resolved>", "--sort <created|updated>", "--asc", "--include-system", "--note-id <id>", "--gitlab-note-id <id>", "--discussion-id <id>", "--format <table|json|jsonl|csv>", "--fields <list|minimal>", "--open"],
|
"flags": ["--limit/-n <N>", "--author/-a <username>", "--note-type <type>", "--contains <text>", "--for-issue <iid>", "--for-mr <iid>", "-p/--project <path>", "--since <period>", "--until <period>", "--path <filepath>", "--resolution <any|unresolved|resolved>", "--sort <created|updated>", "--asc", "--include-system", "--note-id <id>", "--gitlab-note-id <id>", "--discussion-id <id>", "--format <table|json|jsonl|csv>", "--fields <list|minimal>", "--open"],
|
||||||
@@ -2511,7 +2612,7 @@ fn handle_robot_docs(robot_mode: bool, brief: bool) -> Result<(), Box<dyn std::e
|
|||||||
"temporal_intelligence": [
|
"temporal_intelligence": [
|
||||||
"lore --robot sync",
|
"lore --robot sync",
|
||||||
"lore --robot timeline '<keyword>' --since 30d",
|
"lore --robot timeline '<keyword>' --since 30d",
|
||||||
"lore --robot timeline '<keyword>' --depth 2 --expand-mentions"
|
"lore --robot timeline '<keyword>' --depth 2"
|
||||||
],
|
],
|
||||||
"people_intelligence": [
|
"people_intelligence": [
|
||||||
"lore --robot who src/path/to/feature/",
|
"lore --robot who src/path/to/feature/",
|
||||||
@@ -2762,7 +2863,10 @@ async fn handle_list_compat(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
eprintln!("{}", style(format!("Unknown entity: {entity}")).red());
|
eprintln!(
|
||||||
|
"{}",
|
||||||
|
Theme::error().render(&format!("Unknown entity: {entity}"))
|
||||||
|
);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2799,7 +2903,10 @@ async fn handle_show_compat(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
eprintln!("{}", style(format!("Unknown entity: {entity}")).red());
|
eprintln!(
|
||||||
|
"{}",
|
||||||
|
Theme::error().render(&format!("Unknown entity: {entity}"))
|
||||||
|
);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user