Protokoll #15905

ID15905
Zeitstempel2025-12-25 16:57:50.442431
Clientroot
IP145.224.72.140
Modellclaude-sonnet-4-20250514
Statuscompleted
Tokens12,754 (Input: 2,993, Output: 9,761)
Dauer125 ms
Request-Zeit2025-12-25 16:57:50.442431
Response-Zeit2025-12-25 16:57:50.567314

Request

{
    "event": "PreToolUse",
    "tool_name": "Edit",
    "tool_input": {
        "file_path": "\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/pre_rules.py",
        "old_string": "#!\/usr\/bin\/env python3\n\"\"\"\nPre-Hook Regeln (BLOCK) - Beliebig erweiterbar ohne Claude-Neustart.\n\nAlle P-Regeln aus dem Quality Gate Plan.\n\"\"\"\n\nimport re\nfrom pathlib import Path\nfrom typing import Optional\n\n# =============================================================================\n# ALLOWLIST - Pfade die von allen Prüfungen ausgenommen sind\n# =============================================================================\n\nGLOBAL_ALLOWLIST = [\n    \"\/vendor\/\",\n    \"\/tests\/\",\n    \"\/Test\/\",\n]\n\nFACTORY_ALLOWLIST = [\n    \"\/Factory\/\",\n    \"\/Bootstrap\/\",\n]\n\n# =============================================================================\n# COMMON NUMBERS - Erlaubte Magic Numbers (für W5.2, hier zur Referenz)\n# =============================================================================\n\nCOMMON_NUMBERS = {\n    '0', '1', '2',\n    '10', '100', '1000',\n    '60', '24', '365',\n    '30', '31', '28', '29',\n    '12', '52', '7',\n    '200', '201', '204',\n    '301', '302', '304',\n    '400', '401', '403', '404', '500',\n}\n\n# =============================================================================\n# HELPER FUNCTIONS\n# =============================================================================\n\ndef is_in_allowlist(file_path: str, allowlist: list) -> bool:\n    \"\"\"Prüft ob Pfad in Allowlist ist.\"\"\"\n    return any(allowed in file_path for allowed in allowlist)\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\ndef allow() -> dict:\n    \"\"\"Erzeugt Allow-Response.\"\"\"\n    return {\"allowed\": True}\n\n\n# =============================================================================\n# PRÜFUNG 1: SRP + KISS\n# =============================================================================\n\ndef p1_1_responsibility_header(file_path: str, content: str) -> Optional[dict]:\n    \"\"\"P1.1: @responsibility Header erforderlich.\"\"\"\n    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\n        return None\n\n    header_pattern = r\"\/\/\\s*@responsibility:\\s*(.+)\"\n    match = re.search(header_pattern, content)\n\n    if not match:\n        return block(\"P1.1\", \"Missing @responsibility header. Add: \/\/ @responsibility: <single-responsibility>\")\n\n    responsibility_text = match.group(1).strip()\n\n    # Prüfe auf Multi-Responsibility-Wörter\n    multi_words = r\"\\b(und|sowie|außerdem|also|zusätzlich|and|also|additionally)\\b\"\n    if re.search(multi_words, responsibility_text, re.IGNORECASE):\n        return block(\"P1.1\", f\"@responsibility contains multi-responsibility indicator: '{responsibility_text}'\")\n\n    return None\n\n\ndef p1_2_garbage_names(file_path: str, content: str) -> Optional[dict]:\n    \"\"\"P1.2: Müllhalden-Namen blockieren.\"\"\"\n    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\n        return None\n\n    filename = Path(file_path).stem.lower()\n    forbidden = [\"helper\", \"utils\", \"common\", \"misc\", \"base\"]\n\n    for term in forbidden:\n        if term in filename:\n            return block(\"P1.2\", f\"Forbidden name pattern: '{term}' indicates unclear responsibility\")\n\n    return None\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 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# PRÜFUNG 4: OOP\n# =============================================================================\n\ndef p4_1_no_public_properties_in_domain(file_path: str, content: str) -> Optional[dict]:\n    \"\"\"P4.1: Keine public Properties in Domain.\"\"\"\n    if \"\/Domain\/\" not in file_path:\n        return None\n    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\n        return None\n\n    # public readonly ist erlaubt\n    public_props = re.findall(\n        r\"public\\s+(?!readonly|function|const|static\\s+function).*\\$\\w+\",\n        content\n    )\n\n    if public_props:\n        return block(\"P4.1\", \"Public property in Domain. Use getter methods or public readonly.\")\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_pat... [TRUNCATED-a52e2f1d0fd5bcc2]",
        "new_string": "#!\/usr\/bin\/env python3\n\"\"\"\nPre-Hook Regeln (BLOCK) - Modular und erweiterbar ohne Claude-Neustart.\n\nAlle P-Regeln aus dem Quality Gate Plan.\nImportiert Regeln aus spezialisierten Modulen.\n\"\"\"\n\nfrom . import pre_rules_guard\nfrom . import pre_rules_security\nfrom . import pre_rules_validation\n\n\n# =============================================================================\n# HELPER FUNCTIONS\n# =============================================================================\n\ndef allow() -> dict:\n    \"\"\"Erzeugt Allow-Response.\"\"\"\n    return {\"allowed\": True}\n\n\n# =============================================================================\n# MAIN CHECK FUNCTION\n# =============================================================================\n\n# Alle aktiven Regeln in Prüfungsreihenfolge\nRULES = [\n    # Prüfung 1: SRP + KISS + Prüfung 4: OOP\n    *pre_rules_guard.RULES,\n\n    # Prüfung 2: MVC + CRUD + Prüfung 6: SOLID + DIP + H-Regeln\n    *pre_rules_security.RULES,\n\n    # Prüfung 3: PSR + Types\n    *pre_rules_validation.RULES,\n]"
    }
}

