opencode-swarm-plugin 0.4.0 → 0.6.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.
@@ -16,6 +16,8 @@ import {
16
16
  swarm_complete,
17
17
  swarm_subtask_prompt,
18
18
  swarm_evaluation_prompt,
19
+ swarm_select_strategy,
20
+ swarm_plan_prompt,
19
21
  formatSubtaskPromptV2,
20
22
  SUBTASK_PROMPT_V2,
21
23
  } from "./swarm";
@@ -119,6 +121,288 @@ describe("swarm_decompose", () => {
119
121
  });
120
122
  });
121
123
 
124
+ // ============================================================================
125
+ // Strategy Selection Tests
126
+ // ============================================================================
127
+
128
+ describe("swarm_select_strategy", () => {
129
+ it("selects feature-based for 'add' tasks", async () => {
130
+ const result = await swarm_select_strategy.execute(
131
+ {
132
+ task: "Add user authentication with OAuth",
133
+ },
134
+ mockContext,
135
+ );
136
+ const parsed = JSON.parse(result);
137
+
138
+ expect(parsed.strategy).toBe("feature-based");
139
+ expect(parsed.confidence).toBeGreaterThan(0.5);
140
+ expect(parsed.reasoning).toContain("add");
141
+ expect(parsed.guidelines).toBeInstanceOf(Array);
142
+ expect(parsed.anti_patterns).toBeInstanceOf(Array);
143
+ });
144
+
145
+ it("selects file-based for 'refactor' tasks", async () => {
146
+ const result = await swarm_select_strategy.execute(
147
+ {
148
+ task: "Refactor all components to use new API",
149
+ },
150
+ mockContext,
151
+ );
152
+ const parsed = JSON.parse(result);
153
+
154
+ expect(parsed.strategy).toBe("file-based");
155
+ expect(parsed.confidence).toBeGreaterThanOrEqual(0.5);
156
+ expect(parsed.reasoning).toContain("refactor");
157
+ });
158
+
159
+ it("selects risk-based for 'fix security' tasks", async () => {
160
+ const result = await swarm_select_strategy.execute(
161
+ {
162
+ task: "Fix security vulnerability in authentication",
163
+ },
164
+ mockContext,
165
+ );
166
+ const parsed = JSON.parse(result);
167
+
168
+ expect(parsed.strategy).toBe("risk-based");
169
+ expect(parsed.confidence).toBeGreaterThan(0.5);
170
+ // Should match either 'fix' or 'security'
171
+ expect(
172
+ parsed.reasoning.includes("fix") || parsed.reasoning.includes("security"),
173
+ ).toBe(true);
174
+ });
175
+
176
+ it("defaults to feature-based when no keywords match", async () => {
177
+ const result = await swarm_select_strategy.execute(
178
+ {
179
+ task: "Something completely unrelated without keywords",
180
+ },
181
+ mockContext,
182
+ );
183
+ const parsed = JSON.parse(result);
184
+
185
+ expect(parsed.strategy).toBe("feature-based");
186
+ // Confidence should be lower without keyword matches
187
+ expect(parsed.confidence).toBeLessThanOrEqual(0.6);
188
+ expect(parsed.reasoning).toContain("Defaulting to feature-based");
189
+ });
190
+
191
+ it("includes confidence score and reasoning", async () => {
192
+ const result = await swarm_select_strategy.execute(
193
+ {
194
+ task: "Implement new dashboard feature",
195
+ },
196
+ mockContext,
197
+ );
198
+ const parsed = JSON.parse(result);
199
+
200
+ expect(parsed).toHaveProperty("strategy");
201
+ expect(parsed).toHaveProperty("confidence");
202
+ expect(parsed).toHaveProperty("reasoning");
203
+ expect(parsed).toHaveProperty("description");
204
+ expect(typeof parsed.confidence).toBe("number");
205
+ expect(parsed.confidence).toBeGreaterThanOrEqual(0);
206
+ expect(parsed.confidence).toBeLessThanOrEqual(1);
207
+ expect(typeof parsed.reasoning).toBe("string");
208
+ expect(parsed.reasoning.length).toBeGreaterThan(0);
209
+ });
210
+
211
+ it("includes alternative strategies with scores", async () => {
212
+ const result = await swarm_select_strategy.execute(
213
+ {
214
+ task: "Build new payment processing module",
215
+ },
216
+ mockContext,
217
+ );
218
+ const parsed = JSON.parse(result);
219
+
220
+ expect(parsed).toHaveProperty("alternatives");
221
+ expect(parsed.alternatives).toBeInstanceOf(Array);
222
+ expect(parsed.alternatives.length).toBe(2); // 3 strategies - 1 selected = 2 alternatives
223
+
224
+ for (const alt of parsed.alternatives) {
225
+ expect(alt).toHaveProperty("strategy");
226
+ expect(alt).toHaveProperty("description");
227
+ expect(alt).toHaveProperty("score");
228
+ expect(["file-based", "feature-based", "risk-based"]).toContain(
229
+ alt.strategy,
230
+ );
231
+ expect(typeof alt.score).toBe("number");
232
+ }
233
+ });
234
+
235
+ it("includes codebase context in reasoning when provided", async () => {
236
+ const result = await swarm_select_strategy.execute(
237
+ {
238
+ task: "Add new API endpoint",
239
+ codebase_context: "Using Express.js with TypeScript and PostgreSQL",
240
+ },
241
+ mockContext,
242
+ );
243
+ const parsed = JSON.parse(result);
244
+
245
+ expect(parsed.reasoning).toContain("Express.js");
246
+ });
247
+ });
248
+
249
+ // ============================================================================
250
+ // Planning Prompt Tests
251
+ // ============================================================================
252
+
253
+ describe("swarm_plan_prompt", () => {
254
+ it("auto-selects strategy when not specified", async () => {
255
+ const result = await swarm_plan_prompt.execute(
256
+ {
257
+ task: "Add user settings page",
258
+ max_subtasks: 3,
259
+ query_cass: false, // Disable CASS to isolate test
260
+ },
261
+ mockContext,
262
+ );
263
+ const parsed = JSON.parse(result);
264
+
265
+ expect(parsed).toHaveProperty("prompt");
266
+ expect(parsed).toHaveProperty("strategy");
267
+ expect(parsed.strategy).toHaveProperty("selected");
268
+ expect(parsed.strategy).toHaveProperty("reasoning");
269
+ expect(parsed.strategy.selected).toBe("feature-based"); // 'add' keyword
270
+ });
271
+
272
+ it("uses explicit strategy when provided", async () => {
273
+ const result = await swarm_plan_prompt.execute(
274
+ {
275
+ task: "Do something",
276
+ strategy: "risk-based",
277
+ max_subtasks: 3,
278
+ query_cass: false,
279
+ },
280
+ mockContext,
281
+ );
282
+ const parsed = JSON.parse(result);
283
+
284
+ expect(parsed.strategy.selected).toBe("risk-based");
285
+ expect(parsed.strategy.reasoning).toContain("User-specified strategy");
286
+ });
287
+
288
+ it("includes strategy guidelines in prompt", async () => {
289
+ const result = await swarm_plan_prompt.execute(
290
+ {
291
+ task: "Refactor the codebase",
292
+ max_subtasks: 4,
293
+ query_cass: false,
294
+ },
295
+ mockContext,
296
+ );
297
+ const parsed = JSON.parse(result);
298
+
299
+ // Prompt should contain strategy-specific guidelines
300
+ expect(parsed.prompt).toContain("## Strategy:");
301
+ expect(parsed.prompt).toContain("### Guidelines");
302
+ expect(parsed.prompt).toContain("### Anti-Patterns");
303
+ expect(parsed.prompt).toContain("### Examples");
304
+ });
305
+
306
+ it("includes anti-patterns in output", async () => {
307
+ const result = await swarm_plan_prompt.execute(
308
+ {
309
+ task: "Build new feature",
310
+ max_subtasks: 3,
311
+ query_cass: false,
312
+ },
313
+ mockContext,
314
+ );
315
+ const parsed = JSON.parse(result);
316
+
317
+ expect(parsed.strategy).toHaveProperty("anti_patterns");
318
+ expect(parsed.strategy.anti_patterns).toBeInstanceOf(Array);
319
+ expect(parsed.strategy.anti_patterns.length).toBeGreaterThan(0);
320
+ });
321
+
322
+ it("returns expected_schema and validation_note", async () => {
323
+ const result = await swarm_plan_prompt.execute(
324
+ {
325
+ task: "Some task",
326
+ max_subtasks: 5,
327
+ query_cass: false,
328
+ },
329
+ mockContext,
330
+ );
331
+ const parsed = JSON.parse(result);
332
+
333
+ expect(parsed).toHaveProperty("expected_schema", "BeadTree");
334
+ expect(parsed).toHaveProperty("validation_note");
335
+ expect(parsed.validation_note).toContain("swarm_validate_decomposition");
336
+ expect(parsed).toHaveProperty("schema_hint");
337
+ expect(parsed.schema_hint).toHaveProperty("epic");
338
+ expect(parsed.schema_hint).toHaveProperty("subtasks");
339
+ });
340
+
341
+ it("reports CASS status in output (queried flag)", async () => {
342
+ // Test with CASS disabled
343
+ const resultDisabled = await swarm_plan_prompt.execute(
344
+ {
345
+ task: "Add feature",
346
+ max_subtasks: 3,
347
+ query_cass: false,
348
+ },
349
+ mockContext,
350
+ );
351
+ const parsedDisabled = JSON.parse(resultDisabled);
352
+
353
+ expect(parsedDisabled).toHaveProperty("cass_history");
354
+ expect(parsedDisabled.cass_history.queried).toBe(false);
355
+
356
+ // Test with CASS enabled (may or may not be available)
357
+ const resultEnabled = await swarm_plan_prompt.execute(
358
+ {
359
+ task: "Add feature",
360
+ max_subtasks: 3,
361
+ query_cass: true,
362
+ },
363
+ mockContext,
364
+ );
365
+ const parsedEnabled = JSON.parse(resultEnabled);
366
+
367
+ expect(parsedEnabled).toHaveProperty("cass_history");
368
+ expect(parsedEnabled.cass_history).toHaveProperty("queried");
369
+ // If CASS is unavailable, queried will be false with reason
370
+ if (!parsedEnabled.cass_history.queried) {
371
+ expect(parsedEnabled.cass_history).toHaveProperty("reason");
372
+ }
373
+ });
374
+
375
+ it("includes context in prompt when provided", async () => {
376
+ const result = await swarm_plan_prompt.execute(
377
+ {
378
+ task: "Add user profile",
379
+ max_subtasks: 3,
380
+ context: "We use Next.js App Router with server components",
381
+ query_cass: false,
382
+ },
383
+ mockContext,
384
+ );
385
+ const parsed = JSON.parse(result);
386
+
387
+ expect(parsed.prompt).toContain("Next.js App Router");
388
+ expect(parsed.prompt).toContain("server components");
389
+ });
390
+
391
+ it("includes max_subtasks in prompt", async () => {
392
+ const result = await swarm_plan_prompt.execute(
393
+ {
394
+ task: "Build something",
395
+ max_subtasks: 7,
396
+ query_cass: false,
397
+ },
398
+ mockContext,
399
+ );
400
+ const parsed = JSON.parse(result);
401
+
402
+ expect(parsed.prompt).toContain("2-7 independent subtasks");
403
+ });
404
+ });
405
+
122
406
  describe("swarm_validate_decomposition", () => {
123
407
  it("validates correct BeadTree", async () => {
124
408
  const validBeadTree = JSON.stringify({
@@ -959,10 +1243,12 @@ describe("Graceful Degradation", () => {
959
1243
  // Coordinator-Centric Swarm Tools (V2)
960
1244
  // ============================================================================
961
1245
 
962
- describe("Coordinator-Centric Swarm Tools", () => {
1246
+ describe("Swarm Prompt V2 (with Agent Mail/Beads)", () => {
963
1247
  describe("formatSubtaskPromptV2", () => {
964
1248
  it("generates correct prompt with all fields", () => {
965
1249
  const result = formatSubtaskPromptV2({
1250
+ bead_id: "bd-123.1",
1251
+ epic_id: "bd-123",
966
1252
  subtask_title: "Add OAuth provider",
967
1253
  subtask_description: "Configure Google OAuth in the auth config",
968
1254
  files: ["src/auth/google.ts", "src/auth/config.ts"],
@@ -982,16 +1268,15 @@ describe("Coordinator-Centric Swarm Tools", () => {
982
1268
  // Check shared context is included
983
1269
  expect(result).toContain("We are using NextAuth.js v5");
984
1270
 
985
- // Check expected sections exist
986
- expect(result).toContain("## Your Task");
987
- expect(result).toContain("## Files to Modify");
988
- expect(result).toContain("## Context");
989
- expect(result).toContain("## Instructions");
990
- expect(result).toContain("## When Complete");
1271
+ // Check bead/epic IDs are substituted
1272
+ expect(result).toContain("bd-123.1");
1273
+ expect(result).toContain("bd-123");
991
1274
  });
992
1275
 
993
1276
  it("handles missing optional fields", () => {
994
1277
  const result = formatSubtaskPromptV2({
1278
+ bead_id: "bd-456.1",
1279
+ epic_id: "bd-456",
995
1280
  subtask_title: "Simple task",
996
1281
  subtask_description: "",
997
1282
  files: [],
@@ -1004,16 +1289,16 @@ describe("Coordinator-Centric Swarm Tools", () => {
1004
1289
  expect(result).toContain("(see title)");
1005
1290
 
1006
1291
  // Check fallback for empty files
1007
- expect(result).toContain(
1008
- "(no specific files assigned - use your judgment)",
1009
- );
1292
+ expect(result).toContain("(no specific files - use judgment)");
1010
1293
 
1011
1294
  // Check fallback for missing context
1012
- expect(result).toContain("(none provided)");
1295
+ expect(result).toContain("(none)");
1013
1296
  });
1014
1297
 
1015
1298
  it("handles files with special characters", () => {
1016
1299
  const result = formatSubtaskPromptV2({
1300
+ bead_id: "bd-789.1",
1301
+ epic_id: "bd-789",
1017
1302
  subtask_title: "Handle paths",
1018
1303
  subtask_description: "Test file paths",
1019
1304
  files: [
@@ -1030,51 +1315,39 @@ describe("Coordinator-Centric Swarm Tools", () => {
1030
1315
  describe("SUBTASK_PROMPT_V2", () => {
1031
1316
  it("contains expected sections", () => {
1032
1317
  // Check all main sections are present in the template
1033
- expect(SUBTASK_PROMPT_V2).toContain("## Your Task");
1318
+ expect(SUBTASK_PROMPT_V2).toContain("## Task");
1034
1319
  expect(SUBTASK_PROMPT_V2).toContain("{subtask_title}");
1035
1320
  expect(SUBTASK_PROMPT_V2).toContain("{subtask_description}");
1036
1321
 
1037
- expect(SUBTASK_PROMPT_V2).toContain("## Files to Modify");
1322
+ expect(SUBTASK_PROMPT_V2).toContain("## Files");
1038
1323
  expect(SUBTASK_PROMPT_V2).toContain("{file_list}");
1039
1324
 
1040
1325
  expect(SUBTASK_PROMPT_V2).toContain("## Context");
1041
1326
  expect(SUBTASK_PROMPT_V2).toContain("{shared_context}");
1042
1327
 
1043
- expect(SUBTASK_PROMPT_V2).toContain("## Instructions");
1044
- expect(SUBTASK_PROMPT_V2).toContain("Read first");
1045
- expect(SUBTASK_PROMPT_V2).toContain("Plan your approach");
1046
- expect(SUBTASK_PROMPT_V2).toContain("Make the changes");
1047
- expect(SUBTASK_PROMPT_V2).toContain("Verify");
1048
-
1049
- expect(SUBTASK_PROMPT_V2).toContain("## When Complete");
1050
- expect(SUBTASK_PROMPT_V2).toContain('"success"');
1051
- expect(SUBTASK_PROMPT_V2).toContain('"summary"');
1052
- expect(SUBTASK_PROMPT_V2).toContain('"files_modified"');
1328
+ expect(SUBTASK_PROMPT_V2).toContain("## Workflow");
1053
1329
  });
1054
1330
 
1055
- it("does NOT contain Agent Mail instructions", () => {
1056
- // V2 prompt is for coordinator-centric model where subagents don't use Agent Mail
1057
- expect(SUBTASK_PROMPT_V2).not.toContain("Agent Mail");
1058
- expect(SUBTASK_PROMPT_V2).not.toContain("agentmail_");
1059
- expect(SUBTASK_PROMPT_V2).not.toContain("agent_name");
1060
- expect(SUBTASK_PROMPT_V2).not.toContain("send_message");
1331
+ it("DOES contain Agent Mail instructions", () => {
1332
+ // V2 prompt tells agents to USE Agent Mail
1333
+ expect(SUBTASK_PROMPT_V2).toContain("Agent Mail");
1334
+ expect(SUBTASK_PROMPT_V2).toContain("agentmail_send");
1335
+ expect(SUBTASK_PROMPT_V2).toContain("thread_id");
1061
1336
  });
1062
1337
 
1063
- it("does NOT contain beads instructions", () => {
1064
- // V2 prompt is for coordinator-centric model where subagents don't manage beads
1065
- expect(SUBTASK_PROMPT_V2).not.toContain("bead_id");
1066
- expect(SUBTASK_PROMPT_V2).not.toContain("epic_id");
1067
- expect(SUBTASK_PROMPT_V2).not.toContain("bd update");
1068
- expect(SUBTASK_PROMPT_V2).not.toContain("bd close");
1069
- expect(SUBTASK_PROMPT_V2).not.toContain("swarm_progress");
1070
- expect(SUBTASK_PROMPT_V2).not.toContain("swarm_complete");
1338
+ it("DOES contain beads instructions", () => {
1339
+ // V2 prompt tells agents to USE beads
1340
+ expect(SUBTASK_PROMPT_V2).toContain("{bead_id}");
1341
+ expect(SUBTASK_PROMPT_V2).toContain("{epic_id}");
1342
+ expect(SUBTASK_PROMPT_V2).toContain("beads_update");
1343
+ expect(SUBTASK_PROMPT_V2).toContain("beads_create");
1344
+ expect(SUBTASK_PROMPT_V2).toContain("swarm_complete");
1071
1345
  });
1072
1346
 
1073
- it("expects structured JSON response from subagent", () => {
1074
- // The prompt should instruct agents to return structured JSON
1075
- expect(SUBTASK_PROMPT_V2).toContain("```json");
1076
- expect(SUBTASK_PROMPT_V2).toContain('"success"');
1077
- expect(SUBTASK_PROMPT_V2).toContain('"blocker"');
1347
+ it("instructs agents to communicate", () => {
1348
+ expect(SUBTASK_PROMPT_V2).toContain("Never work silently");
1349
+ expect(SUBTASK_PROMPT_V2).toContain("Report progress");
1350
+ expect(SUBTASK_PROMPT_V2).toContain("coordinator");
1078
1351
  });
1079
1352
  });
1080
1353
  });