@ship-cli/opencode 0.1.1 → 0.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.
Files changed (2) hide show
  1. package/package.json +3 -3
  2. package/src/plugin.ts +69 -23
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ship-cli/opencode",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin for Ship - Linear task management integration",
6
6
  "license": "MIT",
@@ -42,10 +42,10 @@
42
42
  "dependencies": {
43
43
  "@opencode-ai/plugin": "^1.0.210",
44
44
  "@opencode-ai/sdk": "^1.0.210",
45
- "effect": "^3.19.13"
45
+ "effect": "^3.19.14"
46
46
  },
47
47
  "devDependencies": {
48
- "@effect/language-service": "^0.63.2",
48
+ "@effect/language-service": "^0.64.1",
49
49
  "@effect/vitest": "^0.27.0",
50
50
  "@types/bun": "latest",
51
51
  "@types/node": "^25.0.3",
package/src/plugin.ts CHANGED
@@ -375,7 +375,8 @@ const ShellService = Context.GenericTag<ShellService>("ShellService");
375
375
  const makeShellService = (_$: BunShell, defaultCwd?: string): ShellService => {
376
376
  const getCommand = (): string[] => {
377
377
  if (process.env.NODE_ENV === "development") {
378
- return ["pnpm", "ship"];
378
+ // Use node with tsx loader directly to avoid pnpm re-escaping arguments with newlines
379
+ return ["node", "--import=tsx", "packages/cli/src/bin.ts"];
379
380
  }
380
381
  return ["ship"];
381
382
  };
@@ -2210,44 +2211,72 @@ const executeAction = (
2210
2211
  // =============================================================================
2211
2212
 
2212
2213
  /**
2213
- * Create the ship tool with the opencode context.
2214
- *
2215
- * @param $ - Bun shell from opencode
2216
- * @param directory - Current working directory from opencode (Instance.directory)
2217
- * @returns ToolDefinition for the ship tool
2214
+ * Build the tool description based on whether the project is a jj repo.
2215
+ * Only includes jj-specific hints when the project uses jj for VCS.
2218
2216
  */
2219
- const createShipTool = ($: BunShell, directory: string, serverUrl?: string): ToolDefinition => {
2220
- const shellService = makeShellService($, directory);
2221
- const ShellServiceLive = Layer.succeed(ShellService, shellService);
2222
- const ShipServiceLive = Layer.effect(ShipService, makeShipService).pipe(
2223
- Layer.provide(ShellServiceLive),
2224
- );
2217
+ const buildToolDescription = (isJjRepo: boolean): string => {
2218
+ const baseDescription = `Linear task management and VCS operations for the current project.`;
2225
2219
 
2226
- const runEffect = <A, E>(effect: Effect.Effect<A, E, ShipService>): Promise<A> =>
2227
- Effect.runPromise(Effect.provide(effect, ShipServiceLive));
2228
-
2229
- return createTool({
2230
- description: `Linear task management and VCS operations for the current project.
2220
+ const jjHints = isJjRepo
2221
+ ? `
2231
2222
 
2232
2223
  IMPORTANT: Always use this tool for VCS operations. NEVER run jj, gh, or git commands directly via bash.
2233
2224
  - Use stack-create instead of: jj new, jj describe, jj bookmark create
2234
2225
  - Use stack-describe instead of: jj describe
2235
2226
  - Use stack-submit instead of: jj git push, gh pr create
2236
- - Use stack-sync instead of: jj git fetch, jj rebase
2227
+ - Use stack-sync instead of: jj git fetch, jj rebase`
2228
+ : "";
2237
2229
 
2230
+ const taskFeatures = `
2238
2231
  Use this tool to:
2239
2232
  - List tasks ready to work on (no blockers)
2240
2233
  - View task details
2241
2234
  - Start/complete tasks
2242
2235
  - Create new tasks
2243
2236
  - Manage task dependencies (blocking relationships)
2244
- - Get AI-optimized context about current work
2237
+ - Get AI-optimized context about current work`;
2238
+
2239
+ const jjFeatures = isJjRepo
2240
+ ? `
2245
2241
  - Manage stacked changes (jj workflow)
2246
2242
  - Start/stop GitHub webhook forwarding for real-time event notifications
2247
- - Subscribe to PR events via the webhook daemon (multi-session support)
2243
+ - Subscribe to PR events via the webhook daemon (multi-session support)`
2244
+ : "";
2245
+
2246
+ const footer = `
2248
2247
 
2249
2248
  Requires ship to be configured in the project (.ship/config.yaml).
2250
- Run 'ship init' in the terminal first if not configured.`,
2249
+ Run 'ship init' in the terminal first if not configured.`;
2250
+
2251
+ return baseDescription + jjHints + taskFeatures + jjFeatures + footer;
2252
+ };
2253
+
2254
+ /**
2255
+ * Create the ship tool with the opencode context.
2256
+ *
2257
+ * @param $ - Bun shell from opencode
2258
+ * @param directory - Current working directory from opencode (Instance.directory)
2259
+ * @param serverUrl - OpenCode server URL for webhook routing
2260
+ * @param isJjRepo - Whether the directory is a jj repository
2261
+ * @returns ToolDefinition for the ship tool
2262
+ */
2263
+ const createShipTool = (
2264
+ $: BunShell,
2265
+ directory: string,
2266
+ serverUrl?: string,
2267
+ isJjRepo?: boolean,
2268
+ ): ToolDefinition => {
2269
+ const shellService = makeShellService($, directory);
2270
+ const ShellServiceLive = Layer.succeed(ShellService, shellService);
2271
+ const ShipServiceLive = Layer.effect(ShipService, makeShipService).pipe(
2272
+ Layer.provide(ShellServiceLive),
2273
+ );
2274
+
2275
+ const runEffect = <A, E>(effect: Effect.Effect<A, E, ShipService>): Promise<A> =>
2276
+ Effect.runPromise(Effect.provide(effect, ShipServiceLive));
2277
+
2278
+ return createTool({
2279
+ description: buildToolDescription(isJjRepo ?? false),
2251
2280
 
2252
2281
  args: {
2253
2282
  action: createTool.schema
@@ -2312,7 +2341,7 @@ Run 'ship init' in the terminal first if not configured.`,
2312
2341
  .string()
2313
2342
  .optional()
2314
2343
  .describe(
2315
- "Description - for task create/update OR for stack-describe (commit body after title)",
2344
+ "For task create: REQUIRED - use template from skill (## Summary, ## Acceptance Criteria with checkboxes, ## Notes). For task update: optional changes. For stack-describe: commit body after title.",
2316
2345
  ),
2317
2346
  priority: createTool.schema
2318
2347
  .enum(["urgent", "high", "medium", "low", "none"])
@@ -2662,18 +2691,35 @@ type ExtendedPluginInput = Parameters<Plugin>[0] & {
2662
2691
  serverUrl?: URL;
2663
2692
  };
2664
2693
 
2694
+ /**
2695
+ * Check if a directory is a jj repository by looking for the .jj directory.
2696
+ * This is a simple filesystem check that doesn't require jj to be installed.
2697
+ */
2698
+ const checkIsJjRepo = async (directory: string): Promise<boolean> => {
2699
+ try {
2700
+ const jjPath = `${directory}/.jj`;
2701
+ const file = Bun.file(jjPath);
2702
+ // Bun.file().exists() returns true for directories too
2703
+ return await file.exists();
2704
+ } catch {
2705
+ return false;
2706
+ }
2707
+ };
2708
+
2665
2709
  export const ShipPlugin = async (input: ExtendedPluginInput) => {
2666
2710
  const { $, directory, serverUrl } = input;
2667
2711
  const shellService = makeShellService($, directory);
2668
2712
  // Convert URL object to string for passing to CLI commands
2669
2713
  const serverUrlString = serverUrl?.toString();
2714
+ // Check if this is a jj repository
2715
+ const isJjRepo = await checkIsJjRepo(directory);
2670
2716
 
2671
2717
  return {
2672
2718
  config: async (config: Parameters<NonNullable<Awaited<ReturnType<Plugin>>["config"]>>[0]) => {
2673
2719
  config.command = { ...config.command, ...SHIP_COMMANDS };
2674
2720
  },
2675
2721
  tool: {
2676
- ship: createShipTool($, directory, serverUrlString),
2722
+ ship: createShipTool($, directory, serverUrlString, isJjRepo),
2677
2723
  },
2678
2724
  "tool.execute.after": createToolExecuteAfterHook(),
2679
2725
  "experimental.session.compacting": createCompactionHook(shellService),