{
"event": "PostToolUse",
"tool_name": "TaskOutput",
"tool_response": {
"retrieval_status": "success",
"task": {
"task_id": "af20b74",
"task_type": "local_agent",
"status": "completed",
"description": "Refactor pre_rules.py split",
"output": "[Tool: Read] {\"file_path\":\"\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/pre_rules.py\"}\n[Tool: Read] {\"file_path\":\"\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/post_rules.py\"}\n[Tool: Glob] {\"pattern\":\"**\/post_rules_*.py\",\"path\":\"\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\"}\n[Tool: Glob] {\"pattern\":\"**\/post_rules_*.py\",\"path\":\"\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\"}\n[Tool: Bash] {\"command\":\"ls -la \/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/\",\"description\":\"List files in quality directory\"}\n[Tool: Bash] {\"command\":\"ls -la \/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/\",\"description\":\"List files in quality directory\"}\n[Tool: Read] {\"file_path\":\"\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/rule_base.py\"}\n[Tool: Read] {\"file_path\":\"\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/rule_base.py\"}\n[Tool: Write] {\"file_path\":\"\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/pre_rules_validation.py\",\"content\":\"#!\/usr\/bin\/env python3\\n\\\"\\\"\\\"\\nPre-Hook Validation Regeln (BLOCK) - PSR + Types.\\n\\nP3.x Regeln: strict_types, namespace, classname, return types\\n\\\"\\\"\\\"\\n\\nimport re\\nfrom pathlib import Path\\nfrom typing import Optional\\nfrom .rule_base import GLOBAL_ALLOWLIST, is_in_allowlist\\n\\n\\n# =============================================================================\\n# HELPER FUNCTIONS\\n# =============================================================================\\n\\ndef block(rule_id: str, message: str) -> dict:\\n \\\"\\\"\\\"Erzeugt Block-Response.\\\"\\\"\\\"\\n return {\\n \\\"allowed\\\": False,\\n \\\"message\\\": f\\\"QUALITY VIOLATION [{rule_id}]: {message}\\\"\\n }\\n\\n\\n# =============================================================================\\n# PRÜFUNG 3: PSR + Types\\n# =============================================================================\\n\\ndef p3_1_strict_types(file_path: str, content: str) -> Optional[dict]:\\n \\\"\\\"\\\"P3.1: strict_types erforderlich.\\\"\\\"\\\"\\n if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\\n return None\\n\\n strict_pattern = r\\\"declare\\\\s*\\\\(\\\\s*strict_types\\\\s*=\\\\s*1\\\\s*\\\\)\\\\s*;\\\"\\n if not re.search(strict_pattern, content):\\n return block(\\\"P3.1\\\", \\\"Missing declare(strict_types=1)\\\")\\n\\n return None\\n\\n\\ndef p3_2_namespace_matches_path(file_path: str, content: str) -> Optional[dict]:\\n \\\"\\\"\\\"P3.2: Namespace muss Pfad entsprechen.\\\"\\\"\\\"\\n if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\\n return None\\n\\n # Namespace-Mapping\\n path_to_namespace = {\\n \\\"\/src\/Controller\/\\\": \\\"Controller\\\\\\\\\\\",\\n \\\"\/src\/Domain\/\\\": \\\"Domain\\\\\\\\\\\",\\n \\\"\/src\/UseCases\/\\\": \\\"UseCases\\\\\\\\\\\",\\n \\\"\/src\/Application\/\\\": \\\"Application\\\\\\\\\\\",\\n \\\"\/src\/Infrastructure\/\\\": \\\"Infrastructure\\\\\\\\\\\",\\n \\\"\/src\/Framework\/\\\": \\\"Framework\\\\\\\\\\\",\\n \\\"\/app\/\\\": \\\"App\\\\\\\\\\\",\\n }\\n\\n namespace_match = re.search(r\\\"namespace\\\\s+([^;]+);\\\", content)\\n if not namespace_match:\\n return None # Kein Namespace = kein Check\\n\\n declared_namespace = namespace_match.group(1).strip()\\n\\n for path_prefix, ns_prefix in path_to_namespace.items():\\n if path_prefix in file_path:\\n if not declared_namespace.startswith(ns_prefix.rstrip(\\\"\\\\\\\\\\\")):\\n return block(\\\"P3.2\\\", f\\\"Namespace '{declared_namespace}' does not match path. Expected: '{ns_prefix}...'\\\")\\n break\\n\\n return None\\n\\n\\ndef p3_3_classname_matches_filename(file_path: str, content: str) -> Optional[dict]:\\n \\\"\\\"\\\"P3.3: Klassenname muss Dateiname entsprechen.\\\"\\\"\\\"\\n if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\\n return None\\n\\n class_match = re.search(r\\\"(?:class|interface|trait|enum)\\\\s+(\\\\w+)\\\", content)\\n if not class_match:\\n return None # Keine Klasse = kein Check\\n\\n class_name = class_match.group(1)\\n expected_name = Path(file_path).stem\\n\\n if class_name != expected_name:\\n return block(\\\"P3.3\\\", f\\\"Class '{class_name}' does not match filename '{expected_name}'\\\")\\n\\n return None\\n\\n\\ndef p3_4_public_method_return_type(file_path: str, content: str) -> Optional[dict]:\\n \\\"\\\"\\\"P3.4: Public Methods müssen Return-Type haben.\\\"\\\"\\\"\\n if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\\n return None\\n\\n # Finde public functions\\n public_methods = re.findall(\\n r\\\"public\\\\s+function\\\\s+(\\\\w+)\\\\s*\\\\([^)]*\\\\)\\\\s*(?::\\\\s*\\\\S+)?\\\",\\n content\\n )\\n\\n for method in public_methods:\\n if method.startswith(\\\"__\\\"):\\n continue # Magic methods überspringen\\n\\n # Prüfe ob Return-Type vorhanden\\n pattern = rf\\\"public\\\\s+function\\\\s+{method}\\\\s*\\\\([^)]*\\\\)\\\\s*:\\\\s*\\\\S+\\\"\\n if not re.search(pattern, content):\\n return block(\\\"P3.4\\\", f\\\"Public method '{method}' missing return type\\\")\\n\\n return None\\n\\n\\n# =============================================================================\\n# RULE COLLECTION\\n# =============================================================================\\n\\nRULES = [\\n p3_1_strict_types,\\n p3_2_namespace_matches_path,\\n p3_3_classname_matches_filename,\\n p3_4_public_method_return_type,\\n]\\n\"}\n[Tool: Write] {\"file_path\":\"\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/pre_rules_security.py\",\"content\":\"#!\/usr\/bin\/env python3\\n\\\"\\\"\\\"\\nPre-Hook Security Regeln (BLOCK) - MVC + Layer Violations + DIP.\\n\\nP2.x Regeln: MVC-Trennung, keine SQL in Controller\\nP6.x Regeln: SOLID + DIP Layer-Violations\\nH-Regeln: Legacy Architecture Guards\\n\\\"\\\"\\\"\\n\\nimport re\\nfrom typing import Optional\\nfrom .rule_base import GLOBAL_ALLOWLIST, is_in_allowlist\\n\\n\\n# =============================================================================\\n# ALLOWLIST\\n# =============================================================================\\n\\nFACTORY_ALLOWLIST = [\\n \\\"\/Factory\/\\\",\\n \\\"\/Bootstrap\/\\\",\\n]\\n\\n\\n# =============================================================================\\n# HELPER FUNCTIONS\\n# =============================================================================\\n\\ndef block(rule_id: str, message: str) -> dict:\\n \\\"\\\"\\\"Erzeugt Block-Response.\\\"\\\"\\\"\\n return {\\n \\\"allowed\\\": False,\\n \\\"message\\\": f\\\"QUALITY VIOLATION [{rule_id}]: {message}\\\"\\n }\\n\\n\\n# =============================================================================\\n# PRÜFUNG 2: MVC + CRUD\\n# =============================================================================\\n\\ndef p2_1_no_sql_in_controller(file_path: str, content: str) -> Optional[dict]:\\n \\\"\\\"\\\"P2.1: Keine SQL-Statements in Controller.\\\"\\\"\\\"\\n if \\\"\/Controller\/\\\" not in file_path:\\n return None\\n if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\\n return None\\n\\n sql_patterns = [\\n r\\\"\\\\bSELECT\\\\s+.+\\\\s+FROM\\\\b\\\",\\n r\\\"\\\\bINSERT\\\\s+INTO\\\\b\\\",\\n r\\\"\\\\bUPDATE\\\\s+\\\\w+\\\\s+SET\\\\b\\\",\\n r\\\"\\\\bDELETE\\\\s+FROM\\\\b\\\",\\n ]\\n\\n for pattern in sql_patterns:\\n if re.search(pattern, content, re.IGNORECASE):\\n return block(\\\"P2.1\\\", \\\"SQL statement in Controller. Move to Repository.\\\")\\n\\n return None\\n\\n\\ndef p2_2_no_transactions_in_controller(file_path: str, content: str) -> Optional[dict]:\\n \\\"\\\"\\\"P2.2: Keine Transaktionen in Controller.\\\"\\\"\\\"\\n if \\\"\/Controller\/\\\" not in file_path:\\n return None\\n if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\\n return None\\n\\n transaction_patterns = [\\n r\\\"\\\\bbeginTransaction\\\\s*\\\\(\\\",\\n r\\\"\\\\bcommit\\\\s*\\\\(\\\",\\n r\\\"\\\\brollBack\\\\s*\\\\(\\\",\\n r\\\"\\\\brollback\\\\s*\\\\(\\\",\\n ]\\n\\n for pattern in transaction_patterns:\\n if re.search(pattern, content):\\n return block(\\\"P2.2\\\", \\\"Transaction control in Controller. Move to UseCase or Repository.\\\")\\n\\n return None\\n\\n\\ndef p2_3_no_echo_in_controller(file_path: str, content: str) -> Optional[dict]:\\n \\\"\\\"\\\"P2.3: Kein echo\/print in Controller.\\\"\\\"\\\"\\n if \\\"\/Controller\/\\\" not in file_path:\\n return None\\n if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\\n return None\\n\\n output_patterns = [\\n r\\\"\\\\becho\\\\s+\\\",\\n r\\\"\\\\becho\\\\(\\\",\\n r\\\"\\\\bprint\\\\s+\\\",\\n r\\\"\\\\bprint\\\\(\\\",\\n ]\\n\\n for pattern in output_patterns:\\n if re.search(pattern, content):\\n return block(\\\"P2.3\\\", \\\"Direct output (echo\/print) in Controller. Use Response object.\\\")\\n\\n return None\\n\\n\\ndef p2_4_no_db_in_usecases(file_path: str, content: str) -> Optional[dict]:\\n \\\"\\\"\\\"P2.4: Keine DB-Artefakte in UseCases.\\\"\\\"\\\"\\n if \\\"\/UseCases\/\\\" not in file_path and \\\"\/Application\/\\\" not in file_path:\\n return None\\n if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\\n return None\\n\\n db_patterns = [\\n r\\\"\\\\bnew\\\\s+PDO\\\\b\\\",\\n r\\\"\\\\bPDO::\\\",\\n r\\\"\\\\bDatabaseFactory::\\\",\\n r\\\"\\\\bSELECT\\\\s+.+\\\\s+FROM\\\\b\\\",\\n r\\\"\\\\bINSERT\\\\s+INTO\\\\b\\\",\\n r\\\"\\\\bUPDATE\\\\s+\\\\w+\\\\s+SET\\\\b\\\",\\n r\\\"\\\\bDELETE\\\\s+FROM\\\\b\\\",\\n ]\\n\\n for pattern in db_patterns:\\n if re.search(pattern, content, re.IGNORECASE):\\n return block(\\\"P2.4\\\", \\\"DB artifact in UseCase\/Application. Use Repository interface.\\\")\\n\\n return None\\n\\n\\n# =============================================================================\\n# PRÜFUNG 6: SOLID + DIP\\n# =============================================================================\\n\\ndef p6_1_application_no_infrastructure_import(file_path: str, content: str) -> Optional[dict]:\\n \\\"\\\"\\\"P6.1: UseCases\/Application darf Infrastructure nicht importieren.\\\"\\\"\\\"\\n if \\\"\/UseCases\/\\\" not in file_path and \\\"\/Application\/\\\" not in file_path:\\n return None\\n if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\\n return None\\n\\n if re.search(r\\\"use\\\\s+Infrastructure\\\\\\\\\\\", content):\\n return block(\\\"P6.1\\\", \\\"Application layer must not import Infrastructure directly (DIP)\\\")\\n\\n return None\\n\\n\\ndef p6_2_domain_no_application... [TRUNCATED-f2d1f86a941fafe4]",
"prompt": "Lese \/var\/www\/CLAUDE.md für Projektkontext.\n\n**Task #362: pre_rules.py - Regelmodule trennen**\n\n1. Analysiere `\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/pre_rules.py` (438 LOC)\n2. Strukturiere analog zu post_rules.py:\n - `pre_rules_security.py`\n - `pre_rules_validation.py`\n - `pre_rules_guard.py`\n3. Teile gemeinsame Basisklasse mit post_rules\n4. Führe `\/var\/www\/scripts\/python-check.sh` aus\n\nMax 150 LOC pro Modul. Konsistent mit post_rules Struktur.",
"result": "[Tool: Read] {\"file_path\":\"\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/pre_rules.py\"}\n[Tool: Read] {\"file_path\":\"\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/post_rules.py\"}\n[Tool: Glob] {\"pattern\":\"**\/post_rules_*.py\",\"path\":\"\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\"}\n[Tool: Glob] {\"pattern\":\"**\/post_rules_*.py\",\"path\":\"\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\"}\n[Tool: Bash] {\"command\":\"ls -la \/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/\",\"description\":\"List files in quality directory\"}\n[Tool: Bash] {\"command\":\"ls -la \/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/\",\"description\":\"List files in quality directory\"}\n[Tool: Read] {\"file_path\":\"\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/rule_base.py\"}\n[Tool: Read] {\"file_path\":\"\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/rule_base.py\"}\n[Tool: Write] {\"file_path\":\"\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/pre_rules_validation.py\",\"content\":\"#!\/usr\/bin\/env python3\\n\\\"\\\"\\\"\\nPre-Hook Validation Regeln (BLOCK) - PSR + Types.\\n\\nP3.x Regeln: strict_types, namespace, classname, return types\\n\\\"\\\"\\\"\\n\\nimport re\\nfrom pathlib import Path\\nfrom typing import Optional\\nfrom .rule_base import GLOBAL_ALLOWLIST, is_in_allowlist\\n\\n\\n# =============================================================================\\n# HELPER FUNCTIONS\\n# =============================================================================\\n\\ndef block(rule_id: str, message: str) -> dict:\\n \\\"\\\"\\\"Erzeugt Block-Response.\\\"\\\"\\\"\\n return {\\n \\\"allowed\\\": False,\\n \\\"message\\\": f\\\"QUALITY VIOLATION [{rule_id}]: {message}\\\"\\n }\\n\\n\\n# =============================================================================\\n# PRÜFUNG 3: PSR + Types\\n# =============================================================================\\n\\ndef p3_1_strict_types(file_path: str, content: str) -> Optional[dict]:\\n \\\"\\\"\\\"P3.1: strict_types erforderlich.\\\"\\\"\\\"\\n if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\\n return None\\n\\n strict_pattern = r\\\"declare\\\\s*\\\\(\\\\s*strict_types\\\\s*=\\\\s*1\\\\s*\\\\)\\\\s*;\\\"\\n if not re.search(strict_pattern, content):\\n return block(\\\"P3.1\\\", \\\"Missing declare(strict_types=1)\\\")\\n\\n return None\\n\\n\\ndef p3_2_namespace_matches_path(file_path: str, content: str) -> Optional[dict]:\\n \\\"\\\"\\\"P3.2: Namespace muss Pfad entsprechen.\\\"\\\"\\\"\\n if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\\n return None\\n\\n # Namespace-Mapping\\n path_to_namespace = {\\n \\\"\/src\/Controller\/\\\": \\\"Controller\\\\\\\\\\\",\\n \\\"\/src\/Domain\/\\\": \\\"Domain\\\\\\\\\\\",\\n \\\"\/src\/UseCases\/\\\": \\\"UseCases\\\\\\\\\\\",\\n \\\"\/src\/Application\/\\\": \\\"Application\\\\\\\\\\\",\\n \\\"\/src\/Infrastructure\/\\\": \\\"Infrastructure\\\\\\\\\\\",\\n \\\"\/src\/Framework\/\\\": \\\"Framework\\\\\\\\\\\",\\n \\\"\/app\/\\\": \\\"App\\\\\\\\\\\",\\n }\\n\\n namespace_match = re.search(r\\\"namespace\\\\s+([^;]+);\\\", content)\\n if not namespace_match:\\n return None # Kein Namespace = kein Check\\n\\n declared_namespace = namespace_match.group(1).strip()\\n\\n for path_prefix, ns_prefix in path_to_namespace.items():\\n if path_prefix in file_path:\\n if not declared_namespace.startswith(ns_prefix.rstrip(\\\"\\\\\\\\\\\")):\\n return block(\\\"P3.2\\\", f\\\"Namespace '{declared_namespace}' does not match path. Expected: '{ns_prefix}...'\\\")\\n break\\n\\n return None\\n\\n\\ndef p3_3_classname_matches_filename(file_path: str, content: str) -> Optional[dict]:\\n \\\"\\\"\\\"P3.3: Klassenname muss Dateiname entsprechen.\\\"\\\"\\\"\\n if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\\n return None\\n\\n class_match = re.search(r\\\"(?:class|interface|trait|enum)\\\\s+(\\\\w+)\\\", content)\\n if not class_match:\\n return None # Keine Klasse = kein Check\\n\\n class_name = class_match.group(1)\\n expected_name = Path(file_path).stem\\n\\n if class_name != expected_name:\\n return block(\\\"P3.3\\\", f\\\"Class '{class_name}' does not match filename '{expected_name}'\\\")\\n\\n return None\\n\\n\\ndef p3_4_public_method_return_type(file_path: str, content: str) -> Optional[dict]:\\n \\\"\\\"\\\"P3.4: Public Methods müssen Return-Type haben.\\\"\\\"\\\"\\n if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\\n return None\\n\\n # Finde public functions\\n public_methods = re.findall(\\n r\\\"public\\\\s+function\\\\s+(\\\\w+)\\\\s*\\\\([^)]*\\\\)\\\\s*(?::\\\\s*\\\\S+)?\\\",\\n content\\n )\\n\\n for method in public_methods:\\n if method.startswith(\\\"__\\\"):\\n continue # Magic methods überspringen\\n\\n # Prüfe ob Return-Type vorhanden\\n pattern = rf\\\"public\\\\s+function\\\\s+{method}\\\\s*\\\\([^)]*\\\\)\\\\s*:\\\\s*\\\\S+\\\"\\n if not re.search(pattern, content):\\n return block(\\\"P3.4\\\", f\\\"Public method '{method}' missing return type\\\")\\n\\n return None\\n\\n\\n# =============================================================================\\n# RULE COLLECTION\\n# =============================================================================\\n\\nRULES = [\\n p3_1_strict_types,\\n p3_2_namespace_matches_path,\\n p3_3_classname_matches_filename,\\n p3_4_public_method_return_type,\\n]\\n\"}\n[Tool: Write] {\"file_path\":\"\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/pre_rules_security.py\",\"content\":\"#!\/usr\/bin\/env python3\\n\\\"\\\"\\\"\\nPre-Hook Security Regeln (BLOCK) - MVC + Layer Violations + DIP.\\n\\nP2.x Regeln: MVC-Trennung, keine SQL in Controller\\nP6.x Regeln: SOLID + DIP Layer-Violations\\nH-Regeln: Legacy Architecture Guards\\n\\\"\\\"\\\"\\n\\nimport re\\nfrom typing import Optional\\nfrom .rule_base import GLOBAL_ALLOWLIST, is_in_allowlist\\n\\n\\n# =============================================================================\\n# ALLOWLIST\\n# =============================================================================\\n\\nFACTORY_ALLOWLIST = [\\n \\\"\/Factory\/\\\",\\n \\\"\/Bootstrap\/\\\",\\n]\\n\\n\\n# =============================================================================\\n# HELPER FUNCTIONS\\n# =============================================================================\\n\\ndef block(rule_id: str, message: str) -> dict:\\n \\\"\\\"\\\"Erzeugt Block-Response.\\\"\\\"\\\"\\n return {\\n \\\"allowed\\\": False,\\n \\\"message\\\": f\\\"QUALITY VIOLATION [{rule_id}]: {message}\\\"\\n }\\n\\n\\n# =============================================================================\\n# PRÜFUNG 2: MVC + CRUD\\n# =============================================================================\\n\\ndef p2_1_no_sql_in_controller(file_path: str, content: str) -> Optional[dict]:\\n \\\"\\\"\\\"P2.1: Keine SQL-Statements in Controller.\\\"\\\"\\\"\\n if \\\"\/Controller\/\\\" not in file_path:\\n return None\\n if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\\n return None\\n\\n sql_patterns = [\\n r\\\"\\\\bSELECT\\\\s+.+\\\\s+FROM\\\\b\\\",\\n r\\\"\\\\bINSERT\\\\s+INTO\\\\b\\\",\\n r\\\"\\\\bUPDATE\\\\s+\\\\w+\\\\s+SET\\\\b\\\",\\n r\\\"\\\\bDELETE\\\\s+FROM\\\\b\\\",\\n ]\\n\\n for pattern in sql_patterns:\\n if re.search(pattern, content, re.IGNORECASE):\\n return block(\\\"P2.1\\\", \\\"SQL statement in Controller. Move to Repository.\\\")\\n\\n return None\\n\\n\\ndef p2_2_no_transactions_in_controller(file_path: str, content: str) -> Optional[dict]:\\n \\\"\\\"\\\"P2.2: Keine Transaktionen in Controller.\\\"\\\"\\\"\\n if \\\"\/Controller\/\\\" not in file_path:\\n return None\\n if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\\n return None\\n\\n transaction_patterns = [\\n r\\\"\\\\bbeginTransaction\\\\s*\\\\(\\\",\\n r\\\"\\\\bcommit\\\\s*\\\\(\\\",\\n r\\\"\\\\brollBack\\\\s*\\\\(\\\",\\n r\\\"\\\\brollback\\\\s*\\\\(\\\",\\n ]\\n\\n for pattern in transaction_patterns:\\n if re.search(pattern, content):\\n return block(\\\"P2.2\\\", \\\"Transaction control in Controller. Move to UseCase or Repository.\\\")\\n\\n return None\\n\\n\\ndef p2_3_no_echo_in_controller(file_path: str, content: str) -> Optional[dict]:\\n \\\"\\\"\\\"P2.3: Kein echo\/print in Controller.\\\"\\\"\\\"\\n if \\\"\/Controller\/\\\" not in file_path:\\n return None\\n if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\\n return None\\n\\n output_patterns = [\\n r\\\"\\\\becho\\\\s+\\\",\\n r\\\"\\\\becho\\\\(\\\",\\n r\\\"\\\\bprint\\\\s+\\\",\\n r\\\"\\\\bprint\\\\(\\\",\\n ]\\n\\n for pattern in output_patterns:\\n if re.search(pattern, content):\\n return block(\\\"P2.3\\\", \\\"Direct output (echo\/print) in Controller. Use Response object.\\\")\\n\\n return None\\n\\n\\ndef p2_4_no_db_in_usecases(file_path: str, content: str) -> Optional[dict]:\\n \\\"\\\"\\\"P2.4: Keine DB-Artefakte in UseCases.\\\"\\\"\\\"\\n if \\\"\/UseCases\/\\\" not in file_path and \\\"\/Application\/\\\" not in file_path:\\n return None\\n if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\\n return None\\n\\n db_patterns = [\\n r\\\"\\\\bnew\\\\s+PDO\\\\b\\\",\\n r\\\"\\\\bPDO::\\\",\\n r\\\"\\\\bDatabaseFactory::\\\",\\n r\\\"\\\\bSELECT\\\\s+.+\\\\s+FROM\\\\b\\\",\\n r\\\"\\\\bINSERT\\\\s+INTO\\\\b\\\",\\n r\\\"\\\\bUPDATE\\\\s+\\\\w+\\\\s+SET\\\\b\\\",\\n r\\\"\\\\bDELETE\\\\s+FROM\\\\b\\\",\\n ]\\n\\n for pattern in db_patterns:\\n if re.search(pattern, content, re.IGNORECASE):\\n return block(\\\"P2.4\\\", \\\"DB artifact in UseCase\/Application. Use Repository interface.\\\")\\n\\n return None\\n\\n\\n# =============================================================================\\n# PRÜFUNG 6: SOLID + DIP\\n# =============================================================================\\n\\ndef p6_1_application_no_infrastructure_import(file_path: str, content: str) -> Optional[dict]:\\n \\\"\\\"\\\"P6.1: UseCases\/Application darf Infrastructure nicht importieren.\\\"\\\"\\\"\\n if \\\"\/UseCases\/\\\" not in file_path and \\\"\/Application\/\\\" not in file_path:\\n return None\\n if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\\n return None\\n\\n if re.search(r\\\"use\\\\s+Infrastructure\\\\\\\\\\\", content):\\n return block(\\\"P6.1\\\", \\\"Application layer must not import Infrastructure directly (DIP)\\\")\\n\\n return None\\n\\n\\ndef p6_2_domain_no_application... [TRUNCATED-f2d1f86a941fafe4]"
}
}
}