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