use super::helpers::wrap_with_meta;
use super::protocol::JsonRpcError;
use crate::cli::registry::ProjectRegistry;
use serde_json::Value;
use std::sync::Arc;
#[derive(Clone)]
pub struct DiagnosticsHandler;
impl DiagnosticsHandler {
pub fn name(&self) -> &str {
"leindex.diagnostics"
}
pub fn title(&self) -> &str {
"LeIndex [Diagnostics]"
}
pub fn description(&self) -> &str {
"Get diagnostic information about the indexed project, including memory usage, index statistics, and system health."
}
pub fn argument_schema(&self) -> Value {
serde_json::json!({
"type": "object",
"properties": {
"project_path": {
"type": "string",
"description": "Project directory (omit to use current project)"
}
},
"required": []
})
}
pub async fn execute(
&self,
registry: &Arc<ProjectRegistry>,
args: Value,
) -> Result<Value, JsonRpcError> {
let project_path = args.get("project_path").and_then(|v| v.as_str());
let handle = registry.get_or_create(project_path).await?;
let mut guard = handle.write().await;
let diagnostics = guard.get_diagnostics().map_err(|e| {
JsonRpcError::internal_error(format!("Failed to get diagnostics: {}", e))
})?;
let stale_fast = guard.is_stale_fast();
let (changed, deleted) = if stale_fast {
guard.check_freshness().unwrap_or_else(|_| (vec![], vec![]))
} else {
(vec![], vec![])
};
let storage_path = guard.storage_path().display().to_string();
let db_size = std::fs::metadata(guard.storage_path().join("leindex.db"))
.map(|m| m.len())
.unwrap_or(0);
let coverage = guard.coverage_report().ok();
let indexed_files_ct = coverage
.as_ref()
.map(|c| c.indexed_files)
.unwrap_or(diagnostics.stats.files_parsed);
let symbol_count = diagnostics.stats.indexed_nodes;
let memory_rss_mb =
(diagnostics.memory_usage_bytes as f64 / 1024.0 / 1024.0 * 100.0).round() / 100.0;
let size_mb = diagnostics.memory_usage_bytes as f64 / 1024.0 / 1024.0;
let failed_parses = diagnostics.stats.failed_parses;
let index_health = diagnostics.index_health.clone();
let is_stale = !changed.is_empty() || !deleted.is_empty();
let stale_bool = if stale_fast { is_stale } else { false };
let pdg_nodes = diagnostics.pdg_nodes;
let pdg_edges = diagnostics.pdg_edges;
let embedding_model = diagnostics.embedding_model.clone();
let pdg_loaded = diagnostics.pdg_loaded;
let search_index_nodes = diagnostics.search_index_nodes;
let total_signatures = diagnostics.stats.total_signatures;
let indexed_nodes = diagnostics.stats.indexed_nodes;
let files_parsed = diagnostics.stats.files_parsed;
let indexing_time_ms = diagnostics.stats.indexing_time_ms;
let mut diag_json = serde_json::to_value(diagnostics)
.map_err(|e| JsonRpcError::internal_error(format!("Serialization error: {}", e)))?;
let (ort_path, ort_version, execution_provider) =
crate::cli::cli::collect_ort_diagnostics();
if let Value::Object(ref mut map) = diag_json {
map.insert("storage_path".to_string(), serde_json::json!(storage_path));
map.insert("db_size_bytes".to_string(), serde_json::json!(db_size));
map.insert("ort_path".to_string(), serde_json::json!(ort_path));
map.insert("ort_version".to_string(), serde_json::json!(ort_version));
map.insert(
"execution_provider".to_string(),
serde_json::json!(execution_provider),
);
map.insert(
"memory_rss_mb".to_string(),
serde_json::json!(memory_rss_mb),
);
map.insert(
"indexed_files".to_string(),
serde_json::json!(indexed_files_ct),
);
map.insert("symbol_count".to_string(), serde_json::json!(symbol_count));
map.insert("index_size_mb".to_string(), serde_json::json!(size_mb));
map.insert("stale".to_string(), serde_json::json!(stale_bool));
map.insert(
"system_health".to_string(),
serde_json::json!({
"index_health": index_health,
"pdg_loaded": pdg_loaded,
"pdg_nodes": pdg_nodes,
"pdg_edges": pdg_edges,
"search_index_nodes": search_index_nodes,
"embedding_model": embedding_model,
"total_signatures": total_signatures,
"indexed_nodes": indexed_nodes,
"files_parsed": files_parsed,
"failed_parses": failed_parses,
"indexing_time_ms": indexing_time_ms,
}),
);
let lm = std::fs::metadata(guard.storage_path().join("leindex.db"))
.and_then(|m| m.modified())
.ok();
let secs_ago = lm.and_then(|t| {
std::time::SystemTime::now()
.duration_since(t)
.ok()
.map(|d| d.as_secs())
});
map.insert(
"last_indexed_secs_ago".to_string(),
serde_json::json!(secs_ago),
);
let mut issues: Vec<Value> = Vec::new();
if failed_parses > 0 {
issues.push(serde_json::json!({
"severity": "warning",
"message": format!("{} files failed to parse", failed_parses),
}));
}
if stale_bool {
issues.push(serde_json::json!({
"severity": "warning",
"message": "Index may be stale. Call LeIndex [Index] with force_reindex=true for fresh results.",
}));
}
map.insert("issues".to_string(), serde_json::json!(issues));
let staleness = if !stale_bool {
serde_json::json!({
"status": "fresh",
"changed_files": 0,
"deleted_files": 0,
})
} else {
serde_json::json!({
"status": "stale",
"changed_files": changed.len(),
"deleted_files": deleted.len(),
"changed_sample": changed.iter().take(10).map(|p| p.display().to_string()).collect::<Vec<_>>(),
"deleted_sample": deleted.iter().take(10).cloned().collect::<Vec<_>>(),
"suggestion": "Call LeIndex [Index] with force_reindex=true to refresh",
})
};
map.insert("freshness".to_string(), staleness);
if let Some(cov) = coverage {
map.insert(
"coverage".to_string(),
serde_json::to_value(cov).unwrap_or(Value::Null),
);
}
}
Ok(wrap_with_meta(diag_json, &guard))
}
}