opencode-swarm-plugin 0.28.2 → 0.29.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.
@@ -3,7 +3,7 @@ Bundled 200 modules in 35ms
3
3
 
4
4
  index.js 1.20 MB (entry point)
5
5
 
6
- Bundled 201 modules in 32ms
6
+ Bundled 201 modules in 34ms
7
7
 
8
8
  plugin.js 1.16 MB (entry point)
9
9
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,67 @@
1
1
  # opencode-swarm-plugin
2
2
 
3
+ ## 0.29.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`a2ff1f4`](https://github.com/joelhooks/swarm-tools/commit/a2ff1f4257a2e9857f63abe4e9b941a573f44380) Thanks [@joelhooks](https://github.com/joelhooks)! - ## 🐝 Cell IDs Now Wear Their Project Colors
8
+
9
+ > _"We may fantasize about being International Men of Mystery, but our code needs to be mundane and clear. One of the most important parts of clear code is good names."_
10
+ > — Martin Fowler, _Refactoring_
11
+
12
+ Cell IDs finally know where they came from. Instead of anonymous `bd-xxx` prefixes,
13
+ new cells proudly display their project name: `swarm-mail-lf2p4u-abc123`.
14
+
15
+ ### What Changed
16
+
17
+ **swarm-mail:**
18
+
19
+ - `generateBeadId()` now reads `package.json` name field from project directory
20
+ - Added `slugifyProjectName()` for safe ID generation (lowercase, special chars → dashes)
21
+ - Falls back to `cell-` prefix if no package.json or no name field
22
+
23
+ **opencode-swarm-plugin:**
24
+
25
+ - Removed all `bd` CLI usage from `swarm-orchestrate.ts` - now uses HiveAdapter
26
+ - Improved compaction hook swarm detection with confidence levels (high/medium/low)
27
+ - Added fallback detection prompt for uncertain swarm states
28
+
29
+ ### Examples
30
+
31
+ | Before | After |
32
+ | ----------------------- | ------------------------------- |
33
+ | `bd-lf2p4u-mjbneh7mqah` | `swarm-mail-lf2p4u-mjbneh7mqah` |
34
+ | `bd-abc123-xyz` | `my-cool-app-abc123-xyz` |
35
+ | (no package.json) | `cell-abc123-xyz` |
36
+
37
+ ### Why It Matters
38
+
39
+ - **Identifiable at a glance** - Know which project a cell belongs to without looking it up
40
+ - **Multi-project workspaces** - Filter/search cells by project prefix
41
+ - **Terminology cleanup** - Removes legacy "bead" (`bd-`) from user-facing IDs
42
+
43
+ ### Backward Compatible
44
+
45
+ Existing `bd-*` IDs still work fine. No migration needed - only NEW cells get project prefixes.
46
+
47
+ ### Compaction: Keeping the Swarm Alive
48
+
49
+ > _"Intelligent and structured group dynamics that emerge not from a leader, but from the local interactions of the elements themselves."_
50
+ > — Daniel Shiffman, _The Nature of Code_
51
+
52
+ The compaction hook now uses multi-signal detection to keep swarms cooking through context compression:
53
+
54
+ - **HIGH confidence:** Active reservations, in_progress cells → full swarm context
55
+ - **MEDIUM confidence:** Open subtasks, unclosed epics → full swarm context
56
+ - **LOW confidence:** Any cells exist → fallback detection prompt
57
+
58
+ Philosophy: Err on the side of continuation. A false positive costs context space. A false negative loses the swarm.
59
+
60
+ ### Patch Changes
61
+
62
+ - Updated dependencies [[`a2ff1f4`](https://github.com/joelhooks/swarm-tools/commit/a2ff1f4257a2e9857f63abe4e9b941a573f44380)]:
63
+ - swarm-mail@0.4.0
64
+
3
65
  ## 0.28.2
4
66
 
5
67
  ### Patch Changes
@@ -5,6 +5,12 @@
5
5
  * When context is compacted, this hook injects instructions for the summarizer
6
6
  * to preserve swarm coordination state and enable seamless resumption.
7
7
  *
8
+ * ## Philosophy: Err on the Side of Continuation
9
+ *
10
+ * It's better to inject swarm context unnecessarily than to lose an active swarm.
11
+ * The cost of a false positive (extra context) is low.
12
+ * The cost of a false negative (lost swarm) is high - wasted work, confused agents.
13
+ *
8
14
  * Hook signature (from @opencode-ai/plugin):
9
15
  * ```typescript
10
16
  * "experimental.session.compacting"?: (
@@ -33,11 +39,23 @@
33
39
  * autonomously after context compression.
34
40
  */
35
41
  export declare const SWARM_COMPACTION_CONTEXT = "## \uD83D\uDC1D SWARM ACTIVE - Keep Cooking\n\nYou are the **COORDINATOR** of an active swarm. Context was compacted but the swarm is still running.\n\n**YOUR JOB:** Keep orchestrating. Spawn agents. Monitor progress. Unblock work. Ship it.\n\n### Preserve in Summary\n\nExtract from session context:\n\n1. **Epic & Subtasks** - IDs, titles, status, file assignments\n2. **What's Running** - Which agents are active, what they're working on \n3. **What's Blocked** - Blockers and what's needed to unblock\n4. **What's Done** - Completed work and any follow-ups needed\n5. **What's Next** - Pending subtasks ready to spawn\n\n### Summary Format\n\n```\n## \uD83D\uDC1D Swarm State\n\n**Epic:** <bd-xxx> - <title>\n**Project:** <path>\n**Progress:** X/Y subtasks complete\n\n**Active:**\n- <bd-xxx>: <title> [in_progress] \u2192 <agent> working on <files>\n\n**Blocked:**\n- <bd-xxx>: <title> - BLOCKED: <reason>\n\n**Completed:**\n- <bd-xxx>: <title> \u2713\n\n**Ready to Spawn:**\n- <bd-xxx>: <title> (files: <...>)\n```\n\n### On Resume - IMMEDIATELY\n\n1. `swarm_status(epic_id=\"<epic>\", project_key=\"<path>\")` - Get current state\n2. `swarmmail_inbox(limit=5)` - Check for agent messages\n3. **Spawn ready subtasks** - Don't wait, fire them off\n4. **Unblock blocked work** - Resolve dependencies, reassign if needed\n5. **Collect completed work** - Close done subtasks, verify quality\n\n### Keep the Swarm Cooking\n\n- **Spawn aggressively** - If a subtask is ready and unblocked, spawn an agent\n- **Monitor actively** - Check status, read messages, respond to blockers\n- **Close the loop** - When all subtasks done, verify and close the epic\n- **Don't stop** - The swarm runs until the epic is closed\n\n**You are not waiting for instructions. You are the coordinator. Coordinate.**\n";
42
+ /**
43
+ * Fallback detection prompt - tells the compactor what to look for
44
+ *
45
+ * Used when we can't definitively detect a swarm but want to be safe.
46
+ * The compactor can check the conversation context for these patterns.
47
+ */
48
+ export declare const SWARM_DETECTION_FALLBACK = "## \uD83D\uDC1D Swarm Detection - Check Your Context\n\n**IMPORTANT:** Before summarizing, check if this session involves an active swarm.\n\nLook for ANY of these patterns in the conversation:\n\n### Tool Calls (definite swarm sign)\n- `swarm_decompose`, `swarm_spawn_subtask`, `swarm_status`, `swarm_complete`\n- `swarmmail_init`, `swarmmail_reserve`, `swarmmail_send`\n- `hive_create_epic`, `hive_start`, `hive_close`\n\n### IDs and Names\n- Cell IDs: `bd-xxx`, `bd-xxx.N` (subtask format)\n- Agent names: BlueLake, RedMountain, GreenValley, etc.\n- Epic references: \"epic\", \"subtask\", \"parent\"\n\n### Coordination Language\n- \"spawn\", \"worker\", \"coordinator\"\n- \"reserve\", \"reservation\", \"files\"\n- \"blocked\", \"unblock\", \"dependency\"\n- \"progress\", \"complete\", \"in_progress\"\n\n### If You Find Swarm Evidence\n\nInclude this in your summary:\n1. Epic ID and title\n2. Project path\n3. Subtask status (running/blocked/done/pending)\n4. Any blockers or issues\n5. What should happen next\n\n**Then tell the resumed session:**\n\"This is an active swarm. Check swarm_status and swarmmail_inbox immediately.\"\n";
36
49
  /**
37
50
  * Create the compaction hook for use in plugin registration
38
51
  *
39
- * Only injects swarm context if there's an active swarm (in-progress beads).
40
- * This keeps the coordinator cooking after compaction.
52
+ * Injects swarm context based on detection confidence:
53
+ * - HIGH/MEDIUM: Full swarm context (definitely/probably a swarm)
54
+ * - LOW: Fallback detection prompt (let compactor check context)
55
+ * - NONE: No injection (probably not a swarm)
56
+ *
57
+ * Philosophy: Err on the side of continuation. A false positive costs
58
+ * a bit of context space. A false negative loses the swarm.
41
59
  *
42
60
  * @example
43
61
  * ```typescript
@@ -1 +1 @@
1
- {"version":3,"file":"compaction-hook.d.ts","sourceRoot":"","sources":["../src/compaction-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAMH;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,2wDAsDpC,CAAC;AAyCF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,KAEhC,QAAQ;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,EAC7B,QAAQ;IAAE,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,KAC5B,OAAO,CAAC,IAAI,CAAC,CAMjB"}
1
+ {"version":3,"file":"compaction-hook.d.ts","sourceRoot":"","sources":["../src/compaction-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AASH;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,2wDAsDpC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,0nCAiCpC,CAAC;AAoIF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,oBAAoB,KAEhC,QAAQ;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,EAC7B,QAAQ;IAAE,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,KAC5B,OAAO,CAAC,IAAI,CAAC,CAcjB"}
package/dist/index.js CHANGED
@@ -28523,27 +28523,11 @@ var toolCheckers = {
28523
28523
  }
28524
28524
  },
28525
28525
  beads: async () => {
28526
- const exists = await commandExists("bd");
28527
- if (!exists) {
28528
- return {
28529
- available: false,
28530
- checkedAt: new Date().toISOString(),
28531
- error: "bd command not found"
28532
- };
28533
- }
28534
- try {
28535
- const result = await Bun.$`bd --version`.quiet().nothrow();
28536
- return {
28537
- available: result.exitCode === 0,
28538
- checkedAt: new Date().toISOString()
28539
- };
28540
- } catch (e) {
28541
- return {
28542
- available: false,
28543
- checkedAt: new Date().toISOString(),
28544
- error: String(e)
28545
- };
28546
- }
28526
+ return {
28527
+ available: false,
28528
+ checkedAt: new Date().toISOString(),
28529
+ error: "bd CLI is deprecated - use hive_* tools with HiveAdapter instead"
28530
+ };
28547
28531
  },
28548
28532
  "swarm-mail": async () => {
28549
28533
  try {
@@ -32663,27 +32647,25 @@ var reviewTools = {
32663
32647
  };
32664
32648
 
32665
32649
  // src/swarm-orchestrate.ts
32666
- async function queryEpicSubtasks(epicId) {
32667
- const beadsAvailable = await isToolAvailable("beads");
32668
- if (!beadsAvailable) {
32669
- warnMissingTool("beads");
32670
- return [];
32671
- }
32672
- const result = await Bun.$`bd list --parent ${epicId} --json`.quiet().nothrow();
32673
- if (result.exitCode !== 0) {
32674
- console.error(`[swarm] ERROR: Failed to query subtasks for epic ${epicId}:`, result.stderr.toString());
32675
- return [];
32676
- }
32650
+ async function queryEpicSubtasks(projectKey, epicId) {
32677
32651
  try {
32678
- const parsed = JSON.parse(result.stdout.toString());
32679
- return exports_external.array(BeadSchema).parse(parsed);
32652
+ const adapter = await getHiveAdapter(projectKey);
32653
+ const cells = await adapter.queryCells(projectKey, { parent_id: epicId });
32654
+ return cells.filter((cell) => cell.status !== "tombstone").map((cell) => ({
32655
+ id: cell.id,
32656
+ title: cell.title,
32657
+ description: cell.description || "",
32658
+ status: cell.status,
32659
+ priority: cell.priority,
32660
+ issue_type: cell.type,
32661
+ created_at: new Date(cell.created_at).toISOString(),
32662
+ updated_at: cell.updated_at ? new Date(cell.updated_at).toISOString() : undefined,
32663
+ dependencies: [],
32664
+ metadata: {}
32665
+ }));
32680
32666
  } catch (error45) {
32681
- if (error45 instanceof exports_external.ZodError) {
32682
- console.error(`[swarm] ERROR: Invalid bead data for epic ${epicId}:`, error45.message);
32683
- return [];
32684
- }
32685
- console.error(`[swarm] ERROR: Failed to parse beads for epic ${epicId}:`, error45);
32686
- throw error45;
32667
+ console.error(`[swarm] ERROR: Failed to query subtasks for epic ${epicId}:`, error45 instanceof Error ? error45.message : String(error45));
32668
+ return [];
32687
32669
  }
32688
32670
  }
32689
32671
  async function querySwarmMessages(projectKey, threadId) {
@@ -33035,7 +33017,7 @@ var swarm_status = tool({
33035
33017
  project_key: tool.schema.string().describe("Project path (for Agent Mail queries)")
33036
33018
  },
33037
33019
  async execute(args) {
33038
- const subtasks = await queryEpicSubtasks(args.epic_id);
33020
+ const subtasks = await queryEpicSubtasks(args.project_key, args.epic_id);
33039
33021
  const statusCounts = {
33040
33022
  running: 0,
33041
33023
  completed: 0,
@@ -33110,8 +33092,13 @@ var swarm_progress = tool({
33110
33092
  };
33111
33093
  const validated = AgentProgressSchema.parse(progress);
33112
33094
  if (args.status === "blocked" || args.status === "in_progress") {
33113
- const beadStatus = args.status === "blocked" ? "blocked" : "in_progress";
33114
- await Bun.$`bd update ${args.bead_id} --status ${beadStatus} --json`.quiet().nothrow();
33095
+ try {
33096
+ const adapter = await getHiveAdapter(args.project_key);
33097
+ const newStatus = args.status === "blocked" ? "blocked" : "in_progress";
33098
+ await adapter.changeCellStatus(args.project_key, args.bead_id, newStatus);
33099
+ } catch (error45) {
33100
+ console.error(`[swarm] Failed to update cell status: ${error45 instanceof Error ? error45.message : String(error45)}`);
33101
+ }
33115
33102
  }
33116
33103
  const epicId = args.bead_id.includes(".") ? args.bead_id.split(".")[0] : args.bead_id;
33117
33104
  await sendSwarmMessage3({
@@ -33255,6 +33242,22 @@ Or use skip_review=true to bypass (not recommended for production work).`
33255
33242
  }
33256
33243
  try {
33257
33244
  const projectKey = args.project_key.replace(/\//g, "-").replace(/\\/g, "-");
33245
+ const adapter = await getHiveAdapter(args.project_key);
33246
+ const cell = await adapter.getCell(projectKey, args.bead_id);
33247
+ if (!cell) {
33248
+ return JSON.stringify({
33249
+ success: false,
33250
+ error: `Bead not found: ${args.bead_id}`,
33251
+ hint: "Check the bead ID is correct. Use hive_query to list open cells."
33252
+ });
33253
+ }
33254
+ if (cell.status === "closed") {
33255
+ return JSON.stringify({
33256
+ success: false,
33257
+ error: `Bead already closed: ${args.bead_id}`,
33258
+ hint: "This bead was already completed. No action needed."
33259
+ });
33260
+ }
33258
33261
  let agentRegistered = false;
33259
33262
  let registrationWarning = "";
33260
33263
  try {
@@ -33339,32 +33342,25 @@ Continuing with completion, but this should be fixed for future subtasks.`;
33339
33342
  }, null, 2);
33340
33343
  }
33341
33344
  }
33342
- const closeResult = await Bun.$`bd close ${args.bead_id} --reason ${args.summary} --json`.cwd(args.project_key).quiet().nothrow();
33343
- if (closeResult.exitCode !== 0) {
33344
- const stderrOutput = closeResult.stderr.toString().trim();
33345
- const stdoutOutput = closeResult.stdout.toString().trim();
33346
- const isNoDatabaseError = stderrOutput.includes("no beads database found");
33347
- const isNotFoundError = stderrOutput.includes("not found") || stderrOutput.includes("does not exist");
33345
+ try {
33346
+ await adapter.closeCell(args.project_key, args.bead_id, args.summary);
33347
+ } catch (closeError) {
33348
+ const errorMessage = closeError instanceof Error ? closeError.message : String(closeError);
33348
33349
  return JSON.stringify({
33349
33350
  success: false,
33350
33351
  error: "Failed to close cell",
33351
- failed_step: "bd close",
33352
- details: stderrOutput || stdoutOutput || "Unknown error from bd close command",
33352
+ failed_step: "closeCell",
33353
+ details: errorMessage,
33353
33354
  bead_id: args.bead_id,
33354
33355
  project_key: args.project_key,
33355
33356
  recovery: {
33356
- steps: isNoDatabaseError ? [
33357
- `1. Verify project_key is correct: "${args.project_key}"`,
33358
- `2. Check .beads/ exists in that directory`,
33359
- `3. Cell ID prefix "${args.bead_id.split("-")[0]}" should match project`,
33360
- `4. Try: hive_close(id="${args.bead_id}", reason="...")`
33361
- ] : [
33362
- `1. Check cell exists: bd show ${args.bead_id}`,
33363
- `2. Check cell status (might already be closed): hive_query()`,
33357
+ steps: [
33358
+ `1. Check cell exists: hive_query()`,
33359
+ `2. Check cell status (might already be closed)`,
33364
33360
  `3. If cell is blocked, unblock first: hive_update(id="${args.bead_id}", status="in_progress")`,
33365
33361
  `4. Try closing directly: hive_close(id="${args.bead_id}", reason="...")`
33366
33362
  ],
33367
- hint: isNoDatabaseError ? `The project_key "${args.project_key}" doesn't have a .beads/ directory. Make sure you're using the correct project path.` : isNotFoundError ? `Cell "${args.bead_id}" not found. It may have been closed already or the ID is incorrect.` : "If cell is in 'blocked' status, you must change it to 'in_progress' or 'open' before closing."
33363
+ hint: "Cell may already be closed, or the ID is incorrect."
33368
33364
  }
33369
33365
  }, null, 2);
33370
33366
  }
@@ -33565,12 +33561,13 @@ ${errorStack.slice(0, 1000)}
33565
33561
  }
33566
33562
  return JSON.stringify({
33567
33563
  success: false,
33568
- error: errorMessage,
33564
+ error: `swarm_complete failed: ${errorMessage}`,
33569
33565
  failed_step: failedStep,
33570
33566
  bead_id: args.bead_id,
33571
33567
  agent_name: args.agent_name,
33572
33568
  coordinator_notified: notificationSent,
33573
33569
  stack_trace: errorStack?.slice(0, 500),
33570
+ hint: "Check the error message above. Common issues: bead not found, session not initialized.",
33574
33571
  context: {
33575
33572
  summary: args.summary,
33576
33573
  files_touched: args.files_touched || [],
@@ -35890,6 +35887,7 @@ async function resetStorage() {
35890
35887
  init_skills();
35891
35888
 
35892
35889
  // src/compaction-hook.ts
35890
+ import { checkSwarmHealth as checkSwarmHealth3 } from "swarm-mail";
35893
35891
  var SWARM_COMPACTION_CONTEXT = `## \uD83D\uDC1D SWARM ACTIVE - Keep Cooking
35894
35892
 
35895
35893
  You are the **COORDINATOR** of an active swarm. Context was compacted but the swarm is still running.
@@ -35945,23 +35943,128 @@ Extract from session context:
35945
35943
 
35946
35944
  **You are not waiting for instructions. You are the coordinator. Coordinate.**
35947
35945
  `;
35948
- async function hasSwarmSign() {
35946
+ var SWARM_DETECTION_FALLBACK = `## \uD83D\uDC1D Swarm Detection - Check Your Context
35947
+
35948
+ **IMPORTANT:** Before summarizing, check if this session involves an active swarm.
35949
+
35950
+ Look for ANY of these patterns in the conversation:
35951
+
35952
+ ### Tool Calls (definite swarm sign)
35953
+ - \`swarm_decompose\`, \`swarm_spawn_subtask\`, \`swarm_status\`, \`swarm_complete\`
35954
+ - \`swarmmail_init\`, \`swarmmail_reserve\`, \`swarmmail_send\`
35955
+ - \`hive_create_epic\`, \`hive_start\`, \`hive_close\`
35956
+
35957
+ ### IDs and Names
35958
+ - Cell IDs: \`bd-xxx\`, \`bd-xxx.N\` (subtask format)
35959
+ - Agent names: BlueLake, RedMountain, GreenValley, etc.
35960
+ - Epic references: "epic", "subtask", "parent"
35961
+
35962
+ ### Coordination Language
35963
+ - "spawn", "worker", "coordinator"
35964
+ - "reserve", "reservation", "files"
35965
+ - "blocked", "unblock", "dependency"
35966
+ - "progress", "complete", "in_progress"
35967
+
35968
+ ### If You Find Swarm Evidence
35969
+
35970
+ Include this in your summary:
35971
+ 1. Epic ID and title
35972
+ 2. Project path
35973
+ 3. Subtask status (running/blocked/done/pending)
35974
+ 4. Any blockers or issues
35975
+ 5. What should happen next
35976
+
35977
+ **Then tell the resumed session:**
35978
+ "This is an active swarm. Check swarm_status and swarmmail_inbox immediately."
35979
+ `;
35980
+ async function detectSwarm() {
35981
+ const reasons = [];
35982
+ let highConfidence = false;
35983
+ let mediumConfidence = false;
35984
+ let lowConfidence = false;
35949
35985
  try {
35950
35986
  const projectKey = getHiveWorkingDirectory();
35951
- const adapter = await getHiveAdapter(projectKey);
35952
- const cells = await adapter.queryCells(projectKey, {});
35953
- if (!Array.isArray(cells))
35954
- return false;
35955
- return cells.some((c) => c.status === "in_progress" || c.status === "open" && c.parent_id || c.type === "epic" && c.status !== "closed");
35987
+ try {
35988
+ const health = await checkSwarmHealth3(projectKey);
35989
+ if (health.healthy && health.stats) {
35990
+ if (health.stats.reservations > 0) {
35991
+ highConfidence = true;
35992
+ reasons.push(`${health.stats.reservations} active file reservations`);
35993
+ }
35994
+ if (health.stats.agents > 0) {
35995
+ mediumConfidence = true;
35996
+ reasons.push(`${health.stats.agents} registered agents`);
35997
+ }
35998
+ if (health.stats.messages > 0) {
35999
+ lowConfidence = true;
36000
+ reasons.push(`${health.stats.messages} swarm messages`);
36001
+ }
36002
+ }
36003
+ } catch {}
36004
+ try {
36005
+ const adapter = await getHiveAdapter(projectKey);
36006
+ const cells = await adapter.queryCells(projectKey, {});
36007
+ if (Array.isArray(cells) && cells.length > 0) {
36008
+ const inProgress = cells.filter((c) => c.status === "in_progress");
36009
+ if (inProgress.length > 0) {
36010
+ highConfidence = true;
36011
+ reasons.push(`${inProgress.length} cells in_progress`);
36012
+ }
36013
+ const subtasks = cells.filter((c) => c.status === "open" && c.parent_id);
36014
+ if (subtasks.length > 0) {
36015
+ mediumConfidence = true;
36016
+ reasons.push(`${subtasks.length} open subtasks`);
36017
+ }
36018
+ const openEpics = cells.filter((c) => c.type === "epic" && c.status !== "closed");
36019
+ if (openEpics.length > 0) {
36020
+ mediumConfidence = true;
36021
+ reasons.push(`${openEpics.length} unclosed epics`);
36022
+ }
36023
+ const oneHourAgo = Date.now() - 60 * 60 * 1000;
36024
+ const recentCells = cells.filter((c) => c.updated_at > oneHourAgo);
36025
+ if (recentCells.length > 0) {
36026
+ mediumConfidence = true;
36027
+ reasons.push(`${recentCells.length} cells updated in last hour`);
36028
+ }
36029
+ if (cells.length > 0) {
36030
+ lowConfidence = true;
36031
+ reasons.push(`${cells.length} total cells in hive`);
36032
+ }
36033
+ }
36034
+ } catch {}
35956
36035
  } catch {
35957
- return false;
36036
+ lowConfidence = true;
36037
+ reasons.push("Could not detect project, using fallback");
36038
+ }
36039
+ let confidence;
36040
+ if (highConfidence) {
36041
+ confidence = "high";
36042
+ } else if (mediumConfidence) {
36043
+ confidence = "medium";
36044
+ } else if (lowConfidence) {
36045
+ confidence = "low";
36046
+ } else {
36047
+ confidence = "none";
35958
36048
  }
36049
+ return {
36050
+ detected: confidence !== "none",
36051
+ confidence,
36052
+ reasons
36053
+ };
35959
36054
  }
35960
36055
  function createCompactionHook() {
35961
36056
  return async (_input, output) => {
35962
- const hasSign = await hasSwarmSign();
35963
- if (hasSign) {
35964
- output.context.push(SWARM_COMPACTION_CONTEXT);
36057
+ const detection = await detectSwarm();
36058
+ if (detection.confidence === "high" || detection.confidence === "medium") {
36059
+ const header = `[Swarm detected: ${detection.reasons.join(", ")}]
36060
+
36061
+ `;
36062
+ output.context.push(header + SWARM_COMPACTION_CONTEXT);
36063
+ } else if (detection.confidence === "low") {
36064
+ const header = `[Possible swarm: ${detection.reasons.join(", ")}]
36065
+
36066
+ `;
36067
+ output.context.push(header + SWARM_DETECTION_FALLBACK);
35965
36068
  }
35966
36069
  };
35967
36070
  }
package/dist/plugin.js CHANGED
@@ -27156,7 +27156,6 @@ var EpicCreateResultSchema = exports_external.object({
27156
27156
  subtasks: exports_external.array(CellSchema),
27157
27157
  rollback_hint: exports_external.string().optional()
27158
27158
  });
27159
- var BeadSchema = CellSchema;
27160
27159
  // src/schemas/evaluation.ts
27161
27160
  init_zod();
27162
27161
  var CriterionEvaluationSchema = exports_external.object({
@@ -28269,27 +28268,11 @@ var toolCheckers = {
28269
28268
  }
28270
28269
  },
28271
28270
  beads: async () => {
28272
- const exists = await commandExists("bd");
28273
- if (!exists) {
28274
- return {
28275
- available: false,
28276
- checkedAt: new Date().toISOString(),
28277
- error: "bd command not found"
28278
- };
28279
- }
28280
- try {
28281
- const result = await Bun.$`bd --version`.quiet().nothrow();
28282
- return {
28283
- available: result.exitCode === 0,
28284
- checkedAt: new Date().toISOString()
28285
- };
28286
- } catch (e) {
28287
- return {
28288
- available: false,
28289
- checkedAt: new Date().toISOString(),
28290
- error: String(e)
28291
- };
28292
- }
28271
+ return {
28272
+ available: false,
28273
+ checkedAt: new Date().toISOString(),
28274
+ error: "bd CLI is deprecated - use hive_* tools with HiveAdapter instead"
28275
+ };
28293
28276
  },
28294
28277
  "swarm-mail": async () => {
28295
28278
  try {
@@ -32263,27 +32246,25 @@ var reviewTools = {
32263
32246
  };
32264
32247
 
32265
32248
  // src/swarm-orchestrate.ts
32266
- async function queryEpicSubtasks(epicId) {
32267
- const beadsAvailable = await isToolAvailable("beads");
32268
- if (!beadsAvailable) {
32269
- warnMissingTool("beads");
32270
- return [];
32271
- }
32272
- const result = await Bun.$`bd list --parent ${epicId} --json`.quiet().nothrow();
32273
- if (result.exitCode !== 0) {
32274
- console.error(`[swarm] ERROR: Failed to query subtasks for epic ${epicId}:`, result.stderr.toString());
32275
- return [];
32276
- }
32249
+ async function queryEpicSubtasks(projectKey, epicId) {
32277
32250
  try {
32278
- const parsed = JSON.parse(result.stdout.toString());
32279
- return exports_external.array(BeadSchema).parse(parsed);
32251
+ const adapter = await getHiveAdapter(projectKey);
32252
+ const cells = await adapter.queryCells(projectKey, { parent_id: epicId });
32253
+ return cells.filter((cell) => cell.status !== "tombstone").map((cell) => ({
32254
+ id: cell.id,
32255
+ title: cell.title,
32256
+ description: cell.description || "",
32257
+ status: cell.status,
32258
+ priority: cell.priority,
32259
+ issue_type: cell.type,
32260
+ created_at: new Date(cell.created_at).toISOString(),
32261
+ updated_at: cell.updated_at ? new Date(cell.updated_at).toISOString() : undefined,
32262
+ dependencies: [],
32263
+ metadata: {}
32264
+ }));
32280
32265
  } catch (error45) {
32281
- if (error45 instanceof exports_external.ZodError) {
32282
- console.error(`[swarm] ERROR: Invalid bead data for epic ${epicId}:`, error45.message);
32283
- return [];
32284
- }
32285
- console.error(`[swarm] ERROR: Failed to parse beads for epic ${epicId}:`, error45);
32286
- throw error45;
32266
+ console.error(`[swarm] ERROR: Failed to query subtasks for epic ${epicId}:`, error45 instanceof Error ? error45.message : String(error45));
32267
+ return [];
32287
32268
  }
32288
32269
  }
32289
32270
  async function querySwarmMessages(projectKey, threadId) {
@@ -32635,7 +32616,7 @@ var swarm_status = tool({
32635
32616
  project_key: tool.schema.string().describe("Project path (for Agent Mail queries)")
32636
32617
  },
32637
32618
  async execute(args) {
32638
- const subtasks = await queryEpicSubtasks(args.epic_id);
32619
+ const subtasks = await queryEpicSubtasks(args.project_key, args.epic_id);
32639
32620
  const statusCounts = {
32640
32621
  running: 0,
32641
32622
  completed: 0,
@@ -32710,8 +32691,13 @@ var swarm_progress = tool({
32710
32691
  };
32711
32692
  const validated = AgentProgressSchema.parse(progress);
32712
32693
  if (args.status === "blocked" || args.status === "in_progress") {
32713
- const beadStatus = args.status === "blocked" ? "blocked" : "in_progress";
32714
- await Bun.$`bd update ${args.bead_id} --status ${beadStatus} --json`.quiet().nothrow();
32694
+ try {
32695
+ const adapter = await getHiveAdapter(args.project_key);
32696
+ const newStatus = args.status === "blocked" ? "blocked" : "in_progress";
32697
+ await adapter.changeCellStatus(args.project_key, args.bead_id, newStatus);
32698
+ } catch (error45) {
32699
+ console.error(`[swarm] Failed to update cell status: ${error45 instanceof Error ? error45.message : String(error45)}`);
32700
+ }
32715
32701
  }
32716
32702
  const epicId = args.bead_id.includes(".") ? args.bead_id.split(".")[0] : args.bead_id;
32717
32703
  await sendSwarmMessage3({
@@ -32855,6 +32841,22 @@ Or use skip_review=true to bypass (not recommended for production work).`
32855
32841
  }
32856
32842
  try {
32857
32843
  const projectKey = args.project_key.replace(/\//g, "-").replace(/\\/g, "-");
32844
+ const adapter = await getHiveAdapter(args.project_key);
32845
+ const cell = await adapter.getCell(projectKey, args.bead_id);
32846
+ if (!cell) {
32847
+ return JSON.stringify({
32848
+ success: false,
32849
+ error: `Bead not found: ${args.bead_id}`,
32850
+ hint: "Check the bead ID is correct. Use hive_query to list open cells."
32851
+ });
32852
+ }
32853
+ if (cell.status === "closed") {
32854
+ return JSON.stringify({
32855
+ success: false,
32856
+ error: `Bead already closed: ${args.bead_id}`,
32857
+ hint: "This bead was already completed. No action needed."
32858
+ });
32859
+ }
32858
32860
  let agentRegistered = false;
32859
32861
  let registrationWarning = "";
32860
32862
  try {
@@ -32939,32 +32941,25 @@ Continuing with completion, but this should be fixed for future subtasks.`;
32939
32941
  }, null, 2);
32940
32942
  }
32941
32943
  }
32942
- const closeResult = await Bun.$`bd close ${args.bead_id} --reason ${args.summary} --json`.cwd(args.project_key).quiet().nothrow();
32943
- if (closeResult.exitCode !== 0) {
32944
- const stderrOutput = closeResult.stderr.toString().trim();
32945
- const stdoutOutput = closeResult.stdout.toString().trim();
32946
- const isNoDatabaseError = stderrOutput.includes("no beads database found");
32947
- const isNotFoundError = stderrOutput.includes("not found") || stderrOutput.includes("does not exist");
32944
+ try {
32945
+ await adapter.closeCell(args.project_key, args.bead_id, args.summary);
32946
+ } catch (closeError) {
32947
+ const errorMessage = closeError instanceof Error ? closeError.message : String(closeError);
32948
32948
  return JSON.stringify({
32949
32949
  success: false,
32950
32950
  error: "Failed to close cell",
32951
- failed_step: "bd close",
32952
- details: stderrOutput || stdoutOutput || "Unknown error from bd close command",
32951
+ failed_step: "closeCell",
32952
+ details: errorMessage,
32953
32953
  bead_id: args.bead_id,
32954
32954
  project_key: args.project_key,
32955
32955
  recovery: {
32956
- steps: isNoDatabaseError ? [
32957
- `1. Verify project_key is correct: "${args.project_key}"`,
32958
- `2. Check .beads/ exists in that directory`,
32959
- `3. Cell ID prefix "${args.bead_id.split("-")[0]}" should match project`,
32960
- `4. Try: hive_close(id="${args.bead_id}", reason="...")`
32961
- ] : [
32962
- `1. Check cell exists: bd show ${args.bead_id}`,
32963
- `2. Check cell status (might already be closed): hive_query()`,
32956
+ steps: [
32957
+ `1. Check cell exists: hive_query()`,
32958
+ `2. Check cell status (might already be closed)`,
32964
32959
  `3. If cell is blocked, unblock first: hive_update(id="${args.bead_id}", status="in_progress")`,
32965
32960
  `4. Try closing directly: hive_close(id="${args.bead_id}", reason="...")`
32966
32961
  ],
32967
- hint: isNoDatabaseError ? `The project_key "${args.project_key}" doesn't have a .beads/ directory. Make sure you're using the correct project path.` : isNotFoundError ? `Cell "${args.bead_id}" not found. It may have been closed already or the ID is incorrect.` : "If cell is in 'blocked' status, you must change it to 'in_progress' or 'open' before closing."
32962
+ hint: "Cell may already be closed, or the ID is incorrect."
32968
32963
  }
32969
32964
  }, null, 2);
32970
32965
  }
@@ -33165,12 +33160,13 @@ ${errorStack.slice(0, 1000)}
33165
33160
  }
33166
33161
  return JSON.stringify({
33167
33162
  success: false,
33168
- error: errorMessage,
33163
+ error: `swarm_complete failed: ${errorMessage}`,
33169
33164
  failed_step: failedStep,
33170
33165
  bead_id: args.bead_id,
33171
33166
  agent_name: args.agent_name,
33172
33167
  coordinator_notified: notificationSent,
33173
33168
  stack_trace: errorStack?.slice(0, 500),
33169
+ hint: "Check the error message above. Common issues: bead not found, session not initialized.",
33174
33170
  context: {
33175
33171
  summary: args.summary,
33176
33172
  files_touched: args.files_touched || [],
@@ -35105,6 +35101,11 @@ var sessionStats = {
35105
35101
 
35106
35102
  // src/index.ts
35107
35103
  init_skills();
35104
+
35105
+ // src/compaction-hook.ts
35106
+ import { checkSwarmHealth as checkSwarmHealth3 } from "swarm-mail";
35107
+
35108
+ // src/index.ts
35108
35109
  var SwarmPlugin = async (input) => {
35109
35110
  const { $, directory } = input;
35110
35111
  setHiveWorkingDirectory(directory);