Coverage for src/jtech_installer/installer/config_generator.py: 90%
49 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-08-20 15:10 -0300
« prev ^ index » next coverage.py v7.8.0, created at 2025-08-20 15:10 -0300
1"""Gerador de configuração core-config.yml personalizada."""
3from pathlib import Path
4from typing import Any, Dict, Optional
6import yaml
8from ..core.models import InstallationConfig, TeamType
11class ConfigGenerator:
12 """Gera configuração core-config.yml personalizada baseada no tipo de equipe."""
14 def __init__(self, base_template_path: Optional[Path] = None):
15 """
16 Inicializa o gerador de configuração.
18 Args:
19 base_template_path: Caminho para template base do core-config.yml
20 """
21 self.base_template_path = base_template_path
23 def generate_config(
24 self, config: InstallationConfig, target_path: Path
25 ) -> Dict[str, Any]:
26 """
27 Gera configuração personalizada baseada no tipo de equipe.
29 Args:
30 config: Configuração de instalação
31 target_path: Caminho do projeto de destino
33 Returns:
34 Dicionário com configuração gerada
35 """
36 # Configuração base comum a todos os tipos
37 base_config = self._get_base_config()
39 # Personalizações por tipo de equipe
40 team_config = self._get_team_specific_config(config.team_type)
42 # Configurações específicas do projeto
43 project_config = self._get_project_specific_config(target_path)
45 # Merge das configurações
46 final_config = {**base_config, **team_config, **project_config}
48 return final_config
50 def _get_base_config(self) -> Dict[str, Any]:
51 """Retorna configuração base comum a todos os tipos."""
52 return {
53 "markdownExploder": True,
54 "slashPrefix": "jtech",
55 "qa": {"qaLocation": "docs/qa"},
56 "prd": {
57 "prdFile": "docs/prd.md",
58 "prdVersion": "v2",
59 "prdSharded": True,
60 "prdShardedLocation": "docs/prd",
61 "epicFilePattern": "epic-{n}*.md",
62 },
63 "architecture": {
64 "architectureFile": "docs/architecture.md",
65 "architectureVersion": "v2",
66 "architectureSharded": True,
67 "architectureShardedLocation": "docs/architecture",
68 },
69 "devDebugLog": ".ai/debug-log.md",
70 "devStoryLocation": "docs/stories",
71 }
73 def _get_team_specific_config(self, team_type: TeamType) -> Dict[str, Any]:
74 """Retorna configurações específicas por tipo de equipe."""
75 configs = {
76 TeamType.ALL: {
77 "customTechnicalDocuments": [
78 "docs/architecture/coding-standards.md",
79 "docs/architecture/tech-stack.md",
80 "docs/architecture/source-tree.md",
81 "docs/architecture/deployment.md",
82 "docs/architecture/security.md",
83 ],
84 "devLoadAlwaysFiles": [
85 "docs/architecture/coding-standards.md",
86 "docs/architecture/tech-stack.md",
87 "docs/architecture/source-tree.md",
88 "docs/architecture/deployment.md",
89 ],
90 },
91 TeamType.FULLSTACK: {
92 "customTechnicalDocuments": [
93 "docs/architecture/coding-standards.md",
94 "docs/architecture/tech-stack.md",
95 "docs/architecture/source-tree.md",
96 "docs/architecture/deployment.md",
97 ],
98 "devLoadAlwaysFiles": [
99 "docs/architecture/coding-standards.md",
100 "docs/architecture/tech-stack.md",
101 "docs/architecture/source-tree.md",
102 ],
103 },
104 TeamType.NO_UI: {
105 "customTechnicalDocuments": [
106 "docs/architecture/coding-standards.md",
107 "docs/architecture/tech-stack.md",
108 "docs/architecture/api-design.md",
109 ],
110 "devLoadAlwaysFiles": [
111 "docs/architecture/coding-standards.md",
112 "docs/architecture/tech-stack.md",
113 "docs/architecture/api-design.md",
114 ],
115 },
116 TeamType.IDE_MINIMAL: {
117 "customTechnicalDocuments": None,
118 "devLoadAlwaysFiles": [
119 "docs/architecture/coding-standards.md",
120 "docs/architecture/tech-stack.md",
121 ],
122 },
123 }
125 return configs.get(team_type, {})
127 def _get_project_specific_config(
128 self, target_path: Path
129 ) -> Dict[str, Any]:
130 """Retorna configurações específicas do projeto."""
131 config = {}
133 # Detecta se é projeto existente (brownfield)
134 if self._is_brownfield_project(target_path):
135 config["projectType"] = "brownfield"
137 # Ajusta caminhos para projetos existentes
138 if (target_path / "documentation").exists():
139 config["prd"]["prdFile"] = "documentation/prd.md"
140 config["architecture"][
141 "architectureFile"
142 ] = "documentation/architecture.md"
143 config["qa"]["qaLocation"] = "documentation/qa"
145 else:
146 config["projectType"] = "greenfield"
148 return config
150 def _is_brownfield_project(self, target_path: Path) -> bool:
151 """Verifica se é um projeto existente (brownfield)."""
152 indicators = [
153 target_path / "src",
154 target_path / "lib",
155 target_path / "app",
156 target_path / "package.json",
157 target_path / "requirements.txt",
158 target_path / "Cargo.toml",
159 target_path / "go.mod",
160 ]
162 return any(indicator.exists() for indicator in indicators)
164 def write_config(
165 self, config_dict: Dict[str, Any], target_path: Path
166 ) -> Path:
167 """
168 Escreve a configuração no arquivo core-config.yml.
170 Args:
171 config_dict: Dicionário com configuração
172 target_path: Caminho do projeto de destino
174 Returns:
175 Caminho do arquivo gerado
176 """
177 config_file = target_path / ".jtech-core" / "core-config.yml"
179 # Garante que o diretório existe
180 config_file.parent.mkdir(parents=True, exist_ok=True)
182 # Escreve o arquivo YAML
183 with open(config_file, "w", encoding="utf-8") as f:
184 yaml.dump(
185 config_dict,
186 f,
187 default_flow_style=False,
188 allow_unicode=True,
189 sort_keys=False,
190 )
192 return config_file
194 def validate_config(self, config_dict: Dict[str, Any]) -> bool:
195 """
196 Valida a configuração gerada.
198 Args:
199 config_dict: Dicionário com configuração
201 Returns:
202 True se válida, False caso contrário
203 """
204 required_keys = ["prd", "architecture", "qa", "slashPrefix"]
206 for key in required_keys:
207 if key not in config_dict:
208 return False
210 # Valida estrutura do PRD
211 prd_config = config_dict.get("prd", {})
212 if not all(k in prd_config for k in ["prdFile", "prdVersion"]):
213 return False
215 # Valida estrutura da arquitetura
216 arch_config = config_dict.get("architecture", {})
217 if not all(
218 k in arch_config
219 for k in ["architectureFile", "architectureVersion"]
220 ):
221 return False
223 return True