opencode-swarm-plugin 0.24.0 → 0.25.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
1
  $ bun build ./src/index.ts --outdir ./dist --target node --external @electric-sql/pglite --external swarm-mail && bun build ./src/plugin.ts --outfile ./dist/plugin.js --target node --external @electric-sql/pglite --external swarm-mail && tsc
2
- Bundled 196 modules in 42ms
2
+ Bundled 197 modules in 32ms
3
3
 
4
4
  index.js 1.16 MB (entry point)
5
5
 
6
- Bundled 197 modules in 36ms
6
+ Bundled 198 modules in 31ms
7
7
 
8
- plugin.js 1.13 MB (entry point)
8
+ plugin.js 1.14 MB (entry point)
9
9
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,48 @@
1
1
  # opencode-swarm-plugin
2
2
 
3
+ ## 0.25.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [`757f4a6`](https://github.com/joelhooks/swarm-tools/commit/757f4a690721b3f04a414e4c1694660862504e54) Thanks [@joelhooks](https://github.com/joelhooks)! - Fix skills_update tool - add `content` parameter as primary (with `body` as backwards-compat alias)
8
+
9
+ The tool was only accepting `body` but users expected `content`. Now both work:
10
+
11
+ - `skills_update(name="foo", content="new stuff")` - preferred
12
+ - `skills_update(name="foo", body="new stuff")` - still works for backwards compat
13
+
14
+ - [`3d619ff`](https://github.com/joelhooks/swarm-tools/commit/3d619ffda78b2e6066491f053e8fad8dac7b5b71) Thanks [@joelhooks](https://github.com/joelhooks)! - Fix swarm_complete failing when bead project doesn't match CWD
15
+
16
+ - Use `project_key` as working directory for `bd close` command
17
+ - Improved error messages with context-specific recovery steps
18
+ - Added planning guardrails to warn when todowrite is used for parallel work (should use swarm)
19
+
20
+ ## 0.25.0
21
+
22
+ ### Minor Changes
23
+
24
+ - [`b70ae35`](https://github.com/joelhooks/swarm-tools/commit/b70ae352876515bdfe68511d72bb472c85b7fdfc) Thanks [@joelhooks](https://github.com/joelhooks)! - Add Socratic planning phase and improved worker prompts to swarm setup
25
+
26
+ **SWARM_COMMAND template:**
27
+
28
+ - Added Phase 0: Socratic Planning - asks clarifying questions before decomposing
29
+ - Supports `--fast`, `--auto`, `--confirm-only` flags to skip questions
30
+ - ONE question at a time with concrete options and recommendations
31
+
32
+ **Worker agent template:**
33
+
34
+ - Reinforces the 9-step survival checklist from SUBTASK_PROMPT_V2
35
+ - Explicitly lists all steps with emphasis on non-negotiables
36
+ - Explains WHY skipping steps causes problems (lost work, conflicts, etc.)
37
+
38
+ **Agent path consolidation:**
39
+
40
+ - Now creates nested paths: `~/.config/opencode/agent/swarm/worker.md`
41
+ - Matches `Task(subagent_type="swarm/worker")` format
42
+ - Cleans up legacy flat files (`swarm-worker.md`) on reinstall
43
+
44
+ To get the new prompts, run `swarm setup` and choose "Reinstall everything".
45
+
3
46
  ## 0.24.0
4
47
 
5
48
  ### Minor Changes
package/bin/swarm.ts CHANGED
@@ -883,7 +883,7 @@ const SWARM_COMMAND = `---
883
883
  description: Decompose task into parallel subtasks and coordinate agents
884
884
  ---
885
885
 
886
- You are a swarm coordinator. Decompose the task into beads and spawn parallel agents.
886
+ You are a swarm coordinator. Your job is to clarify the task, decompose it into beads, and spawn parallel agents.
887
887
 
888
888
  ## Task
889
889
 
@@ -891,42 +891,84 @@ $ARGUMENTS
891
891
 
892
892
  ## Workflow
893
893
 
894
- ### 1. Initialize
894
+ ### Phase 0: Socratic Planning (INTERACTIVE - unless --fast)
895
+
896
+ **Before decomposing, clarify the task with the user.**
897
+
898
+ Check for flags in the task:
899
+ - \`--fast\` → Skip questions, use reasonable defaults
900
+ - \`--auto\` → Zero interaction, heuristic decisions
901
+ - \`--confirm-only\` → Show plan, get yes/no only
902
+
903
+ **Default (no flags): Full Socratic Mode**
904
+
905
+ 1. **Analyze task for ambiguity:**
906
+ - Scope unclear? (what's included/excluded)
907
+ - Strategy unclear? (file-based vs feature-based)
908
+ - Dependencies unclear? (what needs to exist first)
909
+ - Success criteria unclear? (how do we know it's done)
910
+
911
+ 2. **If clarification needed, ask ONE question at a time:**
912
+ \`\`\`
913
+ The task "<task>" needs clarification before I can decompose it.
914
+
915
+ **Question:** <specific question>
916
+
917
+ Options:
918
+ a) <option 1> - <tradeoff>
919
+ b) <option 2> - <tradeoff>
920
+ c) <option 3> - <tradeoff>
921
+
922
+ I'd recommend (b) because <reason>. Which approach?
923
+ \`\`\`
924
+
925
+ 3. **Wait for user response before proceeding**
926
+
927
+ 4. **Iterate if needed** (max 2-3 questions)
928
+
929
+ **Rules:**
930
+ - ONE question at a time - don't overwhelm
931
+ - Offer concrete options - not open-ended
932
+ - Lead with recommendation - save cognitive load
933
+ - Wait for answer - don't assume
934
+
935
+ ### Phase 1: Initialize
895
936
  \`swarmmail_init(project_path="$PWD", task_description="Swarm: <task>")\`
896
937
 
897
- ### 2. Knowledge Gathering (MANDATORY)
938
+ ### Phase 2: Knowledge Gathering (MANDATORY)
898
939
 
899
940
  **Before decomposing, query ALL knowledge sources:**
900
941
 
901
942
  \`\`\`
902
943
  semantic-memory_find(query="<task keywords>", limit=5) # Past learnings
903
944
  cass_search(query="<task description>", limit=5) # Similar past tasks
904
- pdf-brain_search(query="<domain concepts>", limit=5) # Design patterns
905
945
  skills_list() # Available skills
906
946
  \`\`\`
907
947
 
908
948
  Synthesize findings into shared_context for workers.
909
949
 
910
- ### 3. Decompose
950
+ ### Phase 3: Decompose
911
951
  \`\`\`
912
952
  swarm_select_strategy(task="<task>")
913
953
  swarm_plan_prompt(task="<task>", context="<synthesized knowledge>")
914
954
  swarm_validate_decomposition(response="<BeadTree JSON>")
915
955
  \`\`\`
916
956
 
917
- ### 4. Create Beads
957
+ ### Phase 4: Create Beads
918
958
  \`beads_create_epic(epic_title="<task>", subtasks=[...])\`
919
959
 
920
- ### 5. Reserve Files
960
+ ### Phase 5: Reserve Files
921
961
  \`swarmmail_reserve(paths=[...], reason="<bead-id>: <desc>")\`
922
962
 
923
- ### 6. Spawn Agents (ALL in single message)
963
+ ### Phase 6: Spawn Agents (ALL in single message)
924
964
  \`\`\`
925
- swarm_spawn_subtask(bead_id, epic_id, subtask_title, files, shared_context)
965
+ swarm_spawn_subtask(bead_id, epic_id, subtask_title, files, shared_context, project_path="$PWD")
926
966
  Task(subagent_type="swarm/worker", prompt="<from above>")
927
967
  \`\`\`
928
968
 
929
- ### 7. Monitor
969
+ **IMPORTANT:** Pass \`project_path\` to \`swarm_spawn_subtask\` so workers can call \`swarmmail_init\`.
970
+
971
+ ### Phase 7: Monitor
930
972
  \`\`\`
931
973
  swarm_status(epic_id, project_key)
932
974
  swarmmail_inbox()
@@ -934,7 +976,7 @@ swarmmail_inbox()
934
976
 
935
977
  Intervene if: blocked >5min, file conflicts, scope creep.
936
978
 
937
- ### 8. Complete
979
+ ### Phase 8: Complete
938
980
  \`\`\`
939
981
  swarm_complete(...)
940
982
  beads_sync()
@@ -949,7 +991,15 @@ beads_sync()
949
991
  | risk-based | Bug fixes, security | fix, bug, security, critical, urgent |
950
992
  | research-based | Investigation, discovery | research, investigate, explore, learn |
951
993
 
952
- Begin with knowledge gathering now.
994
+ ## Flag Reference
995
+
996
+ | Flag | Effect |
997
+ |------|--------|
998
+ | \`--fast\` | Skip Socratic questions, use defaults |
999
+ | \`--auto\` | Zero interaction, heuristic decisions |
1000
+ | \`--confirm-only\` | Show plan, get yes/no only |
1001
+
1002
+ Begin with Phase 0 (Socratic Planning) unless \`--fast\` or \`--auto\` flag is present.
953
1003
  `;
954
1004
 
955
1005
  const getPlannerAgent = (model: string) => `---
@@ -1017,47 +1067,61 @@ description: Executes subtasks in a swarm - fast, focused, cost-effective
1017
1067
  model: ${model}
1018
1068
  ---
1019
1069
 
1020
- You are a swarm worker agent. Execute your assigned subtask efficiently.
1070
+ You are a swarm worker agent. Your prompt contains a **MANDATORY SURVIVAL CHECKLIST** - follow it IN ORDER.
1021
1071
 
1022
- ## Context
1072
+ ## CRITICAL: Read Your Prompt Carefully
1023
1073
 
1024
- Your prompt includes shared_context from the coordinator's knowledge gathering:
1025
- - Relevant patterns from pdf-brain
1026
- - Similar past approaches from CASS
1027
- - Project-specific learnings from semantic-memory
1074
+ Your Task prompt contains detailed instructions including:
1075
+ - 9-step survival checklist (FOLLOW IN ORDER)
1076
+ - File reservations (YOU reserve, not coordinator)
1077
+ - Progress reporting requirements
1078
+ - Completion protocol
1028
1079
 
1029
- **Use this context** - it contains patterns and prior art relevant to your task.
1080
+ **DO NOT skip steps.** The checklist exists because skipping steps causes:
1081
+ - Lost work (no tracking)
1082
+ - Edit conflicts (no reservations)
1083
+ - Wasted time (no semantic memory query)
1084
+ - Silent failures (no progress reports)
1030
1085
 
1031
- ## Workflow
1086
+ ## Step Summary (details in your prompt)
1032
1087
 
1033
- 1. **Read** assigned files to understand current state
1034
- 2. **Check skills** if you need domain guidance: \`skills_use(name="<relevant-skill>")\`
1035
- 3. **Implement** changes following patterns from shared_context
1036
- 4. **Verify** (typecheck, lint if applicable)
1037
- 5. **Complete** with \`swarm_complete\`
1088
+ 1. **swarmmail_init()** - FIRST, before anything else
1089
+ 2. **semantic-memory_find()** - Check past learnings
1090
+ 3. **skills_list() / skills_use()** - Load relevant skills
1091
+ 4. **swarmmail_reserve()** - YOU reserve your files
1092
+ 5. **Do the work** - Read, implement, verify
1093
+ 6. **swarm_progress()** - Report at 25/50/75%
1094
+ 7. **swarm_checkpoint()** - Before risky operations
1095
+ 8. **semantic-memory_store()** - Store learnings
1096
+ 9. **swarm_complete()** - NOT beads_close
1038
1097
 
1039
- ## Rules
1098
+ ## Non-Negotiables
1040
1099
 
1041
- - Focus ONLY on your assigned files
1042
- - Report blockers immediately via Swarm Mail (don't spin)
1043
- - Use beads_update if blocked
1044
- - Call swarm_complete when done - it handles bead closure and file release
1100
+ - **Step 1 is MANDATORY** - swarm_complete fails without init
1101
+ - **Step 2 saves time** - past agents may have solved this
1102
+ - **Step 4 prevents conflicts** - workers reserve, not coordinator
1103
+ - **Step 6 prevents silent failure** - report progress
1104
+ - **Step 9 is the ONLY way to close** - releases reservations, records learning
1045
1105
 
1046
- ## Communication
1106
+ ## When Blocked
1047
1107
 
1048
1108
  \`\`\`
1049
1109
  swarmmail_send(
1050
1110
  to=["coordinator"],
1051
- subject="Progress/Blocker",
1052
- body="...",
1053
- thread_id="<epic_id>"
1111
+ subject="BLOCKED: <bead-id>",
1112
+ body="<what you need>",
1113
+ importance="high"
1054
1114
  )
1115
+ beads_update(id="<bead-id>", status="blocked")
1055
1116
  \`\`\`
1056
1117
 
1057
- ## Learning
1118
+ ## Focus
1058
1119
 
1059
- If you discover a reusable pattern worth preserving:
1060
- \`semantic-memory_store(information="<pattern + why it matters>")\`
1120
+ - Only modify your assigned files
1121
+ - Don't fix other agents' code - coordinate instead
1122
+ - Report scope changes before expanding
1123
+
1124
+ Begin by reading your full prompt and executing Step 1.
1061
1125
  `;
1062
1126
 
1063
1127
  // ============================================================================
@@ -1238,14 +1302,20 @@ async function setup() {
1238
1302
 
1239
1303
  const pluginPath = join(pluginDir, "swarm.ts");
1240
1304
  const commandPath = join(commandDir, "swarm.md");
1241
- const plannerAgentPath = join(agentDir, "swarm-planner.md");
1242
- const workerAgentPath = join(agentDir, "swarm-worker.md");
1305
+ const swarmAgentDir = join(agentDir, "swarm");
1306
+ const plannerAgentPath = join(swarmAgentDir, "planner.md");
1307
+ const workerAgentPath = join(swarmAgentDir, "worker.md");
1308
+ // Legacy flat paths (for detection/cleanup)
1309
+ const legacyPlannerPath = join(agentDir, "swarm-planner.md");
1310
+ const legacyWorkerPath = join(agentDir, "swarm-worker.md");
1243
1311
 
1244
1312
  const existingFiles = [
1245
1313
  pluginPath,
1246
1314
  commandPath,
1247
1315
  plannerAgentPath,
1248
1316
  workerAgentPath,
1317
+ legacyPlannerPath,
1318
+ legacyWorkerPath,
1249
1319
  ].filter((f) => existsSync(f));
1250
1320
 
1251
1321
  if (existingFiles.length > 0) {
@@ -1302,25 +1372,34 @@ async function setup() {
1302
1372
  process.exit(0);
1303
1373
  }
1304
1374
 
1305
- // Update model lines in agent files
1306
- if (existsSync(plannerAgentPath)) {
1307
- const content = readFileSync(plannerAgentPath, "utf-8");
1375
+ // Update model lines in agent files (check both nested and legacy paths)
1376
+ const plannerPaths = [plannerAgentPath, legacyPlannerPath].filter(existsSync);
1377
+ const workerPaths = [workerAgentPath, legacyWorkerPath].filter(existsSync);
1378
+
1379
+ for (const path of plannerPaths) {
1380
+ const content = readFileSync(path, "utf-8");
1308
1381
  const updated = content.replace(
1309
1382
  /^model: .+$/m,
1310
1383
  `model: ${coordinatorModel}`,
1311
1384
  );
1312
- writeFileSync(plannerAgentPath, updated);
1385
+ writeFileSync(path, updated);
1386
+ }
1387
+ if (plannerPaths.length > 0) {
1313
1388
  p.log.success("Planner: " + coordinatorModel);
1314
1389
  }
1315
- if (existsSync(workerAgentPath)) {
1316
- const content = readFileSync(workerAgentPath, "utf-8");
1390
+
1391
+ for (const path of workerPaths) {
1392
+ const content = readFileSync(path, "utf-8");
1317
1393
  const updated = content.replace(
1318
1394
  /^model: .+$/m,
1319
1395
  `model: ${workerModel}`,
1320
1396
  );
1321
- writeFileSync(workerAgentPath, updated);
1397
+ writeFileSync(path, updated);
1398
+ }
1399
+ if (workerPaths.length > 0) {
1322
1400
  p.log.success("Worker: " + workerModel);
1323
1401
  }
1402
+
1324
1403
  p.outro("Models updated! Your customizations are preserved.");
1325
1404
  return;
1326
1405
  }
@@ -1556,7 +1635,7 @@ async function setup() {
1556
1635
 
1557
1636
  // Create directories if needed
1558
1637
  const skillsDir = join(configDir, "skills");
1559
- for (const dir of [pluginDir, commandDir, agentDir, skillsDir]) {
1638
+ for (const dir of [pluginDir, commandDir, agentDir, swarmAgentDir, skillsDir]) {
1560
1639
  if (!existsSync(dir)) {
1561
1640
  mkdirSync(dir, { recursive: true });
1562
1641
  }
@@ -1568,12 +1647,24 @@ async function setup() {
1568
1647
  writeFileSync(commandPath, SWARM_COMMAND);
1569
1648
  p.log.success("Command: " + commandPath);
1570
1649
 
1650
+ // Write nested agent files (swarm/planner.md, swarm/worker.md)
1651
+ // This is the format used by Task(subagent_type="swarm/worker")
1571
1652
  writeFileSync(plannerAgentPath, getPlannerAgent(coordinatorModel as string));
1572
1653
  p.log.success("Planner agent: " + plannerAgentPath);
1573
1654
 
1574
1655
  writeFileSync(workerAgentPath, getWorkerAgent(workerModel as string));
1575
1656
  p.log.success("Worker agent: " + workerAgentPath);
1576
1657
 
1658
+ // Clean up legacy flat agent files if they exist
1659
+ if (existsSync(legacyPlannerPath)) {
1660
+ rmSync(legacyPlannerPath);
1661
+ p.log.message(dim(" Removed legacy: " + legacyPlannerPath));
1662
+ }
1663
+ if (existsSync(legacyWorkerPath)) {
1664
+ rmSync(legacyWorkerPath);
1665
+ p.log.message(dim(" Removed legacy: " + legacyWorkerPath));
1666
+ }
1667
+
1577
1668
  p.log.success("Skills directory: " + skillsDir);
1578
1669
 
1579
1670
  // Show bundled skills info (and optionally sync to global skills dir)
package/dist/index.d.ts CHANGED
@@ -301,6 +301,7 @@ export declare const allTools: {
301
301
  args: {
302
302
  name: import("zod").ZodString;
303
303
  description: import("zod").ZodOptional<import("zod").ZodString>;
304
+ content: import("zod").ZodOptional<import("zod").ZodString>;
304
305
  body: import("zod").ZodOptional<import("zod").ZodString>;
305
306
  append_body: import("zod").ZodOptional<import("zod").ZodString>;
306
307
  tags: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
@@ -310,6 +311,7 @@ export declare const allTools: {
310
311
  execute(args: {
311
312
  name: string;
312
313
  description?: string | undefined;
314
+ content?: string | undefined;
313
315
  body?: string | undefined;
314
316
  append_body?: string | undefined;
315
317
  tags?: string[] | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -26806,7 +26806,8 @@ Use this to refine skills based on experience:
26806
26806
  args: {
26807
26807
  name: tool.schema.string().describe("Name of the skill to update"),
26808
26808
  description: tool.schema.string().max(1024).optional().describe("New description (replaces existing)"),
26809
- body: tool.schema.string().optional().describe("New body content (replaces existing)"),
26809
+ content: tool.schema.string().optional().describe("New content/body (replaces existing SKILL.md body)"),
26810
+ body: tool.schema.string().optional().describe("Alias for content - new body (replaces existing)"),
26810
26811
  append_body: tool.schema.string().optional().describe("Content to append to existing body"),
26811
26812
  tags: tool.schema.array(tool.schema.string()).optional().describe("New tags (replaces existing)"),
26812
26813
  add_tags: tool.schema.array(tool.schema.string()).optional().describe("Tags to add to existing"),
@@ -26821,8 +26822,9 @@ Use this to refine skills based on experience:
26821
26822
  }
26822
26823
  const newDescription = args.description ?? skill.metadata.description;
26823
26824
  let newBody = skill.body;
26824
- if (args.body) {
26825
- newBody = args.body;
26825
+ const bodyContent = args.content ?? args.body;
26826
+ if (bodyContent) {
26827
+ newBody = bodyContent;
26826
26828
  } else if (args.append_body) {
26827
26829
  newBody = `${skill.body}
26828
26830
 
@@ -26849,7 +26851,7 @@ ${args.append_body}`;
26849
26851
  path: skill.path,
26850
26852
  updated: {
26851
26853
  description: args.description ? true : false,
26852
- body: args.body || args.append_body ? true : false,
26854
+ content: args.content || args.body || args.append_body ? true : false,
26853
26855
  tags: args.tags || args.add_tags ? true : false,
26854
26856
  tools: args.tools ? true : false
26855
26857
  },
@@ -32373,23 +32375,32 @@ Continuing with completion, but this should be fixed for future subtasks.`;
32373
32375
  }, null, 2);
32374
32376
  }
32375
32377
  }
32376
- const closeResult = await Bun.$`bd close ${args.bead_id} --reason ${args.summary} --json`.quiet().nothrow();
32378
+ const closeResult = await Bun.$`bd close ${args.bead_id} --reason ${args.summary} --json`.cwd(args.project_key).quiet().nothrow();
32377
32379
  if (closeResult.exitCode !== 0) {
32378
32380
  const stderrOutput = closeResult.stderr.toString().trim();
32381
+ const stdoutOutput = closeResult.stdout.toString().trim();
32382
+ const isNoDatabaseError = stderrOutput.includes("no beads database found");
32383
+ const isNotFoundError = stderrOutput.includes("not found") || stderrOutput.includes("does not exist");
32379
32384
  return JSON.stringify({
32380
32385
  success: false,
32381
32386
  error: "Failed to close bead",
32382
32387
  failed_step: "bd close",
32383
- details: stderrOutput || "Unknown error from bd close command",
32388
+ details: stderrOutput || stdoutOutput || "Unknown error from bd close command",
32384
32389
  bead_id: args.bead_id,
32390
+ project_key: args.project_key,
32385
32391
  recovery: {
32386
- steps: [
32392
+ steps: isNoDatabaseError ? [
32393
+ `1. Verify project_key is correct: "${args.project_key}"`,
32394
+ `2. Check .beads/ exists in that directory`,
32395
+ `3. Bead ID prefix "${args.bead_id.split("-")[0]}" should match project`,
32396
+ `4. Try: beads_close(id="${args.bead_id}", reason="...")`
32397
+ ] : [
32387
32398
  `1. Check bead exists: bd show ${args.bead_id}`,
32388
32399
  `2. Check bead status (might already be closed): beads_query()`,
32389
32400
  `3. If bead is blocked, unblock first: beads_update(id="${args.bead_id}", status="in_progress")`,
32390
32401
  `4. Try closing directly: beads_close(id="${args.bead_id}", reason="...")`
32391
32402
  ],
32392
- hint: "If bead is in 'blocked' status, you must change it to 'in_progress' or 'open' before closing."
32403
+ hint: isNoDatabaseError ? `The project_key "${args.project_key}" doesn't have a .beads/ directory. Make sure you're using the correct project path.` : isNotFoundError ? `Bead "${args.bead_id}" not found. It may have been closed already or the ID is incorrect.` : "If bead is in 'blocked' status, you must change it to 'in_progress' or 'open' before closing."
32393
32404
  }
32394
32405
  }, null, 2);
32395
32406
  }
@@ -34362,6 +34373,85 @@ function createMetrics(result, toolName) {
34362
34373
  timestamp: Date.now()
34363
34374
  };
34364
34375
  }
34376
+
34377
+ // src/planning-guardrails.ts
34378
+ var FILE_MODIFICATION_PATTERNS = [
34379
+ /\bimplement\b/i,
34380
+ /\bcreate\b.*\.(ts|js|tsx|jsx|py|rs|go|java|rb|swift|kt)/i,
34381
+ /\badd\b.*\.(ts|js|tsx|jsx|py|rs|go|java|rb|swift|kt)/i,
34382
+ /\bupdate\b.*\.(ts|js|tsx|jsx|py|rs|go|java|rb|swift|kt)/i,
34383
+ /\bmodify\b/i,
34384
+ /\brefactor\b/i,
34385
+ /\bextract\b/i,
34386
+ /\bmigrate\b/i,
34387
+ /\bconvert\b/i,
34388
+ /\brewrite\b/i,
34389
+ /\bfix\b.*\.(ts|js|tsx|jsx|py|rs|go|java|rb|swift|kt)/i,
34390
+ /\bwrite\b.*\.(ts|js|tsx|jsx|py|rs|go|java|rb|swift|kt)/i,
34391
+ /src\//i,
34392
+ /lib\//i,
34393
+ /packages?\//i,
34394
+ /components?\//i
34395
+ ];
34396
+ var TRACKING_PATTERNS = [
34397
+ /\breview\b/i,
34398
+ /\bcheck\b/i,
34399
+ /\bverify\b/i,
34400
+ /\btest\b.*pass/i,
34401
+ /\brun\b.*test/i,
34402
+ /\bdeploy\b/i,
34403
+ /\bmerge\b/i,
34404
+ /\bpr\b/i,
34405
+ /\bpush\b/i,
34406
+ /\bcommit\b/i
34407
+ ];
34408
+ function analyzeTodoWrite(args) {
34409
+ const todos = args.todos;
34410
+ if (!todos || !Array.isArray(todos) || todos.length < 6) {
34411
+ return {
34412
+ looksLikeParallelWork: false,
34413
+ fileModificationCount: 0,
34414
+ totalCount: todos?.length ?? 0
34415
+ };
34416
+ }
34417
+ let fileModificationCount = 0;
34418
+ for (const todo of todos) {
34419
+ if (typeof todo !== "object" || todo === null)
34420
+ continue;
34421
+ const content = todo.content ?? "";
34422
+ const isFileModification = FILE_MODIFICATION_PATTERNS.some((pattern) => pattern.test(content));
34423
+ const isTracking = TRACKING_PATTERNS.some((pattern) => pattern.test(content));
34424
+ if (isFileModification && !isTracking) {
34425
+ fileModificationCount++;
34426
+ }
34427
+ }
34428
+ const ratio = fileModificationCount / todos.length;
34429
+ const looksLikeParallelWork = ratio >= 0.5 && fileModificationCount >= 4;
34430
+ if (looksLikeParallelWork) {
34431
+ return {
34432
+ looksLikeParallelWork: true,
34433
+ fileModificationCount,
34434
+ totalCount: todos.length,
34435
+ warning: `⚠️ This looks like a multi-file implementation plan (${fileModificationCount}/${todos.length} items are file modifications).
34436
+
34437
+ Consider using swarm instead:
34438
+ swarm_decompose → beads_create_epic → parallel task spawns
34439
+
34440
+ TodoWrite is for tracking progress, not parallelizable implementation work.
34441
+ Swarm workers can complete these ${fileModificationCount} tasks in parallel.
34442
+
34443
+ (Continuing with todowrite - this is just a suggestion)`
34444
+ };
34445
+ }
34446
+ return {
34447
+ looksLikeParallelWork: false,
34448
+ fileModificationCount,
34449
+ totalCount: todos.length
34450
+ };
34451
+ }
34452
+ function shouldAnalyzeTool(toolName) {
34453
+ return toolName === "todowrite" || toolName === "TodoWrite";
34454
+ }
34365
34455
  // src/storage.ts
34366
34456
  init_learning();
34367
34457
 
@@ -34881,6 +34971,15 @@ var SwarmPlugin = async (input) => {
34881
34971
  await releaseReservations();
34882
34972
  }
34883
34973
  },
34974
+ "tool.execute.before": async (input2, output) => {
34975
+ const toolName = input2.tool;
34976
+ if (shouldAnalyzeTool(toolName)) {
34977
+ const analysis = analyzeTodoWrite(output.args);
34978
+ if (analysis.warning) {
34979
+ console.warn(`[swarm-plugin] ${analysis.warning}`);
34980
+ }
34981
+ }
34982
+ },
34884
34983
  "tool.execute.after": async (input2, output) => {
34885
34984
  const toolName = input2.tool;
34886
34985
  if (output.output && typeof output.output === "string") {
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Planning Guardrails
3
+ *
4
+ * Detects when agents are about to make planning mistakes and warns them.
5
+ * Non-blocking - just emits warnings to help agents self-correct.
6
+ *
7
+ * @module planning-guardrails
8
+ */
9
+ /**
10
+ * Result of analyzing todowrite args
11
+ */
12
+ export interface TodoWriteAnalysis {
13
+ /** Whether this looks like parallel work that should use swarm */
14
+ looksLikeParallelWork: boolean;
15
+ /** Number of todos that look like file modifications */
16
+ fileModificationCount: number;
17
+ /** Total number of todos */
18
+ totalCount: number;
19
+ /** Warning message if applicable */
20
+ warning?: string;
21
+ }
22
+ /**
23
+ * Analyze todowrite args to detect potential planning mistakes
24
+ *
25
+ * Triggers warning when:
26
+ * - 6+ todos created in one call
27
+ * - Most todos match file modification patterns
28
+ * - Few todos match tracking patterns
29
+ *
30
+ * @param args - The todowrite tool arguments
31
+ * @returns Analysis result with optional warning
32
+ */
33
+ export declare function analyzeTodoWrite(args: {
34
+ todos?: unknown[];
35
+ }): TodoWriteAnalysis;
36
+ /**
37
+ * Check if a tool call should trigger planning guardrails
38
+ *
39
+ * @param toolName - Name of the tool being called
40
+ * @returns Whether this tool should be analyzed
41
+ */
42
+ export declare function shouldAnalyzeTool(toolName: string): boolean;
43
+ //# sourceMappingURL=planning-guardrails.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"planning-guardrails.d.ts","sourceRoot":"","sources":["../src/planning-guardrails.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAyCH;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,kEAAkE;IAClE,qBAAqB,EAAE,OAAO,CAAC;IAE/B,wDAAwD;IACxD,qBAAqB,EAAE,MAAM,CAAC;IAE9B,4BAA4B;IAC5B,UAAU,EAAE,MAAM,CAAC;IAEnB,oCAAoC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,EAAE,CAAA;CAAE,GAAG,iBAAiB,CA8D/E;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE3D"}
package/dist/plugin.js CHANGED
@@ -26806,7 +26806,8 @@ Use this to refine skills based on experience:
26806
26806
  args: {
26807
26807
  name: tool.schema.string().describe("Name of the skill to update"),
26808
26808
  description: tool.schema.string().max(1024).optional().describe("New description (replaces existing)"),
26809
- body: tool.schema.string().optional().describe("New body content (replaces existing)"),
26809
+ content: tool.schema.string().optional().describe("New content/body (replaces existing SKILL.md body)"),
26810
+ body: tool.schema.string().optional().describe("Alias for content - new body (replaces existing)"),
26810
26811
  append_body: tool.schema.string().optional().describe("Content to append to existing body"),
26811
26812
  tags: tool.schema.array(tool.schema.string()).optional().describe("New tags (replaces existing)"),
26812
26813
  add_tags: tool.schema.array(tool.schema.string()).optional().describe("Tags to add to existing"),
@@ -26821,8 +26822,9 @@ Use this to refine skills based on experience:
26821
26822
  }
26822
26823
  const newDescription = args.description ?? skill.metadata.description;
26823
26824
  let newBody = skill.body;
26824
- if (args.body) {
26825
- newBody = args.body;
26825
+ const bodyContent = args.content ?? args.body;
26826
+ if (bodyContent) {
26827
+ newBody = bodyContent;
26826
26828
  } else if (args.append_body) {
26827
26829
  newBody = `${skill.body}
26828
26830
 
@@ -26849,7 +26851,7 @@ ${args.append_body}`;
26849
26851
  path: skill.path,
26850
26852
  updated: {
26851
26853
  description: args.description ? true : false,
26852
- body: args.body || args.append_body ? true : false,
26854
+ content: args.content || args.body || args.append_body ? true : false,
26853
26855
  tags: args.tags || args.add_tags ? true : false,
26854
26856
  tools: args.tools ? true : false
26855
26857
  },
@@ -32164,23 +32166,32 @@ Continuing with completion, but this should be fixed for future subtasks.`;
32164
32166
  }, null, 2);
32165
32167
  }
32166
32168
  }
32167
- const closeResult = await Bun.$`bd close ${args.bead_id} --reason ${args.summary} --json`.quiet().nothrow();
32169
+ const closeResult = await Bun.$`bd close ${args.bead_id} --reason ${args.summary} --json`.cwd(args.project_key).quiet().nothrow();
32168
32170
  if (closeResult.exitCode !== 0) {
32169
32171
  const stderrOutput = closeResult.stderr.toString().trim();
32172
+ const stdoutOutput = closeResult.stdout.toString().trim();
32173
+ const isNoDatabaseError = stderrOutput.includes("no beads database found");
32174
+ const isNotFoundError = stderrOutput.includes("not found") || stderrOutput.includes("does not exist");
32170
32175
  return JSON.stringify({
32171
32176
  success: false,
32172
32177
  error: "Failed to close bead",
32173
32178
  failed_step: "bd close",
32174
- details: stderrOutput || "Unknown error from bd close command",
32179
+ details: stderrOutput || stdoutOutput || "Unknown error from bd close command",
32175
32180
  bead_id: args.bead_id,
32181
+ project_key: args.project_key,
32176
32182
  recovery: {
32177
- steps: [
32183
+ steps: isNoDatabaseError ? [
32184
+ `1. Verify project_key is correct: "${args.project_key}"`,
32185
+ `2. Check .beads/ exists in that directory`,
32186
+ `3. Bead ID prefix "${args.bead_id.split("-")[0]}" should match project`,
32187
+ `4. Try: beads_close(id="${args.bead_id}", reason="...")`
32188
+ ] : [
32178
32189
  `1. Check bead exists: bd show ${args.bead_id}`,
32179
32190
  `2. Check bead status (might already be closed): beads_query()`,
32180
32191
  `3. If bead is blocked, unblock first: beads_update(id="${args.bead_id}", status="in_progress")`,
32181
32192
  `4. Try closing directly: beads_close(id="${args.bead_id}", reason="...")`
32182
32193
  ],
32183
- hint: "If bead is in 'blocked' status, you must change it to 'in_progress' or 'open' before closing."
32194
+ hint: isNoDatabaseError ? `The project_key "${args.project_key}" doesn't have a .beads/ directory. Make sure you're using the correct project path.` : isNotFoundError ? `Bead "${args.bead_id}" not found. It may have been closed already or the ID is incorrect.` : "If bead is in 'blocked' status, you must change it to 'in_progress' or 'open' before closing."
32184
32195
  }
32185
32196
  }, null, 2);
32186
32197
  }
@@ -34099,6 +34110,85 @@ function guardrailOutput(toolName, output, config2 = DEFAULT_GUARDRAIL_CONFIG) {
34099
34110
  truncatedLength
34100
34111
  };
34101
34112
  }
34113
+
34114
+ // src/planning-guardrails.ts
34115
+ var FILE_MODIFICATION_PATTERNS = [
34116
+ /\bimplement\b/i,
34117
+ /\bcreate\b.*\.(ts|js|tsx|jsx|py|rs|go|java|rb|swift|kt)/i,
34118
+ /\badd\b.*\.(ts|js|tsx|jsx|py|rs|go|java|rb|swift|kt)/i,
34119
+ /\bupdate\b.*\.(ts|js|tsx|jsx|py|rs|go|java|rb|swift|kt)/i,
34120
+ /\bmodify\b/i,
34121
+ /\brefactor\b/i,
34122
+ /\bextract\b/i,
34123
+ /\bmigrate\b/i,
34124
+ /\bconvert\b/i,
34125
+ /\brewrite\b/i,
34126
+ /\bfix\b.*\.(ts|js|tsx|jsx|py|rs|go|java|rb|swift|kt)/i,
34127
+ /\bwrite\b.*\.(ts|js|tsx|jsx|py|rs|go|java|rb|swift|kt)/i,
34128
+ /src\//i,
34129
+ /lib\//i,
34130
+ /packages?\//i,
34131
+ /components?\//i
34132
+ ];
34133
+ var TRACKING_PATTERNS = [
34134
+ /\breview\b/i,
34135
+ /\bcheck\b/i,
34136
+ /\bverify\b/i,
34137
+ /\btest\b.*pass/i,
34138
+ /\brun\b.*test/i,
34139
+ /\bdeploy\b/i,
34140
+ /\bmerge\b/i,
34141
+ /\bpr\b/i,
34142
+ /\bpush\b/i,
34143
+ /\bcommit\b/i
34144
+ ];
34145
+ function analyzeTodoWrite(args) {
34146
+ const todos = args.todos;
34147
+ if (!todos || !Array.isArray(todos) || todos.length < 6) {
34148
+ return {
34149
+ looksLikeParallelWork: false,
34150
+ fileModificationCount: 0,
34151
+ totalCount: todos?.length ?? 0
34152
+ };
34153
+ }
34154
+ let fileModificationCount = 0;
34155
+ for (const todo of todos) {
34156
+ if (typeof todo !== "object" || todo === null)
34157
+ continue;
34158
+ const content = todo.content ?? "";
34159
+ const isFileModification = FILE_MODIFICATION_PATTERNS.some((pattern) => pattern.test(content));
34160
+ const isTracking = TRACKING_PATTERNS.some((pattern) => pattern.test(content));
34161
+ if (isFileModification && !isTracking) {
34162
+ fileModificationCount++;
34163
+ }
34164
+ }
34165
+ const ratio = fileModificationCount / todos.length;
34166
+ const looksLikeParallelWork = ratio >= 0.5 && fileModificationCount >= 4;
34167
+ if (looksLikeParallelWork) {
34168
+ return {
34169
+ looksLikeParallelWork: true,
34170
+ fileModificationCount,
34171
+ totalCount: todos.length,
34172
+ warning: `⚠️ This looks like a multi-file implementation plan (${fileModificationCount}/${todos.length} items are file modifications).
34173
+
34174
+ Consider using swarm instead:
34175
+ swarm_decompose → beads_create_epic → parallel task spawns
34176
+
34177
+ TodoWrite is for tracking progress, not parallelizable implementation work.
34178
+ Swarm workers can complete these ${fileModificationCount} tasks in parallel.
34179
+
34180
+ (Continuing with todowrite - this is just a suggestion)`
34181
+ };
34182
+ }
34183
+ return {
34184
+ looksLikeParallelWork: false,
34185
+ fileModificationCount,
34186
+ totalCount: todos.length
34187
+ };
34188
+ }
34189
+ function shouldAnalyzeTool(toolName) {
34190
+ return toolName === "todowrite" || toolName === "TodoWrite";
34191
+ }
34102
34192
  // src/storage.ts
34103
34193
  init_learning();
34104
34194
 
@@ -34285,6 +34375,15 @@ var SwarmPlugin = async (input) => {
34285
34375
  await releaseReservations();
34286
34376
  }
34287
34377
  },
34378
+ "tool.execute.before": async (input2, output) => {
34379
+ const toolName = input2.tool;
34380
+ if (shouldAnalyzeTool(toolName)) {
34381
+ const analysis = analyzeTodoWrite(output.args);
34382
+ if (analysis.warning) {
34383
+ console.warn(`[swarm-plugin] ${analysis.warning}`);
34384
+ }
34385
+ }
34386
+ },
34288
34387
  "tool.execute.after": async (input2, output) => {
34289
34388
  const toolName = input2.tool;
34290
34389
  if (output.output && typeof output.output === "string") {
package/dist/skills.d.ts CHANGED
@@ -233,6 +233,7 @@ export declare const skills_update: {
233
233
  args: {
234
234
  name: import("zod").ZodString;
235
235
  description: import("zod").ZodOptional<import("zod").ZodString>;
236
+ content: import("zod").ZodOptional<import("zod").ZodString>;
236
237
  body: import("zod").ZodOptional<import("zod").ZodString>;
237
238
  append_body: import("zod").ZodOptional<import("zod").ZodString>;
238
239
  tags: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
@@ -242,6 +243,7 @@ export declare const skills_update: {
242
243
  execute(args: {
243
244
  name: string;
244
245
  description?: string | undefined;
246
+ content?: string | undefined;
245
247
  body?: string | undefined;
246
248
  append_body?: string | undefined;
247
249
  tags?: string[] | undefined;
@@ -389,6 +391,7 @@ export declare const skillsTools: {
389
391
  args: {
390
392
  name: import("zod").ZodString;
391
393
  description: import("zod").ZodOptional<import("zod").ZodString>;
394
+ content: import("zod").ZodOptional<import("zod").ZodString>;
392
395
  body: import("zod").ZodOptional<import("zod").ZodString>;
393
396
  append_body: import("zod").ZodOptional<import("zod").ZodString>;
394
397
  tags: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
@@ -398,6 +401,7 @@ export declare const skillsTools: {
398
401
  execute(args: {
399
402
  name: string;
400
403
  description?: string | undefined;
404
+ content?: string | undefined;
401
405
  body?: string | undefined;
402
406
  append_body?: string | undefined;
403
407
  tags?: string[] | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../src/skills.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../src/skills.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"swarm-orchestrate.d.ts","sourceRoot":"","sources":["../src/swarm-orchestrate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAgiBxB;;;;;;;;;;GAUG;AACH,eAAO,MAAM,UAAU;;;;;;;;CAuGrB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;CAoFvB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;CA8GzB,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;CA6E1B,CAAC;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiiBzB,CAAC;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkJ/B,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;CA6CjC,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;CAmClC,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;CAmB9B,CAAC;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;CAoJ9B,CAAC;AA4BH;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqG3B,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;CAsGxB,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgMtB,CAAC;AAMH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAc5B,CAAC"}
1
+ {"version":3,"file":"swarm-orchestrate.d.ts","sourceRoot":"","sources":["../src/swarm-orchestrate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAgiBxB;;;;;;;;;;GAUG;AACH,eAAO,MAAM,UAAU;;;;;;;;CAuGrB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;CAoFvB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;CA8GzB,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;CA6E1B,CAAC;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqjBzB,CAAC;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkJ/B,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;CA6CjC,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;CAmClC,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;CAmB9B,CAAC;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;CAoJ9B,CAAC;AA4BH;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqG3B,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;CAsGxB,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgMtB,CAAC;AAMH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAc5B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm-plugin",
3
- "version": "0.24.0",
3
+ "version": "0.25.1",
4
4
  "description": "Multi-agent swarm coordination for OpenCode with learning capabilities, beads integration, and Agent Mail",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/index.ts CHANGED
@@ -44,6 +44,10 @@ import {
44
44
  DEFAULT_GUARDRAIL_CONFIG,
45
45
  type GuardrailResult,
46
46
  } from "./output-guardrails";
47
+ import {
48
+ analyzeTodoWrite,
49
+ shouldAnalyzeTool,
50
+ } from "./planning-guardrails";
47
51
 
48
52
  /**
49
53
  * OpenCode Swarm Plugin
@@ -164,6 +168,24 @@ export const SwarmPlugin: Plugin = async (
164
168
  }
165
169
  },
166
170
 
171
+ /**
172
+ * Hook before tool execution for planning guardrails
173
+ *
174
+ * Warns when agents are about to make planning mistakes:
175
+ * - Using todowrite for multi-file implementation (should use swarm)
176
+ */
177
+ "tool.execute.before": async (input, output) => {
178
+ const toolName = input.tool;
179
+
180
+ // Check for planning anti-patterns
181
+ if (shouldAnalyzeTool(toolName)) {
182
+ const analysis = analyzeTodoWrite(output.args);
183
+ if (analysis.warning) {
184
+ console.warn(`[swarm-plugin] ${analysis.warning}`);
185
+ }
186
+ }
187
+ },
188
+
167
189
  /**
168
190
  * Hook after tool execution for automatic cleanup and guardrails
169
191
  *
@@ -0,0 +1,106 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { analyzeTodoWrite, shouldAnalyzeTool } from "./planning-guardrails";
3
+
4
+ describe("planning-guardrails", () => {
5
+ describe("shouldAnalyzeTool", () => {
6
+ it("returns true for todowrite", () => {
7
+ expect(shouldAnalyzeTool("todowrite")).toBe(true);
8
+ expect(shouldAnalyzeTool("TodoWrite")).toBe(true);
9
+ });
10
+
11
+ it("returns false for other tools", () => {
12
+ expect(shouldAnalyzeTool("beads_create")).toBe(false);
13
+ expect(shouldAnalyzeTool("swarm_decompose")).toBe(false);
14
+ expect(shouldAnalyzeTool("read")).toBe(false);
15
+ });
16
+ });
17
+
18
+ describe("analyzeTodoWrite", () => {
19
+ it("returns no warning for small todo lists", () => {
20
+ const result = analyzeTodoWrite({
21
+ todos: [
22
+ { content: "Implement feature A", status: "pending" },
23
+ { content: "Add tests", status: "pending" },
24
+ ],
25
+ });
26
+
27
+ expect(result.looksLikeParallelWork).toBe(false);
28
+ expect(result.warning).toBeUndefined();
29
+ expect(result.totalCount).toBe(2);
30
+ });
31
+
32
+ it("warns for 6+ file modification todos", () => {
33
+ const result = analyzeTodoWrite({
34
+ todos: [
35
+ { content: "Implement src/auth/login.ts", status: "pending" },
36
+ { content: "Create src/auth/logout.ts", status: "pending" },
37
+ { content: "Add src/auth/types.ts", status: "pending" },
38
+ { content: "Update src/auth/index.ts", status: "pending" },
39
+ { content: "Refactor src/lib/session.ts", status: "pending" },
40
+ { content: "Modify src/middleware/auth.ts", status: "pending" },
41
+ ],
42
+ });
43
+
44
+ expect(result.looksLikeParallelWork).toBe(true);
45
+ expect(result.warning).toBeDefined();
46
+ expect(result.warning).toContain("multi-file implementation plan");
47
+ expect(result.warning).toContain("swarm");
48
+ expect(result.fileModificationCount).toBeGreaterThanOrEqual(4);
49
+ });
50
+
51
+ it("does not warn for tracking/coordination todos", () => {
52
+ const result = analyzeTodoWrite({
53
+ todos: [
54
+ { content: "Review PR #123", status: "pending" },
55
+ { content: "Check tests pass", status: "pending" },
56
+ { content: "Verify deployment", status: "pending" },
57
+ { content: "Run integration tests", status: "pending" },
58
+ { content: "Merge to main", status: "pending" },
59
+ { content: "Push to production", status: "pending" },
60
+ ],
61
+ });
62
+
63
+ expect(result.looksLikeParallelWork).toBe(false);
64
+ expect(result.warning).toBeUndefined();
65
+ });
66
+
67
+ it("does not warn for mixed todos with few file modifications", () => {
68
+ const result = analyzeTodoWrite({
69
+ todos: [
70
+ { content: "Implement src/feature.ts", status: "pending" },
71
+ { content: "Review changes", status: "pending" },
72
+ { content: "Run tests", status: "pending" },
73
+ { content: "Check linting", status: "pending" },
74
+ { content: "Deploy to staging", status: "pending" },
75
+ { content: "Verify in browser", status: "pending" },
76
+ ],
77
+ });
78
+
79
+ // Only 1 file modification out of 6 - should not trigger
80
+ expect(result.looksLikeParallelWork).toBe(false);
81
+ expect(result.warning).toBeUndefined();
82
+ });
83
+
84
+ it("handles empty or missing todos", () => {
85
+ expect(analyzeTodoWrite({}).looksLikeParallelWork).toBe(false);
86
+ expect(analyzeTodoWrite({ todos: [] }).looksLikeParallelWork).toBe(false);
87
+ expect(analyzeTodoWrite({ todos: undefined as any }).looksLikeParallelWork).toBe(false);
88
+ });
89
+
90
+ it("handles malformed todo items", () => {
91
+ const result = analyzeTodoWrite({
92
+ todos: [
93
+ null,
94
+ undefined,
95
+ "string instead of object",
96
+ { noContent: true },
97
+ { content: "Implement src/valid.ts", status: "pending" },
98
+ { content: "Create src/another.ts", status: "pending" },
99
+ ] as any,
100
+ });
101
+
102
+ // Should handle gracefully without crashing
103
+ expect(result.totalCount).toBe(6);
104
+ });
105
+ });
106
+ });
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Planning Guardrails
3
+ *
4
+ * Detects when agents are about to make planning mistakes and warns them.
5
+ * Non-blocking - just emits warnings to help agents self-correct.
6
+ *
7
+ * @module planning-guardrails
8
+ */
9
+
10
+ /**
11
+ * Patterns that suggest file modification work
12
+ * These indicate the todo is about implementation, not tracking
13
+ */
14
+ const FILE_MODIFICATION_PATTERNS = [
15
+ /\bimplement\b/i,
16
+ /\bcreate\b.*\.(ts|js|tsx|jsx|py|rs|go|java|rb|swift|kt)/i,
17
+ /\badd\b.*\.(ts|js|tsx|jsx|py|rs|go|java|rb|swift|kt)/i,
18
+ /\bupdate\b.*\.(ts|js|tsx|jsx|py|rs|go|java|rb|swift|kt)/i,
19
+ /\bmodify\b/i,
20
+ /\brefactor\b/i,
21
+ /\bextract\b/i,
22
+ /\bmigrate\b/i,
23
+ /\bconvert\b/i,
24
+ /\brewrite\b/i,
25
+ /\bfix\b.*\.(ts|js|tsx|jsx|py|rs|go|java|rb|swift|kt)/i,
26
+ /\bwrite\b.*\.(ts|js|tsx|jsx|py|rs|go|java|rb|swift|kt)/i,
27
+ /src\//i,
28
+ /lib\//i,
29
+ /packages?\//i,
30
+ /components?\//i,
31
+ ];
32
+
33
+ /**
34
+ * Patterns that suggest this is tracking/coordination work (OK for todowrite)
35
+ */
36
+ const TRACKING_PATTERNS = [
37
+ /\breview\b/i,
38
+ /\bcheck\b/i,
39
+ /\bverify\b/i,
40
+ /\btest\b.*pass/i,
41
+ /\brun\b.*test/i,
42
+ /\bdeploy\b/i,
43
+ /\bmerge\b/i,
44
+ /\bpr\b/i,
45
+ /\bpush\b/i,
46
+ /\bcommit\b/i,
47
+ ];
48
+
49
+ /**
50
+ * Result of analyzing todowrite args
51
+ */
52
+ export interface TodoWriteAnalysis {
53
+ /** Whether this looks like parallel work that should use swarm */
54
+ looksLikeParallelWork: boolean;
55
+
56
+ /** Number of todos that look like file modifications */
57
+ fileModificationCount: number;
58
+
59
+ /** Total number of todos */
60
+ totalCount: number;
61
+
62
+ /** Warning message if applicable */
63
+ warning?: string;
64
+ }
65
+
66
+ /**
67
+ * Analyze todowrite args to detect potential planning mistakes
68
+ *
69
+ * Triggers warning when:
70
+ * - 6+ todos created in one call
71
+ * - Most todos match file modification patterns
72
+ * - Few todos match tracking patterns
73
+ *
74
+ * @param args - The todowrite tool arguments
75
+ * @returns Analysis result with optional warning
76
+ */
77
+ export function analyzeTodoWrite(args: { todos?: unknown[] }): TodoWriteAnalysis {
78
+ const todos = args.todos;
79
+
80
+ // Not enough todos to analyze
81
+ if (!todos || !Array.isArray(todos) || todos.length < 6) {
82
+ return {
83
+ looksLikeParallelWork: false,
84
+ fileModificationCount: 0,
85
+ totalCount: todos?.length ?? 0,
86
+ };
87
+ }
88
+
89
+ // Count todos that look like file modifications
90
+ let fileModificationCount = 0;
91
+
92
+ for (const todo of todos) {
93
+ if (typeof todo !== "object" || todo === null) continue;
94
+
95
+ const content = (todo as { content?: string }).content ?? "";
96
+
97
+ // Check if it matches file modification patterns
98
+ const isFileModification = FILE_MODIFICATION_PATTERNS.some((pattern) =>
99
+ pattern.test(content)
100
+ );
101
+
102
+ // Check if it matches tracking patterns
103
+ const isTracking = TRACKING_PATTERNS.some((pattern) =>
104
+ pattern.test(content)
105
+ );
106
+
107
+ if (isFileModification && !isTracking) {
108
+ fileModificationCount++;
109
+ }
110
+ // trackingCount not currently used but kept for future ratio analysis
111
+ }
112
+
113
+ // Trigger warning if most todos look like file modifications
114
+ const ratio = fileModificationCount / todos.length;
115
+ const looksLikeParallelWork = ratio >= 0.5 && fileModificationCount >= 4;
116
+
117
+ if (looksLikeParallelWork) {
118
+ return {
119
+ looksLikeParallelWork: true,
120
+ fileModificationCount,
121
+ totalCount: todos.length,
122
+ warning: `⚠️ This looks like a multi-file implementation plan (${fileModificationCount}/${todos.length} items are file modifications).
123
+
124
+ Consider using swarm instead:
125
+ swarm_decompose → beads_create_epic → parallel task spawns
126
+
127
+ TodoWrite is for tracking progress, not parallelizable implementation work.
128
+ Swarm workers can complete these ${fileModificationCount} tasks in parallel.
129
+
130
+ (Continuing with todowrite - this is just a suggestion)`,
131
+ };
132
+ }
133
+
134
+ return {
135
+ looksLikeParallelWork: false,
136
+ fileModificationCount,
137
+ totalCount: todos.length,
138
+ };
139
+ }
140
+
141
+ /**
142
+ * Check if a tool call should trigger planning guardrails
143
+ *
144
+ * @param toolName - Name of the tool being called
145
+ * @returns Whether this tool should be analyzed
146
+ */
147
+ export function shouldAnalyzeTool(toolName: string): boolean {
148
+ return toolName === "todowrite" || toolName === "TodoWrite";
149
+ }
package/src/skills.ts CHANGED
@@ -995,10 +995,14 @@ Use this to refine skills based on experience:
995
995
  .max(1024)
996
996
  .optional()
997
997
  .describe("New description (replaces existing)"),
998
+ content: tool.schema
999
+ .string()
1000
+ .optional()
1001
+ .describe("New content/body (replaces existing SKILL.md body)"),
998
1002
  body: tool.schema
999
1003
  .string()
1000
1004
  .optional()
1001
- .describe("New body content (replaces existing)"),
1005
+ .describe("Alias for content - new body (replaces existing)"),
1002
1006
  append_body: tool.schema
1003
1007
  .string()
1004
1008
  .optional()
@@ -1027,10 +1031,11 @@ Use this to refine skills based on experience:
1027
1031
  // Build updated metadata
1028
1032
  const newDescription = args.description ?? skill.metadata.description;
1029
1033
 
1030
- // Handle body updates
1034
+ // Handle body updates (content is preferred, body is alias for backwards compat)
1031
1035
  let newBody = skill.body;
1032
- if (args.body) {
1033
- newBody = args.body;
1036
+ const bodyContent = args.content ?? args.body;
1037
+ if (bodyContent) {
1038
+ newBody = bodyContent;
1034
1039
  } else if (args.append_body) {
1035
1040
  newBody = `${skill.body}\n\n${args.append_body}`;
1036
1041
  }
@@ -1067,7 +1072,7 @@ Use this to refine skills based on experience:
1067
1072
  path: skill.path,
1068
1073
  updated: {
1069
1074
  description: args.description ? true : false,
1070
- body: args.body || args.append_body ? true : false,
1075
+ content: args.content || args.body || args.append_body ? true : false,
1071
1076
  tags: args.tags || args.add_tags ? true : false,
1072
1077
  tools: args.tools ? true : false,
1073
1078
  },
@@ -1189,29 +1189,49 @@ Continuing with completion, but this should be fixed for future subtasks.`;
1189
1189
  }
1190
1190
  }
1191
1191
 
1192
- // Close the bead
1192
+ // Close the bead - use project_key as working directory to find correct .beads/
1193
+ // This fixes the issue where bead ID prefix (e.g., "pdf-library-g84.2") doesn't match CWD
1193
1194
  const closeResult =
1194
1195
  await Bun.$`bd close ${args.bead_id} --reason ${args.summary} --json`
1196
+ .cwd(args.project_key)
1195
1197
  .quiet()
1196
1198
  .nothrow();
1197
1199
 
1198
1200
  if (closeResult.exitCode !== 0) {
1199
1201
  const stderrOutput = closeResult.stderr.toString().trim();
1202
+ const stdoutOutput = closeResult.stdout.toString().trim();
1203
+
1204
+ // Check for common error patterns and provide better guidance
1205
+ const isNoDatabaseError = stderrOutput.includes("no beads database found");
1206
+ const isNotFoundError = stderrOutput.includes("not found") || stderrOutput.includes("does not exist");
1207
+
1200
1208
  return JSON.stringify(
1201
1209
  {
1202
1210
  success: false,
1203
1211
  error: "Failed to close bead",
1204
1212
  failed_step: "bd close",
1205
- details: stderrOutput || "Unknown error from bd close command",
1213
+ details: stderrOutput || stdoutOutput || "Unknown error from bd close command",
1206
1214
  bead_id: args.bead_id,
1215
+ project_key: args.project_key,
1207
1216
  recovery: {
1208
- steps: [
1209
- `1. Check bead exists: bd show ${args.bead_id}`,
1210
- `2. Check bead status (might already be closed): beads_query()`,
1211
- `3. If bead is blocked, unblock first: beads_update(id="${args.bead_id}", status="in_progress")`,
1212
- `4. Try closing directly: beads_close(id="${args.bead_id}", reason="...")`,
1213
- ],
1214
- hint: "If bead is in 'blocked' status, you must change it to 'in_progress' or 'open' before closing.",
1217
+ steps: isNoDatabaseError
1218
+ ? [
1219
+ `1. Verify project_key is correct: "${args.project_key}"`,
1220
+ `2. Check .beads/ exists in that directory`,
1221
+ `3. Bead ID prefix "${args.bead_id.split("-")[0]}" should match project`,
1222
+ `4. Try: beads_close(id="${args.bead_id}", reason="...")`,
1223
+ ]
1224
+ : [
1225
+ `1. Check bead exists: bd show ${args.bead_id}`,
1226
+ `2. Check bead status (might already be closed): beads_query()`,
1227
+ `3. If bead is blocked, unblock first: beads_update(id="${args.bead_id}", status="in_progress")`,
1228
+ `4. Try closing directly: beads_close(id="${args.bead_id}", reason="...")`,
1229
+ ],
1230
+ hint: isNoDatabaseError
1231
+ ? `The project_key "${args.project_key}" doesn't have a .beads/ directory. Make sure you're using the correct project path.`
1232
+ : isNotFoundError
1233
+ ? `Bead "${args.bead_id}" not found. It may have been closed already or the ID is incorrect.`
1234
+ : "If bead is in 'blocked' status, you must change it to 'in_progress' or 'open' before closing.",
1215
1235
  },
1216
1236
  },
1217
1237
  null,