@cgasgarth/opencode-for-rust 1.1.7 → 1.1.9-next.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cgasgarth/opencode-for-rust",
3
- "version": "1.1.7",
3
+ "version": "1.1.9-next.1",
4
4
  "description": "OpenCode plugin for Rust",
5
5
  "author": {
6
6
  "name": "Sisyphus",
@@ -9,8 +9,8 @@
9
9
  "type": "module",
10
10
  "exports": {
11
11
  ".": {
12
- "types": "./dist/index.d.ts",
13
- "default": "./dist/index.js"
12
+ "types": "./cgasgarth/opencode-for-rust/index.d.ts",
13
+ "default": "./cgasgarth/opencode-for-rust/index.js"
14
14
  }
15
15
  },
16
16
  "repository": {
@@ -22,14 +22,13 @@
22
22
  "provenance": true
23
23
  },
24
24
  "files": [
25
- "dist",
26
- "src/version.ts"
25
+ "cgasgarth"
27
26
  ],
28
27
  "dependencies": {
29
- "@opencode-ai/plugin": "1.0.85"
28
+ "@opencode-ai/plugin": "1.0.85",
29
+ "zod": "^4.0.0"
30
30
  },
31
31
  "devDependencies": {
32
- "zod": "^4.0.0",
33
32
  "@eslint/js": "^9.39.1",
34
33
  "@types/bun": "latest",
35
34
  "@types/node": "^20.12.7",
@@ -50,7 +49,7 @@
50
49
  "scripts": {
51
50
  "test": "bun test tests/",
52
51
  "typecheck": "tsc --noEmit",
53
- "build": "esbuild ./src/index.ts --bundle --format=esm --platform=node --outdir=dist --external:@opencode-ai/plugin --external:zod && tsc --project tsconfig.build.json",
52
+ "build": "esbuild ./src/index.ts --bundle --format=esm --platform=node --outdir=cgasgarth/opencode-for-rust --external:@opencode-ai/plugin --external:zod && tsc --project tsconfig.build.json",
54
53
  "lint": "eslint src --max-warnings 0",
55
54
  "format": "prettier --check .",
56
55
  "prepare": "husky"
package/dist/index.js DELETED
@@ -1,360 +0,0 @@
1
- // src/index.ts
2
- import { tool } from "@opencode-ai/plugin";
3
-
4
- // src/lib/regex-extractor.ts
5
- import * as fs from "fs";
6
- var PATTERNS = [
7
- {
8
- kind: "struct",
9
- regex: /(?:^|\n)(\s*(?:\/\/\/.*\n)*)(\s*pub(?:\([^)]*\))?\s+)?struct\s+(\w+)(?:<[^>]*>)?(?:\s*\{|\s*;)/g,
10
- useBodyExtractor: true
11
- },
12
- {
13
- kind: "enum",
14
- regex: /(?:^|\n)(\s*(?:\/\/\/.*\n)*)(\s*pub(?:\([^)]*\))?\s+)?enum\s+(\w+)(?:<[^>]*>)?(?:\s*\{)/g,
15
- useBodyExtractor: true
16
- },
17
- {
18
- kind: "trait",
19
- regex: /(?:^|\n)(\s*(?:\/\/\/.*\n)*)(\s*pub(?:\([^)]*\))?\s+)?trait\s+(\w+)(?:<[^>]*>)?(?:\s*\{)/g,
20
- useBodyExtractor: true
21
- },
22
- {
23
- kind: "function",
24
- regex: /(?:^|\n)(\s*(?:\/\/\/.*\n)*)(\s*pub(?:\([^)]*\))?\s+)?(?:async\s+)?fn\s+(\w+)(?:<[^>]*>)?\s*\([^)]*\)(?:\s*->\s*[^{]+)?(?:\s*where[^{]*)?\s*\{/g,
25
- useBodyExtractor: true
26
- },
27
- {
28
- kind: "type_alias",
29
- regex: /(?:^|\n)(\s*(?:\/\/\/.*\n)*)(\s*pub(?:\([^)]*\))?\s+)?type\s+(\w+)(?:<[^>]*>)?\s*=\s*[^;]+;/g
30
- },
31
- {
32
- kind: "const",
33
- regex: /(?:^|\n)(\s*(?:\/\/\/.*\n)*)(\s*pub(?:\([^)]*\))?\s+)?const\s+(\w+)\s*:\s*[^=]+=\s*[^;]+;/g
34
- },
35
- {
36
- kind: "impl",
37
- regex: /(?:^|\n)(\s*)impl(?:<[^>]*>)?\s+(?:(\w+)\s+for\s+)?(\w+)(?:<[^>]*>)?(?:\s*where[^{]*)?\s*\{/g,
38
- useBodyExtractor: true
39
- }
40
- ];
41
-
42
- class RegexRustTypeExtractor {
43
- config;
44
- constructor(config) {
45
- this.config = config;
46
- }
47
- async extract(filePath, content) {
48
- const fileContent = content || await fs.promises.readFile(filePath, "utf-8");
49
- const types = [];
50
- for (const { kind, regex, useBodyExtractor } of PATTERNS) {
51
- regex.lastIndex = 0;
52
- let match;
53
- while ((match = regex.exec(fileContent)) !== null) {
54
- const docComment = match[1]?.trim() || undefined;
55
- const visibility = match[2]?.trim();
56
- const name = match[3];
57
- const exported = visibility?.startsWith("pub") ?? false;
58
- const matchStart = match.index + (match[0].startsWith(`
59
- `) ? 1 : 0);
60
- const lineStart = fileContent.substring(0, matchStart).split(`
61
- `).length;
62
- let body = match[0].startsWith(`
63
- `) ? match[0].substring(1) : match[0];
64
- if (kind === "struct" && body.trim().endsWith(";")) {} else if (useBodyExtractor) {
65
- const braceIndex = fileContent.indexOf("{", matchStart);
66
- if (braceIndex !== -1) {
67
- body = this.extractFullBody(fileContent, matchStart);
68
- }
69
- }
70
- const lineEnd = lineStart + body.split(`
71
- `).length - 1;
72
- const signature = body.split(`
73
- `)[0];
74
- types.push({
75
- name,
76
- kind,
77
- signature,
78
- docComment: docComment || undefined,
79
- exported,
80
- filePath,
81
- lineStart,
82
- lineEnd,
83
- body
84
- });
85
- }
86
- }
87
- return types;
88
- }
89
- extractFullBody(content, startIndex) {
90
- let braceCount = 0;
91
- let started = false;
92
- let endIndex = startIndex;
93
- for (let i = startIndex;i < content.length; i++) {
94
- const char = content[i];
95
- if (char === "{") {
96
- braceCount++;
97
- started = true;
98
- } else if (char === "}") {
99
- braceCount--;
100
- if (started && braceCount === 0) {
101
- endIndex = i + 1;
102
- break;
103
- }
104
- }
105
- }
106
- return content.substring(startIndex, endIndex);
107
- }
108
- }
109
-
110
- // src/lib/lookup.ts
111
- import * as path from "path";
112
- import * as fs2 from "fs/promises";
113
- async function* walkDir(dir) {
114
- const entries = await fs2.readdir(dir, { withFileTypes: true });
115
- for (const entry of entries) {
116
- const fullPath = path.join(dir, entry.name);
117
- if (entry.isDirectory()) {
118
- yield* walkDir(fullPath);
119
- } else if (entry.isFile() && entry.name.endsWith(".rs")) {
120
- yield fullPath;
121
- }
122
- }
123
- }
124
-
125
- class RustTypeLookup {
126
- directory;
127
- config;
128
- extractor;
129
- cache = new Map;
130
- lastScanTime = 0;
131
- constructor(directory, config) {
132
- this.directory = directory;
133
- this.config = config;
134
- this.extractor = new RegexRustTypeExtractor(config);
135
- }
136
- async findType(name) {
137
- await this.ensureCache();
138
- return this.cache.get(name);
139
- }
140
- async listTypeNames() {
141
- await this.ensureCache();
142
- return Array.from(this.cache.keys());
143
- }
144
- async refresh() {
145
- this.cache.clear();
146
- for await (const absolutePath of walkDir(this.directory)) {
147
- const relativePath = path.relative(this.directory, absolutePath);
148
- if (this.shouldExclude(relativePath))
149
- continue;
150
- try {
151
- const types = await this.extractor.extract(absolutePath);
152
- for (const type of types) {
153
- if (type.name) {
154
- const existing = this.cache.get(type.name);
155
- if (!existing || this.getPriority(type.kind) < this.getPriority(existing.kind)) {
156
- this.cache.set(type.name, type);
157
- }
158
- }
159
- }
160
- } catch {
161
- continue;
162
- }
163
- }
164
- this.lastScanTime = Date.now();
165
- }
166
- getPriority(kind) {
167
- const priority = {
168
- struct: 1,
169
- enum: 1,
170
- trait: 1,
171
- type_alias: 1,
172
- const: 2,
173
- function: 3,
174
- macro: 4,
175
- impl: 10
176
- };
177
- return priority[kind] || 99;
178
- }
179
- async ensureCache() {
180
- if (this.cache.size === 0) {
181
- await this.refresh();
182
- }
183
- }
184
- shouldExclude(filePath) {
185
- return this.config.excludePatterns.some((pattern) => filePath.includes(pattern));
186
- }
187
- }
188
-
189
- // src/lib/formatter.ts
190
- class RustContentFormatter {
191
- config;
192
- constructor(config) {
193
- this.config = config;
194
- }
195
- formatInjectedTypes(types) {
196
- if (types.length === 0)
197
- return "";
198
- const sortedTypes = this.sortTypes(types);
199
- const startComment = "/" + "* Injected Rust Types *" + "/";
200
- const endComment = "/" + "* End Injected Rust Types *" + "/";
201
- const header = `
202
-
203
- ` + startComment + `
204
- `;
205
- const formattedTypes = sortedTypes.map((type) => {
206
- let output = "";
207
- if (type.docComment) {
208
- output += `${type.docComment}
209
- `;
210
- }
211
- output += type.body || type.signature;
212
- return output;
213
- });
214
- return header + formattedTypes.join(`
215
-
216
- `) + `
217
- ` + endComment + `
218
- `;
219
- }
220
- sortTypes(types) {
221
- return types.sort((a, b) => {
222
- const priority = {
223
- struct: 1,
224
- enum: 2,
225
- trait: 3,
226
- type_alias: 4,
227
- function: 5,
228
- impl: 6,
229
- const: 7,
230
- macro: 8
231
- };
232
- const pA = priority[a.kind] || 99;
233
- const pB = priority[b.kind] || 99;
234
- if (pA !== pB)
235
- return pA - pB;
236
- return a.name.localeCompare(b.name);
237
- });
238
- }
239
- }
240
-
241
- // src/index.ts
242
- import { appendFileSync } from "fs";
243
- var DEBUG_LOG = "/tmp/opencode-rust-plugin.log";
244
- function debugLog(msg) {
245
- const timestamp = new Date().toISOString();
246
- appendFileSync(DEBUG_LOG, `[${timestamp}] ${msg}
247
- `);
248
- }
249
- var RustPlugin = async (context) => {
250
- debugLog(`Plugin initializing with context: ${JSON.stringify(context)}`);
251
- const config = {
252
- enabled: true,
253
- debug: true,
254
- budget: 50,
255
- excludePatterns: [],
256
- imports: false
257
- };
258
- const directory = context.directory || process.cwd();
259
- debugLog(`Using directory: ${directory}`);
260
- const lookup = new RustTypeLookup(directory, config);
261
- const formatter = new RustContentFormatter(config);
262
- const extractor = new RegexRustTypeExtractor(config);
263
- debugLog("Created lookup, formatter, extractor");
264
- if (!tool) {
265
- debugLog('FATAL: @opencode-ai/plugin "tool" export is undefined');
266
- }
267
- const z = tool.schema;
268
- if (!z) {
269
- debugLog("FATAL: tool.schema is undefined - cannot define Zod schemas");
270
- } else {
271
- debugLog("tool.schema is available");
272
- }
273
- try {
274
- await lookup.refresh();
275
- debugLog("Initial refresh completed");
276
- } catch (e) {
277
- debugLog(`Initial refresh failed: ${e}`);
278
- }
279
- const hooks = {
280
- tool: {
281
- lookup_type: tool({
282
- description: "Find a Rust type definition by name",
283
- args: {
284
- name: z.string().describe("The name of the type to look up")
285
- },
286
- async execute(args, _context) {
287
- debugLog(`lookup_type called with: ${JSON.stringify(args)}`);
288
- try {
289
- await lookup.refresh();
290
- debugLog("lookup_type: refresh done");
291
- const type = await lookup.findType(args.name);
292
- debugLog(`lookup_type: findType result: ${type ? "found" : "not found"}`);
293
- if (!type) {
294
- return `Type '${args.name}' not found`;
295
- }
296
- const result = formatter.formatInjectedTypes([type]);
297
- debugLog(`lookup_type: returning ${result.length} chars`);
298
- return result;
299
- } catch (e) {
300
- debugLog(`lookup_type error: ${e}`);
301
- throw e;
302
- }
303
- }
304
- }),
305
- list_types: tool({
306
- description: "List all available Rust type names in the project",
307
- args: {
308
- ignore: z.boolean().optional().describe("Ignore this argument (legacy support)")
309
- },
310
- async execute(_args, _context) {
311
- debugLog("list_types called");
312
- try {
313
- await lookup.refresh();
314
- debugLog("list_types: refresh done");
315
- const names = await lookup.listTypeNames();
316
- debugLog(`list_types: found ${names.length} types`);
317
- if (names.length === 0) {
318
- return "No Rust types found in project";
319
- }
320
- return names.join(`
321
- `);
322
- } catch (e) {
323
- debugLog(`list_types error: ${e}`);
324
- throw e;
325
- }
326
- }
327
- })
328
- },
329
- "tool.execute.after": async (input, output) => {
330
- debugLog(`tool.execute.after: tool=${input.tool}`);
331
- if (input.tool !== "read") {
332
- return;
333
- }
334
- const metadata = output.metadata;
335
- const filePath = metadata?.filePath;
336
- debugLog(`tool.execute.after: filePath=${filePath}`);
337
- if (!filePath || !filePath.endsWith(".rs")) {
338
- return;
339
- }
340
- try {
341
- const types = await extractor.extract(filePath);
342
- debugLog(`tool.execute.after: extracted ${types.length} types`);
343
- const formatted = formatter.formatInjectedTypes(types);
344
- if (formatted) {
345
- output.output = output.output + formatted;
346
- debugLog(`tool.execute.after: appended ${formatted.length} chars`);
347
- }
348
- } catch (error) {
349
- debugLog(`tool.execute.after error: ${error}`);
350
- }
351
- }
352
- };
353
- debugLog("Hooks created, returning");
354
- return hooks;
355
- };
356
- var src_default = RustPlugin;
357
- export {
358
- src_default as default,
359
- RustPlugin
360
- };