Coverage for src/jtech_installer/analyzer/environment.py: 84%

333 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-08-20 15:10 -0300

1"""Analisador avançado de ambiente para JTECH™ Core.""" 

2 

3from dataclasses import dataclass 

4from enum import Enum 

5from pathlib import Path 

6from typing import Any, Dict, List, Optional, Set 

7 

8from ..core.models import InstallationConfig, TeamType 

9 

10 

11class ProjectType(Enum): 

12 """Tipos de projeto detectados.""" 

13 

14 UNKNOWN = "unknown" 

15 PYTHON = "python" 

16 JAVASCRIPT = "javascript" 

17 TYPESCRIPT = "typescript" 

18 JAVA = "java" 

19 CSHARP = "csharp" 

20 GO = "go" 

21 RUST = "rust" 

22 PHP = "php" 

23 RUBY = "ruby" 

24 MIXED = "mixed" 

25 

26 

27class FrameworkType(Enum): 

28 """Tipos de framework detectados.""" 

29 

30 NONE = "none" 

31 DJANGO = "django" 

32 FLASK = "flask" 

33 FASTAPI = "fastapi" 

34 REACT = "react" 

35 VUE = "vue" 

36 ANGULAR = "angular" 

37 NEXTJS = "nextjs" 

38 EXPRESS = "express" 

39 SPRING = "spring" 

40 DOTNET = "dotnet" 

41 LARAVEL = "laravel" 

42 RAILS = "rails" 

43 

44 

45@dataclass 

46class ConflictInfo: 

47 """Informações sobre um conflito detectado.""" 

48 

49 type: str 

50 severity: str # "low", "medium", "high", "critical" 

51 description: str 

52 affected_files: List[str] 

53 recommendation: str 

54 

55 

56@dataclass 

57class ProjectStructure: 

58 """Estrutura de projeto detectada.""" 

59 

60 root_path: Path 

61 project_type: ProjectType 

62 frameworks: List[FrameworkType] 

63 languages: Set[str] 

64 build_tools: Set[str] 

65 dependencies: Dict[str, Any] 

66 existing_configs: List[str] 

67 git_info: Optional[Dict[str, Any]] 

68 

69 

70@dataclass 

71class EnvironmentAnalysis: 

72 """Resultado completo da análise de ambiente.""" 

73 

74 project_structure: ProjectStructure 

75 is_brownfield: bool 

76 conflicts: List[ConflictInfo] 

77 recommendations: List[str] 

78 suggested_team_type: TeamType 

79 compatibility_score: float 

80 warnings: List[str] 

81 

82 

83class AdvancedEnvironmentAnalyzer: 

84 """Analisador avançado de ambiente para detectar projetos existentes.""" 

85 

86 def __init__(self, config: InstallationConfig): 

87 """ 

88 Inicializa o analisador. 

89 

90 Args: 

91 config: Configuração de instalação 

92 """ 

93 self.config = config 

94 self.project_path = config.project_path 

95 

96 # Padrões de detecção 

97 self.language_patterns = { 

98 ProjectType.PYTHON: [ 

99 "*.py", 

100 "requirements.txt", 

101 "setup.py", 

102 "pyproject.toml", 

103 "Pipfile", 

104 "environment.yml", 

105 "conda.yml", 

106 ], 

107 ProjectType.JAVASCRIPT: [ 

108 "*.js", 

109 "package.json", 

110 "yarn.lock", 

111 "npm-shrinkwrap.json", 

112 ], 

113 ProjectType.TYPESCRIPT: [ 

114 "*.ts", 

115 "*.tsx", 

116 "tsconfig.json", 

117 "tslint.json", 

118 ], 

119 ProjectType.JAVA: ["*.java", "pom.xml", "build.gradle", "gradlew"], 

120 ProjectType.CSHARP: [ 

121 "*.cs", 

122 "*.csproj", 

123 "*.sln", 

124 "packages.config", 

125 ], 

126 ProjectType.GO: ["*.go", "go.mod", "go.sum", "Gopkg.toml"], 

127 ProjectType.RUST: ["*.rs", "Cargo.toml", "Cargo.lock"], 

128 ProjectType.PHP: ["*.php", "composer.json", "composer.lock"], 

129 ProjectType.RUBY: ["*.rb", "Gemfile", "Gemfile.lock", "Rakefile"], 

130 } 

