use super::params::*;
use crate::search::{list_files, search_code};
use crate::types::{FileInfo, SearchOptions, SearchResult};
use crate::{circular, complexity, deadcode, duplicates, find, health};
use rmcp::handler::server::wrapper::{Json, Parameters};
use std::path::PathBuf;
pub async fn search_code_tool(params: Parameters<SearchCodeParams>) -> Json<Vec<SearchResult>> {
let params = params.0;
let path_buf = PathBuf::from(params.path.as_deref().unwrap_or("."));
let options = SearchOptions {
extensions: params.extensions,
ignore_case: params.ignore_case.unwrap_or(false),
fuzzy: params.fuzzy.unwrap_or(false),
fuzzy_threshold: params.fuzzy_threshold.unwrap_or(0.6),
max_results: params.max_results.unwrap_or(10),
exclude: params.exclude,
rank: params.rank.unwrap_or(false),
cache: false,
semantic: false,
benchmark: false,
vs_grep: false,
};
Json(search_code(¶ms.query, &path_buf, &options).unwrap_or_default())
}
pub async fn list_files_tool(params: Parameters<ListFilesParams>) -> Json<Vec<FileInfo>> {
let params = params.0;
let path_buf = PathBuf::from(params.path.as_deref().unwrap_or("."));
Json(
list_files(
&path_buf,
params.extensions.as_deref(),
params.exclude.as_deref(),
)
.unwrap_or_default(),
)
}
pub async fn analyze_codebase_tool(
params: Parameters<AnalyzeCodebaseParams>,
) -> Json<serde_json::Value> {
let params = params.0;
let path_buf = PathBuf::from(params.path.as_deref().unwrap_or("."));
let files = list_files(
&path_buf,
params.extensions.as_deref(),
params.exclude.as_deref(),
)
.unwrap_or_default();
if files.is_empty() {
return Json(serde_json::json!({
"message": "No files found to analyze",
"files": []
}));
}
let total_files = files.len();
let total_lines: usize = files.iter().map(|f| f.lines).sum();
let total_size: u64 = files.iter().map(|f| f.size).sum();
let mut ext_counts: std::collections::HashMap<String, (usize, usize, u64)> =
std::collections::HashMap::new();
for file in &files {
let ext = if let Some(ext) = std::path::Path::new(&file.path).extension() {
ext.to_string_lossy().to_string()
} else {
"no extension".to_string()
};
let entry = ext_counts.entry(ext).or_insert((0, 0, 0));
entry.0 += 1;
entry.1 += file.lines;
entry.2 += file.size;
}
let patterns = vec![
("functions", r"fn\s+\w+|function\s+\w+|def\s+\w+"),
("classes", r"class\s+\w+"),
("comments", r"//|#|/\*|<!--"),
("todo", r"TODO|FIXME|HACK|XXX"),
("imports", r"^import|^use|^#include|^require"),
];
let mut pattern_counts = std::collections::HashMap::new();
for (name, pattern) in patterns {
if let Ok(regex) = regex::Regex::new(pattern) {
let mut total_matches = 0;
for file in &files {
if let Ok(content) = std::fs::read_to_string(&file.path) {
for line in content.lines() {
if regex.is_match(line) {
total_matches += 1;
}
}
}
}
pattern_counts.insert(name, total_matches);
}
}
let mut file_vec = files.clone();
file_vec.sort_by(|a, b| b.lines.cmp(&a.lines));
let largest_files: Vec<_> = file_vec
.iter()
.take(5)
.map(|f| {
serde_json::json!({
"path": f.path,
"lines": f.lines,
"size": f.size
})
})
.collect();
Json(serde_json::json!({
"total_files": total_files,
"total_lines": total_lines,
"total_size": total_size,
"file_types": ext_counts.iter().map(|(ext, (count, lines, size))| {
serde_json::json!({
"extension": ext,
"files": count,
"lines": lines,
"size": size
})
}).collect::<Vec<_>>(),
"patterns": pattern_counts,
"largest_files": largest_files
}))
}
pub async fn detect_complexity_tool(
params: Parameters<ComplexityParams>,
) -> Json<serde_json::Value> {
let params = params.0;
let path_buf = PathBuf::from(params.path.as_deref().unwrap_or("."));
let metrics = complexity::calculate_complexity(
&path_buf,
params.extensions.as_deref(),
params.exclude.as_deref(),
)
.unwrap_or_default();
let mut filtered_metrics = metrics;
if let Some(threshold) = params.threshold {
filtered_metrics.retain(|m| {
m.cyclomatic_complexity >= threshold || m.cognitive_complexity >= threshold
});
}
if params.sort.unwrap_or(false) {
filtered_metrics.sort_by(|a, b| {
b.cyclomatic_complexity
.cmp(&a.cyclomatic_complexity)
.then(b.cognitive_complexity.cmp(&a.cognitive_complexity))
});
}
Json(serde_json::json!({
"metrics": filtered_metrics,
"total_files": filtered_metrics.len()
}))
}
pub async fn detect_duplicates_tool(
params: Parameters<DuplicatesParams>,
) -> Json<serde_json::Value> {
let params = params.0;
let path_buf = PathBuf::from(params.path.as_deref().unwrap_or("."));
let duplicates = duplicates::find_duplicates(
&path_buf,
params.extensions.as_deref(),
params.exclude.as_deref(),
params.min_lines.unwrap_or(5),
params.similarity.unwrap_or(0.9),
)
.unwrap_or_default();
Json(serde_json::json!({
"duplicates": duplicates,
"total_duplicates": duplicates.len()
}))
}
pub async fn detect_deadcode_tool(params: Parameters<DeadcodeParams>) -> Json<serde_json::Value> {
let params = params.0;
let path_buf = PathBuf::from(params.path.as_deref().unwrap_or("."));
let dead_code = deadcode::find_dead_code(
&path_buf,
params.extensions.as_deref(),
params.exclude.as_deref(),
)
.unwrap_or_default();
Json(serde_json::json!({
"dead_code": dead_code,
"total_items": dead_code.len()
}))
}
pub async fn detect_circular_tool(params: Parameters<CircularParams>) -> Json<serde_json::Value> {
let params = params.0;
let path_buf = PathBuf::from(params.path.as_deref().unwrap_or("."));
let cycles = circular::find_circular_calls(
&path_buf,
params.extensions.as_deref(),
params.exclude.as_deref(),
)
.unwrap_or_default();
Json(serde_json::json!({
"cycles": cycles.iter().map(|c| serde_json::json!({
"chain": c.chain,
"files": c.files
})).collect::<Vec<_>>(),
"total_cycles": cycles.len()
}))
}
pub async fn find_symbol_tool(params: Parameters<FindSymbolParams>) -> Json<serde_json::Value> {
let params = params.0;
let path_buf = PathBuf::from(params.path.as_deref().unwrap_or("."));
let find_type = find::FindType::from_str(params.find_type.as_deref().unwrap_or("all"));
let report = find::find_symbol(
¶ms.symbol,
&path_buf,
params.extensions.as_deref(),
params.exclude.as_deref(),
find_type,
)
.unwrap_or_else(|_| find::FindReport {
symbol: params.symbol.clone(),
definitions: vec![],
references: vec![],
callers: vec![],
});
Json(serde_json::json!({
"symbol": report.symbol,
"definitions": report.definitions,
"references": report.references,
"callers": report.callers
}))
}
pub async fn get_health_tool(params: Parameters<GetHealthParams>) -> Json<serde_json::Value> {
let params = params.0;
let path_buf = PathBuf::from(params.path.as_deref().unwrap_or("."));
let report = health::scan_health(
&path_buf,
params.extensions.as_deref(),
params.exclude.as_deref(),
)
.unwrap_or_else(|_| health::HealthReport {
score: 0,
dead_code_count: 0,
duplicate_count: 0,
complex_files_count: 0,
total_files: 0,
details: health::HealthDetails {
dead_code_penalty: 0,
duplicate_penalty: 0,
complexity_penalty: 0,
},
});
Json(serde_json::json!({
"score": report.score,
"dead_code_count": report.dead_code_count,
"duplicate_count": report.duplicate_count,
"complex_files_count": report.complex_files_count,
"total_files": report.total_files,
"details": {
"dead_code_penalty": report.details.dead_code_penalty,
"duplicate_penalty": report.details.duplicate_penalty,
"complexity_penalty": report.details.complexity_penalty
}
}))
}