Protokoll #6573
| ID | 6573 |
|---|---|
| Zeitstempel | 2025-12-23 06:22:38.409612 |
| Client | root |
| IP | Warning: Undefined array key "request_ip" in /var/www/dev.campus.systemische-tools.de/src/View/protokoll/show.php on line 13 Deprecated: htmlspecialchars(): Passing null to parameter #1 ($string) of type string is deprecated in /var/www/dev.campus.systemische-tools.de/src/View/protokoll/show.php on line 13 |
| Modell | claude-sonnet-4-20250514 |
| Status | completed |
| Tokens | 1,361 (Input: 0, Output: 0) |
| Dauer | 96 ms |
| Request-Zeit | Warning: Undefined array key "request_timestamp" in /var/www/dev.campus.systemische-tools.de/src/View/protokoll/show.php on line 18 |
| Response-Zeit | - |
Warning: Undefined array key "error_message" in /var/www/dev.campus.systemische-tools.de/src/View/protokoll/show.php on line 22
Request
{
"event": "PreToolUse",
"tool_name": "Read",
"tool_input": {
"file_path": "\/var\/www\/tools\/ki-protokoll\/claude-hook\/architecture_guard.py"
}
}
Response
{
"tool_response": {
"type": "text",
"file": {
"filePath": "\/var\/www\/tools\/ki-protokoll\/claude-hook\/architecture_guard.py",
"content": "#!\/usr\/bin\/env python3\n\"\"\"\nArchitecture Guard - Pre-Hook (Blocking)\n\nEnforces hard_constraints from architecture-gate-contract v1.1\nBlocks file creation on violation. No exceptions.\n\nRules:\n H1: strict_types_required (all PHP files)\n H2: domain_no_infrastructure (Domain layer)\n H3: db_factory_only (only in Factory classes)\n H4: no_new_repository_in_controller (Controller layer)\n H5: no_new_infrastructure_in_controller (Controller layer)\n\nTrigger: PreToolUse (Write) on *.php\n\"\"\"\n\nimport json\nimport re\nimport sys\n\n# Hard rules from architecture-gate-contract v1.1\nHARD_RULES = [\n {\n \"id\": \"H1\",\n \"name\": \"strict_types_required\",\n \"pattern\": r\"declare\\s*\\(\\s*strict_types\\s*=\\s*1\\s*\\)\",\n \"must_match\": True,\n \"applies_to\": \"all\",\n \"message\": \"Missing declare(strict_types=1). Add at top of file after <?php\"\n },\n {\n \"id\": \"H2\",\n \"name\": \"domain_no_infrastructure\",\n \"pattern\": r\"use\\s+Infrastructure\\\\\",\n \"must_match\": False,\n \"applies_to\": \"\/Domain\/\",\n \"message\": \"Domain must not use Infrastructure. Violates DIP.\"\n },\n {\n \"id\": \"H3\",\n \"name\": \"db_factory_only\",\n \"pattern\": r\"DatabaseFactory::\",\n \"must_match\": False,\n \"applies_to_not\": \"\/Factory\/\",\n \"message\": \"DatabaseFactory only allowed in Factory classes. Use DI.\"\n },\n {\n \"id\": \"H4\",\n \"name\": \"no_new_repository_in_controller\",\n \"pattern\": r\"new\\s+\\w+Repository\\s*\\(\",\n \"must_match\": False,\n \"applies_to\": \"\/Controller\/\",\n \"message\": \"new Repository in Controller not allowed. Use DI via constructor.\"\n },\n {\n \"id\": \"H5\",\n \"name\": \"no_new_infrastructure_in_controller\",\n \"pattern\": r\"new\\s+Infrastructure\\\\\",\n \"must_match\": False,\n \"applies_to\": \"\/Controller\/\",\n \"message\": \"new Infrastructure in Controller not allowed. Use DI via constructor.\"\n }\n]\n\n# Paths exempt from all rules\nALLOWED_PATHS = [\n \"\/Factory\/\",\n \"\/Bootstrap\/\",\n \"\/tests\/\",\n \"\/Test\/\",\n \"\/vendor\/\",\n]\n\n\ndef is_allowed_path(file_path: str) -> bool:\n \"\"\"Check if file is in allowlist.\"\"\"\n for allowed in ALLOWED_PATHS:\n if allowed in file_path:\n return True\n return False\n\n\ndef rule_applies(rule: dict, file_path: str) -> bool:\n \"\"\"Check if rule applies to this file path.\"\"\"\n if \"applies_to\" in rule:\n if rule[\"applies_to\"] == \"all\":\n return True\n return rule[\"applies_to\"] in file_path\n\n if \"applies_to_not\" in rule:\n return rule[\"applies_to_not\"] not in file_path\n\n return True\n\n\ndef check_rule(rule: dict, content: str) -> bool:\n \"\"\"\n Check if content violates the rule.\n\n Returns True if VIOLATED, False if OK.\n \"\"\"\n match = re.search(rule[\"pattern\"], content)\n\n if rule[\"must_match\"]:\n # Pattern MUST be present\n return match is None # Violated if NOT found\n else:\n # Pattern must NOT be present\n return match is not None # Violated if found\n\n\ndef check_all_rules(file_path: str, content: str) -> dict:\n \"\"\"\n Check all rules against file.\n\n Returns:\n {\"allowed\": True} if all pass\n {\"allowed\": False, \"message\": \"...\"} on first violation\n \"\"\"\n # Skip non-PHP files\n if not file_path.endswith(\".php\"):\n return {\"allowed\": True}\n\n # Skip allowlisted paths\n if is_allowed_path(file_path):\n return {\"allowed\": True}\n\n for rule in HARD_RULES:\n if not rule_applies(rule, file_path):\n continue\n\n if check_rule(rule, content):\n return {\n \"allowed\": False,\n \"message\": f\"ARCHITECTURE VIOLATION [{rule['id']}]: {rule['message']}\"\n }\n\n return {\"allowed\": True}\n\n\ndef format_output(result: dict) -> str:\n \"\"\"Format output as JSON for Claude Code hook protocol.\"\"\"\n return json.dumps(result)\n\n\ndef main():\n try:\n input_data = json.load(sys.stdin)\n except json.JSONDecodeError:\n # Invalid input, allow by default\n print(json.dumps({\"allowed\": True}))\n sys.exit(0)\n\n tool_name = input_data.get(\"tool_name\", \"\")\n\n # Only check Write operations\n if tool_name != \"Write\":\n print(json.dumps({\"allowed\": True}))\n sys.exit(0)\n\n tool_input = input_data.get(\"tool_input\", {})\n file_path = tool_input.get(\"file_path\", \"\")\n content = tool_input.get(\"content\", \"\")\n\n result = check_all_rules(file_path, content)\n\n print(format_output(result))\n\n if result[\"allowed\"]:\n sys.exit(0)\n else:\n sys.exit(0) # Exit 0 with allowed:false triggers block\n\n\nif __name__ == \"__main__\":\n main()\n",
"numLines": 178,
"startLine": 1,
"totalLines": 178
}
}
}