1use crate::CodePrismMcpServer;
8use anyhow::Result;
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct ToolCapabilities {
18 #[serde(rename = "listChanged")]
20 #[serde(skip_serializing_if = "Option::is_none")]
21 pub list_changed: Option<bool>,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct Tool {
27 pub name: String,
29 #[serde(skip_serializing_if = "Option::is_none")]
31 pub title: Option<String>,
32 pub description: String,
34 #[serde(rename = "inputSchema")]
36 pub input_schema: Value,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct CallToolParams {
42 pub name: String,
44 #[serde(skip_serializing_if = "Option::is_none")]
46 pub arguments: Option<Value>,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct CallToolResult {
52 pub content: Vec<ToolContent>,
54 #[serde(rename = "isError")]
56 #[serde(skip_serializing_if = "Option::is_none")]
57 pub is_error: Option<bool>,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
62#[serde(tag = "type")]
63pub enum ToolContent {
64 #[serde(rename = "text")]
66 Text {
67 text: String,
69 },
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct ListToolsParams {
75 #[serde(skip_serializing_if = "Option::is_none")]
77 pub cursor: Option<String>,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct ListToolsResult {
83 pub tools: Vec<Tool>,
85 #[serde(rename = "nextCursor")]
87 #[serde(skip_serializing_if = "Option::is_none")]
88 pub next_cursor: Option<String>,
89}
90
91pub struct ToolManager {
94 server: std::sync::Arc<tokio::sync::RwLock<CodePrismMcpServer>>,
95}
96
97impl ToolManager {
98 pub fn new(server: std::sync::Arc<tokio::sync::RwLock<CodePrismMcpServer>>) -> Self {
100 Self { server }
101 }
102
103 pub async fn list_tools(&self, _params: ListToolsParams) -> Result<ListToolsResult> {
105 let tools = vec![
106 Tool {
107 name: "repository_stats".to_string(),
108 title: Some("Repository Statistics".to_string()),
109 description: "Get comprehensive statistics about the repository".to_string(),
110 input_schema: serde_json::json!({
111 "type": "object",
112 "properties": {}
113 }),
114 },
115 Tool {
116 name: "trace_path".to_string(),
117 title: Some("Trace Execution Path".to_string()),
118 description: "Find the shortest path between two code symbols".to_string(),
119 input_schema: serde_json::json!({
120 "type": "object",
121 "properties": {
122 "source": {
123 "type": "string",
124 "description": "Source symbol identifier (node ID)"
125 },
126 "target": {
127 "type": "string",
128 "description": "Target symbol identifier (node ID)"
129 },
130 "max_depth": {
131 "type": "number",
132 "description": "Maximum search depth",
133 "default": 10
134 }
135 },
136 "required": ["source", "target"]
137 }),
138 },
139 Tool {
140 name: "explain_symbol".to_string(),
141 title: Some("Explain Symbol".to_string()),
142 description: "Provide detailed explanation of a code symbol with context".to_string(),
143 input_schema: serde_json::json!({
144 "type": "object",
145 "properties": {
146 "symbol_id": {
147 "type": "string",
148 "description": "Symbol identifier (node ID)"
149 },
150 "include_dependencies": {
151 "type": "boolean",
152 "description": "Include dependency information",
153 "default": false
154 },
155 "include_usages": {
156 "type": "boolean",
157 "description": "Include usage information",
158 "default": false
159 },
160 "context_lines": {
161 "type": "number",
162 "description": "Number of lines before and after the symbol to include as context",
163 "default": 4
164 }
165 },
166 "required": ["symbol_id"]
167 }),
168 },
169 Tool {
170 name: "find_dependencies".to_string(),
171 title: Some("Find Dependencies".to_string()),
172 description: "Analyze dependencies for a code symbol or file".to_string(),
173 input_schema: serde_json::json!({
174 "type": "object",
175 "properties": {
176 "target": {
177 "type": "string",
178 "description": "Symbol ID or file path to analyze"
179 },
180 "dependency_type": {
181 "type": "string",
182 "enum": ["direct", "calls", "imports", "reads", "writes"],
183 "description": "Type of dependencies to find",
184 "default": "direct"
185 }
186 },
187 "required": ["target"]
188 }),
189 },
190 Tool {
191 name: "find_references".to_string(),
192 title: Some("Find References".to_string()),
193 description: "Find all references to a symbol across the codebase".to_string(),
194 input_schema: serde_json::json!({
195 "type": "object",
196 "properties": {
197 "symbol_id": {
198 "type": "string",
199 "description": "Symbol identifier to find references for"
200 },
201 "include_definitions": {
202 "type": "boolean",
203 "description": "Include symbol definitions",
204 "default": true
205 },
206 "context_lines": {
207 "type": "number",
208 "description": "Number of lines before and after the symbol to include as context",
209 "default": 4
210 }
211 },
212 "required": ["symbol_id"]
213 }),
214 },
215 Tool {
216 name: "search_symbols".to_string(),
217 title: Some("Search Symbols".to_string()),
218 description: "Search for symbols by name pattern with advanced inheritance filtering".to_string(),
219 input_schema: serde_json::json!({
220 "type": "object",
221 "properties": {
222 "pattern": {
223 "type": "string",
224 "description": "Search pattern (supports regex)"
225 },
226 "symbol_types": {
227 "type": "array",
228 "items": {
229 "type": "string",
230 "enum": ["function", "class", "variable", "module", "method"]
231 },
232 "description": "Filter by symbol types"
233 },
234 "inheritance_filters": {
235 "type": "array",
236 "items": {
237 "type": "string"
238 },
239 "description": "Filter by inheritance relationships (format: 'inherits_from:ClassName', 'metaclass:MetaclassName', 'uses_mixin:MixinName')"
240 },
241 "limit": {
242 "type": "number",
243 "description": "Maximum number of results",
244 "default": 50
245 },
246 "context_lines": {
247 "type": "number",
248 "description": "Number of lines before and after the symbol to include as context",
249 "default": 4
250 }
251 },
252 "required": ["pattern"]
253 }),
254 },
255 Tool {
256 name: "search_content".to_string(),
257 title: Some("Search Content".to_string()),
258 description: "Search across all content including documentation, comments, and configuration files".to_string(),
259 input_schema: serde_json::json!({
260 "type": "object",
261 "properties": {
262 "query": {
263 "type": "string",
264 "description": "Search query text"
265 },
266 "content_types": {
267 "type": "array",
268 "items": {
269 "type": "string",
270 "enum": ["documentation", "comments", "configuration", "code"]
271 },
272 "description": "Types of content to search in"
273 },
274 "file_patterns": {
275 "type": "array",
276 "items": {
277 "type": "string"
278 },
279 "description": "File patterns to include (regex)"
280 },
281 "exclude_patterns": {
282 "type": "array",
283 "items": {
284 "type": "string"
285 },
286 "description": "File patterns to exclude (regex)"
287 },
288 "max_results": {
289 "type": "number",
290 "description": "Maximum number of results",
291 "default": 50
292 },
293 "case_sensitive": {
294 "type": "boolean",
295 "description": "Case sensitive search",
296 "default": false
297 },
298 "use_regex": {
299 "type": "boolean",
300 "description": "Use regex pattern matching",
301 "default": false
302 },
303 "include_context": {
304 "type": "boolean",
305 "description": "Include context around matches",
306 "default": true
307 }
308 },
309 "required": ["query"]
310 }),
311 },
312 Tool {
313 name: "find_files".to_string(),
314 title: Some("Find Files".to_string()),
315 description: "Find files by name or path pattern".to_string(),
316 input_schema: serde_json::json!({
317 "type": "object",
318 "properties": {
319 "pattern": {
320 "type": "string",
321 "description": "File pattern to search for (supports regex)"
322 }
323 },
324 "required": ["pattern"]
325 }),
326 },
327 Tool {
328 name: "content_stats".to_string(),
329 title: Some("Content Statistics".to_string()),
330 description: "Get statistics about indexed content".to_string(),
331 input_schema: serde_json::json!({
332 "type": "object",
333 "properties": {}
334 }),
335 },
336 Tool {
337 name: "analyze_complexity".to_string(),
338 title: Some("Analyze Code Complexity".to_string()),
339 description: "Calculate complexity metrics for code elements including cyclomatic, cognitive, and maintainability metrics".to_string(),
340 input_schema: serde_json::json!({
341 "type": "object",
342 "properties": {
343 "target": {
344 "type": "string",
345 "description": "File path or symbol ID to analyze"
346 },
347 "metrics": {
348 "type": "array",
349 "items": {
350 "type": "string",
351 "enum": ["cyclomatic", "cognitive", "halstead", "maintainability_index", "all"]
352 },
353 "description": "Types of complexity metrics to calculate",
354 "default": ["all"]
355 },
356 "threshold_warnings": {
357 "type": "boolean",
358 "description": "Include warnings for metrics exceeding thresholds",
359 "default": true
360 }
361 },
362 "required": ["target"]
363 }),
364 },
365
366 Tool {
367 name: "detect_patterns".to_string(),
368 title: Some("Detect Design Patterns".to_string()),
369 description: "Identify design patterns, architectural structures, and metaprogramming patterns in the codebase".to_string(),
370 input_schema: serde_json::json!({
371 "type": "object",
372 "properties": {
373 "scope": {
374 "type": "string",
375 "description": "Scope for pattern detection (repository, package, or file)",
376 "default": "repository"
377 },
378 "pattern_types": {
379 "type": "array",
380 "items": {
381 "type": "string",
382 "enum": ["design_patterns", "anti_patterns", "architectural_patterns", "metaprogramming_patterns", "all"]
383 },
384 "description": "Types of patterns to detect",
385 "default": ["all"]
386 },
387 "confidence_threshold": {
388 "type": "number",
389 "description": "Minimum confidence threshold for pattern detection (0.0 to 1.0)",
390 "default": 0.8,
391 "minimum": 0.0,
392 "maximum": 1.0
393 },
394 "include_suggestions": {
395 "type": "boolean",
396 "description": "Include improvement suggestions for detected patterns",
397 "default": true
398 }
399 },
400 "required": []
401 }),
402 },
403 Tool {
404 name: "analyze_transitive_dependencies".to_string(),
405 title: Some("Analyze Transitive Dependencies".to_string()),
406 description: "Analyze complete dependency chains, detect cycles, and map transitive relationships".to_string(),
407 input_schema: serde_json::json!({
408 "type": "object",
409 "properties": {
410 "target": {
411 "type": "string",
412 "description": "Symbol ID or file path to analyze"
413 },
414 "max_depth": {
415 "type": "number",
416 "description": "Maximum depth for transitive analysis",
417 "default": 5,
418 "minimum": 1,
419 "maximum": 20
420 },
421 "detect_cycles": {
422 "type": "boolean",
423 "description": "Detect circular dependencies",
424 "default": true
425 },
426 "include_external_dependencies": {
427 "type": "boolean",
428 "description": "Include external/third-party dependencies",
429 "default": false
430 },
431 "dependency_types": {
432 "type": "array",
433 "items": {
434 "type": "string",
435 "enum": ["calls", "imports", "reads", "writes", "extends", "implements", "all"]
436 },
437 "description": "Types of dependencies to analyze",
438 "default": ["all"]
439 }
440 },
441 "required": ["target"]
442 }),
443 },
444 Tool {
445 name: "trace_data_flow".to_string(),
446 title: Some("Trace Data Flow".to_string()),
447 description: "Track data flow through the codebase, following variable assignments, function parameters, and transformations".to_string(),
448 input_schema: serde_json::json!({
449 "type": "object",
450 "properties": {
451 "variable_or_parameter": {
452 "type": "string",
453 "description": "Symbol ID of variable or parameter to trace"
454 },
455 "direction": {
456 "type": "string",
457 "enum": ["forward", "backward", "both"],
458 "description": "Direction to trace data flow",
459 "default": "forward"
460 },
461 "include_transformations": {
462 "type": "boolean",
463 "description": "Include data transformations (method calls, assignments)",
464 "default": true
465 },
466 "max_depth": {
467 "type": "number",
468 "description": "Maximum depth for data flow tracing",
469 "default": 10,
470 "minimum": 1,
471 "maximum": 50
472 },
473 "follow_function_calls": {
474 "type": "boolean",
475 "description": "Follow data flow through function calls",
476 "default": true
477 },
478 "include_field_access": {
479 "type": "boolean",
480 "description": "Include field access and modifications",
481 "default": true
482 }
483 },
484 "required": ["variable_or_parameter"]
485 }),
486 },
487
488
489
490
491 Tool {
492 name: "trace_inheritance".to_string(),
493 title: Some("Trace Inheritance Hierarchy".to_string()),
494 description: "Analyze complete inheritance hierarchies, metaclasses, and mixin relationships with detailed visualization".to_string(),
495 input_schema: serde_json::json!({
496 "type": "object",
497 "properties": {
498 "class_name": {
499 "type": "string",
500 "description": "Name of the class to analyze (will search for matching classes)"
501 },
502 "class_id": {
503 "type": "string",
504 "description": "Specific class node ID to analyze (alternative to class_name)"
505 },
506 "direction": {
507 "type": "string",
508 "enum": ["up", "down", "both"],
509 "description": "Direction to trace inheritance (up=parents, down=children, both=complete tree)",
510 "default": "both"
511 },
512 "include_metaclasses": {
513 "type": "boolean",
514 "description": "Include metaclass relationships and analysis",
515 "default": true
516 },
517 "include_mixins": {
518 "type": "boolean",
519 "description": "Include mixin relationships and analysis",
520 "default": true
521 },
522 "include_mro": {
523 "type": "boolean",
524 "description": "Include Method Resolution Order analysis",
525 "default": true
526 },
527 "include_dynamic_attributes": {
528 "type": "boolean",
529 "description": "Include dynamic attributes created by metaclasses",
530 "default": true
531 },
532 "max_depth": {
533 "type": "number",
534 "description": "Maximum depth for inheritance traversal",
535 "default": 10,
536 "minimum": 1,
537 "maximum": 50
538 },
539 "include_source_context": {
540 "type": "boolean",
541 "description": "Include source code context for inheritance relationships",
542 "default": false
543 }
544 },
545 "anyOf": [
546 {"required": ["class_name"]},
547 {"required": ["class_id"]}
548 ]
549 }),
550 },
551 Tool {
552 name: "analyze_decorators".to_string(),
553 title: Some("Analyze Decorators".to_string()),
554 description: "Comprehensive decorator analysis and pattern recognition including effects, usage patterns, and framework-specific decorators".to_string(),
555 input_schema: serde_json::json!({
556 "type": "object",
557 "properties": {
558 "decorator_pattern": {
559 "type": "string",
560 "description": "Decorator name or pattern to analyze (supports regex)"
561 },
562 "decorator_id": {
563 "type": "string",
564 "description": "Specific decorator node ID to analyze (alternative to decorator_pattern)"
565 },
566 "scope": {
567 "type": "string",
568 "enum": ["function", "class", "module", "repository"],
569 "description": "Scope for decorator analysis",
570 "default": "repository"
571 },
572 "include_factories": {
573 "type": "boolean",
574 "description": "Include decorator factory analysis",
575 "default": true
576 },
577 "analyze_effects": {
578 "type": "boolean",
579 "description": "Analyze what effects the decorators have on their targets",
580 "default": true
581 },
582 "include_chains": {
583 "type": "boolean",
584 "description": "Analyze decorator chains and their interaction",
585 "default": true
586 },
587 "detect_patterns": {
588 "type": "boolean",
589 "description": "Detect common decorator patterns (registry, caching, validation, etc.)",
590 "default": true
591 },
592 "include_framework_analysis": {
593 "type": "boolean",
594 "description": "Include framework-specific decorator analysis (Flask, Django, FastAPI, etc.)",
595 "default": true
596 },
597 "include_source_context": {
598 "type": "boolean",
599 "description": "Include source code context for decorator usage",
600 "default": false
601 },
602 "confidence_threshold": {
603 "type": "number",
604 "description": "Minimum confidence threshold for pattern detection (0.0 to 1.0)",
605 "default": 0.8,
606 "minimum": 0.0,
607 "maximum": 1.0
608 },
609 "max_results": {
610 "type": "number",
611 "description": "Maximum number of decorator usages to analyze",
612 "default": 100,
613 "minimum": 1,
614 "maximum": 500
615 }
616 },
617 "anyOf": [
618 {"required": ["decorator_pattern"]},
619 {"required": ["decorator_id"]}
620 ]
621 }),
622 },
623 Tool {
624 name: "find_duplicates".to_string(),
625 title: Some("Find Code Duplicates".to_string()),
626 description: "Detect duplicate code patterns and similar code blocks".to_string(),
627 input_schema: serde_json::json!({
628 "type": "object",
629 "properties": {
630 "similarity_threshold": {
631 "type": "number",
632 "description": "Similarity threshold for detecting duplicates (0.0 to 1.0)",
633 "default": 0.8,
634 "minimum": 0.0,
635 "maximum": 1.0
636 },
637 "min_lines": {
638 "type": "number",
639 "description": "Minimum number of lines for a duplicate block",
640 "default": 3,
641 "minimum": 1
642 },
643 "scope": {
644 "type": "string",
645 "description": "Scope for duplicate detection",
646 "default": "repository"
647 }
648 },
649 "required": []
650 }),
651 },
652 Tool {
653 name: "find_unused_code".to_string(),
654 title: Some("Find Unused Code".to_string()),
655 description: "Identify unused functions, classes, variables, and imports".to_string(),
656 input_schema: serde_json::json!({
657 "type": "object",
658 "properties": {
659 "scope": {
660 "type": "string",
661 "description": "Scope for unused code analysis",
662 "default": "repository"
663 },
664 "analyze_types": {
665 "type": "array",
666 "items": {
667 "type": "string",
668 "enum": ["functions", "classes", "variables", "imports", "all"]
669 },
670 "description": "Types of code elements to analyze",
671 "default": ["functions", "classes", "variables", "imports"]
672 },
673 "confidence_threshold": {
674 "type": "number",
675 "description": "Confidence threshold for unused detection",
676 "default": 0.7,
677 "minimum": 0.0,
678 "maximum": 1.0
679 },
680 "consider_external_apis": {
681 "type": "boolean",
682 "description": "Consider external API usage",
683 "default": true
684 },
685 "include_dead_code": {
686 "type": "boolean",
687 "description": "Include dead code block detection",
688 "default": true
689 },
690 "exclude_patterns": {
691 "type": "array",
692 "items": {
693 "type": "string"
694 },
695 "description": "Patterns to exclude from analysis"
696 }
697 },
698 "required": []
699 }),
700 },
701 Tool {
702 name: "analyze_security".to_string(),
703 title: Some("Analyze Security Vulnerabilities".to_string()),
704 description: "Identify security vulnerabilities and potential threats".to_string(),
705 input_schema: serde_json::json!({
706 "type": "object",
707 "properties": {
708 "scope": {
709 "type": "string",
710 "description": "Scope for security analysis",
711 "default": "repository"
712 },
713 "vulnerability_types": {
714 "type": "array",
715 "items": {
716 "type": "string",
717 "enum": ["injection", "authentication", "authorization", "data_exposure", "unsafe_patterns", "crypto", "all"]
718 },
719 "description": "Types of vulnerabilities to check",
720 "default": ["injection", "authentication", "authorization"]
721 },
722 "severity_threshold": {
723 "type": "string",
724 "enum": ["low", "medium", "high", "critical"],
725 "description": "Minimum severity level to report",
726 "default": "medium"
727 },
728 "include_data_flow_analysis": {
729 "type": "boolean",
730 "description": "Include data flow analysis for vulnerability detection",
731 "default": false
732 },
733 "check_external_dependencies": {
734 "type": "boolean",
735 "description": "Check external dependencies for known vulnerabilities",
736 "default": true
737 },
738 "exclude_patterns": {
739 "type": "array",
740 "items": {
741 "type": "string"
742 },
743 "description": "Patterns to exclude from analysis"
744 }
745 },
746 "required": []
747 }),
748 },
749 Tool {
750 name: "analyze_performance".to_string(),
751 title: Some("Analyze Performance Issues".to_string()),
752 description: "Identify performance bottlenecks and optimization opportunities".to_string(),
753 input_schema: serde_json::json!({
754 "type": "object",
755 "properties": {
756 "scope": {
757 "type": "string",
758 "description": "Scope for performance analysis",
759 "default": "repository"
760 },
761 "analysis_types": {
762 "type": "array",
763 "items": {
764 "type": "string",
765 "enum": ["time_complexity", "memory_usage", "hot_spots", "anti_patterns", "scalability", "all"]
766 },
767 "description": "Types of performance analysis to perform",
768 "default": ["time_complexity", "memory_usage", "hot_spots"]
769 },
770 "complexity_threshold": {
771 "type": "string",
772 "enum": ["low", "medium", "high"],
773 "description": "Complexity threshold for reporting issues",
774 "default": "medium"
775 },
776 "include_algorithmic_analysis": {
777 "type": "boolean",
778 "description": "Include algorithmic complexity analysis",
779 "default": true
780 },
781 "detect_bottlenecks": {
782 "type": "boolean",
783 "description": "Detect performance bottlenecks",
784 "default": true
785 },
786 "exclude_patterns": {
787 "type": "array",
788 "items": {
789 "type": "string"
790 },
791 "description": "Patterns to exclude from analysis"
792 }
793 },
794 "required": []
795 }),
796 },
797 Tool {
798 name: "analyze_api_surface".to_string(),
799 title: Some("Analyze API Surface".to_string()),
800 description: "Analyze public API surface, versioning, and breaking changes".to_string(),
801 input_schema: serde_json::json!({
802 "type": "object",
803 "properties": {
804 "scope": {
805 "type": "string",
806 "description": "Scope for API surface analysis",
807 "default": "repository"
808 },
809 "analysis_types": {
810 "type": "array",
811 "items": {
812 "type": "string",
813 "enum": ["public_api", "versioning", "breaking_changes", "documentation_coverage", "compatibility", "all"]
814 },
815 "description": "Types of API analysis to perform",
816 "default": ["public_api", "versioning", "breaking_changes"]
817 },
818 "api_version": {
819 "type": "string",
820 "description": "API version to analyze (optional)"
821 },
822 "include_private_apis": {
823 "type": "boolean",
824 "description": "Include private APIs in analysis",
825 "default": false
826 },
827 "check_documentation_coverage": {
828 "type": "boolean",
829 "description": "Check documentation coverage for APIs",
830 "default": true
831 },
832 "detect_breaking_changes": {
833 "type": "boolean",
834 "description": "Detect potential breaking changes",
835 "default": true
836 },
837 "exclude_patterns": {
838 "type": "array",
839 "items": {
840 "type": "string"
841 },
842 "description": "Patterns to exclude from analysis"
843 }
844 },
845 "required": []
846 }),
847 },
848 ];
849
850 Ok(ListToolsResult {
851 tools,
852 next_cursor: None,
853 })
854 }
855
856 pub async fn call_tool(&self, params: CallToolParams) -> Result<CallToolResult> {
858 let server = self.server.read().await;
859
860 match params.name.as_str() {
861 "repository_stats" => self.repository_stats(&server).await,
862 "trace_path" => self.trace_path(&server, params.arguments).await,
863 "explain_symbol" => self.explain_symbol(&server, params.arguments).await,
864 "find_dependencies" => self.find_dependencies(&server, params.arguments).await,
865 "find_references" => self.find_references(&server, params.arguments).await,
866 "search_symbols" => self.search_symbols(&server, params.arguments).await,
867 "search_content" => self.search_content(&server, params.arguments).await,
868 "find_files" => self.find_files(&server, params.arguments).await,
869 "content_stats" => self.content_stats(&server).await,
870 "analyze_complexity" => self.analyze_complexity(&server, params.arguments).await,
871 "find_duplicates" => self.find_duplicates(&server, params.arguments).await,
872
873 "detect_patterns" => self.detect_patterns(&server, params.arguments).await,
874 "analyze_transitive_dependencies" => {
875 self.analyze_transitive_dependencies(&server, params.arguments)
876 .await
877 }
878 "trace_data_flow" => self.trace_data_flow(&server, params.arguments).await,
879 "find_unused_code" => self.find_unused_code(&server, params.arguments).await,
880
881 "trace_inheritance" => self.trace_inheritance(&server, params.arguments).await,
882 "analyze_decorators" => self.analyze_decorators(&server, params.arguments).await,
883
884 "analyze_security" => self.analyze_security(&server, params.arguments).await,
885 "analyze_performance" => self.analyze_performance(&server, params.arguments).await,
886 "analyze_api_surface" => self.analyze_api_surface(&server, params.arguments).await,
887 _ => Ok(CallToolResult {
888 content: vec![ToolContent::Text {
889 text: format!("Unknown tool: {}", params.name),
890 }],
891 is_error: Some(true),
892 }),
893 }
894 }
895
896 async fn repository_stats(&self, server: &CodePrismMcpServer) -> Result<CallToolResult> {
898 let result = if let Some(repo_path) = server.repository_path() {
899 let file_count = server
900 .scanner()
901 .discover_files(repo_path)
902 .map(|files| files.len())
903 .unwrap_or(0);
904
905 let graph_stats = server.graph_store().get_stats();
906
907 serde_json::json!({
908 "repository_path": repo_path.display().to_string(),
909 "total_files": file_count,
910 "total_nodes": graph_stats.total_nodes,
911 "total_edges": graph_stats.total_edges,
912 "nodes_by_kind": graph_stats.nodes_by_kind,
913 "status": "active"
914 })
915 } else {
916 serde_json::json!({
917 "error": "No repository initialized"
918 })
919 };
920
921 Ok(CallToolResult {
922 content: vec![ToolContent::Text {
923 text: serde_json::to_string_pretty(&result)?,
924 }],
925 is_error: Some(false),
926 })
927 }
928
929 async fn trace_path(
931 &self,
932 server: &CodePrismMcpServer,
933 arguments: Option<Value>,
934 ) -> Result<CallToolResult> {
935 let args = arguments.ok_or_else(|| anyhow::anyhow!("Missing arguments"))?;
936
937 let source_str = args
938 .get("source")
939 .and_then(|v| v.as_str())
940 .ok_or_else(|| anyhow::anyhow!("Missing source parameter"))?;
941
942 let target_str = args
943 .get("target")
944 .and_then(|v| v.as_str())
945 .ok_or_else(|| anyhow::anyhow!("Missing target parameter"))?;
946
947 let max_depth = args
948 .get("max_depth")
949 .and_then(|v| v.as_u64())
950 .map(|v| v as usize);
951
952 let source_id = self.parse_node_id(source_str)?;
954 let target_id = self.parse_node_id(target_str)?;
955
956 match server
957 .graph_query()
958 .find_path(&source_id, &target_id, max_depth)?
959 {
960 Some(path_result) => {
961 let result = serde_json::json!({
962 "found": true,
963 "source": source_str,
964 "target": target_str,
965 "distance": path_result.distance,
966 "path": path_result.path.iter().map(|id| id.to_hex()).collect::<Vec<_>>(),
967 "edges": path_result.edges.iter().map(|edge| {
968 serde_json::json!({
969 "source": edge.source.to_hex(),
970 "target": edge.target.to_hex(),
971 "kind": format!("{:?}", edge.kind)
972 })
973 }).collect::<Vec<_>>()
974 });
975
976 Ok(CallToolResult {
977 content: vec![ToolContent::Text {
978 text: serde_json::to_string_pretty(&result)?,
979 }],
980 is_error: Some(false),
981 })
982 }
983 None => {
984 let result = serde_json::json!({
985 "found": false,
986 "source": source_str,
987 "target": target_str,
988 "message": "No path found between the specified symbols"
989 });
990
991 Ok(CallToolResult {
992 content: vec![ToolContent::Text {
993 text: serde_json::to_string_pretty(&result)?,
994 }],
995 is_error: Some(false),
996 })
997 }
998 }
999 }
1000
1001 async fn explain_symbol(
1003 &self,
1004 server: &CodePrismMcpServer,
1005 arguments: Option<Value>,
1006 ) -> Result<CallToolResult> {
1007 let args = arguments.ok_or_else(|| anyhow::anyhow!("Missing arguments"))?;
1008
1009 let symbol_id_str = args
1010 .get("symbol_id")
1011 .and_then(|v| v.as_str())
1012 .ok_or_else(|| anyhow::anyhow!("Missing symbol_id parameter"))?;
1013
1014 let include_dependencies = args
1015 .get("include_dependencies")
1016 .and_then(|v| v.as_bool())
1017 .unwrap_or(false);
1018
1019 let include_usages = args
1020 .get("include_usages")
1021 .and_then(|v| v.as_bool())
1022 .unwrap_or(false);
1023
1024 let context_lines = args
1025 .get("context_lines")
1026 .and_then(|v| v.as_u64())
1027 .map(|v| v as usize)
1028 .unwrap_or(4);
1029
1030 let symbol_id = self.parse_node_id(symbol_id_str)?;
1031
1032 if let Some(node) = server.graph_store().get_node(&symbol_id) {
1033 let mut result = serde_json::json!({
1034 "symbol": self.create_node_info_with_context(&node, context_lines)
1035 });
1036
1037 if matches!(node.kind, codeprism_core::NodeKind::Class) {
1039 if let Ok(inheritance_info) = server.graph_query().get_inheritance_info(&symbol_id)
1040 {
1041 let mut inheritance_data = serde_json::Map::new();
1042
1043 inheritance_data.insert(
1045 "class_name".to_string(),
1046 serde_json::Value::String(inheritance_info.class_name),
1047 );
1048 inheritance_data.insert(
1049 "is_metaclass".to_string(),
1050 serde_json::Value::Bool(inheritance_info.is_metaclass),
1051 );
1052
1053 if !inheritance_info.base_classes.is_empty() {
1055 let base_classes: Vec<_> = inheritance_info
1056 .base_classes
1057 .iter()
1058 .map(|rel| {
1059 serde_json::json!({
1060 "name": rel.class_name,
1061 "relationship_type": rel.relationship_type,
1062 "file": rel.file.display().to_string(),
1063 "span": {
1064 "start_line": rel.span.start_line,
1065 "end_line": rel.span.end_line,
1066 "start_column": rel.span.start_column,
1067 "end_column": rel.span.end_column
1068 }
1069 })
1070 })
1071 .collect();
1072 inheritance_data.insert(
1073 "base_classes".to_string(),
1074 serde_json::Value::Array(base_classes),
1075 );
1076 }
1077
1078 if !inheritance_info.subclasses.is_empty() {
1080 let subclasses: Vec<_> = inheritance_info
1081 .subclasses
1082 .iter()
1083 .map(|rel| {
1084 serde_json::json!({
1085 "name": rel.class_name,
1086 "file": rel.file.display().to_string(),
1087 "span": {
1088 "start_line": rel.span.start_line,
1089 "end_line": rel.span.end_line,
1090 "start_column": rel.span.start_column,
1091 "end_column": rel.span.end_column
1092 }
1093 })
1094 })
1095 .collect();
1096 inheritance_data.insert(
1097 "subclasses".to_string(),
1098 serde_json::Value::Array(subclasses),
1099 );
1100 }
1101
1102 if let Some(metaclass) = inheritance_info.metaclass {
1104 inheritance_data.insert(
1105 "metaclass".to_string(),
1106 serde_json::json!({
1107 "name": metaclass.class_name,
1108 "file": metaclass.file.display().to_string(),
1109 "span": {
1110 "start_line": metaclass.span.start_line,
1111 "end_line": metaclass.span.end_line,
1112 "start_column": metaclass.span.start_column,
1113 "end_column": metaclass.span.end_column
1114 }
1115 }),
1116 );
1117 }
1118
1119 if !inheritance_info.mixins.is_empty() {
1121 let mixins: Vec<_> = inheritance_info
1122 .mixins
1123 .iter()
1124 .map(|rel| {
1125 serde_json::json!({
1126 "name": rel.class_name,
1127 "file": rel.file.display().to_string(),
1128 "span": {
1129 "start_line": rel.span.start_line,
1130 "end_line": rel.span.end_line,
1131 "start_column": rel.span.start_column,
1132 "end_column": rel.span.end_column
1133 }
1134 })
1135 })
1136 .collect();
1137 inheritance_data
1138 .insert("mixins".to_string(), serde_json::Value::Array(mixins));
1139 }
1140
1141 if !inheritance_info.method_resolution_order.is_empty() {
1143 inheritance_data.insert(
1144 "method_resolution_order".to_string(),
1145 serde_json::Value::Array(
1146 inheritance_info
1147 .method_resolution_order
1148 .iter()
1149 .map(|name| serde_json::Value::String(name.clone()))
1150 .collect(),
1151 ),
1152 );
1153 }
1154
1155 if !inheritance_info.dynamic_attributes.is_empty() {
1157 let dynamic_attrs: Vec<_> = inheritance_info
1158 .dynamic_attributes
1159 .iter()
1160 .map(|attr| {
1161 serde_json::json!({
1162 "name": attr.name,
1163 "created_by": attr.created_by,
1164 "type": attr.attribute_type
1165 })
1166 })
1167 .collect();
1168 inheritance_data.insert(
1169 "dynamic_attributes".to_string(),
1170 serde_json::Value::Array(dynamic_attrs),
1171 );
1172 }
1173
1174 if !inheritance_info.inheritance_chain.is_empty() {
1176 inheritance_data.insert(
1177 "inheritance_chain".to_string(),
1178 serde_json::Value::Array(
1179 inheritance_info
1180 .inheritance_chain
1181 .iter()
1182 .map(|name| serde_json::Value::String(name.clone()))
1183 .collect(),
1184 ),
1185 );
1186 }
1187
1188 result["inheritance"] = serde_json::Value::Object(inheritance_data);
1189 }
1190 }
1191
1192 if include_dependencies {
1193 let dependencies = server
1194 .graph_query()
1195 .find_dependencies(&symbol_id, codeprism_core::graph::DependencyType::Direct)?;
1196
1197 let valid_dependencies: Vec<_> = dependencies
1199 .iter()
1200 .filter(|dep| self.is_valid_dependency_node(&dep.target_node))
1201 .collect();
1202
1203 result["dependencies"] = serde_json::json!(valid_dependencies
1204 .iter()
1205 .map(|dep| {
1206 let mut dep_info =
1207 self.create_node_info_with_context(&dep.target_node, context_lines);
1208 dep_info["edge_kind"] = serde_json::json!(format!("{:?}", dep.edge_kind));
1209 dep_info
1210 })
1211 .collect::<Vec<_>>());
1212 }
1213
1214 if include_usages {
1215 let references = server.graph_query().find_references(&symbol_id)?;
1216 result["usages"] = serde_json::json!(references
1217 .iter()
1218 .map(|ref_| {
1219 let mut usage_info =
1220 self.create_node_info_with_context(&ref_.source_node, context_lines);
1221 usage_info["edge_kind"] =
1222 serde_json::json!(format!("{:?}", ref_.edge_kind));
1223 usage_info["reference_location"] = serde_json::json!({
1224 "file": ref_.location.file.display().to_string(),
1225 "span": {
1226 "start_line": ref_.location.span.start_line,
1227 "end_line": ref_.location.span.end_line,
1228 "start_column": ref_.location.span.start_column,
1229 "end_column": ref_.location.span.end_column
1230 }
1231 });
1232 usage_info
1233 })
1234 .collect::<Vec<_>>());
1235 }
1236
1237 Ok(CallToolResult {
1238 content: vec![ToolContent::Text {
1239 text: serde_json::to_string_pretty(&result)?,
1240 }],
1241 is_error: Some(false),
1242 })
1243 } else {
1244 Ok(CallToolResult {
1245 content: vec![ToolContent::Text {
1246 text: format!("Symbol not found: {}", symbol_id_str),
1247 }],
1248 is_error: Some(true),
1249 })
1250 }
1251 }
1252
1253 fn is_valid_dependency_node(&self, node: &codeprism_core::Node) -> bool {
1255 if matches!(node.kind, codeprism_core::NodeKind::Call) {
1257 if node.name.is_empty()
1259 || node.name == ")"
1260 || node.name == "("
1261 || node.name.trim().is_empty()
1262 || node.name.chars().all(|c| !c.is_alphanumeric() && c != '_')
1263 {
1264 return false;
1265 }
1266 }
1267
1268 true
1270 }
1271
1272 async fn find_dependencies(
1274 &self,
1275 server: &CodePrismMcpServer,
1276 arguments: Option<Value>,
1277 ) -> Result<CallToolResult> {
1278 let args = arguments.ok_or_else(|| anyhow::anyhow!("Missing arguments"))?;
1279
1280 let target = args
1281 .get("target")
1282 .and_then(|v| v.as_str())
1283 .ok_or_else(|| anyhow::anyhow!("Missing target parameter"))?;
1284
1285 let dependency_type_str = args
1286 .get("dependency_type")
1287 .and_then(|v| v.as_str())
1288 .unwrap_or("direct");
1289
1290 let dependency_type = match dependency_type_str {
1291 "direct" => codeprism_core::graph::DependencyType::Direct,
1292 "calls" => codeprism_core::graph::DependencyType::Calls,
1293 "imports" => codeprism_core::graph::DependencyType::Imports,
1294 "reads" => codeprism_core::graph::DependencyType::Reads,
1295 "writes" => codeprism_core::graph::DependencyType::Writes,
1296 _ => {
1297 return Ok(CallToolResult {
1298 content: vec![ToolContent::Text {
1299 text: format!("Invalid dependency type: {}", dependency_type_str),
1300 }],
1301 is_error: Some(true),
1302 })
1303 }
1304 };
1305
1306 let dependencies = if let Ok(node_id) = self.parse_node_id(target) {
1308 server
1309 .graph_query()
1310 .find_dependencies(&node_id, dependency_type)?
1311 } else {
1312 let file_path = std::path::PathBuf::from(target);
1314 let nodes = server.graph_store().get_nodes_in_file(&file_path);
1315 let mut all_deps = Vec::new();
1316 for node in nodes {
1317 let deps = server
1318 .graph_query()
1319 .find_dependencies(&node.id, dependency_type.clone())?;
1320 all_deps.extend(deps);
1321 }
1322 all_deps
1323 };
1324
1325 let valid_dependencies: Vec<_> = dependencies
1327 .iter()
1328 .filter(|dep| self.is_valid_dependency_node(&dep.target_node))
1329 .collect();
1330
1331 let result = serde_json::json!({
1332 "target": target,
1333 "dependency_type": dependency_type_str,
1334 "dependencies": valid_dependencies.iter().map(|dep| {
1335 serde_json::json!({
1336 "id": dep.target_node.id.to_hex(),
1337 "name": dep.target_node.name,
1338 "kind": format!("{:?}", dep.target_node.kind),
1339 "file": dep.target_node.file.display().to_string(),
1340 "edge_kind": format!("{:?}", dep.edge_kind)
1341 })
1342 }).collect::<Vec<_>>()
1343 });
1344
1345 Ok(CallToolResult {
1346 content: vec![ToolContent::Text {
1347 text: serde_json::to_string_pretty(&result)?,
1348 }],
1349 is_error: Some(false),
1350 })
1351 }
1352
1353 async fn find_references(
1355 &self,
1356 server: &CodePrismMcpServer,
1357 arguments: Option<Value>,
1358 ) -> Result<CallToolResult> {
1359 let args = arguments.ok_or_else(|| anyhow::anyhow!("Missing arguments"))?;
1360
1361 let symbol_id_str = args
1362 .get("symbol_id")
1363 .and_then(|v| v.as_str())
1364 .ok_or_else(|| anyhow::anyhow!("Missing symbol_id parameter"))?;
1365
1366 let _include_definitions = args
1367 .get("include_definitions")
1368 .and_then(|v| v.as_bool())
1369 .unwrap_or(true);
1370
1371 let context_lines = args
1372 .get("context_lines")
1373 .and_then(|v| v.as_u64())
1374 .map(|v| v as usize)
1375 .unwrap_or(4);
1376
1377 let symbol_id = self.parse_node_id(symbol_id_str)?;
1378 let references = server.graph_query().find_references(&symbol_id)?;
1379
1380 let result = serde_json::json!({
1381 "symbol_id": symbol_id_str,
1382 "references": references.iter().map(|ref_| {
1383 let mut ref_info = self.create_node_info_with_context(&ref_.source_node, context_lines);
1384 ref_info["edge_kind"] = serde_json::json!(format!("{:?}", ref_.edge_kind));
1385 ref_info["reference_location"] = serde_json::json!({
1386 "file": ref_.location.file.display().to_string(),
1387 "span": {
1388 "start_line": ref_.location.span.start_line,
1389 "end_line": ref_.location.span.end_line,
1390 "start_column": ref_.location.span.start_column,
1391 "end_column": ref_.location.span.end_column
1392 }
1393 });
1394 ref_info
1395 }).collect::<Vec<_>>()
1396 });
1397
1398 Ok(CallToolResult {
1399 content: vec![ToolContent::Text {
1400 text: serde_json::to_string_pretty(&result)?,
1401 }],
1402 is_error: Some(false),
1403 })
1404 }
1405
1406 async fn search_symbols(
1408 &self,
1409 server: &CodePrismMcpServer,
1410 arguments: Option<Value>,
1411 ) -> Result<CallToolResult> {
1412 let args = arguments.ok_or_else(|| anyhow::anyhow!("Missing arguments"))?;
1413
1414 let pattern = args
1415 .get("pattern")
1416 .and_then(|v| v.as_str())
1417 .ok_or_else(|| anyhow::anyhow!("Missing pattern parameter"))?;
1418
1419 let symbol_types = args
1420 .get("symbol_types")
1421 .and_then(|v| v.as_array())
1422 .map(|arr| {
1423 arr.iter()
1424 .filter_map(|v| v.as_str())
1425 .filter_map(|s| match s {
1426 "function" => Some(codeprism_core::NodeKind::Function),
1427 "class" => Some(codeprism_core::NodeKind::Class),
1428 "variable" => Some(codeprism_core::NodeKind::Variable),
1429 "module" => Some(codeprism_core::NodeKind::Module),
1430 "method" => Some(codeprism_core::NodeKind::Method),
1431 _ => None,
1432 })
1433 .collect::<Vec<_>>()
1434 });
1435
1436 let inheritance_filters = args
1437 .get("inheritance_filters")
1438 .and_then(|v| v.as_array())
1439 .map(|arr| {
1440 arr.iter()
1441 .filter_map(|v| v.as_str())
1442 .filter_map(|s| self.parse_inheritance_filter(s))
1443 .collect::<Vec<_>>()
1444 });
1445
1446 let limit = args
1447 .get("limit")
1448 .and_then(|v| v.as_u64())
1449 .map(|v| v as usize);
1450
1451 let context_lines = args
1452 .get("context_lines")
1453 .and_then(|v| v.as_u64())
1454 .map(|v| v as usize)
1455 .unwrap_or(4);
1456
1457 let results = if let Some(ref filters) = inheritance_filters {
1459 server.graph_query().search_symbols_with_inheritance(
1460 pattern,
1461 symbol_types,
1462 Some(filters.clone()),
1463 limit,
1464 )?
1465 } else {
1466 server
1467 .graph_query()
1468 .search_symbols(pattern, symbol_types, limit)?
1469 };
1470
1471 let result = serde_json::json!({
1472 "pattern": pattern,
1473 "inheritance_filters_applied": inheritance_filters.is_some(),
1474 "results": results.iter().map(|symbol| {
1475 let mut symbol_info = self.create_node_info_with_context(&symbol.node, context_lines);
1476 symbol_info["references_count"] = serde_json::json!(symbol.references_count);
1477 symbol_info["dependencies_count"] = serde_json::json!(symbol.dependencies_count);
1478
1479 if matches!(symbol.node.kind, codeprism_core::NodeKind::Class) && inheritance_filters.is_some() {
1481 if let Ok(inheritance_info) = server.graph_query().get_inheritance_info(&symbol.node.id) {
1482 symbol_info["inheritance_summary"] = serde_json::json!({
1483 "is_metaclass": inheritance_info.is_metaclass,
1484 "base_classes": inheritance_info.base_classes.iter().map(|rel| rel.class_name.clone()).collect::<Vec<_>>(),
1485 "mixins": inheritance_info.mixins.iter().map(|rel| rel.class_name.clone()).collect::<Vec<_>>(),
1486 "metaclass": inheritance_info.metaclass.as_ref().map(|mc| mc.class_name.clone())
1487 });
1488 }
1489 }
1490
1491 symbol_info
1492 }).collect::<Vec<_>>()
1493 });
1494
1495 Ok(CallToolResult {
1496 content: vec![ToolContent::Text {
1497 text: serde_json::to_string_pretty(&result)?,
1498 }],
1499 is_error: Some(false),
1500 })
1501 }
1502
1503 fn parse_inheritance_filter(
1505 &self,
1506 filter_str: &str,
1507 ) -> Option<codeprism_core::InheritanceFilter> {
1508 if let Some(colon_pos) = filter_str.find(':') {
1509 let filter_type = &filter_str[..colon_pos];
1510 let class_name = &filter_str[colon_pos + 1..];
1511
1512 match filter_type {
1513 "inherits_from" => Some(codeprism_core::InheritanceFilter::InheritsFrom(
1514 class_name.to_string(),
1515 )),
1516 "metaclass" => Some(codeprism_core::InheritanceFilter::HasMetaclass(
1517 class_name.to_string(),
1518 )),
1519 "uses_mixin" => Some(codeprism_core::InheritanceFilter::UsesMixin(
1520 class_name.to_string(),
1521 )),
1522 _ => None,
1523 }
1524 } else {
1525 None
1526 }
1527 }
1528
1529 fn parse_node_id(&self, hex_str: &str) -> Result<codeprism_core::NodeId> {
1531 codeprism_core::NodeId::from_hex(hex_str)
1532 .map_err(|e| anyhow::anyhow!("Invalid node ID format: {}", e))
1533 }
1534
1535 fn extract_source_context(
1537 &self,
1538 file_path: &std::path::Path,
1539 line_number: usize,
1540 context_lines: usize,
1541 ) -> Option<serde_json::Value> {
1542 let content = match std::fs::read_to_string(file_path) {
1544 Ok(content) => content,
1545 Err(_) => return None,
1546 };
1547
1548 let lines: Vec<&str> = content.lines().collect();
1549 let total_lines = lines.len();
1550
1551 if line_number == 0 || line_number > total_lines {
1552 return None;
1553 }
1554
1555 let target_line_idx = line_number - 1;
1557
1558 let start_idx = target_line_idx.saturating_sub(context_lines);
1560 let end_idx = std::cmp::min(target_line_idx + context_lines, total_lines - 1);
1561
1562 let mut context_lines_with_numbers = Vec::new();
1564 for (i, _) in lines.iter().enumerate().take(end_idx + 1).skip(start_idx) {
1565 context_lines_with_numbers.push(serde_json::json!({
1566 "line_number": i + 1,
1567 "content": lines[i],
1568 "is_target": i == target_line_idx
1569 }));
1570 }
1571
1572 Some(serde_json::json!({
1573 "target_line": line_number,
1574 "context_range": {
1575 "start_line": start_idx + 1,
1576 "end_line": end_idx + 1
1577 },
1578 "lines": context_lines_with_numbers
1579 }))
1580 }
1581
1582 fn create_node_info_with_context(
1584 &self,
1585 node: &codeprism_core::Node,
1586 context_lines: usize,
1587 ) -> serde_json::Value {
1588 let mut node_info = serde_json::json!({
1589 "id": node.id.to_hex(),
1590 "name": node.name,
1591 "kind": format!("{:?}", node.kind),
1592 "language": format!("{:?}", node.lang),
1593 "file": node.file.display().to_string(),
1594 "span": {
1595 "start_line": node.span.start_line,
1596 "end_line": node.span.end_line,
1597 "start_column": node.span.start_column,
1598 "end_column": node.span.end_column
1599 },
1600 "signature": node.signature
1601 });
1602
1603 if let Some(context) =
1605 self.extract_source_context(&node.file, node.span.start_line, context_lines)
1606 {
1607 node_info["source_context"] = context;
1608 }
1609
1610 node_info
1611 }
1612
1613 async fn search_content(
1615 &self,
1616 server: &CodePrismMcpServer,
1617 arguments: Option<Value>,
1618 ) -> Result<CallToolResult> {
1619 let args = arguments.ok_or_else(|| anyhow::anyhow!("Missing arguments"))?;
1620
1621 let query = args
1622 .get("query")
1623 .and_then(|v| v.as_str())
1624 .ok_or_else(|| anyhow::anyhow!("Missing query parameter"))?;
1625
1626 let content_types = args
1627 .get("content_types")
1628 .and_then(|v| v.as_array())
1629 .map(|arr| {
1630 arr.iter()
1631 .filter_map(|v| v.as_str())
1632 .map(|s| s.to_string())
1633 .collect::<Vec<_>>()
1634 })
1635 .unwrap_or_default();
1636
1637 let file_patterns = args
1638 .get("file_patterns")
1639 .and_then(|v| v.as_array())
1640 .map(|arr| {
1641 arr.iter()
1642 .filter_map(|v| v.as_str())
1643 .map(|s| s.to_string())
1644 .collect::<Vec<_>>()
1645 })
1646 .unwrap_or_default();
1647
1648 let exclude_patterns = args
1649 .get("exclude_patterns")
1650 .and_then(|v| v.as_array())
1651 .map(|arr| {
1652 arr.iter()
1653 .filter_map(|v| v.as_str())
1654 .map(|s| s.to_string())
1655 .collect::<Vec<_>>()
1656 })
1657 .unwrap_or_default();
1658
1659 let max_results = args
1660 .get("max_results")
1661 .and_then(|v| v.as_u64())
1662 .map(|v| v as usize)
1663 .unwrap_or(50);
1664
1665 let case_sensitive = args
1666 .get("case_sensitive")
1667 .and_then(|v| v.as_bool())
1668 .unwrap_or(false);
1669
1670 let use_regex = args
1671 .get("use_regex")
1672 .and_then(|v| v.as_bool())
1673 .unwrap_or(false);
1674
1675 let include_context = args
1676 .get("include_context")
1677 .and_then(|v| v.as_bool())
1678 .unwrap_or(true);
1679
1680 let stats = server.content_search().get_stats();
1682 if stats.total_files == 0 {
1683 let result = serde_json::json!({
1684 "query": query,
1685 "results": [],
1686 "total_results": 0,
1687 "status": "no_content_indexed",
1688 "message": "Content search is not yet indexed. This feature requires repository content to be indexed first.",
1689 "suggestion": "Repository indexing may still be in progress. Try again in a few moments."
1690 });
1691
1692 return Ok(CallToolResult {
1693 content: vec![ToolContent::Text {
1694 text: serde_json::to_string_pretty(&result)?,
1695 }],
1696 is_error: Some(false),
1697 });
1698 }
1699
1700 match server
1701 .content_search()
1702 .simple_search(query, Some(max_results))
1703 {
1704 Ok(search_results) => {
1705 let result = serde_json::json!({
1706 "query": query,
1707 "content_types": content_types,
1708 "file_patterns": file_patterns,
1709 "exclude_patterns": exclude_patterns,
1710 "max_results": max_results,
1711 "case_sensitive": case_sensitive,
1712 "use_regex": use_regex,
1713 "include_context": include_context,
1714 "total_results": search_results.len(),
1715 "results": search_results.iter().map(|result| {
1716 serde_json::json!({
1717 "file": result.chunk.file_path.display().to_string(),
1718 "content_type": format!("{:?}", result.chunk.content_type),
1719 "score": result.score,
1720 "matches": result.matches.iter().map(|m| {
1721 serde_json::json!({
1722 "text": m.text,
1723 "line": m.line_number,
1724 "column": m.column_number,
1725 "context_before": m.context_before,
1726 "context_after": m.context_after
1727 })
1728 }).collect::<Vec<_>>(),
1729 "chunk_content_preview": if result.chunk.content.len() > 200 {
1730 format!("{}...", &result.chunk.content[..200])
1731 } else {
1732 result.chunk.content.clone()
1733 }
1734 })
1735 }).collect::<Vec<_>>()
1736 });
1737
1738 Ok(CallToolResult {
1739 content: vec![ToolContent::Text {
1740 text: serde_json::to_string_pretty(&result)?,
1741 }],
1742 is_error: Some(false),
1743 })
1744 }
1745 Err(e) => Ok(CallToolResult {
1746 content: vec![ToolContent::Text {
1747 text: format!("Content search error: {}", e),
1748 }],
1749 is_error: Some(true),
1750 }),
1751 }
1752 }
1753
1754 async fn find_files(
1756 &self,
1757 server: &CodePrismMcpServer,
1758 arguments: Option<Value>,
1759 ) -> Result<CallToolResult> {
1760 let args = arguments.ok_or_else(|| anyhow::anyhow!("Missing arguments"))?;
1761
1762 let pattern = args
1763 .get("pattern")
1764 .and_then(|v| v.as_str())
1765 .ok_or_else(|| anyhow::anyhow!("Missing pattern parameter"))?;
1766
1767 let stats = server.content_search().get_stats();
1769 if stats.total_files == 0 {
1770 if let Some(repo_path) = server.repository_path() {
1772 match server.scanner().discover_files(repo_path) {
1773 Ok(all_files) => {
1774 let pattern_regex = match regex::Regex::new(pattern) {
1775 Ok(regex) => regex,
1776 Err(_) => {
1777 let glob_pattern = pattern.replace("*", ".*").replace("?", ".");
1779 match regex::Regex::new(&glob_pattern) {
1780 Ok(regex) => regex,
1781 Err(e) => {
1782 return Ok(CallToolResult {
1783 content: vec![ToolContent::Text {
1784 text: format!(
1785 "Invalid pattern '{}': {}",
1786 pattern, e
1787 ),
1788 }],
1789 is_error: Some(true),
1790 });
1791 }
1792 }
1793 }
1794 };
1795
1796 let matching_files: Vec<_> = all_files
1797 .iter()
1798 .filter(|path| pattern_regex.is_match(&path.to_string_lossy()))
1799 .collect();
1800
1801 let result = serde_json::json!({
1802 "pattern": pattern,
1803 "total_files": matching_files.len(),
1804 "source": "repository_scan",
1805 "files": matching_files.iter().map(|path| {
1806 serde_json::json!({
1807 "path": path.display().to_string(),
1808 "name": path.file_name()
1809 .and_then(|n| n.to_str())
1810 .unwrap_or(""),
1811 "extension": path.extension()
1812 .and_then(|ext| ext.to_str())
1813 .unwrap_or("")
1814 })
1815 }).collect::<Vec<_>>()
1816 });
1817
1818 return Ok(CallToolResult {
1819 content: vec![ToolContent::Text {
1820 text: serde_json::to_string_pretty(&result)?,
1821 }],
1822 is_error: Some(false),
1823 });
1824 }
1825 Err(e) => {
1826 return Ok(CallToolResult {
1827 content: vec![ToolContent::Text {
1828 text: format!("Failed to scan repository for files: {}", e),
1829 }],
1830 is_error: Some(true),
1831 });
1832 }
1833 }
1834 } else {
1835 let result = serde_json::json!({
1836 "pattern": pattern,
1837 "total_files": 0,
1838 "source": "no_repository",
1839 "files": [],
1840 "message": "No repository is currently loaded"
1841 });
1842
1843 return Ok(CallToolResult {
1844 content: vec![ToolContent::Text {
1845 text: serde_json::to_string_pretty(&result)?,
1846 }],
1847 is_error: Some(false),
1848 });
1849 }
1850 }
1851
1852 match server.content_search().find_files(pattern) {
1853 Ok(files) => {
1854 let result = serde_json::json!({
1855 "pattern": pattern,
1856 "total_files": files.len(),
1857 "source": "content_index",
1858 "files": files.iter().map(|path| {
1859 serde_json::json!({
1860 "path": path.display().to_string(),
1861 "name": path.file_name()
1862 .and_then(|n| n.to_str())
1863 .unwrap_or(""),
1864 "extension": path.extension()
1865 .and_then(|ext| ext.to_str())
1866 .unwrap_or("")
1867 })
1868 }).collect::<Vec<_>>()
1869 });
1870
1871 Ok(CallToolResult {
1872 content: vec![ToolContent::Text {
1873 text: serde_json::to_string_pretty(&result)?,
1874 }],
1875 is_error: Some(false),
1876 })
1877 }
1878 Err(e) => Ok(CallToolResult {
1879 content: vec![ToolContent::Text {
1880 text: format!("File search error: {}", e),
1881 }],
1882 is_error: Some(true),
1883 }),
1884 }
1885 }
1886
1887 async fn content_stats(&self, server: &CodePrismMcpServer) -> Result<CallToolResult> {
1889 let stats = server.content_search().get_stats();
1890
1891 let result = if stats.total_files == 0 {
1892 serde_json::json!({
1893 "total_files": 0,
1894 "total_chunks": 0,
1895 "total_tokens": 0,
1896 "content_by_type": {},
1897 "size_distribution": {},
1898 "status": "no_content_indexed",
1899 "message": "Content indexing has not been performed yet. Only code symbol analysis is available.",
1900 "suggestion": "Content indexing for documentation, configuration files, and comments may still be in progress."
1901 })
1902 } else {
1903 serde_json::json!({
1904 "total_files": stats.total_files,
1905 "total_chunks": stats.total_chunks,
1906 "total_tokens": stats.total_tokens,
1907 "content_by_type": stats.content_by_type,
1908 "size_distribution": stats.size_distribution,
1909 "computed_at": stats.computed_at.duration_since(std::time::UNIX_EPOCH)
1910 .unwrap_or_default()
1911 .as_secs(),
1912 "status": "indexed"
1913 })
1914 };
1915
1916 Ok(CallToolResult {
1917 content: vec![ToolContent::Text {
1918 text: serde_json::to_string_pretty(&result)?,
1919 }],
1920 is_error: Some(false),
1921 })
1922 }
1923
1924 async fn analyze_complexity(
1926 &self,
1927 server: &CodePrismMcpServer,
1928 arguments: Option<Value>,
1929 ) -> Result<CallToolResult> {
1930 let args = arguments.unwrap_or_default();
1931
1932 let target = match args.get("target").and_then(|v| v.as_str()) {
1933 Some(t) => t,
1934 None => {
1935 return Ok(CallToolResult {
1936 content: vec![ToolContent::Text {
1937 text: "Missing required parameter: target".to_string(),
1938 }],
1939 is_error: Some(true),
1940 });
1941 }
1942 };
1943
1944 let metrics = args
1945 .get("metrics")
1946 .and_then(|v| v.as_array())
1947 .map(|arr| {
1948 arr.iter()
1949 .filter_map(|v| v.as_str())
1950 .map(|s| s.to_string())
1951 .collect::<Vec<_>>()
1952 })
1953 .unwrap_or_else(|| vec!["all".to_string()]);
1954
1955 let threshold_warnings = args
1956 .get("threshold_warnings")
1957 .and_then(|v| v.as_bool())
1958 .unwrap_or(true);
1959
1960 let mut complexity_results = Vec::new();
1962
1963 if target.starts_with('/') || target.contains('.') {
1964 if let Some(repo_path) = server.repository_path() {
1966 let file_path = if std::path::Path::new(target).is_absolute() {
1967 std::path::PathBuf::from(target)
1968 } else {
1969 repo_path.join(target)
1970 };
1971
1972 if file_path.exists() {
1973 let file_complexity =
1974 self.analyze_file_complexity(&file_path, &metrics, threshold_warnings)?;
1975 complexity_results.push(file_complexity);
1976 } else {
1977 return Ok(CallToolResult {
1978 content: vec![ToolContent::Text {
1979 text: format!("File not found: {}", target),
1980 }],
1981 is_error: Some(true),
1982 });
1983 }
1984 }
1985 } else {
1986 if let Ok(symbol_id) = self.parse_node_id(target) {
1988 if let Some(node) = server.graph_store().get_node(&symbol_id) {
1989 let symbol_complexity =
1990 self.analyze_symbol_complexity(&node, &metrics, threshold_warnings)?;
1991 complexity_results.push(symbol_complexity);
1992 } else {
1993 return Ok(CallToolResult {
1994 content: vec![ToolContent::Text {
1995 text: format!("Symbol not found: {}", target),
1996 }],
1997 is_error: Some(true),
1998 });
1999 }
2000 } else {
2001 return Ok(CallToolResult {
2002 content: vec![ToolContent::Text {
2003 text: format!("Invalid target format: {}", target),
2004 }],
2005 is_error: Some(true),
2006 });
2007 }
2008 }
2009
2010 let result = serde_json::json!({
2011 "target": target,
2012 "metrics_requested": metrics,
2013 "threshold_warnings": threshold_warnings,
2014 "results": complexity_results,
2015 "summary": {
2016 "total_analyzed": complexity_results.len(),
2017 "high_complexity_items": complexity_results.iter()
2018 .filter(|r| r.get("warnings").and_then(|w| w.as_array()).map(|arr| !arr.is_empty()).unwrap_or(false))
2019 .count()
2020 }
2021 });
2022
2023 Ok(CallToolResult {
2024 content: vec![ToolContent::Text {
2025 text: serde_json::to_string_pretty(&result)?,
2026 }],
2027 is_error: Some(false),
2028 })
2029 }
2030
2031 fn analyze_file_complexity(
2033 &self,
2034 file_path: &std::path::Path,
2035 metrics: &[String],
2036 threshold_warnings: bool,
2037 ) -> Result<serde_json::Value> {
2038 let content = std::fs::read_to_string(file_path)
2040 .map_err(|e| anyhow::anyhow!("Failed to read file {}: {}", file_path.display(), e))?;
2041
2042 let lines = content.lines().collect::<Vec<_>>();
2043 let total_lines = lines.len();
2044
2045 let mut complexity_metrics = serde_json::json!({
2047 "file": file_path.display().to_string(),
2048 "total_lines": total_lines,
2049 "non_empty_lines": lines.iter().filter(|line| !line.trim().is_empty()).count(),
2050 "metrics": {}
2051 });
2052
2053 let mut warnings = Vec::new();
2054
2055 let _include_all = metrics.contains(&"all".to_string());
2057 for metric in metrics {
2058 match metric.as_str() {
2059 "cyclomatic" => {
2060 let cyclomatic = self.calculate_cyclomatic_complexity(&content);
2061 complexity_metrics["metrics"]["cyclomatic_complexity"] = serde_json::json!({
2062 "value": cyclomatic,
2063 "description": "Number of linearly independent paths through the code"
2064 });
2065
2066 if threshold_warnings && cyclomatic > 10 {
2067 warnings.push(format!(
2068 "High cyclomatic complexity: {} (threshold: 10)",
2069 cyclomatic
2070 ));
2071 }
2072 }
2073 "cognitive" => {
2074 let cognitive = self.calculate_cognitive_complexity(&content);
2075 complexity_metrics["metrics"]["cognitive_complexity"] = serde_json::json!({
2076 "value": cognitive,
2077 "description": "Measure of how hard the code is to understand"
2078 });
2079
2080 if threshold_warnings && cognitive > 15 {
2081 warnings.push(format!(
2082 "High cognitive complexity: {} (threshold: 15)",
2083 cognitive
2084 ));
2085 }
2086 }
2087 "halstead" => {
2088 let (volume, difficulty, effort) = self.calculate_halstead_metrics(&content);
2089 complexity_metrics["metrics"]["halstead"] = serde_json::json!({
2090 "volume": volume,
2091 "difficulty": difficulty,
2092 "effort": effort,
2093 "description": "Halstead complexity metrics based on operators and operands"
2094 });
2095 }
2096 "maintainability_index" => {
2097 let mi = self.calculate_maintainability_index(&content, total_lines);
2098 complexity_metrics["metrics"]["maintainability_index"] = serde_json::json!({
2099 "value": mi,
2100 "description": "Maintainability index (0-100, higher is better)"
2101 });
2102
2103 if threshold_warnings && mi < 20.0 {
2104 warnings.push(format!(
2105 "Low maintainability index: {:.1} (threshold: 20)",
2106 mi
2107 ));
2108 }
2109 }
2110 "all" => {
2111 let cyclomatic = self.calculate_cyclomatic_complexity(&content);
2113 complexity_metrics["metrics"]["cyclomatic_complexity"] = serde_json::json!({
2114 "value": cyclomatic,
2115 "description": "Number of linearly independent paths through the code"
2116 });
2117 if threshold_warnings && cyclomatic > 10 {
2118 warnings.push(format!(
2119 "High cyclomatic complexity: {} (threshold: 10)",
2120 cyclomatic
2121 ));
2122 }
2123
2124 let cognitive = self.calculate_cognitive_complexity(&content);
2125 complexity_metrics["metrics"]["cognitive_complexity"] = serde_json::json!({
2126 "value": cognitive,
2127 "description": "Measure of how hard the code is to understand"
2128 });
2129 if threshold_warnings && cognitive > 15 {
2130 warnings.push(format!(
2131 "High cognitive complexity: {} (threshold: 15)",
2132 cognitive
2133 ));
2134 }
2135
2136 let (volume, difficulty, effort) = self.calculate_halstead_metrics(&content);
2137 complexity_metrics["metrics"]["halstead"] = serde_json::json!({
2138 "volume": volume,
2139 "difficulty": difficulty,
2140 "effort": effort,
2141 "description": "Halstead complexity metrics based on operators and operands"
2142 });
2143
2144 let mi = self.calculate_maintainability_index(&content, total_lines);
2145 complexity_metrics["metrics"]["maintainability_index"] = serde_json::json!({
2146 "value": mi,
2147 "description": "Maintainability index (0-100, higher is better)"
2148 });
2149 if threshold_warnings && mi < 20.0 {
2150 warnings.push(format!(
2151 "Low maintainability index: {:.1} (threshold: 20)",
2152 mi
2153 ));
2154 }
2155 }
2156 _ => {
2157 }
2159 }
2160 }
2161
2162 if !warnings.is_empty() {
2163 complexity_metrics["warnings"] = serde_json::json!(warnings);
2164 }
2165
2166 Ok(complexity_metrics)
2167 }
2168
2169 fn analyze_symbol_complexity(
2171 &self,
2172 node: &codeprism_core::Node,
2173 metrics: &[String],
2174 threshold_warnings: bool,
2175 ) -> Result<serde_json::Value> {
2176 let content = std::fs::read_to_string(&node.file)
2178 .map_err(|e| anyhow::anyhow!("Failed to read file {}: {}", node.file.display(), e))?;
2179
2180 let lines = content.lines().collect::<Vec<_>>();
2182 let symbol_content = if node.span.start_line <= lines.len()
2183 && node.span.end_line <= lines.len()
2184 {
2185 lines[(node.span.start_line - 1).max(0)..node.span.end_line.min(lines.len())].join("\n")
2186 } else {
2187 content.clone()
2188 };
2189
2190 let symbol_lines = node.span.end_line - node.span.start_line + 1;
2191
2192 let mut complexity_metrics = serde_json::json!({
2193 "symbol": {
2194 "id": node.id.to_hex(),
2195 "name": node.name,
2196 "kind": format!("{:?}", node.kind),
2197 "file": node.file.display().to_string(),
2198 "span": {
2199 "start_line": node.span.start_line,
2200 "end_line": node.span.end_line,
2201 "lines": symbol_lines
2202 }
2203 },
2204 "metrics": {}
2205 });
2206
2207 let mut warnings = Vec::new();
2208
2209 for metric in metrics {
2211 match metric.as_str() {
2212 "cyclomatic" => {
2213 let cyclomatic = self.calculate_cyclomatic_complexity(&symbol_content);
2214 complexity_metrics["metrics"]["cyclomatic_complexity"] = serde_json::json!({
2215 "value": cyclomatic,
2216 "description": "Number of linearly independent paths through the symbol"
2217 });
2218
2219 if threshold_warnings && cyclomatic > 10 {
2220 warnings.push(format!(
2221 "High cyclomatic complexity: {} (threshold: 10)",
2222 cyclomatic
2223 ));
2224 }
2225 }
2226 "cognitive" => {
2227 let cognitive = self.calculate_cognitive_complexity(&symbol_content);
2228 complexity_metrics["metrics"]["cognitive_complexity"] = serde_json::json!({
2229 "value": cognitive,
2230 "description": "Measure of how hard the symbol is to understand"
2231 });
2232
2233 if threshold_warnings && cognitive > 15 {
2234 warnings.push(format!(
2235 "High cognitive complexity: {} (threshold: 15)",
2236 cognitive
2237 ));
2238 }
2239 }
2240 "halstead" => {
2241 let (volume, difficulty, effort) =
2242 self.calculate_halstead_metrics(&symbol_content);
2243 complexity_metrics["metrics"]["halstead"] = serde_json::json!({
2244 "volume": volume,
2245 "difficulty": difficulty,
2246 "effort": effort,
2247 "description": "Halstead complexity metrics for the symbol"
2248 });
2249 }
2250 "maintainability_index" => {
2251 let mi = self.calculate_maintainability_index(&symbol_content, symbol_lines);
2252 complexity_metrics["metrics"]["maintainability_index"] = serde_json::json!({
2253 "value": mi,
2254 "description": "Maintainability index for the symbol (0-100, higher is better)"
2255 });
2256
2257 if threshold_warnings && mi < 20.0 {
2258 warnings.push(format!(
2259 "Low maintainability index: {:.1} (threshold: 20)",
2260 mi
2261 ));
2262 }
2263 }
2264 "all" => {
2265 let cyclomatic = self.calculate_cyclomatic_complexity(&symbol_content);
2267 complexity_metrics["metrics"]["cyclomatic_complexity"] = serde_json::json!({
2268 "value": cyclomatic,
2269 "description": "Number of linearly independent paths through the symbol"
2270 });
2271 if threshold_warnings && cyclomatic > 10 {
2272 warnings.push(format!(
2273 "High cyclomatic complexity: {} (threshold: 10)",
2274 cyclomatic
2275 ));
2276 }
2277
2278 let cognitive = self.calculate_cognitive_complexity(&symbol_content);
2279 complexity_metrics["metrics"]["cognitive_complexity"] = serde_json::json!({
2280 "value": cognitive,
2281 "description": "Measure of how hard the symbol is to understand"
2282 });
2283 if threshold_warnings && cognitive > 15 {
2284 warnings.push(format!(
2285 "High cognitive complexity: {} (threshold: 15)",
2286 cognitive
2287 ));
2288 }
2289
2290 let (volume, difficulty, effort) =
2291 self.calculate_halstead_metrics(&symbol_content);
2292 complexity_metrics["metrics"]["halstead"] = serde_json::json!({
2293 "volume": volume,
2294 "difficulty": difficulty,
2295 "effort": effort,
2296 "description": "Halstead complexity metrics for the symbol"
2297 });
2298
2299 let mi = self.calculate_maintainability_index(&symbol_content, symbol_lines);
2300 complexity_metrics["metrics"]["maintainability_index"] = serde_json::json!({
2301 "value": mi,
2302 "description": "Maintainability index for the symbol (0-100, higher is better)"
2303 });
2304 if threshold_warnings && mi < 20.0 {
2305 warnings.push(format!(
2306 "Low maintainability index: {:.1} (threshold: 20)",
2307 mi
2308 ));
2309 }
2310 }
2311 _ => {
2312 }
2314 }
2315 }
2316
2317 if !warnings.is_empty() {
2318 complexity_metrics["warnings"] = serde_json::json!(warnings);
2319 }
2320
2321 Ok(complexity_metrics)
2322 }
2323
2324 fn calculate_cyclomatic_complexity(&self, content: &str) -> usize {
2326 let mut complexity = 1; let decision_keywords = [
2330 "if", "else if", "elif", "while", "for", "foreach", "switch", "case", "catch",
2331 "except", "?", "&&", "||", "and", "or",
2332 ];
2333
2334 for keyword in &decision_keywords {
2335 complexity += content.matches(keyword).count();
2336 }
2337
2338 complexity
2339 }
2340
2341 fn calculate_cognitive_complexity(&self, content: &str) -> usize {
2343 let mut complexity = 0;
2344 let mut nesting_level: usize = 0;
2345
2346 let lines = content.lines();
2347 for line in lines {
2348 let trimmed = line.trim();
2349
2350 if trimmed.contains('{')
2352 || trimmed.starts_with("if ")
2353 || trimmed.starts_with("for ")
2354 || trimmed.starts_with("while ")
2355 || trimmed.starts_with("try ")
2356 || trimmed.starts_with("def ")
2357 || trimmed.starts_with("function ")
2358 {
2359 nesting_level += 1;
2360 }
2361
2362 if trimmed.contains('}') {
2364 nesting_level = nesting_level.saturating_sub(1usize);
2365 }
2366
2367 if trimmed.contains("if ") || trimmed.contains("elif ") || trimmed.contains("else if") {
2369 complexity += 1 + nesting_level;
2370 }
2371 if trimmed.contains("while ") || trimmed.contains("for ") {
2372 complexity += 1 + nesting_level;
2373 }
2374 if trimmed.contains("catch ") || trimmed.contains("except ") {
2375 complexity += 1 + nesting_level;
2376 }
2377 }
2378
2379 complexity
2380 }
2381
2382 fn calculate_halstead_metrics(&self, content: &str) -> (f64, f64, f64) {
2384 let operators = [
2386 "=", "+", "-", "*", "/", "==", "!=", "<", ">", "<=", ">=", "&&", "||",
2387 ];
2388 let mut unique_operators = std::collections::HashSet::new();
2389 let mut total_operators = 0;
2390
2391 for op in &operators {
2392 let count = content.matches(op).count();
2393 if count > 0 {
2394 unique_operators.insert(op);
2395 total_operators += count;
2396 }
2397 }
2398
2399 let words: Vec<&str> = content.split_whitespace().collect();
2401 let mut unique_operands = std::collections::HashSet::new();
2402 let mut total_operands = 0;
2403
2404 for word in words {
2405 if word.chars().any(|c| c.is_alphanumeric()) {
2406 unique_operands.insert(word);
2407 total_operands += 1;
2408 }
2409 }
2410
2411 let n1 = unique_operators.len().max(1) as f64; let n2 = unique_operands.len().max(1) as f64; let big_n1 = total_operators.max(1) as f64; let big_n2 = total_operands.max(1) as f64; let vocabulary = n1 + n2;
2417 let length = big_n1 + big_n2;
2418
2419 let safe_vocabulary = vocabulary.max(2.0);
2421 let volume = length * safe_vocabulary.log2();
2422
2423 let difficulty = (n1 / 2.0) * (big_n2 / n2);
2425 let effort = difficulty * volume;
2426
2427 (volume, difficulty, effort)
2428 }
2429
2430 fn calculate_maintainability_index(&self, content: &str, lines_count: usize) -> f64 {
2432 let (volume, difficulty, _effort) = self.calculate_halstead_metrics(content);
2433 let cyclomatic = self.calculate_cyclomatic_complexity(content) as f64;
2434 let loc = lines_count.max(1) as f64; let safe_volume = volume.max(1.0);
2438 let safe_loc = loc.max(1.0);
2439
2440 let volume_penalty = safe_volume.ln() * 8.0; let complexity_penalty = cyclomatic * 5.0; let loc_penalty = safe_loc.ln() * 20.0; let difficulty_penalty = difficulty * 2.0; let mi = 171.0 - volume_penalty - complexity_penalty - loc_penalty - difficulty_penalty;
2449
2450 mi.clamp(0.0, 100.0)
2452 }
2453
2454 fn calculate_content_similarity(&self, content1: &str, content2: &str) -> f64 {
2456 let lines1: Vec<String> = content1
2457 .lines()
2458 .map(|s| s.trim().to_string())
2459 .filter(|s| !s.is_empty())
2460 .collect();
2461 let lines2: Vec<String> = content2
2462 .lines()
2463 .map(|s| s.trim().to_string())
2464 .filter(|s| !s.is_empty())
2465 .collect();
2466
2467 if lines1.is_empty() || lines2.is_empty() {
2468 return 0.0;
2469 }
2470
2471 let set1: std::collections::HashSet<String> = lines1.into_iter().collect();
2473 let set2: std::collections::HashSet<String> = lines2.into_iter().collect();
2474
2475 if set1.is_empty() && set2.is_empty() {
2476 return 1.0;
2477 }
2478
2479 let intersection = set1.intersection(&set2).count();
2480 let union = set1.union(&set2).count();
2481
2482 if union == 0 {
2483 0.0
2484 } else {
2485 intersection as f64 / union as f64
2486 }
2487 }
2488
2489 async fn detect_patterns(
2491 &self,
2492 server: &CodePrismMcpServer,
2493 arguments: Option<Value>,
2494 ) -> Result<CallToolResult> {
2495 let args = arguments.unwrap_or_default();
2496
2497 let scope = args
2498 .get("scope")
2499 .and_then(|v| v.as_str())
2500 .unwrap_or("repository");
2501
2502 let pattern_types: Vec<String> = args
2503 .get("pattern_types")
2504 .and_then(|v| v.as_array())
2505 .map(|arr| {
2506 arr.iter()
2507 .filter_map(|v| v.as_str())
2508 .map(|s| s.to_string())
2509 .collect()
2510 })
2511 .unwrap_or_else(|| vec!["all".to_string()]);
2512
2513 let confidence_threshold = args
2514 .get("confidence_threshold")
2515 .and_then(|v| v.as_f64())
2516 .unwrap_or(0.8);
2517
2518 let include_suggestions = args
2519 .get("include_suggestions")
2520 .and_then(|v| v.as_bool())
2521 .unwrap_or(true);
2522
2523 let result = if let Some(_repo_path) = server.repository_path() {
2524 let detected_patterns = self
2525 .analyze_design_patterns(
2526 server,
2527 &pattern_types,
2528 confidence_threshold,
2529 include_suggestions,
2530 )
2531 .await?;
2532
2533 serde_json::json!({
2534 "scope": scope,
2535 "patterns": detected_patterns,
2536 "summary": {
2537 "total_patterns_detected": detected_patterns.len(),
2538 "confidence_threshold": confidence_threshold,
2539 "pattern_types_analyzed": pattern_types
2540 },
2541 "analysis_successful": true
2542 })
2543 } else {
2544 serde_json::json!({
2545 "error": "No repository initialized",
2546 "analysis_successful": false
2547 })
2548 };
2549
2550 Ok(CallToolResult {
2551 content: vec![ToolContent::Text {
2552 text: serde_json::to_string_pretty(&result)?,
2553 }],
2554 is_error: Some(false),
2555 })
2556 }
2557
2558 async fn analyze_transitive_dependencies(
2560 &self,
2561 server: &CodePrismMcpServer,
2562 arguments: Option<Value>,
2563 ) -> Result<CallToolResult> {
2564 let args = arguments.ok_or_else(|| anyhow::anyhow!("Missing arguments"))?;
2565
2566 let target = args
2567 .get("target")
2568 .and_then(|v| v.as_str())
2569 .ok_or_else(|| anyhow::anyhow!("Missing target parameter"))?;
2570
2571 let max_depth = args
2572 .get("max_depth")
2573 .and_then(|v| v.as_u64())
2574 .map(|v| v as usize)
2575 .unwrap_or(5);
2576
2577 let detect_cycles = args
2578 .get("detect_cycles")
2579 .and_then(|v| v.as_bool())
2580 .unwrap_or(true);
2581
2582 let include_external = args
2583 .get("include_external_dependencies")
2584 .and_then(|v| v.as_bool())
2585 .unwrap_or(false);
2586
2587 let dependency_types: Vec<String> = args
2588 .get("dependency_types")
2589 .and_then(|v| v.as_array())
2590 .map(|arr| {
2591 arr.iter()
2592 .filter_map(|v| v.as_str())
2593 .map(|s| s.to_string())
2594 .collect()
2595 })
2596 .unwrap_or_else(|| vec!["all".to_string()]);
2597
2598 let result = if let Some(_repo_path) = server.repository_path() {
2599 let analysis = self
2600 .perform_transitive_analysis(
2601 server,
2602 target,
2603 max_depth,
2604 detect_cycles,
2605 include_external,
2606 &dependency_types,
2607 )
2608 .await?;
2609
2610 serde_json::json!({
2611 "target": target,
2612 "analysis": analysis,
2613 "parameters": {
2614 "max_depth": max_depth,
2615 "detect_cycles": detect_cycles,
2616 "include_external": include_external,
2617 "dependency_types": dependency_types
2618 },
2619 "analysis_successful": true
2620 })
2621 } else {
2622 serde_json::json!({
2623 "error": "No repository initialized",
2624 "analysis_successful": false
2625 })
2626 };
2627
2628 Ok(CallToolResult {
2629 content: vec![ToolContent::Text {
2630 text: serde_json::to_string_pretty(&result)?,
2631 }],
2632 is_error: Some(false),
2633 })
2634 }
2635
2636 async fn trace_data_flow(
2638 &self,
2639 server: &CodePrismMcpServer,
2640 arguments: Option<Value>,
2641 ) -> Result<CallToolResult> {
2642 let args = arguments.ok_or_else(|| anyhow::anyhow!("Missing arguments"))?;
2643
2644 let variable_or_parameter = args
2645 .get("variable_or_parameter")
2646 .and_then(|v| v.as_str())
2647 .ok_or_else(|| anyhow::anyhow!("Missing variable_or_parameter parameter"))?;
2648
2649 let direction = args
2650 .get("direction")
2651 .and_then(|v| v.as_str())
2652 .unwrap_or("forward");
2653
2654 let include_transformations = args
2655 .get("include_transformations")
2656 .and_then(|v| v.as_bool())
2657 .unwrap_or(true);
2658
2659 let max_depth = args
2660 .get("max_depth")
2661 .and_then(|v| v.as_u64())
2662 .map(|v| v as usize)
2663 .unwrap_or(10);
2664
2665 let follow_function_calls = args
2666 .get("follow_function_calls")
2667 .and_then(|v| v.as_bool())
2668 .unwrap_or(true);
2669
2670 let include_field_access = args
2671 .get("include_field_access")
2672 .and_then(|v| v.as_bool())
2673 .unwrap_or(true);
2674
2675 let symbol_id = self.parse_node_id(variable_or_parameter)?;
2676
2677 let data_flow_result = self
2678 .perform_data_flow_analysis(
2679 server,
2680 &symbol_id,
2681 direction,
2682 include_transformations,
2683 max_depth,
2684 follow_function_calls,
2685 include_field_access,
2686 )
2687 .await?;
2688
2689 Ok(CallToolResult {
2690 content: vec![ToolContent::Text {
2691 text: serde_json::to_string_pretty(&data_flow_result)?,
2692 }],
2693 is_error: Some(false),
2694 })
2695 }
2696
2697 async fn trace_inheritance(
2699 &self,
2700 server: &CodePrismMcpServer,
2701 arguments: Option<Value>,
2702 ) -> Result<CallToolResult> {
2703 let args = arguments.ok_or_else(|| anyhow::anyhow!("Missing arguments"))?;
2704
2705 let target_classes =
2707 if let Some(class_name) = args.get("class_name").and_then(|v| v.as_str()) {
2708 let symbol_types = Some(vec![codeprism_core::NodeKind::Class]);
2710 let limit = Some(10);
2711 let search_results =
2712 server
2713 .graph_query()
2714 .search_symbols(class_name, symbol_types, limit)?;
2715
2716 if search_results.is_empty() {
2717 return Ok(CallToolResult {
2718 content: vec![ToolContent::Text {
2719 text: format!("No classes found matching pattern: {}", class_name),
2720 }],
2721 is_error: Some(true),
2722 });
2723 }
2724
2725 search_results
2727 .into_iter()
2728 .filter_map(|symbol| server.graph_store().get_node(&symbol.node.id))
2729 .filter(|node| matches!(node.kind, codeprism_core::NodeKind::Class))
2730 .collect::<Vec<_>>()
2731 } else if let Some(class_id_str) = args.get("class_id").and_then(|v| v.as_str()) {
2732 let class_id = self.parse_node_id(class_id_str)?;
2734 if let Some(node) = server.graph_store().get_node(&class_id) {
2735 if matches!(node.kind, codeprism_core::NodeKind::Class) {
2736 vec![node]
2737 } else {
2738 return Ok(CallToolResult {
2739 content: vec![ToolContent::Text {
2740 text: format!("Node {} is not a class", class_id_str),
2741 }],
2742 is_error: Some(true),
2743 });
2744 }
2745 } else {
2746 return Ok(CallToolResult {
2747 content: vec![ToolContent::Text {
2748 text: format!("Class not found: {}", class_id_str),
2749 }],
2750 is_error: Some(true),
2751 });
2752 }
2753 } else {
2754 return Ok(CallToolResult {
2755 content: vec![ToolContent::Text {
2756 text: "Either class_name or class_id parameter is required".to_string(),
2757 }],
2758 is_error: Some(true),
2759 });
2760 };
2761
2762 let direction = args
2764 .get("direction")
2765 .and_then(|v| v.as_str())
2766 .unwrap_or("both");
2767
2768 let include_metaclasses = args
2769 .get("include_metaclasses")
2770 .and_then(|v| v.as_bool())
2771 .unwrap_or(true);
2772
2773 let include_mixins = args
2774 .get("include_mixins")
2775 .and_then(|v| v.as_bool())
2776 .unwrap_or(true);
2777
2778 let include_mro = args
2779 .get("include_mro")
2780 .and_then(|v| v.as_bool())
2781 .unwrap_or(true);
2782
2783 let include_dynamic_attributes = args
2784 .get("include_dynamic_attributes")
2785 .and_then(|v| v.as_bool())
2786 .unwrap_or(true);
2787
2788 let max_depth = args
2789 .get("max_depth")
2790 .and_then(|v| v.as_u64())
2791 .map(|v| v as usize)
2792 .unwrap_or(10);
2793
2794 let include_source_context = args
2795 .get("include_source_context")
2796 .and_then(|v| v.as_bool())
2797 .unwrap_or(false);
2798
2799 let mut analysis_results = Vec::new();
2801
2802 for target_class in &target_classes {
2803 let inheritance_info = server
2804 .graph_query()
2805 .get_inheritance_info(&target_class.id)?;
2806
2807 let inheritance_tree = self
2809 .build_inheritance_tree(
2810 server,
2811 &target_class.id,
2812 direction,
2813 max_depth,
2814 include_source_context,
2815 )
2816 .await?;
2817
2818 let metaclass_analysis = if include_metaclasses && inheritance_info.metaclass.is_some()
2820 {
2821 Some(
2822 self.analyze_metaclass_impact(server, &inheritance_info)
2823 .await?,
2824 )
2825 } else {
2826 None
2827 };
2828
2829 let mixin_analysis = if include_mixins && !inheritance_info.mixins.is_empty() {
2831 Some(
2832 self.analyze_mixin_relationships(server, &inheritance_info)
2833 .await?,
2834 )
2835 } else {
2836 None
2837 };
2838
2839 let mro_analysis =
2841 if include_mro && !inheritance_info.method_resolution_order.is_empty() {
2842 Some(
2843 self.analyze_method_resolution_order(server, &inheritance_info)
2844 .await?,
2845 )
2846 } else {
2847 None
2848 };
2849
2850 let dynamic_attributes_analysis =
2852 if include_dynamic_attributes && !inheritance_info.dynamic_attributes.is_empty() {
2853 Some(
2854 self.analyze_dynamic_attributes(server, &inheritance_info)
2855 .await?,
2856 )
2857 } else {
2858 None
2859 };
2860
2861 let diamond_inheritance = self
2863 .detect_diamond_inheritance(server, &target_class.id)
2864 .await?;
2865
2866 let mut analysis = serde_json::json!({
2867 "target_class": {
2868 "id": target_class.id.to_hex(),
2869 "name": target_class.name,
2870 "file": target_class.file.display().to_string(),
2871 "span": {
2872 "start_line": target_class.span.start_line,
2873 "end_line": target_class.span.end_line,
2874 "start_column": target_class.span.start_column,
2875 "end_column": target_class.span.end_column
2876 }
2877 },
2878 "inheritance_tree": inheritance_tree,
2879 "diamond_inheritance": diamond_inheritance,
2880 "basic_inheritance_info": {
2881 "is_metaclass": inheritance_info.is_metaclass,
2882 "base_classes_count": inheritance_info.base_classes.len(),
2883 "subclasses_count": inheritance_info.subclasses.len(),
2884 "inheritance_depth": inheritance_info.inheritance_chain.len() - 1
2885 }
2886 });
2887
2888 if let Some(metaclass) = metaclass_analysis {
2890 analysis["metaclass_analysis"] = metaclass;
2891 }
2892
2893 if let Some(mixins) = mixin_analysis {
2894 analysis["mixin_analysis"] = mixins;
2895 }
2896
2897 if let Some(mro) = mro_analysis {
2898 analysis["method_resolution_order"] = mro;
2899 }
2900
2901 if let Some(dynamic_attrs) = dynamic_attributes_analysis {
2902 analysis["dynamic_attributes_analysis"] = dynamic_attrs;
2903 }
2904
2905 analysis_results.push(analysis);
2906 }
2907
2908 let result = serde_json::json!({
2909 "analysis_results": analysis_results,
2910 "summary": {
2911 "classes_analyzed": target_classes.len(),
2912 "direction": direction,
2913 "max_depth": max_depth,
2914 "options": {
2915 "include_metaclasses": include_metaclasses,
2916 "include_mixins": include_mixins,
2917 "include_mro": include_mro,
2918 "include_dynamic_attributes": include_dynamic_attributes,
2919 "include_source_context": include_source_context
2920 }
2921 }
2922 });
2923
2924 Ok(CallToolResult {
2925 content: vec![ToolContent::Text {
2926 text: serde_json::to_string_pretty(&result)?,
2927 }],
2928 is_error: Some(false),
2929 })
2930 }
2931
2932 async fn build_inheritance_tree(
2934 &self,
2935 server: &CodePrismMcpServer,
2936 class_id: &codeprism_core::NodeId,
2937 direction: &str,
2938 max_depth: usize,
2939 include_source_context: bool,
2940 ) -> Result<serde_json::Value> {
2941 let mut tree = serde_json::Map::new();
2942 let mut visited = std::collections::HashSet::new();
2943
2944 self.build_tree_recursive(
2946 server,
2947 class_id,
2948 &mut tree,
2949 &mut visited,
2950 direction,
2951 0,
2952 max_depth,
2953 include_source_context,
2954 )
2955 .await?;
2956
2957 Ok(serde_json::Value::Object(tree))
2958 }
2959
2960 fn build_tree_recursive<'a>(
2962 &'a self,
2963 server: &'a CodePrismMcpServer,
2964 class_id: &'a codeprism_core::NodeId,
2965 tree: &'a mut serde_json::Map<String, serde_json::Value>,
2966 visited: &'a mut std::collections::HashSet<codeprism_core::NodeId>,
2967 direction: &'a str,
2968 current_depth: usize,
2969 max_depth: usize,
2970 include_source_context: bool,
2971 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + 'a>> {
2972 Box::pin(async move {
2973 if current_depth >= max_depth || visited.contains(class_id) {
2974 return Ok(());
2975 }
2976
2977 visited.insert(*class_id);
2978
2979 if let Some(class_node) = server.graph_store().get_node(class_id) {
2980 if let Ok(inheritance_info) = server.graph_query().get_inheritance_info(class_id) {
2981 let mut class_data = serde_json::Map::new();
2982
2983 class_data.insert(
2985 "id".to_string(),
2986 serde_json::Value::String(class_id.to_hex()),
2987 );
2988 class_data.insert(
2989 "name".to_string(),
2990 serde_json::Value::String(class_node.name.clone()),
2991 );
2992 class_data.insert(
2993 "file".to_string(),
2994 serde_json::Value::String(class_node.file.display().to_string()),
2995 );
2996 class_data.insert(
2997 "is_metaclass".to_string(),
2998 serde_json::Value::Bool(inheritance_info.is_metaclass),
2999 );
3000
3001 if include_source_context {
3003 if let Some(context) = self.extract_source_context(
3004 &class_node.file,
3005 class_node.span.start_line,
3006 3,
3007 ) {
3008 class_data.insert("source_context".to_string(), context);
3009 }
3010 }
3011
3012 if let Some(metaclass) = &inheritance_info.metaclass {
3014 class_data.insert(
3015 "metaclass".to_string(),
3016 serde_json::json!({
3017 "name": metaclass.class_name,
3018 "file": metaclass.file.display().to_string()
3019 }),
3020 );
3021 }
3022
3023 if direction == "up" || direction == "both" {
3025 let mut parents = serde_json::Map::new();
3026 for base_class in &inheritance_info.base_classes {
3027 let base_classes = server
3029 .graph_store()
3030 .get_nodes_by_kind(codeprism_core::NodeKind::Class);
3031 if let Some(base_node) = base_classes
3032 .iter()
3033 .find(|node| node.name == base_class.class_name)
3034 {
3035 self.build_tree_recursive(
3036 server,
3037 &base_node.id,
3038 &mut parents,
3039 visited,
3040 direction,
3041 current_depth + 1,
3042 max_depth,
3043 include_source_context,
3044 )
3045 .await?;
3046 } else {
3047 parents.insert(
3049 base_class.class_name.clone(),
3050 serde_json::json!({
3051 "name": base_class.class_name,
3052 "external": true,
3053 "relationship_type": base_class.relationship_type
3054 }),
3055 );
3056 }
3057 }
3058 if !parents.is_empty() {
3059 class_data.insert(
3060 "parent_classes".to_string(),
3061 serde_json::Value::Object(parents),
3062 );
3063 }
3064 }
3065
3066 if direction == "down" || direction == "both" {
3068 let mut children = serde_json::Map::new();
3069 for subclass in &inheritance_info.subclasses {
3070 let subclasses = server
3072 .graph_store()
3073 .get_nodes_by_kind(codeprism_core::NodeKind::Class);
3074 if let Some(sub_node) = subclasses
3075 .iter()
3076 .find(|node| node.name == subclass.class_name)
3077 {
3078 self.build_tree_recursive(
3079 server,
3080 &sub_node.id,
3081 &mut children,
3082 visited,
3083 direction,
3084 current_depth + 1,
3085 max_depth,
3086 include_source_context,
3087 )
3088 .await?;
3089 }
3090 }
3091 if !children.is_empty() {
3092 class_data.insert(
3093 "child_classes".to_string(),
3094 serde_json::Value::Object(children),
3095 );
3096 }
3097 }
3098
3099 if !inheritance_info.mixins.is_empty() {
3101 let mixins: Vec<_> = inheritance_info
3102 .mixins
3103 .iter()
3104 .map(|mixin| {
3105 serde_json::json!({
3106 "name": mixin.class_name,
3107 "file": mixin.file.display().to_string()
3108 })
3109 })
3110 .collect();
3111 class_data.insert("mixins".to_string(), serde_json::Value::Array(mixins));
3112 }
3113
3114 tree.insert(
3115 class_node.name.clone(),
3116 serde_json::Value::Object(class_data),
3117 );
3118 }
3119 }
3120
3121 Ok(())
3122 })
3123 }
3124
3125 async fn analyze_metaclass_impact(
3127 &self,
3128 server: &CodePrismMcpServer,
3129 inheritance_info: &codeprism_core::InheritanceInfo,
3130 ) -> Result<serde_json::Value> {
3131 if let Some(metaclass) = &inheritance_info.metaclass {
3132 let all_classes = server
3134 .graph_store()
3135 .get_nodes_by_kind(codeprism_core::NodeKind::Class);
3136 let mut affected_classes = Vec::new();
3137
3138 for class in all_classes {
3139 if let Ok(class_inheritance) = server.graph_query().get_inheritance_info(&class.id)
3140 {
3141 if let Some(class_metaclass) = &class_inheritance.metaclass {
3142 if class_metaclass.class_name == metaclass.class_name {
3143 affected_classes.push(serde_json::json!({
3144 "name": class.name,
3145 "file": class.file.display().to_string(),
3146 "dynamic_attributes": class_inheritance.dynamic_attributes
3147 }));
3148 }
3149 }
3150 }
3151 }
3152
3153 Ok(serde_json::json!({
3154 "metaclass": {
3155 "name": metaclass.class_name,
3156 "file": metaclass.file.display().to_string()
3157 },
3158 "affected_classes_count": affected_classes.len(),
3159 "affected_classes": affected_classes,
3160 "creates_dynamic_attributes": !inheritance_info.dynamic_attributes.is_empty(),
3161 "dynamic_attributes": inheritance_info.dynamic_attributes,
3162 "behavior_modifications": [
3163 "class_creation",
3164 "attribute_access",
3165 "method_registration"
3166 ]
3167 }))
3168 } else {
3169 Ok(serde_json::json!(null))
3170 }
3171 }
3172
3173 async fn analyze_mixin_relationships(
3175 &self,
3176 server: &CodePrismMcpServer,
3177 inheritance_info: &codeprism_core::InheritanceInfo,
3178 ) -> Result<serde_json::Value> {
3179 let mut mixin_analysis = Vec::new();
3180
3181 for mixin in &inheritance_info.mixins {
3182 let mixin_classes = server
3184 .graph_store()
3185 .get_nodes_by_kind(codeprism_core::NodeKind::Class);
3186 if let Some(mixin_node) = mixin_classes
3187 .iter()
3188 .find(|node| node.name == mixin.class_name)
3189 {
3190 let mixin_methods = server
3191 .graph_store()
3192 .get_outgoing_edges(&mixin_node.id)
3193 .iter()
3194 .filter_map(|edge| server.graph_store().get_node(&edge.target))
3195 .filter(|node| matches!(node.kind, codeprism_core::NodeKind::Method))
3196 .map(|method| {
3197 serde_json::json!({
3198 "name": method.name,
3199 "file": method.file.display().to_string()
3200 })
3201 })
3202 .collect::<Vec<_>>();
3203
3204 mixin_analysis.push(serde_json::json!({
3205 "name": mixin.class_name,
3206 "file": mixin.file.display().to_string(),
3207 "methods_provided": mixin_methods,
3208 "method_count": mixin_methods.len(),
3209 "mixin_type": if mixin.class_name.ends_with("Mixin") { "explicit" } else { "implicit" }
3210 }));
3211 }
3212 }
3213
3214 Ok(serde_json::json!({
3215 "mixins": mixin_analysis,
3216 "total_mixins": mixin_analysis.len(),
3217 "mixin_pattern_usage": "multiple_inheritance"
3218 }))
3219 }
3220
3221 async fn analyze_method_resolution_order(
3223 &self,
3224 _server: &CodePrismMcpServer,
3225 inheritance_info: &codeprism_core::InheritanceInfo,
3226 ) -> Result<serde_json::Value> {
3227 let mro = &inheritance_info.method_resolution_order;
3228
3229 let mut mro_analysis = Vec::new();
3230 for (index, class_name) in mro.iter().enumerate() {
3231 mro_analysis.push(serde_json::json!({
3232 "order": index,
3233 "class_name": class_name,
3234 "is_root": class_name == "object",
3235 "is_base": index == mro.len() - 1 || class_name == "object",
3236 "is_target": index == 0
3237 }));
3238 }
3239
3240 Ok(serde_json::json!({
3241 "method_resolution_order": mro_analysis,
3242 "mro_length": mro.len(),
3243 "linearization": mro,
3244 "complexity": if mro.len() > 5 { "complex" } else if mro.len() > 3 { "moderate" } else { "simple" },
3245 "has_diamond_pattern": mro.len() > 4 && mro.iter().any(|name| name.contains("Mixin"))
3246 }))
3247 }
3248
3249 async fn analyze_dynamic_attributes(
3251 &self,
3252 _server: &CodePrismMcpServer,
3253 inheritance_info: &codeprism_core::InheritanceInfo,
3254 ) -> Result<serde_json::Value> {
3255 let dynamic_attrs = &inheritance_info.dynamic_attributes;
3256
3257 let mut attribute_analysis = Vec::new();
3258 let mut creation_sources = std::collections::HashMap::new();
3259
3260 for attr in dynamic_attrs {
3261 attribute_analysis.push(serde_json::json!({
3262 "name": attr.name,
3263 "created_by": attr.created_by,
3264 "type": attr.attribute_type,
3265 "creation_source": if attr.created_by.starts_with("metaclass:") { "metaclass" } else { "decorator" }
3266 }));
3267
3268 let source = if attr.created_by.starts_with("metaclass:") {
3269 "metaclass"
3270 } else {
3271 "decorator"
3272 };
3273 *creation_sources.entry(source).or_insert(0) += 1;
3274 }
3275
3276 Ok(serde_json::json!({
3277 "dynamic_attributes": attribute_analysis,
3278 "total_dynamic_attributes": dynamic_attrs.len(),
3279 "creation_sources": creation_sources,
3280 "attribute_types": dynamic_attrs.iter().map(|attr| &attr.attribute_type).collect::<std::collections::HashSet<_>>(),
3281 "patterns": {
3282 "registry_pattern": dynamic_attrs.iter().any(|attr| attr.name.contains("registry") || attr.name.contains("_processors")),
3283 "injection_pattern": dynamic_attrs.iter().any(|attr| attr.created_by.starts_with("metaclass:")),
3284 "decorator_pattern": dynamic_attrs.iter().any(|attr| attr.created_by.starts_with("decorator:"))
3285 }
3286 }))
3287 }
3288
3289 async fn detect_diamond_inheritance(
3291 &self,
3292 server: &CodePrismMcpServer,
3293 class_id: &codeprism_core::NodeId,
3294 ) -> Result<serde_json::Value> {
3295 let inheritance_info = server.graph_query().get_inheritance_info(class_id)?;
3296
3297 let mut diamond_patterns = Vec::new();
3300
3301 if inheritance_info.base_classes.len() > 1 {
3302 for i in 0..inheritance_info.base_classes.len() {
3304 for j in (i + 1)..inheritance_info.base_classes.len() {
3305 let base1 = &inheritance_info.base_classes[i];
3306 let base2 = &inheritance_info.base_classes[j];
3307
3308 let all_classes = server
3310 .graph_store()
3311 .get_nodes_by_kind(codeprism_core::NodeKind::Class);
3312 if let (Some(base1_node), Some(base2_node)) = (
3313 all_classes
3314 .iter()
3315 .find(|node| node.name == base1.class_name),
3316 all_classes
3317 .iter()
3318 .find(|node| node.name == base2.class_name),
3319 ) {
3320 if let (Ok(base1_info), Ok(base2_info)) = (
3321 server.graph_query().get_inheritance_info(&base1_node.id),
3322 server.graph_query().get_inheritance_info(&base2_node.id),
3323 ) {
3324 let common_ancestors: Vec<_> = base1_info
3326 .inheritance_chain
3327 .iter()
3328 .filter(|ancestor| base2_info.inheritance_chain.contains(ancestor))
3329 .cloned()
3330 .collect();
3331
3332 if !common_ancestors.is_empty() {
3333 diamond_patterns.push(serde_json::json!({
3334 "base_classes": [base1.class_name.clone(), base2.class_name.clone()],
3335 "common_ancestors": common_ancestors,
3336 "diamond_type": if common_ancestors.len() > 1 { "complex" } else { "simple" }
3337 }));
3338 }
3339 }
3340 }
3341 }
3342 }
3343 }
3344
3345 Ok(serde_json::json!({
3346 "has_diamond_inheritance": !diamond_patterns.is_empty(),
3347 "diamond_patterns": diamond_patterns,
3348 "multiple_inheritance_count": inheritance_info.base_classes.len(),
3349 "potential_mro_conflicts": diamond_patterns.len() > 1
3350 }))
3351 }
3352
3353 async fn analyze_decorators(
3355 &self,
3356 server: &CodePrismMcpServer,
3357 arguments: Option<Value>,
3358 ) -> Result<CallToolResult> {
3359 let args = arguments.ok_or_else(|| anyhow::anyhow!("Missing arguments"))?;
3360
3361 let target_decorators = if let Some(decorator_pattern) =
3363 args.get("decorator_pattern").and_then(|v| v.as_str())
3364 {
3365 let symbol_types = Some(vec![
3367 codeprism_core::NodeKind::Function,
3368 codeprism_core::NodeKind::Call,
3369 ]);
3370 let limit = args
3371 .get("max_results")
3372 .and_then(|v| v.as_u64())
3373 .map(|v| v as usize)
3374 .unwrap_or(100);
3375
3376 let search_results = server.graph_query().search_symbols(
3377 decorator_pattern,
3378 symbol_types,
3379 Some(limit),
3380 )?;
3381
3382 if search_results.is_empty() {
3383 return Ok(CallToolResult {
3384 content: vec![ToolContent::Text {
3385 text: format!(
3386 "No decorators found matching pattern: {}",
3387 decorator_pattern
3388 ),
3389 }],
3390 is_error: Some(true),
3391 });
3392 }
3393
3394 search_results
3396 .into_iter()
3397 .filter_map(|symbol| server.graph_store().get_node(&symbol.node.id))
3398 .filter(|node| self.is_decorator_node(node))
3399 .collect::<Vec<_>>()
3400 } else if let Some(decorator_id_str) = args.get("decorator_id").and_then(|v| v.as_str()) {
3401 let decorator_id = self.parse_node_id(decorator_id_str)?;
3403 if let Some(node) = server.graph_store().get_node(&decorator_id) {
3404 if self.is_decorator_node(&node) {
3405 vec![node]
3406 } else {
3407 return Ok(CallToolResult {
3408 content: vec![ToolContent::Text {
3409 text: format!("Node {} is not a decorator", decorator_id_str),
3410 }],
3411 is_error: Some(true),
3412 });
3413 }
3414 } else {
3415 return Ok(CallToolResult {
3416 content: vec![ToolContent::Text {
3417 text: format!("Decorator not found: {}", decorator_id_str),
3418 }],
3419 is_error: Some(true),
3420 });
3421 }
3422 } else {
3423 return Ok(CallToolResult {
3424 content: vec![ToolContent::Text {
3425 text: "Either decorator_pattern or decorator_id parameter is required"
3426 .to_string(),
3427 }],
3428 is_error: Some(true),
3429 });
3430 };
3431
3432 let scope = args
3434 .get("scope")
3435 .and_then(|v| v.as_str())
3436 .unwrap_or("repository");
3437
3438 let include_factories = args
3439 .get("include_factories")
3440 .and_then(|v| v.as_bool())
3441 .unwrap_or(true);
3442
3443 let analyze_effects = args
3444 .get("analyze_effects")
3445 .and_then(|v| v.as_bool())
3446 .unwrap_or(true);
3447
3448 let include_chains = args
3449 .get("include_chains")
3450 .and_then(|v| v.as_bool())
3451 .unwrap_or(true);
3452
3453 let detect_patterns = args
3454 .get("detect_patterns")
3455 .and_then(|v| v.as_bool())
3456 .unwrap_or(true);
3457
3458 let include_framework_analysis = args
3459 .get("include_framework_analysis")
3460 .and_then(|v| v.as_bool())
3461 .unwrap_or(true);
3462
3463 let include_source_context = args
3464 .get("include_source_context")
3465 .and_then(|v| v.as_bool())
3466 .unwrap_or(false);
3467
3468 let confidence_threshold = args
3469 .get("confidence_threshold")
3470 .and_then(|v| v.as_f64())
3471 .unwrap_or(0.8);
3472
3473 let mut analysis_results = Vec::new();
3475
3476 for target_decorator in &target_decorators {
3477 let decorator_usage = self
3479 .analyze_decorator_usage(server, &target_decorator.id, scope)
3480 .await?;
3481
3482 let effects_analysis = if analyze_effects {
3484 Some(
3485 self.analyze_decorator_effects(server, &target_decorator.id)
3486 .await?,
3487 )
3488 } else {
3489 None
3490 };
3491
3492 let factory_analysis = if include_factories {
3494 Some(
3495 self.analyze_decorator_factory(server, &target_decorator.id)
3496 .await?,
3497 )
3498 } else {
3499 None
3500 };
3501
3502 let chain_analysis = if include_chains {
3504 Some(
3505 self.analyze_decorator_chains(server, &target_decorator.id)
3506 .await?,
3507 )
3508 } else {
3509 None
3510 };
3511
3512 let framework_analysis = if include_framework_analysis {
3514 Some(
3515 self.analyze_framework_decorators(server, &target_decorator.id)
3516 .await?,
3517 )
3518 } else {
3519 None
3520 };
3521
3522 let pattern_analysis = if detect_patterns {
3524 Some(
3525 self.detect_decorator_patterns(
3526 server,
3527 &target_decorator.id,
3528 confidence_threshold,
3529 )
3530 .await?,
3531 )
3532 } else {
3533 None
3534 };
3535
3536 let mut analysis = serde_json::json!({
3537 "target_decorator": {
3538 "id": target_decorator.id.to_hex(),
3539 "name": target_decorator.name,
3540 "file": target_decorator.file.display().to_string(),
3541 "span": {
3542 "start_line": target_decorator.span.start_line,
3543 "end_line": target_decorator.span.end_line,
3544 "start_column": target_decorator.span.start_column,
3545 "end_column": target_decorator.span.end_column
3546 }
3547 },
3548 "usage_analysis": decorator_usage
3549 });
3550
3551 if include_source_context {
3553 if let Some(context) = self.extract_source_context(
3554 &target_decorator.file,
3555 target_decorator.span.start_line,
3556 3,
3557 ) {
3558 analysis["source_context"] = context;
3559 }
3560 }
3561
3562 if let Some(effects) = effects_analysis {
3564 analysis["effects_analysis"] = effects;
3565 }
3566
3567 if let Some(factory) = factory_analysis {
3568 analysis["factory_analysis"] = factory;
3569 }
3570
3571 if let Some(chains) = chain_analysis {
3572 analysis["chain_analysis"] = chains;
3573 }
3574
3575 if let Some(framework) = framework_analysis {
3576 analysis["framework_analysis"] = framework;
3577 }
3578
3579 if let Some(patterns) = pattern_analysis {
3580 analysis["pattern_analysis"] = patterns;
3581 }
3582
3583 analysis_results.push(analysis);
3584 }
3585
3586 let result = serde_json::json!({
3587 "analysis_results": analysis_results,
3588 "summary": {
3589 "decorators_analyzed": target_decorators.len(),
3590 "scope": scope,
3591 "options": {
3592 "include_factories": include_factories,
3593 "analyze_effects": analyze_effects,
3594 "include_chains": include_chains,
3595 "detect_patterns": detect_patterns,
3596 "include_framework_analysis": include_framework_analysis,
3597 "include_source_context": include_source_context,
3598 "confidence_threshold": confidence_threshold
3599 }
3600 }
3601 });
3602
3603 Ok(CallToolResult {
3604 content: vec![ToolContent::Text {
3605 text: serde_json::to_string_pretty(&result)?,
3606 }],
3607 is_error: Some(false),
3608 })
3609 }
3610
3611 fn is_decorator_node(&self, node: &codeprism_core::Node) -> bool {
3613 if matches!(node.kind, codeprism_core::NodeKind::Function) {
3615 if node.name.starts_with("_") && node.name.len() > 1 {
3617 return false; }
3619
3620 let decorator_indicators = [
3622 "decorator",
3623 "wrap",
3624 "cache",
3625 "validate",
3626 "auth",
3627 "property",
3628 "classmethod",
3629 "staticmethod",
3630 "lru_cache",
3631 "route",
3632 "app",
3633 "requires",
3634 "check",
3635 "log",
3636 "retry",
3637 "timeout",
3638 "rate_limit",
3639 ];
3640
3641 return decorator_indicators
3642 .iter()
3643 .any(|&indicator| node.name.to_lowercase().contains(indicator));
3644 }
3645
3646 if matches!(node.kind, codeprism_core::NodeKind::Call) {
3648 return node.name.starts_with("@")
3650 || node.name.contains("decorator")
3651 || node.name.contains("property")
3652 || node.name.contains("classmethod")
3653 || node.name.contains("staticmethod");
3654 }
3655
3656 false
3657 }
3658
3659 async fn analyze_decorator_usage(
3661 &self,
3662 server: &CodePrismMcpServer,
3663 decorator_id: &codeprism_core::NodeId,
3664 scope: &str,
3665 ) -> Result<serde_json::Value> {
3666 let references = server.graph_query().find_references(decorator_id)?;
3668
3669 let mut usage_locations = Vec::new();
3670 let mut decorated_functions = Vec::new();
3671 let mut decorated_classes = Vec::new();
3672 let mut usage_files = std::collections::HashSet::new();
3673
3674 for reference in &references {
3675 usage_files.insert(reference.location.file.clone());
3676
3677 usage_locations.push(serde_json::json!({
3678 "file": reference.location.file.display().to_string(),
3679 "line": reference.location.span.start_line,
3680 "target_name": reference.source_node.name,
3681 "target_type": format!("{:?}", reference.source_node.kind)
3682 }));
3683
3684 match reference.source_node.kind {
3686 codeprism_core::NodeKind::Function | codeprism_core::NodeKind::Method => {
3687 decorated_functions.push(serde_json::json!({
3688 "name": reference.source_node.name,
3689 "file": reference.source_node.file.display().to_string(),
3690 "type": format!("{:?}", reference.source_node.kind)
3691 }));
3692 }
3693 codeprism_core::NodeKind::Class => {
3694 decorated_classes.push(serde_json::json!({
3695 "name": reference.source_node.name,
3696 "file": reference.source_node.file.display().to_string()
3697 }));
3698 }
3699 _ => {}
3700 }
3701 }
3702
3703 Ok(serde_json::json!({
3704 "usage_count": references.len(),
3705 "files_count": usage_files.len(),
3706 "decorated_functions": decorated_functions,
3707 "decorated_classes": decorated_classes,
3708 "usage_locations": usage_locations,
3709 "scope_coverage": match scope {
3710 "repository" => "full_repository",
3711 "module" => "single_module",
3712 "function" => "function_level",
3713 "class" => "class_level",
3714 _ => "unknown"
3715 }
3716 }))
3717 }
3718
3719 async fn analyze_decorator_effects(
3721 &self,
3722 server: &CodePrismMcpServer,
3723 decorator_id: &codeprism_core::NodeId,
3724 ) -> Result<serde_json::Value> {
3725 let decorator_node = server
3726 .graph_store()
3727 .get_node(decorator_id)
3728 .ok_or_else(|| anyhow::anyhow!("Decorator node not found"))?;
3729
3730 let mut effects = Vec::new();
3732 let mut modifies_signature = false;
3733 let adds_metadata = false;
3734 let mut creates_wrapper = false;
3735 let mut registers_function = false;
3736
3737 let decorator_name = &decorator_node.name.to_lowercase();
3740
3741 if decorator_name.contains("wrapper") || decorator_name.contains("wrap") {
3742 creates_wrapper = true;
3743 effects.push("Creates wrapper function");
3744 }
3745
3746 if decorator_name.contains("property") {
3747 modifies_signature = true;
3748 effects.push("Converts method to property");
3749 }
3750
3751 if decorator_name.contains("cache") || decorator_name.contains("lru") {
3752 effects.push("Adds caching behavior");
3753 }
3754
3755 if decorator_name.contains("validate") {
3756 effects.push("Adds input validation");
3757 }
3758
3759 if decorator_name.contains("auth") || decorator_name.contains("require") {
3760 effects.push("Adds authorization checks");
3761 }
3762
3763 if decorator_name.contains("route") || decorator_name.contains("endpoint") {
3764 registers_function = true;
3765 effects.push("Registers as web endpoint");
3766 }
3767
3768 if decorator_name.contains("log") {
3769 effects.push("Adds logging functionality");
3770 }
3771
3772 if decorator_name.contains("retry") {
3773 effects.push("Adds retry mechanism");
3774 }
3775
3776 if decorator_name.contains("timeout") {
3777 effects.push("Adds timeout handling");
3778 }
3779
3780 if decorator_name.contains("classmethod") || decorator_name.contains("staticmethod") {
3781 modifies_signature = true;
3782 effects.push("Changes method binding");
3783 }
3784
3785 Ok(serde_json::json!({
3786 "effects": effects,
3787 "modifies_signature": modifies_signature,
3788 "adds_metadata": adds_metadata,
3789 "creates_wrapper": creates_wrapper,
3790 "registers_function": registers_function,
3791 "effect_categories": {
3792 "behavioral": effects.iter().any(|e| e.contains("behavior") || e.contains("mechanism")),
3793 "structural": modifies_signature || creates_wrapper,
3794 "registration": registers_function,
3795 "validation": effects.iter().any(|e| e.contains("validation") || e.contains("auth")),
3796 "performance": effects.iter().any(|e| e.contains("cache") || e.contains("timeout"))
3797 }
3798 }))
3799 }
3800
3801 async fn analyze_decorator_factory(
3803 &self,
3804 server: &CodePrismMcpServer,
3805 decorator_id: &codeprism_core::NodeId,
3806 ) -> Result<serde_json::Value> {
3807 let decorator_node = server
3808 .graph_store()
3809 .get_node(decorator_id)
3810 .ok_or_else(|| anyhow::anyhow!("Decorator node not found"))?;
3811
3812 let outgoing_edges = server.graph_store().get_outgoing_edges(decorator_id);
3814 let has_inner_function = outgoing_edges.iter().any(|edge| {
3815 if let Some(target_node) = server.graph_store().get_node(&edge.target) {
3816 matches!(target_node.kind, codeprism_core::NodeKind::Function)
3817 } else {
3818 false
3819 }
3820 });
3821
3822 let is_factory = has_inner_function
3823 || decorator_node.name.to_lowercase().contains("factory")
3824 || decorator_node.name.ends_with("_decorator")
3825 || decorator_node.name.starts_with("make_");
3826
3827 let mut factory_parameters = Vec::new();
3828 if is_factory {
3829 if decorator_node.name.to_lowercase().contains("cache") {
3832 factory_parameters.push("maxsize");
3833 }
3834 if decorator_node.name.to_lowercase().contains("retry") {
3835 factory_parameters.push("attempts");
3836 factory_parameters.push("delay");
3837 }
3838 if decorator_node.name.to_lowercase().contains("timeout") {
3839 factory_parameters.push("seconds");
3840 }
3841 }
3842
3843 Ok(serde_json::json!({
3844 "is_factory": is_factory,
3845 "has_inner_function": has_inner_function,
3846 "factory_parameters": factory_parameters,
3847 "factory_type": if is_factory {
3848 if decorator_node.name.to_lowercase().contains("param") { "parameterized" }
3849 else if has_inner_function { "closure_based" }
3850 else { "configuration_based" }
3851 } else { "simple_decorator" }
3852 }))
3853 }
3854
3855 async fn analyze_decorator_chains(
3857 &self,
3858 server: &CodePrismMcpServer,
3859 decorator_id: &codeprism_core::NodeId,
3860 ) -> Result<serde_json::Value> {
3861 let references = server.graph_query().find_references(decorator_id)?;
3863 let mut chain_analysis = Vec::new();
3864
3865 for reference in &references {
3866 let target_dependencies = server.graph_query().find_dependencies(
3868 &reference.source_node.id,
3869 codeprism_core::graph::DependencyType::Direct,
3870 )?;
3871
3872 let other_decorators: Vec<_> = target_dependencies
3873 .iter()
3874 .filter(|dep| self.is_decorator_node(&dep.target_node))
3875 .filter(|dep| dep.target_node.id != *decorator_id)
3876 .map(|dep| {
3877 serde_json::json!({
3878 "name": dep.target_node.name,
3879 "id": dep.target_node.id.to_hex(),
3880 "file": dep.target_node.file.display().to_string()
3881 })
3882 })
3883 .collect();
3884
3885 if !other_decorators.is_empty() {
3886 chain_analysis.push(serde_json::json!({
3887 "target": {
3888 "name": reference.source_node.name,
3889 "type": format!("{:?}", reference.source_node.kind),
3890 "file": reference.source_node.file.display().to_string()
3891 },
3892 "decorators_in_chain": other_decorators,
3893 "chain_length": other_decorators.len() + 1
3894 }));
3895 }
3896 }
3897
3898 Ok(serde_json::json!({
3899 "chains_found": chain_analysis.len(),
3900 "decorator_chains": chain_analysis,
3901 "has_complex_chains": chain_analysis.iter().any(|chain|
3902 chain["chain_length"].as_u64().unwrap_or(0) > 2
3903 )
3904 }))
3905 }
3906
3907 async fn analyze_framework_decorators(
3909 &self,
3910 _server: &CodePrismMcpServer,
3911 decorator_id: &codeprism_core::NodeId,
3912 ) -> Result<serde_json::Value> {
3913 let decorator_node = _server
3914 .graph_store()
3915 .get_node(decorator_id)
3916 .ok_or_else(|| anyhow::anyhow!("Decorator node not found"))?;
3917
3918 let decorator_name = &decorator_node.name.to_lowercase();
3919 let mut framework_info = serde_json::Map::new();
3920
3921 if decorator_name.contains("route") || decorator_name.contains("app.") {
3923 framework_info.insert(
3924 "framework".to_string(),
3925 serde_json::Value::String("Flask".to_string()),
3926 );
3927 framework_info.insert(
3928 "pattern_type".to_string(),
3929 serde_json::Value::String("routing".to_string()),
3930 );
3931 framework_info.insert(
3932 "creates_endpoint".to_string(),
3933 serde_json::Value::Bool(true),
3934 );
3935 }
3936 else if decorator_name.contains("csrf")
3938 || decorator_name.contains("login_required")
3939 || decorator_name.contains("permission_required")
3940 {
3941 framework_info.insert(
3942 "framework".to_string(),
3943 serde_json::Value::String("Django".to_string()),
3944 );
3945 framework_info.insert(
3946 "pattern_type".to_string(),
3947 serde_json::Value::String("security".to_string()),
3948 );
3949 }
3950 else if decorator_name.contains("depends") || decorator_name.contains("security") {
3952 framework_info.insert(
3953 "framework".to_string(),
3954 serde_json::Value::String("FastAPI".to_string()),
3955 );
3956 framework_info.insert(
3957 "pattern_type".to_string(),
3958 serde_json::Value::String("dependency_injection".to_string()),
3959 );
3960 }
3961 else if decorator_name.contains("fixture")
3963 || decorator_name.contains("mark")
3964 || decorator_name.contains("parametrize")
3965 {
3966 framework_info.insert(
3967 "framework".to_string(),
3968 serde_json::Value::String("pytest".to_string()),
3969 );
3970 framework_info.insert(
3971 "pattern_type".to_string(),
3972 serde_json::Value::String("testing".to_string()),
3973 );
3974 }
3975 else if decorator_name.contains("hybrid") || decorator_name.contains("validates") {
3977 framework_info.insert(
3978 "framework".to_string(),
3979 serde_json::Value::String("SQLAlchemy".to_string()),
3980 );
3981 framework_info.insert(
3982 "pattern_type".to_string(),
3983 serde_json::Value::String("orm".to_string()),
3984 );
3985 }
3986 else if decorator_name.contains("task") || decorator_name.contains("periodic") {
3988 framework_info.insert(
3989 "framework".to_string(),
3990 serde_json::Value::String("Celery".to_string()),
3991 );
3992 framework_info.insert(
3993 "pattern_type".to_string(),
3994 serde_json::Value::String("task_queue".to_string()),
3995 );
3996 }
3997 else {
3999 framework_info.insert(
4000 "framework".to_string(),
4001 serde_json::Value::String("unknown".to_string()),
4002 );
4003 framework_info.insert(
4004 "pattern_type".to_string(),
4005 serde_json::Value::String("custom".to_string()),
4006 );
4007 }
4008
4009 Ok(serde_json::Value::Object(framework_info))
4010 }
4011
4012 async fn detect_decorator_patterns(
4014 &self,
4015 server: &CodePrismMcpServer,
4016 decorator_id: &codeprism_core::NodeId,
4017 confidence_threshold: f64,
4018 ) -> Result<serde_json::Value> {
4019 let decorator_node = server
4020 .graph_store()
4021 .get_node(decorator_id)
4022 .ok_or_else(|| anyhow::anyhow!("Decorator node not found"))?;
4023
4024 let decorator_name = &decorator_node.name.to_lowercase();
4025 let mut detected_patterns = Vec::new();
4026
4027 if decorator_name.contains("register") || decorator_name.contains("route") {
4029 detected_patterns.push(serde_json::json!({
4030 "pattern": "Registry Pattern",
4031 "confidence": 0.9,
4032 "description": "Decorator registers functions in a central registry",
4033 "indicators": ["register", "route", "endpoint"],
4034 "benefits": ["centralized_management", "automatic_discovery", "loose_coupling"]
4035 }));
4036 }
4037
4038 if decorator_name.contains("cache")
4040 || decorator_name.contains("memoize")
4041 || decorator_name.contains("lru")
4042 {
4043 detected_patterns.push(serde_json::json!({
4044 "pattern": "Caching Pattern",
4045 "confidence": 0.95,
4046 "description": "Decorator adds caching to function results",
4047 "indicators": ["cache", "memoize", "lru"],
4048 "benefits": ["performance_improvement", "reduced_computation", "memory_optimization"]
4049 }));
4050 }
4051
4052 if decorator_name.contains("validate")
4054 || decorator_name.contains("check")
4055 || decorator_name.contains("verify")
4056 {
4057 detected_patterns.push(serde_json::json!({
4058 "pattern": "Validation Pattern",
4059 "confidence": 0.85,
4060 "description": "Decorator adds input/output validation",
4061 "indicators": ["validate", "check", "verify"],
4062 "benefits": ["data_integrity", "error_prevention", "security"]
4063 }));
4064 }
4065
4066 if decorator_name.contains("auth")
4068 || decorator_name.contains("require")
4069 || decorator_name.contains("permission")
4070 {
4071 detected_patterns.push(serde_json::json!({
4072 "pattern": "Authorization Pattern",
4073 "confidence": 0.9,
4074 "description": "Decorator adds access control",
4075 "indicators": ["auth", "require", "permission", "login"],
4076 "benefits": ["security", "access_control", "separation_of_concerns"]
4077 }));
4078 }
4079
4080 if decorator_name.contains("retry") || decorator_name.contains("attempt") {
4082 detected_patterns.push(serde_json::json!({
4083 "pattern": "Retry Pattern",
4084 "confidence": 0.8,
4085 "description": "Decorator adds retry logic for failed operations",
4086 "indicators": ["retry", "attempt", "resilience"],
4087 "benefits": ["fault_tolerance", "reliability", "error_recovery"]
4088 }));
4089 }
4090
4091 if decorator_name.contains("log")
4093 || decorator_name.contains("trace")
4094 || decorator_name.contains("audit")
4095 {
4096 detected_patterns.push(serde_json::json!({
4097 "pattern": "Logging Pattern",
4098 "confidence": 0.8,
4099 "description": "Decorator adds logging/auditing functionality",
4100 "indicators": ["log", "trace", "audit"],
4101 "benefits": ["observability", "debugging", "compliance"]
4102 }));
4103 }
4104
4105 if decorator_name.contains("time")
4107 || decorator_name.contains("measure")
4108 || decorator_name.contains("profile")
4109 {
4110 detected_patterns.push(serde_json::json!({
4111 "pattern": "Performance Monitoring Pattern",
4112 "confidence": 0.85,
4113 "description": "Decorator measures execution time and performance",
4114 "indicators": ["time", "measure", "profile"],
4115 "benefits": ["performance_monitoring", "optimization", "benchmarking"]
4116 }));
4117 }
4118
4119 let filtered_patterns: Vec<_> = detected_patterns
4121 .into_iter()
4122 .filter(|pattern| pattern["confidence"].as_f64().unwrap_or(0.0) >= confidence_threshold)
4123 .collect();
4124
4125 Ok(serde_json::json!({
4126 "detected_patterns": filtered_patterns,
4127 "pattern_count": filtered_patterns.len(),
4128 "confidence_threshold": confidence_threshold,
4129 "recommendations": self.get_decorator_recommendations(&filtered_patterns)
4130 }))
4131 }
4132
4133 fn get_decorator_recommendations(&self, patterns: &[serde_json::Value]) -> Vec<String> {
4135 let mut recommendations = Vec::new();
4136
4137 if patterns.is_empty() {
4138 recommendations.push(
4139 "Consider adding more descriptive names to better identify decorator patterns"
4140 .to_string(),
4141 );
4142 recommendations.push("Document the decorator's purpose and effects".to_string());
4143 } else {
4144 recommendations.push("Well-identified decorator patterns found".to_string());
4145
4146 if patterns
4147 .iter()
4148 .any(|p| p["pattern"].as_str().unwrap_or("").contains("Caching"))
4149 {
4150 recommendations.push(
4151 "Consider cache invalidation strategy for caching decorators".to_string(),
4152 );
4153 }
4154
4155 if patterns.iter().any(|p| {
4156 p["pattern"]
4157 .as_str()
4158 .unwrap_or("")
4159 .contains("Authorization")
4160 }) {
4161 recommendations.push("Ensure authorization decorators are applied consistently across similar endpoints".to_string());
4162 }
4163
4164 if patterns
4165 .iter()
4166 .any(|p| p["pattern"].as_str().unwrap_or("").contains("Validation"))
4167 {
4168 recommendations
4169 .push("Consider combining validation patterns with error handling".to_string());
4170 }
4171
4172 if patterns.len() > 3 {
4173 recommendations.push("Consider creating a decorator composition utility for complex decorator chains".to_string());
4174 }
4175 }
4176
4177 recommendations
4178 }
4179
4180 async fn find_duplicates(
4182 &self,
4183 server: &CodePrismMcpServer,
4184 arguments: Option<Value>,
4185 ) -> Result<CallToolResult> {
4186 let args = arguments.unwrap_or_default();
4187
4188 let similarity_threshold = args
4189 .get("similarity_threshold")
4190 .and_then(|v| v.as_f64())
4191 .unwrap_or(0.8);
4192
4193 let min_lines = args
4194 .get("min_lines")
4195 .and_then(|v| v.as_u64())
4196 .map(|v| v as usize)
4197 .unwrap_or(3);
4198
4199 let scope = args
4200 .get("scope")
4201 .and_then(|v| v.as_str())
4202 .unwrap_or("repository");
4203
4204 let result = serde_json::json!({
4206 "scope": scope,
4207 "parameters": {
4208 "similarity_threshold": similarity_threshold,
4209 "min_lines": min_lines
4210 },
4211 "duplicates": [],
4212 "summary": {
4213 "total_duplicates": 0,
4214 "files_analyzed": 0,
4215 "lines_duplicated": 0
4216 },
4217 "analysis_successful": true
4218 });
4219
4220 Ok(CallToolResult {
4221 content: vec![ToolContent::Text {
4222 text: serde_json::to_string_pretty(&result)?,
4223 }],
4224 is_error: Some(false),
4225 })
4226 }
4227
4228 async fn find_unused_code(
4230 &self,
4231 server: &CodePrismMcpServer,
4232 arguments: Option<Value>,
4233 ) -> Result<CallToolResult> {
4234 let args = arguments.unwrap_or_default();
4235
4236 let scope = args
4237 .get("scope")
4238 .and_then(|v| v.as_str())
4239 .unwrap_or("repository");
4240
4241 let analyze_types = args
4242 .get("analyze_types")
4243 .and_then(|v| v.as_array())
4244 .map(|arr| {
4245 arr.iter()
4246 .filter_map(|v| v.as_str().map(|s| s.to_string()))
4247 .collect::<Vec<_>>()
4248 })
4249 .unwrap_or_else(|| {
4250 vec![
4251 "functions".to_string(),
4252 "classes".to_string(),
4253 "variables".to_string(),
4254 "imports".to_string(),
4255 ]
4256 });
4257
4258 let confidence_threshold = args
4259 .get("confidence_threshold")
4260 .and_then(|v| v.as_f64())
4261 .unwrap_or(0.7);
4262
4263 let consider_external_apis = args
4264 .get("consider_external_apis")
4265 .and_then(|v| v.as_bool())
4266 .unwrap_or(true);
4267
4268 let include_dead_code = args
4269 .get("include_dead_code")
4270 .and_then(|v| v.as_bool())
4271 .unwrap_or(true);
4272
4273 let exclude_patterns = args
4274 .get("exclude_patterns")
4275 .and_then(|v| v.as_array())
4276 .map(|arr| {
4277 arr.iter()
4278 .filter_map(|v| v.as_str().map(|s| s.to_string()))
4279 .collect::<Vec<_>>()
4280 })
4281 .unwrap_or_default();
4282
4283 let analysis_result = self
4284 .perform_unused_code_analysis(
4285 server,
4286 scope,
4287 include_dead_code,
4288 consider_external_apis,
4289 confidence_threshold,
4290 &analyze_types,
4291 &exclude_patterns,
4292 )
4293 .await?;
4294
4295 Ok(CallToolResult {
4296 content: vec![ToolContent::Text {
4297 text: serde_json::to_string_pretty(&analysis_result)?,
4298 }],
4299 is_error: Some(false),
4300 })
4301 }
4302
4303 async fn analyze_security(
4305 &self,
4306 server: &CodePrismMcpServer,
4307 arguments: Option<Value>,
4308 ) -> Result<CallToolResult> {
4309 let args = arguments.unwrap_or_default();
4310
4311 let scope = args
4312 .get("scope")
4313 .and_then(|v| v.as_str())
4314 .unwrap_or("repository");
4315
4316 let vulnerability_types = args
4317 .get("vulnerability_types")
4318 .and_then(|v| v.as_array())
4319 .map(|arr| {
4320 arr.iter()
4321 .filter_map(|v| v.as_str().map(|s| s.to_string()))
4322 .collect::<Vec<_>>()
4323 })
4324 .unwrap_or_else(|| {
4325 vec![
4326 "injection".to_string(),
4327 "authentication".to_string(),
4328 "authorization".to_string(),
4329 ]
4330 });
4331
4332 let severity_threshold = args
4333 .get("severity_threshold")
4334 .and_then(|v| v.as_str())
4335 .unwrap_or("medium");
4336
4337 let include_data_flow_analysis = args
4338 .get("include_data_flow_analysis")
4339 .and_then(|v| v.as_bool())
4340 .unwrap_or(false);
4341
4342 let check_external_dependencies = args
4343 .get("check_external_dependencies")
4344 .and_then(|v| v.as_bool())
4345 .unwrap_or(true);
4346
4347 let exclude_patterns = args
4348 .get("exclude_patterns")
4349 .and_then(|v| v.as_array())
4350 .map(|arr| {
4351 arr.iter()
4352 .filter_map(|v| v.as_str().map(|s| s.to_string()))
4353 .collect::<Vec<_>>()
4354 })
4355 .unwrap_or_default();
4356
4357 let analysis_result = self
4358 .perform_security_analysis(
4359 server,
4360 scope,
4361 &vulnerability_types,
4362 severity_threshold,
4363 include_data_flow_analysis,
4364 check_external_dependencies,
4365 &exclude_patterns,
4366 )
4367 .await?;
4368
4369 Ok(CallToolResult {
4370 content: vec![ToolContent::Text {
4371 text: serde_json::to_string_pretty(&analysis_result)?,
4372 }],
4373 is_error: Some(false),
4374 })
4375 }
4376
4377 async fn analyze_performance(
4379 &self,
4380 server: &CodePrismMcpServer,
4381 arguments: Option<Value>,
4382 ) -> Result<CallToolResult> {
4383 let args = arguments.unwrap_or_default();
4384
4385 let scope = args
4386 .get("scope")
4387 .and_then(|v| v.as_str())
4388 .unwrap_or("repository");
4389
4390 let analysis_types = args
4391 .get("analysis_types")
4392 .and_then(|v| v.as_array())
4393 .map(|arr| {
4394 arr.iter()
4395 .filter_map(|v| v.as_str().map(|s| s.to_string()))
4396 .collect::<Vec<_>>()
4397 })
4398 .unwrap_or_else(|| {
4399 vec![
4400 "time_complexity".to_string(),
4401 "memory_usage".to_string(),
4402 "hot_spots".to_string(),
4403 ]
4404 });
4405
4406 let complexity_threshold = args
4407 .get("complexity_threshold")
4408 .and_then(|v| v.as_str())
4409 .unwrap_or("medium");
4410
4411 let include_algorithmic_analysis = args
4412 .get("include_algorithmic_analysis")
4413 .and_then(|v| v.as_bool())
4414 .unwrap_or(true);
4415
4416 let detect_bottlenecks = args
4417 .get("detect_bottlenecks")
4418 .and_then(|v| v.as_bool())
4419 .unwrap_or(true);
4420
4421 let exclude_patterns = args
4422 .get("exclude_patterns")
4423 .and_then(|v| v.as_array())
4424 .map(|arr| {
4425 arr.iter()
4426 .filter_map(|v| v.as_str().map(|s| s.to_string()))
4427 .collect::<Vec<_>>()
4428 })
4429 .unwrap_or_default();
4430
4431 let analysis_result = self
4432 .perform_performance_analysis(
4433 server,
4434 scope,
4435 &analysis_types,
4436 complexity_threshold,
4437 include_algorithmic_analysis,
4438 detect_bottlenecks,
4439 &exclude_patterns,
4440 )
4441 .await?;
4442
4443 Ok(CallToolResult {
4444 content: vec![ToolContent::Text {
4445 text: serde_json::to_string_pretty(&analysis_result)?,
4446 }],
4447 is_error: Some(false),
4448 })
4449 }
4450
4451 async fn analyze_api_surface(
4453 &self,
4454 server: &CodePrismMcpServer,
4455 arguments: Option<Value>,
4456 ) -> Result<CallToolResult> {
4457 let args = arguments.unwrap_or_default();
4458
4459 let scope = args
4460 .get("scope")
4461 .and_then(|v| v.as_str())
4462 .unwrap_or("repository");
4463
4464 let analysis_types = args
4465 .get("analysis_types")
4466 .and_then(|v| v.as_array())
4467 .map(|arr| {
4468 arr.iter()
4469 .filter_map(|v| v.as_str().map(|s| s.to_string()))
4470 .collect::<Vec<_>>()
4471 })
4472 .unwrap_or_else(|| {
4473 vec![
4474 "public_api".to_string(),
4475 "versioning".to_string(),
4476 "breaking_changes".to_string(),
4477 ]
4478 });
4479
4480 let api_version = args.get("api_version").and_then(|v| v.as_str());
4481
4482 let include_private_apis = args
4483 .get("include_private_apis")
4484 .and_then(|v| v.as_bool())
4485 .unwrap_or(false);
4486
4487 let check_documentation_coverage = args
4488 .get("check_documentation_coverage")
4489 .and_then(|v| v.as_bool())
4490 .unwrap_or(true);
4491
4492 let detect_breaking_changes = args
4493 .get("detect_breaking_changes")
4494 .and_then(|v| v.as_bool())
4495 .unwrap_or(true);
4496
4497 let exclude_patterns = args
4498 .get("exclude_patterns")
4499 .and_then(|v| v.as_array())
4500 .map(|arr| {
4501 arr.iter()
4502 .filter_map(|v| v.as_str().map(|s| s.to_string()))
4503 .collect::<Vec<_>>()
4504 })
4505 .unwrap_or_default();
4506
4507 let analysis_result = self
4508 .perform_api_surface_analysis(
4509 server,
4510 scope,
4511 &analysis_types,
4512 api_version,
4513 include_private_apis,
4514 check_documentation_coverage,
4515 detect_breaking_changes,
4516 &exclude_patterns,
4517 )
4518 .await?;
4519
4520 Ok(CallToolResult {
4521 content: vec![ToolContent::Text {
4522 text: serde_json::to_string_pretty(&analysis_result)?,
4523 }],
4524 is_error: Some(false),
4525 })
4526 }
4527}
4528
4529#[cfg(test)]
4530mod tests {
4531 use super::*;
4532 use crate::CodePrismMcpServer;
4533 use std::fs;
4534 use std::sync::Arc;
4535 use tempfile::TempDir;
4536 use tokio::sync::RwLock;
4537
4538 async fn create_test_server() -> Arc<RwLock<CodePrismMcpServer>> {
4539 let temp_dir = TempDir::new().expect("Failed to create temp dir");
4540 let repo_path = temp_dir.path();
4541
4542 fs::write(
4544 repo_path.join("main.py"),
4545 r#"
4546class User:
4547 def __init__(self, name: str):
4548 self.name = name
4549
4550 def get_greeting(self) -> str:
4551 return format_greeting(self.name)
4552
4553def format_greeting(name: str) -> str:
4554 return f"Hello, {name}!"
4555
4556if __name__ == "__main__":
4557 user = User("Alice")
4558 print(user.get_greeting())
4559"#,
4560 )
4561 .unwrap();
4562
4563 fs::write(
4564 repo_path.join("utils.py"),
4565 r#"
4566def validate_input(data: str) -> bool:
4567 return len(data) > 0
4568
4569def process_data(input_data: str) -> str:
4570 if validate_input(input_data):
4571 return input_data.upper()
4572 return ""
4573"#,
4574 )
4575 .unwrap();
4576
4577 let mut server = CodePrismMcpServer::new().expect("Failed to create server");
4578 server
4579 .initialize_with_repository(repo_path)
4580 .await
4581 .expect("Failed to initialize repository");
4582
4583 std::mem::forget(temp_dir);
4585
4586 Arc::new(RwLock::new(server))
4587 }
4588
4589 #[tokio::test]
4590 async fn test_tool_manager_creation() {
4591 let server = create_test_server().await;
4592 let _tool_manager = ToolManager::new(server);
4593
4594 }
4596
4597 #[tokio::test]
4598 async fn test_list_tools() {
4599 let server = create_test_server().await;
4600 let tool_manager = ToolManager::new(server);
4601
4602 let result = tool_manager
4603 .list_tools(ListToolsParams { cursor: None })
4604 .await;
4605 assert!(result.is_ok());
4606
4607 let tools_result = result.unwrap();
4608 assert_eq!(tools_result.tools.len(), 20); assert!(tools_result.next_cursor.is_none());
4610
4611 let tool_names: Vec<String> = tools_result.tools.iter().map(|t| t.name.clone()).collect();
4613 assert!(tool_names.contains(&"repository_stats".to_string()));
4614 assert!(tool_names.contains(&"trace_path".to_string()));
4615 assert!(tool_names.contains(&"explain_symbol".to_string()));
4616 assert!(tool_names.contains(&"find_dependencies".to_string()));
4617 assert!(tool_names.contains(&"find_references".to_string()));
4618 assert!(tool_names.contains(&"search_symbols".to_string()));
4619 assert!(tool_names.contains(&"search_content".to_string()));
4620 assert!(tool_names.contains(&"find_files".to_string()));
4621 assert!(tool_names.contains(&"content_stats".to_string()));
4622 }
4623
4624 #[tokio::test]
4625 async fn test_repository_stats_tool() {
4626 let server = create_test_server().await;
4627 let tool_manager = ToolManager::new(server);
4628
4629 let params = CallToolParams {
4630 name: "repository_stats".to_string(),
4631 arguments: Some(serde_json::json!({})),
4632 };
4633
4634 let result = tool_manager.call_tool(params).await;
4635 assert!(result.is_ok());
4636
4637 let tool_result = result.unwrap();
4638 assert_eq!(tool_result.is_error, Some(false));
4639 assert_eq!(tool_result.content.len(), 1);
4640
4641 if let ToolContent::Text { text } = &tool_result.content[0] {
4642 let stats: serde_json::Value = serde_json::from_str(text).unwrap();
4643 assert!(stats["total_files"].as_u64().unwrap() > 0);
4644 assert!(stats["total_nodes"].as_u64().unwrap() > 0);
4645 assert!(stats["status"].as_str().unwrap() == "active");
4646 } else {
4647 panic!("Expected text content");
4648 }
4649 }
4650
4651 #[tokio::test]
4652 async fn test_search_symbols_tool() {
4653 let server = create_test_server().await;
4654 let tool_manager = ToolManager::new(server);
4655
4656 let params = CallToolParams {
4657 name: "search_symbols".to_string(),
4658 arguments: Some(serde_json::json!({
4659 "pattern": "User",
4660 "symbol_types": ["class"],
4661 "limit": 10
4662 })),
4663 };
4664
4665 let result = tool_manager.call_tool(params).await;
4666 assert!(result.is_ok());
4667
4668 let tool_result = result.unwrap();
4669 assert_eq!(tool_result.is_error, Some(false));
4670
4671 if let ToolContent::Text { text } = &tool_result.content[0] {
4672 let search_result: serde_json::Value = serde_json::from_str(text).unwrap();
4673 assert_eq!(search_result["pattern"].as_str().unwrap(), "User");
4674 assert!(search_result["results"].is_array());
4675 }
4676 }
4677
4678 #[tokio::test]
4679 async fn test_search_symbols_with_regex_pattern() {
4680 let server = create_test_server().await;
4681 let tool_manager = ToolManager::new(server);
4682
4683 let params = CallToolParams {
4684 name: "search_symbols".to_string(),
4685 arguments: Some(serde_json::json!({
4686 "pattern": "get_",
4687 "symbol_types": ["function", "method"],
4688 "limit": 50
4689 })),
4690 };
4691
4692 let result = tool_manager.call_tool(params).await;
4693 assert!(result.is_ok());
4694
4695 let tool_result = result.unwrap();
4696 assert_eq!(tool_result.is_error, Some(false));
4697 }
4698
4699 #[tokio::test]
4700 async fn test_unknown_tool() {
4701 let server = create_test_server().await;
4702 let tool_manager = ToolManager::new(server);
4703
4704 let params = CallToolParams {
4705 name: "unknown_tool".to_string(),
4706 arguments: Some(serde_json::json!({})),
4707 };
4708
4709 let result = tool_manager.call_tool(params).await;
4710 assert!(result.is_ok());
4711
4712 let tool_result = result.unwrap();
4713 assert_eq!(tool_result.is_error, Some(true));
4714
4715 if let ToolContent::Text { text } = &tool_result.content[0] {
4716 assert!(text.contains("Unknown tool: unknown_tool"));
4717 }
4718 }
4719
4720 #[tokio::test]
4721 async fn test_trace_path_missing_arguments() {
4722 let server = create_test_server().await;
4723 let tool_manager = ToolManager::new(server);
4724
4725 let params = CallToolParams {
4726 name: "trace_path".to_string(),
4727 arguments: Some(serde_json::json!({})), };
4729
4730 let result = tool_manager.call_tool(params).await;
4731 assert!(result.is_err()); }
4733
4734 #[tokio::test]
4735 async fn test_explain_symbol_missing_arguments() {
4736 let server = create_test_server().await;
4737 let tool_manager = ToolManager::new(server);
4738
4739 let params = CallToolParams {
4740 name: "explain_symbol".to_string(),
4741 arguments: Some(serde_json::json!({})), };
4743
4744 let result = tool_manager.call_tool(params).await;
4745 assert!(result.is_err()); }
4747
4748 #[tokio::test]
4749 async fn test_find_dependencies_invalid_dependency_type() {
4750 let server = create_test_server().await;
4751 let tool_manager = ToolManager::new(server);
4752
4753 let params = CallToolParams {
4754 name: "find_dependencies".to_string(),
4755 arguments: Some(serde_json::json!({
4756 "target": "fake_target",
4757 "dependency_type": "invalid_type"
4758 })),
4759 };
4760
4761 let result = tool_manager.call_tool(params).await;
4762 assert!(result.is_ok());
4763
4764 let tool_result = result.unwrap();
4765 assert_eq!(tool_result.is_error, Some(true));
4766
4767 if let ToolContent::Text { text } = &tool_result.content[0] {
4768 assert!(text.contains("Invalid dependency type"));
4769 }
4770 }
4771
4772 #[tokio::test]
4773 async fn test_find_references_missing_arguments() {
4774 let server = create_test_server().await;
4775 let tool_manager = ToolManager::new(server);
4776
4777 let params = CallToolParams {
4778 name: "find_references".to_string(),
4779 arguments: Some(serde_json::json!({})), };
4781
4782 let result = tool_manager.call_tool(params).await;
4783 assert!(result.is_err()); }
4785
4786 #[tokio::test]
4787 async fn test_search_symbols_missing_pattern() {
4788 let server = create_test_server().await;
4789 let tool_manager = ToolManager::new(server);
4790
4791 let params = CallToolParams {
4792 name: "search_symbols".to_string(),
4793 arguments: Some(serde_json::json!({})), };
4795
4796 let result = tool_manager.call_tool(params).await;
4797 assert!(result.is_err()); }
4799
4800 #[tokio::test]
4801 async fn test_tool_input_schemas() {
4802 let server = create_test_server().await;
4803 let tool_manager = ToolManager::new(server);
4804
4805 let tools_result = tool_manager
4806 .list_tools(ListToolsParams { cursor: None })
4807 .await
4808 .unwrap();
4809
4810 for tool in tools_result.tools {
4811 assert!(tool.input_schema.is_object());
4813
4814 let schema = tool.input_schema.as_object().unwrap();
4815 assert_eq!(schema.get("type").unwrap().as_str().unwrap(), "object");
4816
4817 if tool.name != "repository_stats" {
4819 assert!(schema.contains_key("properties"));
4821 }
4822
4823 match tool.name.as_str() {
4825 "trace_path" => {
4826 let required = schema.get("required").unwrap().as_array().unwrap();
4827 assert!(required.contains(&serde_json::Value::String("source".to_string())));
4828 assert!(required.contains(&serde_json::Value::String("target".to_string())));
4829 }
4830 "explain_symbol" | "find_references" => {
4831 let required = schema.get("required").unwrap().as_array().unwrap();
4832 assert!(required.contains(&serde_json::Value::String("symbol_id".to_string())));
4833 }
4834 "find_dependencies" => {
4835 let required = schema.get("required").unwrap().as_array().unwrap();
4836 assert!(required.contains(&serde_json::Value::String("target".to_string())));
4837 }
4838 "search_symbols" => {
4839 let required = schema.get("required").unwrap().as_array().unwrap();
4840 assert!(required.contains(&serde_json::Value::String("pattern".to_string())));
4841 }
4842 _ => {} }
4844 }
4845 }
4846
4847 #[tokio::test]
4848 async fn test_tool_capabilities_serialization() {
4849 let capabilities = ToolCapabilities {
4850 list_changed: Some(true),
4851 };
4852
4853 let json = serde_json::to_string(&capabilities).unwrap();
4854 let deserialized: ToolCapabilities = serde_json::from_str(&json).unwrap();
4855
4856 assert_eq!(capabilities.list_changed, deserialized.list_changed);
4857 }
4858
4859 #[tokio::test]
4860 async fn test_call_tool_params_serialization() {
4861 let params = CallToolParams {
4862 name: "test_tool".to_string(),
4863 arguments: Some(serde_json::json!({"key": "value"})),
4864 };
4865
4866 let json = serde_json::to_string(¶ms).unwrap();
4867 let deserialized: CallToolParams = serde_json::from_str(&json).unwrap();
4868
4869 assert_eq!(params.name, deserialized.name);
4870 assert_eq!(params.arguments, deserialized.arguments);
4871 }
4872
4873 #[tokio::test]
4874 async fn test_call_tool_result_serialization() {
4875 let result = CallToolResult {
4876 content: vec![ToolContent::Text {
4877 text: "Test result".to_string(),
4878 }],
4879 is_error: Some(false),
4880 };
4881
4882 let json = serde_json::to_string(&result).unwrap();
4883 let deserialized: CallToolResult = serde_json::from_str(&json).unwrap();
4884
4885 assert_eq!(result.content.len(), deserialized.content.len());
4886 assert_eq!(result.is_error, deserialized.is_error);
4887 }
4888
4889 #[test]
4890 fn test_parse_node_id_valid() {
4891 let server = Arc::new(RwLock::new(CodePrismMcpServer::new().unwrap()));
4892 let tool_manager = ToolManager::new(server);
4893
4894 let valid_hex = "deadbeef12345678";
4896 let result = tool_manager.parse_node_id(valid_hex);
4897
4898 match result {
4901 Ok(_) => {}
4902 Err(_) => {} }
4904 }
4905
4906 #[tokio::test]
4907 async fn test_parse_node_id_invalid() {
4908 let tool_manager = ToolManager::new(create_test_server().await);
4909
4910 let result = tool_manager.parse_node_id("invalid-hex");
4912 assert!(result.is_err());
4913
4914 let result = tool_manager.parse_node_id("abc123");
4916 assert!(result.is_err());
4917 }
4918
4919 #[tokio::test]
4920 async fn test_source_context_extraction() {
4921 use std::fs;
4922 use std::path::Path;
4923 use tempfile::TempDir;
4924
4925 let temp_dir = TempDir::new().expect("Failed to create temp dir");
4926 let test_file = temp_dir.path().join("test.py");
4927
4928 fs::write(
4930 &test_file,
4931 r#"# Line 1: Header comment
4932class TestClass:
4933 """Test class docstring."""
4934
4935 def test_method(self):
4936 """Test method docstring."""
4937 return "Hello, World!"
4938
4939 def another_method(self):
4940 return 42
4941# Line 11: Footer comment"#,
4942 )
4943 .unwrap();
4944
4945 let server = create_test_server().await;
4946 let tool_manager = ToolManager::new(server);
4947
4948 let context = tool_manager.extract_source_context(&test_file, 5, 2);
4950 assert!(context.is_some());
4951
4952 let context_value = context.unwrap();
4953 assert_eq!(context_value["target_line"], 5);
4954 assert_eq!(context_value["context_range"]["start_line"], 3);
4955 assert_eq!(context_value["context_range"]["end_line"], 7);
4956
4957 let lines = context_value["lines"].as_array().unwrap();
4958 assert_eq!(lines.len(), 5); let target_line = lines.iter().find(|line| line["is_target"] == true).unwrap();
4962 assert_eq!(target_line["line_number"], 5);
4963 assert!(target_line["content"]
4964 .as_str()
4965 .unwrap()
4966 .contains("def test_method"));
4967
4968 let context = tool_manager.extract_source_context(&test_file, 1, 3);
4970 assert!(context.is_some());
4971
4972 let context_value = context.unwrap();
4973 assert_eq!(context_value["context_range"]["start_line"], 1);
4974
4975 let context = tool_manager.extract_source_context(&test_file, 11, 3);
4977 assert!(context.is_some());
4978
4979 let context_value = context.unwrap();
4980 assert_eq!(context_value["context_range"]["end_line"], 11);
4981
4982 let context = tool_manager.extract_source_context(&test_file, 100, 2);
4984 assert!(context.is_none());
4985
4986 let context = tool_manager.extract_source_context(Path::new("/nonexistent/file.py"), 1, 2);
4988 assert!(context.is_none());
4989 }
4990
4991 #[tokio::test]
4992 async fn test_context_lines_parameter_validation() {
4993 use std::fs;
4994 use tempfile::TempDir;
4995
4996 let temp_dir = TempDir::new().expect("Failed to create temp dir");
4997 let test_file = temp_dir.path().join("test.py");
4998
4999 fs::write(
5001 &test_file,
5002 r#"# Test file
5003def example_function():
5004 """An example function."""
5005 return "hello"
5006
5007def another_function():
5008 return 42
5009"#,
5010 )
5011 .unwrap();
5012
5013 let server_arc = create_test_server().await;
5014 let tool_manager = ToolManager::new(server_arc);
5015
5016 let context = tool_manager.extract_source_context(&test_file, 2, 0);
5020 assert!(context.is_some());
5021 let context_value = context.unwrap();
5022 let lines = context_value["lines"].as_array().unwrap();
5023 assert_eq!(lines.len(), 1); let context = tool_manager.extract_source_context(&test_file, 2, 2);
5027 assert!(context.is_some());
5028 let context_value = context.unwrap();
5029 let lines = context_value["lines"].as_array().unwrap();
5030 assert!(lines.len() > 1); let context = tool_manager.extract_source_context(&test_file, 2, 100);
5034 assert!(context.is_some());
5035 let context_value = context.unwrap();
5036 let lines = context_value["lines"].as_array().unwrap();
5037 assert!(lines.len() <= 7); }
5039
5040 #[tokio::test]
5041 async fn test_context_with_small_files() {
5042 use std::fs;
5043 use tempfile::TempDir;
5044
5045 let temp_dir = TempDir::new().expect("Failed to create temp dir");
5046 let small_file = temp_dir.path().join("small.py");
5047
5048 fs::write(&small_file, "x = 1\ny = 2").unwrap();
5050
5051 let server_arc = create_test_server().await;
5052 let tool_manager = ToolManager::new(server_arc);
5053
5054 let context = tool_manager.extract_source_context(&small_file, 1, 5);
5056 assert!(context.is_some());
5057
5058 let context_value = context.unwrap();
5059 assert_eq!(context_value["target_line"], 1);
5060
5061 let lines = context_value["lines"].as_array().unwrap();
5062 assert_eq!(lines.len(), 2); let context = tool_manager.extract_source_context(&small_file, 2, 5);
5066 assert!(context.is_some());
5067
5068 let context_value = context.unwrap();
5069 assert_eq!(context_value["target_line"], 2);
5070
5071 let lines = context_value["lines"].as_array().unwrap();
5072 assert_eq!(lines.len(), 2); }
5074
5075 #[tokio::test]
5076 async fn test_context_edge_cases() {
5077 use std::fs;
5078 use std::os::unix::fs::PermissionsExt;
5079 use tempfile::TempDir;
5080
5081 let temp_dir = TempDir::new().expect("Failed to create temp dir");
5082 let server_arc = create_test_server().await;
5083 let tool_manager = ToolManager::new(server_arc);
5084
5085 let empty_file = temp_dir.path().join("empty.py");
5087 fs::write(&empty_file, "").unwrap();
5088
5089 let context = tool_manager.extract_source_context(&empty_file, 1, 2);
5090 assert!(context.is_none()); let normal_file = temp_dir.path().join("normal.py");
5094 fs::write(&normal_file, "line1\nline2\nline3").unwrap();
5095
5096 let context = tool_manager.extract_source_context(&normal_file, 0, 2);
5097 assert!(context.is_none()); let restricted_file = temp_dir.path().join("restricted.py");
5101 fs::write(&restricted_file, "secret content").unwrap();
5102
5103 let mut perms = fs::metadata(&restricted_file).unwrap().permissions();
5105 perms.set_mode(0o000);
5106 fs::set_permissions(&restricted_file, perms).unwrap();
5107
5108 let context = tool_manager.extract_source_context(&restricted_file, 1, 2);
5109 assert!(context.is_none()); let mut perms = fs::metadata(&restricted_file).unwrap().permissions();
5113 perms.set_mode(0o644);
5114 fs::set_permissions(&restricted_file, perms).unwrap();
5115 }
5116
5117 #[tokio::test]
5118 async fn test_node_info_with_context_creation() {
5119 use std::fs;
5120 use tempfile::TempDir;
5121
5122 let temp_dir = TempDir::new().expect("Failed to create temp dir");
5123 let test_file = temp_dir.path().join("test.py");
5124
5125 fs::write(
5127 &test_file,
5128 r#"# Test file
5129def example_function():
5130 """An example function."""
5131 return "hello"
5132"#,
5133 )
5134 .unwrap();
5135
5136 let server_arc = create_test_server().await;
5137 let tool_manager = ToolManager::new(server_arc);
5138
5139 let span = codeprism_core::ast::Span::new(0, 20, 2, 2, 1, 21);
5141 let node = codeprism_core::ast::Node::new(
5142 "test_repo",
5143 codeprism_core::ast::NodeKind::Function,
5144 "example_function".to_string(),
5145 codeprism_core::ast::Language::Python,
5146 test_file.clone(),
5147 span,
5148 );
5149
5150 let node_info = tool_manager.create_node_info_with_context(&node, 2);
5152
5153 assert_eq!(node_info["name"], "example_function");
5155 assert_eq!(node_info["kind"], "Function");
5156 assert_eq!(node_info["language"], "Python");
5157
5158 assert!(node_info["source_context"].is_object());
5160 assert_eq!(node_info["source_context"]["target_line"], 2);
5161
5162 let lines = node_info["source_context"]["lines"].as_array().unwrap();
5163 assert!(!lines.is_empty());
5164
5165 let has_target = lines.iter().any(|line| line["is_target"] == true);
5167 assert!(has_target);
5168 }
5169
5170 #[tokio::test]
5171 async fn test_new_tools_edge_cases() {
5172 let server = create_test_server().await;
5173 let manager = ToolManager::new(server);
5174
5175 let empty_context =
5177 manager.extract_source_context(std::path::Path::new("nonexistent.txt"), 1, 5);
5178 assert!(empty_context.is_none());
5179
5180 let invalid_context = manager.extract_source_context(std::path::Path::new("test.py"), 0, 5);
5182 assert!(invalid_context.is_none());
5183 }
5184
5185 #[tokio::test]
5186 async fn test_analyze_complexity_tool() {
5187 let server = create_test_server().await;
5188 let manager = ToolManager::new(server.clone());
5189
5190 let args = serde_json::json!({
5192 "target": "test_file.py",
5193 "metrics": ["cyclomatic"],
5194 "threshold_warnings": true
5195 });
5196
5197 let result = manager
5198 .analyze_complexity(&*server.read().await, Some(args))
5199 .await;
5200 assert!(result.is_ok());
5201
5202 let call_result = result.unwrap();
5203 assert_eq!(call_result.is_error, Some(true)); assert!(!call_result.content.is_empty());
5205 }
5206
5207 #[tokio::test]
5208 async fn test_analyze_complexity_all_metrics() {
5209 let server = create_test_server().await;
5210 let manager = ToolManager::new(server.clone());
5211
5212 let args = serde_json::json!({
5214 "target": "test_file.py",
5215 "metrics": ["all"],
5216 "threshold_warnings": false
5217 });
5218
5219 let result = manager
5220 .analyze_complexity(&*server.read().await, Some(args))
5221 .await;
5222 assert!(result.is_ok());
5223 }
5224
5225 #[tokio::test]
5226 async fn test_analyze_complexity_missing_target() {
5227 let server = create_test_server().await;
5228 let manager = ToolManager::new(server.clone());
5229
5230 let args = serde_json::json!({
5232 "metrics": ["cyclomatic"]
5233 });
5234
5235 let result = manager
5236 .analyze_complexity(&*server.read().await, Some(args))
5237 .await;
5238 assert!(result.is_ok());
5239
5240 let call_result = result.unwrap();
5241 assert_eq!(call_result.is_error, Some(true));
5242 }
5243
5244 #[tokio::test]
5245 async fn test_find_duplicates_tool() {
5246 let server = create_test_server().await;
5247 let manager = ToolManager::new(server.clone());
5248
5249 let args = serde_json::json!({
5251 "similarity_threshold": 0.8,
5252 "min_lines": 3,
5253 "scope": "repository"
5254 });
5255
5256 let result = manager
5257 .find_duplicates(&*server.read().await, Some(args))
5258 .await;
5259 assert!(result.is_ok());
5260
5261 let call_result = result.unwrap();
5262 assert_eq!(call_result.is_error, Some(false)); assert!(!call_result.content.is_empty());
5264 }
5265
5266 #[tokio::test]
5267 async fn test_find_duplicates_default_params() {
5268 let server = create_test_server().await;
5269 let manager = ToolManager::new(server.clone());
5270
5271 let result = manager.find_duplicates(&*server.read().await, None).await;
5273 assert!(result.is_ok());
5274
5275 let call_result = result.unwrap();
5276 assert!(!call_result.content.is_empty());
5277 }
5278
5279 #[tokio::test]
5280 async fn test_calculate_cyclomatic_complexity() {
5281 let server = create_test_server().await;
5282 let manager = ToolManager::new(server);
5283
5284 let simple_code = "def simple_func():\n return 42";
5286 let complexity = manager.calculate_cyclomatic_complexity(simple_code);
5287 assert_eq!(complexity, 1); let complex_code = r#"
5291def complex_func(x):
5292 if x > 0:
5293 return x
5294 elif x < 0:
5295 return -x
5296 else:
5297 return 0
5298"#;
5299 let complexity = manager.calculate_cyclomatic_complexity(complex_code);
5300 assert!(complexity > 1); }
5302
5303 #[tokio::test]
5304 async fn test_calculate_cognitive_complexity() {
5305 let server = create_test_server().await;
5306 let manager = ToolManager::new(server);
5307
5308 let simple_code = "def simple_func():\n return 42";
5310 let complexity = manager.calculate_cognitive_complexity(simple_code);
5311 assert_eq!(complexity, 0); let nested_code = r#"
5315def nested_func(x):
5316 if x > 0:
5317 for i in range(x):
5318 if i % 2 == 0:
5319 print(i)
5320"#;
5321 let complexity = manager.calculate_cognitive_complexity(nested_code);
5322 assert!(complexity > 0); }
5324
5325 #[tokio::test]
5326 async fn test_calculate_halstead_metrics() {
5327 let server = create_test_server().await;
5328 let manager = ToolManager::new(server);
5329
5330 let code = "x = a + b * c";
5331 let (volume, difficulty, effort) = manager.calculate_halstead_metrics(code);
5332
5333 assert!(volume > 0.0);
5334 assert!(difficulty > 0.0);
5335 assert!(effort > 0.0);
5336 assert!(effort >= difficulty * volume); }
5338
5339 #[tokio::test]
5340 async fn test_calculate_maintainability_index() {
5341 let server = create_test_server().await;
5342 let manager = ToolManager::new(server);
5343
5344 let simple_code = "def simple():\n return 42";
5345 let mi = manager.calculate_maintainability_index(simple_code, 2);
5346
5347 assert!(mi >= 0.0, "MI should be >= 0, got {}", mi);
5348 assert!(mi <= 100.0, "MI should be <= 100, got {}", mi);
5349
5350 let complex_code = r#"
5352def complex_function(a, b, c, d, e):
5353 if a > b:
5354 if c > d:
5355 if e > a:
5356 for i in range(100):
5357 if i % 2 == 0:
5358 result = i * a + b * c + d * e
5359 if result > 1000:
5360 return result
5361 return 0
5362"#;
5363 let mi_complex = manager.calculate_maintainability_index(complex_code, 10);
5364
5365 let (volume_simple, _difficulty_simple, _effort_simple) =
5367 manager.calculate_halstead_metrics(simple_code);
5368 let cyclomatic_simple = manager.calculate_cyclomatic_complexity(simple_code);
5369
5370 let (volume_complex, _difficulty_complex, _effort_complex) =
5371 manager.calculate_halstead_metrics(complex_code);
5372 let cyclomatic_complex = manager.calculate_cyclomatic_complexity(complex_code);
5373
5374 assert!(
5376 mi_complex >= 0.0,
5377 "Complex MI should be >= 0, got {}",
5378 mi_complex
5379 );
5380 assert!(
5381 mi_complex <= 100.0,
5382 "Complex MI should be <= 100, got {}",
5383 mi_complex
5384 );
5385
5386 assert!(
5388 cyclomatic_complex > cyclomatic_simple,
5389 "Complex code should have higher cyclomatic complexity: {} vs {}",
5390 cyclomatic_complex,
5391 cyclomatic_simple
5392 );
5393 assert!(
5394 volume_complex > volume_simple,
5395 "Complex code should have higher volume: {} vs {}",
5396 volume_complex,
5397 volume_simple
5398 );
5399
5400 assert!(mi_complex < mi,
5402 "Complex code should have lower MI: {} vs {} (simple: volume={}, cyclomatic={}, complex: volume={}, cyclomatic={})",
5403 mi_complex, mi, volume_simple, cyclomatic_simple, volume_complex, cyclomatic_complex);
5404 }
5405
5406 #[tokio::test]
5407 async fn test_calculate_content_similarity() {
5408 let server = create_test_server().await;
5409 let manager = ToolManager::new(server);
5410
5411 let content1 = "line 1\nline 2\nline 3";
5413 let content2 = "line 1\nline 2\nline 3";
5414 let similarity = manager.calculate_content_similarity(content1, content2);
5415 assert_eq!(similarity, 1.0);
5416
5417 let content3 = "different\ncontent\nhere";
5419 let similarity2 = manager.calculate_content_similarity(content1, content3);
5420 assert_eq!(similarity2, 0.0);
5421
5422 let content4 = "line 1\nline 2\ndifferent line";
5424 let similarity3 = manager.calculate_content_similarity(content1, content4);
5425 assert!(similarity3 > 0.0 && similarity3 < 1.0);
5426 }
5427
5428 #[tokio::test]
5429 async fn test_complexity_tool_integration() {
5430 use std::fs;
5431 use tempfile::TempDir;
5432
5433 let temp_dir = TempDir::new().unwrap();
5434 let file_path = temp_dir.path().join("test.py");
5435
5436 fs::write(
5438 &file_path,
5439 r#"
5440def test_function(x, y):
5441 if x > 0:
5442 result = x + y
5443 if y > 0:
5444 result *= 2
5445 return result
5446 else:
5447 return 0
5448"#,
5449 )
5450 .unwrap();
5451
5452 let server = create_test_server().await;
5453 let manager = ToolManager::new(server);
5454
5455 let result = manager.analyze_file_complexity(&file_path, &["all".to_string()], true);
5457 assert!(result.is_ok());
5458
5459 let complexity_data = result.unwrap();
5460 assert!(
5461 complexity_data["metrics"]["cyclomatic_complexity"]["value"]
5462 .as_u64()
5463 .unwrap()
5464 > 1
5465 );
5466 assert!(
5467 complexity_data["metrics"]["cognitive_complexity"]["value"]
5468 .as_u64()
5469 .unwrap()
5470 > 0
5471 );
5472 }
5473
5474 #[tokio::test]
5475 async fn test_tool_list_includes_new_tools() {
5476 let server = create_test_server().await;
5477 let manager = ToolManager::new(server);
5478 let params = crate::tools::ListToolsParams { cursor: None };
5479
5480 let result = manager.list_tools(params).await;
5481 assert!(result.is_ok());
5482
5483 let tools_result = result.unwrap();
5484 let tool_names: Vec<&String> = tools_result.tools.iter().map(|t| &t.name).collect();
5485
5486 assert!(tool_names.contains(&&"analyze_complexity".to_string()));
5488 assert!(tool_names.contains(&&"find_duplicates".to_string()));
5489
5490 assert!(tools_result.tools.len() >= 8);
5492 }
5493
5494 #[tokio::test]
5495 async fn test_new_tools_call_routing() {
5496 let server = create_test_server().await;
5497 let manager = ToolManager::new(server);
5498 let server = create_test_server().await;
5499
5500 let complexity_params = crate::tools::CallToolParams {
5502 name: "analyze_complexity".to_string(),
5503 arguments: Some(serde_json::json!({
5504 "target": "test.py",
5505 "metrics": ["cyclomatic"]
5506 })),
5507 };
5508
5509 let result = manager.call_tool(complexity_params).await;
5510 assert!(result.is_ok());
5511
5512 let duplicates_params = crate::tools::CallToolParams {
5514 name: "find_duplicates".to_string(),
5515 arguments: Some(serde_json::json!({
5516 "similarity_threshold": 0.8
5517 })),
5518 };
5519
5520 let result2 = manager.call_tool(duplicates_params).await;
5521 assert!(result2.is_ok());
5522 }
5523
5524 #[tokio::test]
5525 async fn test_detect_patterns_tool() {
5526 let server = create_test_server().await;
5527 let manager = ToolManager::new(server.clone());
5528
5529 let params = crate::tools::CallToolParams {
5531 name: "detect_patterns".to_string(),
5532 arguments: Some(serde_json::json!({
5533 "scope": "repository",
5534 "pattern_types": ["design_patterns"],
5535 "confidence_threshold": 0.8
5536 })),
5537 };
5538
5539 let result = manager.call_tool(params).await;
5540 assert!(result.is_ok());
5541
5542 let call_result = result.unwrap();
5543 assert_eq!(call_result.is_error, Some(false));
5544 assert!(!call_result.content.is_empty());
5545
5546 if let ToolContent::Text { text } = &call_result.content[0] {
5548 let parsed: serde_json::Value = serde_json::from_str(text).unwrap();
5549 assert!(parsed["patterns"].is_array());
5550 assert!(parsed["summary"].is_object());
5551 assert!(parsed["analysis_successful"].as_bool().unwrap_or(false));
5552 }
5553 }
5554
5555 #[tokio::test]
5556 async fn test_analyze_transitive_dependencies_tool() {
5557 let server = create_test_server().await;
5558 let manager = ToolManager::new(server.clone());
5559
5560 let params = crate::tools::CallToolParams {
5562 name: "analyze_transitive_dependencies".to_string(),
5563 arguments: Some(serde_json::json!({
5564 "target": "test_file.py",
5565 "max_depth": 3,
5566 "detect_cycles": true,
5567 "dependency_types": ["calls", "imports"]
5568 })),
5569 };
5570
5571 let result = manager.call_tool(params).await;
5572 assert!(result.is_ok());
5573
5574 let call_result = result.unwrap();
5575 assert_eq!(call_result.is_error, Some(false));
5576
5577 if let ToolContent::Text { text } = &call_result.content[0] {
5579 let parsed: serde_json::Value = serde_json::from_str(text).unwrap();
5580 assert!(parsed["target"].is_string());
5581 assert!(parsed["analysis"].is_object());
5582 assert!(parsed["parameters"].is_object());
5583 assert!(parsed["analysis_successful"].as_bool().unwrap_or(false));
5584 }
5585 }
5586
5587 #[tokio::test]
5588 async fn test_new_phase2_tools_in_list() {
5589 let server = create_test_server().await;
5590 let manager = ToolManager::new(server);
5591 let params = crate::tools::ListToolsParams { cursor: None };
5592
5593 let result = manager.list_tools(params).await;
5594 assert!(result.is_ok());
5595
5596 let tools_result = result.unwrap();
5597 let tool_names: Vec<&String> = tools_result.tools.iter().map(|t| &t.name).collect();
5598
5599 assert!(tool_names.contains(&&"detect_patterns".to_string()));
5601 assert!(tool_names.contains(&&"analyze_transitive_dependencies".to_string()));
5602
5603 assert!(tools_result.tools.len() >= 13); }
5606
5607 #[tokio::test]
5608 async fn test_trace_data_flow_tool() {
5609 let server = create_test_server().await;
5610 let tool_manager = ToolManager::new(server);
5611
5612 let params = CallToolParams {
5614 name: "trace_data_flow".to_string(),
5615 arguments: Some(serde_json::json!({})),
5616 };
5617
5618 let result = tool_manager.call_tool(params).await;
5619 assert!(result.is_err()); let params = CallToolParams {
5623 name: "trace_data_flow".to_string(),
5624 arguments: Some(serde_json::json!({
5625 "variable_or_parameter": "deadbeef12345678", "direction": "forward",
5627 "max_depth": 5
5628 })),
5629 };
5630
5631 let result = tool_manager.call_tool(params).await;
5632 match result {
5634 Ok(tool_result) => {
5635 if let ToolContent::Text { text } = &tool_result.content[0] {
5637 let flow_result: serde_json::Value = serde_json::from_str(text).unwrap();
5638 assert!(flow_result.is_object());
5640 }
5641 }
5642 Err(_) => {
5643 assert!(true);
5645 }
5646 }
5647 }
5648
5649 #[tokio::test]
5650 async fn test_phase3_tools_in_list() {
5651 let server = create_test_server().await;
5652 let tool_manager = ToolManager::new(server);
5653
5654 let result = tool_manager
5655 .list_tools(ListToolsParams { cursor: None })
5656 .await;
5657 assert!(result.is_ok());
5658
5659 let tools_result = result.unwrap();
5660 let tool_names: Vec<String> = tools_result.tools.iter().map(|t| t.name.clone()).collect();
5661
5662 assert!(tool_names.contains(&"trace_data_flow".to_string()));
5664
5665 let trace_data_flow_tool = tools_result
5667 .tools
5668 .iter()
5669 .find(|t| t.name == "trace_data_flow")
5670 .unwrap();
5671
5672 let schema = trace_data_flow_tool.input_schema.as_object().unwrap();
5673 assert!(schema.contains_key("properties"));
5674 assert!(schema.contains_key("required"));
5675
5676 let required = schema.get("required").unwrap().as_array().unwrap();
5677 assert!(required.contains(&serde_json::Value::String(
5678 "variable_or_parameter".to_string()
5679 )));
5680 }
5681
5682 #[tokio::test]
5683 async fn test_find_unused_code_tool() {
5684 let server = create_test_server().await;
5685 let tool_manager = ToolManager::new(server);
5686
5687 let params = CallToolParams {
5689 name: "find_unused_code".to_string(),
5690 arguments: Some(serde_json::json!({})),
5691 };
5692
5693 let result = tool_manager.call_tool(params).await;
5694 assert!(result.is_ok());
5695
5696 let tool_result = result.unwrap();
5697 assert_eq!(tool_result.is_error, Some(false));
5698
5699 if let ToolContent::Text { text } = &tool_result.content[0] {
5700 let unused_result: serde_json::Value = serde_json::from_str(text).unwrap();
5701 assert!(unused_result.is_object());
5703 assert!(unused_result.get("scope").is_some());
5704 assert!(unused_result.get("unused_code").is_some());
5705 assert!(unused_result.get("summary").is_some());
5706 assert!(unused_result.get("recommendations").is_some());
5707 }
5708
5709 let params = CallToolParams {
5711 name: "find_unused_code".to_string(),
5712 arguments: Some(serde_json::json!({
5713 "analyze_types": ["functions", "classes"],
5714 "confidence_threshold": 0.8,
5715 "consider_external_apis": false
5716 })),
5717 };
5718
5719 let result = tool_manager.call_tool(params).await;
5720 assert!(result.is_ok());
5721 }
5722
5723 #[tokio::test]
5724 async fn test_phase3_unused_code_tools_in_list() {
5725 let server = create_test_server().await;
5726 let tool_manager = ToolManager::new(server);
5727
5728 let result = tool_manager
5729 .list_tools(ListToolsParams { cursor: None })
5730 .await;
5731 assert!(result.is_ok());
5732
5733 let tools_result = result.unwrap();
5734 let tool_names: Vec<String> = tools_result.tools.iter().map(|t| t.name.clone()).collect();
5735
5736 assert!(tool_names.contains(&"trace_data_flow".to_string()));
5738 assert!(tool_names.contains(&"find_unused_code".to_string()));
5739
5740 let find_unused_code_tool = tools_result
5742 .tools
5743 .iter()
5744 .find(|t| t.name == "find_unused_code")
5745 .unwrap();
5746
5747 let schema = find_unused_code_tool.input_schema.as_object().unwrap();
5748 assert!(schema.contains_key("properties"));
5749
5750 if let Some(required) = schema.get("required") {
5752 assert!(required.as_array().unwrap().is_empty());
5753 }
5754 }
5755
5756 #[tokio::test]
5757 async fn test_analyze_security_tool() {
5758 let server = create_test_server().await;
5759 let tool_manager = ToolManager::new(server);
5760
5761 let params = CallToolParams {
5762 name: "analyze_security".to_string(),
5763 arguments: Some(serde_json::json!({
5764 "scope": "repository",
5765 "vulnerability_types": ["injection", "authentication"],
5766 "severity_threshold": "medium",
5767 "include_data_flow_analysis": true,
5768 "check_external_dependencies": true
5769 })),
5770 };
5771
5772 let result = tool_manager.call_tool(params).await;
5773 assert!(result.is_ok());
5774
5775 let tool_result = result.unwrap();
5776 assert!(tool_result.is_error.is_none() || !tool_result.is_error.unwrap());
5777 assert!(!tool_result.content.is_empty());
5778
5779 if let ToolContent::Text { text } = &tool_result.content[0] {
5781 let parsed: serde_json::Value = serde_json::from_str(text).unwrap();
5782 assert!(parsed.get("scope").is_some());
5783 assert!(parsed.get("summary").is_some());
5784 assert!(parsed.get("vulnerabilities").is_some());
5785 assert!(parsed.get("recommendations").is_some());
5786 assert!(parsed.get("analysis_parameters").is_some());
5787 }
5788 }
5789
5790 #[tokio::test]
5791 async fn test_analyze_security_default_params() {
5792 let server = create_test_server().await;
5793 let tool_manager = ToolManager::new(server);
5794
5795 let params = CallToolParams {
5796 name: "analyze_security".to_string(),
5797 arguments: Some(serde_json::json!({})),
5798 };
5799
5800 let result = tool_manager.call_tool(params).await;
5801 assert!(result.is_ok());
5802
5803 let tool_result = result.unwrap();
5804 assert!(tool_result.is_error.is_none() || !tool_result.is_error.unwrap());
5805 }
5806
5807 #[tokio::test]
5808 async fn test_analyze_security_specific_vulnerability_types() {
5809 let server = create_test_server().await;
5810 let tool_manager = ToolManager::new(server);
5811
5812 let params = CallToolParams {
5813 name: "analyze_security".to_string(),
5814 arguments: Some(serde_json::json!({
5815 "vulnerability_types": ["injection", "crypto_issues"],
5816 "severity_threshold": "high",
5817 "include_data_flow_analysis": false
5818 })),
5819 };
5820
5821 let result = tool_manager.call_tool(params).await;
5822 assert!(result.is_ok());
5823
5824 let tool_result = result.unwrap();
5825 assert!(tool_result.is_error.is_none() || !tool_result.is_error.unwrap());
5826
5827 if let ToolContent::Text { text } = &tool_result.content[0] {
5829 let parsed: serde_json::Value = serde_json::from_str(text).unwrap();
5830 let vulnerabilities = parsed.get("vulnerabilities").unwrap().as_array().unwrap();
5831
5832 for vuln in vulnerabilities {
5834 let vuln_type = vuln.get("type").unwrap().as_str().unwrap();
5835 assert!(
5836 vuln_type.to_lowercase().contains("injection")
5837 || vuln_type.to_lowercase().contains("crypto")
5838 );
5839 }
5840 }
5841 }
5842
5843 #[tokio::test]
5844 async fn test_phase4_security_tools_in_list() {
5845 let server = create_test_server().await;
5846 let tool_manager = ToolManager::new(server);
5847
5848 let result = tool_manager
5849 .list_tools(ListToolsParams { cursor: None })
5850 .await;
5851 assert!(result.is_ok());
5852
5853 let tools_result = result.unwrap();
5854 let tool_names: Vec<String> = tools_result.tools.iter().map(|t| t.name.clone()).collect();
5855
5856 assert!(tool_names.contains(&"analyze_security".to_string()));
5858
5859 let analyze_security_tool = tools_result
5861 .tools
5862 .iter()
5863 .find(|t| t.name == "analyze_security")
5864 .unwrap();
5865
5866 let schema = analyze_security_tool.input_schema.as_object().unwrap();
5867 assert!(schema.contains_key("properties"));
5868
5869 let properties = schema.get("properties").unwrap().as_object().unwrap();
5870 assert!(properties.contains_key("scope"));
5871 assert!(properties.contains_key("vulnerability_types"));
5872 assert!(properties.contains_key("severity_threshold"));
5873 assert!(properties.contains_key("include_data_flow_analysis"));
5874 assert!(properties.contains_key("check_external_dependencies"));
5875 assert!(properties.contains_key("exclude_patterns"));
5876
5877 if let Some(required) = schema.get("required") {
5879 assert!(required.as_array().unwrap().is_empty());
5880 }
5881 }
5882
5883 #[tokio::test]
5884 async fn test_analyze_security_severity_filtering() {
5885 let server = create_test_server().await;
5886 let tool_manager = ToolManager::new(server);
5887
5888 let params_low = CallToolParams {
5890 name: "analyze_security".to_string(),
5891 arguments: Some(serde_json::json!({
5892 "severity_threshold": "low",
5893 "vulnerability_types": ["all"]
5894 })),
5895 };
5896
5897 let result_low = tool_manager.call_tool(params_low).await;
5898 assert!(result_low.is_ok());
5899
5900 let params_critical = CallToolParams {
5902 name: "analyze_security".to_string(),
5903 arguments: Some(serde_json::json!({
5904 "severity_threshold": "critical",
5905 "vulnerability_types": ["all"]
5906 })),
5907 };
5908
5909 let result_critical = tool_manager.call_tool(params_critical).await;
5910 assert!(result_critical.is_ok());
5911
5912 let tool_result_low = result_low.unwrap();
5914 let tool_result_critical = result_critical.unwrap();
5915
5916 assert!(tool_result_low.is_error.is_none() || !tool_result_low.is_error.unwrap());
5917 assert!(tool_result_critical.is_error.is_none() || !tool_result_critical.is_error.unwrap());
5918 }
5919
5920 #[tokio::test]
5921 async fn test_analyze_performance_tool() {
5922 let server = create_test_server().await;
5923 let tool_manager = ToolManager::new(server);
5924
5925 let params = CallToolParams {
5926 name: "analyze_performance".to_string(),
5927 arguments: Some(serde_json::json!({
5928 "scope": "repository",
5929 "analysis_types": ["time_complexity", "memory_usage"],
5930 "complexity_threshold": "medium",
5931 "include_algorithmic_analysis": true,
5932 "detect_bottlenecks": true
5933 })),
5934 };
5935
5936 let result = tool_manager.call_tool(params).await;
5937 assert!(result.is_ok());
5938
5939 let tool_result = result.unwrap();
5940 assert!(tool_result.is_error.is_none() || !tool_result.is_error.unwrap());
5941 assert!(!tool_result.content.is_empty());
5942
5943 if let ToolContent::Text { text } = &tool_result.content[0] {
5945 let parsed: serde_json::Value = serde_json::from_str(text).unwrap();
5946 assert!(parsed.get("scope").is_some());
5947 assert!(parsed.get("summary").is_some());
5948 assert!(parsed.get("performance_issues").is_some());
5949 assert!(parsed.get("recommendations").is_some());
5950 assert!(parsed.get("analysis_parameters").is_some());
5951 }
5952 }
5953
5954 #[tokio::test]
5955 async fn test_analyze_performance_default_params() {
5956 let server = create_test_server().await;
5957 let tool_manager = ToolManager::new(server);
5958
5959 let params = CallToolParams {
5960 name: "analyze_performance".to_string(),
5961 arguments: Some(serde_json::json!({})),
5962 };
5963
5964 let result = tool_manager.call_tool(params).await;
5965 assert!(result.is_ok());
5966
5967 let tool_result = result.unwrap();
5968 assert!(tool_result.is_error.is_none() || !tool_result.is_error.unwrap());
5969 }
5970
5971 #[tokio::test]
5972 async fn test_analyze_performance_specific_analysis_types() {
5973 let server = create_test_server().await;
5974 let tool_manager = ToolManager::new(server);
5975
5976 let params = CallToolParams {
5977 name: "analyze_performance".to_string(),
5978 arguments: Some(serde_json::json!({
5979 "analysis_types": ["hot_spots", "scalability"],
5980 "complexity_threshold": "high",
5981 "include_algorithmic_analysis": false,
5982 "detect_bottlenecks": false
5983 })),
5984 };
5985
5986 let result = tool_manager.call_tool(params).await;
5987 assert!(result.is_ok());
5988
5989 let tool_result = result.unwrap();
5990 assert!(tool_result.is_error.is_none() || !tool_result.is_error.unwrap());
5991
5992 if let ToolContent::Text { text } = &tool_result.content[0] {
5994 let parsed: serde_json::Value = serde_json::from_str(text).unwrap();
5995 let issues = parsed
5996 .get("performance_issues")
5997 .unwrap()
5998 .as_array()
5999 .unwrap();
6000
6001 for issue in issues {
6003 let issue_category = issue.get("category").unwrap().as_str().unwrap();
6004 assert!(issue_category == "hot_spots" || issue_category == "scalability");
6005 }
6006 }
6007 }
6008
6009 #[tokio::test]
6010 async fn test_analyze_performance_complexity_filtering() {
6011 let server = create_test_server().await;
6012 let tool_manager = ToolManager::new(server);
6013
6014 let params_low = CallToolParams {
6016 name: "analyze_performance".to_string(),
6017 arguments: Some(serde_json::json!({
6018 "complexity_threshold": "low",
6019 "analysis_types": ["all"]
6020 })),
6021 };
6022
6023 let result_low = tool_manager.call_tool(params_low).await;
6024 assert!(result_low.is_ok());
6025
6026 let params_high = CallToolParams {
6028 name: "analyze_performance".to_string(),
6029 arguments: Some(serde_json::json!({
6030 "complexity_threshold": "high",
6031 "analysis_types": ["all"]
6032 })),
6033 };
6034
6035 let result_high = tool_manager.call_tool(params_high).await;
6036 assert!(result_high.is_ok());
6037
6038 let tool_result_low = result_low.unwrap();
6040 let tool_result_high = result_high.unwrap();
6041
6042 assert!(tool_result_low.is_error.is_none() || !tool_result_low.is_error.unwrap());
6043 assert!(tool_result_high.is_error.is_none() || !tool_result_high.is_error.unwrap());
6044 }
6045
6046 #[tokio::test]
6047 async fn test_phase4_performance_tools_in_list() {
6048 let server = create_test_server().await;
6049 let tool_manager = ToolManager::new(server);
6050
6051 let result = tool_manager
6052 .list_tools(ListToolsParams { cursor: None })
6053 .await;
6054 assert!(result.is_ok());
6055
6056 let tools_result = result.unwrap();
6057 let tool_names: Vec<String> = tools_result.tools.iter().map(|t| t.name.clone()).collect();
6058
6059 assert!(tool_names.contains(&"analyze_performance".to_string()));
6061
6062 let analyze_performance_tool = tools_result
6064 .tools
6065 .iter()
6066 .find(|t| t.name == "analyze_performance")
6067 .unwrap();
6068
6069 let schema = analyze_performance_tool.input_schema.as_object().unwrap();
6070 assert!(schema.contains_key("properties"));
6071
6072 let properties = schema.get("properties").unwrap().as_object().unwrap();
6073 assert!(properties.contains_key("scope"));
6074 assert!(properties.contains_key("analysis_types"));
6075 assert!(properties.contains_key("complexity_threshold"));
6076 assert!(properties.contains_key("include_algorithmic_analysis"));
6077 assert!(properties.contains_key("detect_bottlenecks"));
6078 assert!(properties.contains_key("exclude_patterns"));
6079
6080 if let Some(required) = schema.get("required") {
6082 assert!(required.as_array().unwrap().is_empty());
6083 }
6084 }
6085
6086 #[tokio::test]
6087 async fn test_analyze_api_surface_tool() {
6088 let server = create_test_server().await;
6089 let tool_manager = ToolManager::new(server);
6090
6091 let params = CallToolParams {
6092 name: "analyze_api_surface".to_string(),
6093 arguments: Some(serde_json::json!({
6094 "scope": "repository",
6095 "analysis_types": ["public_api", "documentation"],
6096 "api_version": "1.0.0",
6097 "include_private_apis": true,
6098 "check_documentation_coverage": true,
6099 "detect_breaking_changes": true
6100 })),
6101 };
6102
6103 let result = tool_manager.call_tool(params).await;
6104 assert!(result.is_ok());
6105
6106 let tool_result = result.unwrap();
6107 assert!(tool_result.is_error.is_none() || !tool_result.is_error.unwrap());
6108 assert!(!tool_result.content.is_empty());
6109
6110 if let ToolContent::Text { text } = &tool_result.content[0] {
6112 let parsed: serde_json::Value = serde_json::from_str(text).unwrap();
6113 assert!(parsed.get("scope").is_some());
6114 assert!(parsed.get("summary").is_some());
6115 assert!(parsed.get("api_issues").is_some());
6116 assert!(parsed.get("recommendations").is_some());
6117 assert!(parsed.get("analysis_parameters").is_some());
6118 }
6119 }
6120
6121 #[tokio::test]
6122 async fn test_analyze_api_surface_default_params() {
6123 let server = create_test_server().await;
6124 let tool_manager = ToolManager::new(server);
6125
6126 let params = CallToolParams {
6127 name: "analyze_api_surface".to_string(),
6128 arguments: Some(serde_json::json!({})),
6129 };
6130
6131 let result = tool_manager.call_tool(params).await;
6132 assert!(result.is_ok());
6133
6134 let tool_result = result.unwrap();
6135 assert!(tool_result.is_error.is_none() || !tool_result.is_error.unwrap());
6136 }
6137
6138 #[tokio::test]
6139 async fn test_analyze_api_surface_specific_analysis_types() {
6140 let server = create_test_server().await;
6141 let tool_manager = ToolManager::new(server);
6142
6143 let params = CallToolParams {
6144 name: "analyze_api_surface".to_string(),
6145 arguments: Some(serde_json::json!({
6146 "analysis_types": ["versioning", "breaking_changes"],
6147 "api_version": "2.1.0",
6148 "include_private_apis": false,
6149 "detect_breaking_changes": true
6150 })),
6151 };
6152
6153 let result = tool_manager.call_tool(params).await;
6154 assert!(result.is_ok());
6155
6156 let tool_result = result.unwrap();
6157 assert!(tool_result.is_error.is_none() || !tool_result.is_error.unwrap());
6158
6159 if let ToolContent::Text { text } = &tool_result.content[0] {
6161 let parsed: serde_json::Value = serde_json::from_str(text).unwrap();
6162 let issues = parsed.get("api_issues").unwrap().as_array().unwrap();
6163
6164 for issue in issues {
6166 let issue_category = issue.get("category").unwrap().as_str().unwrap();
6167 assert!(issue_category == "versioning" || issue_category == "breaking_changes");
6168 }
6169 }
6170 }
6171
6172 #[tokio::test]
6173 async fn test_analyze_api_surface_with_version() {
6174 let server = create_test_server().await;
6175 let tool_manager = ToolManager::new(server);
6176
6177 let params = CallToolParams {
6178 name: "analyze_api_surface".to_string(),
6179 arguments: Some(serde_json::json!({
6180 "analysis_types": ["compatibility", "versioning"],
6181 "api_version": "v1.2.3",
6182 "include_private_apis": false,
6183 "check_documentation_coverage": false
6184 })),
6185 };
6186
6187 let result = tool_manager.call_tool(params).await;
6188 assert!(result.is_ok());
6189
6190 let tool_result = result.unwrap();
6191 assert!(tool_result.is_error.is_none() || !tool_result.is_error.unwrap());
6192
6193 if let ToolContent::Text { text } = &tool_result.content[0] {
6195 let parsed: serde_json::Value = serde_json::from_str(text).unwrap();
6196 let analysis_params = parsed.get("analysis_parameters").unwrap();
6197 assert_eq!(
6198 analysis_params.get("api_version").unwrap().as_str(),
6199 Some("v1.2.3")
6200 );
6201 }
6202 }
6203
6204 #[tokio::test]
6205 async fn test_phase4_api_surface_tools_in_list() {
6206 let server = create_test_server().await;
6207 let tool_manager = ToolManager::new(server);
6208
6209 let result = tool_manager
6210 .list_tools(ListToolsParams { cursor: None })
6211 .await;
6212 assert!(result.is_ok());
6213
6214 let tools_result = result.unwrap();
6215 let tool_names: Vec<String> = tools_result.tools.iter().map(|t| t.name.clone()).collect();
6216
6217 assert!(tool_names.contains(&"analyze_api_surface".to_string()));
6219
6220 let analyze_api_surface_tool = tools_result
6222 .tools
6223 .iter()
6224 .find(|t| t.name == "analyze_api_surface")
6225 .unwrap();
6226
6227 let schema = analyze_api_surface_tool.input_schema.as_object().unwrap();
6228 assert!(schema.contains_key("properties"));
6229
6230 let properties = schema.get("properties").unwrap().as_object().unwrap();
6231 assert!(properties.contains_key("scope"));
6232 assert!(properties.contains_key("analysis_types"));
6233 assert!(properties.contains_key("api_version"));
6234 assert!(properties.contains_key("include_private_apis"));
6235 assert!(properties.contains_key("check_documentation_coverage"));
6236 assert!(properties.contains_key("detect_breaking_changes"));
6237 assert!(properties.contains_key("exclude_patterns"));
6238
6239 if let Some(required) = schema.get("required") {
6241 assert!(required.as_array().unwrap().is_empty());
6242 }
6243 }
6244
6245 #[tokio::test]
6246 async fn test_phase4_all_tools_integration() {
6247 let server = create_test_server().await;
6248 let tool_manager = ToolManager::new(server);
6249
6250 let result = tool_manager
6251 .list_tools(ListToolsParams { cursor: None })
6252 .await;
6253 assert!(result.is_ok());
6254
6255 let tools_result = result.unwrap();
6256 let tool_names: Vec<String> = tools_result.tools.iter().map(|t| t.name.clone()).collect();
6257
6258 assert!(tool_names.contains(&"analyze_security".to_string()));
6260 assert!(tool_names.contains(&"analyze_performance".to_string()));
6261 assert!(tool_names.contains(&"analyze_api_surface".to_string()));
6262
6263 assert!(tool_names.contains(&"trace_data_flow".to_string()));
6265 assert!(tool_names.contains(&"find_unused_code".to_string()));
6266
6267 assert_eq!(tools_result.tools.len(), 20);
6269 }
6270}
6271
6272impl ToolManager {
6273 async fn analyze_design_patterns(
6275 &self,
6276 server: &CodePrismMcpServer,
6277 pattern_types: &[String],
6278 confidence_threshold: f64,
6279 include_suggestions: bool,
6280 ) -> Result<Vec<serde_json::Value>> {
6281 let mut detected_patterns = Vec::new();
6282
6283 if pattern_types.contains(&"design_patterns".to_string())
6285 || pattern_types.contains(&"all".to_string())
6286 {
6287 let singleton_patterns = self
6288 .detect_singleton_pattern(server, confidence_threshold)
6289 .await?;
6290 detected_patterns.extend(singleton_patterns);
6291
6292 let factory_patterns = self
6293 .detect_factory_pattern(server, confidence_threshold)
6294 .await?;
6295 detected_patterns.extend(factory_patterns);
6296
6297 let observer_patterns = self
6298 .detect_observer_pattern(server, confidence_threshold)
6299 .await?;
6300 detected_patterns.extend(observer_patterns);
6301 }
6302
6303 if pattern_types.contains(&"anti_patterns".to_string())
6305 || pattern_types.contains(&"all".to_string())
6306 {
6307 let anti_patterns = self
6308 .detect_anti_patterns(server, confidence_threshold)
6309 .await?;
6310 detected_patterns.extend(anti_patterns);
6311 }
6312
6313 if pattern_types.contains(&"architectural_patterns".to_string())
6315 || pattern_types.contains(&"all".to_string())
6316 {
6317 let arch_patterns = self
6318 .detect_architectural_patterns(server, confidence_threshold)
6319 .await?;
6320 detected_patterns.extend(arch_patterns);
6321 }
6322
6323 if pattern_types.contains(&"metaprogramming_patterns".to_string())
6325 || pattern_types.contains(&"all".to_string())
6326 {
6327 let metaprogramming_patterns = self
6328 .detect_metaprogramming_patterns(server, confidence_threshold)
6329 .await?;
6330 detected_patterns.extend(metaprogramming_patterns);
6331 }
6332
6333 if include_suggestions {
6335 for pattern in &mut detected_patterns {
6336 if let Some(pattern_obj) = pattern.as_object_mut() {
6337 if let Some(pattern_type) = pattern_obj.get("type").and_then(|v| v.as_str()) {
6338 let suggestions = self.get_pattern_suggestions(pattern_type);
6339 pattern_obj.insert("suggestions".to_string(), suggestions.into());
6340 }
6341 }
6342 }
6343 }
6344
6345 Ok(detected_patterns)
6346 }
6347
6348 async fn perform_transitive_analysis(
6350 &self,
6351 server: &CodePrismMcpServer,
6352 target: &str,
6353 max_depth: usize,
6354 detect_cycles: bool,
6355 _include_external: bool,
6356 dependency_types: &[String],
6357 ) -> Result<serde_json::Value> {
6358 let target_nodes = if target.len() == 32 && target.chars().all(|c| c.is_ascii_hexdigit()) {
6360 if let Ok(node_id) = self.parse_node_id(target) {
6362 if let Some(node) = server.graph_store().get_node(&node_id) {
6363 vec![node]
6364 } else {
6365 return Ok(serde_json::json!({
6366 "error": "Node not found",
6367 "target": target
6368 }));
6369 }
6370 } else {
6371 return Ok(serde_json::json!({
6372 "error": "Invalid node ID format",
6373 "target": target
6374 }));
6375 }
6376 } else {
6377 let file_path = std::path::PathBuf::from(target);
6379 server.graph_store().get_nodes_in_file(&file_path)
6380 };
6381
6382 if target_nodes.is_empty() {
6383 return Ok(serde_json::json!({
6384 "error": "No nodes found for target",
6385 "target": target
6386 }));
6387 }
6388
6389 let mut analysis_results = Vec::new();
6390
6391 for target_node in &target_nodes {
6392 let dependencies = self
6393 .build_transitive_dependencies(server, &target_node.id, max_depth, dependency_types)
6394 .await?;
6395
6396 let mut cycles = Vec::new();
6397 if detect_cycles {
6398 cycles = self
6399 .detect_dependency_cycles(server, &target_node.id, &dependencies)
6400 .await?;
6401 }
6402
6403 let analysis = serde_json::json!({
6404 "target_node": {
6405 "id": target_node.id.to_hex(),
6406 "name": target_node.name,
6407 "kind": format!("{:?}", target_node.kind),
6408 "file": target_node.file.display().to_string(),
6409 "span": target_node.span
6410 },
6411 "transitive_dependencies": dependencies,
6412 "dependency_chains": self.build_dependency_chains(server, &target_node.id, max_depth).await?,
6413 "cycles": cycles,
6414 "statistics": {
6415 "total_dependencies": dependencies.len(),
6416 "max_depth_reached": self.calculate_max_depth(&dependencies),
6417 "cycles_detected": cycles.len()
6418 }
6419 });
6420
6421 analysis_results.push(analysis);
6422 }
6423
6424 Ok(serde_json::json!({
6425 "target_file_or_symbol": target,
6426 "analyses": analysis_results,
6427 "summary": {
6428 "total_nodes_analyzed": target_nodes.len(),
6429 "total_unique_dependencies": self.count_unique_dependencies(&analysis_results),
6430 "total_cycles_found": self.count_total_cycles(&analysis_results)
6431 }
6432 }))
6433 }
6434
6435 async fn detect_singleton_pattern(
6437 &self,
6438 server: &CodePrismMcpServer,
6439 confidence_threshold: f64,
6440 ) -> Result<Vec<serde_json::Value>> {
6441 let mut patterns = Vec::new();
6442 let classes = server
6443 .graph_store()
6444 .get_nodes_by_kind(codeprism_core::NodeKind::Class);
6445
6446 for class in classes {
6447 let mut confidence = 0.0;
6448 let mut indicators = Vec::new();
6449
6450 let methods = server.graph_store().get_outgoing_edges(&class.id);
6452 let has_private_constructor = methods.iter().any(|edge| {
6453 if let Some(target_node) = server.graph_store().get_node(&edge.target) {
6454 target_node.kind == codeprism_core::NodeKind::Method
6455 && target_node.name.contains("__init__")
6456 || target_node.name.contains("constructor")
6457 } else {
6458 false
6459 }
6460 });
6461
6462 if has_private_constructor {
6463 confidence += 0.3;
6464 indicators.push("Private constructor detected");
6465 }
6466
6467 let has_get_instance = methods.iter().any(|edge| {
6469 if let Some(target_node) = server.graph_store().get_node(&edge.target) {
6470 target_node.name.to_lowercase().contains("getinstance")
6471 || target_node.name.to_lowercase().contains("get_instance")
6472 } else {
6473 false
6474 }
6475 });
6476
6477 if has_get_instance {
6478 confidence += 0.4;
6479 indicators.push("getInstance method detected");
6480 }
6481
6482 let variables = server
6484 .graph_store()
6485 .get_nodes_by_kind(codeprism_core::NodeKind::Variable);
6486 let has_static_instance = variables.iter().any(|var| {
6487 var.file == class.file
6488 && (var.name.contains("instance") || var.name.contains("_instance"))
6489 });
6490
6491 if has_static_instance {
6492 confidence += 0.3;
6493 indicators.push("Static instance variable detected");
6494 }
6495
6496 if confidence >= confidence_threshold {
6497 patterns.push(serde_json::json!({
6498 "type": "Singleton",
6499 "category": "design_pattern",
6500 "confidence": confidence,
6501 "class": {
6502 "id": class.id.to_hex(),
6503 "name": class.name,
6504 "file": class.file.display().to_string(),
6505 "span": class.span
6506 },
6507 "indicators": indicators,
6508 "description": "Class appears to implement the Singleton design pattern"
6509 }));
6510 }
6511 }
6512
6513 Ok(patterns)
6514 }
6515
6516 async fn detect_factory_pattern(
6518 &self,
6519 server: &CodePrismMcpServer,
6520 confidence_threshold: f64,
6521 ) -> Result<Vec<serde_json::Value>> {
6522 let mut patterns = Vec::new();
6523 let classes = server
6524 .graph_store()
6525 .get_nodes_by_kind(codeprism_core::NodeKind::Class);
6526
6527 for class in classes {
6528 if class.name.to_lowercase().contains("factory") {
6529 let mut confidence = 0.5; let mut indicators = vec!["Factory in class name".to_string()];
6531
6532 let methods = server.graph_store().get_outgoing_edges(&class.id);
6534 let creation_methods = methods
6535 .iter()
6536 .filter(|edge| {
6537 if let Some(target_node) = server.graph_store().get_node(&edge.target) {
6538 let method_name = target_node.name.to_lowercase();
6539 method_name.contains("create")
6540 || method_name.contains("build")
6541 || method_name.contains("make")
6542 || method_name.contains("new")
6543 } else {
6544 false
6545 }
6546 })
6547 .count();
6548
6549 if creation_methods > 0 {
6550 confidence += 0.3;
6551 indicators.push(format!("{} creation methods detected", creation_methods));
6552 }
6553
6554 if confidence >= confidence_threshold {
6555 patterns.push(serde_json::json!({
6556 "type": "Factory",
6557 "category": "design_pattern",
6558 "confidence": confidence,
6559 "class": {
6560 "id": class.id.to_hex(),
6561 "name": class.name,
6562 "file": class.file.display().to_string(),
6563 "span": class.span
6564 },
6565 "indicators": indicators,
6566 "description": "Class appears to implement the Factory design pattern"
6567 }));
6568 }
6569 }
6570 }
6571
6572 Ok(patterns)
6573 }
6574
6575 async fn detect_observer_pattern(
6577 &self,
6578 server: &CodePrismMcpServer,
6579 confidence_threshold: f64,
6580 ) -> Result<Vec<serde_json::Value>> {
6581 let mut patterns = Vec::new();
6582 let classes = server
6583 .graph_store()
6584 .get_nodes_by_kind(codeprism_core::NodeKind::Class);
6585
6586 for class in classes {
6587 let mut confidence = 0.0;
6588 let mut indicators = Vec::new();
6589
6590 let methods = server.graph_store().get_outgoing_edges(&class.id);
6592 let observer_methods = methods
6593 .iter()
6594 .filter(|edge| {
6595 if let Some(target_node) = server.graph_store().get_node(&edge.target) {
6596 let method_name = target_node.name.to_lowercase();
6597 method_name.contains("notify")
6598 || method_name.contains("update")
6599 || method_name.contains("observe")
6600 || method_name.contains("subscribe")
6601 || method_name.contains("unsubscribe")
6602 } else {
6603 false
6604 }
6605 })
6606 .count();
6607
6608 if observer_methods > 0 {
6609 confidence += 0.4;
6610 indicators.push(format!(
6611 "{} observer-related methods detected",
6612 observer_methods
6613 ));
6614 }
6615
6616 let events = server
6618 .graph_store()
6619 .get_outgoing_edges(&class.id)
6620 .iter()
6621 .filter(|edge| edge.kind == codeprism_core::EdgeKind::Emits)
6622 .count();
6623
6624 if events > 0 {
6625 confidence += 0.3;
6626 indicators.push(format!("{} event emissions detected", events));
6627 }
6628
6629 if confidence >= confidence_threshold {
6630 patterns.push(serde_json::json!({
6631 "type": "Observer",
6632 "category": "design_pattern",
6633 "confidence": confidence,
6634 "class": {
6635 "id": class.id.to_hex(),
6636 "name": class.name,
6637 "file": class.file.display().to_string(),
6638 "span": class.span
6639 },
6640 "indicators": indicators,
6641 "description": "Class appears to implement the Observer design pattern"
6642 }));
6643 }
6644 }
6645
6646 Ok(patterns)
6647 }
6648
6649 async fn detect_anti_patterns(
6651 &self,
6652 server: &CodePrismMcpServer,
6653 confidence_threshold: f64,
6654 ) -> Result<Vec<serde_json::Value>> {
6655 let mut patterns = Vec::new();
6656
6657 let classes = server
6659 .graph_store()
6660 .get_nodes_by_kind(codeprism_core::NodeKind::Class);
6661 for class in classes {
6662 let methods = server.graph_store().get_outgoing_edges(&class.id);
6663 let method_count = methods.len();
6664
6665 if method_count > 20 {
6666 let confidence = ((method_count as f64 - 20.0) / 30.0).min(1.0);
6668 if confidence >= confidence_threshold {
6669 patterns.push(serde_json::json!({
6670 "type": "God Class",
6671 "category": "anti_pattern",
6672 "confidence": confidence,
6673 "class": {
6674 "id": class.id.to_hex(),
6675 "name": class.name,
6676 "file": class.file.display().to_string(),
6677 "span": class.span
6678 },
6679 "indicators": [format!("{} methods detected (threshold: 20)", method_count)],
6680 "description": "Class has too many responsibilities (God Class anti-pattern)",
6681 "severity": "high"
6682 }));
6683 }
6684 }
6685 }
6686
6687 let functions = server
6689 .graph_store()
6690 .get_nodes_by_kind(codeprism_core::NodeKind::Function);
6691 for function in functions {
6692 let lines = function.span.end_line - function.span.start_line + 1;
6693 if lines > 50 {
6694 let confidence = ((lines as f64 - 50.0) / 100.0).min(1.0);
6696 if confidence >= confidence_threshold {
6697 patterns.push(serde_json::json!({
6698 "type": "Long Method",
6699 "category": "anti_pattern",
6700 "confidence": confidence,
6701 "function": {
6702 "id": function.id.to_hex(),
6703 "name": function.name,
6704 "file": function.file.display().to_string(),
6705 "span": function.span
6706 },
6707 "indicators": [format!("{} lines of code (threshold: 50)", lines)],
6708 "description": "Method is too long and complex",
6709 "severity": "medium"
6710 }));
6711 }
6712 }
6713 }
6714
6715 Ok(patterns)
6716 }
6717
6718 async fn detect_architectural_patterns(
6720 &self,
6721 server: &CodePrismMcpServer,
6722 confidence_threshold: f64,
6723 ) -> Result<Vec<serde_json::Value>> {
6724 let mut patterns = Vec::new();
6725
6726 let classes = server
6728 .graph_store()
6729 .get_nodes_by_kind(codeprism_core::NodeKind::Class);
6730 let mut controllers = 0;
6731 let mut models = 0;
6732 let mut views = 0;
6733
6734 for class in &classes {
6735 let name_lower = class.name.to_lowercase();
6736 if name_lower.contains("controller") {
6737 controllers += 1;
6738 } else if name_lower.contains("model") {
6739 models += 1;
6740 } else if name_lower.contains("view") {
6741 views += 1;
6742 }
6743 }
6744
6745 if controllers > 0 && models > 0 && views > 0 {
6746 let confidence =
6747 ((controllers + models + views) as f64 / classes.len() as f64).min(1.0);
6748 if confidence >= confidence_threshold {
6749 patterns.push(serde_json::json!({
6750 "type": "MVC (Model-View-Controller)",
6751 "category": "architectural_pattern",
6752 "confidence": confidence,
6753 "indicators": [
6754 format!("{} Controllers", controllers),
6755 format!("{} Models", models),
6756 format!("{} Views", views)
6757 ],
6758 "description": "Application appears to follow MVC architectural pattern"
6759 }));
6760 }
6761 }
6762
6763 let repository_classes = classes
6765 .iter()
6766 .filter(|c| {
6767 c.name.to_lowercase().contains("repository")
6768 || c.name.to_lowercase().contains("repo")
6769 })
6770 .count();
6771
6772 if repository_classes > 0 {
6773 let confidence = (repository_classes as f64 / classes.len() as f64 * 10.0).min(1.0);
6774 if confidence >= confidence_threshold {
6775 patterns.push(serde_json::json!({
6776 "type": "Repository Pattern",
6777 "category": "architectural_pattern",
6778 "confidence": confidence,
6779 "indicators": [format!("{} Repository classes", repository_classes)],
6780 "description": "Data access appears to follow Repository pattern"
6781 }));
6782 }
6783 }
6784
6785 Ok(patterns)
6786 }
6787
6788 async fn detect_metaprogramming_patterns(
6790 &self,
6791 server: &CodePrismMcpServer,
6792 confidence_threshold: f64,
6793 ) -> Result<Vec<serde_json::Value>> {
6794 let mut patterns = Vec::new();
6795
6796 let registry_patterns = self
6798 .detect_registry_metaclass_pattern(server, confidence_threshold)
6799 .await?;
6800 patterns.extend(registry_patterns);
6801
6802 let attribute_injection_patterns = self
6804 .detect_attribute_injection_metaclass_pattern(server, confidence_threshold)
6805 .await?;
6806 patterns.extend(attribute_injection_patterns);
6807
6808 let decorator_factory_patterns = self
6810 .detect_decorator_factory_pattern(server, confidence_threshold)
6811 .await?;
6812 patterns.extend(decorator_factory_patterns);
6813
6814 let descriptor_patterns = self
6816 .detect_property_descriptor_pattern(server, confidence_threshold)
6817 .await?;
6818 patterns.extend(descriptor_patterns);
6819
6820 let dynamic_attr_patterns = self
6822 .detect_dynamic_attribute_pattern(server, confidence_threshold)
6823 .await?;
6824 patterns.extend(dynamic_attr_patterns);
6825
6826 let mixin_patterns = self
6828 .detect_mixin_pattern(server, confidence_threshold)
6829 .await?;
6830 patterns.extend(mixin_patterns);
6831
6832 let abc_patterns = self
6834 .detect_abstract_base_class_pattern(server, confidence_threshold)
6835 .await?;
6836 patterns.extend(abc_patterns);
6837
6838 let protocol_patterns = self
6840 .detect_protocol_pattern(server, confidence_threshold)
6841 .await?;
6842 patterns.extend(protocol_patterns);
6843
6844 Ok(patterns)
6845 }
6846
6847 async fn detect_registry_metaclass_pattern(
6849 &self,
6850 server: &CodePrismMcpServer,
6851 confidence_threshold: f64,
6852 ) -> Result<Vec<serde_json::Value>> {
6853 let mut patterns = Vec::new();
6854 let classes = server
6855 .graph_store()
6856 .get_nodes_by_kind(codeprism_core::NodeKind::Class);
6857
6858 for class in classes {
6859 if let Ok(inheritance_info) = server.graph_query().get_inheritance_info(&class.id) {
6861 if inheritance_info.is_metaclass {
6862 let mut confidence = 0.5; let mut indicators = vec!["Is a metaclass".to_string()];
6864
6865 if class.name.to_lowercase().contains("registry")
6867 || class.name.to_lowercase().contains("manager")
6868 || class.name.ends_with("Metaclass")
6869 {
6870 confidence += 0.2;
6871 indicators.push("Registry-like naming pattern".to_string());
6872 }
6873
6874 let affected_classes = inheritance_info.subclasses.len();
6876 if affected_classes > 2 {
6877 confidence += 0.3;
6878 indicators.push(format!("Used by {} classes", affected_classes));
6879 }
6880
6881 if !inheritance_info.dynamic_attributes.is_empty() {
6883 confidence += 0.2;
6884 indicators.push(format!(
6885 "{} dynamic attributes created",
6886 inheritance_info.dynamic_attributes.len()
6887 ));
6888 }
6889
6890 if confidence >= confidence_threshold {
6891 patterns.push(serde_json::json!({
6892 "type": "Registry Metaclass",
6893 "category": "metaprogramming_pattern",
6894 "confidence": confidence,
6895 "metaclass": {
6896 "id": class.id.to_hex(),
6897 "name": class.name,
6898 "file": class.file.display().to_string(),
6899 "span": class.span
6900 },
6901 "affected_classes": affected_classes,
6902 "dynamic_attributes": inheritance_info.dynamic_attributes,
6903 "indicators": indicators,
6904 "description": "Metaclass that automatically registers classes and injects functionality"
6905 }));
6906 }
6907 }
6908 }
6909 }
6910
6911 Ok(patterns)
6912 }
6913
6914 async fn detect_attribute_injection_metaclass_pattern(
6916 &self,
6917 server: &CodePrismMcpServer,
6918 confidence_threshold: f64,
6919 ) -> Result<Vec<serde_json::Value>> {
6920 let mut patterns = Vec::new();
6921 let classes = server
6922 .graph_store()
6923 .get_nodes_by_kind(codeprism_core::NodeKind::Class);
6924
6925 for class in classes {
6926 if let Ok(inheritance_info) = server.graph_query().get_inheritance_info(&class.id) {
6927 if inheritance_info.is_metaclass && !inheritance_info.dynamic_attributes.is_empty()
6928 {
6929 let dynamic_count = inheritance_info.dynamic_attributes.len();
6930 let confidence = (dynamic_count as f64 / 10.0).min(1.0);
6931
6932 if confidence >= confidence_threshold {
6933 patterns.push(serde_json::json!({
6934 "type": "Attribute Injection Metaclass",
6935 "category": "metaprogramming_pattern",
6936 "confidence": confidence,
6937 "metaclass": {
6938 "id": class.id.to_hex(),
6939 "name": class.name,
6940 "file": class.file.display().to_string(),
6941 "span": class.span
6942 },
6943 "injected_attributes": inheritance_info.dynamic_attributes,
6944 "indicators": [format!("Injects {} dynamic attributes", dynamic_count)],
6945 "description": "Metaclass that automatically injects attributes into classes"
6946 }));
6947 }
6948 }
6949 }
6950 }
6951
6952 Ok(patterns)
6953 }
6954
6955 async fn detect_decorator_factory_pattern(
6957 &self,
6958 server: &CodePrismMcpServer,
6959 confidence_threshold: f64,
6960 ) -> Result<Vec<serde_json::Value>> {
6961 let mut patterns = Vec::new();
6962 let functions = server
6963 .graph_store()
6964 .get_nodes_by_kind(codeprism_core::NodeKind::Function);
6965
6966 for function in functions {
6967 let mut confidence = 0.0;
6968 let mut indicators = Vec::new();
6969
6970 let name_lower = function.name.to_lowercase();
6972 if name_lower.contains("decorator")
6973 || name_lower.ends_with("_decorator")
6974 || name_lower.starts_with("make_")
6975 || name_lower.contains("factory")
6976 {
6977 confidence += 0.4;
6978 indicators.push("Decorator-like naming pattern".to_string());
6979 }
6980
6981 if let Ok(content) = std::fs::read_to_string(&function.file) {
6984 let lines: Vec<&str> = content.lines().collect();
6985 let start_line = function.span.start_line.saturating_sub(1);
6986 let end_line = function.span.end_line.min(lines.len());
6987
6988 if start_line < end_line {
6989 let function_content: String = lines[start_line..end_line].join("\n");
6990
6991 let nested_defs = function_content.matches("def ").count();
6993 if nested_defs > 1 {
6994 confidence += 0.4;
6995 indicators.push("Contains nested function definitions".to_string());
6996 }
6997
6998 if function_content.contains("return ")
7000 && (function_content.contains("wrapper")
7001 || function_content.contains("decorator"))
7002 {
7003 confidence += 0.3;
7004 indicators.push("Returns wrapper function".to_string());
7005 }
7006 }
7007 }
7008
7009 if confidence >= confidence_threshold {
7010 patterns.push(serde_json::json!({
7011 "type": "Decorator Factory",
7012 "category": "metaprogramming_pattern",
7013 "confidence": confidence,
7014 "function": {
7015 "id": function.id.to_hex(),
7016 "name": function.name,
7017 "file": function.file.display().to_string(),
7018 "span": function.span
7019 },
7020 "indicators": indicators,
7021 "description": "Function that creates and returns decorators"
7022 }));
7023 }
7024 }
7025
7026 Ok(patterns)
7027 }
7028
7029 async fn detect_property_descriptor_pattern(
7031 &self,
7032 server: &CodePrismMcpServer,
7033 confidence_threshold: f64,
7034 ) -> Result<Vec<serde_json::Value>> {
7035 let mut patterns = Vec::new();
7036 let classes = server
7037 .graph_store()
7038 .get_nodes_by_kind(codeprism_core::NodeKind::Class);
7039
7040 for class in classes {
7041 let mut confidence = 0.0;
7042 let mut indicators = Vec::new();
7043 let mut descriptor_methods = Vec::new();
7044
7045 let methods = server.graph_store().get_outgoing_edges(&class.id);
7047 for edge in methods {
7048 if let Some(method_node) = server.graph_store().get_node(&edge.target) {
7049 if method_node.kind == codeprism_core::NodeKind::Method {
7050 match method_node.name.as_str() {
7051 "__get__" => {
7052 confidence += 0.4;
7053 descriptor_methods.push("__get__".to_string());
7054 }
7055 "__set__" => {
7056 confidence += 0.3;
7057 descriptor_methods.push("__set__".to_string());
7058 }
7059 "__delete__" => {
7060 confidence += 0.2;
7061 descriptor_methods.push("__delete__".to_string());
7062 }
7063 "__set_name__" => {
7064 confidence += 0.2;
7065 descriptor_methods.push("__set_name__".to_string());
7066 }
7067 _ => {}
7068 }
7069 }
7070 }
7071 }
7072
7073 if !descriptor_methods.is_empty() {
7074 indicators.push(format!(
7075 "Implements descriptor methods: {}",
7076 descriptor_methods.join(", ")
7077 ));
7078 }
7079
7080 if class.name.to_lowercase().contains("property")
7082 || class.name.to_lowercase().contains("descriptor")
7083 || class.name.to_lowercase().contains("field")
7084 {
7085 confidence += 0.2;
7086 indicators.push("Property-like naming pattern".to_string());
7087 }
7088
7089 if confidence >= confidence_threshold {
7090 patterns.push(serde_json::json!({
7091 "type": "Property Descriptor",
7092 "category": "metaprogramming_pattern",
7093 "confidence": confidence,
7094 "class": {
7095 "id": class.id.to_hex(),
7096 "name": class.name,
7097 "file": class.file.display().to_string(),
7098 "span": class.span
7099 },
7100 "descriptor_methods": descriptor_methods,
7101 "indicators": indicators,
7102 "description": "Class implementing the descriptor protocol for managed attributes"
7103 }));
7104 }
7105 }
7106
7107 Ok(patterns)
7108 }
7109
7110 async fn detect_dynamic_attribute_pattern(
7112 &self,
7113 server: &CodePrismMcpServer,
7114 confidence_threshold: f64,
7115 ) -> Result<Vec<serde_json::Value>> {
7116 let mut patterns = Vec::new();
7117 let classes = server
7118 .graph_store()
7119 .get_nodes_by_kind(codeprism_core::NodeKind::Class);
7120
7121 for class in classes {
7122 let mut confidence = 0.0;
7123 let mut indicators = Vec::new();
7124 let mut dynamic_methods = Vec::new();
7125
7126 let methods = server.graph_store().get_outgoing_edges(&class.id);
7128 for edge in methods {
7129 if let Some(method_node) = server.graph_store().get_node(&edge.target) {
7130 if method_node.kind == codeprism_core::NodeKind::Method {
7131 match method_node.name.as_str() {
7132 "__getattr__" => {
7133 confidence += 0.4;
7134 dynamic_methods.push("__getattr__".to_string());
7135 }
7136 "__setattr__" => {
7137 confidence += 0.3;
7138 dynamic_methods.push("__setattr__".to_string());
7139 }
7140 "__getattribute__" => {
7141 confidence += 0.3;
7142 dynamic_methods.push("__getattribute__".to_string());
7143 }
7144 "__delattr__" => {
7145 confidence += 0.2;
7146 dynamic_methods.push("__delattr__".to_string());
7147 }
7148 _ => {}
7149 }
7150 }
7151 }
7152 }
7153
7154 if !dynamic_methods.is_empty() {
7155 indicators.push(format!(
7156 "Implements dynamic attribute methods: {}",
7157 dynamic_methods.join(", ")
7158 ));
7159 }
7160
7161 if class.name.to_lowercase().contains("proxy")
7163 || class.name.to_lowercase().contains("wrapper")
7164 || class.name.to_lowercase().contains("dynamic")
7165 {
7166 confidence += 0.2;
7167 indicators.push("Dynamic/proxy-like naming pattern".to_string());
7168 }
7169
7170 if confidence >= confidence_threshold {
7171 patterns.push(serde_json::json!({
7172 "type": "Dynamic Attribute Pattern",
7173 "category": "metaprogramming_pattern",
7174 "confidence": confidence,
7175 "class": {
7176 "id": class.id.to_hex(),
7177 "name": class.name,
7178 "file": class.file.display().to_string(),
7179 "span": class.span
7180 },
7181 "dynamic_methods": dynamic_methods,
7182 "indicators": indicators,
7183 "description": "Class with dynamic attribute access and manipulation"
7184 }));
7185 }
7186 }
7187
7188 Ok(patterns)
7189 }
7190
7191 async fn detect_mixin_pattern(
7193 &self,
7194 server: &CodePrismMcpServer,
7195 confidence_threshold: f64,
7196 ) -> Result<Vec<serde_json::Value>> {
7197 let mut patterns = Vec::new();
7198 let classes = server
7199 .graph_store()
7200 .get_nodes_by_kind(codeprism_core::NodeKind::Class);
7201
7202 for class in classes {
7203 let mut confidence = 0.0;
7204 let mut indicators = Vec::new();
7205
7206 if class.name.ends_with("Mixin") || class.name.to_lowercase().contains("mixin") {
7208 confidence += 0.6;
7209 indicators.push("Mixin naming convention".to_string());
7210 }
7211
7212 if let Ok(inheritance_info) = server.graph_query().get_inheritance_info(&class.id) {
7214 let usage_count = inheritance_info.subclasses.len();
7215 if usage_count > 1 {
7216 confidence += 0.3;
7217 indicators.push(format!("Used by {} classes", usage_count));
7218 }
7219
7220 let method_count = server
7222 .graph_store()
7223 .get_outgoing_edges(&class.id)
7224 .iter()
7225 .filter(|edge| {
7226 if let Some(target_node) = server.graph_store().get_node(&edge.target) {
7227 target_node.kind == codeprism_core::NodeKind::Method
7228 } else {
7229 false
7230 }
7231 })
7232 .count();
7233
7234 if method_count > 0 && method_count <= 5 {
7235 confidence += 0.2;
7236 indicators.push(format!(
7237 "Small focused interface ({} methods)",
7238 method_count
7239 ));
7240 }
7241 }
7242
7243 if confidence >= confidence_threshold {
7244 patterns.push(serde_json::json!({
7245 "type": "Mixin Pattern",
7246 "category": "metaprogramming_pattern",
7247 "confidence": confidence,
7248 "class": {
7249 "id": class.id.to_hex(),
7250 "name": class.name,
7251 "file": class.file.display().to_string(),
7252 "span": class.span
7253 },
7254 "indicators": indicators,
7255 "description": "Class designed to be mixed into other classes to provide specific functionality"
7256 }));
7257 }
7258 }
7259
7260 Ok(patterns)
7261 }
7262
7263 async fn detect_abstract_base_class_pattern(
7265 &self,
7266 server: &CodePrismMcpServer,
7267 confidence_threshold: f64,
7268 ) -> Result<Vec<serde_json::Value>> {
7269 let mut patterns = Vec::new();
7270 let classes = server
7271 .graph_store()
7272 .get_nodes_by_kind(codeprism_core::NodeKind::Class);
7273
7274 for class in classes {
7275 let mut confidence = 0.0;
7276 let mut indicators = Vec::new();
7277
7278 if class.name.starts_with("Abstract")
7280 || class.name.starts_with("Base")
7281 || class.name.ends_with("ABC")
7282 || class.name.ends_with("Base")
7283 {
7284 confidence += 0.4;
7285 indicators.push("Abstract/Base naming pattern".to_string());
7286 }
7287
7288 if let Ok(inheritance_info) = server.graph_query().get_inheritance_info(&class.id) {
7290 let subclass_count = inheritance_info.subclasses.len();
7291 if subclass_count > 0 {
7292 confidence += 0.4;
7293 indicators.push(format!("Has {} subclasses", subclass_count));
7294 }
7295
7296 if inheritance_info
7298 .base_classes
7299 .iter()
7300 .any(|base| base.class_name == "ABC")
7301 {
7302 confidence += 0.3;
7303 indicators.push("Inherits from ABC".to_string());
7304 }
7305 }
7306
7307 let methods = server.graph_store().get_outgoing_edges(&class.id);
7309 let abstract_methods = methods
7310 .iter()
7311 .filter(|edge| {
7312 if let Some(method_node) = server.graph_store().get_node(&edge.target) {
7313 if method_node.kind == codeprism_core::NodeKind::Method {
7314 if let Ok(content) = std::fs::read_to_string(&method_node.file) {
7316 let lines: Vec<&str> = content.lines().collect();
7317 let start_line = method_node.span.start_line.saturating_sub(1);
7318 let end_line = method_node.span.end_line.min(lines.len());
7319
7320 if start_line < end_line {
7321 let method_content: String =
7322 lines[start_line..end_line].join("\n");
7323 return method_content.contains("NotImplementedError")
7324 || method_content.contains("@abstractmethod");
7325 }
7326 }
7327 }
7328 }
7329 false
7330 })
7331 .count();
7332
7333 if abstract_methods > 0 {
7334 confidence += 0.3;
7335 indicators.push(format!("{} abstract methods", abstract_methods));
7336 }
7337
7338 if confidence >= confidence_threshold {
7339 patterns.push(serde_json::json!({
7340 "type": "Abstract Base Class",
7341 "category": "metaprogramming_pattern",
7342 "confidence": confidence,
7343 "class": {
7344 "id": class.id.to_hex(),
7345 "name": class.name,
7346 "file": class.file.display().to_string(),
7347 "span": class.span
7348 },
7349 "abstract_methods": abstract_methods,
7350 "indicators": indicators,
7351 "description": "Abstract base class defining interface for subclasses"
7352 }));
7353 }
7354 }
7355
7356 Ok(patterns)
7357 }
7358
7359 async fn detect_protocol_pattern(
7361 &self,
7362 server: &CodePrismMcpServer,
7363 confidence_threshold: f64,
7364 ) -> Result<Vec<serde_json::Value>> {
7365 let mut patterns = Vec::new();
7366 let classes = server
7367 .graph_store()
7368 .get_nodes_by_kind(codeprism_core::NodeKind::Class);
7369
7370 for class in classes {
7371 let mut confidence = 0.0;
7372 let mut indicators = Vec::new();
7373
7374 if class.name.ends_with("Protocol")
7376 || class.name.ends_with("Interface")
7377 || class.name.starts_with("I")
7378 && class
7379 .name
7380 .chars()
7381 .nth(1)
7382 .map_or(false, |c| c.is_uppercase())
7383 {
7384 confidence += 0.5;
7385 indicators.push("Protocol/Interface naming pattern".to_string());
7386 }
7387
7388 if let Ok(inheritance_info) = server.graph_query().get_inheritance_info(&class.id) {
7390 if inheritance_info
7391 .base_classes
7392 .iter()
7393 .any(|base| base.class_name == "Protocol")
7394 {
7395 confidence += 0.4;
7396 indicators.push("Inherits from Protocol".to_string());
7397 }
7398 }
7399
7400 let methods = server.graph_store().get_outgoing_edges(&class.id);
7402 let method_count = methods
7403 .iter()
7404 .filter(|edge| {
7405 if let Some(method_node) = server.graph_store().get_node(&edge.target) {
7406 method_node.kind == codeprism_core::NodeKind::Method
7407 } else {
7408 false
7409 }
7410 })
7411 .count();
7412
7413 if method_count > 0 && method_count <= 10 {
7415 confidence += 0.2;
7416 indicators.push(format!("Defines {} interface methods", method_count));
7417 }
7418
7419 if confidence >= confidence_threshold {
7420 patterns.push(serde_json::json!({
7421 "type": "Protocol/Interface",
7422 "category": "metaprogramming_pattern",
7423 "confidence": confidence,
7424 "class": {
7425 "id": class.id.to_hex(),
7426 "name": class.name,
7427 "file": class.file.display().to_string(),
7428 "span": class.span
7429 },
7430 "method_count": method_count,
7431 "indicators": indicators,
7432 "description": "Protocol or interface defining expected behavior via duck typing"
7433 }));
7434 }
7435 }
7436
7437 Ok(patterns)
7438 }
7439
7440 fn get_pattern_suggestions(&self, pattern_type: &str) -> Vec<String> {
7442 match pattern_type {
7443 "Singleton" => vec![
7444 "Consider using dependency injection instead of Singleton".to_string(),
7445 "Ensure thread safety in multi-threaded environments".to_string(),
7446 "Consider if global state is truly necessary".to_string(),
7447 ],
7448 "Factory" => vec![
7449 "Consider using abstract factory for families of objects".to_string(),
7450 "Ensure proper error handling in object creation".to_string(),
7451 "Document the creation strategies clearly".to_string(),
7452 ],
7453 "Observer" => vec![
7454 "Consider using weak references to prevent memory leaks".to_string(),
7455 "Implement proper error handling in notifications".to_string(),
7456 "Consider async notifications for heavy operations".to_string(),
7457 ],
7458 "God Class" => vec![
7459 "Split into smaller, focused classes".to_string(),
7460 "Apply Single Responsibility Principle".to_string(),
7461 "Extract related methods into separate classes".to_string(),
7462 ],
7463 "Long Method" => vec![
7464 "Break down into smaller, focused methods".to_string(),
7465 "Extract common logic into helper methods".to_string(),
7466 "Consider if the method has too many responsibilities".to_string(),
7467 ],
7468 "Registry Metaclass" => vec![
7469 "Document the registration behavior clearly".to_string(),
7470 "Consider thread safety for registry operations".to_string(),
7471 "Provide clear error messages for registration failures".to_string(),
7472 "Consider using class decorators as an alternative".to_string(),
7473 ],
7474 "Attribute Injection Metaclass" => vec![
7475 "Document all injected attributes".to_string(),
7476 "Avoid name conflicts with user-defined attributes".to_string(),
7477 "Consider using descriptors for complex attribute behavior".to_string(),
7478 ],
7479 "Decorator Factory" => vec![
7480 "Use functools.wraps to preserve function metadata".to_string(),
7481 "Document the decorator's behavior and parameters".to_string(),
7482 "Consider type hints for better IDE support".to_string(),
7483 ],
7484 "Property Descriptor" => vec![
7485 "Implement proper error handling in descriptor methods".to_string(),
7486 "Document the descriptor's behavior clearly".to_string(),
7487 "Consider using __set_name__ for better introspection".to_string(),
7488 ],
7489 "Dynamic Attribute Pattern" => vec![
7490 "Be careful with infinite recursion in __getattribute__".to_string(),
7491 "Document the dynamic attribute behavior".to_string(),
7492 "Consider performance implications of dynamic access".to_string(),
7493 ],
7494 "Mixin Pattern" => vec![
7495 "Keep mixins small and focused on single responsibility".to_string(),
7496 "Use clear naming conventions (e.g., SomethingMixin)".to_string(),
7497 "Document the expected interface and dependencies".to_string(),
7498 ],
7499 "Abstract Base Class" => vec![
7500 "Use @abstractmethod decorator for abstract methods".to_string(),
7501 "Document the contract that subclasses must implement".to_string(),
7502 "Consider using typing.Protocol for structural subtyping".to_string(),
7503 ],
7504 "Protocol/Interface" => vec![
7505 "Use typing.Protocol for static type checking".to_string(),
7506 "Document the expected behavior, not just signatures".to_string(),
7507 "Consider runtime checks if needed".to_string(),
7508 ],
7509 _ => vec!["No specific suggestions available".to_string()],
7510 }
7511 }
7512
7513 async fn build_transitive_dependencies(
7515 &self,
7516 server: &CodePrismMcpServer,
7517 start_node: &codeprism_core::NodeId,
7518 max_depth: usize,
7519 dependency_types: &[String],
7520 ) -> Result<Vec<serde_json::Value>> {
7521 let mut dependencies = Vec::new();
7522 let mut visited = std::collections::HashSet::new();
7523 let mut queue = std::collections::VecDeque::new();
7524
7525 queue.push_back((*start_node, 0));
7526 visited.insert(*start_node);
7527
7528 while let Some((current_node, depth)) = queue.pop_front() {
7529 if depth >= max_depth {
7530 continue;
7531 }
7532
7533 let edges = server.graph_store().get_outgoing_edges(¤t_node);
7534 for edge in edges {
7535 let include_edge = dependency_types.contains(&"all".to_string())
7537 || dependency_types.iter().any(|dt| match dt.as_str() {
7538 "calls" => edge.kind == codeprism_core::EdgeKind::Calls,
7539 "imports" => edge.kind == codeprism_core::EdgeKind::Imports,
7540 "reads" => edge.kind == codeprism_core::EdgeKind::Reads,
7541 "writes" => edge.kind == codeprism_core::EdgeKind::Writes,
7542 "extends" => edge.kind == codeprism_core::EdgeKind::Extends,
7543 "implements" => edge.kind == codeprism_core::EdgeKind::Implements,
7544 _ => false,
7545 });
7546
7547 if include_edge {
7548 if let Some(target_node) = server.graph_store().get_node(&edge.target) {
7549 dependencies.push(serde_json::json!({
7550 "source": {
7551 "id": current_node.to_hex(),
7552 "name": server.graph_store().get_node(¤t_node)
7553 .map(|n| n.name.clone()).unwrap_or("unknown".to_string())
7554 },
7555 "target": {
7556 "id": target_node.id.to_hex(),
7557 "name": target_node.name,
7558 "kind": format!("{:?}", target_node.kind),
7559 "file": target_node.file.display().to_string()
7560 },
7561 "edge_type": format!("{:?}", edge.kind),
7562 "depth": depth + 1
7563 }));
7564
7565 if !visited.contains(&edge.target) {
7566 visited.insert(edge.target);
7567 queue.push_back((edge.target, depth + 1));
7568 }
7569 }
7570 }
7571 }
7572 }
7573
7574 Ok(dependencies)
7575 }
7576
7577 async fn build_dependency_chains(
7579 &self,
7580 server: &CodePrismMcpServer,
7581 start_node: &codeprism_core::NodeId,
7582 max_depth: usize,
7583 ) -> Result<Vec<serde_json::Value>> {
7584 let mut chains = Vec::new();
7585 let current_chain = Vec::new();
7586
7587 self.build_chains_recursive(
7588 server,
7589 *start_node,
7590 current_chain,
7591 &mut chains,
7592 max_depth,
7593 0,
7594 )
7595 .await?;
7596
7597 Ok(chains)
7598 }
7599
7600 fn build_chains_recursive<'a>(
7602 &'a self,
7603 server: &'a CodePrismMcpServer,
7604 current_node: codeprism_core::NodeId,
7605 current_chain: Vec<String>,
7606 all_chains: &'a mut Vec<serde_json::Value>,
7607 max_depth: usize,
7608 current_depth: usize,
7609 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + 'a>> {
7610 Box::pin(async move {
7611 if current_depth >= max_depth {
7612 return Ok(());
7613 }
7614
7615 let mut chain = current_chain;
7616 if let Some(node) = server.graph_store().get_node(¤t_node) {
7617 chain.push(format!("{}:{}", node.name, node.id.to_hex()));
7618 }
7619
7620 let edges = server.graph_store().get_outgoing_edges(¤t_node);
7621 if edges.is_empty() {
7622 if chain.len() > 1 {
7624 all_chains.push(serde_json::json!({
7625 "chain": chain,
7626 "length": chain.len()
7627 }));
7628 }
7629 } else {
7630 for edge in edges {
7631 if edge.kind == codeprism_core::EdgeKind::Calls
7632 || edge.kind == codeprism_core::EdgeKind::Imports
7633 {
7634 self.build_chains_recursive(
7635 server,
7636 edge.target,
7637 chain.clone(),
7638 all_chains,
7639 max_depth,
7640 current_depth + 1,
7641 )
7642 .await?;
7643 }
7644 }
7645 }
7646
7647 Ok(())
7648 })
7649 }
7650
7651 async fn detect_dependency_cycles(
7653 &self,
7654 server: &CodePrismMcpServer,
7655 start_node: &codeprism_core::NodeId,
7656 _dependencies: &[serde_json::Value],
7657 ) -> Result<Vec<serde_json::Value>> {
7658 let mut cycles = Vec::new();
7659 let mut visited = std::collections::HashSet::new();
7660 let mut rec_stack = std::collections::HashSet::new();
7661 let mut path = Vec::new();
7662
7663 self.detect_cycles_dfs(
7664 server,
7665 *start_node,
7666 &mut visited,
7667 &mut rec_stack,
7668 &mut path,
7669 &mut cycles,
7670 )
7671 .await?;
7672
7673 Ok(cycles)
7674 }
7675
7676 fn detect_cycles_dfs<'a>(
7678 &'a self,
7679 server: &'a CodePrismMcpServer,
7680 node: codeprism_core::NodeId,
7681 visited: &'a mut std::collections::HashSet<codeprism_core::NodeId>,
7682 rec_stack: &'a mut std::collections::HashSet<codeprism_core::NodeId>,
7683 path: &'a mut Vec<codeprism_core::NodeId>,
7684 cycles: &'a mut Vec<serde_json::Value>,
7685 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + 'a>> {
7686 Box::pin(async move {
7687 visited.insert(node);
7688 rec_stack.insert(node);
7689 path.push(node);
7690
7691 let edges = server.graph_store().get_outgoing_edges(&node);
7692 for edge in edges {
7693 if edge.kind == codeprism_core::EdgeKind::Calls
7694 || edge.kind == codeprism_core::EdgeKind::Imports
7695 {
7696 if !visited.contains(&edge.target) {
7697 self.detect_cycles_dfs(
7698 server,
7699 edge.target,
7700 visited,
7701 rec_stack,
7702 path,
7703 cycles,
7704 )
7705 .await?;
7706 } else if rec_stack.contains(&edge.target) {
7707 if let Some(cycle_start) = path.iter().position(|&id| id == edge.target) {
7709 let cycle_path: Vec<String> = path[cycle_start..]
7710 .iter()
7711 .map(|id| {
7712 if let Some(node) = server.graph_store().get_node(id) {
7713 format!("{}:{}", node.name, id.to_hex())
7714 } else {
7715 id.to_hex()
7716 }
7717 })
7718 .collect();
7719
7720 cycles.push(serde_json::json!({
7721 "cycle_path": cycle_path,
7722 "cycle_length": cycle_path.len(),
7723 "cycle_type": "dependency_cycle"
7724 }));
7725 }
7726 }
7727 }
7728 }
7729
7730 path.pop();
7731 rec_stack.remove(&node);
7732
7733 Ok(())
7734 })
7735 }
7736
7737 fn calculate_max_depth(&self, dependencies: &[serde_json::Value]) -> usize {
7739 dependencies
7740 .iter()
7741 .filter_map(|dep| dep.get("depth").and_then(|d| d.as_u64()))
7742 .max()
7743 .unwrap_or(0) as usize
7744 }
7745
7746 fn count_unique_dependencies(&self, analyses: &[serde_json::Value]) -> usize {
7748 let mut unique_deps = std::collections::HashSet::new();
7749
7750 for analysis in analyses {
7751 if let Some(deps) = analysis
7752 .get("transitive_dependencies")
7753 .and_then(|d| d.as_array())
7754 {
7755 for dep in deps {
7756 if let Some(target_id) = dep
7757 .get("target")
7758 .and_then(|t| t.get("id"))
7759 .and_then(|id| id.as_str())
7760 {
7761 unique_deps.insert(target_id.to_string());
7762 }
7763 }
7764 }
7765 }
7766
7767 unique_deps.len()
7768 }
7769
7770 fn count_total_cycles(&self, analyses: &[serde_json::Value]) -> usize {
7772 analyses
7773 .iter()
7774 .map(|analysis| {
7775 analysis
7776 .get("cycles")
7777 .and_then(|c| c.as_array())
7778 .map(|arr| arr.len())
7779 .unwrap_or(0)
7780 })
7781 .sum()
7782 }
7783
7784 async fn perform_data_flow_analysis(
7786 &self,
7787 server: &CodePrismMcpServer,
7788 symbol_id: &codeprism_core::NodeId,
7789 direction: &str,
7790 include_transformations: bool,
7791 max_depth: usize,
7792 follow_function_calls: bool,
7793 include_field_access: bool,
7794 ) -> Result<serde_json::Value> {
7795 let start_node = server
7797 .graph_store()
7798 .get_node(symbol_id)
7799 .ok_or_else(|| anyhow::anyhow!("Symbol not found: {}", symbol_id.to_hex()))?;
7800
7801 let mut data_flows = Vec::new();
7802 let mut visited = std::collections::HashSet::new();
7803
7804 match direction {
7805 "forward" => {
7806 self.trace_data_flow_forward(
7807 server,
7808 symbol_id,
7809 &mut data_flows,
7810 &mut visited,
7811 0,
7812 max_depth,
7813 include_transformations,
7814 follow_function_calls,
7815 include_field_access,
7816 )
7817 .await?;
7818 }
7819 "backward" => {
7820 self.trace_data_flow_backward(
7821 server,
7822 symbol_id,
7823 &mut data_flows,
7824 &mut visited,
7825 0,
7826 max_depth,
7827 include_transformations,
7828 follow_function_calls,
7829 include_field_access,
7830 )
7831 .await?;
7832 }
7833 "both" => {
7834 let mut forward_flows = Vec::new();
7835 let mut backward_flows = Vec::new();
7836 let mut forward_visited = std::collections::HashSet::new();
7837 let mut backward_visited = std::collections::HashSet::new();
7838
7839 self.trace_data_flow_forward(
7840 server,
7841 symbol_id,
7842 &mut forward_flows,
7843 &mut forward_visited,
7844 0,
7845 max_depth,
7846 include_transformations,
7847 follow_function_calls,
7848 include_field_access,
7849 )
7850 .await?;
7851
7852 self.trace_data_flow_backward(
7853 server,
7854 symbol_id,
7855 &mut backward_flows,
7856 &mut backward_visited,
7857 0,
7858 max_depth,
7859 include_transformations,
7860 follow_function_calls,
7861 include_field_access,
7862 )
7863 .await?;
7864
7865 return Ok(serde_json::json!({
7866 "starting_symbol": {
7867 "id": start_node.id.to_hex(),
7868 "name": start_node.name,
7869 "kind": format!("{:?}", start_node.kind),
7870 "file": start_node.file.display().to_string(),
7871 "location": {
7872 "line": start_node.span.start_line,
7873 "column": start_node.span.start_column
7874 }
7875 },
7876 "direction": direction,
7877 "forward_flows": forward_flows,
7878 "backward_flows": backward_flows,
7879 "summary": {
7880 "total_forward_flows": forward_flows.len(),
7881 "total_backward_flows": backward_flows.len(),
7882 "max_depth_reached": max_depth,
7883 "unique_symbols_forward": forward_visited.len(),
7884 "unique_symbols_backward": backward_visited.len()
7885 },
7886 "parameters": {
7887 "include_transformations": include_transformations,
7888 "follow_function_calls": follow_function_calls,
7889 "include_field_access": include_field_access,
7890 "max_depth": max_depth
7891 }
7892 }));
7893 }
7894 _ => {
7895 return Err(anyhow::anyhow!(
7896 "Invalid direction: {}. Must be 'forward', 'backward', or 'both'",
7897 direction
7898 ));
7899 }
7900 }
7901
7902 Ok(serde_json::json!({
7903 "starting_symbol": {
7904 "id": start_node.id.to_hex(),
7905 "name": start_node.name,
7906 "kind": format!("{:?}", start_node.kind),
7907 "file": start_node.file.display().to_string(),
7908 "location": {
7909 "line": start_node.span.start_line,
7910 "column": start_node.span.start_column
7911 }
7912 },
7913 "direction": direction,
7914 "data_flows": data_flows,
7915 "summary": {
7916 "total_flows": data_flows.len(),
7917 "max_depth_reached": max_depth,
7918 "unique_symbols": visited.len()
7919 },
7920 "parameters": {
7921 "include_transformations": include_transformations,
7922 "follow_function_calls": follow_function_calls,
7923 "include_field_access": include_field_access,
7924 "max_depth": max_depth
7925 }
7926 }))
7927 }
7928
7929 fn trace_data_flow_forward<'a>(
7931 &'a self,
7932 server: &'a CodePrismMcpServer,
7933 symbol_id: &'a codeprism_core::NodeId,
7934 data_flows: &'a mut Vec<serde_json::Value>,
7935 visited: &'a mut std::collections::HashSet<codeprism_core::NodeId>,
7936 current_depth: usize,
7937 max_depth: usize,
7938 include_transformations: bool,
7939 follow_function_calls: bool,
7940 include_field_access: bool,
7941 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + 'a>> {
7942 Box::pin(async move {
7943 if current_depth >= max_depth || visited.contains(symbol_id) {
7944 return Ok(());
7945 }
7946
7947 visited.insert(*symbol_id);
7948
7949 let current_node = server
7950 .graph_store()
7951 .get_node(symbol_id)
7952 .ok_or_else(|| anyhow::anyhow!("Node not found: {}", symbol_id.to_hex()))?;
7953
7954 let dependencies = server
7956 .graph_query()
7957 .find_dependencies(symbol_id, codeprism_core::graph::DependencyType::Reads)?;
7958
7959 for dep in dependencies
7960 .iter()
7961 .filter(|d| self.is_valid_dependency_node(&d.target_node))
7962 {
7963 let flow_info = serde_json::json!({
7964 "flow_type": "read",
7965 "depth": current_depth,
7966 "source": {
7967 "id": current_node.id.to_hex(),
7968 "name": current_node.name,
7969 "kind": format!("{:?}", current_node.kind),
7970 "file": current_node.file.display().to_string(),
7971 "location": {
7972 "line": current_node.span.start_line,
7973 "column": current_node.span.start_column
7974 }
7975 },
7976 "target": {
7977 "id": dep.target_node.id.to_hex(),
7978 "name": dep.target_node.name,
7979 "kind": format!("{:?}", dep.target_node.kind),
7980 "file": dep.target_node.file.display().to_string(),
7981 "location": {
7982 "line": dep.target_node.span.start_line,
7983 "column": dep.target_node.span.start_column
7984 }
7985 },
7986 "edge_kind": format!("{:?}", dep.edge_kind)
7987 });
7988 data_flows.push(flow_info);
7989
7990 self.trace_data_flow_forward(
7992 server,
7993 &dep.target_node.id,
7994 data_flows,
7995 visited,
7996 current_depth + 1,
7997 max_depth,
7998 include_transformations,
7999 follow_function_calls,
8000 include_field_access,
8001 )
8002 .await?;
8003 }
8004
8005 if follow_function_calls {
8007 let call_dependencies = server
8008 .graph_query()
8009 .find_dependencies(symbol_id, codeprism_core::graph::DependencyType::Calls)?;
8010
8011 for dep in call_dependencies
8012 .iter()
8013 .filter(|d| self.is_valid_dependency_node(&d.target_node))
8014 {
8015 let flow_info = serde_json::json!({
8016 "flow_type": "function_call",
8017 "depth": current_depth,
8018 "source": {
8019 "id": current_node.id.to_hex(),
8020 "name": current_node.name,
8021 "kind": format!("{:?}", current_node.kind),
8022 "file": current_node.file.display().to_string(),
8023 "location": {
8024 "line": current_node.span.start_line,
8025 "column": current_node.span.start_column
8026 }
8027 },
8028 "target": {
8029 "id": dep.target_node.id.to_hex(),
8030 "name": dep.target_node.name,
8031 "kind": format!("{:?}", dep.target_node.kind),
8032 "file": dep.target_node.file.display().to_string(),
8033 "location": {
8034 "line": dep.target_node.span.start_line,
8035 "column": dep.target_node.span.start_column
8036 }
8037 },
8038 "edge_kind": format!("{:?}", dep.edge_kind)
8039 });
8040 data_flows.push(flow_info);
8041
8042 self.trace_data_flow_forward(
8044 server,
8045 &dep.target_node.id,
8046 data_flows,
8047 visited,
8048 current_depth + 1,
8049 max_depth,
8050 include_transformations,
8051 follow_function_calls,
8052 include_field_access,
8053 )
8054 .await?;
8055 }
8056 }
8057
8058 Ok(())
8059 })
8060 }
8061
8062 fn trace_data_flow_backward<'a>(
8064 &'a self,
8065 server: &'a CodePrismMcpServer,
8066 symbol_id: &'a codeprism_core::NodeId,
8067 data_flows: &'a mut Vec<serde_json::Value>,
8068 visited: &'a mut std::collections::HashSet<codeprism_core::NodeId>,
8069 current_depth: usize,
8070 max_depth: usize,
8071 include_transformations: bool,
8072 follow_function_calls: bool,
8073 include_field_access: bool,
8074 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + 'a>> {
8075 Box::pin(async move {
8076 if current_depth >= max_depth || visited.contains(symbol_id) {
8077 return Ok(());
8078 }
8079
8080 visited.insert(*symbol_id);
8081
8082 let current_node = server
8083 .graph_store()
8084 .get_node(symbol_id)
8085 .ok_or_else(|| anyhow::anyhow!("Node not found: {}", symbol_id.to_hex()))?;
8086
8087 let references = server.graph_query().find_references(symbol_id)?;
8089
8090 for ref_info in references.iter() {
8091 if matches!(ref_info.edge_kind, codeprism_core::EdgeKind::Writes) {
8093 let flow_info = serde_json::json!({
8094 "flow_type": "write",
8095 "depth": current_depth,
8096 "source": {
8097 "id": ref_info.source_node.id.to_hex(),
8098 "name": ref_info.source_node.name,
8099 "kind": format!("{:?}", ref_info.source_node.kind),
8100 "file": ref_info.source_node.file.display().to_string(),
8101 "location": {
8102 "line": ref_info.source_node.span.start_line,
8103 "column": ref_info.source_node.span.start_column
8104 }
8105 },
8106 "target": {
8107 "id": current_node.id.to_hex(),
8108 "name": current_node.name,
8109 "kind": format!("{:?}", current_node.kind),
8110 "file": current_node.file.display().to_string(),
8111 "location": {
8112 "line": current_node.span.start_line,
8113 "column": current_node.span.start_column
8114 }
8115 },
8116 "edge_kind": format!("{:?}", ref_info.edge_kind)
8117 });
8118 data_flows.push(flow_info);
8119
8120 self.trace_data_flow_backward(
8122 server,
8123 &ref_info.source_node.id,
8124 data_flows,
8125 visited,
8126 current_depth + 1,
8127 max_depth,
8128 include_transformations,
8129 follow_function_calls,
8130 include_field_access,
8131 )
8132 .await?;
8133 }
8134 }
8135
8136 if follow_function_calls {
8138 for ref_info in references.iter() {
8139 if matches!(ref_info.edge_kind, codeprism_core::EdgeKind::Calls) {
8140 let flow_info = serde_json::json!({
8141 "flow_type": "function_parameter",
8142 "depth": current_depth,
8143 "source": {
8144 "id": ref_info.source_node.id.to_hex(),
8145 "name": ref_info.source_node.name,
8146 "kind": format!("{:?}", ref_info.source_node.kind),
8147 "file": ref_info.source_node.file.display().to_string(),
8148 "location": {
8149 "line": ref_info.source_node.span.start_line,
8150 "column": ref_info.source_node.span.start_column
8151 }
8152 },
8153 "target": {
8154 "id": current_node.id.to_hex(),
8155 "name": current_node.name,
8156 "kind": format!("{:?}", current_node.kind),
8157 "file": current_node.file.display().to_string(),
8158 "location": {
8159 "line": current_node.span.start_line,
8160 "column": current_node.span.start_column
8161 }
8162 },
8163 "edge_kind": format!("{:?}", ref_info.edge_kind)
8164 });
8165 data_flows.push(flow_info);
8166
8167 self.trace_data_flow_backward(
8169 server,
8170 &ref_info.source_node.id,
8171 data_flows,
8172 visited,
8173 current_depth + 1,
8174 max_depth,
8175 include_transformations,
8176 follow_function_calls,
8177 include_field_access,
8178 )
8179 .await?;
8180 }
8181 }
8182 }
8183
8184 Ok(())
8185 })
8186 }
8187
8188 async fn perform_unused_code_analysis(
8190 &self,
8191 server: &CodePrismMcpServer,
8192 scope: &str,
8193 include_dead_code: bool,
8194 consider_external_apis: bool,
8195 confidence_threshold: f64,
8196 analyze_types: &[String],
8197 exclude_patterns: &[String],
8198 ) -> Result<serde_json::Value> {
8199 let mut unused_functions = Vec::new();
8200 let mut unused_classes = Vec::new();
8201 let mut unused_variables = Vec::new();
8202 let mut unused_imports = Vec::new();
8203 let mut dead_code_blocks = Vec::new();
8204
8205 if analyze_types.contains(&"functions".to_string())
8207 || analyze_types.contains(&"all".to_string())
8208 {
8209 unused_functions = self
8210 .find_unused_functions(
8211 server,
8212 confidence_threshold,
8213 consider_external_apis,
8214 exclude_patterns,
8215 )
8216 .await?;
8217 }
8218
8219 if analyze_types.contains(&"classes".to_string())
8220 || analyze_types.contains(&"all".to_string())
8221 {
8222 unused_classes = self
8223 .find_unused_classes(
8224 server,
8225 confidence_threshold,
8226 consider_external_apis,
8227 exclude_patterns,
8228 )
8229 .await?;
8230 }
8231
8232 if analyze_types.contains(&"variables".to_string())
8233 || analyze_types.contains(&"all".to_string())
8234 {
8235 unused_variables = self
8236 .find_unused_variables(server, confidence_threshold, exclude_patterns)
8237 .await?;
8238 }
8239
8240 if analyze_types.contains(&"imports".to_string())
8241 || analyze_types.contains(&"all".to_string())
8242 {
8243 unused_imports = self
8244 .find_unused_imports(server, confidence_threshold, exclude_patterns)
8245 .await?;
8246 }
8247
8248 if include_dead_code {
8249 dead_code_blocks = self
8250 .find_dead_code_blocks(server, confidence_threshold, exclude_patterns)
8251 .await?;
8252 }
8253
8254 Ok(serde_json::json!({
8255 "scope": scope,
8256 "analysis_parameters": {
8257 "include_dead_code": include_dead_code,
8258 "consider_external_apis": consider_external_apis,
8259 "confidence_threshold": confidence_threshold,
8260 "analyze_types": analyze_types
8261 },
8262 "unused_code": {
8263 "functions": unused_functions,
8264 "classes": unused_classes,
8265 "variables": unused_variables,
8266 "imports": unused_imports,
8267 "dead_code_blocks": dead_code_blocks
8268 },
8269 "summary": {
8270 "total_unused_functions": unused_functions.len(),
8271 "total_unused_classes": unused_classes.len(),
8272 "total_unused_variables": unused_variables.len(),
8273 "total_unused_imports": unused_imports.len(),
8274 "total_dead_code_blocks": dead_code_blocks.len(),
8275 "total_unused_elements": unused_functions.len() + unused_classes.len() + unused_variables.len() + unused_imports.len() + dead_code_blocks.len()
8276 },
8277 "recommendations": self.get_unused_code_recommendations(&unused_functions, &unused_classes, &unused_variables, &unused_imports, &dead_code_blocks)
8278 }))
8279 }
8280
8281 async fn find_unused_functions(
8283 &self,
8284 server: &CodePrismMcpServer,
8285 confidence_threshold: f64,
8286 consider_external_apis: bool,
8287 exclude_patterns: &[String],
8288 ) -> Result<Vec<serde_json::Value>> {
8289 let mut unused_functions = Vec::new();
8290 let functions = server
8291 .graph_store()
8292 .get_nodes_by_kind(codeprism_core::NodeKind::Function);
8293
8294 for function in functions {
8295 if exclude_patterns
8297 .iter()
8298 .any(|pattern| function.file.to_string_lossy().contains(pattern))
8299 {
8300 continue;
8301 }
8302
8303 let references = server.graph_query().find_references(&function.id)?;
8304 let mut confidence = 1.0;
8305 let mut usage_indicators = Vec::new();
8306
8307 let call_count = references
8309 .iter()
8310 .filter(|r| matches!(r.edge_kind, codeprism_core::EdgeKind::Calls))
8311 .count();
8312
8313 if call_count == 0 {
8314 usage_indicators.push("No direct function calls found".to_string());
8315 } else {
8316 confidence -= (call_count as f64 * 0.3).min(0.8);
8317 usage_indicators.push(format!("{} function calls found", call_count));
8318 }
8319
8320 if consider_external_apis {
8322 let function_name = &function.name;
8323
8324 if function_name.starts_with("main")
8326 || function_name.starts_with("__")
8327 || function_name.contains("handler")
8328 || function_name.contains("callback")
8329 || function_name.contains("api")
8330 || function_name.contains("endpoint")
8331 {
8332 confidence -= 0.5;
8333 usage_indicators.push("Potentially used by external API".to_string());
8334 }
8335 }
8336
8337 if function.name.starts_with('_') {
8339 confidence += 0.1;
8341 usage_indicators.push("Private function (name starts with _)".to_string());
8342 }
8343
8344 if confidence >= confidence_threshold {
8345 unused_functions.push(serde_json::json!({
8346 "id": function.id.to_hex(),
8347 "name": function.name,
8348 "kind": "Function",
8349 "file": function.file.display().to_string(),
8350 "location": {
8351 "start_line": function.span.start_line,
8352 "end_line": function.span.end_line,
8353 "start_column": function.span.start_column,
8354 "end_column": function.span.end_column
8355 },
8356 "confidence": confidence,
8357 "usage_indicators": usage_indicators,
8358 "lines_of_code": function.span.end_line - function.span.start_line + 1,
8359 "potential_savings": "Remove function to reduce codebase size"
8360 }));
8361 }
8362 }
8363
8364 Ok(unused_functions)
8365 }
8366
8367 async fn find_unused_classes(
8369 &self,
8370 server: &CodePrismMcpServer,
8371 confidence_threshold: f64,
8372 consider_external_apis: bool,
8373 exclude_patterns: &[String],
8374 ) -> Result<Vec<serde_json::Value>> {
8375 let mut unused_classes = Vec::new();
8376 let classes = server
8377 .graph_store()
8378 .get_nodes_by_kind(codeprism_core::NodeKind::Class);
8379
8380 for class in classes {
8381 if exclude_patterns
8383 .iter()
8384 .any(|pattern| class.file.to_string_lossy().contains(pattern))
8385 {
8386 continue;
8387 }
8388
8389 let references = server.graph_query().find_references(&class.id)?;
8390 let mut confidence = 1.0;
8391 let mut usage_indicators = Vec::new();
8392
8393 let usage_count = references
8395 .iter()
8396 .filter(|r| {
8397 matches!(
8398 r.edge_kind,
8399 codeprism_core::EdgeKind::Calls
8400 | codeprism_core::EdgeKind::Extends
8401 | codeprism_core::EdgeKind::Implements
8402 )
8403 })
8404 .count();
8405
8406 if usage_count == 0 {
8407 usage_indicators
8408 .push("No instantiation, inheritance, or implementation found".to_string());
8409 } else {
8410 confidence -= (usage_count as f64 * 0.4).min(0.9);
8411 usage_indicators.push(format!(
8412 "{} usages found (instantiation/inheritance)",
8413 usage_count
8414 ));
8415 }
8416
8417 if consider_external_apis {
8419 let class_name = &class.name;
8420
8421 if class_name.contains("Controller")
8422 || class_name.contains("Service")
8423 || class_name.contains("Handler")
8424 || class_name.contains("Model")
8425 || class_name.contains("Entity")
8426 || class_name.contains("Exception")
8427 || class_name.contains("Error")
8428 {
8429 confidence -= 0.4;
8430 usage_indicators
8431 .push("Potentially used by framework or external system".to_string());
8432 }
8433 }
8434
8435 if confidence >= confidence_threshold {
8436 unused_classes.push(serde_json::json!({
8437 "id": class.id.to_hex(),
8438 "name": class.name,
8439 "kind": "Class",
8440 "file": class.file.display().to_string(),
8441 "location": {
8442 "start_line": class.span.start_line,
8443 "end_line": class.span.end_line,
8444 "start_column": class.span.start_column,
8445 "end_column": class.span.end_column
8446 },
8447 "confidence": confidence,
8448 "usage_indicators": usage_indicators,
8449 "lines_of_code": class.span.end_line - class.span.start_line + 1,
8450 "potential_savings": "Remove class and its methods to reduce codebase complexity"
8451 }));
8452 }
8453 }
8454
8455 Ok(unused_classes)
8456 }
8457
8458 async fn find_unused_variables(
8460 &self,
8461 server: &CodePrismMcpServer,
8462 confidence_threshold: f64,
8463 exclude_patterns: &[String],
8464 ) -> Result<Vec<serde_json::Value>> {
8465 let mut unused_variables = Vec::new();
8466 let variables = server
8467 .graph_store()
8468 .get_nodes_by_kind(codeprism_core::NodeKind::Variable);
8469
8470 for variable in variables {
8471 if exclude_patterns
8473 .iter()
8474 .any(|pattern| variable.file.to_string_lossy().contains(pattern))
8475 {
8476 continue;
8477 }
8478
8479 let references = server.graph_query().find_references(&variable.id)?;
8480 let mut confidence = 1.0;
8481 let mut usage_indicators = Vec::new();
8482
8483 let read_count = references
8485 .iter()
8486 .filter(|r| matches!(r.edge_kind, codeprism_core::EdgeKind::Reads))
8487 .count();
8488
8489 if read_count == 0 {
8490 usage_indicators.push("No reads found".to_string());
8491 } else {
8492 confidence -= (read_count as f64 * 0.5).min(0.9);
8493 usage_indicators.push(format!("{} reads found", read_count));
8494 }
8495
8496 let write_count = references
8498 .iter()
8499 .filter(|r| matches!(r.edge_kind, codeprism_core::EdgeKind::Writes))
8500 .count();
8501
8502 if write_count <= 1 {
8503 usage_indicators.push("Only initial assignment found".to_string());
8505 } else {
8506 confidence -= (write_count as f64 * 0.3).min(0.6);
8507 usage_indicators.push(format!("{} assignments found", write_count));
8508 }
8509
8510 if variable.name.starts_with('_') {
8512 usage_indicators.push("Private variable (name starts with _)".to_string());
8513 }
8514
8515 if confidence >= confidence_threshold {
8516 unused_variables.push(serde_json::json!({
8517 "id": variable.id.to_hex(),
8518 "name": variable.name,
8519 "kind": "Variable",
8520 "file": variable.file.display().to_string(),
8521 "location": {
8522 "start_line": variable.span.start_line,
8523 "end_line": variable.span.end_line,
8524 "start_column": variable.span.start_column,
8525 "end_column": variable.span.end_column
8526 },
8527 "confidence": confidence,
8528 "usage_indicators": usage_indicators,
8529 "potential_savings": "Remove variable declaration and related code"
8530 }));
8531 }
8532 }
8533
8534 Ok(unused_variables)
8535 }
8536
8537 async fn find_unused_imports(
8539 &self,
8540 server: &CodePrismMcpServer,
8541 confidence_threshold: f64,
8542 exclude_patterns: &[String],
8543 ) -> Result<Vec<serde_json::Value>> {
8544 let mut unused_imports = Vec::new();
8545
8546 let mut import_edges = Vec::new();
8548
8549 let node_kinds = [
8551 codeprism_core::NodeKind::Function,
8552 codeprism_core::NodeKind::Class,
8553 codeprism_core::NodeKind::Module,
8554 codeprism_core::NodeKind::Variable,
8555 ];
8556
8557 for kind in &node_kinds {
8558 let nodes = server.graph_store().get_nodes_by_kind(*kind);
8559 for node in nodes {
8560 let edges = server.graph_store().get_outgoing_edges(&node.id);
8561 import_edges.extend(
8562 edges
8563 .into_iter()
8564 .filter(|edge| edge.kind == codeprism_core::EdgeKind::Imports),
8565 );
8566 }
8567 }
8568
8569 for edge in import_edges {
8570 if let (Some(source_node), Some(target_node)) = (
8571 server.graph_store().get_node(&edge.source),
8572 server.graph_store().get_node(&edge.target),
8573 ) {
8574 if exclude_patterns
8576 .iter()
8577 .any(|pattern| source_node.file.to_string_lossy().contains(pattern))
8578 {
8579 continue;
8580 }
8581
8582 let target_references = server.graph_query().find_references(&target_node.id)?;
8584 let mut confidence = 1.0;
8585 let mut usage_indicators = Vec::new();
8586
8587 let usage_count = target_references
8589 .iter()
8590 .filter(|r| !matches!(r.edge_kind, codeprism_core::EdgeKind::Imports))
8591 .count();
8592
8593 if usage_count == 0 {
8594 usage_indicators.push("Import not used in code".to_string());
8595 } else {
8596 confidence -= (usage_count as f64 * 0.6).min(0.9);
8597 usage_indicators.push(format!("{} usages found", usage_count));
8598 }
8599
8600 if confidence >= confidence_threshold {
8601 unused_imports.push(serde_json::json!({
8602 "import_source_id": source_node.id.to_hex(),
8603 "import_target_id": target_node.id.to_hex(),
8604 "imported_name": target_node.name,
8605 "kind": "Import",
8606 "file": source_node.file.display().to_string(),
8607 "location": {
8608 "start_line": source_node.span.start_line,
8609 "end_line": source_node.span.end_line,
8610 "start_column": source_node.span.start_column,
8611 "end_column": source_node.span.end_column
8612 },
8613 "confidence": confidence,
8614 "usage_indicators": usage_indicators,
8615 "potential_savings": "Remove unused import to clean up code"
8616 }));
8617 }
8618 }
8619 }
8620
8621 Ok(unused_imports)
8622 }
8623
8624 async fn find_dead_code_blocks(
8626 &self,
8627 server: &CodePrismMcpServer,
8628 confidence_threshold: f64,
8629 exclude_patterns: &[String],
8630 ) -> Result<Vec<serde_json::Value>> {
8631 let mut dead_code_blocks = Vec::new();
8632
8633 let functions = server
8635 .graph_store()
8636 .get_nodes_by_kind(codeprism_core::NodeKind::Function);
8637
8638 for function in functions {
8639 if exclude_patterns
8641 .iter()
8642 .any(|pattern| function.file.to_string_lossy().contains(pattern))
8643 {
8644 continue;
8645 }
8646
8647 if function.name == "main"
8649 || function.name.starts_with("__")
8650 || function.name.starts_with("test_")
8651 || function.name.contains("init")
8652 {
8653 continue;
8654 }
8655
8656 let references = server.graph_query().find_references(&function.id)?;
8657 let call_count = references
8658 .iter()
8659 .filter(|r| matches!(r.edge_kind, codeprism_core::EdgeKind::Calls))
8660 .count();
8661
8662 if call_count == 0 {
8663 let confidence = 0.95; if confidence >= confidence_threshold {
8666 dead_code_blocks.push(serde_json::json!({
8667 "id": function.id.to_hex(),
8668 "name": function.name,
8669 "kind": "Dead Function",
8670 "file": function.file.display().to_string(),
8671 "location": {
8672 "start_line": function.span.start_line,
8673 "end_line": function.span.end_line,
8674 "start_column": function.span.start_column,
8675 "end_column": function.span.end_column
8676 },
8677 "confidence": confidence,
8678 "description": "Function is never called and appears to be unreachable",
8679 "lines_of_code": function.span.end_line - function.span.start_line + 1,
8680 "potential_savings": "Remove dead function to reduce codebase size and maintenance burden"
8681 }));
8682 }
8683 }
8684 }
8685
8686 Ok(dead_code_blocks)
8687 }
8688
8689 fn get_unused_code_recommendations(
8691 &self,
8692 unused_functions: &[serde_json::Value],
8693 unused_classes: &[serde_json::Value],
8694 unused_variables: &[serde_json::Value],
8695 unused_imports: &[serde_json::Value],
8696 dead_code_blocks: &[serde_json::Value],
8697 ) -> Vec<String> {
8698 let mut recommendations = Vec::new();
8699
8700 if !unused_imports.is_empty() {
8701 recommendations.push(format!(
8702 "Remove {} unused imports to clean up dependencies",
8703 unused_imports.len()
8704 ));
8705 }
8706
8707 if !unused_variables.is_empty() {
8708 recommendations.push(format!(
8709 "Remove {} unused variables to reduce code clutter",
8710 unused_variables.len()
8711 ));
8712 }
8713
8714 if !unused_functions.is_empty() {
8715 let lines_saved: usize = unused_functions
8716 .iter()
8717 .filter_map(|f| f.get("lines_of_code").and_then(|v| v.as_u64()))
8718 .map(|v| v as usize)
8719 .sum();
8720 recommendations.push(format!(
8721 "Remove {} unused functions to save approximately {} lines of code",
8722 unused_functions.len(),
8723 lines_saved
8724 ));
8725 }
8726
8727 if !unused_classes.is_empty() {
8728 let lines_saved: usize = unused_classes
8729 .iter()
8730 .filter_map(|c| c.get("lines_of_code").and_then(|v| v.as_u64()))
8731 .map(|v| v as usize)
8732 .sum();
8733 recommendations.push(format!(
8734 "Remove {} unused classes to save approximately {} lines of code",
8735 unused_classes.len(),
8736 lines_saved
8737 ));
8738 }
8739
8740 if !dead_code_blocks.is_empty() {
8741 recommendations.push(format!(
8742 "Remove {} dead code blocks to eliminate unreachable code",
8743 dead_code_blocks.len()
8744 ));
8745 }
8746
8747 if recommendations.is_empty() {
8748 recommendations
8749 .push("No unused code detected with current confidence threshold".to_string());
8750 } else {
8751 recommendations.push("Consider running tests after removing unused code to ensure no unexpected dependencies".to_string());
8752 recommendations.push(
8753 "Use version control to safely experiment with unused code removal".to_string(),
8754 );
8755 }
8756
8757 recommendations
8758 }
8759
8760 async fn perform_security_analysis(
8762 &self,
8763 server: &CodePrismMcpServer,
8764 scope: &str,
8765 vulnerability_types: &[String],
8766 severity_threshold: &str,
8767 include_data_flow_analysis: bool,
8768 check_external_dependencies: bool,
8769 exclude_patterns: &[String],
8770 ) -> Result<serde_json::Value> {
8771 let mut vulnerabilities = Vec::new();
8772
8773 if vulnerability_types.contains(&"injection".to_string())
8775 || vulnerability_types.contains(&"all".to_string())
8776 {
8777 let injection_vulns = self
8778 .detect_injection_vulnerabilities(server, exclude_patterns)
8779 .await?;
8780 vulnerabilities.extend(injection_vulns);
8781 }
8782
8783 if vulnerability_types.contains(&"authentication".to_string())
8784 || vulnerability_types.contains(&"all".to_string())
8785 {
8786 let auth_vulns = self
8787 .detect_authentication_issues(server, exclude_patterns)
8788 .await?;
8789 vulnerabilities.extend(auth_vulns);
8790 }
8791
8792 if vulnerability_types.contains(&"authorization".to_string())
8793 || vulnerability_types.contains(&"all".to_string())
8794 {
8795 let authz_vulns = self
8796 .detect_authorization_issues(server, exclude_patterns)
8797 .await?;
8798 vulnerabilities.extend(authz_vulns);
8799 }
8800
8801 if vulnerability_types.contains(&"data_exposure".to_string())
8802 || vulnerability_types.contains(&"all".to_string())
8803 {
8804 let data_vulns = self
8805 .detect_data_exposure_issues(server, exclude_patterns)
8806 .await?;
8807 vulnerabilities.extend(data_vulns);
8808 }
8809
8810 if vulnerability_types.contains(&"unsafe_patterns".to_string())
8811 || vulnerability_types.contains(&"all".to_string())
8812 {
8813 let unsafe_vulns = self
8814 .detect_unsafe_patterns(server, exclude_patterns)
8815 .await?;
8816 vulnerabilities.extend(unsafe_vulns);
8817 }
8818
8819 if vulnerability_types.contains(&"crypto_issues".to_string())
8820 || vulnerability_types.contains(&"all".to_string())
8821 {
8822 let crypto_vulns = self.detect_crypto_issues(server, exclude_patterns).await?;
8823 vulnerabilities.extend(crypto_vulns);
8824 }
8825
8826 let severity_order = ["low", "medium", "high", "critical"];
8828 let min_severity_index = severity_order
8829 .iter()
8830 .position(|&s| s == severity_threshold)
8831 .unwrap_or(1);
8832
8833 vulnerabilities.retain(|vuln| {
8834 if let Some(severity) = vuln.get("severity").and_then(|s| s.as_str()) {
8835 severity_order
8836 .iter()
8837 .position(|&s| s == severity)
8838 .unwrap_or(0)
8839 >= min_severity_index
8840 } else {
8841 true }
8843 });
8844
8845 let mut by_severity = std::collections::HashMap::new();
8847 let mut by_type = std::collections::HashMap::new();
8848
8849 for vuln in &vulnerabilities {
8850 if let Some(severity) = vuln.get("severity").and_then(|s| s.as_str()) {
8851 by_severity
8852 .entry(severity.to_string())
8853 .or_insert_with(Vec::new)
8854 .push(vuln);
8855 }
8856 if let Some(vuln_type) = vuln.get("type").and_then(|t| t.as_str()) {
8857 by_type
8858 .entry(vuln_type.to_string())
8859 .or_insert_with(Vec::new)
8860 .push(vuln);
8861 }
8862 }
8863
8864 Ok(serde_json::json!({
8865 "scope": scope,
8866 "analysis_parameters": {
8867 "vulnerability_types": vulnerability_types,
8868 "severity_threshold": severity_threshold,
8869 "include_data_flow_analysis": include_data_flow_analysis,
8870 "check_external_dependencies": check_external_dependencies
8871 },
8872 "vulnerabilities": vulnerabilities,
8873 "summary": {
8874 "total_vulnerabilities": vulnerabilities.len(),
8875 "by_severity": by_severity.iter().map(|(k, v)| (k.clone(), v.len())).collect::<std::collections::HashMap<_, _>>(),
8876 "by_type": by_type.iter().map(|(k, v)| (k.clone(), v.len())).collect::<std::collections::HashMap<_, _>>(),
8877 "critical_count": by_severity.get("critical").map(|v| v.len()).unwrap_or(0),
8878 "high_count": by_severity.get("high").map(|v| v.len()).unwrap_or(0),
8879 "medium_count": by_severity.get("medium").map(|v| v.len()).unwrap_or(0),
8880 "low_count": by_severity.get("low").map(|v| v.len()).unwrap_or(0)
8881 },
8882 "recommendations": self.get_security_recommendations(&vulnerabilities)
8883 }))
8884 }
8885
8886 async fn detect_injection_vulnerabilities(
8888 &self,
8889 server: &CodePrismMcpServer,
8890 exclude_patterns: &[String],
8891 ) -> Result<Vec<serde_json::Value>> {
8892 let mut vulnerabilities = Vec::new();
8893 let functions = server
8894 .graph_store()
8895 .get_nodes_by_kind(codeprism_core::NodeKind::Function);
8896
8897 for function in functions {
8898 if exclude_patterns
8899 .iter()
8900 .any(|pattern| function.file.to_string_lossy().contains(pattern))
8901 {
8902 continue;
8903 }
8904
8905 let function_name_lower = function.name.to_lowercase();
8907 if function_name_lower.contains("sql")
8908 || function_name_lower.contains("query")
8909 || function_name_lower.contains("exec")
8910 {
8911 let references = server.graph_query().find_references(&function.id)?;
8913 let call_count = references
8914 .iter()
8915 .filter(|r| matches!(r.edge_kind, codeprism_core::EdgeKind::Calls))
8916 .count();
8917
8918 if call_count > 0 {
8919 vulnerabilities.push(serde_json::json!({
8920 "type": "Potential SQL Injection",
8921 "severity": "medium",
8922 "function": {
8923 "id": function.id.to_hex(),
8924 "name": function.name,
8925 "file": function.file.display().to_string(),
8926 "location": {
8927 "start_line": function.span.start_line,
8928 "end_line": function.span.end_line
8929 }
8930 },
8931 "description": "Function name suggests SQL operations - ensure proper parameterization",
8932 "recommendation": "Use parameterized queries or prepared statements to prevent SQL injection",
8933 "confidence": 0.6
8934 }));
8935 }
8936 }
8937
8938 if function_name_lower.contains("eval")
8940 || function_name_lower.contains("exec")
8941 || function_name_lower.contains("compile")
8942 {
8943 vulnerabilities.push(serde_json::json!({
8944 "type": "Code Injection Risk",
8945 "severity": "high",
8946 "function": {
8947 "id": function.id.to_hex(),
8948 "name": function.name,
8949 "file": function.file.display().to_string(),
8950 "location": {
8951 "start_line": function.span.start_line,
8952 "end_line": function.span.end_line
8953 }
8954 },
8955 "description": "Function involves dynamic code execution which can be dangerous",
8956 "recommendation": "Avoid dynamic code execution or implement strict input validation",
8957 "confidence": 0.8
8958 }));
8959 }
8960 }
8961
8962 Ok(vulnerabilities)
8963 }
8964
8965 async fn detect_authentication_issues(
8967 &self,
8968 server: &CodePrismMcpServer,
8969 exclude_patterns: &[String],
8970 ) -> Result<Vec<serde_json::Value>> {
8971 let mut vulnerabilities = Vec::new();
8972 let functions = server
8973 .graph_store()
8974 .get_nodes_by_kind(codeprism_core::NodeKind::Function);
8975
8976 for function in functions {
8977 if exclude_patterns
8978 .iter()
8979 .any(|pattern| function.file.to_string_lossy().contains(pattern))
8980 {
8981 continue;
8982 }
8983
8984 let function_name_lower = function.name.to_lowercase();
8985
8986 if function_name_lower.contains("login")
8988 || function_name_lower.contains("auth")
8989 || function_name_lower.contains("signin")
8990 || function_name_lower.contains("password")
8991 {
8992 let variables = server
8994 .graph_store()
8995 .get_nodes_by_kind(codeprism_core::NodeKind::Variable);
8996 let weak_auth_patterns = variables.iter().any(|var| {
8997 let var_name_lower = var.name.to_lowercase();
8998 var.file == function.file
8999 && (var_name_lower.contains("password")
9000 || var_name_lower.contains("secret")
9001 || var_name_lower.contains("token"))
9002 });
9003
9004 if weak_auth_patterns {
9005 vulnerabilities.push(serde_json::json!({
9006 "type": "Authentication Security Concern",
9007 "severity": "medium",
9008 "function": {
9009 "id": function.id.to_hex(),
9010 "name": function.name,
9011 "file": function.file.display().to_string(),
9012 "location": {
9013 "start_line": function.span.start_line,
9014 "end_line": function.span.end_line
9015 }
9016 },
9017 "description": "Authentication function detected - ensure secure implementation",
9018 "recommendation": "Use secure password hashing, implement rate limiting, and secure session management",
9019 "confidence": 0.7
9020 }));
9021 }
9022 }
9023 }
9024
9025 Ok(vulnerabilities)
9026 }
9027
9028 async fn detect_authorization_issues(
9030 &self,
9031 server: &CodePrismMcpServer,
9032 exclude_patterns: &[String],
9033 ) -> Result<Vec<serde_json::Value>> {
9034 let mut vulnerabilities = Vec::new();
9035 let functions = server
9036 .graph_store()
9037 .get_nodes_by_kind(codeprism_core::NodeKind::Function);
9038
9039 for function in functions {
9040 if exclude_patterns
9041 .iter()
9042 .any(|pattern| function.file.to_string_lossy().contains(pattern))
9043 {
9044 continue;
9045 }
9046
9047 let function_name_lower = function.name.to_lowercase();
9048
9049 if function_name_lower.contains("admin")
9051 || function_name_lower.contains("delete")
9052 || function_name_lower.contains("modify")
9053 || function_name_lower.contains("update")
9054 || function_name_lower.contains("create")
9055 {
9056 vulnerabilities.push(serde_json::json!({
9057 "type": "Authorization Check Needed",
9058 "severity": "medium",
9059 "function": {
9060 "id": function.id.to_hex(),
9061 "name": function.name,
9062 "file": function.file.display().to_string(),
9063 "location": {
9064 "start_line": function.span.start_line,
9065 "end_line": function.span.end_line
9066 }
9067 },
9068 "description": "Function performs sensitive operations - ensure proper authorization checks",
9069 "recommendation": "Implement role-based access control and verify user permissions before execution",
9070 "confidence": 0.5
9071 }));
9072 }
9073 }
9074
9075 Ok(vulnerabilities)
9076 }
9077
9078 async fn detect_data_exposure_issues(
9080 &self,
9081 server: &CodePrismMcpServer,
9082 exclude_patterns: &[String],
9083 ) -> Result<Vec<serde_json::Value>> {
9084 let mut vulnerabilities = Vec::new();
9085 let variables = server
9086 .graph_store()
9087 .get_nodes_by_kind(codeprism_core::NodeKind::Variable);
9088
9089 for variable in variables {
9090 if exclude_patterns
9091 .iter()
9092 .any(|pattern| variable.file.to_string_lossy().contains(pattern))
9093 {
9094 continue;
9095 }
9096
9097 let var_name_lower = variable.name.to_lowercase();
9098
9099 if var_name_lower.contains("password")
9101 || var_name_lower.contains("secret")
9102 || var_name_lower.contains("key")
9103 || var_name_lower.contains("token")
9104 || var_name_lower.contains("api_key")
9105 || var_name_lower.contains("private")
9106 {
9107 vulnerabilities.push(serde_json::json!({
9108 "type": "Sensitive Data Exposure",
9109 "severity": "high",
9110 "variable": {
9111 "id": variable.id.to_hex(),
9112 "name": variable.name,
9113 "file": variable.file.display().to_string(),
9114 "location": {
9115 "start_line": variable.span.start_line,
9116 "end_line": variable.span.end_line
9117 }
9118 },
9119 "description": "Variable contains potentially sensitive data",
9120 "recommendation": "Ensure sensitive data is properly encrypted and not logged or exposed",
9121 "confidence": 0.8
9122 }));
9123 }
9124 }
9125
9126 Ok(vulnerabilities)
9127 }
9128
9129 async fn detect_unsafe_patterns(
9131 &self,
9132 server: &CodePrismMcpServer,
9133 exclude_patterns: &[String],
9134 ) -> Result<Vec<serde_json::Value>> {
9135 let mut vulnerabilities = Vec::new();
9136 let functions = server
9137 .graph_store()
9138 .get_nodes_by_kind(codeprism_core::NodeKind::Function);
9139
9140 for function in functions {
9141 if exclude_patterns
9142 .iter()
9143 .any(|pattern| function.file.to_string_lossy().contains(pattern))
9144 {
9145 continue;
9146 }
9147
9148 let function_name_lower = function.name.to_lowercase();
9149
9150 if function_name_lower.contains("unsafe")
9152 || function_name_lower.contains("raw")
9153 || function_name_lower.contains("ptr")
9154 || function_name_lower.contains("malloc")
9155 || function_name_lower.contains("strcpy")
9156 {
9157 vulnerabilities.push(serde_json::json!({
9158 "type": "Unsafe Pattern",
9159 "severity": "medium",
9160 "function": {
9161 "id": function.id.to_hex(),
9162 "name": function.name,
9163 "file": function.file.display().to_string(),
9164 "location": {
9165 "start_line": function.span.start_line,
9166 "end_line": function.span.end_line
9167 }
9168 },
9169 "description": "Function uses potentially unsafe patterns",
9170 "recommendation": "Review for memory safety and input validation",
9171 "confidence": 0.6
9172 }));
9173 }
9174 }
9175
9176 Ok(vulnerabilities)
9177 }
9178
9179 async fn detect_crypto_issues(
9181 &self,
9182 server: &CodePrismMcpServer,
9183 exclude_patterns: &[String],
9184 ) -> Result<Vec<serde_json::Value>> {
9185 let mut vulnerabilities = Vec::new();
9186 let functions = server
9187 .graph_store()
9188 .get_nodes_by_kind(codeprism_core::NodeKind::Function);
9189
9190 for function in functions {
9191 if exclude_patterns
9192 .iter()
9193 .any(|pattern| function.file.to_string_lossy().contains(pattern))
9194 {
9195 continue;
9196 }
9197
9198 let function_name_lower = function.name.to_lowercase();
9199
9200 if function_name_lower.contains("encrypt")
9202 || function_name_lower.contains("decrypt")
9203 || function_name_lower.contains("hash")
9204 || function_name_lower.contains("crypto")
9205 || function_name_lower.contains("cipher")
9206 {
9207 vulnerabilities.push(serde_json::json!({
9208 "type": "Cryptographic Implementation",
9209 "severity": "high",
9210 "function": {
9211 "id": function.id.to_hex(),
9212 "name": function.name,
9213 "file": function.file.display().to_string(),
9214 "location": {
9215 "start_line": function.span.start_line,
9216 "end_line": function.span.end_line
9217 }
9218 },
9219 "description": "Function implements cryptographic operations - ensure secure implementation",
9220 "recommendation": "Use well-tested cryptographic libraries, avoid custom crypto implementations",
9221 "confidence": 0.7
9222 }));
9223 }
9224 }
9225
9226 Ok(vulnerabilities)
9227 }
9228
9229 fn get_security_recommendations(&self, vulnerabilities: &[serde_json::Value]) -> Vec<String> {
9231 let mut recommendations = Vec::new();
9232
9233 let critical_count = vulnerabilities
9234 .iter()
9235 .filter(|v| v.get("severity").and_then(|s| s.as_str()) == Some("critical"))
9236 .count();
9237
9238 let high_count = vulnerabilities
9239 .iter()
9240 .filter(|v| v.get("severity").and_then(|s| s.as_str()) == Some("high"))
9241 .count();
9242
9243 if critical_count > 0 {
9244 recommendations.push(format!(
9245 "URGENT: Address {} critical security vulnerabilities immediately",
9246 critical_count
9247 ));
9248 }
9249
9250 if high_count > 0 {
9251 recommendations.push(format!(
9252 "HIGH PRIORITY: Address {} high-severity security issues",
9253 high_count
9254 ));
9255 }
9256
9257 let injection_count = vulnerabilities
9259 .iter()
9260 .filter(|v| {
9261 v.get("type")
9262 .and_then(|t| t.as_str())
9263 .map(|s| s.contains("Injection"))
9264 .unwrap_or(false)
9265 })
9266 .count();
9267
9268 if injection_count > 0 {
9269 recommendations.push(
9270 "Implement input validation and parameterized queries to prevent injection attacks"
9271 .to_string(),
9272 );
9273 }
9274
9275 let auth_count = vulnerabilities
9276 .iter()
9277 .filter(|v| {
9278 v.get("type")
9279 .and_then(|t| t.as_str())
9280 .map(|s| s.contains("Authentication"))
9281 .unwrap_or(false)
9282 })
9283 .count();
9284
9285 if auth_count > 0 {
9286 recommendations.push(
9287 "Review authentication mechanisms and implement secure password handling"
9288 .to_string(),
9289 );
9290 }
9291
9292 let crypto_count = vulnerabilities
9293 .iter()
9294 .filter(|v| {
9295 v.get("type")
9296 .and_then(|t| t.as_str())
9297 .map(|s| s.contains("Cryptographic"))
9298 .unwrap_or(false)
9299 })
9300 .count();
9301
9302 if crypto_count > 0 {
9303 recommendations.push(
9304 "Use established cryptographic libraries instead of custom implementations"
9305 .to_string(),
9306 );
9307 }
9308
9309 if recommendations.is_empty() {
9310 recommendations.push(
9311 "No significant security vulnerabilities detected with current analysis"
9312 .to_string(),
9313 );
9314 } else {
9315 recommendations.push(
9316 "Conduct regular security audits and implement automated security testing"
9317 .to_string(),
9318 );
9319 recommendations.push("Follow OWASP security guidelines and best practices".to_string());
9320 }
9321
9322 recommendations
9323 }
9324
9325 async fn perform_performance_analysis(
9327 &self,
9328 server: &CodePrismMcpServer,
9329 scope: &str,
9330 analysis_types: &[String],
9331 complexity_threshold: &str,
9332 include_algorithmic_analysis: bool,
9333 detect_bottlenecks: bool,
9334 exclude_patterns: &[String],
9335 ) -> Result<serde_json::Value> {
9336 let mut performance_issues = Vec::new();
9337
9338 if analysis_types.contains(&"time_complexity".to_string())
9340 || analysis_types.contains(&"all".to_string())
9341 {
9342 let complexity_issues = self
9343 .analyze_time_complexity(server, exclude_patterns, include_algorithmic_analysis)
9344 .await?;
9345 performance_issues.extend(complexity_issues);
9346 }
9347
9348 if analysis_types.contains(&"memory_usage".to_string())
9349 || analysis_types.contains(&"all".to_string())
9350 {
9351 let memory_issues = self.analyze_memory_usage(server, exclude_patterns).await?;
9352 performance_issues.extend(memory_issues);
9353 }
9354
9355 if analysis_types.contains(&"hot_spots".to_string())
9356 || analysis_types.contains(&"all".to_string())
9357 {
9358 let hot_spot_issues = self
9359 .detect_performance_hot_spots(server, exclude_patterns, detect_bottlenecks)
9360 .await?;
9361 performance_issues.extend(hot_spot_issues);
9362 }
9363
9364 if analysis_types.contains(&"anti_patterns".to_string())
9365 || analysis_types.contains(&"all".to_string())
9366 {
9367 let anti_pattern_issues = self
9368 .detect_performance_anti_patterns(server, exclude_patterns)
9369 .await?;
9370 performance_issues.extend(anti_pattern_issues);
9371 }
9372
9373 if analysis_types.contains(&"scalability".to_string())
9374 || analysis_types.contains(&"all".to_string())
9375 {
9376 let scalability_issues = self
9377 .analyze_scalability_concerns(server, exclude_patterns)
9378 .await?;
9379 performance_issues.extend(scalability_issues);
9380 }
9381
9382 let complexity_order = ["low", "medium", "high"];
9384 let min_complexity_index = complexity_order
9385 .iter()
9386 .position(|&s| s == complexity_threshold)
9387 .unwrap_or(1);
9388
9389 performance_issues.retain(|issue| {
9390 if let Some(complexity) = issue.get("complexity").and_then(|c| c.as_str()) {
9391 complexity_order
9392 .iter()
9393 .position(|&s| s == complexity)
9394 .unwrap_or(0)
9395 >= min_complexity_index
9396 } else {
9397 true }
9399 });
9400
9401 let mut by_category = std::collections::HashMap::new();
9403 let mut by_complexity = std::collections::HashMap::new();
9404
9405 for issue in &performance_issues {
9406 if let Some(category) = issue.get("category").and_then(|c| c.as_str()) {
9407 by_category
9408 .entry(category.to_string())
9409 .or_insert_with(Vec::new)
9410 .push(issue);
9411 }
9412 if let Some(complexity) = issue.get("complexity").and_then(|c| c.as_str()) {
9413 by_complexity
9414 .entry(complexity.to_string())
9415 .or_insert_with(Vec::new)
9416 .push(issue);
9417 }
9418 }
9419
9420 Ok(serde_json::json!({
9421 "scope": scope,
9422 "analysis_parameters": {
9423 "analysis_types": analysis_types,
9424 "complexity_threshold": complexity_threshold,
9425 "include_algorithmic_analysis": include_algorithmic_analysis,
9426 "detect_bottlenecks": detect_bottlenecks
9427 },
9428 "performance_issues": performance_issues,
9429 "summary": {
9430 "total_issues": performance_issues.len(),
9431 "by_category": by_category.iter().map(|(k, v)| (k.clone(), v.len())).collect::<std::collections::HashMap<_, _>>(),
9432 "by_complexity": by_complexity.iter().map(|(k, v)| (k.clone(), v.len())).collect::<std::collections::HashMap<_, _>>(),
9433 "high_complexity_count": by_complexity.get("high").map(|v| v.len()).unwrap_or(0),
9434 "medium_complexity_count": by_complexity.get("medium").map(|v| v.len()).unwrap_or(0),
9435 "low_complexity_count": by_complexity.get("low").map(|v| v.len()).unwrap_or(0)
9436 },
9437 "recommendations": self.get_performance_recommendations(&performance_issues)
9438 }))
9439 }
9440
9441 async fn analyze_time_complexity(
9443 &self,
9444 server: &CodePrismMcpServer,
9445 exclude_patterns: &[String],
9446 include_algorithmic_analysis: bool,
9447 ) -> Result<Vec<serde_json::Value>> {
9448 let mut issues = Vec::new();
9449 let functions = server
9450 .graph_store()
9451 .get_nodes_by_kind(codeprism_core::NodeKind::Function);
9452
9453 for function in functions {
9454 if exclude_patterns
9455 .iter()
9456 .any(|pattern| function.file.to_string_lossy().contains(pattern))
9457 {
9458 continue;
9459 }
9460
9461 let function_name_lower = function.name.to_lowercase();
9463
9464 if function_name_lower.contains("sort")
9466 || function_name_lower.contains("search")
9467 || function_name_lower.contains("find")
9468 || function_name_lower.contains("filter")
9469 {
9470 let references = server.graph_query().find_references(&function.id)?;
9471 let call_count = references
9472 .iter()
9473 .filter(|r| matches!(r.edge_kind, codeprism_core::EdgeKind::Calls))
9474 .count();
9475
9476 if call_count > 10 {
9477 issues.push(serde_json::json!({
9479 "type": "High Time Complexity Function",
9480 "category": "time_complexity",
9481 "complexity": "high",
9482 "function": {
9483 "id": function.id.to_hex(),
9484 "name": function.name,
9485 "file": function.file.display().to_string(),
9486 "location": {
9487 "start_line": function.span.start_line,
9488 "end_line": function.span.end_line
9489 }
9490 },
9491 "description": format!("Function '{}' appears to involve expensive operations and is frequently called ({} times)", function.name, call_count),
9492 "estimated_complexity": "O(n log n) or worse",
9493 "recommendation": "Consider caching results, optimizing algorithms, or reducing call frequency",
9494 "call_count": call_count
9495 }));
9496 }
9497 }
9498
9499 if include_algorithmic_analysis {
9501 let function_lines = function.span.end_line - function.span.start_line + 1;
9502 let cyclomatic_complexity = self.calculate_cyclomatic_complexity("");
9503
9504 if function_lines > 100 && cyclomatic_complexity > 20 {
9505 issues.push(serde_json::json!({
9506 "type": "Complex Algorithm",
9507 "category": "time_complexity",
9508 "complexity": "medium",
9509 "function": {
9510 "id": function.id.to_hex(),
9511 "name": function.name,
9512 "file": function.file.display().to_string(),
9513 "location": {
9514 "start_line": function.span.start_line,
9515 "end_line": function.span.end_line
9516 }
9517 },
9518 "description": format!("Function '{}' has high complexity ({} lines, complexity: {})", function.name, function_lines, cyclomatic_complexity),
9519 "estimated_complexity": "O(n^2) or worse",
9520 "recommendation": "Break down into smaller functions and optimize algorithms",
9521 "lines_of_code": function_lines,
9522 "cyclomatic_complexity": cyclomatic_complexity
9523 }));
9524 }
9525 }
9526 }
9527
9528 Ok(issues)
9529 }
9530
9531 async fn analyze_memory_usage(
9533 &self,
9534 server: &CodePrismMcpServer,
9535 exclude_patterns: &[String],
9536 ) -> Result<Vec<serde_json::Value>> {
9537 let mut issues = Vec::new();
9538 let functions = server
9539 .graph_store()
9540 .get_nodes_by_kind(codeprism_core::NodeKind::Function);
9541
9542 for function in functions {
9543 if exclude_patterns
9544 .iter()
9545 .any(|pattern| function.file.to_string_lossy().contains(pattern))
9546 {
9547 continue;
9548 }
9549
9550 let function_name_lower = function.name.to_lowercase();
9551
9552 if function_name_lower.contains("load")
9554 || function_name_lower.contains("read")
9555 || function_name_lower.contains("parse")
9556 || function_name_lower.contains("create")
9557 || function_name_lower.contains("build")
9558 {
9559 let variables = server
9561 .graph_store()
9562 .get_nodes_by_kind(codeprism_core::NodeKind::Variable);
9563 let large_data_vars = variables
9564 .iter()
9565 .filter(|var| {
9566 var.file == function.file
9567 && (var.name.to_lowercase().contains("list")
9568 || var.name.to_lowercase().contains("array")
9569 || var.name.to_lowercase().contains("data")
9570 || var.name.to_lowercase().contains("buffer")
9571 || var.name.to_lowercase().contains("cache"))
9572 })
9573 .count();
9574
9575 if large_data_vars > 3 {
9576 issues.push(serde_json::json!({
9577 "type": "High Memory Usage",
9578 "category": "memory_usage",
9579 "complexity": "medium",
9580 "function": {
9581 "id": function.id.to_hex(),
9582 "name": function.name,
9583 "file": function.file.display().to_string(),
9584 "location": {
9585 "start_line": function.span.start_line,
9586 "end_line": function.span.end_line
9587 }
9588 },
9589 "description": format!("Function '{}' uses multiple large data structures ({} variables)", function.name, large_data_vars),
9590 "recommendation": "Consider streaming processing, pagination, or memory pooling",
9591 "large_data_variables": large_data_vars
9592 }));
9593 }
9594 }
9595 }
9596
9597 Ok(issues)
9598 }
9599
9600 async fn detect_performance_hot_spots(
9602 &self,
9603 server: &CodePrismMcpServer,
9604 exclude_patterns: &[String],
9605 detect_bottlenecks: bool,
9606 ) -> Result<Vec<serde_json::Value>> {
9607 let mut issues = Vec::new();
9608 let functions = server
9609 .graph_store()
9610 .get_nodes_by_kind(codeprism_core::NodeKind::Function);
9611
9612 for function in functions {
9613 if exclude_patterns
9614 .iter()
9615 .any(|pattern| function.file.to_string_lossy().contains(pattern))
9616 {
9617 continue;
9618 }
9619
9620 let references = server.graph_query().find_references(&function.id)?;
9622 let call_count = references
9623 .iter()
9624 .filter(|r| matches!(r.edge_kind, codeprism_core::EdgeKind::Calls))
9625 .count();
9626
9627 if call_count > 20 {
9629 let function_lines = function.span.end_line - function.span.start_line + 1;
9630
9631 issues.push(serde_json::json!({
9632 "type": "Performance Hot Spot",
9633 "category": "hot_spots",
9634 "complexity": if call_count > 50 { "high" } else { "medium" },
9635 "function": {
9636 "id": function.id.to_hex(),
9637 "name": function.name,
9638 "file": function.file.display().to_string(),
9639 "location": {
9640 "start_line": function.span.start_line,
9641 "end_line": function.span.end_line
9642 }
9643 },
9644 "description": format!("Function '{}' is called {} times - potential performance hot spot", function.name, call_count),
9645 "recommendation": "Optimize this function as it's frequently called, consider caching or memoization",
9646 "call_count": call_count,
9647 "lines_of_code": function_lines
9648 }));
9649 }
9650
9651 if detect_bottlenecks {
9653 let function_name_lower = function.name.to_lowercase();
9654 if function_name_lower.contains("read")
9655 || function_name_lower.contains("write")
9656 || function_name_lower.contains("fetch")
9657 || function_name_lower.contains("request")
9658 || function_name_lower.contains("query")
9659 {
9660 issues.push(serde_json::json!({
9661 "type": "I/O Bottleneck",
9662 "category": "hot_spots",
9663 "complexity": "medium",
9664 "function": {
9665 "id": function.id.to_hex(),
9666 "name": function.name,
9667 "file": function.file.display().to_string(),
9668 "location": {
9669 "start_line": function.span.start_line,
9670 "end_line": function.span.end_line
9671 }
9672 },
9673 "description": format!("Function '{}' performs I/O operations which can be a bottleneck", function.name),
9674 "recommendation": "Consider async operations, connection pooling, or caching",
9675 "call_count": call_count
9676 }));
9677 }
9678 }
9679 }
9680
9681 Ok(issues)
9682 }
9683
9684 async fn detect_performance_anti_patterns(
9686 &self,
9687 server: &CodePrismMcpServer,
9688 exclude_patterns: &[String],
9689 ) -> Result<Vec<serde_json::Value>> {
9690 let mut issues = Vec::new();
9691 let functions = server
9692 .graph_store()
9693 .get_nodes_by_kind(codeprism_core::NodeKind::Function);
9694
9695 for function in functions {
9696 if exclude_patterns
9697 .iter()
9698 .any(|pattern| function.file.to_string_lossy().contains(pattern))
9699 {
9700 continue;
9701 }
9702
9703 let function_name_lower = function.name.to_lowercase();
9705 if function_name_lower.contains("get") || function_name_lower.contains("fetch") {
9706 let dependencies = server.graph_query().find_dependencies(
9707 &function.id,
9708 codeprism_core::graph::DependencyType::Calls,
9709 )?;
9710 let loop_like_calls = dependencies
9711 .iter()
9712 .filter(|dep| {
9713 let dep_name = dep.target_node.name.to_lowercase();
9714 dep_name.contains("query")
9715 || dep_name.contains("get")
9716 || dep_name.contains("fetch")
9717 })
9718 .count();
9719
9720 if loop_like_calls > 1 {
9721 issues.push(serde_json::json!({
9722 "type": "Potential N+1 Query Pattern",
9723 "category": "anti_patterns",
9724 "complexity": "high",
9725 "function": {
9726 "id": function.id.to_hex(),
9727 "name": function.name,
9728 "file": function.file.display().to_string(),
9729 "location": {
9730 "start_line": function.span.start_line,
9731 "end_line": function.span.end_line
9732 }
9733 },
9734 "description": format!("Function '{}' makes multiple queries/fetches - potential N+1 pattern", function.name),
9735 "recommendation": "Use batch queries, joins, or eager loading to reduce query count",
9736 "query_calls": loop_like_calls
9737 }));
9738 }
9739 }
9740
9741 if function_name_lower.contains("optimize") || function_name_lower.contains("cache") {
9743 let function_lines = function.span.end_line - function.span.start_line + 1;
9744 if function_lines > 50 {
9745 issues.push(serde_json::json!({
9746 "type": "Complex Optimization",
9747 "category": "anti_patterns",
9748 "complexity": "medium",
9749 "function": {
9750 "id": function.id.to_hex(),
9751 "name": function.name,
9752 "file": function.file.display().to_string(),
9753 "location": {
9754 "start_line": function.span.start_line,
9755 "end_line": function.span.end_line
9756 }
9757 },
9758 "description": format!("Function '{}' appears to be a complex optimization - verify it's necessary", function.name),
9759 "recommendation": "Ensure optimization is justified by profiling data",
9760 "lines_of_code": function_lines
9761 }));
9762 }
9763 }
9764 }
9765
9766 Ok(issues)
9767 }
9768
9769 async fn analyze_scalability_concerns(
9771 &self,
9772 server: &CodePrismMcpServer,
9773 exclude_patterns: &[String],
9774 ) -> Result<Vec<serde_json::Value>> {
9775 let mut issues = Vec::new();
9776 let functions = server
9777 .graph_store()
9778 .get_nodes_by_kind(codeprism_core::NodeKind::Function);
9779
9780 for function in functions {
9781 if exclude_patterns
9782 .iter()
9783 .any(|pattern| function.file.to_string_lossy().contains(pattern))
9784 {
9785 continue;
9786 }
9787
9788 let function_name_lower = function.name.to_lowercase();
9789
9790 if function_name_lower.contains("global")
9792 || function_name_lower.contains("singleton")
9793 || function_name_lower.contains("static")
9794 {
9795 issues.push(serde_json::json!({
9796 "type": "Global State Usage",
9797 "category": "scalability",
9798 "complexity": "medium",
9799 "function": {
9800 "id": function.id.to_hex(),
9801 "name": function.name,
9802 "file": function.file.display().to_string(),
9803 "location": {
9804 "start_line": function.span.start_line,
9805 "end_line": function.span.end_line
9806 }
9807 },
9808 "description": format!("Function '{}' uses global state which can limit scalability", function.name),
9809 "recommendation": "Consider dependency injection or stateless design for better scalability"
9810 }));
9811 }
9812
9813 if function_name_lower.contains("wait")
9815 || function_name_lower.contains("sleep")
9816 || function_name_lower.contains("block")
9817 || function_name_lower.contains("sync")
9818 {
9819 issues.push(serde_json::json!({
9820 "type": "Blocking Operation",
9821 "category": "scalability",
9822 "complexity": "high",
9823 "function": {
9824 "id": function.id.to_hex(),
9825 "name": function.name,
9826 "file": function.file.display().to_string(),
9827 "location": {
9828 "start_line": function.span.start_line,
9829 "end_line": function.span.end_line
9830 }
9831 },
9832 "description": format!("Function '{}' performs blocking operations which can hurt scalability", function.name),
9833 "recommendation": "Consider async operations or non-blocking alternatives"
9834 }));
9835 }
9836 }
9837
9838 Ok(issues)
9839 }
9840
9841 fn get_performance_recommendations(&self, issues: &[serde_json::Value]) -> Vec<String> {
9843 let mut recommendations = Vec::new();
9844
9845 let high_complexity_count = issues
9846 .iter()
9847 .filter(|i| i.get("complexity").and_then(|c| c.as_str()) == Some("high"))
9848 .count();
9849
9850 let medium_complexity_count = issues
9851 .iter()
9852 .filter(|i| i.get("complexity").and_then(|c| c.as_str()) == Some("medium"))
9853 .count();
9854
9855 if high_complexity_count > 0 {
9856 recommendations.push(format!(
9857 "HIGH PRIORITY: Address {} high-complexity performance issues",
9858 high_complexity_count
9859 ));
9860 }
9861
9862 if medium_complexity_count > 0 {
9863 recommendations.push(format!(
9864 "MEDIUM PRIORITY: Review {} medium-complexity performance concerns",
9865 medium_complexity_count
9866 ));
9867 }
9868
9869 let hot_spot_count = issues
9871 .iter()
9872 .filter(|i| i.get("category").and_then(|c| c.as_str()) == Some("hot_spots"))
9873 .count();
9874
9875 if hot_spot_count > 0 {
9876 recommendations
9877 .push("Profile hot spots and optimize frequently called functions".to_string());
9878 }
9879
9880 let time_complexity_count = issues
9881 .iter()
9882 .filter(|i| i.get("category").and_then(|c| c.as_str()) == Some("time_complexity"))
9883 .count();
9884
9885 if time_complexity_count > 0 {
9886 recommendations.push(
9887 "Review algorithmic complexity and consider more efficient algorithms".to_string(),
9888 );
9889 }
9890
9891 let memory_count = issues
9892 .iter()
9893 .filter(|i| i.get("category").and_then(|c| c.as_str()) == Some("memory_usage"))
9894 .count();
9895
9896 if memory_count > 0 {
9897 recommendations.push(
9898 "Optimize memory usage with streaming, pagination, or caching strategies"
9899 .to_string(),
9900 );
9901 }
9902
9903 let scalability_count = issues
9904 .iter()
9905 .filter(|i| i.get("category").and_then(|c| c.as_str()) == Some("scalability"))
9906 .count();
9907
9908 if scalability_count > 0 {
9909 recommendations.push(
9910 "Address scalability concerns by reducing global state and blocking operations"
9911 .to_string(),
9912 );
9913 }
9914
9915 if recommendations.is_empty() {
9916 recommendations.push(
9917 "No significant performance issues detected with current analysis".to_string(),
9918 );
9919 } else {
9920 recommendations
9921 .push("Use profiling tools to validate performance assumptions".to_string());
9922 recommendations.push("Implement performance monitoring and alerting".to_string());
9923 recommendations
9924 .push("Consider load testing to validate scalability improvements".to_string());
9925 }
9926
9927 recommendations
9928 }
9929
9930 async fn perform_api_surface_analysis(
9932 &self,
9933 server: &CodePrismMcpServer,
9934 scope: &str,
9935 analysis_types: &[String],
9936 api_version: Option<&str>,
9937 include_private_apis: bool,
9938 check_documentation_coverage: bool,
9939 detect_breaking_changes: bool,
9940 exclude_patterns: &[String],
9941 ) -> Result<serde_json::Value> {
9942 let mut api_issues = Vec::new();
9943
9944 if analysis_types.contains(&"public_api".to_string())
9946 || analysis_types.contains(&"all".to_string())
9947 {
9948 let public_api_analysis = self
9949 .analyze_public_api(server, exclude_patterns, include_private_apis)
9950 .await?;
9951 api_issues.extend(public_api_analysis);
9952 }
9953
9954 if analysis_types.contains(&"versioning".to_string())
9955 || analysis_types.contains(&"all".to_string())
9956 {
9957 let versioning_issues = self
9958 .analyze_api_versioning(server, exclude_patterns, api_version)
9959 .await?;
9960 api_issues.extend(versioning_issues);
9961 }
9962
9963 if analysis_types.contains(&"breaking_changes".to_string())
9964 || analysis_types.contains(&"all".to_string())
9965 {
9966 if detect_breaking_changes {
9967 let breaking_change_issues = self
9968 .detect_api_breaking_changes(server, exclude_patterns)
9969 .await?;
9970 api_issues.extend(breaking_change_issues);
9971 }
9972 }
9973
9974 if analysis_types.contains(&"documentation".to_string())
9975 || analysis_types.contains(&"all".to_string())
9976 {
9977 if check_documentation_coverage {
9978 let doc_coverage_issues = self
9979 .analyze_api_documentation_coverage(server, exclude_patterns)
9980 .await?;
9981 api_issues.extend(doc_coverage_issues);
9982 }
9983 }
9984
9985 if analysis_types.contains(&"compatibility".to_string())
9986 || analysis_types.contains(&"all".to_string())
9987 {
9988 let compatibility_issues = self
9989 .analyze_api_compatibility(server, exclude_patterns, api_version)
9990 .await?;
9991 api_issues.extend(compatibility_issues);
9992 }
9993
9994 let mut by_category = std::collections::HashMap::new();
9996 let mut by_severity = std::collections::HashMap::new();
9997
9998 for issue in &api_issues {
9999 if let Some(category) = issue.get("category").and_then(|c| c.as_str()) {
10000 by_category
10001 .entry(category.to_string())
10002 .or_insert_with(Vec::new)
10003 .push(issue);
10004 }
10005 if let Some(severity) = issue.get("severity").and_then(|s| s.as_str()) {
10006 by_severity
10007 .entry(severity.to_string())
10008 .or_insert_with(Vec::new)
10009 .push(issue);
10010 }
10011 }
10012
10013 Ok(serde_json::json!({
10014 "scope": scope,
10015 "analysis_parameters": {
10016 "analysis_types": analysis_types,
10017 "api_version": api_version,
10018 "include_private_apis": include_private_apis,
10019 "check_documentation_coverage": check_documentation_coverage,
10020 "detect_breaking_changes": detect_breaking_changes
10021 },
10022 "api_issues": api_issues,
10023 "summary": {
10024 "total_issues": api_issues.len(),
10025 "by_category": by_category.iter().map(|(k, v)| (k.clone(), v.len())).collect::<std::collections::HashMap<_, _>>(),
10026 "by_severity": by_severity.iter().map(|(k, v)| (k.clone(), v.len())).collect::<std::collections::HashMap<_, _>>(),
10027 "critical_issues": by_severity.get("critical").map(|v| v.len()).unwrap_or(0),
10028 "high_issues": by_severity.get("high").map(|v| v.len()).unwrap_or(0),
10029 "medium_issues": by_severity.get("medium").map(|v| v.len()).unwrap_or(0),
10030 "low_issues": by_severity.get("low").map(|v| v.len()).unwrap_or(0)
10031 },
10032 "recommendations": self.get_api_recommendations(&api_issues)
10033 }))
10034 }
10035
10036 async fn analyze_public_api(
10038 &self,
10039 server: &CodePrismMcpServer,
10040 exclude_patterns: &[String],
10041 include_private_apis: bool,
10042 ) -> Result<Vec<serde_json::Value>> {
10043 let mut issues = Vec::new();
10044 let functions = server
10045 .graph_store()
10046 .get_nodes_by_kind(codeprism_core::NodeKind::Function);
10047 let classes = server
10048 .graph_store()
10049 .get_nodes_by_kind(codeprism_core::NodeKind::Class);
10050
10051 for function in functions {
10053 if exclude_patterns
10054 .iter()
10055 .any(|pattern| function.file.to_string_lossy().contains(pattern))
10056 {
10057 continue;
10058 }
10059
10060 let function_name = &function.name;
10061 let is_public = self.is_public_api_element(function_name);
10062 let is_private = function_name.starts_with('_') || function_name.contains("private");
10063
10064 if is_public || (include_private_apis && is_private) {
10066 let references = server.graph_query().find_references(&function.id)?;
10067 let external_usage_count = references.len();
10068
10069 issues.push(serde_json::json!({
10070 "type": if is_public { "Public API Function" } else { "Private API Function" },
10071 "category": "public_api",
10072 "severity": if is_public { "medium" } else { "low" },
10073 "function": {
10074 "id": function.id.to_hex(),
10075 "name": function.name,
10076 "file": function.file.display().to_string(),
10077 "location": {
10078 "start_line": function.span.start_line,
10079 "end_line": function.span.end_line
10080 }
10081 },
10082 "description": format!("Function '{}' is part of the {} API surface", function.name, if is_public { "public" } else { "private" }),
10083 "visibility": if is_public { "public" } else { "private" },
10084 "external_usage_count": external_usage_count,
10085 "recommendation": if is_public { "Ensure this function is well-documented and maintains backward compatibility" } else { "Consider if this function should be exposed or kept internal" }
10086 }));
10087 }
10088 }
10089
10090 for class in classes {
10092 if exclude_patterns
10093 .iter()
10094 .any(|pattern| class.file.to_string_lossy().contains(pattern))
10095 {
10096 continue;
10097 }
10098
10099 let class_name = &class.name;
10100 let is_public = self.is_public_api_element(class_name);
10101 let is_private = class_name.starts_with('_') || class_name.contains("Private");
10102
10103 if is_public || (include_private_apis && is_private) {
10104 let edges = server.graph_store().get_outgoing_edges(&class.id);
10106 let public_methods = edges
10107 .iter()
10108 .filter(|edge| {
10109 if let Some(target_node) = server.graph_store().get_node(&edge.target) {
10110 target_node.kind == codeprism_core::NodeKind::Method
10111 && self.is_public_api_element(&target_node.name)
10112 } else {
10113 false
10114 }
10115 })
10116 .count();
10117
10118 issues.push(serde_json::json!({
10119 "type": if is_public { "Public API Class" } else { "Private API Class" },
10120 "category": "public_api",
10121 "severity": if is_public { "medium" } else { "low" },
10122 "class": {
10123 "id": class.id.to_hex(),
10124 "name": class.name,
10125 "file": class.file.display().to_string(),
10126 "location": {
10127 "start_line": class.span.start_line,
10128 "end_line": class.span.end_line
10129 }
10130 },
10131 "description": format!("Class '{}' is part of the {} API surface with {} public methods", class.name, if is_public { "public" } else { "private" }, public_methods),
10132 "visibility": if is_public { "public" } else { "private" },
10133 "public_methods_count": public_methods,
10134 "recommendation": if is_public { "Ensure all public methods are documented and follow API design principles" } else { "Review if this class should be part of the public API" }
10135 }));
10136 }
10137 }
10138
10139 Ok(issues)
10140 }
10141
10142 async fn analyze_api_versioning(
10144 &self,
10145 server: &CodePrismMcpServer,
10146 exclude_patterns: &[String],
10147 api_version: Option<&str>,
10148 ) -> Result<Vec<serde_json::Value>> {
10149 let mut issues = Vec::new();
10150 let functions = server
10151 .graph_store()
10152 .get_nodes_by_kind(codeprism_core::NodeKind::Function);
10153
10154 for function in functions {
10155 if exclude_patterns
10156 .iter()
10157 .any(|pattern| function.file.to_string_lossy().contains(pattern))
10158 {
10159 continue;
10160 }
10161
10162 let function_name = &function.name;
10163
10164 if function_name.contains("_v")
10166 || function_name.contains("V1")
10167 || function_name.contains("V2")
10168 || function_name.contains("version")
10169 {
10170 issues.push(serde_json::json!({
10171 "type": "Versioned API Function",
10172 "category": "versioning",
10173 "severity": "medium",
10174 "function": {
10175 "id": function.id.to_hex(),
10176 "name": function.name,
10177 "file": function.file.display().to_string(),
10178 "location": {
10179 "start_line": function.span.start_line,
10180 "end_line": function.span.end_line
10181 }
10182 },
10183 "description": format!("Function '{}' appears to be version-specific", function.name),
10184 "current_api_version": api_version.unwrap_or("unknown"),
10185 "recommendation": "Ensure versioning strategy is consistent and deprecated versions are properly marked"
10186 }));
10187 }
10188
10189 if function_name.contains("deprecated")
10191 || function_name.contains("legacy")
10192 || function_name.contains("old")
10193 {
10194 issues.push(serde_json::json!({
10195 "type": "Deprecated API Function",
10196 "category": "versioning",
10197 "severity": "high",
10198 "function": {
10199 "id": function.id.to_hex(),
10200 "name": function.name,
10201 "file": function.file.display().to_string(),
10202 "location": {
10203 "start_line": function.span.start_line,
10204 "end_line": function.span.end_line
10205 }
10206 },
10207 "description": format!("Function '{}' appears to be deprecated", function.name),
10208 "recommendation": "Provide migration path and timeline for removal"
10209 }));
10210 }
10211 }
10212
10213 Ok(issues)
10214 }
10215
10216 async fn detect_api_breaking_changes(
10218 &self,
10219 server: &CodePrismMcpServer,
10220 exclude_patterns: &[String],
10221 ) -> Result<Vec<serde_json::Value>> {
10222 let mut issues = Vec::new();
10223 let functions = server
10224 .graph_store()
10225 .get_nodes_by_kind(codeprism_core::NodeKind::Function);
10226
10227 for function in functions {
10228 if exclude_patterns
10229 .iter()
10230 .any(|pattern| function.file.to_string_lossy().contains(pattern))
10231 {
10232 continue;
10233 }
10234
10235 let function_name = &function.name;
10236
10237 let dependencies = server
10239 .graph_query()
10240 .find_dependencies(&function.id, codeprism_core::graph::DependencyType::Direct)?;
10241 let parameter_like_deps = dependencies
10242 .iter()
10243 .filter(|dep| matches!(dep.target_node.kind, codeprism_core::NodeKind::Variable))
10244 .count();
10245
10246 if parameter_like_deps > 5 && self.is_public_api_element(function_name) {
10247 issues.push(serde_json::json!({
10248 "type": "Complex API Function",
10249 "category": "breaking_changes",
10250 "severity": "medium",
10251 "function": {
10252 "id": function.id.to_hex(),
10253 "name": function.name,
10254 "file": function.file.display().to_string(),
10255 "location": {
10256 "start_line": function.span.start_line,
10257 "end_line": function.span.end_line
10258 }
10259 },
10260 "description": format!("Function '{}' has {} parameters - changes could break compatibility", function.name, parameter_like_deps),
10261 "parameter_count": parameter_like_deps,
10262 "recommendation": "Consider using configuration objects or builder patterns to avoid breaking changes"
10263 }));
10264 }
10265
10266 if function_name.contains("remove")
10268 || function_name.contains("delete")
10269 || function_name.contains("drop")
10270 {
10271 if self.is_public_api_element(function_name) {
10272 issues.push(serde_json::json!({
10273 "type": "Potentially Breaking API Function",
10274 "category": "breaking_changes",
10275 "severity": "high",
10276 "function": {
10277 "id": function.id.to_hex(),
10278 "name": function.name,
10279 "file": function.file.display().to_string(),
10280 "location": {
10281 "start_line": function.span.start_line,
10282 "end_line": function.span.end_line
10283 }
10284 },
10285 "description": format!("Function '{}' might remove functionality - potential breaking change", function.name),
10286 "recommendation": "Ensure proper deprecation process and provide alternatives"
10287 }));
10288 }
10289 }
10290 }
10291
10292 Ok(issues)
10293 }
10294
10295 async fn analyze_api_documentation_coverage(
10297 &self,
10298 server: &CodePrismMcpServer,
10299 exclude_patterns: &[String],
10300 ) -> Result<Vec<serde_json::Value>> {
10301 let mut issues = Vec::new();
10302 let functions = server
10303 .graph_store()
10304 .get_nodes_by_kind(codeprism_core::NodeKind::Function);
10305
10306 for function in functions {
10307 if exclude_patterns
10308 .iter()
10309 .any(|pattern| function.file.to_string_lossy().contains(pattern))
10310 {
10311 continue;
10312 }
10313
10314 let function_name = &function.name;
10315
10316 if self.is_public_api_element(function_name) {
10318 let likely_undocumented = !function_name.contains("test")
10320 && !function_name.contains("helper")
10321 && !function_name.starts_with('_');
10322
10323 if likely_undocumented {
10324 let function_lines = function.span.end_line - function.span.start_line + 1;
10325
10326 if function_lines > 5 {
10327 issues.push(serde_json::json!({
10329 "type": "Undocumented API Function",
10330 "category": "documentation",
10331 "severity": "medium",
10332 "function": {
10333 "id": function.id.to_hex(),
10334 "name": function.name,
10335 "file": function.file.display().to_string(),
10336 "location": {
10337 "start_line": function.span.start_line,
10338 "end_line": function.span.end_line
10339 }
10340 },
10341 "description": format!("Public function '{}' may lack adequate documentation", function.name),
10342 "lines_of_code": function_lines,
10343 "recommendation": "Add comprehensive documentation including parameters, return values, and usage examples"
10344 }));
10345 }
10346 }
10347 }
10348 }
10349
10350 Ok(issues)
10351 }
10352
10353 async fn analyze_api_compatibility(
10355 &self,
10356 server: &CodePrismMcpServer,
10357 exclude_patterns: &[String],
10358 api_version: Option<&str>,
10359 ) -> Result<Vec<serde_json::Value>> {
10360 let mut issues = Vec::new();
10361 let functions = server
10362 .graph_store()
10363 .get_nodes_by_kind(codeprism_core::NodeKind::Function);
10364
10365 for function in functions {
10366 if exclude_patterns
10367 .iter()
10368 .any(|pattern| function.file.to_string_lossy().contains(pattern))
10369 {
10370 continue;
10371 }
10372
10373 let function_name = &function.name;
10374
10375 if self.is_public_api_element(function_name) {
10377 let references = server.graph_query().find_references(&function.id)?;
10378 let usage_count = references.len();
10379
10380 if usage_count > 10 {
10382 issues.push(serde_json::json!({
10383 "type": "High-Impact API Function",
10384 "category": "compatibility",
10385 "severity": "high",
10386 "function": {
10387 "id": function.id.to_hex(),
10388 "name": function.name,
10389 "file": function.file.display().to_string(),
10390 "location": {
10391 "start_line": function.span.start_line,
10392 "end_line": function.span.end_line
10393 }
10394 },
10395 "description": format!("Function '{}' has {} usages - changes require careful compatibility planning", function.name, usage_count),
10396 "usage_count": usage_count,
10397 "api_version": api_version.unwrap_or("unknown"),
10398 "recommendation": "Maintain backward compatibility or provide clear migration path"
10399 }));
10400 }
10401 }
10402 }
10403
10404 Ok(issues)
10405 }
10406
10407 fn is_public_api_element(&self, name: &str) -> bool {
10409 !name.starts_with('_')
10411 && !name.contains("private")
10412 && !name.contains("internal")
10413 && !name.contains("test")
10414 && !name.contains("helper")
10415 && !name.contains("util")
10416 }
10417
10418 fn get_api_recommendations(&self, issues: &[serde_json::Value]) -> Vec<String> {
10420 let mut recommendations = Vec::new();
10421
10422 let critical_count = issues
10423 .iter()
10424 .filter(|i| i.get("severity").and_then(|s| s.as_str()) == Some("critical"))
10425 .count();
10426
10427 let high_count = issues
10428 .iter()
10429 .filter(|i| i.get("severity").and_then(|s| s.as_str()) == Some("high"))
10430 .count();
10431
10432 if critical_count > 0 {
10433 recommendations.push(format!(
10434 "CRITICAL: Address {} critical API issues immediately",
10435 critical_count
10436 ));
10437 }
10438
10439 if high_count > 0 {
10440 recommendations.push(format!(
10441 "HIGH PRIORITY: Review {} high-impact API concerns",
10442 high_count
10443 ));
10444 }
10445
10446 let documentation_count = issues
10448 .iter()
10449 .filter(|i| i.get("category").and_then(|c| c.as_str()) == Some("documentation"))
10450 .count();
10451
10452 if documentation_count > 0 {
10453 recommendations.push(
10454 "Improve API documentation coverage for better developer experience".to_string(),
10455 );
10456 }
10457
10458 let breaking_changes_count = issues
10459 .iter()
10460 .filter(|i| i.get("category").and_then(|c| c.as_str()) == Some("breaking_changes"))
10461 .count();
10462
10463 if breaking_changes_count > 0 {
10464 recommendations.push(
10465 "Review potential breaking changes and implement proper deprecation process"
10466 .to_string(),
10467 );
10468 }
10469
10470 let versioning_count = issues
10471 .iter()
10472 .filter(|i| i.get("category").and_then(|c| c.as_str()) == Some("versioning"))
10473 .count();
10474
10475 if versioning_count > 0 {
10476 recommendations.push(
10477 "Establish consistent API versioning strategy and migration paths".to_string(),
10478 );
10479 }
10480
10481 let compatibility_count = issues
10482 .iter()
10483 .filter(|i| i.get("category").and_then(|c| c.as_str()) == Some("compatibility"))
10484 .count();
10485
10486 if compatibility_count > 0 {
10487 recommendations
10488 .push("Maintain backward compatibility for high-usage API functions".to_string());
10489 }
10490
10491 if recommendations.is_empty() {
10492 recommendations.push(
10493 "No significant API surface issues detected with current analysis".to_string(),
10494 );
10495 } else {
10496 recommendations
10497 .push("Implement API design guidelines and review processes".to_string());
10498 recommendations
10499 .push("Consider semantic versioning and changelog maintenance".to_string());
10500 recommendations.push("Set up automated API compatibility testing".to_string());
10501 }
10502
10503 recommendations
10504 }
10505}