summaryrefslogtreecommitdiffhomepage
path: root/packages/tool-edit-file
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-24 16:48:46 +0900
committerAdam Malczewski <[email protected]>2026-06-24 16:48:46 +0900
commit8f6114be790016bd954fcfccbe80a88bd0cb758e (patch)
tree6be223628e35ce83759314f6fcce2161daa370ba /packages/tool-edit-file
parent4935c268dd53592ec264c1b3eaa9805b3e069df5 (diff)
downloaddispatch-8f6114be790016bd954fcfccbe80a88bd0cb758e.tar.gz
dispatch-8f6114be790016bd954fcfccbe80a88bd0cb758e.zip
feat(lsp+tool-edit-file): multi-server diagnostics + per-edit auto-append
LSP extension: - Multi-server aggregation: query ALL connected servers matching the file's extension (not just the first), merge diagnostics tagged by source - Incremental sync: capture each server's textDocumentSync.change during initialize; compute prefix/suffix diff ranges for change:2 servers; full content for change:1 (generic, works for any LSP) - New diff.ts: pure computeChangeRange + offsetToPosition (O(n), tested) - Buffer sync: change(filePath, newText) sends didChange with post-edit in-memory content; openWithText for first open; tracks open doc text - languageId mapping: extended with .rb/.rbs/.c/.cpp/etc. (was 'unknown') - waitForDiagnostics: accepts text override + timeoutMs; returns { formatted, slow, timedOut }; polls for publishDiagnostics push - DiagnosticsStore: hasReceivedPush/clearReceived tracking; formatFiltered with minSeverity (1=Error, 2=Warning) for edit_file integration - LspService.getDiagnostics: service method for cross-extension use tool-edit-file: - After successful edit, calls LSP getDiagnostics with post-edit buffer - Only appends diagnostics with severity ≤ 2 (errors+warnings, no noise) - Appends slow warning (>10s): 'LSP is taking unusually long...' - 60s timeout; graceful degradation when no LSP available - Optional dep on @dispatch/lsp (getService pattern, not manifest depOn) 1468 vitest pass (was 1453, +15 new diff tests).
Diffstat (limited to 'packages/tool-edit-file')
-rw-r--r--packages/tool-edit-file/package.json3
-rw-r--r--packages/tool-edit-file/src/edit-file.ts55
-rw-r--r--packages/tool-edit-file/src/extension.ts20
-rw-r--r--packages/tool-edit-file/tsconfig.json2
4 files changed, 74 insertions, 6 deletions
diff --git a/packages/tool-edit-file/package.json b/packages/tool-edit-file/package.json
index f71ad80..7194cce 100644
--- a/packages/tool-edit-file/package.json
+++ b/packages/tool-edit-file/package.json
@@ -6,6 +6,7 @@
"main": "dist/index.js",
"types": "dist/index.d.ts",
"dependencies": {
- "@dispatch/kernel": "workspace:*"
+ "@dispatch/kernel": "workspace:*",
+ "@dispatch/lsp": "workspace:*"
}
}
diff --git a/packages/tool-edit-file/src/edit-file.ts b/packages/tool-edit-file/src/edit-file.ts
index 36f99e0..1719ea3 100644
--- a/packages/tool-edit-file/src/edit-file.ts
+++ b/packages/tool-edit-file/src/edit-file.ts
@@ -103,13 +103,35 @@ export function computeReplacement(
};
}
+// --- Diagnostics hook ---
+
+/**
+ * Optional post-edit diagnostics hook. Returns formatted diagnostics string
+ * (empty if none) + timing metadata. Injected by the extension from the LSP
+ * service; absent when no LSP is available (graceful degradation).
+ */
+export type DiagnosticsHook = (opts: {
+ readonly filePath: string;
+ readonly text: string;
+ readonly cwd: string;
+}) => Promise<{
+ readonly formatted: string;
+ readonly slow: boolean;
+ readonly timedOut: boolean;
+}>;
+
// --- Shell / edge ---
/**
* Factory: create an edit_file ToolContract bound to a working directory.
* The working directory is injected so the tool is testable.
+ * `diagnostics` is optional — when provided, errors+warnings from LSP servers
+ * are appended to successful edit results (only when errors exist).
*/
-export function createEditFileTool(workingDirectory: string): ToolContract {
+export function createEditFileTool(
+ workingDirectory: string,
+ diagnostics?: DiagnosticsHook,
+): ToolContract {
const workdir = resolve(workingDirectory);
return {
@@ -202,7 +224,36 @@ export function createEditFileTool(workingDirectory: string): ToolContract {
}
const plural = result.count === 1 ? "" : "s";
- return { content: `Replaced ${result.count} occurrence${plural} in "${relPath}".` };
+ let baseContent = `Replaced ${result.count} occurrence${plural} in "${relPath}".`;
+
+ // After a successful edit, query LSP diagnostics (if available).
+ // Only append if there are actual errors/warnings (no noise on clean edits).
+ if (diagnostics) {
+ try {
+ const cwd = ctx.cwd ?? process.cwd();
+ const diag = await diagnostics({
+ filePath: resolvedPath,
+ text: result.content,
+ cwd,
+ });
+ const suffix: string[] = [];
+ if (diag.slow) {
+ suffix.push(
+ "⚠️ LSP is taking unusually long. If this happens more than once, raise it to the user.",
+ );
+ }
+ if (diag.formatted) {
+ suffix.push(diag.formatted);
+ }
+ if (suffix.length > 0) {
+ baseContent += `\n\n${suffix.join("\n\n")}`;
+ }
+ } catch {
+ // LSP diagnostics failure is non-fatal — the edit already succeeded.
+ }
+ }
+
+ return { content: baseContent };
},
};
}
diff --git a/packages/tool-edit-file/src/extension.ts b/packages/tool-edit-file/src/extension.ts
index a4bb19e..bbd8256 100644
--- a/packages/tool-edit-file/src/extension.ts
+++ b/packages/tool-edit-file/src/extension.ts
@@ -1,5 +1,6 @@
import type { Extension } from "@dispatch/kernel";
-import { createEditFileTool } from "./edit-file.js";
+import { lspServiceHandle } from "@dispatch/lsp";
+import { createEditFileTool, type DiagnosticsHook } from "./edit-file.js";
export const extension: Extension = {
manifest: {
@@ -13,6 +14,21 @@ export const extension: Extension = {
contributes: { tools: ["edit_file"] },
},
activate(host) {
- host.defineTool(createEditFileTool(process.cwd()));
+ // Optional LSP integration: if the lsp extension is loaded, wire its
+ // getDiagnostics service as the post-edit diagnostics hook. If absent,
+ // edits proceed without diagnostics (graceful degradation).
+ const lspService = host.getService(lspServiceHandle);
+ const diagnostics: DiagnosticsHook | undefined = lspService
+ ? async (opts) =>
+ lspService.getDiagnostics({
+ filePath: opts.filePath,
+ text: opts.text,
+ cwd: opts.cwd,
+ timeoutMs: 60_000,
+ minSeverity: 2, // errors + warnings only
+ })
+ : undefined;
+
+ host.defineTool(createEditFileTool(process.cwd(), diagnostics));
},
};
diff --git a/packages/tool-edit-file/tsconfig.json b/packages/tool-edit-file/tsconfig.json
index ff99a43..38a7610 100644
--- a/packages/tool-edit-file/tsconfig.json
+++ b/packages/tool-edit-file/tsconfig.json
@@ -2,5 +2,5 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": { "rootDir": "src", "outDir": "dist", "composite": true },
"include": ["src/**/*.ts"],
- "references": [{ "path": "../kernel" }]
+ "references": [{ "path": "../kernel" }, { "path": "../lsp" }]
}