opencodekit 0.6.2 → 0.6.4
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 +1 -1
- package/dist/template/.opencode/AGENTS.md +28 -4
- package/dist/template/.opencode/agent/build.md +11 -3
- package/dist/template/.opencode/agent/rush.md +11 -3
- package/dist/template/.opencode/lib/lsp/client.ts +614 -0
- package/dist/template/.opencode/lib/lsp/config.ts +98 -0
- package/dist/template/.opencode/lib/lsp/constants.ts +138 -0
- package/dist/template/.opencode/lib/lsp/types.ts +138 -0
- package/dist/template/.opencode/lib/lsp/utils.ts +190 -0
- package/dist/template/.opencode/opencode.json +55 -42
- package/dist/template/.opencode/package.json +1 -1
- package/dist/template/.opencode/tool/lsp.ts +325 -0
- package/package.json +1 -1
|
@@ -0,0 +1,325 @@
|
|
|
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
|
+
* Requires: Language servers installed (e.g., typescript-language-server, pyright, gopls)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { resolve } from "node:path";
|
|
10
|
+
import { tool } from "@opencode-ai/plugin";
|
|
11
|
+
import { lspManager } from "../lib/lsp/client";
|
|
12
|
+
import { resolveServer } from "../lib/lsp/config";
|
|
13
|
+
import type {
|
|
14
|
+
CodeAction,
|
|
15
|
+
PrepareRenameResult,
|
|
16
|
+
WorkspaceEdit,
|
|
17
|
+
} from "../lib/lsp/types";
|
|
18
|
+
import {
|
|
19
|
+
applyWorkspaceEdit,
|
|
20
|
+
formatWorkspaceEditPreview,
|
|
21
|
+
} from "../lib/lsp/utils";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Rename a symbol across the entire codebase using LSP
|
|
25
|
+
*/
|
|
26
|
+
export const lsp_rename = tool({
|
|
27
|
+
description: `Rename a symbol (function, variable, class, etc.) across the entire codebase using LSP.
|
|
28
|
+
|
|
29
|
+
This tool performs semantic renaming - it understands code structure and renames all references correctly, including:
|
|
30
|
+
- Function/method definitions and all their calls
|
|
31
|
+
- Variable declarations and usages
|
|
32
|
+
- Class names and their instantiations
|
|
33
|
+
- Import/export statements
|
|
34
|
+
|
|
35
|
+
IMPORTANT: This tool directly modifies files. Review the changes shown in output.`,
|
|
36
|
+
|
|
37
|
+
args: {
|
|
38
|
+
filePath: tool.schema
|
|
39
|
+
.string()
|
|
40
|
+
.describe("Path to the file containing the symbol to rename"),
|
|
41
|
+
line: tool.schema
|
|
42
|
+
.number()
|
|
43
|
+
.describe("Line number (1-based) where the symbol is located"),
|
|
44
|
+
character: tool.schema
|
|
45
|
+
.number()
|
|
46
|
+
.describe("Character position (0-based) within the line"),
|
|
47
|
+
newName: tool.schema.string().describe("The new name for the symbol"),
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
async execute(args) {
|
|
51
|
+
const cwd = process.cwd();
|
|
52
|
+
const absPath = resolve(cwd, args.filePath);
|
|
53
|
+
const server = resolveServer(absPath);
|
|
54
|
+
|
|
55
|
+
if (!server) {
|
|
56
|
+
return `Error: No LSP server available for file type: ${absPath}
|
|
57
|
+
|
|
58
|
+
Install a language server:
|
|
59
|
+
- TypeScript: npm i -g typescript-language-server typescript
|
|
60
|
+
- Python: npm i -g pyright
|
|
61
|
+
- Go: go install golang.org/x/tools/gopls@latest
|
|
62
|
+
- Rust: rustup component add rust-analyzer`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const client = await lspManager.getClient(cwd, server);
|
|
67
|
+
|
|
68
|
+
const prepareResult = (await client.prepareRename(
|
|
69
|
+
absPath,
|
|
70
|
+
args.line,
|
|
71
|
+
args.character,
|
|
72
|
+
)) as PrepareRenameResult | null;
|
|
73
|
+
if (!prepareResult) {
|
|
74
|
+
lspManager.releaseClient(cwd, server.id);
|
|
75
|
+
return "Error: Cannot rename at this position. Make sure the cursor is on a valid symbol.";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const result = (await client.rename(
|
|
79
|
+
absPath,
|
|
80
|
+
args.line,
|
|
81
|
+
args.character,
|
|
82
|
+
args.newName,
|
|
83
|
+
)) as WorkspaceEdit | null;
|
|
84
|
+
lspManager.releaseClient(cwd, server.id);
|
|
85
|
+
|
|
86
|
+
if (!result) {
|
|
87
|
+
return "Error: Rename returned no changes.";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const preview = formatWorkspaceEditPreview(result);
|
|
91
|
+
const applyResult = applyWorkspaceEdit(result);
|
|
92
|
+
|
|
93
|
+
if (!applyResult.success) {
|
|
94
|
+
return `Error applying changes: ${applyResult.error}\n\nPlanned changes:\n${preview}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return `Successfully renamed to "${args.newName}".
|
|
98
|
+
|
|
99
|
+
Files modified:
|
|
100
|
+
${applyResult.filesModified.map((f) => ` - ${f}`).join("\n")}
|
|
101
|
+
|
|
102
|
+
Note: Files were modified directly. Re-read before editing.`;
|
|
103
|
+
} catch (error) {
|
|
104
|
+
return `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get available code actions for a code range
|
|
111
|
+
*/
|
|
112
|
+
export const lsp_code_actions = tool({
|
|
113
|
+
description: `Get available code actions (quick fixes, refactorings) for a code range.
|
|
114
|
+
|
|
115
|
+
This tool lists available actions like:
|
|
116
|
+
- Quick fixes for diagnostics/errors
|
|
117
|
+
- Refactoring options (extract function, inline variable, etc.)
|
|
118
|
+
- Source actions (organize imports, fix all)
|
|
119
|
+
|
|
120
|
+
Use lsp_code_action_apply to execute a specific action.`,
|
|
121
|
+
|
|
122
|
+
args: {
|
|
123
|
+
filePath: tool.schema.string().describe("Path to the file"),
|
|
124
|
+
startLine: tool.schema.number().describe("Start line (1-based)"),
|
|
125
|
+
startCharacter: tool.schema.number().describe("Start character (0-based)"),
|
|
126
|
+
endLine: tool.schema.number().describe("End line (1-based)"),
|
|
127
|
+
endCharacter: tool.schema.number().describe("End character (0-based)"),
|
|
128
|
+
only: tool.schema
|
|
129
|
+
.array(tool.schema.string())
|
|
130
|
+
.optional()
|
|
131
|
+
.describe(
|
|
132
|
+
"Filter by action kind: 'quickfix', 'refactor', 'refactor.extract', 'source.organizeImports'",
|
|
133
|
+
),
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
async execute(args) {
|
|
137
|
+
const cwd = process.cwd();
|
|
138
|
+
const absPath = resolve(cwd, args.filePath);
|
|
139
|
+
const server = resolveServer(absPath);
|
|
140
|
+
|
|
141
|
+
if (!server) {
|
|
142
|
+
return `Error: No LSP server available for file type: ${absPath}`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
const client = await lspManager.getClient(cwd, server);
|
|
147
|
+
const result = (await client.codeAction(
|
|
148
|
+
absPath,
|
|
149
|
+
args.startLine,
|
|
150
|
+
args.startCharacter,
|
|
151
|
+
args.endLine,
|
|
152
|
+
args.endCharacter,
|
|
153
|
+
args.only,
|
|
154
|
+
)) as CodeAction[] | null;
|
|
155
|
+
lspManager.releaseClient(cwd, server.id);
|
|
156
|
+
|
|
157
|
+
if (!result || result.length === 0) {
|
|
158
|
+
return "No code actions available for this range.";
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const actions = result.map((action, index) => {
|
|
162
|
+
const parts = [`${index + 1}. ${action.title}`];
|
|
163
|
+
if (action.kind) parts.push(` Kind: ${action.kind}`);
|
|
164
|
+
if (action.isPreferred) parts.push(" (Preferred)");
|
|
165
|
+
if (action.disabled)
|
|
166
|
+
parts.push(` (Disabled: ${action.disabled.reason})`);
|
|
167
|
+
return parts.join("\n");
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
return `Available code actions:\n\n${actions.join("\n\n")}\n\nUse lsp_refactor_code_action_apply with the action index to apply.`;
|
|
171
|
+
} catch (error) {
|
|
172
|
+
return `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Apply a specific code action
|
|
179
|
+
*/
|
|
180
|
+
export const lsp_code_action_apply = tool({
|
|
181
|
+
description: `Apply a specific code action from lsp_refactor_code_actions.
|
|
182
|
+
|
|
183
|
+
First call lsp_refactor_code_actions to see available actions, then use this tool with the action index to apply it.`,
|
|
184
|
+
|
|
185
|
+
args: {
|
|
186
|
+
filePath: tool.schema.string().describe("Path to the file"),
|
|
187
|
+
startLine: tool.schema.number().describe("Start line (1-based)"),
|
|
188
|
+
startCharacter: tool.schema.number().describe("Start character (0-based)"),
|
|
189
|
+
endLine: tool.schema.number().describe("End line (1-based)"),
|
|
190
|
+
endCharacter: tool.schema.number().describe("End character (0-based)"),
|
|
191
|
+
actionIndex: tool.schema
|
|
192
|
+
.number()
|
|
193
|
+
.describe(
|
|
194
|
+
"Index of the action to apply (1-based, from lsp_refactor_code_actions output)",
|
|
195
|
+
),
|
|
196
|
+
only: tool.schema
|
|
197
|
+
.array(tool.schema.string())
|
|
198
|
+
.optional()
|
|
199
|
+
.describe(
|
|
200
|
+
"Filter by action kind (same filter used in lsp_refactor_code_actions)",
|
|
201
|
+
),
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
async execute(args) {
|
|
205
|
+
const cwd = process.cwd();
|
|
206
|
+
const absPath = resolve(cwd, args.filePath);
|
|
207
|
+
const server = resolveServer(absPath);
|
|
208
|
+
|
|
209
|
+
if (!server) {
|
|
210
|
+
return `Error: No LSP server available for file type: ${absPath}`;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
const client = await lspManager.getClient(cwd, server);
|
|
215
|
+
const actions = (await client.codeAction(
|
|
216
|
+
absPath,
|
|
217
|
+
args.startLine,
|
|
218
|
+
args.startCharacter,
|
|
219
|
+
args.endLine,
|
|
220
|
+
args.endCharacter,
|
|
221
|
+
args.only,
|
|
222
|
+
)) as CodeAction[] | null;
|
|
223
|
+
|
|
224
|
+
if (!actions || actions.length === 0) {
|
|
225
|
+
lspManager.releaseClient(cwd, server.id);
|
|
226
|
+
return "No code actions available for this range.";
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (args.actionIndex < 1 || args.actionIndex > actions.length) {
|
|
230
|
+
lspManager.releaseClient(cwd, server.id);
|
|
231
|
+
return `Invalid action index. Available: 1-${actions.length}`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
let action = actions[args.actionIndex - 1];
|
|
235
|
+
|
|
236
|
+
if (action.data && !action.edit) {
|
|
237
|
+
action = (await client.codeActionResolve(action)) as CodeAction;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
lspManager.releaseClient(cwd, server.id);
|
|
241
|
+
|
|
242
|
+
if (!action.edit) {
|
|
243
|
+
if (action.command) {
|
|
244
|
+
return `Action "${action.title}" has a command instead of edits. Commands are not supported yet.`;
|
|
245
|
+
}
|
|
246
|
+
return `Action "${action.title}" produced no edits.`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const applyResult = applyWorkspaceEdit(action.edit);
|
|
250
|
+
|
|
251
|
+
if (!applyResult.success) {
|
|
252
|
+
return `Error applying action: ${applyResult.error}`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return `Applied: "${action.title}"
|
|
256
|
+
|
|
257
|
+
Files modified:
|
|
258
|
+
${applyResult.filesModified.map((f) => ` - ${f}`).join("\n")}
|
|
259
|
+
|
|
260
|
+
Note: Files were modified directly. Re-read before editing.`;
|
|
261
|
+
} catch (error) {
|
|
262
|
+
return `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Organize imports in a file
|
|
269
|
+
*/
|
|
270
|
+
export const lsp_organize_imports = tool({
|
|
271
|
+
description: `Organize imports in a file using LSP.
|
|
272
|
+
|
|
273
|
+
This removes unused imports and sorts/groups remaining imports according to language conventions.`,
|
|
274
|
+
|
|
275
|
+
args: {
|
|
276
|
+
filePath: tool.schema.string().describe("Path to the file"),
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
async execute(args) {
|
|
280
|
+
const cwd = process.cwd();
|
|
281
|
+
const absPath = resolve(cwd, args.filePath);
|
|
282
|
+
const server = resolveServer(absPath);
|
|
283
|
+
|
|
284
|
+
if (!server) {
|
|
285
|
+
return `Error: No LSP server available for file type: ${absPath}`;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
const client = await lspManager.getClient(cwd, server);
|
|
290
|
+
const actions = (await client.codeAction(absPath, 1, 0, 10000, 0, [
|
|
291
|
+
"source.organizeImports",
|
|
292
|
+
])) as CodeAction[] | null;
|
|
293
|
+
|
|
294
|
+
if (!actions || actions.length === 0) {
|
|
295
|
+
lspManager.releaseClient(cwd, server.id);
|
|
296
|
+
return "No organize imports action available for this file.";
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
let action = actions[0];
|
|
300
|
+
if (action.data && !action.edit) {
|
|
301
|
+
action = (await client.codeActionResolve(action)) as CodeAction;
|
|
302
|
+
}
|
|
303
|
+
lspManager.releaseClient(cwd, server.id);
|
|
304
|
+
|
|
305
|
+
if (!action.edit) {
|
|
306
|
+
return "Organize imports produced no changes (imports may already be organized).";
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const applyResult = applyWorkspaceEdit(action.edit);
|
|
310
|
+
|
|
311
|
+
if (!applyResult.success) {
|
|
312
|
+
return `Error organizing imports: ${applyResult.error}`;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return `Imports organized in ${args.filePath}
|
|
316
|
+
|
|
317
|
+
Note: File was modified directly. Re-read before editing.`;
|
|
318
|
+
} catch (error) {
|
|
319
|
+
return `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// Default export for single-tool registration (uses rename as primary)
|
|
325
|
+
export default lsp_rename;
|