opencode-swarm-plugin 0.31.7 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +10 -9
- package/.turbo/turbo-test.log +319 -317
- package/CHANGELOG.md +134 -0
- package/README.md +7 -4
- package/bin/swarm.ts +388 -128
- package/dist/compaction-hook.d.ts +1 -1
- package/dist/compaction-hook.d.ts.map +1 -1
- package/dist/hive.d.ts.map +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +123 -134
- package/dist/memory-tools.d.ts.map +1 -1
- package/dist/memory.d.ts +5 -4
- package/dist/memory.d.ts.map +1 -1
- package/dist/plugin.js +118 -131
- package/dist/swarm-orchestrate.d.ts +29 -5
- package/dist/swarm-orchestrate.d.ts.map +1 -1
- package/dist/swarm-prompts.d.ts +7 -0
- package/dist/swarm-prompts.d.ts.map +1 -1
- package/dist/swarm.d.ts +0 -2
- package/dist/swarm.d.ts.map +1 -1
- package/evals/lib/{data-loader.test.ts → data-loader.evalite-test.ts} +7 -6
- package/evals/lib/data-loader.ts +1 -1
- package/evals/scorers/{outcome-scorers.test.ts → outcome-scorers.evalite-test.ts} +1 -1
- package/examples/plugin-wrapper-template.ts +19 -4
- package/global-skills/swarm-coordination/SKILL.md +118 -8
- package/package.json +2 -2
- package/src/compaction-hook.ts +5 -3
- package/src/hive.integration.test.ts +83 -1
- package/src/hive.ts +37 -12
- package/src/mandate-storage.integration.test.ts +601 -0
- package/src/memory-tools.ts +6 -4
- package/src/memory.integration.test.ts +117 -49
- package/src/memory.test.ts +41 -217
- package/src/memory.ts +12 -8
- package/src/repo-crawl.integration.test.ts +441 -0
- package/src/skills.integration.test.ts +1056 -0
- package/src/structured.integration.test.ts +817 -0
- package/src/swarm-deferred.integration.test.ts +157 -0
- package/src/swarm-deferred.test.ts +38 -0
- package/src/swarm-mail.integration.test.ts +15 -19
- package/src/swarm-orchestrate.integration.test.ts +282 -0
- package/src/swarm-orchestrate.ts +96 -201
- package/src/swarm-prompts.test.ts +92 -0
- package/src/swarm-prompts.ts +69 -0
- package/src/swarm-review.integration.test.ts +290 -0
- package/src/swarm.integration.test.ts +23 -20
- package/src/tool-adapter.integration.test.ts +1221 -0
package/src/swarm-orchestrate.ts
CHANGED
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
getAgent,
|
|
45
45
|
createEvent,
|
|
46
46
|
appendEvent,
|
|
47
|
+
getSwarmMailLibSQL,
|
|
47
48
|
} from "swarm-mail";
|
|
48
49
|
import {
|
|
49
50
|
addStrike,
|
|
@@ -386,104 +387,9 @@ interface VerificationGateResult {
|
|
|
386
387
|
blockers: string[];
|
|
387
388
|
}
|
|
388
389
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
interface UbsScanResult {
|
|
393
|
-
exitCode: number;
|
|
394
|
-
bugs: Array<{
|
|
395
|
-
file: string;
|
|
396
|
-
line: number;
|
|
397
|
-
severity: string;
|
|
398
|
-
message: string;
|
|
399
|
-
category: string;
|
|
400
|
-
}>;
|
|
401
|
-
summary: {
|
|
402
|
-
total: number;
|
|
403
|
-
critical: number;
|
|
404
|
-
high: number;
|
|
405
|
-
medium: number;
|
|
406
|
-
low: number;
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
/**
|
|
411
|
-
* Run UBS scan on files before completion
|
|
412
|
-
*
|
|
413
|
-
* @param files - Files to scan
|
|
414
|
-
* @returns Scan result or null if UBS not available
|
|
415
|
-
*/
|
|
416
|
-
async function runUbsScan(files: string[]): Promise<UbsScanResult | null> {
|
|
417
|
-
if (files.length === 0) {
|
|
418
|
-
return null;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// Check if UBS is available first
|
|
422
|
-
const ubsAvailable = await isToolAvailable("ubs");
|
|
423
|
-
if (!ubsAvailable) {
|
|
424
|
-
warnMissingTool("ubs");
|
|
425
|
-
return null;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
try {
|
|
429
|
-
// Run UBS scan with JSON output
|
|
430
|
-
const result = await Bun.$`ubs scan ${files.join(" ")} --json`
|
|
431
|
-
.quiet()
|
|
432
|
-
.nothrow();
|
|
433
|
-
|
|
434
|
-
const output = result.stdout.toString();
|
|
435
|
-
if (!output.trim()) {
|
|
436
|
-
return {
|
|
437
|
-
exitCode: result.exitCode,
|
|
438
|
-
bugs: [],
|
|
439
|
-
summary: { total: 0, critical: 0, high: 0, medium: 0, low: 0 },
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
try {
|
|
444
|
-
const parsed = JSON.parse(output);
|
|
445
|
-
|
|
446
|
-
// Basic validation of structure
|
|
447
|
-
if (typeof parsed !== "object" || parsed === null) {
|
|
448
|
-
throw new Error("UBS output is not an object");
|
|
449
|
-
}
|
|
450
|
-
if (!Array.isArray(parsed.bugs)) {
|
|
451
|
-
console.warn("[swarm] UBS output missing bugs array, using empty");
|
|
452
|
-
}
|
|
453
|
-
if (typeof parsed.summary !== "object" || parsed.summary === null) {
|
|
454
|
-
console.warn("[swarm] UBS output missing summary object, using empty");
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
return {
|
|
458
|
-
exitCode: result.exitCode,
|
|
459
|
-
bugs: Array.isArray(parsed.bugs) ? parsed.bugs : [],
|
|
460
|
-
summary: parsed.summary || {
|
|
461
|
-
total: 0,
|
|
462
|
-
critical: 0,
|
|
463
|
-
high: 0,
|
|
464
|
-
medium: 0,
|
|
465
|
-
low: 0,
|
|
466
|
-
},
|
|
467
|
-
};
|
|
468
|
-
} catch (error) {
|
|
469
|
-
// UBS output wasn't JSON - this is an error condition
|
|
470
|
-
console.error(
|
|
471
|
-
`[swarm] CRITICAL: UBS scan failed to parse JSON output because output is malformed:`,
|
|
472
|
-
error,
|
|
473
|
-
);
|
|
474
|
-
console.error(
|
|
475
|
-
`[swarm] Raw output: ${output}. Try: Run 'ubs doctor' to check installation, verify UBS version with 'ubs --version' (need v1.0.0+), or check if UBS supports --json flag.`,
|
|
476
|
-
);
|
|
477
|
-
return {
|
|
478
|
-
exitCode: result.exitCode,
|
|
479
|
-
bugs: [],
|
|
480
|
-
summary: { total: 0, critical: 0, high: 0, medium: 0, low: 0 },
|
|
481
|
-
};
|
|
482
|
-
}
|
|
483
|
-
} catch {
|
|
484
|
-
return null;
|
|
485
|
-
}
|
|
486
|
-
}
|
|
390
|
+
// NOTE: UBS scan (runUbsScan, UbsScanResult) removed in v0.31
|
|
391
|
+
// It was slowing down completion without proportional value.
|
|
392
|
+
// Run UBS manually if needed: ubs scan <files>
|
|
487
393
|
|
|
488
394
|
/**
|
|
489
395
|
* Run typecheck verification
|
|
@@ -608,51 +514,22 @@ async function runTestVerification(
|
|
|
608
514
|
* Run the full Verification Gate
|
|
609
515
|
*
|
|
610
516
|
* Implements the Gate Function (IDENTIFY → RUN → READ → VERIFY → CLAIM):
|
|
611
|
-
* 1.
|
|
612
|
-
* 2.
|
|
613
|
-
*
|
|
517
|
+
* 1. Typecheck
|
|
518
|
+
* 2. Tests for touched files
|
|
519
|
+
*
|
|
520
|
+
* NOTE: UBS scan was removed in v0.31 - it was slowing down completion
|
|
521
|
+
* without providing proportional value. Run UBS manually if needed.
|
|
614
522
|
*
|
|
615
523
|
* All steps must pass (or be skipped with valid reason) to proceed.
|
|
616
524
|
*/
|
|
617
525
|
async function runVerificationGate(
|
|
618
526
|
filesTouched: string[],
|
|
619
|
-
|
|
527
|
+
_skipUbs: boolean = false, // Kept for backward compatibility, now ignored
|
|
620
528
|
): Promise<VerificationGateResult> {
|
|
621
529
|
const steps: VerificationStep[] = [];
|
|
622
530
|
const blockers: string[] = [];
|
|
623
531
|
|
|
624
|
-
// Step 1: UBS scan
|
|
625
|
-
if (!skipUbs && filesTouched.length > 0) {
|
|
626
|
-
const ubsResult = await runUbsScan(filesTouched);
|
|
627
|
-
if (ubsResult) {
|
|
628
|
-
const ubsStep: VerificationStep = {
|
|
629
|
-
name: "ubs_scan",
|
|
630
|
-
command: `ubs scan ${filesTouched.join(" ")}`,
|
|
631
|
-
passed: ubsResult.summary.critical === 0,
|
|
632
|
-
exitCode: ubsResult.exitCode,
|
|
633
|
-
};
|
|
634
|
-
|
|
635
|
-
if (!ubsStep.passed) {
|
|
636
|
-
ubsStep.error = `Found ${ubsResult.summary.critical} critical bugs`;
|
|
637
|
-
blockers.push(
|
|
638
|
-
`UBS found ${ubsResult.summary.critical} critical bug(s). Try: Run 'ubs scan ${filesTouched.join(" ")}' to see details, fix critical bugs in reported files, or use skip_ubs_scan=true to bypass (not recommended).`,
|
|
639
|
-
);
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
steps.push(ubsStep);
|
|
643
|
-
} else {
|
|
644
|
-
steps.push({
|
|
645
|
-
name: "ubs_scan",
|
|
646
|
-
command: "ubs scan",
|
|
647
|
-
passed: true,
|
|
648
|
-
exitCode: 0,
|
|
649
|
-
skipped: true,
|
|
650
|
-
skipReason: "UBS not available",
|
|
651
|
-
});
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
// Step 2: Typecheck
|
|
532
|
+
// Step 1: Typecheck (UBS scan removed in v0.31)
|
|
656
533
|
const typecheckStep = await runTypecheckVerification();
|
|
657
534
|
steps.push(typecheckStep);
|
|
658
535
|
if (!typecheckStep.passed && !typecheckStep.skipped) {
|
|
@@ -1224,11 +1101,39 @@ export const swarm_broadcast = tool({
|
|
|
1224
1101
|
* 4. VERIFY: All checks must pass
|
|
1225
1102
|
* 5. ONLY THEN: Close the cell
|
|
1226
1103
|
*
|
|
1227
|
-
* Closes cell, releases reservations, notifies coordinator
|
|
1104
|
+
* Closes cell, releases reservations, notifies coordinator, and resolves
|
|
1105
|
+
* a DurableDeferred keyed by bead_id for cross-agent task completion signaling.
|
|
1106
|
+
*
|
|
1107
|
+
* ## DurableDeferred Integration
|
|
1108
|
+
*
|
|
1109
|
+
* When a coordinator spawns workers, it can create a deferred BEFORE spawning:
|
|
1110
|
+
*
|
|
1111
|
+
* ```typescript
|
|
1112
|
+
* const swarmMail = await getSwarmMailLibSQL(projectPath);
|
|
1113
|
+
* const db = await swarmMail.getDatabase();
|
|
1114
|
+
*
|
|
1115
|
+
* // Create deferred keyed by bead_id
|
|
1116
|
+
* const deferredUrl = `deferred:${beadId}`;
|
|
1117
|
+
* await db.query(
|
|
1118
|
+
* `INSERT INTO deferred (url, resolved, expires_at, created_at) VALUES (?, 0, ?, ?)`,
|
|
1119
|
+
* [deferredUrl, Date.now() + 3600000, Date.now()]
|
|
1120
|
+
* );
|
|
1121
|
+
*
|
|
1122
|
+
* // Spawn worker (swarm_spawn_subtask...)
|
|
1123
|
+
*
|
|
1124
|
+
* // Await completion
|
|
1125
|
+
* const result = await db.query<{ value: string }>(
|
|
1126
|
+
* `SELECT value FROM deferred WHERE url = ? AND resolved = 1`,
|
|
1127
|
+
* [deferredUrl]
|
|
1128
|
+
* );
|
|
1129
|
+
* ```
|
|
1130
|
+
*
|
|
1131
|
+
* When the worker calls swarm_complete, it resolves the deferred automatically.
|
|
1132
|
+
* Coordinator can await without polling.
|
|
1228
1133
|
*/
|
|
1229
1134
|
export const swarm_complete = tool({
|
|
1230
1135
|
description:
|
|
1231
|
-
"Mark subtask complete with Verification Gate. Runs
|
|
1136
|
+
"Mark subtask complete with Verification Gate. Runs typecheck and tests before allowing completion.",
|
|
1232
1137
|
args: {
|
|
1233
1138
|
project_key: tool.schema.string().describe("Project path"),
|
|
1234
1139
|
agent_name: tool.schema.string().describe("Your Agent Mail name"),
|
|
@@ -1241,16 +1146,12 @@ export const swarm_complete = tool({
|
|
|
1241
1146
|
files_touched: tool.schema
|
|
1242
1147
|
.array(tool.schema.string())
|
|
1243
1148
|
.optional()
|
|
1244
|
-
.describe("Files modified - will be verified (
|
|
1245
|
-
skip_ubs_scan: tool.schema
|
|
1246
|
-
.boolean()
|
|
1247
|
-
.optional()
|
|
1248
|
-
.describe("Skip UBS bug scan (default: false)"),
|
|
1149
|
+
.describe("Files modified - will be verified (typecheck, tests)"),
|
|
1249
1150
|
skip_verification: tool.schema
|
|
1250
1151
|
.boolean()
|
|
1251
1152
|
.optional()
|
|
1252
1153
|
.describe(
|
|
1253
|
-
"Skip ALL verification (
|
|
1154
|
+
"Skip ALL verification (typecheck, tests). Use sparingly! (default: false)",
|
|
1254
1155
|
),
|
|
1255
1156
|
planned_files: tool.schema
|
|
1256
1157
|
.array(tool.schema.string())
|
|
@@ -1397,7 +1298,7 @@ Continuing with completion, but this should be fixed for future subtasks.`;
|
|
|
1397
1298
|
if (!args.skip_verification && args.files_touched?.length) {
|
|
1398
1299
|
verificationResult = await runVerificationGate(
|
|
1399
1300
|
args.files_touched,
|
|
1400
|
-
|
|
1301
|
+
false,
|
|
1401
1302
|
);
|
|
1402
1303
|
|
|
1403
1304
|
// Block completion if verification failed
|
|
@@ -1431,39 +1332,9 @@ Continuing with completion, but this should be fixed for future subtasks.`;
|
|
|
1431
1332
|
}
|
|
1432
1333
|
}
|
|
1433
1334
|
|
|
1434
|
-
// Legacy UBS-only path
|
|
1435
|
-
|
|
1436
|
-
if
|
|
1437
|
-
!args.skip_verification &&
|
|
1438
|
-
!verificationResult &&
|
|
1439
|
-
args.files_touched?.length &&
|
|
1440
|
-
!args.skip_ubs_scan
|
|
1441
|
-
) {
|
|
1442
|
-
ubsResult = await runUbsScan(args.files_touched);
|
|
1443
|
-
|
|
1444
|
-
// Block completion if critical bugs found
|
|
1445
|
-
if (ubsResult && ubsResult.summary.critical > 0) {
|
|
1446
|
-
return JSON.stringify(
|
|
1447
|
-
{
|
|
1448
|
-
success: false,
|
|
1449
|
-
error: `UBS found ${ubsResult.summary.critical} critical bug(s) that must be fixed before completing`,
|
|
1450
|
-
ubs_scan: {
|
|
1451
|
-
critical_count: ubsResult.summary.critical,
|
|
1452
|
-
bugs: ubsResult.bugs.filter((b) => b.severity === "critical"),
|
|
1453
|
-
},
|
|
1454
|
-
hint: `Fix these critical bugs: ${ubsResult.bugs
|
|
1455
|
-
.filter((b) => b.severity === "critical")
|
|
1456
|
-
.map((b) => `${b.file}:${b.line} - ${b.message}`)
|
|
1457
|
-
.slice(0, 3)
|
|
1458
|
-
.join(
|
|
1459
|
-
"; ",
|
|
1460
|
-
)}. Try: Run 'ubs scan ${args.files_touched?.join(" ") || "."} --json' for full report, fix reported issues, or use skip_ubs_scan=true to bypass (not recommended).`,
|
|
1461
|
-
},
|
|
1462
|
-
null,
|
|
1463
|
-
2,
|
|
1464
|
-
);
|
|
1465
|
-
}
|
|
1466
|
-
}
|
|
1335
|
+
// NOTE: Legacy UBS-only path removed in v0.31
|
|
1336
|
+
// UBS scan was slowing down completion without proportional value.
|
|
1337
|
+
// Run UBS manually if needed: ubs scan <files>
|
|
1467
1338
|
|
|
1468
1339
|
// Contract Validation - check files_touched against WorkerHandoff contract
|
|
1469
1340
|
let contractValidation: { valid: boolean; violations: string[] } | null = null;
|
|
@@ -1570,6 +1441,47 @@ This will be recorded as a negative learning signal.`;
|
|
|
1570
1441
|
);
|
|
1571
1442
|
}
|
|
1572
1443
|
|
|
1444
|
+
// Resolve DurableDeferred for cross-agent task completion signaling
|
|
1445
|
+
// This allows coordinator to await worker completion without polling
|
|
1446
|
+
let deferredResolved = false;
|
|
1447
|
+
let deferredError: string | undefined;
|
|
1448
|
+
try {
|
|
1449
|
+
const swarmMail = await getSwarmMailLibSQL(args.project_key);
|
|
1450
|
+
const db = await swarmMail.getDatabase();
|
|
1451
|
+
|
|
1452
|
+
// Resolve deferred keyed by bead_id
|
|
1453
|
+
// Coordinator should have created this deferred before spawning worker
|
|
1454
|
+
const deferredUrl = `deferred:${args.bead_id}`;
|
|
1455
|
+
|
|
1456
|
+
// Check if deferred exists before resolving
|
|
1457
|
+
const checkResult = await db.query<{ url: string; resolved: number }>(
|
|
1458
|
+
`SELECT url, resolved FROM deferred WHERE url = ? AND resolved = 0`,
|
|
1459
|
+
[deferredUrl],
|
|
1460
|
+
);
|
|
1461
|
+
|
|
1462
|
+
if (checkResult.rows.length > 0) {
|
|
1463
|
+
// Resolve with completion payload
|
|
1464
|
+
await db.query(
|
|
1465
|
+
`UPDATE deferred SET resolved = 1, value = ? WHERE url = ? AND resolved = 0`,
|
|
1466
|
+
[JSON.stringify({ completed: true, summary: args.summary }), deferredUrl],
|
|
1467
|
+
);
|
|
1468
|
+
|
|
1469
|
+
deferredResolved = true;
|
|
1470
|
+
} else {
|
|
1471
|
+
// Deferred doesn't exist - worker was likely not spawned via swarm pattern
|
|
1472
|
+
// This is non-fatal - just log for debugging
|
|
1473
|
+
console.info(
|
|
1474
|
+
`[swarm_complete] No deferred found for ${args.bead_id} - task may not be part of active swarm`,
|
|
1475
|
+
);
|
|
1476
|
+
}
|
|
1477
|
+
} catch (error) {
|
|
1478
|
+
// Non-fatal - deferred resolution is optional for backward compatibility
|
|
1479
|
+
deferredError = error instanceof Error ? error.message : String(error);
|
|
1480
|
+
console.warn(
|
|
1481
|
+
`[swarm_complete] Failed to resolve deferred (non-fatal): ${deferredError}`,
|
|
1482
|
+
);
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1573
1485
|
// Sync cell to .hive/issues.jsonl (auto-sync on complete)
|
|
1574
1486
|
// This ensures the worker's completed work persists before process exits
|
|
1575
1487
|
let syncSuccess = false;
|
|
@@ -1737,6 +1649,8 @@ This will be recorded as a negative learning signal.`;
|
|
|
1737
1649
|
sync_error: syncError,
|
|
1738
1650
|
message_sent: messageSent,
|
|
1739
1651
|
message_error: messageError,
|
|
1652
|
+
deferred_resolved: deferredResolved,
|
|
1653
|
+
deferred_error: deferredError,
|
|
1740
1654
|
agent_registration: {
|
|
1741
1655
|
verified: agentRegistered,
|
|
1742
1656
|
warning: registrationWarning || undefined,
|
|
@@ -1755,21 +1669,6 @@ This will be recorded as a negative learning signal.`;
|
|
|
1755
1669
|
: args.skip_verification
|
|
1756
1670
|
? { skipped: true, reason: "skip_verification=true" }
|
|
1757
1671
|
: { skipped: true, reason: "no files_touched provided" },
|
|
1758
|
-
ubs_scan: ubsResult
|
|
1759
|
-
? {
|
|
1760
|
-
ran: true,
|
|
1761
|
-
bugs_found: ubsResult.summary.total,
|
|
1762
|
-
summary: ubsResult.summary,
|
|
1763
|
-
warnings: ubsResult.bugs.filter((b) => b.severity !== "critical"),
|
|
1764
|
-
}
|
|
1765
|
-
: verificationResult
|
|
1766
|
-
? { ran: true, included_in_verification_gate: true }
|
|
1767
|
-
: {
|
|
1768
|
-
ran: false,
|
|
1769
|
-
reason: args.skip_ubs_scan
|
|
1770
|
-
? "skipped"
|
|
1771
|
-
: "no files or ubs unavailable",
|
|
1772
|
-
},
|
|
1773
1672
|
learning_prompt: `## Reflection
|
|
1774
1673
|
|
|
1775
1674
|
Did you learn anything reusable during this subtask? Consider:
|
|
@@ -1820,9 +1719,7 @@ Files touched: ${args.files_touched?.join(", ") || "none recorded"}`,
|
|
|
1820
1719
|
// Determine which step failed
|
|
1821
1720
|
let failedStep = "unknown";
|
|
1822
1721
|
if (errorMessage.includes("verification")) {
|
|
1823
|
-
failedStep = "Verification Gate (
|
|
1824
|
-
} else if (errorMessage.includes("UBS") || errorMessage.includes("ubs")) {
|
|
1825
|
-
failedStep = "UBS scan";
|
|
1722
|
+
failedStep = "Verification Gate (typecheck/tests)";
|
|
1826
1723
|
} else if (errorMessage.includes("evaluation")) {
|
|
1827
1724
|
failedStep = "Self-evaluation parsing";
|
|
1828
1725
|
} else if (
|
|
@@ -1863,10 +1760,9 @@ Files touched: ${args.files_touched?.join(", ") || "none recorded"}`,
|
|
|
1863
1760
|
errorStack
|
|
1864
1761
|
? `### Stack Trace\n\`\`\`\n${errorStack.slice(0, 1000)}\n\`\`\`\n`
|
|
1865
1762
|
: "",
|
|
1866
|
-
|
|
1763
|
+
`### Context`,
|
|
1867
1764
|
`- **Summary**: ${args.summary}`,
|
|
1868
1765
|
`- **Files touched**: ${args.files_touched?.length ? args.files_touched.join(", ") : "none"}`,
|
|
1869
|
-
`- **Skip UBS**: ${args.skip_ubs_scan ?? false}`,
|
|
1870
1766
|
`- **Skip verification**: ${args.skip_verification ?? false}`,
|
|
1871
1767
|
"",
|
|
1872
1768
|
`### Recovery Actions`,
|
|
@@ -1912,10 +1808,9 @@ Files touched: ${args.files_touched?.join(", ") || "none recorded"}`,
|
|
|
1912
1808
|
coordinator_notified: notificationSent,
|
|
1913
1809
|
stack_trace: errorStack?.slice(0, 500),
|
|
1914
1810
|
hint: "Check the error message above. Common issues: bead not found, session not initialized.",
|
|
1915
|
-
|
|
1811
|
+
context: {
|
|
1916
1812
|
summary: args.summary,
|
|
1917
1813
|
files_touched: args.files_touched || [],
|
|
1918
|
-
skip_ubs_scan: args.skip_ubs_scan ?? false,
|
|
1919
1814
|
skip_verification: args.skip_verification ?? false,
|
|
1920
1815
|
},
|
|
1921
1816
|
recovery: {
|
|
@@ -1927,7 +1822,6 @@ Files touched: ${args.files_touched?.join(", ") || "none recorded"}`,
|
|
|
1927
1822
|
],
|
|
1928
1823
|
common_fixes: {
|
|
1929
1824
|
"Verification Gate": "Use skip_verification=true to bypass (not recommended)",
|
|
1930
|
-
"UBS scan": "Use skip_ubs_scan=true to bypass",
|
|
1931
1825
|
"Cell close": "Check cell status with hive_query(), may need hive_update() first",
|
|
1932
1826
|
"Self-evaluation": "Check evaluation JSON format matches EvaluationSchema",
|
|
1933
1827
|
},
|
|
@@ -2554,8 +2448,9 @@ export const swarm_recover = tool({
|
|
|
2554
2448
|
},
|
|
2555
2449
|
async execute(args) {
|
|
2556
2450
|
try {
|
|
2557
|
-
const {
|
|
2558
|
-
const
|
|
2451
|
+
const { getSwarmMailLibSQL } = await import("swarm-mail");
|
|
2452
|
+
const swarmMail = await getSwarmMailLibSQL(args.project_key);
|
|
2453
|
+
const db = await swarmMail.getDatabase();
|
|
2559
2454
|
|
|
2560
2455
|
// Query most recent checkpoint for this epic
|
|
2561
2456
|
const result = await db.query<{
|
|
@@ -174,3 +174,95 @@ describe("formatSubtaskPromptV2", () => {
|
|
|
174
174
|
expect(result).toContain("semantic-memory_find");
|
|
175
175
|
});
|
|
176
176
|
});
|
|
177
|
+
|
|
178
|
+
describe("swarm_spawn_subtask tool", () => {
|
|
179
|
+
test("returns post_completion_instructions field in JSON response", async () => {
|
|
180
|
+
const { swarm_spawn_subtask } = await import("./swarm-prompts");
|
|
181
|
+
|
|
182
|
+
const result = await swarm_spawn_subtask.execute({
|
|
183
|
+
bead_id: "test-project-abc123-task1",
|
|
184
|
+
epic_id: "test-project-abc123-epic1",
|
|
185
|
+
subtask_title: "Implement feature X",
|
|
186
|
+
subtask_description: "Add feature X to the system",
|
|
187
|
+
files: ["src/feature.ts", "src/feature.test.ts"],
|
|
188
|
+
shared_context: "Epic context here",
|
|
189
|
+
project_path: "/Users/joel/Code/project",
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const parsed = JSON.parse(result);
|
|
193
|
+
expect(parsed).toHaveProperty("post_completion_instructions");
|
|
194
|
+
expect(typeof parsed.post_completion_instructions).toBe("string");
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("post_completion_instructions contains mandatory review steps", async () => {
|
|
198
|
+
const { swarm_spawn_subtask } = await import("./swarm-prompts");
|
|
199
|
+
|
|
200
|
+
const result = await swarm_spawn_subtask.execute({
|
|
201
|
+
bead_id: "test-project-abc123-task1",
|
|
202
|
+
epic_id: "test-project-abc123-epic1",
|
|
203
|
+
subtask_title: "Implement feature X",
|
|
204
|
+
files: ["src/feature.ts"],
|
|
205
|
+
project_path: "/Users/joel/Code/project",
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const parsed = JSON.parse(result);
|
|
209
|
+
const instructions = parsed.post_completion_instructions;
|
|
210
|
+
|
|
211
|
+
// Should contain all 5 steps
|
|
212
|
+
expect(instructions).toContain("Step 1: Check Swarm Mail");
|
|
213
|
+
expect(instructions).toContain("swarmmail_inbox()");
|
|
214
|
+
expect(instructions).toContain("Step 2: Review the Work");
|
|
215
|
+
expect(instructions).toContain("swarm_review");
|
|
216
|
+
expect(instructions).toContain("Step 3: Evaluate Against Criteria");
|
|
217
|
+
expect(instructions).toContain("Step 4: Send Feedback");
|
|
218
|
+
expect(instructions).toContain("swarm_review_feedback");
|
|
219
|
+
expect(instructions).toContain("Step 5: ONLY THEN Continue");
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("post_completion_instructions substitutes placeholders", async () => {
|
|
223
|
+
const { swarm_spawn_subtask } = await import("./swarm-prompts");
|
|
224
|
+
|
|
225
|
+
const result = await swarm_spawn_subtask.execute({
|
|
226
|
+
bead_id: "test-project-abc123-task1",
|
|
227
|
+
epic_id: "test-project-abc123-epic1",
|
|
228
|
+
subtask_title: "Implement feature X",
|
|
229
|
+
files: ["src/feature.ts", "src/feature.test.ts"],
|
|
230
|
+
project_path: "/Users/joel/Code/project",
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const parsed = JSON.parse(result);
|
|
234
|
+
const instructions = parsed.post_completion_instructions;
|
|
235
|
+
|
|
236
|
+
// Placeholders should be replaced
|
|
237
|
+
expect(instructions).toContain("/Users/joel/Code/project");
|
|
238
|
+
expect(instructions).toContain("test-project-abc123-epic1");
|
|
239
|
+
expect(instructions).toContain("test-project-abc123-task1");
|
|
240
|
+
expect(instructions).toContain('"src/feature.ts"');
|
|
241
|
+
expect(instructions).toContain('"src/feature.test.ts"');
|
|
242
|
+
|
|
243
|
+
// Placeholders should NOT remain
|
|
244
|
+
expect(instructions).not.toContain("{project_key}");
|
|
245
|
+
expect(instructions).not.toContain("{epic_id}");
|
|
246
|
+
expect(instructions).not.toContain("{task_id}");
|
|
247
|
+
expect(instructions).not.toContain("{files_touched}");
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("post_completion_instructions emphasizes mandatory nature", async () => {
|
|
251
|
+
const { swarm_spawn_subtask } = await import("./swarm-prompts");
|
|
252
|
+
|
|
253
|
+
const result = await swarm_spawn_subtask.execute({
|
|
254
|
+
bead_id: "test-project-abc123-task1",
|
|
255
|
+
epic_id: "test-project-abc123-epic1",
|
|
256
|
+
subtask_title: "Implement feature X",
|
|
257
|
+
files: ["src/feature.ts"],
|
|
258
|
+
project_path: "/Users/joel/Code/project",
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const parsed = JSON.parse(result);
|
|
262
|
+
const instructions = parsed.post_completion_instructions;
|
|
263
|
+
|
|
264
|
+
// Should have strong language
|
|
265
|
+
expect(instructions).toMatch(/⚠️|MANDATORY|NON-NEGOTIABLE|DO NOT skip/i);
|
|
266
|
+
expect(instructions).toContain("DO THIS IMMEDIATELY");
|
|
267
|
+
});
|
|
268
|
+
});
|
package/src/swarm-prompts.ts
CHANGED
|
@@ -551,6 +551,65 @@ Other cell operations:
|
|
|
551
551
|
|
|
552
552
|
Begin now.`;
|
|
553
553
|
|
|
554
|
+
/**
|
|
555
|
+
* Coordinator post-worker checklist - MANDATORY review loop
|
|
556
|
+
*
|
|
557
|
+
* This checklist is returned to coordinators after spawning a worker.
|
|
558
|
+
* It ensures coordinators REVIEW worker output before spawning the next worker.
|
|
559
|
+
*/
|
|
560
|
+
export const COORDINATOR_POST_WORKER_CHECKLIST = `
|
|
561
|
+
## ⚠️ MANDATORY: Post-Worker Review (DO THIS IMMEDIATELY)
|
|
562
|
+
|
|
563
|
+
**A worker just returned. Before doing ANYTHING else, complete this checklist:**
|
|
564
|
+
|
|
565
|
+
### Step 1: Check Swarm Mail
|
|
566
|
+
\`\`\`
|
|
567
|
+
swarmmail_inbox()
|
|
568
|
+
swarmmail_read_message(message_id=N) // Read any messages from the worker
|
|
569
|
+
\`\`\`
|
|
570
|
+
|
|
571
|
+
### Step 2: Review the Work
|
|
572
|
+
\`\`\`
|
|
573
|
+
swarm_review(
|
|
574
|
+
project_key="{project_key}",
|
|
575
|
+
epic_id="{epic_id}",
|
|
576
|
+
task_id="{task_id}",
|
|
577
|
+
files_touched=[{files_touched}]
|
|
578
|
+
)
|
|
579
|
+
\`\`\`
|
|
580
|
+
|
|
581
|
+
This generates a review prompt with:
|
|
582
|
+
- Epic context (what we're trying to achieve)
|
|
583
|
+
- Subtask requirements
|
|
584
|
+
- Git diff of changes
|
|
585
|
+
- Dependency status
|
|
586
|
+
|
|
587
|
+
### Step 3: Evaluate Against Criteria
|
|
588
|
+
- Does the work fulfill the subtask requirements?
|
|
589
|
+
- Does it serve the overall epic goal?
|
|
590
|
+
- Does it enable downstream tasks?
|
|
591
|
+
- Type safety, no obvious bugs?
|
|
592
|
+
|
|
593
|
+
### Step 4: Send Feedback
|
|
594
|
+
\`\`\`
|
|
595
|
+
swarm_review_feedback(
|
|
596
|
+
project_key="{project_key}",
|
|
597
|
+
task_id="{task_id}",
|
|
598
|
+
worker_id="{worker_id}",
|
|
599
|
+
status="approved", // or "needs_changes"
|
|
600
|
+
summary="<brief summary>",
|
|
601
|
+
issues="[]" // or "[{file, line, issue, suggestion}]"
|
|
602
|
+
)
|
|
603
|
+
\`\`\`
|
|
604
|
+
|
|
605
|
+
### Step 5: ONLY THEN Continue
|
|
606
|
+
- If approved: Close the cell, spawn next worker
|
|
607
|
+
- If needs_changes: Worker gets feedback, retries (max 3 attempts)
|
|
608
|
+
- If 3 failures: Mark blocked, escalate to human
|
|
609
|
+
|
|
610
|
+
**⚠️ DO NOT spawn the next worker until review is complete.**
|
|
611
|
+
`;
|
|
612
|
+
|
|
554
613
|
/**
|
|
555
614
|
* Prompt for self-evaluation before completing a subtask.
|
|
556
615
|
*
|
|
@@ -852,6 +911,15 @@ export const swarm_spawn_subtask = tool({
|
|
|
852
911
|
|
|
853
912
|
const selectedModel = selectWorkerModel(subtask, config);
|
|
854
913
|
|
|
914
|
+
// Generate post-completion instructions for coordinator
|
|
915
|
+
const filesJoined = args.files.map(f => `"${f}"`).join(", ");
|
|
916
|
+
const postCompletionInstructions = COORDINATOR_POST_WORKER_CHECKLIST
|
|
917
|
+
.replace(/{project_key}/g, args.project_path || "$PWD")
|
|
918
|
+
.replace(/{epic_id}/g, args.epic_id)
|
|
919
|
+
.replace(/{task_id}/g, args.bead_id)
|
|
920
|
+
.replace(/{files_touched}/g, filesJoined)
|
|
921
|
+
.replace(/{worker_id}/g, "worker"); // Will be filled by actual worker name
|
|
922
|
+
|
|
855
923
|
return JSON.stringify(
|
|
856
924
|
{
|
|
857
925
|
prompt,
|
|
@@ -861,6 +929,7 @@ export const swarm_spawn_subtask = tool({
|
|
|
861
929
|
project_path: args.project_path,
|
|
862
930
|
recovery_context: args.recovery_context,
|
|
863
931
|
recommended_model: selectedModel,
|
|
932
|
+
post_completion_instructions: postCompletionInstructions,
|
|
864
933
|
},
|
|
865
934
|
null,
|
|
866
935
|
2,
|