Protokoll #6785

ID6785
Zeitstempel2025-12-23 07:06:56.990316
Clientroot
IP145.224.96.190
Modellclaude-sonnet-4-20250514
Statuscompleted
Tokens9,540 (Input: 2,219, Output: 7,321)
Dauer98 ms
Request-Zeit2025-12-23 07:06:56.990316
Response-Zeit2025-12-23 07:06:57.088048

Request

{
    "event": "PreToolUse",
    "tool_name": "Edit",
    "tool_input": {
        "file_path": "\/var\/www\/dev.campus.systemische-tools.de\/docs\/plan-code-quality-gates.md",
        "old_string": "# Prüfung 3-6\n\n*Werden nach Abnahme von Prüfung 2 definiert.*",
        "new_string": "# Prüfung 3: PSR + Types\n\n## Ziel\n\n**PSR (PHP Standards Recommendations):**\n- PSR-1: Basic Coding Standard\n- PSR-4: Autoloading (Namespace = Pfad)\n- PSR-12: Extended Coding Style\n\n**Types (Typdisziplin):**\n- `declare(strict_types=1)` in jeder Datei\n- Typ-Deklarationen für Parameter\n- Return-Type-Deklarationen\n- Property-Types (PHP 7.4+)\n\n## Warum vor SOLID\/DIP?\n\nDIP (Dependency Inversion) basiert auf Interfaces.\nInterfaces ohne Typen sind wirkungslos.\nBeispiel:\n\n```php\n\/\/ Ohne Types: Interface ist Dekoration\ninterface UserRepository {\n    public function find($id);  \/\/ Was ist $id? Was kommt zurück?\n}\n\n\/\/ Mit Types: Interface ist Contract\ninterface UserRepository {\n    public function find(int $id): ?User;\n}\n```\n\n**Types vor DIP ist zwingend.**\n\n## Grundsatz\n\nPSR und Types sind vollständig deterministisch prüfbar.\nKein semantischer Spielraum. Kein Kontext erforderlich.\nDaher: Alle Regeln sind Pre-Hook (BLOCK).\n\n---\n\n## Pre-Hook (BLOCK)\n\n### P3.1: strict_types erforderlich\n\n**Regel:** Jede PHP-Datei muss `declare(strict_types=1)` haben.\n\n**Pattern:**\n```python\nstrict_types_pattern = r\"declare\\s*\\(\\s*strict_types\\s*=\\s*1\\s*\\)\\s*;\"\nif not re.search(strict_types_pattern, content):\n    block(\"Missing declare(strict_types=1)\")\n```\n\n**Position:** Muss in den ersten 5 Zeilen sein (nach <?php, vor namespace).\n\n**Begründung:**\n- Ohne strict_types ist PHP-Typisierung wirkungslos\n- Type Coercion versteckt Bugs\n- Grundlage für alle weiteren Type-Prüfungen\n\n---\n\n### P3.2: Namespace muss Pfad entsprechen (PSR-4)\n\n**Regel:** Der Namespace muss dem Dateipfad entsprechen.\n\n**Mapping:**\n```python\npath_to_namespace = {\n    \"\/src\/Controller\/\": \"Controller\\\\\",\n    \"\/src\/Domain\/\": \"Domain\\\\\",\n    \"\/src\/UseCases\/\": \"UseCases\\\\\",\n    \"\/src\/Application\/\": \"Application\\\\\",\n    \"\/src\/Infrastructure\/\": \"Infrastructure\\\\\",\n    \"\/src\/Framework\/\": \"Framework\\\\\",\n    \"\/app\/\": \"App\\\\\",\n}\n```\n\n**Prüfung:**\n```python\nnamespace_match = re.search(r\"namespace\\s+([^;]+);\", content)\nif namespace_match:\n    declared_namespace = namespace_match.group(1)\n    expected_prefix = get_expected_prefix(file_path)\n    if not declared_namespace.startswith(expected_prefix):\n        block(f\"Namespace '{declared_namespace}' does not match path. Expected: '{expected_prefix}...'\")\n```\n\n**Begründung:**\n- PSR-4 Autoloading erfordert Pfad-Namespace-Konsistenz\n- Inkonsistenz führt zu Autoload-Fehlern\n- IDE-Navigation bricht\n\n---\n\n### P3.3: Klassenname muss Dateiname entsprechen (PSR-1)\n\n**Regel:** Der Klassenname muss dem Dateinamen entsprechen.\n\n**Prüfung:**\n```python\nclass_match = re.search(r\"(?:class|interface|trait|enum)\\s+(\\w+)\", content)\nif class_match:\n    class_name = class_match.group(1)\n    expected_name = Path(file_path).stem  # Filename without .php\n    if class_name != expected_name:\n        block(f\"Class '{class_name}' does not match filename '{expected_name}'\")\n```\n\n**Begründung:**\n- PSR-1 Standard\n- Eine Klasse pro Datei\n- Dateiname = Klassenname\n\n---\n\n### P3.4: Public Methods müssen Return-Type haben\n\n**Regel:** Jede public function muss einen Return-Type deklarieren.\n\n**Pattern:**\n```python\n# Findet public functions ohne Return-Type\npublic_without_return = re.findall(\n    r\"public\\s+function\\s+\\w+\\s*\\([^)]*\\)\\s*(?!:)\\s*\\{\",\n    content\n)\nif public_without_return:\n    block(f\"Public method(s) without return type declaration\")\n```\n\n**Ausnahmen:**\n- `__construct` (kein Return-Type erlaubt)\n- `__destruct` (kein Return-Type erlaubt)\n\n**Verfeinertes Pattern:**\n```python\npublic_methods = re.findall(\n    r\"public\\s+function\\s+(\\w+)\\s*\\([^)]*\\)\\s*(?::\\s*\\S+)?\\s*\\{\",\n    content\n)\nfor method in public_methods:\n    if method.startswith(\"__\"):\n        continue  # Magic methods\n    # Check if has return type\n    method_pattern = rf\"public\\s+function\\s+{method}\\s*\\([^)]*\\)\\s*:\\s*\\S+\"\n    if not re.search(method_pattern, content):\n        block(f\"Public method '{method}' missing return type\")\n```\n\n**Begründung:**\n- Public API muss typisiert sein\n- Aufrufer brauchen Contract\n- IDE-Support und statische Analyse\n\n---\n\n### P3.5: Public Methods müssen typisierte Parameter haben\n\n**Regel:** Jeder Parameter einer public function muss typisiert sein.\n\n**Prüfung:**\n```python\n# Findet untypisierte Parameter: $param ohne Type davor\nuntyped_params = re.findall(\n    r\"public\\s+function\\s+\\w+\\s*\\([^)]*(?<![?\\w])\\$\\w+[^)]*\\)\",\n    content\n)\n```\n\n**Komplexität:** Regex für Parameter-Parsing ist fehleranfällig.\n\n**Alternative Prüfung (einfacher):**\n```python\n# Suche nach Parametern ohne Type-Hint\n# Pattern: ( oder , gefolgt von $name ohne Type davor\nuntyped = re.findall(r\"[(,]\\s*\\$\\w+\", content)\n```\n\n**Problem:** Das matcht auch typisierte Parameter falsch.\n\n**Entscheidung:** Diese Prüfung ist ohne AST nicht zuverlässig.\n\n**YAGNI-Korrektur:** Streichen oder vereinfachen auf:\n- PHPStan Level 6+ als externe Prüfung\n- Hier nur: Existenz von Type-Hints prüfen (heuristisch)\n\n**Vereinfachte Regel:**\n```python\n# Warnung wenn viele untypisierte Parameter erkannt werden\n# Keine Blockade, da False Positives möglich\n```\n\n**Entscheidung:** Zu Post-Hook verschieben als Warnung, nicht Block.\n\n---\n\n## Post-Hook (WARN)\n\n### W3.1: Untypisierte Parameter (heuristisch)\n\n**Regel:** Warnung wenn Parameter ohne Type-Hint erkannt werden.\n\n**Pattern:**\n```python\n# Heuristik: Suche nach $param direkt nach ( oder ,\n# ohne Type davor\npotential_untyped = re.findall(r\"function\\s+\\w+\\s*\\([^)]*[(,]\\s*\\$\", content)\nif potential_untyped:\n    warn(\"Potential untyped parameters detected. Run PHPStan for verification.\")\n```\n\n**Warum Warnung:**\n- Regex kann Type-Hints nicht zuverlässig erkennen\n- False Positives möglich\n- PHPStan ist das richtige Tool dafür\n\n---\n\n### W3.2: Nullable ohne expliziten Null-Check\n\n**Regel:** Warnung wenn nullable Types ohne Null-Handling verwendet werden.\n\n**Problem:** Ohne AST nicht prüfbar.\n\n**Entscheidung:** Streichen. PHPStan macht das besser.\n\n---\n\n### W3.3: Mixed Type verwendet\n\n**Regel:** Warnung bei Verwendung von `mixed` Type.\n\n**Pattern:**\n```python\nif re.search(r\":\\s*mixed\\b\", content):\n    warn(\"'mixed' type used. Consider more specific type.\")\n```\n\n**Begründung:**\n- `mixed` ist Type-Escape\n- Sollte vermieden werden\n- Aber: Manchmal legitim (z.B. JSON-Handling)\n\n---\n\n## Gestrichen\n\n### Parameter-Typ-Prüfung als Block\n\n**Ursprüngliche Idee:** Blockieren wenn Parameter ohne Type.\n\n**Warum gestrichen:**\n- Regex-Parsing von PHP-Parametern ist fehleranfällig\n- Default-Values, Variadic, Nullable machen Pattern komplex\n- PHPStan ist das richtige Tool\n- YAGNI: Nicht duplizieren was PHPStan besser kann\n\n**Alternative:** PHPStan als Quality-Gate (externes Tool, nicht Hook)\n\n### Nullable-Handling-Prüfung\n\n**Warum gestrichen:**\n- Kontrollfluss-Analyse ohne AST unmöglich\n- PHPStan macht das\n\n---\n\n## Zusammenfassung\n\n### Pre-Hook\n\n| ID | Prüfung | Aktion |\n|----|---------|--------|\n| P3.1 | strict_types fehlt | BLOCK |\n| P3.2 | Namespace ≠ Pfad | BLOCK |\n| P3.3 | Klassenname ≠ Dateiname | BLOCK |\n| P3.4 | Public Method ohne Return-Type | BLOCK |\n\n### Post-Hook\n\n| ID | Prüfung | Aktion |\n|----|---------|--------|\n| W3.1 | Potentiell untypisierte Parameter | WARN |\n| W3.3 | `mixed` Type verwendet | WARN |\n\n### Allowlist\n\n| Pfad | Grund |\n|------|-------|\n| \/vendor\/ | Third-party |\n| \/tests\/ | Test-Code darf lockerer sein |\n| \/Test\/ | PHPUnit |\n\n---\n\n## Selbstkritik und Entscheidungen\n\n| Frage | Entscheidung | Begründung |\n|-------|--------------|------------|\n| Warum nicht alle Parameter prüfen? | Zu komplex für Regex | PHPStan ist besser, YAGNI |\n| Warum strict_types als Block? | Absolut deterministisch | Entweder da oder nicht |\n| Warum mixed nur Warnung? | Manchmal legitim | JSON, externe APIs |\n| Fehlt Property-Type-Prüfung? | Ja, bewusst | Regex kann Visibility nicht sicher parsen |\n\n---\n\n## Abgrenzung zu externen Tools\n\n| Prüfung | Hook | PHPStan |\n|---------|------|---------|\n| strict_types | ✓ | ✓ |\n| Namespace = Pfad | ✓ | ✓ |\n| Return Types | ✓ | ✓ |\n| Parameter Types | ~ (Warnung) | ✓ (exakt) |\n| Null-Safety | ✗ | ✓ |\n| Type Inference | ✗ | ✓ |\n\n**Fazit:** Hooks prüfen das Offensichtliche. PHPStan prüft das Komplexe.\n\n---\n\n# Prüfung 4-6\n\n*Werden nach Abnahme von Prüfung 3 definiert.*"
    }
}

