{
"tool_response": {
"type": "update",
"filePath": "\/var\/www\/mcp-servers\/mcp_contracts\/validators\/contract_validator.py",
"content": "\"\"\"Contract Validator - Orchestriert Contract-Validierung.\"\"\"\n\nimport os\nfrom typing import Optional, Any\n\nfrom domain.contracts import ContractValidationResult\nfrom validators.scope_resolver import ScopeResolver\nfrom validators.rule_evaluator import RuleEvaluator\n\n\nclass ContractValidator:\n \"\"\"Orchestriert Contract-Validierung mittels ScopeResolver und RuleEvaluator.\"\"\"\n\n BASE_PATH = \"\/var\/www\/dev.campus.systemische-tools.de\"\n SUPPORTED_EXTENSIONS = (\".php\", \".js\", \".css\", \".py\")\n\n def __init__(\n self,\n contract_data: dict[str, Any],\n scope_resolver: Optional[ScopeResolver] = None,\n rule_evaluator: Optional[RuleEvaluator] = None,\n ):\n self.contract_data = contract_data\n self.contract_name = contract_data.get(\"contract\", {}).get(\"name\", \"unknown\")\n self.scope_resolver = scope_resolver or ScopeResolver(self.BASE_PATH)\n self.rule_evaluator = rule_evaluator or RuleEvaluator()\n\n def validate(self, target_path: Optional[str] = None) -> ContractValidationResult:\n \"\"\"Fuehrt vollstaendige Validierung durch.\"\"\"\n result = ContractValidationResult(\n contract=self.contract_name,\n outcome=\"passed\",\n critical=0,\n major=0,\n minor=0,\n findings=[],\n )\n\n contract = self.contract_data.get(\"contract\", {})\n scope = contract.get(\"scope\", {})\n legacy_applicability = self.contract_data.get(\"applicability\")\n\n check_paths = self.scope_resolver.resolve_paths(\n scope, target_path, legacy_applicability\n )\n\n if not check_paths:\n result.findings.append({\"type\": \"info\", \"message\": \"No paths to validate\"})\n return result\n\n for check_path in check_paths:\n if not os.path.exists(check_path):\n result.critical += 1\n result.findings.append({\n \"type\": \"critical\",\n \"factor\": \"path_existence\",\n \"message\": f\"Path does not exist: {check_path}\",\n })\n continue\n\n if os.path.isdir(check_path):\n self._validate_directory(check_path, result)\n else:\n self._validate_file(check_path, result)\n\n result.outcome = self.rule_evaluator.determine_outcome(result)\n return result\n\n def _validate_directory(self, dir_path: str, result: ContractValidationResult) -> None:\n \"\"\"Validiert alle Dateien in einem Verzeichnis.\"\"\"\n for root, _, files in os.walk(dir_path):\n for f in files:\n if f.endswith(self.SUPPORTED_EXTENSIONS):\n self._validate_file(os.path.join(root, f), result)\n\n def _validate_file(self, file_path: str, result: ContractValidationResult) -> None:\n \"\"\"Validiert eine einzelne Datei gegen den Contract.\"\"\"\n try:\n with open(file_path, \"r\", encoding=\"utf-8\") as f:\n content = f.read()\n\n contract = self.contract_data.get(\"contract\", {})\n\n # Rules-basierte Validierung\n for rule in contract.get(\"rules\", []):\n in_files = rule.get(\"in_files\", \"**\/*\")\n if self.scope_resolver.file_matches_pattern(file_path, in_files):\n self.rule_evaluator.evaluate_rule(file_path, content, rule, result)\n\n # Legacy-Formate\n forbidden = contract.get(\"forbidden\", [])\n if forbidden:\n self.rule_evaluator.evaluate_legacy_forbidden(\n file_path, content, forbidden, result\n )\n\n required = contract.get(\"required\", [])\n if required:\n self.rule_evaluator.evaluate_legacy_required(\n file_path, content, required, result\n )\n\n except Exception as e:\n result.minor += 1\n result.findings.append({\n \"type\": \"minor\",\n \"factor\": \"file_read_error\",\n \"file\": file_path,\n \"message\": str(e)[:100],\n })\n\n\ndef run_contract_validation(\n contract_data: dict[str, Any],\n target_path: Optional[str] = None,\n) -> ContractValidationResult:\n \"\"\"Hilfsfunktion fuer Contract-Validierung.\"\"\"\n validator = ContractValidator(contract_data)\n return validator.validate(target_path)\n",
"structuredPatch": [
{
"oldStart": 1,
"oldLines": 27,
"newStart": 1,
"newLines": 32,
"lines": [
"-\"\"\"Contract Validator - YAML-Validierung und Scope-Prüfung\"\"\"",
"+\"\"\"Contract Validator - Orchestriert Contract-Validierung.\"\"\"",
"+",
" import os",
"-import re",
"-import sys",
"-import glob as globlib",
"-from typing import Optional, Dict, Any, List",
"+from typing import Optional, Any",
" ",
"-import yaml",
"-",
"-sys.path.insert(0, \"\/var\/www\/mcp-servers\/mcp_contracts\")",
" from domain.contracts import ContractValidationResult",
"+from validators.scope_resolver import ScopeResolver",
"+from validators.rule_evaluator import RuleEvaluator",
" ",
" ",
" class ContractValidator:",
"- \"\"\"Validiert Contracts gegen ihren definierten Scope\"\"\"",
"+ \"\"\"Orchestriert Contract-Validierung mittels ScopeResolver und RuleEvaluator.\"\"\"",
" ",
" BASE_PATH = \"\/var\/www\/dev.campus.systemische-tools.de\"",
"+ SUPPORTED_EXTENSIONS = (\".php\", \".js\", \".css\", \".py\")",
" ",
"- def __init__(self, contract_data: Dict[str, Any]):",
"+ def __init__(",
"+ self,",
"+ contract_data: dict[str, Any],",
"+ scope_resolver: Optional[ScopeResolver] = None,",
"+ rule_evaluator: Optional[RuleEvaluator] = None,",
"+ ):",
" self.contract_data = contract_data",
" self.contract_name = contract_data.get(\"contract\", {}).get(\"name\", \"unknown\")",
"+ self.scope_resolver = scope_resolver or ScopeResolver(self.BASE_PATH)",
"+ self.rule_evaluator = rule_evaluator or RuleEvaluator()",
" ",
" def validate(self, target_path: Optional[str] = None) -> ContractValidationResult:",
"- \"\"\"Führt vollständige Validierung durch\"\"\"",
"+ \"\"\"Fuehrt vollstaendige Validierung durch.\"\"\"",
" result = ContractValidationResult(",
" contract=self.contract_name,",
" outcome=\"passed\","
]
},
{
"oldStart": 31,
"oldLines": 17,
"newStart": 36,
"newLines": 18,
"lines": [
" findings=[],",
" )",
" ",
"- # Scope ermitteln",
"- check_paths = self._get_check_paths(target_path)",
"+ contract = self.contract_data.get(\"contract\", {})",
"+ scope = contract.get(\"scope\", {})",
"+ legacy_applicability = self.contract_data.get(\"applicability\")",
" ",
"+ check_paths = self.scope_resolver.resolve_paths(",
"+ scope, target_path, legacy_applicability",
"+ )",
"+",
" if not check_paths:",
"- result.findings.append({",
"- \"type\": \"info\",",
"- \"message\": \"No paths to validate\",",
"- })",
"+ result.findings.append({\"type\": \"info\", \"message\": \"No paths to validate\"})",
" return result",
" ",
"- # Dateien validieren",
" for check_path in check_paths:",
" if not os.path.exists(check_path):",
" result.critical += 1"
]
},
{
"oldStart": 57,
"oldLines": 87,
"newStart": 63,
"newLines": 43,
"lines": [
" else:",
" self._validate_file(check_path, result)",
" ",
"- # Outcome bestimmen",
"- result.outcome = self._determine_outcome(result)",
"-",
"+ result.outcome = self.rule_evaluator.determine_outcome(result)",
" return result",
" ",
"- def _get_check_paths(self, target_path: Optional[str] = None) -> List[str]:",
"- \"\"\"Ermittelt zu prüfende Pfade aus Scope (unterstützt alle Formate)\"\"\"",
"- if target_path:",
"- return [target_path]",
"-",
"- check_paths = []",
"- contract = self.contract_data.get(\"contract\", {})",
"- scope = contract.get(\"scope\", {})",
"-",
"- # Neues Standard-Format: scope.paths",
"- paths_list = scope.get(\"paths\", [])",
"-",
"- # Legacy-Formate als Fallback",
"- if not paths_list:",
"- paths_list = scope.get(\"includes\", []) # Legacy: scope.includes",
"- if not paths_list:",
"- paths_list = scope.get(\"applies_to_paths\", []) # Legacy",
"- if not paths_list:",
"- # Legacy: applicability.scope",
"- applicability = self.contract_data.get(\"applicability\", {})",
"- paths_list = applicability.get(\"scope\", [])",
"- if isinstance(paths_list, str):",
"- paths_list = [paths_list]",
"-",
"- excludes = scope.get(\"excludes\", [])",
"-",
"- for pattern in paths_list:",
"- # Glob-Pattern expandieren",
"- full_pattern = os.path.join(self.BASE_PATH, pattern.lstrip(\"\/\"))",
"- matched = globlib.glob(full_pattern, recursive=True)",
"-",
"- if matched:",
"- for path in matched:",
"- # Excludes prüfen",
"- excluded = False",
"- for excl in excludes:",
"- excl_pattern = os.path.join(self.BASE_PATH, excl.lstrip(\"\/\"))",
"- if globlib.fnmatch.fnmatch(path, excl_pattern):",
"- excluded = True",
"- break",
"- if not excluded:",
"- check_paths.append(path)",
"- else:",
"- # Falls kein Match, versuche Basisverzeichnis",
"- pattern_path = pattern.replace(\"**\", \"\").replace(\"*\", \"\").rstrip(\"\/\")",
"- full_path = os.path.join(self.BASE_PATH, pattern_path.lstrip(\"\/\"))",
"- if os.path.exists(full_path):",
"- check_paths.append(full_path)",
"-",
"- return list(set(check_paths)) # Duplikate entfernen",
"-",
" def _validate_directory(self, dir_path: str, result: ContractValidationResult) -> None:",
"- \"\"\"Validiert alle Dateien in einem Verzeichnis\"\"\"",
"- for root, dirs, files in os.walk(dir_path):",
"+ \"\"\"Validiert alle Dateien in einem Verzeichnis.\"\"\"",
"+ for root, _, files in os.walk(dir_path):",
" for f in files:",
"- if f.endswith((\".php\", \".js\", \".css\", \".py\")):",
"- file_path = os.path.join(root, f)",
"- self._validate_file(file_path, result)",
"+ if f.endswith(self.SUPPORTED_EXTENSIONS):",
"+ self._validate_file(os.path.join(root, f), result)",
" ",
" def _validate_file(self, file_path: str, result: ContractValidationResult) -> None:",
"- \"\"\"Validiert eine einzelne Datei gegen den Contract\"\"\"",
"+ \"\"\"Validiert eine einzelne Datei gegen den Contract.\"\"\"",
" try:",
" with open(file_path, \"r\", encoding=\"utf-8\") as f:",
" content = f.read()",
" ",
"- # Neue Rules-basierte Validierung",
"- rules = self.contract_data.get(\"contract\", {}).get(\"rules\", [])",
"- for rule in rules:",
"- self._check_rule(file_path, content, rule, result)",
"+ contract = self.contract_data.get(\"contract\", {})",
" ",
"- # Legacy: Forbidden elements prüfen",
"- self._check_forbidden_elements(file_path, content, result)",
"+ # Rules-basierte Validierung",
"+ for rule in contract.get(\"rules\", []):",
"+ in_files = rule.get(\"in_files\", \"**\/*\")",
"+ if self.scope_resolver.file_matches_pattern(file_path, in_files):",
"+ self.rule_evaluator.evaluate_rule(file_path, content, rule, result)",
" ",
"- # Legacy: Required elements prüfen",
"- self._check_required_elements(file_path, content, result)",
"+ # Legacy-Formate",
"+ forbidden = contract.get(\"forbidden\", [])",
"+ if forbidden:",
"+ self.rule_evaluator.evaluate_legacy_forbidden(",
"+ file_path, content, forbidden, result",
"+ )",
" ",
"+ required = contract.get(\"required\", [])",
"+ if required:",
"+ self.rule_evaluator.evaluate_legacy_required(",
"+ file_path, content, required, result",
"+ )",
"+",
" except Exception as e:",
" result.minor += 1",
" result.findings.append({"
]
},
{
"oldStart": 147,
"oldLines": 263,
"newStart": 109,
"newLines": 11,
"lines": [
" \"message\": str(e)[:100],",
" })",
" ",
"- def _check_rule(",
"- self, file_path: str, content: str, rule: Dict[str, Any], result: ContractValidationResult",
"- ) -> None:",
"- \"\"\"Prüft eine einzelne Rule gegen eine Datei\"\"\"",
"- check_type = rule.get(\"check_type\", \"\")",
"- in_files = rule.get(\"in_files\", \"**\/*\")",
"- rule_id = rule.get(\"id\", \"unknown\")",
"- severity = rule.get(\"severity\", \"major\")",
"- description = rule.get(\"description\", \"\")",
" ",
"- # Prüfen ob Datei zum Rule-Pattern passt",
"- if not self._file_matches_pattern(file_path, in_files):",
"- return",
"-",
"- if check_type == \"line_count\":",
"- self._check_line_count(file_path, content, rule, result)",
"- elif check_type == \"forbidden_pattern\":",
"- self._check_forbidden_pattern(file_path, content, rule, result)",
"- elif check_type == \"required_pattern\":",
"- self._check_required_pattern(file_path, content, rule, result)",
"- elif check_type == \"dependency_check\":",
"- self._check_dependency(file_path, content, rule, result)",
"-",
"- def _file_matches_pattern(self, file_path: str, pattern: str) -> bool:",
"- \"\"\"Prüft ob Datei zum Glob-Pattern passt\"\"\"",
"- # Relative path from BASE_PATH",
"- rel_path = file_path.replace(self.BASE_PATH, \"\").lstrip(\"\/\")",
"-",
"- # Einfache Pattern-Prüfung",
"- if \"**\" in pattern:",
"- # z.B. Controller\/**\/*.php",
"- parts = pattern.split(\"**\")",
"- if len(parts) == 2:",
"- prefix = parts[0].rstrip(\"\/\")",
"- suffix = parts[1].lstrip(\"\/\")",
"- if prefix and not rel_path.startswith(prefix.lstrip(\"\/\")):",
"- return False",
"- if suffix and not globlib.fnmatch.fnmatch(rel_path, f\"*{suffix}\"):",
"- return False",
"- return True",
"- elif \"*\" in pattern:",
"- return globlib.fnmatch.fnmatch(rel_path, pattern)",
"- else:",
"- return pattern in rel_path",
"-",
"- return True",
"-",
"- def _check_line_count(",
"- self, file_path: str, content: str, rule: Dict[str, Any], result: ContractValidationResult",
"- ) -> None:",
"- \"\"\"Prüft Zeilenanzahl einer Datei\"\"\"",
"- max_lines = rule.get(\"max_lines\", 500)",
"- severity = rule.get(\"severity\", \"major\")",
"- rule_id = rule.get(\"id\", \"line_count\")",
"-",
"- line_count = len(content.splitlines())",
"- if line_count > max_lines:",
"- self._add_violation(result, severity, {",
"- \"rule_id\": rule_id,",
"- \"factor\": \"line_count\",",
"- \"file\": file_path,",
"- \"message\": f\"File has {line_count} lines (max: {max_lines})\",",
"- \"actual\": line_count,",
"- \"limit\": max_lines,",
"- })",
"-",
"- def _check_forbidden_pattern(",
"- self, file_path: str, content: str, rule: Dict[str, Any], result: ContractValidationResult",
"- ) -> None:",
"- \"\"\"Prüft auf verbotene Patterns\"\"\"",
"- patterns = rule.get(\"patterns\", [])",
"- severity = rule.get(\"severity\", \"major\")",
"- rule_id = rule.get(\"id\", \"forbidden_pattern\")",
"- description = rule.get(\"description\", \"\")",
"-",
"- for pattern in patterns:",
"- # Versuche als Regex, sonst als String",
"- try:",
"- matches = re.findall(pattern, content)",
"- except re.error:",
"- matches = [pattern] if pattern in content else []",
"-",
"- if matches:",
"- # Zeile finden",
"- line_no = None",
"- for i, line in enumerate(content.splitlines(), 1):",
"- if pattern in line or (re.search(pattern, line) if self._is_regex(pattern) else False):",
"- line_no = i",
"- break",
"-",
"- self._add_violation(result, severity, {",
"- \"rule_id\": rule_id,",
"- \"factor\": \"forbidden_pattern\",",
"- \"file\": file_path,",
"- \"message\": f\"Forbidden pattern found: '{pattern}'\",",
"- \"description\": description,",
"- \"line\": line_no,",
"- \"occurrences\": len(matches),",
"- })",
"-",
"- def _check_required_pattern(",
"- self, file_path: str, content: str, rule: Dict[str, Any], result: ContractValidationResult",
"- ) -> None:",
"- \"\"\"Prüft auf erforderliche Patterns\"\"\"",
"- patterns = rule.get(\"patterns\", [])",
"- severity = rule.get(\"severity\", \"major\")",
"- rule_id = rule.get(\"id\", \"required_pattern\")",
"- description = rule.get(\"description\", \"\")",
"-",
"- for pattern in patterns:",
"- try:",
"- found = bool(re.search(pattern, content))",
"- except re.error:",
"- found = pattern in content",
"-",
"- if not found:",
"- self._add_violation(result, severity, {",
"- \"rule_id\": rule_id,",
"- \"factor\": \"required_pattern\",",
"- \"file\": file_path,",
"- \"message\": f\"Required pattern not found: '{pattern}'\",",
"- \"description\": description,",
"- })",
"-",
"- def _check_dependency(",
"- self, file_path: str, content: str, rule: Dict[str, Any], result: ContractValidationResult",
"- ) -> None:",
"- \"\"\"Prüft auf verbotene Dependencies\/Imports\"\"\"",
"- forbidden_imports = rule.get(\"forbidden_imports\", [])",
"- severity = rule.get(\"severity\", \"critical\")",
"- rule_id = rule.get(\"id\", \"dependency_check\")",
"-",
"- for forbidden in forbidden_imports:",
"- # PHP: use Statement",
"- php_pattern = rf\"use\\s+.*{re.escape(forbidden)}\"",
"- # Python: import Statement",
"- py_pattern = rf\"(from|import)\\s+.*{re.escape(forbidden)}\"",
"-",
"- try:",
"- if re.search(php_pattern, content) or re.search(py_pattern, content):",
"- self._add_violation(result, severity, {",
"- \"rule_id\": rule_id,",
"- \"factor\": \"forbidden_dependency\",",
"- \"file\": file_path,",
"- \"message\": f\"Forbidden import\/dependency: '{forbidden}'\",",
"- })",
"- except re.error:",
"- pass",
"-",
"- def _is_regex(self, pattern: str) -> bool:",
"- \"\"\"Prüft ob String ein Regex-Pattern ist\"\"\"",
"- regex_chars = ['[', ']', '(', ')', '\\\\', '^', '$', '.', '+', '?', '{', '}', '|']",
"- return any(c in pattern for c in regex_chars)",
"-",
"- def _add_violation(",
"- self, result: ContractValidationResult, severity: str, finding: Dict[str, Any]",
"- ) -> None:",
"- \"\"\"Fügt eine Violation zum Result hinzu\"\"\"",
"- finding[\"type\"] = severity",
"-",
"- if severity == \"critical\":",
"- result.critical += 1",
"- elif severity == \"major\":",
"- result.major += 1",
"- else:",
"- result.minor += 1",
"-",
"- result.findings.append(finding)",
"-",
"- def _check_forbidden_elements(",
"- self, file_path: str, content: str, result: ContractValidationResult",
"- ) -> None:",
"- \"\"\"Prüft auf verbotene Elemente\"\"\"",
"- forbidden = self.contract_data.get(\"contract\", {}).get(\"forbidden\", [])",
"-",
"- for item in forbidden:",
"- if isinstance(item, dict):",
"- element = item.get(\"element\", \"\")",
"- severity = item.get(\"severity\", \"minor\")",
"- message = item.get(\"message\", f\"Forbidden element found: {element}\")",
"- else:",
"- element = str(item)",
"- severity = \"minor\"",
"- message = f\"Forbidden element found: {element}\"",
"-",
"- if element and element in content:",
"- if severity == \"critical\":",
"- result.critical += 1",
"- elif severity == \"major\":",
"- result.major += 1",
"- else:",
"- result.minor += 1",
"-",
"- result.findings.append({",
"- \"type\": severity,",
"- \"factor\": \"forbidden_element\",",
"- \"file\": file_path,",
"- \"message\": message,",
"- })",
"-",
"- def _check_required_elements(",
"- self, file_path: str, content: str, result: ContractValidationResult",
"- ) -> None:",
"- \"\"\"Prüft auf erforderliche Elemente\"\"\"",
"- required = self.contract_data.get(\"contract\", {}).get(\"required\", [])",
"-",
"- for item in required:",
"- if isinstance(item, dict):",
"- element = item.get(\"element\", \"\")",
"- severity = item.get(\"severity\", \"major\")",
"- message = item.get(\"message\", f\"Required element missing: {element}\")",
"- applies_to = item.get(\"applies_to\", [])",
"- else:",
"- element = str(item)",
"- severity = \"major\"",
"- message = f\"Required element missing: {element}\"",
"- applies_to = []",
"-",
"- # applies_to prüfen (z.B. nur für bestimmte Dateiendungen)",
"- if applies_to:",
"- matches = False",
"- for pattern in applies_to:",
"- if file_path.endswith(pattern.lstrip(\"*\")):",
"- matches = True",
"- break",
"- if not matches:",
"- continue",
"-",
"- if element and element not in content:",
"- if severity == \"critical\":",
"- result.critical += 1",
"- elif severity == \"major\":",
"- result.major += 1",
"- else:",
"- result.minor += 1",
"-",
"- result.findings.append({",
"- \"type\": severity,",
"- \"factor\": \"required_element\",",
"- \"file\": file_path,",
"- \"message\": message,",
"- })",
"-",
"- def _determine_outcome(self, result: ContractValidationResult) -> str:",
"- \"\"\"Bestimmt das Validierungsergebnis basierend auf Violations\"\"\"",
"- if result.critical > 0:",
"- return \"rejected\"",
"- elif result.major > 2:",
"- return \"revision_required\"",
"- else:",
"- return \"passed\"",
"-",
"-",
" def run_contract_validation(",
"- contract_data: Dict[str, Any],",
"- target_path: Optional[str] = None",
"+ contract_data: dict[str, Any],",
"+ target_path: Optional[str] = None,",
" ) -> ContractValidationResult:",
"- \"\"\"Hilfsfunktion für Contract-Validierung\"\"\"",
"+ \"\"\"Hilfsfunktion fuer Contract-Validierung.\"\"\"",
" validator = ContractValidator(contract_data)",
" return validator.validate(target_path)"
]
}
],
"originalFile": "\"\"\"Contract Validator - YAML-Validierung und Scope-Prüfung\"\"\"\nimport os\nimport re\nimport sys\nimport glob as globlib\nfrom typing import Optional, Dict, Any, List\n\nimport yaml\n\nsys.path.insert(0, \"\/var\/www\/mcp-servers\/mcp_contracts\")\nfrom domain.contracts import ContractValidationResult\n\n\nclass ContractValidator:\n \"\"\"Validiert Contracts gegen ihren definierten Scope\"\"\"\n\n BASE_PATH = \"\/var\/www\/dev.campus.systemische-tools.de\"\n\n def __init__(self, contract_data: Dict[str, Any]):\n self.contract_data = contract_data\n self.contract_name = contract_data.get(\"contract\", {}).get(\"name\", \"unknown\")\n\n def validate(self, target_path: Optional[str] = None) -> ContractValidationResult:\n \"\"\"Führt vollständige Validierung durch\"\"\"\n result = ContractValidationResult(\n contract=self.contract_name,\n outcome=\"passed\",\n critical=0,\n major=0,\n minor=0,\n findings=[],\n )\n\n # Scope ermitteln\n check_paths = self._get_check_paths(target_path)\n\n if not check_paths:\n result.findings.append({\n \"type\": \"info\",\n \"message\": \"No paths to validate\",\n })\n return result\n\n # Dateien validieren\n for check_path in check_paths:\n if not os.path.exists(check_path):\n result.critical += 1\n result.findings.append({\n \"type\": \"critical\",\n \"factor\": \"path_existence\",\n \"message\": f\"Path does not exist: {check_path}\",\n })\n continue\n\n if os.path.isdir(check_path):\n self._validate_directory(check_path, result)\n else:\n self._validate_file(check_path, result)\n\n # Outcome bestimmen\n result.outcome = self._determine_outcome(result)\n\n return result\n\n def _get_check_paths(self, target_path: Optional[str] = None) -> List[str]:\n \"\"\"Ermittelt zu prüfende Pfade aus Scope (unterstützt alle Formate)\"\"\"\n if target_path:\n return [target_path]\n\n check_paths = []\n contract = self.contract_data.get(\"contract\", {})\n scope = contract.get(\"scope\", {})\n\n # Neues Standard-Format: scope.paths\n paths_list = scope.get(\"paths\", [])\n\n # Legacy-Formate als Fallback\n if not paths_list:\n paths_list = scope.get(\"includes\", []) # Legacy: scope.includes\n if not paths_list:\n paths_list = scope.get(\"applies_to_paths\", []) # Legacy\n if not paths_list:\n # Legacy: applicability.scope\n applicability = self.contract_data.get(\"applicability\", {})\n paths_list = applicability.get(\"scope\", [])\n if isinstance(paths_list, str):\n paths_list = [paths_list]\n\n excludes = scope.get(\"excludes\", [])\n\n for pattern in paths_list:\n # Glob-Pattern expandieren\n full_pattern = os.path.join(self.BASE_PATH, pattern.lstrip(\"\/\"))\n matched = globlib.glob(full_pattern, recursive=True)\n\n if matched:\n for path in matched:\n # Excludes prüfen\n excluded = False\n for excl in excludes:\n excl_pattern = os.path.join(self.BASE_PATH, excl.lstrip(\"\/\"))\n if globlib.fnmatch.fnmatch(path, excl_pattern):\n excluded = True\n break\n if not excluded:\n check_paths.append(path)\n else:\n # Falls kein Match, versuche Basisverzeichnis\n pattern_path = pattern.replace(\"**\", \"\").replace(\"*\", \"\").rstrip(\"\/\")\n full_path = os.path.join(self.BASE_PATH, pattern_path.lstrip(\"\/\"))\n if os.path.exists(full_path):\n check_paths.append(full_path)\n\n return list(set(check_paths)) # Duplikate entfernen\n\n def _validate_directory(self, dir_path: str, result: ContractValidationResult) -> None:\n \"\"\"Validiert alle Dateien in einem Verzeichnis\"\"\"\n for root, dirs, files in os.walk(dir_path):\n for f in files:\n if f.endswith((\".php\", \".js\", \".css\", \".py\")):\n file_path = os.path.join(root, f)\n self._validate_file(file_path, result)\n\n def _validate_file(self, file_path: str, result: ContractValidationResult) -> None:\n \"\"\"Validiert eine einzelne Datei gegen den Contract\"\"\"\n try:\n with open(file_path, \"r\", encoding=\"utf-8\") as f:\n content = f.read()\n\n # Neue Rules-basierte Validierung\n rules = self.contract_data.get(\"contract\", {}).get(\"rules\", [])\n for rule in rules:\n self._check_rule(file_path, content, rule, result)\n\n # Legacy: Forbidden elements prüfen\n self._check_forbidden_elements(file_path, content, result)\n\n # Legacy: Required elements prüfen\n self._check_required_elements(file_path, content, result)\n\n except Exception as e:\n result.minor += 1\n result.findings.append({\n \"type\": \"minor\",\n \"factor\": \"file_read_error\",\n \"file\": file_path,\n \"message\": str(e)[:100],\n })\n\n def _check_rule(\n self, file_path: str, content: str, rule: Dict[str, Any], result: ContractValidationResult\n ) -> None:\n \"\"\"Prüft eine einzelne Rule gegen eine Datei\"\"\"\n check_type = rule.get(\"check_type\", \"\")\n in_files = rule.get(\"in_files\", \"**\/*\")\n rule_id = rule.get(\"id\", \"unknown\")\n severity = rule.get(\"severity\", \"major\")\n description = rule.get(\"description\", \"\")\n\n # Prüfen ob Datei zum Rule-Pattern passt\n if not self._file_matches_pattern(file_path, in_files):\n return\n\n if check_type == \"line_count\":\n self._check_line_count(file_path, content, rule, result)\n elif check_type == \"forbidden_pattern\":\n self._check_forbidden_pattern(file_path, content, rule, result)\n elif check_type == \"required_pattern\":\n self._check_required_pattern(file_path, content, rule, result)\n elif check_type == \"dependency_check\":\n self._check_dependency(file_path, content, rule, result)\n\n def _file_matches_pattern(self, file_path: str, pattern: str) -> bool:\n \"\"\"Prüft ob Datei zum Glob-Pattern passt\"\"\"\n # Relative path from BASE_PATH\n rel_path = file_path.replace(self.BASE_PATH, \"\").lstrip(\"\/\")\n\n # Einfache Pattern-Prüfung\n if \"**\" in pattern:\n # z.B. Controller\/**\/*.php\n parts = pattern.split(\"**\")\n if len(parts) == 2:\n prefix = parts[0].rstrip(\"\/\")\n suffix = parts[1].lstrip(\"\/\")\n if prefix and not rel_path.startswith(prefix.lstrip(\"\/\")):\n return False\n if suffix and not globlib.fnmatch.fnmatch(rel_path, f\"*{suffix}\"):\n return False\n return True\n elif \"*\" in pattern:\n return globlib.fnmatch.fnmatch(rel_path, pattern)\n else:\n return pattern in rel_path\n\n return True\n\n def _check_line_count(\n self, file_path: str, content: str, rule: Dict[str, Any], result: ContractValidationResult\n ) -> None:\n \"\"\"Prüft Zeilenanzahl einer Datei\"\"\"\n max_lines = rule.get(\"max_lines\", 500)\n severity = rule.get(\"severity\", \"major\")\n rule_id = rule.get(\"id\", \"line_count\")\n\n line_count = len(content.splitlines())\n if line_count > max_lines:\n self._add_violation(result, severity, {\n \"rule_id\": rule_id,\n \"factor\": \"line_count\",\n \"file\": file_path,\n \"message\": f\"File has {line_count} lines (max: {max_lines})\",\n \"actual\": line_count,\n \"limit\": max_lines,\n })\n\n def _check_forbidden_pattern(\n self, file_path: str, content: str, rule: Dict[str, Any], result: ContractValidationResult\n ) -> None:\n \"\"\"Prüft auf verbotene Patterns\"\"\"\n patterns = rule.get(\"patterns\", [])\n severity = rule.get(\"severity\", \"major\")\n rule_id = rule.get(\"id\", \"forbidden_pattern\")\n description = rule.get(\"description\", \"\")\n\n for pattern in patterns:\n # Versuche als Regex, sonst als String\n try:\n matches = re.findall(pattern, content)\n except re.error:\n matches = [pattern] if pattern in content else []\n\n if matches:\n # Zeile finden\n line_no = None\n for i, line in enumerate(content.splitlines(), 1):\n if pattern in line or (re.search(pattern, line) if self._is_regex(pattern) else False):\n line_no = i\n break\n\n self._add_violation(result, severity, {\n \"rule_id\": rule_id,\n \"factor\": \"forbidden_pattern\",\n \"file\": file_path,\n \"message\": f\"Forbidden pattern found: '{pattern}'\",\n \"description\": description,\n \"line\": line_no,\n \"occurrences\": len(matches),\n })\n\n def _check_required_pattern(\n self, file_path: str, content: str, rule: Dict[str, Any], result: ContractValidationResult\n ) -> None:\n \"\"\"Prüft auf erforderliche Patterns\"\"\"\n patterns = rule.get(\"patterns\", [])\n severity = rule.get(\"severity\", \"major\")\n rule_id = rule.get(\"id\", \"required_pattern\")\n description = rule.get(\"description\", \"\")\n\n for pattern in patterns:\n try:\n found = bool(... [TRUNCATED-c25223228dfd8fdf]"
}
}