Protokoll #15595

ID15595
Zeitstempel2025-12-25 16:55:42.107433
Clientroot
IP145.224.72.140
Modellclaude-sonnet-4-20250514
Statuscompleted
Tokens2,737 (Input: 34, Output: 2,703)
Dauer125 ms
Request-Zeit2025-12-25 16:55:42.107433
Response-Zeit2025-12-25 16:55:42.232403

Request

{
    "event": "PreToolUse",
    "tool_name": "Read",
    "tool_input": {
        "file_path": "\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/post_rules.py"
    }
}

Response

{
    "tool_response": {
        "type": "text",
        "file": {
            "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.\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]",
            "numLines": 468,
            "startLine": 1,
            "totalLines": 468
        }
    }
}
← Vorheriger Zur Liste Nächster →