Skip to main content

aster/map/
sync_manager.rs

1//! 蓝图代码同步管理器
2//!
3//! 核心功能:
4//! 1. sync_code_to_blueprint - 代码变更 → 蓝图更新
5//! 2. sync_blueprint_to_code - 蓝图设计 → 代码生成
6//! 3. 冲突检测和解决机制
7
8use std::collections::HashMap;
9use std::fs;
10use std::path::{Path, PathBuf};
11
12use super::incremental_updater::{IncrementalBlueprintUpdater, UpdateOptions};
13use super::types_chunked::*;
14
15/// 同步选项
16#[derive(Debug, Clone, Default)]
17pub struct SyncOptions {
18    /// 是否显示详细日志
19    pub verbose: bool,
20    /// 进度回调
21    pub on_progress: Option<fn(&str)>,
22}
23
24/// 同步结果
25#[derive(Debug, Clone)]
26pub struct SyncResult {
27    /// 是否成功
28    pub success: bool,
29    /// 结果消息
30    pub message: String,
31    /// 同步的文件
32    pub synced_files: Vec<String>,
33    /// 冲突列表
34    pub conflicts: Vec<Conflict>,
35}
36
37/// 冲突信息
38#[derive(Debug, Clone)]
39pub struct Conflict {
40    /// 冲突类型
41    pub conflict_type: ConflictType,
42    /// 模块 ID
43    pub module_id: String,
44    /// 期望值(蓝图设计)
45    pub expected: Vec<String>,
46    /// 实际值(代码)
47    pub actual: Vec<String>,
48    /// 解决方案
49    pub resolution: ConflictResolution,
50    /// 描述
51    pub description: String,
52}
53
54/// 冲突类型
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub enum ConflictType {
57    /// 导出不匹配
58    ExportMismatch,
59    /// 结构变更
60    StructureChange,
61    /// 内容分歧
62    ContentDiverged,
63}
64
65/// 冲突解决方案
66#[derive(Debug, Clone, PartialEq, Eq)]
67pub enum ConflictResolution {
68    /// 使用蓝图设计
69    UseBlueprint,
70    /// 使用代码
71    UseCode,
72    /// 手动解决
73    Manual,
74}
75
76/// 代码生成结果
77#[derive(Debug, Clone)]
78pub struct CodeGenerationResult {
79    /// 是否成功
80    pub success: bool,
81    /// 生成的文件路径
82    pub file_path: Option<String>,
83    /// 生成的代码
84    pub code: Option<String>,
85    /// 错误消息
86    pub error: Option<String>,
87}
88
89/// 蓝图代码同步管理器
90pub struct BlueprintCodeSyncManager {
91    root_path: PathBuf,
92    map_dir: PathBuf,
93    chunks_dir: PathBuf,
94    index_path: PathBuf,
95    updater: IncrementalBlueprintUpdater,
96}
97
98impl BlueprintCodeSyncManager {
99    /// 创建新的同步管理器
100    pub fn new(root_path: impl AsRef<Path>) -> Self {
101        let root = root_path.as_ref().to_path_buf();
102        let map_dir = root.join(".claude").join("map");
103        let chunks_dir = map_dir.join("chunks");
104        let index_path = map_dir.join("index.json");
105
106        Self {
107            root_path: root.clone(),
108            map_dir,
109            chunks_dir,
110            index_path,
111            updater: IncrementalBlueprintUpdater::new(root),
112        }
113    }
114
115    // ========================================================================
116    // 代码 → 蓝图同步
117    // ========================================================================
118
119    /// 代码变更同步到蓝图
120    pub fn sync_code_to_blueprint(
121        &mut self,
122        changed_files: &[String],
123        options: &SyncOptions,
124    ) -> SyncResult {
125        let mut conflicts = Vec::new();
126        let mut synced_files = Vec::new();
127
128        self.log(
129            options,
130            &format!("开始同步 {} 个文件到蓝图...", changed_files.len()),
131        );
132
133        for file in changed_files {
134            // 1. 检查蓝图中该模块的设计状态
135            let design = self.get_module_design(file);
136
137            // 2. 如果是计划模块,检测是否已实现
138            if let Some(ref d) = design {
139                if d.status == PlannedStatus::Planned {
140                    let code_path = self.root_path.join(file);
141                    if code_path.exists() {
142                        // 从 planned 移动到 implemented
143                        self.update_module_status(file, ModuleStatus::Implemented);
144                        self.log(options, &format!("  ✓ {}: planned → implemented", file));
145                    }
146                }
147            }
148
149            // 3. 分析代码,检测冲突
150            if let Some(conflict) = self.detect_conflict(file, &design) {
151                conflicts.push(conflict);
152                self.log(options, &format!("  ⚠ {}: 检测到冲突", file));
153            }
154
155            synced_files.push(file.clone());
156        }
157
158        // 4. 执行增量更新
159        let update_options = UpdateOptions {
160            files: Some(changed_files.to_vec()),
161            verbose: options.verbose,
162            on_progress: options.on_progress,
163            ..Default::default()
164        };
165        let _ = self.updater.update(&update_options);
166
167        SyncResult {
168            success: true,
169            message: format!(
170                "已同步 {} 个文件,{} 个冲突",
171                synced_files.len(),
172                conflicts.len()
173            ),
174            synced_files,
175            conflicts,
176        }
177    }
178
179    // ========================================================================
180    // 蓝图 → 代码同步
181    // ========================================================================
182
183    /// 蓝图设计同步到代码
184    pub fn sync_blueprint_to_code(
185        &mut self,
186        module_id: &str,
187        options: &SyncOptions,
188    ) -> CodeGenerationResult {
189        self.log(options, &format!("正在从蓝图生成代码: {}...", module_id));
190
191        // 1. 读取设计
192        let design = match self.get_module_design(module_id) {
193            Some(d) => d,
194            None => {
195                return CodeGenerationResult {
196                    success: false,
197                    file_path: None,
198                    code: None,
199                    error: Some(format!("未找到模块设计: {}", module_id)),
200                };
201            }
202        };
203
204        // 2. 检查状态
205        if design.status == PlannedStatus::InProgress {
206            // 已经在进行中,不需要重新生成
207        }
208
209        // 3. 生成代码
210        let code = self.generate_code_from_design(module_id, &design);
211
212        // 4. 确保目录存在
213        let target_path = self.root_path.join(module_id);
214        if let Some(parent) = target_path.parent() {
215            let _ = fs::create_dir_all(parent);
216        }
217
218        // 5. 检查文件是否已存在
219        if target_path.exists() {
220            return CodeGenerationResult {
221                success: false,
222                file_path: None,
223                code: None,
224                error: Some(format!(
225                    "文件已存在: {}。请先删除现有文件或更新蓝图状态。",
226                    module_id
227                )),
228            };
229        }
230
231        // 6. 写入文件
232        if let Err(e) = fs::write(&target_path, &code) {
233            return CodeGenerationResult {
234                success: false,
235                file_path: None,
236                code: None,
237                error: Some(format!("写入文件失败: {}", e)),
238            };
239        }
240
241        // 7. 更新蓝图状态
242        self.update_module_status(module_id, ModuleStatus::InProgress);
243
244        self.log(options, &format!("  ✓ 已生成: {}", module_id));
245
246        CodeGenerationResult {
247            success: true,
248            file_path: Some(target_path.to_string_lossy().to_string()),
249            code: Some(code),
250            error: None,
251        }
252    }
253
254    /// 批量从蓝图生成代码
255    pub fn sync_all_planned_modules(&mut self, options: &SyncOptions) -> SyncResult {
256        let planned_modules = self.get_all_planned_modules();
257        let mut synced_files = Vec::new();
258        let mut conflicts = Vec::new();
259
260        self.log(
261            options,
262            &format!("找到 {} 个计划模块", planned_modules.len()),
263        );
264
265        for module in planned_modules {
266            let result = self.sync_blueprint_to_code(&module.id, options);
267
268            if result.success {
269                synced_files.push(module.id.clone());
270            } else if let Some(ref error) = result.error {
271                if error.contains("已存在") {
272                    conflicts.push(Conflict {
273                        conflict_type: ConflictType::ContentDiverged,
274                        module_id: module.id,
275                        expected: vec!["planned".to_string()],
276                        actual: vec!["file-exists".to_string()],
277                        resolution: ConflictResolution::Manual,
278                        description: error.clone(),
279                    });
280                }
281            }
282        }
283
284        SyncResult {
285            success: conflicts.is_empty(),
286            message: format!(
287                "已生成 {} 个文件,{} 个冲突",
288                synced_files.len(),
289                conflicts.len()
290            ),
291            synced_files,
292            conflicts,
293        }
294    }
295
296    // ========================================================================
297    // 冲突检测
298    // ========================================================================
299
300    /// 检测冲突
301    fn detect_conflict(&self, module_id: &str, design: &Option<PlannedModule>) -> Option<Conflict> {
302        let design = design.as_ref()?;
303
304        let code_path = self.root_path.join(module_id);
305        if !code_path.exists() {
306            return None;
307        }
308
309        // 读取代码
310        let code = fs::read_to_string(&code_path).ok()?;
311
312        // 分析实际导出
313        let actual_exports = self.extract_exports(&code);
314
315        // 与设计期望对比
316        let expected_exports = design.expected_exports.as_ref()?;
317
318        if !expected_exports.is_empty() {
319            let missing: Vec<_> = expected_exports
320                .iter()
321                .filter(|e| !actual_exports.contains(e))
322                .cloned()
323                .collect();
324            let extra: Vec<_> = actual_exports
325                .iter()
326                .filter(|e| !expected_exports.contains(e))
327                .cloned()
328                .collect();
329
330            if !missing.is_empty() || !extra.is_empty() {
331                return Some(Conflict {
332                    conflict_type: ConflictType::ExportMismatch,
333                    module_id: module_id.to_string(),
334                    expected: expected_exports.clone(),
335                    actual: actual_exports,
336                    resolution: ConflictResolution::Manual,
337                    description: format!(
338                        "导出不匹配。缺少: {};多余: {}",
339                        missing.join(", "),
340                        extra.join(", ")
341                    ),
342                });
343            }
344        }
345
346        None
347    }
348
349    /// 提取代码中的导出
350    fn extract_exports(&self, code: &str) -> Vec<String> {
351        let mut exports = Vec::new();
352
353        // 匹配 pub struct/fn/const/enum/trait/type
354        let patterns = [
355            r"pub\s+struct\s+(\w+)",
356            r"pub\s+fn\s+(\w+)",
357            r"pub\s+const\s+(\w+)",
358            r"pub\s+enum\s+(\w+)",
359            r"pub\s+trait\s+(\w+)",
360            r"pub\s+type\s+(\w+)",
361            // TypeScript/JavaScript patterns
362            r"export\s+(?:default\s+)?class\s+(\w+)",
363            r"export\s+(?:default\s+)?function\s+(\w+)",
364            r"export\s+(?:const|let|var)\s+(\w+)",
365            r"export\s+interface\s+(\w+)",
366            r"export\s+type\s+(\w+)",
367            r"export\s+enum\s+(\w+)",
368        ];
369
370        for pattern in patterns {
371            if let Ok(re) = regex::Regex::new(pattern) {
372                for cap in re.captures_iter(code) {
373                    if let Some(name) = cap.get(1) {
374                        exports.push(name.as_str().to_string());
375                    }
376                }
377            }
378        }
379
380        exports.sort();
381        exports.dedup();
382        exports
383    }
384
385    // ========================================================================
386    // 辅助方法
387    // ========================================================================
388
389    /// 获取模块设计
390    fn get_module_design(&self, module_id: &str) -> Option<PlannedModule> {
391        let dir_path = Path::new(module_id)
392            .parent()
393            .map(|p| p.to_string_lossy().to_string())
394            .unwrap_or_default();
395        let dir_path = if dir_path == "." {
396            String::new()
397        } else {
398            dir_path
399        };
400
401        let chunk_file_name = self.get_chunk_file_name(&dir_path);
402        let chunk_path = self.chunks_dir.join(&chunk_file_name);
403
404        if !chunk_path.exists() {
405            return None;
406        }
407
408        let content = fs::read_to_string(&chunk_path).ok()?;
409        let chunk: ChunkData = serde_json::from_str(&content).ok()?;
410
411        // 检查 planned_modules
412        if let Some(ref planned_modules) = chunk.planned_modules {
413            if let Some(planned) = planned_modules.iter().find(|m| m.id == module_id) {
414                return Some(planned.clone());
415            }
416        }
417
418        None
419    }
420
421    /// 更新模块状态
422    fn update_module_status(&self, module_id: &str, status: ModuleStatus) {
423        let dir_path = Path::new(module_id)
424            .parent()
425            .map(|p| p.to_string_lossy().to_string())
426            .unwrap_or_default();
427        let dir_path = if dir_path == "." {
428            String::new()
429        } else {
430            dir_path
431        };
432
433        let chunk_file_name = self.get_chunk_file_name(&dir_path);
434        let chunk_path = self.chunks_dir.join(&chunk_file_name);
435
436        if !chunk_path.exists() {
437            return;
438        }
439
440        let content = match fs::read_to_string(&chunk_path) {
441            Ok(c) => c,
442            Err(_) => return,
443        };
444
445        let mut chunk: ChunkData = match serde_json::from_str(&content) {
446            Ok(c) => c,
447            Err(_) => return,
448        };
449
450        // 如果是从 planned 变成 implemented
451        if status == ModuleStatus::Implemented {
452            if let Some(ref mut planned_modules) = chunk.planned_modules {
453                if let Some(pos) = planned_modules.iter().position(|m| m.id == module_id) {
454                    let planned = planned_modules.remove(pos);
455
456                    // 添加到 module_design_meta
457                    let meta = chunk.module_design_meta.get_or_insert_with(HashMap::new);
458                    meta.insert(
459                        module_id.to_string(),
460                        ModuleDesignMeta {
461                            status: Some(ModuleStatus::Implemented),
462                            design_notes: Some(planned.design_notes),
463                            marked_at: Some(chrono::Utc::now().to_rfc3339()),
464                        },
465                    );
466                }
467            }
468        } else {
469            // 更新现有状态
470            let meta = chunk.module_design_meta.get_or_insert_with(HashMap::new);
471            if let Some(existing) = meta.get_mut(module_id) {
472                existing.status = Some(status);
473                existing.marked_at = Some(chrono::Utc::now().to_rfc3339());
474            } else {
475                meta.insert(
476                    module_id.to_string(),
477                    ModuleDesignMeta {
478                        status: Some(status),
479                        design_notes: None,
480                        marked_at: Some(chrono::Utc::now().to_rfc3339()),
481                    },
482                );
483            }
484        }
485
486        // 写回文件
487        if let Ok(json) = serde_json::to_string_pretty(&chunk) {
488            let _ = fs::write(&chunk_path, json);
489        }
490    }
491
492    /// 获取所有计划模块
493    fn get_all_planned_modules(&self) -> Vec<PlannedModule> {
494        let mut planned_modules = Vec::new();
495
496        if !self.chunks_dir.exists() {
497            return planned_modules;
498        }
499
500        if let Ok(entries) = fs::read_dir(&self.chunks_dir) {
501            for entry in entries.flatten() {
502                let path = entry.path();
503                if path.extension().is_some_and(|e| e == "json") {
504                    if let Ok(content) = fs::read_to_string(&path) {
505                        if let Ok(chunk) = serde_json::from_str::<ChunkData>(&content) {
506                            if let Some(modules) = chunk.planned_modules {
507                                for module in modules {
508                                    if module.status == PlannedStatus::Planned
509                                        || module.status == PlannedStatus::InProgress
510                                    {
511                                        planned_modules.push(module);
512                                    }
513                                }
514                            }
515                        }
516                    }
517                }
518            }
519        }
520
521        planned_modules
522    }
523
524    /// 根据设计生成代码
525    fn generate_code_from_design(&self, module_id: &str, design: &PlannedModule) -> String {
526        let name = Path::new(module_id)
527            .file_stem()
528            .map(|s| s.to_string_lossy().to_string())
529            .unwrap_or_else(|| "module".to_string());
530        let struct_name = self.to_pascal_case(&name);
531
532        // 获取设计备注
533        let design_notes = &design.design_notes;
534
535        // 获取依赖
536        let dependencies = &design.dependencies;
537
538        // 生成导入语句
539        let mut imports = String::new();
540        for dep in dependencies {
541            let dep_name = Path::new(dep)
542                .file_stem()
543                .map(|s| s.to_string_lossy().to_string())
544                .unwrap_or_default();
545            imports.push_str(&format!("// use crate::{}::*; // TODO\n", dep_name));
546        }
547
548        // 生成预期导出
549        let expected_exports = design
550            .expected_exports
551            .clone()
552            .unwrap_or_else(|| vec![struct_name.clone()]);
553
554        // 检测语言类型
555        let is_rust = module_id.ends_with(".rs");
556        let is_typescript = module_id.ends_with(".ts") || module_id.ends_with(".tsx");
557
558        if is_rust {
559            self.generate_rust_code(
560                &name,
561                &struct_name,
562                design_notes,
563                &imports,
564                &expected_exports,
565            )
566        } else if is_typescript {
567            self.generate_typescript_code(
568                &name,
569                &struct_name,
570                design_notes,
571                &imports,
572                &expected_exports,
573            )
574        } else {
575            self.generate_rust_code(
576                &name,
577                &struct_name,
578                design_notes,
579                &imports,
580                &expected_exports,
581            )
582        }
583    }
584
585    /// 生成 Rust 代码
586    fn generate_rust_code(
587        &self,
588        name: &str,
589        struct_name: &str,
590        design_notes: &str,
591        imports: &str,
592        expected_exports: &[String],
593    ) -> String {
594        let other_exports: String = expected_exports
595            .iter()
596            .filter(|e| *e != struct_name)
597            .map(|e| {
598                format!(
599                    "\n/// {}\n/// TODO: 实现\npub const {}: () = ();\n",
600                    e,
601                    e.to_uppercase()
602                )
603            })
604            .collect();
605
606        format!(
607            r#"//! {}
608//!
609//! {}
610//!
611//! @module {}
612//! @created {}
613//! @status in-progress
614
615{}
616/// {}
617///
618/// 设计说明:
619/// {}
620pub struct {} {{
621    // TODO: 添加字段
622}}
623
624impl {} {{
625    /// 创建新实例
626    pub fn new() -> Self {{
627        Self {{
628            // TODO: 初始化
629        }}
630    }}
631
632    // TODO: 实现方法
633}}
634
635impl Default for {} {{
636    fn default() -> Self {{
637        Self::new()
638    }}
639}}
640{}
641"#,
642            name,
643            design_notes,
644            name,
645            chrono::Utc::now().format("%Y-%m-%d"),
646            imports,
647            struct_name,
648            design_notes.replace('\n', "\n/// "),
649            struct_name,
650            struct_name,
651            struct_name,
652            other_exports
653        )
654    }
655
656    /// 生成 TypeScript 代码
657    fn generate_typescript_code(
658        &self,
659        name: &str,
660        class_name: &str,
661        design_notes: &str,
662        imports: &str,
663        expected_exports: &[String],
664    ) -> String {
665        let other_exports: String = expected_exports
666            .iter()
667            .filter(|e| *e != class_name)
668            .map(|e| {
669                format!(
670                    "\n/**\n * {}\n * TODO: 实现\n */\nexport const {} = undefined;\n",
671                    e, e
672                )
673            })
674            .collect();
675
676        format!(
677            r#"/**
678 * {}
679 *
680 * {}
681 *
682 * @module {}
683 * @created {}
684 * @status in-progress
685 */
686
687{}
688/**
689 * {}
690 *
691 * 设计说明:
692 * {}
693 */
694export class {} {{
695  constructor() {{
696    // TODO: 初始化
697  }}
698
699  // TODO: 实现方法
700}}
701{}
702export default {};
703"#,
704            name,
705            design_notes,
706            name,
707            chrono::Utc::now().format("%Y-%m-%d"),
708            imports,
709            class_name,
710            design_notes.replace('\n', "\n * "),
711            class_name,
712            other_exports,
713            class_name
714        )
715    }
716
717    /// 转换为 PascalCase
718    fn to_pascal_case(&self, s: &str) -> String {
719        s.split(['-', '_'])
720            .map(|word| {
721                let mut chars = word.chars();
722                match chars.next() {
723                    None => String::new(),
724                    Some(first) => first.to_uppercase().chain(chars).collect(),
725                }
726            })
727            .collect()
728    }
729
730    /// 获取 chunk 文件名
731    fn get_chunk_file_name(&self, dir_path: &str) -> String {
732        if dir_path.is_empty() || dir_path == "." {
733            "root.json".to_string()
734        } else {
735            format!("{}.json", dir_path.replace(['/', '\\'], "_"))
736        }
737    }
738
739    /// 日志输出
740    fn log(&self, options: &SyncOptions, message: &str) {
741        if options.verbose {
742            if let Some(callback) = options.on_progress {
743                callback(message);
744            } else {
745                println!("{}", message);
746            }
747        }
748    }
749}
750
751// ============================================================================
752// 便捷函数
753// ============================================================================
754
755/// 代码同步到蓝图
756pub fn sync_code_to_blueprint(
757    root_path: impl AsRef<Path>,
758    changed_files: &[String],
759    options: &SyncOptions,
760) -> SyncResult {
761    let mut manager = BlueprintCodeSyncManager::new(root_path);
762    manager.sync_code_to_blueprint(changed_files, options)
763}
764
765/// 蓝图同步到代码
766pub fn sync_blueprint_to_code(
767    root_path: impl AsRef<Path>,
768    module_id: &str,
769    options: &SyncOptions,
770) -> CodeGenerationResult {
771    let mut manager = BlueprintCodeSyncManager::new(root_path);
772    manager.sync_blueprint_to_code(module_id, options)
773}