Response

{
    "tool_response": {
        "filePath": "\/var\/www\/dev.campus.systemische-tools.de\/docs\/plan-code-quality-gates.md",
        "oldString": "# Prüfung 3-6\n\n*Werden nach Abnahme von Prüfung 2 definiert.*",
        "newString": "# Prüfung 3: PSR + Types\n\n## Ziel\n\n**PSR (PHP Standards Recommendations):**\n- PSR-1: Basic Coding Standard\n- PSR-4: Autoloading (Namespace = Pfad)\n- PSR-12: Extended Coding Style\n\n**Types (Typdisziplin):**\n- `declare(strict_types=1)` in jeder Datei\n- Typ-Deklarationen für Parameter\n- Return-Type-Deklarationen\n- Property-Types (PHP 7.4+)\n\n## Warum vor SOLID\/DIP?\n\nDIP (Dependency Inversion) basiert auf Interfaces.\nInterfaces ohne Typen sind wirkungslos.\nBeispiel:\n\n```php\n\/\/ Ohne Types: Interface ist Dekoration\ninterface UserRepository {\n    public function find($id);  \/\/ Was ist $id? Was kommt zurück?\n}\n\n\/\/ Mit Types: Interface ist Contract\ninterface UserRepository {\n    public function find(int $id): ?User;\n}\n```\n\n**Types vor DIP ist zwingend.**\n\n## Grundsatz\n\nPSR und Types sind vollständig deterministisch prüfbar.\nKein semantischer Spielraum. Kein Kontext erforderlich.\nDaher: Alle Regeln sind Pre-Hook (BLOCK).\n\n---\n\n## Pre-Hook (BLOCK)\n\n### P3.1: strict_types erforderlich\n\n**Regel:** Jede PHP-Datei muss `declare(strict_types=1)` haben.\n\n**Pattern:**\n```python\nstrict_types_pattern = r\"declare\\s*\\(\\s*strict_types\\s*=\\s*1\\s*\\)\\s*;\"\nif not re.search(strict_types_pattern, content):\n    block(\"Missing declare(strict_types=1)\")\n```\n\n**Position:** Muss in den ersten 5 Zeilen sein (nach <?php, vor namespace).\n\n**Begründung:**\n- Ohne strict_types ist PHP-Typisierung wirkungslos\n- Type Coercion versteckt Bugs\n- Grundlage für alle weiteren Type-Prüfungen\n\n---\n\n### P3.2: Namespace muss Pfad entsprechen (PSR-4)\n\n**Regel:** Der Namespace muss dem Dateipfad entsprechen.\n\n**Mapping:**\n```python\npath_to_namespace = {\n    \"\/src\/Controller\/\": \"Controller\\\\\",\n    \"\/src\/Domain\/\": \"Domain\\\\\",\n    \"\/src\/UseCases\/\": \"UseCases\\\\\",\n    \"\/src\/Application\/\": \"Application\\\\\",\n    \"\/src\/Infrastructure\/\": \"Infrastructure\\\\\",\n    \"\/src\/Framework\/\": \"Framework\\\\\",\n    \"\/app\/\": \"App\\\\\",\n}\n```\n\n**Prüfung:**\n```python\nnamespace_match = re.search(r\"namespace\\s+([^;]+);\", content)\nif namespace_match:\n    declared_namespace = namespace_match.group(1)\n    expected_prefix = get_expected_prefix(file_path)\n    if not declared_namespace.startswith(expected_prefix):\n        block(f\"Namespace '{declared_namespace}' does not match path. Expected: '{expected_prefix}...'\")\n```\n\n**Begründung:**\n- PSR-4 Autoloading erfordert Pfad-Namespace-Konsistenz\n- Inkonsistenz führt zu Autoload-Fehlern\n- IDE-Navigation bricht\n\n---\n\n### P3.3: Klassenname muss Dateiname entsprechen (PSR-1)\n\n**Regel:** Der Klassenname muss dem Dateinamen entsprechen.\n\n**Prüfung:**\n```python\nclass_match = re.search(r\"(?:class|interface|trait|enum)\\s+(\\w+)\", content)\nif class_match:\n    class_name = class_match.group(1)\n    expected_name = Path(file_path).stem  # Filename without .php\n    if class_name != expected_name:\n        block(f\"Class '{class_name}' does not match filename '{expected_name}'\")\n```\n\n**Begründung:**\n- PSR-1 Standard\n- Eine Klasse pro Datei\n- Dateiname = Klassenname\n\n---\n\n### P3.4: Public Methods müssen Return-Type haben\n\n**Regel:** Jede public function muss einen Return-Type deklarieren.\n\n**Pattern:**\n```python\n# Findet public functions ohne Return-Type\npublic_without_return = re.findall(\n    r\"public\\s+function\\s+\\w+\\s*\\([^)]*\\)\\s*(?!:)\\s*\\{\",\n    content\n)\nif public_without_return:\n    block(f\"Public method(s) without return type declaration\")\n```\n\n**Ausnahmen:**\n- `__construct` (kein Return-Type erlaubt)\n- `__destruct` (kein Return-Type erlaubt)\n\n**Verfeinertes Pattern:**\n```python\npublic_methods = re.findall(\n    r\"public\\s+function\\s+(\\w+)\\s*\\([^)]*\\)\\s*(?::\\s*\\S+)?\\s*\\{\",\n    content\n)\nfor method in public_methods:\n    if method.startswith(\"__\"):\n        continue  # Magic methods\n    # Check if has return type\n    method_pattern = rf\"public\\s+function\\s+{method}\\s*\\([^)]*\\)\\s*:\\s*\\S+\"\n    if not re.search(method_pattern, content):\n        block(f\"Public method '{method}' missing return type\")\n```\n\n**Begründung:**\n- Public API muss typisiert sein\n- Aufrufer brauchen Contract\n- IDE-Support und statische Analyse\n\n---\n\n### P3.5: Public Methods müssen typisierte Parameter haben\n\n**Regel:** Jeder Parameter einer public function muss typisiert sein.\n\n**Prüfung:**\n```python\n# Findet untypisierte Parameter: $param ohne Type davor\nuntyped_params = re.findall(\n    r\"public\\s+function\\s+\\w+\\s*\\([^)]*(?<![?\\w])\\$\\w+[^)]*\\)\",\n    content\n)\n```\n\n**Komplexität:** Regex für Parameter-Parsing ist fehleranfällig.\n\n**Alternative Prüfung (einfacher):**\n```python\n# Suche nach Parametern ohne Type-Hint\n# Pattern: ( oder , gefolgt von $name ohne Type davor\nuntyped = re.findall(r\"[(,]\\s*\\$\\w+\", content)\n```\n\n**Problem:** Das matcht auch typisierte Parameter falsch.\n\n**Entscheidung:** Diese Prüfung ist ohne AST nicht zuverlässig.\n\n**YAGNI-Korrektur:** Streichen oder vereinfachen auf:\n- PHPStan Level 6+ als externe Prüfung\n- Hier nur: Existenz von Type-Hints prüfen (heuristisch)\n\n**Vereinfachte Regel:**\n```python\n# Warnung wenn viele untypisierte Parameter erkannt werden\n# Keine Blockade, da False Positives möglich\n```\n\n**Entscheidung:** Zu Post-Hook verschieben als Warnung, nicht Block.\n\n---\n\n## Post-Hook (WARN)\n\n### W3.1: Untypisierte Parameter (heuristisch)\n\n**Regel:** Warnung wenn Parameter ohne Type-Hint erkannt werden.\n\n**Pattern:**\n```python\n# Heuristik: Suche nach $param direkt nach ( oder ,\n# ohne Type davor\npotential_untyped = re.findall(r\"function\\s+\\w+\\s*\\([^)]*[(,]\\s*\\$\", content)\nif potential_untyped:\n    warn(\"Potential untyped parameters detected. Run PHPStan for verification.\")\n```\n\n**Warum Warnung:**\n- Regex kann Type-Hints nicht zuverlässig erkennen\n- False Positives möglich\n- PHPStan ist das richtige Tool dafür\n\n---\n\n### W3.2: Nullable ohne expliziten Null-Check\n\n**Regel:** Warnung wenn nullable Types ohne Null-Handling verwendet werden.\n\n**Problem:** Ohne AST nicht prüfbar.\n\n**Entscheidung:** Streichen. PHPStan macht das besser.\n\n---\n\n### W3.3: Mixed Type verwendet\n\n**Regel:** Warnung bei Verwendung von `mixed` Type.\n\n**Pattern:**\n```python\nif re.search(r\":\\s*mixed\\b\", content):\n    warn(\"'mixed' type used. Consider more specific type.\")\n```\n\n**Begründung:**\n- `mixed` ist Type-Escape\n- Sollte vermieden werden\n- Aber: Manchmal legitim (z.B. JSON-Handling)\n\n---\n\n## Gestrichen\n\n### Parameter-Typ-Prüfung als Block\n\n**Ursprüngliche Idee:** Blockieren wenn Parameter ohne Type.\n\n**Warum gestrichen:**\n- Regex-Parsing von PHP-Parametern ist fehleranfällig\n- Default-Values, Variadic, Nullable machen Pattern komplex\n- PHPStan ist das richtige Tool\n- YAGNI: Nicht duplizieren was PHPStan besser kann\n\n**Alternative:** PHPStan als Quality-Gate (externes Tool, nicht Hook)\n\n### Nullable-Handling-Prüfung\n\n**Warum gestrichen:**\n- Kontrollfluss-Analyse ohne AST unmöglich\n- PHPStan macht das\n\n---\n\n## Zusammenfassung\n\n### Pre-Hook\n\n| ID | Prüfung | Aktion |\n|----|---------|--------|\n| P3.1 | strict_types fehlt | BLOCK |\n| P3.2 | Namespace ≠ Pfad | BLOCK |\n| P3.3 | Klassenname ≠ Dateiname | BLOCK |\n| P3.4 | Public Method ohne Return-Type | BLOCK |\n\n### Post-Hook\n\n| ID | Prüfung | Aktion |\n|----|---------|--------|\n| W3.1 | Potentiell untypisierte Parameter | WARN |\n| W3.3 | `mixed` Type verwendet | WARN |\n\n### Allowlist\n\n| Pfad | Grund |\n|------|-------|\n| \/vendor\/ | Third-party |\n| \/tests\/ | Test-Code darf lockerer sein |\n| \/Test\/ | PHPUnit |\n\n---\n\n## Selbstkritik und Entscheidungen\n\n| Frage | Entscheidung | Begründung |\n|-------|--------------|------------|\n| Warum nicht alle Parameter prüfen? | Zu komplex für Regex | PHPStan ist besser, YAGNI |\n| Warum strict_types als Block? | Absolut deterministisch | Entweder da oder nicht |\n| Warum mixed nur Warnung? | Manchmal legitim | JSON, externe APIs |\n| Fehlt Property-Type-Prüfung? | Ja, bewusst | Regex kann Visibility nicht sicher parsen |\n\n---\n\n## Abgrenzung zu externen Tools\n\n| Prüfung | Hook | PHPStan |\n|---------|------|---------|\n| strict_types | ✓ | ✓ |\n| Namespace = Pfad | ✓ | ✓ |\n| Return Types | ✓ | ✓ |\n| Parameter Types | ~ (Warnung) | ✓ (exakt) |\n| Null-Safety | ✗ | ✓ |\n| Type Inference | ✗ | ✓ |\n\n**Fazit:** Hooks prüfen das Offensichtliche. PHPStan prüft das Komplexe.\n\n---\n\n# Prüfung 4-6\n\n*Werden nach Abnahme von Prüfung 3 definiert.*",
        "originalFile": "# Code Quality Gates\n\n## Prüfungsreihenfolge\n\n| # | Prinzip | Begründung der Reihenfolge |\n|---|---------|---------------------------|\n| 1 | SRP + KISS | Grundlage für alles. Ohne klare Verantwortung ist nichts anderes prüfbar. |\n| 2 | MVC + CRUD | Strukturelle Trennung. Setzt SRP voraus. |\n| 3 | PSR + Types | Typdisziplin. Muss VOR DIP kommen, weil Interfaces ohne Typen wirkungslos sind. |\n| 4 | OOP | Verhalten + Zustand. Setzt Typen voraus. |\n| 5 | DRY | Duplikationserkennung. Setzt funktionierende Struktur voraus. |\n| 6 | SOLID + DIP | Abhängigkeitsrichtung. Setzt Typen und Interfaces voraus. Kommt zuletzt. |\n\n---\n\n# Prüfung 1: SRP + KISS\n\n## Ziel\n\n**SRP:** Eine Klasse hat genau einen Grund sich zu ändern.\n\n**KISS:** Einfachste Lösung die funktioniert. Keine vorzeitigen Abstraktionen.\n\n## Grundsatz\n\nSRP ist semantisch. Maschinen können \"Verantwortung\" nicht verstehen.\n\nDaher:\n- **Pre-Hook:** Erzwingt explizite Deklaration + blockiert eindeutige Symptome\n- **Post-Hook:** Warnt bei statistischen Indikatoren\n\n---\n\n## Pre-Hook (BLOCK)\n\n### P1.1: Verantwortungs-Header\n\n**Regel:** Jede PHP-Datei muss ihre Verantwortung explizit deklarieren.\n\n**Format:**\n```php\n<?php\ndeclare(strict_types=1);\n\n\/\/ @responsibility: <genau-eine-verantwortung>\n```\n\n**Validierung:**\n\n| Check | Begründung |\n|-------|------------|\n| Header vorhanden | Ohne Deklaration entsteht Scope Creep |\n| Nicht leer | Leerer Header ist wertlos |\n| Keine Multi-Responsibility-Wörter | \"und\", \"sowie\", \"außerdem\", \"zusätzlich\" im Header = mehrere Verantwortungen |\n\n**Pattern:**\n```python\nheader_pattern = r\"\/\/\\s*@responsibility:\\s*(.+)\"\nmulti_words = r\"\\b(und|sowie|außerdem|also|zusätzlich)\\b\"\n\nmatch = re.search(header_pattern, content)\nif not match:\n    block(\"Missing @responsibility header\")\n\nresponsibility_text = match.group(1)\nif re.search(multi_words, responsibility_text, re.IGNORECASE):\n    block(\"@responsibility contains multi-responsibility indicator\")\n```\n\n**Warum diese Wörter:**\n- \"Validiert Benutzer und erstellt Sessions\" = 2 Verantwortungen\n- \"Lädt Daten sowie formatiert Ausgabe\" = 2 Verantwortungen\n- Erzwingt Nachdenken über Singular-Verantwortung\n\n**Allowlist:** `\/vendor\/`, `\/tests\/`, `\/Test\/`\n\n---\n\n### P1.2: Müllhalden-Namen\n\n**Regel:** Bestimmte Begriffe im Dateinamen sind immer Symptome für fehlende klare Verantwortung.\n\n**Blockiert (case-insensitive):**\n\n| Begriff | Begründung |\n|---------|------------|\n| Helper | \"Hilft bei allem\" = keine klare Verantwortung |\n| Utils | Sammelbecken für Funktionen ohne Heimat |\n| Common | \"Wird überall gebraucht\" = gehört nirgends hin |\n| Misc | Explizit: \"Verschiedenes\" |\n| Base | Oft Gott-Klasse mit Shared-State |\n\n**Nicht blockiert:**\n- \"Manager\" → Kann legitim sein (TransactionManager, EntityManager)\n- \"And\" im Namen → Sprachlich, nicht strukturell\n\n**Pattern:**\n```python\nforbidden = [\"helper\", \"utils\", \"common\", \"misc\", \"base\"]\nfilename_lower = filename.lower()\nfor term in forbidden:\n    if term in filename_lower:\n        block(f\"Forbidden name: '{term}' indicates unclear responsibility\")\n```\n\n**Keine Ausnahmen.** Diese Begriffe sind niemals architektonisch sauber.\n\n---\n\n## Post-Hook (WARN)\n\n### W1.1: Klassengröße (LOC)\n\n**Regel:** Große Klassen haben oft mehrere Verantwortungen.\n\n| LOC | Aktion |\n|-----|--------|\n| > 200 | Hinweis |\n| > 300 | Warnung |\n\n**Warum zwei Schwellen:**\n- 200: \"Du näherst dich dem Limit\"\n- 300: \"Hier stimmt strukturell etwas nicht\"\n\n**Messung:** Nicht-leere Zeilen.\n\n---\n\n### W1.2: Public-Method-Count\n\n**Regel:** Viele öffentliche Methoden = viele Interaktionspunkte = breite Verantwortung.\n\n| Count | Aktion |\n|-------|--------|\n| > 10 | Warnung |\n\n**Warum 10:**\n- Ein fokussiertes Interface hat 3-7 Methoden\n- 10+ deutet auf Feature Creep\n\n---\n\n### W1.3: Constructor-Parameter\n\n**Regel:** Viele Dependencies = viele Gründe sich zu ändern.\n\n| Count | Aktion |\n|-------|--------|\n| > 5 | Warnung |\n\n**Warum 5:**\n- 1-3: Normal\n- 4-5: Grenzwertig\n- 6+: Fast immer strukturelles Problem\n\n---\n\n### W1.4: Dependency-Count (use-Statements)\n\n**Regel:** Viele Imports = Kopplung an viele Module = viele Änderungsgründe.\n\n| Count | Aktion |\n|-------|--------|\n| > 10 | Warnung |\n\n**Warum 10:**\n- PHP-interne Classes zählen mit\n- Framework-Imports zählen mit\n- 10+ externe Abhängigkeiten ist ein Warnsignal\n\n---\n\n### W1.5: Verdächtige Namen\n\n**Regel:** Manche Begriffe sind nicht eindeutig Müll, aber verdächtig.\n\n| Begriff | Aktion |\n|---------|--------|\n| Manager | Hinweis |\n| And (als Wort) | Hinweis |\n\n**Warum Hinweis statt Block:**\n- \"UserManager\" kann legitim sein\n- \"ReadAndWrite\" kann legitim sein (obwohl fragwürdig)\n- Menschliche Entscheidung erforderlich\n\n---\n\n## Gestrichen\n\n### Verschachtelungstiefe\n\n**Ursprüngliche Idee:** > 4 Ebenen = Warnung\n\n**Warum gestrichen:**\n- Indent-basierte Messung ist nicht zuverlässig\n- PHP erlaubt verschiedene Styles (2, 4, Tabs)\n- Fluent Interfaces erzeugen tiefe Indents ohne Kontrollfluss\n- Array-Definitionen erzeugen tiefe Indents\n- False Positives zu hoch\n\n**Alternative:** Erst implementieren wenn AST-Parsing verfügbar.\n\n---\n\n## Zusammenfassung\n\n### Pre-Hook\n\n| ID | Prüfung | Aktion |\n|----|---------|--------|\n| P1.1 | @responsibility fehlt oder Multi-Responsibility | BLOCK |\n| P1.2 | Müllhalden-Name (Helper, Utils, Common, Misc, Base) | BLOCK |\n\n### Post-Hook\n\n| ID | Prüfung | Schwelle | Aktion |\n|----|---------|----------|--------|\n| W1.1 | LOC | > 300 | WARN |\n| W1.2 | Public Methods | > 10 | WARN |\n| W1.3 | Constructor Params | > 5 | WARN |\n| W1.4 | Dependencies (use) | > 10 | WARN |\n| W1.5 | Verdächtige Namen | Manager, And | HINT |\n\n### Allowlist\n\n| Pfad | Grund |\n|------|-------|\n| \/vendor\/ | Third-party Code |\n| \/tests\/ | Test-Konventionen anders |\n| \/Test\/ | PHPUnit |\n\n---\n\n## Selbstkritik und Entscheidungen\n\n| Frage | Entscheidung | Begründung |\n|-------|--------------|------------|\n| Ist Multi-Word-Check im Header Over-Engineering? | Nein | Ein Regex. Verhindert \"Macht A und B\". Minimaler Aufwand, hoher Nutzen. |\n| Ist Dependency-Count SRP? | Ja | Viele Dependencies = viele Änderungsgründe = SRP-Verletzung per Definition. |\n| Sollen Factories in Allowlist? | Nein | Auch Factories sollten nicht 50 Methoden haben. Wenn zu groß, aufteilen. |\n| Warum keine Kontext-Schwellen für Repositories? | YAGNI | Komplexität ohne klaren Nutzen. Wer > 10 Methoden braucht, soll bewusst entscheiden. |\n\n---\n\n## Implementierung\n\n**Dateien:**\n- `\/var\/www\/tools\/ki-protokoll\/claude-hook\/srp_kiss_guard.py` (Pre-Hook)\n- `\/var\/www\/tools\/ki-protokoll\/claude-hook\/srp_kiss_metrics.py` (Post-Hook)\n\n**Hooks in settings.json:**\n```json\n{\n  \"PreToolUse\": [\n    {\n      \"matcher\": \"Write\",\n      \"hooks\": [\n        {\n          \"type\": \"command\",\n          \"command\": \"\/var\/www\/tools\/ki-protokoll\/claude-hook\/srp_kiss_guard.py\",\n          \"timeout\": 5\n        }\n      ]\n    }\n  ],\n  \"PostToolUse\": [\n    {\n      \"matcher\": \"Write|Edit\",\n      \"hooks\": [\n        {\n          \"type\": \"command\",\n          \"command\": \"\/var\/www\/tools\/ki-protokoll\/claude-hook\/srp_kiss_metrics.py\",\n          \"timeout\": 5\n        }\n      ]\n    }\n  ]\n}\n```\n\n---\n\n# Prüfung 2: MVC + CRUD\n\n## Ziel\n\n**MVC:** Klare Rollentrennung.\n- Controller: Orchestrierung (nimmt Request, ruft UseCase, gibt Response)\n- View: Darstellung (keine Logik)\n- Model: Geschäftslogik (Domain + UseCases)\n\n**CRUD:** Persistenz-Disziplin.\n- Create\/Read\/Update\/Delete gehören in Repository\n- Transaktionen gehören in UseCase oder Repository, nicht Controller\n- Controller orchestriert, persistiert nicht\n\n## Grundsatz\n\nMVC ist strukturell. Die Rollen sind durch Pfade definiert:\n- `\/Controller\/` = Presentation Layer\n- `\/View\/`, `\/templates\/` = Templates\n- `\/Domain\/` = Business Logic\n- `\/UseCases\/`, `\/Application\/` = Application Logic\n- `\/Infrastructure\/Persistence\/` = Repositories\n\n## Abgrenzung zu anderen Prüfungen\n\n| Aspekt | Prüfung | Begründung |\n|--------|---------|------------|\n| Klassen-Größe | 1 (SRP) | Strukturelle Metrik |\n| Methoden-Anzahl | 1 (SRP) | Strukturelle Metrik |\n| Abhängigkeits-Richtung | 6 (DIP) | Dependency Inversion |\n| **Rollen-Einhaltung** | **2 (MVC)** | Controller\/View\/Model Trennung |\n| **Persistenz-Ort** | **2 (CRUD)** | Wer darf DB anfassen |\n\n---\n\n## Pre-Hook (BLOCK)\n\n### P2.1: Keine SQL-Statements in Controller\n\n**Regel:** Controller dürfen keine direkten SQL-Queries enthalten.\n\n**Scope:** `\/Controller\/`\n\n**Pattern:**\n```python\nsql_patterns = [\n    r\"\\bSELECT\\s+.+\\s+FROM\\b\",\n    r\"\\bINSERT\\s+INTO\\b\",\n    r\"\\bUPDATE\\s+\\w+\\s+SET\\b\",\n    r\"\\bDELETE\\s+FROM\\b\",\n]\n```\n\n**Warum diese Pattern:**\n- Spezifisch genug um False Positives zu vermeiden\n- `SELECT your country` matcht nicht (kein FROM)\n- `INSERT INTO users` matcht (eindeutig SQL)\n\n**Unterschied zu Prüfung 1\/6:**\n- H-Regeln prüfen Objekte (new Repository, DatabaseFactory)\n- P2.1 prüft String-Literale (SQL-Statements)\n\n---\n\n### P2.2: Keine Transaktionen in Controller\n\n**Regel:** Transaktions-Kontrolle gehört nicht in Controller.\n\n**Scope:** `\/Controller\/`\n\n**Pattern:**\n```python\ntransaction_patterns = [\n    r\"\\bbeginTransaction\\s*\\(\",\n    r\"\\bcommit\\s*\\(\",\n    r\"\\brollBack\\s*\\(\",\n    r\"\\brollback\\s*\\(\",\n]\n```\n\n**Begründung:**\n- Transaktionen sind Infrastruktur-Concern\n- Controller soll Request→Response orchestrieren, nicht DB-Konsistenz managen\n- Transaktionen gehören in UseCase oder Repository\n\n---\n\n### P2.3: Kein direktes Rendering in Controller\n\n**Regel:** Controller erzeugen keine direkte Ausgabe.\n\n**Scope:** `\/Controller\/`\n\n**Pattern:**\n```python\noutput_patterns = [\n    r\"\\becho\\s\",\n    r\"\\becho\\(\",\n    r\"\\bprint\\s\",\n    r\"\\bprint\\(\",\n]\n```\n\n**Nicht blockiert:**\n- `print_r` (Debug)\n- `printf` (Formatierung, selten)\n- `return json_encode()` (Response-Objekt)\n\n**Begründung:**\n- Output gehört in View oder Response-Objekt\n- echo\/print in Controller = MVC-Verletzung\n- CLI-Code gehört in `\/Console\/` oder `\/Command\/`, nicht `\/Controller\/`\n\n---\n\n### P2.4: Keine DB-Artefakte in UseCases\n\n**Regel:** UseCases nutzen Repository-Interfaces, nicht direkte DB.\n\n**Scope:** `\/UseC... [TRUNCATED-f729f390425a4edc]",
        "structuredPatch": [
            {
                "oldStart": 538,
                "oldLines": 6,
                "newStart": 538,
                "newLines": 332,
                "lines": [
                    " ",
                    " ---",
                    " ",
                    "-# Prüfung 3-6",
                    "+# Prüfung 3: PSR + Types",
                    " ",
                    "-*Werden nach Abnahme von Prüfung 2 definiert.*",
                    "+## Ziel",
                    "+",
                    "+**PSR (PHP Standards Recommendations):**",
                    "+- PSR-1: Basic Coding Standard",
                    "+- PSR-4: Autoloading (Namespace = Pfad)",
                    "+- PSR-12: Extended Coding Style",
                    "+",
                    "+**Types (Typdisziplin):**",
                    "+- `declare(strict_types=1)` in jeder Datei",
                    "+- Typ-Deklarationen für Parameter",
                    "+- Return-Type-Deklarationen",
                    "+- Property-Types (PHP 7.4+)",
                    "+",
                    "+## Warum vor SOLID\/DIP?",
                    "+",
                    "+DIP (Dependency Inversion) basiert auf Interfaces.",
                    "+Interfaces ohne Typen sind wirkungslos.",
                    "+Beispiel:",
                    "+",
                    "+```php",
                    "+\/\/ Ohne Types: Interface ist Dekoration",
                    "+interface UserRepository {",
                    "+    public function find($id);  \/\/ Was ist $id? Was kommt zurück?",
                    "+}",
                    "+",
                    "+\/\/ Mit Types: Interface ist Contract",
                    "+interface UserRepository {",
                    "+    public function find(int $id): ?User;",
                    "+}",
                    "+```",
                    "+",
                    "+**Types vor DIP ist zwingend.**",
                    "+",
                    "+## Grundsatz",
                    "+",
                    "+PSR und Types sind vollständig deterministisch prüfbar.",
                    "+Kein semantischer Spielraum. Kein Kontext erforderlich.",
                    "+Daher: Alle Regeln sind Pre-Hook (BLOCK).",
                    "+",
                    "+---",
                    "+",
                    "+## Pre-Hook (BLOCK)",
                    "+",
                    "+### P3.1: strict_types erforderlich",
                    "+",
                    "+**Regel:** Jede PHP-Datei muss `declare(strict_types=1)` haben.",
                    "+",
                    "+**Pattern:**",
                    "+```python",
                    "+strict_types_pattern = r\"declare\\s*\\(\\s*strict_types\\s*=\\s*1\\s*\\)\\s*;\"",
                    "+if not re.search(strict_types_pattern, content):",
                    "+    block(\"Missing declare(strict_types=1)\")",
                    "+```",
                    "+",
                    "+**Position:** Muss in den ersten 5 Zeilen sein (nach <?php, vor namespace).",
                    "+",
                    "+**Begründung:**",
                    "+- Ohne strict_types ist PHP-Typisierung wirkungslos",
                    "+- Type Coercion versteckt Bugs",
                    "+- Grundlage für alle weiteren Type-Prüfungen",
                    "+",
                    "+---",
                    "+",
                    "+### P3.2: Namespace muss Pfad entsprechen (PSR-4)",
                    "+",
                    "+**Regel:** Der Namespace muss dem Dateipfad entsprechen.",
                    "+",
                    "+**Mapping:**",
                    "+```python",
                    "+path_to_namespace = {",
                    "+    \"\/src\/Controller\/\": \"Controller\\\\\",",
                    "+    \"\/src\/Domain\/\": \"Domain\\\\\",",
                    "+    \"\/src\/UseCases\/\": \"UseCases\\\\\",",
                    "+    \"\/src\/Application\/\": \"Application\\\\\",",
                    "+    \"\/src\/Infrastructure\/\": \"Infrastructure\\\\\",",
                    "+    \"\/src\/Framework\/\": \"Framework\\\\\",",
                    "+    \"\/app\/\": \"App\\\\\",",
                    "+}",
                    "+```",
                    "+",
                    "+**Prüfung:**",
                    "+```python",
                    "+namespace_match = re.search(r\"namespace\\s+([^;]+);\", content)",
                    "+if namespace_match:",
                    "+    declared_namespace = namespace_match.group(1)",
                    "+    expected_prefix = get_expected_prefix(file_path)",
                    "+    if not declared_namespace.startswith(expected_prefix):",
                    "+        block(f\"Namespace '{declared_namespace}' does not match path. Expected: '{expected_prefix}...'\")",
                    "+```",
                    "+",
                    "+**Begründung:**",
                    "+- PSR-4 Autoloading erfordert Pfad-Namespace-Konsistenz",
                    "+- Inkonsistenz führt zu Autoload-Fehlern",
                    "+- IDE-Navigation bricht",
                    "+",
                    "+---",
                    "+",
                    "+### P3.3: Klassenname muss Dateiname entsprechen (PSR-1)",
                    "+",
                    "+**Regel:** Der Klassenname muss dem Dateinamen entsprechen.",
                    "+",
                    "+**Prüfung:**",
                    "+```python",
                    "+class_match = re.search(r\"(?:class|interface|trait|enum)\\s+(\\w+)\", content)",
                    "+if class_match:",
                    "+    class_name = class_match.group(1)",
                    "+    expected_name = Path(file_path).stem  # Filename without .php",
                    "+    if class_name != expected_name:",
                    "+        block(f\"Class '{class_name}' does not match filename '{expected_name}'\")",
                    "+```",
                    "+",
                    "+**Begründung:**",
                    "+- PSR-1 Standard",
                    "+- Eine Klasse pro Datei",
                    "+- Dateiname = Klassenname",
                    "+",
                    "+---",
                    "+",
                    "+### P3.4: Public Methods müssen Return-Type haben",
                    "+",
                    "+**Regel:** Jede public function muss einen Return-Type deklarieren.",
                    "+",
                    "+**Pattern:**",
                    "+```python",
                    "+# Findet public functions ohne Return-Type",
                    "+public_without_return = re.findall(",
                    "+    r\"public\\s+function\\s+\\w+\\s*\\([^)]*\\)\\s*(?!:)\\s*\\{\",",
                    "+    content",
                    "+)",
                    "+if public_without_return:",
                    "+    block(f\"Public method(s) without return type declaration\")",
                    "+```",
                    "+",
                    "+**Ausnahmen:**",
                    "+- `__construct` (kein Return-Type erlaubt)",
                    "+- `__destruct` (kein Return-Type erlaubt)",
                    "+",
                    "+**Verfeinertes Pattern:**",
                    "+```python",
                    "+public_methods = re.findall(",
                    "+    r\"public\\s+function\\s+(\\w+)\\s*\\([^)]*\\)\\s*(?::\\s*\\S+)?\\s*\\{\",",
                    "+    content",
                    "+)",
                    "+for method in public_methods:",
                    "+    if method.startswith(\"__\"):",
                    "+        continue  # Magic methods",
                    "+    # Check if has return type",
                    "+    method_pattern = rf\"public\\s+function\\s+{method}\\s*\\([^)]*\\)\\s*:\\s*\\S+\"",
                    "+    if not re.search(method_pattern, content):",
                    "+        block(f\"Public method '{method}' missing return type\")",
                    "+```",
                    "+",
                    "+**Begründung:**",
                    "+- Public API muss typisiert sein",
                    "+- Aufrufer brauchen Contract",
                    "+- IDE-Support und statische Analyse",
                    "+",
                    "+---",
                    "+",
                    "+### P3.5: Public Methods müssen typisierte Parameter haben",
                    "+",
                    "+**Regel:** Jeder Parameter einer public function muss typisiert sein.",
                    "+",
                    "+**Prüfung:**",
                    "+```python",
                    "+# Findet untypisierte Parameter: $param ohne Type davor",
                    "+untyped_params = re.findall(",
                    "+    r\"public\\s+function\\s+\\w+\\s*\\([^)]*(?<![?\\w])\\$\\w+[^)]*\\)\",",
                    "+    content",
                    "+)",
                    "+```",
                    "+",
                    "+**Komplexität:** Regex für Parameter-Parsing ist fehleranfällig.",
                    "+",
                    "+**Alternative Prüfung (einfacher):**",
                    "+```python",
                    "+# Suche nach Parametern ohne Type-Hint",
                    "+# Pattern: ( oder , gefolgt von $name ohne Type davor",
                    "+untyped = re.findall(r\"[(,]\\s*\\$\\w+\", content)",
                    "+```",
                    "+",
                    "+**Problem:** Das matcht auch typisierte Parameter falsch.",
                    "+",
                    "+**Entscheidung:** Diese Prüfung ist ohne AST nicht zuverlässig.",
                    "+",
                    "+**YAGNI-Korrektur:** Streichen oder vereinfachen auf:",
                    "+- PHPStan Level 6+ als externe Prüfung",
                    "+- Hier nur: Existenz von Type-Hints prüfen (heuristisch)",
                    "+",
                    "+**Vereinfachte Regel:**",
                    "+```python",
                    "+# Warnung wenn viele untypisierte Parameter erkannt werden",
                    "+# Keine Blockade, da False Positives möglich",
                    "+```",
                    "+",
                    "+**Entscheidung:** Zu Post-Hook verschieben als Warnung, nicht Block.",
                    "+",
                    "+---",
                    "+",
                    "+## Post-Hook (WARN)",
                    "+",
                    "+### W3.1: Untypisierte Parameter (heuristisch)",
                    "+",
                    "+**Regel:** Warnung wenn Parameter ohne Type-Hint erkannt werden.",
                    "+",
                    "+**Pattern:**",
                    "+```python",
                    "+# Heuristik: Suche nach $param direkt nach ( oder ,",
                    "+# ohne Type davor",
                    "+potential_untyped = re.findall(r\"function\\s+\\w+\\s*\\([^)]*[(,]\\s*\\$\", content)",
                    "+if potential_untyped:",
                    "+    warn(\"Potential untyped parameters detected. Run PHPStan for verification.\")",
                    "+```",
                    "+",
                    "+**Warum Warnung:**",
                    "+- Regex kann Type-Hints nicht zuverlässig erkennen",
                    "+- False Positives möglich",
                    "+- PHPStan ist das richtige Tool dafür",
                    "+",
                    "+---",
                    "+",
                    "+### W3.2: Nullable ohne expliziten Null-Check",
                    "+",
                    "+**Regel:** Warnung wenn nullable Types ohne Null-Handling verwendet werden.",
                    "+",
                    "+**Problem:** Ohne AST nicht prüfbar.",
                    "+",
                    "+**Entscheidung:** Streichen. PHPStan macht das besser.",
                    "+",
                    "+---",
                    "+",
                    "+### W3.3: Mixed Type verwendet",
                    "+",
                    "+**Regel:** Warnung bei Verwendung von `mixed` Type.",
                    "+",
                    "+**Pattern:**",
                    "+```python",
                    "+if re.search(r\":\\s*mixed\\b\", content):",
                    "+    warn(\"'mixed' type used. Consider more specific type.\")",
                    "+```",
                    "+",
                    "+**Begründung:**",
                    "+- `mixed` ist Type-Escape",
                    "+- Sollte vermieden werden",
                    "+- Aber: Manchmal legitim (z.B. JSON-Handling)",
                    "+",
                    "+---",
                    "+",
                    "+## Gestrichen",
                    "+",
                    "+### Parameter-Typ-Prüfung als Block",
                    "+",
                    "+**Ursprüngliche Idee:** Blockieren wenn Parameter ohne Type.",
                    "+",
                    "+**Warum gestrichen:**",
                    "+- Regex-Parsing von PHP-Parametern ist fehleranfällig",
                    "+- Default-Values, Variadic, Nullable machen Pattern komplex",
                    "+- PHPStan ist das richtige Tool",
                    "+- YAGNI: Nicht duplizieren was PHPStan besser kann",
                    "+",
                    "+**Alternative:** PHPStan als Quality-Gate (externes Tool, nicht Hook)",
                    "+",
                    "+### Nullable-Handling-Prüfung",
                    "+",
                    "+**Warum gestrichen:**",
                    "+- Kontrollfluss-Analyse ohne AST unmöglich",
                    "+- PHPStan macht das",
                    "+",
                    "+---",
                    "+",
                    "+## Zusammenfassung",
                    "+",
                    "+### Pre-Hook",
                    "+",
                    "+| ID | Prüfung | Aktion |",
                    "+|----|---------|--------|",
                    "+| P3.1 | strict_types fehlt | BLOCK |",
                    "+| P3.2 | Namespace ≠ Pfad | BLOCK |",
                    "+| P3.3 | Klassenname ≠ Dateiname | BLOCK |",
                    "+| P3.4 | Public Method ohne Return-Type | BLOCK |",
                    "+",
                    "+### Post-Hook",
                    "+",
                    "+| ID | Prüfung | Aktion |",
                    "+|----|---------|--------|",
                    "+| W3.1 | Potentiell untypisierte Parameter | WARN |",
                    "+| W3.3 | `mixed` Type verwendet | WARN |",
                    "+",
                    "+### Allowlist",
                    "+",
                    "+| Pfad | Grund |",
                    "+|------|-------|",
                    "+| \/vendor\/ | Third-party |",
                    "+| \/tests\/ | Test-Code darf lockerer sein |",
                    "+| \/Test\/ | PHPUnit |",
                    "+",
                    "+---",
                    "+",
                    "+## Selbstkritik und Entscheidungen",
                    "+",
                    "+| Frage | Entscheidung | Begründung |",
                    "+|-------|--------------|------------|",
                    "+| Warum nicht alle Parameter prüfen? | Zu komplex für Regex | PHPStan ist besser, YAGNI |",
                    "+| Warum strict_types als Block? | Absolut deterministisch | Entweder da oder nicht |",
                    "+| Warum mixed nur Warnung? | Manchmal legitim | JSON, externe APIs |",
                    "+| Fehlt Property-Type-Prüfung? | Ja, bewusst | Regex kann Visibility nicht sicher parsen |",
                    "+",
                    "+---",
                    "+",
                    "+## Abgrenzung zu externen Tools",
                    "+",
                    "+| Prüfung | Hook | PHPStan |",
                    "+|---------|------|---------|",
                    "+| strict_types | ✓ | ✓ |",
                    "+| Namespace = Pfad | ✓ | ✓ |",
                    "+| Return Types | ✓ | ✓ |",
                    "+| Parameter Types | ~ (Warnung) | ✓ (exakt) |",
                    "+| Null-Safety | ✗ | ✓ |",
                    "+| Type Inference | ✗ | ✓ |",
                    "+",
                    "+**Fazit:** Hooks prüfen das Offensichtliche. PHPStan prüft das Komplexe.",
                    "+",
                    "+---",
                    "+",
                    "+# Prüfung 4-6",
                    "+",
                    "+*Werden nach Abnahme von Prüfung 3 definiert.*"
                ]
            }
        ],
        "userModified": false,
        "replaceAll": false
    }
}
← Vorheriger Zur Liste Nächster →