opencode-swarm-plugin 0.31.7 → 0.33.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 (62) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/.turbo/turbo-test.log +324 -316
  3. package/CHANGELOG.md +394 -0
  4. package/README.md +129 -181
  5. package/bin/swarm.test.ts +31 -0
  6. package/bin/swarm.ts +635 -140
  7. package/dist/compaction-hook.d.ts +1 -1
  8. package/dist/compaction-hook.d.ts.map +1 -1
  9. package/dist/hive.d.ts.map +1 -1
  10. package/dist/index.d.ts +17 -2
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +653 -139
  13. package/dist/memory-tools.d.ts.map +1 -1
  14. package/dist/memory.d.ts +5 -4
  15. package/dist/memory.d.ts.map +1 -1
  16. package/dist/observability-tools.d.ts +116 -0
  17. package/dist/observability-tools.d.ts.map +1 -0
  18. package/dist/plugin.js +648 -136
  19. package/dist/skills.d.ts.map +1 -1
  20. package/dist/swarm-orchestrate.d.ts +29 -5
  21. package/dist/swarm-orchestrate.d.ts.map +1 -1
  22. package/dist/swarm-prompts.d.ts +66 -0
  23. package/dist/swarm-prompts.d.ts.map +1 -1
  24. package/dist/swarm.d.ts +17 -2
  25. package/dist/swarm.d.ts.map +1 -1
  26. package/evals/lib/{data-loader.test.ts → data-loader.evalite-test.ts} +7 -6
  27. package/evals/lib/data-loader.ts +1 -1
  28. package/evals/scorers/{outcome-scorers.test.ts → outcome-scorers.evalite-test.ts} +1 -1
  29. package/examples/plugin-wrapper-template.ts +316 -12
  30. package/global-skills/swarm-coordination/SKILL.md +118 -8
  31. package/package.json +3 -2
  32. package/src/compaction-hook.ts +5 -3
  33. package/src/hive.integration.test.ts +83 -1
  34. package/src/hive.ts +37 -12
  35. package/src/index.ts +25 -1
  36. package/src/mandate-storage.integration.test.ts +601 -0
  37. package/src/memory-tools.ts +6 -4
  38. package/src/memory.integration.test.ts +117 -49
  39. package/src/memory.test.ts +41 -217
  40. package/src/memory.ts +12 -8
  41. package/src/observability-tools.test.ts +346 -0
  42. package/src/observability-tools.ts +594 -0
  43. package/src/repo-crawl.integration.test.ts +441 -0
  44. package/src/skills.integration.test.ts +1192 -0
  45. package/src/skills.test.ts +42 -1
  46. package/src/skills.ts +8 -4
  47. package/src/structured.integration.test.ts +817 -0
  48. package/src/swarm-deferred.integration.test.ts +157 -0
  49. package/src/swarm-deferred.test.ts +38 -0
  50. package/src/swarm-mail.integration.test.ts +15 -19
  51. package/src/swarm-orchestrate.integration.test.ts +282 -0
  52. package/src/swarm-orchestrate.test.ts +123 -0
  53. package/src/swarm-orchestrate.ts +279 -201
  54. package/src/swarm-prompts.test.ts +481 -0
  55. package/src/swarm-prompts.ts +297 -0
  56. package/src/swarm-research.integration.test.ts +544 -0
  57. package/src/swarm-research.test.ts +698 -0
  58. package/src/swarm-research.ts +472 -0
  59. package/src/swarm-review.integration.test.ts +290 -0
  60. package/src/swarm.integration.test.ts +23 -20
  61. package/src/swarm.ts +6 -3
  62. package/src/tool-adapter.integration.test.ts +1221 -0
@@ -8,7 +8,9 @@
8
8
  import { describe, expect, test } from "bun:test";
