codeprism_mcp/tools/core/
symbols.rs

1//! Symbol explanation and search tools
2
3use crate::context::session::SessionId;
4use crate::context::workflow::ConfidenceLevel;
5use crate::context::ToolSuggestion;
6use crate::tools::{CallToolParams, CallToolResult, Tool, ToolContent};
7use crate::CodePrismMcpServer;
8use anyhow::Result;
9use serde_json::Value;
10
11/// List symbol tools
12pub fn list_tools() -> Vec<Tool> {
13    vec![
14        Tool {
15            name: "explain_symbol".to_string(),
16            title: Some("Explain Symbol".to_string()),
17            description: "Provide detailed explanation of a code symbol with context".to_string(),
18            input_schema: serde_json::json!({
19                "type": "object",
20                "properties": {
21                    "symbol_id": {
22                        "type": "string",
23                        "description": "Symbol identifier (node ID)"
24                    },
25                    "include_dependencies": {
26                        "type": "boolean",
27                        "description": "Include dependency information",
28                        "default": false
29                    },
30                    "include_usages": {
31                        "type": "boolean",
32                        "description": "Include usage information",
33                        "default": false
34                    },
35                    "context_lines": {
36                        "type": "number",
37                        "description": "Number of lines before and after the symbol to include as context",
38                        "default": 4
39                    }
40                },
41                "required": ["symbol_id"]
42            }),
43        },
44        Tool {
45            name: "search_symbols".to_string(),
46            title: Some("Search Symbols".to_string()),
47            description: "Search for symbols by name pattern with advanced inheritance filtering"
48                .to_string(),
49            input_schema: serde_json::json!({
50                "type": "object",
51                "properties": {
52                    "pattern": {
53                        "type": "string",
54                        "description": "Search pattern (supports regex)"
55                    },
56                    "symbol_types": {
57                        "type": "array",
58                        "items": {
59                            "type": "string",
60                            "enum": ["function", "class", "variable", "module", "method"]
61                        },
62                        "description": "Filter by symbol types"
63                    },
64                    "inheritance_filters": {
65                        "type": "array",
66                        "items": {
67                            "type": "string"
68                        },
69                        "description": "Filter by inheritance relationships (format: 'inherits_from:ClassName', 'metaclass:MetaclassName', 'uses_mixin:MixinName')"
70                    },
71                    "limit": {
72                        "type": "number",
73                        "description": "Maximum number of results",
74                        "default": 50
75                    },
76                    "context_lines": {
77                        "type": "number",
78                        "description": "Number of lines before and after the symbol to include as context",
79                        "default": 4
80                    }
81                },
82                "required": ["pattern"]
83            }),
84        },
85    ]
86}
87
88/// Route symbol tool calls
89pub async fn call_tool(
90    server: &CodePrismMcpServer,
91    params: &CallToolParams,
92) -> Result<CallToolResult> {
93    match params.name.as_str() {
94        "explain_symbol" => explain_symbol(server, params.arguments.as_ref()).await,
95        "search_symbols" => search_symbols(server, params.arguments.as_ref()).await,
96        _ => Err(anyhow::anyhow!("Unknown symbol tool: {}", params.name)),
97    }
98}
99
100/// Parse node ID from hex string
101fn parse_node_id(hex_str: &str) -> Result<codeprism_core::NodeId> {
102    codeprism_core::NodeId::from_hex(hex_str)
103        .map_err(|e| anyhow::anyhow!("Invalid node ID '{}': {}", hex_str, e))
104}
105
106/// Resolve symbol name to node ID using search
107async fn resolve_symbol_name(
108    server: &CodePrismMcpServer,
109    symbol_name: &str,
110) -> Result<Option<codeprism_core::NodeId>> {
111    // Try to search for the symbol by name
112    let results = server
113        .graph_query()
114        .search_symbols(symbol_name, None, Some(10))?;
115
116    // Look for exact match first
117    for result in &results {
118        if result.node.name == symbol_name {
119            return Ok(Some(result.node.id));
120        }
121    }
122
123    // If no exact match, return the first result if any
124    if let Some(first) = results.first() {
125        Ok(Some(first.node.id))
126    } else {
127        Ok(None)
128    }
129}
130
131/// Resolve symbol identifier - try as node ID first, then as symbol name
132async fn resolve_symbol_identifier(
133    server: &CodePrismMcpServer,
134    identifier: &str,
135) -> Result<codeprism_core::NodeId> {
136    // First try to parse as node ID
137    if let Ok(node_id) = parse_node_id(identifier) {
138        return Ok(node_id);
139    }
140
141    // Then try to resolve as symbol name
142    if let Some(node_id) = resolve_symbol_name(server, identifier).await? {
143        return Ok(node_id);
144    }
145
146    Err(anyhow::anyhow!("Could not resolve symbol identifier '{}'. Please provide either a valid node ID (hex string) or symbol name that exists in the codebase.", identifier))
147}
148
149/// Extract source context around a specific line
150fn extract_source_context(
151    file_path: &std::path::Path,
152    line_number: usize,
153    context_lines: usize,
154) -> Option<serde_json::Value> {
155    if let Ok(content) = std::fs::read_to_string(file_path) {
156        let lines: Vec<&str> = content.lines().collect();
157        let total_lines = lines.len();
158
159        if line_number == 0 || line_number > total_lines {
160            return None;
161        }
162
163        // Convert to 0-based indexing
164        let target_line_idx = line_number - 1;
165
166        // Calculate context range
167        let start_idx = target_line_idx.saturating_sub(context_lines);
168        let end_idx = std::cmp::min(target_line_idx + context_lines + 1, total_lines);
169
170        let context_lines_data: Vec<serde_json::Value> = (start_idx..end_idx)
171            .map(|idx| {
172                serde_json::json!({
173                    "line_number": idx + 1,
174                    "content": lines[idx],
175                    "is_target": idx == target_line_idx
176                })
177            })
178            .collect();
179
180        Some(serde_json::json!({
181            "file": file_path.display().to_string(),
182            "target_line": line_number,
183            "context_start": start_idx + 1,
184            "context_end": end_idx,
185            "lines": context_lines_data
186        }))
187    } else {
188        None
189    }
190}
191
192/// Create node info with source context
193fn create_node_info_with_context(
194    node: &codeprism_core::Node,
195    context_lines: usize,
196) -> serde_json::Value {
197    let mut info = serde_json::json!({
198        "id": node.id.to_hex(),
199        "name": node.name,
200        "kind": format!("{:?}", node.kind),
201        "file": node.file.display().to_string(),
202        "span": {
203            "start_line": node.span.start_line,
204            "end_line": node.span.end_line,
205            "start_column": node.span.start_column,
206            "end_column": node.span.end_column
207        }
208    });
209
210    if let Some(context) = extract_source_context(&node.file, node.span.start_line, context_lines) {
211        info["source_context"] = context;
212    }
213
214    info
215}
216
217/// Validate that a dependency node has a valid name
218fn is_valid_dependency_node(node: &codeprism_core::Node) -> bool {
219    // Filter out Call nodes with invalid names
220    if matches!(node.kind, codeprism_core::NodeKind::Call) {
221        // Check for common invalid patterns
222        if node.name.is_empty()
223            || node.name == ")"
224            || node.name == "("
225            || node.name.trim().is_empty()
226            || node.name.chars().all(|c| !c.is_alphanumeric() && c != '_')
227        {
228            return false;
229        }
230    }
231
232    // All other nodes are considered valid
233    true
234}
235
236/// Parse inheritance filter string into InheritanceFilter enum
237fn parse_inheritance_filter(filter_str: &str) -> Option<codeprism_core::InheritanceFilter> {
238    if let Some(colon_pos) = filter_str.find(':') {
239        let filter_type = &filter_str[..colon_pos];
240        let class_name = &filter_str[colon_pos + 1..];
241
242        match filter_type {
243            "inherits_from" => Some(codeprism_core::InheritanceFilter::InheritsFrom(
244                class_name.to_string(),
245            )),
246            "metaclass" => Some(codeprism_core::InheritanceFilter::HasMetaclass(
247                class_name.to_string(),
248            )),
249            "uses_mixin" => Some(codeprism_core::InheritanceFilter::UsesMixin(
250                class_name.to_string(),
251            )),
252            _ => None,
253        }
254    } else {
255        None
256    }
257}
258
259/// Generate workflow suggestions for a symbol
260fn generate_symbol_workflow_suggestions(
261    symbol_id_str: &str,
262    node: &codeprism_core::Node,
263) -> Vec<ToolSuggestion> {
264    let mut suggestions = Vec::new();
265
266    // Suggest finding references if not already done
267    suggestions.push(
268        ToolSuggestion::new(
269            "find_references".to_string(),
270            ConfidenceLevel::High,
271            format!("Find all references to symbol '{}'", node.name),
272            "Understanding of how and where the symbol is used throughout the codebase".to_string(),
273            1,
274        )
275        .with_parameter(
276            "symbol_id".to_string(),
277            serde_json::Value::String(symbol_id_str.to_string()),
278        ),
279    );
280
281    // Suggest dependency analysis
282    suggestions.push(
283        ToolSuggestion::new(
284            "find_dependencies".to_string(),
285            ConfidenceLevel::Medium,
286            format!("Analyze dependencies for symbol '{}'", node.name),
287            "Understanding of what this symbol depends on".to_string(),
288            2,
289        )
290        .with_parameter(
291            "target".to_string(),
292            serde_json::Value::String(symbol_id_str.to_string()),
293        ),
294    );
295
296    // For classes, suggest inheritance analysis
297    if matches!(node.kind, codeprism_core::NodeKind::Class) {
298        suggestions.push(
299            ToolSuggestion::new(
300                "trace_inheritance".to_string(),
301                ConfidenceLevel::Medium,
302                format!("Analyze inheritance hierarchy for class '{}'", node.name),
303                "Complete understanding of inheritance relationships and method resolution"
304                    .to_string(),
305                3,
306            )
307            .with_parameter(
308                "class_name".to_string(),
309                serde_json::Value::String(node.name.clone()),
310            ),
311        );
312    }
313
314    // For functions, suggest data flow analysis
315    if matches!(
316        node.kind,
317        codeprism_core::NodeKind::Function | codeprism_core::NodeKind::Method
318    ) {
319        suggestions.push(
320            ToolSuggestion::new(
321                "trace_data_flow".to_string(),
322                ConfidenceLevel::Medium,
323                format!("Trace data flow through function '{}'", node.name),
324                "Understanding of how data flows through this function".to_string(),
325                3,
326            )
327            .with_parameter(
328                "function_id".to_string(),
329                serde_json::Value::String(symbol_id_str.to_string()),
330            ),
331        );
332    }
333
334    suggestions
335}
336
337/// Explain a symbol with context and workflow guidance
338async fn explain_symbol(
339    server: &CodePrismMcpServer,
340    arguments: Option<&Value>,
341) -> Result<CallToolResult> {
342    let args = arguments.ok_or_else(|| anyhow::anyhow!("Missing arguments"))?;
343
344    // Support both "symbol_id" and "symbol" parameter names for backward compatibility
345    let symbol_id_str = args
346        .get("symbol_id")
347        .or_else(|| args.get("symbol"))
348        .and_then(|v| v.as_str())
349        .ok_or_else(|| anyhow::anyhow!("Missing symbol_id parameter (or symbol)"))?;
350
351    let include_dependencies = args
352        .get("include_dependencies")
353        .and_then(|v| v.as_bool())
354        .unwrap_or(false);
355
356    let include_usages = args
357        .get("include_usages")
358        .and_then(|v| v.as_bool())
359        .unwrap_or(false);
360
361    let context_lines = args
362        .get("context_lines")
363        .and_then(|v| v.as_u64())
364        .map(|v| v as usize)
365        .unwrap_or(4);
366
367    // Session context for workflow guidance
368    let _session_id = args
369        .get("session_id")
370        .and_then(|v| v.as_str())
371        .map(|s| SessionId(s.to_string()));
372
373    let symbol_id = resolve_symbol_identifier(server, symbol_id_str).await?;
374
375    if let Some(node) = server.graph_store().get_node(&symbol_id) {
376        let mut result = serde_json::json!({
377            "symbol": create_node_info_with_context(&node, context_lines)
378        });
379
380        // Enhanced inheritance information for classes
381        if matches!(node.kind, codeprism_core::NodeKind::Class) {
382            if let Ok(inheritance_info) = server.graph_query().get_inheritance_info(&symbol_id) {
383                let mut inheritance_data = serde_json::Map::new();
384
385                // Basic inheritance information
386                inheritance_data.insert(
387                    "class_name".to_string(),
388                    serde_json::Value::String(inheritance_info.class_name),
389                );
390                inheritance_data.insert(
391                    "is_metaclass".to_string(),
392                    serde_json::Value::Bool(inheritance_info.is_metaclass),
393                );
394
395                // Base classes
396                if !inheritance_info.base_classes.is_empty() {
397                    let base_classes: Vec<_> = inheritance_info
398                        .base_classes
399                        .iter()
400                        .map(|rel| {
401                            serde_json::json!({
402                                "name": rel.class_name,
403                                "relationship_type": rel.relationship_type,
404                                "file": rel.file.display().to_string(),
405                                "span": {
406                                    "start_line": rel.span.start_line,
407                                    "end_line": rel.span.end_line,
408                                    "start_column": rel.span.start_column,
409                                    "end_column": rel.span.end_column
410                                }
411                            })
412                        })
413                        .collect();
414                    inheritance_data.insert(
415                        "base_classes".to_string(),
416                        serde_json::Value::Array(base_classes),
417                    );
418                }
419
420                // Subclasses
421                if !inheritance_info.subclasses.is_empty() {
422                    let subclasses: Vec<_> = inheritance_info
423                        .subclasses
424                        .iter()
425                        .map(|rel| {
426                            serde_json::json!({
427                                "name": rel.class_name,
428                                "file": rel.file.display().to_string(),
429                                "span": {
430                                    "start_line": rel.span.start_line,
431                                    "end_line": rel.span.end_line,
432                                    "start_column": rel.span.start_column,
433                                    "end_column": rel.span.end_column
434                                }
435                            })
436                        })
437                        .collect();
438                    inheritance_data.insert(
439                        "subclasses".to_string(),
440                        serde_json::Value::Array(subclasses),
441                    );
442                }
443
444                // Metaclass information
445                if let Some(metaclass) = inheritance_info.metaclass {
446                    inheritance_data.insert(
447                        "metaclass".to_string(),
448                        serde_json::json!({
449                            "name": metaclass.class_name,
450                            "file": metaclass.file.display().to_string(),
451                            "span": {
452                                "start_line": metaclass.span.start_line,
453                                "end_line": metaclass.span.end_line,
454                                "start_column": metaclass.span.start_column,
455                                "end_column": metaclass.span.end_column
456                            }
457                        }),
458                    );
459                }
460
461                // Mixins
462                if !inheritance_info.mixins.is_empty() {
463                    let mixins: Vec<_> = inheritance_info
464                        .mixins
465                        .iter()
466                        .map(|rel| {
467                            serde_json::json!({
468                                "name": rel.class_name,
469                                "file": rel.file.display().to_string(),
470                                "span": {
471                                    "start_line": rel.span.start_line,
472                                    "end_line": rel.span.end_line,
473                                    "start_column": rel.span.start_column,
474                                    "end_column": rel.span.end_column
475                                }
476                            })
477                        })
478                        .collect();
479                    inheritance_data.insert("mixins".to_string(), serde_json::Value::Array(mixins));
480                }
481
482                // Method Resolution Order
483                if !inheritance_info.method_resolution_order.is_empty() {
484                    inheritance_data.insert(
485                        "method_resolution_order".to_string(),
486                        serde_json::Value::Array(
487                            inheritance_info
488                                .method_resolution_order
489                                .iter()
490                                .map(|name| serde_json::Value::String(name.clone()))
491                                .collect(),
492                        ),
493                    );
494                }
495
496                // Dynamic attributes
497                if !inheritance_info.dynamic_attributes.is_empty() {
498                    let dynamic_attrs: Vec<_> = inheritance_info
499                        .dynamic_attributes
500                        .iter()
501                        .map(|attr| {
502                            serde_json::json!({
503                                "name": attr.name,
504                                "created_by": attr.created_by,
505                                "type": attr.attribute_type
506                            })
507                        })
508                        .collect();
509                    inheritance_data.insert(
510                        "dynamic_attributes".to_string(),
511                        serde_json::Value::Array(dynamic_attrs),
512                    );
513                }
514
515                // Full inheritance chain
516                if !inheritance_info.inheritance_chain.is_empty() {
517                    inheritance_data.insert(
518                        "inheritance_chain".to_string(),
519                        serde_json::Value::Array(
520                            inheritance_info
521                                .inheritance_chain
522                                .iter()
523                                .map(|name| serde_json::Value::String(name.clone()))
524                                .collect(),
525                        ),
526                    );
527                }
528
529                result["inheritance"] = serde_json::Value::Object(inheritance_data);
530            }
531        }
532
533        if include_dependencies {
534            let dependencies = server
535                .graph_query()
536                .find_dependencies(&symbol_id, codeprism_core::graph::DependencyType::Direct)?;
537
538            // Filter out invalid Call nodes with malformed names
539            let valid_dependencies: Vec<_> = dependencies
540                .iter()
541                .filter(|dep| is_valid_dependency_node(&dep.target_node))
542                .collect();
543
544            result["dependencies"] = serde_json::json!(valid_dependencies
545                .iter()
546                .map(|dep| {
547                    let mut dep_info =
548                        create_node_info_with_context(&dep.target_node, context_lines);
549                    dep_info["edge_kind"] = serde_json::json!(format!("{:?}", dep.edge_kind));
550                    dep_info
551                })
552                .collect::<Vec<_>>());
553        }
554
555        if include_usages {
556            let references = server.graph_query().find_references(&symbol_id)?;
557            result["usages"] = serde_json::json!(references
558                .iter()
559                .map(|ref_| {
560                    let mut usage_info =
561                        create_node_info_with_context(&ref_.source_node, context_lines);
562                    usage_info["edge_kind"] = serde_json::json!(format!("{:?}", ref_.edge_kind));
563                    usage_info["reference_location"] = serde_json::json!({
564                        "file": ref_.location.file.display().to_string(),
565                        "span": {
566                            "start_line": ref_.location.span.start_line,
567                            "end_line": ref_.location.span.end_line,
568                            "start_column": ref_.location.span.start_column,
569                            "end_column": ref_.location.span.end_column
570                        }
571                    });
572                    usage_info
573                })
574                .collect::<Vec<_>>());
575        }
576
577        // Add workflow guidance
578        let workflow_suggestions = generate_symbol_workflow_suggestions(symbol_id_str, &node);
579        result["workflow_guidance"] = serde_json::json!({
580            "next_steps": workflow_suggestions.iter().map(|suggestion| {
581                serde_json::json!({
582                    "tool": suggestion.tool_name,
583                    "parameters": suggestion.suggested_parameters,
584                    "confidence": format!("{:?}", suggestion.confidence),
585                    "reasoning": suggestion.reasoning,
586                    "expected_outcome": suggestion.expected_outcome,
587                    "priority": suggestion.priority
588                })
589            }).collect::<Vec<_>>(),
590            "analysis_context": {
591                "symbol_type": format!("{:?}", node.kind),
592                "file": node.file.display().to_string(),
593                "complexity_hint": match node.kind {
594                    codeprism_core::NodeKind::Class => "Consider analyzing inheritance relationships and decorator patterns",
595                    codeprism_core::NodeKind::Function | codeprism_core::NodeKind::Method => "Consider tracing data flow and analyzing complexity",
596                    codeprism_core::NodeKind::Module => "Consider exploring contained symbols and dependencies",
597                    _ => "Consider analyzing dependencies and references"
598                }
599            }
600        });
601
602        Ok(CallToolResult {
603            content: vec![ToolContent::Text {
604                text: serde_json::to_string_pretty(&result)?,
605            }],
606            is_error: Some(false),
607        })
608    } else {
609        Ok(CallToolResult {
610            content: vec![ToolContent::Text {
611                text: format!("Symbol not found: {}", symbol_id_str),
612            }],
613            is_error: Some(true),
614        })
615    }
616}
617
618/// Search symbols by pattern
619async fn search_symbols(
620    server: &CodePrismMcpServer,
621    arguments: Option<&Value>,
622) -> Result<CallToolResult> {
623    let args = arguments.ok_or_else(|| anyhow::anyhow!("Missing arguments"))?;
624
625    let pattern = args
626        .get("pattern")
627        .and_then(|v| v.as_str())
628        .ok_or_else(|| anyhow::anyhow!("Missing pattern parameter"))?;
629
630    let symbol_types = args
631        .get("symbol_types")
632        .and_then(|v| v.as_array())
633        .map(|arr| {
634            arr.iter()
635                .filter_map(|v| v.as_str())
636                .filter_map(|s| match s {
637                    "function" => Some(codeprism_core::NodeKind::Function),
638                    "class" => Some(codeprism_core::NodeKind::Class),
639                    "variable" => Some(codeprism_core::NodeKind::Variable),
640                    "module" => Some(codeprism_core::NodeKind::Module),
641                    "method" => Some(codeprism_core::NodeKind::Method),
642                    _ => None,
643                })
644                .collect::<Vec<_>>()
645        });
646
647    let inheritance_filters = args
648        .get("inheritance_filters")
649        .and_then(|v| v.as_array())
650        .map(|arr| {
651            arr.iter()
652                .filter_map(|v| v.as_str())
653                .filter_map(parse_inheritance_filter)
654                .collect::<Vec<_>>()
655        });
656
657    let limit = args
658        .get("limit")
659        .and_then(|v| v.as_u64())
660        .map(|v| v as usize);
661
662    let context_lines = args
663        .get("context_lines")
664        .and_then(|v| v.as_u64())
665        .map(|v| v as usize)
666        .unwrap_or(4);
667
668    // Use enhanced search with inheritance filters if provided
669    let results = if let Some(ref filters) = inheritance_filters {
670        server.graph_query().search_symbols_with_inheritance(
671            pattern,
672            symbol_types,
673            Some(filters.clone()),
674            limit,
675        )?
676    } else {
677        server
678            .graph_query()
679            .search_symbols(pattern, symbol_types, limit)?
680    };
681
682    let result = serde_json::json!({
683        "pattern": pattern,
684        "inheritance_filters_applied": inheritance_filters.is_some(),
685        "results": results.iter().map(|symbol| {
686            let mut symbol_info = create_node_info_with_context(&symbol.node, context_lines);
687            symbol_info["references_count"] = serde_json::json!(symbol.references_count);
688            symbol_info["dependencies_count"] = serde_json::json!(symbol.dependencies_count);
689
690            // Add inheritance info for classes when inheritance filters are used
691            if matches!(symbol.node.kind, codeprism_core::NodeKind::Class) && inheritance_filters.is_some() {
692                if let Ok(inheritance_info) = server.graph_query().get_inheritance_info(&symbol.node.id) {
693                    symbol_info["inheritance_summary"] = serde_json::json!({
694                        "is_metaclass": inheritance_info.is_metaclass,
695                        "base_classes": inheritance_info.base_classes.iter().map(|rel| rel.class_name.clone()).collect::<Vec<_>>(),
696                        "mixins": inheritance_info.mixins.iter().map(|rel| rel.class_name.clone()).collect::<Vec<_>>(),
697                        "metaclass": inheritance_info.metaclass.as_ref().map(|mc| mc.class_name.clone())
698                    });
699                }
700            }
701
702            symbol_info
703        }).collect::<Vec<_>>()
704    });
705
706    Ok(CallToolResult {
707        content: vec![ToolContent::Text {
708            text: serde_json::to_string_pretty(&result)?,
709        }],
710        is_error: Some(false),
711    })
712}