{
"event": "PreToolUse",
"tool_name": "Write",
"tool_input": {
"file_path": "\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/rules_leastsurprise.py",
"content": "#!\/usr\/bin\/env python3\n\"\"\"\nPost-Hook Least Surprise Regeln (WARN) - Least Surprise Principle.\n\nW15.x Regeln: Warnt bei Code der sich anders verhält als erwartet.\n\nPrinzip: \"Code verhält sich so, wie Name und Struktur erwarten lassen. Keine Überraschungen.\"\n\"\"\"\n\nimport re\nfrom typing import List\nfrom .rule_base import Rule\n\n\n# =============================================================================\n# W15: LEAST SURPRISE PRINCIPLE\n# =============================================================================\n\nclass W15_1_GetterWithSideEffect(Rule):\n \"\"\"W15.1: Getter mit Seiteneffekten - unerwartet bei lesenden Methoden.\"\"\"\n\n # Seiteneffekt-Indikatoren\n SIDE_EFFECT_PATTERNS = [\n r\"\\bsave\\s*\\(\",\n r\"\\bupdate\\s*\\(\",\n r\"\\bdelete\\s*\\(\",\n r\"\\binsert\\s*\\(\",\n r\"\\bwrite\\s*\\(\",\n r\"\\bpersist\\s*\\(\",\n r\"\\bflush\\s*\\(\",\n r\"\\bsend\\s*\\(\",\n r\"\\bexecute\\s*\\(\",\n r\"\\$this->\\w+\\s*=\", # Property-Änderung\n r\"\\$this->\\w+\\s*\\[\\s*\\]\\s*=\", # Array-Push\n r\"\\$this->\\w+\\s*\\+\\+\", # Increment\n r\"\\$this->\\w+\\s*--\", # Decrement\n ]\n\n def check(self, file_path: str, content: str) -> List[str]:\n warnings = []\n\n # Finde alle get*-Methoden\n getter_pattern = r\"function\\s+(get[A-Z]\\w*)\\s*\\([^)]*\\)\\s*(?::\\s*[^{]+)?\\s*\\{\"\n getter_matches = list(re.finditer(getter_pattern, content))\n\n for match in getter_matches:\n method_name = match.group(1)\n method_start = match.end()\n\n # Finde das Ende der Methode\n brace_count = 1\n pos = method_start\n while pos < len(content) and brace_count > 0:\n if content[pos] == '{':\n brace_count += 1\n elif content[pos] == '}':\n brace_count -= 1\n pos += 1\n\n method_body = content[method_start:pos]\n\n # Prüfe auf Seiteneffekte\n for side_effect_pattern in self.SIDE_EFFECT_PATTERNS:\n if re.search(side_effect_pattern, method_body):\n warnings.append(\n f\"W15.1: Getter '{method_name}' appears to have side effects. \"\n f\"Getters should be pure read operations.\"\n )\n break # Nur eine Warnung pro Getter\n\n return warnings\n\n\nclass W15_2_SetterWithReturn(Rule):\n \"\"\"W15.2: Setter mit Return-Wert - unerwartet bei schreibenden Methoden.\"\"\"\n\n def check(self, file_path: str, content: str) -> List[str]:\n warnings = []\n\n # Finde set*-Methoden mit Return-Type (außer self\/static für fluent)\n pattern = r\"function\\s+(set[A-Z]\\w*)\\s*\\([^)]+\\)\\s*:\\s*(?!self|static|void|\\$this)[A-Z]\\w*\"\n\n matches = re.findall(pattern, content)\n for method_name in matches:\n warnings.append(\n f\"W15.2: Setter '{method_name}' has unexpected return type. \"\n f\"Setters should return void or self (for fluent interface).\"\n )\n\n return warnings\n\n\nclass W15_3_BoolMethodWithoutQuestion(Rule):\n \"\"\"W15.3: Boolean-Methode ohne is\/has\/can\/should Präfix.\"\"\"\n\n def check(self, file_path: str, content: str) -> List[str]:\n warnings = []\n\n # Finde Methoden die bool zurückgeben\n pattern = r\"function\\s+(\\w+)\\s*\\([^)]*\\)\\s*:\\s*bool\"\n\n matches = re.findall(pattern, content)\n for method_name in matches:\n # Erlaubte Präfixe für bool-Methoden\n if not re.match(r\"^(is|has|can|should|will|was|does|did|are)\", method_name):\n warnings.append(\n f\"W15.3: Boolean method '{method_name}' should start with \"\n f\"is\/has\/can\/should for clarity.\"\n )\n\n return warnings\n\n\nclass W15_4_ConstructorWithReturn(Rule):\n \"\"\"W15.4: Constructor mit explizitem return - unerwartet.\"\"\"\n\n def check(self, file_path: str, content: str) -> List[str]:\n warnings = []\n\n # Finde __construct Methoden\n construct_match = re.search(\n r\"function\\s+__construct\\s*\\([^)]*\\)\\s*\\{\",\n content\n )\n\n if construct_match:\n construct_start = construct_match.end()\n\n # Finde das Ende des Constructors\n brace_count = 1\n pos = construct_start\n while pos < len(content) and brace_count > 0:\n if content[pos] == '{':\n brace_count += 1\n elif content[pos] == '}':\n brace_count -= 1\n pos += 1\n\n construct_body = content[construct_start:pos]\n\n # Prüfe auf return mit Wert (nicht nur 'return;')\n if re.search(r\"\\breturn\\s+[^;]+;\", construct_body):\n warnings.append(\n \"W15.4: Constructor has return statement with value. \"\n \"Constructors should not return values.\"\n )\n\n return warnings\n\n\nclass W15_5_VoidMethodWithEcho(Rule):\n \"\"\"W15.5: Void-Methode mit Echo - unerwartete Ausgabe.\"\"\"\n\n def check(self, file_path: str, content: str) -> List[str]:\n # Überspringe Views und Templates\n if \"\/View\/\" in file_path or \"\/templates\/\" in file_path:\n return []\n\n warnings = []\n\n # Finde void-Methoden\n pattern = r\"function\\s+(\\w+)\\s*\\([^)]*\\)\\s*:\\s*void\\s*\\{\"\n\n for match in re.finditer(pattern, content):\n method_name = match.group(1)\n method_start = match.end()\n\n # Finde das Ende der Methode\n brace_count = 1\n pos = method_start\n while pos < len(content) and brace_count > 0:\n if content[pos] == '{':\n brace_count += 1\n elif content[pos] == '}':\n brace_count -= 1\n pos += 1\n\n method_body = content[method_start:pos]\n\n # Prüfe auf echo\/print\n if re.search(r\"\\b(echo|print)\\b\", method_body):\n warnings.append(\n f\"W15.5: Void method '{method_name}' produces output. \"\n f\"Consider returning a value instead of echoing.\"\n )\n\n return warnings\n\n\nclass W15_6_MagicMethodOverride(Rule):\n \"\"\"W15.6: Magic Methods mit unerwarteter Semantik.\"\"\"\n\n def check(self, file_path: str, content: str) -> List[str]:\n warnings = []\n\n # __toString sollte keine Exception werfen\n tostring_match = re.search(r\"function\\s+__toString\\s*\\([^)]*\\)\\s*:\\s*string\\s*\\{\", content)\n if tostring_match:\n method_start = tostring_match.end()\n brace_count = 1\n pos = method_start\n while pos < len(content) and brace_count > 0:\n if content[pos] == '{':\n brace_count += 1\n elif content[pos] == '}':\n brace_count -= 1\n pos += 1\n\n method_body = content[method_start:pos]\n\n if re.search(r\"\\bthrow\\s+\", method_body):\n warnings.append(\n \"W15.6: __toString() throws exception. This can cause hard-to-debug errors. \"\n \"Return error string instead.\"\n )\n\n return warnings\n\n\n# =============================================================================\n# RULE COLLECTION\n# =============================================================================\n\nRULES = [\n W15_1_GetterWithSideEffect(),\n W15_2_SetterWithReturn(),\n W15_3_BoolMethodWithoutQuestion(),\n W15_4_ConstructorWithReturn(),\n W15_5_VoidMethodWithEcho(),\n W15_6_MagicMethodOverride(),\n]\n"
}
}
{
"tool_response": {
"type": "create",
"filePath": "\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/rules_leastsurprise.py",
"content": "#!\/usr\/bin\/env python3\n\"\"\"\nPost-Hook Least Surprise Regeln (WARN) - Least Surprise Principle.\n\nW15.x Regeln: Warnt bei Code der sich anders verhält als erwartet.\n\nPrinzip: \"Code verhält sich so, wie Name und Struktur erwarten lassen. Keine Überraschungen.\"\n\"\"\"\n\nimport re\nfrom typing import List\nfrom .rule_base import Rule\n\n\n# =============================================================================\n# W15: LEAST SURPRISE PRINCIPLE\n# =============================================================================\n\nclass W15_1_GetterWithSideEffect(Rule):\n \"\"\"W15.1: Getter mit Seiteneffekten - unerwartet bei lesenden Methoden.\"\"\"\n\n # Seiteneffekt-Indikatoren\n SIDE_EFFECT_PATTERNS = [\n r\"\\bsave\\s*\\(\",\n r\"\\bupdate\\s*\\(\",\n r\"\\bdelete\\s*\\(\",\n r\"\\binsert\\s*\\(\",\n r\"\\bwrite\\s*\\(\",\n r\"\\bpersist\\s*\\(\",\n r\"\\bflush\\s*\\(\",\n r\"\\bsend\\s*\\(\",\n r\"\\bexecute\\s*\\(\",\n r\"\\$this->\\w+\\s*=\", # Property-Änderung\n r\"\\$this->\\w+\\s*\\[\\s*\\]\\s*=\", # Array-Push\n r\"\\$this->\\w+\\s*\\+\\+\", # Increment\n r\"\\$this->\\w+\\s*--\", # Decrement\n ]\n\n def check(self, file_path: str, content: str) -> List[str]:\n warnings = []\n\n # Finde alle get*-Methoden\n getter_pattern = r\"function\\s+(get[A-Z]\\w*)\\s*\\([^)]*\\)\\s*(?::\\s*[^{]+)?\\s*\\{\"\n getter_matches = list(re.finditer(getter_pattern, content))\n\n for match in getter_matches:\n method_name = match.group(1)\n method_start = match.end()\n\n # Finde das Ende der Methode\n brace_count = 1\n pos = method_start\n while pos < len(content) and brace_count > 0:\n if content[pos] == '{':\n brace_count += 1\n elif content[pos] == '}':\n brace_count -= 1\n pos += 1\n\n method_body = content[method_start:pos]\n\n # Prüfe auf Seiteneffekte\n for side_effect_pattern in self.SIDE_EFFECT_PATTERNS:\n if re.search(side_effect_pattern, method_body):\n warnings.append(\n f\"W15.1: Getter '{method_name}' appears to have side effects. \"\n f\"Getters should be pure read operations.\"\n )\n break # Nur eine Warnung pro Getter\n\n return warnings\n\n\nclass W15_2_SetterWithReturn(Rule):\n \"\"\"W15.2: Setter mit Return-Wert - unerwartet bei schreibenden Methoden.\"\"\"\n\n def check(self, file_path: str, content: str) -> List[str]:\n warnings = []\n\n # Finde set*-Methoden mit Return-Type (außer self\/static für fluent)\n pattern = r\"function\\s+(set[A-Z]\\w*)\\s*\\([^)]+\\)\\s*:\\s*(?!self|static|void|\\$this)[A-Z]\\w*\"\n\n matches = re.findall(pattern, content)\n for method_name in matches:\n warnings.append(\n f\"W15.2: Setter '{method_name}' has unexpected return type. \"\n f\"Setters should return void or self (for fluent interface).\"\n )\n\n return warnings\n\n\nclass W15_3_BoolMethodWithoutQuestion(Rule):\n \"\"\"W15.3: Boolean-Methode ohne is\/has\/can\/should Präfix.\"\"\"\n\n def check(self, file_path: str, content: str) -> List[str]:\n warnings = []\n\n # Finde Methoden die bool zurückgeben\n pattern = r\"function\\s+(\\w+)\\s*\\([^)]*\\)\\s*:\\s*bool\"\n\n matches = re.findall(pattern, content)\n for method_name in matches:\n # Erlaubte Präfixe für bool-Methoden\n if not re.match(r\"^(is|has|can|should|will|was|does|did|are)\", method_name):\n warnings.append(\n f\"W15.3: Boolean method '{method_name}' should start with \"\n f\"is\/has\/can\/should for clarity.\"\n )\n\n return warnings\n\n\nclass W15_4_ConstructorWithReturn(Rule):\n \"\"\"W15.4: Constructor mit explizitem return - unerwartet.\"\"\"\n\n def check(self, file_path: str, content: str) -> List[str]:\n warnings = []\n\n # Finde __construct Methoden\n construct_match = re.search(\n r\"function\\s+__construct\\s*\\([^)]*\\)\\s*\\{\",\n content\n )\n\n if construct_match:\n construct_start = construct_match.end()\n\n # Finde das Ende des Constructors\n brace_count = 1\n pos = construct_start\n while pos < len(content) and brace_count > 0:\n if content[pos] == '{':\n brace_count += 1\n elif content[pos] == '}':\n brace_count -= 1\n pos += 1\n\n construct_body = content[construct_start:pos]\n\n # Prüfe auf return mit Wert (nicht nur 'return;')\n if re.search(r\"\\breturn\\s+[^;]+;\", construct_body):\n warnings.append(\n \"W15.4: Constructor has return statement with value. \"\n \"Constructors should not return values.\"\n )\n\n return warnings\n\n\nclass W15_5_VoidMethodWithEcho(Rule):\n \"\"\"W15.5: Void-Methode mit Echo - unerwartete Ausgabe.\"\"\"\n\n def check(self, file_path: str, content: str) -> List[str]:\n # Überspringe Views und Templates\n if \"\/View\/\" in file_path or \"\/templates\/\" in file_path:\n return []\n\n warnings = []\n\n # Finde void-Methoden\n pattern = r\"function\\s+(\\w+)\\s*\\([^)]*\\)\\s*:\\s*void\\s*\\{\"\n\n for match in re.finditer(pattern, content):\n method_name = match.group(1)\n method_start = match.end()\n\n # Finde das Ende der Methode\n brace_count = 1\n pos = method_start\n while pos < len(content) and brace_count > 0:\n if content[pos] == '{':\n brace_count += 1\n elif content[pos] == '}':\n brace_count -= 1\n pos += 1\n\n method_body = content[method_start:pos]\n\n # Prüfe auf echo\/print\n if re.search(r\"\\b(echo|print)\\b\", method_body):\n warnings.append(\n f\"W15.5: Void method '{method_name}' produces output. \"\n f\"Consider returning a value instead of echoing.\"\n )\n\n return warnings\n\n\nclass W15_6_MagicMethodOverride(Rule):\n \"\"\"W15.6: Magic Methods mit unerwarteter Semantik.\"\"\"\n\n def check(self, file_path: str, content: str) -> List[str]:\n warnings = []\n\n # __toString sollte keine Exception werfen\n tostring_match = re.search(r\"function\\s+__toString\\s*\\([^)]*\\)\\s*:\\s*string\\s*\\{\", content)\n if tostring_match:\n method_start = tostring_match.end()\n brace_count = 1\n pos = method_start\n while pos < len(content) and brace_count > 0:\n if content[pos] == '{':\n brace_count += 1\n elif content[pos] == '}':\n brace_count -= 1\n pos += 1\n\n method_body = content[method_start:pos]\n\n if re.search(r\"\\bthrow\\s+\", method_body):\n warnings.append(\n \"W15.6: __toString() throws exception. This can cause hard-to-debug errors. \"\n \"Return error string instead.\"\n )\n\n return warnings\n\n\n# =============================================================================\n# RULE COLLECTION\n# =============================================================================\n\nRULES = [\n W15_1_GetterWithSideEffect(),\n W15_2_SetterWithReturn(),\n W15_3_BoolMethodWithoutQuestion(),\n W15_4_ConstructorWithReturn(),\n W15_5_VoidMethodWithEcho(),\n W15_6_MagicMethodOverride(),\n]\n",
"structuredPatch": [],
"originalFile": null
}
}