| 129 | |
| 130 | /** Render diagnostics into a compact, model-friendly report grouped by file. */ |
| 131 | export function formatDiagnostics(diags: Diagnostic[], opts: { checker: string; maxResults: number }): string { |
| 132 | const errors = diags.filter(d => d.severity === 'error').length; |
| 133 | const warnings = diags.filter(d => d.severity === 'warning').length; |
| 134 | |
| 135 | if (diags.length === 0) { |
| 136 | return `# Diagnostics (${opts.checker})\n\n✓ No problems found. Clean.`; |
| 137 | } |
| 138 | |
| 139 | const capped = diags.slice(0, opts.maxResults); |
| 140 | const byFile = new Map<string, Diagnostic[]>(); |
| 141 | for (const d of capped) { |
| 142 | if (!byFile.has(d.file)) byFile.set(d.file, []); |
| 143 | byFile.get(d.file)!.push(d); |
| 144 | } |
| 145 | |
| 146 | const lines: string[] = []; |
| 147 | lines.push(`# Diagnostics (${opts.checker})`); |
| 148 | lines.push(`${errors} error(s), ${warnings} warning(s)${diags.length > capped.length ? ` — showing first ${capped.length}` : ''}`); |
| 149 | lines.push(''); |
| 150 | for (const [file, ds] of byFile) { |
| 151 | lines.push(`## ${file}`); |
| 152 | for (const d of ds) { |
| 153 | const pos = d.col != null ? `${d.line}:${d.col}` : `${d.line}`; |
| 154 | const sev = d.severity.toUpperCase(); |
| 155 | const code = d.code ? ` [${d.code}]` : ''; |
| 156 | lines.push(` ${pos} ${sev}${code} ${d.message}`); |
| 157 | } |
| 158 | lines.push(''); |
| 159 | } |
| 160 | lines.push('Fix the errors above (read the file at the reported line, edit, then re-run diagnostics to confirm).'); |
| 161 | return lines.join('\n'); |
| 162 | } |
| 163 | |
| 164 | /** |
| 165 | * Parse `php -l` (lint) output. Success is "No syntax errors detected in <file>". |