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
|
@@ -1,657 +0,0 @@
|
|
|
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
|
-
// OpenCode's native todo storage location
|
|
10
|
-
const OPENCODE_TODO_DIR = path.join(
|
|
11
|
-
process.env.HOME || "",
|
|
12
|
-
".local",
|
|
13
|
-
"share",
|
|
14
|
-
"opencode",
|
|
15
|
-
"storage",
|
|
16
|
-
"todo",
|
|
17
|
-
);
|
|
18
|
-
|
|
19
|
-
// Shared task lists for cross-session coordination
|
|
20
|
-
const SHARED_TASKS_DIR = path.join(
|
|
21
|
-
process.env.HOME || "",
|
|
22
|
-
".local",
|
|
23
|
-
"share",
|
|
24
|
-
"opencode",
|
|
25
|
-
"storage",
|
|
26
|
-
"shared-tasks",
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
interface BeadTask {
|
|
30
|
-
id: string;
|
|
31
|
-
title: string;
|
|
32
|
-
status: "open" | "in_progress" | "closed";
|
|
33
|
-
priority: number;
|
|
34
|
-
labels: string[];
|
|
35
|
-
blockedBy: string[];
|
|
36
|
-
blocks: string[];
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
interface TodoItem {
|
|
40
|
-
id: string;
|
|
41
|
-
content: string;
|
|
42
|
-
status: "pending" | "in_progress" | "completed" | "cancelled";
|
|
43
|
-
priority: "high" | "medium" | "low";
|
|
44
|
-
blockedBy?: string[];
|
|
45
|
-
blocks?: string[];
|
|
46
|
-
beadId?: string;
|
|
47
|
-
phase?: string;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
interface SharedTaskList {
|
|
51
|
-
id: string;
|
|
52
|
-
name: string;
|
|
53
|
-
created: string;
|
|
54
|
-
updated: string;
|
|
55
|
-
tasks: TodoItem[];
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export default tool({
|
|
59
|
-
description: `Bridge between Beads git-backed tasks and OpenCode's native todo system.
|
|
60
|
-
|
|
61
|
-
Operations:
|
|
62
|
-
- push: Sync Beads tasks to OpenCode todos (makes them visible to subagents via todoread)
|
|
63
|
-
- pull: Update Beads from completed OpenCode todos
|
|
64
|
-
- create_shared: Create a shared task list for cross-session swarm coordination
|
|
65
|
-
- get_shared: Get a shared task list by ID
|
|
66
|
-
- update_shared: Update tasks in a shared list
|
|
67
|
-
- list_shared: List all shared task lists for this project
|
|
68
|
-
|
|
69
|
-
Usage:
|
|
70
|
-
beads-sync({ operation: "push" }) // Push current Beads to OpenCode todos
|
|
71
|
-
beads-sync({ operation: "create_shared", name: "api-refactor-swarm", tasks: [...] })
|
|
72
|
-
beads-sync({ operation: "get_shared", list_id: "abc123" })`,
|
|
73
|
-
|
|
74
|
-
args: {
|
|
75
|
-
operation: tool.schema
|
|
76
|
-
.enum([
|
|
77
|
-
"push",
|
|
78
|
-
"pull",
|
|
79
|
-
"create_shared",
|
|
80
|
-
"get_shared",
|
|
81
|
-
"update_shared",
|
|
82
|
-
"list_shared",
|
|
83
|
-
])
|
|
84
|
-
.describe("Sync operation to perform"),
|
|
85
|
-
session_id: tool.schema
|
|
86
|
-
.string()
|
|
87
|
-
.optional()
|
|
88
|
-
.describe("Target session ID (defaults to current)"),
|
|
89
|
-
list_id: tool.schema
|
|
90
|
-
.string()
|
|
91
|
-
.optional()
|
|
92
|
-
.describe("Shared task list ID (for get_shared/update_shared)"),
|
|
93
|
-
name: tool.schema
|
|
94
|
-
.string()
|
|
95
|
-
.optional()
|
|
96
|
-
.describe("Name for new shared task list"),
|
|
97
|
-
tasks: tool.schema
|
|
98
|
-
.string()
|
|
99
|
-
.optional()
|
|
100
|
-
.describe("JSON array of tasks (for create_shared/update_shared)"),
|
|
101
|
-
filter: tool.schema
|
|
102
|
-
.string()
|
|
103
|
-
.optional()
|
|
104
|
-
.describe("Filter by status: open, in_progress, all (default: open)"),
|
|
105
|
-
},
|
|
106
|
-
|
|
107
|
-
execute: async (args, ctx) => {
|
|
108
|
-
const worktree = ctx.worktree || process.cwd();
|
|
109
|
-
|
|
110
|
-
switch (args.operation) {
|
|
111
|
-
case "push":
|
|
112
|
-
return await pushBeadsToTodos(
|
|
113
|
-
worktree,
|
|
114
|
-
args.session_id,
|
|
115
|
-
args.filter || "open",
|
|
116
|
-
);
|
|
117
|
-
case "pull":
|
|
118
|
-
return await pullTodosToBeads(worktree, args.session_id);
|
|
119
|
-
case "create_shared":
|
|
120
|
-
return await createSharedList(args.name, args.tasks, worktree);
|
|
121
|
-
case "get_shared":
|
|
122
|
-
return await getSharedList(args.list_id);
|
|
123
|
-
case "update_shared":
|
|
124
|
-
return await updateSharedList(args.list_id, args.tasks);
|
|
125
|
-
case "list_shared":
|
|
126
|
-
return await listSharedLists(worktree);
|
|
127
|
-
default:
|
|
128
|
-
return `Error: Unknown operation: ${args.operation}`;
|
|
129
|
-
}
|
|
130
|
-
},
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Push Beads tasks to OpenCode's native todo storage
|
|
135
|
-
* This makes Beads visible to subagents via todoread
|
|
136
|
-
*/
|
|
137
|
-
async function pushBeadsToTodos(
|
|
138
|
-
worktree: string,
|
|
139
|
-
sessionId?: string,
|
|
140
|
-
filter = "open",
|
|
141
|
-
): Promise<string> {
|
|
142
|
-
try {
|
|
143
|
-
// Get beads tasks using br list (beads_rust)
|
|
144
|
-
const statusFilter = filter === "all" ? "" : `--status ${filter}`;
|
|
145
|
-
const { stdout } = await execAsync(`br list ${statusFilter} --json`, {
|
|
146
|
-
cwd: worktree,
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
let beadTasks: BeadTask[];
|
|
150
|
-
try {
|
|
151
|
-
beadTasks = JSON.parse(stdout);
|
|
152
|
-
} catch {
|
|
153
|
-
// br list might not support --json, parse text output
|
|
154
|
-
beadTasks = parseBeadListOutput(stdout);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (beadTasks.length === 0) {
|
|
158
|
-
return JSON.stringify(
|
|
159
|
-
{
|
|
160
|
-
success: true,
|
|
161
|
-
message: "No Beads tasks to sync",
|
|
162
|
-
synced: 0,
|
|
163
|
-
},
|
|
164
|
-
null,
|
|
165
|
-
2,
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Convert to OpenCode todo format
|
|
170
|
-
const todos: TodoItem[] = beadTasks.map((bead) => ({
|
|
171
|
-
id: bead.id,
|
|
172
|
-
content: `[Bead] ${bead.title}`,
|
|
173
|
-
status: mapBeadStatus(bead.status),
|
|
174
|
-
priority: mapBeadPriority(bead.priority),
|
|
175
|
-
blockedBy: bead.blockedBy || [],
|
|
176
|
-
blocks: bead.blocks || [],
|
|
177
|
-
beadId: bead.id,
|
|
178
|
-
}));
|
|
179
|
-
|
|
180
|
-
// Write to OpenCode todo storage
|
|
181
|
-
const targetSessionId = sessionId || (await getCurrentSessionId(worktree));
|
|
182
|
-
const todoPath = path.join(OPENCODE_TODO_DIR, `${targetSessionId}.json`);
|
|
183
|
-
|
|
184
|
-
await fs.mkdir(OPENCODE_TODO_DIR, { recursive: true });
|
|
185
|
-
await fs.writeFile(todoPath, JSON.stringify(todos, null, 2), "utf-8");
|
|
186
|
-
|
|
187
|
-
return JSON.stringify(
|
|
188
|
-
{
|
|
189
|
-
success: true,
|
|
190
|
-
message: `Synced ${todos.length} Beads tasks to OpenCode todos`,
|
|
191
|
-
synced: todos.length,
|
|
192
|
-
session_id: targetSessionId,
|
|
193
|
-
todo_path: todoPath,
|
|
194
|
-
tasks: todos.map((t) => ({
|
|
195
|
-
id: t.id,
|
|
196
|
-
content: t.content,
|
|
197
|
-
status: t.status,
|
|
198
|
-
blockedBy: t.blockedBy,
|
|
199
|
-
})),
|
|
200
|
-
},
|
|
201
|
-
null,
|
|
202
|
-
2,
|
|
203
|
-
);
|
|
204
|
-
} catch (error: any) {
|
|
205
|
-
return JSON.stringify(
|
|
206
|
-
{
|
|
207
|
-
success: false,
|
|
208
|
-
error: `Failed to push Beads to todos: ${error.message}`,
|
|
209
|
-
},
|
|
210
|
-
null,
|
|
211
|
-
2,
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Pull completed todos back to Beads
|
|
218
|
-
*/
|
|
219
|
-
async function pullTodosToBeads(
|
|
220
|
-
worktree: string,
|
|
221
|
-
sessionId?: string,
|
|
222
|
-
): Promise<string> {
|
|
223
|
-
try {
|
|
224
|
-
const targetSessionId = sessionId || (await getCurrentSessionId(worktree));
|
|
225
|
-
const todoPath = path.join(OPENCODE_TODO_DIR, `${targetSessionId}.json`);
|
|
226
|
-
|
|
227
|
-
let todos: TodoItem[];
|
|
228
|
-
try {
|
|
229
|
-
const content = await fs.readFile(todoPath, "utf-8");
|
|
230
|
-
todos = JSON.parse(content);
|
|
231
|
-
} catch {
|
|
232
|
-
return JSON.stringify(
|
|
233
|
-
{
|
|
234
|
-
success: true,
|
|
235
|
-
message: "No todos to pull",
|
|
236
|
-
updated: 0,
|
|
237
|
-
},
|
|
238
|
-
null,
|
|
239
|
-
2,
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Find completed todos that came from Beads
|
|
244
|
-
const completedBeadTodos = todos.filter(
|
|
245
|
-
(t) => t.beadId && t.status === "completed",
|
|
246
|
-
);
|
|
247
|
-
|
|
248
|
-
if (completedBeadTodos.length === 0) {
|
|
249
|
-
return JSON.stringify(
|
|
250
|
-
{
|
|
251
|
-
success: true,
|
|
252
|
-
message: "No completed Bead todos to update",
|
|
253
|
-
updated: 0,
|
|
254
|
-
},
|
|
255
|
-
null,
|
|
256
|
-
2,
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Update Beads status
|
|
261
|
-
const updated: string[] = [];
|
|
262
|
-
for (const todo of completedBeadTodos) {
|
|
263
|
-
try {
|
|
264
|
-
await execAsync(
|
|
265
|
-
`br close ${todo.beadId} --reason "Completed via todo"`,
|
|
266
|
-
{ cwd: worktree },
|
|
267
|
-
);
|
|
268
|
-
updated.push(todo.beadId!);
|
|
269
|
-
} catch {
|
|
270
|
-
// Bead might already be closed
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
return JSON.stringify(
|
|
275
|
-
{
|
|
276
|
-
success: true,
|
|
277
|
-
message: `Updated ${updated.length} Beads from completed todos`,
|
|
278
|
-
updated: updated.length,
|
|
279
|
-
bead_ids: updated,
|
|
280
|
-
},
|
|
281
|
-
null,
|
|
282
|
-
2,
|
|
283
|
-
);
|
|
284
|
-
} catch (error: any) {
|
|
285
|
-
return JSON.stringify(
|
|
286
|
-
{
|
|
287
|
-
success: false,
|
|
288
|
-
error: `Failed to pull todos to Beads: ${error.message}`,
|
|
289
|
-
},
|
|
290
|
-
null,
|
|
291
|
-
2,
|
|
292
|
-
);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Create a shared task list for cross-session swarm coordination
|
|
298
|
-
*/
|
|
299
|
-
async function createSharedList(
|
|
300
|
-
name?: string,
|
|
301
|
-
tasksJson?: string,
|
|
302
|
-
worktree?: string,
|
|
303
|
-
): Promise<string> {
|
|
304
|
-
if (!name) {
|
|
305
|
-
return JSON.stringify({ success: false, error: "name is required" });
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
try {
|
|
309
|
-
const listId = generateListId();
|
|
310
|
-
let tasks: TodoItem[] = [];
|
|
311
|
-
|
|
312
|
-
if (tasksJson) {
|
|
313
|
-
try {
|
|
314
|
-
tasks = JSON.parse(tasksJson);
|
|
315
|
-
} catch {
|
|
316
|
-
return JSON.stringify({
|
|
317
|
-
success: false,
|
|
318
|
-
error: "Invalid tasks JSON",
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
const sharedList: SharedTaskList = {
|
|
324
|
-
id: listId,
|
|
325
|
-
name,
|
|
326
|
-
created: new Date().toISOString(),
|
|
327
|
-
updated: new Date().toISOString(),
|
|
328
|
-
tasks,
|
|
329
|
-
};
|
|
330
|
-
|
|
331
|
-
// Get project ID from worktree
|
|
332
|
-
const projectDir = worktree || process.cwd();
|
|
333
|
-
const projectId = path.basename(projectDir);
|
|
334
|
-
const listPath = path.join(SHARED_TASKS_DIR, projectId, `${listId}.json`);
|
|
335
|
-
|
|
336
|
-
await fs.mkdir(path.dirname(listPath), { recursive: true });
|
|
337
|
-
await fs.writeFile(listPath, JSON.stringify(sharedList, null, 2), "utf-8");
|
|
338
|
-
|
|
339
|
-
return JSON.stringify(
|
|
340
|
-
{
|
|
341
|
-
success: true,
|
|
342
|
-
message: `Created shared task list: ${name}`,
|
|
343
|
-
list_id: listId,
|
|
344
|
-
path: listPath,
|
|
345
|
-
task_count: tasks.length,
|
|
346
|
-
usage: `Share with workers: SHARED_TASK_LIST_ID=${listId}`,
|
|
347
|
-
},
|
|
348
|
-
null,
|
|
349
|
-
2,
|
|
350
|
-
);
|
|
351
|
-
} catch (error: any) {
|
|
352
|
-
return JSON.stringify(
|
|
353
|
-
{
|
|
354
|
-
success: false,
|
|
355
|
-
error: `Failed to create shared list: ${error.message}`,
|
|
356
|
-
},
|
|
357
|
-
null,
|
|
358
|
-
2,
|
|
359
|
-
);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* Get a shared task list by ID
|
|
365
|
-
*/
|
|
366
|
-
async function getSharedList(listId?: string): Promise<string> {
|
|
367
|
-
if (!listId) {
|
|
368
|
-
return JSON.stringify({ success: false, error: "list_id is required" });
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
try {
|
|
372
|
-
// Search in all project directories
|
|
373
|
-
const projectDirs = await fs.readdir(SHARED_TASKS_DIR).catch(() => []);
|
|
374
|
-
|
|
375
|
-
for (const projectId of projectDirs) {
|
|
376
|
-
const listPath = path.join(SHARED_TASKS_DIR, projectId, `${listId}.json`);
|
|
377
|
-
try {
|
|
378
|
-
const content = await fs.readFile(listPath, "utf-8");
|
|
379
|
-
const list: SharedTaskList = JSON.parse(content);
|
|
380
|
-
|
|
381
|
-
// Calculate stats
|
|
382
|
-
const completed = list.tasks.filter(
|
|
383
|
-
(t) => t.status === "completed",
|
|
384
|
-
).length;
|
|
385
|
-
const pending = list.tasks.filter((t) => t.status === "pending").length;
|
|
386
|
-
const inProgress = list.tasks.filter(
|
|
387
|
-
(t) => t.status === "in_progress",
|
|
388
|
-
).length;
|
|
389
|
-
|
|
390
|
-
return JSON.stringify(
|
|
391
|
-
{
|
|
392
|
-
success: true,
|
|
393
|
-
list,
|
|
394
|
-
stats: {
|
|
395
|
-
total: list.tasks.length,
|
|
396
|
-
completed,
|
|
397
|
-
pending,
|
|
398
|
-
in_progress: inProgress,
|
|
399
|
-
progress_percent: Math.round(
|
|
400
|
-
(completed / list.tasks.length) * 100,
|
|
401
|
-
),
|
|
402
|
-
},
|
|
403
|
-
},
|
|
404
|
-
null,
|
|
405
|
-
2,
|
|
406
|
-
);
|
|
407
|
-
} catch {
|
|
408
|
-
continue;
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
return JSON.stringify({
|
|
413
|
-
success: false,
|
|
414
|
-
error: `Shared list not found: ${listId}`,
|
|
415
|
-
});
|
|
416
|
-
} catch (error: any) {
|
|
417
|
-
return JSON.stringify(
|
|
418
|
-
{
|
|
419
|
-
success: false,
|
|
420
|
-
error: `Failed to get shared list: ${error.message}`,
|
|
421
|
-
},
|
|
422
|
-
null,
|
|
423
|
-
2,
|
|
424
|
-
);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
/**
|
|
429
|
-
* Update tasks in a shared list
|
|
430
|
-
*/
|
|
431
|
-
async function updateSharedList(
|
|
432
|
-
listId?: string,
|
|
433
|
-
tasksJson?: string,
|
|
434
|
-
): Promise<string> {
|
|
435
|
-
if (!listId) {
|
|
436
|
-
return JSON.stringify({ success: false, error: "list_id is required" });
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
try {
|
|
440
|
-
// Find the list
|
|
441
|
-
const projectDirs = await fs.readdir(SHARED_TASKS_DIR).catch(() => []);
|
|
442
|
-
let listPath: string | null = null;
|
|
443
|
-
let existingList: SharedTaskList | null = null;
|
|
444
|
-
|
|
445
|
-
for (const projectId of projectDirs) {
|
|
446
|
-
const candidatePath = path.join(
|
|
447
|
-
SHARED_TASKS_DIR,
|
|
448
|
-
projectId,
|
|
449
|
-
`${listId}.json`,
|
|
450
|
-
);
|
|
451
|
-
try {
|
|
452
|
-
const content = await fs.readFile(candidatePath, "utf-8");
|
|
453
|
-
existingList = JSON.parse(content);
|
|
454
|
-
listPath = candidatePath;
|
|
455
|
-
break;
|
|
456
|
-
} catch {
|
|
457
|
-
continue;
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
if (!listPath || !existingList) {
|
|
462
|
-
return JSON.stringify({
|
|
463
|
-
success: false,
|
|
464
|
-
error: `Shared list not found: ${listId}`,
|
|
465
|
-
});
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// Parse and merge updates
|
|
469
|
-
if (tasksJson) {
|
|
470
|
-
try {
|
|
471
|
-
const updates: Partial<TodoItem>[] = JSON.parse(tasksJson);
|
|
472
|
-
|
|
473
|
-
for (const update of updates) {
|
|
474
|
-
if (!update.id) continue;
|
|
475
|
-
|
|
476
|
-
const existingTask = existingList.tasks.find(
|
|
477
|
-
(t) => t.id === update.id,
|
|
478
|
-
);
|
|
479
|
-
if (existingTask) {
|
|
480
|
-
Object.assign(existingTask, update);
|
|
481
|
-
} else {
|
|
482
|
-
existingList.tasks.push(update as TodoItem);
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
} catch {
|
|
486
|
-
return JSON.stringify({
|
|
487
|
-
success: false,
|
|
488
|
-
error: "Invalid tasks JSON",
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
existingList.updated = new Date().toISOString();
|
|
494
|
-
await fs.writeFile(
|
|
495
|
-
listPath,
|
|
496
|
-
JSON.stringify(existingList, null, 2),
|
|
497
|
-
"utf-8",
|
|
498
|
-
);
|
|
499
|
-
|
|
500
|
-
return JSON.stringify(
|
|
501
|
-
{
|
|
502
|
-
success: true,
|
|
503
|
-
message: `Updated shared list: ${existingList.name}`,
|
|
504
|
-
list_id: listId,
|
|
505
|
-
task_count: existingList.tasks.length,
|
|
506
|
-
},
|
|
507
|
-
null,
|
|
508
|
-
2,
|
|
509
|
-
);
|
|
510
|
-
} catch (error: any) {
|
|
511
|
-
return JSON.stringify(
|
|
512
|
-
{
|
|
513
|
-
success: false,
|
|
514
|
-
error: `Failed to update shared list: ${error.message}`,
|
|
515
|
-
},
|
|
516
|
-
null,
|
|
517
|
-
2,
|
|
518
|
-
);
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
/**
|
|
523
|
-
* List all shared task lists for this project
|
|
524
|
-
*/
|
|
525
|
-
async function listSharedLists(worktree?: string): Promise<string> {
|
|
526
|
-
try {
|
|
527
|
-
const projectDir = worktree || process.cwd();
|
|
528
|
-
const projectId = path.basename(projectDir);
|
|
529
|
-
const projectListDir = path.join(SHARED_TASKS_DIR, projectId);
|
|
530
|
-
|
|
531
|
-
let files: string[];
|
|
532
|
-
try {
|
|
533
|
-
files = await fs.readdir(projectListDir);
|
|
534
|
-
} catch {
|
|
535
|
-
return JSON.stringify(
|
|
536
|
-
{
|
|
537
|
-
success: true,
|
|
538
|
-
lists: [],
|
|
539
|
-
message: "No shared task lists found for this project",
|
|
540
|
-
},
|
|
541
|
-
null,
|
|
542
|
-
2,
|
|
543
|
-
);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
const lists: Array<{
|
|
547
|
-
id: string;
|
|
548
|
-
name: string;
|
|
549
|
-
task_count: number;
|
|
550
|
-
completed: number;
|
|
551
|
-
updated: string;
|
|
552
|
-
}> = [];
|
|
553
|
-
|
|
554
|
-
for (const file of files) {
|
|
555
|
-
if (!file.endsWith(".json")) continue;
|
|
556
|
-
|
|
557
|
-
try {
|
|
558
|
-
const content = await fs.readFile(
|
|
559
|
-
path.join(projectListDir, file),
|
|
560
|
-
"utf-8",
|
|
561
|
-
);
|
|
562
|
-
const list: SharedTaskList = JSON.parse(content);
|
|
563
|
-
lists.push({
|
|
564
|
-
id: list.id,
|
|
565
|
-
name: list.name,
|
|
566
|
-
task_count: list.tasks.length,
|
|
567
|
-
completed: list.tasks.filter((t) => t.status === "completed").length,
|
|
568
|
-
updated: list.updated,
|
|
569
|
-
});
|
|
570
|
-
} catch {
|
|
571
|
-
continue;
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
return JSON.stringify(
|
|
576
|
-
{
|
|
577
|
-
success: true,
|
|
578
|
-
lists,
|
|
579
|
-
count: lists.length,
|
|
580
|
-
},
|
|
581
|
-
null,
|
|
582
|
-
2,
|
|
583
|
-
);
|
|
584
|
-
} catch (error: any) {
|
|
585
|
-
return JSON.stringify(
|
|
586
|
-
{
|
|
587
|
-
success: false,
|
|
588
|
-
error: `Failed to list shared lists: ${error.message}`,
|
|
589
|
-
},
|
|
590
|
-
null,
|
|
591
|
-
2,
|
|
592
|
-
);
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
// Helper functions
|
|
597
|
-
|
|
598
|
-
function parseBeadListOutput(output: string): BeadTask[] {
|
|
599
|
-
const lines = output.trim().split("\n").filter(Boolean);
|
|
600
|
-
const tasks: BeadTask[] = [];
|
|
601
|
-
|
|
602
|
-
for (const line of lines) {
|
|
603
|
-
// Parse lines like: #123 [open] (P2) Task title
|
|
604
|
-
const match = line.match(/^#?(\S+)\s+\[(\w+)\]\s+(?:\(P(\d)\))?\s*(.+)$/);
|
|
605
|
-
if (match) {
|
|
606
|
-
tasks.push({
|
|
607
|
-
id: match[1],
|
|
608
|
-
status: match[2] as BeadTask["status"],
|
|
609
|
-
priority: match[3] ? Number.parseInt(match[3]) : 2,
|
|
610
|
-
title: match[4],
|
|
611
|
-
labels: [],
|
|
612
|
-
blockedBy: [],
|
|
613
|
-
blocks: [],
|
|
614
|
-
});
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
return tasks;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
function mapBeadStatus(
|
|
622
|
-
status: string,
|
|
623
|
-
): "pending" | "in_progress" | "completed" | "cancelled" {
|
|
624
|
-
switch (status) {
|
|
625
|
-
case "open":
|
|
626
|
-
return "pending";
|
|
627
|
-
case "in_progress":
|
|
628
|
-
return "in_progress";
|
|
629
|
-
case "closed":
|
|
630
|
-
return "completed";
|
|
631
|
-
default:
|
|
632
|
-
return "pending";
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
function mapBeadPriority(priority: number): "high" | "medium" | "low" {
|
|
637
|
-
if (priority <= 1) return "high";
|
|
638
|
-
if (priority <= 2) return "medium";
|
|
639
|
-
return "low";
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
async function getCurrentSessionId(worktree: string): Promise<string> {
|
|
643
|
-
// Try to get from environment or generate based on project
|
|
644
|
-
const envSessionId = process.env.OPENCODE_SESSION_ID;
|
|
645
|
-
if (envSessionId) return envSessionId;
|
|
646
|
-
|
|
647
|
-
// Generate a session-like ID based on project path
|
|
648
|
-
const projectName = path.basename(worktree);
|
|
649
|
-
const timestamp = Date.now().toString(36);
|
|
650
|
-
return `ses_${timestamp}_${projectName.slice(0, 10)}`;
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
function generateListId(): string {
|
|
654
|
-
const timestamp = Date.now().toString(36);
|
|
655
|
-
const random = Math.random().toString(36).slice(2, 8);
|
|
656
|
-
return `shl_${timestamp}${random}`;
|
|
657
|
-
}
|