9
9
  import {
10
10
  formatSubtaskPromptV2,
11
+ formatResearcherPrompt,
11
12
  SUBTASK_PROMPT_V2,
13
+ RESEARCHER_PROMPT,
12
14
  } from "./swarm-prompts";
13
15
 
14
16
  describe("SUBTASK_PROMPT_V2", () => {
@@ -174,3 +176,482 @@ describe("formatSubtaskPromptV2", () => {
174
176
  expect(result).toContain("semantic-memory_find");
175
177
  });
176
178
  });
179
+
180
+ describe("swarm_spawn_subtask tool", () => {
181
+ test("returns post_completion_instructions field in JSON response", async () => {
182
+ const { swarm_spawn_subtask } = await import("./swarm-prompts");
183
+
184
+ const result = await swarm_spawn_subtask.execute({
185
+ bead_id: "test-project-abc123-task1",
186
+ epic_id: "test-project-abc123-epic1",
187
+ subtask_title: "Implement feature X",
188
+ subtask_description: "Add feature X to the system",
189
+ files: ["src/feature.ts", "src/feature.test.ts"],
190
+ shared_context: "Epic context here",
191
+ project_path: "/Users/joel/Code/project",
192
+ });
193
+
194
+ const parsed = JSON.parse(result);
195
+ expect(parsed).toHaveProperty("post_completion_instructions");
196
+ expect(typeof parsed.post_completion_instructions).toBe("string");
197
+ });
198
+
199
+ test("post_completion_instructions contains mandatory review steps", async () => {
200
+ const { swarm_spawn_subtask } = await import("./swarm-prompts");
201
+
202
+ const result = await swarm_spawn_subtask.execute({
203
+ bead_id: "test-project-abc123-task1",
204
+ epic_id: "test-project-abc123-epic1",
205
+ subtask_title: "Implement feature X",
206
+ files: ["src/feature.ts"],
207
+ project_path: "/Users/joel/Code/project",
208
+ });
209
+
210
+ const parsed = JSON.parse(result);
211
+ const instructions = parsed.post_completion_instructions;
212
+
213
+ // Should contain all 5 steps
214
+ expect(instructions).toContain("Step 1: Check Swarm Mail");
215
+ expect(instructions).toContain("swarmmail_inbox()");
216
+ expect(instructions).toContain("Step 2: Review the Work");
217
+ expect(instructions).toContain("swarm_review");
218
+ expect(instructions).toContain("Step 3: Evaluate Against Criteria");
219
+ expect(instructions).toContain("Step 4: Send Feedback");
220
+ expect(instructions).toContain("swarm_review_feedback");
221
+ expect(instructions).toContain("Step 5: ONLY THEN Continue");
222
+ });
223
+
224
+ test("post_completion_instructions substitutes placeholders", async () => {
225
+ const { swarm_spawn_subtask } = await import("./swarm-prompts");
226
+
227
+ const result = await swarm_spawn_subtask.execute({
228
+ bead_id: "test-project-abc123-task1",
229
+ epic_id: "test-project-abc123-epic1",
230
+ subtask_title: "Implement feature X",
231
+ files: ["src/feature.ts", "src/feature.test.ts"],
232
+ project_path: "/Users/joel/Code/project",
233
+ });
234
+
235
+ const parsed = JSON.parse(result);
236
+ const instructions = parsed.post_completion_instructions;
237
+
238
+ // Placeholders should be replaced
239
+ expect(instructions).toContain("/Users/joel/Code/project");
240
+ expect(instructions).toContain("test-project-abc123-epic1");
241
+ expect(instructions).toContain("test-project-abc123-task1");
242
+ expect(instructions).toContain('"src/feature.ts"');
243
+ expect(instructions).toContain('"src/feature.test.ts"');
244
+
245
+ // Placeholders should NOT remain
246
+ expect(instructions).not.toContain("{project_key}");
247
+ expect(instructions).not.toContain("{epic_id}");
248
+ expect(instructions).not.toContain("{task_id}");
249
+ expect(instructions).not.toContain("{files_touched}");
250
+ });
251
+
252
+ test("post_completion_instructions emphasizes mandatory nature", async () => {
253
+ const { swarm_spawn_subtask } = await import("./swarm-prompts");
254
+
255
+ const result = await swarm_spawn_subtask.execute({
256
+ bead_id: "test-project-abc123-task1",
257
+ epic_id: "test-project-abc123-epic1",
258
+ subtask_title: "Implement feature X",
259
+ files: ["src/feature.ts"],
260
+ project_path: "/Users/joel/Code/project",
261
+ });
262
+
263
+ const parsed = JSON.parse(result);
264
+ const instructions = parsed.post_completion_instructions;
265
+
266
+ // Should have strong language
267
+ expect(instructions).toMatch(/⚠️|MANDATORY|NON-NEGOTIABLE|DO NOT skip/i);
268
+ expect(instructions).toContain("DO THIS IMMEDIATELY");
269
+ });
270
+ });
271
+
272
+ describe("RESEARCHER_PROMPT", () => {
273
+ describe("required sections", () => {
274
+ test("includes IDENTITY section with research_id and epic_id", () => {
275
+ expect(RESEARCHER_PROMPT).toContain("## [IDENTITY]");
276
+ expect(RESEARCHER_PROMPT).toContain("{research_id}");
277
+ expect(RESEARCHER_PROMPT).toContain("{epic_id}");
278
+ });
279
+
280
+ test("includes MISSION section explaining the role", () => {
281
+ expect(RESEARCHER_PROMPT).toContain("## [MISSION]");
282
+ expect(RESEARCHER_PROMPT).toMatch(/gather.*documentation/i);
283
+ });
284
+
285
+ test("includes WORKFLOW section with numbered steps", () => {
286
+ expect(RESEARCHER_PROMPT).toContain("## [WORKFLOW]");
287
+ expect(RESEARCHER_PROMPT).toContain("### Step 1:");
288
+ expect(RESEARCHER_PROMPT).toContain("### Step 2:");
289
+ });
290
+
291
+ test("includes CRITICAL REQUIREMENTS section", () => {
292
+ expect(RESEARCHER_PROMPT).toContain("## [CRITICAL REQUIREMENTS]");
293
+ expect(RESEARCHER_PROMPT).toMatch(/NON-NEGOTIABLE/i);
294
+ });
295
+ });
296
+
297
+ describe("workflow steps", () => {
298
+ test("Step 1 is swarmmail_init (MANDATORY FIRST)", () => {
299
+ expect(RESEARCHER_PROMPT).toMatch(/### Step 1:.*Initialize/i);
300
+ expect(RESEARCHER_PROMPT).toContain("swarmmail_init");
301
+ expect(RESEARCHER_PROMPT).toContain("project_path");
302
+ });
303
+
304
+ test("Step 2 is discovering available documentation tools", () => {
305
+ const step2Match = RESEARCHER_PROMPT.match(/### Step 2:[\s\S]*?### Step 3:/);
306
+ expect(step2Match).not.toBeNull();
307
+ if (!step2Match) return;
308
+
309
+ const step2Content = step2Match[0];
310
+ expect(step2Content).toMatch(/discover.*tools/i);
311
+ expect(step2Content).toContain("nextjs_docs");
312
+ expect(step2Content).toContain("context7");
313
+ expect(step2Content).toContain("fetch");
314
+ expect(step2Content).toContain("pdf-brain");
315
+ });
316
+
317
+ test("Step 3 is reading installed versions", () => {
318
+ const step3Match = RESEARCHER_PROMPT.match(/### Step 3:[\s\S]*?### Step 4:/);
319
+ expect(step3Match).not.toBeNull();
320
+ if (!step3Match) return;
321
+
322
+ const step3Content = step3Match[0];
323
+ expect(step3Content).toMatch(/read.*installed.*version/i);
324
+ expect(step3Content).toContain("package.json");
325
+ });
326
+
327
+ test("Step 4 is fetching documentation", () => {
328
+ const step4Match = RESEARCHER_PROMPT.match(/### Step 4:[\s\S]*?### Step 5:/);
329
+ expect(step4Match).not.toBeNull();
330
+ if (!step4Match) return;
331
+
332
+ const step4Content = step4Match[0];
333
+ expect(step4Content).toMatch(/fetch.*documentation/i);
334
+ expect(step4Content).toContain("INSTALLED version");
335
+ });
336
+
337
+ test("Step 5 is storing detailed findings in semantic-memory", () => {
338
+ const step5Match = RESEARCHER_PROMPT.match(/### Step 5:[\s\S]*?### Step 6:/);
339
+ expect(step5Match).not.toBeNull();
340
+ if (!step5Match) return;
341
+
342
+ const step5Content = step5Match[0];
343
+ expect(step5Content).toContain("semantic-memory_store");
344
+ expect(step5Content).toMatch(/store.*individually/i);
345
+ });
346
+
347
+ test("Step 6 is broadcasting summary to coordinator", () => {
348
+ const step6Match = RESEARCHER_PROMPT.match(/### Step 6:[\s\S]*?### Step 7:/);
349
+ expect(step6Match).not.toBeNull();
350
+ if (!step6Match) return;
351
+
352
+ const step6Content = step6Match[0];
353
+ expect(step6Content).toContain("swarmmail_send");
354
+ expect(step6Content).toContain("coordinator");
355
+ });
356
+
357
+ test("Step 7 is returning structured JSON output", () => {
358
+ const step7Match = RESEARCHER_PROMPT.match(/### Step 7:[\s\S]*?(?=## \[|$)/);
359
+ expect(step7Match).not.toBeNull();
360
+ if (!step7Match) return;
361
+
362
+ const step7Content = step7Match[0];
363
+ expect(step7Content).toContain("JSON");
364
+ expect(step7Content).toContain("technologies");
365
+ expect(step7Content).toContain("summary");
366
+ });
367
+ });
368
+
369
+ describe("coordinator-provided tech stack", () => {
370
+ test("emphasizes that coordinator provides the tech list", () => {
371
+ expect(RESEARCHER_PROMPT).toMatch(/COORDINATOR PROVIDED.*TECHNOLOGIES/i);
372
+ expect(RESEARCHER_PROMPT).toContain("{tech_stack}");
373
+ });
374
+
375
+ test("clarifies researcher does NOT discover what to research", () => {
376
+ expect(RESEARCHER_PROMPT).toMatch(/NOT discover what to research/i);
377
+ expect(RESEARCHER_PROMPT).toMatch(/DO discover.*TOOLS/i);
378
+ });
379
+ });
380
+
381
+ describe("upgrade comparison mode", () => {
382
+ test("includes placeholder for check_upgrades mode", () => {
383
+ expect(RESEARCHER_PROMPT).toContain("{check_upgrades}");
384
+ });
385
+
386
+ test("mentions comparing installed vs latest when in upgrade mode", () => {
387
+ expect(RESEARCHER_PROMPT).toMatch(/check-upgrades/i);
388
+ expect(RESEARCHER_PROMPT).toMatch(/compare|latest.*version/i);
389
+ });
390
+ });
391
+
392
+ describe("output requirements", () => {
393
+ test("specifies TWO output destinations: semantic-memory and return JSON", () => {
394
+ expect(RESEARCHER_PROMPT).toMatch(/TWO places/i);
395
+ expect(RESEARCHER_PROMPT).toContain("semantic-memory");
396
+ expect(RESEARCHER_PROMPT).toContain("Return JSON");
397
+ });
398
+
399
+ test("explains semantic-memory is for detailed findings", () => {
400
+ expect(RESEARCHER_PROMPT).toMatch(/semantic-memory.*detailed/i);
401
+ });
402
+
403
+ test("explains return JSON is for condensed summary", () => {
404
+ expect(RESEARCHER_PROMPT).toMatch(/return.*condensed.*summary/i);
405
+ });
406
+ });
407
+ });
408
+
409
+ describe("formatResearcherPrompt", () => {
410
+ test("substitutes research_id placeholder", () => {
411
+ const result = formatResearcherPrompt({
412
+ research_id: "research-abc123",
413
+ epic_id: "epic-xyz789",
414
+ tech_stack: ["Next.js", "React"],
415
+ project_path: "/path/to/project",
416
+ check_upgrades: false,
417
+ });
418
+
419
+ expect(result).toContain("research-abc123");
420
+ expect(result).not.toContain("{research_id}");
421
+ });
422
+
423
+ test("substitutes epic_id placeholder", () => {
424
+ const result = formatResearcherPrompt({
425
+ research_id: "research-abc123",
426
+ epic_id: "epic-xyz789",
427
+ tech_stack: ["Next.js"],
428
+ project_path: "/path/to/project",
429
+ check_upgrades: false,
430
+ });
431
+
432
+ expect(result).toContain("epic-xyz789");
433
+ expect(result).not.toContain("{epic_id}");
434
+ });
435
+
436
+ test("formats tech_stack as bulleted list", () => {
437
+ const result = formatResearcherPrompt({
438
+ research_id: "research-abc123",
439
+ epic_id: "epic-xyz789",
440
+ tech_stack: ["Next.js", "React", "TypeScript"],
441
+ project_path: "/path/to/project",
442
+ check_upgrades: false,
443
+ });
444
+
445
+ expect(result).toContain("- Next.js");
446
+ expect(result).toContain("- React");
447
+ expect(result).toContain("- TypeScript");
448
+ });
449
+
450
+ test("substitutes project_path placeholder", () => {
451
+ const result = formatResearcherPrompt({
452
+ research_id: "research-abc123",
453
+ epic_id: "epic-xyz789",
454
+ tech_stack: ["Next.js"],
455
+ project_path: "/Users/joel/Code/my-project",
456
+ check_upgrades: false,
457
+ });
458
+
459
+ expect(result).toContain("/Users/joel/Code/my-project");
460
+ expect(result).not.toContain("{project_path}");
461
+ });
462
+
463
+ test("includes DEFAULT MODE text when check_upgrades=false", () => {
464
+ const result = formatResearcherPrompt({
465
+ research_id: "research-abc123",
466
+ epic_id: "epic-xyz789",
467
+ tech_stack: ["Next.js"],
468
+ project_path: "/path/to/project",
469
+ check_upgrades: false,
470
+ });
471
+
472
+ expect(result).toContain("DEFAULT MODE");
473
+ expect(result).toContain("INSTALLED versions only");
474
+ });
475
+
476
+ test("includes UPGRADE COMPARISON MODE text when check_upgrades=true", () => {
477
+ const result = formatResearcherPrompt({
478
+ research_id: "research-abc123",
479
+ epic_id: "epic-xyz789",
480
+ tech_stack: ["Next.js"],
481
+ project_path: "/path/to/project",
482
+ check_upgrades: true,
483
+ });
484
+
485
+ expect(result).toContain("UPGRADE COMPARISON MODE");
486
+ expect(result).toContain("BOTH installed AND latest");
487
+ expect(result).toContain("breaking changes");
488
+ });
489
+ });
490
+
491
+ describe("on-demand research section", () => {
492
+ test("includes ON-DEMAND RESEARCH section after Step 9", () => {
493
+ // Find Step 9 and the section after it
494
+ const step9Pos = SUBTASK_PROMPT_V2.indexOf("### Step 9:");
495
+ const swarmMailPos = SUBTASK_PROMPT_V2.indexOf("## [SWARM MAIL COMMUNICATION]");
496
+
497
+ expect(step9Pos).toBeGreaterThan(0);
498
+ expect(swarmMailPos).toBeGreaterThan(step9Pos);
499
+
500
+ // Extract the section between Step 9 and SWARM MAIL
501
+ const betweenSection = SUBTASK_PROMPT_V2.substring(step9Pos, swarmMailPos);
502
+
503
+ expect(betweenSection).toContain("## [ON-DEMAND RESEARCH]");
504
+ });
505
+
506
+ test("research section instructs to check semantic-memory first", () => {
507
+ const researchMatch = SUBTASK_PROMPT_V2.match(/## \[ON-DEMAND RESEARCH\][\s\S]*?## \[SWARM MAIL/);
508
+ expect(researchMatch).not.toBeNull();
509
+ if (!researchMatch) return;
510
+
511
+ const researchContent = researchMatch[0];
512
+ expect(researchContent).toContain("semantic-memory_find");
513
+ expect(researchContent).toMatch(/check.*semantic-memory.*first/i);
514
+ });
515
+
516
+ test("research section includes swarm_spawn_researcher tool usage", () => {
517
+ const researchMatch = SUBTASK_PROMPT_V2.match(/## \[ON-DEMAND RESEARCH\][\s\S]*?## \[SWARM MAIL/);
518
+ expect(researchMatch).not.toBeNull();
519
+ if (!researchMatch) return;
520
+
521
+ const researchContent = researchMatch[0];
522
+ expect(researchContent).toContain("swarm_spawn_researcher");
523
+ });
524
+
525
+ test("research section lists specific research triggers", () => {
526
+ const researchMatch = SUBTASK_PROMPT_V2.match(/## \[ON-DEMAND RESEARCH\][\s\S]*?## \[SWARM MAIL/);
527
+ expect(researchMatch).not.toBeNull();
528
+ if (!researchMatch) return;
529
+
530
+ const researchContent = researchMatch[0];
531
+
532
+ // Should list when TO research
533
+ expect(researchContent).toMatch(/triggers|when to research/i);
534
+ expect(researchContent).toMatch(/API.*works|breaking changes|outdated/i);
535
+ });
536
+
537
+ test("research section lists when NOT to research", () => {
538
+ const researchMatch = SUBTASK_PROMPT_V2.match(/## \[ON-DEMAND RESEARCH\][\s\S]*?## \[SWARM MAIL/);
539
+ expect(researchMatch).not.toBeNull();
540
+ if (!researchMatch) return;
541
+
542
+ const researchContent = researchMatch[0];
543
+
544
+ // Should list when to SKIP research
545
+ expect(researchContent).toMatch(/don't research|skip research/i);
546
+ expect(researchContent).toMatch(/standard patterns|well-documented|obvious/i);
547
+ });
548
+
549
+ test("research section includes 3-step workflow", () => {
550
+ const researchMatch = SUBTASK_PROMPT_V2.match(/## \[ON-DEMAND RESEARCH\][\s\S]*?## \[SWARM MAIL/);
551
+ expect(researchMatch).not.toBeNull();
552
+ if (!researchMatch) return;
553
+
554
+ const researchContent = researchMatch[0];
555
+
556
+ // Should have numbered steps
557
+ expect(researchContent).toMatch(/1\.\s*.*Check semantic-memory/i);
558
+ expect(researchContent).toMatch(/2\.\s*.*spawn researcher/i);
559
+ expect(researchContent).toMatch(/3\.\s*.*wait.*continue/i);
560
+ });
561
+ });
562
+
563
+ describe("swarm_spawn_researcher tool", () => {
564
+ test("returns JSON with prompt field", async () => {
565
+ const { swarm_spawn_researcher } = await import("./swarm-prompts");
566
+
567
+ const result = await swarm_spawn_researcher.execute({
568
+ research_id: "research-abc123",
569
+ epic_id: "epic-xyz789",
570
+ tech_stack: ["Next.js", "React"],
571
+ project_path: "/Users/joel/Code/project",
572
+ });
573
+
574
+ const parsed = JSON.parse(result);
575
+ expect(parsed).toHaveProperty("prompt");
576
+ expect(typeof parsed.prompt).toBe("string");
577
+ expect(parsed.prompt.length).toBeGreaterThan(100);
578
+ });
579
+
580
+ test("returns subagent_type field as 'swarm/researcher'", async () => {
581
+ const { swarm_spawn_researcher } = await import("./swarm-prompts");
582
+
583
+ const result = await swarm_spawn_researcher.execute({
584
+ research_id: "research-abc123",
585
+ epic_id: "epic-xyz789",
586
+ tech_stack: ["Next.js"],
587
+ project_path: "/Users/joel/Code/project",
588
+ });
589
+
590
+ const parsed = JSON.parse(result);
591
+ expect(parsed.subagent_type).toBe("swarm/researcher");
592
+ });
593
+
594
+ test("returns expected_output schema", async () => {
595
+ const { swarm_spawn_researcher } = await import("./swarm-prompts");
596
+
597
+ const result = await swarm_spawn_researcher.execute({
598
+ research_id: "research-abc123",
599
+ epic_id: "epic-xyz789",
600
+ tech_stack: ["Next.js"],
601
+ project_path: "/Users/joel/Code/project",
602
+ });
603
+
604
+ const parsed = JSON.parse(result);
605
+ expect(parsed).toHaveProperty("expected_output");
606
+ expect(parsed.expected_output).toHaveProperty("technologies");
607
+ expect(parsed.expected_output).toHaveProperty("summary");
608
+ });
609
+
610
+ test("defaults check_upgrades to false when not provided", async () => {
611
+ const { swarm_spawn_researcher } = await import("./swarm-prompts");
612
+
613
+ const result = await swarm_spawn_researcher.execute({
614
+ research_id: "research-abc123",
615
+ epic_id: "epic-xyz789",
616
+ tech_stack: ["Next.js"],
617
+ project_path: "/Users/joel/Code/project",
618
+ });
619
+
620
+ const parsed = JSON.parse(result);
621
+ expect(parsed.check_upgrades).toBe(false);
622
+ });
623
+
624
+ test("respects check_upgrades when provided as true", async () => {
625
+ const { swarm_spawn_researcher } = await import("./swarm-prompts");
626
+
627
+ const result = await swarm_spawn_researcher.execute({
628
+ research_id: "research-abc123",
629
+ epic_id: "epic-xyz789",
630
+ tech_stack: ["Next.js"],
631
+ project_path: "/Users/joel/Code/project",
632
+ check_upgrades: true,
633
+ });
634
+
635
+ const parsed = JSON.parse(result);
636
+ expect(parsed.check_upgrades).toBe(true);
637
+ });
638
+
639
+ test("includes all input parameters in returned JSON", async () => {
640
+ const { swarm_spawn_researcher } = await import("./swarm-prompts");
641
+
642
+ const result = await swarm_spawn_researcher.execute({
643
+ research_id: "research-abc123",
644
+ epic_id: "epic-xyz789",
645
+ tech_stack: ["Next.js", "React", "TypeScript"],
646
+ project_path: "/Users/joel/Code/project",
647
+ check_upgrades: true,
648
+ });
649
+
650
+ const parsed = JSON.parse(result);
651
+ expect(parsed.research_id).toBe("research-abc123");
652
+ expect(parsed.epic_id).toBe("epic-xyz789");
653
+ expect(parsed.tech_stack).toEqual(["Next.js", "React", "TypeScript"]);
654
+ expect(parsed.project_path).toBe("/Users/joel/Code/project");
655
+ expect(parsed.check_upgrades).toBe(true);
656
+ });
657
+ });