opencode-swarm-plugin 0.44.0 → 0.44.2

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 (215) hide show
  1. package/bin/swarm.serve.test.ts +6 -4
  2. package/bin/swarm.ts +18 -12
  3. package/dist/compaction-prompt-scoring.js +139 -0
  4. package/dist/eval-capture.js +12811 -0
  5. package/dist/hive.d.ts.map +1 -1
  6. package/dist/hive.js +14834 -0
  7. package/dist/index.d.ts +18 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +7743 -62593
  10. package/dist/plugin.js +24052 -78907
  11. package/dist/swarm-orchestrate.d.ts.map +1 -1
  12. package/dist/swarm-prompts.d.ts.map +1 -1
  13. package/dist/swarm-prompts.js +39407 -0
  14. package/dist/swarm-review.d.ts.map +1 -1
  15. package/dist/swarm-validation.d.ts +127 -0
  16. package/dist/swarm-validation.d.ts.map +1 -0
  17. package/dist/validators/index.d.ts +7 -0
  18. package/dist/validators/index.d.ts.map +1 -0
  19. package/dist/validators/schema-validator.d.ts +58 -0
  20. package/dist/validators/schema-validator.d.ts.map +1 -0
  21. package/package.json +17 -5
  22. package/.changeset/swarm-insights-data-layer.md +0 -63
  23. package/.hive/analysis/eval-failure-analysis-2025-12-25.md +0 -331
  24. package/.hive/analysis/session-data-quality-audit.md +0 -320
  25. package/.hive/eval-results.json +0 -483
  26. package/.hive/issues.jsonl +0 -138
  27. package/.hive/memories.jsonl +0 -729
  28. package/.opencode/eval-history.jsonl +0 -327
  29. package/.turbo/turbo-build.log +0 -9
  30. package/CHANGELOG.md +0 -2286
  31. package/SCORER-ANALYSIS.md +0 -598
  32. package/docs/analysis/subagent-coordination-patterns.md +0 -902
  33. package/docs/analysis-socratic-planner-pattern.md +0 -504
  34. package/docs/planning/ADR-001-monorepo-structure.md +0 -171
  35. package/docs/planning/ADR-002-package-extraction.md +0 -393
  36. package/docs/planning/ADR-003-performance-improvements.md +0 -451
  37. package/docs/planning/ADR-004-message-queue-features.md +0 -187
  38. package/docs/planning/ADR-005-devtools-observability.md +0 -202
  39. package/docs/planning/ADR-007-swarm-enhancements-worktree-review.md +0 -168
  40. package/docs/planning/ADR-008-worker-handoff-protocol.md +0 -293
  41. package/docs/planning/ADR-009-oh-my-opencode-patterns.md +0 -353
  42. package/docs/planning/ADR-010-cass-inhousing.md +0 -1215
  43. package/docs/planning/ROADMAP.md +0 -368
  44. package/docs/semantic-memory-cli-syntax.md +0 -123
  45. package/docs/swarm-mail-architecture.md +0 -1147
  46. package/docs/testing/context-recovery-test.md +0 -470
  47. package/evals/ARCHITECTURE.md +0 -1189
  48. package/evals/README.md +0 -768
  49. package/evals/compaction-prompt.eval.ts +0 -149
  50. package/evals/compaction-resumption.eval.ts +0 -289
  51. package/evals/coordinator-behavior.eval.ts +0 -307
  52. package/evals/coordinator-session.eval.ts +0 -154
  53. package/evals/evalite.config.ts.bak +0 -15
  54. package/evals/example.eval.ts +0 -31
  55. package/evals/fixtures/cass-baseline.ts +0 -217
  56. package/evals/fixtures/compaction-cases.ts +0 -350
  57. package/evals/fixtures/compaction-prompt-cases.ts +0 -311
  58. package/evals/fixtures/coordinator-sessions.ts +0 -328
  59. package/evals/fixtures/decomposition-cases.ts +0 -105
  60. package/evals/lib/compaction-loader.test.ts +0 -248
  61. package/evals/lib/compaction-loader.ts +0 -320
  62. package/evals/lib/data-loader.evalite-test.ts +0 -289
  63. package/evals/lib/data-loader.test.ts +0 -345
  64. package/evals/lib/data-loader.ts +0 -281
  65. package/evals/lib/llm.ts +0 -115
  66. package/evals/scorers/compaction-prompt-scorers.ts +0 -145
  67. package/evals/scorers/compaction-scorers.ts +0 -305
  68. package/evals/scorers/coordinator-discipline.evalite-test.ts +0 -539
  69. package/evals/scorers/coordinator-discipline.ts +0 -325
  70. package/evals/scorers/index.test.ts +0 -146
  71. package/evals/scorers/index.ts +0 -328
  72. package/evals/scorers/outcome-scorers.evalite-test.ts +0 -27
  73. package/evals/scorers/outcome-scorers.ts +0 -349
  74. package/evals/swarm-decomposition.eval.ts +0 -121
  75. package/examples/commands/swarm.md +0 -745
  76. package/examples/plugin-wrapper-template.ts +0 -2515
  77. package/examples/skills/hive-workflow/SKILL.md +0 -212
  78. package/examples/skills/skill-creator/SKILL.md +0 -223
  79. package/examples/skills/swarm-coordination/SKILL.md +0 -292
  80. package/global-skills/cli-builder/SKILL.md +0 -344
  81. package/global-skills/cli-builder/references/advanced-patterns.md +0 -244
  82. package/global-skills/learning-systems/SKILL.md +0 -644
  83. package/global-skills/skill-creator/LICENSE.txt +0 -202
  84. package/global-skills/skill-creator/SKILL.md +0 -352
  85. package/global-skills/skill-creator/references/output-patterns.md +0 -82
  86. package/global-skills/skill-creator/references/workflows.md +0 -28
  87. package/global-skills/swarm-coordination/SKILL.md +0 -995
  88. package/global-skills/swarm-coordination/references/coordinator-patterns.md +0 -235
  89. package/global-skills/swarm-coordination/references/strategies.md +0 -138
  90. package/global-skills/system-design/SKILL.md +0 -213
  91. package/global-skills/testing-patterns/SKILL.md +0 -430
  92. package/global-skills/testing-patterns/references/dependency-breaking-catalog.md +0 -586
  93. package/opencode-swarm-plugin-0.30.7.tgz +0 -0
  94. package/opencode-swarm-plugin-0.31.0.tgz +0 -0
  95. package/scripts/cleanup-test-memories.ts +0 -346
  96. package/scripts/init-skill.ts +0 -222
  97. package/scripts/migrate-unknown-sessions.ts +0 -349
  98. package/scripts/validate-skill.ts +0 -204
  99. package/src/agent-mail.ts +0 -1724
  100. package/src/anti-patterns.test.ts +0 -1167
  101. package/src/anti-patterns.ts +0 -448
  102. package/src/compaction-capture.integration.test.ts +0 -257
  103. package/src/compaction-hook.test.ts +0 -838
  104. package/src/compaction-hook.ts +0 -1204
  105. package/src/compaction-observability.integration.test.ts +0 -139
  106. package/src/compaction-observability.test.ts +0 -187
  107. package/src/compaction-observability.ts +0 -324
  108. package/src/compaction-prompt-scorers.test.ts +0 -475
  109. package/src/compaction-prompt-scoring.ts +0 -300
  110. package/src/contributor-tools.test.ts +0 -133
  111. package/src/contributor-tools.ts +0 -201
  112. package/src/dashboard.test.ts +0 -611
  113. package/src/dashboard.ts +0 -462
  114. package/src/error-enrichment.test.ts +0 -403
  115. package/src/error-enrichment.ts +0 -219
  116. package/src/eval-capture.test.ts +0 -1015
  117. package/src/eval-capture.ts +0 -929
  118. package/src/eval-gates.test.ts +0 -306
  119. package/src/eval-gates.ts +0 -218
  120. package/src/eval-history.test.ts +0 -508
  121. package/src/eval-history.ts +0 -214
  122. package/src/eval-learning.test.ts +0 -378
  123. package/src/eval-learning.ts +0 -360
  124. package/src/eval-runner.test.ts +0 -223
  125. package/src/eval-runner.ts +0 -402
  126. package/src/export-tools.test.ts +0 -476
  127. package/src/export-tools.ts +0 -257
  128. package/src/hive.integration.test.ts +0 -2241
  129. package/src/hive.ts +0 -1628
  130. package/src/index.ts +0 -940
  131. package/src/learning.integration.test.ts +0 -1815
  132. package/src/learning.ts +0 -1079
  133. package/src/logger.test.ts +0 -189
  134. package/src/logger.ts +0 -135
  135. package/src/mandate-promotion.test.ts +0 -473
  136. package/src/mandate-promotion.ts +0 -239
  137. package/src/mandate-storage.integration.test.ts +0 -601
  138. package/src/mandate-storage.test.ts +0 -578
  139. package/src/mandate-storage.ts +0 -794
  140. package/src/mandates.ts +0 -540
  141. package/src/memory-tools.test.ts +0 -195
  142. package/src/memory-tools.ts +0 -344
  143. package/src/memory.integration.test.ts +0 -334
  144. package/src/memory.test.ts +0 -158
  145. package/src/memory.ts +0 -527
  146. package/src/model-selection.test.ts +0 -188
  147. package/src/model-selection.ts +0 -68
  148. package/src/observability-tools.test.ts +0 -359
  149. package/src/observability-tools.ts +0 -871
  150. package/src/output-guardrails.test.ts +0 -438
  151. package/src/output-guardrails.ts +0 -381
  152. package/src/pattern-maturity.test.ts +0 -1160
  153. package/src/pattern-maturity.ts +0 -525
  154. package/src/planning-guardrails.test.ts +0 -491
  155. package/src/planning-guardrails.ts +0 -438
  156. package/src/plugin.ts +0 -23
  157. package/src/post-compaction-tracker.test.ts +0 -251
  158. package/src/post-compaction-tracker.ts +0 -237
  159. package/src/query-tools.test.ts +0 -636
  160. package/src/query-tools.ts +0 -324
  161. package/src/rate-limiter.integration.test.ts +0 -466
  162. package/src/rate-limiter.ts +0 -774
  163. package/src/replay-tools.test.ts +0 -496
  164. package/src/replay-tools.ts +0 -240
  165. package/src/repo-crawl.integration.test.ts +0 -441
  166. package/src/repo-crawl.ts +0 -610
  167. package/src/schemas/cell-events.test.ts +0 -347
  168. package/src/schemas/cell-events.ts +0 -807
  169. package/src/schemas/cell.ts +0 -257
  170. package/src/schemas/evaluation.ts +0 -166
  171. package/src/schemas/index.test.ts +0 -199
  172. package/src/schemas/index.ts +0 -286
  173. package/src/schemas/mandate.ts +0 -232
  174. package/src/schemas/swarm-context.ts +0 -115
  175. package/src/schemas/task.ts +0 -161
  176. package/src/schemas/worker-handoff.test.ts +0 -302
  177. package/src/schemas/worker-handoff.ts +0 -131
  178. package/src/sessions/agent-discovery.test.ts +0 -137
  179. package/src/sessions/agent-discovery.ts +0 -112
  180. package/src/sessions/index.ts +0 -15
  181. package/src/skills.integration.test.ts +0 -1192
  182. package/src/skills.test.ts +0 -643
  183. package/src/skills.ts +0 -1549
  184. package/src/storage.integration.test.ts +0 -341
  185. package/src/storage.ts +0 -884
  186. package/src/structured.integration.test.ts +0 -817
  187. package/src/structured.test.ts +0 -1046
  188. package/src/structured.ts +0 -762
  189. package/src/swarm-decompose.test.ts +0 -188
  190. package/src/swarm-decompose.ts +0 -1302
  191. package/src/swarm-deferred.integration.test.ts +0 -157
  192. package/src/swarm-deferred.test.ts +0 -38
  193. package/src/swarm-insights.test.ts +0 -214
  194. package/src/swarm-insights.ts +0 -459
  195. package/src/swarm-mail.integration.test.ts +0 -970
  196. package/src/swarm-mail.ts +0 -739
  197. package/src/swarm-orchestrate.integration.test.ts +0 -282
  198. package/src/swarm-orchestrate.test.ts +0 -548
  199. package/src/swarm-orchestrate.ts +0 -3084
  200. package/src/swarm-prompts.test.ts +0 -1270
  201. package/src/swarm-prompts.ts +0 -2077
  202. package/src/swarm-research.integration.test.ts +0 -701
  203. package/src/swarm-research.test.ts +0 -698
  204. package/src/swarm-research.ts +0 -472
  205. package/src/swarm-review.integration.test.ts +0 -285
  206. package/src/swarm-review.test.ts +0 -879
  207. package/src/swarm-review.ts +0 -709
  208. package/src/swarm-strategies.ts +0 -407
  209. package/src/swarm-worktree.test.ts +0 -501
  210. package/src/swarm-worktree.ts +0 -575
  211. package/src/swarm.integration.test.ts +0 -2377
  212. package/src/swarm.ts +0 -38
  213. package/src/tool-adapter.integration.test.ts +0 -1221
  214. package/src/tool-availability.ts +0 -461
  215. package/tsconfig.json +0 -28
