{
"event": "PreToolUse",
"tool_name": "Edit",
"tool_input": {
"file_path": "\/var\/www\/tools\/ki-protokoll\/claude-hook\/hook_dispatcher.py",
"old_string": " # Nur für Write\/Edit auf PHP-Dateien\n file_path = tool_input.get(\"file_path\", \"\")\n content = tool_input.get(\"content\", \"\")\n\n if not file_path.endswith(\".php\"):\n print(json.dumps({\"allowed\": True}))\n sys.exit(0)\n\n # Modul laden und ausführen\n if mode == \"pre\":\n module = load_module(\"pre_rules\")\n if module and hasattr(module, \"check\"):\n result = module.check(file_path, content)\n print(json.dumps(result))\n else:\n print(json.dumps({\"allowed\": True}))\n\n elif mode == \"post\":\n module = load_module(\"post_rules\")\n if module and hasattr(module, \"check\"):\n result = module.check(file_path, content)\n # Post-Hook gibt Warnungen aus, blockiert nicht\n if result.get(\"warnings\"):\n for warning in result[\"warnings\"]:\n print(f\"[QUALITY] {warning}\", file=sys.stderr)\n print(json.dumps({\"allowed\": True}))\n else:\n print(json.dumps({\"allowed\": True}))",
"new_string": " # Nur für Write\/Edit auf PHP-Dateien\n file_path = tool_input.get(\"file_path\", \"\")\n content = tool_input.get(\"content\", \"\")\n\n if not file_path.endswith(\".php\"):\n # Nicht-PHP: durchlassen ohne Output (implizit allow)\n sys.exit(0)\n\n # Modul laden und ausführen\n if mode == \"pre\":\n module = load_module(\"pre_rules\")\n if module and hasattr(module, \"check\"):\n result = module.check(file_path, content)\n if not result.get(\"allowed\", True):\n # BLOCK: Korrektes Claude Code Format\n output = {\n \"hookSpecificOutput\": {\n \"hookEventName\": \"PreToolUse\",\n \"permissionDecision\": \"deny\",\n \"permissionDecisionReason\": result.get(\"message\", \"Quality check failed\")\n }\n }\n print(json.dumps(output))\n # Bei allow: kein Output nötig (implizit allow)\n\n elif mode == \"post\":\n module = load_module(\"post_rules\")\n if module and hasattr(module, \"check\"):\n result = module.check(file_path, content)\n # Post-Hook gibt Warnungen aus, blockiert nicht\n if result.get(\"warnings\"):\n for warning in result[\"warnings\"]:\n print(f\"[QUALITY] {warning}\", file=sys.stderr)\n # Post-Hook blockiert nie"
}
}
{
"tool_response": {
"filePath": "\/var\/www\/tools\/ki-protokoll\/claude-hook\/hook_dispatcher.py",
"oldString": " # Nur für Write\/Edit auf PHP-Dateien\n file_path = tool_input.get(\"file_path\", \"\")\n content = tool_input.get(\"content\", \"\")\n\n if not file_path.endswith(\".php\"):\n print(json.dumps({\"allowed\": True}))\n sys.exit(0)\n\n # Modul laden und ausführen\n if mode == \"pre\":\n module = load_module(\"pre_rules\")\n if module and hasattr(module, \"check\"):\n result = module.check(file_path, content)\n print(json.dumps(result))\n else:\n print(json.dumps({\"allowed\": True}))\n\n elif mode == \"post\":\n module = load_module(\"post_rules\")\n if module and hasattr(module, \"check\"):\n result = module.check(file_path, content)\n # Post-Hook gibt Warnungen aus, blockiert nicht\n if result.get(\"warnings\"):\n for warning in result[\"warnings\"]:\n print(f\"[QUALITY] {warning}\", file=sys.stderr)\n print(json.dumps({\"allowed\": True}))\n else:\n print(json.dumps({\"allowed\": True}))",
"newString": " # Nur für Write\/Edit auf PHP-Dateien\n file_path = tool_input.get(\"file_path\", \"\")\n content = tool_input.get(\"content\", \"\")\n\n if not file_path.endswith(\".php\"):\n # Nicht-PHP: durchlassen ohne Output (implizit allow)\n sys.exit(0)\n\n # Modul laden und ausführen\n if mode == \"pre\":\n module = load_module(\"pre_rules\")\n if module and hasattr(module, \"check\"):\n result = module.check(file_path, content)\n if not result.get(\"allowed\", True):\n # BLOCK: Korrektes Claude Code Format\n output = {\n \"hookSpecificOutput\": {\n \"hookEventName\": \"PreToolUse\",\n \"permissionDecision\": \"deny\",\n \"permissionDecisionReason\": result.get(\"message\", \"Quality check failed\")\n }\n }\n print(json.dumps(output))\n # Bei allow: kein Output nötig (implizit allow)\n\n elif mode == \"post\":\n module = load_module(\"post_rules\")\n if module and hasattr(module, \"check\"):\n result = module.check(file_path, content)\n # Post-Hook gibt Warnungen aus, blockiert nicht\n if result.get(\"warnings\"):\n for warning in result[\"warnings\"]:\n print(f\"[QUALITY] {warning}\", file=sys.stderr)\n # Post-Hook blockiert nie",
"originalFile": "#!\/usr\/bin\/env python3\n\"\"\"\nHook Dispatcher - Stabiler Einstiegspunkt für Claude Code Hooks.\n\nNIEMALS ÄNDERN. Änderungen an Regeln nur in quality\/*.py\n\nUsage:\n hook_dispatcher.py pre # PreToolUse (BLOCK)\n hook_dispatcher.py post # PostToolUse (WARN)\n\"\"\"\n\nimport json\nimport sys\nimport importlib.util\nfrom pathlib import Path\n\n\ndef load_module(name: str):\n \"\"\"Lädt ein Python-Modul dynamisch.\"\"\"\n module_path = Path(__file__).parent \/ \"quality\" \/ f\"{name}.py\"\n if not module_path.exists():\n return None\n\n spec = importlib.util.spec_from_file_location(name, module_path)\n module = importlib.util.module_from_spec(spec)\n spec.loader.exec_module(module)\n return module\n\n\ndef main():\n if len(sys.argv) < 2:\n print(json.dumps({\"error\": \"Usage: hook_dispatcher.py pre|post\"}))\n sys.exit(1)\n\n mode = sys.argv[1]\n\n # Hook-Input von stdin lesen\n try:\n hook_input = json.load(sys.stdin)\n except json.JSONDecodeError:\n print(json.dumps({\"allowed\": True})) # Bei Fehler durchlassen\n sys.exit(0)\n\n # Tool-Daten extrahieren\n tool_name = hook_input.get(\"tool_name\", \"\")\n tool_input = hook_input.get(\"tool_input\", {})\n\n # Nur für Write\/Edit auf PHP-Dateien\n file_path = tool_input.get(\"file_path\", \"\")\n content = tool_input.get(\"content\", \"\")\n\n if not file_path.endswith(\".php\"):\n print(json.dumps({\"allowed\": True}))\n sys.exit(0)\n\n # Modul laden und ausführen\n if mode == \"pre\":\n module = load_module(\"pre_rules\")\n if module and hasattr(module, \"check\"):\n result = module.check(file_path, content)\n print(json.dumps(result))\n else:\n print(json.dumps({\"allowed\": True}))\n\n elif mode == \"post\":\n module = load_module(\"post_rules\")\n if module and hasattr(module, \"check\"):\n result = module.check(file_path, content)\n # Post-Hook gibt Warnungen aus, blockiert nicht\n if result.get(\"warnings\"):\n for warning in result[\"warnings\"]:\n print(f\"[QUALITY] {warning}\", file=sys.stderr)\n print(json.dumps({\"allowed\": True}))\n else:\n print(json.dumps({\"allowed\": True}))\n\n else:\n print(json.dumps({\"error\": f\"Unknown mode: {mode}\"}))\n sys.exit(1)\n\n\nif __name__ == \"__main__\":\n main()\n",
"structuredPatch": [
{
"oldStart": 50,
"oldLines": 7,
"newStart": 50,
"newLines": 7,
"lines": [
" content = tool_input.get(\"content\", \"\")",
" ",
" if not file_path.endswith(\".php\"):",
"- print(json.dumps({\"allowed\": True}))",
"+ # Nicht-PHP: durchlassen ohne Output (implizit allow)",
" sys.exit(0)",
" ",
" # Modul laden und ausführen"
]
},
{
"oldStart": 58,
"oldLines": 9,
"newStart": 58,
"newLines": 17,
"lines": [
" module = load_module(\"pre_rules\")",
" if module and hasattr(module, \"check\"):",
" result = module.check(file_path, content)",
"- print(json.dumps(result))",
"- else:",
"- print(json.dumps({\"allowed\": True}))",
"+ if not result.get(\"allowed\", True):",
"+ # BLOCK: Korrektes Claude Code Format",
"+ output = {",
"+ \"hookSpecificOutput\": {",
"+ \"hookEventName\": \"PreToolUse\",",
"+ \"permissionDecision\": \"deny\",",
"+ \"permissionDecisionReason\": result.get(\"message\", \"Quality check failed\")",
"+ }",
"+ }",
"+ print(json.dumps(output))",
"+ # Bei allow: kein Output nötig (implizit allow)",
" ",
" elif mode == \"post\":",
" module = load_module(\"post_rules\")"
]
},
{
"oldStart": 70,
"oldLines": 9,
"newStart": 78,
"newLines": 7,
"lines": [
" if result.get(\"warnings\"):",
" for warning in result[\"warnings\"]:",
" print(f\"[QUALITY] {warning}\", file=sys.stderr)",
"- print(json.dumps({\"allowed\": True}))",
"- else:",
"- print(json.dumps({\"allowed\": True}))",
"+ # Post-Hook blockiert nie",
" ",
" else:",
" print(json.dumps({\"error\": f\"Unknown mode: {mode}\"}))"
]
}
],
"userModified": false,
"replaceAll": false
}
}