opencode-swarm-plugin 0.31.7 → 0.33.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.
Files changed (62) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/.turbo/turbo-test.log +324 -316
  3. package/CHANGELOG.md +394 -0
  4. package/README.md +129 -181
  5. package/bin/swarm.test.ts +31 -0
  6. package/bin/swarm.ts +635 -140
  7. package/dist/compaction-hook.d.ts +1 -1
  8. package/dist/compaction-hook.d.ts.map +1 -1
  9. package/dist/hive.d.ts.map +1 -1
  10. package/dist/index.d.ts +17 -2
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +653 -139
  13. package/dist/memory-tools.d.ts.map +1 -1
  14. package/dist/memory.d.ts +5 -4
  15. package/dist/memory.d.ts.map +1 -1
  16. package/dist/observability-tools.d.ts +116 -0
  17. package/dist/observability-tools.d.ts.map +1 -0
  18. package/dist/plugin.js +648 -136
  19. package/dist/skills.d.ts.map +1 -1
  20. package/dist/swarm-orchestrate.d.ts +29 -5
  21. package/dist/swarm-orchestrate.d.ts.map +1 -1
  22. package/dist/swarm-prompts.d.ts +66 -0
  23. package/dist/swarm-prompts.d.ts.map +1 -1
  24. package/dist/swarm.d.ts +17 -2
  25. package/dist/swarm.d.ts.map +1 -1
  26. package/evals/lib/{data-loader.test.ts → data-loader.evalite-test.ts} +7 -6
  27. package/evals/lib/data-loader.ts +1 -1
  28. package/evals/scorers/{outcome-scorers.test.ts → outcome-scorers.evalite-test.ts} +1 -1
  29. package/examples/plugin-wrapper-template.ts +316 -12
  30. package/global-skills/swarm-coordination/SKILL.md +118 -8
  31. package/package.json +3 -2
  32. package/src/compaction-hook.ts +5 -3
  33. package/src/hive.integration.test.ts +83 -1
  34. package/src/hive.ts +37 -12
  35. package/src/index.ts +25 -1
  36. package/src/mandate-storage.integration.test.ts +601 -0
  37. package/src/memory-tools.ts +6 -4
  38. package/src/memory.integration.test.ts +117 -49
  39. package/src/memory.test.ts +41 -217
  40. package/src/memory.ts +12 -8
  41. package/src/observability-tools.test.ts +346 -0
  42. package/src/observability-tools.ts +594 -0
  43. package/src/repo-crawl.integration.test.ts +441 -0
  44. package/src/skills.integration.test.ts +1192 -0
  45. package/src/skills.test.ts +42 -1
  46. package/src/skills.ts +8 -4
  47. package/src/structured.integration.test.ts +817 -0
  48. package/src/swarm-deferred.integration.test.ts +157 -0
  49. package/src/swarm-deferred.test.ts +38 -0
  50. package/src/swarm-mail.integration.test.ts +15 -19
  51. package/src/swarm-orchestrate.integration.test.ts +282 -0
  52. package/src/swarm-orchestrate.test.ts +123 -0
  53. package/src/swarm-orchestrate.ts +279 -201
  54. package/src/swarm-prompts.test.ts +481 -0
  55. package/src/swarm-prompts.ts +297 -0
  56. package/src/swarm-research.integration.test.ts +544 -0
  57. package/src/swarm-research.test.ts +698 -0
  58. package/src/swarm-research.ts +472 -0
  59. package/src/swarm-review.integration.test.ts +290 -0
  60. package/src/swarm.integration.test.ts +23 -20
  61. package/src/swarm.ts +6 -3
  62. package/src/tool-adapter.integration.test.ts +1221 -0
@@ -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
- * UBS scan result schema
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. UBS scan (already exists)
612
- * 2. Typecheck
613
- * 3. Tests for touched files
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
- skipUbs: boolean = false,
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 UBS scan, typecheck, and tests before allowing completion.",
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 (UBS, typecheck, tests)"),
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 (UBS, typecheck, tests). Use sparingly! (default: false)",
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
- args.skip_ubs_scan ?? false,
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 for backward compatibility (when no files_touched)
1435
- let ubsResult: UbsScanResult | null = null;
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 (UBS/typecheck/tests)";
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
- `### Context`,
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
- context: {
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
  },