131 

132 self.framework_patterns = { 

133 FrameworkType.DJANGO: ["manage.py", "settings.py", "wsgi.py"], 

134 FrameworkType.FLASK: ["app.py", "application.py", "flask"], 

135 FrameworkType.FASTAPI: ["main.py", "fastapi"], 

136 FrameworkType.REACT: ["react", "jsx", "package.json"], 

137 FrameworkType.VUE: ["vue", "vue.config.js"], 

138 FrameworkType.ANGULAR: ["angular.json", "@angular"], 

139 FrameworkType.NEXTJS: ["next.config.js", "pages/"], 

140 FrameworkType.EXPRESS: ["express", "app.js", "server.js"], 

141 FrameworkType.SPRING: ["pom.xml", "@SpringBootApplication"], 

142 FrameworkType.DOTNET: [".csproj", "Program.cs", "Startup.cs"], 

143 FrameworkType.LARAVEL: ["artisan", "composer.json", "laravel"], 

144 FrameworkType.RAILS: ["Gemfile", "config/application.rb"], 

145 } 

146 

147 def analyze_environment(self) -> EnvironmentAnalysis: 

148 """ 

149 Executa análise completa do ambiente. 

150 

151 Returns: 

152 Resultado da análise de ambiente 

153 """ 

154 # Detectar estrutura do projeto 

155 project_structure = self._detect_project_structure() 

156 

157 # Verificar se é brownfield 

158 is_brownfield = self._is_brownfield_project(project_structure) 

159 

160 # Detectar conflitos 

161 conflicts = self._detect_conflicts(project_structure) 

162 

163 # Gerar recomendações 

164 recommendations = self._generate_recommendations( 

165 project_structure, conflicts 

166 ) 

167 

168 # Sugerir tipo de equipe 

169 suggested_team_type = self._suggest_team_type(project_structure) 

170 

171 # Calcular score de compatibilidade 

172 compatibility_score = self._calculate_compatibility_score( 

173 project_structure, conflicts 

174 ) 

175 

176 # Gerar warnings 

177 warnings = self._generate_warnings(project_structure, conflicts) 

178 

179 return EnvironmentAnalysis( 

180 project_structure=project_structure, 

181 is_brownfield=is_brownfield, 

182 conflicts=conflicts, 

183 recommendations=recommendations, 

184 suggested_team_type=suggested_team_type, 

185 compatibility_score=compatibility_score, 

186 warnings=warnings, 

187 ) 

188 

189 def _detect_project_structure(self) -> ProjectStructure: 

190 """Detecta a estrutura do projeto.""" 

191 languages = set() 

192 detected_types = [] 

193 frameworks = [] 

194 build_tools = set() 

195 dependencies = {} 

196 existing_configs = [] 

197 

198 # Analisar arquivos no diretório 

199 for file_path in self.project_path.rglob("*"): 

200 if file_path.is_file() and not self._should_ignore_file(file_path): 

201 # Detectar linguagens 

202 for project_type, patterns in self.language_patterns.items(): 

203 if any(file_path.match(pattern) for pattern in patterns): 

204 detected_types.append(project_type) 

205 languages.add(project_type.value) 

206 

207 # Detectar frameworks 

208 frameworks.extend(self._detect_frameworks_in_file(file_path)) 

209 

210 # Detectar build tools 

211 build_tools.update(self._detect_build_tools(file_path)) 

212 

213 # Detectar configs existentes 

214 if self._is_config_file(file_path): 

215 rel_path = str(file_path.relative_to(self.project_path)) 

216 existing_configs.append(rel_path) 

217 

218 # Analisar dependências 

219 dependencies = self._analyze_dependencies() 

220 

221 # Determinar tipo principal do projeto 

222 if len(detected_types) == 0: 

223 main_type = ProjectType.UNKNOWN 

224 elif len(detected_types) == 1: 

225 main_type = detected_types[0] 

226 else: 

227 # Para projeto misto, verificar se há realmente múltiplas linguagens 

228 # diferentes ou apenas frameworks da mesma linguagem 

229 unique_lang_count = len(languages) 

230 if unique_lang_count == 1: 

231 # Se só há uma linguagem, usar o tipo dessa linguagem 