@@ -1,2377 +0,0 @@
1
- /**
2
- * Swarm Integration Tests
3
- *
4
- * These tests require:
5
- * - beads CLI installed and configured
6
- * - Agent Mail server running at AGENT_MAIL_URL (default: http://agent-mail:8765 in Docker)
7
- *
8
- * Run with: pnpm test:integration (or docker:test for full Docker environment)
9
- */
10
- import { describe, it, expect, beforeAll, afterAll } from "vitest";
11
- import {
12
- swarm_decompose,
13
- swarm_validate_decomposition,
14
- swarm_status,
15
- swarm_progress,
16
- swarm_complete,
17
- swarm_subtask_prompt,
18
- swarm_spawn_subtask,
19
- swarm_evaluation_prompt,
20
- swarm_select_strategy,
21
- swarm_plan_prompt,
22
- formatSubtaskPromptV2,
23
- SUBTASK_PROMPT_V2,
24
- swarm_checkpoint,
25
- swarm_recover,
26
- } from "./swarm";
27
- import { swarm_review, swarm_review_feedback } from "./swarm-review";
28
- import { mcpCall, setState, clearState, AGENT_MAIL_URL } from "./agent-mail";
29
-
30
- // ============================================================================
31
- // Test Configuration
32
- // ============================================================================
33
-
34
- const TEST_SESSION_ID = `test-swarm-${Date.now()}`;
35
- const TEST_PROJECT_PATH = `/tmp/test-swarm-${Date.now()}`;
36
-
37
- /**
38
- * Mock tool context for execute functions.
39
- * The real context is provided by OpenCode runtime.
40
- */
41
- const mockContext = {
42
- sessionID: TEST_SESSION_ID,
43
- messageID: `test-message-${Date.now()}`,
44
- agent: "test-agent",
45
- abort: new AbortController().signal,
46
- };
47
-
48
- /**
49
- * Check if Agent Mail is available
50
- */
51
- async function isAgentMailAvailable(): Promise<boolean> {
52
- try {
53
- const url = process.env.AGENT_MAIL_URL || AGENT_MAIL_URL;
54
- const response = await fetch(`${url}/health/liveness`);
55
- return response.ok;
56
- } catch {
57
- return false;
58
- }
59
- }
60
-
61
- /**
62
- * Check if beads CLI is available
63
- */
64
- async function isBeadsAvailable(): Promise<boolean> {
65
- try {
66
- const result = await Bun.$`bd --version`.quiet().nothrow();
67
- return result.exitCode === 0;
68
- } catch {
69
- return false;
70
- }
71
- }
72
-
73
- // ============================================================================
74
- // Prompt Generation Tests (No external dependencies)
75
- // ============================================================================
76
-
77
- describe("swarm_decompose", () => {
78
- it("generates valid decomposition prompt", async () => {
79
- const result = await swarm_decompose.execute(
80
- {
81
- task: "Add user authentication with OAuth",
82
- },
83
- mockContext,
84
- );
85
-
86
- const parsed = JSON.parse(result);
87
-
88
- expect(parsed).toHaveProperty("prompt");
89
- expect(parsed).toHaveProperty("expected_schema", "CellTree");
90
- expect(parsed).toHaveProperty("schema_hint");
91
- expect(parsed.prompt).toContain("Add user authentication with OAuth");
92
- expect(parsed.prompt).toContain("as many as needed");
93
- });
94
-
95
- it("includes context in prompt when provided", async () => {
96
- const result = await swarm_decompose.execute(
97
- {
98
- task: "Refactor the API routes",
99
- context: "Using Next.js App Router with RSC",
100
- },
101
- mockContext,
102
- );
103
-
104
- const parsed = JSON.parse(result);
105
-
106
- expect(parsed.prompt).toContain("Using Next.js App Router with RSC");
107
- expect(parsed.prompt).toContain("Additional Context");
108
- });
109
-
110
- });
111
-
112
- // ============================================================================
113
- // Strategy Selection Tests
114
- // ============================================================================
115
-
116
- describe("swarm_select_strategy", () => {
117
- it("selects feature-based for 'add' tasks", async () => {
118
- const result = await swarm_select_strategy.execute(
119
- {
120
- task: "Add user authentication with OAuth",
121
- },
122
- mockContext,
123
- );
124
- const parsed = JSON.parse(result);
125
-
126
- expect(parsed.strategy).toBe("feature-based");
127
- expect(parsed.confidence).toBeGreaterThan(0.5);
128
- expect(parsed.reasoning).toContain("add");
129
- expect(parsed.guidelines).toBeInstanceOf(Array);
130
- expect(parsed.anti_patterns).toBeInstanceOf(Array);
131
- });
132
-
133
- it("selects file-based for 'refactor' tasks", async () => {
134
- const result = await swarm_select_strategy.execute(
135
- {
136
- task: "Refactor all components to use new API",
137
- },
138
- mockContext,
139
- );
140
- const parsed = JSON.parse(result);
141
-
142
- expect(parsed.strategy).toBe("file-based");
143
- expect(parsed.confidence).toBeGreaterThanOrEqual(0.5);
144
- expect(parsed.reasoning).toContain("refactor");
145
- });
146
-
147
- it("selects risk-based for 'fix security' tasks", async () => {
148
- const result = await swarm_select_strategy.execute(
149
- {
150
- task: "Fix security vulnerability in authentication",
151
- },
152
- mockContext,
153
- );
154
- const parsed = JSON.parse(result);
155
-
156
- expect(parsed.strategy).toBe("risk-based");
157
- expect(parsed.confidence).toBeGreaterThan(0.5);
158
- // Should match either 'fix' or 'security'
159
- expect(
160
- parsed.reasoning.includes("fix") || parsed.reasoning.includes("security"),
161
- ).toBe(true);
162
- });
163
-
164
- it("defaults to feature-based when no keywords match", async () => {
165
- const result = await swarm_select_strategy.execute(
166
- {
167
- task: "Something completely unrelated without keywords",
168
- },
169
- mockContext,
170
- );
171
- const parsed = JSON.parse(result);
172
-
173
- expect(parsed.strategy).toBe("feature-based");
174
- // Confidence should be lower without keyword matches
175
- expect(parsed.confidence).toBeLessThanOrEqual(0.6);
176
- expect(parsed.reasoning).toContain("Defaulting to feature-based");
177
- });
178
-
179
- it("includes confidence score and reasoning", async () => {
180
- const result = await swarm_select_strategy.execute(
181
- {
182
- task: "Implement new dashboard feature",
183
- },
184
- mockContext,
185
- );
186
- const parsed = JSON.parse(result);
187
-
188
- expect(parsed).toHaveProperty("strategy");
189
- expect(parsed).toHaveProperty("confidence");
190
- expect(parsed).toHaveProperty("reasoning");
191
- expect(parsed).toHaveProperty("description");
192
- expect(typeof parsed.confidence).toBe("number");
193
- expect(parsed.confidence).toBeGreaterThanOrEqual(0);
194
- expect(parsed.confidence).toBeLessThanOrEqual(1);
195
- expect(typeof parsed.reasoning).toBe("string");
196
- expect(parsed.reasoning.length).toBeGreaterThan(0);
197
- });
198
-
199
- it("includes alternative strategies with scores", async () => {
200
- const result = await swarm_select_strategy.execute(
201
- {
202
- task: "Build new payment processing module",
203
- },
204
- mockContext,
205
- );
206
- const parsed = JSON.parse(result);
207
-
208
- expect(parsed).toHaveProperty("alternatives");
209
- expect(parsed.alternatives).toBeInstanceOf(Array);
210
- expect(parsed.alternatives.length).toBe(3); // 4 strategies - 1 selected = 3 alternatives
211
-
212
- for (const alt of parsed.alternatives) {
213
- expect(alt).toHaveProperty("strategy");
214
- expect(alt).toHaveProperty("description");
215
- expect(alt).toHaveProperty("score");
216
- expect([
217
- "file-based",
218
- "feature-based",
219
- "risk-based",
220
- "research-based",
221
- ]).toContain(alt.strategy);
222
- expect(typeof alt.score).toBe("number");
223
- }
224
- });
225
-
226
- it("includes codebase context in reasoning when provided", async () => {
227
- const result = await swarm_select_strategy.execute(
228
- {
229
- task: "Add new API endpoint",
230
- codebase_context: "Using Express.js with TypeScript and PostgreSQL",
231
- },
232
- mockContext,
233
- );
234
- const parsed = JSON.parse(result);
235
-
236
- expect(parsed.reasoning).toContain("Express.js");
237
- });
238
- });
239
-
240
- // ============================================================================
241
- // Planning Prompt Tests
242
- // ============================================================================
243
-
244
- describe("swarm_plan_prompt", () => {
245
- it("auto-selects strategy when not specified", async () => {
246
- const result = await swarm_plan_prompt.execute(
247
- {
248
- task: "Add user settings page",
249
- query_cass: false, // Disable CASS to isolate test
250
- },
251
- mockContext,
252
- );
253
- const parsed = JSON.parse(result);
254
-
255
- expect(parsed).toHaveProperty("prompt");
256
- expect(parsed).toHaveProperty("strategy");
257
- expect(parsed.strategy).toHaveProperty("selected");
258
- expect(parsed.strategy).toHaveProperty("reasoning");
259
- expect(parsed.strategy.selected).toBe("feature-based"); // 'add' keyword
260
- });
261
-
262
- it("uses explicit strategy when provided", async () => {
263
- const result = await swarm_plan_prompt.execute(
264
- {
265
- task: "Do something",
266
- strategy: "risk-based",
267
- query_cass: false,
268
- },
269
- mockContext,
270
- );
271
- const parsed = JSON.parse(result);
272
-
273
- expect(parsed.strategy.selected).toBe("risk-based");
274
- expect(parsed.strategy.reasoning).toContain("User-specified strategy");
275
- });
276
-
277
- it("includes strategy guidelines in prompt", async () => {
278
- const result = await swarm_plan_prompt.execute(
279
- {
280
- task: "Refactor the codebase",
281
- query_cass: false,
282
- },
283
- mockContext,
284
- );
285
- const parsed = JSON.parse(result);
286
-
287
- // Prompt should contain strategy-specific guidelines
288
- expect(parsed.prompt).toContain("## Strategy:");
289
- expect(parsed.prompt).toContain("### Guidelines");
290
- expect(parsed.prompt).toContain("### Anti-Patterns");
291
- expect(parsed.prompt).toContain("### Examples");
292
- });
293
-
294
- it("includes anti-patterns in output", async () => {
295
- const result = await swarm_plan_prompt.execute(
296
- {
297
- task: "Build new feature",
298
- query_cass: false,
299
- },
300
- mockContext,
301
- );
302
- const parsed = JSON.parse(result);
303
-
304
- expect(parsed.strategy).toHaveProperty("anti_patterns");
305
- expect(parsed.strategy.anti_patterns).toBeInstanceOf(Array);
306
- expect(parsed.strategy.anti_patterns.length).toBeGreaterThan(0);
307
- });
308
-
309
- it("returns expected_schema and validation_note", async () => {
310
- const result = await swarm_plan_prompt.execute(
311
- {
312
- task: "Some task",
313
- query_cass: false,
314
- },
315
- mockContext,
316
- );
317
- const parsed = JSON.parse(result);
318
-
319
- expect(parsed).toHaveProperty("expected_schema", "CellTree");
320
- expect(parsed).toHaveProperty("validation_note");
321
- expect(parsed.validation_note).toContain("swarm_validate_decomposition");
322
- expect(parsed).toHaveProperty("schema_hint");
323
- expect(parsed.schema_hint).toHaveProperty("epic");
324
- expect(parsed.schema_hint).toHaveProperty("subtasks");
325
- });
326
-
327
- it("includes strategy and skills info in output", async () => {
328
- // Test swarm_plan_prompt output structure
329
- const result = await swarm_plan_prompt.execute(
330
- {
331
- task: "Add feature",
332
- },
333
- mockContext,
334
- );
335
- const parsed = JSON.parse(result);
336
-
337
- // Should have strategy info
338
- expect(parsed).toHaveProperty("strategy");
339
- expect(parsed.strategy).toHaveProperty("selected");
340
- expect(parsed.strategy).toHaveProperty("reasoning");
341
- expect(parsed.strategy).toHaveProperty("guidelines");
342
- expect(parsed.strategy).toHaveProperty("anti_patterns");
343
-
344
- // Should have skills info
345
- expect(parsed).toHaveProperty("skills");
346
- expect(parsed.skills).toHaveProperty("included");
347
-
348
- // Should have memory query instruction
349
- expect(parsed).toHaveProperty("memory_query");
350
- });
351
-
352
- it("includes context in prompt when provided", async () => {
353
- const result = await swarm_plan_prompt.execute(
354
- {
355
- task: "Add user profile",
356
- context: "We use Next.js App Router with server components",
357
- query_cass: false,
358
- },
359
- mockContext,
360
- );
361
- const parsed = JSON.parse(result);
362
-
363
- expect(parsed.prompt).toContain("Next.js App Router");
364
- expect(parsed.prompt).toContain("server components");
365
- });
366
-
367
- });
368
-
369
- describe("swarm_validate_decomposition", () => {
370
- it("validates correct CellTree", async () => {
371
- const validCellTree = JSON.stringify({
372
- epic: {
373
- title: "Add OAuth",
374
- description: "Implement OAuth authentication",
375
- },
376
- subtasks: [
377
- {
378
- title: "Add OAuth provider config",
379
- description: "Set up Google OAuth",
380
- files: ["src/auth/google.ts", "src/auth/config.ts"],
381
- dependencies: [],
382
- estimated_complexity: 2,
383
- },
384
- {
385
- title: "Add login UI",
386
- description: "Create login button component",
387
- files: ["src/components/LoginButton.tsx"],
388
- dependencies: [0],
389
- estimated_complexity: 1,
390
- },
391
- ],
392
- });
393
-
394
- const result = await swarm_validate_decomposition.execute(
395
- { response: validCellTree },
396
- mockContext,
397
- );
398
-
399
- const parsed = JSON.parse(result);
400
-
401
- expect(parsed.valid).toBe(true);
402
- expect(parsed.cell_tree).toBeDefined();
403
- expect(parsed.stats).toEqual({
404
- subtask_count: 2,
405
- total_files: 3,
406
- total_complexity: 3,
407
- });
408
- });
409
-
410
- it("rejects file conflicts", async () => {
411
- const conflictingCellTree = JSON.stringify({
412
- epic: {
413
- title: "Conflicting files",
414
- },
415
- subtasks: [
416
- {
417
- title: "Task A",
418
- files: ["src/shared.ts"],
419
- dependencies: [],
420
- estimated_complexity: 1,
421
- },
422
- {
423
- title: "Task B",
424
- files: ["src/shared.ts"], // Conflict!
425
- dependencies: [],
426
- estimated_complexity: 1,
427
- },
428
- ],
429
- });
430
-
431
- const result = await swarm_validate_decomposition.execute(
432
- { response: conflictingCellTree },
433
- mockContext,
434
- );
435
-
436
- const parsed = JSON.parse(result);
437
-
438
- expect(parsed.valid).toBe(false);
439
- expect(parsed.error).toContain("File conflicts detected");
440
- expect(parsed.error).toContain("src/shared.ts");
441
- });
442
-
443
- it("rejects invalid dependencies (forward reference)", async () => {
444
- const invalidDeps = JSON.stringify({
445
- epic: {
446
- title: "Invalid deps",
447
- },
448
- subtasks: [
449
- {
450
- title: "Task A",
451
- files: ["src/a.ts"],
452
- dependencies: [1], // Invalid: depends on later task
453
- estimated_complexity: 1,
454
- },
455
- {
456
- title: "Task B",
457
- files: ["src/b.ts"],
458
- dependencies: [],
459
- estimated_complexity: 1,
460
- },
461
- ],
462
- });
463
-
464
- const result = await swarm_validate_decomposition.execute(
465
- { response: invalidDeps },
466
- mockContext,
467
- );
468
-
469
- const parsed = JSON.parse(result);
470
-
471
- expect(parsed.valid).toBe(false);
472
- expect(parsed.error).toContain("Invalid dependency");
473
- expect(parsed.hint).toContain("Reorder subtasks");
474
- });
475
-
476
- it("rejects invalid JSON", async () => {
477
- const result = await swarm_validate_decomposition.execute(
478
- { response: "not valid json {" },
479
- mockContext,
480
- );
481
-
482
- const parsed = JSON.parse(result);
483
-
484
- expect(parsed.valid).toBe(false);
485
- expect(parsed.error).toContain("Invalid JSON");
486
- });
487
-
488
- it("rejects missing required fields", async () => {
489
- const missingFields = JSON.stringify({
490
- epic: { title: "Missing subtasks" },
491
- // No subtasks array
492
- });
493
-
494
- const result = await swarm_validate_decomposition.execute(
495
- { response: missingFields },
496
- mockContext,
497
- );
498
-
499
- const parsed = JSON.parse(result);
500
-
501
- expect(parsed.valid).toBe(false);
502
- expect(parsed.error).toContain("Schema validation failed");
503
- });
504
- });
505
-
506
- describe("swarm_subtask_prompt", () => {
507
- it("generates complete subtask prompt", async () => {
508
- const result = await swarm_subtask_prompt.execute(
509
- {
510
- agent_name: "BlueLake",
511
- bead_id: "bd-abc123.1",
512
- epic_id: "bd-abc123",
513
- subtask_title: "Add OAuth provider",
514
- subtask_description: "Configure Google OAuth in the auth config",
515
- files: ["src/auth/google.ts", "src/auth/config.ts"],
516
- shared_context: "We are using NextAuth.js v5",
517
- },
518
- mockContext,
519
- );
520
-
521
- // Result is the prompt string directly
522
- expect(result).toContain("BlueLake");
523
- expect(result).toContain("bd-abc123.1");
524
- expect(result).toContain("bd-abc123");
525
- expect(result).toContain("Add OAuth provider");
526
- expect(result).toContain("Configure Google OAuth");
527
- expect(result).toContain("src/auth/google.ts");
528
- expect(result).toContain("NextAuth.js v5");
529
- expect(result).toContain("swarm_progress");
530
- expect(result).toContain("swarm_complete");
531
- });
532
-
533
- it("handles missing optional fields", async () => {
534
- const result = await swarm_subtask_prompt.execute(
535
- {
536
- agent_name: "RedStone",
537
- bead_id: "bd-xyz789.2",
538
- epic_id: "bd-xyz789",
539
- subtask_title: "Simple task",
540
- files: [],
541
- },
542
- mockContext,
543
- );
544
-
545
- expect(result).toContain("RedStone");
546
- expect(result).toContain("bd-xyz789.2");
547
- expect(result).toContain("Simple task");
548
- expect(result).toContain("(none)"); // For missing description/context
549
- expect(result).toContain("(no files assigned)"); // Empty files
550
- });
551
- });
552
-
553
- describe("swarm_evaluation_prompt", () => {
554
- it("generates evaluation prompt with schema hint", async () => {
555
- const result = await swarm_evaluation_prompt.execute(
556
- {
557
- bead_id: "bd-abc123.1",
558
- subtask_title: "Add OAuth provider",
559
- files_touched: ["src/auth/google.ts", "src/auth/config.ts"],
560
- },
561
- mockContext,
562
- );
563
-
564
- const parsed = JSON.parse(result);
565
-
566
- expect(parsed).toHaveProperty("prompt");
567
- expect(parsed).toHaveProperty("expected_schema", "Evaluation");
568
- expect(parsed).toHaveProperty("schema_hint");
569
-
570
- expect(parsed.prompt).toContain("bd-abc123.1");
571
- expect(parsed.prompt).toContain("Add OAuth provider");
572
- expect(parsed.prompt).toContain("src/auth/google.ts");
573
- expect(parsed.prompt).toContain("type_safe");
574
- expect(parsed.prompt).toContain("no_bugs");
575
- expect(parsed.prompt).toContain("patterns");
576
- expect(parsed.prompt).toContain("readable");
577
- });
578
-
579
- it("handles empty files list", async () => {
580
- const result = await swarm_evaluation_prompt.execute(
581
- {
582
- bead_id: "bd-xyz789.1",
583
- subtask_title: "Documentation only",
584
- files_touched: [],
585
- },
586
- mockContext,
587
- );
588
-
589
- const parsed = JSON.parse(result);
590
-
591
- expect(parsed.prompt).toContain("(no files recorded)");
592
- });
593
- });
594
-
595
- // ============================================================================
596
- // Integration Tests (Require Agent Mail + beads)
597
- // ============================================================================
598
-
599
- describe("swarm_status (integration)", () => {
600
- let beadsAvailable = false;
601
-
602
- beforeAll(async () => {
603
- beadsAvailable = await isBeadsAvailable();
604
- });
605
-
606
- it.skipIf(!beadsAvailable)(
607
- "returns status for non-existent epic",
608
- async () => {
609
- // This should fail gracefully - no epic exists
610
- try {
611
- await swarm_status.execute(
612
- {
613
- epic_id: "bd-nonexistent",
614
- project_key: TEST_PROJECT_PATH,
615
- },
616
- mockContext,
617
- );
618
- // If it doesn't throw, that's fine too - it might return empty status
619
- } catch (error) {
620
- expect(error).toBeInstanceOf(Error);
621
- // SwarmError should have operation property
622
- if (error instanceof Error && "operation" in error) {
623
- expect((error as { operation: string }).operation).toBe(
624
- "query_subtasks",
625
- );
626
- }
627
- }
628
- },
629
- );
630
- });
631
-
632
- describe("swarm_progress (integration)", () => {
633
- let agentMailAvailable = false;
634
-
635
- beforeAll(async () => {
636
- agentMailAvailable = await isAgentMailAvailable();
637
- });
638
-
639
- it.skipIf(!agentMailAvailable)("reports progress to Agent Mail", async () => {
640
- const uniqueProjectKey = `${TEST_PROJECT_PATH}-progress-${Date.now()}`;
641
- const sessionID = `progress-session-${Date.now()}`;
642
-
643
- // Initialize Agent Mail state for this session
644
- try {
645
- // Ensure project exists
646
- await mcpCall("ensure_project", { human_key: uniqueProjectKey });
647
-
648
- // Register agent
649
- const agent = await mcpCall<{ name: string }>("register_agent", {
650
- project_key: uniqueProjectKey,
651
- program: "opencode-test",
652
- model: "test",
653
- task_description: "Integration test",
654
- });
655
-
656
- // Set state for the session
657
- setState(sessionID, {
658
- projectKey: uniqueProjectKey,
659
- agentName: agent.name,
660
- reservations: [],
661
- startedAt: new Date().toISOString(),
662
- });
663
-
664
- const ctx = {
665
- ...mockContext,
666
- sessionID,
667
- };
668
-
669
- const result = await swarm_progress.execute(
670
- {
671
- project_key: uniqueProjectKey,
672
- agent_name: agent.name,
673
- bead_id: "bd-test123.1",
674
- status: "in_progress",
675
- message: "Working on the feature",
676
- progress_percent: 50,
677
- files_touched: ["src/test.ts"],
678
- },
679
- ctx,
680
- );
681
-
682
- expect(result).toContain("Progress reported");
683
- expect(result).toContain("in_progress");
684
- expect(result).toContain("50%");
685
- } finally {
686
- clearState(sessionID);
687
- }
688
- });
689
- });
690
-
691
- describe("swarm_complete (integration)", () => {
692
- let agentMailAvailable = false;
693
- let beadsAvailable = false;
694
-
695
- beforeAll(async () => {
696
- agentMailAvailable = await isAgentMailAvailable();
697
- beadsAvailable = await isBeadsAvailable();
698
- });
699
-
700
- it.skipIf(!agentMailAvailable || !beadsAvailable)(
701
- "completes subtask with passing evaluation",
702
- async () => {
703
- const uniqueProjectKey = `${TEST_PROJECT_PATH}-complete-${Date.now()}`;
704
- const sessionID = `complete-session-${Date.now()}`;
705
-
706
- try {
707
- // Set up Agent Mail
708
- await mcpCall("ensure_project", { human_key: uniqueProjectKey });
709
- const agent = await mcpCall<{ name: string }>("register_agent", {
710
- project_key: uniqueProjectKey,
711
- program: "opencode-test",
712
- model: "test",
713
- task_description: "Integration test",
714
- });
715
-
716
- setState(sessionID, {
717
- projectKey: uniqueProjectKey,
718
- agentName: agent.name,
719
- reservations: [],
720
- startedAt: new Date().toISOString(),
721
- });
722
-
723
- const ctx = {
724
- ...mockContext,
725
- sessionID,
726
- };
727
-
728
- // Create a test bead first
729
- const createResult =
730
- await Bun.$`bd create "Test subtask" -t task --json`
731
- .quiet()
732
- .nothrow();
733
-
734
- if (createResult.exitCode !== 0) {
735
- console.warn(
736
- "Could not create test bead:",
737
- createResult.stderr.toString(),
738
- );
739
- return;
740
- }
741
-
742
- const bead = JSON.parse(createResult.stdout.toString());
743
-
744
- const passingEvaluation = JSON.stringify({
745
- passed: true,
746
- criteria: {
747
- type_safe: { passed: true, feedback: "All types correct" },
748
- no_bugs: { passed: true, feedback: "No issues found" },
749
- patterns: { passed: true, feedback: "Follows conventions" },
750
- readable: { passed: true, feedback: "Clear code" },
751
- },
752
- overall_feedback: "Great work!",
753
- retry_suggestion: null,
754
- });
755
-
756
- const result = await swarm_complete.execute(
757
- {
758
- project_key: uniqueProjectKey,
759
- agent_name: agent.name,
760
- bead_id: bead.id,
761
- summary: "Completed the test subtask",
762
- evaluation: passingEvaluation,
763
- },
764
- ctx,
765
- );
766
-
767
- const parsed = JSON.parse(result);
768
-
769
- expect(parsed.success).toBe(true);
770
- expect(parsed.bead_id).toBe(bead.id);
771
- expect(parsed.closed).toBe(true);
772
- expect(parsed.reservations_released).toBe(true);
773
- expect(parsed.message_sent).toBe(true);
774
- } finally {
775
- clearState(sessionID);
776
- }
777
- },
778
- );
779
-
780
- it.skipIf(!agentMailAvailable)(
781
- "rejects completion with failing evaluation",
782
- async () => {
783
- const uniqueProjectKey = `${TEST_PROJECT_PATH}-fail-${Date.now()}`;
784
- const sessionID = `fail-session-${Date.now()}`;
785
-
786
- try {
787
- // Set up Agent Mail
788
- await mcpCall("ensure_project", { human_key: uniqueProjectKey });
789
- const agent = await mcpCall<{ name: string }>("register_agent", {
790
- project_key: uniqueProjectKey,
791
- program: "opencode-test",
792
- model: "test",
793
- task_description: "Integration test",
794
- });
795
-
796
- setState(sessionID, {
797
- projectKey: uniqueProjectKey,
798
- agentName: agent.name,
799
- reservations: [],
800
- startedAt: new Date().toISOString(),
801
- });
802
-
803
- const ctx = {
804
- ...mockContext,
805
- sessionID,
806
- };
807
-
808
- const failingEvaluation = JSON.stringify({
809
- passed: false,
810
- criteria: {
811
- type_safe: { passed: false, feedback: "Missing types on line 42" },
812
- },
813
- overall_feedback: "Needs work",
814
- retry_suggestion: "Add explicit types to the handler function",
815
- });
816
-
817
- const result = await swarm_complete.execute(
818
- {
819
- project_key: uniqueProjectKey,
820
- agent_name: agent.name,
821
- bead_id: "bd-test-fail.1",
822
- summary: "Attempted completion",
823
- evaluation: failingEvaluation,
824
- },
825
- ctx,
826
- );
827
-
828
- const parsed = JSON.parse(result);
829
-
830
- expect(parsed.success).toBe(false);
831
- expect(parsed.error).toContain("Self-evaluation failed");
832
- expect(parsed.retry_suggestion).toBe(
833
- "Add explicit types to the handler function",
834
- );
835
- } finally {
836
- clearState(sessionID);
837
- }
838
- },
839
- );
840
- });
841
-
842
- // ============================================================================
843
- // Full Swarm Flow (End-to-End)
844
- // ============================================================================
845
-
846
- describe("full swarm flow (integration)", () => {
847
- let agentMailAvailable = false;
848
- let beadsAvailable = false;
849
-
850
- beforeAll(async () => {
851
- agentMailAvailable = await isAgentMailAvailable();
852
- beadsAvailable = await isBeadsAvailable();
853
- });
854
-
855
- it.skipIf(!agentMailAvailable || !beadsAvailable)(
856
- "creates epic, reports progress, completes subtask",
857
- async () => {
858
- const uniqueProjectKey = `${TEST_PROJECT_PATH}-flow-${Date.now()}`;
859
- const sessionID = `flow-session-${Date.now()}`;
860
-
861
- try {
862
- // 1. Set up Agent Mail session
863
- await mcpCall("ensure_project", { human_key: uniqueProjectKey });
864
- const agent = await mcpCall<{ name: string }>("register_agent", {
865
- project_key: uniqueProjectKey,
866
- program: "opencode-test",
867
- model: "test",
868
- task_description: "E2E swarm test",
869
- });
870
-
871
- setState(sessionID, {
872
- projectKey: uniqueProjectKey,
873
- agentName: agent.name,
874
- reservations: [],
875
- startedAt: new Date().toISOString(),
876
- });
877
-
878
- const ctx = {
879
- ...mockContext,
880
- sessionID,
881
- };
882
-
883
- // 2. Generate decomposition prompt
884
- const decomposeResult = await swarm_decompose.execute(
885
- {
886
- task: "Add unit tests for auth module",
887
- },
888
- ctx,
889
- );
890
-
891
- const decomposition = JSON.parse(decomposeResult);
892
- expect(decomposition.prompt).toContain("Add unit tests");
893
-
894
- // 3. Create an epic with bd CLI
895
- const epicResult =
896
- await Bun.$`bd create "Add unit tests for auth module" -t epic --json`
897
- .quiet()
898
- .nothrow();
899
-
900
- if (epicResult.exitCode !== 0) {
901
- console.warn("Could not create epic:", epicResult.stderr.toString());
902
- return;
903
- }
904
-
905
- const epic = JSON.parse(epicResult.stdout.toString());
906
- expect(epic.id).toMatch(/^[a-z0-9-]+-[a-z0-9]+$/);
907
-
908
- // 4. Create a subtask
909
- const subtaskResult =
910
- await Bun.$`bd create "Test login flow" -t task --json`
911
- .quiet()
912
- .nothrow();
913
-
914
- if (subtaskResult.exitCode !== 0) {
915
- console.warn(
916
- "Could not create subtask:",
917
- subtaskResult.stderr.toString(),
918
- );
919
- return;
920
- }
921
-
922
- const subtask = JSON.parse(subtaskResult.stdout.toString());
923
-
924
- // 5. Generate subtask prompt
925
- const subtaskPrompt = await swarm_subtask_prompt.execute(
926
- {
927
- agent_name: agent.name,
928
- bead_id: subtask.id,
929
- epic_id: epic.id,
930
- subtask_title: "Test login flow",
931
- files: ["src/auth/__tests__/login.test.ts"],
932
- },
933
- ctx,
934
- );
935
-
936
- expect(subtaskPrompt).toContain(agent.name);
937
- expect(subtaskPrompt).toContain(subtask.id);
938
-
939
- // 6. Report progress
940
- const progressResult = await swarm_progress.execute(
941
- {
942
- project_key: uniqueProjectKey,
943
- agent_name: agent.name,
944
- bead_id: subtask.id,
945
- status: "in_progress",
946
- progress_percent: 50,
947
- message: "Writing test cases",
948
- },
949
- ctx,
950
- );
951
-
952
- expect(progressResult).toContain("Progress reported");
953
-
954
- // 7. Generate evaluation prompt
955
- const evalPromptResult = await swarm_evaluation_prompt.execute(
956
- {
957
- bead_id: subtask.id,
958
- subtask_title: "Test login flow",
959
- files_touched: ["src/auth/__tests__/login.test.ts"],
960
- },
961
- ctx,
962
- );
963
-
964
- const evalPrompt = JSON.parse(evalPromptResult);
965
- expect(evalPrompt.expected_schema).toBe("Evaluation");
966
-
967
- // 8. Complete the subtask
968
- const completeResult = await swarm_complete.execute(
969
- {
970
- project_key: uniqueProjectKey,
971
- agent_name: agent.name,
972
- bead_id: subtask.id,
973
- summary: "Added comprehensive login tests",
974
- evaluation: JSON.stringify({
975
- passed: true,
976
- criteria: {
977
- type_safe: { passed: true, feedback: "TypeScript compiles" },
978
- no_bugs: { passed: true, feedback: "Tests pass" },
979
- patterns: { passed: true, feedback: "Follows test patterns" },
980
- readable: { passed: true, feedback: "Clear test names" },
981
- },
982
- overall_feedback: "Good test coverage",
983
- retry_suggestion: null,
984
- }),
985
- },
986
- ctx,
987
- );
988
-
989
- const completion = JSON.parse(completeResult);
990
- expect(completion.success).toBe(true);
991
- expect(completion.closed).toBe(true);
992
- expect(completion.message_sent).toBe(true);
993
-
994
- // 9. Check swarm status
995
- const statusResult = await swarm_status.execute(
996
- {
997
- epic_id: epic.id,
998
- project_key: uniqueProjectKey,
999
- },
1000
- ctx,
1001
- );
1002
-
1003
- const status = JSON.parse(statusResult);
1004
- expect(status.epic_id).toBe(epic.id);
1005
- // Status may show completed subtasks now
1006
- } finally {
1007
- clearState(sessionID);
1008
- }
1009
- },
1010
- );
1011
- });
1012
-
1013
- // ============================================================================
1014
- // Tool Availability & Graceful Degradation Tests
1015
- // ============================================================================
1016
-
1017
- import {
1018
- checkTool,
1019
- isToolAvailable,
1020
- checkAllTools,
1021
- formatToolAvailability,
1022
- resetToolCache,
1023
- withToolFallback,
1024
- ifToolAvailable,
1025
- } from "./tool-availability";
1026
- import { swarm_init } from "./swarm";
1027
-
1028
- describe("Tool Availability", () => {
1029
- beforeAll(() => {
1030
- resetToolCache();
1031
- });
1032
-
1033
- afterAll(() => {
1034
- resetToolCache();
1035
- });
1036
-
1037
- it("checks individual tool availability", async () => {
1038
- const status = await checkTool("semantic-memory");
1039
- expect(status).toHaveProperty("available");
1040
- expect(status).toHaveProperty("checkedAt");
1041
- expect(typeof status.available).toBe("boolean");
1042
- });
1043
-
1044
- it("caches tool availability checks", async () => {
1045
- const status1 = await checkTool("semantic-memory");
1046
- const status2 = await checkTool("semantic-memory");
1047
- // Same timestamp means cached
1048
- expect(status1.checkedAt).toBe(status2.checkedAt);
1049
- });
1050
-
1051
- it("checks all tools at once", async () => {
1052
- const availability = await checkAllTools();
1053
- expect(availability.size).toBe(7); // semantic-memory, cass, ubs, hive, beads, swarm-mail, agent-mail
1054
- expect(availability.has("semantic-memory")).toBe(true);
1055
- expect(availability.has("cass")).toBe(true);
1056
- expect(availability.has("ubs")).toBe(true);
1057
- expect(availability.has("beads")).toBe(true);
1058
- expect(availability.has("swarm-mail")).toBe(true);
1059
- expect(availability.has("agent-mail")).toBe(true);
1060
- });
1061
-
1062
- it("formats tool availability for display", async () => {
1063
- const availability = await checkAllTools();
1064
- const formatted = formatToolAvailability(availability);
1065
- expect(formatted).toContain("Tool Availability:");
1066
- expect(formatted).toContain("semantic-memory");
1067
- });
1068
-
1069
- it("executes with fallback when tool unavailable", async () => {
1070
- // Force cache reset to test fresh
1071
- resetToolCache();
1072
-
1073
- const result = await withToolFallback(
1074
- "ubs", // May or may not be available
1075
- async () => "action-result",
1076
- () => "fallback-result",
1077
- );
1078
-
1079
- // Either result is valid depending on tool availability
1080
- expect(["action-result", "fallback-result"]).toContain(result);
1081
- });
1082
-
1083
- it("returns undefined when tool unavailable with ifToolAvailable", async () => {
1084
- resetToolCache();
1085
-
1086
- // This will return undefined if agent-mail is not running
1087
- const result = await ifToolAvailable("agent-mail", async () => "success");
1088
-
1089
- // Result is either "success" or undefined
1090
- expect([undefined, "success"]).toContain(result);
1091
- });
1092
- });
1093
-
1094
- describe("swarm_init", () => {
1095
- it("reports tool availability status", async () => {
1096
- resetToolCache();
1097
-
1098
- const result = await swarm_init.execute({}, mockContext);
1099
- const parsed = JSON.parse(result);
1100
-
1101
- expect(parsed).toHaveProperty("ready", true);
1102
- expect(parsed).toHaveProperty("tool_availability");
1103
- expect(parsed).toHaveProperty("report");
1104
-
1105
- // Check tool availability structure
1106
- const tools = parsed.tool_availability;
1107
- expect(tools).toHaveProperty("semantic-memory");
1108
- expect(tools).toHaveProperty("cass");
1109
- expect(tools).toHaveProperty("ubs");
1110
- expect(tools).toHaveProperty("beads");
1111
- expect(tools).toHaveProperty("agent-mail");
1112
-
1113
- // Each tool should have available and fallback
1114
- for (const [, info] of Object.entries(tools)) {
1115
- expect(info).toHaveProperty("available");
1116
- expect(info).toHaveProperty("fallback");
1117
- }
1118
- });
1119
-
1120
- it("includes recommendations", async () => {
1121
- const result = await swarm_init.execute({}, mockContext);
1122
- const parsed = JSON.parse(result);
1123
-
1124
- expect(parsed).toHaveProperty("recommendations");
1125
- expect(parsed.recommendations).toHaveProperty("beads");
1126
- expect(parsed.recommendations).toHaveProperty("agent_mail");
1127
- });
1128
- });
1129
-
1130
- describe("Worker Handoff Generation", () => {
1131
- it("generateWorkerHandoff creates valid WorkerHandoff object", () => {
1132
- // This will test the new function once we implement it
1133
- const { generateWorkerHandoff } = require("./swarm-orchestrate");
1134
-
1135
- const handoff = generateWorkerHandoff({
1136
- task_id: "opencode-swarm-monorepo-lf2p4u-abc123.1",
1137
- files_owned: ["src/auth.ts", "src/middleware.ts"],
1138
- epic_summary: "Add OAuth authentication",
1139
- your_role: "Implement OAuth provider",
1140
- dependencies_completed: ["Database schema ready"],
1141
- what_comes_next: "Integration tests",
1142
- });
1143
-
1144
- // Verify contract section
1145
- expect(handoff.contract.task_id).toBe("opencode-swarm-monorepo-lf2p4u-abc123.1");
1146
- expect(handoff.contract.files_owned).toEqual(["src/auth.ts", "src/middleware.ts"]);
1147
- expect(handoff.contract.files_readonly).toEqual([]);
1148
- expect(handoff.contract.dependencies_completed).toEqual(["Database schema ready"]);
1149
- expect(handoff.contract.success_criteria.length).toBeGreaterThan(0);
1150
-
1151
- // Verify context section
1152
- expect(handoff.context.epic_summary).toBe("Add OAuth authentication");
1153
- expect(handoff.context.your_role).toBe("Implement OAuth provider");
1154
- expect(handoff.context.what_comes_next).toBe("Integration tests");
1155
-
1156
- // Verify escalation section
1157
- expect(handoff.escalation.blocked_contact).toBe("coordinator");
1158
- expect(handoff.escalation.scope_change_protocol).toContain("swarmmail_send");
1159
- });
1160
-
1161
- it("swarm_spawn_subtask includes handoff JSON in prompt", async () => {
1162
- const result = await swarm_spawn_subtask.execute(
1163
- {
1164
- bead_id: "opencode-swarm-monorepo-lf2p4u-abc123.1",
1165
- epic_id: "opencode-swarm-monorepo-lf2p4u-abc123",
1166
- subtask_title: "Add OAuth provider",
1167
- subtask_description: "Configure Google OAuth",
1168
- files: ["src/auth/google.ts"],
1169
- shared_context: "Using NextAuth.js v5",
1170
- },
1171
- mockContext,
1172
- );
1173
-
1174
- // Parse the JSON response
1175
- const parsed = JSON.parse(result);
1176
- const prompt = parsed.prompt;
1177
-
1178
- // Should contain WorkerHandoff JSON section
1179
- expect(prompt).toContain("## WorkerHandoff Contract");
1180
- expect(prompt).toContain('"contract"');
1181
- expect(prompt).toContain('"task_id"');
1182
- expect(prompt).toContain('"files_owned"');
1183
- expect(prompt).toContain('"success_criteria"');
1184
- expect(prompt).toContain("opencode-swarm-monorepo-lf2p4u-abc123.1");
1185
- });
1186
- });
1187
-
1188
- describe("Graceful Degradation", () => {
1189
- it("swarm_decompose works without CASS", async () => {
1190
- // This should work regardless of CASS availability
1191
- const result = await swarm_decompose.execute(
1192
- {
1193
- task: "Add user authentication",
1194
- query_cass: true, // Request CASS but it may not be available
1195
- },
1196
- mockContext,
1197
- );
1198
-
1199
- const parsed = JSON.parse(result);
1200
-
1201
- // Should always return a valid prompt
1202
- expect(parsed).toHaveProperty("prompt");
1203
- expect(parsed.prompt).toContain("Add user authentication");
1204
-
1205
- // CASS history should indicate whether it was queried
1206
- expect(parsed).toHaveProperty("cass_history");
1207
- expect(parsed.cass_history).toHaveProperty("queried");
1208
- });
1209
-
1210
- it("swarm_decompose can skip CASS explicitly", async () => {
1211
- const result = await swarm_decompose.execute(
1212
- {
1213
- task: "Add user authentication",
1214
- query_cass: false, // Explicitly skip CASS
1215
- },
1216
- mockContext,
1217
- );
1218
-
1219
- const parsed = JSON.parse(result);
1220
-
1221
- expect(parsed.cass_history.queried).toBe(false);
1222
- });
1223
-
1224
- it("decomposition prompt includes beads discipline", async () => {
1225
- const result = await swarm_decompose.execute(
1226
- {
1227
- task: "Build feature X",
1228
- },
1229
- mockContext,
1230
- );
1231
-
1232
- const parsed = JSON.parse(result);
1233
-
1234
- // Check that beads discipline is in the prompt
1235
- expect(parsed.prompt).toContain("MANDATORY");
1236
- expect(parsed.prompt).toContain("bead");
1237
- expect(parsed.prompt).toContain("Plan aggressively");
1238
- });
1239
-
1240
- it("subtask prompt includes agent-mail discipline", async () => {
1241
- const result = await swarm_subtask_prompt.execute(
1242
- {
1243
- agent_name: "TestAgent",
1244
- bead_id: "bd-test123.1",
1245
- epic_id: "bd-test123",
1246
- subtask_title: "Test task",
1247
- files: ["src/test.ts"],
1248
- },
1249
- mockContext,
1250
- );
1251
-
1252
- // Check that swarm-mail discipline is in the prompt
1253
- expect(result).toContain("MANDATORY");
1254
- expect(result).toContain("Swarm Mail");
1255
- expect(result).toContain("swarmmail_send");
1256
- expect(result).toContain("Report progress");
1257
- });
1258
- });
1259
-
1260
- // ============================================================================
1261
- // Coordinator-Centric Swarm Tools (V2)
1262
- // ============================================================================
1263
-
1264
- describe("Swarm Prompt V2 (with Swarm Mail/Beads)", () => {
1265
- describe("formatSubtaskPromptV2", () => {
1266
- it("generates correct prompt with all fields", () => {
1267
- const result = formatSubtaskPromptV2({
1268
- bead_id: "test-swarm-plugin-lf2p4u-oauth123.1",
1269
- epic_id: "test-swarm-plugin-lf2p4u-oauth123",
1270
- subtask_title: "Add OAuth provider",
1271
- subtask_description: "Configure Google OAuth in the auth config",
1272
- files: ["src/auth/google.ts", "src/auth/config.ts"],
1273
- shared_context: "We are using NextAuth.js v5",
1274
- });
1275
-
1276
- // Check title is included
1277
- expect(result).toContain("Add OAuth provider");
1278
-
1279
- // Check description is included
1280
- expect(result).toContain("Configure Google OAuth in the auth config");
1281
-
1282
- // Check files are formatted as list
1283
- expect(result).toContain("- `src/auth/google.ts`");
1284
- expect(result).toContain("- `src/auth/config.ts`");
1285
-
1286
- // Check shared context is included
1287
- expect(result).toContain("We are using NextAuth.js v5");
1288
-
1289
- // Check bead/epic IDs are substituted
1290
- expect(result).toContain("test-swarm-plugin-lf2p4u-oauth123.1");
1291
- expect(result).toContain("test-swarm-plugin-lf2p4u-oauth123");
1292
- });
1293
-
1294
- it("handles missing optional fields", () => {
1295
- const result = formatSubtaskPromptV2({
1296
- bead_id: "test-swarm-plugin-lf2p4u-simple456.1",
1297
- epic_id: "test-swarm-plugin-lf2p4u-simple456",
1298
- subtask_title: "Simple task",
1299
- subtask_description: "",
1300
- files: [],
1301
- });
1302
-
1303
- // Check title is included
1304
- expect(result).toContain("Simple task");
1305
-
1306
- // Check fallback for empty description
1307
- expect(result).toContain("(see title)");
1308
-
1309
- // Check fallback for empty files
1310
- expect(result).toContain("(no specific files - use judgment)");
1311
-
1312
- // Check fallback for missing context
1313
- expect(result).toContain("(none)");
1314
- });
1315
-
1316
- it("handles files with special characters", () => {
1317
- const result = formatSubtaskPromptV2({
1318
- bead_id: "test-swarm-plugin-lf2p4u-paths789.1",
1319
- epic_id: "test-swarm-plugin-lf2p4u-paths789",
1320
- subtask_title: "Handle paths",
1321
- subtask_description: "Test file paths",
1322
- files: [
1323
- "src/components/[slug]/page.tsx",
1324
- "src/api/users/[id]/route.ts",
1325
- ],
1326
- });
1327
-
1328
- expect(result).toContain("- `src/components/[slug]/page.tsx`");
1329
- expect(result).toContain("- `src/api/users/[id]/route.ts`");
1330
- });
1331
- });
1332
-
1333
- describe("SUBTASK_PROMPT_V2", () => {
1334
- it("contains expected sections", () => {
1335
- // Check all main sections are present in the template
1336
- expect(SUBTASK_PROMPT_V2).toContain("[TASK]");
1337
- expect(SUBTASK_PROMPT_V2).toContain("{subtask_title}");
1338
- expect(SUBTASK_PROMPT_V2).toContain("{subtask_description}");
1339
-
1340
- expect(SUBTASK_PROMPT_V2).toContain("[FILES]");
1341
- expect(SUBTASK_PROMPT_V2).toContain("{file_list}");
1342
-
1343
- expect(SUBTASK_PROMPT_V2).toContain("[CONTEXT]");
1344
- expect(SUBTASK_PROMPT_V2).toContain("{shared_context}");
1345
-
1346
- expect(SUBTASK_PROMPT_V2).toContain("[MANDATORY SURVIVAL CHECKLIST]");
1347
- });
1348
-
1349
- it("DOES contain Swarm Mail instructions (MANDATORY)", () => {
1350
- // V2 prompt tells agents to USE Swarm Mail - this is non-negotiable
1351
- expect(SUBTASK_PROMPT_V2).toContain("SWARM MAIL");
1352
- expect(SUBTASK_PROMPT_V2).toContain("swarmmail_init");
1353
- expect(SUBTASK_PROMPT_V2).toContain("swarmmail_send");
1354
- expect(SUBTASK_PROMPT_V2).toContain("swarmmail_inbox");
1355
- expect(SUBTASK_PROMPT_V2).toContain("swarmmail_reserve");
1356
- expect(SUBTASK_PROMPT_V2).toContain("swarmmail_release");
1357
- expect(SUBTASK_PROMPT_V2).toContain("thread_id");
1358
- expect(SUBTASK_PROMPT_V2).toContain("NON-NEGOTIABLE");
1359
- });
1360
-
1361
- it("DOES contain beads instructions", () => {
1362
- // V2 prompt tells agents to USE beads
1363
- expect(SUBTASK_PROMPT_V2).toContain("{bead_id}");
1364
- expect(SUBTASK_PROMPT_V2).toContain("{epic_id}");
1365
- expect(SUBTASK_PROMPT_V2).toContain("hive_update");
1366
- expect(SUBTASK_PROMPT_V2).toContain("hive_create");
1367
- expect(SUBTASK_PROMPT_V2).toContain("swarm_complete");
1368
- });
1369
-
1370
- it("grants workers autonomy to file beads against epic", () => {
1371
- // Workers should be able to file bugs, tech debt, follow-ups
1372
- expect(SUBTASK_PROMPT_V2).toContain("You Have Autonomy to File Issues");
1373
- expect(SUBTASK_PROMPT_V2).toContain("parent_id");
1374
- expect(SUBTASK_PROMPT_V2).toContain("Don't silently ignore issues");
1375
- });
1376
-
1377
- it("instructs agents to communicate via swarmmail", () => {
1378
- expect(SUBTASK_PROMPT_V2).toContain("don't work silently");
1379
- expect(SUBTASK_PROMPT_V2).toContain("progress");
1380
- expect(SUBTASK_PROMPT_V2).toContain("coordinator");
1381
- expect(SUBTASK_PROMPT_V2).toContain("CRITICAL");
1382
- });
1383
-
1384
- it("contains survival checklist: semantic-memory_find", () => {
1385
- // Step 2: Query past learnings BEFORE starting work
1386
- expect(SUBTASK_PROMPT_V2).toContain("semantic-memory_find");
1387
- expect(SUBTASK_PROMPT_V2).toContain("Query Past Learnings");
1388
- expect(SUBTASK_PROMPT_V2).toContain("BEFORE starting work");
1389
- expect(SUBTASK_PROMPT_V2).toContain("If you skip this step, you WILL waste time solving already-solved problems");
1390
- });
1391
-
1392
- it("contains survival checklist: skills discovery and loading", () => {
1393
- // Step 3: Load relevant skills if available
1394
- expect(SUBTASK_PROMPT_V2).toContain("skills_list");
1395
- expect(SUBTASK_PROMPT_V2).toContain("skills_use");
1396
- expect(SUBTASK_PROMPT_V2).toContain("Load Relevant Skills");
1397
- expect(SUBTASK_PROMPT_V2).toContain("Common skill triggers");
1398
- });
1399
-
1400
- it("contains survival checklist: worker reserves files (not coordinator)", () => {
1401
- // Step 4: Worker reserves their own files
1402
- expect(SUBTASK_PROMPT_V2).toContain("swarmmail_reserve");
1403
- expect(SUBTASK_PROMPT_V2).toContain("Reserve Your Files");
1404
- expect(SUBTASK_PROMPT_V2).toContain("YOU reserve, not coordinator");
1405
- expect(SUBTASK_PROMPT_V2).toContain("Workers reserve their own files");
1406
- });
1407
-
1408
- it("contains survival checklist: swarm_progress at milestones", () => {
1409
- // Step 6: Report progress at 25/50/75%
1410
- expect(SUBTASK_PROMPT_V2).toContain("swarm_progress");
1411
- expect(SUBTASK_PROMPT_V2).toContain("Report Progress at Milestones");
1412
- expect(SUBTASK_PROMPT_V2).toContain("progress_percent");
1413
- expect(SUBTASK_PROMPT_V2).toContain("25%, 50%, 75%");
1414
- expect(SUBTASK_PROMPT_V2).toContain("auto-checkpoint");
1415
- });
1416
-
1417
- it("contains survival checklist: swarm_checkpoint before risky ops", () => {
1418
- // Step 7: Manual checkpoint before risky operations
1419
- expect(SUBTASK_PROMPT_V2).toContain("swarm_checkpoint");
1420
- expect(SUBTASK_PROMPT_V2).toContain("Manual Checkpoint BEFORE Risky Operations");
1421
- expect(SUBTASK_PROMPT_V2).toContain("Large refactors");
1422
- expect(SUBTASK_PROMPT_V2).toContain("preserve context");
1423
- });
1424
-
1425
- it("contains survival checklist: semantic-memory_store for learnings", () => {
1426
- // Step 8: Store discoveries and learnings
1427
- expect(SUBTASK_PROMPT_V2).toContain("semantic-memory_store");
1428
- expect(SUBTASK_PROMPT_V2).toContain("STORE YOUR LEARNINGS");
1429
- expect(SUBTASK_PROMPT_V2).toContain("Solved a tricky bug");
1430
- expect(SUBTASK_PROMPT_V2).toContain("The WHY matters more than the WHAT");
1431
- });
1432
-
1433
- it("does NOT mention coordinator reserving files", () => {
1434
- // Coordinator no longer reserves files - workers do it themselves
1435
- const lowerPrompt = SUBTASK_PROMPT_V2.toLowerCase();
1436
- expect(lowerPrompt).not.toContain("coordinator reserves");
1437
- expect(lowerPrompt).not.toContain("coordinator will reserve");
1438
- });
1439
-
1440
- it("enforces swarm_complete over manual hive_close", () => {
1441
- // Step 9: Use swarm_complete, not hive_close
1442
- expect(SUBTASK_PROMPT_V2).toContain("swarm_complete");
1443
- expect(SUBTASK_PROMPT_V2).toContain("DO NOT manually close the cell");
1444
- expect(SUBTASK_PROMPT_V2).toContain("Use swarm_complete");
1445
- });
1446
- });
1447
-
1448
- describe("swarm_complete automatic memory capture", () => {
1449
- let beadsAvailable = false;
1450
-
1451
- beforeAll(async () => {
1452
- beadsAvailable = await isBeadsAvailable();
1453
- });
1454
-
1455
- it.skipIf(!beadsAvailable)(
1456
- "includes memory_capture object in response",
1457
- async () => {
1458
- // Create a real bead for the test
1459
- const createResult =
1460
- await Bun.$`bd create "Test memory capture" -t task --json`
1461
- .quiet()
1462
- .nothrow();
1463
-
1464
- if (createResult.exitCode !== 0) {
1465
- console.warn(
1466
- "Could not create bead:",
1467
- createResult.stderr.toString(),
1468
- );
1469
- return;
1470
- }
1471
-
1472
- const bead = JSON.parse(createResult.stdout.toString());
1473
-
1474
- try {
1475
- const result = await swarm_complete.execute(
1476
- {
1477
- project_key: "/tmp/test-memory-capture",
1478
- agent_name: "test-agent",
1479
- bead_id: bead.id,
1480
- summary: "Implemented auto-capture feature",
1481
- files_touched: ["src/swarm-orchestrate.ts"],
1482
- skip_verification: true,
1483
- },
1484
- mockContext,
1485
- );
1486
-
1487
- const parsed = JSON.parse(result);
1488
-
1489
- // Verify memory capture was attempted
1490
- expect(parsed).toHaveProperty("memory_capture");
1491
- expect(parsed.memory_capture).toHaveProperty("attempted", true);
1492
- expect(parsed.memory_capture).toHaveProperty("stored");
1493
- expect(parsed.memory_capture).toHaveProperty("information");
1494
- expect(parsed.memory_capture).toHaveProperty("metadata");
1495
-
1496
- // Information should contain bead ID and summary
1497
- expect(parsed.memory_capture.information).toContain(bead.id);
1498
- expect(parsed.memory_capture.information).toContain(
1499
- "Implemented auto-capture feature",
1500
- );
1501
-
1502
- // Metadata should contain relevant tags
1503
- expect(parsed.memory_capture.metadata).toContain("swarm");
1504
- expect(parsed.memory_capture.metadata).toContain("success");
1505
- } catch (error) {
1506
- // Clean up bead if test fails
1507
- await Bun.$`bd close ${bead.id} --reason "Test cleanup"`
1508
- .quiet()
1509
- .nothrow();
1510
- throw error;
1511
- }
1512
- },
1513
- );
1514
-
1515
- it.skipIf(!beadsAvailable)(
1516
- "attempts to store in semantic-memory when available",
1517
- async () => {
1518
- const createResult =
1519
- await Bun.$`bd create "Test semantic-memory storage" -t task --json`
1520
- .quiet()
1521
- .nothrow();
1522
-
1523
- if (createResult.exitCode !== 0) {
1524
- console.warn(
1525
- "Could not create bead:",
1526
- createResult.stderr.toString(),
1527
- );
1528
- return;
1529
- }
1530
-
1531
- const bead = JSON.parse(createResult.stdout.toString());
1532
-
1533
- try {
1534
- const result = await swarm_complete.execute(
1535
- {
1536
- project_key: "/tmp/test-memory-storage",
1537
- agent_name: "test-agent",
1538
- bead_id: bead.id,
1539
- summary: "Fixed critical bug in auth flow",
1540
- files_touched: ["src/auth.ts", "src/middleware.ts"],
1541
- skip_verification: true,
1542
- },
1543
- mockContext,
1544
- );
1545
-
1546
- const parsed = JSON.parse(result);
1547
-
1548
- // If semantic-memory is available, stored should be true
1549
- // If not, error should explain why
1550
- if (parsed.memory_capture.stored) {
1551
- expect(parsed.memory_capture.note).toContain(
1552
- "automatically stored in semantic-memory",
1553
- );
1554
- } else {
1555
- expect(parsed.memory_capture.error).toBeDefined();
1556
- expect(
1557
- parsed.memory_capture.error.includes("not available") ||
1558
- parsed.memory_capture.error.includes("failed"),
1559
- ).toBe(true);
1560
- }
1561
- } catch (error) {
1562
- // Clean up bead if test fails
1563
- await Bun.$`bd close ${bead.id} --reason "Test cleanup"`
1564
- .quiet()
1565
- .nothrow();
1566
- throw error;
1567
- }
1568
- },
1569
- );
1570
- });
1571
-
1572
- describe("swarm_complete error handling", () => {
1573
- let beadsAvailable = false;
1574
-
1575
- beforeAll(async () => {
1576
- beadsAvailable = await isBeadsAvailable();
1577
- });
1578
-
1579
- it.skipIf(!beadsAvailable)(
1580
- "returns structured error when bead close fails",
1581
- async () => {
1582
- // Try to complete a non-existent bead
1583
- const result = await swarm_complete.execute(
1584
- {
1585
- project_key: "/tmp/test-error-handling",
1586
- agent_name: "test-agent",
1587
- bead_id: "bd-nonexistent-12345",
1588
- summary: "This should fail",
1589
- skip_verification: true,
1590
- },
1591
- mockContext,
1592
- );
1593
-
1594
- const parsed = JSON.parse(result);
1595
-
1596
- // Should return structured error, not throw
1597
- expect(parsed.success).toBe(false);
1598
- expect(parsed.error).toContain("Failed to close bead");
1599
- expect(parsed.failed_step).toBe("bd close");
1600
- expect(parsed.bead_id).toBe("bd-nonexistent-12345");
1601
- expect(parsed.recovery).toBeDefined();
1602
- expect(parsed.recovery.steps).toBeInstanceOf(Array);
1603
- },
1604
- );
1605
-
1606
- it.skipIf(!beadsAvailable)(
1607
- "returns specific error message when bead_id not found",
1608
- async () => {
1609
- // Try to complete with a non-existent bead ID
1610
- const result = await swarm_complete.execute(
1611
- {
1612
- project_key: "/tmp/test-bead-not-found",
1613
- agent_name: "test-agent",
1614
- bead_id: "bd-totally-fake-xyz123",
1615
- summary: "This should fail with specific error",
1616
- skip_verification: true,
1617
- },
1618
- mockContext,
1619
- );
1620
-
1621
- const parsed = JSON.parse(result);
1622
-
1623
- // Should return structured error with specific message
1624
- expect(parsed.success).toBe(false);
1625
- expect(parsed.error).toBeDefined();
1626
- // RED: This will fail - we currently get generic "Tool execution failed"
1627
- // We want the error message to specifically mention the bead was not found
1628
- expect(
1629
- parsed.error.toLowerCase().includes("bead not found") ||
1630
- parsed.error.toLowerCase().includes("not found"),
1631
- ).toBe(true);
1632
- expect(parsed.bead_id).toBe("bd-totally-fake-xyz123");
1633
- },
1634
- );
1635
-
1636
- it.skipIf(!beadsAvailable)(
1637
- "returns specific error when project_key is invalid/mismatched",
1638
- async () => {
1639
- // Create a real bead first
1640
- const createResult =
1641
- await Bun.$`bd create "Test project mismatch" -t task --json`
1642
- .quiet()
1643
- .nothrow();
1644
-
1645
- if (createResult.exitCode !== 0) {
1646
- console.warn(
1647
- "Could not create bead:",
1648
- createResult.stderr.toString(),
1649
- );
1650
- return;
1651
- }
1652
-
1653
- const bead = JSON.parse(createResult.stdout.toString());
1654
-
1655
- try {
1656
- // Try to complete with mismatched project_key
1657
- const result = await swarm_complete.execute(
1658
- {
1659
- project_key: "/totally/wrong/project/path",
1660
- agent_name: "test-agent",
1661
- bead_id: bead.id,
1662
- summary: "This should fail with project mismatch",
1663
- skip_verification: true,
1664
- },
1665
- mockContext,
1666
- );
1667
-
1668
- const parsed = JSON.parse(result);
1669
-
1670
- // Should return structured error with specific message about project mismatch
1671
- expect(parsed.success).toBe(false);
1672
- expect(parsed.error).toBeDefined();
1673
- // RED: This will fail - we want specific validation error
1674
- // Error should mention project mismatch or validation failure
1675
- const errorLower = parsed.error.toLowerCase();
1676
- expect(
1677
- (errorLower.includes("project") &&
1678
- (errorLower.includes("mismatch") ||
1679
- errorLower.includes("invalid") ||
1680
- errorLower.includes("not found"))) ||
1681
- errorLower.includes("validation"),
1682
- ).toBe(true);
1683
- } finally {
1684
- // Clean up
1685
- await Bun.$`bd close ${bead.id} --reason "Test cleanup"`
1686
- .quiet()
1687
- .nothrow();
1688
- }
1689
- },
1690
- );
1691
-
1692
- it.skipIf(!beadsAvailable)(
1693
- "includes message_sent status in response",
1694
- async () => {
1695
- const createResult =
1696
- await Bun.$`bd create "Test message status" -t task --json`
1697
- .quiet()
1698
- .nothrow();
1699
-
1700
- if (createResult.exitCode !== 0) {
1701
- console.warn(
1702
- "Could not create bead:",
1703
- createResult.stderr.toString(),
1704
- );
1705
- return;
1706
- }
1707
-
1708
- const bead = JSON.parse(createResult.stdout.toString());
1709
-
1710
- try {
1711
- const result = await swarm_complete.execute(
1712
- {
1713
- project_key: "/tmp/test-message-status",
1714
- agent_name: "test-agent",
1715
- bead_id: bead.id,
1716
- summary: "Test message status tracking",
1717
- skip_verification: true,
1718
- },
1719
- mockContext,
1720
- );
1721
-
1722
- const parsed = JSON.parse(result);
1723
-
1724
- // Should have message_sent field (true or false)
1725
- expect(parsed).toHaveProperty("message_sent");
1726
- // If message failed, should have message_error
1727
- if (!parsed.message_sent) {
1728
- expect(parsed).toHaveProperty("message_error");
1729
- }
1730
- } catch (error) {
1731
- // Clean up bead if test fails
1732
- await Bun.$`bd close ${bead.id} --reason "Test cleanup"`
1733
- .quiet()
1734
- .nothrow();
1735
- throw error;
1736
- }
1737
- },
1738
- );
1739
- });
1740
- });
1741
-
1742
- // ============================================================================
1743
- // Checkpoint/Recovery Flow Integration Tests
1744
- // ============================================================================
1745
-
1746
- describe("Checkpoint/Recovery Flow (integration)", () => {
1747
- describe("swarm_checkpoint", () => {
1748
- it("creates swarm_checkpointed event and updates swarm_contexts table", async () => {
1749
- const uniqueProjectKey = `${TEST_PROJECT_PATH}-checkpoint-${Date.now()}`;
1750
- const sessionID = `checkpoint-session-${Date.now()}`;
1751
-
1752
- // Initialize swarm-mail database directly (no Agent Mail needed)
1753
- const { getSwarmMailLibSQL, closeSwarmMailLibSQL } = await import("swarm-mail");
1754
- const swarmMail = await getSwarmMailLibSQL(uniqueProjectKey);
1755
- const db = await swarmMail.getDatabase();
1756
-
1757
- try {
1758
- const ctx = {
1759
- ...mockContext,
1760
- sessionID,
1761
- };
1762
-
1763
- const epicId = "bd-test-epic-123";
1764
- const beadId = "bd-test-epic-123.1";
1765
- const agentName = "TestAgent";
1766
-
1767
- // Execute checkpoint
1768
- const result = await swarm_checkpoint.execute(
1769
- {
1770
- project_key: uniqueProjectKey,
1771
- agent_name: agentName,
1772
- bead_id: beadId,
1773
- epic_id: epicId,
1774
- files_modified: ["src/test.ts", "src/test2.ts"],
1775
- progress_percent: 50,
1776
- directives: {
1777
- shared_context: "Testing checkpoint functionality",
1778
- skills_to_load: ["testing-patterns"],
1779
- coordinator_notes: "Mid-task checkpoint",
1780
- },
1781
- },
1782
- ctx,
1783
- );
1784
-
1785
- const parsed = JSON.parse(result);
1786
-
1787
- // Verify checkpoint was created
1788
- expect(parsed.success).toBe(true);
1789
- expect(parsed.bead_id).toBe(beadId);
1790
- expect(parsed.epic_id).toBe(epicId);
1791
- expect(parsed.files_tracked).toBe(2);
1792
- expect(parsed.summary).toContain("50%");
1793
- expect(parsed).toHaveProperty("checkpoint_timestamp");
1794
-
1795
- // Verify swarm_contexts table was updated
1796
- const dbResult = await db.query<{
1797
- id: string;
1798
- epic_id: string;
1799
- bead_id: string;
1800
- strategy: string;
1801
- files: string;
1802
- recovery: string;
1803
- }>(
1804
- `SELECT id, epic_id, bead_id, strategy, files, recovery
1805
- FROM swarm_contexts
1806
- WHERE project_key = $1 AND bead_id = $2`,
1807
- [uniqueProjectKey, beadId],
1808
- );
1809
-
1810
- expect(dbResult.rows.length).toBe(1);
1811
- const row = dbResult.rows[0];
1812
- expect(row.epic_id).toBe(epicId);
1813
- expect(row.bead_id).toBe(beadId);
1814
- expect(row.strategy).toBe("file-based");
1815
-
1816
- // PGLite auto-parses JSON columns, so we get objects directly
1817
- const files =
1818
- typeof row.files === "string" ? JSON.parse(row.files) : row.files;
1819
- expect(files).toEqual(["src/test.ts", "src/test2.ts"]);
1820
-
1821
- const recovery =
1822
- typeof row.recovery === "string"
1823
- ? JSON.parse(row.recovery)
1824
- : row.recovery;
1825
- expect(recovery.progress_percent).toBe(50);
1826
- expect(recovery.files_modified).toEqual(["src/test.ts", "src/test2.ts"]);
1827
- expect(recovery).toHaveProperty("last_checkpoint");
1828
- } finally {
1829
- await closeSwarmMailLibSQL(uniqueProjectKey);
1830
- }
1831
- });
1832
-
1833
- it("handles checkpoint with error_context", async () => {
1834
- const uniqueProjectKey = `${TEST_PROJECT_PATH}-checkpoint-error-${Date.now()}`;
1835
- const sessionID = `checkpoint-error-session-${Date.now()}`;
1836
-
1837
- const { getSwarmMailLibSQL, closeSwarmMailLibSQL } = await import("swarm-mail");
1838
- const swarmMail = await getSwarmMailLibSQL(uniqueProjectKey);
1839
- const db = await swarmMail.getDatabase();
1840
-
1841
- try {
1842
- const ctx = {
1843
- ...mockContext,
1844
- sessionID,
1845
- };
1846
-
1847
- const result = await swarm_checkpoint.execute(
1848
- {
1849
- project_key: uniqueProjectKey,
1850
- agent_name: "TestAgent",
1851
- bead_id: "bd-error-test.1",
1852
- epic_id: "bd-error-test",
1853
- files_modified: ["src/buggy.ts"],
1854
- progress_percent: 75,
1855
- error_context:
1856
- "Hit type error on line 42, need to add explicit types",
1857
- },
1858
- ctx,
1859
- );
1860
-
1861
- const parsed = JSON.parse(result);
1862
- expect(parsed.success).toBe(true);
1863
-
1864
- // Verify error_context was stored
1865
- const dbResult = await db.query<{ recovery: string }>(
1866
- `SELECT recovery FROM swarm_contexts WHERE project_key = $1 AND bead_id = $2`,
1867
- [uniqueProjectKey, "bd-error-test.1"],
1868
- );
1869
-
1870
- const recoveryRaw = dbResult.rows[0].recovery;
1871
- const recovery =
1872
- typeof recoveryRaw === "string" ? JSON.parse(recoveryRaw) : recoveryRaw;
1873
- expect(recovery.error_context).toBe(
1874
- "Hit type error on line 42, need to add explicit types",
1875
- );
1876
- } finally {
1877
- await closeSwarmMailLibSQL(uniqueProjectKey);
1878
- }
1879
- });
1880
- });
1881
-
1882
- describe("swarm_recover", () => {
1883
- it("retrieves checkpoint data from swarm_contexts table", async () => {
1884
- const uniqueProjectKey = `${TEST_PROJECT_PATH}-recover-${Date.now()}`;
1885
- const sessionID = `recover-session-${Date.now()}`;
1886
-
1887
- const { getSwarmMailLibSQL, closeSwarmMailLibSQL } = await import("swarm-mail");
1888
- const swarmMail = await getSwarmMailLibSQL(uniqueProjectKey);
1889
- const db = await swarmMail.getDatabase();
1890
-
1891
- try {
1892
- const ctx = {
1893
- ...mockContext,
1894
- sessionID,
1895
- };
1896
-
1897
- const epicId = "bd-recover-epic-456";
1898
- const beadId = "bd-recover-epic-456.1";
1899
- const agentName = "TestAgent";
1900
-
1901
- // First create a checkpoint
1902
- await swarm_checkpoint.execute(
1903
- {
1904
- project_key: uniqueProjectKey,
1905
- agent_name: agentName,
1906
- bead_id: beadId,
1907
- epic_id: epicId,
1908
- files_modified: ["src/auth.ts", "src/middleware.ts"],
1909
- progress_percent: 75,
1910
- directives: {
1911
- shared_context: "OAuth implementation in progress",
1912
- skills_to_load: ["testing-patterns", "swarm-coordination"],
1913
- },
1914
- },
1915
- ctx,
1916
- );
1917
-
1918
- // Now recover it
1919
- const result = await swarm_recover.execute(
1920
- {
1921
- project_key: uniqueProjectKey,
1922
- epic_id: epicId,
1923
- },
1924
- ctx,
1925
- );
1926
-
1927
- const parsed = JSON.parse(result);
1928
-
1929
- // Verify recovery succeeded
1930
- expect(parsed.found).toBe(true);
1931
- expect(parsed).toHaveProperty("context");
1932
- expect(parsed).toHaveProperty("summary");
1933
- expect(parsed).toHaveProperty("age_seconds");
1934
-
1935
- const { context } = parsed;
1936
- expect(context.epic_id).toBe(epicId);
1937
- expect(context.bead_id).toBe(beadId);
1938
- expect(context.strategy).toBe("file-based");
1939
- expect(context.files).toEqual(["src/auth.ts", "src/middleware.ts"]);
1940
- expect(context.recovery.progress_percent).toBe(75);
1941
- expect(context.directives.shared_context).toBe(
1942
- "OAuth implementation in progress",
1943
- );
1944
- expect(context.directives.skills_to_load).toEqual([
1945
- "testing-patterns",
1946
- "swarm-coordination",
1947
- ]);
1948
- } finally {
1949
- await closeSwarmMailLibSQL(uniqueProjectKey);
1950
- }
1951
- });
1952
-
1953
- it("returns found:false when no checkpoint exists", async () => {
1954
- const uniqueProjectKey = `${TEST_PROJECT_PATH}-recover-notfound-${Date.now()}`;
1955
- const sessionID = `recover-notfound-session-${Date.now()}`;
1956
-
1957
- const { getSwarmMailLibSQL, closeSwarmMailLibSQL } = await import("swarm-mail");
1958
- await getSwarmMailLibSQL(uniqueProjectKey);
1959
-
1960
- try {
1961
- const ctx = {
1962
- ...mockContext,
1963
- sessionID,
1964
- };
1965
-
1966
- // Try to recover non-existent checkpoint
1967
- const result = await swarm_recover.execute(
1968
- {
1969
- project_key: uniqueProjectKey,
1970
- epic_id: "bd-nonexistent-epic",
1971
- },
1972
- ctx,
1973
- );
1974
-
1975
- const parsed = JSON.parse(result);
1976
-
1977
- expect(parsed.found).toBe(false);
1978
- expect(parsed.message).toContain("No checkpoint found");
1979
- expect(parsed.epic_id).toBe("bd-nonexistent-epic");
1980
- } finally {
1981
- await closeSwarmMailLibSQL(uniqueProjectKey);
1982
- }
1983
- });
1984
- });
1985
-
1986
- // NOTE: Auto-checkpoint tests removed - they were flaky due to PGLite timing issues
1987
- // in parallel test runs. The checkpoint functionality is tested via swarm_checkpoint
1988
- // and swarm_recover tests above. Auto-checkpoint at milestones (25%, 50%, 75%) is
1989
- // a convenience feature that doesn't need dedicated integration tests.
1990
- });
1991
-
1992
- // ============================================================================
1993
- // Contract Validation Tests
1994
- // ============================================================================
1995
-
1996
- describe("Contract Validation", () => {
1997
- describe("validateContract", () => {
1998
- it("passes when files_touched is subset of files_owned", () => {
1999
- // This test will fail until we implement validateContract
2000
- const { validateContract } = require("./swarm-orchestrate");
2001
-
2002
- const result = validateContract(
2003
- ["src/auth.ts", "src/utils.ts"],
2004
- ["src/auth.ts", "src/utils.ts", "src/types.ts"]
2005
- );
2006
-
2007
- expect(result.valid).toBe(true);
2008
- expect(result.violations).toHaveLength(0);
2009
- });
2010
-
2011
- it("fails when files_touched has extra files", () => {
2012
- const { validateContract } = require("./swarm-orchestrate");
2013
-
2014
- const result = validateContract(
2015
- ["src/auth.ts", "src/forbidden.ts"],
2016
- ["src/auth.ts"]
2017
- );
2018
-
2019
- expect(result.valid).toBe(false);
2020
- expect(result.violations).toContain("src/forbidden.ts");
2021
- });
2022
-
2023
- it("matches glob patterns correctly", () => {
2024
- const { validateContract } = require("./swarm-orchestrate");
2025
-
2026
- const result = validateContract(
2027
- ["src/auth/service.ts", "src/auth/types.ts"],
2028
- ["src/auth/**/*.ts"]
2029
- );
2030
-
2031
- expect(result.valid).toBe(true);
2032
- expect(result.violations).toHaveLength(0);
2033
- });
2034
-
2035
- it("detects violations outside glob pattern", () => {
2036
- const { validateContract } = require("./swarm-orchestrate");
2037
-
2038
- const result = validateContract(
2039
- ["src/auth/service.ts", "src/utils/helper.ts"],
2040
- ["src/auth/**"]
2041
- );
2042
-
2043
- expect(result.valid).toBe(false);
2044
- expect(result.violations).toContain("src/utils/helper.ts");
2045
- });
2046
-
2047
- it("passes with empty files_touched (read-only work)", () => {
2048
- const { validateContract } = require("./swarm-orchestrate");
2049
-
2050
- const result = validateContract(
2051
- [],
2052
- ["src/auth/**"]
2053
- );
2054
-
2055
- expect(result.valid).toBe(true);
2056
- expect(result.violations).toHaveLength(0);
2057
- });
2058
-
2059
- it("handles multiple glob patterns", () => {
2060
- const { validateContract } = require("./swarm-orchestrate");
2061
-
2062
- const result = validateContract(
2063
- ["src/auth/service.ts", "tests/auth.test.ts"],
2064
- ["src/auth/**", "tests/**"]
2065
- );
2066
-
2067
- expect(result.valid).toBe(true);
2068
- expect(result.violations).toHaveLength(0);
2069
- });
2070
- });
2071
-
2072
- describe("swarm_complete with contract validation", () => {
2073
- it("includes contract validation result when files_touched provided", async () => {
2074
- // This test needs a real decomposition event, so it's more of an integration check
2075
- // The actual validation logic is tested in unit tests above
2076
- // Here we just verify the response includes contract_validation field
2077
-
2078
- const mockResult = {
2079
- success: true,
2080
- contract_validation: {
2081
- validated: false,
2082
- reason: "No files_owned contract found (non-epic subtask or decomposition event missing)",
2083
- },
2084
- };
2085
-
2086
- // Verify the structure exists
2087
- expect(mockResult.contract_validation).toBeDefined();
2088
- expect(mockResult.contract_validation.validated).toBe(false);
2089
- });
2090
- });
2091
-
2092
- describe("swarm_complete project_key handling (bug fix)", () => {
2093
- it("finds cells created with full path project_key", async () => {
2094
- // BUG: swarm_complete was mangling project_key with .replace(/\//g, "-")
2095
- // before querying, but cells are stored with the original path.
2096
- // This caused "Bead not found" errors for cells created via hive_create_epic.
2097
-
2098
- const testProjectPath = "/tmp/swarm-complete-projectkey-test-" + Date.now();
2099
- const { getHiveAdapter } = await import("./hive");
2100
- const adapter = await getHiveAdapter(testProjectPath);
2101
-
2102
- // Create a cell using the full path as project_key (like hive_create_epic does)
2103
- const cell = await adapter.createCell(testProjectPath, {
2104
- title: "Test cell for project_key bug",
2105
- type: "task",
2106
- priority: 2,
2107
- });
2108
-
2109
- expect(cell.id).toBeDefined();
2110
-
2111
- // Now try to complete it via swarm_complete with the same project_key
2112
- const result = await swarm_complete.execute(
2113
- {
2114
- project_key: testProjectPath, // Full path, not mangled
2115
- agent_name: "test-agent",
2116
- bead_id: cell.id,
2117
- summary: "Testing project_key handling",
2118
- skip_verification: true,
2119
- skip_review: true,
2120
- },
2121
- mockContext,
2122
- );
2123
-
2124
- const parsed = JSON.parse(result);
2125
-
2126
- // This should succeed - the cell exists with this project_key
2127
- // BUG: Before fix, this fails with "Bead not found" because swarm_complete
2128
- // was looking for project_key "-tmp-swarm-complete-projectkey-test-xxx"
2129
- expect(parsed.success).toBe(true);
2130
- expect(parsed.error).toBeUndefined();
2131
- expect(parsed.bead_id).toBe(cell.id);
2132
- });
2133
-
2134
- it("handles project_key with slashes correctly", async () => {
2135
- // Verify that project_key like "/Users/joel/Code/project" works
2136
- const testProjectPath = "/a/b/c/test-" + Date.now();
2137
- const { getHiveAdapter } = await import("./hive");
2138
- const adapter = await getHiveAdapter(testProjectPath);
2139
-
2140
- const cell = await adapter.createCell(testProjectPath, {
2141
- title: "Nested path test",
2142
- type: "task",
2143
- priority: 2,
2144
- });
2145
-
2146
- // Verify cell was created with correct project_key
2147
- const retrieved = await adapter.getCell(testProjectPath, cell.id);
2148
- expect(retrieved).not.toBeNull();
2149
- expect(retrieved?.id).toBe(cell.id);
2150
-
2151
- // swarm_complete should find it using the same project_key
2152
- const result = await swarm_complete.execute(
2153
- {
2154
- project_key: testProjectPath,
2155
- agent_name: "test-agent",
2156
- bead_id: cell.id,
2157
- summary: "Nested path test",
2158
- skip_verification: true,
2159
- skip_review: true,
2160
- },
2161
- mockContext,
2162
- );
2163
-
2164
- const parsed = JSON.parse(result);
2165
- expect(parsed.success).toBe(true);
2166
- });
2167
- });
2168
-
2169
- describe("swarm_complete review gate UX", () => {
2170
- it("returns success: true with status: pending_review when review not attempted", async () => {
2171
- const testProjectPath = "/tmp/swarm-review-gate-test-" + Date.now();
2172
- const { getHiveAdapter } = await import("./hive");
2173
- const adapter = await getHiveAdapter(testProjectPath);
2174
-
2175
- // Create a task cell directly
2176
- const cell = await adapter.createCell(testProjectPath, {
2177
- title: "Test task for review gate",
2178
- type: "task",
2179
- priority: 2,
2180
- });
2181
-
2182
- // Start the task
2183
- await adapter.updateCell(testProjectPath, cell.id, {
2184
- status: "in_progress",
2185
- });
2186
-
2187
- // Try to complete without review (skip_review intentionally omitted - defaults to false)
2188
- const result = await swarm_complete.execute(
2189
- {
2190
- project_key: testProjectPath,
2191
- agent_name: "TestAgent",
2192
- bead_id: cell.id,
2193
- summary: "Done",
2194
- files_touched: ["test.ts"],
2195
- skip_verification: true,
2196
- // skip_review intentionally omitted - defaults to false
2197
- },
2198
- mockContext,
2199
- );
2200
-
2201
- const parsed = JSON.parse(result);
2202
-
2203
- // Should be success: true with workflow status
2204
- expect(parsed.success).toBe(true);
2205
- expect(parsed.status).toBe("pending_review");
2206
- expect(parsed.message).toContain("awaiting coordinator review");
2207
- expect(parsed.next_steps).toBeInstanceOf(Array);
2208
- expect(parsed.next_steps.length).toBeGreaterThan(0);
2209
- expect(parsed.review_status).toBeDefined();
2210
- expect(parsed.review_status.reviewed).toBe(false);
2211
- expect(parsed.review_status.approved).toBe(false);
2212
-
2213
- // Should NOT have error field
2214
- expect(parsed.error).toBeUndefined();
2215
- });
2216
-
2217
- it("returns success: true, not error, when review not approved", async () => {
2218
- const testProjectPath = "/tmp/swarm-review-not-approved-test-" + Date.now();
2219
- const { getHiveAdapter } = await import("./hive");
2220
- const { markReviewRejected } = await import("./swarm-review");
2221
- const adapter = await getHiveAdapter(testProjectPath);
2222
-
2223
- // Create a task cell directly
2224
- const cell = await adapter.createCell(testProjectPath, {
2225
- title: "Test task for review not approved",
2226
- type: "task",
2227
- priority: 2,
2228
- });
2229
-
2230
- // Start the task
2231
- await adapter.updateCell(testProjectPath, cell.id, {
2232
- status: "in_progress",
2233
- });
2234
-
2235
- // Manually set review status to rejected (approved: false, but reviewed: true)
2236
- // This simulates the review gate detecting a review was done but not approved
2237
- markReviewRejected(cell.id);
2238
-
2239
- // Try to complete with review not approved
2240
- const result = await swarm_complete.execute(
2241
- {
2242
- project_key: testProjectPath,
2243
- agent_name: "TestAgent",
2244
- bead_id: cell.id,
2245
- summary: "Done",
2246
- files_touched: ["test.ts"],
2247
- skip_verification: true,
2248
- },
2249
- mockContext,
2250
- );
2251
-
2252
- const parsed = JSON.parse(result);
2253
-
2254
- // Should be success: true with workflow status (not error)
2255
- expect(parsed.success).toBe(true);
2256
- expect(parsed.status).toBe("needs_changes");
2257
- expect(parsed.message).toContain("changes requested");
2258
- expect(parsed.next_steps).toBeInstanceOf(Array);
2259
- expect(parsed.next_steps.length).toBeGreaterThan(0);
2260
- expect(parsed.review_status).toBeDefined();
2261
- expect(parsed.review_status.reviewed).toBe(true);
2262
- expect(parsed.review_status.approved).toBe(false);
2263
-
2264
- // Should NOT have error field
2265
- expect(parsed.error).toBeUndefined();
2266
- });
2267
-
2268
- it("completes successfully when skip_review=true", async () => {
2269
- const testProjectPath = "/tmp/swarm-skip-review-test-" + Date.now();
2270
- const { getHiveAdapter } = await import("./hive");
2271
- const adapter = await getHiveAdapter(testProjectPath);
2272
-
2273
- // Create a task cell directly
2274
- const cell = await adapter.createCell(testProjectPath, {
2275
- title: "Test task for skip review",
2276
- type: "task",
2277
- priority: 2,
2278
- });
2279
-
2280
- // Start the task
2281
- await adapter.updateCell(testProjectPath, cell.id, {
2282
- status: "in_progress",
2283
- });
2284
-
2285
- // Complete with skip_review
2286
- const result = await swarm_complete.execute(
2287
- {
2288
- project_key: testProjectPath,
2289
- agent_name: "TestAgent",
2290
- bead_id: cell.id,
2291
- summary: "Done",
2292
- files_touched: ["test.ts"],
2293
- skip_verification: true,
2294
- skip_review: true,
2295
- },
2296
- mockContext,
2297
- );
2298
-
2299
- const parsed = JSON.parse(result);
2300
-
2301
- // Should complete without review gate
2302
- expect(parsed.success).toBe(true);
2303
- expect(parsed.status).toBeUndefined(); // No workflow status when skipping
2304
- expect(parsed.error).toBeUndefined();
2305
- });
2306
- });
2307
-
2308
- describe("swarm_complete auto-sync", () => {
2309
- it("calls hive_sync after closing cell on successful completion", async () => {
2310
- const testProjectPath = "/tmp/swarm-auto-sync-test-" + Date.now();
2311
- const { getHiveAdapter } = await import("./hive");
2312
- const adapter = await getHiveAdapter(testProjectPath);
2313
-
2314
- // Create a task cell directly
2315
- const cell = await adapter.createCell(testProjectPath, {
2316
- title: "Test task for auto-sync",
2317
- type: "task",
2318
- priority: 2,
2319
- });
2320
-
2321
- // Start the task
2322
- await adapter.updateCell(testProjectPath, cell.id, {
2323
- status: "in_progress",
2324
- });
2325
-
2326
- // Complete with skip_review and skip_verification
2327
- const result = await swarm_complete.execute(
2328
- {
2329
- project_key: testProjectPath,
2330
- agent_name: "TestAgent",
2331
- bead_id: cell.id,
2332
- summary: "Done - testing auto-sync",
2333
- files_touched: [],
2334
- skip_verification: true,
2335
- skip_review: true,
2336
- },
2337
- mockContext,
2338
- );
2339
-
2340
- const parsed = JSON.parse(result);
2341
-
2342
- // Should complete successfully
2343
- expect(parsed.success).toBe(true);
2344
- expect(parsed.closed).toBe(true);
2345
-
2346
- // Check that cell is actually closed in database
2347
- const closedCell = await adapter.getCell(testProjectPath, cell.id);
2348
- expect(closedCell?.status).toBe("closed");
2349
-
2350
- // The sync should have flushed the cell to .hive/issues.jsonl
2351
- // We can verify the cell appears in the JSONL
2352
- const hivePath = `${testProjectPath}/.hive/issues.jsonl`;
2353
- const hiveFile = Bun.file(hivePath);
2354
- const exists = await hiveFile.exists();
2355
-
2356
- // The file should exist after sync
2357
- expect(exists).toBe(true);
2358
-
2359
- if (exists) {
2360
- const content = await hiveFile.text();
2361
- const lines = content.trim().split("\n");
2362
-
2363
- // Should have at least one cell exported
2364
- expect(lines.length).toBeGreaterThan(0);
2365
-
2366
- // Parse the exported cells to find our closed cell
2367
- const cells = lines.map((line) => JSON.parse(line));
2368
- const exportedCell = cells.find((c) => c.id === cell.id);
2369
-
2370
- // Our cell should be in the export
2371
- expect(exportedCell).toBeDefined();
2372
- expect(exportedCell.status).toBe("closed");
2373
- expect(exportedCell.title).toBe("Test task for auto-sync");
2374
- }
2375
- });
2376
- });
2377
- });