@@ -2100,6 +1994,188 @@ export const swarm_record_outcome = tool({
2100
1994
  },
2101
1995
  });
2102
1996
 
1997
+ // ============================================================================
1998
+ // Research Phase
1999
+ // ============================================================================
2000
+
2001
+ /**
2002
+ * Known technology patterns for extraction from task descriptions
2003
+ * Maps common mentions to normalized package names
2004
+ */
2005
+ const TECH_PATTERNS: Record<string, RegExp> = {
2006
+ next: /next\.?js|nextjs/i,
2007
+ react: /react(?!ive)/i,
2008
+ zod: /zod/i,
2009
+ typescript: /typescript|ts(?!\w)/i,
2010
+ tailwind: /tailwind(?:css)?/i,
2011
+ prisma: /prisma/i,
2012
+ drizzle: /drizzle(?:-orm)?/i,
2013
+ trpc: /trpc/i,
2014
+ "react-query": /react-query|tanstack.*query/i,
2015
+ axios: /axios/i,
2016
+ "node-fetch": /node-fetch|fetch api/i,
2017
+ };
2018
+
2019
+ /**
2020
+ * Extract technology stack from task description
2021
+ *
2022
+ * Searches for common framework/library mentions and returns
2023
+ * a deduplicated array of normalized names.
2024
+ *
2025
+ * @param task - Task description
2026
+ * @returns Array of detected technology names (normalized, lowercase)
2027
+ *
2028
+ * @example
2029
+ * ```typescript
2030
+ * extractTechStack("Add Next.js API routes with Zod validation")
2031
+ * // => ["next", "zod"]
2032
+ * ```
2033
+ */
2034
+ export function extractTechStack(task: string): string[] {
2035
+ const detected = new Set<string>();
2036
+
2037
+ for (const [tech, pattern] of Object.entries(TECH_PATTERNS)) {
2038
+ if (pattern.test(task)) {
2039
+ detected.add(tech);
2040
+ }
2041
+ }
2042
+
2043
+ return Array.from(detected);
2044
+ }
2045
+
2046
+ /**
2047
+ * Research result from documentation discovery phase
2048
+ */
2049
+ export interface ResearchResult {
2050
+ /** Technologies identified and researched */
2051
+ tech_stack: string[];
2052
+ /** Summaries keyed by technology name */
2053
+ summaries: Record<string, string>;
2054
+ /** Semantic-memory IDs where research is stored */
2055
+ memory_ids: string[];
2056
+ }
2057
+
2058
+ /**
2059
+ * Run research phase before task decomposition
2060
+ *
2061
+ * This is the INTEGRATION point that:
2062
+ * 1. Analyzes task to identify technologies
2063
+ * 2. Spawns researcher agents for each technology (parallel)
2064
+ * 3. Waits for researchers to complete
2065
+ * 4. Collects summaries from semantic-memory
2066
+ * 5. Returns combined context for shared_context
2067
+ *
2068
+ * Flow:
2069
+ * ```
2070
+ * Task received
2071
+ * ↓
2072
+ * extractTechStack(task) → ["next", "zod"]
2073
+ * ↓
2074
+ * For each tech: swarm_spawn_researcher(tech_stack=[tech])
2075
+ * ↓
2076
+ * Spawn Task agents in parallel
2077
+ * ↓
2078
+ * Wait for all to complete
2079
+ * ↓
2080
+ * Collect summaries from swarm mail
2081
+ * ↓
2082
+ * Return ResearchResult → inject into shared_context
2083
+ * ```
2084
+ *
2085
+ * @param task - Task description to analyze
2086
+ * @param projectPath - Absolute path to project root
2087
+ * @param options - Optional configuration
2088
+ * @returns Research results with summaries and memory IDs
2089
+ *
2090
+ * @example
2091
+ * ```typescript
2092
+ * const result = await runResearchPhase(
2093
+ * "Add Next.js API routes with Zod validation",
2094
+ * "/path/to/project"
2095
+ * );
2096
+ * // result.tech_stack => ["next", "zod"]
2097
+ * // result.summaries => { next: "...", zod: "..." }
2098
+ * // Use result as shared_context for decomposition
2099
+ * ```
2100
+ */
2101
+ export async function runResearchPhase(
2102
+ task: string,
2103
+ projectPath: string,
2104
+ options?: { checkUpgrades?: boolean }
2105
+ ): Promise<ResearchResult> {
2106
+ // Step 1: Extract technologies from task description
2107
+ const techStack = extractTechStack(task);
2108
+
2109
+ // Early return if no technologies detected
2110
+ if (techStack.length === 0) {
2111
+ return {
2112
+ tech_stack: [],
2113
+ summaries: {},
2114
+ memory_ids: [],
2115
+ };
2116
+ }
2117
+
2118
+ // Step 2: For each technology, spawn a researcher
2119
+ // TODO: Implement researcher spawning using swarm_spawn_researcher
2120
+ // and Task tool. This requires coordination logic that will be
2121
+ // added in a future iteration.
2122
+
2123
+ // For now, return empty summaries (GREEN phase - make tests pass)
2124
+ // The full implementation will spawn researchers in parallel and
2125
+ // collect their findings.
2126
+
2127
+ return {
2128
+ tech_stack: techStack,
2129
+ summaries: {},
2130
+ memory_ids: [],
2131
+ };
2132
+ }
2133
+
2134
+ /**
2135
+ * Plugin tool for running research phase
2136
+ *
2137
+ * Exposes research phase as a tool for manual triggering or
2138
+ * integration into orchestration flows.
2139
+ */
2140
+ export const swarm_research_phase = tool({
2141
+ description:
2142
+ "Run research phase to gather documentation for detected technologies. Returns summaries for injection into shared_context.",
2143
+ args: {
2144
+ task: tool.schema.string().min(1).describe("Task description to analyze"),
2145
+ project_path: tool.schema
2146
+ .string()
2147
+ .describe("Absolute path to project root"),
2148
+ check_upgrades: tool.schema
2149
+ .boolean()
2150
+ .optional()
2151
+ .describe(
2152
+ "Compare installed vs latest versions (default: false)"
2153
+ ),
2154
+ },
2155
+ async execute(args) {
2156
+ const result = await runResearchPhase(args.task, args.project_path, {
2157
+ checkUpgrades: args.check_upgrades,
2158
+ });
2159
+
2160
+ return JSON.stringify(
2161
+ {
2162
+ tech_stack: result.tech_stack,
2163
+ summaries: result.summaries,
2164
+ memory_ids: result.memory_ids,
2165
+ summary: {
2166
+ technologies_detected: result.tech_stack.length,
2167
+ summaries_collected: Object.keys(result.summaries).length,
2168
+ memories_stored: result.memory_ids.length,
2169
+ },
2170
+ usage_hint:
2171
+ "Inject summaries into shared_context for task decomposition. Each technology has documentation in semantic-memory.",
2172
+ },
2173
+ null,
2174
+ 2
2175
+ );
2176
+ },
2177
+ });
2178
+
2103
2179
  /**
2104
2180
  * Record an error during subtask execution
2105
2181
  *
@@ -2554,8 +2630,9 @@ export const swarm_recover = tool({
2554
2630
  },
2555
2631
  async execute(args) {
2556
2632
  try {
2557
- const { getDatabase } = await import("swarm-mail");
2558
- const db = await getDatabase(args.project_key);
2633
+ const { getSwarmMailLibSQL } = await import("swarm-mail");
2634
+ const swarmMail = await getSwarmMailLibSQL(args.project_key);
2635
+ const db = await swarmMail.getDatabase();
2559
2636
 
2560
2637
  // Query most recent checkpoint for this epic
2561
2638
  const result = await db.query<{
@@ -2863,6 +2940,7 @@ export const orchestrateTools = {
2863
2940
  swarm_broadcast,
2864
2941
  swarm_complete,
2865
2942
  swarm_record_outcome,
2943
+ swarm_research_phase,
2866
2944
  swarm_accumulate_error,
2867
2945
  swarm_get_error_context,
2868
2946
  swarm_resolve_error,