Skip to main content
Glama

In Memoria

blueprint.rs14.1 kB
//! Project blueprint analysis - entry points and feature mapping #[cfg(feature = "napi-bindings")] use napi_derive::napi; use crate::types::ParseError; use crate::analysis::FrameworkInfo; use std::path::Path; use std::fs; /// Entry point information #[derive(Debug, Clone)] #[cfg_attr(feature = "napi-bindings", napi(object))] pub struct EntryPoint { pub entry_type: String, // 'web', 'api', 'cli', 'script' pub file_path: String, pub framework: Option<String>, pub confidence: f64, } /// Key directory information #[derive(Debug, Clone)] #[cfg_attr(feature = "napi-bindings", napi(object))] pub struct KeyDirectory { pub path: String, pub dir_type: String, // 'components', 'utils', 'services', etc. pub file_count: u32, } /// Feature mapping information #[derive(Debug, Clone)] #[cfg_attr(feature = "napi-bindings", napi(object))] pub struct FeatureMap { pub id: String, pub feature_name: String, pub primary_files: Vec<String>, pub related_files: Vec<String>, pub dependencies: Vec<String>, } /// Blueprint analyzer for detecting project structure #[cfg_attr(feature = "napi-bindings", napi)] pub struct BlueprintAnalyzer; impl Default for BlueprintAnalyzer { fn default() -> Self { Self::new() } } #[cfg_attr(feature = "napi-bindings", napi)] impl BlueprintAnalyzer { #[cfg_attr(feature = "napi-bindings", napi(constructor))] pub fn new() -> Self { BlueprintAnalyzer } /// Detect entry points using AST-based analysis and pattern matching #[cfg_attr(feature = "napi-bindings", napi)] pub async fn detect_entry_points( path: String, frameworks: Vec<FrameworkInfo>, ) -> Result<Vec<EntryPoint>, ParseError> { let mut entry_points = Vec::new(); let framework_names: Vec<String> = frameworks.iter().map(|f| f.name.clone()).collect(); // Check for common entry point file patterns first Self::check_entry_point_patterns(&path, &framework_names, &mut entry_points)?; // TODO: For v2, add AST-based detection to find programmatic entry points // (main functions, app.listen() calls, etc.) Ok(entry_points) } /// Check common entry point file patterns fn check_entry_point_patterns( project_path: &str, frameworks: &[String], entry_points: &mut Vec<EntryPoint>, ) -> Result<(), ParseError> { let path = Path::new(project_path); // React/Next.js entry points if frameworks.iter().any(|f| { let lower = f.to_lowercase(); lower.contains("react") || lower.contains("next") }) { let react_entries = vec![ "src/index.tsx", "src/index.jsx", "src/App.tsx", "src/App.jsx", "pages/_app.tsx", "pages/_app.js", "app/page.tsx", "app/layout.tsx" // Next.js 13+ ]; for entry in react_entries { let full_path = path.join(entry); if full_path.exists() { entry_points.push(EntryPoint { entry_type: "web".to_string(), file_path: entry.to_string(), framework: Some("react".to_string()), confidence: 0.9, }); } } } // Express/Node API entry points if frameworks.iter().any(|f| { let lower = f.to_lowercase(); lower.contains("express") || lower.contains("node") }) { let api_entries = vec![ "server.js", "app.js", "index.js", "src/server.ts", "src/app.ts", "src/index.ts", "src/main.ts" ]; for entry in api_entries { let full_path = path.join(entry); if full_path.exists() { entry_points.push(EntryPoint { entry_type: "api".to_string(), file_path: entry.to_string(), framework: Some("express".to_string()), confidence: 0.85, }); } } } // Python entry points if frameworks.iter().any(|f| { let lower = f.to_lowercase(); lower.contains("python") || lower.contains("fastapi") || lower.contains("flask") || lower.contains("django") }) { let python_entries = vec![ "main.py", "app.py", "server.py", "api/main.py", "src/main.py", "manage.py" // Django ]; for entry in python_entries { let full_path = path.join(entry); if full_path.exists() { let framework_hint = if frameworks.iter().any(|f| f.to_lowercase().contains("fastapi")) { Some("fastapi".to_string()) } else if frameworks.iter().any(|f| f.to_lowercase().contains("flask")) { Some("flask".to_string()) } else if frameworks.iter().any(|f| f.to_lowercase().contains("django")) { Some("django".to_string()) } else { Some("python".to_string()) }; entry_points.push(EntryPoint { entry_type: "api".to_string(), file_path: entry.to_string(), framework: framework_hint, confidence: 0.85, }); } } } // Rust entry points if frameworks.iter().any(|f| f.to_lowercase().contains("rust")) { let rust_entries = vec!["src/main.rs", "src/lib.rs"]; for entry in rust_entries { let full_path = path.join(entry); if full_path.exists() { let entry_type = if entry.contains("main") { "cli" } else { "library" }; entry_points.push(EntryPoint { entry_type: entry_type.to_string(), file_path: entry.to_string(), framework: Some("rust".to_string()), confidence: 0.95, }); } } } // Go entry points if frameworks.iter().any(|f| f.to_lowercase().contains("go")) { let go_entries = vec!["main.go", "cmd/main.go", "cmd/server/main.go"]; for entry in go_entries { let full_path = path.join(entry); if full_path.exists() { entry_points.push(EntryPoint { entry_type: "api".to_string(), file_path: entry.to_string(), framework: Some("go".to_string()), confidence: 0.9, }); } } } // CLI entry points (language-agnostic) let cli_entries = vec!["cli.js", "bin/cli.js", "src/cli.ts", "src/cli.js"]; for entry in cli_entries { let full_path = path.join(entry); if full_path.exists() { entry_points.push(EntryPoint { entry_type: "cli".to_string(), file_path: entry.to_string(), framework: None, confidence: 0.8, }); } } Ok(()) } /// Map key directories in the project #[cfg_attr(feature = "napi-bindings", napi)] pub async fn map_key_directories(path: String) -> Result<Vec<KeyDirectory>, ParseError> { let mut key_dirs = Vec::new(); let project_path = Path::new(&path); let common_dirs = vec![ ("src/components", "components"), ("src/utils", "utils"), ("src/services", "services"), ("src/api", "api"), ("src/auth", "auth"), ("src/models", "models"), ("src/views", "views"), ("src/pages", "pages"), ("src/lib", "library"), ("lib", "library"), ("utils", "utils"), ("middleware", "middleware"), ("routes", "routes"), ("controllers", "controllers"), ]; for (dir_pattern, dir_type) in common_dirs { let full_path = project_path.join(dir_pattern); if full_path.exists() && full_path.is_dir() { let file_count = Self::count_files_in_directory(&full_path, 5, 0)?; key_dirs.push(KeyDirectory { path: dir_pattern.to_string(), dir_type: dir_type.to_string(), file_count, }); } } Ok(key_dirs) } /// Build feature map for the project #[cfg_attr(feature = "napi-bindings", napi)] pub async fn build_feature_map(path: String) -> Result<Vec<FeatureMap>, ParseError> { let mut feature_maps = Vec::new(); let project_path = Path::new(&path); let feature_patterns: Vec<(&str, Vec<&str>)> = vec![ ("authentication", vec!["auth", "authentication"]), ("api", vec!["api", "routes", "endpoints", "controllers"]), ("database", vec!["db", "database", "models", "schemas", "migrations", "storage"]), ("ui-components", vec!["components", "ui"]), ("views", vec!["views", "pages", "screens"]), ("services", vec!["services", "api-clients"]), ("utilities", vec!["utils", "helpers", "lib"]), ("testing", vec!["tests", "__tests__", "test"]), ("configuration", vec!["config", ".config", "settings"]), ("middleware", vec!["middleware", "middlewares"]), ]; for (feature_name, directories) in feature_patterns { let mut primary_files = Vec::new(); let mut related_files = Vec::new(); for dir in &directories { let src_path = project_path.join("src").join(dir); let alt_path = project_path.join(dir); for check_path in &[src_path, alt_path] { if check_path.exists() && check_path.is_dir() { let files = Self::collect_files_in_directory(check_path, project_path, 5, 0)?; if !files.is_empty() { let mid_point = files.len().div_ceil(2); primary_files.extend_from_slice(&files[0..mid_point]); if mid_point < files.len() { related_files.extend_from_slice(&files[mid_point..]); } } } } } if !primary_files.is_empty() { // Deduplicate primary_files.sort(); primary_files.dedup(); related_files.sort(); related_files.dedup(); feature_maps.push(FeatureMap { id: uuid::Uuid::new_v4().to_string(), feature_name: feature_name.to_string(), primary_files, related_files, dependencies: Vec::new(), }); } } Ok(feature_maps) } /// Count files in directory with depth limit fn count_files_in_directory(dir_path: &Path, max_depth: u32, current_depth: u32) -> Result<u32, ParseError> { if current_depth >= max_depth { return Ok(0); } let mut count = 0; let entries = fs::read_dir(dir_path).map_err(|e| ParseError::from_reason(format!("Failed to read directory: {}", e)))?; for entry in entries.flatten() { let path = entry.path(); let file_name = path.file_name().and_then(|n| n.to_str()).unwrap_or(""); // Skip common ignore patterns if ["node_modules", ".git", "dist", "build", ".next", "__pycache__", "venv", "target"].contains(&file_name) { continue; } if path.is_dir() { count += Self::count_files_in_directory(&path, max_depth, current_depth + 1)?; } else if path.is_file() { count += 1; } } Ok(count) } /// Collect files from directory with depth limit fn collect_files_in_directory( dir_path: &Path, project_root: &Path, max_depth: u32, current_depth: u32, ) -> Result<Vec<String>, ParseError> { if current_depth >= max_depth { return Ok(Vec::new()); } let mut files = Vec::new(); let entries = fs::read_dir(dir_path).map_err(|e| ParseError::from_reason(format!("Failed to read directory: {}", e)))?; for entry in entries.flatten() { let path = entry.path(); let file_name = path.file_name().and_then(|n| n.to_str()).unwrap_or(""); // Skip common ignore patterns if ["node_modules", ".git", "dist", "build", ".next", "__pycache__", "venv", "target"].contains(&file_name) { continue; } if path.is_dir() { let nested = Self::collect_files_in_directory(&path, project_root, max_depth, current_depth + 1)?; files.extend(nested); } else if path.is_file() { // Only include source code files if let Some(ext) = path.extension().and_then(|e| e.to_str()) { if ["ts", "tsx", "js", "jsx", "py", "rs", "go", "java", "c", "cpp", "cs"].contains(&ext) { if let Ok(relative) = path.strip_prefix(project_root) { files.push(relative.to_string_lossy().to_string()); } } } } } Ok(files) } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/pi22by7/In-Memoria'

If you have feedback or need assistance with the MCP directory API, please join our Discord server