Skip to main content

matrixcode_core/memory/
project.rs

1//! Project structure analysis for automatic memory creation.
2
3use std::fs;
4use std::path::PathBuf;
5
6use super::storage::MemoryStorage;
7use super::types::{MemoryCategory, MemoryEntry};
8
9// ============================================================================
10// Project Type Configuration
11// ============================================================================
12
13/// Project type detection configuration.
14pub struct ProjectTypeConfig {
15    pub type_name: &'static str,
16    pub detect_files: &'static [&'static str],
17    pub entry_files: &'static [&'static str],
18    pub key_dirs: &'static [&'static str],
19    pub tech_stack: &'static str,
20}
21
22pub const PROJECT_TYPE_CONFIGS: &[ProjectTypeConfig] = &[
23    ProjectTypeConfig {
24        type_name: "Rust",
25        detect_files: &["Cargo.toml"],
26        entry_files: &["src/main.rs", "src/lib.rs"],
27        key_dirs: &["src", "tests", "examples"],
28        tech_stack: "Rust",
29    },
30    ProjectTypeConfig {
31        type_name: "Node.js",
32        detect_files: &["package.json"],
33        entry_files: &["index.js", "src/index.js", "app.js"],
34        key_dirs: &["src", "lib", "components", "pages"],
35        tech_stack: "Node.js",
36    },
37    ProjectTypeConfig {
38        type_name: "TypeScript",
39        detect_files: &["tsconfig.json", "package.json"],
40        entry_files: &["src/index.ts", "src/main.ts"],
41        key_dirs: &["src", "lib", "components"],
42        tech_stack: "TypeScript",
43    },
44    ProjectTypeConfig {
45        type_name: "React",
46        detect_files: &["package.json"],
47        entry_files: &["src/index.tsx", "src/App.tsx"],
48        key_dirs: &["src/components", "src/pages", "src/hooks"],
49        tech_stack: "React + TypeScript",
50    },
51    ProjectTypeConfig {
52        type_name: "Vue",
53        detect_files: &["vue.config.js", "vite.config.js", "package.json"],
54        entry_files: &["src/main.ts", "src/App.vue"],
55        key_dirs: &["src/components", "src/views", "src/stores"],
56        tech_stack: "Vue.js",
57    },
58    ProjectTypeConfig {
59        type_name: "Python",
60        detect_files: &["requirements.txt", "setup.py", "pyproject.toml"],
61        entry_files: &["main.py", "app.py", "__main__.py"],
62        key_dirs: &["src", "lib", "tests", "app"],
63        tech_stack: "Python",
64    },
65    ProjectTypeConfig {
66        type_name: "Go",
67        detect_files: &["go.mod"],
68        entry_files: &["main.go", "cmd/main.go"],
69        key_dirs: &["cmd", "pkg", "internal", "api"],
70        tech_stack: "Go",
71    },
72    ProjectTypeConfig {
73        type_name: "Java",
74        detect_files: &["pom.xml", "build.gradle"],
75        entry_files: &["src/main/java/Main.java"],
76        key_dirs: &["src/main/java", "src/test/java"],
77        tech_stack: "Java",
78    },
79];
80
81pub const IGNORE_DIRS: &[&str] = &[
82    ".git",
83    ".github",
84    ".matrix",
85    ".idea",
86    ".vscode",
87    "node_modules",
88    "target",
89    "build",
90    "dist",
91    "out",
92    "vendor",
93    "__pycache__",
94    ".venv",
95    "venv",
96    "env",
97];
98
99// ============================================================================
100// Project Structure Analyzer
101// ============================================================================
102
103pub struct ProjectStructureAnalyzer {
104    project_root: PathBuf,
105}
106
107impl ProjectStructureAnalyzer {
108    pub fn new(project_root: PathBuf) -> Self {
109        Self { project_root }
110    }
111
112    pub fn detect_project_type(&self) -> Option<&'static ProjectTypeConfig> {
113        for config in PROJECT_TYPE_CONFIGS {
114            for detect_file in config.detect_files {
115                if detect_file.starts_with('*') {
116                    let extension = detect_file.trim_start_matches('*');
117                    if let Ok(entries) = fs::read_dir(&self.project_root) {
118                        for entry in entries.flatten() {
119                            if entry.file_name().to_string_lossy().ends_with(extension) {
120                                return Some(config);
121                            }
122                        }
123                    }
124                } else {
125                    let path = self.project_root.join(detect_file);
126                    if path.exists() {
127                        return Some(config);
128                    }
129                }
130            }
131        }
132        None
133    }
134
135    pub fn find_entry_file(&self, config: &ProjectTypeConfig) -> Option<String> {
136        for entry_file in config.entry_files {
137            let path = self.project_root.join(entry_file);
138            if path.exists() {
139                return Some(entry_file.to_string());
140            }
141        }
142        None
143    }
144
145    pub fn scan_key_directories(&self, config: &ProjectTypeConfig) -> Vec<(String, String)> {
146        let mut dirs_info: Vec<(String, String)> = Vec::new();
147
148        for key_dir in config.key_dirs {
149            let path = self.project_root.join(key_dir);
150            if path.exists() && path.is_dir() {
151                let purpose = self.infer_directory_purpose(key_dir);
152                dirs_info.push((key_dir.to_string(), purpose));
153            }
154        }
155
156        dirs_info
157    }
158
159    fn infer_directory_purpose(&self, dir_name: &str) -> String {
160        let dir_lower = dir_name.to_lowercase();
161
162        if dir_lower.contains("component") {
163            return "组件目录".to_string();
164        }
165        if dir_lower.contains("page") || dir_lower == "views" {
166            return "页面目录".to_string();
167        }
168        if dir_lower.contains("hook") {
169            return "Hook目录".to_string();
170        }
171        if dir_lower.contains("util") || dir_lower == "lib" {
172            return "工具目录".to_string();
173        }
174        if dir_lower.contains("test") {
175            return "测试目录".to_string();
176        }
177        if dir_lower.contains("api") {
178            return "API目录".to_string();
179        }
180        if dir_lower.contains("model") {
181            return "模型目录".to_string();
182        }
183        if dir_lower.contains("service") {
184            return "服务目录".to_string();
185        }
186
187        "代码目录".to_string()
188    }
189
190    pub fn generate_memories(&self) -> Vec<MemoryEntry> {
191        let mut entries: Vec<MemoryEntry> = Vec::new();
192
193        let config = match self.detect_project_type() {
194            Some(c) => c,
195            None => return entries,
196        };
197
198        // Tech stack
199        entries.push(MemoryEntry::new(
200            MemoryCategory::Technical,
201            format!("项目技术栈: {}", config.tech_stack),
202            None,
203        ));
204
205        // Entry file
206        if let Some(entry) = self.find_entry_file(config) {
207            entries.push(MemoryEntry::new(
208                MemoryCategory::Structure,
209                format!("入口文件: {}", entry),
210                None,
211            ));
212        }
213
214        // Key directories
215        for (dir, purpose) in self.scan_key_directories(config) {
216            entries.push(MemoryEntry::new(
217                MemoryCategory::Structure,
218                format!("{} 是 {}", dir, purpose),
219                None,
220            ));
221        }
222
223        entries
224    }
225}
226
227/// Generate project structure memories and save.
228pub fn generate_project_structure_memories(
229    project_root: &std::path::Path,
230    storage: &mut MemoryStorage,
231) -> usize {
232    let analyzer = ProjectStructureAnalyzer::new(project_root.to_path_buf());
233    let entries = analyzer.generate_memories();
234
235    let count = entries.len();
236    for entry in entries {
237        if let Err(e) = storage.add_entry(entry, true) {
238            log::warn!("Failed to save project memory: {}", e);
239        }
240    }
241
242    count
243}