acp/
scan.rs

1//! @acp:module "Scanner"
2//! @acp:summary "Project scanning and auto-detection"
3//! @acp:domain cli
4//! @acp:layer service
5
6use std::collections::HashMap;
7use std::path::Path;
8use walkdir::WalkDir;
9
10/// Detected project information
11#[derive(Debug, Default)]
12pub struct ProjectScan {
13    pub languages: Vec<DetectedLanguage>,
14    pub has_package_json: bool,
15    pub has_cargo_toml: bool,
16    pub has_pyproject_toml: bool,
17    pub has_go_mod: bool,
18    // pub mcp_available: bool,  // TODO: Detect MCP server availability
19}
20
21#[derive(Debug, Clone)]
22pub struct DetectedLanguage {
23    pub name: &'static str,
24    pub patterns: Vec<&'static str>,
25    pub file_count: usize,
26}
27
28/// Scan project directory to detect languages and configuration
29pub fn scan_project<P: AsRef<Path>>(root: P) -> ProjectScan {
30    let root = root.as_ref();
31    let mut ext_counts: HashMap<String, usize> = HashMap::new();
32    let mut scan = ProjectScan::default();
33
34    for entry in WalkDir::new(root)
35        .max_depth(10)
36        .into_iter()
37        .filter_map(|e| e.ok())
38    {
39        let path = entry.path();
40
41        // Skip common non-source directories
42        if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
43            if matches!(
44                name,
45                "node_modules" | "target" | "dist" | "build" | ".git" | "vendor" | "__pycache__"
46            ) {
47                continue;
48            }
49        }
50
51        // Check for project files
52        if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
53            match name {
54                "package.json" => scan.has_package_json = true,
55                "Cargo.toml" => scan.has_cargo_toml = true,
56                "pyproject.toml" | "setup.py" => scan.has_pyproject_toml = true,
57                "go.mod" => scan.has_go_mod = true,
58                _ => {}
59            }
60        }
61
62        // Count file extensions
63        if path.is_file() {
64            if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
65                let ext = ext.to_lowercase();
66                *ext_counts.entry(ext).or_insert(0) += 1;
67            }
68        }
69    }
70
71    // Map extensions to languages
72    let lang_mappings: [(&str, &[&str], &[&str]); 6] = [
73        ("TypeScript", &["ts", "tsx"], &["**/*.ts", "**/*.tsx"]),
74        (
75            "JavaScript",
76            &["js", "jsx", "mjs"],
77            &["**/*.js", "**/*.jsx", "**/*.mjs"],
78        ),
79        ("Rust", &["rs"], &["**/*.rs"]),
80        ("Python", &["py"], &["**/*.py"]),
81        ("Go", &["go"], &["**/*.go"]),
82        ("Java", &["java"], &["**/*.java"]),
83    ];
84
85    for (name, exts, patterns) in lang_mappings {
86        let count: usize = exts.iter().filter_map(|e| ext_counts.get(*e)).sum();
87
88        if count > 0 {
89            scan.languages.push(DetectedLanguage {
90                name,
91                patterns: patterns.to_vec(),
92                file_count: count,
93            });
94        }
95    }
96
97    // Sort by file count descending
98    scan.languages
99        .sort_by(|a, b| b.file_count.cmp(&a.file_count));
100
101    // TODO: MCP detection (commented out for future implementation)
102    // scan.mcp_available = detect_mcp_server();
103
104    scan
105}
106
107// TODO: Detect MCP server availability
108// This will be used in the future to determine if we can use MCP for enhanced functionality
109//
110// fn detect_mcp_server() -> bool {
111//     // Check for MCP server configuration
112//     // Option 1: Check for claude_desktop_config.json
113//     // let config_path = dirs::config_dir()
114//     //     .map(|d| d.join("Claude").join("claude_desktop_config.json"));
115//     // if let Some(path) = config_path {
116//     //     if path.exists() {
117//     //         // Parse and check for ACP MCP server
118//     //         return true;
119//     //     }
120//     // }
121//     //
122//     // Option 2: Try to connect to MCP server
123//     // Option 3: Check for .mcp.json in project root
124//     //
125//     false
126// }