opencode-swarm-plugin 0.25.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.
- package/.turbo/turbo-build.log +3 -3
- package/CHANGELOG.md +17 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +107 -8
- package/dist/planning-guardrails.d.ts +43 -0
- package/dist/planning-guardrails.d.ts.map +1 -0
- package/dist/plugin.js +107 -8
- package/dist/skills.d.ts +4 -0
- package/dist/skills.d.ts.map +1 -1
- package/dist/swarm-orchestrate.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +22 -0
- package/src/planning-guardrails.test.ts +106 -0
- package/src/planning-guardrails.ts +149 -0
- package/src/skills.ts +10 -5
- package/src/swarm-orchestrate.ts +29 -9
package/.turbo/turbo-build.log
CHANGED
|
@@ -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
|
|
2
|
+
Bundled 197 modules in 32ms
|
|
3
3
|
|
|
4
4
|
index.js 1.16 MB (entry point)
|
|
5
5
|
|
|
6
|
-
Bundled
|
|
6
|
+
Bundled 198 modules in 31ms
|
|
7
7
|
|
|
8
|
-
plugin.js 1.
|
|
8
|
+
plugin.js 1.14 MB (entry point)
|
|
9
9
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
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
|
+
|
|
3
20
|
## 0.25.0
|
|
4
21
|
|
|
5
22
|
### Minor Changes
|
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;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAAE,MAAM,EAAsB,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAAE,MAAM,EAAsB,MAAM,qBAAqB,CAAC;AA6BtE;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,WAAW,EAAE,MAkMzB,CAAC;AAEF;;;;;;;GAOG;AACH,eAAe,WAAW,CAAC;AAM3B;;GAEG;AACH,cAAc,WAAW,CAAC;AAE1B;;;;;;;GAOG;AACH,cAAc,SAAS,CAAC;AAExB;;;;;;;;;;;;GAYG;AACH,OAAO,EACL,cAAc,EACd,cAAc,EACd,4BAA4B,EAC5B,4BAA4B,EAC5B,oBAAoB,EACpB,4BAA4B,EAC5B,4BAA4B,EAC5B,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,KAAK,cAAc,GACpB,MAAM,cAAc,CAAC;AAEtB;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EACL,cAAc,EACd,4BAA4B,EAC5B,4BAA4B,EAC5B,iBAAiB,EACjB,KAAK,cAAc,GACpB,MAAM,cAAc,CAAC;AAEtB;;;;;GAKG;AACH,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD;;;;;;GAMG;AACH,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,eAAe,EACf,eAAe,GAChB,MAAM,cAAc,CAAC;AAEtB;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EACL,UAAU,EACV,UAAU,EACV,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,EACrB,sBAAsB,EACtB,iBAAiB,EAEjB,UAAU,EACV,cAAc,EACd,wBAAwB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,GACxB,MAAM,SAAS,CAAC;AAMjB;;;;;GAKG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAQX,CAAC;AAEX;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,OAAO,QAAQ,CAAC;AAEhD;;;;;;;;;;;;;GAaG;AACH,OAAO,EACL,aAAa,EACb,yBAAyB,EACzB,UAAU,EACV,UAAU,EACV,YAAY,EACZ,eAAe,EACf,qBAAqB,EACrB,yBAAyB,EACzB,sBAAsB,EACtB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,kBAAkB,GACxB,MAAM,WAAW,CAAC;AAEnB;;;;;;;;;;;;;GAaG;AACH,OAAO,EACL,SAAS,EACT,eAAe,EACf,aAAa,EACb,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,WAAW,EACX,sBAAsB,EACtB,cAAc,EACd,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,gBAAgB,GACtB,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9D;;;;;;;;;;;;;;GAcG;AACH,OAAO,EACL,WAAW,EACX,cAAc,EACd,QAAQ,EACR,UAAU,EACV,gBAAgB,EAChB,yBAAyB,EACzB,qBAAqB,EACrB,wBAAwB,EACxB,kBAAkB,EAClB,KAAK,KAAK,EACV,KAAK,aAAa,EAClB,KAAK,QAAQ,GACd,MAAM,UAAU,CAAC;AAElB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAExD;;;;;;;;;;;;GAYG;AACH,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,wBAAwB,EACxB,sBAAsB,EACtB,4BAA4B,EAC5B,8BAA8B,EAC9B,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,yBAAyB,GAC/B,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;;;;GAWG;AACH,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,qBAAqB,EACrB,uBAAuB,EACvB,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,eAAe,GACrB,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;GAaG;AACH,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,aAAa,EACb,wBAAwB,EACxB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACtB,MAAM,qBAAqB,CAAC"}
|
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
|
-
|
|
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
|
-
|
|
26825
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26825
|
-
|
|
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
|
-
|
|
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;
|
package/dist/skills.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../src/skills.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAoBH;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,WAAW,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,KAAK;IACpB,kCAAkC;IAClC,QAAQ,EAAE,aAAa,CAAC;IACxB,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,UAAU,EAAE,OAAO,CAAC;IACpB,kDAAkD;IAClD,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,OAAO,CAAC;CACrB;AAYD;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAG3D;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG;IACjD,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;CACd,CAQA;AAmKD;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAClC,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAuD7B;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAGlE;AAED;;GAEG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC,CAQtD;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAMD;;;;;GAKG;AACH,eAAO,MAAM,WAAW;;;;;;;;CAyCtB,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;CAoCrB,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;CAwEzB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;CAsDtB,CAAC;AAeH;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,oEAAoE;IACpE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,kCAAkC;IAClC,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAClB,qBAAqB,CA2FvB;AAwGD;;;;;GAKG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;CA6GxB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,aAAa
|
|
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
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
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("
|
|
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
|
-
|
|
1033
|
-
|
|
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
|
-
|
|
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
|
},
|
package/src/swarm-orchestrate.ts
CHANGED
|
@@ -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
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
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,
|