/dev/null"); if ($lsattr_out && preg_match('/^----i/', $lsattr_out)) { $is_immutable = true; $chattr_i_count++; } $size = @filesize($f); $uncovered[] = [ 'name' => $name, 'size' => $size, 'immutable' => $is_immutable, 'size_human' => $size ? round($size/1024, 1) . ' KB' : '?' ]; } } $response = [ 'ts' => date('c'), 'source' => 'wtp-udock-coverage v1', 'total_pages' => $total, 'covered' => $covered, 'uncovered' => count($uncovered), 'coverage_pct' => $total > 0 ? round(100 * $covered / $total, 1) : 0, 'by_pattern' => [ 'wtp-unified-dock.js' => $by_pattern['wtp-unified-dock.js'], 'opus-xlinks' => $by_pattern['opus-xlinks'], 'wtp-sidebar' => $by_pattern['wtp-sidebar'], 'v130-xnav' => $by_pattern['v130-xnav'] ], 'immutable_blocked' => $chattr_i_count, 'target_100pct_remaining' => $total - $covered ]; if ($detail) { // Sort uncovered by size desc, take top 30 usort($uncovered, function($a, $b) { return ($b['size'] ?? 0) - ($a['size'] ?? 0); }); $response['uncovered_top30'] = array_slice($uncovered, 0, 30); } echo json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);