232 main_type = ( 

233 list(detected_types)[0] 

234 if detected_types 

235 else ProjectType.UNKNOWN 

236 ) 

237 else: 

238 main_type = ProjectType.MIXED 

239 

240 # Remover duplicatas de frameworks 

241 unique_frameworks = list(set(frameworks)) 

242 

243 # Detectar informações do Git 

244 git_info = self._analyze_git_info() 

245 

246 return ProjectStructure( 

247 root_path=self.project_path, 

248 project_type=main_type, 

249 frameworks=unique_frameworks, 

250 languages=languages, 

251 build_tools=build_tools, 

252 dependencies=dependencies, 

253 existing_configs=existing_configs, 

254 git_info=git_info, 

255 ) 

256 

257 def _is_brownfield_project(self, structure: ProjectStructure) -> bool: 

258 """Determina se é um projeto brownfield.""" 

259 indicators = [ 

260 len(structure.languages) > 0, 

261 len(structure.existing_configs) > 0, 

262 structure.project_type != ProjectType.UNKNOWN, 

263 len(structure.frameworks) > 0, 

264 structure.git_info is not None, 

265 ] 

266 

267 return sum(indicators) >= 2 

268 

269 def _detect_conflicts( 

270 self, structure: ProjectStructure 

271 ) -> List[ConflictInfo]: 

272 """Detecta conflitos potenciais.""" 

273 conflicts = [] 

274 

275 # Conflitos de configuração existente 

276 conflicts.extend(self._detect_config_conflicts(structure)) 

277 

278 # Conflitos de estrutura de diretório 

279 conflicts.extend(self._detect_directory_conflicts(structure)) 

280 

281 # Conflitos de dependências 

282 conflicts.extend(self._detect_dependency_conflicts(structure)) 

283 

284 # Conflitos de VS Code 

285 if self.config.vs_code_integration: 

286 conflicts.extend(self._detect_vscode_conflicts(structure)) 

287 

288 return conflicts 

289 

290 def _detect_config_conflicts( 

291 self, structure: ProjectStructure 

292 ) -> List[ConflictInfo]: 

293 """Detecta conflitos de configuração.""" 

294 conflicts = [] 

295 

296 # Verificar se já existe core-config.yml 

297 core_config_path = ( 

298 self.project_path / ".jtech-core" / "core-config.yml" 

299 ) 

300 if core_config_path.exists(): 

301 conflicts.append( 

302 ConflictInfo( 

303 type="config_override", 

304 severity="medium", 

305 description="Arquivo core-config.yml já existe", 

306 affected_files=[ 

307 str(core_config_path.relative_to(self.project_path)) 

308 ], 

309 recommendation="O arquivo existente será preservado e mesclado", 

310 ) 

311 ) 

312 

313 # Verificar conflitos com outros frameworks de documentação 

314 doc_conflicts = [] 

315 for config_file in structure.existing_configs: 

316 if any( 

317 doc_tool in config_file.lower() 

318 for doc_tool in ["sphinx", "mkdocs", "gitbook", "docusaurus"] 

319 ): 

320 doc_conflicts.append(config_file) 

321 

322 if doc_conflicts: 

323 conflicts.append( 

324 ConflictInfo( 

325 type="documentation_conflict", 

326 severity="low", 

327 description="Sistema de documentação existente detectado", 

328 affected_files=doc_conflicts, 

329 recommendation="Considere integrar com o sistema existente", 

330 ) 

331 ) 

332 

333 return conflicts 

334 

335 def _detect_directory_conflicts( 

336 self, structure: ProjectStructure 

337 ) -> List[ConflictInfo]: 

338 """Detecta conflitos de estrutura de diretório.""" 

339 conflicts = [] 

340 

341 # Verificar se .jtech-core já existe 

342 jtech_dir = self.project_path / ".jtech-core" 

343 if jtech_dir.exists(): 

344 conflicts.append( 

345 ConflictInfo( 

346 type="directory_exists", 

347 severity="high", 

348 description="Diretório .jtech-core já existe", 

349 affected_files=[".jtech-core/"], 

350 recommendation="Conteúdo existente será preservado quando possível", 

351 ) 

352 ) 

353 

354 # Verificar conflitos com .github 

