use mcp_host::protocol::types::JsonRpcResponse;
use serde::Deserialize;
use serde_json::Value;
use std::collections::HashMap;
use std::fmt::Write;
use std::sync::{Arc, Mutex};
use super::state::ServerState;
use super::utils::{collect_files, parse_arguments};
pub fn handle_kimjongrails(
id: Value,
arguments: Option<Value>,
watcher_state: Arc<Mutex<ServerState>>,
) -> JsonRpcResponse {
#[derive(Deserialize)]
struct Args {
paths: Vec<String>,
}
let args: Args = match parse_arguments(&id, arguments) {
Ok(args) => args,
Err(response) => return *response,
};
let mut log_output = String::new();
let mut fixed_count = 0;
let mut rule_counts: HashMap<&str, usize> = HashMap::new();
let all_files: Vec<std::path::PathBuf> = args
.paths
.iter()
.map(std::path::Path::new)
.filter(|p| p.exists())
.flat_map(collect_files)
.collect();
let progress_token = {
let state = watcher_state.lock().unwrap();
let total = u32::try_from(all_files.len()).unwrap_or(u32::MAX);
state.progress_tracker.start("dictator", total)
};
for (file_idx, file) in all_files.iter().enumerate() {
{
let state = watcher_state.lock().unwrap();
let current = u32::try_from(file_idx + 1).unwrap_or(u32::MAX);
state.progress_tracker.progress(&progress_token, current);
}
let text = match std::fs::read_to_string(file) {
Ok(t) => t,
Err(e) => {
let _ = writeln!(log_output, "! Cannot read {}: {}", file.display(), e);
continue;
}
};
let (fixed, changes) = apply_fixes(&text);
if !changes.is_empty() && fixed != text {
if let Err(e) = std::fs::write(file, &fixed) {
let _ = writeln!(log_output, "! Cannot write {}: {}", file.display(), e);
} else {
fixed_count += 1;
let _ = writeln!(log_output, "* {} ({})", file.display(), changes.join(", "));
for change in &changes {
let rule = match change.as_str() {
"trailing whitespace" => "supreme/trailing-whitespace",
"final newline" => "supreme/missing-final-newline",
"CRLF->LF" => "supreme/crlf",
_ => "supreme/unknown",
};
*rule_counts.entry(rule).or_default() += 1;
}
}
}
}
{
let state = watcher_state.lock().unwrap();
state.progress_tracker.finish(&progress_token);
}
let output = build_summary(fixed_count, &rule_counts, &log_output);
JsonRpcResponse {
jsonrpc: "2.0".to_string(),
id,
result: Some(serde_json::json!({
"content": [{ "type": "text", "text": output }]
})),
error: None,
}
}
fn apply_fixes(text: &str) -> (String, Vec<String>) {
let mut fixed = text.to_string();
let mut changes = Vec::new();
let lines: Vec<&str> = fixed.lines().collect();
let trimmed: Vec<String> = lines.iter().map(|l| l.trim_end().to_string()).collect();
if lines.iter().zip(trimmed.iter()).any(|(a, b)| *a != b) {
fixed = trimmed.join("\n");
changes.push("trailing whitespace".to_string());
}
if !fixed.ends_with('\n') {
fixed.push('\n');
changes.push("final newline".to_string());
}
if fixed.contains("\r\n") {
fixed = fixed.replace("\r\n", "\n");
changes.push("CRLF->LF".to_string());
}
(fixed, changes)
}
fn build_summary(
fixed_count: usize,
rule_counts: &HashMap<&str, usize>,
log_output: &str,
) -> String {
if fixed_count == 0 {
return "No fixes needed".to_string();
}
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let log_path = format!("/tmp/dictator-fixes-{timestamp}.log");
let log_written = std::fs::write(&log_path, log_output).is_ok();
let mut summary = format!("Fixed {fixed_count} files:\n");
let mut rules: Vec<_> = rule_counts.iter().collect();
rules.sort_by_key(|(rule, _)| *rule);
for (rule, count) in rules {
let _ = writeln!(summary, " {rule}: {count}");
}
if log_written {
let _ = write!(summary, "Details: {log_path}");
}
summary
}