1use std::collections::HashMap;
9use std::path::{Path, PathBuf};
10
11use super::enhanced_generator::EnhancedOntologyGenerator;
12use super::types_chunked::*;
13use super::types_enhanced::*;
14
15pub 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 pub fn generate(&self) -> std::io::Result<()> {
44 let generator = EnhancedOntologyGenerator::new(&self.root_path, None);
46 let blueprint = generator.generate();
47
48 std::fs::create_dir_all(&self.map_dir)?;
50 std::fs::create_dir_all(&self.chunks_dir)?;
51
52 let chunks = self.group_modules_by_directory(&blueprint.modules);
54
55 let chunk_metadata = self.generate_chunks(&chunks, &blueprint)?;
57
58 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}