diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index afea5d127..b62979144 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -1,7 +1,6 @@ # Changelog ## [Unreleased] - ### Added - Added subcommands to `/copy` command: `code` (copy last code block), `all` (copy all code blocks), `cmd` (copy last bash/python command), and `last` (copy full message) @@ -16,6 +15,7 @@ ### Changed +- Improved diagnostic message formatting to group errors by file path with indented details for better readability - Modified eager todo prelude to use hidden custom message type instead of visible developer message, preventing duplicate prompt text in session history - Updated eager todo prompt to remove dynamic user request injection, simplifying the template and preventing request repetition in displayed messages - Modified eager todo enforcement to prepend the todo reminder to the first user turn instead of executing it as a separate synthetic turn, reducing unnecessary prompt calls diff --git a/packages/coding-agent/src/lsp/index.ts b/packages/coding-agent/src/lsp/index.ts index 42ebcdb43..23fee0593 100644 --- a/packages/coding-agent/src/lsp/index.ts +++ b/packages/coding-agent/src/lsp/index.ts @@ -56,6 +56,7 @@ import { formatDiagnostic, formatDiagnosticsSummary, formatDocumentSymbol, + formatGroupedDiagnosticMessages, formatLocation, formatSymbolInformation, formatWorkspaceEdit, @@ -1082,7 +1083,7 @@ export class LspTool implements AgentTool formatDiagnostic(d, relPath)); - const output = `${summary}:\n${formatted.map(f => ` ${f}`).join("\n")}`; + const output = `${summary}:\n${formatGroupedDiagnosticMessages(formatted)}`; return { content: [{ type: "text", text: output }], details: { action, serverName: Array.from(allServerNames).join(", "), success: true }, @@ -1094,9 +1095,8 @@ export class LspTool implements AgentTool formatDiagnostic(d, relPath)); + results.push(formatGroupedDiagnosticMessages(formatted)); } } diff --git a/packages/coding-agent/src/lsp/utils.ts b/packages/coding-agent/src/lsp/utils.ts index 35b6620b1..860de6382 100644 --- a/packages/coding-agent/src/lsp/utils.ts +++ b/packages/coding-agent/src/lsp/utils.ts @@ -281,6 +281,87 @@ export function formatDiagnostic(diagnostic: Diagnostic, filePath: string): stri return `${filePath}:${line}:${col} [${severity}] ${source}${message}${code}`; } +// Regex: split on the first `:digits:digits` boundary to separate path from the rest +const DIAG_PATH_RE = /^(.+?):(\d+:\d+\s+.*)$/; + +/** + * Reformat pre-formatted diagnostic messages into grep-style directory/file groups. + * Input: ["path:line:col [sev] msg", ...] + * Output: "# dir\n## └─ file.ts\n line:col [sev] msg" + * + * Messages that don't match the expected format are appended ungrouped at the end. + */ +export function formatGroupedDiagnosticMessages(messages: string[]): string { + const diagnosticsByFile = new Map(); + const fileOrder: string[] = []; + const ungrouped: string[] = []; + + for (const msg of messages) { + const match = DIAG_PATH_RE.exec(msg); + if (!match) { + ungrouped.push(msg); + continue; + } + + const [, rawFilePath, rest] = match; + const filePath = rawFilePath.replace(/\\/g, "/"); + if (!diagnosticsByFile.has(filePath)) { + diagnosticsByFile.set(filePath, []); + fileOrder.push(filePath); + } + diagnosticsByFile.get(filePath)?.push(rest); + } + + if (diagnosticsByFile.size === 0) { + return ungrouped.join("\n"); + } + + const filesByDirectory = new Map(); + for (const filePath of fileOrder) { + const directory = path.dirname(filePath).replace(/\\/g, "/"); + if (!filesByDirectory.has(directory)) { + filesByDirectory.set(directory, []); + } + filesByDirectory.get(directory)?.push(filePath); + } + + const lines: string[] = []; + for (const [directory, directoryFiles] of filesByDirectory) { + if (directory === ".") { + for (const filePath of directoryFiles) { + if (lines.length > 0) { + lines.push(""); + } + lines.push(`# ${path.basename(filePath)}`); + for (const diagnostic of diagnosticsByFile.get(filePath) ?? []) { + lines.push(` ${diagnostic}`); + } + } + continue; + } + + if (lines.length > 0) { + lines.push(""); + } + lines.push(`# ${directory}`); + for (const filePath of directoryFiles) { + lines.push(`## └─ ${path.basename(filePath)}`); + for (const diagnostic of diagnosticsByFile.get(filePath) ?? []) { + lines.push(` ${diagnostic}`); + } + } + } + + if (ungrouped.length > 0) { + lines.push(""); + for (const msg of ungrouped) { + lines.push(msg); + } + } + + return lines.join("\n"); +} + /** * Format diagnostics grouped by severity. */ diff --git a/packages/coding-agent/src/tools/output-meta.ts b/packages/coding-agent/src/tools/output-meta.ts index 37199f809..18af32bff 100644 --- a/packages/coding-agent/src/tools/output-meta.ts +++ b/packages/coding-agent/src/tools/output-meta.ts @@ -12,6 +12,7 @@ import type { AgentToolUpdateCallback, } from "@oh-my-pi/pi-agent-core"; import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai"; +import { formatGroupedDiagnosticMessages } from "../lsp/utils"; import type { Theme } from "../modes/theme/theme"; import type { OutputSummary, TruncationResult } from "../session/streaming-output"; import { formatBytes, wrapBrackets } from "./render-utils"; @@ -386,7 +387,7 @@ export function formatOutputNotice(meta: OutputMeta | undefined): string { let diagnosticsNotice = ""; if (meta.diagnostics && meta.diagnostics.messages.length > 0) { const d = meta.diagnostics; - diagnosticsNotice = `\n\nLSP Diagnostics (${d.summary}):\n ${d.messages.join("\n ")}`; + diagnosticsNotice = `\n\nLSP Diagnostics (${d.summary}):\n${formatGroupedDiagnosticMessages(d.messages)}`; } const notice = parts.length ? `\n\n[${parts.join(". ")}]` : "";