{
"event": "PreToolUse",
"tool_name": "Write",
"tool_input": {
"file_path": "\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/rules_security.py",
"content": "#!\/usr\/bin\/env python3\n\"\"\"\nSecurity & Architecture Rules (W2, W3, W7).\n\nRegeln für MVC-Architektur, Type-Safety und Dependency Injection.\n\"\"\"\n\nimport re\nfrom typing import List\nfrom .rule_base import Rule\n\n\n# =============================================================================\n# W2: MVC + CRUD - Architectural Boundaries\n# =============================================================================\n\nclass W2_1_BusinessKeywordsInController(Rule):\n \"\"\"W2.1: Business-Keywords in Controller.\"\"\"\n\n def should_skip(self, file_path: str) -> bool:\n if \"\/Controller\/\" not in file_path:\n return True\n return super().should_skip(file_path)\n\n def check(self, file_path: str, content: str) -> List[str]:\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\nclass W2_2_PrivateMethodsInController(Rule):\n \"\"\"W2.2: Viele private Methoden in Controller (max 5).\"\"\"\n\n def should_skip(self, file_path: str) -> bool:\n if \"\/Controller\/\" not in file_path:\n return True\n return super().should_skip(file_path)\n\n def check(self, file_path: str, content: str) -> List[str]:\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# W3: PSR + Types - Type Safety\n# =============================================================================\n\nclass W3_1_PotentialUntypedParams(Rule):\n \"\"\"W3.1: Potentiell untypisierte Parameter.\"\"\"\n\n def check(self, file_path: str, content: str) -> List[str]:\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\nclass W3_3_MixedType(Rule):\n \"\"\"W3.3: mixed Type verwendet.\"\"\"\n\n def check(self, file_path: str, content: str) -> List[str]:\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# W7: DI + AUTOWIRING - Dependency Injection\n# =============================================================================\n\nclass W7_1_UseCaseInterfaceDependency(Rule):\n \"\"\"W7.1: UseCase mit Interface-Dependency - Hinweis zur Registrierung.\"\"\"\n\n def should_skip(self, file_path: str) -> bool:\n if \"\/UseCases\/\" not in file_path:\n return True\n return super().should_skip(file_path)\n\n def check(self, file_path: str, content: str) -> List[str]:\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 interface_deps = re.findall(r\"(\\w+Interface)\\s+\\$\", params)\n\n if interface_deps:\n return [f\"W7.1: Hint - UseCase uses interfaces: {', '.join(interface_deps)}. Ensure registered in services.php.\"]\n\n return []\n\n\n# =============================================================================\n# RULE COLLECTION\n# =============================================================================\n\nRULES = [\n W2_1_BusinessKeywordsInController(),\n W2_2_PrivateMethodsInController(),\n W3_1_PotentialUntypedParams(),\n W3_3_MixedType(),\n W7_1_UseCaseInterfaceDependency(),\n]\n"
}
}
{
"tool_response": {
"type": "create",
"filePath": "\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/rules_security.py",
"content": "#!\/usr\/bin\/env python3\n\"\"\"\nSecurity & Architecture Rules (W2, W3, W7).\n\nRegeln für MVC-Architektur, Type-Safety und Dependency Injection.\n\"\"\"\n\nimport re\nfrom typing import List\nfrom .rule_base import Rule\n\n\n# =============================================================================\n# W2: MVC + CRUD - Architectural Boundaries\n# =============================================================================\n\nclass W2_1_BusinessKeywordsInController(Rule):\n \"\"\"W2.1: Business-Keywords in Controller.\"\"\"\n\n def should_skip(self, file_path: str) -> bool:\n if \"\/Controller\/\" not in file_path:\n return True\n return super().should_skip(file_path)\n\n def check(self, file_path: str, content: str) -> List[str]:\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\nclass W2_2_PrivateMethodsInController(Rule):\n \"\"\"W2.2: Viele private Methoden in Controller (max 5).\"\"\"\n\n def should_skip(self, file_path: str) -> bool:\n if \"\/Controller\/\" not in file_path:\n return True\n return super().should_skip(file_path)\n\n def check(self, file_path: str, content: str) -> List[str]:\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# W3: PSR + Types - Type Safety\n# =============================================================================\n\nclass W3_1_PotentialUntypedParams(Rule):\n \"\"\"W3.1: Potentiell untypisierte Parameter.\"\"\"\n\n def check(self, file_path: str, content: str) -> List[str]:\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\nclass W3_3_MixedType(Rule):\n \"\"\"W3.3: mixed Type verwendet.\"\"\"\n\n def check(self, file_path: str, content: str) -> List[str]:\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# W7: DI + AUTOWIRING - Dependency Injection\n# =============================================================================\n\nclass W7_1_UseCaseInterfaceDependency(Rule):\n \"\"\"W7.1: UseCase mit Interface-Dependency - Hinweis zur Registrierung.\"\"\"\n\n def should_skip(self, file_path: str) -> bool:\n if \"\/UseCases\/\" not in file_path:\n return True\n return super().should_skip(file_path)\n\n def check(self, file_path: str, content: str) -> List[str]:\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 interface_deps = re.findall(r\"(\\w+Interface)\\s+\\$\", params)\n\n if interface_deps:\n return [f\"W7.1: Hint - UseCase uses interfaces: {', '.join(interface_deps)}. Ensure registered in services.php.\"]\n\n return []\n\n\n# =============================================================================\n# RULE COLLECTION\n# =============================================================================\n\nRULES = [\n W2_1_BusinessKeywordsInController(),\n W2_2_PrivateMethodsInController(),\n W3_1_PotentialUntypedParams(),\n W3_3_MixedType(),\n W7_1_UseCaseInterfaceDependency(),\n]\n",
"structuredPatch": [],
"originalFile": null
}
}