use std::path::PathBuf;
use std::time::Instant;
use anyhow::{Context, Result};
use sqry_core::graph::unified::build::BuildConfig;
use sqry_core::graph::unified::persistence::{GraphStorage, Manifest, load_header_from_path};
use sqry_plugin_registry::create_plugin_manager;
use crate::engine::{canonicalize_in_workspace, engine_for_workspace};
use crate::tools::{GetIndexStatusArgs, RebuildIndexArgs};
use crate::execution::types::{IndexStatusData, RebuildIndexData, ToolExecution};
use crate::execution::utils::duration_to_ms;
fn resolve_workspace_path(path: &str) -> Option<PathBuf> {
if path == "." {
None
} else {
Some(PathBuf::from(path))
}
}
pub fn execute_index_status(args: &GetIndexStatusArgs) -> Result<ToolExecution<IndexStatusData>> {
let start = Instant::now();
let workspace_path = resolve_workspace_path(&args.path);
let engine = engine_for_workspace(workspace_path.as_ref())?;
let workspace_root = engine.workspace_root().to_path_buf();
let target = canonicalize_in_workspace(&args.path, &workspace_root)?;
let mut candidate_roots = Vec::new();
if target.is_dir() {
candidate_roots.push(target.clone());
} else if let Some(parent) = target.parent() {
candidate_roots.push(parent.to_path_buf());
}
if !candidate_roots
.iter()
.any(|p| p == workspace_root.as_path())
{
candidate_roots.push(workspace_root.clone());
}
tracing::debug!(path = %args.path, "Executing index_status tool");
let mut graph_state: Option<(PathBuf, Manifest)> = None;
for root in candidate_roots {
let storage = GraphStorage::new(&root);
if !storage.exists() {
continue;
}
if let Ok(manifest) = storage.load_manifest() {
graph_state = Some((root, manifest));
break;
}
}
let (data, used_graph_flag) = match graph_state {
Some((root, manifest)) => {
let storage = GraphStorage::new(&root);
let files_indexed: Option<u64> =
if let Ok(header) = load_header_from_path(storage.snapshot_path()) {
header.file_count.try_into().ok()
} else if !manifest.file_count.is_empty() {
manifest.file_count.values().sum::<usize>().try_into().ok()
} else {
None
};
(
IndexStatusData {
has_index: true,
root_path: Some(crate::execution::symbol_utils::path_to_forward_slash(&root)),
indexed_symbols: manifest.node_count.try_into().ok(),
files_indexed,
index_version: Some(format!(
"{}.{}",
manifest.schema_version, manifest.snapshot_format_version
)),
created_at: Some(manifest.built_at.clone()),
updated_at: Some(manifest.built_at),
has_relations: Some(manifest.edge_count > 0),
},
true,
)
}
None => (
IndexStatusData {
has_index: false,
root_path: Some(crate::execution::symbol_utils::path_to_forward_slash(
&target,
)),
indexed_symbols: None,
files_indexed: None,
index_version: None,
created_at: None,
updated_at: None,
has_relations: None,
},
false,
),
};
tracing::debug!(has_index = data.has_index, "index_status tool completed");
Ok(ToolExecution {
data,
used_index: false,
used_graph: used_graph_flag,
graph_metadata: None,
execution_ms: duration_to_ms(start.elapsed()),
next_page_token: None,
total: Some(1),
truncated: Some(false),
candidates_scanned: None,
workspace_path: crate::execution::symbol_utils::path_to_forward_slash(
engine.workspace_root(),
),
})
}
#[allow(clippy::too_many_lines)]
pub fn execute_rebuild_index(args: &RebuildIndexArgs) -> Result<ToolExecution<RebuildIndexData>> {
let start = Instant::now();
let workspace_path = resolve_workspace_path(&args.path);
let engine = engine_for_workspace(workspace_path.as_ref())?;
let workspace_root = engine.workspace_root().to_path_buf();
let target = canonicalize_in_workspace(&args.path, &workspace_root)?;
let root_path = if target.is_dir() {
target.clone()
} else if let Some(parent) = target.parent() {
parent.to_path_buf()
} else {
workspace_root.clone()
};
tracing::info!(path = %root_path.display(), force = args.force, "Executing rebuild_index tool");
let storage = GraphStorage::new(&root_path);
if storage.exists() && !args.force {
let manifest = storage
.load_manifest()
.context("Index exists but manifest is unreadable")?;
let files_indexed: u64 = if let Ok(header) = load_header_from_path(storage.snapshot_path())
{
header.file_count.try_into().unwrap_or(0)
} else if !manifest.file_count.is_empty() {
manifest
.file_count
.values()
.sum::<usize>()
.try_into()
.unwrap_or(0)
} else {
0
};
return Ok(ToolExecution {
data: RebuildIndexData {
success: true,
root_path: crate::execution::symbol_utils::path_to_forward_slash(&root_path),
node_count: manifest.node_count.try_into().unwrap_or(0),
edge_count: manifest.edge_count.try_into().unwrap_or(0),
files_indexed,
built_at: manifest.built_at,
message: Some("Index already exists. Use force=true to rebuild.".to_string()),
},
used_index: false,
used_graph: true,
graph_metadata: None,
execution_ms: duration_to_ms(start.elapsed()),
next_page_token: None,
total: Some(1),
truncated: Some(false),
candidates_scanned: None,
workspace_path: crate::execution::symbol_utils::path_to_forward_slash(
engine.workspace_root(),
),
});
}
let plugins = create_plugin_manager();
let build_config = BuildConfig::default();
let (_graph, build_result) = sqry_core::graph::unified::build::build_and_persist_graph(
&root_path,
&plugins,
&build_config,
"mcp:rebuild_index",
)
.context("Failed to build and persist unified graph")?;
let node_count: u64 = build_result.node_count.try_into().unwrap_or(0);
let edge_count: u64 = build_result.edge_count.try_into().unwrap_or(0);
let files_indexed: u64 = build_result.total_files.try_into().unwrap_or(0);
tracing::info!(
node_count,
edge_count,
"rebuild_index tool completed successfully"
);
Ok(ToolExecution {
data: RebuildIndexData {
success: true,
root_path: crate::execution::symbol_utils::path_to_forward_slash(&root_path),
node_count,
edge_count,
files_indexed,
built_at: build_result.built_at,
message: Some("Index rebuilt successfully.".to_string()),
},
used_index: false,
used_graph: true,
graph_metadata: None,
execution_ms: duration_to_ms(start.elapsed()),
next_page_token: None,
total: Some(1),
truncated: Some(false),
candidates_scanned: None,
workspace_path: crate::execution::symbol_utils::path_to_forward_slash(
engine.workspace_root(),
),
})
}