@wrongstack/tools 0.286.0 → 0.289.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/_edit-match.d.ts.map +1 -1
- package/dist/_fetch-guard.d.ts.map +1 -1
- package/dist/_redact-command.d.ts.map +1 -1
- package/dist/_spawn-stream.d.ts.map +1 -1
- package/dist/audit.js +61 -14
- package/dist/audit.js.map +3 -3
- package/dist/bash.d.ts +1 -1
- package/dist/bash.d.ts.map +1 -1
- package/dist/bash.js +97 -19
- package/dist/bash.js.map +3 -3
- package/dist/batch-tool-use.d.ts +1 -1
- package/dist/batch-tool-use.d.ts.map +1 -1
- package/dist/batch-tool-use.js +26 -8
- package/dist/batch-tool-use.js.map +2 -2
- package/dist/browser/index.js +1 -1
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/types.d.ts +1 -1
- package/dist/browser/types.d.ts.map +1 -1
- package/dist/builtin.d.ts +12 -10
- package/dist/builtin.d.ts.map +1 -1
- package/dist/builtin.js +2107 -864
- package/dist/builtin.js.map +4 -4
- package/dist/codebase-index/background-indexer.d.ts.map +1 -1
- package/dist/codebase-index/bm25.d.ts +1 -2
- package/dist/codebase-index/bm25.d.ts.map +1 -1
- package/dist/codebase-index/codebase-index-tool.d.ts.map +1 -1
- package/dist/codebase-index/codebase-search-tool.d.ts.map +1 -1
- package/dist/codebase-index/codebase-stats-tool.d.ts +7 -0
- package/dist/codebase-index/codebase-stats-tool.d.ts.map +1 -1
- package/dist/codebase-index/go-parser.d.ts.map +1 -1
- package/dist/codebase-index/index-service.d.ts +11 -1
- package/dist/codebase-index/index-service.d.ts.map +1 -1
- package/dist/codebase-index/index.d.ts +2 -1
- package/dist/codebase-index/index.d.ts.map +1 -1
- package/dist/codebase-index/index.js +934 -283
- package/dist/codebase-index/index.js.map +4 -4
- package/dist/codebase-index/indexer.d.ts.map +1 -1
- package/dist/codebase-index/py-parser.d.ts.map +1 -1
- package/dist/codebase-index/rs-parser.d.ts.map +1 -1
- package/dist/codebase-index/schema.d.ts +43 -1
- package/dist/codebase-index/schema.d.ts.map +1 -1
- package/dist/codebase-index/ts-parser.d.ts.map +1 -1
- package/dist/codebase-index/worker-protocol.d.ts +1 -0
- package/dist/codebase-index/worker-protocol.d.ts.map +1 -1
- package/dist/codebase-index/worker.js +793 -241
- package/dist/codebase-index/worker.js.map +4 -4
- package/dist/codebase-index/writer.d.ts +27 -1
- package/dist/codebase-index/writer.d.ts.map +1 -1
- package/dist/e2e.js +477 -0
- package/dist/e2e.js.map +7 -0
- package/dist/edit.d.ts.map +1 -1
- package/dist/edit.js +36 -12
- package/dist/edit.js.map +3 -3
- package/dist/exec.d.ts +1 -1
- package/dist/exec.d.ts.map +1 -1
- package/dist/exec.js +60 -5
- package/dist/exec.js.map +3 -3
- package/dist/fetch.js +2 -2
- package/dist/fetch.js.map +2 -2
- package/dist/format.js +61 -14
- package/dist/format.js.map +3 -3
- package/dist/glob.js +2 -2
- package/dist/glob.js.map +1 -1
- package/dist/grep.d.ts.map +1 -1
- package/dist/grep.js +2 -2
- package/dist/grep.js.map +2 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2535 -865
- package/dist/index.js.map +4 -4
- package/dist/install.js +61 -14
- package/dist/install.js.map +3 -3
- package/dist/kanban.d.ts +16 -2
- package/dist/kanban.d.ts.map +1 -1
- package/dist/kanban.js +795 -532
- package/dist/kanban.js.map +4 -4
- package/dist/languages/detect.d.ts.map +1 -1
- package/dist/languages/index.js +61 -14
- package/dist/languages/index.js.map +3 -3
- package/dist/languages/types.d.ts +7 -0
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/lint.js +61 -14
- package/dist/lint.js.map +3 -3
- package/dist/next-steps.d.ts +6 -2
- package/dist/next-steps.d.ts.map +1 -1
- package/dist/next-steps.js +3 -3
- package/dist/next-steps.js.map +2 -2
- package/dist/outdated.js +61 -14
- package/dist/outdated.js.map +3 -3
- package/dist/pack.js +2104 -861
- package/dist/pack.js.map +4 -4
- package/dist/plan.d.ts.map +1 -1
- package/dist/plan.js +158 -4
- package/dist/plan.js.map +4 -4
- package/dist/process-registry.js +12 -5
- package/dist/process-registry.js.map +2 -2
- package/dist/search.js +2 -2
- package/dist/search.js.map +2 -2
- package/dist/session-kanban.d.ts +47 -0
- package/dist/session-kanban.d.ts.map +1 -0
- package/dist/session-kanban.js +626 -0
- package/dist/session-kanban.js.map +7 -0
- package/dist/task.d.ts.map +1 -1
- package/dist/task.js +167 -4
- package/dist/task.js.map +4 -4
- package/dist/test.js +61 -14
- package/dist/test.js.map +3 -3
- package/dist/todo.d.ts.map +1 -1
- package/dist/todo.js +159 -7
- package/dist/todo.js.map +4 -4
- package/dist/tool-icon-map.d.ts.map +1 -1
- package/dist/tool-icons.d.ts.map +1 -1
- package/dist/tool-icons.js +7 -1
- package/dist/tool-icons.js.map +2 -2
- package/dist/tree.js +2 -2
- package/dist/tree.js.map +1 -1
- package/dist/typecheck.js +61 -14
- package/dist/typecheck.js.map +3 -3
- package/package.json +11 -3
package/dist/kanban.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/kanban.ts
|
|
2
|
-
import {
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { deserializeTaskGraph as deserializeTaskGraph2, loadTasks as loadTasks2, serializeTaskGraph } from "@wrongstack/core";
|
|
3
4
|
import {
|
|
4
5
|
addCheckToTask,
|
|
5
6
|
addColumn,
|
|
@@ -11,18 +12,19 @@ import {
|
|
|
11
12
|
assignTask,
|
|
12
13
|
claimReadyTask,
|
|
13
14
|
copyTaskToBoard,
|
|
14
|
-
createBoard,
|
|
15
|
+
createBoard as createBoard2,
|
|
16
|
+
createBoardFromTaskGraph,
|
|
15
17
|
duplicateBoard,
|
|
16
18
|
exportBoardAsMarkdown,
|
|
17
19
|
exportBoardToTaskGraph,
|
|
18
20
|
generateBoardFromDescription,
|
|
19
|
-
getBoard,
|
|
21
|
+
getBoard as getBoard2,
|
|
20
22
|
getKanbanOrchestrationSnapshot,
|
|
21
23
|
getKanbanQueueHealth,
|
|
22
24
|
getTask,
|
|
23
25
|
getTaskChain,
|
|
24
26
|
heartbeatTaskAssignment,
|
|
25
|
-
listBoards,
|
|
27
|
+
listBoards as listBoards2,
|
|
26
28
|
listKanbanEvents,
|
|
27
29
|
listReadyTasks,
|
|
28
30
|
mergeTasks,
|
|
@@ -30,26 +32,87 @@ import {
|
|
|
30
32
|
parseLinesIntoTasks,
|
|
31
33
|
recoverStaleTaskAssignments,
|
|
32
34
|
releaseTaskClaim,
|
|
33
|
-
removeBoard,
|
|
35
|
+
removeBoard as removeBoard2,
|
|
34
36
|
removeColumn,
|
|
35
37
|
removeTask,
|
|
36
38
|
searchKanban,
|
|
37
39
|
setTaskChain,
|
|
38
40
|
splitTask,
|
|
39
|
-
syncBoardFromTaskGraph,
|
|
41
|
+
syncBoardFromTaskGraph as syncBoardFromTaskGraph2,
|
|
42
|
+
transitionTask,
|
|
40
43
|
transferTaskToBoard,
|
|
41
|
-
|
|
44
|
+
touchKanbanPresence as touchKanbanPresence2,
|
|
45
|
+
updateBoard as updateBoard2,
|
|
42
46
|
updateCheckOnTask,
|
|
43
47
|
updateColumn,
|
|
44
48
|
updateGoalMetricOnTask,
|
|
45
49
|
updateTask,
|
|
46
50
|
updateTaskAssignment
|
|
47
51
|
} from "@wrongstack/kanban";
|
|
52
|
+
|
|
53
|
+
// src/session-kanban.ts
|
|
54
|
+
import {
|
|
55
|
+
deserializeTaskGraph,
|
|
56
|
+
GlobalMailbox,
|
|
57
|
+
loadPlan,
|
|
58
|
+
loadTasks,
|
|
59
|
+
mutatePlan,
|
|
60
|
+
mutateTasks
|
|
61
|
+
} from "@wrongstack/core";
|
|
62
|
+
import { resolveWstackPaths } from "@wrongstack/core/utils";
|
|
63
|
+
import {
|
|
64
|
+
createBoard,
|
|
65
|
+
getBoard,
|
|
66
|
+
getKanbanDir,
|
|
67
|
+
listBoards,
|
|
68
|
+
removeBoard,
|
|
69
|
+
syncBoardFromTaskGraph,
|
|
70
|
+
touchKanbanPresence,
|
|
71
|
+
updateBoard
|
|
72
|
+
} from "@wrongstack/kanban";
|
|
73
|
+
function taskFileToSerializedGraph(tasks, sessionId) {
|
|
74
|
+
const ids = new Set(tasks.map((task) => task.id));
|
|
75
|
+
const nodes = tasks.map((task, index) => ({
|
|
76
|
+
id: task.id,
|
|
77
|
+
title: task.title,
|
|
78
|
+
description: task.description ?? "",
|
|
79
|
+
type: task.type,
|
|
80
|
+
priority: task.priority,
|
|
81
|
+
status: task.status,
|
|
82
|
+
...task.assignee ? { assignee: task.assignee } : {},
|
|
83
|
+
...task.estimateHours !== void 0 ? { estimateHours: task.estimateHours } : {},
|
|
84
|
+
createdAt: index,
|
|
85
|
+
updatedAt: index
|
|
86
|
+
}));
|
|
87
|
+
const edges = tasks.flatMap(
|
|
88
|
+
(task) => (task.dependsOn ?? []).filter((dependency) => ids.has(dependency)).map((dependency) => ({
|
|
89
|
+
id: `${dependency}->${task.id}`,
|
|
90
|
+
from: dependency,
|
|
91
|
+
to: task.id,
|
|
92
|
+
type: "depends_on"
|
|
93
|
+
}))
|
|
94
|
+
);
|
|
95
|
+
const hasIncoming = new Set(edges.map((edge) => edge.to));
|
|
96
|
+
const rootNodes = nodes.filter((node) => !hasIncoming.has(node.id)).map((node) => node.id);
|
|
97
|
+
return {
|
|
98
|
+
// Keep the historical graph id so existing mirrored task cards are reused.
|
|
99
|
+
id: `session:${sessionId}`,
|
|
100
|
+
specId: `session:${sessionId}`,
|
|
101
|
+
title: "Session tasks",
|
|
102
|
+
nodes,
|
|
103
|
+
edges,
|
|
104
|
+
rootNodes: rootNodes.length ? rootNodes : nodes[0] ? [nodes[0].id] : [],
|
|
105
|
+
createdAt: 0,
|
|
106
|
+
updatedAt: 0
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/kanban.ts
|
|
48
111
|
var kanbanTool = {
|
|
49
112
|
name: "kanban",
|
|
50
113
|
category: "Project",
|
|
51
|
-
description: "Manage project-scoped multi-kanban boards stored under .wrongstack/kanbans.
|
|
52
|
-
usageHint: "Use this for durable project kanban state.
|
|
114
|
+
description: "Manage and audit project-scoped multi-kanban boards stored under .wrongstack/kanbans. Managed cards enforce fully specified details and adjacent Backlog \u2192 Todo \u2192 Running \u2192 Review \u2192 Done transitions with persistent comments and evidence. Successful board access records live agent/session presence.",
|
|
115
|
+
usageHint: "Use this for durable project kanban state. Reassess with get_board whenever evidence changes; agents may add, update, split, merge, reprioritize, or remove tasks so the board remains a live plan. Presence includes active/last-seen session and agent metadata. For managed boards, fully fill card details, use transition_task after every material step, attach truthful evidence, and never use update_task/move_task to bypass lifecycle guards. Worker completion enters Review; only passed acceptance criteria plus review evidence allow Done.",
|
|
53
116
|
permission: "confirm",
|
|
54
117
|
mutating: true,
|
|
55
118
|
capabilities: ["fs.write"],
|
|
@@ -71,6 +134,8 @@ var kanbanTool = {
|
|
|
71
134
|
"export_markdown",
|
|
72
135
|
"export_task_graph",
|
|
73
136
|
"sync_task_graph",
|
|
137
|
+
"create_from_graph",
|
|
138
|
+
"import_session_tasks",
|
|
74
139
|
"search_tasks",
|
|
75
140
|
"ready_tasks",
|
|
76
141
|
"snapshot",
|
|
@@ -84,6 +149,7 @@ var kanbanTool = {
|
|
|
84
149
|
"transfer_task",
|
|
85
150
|
"get_task",
|
|
86
151
|
"update_task",
|
|
152
|
+
"transition_task",
|
|
87
153
|
"move_task",
|
|
88
154
|
"delete_task",
|
|
89
155
|
"set_chain",
|
|
@@ -114,9 +180,14 @@ var kanbanTool = {
|
|
|
114
180
|
targetColumnId: { type: "string" },
|
|
115
181
|
title: { type: "string" },
|
|
116
182
|
description: { type: "string" },
|
|
183
|
+
dueDate: { type: "string" },
|
|
117
184
|
tags: { type: "array", items: { type: "string" } },
|
|
118
185
|
labels: { type: "array", items: { type: "string" } },
|
|
119
186
|
priority: { type: "string", enum: ["critical", "high", "medium", "low"] },
|
|
187
|
+
taskType: {
|
|
188
|
+
type: "string",
|
|
189
|
+
enum: ["feature", "bugfix", "refactor", "docs", "test", "chore"]
|
|
190
|
+
},
|
|
120
191
|
status: {
|
|
121
192
|
type: "string",
|
|
122
193
|
enum: [
|
|
@@ -156,11 +227,36 @@ var kanbanTool = {
|
|
|
156
227
|
type: "string",
|
|
157
228
|
enum: ["assigned", "queued", "running", "completed", "failed", "cancelled"]
|
|
158
229
|
},
|
|
230
|
+
lifecycleStage: {
|
|
231
|
+
type: "string",
|
|
232
|
+
enum: ["backlog", "todo", "running", "review", "done"]
|
|
233
|
+
},
|
|
234
|
+
transitionAction: { type: "string" },
|
|
235
|
+
transitionComment: { type: "string" },
|
|
236
|
+
attachmentUrl: { type: "string" },
|
|
237
|
+
attachmentTitle: { type: "string" },
|
|
238
|
+
attachmentType: {
|
|
239
|
+
type: "string",
|
|
240
|
+
enum: ["issue", "pr", "doc", "commit", "design", "file", "url", "other"]
|
|
241
|
+
},
|
|
159
242
|
releaseStatus: { type: "string", enum: ["pending", "ready", "blocked"] },
|
|
160
243
|
releaseReason: { type: "string" },
|
|
161
244
|
clearAssignee: { type: "boolean" },
|
|
162
|
-
recoveryMode: { type: "string", enum: ["release", "retry", "fail"] },
|
|
245
|
+
recoveryMode: { type: "string", enum: ["auto", "release", "retry", "fail"] },
|
|
163
246
|
recoveryNow: { type: "string" },
|
|
247
|
+
recoveryPolicyFailOnCostCeiling: { type: "boolean" },
|
|
248
|
+
recoveryPolicyReleaseOnFailureKinds: { type: "array", items: { type: "string" } },
|
|
249
|
+
recoveryPolicyReleaseOnHeartbeatDue: { type: "boolean" },
|
|
250
|
+
recoveryPolicyRetryPolicyOverride: {
|
|
251
|
+
type: "string",
|
|
252
|
+
enum: ["off", "incremental", "exponential"]
|
|
253
|
+
},
|
|
254
|
+
assignee: { type: "string" },
|
|
255
|
+
costCeilingUsd: { type: "number" },
|
|
256
|
+
retryPolicy: { type: "string", enum: ["off", "incremental", "exponential"] },
|
|
257
|
+
lastFailureKind: { type: "string" },
|
|
258
|
+
dependsOn: { type: "array", items: { type: "string" } },
|
|
259
|
+
estimatedHours: { type: "number" },
|
|
164
260
|
taskGraph: { type: "object" },
|
|
165
261
|
graphId: { type: "string" },
|
|
166
262
|
specId: { type: "string" },
|
|
@@ -213,538 +309,632 @@ var kanbanTool = {
|
|
|
213
309
|
async execute(input, ctx) {
|
|
214
310
|
const projectRoot = ctx.projectRoot;
|
|
215
311
|
if (!projectRoot) return fail("No project root is available.");
|
|
312
|
+
const withPresence = async (result) => {
|
|
313
|
+
const boardId = result.board?.id ?? input.boardId;
|
|
314
|
+
if (!result.ok || !boardId || !ctx.session?.id || !ctx.agentId) return result;
|
|
315
|
+
try {
|
|
316
|
+
const board = await touchKanbanPresence2(projectRoot, boardId, {
|
|
317
|
+
sessionId: ctx.session.id,
|
|
318
|
+
agentId: ctx.agentId,
|
|
319
|
+
agentName: ctx.agentName,
|
|
320
|
+
taskId: input.taskId ?? result.task?.id,
|
|
321
|
+
runTaskId: input.runTaskId
|
|
322
|
+
});
|
|
323
|
+
return board ? { ...result, board } : result;
|
|
324
|
+
} catch {
|
|
325
|
+
return result;
|
|
326
|
+
}
|
|
327
|
+
};
|
|
216
328
|
try {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
return okBoard(await getBoard(projectRoot, board.id) ?? board, "Board generated.");
|
|
277
|
-
}
|
|
278
|
-
case "export_markdown": {
|
|
279
|
-
const board = await requireBoard(projectRoot, input.boardId);
|
|
280
|
-
if (!board) return fail("Board not found.");
|
|
281
|
-
return {
|
|
282
|
-
ok: true,
|
|
283
|
-
message: "Board exported.",
|
|
284
|
-
board,
|
|
285
|
-
markdown: exportBoardAsMarkdown(board)
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
case "export_task_graph": {
|
|
289
|
-
if (!input.boardId) return fail("export_task_graph requires boardId.");
|
|
290
|
-
const exported = await exportBoardToTaskGraph(projectRoot, input.boardId, {
|
|
291
|
-
...input.graphId !== void 0 ? { graphId: input.graphId } : {},
|
|
292
|
-
...input.specId !== void 0 ? { specId: input.specId } : {},
|
|
293
|
-
...input.title !== void 0 ? { title: input.title } : {},
|
|
294
|
-
...input.preserveOriginTaskIds !== void 0 ? { preserveOriginTaskIds: input.preserveOriginTaskIds } : {},
|
|
295
|
-
...input.includeArchived !== void 0 ? { includeArchived: input.includeArchived } : {}
|
|
296
|
-
});
|
|
297
|
-
if (!exported) return fail("Board not found.");
|
|
298
|
-
return {
|
|
299
|
-
ok: true,
|
|
300
|
-
message: `Task graph exported with ${exported.graph.nodes.size} node(s).`,
|
|
301
|
-
board: exported.board,
|
|
302
|
-
taskGraph: serializeTaskGraph(exported.graph)
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
case "sync_task_graph": {
|
|
306
|
-
if (!input.boardId || !input.taskGraph) {
|
|
307
|
-
return fail("sync_task_graph requires boardId and taskGraph.");
|
|
308
|
-
}
|
|
309
|
-
const graph = deserializeTaskGraph(input.taskGraph);
|
|
310
|
-
const result = await syncBoardFromTaskGraph(projectRoot, input.boardId, graph, {
|
|
311
|
-
...input.title !== void 0 ? { title: input.title } : {},
|
|
312
|
-
...input.description !== void 0 ? { description: input.description } : {},
|
|
313
|
-
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
314
|
-
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
315
|
-
...input.sourceSystem !== void 0 ? { sourceSystem: input.sourceSystem } : {},
|
|
316
|
-
...input.phaseId !== void 0 ? { phaseId: input.phaseId } : {},
|
|
317
|
-
...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {},
|
|
318
|
-
...input.archiveMissingTasks !== void 0 ? { archiveMissingTasks: input.archiveMissingTasks } : {},
|
|
319
|
-
...input.preserveManualDependencies !== void 0 ? { preserveManualDependencies: input.preserveManualDependencies } : {}
|
|
320
|
-
});
|
|
321
|
-
return result ? {
|
|
322
|
-
ok: true,
|
|
323
|
-
message: `Task graph synced: ${result.createdTaskIds.length} created, ${result.updatedTaskIds.length} updated, ${result.archivedTaskIds.length} archived.`,
|
|
324
|
-
board: result.board
|
|
325
|
-
} : fail("Board not found.");
|
|
326
|
-
}
|
|
327
|
-
case "search_tasks": {
|
|
328
|
-
const tasks = await searchKanban(projectRoot, {
|
|
329
|
-
query: input.query,
|
|
330
|
-
boardId: input.boardId,
|
|
331
|
-
assignedAgent: input.agentId,
|
|
332
|
-
status: input.status,
|
|
333
|
-
priority: input.priority,
|
|
334
|
-
label: input.labels?.[0],
|
|
335
|
-
chainId: input.chainId
|
|
336
|
-
});
|
|
337
|
-
return { ok: true, message: `${tasks.length} task(s) matched.`, tasks };
|
|
338
|
-
}
|
|
339
|
-
case "ready_tasks": {
|
|
340
|
-
const tasks = await listReadyTasks(projectRoot, {
|
|
341
|
-
query: input.query,
|
|
342
|
-
boardId: input.boardId,
|
|
343
|
-
assignedAgent: input.agentId,
|
|
344
|
-
priority: input.priority,
|
|
345
|
-
label: input.labels?.[0],
|
|
346
|
-
chainId: input.chainId,
|
|
347
|
-
limit: input.limit
|
|
348
|
-
});
|
|
349
|
-
return { ok: true, message: `${tasks.length} ready task(s).`, tasks };
|
|
350
|
-
}
|
|
351
|
-
case "snapshot": {
|
|
352
|
-
const snapshot = await getKanbanOrchestrationSnapshot(projectRoot, {
|
|
353
|
-
query: input.query,
|
|
354
|
-
boardId: input.boardId,
|
|
355
|
-
assignedAgent: input.agentId,
|
|
356
|
-
status: input.status,
|
|
357
|
-
priority: input.priority,
|
|
358
|
-
label: input.labels?.[0],
|
|
359
|
-
chainId: input.chainId
|
|
360
|
-
});
|
|
361
|
-
return {
|
|
362
|
-
ok: true,
|
|
363
|
-
message: `${snapshot.ready.length} ready, ${snapshot.running.length} running, ${snapshot.blocked.length} blocked.`,
|
|
364
|
-
snapshot
|
|
365
|
-
};
|
|
366
|
-
}
|
|
367
|
-
case "add_column": {
|
|
368
|
-
if (!input.boardId || !input.title) return fail("add_column requires boardId and title.");
|
|
369
|
-
const result = await addColumn(projectRoot, input.boardId, {
|
|
370
|
-
title: input.title,
|
|
371
|
-
...input.description !== void 0 ? { description: input.description } : {}
|
|
372
|
-
});
|
|
373
|
-
return result ? okBoard(result.board, "Column added.") : fail("Board not found.");
|
|
374
|
-
}
|
|
375
|
-
case "update_column": {
|
|
376
|
-
if (!input.boardId || !input.columnId)
|
|
377
|
-
return fail("update_column requires boardId and columnId.");
|
|
378
|
-
const board = await updateColumn(projectRoot, input.boardId, input.columnId, {
|
|
379
|
-
...input.title !== void 0 ? { title: input.title } : {},
|
|
380
|
-
...input.description !== void 0 ? { description: input.description } : {},
|
|
381
|
-
...input.order !== void 0 ? { order: input.order } : {}
|
|
382
|
-
});
|
|
383
|
-
return board ? okBoard(board, "Column updated.") : fail("Column not found.");
|
|
384
|
-
}
|
|
385
|
-
case "delete_column": {
|
|
386
|
-
if (!input.boardId || !input.columnId)
|
|
387
|
-
return fail("delete_column requires boardId and columnId.");
|
|
388
|
-
const board = await removeColumn(projectRoot, input.boardId, input.columnId, {
|
|
389
|
-
moveTasksToColumnId: input.moveTasksToColumnId
|
|
390
|
-
});
|
|
391
|
-
return board ? okBoard(board, "Column deleted.") : fail("Column not found.");
|
|
392
|
-
}
|
|
393
|
-
case "add_task": {
|
|
394
|
-
if (!input.boardId || !input.title) return fail("add_task requires boardId and title.");
|
|
395
|
-
const result = await addTask(projectRoot, input.boardId, taskInput(input));
|
|
396
|
-
return result ? okTask(result.board, result.task, "Task added.") : fail("Board not found.");
|
|
397
|
-
}
|
|
398
|
-
case "split_task": {
|
|
399
|
-
if (!input.boardId || !input.taskId || !input.childTitles?.length) {
|
|
400
|
-
return fail("split_task requires boardId, taskId, and childTitles.");
|
|
401
|
-
}
|
|
402
|
-
const result = await splitTask(projectRoot, input.boardId, input.taskId, {
|
|
403
|
-
titles: input.childTitles,
|
|
404
|
-
...input.targetColumnId !== void 0 ? { columnId: input.targetColumnId } : {},
|
|
405
|
-
...input.inheritAssignment !== void 0 ? { inheritAssignment: input.inheritAssignment } : {},
|
|
406
|
-
...input.inheritLabels !== void 0 ? { inheritLabels: input.inheritLabels } : {},
|
|
407
|
-
...input.inheritSuccessCriteria !== void 0 ? { inheritSuccessCriteria: input.inheritSuccessCriteria } : {},
|
|
408
|
-
...input.inheritGoalMetrics !== void 0 ? { inheritGoalMetrics: input.inheritGoalMetrics } : {},
|
|
409
|
-
...input.inheritDependencies !== void 0 ? { inheritDependencies: input.inheritDependencies } : {},
|
|
410
|
-
...input.chainChildren !== void 0 ? { chainChildren: input.chainChildren } : {},
|
|
411
|
-
...input.rewireDependents !== void 0 ? { rewireDependents: input.rewireDependents } : {}
|
|
412
|
-
});
|
|
413
|
-
return result ? {
|
|
414
|
-
ok: true,
|
|
415
|
-
message: `${result.children.length} child task(s) created.`,
|
|
416
|
-
board: result.board,
|
|
417
|
-
task: result.parent,
|
|
418
|
-
children: result.children
|
|
419
|
-
} : fail("Task not found.");
|
|
420
|
-
}
|
|
421
|
-
case "merge_tasks": {
|
|
422
|
-
if (!input.boardId || !input.taskIds?.length || !input.title) {
|
|
423
|
-
return fail("merge_tasks requires boardId, taskIds, and title.");
|
|
424
|
-
}
|
|
425
|
-
const result = await mergeTasks(projectRoot, input.boardId, {
|
|
426
|
-
taskIds: input.taskIds,
|
|
427
|
-
title: input.title,
|
|
428
|
-
...input.description !== void 0 ? { description: input.description } : {},
|
|
429
|
-
...input.targetColumnId !== void 0 ? { targetColumnId: input.targetColumnId } : {},
|
|
430
|
-
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {},
|
|
431
|
-
...input.closeSourceTasks !== void 0 ? { closeSourceTasks: input.closeSourceTasks } : {}
|
|
432
|
-
});
|
|
433
|
-
return result ? okTask(result.board, result.task, "Tasks merged.") : fail("Board or task not found.");
|
|
434
|
-
}
|
|
435
|
-
case "copy_task": {
|
|
436
|
-
if (!input.boardId || !input.taskId || !input.targetBoardId) {
|
|
437
|
-
return fail("copy_task requires boardId, taskId, and targetBoardId.");
|
|
438
|
-
}
|
|
439
|
-
const result = await copyTaskToBoard(
|
|
440
|
-
projectRoot,
|
|
441
|
-
input.boardId,
|
|
442
|
-
input.taskId,
|
|
443
|
-
input.targetBoardId,
|
|
444
|
-
{
|
|
445
|
-
...input.targetColumnId !== void 0 ? { targetColumnId: input.targetColumnId } : {},
|
|
446
|
-
...input.order !== void 0 ? { targetOrder: input.order } : {},
|
|
447
|
-
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {},
|
|
448
|
-
...input.preserveDependencies !== void 0 ? { preserveDependencies: input.preserveDependencies } : {}
|
|
329
|
+
const result = await (async () => {
|
|
330
|
+
switch (input.action) {
|
|
331
|
+
case "list_boards": {
|
|
332
|
+
const boards = await listBoards2(projectRoot);
|
|
333
|
+
return { ok: true, message: `${boards.length} board(s).`, boards };
|
|
334
|
+
}
|
|
335
|
+
case "get_board": {
|
|
336
|
+
const board = await requireBoard(projectRoot, input.boardId);
|
|
337
|
+
return board ? okBoard(board) : fail("Board not found.");
|
|
338
|
+
}
|
|
339
|
+
case "create_board": {
|
|
340
|
+
if (!input.title) return fail("create_board requires title.");
|
|
341
|
+
const board = await createBoard2(projectRoot, {
|
|
342
|
+
title: input.title,
|
|
343
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
344
|
+
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
345
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {}
|
|
346
|
+
});
|
|
347
|
+
return { ok: true, message: `Board created: ${board.title}`, board };
|
|
348
|
+
}
|
|
349
|
+
case "update_board": {
|
|
350
|
+
if (!input.boardId) return fail("update_board requires boardId.");
|
|
351
|
+
const board = await updateBoard2(projectRoot, input.boardId, {
|
|
352
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
353
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
354
|
+
...input.tags !== void 0 ? { tags: input.tags } : {}
|
|
355
|
+
});
|
|
356
|
+
return board ? okBoard(board, "Board updated.") : fail("Board not found.");
|
|
357
|
+
}
|
|
358
|
+
case "duplicate_board": {
|
|
359
|
+
if (!input.boardId) return fail("duplicate_board requires boardId.");
|
|
360
|
+
const board = await duplicateBoard(projectRoot, input.boardId, {
|
|
361
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
362
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
363
|
+
...input.includeTasks !== void 0 ? { includeTasks: input.includeTasks } : {},
|
|
364
|
+
...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {},
|
|
365
|
+
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {}
|
|
366
|
+
});
|
|
367
|
+
return board ? okBoard(board, "Board duplicated.") : fail("Board not found.");
|
|
368
|
+
}
|
|
369
|
+
case "delete_board": {
|
|
370
|
+
if (!input.boardId) return fail("delete_board requires boardId.");
|
|
371
|
+
const removed = await removeBoard2(projectRoot, input.boardId);
|
|
372
|
+
return { ok: removed, message: removed ? "Board deleted." : "Board not found." };
|
|
373
|
+
}
|
|
374
|
+
case "generate_board": {
|
|
375
|
+
if (!input.description) return fail("generate_board requires description.");
|
|
376
|
+
const boardInput = generateBoardFromDescription({
|
|
377
|
+
description: input.description,
|
|
378
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
379
|
+
...input.context !== void 0 ? { context: input.context } : {},
|
|
380
|
+
...input.columns !== void 0 ? { columns: input.columns } : {}
|
|
381
|
+
});
|
|
382
|
+
const board = await createBoard2(projectRoot, boardInput);
|
|
383
|
+
for (const taskInput2 of parseLinesIntoTasks(
|
|
384
|
+
input.description,
|
|
385
|
+
board.columns[0]?.id ?? "backlog"
|
|
386
|
+
)) {
|
|
387
|
+
await addTask(projectRoot, board.id, taskInput2);
|
|
449
388
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
return
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
389
|
+
return okBoard(await getBoard2(projectRoot, board.id) ?? board, "Board generated.");
|
|
390
|
+
}
|
|
391
|
+
case "export_markdown": {
|
|
392
|
+
const board = await requireBoard(projectRoot, input.boardId);
|
|
393
|
+
if (!board) return fail("Board not found.");
|
|
394
|
+
return {
|
|
395
|
+
ok: true,
|
|
396
|
+
message: "Board exported.",
|
|
397
|
+
board,
|
|
398
|
+
markdown: exportBoardAsMarkdown(board)
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
case "export_task_graph": {
|
|
402
|
+
if (!input.boardId) return fail("export_task_graph requires boardId.");
|
|
403
|
+
const exported = await exportBoardToTaskGraph(projectRoot, input.boardId, {
|
|
404
|
+
...input.graphId !== void 0 ? { graphId: input.graphId } : {},
|
|
405
|
+
...input.specId !== void 0 ? { specId: input.specId } : {},
|
|
406
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
407
|
+
...input.preserveOriginTaskIds !== void 0 ? { preserveOriginTaskIds: input.preserveOriginTaskIds } : {},
|
|
408
|
+
...input.includeArchived !== void 0 ? { includeArchived: input.includeArchived } : {}
|
|
409
|
+
});
|
|
410
|
+
if (!exported) return fail("Board not found.");
|
|
411
|
+
return {
|
|
412
|
+
ok: true,
|
|
413
|
+
message: `Task graph exported with ${exported.graph.nodes.size} node(s).`,
|
|
414
|
+
board: exported.board,
|
|
415
|
+
taskGraph: serializeTaskGraph(exported.graph)
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
case "sync_task_graph": {
|
|
419
|
+
if (!input.boardId || !input.taskGraph) {
|
|
420
|
+
return fail("sync_task_graph requires boardId and taskGraph.");
|
|
421
|
+
}
|
|
422
|
+
const graph = deserializeTaskGraph2(input.taskGraph);
|
|
423
|
+
const result2 = await syncBoardFromTaskGraph2(projectRoot, input.boardId, graph, {
|
|
424
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
425
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
426
|
+
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
427
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
428
|
+
...input.sourceSystem !== void 0 ? { sourceSystem: input.sourceSystem } : {},
|
|
429
|
+
...input.phaseId !== void 0 ? { phaseId: input.phaseId } : {},
|
|
430
|
+
...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {},
|
|
431
|
+
...input.archiveMissingTasks !== void 0 ? { archiveMissingTasks: input.archiveMissingTasks } : {},
|
|
432
|
+
...input.preserveManualDependencies !== void 0 ? { preserveManualDependencies: input.preserveManualDependencies } : {}
|
|
433
|
+
});
|
|
434
|
+
return result2 ? {
|
|
435
|
+
ok: true,
|
|
436
|
+
message: `Task graph synced: ${result2.createdTaskIds.length} created, ${result2.updatedTaskIds.length} updated, ${result2.archivedTaskIds.length} archived.`,
|
|
437
|
+
board: result2.board
|
|
438
|
+
} : fail("Board not found.");
|
|
439
|
+
}
|
|
440
|
+
case "create_from_graph": {
|
|
441
|
+
if (!input.taskGraph) return fail("create_from_graph requires taskGraph.");
|
|
442
|
+
const graph = deserializeTaskGraph2(input.taskGraph);
|
|
443
|
+
const { board } = await createBoardFromTaskGraph(projectRoot, graph, {
|
|
444
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
445
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
446
|
+
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
447
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
448
|
+
...input.sourceSystem !== void 0 ? { sourceSystem: input.sourceSystem } : {},
|
|
449
|
+
...input.phaseId !== void 0 ? { phaseId: input.phaseId } : {},
|
|
450
|
+
...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {}
|
|
451
|
+
});
|
|
452
|
+
return {
|
|
453
|
+
ok: true,
|
|
454
|
+
message: `Created board "${board.title}" from task graph with ${board.tasks.length} tasks.`,
|
|
455
|
+
board
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
case "import_session_tasks": {
|
|
459
|
+
const taskPath = ctx.meta?.["task.path"];
|
|
460
|
+
if (!taskPath) return fail("No session task file for this session.");
|
|
461
|
+
const file = await loadTasks2(taskPath);
|
|
462
|
+
if (!file || file.tasks.length === 0) return fail("No session tasks to import.");
|
|
463
|
+
const sessionId = ctx.session?.id ?? file.sessionId ?? "session";
|
|
464
|
+
const graph = deserializeTaskGraph2(taskFileToSerializedGraph(file.tasks, sessionId));
|
|
465
|
+
const tags = ["session", `session:${sessionId}`];
|
|
466
|
+
const existing = (await listBoards2(projectRoot)).find(
|
|
467
|
+
(b) => b.tags?.includes(`session:${sessionId}`)
|
|
468
|
+
);
|
|
469
|
+
if (existing) {
|
|
470
|
+
const result2 = await syncBoardFromTaskGraph2(projectRoot, existing.id, graph, {
|
|
471
|
+
sourceSystem: "session",
|
|
472
|
+
tags,
|
|
473
|
+
archiveMissingTasks: true,
|
|
474
|
+
includeCompletedTasks: true
|
|
475
|
+
});
|
|
476
|
+
return result2 ? {
|
|
477
|
+
ok: true,
|
|
478
|
+
message: `Synced ${file.tasks.length} session tasks into board "${result2.board.title}".`,
|
|
479
|
+
board: result2.board
|
|
480
|
+
} : fail("Session board vanished mid-sync.");
|
|
481
|
+
}
|
|
482
|
+
const { board } = await createBoardFromTaskGraph(projectRoot, graph, {
|
|
483
|
+
title: `Session tasks (${sessionId.slice(0, 8)})`,
|
|
484
|
+
sourceSystem: "session",
|
|
485
|
+
tags
|
|
486
|
+
});
|
|
487
|
+
return {
|
|
488
|
+
ok: true,
|
|
489
|
+
message: `Imported ${file.tasks.length} session tasks into new board "${board.title}".`,
|
|
490
|
+
board
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
case "search_tasks": {
|
|
494
|
+
const tasks = await searchKanban(projectRoot, {
|
|
495
|
+
query: input.query,
|
|
496
|
+
boardId: input.boardId,
|
|
497
|
+
assignedAgent: input.agentId,
|
|
498
|
+
status: input.status,
|
|
499
|
+
priority: input.priority,
|
|
500
|
+
label: input.labels?.[0],
|
|
501
|
+
chainId: input.chainId
|
|
502
|
+
});
|
|
503
|
+
return { ok: true, message: `${tasks.length} task(s) matched.`, tasks };
|
|
504
|
+
}
|
|
505
|
+
case "ready_tasks": {
|
|
506
|
+
const tasks = await listReadyTasks(projectRoot, {
|
|
507
|
+
query: input.query,
|
|
508
|
+
boardId: input.boardId,
|
|
509
|
+
assignedAgent: input.agentId,
|
|
510
|
+
priority: input.priority,
|
|
511
|
+
label: input.labels?.[0],
|
|
512
|
+
chainId: input.chainId,
|
|
513
|
+
limit: input.limit
|
|
514
|
+
});
|
|
515
|
+
return { ok: true, message: `${tasks.length} ready task(s).`, tasks };
|
|
516
|
+
}
|
|
517
|
+
case "snapshot": {
|
|
518
|
+
const snapshot = await getKanbanOrchestrationSnapshot(projectRoot, {
|
|
519
|
+
query: input.query,
|
|
520
|
+
boardId: input.boardId,
|
|
521
|
+
assignedAgent: input.agentId,
|
|
522
|
+
status: input.status,
|
|
523
|
+
priority: input.priority,
|
|
524
|
+
label: input.labels?.[0],
|
|
525
|
+
chainId: input.chainId
|
|
526
|
+
});
|
|
527
|
+
return {
|
|
528
|
+
ok: true,
|
|
529
|
+
message: `${snapshot.ready.length} ready, ${snapshot.running.length} running, ${snapshot.blocked.length} blocked.`,
|
|
530
|
+
snapshot
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
case "add_column": {
|
|
534
|
+
if (!input.boardId || !input.title) return fail("add_column requires boardId and title.");
|
|
535
|
+
const result2 = await addColumn(projectRoot, input.boardId, {
|
|
536
|
+
title: input.title,
|
|
537
|
+
...input.description !== void 0 ? { description: input.description } : {}
|
|
538
|
+
});
|
|
539
|
+
return result2 ? okBoard(result2.board, "Column added.") : fail("Board not found.");
|
|
540
|
+
}
|
|
541
|
+
case "update_column": {
|
|
542
|
+
if (!input.boardId || !input.columnId)
|
|
543
|
+
return fail("update_column requires boardId and columnId.");
|
|
544
|
+
const board = await updateColumn(projectRoot, input.boardId, input.columnId, {
|
|
545
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
546
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
547
|
+
...input.order !== void 0 ? { order: input.order } : {}
|
|
548
|
+
});
|
|
549
|
+
return board ? okBoard(board, "Column updated.") : fail("Column not found.");
|
|
550
|
+
}
|
|
551
|
+
case "delete_column": {
|
|
552
|
+
if (!input.boardId || !input.columnId)
|
|
553
|
+
return fail("delete_column requires boardId and columnId.");
|
|
554
|
+
const board = await removeColumn(projectRoot, input.boardId, input.columnId, {
|
|
555
|
+
moveTasksToColumnId: input.moveTasksToColumnId
|
|
556
|
+
});
|
|
557
|
+
return board ? okBoard(board, "Column deleted.") : fail("Column not found.");
|
|
558
|
+
}
|
|
559
|
+
case "add_task": {
|
|
560
|
+
if (!input.boardId || !input.title) return fail("add_task requires boardId and title.");
|
|
561
|
+
const result2 = await addTask(projectRoot, input.boardId, taskInput(input));
|
|
562
|
+
return result2 ? okTask(result2.board, result2.task, "Task added.") : fail("Board not found.");
|
|
563
|
+
}
|
|
564
|
+
case "split_task": {
|
|
565
|
+
if (!input.boardId || !input.taskId || !input.childTitles?.length) {
|
|
566
|
+
return fail("split_task requires boardId, taskId, and childTitles.");
|
|
567
|
+
}
|
|
568
|
+
const result2 = await splitTask(projectRoot, input.boardId, input.taskId, {
|
|
569
|
+
titles: input.childTitles,
|
|
570
|
+
...input.targetColumnId !== void 0 ? { columnId: input.targetColumnId } : {},
|
|
571
|
+
...input.inheritAssignment !== void 0 ? { inheritAssignment: input.inheritAssignment } : {},
|
|
572
|
+
...input.inheritLabels !== void 0 ? { inheritLabels: input.inheritLabels } : {},
|
|
573
|
+
...input.inheritSuccessCriteria !== void 0 ? { inheritSuccessCriteria: input.inheritSuccessCriteria } : {},
|
|
574
|
+
...input.inheritGoalMetrics !== void 0 ? { inheritGoalMetrics: input.inheritGoalMetrics } : {},
|
|
575
|
+
...input.inheritDependencies !== void 0 ? { inheritDependencies: input.inheritDependencies } : {},
|
|
576
|
+
...input.chainChildren !== void 0 ? { chainChildren: input.chainChildren } : {},
|
|
577
|
+
...input.rewireDependents !== void 0 ? { rewireDependents: input.rewireDependents } : {}
|
|
578
|
+
});
|
|
579
|
+
return result2 ? {
|
|
580
|
+
ok: true,
|
|
581
|
+
message: `${result2.children.length} child task(s) created.`,
|
|
582
|
+
board: result2.board,
|
|
583
|
+
task: result2.parent,
|
|
584
|
+
children: result2.children
|
|
585
|
+
} : fail("Task not found.");
|
|
586
|
+
}
|
|
587
|
+
case "merge_tasks": {
|
|
588
|
+
if (!input.boardId || !input.taskIds?.length || !input.title) {
|
|
589
|
+
return fail("merge_tasks requires boardId, taskIds, and title.");
|
|
590
|
+
}
|
|
591
|
+
const result2 = await mergeTasks(projectRoot, input.boardId, {
|
|
592
|
+
taskIds: input.taskIds,
|
|
593
|
+
title: input.title,
|
|
594
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
463
595
|
...input.targetColumnId !== void 0 ? { targetColumnId: input.targetColumnId } : {},
|
|
464
|
-
...input.order !== void 0 ? { targetOrder: input.order } : {},
|
|
465
596
|
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {},
|
|
466
|
-
...input.
|
|
597
|
+
...input.closeSourceTasks !== void 0 ? { closeSourceTasks: input.closeSourceTasks } : {}
|
|
598
|
+
});
|
|
599
|
+
return result2 ? okTask(result2.board, result2.task, "Tasks merged.") : fail("Board or task not found.");
|
|
600
|
+
}
|
|
601
|
+
case "copy_task": {
|
|
602
|
+
if (!input.boardId || !input.taskId || !input.targetBoardId) {
|
|
603
|
+
return fail("copy_task requires boardId, taskId, and targetBoardId.");
|
|
467
604
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
return fail("update_task requires boardId and taskId.");
|
|
479
|
-
const board = await updateTask(
|
|
480
|
-
projectRoot,
|
|
481
|
-
input.boardId,
|
|
482
|
-
input.taskId,
|
|
483
|
-
taskPatch(input)
|
|
484
|
-
);
|
|
485
|
-
return board ? okBoard(board, "Task updated.") : fail("Task not found.");
|
|
486
|
-
}
|
|
487
|
-
case "move_task": {
|
|
488
|
-
if (!input.boardId || !input.taskId || !input.targetColumnId) {
|
|
489
|
-
return fail("move_task requires boardId, taskId, and targetColumnId.");
|
|
490
|
-
}
|
|
491
|
-
const board = await moveTask(
|
|
492
|
-
projectRoot,
|
|
493
|
-
input.boardId,
|
|
494
|
-
input.taskId,
|
|
495
|
-
input.targetColumnId,
|
|
496
|
-
input.order
|
|
497
|
-
);
|
|
498
|
-
return board ? okBoard(board, "Task moved.") : fail("Move failed.");
|
|
499
|
-
}
|
|
500
|
-
case "delete_task": {
|
|
501
|
-
if (!input.boardId || !input.taskId)
|
|
502
|
-
return fail("delete_task requires boardId and taskId.");
|
|
503
|
-
const board = await removeTask(projectRoot, input.boardId, input.taskId);
|
|
504
|
-
return board ? okBoard(board, "Task deleted.") : fail("Task not found.");
|
|
505
|
-
}
|
|
506
|
-
case "set_chain": {
|
|
507
|
-
if (!input.boardId || !input.taskIds?.length) {
|
|
508
|
-
return fail("set_chain requires boardId and taskIds.");
|
|
509
|
-
}
|
|
510
|
-
const result = await setTaskChain(projectRoot, input.boardId, {
|
|
511
|
-
taskIds: input.taskIds,
|
|
512
|
-
...input.chainId !== void 0 ? { chainId: input.chainId } : {},
|
|
513
|
-
...input.enforceDependencies !== void 0 ? { enforceDependencies: input.enforceDependencies } : {}
|
|
514
|
-
});
|
|
515
|
-
return result ? {
|
|
516
|
-
ok: true,
|
|
517
|
-
message: `Chain set: ${result.chainId}`,
|
|
518
|
-
board: result.board,
|
|
519
|
-
chain: result.tasks
|
|
520
|
-
} : fail("Board or task not found.");
|
|
521
|
-
}
|
|
522
|
-
case "get_chain": {
|
|
523
|
-
if (!input.boardId || !(input.taskId || input.chainId)) {
|
|
524
|
-
return fail("get_chain requires boardId and taskId or chainId.");
|
|
525
|
-
}
|
|
526
|
-
const result = await getTaskChain(
|
|
527
|
-
projectRoot,
|
|
528
|
-
input.boardId,
|
|
529
|
-
input.taskId ?? input.chainId ?? ""
|
|
530
|
-
);
|
|
531
|
-
return result ? {
|
|
532
|
-
ok: true,
|
|
533
|
-
message: `Chain loaded: ${result.chainId}`,
|
|
534
|
-
board: result.board,
|
|
535
|
-
chain: result.tasks
|
|
536
|
-
} : fail("Chain not found.");
|
|
537
|
-
}
|
|
538
|
-
case "claim_task": {
|
|
539
|
-
const result = await claimReadyTask(projectRoot, {
|
|
540
|
-
...input.boardId !== void 0 ? { boardId: input.boardId } : {},
|
|
541
|
-
...input.taskId !== void 0 ? { taskId: input.taskId } : {},
|
|
542
|
-
...assignmentInput(input),
|
|
543
|
-
status: input.assignmentStatus ?? "queued"
|
|
544
|
-
});
|
|
545
|
-
return result ? okTask(result.board, result.task, "Task claimed.") : fail("No ready kanban task matched the claim.");
|
|
546
|
-
}
|
|
547
|
-
case "release_task": {
|
|
548
|
-
if (!input.boardId || !input.taskId) {
|
|
549
|
-
return fail("release_task requires boardId and taskId.");
|
|
550
|
-
}
|
|
551
|
-
const board = await releaseTaskClaim(projectRoot, input.boardId, input.taskId, {
|
|
552
|
-
...input.releaseStatus !== void 0 ? { status: input.releaseStatus } : {},
|
|
553
|
-
...input.releaseReason !== void 0 ? { reason: input.releaseReason } : {},
|
|
554
|
-
...input.clearAssignee !== void 0 ? { clearAssignee: input.clearAssignee } : {}
|
|
555
|
-
});
|
|
556
|
-
return board ? okBoard(board, "Task claim released.") : fail("Task not found.");
|
|
557
|
-
}
|
|
558
|
-
case "assign_task": {
|
|
559
|
-
if (!input.boardId || !input.taskId)
|
|
560
|
-
return fail("assign_task requires boardId and taskId.");
|
|
561
|
-
const board = await assignTask(
|
|
562
|
-
projectRoot,
|
|
563
|
-
input.boardId,
|
|
564
|
-
input.taskId,
|
|
565
|
-
assignmentInput(input)
|
|
566
|
-
);
|
|
567
|
-
return board ? okBoard(board, "Task assigned.") : fail("Task not found.");
|
|
568
|
-
}
|
|
569
|
-
case "mark_assignment": {
|
|
570
|
-
if (!input.boardId || !input.taskId)
|
|
571
|
-
return fail("mark_assignment requires boardId and taskId.");
|
|
572
|
-
const assignmentStatus = input.assignmentStatus ?? (input.status === "completed" ? "completed" : input.error ? "failed" : void 0);
|
|
573
|
-
const board = await updateTaskAssignment(projectRoot, input.boardId, input.taskId, {
|
|
574
|
-
...assignmentStatus !== void 0 ? { status: assignmentStatus } : {},
|
|
575
|
-
...input.subagentId !== void 0 ? { subagentId: input.subagentId } : {},
|
|
576
|
-
...input.runTaskId !== void 0 ? { runTaskId: input.runTaskId } : {},
|
|
577
|
-
...input.lastResult !== void 0 ? { lastResult: input.lastResult } : {},
|
|
578
|
-
...input.error !== void 0 ? { error: input.error } : {},
|
|
579
|
-
...input.agentId !== void 0 ? { agentId: input.agentId } : {},
|
|
580
|
-
...input.leaseId !== void 0 ? { leaseId: input.leaseId } : {},
|
|
581
|
-
...input.claimedAt !== void 0 ? { claimedAt: input.claimedAt } : {},
|
|
582
|
-
...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
|
|
583
|
-
...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {},
|
|
584
|
-
...input.attempt !== void 0 ? { attempt: input.attempt } : {},
|
|
585
|
-
...input.maxAttempts !== void 0 ? { maxAttempts: input.maxAttempts } : {}
|
|
586
|
-
});
|
|
587
|
-
return board ? okBoard(board, "Assignment updated.") : fail("Task not found.");
|
|
588
|
-
}
|
|
589
|
-
case "heartbeat_assignment": {
|
|
590
|
-
if (!input.boardId || !input.taskId) {
|
|
591
|
-
return fail("heartbeat_assignment requires boardId and taskId.");
|
|
592
|
-
}
|
|
593
|
-
const board = await heartbeatTaskAssignment(projectRoot, input.boardId, input.taskId, {
|
|
594
|
-
...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
|
|
595
|
-
...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {}
|
|
596
|
-
});
|
|
597
|
-
return board ? okBoard(board, "Assignment heartbeat updated.") : fail("Task assignment not found.");
|
|
598
|
-
}
|
|
599
|
-
case "recover_stale": {
|
|
600
|
-
if (!input.boardId) return fail("recover_stale requires boardId.");
|
|
601
|
-
const policyFields = [
|
|
602
|
-
input.recoveryPolicyFailOnCostCeiling !== void 0,
|
|
603
|
-
input.recoveryPolicyReleaseOnFailureKinds !== void 0,
|
|
604
|
-
input.recoveryPolicyReleaseOnHeartbeatDue !== void 0,
|
|
605
|
-
input.recoveryPolicyRetryPolicyOverride !== void 0
|
|
606
|
-
].some(Boolean);
|
|
607
|
-
const result = await recoverStaleTaskAssignments(projectRoot, input.boardId, {
|
|
608
|
-
...input.recoveryMode !== void 0 ? { mode: input.recoveryMode } : {},
|
|
609
|
-
...input.recoveryNow !== void 0 ? { now: input.recoveryNow } : {},
|
|
610
|
-
...input.releaseReason !== void 0 ? { reason: input.releaseReason } : {},
|
|
611
|
-
...input.clearAssignee !== void 0 ? { clearAssignee: input.clearAssignee } : {},
|
|
612
|
-
...policyFields ? {
|
|
613
|
-
policy: {
|
|
614
|
-
...input.recoveryPolicyFailOnCostCeiling !== void 0 ? { failWhenCostCeilingSet: input.recoveryPolicyFailOnCostCeiling } : {},
|
|
615
|
-
...input.recoveryPolicyReleaseOnFailureKinds !== void 0 ? {
|
|
616
|
-
releaseOnFailureKinds: input.recoveryPolicyReleaseOnFailureKinds
|
|
617
|
-
} : {},
|
|
618
|
-
...input.recoveryPolicyReleaseOnHeartbeatDue !== void 0 ? {
|
|
619
|
-
releaseOnHeartbeatDue: input.recoveryPolicyReleaseOnHeartbeatDue
|
|
620
|
-
} : {},
|
|
621
|
-
...input.recoveryPolicyRetryPolicyOverride !== void 0 ? {
|
|
622
|
-
retryPolicyOverride: input.recoveryPolicyRetryPolicyOverride
|
|
623
|
-
} : {}
|
|
605
|
+
const result2 = await copyTaskToBoard(
|
|
606
|
+
projectRoot,
|
|
607
|
+
input.boardId,
|
|
608
|
+
input.taskId,
|
|
609
|
+
input.targetBoardId,
|
|
610
|
+
{
|
|
611
|
+
...input.targetColumnId !== void 0 ? { targetColumnId: input.targetColumnId } : {},
|
|
612
|
+
...input.order !== void 0 ? { targetOrder: input.order } : {},
|
|
613
|
+
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {},
|
|
614
|
+
...input.preserveDependencies !== void 0 ? { preserveDependencies: input.preserveDependencies } : {}
|
|
624
615
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
input.boardId
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
616
|
+
);
|
|
617
|
+
return result2 ? okTask(result2.targetBoard, result2.task, "Task copied to target board.") : fail("Board or task not found.");
|
|
618
|
+
}
|
|
619
|
+
case "transfer_task": {
|
|
620
|
+
if (!input.boardId || !input.taskId || !input.targetBoardId) {
|
|
621
|
+
return fail("transfer_task requires boardId, taskId, and targetBoardId.");
|
|
622
|
+
}
|
|
623
|
+
const result2 = await transferTaskToBoard(
|
|
624
|
+
projectRoot,
|
|
625
|
+
input.boardId,
|
|
626
|
+
input.taskId,
|
|
627
|
+
input.targetBoardId,
|
|
628
|
+
{
|
|
629
|
+
...input.targetColumnId !== void 0 ? { targetColumnId: input.targetColumnId } : {},
|
|
630
|
+
...input.order !== void 0 ? { targetOrder: input.order } : {},
|
|
631
|
+
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {},
|
|
632
|
+
...input.preserveDependencies !== void 0 ? { preserveDependencies: input.preserveDependencies } : {}
|
|
633
|
+
}
|
|
634
|
+
);
|
|
635
|
+
return result2 ? okTask(result2.targetBoard, result2.task, "Task transferred to target board.") : fail("Board or task not found.");
|
|
636
|
+
}
|
|
637
|
+
case "get_task": {
|
|
638
|
+
if (!input.boardId || !input.taskId) return fail("get_task requires boardId and taskId.");
|
|
639
|
+
const task = await getTask(projectRoot, input.boardId, input.taskId);
|
|
640
|
+
return task ? { ok: true, message: "Task loaded.", task } : fail("Task not found.");
|
|
641
|
+
}
|
|
642
|
+
case "update_task": {
|
|
643
|
+
if (!input.boardId || !input.taskId)
|
|
644
|
+
return fail("update_task requires boardId and taskId.");
|
|
645
|
+
const board = await updateTask(
|
|
646
|
+
projectRoot,
|
|
647
|
+
input.boardId,
|
|
648
|
+
input.taskId,
|
|
649
|
+
taskPatch(input)
|
|
650
|
+
);
|
|
651
|
+
return board ? okBoard(board, "Task updated.") : fail("Task not found.");
|
|
652
|
+
}
|
|
653
|
+
case "transition_task": {
|
|
654
|
+
if (!input.boardId || !input.taskId || !input.lifecycleStage || !input.author || !input.transitionComment) {
|
|
655
|
+
return fail(
|
|
656
|
+
"transition_task requires boardId, taskId, lifecycleStage, author, and transitionComment."
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
const result2 = await transitionTask(projectRoot, input.boardId, input.taskId, {
|
|
660
|
+
to: input.lifecycleStage,
|
|
661
|
+
actor: input.author,
|
|
662
|
+
comment: input.transitionComment,
|
|
663
|
+
...input.transitionAction !== void 0 ? { action: input.transitionAction } : {},
|
|
664
|
+
...input.attachmentUrl !== void 0 ? {
|
|
665
|
+
attachment: {
|
|
666
|
+
url: input.attachmentUrl,
|
|
667
|
+
type: input.attachmentType ?? "url",
|
|
668
|
+
...input.attachmentTitle !== void 0 ? { title: input.attachmentTitle } : {}
|
|
669
|
+
}
|
|
670
|
+
} : {},
|
|
671
|
+
patch: taskPatch(input)
|
|
672
|
+
});
|
|
673
|
+
return result2 ? okTask(result2.board, result2.task, `Task advanced to ${result2.transition.to}.`) : fail("Board or task not found.");
|
|
674
|
+
}
|
|
675
|
+
case "move_task": {
|
|
676
|
+
if (!input.boardId || !input.taskId || !input.targetColumnId) {
|
|
677
|
+
return fail("move_task requires boardId, taskId, and targetColumnId.");
|
|
678
|
+
}
|
|
679
|
+
const board = await moveTask(
|
|
680
|
+
projectRoot,
|
|
681
|
+
input.boardId,
|
|
682
|
+
input.taskId,
|
|
683
|
+
input.targetColumnId,
|
|
684
|
+
input.order
|
|
685
|
+
);
|
|
686
|
+
return board ? okBoard(board, "Task moved.") : fail("Move failed.");
|
|
687
|
+
}
|
|
688
|
+
case "delete_task": {
|
|
689
|
+
if (!input.boardId || !input.taskId)
|
|
690
|
+
return fail("delete_task requires boardId and taskId.");
|
|
691
|
+
const board = await removeTask(projectRoot, input.boardId, input.taskId);
|
|
692
|
+
return board ? okBoard(board, "Task deleted.") : fail("Task not found.");
|
|
693
|
+
}
|
|
694
|
+
case "set_chain": {
|
|
695
|
+
if (!input.boardId || !input.taskIds?.length) {
|
|
696
|
+
return fail("set_chain requires boardId and taskIds.");
|
|
697
|
+
}
|
|
698
|
+
const result2 = await setTaskChain(projectRoot, input.boardId, {
|
|
699
|
+
taskIds: input.taskIds,
|
|
700
|
+
...input.chainId !== void 0 ? { chainId: input.chainId } : {},
|
|
701
|
+
...input.enforceDependencies !== void 0 ? { enforceDependencies: input.enforceDependencies } : {}
|
|
702
|
+
});
|
|
703
|
+
return result2 ? {
|
|
704
|
+
ok: true,
|
|
705
|
+
message: `Chain set: ${result2.chainId}`,
|
|
706
|
+
board: result2.board,
|
|
707
|
+
chain: result2.tasks
|
|
708
|
+
} : fail("Board or task not found.");
|
|
709
|
+
}
|
|
710
|
+
case "get_chain": {
|
|
711
|
+
if (!input.boardId || !(input.taskId || input.chainId)) {
|
|
712
|
+
return fail("get_chain requires boardId and taskId or chainId.");
|
|
713
|
+
}
|
|
714
|
+
const result2 = await getTaskChain(
|
|
715
|
+
projectRoot,
|
|
716
|
+
input.boardId,
|
|
717
|
+
input.taskId ?? input.chainId ?? ""
|
|
718
|
+
);
|
|
719
|
+
return result2 ? {
|
|
720
|
+
ok: true,
|
|
721
|
+
message: `Chain loaded: ${result2.chainId}`,
|
|
722
|
+
board: result2.board,
|
|
723
|
+
chain: result2.tasks
|
|
724
|
+
} : fail("Chain not found.");
|
|
725
|
+
}
|
|
726
|
+
case "claim_task": {
|
|
727
|
+
const result2 = await claimReadyTask(projectRoot, {
|
|
728
|
+
...input.boardId !== void 0 ? { boardId: input.boardId } : {},
|
|
729
|
+
...input.taskId !== void 0 ? { taskId: input.taskId } : {},
|
|
730
|
+
...assignmentInput(input),
|
|
731
|
+
status: input.assignmentStatus ?? "queued"
|
|
732
|
+
});
|
|
733
|
+
return result2 ? okTask(result2.board, result2.task, "Task claimed.") : fail("No ready kanban task matched the claim.");
|
|
734
|
+
}
|
|
735
|
+
case "release_task": {
|
|
736
|
+
if (!input.boardId || !input.taskId) {
|
|
737
|
+
return fail("release_task requires boardId and taskId.");
|
|
738
|
+
}
|
|
739
|
+
const board = await releaseTaskClaim(projectRoot, input.boardId, input.taskId, {
|
|
740
|
+
...input.releaseStatus !== void 0 ? { status: input.releaseStatus } : {},
|
|
741
|
+
...input.releaseReason !== void 0 ? { reason: input.releaseReason } : {},
|
|
742
|
+
...input.clearAssignee !== void 0 ? { clearAssignee: input.clearAssignee } : {}
|
|
743
|
+
});
|
|
744
|
+
return board ? okBoard(board, "Task claim released.") : fail("Task not found.");
|
|
745
|
+
}
|
|
746
|
+
case "assign_task": {
|
|
747
|
+
if (!input.boardId || !input.taskId)
|
|
748
|
+
return fail("assign_task requires boardId and taskId.");
|
|
749
|
+
const board = await assignTask(
|
|
750
|
+
projectRoot,
|
|
751
|
+
input.boardId,
|
|
752
|
+
input.taskId,
|
|
753
|
+
assignmentInput(input)
|
|
754
|
+
);
|
|
755
|
+
return board ? okBoard(board, "Task assigned.") : fail("Task not found.");
|
|
756
|
+
}
|
|
757
|
+
case "mark_assignment": {
|
|
758
|
+
if (!input.boardId || !input.taskId)
|
|
759
|
+
return fail("mark_assignment requires boardId and taskId.");
|
|
760
|
+
const assignmentStatus = input.assignmentStatus ?? (input.status === "completed" ? "completed" : input.error ? "failed" : void 0);
|
|
761
|
+
const board = await updateTaskAssignment(projectRoot, input.boardId, input.taskId, {
|
|
762
|
+
...assignmentStatus !== void 0 ? { status: assignmentStatus } : {},
|
|
763
|
+
...input.subagentId !== void 0 ? { subagentId: input.subagentId } : {},
|
|
764
|
+
...input.runTaskId !== void 0 ? { runTaskId: input.runTaskId } : {},
|
|
765
|
+
...input.lastResult !== void 0 ? { lastResult: input.lastResult } : {},
|
|
766
|
+
...input.error !== void 0 ? { error: input.error } : {},
|
|
767
|
+
...input.agentId !== void 0 ? { agentId: input.agentId } : {},
|
|
768
|
+
...input.leaseId !== void 0 ? { leaseId: input.leaseId } : {},
|
|
769
|
+
...input.claimedAt !== void 0 ? { claimedAt: input.claimedAt } : {},
|
|
770
|
+
...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
|
|
771
|
+
...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {},
|
|
772
|
+
...input.attempt !== void 0 ? { attempt: input.attempt } : {},
|
|
773
|
+
...input.maxAttempts !== void 0 ? { maxAttempts: input.maxAttempts } : {}
|
|
774
|
+
});
|
|
775
|
+
return board ? okBoard(board, "Assignment updated.") : fail("Task not found.");
|
|
776
|
+
}
|
|
777
|
+
case "heartbeat_assignment": {
|
|
778
|
+
if (!input.boardId || !input.taskId) {
|
|
779
|
+
return fail("heartbeat_assignment requires boardId and taskId.");
|
|
780
|
+
}
|
|
781
|
+
const board = await heartbeatTaskAssignment(projectRoot, input.boardId, input.taskId, {
|
|
782
|
+
...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
|
|
783
|
+
...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {}
|
|
784
|
+
});
|
|
785
|
+
return board ? okBoard(board, "Assignment heartbeat updated.") : fail("Task assignment not found.");
|
|
786
|
+
}
|
|
787
|
+
case "recover_stale": {
|
|
788
|
+
if (!input.boardId) return fail("recover_stale requires boardId.");
|
|
789
|
+
const policyFields = [
|
|
790
|
+
input.recoveryPolicyFailOnCostCeiling !== void 0,
|
|
791
|
+
input.recoveryPolicyReleaseOnFailureKinds !== void 0,
|
|
792
|
+
input.recoveryPolicyReleaseOnHeartbeatDue !== void 0,
|
|
793
|
+
input.recoveryPolicyRetryPolicyOverride !== void 0
|
|
794
|
+
].some(Boolean);
|
|
795
|
+
const result2 = await recoverStaleTaskAssignments(projectRoot, input.boardId, {
|
|
796
|
+
...input.recoveryMode !== void 0 ? { mode: input.recoveryMode } : {},
|
|
797
|
+
...input.recoveryNow !== void 0 ? { now: input.recoveryNow } : {},
|
|
798
|
+
...input.releaseReason !== void 0 ? { reason: input.releaseReason } : {},
|
|
799
|
+
...input.clearAssignee !== void 0 ? { clearAssignee: input.clearAssignee } : {},
|
|
800
|
+
...policyFields ? {
|
|
801
|
+
policy: {
|
|
802
|
+
...input.recoveryPolicyFailOnCostCeiling !== void 0 ? { failWhenCostCeilingSet: input.recoveryPolicyFailOnCostCeiling } : {},
|
|
803
|
+
...input.recoveryPolicyReleaseOnFailureKinds !== void 0 ? {
|
|
804
|
+
releaseOnFailureKinds: input.recoveryPolicyReleaseOnFailureKinds
|
|
805
|
+
} : {},
|
|
806
|
+
...input.recoveryPolicyReleaseOnHeartbeatDue !== void 0 ? {
|
|
807
|
+
releaseOnHeartbeatDue: input.recoveryPolicyReleaseOnHeartbeatDue
|
|
808
|
+
} : {},
|
|
809
|
+
...input.recoveryPolicyRetryPolicyOverride !== void 0 ? {
|
|
810
|
+
retryPolicyOverride: input.recoveryPolicyRetryPolicyOverride
|
|
811
|
+
} : {}
|
|
812
|
+
}
|
|
813
|
+
} : {}
|
|
814
|
+
});
|
|
815
|
+
return result2 ? {
|
|
816
|
+
ok: true,
|
|
817
|
+
message: `Recovered ${result2.tasks.length} stale assignment(s).`,
|
|
818
|
+
board: result2.board,
|
|
819
|
+
recoveredTasks: result2.tasks
|
|
820
|
+
} : { ok: true, message: "No stale assignment matched.", recoveredTasks: [] };
|
|
821
|
+
}
|
|
822
|
+
case "events": {
|
|
823
|
+
if (!input.boardId) return fail("events requires boardId.");
|
|
824
|
+
const eventList = await listKanbanEvents(projectRoot, input.boardId);
|
|
825
|
+
return {
|
|
826
|
+
ok: true,
|
|
827
|
+
message: `${eventList.length} event(s).`,
|
|
828
|
+
events: eventList
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
case "queue_health": {
|
|
832
|
+
const health = await getKanbanQueueHealth(projectRoot, {
|
|
833
|
+
...input.boardId !== void 0 ? { boardId: input.boardId } : {}
|
|
834
|
+
});
|
|
835
|
+
return {
|
|
836
|
+
ok: true,
|
|
837
|
+
message: `Counts: ready=${health.counts.ready}, running=${health.counts.running}, stale=${health.staleAssignments.count}.`,
|
|
838
|
+
queueHealth: health
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
case "add_dependency": {
|
|
842
|
+
if (!input.boardId || !input.taskId || !input.dependencyTaskId) {
|
|
843
|
+
return fail("add_dependency requires boardId, taskId, and dependencyTaskId.");
|
|
844
|
+
}
|
|
845
|
+
const board = await addDependency(
|
|
846
|
+
projectRoot,
|
|
847
|
+
input.boardId,
|
|
848
|
+
input.taskId,
|
|
849
|
+
input.dependencyTaskId
|
|
850
|
+
);
|
|
851
|
+
return board ? okBoard(board, "Dependency added.") : fail("Task not found.");
|
|
852
|
+
}
|
|
853
|
+
case "add_goal_metric": {
|
|
854
|
+
if (!input.boardId || !input.taskId || !input.metricName) {
|
|
855
|
+
return fail("add_goal_metric requires boardId, taskId, and metricName.");
|
|
856
|
+
}
|
|
857
|
+
const board = await addGoalMetricToTask(projectRoot, input.boardId, input.taskId, {
|
|
858
|
+
name: input.metricName,
|
|
690
859
|
...input.metricStatus !== void 0 ? { status: input.metricStatus } : {},
|
|
691
860
|
...input.metricTarget !== void 0 ? { target: input.metricTarget } : {},
|
|
692
861
|
...input.metricCurrent !== void 0 ? { current: input.metricCurrent } : {},
|
|
693
862
|
...input.metricUnit !== void 0 ? { unit: input.metricUnit } : {},
|
|
694
863
|
...input.metricNotes !== void 0 ? { notes: input.metricNotes } : {}
|
|
864
|
+
});
|
|
865
|
+
return board ? okBoard(board, "Goal metric added.") : fail("Task not found.");
|
|
866
|
+
}
|
|
867
|
+
case "update_goal_metric": {
|
|
868
|
+
if (!input.boardId || !input.taskId || !input.metricId) {
|
|
869
|
+
return fail("update_goal_metric requires boardId, taskId, and metricId.");
|
|
695
870
|
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
projectRoot,
|
|
716
|
-
input.boardId,
|
|
717
|
-
input.taskId,
|
|
718
|
-
input.checkId,
|
|
719
|
-
{
|
|
720
|
-
...input.checkDescription !== void 0 ? { description: input.checkDescription } : {},
|
|
721
|
-
...input.checkStatus !== void 0 ? { status: input.checkStatus } : {}
|
|
871
|
+
const board = await updateGoalMetricOnTask(
|
|
872
|
+
projectRoot,
|
|
873
|
+
input.boardId,
|
|
874
|
+
input.taskId,
|
|
875
|
+
input.metricId,
|
|
876
|
+
{
|
|
877
|
+
...input.metricName !== void 0 ? { name: input.metricName } : {},
|
|
878
|
+
...input.metricStatus !== void 0 ? { status: input.metricStatus } : {},
|
|
879
|
+
...input.metricTarget !== void 0 ? { target: input.metricTarget } : {},
|
|
880
|
+
...input.metricCurrent !== void 0 ? { current: input.metricCurrent } : {},
|
|
881
|
+
...input.metricUnit !== void 0 ? { unit: input.metricUnit } : {},
|
|
882
|
+
...input.metricNotes !== void 0 ? { notes: input.metricNotes } : {}
|
|
883
|
+
}
|
|
884
|
+
);
|
|
885
|
+
return board ? okBoard(board, "Goal metric updated.") : fail("Metric not found.");
|
|
886
|
+
}
|
|
887
|
+
case "add_check": {
|
|
888
|
+
if (!input.boardId || !input.taskId || !input.checkDescription) {
|
|
889
|
+
return fail("add_check requires boardId, taskId, and checkDescription.");
|
|
722
890
|
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
return
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
891
|
+
const board = await addCheckToTask(projectRoot, input.boardId, input.taskId, {
|
|
892
|
+
description: input.checkDescription,
|
|
893
|
+
type: "manual",
|
|
894
|
+
status: input.checkStatus
|
|
895
|
+
});
|
|
896
|
+
return board ? okBoard(board, "Check added.") : fail("Task not found.");
|
|
897
|
+
}
|
|
898
|
+
case "update_check": {
|
|
899
|
+
if (!input.boardId || !input.taskId || !input.checkId) {
|
|
900
|
+
return fail("update_check requires boardId, taskId, and checkId.");
|
|
901
|
+
}
|
|
902
|
+
const board = await updateCheckOnTask(
|
|
903
|
+
projectRoot,
|
|
904
|
+
input.boardId,
|
|
905
|
+
input.taskId,
|
|
906
|
+
input.checkId,
|
|
907
|
+
{
|
|
908
|
+
...input.checkDescription !== void 0 ? { description: input.checkDescription } : {},
|
|
909
|
+
...input.checkStatus !== void 0 ? { status: input.checkStatus } : {}
|
|
910
|
+
}
|
|
911
|
+
);
|
|
912
|
+
return board ? okBoard(board, "Check updated.") : fail("Check not found.");
|
|
913
|
+
}
|
|
914
|
+
case "add_note": {
|
|
915
|
+
if (!input.boardId || !input.taskId || !input.note)
|
|
916
|
+
return fail("add_note requires boardId, taskId, and note.");
|
|
917
|
+
const board = await addNoteToTask(projectRoot, input.boardId, input.taskId, {
|
|
918
|
+
author: input.author ?? "agent",
|
|
919
|
+
content: input.note
|
|
920
|
+
});
|
|
921
|
+
return board ? okBoard(board, "Note added.") : fail("Task not found.");
|
|
922
|
+
}
|
|
923
|
+
case "add_link": {
|
|
924
|
+
if (!input.boardId || !input.taskId || !input.url)
|
|
925
|
+
return fail("add_link requires boardId, taskId, and url.");
|
|
926
|
+
const board = await addLinkToTask(projectRoot, input.boardId, input.taskId, {
|
|
927
|
+
url: input.url,
|
|
928
|
+
type: input.linkType ?? "url",
|
|
929
|
+
...input.linkTitle !== void 0 ? { title: input.linkTitle } : {}
|
|
930
|
+
});
|
|
931
|
+
return board ? okBoard(board, "Link added.") : fail("Task not found.");
|
|
932
|
+
}
|
|
933
|
+
default:
|
|
934
|
+
return fail(`Unknown kanban action: ${input.action}`);
|
|
744
935
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
}
|
|
936
|
+
})();
|
|
937
|
+
return withPresence(result);
|
|
748
938
|
} catch (err) {
|
|
749
939
|
return fail(err instanceof Error ? err.message : String(err));
|
|
750
940
|
}
|
|
@@ -760,7 +950,7 @@ function okTask(board, task, message) {
|
|
|
760
950
|
return { ok: true, message, board, task };
|
|
761
951
|
}
|
|
762
952
|
async function requireBoard(projectRoot, boardId) {
|
|
763
|
-
return boardId ?
|
|
953
|
+
return boardId ? getBoard2(projectRoot, boardId) : null;
|
|
764
954
|
}
|
|
765
955
|
function taskInput(input) {
|
|
766
956
|
const assignment = hasAssignmentInput(input) ? assignmentForTaskCreate(input) : void 0;
|
|
@@ -768,26 +958,99 @@ function taskInput(input) {
|
|
|
768
958
|
title: input.title ?? "",
|
|
769
959
|
columnId: input.columnId,
|
|
770
960
|
description: input.description,
|
|
961
|
+
dueDate: input.dueDate,
|
|
771
962
|
priority: input.priority,
|
|
963
|
+
...input.taskType !== void 0 ? { type: input.taskType } : {},
|
|
772
964
|
status: input.status,
|
|
773
965
|
labels: input.labels,
|
|
774
966
|
...assignment?.agentId ?? assignment?.role ?? assignment?.name ? { assignedAgent: assignment.agentId ?? assignment.role ?? assignment.name } : {},
|
|
775
967
|
...input.assignee ?? assignment?.name ?? assignment?.agentId ? { assignee: input.assignee ?? assignment?.name ?? assignment?.agentId } : {},
|
|
776
|
-
...input
|
|
777
|
-
...
|
|
968
|
+
...mergedDependsOn(input) ? { dependsOn: mergedDependsOn(input) } : {},
|
|
969
|
+
...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {},
|
|
970
|
+
...input.actualHours !== void 0 ? { actualHours: input.actualHours } : {},
|
|
971
|
+
...assignment ? { assignment } : {},
|
|
972
|
+
...input.order !== void 0 ? { order: input.order } : {},
|
|
973
|
+
// Sprint 3 durable task-level fields
|
|
974
|
+
...input.retryPolicy !== void 0 ? { retryPolicy: input.retryPolicy } : {},
|
|
975
|
+
...input.costCeilingUsd !== void 0 ? { costCeilingUsd: input.costCeilingUsd } : {},
|
|
976
|
+
// Relationships — chainId alone cannot construct a KanbanTaskChainRef,
|
|
977
|
+
// so chain/previousTaskId/nextTaskId must be set via update_task.
|
|
978
|
+
...input.childTitles !== void 0 ? { childTaskIds: input.childTitles } : {},
|
|
979
|
+
// Detail objects
|
|
980
|
+
...input.checkDescription !== void 0 ? {
|
|
981
|
+
successCriteria: [
|
|
982
|
+
{
|
|
983
|
+
id: randomUUID(),
|
|
984
|
+
description: input.checkDescription,
|
|
985
|
+
type: "manual",
|
|
986
|
+
status: input.checkStatus ?? "pending"
|
|
987
|
+
}
|
|
988
|
+
]
|
|
989
|
+
} : {},
|
|
990
|
+
...input.metricName !== void 0 ? {
|
|
991
|
+
goalMetrics: [
|
|
992
|
+
{
|
|
993
|
+
id: randomUUID(),
|
|
994
|
+
name: input.metricName,
|
|
995
|
+
status: input.metricStatus ?? "pending",
|
|
996
|
+
...input.metricTarget !== void 0 ? { target: input.metricTarget } : {},
|
|
997
|
+
...input.metricCurrent !== void 0 ? { current: input.metricCurrent } : {},
|
|
998
|
+
...input.metricUnit !== void 0 ? { unit: input.metricUnit } : {},
|
|
999
|
+
...input.metricNotes !== void 0 ? { notes: input.metricNotes } : {}
|
|
1000
|
+
}
|
|
1001
|
+
]
|
|
1002
|
+
} : {},
|
|
1003
|
+
...input.url !== void 0 ? {
|
|
1004
|
+
links: [
|
|
1005
|
+
{
|
|
1006
|
+
url: input.url,
|
|
1007
|
+
type: input.linkType ?? "url",
|
|
1008
|
+
...input.linkTitle !== void 0 ? { title: input.linkTitle } : {}
|
|
1009
|
+
}
|
|
1010
|
+
]
|
|
1011
|
+
} : {},
|
|
1012
|
+
...input.note !== void 0 ? {
|
|
1013
|
+
notes: [
|
|
1014
|
+
{
|
|
1015
|
+
id: randomUUID(),
|
|
1016
|
+
author: input.author ?? "agent",
|
|
1017
|
+
content: input.note,
|
|
1018
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1019
|
+
}
|
|
1020
|
+
]
|
|
1021
|
+
} : {},
|
|
1022
|
+
// Origin tracking
|
|
1023
|
+
...input.graphId !== void 0 ? {
|
|
1024
|
+
origin: {
|
|
1025
|
+
system: input.sourceSystem ?? "kanban-tool",
|
|
1026
|
+
...input.graphId !== void 0 ? { graphId: input.graphId } : {},
|
|
1027
|
+
...input.specId !== void 0 ? { specId: input.specId } : {},
|
|
1028
|
+
...input.phaseId !== void 0 ? { phaseId: input.phaseId } : {}
|
|
1029
|
+
}
|
|
1030
|
+
} : {}
|
|
778
1031
|
};
|
|
779
1032
|
}
|
|
1033
|
+
function mergedDependsOn(input) {
|
|
1034
|
+
const ids = [
|
|
1035
|
+
...input.dependsOn ?? [],
|
|
1036
|
+
...input.dependencyTaskId !== void 0 ? [input.dependencyTaskId] : []
|
|
1037
|
+
].filter((id, i, arr) => id && arr.indexOf(id) === i);
|
|
1038
|
+
return ids.length > 0 ? ids : void 0;
|
|
1039
|
+
}
|
|
780
1040
|
function taskPatch(input) {
|
|
781
1041
|
return {
|
|
782
1042
|
title: input.title,
|
|
783
1043
|
description: input.description,
|
|
1044
|
+
dueDate: input.dueDate,
|
|
784
1045
|
columnId: input.columnId,
|
|
785
1046
|
order: input.order,
|
|
786
1047
|
priority: input.priority,
|
|
1048
|
+
...input.taskType !== void 0 ? { type: input.taskType } : {},
|
|
787
1049
|
status: input.status,
|
|
788
1050
|
labels: input.labels,
|
|
789
1051
|
assignedAgent: input.agentId,
|
|
790
|
-
...input
|
|
1052
|
+
...mergedDependsOn(input) ? { dependsOn: mergedDependsOn(input) } : {},
|
|
1053
|
+
...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {}
|
|
791
1054
|
};
|
|
792
1055
|
}
|
|
793
1056
|
function assignmentInput(input) {
|