opencodekit 0.16.4 → 0.16.6
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/dist/index.js +1 -1
- package/dist/template/.opencode/AGENTS.md +106 -384
- package/dist/template/.opencode/README.md +170 -104
- package/dist/template/.opencode/agent/build.md +39 -32
- package/dist/template/.opencode/agent/explore.md +2 -0
- package/dist/template/.opencode/agent/review.md +3 -0
- package/dist/template/.opencode/agent/scout.md +22 -11
- package/dist/template/.opencode/command/create.md +164 -106
- package/dist/template/.opencode/command/design.md +5 -1
- package/dist/template/.opencode/command/handoff.md +6 -4
- package/dist/template/.opencode/command/init.md +1 -1
- package/dist/template/.opencode/command/plan.md +26 -23
- package/dist/template/.opencode/command/research.md +13 -6
- package/dist/template/.opencode/command/resume.md +8 -6
- package/dist/template/.opencode/command/ship.md +1 -1
- package/dist/template/.opencode/command/start.md +30 -25
- package/dist/template/.opencode/command/status.md +9 -42
- package/dist/template/.opencode/command/verify.md +11 -11
- package/dist/template/.opencode/memory/README.md +67 -37
- package/dist/template/.opencode/memory/_templates/prd.md +102 -18
- package/dist/template/.opencode/memory/project/gotchas.md +31 -0
- package/dist/template/.opencode/memory.db +0 -0
- package/dist/template/.opencode/memory.db-shm +0 -0
- package/dist/template/.opencode/memory.db-wal +0 -0
- package/dist/template/.opencode/opencode.json +0 -10
- package/dist/template/.opencode/package.json +1 -1
- package/dist/template/.opencode/skill/beads/SKILL.md +164 -380
- package/dist/template/.opencode/skill/beads/references/BOUNDARIES.md +23 -22
- package/dist/template/.opencode/skill/beads/references/DEPENDENCIES.md +23 -29
- package/dist/template/.opencode/skill/beads/references/RESUMABILITY.md +5 -8
- package/dist/template/.opencode/skill/beads/references/WORKFLOWS.md +43 -39
- package/dist/template/.opencode/skill/beads-bridge/SKILL.md +80 -53
- package/dist/template/.opencode/skill/brainstorming/SKILL.md +19 -5
- package/dist/template/.opencode/skill/context-engineering/SKILL.md +30 -63
- package/dist/template/.opencode/skill/context-management/SKILL.md +115 -0
- package/dist/template/.opencode/skill/deep-research/SKILL.md +4 -4
- package/dist/template/.opencode/skill/development-lifecycle/SKILL.md +305 -0
- package/dist/template/.opencode/skill/memory-system/SKILL.md +3 -3
- package/dist/template/.opencode/skill/prd/SKILL.md +47 -122
- package/dist/template/.opencode/skill/prd-task/SKILL.md +48 -4
- package/dist/template/.opencode/skill/prd-task/references/prd-schema.json +120 -24
- package/dist/template/.opencode/skill/swarm-coordination/SKILL.md +79 -61
- package/dist/template/.opencode/skill/tool-priority/SKILL.md +31 -22
- package/dist/template/.opencode/tool/context7.ts +183 -0
- package/dist/template/.opencode/tool/memory-admin.ts +445 -0
- package/dist/template/.opencode/tool/swarm.ts +572 -0
- package/package.json +1 -1
- package/dist/template/.opencode/memory/_templates/spec.md +0 -66
- package/dist/template/.opencode/tool/beads-sync.ts +0 -657
- package/dist/template/.opencode/tool/context7-query-docs.ts +0 -89
- package/dist/template/.opencode/tool/context7-resolve-library-id.ts +0 -113
- package/dist/template/.opencode/tool/memory-maintain.ts +0 -167
- package/dist/template/.opencode/tool/memory-migrate.ts +0 -319
- package/dist/template/.opencode/tool/swarm-delegate.ts +0 -180
- package/dist/template/.opencode/tool/swarm-monitor.ts +0 -388
- package/dist/template/.opencode/tool/swarm-plan.ts +0 -697
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
import { exec } from "node:child_process";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { tool } from "@opencode-ai/plugin";
|
|
6
|
+
|
|
7
|
+
const execAsync = promisify(exec);
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Unified swarm orchestration tool.
|
|
11
|
+
* Consolidates: swarm-plan, swarm-monitor, swarm-delegate, beads-sync
|
|
12
|
+
*/
|
|
13
|
+
export default tool({
|
|
14
|
+
description: `Swarm orchestration for parallel task execution.
|
|
15
|
+
|
|
16
|
+
Operations:
|
|
17
|
+
- plan: Analyze task for parallel execution
|
|
18
|
+
- monitor: Track worker progress
|
|
19
|
+
- delegate: Create delegation packet
|
|
20
|
+
- sync: Bridge Beads tasks to OpenCode todos
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
swarm({ op: "plan", task: "..." })
|
|
24
|
+
swarm({ op: "monitor", team: "...", action: "status" })
|
|
25
|
+
swarm({ op: "delegate", bead_id: "...", outcome: "..." })
|
|
26
|
+
swarm({ op: "sync", action: "push" })`,
|
|
27
|
+
|
|
28
|
+
args: {
|
|
29
|
+
op: tool.schema
|
|
30
|
+
.enum(["plan", "monitor", "delegate", "sync"])
|
|
31
|
+
.describe("Operation: plan, monitor, delegate, sync"),
|
|
32
|
+
// Plan args
|
|
33
|
+
task: tool.schema.string().optional().describe("Task description (plan)"),
|
|
34
|
+
files: tool.schema
|
|
35
|
+
.string()
|
|
36
|
+
.optional()
|
|
37
|
+
.describe("Comma-separated files (plan)"),
|
|
38
|
+
// Monitor args
|
|
39
|
+
team: tool.schema.string().optional().describe("Team name (monitor)"),
|
|
40
|
+
action: tool.schema
|
|
41
|
+
.string()
|
|
42
|
+
.optional()
|
|
43
|
+
.describe(
|
|
44
|
+
"Monitor action: update, render, status, clear | Sync action: push, pull",
|
|
45
|
+
),
|
|
46
|
+
worker_id: tool.schema.string().optional().describe("Worker ID (monitor)"),
|
|
47
|
+
phase: tool.schema.string().optional().describe("Phase name (monitor)"),
|
|
48
|
+
progress: tool.schema
|
|
49
|
+
.number()
|
|
50
|
+
.min(0)
|
|
51
|
+
.max(100)
|
|
52
|
+
.optional()
|
|
53
|
+
.describe("Progress 0-100 (monitor)"),
|
|
54
|
+
status: tool.schema.string().optional().describe("Worker status (monitor)"),
|
|
55
|
+
file: tool.schema.string().optional().describe("Current file (monitor)"),
|
|
56
|
+
// Delegate args
|
|
57
|
+
bead_id: tool.schema.string().optional().describe("Bead ID (delegate)"),
|
|
58
|
+
title: tool.schema.string().optional().describe("Task title (delegate)"),
|
|
59
|
+
outcome: tool.schema
|
|
60
|
+
.string()
|
|
61
|
+
.optional()
|
|
62
|
+
.describe("Expected outcome (delegate)"),
|
|
63
|
+
must_do: tool.schema
|
|
64
|
+
.string()
|
|
65
|
+
.optional()
|
|
66
|
+
.describe("Must do list (delegate)"),
|
|
67
|
+
must_not: tool.schema
|
|
68
|
+
.string()
|
|
69
|
+
.optional()
|
|
70
|
+
.describe("Must not do list (delegate)"),
|
|
71
|
+
checks: tool.schema
|
|
72
|
+
.string()
|
|
73
|
+
.optional()
|
|
74
|
+
.describe("Acceptance checks (delegate)"),
|
|
75
|
+
context: tool.schema
|
|
76
|
+
.string()
|
|
77
|
+
.optional()
|
|
78
|
+
.describe("Extra context (delegate)"),
|
|
79
|
+
write: tool.schema
|
|
80
|
+
.boolean()
|
|
81
|
+
.optional()
|
|
82
|
+
.describe("Write to file (delegate)"),
|
|
83
|
+
// Sync args
|
|
84
|
+
filter: tool.schema
|
|
85
|
+
.string()
|
|
86
|
+
.optional()
|
|
87
|
+
.describe("Filter: open, in_progress, all (sync)"),
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
execute: async (args, ctx) => {
|
|
91
|
+
const worktree = ctx.worktree || process.cwd();
|
|
92
|
+
|
|
93
|
+
switch (args.op) {
|
|
94
|
+
case "plan":
|
|
95
|
+
return planOperation(args.task || "", args.files);
|
|
96
|
+
case "monitor":
|
|
97
|
+
return monitorOperation(args, worktree);
|
|
98
|
+
case "delegate":
|
|
99
|
+
return delegateOperation(args);
|
|
100
|
+
case "sync":
|
|
101
|
+
return syncOperation(args, worktree);
|
|
102
|
+
default:
|
|
103
|
+
return `Error: Unknown operation: ${args.op}`;
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// ============================================================
|
|
109
|
+
// PLAN OPERATION (from swarm-plan.ts)
|
|
110
|
+
// ============================================================
|
|
111
|
+
|
|
112
|
+
interface TaskClassification {
|
|
113
|
+
type: "search" | "batch" | "writing" | "sequential" | "mixed";
|
|
114
|
+
coupling: "high" | "medium" | "low";
|
|
115
|
+
recommended_agents: number;
|
|
116
|
+
reasoning: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function planOperation(task: string, files?: string): string {
|
|
120
|
+
const fileList = files?.split(",").filter(Boolean) || [];
|
|
121
|
+
const fileCount = Number.parseInt(files || "0") || fileList.length;
|
|
122
|
+
|
|
123
|
+
const classification = classifyTask(task, fileList);
|
|
124
|
+
const collapseCheck = detectSerialCollapse(
|
|
125
|
+
task,
|
|
126
|
+
fileCount,
|
|
127
|
+
classification.recommended_agents,
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
let recommendation: string;
|
|
131
|
+
if (collapseCheck.is_collapse) {
|
|
132
|
+
recommendation = `Swarm: ${Math.min(fileCount, 5)} agents (serial collapse detected)`;
|
|
133
|
+
} else if (classification.recommended_agents > 1) {
|
|
134
|
+
recommendation = `Swarm: ${classification.recommended_agents} agents`;
|
|
135
|
+
} else {
|
|
136
|
+
recommendation = "Single agent sufficient";
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return JSON.stringify(
|
|
140
|
+
{
|
|
141
|
+
task: task.slice(0, 100),
|
|
142
|
+
file_count: fileCount,
|
|
143
|
+
classification,
|
|
144
|
+
serial_collapse: collapseCheck,
|
|
145
|
+
recommendation,
|
|
146
|
+
},
|
|
147
|
+
null,
|
|
148
|
+
2,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function classifyTask(task: string, files: string[]): TaskClassification {
|
|
153
|
+
const searchPatterns = /research|find|search|explore|investigate/i;
|
|
154
|
+
const batchPatterns = /refactor|update|migrate|convert.*all|batch/i;
|
|
155
|
+
const sequentialPatterns = /debug|fix.*issue|optimize|complex/i;
|
|
156
|
+
|
|
157
|
+
const coupling = analyzeCoupling(files);
|
|
158
|
+
|
|
159
|
+
if (searchPatterns.test(task)) {
|
|
160
|
+
return {
|
|
161
|
+
type: "search",
|
|
162
|
+
coupling: "low",
|
|
163
|
+
recommended_agents: Math.min(Math.max(files.length, 3), 5),
|
|
164
|
+
reasoning: "Search tasks benefit from parallel exploration",
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if ((batchPatterns.test(task) || files.length > 3) && files.length > 0) {
|
|
169
|
+
return {
|
|
170
|
+
type: "batch",
|
|
171
|
+
coupling,
|
|
172
|
+
recommended_agents: Math.min(files.length, 8),
|
|
173
|
+
reasoning: `Batch processing ${files.length} files`,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (
|
|
178
|
+
sequentialPatterns.test(task) ||
|
|
179
|
+
coupling === "high" ||
|
|
180
|
+
files.length <= 2
|
|
181
|
+
) {
|
|
182
|
+
return {
|
|
183
|
+
type: "sequential",
|
|
184
|
+
coupling: "high",
|
|
185
|
+
recommended_agents: 1,
|
|
186
|
+
reasoning: "High coupling requires sequential execution",
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
type: "mixed",
|
|
192
|
+
coupling,
|
|
193
|
+
recommended_agents: Math.min(files.length || 2, 4),
|
|
194
|
+
reasoning: "Mixed approach with verification",
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function analyzeCoupling(files: string[]): "high" | "medium" | "low" {
|
|
199
|
+
if (files.length <= 1) return "high";
|
|
200
|
+
if (files.length <= 3) return "medium";
|
|
201
|
+
const dirs = files.map((f) => path.dirname(f));
|
|
202
|
+
const uniqueDirs = new Set(dirs);
|
|
203
|
+
if (uniqueDirs.size === 1) return "high";
|
|
204
|
+
if (uniqueDirs.size <= files.length / 2) return "medium";
|
|
205
|
+
return "low";
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function detectSerialCollapse(
|
|
209
|
+
task: string,
|
|
210
|
+
fileCount: number,
|
|
211
|
+
agents: number,
|
|
212
|
+
): { is_collapse: boolean; warnings: string[] } {
|
|
213
|
+
const warnings: string[] = [];
|
|
214
|
+
if (fileCount >= 5 && agents === 1) warnings.push("Many files, single agent");
|
|
215
|
+
if (/research|search/i.test(task) && agents === 1)
|
|
216
|
+
warnings.push("Search with single agent");
|
|
217
|
+
if (/refactor.*all|update.*all/i.test(task) && agents === 1)
|
|
218
|
+
warnings.push("Batch with single agent");
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
is_collapse:
|
|
222
|
+
warnings.length >= 2 || (warnings.length === 1 && fileCount > 8),
|
|
223
|
+
warnings,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ============================================================
|
|
228
|
+
// MONITOR OPERATION (from swarm-monitor.ts)
|
|
229
|
+
// ============================================================
|
|
230
|
+
|
|
231
|
+
const PROGRESS_FILE = ".beads/swarm-progress.jsonl";
|
|
232
|
+
|
|
233
|
+
interface ProgressEntry {
|
|
234
|
+
timestamp: string;
|
|
235
|
+
team_name: string;
|
|
236
|
+
worker_id: string;
|
|
237
|
+
phase: string;
|
|
238
|
+
progress: number;
|
|
239
|
+
status: string;
|
|
240
|
+
file?: string;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function monitorOperation(
|
|
244
|
+
args: {
|
|
245
|
+
team?: string;
|
|
246
|
+
action?: string;
|
|
247
|
+
worker_id?: string;
|
|
248
|
+
phase?: string;
|
|
249
|
+
progress?: number;
|
|
250
|
+
status?: string;
|
|
251
|
+
file?: string;
|
|
252
|
+
},
|
|
253
|
+
worktree: string,
|
|
254
|
+
): Promise<string> {
|
|
255
|
+
const team = args.team || "default";
|
|
256
|
+
const action = args.action || "status";
|
|
257
|
+
|
|
258
|
+
switch (action) {
|
|
259
|
+
case "update":
|
|
260
|
+
return updateProgress(
|
|
261
|
+
{
|
|
262
|
+
timestamp: new Date().toISOString(),
|
|
263
|
+
team_name: team,
|
|
264
|
+
worker_id: args.worker_id || "unknown",
|
|
265
|
+
phase: args.phase || "unknown",
|
|
266
|
+
progress: args.progress || 0,
|
|
267
|
+
status: args.status || "idle",
|
|
268
|
+
file: args.file,
|
|
269
|
+
},
|
|
270
|
+
worktree,
|
|
271
|
+
);
|
|
272
|
+
case "render":
|
|
273
|
+
return renderProgress(team, worktree);
|
|
274
|
+
case "status":
|
|
275
|
+
return getFullStatus(team, worktree);
|
|
276
|
+
case "clear":
|
|
277
|
+
return clearTeam(team, worktree);
|
|
278
|
+
default:
|
|
279
|
+
return `Unknown action: ${action}`;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async function updateProgress(
|
|
284
|
+
entry: ProgressEntry,
|
|
285
|
+
worktree: string,
|
|
286
|
+
): Promise<string> {
|
|
287
|
+
const progressPath = path.join(worktree, PROGRESS_FILE);
|
|
288
|
+
await fs.mkdir(path.dirname(progressPath), { recursive: true });
|
|
289
|
+
await fs.appendFile(progressPath, `${JSON.stringify(entry)}\n`, "utf-8");
|
|
290
|
+
return JSON.stringify({ success: true, record: entry }, null, 2);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function getProgress(
|
|
294
|
+
team: string,
|
|
295
|
+
worktree: string,
|
|
296
|
+
): Promise<{ workers: ProgressEntry[] }> {
|
|
297
|
+
const progressPath = path.join(worktree, PROGRESS_FILE);
|
|
298
|
+
try {
|
|
299
|
+
const content = await fs.readFile(progressPath, "utf-8");
|
|
300
|
+
const entries = content
|
|
301
|
+
.trim()
|
|
302
|
+
.split("\n")
|
|
303
|
+
.filter(Boolean)
|
|
304
|
+
.map((l) => JSON.parse(l) as ProgressEntry)
|
|
305
|
+
.filter((e) => e.team_name === team);
|
|
306
|
+
const workerMap = new Map<string, ProgressEntry>();
|
|
307
|
+
for (const e of entries) workerMap.set(e.worker_id, e);
|
|
308
|
+
return { workers: Array.from(workerMap.values()) };
|
|
309
|
+
} catch {
|
|
310
|
+
return { workers: [] };
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async function renderProgress(team: string, worktree: string): Promise<string> {
|
|
315
|
+
const { workers } = await getProgress(team, worktree);
|
|
316
|
+
if (workers.length === 0) return `No progress for team: ${team}`;
|
|
317
|
+
|
|
318
|
+
let output = `## Swarm: ${team}\n\n| Worker | Phase | Progress | Status |\n|---|---|---|---|\n`;
|
|
319
|
+
for (const w of workers) {
|
|
320
|
+
output += `| ${w.worker_id} | ${w.phase} | ${w.progress}% | ${w.status} |\n`;
|
|
321
|
+
}
|
|
322
|
+
return output;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async function getFullStatus(team: string, worktree: string): Promise<string> {
|
|
326
|
+
const { workers } = await getProgress(team, worktree);
|
|
327
|
+
return JSON.stringify(
|
|
328
|
+
{
|
|
329
|
+
team,
|
|
330
|
+
workers: workers.length,
|
|
331
|
+
completed: workers.filter((w) => w.status === "completed").length,
|
|
332
|
+
working: workers.filter((w) => w.status === "working").length,
|
|
333
|
+
errors: workers.filter((w) => w.status === "error").length,
|
|
334
|
+
details: workers,
|
|
335
|
+
},
|
|
336
|
+
null,
|
|
337
|
+
2,
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async function clearTeam(team: string, worktree: string): Promise<string> {
|
|
342
|
+
const progressPath = path.join(worktree, PROGRESS_FILE);
|
|
343
|
+
try {
|
|
344
|
+
const content = await fs.readFile(progressPath, "utf-8");
|
|
345
|
+
const entries = content
|
|
346
|
+
.trim()
|
|
347
|
+
.split("\n")
|
|
348
|
+
.filter(Boolean)
|
|
349
|
+
.map((l) => JSON.parse(l) as ProgressEntry);
|
|
350
|
+
const other = entries.filter((e) => e.team_name !== team);
|
|
351
|
+
await fs.writeFile(
|
|
352
|
+
progressPath,
|
|
353
|
+
other.map((e) => JSON.stringify(e)).join("\n") +
|
|
354
|
+
(other.length ? "\n" : ""),
|
|
355
|
+
"utf-8",
|
|
356
|
+
);
|
|
357
|
+
return JSON.stringify({
|
|
358
|
+
success: true,
|
|
359
|
+
cleared: entries.length - other.length,
|
|
360
|
+
});
|
|
361
|
+
} catch {
|
|
362
|
+
return JSON.stringify({ success: true, cleared: 0 });
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// ============================================================
|
|
367
|
+
// DELEGATE OPERATION (from swarm-delegate.ts)
|
|
368
|
+
// ============================================================
|
|
369
|
+
|
|
370
|
+
function delegateOperation(args: {
|
|
371
|
+
bead_id?: string;
|
|
372
|
+
title?: string;
|
|
373
|
+
outcome?: string;
|
|
374
|
+
must_do?: string;
|
|
375
|
+
must_not?: string;
|
|
376
|
+
checks?: string;
|
|
377
|
+
context?: string;
|
|
378
|
+
write?: boolean;
|
|
379
|
+
}): string {
|
|
380
|
+
if (!args.bead_id) return "Error: bead_id required";
|
|
381
|
+
if (!args.outcome) return "Error: outcome required";
|
|
382
|
+
|
|
383
|
+
const split = (s?: string) =>
|
|
384
|
+
s
|
|
385
|
+
?.split(/[,\n]/)
|
|
386
|
+
.map((x) => x.trim())
|
|
387
|
+
.filter(Boolean) || [];
|
|
388
|
+
const bullets = (items: string[]) =>
|
|
389
|
+
items.length ? items.map((i) => `- ${i}`).join("\n") : "- (none)";
|
|
390
|
+
|
|
391
|
+
const packet = [
|
|
392
|
+
"# Delegation Packet",
|
|
393
|
+
"",
|
|
394
|
+
`- TASK: ${args.bead_id}${args.title ? ` - ${args.title}` : ""}`,
|
|
395
|
+
`- EXPECTED OUTCOME: ${args.outcome}`,
|
|
396
|
+
"- MUST DO:",
|
|
397
|
+
bullets(split(args.must_do)),
|
|
398
|
+
"- MUST NOT DO:",
|
|
399
|
+
bullets(split(args.must_not)),
|
|
400
|
+
"- ACCEPTANCE CHECKS:",
|
|
401
|
+
bullets(split(args.checks)),
|
|
402
|
+
"- CONTEXT:",
|
|
403
|
+
args.context || "(none)",
|
|
404
|
+
].join("\n");
|
|
405
|
+
|
|
406
|
+
if (!args.write) return packet;
|
|
407
|
+
|
|
408
|
+
// Write to artifact file (sync for simplicity)
|
|
409
|
+
const artifactDir = path.join(
|
|
410
|
+
process.cwd(),
|
|
411
|
+
".beads",
|
|
412
|
+
"artifacts",
|
|
413
|
+
args.bead_id,
|
|
414
|
+
);
|
|
415
|
+
const outPath = path.join(artifactDir, "delegation.md");
|
|
416
|
+
fs.mkdir(artifactDir, { recursive: true })
|
|
417
|
+
.then(() =>
|
|
418
|
+
fs.appendFile(
|
|
419
|
+
outPath,
|
|
420
|
+
`\n---\nGenerated: ${new Date().toISOString()}\n---\n\n${packet}\n`,
|
|
421
|
+
),
|
|
422
|
+
)
|
|
423
|
+
.catch(() => {});
|
|
424
|
+
|
|
425
|
+
return `✓ Delegation packet written to ${outPath}\n\n${packet}`;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// ============================================================
|
|
429
|
+
// SYNC OPERATION (from beads-sync.ts)
|
|
430
|
+
// ============================================================
|
|
431
|
+
|
|
432
|
+
const OPENCODE_TODO_DIR = path.join(
|
|
433
|
+
process.env.HOME || "",
|
|
434
|
+
".local",
|
|
435
|
+
"share",
|
|
436
|
+
"opencode",
|
|
437
|
+
"storage",
|
|
438
|
+
"todo",
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
interface BeadTask {
|
|
442
|
+
id: string;
|
|
443
|
+
title: string;
|
|
444
|
+
status: string;
|
|
445
|
+
priority: number;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async function syncOperation(
|
|
449
|
+
args: { action?: string; filter?: string },
|
|
450
|
+
worktree: string,
|
|
451
|
+
): Promise<string> {
|
|
452
|
+
const action = args.action || "push";
|
|
453
|
+
|
|
454
|
+
if (action === "push") {
|
|
455
|
+
return pushBeadsToTodos(worktree, args.filter || "open");
|
|
456
|
+
}
|
|
457
|
+
if (action === "pull") {
|
|
458
|
+
return pullTodosToBeads(worktree);
|
|
459
|
+
}
|
|
460
|
+
return `Unknown sync action: ${action}`;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
async function pushBeadsToTodos(
|
|
464
|
+
worktree: string,
|
|
465
|
+
filter: string,
|
|
466
|
+
): Promise<string> {
|
|
467
|
+
try {
|
|
468
|
+
const statusFilter = filter === "all" ? "" : `--status ${filter}`;
|
|
469
|
+
const { stdout } = await execAsync(`br list ${statusFilter} --json`, {
|
|
470
|
+
cwd: worktree,
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
let tasks: BeadTask[];
|
|
474
|
+
try {
|
|
475
|
+
tasks = JSON.parse(stdout);
|
|
476
|
+
} catch {
|
|
477
|
+
tasks = parseBeadListOutput(stdout);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (tasks.length === 0) {
|
|
481
|
+
return JSON.stringify({
|
|
482
|
+
success: true,
|
|
483
|
+
message: "No tasks to sync",
|
|
484
|
+
synced: 0,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const todos = tasks.map((t) => ({
|
|
489
|
+
id: t.id,
|
|
490
|
+
content: `[Bead] ${t.title}`,
|
|
491
|
+
status:
|
|
492
|
+
t.status === "closed"
|
|
493
|
+
? "completed"
|
|
494
|
+
: t.status === "in_progress"
|
|
495
|
+
? "in_progress"
|
|
496
|
+
: "pending",
|
|
497
|
+
priority: t.priority <= 1 ? "high" : t.priority <= 2 ? "medium" : "low",
|
|
498
|
+
beadId: t.id,
|
|
499
|
+
}));
|
|
500
|
+
|
|
501
|
+
const sessionId = `ses_${Date.now().toString(36)}_${path.basename(worktree).slice(0, 10)}`;
|
|
502
|
+
const todoPath = path.join(OPENCODE_TODO_DIR, `${sessionId}.json`);
|
|
503
|
+
|
|
504
|
+
await fs.mkdir(OPENCODE_TODO_DIR, { recursive: true });
|
|
505
|
+
await fs.writeFile(todoPath, JSON.stringify(todos, null, 2), "utf-8");
|
|
506
|
+
|
|
507
|
+
return JSON.stringify({
|
|
508
|
+
success: true,
|
|
509
|
+
synced: todos.length,
|
|
510
|
+
session_id: sessionId,
|
|
511
|
+
});
|
|
512
|
+
} catch (error: unknown) {
|
|
513
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
514
|
+
return JSON.stringify({ success: false, error: message });
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
async function pullTodosToBeads(worktree: string): Promise<string> {
|
|
519
|
+
// Simplified: scan todo files and close completed beads
|
|
520
|
+
try {
|
|
521
|
+
const files = await fs.readdir(OPENCODE_TODO_DIR).catch(() => []);
|
|
522
|
+
let updated = 0;
|
|
523
|
+
|
|
524
|
+
for (const file of files) {
|
|
525
|
+
if (!file.endsWith(".json")) continue;
|
|
526
|
+
const content = await fs.readFile(
|
|
527
|
+
path.join(OPENCODE_TODO_DIR, file),
|
|
528
|
+
"utf-8",
|
|
529
|
+
);
|
|
530
|
+
const todos = JSON.parse(content);
|
|
531
|
+
|
|
532
|
+
for (const todo of todos) {
|
|
533
|
+
if (todo.beadId && todo.status === "completed") {
|
|
534
|
+
try {
|
|
535
|
+
await execAsync(
|
|
536
|
+
`br close ${todo.beadId} --reason "Completed via todo"`,
|
|
537
|
+
{
|
|
538
|
+
cwd: worktree,
|
|
539
|
+
},
|
|
540
|
+
);
|
|
541
|
+
updated++;
|
|
542
|
+
} catch {
|
|
543
|
+
// Already closed
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return JSON.stringify({ success: true, updated });
|
|
550
|
+
} catch (error: unknown) {
|
|
551
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
552
|
+
return JSON.stringify({ success: false, error: message });
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function parseBeadListOutput(output: string): BeadTask[] {
|
|
557
|
+
const lines = output.trim().split("\n").filter(Boolean);
|
|
558
|
+
const tasks: BeadTask[] = [];
|
|
559
|
+
|
|
560
|
+
for (const line of lines) {
|
|
561
|
+
const match = line.match(/^#?(\S+)\s+\[(\w+)\]\s+(?:\(P(\d)\))?\s*(.+)$/);
|
|
562
|
+
if (match) {
|
|
563
|
+
tasks.push({
|
|
564
|
+
id: match[1],
|
|
565
|
+
status: match[2],
|
|
566
|
+
priority: match[3] ? Number.parseInt(match[3]) : 2,
|
|
567
|
+
title: match[4],
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
return tasks;
|
|
572
|
+
}
|
package/package.json
CHANGED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
# Specification
|
|
2
|
-
|
|
3
|
-
**Bead:** [bead-id]
|
|
4
|
-
**Proposal:** [link to proposal]
|
|
5
|
-
**Date:** [YYYY-MM-DD]
|
|
6
|
-
|
|
7
|
-
## Bead Metadata
|
|
8
|
-
|
|
9
|
-
```yaml
|
|
10
|
-
depends_on: [] # Bead IDs that must complete before this one
|
|
11
|
-
parallel: true # Can run concurrently with other parallel beads
|
|
12
|
-
conflicts_with: [] # Bead IDs that modify same files (cannot parallelize)
|
|
13
|
-
blocks: [] # Bead IDs that are waiting on this one
|
|
14
|
-
estimated_hours: 2 # Time estimate for planning
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## Requirements
|
|
20
|
-
|
|
21
|
-
### [Requirement Name]
|
|
22
|
-
|
|
23
|
-
Brief description of what must be true.
|
|
24
|
-
|
|
25
|
-
#### Scenarios
|
|
26
|
-
|
|
27
|
-
**WHEN** [precondition or trigger]
|
|
28
|
-
**THEN** [expected outcome]
|
|
29
|
-
|
|
30
|
-
**WHEN** [edge case condition]
|
|
31
|
-
**THEN** [expected behavior]
|
|
32
|
-
|
|
33
|
-
### [Requirement Name]
|
|
34
|
-
|
|
35
|
-
Brief description.
|
|
36
|
-
|
|
37
|
-
#### Scenarios
|
|
38
|
-
|
|
39
|
-
**WHEN** [condition]
|
|
40
|
-
**THEN** [outcome]
|
|
41
|
-
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## Non-Functional Requirements
|
|
45
|
-
|
|
46
|
-
- **Performance:** [constraint if applicable]
|
|
47
|
-
- **Security:** [constraint if applicable]
|
|
48
|
-
- **Compatibility:** [constraint if applicable]
|
|
49
|
-
|
|
50
|
-
---
|
|
51
|
-
|
|
52
|
-
## Affected Files
|
|
53
|
-
|
|
54
|
-
List files this bead will modify (for conflict detection with other beads):
|
|
55
|
-
|
|
56
|
-
```yaml
|
|
57
|
-
files:
|
|
58
|
-
- src/path/to/file.ts # Why
|
|
59
|
-
- src/path/to/other.ts # Why
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
---
|
|
63
|
-
|
|
64
|
-
## Out of Scope
|
|
65
|
-
|
|
66
|
-
- [Explicitly excluded capability]
|