355 github_dir = self.project_path / ".github" 

356 if github_dir.exists(): 

357 existing_workflows = list(github_dir.glob("workflows/*.yml")) 

358 if existing_workflows: 

359 conflicts.append( 

360 ConflictInfo( 

361 type="github_workflows", 

362 severity="medium", 

363 description="GitHub workflows existentes detectados", 

364 affected_files=[ 

365 str(f.relative_to(self.project_path)) 

366 for f in existing_workflows 

367 ], 

368 recommendation="Chatmodes serão adicionados sem afetar workflows", 

369 ) 

370 ) 

371 

372 return conflicts 

373 

374 def _detect_dependency_conflicts( 

375 self, structure: ProjectStructure 

376 ) -> List[ConflictInfo]: 

377 """Detecta conflitos de dependências.""" 

378 conflicts = [] 

379 

380 # Verificar conflitos Python 

381 if ( 

382 ProjectType.PYTHON in [structure.project_type] 

383 or "python" in structure.languages 

384 ): 

385 python_conflicts = self._check_python_conflicts(structure) 

386 conflicts.extend(python_conflicts) 

387 

388 # Verificar conflitos JavaScript/TypeScript 

389 if any( 

390 lang in structure.languages 

391 for lang in ["javascript", "typescript"] 

392 ): 

393 js_conflicts = self._check_js_conflicts(structure) 

394 conflicts.extend(js_conflicts) 

395 

396 return conflicts 

397 

398 def _detect_vscode_conflicts( 

399 self, structure: ProjectStructure 

400 ) -> List[ConflictInfo]: 

401 """Detecta conflitos do VS Code.""" 

402 conflicts = [] 

403 

404 vscode_dir = self.project_path / ".vscode" 

405 if vscode_dir.exists(): 

406 existing_files = [] 

407 for vscode_file in [ 

408 "settings.json", 

409 "extensions.json", 

410 "tasks.json", 

411 "launch.json", 

412 ]: 

413 if (vscode_dir / vscode_file).exists(): 

414 existing_files.append(f".vscode/{vscode_file}") 

415 

416 if existing_files: 

417 conflicts.append( 

418 ConflictInfo( 

419 type="vscode_config_exists", 

420 severity="medium", 

421 description="Configurações VS Code existentes detectadas", 

422 affected_files=existing_files, 

423 recommendation="Configurações serão mescladas preservando as existentes", 

424 ) 

425 ) 

426 

427 return conflicts 

428 

429 def _generate_recommendations( 

430 self, structure: ProjectStructure, conflicts: List[ConflictInfo] 

431 ) -> List[str]: 

432 """Gera recomendações baseadas na análise.""" 

433 recommendations = [] 

434 

435 # Recomendações baseadas no tipo de projeto 

436 if structure.project_type == ProjectType.PYTHON: 

437 recommendations.append( 

438 "Considere usar o tipo de equipe 'fullstack' ou 'no-ui' para projetos Python" 

439 ) 

440 

441 elif ( 

442 structure.project_type == ProjectType.JAVASCRIPT 

443 or structure.project_type == ProjectType.TYPESCRIPT 

444 ): 

445 if ( 

446 FrameworkType.REACT in structure.frameworks 

447 or FrameworkType.VUE in structure.frameworks 

448 ): 

449 recommendations.append( 

450 "Tipo de equipe 'fullstack' recomendado para projetos frontend" 

451 ) 

452 else: 

453 recommendations.append( 

454 "Considere o tipo 'no-ui' para projetos backend JavaScript" 

455 ) 

456 

457 elif structure.project_type == ProjectType.MIXED: 

458 recommendations.append( 

459 "Projeto multi-linguagem detectado - tipo 'all' recomendado" 

460 ) 

461 

462 # Recomendações baseadas em conflitos 

463 high_severity_conflicts = [ 

464 c for c in conflicts if c.severity == "high" 

465 ] 

466 if high_severity_conflicts: 

467 recommendations.append( 

468 "Execute backup antes da instalação devido a conflitos de alta prioridade" 

469 ) 

470 

471 medium_severity_conflicts = [ 

472 c for c in conflicts if c.severity == "medium" 

473 ] 

474 if len(medium_severity_conflicts) > 2: 

