@kleber.mottajr/juninho 1.0.1 → 1.2.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 +112 -13
- package/dist/cli.js +53 -29
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +5 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +7 -1
- package/dist/config.js.map +1 -1
- package/dist/installer.d.ts +2 -0
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +178 -54
- package/dist/installer.js.map +1 -1
- package/dist/lint-detection.d.ts +26 -0
- package/dist/lint-detection.d.ts.map +1 -0
- package/dist/lint-detection.js +200 -0
- package/dist/lint-detection.js.map +1 -0
- package/dist/models.js +4 -4
- package/dist/models.js.map +1 -1
- package/dist/project-types.d.ts +47 -0
- package/dist/project-types.d.ts.map +1 -0
- package/dist/project-types.js +251 -0
- package/dist/project-types.js.map +1 -0
- package/dist/templates/agents.d.ts +2 -1
- package/dist/templates/agents.d.ts.map +1 -1
- package/dist/templates/agents.js +7 -5
- package/dist/templates/agents.js.map +1 -1
- package/dist/templates/commands.d.ts.map +1 -1
- package/dist/templates/commands.js +225 -150
- package/dist/templates/commands.js.map +1 -1
- package/dist/templates/docs.d.ts +2 -1
- package/dist/templates/docs.d.ts.map +1 -1
- package/dist/templates/docs.js +61 -14
- package/dist/templates/docs.js.map +1 -1
- package/dist/templates/plugins.d.ts +2 -1
- package/dist/templates/plugins.d.ts.map +1 -1
- package/dist/templates/plugins.js +167 -102
- package/dist/templates/plugins.js.map +1 -1
- package/dist/templates/skills.d.ts +2 -1
- package/dist/templates/skills.d.ts.map +1 -1
- package/dist/templates/skills.js +708 -195
- package/dist/templates/skills.js.map +1 -1
- package/dist/templates/support-scripts.d.ts +2 -1
- package/dist/templates/support-scripts.d.ts.map +1 -1
- package/dist/templates/support-scripts.js +468 -21
- package/dist/templates/support-scripts.js.map +1 -1
- package/dist/templates/tools.d.ts +2 -1
- package/dist/templates/tools.d.ts.map +1 -1
- package/dist/templates/tools.js +315 -74
- package/dist/templates/tools.js.map +1 -1
- package/package.json +1 -1
package/dist/templates/tools.js
CHANGED
|
@@ -6,15 +6,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.writeTools = writeTools;
|
|
7
7
|
const fs_1 = require("fs");
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
|
-
|
|
9
|
+
const project_types_js_1 = require("../project-types.js");
|
|
10
|
+
function writeTools(projectDir, projectType = "node-nextjs", isKotlin = false) {
|
|
10
11
|
const toolsDir = path_1.default.join(projectDir, ".opencode", "tools");
|
|
11
|
-
(0,
|
|
12
|
-
(0, fs_1.writeFileSync)(path_1.default.join(toolsDir, "
|
|
13
|
-
(0, fs_1.writeFileSync)(path_1.default.join(toolsDir, "
|
|
14
|
-
(0, fs_1.writeFileSync)(path_1.default.join(toolsDir, "
|
|
12
|
+
const config = (0, project_types_js_1.getEffectiveConfig)(projectType, isKotlin);
|
|
13
|
+
(0, fs_1.writeFileSync)(path_1.default.join(toolsDir, "find-pattern.ts"), findPattern(projectType, isKotlin));
|
|
14
|
+
(0, fs_1.writeFileSync)(path_1.default.join(toolsDir, "next-version.ts"), nextVersion(config.migrationDirs));
|
|
15
|
+
(0, fs_1.writeFileSync)(path_1.default.join(toolsDir, "lsp.ts"), lsp(projectType, isKotlin));
|
|
16
|
+
(0, fs_1.writeFileSync)(path_1.default.join(toolsDir, "ast-grep.ts"), astGrep(config.astGrepLang));
|
|
15
17
|
}
|
|
16
18
|
// ─── Find Pattern ─────────────────────────────────────────────────────────────
|
|
17
|
-
|
|
19
|
+
function findPattern(projectType, isKotlin) {
|
|
20
|
+
const fallbackPatterns = getFallbackPatterns(projectType, isKotlin);
|
|
21
|
+
return `import { tool } from "@opencode-ai/plugin"
|
|
18
22
|
import { z } from "zod"
|
|
19
23
|
import { existsSync, readFileSync } from "fs"
|
|
20
24
|
import path from "path"
|
|
@@ -23,17 +27,7 @@ export const find_pattern = tool({
|
|
|
23
27
|
name: "find_pattern",
|
|
24
28
|
description: "Find canonical code patterns in the codebase for consistent implementation",
|
|
25
29
|
parameters: z.object({
|
|
26
|
-
patternType: z.
|
|
27
|
-
"api-route",
|
|
28
|
-
"server-action",
|
|
29
|
-
"react-component",
|
|
30
|
-
"prisma-query",
|
|
31
|
-
"error-handler",
|
|
32
|
-
"test-unit",
|
|
33
|
-
"test-integration",
|
|
34
|
-
"zod-schema",
|
|
35
|
-
"middleware",
|
|
36
|
-
]).describe("The type of pattern to find"),
|
|
30
|
+
patternType: z.string().describe("The type of pattern to find (e.g. api-route, service, repository, test-unit, error-handler)"),
|
|
37
31
|
cwd: z.string().optional().describe("Working directory (defaults to process.cwd())"),
|
|
38
32
|
}),
|
|
39
33
|
execute: async ({ patternType, cwd: cwdInput }) => {
|
|
@@ -51,8 +45,83 @@ export const find_pattern = tool({
|
|
|
51
45
|
}
|
|
52
46
|
|
|
53
47
|
// Fallback patterns
|
|
54
|
-
const FALLBACK_PATTERNS: Record<string, string> = {
|
|
55
|
-
|
|
48
|
+
const FALLBACK_PATTERNS: Record<string, string> = ${JSON.stringify(fallbackPatterns, null, 6).replace(/\n/g, "\n ")}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
pattern: patternType,
|
|
52
|
+
example: FALLBACK_PATTERNS[patternType] ?? "No canonical pattern found. Check docs/principles/manifest.",
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
`;
|
|
57
|
+
}
|
|
58
|
+
function getFallbackPatterns(projectType, isKotlin) {
|
|
59
|
+
if (projectType === "java" && isKotlin) {
|
|
60
|
+
return {
|
|
61
|
+
"service": `// src/main/kotlin/com/example/FooService.kt
|
|
62
|
+
@Service
|
|
63
|
+
class FooService(
|
|
64
|
+
private val repository: FooRepository,
|
|
65
|
+
) {
|
|
66
|
+
fun findById(id: Long): Foo {
|
|
67
|
+
return repository.findById(id)
|
|
68
|
+
.orElseThrow { NotFoundException("Foo not found: $id") }
|
|
69
|
+
}
|
|
70
|
+
}`,
|
|
71
|
+
"repository": `// src/main/kotlin/com/example/FooRepository.kt
|
|
72
|
+
@Repository
|
|
73
|
+
interface FooRepository : JpaRepository<Foo, Long> {
|
|
74
|
+
fun findByName(name: String): List<Foo>
|
|
75
|
+
}`,
|
|
76
|
+
"controller": `// src/main/kotlin/com/example/FooController.kt
|
|
77
|
+
@RestController
|
|
78
|
+
@RequestMapping("/api/foo")
|
|
79
|
+
class FooController(
|
|
80
|
+
private val service: FooService,
|
|
81
|
+
) {
|
|
82
|
+
@GetMapping("/{id}")
|
|
83
|
+
fun getById(@PathVariable id: Long): ResponseEntity<Foo> {
|
|
84
|
+
return ResponseEntity.ok(service.findById(id))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@PostMapping
|
|
88
|
+
fun create(@Valid @RequestBody request: CreateFooRequest): ResponseEntity<Foo> {
|
|
89
|
+
return ResponseEntity.status(HttpStatus.CREATED).body(service.create(request))
|
|
90
|
+
}
|
|
91
|
+
}`,
|
|
92
|
+
"test-unit": `// src/test/kotlin/com/example/FooServiceTest.kt
|
|
93
|
+
@ExtendWith(MockitoExtension::class)
|
|
94
|
+
class FooServiceTest {
|
|
95
|
+
|
|
96
|
+
@Mock
|
|
97
|
+
lateinit var repository: FooRepository
|
|
98
|
+
|
|
99
|
+
@InjectMocks
|
|
100
|
+
lateinit var service: FooService
|
|
101
|
+
|
|
102
|
+
@Test
|
|
103
|
+
fun \`should find by id\`() {
|
|
104
|
+
whenever(repository.findById(1L)).thenReturn(Optional.of(foo))
|
|
105
|
+
val result = service.findById(1L)
|
|
106
|
+
assertThat(result).isEqualTo(foo)
|
|
107
|
+
}
|
|
108
|
+
}`,
|
|
109
|
+
"error-handler": `// src/main/kotlin/com/example/GlobalExceptionHandler.kt
|
|
110
|
+
@RestControllerAdvice
|
|
111
|
+
class GlobalExceptionHandler {
|
|
112
|
+
|
|
113
|
+
@ExceptionHandler(NotFoundException::class)
|
|
114
|
+
fun handleNotFound(ex: NotFoundException): ResponseEntity<ErrorResponse> {
|
|
115
|
+
return ResponseEntity.status(HttpStatus.NOT_FOUND)
|
|
116
|
+
.body(ErrorResponse(ex.message ?: "Not found"))
|
|
117
|
+
}
|
|
118
|
+
}`,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
switch (projectType) {
|
|
122
|
+
case "node-nextjs":
|
|
123
|
+
return {
|
|
124
|
+
"api-route": `// app/api/example/route.ts
|
|
56
125
|
import { NextRequest, NextResponse } from "next/server"
|
|
57
126
|
|
|
58
127
|
export async function GET(req: NextRequest) {
|
|
@@ -62,8 +131,8 @@ export async function GET(req: NextRequest) {
|
|
|
62
131
|
} catch (error) {
|
|
63
132
|
return NextResponse.json({ error: "Internal server error" }, { status: 500 })
|
|
64
133
|
}
|
|
65
|
-
}
|
|
66
|
-
|
|
134
|
+
}`,
|
|
135
|
+
"server-action": `// app/actions/example.ts
|
|
67
136
|
"use server"
|
|
68
137
|
import { revalidatePath } from "next/cache"
|
|
69
138
|
import { z } from "zod"
|
|
@@ -76,8 +145,8 @@ export async function createExample(formData: FormData) {
|
|
|
76
145
|
// ... implementation
|
|
77
146
|
revalidatePath("/")
|
|
78
147
|
return { success: true }
|
|
79
|
-
}
|
|
80
|
-
|
|
148
|
+
}`,
|
|
149
|
+
"zod-schema": `import { z } from "zod"
|
|
81
150
|
|
|
82
151
|
export const ExampleSchema = z.object({
|
|
83
152
|
id: z.string().cuid(),
|
|
@@ -85,18 +154,153 @@ export const ExampleSchema = z.object({
|
|
|
85
154
|
createdAt: z.date(),
|
|
86
155
|
})
|
|
87
156
|
|
|
88
|
-
export type Example = z.infer<typeof ExampleSchema
|
|
157
|
+
export type Example = z.infer<typeof ExampleSchema>`,
|
|
158
|
+
};
|
|
159
|
+
case "node-generic":
|
|
160
|
+
return {
|
|
161
|
+
"service": `// src/services/example.ts
|
|
162
|
+
export class ExampleService {
|
|
163
|
+
constructor(private readonly repository: ExampleRepository) {}
|
|
164
|
+
|
|
165
|
+
async findById(id: string): Promise<Example> {
|
|
166
|
+
const result = await this.repository.findById(id)
|
|
167
|
+
if (!result) throw new NotFoundError(\`Example \${id} not found\`)
|
|
168
|
+
return result
|
|
169
|
+
}
|
|
170
|
+
}`,
|
|
171
|
+
"error-handler": `// src/middleware/error-handler.ts
|
|
172
|
+
export function errorHandler(err: Error, req: Request, res: Response, next: NextFunction) {
|
|
173
|
+
if (err instanceof NotFoundError) {
|
|
174
|
+
return res.status(404).json({ error: err.message })
|
|
175
|
+
}
|
|
176
|
+
console.error(err)
|
|
177
|
+
res.status(500).json({ error: "Internal server error" })
|
|
178
|
+
}`,
|
|
179
|
+
};
|
|
180
|
+
case "python":
|
|
181
|
+
return {
|
|
182
|
+
"service": `# src/services/foo_service.py
|
|
183
|
+
class FooService:
|
|
184
|
+
def __init__(self, repository: FooRepository):
|
|
185
|
+
self._repository = repository
|
|
186
|
+
|
|
187
|
+
def find_by_id(self, id: int) -> Foo:
|
|
188
|
+
result = self._repository.find_by_id(id)
|
|
189
|
+
if not result:
|
|
190
|
+
raise NotFoundError(f"Foo {id} not found")
|
|
191
|
+
return result`,
|
|
192
|
+
"test-unit": `# tests/test_foo_service.py
|
|
193
|
+
import pytest
|
|
194
|
+
from unittest.mock import MagicMock
|
|
195
|
+
|
|
196
|
+
class TestFooService:
|
|
197
|
+
def test_find_by_id_returns_foo(self):
|
|
198
|
+
repository = MagicMock()
|
|
199
|
+
repository.find_by_id.return_value = Foo(id=1, name="test")
|
|
200
|
+
service = FooService(repository)
|
|
201
|
+
|
|
202
|
+
result = service.find_by_id(1)
|
|
203
|
+
|
|
204
|
+
assert result.name == "test"
|
|
205
|
+
repository.find_by_id.assert_called_once_with(1)`,
|
|
206
|
+
};
|
|
207
|
+
case "go":
|
|
208
|
+
return {
|
|
209
|
+
"handler": `// internal/handler/foo.go
|
|
210
|
+
func (h *FooHandler) GetByID(w http.ResponseWriter, r *http.Request) {
|
|
211
|
+
id := chi.URLParam(r, "id")
|
|
212
|
+
|
|
213
|
+
foo, err := h.service.FindByID(r.Context(), id)
|
|
214
|
+
if err != nil {
|
|
215
|
+
if errors.Is(err, ErrNotFound) {
|
|
216
|
+
http.Error(w, "not found", http.StatusNotFound)
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
http.Error(w, "internal error", http.StatusInternalServerError)
|
|
220
|
+
return
|
|
89
221
|
}
|
|
90
222
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
223
|
+
json.NewEncoder(w).Encode(foo)
|
|
224
|
+
}`,
|
|
225
|
+
"test-unit": `// internal/service/foo_test.go
|
|
226
|
+
func TestFooService_FindByID(t *testing.T) {
|
|
227
|
+
tests := []struct {
|
|
228
|
+
name string
|
|
229
|
+
id string
|
|
230
|
+
want *Foo
|
|
231
|
+
wantErr bool
|
|
232
|
+
}{
|
|
233
|
+
{name: "found", id: "1", want: &Foo{ID: "1"}, wantErr: false},
|
|
234
|
+
{name: "not found", id: "999", want: nil, wantErr: true},
|
|
94
235
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
236
|
+
|
|
237
|
+
for _, tt := range tests {
|
|
238
|
+
t.Run(tt.name, func(t *testing.T) {
|
|
239
|
+
got, err := svc.FindByID(context.Background(), tt.id)
|
|
240
|
+
if (err != nil) != tt.wantErr {
|
|
241
|
+
t.Errorf("error = %v, wantErr %v", err, tt.wantErr)
|
|
242
|
+
}
|
|
243
|
+
if !reflect.DeepEqual(got, tt.want) {
|
|
244
|
+
t.Errorf("got %v, want %v", got, tt.want)
|
|
245
|
+
}
|
|
246
|
+
})
|
|
247
|
+
}
|
|
248
|
+
}`,
|
|
249
|
+
};
|
|
250
|
+
case "java":
|
|
251
|
+
return {
|
|
252
|
+
"service": `// src/main/java/com/example/FooService.java
|
|
253
|
+
@Service
|
|
254
|
+
public class FooService {
|
|
255
|
+
private final FooRepository repository;
|
|
256
|
+
|
|
257
|
+
public FooService(FooRepository repository) {
|
|
258
|
+
this.repository = repository;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
public Foo findById(Long id) {
|
|
262
|
+
return repository.findById(id)
|
|
263
|
+
.orElseThrow(() -> new NotFoundException("Foo not found: " + id));
|
|
264
|
+
}
|
|
265
|
+
}`,
|
|
266
|
+
"controller": `// src/main/java/com/example/FooController.java
|
|
267
|
+
@RestController
|
|
268
|
+
@RequestMapping("/api/foo")
|
|
269
|
+
public class FooController {
|
|
270
|
+
private final FooService service;
|
|
271
|
+
|
|
272
|
+
@GetMapping("/{id}")
|
|
273
|
+
public ResponseEntity<Foo> getById(@PathVariable Long id) {
|
|
274
|
+
return ResponseEntity.ok(service.findById(id));
|
|
275
|
+
}
|
|
276
|
+
}`,
|
|
277
|
+
"test-unit": `// src/test/java/com/example/FooServiceTest.java
|
|
278
|
+
@ExtendWith(MockitoExtension.class)
|
|
279
|
+
class FooServiceTest {
|
|
280
|
+
|
|
281
|
+
@Mock
|
|
282
|
+
private FooRepository repository;
|
|
283
|
+
|
|
284
|
+
@InjectMocks
|
|
285
|
+
private FooService service;
|
|
286
|
+
|
|
287
|
+
@Test
|
|
288
|
+
void shouldFindById() {
|
|
289
|
+
when(repository.findById(1L)).thenReturn(Optional.of(foo));
|
|
290
|
+
var result = service.findById(1L);
|
|
291
|
+
assertThat(result).isEqualTo(foo);
|
|
292
|
+
}
|
|
293
|
+
}`,
|
|
294
|
+
};
|
|
295
|
+
case "generic":
|
|
296
|
+
default:
|
|
297
|
+
return {};
|
|
298
|
+
}
|
|
299
|
+
}
|
|
98
300
|
// ─── Next Version ─────────────────────────────────────────────────────────────
|
|
99
|
-
|
|
301
|
+
function nextVersion(migrationDirs) {
|
|
302
|
+
const dirsJson = JSON.stringify(migrationDirs);
|
|
303
|
+
return `import { tool } from "@opencode-ai/plugin"
|
|
100
304
|
import { z } from "zod"
|
|
101
305
|
import { existsSync, readdirSync } from "fs"
|
|
102
306
|
import path from "path"
|
|
@@ -111,21 +315,12 @@ export const next_version = tool({
|
|
|
111
315
|
execute: async ({ type, cwd: cwdInput }) => {
|
|
112
316
|
const cwd = cwdInput ?? process.cwd()
|
|
113
317
|
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
"prisma/migrations",
|
|
117
|
-
"db/migrations",
|
|
118
|
-
"migrations",
|
|
119
|
-
"drizzle",
|
|
120
|
-
],
|
|
121
|
-
schema: [
|
|
122
|
-
"prisma",
|
|
123
|
-
"db",
|
|
124
|
-
"src/db",
|
|
125
|
-
],
|
|
126
|
-
}
|
|
318
|
+
const migrationDirs = ${dirsJson}
|
|
319
|
+
const schemaDirs = ["prisma", "db", "src/db", "src/main/resources"]
|
|
127
320
|
|
|
128
|
-
|
|
321
|
+
const dirs = type === "migration" ? migrationDirs : schemaDirs
|
|
322
|
+
|
|
323
|
+
for (const dir of dirs) {
|
|
129
324
|
const fullDir = path.join(cwd, dir)
|
|
130
325
|
if (!existsSync(fullDir)) continue
|
|
131
326
|
|
|
@@ -161,34 +356,37 @@ export const next_version = tool({
|
|
|
161
356
|
},
|
|
162
357
|
})
|
|
163
358
|
`;
|
|
359
|
+
}
|
|
164
360
|
// ─── LSP ──────────────────────────────────────────────────────────────────────
|
|
165
|
-
|
|
361
|
+
function lsp(projectType, isKotlin) {
|
|
362
|
+
const { typeCheckCmd, grepExtensions } = getLspConfig(projectType, isKotlin);
|
|
363
|
+
return `import { tool } from "@opencode-ai/plugin"
|
|
166
364
|
import { z } from "zod"
|
|
167
365
|
import { execSync } from "child_process"
|
|
168
366
|
|
|
169
|
-
// LSP tools
|
|
170
|
-
//
|
|
367
|
+
// LSP tools — type checker and reference search
|
|
368
|
+
// Type checker: ${typeCheckCmd}
|
|
171
369
|
|
|
172
|
-
function
|
|
370
|
+
function runTypeCheck(cwd: string, args: string): string {
|
|
173
371
|
try {
|
|
174
|
-
return execSync(
|
|
372
|
+
return execSync(\`${typeCheckCmd} \${args}\`, { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] })
|
|
175
373
|
} catch (e: any) {
|
|
176
|
-
return e.stdout ?? e.message ?? "
|
|
374
|
+
return e.stdout ?? e.message ?? "type check failed"
|
|
177
375
|
}
|
|
178
376
|
}
|
|
179
377
|
|
|
180
378
|
export const lsp_diagnostics = tool({
|
|
181
379
|
name: "lsp_diagnostics",
|
|
182
|
-
description: "Get
|
|
380
|
+
description: "Get diagnostics (errors and warnings) for a file or directory",
|
|
183
381
|
parameters: z.object({
|
|
184
382
|
path: z.string().describe("File or directory to check"),
|
|
185
383
|
severity: z.enum(["error", "warning", "info"]).optional().default("error"),
|
|
186
384
|
}),
|
|
187
385
|
execute: async ({ path: targetPath, severity }) => {
|
|
188
|
-
const output =
|
|
386
|
+
const output = runTypeCheck(process.cwd(), \`2>&1 | grep "\${targetPath}"\`)
|
|
189
387
|
const lines = output.split("\\n").filter((l) => {
|
|
190
|
-
if (severity === "error") return l.includes("error
|
|
191
|
-
if (severity === "warning") return l.includes("warning
|
|
388
|
+
if (severity === "error") return l.includes("error") || l.includes("ERROR")
|
|
389
|
+
if (severity === "warning") return l.includes("warning") || l.includes("WARN")
|
|
192
390
|
return l.trim().length > 0
|
|
193
391
|
})
|
|
194
392
|
return { diagnostics: lines, count: lines.length }
|
|
@@ -204,18 +402,16 @@ export const lsp_goto_definition = tool({
|
|
|
204
402
|
character: z.number().describe("Character position (0-indexed)"),
|
|
205
403
|
}),
|
|
206
404
|
execute: async ({ file, line, character }) => {
|
|
207
|
-
// Use grep as fallback for definition finding
|
|
208
405
|
try {
|
|
209
406
|
const content = require("fs").readFileSync(file, "utf-8")
|
|
210
407
|
const lines = content.split("\\n")
|
|
211
408
|
const targetLine = lines[line - 1] ?? ""
|
|
212
|
-
// Extract symbol at position
|
|
213
409
|
const before = targetLine.slice(0, character)
|
|
214
410
|
const after = targetLine.slice(character)
|
|
215
411
|
const symbolMatch = /[\\w$]+$/.exec(before)
|
|
216
412
|
const symbolEnd = /^[\\w$]*/.exec(after)
|
|
217
413
|
const symbol = (symbolMatch?.[0] ?? "") + (symbolEnd?.[0] ?? "")
|
|
218
|
-
return { symbol, hint: \`Search for 'export.*\${symbol}|function \${symbol}|class \${symbol}|const \${symbol}'\` }
|
|
414
|
+
return { symbol, hint: \`Search for 'export.*\${symbol}|function \${symbol}|class \${symbol}|const \${symbol}|fun \${symbol}|def \${symbol}|func \${symbol}'\` }
|
|
219
415
|
} catch {
|
|
220
416
|
return { error: "Could not read file" }
|
|
221
417
|
}
|
|
@@ -239,10 +435,10 @@ export const lsp_find_references = tool({
|
|
|
239
435
|
const after = lineContent.slice(character)
|
|
240
436
|
const symbol = (/[\\w$]+$/.exec(before)?.[0] ?? "") + (/^[\\w$]*/.exec(after)?.[0] ?? "")
|
|
241
437
|
if (!symbol) return { error: "No symbol at position" }
|
|
242
|
-
const result = execSync(
|
|
243
|
-
|
|
244
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
245
|
-
|
|
438
|
+
const result = execSync(
|
|
439
|
+
\`grep -rn ${grepExtensions} "\${symbol}" .\`,
|
|
440
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
441
|
+
)
|
|
246
442
|
const refs = result.split("\\n").filter(Boolean)
|
|
247
443
|
return { symbol, references: refs.slice(0, 20), total: refs.length }
|
|
248
444
|
} catch (e: any) {
|
|
@@ -268,6 +464,13 @@ export const lsp_document_symbols = tool({
|
|
|
268
464
|
[/^export\\s+(const|let|var)\\s+(\\w+)/, "variable"],
|
|
269
465
|
[/^export\\s+(default\\s+)?class\\s+(\\w+)/, "class"],
|
|
270
466
|
[/^export\\s+(type|interface)\\s+(\\w+)/, "type"],
|
|
467
|
+
[/^\\s*fun\\s+(\\w+)/, "function"],
|
|
468
|
+
[/^\\s*class\\s+(\\w+)/, "class"],
|
|
469
|
+
[/^\\s*interface\\s+(\\w+)/, "interface"],
|
|
470
|
+
[/^\\s*data\\s+class\\s+(\\w+)/, "class"],
|
|
471
|
+
[/^\\s*object\\s+(\\w+)/, "object"],
|
|
472
|
+
[/^\\s*def\\s+(\\w+)/, "function"],
|
|
473
|
+
[/^\\s*func\\s+(\\w+)/, "function"],
|
|
271
474
|
[/^\\s+(async\\s+)?(\\w+)\\s*\\(/, "method"],
|
|
272
475
|
]
|
|
273
476
|
for (const [pattern, kind] of patterns) {
|
|
@@ -295,7 +498,7 @@ export const lsp_workspace_symbols = tool({
|
|
|
295
498
|
execute: async ({ query }) => {
|
|
296
499
|
try {
|
|
297
500
|
const result = execSync(
|
|
298
|
-
\`grep -rn
|
|
501
|
+
\`grep -rn ${grepExtensions} -E "export.*(function|class|const|interface|type|fun|def|func).*\${query}" .\`,
|
|
299
502
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
300
503
|
)
|
|
301
504
|
return { query, matches: result.split("\\n").filter(Boolean).slice(0, 15) }
|
|
@@ -307,7 +510,7 @@ export const lsp_workspace_symbols = tool({
|
|
|
307
510
|
|
|
308
511
|
export const lsp_prepare_rename = tool({
|
|
309
512
|
name: "lsp_prepare_rename",
|
|
310
|
-
description: "Check if the symbol at the given position can be safely renamed.
|
|
513
|
+
description: "Check if the symbol at the given position can be safely renamed.",
|
|
311
514
|
parameters: z.object({
|
|
312
515
|
file: z.string().describe("Source file path"),
|
|
313
516
|
line: z.number().describe("Line number (1-indexed)"),
|
|
@@ -328,8 +531,7 @@ export const lsp_prepare_rename = tool({
|
|
|
328
531
|
return { canRename: false, reason: "No symbol at the given position" }
|
|
329
532
|
}
|
|
330
533
|
|
|
331
|
-
|
|
332
|
-
const KEYWORDS = new Set(["const", "let", "var", "function", "class", "import", "export", "return", "if", "else", "for", "while"])
|
|
534
|
+
const KEYWORDS = new Set(["const", "let", "var", "function", "class", "import", "export", "return", "if", "else", "for", "while", "fun", "val", "def", "func", "package", "object", "data"])
|
|
333
535
|
if (KEYWORDS.has(symbol)) {
|
|
334
536
|
return { canRename: false, reason: \`'\${symbol}' is a language keyword\` }
|
|
335
537
|
}
|
|
@@ -367,10 +569,10 @@ export const lsp_rename = tool({
|
|
|
367
569
|
const oldName = (/[\\w$]+$/.exec(before)?.[0] ?? "") + (/^[\\w$]*/.exec(after)?.[0] ?? "")
|
|
368
570
|
if (!oldName) return { error: "No symbol at position" }
|
|
369
571
|
|
|
370
|
-
const result = execSync(
|
|
371
|
-
|
|
372
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
373
|
-
|
|
572
|
+
const result = execSync(
|
|
573
|
+
\`grep -rln ${grepExtensions} "\\\\b\${oldName}\\\\b" .\`,
|
|
574
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
575
|
+
)
|
|
374
576
|
const files = result.split("\\n").filter(Boolean)
|
|
375
577
|
return {
|
|
376
578
|
oldName,
|
|
@@ -385,8 +587,47 @@ export const lsp_rename = tool({
|
|
|
385
587
|
},
|
|
386
588
|
})
|
|
387
589
|
`;
|
|
590
|
+
}
|
|
591
|
+
function getLspConfig(projectType, isKotlin) {
|
|
592
|
+
if (projectType === "java" && isKotlin) {
|
|
593
|
+
return {
|
|
594
|
+
typeCheckCmd: "./gradlew compileKotlin --console=plain",
|
|
595
|
+
grepExtensions: '--include="*.kt" --include="*.kts" --include="*.java"',
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
switch (projectType) {
|
|
599
|
+
case "node-nextjs":
|
|
600
|
+
case "node-generic":
|
|
601
|
+
return {
|
|
602
|
+
typeCheckCmd: "npx tsc --noEmit --pretty false",
|
|
603
|
+
grepExtensions: '--include="*.ts" --include="*.tsx"',
|
|
604
|
+
};
|
|
605
|
+
case "python":
|
|
606
|
+
return {
|
|
607
|
+
typeCheckCmd: "mypy --no-error-summary",
|
|
608
|
+
grepExtensions: '--include="*.py"',
|
|
609
|
+
};
|
|
610
|
+
case "go":
|
|
611
|
+
return {
|
|
612
|
+
typeCheckCmd: "go vet ./...",
|
|
613
|
+
grepExtensions: '--include="*.go"',
|
|
614
|
+
};
|
|
615
|
+
case "java":
|
|
616
|
+
return {
|
|
617
|
+
typeCheckCmd: "./gradlew compileJava --console=plain",
|
|
618
|
+
grepExtensions: '--include="*.java"',
|
|
619
|
+
};
|
|
620
|
+
case "generic":
|
|
621
|
+
default:
|
|
622
|
+
return {
|
|
623
|
+
typeCheckCmd: "echo 'No type checker configured'",
|
|
624
|
+
grepExtensions: '--include="*.ts" --include="*.tsx" --include="*.py" --include="*.go" --include="*.java" --include="*.kt"',
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
}
|
|
388
628
|
// ─── AST Grep ─────────────────────────────────────────────────────────────────
|
|
389
|
-
|
|
629
|
+
function astGrep(defaultLang) {
|
|
630
|
+
return `import { tool } from "@opencode-ai/plugin"
|
|
390
631
|
import { z } from "zod"
|
|
391
632
|
import { execSync } from "child_process"
|
|
392
633
|
|
|
@@ -398,7 +639,6 @@ function runAstGrep(args: string): { output: string; error?: string } {
|
|
|
398
639
|
})
|
|
399
640
|
return { output }
|
|
400
641
|
} catch (e: any) {
|
|
401
|
-
// ast-grep may not be installed
|
|
402
642
|
if (e.code === "ENOENT" || e.message?.includes("not found")) {
|
|
403
643
|
return { output: "", error: "ast-grep not installed. Run: npm install -g @ast-grep/cli" }
|
|
404
644
|
}
|
|
@@ -411,7 +651,7 @@ export const ast_grep_search = tool({
|
|
|
411
651
|
description: "Search for code patterns using AST matching. More precise than text search. Use meta-variables: $NAME (single node), $$$ARGS (multiple nodes).",
|
|
412
652
|
parameters: z.object({
|
|
413
653
|
pattern: z.string().describe("AST pattern with meta-variables. E.g.: 'console.log($MSG)', 'function $NAME($$$ARGS)'"),
|
|
414
|
-
language: z.enum(["typescript", "javascript", "tsx", "python", "rust", "go"]).default("
|
|
654
|
+
language: z.enum(["typescript", "javascript", "tsx", "python", "rust", "go", "java", "kotlin"]).default("${defaultLang}"),
|
|
415
655
|
path: z.string().optional().describe("Directory or file to search (defaults to current directory)"),
|
|
416
656
|
maxResults: z.number().optional().default(20),
|
|
417
657
|
}),
|
|
@@ -441,7 +681,7 @@ export const ast_grep_replace = tool({
|
|
|
441
681
|
parameters: z.object({
|
|
442
682
|
pattern: z.string().describe("Pattern to match (use meta-variables like $NAME, $$$ARGS)"),
|
|
443
683
|
replacement: z.string().describe("Replacement pattern (use same meta-variables)"),
|
|
444
|
-
language: z.enum(["typescript", "javascript", "tsx", "python", "rust", "go"]).default("
|
|
684
|
+
language: z.enum(["typescript", "javascript", "tsx", "python", "rust", "go", "java", "kotlin"]).default("${defaultLang}"),
|
|
445
685
|
path: z.string().optional().describe("Directory or file to transform"),
|
|
446
686
|
dryRun: z.boolean().optional().default(true).describe("Preview changes without applying (default: true)"),
|
|
447
687
|
}),
|
|
@@ -465,4 +705,5 @@ export const ast_grep_replace = tool({
|
|
|
465
705
|
},
|
|
466
706
|
})
|
|
467
707
|
`;
|
|
708
|
+
}
|
|
468
709
|
//# sourceMappingURL=tools.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/templates/tools.ts"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/templates/tools.ts"],"names":[],"mappings":"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"}
|