Coverage for src/jtech_installer/validator/integrity.py: 97%
97 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"""Integrity validator for JTECH™ Installer"""
3import hashlib
4from dataclasses import dataclass
5from pathlib import Path
6from typing import Dict, List, Optional
8from ..core.exceptions import JTechInstallerException
9from ..core.models import InstallationConfig
12@dataclass
13class IntegrityCheckResult:
14 """Resultado de verificação de integridade"""
16 component: str
17 is_valid: bool
18 expected_checksum: Optional[str] = None
19 actual_checksum: Optional[str] = None
20 error_message: Optional[str] = None
23class IntegrityValidator:
24 """Valida a integridade da instalação"""
26 def __init__(self, config: InstallationConfig):
27 self.config = config
28 self.project_path = config.project_path
29 self.jtech_core_path = self.project_path / ".jtech-core"
31 def validate_all(self) -> bool:
32 """Executa todas as validações de integridade"""
33 results = []
35 # Validar estrutura de diretórios
36 results.extend(self._validate_directory_structure())
38 # Validar arquivos de configuração
39 results.extend(self._validate_config_files())
41 # Validar agentes instalados
42 results.extend(self._validate_agents())
44 # Validar chatmodes
45 results.extend(self._validate_chatmodes())
47 # Verificar se há falhas
48 failed_checks = [r for r in results if not r.is_valid]
50 if failed_checks:
51 for failure in failed_checks:
52 print(f"❌ {failure.component}: {failure.error_message}")
53 return False
55 print(f"✅ Todos os {len(results)} checks de integridade passaram")
56 return True
58 def _validate_directory_structure(self) -> List[IntegrityCheckResult]:
59 """Valida se a estrutura de diretórios está correta"""
60 results = []
62 required_dirs = [
63 ".jtech-core",
64 ".jtech-core/agents",
65 ".jtech-core/chatmodes",
66 ".jtech-core/templates",
67 ".jtech-core/workflows",
68 ".jtech-core/tasks",
69 ".jtech-core/checklists",
70 ".jtech-core/data",
71 ".jtech-core/utils",
72 ]
74 for dir_path in required_dirs:
75 full_path = self.project_path / dir_path
76 if full_path.exists() and full_path.is_dir():
77 results.append(
78 IntegrityCheckResult(
79 component=f"Directory {dir_path}", is_valid=True
80 )
81 )
82 else:
83 results.append(
84 IntegrityCheckResult(
85 component=f"Directory {dir_path}",
86 is_valid=False,
87 error_message=f"Diretório {dir_path} não existe",
88 )
89 )
91 return results
93 def _validate_config_files(self) -> List[IntegrityCheckResult]:
94 """Valida arquivos de configuração críticos"""
95 results = []
97 # Verificar core-config.yml
98 core_config = self.jtech_core_path / "core-config.yml"
99 if core_config.exists():
100 results.append(
101 IntegrityCheckResult(
102 component="core-config.yml", is_valid=True
103 )
104 )
105 else:
106 results.append(
107 IntegrityCheckResult(
108 component="core-config.yml",
109 is_valid=False,
110 error_message="Arquivo core-config.yml não encontrado",
111 )
112 )
114 # Verificar .vscode/settings.json se VS Code habilitado
115 if self.config.vs_code_integration:
116 vscode_settings = self.project_path / ".vscode" / "settings.json"
117 if vscode_settings.exists():
118 results.append(
119 IntegrityCheckResult(
120 component=".vscode/settings.json", is_valid=True
121 )
122 )
123 else:
124 results.append(
125 IntegrityCheckResult(
126 component=".vscode/settings.json",
127 is_valid=False,
128 error_message="Arquivo settings.json não encontrado",
129 )
130 )
132 return results
134 def _validate_agents(self) -> List[IntegrityCheckResult]:
135 """Valida integridade dos agentes instalados"""
136 results = []
137 agents_dir = self.jtech_core_path / "agents"
139 if not agents_dir.exists():
140 results.append(
141 IntegrityCheckResult(
142 component="Agents directory",
143 is_valid=False,
144 error_message="Diretório de agentes não existe",
145 )
146 )
147 return results
149 # Verificar se há pelo menos alguns agentes básicos
150 basic_agents = ["pm.md", "architect.md", "dev.md"]
151 found_agents = 0
153 for agent_file in basic_agents:
154 agent_path = agents_dir / agent_file
155 if agent_path.exists():
156 found_agents += 1
157 results.append(
158 IntegrityCheckResult(
159 component=f"Agent {agent_file}", is_valid=True
160 )
161 )
162 else:
163 results.append(
164 IntegrityCheckResult(
165 component=f"Agent {agent_file}",
166 is_valid=False,
167 error_message=f"Agente {agent_file} não encontrado",
168 )
169 )
171 return results
173 def _validate_chatmodes(self) -> List[IntegrityCheckResult]:
174 """Valida chatmodes instalados"""
175 results = []
176 chatmodes_dir = self.project_path / ".github" / "chatmodes"
178 if not chatmodes_dir.exists():
179 results.append(
180 IntegrityCheckResult(
181 component="Chatmodes directory",
182 is_valid=False,
183 error_message="Diretório .github/chatmodes não existe",
184 )
185 )
186 return results
188 # Contar arquivos .chatmode.md
189 chatmode_files = list(chatmodes_dir.glob("*.chatmode.md"))
191 if len(chatmode_files) > 0:
192 results.append(
193 IntegrityCheckResult(
194 component=f"Chatmodes ({len(chatmode_files)} arquivos)",
195 is_valid=True,
196 )
197 )
198 else:
199 results.append(
200 IntegrityCheckResult(
201 component="Chatmodes",
202 is_valid=False,
203 error_message="Nenhum arquivo .chatmode.md encontrado",
204 )
205 )
207 return results
209 def calculate_file_checksum(self, file_path: Path) -> str:
210 """Calcula checksum SHA256 de um arquivo"""
211 if not file_path.exists():
212 raise JTechInstallerException(
213 f"Arquivo não encontrado: {file_path}"
214 )
216 sha256_hash = hashlib.sha256()
217 with open(file_path, "rb") as f:
218 for chunk in iter(lambda: f.read(4096), b""):
219 sha256_hash.update(chunk)
220 return sha256_hash.hexdigest()
222 def verify_checksums(
223 self, expected_checksums: Dict[str, str]
224 ) -> List[IntegrityCheckResult]:
225 """Verifica checksums de arquivos específicos"""
226 results = []
228 for relative_path, expected_checksum in expected_checksums.items():
229 file_path = self.project_path / relative_path
231 try:
232 actual_checksum = self.calculate_file_checksum(file_path)
233 is_valid = actual_checksum == expected_checksum
235 results.append(
236 IntegrityCheckResult(
237 component=f"Checksum {relative_path}",
238 is_valid=is_valid,
239 expected_checksum=expected_checksum,
240 actual_checksum=actual_checksum,
241 error_message=(
242 None if is_valid else "Checksum não confere"
243 ),
244 )
245 )
246 except Exception as e:
247 results.append(
248 IntegrityCheckResult(
249 component=f"Checksum {relative_path}",
250 is_valid=False,
251 error_message=str(e),
252 )
253 )
255 return results