@sven1103/opencode-worktree-workflow 1.0.0-alpha.1 → 1.0.0-alpha.3

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.
Files changed (3) hide show
  1. package/index.cjs +12 -0
  2. package/package.json +16 -3
  3. package/src/index.js +44 -60
package/index.cjs ADDED
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+
3
+ const plugin = {
4
+ id: "@sven1103/opencode-worktree-workflow",
5
+ async server(...args) {
6
+ const mod = await import("./src/index.js");
7
+ return mod.WorktreeWorkflowPlugin(...args);
8
+ },
9
+ };
10
+
11
+ module.exports = plugin;
12
+ module.exports.default = plugin;
package/package.json CHANGED
@@ -1,16 +1,29 @@
1
1
  {
2
2
  "name": "@sven1103/opencode-worktree-workflow",
3
- "version": "1.0.0-alpha.1",
3
+ "version": "1.0.0-alpha.3",
4
4
  "description": "OpenCode plugin for creating and cleaning up git worktrees.",
5
5
  "type": "module",
6
- "main": "./src/index.js",
6
+ "main": "./index.cjs",
7
7
  "bin": {
8
8
  "opencode-worktree-workflow": "./src/cli.js"
9
9
  },
10
10
  "exports": {
11
- ".": "./src/index.js"
11
+ ".": {
12
+ "require": "./index.cjs",
13
+ "import": "./src/index.js",
14
+ "default": "./index.cjs"
15
+ },
16
+ "./server": {
17
+ "require": "./index.cjs",
18
+ "import": "./src/index.js",
19
+ "default": "./index.cjs"
20
+ }
12
21
  },
22
+ "oc-plugin": [
23
+ "server"
24
+ ],
13
25
  "files": [
26
+ "index.cjs",
14
27
  "src",
15
28
  "schemas",
16
29
  "skills"
package/src/index.js CHANGED
@@ -163,6 +163,8 @@ export const __internal = {
163
163
  hasOpaqueRepoRootAbsoluteReference,
164
164
  };
165
165
 
166
+ export const pluginID = "@sven1103/opencode-worktree-workflow";
167
+
166
168
  export const WorktreeWorkflowPlugin = async ({ $, directory }) => {
167
169
  const service = createWorktreeWorkflowService({
168
170
  directory,
@@ -170,14 +172,14 @@ export const WorktreeWorkflowPlugin = async ({ $, directory }) => {
170
172
  stateStore: createRuntimeStateStore(),
171
173
  });
172
174
 
173
- async function onToolExecuteBefore(input) {
174
- const toolName = input?.tool?.name ?? input?.toolName;
175
- const args = input?.args || {};
175
+ async function onToolExecuteBefore(input, output) {
176
+ const toolName = input?.tool;
177
+ const args = output?.args || {};
176
178
  const classification = classifyToolExecution({ toolName, args });
177
- if (classification.bypass) return input;
179
+ if (classification.bypass) return;
178
180
  const rewritePolicy = getToolRewritePolicy({ toolName });
179
181
 
180
- const sessionID = input?.sessionID ?? input?.context?.sessionID;
182
+ const sessionID = input?.sessionID;
181
183
  let binding = null;
182
184
 
183
185
  if (classification.requiresIsolation) {
@@ -199,7 +201,7 @@ export const WorktreeWorkflowPlugin = async ({ $, directory }) => {
199
201
  if (activeTask?.worktree_path) binding = { repoRoot, task: activeTask };
200
202
  }
201
203
 
202
- if (!binding) return input;
204
+ if (!binding) return;
203
205
 
204
206
  if (toolName === "task") {
205
207
  const handoffPath = resolveSafeHandoffPath({
@@ -212,13 +214,11 @@ export const WorktreeWorkflowPlugin = async ({ $, directory }) => {
212
214
  }
213
215
  const workspaceContext = buildWorkspaceContext({ task: binding.task, workspaceRole: deriveWorkspaceRole({ subagentType: args.subagent_type }) });
214
216
  await enrichHandoffArtifact(handoffPath, workspaceContext);
215
- return {
216
- ...input,
217
- args: {
218
- ...args,
219
- prompt: `${args.prompt}\n\nWorkspace binding:\n- task_id: ${workspaceContext.task_id}\n- worktree_path: ${workspaceContext.worktree_path}\n- workspace_role: ${workspaceContext.workspace_role}`,
220
- },
217
+ output.args = {
218
+ ...args,
219
+ prompt: `${args.prompt}\n\nWorkspace binding:\n- task_id: ${workspaceContext.task_id}\n- worktree_path: ${workspaceContext.worktree_path}\n- workspace_role: ${workspaceContext.workspace_role}`,
221
220
  };
221
+ return;
222
222
  }
223
223
 
224
224
  const nextArgs = { ...args };
@@ -239,15 +239,15 @@ export const WorktreeWorkflowPlugin = async ({ $, directory }) => {
239
239
  }
240
240
  }
241
241
 
242
- return { ...input, args: nextArgs };
242
+ output.args = nextArgs;
243
243
  }
244
244
 
245
- async function onToolExecuteAfter(input) {
246
- const toolName = input?.tool?.name ?? input?.toolName;
245
+ async function onToolExecuteAfter(input, output) {
246
+ const toolName = input?.tool;
247
247
  const args = input?.args || {};
248
- const sessionID = input?.sessionID ?? input?.context?.sessionID;
248
+ const sessionID = input?.sessionID;
249
249
  if (toolName === "worktree_prepare" && sessionID) {
250
- const result = input?.metadata?.result ?? input?.result;
250
+ const result = output?.metadata?.result;
251
251
  if (result?.branch && result?.worktree_path) {
252
252
  const repoRoot = await service.getRepoRoot();
253
253
  await service.updateStateForPrepare(repoRoot, sessionID, result, "manual");
@@ -273,21 +273,14 @@ export const WorktreeWorkflowPlugin = async ({ $, directory }) => {
273
273
  });
274
274
  if (persisted && lifecycle.signal === "complete") {
275
275
  try {
276
- const advisory = await service.buildCleanupAdvisoryPreview({ repoRoot, activeWorktree: input?.context?.worktree ?? input?.worktree ?? directory });
277
- const parts = Array.isArray(input?.output?.parts) ? [...input.output.parts] : [];
278
- parts.push({ type: "text", text: advisory.message });
276
+ const advisory = await service.buildCleanupAdvisoryPreview({ repoRoot, activeWorktree: directory });
279
277
  await service.recordToolUsage({ sessionID });
280
- return {
281
- ...input,
282
- output: {
283
- ...(input?.output && typeof input.output === "object" ? input.output : {}),
284
- parts,
285
- },
286
- metadata: {
287
- ...(input?.metadata && typeof input.metadata === "object" ? input.metadata : {}),
288
- advisory_cleanup_preview: advisory,
289
- },
278
+ output.output = output.output ? `${output.output}\n\n${advisory.message}` : advisory.message;
279
+ output.metadata = {
280
+ ...(output?.metadata && typeof output.metadata === "object" ? output.metadata : {}),
281
+ advisory_cleanup_preview: advisory,
290
282
  };
283
+ return;
291
284
  } catch {
292
285
  // Advisory preview is non-fatal.
293
286
  }
@@ -299,53 +292,39 @@ export const WorktreeWorkflowPlugin = async ({ $, directory }) => {
299
292
  }
300
293
  }
301
294
  if (sessionID) await service.recordToolUsage({ sessionID });
302
- return input;
303
295
  }
304
296
 
305
- async function onCommandExecuteBefore(input) {
306
- const commandName = input?.command?.name ?? input?.name;
307
- const normalizedName = typeof commandName === "string" ? commandName.replace(/^\//, "") : "";
308
- if (normalizedName !== "wt-new" && normalizedName !== "wt-clean") return input;
297
+ async function onCommandExecuteBefore(input, output) {
298
+ const normalizedName = typeof input?.command === "string" ? input.command.replace(/^\//, "") : "";
299
+ if (normalizedName !== "wt-new" && normalizedName !== "wt-clean") return;
309
300
 
310
- const argsText = typeof input?.arguments === "string" ? input.arguments : typeof input?.args === "string" ? input.args : "";
301
+ const argsText = typeof input?.arguments === "string" ? input.arguments : "";
311
302
  const parts = normalizedName === "wt-new" ? buildWtNewCommandPromptParts(argsText) : buildWtCleanCommandPromptParts(argsText);
312
-
313
- return {
314
- ...input,
315
- output: {
316
- ...(input?.output && typeof input.output === "object" ? input.output : {}),
317
- parts,
318
- },
319
- };
303
+ output.parts = parts;
320
304
  }
321
305
 
322
- async function onExperimentalChatSystemTransform(input) {
323
- const sessionID = input?.sessionID ?? input?.context?.sessionID;
324
- const existingSystem = typeof input?.system === "string" ? input.system : "";
325
- if (!sessionID || existingSystem.includes(WORKSPACE_SYSTEM_CONTEXT_MARKER)) return input;
306
+ async function onExperimentalChatSystemTransform(input, output) {
307
+ const sessionID = input?.sessionID;
308
+ const existingSystem = Array.isArray(output?.system) ? output.system : [];
309
+ if (!sessionID || existingSystem.some((entry) => entry.includes(WORKSPACE_SYSTEM_CONTEXT_MARKER))) return;
326
310
 
327
311
  const repoRoot = await service.getRepoRoot();
328
312
  const { activeTask } = await service.getSessionBinding({ repoRoot, sessionID });
329
- if (!activeTask?.worktree_path) return input;
313
+ if (!activeTask?.worktree_path) return;
330
314
 
331
315
  const workspaceContext = buildWorkspaceContext({
332
316
  task: activeTask,
333
317
  workspaceRole: activeTask.workspace_role,
334
318
  });
335
319
  const injected = formatWorkspaceSystemContext(workspaceContext);
336
- return {
337
- ...input,
338
- system: existingSystem ? `${existingSystem}\n\n${injected}` : injected,
339
- };
320
+ output.system = [...existingSystem, injected];
340
321
  }
341
322
 
342
323
  return {
343
- hooks: {
344
- "command.execute.before": onCommandExecuteBefore,
345
- "experimental.chat.system.transform": onExperimentalChatSystemTransform,
346
- "tool.execute.before": onToolExecuteBefore,
347
- "tool.execute.after": onToolExecuteAfter,
348
- },
324
+ "command.execute.before": onCommandExecuteBefore,
325
+ "experimental.chat.system.transform": onExperimentalChatSystemTransform,
326
+ "tool.execute.before": onToolExecuteBefore,
327
+ "tool.execute.after": onToolExecuteAfter,
349
328
  tool: {
350
329
  worktree_prepare: tool({
351
330
  description: "Create a synced git worktree from a descriptive title",
@@ -381,4 +360,9 @@ export const WorktreeWorkflowPlugin = async ({ $, directory }) => {
381
360
  };
382
361
  };
383
362
 
384
- export default WorktreeWorkflowPlugin;
363
+ const plugin = {
364
+ id: pluginID,
365
+ server: WorktreeWorkflowPlugin,
366
+ };
367
+
368
+ export default plugin;