@wrongstack/plug-lsp 0.269.0 → 0.272.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/index.js +164 -14
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -85,7 +85,7 @@ The primary interface for all LSP operations:
|
|
|
85
85
|
|
|
86
86
|
## Registered Tools
|
|
87
87
|
|
|
88
|
-
The plugin registers
|
|
88
|
+
The plugin registers 5 tools into WrongStack's tool system. These are kept because
|
|
89
89
|
LSP provides genuinely unique data or capability the agent cannot replicate with
|
|
90
90
|
basic tools (read, grep, edit) at comparable cost.
|
|
91
91
|
|
|
@@ -93,6 +93,7 @@ basic tools (read, grep, edit) at comparable cost.
|
|
|
93
93
|
|---|---|---|
|
|
94
94
|
| `lsp_diagnostics` | `auto` | Get type/lint diagnostics for a file or whole workspace |
|
|
95
95
|
| `lsp_definition` | `auto` | Jump to the definition of a symbol (more precise than grep) |
|
|
96
|
+
| `lsp_completion` | `auto` | Semantic completions for a cursor location, including live editor content when provided |
|
|
96
97
|
| `lsp_rename` | `confirm` | Safe semantic rename across the workspace |
|
|
97
98
|
| `codebase-lsp-search` | `auto` | Fast symbol search via WrongStack's index, with LSP fallback |
|
|
98
99
|
|
|
@@ -236,7 +237,7 @@ packages/plug-lsp/src/
|
|
|
236
237
|
│ ├── lsp-server.ts — per-server LSP client (one process per server)
|
|
237
238
|
│ ├── connection.ts — JSON-RPC 2.0 over stdio transport
|
|
238
239
|
│ └── lifecycle.ts — state machine for server lifecycle
|
|
239
|
-
├── tools/ —
|
|
240
|
+
├── tools/ — 5 LSP-backed agent tools (diagnostics, definition, completion, rename, codebase-search)
|
|
240
241
|
├── registry.ts — manages all server instances
|
|
241
242
|
├── document-tracker.ts — tracks open/edited files across sessions
|
|
242
243
|
├── auto-discover.ts — PATH and node_modules discovery
|
package/dist/index.js
CHANGED
|
@@ -593,7 +593,17 @@ var DocumentTracker = class {
|
|
|
593
593
|
const absPath = this.resolve(filePath);
|
|
594
594
|
const languageId = languageIdFor(absPath);
|
|
595
595
|
if (!languageId) return;
|
|
596
|
-
|
|
596
|
+
let text;
|
|
597
|
+
if (knownText !== void 0) {
|
|
598
|
+
text = knownText;
|
|
599
|
+
} else {
|
|
600
|
+
try {
|
|
601
|
+
text = await fs2.readFile(absPath, "utf8");
|
|
602
|
+
} catch (err) {
|
|
603
|
+
this.log.debug(`LSP tracker could not read file ${absPath}`, err);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
597
607
|
let doc = this.docs.get(absPath);
|
|
598
608
|
if (!doc) {
|
|
599
609
|
doc = {
|
|
@@ -606,6 +616,14 @@ var DocumentTracker = class {
|
|
|
606
616
|
};
|
|
607
617
|
this.docs.set(absPath, doc);
|
|
608
618
|
this.events?.emit("lsp.document.opened", { path: absPath, language: languageId });
|
|
619
|
+
} else if (knownText !== void 0 && knownText !== doc.text) {
|
|
620
|
+
doc.version++;
|
|
621
|
+
doc.text = knownText;
|
|
622
|
+
for (const server of this.registry().list()) {
|
|
623
|
+
if (server.state !== "ready" || !server.config.languages.includes(languageId)) continue;
|
|
624
|
+
if (!doc.serverNames.has(server.name)) continue;
|
|
625
|
+
server.notifyDidChange({ uri: doc.uri, version: doc.version }, knownText);
|
|
626
|
+
}
|
|
609
627
|
}
|
|
610
628
|
for (const server of this.registry().list()) {
|
|
611
629
|
if (server.state !== "ready" || !server.config.languages.includes(languageId)) continue;
|
|
@@ -1012,6 +1030,9 @@ var LSPServer = class {
|
|
|
1012
1030
|
async hover(params, timeoutMs, signal) {
|
|
1013
1031
|
return await this.request("textDocument/hover", params, timeoutMs, signal);
|
|
1014
1032
|
}
|
|
1033
|
+
async completion(params, timeoutMs, signal) {
|
|
1034
|
+
return await this.request("textDocument/completion", params, timeoutMs, signal);
|
|
1035
|
+
}
|
|
1015
1036
|
async documentSymbol(params, timeoutMs, signal) {
|
|
1016
1037
|
return await this.request("textDocument/documentSymbol", params, timeoutMs, signal);
|
|
1017
1038
|
}
|
|
@@ -1898,6 +1919,9 @@ function formatCodebaseLspResults(output, cwd) {
|
|
|
1898
1919
|
}
|
|
1899
1920
|
|
|
1900
1921
|
// src/server/capabilities.ts
|
|
1922
|
+
function supportsCompletion(cap) {
|
|
1923
|
+
return !!cap.completionProvider;
|
|
1924
|
+
}
|
|
1901
1925
|
function supportsDefinition(cap) {
|
|
1902
1926
|
return !!cap.definitionProvider;
|
|
1903
1927
|
}
|
|
@@ -2116,18 +2140,6 @@ function deduplicateByKey(items) {
|
|
|
2116
2140
|
});
|
|
2117
2141
|
}
|
|
2118
2142
|
|
|
2119
|
-
// src/formatters/location.ts
|
|
2120
|
-
function formatLocations(locations, cwd, limit = 100) {
|
|
2121
|
-
if (!locations || locations.length === 0) return "No locations found.";
|
|
2122
|
-
const lines = locations.slice(0, limit).map((loc) => {
|
|
2123
|
-
const uri = "uri" in loc ? loc.uri : loc.targetUri;
|
|
2124
|
-
const range = "range" in loc ? loc.range : loc.targetSelectionRange;
|
|
2125
|
-
return `${displayPath(uriToPath(uri), cwd)}:${range.start.line + 1}:${range.start.character + 1}`;
|
|
2126
|
-
});
|
|
2127
|
-
if (locations.length > limit) lines.push(`... truncated ${locations.length - limit} more`);
|
|
2128
|
-
return lines.join("\n");
|
|
2129
|
-
}
|
|
2130
|
-
|
|
2131
2143
|
// src/position.ts
|
|
2132
2144
|
function humanToLSP(content, pos) {
|
|
2133
2145
|
const lines = splitLines(content);
|
|
@@ -2153,6 +2165,143 @@ function clamp(n, min, max) {
|
|
|
2153
2165
|
return Math.max(min, Math.min(max, n));
|
|
2154
2166
|
}
|
|
2155
2167
|
|
|
2168
|
+
// src/tools/completion.ts
|
|
2169
|
+
function createCompletionTool(deps) {
|
|
2170
|
+
return {
|
|
2171
|
+
name: "lsp_completion",
|
|
2172
|
+
description: "Get semantic code completions from a configured language server.",
|
|
2173
|
+
usageHint: 'Use for context-aware completion at a cursor location. Lines and columns are 1-based; pass trigger_character for member access like ".".',
|
|
2174
|
+
inputSchema: {
|
|
2175
|
+
type: "object",
|
|
2176
|
+
properties: {
|
|
2177
|
+
path: { type: "string" },
|
|
2178
|
+
line: { type: "integer" },
|
|
2179
|
+
character: { type: "integer" },
|
|
2180
|
+
content: { type: "string", maxLength: 5e5 },
|
|
2181
|
+
limit: { type: "integer", minimum: 1, maximum: 100 },
|
|
2182
|
+
trigger_character: { type: "string" },
|
|
2183
|
+
format: { type: "string", enum: ["text", "json"] }
|
|
2184
|
+
},
|
|
2185
|
+
required: ["path", "line", "character"]
|
|
2186
|
+
},
|
|
2187
|
+
permission: "auto",
|
|
2188
|
+
mutating: false,
|
|
2189
|
+
timeoutMs: LSP_CONSTANTS.TOOL_TIMEOUT_MS,
|
|
2190
|
+
maxOutputBytes: 32768,
|
|
2191
|
+
async execute(input, ctx, opts) {
|
|
2192
|
+
try {
|
|
2193
|
+
const file = resolveInputPath(input.path, ctx);
|
|
2194
|
+
const server = await requireServer(deps.registry, file, opts.signal);
|
|
2195
|
+
if (server.capabilities && !supportsCompletion(server.capabilities)) {
|
|
2196
|
+
throw new LSPError(
|
|
2197
|
+
"LSP_CAPABILITY_MISSING" /* CapabilityMissing */,
|
|
2198
|
+
`Server "${server.name}" does not support completion`
|
|
2199
|
+
);
|
|
2200
|
+
}
|
|
2201
|
+
const content = typeof input.content === "string" ? input.content : await readDocumentContent(file, deps.tracker);
|
|
2202
|
+
await deps.tracker.open(file, content);
|
|
2203
|
+
const position = humanToLSP(content, { line: input.line, character: input.character });
|
|
2204
|
+
const result = await server.completion(
|
|
2205
|
+
{
|
|
2206
|
+
textDocument: { uri: pathToUri(file) },
|
|
2207
|
+
position,
|
|
2208
|
+
context: input.trigger_character ? { triggerKind: 2, triggerCharacter: input.trigger_character } : { triggerKind: 1 }
|
|
2209
|
+
},
|
|
2210
|
+
LSP_CONSTANTS.TOOL_TIMEOUT_MS,
|
|
2211
|
+
opts.signal
|
|
2212
|
+
);
|
|
2213
|
+
const items = collectCompletionItems(result, Math.min(input.limit ?? 25, 100));
|
|
2214
|
+
if (input.format === "json") {
|
|
2215
|
+
return JSON.stringify({
|
|
2216
|
+
items: items.map((item) => ({
|
|
2217
|
+
label: item.label,
|
|
2218
|
+
insertText: item.insertText ?? item.label,
|
|
2219
|
+
kind: item.kind ? completionKindName(item.kind) : void 0,
|
|
2220
|
+
detail: compact(item.detail),
|
|
2221
|
+
documentation: compact(documentationText(item.documentation))
|
|
2222
|
+
}))
|
|
2223
|
+
});
|
|
2224
|
+
}
|
|
2225
|
+
return formatCompletionItems(items, result);
|
|
2226
|
+
} catch (err) {
|
|
2227
|
+
return stringifyToolError(err);
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
};
|
|
2231
|
+
}
|
|
2232
|
+
function collectCompletionItems(result, limit) {
|
|
2233
|
+
const items = Array.isArray(result) ? result : result?.items ?? [];
|
|
2234
|
+
return items.slice(0, limit);
|
|
2235
|
+
}
|
|
2236
|
+
function formatCompletionItems(visibleItems, result) {
|
|
2237
|
+
const items = Array.isArray(result) ? result : result?.items ?? [];
|
|
2238
|
+
if (items.length === 0) return "No completions found.";
|
|
2239
|
+
const lines = visibleItems.map((item, index) => {
|
|
2240
|
+
const label = item.label || item.insertText || "(unnamed)";
|
|
2241
|
+
const kind = item.kind ? completionKindName(item.kind) : "Completion";
|
|
2242
|
+
const detail = compact(item.detail);
|
|
2243
|
+
const docs = compact(documentationText(item.documentation));
|
|
2244
|
+
const suffix = [detail, docs].filter(Boolean).join(" \u2014 ");
|
|
2245
|
+
return `${index + 1}. ${label} [${kind}]${suffix ? ` \u2014 ${suffix}` : ""}`;
|
|
2246
|
+
});
|
|
2247
|
+
if (items.length > visibleItems.length) {
|
|
2248
|
+
lines.push(`... truncated ${items.length - visibleItems.length} more`);
|
|
2249
|
+
}
|
|
2250
|
+
return lines.join("\n");
|
|
2251
|
+
}
|
|
2252
|
+
function documentationText(value) {
|
|
2253
|
+
if (!value) return void 0;
|
|
2254
|
+
if (typeof value === "string") return value;
|
|
2255
|
+
return value.value;
|
|
2256
|
+
}
|
|
2257
|
+
function compact(value) {
|
|
2258
|
+
const cleaned = value?.replace(/\s*\r?\n\s*/g, " ").trim();
|
|
2259
|
+
if (!cleaned) return void 0;
|
|
2260
|
+
return cleaned.length <= 160 ? cleaned : `${cleaned.slice(0, 157)}...`;
|
|
2261
|
+
}
|
|
2262
|
+
function completionKindName(kind) {
|
|
2263
|
+
const names = {
|
|
2264
|
+
1: "Text",
|
|
2265
|
+
2: "Method",
|
|
2266
|
+
3: "Function",
|
|
2267
|
+
4: "Constructor",
|
|
2268
|
+
5: "Field",
|
|
2269
|
+
6: "Variable",
|
|
2270
|
+
7: "Class",
|
|
2271
|
+
8: "Interface",
|
|
2272
|
+
9: "Module",
|
|
2273
|
+
10: "Property",
|
|
2274
|
+
11: "Unit",
|
|
2275
|
+
12: "Value",
|
|
2276
|
+
13: "Enum",
|
|
2277
|
+
14: "Keyword",
|
|
2278
|
+
15: "Snippet",
|
|
2279
|
+
16: "Color",
|
|
2280
|
+
17: "File",
|
|
2281
|
+
18: "Reference",
|
|
2282
|
+
19: "Folder",
|
|
2283
|
+
20: "EnumMember",
|
|
2284
|
+
21: "Constant",
|
|
2285
|
+
22: "Struct",
|
|
2286
|
+
23: "Event",
|
|
2287
|
+
24: "Operator",
|
|
2288
|
+
25: "TypeParameter"
|
|
2289
|
+
};
|
|
2290
|
+
return names[kind] ?? `Kind ${kind}`;
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
// src/formatters/location.ts
|
|
2294
|
+
function formatLocations(locations, cwd, limit = 100) {
|
|
2295
|
+
if (!locations || locations.length === 0) return "No locations found.";
|
|
2296
|
+
const lines = locations.slice(0, limit).map((loc) => {
|
|
2297
|
+
const uri = "uri" in loc ? loc.uri : loc.targetUri;
|
|
2298
|
+
const range = "range" in loc ? loc.range : loc.targetSelectionRange;
|
|
2299
|
+
return `${displayPath(uriToPath(uri), cwd)}:${range.start.line + 1}:${range.start.character + 1}`;
|
|
2300
|
+
});
|
|
2301
|
+
if (locations.length > limit) lines.push(`... truncated ${locations.length - limit} more`);
|
|
2302
|
+
return lines.join("\n");
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2156
2305
|
// src/tools/definition.ts
|
|
2157
2306
|
function createDefinitionTool(deps) {
|
|
2158
2307
|
return {
|
|
@@ -2372,6 +2521,7 @@ function makeLSPTools(deps) {
|
|
|
2372
2521
|
return [
|
|
2373
2522
|
createDiagnosticsTool(deps),
|
|
2374
2523
|
createDefinitionTool(deps),
|
|
2524
|
+
createCompletionTool(deps),
|
|
2375
2525
|
createCodebaseLspSearchTool(deps),
|
|
2376
2526
|
createRenameTool(deps)
|
|
2377
2527
|
];
|
|
@@ -2414,7 +2564,7 @@ var plugin = {
|
|
|
2414
2564
|
void tracker.forceCloseAll().finally(() => registry.shutdown());
|
|
2415
2565
|
}),
|
|
2416
2566
|
api.events.on("tool.executed", (event) => {
|
|
2417
|
-
void tracker.handleToolExecuted(event);
|
|
2567
|
+
void tracker.handleToolExecuted(event).catch((err) => api.log.debug("LSP tracker failed to handle tool event", err));
|
|
2418
2568
|
})
|
|
2419
2569
|
];
|
|
2420
2570
|
teardownState = {
|