Response

{
    "tool_response": {
        "filePath": "\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/pre_rules.py",
        "oldString": "#!\/usr\/bin\/env python3\n\"\"\"\nPre-Hook Regeln (BLOCK) - Beliebig erweiterbar ohne Claude-Neustart.\n\nAlle P-Regeln aus dem Quality Gate Plan.\n\"\"\"\n\nimport re\nfrom pathlib import Path\nfrom typing import Optional\n\n# =============================================================================\n# ALLOWLIST - Pfade die von allen Prüfungen ausgenommen sind\n# =============================================================================\n\nGLOBAL_ALLOWLIST = [\n    \"\/vendor\/\",\n    \"\/tests\/\",\n    \"\/Test\/\",\n]\n\nFACTORY_ALLOWLIST = [\n    \"\/Factory\/\",\n    \"\/Bootstrap\/\",\n]\n\n# =============================================================================\n# COMMON NUMBERS - Erlaubte Magic Numbers (für W5.2, hier zur Referenz)\n# =============================================================================\n\nCOMMON_NUMBERS = {\n    '0', '1', '2',\n    '10', '100', '1000',\n    '60', '24', '365',\n    '30', '31', '28', '29',\n    '12', '52', '7',\n    '200', '201', '204',\n    '301', '302', '304',\n    '400', '401', '403', '404', '500',\n}\n\n# =============================================================================\n# HELPER FUNCTIONS\n# =============================================================================\n\ndef is_in_allowlist(file_path: str, allowlist: list) -> bool:\n    \"\"\"Prüft ob Pfad in Allowlist ist.\"\"\"\n    return any(allowed in file_path for allowed in allowlist)\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\ndef allow() -> dict:\n    \"\"\"Erzeugt Allow-Response.\"\"\"\n    return {\"allowed\": True}\n\n\n# =============================================================================\n# PRÜFUNG 1: SRP + KISS\n# =============================================================================\n\ndef p1_1_responsibility_header(file_path: str, content: str) -> Optional[dict]:\n    \"\"\"P1.1: @responsibility Header erforderlich.\"\"\"\n    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\n        return None\n\n    header_pattern = r\"\/\/\\s*@responsibility:\\s*(.+)\"\n    match = re.search(header_pattern, content)\n\n    if not match:\n        return block(\"P1.1\", \"Missing @responsibility header. Add: \/\/ @responsibility: <single-responsibility>\")\n\n    responsibility_text = match.group(1).strip()\n\n    # Prüfe auf Multi-Responsibility-Wörter\n    multi_words = r\"\\b(und|sowie|außerdem|also|zusätzlich|and|also|additionally)\\b\"\n    if re.search(multi_words, responsibility_text, re.IGNORECASE):\n        return block(\"P1.1\", f\"@responsibility contains multi-responsibility indicator: '{responsibility_text}'\")\n\n    return None\n\n\ndef p1_2_garbage_names(file_path: str, content: str) -> Optional[dict]:\n    \"\"\"P1.2: Müllhalden-Namen blockieren.\"\"\"\n    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\n        return None\n\n    filename = Path(file_path).stem.lower()\n    forbidden = [\"helper\", \"utils\", \"common\", \"misc\", \"base\"]\n\n    for term in forbidden:\n        if term in filename:\n            return block(\"P1.2\", f\"Forbidden name pattern: '{term}' indicates unclear responsibility\")\n\n    return None\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 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# PRÜFUNG 4: OOP\n# =============================================================================\n\ndef p4_1_no_public_properties_in_domain(file_path: str, content: str) -> Optional[dict]:\n    \"\"\"P4.1: Keine public Properties in Domain.\"\"\"\n    if \"\/Domain\/\" not in file_path:\n        return None\n    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\n        return None\n\n    # public readonly ist erlaubt\n    public_props = re.findall(\n        r\"public\\s+(?!readonly|function|const|static\\s+function).*\\$\\w+\",\n        content\n    )\n\n    if public_props:\n        return block(\"P4.1\", \"Public property in Domain. Use getter methods or public readonly.\")\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_pat... [TRUNCATED-a52e2f1d0fd5bcc2]",
        "newString": "#!\/usr\/bin\/env python3\n\"\"\"\nPre-Hook Regeln (BLOCK) - Modular und erweiterbar ohne Claude-Neustart.\n\nAlle P-Regeln aus dem Quality Gate Plan.\nImportiert Regeln aus spezialisierten Modulen.\n\"\"\"\n\nfrom . import pre_rules_guard\nfrom . import pre_rules_security\nfrom . import pre_rules_validation\n\n\n# =============================================================================\n# HELPER FUNCTIONS\n# =============================================================================\n\ndef allow() -> dict:\n    \"\"\"Erzeugt Allow-Response.\"\"\"\n    return {\"allowed\": True}\n\n\n# =============================================================================\n# MAIN CHECK FUNCTION\n# =============================================================================\n\n# Alle aktiven Regeln in Prüfungsreihenfolge\nRULES = [\n    # Prüfung 1: SRP + KISS + Prüfung 4: OOP\n    *pre_rules_guard.RULES,\n\n    # Prüfung 2: MVC + CRUD + Prüfung 6: SOLID + DIP + H-Regeln\n    *pre_rules_security.RULES,\n\n    # Prüfung 3: PSR + Types\n    *pre_rules_validation.RULES,\n]",
        "originalFile": "#!\/usr\/bin\/env python3\n\"\"\"\nPre-Hook Regeln (BLOCK) - Beliebig erweiterbar ohne Claude-Neustart.\n\nAlle P-Regeln aus dem Quality Gate Plan.\n\"\"\"\n\nimport re\nfrom pathlib import Path\nfrom typing import Optional\n\n# =============================================================================\n# ALLOWLIST - Pfade die von allen Prüfungen ausgenommen sind\n# =============================================================================\n\nGLOBAL_ALLOWLIST = [\n    \"\/vendor\/\",\n    \"\/tests\/\",\n    \"\/Test\/\",\n]\n\nFACTORY_ALLOWLIST = [\n    \"\/Factory\/\",\n    \"\/Bootstrap\/\",\n]\n\n# =============================================================================\n# COMMON NUMBERS - Erlaubte Magic Numbers (für W5.2, hier zur Referenz)\n# =============================================================================\n\nCOMMON_NUMBERS = {\n    '0', '1', '2',\n    '10', '100', '1000',\n    '60', '24', '365',\n    '30', '31', '28', '29',\n    '12', '52', '7',\n    '200', '201', '204',\n    '301', '302', '304',\n    '400', '401', '403', '404', '500',\n}\n\n# =============================================================================\n# HELPER FUNCTIONS\n# =============================================================================\n\ndef is_in_allowlist(file_path: str, allowlist: list) -> bool:\n    \"\"\"Prüft ob Pfad in Allowlist ist.\"\"\"\n    return any(allowed in file_path for allowed in allowlist)\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\ndef allow() -> dict:\n    \"\"\"Erzeugt Allow-Response.\"\"\"\n    return {\"allowed\": True}\n\n\n# =============================================================================\n# PRÜFUNG 1: SRP + KISS\n# =============================================================================\n\ndef p1_1_responsibility_header(file_path: str, content: str) -> Optional[dict]:\n    \"\"\"P1.1: @responsibility Header erforderlich.\"\"\"\n    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\n        return None\n\n    header_pattern = r\"\/\/\\s*@responsibility:\\s*(.+)\"\n    match = re.search(header_pattern, content)\n\n    if not match:\n        return block(\"P1.1\", \"Missing @responsibility header. Add: \/\/ @responsibility: <single-responsibility>\")\n\n    responsibility_text = match.group(1).strip()\n\n    # Prüfe auf Multi-Responsibility-Wörter\n    multi_words = r\"\\b(und|sowie|außerdem|also|zusätzlich|and|also|additionally)\\b\"\n    if re.search(multi_words, responsibility_text, re.IGNORECASE):\n        return block(\"P1.1\", f\"@responsibility contains multi-responsibility indicator: '{responsibility_text}'\")\n\n    return None\n\n\ndef p1_2_garbage_names(file_path: str, content: str) -> Optional[dict]:\n    \"\"\"P1.2: Müllhalden-Namen blockieren.\"\"\"\n    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\n        return None\n\n    filename = Path(file_path).stem.lower()\n    forbidden = [\"helper\", \"utils\", \"common\", \"misc\", \"base\"]\n\n    for term in forbidden:\n        if term in filename:\n            return block(\"P1.2\", f\"Forbidden name pattern: '{term}' indicates unclear responsibility\")\n\n    return None\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 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# PRÜFUNG 4: OOP\n# =============================================================================\n\ndef p4_1_no_public_properties_in_domain(file_path: str, content: str) -> Optional[dict]:\n    \"\"\"P4.1: Keine public Properties in Domain.\"\"\"\n    if \"\/Domain\/\" not in file_path:\n        return None\n    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\n        return None\n\n    # public readonly ist erlaubt\n    public_props = re.findall(\n        r\"public\\s+(?!readonly|function|const|static\\s+function).*\\$\\w+\",\n        content\n    )\n\n    if public_props:\n        return block(\"P4.1\", \"Public property in Domain. Use getter methods or public readonly.\")\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_pat... [TRUNCATED-8b8fe0235d9fc756]",
        "structuredPatch": [
            {
                "oldStart": 1,
                "oldLines": 426,
                "newStart": 1,
                "newLines": 39,
                "lines": [
                    " #!\/usr\/bin\/env python3",
                    " \"\"\"",
                    "-Pre-Hook Regeln (BLOCK) - Beliebig erweiterbar ohne Claude-Neustart.",
                    "+Pre-Hook Regeln (BLOCK) - Modular und erweiterbar ohne Claude-Neustart.",
                    " ",
                    " Alle P-Regeln aus dem Quality Gate Plan.",
                    "+Importiert Regeln aus spezialisierten Modulen.",
                    " \"\"\"",
                    " ",
                    "-import re",
                    "-from pathlib import Path",
                    "-from typing import Optional",
                    "+from . import pre_rules_guard",
                    "+from . import pre_rules_security",
                    "+from . import pre_rules_validation",
                    " ",
                    "-# =============================================================================",
                    "-# ALLOWLIST - Pfade die von allen Prüfungen ausgenommen sind",
                    "-# =============================================================================",
                    " ",
                    "-GLOBAL_ALLOWLIST = [",
                    "-    \"\/vendor\/\",",
                    "-    \"\/tests\/\",",
                    "-    \"\/Test\/\",",
                    "-]",
                    "-",
                    "-FACTORY_ALLOWLIST = [",
                    "-    \"\/Factory\/\",",
                    "-    \"\/Bootstrap\/\",",
                    "-]",
                    "-",
                    " # =============================================================================",
                    "-# COMMON NUMBERS - Erlaubte Magic Numbers (für W5.2, hier zur Referenz)",
                    "-# =============================================================================",
                    "-",
                    "-COMMON_NUMBERS = {",
                    "-    '0', '1', '2',",
                    "-    '10', '100', '1000',",
                    "-    '60', '24', '365',",
                    "-    '30', '31', '28', '29',",
                    "-    '12', '52', '7',",
                    "-    '200', '201', '204',",
                    "-    '301', '302', '304',",
                    "-    '400', '401', '403', '404', '500',",
                    "-}",
                    "-",
                    "-# =============================================================================",
                    " # HELPER FUNCTIONS",
                    " # =============================================================================",
                    " ",
                    "-def is_in_allowlist(file_path: str, allowlist: list) -> bool:",
                    "-    \"\"\"Prüft ob Pfad in Allowlist ist.\"\"\"",
                    "-    return any(allowed in file_path for allowed in allowlist)",
                    "-",
                    "-",
                    "-def block(rule_id: str, message: str) -> dict:",
                    "-    \"\"\"Erzeugt Block-Response.\"\"\"",
                    "-    return {",
                    "-        \"allowed\": False,",
                    "-        \"message\": f\"QUALITY VIOLATION [{rule_id}]: {message}\"",
                    "-    }",
                    "-",
                    "-",
                    " def allow() -> dict:",
                    "     \"\"\"Erzeugt Allow-Response.\"\"\"",
                    "     return {\"allowed\": True}",
                    " ",
                    " ",
                    " # =============================================================================",
                    "-# PRÜFUNG 1: SRP + KISS",
                    "-# =============================================================================",
                    "-",
                    "-def p1_1_responsibility_header(file_path: str, content: str) -> Optional[dict]:",
                    "-    \"\"\"P1.1: @responsibility Header erforderlich.\"\"\"",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):",
                    "-        return None",
                    "-",
                    "-    header_pattern = r\"\/\/\\s*@responsibility:\\s*(.+)\"",
                    "-    match = re.search(header_pattern, content)",
                    "-",
                    "-    if not match:",
                    "-        return block(\"P1.1\", \"Missing @responsibility header. Add: \/\/ @responsibility: <single-responsibility>\")",
                    "-",
                    "-    responsibility_text = match.group(1).strip()",
                    "-",
                    "-    # Prüfe auf Multi-Responsibility-Wörter",
                    "-    multi_words = r\"\\b(und|sowie|außerdem|also|zusätzlich|and|also|additionally)\\b\"",
                    "-    if re.search(multi_words, responsibility_text, re.IGNORECASE):",
                    "-        return block(\"P1.1\", f\"@responsibility contains multi-responsibility indicator: '{responsibility_text}'\")",
                    "-",
                    "-    return None",
                    "-",
                    "-",
                    "-def p1_2_garbage_names(file_path: str, content: str) -> Optional[dict]:",
                    "-    \"\"\"P1.2: Müllhalden-Namen blockieren.\"\"\"",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):",
                    "-        return None",
                    "-",
                    "-    filename = Path(file_path).stem.lower()",
                    "-    forbidden = [\"helper\", \"utils\", \"common\", \"misc\", \"base\"]",
                    "-",
                    "-    for term in forbidden:",
                    "-        if term in filename:",
                    "-            return block(\"P1.2\", f\"Forbidden name pattern: '{term}' indicates unclear responsibility\")",
                    "-",
                    "-    return None",
                    "-",
                    "-",
                    "-# =============================================================================",
                    "-# PRÜFUNG 2: MVC + CRUD",
                    "-# =============================================================================",
                    "-",
                    "-def p2_1_no_sql_in_controller(file_path: str, content: str) -> Optional[dict]:",
                    "-    \"\"\"P2.1: Keine SQL-Statements in Controller.\"\"\"",
                    "-    if \"\/Controller\/\" not in file_path:",
                    "-        return None",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):",
                    "-        return None",
                    "-",
                    "-    sql_patterns = [",
                    "-        r\"\\bSELECT\\s+.+\\s+FROM\\b\",",
                    "-        r\"\\bINSERT\\s+INTO\\b\",",
                    "-        r\"\\bUPDATE\\s+\\w+\\s+SET\\b\",",
                    "-        r\"\\bDELETE\\s+FROM\\b\",",
                    "-    ]",
                    "-",
                    "-    for pattern in sql_patterns:",
                    "-        if re.search(pattern, content, re.IGNORECASE):",
                    "-            return block(\"P2.1\", \"SQL statement in Controller. Move to Repository.\")",
                    "-",
                    "-    return None",
                    "-",
                    "-",
                    "-def p2_2_no_transactions_in_controller(file_path: str, content: str) -> Optional[dict]:",
                    "-    \"\"\"P2.2: Keine Transaktionen in Controller.\"\"\"",
                    "-    if \"\/Controller\/\" not in file_path:",
                    "-        return None",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):",
                    "-        return None",
                    "-",
                    "-    transaction_patterns = [",
                    "-        r\"\\bbeginTransaction\\s*\\(\",",
                    "-        r\"\\bcommit\\s*\\(\",",
                    "-        r\"\\brollBack\\s*\\(\",",
                    "-        r\"\\brollback\\s*\\(\",",
                    "-    ]",
                    "-",
                    "-    for pattern in transaction_patterns:",
                    "-        if re.search(pattern, content):",
                    "-            return block(\"P2.2\", \"Transaction control in Controller. Move to UseCase or Repository.\")",
                    "-",
                    "-    return None",
                    "-",
                    "-",
                    "-def p2_3_no_echo_in_controller(file_path: str, content: str) -> Optional[dict]:",
                    "-    \"\"\"P2.3: Kein echo\/print in Controller.\"\"\"",
                    "-    if \"\/Controller\/\" not in file_path:",
                    "-        return None",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):",
                    "-        return None",
                    "-",
                    "-    output_patterns = [",
                    "-        r\"\\becho\\s+\",",
                    "-        r\"\\becho\\(\",",
                    "-        r\"\\bprint\\s+\",",
                    "-        r\"\\bprint\\(\",",
                    "-    ]",
                    "-",
                    "-    for pattern in output_patterns:",
                    "-        if re.search(pattern, content):",
                    "-            return block(\"P2.3\", \"Direct output (echo\/print) in Controller. Use Response object.\")",
                    "-",
                    "-    return None",
                    "-",
                    "-",
                    "-def p2_4_no_db_in_usecases(file_path: str, content: str) -> Optional[dict]:",
                    "-    \"\"\"P2.4: Keine DB-Artefakte in UseCases.\"\"\"",
                    "-    if \"\/UseCases\/\" not in file_path and \"\/Application\/\" not in file_path:",
                    "-        return None",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):",
                    "-        return None",
                    "-",
                    "-    db_patterns = [",
                    "-        r\"\\bnew\\s+PDO\\b\",",
                    "-        r\"\\bPDO::\",",
                    "-        r\"\\bDatabaseFactory::\",",
                    "-        r\"\\bSELECT\\s+.+\\s+FROM\\b\",",
                    "-        r\"\\bINSERT\\s+INTO\\b\",",
                    "-        r\"\\bUPDATE\\s+\\w+\\s+SET\\b\",",
                    "-        r\"\\bDELETE\\s+FROM\\b\",",
                    "-    ]",
                    "-",
                    "-    for pattern in db_patterns:",
                    "-        if re.search(pattern, content, re.IGNORECASE):",
                    "-            return block(\"P2.4\", \"DB artifact in UseCase\/Application. Use Repository interface.\")",
                    "-",
                    "-    return None",
                    "-",
                    "-",
                    "-# =============================================================================",
                    "-# PRÜFUNG 3: PSR + Types",
                    "-# =============================================================================",
                    "-",
                    "-def p3_1_strict_types(file_path: str, content: str) -> Optional[dict]:",
                    "-    \"\"\"P3.1: strict_types erforderlich.\"\"\"",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):",
                    "-        return None",
                    "-",
                    "-    strict_pattern = r\"declare\\s*\\(\\s*strict_types\\s*=\\s*1\\s*\\)\\s*;\"",
                    "-    if not re.search(strict_pattern, content):",
                    "-        return block(\"P3.1\", \"Missing declare(strict_types=1)\")",
                    "-",
                    "-    return None",
                    "-",
                    "-",
                    "-def p3_2_namespace_matches_path(file_path: str, content: str) -> Optional[dict]:",
                    "-    \"\"\"P3.2: Namespace muss Pfad entsprechen.\"\"\"",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):",
                    "-        return None",
                    "-",
                    "-    # Namespace-Mapping",
                    "-    path_to_namespace = {",
                    "-        \"\/src\/Controller\/\": \"Controller\\\\\",",
                    "-        \"\/src\/Domain\/\": \"Domain\\\\\",",
                    "-        \"\/src\/UseCases\/\": \"UseCases\\\\\",",
                    "-        \"\/src\/Application\/\": \"Application\\\\\",",
                    "-        \"\/src\/Infrastructure\/\": \"Infrastructure\\\\\",",
                    "-        \"\/src\/Framework\/\": \"Framework\\\\\",",
                    "-        \"\/app\/\": \"App\\\\\",",
                    "-    }",
                    "-",
                    "-    namespace_match = re.search(r\"namespace\\s+([^;]+);\", content)",
                    "-    if not namespace_match:",
                    "-        return None  # Kein Namespace = kein Check",
                    "-",
                    "-    declared_namespace = namespace_match.group(1).strip()",
                    "-",
                    "-    for path_prefix, ns_prefix in path_to_namespace.items():",
                    "-        if path_prefix in file_path:",
                    "-            if not declared_namespace.startswith(ns_prefix.rstrip(\"\\\\\")):",
                    "-                return block(\"P3.2\", f\"Namespace '{declared_namespace}' does not match path. Expected: '{ns_prefix}...'\")",
                    "-            break",
                    "-",
                    "-    return None",
                    "-",
                    "-",
                    "-def p3_3_classname_matches_filename(file_path: str, content: str) -> Optional[dict]:",
                    "-    \"\"\"P3.3: Klassenname muss Dateiname entsprechen.\"\"\"",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):",
                    "-        return None",
                    "-",
                    "-    class_match = re.search(r\"(?:class|interface|trait|enum)\\s+(\\w+)\", content)",
                    "-    if not class_match:",
                    "-        return None  # Keine Klasse = kein Check",
                    "-",
                    "-    class_name = class_match.group(1)",
                    "-    expected_name = Path(file_path).stem",
                    "-",
                    "-    if class_name != expected_name:",
                    "-        return block(\"P3.3\", f\"Class '{class_name}' does not match filename '{expected_name}'\")",
                    "-",
                    "-    return None",
                    "-",
                    "-",
                    "-def p3_4_public_method_return_type(file_path: str, content: str) -> Optional[dict]:",
                    "-    \"\"\"P3.4: Public Methods müssen Return-Type haben.\"\"\"",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):",
                    "-        return None",
                    "-",
                    "-    # Finde public functions",
                    "-    public_methods = re.findall(",
                    "-        r\"public\\s+function\\s+(\\w+)\\s*\\([^)]*\\)\\s*(?::\\s*\\S+)?\",",
                    "-        content",
                    "-    )",
                    "-",
                    "-    for method in public_methods:",
                    "-        if method.startswith(\"__\"):",
                    "-            continue  # Magic methods überspringen",
                    "-",
                    "-        # Prüfe ob Return-Type vorhanden",
                    "-        pattern = rf\"public\\s+function\\s+{method}\\s*\\([^)]*\\)\\s*:\\s*\\S+\"",
                    "-        if not re.search(pattern, content):",
                    "-            return block(\"P3.4\", f\"Public method '{method}' missing return type\")",
                    "-",
                    "-    return None",
                    "-",
                    "-",
                    "-# =============================================================================",
                    "-# PRÜFUNG 4: OOP",
                    "-# =============================================================================",
                    "-",
                    "-def p4_1_no_public_properties_in_domain(file_path: str, content: str) -> Optional[dict]:",
                    "-    \"\"\"P4.1: Keine public Properties in Domain.\"\"\"",
                    "-    if \"\/Domain\/\" not in file_path:",
                    "-        return None",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):",
                    "-        return None",
                    "-",
                    "-    # public readonly ist erlaubt",
                    "-    public_props = re.findall(",
                    "-        r\"public\\s+(?!readonly|function|const|static\\s+function).*\\$\\w+\",",
                    "-        content",
                    "-    )",
                    "-",
                    "-    if public_props:",
                    "-        return block(\"P4.1\", \"Public property in Domain. Use getter methods or public readonly.\")",
                    "-",
                    "-    return None",
                    "-",
                    "-",
                    "-# =============================================================================",
                    "-# PRÜFUNG 6: SOLID + DIP",
                    "-# =============================================================================",
                    "-",
                    "-def p6_1_application_no_infrastructure_import(file_path: str, content: str) -> Optional[dict]:",
                    "-    \"\"\"P6.1: UseCases\/Application darf Infrastructure nicht importieren.\"\"\"",
                    "-    if \"\/UseCases\/\" not in file_path and \"\/Application\/\" not in file_path:",
                    "-        return None",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):",
                    "-        return None",
                    "-",
                    "-    if re.search(r\"use\\s+Infrastructure\\\\\", content):",
                    "-        return block(\"P6.1\", \"Application layer must not import Infrastructure directly (DIP)\")",
                    "-",
                    "-    return None",
                    "-",
                    "-",
                    "-def p6_2_domain_no_application_import(file_path: str, content: str) -> Optional[dict]:",
                    "-    \"\"\"P6.2: Domain darf Application-Layer nicht importieren.\"\"\"",
                    "-    if \"\/Domain\/\" not in file_path:",
                    "-        return None",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):",
                    "-        return None",
                    "-",
                    "-    if re.search(r\"use\\s+(UseCases|Application)\\\\\", content):",
                    "-        return block(\"P6.2\", \"Domain must not import Application layer (layer violation)\")",
                    "-",
                    "-    return None",
                    "-",
                    "-",
                    "-# =============================================================================",
                    "-# LEGACY H-REGELN (aus architecture_guard.py)",
                    "-# =============================================================================",
                    "-",
                    "-def h2_domain_no_infrastructure(file_path: str, content: str) -> Optional[dict]:",
                    "-    \"\"\"H2: Domain ohne Infrastructure.\"\"\"",
                    "-    if \"\/Domain\/\" not in file_path:",
                    "-        return None",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):",
                    "-        return None",
                    "-",
                    "-    if re.search(r\"use\\s+Infrastructure\\\\\", content):",
                    "-        return block(\"H2\", \"Domain must not use Infrastructure\")",
                    "-",
                    "-    return None",
                    "-",
                    "-",
                    "-def h3_database_factory_only_in_factory(file_path: str, content: str) -> Optional[dict]:",
                    "-    \"\"\"H3: DatabaseFactory nur in Factory.\"\"\"",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST + FACTORY_ALLOWLIST):",
                    "-        return None",
                    "-",
                    "-    if re.search(r\"DatabaseFactory::\", content):",
                    "-        return block(\"H3\", \"DatabaseFactory only allowed in Factory classes\")",
                    "-",
                    "-    return None",
                    "-",
                    "-",
                    "-def h4_no_new_repository_in_controller(file_path: str, content: str) -> Optional[dict]:",
                    "-    \"\"\"H4: Kein new Repository in Controller.\"\"\"",
                    "-    if \"\/Controller\/\" not in file_path:",
                    "-        return None",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):",
                    "-        return None",
                    "-",
                    "-    if re.search(r\"new\\s+\\w+Repository\\s*\\(\", content):",
                    "-        return block(\"H4\", \"new Repository in Controller not allowed. Use DI.\")",
                    "-",
                    "-    return None",
                    "-",
                    "-",
                    "-def h5_no_new_infrastructure_in_controller(file_path: str, content: str) -> Optional[dict]:",
                    "-    \"\"\"H5: Kein new Infrastructure in Controller.\"\"\"",
                    "-    if \"\/Controller\/\" not in file_path:",
                    "-        return None",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):",
                    "-        return None",
                    "-",
                    "-    if re.search(r\"new\\s+Infrastructure\\\\\", content):",
                    "-        return block(\"H5\", \"new Infrastructure in Controller not allowed. Use DI.\")",
                    "-",
                    "-    return None",
                    "-",
                    "-",
                    "-# =============================================================================",
                    " # MAIN CHECK FUNCTION",
                    " # =============================================================================",
                    " ",
                    " # Alle aktiven Regeln in Prüfungsreihenfolge",
                    " RULES = [",
                    "-    # Prüfung 1: SRP + KISS",
                    "-    p1_1_responsibility_header,",
                    "-    p1_2_garbage_names,",
                    "+    # Prüfung 1: SRP + KISS + Prüfung 4: OOP",
                    "+    *pre_rules_guard.RULES,",
                    " ",
                    "-    # Prüfung 2: MVC + CRUD",
                    "-    p2_1_no_sql_in_controller,",
                    "-    p2_2_no_transactions_in_controller,",
                    "-    p2_3_no_echo_in_controller,",
                    "-    p2_4_no_db_in_usecases,",
                    "+    # Prüfung 2: MVC + CRUD + Prüfung 6: SOLID + DIP + H-Regeln",
                    "+    *pre_rules_security.RULES,",
                    " ",
                    "     # Prüfung 3: PSR + Types",
                    "-    p3_1_strict_types,",
                    "-    p3_2_namespace_matches_path,",
                    "-    p3_3_classname_matches_filename,",
                    "-    p3_4_public_method_return_type,",
                    "-",
                    "-    # Prüfung 4: OOP",
                    "-    p4_1_no_public_properties_in_domain,",
                    "-",
                    "-    # Prüfung 6: SOLID + DIP",
                    "-    p6_1_application_no_infrastructure_import,",
                    "-    p6_2_domain_no_application_import,",
                    "-",
                    "-    # Legacy H-Regeln",
                    "-    h2_domain_no_infrastructure,",
                    "-    h3_database_factory_only_in_factory,",
                    "-    h4_no_new_repository_in_controller,",
                    "-    h5_no_new_infrastructure_in_controller,",
                    "+    *pre_rules_validation.RULES,",
                    " ]",
                    " ",
                    " "
                ]
            }
        ],
        "userModified": false,
        "replaceAll": false
    }
}
← Vorheriger Zur Liste Nächster →