@wrongstack/tools 0.285.0 → 0.287.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.
Files changed (76) hide show
  1. package/README.md +21 -2
  2. package/dist/_edit-match.d.ts.map +1 -1
  3. package/dist/batch-tool-use.d.ts +1 -1
  4. package/dist/batch-tool-use.d.ts.map +1 -1
  5. package/dist/batch-tool-use.js +26 -8
  6. package/dist/batch-tool-use.js.map +2 -2
  7. package/dist/browser/artifacts.d.ts +13 -0
  8. package/dist/browser/artifacts.d.ts.map +1 -0
  9. package/dist/browser/index.d.ts +7 -0
  10. package/dist/browser/index.d.ts.map +1 -0
  11. package/dist/browser/index.js +1264 -0
  12. package/dist/browser/index.js.map +7 -0
  13. package/dist/browser/manager.d.ts +57 -0
  14. package/dist/browser/manager.d.ts.map +1 -0
  15. package/dist/browser/network-guard-proxy.d.ts +23 -0
  16. package/dist/browser/network-guard-proxy.d.ts.map +1 -0
  17. package/dist/browser/security.d.ts +29 -0
  18. package/dist/browser/security.d.ts.map +1 -0
  19. package/dist/browser/tools.d.ts +62 -0
  20. package/dist/browser/tools.d.ts.map +1 -0
  21. package/dist/browser/types.d.ts +60 -0
  22. package/dist/browser/types.d.ts.map +1 -0
  23. package/dist/builtin.d.ts +4 -3
  24. package/dist/builtin.d.ts.map +1 -1
  25. package/dist/builtin.js +2609 -525
  26. package/dist/builtin.js.map +4 -4
  27. package/dist/codebase-index/go-parser.d.ts.map +1 -1
  28. package/dist/codebase-index/index.js +190 -96
  29. package/dist/codebase-index/index.js.map +4 -4
  30. package/dist/codebase-index/py-parser.d.ts.map +1 -1
  31. package/dist/codebase-index/rs-parser.d.ts.map +1 -1
  32. package/dist/codebase-index/worker.js +188 -94
  33. package/dist/codebase-index/worker.js.map +4 -4
  34. package/dist/e2e.d.ts +56 -0
  35. package/dist/e2e.d.ts.map +1 -0
  36. package/dist/edit.d.ts.map +1 -1
  37. package/dist/edit.js +36 -12
  38. package/dist/edit.js.map +3 -3
  39. package/dist/exec.js +1 -1
  40. package/dist/exec.js.map +2 -2
  41. package/dist/index.d.ts +3 -0
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +2895 -511
  44. package/dist/index.js.map +4 -4
  45. package/dist/kanban.d.ts +7 -2
  46. package/dist/kanban.d.ts.map +1 -1
  47. package/dist/kanban.js +158 -19
  48. package/dist/kanban.js.map +4 -4
  49. package/dist/next-steps.d.ts +6 -2
  50. package/dist/next-steps.d.ts.map +1 -1
  51. package/dist/next-steps.js +3 -3
  52. package/dist/next-steps.js.map +2 -2
  53. package/dist/pack.js +2625 -525
  54. package/dist/pack.js.map +4 -4
  55. package/dist/plan.d.ts.map +1 -1
  56. package/dist/plan.js +154 -4
  57. package/dist/plan.js.map +4 -4
  58. package/dist/search.d.ts.map +1 -1
  59. package/dist/search.js +8 -2
  60. package/dist/search.js.map +2 -2
  61. package/dist/session-kanban.d.ts +38 -0
  62. package/dist/session-kanban.d.ts.map +1 -0
  63. package/dist/session-kanban.js +489 -0
  64. package/dist/session-kanban.js.map +7 -0
  65. package/dist/task.d.ts.map +1 -1
  66. package/dist/task.js +163 -4
  67. package/dist/task.js.map +4 -4
  68. package/dist/todo.d.ts.map +1 -1
  69. package/dist/todo.js +155 -7
  70. package/dist/todo.js.map +4 -4
  71. package/dist/tool-icon-map.d.ts +2 -1
  72. package/dist/tool-icon-map.d.ts.map +1 -1
  73. package/dist/tool-icons.d.ts.map +1 -1
  74. package/dist/tool-icons.js +7 -1
  75. package/dist/tool-icons.js.map +2 -2
  76. package/package.json +12 -3
