{
"tool_response": {
"filePath": "\/var\/www\/tools\/ki-protokoll\/claude-hook\/hook_dispatcher.py",
"oldString": "#!\/usr\/bin\/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\nimport os\nfrom pathlib import Path\n\n# Add quality directory to path for relative imports\nHOOK_DIR = Path(__file__).parent\nsys.path.insert(0, str(HOOK_DIR))",
"newString": "#!\/usr\/bin\/python3\n\"\"\"\nHook Dispatcher - Stabiler Einstiegspunkt für Claude Code Hooks.\n\nÄnderungen an Regeln nur in quality\/*.py\n\nUsage:\n hook_dispatcher.py pre # PreToolUse (BLOCK)\n hook_dispatcher.py post # PostToolUse (WARN)\n\nUnterstützte Dateitypen:\n - .php: Alle PHP-Dateien\n - .py: Nur in PYTHON_CHECK_PATHS\n\"\"\"\n\nimport json\nimport sys\nimport importlib.util\nimport os\nfrom pathlib import Path\n\n# Add quality directory to path for relative imports\nHOOK_DIR = Path(__file__).parent\nsys.path.insert(0, str(HOOK_DIR))\n\n# =============================================================================\n# KONFIGURATION: Welche Dateien werden geprüft?\n# =============================================================================\n\n# Erlaubte Dateiendungen\nALLOWED_EXTENSIONS = {\".php\", \".py\"}\n\n# Python-Dateien werden NUR in diesen Pfaden geprüft\nPYTHON_CHECK_PATHS = [\n \"\/var\/www\/scripts\/pipeline\/\",\n]\n\n# Diese Pfade werden IMMER übersprungen\nSKIP_PATHS = [\n \"\/venv\/\",\n \"\/__pycache__\/\",\n \"\/tests\/\",\n \"\/vendor\/\",\n]\n\n\ndef should_check(file_path: str) -> bool:\n \"\"\"\n Prüft ob eine Datei vom Hook-System geprüft werden soll.\n\n Args:\n file_path: Absoluter Pfad zur Datei\n\n Returns:\n True wenn Datei geprüft werden soll\n \"\"\"\n # Skip-Pfade immer überspringen\n if any(skip in file_path for skip in SKIP_PATHS):\n return False\n\n # Dateiendung prüfen\n ext = Path(file_path).suffix\n if ext not in ALLOWED_EXTENSIONS:\n return False\n\n # PHP-Dateien: immer prüfen\n if ext == \".php\":\n return True\n\n # Python-Dateien: nur in bestimmten Pfaden\n if ext == \".py\":\n return any(file_path.startswith(p) for p in PYTHON_CHECK_PATHS)\n\n return False",
"originalFile": "#!\/usr\/bin\/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\nimport os\nfrom pathlib import Path\n\n# Add quality directory to path for relative imports\nHOOK_DIR = Path(__file__).parent\nsys.path.insert(0, str(HOOK_DIR))\n\n# DB-Password aus .env laden\nenv_path = Path(__file__).parent \/ \".env\"\nif env_path.exists():\n with open(env_path) as f:\n for line in f:\n if \"=\" in line and not line.startswith(\"#\"):\n key, value = line.strip().split(\"=\", 1)\n os.environ[key] = value\n\n\ndef load_module(name: str):\n \"\"\"Lädt ein Python-Modul dynamisch aus quality\/.\"\"\"\n module_path = HOOK_DIR \/ \"quality\" \/ f\"{name}.py\"\n if not module_path.exists():\n return None\n\n # Use regular import to handle relative imports properly\n import importlib\n return importlib.import_module(f\"quality.{name}\")\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\n # Für Edit: Simuliere vollständigen Datei-Inhalt nach dem Edit\n if tool_name == \"Edit\":\n old_string = tool_input.get(\"old_string\", \"\")\n new_string = tool_input.get(\"new_string\", \"\")\n try:\n with open(file_path, \"r\", encoding=\"utf-8\") as f:\n full_content = f.read()\n # Simuliere den Edit\n content = full_content.replace(old_string, new_string, 1)\n except (OSError, IOError):\n content = new_string # Fallback bei Lesefehler\n else:\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 # Task für Violation erstellen\n try:\n task_creator = load_module(\"task_creator\")\n if task_creator:\n # Parse Rule-ID aus Message\n msg = result.get(\"message\", \"\")\n rule_id = \"UNKNOWN\"\n if \"[\" in msg and \"]\" in msg:\n rule_id = msg.split(\"[\")[1].split(\"]\")[0]\n task_creator.create_violation_task(\n file_path, rule_id, msg, \"block\"\n )\n except Exception:\n pass # Task-Erstellung darf Hook nicht crashen\n\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 # Tasks für Warnungen erstellen\n try:\n task_creator = load_module(\"task_creator\")\n if task_creator:\n task_creator.create_tasks_for_warnings(\n file_path, result[\"warnings\"]\n )\n except Exception:\n pass # Task-Erstellung darf Hook nicht crashen\n\n for warning in result[\"warnings\"]:\n print(f\"[QUALITY] {warning}\", file=sys.stderr)\n # Post-Hook blockiert nie\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": 2,
"oldLines": 11,
"newStart": 2,
"newLines": 15,
"lines": [
" \"\"\"",
" Hook Dispatcher - Stabiler Einstiegspunkt für Claude Code Hooks.",
" ",
"-NIEMALS ÄNDERN. Änderungen an Regeln nur in quality\/*.py",
"+Änderungen an Regeln nur in quality\/*.py",
" ",
" Usage:",
" hook_dispatcher.py pre # PreToolUse (BLOCK)",
" hook_dispatcher.py post # PostToolUse (WARN)",
"+",
"+Unterstützte Dateitypen:",
"+ - .php: Alle PHP-Dateien",
"+ - .py: Nur in PYTHON_CHECK_PATHS",
" \"\"\"",
" ",
" import json"
]
},
{
"oldStart": 19,
"oldLines": 6,
"newStart": 23,
"newLines": 56,
"lines": [
" HOOK_DIR = Path(__file__).parent",
" sys.path.insert(0, str(HOOK_DIR))",
" ",
"+# =============================================================================",
"+# KONFIGURATION: Welche Dateien werden geprüft?",
"+# =============================================================================",
"+",
"+# Erlaubte Dateiendungen",
"+ALLOWED_EXTENSIONS = {\".php\", \".py\"}",
"+",
"+# Python-Dateien werden NUR in diesen Pfaden geprüft",
"+PYTHON_CHECK_PATHS = [",
"+ \"\/var\/www\/scripts\/pipeline\/\",",
"+]",
"+",
"+# Diese Pfade werden IMMER übersprungen",
"+SKIP_PATHS = [",
"+ \"\/venv\/\",",
"+ \"\/__pycache__\/\",",
"+ \"\/tests\/\",",
"+ \"\/vendor\/\",",
"+]",
"+",
"+",
"+def should_check(file_path: str) -> bool:",
"+ \"\"\"",
"+ Prüft ob eine Datei vom Hook-System geprüft werden soll.",
"+",
"+ Args:",
"+ file_path: Absoluter Pfad zur Datei",
"+",
"+ Returns:",
"+ True wenn Datei geprüft werden soll",
"+ \"\"\"",
"+ # Skip-Pfade immer überspringen",
"+ if any(skip in file_path for skip in SKIP_PATHS):",
"+ return False",
"+",
"+ # Dateiendung prüfen",
"+ ext = Path(file_path).suffix",
"+ if ext not in ALLOWED_EXTENSIONS:",
"+ return False",
"+",
"+ # PHP-Dateien: immer prüfen",
"+ if ext == \".php\":",
"+ return True",
"+",
"+ # Python-Dateien: nur in bestimmten Pfaden",
"+ if ext == \".py\":",
"+ return any(file_path.startswith(p) for p in PYTHON_CHECK_PATHS)",
"+",
"+ return False",
"+",
" # DB-Password aus .env laden",
" env_path = Path(__file__).parent \/ \".env\"",
" if env_path.exists():"
]
}
],
"userModified": false,
"replaceAll": false
}
}