475 recommendations.append( 

476 "Considere revisar configurações existentes antes da instalação" 

477 ) 

478 

479 # Recomendações específicas 

480 if not structure.git_info: 

481 recommendations.append( 

482 "Considere inicializar repositório Git antes da instalação" 

483 ) 

484 

485 if ".gitignore" not in [ 

486 Path(f).name for f in structure.existing_configs 

487 ]: 

488 recommendations.append( 

489 "Adicione .gitignore apropriado para seu tipo de projeto" 

490 ) 

491 

492 return recommendations 

493 

494 def _suggest_team_type(self, structure: ProjectStructure) -> TeamType: 

495 """Sugere tipo de equipe baseado na estrutura.""" 

496 # Lógica de sugestão baseada em frameworks e linguagens 

497 frontend_frameworks = { 

498 FrameworkType.REACT, 

499 FrameworkType.VUE, 

500 FrameworkType.ANGULAR, 

501 FrameworkType.NEXTJS, 

502 } 

503 backend_frameworks = { 

504 FrameworkType.DJANGO, 

505 FrameworkType.FLASK, 

506 FrameworkType.FASTAPI, 

507 FrameworkType.EXPRESS, 

508 } 

509 

510 has_frontend = any( 

511 fw in structure.frameworks for fw in frontend_frameworks 

512 ) 

513 has_backend = any( 

514 fw in structure.frameworks for fw in backend_frameworks 

515 ) 

516 

517 if has_frontend and has_backend: 

518 return TeamType.FULLSTACK 

519 elif has_frontend: 

520 return ( 

521 TeamType.FULLSTACK 

522 ) # Frontend ainda precisa de capacidades full-stack 

523 elif has_backend: 

524 return TeamType.NO_UI 

525 elif len(structure.languages) > 2: 

526 return TeamType.ALL 

527 else: 

528 return TeamType.IDE_MINIMAL 

529 

530 def _calculate_compatibility_score( 

531 self, structure: ProjectStructure, conflicts: List[ConflictInfo] 

532 ) -> float: 

533 """Calcula score de compatibilidade (0-100).""" 

534 base_score = 100.0 

535 

536 # Penalizar por conflitos 

537 for conflict in conflicts: 

538 if conflict.severity == "critical": 

539 base_score -= 25 

540 elif conflict.severity == "high": 

541 base_score -= 15 

542 elif conflict.severity == "medium": 

543 base_score -= 10 

544 elif conflict.severity == "low": 

545 base_score -= 5 

546 

547 # Bonificar por indicadores positivos 

548 if structure.git_info: 

549 base_score += 5 

550 

551 if structure.project_type != ProjectType.UNKNOWN: 

552 base_score += 10 

553 

554 if len(structure.frameworks) > 0: 

555 base_score += 5 

556 

557 return max(0.0, min(100.0, base_score)) 

558 

559 def _generate_warnings( 

560 self, structure: ProjectStructure, conflicts: List[ConflictInfo] 

561 ) -> List[str]: 

562 """Gera warnings baseados na análise.""" 

563 warnings = [] 

564 

565 # Warnings para conflitos críticos 

566 critical_conflicts = [c for c in conflicts if c.severity == "critical"] 

567 for conflict in critical_conflicts: 

568 warnings.append(f"CRÍTICO: {conflict.description}") 

569 

570 # Warnings para estrutura 

571 if structure.project_type == ProjectType.UNKNOWN: 

572 warnings.append( 

573 "Tipo de projeto não identificado - instalação pode não ser otimizada" 

574 ) 

575 

576 if not structure.existing_configs: 

577 warnings.append( 

578 "Nenhum arquivo de configuração detectado - verifique se está no diretório correto" 

579 ) 

580 

581 return warnings 

582 

583 # Métodos auxiliares 

584 def _should_ignore_file(self, file_path: Path) -> bool: 

585 """Verifica se arquivo deve ser ignorado na análise.""" 

586 ignore_patterns = [ 

587 "*/node_modules/*", 

588 "*/.git/*", 

589 "*/__pycache__/*", 

590 "*/venv/*", 

591 "*/.venv/*", 

592 "*/.env/*", 

593 "*/dist/*", 

594 "*/build/*", 

595 "*/env/*", 

596 ] 

597 

