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,1270 +0,0 @@
1
- /**
2
- * Tests for swarm-prompts.ts
3
- *
4
- * Validates that prompt templates contain required sections and emphasis
5
- * for memory usage, coordination, and TDD workflow.
6
- */
7
-
8
- import { describe, expect, test } from "bun:test";
9
- import {
10
- formatSubtaskPromptV2,
11
- formatResearcherPrompt,
12
- formatCoordinatorPrompt,
13
- SUBTASK_PROMPT_V2,
14
- RESEARCHER_PROMPT,
15
- COORDINATOR_PROMPT,
16
- } from "./swarm-prompts";
17
-
18
- describe("SUBTASK_PROMPT_V2", () => {
19
- describe("memory query emphasis", () => {
20
- test("Step 2 is semantic-memory_find and marked MANDATORY", () => {
21
- expect(SUBTASK_PROMPT_V2).toContain("### Step 2:");
22
- expect(SUBTASK_PROMPT_V2).toContain("semantic-memory_find");
23
- // Must have MANDATORY in the step header
24
- expect(SUBTASK_PROMPT_V2).toMatch(/### Step 2:.*MANDATORY/i);
25
- });
26
-
27
- test("memory query step has visual emphasis (emoji or caps)", () => {
28
- // Should have emoji or CRITICAL/ALWAYS in caps
29
- const step2Match = SUBTASK_PROMPT_V2.match(/### Step 2:[\s\S]*?### Step 3:/);
30
- expect(step2Match).not.toBeNull();
31
- if (!step2Match) return;
32
- const step2Content = step2Match[0];
33
-
34
- // Must have at least one of: emoji, CRITICAL, ALWAYS, MANDATORY
35
- const hasEmphasis =
36
- /🧠|⚠️|CRITICAL|ALWAYS|MANDATORY/.test(step2Content);
37
- expect(hasEmphasis).toBe(true);
38
- });
39
-
40
- test("memory query step includes query examples by task type", () => {
41
- const step2Match = SUBTASK_PROMPT_V2.match(/### Step 2:[\s\S]*?### Step 3:/);
42
- expect(step2Match).not.toBeNull();
43
- if (!step2Match) return;
44
- const step2Content = step2Match[0];
45
-
46
- // Should have examples for different task types
47
- expect(step2Content).toContain("Bug fix");
48
- expect(step2Content).toContain("New feature");
49
- expect(step2Content).toContain("Refactor");
50
- });
51
-
52
- test("memory query step explains WHY it's mandatory", () => {
53
- const step2Match = SUBTASK_PROMPT_V2.match(/### Step 2:[\s\S]*?### Step 3:/);
54
- expect(step2Match).not.toBeNull();
55
- if (!step2Match) return;
56
- const step2Content = step2Match[0];
57
-
58
- // Should explain consequences of skipping
59
- expect(step2Content).toMatch(/skip|waste|repeat|already.solved/i);
60
- });
61
- });
62
-
63
- describe("memory storage emphasis", () => {
64
- test("has a dedicated section for storing learnings", () => {
65
- // Should have a prominent section about storing memories
66
- expect(SUBTASK_PROMPT_V2).toMatch(/##.*STORE.*LEARNING|### Step.*Store.*Learning/i);
67
- });
68
-
69
- test("storage section lists triggers for when to store", () => {
70
- // Should mention triggers: bugs, gotchas, patterns, failed approaches
71
- expect(SUBTASK_PROMPT_V2).toContain("bug");
72
- expect(SUBTASK_PROMPT_V2).toMatch(/gotcha|quirk|workaround/i);
73
- expect(SUBTASK_PROMPT_V2).toMatch(/pattern|domain/i);
74
- });
75
-
76
- test("storage section emphasizes WHY not just WHAT", () => {
77
- expect(SUBTASK_PROMPT_V2).toMatch(/WHY.*not.*WHAT|why.*matters/i);
78
- });
79
-
80
- test("storage section warns against generic knowledge", () => {
81
- expect(SUBTASK_PROMPT_V2).toMatch(/don't store.*generic|generic knowledge/i);
82
- });
83
- });
84
-
85
- describe("checklist order", () => {
86
- test("Step 1 is swarmmail_init", () => {
87
- expect(SUBTASK_PROMPT_V2).toMatch(/### Step 1:[\s\S]*?swarmmail_init/);
88
- });
89
-
90
- test("Step 2 is semantic-memory_find (before skills)", () => {
91
- const step2Pos = SUBTASK_PROMPT_V2.indexOf("### Step 2:");
92
- const step3Pos = SUBTASK_PROMPT_V2.indexOf("### Step 3:");
93
- const memoryFindPos = SUBTASK_PROMPT_V2.indexOf("semantic-memory_find");
94
- const skillsPos = SUBTASK_PROMPT_V2.indexOf("skills_list");
95
-
96
- // Memory find should be in Step 2, before skills in Step 3
97
- expect(memoryFindPos).toBeGreaterThan(step2Pos);
98
- expect(memoryFindPos).toBeLessThan(step3Pos);
99
- expect(skillsPos).toBeGreaterThan(step3Pos);
100
- });
101
-
102
- test("semantic-memory_store comes before swarm_complete", () => {
103
- const storePos = SUBTASK_PROMPT_V2.indexOf("semantic-memory_store");
104
- const completePos = SUBTASK_PROMPT_V2.lastIndexOf("swarm_complete");
105
-
106
- expect(storePos).toBeGreaterThan(0);
107
- expect(storePos).toBeLessThan(completePos);
108
- });
109
-
110
- test("final step is swarm_complete (not hive_close)", () => {
111
- // Find the last "### Step N:" pattern
112
- const stepMatches = [...SUBTASK_PROMPT_V2.matchAll(/### Step (\d+):/g)];
113
- expect(stepMatches.length).toBeGreaterThan(0);
114
-
115
- const lastStepNum = Math.max(...stepMatches.map(m => parseInt(m[1])));
116
- const lastStepMatch = SUBTASK_PROMPT_V2.match(
117
- new RegExp(`### Step ${lastStepNum}:[\\s\\S]*?(?=## \\[|$)`)
118
- );
119
- expect(lastStepMatch).not.toBeNull();
120
- if (!lastStepMatch) return;
121
-
122
- const lastStepContent = lastStepMatch[0];
123
- expect(lastStepContent).toContain("swarm_complete");
124
- expect(lastStepContent).toMatch(/NOT.*hive_close|DO NOT.*hive_close/i);
125
- });
126
- });
127
-
128
- describe("critical requirements section", () => {
129
- test("lists memory query as non-negotiable", () => {
130
- const criticalSection = SUBTASK_PROMPT_V2.match(/\[CRITICAL REQUIREMENTS\][\s\S]*?Begin now/);
131
- expect(criticalSection).not.toBeNull();
132
- if (!criticalSection) return;
133
-
134
- expect(criticalSection[0]).toMatch(/semantic-memory_find|memory.*MUST|Step 2.*MUST/i);
135
- });
136
-
137
- test("lists consequences of skipping memory steps", () => {
138
- const criticalSection = SUBTASK_PROMPT_V2.match(/\[CRITICAL REQUIREMENTS\][\s\S]*?Begin now/);
139
- expect(criticalSection).not.toBeNull();
140
- if (!criticalSection) return;
141
-
142
- // Should mention consequences for skipping memory
143
- expect(criticalSection[0]).toMatch(/repeat|waste|already.solved|mistakes/i);
144
- });
145
- });
146
- });
147
-
148
- describe("formatSubtaskPromptV2", () => {
149
- test("substitutes all placeholders correctly", async () => {
150
- const result = await formatSubtaskPromptV2({
151
- bead_id: "test-project-abc123-bead456",
152
- epic_id: "test-project-abc123-epic789",
153
- subtask_title: "Test Subtask",
154
- subtask_description: "Do the test thing",
155
- files: ["src/test.ts", "src/test.test.ts"],
156
- shared_context: "This is shared context",
157
- project_path: "/path/to/project",
158
- });
159
-
160
- expect(result).toContain("test-project-abc123-bead456");
161
- expect(result).toContain("test-project-abc123-epic789");
162
- expect(result).toContain("Test Subtask");
163
- expect(result).toContain("Do the test thing");
164
- expect(result).toContain("src/test.ts");
165
- expect(result).toContain("/path/to/project");
166
- });
167
-
168
- test("includes memory query step with MANDATORY emphasis", async () => {
169
- const result = await formatSubtaskPromptV2({
170
- bead_id: "test-project-abc123-def456",
171
- epic_id: "test-project-abc123-ghi789",
172
- subtask_title: "Test",
173
- subtask_description: "",
174
- files: [],
175
- });
176
-
177
- expect(result).toMatch(/Step 2:.*MANDATORY/i);
178
- expect(result).toContain("semantic-memory_find");
179
- });
180
- });
181
-
182
- describe("swarm_spawn_subtask tool", () => {
183
- test("returns post_completion_instructions field in JSON response", async () => {
184
- const { swarm_spawn_subtask } = await import("./swarm-prompts");
185
-
186
- const result = await swarm_spawn_subtask.execute({
187
- bead_id: "test-project-abc123-task1",
188
- epic_id: "test-project-abc123-epic1",
189
- subtask_title: "Implement feature X",
190
- subtask_description: "Add feature X to the system",
191
- files: ["src/feature.ts", "src/feature.test.ts"],
192
- shared_context: "Epic context here",
193
- project_path: "/Users/joel/Code/project",
194
- });
195
-
196
- const parsed = JSON.parse(result);
197
- expect(parsed).toHaveProperty("post_completion_instructions");
198
- expect(typeof parsed.post_completion_instructions).toBe("string");
199
- });
200
-
201
- test("post_completion_instructions contains mandatory review steps", async () => {
202
- const { swarm_spawn_subtask } = await import("./swarm-prompts");
203
-
204
- const result = await swarm_spawn_subtask.execute({
205
- bead_id: "test-project-abc123-task1",
206
- epic_id: "test-project-abc123-epic1",
207
- subtask_title: "Implement feature X",
208
- files: ["src/feature.ts"],
209
- project_path: "/Users/joel/Code/project",
210
- });
211
-
212
- const parsed = JSON.parse(result);
213
- const instructions = parsed.post_completion_instructions;
214
-
215
- // Should contain all 5 steps
216
- expect(instructions).toContain("Step 1: Check Swarm Mail");
217
- expect(instructions).toContain("swarmmail_inbox()");
218
- expect(instructions).toContain("Step 2: Review the Work");
219
- expect(instructions).toContain("swarm_review");
220
- expect(instructions).toContain("Step 3: Evaluate Against Criteria");
221
- expect(instructions).toContain("Step 4: Send Feedback");
222
- expect(instructions).toContain("swarm_review_feedback");
223
- expect(instructions).toContain("Step 5: Take Action Based on Review");
224
- expect(instructions).toContain("swarm_spawn_retry"); // Should include retry flow
225
- });
226
-
227
- test("post_completion_instructions substitutes placeholders", async () => {
228
- const { swarm_spawn_subtask } = await import("./swarm-prompts");
229
-
230
- const result = await swarm_spawn_subtask.execute({
231
- bead_id: "test-project-abc123-task1",
232
- epic_id: "test-project-abc123-epic1",
233
- subtask_title: "Implement feature X",
234
- files: ["src/feature.ts", "src/feature.test.ts"],
235
- project_path: "/Users/joel/Code/project",
236
- });
237
-
238
- const parsed = JSON.parse(result);
239
- const instructions = parsed.post_completion_instructions;
240
-
241
- // Placeholders should be replaced
242
- expect(instructions).toContain("/Users/joel/Code/project");
243
- expect(instructions).toContain("test-project-abc123-epic1");
244
- expect(instructions).toContain("test-project-abc123-task1");
245
- expect(instructions).toContain('"src/feature.ts"');
246
- expect(instructions).toContain('"src/feature.test.ts"');
247
-
248
- // Placeholders should NOT remain
249
- expect(instructions).not.toContain("{project_key}");
250
- expect(instructions).not.toContain("{epic_id}");
251
- expect(instructions).not.toContain("{task_id}");
252
- expect(instructions).not.toContain("{files_touched}");
253
- });
254
-
255
- test("post_completion_instructions emphasizes mandatory nature", async () => {
256
- const { swarm_spawn_subtask } = await import("./swarm-prompts");
257
-
258
- const result = await swarm_spawn_subtask.execute({
259
- bead_id: "test-project-abc123-task1",
260
- epic_id: "test-project-abc123-epic1",
261
- subtask_title: "Implement feature X",
262
- files: ["src/feature.ts"],
263
- project_path: "/Users/joel/Code/project",
264
- });
265
-
266
- const parsed = JSON.parse(result);
267
- const instructions = parsed.post_completion_instructions;
268
-
269
- // Should have strong language
270
- expect(instructions).toMatch(/⚠️|MANDATORY|NON-NEGOTIABLE|DO NOT skip/i);
271
- expect(instructions).toContain("DO THIS IMMEDIATELY");
272
- });
273
- });
274
-
275
- describe("RESEARCHER_PROMPT", () => {
276
- describe("required sections", () => {
277
- test("includes IDENTITY section with research_id and epic_id", () => {
278
- expect(RESEARCHER_PROMPT).toContain("## [IDENTITY]");
279
- expect(RESEARCHER_PROMPT).toContain("{research_id}");
280
- expect(RESEARCHER_PROMPT).toContain("{epic_id}");
281
- });
282
-
283
- test("includes MISSION section explaining the role", () => {
284
- expect(RESEARCHER_PROMPT).toContain("## [MISSION]");
285
- expect(RESEARCHER_PROMPT).toMatch(/gather.*documentation/i);
286
- });
287
-
288
- test("includes WORKFLOW section with numbered steps", () => {
289
- expect(RESEARCHER_PROMPT).toContain("## [WORKFLOW]");
290
- expect(RESEARCHER_PROMPT).toContain("### Step 1:");
291
- expect(RESEARCHER_PROMPT).toContain("### Step 2:");
292
- });
293
-
294
- test("includes CRITICAL REQUIREMENTS section", () => {
295
- expect(RESEARCHER_PROMPT).toContain("## [CRITICAL REQUIREMENTS]");
296
- expect(RESEARCHER_PROMPT).toMatch(/NON-NEGOTIABLE/i);
297
- });
298
- });
299
-
300
- describe("workflow steps", () => {
301
- test("Step 1 is swarmmail_init (MANDATORY FIRST)", () => {
302
- expect(RESEARCHER_PROMPT).toMatch(/### Step 1:.*Initialize/i);
303
- expect(RESEARCHER_PROMPT).toContain("swarmmail_init");
304
- expect(RESEARCHER_PROMPT).toContain("project_path");
305
- });
306
-
307
- test("Step 2 is discovering available documentation tools", () => {
308
- const step2Match = RESEARCHER_PROMPT.match(/### Step 2:[\s\S]*?### Step 3:/);
309
- expect(step2Match).not.toBeNull();
310
- if (!step2Match) return;
311
-
312
- const step2Content = step2Match[0];
313
- expect(step2Content).toMatch(/discover.*tools/i);
314
- expect(step2Content).toContain("nextjs_docs");
315
- expect(step2Content).toContain("context7");
316
- expect(step2Content).toContain("fetch");
317
- expect(step2Content).toContain("pdf-brain");
318
- });
319
-
320
- test("Step 3 is reading installed versions", () => {
321
- const step3Match = RESEARCHER_PROMPT.match(/### Step 3:[\s\S]*?### Step 4:/);
322
- expect(step3Match).not.toBeNull();
323
- if (!step3Match) return;
324
-
325
- const step3Content = step3Match[0];
326
- expect(step3Content).toMatch(/read.*installed.*version/i);
327
- expect(step3Content).toContain("package.json");
328
- });
329
-
330
- test("Step 4 is fetching documentation", () => {
331
- const step4Match = RESEARCHER_PROMPT.match(/### Step 4:[\s\S]*?### Step 5:/);
332
- expect(step4Match).not.toBeNull();
333
- if (!step4Match) return;
334
-
335
- const step4Content = step4Match[0];
336
- expect(step4Content).toMatch(/fetch.*documentation/i);
337
- expect(step4Content).toContain("INSTALLED version");
338
- });
339
-
340
- test("Step 5 is storing detailed findings in semantic-memory", () => {
341
- const step5Match = RESEARCHER_PROMPT.match(/### Step 5:[\s\S]*?### Step 6:/);
342
- expect(step5Match).not.toBeNull();
343
- if (!step5Match) return;
344
-
345
- const step5Content = step5Match[0];
346
- expect(step5Content).toContain("semantic-memory_store");
347
- expect(step5Content).toMatch(/store.*individually/i);
348
- });
349
-
350
- test("Step 6 is broadcasting summary to coordinator", () => {
351
- const step6Match = RESEARCHER_PROMPT.match(/### Step 6:[\s\S]*?### Step 7:/);
352
- expect(step6Match).not.toBeNull();
353
- if (!step6Match) return;
354
-
355
- const step6Content = step6Match[0];
356
- expect(step6Content).toContain("swarmmail_send");
357
- expect(step6Content).toContain("coordinator");
358
- });
359
-
360
- test("Step 7 is returning structured JSON output", () => {
361
- const step7Match = RESEARCHER_PROMPT.match(/### Step 7:[\s\S]*?(?=## \[|$)/);
362
- expect(step7Match).not.toBeNull();
363
- if (!step7Match) return;
364
-
365
- const step7Content = step7Match[0];
366
- expect(step7Content).toContain("JSON");
367
- expect(step7Content).toContain("technologies");
368
- expect(step7Content).toContain("summary");
369
- });
370
- });
371
-
372
- describe("coordinator-provided tech stack", () => {
373
- test("emphasizes that coordinator provides the tech list", () => {
374
- expect(RESEARCHER_PROMPT).toMatch(/COORDINATOR PROVIDED.*TECHNOLOGIES/i);
375
- expect(RESEARCHER_PROMPT).toContain("{tech_stack}");
376
- });
377
-
378
- test("clarifies researcher does NOT discover what to research", () => {
379
- expect(RESEARCHER_PROMPT).toMatch(/NOT discover what to research/i);
380
- expect(RESEARCHER_PROMPT).toMatch(/DO discover.*TOOLS/i);
381
- });
382
- });
383
-
384
- describe("upgrade comparison mode", () => {
385
- test("includes placeholder for check_upgrades mode", () => {
386
- expect(RESEARCHER_PROMPT).toContain("{check_upgrades}");
387
- });
388
-
389
- test("mentions comparing installed vs latest when in upgrade mode", () => {
390
- expect(RESEARCHER_PROMPT).toMatch(/check-upgrades/i);
391
- expect(RESEARCHER_PROMPT).toMatch(/compare|latest.*version/i);
392
- });
393
- });
394
-
395
- describe("output requirements", () => {
396
- test("specifies TWO output destinations: semantic-memory and return JSON", () => {
397
- expect(RESEARCHER_PROMPT).toMatch(/TWO places/i);
398
- expect(RESEARCHER_PROMPT).toContain("semantic-memory");
399
- expect(RESEARCHER_PROMPT).toContain("Return JSON");
400
- });
401
-
402
- test("explains semantic-memory is for detailed findings", () => {
403
- expect(RESEARCHER_PROMPT).toMatch(/semantic-memory.*detailed/i);
404
- });
405
-
406
- test("explains return JSON is for condensed summary", () => {
407
- expect(RESEARCHER_PROMPT).toMatch(/return.*condensed.*summary/i);
408
- });
409
- });
410
- });
411
-
412
- describe("formatResearcherPrompt", () => {
413
- test("substitutes research_id placeholder", () => {
414
- const result = formatResearcherPrompt({
415
- research_id: "research-abc123",
416
- epic_id: "epic-xyz789",
417
- tech_stack: ["Next.js", "React"],
418
- project_path: "/path/to/project",
419
- check_upgrades: false,
420
- });
421
-
422
- expect(result).toContain("research-abc123");
423
- expect(result).not.toContain("{research_id}");
424
- });
425
-
426
- test("substitutes epic_id placeholder", () => {
427
- const result = formatResearcherPrompt({
428
- research_id: "research-abc123",
429
- epic_id: "epic-xyz789",
430
- tech_stack: ["Next.js"],
431
- project_path: "/path/to/project",
432
- check_upgrades: false,
433
- });
434
-
435
- expect(result).toContain("epic-xyz789");
436
- expect(result).not.toContain("{epic_id}");
437
- });
438
-
439
- test("formats tech_stack as bulleted list", () => {
440
- const result = formatResearcherPrompt({
441
- research_id: "research-abc123",
442
- epic_id: "epic-xyz789",
443
- tech_stack: ["Next.js", "React", "TypeScript"],
444
- project_path: "/path/to/project",
445
- check_upgrades: false,
446
- });
447
-
448
- expect(result).toContain("- Next.js");
449
- expect(result).toContain("- React");
450
- expect(result).toContain("- TypeScript");
451
- });
452
-
453
- test("substitutes project_path placeholder", () => {
454
- const result = formatResearcherPrompt({
455
- research_id: "research-abc123",
456
- epic_id: "epic-xyz789",
457
- tech_stack: ["Next.js"],
458
- project_path: "/Users/joel/Code/my-project",
459
- check_upgrades: false,
460
- });
461
-
462
- expect(result).toContain("/Users/joel/Code/my-project");
463
- expect(result).not.toContain("{project_path}");
464
- });
465
-
466
- test("includes DEFAULT MODE text when check_upgrades=false", () => {
467
- const result = formatResearcherPrompt({
468
- research_id: "research-abc123",
469
- epic_id: "epic-xyz789",
470
- tech_stack: ["Next.js"],
471
- project_path: "/path/to/project",
472
- check_upgrades: false,
473
- });
474
-
475
- expect(result).toContain("DEFAULT MODE");
476
- expect(result).toContain("INSTALLED versions only");
477
- });
478
-
479
- test("includes UPGRADE COMPARISON MODE text when check_upgrades=true", () => {
480
- const result = formatResearcherPrompt({
481
- research_id: "research-abc123",
482
- epic_id: "epic-xyz789",
483
- tech_stack: ["Next.js"],
484
- project_path: "/path/to/project",
485
- check_upgrades: true,
486
- });
487
-
488
- expect(result).toContain("UPGRADE COMPARISON MODE");
489
- expect(result).toContain("BOTH installed AND latest");
490
- expect(result).toContain("breaking changes");
491
- });
492
- });
493
-
494
- describe("on-demand research section", () => {
495
- test("includes ON-DEMAND RESEARCH section after Step 9", () => {
496
- // Find Step 9 and the section after it
497
- const step9Pos = SUBTASK_PROMPT_V2.indexOf("### Step 9:");
498
- const swarmMailPos = SUBTASK_PROMPT_V2.indexOf("## [SWARM MAIL COMMUNICATION]");
499
-
500
- expect(step9Pos).toBeGreaterThan(0);
501
- expect(swarmMailPos).toBeGreaterThan(step9Pos);
502
-
503
- // Extract the section between Step 9 and SWARM MAIL
504
- const betweenSection = SUBTASK_PROMPT_V2.substring(step9Pos, swarmMailPos);
505
-
506
- expect(betweenSection).toContain("## [ON-DEMAND RESEARCH]");
507
- });
508
-
509
- test("research section instructs to check semantic-memory first", () => {
510
- const researchMatch = SUBTASK_PROMPT_V2.match(/## \[ON-DEMAND RESEARCH\][\s\S]*?## \[SWARM MAIL/);
511
- expect(researchMatch).not.toBeNull();
512
- if (!researchMatch) return;
513
-
514
- const researchContent = researchMatch[0];
515
- expect(researchContent).toContain("semantic-memory_find");
516
- expect(researchContent).toMatch(/check.*semantic-memory.*first/i);
517
- });
518
-
519
- test("research section includes swarm_spawn_researcher tool usage", () => {
520
- const researchMatch = SUBTASK_PROMPT_V2.match(/## \[ON-DEMAND RESEARCH\][\s\S]*?## \[SWARM MAIL/);
521
- expect(researchMatch).not.toBeNull();
522
- if (!researchMatch) return;
523
-
524
- const researchContent = researchMatch[0];
525
- expect(researchContent).toContain("swarm_spawn_researcher");
526
- });
527
-
528
- test("research section lists specific research triggers", () => {
529
- const researchMatch = SUBTASK_PROMPT_V2.match(/## \[ON-DEMAND RESEARCH\][\s\S]*?## \[SWARM MAIL/);
530
- expect(researchMatch).not.toBeNull();
531
- if (!researchMatch) return;
532
-
533
- const researchContent = researchMatch[0];
534
-
535
- // Should list when TO research
536
- expect(researchContent).toMatch(/triggers|when to research/i);
537
- expect(researchContent).toMatch(/API.*works|breaking changes|outdated/i);
538
- });
539
-
540
- test("research section lists when NOT to research", () => {
541
- const researchMatch = SUBTASK_PROMPT_V2.match(/## \[ON-DEMAND RESEARCH\][\s\S]*?## \[SWARM MAIL/);
542
- expect(researchMatch).not.toBeNull();
543
- if (!researchMatch) return;
544
-
545
- const researchContent = researchMatch[0];
546
-
547
- // Should list when to SKIP research
548
- expect(researchContent).toMatch(/don't research|skip research/i);
549
- expect(researchContent).toMatch(/standard patterns|well-documented|obvious/i);
550
- });
551
-
552
- test("research section includes 3-step workflow", () => {
553
- const researchMatch = SUBTASK_PROMPT_V2.match(/## \[ON-DEMAND RESEARCH\][\s\S]*?## \[SWARM MAIL/);
554
- expect(researchMatch).not.toBeNull();
555
- if (!researchMatch) return;
556
-
557
- const researchContent = researchMatch[0];
558
-
559
- // Should have numbered steps
560
- expect(researchContent).toMatch(/1\.\s*.*Check semantic-memory/i);
561
- expect(researchContent).toMatch(/2\.\s*.*spawn researcher/i);
562
- expect(researchContent).toMatch(/3\.\s*.*wait.*continue/i);
563
- });
564
- });
565
-
566
- describe("swarm_spawn_researcher tool", () => {
567
- test("returns JSON with prompt field", async () => {
568
- const { swarm_spawn_researcher } = await import("./swarm-prompts");
569
-
570
- const result = await swarm_spawn_researcher.execute({
571
- research_id: "research-abc123",
572
- epic_id: "epic-xyz789",
573
- tech_stack: ["Next.js", "React"],
574
- project_path: "/Users/joel/Code/project",
575
- });
576
-
577
- const parsed = JSON.parse(result);
578
- expect(parsed).toHaveProperty("prompt");
579
- expect(typeof parsed.prompt).toBe("string");
580
- expect(parsed.prompt.length).toBeGreaterThan(100);
581
- });
582
-
583
- test("returns subagent_type field as 'swarm/researcher'", async () => {
584
- const { swarm_spawn_researcher } = await import("./swarm-prompts");
585
-
586
- const result = await swarm_spawn_researcher.execute({
587
- research_id: "research-abc123",
588
- epic_id: "epic-xyz789",
589
- tech_stack: ["Next.js"],
590
- project_path: "/Users/joel/Code/project",
591
- });
592
-
593
- const parsed = JSON.parse(result);
594
- expect(parsed.subagent_type).toBe("swarm/researcher");
595
- });
596
-
597
- test("returns expected_output schema", async () => {
598
- const { swarm_spawn_researcher } = await import("./swarm-prompts");
599
-
600
- const result = await swarm_spawn_researcher.execute({
601
- research_id: "research-abc123",
602
- epic_id: "epic-xyz789",
603
- tech_stack: ["Next.js"],
604
- project_path: "/Users/joel/Code/project",
605
- });
606
-
607
- const parsed = JSON.parse(result);
608
- expect(parsed).toHaveProperty("expected_output");
609
- expect(parsed.expected_output).toHaveProperty("technologies");
610
- expect(parsed.expected_output).toHaveProperty("summary");
611
- });
612
-
613
- test("defaults check_upgrades to false when not provided", async () => {
614
- const { swarm_spawn_researcher } = await import("./swarm-prompts");
615
-
616
- const result = await swarm_spawn_researcher.execute({
617
- research_id: "research-abc123",
618
- epic_id: "epic-xyz789",
619
- tech_stack: ["Next.js"],
620
- project_path: "/Users/joel/Code/project",
621
- });
622
-
623
- const parsed = JSON.parse(result);
624
- expect(parsed.check_upgrades).toBe(false);
625
- });
626
-
627
- test("respects check_upgrades when provided as true", async () => {
628
- const { swarm_spawn_researcher } = await import("./swarm-prompts");
629
-
630
- const result = await swarm_spawn_researcher.execute({
631
- research_id: "research-abc123",
632
- epic_id: "epic-xyz789",
633
- tech_stack: ["Next.js"],
634
- project_path: "/Users/joel/Code/project",
635
- check_upgrades: true,
636
- });
637
-
638
- const parsed = JSON.parse(result);
639
- expect(parsed.check_upgrades).toBe(true);
640
- });
641
-
642
- test("includes all input parameters in returned JSON", async () => {
643
- const { swarm_spawn_researcher } = await import("./swarm-prompts");
644
-
645
- const result = await swarm_spawn_researcher.execute({
646
- research_id: "research-abc123",
647
- epic_id: "epic-xyz789",
648
- tech_stack: ["Next.js", "React", "TypeScript"],
649
- project_path: "/Users/joel/Code/project",
650
- check_upgrades: true,
651
- });
652
-
653
- const parsed = JSON.parse(result);
654
- expect(parsed.research_id).toBe("research-abc123");
655
- expect(parsed.epic_id).toBe("epic-xyz789");
656
- expect(parsed.tech_stack).toEqual(["Next.js", "React", "TypeScript"]);
657
- expect(parsed.project_path).toBe("/Users/joel/Code/project");
658
- expect(parsed.check_upgrades).toBe(true);
659
- });
660
- });
661
-
662
- describe("swarm_spawn_retry tool", () => {
663
- test("generates valid retry prompt with issues", async () => {
664
- const { swarm_spawn_retry } = await import("./swarm-prompts");
665
-
666
- const result = await swarm_spawn_retry.execute({
667
- bead_id: "test-project-abc123-task1",
668
- epic_id: "test-project-abc123-epic1",
669
- original_prompt: "Original task: implement feature X",
670
- attempt: 1,
671
- issues: JSON.stringify([
672
- { file: "src/feature.ts", line: 42, issue: "Missing null check", suggestion: "Add null check" }
673
- ]),
674
- files: ["src/feature.ts"],
675
- project_path: "/Users/joel/Code/project",
676
- });
677
-
678
- const parsed = JSON.parse(result);
679
- expect(parsed).toHaveProperty("prompt");
680
- expect(typeof parsed.prompt).toBe("string");
681
- expect(parsed.prompt).toContain("RETRY ATTEMPT");
682
- expect(parsed.prompt).toContain("Missing null check");
683
- });
684
-
685
- test("includes attempt number in prompt header", async () => {
686
- const { swarm_spawn_retry } = await import("./swarm-prompts");
687
-
688
- const result = await swarm_spawn_retry.execute({
689
- bead_id: "test-project-abc123-task1",
690
- epic_id: "test-project-abc123-epic1",
691
- original_prompt: "Original task",
692
- attempt: 2,
693
- issues: "[]",
694
- files: ["src/test.ts"],
695
- });
696
-
697
- const parsed = JSON.parse(result);
698
- expect(parsed.prompt).toContain("RETRY ATTEMPT 2/3");
699
- expect(parsed.attempt).toBe(2);
700
- });
701
-
702
- test("includes diff when provided", async () => {
703
- const { swarm_spawn_retry } = await import("./swarm-prompts");
704
-
705
- const diffContent = `diff --git a/src/test.ts b/src/test.ts
706
- +++ b/src/test.ts
707
- @@ -1 +1 @@
708
- -const x = 1;
709
- +const x = null;`;
710
-
711
- const result = await swarm_spawn_retry.execute({
712
- bead_id: "test-project-abc123-task1",
713
- epic_id: "test-project-abc123-epic1",
714
- original_prompt: "Original task",
715
- attempt: 1,
716
- issues: "[]",
717
- diff: diffContent,
718
- files: ["src/test.ts"],
719
- });
720
-
721
- const parsed = JSON.parse(result);
722
- expect(parsed.prompt).toContain(diffContent);
723
- expect(parsed.prompt).toContain("PREVIOUS ATTEMPT");
724
- });
725
-
726
- test("rejects attempt > 3 with error", async () => {
727
- const { swarm_spawn_retry } = await import("./swarm-prompts");
728
-
729
- await expect(async () => {
730
- await swarm_spawn_retry.execute({
731
- bead_id: "test-project-abc123-task1",
732
- epic_id: "test-project-abc123-epic1",
733
- original_prompt: "Original task",
734
- attempt: 4,
735
- issues: "[]",
736
- files: ["src/test.ts"],
737
- });
738
- }).toThrow(/attempt.*exceeds.*maximum/i);
739
- });
740
-
741
- test("formats issues as readable list", async () => {
742
- const { swarm_spawn_retry } = await import("./swarm-prompts");
743
-
744
- const issues = [
745
- { file: "src/a.ts", line: 10, issue: "Missing error handling", suggestion: "Add try-catch" },
746
- { file: "src/b.ts", line: 20, issue: "Type mismatch", suggestion: "Fix types" }
747
- ];
748
-
749
- const result = await swarm_spawn_retry.execute({
750
- bead_id: "test-project-abc123-task1",
751
- epic_id: "test-project-abc123-epic1",
752
- original_prompt: "Original task",
753
- attempt: 1,
754
- issues: JSON.stringify(issues),
755
- files: ["src/a.ts", "src/b.ts"],
756
- });
757
-
758
- const parsed = JSON.parse(result);
759
- expect(parsed.prompt).toContain("ISSUES FROM PREVIOUS ATTEMPT");
760
- expect(parsed.prompt).toContain("src/a.ts:10");
761
- expect(parsed.prompt).toContain("Missing error handling");
762
- expect(parsed.prompt).toContain("src/b.ts:20");
763
- expect(parsed.prompt).toContain("Type mismatch");
764
- });
765
-
766
- test("returns expected response structure", async () => {
767
- const { swarm_spawn_retry } = await import("./swarm-prompts");
768
-
769
- const result = await swarm_spawn_retry.execute({
770
- bead_id: "test-project-abc123-task1",
771
- epic_id: "test-project-abc123-epic1",
772
- original_prompt: "Original task",
773
- attempt: 1,
774
- issues: "[]",
775
- files: ["src/test.ts"],
776
- project_path: "/Users/joel/Code/project",
777
- });
778
-
779
- const parsed = JSON.parse(result);
780
- expect(parsed).toHaveProperty("prompt");
781
- expect(parsed).toHaveProperty("bead_id", "test-project-abc123-task1");
782
- expect(parsed).toHaveProperty("attempt", 1);
783
- expect(parsed).toHaveProperty("max_attempts", 3);
784
- expect(parsed).toHaveProperty("files");
785
- expect(parsed.files).toEqual(["src/test.ts"]);
786
- });
787
-
788
- test("includes standard worker contract (swarmmail_init, reserve, complete)", async () => {
789
- const { swarm_spawn_retry } = await import("./swarm-prompts");
790
-
791
- const result = await swarm_spawn_retry.execute({
792
- bead_id: "test-project-abc123-task1",
793
- epic_id: "test-project-abc123-epic1",
794
- original_prompt: "Original task",
795
- attempt: 1,
796
- issues: "[]",
797
- files: ["src/test.ts"],
798
- project_path: "/Users/joel/Code/project",
799
- });
800
-
801
- const parsed = JSON.parse(result);
802
- expect(parsed.prompt).toContain("swarmmail_init");
803
- expect(parsed.prompt).toContain("swarmmail_reserve");
804
- expect(parsed.prompt).toContain("swarm_complete");
805
- });
806
-
807
- test("instructs to preserve working changes", async () => {
808
- const { swarm_spawn_retry } = await import("./swarm-prompts");
809
-
810
- const result = await swarm_spawn_retry.execute({
811
- bead_id: "test-project-abc123-task1",
812
- epic_id: "test-project-abc123-epic1",
813
- original_prompt: "Original task",
814
- attempt: 1,
815
- issues: JSON.stringify([{ file: "src/test.ts", line: 1, issue: "Bug", suggestion: "Fix" }]),
816
- files: ["src/test.ts"],
817
- });
818
-
819
- const parsed = JSON.parse(result);
820
- expect(parsed.prompt).toMatch(/preserve.*working|fix.*while preserving/i);
821
- });
822
- });
823
-
824
- describe("COORDINATOR_PROMPT", () => {
825
- test("constant exists and is exported", () => {
826
- expect(COORDINATOR_PROMPT).toBeDefined();
827
- expect(typeof COORDINATOR_PROMPT).toBe("string");
828
- expect(COORDINATOR_PROMPT.length).toBeGreaterThan(100);
829
- });
830
-
831
- test("contains all phase headers (0-8)", () => {
832
- expect(COORDINATOR_PROMPT).toContain("Phase 0:");
833
- expect(COORDINATOR_PROMPT).toContain("Phase 1:");
834
- expect(COORDINATOR_PROMPT).toContain("Phase 2:");
835
- expect(COORDINATOR_PROMPT).toContain("Phase 3:");
836
- expect(COORDINATOR_PROMPT).toContain("Phase 4:");
837
- expect(COORDINATOR_PROMPT).toContain("Phase 5:");
838
- expect(COORDINATOR_PROMPT).toContain("Phase 6:");
839
- expect(COORDINATOR_PROMPT).toContain("Phase 7:");
840
- expect(COORDINATOR_PROMPT).toContain("Phase 8:");
841
- });
842
-
843
- test("contains Phase 1.5: Research Phase section", () => {
844
- expect(COORDINATOR_PROMPT).toContain("Phase 1.5:");
845
- expect(COORDINATOR_PROMPT).toMatch(/Phase 1\.5:.*Research/i);
846
- });
847
-
848
- test("Phase 1.5 documents swarm_spawn_researcher usage", () => {
849
- // Extract Phase 1.5 section
850
- const phase15Match = COORDINATOR_PROMPT.match(/Phase 1\.5:[\s\S]*?Phase 2:/);
851
- expect(phase15Match).not.toBeNull();
852
- if (!phase15Match) return;
853
- const phase15Content = phase15Match[0];
854
-
855
- expect(phase15Content).toContain("swarm_spawn_researcher");
856
- expect(phase15Content).toContain("Task(subagent_type=\"swarm/researcher\"");
857
- });
858
-
859
- test("has section explicitly forbidding direct research tool calls", () => {
860
- expect(COORDINATOR_PROMPT).toMatch(/NEVER.*direct|forbidden.*tools|do not call directly/i);
861
- });
862
-
863
- test("forbidden tools section lists all prohibited tools", () => {
864
- const forbiddenTools = [
865
- "repo-crawl_",
866
- "repo-autopsy_",
867
- "webfetch",
868
- "fetch_fetch",
869
- "context7_",
870
- "pdf-brain_search",
871
- "pdf-brain_read"
872
- ];
873
-
874
- for (const tool of forbiddenTools) {
875
- expect(COORDINATOR_PROMPT).toContain(tool);
876
- }
877
- });
878
-
879
- test("forbidden tools section explains to use swarm_spawn_researcher instead", () => {
880
- // Find the forbidden tools section
881
- const forbiddenMatch = COORDINATOR_PROMPT.match(/(FORBIDDEN.*for coordinators|NEVER.*FETCH.*DIRECTLY)[\s\S]{0,500}swarm_spawn_researcher/i);
882
- expect(forbiddenMatch).not.toBeNull();
883
- });
884
-
885
- test("contains coordinator role boundaries section", () => {
886
- expect(COORDINATOR_PROMPT).toContain("Coordinator Role Boundaries");
887
- expect(COORDINATOR_PROMPT).toMatch(/COORDINATORS NEVER.*EXECUTE.*WORK/i);
888
- });
889
-
890
- test("contains MANDATORY review loop section", () => {
891
- expect(COORDINATOR_PROMPT).toContain("MANDATORY Review Loop");
892
- expect(COORDINATOR_PROMPT).toContain("swarm_review");
893
- expect(COORDINATOR_PROMPT).toContain("swarm_review_feedback");
894
- });
895
-
896
- test("Phase 1.5 positioned between Phase 1 (Initialize) and Phase 2 (Knowledge)", () => {
897
- const phase1Pos = COORDINATOR_PROMPT.indexOf("Phase 1:");
898
- const phase15Pos = COORDINATOR_PROMPT.indexOf("Phase 1.5:");
899
- const phase2Pos = COORDINATOR_PROMPT.indexOf("Phase 2:");
900
-
901
- expect(phase15Pos).toBeGreaterThan(phase1Pos);
902
- expect(phase15Pos).toBeLessThan(phase2Pos);
903
- });
904
- });
905
-
906
- describe("formatCoordinatorPrompt", () => {
907
- test("function exists and returns string", () => {
908
- expect(formatCoordinatorPrompt).toBeDefined();
909
- const result = formatCoordinatorPrompt({ task: "test task", projectPath: "/test" });
910
- expect(typeof result).toBe("string");
911
- });
912
-
913
- test("substitutes {task} placeholder", () => {
914
- const result = formatCoordinatorPrompt({
915
- task: "Implement auth",
916
- projectPath: "/test"
917
- });
918
- expect(result).toContain("Implement auth");
919
- });
920
-
921
- test("substitutes {project_path} placeholder", () => {
922
- const result = formatCoordinatorPrompt({
923
- task: "test",
924
- projectPath: "/Users/joel/my-project"
925
- });
926
- expect(result).toContain("/Users/joel/my-project");
927
- });
928
-
929
- test("returns complete prompt with all phases", () => {
930
- const result = formatCoordinatorPrompt({
931
- task: "test",
932
- projectPath: "/test"
933
- });
934
-
935
- // Should contain all phase headers
936
- for (let i = 0; i <= 8; i++) {
937
- expect(result).toContain(`Phase ${i}:`);
938
- }
939
- expect(result).toContain("Phase 1.5:");
940
- });
941
- });
942
-
943
- describe("getRecentEvalFailures", () => {
944
- test("returns empty string when no failures exist", async () => {
945
- const { getRecentEvalFailures } = await import("./swarm-prompts");
946
- const result = await getRecentEvalFailures();
947
-
948
- // Should not throw and returns string
949
- expect(typeof result).toBe("string");
950
- // When no failures, returns empty or a message - either is acceptable
951
- });
952
-
953
- test("returns formatted string when failures exist", async () => {
954
- const { getRecentEvalFailures } = await import("./swarm-prompts");
955
-
956
- // This test depends on actual memory state
957
- // Just verify it doesn't throw and returns a string
958
- const result = await getRecentEvalFailures();
959
- expect(typeof result).toBe("string");
960
- });
961
-
962
- test("includes warning emoji in header when failures present", async () => {
963
- const { getRecentEvalFailures } = await import("./swarm-prompts");
964
-
965
- // If there are failures in the system, the header should have ⚠️
966
- const result = await getRecentEvalFailures();
967
-
968
- // Either empty (no failures) or contains the warning section
969
- if (result.length > 0) {
970
- expect(result).toMatch(/⚠️|Recent Eval Failures/);
971
- }
972
- });
973
-
974
- test("handles memory adapter errors gracefully", async () => {
975
- const { getRecentEvalFailures } = await import("./swarm-prompts");
976
-
977
- // Should not throw even if memory is unavailable
978
- await expect(getRecentEvalFailures()).resolves.toBeDefined();
979
- });
980
- });
981
-
982
- describe("getPromptInsights", () => {
983
- describe("for coordinators (planning prompts)", () => {
984
- test("returns formatted insights string", async () => {
985
- const { getPromptInsights } = await import("./swarm-prompts");
986
- const result = await getPromptInsights({ role: "coordinator" });
987
-
988
- expect(typeof result).toBe("string");
989
- });
990
-
991
- test.skip("includes strategy success rates when data exists", async () => {
992
- const { getPromptInsights } = await import("./swarm-prompts");
993
- const result = await getPromptInsights({ role: "coordinator" });
994
-
995
- // If there's data, should mention strategies
996
- if (result.length > 0) {
997
- expect(result).toMatch(/strategy|file-based|feature-based|risk-based/i);
998
- }
999
- });
1000
-
1001
- test.skip("includes recent failure patterns", async () => {
1002
- const { getPromptInsights } = await import("./swarm-prompts");
1003
- const result = await getPromptInsights({ role: "coordinator" });
1004
-
1005
- // Should query for failures and anti-patterns
1006
- if (result.length > 0) {
1007
- expect(result).toMatch(/avoid|failure|anti-pattern|success rate/i);
1008
- }
1009
- });
1010
-
1011
- test.skip("returns empty string when no data available", async () => {
1012
- const { getPromptInsights } = await import("./swarm-prompts");
1013
-
1014
- // With project_key filter that doesn't exist, should return empty
1015
- const result = await getPromptInsights({
1016
- role: "coordinator",
1017
- project_key: "non-existent-project-xyz123"
1018
- });
1019
-
1020
- expect(typeof result).toBe("string");
1021
- });
1022
- });
1023
-
1024
- describe("for workers (subtask prompts)", () => {
1025
- test.skip("returns formatted insights string", async () => {
1026
- const { getPromptInsights } = await import("./swarm-prompts");
1027
- const result = await getPromptInsights({
1028
- role: "worker",
1029
- files: ["src/test.ts"]
1030
- });
1031
-
1032
- expect(typeof result).toBe("string");
1033
- });
1034
-
1035
- test.skip("queries semantic-memory for file-specific learnings", async () => {
1036
- const { getPromptInsights } = await import("./swarm-prompts");
1037
- const result = await getPromptInsights({
1038
- role: "worker",
1039
- files: ["src/auth.ts", "src/api/login.ts"]
1040
- });
1041
-
1042
- // Should query semantic memory with file/domain keywords
1043
- // Result format doesn't matter, just verify it doesn't throw
1044
- expect(typeof result).toBe("string");
1045
- });
1046
-
1047
- test.skip("includes common pitfalls for domain area", async () => {
1048
- const { getPromptInsights } = await import("./swarm-prompts");
1049
- const result = await getPromptInsights({
1050
- role: "worker",
1051
- domain: "authentication"
1052
- });
1053
-
1054
- if (result.length > 0) {
1055
- expect(result).toMatch(/pitfall|gotcha|warning|common|issue/i);
1056
- }
1057
- });
1058
- });
1059
-
1060
- describe("handles errors gracefully", () => {
1061
- test.skip("returns empty string when database unavailable", async () => {
1062
- const { getPromptInsights } = await import("./swarm-prompts");
1063
-
1064
- // Should not throw even if swarm-mail DB is unavailable
1065
- await expect(getPromptInsights({ role: "coordinator" })).resolves.toBeDefined();
1066
- });
1067
-
1068
- test.skip("returns empty string when semantic-memory unavailable", async () => {
1069
- const { getPromptInsights } = await import("./swarm-prompts");
1070
-
1071
- // Should not throw even if memory is unavailable
1072
- await expect(getPromptInsights({ role: "worker", files: [] })).resolves.toBeDefined();
1073
- });
1074
- });
1075
-
1076
- describe("formatting", () => {
1077
- test.skip("formats strategy stats as readable table", async () => {
1078
- const { getPromptInsights } = await import("./swarm-prompts");
1079
- const result = await getPromptInsights({ role: "coordinator" });
1080
-
1081
- if (result.includes("Strategy")) {
1082
- // Should use markdown table or similar readable format
1083
- expect(result).toMatch(/\|.*\||\n-+\n|Strategy.*Success/i);
1084
- }
1085
- });
1086
-
1087
- test.skip("limits output to prevent context bloat", async () => {
1088
- const { getPromptInsights } = await import("./swarm-prompts");
1089
- const result = await getPromptInsights({ role: "coordinator" });
1090
-
1091
- // Should cap at reasonable length (say, 1500 chars max)
1092
- expect(result.length).toBeLessThan(2000);
1093
- });
1094
-
1095
- test.skip("includes visual emphasis (emoji or markdown)", async () => {
1096
- const { getPromptInsights } = await import("./swarm-prompts");
1097
- const result = await getPromptInsights({ role: "coordinator" });
1098
-
1099
- if (result.length > 0) {
1100
- // Should have at least some formatting
1101
- expect(result).toMatch(/##|📊|✅|❌|⚠️|\*\*/);
1102
- }
1103
- });
1104
- });
1105
-
1106
- describe("getPromptInsights integration with swarm-insights", () => {
1107
- test("coordinator role uses swarm-insights data layer", async () => {
1108
- const { getPromptInsights } = await import("./swarm-prompts");
1109
-
1110
- // Should call new data layer, not old swarm-mail analytics
1111
- const result = await getPromptInsights({ role: "coordinator" });
1112
-
1113
- // If we have data, it should be formatted by formatInsightsForPrompt
1114
- if (result.length > 0) {
1115
- // New format has "Historical Insights" section
1116
- expect(result).toMatch(/Historical Insights|Strategy Performance|Common Pitfalls/i);
1117
- }
1118
- });
1119
-
1120
- test("coordinator insights have expected structure when data exists", async () => {
1121
- const { getPromptInsights } = await import("./swarm-prompts");
1122
- const result = await getPromptInsights({ role: "coordinator" });
1123
-
1124
- // Should have Historical Insights header
1125
- if (result.length > 0) {
1126
- expect(result).toContain("📊 Historical Insights");
1127
- expect(result).toContain("Use these learnings when selecting decomposition strategies");
1128
- }
1129
- });
1130
-
1131
- test("coordinator insights use formatInsightsForPrompt output", async () => {
1132
- const { getPromptInsights } = await import("./swarm-prompts");
1133
- const result = await getPromptInsights({ role: "coordinator" });
1134
-
1135
- // formatInsightsForPrompt produces specific markdown patterns
1136
- if (result.length > 0 && result.includes("Strategy")) {
1137
- // Should have Strategy Performance or Common Pitfalls sections
1138
- const hasExpectedSections =
1139
- result.includes("Strategy Performance") ||
1140
- result.includes("Common Pitfalls");
1141
- expect(hasExpectedSections).toBe(true);
1142
- }
1143
- });
1144
-
1145
- test("coordinator insights are concise (<500 tokens)", async () => {
1146
- const { getPromptInsights } = await import("./swarm-prompts");
1147
- const result = await getPromptInsights({ role: "coordinator" });
1148
-
1149
- // formatInsightsForPrompt enforces maxTokens=500 by default
1150
- // Rough estimate: 4 chars per token = 2000 chars max
1151
- if (result.length > 0) {
1152
- expect(result.length).toBeLessThan(2000);
1153
- }
1154
- });
1155
-
1156
- test("gracefully handles missing data", async () => {
1157
- const { getPromptInsights } = await import("./swarm-prompts");
1158
-
1159
- // Should not throw if database is empty or missing
1160
- const result = await getPromptInsights({ role: "coordinator" });
1161
-
1162
- // Empty string is acceptable when no data
1163
- expect(typeof result).toBe("string");
1164
- });
1165
-
1166
- test("imports from swarm-insights module", async () => {
1167
- // Verify the imports exist
1168
- const insights = await import("./swarm-insights");
1169
-
1170
- expect(insights.getStrategyInsights).toBeDefined();
1171
- expect(insights.getPatternInsights).toBeDefined();
1172
- expect(insights.formatInsightsForPrompt).toBeDefined();
1173
- });
1174
- });
1175
-
1176
- describe("worker insights integration with swarm-insights", () => {
1177
- test("worker role uses getFileInsights from swarm-insights data layer", async () => {
1178
- const { getPromptInsights } = await import("./swarm-prompts");
1179
-
1180
- // Should call getFileInsights for file-specific insights
1181
- const result = await getPromptInsights({
1182
- role: "worker",
1183
- files: ["src/auth.ts", "src/db.ts"],
1184
- domain: "authentication"
1185
- });
1186
-
1187
- // If we have data, it should be formatted by formatInsightsForPrompt
1188
- if (result.length > 0) {
1189
- // New format has "File-Specific Gotchas" or semantic memory learnings
1190
- expect(result).toMatch(/File-Specific Gotchas|Relevant Learnings/i);
1191
- }
1192
- });
1193
-
1194
- test("worker insights include file-specific gotchas when available", async () => {
1195
- const { getPromptInsights } = await import("./swarm-prompts");
1196
- const result = await getPromptInsights({
1197
- role: "worker",
1198
- files: ["src/test-file.ts"],
1199
- });
1200
-
1201
- // Should contain either file gotchas or semantic memory results
1202
- if (result.length > 0) {
1203
- const hasFileInsights =
1204
- result.includes("File-Specific Gotchas") ||
1205
- result.includes("Relevant Learnings");
1206
- expect(hasFileInsights).toBe(true);
1207
- }
1208
- });
1209
-
1210
- test("worker insights combine event store failures + semantic memory", async () => {
1211
- const { getPromptInsights } = await import("./swarm-prompts");
1212
- const result = await getPromptInsights({
1213
- role: "worker",
1214
- files: ["src/complex.ts"],
1215
- domain: "complex feature"
1216
- });
1217
-
1218
- // Should potentially have both sources of insight
1219
- // At minimum, should return string (empty if no data)
1220
- expect(typeof result).toBe("string");
1221
- });
1222
-
1223
- test("worker insights are concise (<300 tokens per file)", async () => {
1224
- const { getPromptInsights } = await import("./swarm-prompts");
1225
- const result = await getPromptInsights({
1226
- role: "worker",
1227
- files: ["src/file1.ts", "src/file2.ts", "src/file3.ts"],
1228
- });
1229
-
1230
- // <300 tokens per file = 900 tokens max for 3 files
1231
- // Rough estimate: 4 chars per token = 3600 chars max
1232
- if (result.length > 0) {
1233
- expect(result.length).toBeLessThan(3600);
1234
- }
1235
- });
1236
-
1237
- test("formatSubtaskPromptV2 includes file insights in shared_context", async () => {
1238
- const result = await formatSubtaskPromptV2({
1239
- bead_id: "test-123",
1240
- epic_id: "epic-456",
1241
- subtask_title: "Implement auth",
1242
- subtask_description: "Add authentication flow",
1243
- files: ["src/auth.ts", "src/user.ts"],
1244
- shared_context: "Original context from coordinator",
1245
- });
1246
-
1247
- // shared_context should be replaced and insights potentially included
1248
- // At minimum, the original context should be in the prompt
1249
- expect(result).toContain("Original context from coordinator");
1250
- });
1251
-
1252
- test("worker insights gracefully handle missing files parameter", async () => {
1253
- const { getPromptInsights } = await import("./swarm-prompts");
1254
-
1255
- // Should not throw with no files or domain
1256
- const result = await getPromptInsights({ role: "worker" });
1257
-
1258
- // Empty string is acceptable when no context to query
1259
- expect(typeof result).toBe("string");
1260
- });
1261
-
1262
- test("worker insights use swarm-insights getFileInsights", async () => {
1263
- // Verify the function is imported
1264
- const insights = await import("./swarm-insights");
1265
-
1266
- expect(insights.getFileInsights).toBeDefined();
1267
- expect(typeof insights.getFileInsights).toBe("function");
1268
- });
1269
- });
1270
- });