use anyhow::Result;
use super::super::BatchContext;
use crate::cli::validate_finite_f32;
use cqs::normalize_path;
pub(in crate::cli::batch) fn dispatch_blame(
ctx: &BatchContext,
target: &str,
depth: usize,
show_callers: bool,
) -> Result<serde_json::Value> {
let _span = tracing::info_span!("batch_blame", target).entered();
let data = crate::cli::commands::blame::build_blame_data(
&ctx.store(),
&ctx.root,
target,
depth,
show_callers,
)?;
Ok(crate::cli::commands::blame::blame_to_json(&data, &ctx.root))
}
pub(in crate::cli::batch) fn dispatch_explain(
ctx: &BatchContext,
target: &str,
tokens: Option<usize>,
) -> Result<serde_json::Value> {
let _span = tracing::info_span!("batch_explain", target).entered();
let index = ctx.vector_index()?;
let index = index.as_deref();
let embedder = if tokens.is_some() {
Some(ctx.embedder()?)
} else {
None
};
let data = crate::cli::commands::explain::build_explain_data(
&ctx.store(),
&ctx.cqs_dir,
target,
tokens,
Some(index),
embedder,
&ctx.model_config,
)?;
let output = crate::cli::commands::explain::build_explain_output(&data, &ctx.root);
Ok(serde_json::to_value(&output)?)
}
pub(in crate::cli::batch) fn dispatch_similar(
ctx: &BatchContext,
name: &str,
limit: usize,
threshold: f32,
) -> Result<serde_json::Value> {
let _span = tracing::info_span!("batch_similar", name).entered();
let threshold = validate_finite_f32(threshold, "threshold")?;
let limit = limit.clamp(1, 100);
let resolved = cqs::resolve_target(&ctx.store(), name)?;
let chunk = &resolved.chunk;
let (source_chunk, embedding) = ctx
.store()
.get_chunk_with_embedding(&chunk.id)?
.ok_or_else(|| anyhow::anyhow!("Could not load embedding for '{}'", chunk.name))?;
let filter = cqs::SearchFilter::default();
let index = ctx.vector_index()?;
let index = index.as_deref();
let results = ctx.store().search_filtered_with_index(
&embedding,
&filter,
limit.saturating_add(1),
threshold,
index,
)?;
let filtered: Vec<_> = results
.into_iter()
.filter(|r| r.chunk.id != source_chunk.id)
.take(limit)
.collect();
let json_results: Vec<serde_json::Value> = filtered
.iter()
.map(|r| {
serde_json::json!({
"name": r.chunk.name,
"file": normalize_path(&r.chunk.file),
"score": r.score,
})
})
.collect();
Ok(serde_json::json!({
"results": json_results,
"target": chunk.name,
"total": json_results.len(),
}))
}
pub(in crate::cli::batch) fn dispatch_context(
ctx: &BatchContext,
path: &str,
summary: bool,
compact: bool,
tokens: Option<usize>,
) -> Result<serde_json::Value> {
let _span = tracing::info_span!("batch_context", path).entered();
if compact {
let data = crate::cli::commands::context::build_compact_data(&ctx.store(), path)?;
return Ok(crate::cli::commands::context::compact_to_json(&data, path));
}
if summary {
let data = crate::cli::commands::context::build_full_data(&ctx.store(), path, &ctx.root)?;
return Ok(crate::cli::commands::context::summary_to_json(&data, path));
}
let chunks = ctx.store().get_chunks_by_origin(path)?;
if chunks.is_empty() {
anyhow::bail!(
"No indexed chunks found for '{}'. Is the file indexed?",
path
);
}
let (chunks, token_info) = if let Some(budget) = tokens {
let embedder = ctx.embedder()?;
let names: Vec<&str> = chunks.iter().map(|c| c.name.as_str()).collect();
let caller_counts = ctx.store().get_caller_counts_batch(&names)?;
let (included, used) = crate::cli::commands::context::pack_by_relevance(
&chunks,
&caller_counts,
budget,
embedder,
);
let filtered: Vec<_> = chunks
.into_iter()
.filter(|c| included.contains(&c.name))
.collect();
(filtered, Some((used, budget)))
} else {
(chunks, None)
};
let entries: Vec<_> = chunks
.iter()
.map(|c| {
serde_json::json!({
"name": c.name,
"chunk_type": c.chunk_type.to_string(),
"language": c.language.to_string(),
"lines": [c.line_start, c.line_end],
"signature": c.signature,
"content": c.content,
})
})
.collect();
let mut response = serde_json::json!({
"file": path,
"chunks": entries,
"total": entries.len(),
});
crate::cli::commands::inject_token_info(&mut response, token_info);
Ok(response)
}
pub(in crate::cli::batch) fn dispatch_stats(ctx: &BatchContext) -> Result<serde_json::Value> {
let _span = tracing::info_span!("batch_stats").entered();
let errors = ctx.error_count.load(std::sync::atomic::Ordering::Relaxed);
let mut output = crate::cli::commands::build_stats(&ctx.store())?;
output.errors = Some(errors as usize);
Ok(serde_json::to_value(&output)?)
}
pub(in crate::cli::batch) fn dispatch_onboard(
ctx: &BatchContext,
query: &str,
depth: usize,
tokens: Option<usize>,
) -> Result<serde_json::Value> {
let _span = tracing::info_span!("batch_onboard", query, depth).entered();
let embedder = ctx.embedder()?;
let depth = depth.clamp(1, 5);
let result = cqs::onboard(&ctx.store(), embedder, query, &ctx.root, depth)?;
let Some(budget) = tokens else {
return serde_json::to_value(&result)
.map_err(|e| anyhow::anyhow!("Failed to serialize onboard: {e}"));
};
let named_items = crate::cli::commands::onboard_scored_names(&result);
let (content_map, used) =
crate::cli::commands::fetch_and_pack_content(&ctx.store(), embedder, &named_items, budget);
let mut json = serde_json::to_value(&result)
.map_err(|e| anyhow::anyhow!("Failed to serialize onboard: {e}"))?;
crate::cli::commands::inject_content_into_onboard_json(&mut json, &content_map, &result);
crate::cli::commands::inject_token_info(&mut json, Some((used, budget)));
Ok(json)
}
pub(in crate::cli::batch) fn dispatch_read(
ctx: &BatchContext,
path: &str,
focus: Option<&str>,
) -> Result<serde_json::Value> {
let _span = tracing::info_span!("batch_read", path).entered();
if let Some(focus) = focus {
return dispatch_read_focused(ctx, focus);
}
let (file_path, content) = crate::cli::commands::read::validate_and_read_file(&ctx.root, path)?;
let audit_state = ctx.audit_state();
let notes = ctx.notes();
let (header, notes_injected) =
crate::cli::commands::read::build_file_note_header(path, &file_path, audit_state, ¬es);
let enriched = if header.is_empty() {
content
} else {
format!("{}{}", header, content)
};
Ok(serde_json::json!({
"path": path,
"content": enriched,
"notes_injected": notes_injected,
}))
}
fn dispatch_read_focused(ctx: &BatchContext, focus: &str) -> Result<serde_json::Value> {
let _span = tracing::info_span!("batch_read_focused", focus).entered();
let audit_state = ctx.audit_state();
let notes = ctx.notes();
let result = crate::cli::commands::read::build_focused_output(
&ctx.store(),
focus,
&ctx.root,
audit_state,
¬es,
)?;
let mut json = serde_json::json!({
"focus": focus,
"content": result.output,
});
if let Some(ref h) = result.hints {
json["hints"] = serde_json::json!({
"caller_count": h.caller_count,
"test_count": h.test_count,
"no_callers": h.caller_count == 0,
"no_tests": h.test_count == 0,
});
}
Ok(json)
}