1use std::fs;
4use std::path::PathBuf;
5
6use super::storage::MemoryStorage;
7use super::types::{MemoryCategory, MemoryEntry};
8
9pub 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
99pub 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 entries.push(MemoryEntry::new(
200 MemoryCategory::Technical,
201 format!("项目技术栈: {}", config.tech_stack),
202 None,
203 ));
204
205 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 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
227pub 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}