From 3ee95f5c390c505d8cc1bc19316d2050b9609884 Mon Sep 17 00:00:00 2001 From: Riz Ashraf Date: Sat, 10 Oct 2026 18:45:00 +0100 Subject: [PATCH] Format massive JSON arrays into Markdown natively to avoid context blowouts --- server/src/handlers/ast.rs | 94 +++++++++++++++++++++++++++++------- server/src/handlers/git.rs | 63 ++++++++++++++++++------ server/src/handlers/graph.rs | 40 +++++++++++---- server/src/handlers/meta.rs | 2 +- 4 files changed, 157 insertions(+), 42 deletions(-) diff --git a/server/src/handlers/ast.rs b/server/src/handlers/ast.rs index 53968d9..e5364e0 100644 --- a/server/src/handlers/ast.rs +++ b/server/src/handlers/ast.rs @@ -498,15 +498,37 @@ impl McpTool for FindSymbolReferencesHandler { if matches.len() < limit { let remaining = limit - matches.len(); + let target_sym_clone = target_sym.clone(); let disk_matches = tokio::task::spawn_blocking(move || { - scan_workspace_for_symbol(&target_sym, remaining, false, custom_dir) + scan_workspace_for_symbol(&target_sym_clone, remaining, false, custom_dir) }) .await .unwrap_or_default(); matches.extend(disk_matches); } - Ok(serde_yaml::to_string(&matches)?) + let mut out = String::new(); + out.push_str(&format!("## Symbol References for `{}`\n\n", target_sym)); + for match_item in &matches { + if let Some(source) = match_item.get("source").and_then(|s| s.as_str()) { + if source == "snippet" { + let name = match_item.get("name").and_then(|n| n.as_str()).unwrap_or("Unknown"); + let code = match_item.get("code").and_then(|c| c.as_str()).unwrap_or(""); + out.push_str(&format!("### Snippet: {}\n```rust\n{}\n```\n\n", name, code)); + } + } else { + let file = match_item.get("file_path").and_then(|f| f.as_str()).unwrap_or("Unknown"); + let line = match_item.get("line").and_then(|l| l.as_u64()).unwrap_or(0); + let content = match_item.get("content").and_then(|c| c.as_str()).unwrap_or(""); + out.push_str(&format!("- `{}:{}`: `{}`\n", file, line, content)); + } + } + + if matches.is_empty() { + out.push_str("No references found.\n"); + } + + Ok(out) } } @@ -554,15 +576,37 @@ impl McpTool for GetCallersHandler { if callers.len() < limit { let remaining = limit - callers.len(); + let target_fn_clone = target_fn.clone(); let disk_callers = tokio::task::spawn_blocking(move || { - scan_workspace_for_symbol(&target_fn, remaining, true, custom_dir) + scan_workspace_for_symbol(&target_fn_clone, remaining, true, custom_dir) }) .await .unwrap_or_default(); callers.extend(disk_callers); } - Ok(serde_yaml::to_string(&callers)?) + let mut out = String::new(); + out.push_str(&format!("## Callers for `{}`\n\n", target_fn)); + for caller in &callers { + if let Some(source) = caller.get("source").and_then(|s| s.as_str()) { + if source == "snippet" { + let name = caller.get("name").and_then(|n| n.as_str()).unwrap_or("Unknown"); + let code = caller.get("code").and_then(|c| c.as_str()).unwrap_or(""); + out.push_str(&format!("### Snippet: {}\n```rust\n{}\n```\n\n", name, code)); + } + } else { + let file = caller.get("file_path").and_then(|f| f.as_str()).unwrap_or("Unknown"); + let line = caller.get("line").and_then(|l| l.as_u64()).unwrap_or(0); + let content = caller.get("content").and_then(|c| c.as_str()).unwrap_or(""); + out.push_str(&format!("- `{}:{}`: `{}`\n", file, line, content)); + } + } + + if callers.is_empty() { + out.push_str("No callers found.\n"); + } + + Ok(out) } } @@ -637,21 +681,35 @@ impl McpTool for AnalyzeImpactHandler { "LOW" }; - let result = serde_json::json!({ - "target_symbol": sym, - "risk_level": risk_level, - "ast_callers_count": caller_count, - "ast_callers_sample": callers.into_iter().take(10).collect::>(), - "graph_relations_count": graph_count, - "graph_relations": kg_connected, - "recommendation": match risk_level { - "CRITICAL" | "HIGH" => "Requires comprehensive unit test verification and backwards compatibility checks before modifying.", - "MEDIUM" => "Verify direct call sites and run affected module tests.", - _ => "Safe to modify with standard unit test verification.", - } - }); + let mut out = String::new(); + out.push_str(&format!("## Impact Analysis for `{}`\n\n", sym)); + out.push_str(&format!("**Risk Level:** {}\n\n", risk_level)); + + let rec = match risk_level { + "CRITICAL" | "HIGH" => "Requires comprehensive unit test verification and backwards compatibility checks before modifying.", + "MEDIUM" => "Verify direct call sites and run affected module tests.", + _ => "Safe to modify with standard unit test verification.", + }; + out.push_str(&format!("**Recommendation:** {}\n\n", rec)); + + out.push_str(&format!("### AST Callers ({} total, showing up to 10)\n", caller_count)); + for c in callers.into_iter().take(10) { + out.push_str(&format!("- `{}`\n", c)); + } + if caller_count == 0 { + out.push_str("No callers found.\n"); + } + out.push_str("\n"); + + out.push_str(&format!("### Graph Relations ({})\n", graph_count)); + for g in kg_connected { + out.push_str(&format!("- {}\n", g)); + } + if graph_count == 0 { + out.push_str("No graph relations found.\n"); + } - Ok(serde_yaml::to_string(&result)?) + Ok(out) } } diff --git a/server/src/handlers/git.rs b/server/src/handlers/git.rs index e30910b..fa08b8d 100644 --- a/server/src/handlers/git.rs +++ b/server/src/handlers/git.rs @@ -27,7 +27,7 @@ impl McpTool for GetActiveWorktreeContextHandler { _state: Arc, ) -> crate::error::Result { let result = - tokio::task::spawn_blocking(move || -> crate::error::Result { + tokio::task::spawn_blocking(move || -> crate::error::Result { let cwd = env::current_dir().map_err(|e| crate::error::AppError::Internal(format!("Failed to get current directory: {}", e)))?; let repo = git2::Repository::discover(&cwd).map_err(|e| { crate::error::AppError::Internal(format!("Not in a git repository: {}", e)) @@ -79,16 +79,34 @@ impl McpTool for GetActiveWorktreeContextHandler { diff_output.push_str("\n... [Diff truncated due to size]"); } - Ok(json!({ - "branch": branch_name, - "modified_files": files, - "diff": diff_output - })) + let mut out = String::new(); + out.push_str("## Active Worktree Context\n\n"); + out.push_str(&format!("**Branch:** `{}`\n\n", branch_name)); + + out.push_str("### Modified Files\n"); + if files.is_empty() { + out.push_str("No modified files.\n"); + } else { + for f in files { + out.push_str(&format!("- `{}`\n", f)); + } + } + + out.push_str("\n### Diff\n"); + if diff_output.is_empty() { + out.push_str("No changes.\n"); + } else { + out.push_str("```diff\n"); + out.push_str(&diff_output); + out.push_str("\n```\n"); + } + + Ok(out) }) .await .map_err(|e| crate::error::AppError::Internal(format!("Task panic: {}", e)))??; - Ok::(serde_yaml::to_string(&result)?) + Ok(result) } } @@ -170,7 +188,25 @@ impl McpTool for QueryGitDiffsHandler { matches.extend(git_matches); } - Ok(serde_yaml::to_string(&matches)?) + let mut out = String::new(); + out.push_str(&format!("## Git Diffs for `{}`\n\n", req.query)); + for m in &matches { + if let Some(file) = m.get("file_path").and_then(|f| f.as_str()) { + let desc = m.get("description").and_then(|d| d.as_str()).unwrap_or(""); + let commit = m.get("commit").and_then(|c| c.as_str()).unwrap_or(""); + out.push_str(&format!("- **File:** `{}` | **Commit:** `{}`\n - {}\n", file, commit, desc)); + } else { + let commit = m.get("commit_id").and_then(|c| c.as_str()).unwrap_or(""); + let author = m.get("author").and_then(|a| a.as_str()).unwrap_or(""); + let msg = m.get("message").and_then(|m| m.as_str()).unwrap_or(""); + out.push_str(&format!("- **Commit:** `{}` | **Author:** {}\n - {}\n", commit, author, msg)); + } + } + if matches.is_empty() { + out.push_str("No git diffs found.\n"); + } + + Ok(out) } } @@ -193,10 +229,10 @@ mod tests { .map_err(|e| format!("Failed to get worktree context: {}", e)) .unwrap(); - let parsed: serde_json::Value = serde_yaml::from_str(&result).unwrap(); - assert!(parsed.get("branch").is_some()); - assert!(parsed.get("modified_files").is_some()); - assert!(parsed.get("diff").is_some()); + assert!(result.contains("## Active Worktree Context")); + assert!(result.contains("**Branch:**")); + assert!(result.contains("### Modified Files")); + assert!(result.contains("### Diff")); } #[tokio::test] @@ -212,7 +248,6 @@ mod tests { .map_err(|e| format!("Failed to get worktree context: {}", e)) .unwrap(); - let parsed: serde_json::Value = serde_yaml::from_str(&result).unwrap(); - assert!(parsed.get("branch").is_some() || parsed.is_object()); + assert!(result.contains("## Active Worktree Context")); } } diff --git a/server/src/handlers/graph.rs b/server/src/handlers/graph.rs index e8de50c..08df5f2 100644 --- a/server/src/handlers/graph.rs +++ b/server/src/handlers/graph.rs @@ -1279,16 +1279,38 @@ impl McpTool for SweepGraphHealthHandler { } }); - let report = serde_json::json!({ - "orphaned_entities": orphans, - "orphans_pruned": auto_prune, - "stale_entities": stale_entities, - "stale_pruned": auto_prune_stale, - "potential_duplicates": duplicates, - "health_score": if orphans.is_empty() && duplicates.is_empty() && stale_entities.is_empty() { "100%" } else { "Needs Maintenance" } - }); + let mut out = String::new(); + out.push_str("## Graph Health Report\n\n"); + let score = if orphans.is_empty() && duplicates.is_empty() && stale_entities.is_empty() { "100% (Healthy)" } else { "Needs Maintenance" }; + out.push_str(&format!("**Health Status:** {}\n\n", score)); - Ok(serde_yaml::to_string(&report)?) + out.push_str(&format!("### Orphaned Entities ({})\n", orphans.len())); + if auto_prune { + out.push_str("*These orphans were automatically pruned.*\n"); + } + for o in orphans { + out.push_str(&format!("- `{}`\n", o)); + } + out.push_str("\n"); + + out.push_str(&format!("### Stale Entities ({})\n", stale_entities.len())); + if auto_prune_stale { + out.push_str("*These stale entities were automatically pruned.*\n"); + } + for s in stale_entities { + out.push_str(&format!("- `{}`\n", s)); + } + out.push_str("\n"); + + out.push_str(&format!("### Potential Duplicates ({})\n", duplicates.len())); + for d in duplicates { + let a = d.get("entity_a").and_then(|x| x.as_str()).unwrap_or("?"); + let b = d.get("entity_b").and_then(|x| x.as_str()).unwrap_or("?"); + let act = d.get("suggested_action").and_then(|x| x.as_str()).unwrap_or(""); + out.push_str(&format!("- `{}` and `{}`\n - Suggested: `{}`\n", a, b, act)); + } + + Ok(out) } } diff --git a/server/src/handlers/meta.rs b/server/src/handlers/meta.rs index ad2f7a3..e4c7f4d 100644 --- a/server/src/handlers/meta.rs +++ b/server/src/handlers/meta.rs @@ -2336,7 +2336,7 @@ mod tests { .execute(serde_json::json!({}), state.clone()) .await .unwrap(); - assert!(sweep_h_res.contains("orphaned_entities")); + assert!(sweep_h_res.contains("Orphaned Entities")); // AutoSessionCheckpoint let chk = AutoSessionCheckpointHandler;