1use std::collections::HashMap;
8use std::path::Path;
9
10use serde_json::{json, Value};
11
12use crate::engine::query::{ImpactParams, MatchMode, SymbolLookupParams};
13use crate::engine::QueryEngine;
14use crate::format::reader::AcbReader;
15use crate::graph::CodeGraph;
16use crate::grounding::{Grounded, GroundingEngine, GroundingResult};
17use crate::types::{CodeUnitType, EdgeType};
18use crate::workspace::{ContextRole, TranslationMap, TranslationStatus, WorkspaceManager};
19
20use super::protocol::{JsonRpcError, JsonRpcRequest, JsonRpcResponse};
21
22const SERVER_NAME: &str = "agentic-codebase";
24const SERVER_VERSION: &str = "0.1.0";
26const PROTOCOL_VERSION: &str = "2024-11-05";
28
29#[derive(Debug, Clone)]
31pub struct OperationRecord {
32 pub tool_name: String,
33 pub summary: String,
34 pub timestamp: u64,
35 pub graph_name: Option<String>,
36}
37
38#[derive(Debug)]
43pub struct McpServer {
44 graphs: HashMap<String, CodeGraph>,
46 engine: QueryEngine,
48 initialized: bool,
50 operation_log: Vec<OperationRecord>,
52 session_start_time: Option<u64>,
54 workspace_manager: WorkspaceManager,
56 translation_maps: HashMap<String, TranslationMap>,
58 deferred_graph: Option<(String, String)>,
60}
61
62impl McpServer {
63 fn parse_unit_type(raw: &str) -> Option<CodeUnitType> {
64 match raw.trim().to_ascii_lowercase().as_str() {
65 "module" | "modules" => Some(CodeUnitType::Module),
66 "symbol" | "symbols" => Some(CodeUnitType::Symbol),
67 "type" | "types" => Some(CodeUnitType::Type),
68 "function" | "functions" => Some(CodeUnitType::Function),
69 "parameter" | "parameters" => Some(CodeUnitType::Parameter),
70 "import" | "imports" => Some(CodeUnitType::Import),
71 "test" | "tests" => Some(CodeUnitType::Test),
72 "doc" | "docs" | "document" | "documents" => Some(CodeUnitType::Doc),
73 "config" | "configs" => Some(CodeUnitType::Config),
74 "pattern" | "patterns" => Some(CodeUnitType::Pattern),
75 "trait" | "traits" => Some(CodeUnitType::Trait),
76 "impl" | "implementation" | "implementations" => Some(CodeUnitType::Impl),
77 "macro" | "macros" => Some(CodeUnitType::Macro),
78 _ => None,
79 }
80 }
81
82 pub fn new() -> Self {
84 Self {
85 graphs: HashMap::new(),
86 engine: QueryEngine::new(),
87 initialized: false,
88 operation_log: Vec::new(),
89 session_start_time: None,
90 workspace_manager: WorkspaceManager::new(),
91 translation_maps: HashMap::new(),
92 deferred_graph: None,
93 }
94 }
95
96 pub fn load_graph(&mut self, name: String, graph: CodeGraph) {
98 self.graphs.insert(name, graph);
99 }
100
101 pub fn set_deferred_graph(&mut self, name: String, path: String) {
107 self.deferred_graph = Some((name, path));
108 }
109
110 fn try_lazy_load(&mut self) {
113 if let Some((name, path)) = self.deferred_graph.take() {
114 match AcbReader::read_from_file(Path::new(&path)) {
115 Ok(graph) => {
116 self.graphs.insert(name, graph);
117 }
118 Err(_) => {
119 self.deferred_graph = Some((name, path));
121 }
122 }
123 }
124 }
125
126 pub fn unload_graph(&mut self, name: &str) -> Option<CodeGraph> {
128 self.graphs.remove(name)
129 }
130
131 pub fn get_graph(&self, name: &str) -> Option<&CodeGraph> {
133 self.graphs.get(name)
134 }
135
136 pub fn graph_names(&self) -> Vec<&str> {
138 self.graphs.keys().map(|s| s.as_str()).collect()
139 }
140
141 pub fn is_initialized(&self) -> bool {
143 self.initialized
144 }
145
146 pub fn handle_raw(&mut self, raw: &str) -> String {
151 let response = match super::protocol::parse_request(raw) {
152 Ok(request) => {
153 if request.id.is_none() {
154 self.handle_notification(&request.method, &request.params);
155 return String::new();
156 }
157 self.handle_request(request)
158 }
159 Err(error_response) => error_response,
160 };
161 serde_json::to_string(&response).unwrap_or_else(|_| {
162 r#"{"jsonrpc":"2.0","id":null,"error":{"code":-32603,"message":"Serialization failed"}}"#
163 .to_string()
164 })
165 }
166
167 pub fn handle_request(&mut self, request: JsonRpcRequest) -> JsonRpcResponse {
169 let id = request.id.clone().unwrap_or(Value::Null);
170 match request.method.as_str() {
171 "initialize" => self.handle_initialize(id, &request.params),
172 "shutdown" => self.handle_shutdown(id),
173 "tools/list" => self.handle_tools_list(id),
174 "tools/call" => self.handle_tools_call(id, &request.params),
175 "resources/list" => self.handle_resources_list(id),
176 "resources/read" => self.handle_resources_read(id, &request.params),
177 "prompts/list" => self.handle_prompts_list(id),
178 _ => JsonRpcResponse::error(id, JsonRpcError::method_not_found(&request.method)),
179 }
180 }
181
182 fn handle_notification(&mut self, method: &str, _params: &Value) {
186 if method == "notifications/initialized" {
187 self.initialized = true;
188 }
189 }
190
191 fn handle_initialize(&mut self, id: Value, _params: &Value) -> JsonRpcResponse {
197 self.initialized = true;
198 self.session_start_time = Some(
199 std::time::SystemTime::now()
200 .duration_since(std::time::UNIX_EPOCH)
201 .unwrap_or_default()
202 .as_secs(),
203 );
204 self.operation_log.clear();
205 JsonRpcResponse::success(
206 id,
207 json!({
208 "protocolVersion": PROTOCOL_VERSION,
209 "capabilities": {
210 "tools": { "listChanged": false },
211 "resources": { "subscribe": false, "listChanged": false },
212 "prompts": { "listChanged": false }
213 },
214 "serverInfo": {
215 "name": SERVER_NAME,
216 "version": SERVER_VERSION
217 }
218 }),
219 )
220 }
221
222 fn handle_shutdown(&mut self, id: Value) -> JsonRpcResponse {
224 self.initialized = false;
225 JsonRpcResponse::success(id, json!(null))
226 }
227
228 fn handle_tools_list(&self, id: Value) -> JsonRpcResponse {
230 JsonRpcResponse::success(
231 id,
232 json!({
233 "tools": [
234 {
235 "name": "symbol_lookup",
236 "description": "Look up symbols by name in the code graph",
237 "inputSchema": {
238 "type": "object",
239 "properties": {
240 "graph": { "type": "string", "description": "Graph name" },
241 "name": { "type": "string", "description": "Symbol name to search for" },
242 "mode": { "type": "string", "enum": ["exact", "prefix", "contains", "fuzzy"], "default": "prefix" },
243 "limit": { "type": "integer", "minimum": 1, "default": 10 }
244 },
245 "required": ["name"]
246 }
247 },
248 {
249 "name": "impact_analysis",
250 "description": "Analyse the impact of changing a code unit",
251 "inputSchema": {
252 "type": "object",
253 "properties": {
254 "graph": { "type": "string", "description": "Graph name" },
255 "unit_id": { "type": "integer", "description": "Code unit ID to analyse" },
256 "max_depth": { "type": "integer", "minimum": 0, "default": 3 }
257 },
258 "required": ["unit_id"]
259 }
260 },
261 {
262 "name": "graph_stats",
263 "description": "Get summary statistics about a loaded code graph",
264 "inputSchema": {
265 "type": "object",
266 "properties": {
267 "graph": { "type": "string", "description": "Graph name" }
268 }
269 }
270 },
271 {
272 "name": "list_units",
273 "description": "List code units in a graph, optionally filtered by type",
274 "inputSchema": {
275 "type": "object",
276 "properties": {
277 "graph": { "type": "string", "description": "Graph name" },
278 "unit_type": {
279 "type": "string",
280 "description": "Filter by unit type",
281 "enum": [
282 "module", "symbol", "type", "function", "parameter", "import",
283 "test", "doc", "config", "pattern", "trait", "impl", "macro"
284 ]
285 },
286 "limit": { "type": "integer", "default": 50 }
287 }
288 }
289 },
290 {
291 "name": "analysis_log",
292 "description": "Log the intent and context behind a code analysis. Call this to record WHY you are performing a lookup or analysis",
293 "inputSchema": {
294 "type": "object",
295 "properties": {
296 "intent": {
297 "type": "string",
298 "description": "Why you are analysing — the goal or reason for the code query"
299 },
300 "finding": {
301 "type": "string",
302 "description": "What you found or concluded from the analysis"
303 },
304 "graph": {
305 "type": "string",
306 "description": "Optional graph name this analysis relates to"
307 },
308 "topic": {
309 "type": "string",
310 "description": "Optional topic or category (e.g., 'refactoring', 'bug-hunt')"
311 }
312 },
313 "required": ["intent"]
314 }
315 },
316 {
318 "name": "codebase_ground",
319 "description": "Verify a claim about code has graph evidence. Use before asserting code exists",
320 "inputSchema": {
321 "type": "object",
322 "properties": {
323 "claim": { "type": "string", "description": "The claim to verify (e.g., 'function validate_token exists')" },
324 "graph": { "type": "string", "description": "Graph name" },
325 "strict": { "type": "boolean", "description": "If true, partial matches return Ungrounded (default: false)", "default": false }
326 },
327 "required": ["claim"]
328 }
329 },
330 {
331 "name": "codebase_evidence",
332 "description": "Get graph evidence for a symbol name",
333 "inputSchema": {
334 "type": "object",
335 "properties": {
336 "name": { "type": "string", "description": "Symbol name to find" },
337 "graph": { "type": "string", "description": "Graph name" },
338 "types": {
339 "type": "array",
340 "items": { "type": "string" },
341 "description": "Filter by type: function, struct, enum, module, trait (optional)"
342 }
343 },
344 "required": ["name"]
345 }
346 },
347 {
348 "name": "codebase_suggest",
349 "description": "Find symbols similar to a name (for corrections)",
350 "inputSchema": {
351 "type": "object",
352 "properties": {
353 "name": { "type": "string", "description": "Name to find similar matches for" },
354 "graph": { "type": "string", "description": "Graph name" },
355 "limit": { "type": "integer", "minimum": 1, "default": 5, "description": "Max suggestions (default: 5)" }
356 },
357 "required": ["name"]
358 }
359 },
360 {
362 "name": "workspace_create",
363 "description": "Create a workspace to load multiple codebases",
364 "inputSchema": {
365 "type": "object",
366 "properties": {
367 "name": { "type": "string", "description": "Workspace name (e.g., 'cpp-to-rust-migration')" }
368 },
369 "required": ["name"]
370 }
371 },
372 {
373 "name": "workspace_add",
374 "description": "Add a codebase to an existing workspace",
375 "inputSchema": {
376 "type": "object",
377 "properties": {
378 "workspace": { "type": "string", "description": "Workspace name or id" },
379 "graph": { "type": "string", "description": "Name of a loaded graph to add" },
380 "path": { "type": "string", "description": "Path label for this codebase" },
381 "role": { "type": "string", "enum": ["source", "target", "reference", "comparison"], "description": "Role of this codebase" },
382 "language": { "type": "string", "description": "Optional language hint" }
383 },
384 "required": ["workspace", "graph", "role"]
385 }
386 },
387 {
388 "name": "workspace_list",
389 "description": "List all contexts in a workspace",
390 "inputSchema": {
391 "type": "object",
392 "properties": {
393 "workspace": { "type": "string", "description": "Workspace name or id" }
394 },
395 "required": ["workspace"]
396 }
397 },
398 {
399 "name": "workspace_query",
400 "description": "Search across all codebases in workspace",
401 "inputSchema": {
402 "type": "object",
403 "properties": {
404 "workspace": { "type": "string", "description": "Workspace name or id" },
405 "query": { "type": "string", "description": "Search query" },
406 "roles": { "type": "array", "items": { "type": "string" }, "description": "Filter by role (optional)" }
407 },
408 "required": ["workspace", "query"]
409 }
410 },
411 {
412 "name": "workspace_compare",
413 "description": "Compare a symbol between source and target",
414 "inputSchema": {
415 "type": "object",
416 "properties": {
417 "workspace": { "type": "string", "description": "Workspace name or id" },
418 "symbol": { "type": "string", "description": "Symbol to compare" }
419 },
420 "required": ["workspace", "symbol"]
421 }
422 },
423 {
424 "name": "workspace_xref",
425 "description": "Find where symbol exists/doesn't exist across contexts",
426 "inputSchema": {
427 "type": "object",
428 "properties": {
429 "workspace": { "type": "string", "description": "Workspace name or id" },
430 "symbol": { "type": "string", "description": "Symbol to find" }
431 },
432 "required": ["workspace", "symbol"]
433 }
434 },
435 {
437 "name": "translation_record",
438 "description": "Record source→target symbol mapping",
439 "inputSchema": {
440 "type": "object",
441 "properties": {
442 "workspace": { "type": "string", "description": "Workspace name or id" },
443 "source_symbol": { "type": "string", "description": "Symbol in source codebase" },
444 "target_symbol": { "type": "string", "description": "Symbol in target (null if not ported)" },
445 "status": { "type": "string", "enum": ["not_started", "in_progress", "ported", "verified", "skipped"], "description": "Porting status" },
446 "notes": { "type": "string", "description": "Optional notes" }
447 },
448 "required": ["workspace", "source_symbol", "status"]
449 }
450 },
451 {
452 "name": "translation_progress",
453 "description": "Get migration progress statistics",
454 "inputSchema": {
455 "type": "object",
456 "properties": {
457 "workspace": { "type": "string", "description": "Workspace name or id" }
458 },
459 "required": ["workspace"]
460 }
461 },
462 {
463 "name": "translation_remaining",
464 "description": "List symbols not yet ported",
465 "inputSchema": {
466 "type": "object",
467 "properties": {
468 "workspace": { "type": "string", "description": "Workspace name or id" },
469 "module": { "type": "string", "description": "Filter by module (optional)" }
470 },
471 "required": ["workspace"]
472 }
473 },
474 {
476 "name": "impact_analyze",
477 "description": "Analyze the full impact of a proposed code change with blast radius and risk assessment",
478 "inputSchema": {
479 "type": "object",
480 "properties": {
481 "graph": { "type": "string", "description": "Graph name" },
482 "unit_id": { "type": "integer", "description": "Target code unit ID" },
483 "change_type": { "type": "string", "enum": ["signature", "behavior", "deletion", "rename", "move"], "default": "behavior" },
484 "max_depth": { "type": "integer", "minimum": 1, "default": 5 }
485 },
486 "required": ["unit_id"]
487 }
488 },
489 {
490 "name": "impact_path",
491 "description": "Find the impact path between two code units",
492 "inputSchema": {
493 "type": "object",
494 "properties": {
495 "graph": { "type": "string", "description": "Graph name" },
496 "from": { "type": "integer", "description": "Source unit ID" },
497 "to": { "type": "integer", "description": "Target unit ID" }
498 },
499 "required": ["from", "to"]
500 }
501 },
502 {
504 "name": "prophecy",
505 "description": "Predict the future of a code unit based on history, complexity, and dependencies",
506 "inputSchema": {
507 "type": "object",
508 "properties": {
509 "graph": { "type": "string", "description": "Graph name" },
510 "unit_id": { "type": "integer", "description": "Code unit ID to predict" }
511 },
512 "required": ["unit_id"]
513 }
514 },
515 {
516 "name": "prophecy_if",
517 "description": "What-if scenario: predict impact of a hypothetical change",
518 "inputSchema": {
519 "type": "object",
520 "properties": {
521 "graph": { "type": "string", "description": "Graph name" },
522 "unit_id": { "type": "integer", "description": "Code unit ID" },
523 "change_type": { "type": "string", "enum": ["signature", "behavior", "deletion", "rename", "move"], "default": "behavior" }
524 },
525 "required": ["unit_id"]
526 }
527 },
528 {
530 "name": "regression_predict",
531 "description": "Predict which tests are most likely affected by a change",
532 "inputSchema": {
533 "type": "object",
534 "properties": {
535 "graph": { "type": "string", "description": "Graph name" },
536 "unit_id": { "type": "integer", "description": "Changed code unit ID" },
537 "max_depth": { "type": "integer", "minimum": 1, "default": 5 }
538 },
539 "required": ["unit_id"]
540 }
541 },
542 {
543 "name": "regression_minimal",
544 "description": "Get the minimal test set needed for a change",
545 "inputSchema": {
546 "type": "object",
547 "properties": {
548 "graph": { "type": "string", "description": "Graph name" },
549 "unit_id": { "type": "integer", "description": "Changed code unit ID" },
550 "threshold": { "type": "number", "description": "Minimum probability threshold (0.0-1.0)", "default": 0.5 }
551 },
552 "required": ["unit_id"]
553 }
554 },
555 {
557 "name": "codebase_ground_claim",
558 "description": "Ground a claim with full citations including file locations and code snippets",
559 "inputSchema": {
560 "type": "object",
561 "properties": {
562 "graph": { "type": "string", "description": "Graph name" },
563 "claim": { "type": "string", "description": "The claim to verify and cite" }
564 },
565 "required": ["claim"]
566 }
567 },
568 {
569 "name": "codebase_cite",
570 "description": "Get a citation for a specific code unit",
571 "inputSchema": {
572 "type": "object",
573 "properties": {
574 "graph": { "type": "string", "description": "Graph name" },
575 "unit_id": { "type": "integer", "description": "Code unit ID to cite" }
576 },
577 "required": ["unit_id"]
578 }
579 },
580 {
582 "name": "hallucination_check",
583 "description": "Check AI-generated output for hallucinations about code",
584 "inputSchema": {
585 "type": "object",
586 "properties": {
587 "graph": { "type": "string", "description": "Graph name" },
588 "output": { "type": "string", "description": "AI-generated text to check" }
589 },
590 "required": ["output"]
591 }
592 },
593 {
595 "name": "truth_register",
596 "description": "Register a truth claim for ongoing maintenance",
597 "inputSchema": {
598 "type": "object",
599 "properties": {
600 "graph": { "type": "string", "description": "Graph name" },
601 "claim": { "type": "string", "description": "The truth claim to maintain" }
602 },
603 "required": ["claim"]
604 }
605 },
606 {
607 "name": "truth_check",
608 "description": "Check if a registered truth is still valid",
609 "inputSchema": {
610 "type": "object",
611 "properties": {
612 "graph": { "type": "string", "description": "Graph name" },
613 "claim": { "type": "string", "description": "The truth claim to check" }
614 },
615 "required": ["claim"]
616 }
617 },
618 {
620 "name": "concept_find",
621 "description": "Find code implementing a concept (e.g., authentication, payment)",
622 "inputSchema": {
623 "type": "object",
624 "properties": {
625 "graph": { "type": "string", "description": "Graph name" },
626 "concept": { "type": "string", "description": "Concept to find (e.g., 'authentication', 'payment')" }
627 },
628 "required": ["concept"]
629 }
630 },
631 {
632 "name": "concept_map",
633 "description": "Map all detected concepts in the codebase",
634 "inputSchema": {
635 "type": "object",
636 "properties": {
637 "graph": { "type": "string", "description": "Graph name" }
638 }
639 }
640 },
641 {
642 "name": "concept_explain",
643 "description": "Explain how a concept is implemented with details",
644 "inputSchema": {
645 "type": "object",
646 "properties": {
647 "graph": { "type": "string", "description": "Graph name" },
648 "concept": { "type": "string", "description": "Concept to explain" }
649 },
650 "required": ["concept"]
651 }
652 },
653 {
655 "name": "architecture_infer",
656 "description": "Infer the architecture pattern of the codebase",
657 "inputSchema": {
658 "type": "object",
659 "properties": {
660 "graph": { "type": "string", "description": "Graph name" }
661 }
662 }
663 },
664 {
665 "name": "architecture_validate",
666 "description": "Validate the codebase against its inferred architecture",
667 "inputSchema": {
668 "type": "object",
669 "properties": {
670 "graph": { "type": "string", "description": "Graph name" }
671 }
672 }
673 },
674 {
676 "name": "search_semantic",
677 "description": "Natural-language semantic search across the codebase",
678 "inputSchema": {
679 "type": "object",
680 "properties": {
681 "graph": { "type": "string", "description": "Graph name" },
682 "query": { "type": "string", "description": "Natural-language search query" },
683 "top_k": { "type": "integer", "minimum": 1, "default": 10 }
684 },
685 "required": ["query"]
686 }
687 },
688 {
689 "name": "search_similar",
690 "description": "Find code units similar to a given unit",
691 "inputSchema": {
692 "type": "object",
693 "properties": {
694 "graph": { "type": "string", "description": "Graph name" },
695 "unit_id": { "type": "integer", "description": "Unit ID to find similar units for" },
696 "top_k": { "type": "integer", "minimum": 1, "default": 10 }
697 },
698 "required": ["unit_id"]
699 }
700 },
701 {
702 "name": "search_explain",
703 "description": "Explain why a unit matched a search query",
704 "inputSchema": {
705 "type": "object",
706 "properties": {
707 "graph": { "type": "string", "description": "Graph name" },
708 "unit_id": { "type": "integer", "description": "Unit ID" },
709 "query": { "type": "string", "description": "The search query" }
710 },
711 "required": ["unit_id", "query"]
712 }
713 },
714 {
716 "name": "compare_codebases",
717 "description": "Full structural, conceptual, and pattern comparison between two codebases in a workspace",
718 "inputSchema": {
719 "type": "object",
720 "properties": {
721 "workspace": { "type": "string", "description": "Workspace name or id" }
722 },
723 "required": ["workspace"]
724 }
725 },
726 {
727 "name": "compare_concept",
728 "description": "Compare how a concept is implemented across two codebases",
729 "inputSchema": {
730 "type": "object",
731 "properties": {
732 "workspace": { "type": "string", "description": "Workspace name or id" },
733 "concept": { "type": "string", "description": "Concept to compare (e.g., 'authentication')" }
734 },
735 "required": ["workspace", "concept"]
736 }
737 },
738 {
739 "name": "compare_migrate",
740 "description": "Generate a migration plan from source to target codebase",
741 "inputSchema": {
742 "type": "object",
743 "properties": {
744 "workspace": { "type": "string", "description": "Workspace name or id" }
745 },
746 "required": ["workspace"]
747 }
748 },
749 {
751 "name": "archaeology_node",
752 "description": "Investigate the full history and evolution of a code unit",
753 "inputSchema": {
754 "type": "object",
755 "properties": {
756 "graph": { "type": "string", "description": "Graph name" },
757 "unit_id": { "type": "integer", "description": "Code unit ID to investigate" }
758 },
759 "required": ["unit_id"]
760 }
761 },
762 {
763 "name": "archaeology_why",
764 "description": "Explain why code looks the way it does based on its history",
765 "inputSchema": {
766 "type": "object",
767 "properties": {
768 "graph": { "type": "string", "description": "Graph name" },
769 "unit_id": { "type": "integer", "description": "Code unit ID" }
770 },
771 "required": ["unit_id"]
772 }
773 },
774 {
775 "name": "archaeology_when",
776 "description": "Get the timeline of changes for a code unit",
777 "inputSchema": {
778 "type": "object",
779 "properties": {
780 "graph": { "type": "string", "description": "Graph name" },
781 "unit_id": { "type": "integer", "description": "Code unit ID" }
782 },
783 "required": ["unit_id"]
784 }
785 },
786 {
788 "name": "pattern_extract",
789 "description": "Extract all detected patterns from the codebase",
790 "inputSchema": {
791 "type": "object",
792 "properties": {
793 "graph": { "type": "string", "description": "Graph name" }
794 }
795 }
796 },
797 {
798 "name": "pattern_check",
799 "description": "Check a code unit against detected patterns for violations",
800 "inputSchema": {
801 "type": "object",
802 "properties": {
803 "graph": { "type": "string", "description": "Graph name" },
804 "unit_id": { "type": "integer", "description": "Code unit ID to check" }
805 },
806 "required": ["unit_id"]
807 }
808 },
809 {
810 "name": "pattern_suggest",
811 "description": "Suggest patterns for new code based on file location",
812 "inputSchema": {
813 "type": "object",
814 "properties": {
815 "graph": { "type": "string", "description": "Graph name" },
816 "file_path": { "type": "string", "description": "File path for pattern suggestions" }
817 },
818 "required": ["file_path"]
819 }
820 },
821 { "name": "resurrect_search", "description": "Search for traces of deleted code", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" }, "query": { "type": "string", "description": "Search query for deleted code traces" }, "max_results": { "type": "integer", "minimum": 1, "default": 10 } }, "required": ["query"] } },
823 { "name": "resurrect_attempt", "description": "Attempt to reconstruct deleted code from traces", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" }, "query": { "type": "string", "description": "Description of the code to resurrect" } }, "required": ["query"] } },
824 { "name": "resurrect_verify", "description": "Verify a resurrection attempt is accurate", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" }, "original_name": { "type": "string", "description": "Original name of the deleted code" }, "reconstructed": { "type": "string", "description": "Reconstructed code to verify" } }, "required": ["original_name", "reconstructed"] } },
825 { "name": "resurrect_history", "description": "Get resurrection history for the codebase", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" } } } },
826 { "name": "genetics_dna", "description": "Extract the DNA (core patterns) of a code unit", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" }, "unit_id": { "type": "integer", "description": "Code unit ID" } }, "required": ["unit_id"] } },
828 { "name": "genetics_lineage", "description": "Trace the lineage of a code unit through evolution", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" }, "unit_id": { "type": "integer", "description": "Code unit ID" }, "max_depth": { "type": "integer", "minimum": 1, "default": 10 } }, "required": ["unit_id"] } },
829 { "name": "genetics_mutations", "description": "Detect mutations (unexpected changes) in code patterns", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" }, "unit_id": { "type": "integer", "description": "Code unit ID" } }, "required": ["unit_id"] } },
830 { "name": "genetics_diseases", "description": "Diagnose inherited code diseases (anti-patterns passed through lineage)", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" }, "unit_id": { "type": "integer", "description": "Code unit ID" } }, "required": ["unit_id"] } },
831 { "name": "telepathy_connect", "description": "Establish telepathic connection between codebases", "inputSchema": { "type": "object", "properties": { "workspace": { "type": "string", "description": "Workspace name or id" }, "source_graph": { "type": "string", "description": "Source graph name" }, "target_graph": { "type": "string", "description": "Target graph name" } }, "required": ["workspace"] } },
833 { "name": "telepathy_broadcast", "description": "Broadcast a code insight to connected codebases", "inputSchema": { "type": "object", "properties": { "workspace": { "type": "string", "description": "Workspace name or id" }, "insight": { "type": "string", "description": "The code insight to broadcast" }, "source_graph": { "type": "string", "description": "Source graph name" } }, "required": ["workspace", "insight"] } },
834 { "name": "telepathy_listen", "description": "Listen for insights from connected codebases", "inputSchema": { "type": "object", "properties": { "workspace": { "type": "string", "description": "Workspace name or id" }, "target_graph": { "type": "string", "description": "Target graph name to listen from" } }, "required": ["workspace"] } },
835 { "name": "telepathy_consensus", "description": "Find consensus patterns across connected codebases", "inputSchema": { "type": "object", "properties": { "workspace": { "type": "string", "description": "Workspace name or id" }, "concept": { "type": "string", "description": "Concept to find consensus on" } }, "required": ["workspace", "concept"] } },
836 { "name": "soul_extract", "description": "Extract the soul (essential purpose and values) of code", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" }, "unit_id": { "type": "integer", "description": "Code unit ID" } }, "required": ["unit_id"] } },
838 { "name": "soul_compare", "description": "Compare souls across code reincarnations", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" }, "unit_id_a": { "type": "integer", "description": "First code unit ID" }, "unit_id_b": { "type": "integer", "description": "Second code unit ID" } }, "required": ["unit_id_a", "unit_id_b"] } },
839 { "name": "soul_preserve", "description": "Preserve a code soul during rewrite", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" }, "unit_id": { "type": "integer", "description": "Code unit ID" }, "new_language": { "type": "string", "description": "Target language for rewrite" } }, "required": ["unit_id"] } },
840 { "name": "soul_reincarnate", "description": "Guide a soul to a new code manifestation", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" }, "soul_id": { "type": "string", "description": "Soul identifier" }, "target_context": { "type": "string", "description": "Target context for reincarnation" } }, "required": ["soul_id", "target_context"] } },
841 { "name": "soul_karma", "description": "Analyze the karma (positive/negative impact history) of code", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" }, "unit_id": { "type": "integer", "description": "Code unit ID" } }, "required": ["unit_id"] } },
842 { "name": "omniscience_search", "description": "Search across global code knowledge", "inputSchema": { "type": "object", "properties": { "query": { "type": "string", "description": "Search query" }, "languages": { "type": "array", "items": { "type": "string" }, "description": "Filter by languages" }, "max_results": { "type": "integer", "minimum": 1, "default": 10 } }, "required": ["query"] } },
844 { "name": "omniscience_best", "description": "Find the best implementation of a concept globally", "inputSchema": { "type": "object", "properties": { "capability": { "type": "string", "description": "Capability to find best implementation for" }, "criteria": { "type": "array", "items": { "type": "string" }, "description": "Evaluation criteria" } }, "required": ["capability"] } },
845 { "name": "omniscience_census", "description": "Global code census for a concept", "inputSchema": { "type": "object", "properties": { "concept": { "type": "string", "description": "Concept to census" }, "languages": { "type": "array", "items": { "type": "string" }, "description": "Filter by languages" } }, "required": ["concept"] } },
846 { "name": "omniscience_vuln", "description": "Scan for known vulnerability patterns", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" }, "pattern": { "type": "string", "description": "Vulnerability pattern to scan for" }, "cve": { "type": "string", "description": "CVE identifier to check" } } } },
847 { "name": "omniscience_trend", "description": "Find emerging or declining code patterns", "inputSchema": { "type": "object", "properties": { "domain": { "type": "string", "description": "Domain to analyze trends in" }, "threshold": { "type": "number", "default": 0.5 } }, "required": ["domain"] } },
848 { "name": "omniscience_compare", "description": "Compare your code to global best practices", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Graph name" }, "unit_id": { "type": "integer", "description": "Code unit ID to compare" } }, "required": ["unit_id"] } },
849 { "name": "omniscience_api_usage", "description": "Find all usages of an API globally", "inputSchema": { "type": "object", "properties": { "api": { "type": "string", "description": "API name to search for" }, "method": { "type": "string", "description": "Specific method within the API" } }, "required": ["api"] } },
850 { "name": "omniscience_solve", "description": "Find code that solves a specific problem", "inputSchema": { "type": "object", "properties": { "problem": { "type": "string", "description": "Problem description to solve" }, "languages": { "type": "array", "items": { "type": "string" }, "description": "Preferred languages" }, "max_results": { "type": "integer", "minimum": 1, "default": 5 } }, "required": ["problem"] } }
851 ]
852 }),
853 )
854 }
855
856 fn handle_tools_call(&mut self, id: Value, params: &Value) -> JsonRpcResponse {
858 if self.graphs.is_empty() {
860 self.try_lazy_load();
861 }
862
863 let tool_name = match params.get("name").and_then(|v| v.as_str()) {
864 Some(name) => name,
865 None => {
866 return JsonRpcResponse::error(
867 id,
868 JsonRpcError::invalid_params("Missing 'name' field in tools/call params"),
869 );
870 }
871 };
872
873 let arguments = params
874 .get("arguments")
875 .cloned()
876 .unwrap_or(Value::Object(serde_json::Map::new()));
877
878 let result = match tool_name {
879 "symbol_lookup" => self.tool_symbol_lookup(id.clone(), &arguments),
880 "impact_analysis" => self.tool_impact_analysis(id.clone(), &arguments),
881 "graph_stats" => self.tool_graph_stats(id.clone(), &arguments),
882 "list_units" => self.tool_list_units(id.clone(), &arguments),
883 "analysis_log" => return self.tool_analysis_log(id, &arguments),
884 "codebase_ground" => self.tool_codebase_ground(id.clone(), &arguments),
886 "codebase_evidence" => self.tool_codebase_evidence(id.clone(), &arguments),
887 "codebase_suggest" => self.tool_codebase_suggest(id.clone(), &arguments),
888 "workspace_create" => return self.tool_workspace_create(id, &arguments),
890 "workspace_add" => return self.tool_workspace_add(id, &arguments),
891 "workspace_list" => self.tool_workspace_list(id.clone(), &arguments),
892 "workspace_query" => self.tool_workspace_query(id.clone(), &arguments),
893 "workspace_compare" => self.tool_workspace_compare(id.clone(), &arguments),
894 "workspace_xref" => self.tool_workspace_xref(id.clone(), &arguments),
895 "translation_record" => return self.tool_translation_record(id, &arguments),
897 "translation_progress" => self.tool_translation_progress(id.clone(), &arguments),
898 "translation_remaining" => self.tool_translation_remaining(id.clone(), &arguments),
899 "impact_analyze" => self.tool_impact_analyze(id.clone(), &arguments),
902 "impact_path" => self.tool_impact_path(id.clone(), &arguments),
903 "prophecy" => self.tool_prophecy(id.clone(), &arguments),
905 "prophecy_if" => self.tool_prophecy_if(id.clone(), &arguments),
906 "regression_predict" => self.tool_regression_predict(id.clone(), &arguments),
908 "regression_minimal" => self.tool_regression_minimal(id.clone(), &arguments),
909 "codebase_ground_claim" => self.tool_codebase_ground_claim(id.clone(), &arguments),
911 "codebase_cite" => self.tool_codebase_cite(id.clone(), &arguments),
912 "hallucination_check" => self.tool_hallucination_check(id.clone(), &arguments),
914 "truth_register" => self.tool_truth_register(id.clone(), &arguments),
916 "truth_check" => self.tool_truth_check(id.clone(), &arguments),
917 "concept_find" => self.tool_concept_find(id.clone(), &arguments),
919 "concept_map" => self.tool_concept_map(id.clone(), &arguments),
920 "concept_explain" => self.tool_concept_explain(id.clone(), &arguments),
921 "architecture_infer" => self.tool_architecture_infer(id.clone(), &arguments),
923 "architecture_validate" => self.tool_architecture_validate(id.clone(), &arguments),
924 "search_semantic" => self.tool_search_semantic(id.clone(), &arguments),
926 "search_similar" => self.tool_search_similar(id.clone(), &arguments),
927 "search_explain" => self.tool_search_explain(id.clone(), &arguments),
928 "compare_codebases" => self.tool_compare_codebases(id.clone(), &arguments),
930 "compare_concept" => self.tool_compare_concept(id.clone(), &arguments),
931 "compare_migrate" => self.tool_compare_migrate(id.clone(), &arguments),
932 "archaeology_node" => self.tool_archaeology_node(id.clone(), &arguments),
934 "archaeology_why" => self.tool_archaeology_why(id.clone(), &arguments),
935 "archaeology_when" => self.tool_archaeology_when(id.clone(), &arguments),
936 "pattern_extract" => self.tool_pattern_extract(id.clone(), &arguments),
938 "pattern_check" => self.tool_pattern_check(id.clone(), &arguments),
939 "pattern_suggest" => self.tool_pattern_suggest(id.clone(), &arguments),
940 "resurrect_search" => self.tool_resurrect_search(id.clone(), &arguments),
942 "resurrect_attempt" => self.tool_resurrect_attempt(id.clone(), &arguments),
943 "resurrect_verify" => self.tool_resurrect_verify(id.clone(), &arguments),
944 "resurrect_history" => self.tool_resurrect_history(id.clone(), &arguments),
945 "genetics_dna" => self.tool_genetics_dna(id.clone(), &arguments),
947 "genetics_lineage" => self.tool_genetics_lineage(id.clone(), &arguments),
948 "genetics_mutations" => self.tool_genetics_mutations(id.clone(), &arguments),
949 "genetics_diseases" => self.tool_genetics_diseases(id.clone(), &arguments),
950 "telepathy_connect" => self.tool_telepathy_connect(id.clone(), &arguments),
952 "telepathy_broadcast" => self.tool_telepathy_broadcast(id.clone(), &arguments),
953 "telepathy_listen" => self.tool_telepathy_listen(id.clone(), &arguments),
954 "telepathy_consensus" => self.tool_telepathy_consensus(id.clone(), &arguments),
955 "soul_extract" => self.tool_soul_extract(id.clone(), &arguments),
957 "soul_compare" => self.tool_soul_compare(id.clone(), &arguments),
958 "soul_preserve" => self.tool_soul_preserve(id.clone(), &arguments),
959 "soul_reincarnate" => self.tool_soul_reincarnate(id.clone(), &arguments),
960 "soul_karma" => self.tool_soul_karma(id.clone(), &arguments),
961 "omniscience_search" => self.tool_omniscience_search(id.clone(), &arguments),
963 "omniscience_best" => self.tool_omniscience_best(id.clone(), &arguments),
964 "omniscience_census" => self.tool_omniscience_census(id.clone(), &arguments),
965 "omniscience_vuln" => self.tool_omniscience_vuln(id.clone(), &arguments),
966 "omniscience_trend" => self.tool_omniscience_trend(id.clone(), &arguments),
967 "omniscience_compare" => self.tool_omniscience_compare(id.clone(), &arguments),
968 "omniscience_api_usage" => self.tool_omniscience_api_usage(id.clone(), &arguments),
969 "omniscience_solve" => self.tool_omniscience_solve(id.clone(), &arguments),
970 _ => {
971 return JsonRpcResponse::error(
972 id,
973 JsonRpcError::tool_not_found(format!("Tool not found: {}", tool_name)),
974 );
975 }
976 };
977
978 let result = result.into_tool_error_if_needed();
981
982 let now = std::time::SystemTime::now()
984 .duration_since(std::time::UNIX_EPOCH)
985 .unwrap_or_default()
986 .as_secs();
987 let summary = truncate_json_summary(&arguments, 200);
988 let graph_name = arguments
989 .get("graph")
990 .and_then(|v| v.as_str())
991 .map(String::from);
992 self.operation_log.push(OperationRecord {
993 tool_name: tool_name.to_string(),
994 summary,
995 timestamp: now,
996 graph_name,
997 });
998
999 result
1000 }
1001
1002 fn handle_resources_list(&self, id: Value) -> JsonRpcResponse {
1004 let mut resources = Vec::new();
1005
1006 for name in self.graphs.keys() {
1007 resources.push(json!({
1008 "uri": format!("acb://graphs/{}/stats", name),
1009 "name": format!("{} statistics", name),
1010 "description": format!("Statistics for the {} code graph", name),
1011 "mimeType": "application/json"
1012 }));
1013 resources.push(json!({
1014 "uri": format!("acb://graphs/{}/units", name),
1015 "name": format!("{} units", name),
1016 "description": format!("All code units in the {} graph", name),
1017 "mimeType": "application/json"
1018 }));
1019 }
1020
1021 JsonRpcResponse::success(id, json!({ "resources": resources }))
1022 }
1023
1024 fn handle_resources_read(&self, id: Value, params: &Value) -> JsonRpcResponse {
1026 let uri = match params.get("uri").and_then(|v| v.as_str()) {
1027 Some(u) => u,
1028 None => {
1029 return JsonRpcResponse::error(
1030 id,
1031 JsonRpcError::invalid_params("Missing 'uri' field"),
1032 );
1033 }
1034 };
1035
1036 if let Some(rest) = uri.strip_prefix("acb://graphs/") {
1038 let parts: Vec<&str> = rest.splitn(2, '/').collect();
1039 if parts.len() == 2 {
1040 let graph_name = parts[0];
1041 let resource = parts[1];
1042
1043 if let Some(graph) = self.graphs.get(graph_name) {
1044 return match resource {
1045 "stats" => {
1046 let stats = graph.stats();
1047 JsonRpcResponse::success(
1048 id,
1049 json!({
1050 "contents": [{
1051 "uri": uri,
1052 "mimeType": "application/json",
1053 "text": serde_json::to_string_pretty(&json!({
1054 "unit_count": stats.unit_count,
1055 "edge_count": stats.edge_count,
1056 "dimension": stats.dimension,
1057 })).unwrap_or_default()
1058 }]
1059 }),
1060 )
1061 }
1062 "units" => {
1063 let units: Vec<Value> = graph
1064 .units()
1065 .iter()
1066 .map(|u| {
1067 json!({
1068 "id": u.id,
1069 "name": u.name,
1070 "type": u.unit_type.label(),
1071 "file": u.file_path.display().to_string(),
1072 })
1073 })
1074 .collect();
1075 JsonRpcResponse::success(
1076 id,
1077 json!({
1078 "contents": [{
1079 "uri": uri,
1080 "mimeType": "application/json",
1081 "text": serde_json::to_string_pretty(&units).unwrap_or_default()
1082 }]
1083 }),
1084 )
1085 }
1086 _ => JsonRpcResponse::error(
1087 id,
1088 JsonRpcError::invalid_params(format!(
1089 "Unknown resource type: {}",
1090 resource
1091 )),
1092 ),
1093 };
1094 } else {
1095 return JsonRpcResponse::error(
1096 id,
1097 JsonRpcError::invalid_params(format!("Graph not found: {}", graph_name)),
1098 );
1099 }
1100 }
1101 }
1102
1103 JsonRpcResponse::error(
1104 id,
1105 JsonRpcError::invalid_params(format!("Invalid resource URI: {}", uri)),
1106 )
1107 }
1108
1109 fn handle_prompts_list(&self, id: Value) -> JsonRpcResponse {
1111 JsonRpcResponse::success(
1112 id,
1113 json!({
1114 "prompts": [
1115 {
1116 "name": "analyse_unit",
1117 "description": "Analyse a code unit including its dependencies, stability, and test coverage",
1118 "arguments": [
1119 {
1120 "name": "graph",
1121 "description": "Graph name",
1122 "required": false
1123 },
1124 {
1125 "name": "unit_name",
1126 "description": "Name of the code unit to analyse",
1127 "required": true
1128 }
1129 ]
1130 },
1131 {
1132 "name": "explain_coupling",
1133 "description": "Explain coupling between two code units",
1134 "arguments": [
1135 {
1136 "name": "graph",
1137 "description": "Graph name",
1138 "required": false
1139 },
1140 {
1141 "name": "unit_a",
1142 "description": "First unit name",
1143 "required": true
1144 },
1145 {
1146 "name": "unit_b",
1147 "description": "Second unit name",
1148 "required": true
1149 }
1150 ]
1151 }
1152 ]
1153 }),
1154 )
1155 }
1156
1157 fn resolve_graph<'a>(
1163 &'a self,
1164 args: &'a Value,
1165 ) -> Result<(&'a str, &'a CodeGraph), JsonRpcError> {
1166 let graph_name = args.get("graph").and_then(|v| v.as_str()).unwrap_or("");
1167
1168 if graph_name.is_empty() {
1169 if let Some((name, graph)) = self.graphs.iter().next() {
1171 return Ok((name.as_str(), graph));
1172 }
1173 return Err(JsonRpcError::invalid_params(
1174 "No graphs loaded. Start the MCP server with --graph <path.acb>, \
1175 or set AGENTRA_WORKSPACE_ROOT to a repository for auto-compilation.",
1176 ));
1177 }
1178
1179 self.graphs
1180 .get(graph_name)
1181 .map(|g| (graph_name, g))
1182 .ok_or_else(|| JsonRpcError::invalid_params(format!("Graph not found: {}", graph_name)))
1183 }
1184
1185 fn tool_symbol_lookup(&self, id: Value, args: &Value) -> JsonRpcResponse {
1187 let (_, graph) = match self.resolve_graph(args) {
1188 Ok(g) => g,
1189 Err(e) => return JsonRpcResponse::error(id, e),
1190 };
1191
1192 let name = match args.get("name").and_then(|v| v.as_str()) {
1193 Some(n) => n.to_string(),
1194 None => {
1195 return JsonRpcResponse::error(
1196 id,
1197 JsonRpcError::invalid_params("Missing 'name' argument"),
1198 );
1199 }
1200 };
1201
1202 let mode_raw = args
1203 .get("mode")
1204 .and_then(|v| v.as_str())
1205 .unwrap_or("prefix");
1206 let mode = match mode_raw {
1207 "exact" => MatchMode::Exact,
1208 "prefix" => MatchMode::Prefix,
1209 "contains" => MatchMode::Contains,
1210 "fuzzy" => MatchMode::Fuzzy,
1211 _ => {
1212 return JsonRpcResponse::error(
1213 id,
1214 JsonRpcError::invalid_params(format!(
1215 "Invalid 'mode': {mode_raw}. Expected one of: exact, prefix, contains, fuzzy"
1216 )),
1217 );
1218 }
1219 };
1220
1221 let limit = args.get("limit").and_then(|v| v.as_u64()).unwrap_or(10) as usize;
1222
1223 let params = SymbolLookupParams {
1224 name,
1225 mode,
1226 limit,
1227 ..SymbolLookupParams::default()
1228 };
1229
1230 match self.engine.symbol_lookup(graph, params) {
1231 Ok(units) => {
1232 let results: Vec<Value> = units
1233 .iter()
1234 .map(|u| {
1235 json!({
1236 "id": u.id,
1237 "name": u.name,
1238 "qualified_name": u.qualified_name,
1239 "type": u.unit_type.label(),
1240 "file": u.file_path.display().to_string(),
1241 "language": u.language.name(),
1242 "complexity": u.complexity,
1243 })
1244 })
1245 .collect();
1246 JsonRpcResponse::success(
1247 id,
1248 json!({
1249 "content": [{
1250 "type": "text",
1251 "text": serde_json::to_string_pretty(&results).unwrap_or_default()
1252 }]
1253 }),
1254 )
1255 }
1256 Err(e) => JsonRpcResponse::error(id, JsonRpcError::internal_error(e.to_string())),
1257 }
1258 }
1259
1260 fn tool_impact_analysis(&self, id: Value, args: &Value) -> JsonRpcResponse {
1262 let (_, graph) = match self.resolve_graph(args) {
1263 Ok(g) => g,
1264 Err(e) => return JsonRpcResponse::error(id, e),
1265 };
1266
1267 let unit_id = match args.get("unit_id").and_then(|v| v.as_u64()) {
1268 Some(uid) => uid,
1269 None => {
1270 return JsonRpcResponse::error(
1271 id,
1272 JsonRpcError::invalid_params("Missing 'unit_id' argument"),
1273 );
1274 }
1275 };
1276
1277 let max_depth = match args.get("max_depth") {
1278 None => 3,
1279 Some(v) => {
1280 let depth = match v.as_i64() {
1281 Some(d) => d,
1282 None => {
1283 return JsonRpcResponse::error(
1284 id,
1285 JsonRpcError::invalid_params("'max_depth' must be an integer >= 0"),
1286 );
1287 }
1288 };
1289 if depth < 0 {
1290 return JsonRpcResponse::error(
1291 id,
1292 JsonRpcError::invalid_params("'max_depth' must be >= 0"),
1293 );
1294 }
1295 depth as u32
1296 }
1297 };
1298 let edge_types = vec![
1299 EdgeType::Calls,
1300 EdgeType::Imports,
1301 EdgeType::Inherits,
1302 EdgeType::Implements,
1303 EdgeType::UsesType,
1304 EdgeType::FfiBinds,
1305 EdgeType::References,
1306 EdgeType::Returns,
1307 EdgeType::ParamType,
1308 EdgeType::Overrides,
1309 EdgeType::Contains,
1310 ];
1311
1312 let params = ImpactParams {
1313 unit_id,
1314 max_depth,
1315 edge_types,
1316 };
1317
1318 match self.engine.impact_analysis(graph, params) {
1319 Ok(result) => {
1320 let impacted: Vec<Value> = result
1321 .impacted
1322 .iter()
1323 .map(|i| {
1324 json!({
1325 "unit_id": i.unit_id,
1326 "depth": i.depth,
1327 "risk_score": i.risk_score,
1328 "has_tests": i.has_tests,
1329 })
1330 })
1331 .collect();
1332 JsonRpcResponse::success(
1333 id,
1334 json!({
1335 "content": [{
1336 "type": "text",
1337 "text": serde_json::to_string_pretty(&json!({
1338 "root_id": result.root_id,
1339 "overall_risk": result.overall_risk,
1340 "impacted_count": result.impacted.len(),
1341 "impacted": impacted,
1342 "recommendations": result.recommendations,
1343 })).unwrap_or_default()
1344 }]
1345 }),
1346 )
1347 }
1348 Err(e) => JsonRpcResponse::error(id, JsonRpcError::internal_error(e.to_string())),
1349 }
1350 }
1351
1352 fn tool_graph_stats(&self, id: Value, args: &Value) -> JsonRpcResponse {
1354 let (name, graph) = match self.resolve_graph(args) {
1355 Ok(g) => g,
1356 Err(e) => return JsonRpcResponse::error(id, e),
1357 };
1358
1359 let stats = graph.stats();
1360 JsonRpcResponse::success(
1361 id,
1362 json!({
1363 "content": [{
1364 "type": "text",
1365 "text": serde_json::to_string_pretty(&json!({
1366 "graph": name,
1367 "unit_count": stats.unit_count,
1368 "edge_count": stats.edge_count,
1369 "dimension": stats.dimension,
1370 })).unwrap_or_default()
1371 }]
1372 }),
1373 )
1374 }
1375
1376 fn tool_list_units(&self, id: Value, args: &Value) -> JsonRpcResponse {
1378 let (_, graph) = match self.resolve_graph(args) {
1379 Ok(g) => g,
1380 Err(e) => return JsonRpcResponse::error(id, e),
1381 };
1382
1383 let limit = args.get("limit").and_then(|v| v.as_u64()).unwrap_or(50) as usize;
1384 let unit_type_filter = match args.get("unit_type").and_then(|v| v.as_str()) {
1385 Some(raw) => match Self::parse_unit_type(raw) {
1386 Some(parsed) => Some(parsed),
1387 None => {
1388 return JsonRpcResponse::error(
1389 id,
1390 JsonRpcError::invalid_params(format!(
1391 "Unknown unit_type '{}'. Expected one of: module, symbol, type, function, parameter, import, test, doc, config, pattern, trait, impl, macro.",
1392 raw
1393 )),
1394 );
1395 }
1396 },
1397 None => None,
1398 };
1399
1400 let units: Vec<Value> = graph
1401 .units()
1402 .iter()
1403 .filter(|u| {
1404 if let Some(expected) = unit_type_filter {
1405 u.unit_type == expected
1406 } else {
1407 true
1408 }
1409 })
1410 .take(limit)
1411 .map(|u| {
1412 json!({
1413 "id": u.id,
1414 "name": u.name,
1415 "type": u.unit_type.label(),
1416 "file": u.file_path.display().to_string(),
1417 })
1418 })
1419 .collect();
1420
1421 JsonRpcResponse::success(
1422 id,
1423 json!({
1424 "content": [{
1425 "type": "text",
1426 "text": serde_json::to_string_pretty(&units).unwrap_or_default()
1427 }]
1428 }),
1429 )
1430 }
1431
1432 fn tool_analysis_log(&mut self, id: Value, args: &Value) -> JsonRpcResponse {
1434 let intent = match args.get("intent").and_then(|v| v.as_str()) {
1435 Some(i) if !i.trim().is_empty() => i,
1436 _ => {
1437 return JsonRpcResponse::error(
1438 id,
1439 JsonRpcError::invalid_params("'intent' is required and must not be empty"),
1440 );
1441 }
1442 };
1443
1444 let finding = args.get("finding").and_then(|v| v.as_str());
1445 let graph_name = args.get("graph").and_then(|v| v.as_str());
1446 let topic = args.get("topic").and_then(|v| v.as_str());
1447
1448 let now = std::time::SystemTime::now()
1449 .duration_since(std::time::UNIX_EPOCH)
1450 .unwrap_or_default()
1451 .as_secs();
1452
1453 let mut summary_parts = vec![format!("intent: {intent}")];
1454 if let Some(f) = finding {
1455 summary_parts.push(format!("finding: {f}"));
1456 }
1457 if let Some(t) = topic {
1458 summary_parts.push(format!("topic: {t}"));
1459 }
1460
1461 let record = OperationRecord {
1462 tool_name: "analysis_log".to_string(),
1463 summary: summary_parts.join(" | "),
1464 timestamp: now,
1465 graph_name: graph_name.map(String::from),
1466 };
1467
1468 let index = self.operation_log.len();
1469 self.operation_log.push(record);
1470
1471 JsonRpcResponse::success(
1472 id,
1473 json!({
1474 "content": [{
1475 "type": "text",
1476 "text": serde_json::to_string_pretty(&json!({
1477 "log_index": index,
1478 "message": "Analysis context logged"
1479 })).unwrap_or_default()
1480 }]
1481 }),
1482 )
1483 }
1484
1485 pub fn operation_log(&self) -> &[OperationRecord] {
1487 &self.operation_log
1488 }
1489
1490 pub fn workspace_manager(&self) -> &WorkspaceManager {
1492 &self.workspace_manager
1493 }
1494
1495 pub fn workspace_manager_mut(&mut self) -> &mut WorkspaceManager {
1497 &mut self.workspace_manager
1498 }
1499
1500 fn tool_codebase_ground(&self, id: Value, args: &Value) -> JsonRpcResponse {
1506 let (_, graph) = match self.resolve_graph(args) {
1507 Ok(g) => g,
1508 Err(e) => return JsonRpcResponse::error(id, e),
1509 };
1510
1511 let claim = match args.get("claim").and_then(|v| v.as_str()) {
1512 Some(c) if !c.trim().is_empty() => c,
1513 _ => {
1514 return JsonRpcResponse::error(
1515 id,
1516 JsonRpcError::invalid_params("Missing or empty 'claim' argument"),
1517 );
1518 }
1519 };
1520
1521 let strict = args
1522 .get("strict")
1523 .and_then(|v| v.as_bool())
1524 .unwrap_or(false);
1525
1526 let engine = GroundingEngine::new(graph);
1527 let result = engine.ground_claim(claim);
1528
1529 let result = if strict {
1531 match result {
1532 GroundingResult::Partial {
1533 unsupported,
1534 suggestions,
1535 ..
1536 } => GroundingResult::Ungrounded {
1537 claim: claim.to_string(),
1538 suggestions: {
1539 let mut s = unsupported;
1540 s.extend(suggestions);
1541 s
1542 },
1543 },
1544 other => other,
1545 }
1546 } else {
1547 result
1548 };
1549
1550 let output = match &result {
1551 GroundingResult::Verified {
1552 evidence,
1553 confidence,
1554 } => json!({
1555 "status": "verified",
1556 "confidence": confidence,
1557 "evidence": evidence.iter().map(|e| json!({
1558 "node_id": e.node_id,
1559 "node_type": e.node_type,
1560 "name": e.name,
1561 "file_path": e.file_path,
1562 "line_number": e.line_number,
1563 "snippet": e.snippet,
1564 })).collect::<Vec<_>>(),
1565 }),
1566 GroundingResult::Partial {
1567 supported,
1568 unsupported,
1569 suggestions,
1570 } => json!({
1571 "status": "partial",
1572 "supported": supported,
1573 "unsupported": unsupported,
1574 "suggestions": suggestions,
1575 }),
1576 GroundingResult::Ungrounded {
1577 claim, suggestions, ..
1578 } => json!({
1579 "status": "ungrounded",
1580 "claim": claim,
1581 "suggestions": suggestions,
1582 }),
1583 };
1584
1585 JsonRpcResponse::success(
1586 id,
1587 json!({
1588 "content": [{
1589 "type": "text",
1590 "text": serde_json::to_string_pretty(&output).unwrap_or_default()
1591 }]
1592 }),
1593 )
1594 }
1595
1596 fn tool_codebase_evidence(&self, id: Value, args: &Value) -> JsonRpcResponse {
1598 let (_, graph) = match self.resolve_graph(args) {
1599 Ok(g) => g,
1600 Err(e) => return JsonRpcResponse::error(id, e),
1601 };
1602
1603 let name = match args.get("name").and_then(|v| v.as_str()) {
1604 Some(n) if !n.trim().is_empty() => n,
1605 _ => {
1606 return JsonRpcResponse::error(
1607 id,
1608 JsonRpcError::invalid_params("Missing or empty 'name' argument"),
1609 );
1610 }
1611 };
1612
1613 let type_filters: Vec<String> = args
1614 .get("types")
1615 .and_then(|v| v.as_array())
1616 .map(|arr| {
1617 arr.iter()
1618 .filter_map(|v| v.as_str().map(|s| s.to_lowercase()))
1619 .collect()
1620 })
1621 .unwrap_or_default();
1622
1623 let engine = GroundingEngine::new(graph);
1624 let mut evidence = engine.find_evidence(name);
1625
1626 if !type_filters.is_empty() {
1628 evidence.retain(|e| type_filters.contains(&e.node_type.to_lowercase()));
1629 }
1630
1631 let output: Vec<Value> = evidence
1632 .iter()
1633 .map(|e| {
1634 json!({
1635 "node_id": e.node_id,
1636 "node_type": e.node_type,
1637 "name": e.name,
1638 "file_path": e.file_path,
1639 "line_number": e.line_number,
1640 "snippet": e.snippet,
1641 })
1642 })
1643 .collect();
1644
1645 JsonRpcResponse::success(
1646 id,
1647 json!({
1648 "content": [{
1649 "type": "text",
1650 "text": serde_json::to_string_pretty(&output).unwrap_or_default()
1651 }]
1652 }),
1653 )
1654 }
1655
1656 fn tool_codebase_suggest(&self, id: Value, args: &Value) -> JsonRpcResponse {
1658 let (_, graph) = match self.resolve_graph(args) {
1659 Ok(g) => g,
1660 Err(e) => return JsonRpcResponse::error(id, e),
1661 };
1662
1663 let name = match args.get("name").and_then(|v| v.as_str()) {
1664 Some(n) if !n.trim().is_empty() => n,
1665 _ => {
1666 return JsonRpcResponse::error(
1667 id,
1668 JsonRpcError::invalid_params("Missing or empty 'name' argument"),
1669 );
1670 }
1671 };
1672
1673 let limit = args.get("limit").and_then(|v| v.as_u64()).unwrap_or(5) as usize;
1674
1675 let engine = GroundingEngine::new(graph);
1676 let suggestions = engine.suggest_similar(name, limit);
1677
1678 JsonRpcResponse::success(
1679 id,
1680 json!({
1681 "content": [{
1682 "type": "text",
1683 "text": serde_json::to_string_pretty(&json!({
1684 "query": name,
1685 "suggestions": suggestions,
1686 })).unwrap_or_default()
1687 }]
1688 }),
1689 )
1690 }
1691
1692 fn resolve_workspace_id(&self, args: &Value) -> Result<String, JsonRpcError> {
1699 let raw = args.get("workspace").and_then(|v| v.as_str()).unwrap_or("");
1700 if raw.is_empty() {
1701 return self
1703 .workspace_manager
1704 .get_active()
1705 .map(|s| s.to_string())
1706 .ok_or_else(|| {
1707 JsonRpcError::invalid_params("No workspace specified and none active")
1708 });
1709 }
1710
1711 if raw.starts_with("ws-") {
1713 self.workspace_manager
1715 .list(raw)
1716 .map(|_| raw.to_string())
1717 .map_err(JsonRpcError::invalid_params)
1718 } else {
1719 self.workspace_manager
1722 .list(raw)
1723 .map(|_| raw.to_string())
1724 .map_err(JsonRpcError::invalid_params)
1725 }
1726 }
1727
1728 fn tool_workspace_create(&mut self, id: Value, args: &Value) -> JsonRpcResponse {
1730 let name = match args.get("name").and_then(|v| v.as_str()) {
1731 Some(n) if !n.trim().is_empty() => n,
1732 _ => {
1733 return JsonRpcResponse::error(
1734 id,
1735 JsonRpcError::invalid_params("Missing or empty 'name' argument"),
1736 );
1737 }
1738 };
1739
1740 let ws_id = self.workspace_manager.create(name);
1741
1742 JsonRpcResponse::success(
1743 id,
1744 json!({
1745 "content": [{
1746 "type": "text",
1747 "text": serde_json::to_string_pretty(&json!({
1748 "workspace_id": ws_id,
1749 "name": name,
1750 "message": "Workspace created"
1751 })).unwrap_or_default()
1752 }]
1753 }),
1754 )
1755 }
1756
1757 fn tool_workspace_add(&mut self, id: Value, args: &Value) -> JsonRpcResponse {
1759 let ws_id = match self.resolve_workspace_id(args) {
1760 Ok(ws) => ws,
1761 Err(e) => return JsonRpcResponse::error(id, e),
1762 };
1763
1764 let graph_name = match args.get("graph").and_then(|v| v.as_str()) {
1765 Some(n) if !n.trim().is_empty() => n.to_string(),
1766 _ => {
1767 return JsonRpcResponse::error(
1768 id,
1769 JsonRpcError::invalid_params("Missing or empty 'graph' argument"),
1770 );
1771 }
1772 };
1773
1774 let graph = match self.graphs.get(&graph_name) {
1776 Some(g) => g.clone(),
1777 None => {
1778 return JsonRpcResponse::error(
1779 id,
1780 JsonRpcError::invalid_params(format!("Graph not found: {}", graph_name)),
1781 );
1782 }
1783 };
1784
1785 let role_str = args
1786 .get("role")
1787 .and_then(|v| v.as_str())
1788 .unwrap_or("source");
1789 let role = match ContextRole::parse_str(role_str) {
1790 Some(r) => r,
1791 None => {
1792 return JsonRpcResponse::error(
1793 id,
1794 JsonRpcError::invalid_params(format!(
1795 "Invalid role '{}'. Expected: source, target, reference, comparison",
1796 role_str
1797 )),
1798 );
1799 }
1800 };
1801
1802 let path = args
1803 .get("path")
1804 .and_then(|v| v.as_str())
1805 .unwrap_or(&graph_name)
1806 .to_string();
1807 let language = args
1808 .get("language")
1809 .and_then(|v| v.as_str())
1810 .map(String::from);
1811
1812 match self
1813 .workspace_manager
1814 .add_context(&ws_id, &path, role, language, graph)
1815 {
1816 Ok(ctx_id) => JsonRpcResponse::success(
1817 id,
1818 json!({
1819 "content": [{
1820 "type": "text",
1821 "text": serde_json::to_string_pretty(&json!({
1822 "context_id": ctx_id,
1823 "workspace_id": ws_id,
1824 "graph": graph_name,
1825 "message": "Context added to workspace"
1826 })).unwrap_or_default()
1827 }]
1828 }),
1829 ),
1830 Err(e) => JsonRpcResponse::error(id, JsonRpcError::invalid_params(e)),
1831 }
1832 }
1833
1834 fn tool_workspace_list(&self, id: Value, args: &Value) -> JsonRpcResponse {
1836 let ws_id = match self.resolve_workspace_id(args) {
1837 Ok(ws) => ws,
1838 Err(e) => return JsonRpcResponse::error(id, e),
1839 };
1840
1841 match self.workspace_manager.list(&ws_id) {
1842 Ok(workspace) => {
1843 let contexts: Vec<Value> = workspace
1844 .contexts
1845 .iter()
1846 .map(|c| {
1847 json!({
1848 "id": c.id,
1849 "role": c.role.label(),
1850 "path": c.path,
1851 "language": c.language,
1852 "unit_count": c.graph.units().len(),
1853 })
1854 })
1855 .collect();
1856
1857 JsonRpcResponse::success(
1858 id,
1859 json!({
1860 "content": [{
1861 "type": "text",
1862 "text": serde_json::to_string_pretty(&json!({
1863 "workspace_id": ws_id,
1864 "name": workspace.name,
1865 "context_count": workspace.contexts.len(),
1866 "contexts": contexts,
1867 })).unwrap_or_default()
1868 }]
1869 }),
1870 )
1871 }
1872 Err(e) => JsonRpcResponse::error(id, JsonRpcError::invalid_params(e)),
1873 }
1874 }
1875
1876 fn tool_workspace_query(&self, id: Value, args: &Value) -> JsonRpcResponse {
1878 let ws_id = match self.resolve_workspace_id(args) {
1879 Ok(ws) => ws,
1880 Err(e) => return JsonRpcResponse::error(id, e),
1881 };
1882
1883 let query = match args.get("query").and_then(|v| v.as_str()) {
1884 Some(q) if !q.trim().is_empty() => q,
1885 _ => {
1886 return JsonRpcResponse::error(
1887 id,
1888 JsonRpcError::invalid_params("Missing or empty 'query' argument"),
1889 );
1890 }
1891 };
1892
1893 let role_filters: Vec<String> = args
1894 .get("roles")
1895 .and_then(|v| v.as_array())
1896 .map(|arr| {
1897 arr.iter()
1898 .filter_map(|v| v.as_str().map(|s| s.to_lowercase()))
1899 .collect()
1900 })
1901 .unwrap_or_default();
1902
1903 match self.workspace_manager.query_all(&ws_id, query) {
1904 Ok(results) => {
1905 let mut filtered = results;
1906 if !role_filters.is_empty() {
1907 filtered.retain(|r| role_filters.contains(&r.context_role.label().to_string()));
1908 }
1909
1910 let output: Vec<Value> = filtered
1911 .iter()
1912 .map(|r| {
1913 json!({
1914 "context_id": r.context_id,
1915 "role": r.context_role.label(),
1916 "matches": r.matches.iter().map(|m| json!({
1917 "unit_id": m.unit_id,
1918 "name": m.name,
1919 "qualified_name": m.qualified_name,
1920 "type": m.unit_type,
1921 "file": m.file_path,
1922 })).collect::<Vec<_>>(),
1923 })
1924 })
1925 .collect();
1926
1927 JsonRpcResponse::success(
1928 id,
1929 json!({
1930 "content": [{
1931 "type": "text",
1932 "text": serde_json::to_string_pretty(&output).unwrap_or_default()
1933 }]
1934 }),
1935 )
1936 }
1937 Err(e) => JsonRpcResponse::error(id, JsonRpcError::invalid_params(e)),
1938 }
1939 }
1940
1941 fn tool_workspace_compare(&self, id: Value, args: &Value) -> JsonRpcResponse {
1943 let ws_id = match self.resolve_workspace_id(args) {
1944 Ok(ws) => ws,
1945 Err(e) => return JsonRpcResponse::error(id, e),
1946 };
1947
1948 let symbol = match args.get("symbol").and_then(|v| v.as_str()) {
1949 Some(s) if !s.trim().is_empty() => s,
1950 _ => {
1951 return JsonRpcResponse::error(
1952 id,
1953 JsonRpcError::invalid_params("Missing or empty 'symbol' argument"),
1954 );
1955 }
1956 };
1957
1958 match self.workspace_manager.compare(&ws_id, symbol) {
1959 Ok(cmp) => {
1960 let contexts: Vec<Value> = cmp
1961 .contexts
1962 .iter()
1963 .map(|c| {
1964 json!({
1965 "context_id": c.context_id,
1966 "role": c.role.label(),
1967 "found": c.found,
1968 "unit_type": c.unit_type,
1969 "signature": c.signature,
1970 "file_path": c.file_path,
1971 })
1972 })
1973 .collect();
1974
1975 JsonRpcResponse::success(
1976 id,
1977 json!({
1978 "content": [{
1979 "type": "text",
1980 "text": serde_json::to_string_pretty(&json!({
1981 "symbol": cmp.symbol,
1982 "semantic_match": cmp.semantic_match,
1983 "structural_diff": cmp.structural_diff,
1984 "contexts": contexts,
1985 })).unwrap_or_default()
1986 }]
1987 }),
1988 )
1989 }
1990 Err(e) => JsonRpcResponse::error(id, JsonRpcError::invalid_params(e)),
1991 }
1992 }
1993
1994 fn tool_workspace_xref(&self, id: Value, args: &Value) -> JsonRpcResponse {
1996 let ws_id = match self.resolve_workspace_id(args) {
1997 Ok(ws) => ws,
1998 Err(e) => return JsonRpcResponse::error(id, e),
1999 };
2000
2001 let symbol = match args.get("symbol").and_then(|v| v.as_str()) {
2002 Some(s) if !s.trim().is_empty() => s,
2003 _ => {
2004 return JsonRpcResponse::error(
2005 id,
2006 JsonRpcError::invalid_params("Missing or empty 'symbol' argument"),
2007 );
2008 }
2009 };
2010
2011 match self.workspace_manager.cross_reference(&ws_id, symbol) {
2012 Ok(xref) => {
2013 let found: Vec<Value> = xref
2014 .found_in
2015 .iter()
2016 .map(|(ctx_id, role)| json!({"context_id": ctx_id, "role": role.label()}))
2017 .collect();
2018 let missing: Vec<Value> = xref
2019 .missing_from
2020 .iter()
2021 .map(|(ctx_id, role)| json!({"context_id": ctx_id, "role": role.label()}))
2022 .collect();
2023
2024 JsonRpcResponse::success(
2025 id,
2026 json!({
2027 "content": [{
2028 "type": "text",
2029 "text": serde_json::to_string_pretty(&json!({
2030 "symbol": xref.symbol,
2031 "found_in": found,
2032 "missing_from": missing,
2033 })).unwrap_or_default()
2034 }]
2035 }),
2036 )
2037 }
2038 Err(e) => JsonRpcResponse::error(id, JsonRpcError::invalid_params(e)),
2039 }
2040 }
2041
2042 fn tool_translation_record(&mut self, id: Value, args: &Value) -> JsonRpcResponse {
2048 let ws_id = match self.resolve_workspace_id(args) {
2049 Ok(ws) => ws,
2050 Err(e) => return JsonRpcResponse::error(id, e),
2051 };
2052
2053 let source_symbol = match args.get("source_symbol").and_then(|v| v.as_str()) {
2054 Some(s) if !s.trim().is_empty() => s,
2055 _ => {
2056 return JsonRpcResponse::error(
2057 id,
2058 JsonRpcError::invalid_params("Missing or empty 'source_symbol' argument"),
2059 );
2060 }
2061 };
2062
2063 let target_symbol = args.get("target_symbol").and_then(|v| v.as_str());
2064
2065 let status_str = args
2066 .get("status")
2067 .and_then(|v| v.as_str())
2068 .unwrap_or("not_started");
2069 let status = match TranslationStatus::parse_str(status_str) {
2070 Some(s) => s,
2071 None => {
2072 return JsonRpcResponse::error(
2073 id,
2074 JsonRpcError::invalid_params(format!(
2075 "Invalid status '{}'. Expected: not_started, in_progress, ported, verified, skipped",
2076 status_str
2077 )),
2078 );
2079 }
2080 };
2081
2082 let notes = args.get("notes").and_then(|v| v.as_str()).map(String::from);
2083
2084 let tmap = self
2087 .translation_maps
2088 .entry(ws_id.clone())
2089 .or_insert_with(|| {
2090 let (src, tgt) = if let Ok(ws) = self.workspace_manager.list(&ws_id) {
2092 let src = ws
2093 .contexts
2094 .iter()
2095 .find(|c| c.role == ContextRole::Source)
2096 .map(|c| c.id.clone())
2097 .unwrap_or_default();
2098 let tgt = ws
2099 .contexts
2100 .iter()
2101 .find(|c| c.role == ContextRole::Target)
2102 .map(|c| c.id.clone())
2103 .unwrap_or_default();
2104 (src, tgt)
2105 } else {
2106 (String::new(), String::new())
2107 };
2108 TranslationMap::new(src, tgt)
2109 });
2110
2111 tmap.record(source_symbol, target_symbol, status, notes);
2112
2113 JsonRpcResponse::success(
2114 id,
2115 json!({
2116 "content": [{
2117 "type": "text",
2118 "text": serde_json::to_string_pretty(&json!({
2119 "source_symbol": source_symbol,
2120 "target_symbol": target_symbol,
2121 "status": status_str,
2122 "message": "Translation mapping recorded"
2123 })).unwrap_or_default()
2124 }]
2125 }),
2126 )
2127 }
2128
2129 fn tool_translation_progress(&self, id: Value, args: &Value) -> JsonRpcResponse {
2131 let ws_id = match self.resolve_workspace_id(args) {
2132 Ok(ws) => ws,
2133 Err(e) => return JsonRpcResponse::error(id, e),
2134 };
2135
2136 let progress = match self.translation_maps.get(&ws_id) {
2137 Some(tmap) => tmap.progress(),
2138 None => {
2139 crate::workspace::TranslationProgress {
2141 total: 0,
2142 not_started: 0,
2143 in_progress: 0,
2144 ported: 0,
2145 verified: 0,
2146 skipped: 0,
2147 percent_complete: 0.0,
2148 }
2149 }
2150 };
2151
2152 JsonRpcResponse::success(
2153 id,
2154 json!({
2155 "content": [{
2156 "type": "text",
2157 "text": serde_json::to_string_pretty(&json!({
2158 "workspace": ws_id,
2159 "total": progress.total,
2160 "not_started": progress.not_started,
2161 "in_progress": progress.in_progress,
2162 "ported": progress.ported,
2163 "verified": progress.verified,
2164 "skipped": progress.skipped,
2165 "percent_complete": progress.percent_complete,
2166 })).unwrap_or_default()
2167 }]
2168 }),
2169 )
2170 }
2171
2172 fn tool_translation_remaining(&self, id: Value, args: &Value) -> JsonRpcResponse {
2174 let ws_id = match self.resolve_workspace_id(args) {
2175 Ok(ws) => ws,
2176 Err(e) => return JsonRpcResponse::error(id, e),
2177 };
2178
2179 let module_filter = args
2180 .get("module")
2181 .and_then(|v| v.as_str())
2182 .map(|s| s.to_lowercase());
2183
2184 let remaining = match self.translation_maps.get(&ws_id) {
2185 Some(tmap) => {
2186 let mut items = tmap.remaining();
2187 if let Some(ref module) = module_filter {
2188 items.retain(|m| m.source_symbol.to_lowercase().contains(module.as_str()));
2189 }
2190 items
2191 .iter()
2192 .map(|m| {
2193 json!({
2194 "source_symbol": m.source_symbol,
2195 "status": m.status.label(),
2196 "notes": m.notes,
2197 })
2198 })
2199 .collect::<Vec<_>>()
2200 }
2201 None => Vec::new(),
2202 };
2203
2204 JsonRpcResponse::success(
2205 id,
2206 json!({
2207 "content": [{
2208 "type": "text",
2209 "text": serde_json::to_string_pretty(&json!({
2210 "workspace": ws_id,
2211 "remaining_count": remaining.len(),
2212 "remaining": remaining,
2213 })).unwrap_or_default()
2214 }]
2215 }),
2216 )
2217 }
2218
2219 fn tool_impact_analyze(&self, id: Value, args: &Value) -> JsonRpcResponse {
2226 let (_, graph) = match self.resolve_graph(args) {
2227 Ok(g) => g,
2228 Err(e) => return JsonRpcResponse::error(id, e),
2229 };
2230 let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
2231 let max_depth = args.get("max_depth").and_then(|v| v.as_u64()).unwrap_or(5) as u32;
2232 let change_type_str = args
2233 .get("change_type")
2234 .and_then(|v| v.as_str())
2235 .unwrap_or("behavior");
2236 let change_type = match change_type_str {
2237 "signature" => crate::engine::impact::ChangeType::Signature,
2238 "deletion" => crate::engine::impact::ChangeType::Deletion,
2239 "rename" => crate::engine::impact::ChangeType::Rename,
2240 "move" => crate::engine::impact::ChangeType::Move,
2241 _ => crate::engine::impact::ChangeType::Behavior,
2242 };
2243
2244 let analyzer = crate::engine::impact::ImpactAnalyzer::new(graph);
2245 let change = crate::engine::impact::ProposedChange {
2246 target: unit_id,
2247 change_type,
2248 description: format!("Proposed {} change to unit {}", change_type_str, unit_id),
2249 };
2250 let result = analyzer.analyze(change, max_depth);
2251 let viz = analyzer.visualize(&result);
2252
2253 JsonRpcResponse::success(
2254 id,
2255 json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&viz).unwrap_or_default() }] }),
2256 )
2257 }
2258
2259 fn tool_impact_path(&self, id: Value, args: &Value) -> JsonRpcResponse {
2260 let (_, graph) = match self.resolve_graph(args) {
2261 Ok(g) => g,
2262 Err(e) => return JsonRpcResponse::error(id, e),
2263 };
2264 let from = args.get("from").and_then(|v| v.as_u64()).unwrap_or(0);
2265 let to = args.get("to").and_then(|v| v.as_u64()).unwrap_or(0);
2266
2267 let analyzer = crate::engine::impact::ImpactAnalyzer::new(graph);
2268 let path = analyzer.impact_path(from, to);
2269
2270 JsonRpcResponse::success(
2271 id,
2272 json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "from": from, "to": to, "path": path })).unwrap_or_default() }] }),
2273 )
2274 }
2275
2276 fn tool_prophecy(&self, id: Value, args: &Value) -> JsonRpcResponse {
2279 let (_, graph) = match self.resolve_graph(args) {
2280 Ok(g) => g,
2281 Err(e) => return JsonRpcResponse::error(id, e),
2282 };
2283 let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
2284
2285 let engine = crate::temporal::prophecy_v2::EnhancedProphecyEngine::new(graph);
2286 let subject = crate::temporal::prophecy_v2::ProphecySubject::Node(unit_id);
2287 let horizon = crate::temporal::prophecy_v2::ProphecyHorizon::MediumTerm;
2288 let result = engine.prophecy(subject, horizon);
2289
2290 JsonRpcResponse::success(
2291 id,
2292 json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&result).unwrap_or_default() }] }),
2293 )
2294 }
2295
2296 fn tool_prophecy_if(&self, id: Value, args: &Value) -> JsonRpcResponse {
2297 let (_, graph) = match self.resolve_graph(args) {
2298 Ok(g) => g,
2299 Err(e) => return JsonRpcResponse::error(id, e),
2300 };
2301 let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
2302 let change_type_str = args
2303 .get("change_type")
2304 .and_then(|v| v.as_str())
2305 .unwrap_or("behavior");
2306
2307 let engine = crate::temporal::prophecy_v2::EnhancedProphecyEngine::new(graph);
2308 let subject = crate::temporal::prophecy_v2::ProphecySubject::Node(unit_id);
2309 let horizon = crate::temporal::prophecy_v2::ProphecyHorizon::MediumTerm;
2310 let scenario = format!("{} change to unit {}", change_type_str, unit_id);
2311 let result = engine.prophecy_if(subject, &scenario, horizon);
2312
2313 JsonRpcResponse::success(
2314 id,
2315 json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&result).unwrap_or_default() }] }),
2316 )
2317 }
2318
2319 fn tool_regression_predict(&self, id: Value, args: &Value) -> JsonRpcResponse {
2322 let (_, graph) = match self.resolve_graph(args) {
2323 Ok(g) => g,
2324 Err(e) => return JsonRpcResponse::error(id, e),
2325 };
2326 let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
2327 let max_depth = args.get("max_depth").and_then(|v| v.as_u64()).unwrap_or(5) as u32;
2328
2329 let predictor = crate::engine::regression::RegressionPredictor::new(graph);
2330 let oracle = predictor.predict(unit_id, max_depth);
2331
2332 let results: Vec<Value> = oracle
2333 .likely_failures
2334 .iter()
2335 .map(|p| {
2336 json!({
2337 "test_id": p.test.unit_id,
2338 "test_function": p.test.function,
2339 "test_file": p.test.file,
2340 "failure_probability": p.failure_probability,
2341 "reason": p.reason,
2342 })
2343 })
2344 .collect();
2345
2346 JsonRpcResponse::success(
2347 id,
2348 json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "changed_unit": unit_id, "likely_failures": results, "safe_to_skip": oracle.safe_to_skip.len() })).unwrap_or_default() }] }),
2349 )
2350 }
2351
2352 fn tool_regression_minimal(&self, id: Value, args: &Value) -> JsonRpcResponse {
2353 let (_, graph) = match self.resolve_graph(args) {
2354 Ok(g) => g,
2355 Err(e) => return JsonRpcResponse::error(id, e),
2356 };
2357 let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
2358 let _threshold = args
2359 .get("threshold")
2360 .and_then(|v| v.as_f64())
2361 .unwrap_or(0.5);
2362
2363 let predictor = crate::engine::regression::RegressionPredictor::new(graph);
2364 let minimal = predictor.minimal_test_set(unit_id);
2365
2366 let results: Vec<Value> = minimal
2367 .iter()
2368 .map(|t| {
2369 json!({
2370 "test_id": t.unit_id,
2371 "test_function": t.function,
2372 "test_file": t.file,
2373 })
2374 })
2375 .collect();
2376
2377 JsonRpcResponse::success(
2378 id,
2379 json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "changed_unit": unit_id, "minimal_tests": results })).unwrap_or_default() }] }),
2380 )
2381 }
2382
2383 fn tool_codebase_ground_claim(&self, id: Value, args: &Value) -> JsonRpcResponse {
2386 let (_, graph) = match self.resolve_graph(args) {
2387 Ok(g) => g,
2388 Err(e) => return JsonRpcResponse::error(id, e),
2389 };
2390 let claim = args.get("claim").and_then(|v| v.as_str()).unwrap_or("");
2391
2392 let engine = crate::grounding::citation::CitationEngine::new(graph);
2393 let result = engine.ground_claim(claim);
2394
2395 JsonRpcResponse::success(
2396 id,
2397 json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&result).unwrap_or_default() }] }),
2398 )
2399 }
2400
2401 fn tool_codebase_cite(&self, id: Value, args: &Value) -> JsonRpcResponse {
2402 let (_, graph) = match self.resolve_graph(args) {
2403 Ok(g) => g,
2404 Err(e) => return JsonRpcResponse::error(id, e),
2405 };
2406 let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
2407
2408 let engine = crate::grounding::citation::CitationEngine::new(graph);
2409 let citation = engine.cite_node(unit_id);
2410
2411 JsonRpcResponse::success(
2412 id,
2413 json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "unit_id": unit_id, "citation": citation })).unwrap_or_default() }] }),
2414 )
2415 }
2416
2417 fn tool_hallucination_check(&self, id: Value, args: &Value) -> JsonRpcResponse {
2420 let (_, graph) = match self.resolve_graph(args) {
2421 Ok(g) => g,
2422 Err(e) => return JsonRpcResponse::error(id, e),
2423 };
2424 let output = args.get("output").and_then(|v| v.as_str()).unwrap_or("");
2425
2426 let detector = crate::grounding::hallucination::HallucinationDetector::new(graph);
2427 let result = detector.check_output(output);
2428
2429 JsonRpcResponse::success(
2430 id,
2431 json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&result).unwrap_or_default() }] }),
2432 )
2433 }
2434
2435 fn tool_truth_register(&self, id: Value, args: &Value) -> JsonRpcResponse {
2438 let (_, graph) = match self.resolve_graph(args) {
2439 Ok(g) => g,
2440 Err(e) => return JsonRpcResponse::error(id, e),
2441 };
2442 let claim = args.get("claim").and_then(|v| v.as_str()).unwrap_or("");
2443
2444 let mut maintainer = crate::grounding::truth::TruthMaintainer::new(graph);
2445 let truth = maintainer.register_truth(claim);
2446
2447 JsonRpcResponse::success(
2448 id,
2449 json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&truth).unwrap_or_default() }] }),
2450 )
2451 }
2452
2453 fn tool_truth_check(&self, id: Value, args: &Value) -> JsonRpcResponse {
2454 let (_, graph) = match self.resolve_graph(args) {
2455 Ok(g) => g,
2456 Err(e) => return JsonRpcResponse::error(id, e),
2457 };
2458 let claim = args.get("claim").and_then(|v| v.as_str()).unwrap_or("");
2459
2460 let maintainer = crate::grounding::truth::TruthMaintainer::new(graph);
2461 let result = maintainer.check_truth(claim);
2462
2463 JsonRpcResponse::success(
2464 id,
2465 json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "claim": claim, "status": format!("{:?}", result) })).unwrap_or_default() }] }),
2466 )
2467 }
2468
2469 fn tool_concept_find(&self, id: Value, args: &Value) -> JsonRpcResponse {
2472 let (_, graph) = match self.resolve_graph(args) {
2473 Ok(g) => g,
2474 Err(e) => return JsonRpcResponse::error(id, e),
2475 };
2476 let concept = args.get("concept").and_then(|v| v.as_str()).unwrap_or("");
2477
2478 let navigator = crate::semantic::concept_nav::ConceptNavigator::new(graph);
2479 let query = crate::semantic::concept_nav::ConceptQuery {
2480 description: concept.to_string(),
2481 constraints: Vec::new(),
2482 };
2483 let result = navigator.find_concept(query);
2484
2485 JsonRpcResponse::success(
2486 id,
2487 json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&result).unwrap_or_default() }] }),
2488 )
2489 }
2490
2491 fn tool_concept_map(&self, id: Value, args: &Value) -> JsonRpcResponse {
2492 let (_, graph) = match self.resolve_graph(args) {
2493 Ok(g) => g,
2494 Err(e) => return JsonRpcResponse::error(id, e),
2495 };
2496
2497 let navigator = crate::semantic::concept_nav::ConceptNavigator::new(graph);
2498 let concepts = navigator.map_all_concepts();
2499
2500 let results: Vec<Value> = concepts
2501 .iter()
2502 .map(|c| {
2503 json!({
2504 "name": c.name,
2505 "description": c.description,
2506 "implementation_count": c.implementations.len(),
2507 })
2508 })
2509 .collect();
2510
2511 JsonRpcResponse::success(
2512 id,
2513 json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "concepts": results })).unwrap_or_default() }] }),
2514 )
2515 }
2516
2517 fn tool_concept_explain(&self, id: Value, args: &Value) -> JsonRpcResponse {
2518 let (_, graph) = match self.resolve_graph(args) {
2519 Ok(g) => g,
2520 Err(e) => return JsonRpcResponse::error(id, e),
2521 };
2522 let concept = args.get("concept").and_then(|v| v.as_str()).unwrap_or("");
2523
2524 let navigator = crate::semantic::concept_nav::ConceptNavigator::new(graph);
2525 let result = navigator.explain_concept(concept);
2526
2527 JsonRpcResponse::success(
2528 id,
2529 json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&result).unwrap_or_default() }] }),
2530 )
2531 }
2532
2533 fn tool_architecture_infer(&self, id: Value, args: &Value) -> JsonRpcResponse {
2536 let (_, graph) = match self.resolve_graph(args) {
2537 Ok(g) => g,
2538 Err(e) => return JsonRpcResponse::error(id, e),
2539 };
2540
2541 let inferrer = crate::semantic::architecture::ArchitectureInferrer::new(graph);
2542 let architecture = inferrer.infer();
2543 let diagram = inferrer.diagram(&architecture);
2544
2545 JsonRpcResponse::success(
2546 id,
2547 json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "architecture": architecture, "diagram": diagram })).unwrap_or_default() }] }),
2548 )
2549 }
2550
2551 fn tool_architecture_validate(&self, id: Value, args: &Value) -> JsonRpcResponse {
2552 let (_, graph) = match self.resolve_graph(args) {
2553 Ok(g) => g,
2554 Err(e) => return JsonRpcResponse::error(id, e),
2555 };
2556
2557 let inferrer = crate::semantic::architecture::ArchitectureInferrer::new(graph);
2558 let architecture = inferrer.infer();
2559 let anomalies = inferrer.validate(architecture.pattern);
2560
2561 let results: Vec<Value> = anomalies
2562 .iter()
2563 .map(|a| {
2564 json!({
2565 "description": a.description,
2566 "severity": format!("{:?}", a.severity),
2567 "expected": a.expected,
2568 "actual": a.actual,
2569 })
2570 })
2571 .collect();
2572
2573 JsonRpcResponse::success(
2574 id,
2575 json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "anomalies": results, "count": results.len() })).unwrap_or_default() }] }),
2576 )
2577 }
2578
2579 fn tool_search_semantic(&self, id: Value, args: &Value) -> JsonRpcResponse {
2582 let (_, graph) = match self.resolve_graph(args) {
2583 Ok(g) => g,
2584 Err(e) => return JsonRpcResponse::error(id, e),
2585 };
2586 let query = args.get("query").and_then(|v| v.as_str()).unwrap_or("");
2587 let top_k = args.get("top_k").and_then(|v| v.as_u64()).unwrap_or(10) as usize;
2588
2589 let engine = crate::index::semantic_search::SemanticSearchEngine::new(graph);
2590 let result = engine.search(query, top_k);
2591
2592 JsonRpcResponse::success(
2593 id,
2594 json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&result).unwrap_or_default() }] }),
2595 )
2596 }
2597
2598 fn tool_search_similar(&self, id: Value, args: &Value) -> JsonRpcResponse {
2599 let (_, graph) = match self.resolve_graph(args) {
2600 Ok(g) => g,
2601 Err(e) => return JsonRpcResponse::error(id, e),
2602 };
2603 let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
2604 let top_k = args.get("top_k").and_then(|v| v.as_u64()).unwrap_or(10) as usize;
2605
2606 let engine = crate::index::semantic_search::SemanticSearchEngine::new(graph);
2607 let results = engine.find_similar(unit_id, top_k);
2608
2609 JsonRpcResponse::success(
2610 id,
2611 json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "unit_id": unit_id, "similar": results })).unwrap_or_default() }] }),
2612 )
2613 }
2614
2615 fn tool_search_explain(&self, id: Value, args: &Value) -> JsonRpcResponse {
2616 let (_, graph) = match self.resolve_graph(args) {
2617 Ok(g) => g,
2618 Err(e) => return JsonRpcResponse::error(id, e),
2619 };
2620 let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
2621 let query = args.get("query").and_then(|v| v.as_str()).unwrap_or("");
2622
2623 let engine = crate::index::semantic_search::SemanticSearchEngine::new(graph);
2624 let explanation = engine.explain_match(unit_id, query);
2625
2626 JsonRpcResponse::success(
2627 id,
2628 json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "unit_id": unit_id, "query": query, "explanation": explanation })).unwrap_or_default() }] }),
2629 )
2630 }
2631
2632 fn tool_compare_codebases(&self, id: Value, args: &Value) -> JsonRpcResponse {
2635 let ws_id = match self.resolve_workspace_id(args) {
2636 Ok(id) => id,
2637 Err(e) => return JsonRpcResponse::error(id, e),
2638 };
2639 let workspace = match self.workspace_manager.list(&ws_id) {
2640 Ok(ws) => ws,
2641 Err(e) => return JsonRpcResponse::error(id, JsonRpcError::invalid_params(e)),
2642 };
2643 if workspace.contexts.len() < 2 {
2644 return JsonRpcResponse::error(
2645 id,
2646 JsonRpcError::invalid_params("Need at least 2 contexts in workspace to compare"),
2647 );
2648 }
2649
2650 let comparer = crate::workspace::compare::CodebaseComparer::new(
2651 &workspace.contexts[0].graph,
2652 &workspace.contexts[0].id,
2653 &workspace.contexts[1].graph,
2654 &workspace.contexts[1].id,
2655 );
2656 let result = comparer.compare();
2657
2658 JsonRpcResponse::success(
2659 id,
2660 json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&result).unwrap_or_default() }] }),
2661 )
2662 }
2663
2664 fn tool_compare_concept(&self, id: Value, args: &Value) -> JsonRpcResponse {
2665 let ws_id = match self.resolve_workspace_id(args) {
2666 Ok(id) => id,
2667 Err(e) => return JsonRpcResponse::error(id, e),
2668 };
2669 let concept = args.get("concept").and_then(|v| v.as_str()).unwrap_or("");
2670 let workspace = match self.workspace_manager.list(&ws_id) {
2671 Ok(ws) => ws,
2672 Err(e) => return JsonRpcResponse::error(id, JsonRpcError::invalid_params(e)),
2673 };
2674 if workspace.contexts.len() < 2 {
2675 return JsonRpcResponse::error(
2676 id,
2677 JsonRpcError::invalid_params("Need at least 2 contexts"),
2678 );
2679 }
2680
2681 let comparer = crate::workspace::compare::CodebaseComparer::new(
2682 &workspace.contexts[0].graph,
2683 &workspace.contexts[0].id,
2684 &workspace.contexts[1].graph,
2685 &workspace.contexts[1].id,
2686 );
2687 let result = comparer.compare_concept(concept);
2688
2689 JsonRpcResponse::success(
2690 id,
2691 json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&result).unwrap_or_default() }] }),
2692 )
2693 }
2694
2695 fn tool_compare_migrate(&self, id: Value, args: &Value) -> JsonRpcResponse {
2696 let ws_id = match self.resolve_workspace_id(args) {
2697 Ok(id) => id,
2698 Err(e) => return JsonRpcResponse::error(id, e),
2699 };
2700 let workspace = match self.workspace_manager.list(&ws_id) {
2701 Ok(ws) => ws,
2702 Err(e) => return JsonRpcResponse::error(id, JsonRpcError::invalid_params(e)),
2703 };
2704 if workspace.contexts.len() < 2 {
2705 return JsonRpcResponse::error(
2706 id,
2707 JsonRpcError::invalid_params("Need at least 2 contexts"),
2708 );
2709 }
2710
2711 let comparer = crate::workspace::compare::CodebaseComparer::new(
2712 &workspace.contexts[0].graph,
2713 &workspace.contexts[0].id,
2714 &workspace.contexts[1].graph,
2715 &workspace.contexts[1].id,
2716 );
2717 let plan = comparer.migration_plan();
2718
2719 JsonRpcResponse::success(
2720 id,
2721 json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&plan).unwrap_or_default() }] }),
2722 )
2723 }
2724
2725 fn tool_archaeology_node(&self, id: Value, args: &Value) -> JsonRpcResponse {
2728 let (_, graph) = match self.resolve_graph(args) {
2729 Ok(g) => g,
2730 Err(e) => return JsonRpcResponse::error(id, e),
2731 };
2732 let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
2733
2734 let history = crate::temporal::history::ChangeHistory::new();
2735 let archaeologist = crate::temporal::archaeology::CodeArchaeologist::new(graph, history);
2736 let result = archaeologist.investigate(unit_id);
2737
2738 JsonRpcResponse::success(
2739 id,
2740 json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "unit_id": unit_id, "result": result })).unwrap_or_default() }] }),
2741 )
2742 }
2743
2744 fn tool_archaeology_why(&self, id: Value, args: &Value) -> JsonRpcResponse {
2745 let (_, graph) = match self.resolve_graph(args) {
2746 Ok(g) => g,
2747 Err(e) => return JsonRpcResponse::error(id, e),
2748 };
2749 let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
2750
2751 let history = crate::temporal::history::ChangeHistory::new();
2752 let archaeologist = crate::temporal::archaeology::CodeArchaeologist::new(graph, history);
2753 let result = archaeologist.investigate(unit_id);
2754 let explanation = result
2755 .map(|r| r.why_explanation)
2756 .unwrap_or_else(|| "Unit not found".to_string());
2757
2758 JsonRpcResponse::success(
2759 id,
2760 json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "unit_id": unit_id, "explanation": explanation })).unwrap_or_default() }] }),
2761 )
2762 }
2763
2764 fn tool_archaeology_when(&self, id: Value, args: &Value) -> JsonRpcResponse {
2765 let (_, graph) = match self.resolve_graph(args) {
2766 Ok(g) => g,
2767 Err(e) => return JsonRpcResponse::error(id, e),
2768 };
2769 let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
2770
2771 let history = crate::temporal::history::ChangeHistory::new();
2772 let archaeologist = crate::temporal::archaeology::CodeArchaeologist::new(graph, history);
2773 let timeline = archaeologist.when_changed(unit_id);
2774
2775 JsonRpcResponse::success(
2776 id,
2777 json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "unit_id": unit_id, "timeline": timeline })).unwrap_or_default() }] }),
2778 )
2779 }
2780
2781 fn tool_pattern_extract(&self, id: Value, args: &Value) -> JsonRpcResponse {
2784 let (_, graph) = match self.resolve_graph(args) {
2785 Ok(g) => g,
2786 Err(e) => return JsonRpcResponse::error(id, e),
2787 };
2788
2789 let extractor = crate::semantic::pattern_extract::PatternExtractor::new(graph);
2790 let patterns = extractor.extract_patterns();
2791
2792 JsonRpcResponse::success(
2793 id,
2794 json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&patterns).unwrap_or_default() }] }),
2795 )
2796 }
2797
2798 fn tool_pattern_check(&self, id: Value, args: &Value) -> JsonRpcResponse {
2799 let (_, graph) = match self.resolve_graph(args) {
2800 Ok(g) => g,
2801 Err(e) => return JsonRpcResponse::error(id, e),
2802 };
2803 let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
2804
2805 let extractor = crate::semantic::pattern_extract::PatternExtractor::new(graph);
2806 let violations = extractor.check_patterns(unit_id);
2807
2808 JsonRpcResponse::success(
2809 id,
2810 json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "unit_id": unit_id, "violations": violations })).unwrap_or_default() }] }),
2811 )
2812 }
2813
2814 fn tool_pattern_suggest(&self, id: Value, args: &Value) -> JsonRpcResponse {
2815 let (_, graph) = match self.resolve_graph(args) {
2816 Ok(g) => g,
2817 Err(e) => return JsonRpcResponse::error(id, e),
2818 };
2819 let file_path = args.get("file_path").and_then(|v| v.as_str()).unwrap_or("");
2820
2821 let extractor = crate::semantic::pattern_extract::PatternExtractor::new(graph);
2822 let suggestions = extractor.suggest_patterns(file_path);
2823
2824 JsonRpcResponse::success(
2825 id,
2826 json!({ "content": [{ "type": "text", "text": serde_json::to_string_pretty(&json!({ "file_path": file_path, "suggestions": suggestions })).unwrap_or_default() }] }),
2827 )
2828 }
2829
2830 fn tool_resurrect_search(&self, id: Value, args: &Value) -> JsonRpcResponse {
2833 let (_, graph) = match self.resolve_graph(args) {
2834 Ok(g) => g,
2835 Err(e) => return JsonRpcResponse::error(id, e),
2836 };
2837 let query = args.get("query").and_then(|v| v.as_str()).unwrap_or("");
2838 let max_results = args
2839 .get("max_results")
2840 .and_then(|v| v.as_u64())
2841 .unwrap_or(10) as usize;
2842 let query_lower = query.to_lowercase();
2843 let mut traces: Vec<Value> = Vec::new();
2844 for unit in graph.units() {
2845 let name_lower = unit.name.to_lowercase();
2846 let doc_lower = unit.doc_summary.as_deref().unwrap_or("").to_lowercase();
2847 if name_lower.contains(&query_lower) || doc_lower.contains(&query_lower) {
2848 let is_deprecated = doc_lower.contains("deprecated")
2849 || doc_lower.contains("removed")
2850 || name_lower.contains("deprecated")
2851 || name_lower.starts_with("old_");
2852 traces.push(json!({"unit_id": unit.id, "name": unit.name, "type": unit.unit_type.label(), "file": unit.file_path.display().to_string(), "is_deprecated": is_deprecated, "doc": unit.doc_summary, "trace_type": if is_deprecated { "deprecated" } else { "reference" }}));
2853 if traces.len() >= max_results {
2854 break;
2855 }
2856 }
2857 }
2858 JsonRpcResponse::success(
2859 id,
2860 json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"query": query, "traces_found": traces.len(), "traces": traces})).unwrap_or_default()}]}),
2861 )
2862 }
2863
2864 fn tool_resurrect_attempt(&self, id: Value, args: &Value) -> JsonRpcResponse {
2865 let (_, graph) = match self.resolve_graph(args) {
2866 Ok(g) => g,
2867 Err(e) => return JsonRpcResponse::error(id, e),
2868 };
2869 let query = args.get("query").and_then(|v| v.as_str()).unwrap_or("");
2870 let query_lower = query.to_lowercase();
2871 let mut evidence: Vec<Value> = Vec::new();
2872 for unit in graph.units() {
2873 let name_lower = unit.name.to_lowercase();
2874 let doc_lower = unit.doc_summary.as_deref().unwrap_or("").to_lowercase();
2875 let sig_lower = unit.signature.as_deref().unwrap_or("").to_lowercase();
2876 if (unit.unit_type == CodeUnitType::Test
2877 && (name_lower.contains(&query_lower) || doc_lower.contains(&query_lower)))
2878 || (unit.unit_type == CodeUnitType::Doc && doc_lower.contains(&query_lower))
2879 || sig_lower.contains(&query_lower)
2880 {
2881 evidence.push(json!({"source": unit.unit_type.label(), "unit_id": unit.id, "name": unit.name, "signature": unit.signature, "doc": unit.doc_summary, "file": unit.file_path.display().to_string()}));
2882 }
2883 }
2884 let status = if evidence.is_empty() {
2885 "insufficient_evidence"
2886 } else {
2887 "partial_reconstruction"
2888 };
2889 let confidence = if evidence.len() > 5 {
2890 "high"
2891 } else if evidence.len() > 2 {
2892 "medium"
2893 } else {
2894 "low"
2895 };
2896 JsonRpcResponse::success(
2897 id,
2898 json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"query": query, "status": status, "confidence": confidence, "evidence_count": evidence.len(), "evidence": evidence})).unwrap_or_default()}]}),
2899 )
2900 }
2901
2902 fn tool_resurrect_verify(&self, id: Value, args: &Value) -> JsonRpcResponse {
2903 let (_, graph) = match self.resolve_graph(args) {
2904 Ok(g) => g,
2905 Err(e) => return JsonRpcResponse::error(id, e),
2906 };
2907 let original_name = args
2908 .get("original_name")
2909 .and_then(|v| v.as_str())
2910 .unwrap_or("");
2911 let reconstructed = args
2912 .get("reconstructed")
2913 .and_then(|v| v.as_str())
2914 .unwrap_or("");
2915 let name_lower = original_name.to_lowercase();
2916 let mut refs = 0u64;
2917 let mut has_tests = false;
2918 for unit in graph.units() {
2919 if unit.name.to_lowercase().contains(&name_lower) {
2920 refs += 1;
2921 if unit.unit_type == CodeUnitType::Test {
2922 has_tests = true;
2923 }
2924 }
2925 }
2926 let status = if refs > 0 {
2927 "plausible"
2928 } else {
2929 "unverifiable"
2930 };
2931 let confidence = if has_tests && refs > 2 {
2932 "high"
2933 } else if refs > 0 {
2934 "medium"
2935 } else {
2936 "low"
2937 };
2938 JsonRpcResponse::success(
2939 id,
2940 json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"original_name": original_name, "reconstructed_length": reconstructed.len(), "references": refs, "has_tests": has_tests, "status": status, "confidence": confidence})).unwrap_or_default()}]}),
2941 )
2942 }
2943
2944 fn tool_resurrect_history(&self, id: Value, args: &Value) -> JsonRpcResponse {
2945 let (_, graph) = match self.resolve_graph(args) {
2946 Ok(g) => g,
2947 Err(e) => return JsonRpcResponse::error(id, e),
2948 };
2949 let mut versions: Vec<Value> = Vec::new();
2950 for edge in graph.edges() {
2951 if edge.edge_type == EdgeType::VersionOf {
2952 let src = graph
2953 .get_unit(edge.source_id)
2954 .map(|u| u.name.as_str())
2955 .unwrap_or("?");
2956 let tgt = graph
2957 .get_unit(edge.target_id)
2958 .map(|u| u.name.as_str())
2959 .unwrap_or("?");
2960 versions.push(json!({"newer_id": edge.source_id, "newer": src, "older_id": edge.target_id, "older": tgt}));
2961 }
2962 }
2963 let mut deprecated: Vec<Value> = Vec::new();
2964 for unit in graph.units() {
2965 let doc = unit.doc_summary.as_deref().unwrap_or("").to_lowercase();
2966 if doc.contains("deprecated") || unit.name.to_lowercase().contains("deprecated") {
2967 deprecated.push(
2968 json!({"unit_id": unit.id, "name": unit.name, "type": unit.unit_type.label()}),
2969 );
2970 }
2971 }
2972 JsonRpcResponse::success(
2973 id,
2974 json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"versions": versions, "deprecated": deprecated})).unwrap_or_default()}]}),
2975 )
2976 }
2977
2978 fn tool_genetics_dna(&self, id: Value, args: &Value) -> JsonRpcResponse {
2981 let (_, graph) = match self.resolve_graph(args) {
2982 Ok(g) => g,
2983 Err(e) => return JsonRpcResponse::error(id, e),
2984 };
2985 let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
2986 let unit = match graph.get_unit(unit_id) {
2987 Some(u) => u,
2988 None => {
2989 return JsonRpcResponse::error(
2990 id,
2991 JsonRpcError::invalid_params(format!("Unit {} not found", unit_id)),
2992 )
2993 }
2994 };
2995 let outgoing = graph.edges_from(unit_id);
2996 let incoming = graph.edges_to(unit_id);
2997 let mut out_types: Vec<&str> = outgoing.iter().map(|e| e.edge_type.label()).collect();
2998 out_types.sort();
2999 out_types.dedup();
3000 let mut in_types: Vec<&str> = incoming.iter().map(|e| e.edge_type.label()).collect();
3001 in_types.sort();
3002 in_types.dedup();
3003 let naming = if unit.name.contains('_') {
3004 "snake_case"
3005 } else if unit
3006 .name
3007 .chars()
3008 .next()
3009 .map(|c| c.is_uppercase())
3010 .unwrap_or(false)
3011 {
3012 "PascalCase"
3013 } else {
3014 "camelCase"
3015 };
3016 JsonRpcResponse::success(
3017 id,
3018 json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"unit_id": unit_id, "name": unit.name, "type": unit.unit_type.label(), "naming": naming, "complexity": unit.complexity, "is_async": unit.is_async, "visibility": format!("{:?}", unit.visibility), "out_edge_types": out_types, "in_edge_types": in_types, "out_count": outgoing.len(), "in_count": incoming.len(), "has_tests": incoming.iter().any(|e| e.edge_type == EdgeType::Tests), "has_docs": incoming.iter().any(|e| e.edge_type == EdgeType::Documents), "stability": unit.stability_score, "signature": unit.signature})).unwrap_or_default()}]}),
3019 )
3020 }
3021
3022 fn tool_genetics_lineage(&self, id: Value, args: &Value) -> JsonRpcResponse {
3023 let (_, graph) = match self.resolve_graph(args) {
3024 Ok(g) => g,
3025 Err(e) => return JsonRpcResponse::error(id, e),
3026 };
3027 let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
3028 let max_depth = args.get("max_depth").and_then(|v| v.as_u64()).unwrap_or(10) as usize;
3029 let mut lineage: Vec<Value> = Vec::new();
3030 let mut visited = std::collections::HashSet::new();
3031 let mut frontier = vec![(unit_id, 0usize)];
3032 while let Some((current, depth)) = frontier.pop() {
3033 if depth > max_depth || !visited.insert(current) {
3034 continue;
3035 }
3036 if let Some(unit) = graph.get_unit(current) {
3037 lineage.push(json!({"unit_id": current, "name": unit.name, "type": unit.unit_type.label(), "depth": depth, "file": unit.file_path.display().to_string()}));
3038 for edge in graph.edges_to(current) {
3039 if matches!(
3040 edge.edge_type,
3041 EdgeType::Contains
3042 | EdgeType::Inherits
3043 | EdgeType::VersionOf
3044 | EdgeType::Implements
3045 ) {
3046 frontier.push((edge.source_id, depth + 1));
3047 }
3048 }
3049 }
3050 }
3051 JsonRpcResponse::success(
3052 id,
3053 json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"unit_id": unit_id, "lineage_depth": lineage.len(), "lineage": lineage})).unwrap_or_default()}]}),
3054 )
3055 }
3056
3057 fn tool_genetics_mutations(&self, id: Value, args: &Value) -> JsonRpcResponse {
3058 let (_, graph) = match self.resolve_graph(args) {
3059 Ok(g) => g,
3060 Err(e) => return JsonRpcResponse::error(id, e),
3061 };
3062 let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
3063 let unit = match graph.get_unit(unit_id) {
3064 Some(u) => u,
3065 None => {
3066 return JsonRpcResponse::error(
3067 id,
3068 JsonRpcError::invalid_params(format!("Unit {} not found", unit_id)),
3069 )
3070 }
3071 };
3072 let mut mutations: Vec<Value> = Vec::new();
3073 if unit.complexity > 20 {
3074 mutations.push(json!({"type": "complexity_mutation", "description": format!("Complexity {} is unusually high", unit.complexity), "severity": "medium"}));
3075 }
3076 if unit.stability_score < 0.3 && unit.change_count > 5 {
3077 mutations.push(json!({"type": "stability_mutation", "description": format!("Low stability ({:.2}) with {} changes", unit.stability_score, unit.change_count), "severity": "high"}));
3078 }
3079 let breaks = graph
3080 .edges_from(unit_id)
3081 .into_iter()
3082 .filter(|e| e.edge_type == EdgeType::BreaksWith)
3083 .count();
3084 if breaks > 0 {
3085 mutations.push(json!({"type": "breaking_mutation", "description": format!("{} breaking relationships", breaks), "severity": "high"}));
3086 }
3087 let parents: Vec<_> = graph
3089 .edges_to(unit_id)
3090 .into_iter()
3091 .filter(|e| e.edge_type == EdgeType::Contains)
3092 .collect();
3093 for pe in &parents {
3094 let sibs: Vec<_> = graph
3095 .edges_from(pe.source_id)
3096 .into_iter()
3097 .filter(|e| e.edge_type == EdgeType::Contains && e.target_id != unit_id)
3098 .collect();
3099 let unit_snake = unit.name.contains('_');
3100 for se in &sibs {
3101 if let Some(sib) = graph.get_unit(se.target_id) {
3102 if sib.unit_type == unit.unit_type && sib.name.contains('_') != unit_snake {
3103 mutations.push(json!({"type": "naming_mutation", "description": format!("'{}' differs from sibling '{}'", unit.name, sib.name), "severity": "low"}));
3104 break;
3105 }
3106 }
3107 }
3108 }
3109 JsonRpcResponse::success(
3110 id,
3111 json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"unit_id": unit_id, "name": unit.name, "mutations": mutations})).unwrap_or_default()}]}),
3112 )
3113 }
3114
3115 fn tool_genetics_diseases(&self, id: Value, args: &Value) -> JsonRpcResponse {
3116 let (_, graph) = match self.resolve_graph(args) {
3117 Ok(g) => g,
3118 Err(e) => return JsonRpcResponse::error(id, e),
3119 };
3120 let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
3121 let unit = match graph.get_unit(unit_id) {
3122 Some(u) => u,
3123 None => {
3124 return JsonRpcResponse::error(
3125 id,
3126 JsonRpcError::invalid_params(format!("Unit {} not found", unit_id)),
3127 )
3128 }
3129 };
3130 let mut diseases: Vec<Value> = Vec::new();
3131 let out = graph.edges_from(unit_id);
3132 let inc = graph.edges_to(unit_id);
3133 if out.len() > 20 {
3134 diseases.push(json!({"disease": "god_object", "severity": "high", "detail": format!("{} outgoing edges", out.len())}));
3135 }
3136 let targets: std::collections::HashSet<u64> = out.iter().map(|e| e.target_id).collect();
3137 let sources: std::collections::HashSet<u64> = inc.iter().map(|e| e.source_id).collect();
3138 let circular: Vec<u64> = targets.intersection(&sources).copied().collect();
3139 if !circular.is_empty() {
3140 diseases.push(
3141 json!({"disease": "circular_dependency", "severity": "high", "with": circular}),
3142 );
3143 }
3144 let calls_out = out
3145 .iter()
3146 .filter(|e| e.edge_type == EdgeType::Calls)
3147 .count();
3148 let calls_in = inc
3149 .iter()
3150 .filter(|e| e.edge_type == EdgeType::Calls)
3151 .count();
3152 if calls_out > 10 && calls_out > calls_in * 3 {
3153 diseases.push(json!({"disease": "feature_envy", "severity": "medium", "calls_out": calls_out, "calls_in": calls_in}));
3154 }
3155 if !inc.iter().any(|e| e.edge_type == EdgeType::Tests)
3156 && unit.unit_type == CodeUnitType::Function
3157 {
3158 diseases.push(json!({"disease": "untested", "severity": "medium"}));
3159 }
3160 let non_contains = inc
3161 .iter()
3162 .filter(|e| e.edge_type != EdgeType::Contains)
3163 .count();
3164 if non_contains == 0 && !inc.is_empty() {
3165 diseases.push(json!({"disease": "orphan_code", "severity": "medium"}));
3166 }
3167 JsonRpcResponse::success(
3168 id,
3169 json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"unit_id": unit_id, "name": unit.name, "diseases": diseases})).unwrap_or_default()}]}),
3170 )
3171 }
3172
3173 fn tool_telepathy_connect(&self, id: Value, args: &Value) -> JsonRpcResponse {
3176 let ws_id = match self.resolve_workspace_id(args) {
3177 Ok(ws) => ws,
3178 Err(e) => return JsonRpcResponse::error(id, e),
3179 };
3180 let workspace = match self.workspace_manager.list(&ws_id) {
3181 Ok(ws) => ws,
3182 Err(e) => return JsonRpcResponse::error(id, JsonRpcError::invalid_params(e)),
3183 };
3184 let connections: Vec<Value> = workspace.contexts.iter().map(|c| json!({"context_id": c.id, "role": c.role.label(), "path": c.path, "units": c.graph.units().len()})).collect();
3185 JsonRpcResponse::success(
3186 id,
3187 json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"workspace": ws_id, "status": "connected", "connections": connections})).unwrap_or_default()}]}),
3188 )
3189 }
3190
3191 fn tool_telepathy_broadcast(&self, id: Value, args: &Value) -> JsonRpcResponse {
3192 let ws_id = match self.resolve_workspace_id(args) {
3193 Ok(ws) => ws,
3194 Err(e) => return JsonRpcResponse::error(id, e),
3195 };
3196 let insight = args.get("insight").and_then(|v| v.as_str()).unwrap_or("");
3197 let source_graph = args
3198 .get("source_graph")
3199 .and_then(|v| v.as_str())
3200 .unwrap_or("");
3201 let results = match self.workspace_manager.query_all(&ws_id, insight) {
3202 Ok(r) => r,
3203 Err(e) => return JsonRpcResponse::error(id, JsonRpcError::invalid_params(e)),
3204 };
3205 let receivers: Vec<Value> = results.iter().map(|r| json!({"context_id": r.context_id, "role": r.context_role.label(), "matches": r.matches.len()})).collect();
3206 JsonRpcResponse::success(
3207 id,
3208 json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"workspace": ws_id, "insight": insight, "source": source_graph, "receivers": receivers})).unwrap_or_default()}]}),
3209 )
3210 }
3211
3212 fn tool_telepathy_listen(&self, id: Value, args: &Value) -> JsonRpcResponse {
3213 let ws_id = match self.resolve_workspace_id(args) {
3214 Ok(ws) => ws,
3215 Err(e) => return JsonRpcResponse::error(id, e),
3216 };
3217 let workspace = match self.workspace_manager.list(&ws_id) {
3218 Ok(ws) => ws,
3219 Err(e) => return JsonRpcResponse::error(id, JsonRpcError::invalid_params(e)),
3220 };
3221 let mut insights: Vec<Value> = Vec::new();
3222 for ctx in &workspace.contexts {
3223 let high_cx: Vec<&str> = ctx
3224 .graph
3225 .units()
3226 .iter()
3227 .filter(|u| u.complexity > 15)
3228 .take(5)
3229 .map(|u| u.name.as_str())
3230 .collect();
3231 if !high_cx.is_empty() {
3232 insights.push(json!({"context": ctx.id, "role": ctx.role.label(), "type": "high_complexity", "units": high_cx}));
3233 }
3234 let tests = ctx
3235 .graph
3236 .units()
3237 .iter()
3238 .filter(|u| u.unit_type == CodeUnitType::Test)
3239 .count();
3240 let funcs = ctx
3241 .graph
3242 .units()
3243 .iter()
3244 .filter(|u| u.unit_type == CodeUnitType::Function)
3245 .count();
3246 if funcs > 0 {
3247 insights.push(json!({"context": ctx.id, "role": ctx.role.label(), "type": "test_ratio", "tests": tests, "functions": funcs, "ratio": tests as f64 / funcs as f64}));
3248 }
3249 }
3250 JsonRpcResponse::success(
3251 id,
3252 json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"workspace": ws_id, "insights": insights})).unwrap_or_default()}]}),
3253 )
3254 }
3255
3256 fn tool_telepathy_consensus(&self, id: Value, args: &Value) -> JsonRpcResponse {
3257 let ws_id = match self.resolve_workspace_id(args) {
3258 Ok(ws) => ws,
3259 Err(e) => return JsonRpcResponse::error(id, e),
3260 };
3261 let concept = args.get("concept").and_then(|v| v.as_str()).unwrap_or("");
3262 let results = match self.workspace_manager.query_all(&ws_id, concept) {
3263 Ok(r) => r,
3264 Err(e) => return JsonRpcResponse::error(id, JsonRpcError::invalid_params(e)),
3265 };
3266 let total = results.len();
3267 let with_matches = results.iter().filter(|r| !r.matches.is_empty()).count();
3268 let level = if with_matches == total && total > 0 {
3269 "universal"
3270 } else if with_matches as f64 / total.max(1) as f64 > 0.5 {
3271 "majority"
3272 } else if with_matches > 0 {
3273 "minority"
3274 } else {
3275 "none"
3276 };
3277 let details: Vec<Value> = results.iter().map(|r| json!({"context_id": r.context_id, "role": r.context_role.label(), "has_concept": !r.matches.is_empty(), "count": r.matches.len()})).collect();
3278 JsonRpcResponse::success(
3279 id,
3280 json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"concept": concept, "consensus": level, "total": total, "with_concept": with_matches, "details": details})).unwrap_or_default()}]}),
3281 )
3282 }
3283
3284 fn tool_soul_extract(&self, id: Value, args: &Value) -> JsonRpcResponse {
3287 let (_, graph) = match self.resolve_graph(args) {
3288 Ok(g) => g,
3289 Err(e) => return JsonRpcResponse::error(id, e),
3290 };
3291 let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
3292 let unit = match graph.get_unit(unit_id) {
3293 Some(u) => u,
3294 None => {
3295 return JsonRpcResponse::error(
3296 id,
3297 JsonRpcError::invalid_params(format!("Unit {} not found", unit_id)),
3298 )
3299 }
3300 };
3301 let out = graph.edges_from(unit_id);
3302 let inc = graph.edges_to(unit_id);
3303 let purpose = format!(
3304 "{} {} that {}",
3305 format!("{:?}", unit.visibility).to_lowercase(),
3306 unit.unit_type.label(),
3307 if out.iter().any(|e| e.edge_type == EdgeType::Calls) {
3308 "orchestrates calls"
3309 } else if unit.unit_type == CodeUnitType::Test {
3310 "verifies behavior"
3311 } else if unit.unit_type == CodeUnitType::Doc {
3312 "documents knowledge"
3313 } else {
3314 "provides functionality"
3315 }
3316 );
3317 let mut values: Vec<&str> = Vec::new();
3318 if inc.iter().any(|e| e.edge_type == EdgeType::Tests) {
3319 values.push("correctness");
3320 }
3321 if inc.iter().any(|e| e.edge_type == EdgeType::Documents) {
3322 values.push("documentation");
3323 }
3324 if unit.stability_score > 0.8 {
3325 values.push("stability");
3326 }
3327 if unit.complexity < 5 {
3328 values.push("simplicity");
3329 }
3330 if unit.is_async {
3331 values.push("concurrency");
3332 }
3333 let deps: Vec<String> = out
3334 .iter()
3335 .filter_map(|e| {
3336 graph
3337 .get_unit(e.target_id)
3338 .map(|u| format!("{}:{}", e.edge_type.label(), u.name.clone()))
3339 })
3340 .take(10)
3341 .collect();
3342 JsonRpcResponse::success(
3343 id,
3344 json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"unit_id": unit_id, "name": unit.name, "soul_id": format!("soul-{}-{}", unit_id, unit.name), "purpose": purpose, "values": values, "dependencies": deps, "signature": unit.signature, "complexity": unit.complexity, "stability": unit.stability_score, "language": unit.language.name()})).unwrap_or_default()}]}),
3345 )
3346 }
3347
3348 fn tool_soul_compare(&self, id: Value, args: &Value) -> JsonRpcResponse {
3349 let (_, graph) = match self.resolve_graph(args) {
3350 Ok(g) => g,
3351 Err(e) => return JsonRpcResponse::error(id, e),
3352 };
3353 let id_a = args.get("unit_id_a").and_then(|v| v.as_u64()).unwrap_or(0);
3354 let id_b = args.get("unit_id_b").and_then(|v| v.as_u64()).unwrap_or(0);
3355 let ua = match graph.get_unit(id_a) {
3356 Some(u) => u,
3357 None => {
3358 return JsonRpcResponse::error(
3359 id,
3360 JsonRpcError::invalid_params(format!("Unit {} not found", id_a)),
3361 )
3362 }
3363 };
3364 let ub = match graph.get_unit(id_b) {
3365 Some(u) => u,
3366 None => {
3367 return JsonRpcResponse::error(
3368 id,
3369 JsonRpcError::invalid_params(format!("Unit {} not found", id_b)),
3370 )
3371 }
3372 };
3373 let ea: std::collections::HashSet<String> = graph
3374 .edges_from(id_a)
3375 .iter()
3376 .map(|e| e.edge_type.label().to_string())
3377 .collect();
3378 let eb: std::collections::HashSet<String> = graph
3379 .edges_from(id_b)
3380 .iter()
3381 .map(|e| e.edge_type.label().to_string())
3382 .collect();
3383 let shared: Vec<&String> = ea.intersection(&eb).collect();
3384 let type_match = ua.unit_type == ub.unit_type;
3385 let cdiff = (ua.complexity as i64 - ub.complexity as i64).unsigned_abs();
3386 let mut sim = 0.0f64;
3387 if type_match {
3388 sim += 0.3;
3389 }
3390 if ua.is_async == ub.is_async {
3391 sim += 0.1;
3392 }
3393 if cdiff < 5 {
3394 sim += 0.2;
3395 }
3396 sim += 0.4 * (shared.len() as f64 / ea.len().max(eb.len()).max(1) as f64);
3397 let verdict = if sim > 0.8 {
3398 "same_soul"
3399 } else if sim > 0.5 {
3400 "related_souls"
3401 } else {
3402 "different_souls"
3403 };
3404 JsonRpcResponse::success(
3405 id,
3406 json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"unit_a": {"id": id_a, "name": ua.name}, "unit_b": {"id": id_b, "name": ub.name}, "similarity": sim, "type_match": type_match, "complexity_diff": cdiff, "shared_edges": shared, "verdict": verdict})).unwrap_or_default()}]}),
3407 )
3408 }
3409
3410 fn tool_soul_preserve(&self, id: Value, args: &Value) -> JsonRpcResponse {
3411 let (_, graph) = match self.resolve_graph(args) {
3412 Ok(g) => g,
3413 Err(e) => return JsonRpcResponse::error(id, e),
3414 };
3415 let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
3416 let new_lang = args
3417 .get("new_language")
3418 .and_then(|v| v.as_str())
3419 .unwrap_or("unknown");
3420 let unit = match graph.get_unit(unit_id) {
3421 Some(u) => u,
3422 None => {
3423 return JsonRpcResponse::error(
3424 id,
3425 JsonRpcError::invalid_params(format!("Unit {} not found", unit_id)),
3426 )
3427 }
3428 };
3429 let out = graph.edges_from(unit_id);
3430 let inc = graph.edges_to(unit_id);
3431 let risk = if out.len() > 10 {
3432 "high"
3433 } else if out.len() > 5 {
3434 "medium"
3435 } else {
3436 "low"
3437 };
3438 JsonRpcResponse::success(
3439 id,
3440 json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"unit_id": unit_id, "name": unit.name, "original_language": unit.language.name(), "target_language": new_lang, "soul_id": format!("soul-{}-{}", unit_id, unit.name), "purpose": unit.doc_summary, "signature": unit.signature, "deps": out.len(), "dependents": inc.len(), "is_async": unit.is_async, "tests": inc.iter().filter(|e| e.edge_type == EdgeType::Tests).count(), "risk": risk})).unwrap_or_default()}]}),
3441 )
3442 }
3443
3444 fn tool_soul_reincarnate(&self, id: Value, args: &Value) -> JsonRpcResponse {
3445 let (_gn, graph) = match self.resolve_graph(args) {
3446 Ok(g) => g,
3447 Err(e) => return JsonRpcResponse::error(id, e),
3448 };
3449 let soul_id = args.get("soul_id").and_then(|v| v.as_str()).unwrap_or("");
3450 let target_ctx = args
3451 .get("target_context")
3452 .and_then(|v| v.as_str())
3453 .unwrap_or("");
3454 let uid: u64 = soul_id
3455 .strip_prefix("soul-")
3456 .and_then(|s| s.split('-').next())
3457 .and_then(|s| s.parse().ok())
3458 .unwrap_or(0);
3459 let guide = if let Some(unit) = graph.get_unit(uid) {
3460 let deps: Vec<String> = graph
3461 .edges_from(uid)
3462 .iter()
3463 .filter_map(|e| graph.get_unit(e.target_id).map(|u| u.name.clone()))
3464 .take(10)
3465 .collect();
3466 json!({"soul_id": soul_id, "target": target_ctx, "status": "guidance_ready", "name": unit.name, "type": unit.unit_type.label(), "signature": unit.signature, "purpose": unit.doc_summary, "deps": deps})
3467 } else {
3468 json!({"soul_id": soul_id, "target": target_ctx, "status": "soul_not_found"})
3469 };
3470 JsonRpcResponse::success(
3471 id,
3472 json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&guide).unwrap_or_default()}]}),
3473 )
3474 }
3475
3476 fn tool_soul_karma(&self, id: Value, args: &Value) -> JsonRpcResponse {
3477 let (_, graph) = match self.resolve_graph(args) {
3478 Ok(g) => g,
3479 Err(e) => return JsonRpcResponse::error(id, e),
3480 };
3481 let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
3482 let unit = match graph.get_unit(unit_id) {
3483 Some(u) => u,
3484 None => {
3485 return JsonRpcResponse::error(
3486 id,
3487 JsonRpcError::invalid_params(format!("Unit {} not found", unit_id)),
3488 )
3489 }
3490 };
3491 let inc = graph.edges_to(unit_id);
3492 let out = graph.edges_from(unit_id);
3493 let mut pos = 0i64;
3494 let mut neg = 0i64;
3495 let mut details: Vec<Value> = Vec::new();
3496 let tc = inc
3497 .iter()
3498 .filter(|e| e.edge_type == EdgeType::Tests)
3499 .count();
3500 if tc > 0 {
3501 pos += tc as i64 * 10;
3502 details
3503 .push(json!({"k": "positive", "r": format!("{} tests", tc), "p": tc as i64 * 10}));
3504 }
3505 let dc = inc
3506 .iter()
3507 .filter(|e| e.edge_type == EdgeType::Documents)
3508 .count();
3509 if dc > 0 {
3510 pos += dc as i64 * 5;
3511 details.push(json!({"k": "positive", "r": format!("{} docs", dc), "p": dc as i64 * 5}));
3512 }
3513 if unit.stability_score > 0.8 {
3514 pos += 15;
3515 details.push(json!({"k": "positive", "r": "high stability", "p": 15}));
3516 }
3517 if unit.complexity < 10 {
3518 pos += 10;
3519 details.push(json!({"k": "positive", "r": "low complexity", "p": 10}));
3520 }
3521 let br = out
3522 .iter()
3523 .filter(|e| e.edge_type == EdgeType::BreaksWith)
3524 .count();
3525 if br > 0 {
3526 neg += br as i64 * 20;
3527 details.push(
3528 json!({"k": "negative", "r": format!("{} breaks", br), "p": -(br as i64 * 20)}),
3529 );
3530 }
3531 if unit.complexity > 20 {
3532 neg += 15;
3533 details.push(json!({"k": "negative", "r": "very high complexity", "p": -15}));
3534 }
3535 if tc == 0 && unit.unit_type == CodeUnitType::Function {
3536 neg += 10;
3537 details.push(json!({"k": "negative", "r": "no tests", "p": -10}));
3538 }
3539 if unit.stability_score < 0.3 {
3540 neg += 10;
3541 details.push(json!({"k": "negative", "r": "low stability", "p": -10}));
3542 }
3543 let total = pos - neg;
3544 let level = if total > 30 {
3545 "enlightened"
3546 } else if total > 10 {
3547 "good"
3548 } else if total > -10 {
3549 "neutral"
3550 } else {
3551 "troubled"
3552 };
3553 JsonRpcResponse::success(
3554 id,
3555 json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"unit_id": unit_id, "name": unit.name, "karma": total, "positive": pos, "negative": neg, "level": level, "details": details})).unwrap_or_default()}]}),
3556 )
3557 }
3558
3559 fn tool_omniscience_search(&self, id: Value, args: &Value) -> JsonRpcResponse {
3562 let query = args.get("query").and_then(|v| v.as_str()).unwrap_or("");
3563 let languages: Vec<String> = args
3564 .get("languages")
3565 .and_then(|v| v.as_array())
3566 .map(|a| {
3567 a.iter()
3568 .filter_map(|v| v.as_str().map(String::from))
3569 .collect()
3570 })
3571 .unwrap_or_default();
3572 let max_results = args
3573 .get("max_results")
3574 .and_then(|v| v.as_u64())
3575 .unwrap_or(10) as usize;
3576 let ql = query.to_lowercase();
3577 let mut results: Vec<Value> = Vec::new();
3578 for (gn, graph) in &self.graphs {
3579 for unit in graph.units() {
3580 if results.len() >= max_results {
3581 break;
3582 }
3583 if !languages.is_empty()
3584 && !languages
3585 .iter()
3586 .any(|l| l.eq_ignore_ascii_case(unit.language.name()))
3587 {
3588 continue;
3589 }
3590 if unit.name.to_lowercase().contains(&ql)
3591 || unit.qualified_name.to_lowercase().contains(&ql)
3592 {
3593 results.push(json!({"graph": gn, "unit_id": unit.id, "name": unit.name, "qualified_name": unit.qualified_name, "type": unit.unit_type.label(), "language": unit.language.name(), "file": unit.file_path.display().to_string()}));
3594 }
3595 }
3596 }
3597 JsonRpcResponse::success(
3598 id,
3599 json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"query": query, "count": results.len(), "results": results})).unwrap_or_default()}]}),
3600 )
3601 }
3602
3603 fn tool_omniscience_best(&self, id: Value, args: &Value) -> JsonRpcResponse {
3604 let cap = args
3605 .get("capability")
3606 .and_then(|v| v.as_str())
3607 .unwrap_or("");
3608 let criteria: Vec<String> = args
3609 .get("criteria")
3610 .and_then(|v| v.as_array())
3611 .map(|a| {
3612 a.iter()
3613 .filter_map(|v| v.as_str().map(String::from))
3614 .collect()
3615 })
3616 .unwrap_or_default();
3617 let cl = cap.to_lowercase();
3618 let mut cands: Vec<Value> = Vec::new();
3619 for (gn, graph) in &self.graphs {
3620 for unit in graph.units() {
3621 if unit.name.to_lowercase().contains(&cl)
3622 || unit.qualified_name.to_lowercase().contains(&cl)
3623 {
3624 let inc = graph.edges_to(unit.id);
3625 let has_t = inc.iter().any(|e| e.edge_type == EdgeType::Tests);
3626 let has_d = inc.iter().any(|e| e.edge_type == EdgeType::Documents);
3627 let mut s = 0.15f64;
3628 if has_t {
3629 s += 0.3;
3630 }
3631 if has_d {
3632 s += 0.2;
3633 }
3634 if unit.stability_score > 0.7 {
3635 s += 0.2;
3636 }
3637 if unit.complexity < 15 {
3638 s += 0.15;
3639 }
3640 cands.push(json!({"graph": gn, "unit_id": unit.id, "name": unit.name, "score": s, "has_tests": has_t, "has_docs": has_d, "stability": unit.stability_score, "complexity": unit.complexity}));
3641 }
3642 }
3643 }
3644 cands.sort_by(|a, b| {
3645 b["score"]
3646 .as_f64()
3647 .unwrap_or(0.0)
3648 .partial_cmp(&a["score"].as_f64().unwrap_or(0.0))
3649 .unwrap_or(std::cmp::Ordering::Equal)
3650 });
3651 cands.truncate(5);
3652 JsonRpcResponse::success(
3653 id,
3654 json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"capability": cap, "criteria": criteria, "best": cands})).unwrap_or_default()}]}),
3655 )
3656 }
3657
3658 fn tool_omniscience_census(&self, id: Value, args: &Value) -> JsonRpcResponse {
3659 let concept = args.get("concept").and_then(|v| v.as_str()).unwrap_or("");
3660 let languages: Vec<String> = args
3661 .get("languages")
3662 .and_then(|v| v.as_array())
3663 .map(|a| {
3664 a.iter()
3665 .filter_map(|v| v.as_str().map(String::from))
3666 .collect()
3667 })
3668 .unwrap_or_default();
3669 let cl = concept.to_lowercase();
3670 let mut by_lang: HashMap<String, usize> = HashMap::new();
3671 let mut by_type: HashMap<String, usize> = HashMap::new();
3672 let mut total = 0usize;
3673 for graph in self.graphs.values() {
3674 for unit in graph.units() {
3675 if !languages.is_empty()
3676 && !languages
3677 .iter()
3678 .any(|l| l.eq_ignore_ascii_case(unit.language.name()))
3679 {
3680 continue;
3681 }
3682 if unit.name.to_lowercase().contains(&cl)
3683 || unit.qualified_name.to_lowercase().contains(&cl)
3684 {
3685 total += 1;
3686 *by_lang.entry(unit.language.name().to_string()).or_insert(0) += 1;
3687 *by_type
3688 .entry(unit.unit_type.label().to_string())
3689 .or_insert(0) += 1;
3690 }
3691 }
3692 }
3693 JsonRpcResponse::success(
3694 id,
3695 json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"concept": concept, "total": total, "by_language": by_lang, "by_type": by_type, "graphs": self.graphs.len()})).unwrap_or_default()}]}),
3696 )
3697 }
3698
3699 fn tool_omniscience_vuln(&self, id: Value, args: &Value) -> JsonRpcResponse {
3700 let (_, graph) = match self.resolve_graph(args) {
3701 Ok(g) => g,
3702 Err(e) => return JsonRpcResponse::error(id, e),
3703 };
3704 let pattern = args.get("pattern").and_then(|v| v.as_str()).unwrap_or("");
3705 let cve = args.get("cve").and_then(|v| v.as_str()).unwrap_or("");
3706 let kws: Vec<&str> = if pattern.is_empty() {
3707 vec![
3708 "unsafe",
3709 "eval",
3710 "exec",
3711 "sql",
3712 "inject",
3713 "deserialize",
3714 "shell",
3715 ]
3716 } else {
3717 vec![pattern]
3718 };
3719 let mut findings: Vec<Value> = Vec::new();
3720 for unit in graph.units() {
3721 let nl = unit.name.to_lowercase();
3722 let sl = unit.signature.as_deref().unwrap_or("").to_lowercase();
3723 for &kw in &kws {
3724 if nl.contains(kw) || sl.contains(kw) {
3725 let sev = if kw == "unsafe" || kw == "eval" || kw == "exec" {
3726 "high"
3727 } else {
3728 "medium"
3729 };
3730 findings.push(json!({"unit_id": unit.id, "name": unit.name, "type": unit.unit_type.label(), "file": unit.file_path.display().to_string(), "pattern": kw, "severity": sev}));
3731 break;
3732 }
3733 }
3734 }
3735 JsonRpcResponse::success(
3736 id,
3737 json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"pattern": pattern, "cve": cve, "count": findings.len(), "findings": findings})).unwrap_or_default()}]}),
3738 )
3739 }
3740
3741 fn tool_omniscience_trend(&self, id: Value, args: &Value) -> JsonRpcResponse {
3742 let domain = args.get("domain").and_then(|v| v.as_str()).unwrap_or("");
3743 let dl = domain.to_lowercase();
3744 let mut trends: Vec<Value> = Vec::new();
3745 for (gn, graph) in &self.graphs {
3746 let m: Vec<_> = graph
3747 .units()
3748 .iter()
3749 .filter(|u| {
3750 u.name.to_lowercase().contains(&dl)
3751 || u.qualified_name.to_lowercase().contains(&dl)
3752 })
3753 .collect();
3754 if !m.is_empty() {
3755 let avg_s: f64 =
3756 m.iter().map(|u| u.stability_score as f64).sum::<f64>() / m.len() as f64;
3757 let avg_c: f64 =
3758 m.iter().map(|u| u.change_count as f64).sum::<f64>() / m.len() as f64;
3759 let avg_x: f64 =
3760 m.iter().map(|u| u.complexity as f64).sum::<f64>() / m.len() as f64;
3761 let dir = if avg_c > 5.0 && avg_s < 0.5 {
3762 "declining"
3763 } else if avg_s > 0.7 && avg_x < 15.0 {
3764 "stable"
3765 } else {
3766 "emerging"
3767 };
3768 trends.push(json!({"graph": gn, "count": m.len(), "avg_stability": avg_s, "avg_changes": avg_c, "avg_complexity": avg_x, "trend": dir}));
3769 }
3770 }
3771 JsonRpcResponse::success(
3772 id,
3773 json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"domain": domain, "graphs": self.graphs.len(), "trends": trends})).unwrap_or_default()}]}),
3774 )
3775 }
3776
3777 fn tool_omniscience_compare(&self, id: Value, args: &Value) -> JsonRpcResponse {
3778 let (_, graph) = match self.resolve_graph(args) {
3779 Ok(g) => g,
3780 Err(e) => return JsonRpcResponse::error(id, e),
3781 };
3782 let unit_id = args.get("unit_id").and_then(|v| v.as_u64()).unwrap_or(0);
3783 let unit = match graph.get_unit(unit_id) {
3784 Some(u) => u,
3785 None => {
3786 return JsonRpcResponse::error(
3787 id,
3788 JsonRpcError::invalid_params(format!("Unit {} not found", unit_id)),
3789 )
3790 }
3791 };
3792 let inc = graph.edges_to(unit_id);
3793 let out = graph.edges_from(unit_id);
3794 let has_t = inc.iter().any(|e| e.edge_type == EdgeType::Tests);
3795 let has_d = inc.iter().any(|e| e.edge_type == EdgeType::Documents);
3796 let mut practices: Vec<Value> = Vec::new();
3797 let mut score = 0u32;
3798 if has_t {
3799 score += 20;
3800 practices.push(json!({"p": "tests", "s": "pass", "pts": 20}));
3801 } else {
3802 practices.push(json!({"p": "tests", "s": "fail", "rec": "Add tests"}));
3803 }
3804 if has_d {
3805 score += 15;
3806 practices.push(json!({"p": "docs", "s": "pass", "pts": 15}));
3807 } else {
3808 practices.push(json!({"p": "docs", "s": "fail", "rec": "Add docs"}));
3809 }
3810 if unit.complexity < 10 {
3811 score += 20;
3812 practices.push(json!({"p": "complexity", "s": "pass", "pts": 20}));
3813 } else if unit.complexity < 20 {
3814 score += 10;
3815 practices.push(json!({"p": "complexity", "s": "warn", "pts": 10}));
3816 } else {
3817 practices.push(json!({"p": "complexity", "s": "fail", "rec": "Refactor"}));
3818 }
3819 if unit.stability_score > 0.7 {
3820 score += 15;
3821 practices.push(json!({"p": "stability", "s": "pass", "pts": 15}));
3822 } else {
3823 practices.push(json!({"p": "stability", "s": "fail", "rec": "Stabilize"}));
3824 }
3825 if out.len() < 10 {
3826 score += 15;
3827 practices.push(json!({"p": "coupling", "s": "pass", "pts": 15}));
3828 } else {
3829 practices.push(json!({"p": "coupling", "s": "fail", "rec": "Reduce deps"}));
3830 }
3831 if unit.doc_summary.is_some() {
3832 score += 15;
3833 practices.push(json!({"p": "doc_summary", "s": "pass", "pts": 15}));
3834 } else {
3835 practices.push(json!({"p": "doc_summary", "s": "fail", "rec": "Add summary"}));
3836 }
3837 let grade = if score >= 80 {
3838 "A"
3839 } else if score >= 60 {
3840 "B"
3841 } else if score >= 40 {
3842 "C"
3843 } else {
3844 "D"
3845 };
3846 JsonRpcResponse::success(
3847 id,
3848 json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"unit_id": unit_id, "name": unit.name, "score": score, "max": 100, "grade": grade, "practices": practices})).unwrap_or_default()}]}),
3849 )
3850 }
3851
3852 fn tool_omniscience_api_usage(&self, id: Value, args: &Value) -> JsonRpcResponse {
3853 let api = args.get("api").and_then(|v| v.as_str()).unwrap_or("");
3854 let method = args.get("method").and_then(|v| v.as_str()).unwrap_or("");
3855 let al = api.to_lowercase();
3856 let ml = method.to_lowercase();
3857 let mut usages: Vec<Value> = Vec::new();
3858 for (gn, graph) in &self.graphs {
3859 for unit in graph.units() {
3860 let nl = unit.name.to_lowercase();
3861 let ql = unit.qualified_name.to_lowercase();
3862 let sl = unit.signature.as_deref().unwrap_or("").to_lowercase();
3863 let ma = nl.contains(&al) || ql.contains(&al) || sl.contains(&al);
3864 let mm = method.is_empty() || nl.contains(&ml) || sl.contains(&ml);
3865 if ma && mm {
3866 usages.push(json!({"graph": gn, "unit_id": unit.id, "name": unit.name, "qualified_name": unit.qualified_name, "type": unit.unit_type.label(), "file": unit.file_path.display().to_string(), "signature": unit.signature}));
3867 }
3868 }
3869 }
3870 JsonRpcResponse::success(
3871 id,
3872 json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"api": api, "method": method, "count": usages.len(), "usages": usages})).unwrap_or_default()}]}),
3873 )
3874 }
3875
3876 fn tool_omniscience_solve(&self, id: Value, args: &Value) -> JsonRpcResponse {
3877 let problem = args.get("problem").and_then(|v| v.as_str()).unwrap_or("");
3878 let languages: Vec<String> = args
3879 .get("languages")
3880 .and_then(|v| v.as_array())
3881 .map(|a| {
3882 a.iter()
3883 .filter_map(|v| v.as_str().map(String::from))
3884 .collect()
3885 })
3886 .unwrap_or_default();
3887 let max_r = args
3888 .get("max_results")
3889 .and_then(|v| v.as_u64())
3890 .unwrap_or(5) as usize;
3891 let kws: Vec<String> = problem
3892 .to_lowercase()
3893 .split_whitespace()
3894 .filter(|w| w.len() > 3)
3895 .map(String::from)
3896 .collect();
3897 let mut sols: Vec<Value> = Vec::new();
3898 for (gn, graph) in &self.graphs {
3899 for unit in graph.units() {
3900 if sols.len() >= max_r {
3901 break;
3902 }
3903 if !languages.is_empty()
3904 && !languages
3905 .iter()
3906 .any(|l| l.eq_ignore_ascii_case(unit.language.name()))
3907 {
3908 continue;
3909 }
3910 let nl = unit.name.to_lowercase();
3911 let dl = unit.doc_summary.as_deref().unwrap_or("").to_lowercase();
3912 let mc = kws
3913 .iter()
3914 .filter(|kw| nl.contains(kw.as_str()) || dl.contains(kw.as_str()))
3915 .count();
3916 if mc > 0 {
3917 let rel = mc as f64 / kws.len().max(1) as f64;
3918 sols.push(json!({"graph": gn, "unit_id": unit.id, "name": unit.name, "type": unit.unit_type.label(), "language": unit.language.name(), "file": unit.file_path.display().to_string(), "relevance": rel, "doc": unit.doc_summary, "signature": unit.signature}));
3919 }
3920 }
3921 }
3922 sols.sort_by(|a, b| {
3923 b["relevance"]
3924 .as_f64()
3925 .unwrap_or(0.0)
3926 .partial_cmp(&a["relevance"].as_f64().unwrap_or(0.0))
3927 .unwrap_or(std::cmp::Ordering::Equal)
3928 });
3929 sols.truncate(max_r);
3930 JsonRpcResponse::success(
3931 id,
3932 json!({"content": [{"type": "text", "text": serde_json::to_string_pretty(&json!({"problem": problem, "count": sols.len(), "solutions": sols})).unwrap_or_default()}]}),
3933 )
3934 }
3935}
3936
3937fn truncate_json_summary(value: &Value, max_len: usize) -> String {
3939 let s = value.to_string();
3940 if s.len() <= max_len {
3941 s
3942 } else {
3943 format!("{}...", &s[..max_len])
3944 }
3945}
3946
3947impl Default for McpServer {
3948 fn default() -> Self {
3949 Self::new()
3950 }
3951}