Skip to main content

aster/map/
chunked_generator.rs

1//! 分块代码蓝图生成器
2//!
3//! 核心策略:
4//! 1. 复用 EnhancedOntologyGenerator 生成完整蓝图
5//! 2. 按目录拆分成多个 chunk 文件
6//! 3. 生成轻量级 index.json
7
8use std::collections::HashMap;
9use std::path::{Path, PathBuf};
10
11use super::enhanced_generator::EnhancedOntologyGenerator;
12use super::types_chunked::*;
13use super::types_enhanced::*;
14
15/// 分块蓝图生成器
16pub struct ChunkedBlueprintGenerator {
17    root_path: PathBuf,
18    options: ChunkedGenerateOptions,
19    map_dir: PathBuf,
20    chunks_dir: PathBuf,
21}
22
23impl ChunkedBlueprintGenerator {
24    pub fn new(root_path: impl AsRef<Path>, options: Option<ChunkedGenerateOptions>) -> Self {
25        let root = root_path.as_ref().to_path_buf();
26        let opts = options.unwrap_or_default();
27        let map_dir = opts
28            .output_dir
29            .as_ref()
30            .map(PathBuf::from)
31            .unwrap_or_else(|| root.join(".claude").join("map"));
32        let chunks_dir = map_dir.join("chunks");
33
34        Self {
35            root_path: root,
36            options: opts,
37            map_dir,
38            chunks_dir,
39        }
40    }
41
42    /// 生成分块蓝图
43    pub fn generate(&self) -> std::io::Result<()> {
44        // 1. 生成完整蓝图
45        let generator = EnhancedOntologyGenerator::new(&self.root_path, None);
46        let blueprint = generator.generate();
47
48        // 2. 确保目录存在
49        std::fs::create_dir_all(&self.map_dir)?;
50        std::fs::create_dir_all(&self.chunks_dir)?;
51
52        // 3. 按目录分组模块
53        let chunks = self.group_modules_by_directory(&blueprint.modules);
54
55        // 4. 生成每个 chunk 文件
56        let chunk_metadata = self.generate_chunks(&chunks, &blueprint)?;
57
58        // 5. 生成 index.json
59        let index = self.build_index_file(&blueprint, &chunks, &chunk_metadata);
60        let index_path = self.map_dir.join("index.json");
61        let json = serde_json::to_string_pretty(&index)?;
62        std::fs::write(index_path, json)?;
63
64        Ok(())
65    }
66
67    fn group_modules_by_directory(
68        &self,
69        modules: &HashMap<String, EnhancedModule>,
70    ) -> HashMap<String, Vec<EnhancedModule>> {
71        let mut chunks: HashMap<String, Vec<EnhancedModule>> = HashMap::new();
72
73        for module in modules.values() {
74            let dir_path = self.get_module_directory(&module.id);
75            chunks.entry(dir_path).or_default().push(module.clone());
76        }
77
78        chunks
79    }
80
81    fn get_module_directory(&self, module_id: &str) -> String {
82        let path = Path::new(module_id);
83        path.parent()
84            .map(|p| p.to_string_lossy().to_string())
85            .unwrap_or_default()
86    }
87
88    fn generate_chunks(
89        &self,
90        chunks: &HashMap<String, Vec<EnhancedModule>>,
91        blueprint: &EnhancedCodeBlueprint,
92    ) -> std::io::Result<HashMap<String, ChunkMetadata>> {
93        let mut metadata_map = HashMap::new();
94
95        for (dir_path, modules) in chunks {
96            let chunk_data = self.build_chunk_file(dir_path, modules, blueprint);
97            let metadata = self.write_chunk_file(dir_path, &chunk_data)?;
98            metadata_map.insert(dir_path.clone(), metadata);
99        }
100
101        Ok(metadata_map)
102    }
103
104    fn build_chunk_file(
105        &self,
106        dir_path: &str,
107        modules: &[EnhancedModule],
108        blueprint: &EnhancedCodeBlueprint,
109    ) -> ChunkData {
110        let module_ids: std::collections::HashSet<_> =
111            modules.iter().map(|m| m.id.clone()).collect();
112
113        let chunk_modules: HashMap<String, EnhancedModule> =
114            modules.iter().map(|m| (m.id.clone(), m.clone())).collect();
115
116        let chunk_symbols: HashMap<String, SymbolEntry> = blueprint
117            .symbols
118            .iter()
119            .filter(|(_, s)| module_ids.contains(&s.module_id))
120            .map(|(k, v)| (k.clone(), v.clone()))
121            .collect();
122
123        let chunk_refs = ChunkReferences {
124            module_deps: blueprint
125                .references
126                .module_deps
127                .iter()
128                .filter(|d| module_ids.contains(&d.source) || module_ids.contains(&d.target))
129                .cloned()
130                .collect(),
131            symbol_calls: blueprint
132                .references
133                .symbol_calls
134                .iter()
135                .filter(|c| {
136                    let caller_mod = c.caller.split("::").next().unwrap_or("");
137                    let callee_mod = c.callee.split("::").next().unwrap_or("");
138                    module_ids.contains(caller_mod) || module_ids.contains(callee_mod)
139                })
140                .cloned()
141                .collect(),
142            type_refs: blueprint
143                .references
144                .type_refs
145                .iter()
146                .filter(|r| {
147                    let child_mod = r.child.split("::").next().unwrap_or("");
148                    let parent_mod = r.parent.split("::").next().unwrap_or("");
149                    module_ids.contains(child_mod) || module_ids.contains(parent_mod)
150                })
151                .cloned()
152                .collect(),
153        };
154
155        ChunkData {
156            path: dir_path.to_string(),
157            modules: chunk_modules,
158            symbols: chunk_symbols,
159            references: chunk_refs,
160            metadata: None,
161            planned_modules: None,
162            refactoring_tasks: None,
163            module_design_meta: None,
164        }
165    }
166
167    fn write_chunk_file(
168        &self,
169        dir_path: &str,
170        chunk_data: &ChunkData,
171    ) -> std::io::Result<ChunkMetadata> {
172        let chunk_file_name = self.get_chunk_file_name(dir_path);
173        let chunk_path = self.chunks_dir.join(&chunk_file_name);
174
175        let json = serde_json::to_string_pretty(chunk_data)?;
176
177        let checksum = if self.options.with_checksum {
178            use std::hash::{Hash, Hasher};
179            let mut hasher = std::collections::hash_map::DefaultHasher::new();
180            json.hash(&mut hasher);
181            format!("{:x}", hasher.finish())
182        } else {
183            String::new()
184        };
185
186        std::fs::write(chunk_path, &json)?;
187
188        Ok(ChunkMetadata {
189            last_modified: chrono::Utc::now().to_rfc3339(),
190            module_count: chunk_data.modules.len(),
191            checksum,
192        })
193    }
194
195    fn get_chunk_file_name(&self, dir_path: &str) -> String {
196        if dir_path.is_empty() {
197            "root.json".to_string()
198        } else {
199            format!("{}.json", dir_path.replace(['/', '\\'], "_"))
200        }
201    }
202
203    fn build_index_file(
204        &self,
205        blueprint: &EnhancedCodeBlueprint,
206        chunks: &HashMap<String, Vec<EnhancedModule>>,
207        _chunk_metadata: &HashMap<String, ChunkMetadata>,
208    ) -> ChunkedIndex {
209        let mut chunk_index = HashMap::new();
210        for dir_path in chunks.keys() {
211            chunk_index.insert(
212                dir_path.clone(),
213                format!("chunks/{}", self.get_chunk_file_name(dir_path)),
214            );
215        }
216
217        let global_dep_graph = if self.options.with_global_dependency_graph {
218            Some(self.build_global_dependency_graph(blueprint))
219        } else {
220            None
221        };
222
223        ChunkedIndex {
224            format: "chunked-v1".to_string(),
225            meta: ChunkedMeta {
226                version: blueprint.meta.version.clone(),
227                generated_at: blueprint.meta.generated_at.clone(),
228                generator_version: blueprint.meta.generator_version.clone(),
229                updated_at: Some(chrono::Utc::now().to_rfc3339()),
230            },
231            project: blueprint.project.clone(),
232            views: self.build_lightweight_views(blueprint, chunks, &chunk_index),
233            statistics: blueprint.statistics.clone(),
234            chunk_index,
235            global_dependency_graph: global_dep_graph,
236        }
237    }
238
239    fn build_lightweight_views(
240        &self,
241        blueprint: &EnhancedCodeBlueprint,
242        chunks: &HashMap<String, Vec<EnhancedModule>>,
243        chunk_index: &HashMap<String, String>,
244    ) -> LightweightViews {
245        LightweightViews {
246            directory_tree: self.convert_tree_with_chunks(
247                &blueprint.views.directory_tree,
248                chunks,
249                chunk_index,
250            ),
251            architecture_layers: self.convert_layers_with_chunks(
252                &blueprint.views.architecture_layers,
253                chunks,
254                chunk_index,
255            ),
256        }
257    }
258
259    fn convert_tree_with_chunks(
260        &self,
261        tree: &DirectoryNode,
262        chunks: &HashMap<String, Vec<EnhancedModule>>,
263        chunk_index: &HashMap<String, String>,
264    ) -> DirectoryNodeWithChunk {
265        let chunk_file = chunk_index.get(&tree.path).cloned();
266        let module_count = chunks.get(&tree.path).map(|m| m.len());
267
268        DirectoryNodeWithChunk {
269            name: tree.name.clone(),
270            path: tree.path.clone(),
271            node_type: tree.node_type,
272            chunk_file,
273            module_count,
274            children: tree.children.as_ref().map(|children| {
275                children
276                    .iter()
277                    .map(|c| self.convert_tree_with_chunks(c, chunks, chunk_index))
278                    .collect()
279            }),
280        }
281    }
282
283    fn convert_layers_with_chunks(
284        &self,
285        layers: &ArchitectureLayers,
286        chunks: &HashMap<String, Vec<EnhancedModule>>,
287        chunk_index: &HashMap<String, String>,
288    ) -> ArchitectureLayersWithChunks {
289        ArchitectureLayersWithChunks {
290            presentation: self.convert_layer(&layers.presentation, chunks, chunk_index),
291            business: self.convert_layer(&layers.business, chunks, chunk_index),
292            data: self.convert_layer(&layers.data, chunks, chunk_index),
293            infrastructure: self.convert_layer(&layers.infrastructure, chunks, chunk_index),
294            cross_cutting: self.convert_layer(&layers.cross_cutting, chunks, chunk_index),
295        }
296    }
297
298    fn convert_layer(
299        &self,
300        layer: &LayerInfo,
301        _chunks: &HashMap<String, Vec<EnhancedModule>>,
302        chunk_index: &HashMap<String, String>,
303    ) -> LayerWithChunks {
304        let mut chunk_files = std::collections::HashSet::new();
305        for module_id in &layer.modules {
306            let dir = self.get_module_directory(module_id);
307            if let Some(chunk_file) = chunk_index.get(&dir) {
308                chunk_files.insert(chunk_file.clone());
309            }
310        }
311
312        LayerWithChunks {
313            name: layer.description.clone(),
314            description: Some(layer.description.clone()),
315            chunk_files: chunk_files.into_iter().collect(),
316            module_count: layer.modules.len(),
317        }
318    }
319
320    fn build_global_dependency_graph(
321        &self,
322        blueprint: &EnhancedCodeBlueprint,
323    ) -> HashMap<String, GlobalDependencyNode> {
324        let mut graph: HashMap<String, GlobalDependencyNode> = HashMap::new();
325
326        for module_id in blueprint.modules.keys() {
327            graph.insert(
328                module_id.clone(),
329                GlobalDependencyNode {
330                    imports: Vec::new(),
331                    imported_by: Vec::new(),
332                    exports_symbols: false,
333                },
334            );
335        }
336
337        for dep in &blueprint.references.module_deps {
338            if let Some(node) = graph.get_mut(&dep.source) {
339                node.imports.push(dep.target.clone());
340            }
341            if let Some(node) = graph.get_mut(&dep.target) {
342                node.imported_by.push(dep.source.clone());
343            }
344        }
345
346        for (module_id, module) in &blueprint.modules {
347            if let Some(node) = graph.get_mut(module_id) {
348                node.exports_symbols = !module.exports.is_empty();
349            }
350        }
351
352        graph
353    }
354}