opencode-swarm-plugin 0.30.0 → 0.30.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.
- package/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +47 -0
- package/README.md +3 -6
- package/bin/swarm.ts +151 -22
- package/dist/hive.d.ts.map +1 -1
- package/dist/index.d.ts +94 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18826 -3467
- package/dist/memory-tools.d.ts +209 -0
- package/dist/memory-tools.d.ts.map +1 -0
- package/dist/memory.d.ts +124 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/plugin.js +18766 -3418
- package/dist/schemas/index.d.ts +7 -0
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/worker-handoff.d.ts +78 -0
- package/dist/schemas/worker-handoff.d.ts.map +1 -0
- package/dist/swarm-orchestrate.d.ts +50 -0
- package/dist/swarm-orchestrate.d.ts.map +1 -1
- package/dist/swarm-prompts.d.ts +1 -1
- package/dist/swarm-prompts.d.ts.map +1 -1
- package/dist/swarm-review.d.ts +4 -0
- package/dist/swarm-review.d.ts.map +1 -1
- package/docs/planning/ADR-008-worker-handoff-protocol.md +293 -0
- package/package.json +3 -1
- package/src/hive.integration.test.ts +114 -0
- package/src/hive.ts +33 -22
- package/src/index.ts +38 -3
- package/src/memory-tools.test.ts +111 -0
- package/src/memory-tools.ts +273 -0
- package/src/memory.integration.test.ts +266 -0
- package/src/memory.test.ts +334 -0
- package/src/memory.ts +441 -0
- package/src/schemas/index.ts +18 -0
- package/src/schemas/worker-handoff.test.ts +271 -0
- package/src/schemas/worker-handoff.ts +131 -0
- package/src/swarm-orchestrate.ts +262 -24
- package/src/swarm-prompts.ts +48 -5
- package/src/swarm-review.ts +7 -0
- package/src/swarm.integration.test.ts +386 -9
package/src/swarm-orchestrate.ts
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
import { tool } from "@opencode-ai/plugin";
|
|
23
23
|
import { z } from "zod";
|
|
24
|
+
import { minimatch } from "minimatch";
|
|
24
25
|
import {
|
|
25
26
|
type AgentProgress,
|
|
26
27
|
AgentProgressSchema,
|
|
@@ -32,6 +33,10 @@ import {
|
|
|
32
33
|
type SwarmStatus,
|
|
33
34
|
SwarmStatusSchema,
|
|
34
35
|
} from "./schemas";
|
|
36
|
+
import {
|
|
37
|
+
type WorkerHandoff,
|
|
38
|
+
WorkerHandoffSchema,
|
|
39
|
+
} from "./schemas/worker-handoff";
|
|
35
40
|
import {
|
|
36
41
|
getSwarmInbox,
|
|
37
42
|
releaseSwarmFiles,
|
|
@@ -82,6 +87,182 @@ import {
|
|
|
82
87
|
// Helper Functions
|
|
83
88
|
// ============================================================================
|
|
84
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Generate a WorkerHandoff object from subtask parameters
|
|
92
|
+
*
|
|
93
|
+
* Creates a machine-readable contract that replaces prose instructions in SUBTASK_PROMPT_V2.
|
|
94
|
+
* Workers receive typed handoffs with explicit files, criteria, and escalation paths.
|
|
95
|
+
*
|
|
96
|
+
* @param params - Subtask parameters
|
|
97
|
+
* @returns WorkerHandoff object validated against schema
|
|
98
|
+
*/
|
|
99
|
+
export function generateWorkerHandoff(params: {
|
|
100
|
+
task_id: string;
|
|
101
|
+
files_owned: string[];
|
|
102
|
+
files_readonly?: string[];
|
|
103
|
+
dependencies_completed?: string[];
|
|
104
|
+
success_criteria?: string[];
|
|
105
|
+
epic_summary: string;
|
|
106
|
+
your_role: string;
|
|
107
|
+
what_others_did?: string;
|
|
108
|
+
what_comes_next?: string;
|
|
109
|
+
}): WorkerHandoff {
|
|
110
|
+
const handoff: WorkerHandoff = {
|
|
111
|
+
contract: {
|
|
112
|
+
task_id: params.task_id,
|
|
113
|
+
files_owned: params.files_owned,
|
|
114
|
+
files_readonly: params.files_readonly || [],
|
|
115
|
+
dependencies_completed: params.dependencies_completed || [],
|
|
116
|
+
success_criteria: params.success_criteria || [
|
|
117
|
+
"All files compile without errors",
|
|
118
|
+
"Tests pass for modified code",
|
|
119
|
+
"Code follows project patterns",
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
context: {
|
|
123
|
+
epic_summary: params.epic_summary,
|
|
124
|
+
your_role: params.your_role,
|
|
125
|
+
what_others_did: params.what_others_did || "",
|
|
126
|
+
what_comes_next: params.what_comes_next || "",
|
|
127
|
+
},
|
|
128
|
+
escalation: {
|
|
129
|
+
blocked_contact: "coordinator",
|
|
130
|
+
scope_change_protocol:
|
|
131
|
+
"Send swarmmail_send(to=['coordinator'], subject='Scope change request: <task_id>', importance='high') and wait for approval before expanding beyond files_owned",
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Validate against schema
|
|
136
|
+
return WorkerHandoffSchema.parse(handoff);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Validate that files_touched is a subset of files_owned (supports globs)
|
|
141
|
+
*
|
|
142
|
+
* Checks contract compliance - workers should only modify files they own.
|
|
143
|
+
* Glob patterns in files_owned are matched against files_touched paths.
|
|
144
|
+
*
|
|
145
|
+
* @param files_touched - Actual files modified by the worker
|
|
146
|
+
* @param files_owned - Files the worker is allowed to modify (may include globs)
|
|
147
|
+
* @returns Validation result with violations list
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```typescript
|
|
151
|
+
* // Exact match - passes
|
|
152
|
+
* validateContract(["src/a.ts"], ["src/a.ts", "src/b.ts"])
|
|
153
|
+
* // => { valid: true, violations: [] }
|
|
154
|
+
*
|
|
155
|
+
* // Glob match - passes
|
|
156
|
+
* validateContract(["src/auth/service.ts"], ["src/auth/**"])
|
|
157
|
+
* // => { valid: true, violations: [] }
|
|
158
|
+
*
|
|
159
|
+
* // Violation - fails
|
|
160
|
+
* validateContract(["src/other.ts"], ["src/auth/**"])
|
|
161
|
+
* // => { valid: false, violations: ["src/other.ts"] }
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
export function validateContract(
|
|
165
|
+
files_touched: string[],
|
|
166
|
+
files_owned: string[]
|
|
167
|
+
): { valid: boolean; violations: string[] } {
|
|
168
|
+
// Empty files_touched is valid (read-only work)
|
|
169
|
+
if (files_touched.length === 0) {
|
|
170
|
+
return { valid: true, violations: [] };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const violations: string[] = [];
|
|
174
|
+
|
|
175
|
+
for (const touchedFile of files_touched) {
|
|
176
|
+
let matched = false;
|
|
177
|
+
|
|
178
|
+
for (const ownedPattern of files_owned) {
|
|
179
|
+
// Check if pattern is a glob or exact match
|
|
180
|
+
if (ownedPattern.includes("*") || ownedPattern.includes("?")) {
|
|
181
|
+
// Glob pattern - use minimatch
|
|
182
|
+
if (minimatch(touchedFile, ownedPattern)) {
|
|
183
|
+
matched = true;
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
// Exact match
|
|
188
|
+
if (touchedFile === ownedPattern) {
|
|
189
|
+
matched = true;
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!matched) {
|
|
196
|
+
violations.push(touchedFile);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
valid: violations.length === 0,
|
|
202
|
+
violations,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Get files_owned for a subtask from DecompositionGeneratedEvent
|
|
208
|
+
*
|
|
209
|
+
* Queries the event log for the decomposition that created this epic,
|
|
210
|
+
* then extracts the files array for the matching subtask.
|
|
211
|
+
*
|
|
212
|
+
* @param projectKey - Project path
|
|
213
|
+
* @param epicId - Epic ID
|
|
214
|
+
* @param subtaskId - Subtask cell ID
|
|
215
|
+
* @returns Array of file patterns this subtask owns, or null if not found
|
|
216
|
+
*/
|
|
217
|
+
async function getSubtaskFilesOwned(
|
|
218
|
+
projectKey: string,
|
|
219
|
+
epicId: string,
|
|
220
|
+
subtaskId: string
|
|
221
|
+
): Promise<string[] | null> {
|
|
222
|
+
try {
|
|
223
|
+
// Import readEvents from swarm-mail
|
|
224
|
+
const { readEvents } = await import("swarm-mail");
|
|
225
|
+
|
|
226
|
+
// Query for decomposition_generated events for this epic
|
|
227
|
+
const events = await readEvents({
|
|
228
|
+
projectKey,
|
|
229
|
+
types: ["decomposition_generated"],
|
|
230
|
+
}, projectKey);
|
|
231
|
+
|
|
232
|
+
// Find the event for this epic
|
|
233
|
+
const decompositionEvent = events.find((e: any) =>
|
|
234
|
+
e.type === "decomposition_generated" && e.epic_id === epicId
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
if (!decompositionEvent) {
|
|
238
|
+
console.warn(`[swarm_complete] No decomposition event found for epic ${epicId}`);
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Extract subtask index from subtask ID (e.g., "bd-abc123.0" -> 0)
|
|
243
|
+
// Subtask IDs follow pattern: epicId.index
|
|
244
|
+
const subtaskMatch = subtaskId.match(/\.(\d+)$/);
|
|
245
|
+
if (!subtaskMatch) {
|
|
246
|
+
console.warn(`[swarm_complete] Could not parse subtask index from ${subtaskId}`);
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const subtaskIndex = parseInt(subtaskMatch[1], 10);
|
|
251
|
+
const subtasks = (decompositionEvent as any).subtasks || [];
|
|
252
|
+
|
|
253
|
+
if (subtaskIndex >= subtasks.length) {
|
|
254
|
+
console.warn(`[swarm_complete] Subtask index ${subtaskIndex} out of range (${subtasks.length} subtasks)`);
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const subtask = subtasks[subtaskIndex];
|
|
259
|
+
return subtask.files || [];
|
|
260
|
+
} catch (error) {
|
|
261
|
+
console.error(`[swarm_complete] Failed to query subtask files:`, error);
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
85
266
|
/**
|
|
86
267
|
* Query beads for subtasks of an epic using HiveAdapter (not bd CLI)
|
|
87
268
|
*/
|
|
@@ -1109,17 +1290,16 @@ export const swarm_complete = tool({
|
|
|
1109
1290
|
if (!reviewStatusResult.reviewed) {
|
|
1110
1291
|
return JSON.stringify(
|
|
1111
1292
|
{
|
|
1112
|
-
success:
|
|
1113
|
-
|
|
1293
|
+
success: true,
|
|
1294
|
+
status: "pending_review",
|
|
1114
1295
|
review_status: reviewStatusResult,
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
Or use skip_review=true to bypass (not recommended for production work).`,
|
|
1296
|
+
message: "Task completed but awaiting coordinator review before finalization.",
|
|
1297
|
+
next_steps: [
|
|
1298
|
+
`Request review with swarm_review(project_key="${args.project_key}", epic_id="${epicId}", task_id="${args.bead_id}", files_touched=[...])`,
|
|
1299
|
+
"Wait for coordinator to review and approve with swarm_review_feedback",
|
|
1300
|
+
"Once approved, call swarm_complete again to finalize",
|
|
1301
|
+
"Or use skip_review=true to bypass (not recommended for production work)",
|
|
1302
|
+
],
|
|
1123
1303
|
},
|
|
1124
1304
|
null,
|
|
1125
1305
|
2,
|
|
@@ -1129,15 +1309,15 @@ Or use skip_review=true to bypass (not recommended for production work).`,
|
|
|
1129
1309
|
// Review was attempted but not approved
|
|
1130
1310
|
return JSON.stringify(
|
|
1131
1311
|
{
|
|
1132
|
-
success:
|
|
1133
|
-
|
|
1312
|
+
success: true,
|
|
1313
|
+
status: "needs_changes",
|
|
1134
1314
|
review_status: reviewStatusResult,
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1315
|
+
message: `Task reviewed but changes requested. ${reviewStatusResult.remaining_attempts} attempt(s) remaining.`,
|
|
1316
|
+
next_steps: [
|
|
1317
|
+
"Address the feedback from the reviewer",
|
|
1318
|
+
`Request another review with swarm_review(project_key="${args.project_key}", epic_id="${epicId}", task_id="${args.bead_id}", files_touched=[...])`,
|
|
1319
|
+
"Once approved, call swarm_complete again to finalize",
|
|
1320
|
+
],
|
|
1141
1321
|
},
|
|
1142
1322
|
null,
|
|
1143
1323
|
2,
|
|
@@ -1147,15 +1327,14 @@ Or use skip_review=true to bypass (not recommended for production work).`,
|
|
|
1147
1327
|
|
|
1148
1328
|
try {
|
|
1149
1329
|
// Validate bead_id exists and is not already closed (EARLY validation)
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
.replace(/\\/g, "-");
|
|
1330
|
+
// NOTE: Use args.project_key directly - cells are stored with the original path
|
|
1331
|
+
// (e.g., "/Users/joel/Code/project"), not a mangled version.
|
|
1153
1332
|
|
|
1154
1333
|
// Use HiveAdapter for validation (not bd CLI)
|
|
1155
1334
|
const adapter = await getHiveAdapter(args.project_key);
|
|
1156
1335
|
|
|
1157
1336
|
// 1. Check if bead exists
|
|
1158
|
-
const cell = await adapter.getCell(
|
|
1337
|
+
const cell = await adapter.getCell(args.project_key, args.bead_id);
|
|
1159
1338
|
if (!cell) {
|
|
1160
1339
|
return JSON.stringify({
|
|
1161
1340
|
success: false,
|
|
@@ -1180,14 +1359,14 @@ Or use skip_review=true to bypass (not recommended for production work).`,
|
|
|
1180
1359
|
|
|
1181
1360
|
try {
|
|
1182
1361
|
const agent = await getAgent(
|
|
1183
|
-
|
|
1362
|
+
args.project_key,
|
|
1184
1363
|
args.agent_name,
|
|
1185
1364
|
args.project_key,
|
|
1186
1365
|
);
|
|
1187
1366
|
agentRegistered = agent !== null;
|
|
1188
1367
|
|
|
1189
1368
|
if (!agentRegistered) {
|
|
1190
|
-
registrationWarning = `⚠️ WARNING: Agent '${args.agent_name}' was NOT registered in swarm-mail for project '${
|
|
1369
|
+
registrationWarning = `⚠️ WARNING: Agent '${args.agent_name}' was NOT registered in swarm-mail for project '${args.project_key}'.
|
|
1191
1370
|
|
|
1192
1371
|
This usually means you skipped the MANDATORY swarmmail_init step.
|
|
1193
1372
|
|
|
@@ -1286,6 +1465,48 @@ Continuing with completion, but this should be fixed for future subtasks.`;
|
|
|
1286
1465
|
}
|
|
1287
1466
|
}
|
|
1288
1467
|
|
|
1468
|
+
// Contract Validation - check files_touched against WorkerHandoff contract
|
|
1469
|
+
let contractValidation: { valid: boolean; violations: string[] } | null = null;
|
|
1470
|
+
let contractWarning: string | undefined;
|
|
1471
|
+
|
|
1472
|
+
if (args.files_touched && args.files_touched.length > 0) {
|
|
1473
|
+
// Extract epic ID from subtask ID
|
|
1474
|
+
const isSubtask = args.bead_id.includes(".");
|
|
1475
|
+
|
|
1476
|
+
if (isSubtask) {
|
|
1477
|
+
const epicId = args.bead_id.split(".")[0];
|
|
1478
|
+
|
|
1479
|
+
// Query decomposition event for files_owned
|
|
1480
|
+
const filesOwned = await getSubtaskFilesOwned(
|
|
1481
|
+
args.project_key,
|
|
1482
|
+
epicId,
|
|
1483
|
+
args.bead_id
|
|
1484
|
+
);
|
|
1485
|
+
|
|
1486
|
+
if (filesOwned) {
|
|
1487
|
+
contractValidation = validateContract(args.files_touched, filesOwned);
|
|
1488
|
+
|
|
1489
|
+
if (!contractValidation.valid) {
|
|
1490
|
+
// Contract violation - log warning (don't block completion)
|
|
1491
|
+
contractWarning = `⚠️ CONTRACT VIOLATION: Modified files outside owned scope
|
|
1492
|
+
|
|
1493
|
+
**Files owned**: ${filesOwned.join(", ")}
|
|
1494
|
+
**Files touched**: ${args.files_touched.join(", ")}
|
|
1495
|
+
**Violations**: ${contractValidation.violations.join(", ")}
|
|
1496
|
+
|
|
1497
|
+
This indicates scope creep - the worker modified files they weren't assigned.
|
|
1498
|
+
This will be recorded as a negative learning signal.`;
|
|
1499
|
+
|
|
1500
|
+
console.warn(`[swarm_complete] ${contractWarning}`);
|
|
1501
|
+
} else {
|
|
1502
|
+
console.log(`[swarm_complete] Contract validation passed: all ${args.files_touched.length} files within owned scope`);
|
|
1503
|
+
}
|
|
1504
|
+
} else {
|
|
1505
|
+
console.warn(`[swarm_complete] Could not retrieve files_owned for contract validation - skipping`);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1289
1510
|
// Parse and validate evaluation if provided
|
|
1290
1511
|
let parsedEvaluation: Evaluation | undefined;
|
|
1291
1512
|
if (args.evaluation) {
|
|
@@ -1367,6 +1588,8 @@ Continuing with completion, but this should be fixed for future subtasks.`;
|
|
|
1367
1588
|
error_count: args.error_count || 0,
|
|
1368
1589
|
retry_count: args.retry_count || 0,
|
|
1369
1590
|
success: true,
|
|
1591
|
+
scope_violation: contractValidation ? !contractValidation.valid : undefined,
|
|
1592
|
+
violation_files: contractValidation?.violations,
|
|
1370
1593
|
});
|
|
1371
1594
|
await appendEvent(event, args.project_key);
|
|
1372
1595
|
} catch (error) {
|
|
@@ -1544,6 +1767,21 @@ Files touched: ${args.files_touched?.join(", ") || "none recorded"}`,
|
|
|
1544
1767
|
? "Learning automatically stored in semantic-memory"
|
|
1545
1768
|
: `Failed to store: ${memoryError}. Learning lost unless semantic-memory is available.`,
|
|
1546
1769
|
},
|
|
1770
|
+
// Contract validation result
|
|
1771
|
+
contract_validation: contractValidation
|
|
1772
|
+
? {
|
|
1773
|
+
validated: true,
|
|
1774
|
+
passed: contractValidation.valid,
|
|
1775
|
+
violations: contractValidation.violations,
|
|
1776
|
+
warning: contractWarning,
|
|
1777
|
+
note: contractValidation.valid
|
|
1778
|
+
? "All files within owned scope"
|
|
1779
|
+
: "Scope violation detected - recorded as negative learning signal",
|
|
1780
|
+
}
|
|
1781
|
+
: {
|
|
1782
|
+
validated: false,
|
|
1783
|
+
reason: "No files_owned contract found (non-epic subtask or decomposition event missing)",
|
|
1784
|
+
},
|
|
1547
1785
|
};
|
|
1548
1786
|
|
|
1549
1787
|
return JSON.stringify(response, null, 2);
|
package/src/swarm-prompts.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { tool } from "@opencode-ai/plugin";
|
|
16
|
+
import { generateWorkerHandoff } from "./swarm-orchestrate";
|
|
16
17
|
|
|
17
18
|
// ============================================================================
|
|
18
19
|
// Prompt Templates
|
|
@@ -326,10 +327,32 @@ swarmmail_reserve(
|
|
|
326
327
|
|
|
327
328
|
**Workers reserve their own files.** This prevents edit conflicts with other agents.
|
|
328
329
|
|
|
329
|
-
### Step 5: Do the Work
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
330
|
+
### Step 5: Do the Work (TDD MANDATORY)
|
|
331
|
+
|
|
332
|
+
**Follow RED → GREEN → REFACTOR. No exceptions.**
|
|
333
|
+
|
|
334
|
+
1. **RED**: Write a failing test that describes the expected behavior
|
|
335
|
+
- Test MUST fail before you write implementation
|
|
336
|
+
- If test passes immediately, your test is wrong
|
|
337
|
+
|
|
338
|
+
2. **GREEN**: Write minimal code to make the test pass
|
|
339
|
+
- Don't over-engineer - just make it green
|
|
340
|
+
- Hardcode if needed, refactor later
|
|
341
|
+
|
|
342
|
+
3. **REFACTOR**: Clean up while tests stay green
|
|
343
|
+
- Run tests after every change
|
|
344
|
+
- If tests break, undo and try again
|
|
345
|
+
|
|
346
|
+
\`\`\`bash
|
|
347
|
+
# Run tests continuously
|
|
348
|
+
bun test <your-test-file> --watch
|
|
349
|
+
\`\`\`
|
|
350
|
+
|
|
351
|
+
**Why TDD?**
|
|
352
|
+
- Catches bugs before they exist
|
|
353
|
+
- Documents expected behavior
|
|
354
|
+
- Enables fearless refactoring
|
|
355
|
+
- Proves your code works
|
|
333
356
|
|
|
334
357
|
### Step 6: Report Progress at Milestones
|
|
335
358
|
\`\`\`
|
|
@@ -591,6 +614,26 @@ export function formatSubtaskPromptV2(params: {
|
|
|
591
614
|
}
|
|
592
615
|
}
|
|
593
616
|
|
|
617
|
+
// Generate WorkerHandoff contract (machine-readable section)
|
|
618
|
+
const handoff = generateWorkerHandoff({
|
|
619
|
+
task_id: params.bead_id,
|
|
620
|
+
files_owned: params.files,
|
|
621
|
+
files_readonly: [],
|
|
622
|
+
dependencies_completed: [],
|
|
623
|
+
success_criteria: [
|
|
624
|
+
"All files compile without errors",
|
|
625
|
+
"Tests pass for modified code",
|
|
626
|
+
"Code follows project patterns",
|
|
627
|
+
],
|
|
628
|
+
epic_summary: params.subtask_description || params.subtask_title,
|
|
629
|
+
your_role: params.subtask_title,
|
|
630
|
+
what_others_did: params.recovery_context?.shared_context || "",
|
|
631
|
+
what_comes_next: "",
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
const handoffJson = JSON.stringify(handoff, null, 2);
|
|
635
|
+
const handoffSection = `\n## WorkerHandoff Contract\n\nThis is your machine-readable contract. The contract IS the instruction.\n\n\`\`\`json\n${handoffJson}\n\`\`\`\n`;
|
|
636
|
+
|
|
594
637
|
return SUBTASK_PROMPT_V2.replace(/{bead_id}/g, params.bead_id)
|
|
595
638
|
.replace(/{epic_id}/g, params.epic_id)
|
|
596
639
|
.replace(/{project_path}/g, params.project_path || "$PWD")
|
|
@@ -602,7 +645,7 @@ export function formatSubtaskPromptV2(params: {
|
|
|
602
645
|
.replace("{file_list}", fileList)
|
|
603
646
|
.replace("{shared_context}", params.shared_context || "(none)")
|
|
604
647
|
.replace("{compressed_context}", compressedSection)
|
|
605
|
-
.replace("{error_context}", errorSection + recoverySection);
|
|
648
|
+
.replace("{error_context}", errorSection + recoverySection + handoffSection);
|
|
606
649
|
}
|
|
607
650
|
|
|
608
651
|
/**
|
package/src/swarm-review.ts
CHANGED
|
@@ -686,6 +686,13 @@ export function clearReviewStatus(taskId: string): void {
|
|
|
686
686
|
clearAttempts(taskId);
|
|
687
687
|
}
|
|
688
688
|
|
|
689
|
+
/**
|
|
690
|
+
* Mark a task as reviewed but not approved (for testing)
|
|
691
|
+
*/
|
|
692
|
+
export function markReviewRejected(taskId: string): void {
|
|
693
|
+
reviewStatus.set(taskId, { approved: false, timestamp: Date.now() });
|
|
694
|
+
}
|
|
695
|
+
|
|
689
696
|
// ============================================================================
|
|
690
697
|
// Exports
|
|
691
698
|
// ============================================================================
|