1use chrono::Utc;
14use std::collections::HashMap;
15use std::fs;
16use std::path::{Path, PathBuf};
17use tokio::sync::mpsc;
18
19use super::blueprint_manager::BlueprintManager;
20use super::task_tree_manager::TaskTreeManager;
21use super::types::{
22 Blueprint, BlueprintSource, BlueprintStatus, BusinessProcess, ModuleType, MoscowPriority,
23 NfrCategory, NonFunctionalRequirement, ProcessStep, ProcessType, SystemModule, TaskNode,
24 TaskStatus, TaskTree,
25};
26
27#[derive(Debug, Clone)]
33pub struct AnalyzerConfig {
34 pub root_dir: PathBuf,
36 pub project_name: Option<String>,
38 pub project_description: Option<String>,
40 pub ignore_dirs: Vec<String>,
42 pub ignore_patterns: Vec<String>,
44 pub max_depth: usize,
46 pub include_tests: bool,
48 pub granularity: AnalysisGranularity,
50 pub use_ai: bool,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum AnalysisGranularity {
57 Coarse,
58 Medium,
59 Fine,
60}
61
62impl Default for AnalyzerConfig {
63 fn default() -> Self {
64 Self {
65 root_dir: std::env::current_dir().unwrap_or_default(),
66 project_name: None,
67 project_description: None,
68 ignore_dirs: vec![
69 "node_modules".to_string(),
70 ".git".to_string(),
71 "dist".to_string(),
72 "build".to_string(),
73 "coverage".to_string(),
74 ".next".to_string(),
75 "__pycache__".to_string(),
76 "venv".to_string(),
77 "target".to_string(),
78 ],
79 ignore_patterns: vec![
80 "*.min.js".to_string(),
81 "*.map".to_string(),
82 "*.lock".to_string(),
83 "package-lock.json".to_string(),
84 ],
85 max_depth: 10,
86 include_tests: true,
87 granularity: AnalysisGranularity::Medium,
88 use_ai: true,
89 }
90 }
91}
92
93#[derive(Debug, Clone)]
99pub struct CodebaseInfo {
100 pub name: String,
101 pub description: String,
102 pub root_dir: PathBuf,
103 pub language: String,
104 pub framework: Option<String>,
105 pub modules: Vec<DetectedModule>,
106 pub dependencies: Vec<String>,
107 pub dev_dependencies: Vec<String>,
108 pub scripts: HashMap<String, String>,
109 pub structure: DirectoryNode,
110 pub stats: CodebaseStats,
111 pub ai_analysis: Option<AIAnalysisResult>,
113}
114
115#[derive(Debug, Clone)]
117pub struct DetectedModule {
118 pub name: String,
119 pub path: PathBuf,
120 pub root_path: String,
122 pub module_type: DetectedModuleType,
123 pub files: Vec<PathBuf>,
124 pub exports: Vec<String>,
125 pub imports: Vec<String>,
126 pub responsibilities: Vec<String>,
127 pub suggested_tasks: Vec<String>,
128 pub ai_description: Option<String>,
130 pub core_features: Option<Vec<String>>,
132 pub boundary_constraints: Option<Vec<String>>,
134 pub protected_files: Option<Vec<String>>,
136}
137
138#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub enum DetectedModuleType {
141 Frontend,
142 Backend,
143 Database,
144 Service,
145 Infrastructure,
146 Other,
147}
148
149impl From<DetectedModuleType> for ModuleType {
150 fn from(t: DetectedModuleType) -> Self {
151 match t {
152 DetectedModuleType::Frontend => ModuleType::Frontend,
153 DetectedModuleType::Backend => ModuleType::Backend,
154 DetectedModuleType::Database => ModuleType::Database,
155 DetectedModuleType::Service => ModuleType::Service,
156 DetectedModuleType::Infrastructure => ModuleType::Infrastructure,
157 DetectedModuleType::Other => ModuleType::Other,
158 }
159 }
160}
161
162#[derive(Debug, Clone)]
164pub struct DirectoryNode {
165 pub name: String,
166 pub path: PathBuf,
167 pub node_type: NodeType,
168 pub children: Vec<DirectoryNode>,
169 pub extension: Option<String>,
170 pub size: Option<u64>,
171}
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq)]
175pub enum NodeType {
176 Directory,
177 File,
178}
179
180#[derive(Debug, Clone, Default)]
182pub struct CodebaseStats {
183 pub total_files: usize,
184 pub total_dirs: usize,
185 pub total_lines: usize,
186 pub files_by_type: HashMap<String, usize>,
187 pub largest_files: Vec<(PathBuf, usize)>,
188}
189
190#[derive(Debug, Clone)]
192pub struct AIModuleAnalysis {
193 pub name: String,
195 pub purpose: String,
197 pub responsibilities: Vec<String>,
199 pub dependencies: Vec<String>,
201 pub core_features: Vec<String>,
203 pub boundary_constraints: Vec<String>,
205 pub protected_files: Vec<String>,
207 pub public_interfaces: Vec<String>,
209 pub internal_details: Vec<String>,
211}
212
213#[derive(Debug, Clone)]
215pub struct AIAnalysisResult {
216 pub overview: String,
218 pub architecture_pattern: String,
220 pub core_features: Vec<String>,
222 pub module_analysis: Vec<AIModuleAnalysis>,
224 pub business_flows: Vec<BusinessFlowInfo>,
226 pub architecture_decisions: Vec<String>,
228 pub technical_debts: Vec<String>,
230}
231
232#[derive(Debug, Clone)]
234pub struct BusinessFlowInfo {
235 pub name: String,
236 pub description: String,
237 pub steps: Vec<String>,
238}
239
240#[derive(Debug, Clone)]
242pub enum AnalyzerEvent {
243 Started { root_dir: PathBuf },
244 AIStarted,
245 AICompleted { analysis: AIAnalysisResult },
246 AIError { error: String },
247 CodebaseCompleted { stats: CodebaseStats },
248 BlueprintCompleted { blueprint_id: String },
249 TaskTreeCompleted { task_tree_id: String },
250 Completed,
251}
252
253pub struct CodebaseAnalyzer {
259 config: AnalyzerConfig,
260 event_sender: Option<mpsc::Sender<AnalyzerEvent>>,
261}
262
263impl CodebaseAnalyzer {
264 pub fn new(config: AnalyzerConfig) -> Self {
266 Self {
267 config,
268 event_sender: None,
269 }
270 }
271
272 pub fn with_event_sender(mut self, sender: mpsc::Sender<AnalyzerEvent>) -> Self {
274 self.event_sender = Some(sender);
275 self
276 }
277
278 async fn emit(&self, event: AnalyzerEvent) {
280 if let Some(ref sender) = self.event_sender {
281 let _ = sender.send(event).await;
282 }
283 }
284
285 pub async fn analyze_and_generate(
291 &mut self,
292 blueprint_manager: &mut BlueprintManager,
293 task_tree_manager: &mut TaskTreeManager,
294 ) -> Result<AnalyzeResult, String> {
295 self.emit(AnalyzerEvent::Started {
296 root_dir: self.config.root_dir.clone(),
297 })
298 .await;
299
300 let mut codebase = self.analyze()?;
302
303 if let Some(ref name) = self.config.project_name {
305 codebase.name = name.clone();
306 }
307 if let Some(ref desc) = self.config.project_description {
308 codebase.description = desc.clone();
309 }
310
311 if self.config.use_ai {
313 self.emit(AnalyzerEvent::AIStarted).await;
314 match self.analyze_with_ai(&codebase).await {
315 Ok(analysis) => {
316 self.emit(AnalyzerEvent::AICompleted {
317 analysis: analysis.clone(),
318 })
319 .await;
320 self.enhance_modules_with_ai(&mut codebase, &analysis);
322 codebase.ai_analysis = Some(analysis);
323 }
324 Err(e) => {
325 self.emit(AnalyzerEvent::AIError { error: e }).await;
326 }
328 }
329 }
330
331 self.emit(AnalyzerEvent::CodebaseCompleted {
332 stats: codebase.stats.clone(),
333 })
334 .await;
335
336 let blueprint = self
338 .generate_blueprint(&codebase, blueprint_manager)
339 .await?;
340 self.emit(AnalyzerEvent::BlueprintCompleted {
341 blueprint_id: blueprint.id.clone(),
342 })
343 .await;
344
345 let task_tree = self
347 .generate_task_tree_with_passed_status(&blueprint, task_tree_manager)
348 .await?;
349 self.emit(AnalyzerEvent::TaskTreeCompleted {
350 task_tree_id: task_tree.id.clone(),
351 })
352 .await;
353
354 self.emit(AnalyzerEvent::Completed).await;
355
356 Ok(AnalyzeResult {
357 codebase,
358 blueprint,
359 task_tree,
360 })
361 }
362
363 pub fn analyze(&self) -> Result<CodebaseInfo, String> {
369 let root_dir = &self.config.root_dir;
370
371 let (language, framework) = self.detect_project_type(root_dir)?;
373
374 let structure = self.scan_directory(root_dir, 0)?;
376
377 let modules = self.detect_modules(root_dir, &structure);
379
380 let (dependencies, dev_dependencies, scripts) = self.read_package_info(root_dir);
382
383 let stats = self.calculate_stats(&structure);
385
386 let name = self.config.project_name.clone().unwrap_or_else(|| {
388 root_dir
389 .file_name()
390 .and_then(|n| n.to_str())
391 .unwrap_or("unknown")
392 .to_string()
393 });
394
395 let description = self.config.project_description.clone().unwrap_or_else(|| {
396 self.generate_project_description(&name, &language, framework.as_deref(), &modules)
397 });
398
399 Ok(CodebaseInfo {
400 name,
401 description,
402 root_dir: root_dir.clone(),
403 language,
404 framework,
405 modules,
406 dependencies,
407 dev_dependencies,
408 scripts,
409 structure,
410 stats,
411 ai_analysis: None,
412 })
413 }
414
415 fn detect_project_type(&self, root_dir: &Path) -> Result<(String, Option<String>), String> {
417 let entries: Vec<_> = fs::read_dir(root_dir)
418 .map_err(|e| format!("无法读取目录: {}", e))?
419 .filter_map(|e| e.ok())
420 .map(|e| e.file_name().to_string_lossy().to_string())
421 .collect();
422
423 if entries.iter().any(|f| f == "package.json") {
425 let pkg_path = root_dir.join("package.json");
426 if let Ok(content) = fs::read_to_string(&pkg_path) {
427 if let Ok(pkg) = serde_json::from_str::<serde_json::Value>(&content) {
428 let deps = pkg.get("dependencies").and_then(|d| d.as_object());
429 let dev_deps = pkg.get("devDependencies").and_then(|d| d.as_object());
430
431 let has_dep = |name: &str| {
432 deps.map(|d| d.contains_key(name)).unwrap_or(false)
433 || dev_deps.map(|d| d.contains_key(name)).unwrap_or(false)
434 };
435
436 let language = if entries.iter().any(|f| f == "tsconfig.json") {
437 "TypeScript"
438 } else {
439 "JavaScript"
440 };
441
442 let framework = if has_dep("react") || has_dep("react-dom") {
443 Some("React")
444 } else if has_dep("vue") {
445 Some("Vue")
446 } else if has_dep("@angular/core") {
447 Some("Angular")
448 } else if has_dep("next") {
449 Some("Next.js")
450 } else if has_dep("express") {
451 Some("Express")
452 } else if has_dep("fastify") {
453 Some("Fastify")
454 } else if has_dep("@nestjs/core") {
455 Some("NestJS")
456 } else {
457 None
458 };
459
460 return Ok((language.to_string(), framework.map(|s| s.to_string())));
461 }
462 }
463 }
464
465 if entries.iter().any(|f| f == "Cargo.toml") {
467 return Ok(("Rust".to_string(), None));
468 }
469
470 if entries
472 .iter()
473 .any(|f| f == "requirements.txt" || f == "setup.py" || f == "pyproject.toml")
474 {
475 let mut framework = None;
476 let req_path = root_dir.join("requirements.txt");
477 if let Ok(content) = fs::read_to_string(&req_path) {
478 if content.contains("django") {
479 framework = Some("Django".to_string());
480 } else if content.contains("flask") {
481 framework = Some("Flask".to_string());
482 } else if content.contains("fastapi") {
483 framework = Some("FastAPI".to_string());
484 }
485 }
486 return Ok(("Python".to_string(), framework));
487 }
488
489 if entries.iter().any(|f| f == "go.mod") {
491 return Ok(("Go".to_string(), None));
492 }
493
494 if entries
496 .iter()
497 .any(|f| f == "pom.xml" || f == "build.gradle")
498 {
499 return Ok(("Java".to_string(), Some("Spring".to_string())));
500 }
501
502 Ok(("Unknown".to_string(), None))
503 }
504
505 fn scan_directory(&self, dir_path: &Path, depth: usize) -> Result<DirectoryNode, String> {
507 let name = dir_path
508 .file_name()
509 .and_then(|n| n.to_str())
510 .unwrap_or("")
511 .to_string();
512
513 if depth > self.config.max_depth {
515 return Ok(DirectoryNode {
516 name,
517 path: dir_path.to_path_buf(),
518 node_type: NodeType::Directory,
519 children: vec![],
520 extension: None,
521 size: None,
522 });
523 }
524
525 if self.config.ignore_dirs.contains(&name) {
527 return Ok(DirectoryNode {
528 name,
529 path: dir_path.to_path_buf(),
530 node_type: NodeType::Directory,
531 children: vec![],
532 extension: None,
533 size: None,
534 });
535 }
536
537 let metadata = fs::metadata(dir_path).map_err(|e| format!("无法读取元数据: {}", e))?;
538
539 if metadata.is_file() {
540 let extension = dir_path
541 .extension()
542 .and_then(|e| e.to_str())
543 .map(|s| s.to_string());
544 return Ok(DirectoryNode {
545 name,
546 path: dir_path.to_path_buf(),
547 node_type: NodeType::File,
548 children: vec![],
549 extension,
550 size: Some(metadata.len()),
551 });
552 }
553
554 let mut children = Vec::new();
555 let entries = fs::read_dir(dir_path).map_err(|e| format!("无法读取目录: {}", e))?;
556
557 for entry in entries.filter_map(|e| e.ok()) {
558 let entry_name = entry.file_name().to_string_lossy().to_string();
559
560 if self.config.ignore_dirs.contains(&entry_name) {
562 continue;
563 }
564 if self.should_ignore(&entry_name) {
565 continue;
566 }
567
568 if let Ok(child) = self.scan_directory(&entry.path(), depth + 1) {
569 children.push(child);
570 }
571 }
572
573 Ok(DirectoryNode {
574 name,
575 path: dir_path.to_path_buf(),
576 node_type: NodeType::Directory,
577 children,
578 extension: None,
579 size: None,
580 })
581 }
582
583 fn should_ignore(&self, name: &str) -> bool {
585 for pattern in &self.config.ignore_patterns {
586 if self.match_pattern(name, pattern) {
587 return true;
588 }
589 }
590 false
591 }
592
593 fn match_pattern(&self, name: &str, pattern: &str) -> bool {
595 let regex_pattern = format!("^{}$", pattern.replace("*", ".*"));
596 regex::Regex::new(®ex_pattern)
597 .map(|r| r.is_match(name))
598 .unwrap_or(false)
599 }
600
601 fn detect_modules(&self, root_dir: &Path, structure: &DirectoryNode) -> Vec<DetectedModule> {
603 let mut modules = Vec::new();
604 self.scan_for_modules(structure, 0, "", &mut modules, root_dir);
605
606 if modules.is_empty() {
608 if let Some(src_dir) = structure.children.iter().find(|c| c.name == "src") {
609 self.scan_for_modules(src_dir, 1, "src", &mut modules, root_dir);
610 }
611 }
612
613 if modules.is_empty() {
615 if let Some(src_dir) = structure.children.iter().find(|c| c.name == "src") {
616 modules.push(DetectedModule {
617 name: "main".to_string(),
618 path: src_dir.path.clone(),
619 root_path: "src".to_string(),
620 module_type: DetectedModuleType::Backend,
621 files: self.collect_files(src_dir),
622 exports: vec![],
623 imports: vec![],
624 responsibilities: vec!["主要业务逻辑".to_string()],
625 suggested_tasks: vec!["代码重构".to_string(), "添加测试".to_string()],
626 ai_description: None,
627 core_features: None,
628 boundary_constraints: None,
629 protected_files: None,
630 });
631 }
632 }
633
634 modules
635 }
636
637 fn scan_for_modules(
639 &self,
640 node: &DirectoryNode,
641 depth: usize,
642 parent_path: &str,
643 modules: &mut Vec<DetectedModule>,
644 root_dir: &Path,
645 ) {
646 if node.node_type != NodeType::Directory || depth > 3 {
647 return;
648 }
649
650 for child in &node.children {
651 if child.node_type != NodeType::Directory {
652 continue;
653 }
654 if self.config.ignore_dirs.contains(&child.name) {
655 continue;
656 }
657
658 let (module_type, is_leaf) = self.match_module_pattern(&child.name);
660
661 if let Some(mt) = module_type {
662 if is_leaf {
663 if let Some(module) = self.analyze_module_deep(child, mt, parent_path, root_dir)
665 {
666 if !module.files.is_empty() {
667 modules.push(module);
668 }
669 }
670 } else {
671 let new_parent = if parent_path.is_empty() {
673 child.name.clone()
674 } else {
675 format!("{}/{}", parent_path, child.name)
676 };
677 self.scan_for_modules(child, depth + 1, &new_parent, modules, root_dir);
678 }
679 } else if depth > 0 {
680 let files = self.collect_files(child);
682 let code_files: Vec<_> = files
683 .iter()
684 .filter(|f| {
685 let ext = f.extension().and_then(|e| e.to_str()).unwrap_or("");
686 matches!(ext, "ts" | "tsx" | "js" | "jsx" | "py" | "go" | "rs")
687 })
688 .collect();
689
690 if code_files.len() >= 5 {
691 let inferred_type = self.infer_module_type(&child.name, &files);
692 if let Some(module) =
693 self.analyze_module_deep(child, inferred_type, parent_path, root_dir)
694 {
695 modules.push(module);
696 }
697 }
698 }
699 }
700 }
701
702 fn match_module_pattern(&self, name: &str) -> (Option<DetectedModuleType>, bool) {
704 let name_lower = name.to_lowercase();
705
706 if matches!(
708 name_lower.as_str(),
709 "client" | "frontend" | "pages" | "components" | "ui"
710 ) {
711 return (Some(DetectedModuleType::Frontend), true);
712 }
713 if matches!(name_lower.as_str(), "server" | "api" | "routes" | "core") {
715 return (Some(DetectedModuleType::Backend), true);
716 }
717 if matches!(name_lower.as_str(), "database" | "db" | "models") {
719 return (Some(DetectedModuleType::Database), true);
720 }
721 if matches!(
723 name_lower.as_str(),
724 "services"
725 | "utils"
726 | "helpers"
727 | "tools"
728 | "blueprint"
729 | "parser"
730 | "hooks"
731 | "plugins"
732 ) {
733 return (Some(DetectedModuleType::Service), true);
734 }
735 if matches!(name_lower.as_str(), "config" | "infra" | "deploy") {
737 return (Some(DetectedModuleType::Infrastructure), true);
738 }
739 if matches!(name_lower.as_str(), "lib" | "src" | "web") {
741 return (Some(DetectedModuleType::Backend), false);
742 }
743
744 (None, false)
745 }
746
747 fn infer_module_type(&self, _name: &str, files: &[PathBuf]) -> DetectedModuleType {
749 let has_react = files.iter().any(|f| {
750 let ext = f.extension().and_then(|e| e.to_str()).unwrap_or("");
751 ext == "tsx" || ext == "jsx"
752 });
753 let has_routes = files.iter().any(|f| {
754 let name = f.file_name().and_then(|n| n.to_str()).unwrap_or("");
755 name.contains("route") || name.contains("api")
756 });
757 let has_models = files.iter().any(|f| {
758 let name = f.file_name().and_then(|n| n.to_str()).unwrap_or("");
759 name.contains("model") || name.contains("schema")
760 });
761 let has_config = files.iter().any(|f| {
762 let name = f.file_name().and_then(|n| n.to_str()).unwrap_or("");
763 name.contains("config") || name.contains(".env")
764 });
765
766 if has_react {
767 DetectedModuleType::Frontend
768 } else if has_models {
769 DetectedModuleType::Database
770 } else if has_routes {
771 DetectedModuleType::Backend
772 } else if has_config {
773 DetectedModuleType::Infrastructure
774 } else {
775 DetectedModuleType::Service
776 }
777 }
778
779 fn analyze_module_deep(
781 &self,
782 node: &DirectoryNode,
783 module_type: DetectedModuleType,
784 parent_path: &str,
785 root_dir: &Path,
786 ) -> Option<DetectedModule> {
787 let files = self.collect_files(node);
788 if files.is_empty() {
789 return None;
790 }
791
792 let module_name = if parent_path.is_empty() {
794 node.name.clone()
795 } else {
796 format!("{}/{}", parent_path, node.name)
797 };
798
799 let root_path = node
801 .path
802 .strip_prefix(root_dir)
803 .map(|p| p.to_string_lossy().to_string())
804 .unwrap_or_else(|_| node.name.clone());
805
806 let responsibilities = self.infer_responsibilities(&node.name, module_type, &files);
808
809 let suggested_tasks = self.generate_suggested_tasks(module_type, &files);
811
812 let exports = self.extract_exports_from_index(node);
814
815 let imports = self.extract_imports_from_files(&files);
817
818 Some(DetectedModule {
819 name: module_name,
820 path: node.path.clone(),
821 root_path,
822 module_type,
823 files,
824 exports,
825 imports,
826 responsibilities,
827 suggested_tasks,
828 ai_description: None,
829 core_features: None,
830 boundary_constraints: None,
831 protected_files: None,
832 })
833 }
834
835 fn collect_files(&self, node: &DirectoryNode) -> Vec<PathBuf> {
837 let mut files = Vec::new();
838
839 if node.node_type == NodeType::File {
840 files.push(node.path.clone());
841 } else {
842 for child in &node.children {
843 files.extend(self.collect_files(child));
844 }
845 }
846
847 files
848 }
849
850 fn infer_responsibilities(
852 &self,
853 _name: &str,
854 module_type: DetectedModuleType,
855 files: &[PathBuf],
856 ) -> Vec<String> {
857 let mut responsibilities = Vec::new();
858
859 match module_type {
860 DetectedModuleType::Frontend => {
861 responsibilities.push("用户界面渲染".to_string());
862 responsibilities.push("用户交互处理".to_string());
863 if files.iter().any(|f| {
864 let name = f.to_string_lossy();
865 name.contains("state") || name.contains("store")
866 }) {
867 responsibilities.push("状态管理".to_string());
868 }
869 }
870 DetectedModuleType::Backend => {
871 responsibilities.push("业务逻辑处理".to_string());
872 responsibilities.push("API 接口提供".to_string());
873 if files.iter().any(|f| f.to_string_lossy().contains("auth")) {
874 responsibilities.push("认证授权".to_string());
875 }
876 }
877 DetectedModuleType::Database => {
878 responsibilities.push("数据持久化".to_string());
879 responsibilities.push("数据模型定义".to_string());
880 responsibilities.push("数据库迁移".to_string());
881 }
882 DetectedModuleType::Service => {
883 responsibilities.push("通用服务提供".to_string());
884 responsibilities.push("工具函数".to_string());
885 }
886 DetectedModuleType::Infrastructure => {
887 responsibilities.push("配置管理".to_string());
888 responsibilities.push("部署脚本".to_string());
889 }
890 DetectedModuleType::Other => {
891 responsibilities.push("其他功能".to_string());
892 }
893 }
894
895 responsibilities
896 }
897
898 fn generate_suggested_tasks(
900 &self,
901 module_type: DetectedModuleType,
902 files: &[PathBuf],
903 ) -> Vec<String> {
904 let mut tasks = vec!["代码审查和重构".to_string()];
905
906 let has_tests = files.iter().any(|f| {
908 let name = f.to_string_lossy();
909 name.contains(".test.") || name.contains(".spec.") || name.contains("__tests__")
910 });
911 if !has_tests {
912 tasks.push("添加单元测试".to_string());
913 }
914
915 match module_type {
916 DetectedModuleType::Frontend => {
917 tasks.push("UI/UX 优化".to_string());
918 tasks.push("性能优化".to_string());
919 }
920 DetectedModuleType::Backend => {
921 tasks.push("API 文档完善".to_string());
922 tasks.push("错误处理优化".to_string());
923 }
924 DetectedModuleType::Database => {
925 tasks.push("索引优化".to_string());
926 tasks.push("数据迁移脚本".to_string());
927 }
928 _ => {}
929 }
930
931 tasks
932 }
933
934 fn extract_exports_from_index(&self, node: &DirectoryNode) -> Vec<String> {
936 let mut exports = Vec::new();
937
938 let index_file = node.children.iter().find(|c| {
940 c.node_type == NodeType::File
941 && (c.name == "index.ts"
942 || c.name == "index.js"
943 || c.name == "mod.rs"
944 || c.name == "lib.rs")
945 });
946
947 if let Some(index) = index_file {
948 if let Ok(content) = fs::read_to_string(&index.path) {
949 let re = regex::Regex::new(
951 r"export\s+(?:const|function|class|type|interface|enum)\s+(\w+)",
952 )
953 .ok();
954 if let Some(re) = re {
955 for cap in re.captures_iter(&content) {
956 if let Some(name) = cap.get(1) {
957 exports.push(name.as_str().to_string());
958 }
959 }
960 }
961
962 let re_rust = regex::Regex::new(r"pub\s+(?:use|mod)\s+(\w+)").ok();
964 if let Some(re) = re_rust {
965 for cap in re.captures_iter(&content) {
966 if let Some(name) = cap.get(1) {
967 exports.push(name.as_str().to_string());
968 }
969 }
970 }
971 }
972 }
973
974 exports.into_iter().take(20).collect()
975 }
976
977 fn extract_imports_from_files(&self, files: &[PathBuf]) -> Vec<String> {
979 use once_cell::sync::Lazy;
980
981 static RE_TS_IMPORT: Lazy<regex::Regex> =
982 Lazy::new(|| regex::Regex::new(r#"import\s+.*from\s+['"](\.[^'"]+)['"]"#).unwrap());
983 static RE_RUST_USE: Lazy<regex::Regex> =
984 Lazy::new(|| regex::Regex::new(r"use\s+(?:crate|super)::(\w+)").unwrap());
985
986 let mut imports = std::collections::HashSet::new();
987
988 for file in files.iter().take(10) {
990 let ext = file.extension().and_then(|e| e.to_str()).unwrap_or("");
991 if !matches!(ext, "ts" | "tsx" | "js" | "rs") {
992 continue;
993 }
994
995 if let Ok(content) = fs::read_to_string(file) {
996 for cap in RE_TS_IMPORT.captures_iter(&content) {
998 if let Some(import_path) = cap.get(1) {
999 let parts: Vec<&str> = import_path
1000 .as_str()
1001 .split('/')
1002 .filter(|p| *p != "." && *p != "..")
1003 .collect();
1004 if let Some(first) = parts.first() {
1005 imports.insert(first.to_string());
1006 }
1007 }
1008 }
1009
1010 for cap in RE_RUST_USE.captures_iter(&content) {
1012 if let Some(name) = cap.get(1) {
1013 imports.insert(name.as_str().to_string());
1014 }
1015 }
1016 }
1017 }
1018
1019 imports.into_iter().collect()
1020 }
1021
1022 fn read_package_info(
1024 &self,
1025 root_dir: &Path,
1026 ) -> (Vec<String>, Vec<String>, HashMap<String, String>) {
1027 let pkg_path = root_dir.join("package.json");
1028
1029 if !pkg_path.exists() {
1030 let cargo_path = root_dir.join("Cargo.toml");
1032 if cargo_path.exists() {
1033 if let Ok(content) = fs::read_to_string(&cargo_path) {
1034 let mut deps = Vec::new();
1035 let mut in_deps = false;
1036 for line in content.lines() {
1037 if line.starts_with("[dependencies]") {
1038 in_deps = true;
1039 continue;
1040 }
1041 if line.starts_with('[') {
1042 in_deps = false;
1043 }
1044 if in_deps {
1045 if let Some(name) = line.split('=').next() {
1046 let name = name.trim();
1047 if !name.is_empty() {
1048 deps.push(name.to_string());
1049 }
1050 }
1051 }
1052 }
1053 return (deps, vec![], HashMap::new());
1054 }
1055 }
1056 return (vec![], vec![], HashMap::new());
1057 }
1058
1059 if let Ok(content) = fs::read_to_string(&pkg_path) {
1060 if let Ok(pkg) = serde_json::from_str::<serde_json::Value>(&content) {
1061 let deps = pkg
1062 .get("dependencies")
1063 .and_then(|d| d.as_object())
1064 .map(|d| d.keys().cloned().collect())
1065 .unwrap_or_default();
1066
1067 let dev_deps = pkg
1068 .get("devDependencies")
1069 .and_then(|d| d.as_object())
1070 .map(|d| d.keys().cloned().collect())
1071 .unwrap_or_default();
1072
1073 let scripts = pkg
1074 .get("scripts")
1075 .and_then(|s| s.as_object())
1076 .map(|s| {
1077 s.iter()
1078 .filter_map(|(k, v)| v.as_str().map(|v| (k.clone(), v.to_string())))
1079 .collect()
1080 })
1081 .unwrap_or_default();
1082
1083 return (deps, dev_deps, scripts);
1084 }
1085 }
1086
1087 (vec![], vec![], HashMap::new())
1088 }
1089
1090 fn calculate_stats(&self, structure: &DirectoryNode) -> CodebaseStats {
1092 let mut stats = CodebaseStats::default();
1093 let mut file_sizes: Vec<(PathBuf, usize)> = Vec::new();
1094
1095 self.traverse_for_stats(structure, &mut stats, &mut file_sizes);
1096
1097 file_sizes.sort_by(|a, b| b.1.cmp(&a.1));
1099 stats.largest_files = file_sizes.into_iter().take(10).collect();
1100
1101 stats
1102 }
1103
1104 fn traverse_for_stats(
1106 &self,
1107 node: &DirectoryNode,
1108 stats: &mut CodebaseStats,
1109 file_sizes: &mut Vec<(PathBuf, usize)>,
1110 ) {
1111 match node.node_type {
1112 NodeType::File => {
1113 stats.total_files += 1;
1114 let ext = node
1115 .extension
1116 .clone()
1117 .unwrap_or_else(|| "unknown".to_string());
1118 *stats.files_by_type.entry(ext).or_insert(0) += 1;
1119
1120 if let Ok(content) = fs::read_to_string(&node.path) {
1122 let lines = content.lines().count();
1123 stats.total_lines += lines;
1124 file_sizes.push((node.path.clone(), lines));
1125 }
1126 }
1127 NodeType::Directory => {
1128 stats.total_dirs += 1;
1129 for child in &node.children {
1130 self.traverse_for_stats(child, stats, file_sizes);
1131 }
1132 }
1133 }
1134 }
1135
1136 fn generate_project_description(
1138 &self,
1139 name: &str,
1140 language: &str,
1141 framework: Option<&str>,
1142 modules: &[DetectedModule],
1143 ) -> String {
1144 let mut parts = Vec::new();
1145
1146 parts.push(format!("{} 是一个", name));
1147
1148 if let Some(fw) = framework {
1149 parts.push(format!("基于 {} 框架的", fw));
1150 }
1151
1152 parts.push(format!("{} 项目。", language));
1153
1154 if !modules.is_empty() {
1155 parts.push(format!("包含 {} 个主要模块:", modules.len()));
1156 let module_names: Vec<_> = modules.iter().map(|m| m.name.as_str()).collect();
1157 parts.push(format!("{}。", module_names.join("、")));
1158 }
1159
1160 parts.join("")
1161 }
1162
1163 async fn analyze_with_ai(&self, codebase: &CodebaseInfo) -> Result<AIAnalysisResult, String> {
1169 let _context = self.build_ai_context(codebase);
1171
1172 Ok(self.generate_rule_based_analysis(codebase))
1175 }
1176
1177 fn build_ai_context(&self, codebase: &CodebaseInfo) -> String {
1179 let mut lines = Vec::new();
1180
1181 lines.push(format!("# 项目: {}", codebase.name));
1182 lines.push(format!("语言: {}", codebase.language));
1183 if let Some(ref fw) = codebase.framework {
1184 lines.push(format!("框架: {}", fw));
1185 }
1186 lines.push(String::new());
1187
1188 lines.push("## 检测到的模块".to_string());
1189 for module in &codebase.modules {
1190 lines.push(format!(
1191 "- {} ({:?}): {} 文件",
1192 module.name,
1193 module.module_type,
1194 module.files.len()
1195 ));
1196 }
1197 lines.push(String::new());
1198
1199 lines.push("## 依赖".to_string());
1200 let deps: Vec<_> = codebase.dependencies.iter().take(20).collect();
1201 lines.push(format!(
1202 "主要依赖: {}",
1203 deps.iter()
1204 .map(|s| s.as_str())
1205 .collect::<Vec<_>>()
1206 .join(", ")
1207 ));
1208
1209 lines.join("\n")
1210 }
1211
1212 fn generate_rule_based_analysis(&self, codebase: &CodebaseInfo) -> AIAnalysisResult {
1214 let mut core_features = Vec::new();
1215
1216 for module in &codebase.modules {
1218 core_features.extend(module.responsibilities.clone());
1219 }
1220
1221 if codebase
1223 .dependencies
1224 .iter()
1225 .any(|d| d == "express" || d == "fastify")
1226 {
1227 core_features.push("HTTP API 服务".to_string());
1228 }
1229 if codebase
1230 .dependencies
1231 .iter()
1232 .any(|d| d == "monaster" || d == "prisma")
1233 {
1234 core_features.push("数据库操作".to_string());
1235 }
1236 if codebase
1237 .dependencies
1238 .iter()
1239 .any(|d| d == "react" || d == "vue")
1240 {
1241 core_features.push("前端界面".to_string());
1242 }
1243
1244 core_features.sort();
1246 core_features.dedup();
1247
1248 AIAnalysisResult {
1249 overview: codebase.description.clone(),
1250 architecture_pattern: self.infer_architecture_pattern(codebase),
1251 core_features,
1252 module_analysis: codebase
1253 .modules
1254 .iter()
1255 .map(|m| AIModuleAnalysis {
1256 name: m.name.clone(),
1257 purpose: format!("{:?} 模块", m.module_type),
1258 responsibilities: m.responsibilities.clone(),
1259 dependencies: m.imports.clone(),
1260 core_features: m.responsibilities.iter().take(3).cloned().collect(),
1261 boundary_constraints: self.infer_boundary_constraints(m.module_type),
1262 protected_files: self.infer_protected_files(m),
1263 public_interfaces: m.exports.clone(),
1264 internal_details: vec![],
1265 })
1266 .collect(),
1267 business_flows: vec![],
1268 architecture_decisions: vec![],
1269 technical_debts: vec![],
1270 }
1271 }
1272
1273 fn infer_architecture_pattern(&self, codebase: &CodebaseInfo) -> String {
1275 let module_types: Vec<_> = codebase.modules.iter().map(|m| m.module_type).collect();
1276
1277 if module_types.contains(&DetectedModuleType::Frontend)
1278 && module_types.contains(&DetectedModuleType::Backend)
1279 {
1280 return "前后端分离".to_string();
1281 }
1282 if codebase.dependencies.iter().any(|d| d == "@nestjs/core") {
1283 return "NestJS 模块化架构".to_string();
1284 }
1285 if codebase
1286 .structure
1287 .children
1288 .iter()
1289 .any(|c| c.name == "services")
1290 {
1291 return "微服务架构".to_string();
1292 }
1293 "MVC / 分层架构".to_string()
1294 }
1295
1296 fn infer_boundary_constraints(&self, module_type: DetectedModuleType) -> Vec<String> {
1298 match module_type {
1299 DetectedModuleType::Frontend => vec![
1300 "不应直接访问数据库".to_string(),
1301 "业务逻辑应通过 API 调用后端".to_string(),
1302 ],
1303 DetectedModuleType::Backend => vec![
1304 "不应包含 UI 渲染逻辑".to_string(),
1305 "数据验证应在 API 边界完成".to_string(),
1306 ],
1307 DetectedModuleType::Database => vec![
1308 "不应包含业务逻辑".to_string(),
1309 "数据模型变更需要迁移脚本".to_string(),
1310 ],
1311 DetectedModuleType::Service => {
1312 vec!["应保持无状态".to_string(), "不应依赖特定框架".to_string()]
1313 }
1314 DetectedModuleType::Infrastructure => vec![
1315 "配置不应硬编码".to_string(),
1316 "敏感信息应使用环境变量".to_string(),
1317 ],
1318 DetectedModuleType::Other => vec![],
1319 }
1320 }
1321
1322 fn infer_protected_files(&self, module: &DetectedModule) -> Vec<String> {
1324 let mut protected = Vec::new();
1325
1326 for file in &module.files {
1327 let file_name = file.file_name().and_then(|n| n.to_str()).unwrap_or("");
1328
1329 if file_name.starts_with("index.") || file_name == "mod.rs" || file_name == "lib.rs" {
1331 protected.push(file.to_string_lossy().to_string());
1332 }
1333 if file_name == "types.ts" || file_name.ends_with(".d.ts") || file_name == "types.rs" {
1335 protected.push(file.to_string_lossy().to_string());
1336 }
1337 if file_name.contains("config") || file_name.contains("constants") {
1339 protected.push(file.to_string_lossy().to_string());
1340 }
1341 }
1342
1343 protected.into_iter().take(10).collect()
1344 }
1345
1346 fn enhance_modules_with_ai(&self, codebase: &mut CodebaseInfo, analysis: &AIAnalysisResult) {
1348 for module in &mut codebase.modules {
1349 let ai_module = self.find_matching_ai_module(&module.name, &analysis.module_analysis);
1351
1352 if let Some(ai_mod) = ai_module {
1353 module.ai_description = Some(ai_mod.purpose.clone());
1354
1355 let mut responsibilities = module.responsibilities.clone();
1357 responsibilities.extend(ai_mod.responsibilities.clone());
1358 responsibilities.sort();
1359 responsibilities.dedup();
1360 module.responsibilities = responsibilities;
1361
1362 module.core_features = Some(if !ai_mod.core_features.is_empty() {
1364 ai_mod.core_features.clone()
1365 } else {
1366 module.responsibilities.iter().take(3).cloned().collect()
1367 });
1368
1369 module.boundary_constraints = Some(if !ai_mod.boundary_constraints.is_empty() {
1371 ai_mod.boundary_constraints.clone()
1372 } else {
1373 self.infer_boundary_constraints(module.module_type)
1374 });
1375
1376 let mut protected = ai_mod.protected_files.clone();
1378 protected.extend(self.infer_protected_files(module));
1379 protected.sort();
1380 protected.dedup();
1381 module.protected_files = Some(protected.into_iter().take(10).collect());
1382
1383 if !ai_mod.public_interfaces.is_empty() {
1385 let mut exports = module.exports.clone();
1386 exports.extend(ai_mod.public_interfaces.clone());
1387 exports.sort();
1388 exports.dedup();
1389 module.exports = exports;
1390 }
1391 } else {
1392 module.core_features =
1394 Some(module.responsibilities.iter().take(3).cloned().collect());
1395 module.boundary_constraints =
1396 Some(self.infer_boundary_constraints(module.module_type));
1397 module.protected_files = Some(self.infer_protected_files(module));
1398 }
1399 }
1400 }
1401
1402 fn find_matching_ai_module<'a>(
1404 &self,
1405 module_name: &str,
1406 ai_modules: &'a [AIModuleAnalysis],
1407 ) -> Option<&'a AIModuleAnalysis> {
1408 let normalized_name = module_name.to_lowercase();
1409
1410 if let Some(m) = ai_modules
1412 .iter()
1413 .find(|m| m.name.to_lowercase() == normalized_name)
1414 {
1415 return Some(m);
1416 }
1417
1418 let last_part = normalized_name
1420 .rsplit('/')
1421 .next()
1422 .unwrap_or(&normalized_name);
1423 if let Some(m) = ai_modules.iter().find(|m| {
1424 let ai_last = m.name.to_lowercase();
1425 let ai_last = ai_last.rsplit('/').next().unwrap_or(&ai_last);
1426 ai_last == last_part
1427 }) {
1428 return Some(m);
1429 }
1430
1431 ai_modules.iter().find(|m| {
1433 let ai_name = m.name.to_lowercase();
1434 ai_name.contains(last_part) || last_part.contains(&ai_name)
1435 })
1436 }
1437
1438 async fn generate_blueprint(
1444 &self,
1445 codebase: &CodebaseInfo,
1446 blueprint_manager: &mut BlueprintManager,
1447 ) -> Result<Blueprint, String> {
1448 let blueprint = blueprint_manager
1450 .create_blueprint(codebase.name.clone(), codebase.description.clone())
1451 .await
1452 .map_err(|e| e.to_string())?;
1453
1454 for module in &codebase.modules {
1456 let tech_stack = self.infer_tech_stack(codebase, module);
1457 let sys_module = SystemModule {
1458 id: uuid::Uuid::new_v4().to_string(),
1459 name: module.name.clone(),
1460 description: module
1461 .ai_description
1462 .clone()
1463 .unwrap_or_else(|| format!("{} 模块 - {:?}", module.name, module.module_type)),
1464 module_type: module.module_type.into(),
1465 responsibilities: module.responsibilities.clone(),
1466 dependencies: vec![],
1467 interfaces: vec![],
1468 tech_stack: Some(tech_stack),
1469 root_path: Some(module.root_path.clone()),
1470 };
1471 blueprint_manager
1472 .add_module(&blueprint.id, sys_module)
1473 .await
1474 .map_err(|e| e.to_string())?;
1475 }
1476
1477 if let Some(ref analysis) = codebase.ai_analysis {
1479 if !analysis.business_flows.is_empty() {
1480 for flow in &analysis.business_flows {
1481 let process = BusinessProcess {
1482 id: uuid::Uuid::new_v4().to_string(),
1483 name: flow.name.clone(),
1484 description: flow.description.clone(),
1485 process_type: ProcessType::ToBe,
1486 steps: flow
1487 .steps
1488 .iter()
1489 .enumerate()
1490 .map(|(i, step)| ProcessStep {
1491 id: uuid::Uuid::new_v4().to_string(),
1492 order: i as u32 + 1,
1493 name: step.clone(),
1494 description: step.clone(),
1495 actor: "系统".to_string(),
1496 system_action: Some(step.clone()),
1497 user_action: None,
1498 conditions: vec![],
1499 outcomes: vec![],
1500 })
1501 .collect(),
1502 actors: vec!["系统".to_string(), "用户".to_string()],
1503 inputs: vec![],
1504 outputs: vec![],
1505 };
1506 blueprint_manager
1507 .add_business_process(&blueprint.id, process)
1508 .await
1509 .map_err(|e| e.to_string())?;
1510 }
1511 }
1512 }
1513
1514 if codebase.ai_analysis.is_none()
1516 || codebase
1517 .ai_analysis
1518 .as_ref()
1519 .map(|a| a.business_flows.is_empty())
1520 .unwrap_or(true)
1521 {
1522 let default_process = BusinessProcess {
1523 id: uuid::Uuid::new_v4().to_string(),
1524 name: "开发维护流程".to_string(),
1525 description: "现有项目的开发和维护流程".to_string(),
1526 process_type: ProcessType::ToBe,
1527 steps: vec![
1528 ProcessStep {
1529 id: uuid::Uuid::new_v4().to_string(),
1530 order: 1,
1531 name: "需求分析".to_string(),
1532 description: "分析新功能需求或 bug 修复需求".to_string(),
1533 actor: "开发者".to_string(),
1534 system_action: None,
1535 user_action: Some("分析需求".to_string()),
1536 conditions: vec![],
1537 outcomes: vec!["需求文档".to_string()],
1538 },
1539 ProcessStep {
1540 id: uuid::Uuid::new_v4().to_string(),
1541 order: 2,
1542 name: "编写测试".to_string(),
1543 description: "根据需求编写测试用例".to_string(),
1544 actor: "开发者".to_string(),
1545 system_action: None,
1546 user_action: Some("编写测试".to_string()),
1547 conditions: vec!["需求文档".to_string()],
1548 outcomes: vec!["测试用例".to_string()],
1549 },
1550 ProcessStep {
1551 id: uuid::Uuid::new_v4().to_string(),
1552 order: 3,
1553 name: "编写代码".to_string(),
1554 description: "实现功能或修复 bug".to_string(),
1555 actor: "开发者".to_string(),
1556 system_action: None,
1557 user_action: Some("编写代码".to_string()),
1558 conditions: vec!["测试用例".to_string()],
1559 outcomes: vec!["代码实现".to_string()],
1560 },
1561 ],
1562 actors: vec!["开发者".to_string()],
1563 inputs: vec![],
1564 outputs: vec![],
1565 };
1566 blueprint_manager
1567 .add_business_process(&blueprint.id, default_process)
1568 .await
1569 .map_err(|e| e.to_string())?;
1570 }
1571
1572 let nfr = NonFunctionalRequirement {
1574 id: uuid::Uuid::new_v4().to_string(),
1575 category: NfrCategory::Maintainability,
1576 name: "代码可维护性".to_string(),
1577 description: "保持代码清晰、有文档、有测试".to_string(),
1578 priority: MoscowPriority::Must,
1579 metric: None,
1580 };
1581 blueprint_manager
1582 .add_nfr(&blueprint.id, nfr)
1583 .await
1584 .map_err(|e| e.to_string())?;
1585
1586 let mut blueprint = blueprint_manager
1589 .get_blueprint(&blueprint.id)
1590 .await
1591 .ok_or_else(|| "蓝图不存在".to_string())?;
1592 blueprint.status = BlueprintStatus::Approved;
1593 blueprint.approved_at = Some(Utc::now());
1594 blueprint.approved_by = Some("system".to_string());
1595 blueprint.source = Some(BlueprintSource::Codebase);
1596
1597 Ok(blueprint)
1598 }
1599
1600 async fn generate_task_tree_with_passed_status(
1602 &self,
1603 blueprint: &Blueprint,
1604 task_tree_manager: &mut TaskTreeManager,
1605 ) -> Result<TaskTree, String> {
1606 let mut task_tree = task_tree_manager
1608 .generate_from_blueprint(blueprint)
1609 .await
1610 .map_err(|e| e.to_string())?;
1611
1612 Self::mark_all_tasks_as_passed(&mut task_tree.root);
1614
1615 task_tree.stats = task_tree_manager.calculate_stats(&task_tree.root);
1617 task_tree.status = super::types::TaskTreeStatus::Completed;
1618
1619 Ok(task_tree)
1620 }
1621
1622 fn mark_all_tasks_as_passed(task: &mut TaskNode) {
1624 task.status = TaskStatus::Passed;
1625 task.completed_at = Some(Utc::now());
1626
1627 for child in &mut task.children {
1628 Self::mark_all_tasks_as_passed(child);
1629 }
1630 }
1631
1632 fn infer_tech_stack(&self, codebase: &CodebaseInfo, module: &DetectedModule) -> Vec<String> {
1634 let mut stack = Vec::new();
1635
1636 stack.push(codebase.language.clone());
1637
1638 if let Some(ref fw) = codebase.framework {
1639 stack.push(fw.clone());
1640 }
1641
1642 match module.module_type {
1644 DetectedModuleType::Frontend => {
1645 if codebase.dependencies.iter().any(|d| d == "react") {
1646 stack.push("React".to_string());
1647 }
1648 if codebase.dependencies.iter().any(|d| d == "vue") {
1649 stack.push("Vue".to_string());
1650 }
1651 if codebase.dependencies.iter().any(|d| d == "tailwindcss") {
1652 stack.push("Tailwind CSS".to_string());
1653 }
1654 }
1655 DetectedModuleType::Backend => {
1656 if codebase.dependencies.iter().any(|d| d == "express") {
1657 stack.push("Express".to_string());
1658 }
1659 if codebase.dependencies.iter().any(|d| d == "fastify") {
1660 stack.push("Fastify".to_string());
1661 }
1662 }
1663 DetectedModuleType::Database => {
1664 if codebase.dependencies.iter().any(|d| d == "prisma") {
1665 stack.push("Prisma".to_string());
1666 }
1667 if codebase.dependencies.iter().any(|d| d == "monaster") {
1668 stack.push("MongoDB".to_string());
1669 }
1670 }
1671 _ => {}
1672 }
1673
1674 stack
1675 }
1676
1677 pub fn set_root_dir(&mut self, root_dir: PathBuf) {
1679 self.config.root_dir = root_dir;
1680 }
1681}
1682
1683#[derive(Debug, Clone)]
1689pub struct AnalyzeResult {
1690 pub codebase: CodebaseInfo,
1691 pub blueprint: Blueprint,
1692 pub task_tree: TaskTree,
1693}
1694
1695pub fn create_codebase_analyzer(config: AnalyzerConfig) -> CodebaseAnalyzer {
1701 CodebaseAnalyzer::new(config)
1702}
1703
1704pub async fn quick_analyze(
1706 root_dir: PathBuf,
1707 blueprint_manager: &mut BlueprintManager,
1708 task_tree_manager: &mut TaskTreeManager,
1709) -> Result<AnalyzeResult, String> {
1710 let config = AnalyzerConfig {
1711 root_dir,
1712 ..Default::default()
1713 };
1714 let mut analyzer = CodebaseAnalyzer::new(config);
1715 analyzer
1716 .analyze_and_generate(blueprint_manager, task_tree_manager)
1717 .await
1718}