diff --git a/src/ClaudeDev.ts b/src/ClaudeDev.ts
index 027b261..6ff3afa 100644
--- a/src/ClaudeDev.ts
+++ b/src/ClaudeDev.ts
@@ -28,6 +28,7 @@ import { extractTextFromFile } from "./utils/extract-text"
import { regexSearchFiles } from "./utils/ripgrep"
import { parseMentions } from "./utils/context-mentions"
import { UrlContentFetcher } from "./utils/UrlContentFetcher"
+import { diagnosticsToProblemsString, getNewDiagnostics } from "./utils/diagnostics"
const SYSTEM_PROMPT =
async () => `You are Claude Dev, a highly skilled software developer with extensive knowledge in many programming languages, frameworks, design patterns, and best practices.
@@ -762,6 +763,9 @@ export class ClaudeDev {
}
}
+ // get diagnostics before editing the file, we'll compare to diagnostics after editing to see if claude needs to fix anything
+ const preDiagnostics = vscode.languages.getDiagnostics()
+
let originalContent: string
if (fileExists) {
originalContent = await fs.readFile(absolutePath, "utf-8")
@@ -1037,6 +1041,27 @@ export class ClaudeDev {
await this.closeDiffViews()
+ /*
+ Getting diagnostics before and after the file edit is a better approach than
+ automatically tracking problems in real-time. This method ensures we only
+ report new problems that are a direct result of this specific edit.
+ Since these are new problems resulting from Claude's edit, we know they're
+ directly related to the work he's doing. This eliminates the risk of Claude
+ going off-task or getting distracted by unrelated issues, which was a problem
+ with the previous auto-debug approach. Some users' machines may be slow to
+ update diagnostics, so this approach provides a good balance between automation
+ and avoiding potential issues where Claude might get stuck in loops due to
+ outdated problem information. If no new problems show up by the time the user
+ accepts the changes, they can always debug later using the '@problems' mention.
+ This way, Claude only becomes aware of new problems resulting from his edits
+ and can address them accordingly. If problems don't change immediately after
+ applying a fix, Claude won't be notified, which is generally fine since the
+ initial fix is usually correct and it may just take time for linters to catch up.
+ */
+ const postDiagnostics = vscode.languages.getDiagnostics()
+ const newProblems = diagnosticsToProblemsString(getNewDiagnostics(preDiagnostics, postDiagnostics), cwd) // will be empty string if no errors/warnings
+ const newProblemsMessage =
+ newProblems.length > 0 ? `\n\nNew problems detected after saving the file:\n${newProblems}` : ""
// await vscode.window.showTextDocument(vscode.Uri.file(absolutePath), { preview: false })
// If the edited content has different EOL characters, we don't want to show a diff with all the EOL differences.
@@ -1056,11 +1081,16 @@ export class ClaudeDev {
return [
false,
await this.formatToolResult(
- `The user made the following updates to your content:\n\n${userDiff}\n\nThe updated content, which includes both your original modifications and the user's additional edits, has been successfully saved to ${relPath}. Note this does not mean you need to re-write the file with the user's changes, they have already been applied to the file.`
+ `The user made the following updates to your content:\n\n${userDiff}\n\nThe updated content, which includes both your original modifications and the user's additional edits, has been successfully saved to ${relPath}. (Note this does not mean you need to re-write the file with the user's changes, as they have already been applied to the file.)${newProblemsMessage}`
),
]
} else {
- return [false, await this.formatToolResult(`The content was successfully saved to ${relPath}.`)]
+ return [
+ false,
+ await this.formatToolResult(
+ `The content was successfully saved to ${relPath}.${newProblemsMessage}`
+ ),
+ ]
}
} catch (error) {
const errorString = `Error writing file: ${JSON.stringify(serializeError(error))}`
diff --git a/src/utils/context-mentions.ts b/src/utils/context-mentions.ts
index 833510e..4203e5c 100644
--- a/src/utils/context-mentions.ts
+++ b/src/utils/context-mentions.ts
@@ -6,6 +6,7 @@ import { mentionRegexGlobal } from "../shared/context-mentions"
import fs from "fs/promises"
import { extractTextFromFile } from "./extract-text"
import { isBinaryFile } from "isbinaryfile"
+import { diagnosticsToProblemsString } from "./diagnostics"
export function openMention(mention?: string): void {
if (!mention) {
@@ -93,8 +94,8 @@ export async function parseMentions(text: string, cwd: string, urlContentFetcher
}
} else if (mention === "problems") {
try {
- const diagnostics = await getWorkspaceDiagnostics(cwd)
- parsedText += `\n\n\n${diagnostics}\n`
+ const problems = getWorkspaceProblems(cwd)
+ parsedText += `\n\n\n${problems}\n`
} catch (error) {
parsedText += `\n\n\nError fetching diagnostics: ${error.message}\n`
}
@@ -168,28 +169,11 @@ async function getFileOrFolderContent(mentionPath: string, cwd: string): Promise
}
}
-async function getWorkspaceDiagnostics(cwd: string): Promise {
+function getWorkspaceProblems(cwd: string): string {
const diagnostics = vscode.languages.getDiagnostics()
-
- let diagnosticsDetails = ""
- for (const [uri, fileDiagnostics] of diagnostics) {
- const problems = fileDiagnostics.filter(
- (d) => d.severity === vscode.DiagnosticSeverity.Error || d.severity === vscode.DiagnosticSeverity.Warning
- )
- if (problems.length > 0) {
- diagnosticsDetails += `\nFile: ${path.relative(cwd, uri.fsPath)}`
- for (const diagnostic of problems) {
- let severity = diagnostic.severity === vscode.DiagnosticSeverity.Error ? "Error" : "Warning"
- const line = diagnostic.range.start.line + 1 // VSCode lines are 0-indexed
- const source = diagnostic.source ? `${diagnostic.source} ` : ""
- diagnosticsDetails += `\n- [${source}${severity}] Line ${line}: ${diagnostic.message}`
- }
- }
- }
-
- if (!diagnosticsDetails) {
+ const result = diagnosticsToProblemsString(diagnostics, cwd)
+ if (!result) {
return "No errors or warnings detected."
}
-
- return diagnosticsDetails.trim()
+ return result
}
diff --git a/src/utils/diagnostics.ts b/src/utils/diagnostics.ts
new file mode 100644
index 0000000..29129c9
--- /dev/null
+++ b/src/utils/diagnostics.ts
@@ -0,0 +1,90 @@
+import * as vscode from "vscode"
+import * as path from "path"
+import deepEqual from "fast-deep-equal"
+
+export function getNewDiagnostics(
+ oldDiagnostics: [vscode.Uri, vscode.Diagnostic[]][],
+ newDiagnostics: [vscode.Uri, vscode.Diagnostic[]][]
+): [vscode.Uri, vscode.Diagnostic[]][] {
+ const newProblems: [vscode.Uri, vscode.Diagnostic[]][] = []
+ const oldMap = new Map(oldDiagnostics)
+
+ for (const [uri, newDiags] of newDiagnostics) {
+ const oldDiags = oldMap.get(uri) || []
+ const newProblemsForUri = newDiags.filter((newDiag) => !oldDiags.some((oldDiag) => deepEqual(oldDiag, newDiag)))
+
+ if (newProblemsForUri.length > 0) {
+ newProblems.push([uri, newProblemsForUri])
+ }
+ }
+
+ return newProblems
+}
+
+// Usage:
+// const oldDiagnostics = // ... your old diagnostics array
+// const newDiagnostics = // ... your new diagnostics array
+// const newProblems = getNewDiagnostics(oldDiagnostics, newDiagnostics);
+
+// Example usage with mocks:
+//
+// // Mock old diagnostics
+// const oldDiagnostics: [vscode.Uri, vscode.Diagnostic[]][] = [
+// [vscode.Uri.file("/path/to/file1.ts"), [
+// new vscode.Diagnostic(new vscode.Range(0, 0, 0, 10), "Old error in file1", vscode.DiagnosticSeverity.Error)
+// ]],
+// [vscode.Uri.file("/path/to/file2.ts"), [
+// new vscode.Diagnostic(new vscode.Range(5, 5, 5, 15), "Old warning in file2", vscode.DiagnosticSeverity.Warning)
+// ]]
+// ];
+//
+// // Mock new diagnostics
+// const newDiagnostics: [vscode.Uri, vscode.Diagnostic[]][] = [
+// [vscode.Uri.file("/path/to/file1.ts"), [
+// new vscode.Diagnostic(new vscode.Range(0, 0, 0, 10), "Old error in file1", vscode.DiagnosticSeverity.Error),
+// new vscode.Diagnostic(new vscode.Range(2, 2, 2, 12), "New error in file1", vscode.DiagnosticSeverity.Error)
+// ]],
+// [vscode.Uri.file("/path/to/file2.ts"), [
+// new vscode.Diagnostic(new vscode.Range(5, 5, 5, 15), "Old warning in file2", vscode.DiagnosticSeverity.Warning)
+// ]],
+// [vscode.Uri.file("/path/to/file3.ts"), [
+// new vscode.Diagnostic(new vscode.Range(1, 1, 1, 11), "New error in file3", vscode.DiagnosticSeverity.Error)
+// ]]
+// ];
+//
+// const newProblems = getNewProblems(oldDiagnostics, newDiagnostics);
+//
+// console.log("New problems:");
+// for (const [uri, diagnostics] of newProblems) {
+// console.log(`File: ${uri.fsPath}`);
+// for (const diagnostic of diagnostics) {
+// console.log(`- ${diagnostic.message} (${diagnostic.range.start.line}:${diagnostic.range.start.character})`);
+// }
+// }
+//
+// // Expected output:
+// // New problems:
+// // File: /path/to/file1.ts
+// // - New error in file1 (2:2)
+// // File: /path/to/file3.ts
+// // - New error in file3 (1:1)
+
+// will return empty string if no errors/warnings
+export function diagnosticsToProblemsString(diagnostics: [vscode.Uri, vscode.Diagnostic[]][], cwd: string): string {
+ let result = ""
+ for (const [uri, fileDiagnostics] of diagnostics) {
+ const problems = fileDiagnostics.filter(
+ (d) => d.severity === vscode.DiagnosticSeverity.Error || d.severity === vscode.DiagnosticSeverity.Warning
+ )
+ if (problems.length > 0) {
+ result += `\n\n${path.relative(cwd, uri.fsPath)}`
+ for (const diagnostic of problems) {
+ let severity = diagnostic.severity === vscode.DiagnosticSeverity.Error ? "Error" : "Warning"
+ const line = diagnostic.range.start.line + 1 // VSCode lines are 0-indexed
+ const source = diagnostic.source ? `${diagnostic.source} ` : ""
+ result += `\n- [${source}${severity}] Line ${line}: ${diagnostic.message}`
+ }
+ }
+ }
+ return result.trim()
+}