598 # Verificar se alguma parte do caminho contém termos a ignorar 

599 ignore_terms = [ 

600 "node_modules", 

601 ".git", 

602 "__pycache__", 

603 "venv", 

604 ".venv", 

605 ".env", 

606 "dist", 

607 "build", 

608 "env", 

609 ] 

610 

611 # Verificar padrões diretos 

612 if any(file_path.match(pattern) for pattern in ignore_patterns): 

613 return True 

614 

615 # Verificar se algum componente do path contém termos a ignorar 

616 path_parts = file_path.parts 

617 for part in path_parts: 

618 if any(term in part for term in ignore_terms): 

619 return True 

620 

621 return False 

622 

623 def _detect_frameworks_in_file( 

624 self, file_path: Path 

625 ) -> List[FrameworkType]: 

626 """Detecta frameworks baseado em um arquivo.""" 

627 frameworks = [] 

628 

629 # Análise baseada em nome/estrutura de arquivo 

630 for framework, patterns in self.framework_patterns.items(): 

631 if any(pattern in str(file_path).lower() for pattern in patterns): 

632 frameworks.append(framework) 

633 

634 # Análise de conteúdo para alguns casos específicos 

635 if file_path.name == "package.json": 

636 frameworks.extend(self._analyze_package_json(file_path)) 

637 elif file_path.name in ["requirements.txt", "pyproject.toml"]: 

638 frameworks.extend(self._analyze_python_deps(file_path)) 

639 

640 return frameworks 

641 

642 def _detect_build_tools(self, file_path: Path) -> Set[str]: 

643 """Detecta ferramentas de build.""" 

644 build_tools = set() 

645 

646 build_indicators = { 

647 "webpack.config.js": "webpack", 

648 "vite.config.js": "vite", 

649 "rollup.config.js": "rollup", 

650 "gulpfile.js": "gulp", 

651 "Gruntfile.js": "grunt", 

652 "Makefile": "make", 

653 "CMakeLists.txt": "cmake", 

654 "build.gradle": "gradle", 

655 "pom.xml": "maven", 

656 } 

657 

658 for indicator, tool in build_indicators.items(): 

659 if file_path.name == indicator: 

660 build_tools.add(tool) 

661 

662 return build_tools 

663 

664 def _is_config_file(self, file_path: Path) -> bool: 

665 """Verifica se é um arquivo de configuração.""" 

666 config_extensions = { 

667 ".json", 

668 ".yml", 

669 ".yaml", 

670 ".toml", 

671 ".ini", 

672 ".cfg", 

673 ".conf", 

674 } 

675 config_names = { 

676 "package.json", 

677 "composer.json", 

678 "Cargo.toml", 

679 "pyproject.toml", 

680 "requirements.txt", 

681 "Gemfile", 

682 "go.mod", 

683 ".gitignore", 

684 "README.md", 

685 } 

686 

687 return ( 

688 file_path.suffix in config_extensions 

689 or file_path.name in config_names 

690 ) 

691 

692 def _analyze_dependencies(self) -> Dict[str, Any]: 

693 """Analisa dependências do projeto.""" 

694 dependencies = {} 

695 

696 # Python 

697 req_file = self.project_path / "requirements.txt" 

698 if req_file.exists(): 

699 dependencies["python"] = self._parse_requirements_txt(req_file) 

700 

701 # Node.js 

702 package_file = self.project_path / "package.json" 

703 if package_file.exists(): 

704 dependencies["nodejs"] = self._parse_package_json(package_file) 

705 

706 return dependencies 

707 

708 def _analyze_git_info(self) -> Optional[Dict[str, Any]]: 

709 """Analisa informações do Git.""" 

710 git_dir = self.project_path / ".git" 

711 if not git_dir.exists(): 

712 return None 

713 

714 git_info = {"has_git": True} 

715 

716 # Verificar se há commits 

717 try: 

718 # Verificação simples de estrutura Git 

719 if (git_dir / "HEAD").exists(): 

720 git_info["initialized"] = True 

721 

722 # Verificar branches remotos 

723 refs_dir = git_dir / "refs" / "remotes" 

724 if refs_dir.exists(): 

725 git_info["has_remotes"] = True 

726 except Exception: 

727 pass 

728 

729 return git_info 

730 

