opencode-swarm-plugin 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.beads/issues.jsonl +443 -0
- package/README.md +76 -0
- package/dist/index.js +335 -5
- package/dist/plugin.js +332 -5
- package/examples/agents/swarm-planner.md +138 -0
- package/examples/commands/swarm.md +261 -34
- package/package.json +1 -1
- package/src/index.ts +12 -0
- package/src/learning.ts +13 -0
- package/src/swarm.integration.test.ts +284 -0
- package/src/swarm.ts +481 -3
|
@@ -2,49 +2,276 @@
|
|
|
2
2
|
description: Decompose task into parallel subtasks and coordinate agents
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
You are a swarm coordinator.
|
|
5
|
+
You are a swarm coordinator. Take a complex task, break it into beads, and unleash parallel agents.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Usage
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
```
|
|
10
|
+
/swarm <task description or bead-id>
|
|
11
|
+
/swarm --to-main <task> # Skip PR, push directly to main (use sparingly)
|
|
12
|
+
/swarm --no-sync <task> # Skip mid-task context sync (for simple independent tasks)
|
|
13
|
+
```
|
|
10
14
|
|
|
11
|
-
|
|
15
|
+
**Default behavior: Feature branch + PR with context sync.** All swarm work goes to a feature branch, agents share context mid-task, and creates a PR for review.
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
2. Analyze the task and create a decomposition with:
|
|
15
|
-
- Epic title and description
|
|
16
|
-
- 2-5 parallelizable subtasks with file assignments
|
|
17
|
-
- No file conflicts between subtasks
|
|
18
|
-
3. Create the epic using `beads_create_epic`
|
|
19
|
-
4. For each subtask:
|
|
20
|
-
- Mark bead in_progress with `beads_start`
|
|
21
|
-
- Use `swarm_spawn_subtask` to generate a prompt (includes Agent Mail/beads instructions)
|
|
22
|
-
- Spawn a Task agent with that prompt
|
|
23
|
-
5. Monitor progress via Agent Mail inbox
|
|
24
|
-
6. After all subtasks complete:
|
|
25
|
-
- Close the epic with `beads_close`
|
|
26
|
-
- Sync to git with `beads_sync`
|
|
17
|
+
## Step 1: Initialize Session
|
|
27
18
|
|
|
28
|
-
|
|
19
|
+
Use the plugin's agent-mail tools to register:
|
|
29
20
|
|
|
30
|
-
|
|
21
|
+
```
|
|
22
|
+
agentmail_init with project_path=$PWD, task_description="Swarm coordinator: <task>"
|
|
23
|
+
```
|
|
31
24
|
|
|
32
|
-
|
|
33
|
-
- **Beads** - `beads_update`, `beads_create`, `swarm_complete`
|
|
34
|
-
- All standard tools (Read, Write, Edit, Bash, etc.)
|
|
25
|
+
This returns your agent name and session state. Remember it.
|
|
35
26
|
|
|
36
|
-
|
|
27
|
+
## Step 2: Create Feature Branch
|
|
37
28
|
|
|
38
|
-
|
|
39
|
-
- Update bead status if blocked
|
|
40
|
-
- Create new beads for discovered issues
|
|
41
|
-
- Use `swarm_complete` when done
|
|
29
|
+
**CRITICAL: Never push directly to main.**
|
|
42
30
|
|
|
43
|
-
|
|
31
|
+
```bash
|
|
32
|
+
# Create branch from bead ID or task name
|
|
33
|
+
git checkout -b swarm/<bead-id> # e.g., swarm/trt-buddy-d7d
|
|
34
|
+
# Or for ad-hoc tasks:
|
|
35
|
+
git checkout -b swarm/<short-description> # e.g., swarm/contextual-checkins
|
|
44
36
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
- Check `agentmail_inbox` periodically for updates
|
|
48
|
-
- Use `agentmail_summarize_thread` to get thread overview
|
|
37
|
+
git push -u origin HEAD
|
|
38
|
+
```
|
|
49
39
|
|
|
50
|
-
|
|
40
|
+
## Step 3: Understand the Task
|
|
41
|
+
|
|
42
|
+
If given a bead-id:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
beads_query with id=<bead-id>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
If given a description, analyze it to understand scope.
|
|
49
|
+
|
|
50
|
+
## Step 4: Select Strategy & Decompose
|
|
51
|
+
|
|
52
|
+
### Option A: Use the Planner Agent (Recommended)
|
|
53
|
+
|
|
54
|
+
Spawn the `@swarm-planner` agent to handle decomposition:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
Task(
|
|
58
|
+
subagent_type="general",
|
|
59
|
+
description="Plan swarm decomposition",
|
|
60
|
+
prompt="You are @swarm-planner. Decompose this task: <task description>. Use swarm_select_strategy and swarm_plan_prompt to guide your decomposition. Return ONLY valid BeadTree JSON."
|
|
61
|
+
)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Option B: Manual Decomposition
|
|
65
|
+
|
|
66
|
+
1. **Select strategy**:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
swarm_select_strategy with task="<task description>"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
2. **Get planning prompt**:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
swarm_plan_prompt with task="<task description>", strategy="<selected or auto>"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
3. **Create decomposition** following the prompt guidelines
|
|
79
|
+
|
|
80
|
+
4. **Validate**:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
swarm_validate_decomposition with response="<your BeadTree JSON>"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Create Beads
|
|
87
|
+
|
|
88
|
+
Once you have a valid BeadTree:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
beads_create_epic with epic_title="<parent task>", subtasks=[{title, description, files, priority}...]
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Decomposition rules:**
|
|
95
|
+
|
|
96
|
+
- Each bead should be completable by one agent
|
|
97
|
+
- Beads should be independent (parallelizable) where possible
|
|
98
|
+
- If there are dependencies, order them in the subtasks array
|
|
99
|
+
- Aim for 3-7 beads per swarm (too few = not parallel, too many = coordination overhead)
|
|
100
|
+
|
|
101
|
+
## Step 5: Reserve Files
|
|
102
|
+
|
|
103
|
+
For each subtask, reserve the files it will touch:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
agentmail_reserve with paths=[<files>], reason="<bead-id>: <brief description>"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Conflict prevention:**
|
|
110
|
+
|
|
111
|
+
- No two agents should edit the same file
|
|
112
|
+
- If overlap exists, merge beads or sequence them
|
|
113
|
+
|
|
114
|
+
## Step 6: Spawn the Swarm
|
|
115
|
+
|
|
116
|
+
**CRITICAL: Spawn ALL agents in a SINGLE message with multiple Task calls.**
|
|
117
|
+
|
|
118
|
+
Use the prompt generator for each subtask:
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
swarm_spawn_subtask with bead_id="<bead-id>", epic_id="<epic-id>", subtask_title="<title>", subtask_description="<description>", files=[<files>], shared_context="Branch: swarm/<id>, sync_enabled: true"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Then spawn agents with the generated prompts:
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
Task(
|
|
128
|
+
subagent_type="general",
|
|
129
|
+
description="Swarm worker: <bead-title>",
|
|
130
|
+
prompt="<output from swarm_spawn_subtask>"
|
|
131
|
+
)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Spawn ALL agents in parallel in a single response.
|
|
135
|
+
|
|
136
|
+
## Step 7: Monitor Progress (unless --no-sync)
|
|
137
|
+
|
|
138
|
+
Check swarm status:
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
swarm_status with epic_id="<parent-bead-id>"
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Monitor inbox for progress updates:
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
agentmail_inbox
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**When you receive progress updates:**
|
|
151
|
+
|
|
152
|
+
1. **Review decisions made** - Are agents making compatible choices?
|
|
153
|
+
2. **Check for pattern conflicts** - Different approaches to the same problem?
|
|
154
|
+
3. **Identify shared concerns** - Common blockers or discoveries?
|
|
155
|
+
|
|
156
|
+
**If you spot incompatibilities, broadcast shared context:**
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
agentmail_send with to=["*"], subject="Coordinator Update", body="<guidance>", thread_id="<epic-id>", importance="high"
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Step 8: Collect Results
|
|
163
|
+
|
|
164
|
+
When agents complete, they send completion messages. Summarize the thread:
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
agentmail_summarize_thread with thread_id="<epic-id>"
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Step 9: Complete Swarm
|
|
171
|
+
|
|
172
|
+
Use the swarm completion tool:
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
swarm_complete with project_key=$PWD, agent_name=<YOUR_NAME>, bead_id="<epic-id>", summary="<what was accomplished>", files_touched=[<all files>]
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
This:
|
|
179
|
+
|
|
180
|
+
- Runs UBS bug scan on touched files
|
|
181
|
+
- Releases file reservations
|
|
182
|
+
- Closes the bead
|
|
183
|
+
- Records outcome for learning
|
|
184
|
+
|
|
185
|
+
Then sync beads:
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
beads_sync
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Step 10: Create PR
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
gh pr create --title "feat: <epic title>" --body "$(cat <<'EOF'
|
|
195
|
+
## Summary
|
|
196
|
+
<1-3 bullet points from swarm results>
|
|
197
|
+
|
|
198
|
+
## Beads Completed
|
|
199
|
+
- <bead-id>: <summary>
|
|
200
|
+
- <bead-id>: <summary>
|
|
201
|
+
|
|
202
|
+
## Files Changed
|
|
203
|
+
<aggregate list>
|
|
204
|
+
|
|
205
|
+
## Testing
|
|
206
|
+
- [ ] Type check passes
|
|
207
|
+
- [ ] Tests pass (if applicable)
|
|
208
|
+
EOF
|
|
209
|
+
)"
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Report summary:
|
|
213
|
+
|
|
214
|
+
```markdown
|
|
215
|
+
## Swarm Complete: <task>
|
|
216
|
+
|
|
217
|
+
### PR: #<number>
|
|
218
|
+
|
|
219
|
+
### Agents Spawned: N
|
|
220
|
+
|
|
221
|
+
### Beads Closed: N
|
|
222
|
+
|
|
223
|
+
### Work Completed
|
|
224
|
+
|
|
225
|
+
- [bead-id]: [summary]
|
|
226
|
+
|
|
227
|
+
### Files Changed
|
|
228
|
+
|
|
229
|
+
- [aggregate list]
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Failure Handling
|
|
233
|
+
|
|
234
|
+
If an agent fails:
|
|
235
|
+
|
|
236
|
+
- Check its messages: `agentmail_inbox`
|
|
237
|
+
- The bead remains in-progress
|
|
238
|
+
- Manually investigate or re-spawn
|
|
239
|
+
|
|
240
|
+
If file conflicts occur:
|
|
241
|
+
|
|
242
|
+
- Agent Mail reservations should prevent this
|
|
243
|
+
- If it happens, one agent needs to wait
|
|
244
|
+
|
|
245
|
+
## Direct-to-Main Mode (--to-main)
|
|
246
|
+
|
|
247
|
+
Only use when explicitly requested. Skips branch/PR:
|
|
248
|
+
|
|
249
|
+
- Trivial fixes across many files
|
|
250
|
+
- Automated migrations with high confidence
|
|
251
|
+
- User explicitly says "push to main"
|
|
252
|
+
|
|
253
|
+
## No-Sync Mode (--no-sync)
|
|
254
|
+
|
|
255
|
+
Skip mid-task context sharing when tasks are truly independent:
|
|
256
|
+
|
|
257
|
+
- Simple mechanical changes (find/replace, formatting, lint fixes)
|
|
258
|
+
- Tasks with zero integration points
|
|
259
|
+
- Completely separate feature areas with no shared types
|
|
260
|
+
|
|
261
|
+
In this mode:
|
|
262
|
+
|
|
263
|
+
- Agents skip the mid-task progress message
|
|
264
|
+
- Coordinator skips Step 7 (monitoring)
|
|
265
|
+
- Faster execution, less coordination overhead
|
|
266
|
+
|
|
267
|
+
**Default is sync ON** - prefer sharing context. Use `--no-sync` deliberately.
|
|
268
|
+
|
|
269
|
+
## Strategy Reference
|
|
270
|
+
|
|
271
|
+
| Strategy | Best For | Auto-Detected Keywords |
|
|
272
|
+
| ----------------- | --------------------------- | ---------------------------------------------- |
|
|
273
|
+
| **file-based** | Refactoring, migrations | refactor, migrate, rename, update all, convert |
|
|
274
|
+
| **feature-based** | New features, functionality | add, implement, build, create, feature, new |
|
|
275
|
+
| **risk-based** | Bug fixes, security | fix, bug, security, critical, urgent, hotfix |
|
|
276
|
+
|
|
277
|
+
Use `swarm_select_strategy` to see which strategy is recommended and why.
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -254,6 +254,12 @@ export {
|
|
|
254
254
|
* - swarmTools - Swarm orchestration tools
|
|
255
255
|
* - SwarmError, DecompositionError - Error classes
|
|
256
256
|
* - formatSubtaskPrompt, formatEvaluationPrompt - Prompt helpers
|
|
257
|
+
* - selectStrategy, formatStrategyGuidelines - Strategy selection helpers
|
|
258
|
+
* - STRATEGIES - Strategy definitions
|
|
259
|
+
*
|
|
260
|
+
* Types:
|
|
261
|
+
* - DecompositionStrategy - Strategy type union
|
|
262
|
+
* - StrategyDefinition - Strategy definition interface
|
|
257
263
|
*
|
|
258
264
|
* NOTE: Prompt template strings (DECOMPOSITION_PROMPT, etc.) are NOT exported
|
|
259
265
|
* to avoid confusing the plugin loader which tries to call all exports as functions
|
|
@@ -266,6 +272,12 @@ export {
|
|
|
266
272
|
formatSubtaskPromptV2,
|
|
267
273
|
formatEvaluationPrompt,
|
|
268
274
|
SUBTASK_PROMPT_V2,
|
|
275
|
+
// Strategy exports
|
|
276
|
+
STRATEGIES,
|
|
277
|
+
selectStrategy,
|
|
278
|
+
formatStrategyGuidelines,
|
|
279
|
+
type DecompositionStrategy,
|
|
280
|
+
type StrategyDefinition,
|
|
269
281
|
} from "./swarm";
|
|
270
282
|
|
|
271
283
|
/**
|
package/src/learning.ts
CHANGED
|
@@ -64,6 +64,16 @@ export const CriterionWeightSchema = z.object({
|
|
|
64
64
|
});
|
|
65
65
|
export type CriterionWeight = z.infer<typeof CriterionWeightSchema>;
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Decomposition strategies for tracking which approach was used
|
|
69
|
+
*/
|
|
70
|
+
export const DecompositionStrategySchema = z.enum([
|
|
71
|
+
"file-based",
|
|
72
|
+
"feature-based",
|
|
73
|
+
"risk-based",
|
|
74
|
+
]);
|
|
75
|
+
export type DecompositionStrategy = z.infer<typeof DecompositionStrategySchema>;
|
|
76
|
+
|
|
67
77
|
/**
|
|
68
78
|
* Outcome signals from a completed subtask
|
|
69
79
|
*
|
|
@@ -85,6 +95,8 @@ export const OutcomeSignalsSchema = z.object({
|
|
|
85
95
|
files_touched: z.array(z.string()).default([]),
|
|
86
96
|
/** Timestamp when outcome was recorded */
|
|
87
97
|
timestamp: z.string(), // ISO-8601
|
|
98
|
+
/** Decomposition strategy used for this task */
|
|
99
|
+
strategy: DecompositionStrategySchema.optional(),
|
|
88
100
|
});
|
|
89
101
|
export type OutcomeSignals = z.infer<typeof OutcomeSignalsSchema>;
|
|
90
102
|
|
|
@@ -435,4 +447,5 @@ export const learningSchemas = {
|
|
|
435
447
|
CriterionWeightSchema,
|
|
436
448
|
OutcomeSignalsSchema,
|
|
437
449
|
ScoredOutcomeSchema,
|
|
450
|
+
DecompositionStrategySchema,
|
|
438
451
|
};
|
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
swarm_complete,
|
|
17
17
|
swarm_subtask_prompt,
|
|
18
18
|
swarm_evaluation_prompt,
|
|
19
|
+
swarm_select_strategy,
|
|
20
|
+
swarm_plan_prompt,
|
|
19
21
|
formatSubtaskPromptV2,
|
|
20
22
|
SUBTASK_PROMPT_V2,
|
|
21
23
|
} from "./swarm";
|
|
@@ -119,6 +121,288 @@ describe("swarm_decompose", () => {
|
|
|
119
121
|
});
|
|
120
122
|
});
|
|
121
123
|
|
|
124
|
+
// ============================================================================
|
|
125
|
+
// Strategy Selection Tests
|
|
126
|
+
// ============================================================================
|
|
127
|
+
|
|
128
|
+
describe("swarm_select_strategy", () => {
|
|
129
|
+
it("selects feature-based for 'add' tasks", async () => {
|
|
130
|
+
const result = await swarm_select_strategy.execute(
|
|
131
|
+
{
|
|
132
|
+
task: "Add user authentication with OAuth",
|
|
133
|
+
},
|
|
134
|
+
mockContext,
|
|
135
|
+
);
|
|
136
|
+
const parsed = JSON.parse(result);
|
|
137
|
+
|
|
138
|
+
expect(parsed.strategy).toBe("feature-based");
|
|
139
|
+
expect(parsed.confidence).toBeGreaterThan(0.5);
|
|
140
|
+
expect(parsed.reasoning).toContain("add");
|
|
141
|
+
expect(parsed.guidelines).toBeInstanceOf(Array);
|
|
142
|
+
expect(parsed.anti_patterns).toBeInstanceOf(Array);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("selects file-based for 'refactor' tasks", async () => {
|
|
146
|
+
const result = await swarm_select_strategy.execute(
|
|
147
|
+
{
|
|
148
|
+
task: "Refactor all components to use new API",
|
|
149
|
+
},
|
|
150
|
+
mockContext,
|
|
151
|
+
);
|
|
152
|
+
const parsed = JSON.parse(result);
|
|
153
|
+
|
|
154
|
+
expect(parsed.strategy).toBe("file-based");
|
|
155
|
+
expect(parsed.confidence).toBeGreaterThanOrEqual(0.5);
|
|
156
|
+
expect(parsed.reasoning).toContain("refactor");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("selects risk-based for 'fix security' tasks", async () => {
|
|
160
|
+
const result = await swarm_select_strategy.execute(
|
|
161
|
+
{
|
|
162
|
+
task: "Fix security vulnerability in authentication",
|
|
163
|
+
},
|
|
164
|
+
mockContext,
|
|
165
|
+
);
|
|
166
|
+
const parsed = JSON.parse(result);
|
|
167
|
+
|
|
168
|
+
expect(parsed.strategy).toBe("risk-based");
|
|
169
|
+
expect(parsed.confidence).toBeGreaterThan(0.5);
|
|
170
|
+
// Should match either 'fix' or 'security'
|
|
171
|
+
expect(
|
|
172
|
+
parsed.reasoning.includes("fix") || parsed.reasoning.includes("security"),
|
|
173
|
+
).toBe(true);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("defaults to feature-based when no keywords match", async () => {
|
|
177
|
+
const result = await swarm_select_strategy.execute(
|
|
178
|
+
{
|
|
179
|
+
task: "Something completely unrelated without keywords",
|
|
180
|
+
},
|
|
181
|
+
mockContext,
|
|
182
|
+
);
|
|
183
|
+
const parsed = JSON.parse(result);
|
|
184
|
+
|
|
185
|
+
expect(parsed.strategy).toBe("feature-based");
|
|
186
|
+
// Confidence should be lower without keyword matches
|
|
187
|
+
expect(parsed.confidence).toBeLessThanOrEqual(0.6);
|
|
188
|
+
expect(parsed.reasoning).toContain("Defaulting to feature-based");
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("includes confidence score and reasoning", async () => {
|
|
192
|
+
const result = await swarm_select_strategy.execute(
|
|
193
|
+
{
|
|
194
|
+
task: "Implement new dashboard feature",
|
|
195
|
+
},
|
|
196
|
+
mockContext,
|
|
197
|
+
);
|
|
198
|
+
const parsed = JSON.parse(result);
|
|
199
|
+
|
|
200
|
+
expect(parsed).toHaveProperty("strategy");
|
|
201
|
+
expect(parsed).toHaveProperty("confidence");
|
|
202
|
+
expect(parsed).toHaveProperty("reasoning");
|
|
203
|
+
expect(parsed).toHaveProperty("description");
|
|
204
|
+
expect(typeof parsed.confidence).toBe("number");
|
|
205
|
+
expect(parsed.confidence).toBeGreaterThanOrEqual(0);
|
|
206
|
+
expect(parsed.confidence).toBeLessThanOrEqual(1);
|
|
207
|
+
expect(typeof parsed.reasoning).toBe("string");
|
|
208
|
+
expect(parsed.reasoning.length).toBeGreaterThan(0);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("includes alternative strategies with scores", async () => {
|
|
212
|
+
const result = await swarm_select_strategy.execute(
|
|
213
|
+
{
|
|
214
|
+
task: "Build new payment processing module",
|
|
215
|
+
},
|
|
216
|
+
mockContext,
|
|
217
|
+
);
|
|
218
|
+
const parsed = JSON.parse(result);
|
|
219
|
+
|
|
220
|
+
expect(parsed).toHaveProperty("alternatives");
|
|
221
|
+
expect(parsed.alternatives).toBeInstanceOf(Array);
|
|
222
|
+
expect(parsed.alternatives.length).toBe(2); // 3 strategies - 1 selected = 2 alternatives
|
|
223
|
+
|
|
224
|
+
for (const alt of parsed.alternatives) {
|
|
225
|
+
expect(alt).toHaveProperty("strategy");
|
|
226
|
+
expect(alt).toHaveProperty("description");
|
|
227
|
+
expect(alt).toHaveProperty("score");
|
|
228
|
+
expect(["file-based", "feature-based", "risk-based"]).toContain(
|
|
229
|
+
alt.strategy,
|
|
230
|
+
);
|
|
231
|
+
expect(typeof alt.score).toBe("number");
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("includes codebase context in reasoning when provided", async () => {
|
|
236
|
+
const result = await swarm_select_strategy.execute(
|
|
237
|
+
{
|
|
238
|
+
task: "Add new API endpoint",
|
|
239
|
+
codebase_context: "Using Express.js with TypeScript and PostgreSQL",
|
|
240
|
+
},
|
|
241
|
+
mockContext,
|
|
242
|
+
);
|
|
243
|
+
const parsed = JSON.parse(result);
|
|
244
|
+
|
|
245
|
+
expect(parsed.reasoning).toContain("Express.js");
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// ============================================================================
|
|
250
|
+
// Planning Prompt Tests
|
|
251
|
+
// ============================================================================
|
|
252
|
+
|
|
253
|
+
describe("swarm_plan_prompt", () => {
|
|
254
|
+
it("auto-selects strategy when not specified", async () => {
|
|
255
|
+
const result = await swarm_plan_prompt.execute(
|
|
256
|
+
{
|
|
257
|
+
task: "Add user settings page",
|
|
258
|
+
max_subtasks: 3,
|
|
259
|
+
query_cass: false, // Disable CASS to isolate test
|
|
260
|
+
},
|
|
261
|
+
mockContext,
|
|
262
|
+
);
|
|
263
|
+
const parsed = JSON.parse(result);
|
|
264
|
+
|
|
265
|
+
expect(parsed).toHaveProperty("prompt");
|
|
266
|
+
expect(parsed).toHaveProperty("strategy");
|
|
267
|
+
expect(parsed.strategy).toHaveProperty("selected");
|
|
268
|
+
expect(parsed.strategy).toHaveProperty("reasoning");
|
|
269
|
+
expect(parsed.strategy.selected).toBe("feature-based"); // 'add' keyword
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("uses explicit strategy when provided", async () => {
|
|
273
|
+
const result = await swarm_plan_prompt.execute(
|
|
274
|
+
{
|
|
275
|
+
task: "Do something",
|
|
276
|
+
strategy: "risk-based",
|
|
277
|
+
max_subtasks: 3,
|
|
278
|
+
query_cass: false,
|
|
279
|
+
},
|
|
280
|
+
mockContext,
|
|
281
|
+
);
|
|
282
|
+
const parsed = JSON.parse(result);
|
|
283
|
+
|
|
284
|
+
expect(parsed.strategy.selected).toBe("risk-based");
|
|
285
|
+
expect(parsed.strategy.reasoning).toContain("User-specified strategy");
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("includes strategy guidelines in prompt", async () => {
|
|
289
|
+
const result = await swarm_plan_prompt.execute(
|
|
290
|
+
{
|
|
291
|
+
task: "Refactor the codebase",
|
|
292
|
+
max_subtasks: 4,
|
|
293
|
+
query_cass: false,
|
|
294
|
+
},
|
|
295
|
+
mockContext,
|
|
296
|
+
);
|
|
297
|
+
const parsed = JSON.parse(result);
|
|
298
|
+
|
|
299
|
+
// Prompt should contain strategy-specific guidelines
|
|
300
|
+
expect(parsed.prompt).toContain("## Strategy:");
|
|
301
|
+
expect(parsed.prompt).toContain("### Guidelines");
|
|
302
|
+
expect(parsed.prompt).toContain("### Anti-Patterns");
|
|
303
|
+
expect(parsed.prompt).toContain("### Examples");
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it("includes anti-patterns in output", async () => {
|
|
307
|
+
const result = await swarm_plan_prompt.execute(
|
|
308
|
+
{
|
|
309
|
+
task: "Build new feature",
|
|
310
|
+
max_subtasks: 3,
|
|
311
|
+
query_cass: false,
|
|
312
|
+
},
|
|
313
|
+
mockContext,
|
|
314
|
+
);
|
|
315
|
+
const parsed = JSON.parse(result);
|
|
316
|
+
|
|
317
|
+
expect(parsed.strategy).toHaveProperty("anti_patterns");
|
|
318
|
+
expect(parsed.strategy.anti_patterns).toBeInstanceOf(Array);
|
|
319
|
+
expect(parsed.strategy.anti_patterns.length).toBeGreaterThan(0);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it("returns expected_schema and validation_note", async () => {
|
|
323
|
+
const result = await swarm_plan_prompt.execute(
|
|
324
|
+
{
|
|
325
|
+
task: "Some task",
|
|
326
|
+
max_subtasks: 5,
|
|
327
|
+
query_cass: false,
|
|
328
|
+
},
|
|
329
|
+
mockContext,
|
|
330
|
+
);
|
|
331
|
+
const parsed = JSON.parse(result);
|
|
332
|
+
|
|
333
|
+
expect(parsed).toHaveProperty("expected_schema", "BeadTree");
|
|
334
|
+
expect(parsed).toHaveProperty("validation_note");
|
|
335
|
+
expect(parsed.validation_note).toContain("swarm_validate_decomposition");
|
|
336
|
+
expect(parsed).toHaveProperty("schema_hint");
|
|
337
|
+
expect(parsed.schema_hint).toHaveProperty("epic");
|
|
338
|
+
expect(parsed.schema_hint).toHaveProperty("subtasks");
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it("reports CASS status in output (queried flag)", async () => {
|
|
342
|
+
// Test with CASS disabled
|
|
343
|
+
const resultDisabled = await swarm_plan_prompt.execute(
|
|
344
|
+
{
|
|
345
|
+
task: "Add feature",
|
|
346
|
+
max_subtasks: 3,
|
|
347
|
+
query_cass: false,
|
|
348
|
+
},
|
|
349
|
+
mockContext,
|
|
350
|
+
);
|
|
351
|
+
const parsedDisabled = JSON.parse(resultDisabled);
|
|
352
|
+
|
|
353
|
+
expect(parsedDisabled).toHaveProperty("cass_history");
|
|
354
|
+
expect(parsedDisabled.cass_history.queried).toBe(false);
|
|
355
|
+
|
|
356
|
+
// Test with CASS enabled (may or may not be available)
|
|
357
|
+
const resultEnabled = await swarm_plan_prompt.execute(
|
|
358
|
+
{
|
|
359
|
+
task: "Add feature",
|
|
360
|
+
max_subtasks: 3,
|
|
361
|
+
query_cass: true,
|
|
362
|
+
},
|
|
363
|
+
mockContext,
|
|
364
|
+
);
|
|
365
|
+
const parsedEnabled = JSON.parse(resultEnabled);
|
|
366
|
+
|
|
367
|
+
expect(parsedEnabled).toHaveProperty("cass_history");
|
|
368
|
+
expect(parsedEnabled.cass_history).toHaveProperty("queried");
|
|
369
|
+
// If CASS is unavailable, queried will be false with reason
|
|
370
|
+
if (!parsedEnabled.cass_history.queried) {
|
|
371
|
+
expect(parsedEnabled.cass_history).toHaveProperty("reason");
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it("includes context in prompt when provided", async () => {
|
|
376
|
+
const result = await swarm_plan_prompt.execute(
|
|
377
|
+
{
|
|
378
|
+
task: "Add user profile",
|
|
379
|
+
max_subtasks: 3,
|
|
380
|
+
context: "We use Next.js App Router with server components",
|
|
381
|
+
query_cass: false,
|
|
382
|
+
},
|
|
383
|
+
mockContext,
|
|
384
|
+
);
|
|
385
|
+
const parsed = JSON.parse(result);
|
|
386
|
+
|
|
387
|
+
expect(parsed.prompt).toContain("Next.js App Router");
|
|
388
|
+
expect(parsed.prompt).toContain("server components");
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it("includes max_subtasks in prompt", async () => {
|
|
392
|
+
const result = await swarm_plan_prompt.execute(
|
|
393
|
+
{
|
|
394
|
+
task: "Build something",
|
|
395
|
+
max_subtasks: 7,
|
|
396
|
+
query_cass: false,
|
|
397
|
+
},
|
|
398
|
+
mockContext,
|
|
399
|
+
);
|
|
400
|
+
const parsed = JSON.parse(result);
|
|
401
|
+
|
|
402
|
+
expect(parsed.prompt).toContain("2-7 independent subtasks");
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
|
|
122
406
|
describe("swarm_validate_decomposition", () => {
|
|
123
407
|
it("validates correct BeadTree", async () => {
|
|
124
408
|
const validBeadTree = JSON.stringify({
|