opencodekit 0.12.0 → 0.12.2
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/dist/index.js +43 -15
- package/dist/template/.opencode/AGENTS.md +33 -23
- package/dist/template/.opencode/agent/explore.md +6 -6
- package/dist/template/.opencode/command/brainstorm.md +1 -1
- package/dist/template/.opencode/command/fix-types.md +3 -3
- package/dist/template/.opencode/command/new-feature.md +1 -1
- package/dist/template/.opencode/command/research-ui.md +1 -1
- package/dist/template/.opencode/command/research.md +7 -3
- package/dist/template/.opencode/command/resume.md +11 -2
- package/dist/template/.opencode/command/review-codebase.md +1 -1
- package/dist/template/.opencode/command/status.md +3 -0
- package/dist/template/.opencode/command/triage.md +4 -0
- package/dist/template/.opencode/dcp.jsonc +16 -33
- package/dist/template/.opencode/memory/project/commands.md +44 -5
- package/dist/template/.opencode/memory/project/conventions.md +104 -5
- package/dist/template/.opencode/memory/project/gotchas.md +43 -9
- package/dist/template/.opencode/opencode.json +48 -17
- package/dist/template/.opencode/package.json +1 -1
- package/dist/template/.opencode/plugin/sessions.ts +295 -38
- package/package.json +1 -1
- package/dist/template/.opencode/tool/lsp.ts +0 -786
|
@@ -1,786 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LSP Refactor Tools for OpenCode
|
|
3
|
-
* Semantic code refactoring using Language Server Protocol
|
|
4
|
-
*
|
|
5
|
-
* Provides: lsp_rename, lsp_code_actions, lsp_code_action_apply, lsp_organize_imports
|
|
6
|
-
*
|
|
7
|
-
* Uses OpenCode's bundled LSP servers when available, falls back to system-installed servers.
|
|
8
|
-
* @see https://opencode.ai/docs/lsp/
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { resolve } from "node:path";
|
|
12
|
-
import { tool } from "@opencode-ai/plugin";
|
|
13
|
-
import { lspManager } from "../lib/lsp/client";
|
|
14
|
-
import { resolveServer } from "../lib/lsp/config";
|
|
15
|
-
import type {
|
|
16
|
-
CodeAction,
|
|
17
|
-
Diagnostic,
|
|
18
|
-
DocumentSymbol,
|
|
19
|
-
HoverResult,
|
|
20
|
-
Location,
|
|
21
|
-
LocationLink,
|
|
22
|
-
PrepareRenameResult,
|
|
23
|
-
SymbolInfo,
|
|
24
|
-
WorkspaceEdit,
|
|
25
|
-
} from "../lib/lsp/types";
|
|
26
|
-
import {
|
|
27
|
-
applyWorkspaceEdit,
|
|
28
|
-
formatWorkspaceEditPreview,
|
|
29
|
-
} from "../lib/lsp/utils";
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Rename a symbol across the entire codebase using LSP
|
|
33
|
-
*/
|
|
34
|
-
export const lsp_rename = tool({
|
|
35
|
-
description: `Rename a symbol (function, variable, class, etc.) across the entire codebase using LSP.
|
|
36
|
-
|
|
37
|
-
This tool performs semantic renaming - it understands code structure and renames all references correctly, including:
|
|
38
|
-
- Function/method definitions and all their calls
|
|
39
|
-
- Variable declarations and usages
|
|
40
|
-
- Class names and their instantiations
|
|
41
|
-
- Import/export statements
|
|
42
|
-
|
|
43
|
-
IMPORTANT: This tool directly modifies files. Review the changes shown in output.`,
|
|
44
|
-
|
|
45
|
-
args: {
|
|
46
|
-
filePath: tool.schema
|
|
47
|
-
.string()
|
|
48
|
-
.describe("Path to the file containing the symbol to rename"),
|
|
49
|
-
line: tool.schema
|
|
50
|
-
.number()
|
|
51
|
-
.describe("Line number (1-based) where the symbol is located"),
|
|
52
|
-
character: tool.schema
|
|
53
|
-
.number()
|
|
54
|
-
.describe("Character position (0-based) within the line"),
|
|
55
|
-
newName: tool.schema.string().describe("The new name for the symbol"),
|
|
56
|
-
},
|
|
57
|
-
|
|
58
|
-
async execute(args) {
|
|
59
|
-
const cwd = process.cwd();
|
|
60
|
-
const absPath = resolve(cwd, args.filePath);
|
|
61
|
-
const server = resolveServer(absPath);
|
|
62
|
-
|
|
63
|
-
if (!server) {
|
|
64
|
-
return `Error: No LSP server available for file type: ${absPath}
|
|
65
|
-
|
|
66
|
-
OpenCode supports 30+ languages with built-in LSP servers.
|
|
67
|
-
See https://opencode.ai/docs/lsp/ for the full list.
|
|
68
|
-
|
|
69
|
-
Common servers:
|
|
70
|
-
- TypeScript: Requires 'typescript' in project dependencies
|
|
71
|
-
- Python: Requires 'pyright' dependency installed
|
|
72
|
-
- Go: Requires 'go' command available
|
|
73
|
-
- Rust: Requires 'rust-analyzer' command available
|
|
74
|
-
- Java: Requires Java SDK 21+ installed (OpenCode auto-installs jdtls)
|
|
75
|
-
- C/C++: Requires 'clangd' available (OpenCode auto-installs for C/C++ projects)`;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
const client = await lspManager.getClient(cwd, server);
|
|
80
|
-
|
|
81
|
-
const prepareResult = (await client.prepareRename(
|
|
82
|
-
absPath,
|
|
83
|
-
args.line,
|
|
84
|
-
args.character,
|
|
85
|
-
)) as PrepareRenameResult | null;
|
|
86
|
-
if (!prepareResult) {
|
|
87
|
-
lspManager.releaseClient(cwd, server.id);
|
|
88
|
-
return "Error: Cannot rename at this position. Make sure the cursor is on a valid symbol.";
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const result = (await client.rename(
|
|
92
|
-
absPath,
|
|
93
|
-
args.line,
|
|
94
|
-
args.character,
|
|
95
|
-
args.newName,
|
|
96
|
-
)) as WorkspaceEdit | null;
|
|
97
|
-
lspManager.releaseClient(cwd, server.id);
|
|
98
|
-
|
|
99
|
-
if (!result) {
|
|
100
|
-
return "Error: Rename returned no changes.";
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const preview = formatWorkspaceEditPreview(result);
|
|
104
|
-
const applyResult = applyWorkspaceEdit(result);
|
|
105
|
-
|
|
106
|
-
if (!applyResult.success) {
|
|
107
|
-
return `Error applying changes: ${applyResult.error}\n\nPlanned changes:\n${preview}`;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return `Successfully renamed to "${args.newName}".
|
|
111
|
-
|
|
112
|
-
Files modified:
|
|
113
|
-
${applyResult.filesModified.map((f) => ` - ${f}`).join("\n")}
|
|
114
|
-
|
|
115
|
-
Note: Files were modified directly. Re-read before editing.`;
|
|
116
|
-
} catch (error) {
|
|
117
|
-
return `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Get available code actions for a code range
|
|
124
|
-
*/
|
|
125
|
-
export const lsp_code_actions = tool({
|
|
126
|
-
description: `Get available code actions (quick fixes, refactorings) for a code range.
|
|
127
|
-
|
|
128
|
-
This tool lists available actions like:
|
|
129
|
-
- Quick fixes for diagnostics/errors
|
|
130
|
-
- Refactoring options (extract function, inline variable, etc.)
|
|
131
|
-
- Source actions (organize imports, fix all)
|
|
132
|
-
|
|
133
|
-
Use lsp_code_action_apply to execute a specific action.`,
|
|
134
|
-
|
|
135
|
-
args: {
|
|
136
|
-
filePath: tool.schema.string().describe("Path to the file"),
|
|
137
|
-
startLine: tool.schema.number().describe("Start line (1-based)"),
|
|
138
|
-
startCharacter: tool.schema.number().describe("Start character (0-based)"),
|
|
139
|
-
endLine: tool.schema.number().describe("End line (1-based)"),
|
|
140
|
-
endCharacter: tool.schema.number().describe("End character (0-based)"),
|
|
141
|
-
only: tool.schema
|
|
142
|
-
.array(tool.schema.string())
|
|
143
|
-
.optional()
|
|
144
|
-
.describe(
|
|
145
|
-
"Filter by action kind: 'quickfix', 'refactor', 'refactor.extract', 'source.organizeImports'",
|
|
146
|
-
),
|
|
147
|
-
},
|
|
148
|
-
|
|
149
|
-
async execute(args) {
|
|
150
|
-
const cwd = process.cwd();
|
|
151
|
-
const absPath = resolve(cwd, args.filePath);
|
|
152
|
-
const server = resolveServer(absPath);
|
|
153
|
-
|
|
154
|
-
if (!server) {
|
|
155
|
-
return `Error: No LSP server available for file type: ${absPath}`;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
try {
|
|
159
|
-
const client = await lspManager.getClient(cwd, server);
|
|
160
|
-
const result = (await client.codeAction(
|
|
161
|
-
absPath,
|
|
162
|
-
args.startLine,
|
|
163
|
-
args.startCharacter,
|
|
164
|
-
args.endLine,
|
|
165
|
-
args.endCharacter,
|
|
166
|
-
args.only,
|
|
167
|
-
)) as CodeAction[] | null;
|
|
168
|
-
lspManager.releaseClient(cwd, server.id);
|
|
169
|
-
|
|
170
|
-
if (!result || result.length === 0) {
|
|
171
|
-
return "No code actions available for this range.";
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const actions = result.map((action, index) => {
|
|
175
|
-
const parts = [`${index + 1}. ${action.title}`];
|
|
176
|
-
if (action.kind) parts.push(` Kind: ${action.kind}`);
|
|
177
|
-
if (action.isPreferred) parts.push(" (Preferred)");
|
|
178
|
-
if (action.disabled)
|
|
179
|
-
parts.push(` (Disabled: ${action.disabled.reason})`);
|
|
180
|
-
return parts.join("\n");
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
return `Available code actions:\n\n${actions.join("\n\n")}\n\nUse lsp_refactor_code_action_apply with the action index to apply.`;
|
|
184
|
-
} catch (error) {
|
|
185
|
-
return `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
186
|
-
}
|
|
187
|
-
},
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Apply a specific code action
|
|
192
|
-
*/
|
|
193
|
-
export const lsp_code_action_apply = tool({
|
|
194
|
-
description: `Apply a specific code action from lsp_refactor_code_actions.
|
|
195
|
-
|
|
196
|
-
First call lsp_refactor_code_actions to see available actions, then use this tool with the action index to apply it.`,
|
|
197
|
-
|
|
198
|
-
args: {
|
|
199
|
-
filePath: tool.schema.string().describe("Path to the file"),
|
|
200
|
-
startLine: tool.schema.number().describe("Start line (1-based)"),
|
|
201
|
-
startCharacter: tool.schema.number().describe("Start character (0-based)"),
|
|
202
|
-
endLine: tool.schema.number().describe("End line (1-based)"),
|
|
203
|
-
endCharacter: tool.schema.number().describe("End character (0-based)"),
|
|
204
|
-
actionIndex: tool.schema
|
|
205
|
-
.number()
|
|
206
|
-
.describe(
|
|
207
|
-
"Index of the action to apply (1-based, from lsp_refactor_code_actions output)",
|
|
208
|
-
),
|
|
209
|
-
only: tool.schema
|
|
210
|
-
.array(tool.schema.string())
|
|
211
|
-
.optional()
|
|
212
|
-
.describe(
|
|
213
|
-
"Filter by action kind (same filter used in lsp_refactor_code_actions)",
|
|
214
|
-
),
|
|
215
|
-
},
|
|
216
|
-
|
|
217
|
-
async execute(args) {
|
|
218
|
-
const cwd = process.cwd();
|
|
219
|
-
const absPath = resolve(cwd, args.filePath);
|
|
220
|
-
const server = resolveServer(absPath);
|
|
221
|
-
|
|
222
|
-
if (!server) {
|
|
223
|
-
return `Error: No LSP server available for file type: ${absPath}`;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
try {
|
|
227
|
-
const client = await lspManager.getClient(cwd, server);
|
|
228
|
-
const actions = (await client.codeAction(
|
|
229
|
-
absPath,
|
|
230
|
-
args.startLine,
|
|
231
|
-
args.startCharacter,
|
|
232
|
-
args.endLine,
|
|
233
|
-
args.endCharacter,
|
|
234
|
-
args.only,
|
|
235
|
-
)) as CodeAction[] | null;
|
|
236
|
-
|
|
237
|
-
if (!actions || actions.length === 0) {
|
|
238
|
-
lspManager.releaseClient(cwd, server.id);
|
|
239
|
-
return "No code actions available for this range.";
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (args.actionIndex < 1 || args.actionIndex > actions.length) {
|
|
243
|
-
lspManager.releaseClient(cwd, server.id);
|
|
244
|
-
return `Invalid action index. Available: 1-${actions.length}`;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
let action = actions[args.actionIndex - 1];
|
|
248
|
-
|
|
249
|
-
if (action.data && !action.edit) {
|
|
250
|
-
action = (await client.codeActionResolve(action)) as CodeAction;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
lspManager.releaseClient(cwd, server.id);
|
|
254
|
-
|
|
255
|
-
if (!action.edit) {
|
|
256
|
-
if (action.command) {
|
|
257
|
-
return `Action "${action.title}" has a command instead of edits. Commands are not supported yet.`;
|
|
258
|
-
}
|
|
259
|
-
return `Action "${action.title}" produced no edits.`;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const applyResult = applyWorkspaceEdit(action.edit);
|
|
263
|
-
|
|
264
|
-
if (!applyResult.success) {
|
|
265
|
-
return `Error applying action: ${applyResult.error}`;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
return `Applied: "${action.title}"
|
|
269
|
-
|
|
270
|
-
Files modified:
|
|
271
|
-
${applyResult.filesModified.map((f) => ` - ${f}`).join("\n")}
|
|
272
|
-
|
|
273
|
-
Note: Files were modified directly. Re-read before editing.`;
|
|
274
|
-
} catch (error) {
|
|
275
|
-
return `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
276
|
-
}
|
|
277
|
-
},
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Organize imports in a file
|
|
282
|
-
*/
|
|
283
|
-
export const lsp_organize_imports = tool({
|
|
284
|
-
description: `Organize imports in a file using LSP.
|
|
285
|
-
|
|
286
|
-
This removes unused imports and sorts/groups remaining imports according to language conventions.`,
|
|
287
|
-
|
|
288
|
-
args: {
|
|
289
|
-
filePath: tool.schema.string().describe("Path to the file"),
|
|
290
|
-
},
|
|
291
|
-
|
|
292
|
-
async execute(args) {
|
|
293
|
-
const cwd = process.cwd();
|
|
294
|
-
const absPath = resolve(cwd, args.filePath);
|
|
295
|
-
const server = resolveServer(absPath);
|
|
296
|
-
|
|
297
|
-
if (!server) {
|
|
298
|
-
return `Error: No LSP server available for file type: ${absPath}`;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
try {
|
|
302
|
-
const client = await lspManager.getClient(cwd, server);
|
|
303
|
-
const actions = (await client.codeAction(absPath, 1, 0, 10000, 0, [
|
|
304
|
-
"source.organizeImports",
|
|
305
|
-
])) as CodeAction[] | null;
|
|
306
|
-
|
|
307
|
-
if (!actions || actions.length === 0) {
|
|
308
|
-
lspManager.releaseClient(cwd, server.id);
|
|
309
|
-
return "No organize imports action available for this file.";
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
let action = actions[0];
|
|
313
|
-
if (action.data && !action.edit) {
|
|
314
|
-
action = (await client.codeActionResolve(action)) as CodeAction;
|
|
315
|
-
}
|
|
316
|
-
lspManager.releaseClient(cwd, server.id);
|
|
317
|
-
|
|
318
|
-
if (!action.edit) {
|
|
319
|
-
return "Organize imports produced no changes (imports may already be organized).";
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const applyResult = applyWorkspaceEdit(action.edit);
|
|
323
|
-
|
|
324
|
-
if (!applyResult.success) {
|
|
325
|
-
return `Error organizing imports: ${applyResult.error}`;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
return `Imports organized in ${args.filePath}
|
|
329
|
-
|
|
330
|
-
Note: File was modified directly. Re-read before editing.`;
|
|
331
|
-
} catch (error) {
|
|
332
|
-
return `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
333
|
-
}
|
|
334
|
-
},
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Get hover information (type/documentation) at a position
|
|
339
|
-
*/
|
|
340
|
-
export const lsp_hover = tool({
|
|
341
|
-
description: `Get type information and documentation for a symbol at a specific position.
|
|
342
|
-
|
|
343
|
-
Returns type signatures, JSDoc/docstrings, and other hover information the language server provides.
|
|
344
|
-
Useful for understanding what a variable/function is without reading its definition.`,
|
|
345
|
-
|
|
346
|
-
args: {
|
|
347
|
-
filePath: tool.schema.string().describe("Path to the file"),
|
|
348
|
-
line: tool.schema.number().describe("Line number (1-based)"),
|
|
349
|
-
character: tool.schema.number().describe("Character position (0-based)"),
|
|
350
|
-
},
|
|
351
|
-
|
|
352
|
-
async execute(args) {
|
|
353
|
-
const cwd = process.cwd();
|
|
354
|
-
const absPath = resolve(cwd, args.filePath);
|
|
355
|
-
const server = resolveServer(absPath);
|
|
356
|
-
|
|
357
|
-
if (!server) {
|
|
358
|
-
return `Error: No LSP server available for file type: ${absPath}`;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
try {
|
|
362
|
-
const client = await lspManager.getClient(cwd, server);
|
|
363
|
-
const result = (await client.hover(
|
|
364
|
-
absPath,
|
|
365
|
-
args.line,
|
|
366
|
-
args.character,
|
|
367
|
-
)) as HoverResult | null;
|
|
368
|
-
lspManager.releaseClient(cwd, server.id);
|
|
369
|
-
|
|
370
|
-
if (!result) {
|
|
371
|
-
return "No hover information available at this position.";
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
return formatHoverResult(result);
|
|
375
|
-
} catch (error) {
|
|
376
|
-
return `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
377
|
-
}
|
|
378
|
-
},
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
* Go to definition of a symbol
|
|
383
|
-
*/
|
|
384
|
-
export const lsp_goto_definition = tool({
|
|
385
|
-
description: `Find the definition location of a symbol at a specific position.
|
|
386
|
-
|
|
387
|
-
Returns the file path and position where the symbol is defined.
|
|
388
|
-
Works for functions, variables, classes, types, imports, etc.`,
|
|
389
|
-
|
|
390
|
-
args: {
|
|
391
|
-
filePath: tool.schema.string().describe("Path to the file"),
|
|
392
|
-
line: tool.schema.number().describe("Line number (1-based)"),
|
|
393
|
-
character: tool.schema.number().describe("Character position (0-based)"),
|
|
394
|
-
},
|
|
395
|
-
|
|
396
|
-
async execute(args) {
|
|
397
|
-
const cwd = process.cwd();
|
|
398
|
-
const absPath = resolve(cwd, args.filePath);
|
|
399
|
-
const server = resolveServer(absPath);
|
|
400
|
-
|
|
401
|
-
if (!server) {
|
|
402
|
-
return `Error: No LSP server available for file type: ${absPath}`;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
try {
|
|
406
|
-
const client = await lspManager.getClient(cwd, server);
|
|
407
|
-
const result = (await client.definition(
|
|
408
|
-
absPath,
|
|
409
|
-
args.line,
|
|
410
|
-
args.character,
|
|
411
|
-
)) as Location | Location[] | LocationLink[] | null;
|
|
412
|
-
lspManager.releaseClient(cwd, server.id);
|
|
413
|
-
|
|
414
|
-
if (!result) {
|
|
415
|
-
return "No definition found at this position.";
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
return formatLocations(result);
|
|
419
|
-
} catch (error) {
|
|
420
|
-
return `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
421
|
-
}
|
|
422
|
-
},
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
/**
|
|
426
|
-
* Find all references to a symbol
|
|
427
|
-
*/
|
|
428
|
-
export const lsp_find_references = tool({
|
|
429
|
-
description: `Find all references to a symbol across the workspace.
|
|
430
|
-
|
|
431
|
-
Returns all locations where the symbol is used, including the definition.
|
|
432
|
-
Useful for understanding usage before refactoring.`,
|
|
433
|
-
|
|
434
|
-
args: {
|
|
435
|
-
filePath: tool.schema.string().describe("Path to the file"),
|
|
436
|
-
line: tool.schema.number().describe("Line number (1-based)"),
|
|
437
|
-
character: tool.schema.number().describe("Character position (0-based)"),
|
|
438
|
-
includeDeclaration: tool.schema
|
|
439
|
-
.boolean()
|
|
440
|
-
.optional()
|
|
441
|
-
.describe("Include the declaration itself (default: true)"),
|
|
442
|
-
},
|
|
443
|
-
|
|
444
|
-
async execute(args) {
|
|
445
|
-
const cwd = process.cwd();
|
|
446
|
-
const absPath = resolve(cwd, args.filePath);
|
|
447
|
-
const server = resolveServer(absPath);
|
|
448
|
-
|
|
449
|
-
if (!server) {
|
|
450
|
-
return `Error: No LSP server available for file type: ${absPath}`;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
try {
|
|
454
|
-
const client = await lspManager.getClient(cwd, server);
|
|
455
|
-
const result = (await client.references(
|
|
456
|
-
absPath,
|
|
457
|
-
args.line,
|
|
458
|
-
args.character,
|
|
459
|
-
args.includeDeclaration ?? true,
|
|
460
|
-
)) as Location[] | null;
|
|
461
|
-
lspManager.releaseClient(cwd, server.id);
|
|
462
|
-
|
|
463
|
-
if (!result || result.length === 0) {
|
|
464
|
-
return "No references found.";
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
const locations = result.slice(0, 50); // Limit results
|
|
468
|
-
const formatted = locations.map((loc) => formatLocation(loc)).join("\n");
|
|
469
|
-
const suffix =
|
|
470
|
-
result.length > 50 ? `\n\n... and ${result.length - 50} more` : "";
|
|
471
|
-
|
|
472
|
-
return `Found ${result.length} reference(s):\n\n${formatted}${suffix}`;
|
|
473
|
-
} catch (error) {
|
|
474
|
-
return `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
475
|
-
}
|
|
476
|
-
},
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
/**
|
|
480
|
-
* Get document symbols (outline) for a file
|
|
481
|
-
*/
|
|
482
|
-
export const lsp_document_symbols = tool({
|
|
483
|
-
description: `Get the symbol outline of a file (classes, functions, variables, etc.).
|
|
484
|
-
|
|
485
|
-
Returns a hierarchical structure of all symbols in the file.
|
|
486
|
-
Useful for understanding file structure quickly.`,
|
|
487
|
-
|
|
488
|
-
args: {
|
|
489
|
-
filePath: tool.schema.string().describe("Path to the file"),
|
|
490
|
-
},
|
|
491
|
-
|
|
492
|
-
async execute(args) {
|
|
493
|
-
const cwd = process.cwd();
|
|
494
|
-
const absPath = resolve(cwd, args.filePath);
|
|
495
|
-
const server = resolveServer(absPath);
|
|
496
|
-
|
|
497
|
-
if (!server) {
|
|
498
|
-
return `Error: No LSP server available for file type: ${absPath}`;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
try {
|
|
502
|
-
const client = await lspManager.getClient(cwd, server);
|
|
503
|
-
const result = (await client.documentSymbols(absPath)) as
|
|
504
|
-
| DocumentSymbol[]
|
|
505
|
-
| SymbolInfo[]
|
|
506
|
-
| null;
|
|
507
|
-
lspManager.releaseClient(cwd, server.id);
|
|
508
|
-
|
|
509
|
-
if (!result || result.length === 0) {
|
|
510
|
-
return "No symbols found in this file.";
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
return formatDocumentSymbols(result);
|
|
514
|
-
} catch (error) {
|
|
515
|
-
return `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
516
|
-
}
|
|
517
|
-
},
|
|
518
|
-
});
|
|
519
|
-
|
|
520
|
-
/**
|
|
521
|
-
* Search for symbols across the workspace
|
|
522
|
-
*/
|
|
523
|
-
export const lsp_workspace_symbols = tool({
|
|
524
|
-
description: `Search for symbols by name across the entire workspace.
|
|
525
|
-
|
|
526
|
-
Performs fuzzy matching on symbol names (functions, classes, variables, etc.).
|
|
527
|
-
Useful for finding where something is defined without knowing the exact file.`,
|
|
528
|
-
|
|
529
|
-
args: {
|
|
530
|
-
query: tool.schema
|
|
531
|
-
.string()
|
|
532
|
-
.describe("Symbol name to search for (fuzzy match)"),
|
|
533
|
-
filePath: tool.schema
|
|
534
|
-
.string()
|
|
535
|
-
.optional()
|
|
536
|
-
.describe("Any file in the project (used to determine language server)"),
|
|
537
|
-
},
|
|
538
|
-
|
|
539
|
-
async execute(args) {
|
|
540
|
-
const cwd = process.cwd();
|
|
541
|
-
const absPath = args.filePath
|
|
542
|
-
? resolve(cwd, args.filePath)
|
|
543
|
-
: resolve(cwd, ".");
|
|
544
|
-
const server = resolveServer(absPath);
|
|
545
|
-
|
|
546
|
-
if (!server) {
|
|
547
|
-
return `Error: No LSP server available. Provide a filePath to help identify the language.`;
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
try {
|
|
551
|
-
const client = await lspManager.getClient(cwd, server);
|
|
552
|
-
const result = (await client.workspaceSymbols(args.query)) as
|
|
553
|
-
| SymbolInfo[]
|
|
554
|
-
| null;
|
|
555
|
-
lspManager.releaseClient(cwd, server.id);
|
|
556
|
-
|
|
557
|
-
if (!result || result.length === 0) {
|
|
558
|
-
return `No symbols found matching "${args.query}".`;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
const symbols = result.slice(0, 30); // Limit results
|
|
562
|
-
const formatted = symbols
|
|
563
|
-
.map(
|
|
564
|
-
(sym) =>
|
|
565
|
-
`${getSymbolKindName(sym.kind)} ${sym.name}${sym.containerName ? ` (in ${sym.containerName})` : ""}\n ${formatUri(sym.location.uri)}:${sym.location.range.start.line + 1}`,
|
|
566
|
-
)
|
|
567
|
-
.join("\n\n");
|
|
568
|
-
const suffix =
|
|
569
|
-
result.length > 30 ? `\n\n... and ${result.length - 30} more` : "";
|
|
570
|
-
|
|
571
|
-
return `Found ${result.length} symbol(s):\n\n${formatted}${suffix}`;
|
|
572
|
-
} catch (error) {
|
|
573
|
-
return `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
574
|
-
}
|
|
575
|
-
},
|
|
576
|
-
});
|
|
577
|
-
|
|
578
|
-
/**
|
|
579
|
-
* Get diagnostics (errors, warnings) for a file
|
|
580
|
-
*/
|
|
581
|
-
export const lsp_diagnostics = tool({
|
|
582
|
-
description: `Get diagnostics (errors, warnings, hints) for a file from the language server.
|
|
583
|
-
|
|
584
|
-
Returns type errors, lint warnings, and other issues detected by the language server.
|
|
585
|
-
Useful for checking if code has problems before running tests.`,
|
|
586
|
-
|
|
587
|
-
args: {
|
|
588
|
-
filePath: tool.schema.string().describe("Path to the file"),
|
|
589
|
-
severity: tool.schema
|
|
590
|
-
.string()
|
|
591
|
-
.optional()
|
|
592
|
-
.describe(
|
|
593
|
-
"Filter by severity: 'error', 'warning', 'info', 'hint', or 'all' (default: all)",
|
|
594
|
-
),
|
|
595
|
-
},
|
|
596
|
-
|
|
597
|
-
async execute(args) {
|
|
598
|
-
const cwd = process.cwd();
|
|
599
|
-
const absPath = resolve(cwd, args.filePath);
|
|
600
|
-
const server = resolveServer(absPath);
|
|
601
|
-
|
|
602
|
-
if (!server) {
|
|
603
|
-
return `Error: No LSP server available for file type: ${absPath}`;
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
try {
|
|
607
|
-
const client = await lspManager.getClient(cwd, server);
|
|
608
|
-
const result = await client.diagnostics(absPath);
|
|
609
|
-
lspManager.releaseClient(cwd, server.id);
|
|
610
|
-
|
|
611
|
-
let diagnostics = result.items || [];
|
|
612
|
-
|
|
613
|
-
// Filter by severity if specified
|
|
614
|
-
if (args.severity && args.severity !== "all") {
|
|
615
|
-
const severityMap: Record<string, number> = {
|
|
616
|
-
error: 1,
|
|
617
|
-
warning: 2,
|
|
618
|
-
info: 3,
|
|
619
|
-
hint: 4,
|
|
620
|
-
};
|
|
621
|
-
const targetSeverity = severityMap[args.severity.toLowerCase()];
|
|
622
|
-
if (targetSeverity) {
|
|
623
|
-
diagnostics = diagnostics.filter(
|
|
624
|
-
(d) => d.severity === targetSeverity,
|
|
625
|
-
);
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
if (diagnostics.length === 0) {
|
|
630
|
-
return args.severity && args.severity !== "all"
|
|
631
|
-
? `No ${args.severity} diagnostics found.`
|
|
632
|
-
: "No diagnostics found. File appears clean.";
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
return formatDiagnostics(diagnostics, args.filePath);
|
|
636
|
-
} catch (error) {
|
|
637
|
-
return `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
638
|
-
}
|
|
639
|
-
},
|
|
640
|
-
});
|
|
641
|
-
|
|
642
|
-
// ============ Helper Functions ============
|
|
643
|
-
|
|
644
|
-
function formatHoverResult(hover: HoverResult): string {
|
|
645
|
-
const contents = hover.contents;
|
|
646
|
-
|
|
647
|
-
if (typeof contents === "string") {
|
|
648
|
-
return contents;
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
if (Array.isArray(contents)) {
|
|
652
|
-
return contents
|
|
653
|
-
.map((c) => (typeof c === "string" ? c : c.value))
|
|
654
|
-
.join("\n\n");
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
if (typeof contents === "object" && "value" in contents) {
|
|
658
|
-
return contents.value;
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
return JSON.stringify(contents);
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
function formatUri(uri: string): string {
|
|
665
|
-
return uri.replace("file://", "").replace(process.cwd() + "/", "");
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
function formatLocation(loc: Location | LocationLink): string {
|
|
669
|
-
if ("targetUri" in loc) {
|
|
670
|
-
return `${formatUri(loc.targetUri)}:${loc.targetRange.start.line + 1}:${loc.targetRange.start.character}`;
|
|
671
|
-
}
|
|
672
|
-
return `${formatUri(loc.uri)}:${loc.range.start.line + 1}:${loc.range.start.character}`;
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
function formatLocations(
|
|
676
|
-
result: Location | Location[] | LocationLink[],
|
|
677
|
-
): string {
|
|
678
|
-
const locations = Array.isArray(result) ? result : [result];
|
|
679
|
-
|
|
680
|
-
if (locations.length === 0) {
|
|
681
|
-
return "No locations found.";
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
if (locations.length === 1) {
|
|
685
|
-
return `Definition: ${formatLocation(locations[0] as Location | LocationLink)}`;
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
return `Found ${locations.length} location(s):\n${locations.map((l) => ` ${formatLocation(l as Location | LocationLink)}`).join("\n")}`;
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
function formatDocumentSymbols(
|
|
692
|
-
symbols: DocumentSymbol[] | SymbolInfo[],
|
|
693
|
-
indent = 0,
|
|
694
|
-
): string {
|
|
695
|
-
const prefix = " ".repeat(indent);
|
|
696
|
-
const lines: string[] = [];
|
|
697
|
-
|
|
698
|
-
for (const sym of symbols) {
|
|
699
|
-
const kindName = getSymbolKindName(sym.kind);
|
|
700
|
-
const line =
|
|
701
|
-
"range" in sym
|
|
702
|
-
? sym.range.start.line + 1
|
|
703
|
-
: "location" in sym
|
|
704
|
-
? sym.location.range.start.line + 1
|
|
705
|
-
: "?";
|
|
706
|
-
|
|
707
|
-
lines.push(`${prefix}${kindName} ${sym.name} (line ${line})`);
|
|
708
|
-
|
|
709
|
-
if ("children" in sym && sym.children) {
|
|
710
|
-
lines.push(formatDocumentSymbols(sym.children, indent + 1));
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
return lines.join("\n");
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
function formatDiagnostics(
|
|
718
|
-
diagnostics: Diagnostic[],
|
|
719
|
-
filePath: string,
|
|
720
|
-
): string {
|
|
721
|
-
const severityNames = ["", "Error", "Warning", "Info", "Hint"];
|
|
722
|
-
|
|
723
|
-
const sorted = [...diagnostics].sort((a, b) => {
|
|
724
|
-
const sevA = a.severity ?? 4;
|
|
725
|
-
const sevB = b.severity ?? 4;
|
|
726
|
-
if (sevA !== sevB) return sevA - sevB;
|
|
727
|
-
return a.range.start.line - b.range.start.line;
|
|
728
|
-
});
|
|
729
|
-
|
|
730
|
-
const lines = sorted.map((d) => {
|
|
731
|
-
const sev = severityNames[d.severity ?? 4] || "Unknown";
|
|
732
|
-
const line = d.range.start.line + 1;
|
|
733
|
-
const col = d.range.start.character + 1;
|
|
734
|
-
const source = d.source ? `[${d.source}]` : "";
|
|
735
|
-
const code = d.code ? ` (${d.code})` : "";
|
|
736
|
-
return `${sev}: ${filePath}:${line}:${col}${code} ${source}\n ${d.message}`;
|
|
737
|
-
});
|
|
738
|
-
|
|
739
|
-
const errorCount = diagnostics.filter((d) => d.severity === 1).length;
|
|
740
|
-
const warnCount = diagnostics.filter((d) => d.severity === 2).length;
|
|
741
|
-
|
|
742
|
-
let summary = `Found ${diagnostics.length} diagnostic(s)`;
|
|
743
|
-
if (errorCount > 0 || warnCount > 0) {
|
|
744
|
-
const parts: string[] = [];
|
|
745
|
-
if (errorCount > 0) parts.push(`${errorCount} error(s)`);
|
|
746
|
-
if (warnCount > 0) parts.push(`${warnCount} warning(s)`);
|
|
747
|
-
summary += `: ${parts.join(", ")}`;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
return `${summary}\n\n${lines.join("\n\n")}`;
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
function getSymbolKindName(kind: number): string {
|
|
754
|
-
const kinds: Record<number, string> = {
|
|
755
|
-
1: "File",
|
|
756
|
-
2: "Module",
|
|
757
|
-
3: "Namespace",
|
|
758
|
-
4: "Package",
|
|
759
|
-
5: "Class",
|
|
760
|
-
6: "Method",
|
|
761
|
-
7: "Property",
|
|
762
|
-
8: "Field",
|
|
763
|
-
9: "Constructor",
|
|
764
|
-
10: "Enum",
|
|
765
|
-
11: "Interface",
|
|
766
|
-
12: "Function",
|
|
767
|
-
13: "Variable",
|
|
768
|
-
14: "Constant",
|
|
769
|
-
15: "String",
|
|
770
|
-
16: "Number",
|
|
771
|
-
17: "Boolean",
|
|
772
|
-
18: "Array",
|
|
773
|
-
19: "Object",
|
|
774
|
-
20: "Key",
|
|
775
|
-
21: "Null",
|
|
776
|
-
22: "EnumMember",
|
|
777
|
-
23: "Struct",
|
|
778
|
-
24: "Event",
|
|
779
|
-
25: "Operator",
|
|
780
|
-
26: "TypeParameter",
|
|
781
|
-
};
|
|
782
|
-
return kinds[kind] || `Kind(${kind})`;
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
// Default export for single-tool registration (uses rename as primary)
|
|
786
|
-
export default lsp_rename;
|