opencode-swarm-plugin 0.5.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.
- package/.beads/issues.jsonl +443 -0
- package/README.md +76 -0
- package/examples/agents/swarm-planner.md +138 -0
- package/examples/commands/swarm.md +261 -34
- package/package.json +1 -1
- package/src/index.ts +12 -0
- package/src/learning.ts +13 -0
- package/src/swarm.integration.test.ts +284 -0
- package/src/swarm.ts +481 -3
|
@@ -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({
|