use std::env;
use crate::cli::OutputFormat;
use crate::commands::format_token_count;
use ctx::analytics;
use ctx::embeddings::{self, Provider};
use ctx::error::Result;
use ctx::index;
use ctx::output;
use ctx::smart::{format_dry_run, format_explain, smart_context, SmartConfig};
use ctx::tokens;
use ctx::walker;
#[allow(clippy::too_many_arguments)]
pub fn run_smart(
task: &str,
max_tokens: usize,
depth: i32,
top: usize,
explain: bool,
dry_run: bool,
provider: Provider,
format: OutputFormat,
show_sizes: bool,
no_tree: bool,
) -> Result<()> {
let root = env::current_dir()?;
let db = index::open_database(&root)?;
let embedding_count = db.count_embeddings()?;
if embedding_count == 0 {
eprintln!("No embeddings found. Run 'ctx embed' first to generate embeddings.");
return Ok(());
}
if provider == Provider::Local {
eprintln!("Initializing local embedding model (first run downloads ~90MB)...");
}
let provider =
embeddings::build_provider(provider, &ctx::config::CtxConfig::load(&root).embedding)?;
embeddings::warn_index_mismatch(&db, provider.as_ref());
let analytics = analytics::Analytics::open(&root)?;
let effective_max_tokens = if dry_run { usize::MAX } else { max_tokens };
let config = SmartConfig {
max_tokens: effective_max_tokens,
depth,
top,
encoding: tokens::Encoding::default(),
};
eprintln!("Analyzing task: \"{}\"...", task);
let result = smart_context(&db, &analytics, provider.as_ref(), task, config)?;
if result.selected_files.is_empty() {
eprintln!("No relevant files found for: \"{}\"", task);
std::process::exit(2);
}
if dry_run {
println!("{}", format_dry_run(&result));
return Ok(());
}
if explain {
eprintln!("{}", format_explain(&result));
}
eprintln!(
"Selected {} files ({} tokens){}",
result.selected_files.len(),
result.total_tokens,
if result.truncated {
format!(", {} omitted", result.omitted_count)
} else {
String::new()
}
);
if let Some(top) = result.selected_files.first() {
if top.token_count > max_tokens {
eprintln!(
"note: {} ({} tokens) exceeds the {}-token budget; included alone \
— raise --max-tokens to include more",
top.path, top.token_count, max_tokens
);
}
}
let entries: Vec<walker::FileEntry> = result
.selected_files
.iter()
.map(|f| {
let relative_path = std::path::PathBuf::from(&f.path);
let absolute_path = root.join(&relative_path);
let size = std::fs::metadata(&absolute_path)
.map(|m| m.len())
.unwrap_or(0);
walker::FileEntry {
absolute_path,
relative_path,
size,
}
})
.collect();
let output_result = if entries.is_empty() {
eprintln!("No files to include in context.");
return Ok(());
} else {
output::stream_context(&root, &entries, format.to_lib(), !no_tree, show_sizes)?
};
eprintln!(
"Generated context: {} files, ~{} tokens",
output_result.file_count,
format_token_count(output_result.output_bytes.div_ceil(4))
);
Ok(())
}