opencode-swarm-plugin 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.beads/.local_version +1 -0
- package/.beads/README.md +81 -0
- package/.beads/config.yaml +62 -0
- package/.beads/issues.jsonl +549 -0
- package/.beads/metadata.json +4 -0
- package/.gitattributes +3 -0
- package/Dockerfile +30 -0
- package/README.md +312 -0
- package/bun.lock +212 -0
- package/dist/index.js +14627 -0
- package/dist/plugin.js +14562 -0
- package/docker/agent-mail/Dockerfile +23 -0
- package/docker/agent-mail/__pycache__/server.cpython-314.pyc +0 -0
- package/docker/agent-mail/requirements.txt +3 -0
- package/docker/agent-mail/server.py +879 -0
- package/docker-compose.yml +45 -0
- package/package.json +52 -0
- package/scripts/docker-entrypoint.sh +54 -0
- package/src/agent-mail.integration.test.ts +1321 -0
- package/src/agent-mail.ts +665 -0
- package/src/anti-patterns.ts +430 -0
- package/src/beads.integration.test.ts +688 -0
- package/src/beads.ts +603 -0
- package/src/index.ts +267 -0
- package/src/learning.integration.test.ts +1104 -0
- package/src/learning.ts +438 -0
- package/src/pattern-maturity.ts +487 -0
- package/src/plugin.ts +11 -0
- package/src/schemas/bead.ts +152 -0
- package/src/schemas/evaluation.ts +133 -0
- package/src/schemas/index.test.ts +199 -0
- package/src/schemas/index.ts +77 -0
- package/src/schemas/task.ts +129 -0
- package/src/structured.ts +708 -0
- package/src/swarm.integration.test.ts +763 -0
- package/src/swarm.ts +1411 -0
- package/tsconfig.json +28 -0
- package/vitest.integration.config.ts +13 -0
package/src/beads.ts
ADDED
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Beads Module - Type-safe wrappers around the `bd` CLI
|
|
3
|
+
*
|
|
4
|
+
* This module provides validated, type-safe operations for the beads
|
|
5
|
+
* issue tracker. All responses are parsed and validated with Zod schemas.
|
|
6
|
+
*
|
|
7
|
+
* Key principles:
|
|
8
|
+
* - Always use `--json` flag for bd commands
|
|
9
|
+
* - Validate all output with Zod schemas
|
|
10
|
+
* - Throw typed errors on failure
|
|
11
|
+
* - Support atomic epic creation with rollback hints
|
|
12
|
+
*/
|
|
13
|
+
import { tool } from "@opencode-ai/plugin";
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
import {
|
|
16
|
+
BeadSchema,
|
|
17
|
+
BeadCreateArgsSchema,
|
|
18
|
+
BeadUpdateArgsSchema,
|
|
19
|
+
BeadCloseArgsSchema,
|
|
20
|
+
BeadQueryArgsSchema,
|
|
21
|
+
EpicCreateArgsSchema,
|
|
22
|
+
EpicCreateResultSchema,
|
|
23
|
+
type Bead,
|
|
24
|
+
type BeadCreateArgs,
|
|
25
|
+
type EpicCreateResult,
|
|
26
|
+
} from "./schemas";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Custom error for bead operations
|
|
30
|
+
*/
|
|
31
|
+
export class BeadError extends Error {
|
|
32
|
+
constructor(
|
|
33
|
+
message: string,
|
|
34
|
+
public readonly command: string,
|
|
35
|
+
public readonly exitCode?: number,
|
|
36
|
+
public readonly stderr?: string,
|
|
37
|
+
) {
|
|
38
|
+
super(message);
|
|
39
|
+
this.name = "BeadError";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Custom error for validation failures
|
|
45
|
+
*/
|
|
46
|
+
export class BeadValidationError extends Error {
|
|
47
|
+
constructor(
|
|
48
|
+
message: string,
|
|
49
|
+
public readonly zodError: z.ZodError,
|
|
50
|
+
) {
|
|
51
|
+
super(message);
|
|
52
|
+
this.name = "BeadValidationError";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Build a bd create command from args
|
|
58
|
+
*/
|
|
59
|
+
function buildCreateCommand(args: BeadCreateArgs): string[] {
|
|
60
|
+
const parts = ["bd", "create", args.title];
|
|
61
|
+
|
|
62
|
+
if (args.type && args.type !== "task") {
|
|
63
|
+
parts.push("-t", args.type);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (args.priority !== undefined && args.priority !== 2) {
|
|
67
|
+
parts.push("-p", args.priority.toString());
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (args.description) {
|
|
71
|
+
parts.push("-d", args.description);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (args.parent_id) {
|
|
75
|
+
parts.push("--parent", args.parent_id);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
parts.push("--json");
|
|
79
|
+
return parts;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Parse and validate bead JSON output
|
|
84
|
+
* Handles both object and array responses (CLI may return either)
|
|
85
|
+
*/
|
|
86
|
+
function parseBead(output: string): Bead {
|
|
87
|
+
try {
|
|
88
|
+
const parsed = JSON.parse(output);
|
|
89
|
+
// CLI commands like `bd close`, `bd update` return arrays even for single items
|
|
90
|
+
const data = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
91
|
+
if (!data) {
|
|
92
|
+
throw new BeadError("No bead data in response", "parse");
|
|
93
|
+
}
|
|
94
|
+
return BeadSchema.parse(data);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (error instanceof z.ZodError) {
|
|
97
|
+
throw new BeadValidationError(
|
|
98
|
+
`Invalid bead data: ${error.message}`,
|
|
99
|
+
error,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
if (error instanceof BeadError) {
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
throw new BeadError(`Failed to parse bead JSON: ${output}`, "parse");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Parse and validate array of beads
|
|
111
|
+
*/
|
|
112
|
+
function parseBeads(output: string): Bead[] {
|
|
113
|
+
try {
|
|
114
|
+
const parsed = JSON.parse(output);
|
|
115
|
+
return z.array(BeadSchema).parse(parsed);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (error instanceof z.ZodError) {
|
|
118
|
+
throw new BeadValidationError(
|
|
119
|
+
`Invalid beads data: ${error.message}`,
|
|
120
|
+
error,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
throw new BeadError(`Failed to parse beads JSON: ${output}`, "parse");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ============================================================================
|
|
128
|
+
// Tool Definitions
|
|
129
|
+
// ============================================================================
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Create a new bead with type-safe validation
|
|
133
|
+
*/
|
|
134
|
+
export const beads_create = tool({
|
|
135
|
+
description: "Create a new bead with type-safe validation",
|
|
136
|
+
args: {
|
|
137
|
+
title: tool.schema.string().describe("Bead title"),
|
|
138
|
+
type: tool.schema
|
|
139
|
+
.enum(["bug", "feature", "task", "epic", "chore"])
|
|
140
|
+
.optional()
|
|
141
|
+
.describe("Issue type (default: task)"),
|
|
142
|
+
priority: tool.schema
|
|
143
|
+
.number()
|
|
144
|
+
.min(0)
|
|
145
|
+
.max(3)
|
|
146
|
+
.optional()
|
|
147
|
+
.describe("Priority 0-3 (default: 2)"),
|
|
148
|
+
description: tool.schema.string().optional().describe("Bead description"),
|
|
149
|
+
parent_id: tool.schema
|
|
150
|
+
.string()
|
|
151
|
+
.optional()
|
|
152
|
+
.describe("Parent bead ID for epic children"),
|
|
153
|
+
},
|
|
154
|
+
async execute(args, ctx) {
|
|
155
|
+
const validated = BeadCreateArgsSchema.parse(args);
|
|
156
|
+
const cmdParts = buildCreateCommand(validated);
|
|
157
|
+
|
|
158
|
+
// Execute command
|
|
159
|
+
const result = await Bun.$`${cmdParts}`.quiet().nothrow();
|
|
160
|
+
|
|
161
|
+
if (result.exitCode !== 0) {
|
|
162
|
+
throw new BeadError(
|
|
163
|
+
`Failed to create bead: ${result.stderr.toString()}`,
|
|
164
|
+
cmdParts.join(" "),
|
|
165
|
+
result.exitCode,
|
|
166
|
+
result.stderr.toString(),
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const bead = parseBead(result.stdout.toString());
|
|
171
|
+
return JSON.stringify(bead, null, 2);
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Create an epic with subtasks in one atomic operation
|
|
177
|
+
*/
|
|
178
|
+
export const beads_create_epic = tool({
|
|
179
|
+
description: "Create epic with subtasks in one atomic operation",
|
|
180
|
+
args: {
|
|
181
|
+
epic_title: tool.schema.string().describe("Epic title"),
|
|
182
|
+
epic_description: tool.schema
|
|
183
|
+
.string()
|
|
184
|
+
.optional()
|
|
185
|
+
.describe("Epic description"),
|
|
186
|
+
subtasks: tool.schema
|
|
187
|
+
.array(
|
|
188
|
+
tool.schema.object({
|
|
189
|
+
title: tool.schema.string(),
|
|
190
|
+
priority: tool.schema.number().min(0).max(3).optional(),
|
|
191
|
+
files: tool.schema.array(tool.schema.string()).optional(),
|
|
192
|
+
}),
|
|
193
|
+
)
|
|
194
|
+
.describe("Subtasks to create under the epic"),
|
|
195
|
+
},
|
|
196
|
+
async execute(args, ctx) {
|
|
197
|
+
const validated = EpicCreateArgsSchema.parse(args);
|
|
198
|
+
const created: Bead[] = [];
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
// 1. Create epic
|
|
202
|
+
const epicCmd = buildCreateCommand({
|
|
203
|
+
title: validated.epic_title,
|
|
204
|
+
type: "epic",
|
|
205
|
+
priority: 1,
|
|
206
|
+
description: validated.epic_description,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const epicResult = await Bun.$`${epicCmd}`.quiet().nothrow();
|
|
210
|
+
|
|
211
|
+
if (epicResult.exitCode !== 0) {
|
|
212
|
+
throw new BeadError(
|
|
213
|
+
`Failed to create epic: ${epicResult.stderr.toString()}`,
|
|
214
|
+
epicCmd.join(" "),
|
|
215
|
+
epicResult.exitCode,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const epic = parseBead(epicResult.stdout.toString());
|
|
220
|
+
created.push(epic);
|
|
221
|
+
|
|
222
|
+
// 2. Create subtasks
|
|
223
|
+
for (const subtask of validated.subtasks) {
|
|
224
|
+
const subtaskCmd = buildCreateCommand({
|
|
225
|
+
title: subtask.title,
|
|
226
|
+
type: "task",
|
|
227
|
+
priority: subtask.priority ?? 2,
|
|
228
|
+
parent_id: epic.id,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const subtaskResult = await Bun.$`${subtaskCmd}`.quiet().nothrow();
|
|
232
|
+
|
|
233
|
+
if (subtaskResult.exitCode !== 0) {
|
|
234
|
+
throw new BeadError(
|
|
235
|
+
`Failed to create subtask: ${subtaskResult.stderr.toString()}`,
|
|
236
|
+
subtaskCmd.join(" "),
|
|
237
|
+
subtaskResult.exitCode,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const subtaskBead = parseBead(subtaskResult.stdout.toString());
|
|
242
|
+
created.push(subtaskBead);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const result: EpicCreateResult = {
|
|
246
|
+
success: true,
|
|
247
|
+
epic,
|
|
248
|
+
subtasks: created.slice(1),
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
return JSON.stringify(result, null, 2);
|
|
252
|
+
} catch (error) {
|
|
253
|
+
// Partial failure - return what was created with rollback hint
|
|
254
|
+
const rollbackHint = created
|
|
255
|
+
.map((b) => `bd close ${b.id} --reason "Rollback partial epic"`)
|
|
256
|
+
.join("\n");
|
|
257
|
+
|
|
258
|
+
const result: EpicCreateResult = {
|
|
259
|
+
success: false,
|
|
260
|
+
epic: created[0] || ({} as Bead),
|
|
261
|
+
subtasks: created.slice(1),
|
|
262
|
+
rollback_hint: rollbackHint,
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
return JSON.stringify(
|
|
266
|
+
{
|
|
267
|
+
...result,
|
|
268
|
+
error: error instanceof Error ? error.message : String(error),
|
|
269
|
+
},
|
|
270
|
+
null,
|
|
271
|
+
2,
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Query beads with filters
|
|
279
|
+
*/
|
|
280
|
+
export const beads_query = tool({
|
|
281
|
+
description: "Query beads with filters (replaces bd list, bd ready, bd wip)",
|
|
282
|
+
args: {
|
|
283
|
+
status: tool.schema
|
|
284
|
+
.enum(["open", "in_progress", "blocked", "closed"])
|
|
285
|
+
.optional()
|
|
286
|
+
.describe("Filter by status"),
|
|
287
|
+
type: tool.schema
|
|
288
|
+
.enum(["bug", "feature", "task", "epic", "chore"])
|
|
289
|
+
.optional()
|
|
290
|
+
.describe("Filter by type"),
|
|
291
|
+
ready: tool.schema
|
|
292
|
+
.boolean()
|
|
293
|
+
.optional()
|
|
294
|
+
.describe("Only show unblocked beads (uses bd ready)"),
|
|
295
|
+
limit: tool.schema
|
|
296
|
+
.number()
|
|
297
|
+
.optional()
|
|
298
|
+
.describe("Max results to return (default: 20)"),
|
|
299
|
+
},
|
|
300
|
+
async execute(args, ctx) {
|
|
301
|
+
const validated = BeadQueryArgsSchema.parse(args);
|
|
302
|
+
|
|
303
|
+
let cmd: string[];
|
|
304
|
+
|
|
305
|
+
if (validated.ready) {
|
|
306
|
+
cmd = ["bd", "ready", "--json"];
|
|
307
|
+
} else {
|
|
308
|
+
cmd = ["bd", "list", "--json"];
|
|
309
|
+
if (validated.status) {
|
|
310
|
+
cmd.push("--status", validated.status);
|
|
311
|
+
}
|
|
312
|
+
if (validated.type) {
|
|
313
|
+
cmd.push("--type", validated.type);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const result = await Bun.$`${cmd}`.quiet().nothrow();
|
|
318
|
+
|
|
319
|
+
if (result.exitCode !== 0) {
|
|
320
|
+
throw new BeadError(
|
|
321
|
+
`Failed to query beads: ${result.stderr.toString()}`,
|
|
322
|
+
cmd.join(" "),
|
|
323
|
+
result.exitCode,
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const beads = parseBeads(result.stdout.toString());
|
|
328
|
+
const limited = beads.slice(0, validated.limit);
|
|
329
|
+
|
|
330
|
+
return JSON.stringify(limited, null, 2);
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Update a bead's status or description
|
|
336
|
+
*/
|
|
337
|
+
export const beads_update = tool({
|
|
338
|
+
description: "Update bead status/description",
|
|
339
|
+
args: {
|
|
340
|
+
id: tool.schema.string().describe("Bead ID"),
|
|
341
|
+
status: tool.schema
|
|
342
|
+
.enum(["open", "in_progress", "blocked", "closed"])
|
|
343
|
+
.optional()
|
|
344
|
+
.describe("New status"),
|
|
345
|
+
description: tool.schema.string().optional().describe("New description"),
|
|
346
|
+
priority: tool.schema
|
|
347
|
+
.number()
|
|
348
|
+
.min(0)
|
|
349
|
+
.max(3)
|
|
350
|
+
.optional()
|
|
351
|
+
.describe("New priority"),
|
|
352
|
+
},
|
|
353
|
+
async execute(args, ctx) {
|
|
354
|
+
const validated = BeadUpdateArgsSchema.parse(args);
|
|
355
|
+
|
|
356
|
+
const cmd = ["bd", "update", validated.id];
|
|
357
|
+
|
|
358
|
+
if (validated.status) {
|
|
359
|
+
cmd.push("--status", validated.status);
|
|
360
|
+
}
|
|
361
|
+
if (validated.description) {
|
|
362
|
+
cmd.push("-d", validated.description);
|
|
363
|
+
}
|
|
364
|
+
if (validated.priority !== undefined) {
|
|
365
|
+
cmd.push("-p", validated.priority.toString());
|
|
366
|
+
}
|
|
367
|
+
cmd.push("--json");
|
|
368
|
+
|
|
369
|
+
const result = await Bun.$`${cmd}`.quiet().nothrow();
|
|
370
|
+
|
|
371
|
+
if (result.exitCode !== 0) {
|
|
372
|
+
throw new BeadError(
|
|
373
|
+
`Failed to update bead: ${result.stderr.toString()}`,
|
|
374
|
+
cmd.join(" "),
|
|
375
|
+
result.exitCode,
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const bead = parseBead(result.stdout.toString());
|
|
380
|
+
return JSON.stringify(bead, null, 2);
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Close a bead with reason
|
|
386
|
+
*/
|
|
387
|
+
export const beads_close = tool({
|
|
388
|
+
description: "Close a bead with reason",
|
|
389
|
+
args: {
|
|
390
|
+
id: tool.schema.string().describe("Bead ID"),
|
|
391
|
+
reason: tool.schema.string().describe("Completion reason"),
|
|
392
|
+
},
|
|
393
|
+
async execute(args, ctx) {
|
|
394
|
+
const validated = BeadCloseArgsSchema.parse(args);
|
|
395
|
+
|
|
396
|
+
const cmd = [
|
|
397
|
+
"bd",
|
|
398
|
+
"close",
|
|
399
|
+
validated.id,
|
|
400
|
+
"--reason",
|
|
401
|
+
validated.reason,
|
|
402
|
+
"--json",
|
|
403
|
+
];
|
|
404
|
+
|
|
405
|
+
const result = await Bun.$`${cmd}`.quiet().nothrow();
|
|
406
|
+
|
|
407
|
+
if (result.exitCode !== 0) {
|
|
408
|
+
throw new BeadError(
|
|
409
|
+
`Failed to close bead: ${result.stderr.toString()}`,
|
|
410
|
+
cmd.join(" "),
|
|
411
|
+
result.exitCode,
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const bead = parseBead(result.stdout.toString());
|
|
416
|
+
return `Closed ${bead.id}: ${validated.reason}`;
|
|
417
|
+
},
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Mark a bead as in-progress
|
|
422
|
+
*/
|
|
423
|
+
export const beads_start = tool({
|
|
424
|
+
description:
|
|
425
|
+
"Mark a bead as in-progress (shortcut for update --status in_progress)",
|
|
426
|
+
args: {
|
|
427
|
+
id: tool.schema.string().describe("Bead ID"),
|
|
428
|
+
},
|
|
429
|
+
async execute(args, ctx) {
|
|
430
|
+
const cmd = ["bd", "update", args.id, "--status", "in_progress", "--json"];
|
|
431
|
+
|
|
432
|
+
const result = await Bun.$`${cmd}`.quiet().nothrow();
|
|
433
|
+
|
|
434
|
+
if (result.exitCode !== 0) {
|
|
435
|
+
throw new BeadError(
|
|
436
|
+
`Failed to start bead: ${result.stderr.toString()}`,
|
|
437
|
+
cmd.join(" "),
|
|
438
|
+
result.exitCode,
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const bead = parseBead(result.stdout.toString());
|
|
443
|
+
return `Started: ${bead.id}`;
|
|
444
|
+
},
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Get the next ready bead
|
|
449
|
+
*/
|
|
450
|
+
export const beads_ready = tool({
|
|
451
|
+
description: "Get the next ready bead (unblocked, highest priority)",
|
|
452
|
+
args: {},
|
|
453
|
+
async execute(args, ctx) {
|
|
454
|
+
const cmd = ["bd", "ready", "--json"];
|
|
455
|
+
|
|
456
|
+
const result = await Bun.$`${cmd}`.quiet().nothrow();
|
|
457
|
+
|
|
458
|
+
if (result.exitCode !== 0) {
|
|
459
|
+
throw new BeadError(
|
|
460
|
+
`Failed to get ready beads: ${result.stderr.toString()}`,
|
|
461
|
+
cmd.join(" "),
|
|
462
|
+
result.exitCode,
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const beads = parseBeads(result.stdout.toString());
|
|
467
|
+
|
|
468
|
+
if (beads.length === 0) {
|
|
469
|
+
return "No ready beads";
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const next = beads[0];
|
|
473
|
+
return JSON.stringify(next, null, 2);
|
|
474
|
+
},
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Sync beads to git and push
|
|
479
|
+
*/
|
|
480
|
+
export const beads_sync = tool({
|
|
481
|
+
description: "Sync beads to git and push (MANDATORY at session end)",
|
|
482
|
+
args: {
|
|
483
|
+
auto_pull: tool.schema
|
|
484
|
+
.boolean()
|
|
485
|
+
.optional()
|
|
486
|
+
.describe("Pull before sync (default: true)"),
|
|
487
|
+
},
|
|
488
|
+
async execute(args, ctx) {
|
|
489
|
+
const autoPull = args.auto_pull ?? true;
|
|
490
|
+
|
|
491
|
+
// 1. Pull if requested
|
|
492
|
+
if (autoPull) {
|
|
493
|
+
const pullResult = await Bun.$`git pull --rebase`.quiet().nothrow();
|
|
494
|
+
if (pullResult.exitCode !== 0) {
|
|
495
|
+
throw new BeadError(
|
|
496
|
+
`Failed to pull: ${pullResult.stderr.toString()}`,
|
|
497
|
+
"git pull --rebase",
|
|
498
|
+
pullResult.exitCode,
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// 2. Sync beads
|
|
504
|
+
const syncResult = await Bun.$`bd sync`.quiet().nothrow();
|
|
505
|
+
if (syncResult.exitCode !== 0) {
|
|
506
|
+
throw new BeadError(
|
|
507
|
+
`Failed to sync beads: ${syncResult.stderr.toString()}`,
|
|
508
|
+
"bd sync",
|
|
509
|
+
syncResult.exitCode,
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// 3. Push
|
|
514
|
+
const pushResult = await Bun.$`git push`.quiet().nothrow();
|
|
515
|
+
if (pushResult.exitCode !== 0) {
|
|
516
|
+
throw new BeadError(
|
|
517
|
+
`Failed to push: ${pushResult.stderr.toString()}`,
|
|
518
|
+
"git push",
|
|
519
|
+
pushResult.exitCode,
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// 4. Verify clean state
|
|
524
|
+
const statusResult = await Bun.$`git status --porcelain`.quiet().nothrow();
|
|
525
|
+
const status = statusResult.stdout.toString().trim();
|
|
526
|
+
|
|
527
|
+
if (status !== "") {
|
|
528
|
+
return `Beads synced and pushed, but working directory not clean:\n${status}`;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return "Beads synced and pushed successfully";
|
|
532
|
+
},
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Link a bead to an Agent Mail thread
|
|
537
|
+
*/
|
|
538
|
+
export const beads_link_thread = tool({
|
|
539
|
+
description: "Add metadata linking bead to Agent Mail thread",
|
|
540
|
+
args: {
|
|
541
|
+
bead_id: tool.schema.string().describe("Bead ID"),
|
|
542
|
+
thread_id: tool.schema.string().describe("Agent Mail thread ID"),
|
|
543
|
+
},
|
|
544
|
+
async execute(args, ctx) {
|
|
545
|
+
// Update bead description to include thread link
|
|
546
|
+
// This is a workaround since bd doesn't have native metadata support
|
|
547
|
+
const queryResult = await Bun.$`bd show ${args.bead_id} --json`
|
|
548
|
+
.quiet()
|
|
549
|
+
.nothrow();
|
|
550
|
+
|
|
551
|
+
if (queryResult.exitCode !== 0) {
|
|
552
|
+
throw new BeadError(
|
|
553
|
+
`Failed to get bead: ${queryResult.stderr.toString()}`,
|
|
554
|
+
`bd show ${args.bead_id} --json`,
|
|
555
|
+
queryResult.exitCode,
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const bead = parseBead(queryResult.stdout.toString());
|
|
560
|
+
const existingDesc = bead.description || "";
|
|
561
|
+
|
|
562
|
+
// Add thread link if not already present
|
|
563
|
+
const threadMarker = `[thread:${args.thread_id}]`;
|
|
564
|
+
if (existingDesc.includes(threadMarker)) {
|
|
565
|
+
return `Bead ${args.bead_id} already linked to thread ${args.thread_id}`;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const newDesc = existingDesc
|
|
569
|
+
? `${existingDesc}\n\n${threadMarker}`
|
|
570
|
+
: threadMarker;
|
|
571
|
+
|
|
572
|
+
const updateResult =
|
|
573
|
+
await Bun.$`bd update ${args.bead_id} -d ${newDesc} --json`
|
|
574
|
+
.quiet()
|
|
575
|
+
.nothrow();
|
|
576
|
+
|
|
577
|
+
if (updateResult.exitCode !== 0) {
|
|
578
|
+
throw new BeadError(
|
|
579
|
+
`Failed to update bead: ${updateResult.stderr.toString()}`,
|
|
580
|
+
`bd update ${args.bead_id} -d ...`,
|
|
581
|
+
updateResult.exitCode,
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
return `Linked bead ${args.bead_id} to thread ${args.thread_id}`;
|
|
586
|
+
},
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
// ============================================================================
|
|
590
|
+
// Export all tools
|
|
591
|
+
// ============================================================================
|
|
592
|
+
|
|
593
|
+
export const beadsTools = {
|
|
594
|
+
beads_create: beads_create,
|
|
595
|
+
beads_create_epic: beads_create_epic,
|
|
596
|
+
beads_query: beads_query,
|
|
597
|
+
beads_update: beads_update,
|
|
598
|
+
beads_close: beads_close,
|
|
599
|
+
beads_start: beads_start,
|
|
600
|
+
beads_ready: beads_ready,
|
|
601
|
+
beads_sync: beads_sync,
|
|
602
|
+
beads_link_thread: beads_link_thread,
|
|
603
|
+
};
|