Protokoll #15796

ID15796
Zeitstempel2025-12-25 16:56:54.144862
Clientroot
IP145.224.72.140
Modellclaude-sonnet-4-20250514
Statuscompleted
Tokens7,782 (Input: 356, Output: 7,426)
Dauer118 ms
Request-Zeit2025-12-25 16:56:54.144862
Response-Zeit2025-12-25 16:56:54.263168

Request

{
    "event": "PreToolUse",
    "tool_name": "Write",
    "tool_input": {
        "file_path": "\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/post_rules.py",
        "content": "#!\/usr\/bin\/env python3\n\"\"\"\nPost-Hook Regeln (WARN) - Beliebig erweiterbar ohne Claude-Neustart.\n\nAlle W-Regeln aus dem Quality Gate Plan.\nOrganisiert in separate Module nach Kategorie.\n\"\"\"\n\nfrom typing import List\nfrom .rules_quality import RULES as QUALITY_RULES\nfrom .rules_security import RULES as SECURITY_RULES\nfrom .rules_style import RULES as STYLE_RULES\n\n\n# =============================================================================\n# ALLE AKTIVEN REGELN\n# =============================================================================\n\nRULES = QUALITY_RULES + SECURITY_RULES + STYLE_RULES\n\n\n# =============================================================================\n# MAIN CHECK FUNCTION\n# =============================================================================\n\ndef check(file_path: str, content: str) -> dict:\n    \"\"\"\n    Führt alle Post-Hook-Regeln aus.\n    Sammelt alle Warnungen.\n\n    Args:\n        file_path: Pfad zur zu prüfenden Datei\n        content: Dateiinhalt\n\n    Returns:\n        Dict mit warnings-Liste\n    \"\"\"\n    all_warnings = []\n\n    for rule in RULES:\n        warnings = rule(file_path, content)\n        all_warnings.extend(warnings)\n\n    return {\"warnings\": all_warnings}\n"
    }
}

Response

