Protokoll #15297

ID15297
Zeitstempel2025-12-25 16:14:05.954077
Clientroot
IP145.224.72.140
Modellclaude-sonnet-4-20250514
Statuscompleted
Tokens12,697 (Input: 2,645, Output: 10,052)
Dauer110 ms
Request-Zeit2025-12-25 16:14:05.954077
Response-Zeit2025-12-25 16:14:06.063618

Request

{
    "event": "PreToolUse",
    "tool_name": "Write",
    "tool_input": {
        "file_path": "\/var\/www\/dev.campus.systemische-tools.de\/src\/Infrastructure\/CodeAnalysis\/CodeQualityChecker.php",
        "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Infrastructure\\CodeAnalysis;\n\n\/\/ @responsibility: Code-Hygiene-Analyse (Score 0-100, normalisiert, gewichtet)\n\nfinal class CodeQualityChecker\n{\n    \/** @var array<string, string> Regex patterns for secret detection *\/\n    private const SECRET_PATTERNS = [\n        'password' => '\/[\"\\'](?:password|passwd|pwd)[\"\\']\\\\s*[=:]\\\\s*[\"\\'][^\"\\']{3,}[\"\\']\/i',\n        'api_key' => '\/[\"\\'](?:api[_-]?key|apikey|secret[_-]?key)[\"\\']\\\\s*[=:]\\\\s*[\"\\'][^\"\\']{8,}[\"\\']\/i',\n        'token' => '\/[\"\\'](?:token|auth[_-]?token|access[_-]?token)[\"\\']\\\\s*[=:]\\\\s*[\"\\'][^\"\\']{8,}[\"\\']\/i',\n        'url_with_creds' => '\/https?:\\\\\/\\\\\/[^:]+:[^@]+@\/i',\n    ];\n\n    \/** @var array<string, string> Regex patterns for magic number detection *\/\n    private const MAGIC_NUMBER_PATTERNS = [\n        'magic_number' => '\/(?<![\\\\w])(?:100|1000|60|24|365|3600|86400)(?![\\\\w])\/',\n    ];\n\n    \/** @var array<string, array{optimal: int, max: int}> Default hygiene thresholds *\/\n    private const HYGIENE_DEFAULTS = [\n        'loc' => ['optimal' => 200, 'max' => 500],\n        'methods' => ['optimal' => 10, 'max' => 20],\n        'classes' => ['optimal' => 1, 'max' => 3],\n        'dependencies' => ['optimal' => 5, 'max' => 15],\n        'secrets' => ['optimal' => 0, 'max' => 1],\n        'magic_numbers' => ['optimal' => 0, 'max' => 10],\n    ];\n\n    \/** @var array<string, array<string, array{optimal: int, max: int}>> File type specific modifiers *\/\n    private const FILE_TYPE_MODIFIERS = [\n        'Controller' => [\n            'loc' => ['optimal' => 300, 'max' => 500],\n            'methods' => ['optimal' => 15, 'max' => 25],\n        ],\n        'Entity' => [\n            'loc' => ['optimal' => 100, 'max' => 300],\n            'methods' => ['optimal' => 20, 'max' => 30],\n        ],\n        'Repository' => [\n            'loc' => ['optimal' => 250, 'max' => 400],\n            'dependencies' => ['optimal' => 3, 'max' => 8],\n        ],\n        'Service' => [\n            'loc' => ['optimal' => 150, 'max' => 350],\n        ],\n        'UseCase' => [\n            'loc' => ['optimal' => 150, 'max' => 350],\n        ],\n    ];\n\n    \/** @var array<string, float> Factor weights (must sum to 1.0) *\/\n    private const WEIGHTS = [\n        'dependencies' => 0.25,\n        'loc' => 0.20,\n        'methods' => 0.20,\n        'secrets' => 0.15,\n        'classes' => 0.10,\n        'magic_numbers' => 0.10,\n    ];\n\n    \/** @var array<string, array{min: int, max: int}> Hygiene status ranges *\/\n    private const HYGIENE_STATUS = [\n        'Sauber' => ['min' => 80, 'max' => 100],\n        'Gepflegt' => ['min' => 60, 'max' => 79],\n        'Vernachlässigt' => ['min' => 40, 'max' => 59],\n        'Verschmutzt' => ['min' => 20, 'max' => 39],\n        'Kritisch' => ['min' => 0, 'max' => 19],\n    ];\n\n    \/**\n     * Analysiert eine Datei und berechnet den Code Hygiene Score.\n     *\n     * @param array<string, mixed> $analysisData Daten aus code_analysis\n     * @return array{\n     *     hygiene_score: int,\n     *     hygiene_status: string,\n     *     factor_scores: array<string, int>,\n     *     issues_count: int,\n     *     warnings_count: int,\n     *     issues_json: string\n     * }\n     *\/\n    public function analyze(array $analysisData): array\n    {\n        $issues = [];\n        $filePath = $analysisData['file_path'] ?? '';\n        $content = $analysisData['file_content'] ?? '';\n\n        \/\/ Determine file type for modifiers\n        $fileType = $this->detectFileType($filePath);\n        $thresholds = $this->getThresholdsForType($fileType);\n\n        \/\/ Extract metrics\n        $metrics = [\n            'loc' => (int) ($analysisData['line_count'] ?? 0),\n            'methods' => $this->countMethods($analysisData),\n            'classes' => (int) ($analysisData['class_count'] ?? 1),\n            'dependencies' => $this->countDependencies($analysisData),\n            'secrets' => 0,\n            'magic_numbers' => 0,\n        ];\n\n        \/\/ Detect secrets and magic numbers\n        $secretIssues = $this->detectSecrets($content, $filePath);\n        $metrics['secrets'] = count($secretIssues);\n        $issues = array_merge($issues, $secretIssues);\n\n        $magicIssues = $this->detectMagicNumbers($content, $filePath);\n        $metrics['magic_numbers'] = count($magicIssues);\n        $issues = array_merge($issues, $magicIssues);\n\n        \/\/ Add threshold violation issues\n        $issues = array_merge($issues, $this->detectThresholdViolations($metrics, $thresholds));\n\n        \/\/ Calculate normalized factor scores\n        $factorScores = [];\n        foreach (self::WEIGHTS as $factor => $weight) {\n            $factorScores[$factor] = $this->normalize(\n                $metrics[$factor],\n                $thresholds[$factor]['optimal'],\n                $thresholds[$factor]['max']\n            );\n        }\n\n        \/\/ Calculate weighted hygiene score\n        $hygieneScore = $this->calculateWeightedScore($factorScores);\n\n        \/\/ Apply hard fail for secrets\n        if ($metrics['secrets'] > 0) {\n            $hygieneScore = min($hygieneScore, 20);\n        }\n\n        \/\/ Determine hygiene status\n        $hygieneStatus = $this->determineStatus($hygieneScore);\n\n        \/\/ Count warnings\n        $warningsCount = count(array_filter($issues, fn ($i) => $i['severity'] === 'warning'));\n\n        return [\n            'hygiene_score' => $hygieneScore,\n            'hygiene_status' => $hygieneStatus,\n            'factor_scores' => $factorScores,\n            'issues_count' => count($issues),\n            'warnings_count' => $warningsCount,\n            'issues_json' => json_encode($issues, JSON_UNESCAPED_UNICODE),\n        ];\n    }\n\n    \/**\n     * Normalisiert einen Wert auf 0-100 Skala.\n     * Formel: max(0, 100 - ((value - optimal) \/ (max - optimal)) * 100)\n     *\/\n    private function normalize(int $value, int $optimal, int $max): int\n    {\n        if ($value <= $optimal) {\n            return 100;\n        }\n\n        if ($value >= $max) {\n            return 0;\n        }\n\n        $range = $max - $optimal;\n        if ($range === 0) {\n            return 0;\n        }\n\n        $normalized = 100 - (($value - $optimal) \/ $range) * 100;\n\n        return (int) max(0, min(100, $normalized));\n    }\n\n    \/**\n     * Berechnet den gewichteten Gesamtscore.\n     *\n     * @param array<string, int> $factorScores\n     *\/\n    private function calculateWeightedScore(array $factorScores): int\n    {\n        $weightedSum = 0.0;\n\n        foreach (self::WEIGHTS as $factor => $weight) {\n            $weightedSum += ($factorScores[$factor] ?? 0) * $weight;\n        }\n\n        return (int) round($weightedSum);\n    }\n\n    \/**\n     * Ermittelt den Dateityp aus dem Pfad.\n     *\/\n    private function detectFileType(string $filePath): ?string\n    {\n        $filename = basename($filePath);\n\n        foreach (array_keys(self::FILE_TYPE_MODIFIERS) as $type) {\n            if (str_contains($filename, $type)) {\n                return $type;\n            }\n        }\n\n        \/\/ Check directory\n        if (str_contains($filePath, '\/Controller\/')) {\n            return 'Controller';\n        }\n        if (str_contains($filePath, '\/Entity\/')) {\n            return 'Entity';\n        }\n        if (str_contains($filePath, '\/Repository\/') || str_contains($filePath, '\/Persistence\/')) {\n            return 'Repository';\n        }\n        if (str_contains($filePath, '\/Service\/')) {\n            return 'Service';\n        }\n        if (str_contains($filePath, '\/UseCase\/') || str_contains($filePath, '\/UseCases\/')) {\n            return 'UseCase';\n        }\n\n        return null;\n    }\n\n    \/**\n     * Gibt die Thresholds für einen Dateityp zurück.\n     *\n     * @return array<string, array{optimal: int, max: int}>\n     *\/\n    private function getThresholdsForType(?string $fileType): array\n    {\n        $thresholds = self::HYGIENE_DEFAULTS;\n\n        if ($fileType !== null && isset(self::FILE_TYPE_MODIFIERS[$fileType])) {\n            foreach (self::FILE_TYPE_MODIFIERS[$fileType] as $factor => $values) {\n                $thresholds[$factor] = $values;\n            }\n        }\n\n        return $thresholds;\n    }\n\n    \/**\n     * Zählt die Methoden aus den Analysedaten.\n     *\n     * @param array<string, mixed> $analysisData\n     *\/\n    private function countMethods(array $analysisData): int\n    {\n        $functions = json_decode($analysisData['functions'] ?? '[]', true);\n\n        return is_array($functions) ? count($functions) : 0;\n    }\n\n    \/**\n     * Zählt die Dependencies aus den Analysedaten.\n     *\n     * @param array<string, mixed> $analysisData\n     *\/\n    private function countDependencies(array $analysisData): int\n    {\n        $uses = json_decode($analysisData['uses'] ?? '[]', true);\n\n        return is_array($uses) ? count($uses) : 0;\n    }\n\n    \/**\n     * Erkennt Secrets im Code (Hard Fail).\n     *\n     * @return array<array{type: string, rule: string, message: string, severity: string, line?: int}>\n     *\/\n    private function detectSecrets(string $content, string $filePath): array\n    {\n        $issues = [];\n        $filename = basename($filePath);\n\n        \/\/ Skip config files\n        if (preg_match('\/^(config|\\.env|test|spec)\/i', $filename)) {\n            return [];\n        }\n\n        foreach (self::SECRET_PATTERNS as $type => $pattern) {\n            if (preg_match_all($pattern, $content, $matches, PREG_OFFSET_CAPTURE)) {\n                foreach ($matches[0] as $match) {\n                    $line = substr_count(substr($content, 0, $match[1]), \"\\n\") + 1;\n                    $issues[] = [\n                        'type' => 'secret',\n                        'rule' => \"hardcoded-{$type}\",\n                        'message' => \"KRITISCH: Mögliches hardcoded {$type} gefunden\",\n                        'severity' => 'critical',\n                        'line' => $line,\n                    ];\n                }\n            }\n        }\n\n        return $issues;\n    }\n\n    \/**\n     * Erkennt Magic Numbers im Code.\n     *\n     * @return array<array{type: string, rule: string, message: str... [TRUNCATED-a42357cb98152efe]"
    }
}