@@ -0,0 +1,489 @@
1
+ // src/session-kanban.ts
2
+ import { watch } from "node:fs";
3
+ import { basename, dirname } from "node:path";
4
+ import {
5
+ deserializeTaskGraph,
6
+ loadPlan,
7
+ loadTasks,
8
+ mutatePlan,
9
+ mutateTasks
10
+ } from "@wrongstack/core";
11
+ import {
12
+ createBoard,
13
+ getBoard,
14
+ listBoards,
15
+ removeBoard,
16
+ syncBoardFromTaskGraph,
17
+ updateBoard
18
+ } from "@wrongstack/kanban";
19
+ var SESSION_BOARD_TAG = "session-work";
20
+ var MIRROR_DISABLED_ENV = "WRONGSTACK_KANBAN_TASK_MIRROR";
21
+ var SESSION_KANBAN_COLUMNS = [
22
+ { id: "todo", title: "Todo", order: 0, wipLimit: 0, color: "#2563eb" },
23
+ { id: "in-progress", title: "Running", order: 1, wipLimit: 1, color: "#d97706" },
24
+ { id: "review", title: "Preview", order: 2, wipLimit: 0, color: "#7c3aed" },
25
+ { id: "done", title: "Done", order: 3, wipLimit: 0, color: "#16a34a" }
26
+ ];
27
+ var boardQueue = /* @__PURE__ */ new Map();
28
+ var boardEnsures = /* @__PURE__ */ new Map();
29
+ var bindings = /* @__PURE__ */ new WeakMap();
30
+ var suppressedTodoMirrors = /* @__PURE__ */ new WeakSet();
31
+ var activeSessionBoards = /* @__PURE__ */ new Map();
32
+ function boardKey(projectRoot, sessionId) {
33
+ return `${projectRoot}\0${sessionId}`;
34
+ }
35
+ function sessionTag(sessionId) {
36
+ return `session:${sessionId}`;
37
+ }
38
+ function sessionBoardTitle(sessionId) {
39
+ const leaf = sessionId.split(/[\\/]/).filter(Boolean).pop() ?? sessionId;
40
+ return `Session ${leaf.slice(0, 12)}`;
41
+ }
42
+ function sessionBoardTags(sessionId) {
43
+ return ["session", SESSION_BOARD_TAG, sessionTag(sessionId)];
44
+ }
45
+ function sessionIdFromTags(tags) {
46
+ const tag = tags?.find((candidate) => candidate.startsWith("session:"));
47
+ return tag?.slice("session:".length) || null;
48
+ }
49
+ function isOwnedSessionBoard(tags) {
50
+ return Boolean(tags?.includes(SESSION_BOARD_TAG) && sessionIdFromTags(tags));
51
+ }
52
+ function retainActiveSessionBoard(projectRoot, sessionId) {
53
+ const key = boardKey(projectRoot, sessionId);
54
+ activeSessionBoards.set(key, (activeSessionBoards.get(key) ?? 0) + 1);
55
+ }
56
+ function releaseActiveSessionBoard(projectRoot, sessionId) {
57
+ const key = boardKey(projectRoot, sessionId);
58
+ const remaining = (activeSessionBoards.get(key) ?? 0) - 1;
59
+ if (remaining > 0) activeSessionBoards.set(key, remaining);
60
+ else activeSessionBoards.delete(key);
61
+ }
62
+ function isSessionBoardActive(projectRoot, sessionId) {
63
+ return (activeSessionBoards.get(boardKey(projectRoot, sessionId)) ?? 0) > 0;
64
+ }
65
+ function sameColumns(columns) {
66
+ return columns.length === SESSION_KANBAN_COLUMNS.length && columns.every((column, index) => column.id === SESSION_KANBAN_COLUMNS[index]?.id);
67
+ }
68
+ async function ensureSessionKanbanBoard(projectRoot, sessionId) {
69
+ if (!projectRoot || !sessionId || process.env[MIRROR_DISABLED_ENV] === "0") return null;
70
+ const key = boardKey(projectRoot, sessionId);
71
+ const inFlight = boardEnsures.get(key);
72
+ if (inFlight) return inFlight;
73
+ const promise = (async () => {
74
+ const summary = (await listBoards(projectRoot)).find(
75
+ (board2) => board2.tags?.includes(sessionTag(sessionId))
76
+ );
77
+ let board = summary ? await getBoard(projectRoot, summary.id) : null;
78
+ if (!board) {
79
+ return createBoard(projectRoot, {
80
+ title: sessionBoardTitle(sessionId),
81
+ description: "Live session work: todos, tasks, and plan items.",
82
+ tags: sessionBoardTags(sessionId),
83
+ columns: SESSION_KANBAN_COLUMNS,
84
+ generatedBy: `session-kanban:${sessionId}`
85
+ });
86
+ }
87
+ if (!sameColumns(board.columns) || !board.tags?.includes(SESSION_BOARD_TAG)) {
88
+ board = await updateBoard(projectRoot, board.id, {
89
+ title: sessionBoardTitle(sessionId),
90
+ description: "Live session work: todos, tasks, and plan items.",
91
+ tags: [.../* @__PURE__ */ new Set([...board.tags ?? [], ...sessionBoardTags(sessionId)])],
92
+ columns: SESSION_KANBAN_COLUMNS
93
+ }) ?? board;
94
+ }
95
+ return board;
96
+ })();
97
+ boardEnsures.set(key, promise);
98
+ try {
99
+ return await promise;
100
+ } finally {
101
+ boardEnsures.delete(key);
102
+ }
103
+ }
104
+ function enqueueBoardWork(projectRoot, sessionId, work) {
105
+ const key = boardKey(projectRoot, sessionId);
106
+ const previous = boardQueue.get(key) ?? Promise.resolve();
107
+ const result = previous.catch(() => void 0).then(work);
108
+ const tail = result.then(
109
+ () => void 0,
110
+ () => void 0
111
+ );
112
+ boardQueue.set(key, tail);
113
+ void tail.then(() => {
114
+ if (boardQueue.get(key) === tail) boardQueue.delete(key);
115
+ });
116
+ return result;
117
+ }
118
+ async function removeEmptySessionBoard(projectRoot, boardId, sessionId) {
119
+ return enqueueBoardWork(projectRoot, sessionId, async () => {
120
+ if (isSessionBoardActive(projectRoot, sessionId)) return null;
121
+ const board = await getBoard(projectRoot, boardId);
122
+ if (!board || board.tasks.length > 0 || !isOwnedSessionBoard(board.tags)) return null;
123
+ if (sessionIdFromTags(board.tags) !== sessionId) return null;
124
+ return await removeBoard(projectRoot, board.id) ? board.id : null;
125
+ });
126
+ }
127
+ async function cleanupSessionKanbanBoardIfEmpty(projectRoot, sessionId) {
128
+ if (!projectRoot || !sessionId || process.env[MIRROR_DISABLED_ENV] === "0") return [];
129
+ if (isSessionBoardActive(projectRoot, sessionId)) return [];
130
+ const candidates = (await listBoards(projectRoot)).filter(
131
+ (board) => board.taskCount === 0 && isOwnedSessionBoard(board.tags) && sessionIdFromTags(board.tags) === sessionId
132
+ );
133
+ const removed = await Promise.all(
134
+ candidates.map((board) => removeEmptySessionBoard(projectRoot, board.id, sessionId))
135
+ );
136
+ return removed.filter((boardId) => Boolean(boardId));
137
+ }
138
+ async function cleanupEmptySessionKanbanBoards(projectRoot, activeSessionId = "") {
139
+ if (!projectRoot || process.env[MIRROR_DISABLED_ENV] === "0") return [];
140
+ const candidates = (await listBoards(projectRoot)).flatMap((board) => {
141
+ const ownerSessionId = sessionIdFromTags(board.tags);
142
+ return board.taskCount === 0 && isOwnedSessionBoard(board.tags) && ownerSessionId && ownerSessionId !== activeSessionId && !isSessionBoardActive(projectRoot, ownerSessionId) ? [{ boardId: board.id, sessionId: ownerSessionId }] : [];
143
+ });
144
+ const removed = await Promise.all(
145
+ candidates.map(
146
+ ({ boardId, sessionId }) => removeEmptySessionBoard(projectRoot, boardId, sessionId)
147
+ )
148
+ );
149
+ return removed.filter((boardId) => Boolean(boardId));
150
+ }
151
+ async function projectGraph(projectRoot, sessionId, graph, sourceSystem) {
152
+ if (!projectRoot || !sessionId || process.env[MIRROR_DISABLED_ENV] === "0") return null;
153
+ return enqueueBoardWork(projectRoot, sessionId, async () => {
154
+ const board = await ensureSessionKanbanBoard(projectRoot, sessionId);
155
+ if (!board) return null;
156
+ const result = await syncBoardFromTaskGraph(
157
+ projectRoot,
158
+ board.id,
159
+ deserializeTaskGraph(graph),
160
+ {
161
+ sourceSystem,
162
+ tags: [.../* @__PURE__ */ new Set([...board.tags ?? [], ...sessionBoardTags(sessionId)])],
163
+ archiveMissingTasks: true,
164
+ includeCompletedTasks: true
165
+ }
166
+ );
167
+ return result?.board ?? null;
168
+ });
169
+ }
170
+ function todoListToSerializedGraph(todos, sessionId) {
171
+ const nodes = todos.map((todo, index) => ({
172
+ id: todo.id,
173
+ title: todo.content,
174
+ description: todo.activeForm ?? "",
175
+ type: "chore",
176
+ priority: "medium",
177
+ status: todo.status,
178
+ createdAt: index,
179
+ updatedAt: index
180
+ }));
181
+ return {
182
+ id: `todo:${sessionId}`,
183
+ specId: `todo:${sessionId}`,
184
+ title: "Session todos",
185
+ nodes,
186
+ edges: [],
187
+ rootNodes: nodes.map((node) => node.id),
188
+ createdAt: 0,
189
+ updatedAt: 0
190
+ };
191
+ }
192
+ function taskFileToSerializedGraph(tasks, sessionId) {
193
+ const ids = new Set(tasks.map((task) => task.id));
194
+ const nodes = tasks.map((task, index) => ({
195
+ id: task.id,
196
+ title: task.title,
197
+ description: task.description ?? "",
198
+ type: task.type,
199
+ priority: task.priority,
200
+ status: task.status,
201
+ ...task.assignee ? { assignee: task.assignee } : {},
202
+ ...task.estimateHours !== void 0 ? { estimateHours: task.estimateHours } : {},
203
+ createdAt: index,
204
+ updatedAt: index
205
+ }));
206
+ const edges = tasks.flatMap(
207
+ (task) => (task.dependsOn ?? []).filter((dependency) => ids.has(dependency)).map((dependency) => ({
208
+ id: `${dependency}->${task.id}`,
209
+ from: dependency,
210
+ to: task.id,
211
+ type: "depends_on"
212
+ }))
213
+ );
214
+ const hasIncoming = new Set(edges.map((edge) => edge.to));
215
+ const rootNodes = nodes.filter((node) => !hasIncoming.has(node.id)).map((node) => node.id);
216
+ return {
217
+ // Keep the historical graph id so existing mirrored task cards are reused.
218
+ id: `session:${sessionId}`,
219
+ specId: `session:${sessionId}`,
220
+ title: "Session tasks",
221
+ nodes,
222
+ edges,
223
+ rootNodes: rootNodes.length ? rootNodes : nodes[0] ? [nodes[0].id] : [],
224
+ createdAt: 0,
225
+ updatedAt: 0
226
+ };
227
+ }
228
+ var PLAN_STATUS_TO_TASK = {
229
+ open: "pending",
230
+ in_progress: "in_progress",
231
+ done: "completed"
232
+ };
233
+ function planFileToSerializedGraph(items, sessionId) {
234
+ const nodes = items.map((item, index) => ({
235
+ id: item.id,
236
+ title: item.title,
237
+ description: item.details ?? "",
238
+ type: "chore",
239
+ priority: "medium",
240
+ status: PLAN_STATUS_TO_TASK[item.status],
241
+ createdAt: index,
242
+ updatedAt: index
243
+ }));
244
+ return {
245
+ id: `plan:${sessionId}`,
246
+ specId: `plan:${sessionId}`,
247
+ title: "Session plan",
248
+ nodes,
249
+ edges: [],
250
+ rootNodes: nodes.map((node) => node.id),
251
+ createdAt: 0,
252
+ updatedAt: 0
253
+ };
254
+ }
255
+ function projectSessionTodosToKanban(projectRoot, todos, sessionId) {
256
+ return projectGraph(
257
+ projectRoot,
258
+ sessionId,
259
+ todoListToSerializedGraph(todos, sessionId),
260
+ "session-todo"
261
+ );
262
+ }
263
+ function projectSessionTasksToKanban(projectRoot, tasks, sessionId) {
264
+ return projectGraph(
265
+ projectRoot,
266
+ sessionId,
267
+ taskFileToSerializedGraph(tasks, sessionId),
268
+ "session-task"
269
+ );
270
+ }
271
+ function projectSessionPlanToKanban(projectRoot, items, sessionId) {
272
+ return projectGraph(
273
+ projectRoot,
274
+ sessionId,
275
+ planFileToSerializedGraph(items, sessionId),
276
+ "session-plan"
277
+ );
278
+ }
279
+ function fireAndForget(work) {
280
+ void work.catch(() => {
281
+ });
282
+ }
283
+ function mirrorSessionTodosToKanban(projectRoot, todos, sessionId) {
284
+ fireAndForget(projectSessionTodosToKanban(projectRoot, todos, sessionId));
285
+ }
286
+ function mirrorSessionTasksToKanban(projectRoot, tasks, sessionId) {
287
+ fireAndForget(projectSessionTasksToKanban(projectRoot, tasks, sessionId));
288
+ }
289
+ function mirrorSessionPlanToKanban(projectRoot, items, sessionId) {
290
+ fireAndForget(projectSessionPlanToKanban(projectRoot, items, sessionId));
291
+ }
292
+ function attachSessionKanbanMirror(context) {
293
+ const existing = bindings.get(context);
294
+ if (existing) return existing;
295
+ const attachedProjectRoot = context.projectRoot ?? "";
296
+ let registeredSessionId = "";
297
+ const syncActiveSessionRegistration = () => {
298
+ if (!attachedProjectRoot) return;
299
+ const currentSessionId = context.session?.id ?? "";
300
+ if (currentSessionId === registeredSessionId) return;
301
+ if (registeredSessionId) {
302
+ releaseActiveSessionBoard(attachedProjectRoot, registeredSessionId);
303
+ fireAndForget(cleanupSessionKanbanBoardIfEmpty(attachedProjectRoot, registeredSessionId));
304
+ }
305
+ registeredSessionId = currentSessionId;
306
+ if (registeredSessionId) {
307
+ retainActiveSessionBoard(attachedProjectRoot, registeredSessionId);
308
+ }
309
+ };
310
+ syncActiveSessionRegistration();
311
+ let watcher = null;
312
+ let watchedDir = "";
313
+ let timer = null;
314
+ const sessionId = () => context.session?.id ?? "";
315
+ const refreshFiles = async () => {
316
+ const id = sessionId();
317
+ if (!id) return;
318
+ const planPath = context.meta["plan.path"];
319
+ if (typeof planPath === "string" && planPath) {
320
+ const plan = await loadPlan(planPath);
321
+ if (plan) await projectSessionPlanToKanban(context.projectRoot, plan.items, id);
322
+ }
323
+ const taskPath = context.meta["task.path"];
324
+ if (typeof taskPath === "string" && taskPath) {
325
+ const tasks = await loadTasks(taskPath);
326
+ if (tasks) await projectSessionTasksToKanban(context.projectRoot, tasks.tasks, id);
327
+ }
328
+ };
329
+ const configureWatcher = () => {
330
+ const planPath = context.meta["plan.path"];
331
+ const taskPath = context.meta["task.path"];
332
+ const candidate = typeof planPath === "string" && planPath ? dirname(planPath) : typeof taskPath === "string" && taskPath ? dirname(taskPath) : "";
333
+ if (!candidate || candidate === watchedDir) return;
334
+ watcher?.close();
335
+ watcher = null;
336
+ watchedDir = candidate;
337
+ try {
338
+ watcher = watch(candidate, { persistent: false }, (_event, filename) => {
339
+ const name = filename?.toString();
340
+ const currentPlanPath = context.meta["plan.path"];
341
+ const currentTaskPath = context.meta["task.path"];
342
+ const planName = typeof currentPlanPath === "string" ? basename(currentPlanPath) : "";
343
+ const taskName = typeof currentTaskPath === "string" ? basename(currentTaskPath) : "";
344
+ if (name && name !== planName && name !== taskName) return;
345
+ if (timer) clearTimeout(timer);
346
+ timer = setTimeout(() => fireAndForget(refreshFiles()), 60);
347
+ });
348
+ watcher.on("error", () => watcher?.close());
349
+ } catch {
350
+ watcher = null;
351
+ watchedDir = "";
352
+ }
353
+ };
354
+ const unsubscribe = context.state.onChange((change) => {
355
+ if (change.kind === "todos_replaced" && !suppressedTodoMirrors.has(context)) {
356
+ mirrorSessionTodosToKanban(
357
+ context.projectRoot,
358
+ change.completedSnapshot ?? change.todos,
359
+ sessionId()
360
+ );
361
+ return;
362
+ }
363
+ if (change.kind === "meta_set" && (change.key === "plan.path" || change.key === "task.path")) {
364
+ syncActiveSessionRegistration();
365
+ configureWatcher();
366
+ fireAndForget(ensureSessionKanbanBoard(context.projectRoot, sessionId()));
367
+ fireAndForget(refreshFiles());
368
+ }
369
+ });
370
+ configureWatcher();
371
+ const detach = () => {
372
+ unsubscribe();
373
+ if (timer) clearTimeout(timer);
374
+ watcher?.close();
375
+ bindings.delete(context);
376
+ if (attachedProjectRoot && registeredSessionId) {
377
+ releaseActiveSessionBoard(attachedProjectRoot, registeredSessionId);
378
+ fireAndForget(cleanupSessionKanbanBoardIfEmpty(attachedProjectRoot, registeredSessionId));
379
+ registeredSessionId = "";
380
+ }
381
+ };
382
+ bindings.set(context, detach);
383
+ return detach;
384
+ }
385
+ async function hydrateSessionKanban(context) {
386
+ const id = context.session?.id ?? "";
387
+ if (!id) return null;
388
+ await cleanupEmptySessionKanbanBoards(context.projectRoot, id);
389
+ let board = await ensureSessionKanbanBoard(context.projectRoot, id);
390
+ if (context.todos.length) {
391
+ board = await projectSessionTodosToKanban(context.projectRoot, context.todos, id);
392
+ }
393
+ const planPath = context.meta["plan.path"];
394
+ if (typeof planPath === "string" && planPath) {
395
+ const plan = await loadPlan(planPath);
396
+ if (plan) board = await projectSessionPlanToKanban(context.projectRoot, plan.items, id);
397
+ }
398
+ const taskPath = context.meta["task.path"];
399
+ if (typeof taskPath === "string" && taskPath) {
400
+ const tasks = await loadTasks(taskPath);
401
+ if (tasks) board = await projectSessionTasksToKanban(context.projectRoot, tasks.tasks, id);
402
+ }
403
+ return board;
404
+ }
405
+ function sourceStatus(task) {
406
+ if (task.status === "completed") return "completed";
407
+ if (task.status === "in_progress") return "in_progress";
408
+ if (task.status === "review") return "review";
409
+ if (task.status === "blocked") return "blocked";
410
+ if (task.status === "failed") return "failed";
411
+ return "pending";
412
+ }
413
+ async function applySessionKanbanTaskToSource(context, task, options = {}) {
414
+ const originId = task.origin?.taskId;
415
+ const graphId = task.origin?.graphId ?? "";
416
+ if (!originId) return { source: null };
417
+ if (task.origin?.system === "session-todo" || graphId.startsWith("todo:")) {
418
+ const next = options.remove ? context.todos.filter((todo) => todo.id !== originId) : context.todos.map(
419
+ (todo) => todo.id === originId ? {
420
+ ...todo,
421
+ content: task.title,
422
+ status: sourceStatus(task) === "completed" ? "completed" : sourceStatus(task) === "in_progress" || sourceStatus(task) === "review" ? "in_progress" : "pending"
423
+ } : todo
424
+ );
425
+ suppressedTodoMirrors.add(context);
426
+ try {
427
+ context.state.replaceTodos(next);
428
+ } finally {
429
+ suppressedTodoMirrors.delete(context);
430
+ }
431
+ return { source: "todo", todos: [...context.todos] };
432
+ }
433
+ const id = context.session?.id ?? "";
434
+ if (task.origin?.system === "session-plan" || graphId.startsWith("plan:")) {
435
+ const planPath = context.meta["plan.path"];
436
+ if (typeof planPath !== "string" || !planPath) return { source: "plan" };
437
+ const plan = await mutatePlan(planPath, id, (file) => ({
438
+ ...file,
439
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
440
+ items: options.remove ? file.items.filter((item) => item.id !== originId) : file.items.map(
441
+ (item) => item.id === originId ? {
442
+ ...item,
443
+ title: task.title,
444
+ details: task.description,
445
+ status: task.status === "completed" ? "done" : task.status === "in_progress" || task.status === "review" ? "in_progress" : "open",
446
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
447
+ } : item
448
+ )
449
+ }));
450
+ return { source: "plan", plan };
451
+ }
452
+ if (task.origin?.system === "session-task" || task.origin?.system === "session" || graphId.startsWith("session:")) {
453
+ const taskPath = context.meta["task.path"];
454
+ if (typeof taskPath !== "string" || !taskPath) return { source: "task" };
455
+ const tasks = await mutateTasks(taskPath, id, (file) => ({
456
+ ...file,
457
+ tasks: options.remove ? file.tasks.filter((item) => item.id !== originId) : file.tasks.map(
458
+ (item) => item.id === originId ? {
459
+ ...item,
460
+ title: task.title,
461
+ description: task.description,
462
+ status: sourceStatus(task),
463
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
464
+ } : item
465
+ )
466
+ }));
467
+ return { source: "task", tasks };
468
+ }
469
+ return { source: null };
470
+ }
471
+ export {
472
+ SESSION_KANBAN_COLUMNS,
473
+ applySessionKanbanTaskToSource,
474
+ attachSessionKanbanMirror,
475
+ cleanupEmptySessionKanbanBoards,
476
+ cleanupSessionKanbanBoardIfEmpty,
477
+ ensureSessionKanbanBoard,
478
+ hydrateSessionKanban,
479
+ mirrorSessionPlanToKanban,
480
+ mirrorSessionTasksToKanban,
481
+ mirrorSessionTodosToKanban,
482
+ planFileToSerializedGraph,
483
+ projectSessionPlanToKanban,
484
+ projectSessionTasksToKanban,
485
+ projectSessionTodosToKanban,
486
+ taskFileToSerializedGraph,
487
+ todoListToSerializedGraph
488
+ };
489
+ //# sourceMappingURL=session-kanban.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/session-kanban.ts"],
4
+ "sourcesContent": ["import { type FSWatcher, watch } from 'node:fs';\nimport { basename, dirname } from 'node:path';\nimport {\n type Context,\n deserializeTaskGraph,\n loadPlan,\n loadTasks,\n mutatePlan,\n mutateTasks,\n type PlanFile,\n type PlanItem,\n type SerializedTaskGraph,\n type TaskFile,\n type TaskItem,\n type TaskStatus,\n type TodoItem,\n} from '@wrongstack/core';\nimport {\n createBoard,\n getBoard,\n type KanbanBoard,\n type KanbanColumn,\n type KanbanTask,\n listBoards,\n removeBoard,\n syncBoardFromTaskGraph,\n updateBoard,\n} from '@wrongstack/kanban';\n\nconst SESSION_BOARD_TAG = 'session-work';\nconst MIRROR_DISABLED_ENV = 'WRONGSTACK_KANBAN_TASK_MIRROR';\n\n/** The canonical workflow shared by WebUI and TUI session boards. */\nexport const SESSION_KANBAN_COLUMNS: KanbanColumn[] = [\n { id: 'todo', title: 'Todo', order: 0, wipLimit: 0, color: '#2563eb' },\n { id: 'in-progress', title: 'Running', order: 1, wipLimit: 1, color: '#d97706' },\n { id: 'review', title: 'Preview', order: 2, wipLimit: 0, color: '#7c3aed' },\n { id: 'done', title: 'Done', order: 3, wipLimit: 0, color: '#16a34a' },\n];\n\nconst boardQueue = new Map<string, Promise<void>>();\nconst boardEnsures = new Map<string, Promise<KanbanBoard>>();\nconst bindings = new WeakMap<Context, () => void>();\nconst suppressedTodoMirrors = new WeakSet<Context>();\nconst activeSessionBoards = new Map<string, number>();\n\nfunction boardKey(projectRoot: string, sessionId: string): string {\n return `${projectRoot}\\0${sessionId}`;\n}\n\nfunction sessionTag(sessionId: string): string {\n return `session:${sessionId}`;\n}\n\nfunction sessionBoardTitle(sessionId: string): string {\n const leaf = sessionId.split(/[\\\\/]/).filter(Boolean).pop() ?? sessionId;\n return `Session ${leaf.slice(0, 12)}`;\n}\n\nfunction sessionBoardTags(sessionId: string): string[] {\n return ['session', SESSION_BOARD_TAG, sessionTag(sessionId)];\n}\n\nfunction sessionIdFromTags(tags: readonly string[] | undefined): string | null {\n const tag = tags?.find((candidate) => candidate.startsWith('session:'));\n return tag?.slice('session:'.length) || null;\n}\n\nfunction isOwnedSessionBoard(tags: readonly string[] | undefined): boolean {\n return Boolean(tags?.includes(SESSION_BOARD_TAG) && sessionIdFromTags(tags));\n}\n\nfunction retainActiveSessionBoard(projectRoot: string, sessionId: string): void {\n const key = boardKey(projectRoot, sessionId);\n activeSessionBoards.set(key, (activeSessionBoards.get(key) ?? 0) + 1);\n}\n\nfunction releaseActiveSessionBoard(projectRoot: string, sessionId: string): void {\n const key = boardKey(projectRoot, sessionId);\n const remaining = (activeSessionBoards.get(key) ?? 0) - 1;\n if (remaining > 0) activeSessionBoards.set(key, remaining);\n else activeSessionBoards.delete(key);\n}\n\nfunction isSessionBoardActive(projectRoot: string, sessionId: string): boolean {\n return (activeSessionBoards.get(boardKey(projectRoot, sessionId)) ?? 0) > 0;\n}\n\nfunction sameColumns(columns: readonly KanbanColumn[]): boolean {\n return (\n columns.length === SESSION_KANBAN_COLUMNS.length &&\n columns.every((column, index) => column.id === SESSION_KANBAN_COLUMNS[index]?.id)\n );\n}\n\n/** Create (or migrate) the single Kanban board owned by a session. */\nexport async function ensureSessionKanbanBoard(\n projectRoot: string | undefined,\n sessionId: string,\n): Promise<KanbanBoard | null> {\n if (!projectRoot || !sessionId || process.env[MIRROR_DISABLED_ENV] === '0') return null;\n const key = boardKey(projectRoot, sessionId);\n const inFlight = boardEnsures.get(key);\n if (inFlight) return inFlight;\n\n const promise = (async () => {\n const summary = (await listBoards(projectRoot)).find((board) =>\n board.tags?.includes(sessionTag(sessionId)),\n );\n let board = summary ? await getBoard(projectRoot, summary.id) : null;\n if (!board) {\n return createBoard(projectRoot, {\n title: sessionBoardTitle(sessionId),\n description: 'Live session work: todos, tasks, and plan items.',\n tags: sessionBoardTags(sessionId),\n columns: SESSION_KANBAN_COLUMNS,\n generatedBy: `session-kanban:${sessionId}`,\n });\n }\n\n // Boards produced by the older task/plan mirrors used the generic five\n // columns. Migrate only session-owned boards; updateBoard reconciles cards\n // from Backlog into Todo while preserving Running/Preview/Done cards.\n if (!sameColumns(board.columns) || !board.tags?.includes(SESSION_BOARD_TAG)) {\n board =\n (await updateBoard(projectRoot, board.id, {\n title: sessionBoardTitle(sessionId),\n description: 'Live session work: todos, tasks, and plan items.',\n tags: [...new Set([...(board.tags ?? []), ...sessionBoardTags(sessionId)])],\n columns: SESSION_KANBAN_COLUMNS,\n })) ?? board;\n }\n return board;\n })();\n\n boardEnsures.set(key, promise);\n try {\n return await promise;\n } finally {\n boardEnsures.delete(key);\n }\n}\n\nfunction enqueueBoardWork<T>(\n projectRoot: string,\n sessionId: string,\n work: () => Promise<T>,\n): Promise<T> {\n const key = boardKey(projectRoot, sessionId);\n const previous = boardQueue.get(key) ?? Promise.resolve();\n const result = previous.catch(() => undefined).then(work);\n const tail = result.then(\n () => undefined,\n () => undefined,\n );\n boardQueue.set(key, tail);\n void tail.then(() => {\n if (boardQueue.get(key) === tail) boardQueue.delete(key);\n });\n return result;\n}\n\nasync function removeEmptySessionBoard(\n projectRoot: string,\n boardId: string,\n sessionId: string,\n): Promise<string | null> {\n return enqueueBoardWork(projectRoot, sessionId, async () => {\n if (isSessionBoardActive(projectRoot, sessionId)) return null;\n const board = await getBoard(projectRoot, boardId);\n if (!board || board.tasks.length > 0 || !isOwnedSessionBoard(board.tags)) return null;\n if (sessionIdFromTags(board.tags) !== sessionId) return null;\n return (await removeBoard(projectRoot, board.id)) ? board.id : null;\n });\n}\n\n/** Remove a particular inactive session's system-owned board when it has no cards. */\nexport async function cleanupSessionKanbanBoardIfEmpty(\n projectRoot: string | undefined,\n sessionId: string,\n): Promise<string[]> {\n if (!projectRoot || !sessionId || process.env[MIRROR_DISABLED_ENV] === '0') return [];\n if (isSessionBoardActive(projectRoot, sessionId)) return [];\n const candidates = (await listBoards(projectRoot)).filter(\n (board) =>\n board.taskCount === 0 &&\n isOwnedSessionBoard(board.tags) &&\n sessionIdFromTags(board.tags) === sessionId,\n );\n const removed = await Promise.all(\n candidates.map((board) => removeEmptySessionBoard(projectRoot, board.id, sessionId)),\n );\n return removed.filter((boardId): boardId is string => Boolean(boardId));\n}\n\n/** Prune stale empty session boards while preserving manual and live boards. */\nexport async function cleanupEmptySessionKanbanBoards(\n projectRoot: string | undefined,\n activeSessionId = '',\n): Promise<string[]> {\n if (!projectRoot || process.env[MIRROR_DISABLED_ENV] === '0') return [];\n const candidates = (await listBoards(projectRoot)).flatMap((board) => {\n const ownerSessionId = sessionIdFromTags(board.tags);\n return board.taskCount === 0 &&\n isOwnedSessionBoard(board.tags) &&\n ownerSessionId &&\n ownerSessionId !== activeSessionId &&\n !isSessionBoardActive(projectRoot, ownerSessionId)\n ? [{ boardId: board.id, sessionId: ownerSessionId }]\n : [];\n });\n const removed = await Promise.all(\n candidates.map(({ boardId, sessionId }) =>\n removeEmptySessionBoard(projectRoot, boardId, sessionId),\n ),\n );\n return removed.filter((boardId): boardId is string => Boolean(boardId));\n}\n\nasync function projectGraph(\n projectRoot: string | undefined,\n sessionId: string,\n graph: SerializedTaskGraph,\n sourceSystem: 'session-todo' | 'session-task' | 'session-plan',\n): Promise<KanbanBoard | null> {\n if (!projectRoot || !sessionId || process.env[MIRROR_DISABLED_ENV] === '0') return null;\n return enqueueBoardWork(projectRoot, sessionId, async () => {\n const board = await ensureSessionKanbanBoard(projectRoot, sessionId);\n if (!board) return null;\n const result = await syncBoardFromTaskGraph(\n projectRoot,\n board.id,\n deserializeTaskGraph(graph),\n {\n sourceSystem,\n tags: [...new Set([...(board.tags ?? []), ...sessionBoardTags(sessionId)])],\n archiveMissingTasks: true,\n includeCompletedTasks: true,\n },\n );\n return result?.board ?? null;\n });\n}\n\nexport function todoListToSerializedGraph(\n todos: readonly TodoItem[],\n sessionId: string,\n): SerializedTaskGraph {\n const nodes = todos.map((todo, index) => ({\n id: todo.id,\n title: todo.content,\n description: todo.activeForm ?? '',\n type: 'chore' as const,\n priority: 'medium' as const,\n status: todo.status,\n createdAt: index,\n updatedAt: index,\n }));\n return {\n id: `todo:${sessionId}`,\n specId: `todo:${sessionId}`,\n title: 'Session todos',\n nodes,\n edges: [],\n rootNodes: nodes.map((node) => node.id),\n createdAt: 0,\n updatedAt: 0,\n };\n}\n\nexport function taskFileToSerializedGraph(\n tasks: readonly TaskItem[],\n sessionId: string,\n): SerializedTaskGraph {\n const ids = new Set(tasks.map((task) => task.id));\n const nodes = tasks.map((task, index) => ({\n id: task.id,\n title: task.title,\n description: task.description ?? '',\n type: task.type,\n priority: task.priority,\n status: task.status,\n ...(task.assignee ? { assignee: task.assignee } : {}),\n ...(task.estimateHours !== undefined ? { estimateHours: task.estimateHours } : {}),\n createdAt: index,\n updatedAt: index,\n }));\n const edges = tasks.flatMap((task) =>\n (task.dependsOn ?? [])\n .filter((dependency) => ids.has(dependency))\n .map((dependency) => ({\n id: `${dependency}->${task.id}`,\n from: dependency,\n to: task.id,\n type: 'depends_on' as const,\n })),\n );\n const hasIncoming = new Set(edges.map((edge) => edge.to));\n const rootNodes = nodes.filter((node) => !hasIncoming.has(node.id)).map((node) => node.id);\n return {\n // Keep the historical graph id so existing mirrored task cards are reused.\n id: `session:${sessionId}`,\n specId: `session:${sessionId}`,\n title: 'Session tasks',\n nodes,\n edges,\n rootNodes: rootNodes.length ? rootNodes : nodes[0] ? [nodes[0].id] : [],\n createdAt: 0,\n updatedAt: 0,\n };\n}\n\nconst PLAN_STATUS_TO_TASK: Record<PlanItem['status'], TaskStatus> = {\n open: 'pending',\n in_progress: 'in_progress',\n done: 'completed',\n};\n\nexport function planFileToSerializedGraph(\n items: readonly PlanItem[],\n sessionId: string,\n): SerializedTaskGraph {\n const nodes = items.map((item, index) => ({\n id: item.id,\n title: item.title,\n description: item.details ?? '',\n type: 'chore' as const,\n priority: 'medium' as const,\n status: PLAN_STATUS_TO_TASK[item.status],\n createdAt: index,\n updatedAt: index,\n }));\n return {\n id: `plan:${sessionId}`,\n specId: `plan:${sessionId}`,\n title: 'Session plan',\n nodes,\n edges: [],\n rootNodes: nodes.map((node) => node.id),\n createdAt: 0,\n updatedAt: 0,\n };\n}\n\nexport function projectSessionTodosToKanban(\n projectRoot: string | undefined,\n todos: readonly TodoItem[],\n sessionId: string,\n): Promise<KanbanBoard | null> {\n return projectGraph(\n projectRoot,\n sessionId,\n todoListToSerializedGraph(todos, sessionId),\n 'session-todo',\n );\n}\n\nexport function projectSessionTasksToKanban(\n projectRoot: string | undefined,\n tasks: readonly TaskItem[],\n sessionId: string,\n): Promise<KanbanBoard | null> {\n return projectGraph(\n projectRoot,\n sessionId,\n taskFileToSerializedGraph(tasks, sessionId),\n 'session-task',\n );\n}\n\nexport function projectSessionPlanToKanban(\n projectRoot: string | undefined,\n items: readonly PlanItem[],\n sessionId: string,\n): Promise<KanbanBoard | null> {\n return projectGraph(\n projectRoot,\n sessionId,\n planFileToSerializedGraph(items, sessionId),\n 'session-plan',\n );\n}\n\nfunction fireAndForget(work: Promise<unknown>): void {\n void work.catch(() => {\n // The session files/state remain recoverable if the observational board\n // cannot be written; the next mutation or file watcher retries the mirror.\n });\n}\n\nexport function mirrorSessionTodosToKanban(\n projectRoot: string | undefined,\n todos: readonly TodoItem[],\n sessionId: string,\n): void {\n fireAndForget(projectSessionTodosToKanban(projectRoot, todos, sessionId));\n}\n\nexport function mirrorSessionTasksToKanban(\n projectRoot: string | undefined,\n tasks: readonly TaskItem[],\n sessionId: string,\n): void {\n fireAndForget(projectSessionTasksToKanban(projectRoot, tasks, sessionId));\n}\n\nexport function mirrorSessionPlanToKanban(\n projectRoot: string | undefined,\n items: readonly PlanItem[],\n sessionId: string,\n): void {\n fireAndForget(projectSessionPlanToKanban(projectRoot, items, sessionId));\n}\n\n/**\n * Bind all live session work paths to Kanban. Todo changes are observed from\n * ConversationState; plan/task sidecars are watched so slash commands, WebUI,\n * plugins, and tools all pass through the same board.\n */\nexport function attachSessionKanbanMirror(context: Context): () => void {\n const existing = bindings.get(context);\n if (existing) return existing;\n\n const attachedProjectRoot = context.projectRoot ?? '';\n let registeredSessionId = '';\n const syncActiveSessionRegistration = () => {\n if (!attachedProjectRoot) return;\n const currentSessionId = context.session?.id ?? '';\n if (currentSessionId === registeredSessionId) return;\n if (registeredSessionId) {\n releaseActiveSessionBoard(attachedProjectRoot, registeredSessionId);\n fireAndForget(cleanupSessionKanbanBoardIfEmpty(attachedProjectRoot, registeredSessionId));\n }\n registeredSessionId = currentSessionId;\n if (registeredSessionId) {\n retainActiveSessionBoard(attachedProjectRoot, registeredSessionId);\n }\n };\n syncActiveSessionRegistration();\n\n let watcher: FSWatcher | null = null;\n let watchedDir = '';\n let timer: NodeJS.Timeout | null = null;\n\n const sessionId = () => context.session?.id ?? '';\n const refreshFiles = async () => {\n const id = sessionId();\n if (!id) return;\n const planPath = context.meta['plan.path'];\n if (typeof planPath === 'string' && planPath) {\n const plan = await loadPlan(planPath);\n if (plan) await projectSessionPlanToKanban(context.projectRoot, plan.items, id);\n }\n const taskPath = context.meta['task.path'];\n if (typeof taskPath === 'string' && taskPath) {\n const tasks = await loadTasks(taskPath);\n if (tasks) await projectSessionTasksToKanban(context.projectRoot, tasks.tasks, id);\n }\n };\n\n const configureWatcher = () => {\n const planPath = context.meta['plan.path'];\n const taskPath = context.meta['task.path'];\n const candidate =\n typeof planPath === 'string' && planPath\n ? dirname(planPath)\n : typeof taskPath === 'string' && taskPath\n ? dirname(taskPath)\n : '';\n if (!candidate || candidate === watchedDir) return;\n watcher?.close();\n watcher = null;\n watchedDir = candidate;\n try {\n watcher = watch(candidate, { persistent: false }, (_event, filename) => {\n const name = filename?.toString();\n const currentPlanPath = context.meta['plan.path'];\n const currentTaskPath = context.meta['task.path'];\n const planName = typeof currentPlanPath === 'string' ? basename(currentPlanPath) : '';\n const taskName = typeof currentTaskPath === 'string' ? basename(currentTaskPath) : '';\n if (name && name !== planName && name !== taskName) return;\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => fireAndForget(refreshFiles()), 60);\n });\n watcher.on('error', () => watcher?.close());\n } catch {\n // The session directory can briefly disappear during project/session\n // switches; the next meta update re-attempts the binding.\n watcher = null;\n watchedDir = '';\n }\n };\n\n const unsubscribe = context.state.onChange((change) => {\n if (change.kind === 'todos_replaced' && !suppressedTodoMirrors.has(context)) {\n // ConversationState auto-clears an all-done tactical list. Project the\n // pre-clear completion snapshot so every card reaches Done atomically.\n mirrorSessionTodosToKanban(\n context.projectRoot,\n change.completedSnapshot ?? change.todos,\n sessionId(),\n );\n return;\n }\n if (change.kind === 'meta_set' && (change.key === 'plan.path' || change.key === 'task.path')) {\n syncActiveSessionRegistration();\n configureWatcher();\n fireAndForget(ensureSessionKanbanBoard(context.projectRoot, sessionId()));\n fireAndForget(refreshFiles());\n }\n });\n\n configureWatcher();\n\n const detach = () => {\n unsubscribe();\n if (timer) clearTimeout(timer);\n watcher?.close();\n bindings.delete(context);\n if (attachedProjectRoot && registeredSessionId) {\n releaseActiveSessionBoard(attachedProjectRoot, registeredSessionId);\n fireAndForget(cleanupSessionKanbanBoardIfEmpty(attachedProjectRoot, registeredSessionId));\n registeredSessionId = '';\n }\n };\n bindings.set(context, detach);\n return detach;\n}\n\n/** Fully hydrate a session board before a host announces the session as ready. */\nexport async function hydrateSessionKanban(context: Context): Promise<KanbanBoard | null> {\n const id = context.session?.id ?? '';\n if (!id) return null;\n await cleanupEmptySessionKanbanBoards(context.projectRoot, id);\n let board = await ensureSessionKanbanBoard(context.projectRoot, id);\n if (context.todos.length) {\n board = await projectSessionTodosToKanban(context.projectRoot, context.todos, id);\n }\n const planPath = context.meta['plan.path'];\n if (typeof planPath === 'string' && planPath) {\n const plan = await loadPlan(planPath);\n if (plan) board = await projectSessionPlanToKanban(context.projectRoot, plan.items, id);\n }\n const taskPath = context.meta['task.path'];\n if (typeof taskPath === 'string' && taskPath) {\n const tasks = await loadTasks(taskPath);\n if (tasks) board = await projectSessionTasksToKanban(context.projectRoot, tasks.tasks, id);\n }\n return board;\n}\n\nexport interface SessionKanbanSourceUpdate {\n source: 'todo' | 'task' | 'plan' | null;\n todos?: TodoItem[] | undefined;\n tasks?: TaskFile | undefined;\n plan?: PlanFile | undefined;\n}\n\nfunction sourceStatus(task: KanbanTask): TaskStatus {\n if (task.status === 'completed') return 'completed';\n if (task.status === 'in_progress') return 'in_progress';\n if (task.status === 'review') return 'review';\n if (task.status === 'blocked') return 'blocked';\n if (task.status === 'failed') return 'failed';\n return 'pending';\n}\n\n/** Reflect a TUI/WebUI Kanban card edit back to its originating work list. */\nexport async function applySessionKanbanTaskToSource(\n context: Context,\n task: KanbanTask,\n options: { remove?: boolean | undefined } = {},\n): Promise<SessionKanbanSourceUpdate> {\n const originId = task.origin?.taskId;\n const graphId = task.origin?.graphId ?? '';\n if (!originId) return { source: null };\n\n if (task.origin?.system === 'session-todo' || graphId.startsWith('todo:')) {\n const next = options.remove\n ? context.todos.filter((todo) => todo.id !== originId)\n : context.todos.map((todo) =>\n todo.id === originId\n ? {\n ...todo,\n content: task.title,\n status:\n sourceStatus(task) === 'completed'\n ? ('completed' as const)\n : sourceStatus(task) === 'in_progress' || sourceStatus(task) === 'review'\n ? ('in_progress' as const)\n : ('pending' as const),\n }\n : todo,\n );\n suppressedTodoMirrors.add(context);\n try {\n context.state.replaceTodos(next);\n } finally {\n suppressedTodoMirrors.delete(context);\n }\n return { source: 'todo', todos: [...context.todos] };\n }\n\n const id = context.session?.id ?? '';\n if (task.origin?.system === 'session-plan' || graphId.startsWith('plan:')) {\n const planPath = context.meta['plan.path'];\n if (typeof planPath !== 'string' || !planPath) return { source: 'plan' };\n const plan = await mutatePlan(planPath, id, (file) => ({\n ...file,\n updatedAt: new Date().toISOString(),\n items: options.remove\n ? file.items.filter((item) => item.id !== originId)\n : file.items.map((item) =>\n item.id === originId\n ? {\n ...item,\n title: task.title,\n details: task.description,\n status:\n task.status === 'completed'\n ? ('done' as const)\n : task.status === 'in_progress' || task.status === 'review'\n ? ('in_progress' as const)\n : ('open' as const),\n updatedAt: new Date().toISOString(),\n }\n : item,\n ),\n }));\n return { source: 'plan', plan };\n }\n\n if (\n task.origin?.system === 'session-task' ||\n task.origin?.system === 'session' ||\n graphId.startsWith('session:')\n ) {\n const taskPath = context.meta['task.path'];\n if (typeof taskPath !== 'string' || !taskPath) return { source: 'task' };\n const tasks = await mutateTasks(taskPath, id, (file) => ({\n ...file,\n tasks: options.remove\n ? file.tasks.filter((item) => item.id !== originId)\n : file.tasks.map((item) =>\n item.id === originId\n ? {\n ...item,\n title: task.title,\n description: task.description,\n status: sourceStatus(task),\n updatedAt: new Date().toISOString(),\n }\n : item,\n ),\n }));\n return { source: 'task', tasks };\n }\n\n return { source: null };\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["board"]
7
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../src/task.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,UAAU,EAIhB,MAAM,kBAAkB,CAAC;AAU1B,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAqC7C,UAAU,SAAS;IACjB,yHAAyH;IACzH,MAAM,EAAE,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,CAAC;IACtE,yCAAyC;IACzC,KAAK,CAAC,EAAE,QAAQ,EAAE,GAAG,SAAS,CAAC;IAC/B,+EAA+E;IAC/E,IAAI,CAAC,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,GAAG,WAAW,GAAG,WAAW,CAAC,GAAG,SAAS,CAAC;IACpE,8DAA8D;IAC9D,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxB,oCAAoC;IACpC,MAAM,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;IAChC,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAChC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;CAC/B;AAED,UAAU,UAAU;IAClB,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,UAAU,CA4ahD,CAAC"}
1
+ {"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../src/task.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,UAAU,EAIhB,MAAM,kBAAkB,CAAC;AAU1B,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAsC7C,UAAU,SAAS;IACjB,yHAAyH;IACzH,MAAM,EAAE,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,CAAC;IACtE,yCAAyC;IACzC,KAAK,CAAC,EAAE,QAAQ,EAAE,GAAG,SAAS,CAAC;IAC/B,+EAA+E;IAC/E,IAAI,CAAC,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,GAAG,WAAW,GAAG,WAAW,CAAC,GAAG,SAAS,CAAC;IACpE,8DAA8D;IAC9D,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxB,oCAAoC;IACpC,MAAM,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;IAChC,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAChC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;CAC/B;AAED,UAAU,UAAU;IAClB,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,UAAU,CAgbhD,CAAC"}