{
    "tool_response": {
        "type": "update",
        "filePath": "\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/post_rules.py",
        "content": "#!\/usr\/bin\/env python3\n\"\"\"\nPost-Hook Regeln (WARN) - Beliebig erweiterbar ohne Claude-Neustart.\n\nAlle W-Regeln aus dem Quality Gate Plan.\nOrganisiert in separate Module nach Kategorie.\n\"\"\"\n\nfrom typing import List\nfrom .rules_quality import RULES as QUALITY_RULES\nfrom .rules_security import RULES as SECURITY_RULES\nfrom .rules_style import RULES as STYLE_RULES\n\n\n# =============================================================================\n# ALLE AKTIVEN REGELN\n# =============================================================================\n\nRULES = QUALITY_RULES + SECURITY_RULES + STYLE_RULES\n\n\n# =============================================================================\n# MAIN CHECK FUNCTION\n# =============================================================================\n\ndef check(file_path: str, content: str) -> dict:\n    \"\"\"\n    Führt alle Post-Hook-Regeln aus.\n    Sammelt alle Warnungen.\n\n    Args:\n        file_path: Pfad zur zu prüfenden Datei\n        content: Dateiinhalt\n\n    Returns:\n        Dict mit warnings-Liste\n    \"\"\"\n    all_warnings = []\n\n    for rule in RULES:\n        warnings = rule(file_path, content)\n        all_warnings.extend(warnings)\n\n    return {\"warnings\": all_warnings}\n",
        "structuredPatch": [
            {
                "oldStart": 3,
                "oldLines": 460,
                "newStart": 3,
                "newLines": 37,
                "lines": [
                    " Post-Hook Regeln (WARN) - Beliebig erweiterbar ohne Claude-Neustart.",
                    " ",
                    " Alle W-Regeln aus dem Quality Gate Plan.",
                    "+Organisiert in separate Module nach Kategorie.",
                    " \"\"\"",
                    " ",
                    "-import re",
                    "-from pathlib import Path",
                    "-from collections import Counter",
                    " from typing import List",
                    "+from .rules_quality import RULES as QUALITY_RULES",
                    "+from .rules_security import RULES as SECURITY_RULES",
                    "+from .rules_style import RULES as STYLE_RULES",
                    " ",
                    "-# =============================================================================",
                    "-# ALLOWLIST",
                    "-# =============================================================================",
                    " ",
                    "-GLOBAL_ALLOWLIST = [",
                    "-    \"\/vendor\/\",",
                    "-    \"\/tests\/\",",
                    "-    \"\/Test\/\",",
                    "-]",
                    "-",
                    "-DTO_ALLOWLIST = [",
                    "-    \"\/Infrastructure\/DTO\/\",",
                    "-    \"\/DTO\/\",",
                    "-]",
                    "-",
                    "-MIGRATION_ALLOWLIST = [",
                    "-    \"\/migrations\/\",",
                    "-    \"\/Migration\/\",",
                    "-]",
                    "-",
                    " # =============================================================================",
                    "-# COMMON NUMBERS - Erlaubte Magic Numbers",
                    "+# ALLE AKTIVEN REGELN",
                    " # =============================================================================",
                    " ",
                    "-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',",
                    "-}",
                    "+RULES = QUALITY_RULES + SECURITY_RULES + STYLE_RULES",
                    " ",
                    "-# =============================================================================",
                    "-# 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 count_non_empty_lines(content: str) -> int:",
                    "-    \"\"\"Zählt nicht-leere Zeilen.\"\"\"",
                    "-    return len([line for line in content.split('\\n') if line.strip()])",
                    "-",
                    "-",
                    " # =============================================================================",
                    "-# PRÜFUNG 1: SRP + KISS (Warnungen)",
                    "-# =============================================================================",
                    "-",
                    "-def w1_1_class_size(file_path: str, content: str) -> List[str]:",
                    "-    \"\"\"W1.1: Klassengröße.\"\"\"",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):",
                    "-        return []",
                    "-",
                    "-    loc = count_non_empty_lines(content)",
                    "-    warnings = []",
                    "-",
                    "-    if loc > 300:",
                    "-        warnings.append(f\"W1.1: Class has {loc} lines (max 300). Consider splitting.\")",
                    "-    elif loc > 200:",
                    "-        warnings.append(f\"W1.1: Class has {loc} lines (approaching limit of 300).\")",
                    "-",
                    "-    return warnings",
                    "-",
                    "-",
                    "-def w1_2_public_method_count(file_path: str, content: str) -> List[str]:",
                    "-    \"\"\"W1.2: Anzahl public methods.\"\"\"",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):",
                    "-        return []",
                    "-",
                    "-    public_methods = re.findall(r\"public\\s+function\\s+\\w+\", content)",
                    "-    count = len(public_methods)",
                    "-",
                    "-    if count > 10:",
                    "-        return [f\"W1.2: Class has {count} public methods (max 10). Consider splitting.\"]",
                    "-",
                    "-    return []",
                    "-",
                    "-",
                    "-def w1_3_constructor_params(file_path: str, content: str) -> List[str]:",
                    "-    \"\"\"W1.3: Constructor-Parameter.\"\"\"",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):",
                    "-        return []",
                    "-",
                    "-    # Finde Constructor",
                    "-    constructor_match = re.search(r\"function\\s+__construct\\s*\\(([^)]*)\\)\", content, re.DOTALL)",
                    "-    if not constructor_match:",
                    "-        return []",
                    "-",
                    "-    params = constructor_match.group(1)",
                    "-    # Zähle Parameter (durch Komma getrennt, aber nicht in Klammern)",
                    "-    param_count = len([p for p in params.split(',') if p.strip()])",
                    "-",
                    "-    if param_count > 5:",
                    "-        return [f\"W1.3: Constructor has {param_count} parameters (max 5). Consider refactoring.\"]",
                    "-",
                    "-    return []",
                    "-",
                    "-",
                    "-def w1_4_dependency_count(file_path: str, content: str) -> List[str]:",
                    "-    \"\"\"W1.4: Anzahl use-Statements.\"\"\"",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):",
                    "-        return []",
                    "-",
                    "-    use_statements = re.findall(r\"^use\\s+\", content, re.MULTILINE)",
                    "-    count = len(use_statements)",
                    "-",
                    "-    if count > 10:",
                    "-        return [f\"W1.4: Class has {count} dependencies (max 10). Consider reducing coupling.\"]",
                    "-",
                    "-    return []",
                    "-",
                    "-",
                    "-def w1_5_suspicious_names(file_path: str, content: str) -> List[str]:",
                    "-    \"\"\"W1.5: Verdächtige Namen (Hint).\"\"\"",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):",
                    "-        return []",
                    "-",
                    "-    filename = Path(file_path).stem",
                    "-    hints = []",
                    "-",
                    "-    if \"Manager\" in filename:",
                    "-        hints.append(f\"W1.5: Hint - '{filename}' contains 'Manager'. Verify single responsibility.\")",
                    "-",
                    "-    # \"And\" als separates Wort im CamelCase",
                    "-    if re.search(r\"[a-z]And[A-Z]\", filename):",
                    "-        hints.append(f\"W1.5: Hint - '{filename}' contains 'And'. May indicate multiple responsibilities.\")",
                    "-",
                    "-    return hints",
                    "-",
                    "-",
                    "-# =============================================================================",
                    "-# PRÜFUNG 2: MVC + CRUD (Warnungen)",
                    "-# =============================================================================",
                    "-",
                    "-def w2_1_business_keywords_in_controller(file_path: str, content: str) -> List[str]:",
                    "-    \"\"\"W2.1: Business-Keywords in Controller.\"\"\"",
                    "-    if \"\/Controller\/\" not in file_path:",
                    "-        return []",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):",
                    "-        return []",
                    "-",
                    "-    keywords = [",
                    "-        (r\"\\bcalculate\\w*\\s*\\(\", \"calculate\"),",
                    "-        (r\"\\bcompute\\w*\\s*\\(\", \"compute\"),",
                    "-        (r\"\\bvalidate\\w*\\s*\\(\", \"validate\"),",
                    "-        (r\"\\bprocess\\w*\\s*\\(\", \"process\"),",
                    "-        (r\"\\btransform\\w*\\s*\\(\", \"transform\"),",
                    "-        (r\"\\bconvert\\w*\\s*\\(\", \"convert\"),",
                    "-    ]",
                    "-",
                    "-    found = []",
                    "-    for pattern, name in keywords:",
                    "-        if re.search(pattern, content, re.IGNORECASE):",
                    "-            found.append(name)",
                    "-",
                    "-    if found:",
                    "-        return [f\"W2.1: Business keywords in Controller: {', '.join(found)}. Consider moving to Domain\/UseCase.\"]",
                    "-",
                    "-    return []",
                    "-",
                    "-",
                    "-def w2_2_private_methods_in_controller(file_path: str, content: str) -> List[str]:",
                    "-    \"\"\"W2.2: Viele private Methoden in Controller.\"\"\"",
                    "-    if \"\/Controller\/\" not in file_path:",
                    "-        return []",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):",
                    "-        return []",
                    "-",
                    "-    private_methods = re.findall(r\"private\\s+function\\s+\\w+\", content)",
                    "-    count = len(private_methods)",
                    "-",
                    "-    if count > 5:",
                    "-        return [f\"W2.2: Controller has {count} private methods (max 5). Extract to Service.\"]",
                    "-",
                    "-    return []",
                    "-",
                    "-",
                    "-# =============================================================================",
                    "-# PRÜFUNG 3: PSR + Types (Warnungen)",
                    "-# =============================================================================",
                    "-",
                    "-def w3_1_potential_untyped_params(file_path: str, content: str) -> List[str]:",
                    "-    \"\"\"W3.1: Potentiell untypisierte Parameter.\"\"\"",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):",
                    "-        return []",
                    "-",
                    "-    # Heuristik: Suche nach $param direkt nach ( oder ,",
                    "-    potential = re.findall(r\"function\\s+\\w+\\s*\\([^)]*[(,]\\s*\\$\", content)",
                    "-",
                    "-    if potential:",
                    "-        return [\"W3.1: Potential untyped parameters detected. Run PHPStan for verification.\"]",
                    "-",
                    "-    return []",
                    "-",
                    "-",
                    "-def w3_3_mixed_type(file_path: str, content: str) -> List[str]:",
                    "-    \"\"\"W3.3: mixed Type verwendet.\"\"\"",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):",
                    "-        return []",
                    "-",
                    "-    if re.search(r\":\\s*mixed\\b\", content):",
                    "-        return [\"W3.3: 'mixed' type used. Consider more specific type.\"]",
                    "-",
                    "-    return []",
                    "-",
                    "-",
                    "-# =============================================================================",
                    "-# PRÜFUNG 4: OOP (Warnungen)",
                    "-# =============================================================================",
                    "-",
                    "-def w4_1_anemic_model(file_path: str, content: str) -> List[str]:",
                    "-    \"\"\"W4.1: Anämisches Model (hohe Accessor-Ratio).\"\"\"",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST + DTO_ALLOWLIST):",
                    "-        return []",
                    "-",
                    "-    getters = len(re.findall(r\"public\\s+function\\s+get[A-Z]\\w*\\s*\\(\", content))",
                    "-    setters = len(re.findall(r\"public\\s+function\\s+set[A-Z]\\w*\\s*\\(\", content))",
                    "-    all_public = len(re.findall(r\"public\\s+function\\s+\\w+\", content))",
                    "-",
                    "-    if all_public > 4:",
                    "-        accessor_ratio = (getters + setters) \/ all_public",
                    "-        if accessor_ratio > 0.7:",
                    "-            return [f\"W4.1: Potential anemic model: {int(accessor_ratio*100)}% accessors. Consider adding behavior.\"]",
                    "-",
                    "-    return []",
                    "-",
                    "-",
                    "-def w4_2_class_without_behavior(file_path: str, content: str) -> List[str]:",
                    "-    \"\"\"W4.2: Klasse ohne Verhalten.\"\"\"",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST + DTO_ALLOWLIST):",
                    "-        return []",
                    "-",
                    "-    has_properties = bool(re.search(r\"(?:private|protected|public)\\s+.*\\$\\w+\", content))",
                    "-    methods = re.findall(r\"(?:public|private|protected)\\s+function\\s+(\\w+)\", content)",
                    "-    real_methods = [m for m in methods if not m.startswith((\"get\", \"set\", \"__\"))]",
                    "-",
                    "-    if has_properties and len(real_methods) == 0:",
                    "-        return [\"W4.2: Class has properties but no behavior methods.\"]",
                    "-",
                    "-    return []",
                    "-",
                    "-",
                    "-def w4_3_low_encapsulation(file_path: str, content: str) -> List[str]:",
                    "-    \"\"\"W4.3: Niedrige Kapselung.\"\"\"",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST + DTO_ALLOWLIST):",
                    "-        return []",
                    "-",
                    "-    public_props = len(re.findall(r\"public\\s+(?!function|const).*\\$\", content))",
                    "-    protected_props = len(re.findall(r\"protected\\s+(?!function|const).*\\$\", content))",
                    "-    private_props = len(re.findall(r\"private\\s+(?!function|const).*\\$\", content))",
                    "-",
                    "-    total = public_props + protected_props + private_props",
                    "-    if total > 3:",
                    "-        exposed_ratio = (public_props + protected_props) \/ total",
                    "-        if exposed_ratio > 0.5:",
                    "-            return [f\"W4.3: Low encapsulation: {int(exposed_ratio*100)}% exposed properties.\"]",
                    "-",
                    "-    return []",
                    "-",
                    "-",
                    "-def w4_4_high_static_ratio(file_path: str, content: str) -> List[str]:",
                    "-    \"\"\"W4.4: Hohe Static-Ratio.\"\"\"",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):",
                    "-        return []",
                    "-",
                    "-    static_methods = len(re.findall(r\"public\\s+static\\s+function\", content))",
                    "-    all_methods = len(re.findall(r\"public\\s+function\", content))",
                    "-",
                    "-    if all_methods > 0 and static_methods > 2:",
                    "-        static_ratio = static_methods \/ all_methods",
                    "-        if static_ratio > 0.5:",
                    "-            return [f\"W4.4: High static method ratio: {static_methods}\/{all_methods}. Consider instance methods.\"]",
                    "-",
                    "-    return []",
                    "-",
                    "-",
                    "-# =============================================================================",
                    "-# PRÜFUNG 5: DRY (Warnungen)",
                    "-# =============================================================================",
                    "-",
                    "-def w5_1_repeated_strings(file_path: str, content: str) -> List[str]:",
                    "-    \"\"\"W5.1: Wiederholte String-Literale.\"\"\"",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST + MIGRATION_ALLOWLIST):",
                    "-        return []",
                    "-",
                    "-    # Finde Strings > 20 Zeichen",
                    "-    string_literals = re.findall(r'\"([^\"]{20,})\"', content)",
                    "-    counts = Counter(string_literals)",
                    "-",
                    "-    warnings = []",
                    "-    for s, count in counts.items():",
                    "-        if count > 3:",
                    "-            truncated = s[:40] + \"...\" if len(s) > 40 else s",
                    "-            warnings.append(f\"W5.1: String repeated {count}x: '{truncated}' - consider constant.\")",
                    "-",
                    "-    return warnings",
                    "-",
                    "-",
                    "-def w5_2_magic_numbers(file_path: str, content: str) -> List[str]:",
                    "-    \"\"\"W5.2: Wiederholte Magic Numbers.\"\"\"",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST + MIGRATION_ALLOWLIST):",
                    "-        return []",
                    "-",
                    "-    # Finde Zahlen (2+ Ziffern, nicht in Konstanten)",
                    "-    numbers = re.findall(r'(?<![A-Z_])(\\d{2,})(?![A-Z_])', content)",
                    "-",
                    "-    warnings = []",
                    "-    for num in set(numbers):",
                    "-        if num not in COMMON_NUMBERS and numbers.count(num) > 2:",
                    "-            warnings.append(f\"W5.2: Magic number {num} repeated {numbers.count(num)}x - consider constant.\")",
                    "-",
                    "-    return warnings",
                    "-",
                    "-",
                    "-# =============================================================================",
                    "-# PRÜFUNG 6: SOLID + DIP (Warnungen)",
                    "-# =============================================================================",
                    "-",
                    "-def w6_1_interface_too_large(file_path: str, content: str) -> List[str]:",
                    "-    \"\"\"W6.1: Interface mit zu vielen Methoden.\"\"\"",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):",
                    "-        return []",
                    "-",
                    "-    interface_match = re.search(r\"interface\\s+(\\w+)[^{]*\\{\", content)",
                    "-    if not interface_match:",
                    "-        return []",
                    "-",
                    "-    interface_name = interface_match.group(1)",
                    "-",
                    "-    # Zähle function-Deklarationen im Interface",
                    "-    # Einfache Heuristik: Zähle alle \"public function\" nach dem Interface-Match",
                    "-    interface_start = interface_match.end()",
                    "-    interface_content = content[interface_start:]",
                    "-",
                    "-    # Finde das Ende des Interface (erste } auf Level 0)",
                    "-    brace_count = 1",
                    "-    end_pos = 0",
                    "-    for i, char in enumerate(interface_content):",
                    "-        if char == '{':",
                    "-            brace_count += 1",
                    "-        elif char == '}':",
                    "-            brace_count -= 1",
                    "-            if brace_count == 0:",
                    "-                end_pos = i",
                    "-                break",
                    "-",
                    "-    interface_body = interface_content[:end_pos]",
                    "-    method_count = len(re.findall(r\"function\\s+\\w+\", interface_body))",
                    "-",
                    "-    if method_count > 7:",
                    "-        return [f\"W6.1: Interface '{interface_name}' has {method_count} methods (max 7). Consider splitting (ISP).\"]",
                    "-",
                    "-    return []",
                    "-",
                    "-",
                    "-def w6_2_too_many_interfaces(file_path: str, content: str) -> List[str]:",
                    "-    \"\"\"W6.2: Klasse implementiert zu viele Interfaces.\"\"\"",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):",
                    "-        return []",
                    "-",
                    "-    implements_match = re.search(r\"implements\\s+([^{]+)\", content)",
                    "-    if not implements_match:",
                    "-        return []",
                    "-",
                    "-    interfaces = [i.strip() for i in implements_match.group(1).split(',') if i.strip()]",
                    "-    count = len(interfaces)",
                    "-",
                    "-    if count > 5:",
                    "-        return [f\"W6.2: Class implements {count} interfaces (max 5). Potential role confusion.\"]",
                    "-",
                    "-    return []",
                    "-",
                    "-",
                    "-# =============================================================================",
                    "-# PRÜFUNG 7: DI + AUTOWIRING",
                    "-# =============================================================================",
                    "-",
                    "-def w7_1_usecase_interface_dependency(file_path: str, content: str) -> List[str]:",
                    "-    \"\"\"W7.1: UseCase mit Interface-Dependency - Hinweis zur Registrierung.\"\"\"",
                    "-    if \"\/UseCases\/\" not in file_path:",
                    "-        return []",
                    "-    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):",
                    "-        return []",
                    "-",
                    "-    # Finde Constructor-Parameter die auf Interface enden",
                    "-    constructor_match = re.search(r\"function\\s+__construct\\s*\\(([^)]*)\\)\", content, re.DOTALL)",
                    "-    if not constructor_match:",
                    "-        return []",
                    "-",
                    "-    params = constructor_match.group(1)",
                    "-    interface_deps = re.findall(r\"(\\w+Interface)\\s+\\$\", params)",
                    "-",
                    "-    if interface_deps:",
                    "-        return [f\"W7.1: Hint - UseCase uses interfaces: {', '.join(interface_deps)}. Ensure registered in services.php.\"]",
                    "-",
                    "-    return []",
                    "-",
                    "-",
                    "-# =============================================================================",
                    " # MAIN CHECK FUNCTION",
                    " # =============================================================================",
                    " ",
                    "-# Alle aktiven Regeln",
                    "-RULES = [",
                    "-    # Prüfung 1: SRP + KISS",
                    "-    w1_1_class_size,",
                    "-    w1_2_public_method_count,",
                    "-    w1_3_constructor_params,",
                    "-    w1_4_dependency_count,",
                    "-    w1_5_suspicious_names,",
                    "-",
                    "-    # Prüfung 2: MVC + CRUD",
                    "-    w2_1_business_keywords_in_controller,",
                    "-    w2_2_private_methods_in_controller,",
                    "-",
                    "-    # Prüfung 3: PSR + Types",
                    "-    w3_1_potential_untyped_params,",
                    "-    w3_3_mixed_type,",
                    "-",
                    "-    # Prüfung 4: OOP",
                    "-    w4_1_anemic_model,",
                    "-    w4_2_class_without_behavior,",
                    "-    w4_3_low_encapsulation,",
                    "-    w4_4_high_static_ratio,",
                    "-",
                    "-    # Prüfung 5: DRY",
                    "-    w5_1_repeated_strings,",
                    "-    w5_2_magic_numbers,",
                    "-",
                    "-    # Prüfung 6: SOLID + DIP",
                    "-    w6_1_interface_too_large,",
                    "-    w6_2_too_many_interfaces,",
                    "-",
                    "-    # Prüfung 7: DI + Autowiring",
                    "-    w7_1_usecase_interface_dependency,",
                    "-]",
                    "-",
                    "-",
                    " def check(file_path: str, content: str) -> dict:",
                    "     \"\"\"",
                    "     Führt alle Post-Hook-Regeln aus.",
                    "     Sammelt alle Warnungen.",
                    "+",
                    "+    Args:",
                    "+        file_path: Pfad zur zu prüfenden Datei",
                    "+        content: Dateiinhalt",
                    "+",
                    "+    Returns:",
                    "+        Dict mit warnings-Liste",
                    "     \"\"\"",
                    "     all_warnings = []",
                    " "
                ]
            }
        ],
        "originalFile": "#!\/usr\/bin\/env python3\n\"\"\"\nPost-Hook Regeln (WARN) - Beliebig erweiterbar ohne Claude-Neustart.\n\nAlle W-Regeln aus dem Quality Gate Plan.\n\"\"\"\n\nimport re\nfrom pathlib import Path\nfrom collections import Counter\nfrom typing import List\n\n# =============================================================================\n# ALLOWLIST\n# =============================================================================\n\nGLOBAL_ALLOWLIST = [\n    \"\/vendor\/\",\n    \"\/tests\/\",\n    \"\/Test\/\",\n]\n\nDTO_ALLOWLIST = [\n    \"\/Infrastructure\/DTO\/\",\n    \"\/DTO\/\",\n]\n\nMIGRATION_ALLOWLIST = [\n    \"\/migrations\/\",\n    \"\/Migration\/\",\n]\n\n# =============================================================================\n# COMMON NUMBERS - Erlaubte Magic Numbers\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 count_non_empty_lines(content: str) -> int:\n    \"\"\"Zählt nicht-leere Zeilen.\"\"\"\n    return len([line for line in content.split('\\n') if line.strip()])\n\n\n# =============================================================================\n# PRÜFUNG 1: SRP + KISS (Warnungen)\n# =============================================================================\n\ndef w1_1_class_size(file_path: str, content: str) -> List[str]:\n    \"\"\"W1.1: Klassengröße.\"\"\"\n    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\n        return []\n\n    loc = count_non_empty_lines(content)\n    warnings = []\n\n    if loc > 300:\n        warnings.append(f\"W1.1: Class has {loc} lines (max 300). Consider splitting.\")\n    elif loc > 200:\n        warnings.append(f\"W1.1: Class has {loc} lines (approaching limit of 300).\")\n\n    return warnings\n\n\ndef w1_2_public_method_count(file_path: str, content: str) -> List[str]:\n    \"\"\"W1.2: Anzahl public methods.\"\"\"\n    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\n        return []\n\n    public_methods = re.findall(r\"public\\s+function\\s+\\w+\", content)\n    count = len(public_methods)\n\n    if count > 10:\n        return [f\"W1.2: Class has {count} public methods (max 10). Consider splitting.\"]\n\n    return []\n\n\ndef w1_3_constructor_params(file_path: str, content: str) -> List[str]:\n    \"\"\"W1.3: Constructor-Parameter.\"\"\"\n    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\n        return []\n\n    # Finde Constructor\n    constructor_match = re.search(r\"function\\s+__construct\\s*\\(([^)]*)\\)\", content, re.DOTALL)\n    if not constructor_match:\n        return []\n\n    params = constructor_match.group(1)\n    # Zähle Parameter (durch Komma getrennt, aber nicht in Klammern)\n    param_count = len([p for p in params.split(',') if p.strip()])\n\n    if param_count > 5:\n        return [f\"W1.3: Constructor has {param_count} parameters (max 5). Consider refactoring.\"]\n\n    return []\n\n\ndef w1_4_dependency_count(file_path: str, content: str) -> List[str]:\n    \"\"\"W1.4: Anzahl use-Statements.\"\"\"\n    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\n        return []\n\n    use_statements = re.findall(r\"^use\\s+\", content, re.MULTILINE)\n    count = len(use_statements)\n\n    if count > 10:\n        return [f\"W1.4: Class has {count} dependencies (max 10). Consider reducing coupling.\"]\n\n    return []\n\n\ndef w1_5_suspicious_names(file_path: str, content: str) -> List[str]:\n    \"\"\"W1.5: Verdächtige Namen (Hint).\"\"\"\n    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\n        return []\n\n    filename = Path(file_path).stem\n    hints = []\n\n    if \"Manager\" in filename:\n        hints.append(f\"W1.5: Hint - '{filename}' contains 'Manager'. Verify single responsibility.\")\n\n    # \"And\" als separates Wort im CamelCase\n    if re.search(r\"[a-z]And[A-Z]\", filename):\n        hints.append(f\"W1.5: Hint - '{filename}' contains 'And'. May indicate multiple responsibilities.\")\n\n    return hints\n\n\n# =============================================================================\n# PRÜFUNG 2: MVC + CRUD (Warnungen)\n# =============================================================================\n\ndef w2_1_business_keywords_in_controller(file_path: str, content: str) -> List[str]:\n    \"\"\"W2.1: Business-Keywords in Controller.\"\"\"\n    if \"\/Controller\/\" not in file_path:\n        return []\n    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\n        return []\n\n    keywords = [\n        (r\"\\bcalculate\\w*\\s*\\(\", \"calculate\"),\n        (r\"\\bcompute\\w*\\s*\\(\", \"compute\"),\n        (r\"\\bvalidate\\w*\\s*\\(\", \"validate\"),\n        (r\"\\bprocess\\w*\\s*\\(\", \"process\"),\n        (r\"\\btransform\\w*\\s*\\(\", \"transform\"),\n        (r\"\\bconvert\\w*\\s*\\(\", \"convert\"),\n    ]\n\n    found = []\n    for pattern, name in keywords:\n        if re.search(pattern, content, re.IGNORECASE):\n            found.append(name)\n\n    if found:\n        return [f\"W2.1: Business keywords in Controller: {', '.join(found)}. Consider moving to Domain\/UseCase.\"]\n\n    return []\n\n\ndef w2_2_private_methods_in_controller(file_path: str, content: str) -> List[str]:\n    \"\"\"W2.2: Viele private Methoden in Controller.\"\"\"\n    if \"\/Controller\/\" not in file_path:\n        return []\n    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\n        return []\n\n    private_methods = re.findall(r\"private\\s+function\\s+\\w+\", content)\n    count = len(private_methods)\n\n    if count > 5:\n        return [f\"W2.2: Controller has {count} private methods (max 5). Extract to Service.\"]\n\n    return []\n\n\n# =============================================================================\n# PRÜFUNG 3: PSR + Types (Warnungen)\n# =============================================================================\n\ndef w3_1_potential_untyped_params(file_path: str, content: str) -> List[str]:\n    \"\"\"W3.1: Potentiell untypisierte Parameter.\"\"\"\n    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\n        return []\n\n    # Heuristik: Suche nach $param direkt nach ( oder ,\n    potential = re.findall(r\"function\\s+\\w+\\s*\\([^)]*[(,]\\s*\\$\", content)\n\n    if potential:\n        return [\"W3.1: Potential untyped parameters detected. Run PHPStan for verification.\"]\n\n    return []\n\n\ndef w3_3_mixed_type(file_path: str, content: str) -> List[str]:\n    \"\"\"W3.3: mixed Type verwendet.\"\"\"\n    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\n        return []\n\n    if re.search(r\":\\s*mixed\\b\", content):\n        return [\"W3.3: 'mixed' type used. Consider more specific type.\"]\n\n    return []\n\n\n# =============================================================================\n# PRÜFUNG 4: OOP (Warnungen)\n# =============================================================================\n\ndef w4_1_anemic_model(file_path: str, content: str) -> List[str]:\n    \"\"\"W4.1: Anämisches Model (hohe Accessor-Ratio).\"\"\"\n    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST + DTO_ALLOWLIST):\n        return []\n\n    getters = len(re.findall(r\"public\\s+function\\s+get[A-Z]\\w*\\s*\\(\", content))\n    setters = len(re.findall(r\"public\\s+function\\s+set[A-Z]\\w*\\s*\\(\", content))\n    all_public = len(re.findall(r\"public\\s+function\\s+\\w+\", content))\n\n    if all_public > 4:\n        accessor_ratio = (getters + setters) \/ all_public\n        if accessor_ratio > 0.7:\n            return [f\"W4.1: Potential anemic model: {int(accessor_ratio*100)}% accessors. Consider adding behavior.\"]\n\n    return []\n\n\ndef w4_2_class_without_behavior(file_path: str, content: str) -> List[str]:\n    \"\"\"W4.2: Klasse ohne Verhalten.\"\"\"\n    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST + DTO_ALLOWLIST):\n        return []\n\n    has_properties = bool(re.search(r\"(?:private|protected|public)\\s+.*\\$\\w+\", content))\n    methods = re.findall(r\"(?:public|private|protected)\\s+function\\s+(\\w+)\", content)\n    real_methods = [m for m in methods if not m.startswith((\"get\", \"set\", \"__\"))]\n\n    if has_properties and len(real_methods) == 0:\n        return [\"W4.2: Class has properties but no behavior methods.\"]\n\n    return []\n\n\ndef w4_3_low_encapsulation(file_path: str, content: str) -> List[str]:\n    \"\"\"W4.3: Niedrige Kapselung.\"\"\"\n    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST + DTO_ALLOWLIST):\n        return []\n\n    public_props = len(re.findall(r\"public\\s+(?!function|const).*\\$\", content))\n    protected_props = len(re.findall(r\"protected\\s+(?!function|const).*\\$\", content))\n    private_props = len(re.findall(r\"private\\s+(?!function|const).*\\$\", content))\n\n    total = public_props + protected_props + private_props\n    if total > 3:\n        exposed_ratio = (public_props + protected_props) \/ total\n        if exposed_ratio > 0.5:\n            return [f\"W4.3: Low encapsulation: {int(exposed_ratio*100)}% exposed properties.\"]\n\n    return []\n\n\ndef w4_4_high_static_ratio(file_path: str, content: str) -> List[str]:\n    \"\"\"W4.4: Hohe Static-Ratio.\"\"\"\n    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):\n        return []\n\n    static_methods = len(re.findall(r\"public\\s+static\\s+function\", content))\n    all_methods = len(re.findall(r\"public\\s+function\", content))\n\n    if all_methods > 0 and static_methods > 2:\n        static_ratio = static_methods \/ all_methods\n        if static_ratio > 0.5:\n            return [f\"W4.4: High static method ratio: {static_methods}\/{all_methods}. Consider instance methods.\"]\n\n    return []\n\n\n# =============================================================================\n# PRÜFUNG 5: DRY (Warnungen)\n# =============================================================================\n\ndef w5_1_repeated_strings(file_path: str, content: str) -> List[str]:\n    \"\"\"W5.1: Wiederholte String-Literale.\"\"\"\n    if is_in_allowlist(file_path, GLOBAL_ALLOWLIST + MIGRATION_ALLOWLIST):\n        return []\n\n    # Finde Strings > 20 Zeichen\n    ... [TRUNCATED-b7b8062923829b73]"
    }
}
← Vorheriger Zur Liste Nächster →