@skastr0/quartz-opencode-plugin 0.1.0 → 0.2.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/README.md CHANGED
@@ -26,7 +26,7 @@ The bundle is written to `dist/server.js` and exported as `@skastr0/quartz-openc
26
26
 
27
27
  ## Runtime Behavior
28
28
 
29
- The plugin roots Quartz at the OpenCode workspace directory. It creates one Effect runtime, reuses the analyzer across tool calls, and marks the analyzer cache dirty after file-modifying tools such as `edit`, `write`, or `morph-mcp_edit_file`.
29
+ The plugin roots one persistent native Quartz analyzer at the OpenCode workspace directory and reuses it across tool calls. It marks the analyzer cache dirty from OpenCode's `file.edited` and `file.watcher.updated` events, with tool-hook fallbacks for `apply_patch`, `edit`, `write`, and `morph-mcp_edit_file`. The analyzer is disposed when OpenCode emits `server.instance.disposed` for that workspace directory; deleting a session does not close it.
30
30
 
31
31
  Use the optional `package` argument when the workspace has multiple discovered TypeScript packages.
32
32
 
@@ -58,6 +58,7 @@ Relationships and refactors:
58
58
  - `type_why_error`
59
59
  - `type_explain`
60
60
  - `type_transform_search`
61
+ - `type_verify_contract`
61
62
 
62
63
  ## Examples
63
64
 
package/dist/server.js CHANGED
@@ -1,13 +1,11 @@
1
1
  // @bun
2
2
  // src/server.ts
3
3
  import { tool } from "@opencode-ai/plugin";
4
- import { Effect, ManagedRuntime } from "effect";
5
- import { CoreLayer, TypeAnalyzerService } from "@skastr0/quartz-core";
6
- var run = (effect) => Effect.runPromise(effect.pipe(Effect.mapError((error) => error instanceof Error ? error : new Error(String(error)))));
4
+ import { createTypeAnalyzer } from "@skastr0/quartz-engine";
7
5
  var optionalPackageArg = tool.schema.string().optional().describe("Package/directory to analyze. Omit for root or single-package projects.");
8
6
  var limitArg = tool.schema.number().optional().describe("Maximum number of results");
9
7
  var json = (value) => JSON.stringify(value, null, 2);
10
- var FILE_MODIFYING_TOOLS = new Set(["edit", "morph-mcp_edit_file", "write"]);
8
+ var FILE_MODIFYING_TOOLS = new Set(["apply_patch", "edit", "morph-mcp_edit_file", "write"]);
11
9
  var packageOption = (packageName) => packageName === undefined ? {} : { packageName };
12
10
  var optionalOption = (key, value) => value === undefined ? {} : { [key]: value };
13
11
  var createDiscoveryTools = (analyzer) => ({
@@ -15,7 +13,7 @@ var createDiscoveryTools = (analyzer) => ({
15
13
  description: "List TypeScript packages discovered from tsconfig.json files.",
16
14
  args: {},
17
15
  async execute() {
18
- return json(await run(analyzer.getPackages()));
16
+ return json(await analyzer.getPackages());
19
17
  }
20
18
  }),
21
19
  type_symbols: tool({
@@ -27,12 +25,12 @@ var createDiscoveryTools = (analyzer) => ({
27
25
  limit: limitArg
28
26
  },
29
27
  async execute(args) {
30
- return json(await run(analyzer.listSymbols({
28
+ return json(await analyzer.listSymbols({
31
29
  ...packageOption(args.package),
32
30
  ...optionalOption("pattern", args.pattern),
33
31
  ...optionalOption("kind", args.kind),
34
32
  ...optionalOption("limit", args.limit)
35
- })));
33
+ }));
36
34
  }
37
35
  }),
38
36
  type_info: tool({
@@ -42,7 +40,7 @@ var createDiscoveryTools = (analyzer) => ({
42
40
  package: optionalPackageArg
43
41
  },
44
42
  async execute(args) {
45
- return json(await run(analyzer.getTypeInfo(args.symbol, args.package)));
43
+ return json(await analyzer.getTypeInfo(args.symbol, args.package));
46
44
  }
47
45
  }),
48
46
  type_expand: tool({
@@ -52,7 +50,7 @@ var createDiscoveryTools = (analyzer) => ({
52
50
  package: optionalPackageArg
53
51
  },
54
52
  async execute(args) {
55
- return json(await run(analyzer.expandType(args.symbol, args.package)));
53
+ return json(await analyzer.expandType(args.symbol, args.package));
56
54
  }
57
55
  }),
58
56
  type_related: tool({
@@ -62,7 +60,7 @@ var createDiscoveryTools = (analyzer) => ({
62
60
  package: optionalPackageArg
63
61
  },
64
62
  async execute(args) {
65
- return json(await run(analyzer.findRelated(args.symbol, args.package)));
63
+ return json(await analyzer.findRelated(args.symbol, args.package));
66
64
  }
67
65
  }),
68
66
  type_search: tool({
@@ -76,14 +74,14 @@ var createDiscoveryTools = (analyzer) => ({
76
74
  limit: limitArg
77
75
  },
78
76
  async execute(args) {
79
- return json(await run(analyzer.searchTypes({
77
+ return json(await analyzer.searchTypes({
80
78
  query: args.query,
81
79
  ...optionalOption("pattern", args.pattern),
82
80
  ...optionalOption("hasProperty", args.hasProperty),
83
81
  ...optionalOption("extends", args.extends),
84
82
  ...packageOption(args.package),
85
83
  ...optionalOption("limit", args.limit)
86
- })));
84
+ }));
87
85
  }
88
86
  })
89
87
  });
@@ -95,7 +93,7 @@ var createAnalysisTools = (analyzer) => ({
95
93
  package: optionalPackageArg
96
94
  },
97
95
  async execute(args) {
98
- return json(await run(analyzer.evalType(args.expression, args.package)));
96
+ return json(await analyzer.evalType(args.expression, args.package));
99
97
  }
100
98
  }),
101
99
  type_diagnostics: tool({
@@ -105,10 +103,10 @@ var createAnalysisTools = (analyzer) => ({
105
103
  explain: tool.schema.boolean().optional().describe("Include explanations for diagnostics")
106
104
  },
107
105
  async execute(args) {
108
- return json(await run(analyzer.getDiagnostics({
106
+ return json(await analyzer.getDiagnostics({
109
107
  ...packageOption(args.package),
110
108
  ...optionalOption("explain", args.explain)
111
- })));
109
+ }));
112
110
  }
113
111
  }),
114
112
  type_check_snippet: tool({
@@ -118,7 +116,7 @@ var createAnalysisTools = (analyzer) => ({
118
116
  package: optionalPackageArg
119
117
  },
120
118
  async execute(args) {
121
- return json(await run(analyzer.checkSnippet(args.code, args.package)));
119
+ return json(await analyzer.checkSnippet(args.code, args.package));
122
120
  }
123
121
  }),
124
122
  type_at_position: tool({
@@ -130,7 +128,7 @@ var createAnalysisTools = (analyzer) => ({
130
128
  package: optionalPackageArg
131
129
  },
132
130
  async execute(args) {
133
- return json(await run(analyzer.getTypeAtPosition(args.file, args.line, args.column, args.package)));
131
+ return json(await analyzer.getTypeAtPosition(args.file, args.line, args.column, args.package));
134
132
  }
135
133
  }),
136
134
  type_file: tool({
@@ -142,11 +140,11 @@ var createAnalysisTools = (analyzer) => ({
142
140
  package: optionalPackageArg
143
141
  },
144
142
  async execute(args) {
145
- return json(await run(analyzer.getFileDeclarations(args.file, {
143
+ return json(await analyzer.getFileDeclarations(args.file, {
146
144
  ...packageOption(args.package),
147
145
  ...optionalOption("symbol", args.symbol),
148
146
  ...optionalOption("includePrivate", args.includePrivate)
149
- })));
147
+ }));
150
148
  }
151
149
  }),
152
150
  type_refresh: tool({
@@ -155,7 +153,7 @@ var createAnalysisTools = (analyzer) => ({
155
153
  package: optionalPackageArg
156
154
  },
157
155
  async execute(args) {
158
- return await run(analyzer.refresh(args.package));
156
+ return await analyzer.refresh(args.package);
159
157
  }
160
158
  })
161
159
  });
@@ -176,7 +174,7 @@ var createCompatibilityTools = (analyzer) => ({
176
174
  package: optionalPackageArg
177
175
  },
178
176
  async execute(args) {
179
- return json(await run(analyzer.checkCompatibility(args.from, args.to, args.package)));
177
+ return json(await analyzer.checkCompatibility(args.from, args.to, args.package));
180
178
  }
181
179
  })
182
180
  });
@@ -190,11 +188,11 @@ var createGraphTools = (analyzer) => ({
190
188
  package: optionalPackageArg
191
189
  },
192
190
  async execute(args) {
193
- return json(await run(analyzer.generateGraph(args.symbol, {
191
+ return json(await analyzer.generateGraph(args.symbol, {
194
192
  ...packageOption(args.package),
195
193
  ...optionalOption("depth", args.depth),
196
194
  ...optionalOption("format", args.format)
197
- })));
195
+ }));
198
196
  }
199
197
  })
200
198
  });
@@ -207,12 +205,12 @@ var createRefactorTools = (analyzer) => ({
207
205
  package: optionalPackageArg
208
206
  },
209
207
  async execute(args) {
210
- return json(await run(analyzer.previewRefactor({
208
+ return json(await analyzer.previewRefactor({
211
209
  action: "rename",
212
210
  symbol: args.symbol,
213
211
  to: args.to,
214
212
  ...packageOption(args.package)
215
- })));
213
+ }));
216
214
  }
217
215
  })
218
216
  });
@@ -227,13 +225,13 @@ var createExplanationTools = (analyzer) => ({
227
225
  package: optionalPackageArg
228
226
  },
229
227
  async execute(args) {
230
- return json(await run(analyzer.explainError({
228
+ return json(await analyzer.explainError({
231
229
  ...packageOption(args.package),
232
230
  ...optionalOption("code", args.code),
233
231
  ...optionalOption("message", args.message),
234
232
  ...optionalOption("file", args.file),
235
233
  ...optionalOption("line", args.line)
236
- })));
234
+ }));
237
235
  }
238
236
  }),
239
237
  type_explain: tool({
@@ -243,7 +241,7 @@ var createExplanationTools = (analyzer) => ({
243
241
  package: optionalPackageArg
244
242
  },
245
243
  async execute(args) {
246
- return json(await run(analyzer.explainType(args.expression, args.package)));
244
+ return json(await analyzer.explainType(args.expression, args.package));
247
245
  }
248
246
  })
249
247
  });
@@ -266,7 +264,7 @@ var createTransformSearchTools = (analyzer) => ({
266
264
  package: optionalPackageArg
267
265
  },
268
266
  async execute(args) {
269
- return await run(analyzer.transformSearch({
267
+ return json(await analyzer.transformSearch({
270
268
  ...packageOption(args.package),
271
269
  ...optionalOption("from", args.from),
272
270
  ...optionalOption("to", args.to),
@@ -298,7 +296,7 @@ var createVerifyContractTools = (analyzer) => ({
298
296
  package: optionalPackageArg
299
297
  },
300
298
  async execute(args) {
301
- return json(await run(analyzer.verifyContract({
299
+ return json(await analyzer.verifyContract({
302
300
  ...packageOption(args.package),
303
301
  ...optionalOption("from", args.from),
304
302
  ...optionalOption("to", args.to),
@@ -307,7 +305,7 @@ var createVerifyContractTools = (analyzer) => ({
307
305
  ...optionalOption("includeDiagnostics", args.includeDiagnostics),
308
306
  ...optionalOption("includeTransformEvidence", args.includeTransformEvidence),
309
307
  ...optionalOption("transformLimit", args.transformLimit)
310
- })));
308
+ }));
311
309
  }
312
310
  })
313
311
  });
@@ -317,9 +315,34 @@ var createToolDefinitions = (analyzer) => ({
317
315
  ...createRelationshipTools(analyzer)
318
316
  });
319
317
  var QuartzPlugin = async (ctx) => {
320
- const runtime = ManagedRuntime.make(CoreLayer(ctx.directory));
321
- const analyzer = runtime.runSync(TypeAnalyzerService);
318
+ const analyzer = await createTypeAnalyzer(ctx.directory, { collectTiming: true });
322
319
  const client = ctx.client;
320
+ let disposePromise = null;
321
+ const disposeAnalyzer = (reason) => {
322
+ disposePromise ??= (async () => {
323
+ try {
324
+ await analyzer.dispose();
325
+ await client.app?.log?.({
326
+ body: {
327
+ service: "quartz",
328
+ level: "debug",
329
+ message: "quartz analyzer disposed",
330
+ extra: { reason }
331
+ }
332
+ });
333
+ } catch (cause) {
334
+ await client.app?.log?.({
335
+ body: {
336
+ service: "quartz",
337
+ level: "warn",
338
+ message: "quartz analyzer dispose failed",
339
+ extra: { reason, cause: cause instanceof Error ? cause.message : String(cause) }
340
+ }
341
+ });
342
+ }
343
+ })();
344
+ return disposePromise;
345
+ };
323
346
  return {
324
347
  event: async ({ event }) => {
325
348
  if (event.type === "session.idle") {
@@ -328,17 +351,24 @@ var QuartzPlugin = async (ctx) => {
328
351
  service: "quartz",
329
352
  level: "debug",
330
353
  message: "session idle observed by quartz plugin",
331
- extra: { sessionID: event.properties.sessionID }
354
+ extra: { sessionID: event.properties?.sessionID }
332
355
  }
333
356
  });
334
357
  }
358
+ if (event.type === "file.edited" || event.type === "file.watcher.updated") {
359
+ await analyzer.markDirty();
360
+ }
361
+ if (event.type === "server.instance.disposed" && event.properties.directory === ctx.directory) {
362
+ await disposeAnalyzer(event.type);
363
+ }
335
364
  },
336
365
  "tool.execute.after": async (input) => {
337
366
  if (FILE_MODIFYING_TOOLS.has(input.tool)) {
338
- await run(analyzer.markDirty());
367
+ await analyzer.markDirty();
339
368
  }
340
369
  },
341
- tool: createToolDefinitions(analyzer)
370
+ tool: createToolDefinitions(analyzer),
371
+ dispose: () => disposeAnalyzer("explicit")
342
372
  };
343
373
  };
344
374
  var server_default = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skastr0/quartz-opencode-plugin",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "main": "./dist/server.js",
6
6
  "types": "./dist/server.d.ts",
@@ -17,12 +17,11 @@
17
17
  "README.md"
18
18
  ],
19
19
  "scripts": {
20
- "build": "rm -rf dist && bun build ./src/server.ts --outdir ./dist --target bun --format esm --external @opencode-ai/plugin --external @skastr0/quartz-core --external effect && tsc -p tsconfig.build.json",
20
+ "build": "bun ../../scripts/clean-build-output.ts opencode-plugin && bun build ./src/server.ts --outdir ./dist --target bun --format esm --external @opencode-ai/plugin --external @skastr0/quartz-engine && tsc -p tsconfig.build.json",
21
21
  "prepack": "bun run build"
22
22
  },
23
23
  "dependencies": {
24
- "@skastr0/quartz-core": "0.1.0",
25
- "effect": "^3.21.2"
24
+ "@skastr0/quartz-engine": "0.2.1"
26
25
  },
27
26
  "peerDependencies": {
28
27
  "@opencode-ai/plugin": ">=1.4.0"