Response

{
    "tool_response": {
        "type": "update",
        "filePath": "\/var\/www\/dev.campus.systemische-tools.de\/src\/Infrastructure\/CodeAnalysis\/CodeQualityChecker.php",
        "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Infrastructure\\CodeAnalysis;\n\n\/\/ @responsibility: Code-Hygiene-Analyse (Score 0-100, normalisiert, gewichtet)\n\nfinal class CodeQualityChecker\n{\n    \/** @var array<string, string> Regex patterns for secret detection *\/\n    private const SECRET_PATTERNS = [\n        'password' => '\/[\"\\'](?:password|passwd|pwd)[\"\\']\\\\s*[=:]\\\\s*[\"\\'][^\"\\']{3,}[\"\\']\/i',\n        'api_key' => '\/[\"\\'](?:api[_-]?key|apikey|secret[_-]?key)[\"\\']\\\\s*[=:]\\\\s*[\"\\'][^\"\\']{8,}[\"\\']\/i',\n        'token' => '\/[\"\\'](?:token|auth[_-]?token|access[_-]?token)[\"\\']\\\\s*[=:]\\\\s*[\"\\'][^\"\\']{8,}[\"\\']\/i',\n        'url_with_creds' => '\/https?:\\\\\/\\\\\/[^:]+:[^@]+@\/i',\n    ];\n\n    \/** @var array<string, string> Regex patterns for magic number detection *\/\n    private const MAGIC_NUMBER_PATTERNS = [\n        'magic_number' => '\/(?<![\\\\w])(?:100|1000|60|24|365|3600|86400)(?![\\\\w])\/',\n    ];\n\n    \/** @var array<string, array{optimal: int, max: int}> Default hygiene thresholds *\/\n    private const HYGIENE_DEFAULTS = [\n        'loc' => ['optimal' => 200, 'max' => 500],\n        'methods' => ['optimal' => 10, 'max' => 20],\n        'classes' => ['optimal' => 1, 'max' => 3],\n        'dependencies' => ['optimal' => 5, 'max' => 15],\n        'secrets' => ['optimal' => 0, 'max' => 1],\n        'magic_numbers' => ['optimal' => 0, 'max' => 10],\n    ];\n\n    \/** @var array<string, array<string, array{optimal: int, max: int}>> File type specific modifiers *\/\n    private const FILE_TYPE_MODIFIERS = [\n        'Controller' => [\n            'loc' => ['optimal' => 300, 'max' => 500],\n            'methods' => ['optimal' => 15, 'max' => 25],\n        ],\n        'Entity' => [\n            'loc' => ['optimal' => 100, 'max' => 300],\n            'methods' => ['optimal' => 20, 'max' => 30],\n        ],\n        'Repository' => [\n            'loc' => ['optimal' => 250, 'max' => 400],\n            'dependencies' => ['optimal' => 3, 'max' => 8],\n        ],\n        'Service' => [\n            'loc' => ['optimal' => 150, 'max' => 350],\n        ],\n        'UseCase' => [\n            'loc' => ['optimal' => 150, 'max' => 350],\n        ],\n    ];\n\n    \/** @var array<string, float> Factor weights (must sum to 1.0) *\/\n    private const WEIGHTS = [\n        'dependencies' => 0.25,\n        'loc' => 0.20,\n        'methods' => 0.20,\n        'secrets' => 0.15,\n        'classes' => 0.10,\n        'magic_numbers' => 0.10,\n    ];\n\n    \/** @var array<string, array{min: int, max: int}> Hygiene status ranges *\/\n    private const HYGIENE_STATUS = [\n        'Sauber' => ['min' => 80, 'max' => 100],\n        'Gepflegt' => ['min' => 60, 'max' => 79],\n        'Vernachlässigt' => ['min' => 40, 'max' => 59],\n        'Verschmutzt' => ['min' => 20, 'max' => 39],\n        'Kritisch' => ['min' => 0, 'max' => 19],\n    ];\n\n    \/**\n     * Analysiert eine Datei und berechnet den Code Hygiene Score.\n     *\n     * @param array<string, mixed> $analysisData Daten aus code_analysis\n     * @return array{\n     *     hygiene_score: int,\n     *     hygiene_status: string,\n     *     factor_scores: array<string, int>,\n     *     issues_count: int,\n     *     warnings_count: int,\n     *     issues_json: string\n     * }\n     *\/\n    public function analyze(array $analysisData): array\n    {\n        $issues = [];\n        $filePath = $analysisData['file_path'] ?? '';\n        $content = $analysisData['file_content'] ?? '';\n\n        \/\/ Determine file type for modifiers\n        $fileType = $this->detectFileType($filePath);\n        $thresholds = $this->getThresholdsForType($fileType);\n\n        \/\/ Extract metrics\n        $metrics = [\n            'loc' => (int) ($analysisData['line_count'] ?? 0),\n            'methods' => $this->countMethods($analysisData),\n            'classes' => (int) ($analysisData['class_count'] ?? 1),\n            'dependencies' => $this->countDependencies($analysisData),\n            'secrets' => 0,\n            'magic_numbers' => 0,\n        ];\n\n        \/\/ Detect secrets and magic numbers\n        $secretIssues = $this->detectSecrets($content, $filePath);\n        $metrics['secrets'] = count($secretIssues);\n        $issues = array_merge($issues, $secretIssues);\n\n        $magicIssues = $this->detectMagicNumbers($content, $filePath);\n        $metrics['magic_numbers'] = count($magicIssues);\n        $issues = array_merge($issues, $magicIssues);\n\n        \/\/ Add threshold violation issues\n        $issues = array_merge($issues, $this->detectThresholdViolations($metrics, $thresholds));\n\n        \/\/ Calculate normalized factor scores\n        $factorScores = [];\n        foreach (self::WEIGHTS as $factor => $weight) {\n            $factorScores[$factor] = $this->normalize(\n                $metrics[$factor],\n                $thresholds[$factor]['optimal'],\n                $thresholds[$factor]['max']\n            );\n        }\n\n        \/\/ Calculate weighted hygiene score\n        $hygieneScore = $this->calculateWeightedScore($factorScores);\n\n        \/\/ Apply hard fail for secrets\n        if ($metrics['secrets'] > 0) {\n            $hygieneScore = min($hygieneScore, 20);\n        }\n\n        \/\/ Determine hygiene status\n        $hygieneStatus = $this->determineStatus($hygieneScore);\n\n        \/\/ Count warnings\n        $warningsCount = count(array_filter($issues, fn ($i) => $i['severity'] === 'warning'));\n\n        return [\n            'hygiene_score' => $hygieneScore,\n            'hygiene_status' => $hygieneStatus,\n            'factor_scores' => $factorScores,\n            'issues_count' => count($issues),\n            'warnings_count' => $warningsCount,\n            'issues_json' => json_encode($issues, JSON_UNESCAPED_UNICODE),\n        ];\n    }\n\n    \/**\n     * Normalisiert einen Wert auf 0-100 Skala.\n     * Formel: max(0, 100 - ((value - optimal) \/ (max - optimal)) * 100)\n     *\/\n    private function normalize(int $value, int $optimal, int $max): int\n    {\n        if ($value <= $optimal) {\n            return 100;\n        }\n\n        if ($value >= $max) {\n            return 0;\n        }\n\n        $range = $max - $optimal;\n        if ($range === 0) {\n            return 0;\n        }\n\n        $normalized = 100 - (($value - $optimal) \/ $range) * 100;\n\n        return (int) max(0, min(100, $normalized));\n    }\n\n    \/**\n     * Berechnet den gewichteten Gesamtscore.\n     *\n     * @param array<string, int> $factorScores\n     *\/\n    private function calculateWeightedScore(array $factorScores): int\n    {\n        $weightedSum = 0.0;\n\n        foreach (self::WEIGHTS as $factor => $weight) {\n            $weightedSum += ($factorScores[$factor] ?? 0) * $weight;\n        }\n\n        return (int) round($weightedSum);\n    }\n\n    \/**\n     * Ermittelt den Dateityp aus dem Pfad.\n     *\/\n    private function detectFileType(string $filePath): ?string\n    {\n        $filename = basename($filePath);\n\n        foreach (array_keys(self::FILE_TYPE_MODIFIERS) as $type) {\n            if (str_contains($filename, $type)) {\n                return $type;\n            }\n        }\n\n        \/\/ Check directory\n        if (str_contains($filePath, '\/Controller\/')) {\n            return 'Controller';\n        }\n        if (str_contains($filePath, '\/Entity\/')) {\n            return 'Entity';\n        }\n        if (str_contains($filePath, '\/Repository\/') || str_contains($filePath, '\/Persistence\/')) {\n            return 'Repository';\n        }\n        if (str_contains($filePath, '\/Service\/')) {\n            return 'Service';\n        }\n        if (str_contains($filePath, '\/UseCase\/') || str_contains($filePath, '\/UseCases\/')) {\n            return 'UseCase';\n        }\n\n        return null;\n    }\n\n    \/**\n     * Gibt die Thresholds für einen Dateityp zurück.\n     *\n     * @return array<string, array{optimal: int, max: int}>\n     *\/\n    private function getThresholdsForType(?string $fileType): array\n    {\n        $thresholds = self::HYGIENE_DEFAULTS;\n\n        if ($fileType !== null && isset(self::FILE_TYPE_MODIFIERS[$fileType])) {\n            foreach (self::FILE_TYPE_MODIFIERS[$fileType] as $factor => $values) {\n                $thresholds[$factor] = $values;\n            }\n        }\n\n        return $thresholds;\n    }\n\n    \/**\n     * Zählt die Methoden aus den Analysedaten.\n     *\n     * @param array<string, mixed> $analysisData\n     *\/\n    private function countMethods(array $analysisData): int\n    {\n        $functions = json_decode($analysisData['functions'] ?? '[]', true);\n\n        return is_array($functions) ? count($functions) : 0;\n    }\n\n    \/**\n     * Zählt die Dependencies aus den Analysedaten.\n     *\n     * @param array<string, mixed> $analysisData\n     *\/\n    private function countDependencies(array $analysisData): int\n    {\n        $uses = json_decode($analysisData['uses'] ?? '[]', true);\n\n        return is_array($uses) ? count($uses) : 0;\n    }\n\n    \/**\n     * Erkennt Secrets im Code (Hard Fail).\n     *\n     * @return array<array{type: string, rule: string, message: string, severity: string, line?: int}>\n     *\/\n    private function detectSecrets(string $content, string $filePath): array\n    {\n        $issues = [];\n        $filename = basename($filePath);\n\n        \/\/ Skip config files\n        if (preg_match('\/^(config|\\.env|test|spec)\/i', $filename)) {\n            return [];\n        }\n\n        foreach (self::SECRET_PATTERNS as $type => $pattern) {\n            if (preg_match_all($pattern, $content, $matches, PREG_OFFSET_CAPTURE)) {\n                foreach ($matches[0] as $match) {\n                    $line = substr_count(substr($content, 0, $match[1]), \"\\n\") + 1;\n                    $issues[] = [\n                        'type' => 'secret',\n                        'rule' => \"hardcoded-{$type}\",\n                        'message' => \"KRITISCH: Mögliches hardcoded {$type} gefunden\",\n                        'severity' => 'critical',\n                        'line' => $line,\n                    ];\n                }\n            }\n        }\n\n        return $issues;\n    }\n\n    \/**\n     * Erkennt Magic Numbers im Code.\n     *\n     * @return array<array{type: string, rule: string, message: str... [TRUNCATED-a42357cb98152efe]",
        "structuredPatch": [
            {
                "oldStart": 4,
                "oldLines": 210,
                "newStart": 4,
                "newLines": 324,
                "lines": [
                    " ",
                    " namespace Infrastructure\\CodeAnalysis;",
                    " ",
                    "-\/\/ @responsibility: Code-Qualitätsanalyse (Hardcoded, Complexity, Metrics)",
                    "+\/\/ @responsibility: Code-Hygiene-Analyse (Score 0-100, normalisiert, gewichtet)",
                    " ",
                    " final class CodeQualityChecker",
                    " {",
                    "-    \/** @var array<string, string> Regex patterns for hardcoded detection *\/",
                    "-    private const HARDCODED_PATTERNS = [",
                    "+    \/** @var array<string, string> Regex patterns for secret detection *\/",
                    "+    private const SECRET_PATTERNS = [",
                    "         'password' => '\/[\"\\'](?:password|passwd|pwd)[\"\\']\\\\s*[=:]\\\\s*[\"\\'][^\"\\']{3,}[\"\\']\/i',",
                    "         'api_key' => '\/[\"\\'](?:api[_-]?key|apikey|secret[_-]?key)[\"\\']\\\\s*[=:]\\\\s*[\"\\'][^\"\\']{8,}[\"\\']\/i',",
                    "         'token' => '\/[\"\\'](?:token|auth[_-]?token|access[_-]?token)[\"\\']\\\\s*[=:]\\\\s*[\"\\'][^\"\\']{8,}[\"\\']\/i',",
                    "-        'ip_address' => '\/[\"\\']\\\\d{1,3}\\\\.\\\\d{1,3}\\\\.\\\\d{1,3}\\\\.\\\\d{1,3}[\"\\']\/i',",
                    "         'url_with_creds' => '\/https?:\\\\\/\\\\\/[^:]+:[^@]+@\/i',",
                    "+    ];",
                    "+",
                    "+    \/** @var array<string, string> Regex patterns for magic number detection *\/",
                    "+    private const MAGIC_NUMBER_PATTERNS = [",
                    "         'magic_number' => '\/(?<![\\\\w])(?:100|1000|60|24|365|3600|86400)(?![\\\\w])\/',",
                    "     ];",
                    " ",
                    "-    \/** @var int Minimum score for worst quality *\/",
                    "-    private const SCORE_MIN = 20;",
                    "+    \/** @var array<string, array{optimal: int, max: int}> Default hygiene thresholds *\/",
                    "+    private const HYGIENE_DEFAULTS = [",
                    "+        'loc' => ['optimal' => 200, 'max' => 500],",
                    "+        'methods' => ['optimal' => 10, 'max' => 20],",
                    "+        'classes' => ['optimal' => 1, 'max' => 3],",
                    "+        'dependencies' => ['optimal' => 5, 'max' => 15],",
                    "+        'secrets' => ['optimal' => 0, 'max' => 1],",
                    "+        'magic_numbers' => ['optimal' => 0, 'max' => 10],",
                    "+    ];",
                    " ",
                    "-    \/** @var array<string, int> Thresholds for scoring *\/",
                    "-    private const THRESHOLDS = [",
                    "-        'loc_excellent' => 100,",
                    "-        'loc_good' => 200,",
                    "-        'loc_acceptable' => 400,",
                    "-        'loc_poor' => 600,",
                    "-        'methods_excellent' => 5,",
                    "-        'methods_good' => 10,",
                    "-        'methods_acceptable' => 15,",
                    "-        'methods_poor' => 20,",
                    "-        'deps_excellent' => 3,",
                    "-        'deps_good' => 7,",
                    "-        'deps_acceptable' => 12,",
                    "-        'deps_poor' => 20,",
                    "+    \/** @var array<string, array<string, array{optimal: int, max: int}>> File type specific modifiers *\/",
                    "+    private const FILE_TYPE_MODIFIERS = [",
                    "+        'Controller' => [",
                    "+            'loc' => ['optimal' => 300, 'max' => 500],",
                    "+            'methods' => ['optimal' => 15, 'max' => 25],",
                    "+        ],",
                    "+        'Entity' => [",
                    "+            'loc' => ['optimal' => 100, 'max' => 300],",
                    "+            'methods' => ['optimal' => 20, 'max' => 30],",
                    "+        ],",
                    "+        'Repository' => [",
                    "+            'loc' => ['optimal' => 250, 'max' => 400],",
                    "+            'dependencies' => ['optimal' => 3, 'max' => 8],",
                    "+        ],",
                    "+        'Service' => [",
                    "+            'loc' => ['optimal' => 150, 'max' => 350],",
                    "+        ],",
                    "+        'UseCase' => [",
                    "+            'loc' => ['optimal' => 150, 'max' => 350],",
                    "+        ],",
                    "     ];",
                    " ",
                    "+    \/** @var array<string, float> Factor weights (must sum to 1.0) *\/",
                    "+    private const WEIGHTS = [",
                    "+        'dependencies' => 0.25,",
                    "+        'loc' => 0.20,",
                    "+        'methods' => 0.20,",
                    "+        'secrets' => 0.15,",
                    "+        'classes' => 0.10,",
                    "+        'magic_numbers' => 0.10,",
                    "+    ];",
                    "+",
                    "+    \/** @var array<string, array{min: int, max: int}> Hygiene status ranges *\/",
                    "+    private const HYGIENE_STATUS = [",
                    "+        'Sauber' => ['min' => 80, 'max' => 100],",
                    "+        'Gepflegt' => ['min' => 60, 'max' => 79],",
                    "+        'Vernachlässigt' => ['min' => 40, 'max' => 59],",
                    "+        'Verschmutzt' => ['min' => 20, 'max' => 39],",
                    "+        'Kritisch' => ['min' => 0, 'max' => 19],",
                    "+    ];",
                    "+",
                    "     \/**",
                    "-     * Analysiert eine Datei auf Qualitätsprobleme.",
                    "+     * Analysiert eine Datei und berechnet den Code Hygiene Score.",
                    "      *",
                    "      * @param array<string, mixed> $analysisData Daten aus code_analysis",
                    "      * @return array{",
                    "-     *     complexity_score: int,",
                    "-     *     loc_score: int,",
                    "-     *     dependency_score: int,",
                    "-     *     hardcoded_count: int,",
                    "+     *     hygiene_score: int,",
                    "+     *     hygiene_status: string,",
                    "+     *     factor_scores: array<string, int>,",
                    "      *     issues_count: int,",
                    "      *     warnings_count: int,",
                    "-     *     quality_grade: string,",
                    "      *     issues_json: string",
                    "      * }",
                    "      *\/",
                    "     public function analyze(array $analysisData): array",
                    "     {",
                    "         $issues = [];",
                    "-        $warnings = [];",
                    "-",
                    "         $filePath = $analysisData['file_path'] ?? '';",
                    "-        \/\/ Use stored content from DB (scan runs as root with full access)",
                    "         $content = $analysisData['file_content'] ?? '';",
                    " ",
                    "-        \/\/ LOC Analysis",
                    "-        $lineCount = $analysisData['line_count'] ?? 0;",
                    "-        $locScore = $this->calculateLocScore($lineCount);",
                    "-        if ($lineCount > self::THRESHOLDS['loc_poor']) {",
                    "-            $issues[] = [",
                    "-                'type' => 'complexity',",
                    "-                'rule' => 'file-too-long',",
                    "-                'message' => \"Datei hat {$lineCount} Zeilen (max empfohlen: \" . self::THRESHOLDS['loc_acceptable'] . ')',",
                    "-                'severity' => 'warning',",
                    "-            ];",
                    "-        }",
                    "+        \/\/ Determine file type for modifiers",
                    "+        $fileType = $this->detectFileType($filePath);",
                    "+        $thresholds = $this->getThresholdsForType($fileType);",
                    " ",
                    "-        \/\/ Method Count Analysis",
                    "-        $functions = json_decode($analysisData['functions'] ?? '[]', true);",
                    "-        $methodCount = is_array($functions) ? count($functions) : 0;",
                    "-        $methodScore = $this->calculateMethodScore($methodCount);",
                    "-        if ($methodCount > self::THRESHOLDS['methods_poor']) {",
                    "-            $issues[] = [",
                    "-                'type' => 'srp',",
                    "-                'rule' => 'too-many-methods',",
                    "-                'message' => \"Klasse hat {$methodCount} Methoden (SRP-Verletzung möglich)\",",
                    "-                'severity' => 'warning',",
                    "-            ];",
                    "-        }",
                    "+        \/\/ Extract metrics",
                    "+        $metrics = [",
                    "+            'loc' => (int) ($analysisData['line_count'] ?? 0),",
                    "+            'methods' => $this->countMethods($analysisData),",
                    "+            'classes' => (int) ($analysisData['class_count'] ?? 1),",
                    "+            'dependencies' => $this->countDependencies($analysisData),",
                    "+            'secrets' => 0,",
                    "+            'magic_numbers' => 0,",
                    "+        ];",
                    " ",
                    "-        \/\/ Dependency Analysis",
                    "-        $uses = json_decode($analysisData['uses'] ?? '[]', true);",
                    "-        $depsCount = is_array($uses) ? count($uses) : 0;",
                    "-        $depScore = $this->calculateDependencyScore($depsCount);",
                    "-        if ($depsCount > self::THRESHOLDS['deps_poor']) {",
                    "-            $issues[] = [",
                    "-                'type' => 'coupling',",
                    "-                'rule' => 'too-many-dependencies',",
                    "-                'message' => \"Klasse hat {$depsCount} Dependencies (hohe Kopplung)\",",
                    "-                'severity' => 'warning',",
                    "-            ];",
                    "+        \/\/ Detect secrets and magic numbers",
                    "+        $secretIssues = $this->detectSecrets($content, $filePath);",
                    "+        $metrics['secrets'] = count($secretIssues);",
                    "+        $issues = array_merge($issues, $secretIssues);",
                    "+",
                    "+        $magicIssues = $this->detectMagicNumbers($content, $filePath);",
                    "+        $metrics['magic_numbers'] = count($magicIssues);",
                    "+        $issues = array_merge($issues, $magicIssues);",
                    "+",
                    "+        \/\/ Add threshold violation issues",
                    "+        $issues = array_merge($issues, $this->detectThresholdViolations($metrics, $thresholds));",
                    "+",
                    "+        \/\/ Calculate normalized factor scores",
                    "+        $factorScores = [];",
                    "+        foreach (self::WEIGHTS as $factor => $weight) {",
                    "+            $factorScores[$factor] = $this->normalize(",
                    "+                $metrics[$factor],",
                    "+                $thresholds[$factor]['optimal'],",
                    "+                $thresholds[$factor]['max']",
                    "+            );",
                    "         }",
                    " ",
                    "-        \/\/ Hardcoded Detection",
                    "-        $hardcodedIssues = $this->detectHardcoded($content, $filePath);",
                    "-        $hardcodedCount = count($hardcodedIssues);",
                    "-        $issues = array_merge($issues, $hardcodedIssues);",
                    "+        \/\/ Calculate weighted hygiene score",
                    "+        $hygieneScore = $this->calculateWeightedScore($factorScores);",
                    " ",
                    "-        \/\/ Calculate complexity score (weighted average)",
                    "-        $complexityScore = (int) (($locScore * 0.3 + $methodScore * 0.3 + $depScore * 0.4));",
                    "+        \/\/ Apply hard fail for secrets",
                    "+        if ($metrics['secrets'] > 0) {",
                    "+            $hygieneScore = min($hygieneScore, 20);",
                    "+        }",
                    " ",
                    "-        \/\/ Penalize for hardcoded values",
                    "-        $complexityScore = max(0, $complexityScore - ($hardcodedCount * 10));",
                    "+        \/\/ Determine hygiene status",
                    "+        $hygieneStatus = $this->determineStatus($hygieneScore);",
                    " ",
                    "-        \/\/ Determine grade",
                    "-        $totalIssues = count($issues);",
                    "-        $totalWarnings = count(array_filter($issues, fn ($i) => $i['severity'] === 'warning'));",
                    "-        $grade = $this->calculateGrade($complexityScore, $totalIssues);",
                    "+        \/\/ Count warnings",
                    "+        $warningsCount = count(array_filter($issues, fn ($i) => $i['severity'] === 'warning'));",
                    " ",
                    "         return [",
                    "-            'complexity_score' => $complexityScore,",
                    "-            'loc_score' => $locScore,",
                    "-            'dependency_score' => $depScore,",
                    "-            'hardcoded_count' => $hardcodedCount,",
                    "-            'issues_count' => $totalIssues,",
                    "-            'warnings_count' => $totalWarnings,",
                    "-            'quality_grade' => $grade,",
                    "+            'hygiene_score' => $hygieneScore,",
                    "+            'hygiene_status' => $hygieneStatus,",
                    "+            'factor_scores' => $factorScores,",
                    "+            'issues_count' => count($issues),",
                    "+            'warnings_count' => $warningsCount,",
                    "             'issues_json' => json_encode($issues, JSON_UNESCAPED_UNICODE),",
                    "         ];",
                    "     }",
                    " ",
                    "-    private function calculateLocScore(int $loc): int",
                    "+    \/**",
                    "+     * Normalisiert einen Wert auf 0-100 Skala.",
                    "+     * Formel: max(0, 100 - ((value - optimal) \/ (max - optimal)) * 100)",
                    "+     *\/",
                    "+    private function normalize(int $value, int $optimal, int $max): int",
                    "     {",
                    "-        if ($loc <= self::THRESHOLDS['loc_excellent']) {",
                    "+        if ($value <= $optimal) {",
                    "             return 100;",
                    "         }",
                    "-        if ($loc <= self::THRESHOLDS['loc_good']) {",
                    "-            return 80;",
                    "+",
                    "+        if ($value >= $max) {",
                    "+            return 0;",
                    "         }",
                    "-        if ($loc <= self::THRESHOLDS['loc_acceptable']) {",
                    "-            return 60;",
                    "+",
                    "+        $range = $max - $optimal;",
                    "+        if ($range === 0) {",
                    "+            return 0;",
                    "         }",
                    "-        if ($loc <= self::THRESHOLDS['loc_poor']) {",
                    "-            return 40;",
                    "+",
                    "+        $normalized = 100 - (($value - $optimal) \/ $range) * 100;",
                    "+",
                    "+        return (int) max(0, min(100, $normalized));",
                    "+    }",
                    "+",
                    "+    \/**",
                    "+     * Berechnet den gewichteten Gesamtscore.",
                    "+     *",
                    "+     * @param array<string, int> $factorScores",
                    "+     *\/",
                    "+    private function calculateWeightedScore(array $factorScores): int",
                    "+    {",
                    "+        $weightedSum = 0.0;",
                    "+",
                    "+        foreach (self::WEIGHTS as $factor => $weight) {",
                    "+            $weightedSum += ($factorScores[$factor] ?? 0) * $weight;",
                    "         }",
                    " ",
                    "-        return self::SCORE_MIN;",
                    "+        return (int) round($weightedSum);",
                    "     }",
                    " ",
                    "-    private function calculateMethodScore(int $count): int",
                    "+    \/**",
                    "+     * Ermittelt den Dateityp aus dem Pfad.",
                    "+     *\/",
                    "+    private function detectFileType(string $filePath): ?string",
                    "     {",
                    "-        if ($count <= self::THRESHOLDS['methods_excellent']) {",
                    "-            return 100;",
                    "+        $filename = basename($filePath);",
                    "+",
                    "+        foreach (array_keys(self::FILE_TYPE_MODIFIERS) as $type) {",
                    "+            if (str_contains($filename, $type)) {",
                    "+                return $type;",
                    "+            }",
                    "         }",
                    "-        if ($count <= self::THRESHOLDS['methods_good']) {",
                    "-            return 80;",
                    "+",
                    "+        \/\/ Check directory",
                    "+        if (str_contains($filePath, '\/Controller\/')) {",
                    "+            return 'Controller';",
                    "         }",
                    "-        if ($count <= self::THRESHOLDS['methods_acceptable']) {",
                    "-            return 60;",
                    "+        if (str_contains($filePath, '\/Entity\/')) {",
                    "+            return 'Entity';",
                    "         }",
                    "-        if ($count <= self::THRESHOLDS['methods_poor']) {",
                    "-            return 40;",
                    "+        if (str_contains($filePath, '\/Repository\/') || str_contains($filePath, '\/Persistence\/')) {",
                    "+            return 'Repository';",
                    "         }",
                    "+        if (str_contains($filePath, '\/Service\/')) {",
                    "+            return 'Service';",
                    "+        }",
                    "+        if (str_contains($filePath, '\/UseCase\/') || str_contains($filePath, '\/UseCases\/')) {",
                    "+            return 'UseCase';",
                    "+        }",
                    " ",
                    "-        return self::SCORE_MIN;",
                    "+        return null;",
                    "     }",
                    " ",
                    "-    private function calculateDependencyScore(int $count): int",
                    "+    \/**",
                    "+     * Gibt die Thresholds für einen Dateityp zurück.",
                    "+     *",
                    "+     * @return array<string, array{optimal: int, max: int}>",
                    "+     *\/",
                    "+    private function getThresholdsForType(?string $fileType): array",
                    "     {",
                    "-        if ($count <= self::THRESHOLDS['deps_excellent']) {",
                    "-            return 100;",
                    "+        $thresholds = self::HYGIENE_DEFAULTS;",
                    "+",
                    "+        if ($fileType !== null && isset(self::FILE_TYPE_MODIFIERS[$fileType])) {",
                    "+            foreach (self::FILE_TYPE_MODIFIERS[$fileType] as $factor => $values) {",
                    "+                $thresholds[$factor] = $values;",
                    "+            }",
                    "         }",
                    "-        if ($count <= self::THRESHOLDS['deps_good']) {",
                    "-            return 80;",
                    "-        }",
                    "-        if ($count <= self::THRESHOLDS['deps_acceptable']) {",
                    "-            return 60;",
                    "-        }",
                    "-        if ($count <= self::THRESHOLDS['deps_poor']) {",
                    "-            return 40;",
                    "-        }",
                    " ",
                    "-        return self::SCORE_MIN;",
                    "+        return $thresholds;",
                    "     }",
                    " ",
                    "     \/**",
                    "+     * Zählt die Methoden aus den Analysedaten.",
                    "+     *",
                    "+     * @param array<string, mixed> $analysisData",
                    "+     *\/",
                    "+    private function countMethods(array $analysisData): int",
                    "+    {",
                    "+        $functions = json_decode($analysisData['functions'] ?? '[]', true);",
                    "+",
                    "+        return is_array($functions) ? count($functions) : 0;",
                    "+    }",
                    "+",
                    "+    \/**",
                    "+     * Zählt die Dependencies aus den Analysedaten.",
                    "+     *",
                    "+     * @param array<string, mixed> $analysisData",
                    "+     *\/",
                    "+    private function countDependencies(array $analysisData): int",
                    "+    {",
                    "+        $uses = json_decode($analysisData['uses'] ?? '[]', true);",
                    "+",
                    "+        return is_array($uses) ? count($uses) : 0;",
                    "+    }",
                    "+",
                    "+    \/**",
                    "+     * Erkennt Secrets im Code (Hard Fail).",
                    "+     *",
                    "      * @return array<array{type: string, rule: string, message: string, severity: string, line?: int}>",
                    "      *\/",
                    "-    private function detectHardcoded(string $content, string $filePath): array",
                    "+    private function detectSecrets(string $content, string $filePath): array",
                    "     {",
                    "         $issues = [];",
                    "-",
                    "-        \/\/ Skip config files and test files",
                    "         $filename = basename($filePath);",
                    "+",
                    "+        \/\/ Skip config files",
                    "         if (preg_match('\/^(config|\\.env|test|spec)\/i', $filename)) {",
                    "             return [];",
                    "         }",
                    " ",
                    "-        foreach (self::HARDCODED_PATTERNS as $type => $pattern) {",
                    "-            \/\/ Skip magic numbers in config\/constant files",
                    "-            if ($type === 'magic_number' && preg_match('\/const|config\/i', $filePath)) {",
                    "-                continue;",
                    "+        foreach (self::SECRET_PATTERNS as $type => $pattern) {",
                    "+            if (preg_match_all($pattern, $content, $matches, PREG_OFFSET_CAPTURE)) {",
                    "+                foreach ($matches[0] as $match) {",
                    "+                    $line = substr_count(substr($content, 0, $match[1]), \"\\n\") + 1;",
                    "+                    $issues[] = [",
                    "+                        'type' => 'secret',",
                    "+                        'rule' => \"hardcoded-{$type}\",",
                    "+                        'message' => \"KRITISCH: Mögliches hardcoded {$type} gefunden\",",
                    "+                        'severity' => 'critical',",
                    "+                        'line' => $line,",
                    "+                    ];",
                    "+                }",
                    "             }",
                    "+        }",
                    " ",
                    "+        return $issues;",
                    "+    }",
                    "+",
                    "+    \/**",
                    "+     * Erkennt Magic Numbers im Code.",
                    "+     *",
                    "+     * @return array<array{type: string, rule: string, message: string, severity: string, line?: int}>",
                    "+     *\/",
                    "+    private function detectMagicNumbers(string $content, string $filePath): array",
                    "+    {",
                    "+        $issues = [];",
                    "+",
                    "+        \/\/ Skip config\/constant files",
                    "+        if (preg_match('\/const|config\/i', $filePath)) {",
                    "+            return [];",
                    "+        }",
                    "+",
                    "+        foreach (self::MAGIC_NUMBER_PATTERNS as $type => $pattern) {",
                    "             if (preg_match_all($pattern, $content, $matches, PREG_OFFSET_CAPTURE)) {",
                    "                 foreach ($matches[0] as $match) {",
                    "                     $line = substr_count(substr($content, 0, $match[1]), \"\\n\") + 1;",
                    "                     $issues[] = [",
                    "-                        'type' => 'hardcoded',",
                    "-                        'rule' => \"hardcoded-{$type}\",",
                    "-                        'message' => \"Möglicher hardcoded {$type}: \" . substr($match[0], 0, 30) . '...',",
                    "-                        'severity' => $type === 'magic_number' ? 'info' : 'warning',",
                    "+                        'type' => 'magic_number',",
                    "+                        'rule' => 'hardcoded-magic-number',",
                    "+                        'message' => \"Magic Number gefunden: {$match[0]}\",",
                    "+                        'severity' => 'info',",
                    "                         'line' => $line,",
                    "                     ];",
                    "                 }"
                ]
            },
            {
                "oldStart": 217,
                "oldLines": 24,
                "newStart": 331,
                "newLines": 58,
                "lines": [
                    "         return $issues;",
                    "     }",
                    " ",
                    "-    private function calculateGrade(int $score, int $issueCount): string",
                    "+    \/**",
                    "+     * Erkennt Threshold-Verletzungen.",
                    "+     *",
                    "+     * @param array<string, int> $metrics",
                    "+     * @param array<string, array{optimal: int, max: int}> $thresholds",
                    "+     * @return array<array{type: string, rule: string, message: string, severity: string}>",
                    "+     *\/",
                    "+    private function detectThresholdViolations(array $metrics, array $thresholds): array",
                    "     {",
                    "-        \/\/ Penalize for issues",
                    "-        $adjustedScore = $score - ($issueCount * 5);",
                    "+        $issues = [];",
                    " ",
                    "-        if ($adjustedScore >= 90) {",
                    "-            return 'A';",
                    "+        if ($metrics['loc'] > $thresholds['loc']['max']) {",
                    "+            $issues[] = [",
                    "+                'type' => 'complexity',",
                    "+                'rule' => 'file-too-long',",
                    "+                'message' => \"Datei hat {$metrics['loc']} Zeilen (max: {$thresholds['loc']['max']})\",",
                    "+                'severity' => 'warning',",
                    "+            ];",
                    "         }",
                    "-        if ($adjustedScore >= 75) {",
                    "-            return 'B';",
                    "+",
                    "+        if ($metrics['methods'] > $thresholds['methods']['max']) {",
                    "+            $issues[] = [",
                    "+                'type' => 'srp',",
                    "+                'rule' => 'too-many-methods',",
                    "+                'message' => \"Klasse hat {$metrics['methods']} Methoden (max: {$thresholds['methods']['max']})\",",
                    "+                'severity' => 'warning',",
                    "+            ];",
                    "         }",
                    "-        if ($adjustedScore >= 60) {",
                    "-            return 'C';",
                    "+",
                    "+        if ($metrics['dependencies'] > $thresholds['dependencies']['max']) {",
                    "+            $issues[] = [",
                    "+                'type' => 'coupling',",
                    "+                'rule' => 'too-many-dependencies',",
                    "+                'message' => \"Klasse hat {$metrics['dependencies']} Dependencies (max: {$thresholds['dependencies']['max']})\",",
                    "+                'severity' => 'warning',",
                    "+            ];",
                    "         }",
                    "-        if ($adjustedScore >= 40) {",
                    "-            return 'D';",
                    "+",
                    "+        return $issues;",
                    "+    }",
                    "+",
                    "+    \/**",
                    "+     * Bestimmt den Hygiene-Status basierend auf dem Score.",
                    "+     *\/",
                    "+    private function determineStatus(int $score): string",
                    "+    {",
                    "+        foreach (self::HYGIENE_STATUS as $status => $range) {",
                    "+            if ($score >= $range['min'] && $score <= $range['max']) {",
                    "+                return $status;",
                    "+            }",
                    "         }",
                    " ",
                    "-        return 'F';",
                    "+        return 'Kritisch';",
                    "     }",
                    " }"
                ]
            }
        ],
        "originalFile": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Infrastructure\\CodeAnalysis;\n\n\/\/ @responsibility: Code-Qualitätsanalyse (Hardcoded, Complexity, Metrics)\n\nfinal class CodeQualityChecker\n{\n    \/** @var array<string, string> Regex patterns for hardcoded detection *\/\n    private const HARDCODED_PATTERNS = [\n        'password' => '\/[\"\\'](?:password|passwd|pwd)[\"\\']\\\\s*[=:]\\\\s*[\"\\'][^\"\\']{3,}[\"\\']\/i',\n        'api_key' => '\/[\"\\'](?:api[_-]?key|apikey|secret[_-]?key)[\"\\']\\\\s*[=:]\\\\s*[\"\\'][^\"\\']{8,}[\"\\']\/i',\n        'token' => '\/[\"\\'](?:token|auth[_-]?token|access[_-]?token)[\"\\']\\\\s*[=:]\\\\s*[\"\\'][^\"\\']{8,}[\"\\']\/i',\n        'ip_address' => '\/[\"\\']\\\\d{1,3}\\\\.\\\\d{1,3}\\\\.\\\\d{1,3}\\\\.\\\\d{1,3}[\"\\']\/i',\n        'url_with_creds' => '\/https?:\\\\\/\\\\\/[^:]+:[^@]+@\/i',\n        'magic_number' => '\/(?<![\\\\w])(?:100|1000|60|24|365|3600|86400)(?![\\\\w])\/',\n    ];\n\n    \/** @var int Minimum score for worst quality *\/\n    private const SCORE_MIN = 20;\n\n    \/** @var array<string, int> Thresholds for scoring *\/\n    private const THRESHOLDS = [\n        'loc_excellent' => 100,\n        'loc_good' => 200,\n        'loc_acceptable' => 400,\n        'loc_poor' => 600,\n        'methods_excellent' => 5,\n        'methods_good' => 10,\n        'methods_acceptable' => 15,\n        'methods_poor' => 20,\n        'deps_excellent' => 3,\n        'deps_good' => 7,\n        'deps_acceptable' => 12,\n        'deps_poor' => 20,\n    ];\n\n    \/**\n     * Analysiert eine Datei auf Qualitätsprobleme.\n     *\n     * @param array<string, mixed> $analysisData Daten aus code_analysis\n     * @return array{\n     *     complexity_score: int,\n     *     loc_score: int,\n     *     dependency_score: int,\n     *     hardcoded_count: int,\n     *     issues_count: int,\n     *     warnings_count: int,\n     *     quality_grade: string,\n     *     issues_json: string\n     * }\n     *\/\n    public function analyze(array $analysisData): array\n    {\n        $issues = [];\n        $warnings = [];\n\n        $filePath = $analysisData['file_path'] ?? '';\n        \/\/ Use stored content from DB (scan runs as root with full access)\n        $content = $analysisData['file_content'] ?? '';\n\n        \/\/ LOC Analysis\n        $lineCount = $analysisData['line_count'] ?? 0;\n        $locScore = $this->calculateLocScore($lineCount);\n        if ($lineCount > self::THRESHOLDS['loc_poor']) {\n            $issues[] = [\n                'type' => 'complexity',\n                'rule' => 'file-too-long',\n                'message' => \"Datei hat {$lineCount} Zeilen (max empfohlen: \" . self::THRESHOLDS['loc_acceptable'] . ')',\n                'severity' => 'warning',\n            ];\n        }\n\n        \/\/ Method Count Analysis\n        $functions = json_decode($analysisData['functions'] ?? '[]', true);\n        $methodCount = is_array($functions) ? count($functions) : 0;\n        $methodScore = $this->calculateMethodScore($methodCount);\n        if ($methodCount > self::THRESHOLDS['methods_poor']) {\n            $issues[] = [\n                'type' => 'srp',\n                'rule' => 'too-many-methods',\n                'message' => \"Klasse hat {$methodCount} Methoden (SRP-Verletzung möglich)\",\n                'severity' => 'warning',\n            ];\n        }\n\n        \/\/ Dependency Analysis\n        $uses = json_decode($analysisData['uses'] ?? '[]', true);\n        $depsCount = is_array($uses) ? count($uses) : 0;\n        $depScore = $this->calculateDependencyScore($depsCount);\n        if ($depsCount > self::THRESHOLDS['deps_poor']) {\n            $issues[] = [\n                'type' => 'coupling',\n                'rule' => 'too-many-dependencies',\n                'message' => \"Klasse hat {$depsCount} Dependencies (hohe Kopplung)\",\n                'severity' => 'warning',\n            ];\n        }\n\n        \/\/ Hardcoded Detection\n        $hardcodedIssues = $this->detectHardcoded($content, $filePath);\n        $hardcodedCount = count($hardcodedIssues);\n        $issues = array_merge($issues, $hardcodedIssues);\n\n        \/\/ Calculate complexity score (weighted average)\n        $complexityScore = (int) (($locScore * 0.3 + $methodScore * 0.3 + $depScore * 0.4));\n\n        \/\/ Penalize for hardcoded values\n        $complexityScore = max(0, $complexityScore - ($hardcodedCount * 10));\n\n        \/\/ Determine grade\n        $totalIssues = count($issues);\n        $totalWarnings = count(array_filter($issues, fn ($i) => $i['severity'] === 'warning'));\n        $grade = $this->calculateGrade($complexityScore, $totalIssues);\n\n        return [\n            'complexity_score' => $complexityScore,\n            'loc_score' => $locScore,\n            'dependency_score' => $depScore,\n            'hardcoded_count' => $hardcodedCount,\n            'issues_count' => $totalIssues,\n            'warnings_count' => $totalWarnings,\n            'quality_grade' => $grade,\n            'issues_json' => json_encode($issues, JSON_UNESCAPED_UNICODE),\n        ];\n    }\n\n    private function calculateLocScore(int $loc): int\n    {\n        if ($loc <= self::THRESHOLDS['loc_excellent']) {\n            return 100;\n        }\n        if ($loc <= self::THRESHOLDS['loc_good']) {\n            return 80;\n        }\n        if ($loc <= self::THRESHOLDS['loc_acceptable']) {\n            return 60;\n        }\n        if ($loc <= self::THRESHOLDS['loc_poor']) {\n            return 40;\n        }\n\n        return self::SCORE_MIN;\n    }\n\n    private function calculateMethodScore(int $count): int\n    {\n        if ($count <= self::THRESHOLDS['methods_excellent']) {\n            return 100;\n        }\n        if ($count <= self::THRESHOLDS['methods_good']) {\n            return 80;\n        }\n        if ($count <= self::THRESHOLDS['methods_acceptable']) {\n            return 60;\n        }\n        if ($count <= self::THRESHOLDS['methods_poor']) {\n            return 40;\n        }\n\n        return self::SCORE_MIN;\n    }\n\n    private function calculateDependencyScore(int $count): int\n    {\n        if ($count <= self::THRESHOLDS['deps_excellent']) {\n            return 100;\n        }\n        if ($count <= self::THRESHOLDS['deps_good']) {\n            return 80;\n        }\n        if ($count <= self::THRESHOLDS['deps_acceptable']) {\n            return 60;\n        }\n        if ($count <= self::THRESHOLDS['deps_poor']) {\n            return 40;\n        }\n\n        return self::SCORE_MIN;\n    }\n\n    \/**\n     * @return array<array{type: string, rule: string, message: string, severity: string, line?: int}>\n     *\/\n    private function detectHardcoded(string $content, string $filePath): array\n    {\n        $issues = [];\n\n        \/\/ Skip config files and test files\n        $filename = basename($filePath);\n        if (preg_match('\/^(config|\\.env|test|spec)\/i', $filename)) {\n            return [];\n        }\n\n        foreach (self::HARDCODED_PATTERNS as $type => $pattern) {\n            \/\/ Skip magic numbers in config\/constant files\n            if ($type === 'magic_number' && preg_match('\/const|config\/i', $filePath)) {\n                continue;\n            }\n\n            if (preg_match_all($pattern, $content, $matches, PREG_OFFSET_CAPTURE)) {\n                foreach ($matches[0] as $match) {\n                    $line = substr_count(substr($content, 0, $match[1]), \"\\n\") + 1;\n                    $issues[] = [\n                        'type' => 'hardcoded',\n                        'rule' => \"hardcoded-{$type}\",\n                        'message' => \"Möglicher hardcoded {$type}: \" . substr($match[0], 0, 30) . '...',\n                        'severity' => $type === 'magic_number' ? 'info' : 'warning',\n                        'line' => $line,\n                    ];\n                }\n            }\n        }\n\n        return $issues;\n    }\n\n    private function calculateGrade(int $score, int $issueCount): string\n    {\n        \/\/ Penalize for issues\n        $adjustedScore = $score - ($issueCount * 5);\n\n        if ($adjustedScore >= 90) {\n            return 'A';\n        }\n        if ($adjustedScore >= 75) {\n            return 'B';\n        }\n        if ($adjustedScore >= 60) {\n            return 'C';\n        }\n        if ($adjustedScore >= 40) {\n            return 'D';\n        }\n\n        return 'F';\n    }\n}\n"
    }
}
← Vorheriger Zur Liste Nächster →