731 def _check_python_conflicts( 

732 self, structure: ProjectStructure 

733 ) -> List[ConflictInfo]: 

734 """Verifica conflitos específicos do Python.""" 

735 conflicts = [] 

736 

737 # Verificar múltiplos gerenciadores de dependência 

738 dep_files = [ 

739 "requirements.txt", 

740 "Pipfile", 

741 "pyproject.toml", 

742 "environment.yml", 

743 ] 

744 existing_dep_files = [ 

745 f for f in dep_files if (self.project_path / f).exists() 

746 ] 

747 

748 if len(existing_dep_files) > 1: 

749 conflicts.append( 

750 ConflictInfo( 

751 type="multiple_dependency_managers", 

752 severity="medium", 

753 description="Múltiplos gerenciadores de dependência Python detectados", 

754 affected_files=existing_dep_files, 

755 recommendation="Considere padronizar em um único gerenciador", 

756 ) 

757 ) 

758 

759 return conflicts 

760 

761 def _check_js_conflicts( 

762 self, structure: ProjectStructure 

763 ) -> List[ConflictInfo]: 

764 """Verifica conflitos específicos do JavaScript.""" 

765 conflicts = [] 

766 

767 # Verificar múltiplos lock files 

768 lock_files = ["package-lock.json", "yarn.lock", "pnpm-lock.yaml"] 

769 existing_locks = [ 

770 f for f in lock_files if (self.project_path / f).exists() 

771 ] 

772 

773 if len(existing_locks) > 1: 

774 conflicts.append( 

775 ConflictInfo( 

776 type="multiple_package_managers", 

777 severity="medium", 

778 description="Múltiplos gerenciadores de pacote JavaScript detectados", 

779 affected_files=existing_locks, 

780 recommendation="Use apenas um gerenciador de pacotes", 

781 ) 

782 ) 

783 

784 return conflicts 

785 

786 def _analyze_package_json(self, file_path: Path) -> List[FrameworkType]: 

787 """Analisa package.json para detectar frameworks.""" 

788 frameworks = [] 

789 try: 

790 import json 

791 

792 with open(file_path, "r", encoding="utf-8") as f: 

793 data = json.load(f) 

794 

795 deps = { 

796 **data.get("dependencies", {}), 

797 **data.get("devDependencies", {}), 

798 } 

799 

800 if "react" in deps: 

801 frameworks.append(FrameworkType.REACT) 

802 if "vue" in deps: 

803 frameworks.append(FrameworkType.VUE) 

804 if "@angular/core" in deps: 

805 frameworks.append(FrameworkType.ANGULAR) 

806 if "next" in deps: 

807 frameworks.append(FrameworkType.NEXTJS) 

808 if "express" in deps: 

809 frameworks.append(FrameworkType.EXPRESS) 

810 

811 except Exception: 

812 pass 

813 

814 return frameworks 

815 

816 def _analyze_python_deps(self, file_path: Path) -> List[FrameworkType]: 

817 """Analisa dependências Python para detectar frameworks.""" 

818 frameworks = [] 

819 try: 

820 content = file_path.read_text(encoding="utf-8").lower() 

821 

822 # Usar regex para correspondências exatas de pacotes 

823 import re 

824 

825 if re.search(r"\bdjango\b", content): 

826 frameworks.append(FrameworkType.DJANGO) 

827 if re.search(r"\bflask\b", content): 

828 frameworks.append(FrameworkType.FLASK) 

829 if re.search(r"\bfastapi\b", content): 

830 frameworks.append(FrameworkType.FASTAPI) 

831 

832 except Exception: 

833 pass 

834 

835 return frameworks 

836 

837 def _parse_requirements_txt(self, file_path: Path) -> List[str]: 

838 """Parse requirements.txt.""" 

839 try: 

840 return [ 

841 line.strip() 

842 for line in file_path.read_text().splitlines() 

843 if line.strip() and not line.startswith("#") 

844 ] 

845 except Exception: 

846 return [] 

847 

848 def _parse_package_json(self, file_path: Path) -> Dict[str, Any]: 

849 """Parse package.json.""" 

850 try: 

851 import json 

852 

853 with open(file_path, "r", encoding="utf-8") as f: 

854 return json.load(f) 

855 except Exception: 

856 return {}