1use anyhow::Result;
8use serde::{Deserialize, Serialize};
9use std::path::Path;
10
11use crate::CodePrismMcpServer;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct ResourceCapabilities {
16 #[serde(skip_serializing_if = "Option::is_none")]
18 pub subscribe: Option<bool>,
19 #[serde(rename = "listChanged")]
21 #[serde(skip_serializing_if = "Option::is_none")]
22 pub list_changed: Option<bool>,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct Resource {
28 pub uri: String,
30 #[serde(skip_serializing_if = "Option::is_none")]
32 pub name: Option<String>,
33 #[serde(skip_serializing_if = "Option::is_none")]
35 pub description: Option<String>,
36 #[serde(rename = "mimeType")]
38 #[serde(skip_serializing_if = "Option::is_none")]
39 pub mime_type: Option<String>,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct ResourceContent {
45 pub uri: String,
47 #[serde(rename = "mimeType")]
49 #[serde(skip_serializing_if = "Option::is_none")]
50 pub mime_type: Option<String>,
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub text: Option<String>,
54 #[serde(skip_serializing_if = "Option::is_none")]
56 pub blob: Option<String>,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct ListResourcesParams {
62 #[serde(skip_serializing_if = "Option::is_none")]
64 pub cursor: Option<String>,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct ListResourcesResult {
70 pub resources: Vec<Resource>,
72 #[serde(rename = "nextCursor")]
74 #[serde(skip_serializing_if = "Option::is_none")]
75 pub next_cursor: Option<String>,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct ReadResourceParams {
81 pub uri: String,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct ReadResourceResult {
88 pub contents: Vec<ResourceContent>,
90}
91
92pub struct ResourceManager {
94 server: std::sync::Arc<tokio::sync::RwLock<CodePrismMcpServer>>,
95}
96
97impl ResourceManager {
98 pub fn new(server: std::sync::Arc<tokio::sync::RwLock<CodePrismMcpServer>>) -> Self {
100 Self { server }
101 }
102
103 fn extract_source_context(
105 &self,
106 file_path: &std::path::Path,
107 line_number: usize,
108 context_lines: usize,
109 ) -> Option<serde_json::Value> {
110 let content = match std::fs::read_to_string(file_path) {
112 Ok(content) => content,
113 Err(_) => return None,
114 };
115
116 let lines: Vec<&str> = content.lines().collect();
117 let total_lines = lines.len();
118
119 if line_number == 0 || line_number > total_lines {
120 return None;
121 }
122
123 let target_line_idx = line_number - 1;
125
126 let start_idx = target_line_idx.saturating_sub(context_lines);
128 let end_idx = std::cmp::min(target_line_idx + context_lines, total_lines - 1);
129
130 let mut context_lines_with_numbers = Vec::new();
132 for (i, _) in lines.iter().enumerate().take(end_idx + 1).skip(start_idx) {
133 context_lines_with_numbers.push(serde_json::json!({
134 "line_number": i + 1,
135 "content": lines[i],
136 "is_target": i == target_line_idx
137 }));
138 }
139
140 Some(serde_json::json!({
141 "target_line": line_number,
142 "context_range": {
143 "start_line": start_idx + 1,
144 "end_line": end_idx + 1
145 },
146 "lines": context_lines_with_numbers
147 }))
148 }
149
150 fn create_node_info_with_context(
152 &self,
153 node: &codeprism_core::Node,
154 context_lines: usize,
155 ) -> serde_json::Value {
156 let mut node_info = serde_json::json!({
157 "id": node.id.to_hex(),
158 "name": node.name,
159 "kind": format!("{:?}", node.kind),
160 "language": format!("{:?}", node.lang),
161 "file": node.file.display().to_string(),
162 "span": {
163 "start_line": node.span.start_line,
164 "end_line": node.span.end_line,
165 "start_column": node.span.start_column,
166 "end_column": node.span.end_column
167 },
168 "signature": node.signature
169 });
170
171 if let Some(context) =
173 self.extract_source_context(&node.file, node.span.start_line, context_lines)
174 {
175 node_info["source_context"] = context;
176 }
177
178 node_info
179 }
180
181 pub async fn list_resources(
183 &self,
184 _params: ListResourcesParams,
185 ) -> Result<ListResourcesResult> {
186 let server = self.server.read().await;
187
188 let mut resources = Vec::new();
189
190 if let Some(repo_path) = server.repository_path() {
192 resources.push(Resource {
194 uri: "codeprism://repository/".to_string(),
195 name: Some("Repository Root".to_string()),
196 description: Some("Root directory of the indexed repository".to_string()),
197 mime_type: Some("application/vnd.codeprism.directory".to_string()),
198 });
199
200 resources.push(Resource {
202 uri: "codeprism://repository/stats".to_string(),
203 name: Some("Repository Statistics".to_string()),
204 description: Some("Statistical information about the repository".to_string()),
205 mime_type: Some("application/json".to_string()),
206 });
207
208 resources.push(Resource {
210 uri: "codeprism://repository/config".to_string(),
211 name: Some("Repository Configuration".to_string()),
212 description: Some("Configuration and metadata for the repository".to_string()),
213 mime_type: Some("application/json".to_string()),
214 });
215
216 resources.push(Resource {
218 uri: "codeprism://repository/tree".to_string(),
219 name: Some("File Tree".to_string()),
220 description: Some("Complete file tree structure of the repository".to_string()),
221 mime_type: Some("application/json".to_string()),
222 });
223
224 resources.push(Resource {
226 uri: "codeprism://graph/repository".to_string(),
227 name: Some("Repository Graph".to_string()),
228 description: Some("Graph structure and statistics for the repository".to_string()),
229 mime_type: Some("application/json".to_string()),
230 });
231
232 resources.push(Resource {
234 uri: "codeprism://symbols/functions".to_string(),
235 name: Some("Functions".to_string()),
236 description: Some("All function symbols in the repository".to_string()),
237 mime_type: Some("application/json".to_string()),
238 });
239
240 resources.push(Resource {
241 uri: "codeprism://symbols/classes".to_string(),
242 name: Some("Classes".to_string()),
243 description: Some("All class symbols in the repository".to_string()),
244 mime_type: Some("application/json".to_string()),
245 });
246
247 resources.push(Resource {
248 uri: "codeprism://symbols/variables".to_string(),
249 name: Some("Variables".to_string()),
250 description: Some("All variable symbols in the repository".to_string()),
251 mime_type: Some("application/json".to_string()),
252 });
253
254 resources.push(Resource {
255 uri: "codeprism://symbols/modules".to_string(),
256 name: Some("Modules".to_string()),
257 description: Some("All module symbols in the repository".to_string()),
258 mime_type: Some("application/json".to_string()),
259 });
260
261 resources.push(Resource {
263 uri: "codeprism://metrics/quality_dashboard".to_string(),
264 name: Some("Quality Dashboard".to_string()),
265 description: Some(
266 "Code quality metrics, complexity analysis, and technical debt assessment"
267 .to_string(),
268 ),
269 mime_type: Some("application/json".to_string()),
270 });
271
272 resources.push(Resource {
274 uri: "codeprism://architecture/layers".to_string(),
275 name: Some("Architectural Layers".to_string()),
276 description: Some(
277 "Layer structure identification and architectural organization".to_string(),
278 ),
279 mime_type: Some("application/json".to_string()),
280 });
281
282 resources.push(Resource {
283 uri: "codeprism://architecture/patterns".to_string(),
284 name: Some("Architectural Patterns".to_string()),
285 description: Some(
286 "Detected design patterns and architectural structures".to_string(),
287 ),
288 mime_type: Some("application/json".to_string()),
289 });
290
291 resources.push(Resource {
292 uri: "codeprism://architecture/dependencies".to_string(),
293 name: Some("Architectural Dependencies".to_string()),
294 description: Some(
295 "High-level dependency analysis and architectural dependency graph".to_string(),
296 ),
297 mime_type: Some("application/json".to_string()),
298 });
299
300 if let Ok(scan_result) = server.scanner().discover_files(repo_path) {
302 for file_path in scan_result.iter().take(100) {
303 if let Ok(relative_path) = file_path.strip_prefix(repo_path) {
305 let uri =
306 format!("codeprism://repository/file/{}", relative_path.display());
307 let name = file_path
308 .file_name()
309 .and_then(|n| n.to_str())
310 .unwrap_or("unknown")
311 .to_string();
312
313 let mime_type = detect_mime_type(file_path);
314
315 resources.push(Resource {
316 uri,
317 name: Some(name),
318 description: Some(format!("Source file: {}", relative_path.display())),
319 mime_type: Some(mime_type),
320 });
321 }
322 }
323 }
324 }
325
326 Ok(ListResourcesResult {
327 resources,
328 next_cursor: None, })
330 }
331
332 pub async fn read_resource(&self, params: ReadResourceParams) -> Result<ReadResourceResult> {
334 let server = self.server.read().await;
335
336 let content = if params.uri.starts_with("codeprism://repository/")
337 || params.uri.starts_with("codeprism://graph/")
338 || params.uri.starts_with("codeprism://symbols/")
339 || params.uri.starts_with("codeprism://metrics/")
340 || params.uri.starts_with("codeprism://architecture/")
341 {
342 self.handle_repository_resource(&server, ¶ms.uri)
343 .await?
344 } else {
345 return Err(anyhow::anyhow!("Unsupported resource URI: {}", params.uri));
346 };
347
348 Ok(ReadResourceResult {
349 contents: vec![content],
350 })
351 }
352
353 async fn handle_repository_resource(
355 &self,
356 server: &CodePrismMcpServer,
357 uri: &str,
358 ) -> Result<ResourceContent> {
359 let repo_path = server
360 .repository_path()
361 .ok_or_else(|| anyhow::anyhow!("No repository initialized"))?;
362
363 match uri {
364 "codeprism://repository/" => {
365 let info = serde_json::json!({
367 "path": repo_path.display().to_string(),
368 "name": repo_path.file_name().and_then(|n| n.to_str()).unwrap_or("repository"),
369 "type": "repository_root"
370 });
371
372 Ok(ResourceContent {
373 uri: uri.to_string(),
374 mime_type: Some("application/json".to_string()),
375 text: Some(serde_json::to_string_pretty(&info)?),
376 blob: None,
377 })
378 }
379
380 "codeprism://repository/stats" => {
381 let stats = server.repository_manager().get_total_stats();
383 let stats_json = serde_json::json!({
384 "total_repositories": stats.get("repositories").unwrap_or(&0),
385 "total_files": stats.get("files").unwrap_or(&0),
386 "total_nodes": stats.get("nodes").unwrap_or(&0),
387 "total_edges": stats.get("edges").unwrap_or(&0)
388 });
389
390 Ok(ResourceContent {
391 uri: uri.to_string(),
392 mime_type: Some("application/json".to_string()),
393 text: Some(serde_json::to_string_pretty(&stats_json)?),
394 blob: None,
395 })
396 }
397
398 "codeprism://repository/config" => {
399 let config = serde_json::json!({
401 "path": repo_path.display().to_string(),
402 "scanner_config": {
403 "supported_extensions": ["js", "ts", "py", "java"],
404 "ignore_patterns": [".git", "node_modules", "__pycache__"]
405 }
406 });
407
408 Ok(ResourceContent {
409 uri: uri.to_string(),
410 mime_type: Some("application/json".to_string()),
411 text: Some(serde_json::to_string_pretty(&config)?),
412 blob: None,
413 })
414 }
415
416 "codeprism://repository/tree" => {
417 let files = server.scanner().discover_files(repo_path)?;
419 let tree = files
420 .iter()
421 .filter_map(|path| path.strip_prefix(repo_path).ok())
422 .map(|path| path.display().to_string())
423 .collect::<Vec<_>>();
424
425 let tree_json = serde_json::json!({
426 "files": tree,
427 "total_count": tree.len()
428 });
429
430 Ok(ResourceContent {
431 uri: uri.to_string(),
432 mime_type: Some("application/json".to_string()),
433 text: Some(serde_json::to_string_pretty(&tree_json)?),
434 blob: None,
435 })
436 }
437
438 "codeprism://graph/repository" => {
439 let graph_stats = server.graph_store().get_stats();
441 let graph_json = serde_json::json!({
442 "nodes": graph_stats.total_nodes,
443 "edges": graph_stats.total_edges,
444 "files": graph_stats.total_files,
445 "nodes_by_kind": graph_stats.nodes_by_kind,
446 "last_updated": std::time::SystemTime::now()
447 .duration_since(std::time::UNIX_EPOCH)
448 .unwrap_or_default()
449 .as_secs()
450 });
451
452 Ok(ResourceContent {
453 uri: uri.to_string(),
454 mime_type: Some("application/json".to_string()),
455 text: Some(serde_json::to_string_pretty(&graph_json)?),
456 blob: None,
457 })
458 }
459
460 "codeprism://symbols/functions" => {
461 let functions = server
463 .graph_store()
464 .get_nodes_by_kind(codeprism_core::NodeKind::Function);
465 let functions_json = serde_json::json!(functions
466 .iter()
467 .map(|node| { self.create_node_info_with_context(node, 5) })
468 .collect::<Vec<_>>());
469
470 Ok(ResourceContent {
471 uri: uri.to_string(),
472 mime_type: Some("application/json".to_string()),
473 text: Some(serde_json::to_string_pretty(&functions_json)?),
474 blob: None,
475 })
476 }
477
478 "codeprism://symbols/classes" => {
479 let classes = server
481 .graph_store()
482 .get_nodes_by_kind(codeprism_core::NodeKind::Class);
483 let classes_json = serde_json::json!(classes
484 .iter()
485 .map(|node| { self.create_node_info_with_context(node, 5) })
486 .collect::<Vec<_>>());
487
488 Ok(ResourceContent {
489 uri: uri.to_string(),
490 mime_type: Some("application/json".to_string()),
491 text: Some(serde_json::to_string_pretty(&classes_json)?),
492 blob: None,
493 })
494 }
495
496 "codeprism://symbols/variables" => {
497 let variables = server
499 .graph_store()
500 .get_nodes_by_kind(codeprism_core::NodeKind::Variable);
501 let variables_json = serde_json::json!(variables
502 .iter()
503 .map(|node| { self.create_node_info_with_context(node, 5) })
504 .collect::<Vec<_>>());
505
506 Ok(ResourceContent {
507 uri: uri.to_string(),
508 mime_type: Some("application/json".to_string()),
509 text: Some(serde_json::to_string_pretty(&variables_json)?),
510 blob: None,
511 })
512 }
513
514 "codeprism://symbols/modules" => {
515 let modules = server
517 .graph_store()
518 .get_nodes_by_kind(codeprism_core::NodeKind::Module);
519 let modules_json = serde_json::json!(modules
520 .iter()
521 .map(|node| { self.create_node_info_with_context(node, 5) })
522 .collect::<Vec<_>>());
523
524 Ok(ResourceContent {
525 uri: uri.to_string(),
526 mime_type: Some("application/json".to_string()),
527 text: Some(serde_json::to_string_pretty(&modules_json)?),
528 blob: None,
529 })
530 }
531
532 "codeprism://metrics/quality_dashboard" => {
533 let graph_stats = server.graph_store().get_stats();
535 let functions = server
536 .graph_store()
537 .get_nodes_by_kind(codeprism_core::NodeKind::Function);
538 let classes = server
539 .graph_store()
540 .get_nodes_by_kind(codeprism_core::NodeKind::Class);
541
542 let total_functions = functions.len();
544 let total_classes = classes.len();
545 let average_file_nodes = if graph_stats.total_files > 0 {
546 graph_stats.total_nodes as f64 / graph_stats.total_files as f64
547 } else {
548 0.0
549 };
550
551 let complexity_distribution = serde_json::json!({
553 "low": (total_functions as f64 * 0.6) as usize,
554 "medium": (total_functions as f64 * 0.3) as usize,
555 "high": (total_functions as f64 * 0.1) as usize
556 });
557
558 let technical_debt = serde_json::json!({
560 "large_functions": functions.iter().filter(|f| f.span.len() > 100).count(),
561 "files_with_many_functions": "estimated_based_on_node_distribution",
562 "potential_duplicates": "requires_duplicate_analysis",
563 "complex_classes": classes.iter().filter(|c| {
564 server.graph_store().get_outgoing_edges(&c.id).len() > 15
565 }).count()
566 });
567
568 let maintainability_score = if total_functions > 0 {
570 let large_function_ratio =
571 technical_debt["large_functions"].as_u64().unwrap_or(0) as f64
572 / total_functions as f64;
573 let complex_class_ratio =
574 technical_debt["complex_classes"].as_u64().unwrap_or(0) as f64
575 / total_classes.max(1) as f64;
576 ((1.0 - large_function_ratio * 0.5 - complex_class_ratio * 0.3) * 100.0)
577 .max(0.0)
578 } else {
579 100.0
580 };
581
582 let quality_json = serde_json::json!({
583 "repository_overview": {
584 "total_files": graph_stats.total_files,
585 "total_nodes": graph_stats.total_nodes,
586 "total_edges": graph_stats.total_edges,
587 "average_nodes_per_file": average_file_nodes
588 },
589 "code_structure": {
590 "functions": total_functions,
591 "classes": total_classes,
592 "modules": graph_stats.nodes_by_kind.get(&codeprism_core::NodeKind::Module).unwrap_or(&0),
593 "variables": graph_stats.nodes_by_kind.get(&codeprism_core::NodeKind::Variable).unwrap_or(&0)
594 },
595 "complexity_distribution": complexity_distribution,
596 "technical_debt": technical_debt,
597 "quality_scores": {
598 "overall": ((maintainability_score + 70.0) / 2.0).clamp(0.0, 100.0),
599 "maintainability": maintainability_score,
600 "readability": 75.0
601 },
602 "recommendations": [
603 "Consider refactoring functions longer than 100 lines",
604 "Review classes with more than 15 methods for single responsibility principle",
605 "Use analyze_complexity tool for detailed complexity metrics",
606 "Use detect_patterns tool to identify architectural patterns"
607 ]
608 });
609
610 Ok(ResourceContent {
611 uri: uri.to_string(),
612 mime_type: Some("application/json".to_string()),
613 text: Some(serde_json::to_string_pretty(&quality_json)?),
614 blob: None,
615 })
616 }
617
618 "codeprism://architecture/layers" => {
619 let classes = server
621 .graph_store()
622 .get_nodes_by_kind(codeprism_core::NodeKind::Class);
623 let functions = server
624 .graph_store()
625 .get_nodes_by_kind(codeprism_core::NodeKind::Function);
626
627 let mut layers = std::collections::HashMap::new();
629
630 let presentation_classes = classes
632 .iter()
633 .filter(|c| {
634 let name_lower = c.name.to_lowercase();
635 name_lower.contains("controller")
636 || name_lower.contains("view")
637 || name_lower.contains("ui")
638 || name_lower.contains("component")
639 || c.file.to_string_lossy().contains("view")
640 || c.file.to_string_lossy().contains("ui")
641 })
642 .count();
643
644 let business_classes = classes
646 .iter()
647 .filter(|c| {
648 let name_lower = c.name.to_lowercase();
649 name_lower.contains("service")
650 || name_lower.contains("business")
651 || name_lower.contains("logic")
652 || name_lower.contains("manager")
653 || c.file.to_string_lossy().contains("service")
654 || c.file.to_string_lossy().contains("business")
655 })
656 .count();
657
658 let data_classes = classes
660 .iter()
661 .filter(|c| {
662 let name_lower = c.name.to_lowercase();
663 name_lower.contains("repository")
664 || name_lower.contains("dao")
665 || name_lower.contains("data")
666 || name_lower.contains("model")
667 || c.file.to_string_lossy().contains("repository")
668 || c.file.to_string_lossy().contains("model")
669 })
670 .count();
671
672 let infrastructure_classes = classes
674 .iter()
675 .filter(|c| {
676 let name_lower = c.name.to_lowercase();
677 name_lower.contains("config")
678 || name_lower.contains("util")
679 || name_lower.contains("helper")
680 || name_lower.contains("infrastructure")
681 || c.file.to_string_lossy().contains("config")
682 || c.file.to_string_lossy().contains("util")
683 })
684 .count();
685
686 layers.insert("presentation", presentation_classes);
687 layers.insert("business", business_classes);
688 layers.insert("data", data_classes);
689 layers.insert("infrastructure", infrastructure_classes);
690
691 let all_files = server.graph_store().get_all_files();
693 let mut directory_layers = std::collections::HashMap::new();
694
695 for file in &all_files {
696 if let Some(parent) = file.parent() {
697 let dir_name = parent.file_name().and_then(|n| n.to_str()).unwrap_or("");
698 *directory_layers.entry(dir_name.to_string()).or_insert(0) += 1;
699 }
700 }
701
702 let layers_json = serde_json::json!({
703 "layer_analysis": {
704 "presentation_layer": {
705 "classes": presentation_classes,
706 "description": "Controllers, views, UI components"
707 },
708 "business_layer": {
709 "classes": business_classes,
710 "description": "Business logic, services, managers"
711 },
712 "data_layer": {
713 "classes": data_classes,
714 "description": "Repositories, DAOs, data models"
715 },
716 "infrastructure_layer": {
717 "classes": infrastructure_classes,
718 "description": "Configuration, utilities, infrastructure"
719 }
720 },
721 "directory_structure": directory_layers,
722 "total_classes": classes.len(),
723 "total_functions": functions.len(),
724 "layering_assessment": {
725 "well_layered": presentation_classes > 0 && business_classes > 0 && data_classes > 0,
726 "dominant_layer": layers.iter().max_by_key(|(_, count)| *count).map(|(name, _)| *name).unwrap_or("unclear"),
727 "architectural_style": if presentation_classes > 0 && business_classes > 0 && data_classes > 0 {
728 "Layered Architecture"
729 } else {
730 "Unclear or Monolithic"
731 }
732 }
733 });
734
735 Ok(ResourceContent {
736 uri: uri.to_string(),
737 mime_type: Some("application/json".to_string()),
738 text: Some(serde_json::to_string_pretty(&layers_json)?),
739 blob: None,
740 })
741 }
742
743 "codeprism://architecture/patterns" => {
744 let classes = server
746 .graph_store()
747 .get_nodes_by_kind(codeprism_core::NodeKind::Class);
748 let mut detected_patterns = Vec::new();
749
750 for class in &classes {
752 let methods = server.graph_store().get_outgoing_edges(&class.id);
753 let has_get_instance = methods.iter().any(|edge| {
754 if let Some(target_node) = server.graph_store().get_node(&edge.target) {
755 target_node.name.to_lowercase().contains("getinstance")
756 || target_node.name.to_lowercase().contains("get_instance")
757 } else {
758 false
759 }
760 });
761
762 if has_get_instance {
763 detected_patterns.push(serde_json::json!({
764 "pattern": "Singleton",
765 "class": class.name,
766 "file": class.file.display().to_string(),
767 "confidence": "medium"
768 }));
769 }
770 }
771
772 let factory_classes = classes
774 .iter()
775 .filter(|c| c.name.to_lowercase().contains("factory"))
776 .map(|c| {
777 serde_json::json!({
778 "pattern": "Factory",
779 "class": c.name,
780 "file": c.file.display().to_string(),
781 "confidence": "high"
782 })
783 })
784 .collect::<Vec<_>>();
785
786 detected_patterns.extend(factory_classes);
787
788 let controllers = classes
790 .iter()
791 .filter(|c| c.name.to_lowercase().contains("controller"))
792 .count();
793 let models = classes
794 .iter()
795 .filter(|c| c.name.to_lowercase().contains("model"))
796 .count();
797 let views = classes
798 .iter()
799 .filter(|c| c.name.to_lowercase().contains("view"))
800 .count();
801
802 if controllers > 0 && models > 0 && views > 0 {
803 detected_patterns.push(serde_json::json!({
804 "pattern": "MVC (Model-View-Controller)",
805 "components": {
806 "controllers": controllers,
807 "models": models,
808 "views": views
809 },
810 "confidence": "high"
811 }));
812 }
813
814 let patterns_json = serde_json::json!({
815 "detected_patterns": detected_patterns,
816 "pattern_summary": {
817 "total_patterns": detected_patterns.len(),
818 "design_patterns": detected_patterns.iter().filter(|p|
819 p["pattern"].as_str().unwrap_or("") != "MVC (Model-View-Controller)"
820 ).count(),
821 "architectural_patterns": if controllers > 0 && models > 0 && views > 0 { 1 } else { 0 }
822 },
823 "recommendations": [
824 "Use detect_patterns tool for detailed pattern analysis",
825 "Consider implementing missing patterns for better architecture",
826 "Review pattern implementations for best practices"
827 ]
828 });
829
830 Ok(ResourceContent {
831 uri: uri.to_string(),
832 mime_type: Some("application/json".to_string()),
833 text: Some(serde_json::to_string_pretty(&patterns_json)?),
834 blob: None,
835 })
836 }
837
838 "codeprism://architecture/dependencies" => {
839 let graph_stats = server.graph_store().get_stats();
841 let files = server.graph_store().get_all_files();
842
843 let mut file_dependencies = std::collections::HashMap::new();
845 let mut total_dependencies = 0;
846
847 for file in &files {
848 let nodes = server.graph_store().get_nodes_in_file(file);
849 let mut file_dep_count = 0;
850
851 for node in nodes {
852 let outgoing = server.graph_store().get_outgoing_edges(&node.id);
853 file_dep_count += outgoing.len();
854 total_dependencies += outgoing.len();
855 }
856
857 if let Some(file_name) = file.file_name().and_then(|n| n.to_str()) {
858 file_dependencies.insert(file_name.to_string(), file_dep_count);
859 }
860 }
861
862 let average_dependencies = if !files.is_empty() {
864 total_dependencies as f64 / files.len() as f64
865 } else {
866 0.0
867 };
868
869 let highly_coupled_files: Vec<_> = file_dependencies
870 .iter()
871 .filter(|(_, &count)| count as f64 > average_dependencies * 1.5)
872 .map(|(name, count)| {
873 serde_json::json!({
874 "file": name,
875 "dependencies": count
876 })
877 })
878 .collect();
879
880 let import_edges = graph_stats.total_edges; let potential_cycles = if import_edges > graph_stats.total_nodes {
883 (import_edges as f64 - graph_stats.total_nodes as f64).max(0.0) as usize
884 } else {
885 0
886 };
887
888 let dependencies_json = serde_json::json!({
889 "dependency_overview": {
890 "total_files": files.len(),
891 "total_dependencies": total_dependencies,
892 "average_dependencies_per_file": average_dependencies,
893 "highly_coupled_files": highly_coupled_files.len()
894 },
895 "coupling_analysis": {
896 "files_above_average": file_dependencies.iter()
897 .filter(|(_, &count)| count as f64 > average_dependencies)
898 .count(),
899 "max_dependencies": file_dependencies.values().max().unwrap_or(&0),
900 "min_dependencies": file_dependencies.values().min().unwrap_or(&0)
901 },
902 "highly_coupled_files": highly_coupled_files,
903 "potential_issues": {
904 "potential_cycles": potential_cycles,
905 "coupling_hotspots": highly_coupled_files.len()
906 },
907 "recommendations": [
908 "Use analyze_transitive_dependencies tool for detailed cycle detection",
909 "Consider refactoring highly coupled files",
910 "Review dependency chains for optimization opportunities"
911 ]
912 });
913
914 Ok(ResourceContent {
915 uri: uri.to_string(),
916 mime_type: Some("application/json".to_string()),
917 text: Some(serde_json::to_string_pretty(&dependencies_json)?),
918 blob: None,
919 })
920 }
921
922 uri if uri.starts_with("codeprism://repository/file/") => {
923 let file_path = uri.strip_prefix("codeprism://repository/file/").unwrap();
925 let full_path = repo_path.join(file_path);
926
927 if !full_path.exists() {
928 return Err(anyhow::anyhow!("File not found: {}", file_path));
929 }
930
931 let content = std::fs::read_to_string(&full_path)
932 .map_err(|e| anyhow::anyhow!("Failed to read file {}: {}", file_path, e))?;
933
934 Ok(ResourceContent {
935 uri: uri.to_string(),
936 mime_type: Some(detect_mime_type(&full_path)),
937 text: Some(content),
938 blob: None,
939 })
940 }
941
942 _ => Err(anyhow::anyhow!("Unknown resource URI: {}", uri)),
943 }
944 }
945}
946
947fn detect_mime_type(path: &Path) -> String {
949 match path.extension().and_then(|ext| ext.to_str()) {
950 Some("js") => "application/javascript".to_string(),
951 Some("ts") => "application/typescript".to_string(),
952 Some("py") => "text/x-python".to_string(),
953 Some("java") => "text/x-java-source".to_string(),
954 Some("json") => "application/json".to_string(),
955 Some("md") => "text/markdown".to_string(),
956 Some("txt") => "text/plain".to_string(),
957 Some("html") => "text/html".to_string(),
958 Some("css") => "text/css".to_string(),
959 Some("xml") => "application/xml".to_string(),
960 Some("yaml") | Some("yml") => "application/yaml".to_string(),
961 Some("toml") => "application/toml".to_string(),
962 _ => "text/plain".to_string(),
963 }
964}
965
966#[cfg(test)]
967mod tests {
968 use super::*;
969 use std::fs;
970 use tempfile::TempDir;
971
972 #[tokio::test]
973 async fn test_resource_capabilities() {
974 let capabilities = ResourceCapabilities {
975 subscribe: Some(true),
976 list_changed: Some(true),
977 };
978
979 assert_eq!(capabilities.subscribe, Some(true));
980 assert_eq!(capabilities.list_changed, Some(true));
981 }
982
983 #[test]
984 fn test_resource_serialization() {
985 let resource = Resource {
986 uri: "codeprism://repository/test.py".to_string(),
987 name: Some("test.py".to_string()),
988 description: Some("A Python test file".to_string()),
989 mime_type: Some("text/x-python".to_string()),
990 };
991
992 let json = serde_json::to_string(&resource).unwrap();
993 let deserialized: Resource = serde_json::from_str(&json).unwrap();
994
995 assert_eq!(resource.uri, deserialized.uri);
996 assert_eq!(resource.name, deserialized.name);
997 assert_eq!(resource.description, deserialized.description);
998 assert_eq!(resource.mime_type, deserialized.mime_type);
999 }
1000
1001 #[test]
1002 fn test_mime_type_detection() {
1003 assert_eq!(
1004 detect_mime_type(Path::new("test.js")),
1005 "application/javascript"
1006 );
1007 assert_eq!(detect_mime_type(Path::new("test.py")), "text/x-python");
1008 assert_eq!(
1009 detect_mime_type(Path::new("test.java")),
1010 "text/x-java-source"
1011 );
1012 assert_eq!(detect_mime_type(Path::new("test.unknown")), "text/plain");
1013 }
1014
1015 async fn create_test_server() -> crate::CodePrismMcpServer {
1016 let temp_dir = TempDir::new().expect("Failed to create temp dir");
1017 let repo_path = temp_dir.path();
1018
1019 fs::write(
1021 repo_path.join("main.py"),
1022 r#"
1023class Application:
1024 """Main application class."""
1025
1026 def __init__(self, name: str):
1027 self.name = name
1028 self.users = []
1029
1030 def add_user(self, user: 'User') -> None:
1031 """Add a user to the application."""
1032 self.users.append(user)
1033
1034 def run(self) -> None:
1035 """Run the application."""
1036 print(f"Running {self.name}")
1037
1038class User:
1039 """User class representing a system user."""
1040
1041 def __init__(self, username: str, email: str):
1042 self.username = username
1043 self.email = email
1044
1045 def get_display_name(self) -> str:
1046 """Get the display name for the user."""
1047 return f"{self.username} ({self.email})"
1048
1049def create_app() -> Application:
1050 """Create and configure the application."""
1051 app = Application("MyApp")
1052 return app
1053
1054if __name__ == "__main__":
1055 app = create_app()
1056 user = User("alice", "alice@example.com")
1057 app.add_user(user)
1058 app.run()
1059"#,
1060 )
1061 .unwrap();
1062
1063 fs::write(
1064 repo_path.join("utils.py"),
1065 r#"
1066"""Utility functions for the application."""
1067
1068import os
1069import json
1070from typing import Dict, Any, List, Optional
1071
1072def load_config(config_path: str) -> Dict[str, Any]:
1073 """Load configuration from a JSON file."""
1074 if not os.path.exists(config_path):
1075 return {}
1076
1077 with open(config_path, 'r') as f:
1078 return json.load(f)
1079
1080def validate_email(email: str) -> bool:
1081 """Simple email validation."""
1082 return '@' in email and '.' in email
1083
1084def format_user_list(users: List['User']) -> str:
1085 """Format a list of users for display."""
1086 if not users:
1087 return "No users"
1088
1089 return ', '.join(user.get_display_name() for user in users)
1090
1091class ConfigManager:
1092 """Manages application configuration."""
1093
1094 def __init__(self, config_path: str):
1095 self.config_path = config_path
1096 self.config = load_config(config_path)
1097
1098 def get(self, key: str, default: Any = None) -> Any:
1099 """Get a configuration value."""
1100 return self.config.get(key, default)
1101
1102 def set(self, key: str, value: Any) -> None:
1103 """Set a configuration value."""
1104 self.config[key] = value
1105"#,
1106 )
1107 .unwrap();
1108
1109 fs::write(
1110 repo_path.join("constants.py"),
1111 r#"
1112"""Application constants."""
1113
1114# Database configuration
1115DATABASE_URL = "sqlite:///app.db"
1116MAX_CONNECTIONS = 10
1117
1118# User limits
1119MAX_USERNAME_LENGTH = 50
1120MAX_EMAIL_LENGTH = 100
1121
1122# Application settings
1123APP_NAME = "MyApplication"
1124VERSION = "1.0.0"
1125DEBUG = False
1126
1127# Feature flags
1128ENABLE_LOGGING = True
1129ENABLE_METRICS = False
1130ENABLE_CACHE = True
1131"#,
1132 )
1133 .unwrap();
1134
1135 let mut server = crate::CodePrismMcpServer::new().expect("Failed to create server");
1136 server
1137 .initialize_with_repository(repo_path)
1138 .await
1139 .expect("Failed to initialize repository");
1140
1141 std::mem::forget(temp_dir);
1143
1144 server
1145 }
1146
1147 #[tokio::test]
1148 async fn test_resource_manager_creation() {
1149 let server = create_test_server().await;
1150 let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1151 let _resource_manager = ResourceManager::new(server_arc);
1152
1153 }
1155
1156 #[tokio::test]
1157 async fn test_list_resources_with_repository() {
1158 let server = create_test_server().await;
1159 let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1160 let resource_manager = ResourceManager::new(server_arc);
1161
1162 let params = ListResourcesParams { cursor: None };
1163 let result = resource_manager.list_resources(params).await;
1164 assert!(result.is_ok());
1165
1166 let resources_result = result.unwrap();
1167 assert!(!resources_result.resources.is_empty());
1168 assert!(resources_result.next_cursor.is_none());
1169
1170 let resource_uris: Vec<String> = resources_result
1172 .resources
1173 .iter()
1174 .map(|r| r.uri.clone())
1175 .collect();
1176
1177 assert!(resource_uris
1179 .iter()
1180 .any(|uri| uri == "codeprism://repository/"));
1181 assert!(resource_uris
1182 .iter()
1183 .any(|uri| uri == "codeprism://repository/stats"));
1184 assert!(resource_uris
1185 .iter()
1186 .any(|uri| uri == "codeprism://repository/config"));
1187 assert!(resource_uris
1188 .iter()
1189 .any(|uri| uri == "codeprism://repository/tree"));
1190
1191 assert!(resource_uris
1193 .iter()
1194 .any(|uri| uri == "codeprism://graph/repository"));
1195
1196 assert!(resource_uris
1198 .iter()
1199 .any(|uri| uri == "codeprism://symbols/functions"));
1200 assert!(resource_uris
1201 .iter()
1202 .any(|uri| uri == "codeprism://symbols/classes"));
1203 assert!(resource_uris
1204 .iter()
1205 .any(|uri| uri == "codeprism://symbols/variables"));
1206 assert!(resource_uris
1207 .iter()
1208 .any(|uri| uri == "codeprism://symbols/modules"));
1209
1210 assert!(resource_uris.iter().any(|uri| uri.contains("main.py")));
1212 assert!(resource_uris.iter().any(|uri| uri.contains("utils.py")));
1213 assert!(resource_uris.iter().any(|uri| uri.contains("constants.py")));
1214 }
1215
1216 #[tokio::test]
1217 async fn test_read_repository_root_resource() {
1218 let server = create_test_server().await;
1219 let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1220 let resource_manager = ResourceManager::new(server_arc);
1221
1222 let params = ReadResourceParams {
1223 uri: "codeprism://repository/".to_string(),
1224 };
1225
1226 let result = resource_manager.read_resource(params).await;
1227 assert!(result.is_ok());
1228
1229 let read_result = result.unwrap();
1230 assert_eq!(read_result.contents.len(), 1);
1231
1232 let content = &read_result.contents[0];
1233 assert_eq!(content.uri, "codeprism://repository/");
1234 assert_eq!(content.mime_type, Some("application/json".to_string()));
1235 assert!(content.text.is_some());
1236
1237 let info: serde_json::Value = serde_json::from_str(content.text.as_ref().unwrap()).unwrap();
1238 assert!(info["path"].is_string());
1239 assert_eq!(info["type"].as_str().unwrap(), "repository_root");
1240 }
1241
1242 #[tokio::test]
1243 async fn test_read_repository_stats_resource() {
1244 let server = create_test_server().await;
1245 let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1246 let resource_manager = ResourceManager::new(server_arc);
1247
1248 let params = ReadResourceParams {
1249 uri: "codeprism://repository/stats".to_string(),
1250 };
1251
1252 let result = resource_manager.read_resource(params).await;
1253 assert!(result.is_ok());
1254
1255 let read_result = result.unwrap();
1256 assert_eq!(read_result.contents.len(), 1);
1257
1258 let content = &read_result.contents[0];
1259 assert_eq!(content.uri, "codeprism://repository/stats");
1260 assert_eq!(content.mime_type, Some("application/json".to_string()));
1261 assert!(content.text.is_some());
1262
1263 let stats: serde_json::Value =
1264 serde_json::from_str(content.text.as_ref().unwrap()).unwrap();
1265 assert!(stats["total_files"].is_number());
1266 assert!(stats["total_nodes"].is_number());
1267 assert!(stats["total_edges"].is_number());
1268 }
1269
1270 #[tokio::test]
1271 async fn test_read_repository_config_resource() {
1272 let server = create_test_server().await;
1273 let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1274 let resource_manager = ResourceManager::new(server_arc);
1275
1276 let params = ReadResourceParams {
1277 uri: "codeprism://repository/config".to_string(),
1278 };
1279
1280 let result = resource_manager.read_resource(params).await;
1281 assert!(result.is_ok());
1282
1283 let read_result = result.unwrap();
1284 let content = &read_result.contents[0];
1285
1286 let config: serde_json::Value =
1287 serde_json::from_str(content.text.as_ref().unwrap()).unwrap();
1288 assert!(config["path"].is_string());
1289 assert!(config["scanner_config"].is_object());
1290 assert!(config["scanner_config"]["supported_extensions"].is_array());
1291 }
1292
1293 #[tokio::test]
1294 async fn test_read_file_tree_resource() {
1295 let server = create_test_server().await;
1296 let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1297 let resource_manager = ResourceManager::new(server_arc);
1298
1299 let params = ReadResourceParams {
1300 uri: "codeprism://repository/tree".to_string(),
1301 };
1302
1303 let result = resource_manager.read_resource(params).await;
1304 assert!(result.is_ok());
1305
1306 let read_result = result.unwrap();
1307 let content = &read_result.contents[0];
1308
1309 let tree: serde_json::Value = serde_json::from_str(content.text.as_ref().unwrap()).unwrap();
1310 assert!(tree["files"].is_array());
1311 assert!(tree["total_count"].is_number());
1312
1313 let files = tree["files"].as_array().unwrap();
1314 assert!(files
1315 .iter()
1316 .any(|f| f.as_str().unwrap().contains("main.py")));
1317 assert!(files
1318 .iter()
1319 .any(|f| f.as_str().unwrap().contains("utils.py")));
1320 }
1321
1322 #[tokio::test]
1323 async fn test_read_graph_repository_resource() {
1324 let server = create_test_server().await;
1325 let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1326 let resource_manager = ResourceManager::new(server_arc);
1327
1328 let params = ReadResourceParams {
1329 uri: "codeprism://graph/repository".to_string(),
1330 };
1331
1332 let result = resource_manager.read_resource(params).await;
1333 assert!(result.is_ok());
1334
1335 let read_result = result.unwrap();
1336 let content = &read_result.contents[0];
1337
1338 let graph: serde_json::Value =
1339 serde_json::from_str(content.text.as_ref().unwrap()).unwrap();
1340 assert!(graph["nodes"].is_number());
1341 assert!(graph["edges"].is_number());
1342 assert!(graph["files"].is_number());
1343 assert!(graph["nodes_by_kind"].is_object());
1344 assert!(graph["last_updated"].is_number());
1345 }
1346
1347 #[tokio::test]
1348 async fn test_read_symbols_functions_resource() {
1349 let server = create_test_server().await;
1350 let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1351 let resource_manager = ResourceManager::new(server_arc);
1352
1353 let params = ReadResourceParams {
1354 uri: "codeprism://symbols/functions".to_string(),
1355 };
1356
1357 let result = resource_manager.read_resource(params).await;
1358 assert!(result.is_ok());
1359
1360 let read_result = result.unwrap();
1361 let content = &read_result.contents[0];
1362
1363 let functions: serde_json::Value =
1364 serde_json::from_str(content.text.as_ref().unwrap()).unwrap();
1365 assert!(functions.is_array());
1366
1367 if let Some(first_function) = functions.as_array().unwrap().first() {
1369 assert!(first_function["id"].is_string());
1370 assert!(first_function["name"].is_string());
1371 assert!(first_function["file"].is_string());
1372 assert!(first_function["span"].is_object());
1373 assert!(first_function["language"].is_string());
1374 }
1375 }
1376
1377 #[tokio::test]
1378 async fn test_read_symbols_classes_resource() {
1379 let server = create_test_server().await;
1380 let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1381 let resource_manager = ResourceManager::new(server_arc);
1382
1383 let params = ReadResourceParams {
1384 uri: "codeprism://symbols/classes".to_string(),
1385 };
1386
1387 let result = resource_manager.read_resource(params).await;
1388 assert!(result.is_ok());
1389
1390 let read_result = result.unwrap();
1391 let content = &read_result.contents[0];
1392
1393 let classes: serde_json::Value =
1394 serde_json::from_str(content.text.as_ref().unwrap()).unwrap();
1395 assert!(classes.is_array());
1396 }
1397
1398 #[tokio::test]
1399 async fn test_read_file_resource() {
1400 let server = create_test_server().await;
1401 let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1402 let resource_manager = ResourceManager::new(server_arc);
1403
1404 let params = ReadResourceParams {
1405 uri: "codeprism://repository/file/main.py".to_string(),
1406 };
1407
1408 let result = resource_manager.read_resource(params).await;
1409 assert!(result.is_ok());
1410
1411 let read_result = result.unwrap();
1412 let content = &read_result.contents[0];
1413
1414 assert_eq!(content.uri, "codeprism://repository/file/main.py");
1415 assert_eq!(content.mime_type, Some("text/x-python".to_string()));
1416 assert!(content.text.is_some());
1417
1418 let file_content = content.text.as_ref().unwrap();
1419 assert!(file_content.contains("class Application"));
1420 assert!(file_content.contains("class User"));
1421 assert!(file_content.contains("def create_app"));
1422 }
1423
1424 #[tokio::test]
1425 async fn test_read_nonexistent_file_resource() {
1426 let server = create_test_server().await;
1427 let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1428 let resource_manager = ResourceManager::new(server_arc);
1429
1430 let params = ReadResourceParams {
1431 uri: "codeprism://repository/file/nonexistent.py".to_string(),
1432 };
1433
1434 let result = resource_manager.read_resource(params).await;
1435 assert!(result.is_err());
1436
1437 let error = result.unwrap_err();
1438 assert!(error.to_string().contains("File not found"));
1439 }
1440
1441 #[tokio::test]
1442 async fn test_read_unsupported_resource_uri() {
1443 let server = create_test_server().await;
1444 let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1445 let resource_manager = ResourceManager::new(server_arc);
1446
1447 let params = ReadResourceParams {
1448 uri: "invalid://unsupported/resource".to_string(),
1449 };
1450
1451 let result = resource_manager.read_resource(params).await;
1452 assert!(result.is_err());
1453
1454 let error = result.unwrap_err();
1455 assert!(error.to_string().contains("Unsupported resource URI"));
1456 }
1457
1458 #[tokio::test]
1459 async fn test_read_unknown_repository_resource() {
1460 let server = create_test_server().await;
1461 let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1462 let resource_manager = ResourceManager::new(server_arc);
1463
1464 let params = ReadResourceParams {
1465 uri: "codeprism://repository/unknown_resource".to_string(),
1466 };
1467
1468 let result = resource_manager.read_resource(params).await;
1469 assert!(result.is_err());
1470
1471 let error = result.unwrap_err();
1472 assert!(error.to_string().contains("Unknown resource URI"));
1473 }
1474
1475 #[test]
1476 fn test_resource_content_serialization() {
1477 let content = ResourceContent {
1478 uri: "codeprism://test".to_string(),
1479 mime_type: Some("application/json".to_string()),
1480 text: Some("{}".to_string()),
1481 blob: None,
1482 };
1483
1484 let json = serde_json::to_string(&content).unwrap();
1485 let deserialized: ResourceContent = serde_json::from_str(&json).unwrap();
1486
1487 assert_eq!(content.uri, deserialized.uri);
1488 assert_eq!(content.mime_type, deserialized.mime_type);
1489 assert_eq!(content.text, deserialized.text);
1490 assert_eq!(content.blob, deserialized.blob);
1491 }
1492
1493 #[test]
1494 fn test_list_resources_params_serialization() {
1495 let params = ListResourcesParams {
1496 cursor: Some("test_cursor".to_string()),
1497 };
1498
1499 let json = serde_json::to_string(¶ms).unwrap();
1500 let deserialized: ListResourcesParams = serde_json::from_str(&json).unwrap();
1501
1502 assert_eq!(params.cursor, deserialized.cursor);
1503 }
1504
1505 #[test]
1506 fn test_read_resource_params_serialization() {
1507 let params = ReadResourceParams {
1508 uri: "codeprism://test".to_string(),
1509 };
1510
1511 let json = serde_json::to_string(¶ms).unwrap();
1512 let deserialized: ReadResourceParams = serde_json::from_str(&json).unwrap();
1513
1514 assert_eq!(params.uri, deserialized.uri);
1515 }
1516
1517 #[test]
1518 fn test_additional_mime_types() {
1519 assert_eq!(
1520 detect_mime_type(Path::new("config.json")),
1521 "application/json"
1522 );
1523 assert_eq!(detect_mime_type(Path::new("README.md")), "text/markdown");
1524 assert_eq!(detect_mime_type(Path::new("data.xml")), "application/xml");
1525 assert_eq!(
1526 detect_mime_type(Path::new("config.yaml")),
1527 "application/yaml"
1528 );
1529 assert_eq!(
1530 detect_mime_type(Path::new("config.yml")),
1531 "application/yaml"
1532 );
1533 assert_eq!(
1534 detect_mime_type(Path::new("Cargo.toml")),
1535 "application/toml"
1536 );
1537 assert_eq!(detect_mime_type(Path::new("index.html")), "text/html");
1538 assert_eq!(detect_mime_type(Path::new("styles.css")), "text/css");
1539 assert_eq!(detect_mime_type(Path::new("notes.txt")), "text/plain");
1540 }
1541
1542 #[tokio::test]
1543 async fn test_symbol_resources_include_source_context() {
1544 let server = create_test_server().await;
1545 let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1546 let resource_manager = ResourceManager::new(server_arc.clone());
1547
1548 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
1550
1551 let params = ReadResourceParams {
1553 uri: "codeprism://symbols/functions".to_string(),
1554 };
1555
1556 let result = resource_manager.read_resource(params).await;
1557 assert!(result.is_ok());
1558
1559 let read_result = result.unwrap();
1560 let content = &read_result.contents[0];
1561 assert!(content.text.is_some());
1562
1563 let functions: serde_json::Value =
1564 serde_json::from_str(content.text.as_ref().unwrap()).unwrap();
1565
1566 if let Some(functions_array) = functions.as_array() {
1567 if !functions_array.is_empty() {
1568 let first_function = &functions_array[0];
1569
1570 assert!(first_function["id"].is_string());
1572 assert!(first_function["name"].is_string());
1573 assert!(first_function["kind"].is_string());
1574 assert!(first_function["file"].is_string());
1575
1576 assert!(first_function["source_context"].is_object());
1578 assert!(first_function["source_context"]["target_line"].is_number());
1579 assert!(first_function["source_context"]["lines"].is_array());
1580
1581 let lines = first_function["source_context"]["lines"]
1582 .as_array()
1583 .unwrap();
1584 assert!(!lines.is_empty());
1585
1586 let has_target = lines.iter().any(|line| line["is_target"] == true);
1588 assert!(has_target);
1589 }
1590 }
1591 }
1592
1593 #[tokio::test]
1594 async fn test_classes_resource_with_context() {
1595 let server = create_test_server().await;
1596 let server_arc = std::sync::Arc::new(tokio::sync::RwLock::new(server));
1597 let resource_manager = ResourceManager::new(server_arc);
1598
1599 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
1601
1602 let params = ReadResourceParams {
1603 uri: "codeprism://symbols/classes".to_string(),
1604 };
1605
1606 let result = resource_manager.read_resource(params).await;
1607 assert!(result.is_ok());
1608
1609 let read_result = result.unwrap();
1610 let content = &read_result.contents[0];
1611
1612 let classes: serde_json::Value =
1613 serde_json::from_str(content.text.as_ref().unwrap()).unwrap();
1614
1615 if let Some(classes_array) = classes.as_array() {
1616 if !classes_array.is_empty() {
1617 let first_class = &classes_array[0];
1618
1619 assert!(first_class["source_context"].is_object());
1621 assert!(first_class["source_context"]["target_line"].is_number());
1622 assert!(first_class["source_context"]["lines"].is_array());
1623 }
1624 }
1625 }
1626
1627 #[tokio::test]
1628 async fn test_context_extraction_in_resource_manager() {
1629 use std::fs;
1630 use tempfile::TempDir;
1631
1632 let temp_dir = TempDir::new().unwrap();
1633 let test_file = temp_dir.path().join("test.py");
1634
1635 fs::write(
1637 &test_file,
1638 r#"# Test file
1639def example_function():
1640 """An example function."""
1641 return "hello"
1642"#,
1643 )
1644 .unwrap();
1645
1646 let server = create_test_server().await;
1647 let resource_manager =
1648 ResourceManager::new(std::sync::Arc::new(tokio::sync::RwLock::new(server)));
1649
1650 let context = resource_manager.extract_source_context(&test_file, 2, 2);
1652 assert!(context.is_some());
1653
1654 let context_value = context.unwrap();
1655 assert_eq!(context_value["target_line"], 2);
1656
1657 let lines = context_value["lines"].as_array().unwrap();
1658 assert!(!lines.is_empty());
1659 }
1660
1661 #[tokio::test]
1662 async fn test_architectural_layers_resource() {
1663 let server = create_test_server().await;
1664 let resource_manager =
1665 ResourceManager::new(std::sync::Arc::new(tokio::sync::RwLock::new(server)));
1666
1667 let params = ReadResourceParams {
1668 uri: "codeprism://architecture/layers".to_string(),
1669 };
1670
1671 let result = resource_manager.read_resource(params).await;
1672 assert!(result.is_ok());
1673
1674 let resource_result = result.unwrap();
1675 assert_eq!(resource_result.contents.len(), 1);
1676
1677 let content = &resource_result.contents[0];
1678 assert_eq!(content.uri, "codeprism://architecture/layers");
1679 assert_eq!(content.mime_type, Some("application/json".to_string()));
1680 assert!(content.text.is_some());
1681
1682 let json_text = content.text.as_ref().unwrap();
1684 let parsed: serde_json::Value = serde_json::from_str(json_text).unwrap();
1685 assert!(parsed["layer_analysis"].is_object());
1686 assert!(parsed["directory_structure"].is_object());
1687 assert!(parsed["layering_assessment"].is_object());
1688 }
1689
1690 #[tokio::test]
1691 async fn test_architectural_patterns_resource() {
1692 let server = create_test_server().await;
1693 let resource_manager =
1694 ResourceManager::new(std::sync::Arc::new(tokio::sync::RwLock::new(server)));
1695
1696 let params = ReadResourceParams {
1697 uri: "codeprism://architecture/patterns".to_string(),
1698 };
1699
1700 let result = resource_manager.read_resource(params).await;
1701 assert!(result.is_ok());
1702
1703 let resource_result = result.unwrap();
1704 assert_eq!(resource_result.contents.len(), 1);
1705
1706 let content = &resource_result.contents[0];
1707 assert_eq!(content.uri, "codeprism://architecture/patterns");
1708 assert!(content.text.is_some());
1709
1710 let json_text = content.text.as_ref().unwrap();
1712 let parsed: serde_json::Value = serde_json::from_str(json_text).unwrap();
1713 assert!(parsed["detected_patterns"].is_array());
1714 assert!(parsed["pattern_summary"].is_object());
1715 assert!(parsed["recommendations"].is_array());
1716 }
1717
1718 #[tokio::test]
1719 async fn test_architectural_dependencies_resource() {
1720 let server = create_test_server().await;
1721 let resource_manager =
1722 ResourceManager::new(std::sync::Arc::new(tokio::sync::RwLock::new(server)));
1723
1724 let params = ReadResourceParams {
1725 uri: "codeprism://architecture/dependencies".to_string(),
1726 };
1727
1728 let result = resource_manager.read_resource(params).await;
1729 assert!(result.is_ok());
1730
1731 let resource_result = result.unwrap();
1732 assert_eq!(resource_result.contents.len(), 1);
1733
1734 let content = &resource_result.contents[0];
1735 assert_eq!(content.uri, "codeprism://architecture/dependencies");
1736 assert!(content.text.is_some());
1737
1738 let json_text = content.text.as_ref().unwrap();
1740 let parsed: serde_json::Value = serde_json::from_str(json_text).unwrap();
1741 assert!(parsed["dependency_overview"].is_object());
1742 assert!(parsed["coupling_analysis"].is_object());
1743 assert!(parsed["potential_issues"].is_object());
1744 assert!(parsed["recommendations"].is_array());
1745 }
1746
1747 #[tokio::test]
1748 async fn test_architectural_resources_in_list() {
1749 let server = create_test_server().await;
1750 let resource_manager =
1751 ResourceManager::new(std::sync::Arc::new(tokio::sync::RwLock::new(server)));
1752
1753 let params = ListResourcesParams { cursor: None };
1754 let result = resource_manager.list_resources(params).await;
1755 assert!(result.is_ok());
1756
1757 let resources_result = result.unwrap();
1758 let resource_uris: Vec<&String> =
1759 resources_result.resources.iter().map(|r| &r.uri).collect();
1760
1761 assert!(resource_uris.contains(&&"codeprism://architecture/layers".to_string()));
1763 assert!(resource_uris.contains(&&"codeprism://architecture/patterns".to_string()));
1764 assert!(resource_uris.contains(&&"codeprism://architecture/dependencies".to_string()));
1765
1766 assert!(resources_result.resources.len() >= 12); }
1769
1770 #[tokio::test]
1771 async fn test_enhanced_quality_dashboard() {
1772 let server = create_test_server().await;
1773 let resource_manager =
1774 ResourceManager::new(std::sync::Arc::new(tokio::sync::RwLock::new(server)));
1775
1776 let params = ReadResourceParams {
1777 uri: "codeprism://metrics/quality_dashboard".to_string(),
1778 };
1779
1780 let result = resource_manager.read_resource(params).await;
1781 assert!(result.is_ok());
1782
1783 let resource_result = result.unwrap();
1784 let content = &resource_result.contents[0];
1785 let json_text = content.text.as_ref().unwrap();
1786 let parsed: serde_json::Value = serde_json::from_str(json_text).unwrap();
1787
1788 assert!(parsed["repository_overview"].is_object());
1790 assert!(parsed["code_structure"].is_object());
1791 assert!(parsed["complexity_distribution"].is_object());
1792 assert!(parsed["technical_debt"].is_object());
1793 assert!(parsed["quality_scores"].is_object());
1794 assert!(parsed["recommendations"].is_array());
1795
1796 let quality_scores = &parsed["quality_scores"];
1798 assert!(quality_scores["overall"].is_number());
1799 assert!(quality_scores["maintainability"].is_number());
1800 assert!(quality_scores["readability"].is_number());
1801 }
1802
1803 #[tokio::test]
1804 async fn test_architectural_resource_error_handling() {
1805 let server = create_test_server().await;
1806 let resource_manager =
1807 ResourceManager::new(std::sync::Arc::new(tokio::sync::RwLock::new(server)));
1808
1809 let params = ReadResourceParams {
1811 uri: "codeprism://architecture/invalid".to_string(),
1812 };
1813
1814 let result = resource_manager.read_resource(params).await;
1815 assert!(result.is_err()); }
1817}