@wrongstack/tools 0.295.0 → 0.296.2

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 (142) hide show
  1. package/dist/auto-proceed-loop-guard.d.ts +98 -0
  2. package/dist/auto-proceed-loop-guard.d.ts.map +1 -1
  3. package/dist/auto-proceed-loop-guard.js +119 -1
  4. package/dist/auto-proceed-loop-guard.js.map +2 -2
  5. package/dist/bash.js +92 -41
  6. package/dist/bash.js.map +2 -2
  7. package/dist/browser/index.js +17 -7
  8. package/dist/browser/index.js.map +2 -2
  9. package/dist/browser/network-guard-proxy.d.ts.map +1 -1
  10. package/dist/builtin.d.ts +2 -2
  11. package/dist/builtin.d.ts.map +1 -1
  12. package/dist/builtin.js +9158 -7302
  13. package/dist/builtin.js.map +4 -4
  14. package/dist/codebase-index/background-indexer.d.ts +28 -2
  15. package/dist/codebase-index/background-indexer.d.ts.map +1 -1
  16. package/dist/codebase-index/codebase-index-tool.d.ts.map +1 -1
  17. package/dist/codebase-index/dead-code-scan.d.ts +77 -0
  18. package/dist/codebase-index/dead-code-scan.d.ts.map +1 -0
  19. package/dist/codebase-index/generic-parser.d.ts.map +1 -1
  20. package/dist/codebase-index/index-service.d.ts +4 -3
  21. package/dist/codebase-index/index-service.d.ts.map +1 -1
  22. package/dist/codebase-index/index.d.ts +11 -8
  23. package/dist/codebase-index/index.d.ts.map +1 -1
  24. package/dist/codebase-index/index.js +5048 -3582
  25. package/dist/codebase-index/index.js.map +4 -4
  26. package/dist/codebase-index/indexer.d.ts +2 -0
  27. package/dist/codebase-index/indexer.d.ts.map +1 -1
  28. package/dist/codebase-index/parser-dispatch.d.ts +8 -0
  29. package/dist/codebase-index/parser-dispatch.d.ts.map +1 -0
  30. package/dist/codebase-index/project-server-cache.d.ts +11 -0
  31. package/dist/codebase-index/project-server-cache.d.ts.map +1 -0
  32. package/dist/codebase-index/project-server-client.d.ts +74 -0
  33. package/dist/codebase-index/project-server-client.d.ts.map +1 -0
  34. package/dist/codebase-index/project-server-endpoint.d.ts +24 -0
  35. package/dist/codebase-index/project-server-endpoint.d.ts.map +1 -0
  36. package/dist/codebase-index/project-server-protocol.d.ts +88 -0
  37. package/dist/codebase-index/project-server-protocol.d.ts.map +1 -0
  38. package/dist/codebase-index/project-server.d.ts +11 -0
  39. package/dist/codebase-index/project-server.d.ts.map +1 -0
  40. package/dist/codebase-index/project-server.js +5061 -0
  41. package/dist/codebase-index/project-server.js.map +7 -0
  42. package/dist/codebase-index/refs-extractor.d.ts +1 -0
  43. package/dist/codebase-index/refs-extractor.d.ts.map +1 -1
  44. package/dist/codebase-index/schema.d.ts +11 -0
  45. package/dist/codebase-index/schema.d.ts.map +1 -1
  46. package/dist/codebase-index/sqlite-runtime.d.ts +9 -0
  47. package/dist/codebase-index/sqlite-runtime.d.ts.map +1 -0
  48. package/dist/codebase-index/ts-parser.d.ts +5 -1
  49. package/dist/codebase-index/ts-parser.d.ts.map +1 -1
  50. package/dist/codebase-index/worker-protocol.d.ts +19 -1
  51. package/dist/codebase-index/worker-protocol.d.ts.map +1 -1
  52. package/dist/codebase-index/worker.js +3869 -3423
  53. package/dist/codebase-index/worker.js.map +4 -4
  54. package/dist/codebase-index/writer-admin.d.ts +16 -0
  55. package/dist/codebase-index/writer-admin.d.ts.map +1 -0
  56. package/dist/codebase-index/writer-bulk-insert.d.ts +25 -0
  57. package/dist/codebase-index/writer-bulk-insert.d.ts.map +1 -0
  58. package/dist/codebase-index/writer-graph-helpers.d.ts +52 -0
  59. package/dist/codebase-index/writer-graph-helpers.d.ts.map +1 -0
  60. package/dist/codebase-index/writer-graph-reader.d.ts +11 -0
  61. package/dist/codebase-index/writer-graph-reader.d.ts.map +1 -0
  62. package/dist/codebase-index/writer-helpers.d.ts +15 -0
  63. package/dist/codebase-index/writer-helpers.d.ts.map +1 -0
  64. package/dist/codebase-index/writer-pragmas.d.ts +3 -0
  65. package/dist/codebase-index/writer-pragmas.d.ts.map +1 -0
  66. package/dist/codebase-index/writer-ref-mapper.d.ts +11 -0
  67. package/dist/codebase-index/writer-ref-mapper.d.ts.map +1 -0
  68. package/dist/codebase-index/writer-schema.d.ts +7 -0
  69. package/dist/codebase-index/writer-schema.d.ts.map +1 -0
  70. package/dist/codebase-index/writer-search-helpers.d.ts +28 -0
  71. package/dist/codebase-index/writer-search-helpers.d.ts.map +1 -0
  72. package/dist/codebase-index/writer-store-pool.d.ts +45 -0
  73. package/dist/codebase-index/writer-store-pool.d.ts.map +1 -0
  74. package/dist/codebase-index/writer.d.ts +53 -88
  75. package/dist/codebase-index/writer.d.ts.map +1 -1
  76. package/dist/exec.js +92 -41
  77. package/dist/exec.js.map +2 -2
  78. package/dist/glob.d.ts.map +1 -1
  79. package/dist/glob.js +80 -30
  80. package/dist/glob.js.map +4 -4
  81. package/dist/grep.d.ts.map +1 -1
  82. package/dist/grep.js +87 -20
  83. package/dist/grep.js.map +4 -4
  84. package/dist/index.d.ts +3 -2
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/index.js +9610 -7755
  87. package/dist/index.js.map +4 -4
  88. package/dist/kanban-board-inputs.d.ts +34 -0
  89. package/dist/kanban-board-inputs.d.ts.map +1 -0
  90. package/dist/kanban-decomposition-actions.d.ts +4 -0
  91. package/dist/kanban-decomposition-actions.d.ts.map +1 -0
  92. package/dist/kanban-detail-actions.d.ts +3 -0
  93. package/dist/kanban-detail-actions.d.ts.map +1 -0
  94. package/dist/kanban-evidence-bridge.d.ts +21 -0
  95. package/dist/kanban-evidence-bridge.d.ts.map +1 -0
  96. package/dist/kanban-presence.d.ts +10 -0
  97. package/dist/kanban-presence.d.ts.map +1 -0
  98. package/dist/kanban-split-task-handler.d.ts +6 -0
  99. package/dist/kanban-split-task-handler.d.ts.map +1 -0
  100. package/dist/kanban-task-inputs.d.ts +73 -0
  101. package/dist/kanban-task-inputs.d.ts.map +1 -0
  102. package/dist/kanban-tool-results.d.ts +14 -0
  103. package/dist/kanban-tool-results.d.ts.map +1 -0
  104. package/dist/kanban-tool-schema.d.ts +5 -0
  105. package/dist/kanban-tool-schema.d.ts.map +1 -0
  106. package/dist/kanban-tool-types.d.ts +135 -0
  107. package/dist/kanban-tool-types.d.ts.map +1 -0
  108. package/dist/kanban.d.ts +2 -130
  109. package/dist/kanban.d.ts.map +1 -1
  110. package/dist/kanban.js +944 -582
  111. package/dist/kanban.js.map +4 -4
  112. package/dist/pack.js +9157 -7302
  113. package/dist/pack.js.map +4 -4
  114. package/dist/plan.js +2 -2
  115. package/dist/plan.js.map +2 -2
  116. package/dist/process-registry-persistent.d.ts +1 -0
  117. package/dist/process-registry-persistent.d.ts.map +1 -1
  118. package/dist/ps-slash.js +92 -41
  119. package/dist/ps-slash.js.map +2 -2
  120. package/dist/replace.js +8 -0
  121. package/dist/replace.js.map +2 -2
  122. package/dist/session-kanban.d.ts.map +1 -1
  123. package/dist/session-kanban.js +15 -20
  124. package/dist/session-kanban.js.map +2 -2
  125. package/dist/task.d.ts.map +1 -1
  126. package/dist/task.js +48 -6
  127. package/dist/task.js.map +2 -2
  128. package/dist/todo.d.ts.map +1 -1
  129. package/dist/todo.js +47 -5
  130. package/dist/todo.js.map +2 -2
  131. package/dist/tool-tier.js +9159 -7303
  132. package/dist/tool-tier.js.map +4 -4
  133. package/dist/tree.d.ts.map +1 -1
  134. package/dist/tree.js +2 -14
  135. package/dist/tree.js.map +2 -2
  136. package/dist/win32.d.ts +10 -0
  137. package/dist/win32.d.ts.map +1 -0
  138. package/dist/win32.js +67 -0
  139. package/dist/win32.js.map +7 -0
  140. package/package.json +9 -5
  141. package/dist/shim/node-sqlite.d.ts +0 -3
  142. package/dist/shim/node-sqlite.d.ts.map +0 -1
package/dist/kanban.js CHANGED
@@ -1,15 +1,10 @@
1
1
  // src/kanban.ts
2
- import { randomUUID } from "node:crypto";
3
2
  import { deserializeTaskGraph as deserializeTaskGraph2, serializeTaskGraph } from "@wrongstack/core/tasking";
4
3
  import { loadTasks as loadTasks2 } from "@wrongstack/core/storage";
5
4
  import {
6
- addCheckToTask,
7
5
  addColumn,
8
- addDependency,
9
- addGoalMetricToTask,
10
- addLinkToTask,
11
- addNoteToTask,
12
6
  addTask,
7
+ adoptManagedLifecycle,
13
8
  assignTask,
14
9
  claimReadyTask,
15
10
  copyTaskToBoard,
@@ -19,7 +14,7 @@ import {
19
14
  exportBoardAsMarkdown,
20
15
  exportBoardToTaskGraph,
21
16
  createBoardFromText,
22
- getBoard as getBoard2,
17
+ getBoard as getBoard3,
23
18
  getKanbanOrchestrationSnapshot,
24
19
  getKanbanQueueHealth,
25
20
  getTask,
@@ -32,28 +27,752 @@ import {
32
27
  moveTask,
33
28
  parseLinesIntoTasks,
34
29
  recoverStaleTaskAssignments,
30
+ repairManagedTaskProjection,
35
31
  releaseTaskClaim,
36
32
  removeBoard as removeBoard2,
37
33
  removeColumn,
38
34
  removeTask,
39
35
  searchKanban,
40
36
  setTaskChain,
41
- splitTask,
42
37
  syncBoardFromTaskGraph as syncBoardFromTaskGraph2,
43
38
  transitionTask,
44
39
  transferTaskToBoard,
45
- touchKanbanPresence as touchKanbanPresence2,
46
40
  updateBoard as updateBoard2,
47
- updateCheckOnTask,
48
41
  updateColumn,
49
- updateGoalMetricOnTask,
50
- updateTask,
42
+ updateTask as updateTask2,
51
43
  updateTaskAssignment,
44
+ verifyTaskCompletion as verifyTaskCompletion2,
45
+ finalizeTaskCompletion
46
+ } from "@wrongstack/kanban";
47
+
48
+ // src/kanban-evidence-bridge.ts
49
+ import { recordCompletedWorkEvidence } from "@wrongstack/core/utils";
50
+ function kanbanEvidenceKey(boardId, taskId) {
51
+ return `kanban:${boardId}:${taskId}`;
52
+ }
53
+ function kanbanEvidencePointer(boardId, taskId) {
54
+ return `kanban://${boardId}/${taskId}#verificationReport`;
55
+ }
56
+ function recordKanbanVerificationEvidence(ctx, report) {
57
+ try {
58
+ const passed = report.checks.filter((check) => check.status === "passed").length;
59
+ const completedAt = Date.parse(report.completedAt);
60
+ recordCompletedWorkEvidence(ctx, {
61
+ key: kanbanEvidenceKey(report.boardId, report.taskId),
62
+ source: "verification",
63
+ summary: `${report.taskTitle} \u2014 verification ${report.verdict} (${passed}/${report.checks.length} checks)`,
64
+ ...Number.isFinite(completedAt) ? { completedAt } : {},
65
+ evidence: kanbanEvidencePointer(report.boardId, report.taskId)
66
+ });
67
+ } catch {
68
+ }
69
+ }
70
+
71
+ // src/kanban-decomposition-actions.ts
72
+ import {
73
+ assessTaskAtomicity,
74
+ proposeTaskDecomposition,
75
+ updateTask,
52
76
  verifyTaskCompletion
53
77
  } from "@wrongstack/kanban";
54
78
 
79
+ // src/kanban-tool-results.ts
80
+ function atomicityNudge(task) {
81
+ if (task.atomicityAssessment?.verdict !== "needs_decomposition") return "";
82
+ const reasons = task.atomicityAssessment.criteria.filter((entry) => entry.score < 1).map((entry) => entry.reason).join(" | ");
83
+ return ` Atomicity: needs_decomposition (score ${task.atomicityAssessment.score}) \u2014 call propose_decomposition with 2+ subtasks before dispatch. Reasons: ${reasons}`;
84
+ }
85
+ function readEnvGateEnforcement() {
86
+ const raw = process.env["WRONGSTACK_KANBAN_GATE"]?.trim().toLowerCase();
87
+ return raw === "strict" || raw === "soft" || raw === "off" ? raw : void 0;
88
+ }
89
+ function fail(message) {
90
+ return { ok: false, message };
91
+ }
92
+ function okBoard(board, message = "Board loaded.") {
93
+ return { ok: true, message, board };
94
+ }
95
+ function okTask(board, task, message) {
96
+ return { ok: true, message, board, task };
97
+ }
98
+
99
+ // src/kanban-decomposition-actions.ts
100
+ async function handleKanbanDecompositionAction(projectRoot, input, ctx) {
101
+ switch (input.action) {
102
+ case "assess_atomicity": {
103
+ if (!input.boardId || !input.taskId) {
104
+ return fail("assess_atomicity requires boardId and taskId.");
105
+ }
106
+ const result = await assessTaskAtomicity(projectRoot, input.boardId, input.taskId, {
107
+ assessedBy: "agent",
108
+ ...ctx.agentId !== void 0 ? { eventContext: { actor: ctx.agentId } } : {}
109
+ });
110
+ if (!result) return fail("Task not found.");
111
+ const failing = result.assessment.criteria.filter((entry) => entry.score < 1).map((entry) => entry.reason);
112
+ const guidance = result.assessment.verdict === "needs_decomposition" ? ` This task should be split before dispatch \u2014 call propose_decomposition with 2+ subtasks (each with one verifiable success criterion). Reasons: ${failing.join(" | ")}` : result.assessment.verdict === "composite" ? " Container task: work happens in its children; it is verified via subtask aggregation." : "";
113
+ return okTask(
114
+ result.board,
115
+ result.task,
116
+ `Atomicity verdict: ${result.assessment.verdict} (score ${result.assessment.score}).${guidance}`
117
+ );
118
+ }
119
+ case "propose_decomposition": {
120
+ if (!input.boardId || !input.taskId || !input.subtasks?.length) {
121
+ return fail("propose_decomposition requires boardId, taskId, and subtasks (2+).");
122
+ }
123
+ if (input.subtasks.length < 2) {
124
+ return fail("propose_decomposition requires at least two subtasks.");
125
+ }
126
+ const invalid = input.subtasks.find(
127
+ (subtask) => typeof subtask?.title !== "string" || !subtask.title.trim()
128
+ );
129
+ if (invalid) return fail("Every proposed subtask needs a non-blank title.");
130
+ const result = await proposeTaskDecomposition(
131
+ projectRoot,
132
+ input.boardId,
133
+ input.taskId,
134
+ {
135
+ subtasks: input.subtasks,
136
+ ...input.note !== void 0 ? { rationale: input.note } : {},
137
+ ...ctx.agentId !== void 0 ? { proposedBy: ctx.agentId } : {}
138
+ },
139
+ ctx.agentId !== void 0 ? { actor: ctx.agentId } : {}
140
+ );
141
+ if (!result) return fail("Task not found.");
142
+ const message = result.proposal.status === "applied" ? `Decomposition applied: ${result.proposal.appliedChildTaskIds?.length ?? 0} child tasks created (parent marked atomic).` : 'Decomposition proposal recorded \u2014 awaiting approval (board policy is "propose"). It can be approved from the WebUI or via update_task.';
143
+ return okTask(result.board, result.task, message);
144
+ }
145
+ case "verify_completion": {
146
+ if (!input.boardId || !input.taskId) {
147
+ return fail("verify_completion requires boardId and taskId.");
148
+ }
149
+ const verResult = await verifyTaskCompletion(projectRoot, input.boardId, input.taskId);
150
+ const persistedBoard = await updateTask(projectRoot, input.boardId, input.taskId, {
151
+ verificationReport: verResult.report,
152
+ successCriteria: verResult.task.successCriteria
153
+ });
154
+ if (!persistedBoard) {
155
+ return {
156
+ ok: false,
157
+ verdict: verResult.report.verdict,
158
+ message: `Verification succeeded but persist failed: ${verResult.report.markdownSummary}. Board may be stale \u2014 re-run verify_completion.`,
159
+ board: verResult.board
160
+ };
161
+ }
162
+ recordKanbanVerificationEvidence(ctx, verResult.report);
163
+ const freshTask = persistedBoard.tasks?.find((t) => t.id === input.taskId);
164
+ const deterministicVerdicts = ["passed", "failed", "needs_human", "incomplete"];
165
+ return {
166
+ ok: deterministicVerdicts.includes(
167
+ verResult.report.verdict
168
+ ),
169
+ verdict: verResult.report.verdict,
170
+ message: verResult.report.markdownSummary,
171
+ board: persistedBoard,
172
+ task: freshTask ?? verResult.task
173
+ };
174
+ }
175
+ default:
176
+ return void 0;
177
+ }
178
+ }
179
+
180
+ // src/kanban-detail-actions.ts
181
+ import {
182
+ addCheckToTask,
183
+ addDependency,
184
+ addGoalMetricToTask,
185
+ addLinkToTask,
186
+ addNoteToTask,
187
+ updateCheckOnTask,
188
+ updateGoalMetricOnTask
189
+ } from "@wrongstack/kanban";
190
+
191
+ // src/kanban-split-task-handler.ts
192
+ import { getBoard, splitTask } from "@wrongstack/kanban";
193
+ async function handleSplitTask(projectRoot, input, extraSplitOptions) {
194
+ const boardId = input.boardId;
195
+ const taskId = input.taskId;
196
+ const childTitles = input.childTitles;
197
+ if (!boardId || !taskId || !childTitles?.length) {
198
+ return fail("split requires boardId, taskId, and at least one childTitles.");
199
+ }
200
+ const {
201
+ targetColumnId,
202
+ inheritAssignment,
203
+ inheritLabels,
204
+ inheritSuccessCriteria,
205
+ inheritGoalMetrics,
206
+ inheritDependencies,
207
+ chainChildren,
208
+ rewireDependents
209
+ } = input;
210
+ const result = await splitTask(projectRoot, boardId, taskId, {
211
+ titles: childTitles,
212
+ ...extraSplitOptions,
213
+ ...targetColumnId !== void 0 ? { columnId: targetColumnId } : {},
214
+ ...inheritAssignment !== void 0 ? { inheritAssignment } : {},
215
+ ...inheritLabels !== void 0 ? { inheritLabels } : {},
216
+ ...inheritSuccessCriteria !== void 0 ? { inheritSuccessCriteria } : {},
217
+ ...inheritGoalMetrics !== void 0 ? { inheritGoalMetrics } : {},
218
+ ...inheritDependencies !== void 0 ? { inheritDependencies } : {},
219
+ ...chainChildren !== void 0 ? { chainChildren } : {},
220
+ ...rewireDependents !== void 0 ? { rewireDependents } : {}
221
+ });
222
+ if (!result) return fail("Task not found.");
223
+ const freshParent = result.board.tasks?.find((t) => t.id === taskId);
224
+ if (!freshParent) {
225
+ return fail(
226
+ `Split succeeded but parent ${taskId} not found in returned board. Children: [${result.children.map((c) => c.id).join(", ")}].`
227
+ );
228
+ }
229
+ return {
230
+ ok: true,
231
+ message: `${result.children.length} child task(s) created.`,
232
+ board: result.board,
233
+ task: freshParent,
234
+ children: result.children
235
+ };
236
+ }
237
+ async function requireBoard(projectRoot, boardId) {
238
+ return boardId ? getBoard(projectRoot, boardId) : null;
239
+ }
240
+
241
+ // src/kanban-detail-actions.ts
242
+ async function handleKanbanDetailAction(projectRoot, input) {
243
+ switch (input.action) {
244
+ case "add_dependency": {
245
+ if (!input.boardId || !input.taskId || !input.dependencyTaskId) {
246
+ return fail("add_dependency requires boardId, taskId, and dependencyTaskId.");
247
+ }
248
+ const board = await addDependency(
249
+ projectRoot,
250
+ input.boardId,
251
+ input.taskId,
252
+ input.dependencyTaskId
253
+ );
254
+ return board ? okBoard(board, "Dependency added.") : fail("Task not found.");
255
+ }
256
+ case "add_goal_metric": {
257
+ if (!input.boardId || !input.taskId || !input.metricName) {
258
+ return fail("add_goal_metric requires boardId, taskId, and metricName.");
259
+ }
260
+ const board = await addGoalMetricToTask(projectRoot, input.boardId, input.taskId, {
261
+ name: input.metricName,
262
+ ...input.metricStatus !== void 0 ? { status: input.metricStatus } : {},
263
+ ...input.metricTarget !== void 0 ? { target: input.metricTarget } : {},
264
+ ...input.metricCurrent !== void 0 ? { current: input.metricCurrent } : {},
265
+ ...input.metricUnit !== void 0 ? { unit: input.metricUnit } : {},
266
+ ...input.metricNotes !== void 0 ? { notes: input.metricNotes } : {}
267
+ });
268
+ return board ? okBoard(board, "Goal metric added.") : fail("Task not found.");
269
+ }
270
+ case "update_goal_metric": {
271
+ if (!input.boardId || !input.taskId || !input.metricId) {
272
+ return fail("update_goal_metric requires boardId, taskId, and metricId.");
273
+ }
274
+ const board = await updateGoalMetricOnTask(
275
+ projectRoot,
276
+ input.boardId,
277
+ input.taskId,
278
+ input.metricId,
279
+ {
280
+ ...input.metricName !== void 0 ? { name: input.metricName } : {},
281
+ ...input.metricStatus !== void 0 ? { status: input.metricStatus } : {},
282
+ ...input.metricTarget !== void 0 ? { target: input.metricTarget } : {},
283
+ ...input.metricCurrent !== void 0 ? { current: input.metricCurrent } : {},
284
+ ...input.metricUnit !== void 0 ? { unit: input.metricUnit } : {},
285
+ ...input.metricNotes !== void 0 ? { notes: input.metricNotes } : {}
286
+ }
287
+ );
288
+ return board ? okBoard(board, "Goal metric updated.") : fail("Metric not found.");
289
+ }
290
+ case "add_check": {
291
+ if (!input.boardId || !input.taskId || !input.checkDescription) {
292
+ return fail("add_check requires boardId, taskId, and checkDescription.");
293
+ }
294
+ const board = await addCheckToTask(projectRoot, input.boardId, input.taskId, {
295
+ description: input.checkDescription,
296
+ type: "manual",
297
+ status: input.checkStatus
298
+ });
299
+ return board ? okBoard(board, "Check added.") : fail("Task not found.");
300
+ }
301
+ case "update_check": {
302
+ if (!input.boardId || !input.taskId || !input.checkId) {
303
+ return fail("update_check requires boardId, taskId, and checkId.");
304
+ }
305
+ const board = await updateCheckOnTask(
306
+ projectRoot,
307
+ input.boardId,
308
+ input.taskId,
309
+ input.checkId,
310
+ {
311
+ ...input.checkDescription !== void 0 ? { description: input.checkDescription } : {},
312
+ ...input.checkStatus !== void 0 ? { status: input.checkStatus } : {}
313
+ }
314
+ );
315
+ return board ? okBoard(board, "Check updated.") : fail("Check not found.");
316
+ }
317
+ case "add_note": {
318
+ if (!input.boardId || !input.taskId || !input.note)
319
+ return fail("add_note requires boardId, taskId, and note.");
320
+ const board = await addNoteToTask(projectRoot, input.boardId, input.taskId, {
321
+ author: input.author ?? "agent",
322
+ content: input.note
323
+ });
324
+ return board ? okBoard(board, "Note added.") : fail("Task not found.");
325
+ }
326
+ case "add_link": {
327
+ if (!input.boardId || !input.taskId || !input.url)
328
+ return fail("add_link requires boardId, taskId, and url.");
329
+ const board = await addLinkToTask(projectRoot, input.boardId, input.taskId, {
330
+ url: input.url,
331
+ type: input.linkType ?? "url",
332
+ ...input.linkTitle !== void 0 ? { title: input.linkTitle } : {}
333
+ });
334
+ return board ? okBoard(board, "Link added.") : fail("Task not found.");
335
+ }
336
+ case "split_atomic": {
337
+ if (!input.boardId || !input.taskId || !input.childTitles?.length) {
338
+ return fail("split_atomic requires boardId, taskId, and childTitles (at least one).");
339
+ }
340
+ return handleSplitTask(projectRoot, input, { atomic: true });
341
+ }
342
+ default:
343
+ return void 0;
344
+ }
345
+ }
346
+
347
+ // src/kanban-board-inputs.ts
348
+ function boardCreateInput(input, title) {
349
+ return {
350
+ title,
351
+ ...input.description !== void 0 ? { description: input.description } : {},
352
+ ...input.tags !== void 0 ? { tags: input.tags } : {},
353
+ ...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
354
+ ...input.atomicityMode !== void 0 ? {
355
+ atomicity: {
356
+ mode: input.atomicityMode,
357
+ decomposition: input.atomicityDecomposition ?? "propose"
358
+ }
359
+ } : {},
360
+ ...input.gateEnforcement !== void 0 ? { completionGate: { enforcement: input.gateEnforcement } } : {}
361
+ };
362
+ }
363
+ function boardUpdatePatch(input) {
364
+ return {
365
+ ...input.title !== void 0 ? { title: input.title } : {},
366
+ ...input.description !== void 0 ? { description: input.description } : {},
367
+ ...input.tags !== void 0 ? { tags: input.tags } : {},
368
+ ...input.atomicityMode !== void 0 ? {
369
+ atomicity: {
370
+ mode: input.atomicityMode,
371
+ decomposition: input.atomicityDecomposition ?? "propose"
372
+ }
373
+ } : {},
374
+ ...input.gateEnforcement !== void 0 ? { completionGate: { enforcement: input.gateEnforcement } } : {}
375
+ };
376
+ }
377
+ function duplicateBoardOptions(input) {
378
+ return {
379
+ ...input.title !== void 0 ? { title: input.title } : {},
380
+ ...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
381
+ ...input.includeTasks !== void 0 ? { includeTasks: input.includeTasks } : {},
382
+ ...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {},
383
+ ...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {}
384
+ };
385
+ }
386
+
387
+ // src/kanban-task-inputs.ts
388
+ import { randomUUID } from "node:crypto";
389
+ function taskInput(input) {
390
+ const assignment = hasAssignmentInput(input) ? assignmentForTaskCreate(input) : void 0;
391
+ return {
392
+ title: input.title ?? "",
393
+ columnId: input.columnId,
394
+ description: input.description,
395
+ dueDate: input.dueDate,
396
+ priority: input.priority,
397
+ ...input.taskType !== void 0 ? { type: input.taskType } : {},
398
+ status: input.status,
399
+ labels: input.labels,
400
+ ...assignment?.agentId ?? assignment?.role ?? assignment?.name ? { assignedAgent: assignment.agentId ?? assignment.role ?? assignment.name } : {},
401
+ ...input.assignee ?? assignment?.name ?? assignment?.agentId ? { assignee: input.assignee ?? assignment?.name ?? assignment?.agentId } : {},
402
+ ...mergedDependsOn(input) ? { dependsOn: mergedDependsOn(input) } : {},
403
+ ...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {},
404
+ ...input.actualHours !== void 0 ? { actualHours: input.actualHours } : {},
405
+ ...assignment ? { assignment } : {},
406
+ ...input.order !== void 0 ? { order: input.order } : {},
407
+ ...input.retryPolicy !== void 0 ? { retryPolicy: input.retryPolicy } : {},
408
+ ...input.costCeilingUsd !== void 0 ? { costCeilingUsd: input.costCeilingUsd } : {},
409
+ ...input.childTitles !== void 0 ? { childTaskIds: input.childTitles } : {},
410
+ ...input.checkDescription !== void 0 ? {
411
+ successCriteria: [
412
+ {
413
+ id: randomUUID(),
414
+ description: input.checkDescription,
415
+ type: "manual",
416
+ status: input.checkStatus ?? "pending"
417
+ }
418
+ ]
419
+ } : {},
420
+ ...input.metricName !== void 0 ? {
421
+ goalMetrics: [
422
+ {
423
+ id: randomUUID(),
424
+ name: input.metricName,
425
+ status: input.metricStatus ?? "pending",
426
+ ...input.metricTarget !== void 0 ? { target: input.metricTarget } : {},
427
+ ...input.metricCurrent !== void 0 ? { current: input.metricCurrent } : {},
428
+ ...input.metricUnit !== void 0 ? { unit: input.metricUnit } : {},
429
+ ...input.metricNotes !== void 0 ? { notes: input.metricNotes } : {}
430
+ }
431
+ ]
432
+ } : {},
433
+ ...input.url !== void 0 ? {
434
+ links: [
435
+ {
436
+ url: input.url,
437
+ type: input.linkType ?? "url",
438
+ ...input.linkTitle !== void 0 ? { title: input.linkTitle } : {}
439
+ }
440
+ ]
441
+ } : {},
442
+ ...input.note !== void 0 ? {
443
+ notes: [
444
+ {
445
+ id: randomUUID(),
446
+ author: input.author ?? "agent",
447
+ content: input.note,
448
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
449
+ }
450
+ ]
451
+ } : {},
452
+ ...input.graphId !== void 0 ? {
453
+ origin: {
454
+ system: input.sourceSystem ?? "kanban-tool",
455
+ ...input.graphId !== void 0 ? { graphId: input.graphId } : {},
456
+ ...input.specId !== void 0 ? { specId: input.specId } : {},
457
+ ...input.phaseId !== void 0 ? { phaseId: input.phaseId } : {}
458
+ }
459
+ } : {}
460
+ };
461
+ }
462
+ function mergedDependsOn(input) {
463
+ const ids = [
464
+ ...input.dependsOn ?? [],
465
+ ...input.dependencyTaskId !== void 0 ? [input.dependencyTaskId] : []
466
+ ].filter((id, i, arr) => id && arr.indexOf(id) === i);
467
+ return ids.length > 0 ? ids : void 0;
468
+ }
469
+ function taskPatch(input) {
470
+ return {
471
+ title: input.title,
472
+ description: input.description,
473
+ dueDate: input.dueDate,
474
+ columnId: input.columnId,
475
+ order: input.order,
476
+ priority: input.priority,
477
+ ...input.taskType !== void 0 ? { type: input.taskType } : {},
478
+ status: input.status,
479
+ labels: input.labels,
480
+ assignedAgent: input.agentId,
481
+ ...mergedDependsOn(input) ? { dependsOn: mergedDependsOn(input) } : {},
482
+ ...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {},
483
+ ...input.actualHours !== void 0 ? { actualHours: input.actualHours } : {}
484
+ };
485
+ }
486
+ function assignmentInput(input) {
487
+ return {
488
+ agentId: input.agentId,
489
+ name: input.name,
490
+ role: input.role,
491
+ provider: input.provider,
492
+ model: input.model,
493
+ fallbackProfile: input.fallbackProfile,
494
+ fallbackModels: input.fallbackModels,
495
+ tools: input.tools,
496
+ allowedCapabilities: input.allowedCapabilities,
497
+ assignee: input.assignee,
498
+ leaseId: input.leaseId,
499
+ claimedAt: input.claimedAt,
500
+ heartbeatAt: input.heartbeatAt,
501
+ leaseExpiresAt: input.leaseExpiresAt,
502
+ attempt: input.attempt,
503
+ maxAttempts: input.maxAttempts,
504
+ costCeilingUsd: input.costCeilingUsd,
505
+ retryPolicy: input.retryPolicy,
506
+ lastFailureKind: input.lastFailureKind
507
+ };
508
+ }
509
+ function hasAssignmentInput(input) {
510
+ return input.agentId !== void 0 || input.name !== void 0 || input.role !== void 0 || input.provider !== void 0 || input.model !== void 0 || input.fallbackProfile !== void 0 || input.fallbackModels !== void 0 || input.tools !== void 0 || input.allowedCapabilities !== void 0 || input.assignee !== void 0 || input.leaseId !== void 0 || input.claimedAt !== void 0 || input.heartbeatAt !== void 0 || input.leaseExpiresAt !== void 0 || input.attempt !== void 0 || input.maxAttempts !== void 0 || input.costCeilingUsd !== void 0 || input.retryPolicy !== void 0 || input.lastFailureKind !== void 0 || input.assignmentStatus !== void 0;
511
+ }
512
+ function assignmentForTaskCreate(input) {
513
+ return {
514
+ status: input.assignmentStatus ?? "assigned",
515
+ ...input.agentId !== void 0 ? { agentId: input.agentId } : {},
516
+ ...input.name !== void 0 ? { name: input.name } : {},
517
+ ...input.role !== void 0 ? { role: input.role } : {},
518
+ ...input.provider !== void 0 ? { provider: input.provider } : {},
519
+ ...input.model !== void 0 ? { model: input.model } : {},
520
+ ...input.fallbackProfile !== void 0 ? { fallbackProfile: input.fallbackProfile } : {},
521
+ ...input.fallbackModels !== void 0 ? { fallbackModels: input.fallbackModels } : {},
522
+ ...input.tools !== void 0 ? { tools: input.tools } : {},
523
+ ...input.allowedCapabilities !== void 0 ? { allowedCapabilities: input.allowedCapabilities } : {},
524
+ ...input.leaseId !== void 0 ? { leaseId: input.leaseId } : {},
525
+ ...input.claimedAt !== void 0 ? { claimedAt: input.claimedAt } : {},
526
+ ...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
527
+ ...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {},
528
+ ...input.attempt !== void 0 ? { attempt: input.attempt } : {},
529
+ ...input.maxAttempts !== void 0 ? { maxAttempts: input.maxAttempts } : {},
530
+ ...input.costCeilingUsd !== void 0 ? { costCeilingUsd: input.costCeilingUsd } : {},
531
+ ...input.retryPolicy !== void 0 ? { retryPolicy: input.retryPolicy } : {},
532
+ ...input.lastFailureKind !== void 0 ? { lastFailureKind: input.lastFailureKind } : {}
533
+ };
534
+ }
535
+
536
+ // src/kanban-tool-schema.ts
537
+ var KANBAN_TOOL_DESCRIPTION = "Manage and audit project-scoped multi-kanban boards through the shared IPC Kanban server and its SQLite store. 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. Use verify_completion to validate a task against its success criteria and persist the verification report. Use split_atomic to split a task into child tasks with parent.atomic=true in a single atomic board mutation, enforcing subtree verification before the parent can complete.";
538
+ var KANBAN_TOOL_USAGE_HINT = "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. Use verify_completion to generate a verification report (persisted automatically) and split_atomic to atomically create child subtasks with the atomic flag pre-set.";
539
+ var KANBAN_INPUT_SCHEMA = {
540
+ type: "object",
541
+ properties: {
542
+ action: {
543
+ type: "string",
544
+ enum: [
545
+ "list_boards",
546
+ "get_board",
547
+ "create_board",
548
+ "duplicate_board",
549
+ "update_board",
550
+ "adopt_managed_lifecycle",
551
+ "delete_board",
552
+ "generate_board",
553
+ "export_markdown",
554
+ "export_task_graph",
555
+ "sync_task_graph",
556
+ "create_from_graph",
557
+ "import_session_tasks",
558
+ "search_tasks",
559
+ "ready_tasks",
560
+ "snapshot",
561
+ "add_column",
562
+ "update_column",
563
+ "delete_column",
564
+ "add_task",
565
+ "split_task",
566
+ "merge_tasks",
567
+ "copy_task",
568
+ "transfer_task",
569
+ "get_task",
570
+ "update_task",
571
+ "transition_task",
572
+ "repair_managed_projection",
573
+ "move_task",
574
+ "delete_task",
575
+ "set_chain",
576
+ "get_chain",
577
+ "claim_task",
578
+ "release_task",
579
+ "assign_task",
580
+ "mark_assignment",
581
+ "heartbeat_assignment",
582
+ "recover_stale",
583
+ "events",
584
+ "queue_health",
585
+ "add_dependency",
586
+ "add_goal_metric",
587
+ "update_goal_metric",
588
+ "add_check",
589
+ "update_check",
590
+ "add_note",
591
+ "add_link",
592
+ "verify_completion",
593
+ "split_atomic",
594
+ "assess_atomicity",
595
+ "propose_decomposition"
596
+ ]
597
+ },
598
+ boardId: { type: "string" },
599
+ taskId: { type: "string" },
600
+ taskIds: { type: "array", items: { type: "string" } },
601
+ chainId: { type: "string" },
602
+ columnId: { type: "string" },
603
+ targetBoardId: { type: "string" },
604
+ targetColumnId: { type: "string" },
605
+ title: { type: "string" },
606
+ description: { type: "string" },
607
+ dueDate: { type: "string" },
608
+ tags: { type: "array", items: { type: "string" } },
609
+ labels: { type: "array", items: { type: "string" } },
610
+ priority: { type: "string", enum: ["critical", "high", "medium", "low"] },
611
+ taskType: {
612
+ type: "string",
613
+ enum: ["feature", "bugfix", "refactor", "docs", "test", "chore"]
614
+ },
615
+ status: {
616
+ type: "string",
617
+ enum: [
618
+ "pending",
619
+ "ready",
620
+ "in_progress",
621
+ "blocked",
622
+ "review",
623
+ "completed",
624
+ "failed",
625
+ "archived"
626
+ ]
627
+ },
628
+ order: { type: "number" },
629
+ query: { type: "string" },
630
+ limit: { type: "number" },
631
+ agentId: { type: "string" },
632
+ name: { type: "string" },
633
+ role: { type: "string" },
634
+ provider: { type: "string" },
635
+ model: { type: "string" },
636
+ fallbackProfile: { type: "string" },
637
+ fallbackModels: { type: "array", items: { type: "string" } },
638
+ tools: { type: "array", items: { type: "string" } },
639
+ allowedCapabilities: { type: "array", items: { type: "string" } },
640
+ leaseId: { type: "string" },
641
+ claimedAt: { type: "string" },
642
+ heartbeatAt: { type: "string" },
643
+ leaseExpiresAt: { type: "string" },
644
+ attempt: { type: "number" },
645
+ maxAttempts: { type: "number" },
646
+ subagentId: { type: "string" },
647
+ runTaskId: { type: "string" },
648
+ lastResult: { type: "string" },
649
+ error: { type: "string" },
650
+ expectedLeaseId: { type: "string" },
651
+ assignmentStatus: {
652
+ type: "string",
653
+ enum: ["assigned", "queued", "running", "completed", "failed", "cancelled"]
654
+ },
655
+ lifecycleStage: {
656
+ type: "string",
657
+ enum: ["backlog", "todo", "running", "review", "done"]
658
+ },
659
+ transitionAction: { type: "string" },
660
+ transitionComment: { type: "string" },
661
+ attachmentUrl: { type: "string" },
662
+ attachmentTitle: { type: "string" },
663
+ attachmentType: {
664
+ type: "string",
665
+ enum: ["issue", "pr", "doc", "commit", "design", "file", "url", "other"]
666
+ },
667
+ releaseStatus: { type: "string", enum: ["pending", "ready", "blocked"] },
668
+ releaseReason: { type: "string" },
669
+ clearAssignee: { type: "boolean" },
670
+ recoveryMode: { type: "string", enum: ["auto", "release", "retry", "fail"] },
671
+ recoveryNow: { type: "string" },
672
+ recoveryPolicyFailOnCostCeiling: { type: "boolean" },
673
+ recoveryPolicyReleaseOnFailureKinds: { type: "array", items: { type: "string" } },
674
+ recoveryPolicyReleaseOnHeartbeatDue: { type: "boolean" },
675
+ recoveryPolicyRetryPolicyOverride: {
676
+ type: "string",
677
+ enum: ["off", "incremental", "exponential"]
678
+ },
679
+ assignee: { type: "string" },
680
+ costCeilingUsd: { type: "number" },
681
+ retryPolicy: { type: "string", enum: ["off", "incremental", "exponential"] },
682
+ lastFailureKind: { type: "string" },
683
+ dependsOn: { type: "array", items: { type: "string" } },
684
+ estimatedHours: { type: "number" },
685
+ actualHours: { type: "number" },
686
+ taskGraph: { type: "object" },
687
+ graphId: { type: "string" },
688
+ specId: { type: "string" },
689
+ sourceSystem: { type: "string" },
690
+ phaseId: { type: "string" },
691
+ preserveOriginTaskIds: { type: "boolean" },
692
+ includeArchived: { type: "boolean" },
693
+ archiveMissingTasks: { type: "boolean" },
694
+ preserveManualDependencies: { type: "boolean" },
695
+ dependencyTaskId: { type: "string" },
696
+ enforceDependencies: { type: "boolean" },
697
+ childTitles: { type: "array", items: { type: "string" } },
698
+ inheritAssignment: { type: "boolean" },
699
+ inheritLabels: { type: "boolean" },
700
+ inheritSuccessCriteria: { type: "boolean" },
701
+ inheritGoalMetrics: { type: "boolean" },
702
+ inheritDependencies: { type: "boolean" },
703
+ chainChildren: { type: "boolean" },
704
+ rewireDependents: { type: "boolean" },
705
+ closeSourceTasks: { type: "boolean" },
706
+ metricId: { type: "string" },
707
+ metricName: { type: "string" },
708
+ metricTarget: { oneOf: [{ type: "string" }, { type: "number" }] },
709
+ metricCurrent: { oneOf: [{ type: "string" }, { type: "number" }] },
710
+ metricUnit: { type: "string" },
711
+ metricStatus: { type: "string", enum: ["pending", "met", "missed", "waived"] },
712
+ metricNotes: { type: "string" },
713
+ checkId: { type: "string" },
714
+ checkDescription: { type: "string" },
715
+ checkStatus: { type: "string", enum: ["pending", "passed", "failed", "skipped"] },
716
+ note: { type: "string" },
717
+ author: { type: "string" },
718
+ url: { type: "string" },
719
+ linkTitle: { type: "string" },
720
+ linkType: {
721
+ type: "string",
722
+ enum: ["issue", "pr", "doc", "commit", "design", "file", "url", "other"]
723
+ },
724
+ context: { type: "string" },
725
+ columns: { type: "array", items: { type: "string" } },
726
+ generatedBy: { type: "string" },
727
+ includeTasks: { type: "boolean" },
728
+ includeCompletedTasks: { type: "boolean" },
729
+ preserveAssignment: { type: "boolean" },
730
+ preserveDependencies: { type: "boolean" },
731
+ moveTasksToColumnId: { type: "string" },
732
+ atomicityMode: { type: "string", enum: ["off", "assess", "enforce"] },
733
+ atomicityDecomposition: { type: "string", enum: ["auto", "propose"] },
734
+ gateEnforcement: { type: "string", enum: ["strict", "soft", "off"] },
735
+ subtasks: {
736
+ type: "array",
737
+ minItems: 2,
738
+ items: {
739
+ type: "object",
740
+ properties: {
741
+ title: { type: "string" },
742
+ description: { type: "string" },
743
+ successCriteria: { type: "array", items: { type: "string" } },
744
+ dependsOnIndex: { type: "array", items: { type: "number" } }
745
+ },
746
+ required: ["title"]
747
+ }
748
+ }
749
+ },
750
+ required: ["action"]
751
+ };
752
+
753
+ // src/kanban-presence.ts
754
+ import { touchKanbanPresence } from "@wrongstack/kanban";
755
+ function createKanbanPresenceWrapper(projectRoot, input, ctx) {
756
+ return async (result) => {
757
+ const boardId = result.board?.id ?? input.boardId;
758
+ if (!result.ok || !boardId || !ctx.session?.id || !ctx.agentId) return result;
759
+ try {
760
+ const board = await touchKanbanPresence(projectRoot, boardId, {
761
+ sessionId: ctx.session.id,
762
+ agentId: ctx.agentId,
763
+ agentName: ctx.agentName,
764
+ taskId: input.taskId ?? result.task?.id,
765
+ runTaskId: input.runTaskId
766
+ });
767
+ return board ? { ...result, board } : result;
768
+ } catch {
769
+ return result;
770
+ }
771
+ };
772
+ }
773
+
55
774
  // src/session-kanban.ts
56
- import { GlobalMailbox } from "@wrongstack/core/coordination";
775
+ import { getSharedProjectMailbox } from "@wrongstack/core/coordination";
57
776
  import {
58
777
  loadPlan,
59
778
  loadTasks,
@@ -63,13 +782,13 @@ import {
63
782
  import { deserializeTaskGraph } from "@wrongstack/core/tasking";
64
783
  import { resolveWstackPaths } from "@wrongstack/core/utils";
65
784
  import {
785
+ bridgeKanbanSupervisor,
66
786
  createBoard,
67
- getBoard,
68
- getKanbanDir,
787
+ getBoard as getBoard2,
69
788
  listBoards,
70
789
  removeBoard,
71
790
  syncBoardFromTaskGraph,
72
- touchKanbanPresence,
791
+ touchKanbanPresence as touchKanbanPresence2,
73
792
  updateBoard
74
793
  } from "@wrongstack/kanban";
75
794
  function taskFileToSerializedGraph(tasks, sessionId) {
@@ -113,226 +832,22 @@ function taskFileToSerializedGraph(tasks, sessionId) {
113
832
  var kanbanTool = {
114
833
  name: "kanban",
115
834
  category: "Project",
116
- 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. Use verify_completion to validate a task against its success criteria and persist the verification report. Use split_atomic to split a task into child tasks with parent.atomic=true in a single atomic board mutation, enforcing subtree verification before the parent can complete.",
117
- 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. Use verify_completion to generate a verification report (persisted automatically) and split_atomic to atomically create child subtasks with the atomic flag pre-set.",
835
+ description: KANBAN_TOOL_DESCRIPTION,
836
+ usageHint: KANBAN_TOOL_USAGE_HINT,
118
837
  permission: "confirm",
119
838
  mutating: true,
120
839
  capabilities: ["fs.write"],
121
840
  icon: "task",
122
- timeoutMs: 5e3,
123
- inputSchema: {
124
- type: "object",
125
- properties: {
126
- action: {
127
- type: "string",
128
- enum: [
129
- "list_boards",
130
- "get_board",
131
- "create_board",
132
- "duplicate_board",
133
- "update_board",
134
- "delete_board",
135
- "generate_board",
136
- "export_markdown",
137
- "export_task_graph",
138
- "sync_task_graph",
139
- "create_from_graph",
140
- "import_session_tasks",
141
- "search_tasks",
142
- "ready_tasks",
143
- "snapshot",
144
- "add_column",
145
- "update_column",
146
- "delete_column",
147
- "add_task",
148
- "split_task",
149
- "merge_tasks",
150
- "copy_task",
151
- "transfer_task",
152
- "get_task",
153
- "update_task",
154
- "transition_task",
155
- "move_task",
156
- "delete_task",
157
- "set_chain",
158
- "get_chain",
159
- "claim_task",
160
- "release_task",
161
- "assign_task",
162
- "mark_assignment",
163
- "heartbeat_assignment",
164
- "recover_stale",
165
- "events",
166
- "queue_health",
167
- "add_dependency",
168
- "add_goal_metric",
169
- "update_goal_metric",
170
- "add_check",
171
- "update_check",
172
- "add_note",
173
- "add_link",
174
- "verify_completion",
175
- "split_atomic"
176
- ]
177
- },
178
- boardId: { type: "string" },
179
- taskId: { type: "string" },
180
- taskIds: { type: "array", items: { type: "string" } },
181
- chainId: { type: "string" },
182
- columnId: { type: "string" },
183
- targetBoardId: { type: "string" },
184
- targetColumnId: { type: "string" },
185
- title: { type: "string" },
186
- description: { type: "string" },
187
- dueDate: { type: "string" },
188
- tags: { type: "array", items: { type: "string" } },
189
- labels: { type: "array", items: { type: "string" } },
190
- priority: { type: "string", enum: ["critical", "high", "medium", "low"] },
191
- taskType: {
192
- type: "string",
193
- enum: ["feature", "bugfix", "refactor", "docs", "test", "chore"]
194
- },
195
- status: {
196
- type: "string",
197
- enum: [
198
- "pending",
199
- "ready",
200
- "in_progress",
201
- "blocked",
202
- "review",
203
- "completed",
204
- "failed",
205
- "archived"
206
- ]
207
- },
208
- order: { type: "number" },
209
- query: { type: "string" },
210
- limit: { type: "number" },
211
- agentId: { type: "string" },
212
- name: { type: "string" },
213
- role: { type: "string" },
214
- provider: { type: "string" },
215
- model: { type: "string" },
216
- fallbackProfile: { type: "string" },
217
- fallbackModels: { type: "array", items: { type: "string" } },
218
- tools: { type: "array", items: { type: "string" } },
219
- allowedCapabilities: { type: "array", items: { type: "string" } },
220
- leaseId: { type: "string" },
221
- claimedAt: { type: "string" },
222
- heartbeatAt: { type: "string" },
223
- leaseExpiresAt: { type: "string" },
224
- attempt: { type: "number" },
225
- maxAttempts: { type: "number" },
226
- subagentId: { type: "string" },
227
- runTaskId: { type: "string" },
228
- lastResult: { type: "string" },
229
- error: { type: "string" },
230
- expectedLeaseId: { type: "string" },
231
- assignmentStatus: {
232
- type: "string",
233
- enum: ["assigned", "queued", "running", "completed", "failed", "cancelled"]
234
- },
235
- lifecycleStage: {
236
- type: "string",
237
- enum: ["backlog", "todo", "running", "review", "done"]
238
- },
239
- transitionAction: { type: "string" },
240
- transitionComment: { type: "string" },
241
- attachmentUrl: { type: "string" },
242
- attachmentTitle: { type: "string" },
243
- attachmentType: {
244
- type: "string",
245
- enum: ["issue", "pr", "doc", "commit", "design", "file", "url", "other"]
246
- },
247
- releaseStatus: { type: "string", enum: ["pending", "ready", "blocked"] },
248
- releaseReason: { type: "string" },
249
- clearAssignee: { type: "boolean" },
250
- recoveryMode: { type: "string", enum: ["auto", "release", "retry", "fail"] },
251
- recoveryNow: { type: "string" },
252
- recoveryPolicyFailOnCostCeiling: { type: "boolean" },
253
- recoveryPolicyReleaseOnFailureKinds: { type: "array", items: { type: "string" } },
254
- recoveryPolicyReleaseOnHeartbeatDue: { type: "boolean" },
255
- recoveryPolicyRetryPolicyOverride: {
256
- type: "string",
257
- enum: ["off", "incremental", "exponential"]
258
- },
259
- assignee: { type: "string" },
260
- costCeilingUsd: { type: "number" },
261
- retryPolicy: { type: "string", enum: ["off", "incremental", "exponential"] },
262
- lastFailureKind: { type: "string" },
263
- dependsOn: { type: "array", items: { type: "string" } },
264
- estimatedHours: { type: "number" },
265
- actualHours: { type: "number" },
266
- taskGraph: { type: "object" },
267
- graphId: { type: "string" },
268
- specId: { type: "string" },
269
- sourceSystem: { type: "string" },
270
- phaseId: { type: "string" },
271
- preserveOriginTaskIds: { type: "boolean" },
272
- includeArchived: { type: "boolean" },
273
- archiveMissingTasks: { type: "boolean" },
274
- preserveManualDependencies: { type: "boolean" },
275
- dependencyTaskId: { type: "string" },
276
- enforceDependencies: { type: "boolean" },
277
- childTitles: { type: "array", items: { type: "string" } },
278
- inheritAssignment: { type: "boolean" },
279
- inheritLabels: { type: "boolean" },
280
- inheritSuccessCriteria: { type: "boolean" },
281
- inheritGoalMetrics: { type: "boolean" },
282
- inheritDependencies: { type: "boolean" },
283
- chainChildren: { type: "boolean" },
284
- rewireDependents: { type: "boolean" },
285
- closeSourceTasks: { type: "boolean" },
286
- metricId: { type: "string" },
287
- metricName: { type: "string" },
288
- metricTarget: { oneOf: [{ type: "string" }, { type: "number" }] },
289
- metricCurrent: { oneOf: [{ type: "string" }, { type: "number" }] },
290
- metricUnit: { type: "string" },
291
- metricStatus: { type: "string", enum: ["pending", "met", "missed", "waived"] },
292
- metricNotes: { type: "string" },
293
- checkId: { type: "string" },
294
- checkDescription: { type: "string" },
295
- checkStatus: { type: "string", enum: ["pending", "passed", "failed", "skipped"] },
296
- note: { type: "string" },
297
- author: { type: "string" },
298
- url: { type: "string" },
299
- linkTitle: { type: "string" },
300
- linkType: {
301
- type: "string",
302
- enum: ["issue", "pr", "doc", "commit", "design", "file", "url", "other"]
303
- },
304
- context: { type: "string" },
305
- columns: { type: "array", items: { type: "string" } },
306
- generatedBy: { type: "string" },
307
- includeTasks: { type: "boolean" },
308
- includeCompletedTasks: { type: "boolean" },
309
- preserveAssignment: { type: "boolean" },
310
- preserveDependencies: { type: "boolean" },
311
- moveTasksToColumnId: { type: "string" }
312
- },
313
- required: ["action"]
314
- },
841
+ timeoutMs: 3e4,
842
+ inputSchema: KANBAN_INPUT_SCHEMA,
315
843
  async execute(input, ctx) {
316
844
  const projectRoot = ctx.projectRoot;
317
845
  if (!projectRoot) return fail("No project root is available.");
318
- const withPresence = async (result) => {
319
- const boardId = result.board?.id ?? input.boardId;
320
- if (!result.ok || !boardId || !ctx.session?.id || !ctx.agentId) return result;
321
- try {
322
- const board = await touchKanbanPresence2(projectRoot, boardId, {
323
- sessionId: ctx.session.id,
324
- agentId: ctx.agentId,
325
- agentName: ctx.agentName,
326
- taskId: input.taskId ?? result.task?.id,
327
- runTaskId: input.runTaskId
328
- });
329
- return board ? { ...result, board } : result;
330
- } catch {
331
- return result;
332
- }
333
- };
846
+ const withPresence = createKanbanPresenceWrapper(projectRoot, input, ctx);
334
847
  try {
335
848
  const result = await (async () => {
849
+ const decompositionResult = await handleKanbanDecompositionAction(projectRoot, input, ctx);
850
+ if (decompositionResult !== void 0) return decompositionResult;
336
851
  switch (input.action) {
337
852
  case "list_boards": {
338
853
  const boards = await listBoards2(projectRoot);
@@ -344,32 +859,39 @@ var kanbanTool = {
344
859
  }
345
860
  case "create_board": {
346
861
  if (!input.title) return fail("create_board requires title.");
347
- const board = await createBoard2(projectRoot, {
348
- title: input.title,
349
- ...input.description !== void 0 ? { description: input.description } : {},
350
- ...input.tags !== void 0 ? { tags: input.tags } : {},
351
- ...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {}
352
- });
862
+ const board = await createBoard2(projectRoot, boardCreateInput(input, input.title));
353
863
  return { ok: true, message: `Board created: ${board.title}`, board };
354
864
  }
355
865
  case "update_board": {
356
866
  if (!input.boardId) return fail("update_board requires boardId.");
357
- const board = await updateBoard2(projectRoot, input.boardId, {
358
- ...input.title !== void 0 ? { title: input.title } : {},
359
- ...input.description !== void 0 ? { description: input.description } : {},
360
- ...input.tags !== void 0 ? { tags: input.tags } : {}
361
- });
867
+ const board = await updateBoard2(projectRoot, input.boardId, boardUpdatePatch(input));
362
868
  return board ? okBoard(board, "Board updated.") : fail("Board not found.");
363
869
  }
870
+ case "adopt_managed_lifecycle": {
871
+ if (!input.boardId || !input.author || !input.transitionComment) {
872
+ return fail(
873
+ "adopt_managed_lifecycle requires boardId, author, transitionComment, and five ordered columns."
874
+ );
875
+ }
876
+ if (input.columns?.length !== 5) {
877
+ return fail(
878
+ "adopt_managed_lifecycle columns must be ordered as backlog, todo, running, review, done."
879
+ );
880
+ }
881
+ const [backlog, todo, running, review, done] = input.columns;
882
+ if (!backlog || !todo || !running || !review || !done) {
883
+ return fail("adopt_managed_lifecycle columns must contain five nonblank ids.");
884
+ }
885
+ const board = await adoptManagedLifecycle(projectRoot, input.boardId, {
886
+ columns: { backlog, todo, running, review, done },
887
+ actor: input.author,
888
+ comment: input.transitionComment
889
+ });
890
+ return board ? okBoard(board, "Managed lifecycle adopted without moving existing cards.") : fail("Board not found.");
891
+ }
364
892
  case "duplicate_board": {
365
893
  if (!input.boardId) return fail("duplicate_board requires boardId.");
366
- const board = await duplicateBoard(projectRoot, input.boardId, {
367
- ...input.title !== void 0 ? { title: input.title } : {},
368
- ...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
369
- ...input.includeTasks !== void 0 ? { includeTasks: input.includeTasks } : {},
370
- ...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {},
371
- ...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {}
372
- });
894
+ const board = await duplicateBoard(projectRoot, input.boardId, duplicateBoardOptions(input));
373
895
  return board ? okBoard(board, "Board duplicated.") : fail("Board not found.");
374
896
  }
375
897
  case "delete_board": {
@@ -392,7 +914,7 @@ var kanbanTool = {
392
914
  )) {
393
915
  await addTask(projectRoot, board.id, taskInput2);
394
916
  }
395
- return okBoard(await getBoard2(projectRoot, board.id) ?? board, "Board generated.");
917
+ return okBoard(await getBoard3(projectRoot, board.id) ?? board, "Board generated.");
396
918
  }
397
919
  case "export_markdown": {
398
920
  const board = await requireBoard(projectRoot, input.boardId);
@@ -565,7 +1087,12 @@ var kanbanTool = {
565
1087
  case "add_task": {
566
1088
  if (!input.boardId || !input.title) return fail("add_task requires boardId and title.");
567
1089
  const result2 = await addTask(projectRoot, input.boardId, taskInput(input));
568
- return result2 ? okTask(result2.board, result2.task, "Task added.") : fail("Board not found.");
1090
+ if (!result2) return fail("Board not found.");
1091
+ return okTask(
1092
+ result2.board,
1093
+ result2.task,
1094
+ `Task added.${atomicityNudge(result2.task)}`
1095
+ );
569
1096
  }
570
1097
  case "split_task": {
571
1098
  if (!input.boardId || !input.taskId || !input.childTitles?.length) {
@@ -631,7 +1158,7 @@ var kanbanTool = {
631
1158
  case "update_task": {
632
1159
  if (!input.boardId || !input.taskId)
633
1160
  return fail("update_task requires boardId and taskId.");
634
- const board = await updateTask(
1161
+ const board = await updateTask2(
635
1162
  projectRoot,
636
1163
  input.boardId,
637
1164
  input.taskId,
@@ -645,6 +1172,19 @@ var kanbanTool = {
645
1172
  "transition_task requires boardId, taskId, lifecycleStage, author, and transitionComment."
646
1173
  );
647
1174
  }
1175
+ if (input.lifecycleStage === "done") {
1176
+ const boardBefore = await getBoard3(projectRoot, input.boardId);
1177
+ const taskBefore = boardBefore ? await getTask(projectRoot, input.boardId, input.taskId) : null;
1178
+ if (boardBefore && taskBefore && !taskBefore.verificationReport && (taskBefore.atomic || Boolean(taskBefore.successCriteria?.length))) {
1179
+ const preGate = await verifyTaskCompletion2(projectRoot, input.boardId, taskBefore.id, {
1180
+ persist: false
1181
+ });
1182
+ await updateTask2(projectRoot, input.boardId, taskBefore.id, {
1183
+ verificationReport: preGate.report,
1184
+ successCriteria: preGate.task.successCriteria
1185
+ });
1186
+ }
1187
+ }
648
1188
  const result2 = await transitionTask(projectRoot, input.boardId, input.taskId, {
649
1189
  to: input.lifecycleStage,
650
1190
  actor: input.author,
@@ -659,7 +1199,22 @@ var kanbanTool = {
659
1199
  } : {},
660
1200
  patch: taskPatch(input)
661
1201
  });
662
- return result2 ? okTask(result2.board, result2.task, `Task advanced to ${result2.transition.to}.`) : fail("Board or task not found.");
1202
+ if (result2 && input.lifecycleStage === "done" && result2.task.verificationReport) {
1203
+ recordKanbanVerificationEvidence(ctx, result2.task.verificationReport);
1204
+ }
1205
+ return result2 ? okTask(result2.board, result2.task, `Task advanced to ${result2.transition.to}.`) : fail("Board or task not found.");
1206
+ }
1207
+ case "repair_managed_projection": {
1208
+ if (!input.boardId || !input.taskId || !input.author || !input.transitionComment) {
1209
+ return fail(
1210
+ "repair_managed_projection requires boardId, taskId, author, and transitionComment."
1211
+ );
1212
+ }
1213
+ const result2 = await repairManagedTaskProjection(projectRoot, input.boardId, input.taskId, {
1214
+ actor: input.author,
1215
+ comment: input.transitionComment
1216
+ });
1217
+ return result2 ? okTask(result2.board, result2.task, "Managed card projection repaired from lifecycle history.") : fail("Board or task not found.");
663
1218
  }
664
1219
  case "move_task": {
665
1220
  if (!input.boardId || !input.taskId || !input.targetColumnId) {
@@ -772,7 +1327,142 @@ var kanbanTool = {
772
1327
  // is atomic inside updateTaskAssignment's mutateBoard lock.
773
1328
  input.expectedLeaseId !== void 0 ? { expectedLeaseId: input.expectedLeaseId } : {}
774
1329
  );
775
- return board ? okBoard(board, "Assignment updated.") : fail("Task not found.");
1330
+ if (!board) return fail("Task not found.");
1331
+ if (assignmentStatus === "completed" && board.lifecycle?.mode !== "managed") {
1332
+ const envGate = readEnvGateEnforcement();
1333
+ const finalized = await finalizeTaskCompletion(projectRoot, board.id, input.taskId, {
1334
+ ...board.completionGate === void 0 && envGate !== void 0 ? { enforcement: envGate } : {},
1335
+ ...ctx.agentId !== void 0 ? { eventContext: { actor: ctx.agentId } } : {}
1336
+ });
1337
+ if (finalized) {
1338
+ if (finalized.gate.report) {
1339
+ recordKanbanVerificationEvidence(ctx, finalized.gate.report);
1340
+ }
1341
+ const gateSummary = {
1342
+ enforcement: finalized.gate.enforcement,
1343
+ allowed: finalized.gate.allowed,
1344
+ verdict: finalized.gate.verdict,
1345
+ issues: finalized.gate.issues.map((issue) => issue.message)
1346
+ };
1347
+ const gateMessage = finalized.gate.allowed ? `Completion gate ${finalized.gate.verdict === "skipped" ? "skipped" : "passed"}; task completed.` : finalized.gate.enforcement === "strict" ? `Completion gate BLOCKED (verdict: ${finalized.gate.verdict}); task parked in review. Issues: ${gateSummary.issues.join(" | ")}` : `Completion gate failed softly (verdict: ${finalized.gate.verdict}); task completed with warnings. Issues: ${gateSummary.issues.join(" | ")}`;
1348
+ return {
1349
+ ...okTask(finalized.board, finalized.task, `Assignment updated. ${gateMessage}`),
1350
+ gate: gateSummary
1351
+ };
1352
+ }
1353
+ } else if (board.lifecycle?.mode === "managed") {
1354
+ const managedTask = board.tasks.find(
1355
+ (candidate) => candidate.id === input.taskId
1356
+ );
1357
+ const stage = managedTask?.lifecycle?.currentStage;
1358
+ const actor = ctx.agentId ?? "kanban-agent";
1359
+ let transitionResult = null;
1360
+ const lifecycleWarnings = [];
1361
+ if (assignmentStatus === "running" && stage === "todo") {
1362
+ try {
1363
+ transitionResult = await transitionTask(
1364
+ projectRoot,
1365
+ board.id,
1366
+ input.taskId,
1367
+ { to: "running", actor, comment: "Work started." }
1368
+ );
1369
+ } catch (err) {
1370
+ lifecycleWarnings.push(
1371
+ `Lifecycle transition to Running deferred: ${err instanceof Error ? err.message : String(err)}`
1372
+ );
1373
+ }
1374
+ }
1375
+ if (assignmentStatus === "completed" && stage === "running") {
1376
+ const comment = typeof input.lastResult === "string" && input.lastResult.trim().length > 0 ? input.lastResult.trim().slice(0, 1e3) : "Work completed.";
1377
+ try {
1378
+ transitionResult = await transitionTask(
1379
+ projectRoot,
1380
+ board.id,
1381
+ input.taskId,
1382
+ {
1383
+ to: "review",
1384
+ actor,
1385
+ comment,
1386
+ attachment: {
1387
+ url: `kanban://task/${input.taskId}/result`,
1388
+ title: "Worker completion result",
1389
+ type: "file"
1390
+ },
1391
+ patch: {
1392
+ // Only patch non-description fields so the
1393
+ // original card description is preserved.
1394
+ ...input.agentId !== void 0 ? { assignedAgent: input.agentId } : {}
1395
+ }
1396
+ }
1397
+ );
1398
+ } catch (err) {
1399
+ lifecycleWarnings.push(
1400
+ `Lifecycle transition to Review failed: ${err instanceof Error ? err.message : String(err)}`
1401
+ );
1402
+ }
1403
+ if (transitionResult) {
1404
+ const hasCriteria = (transitionResult.task.successCriteria?.length ?? 0) > 0 || transitionResult.task.atomic === true;
1405
+ if (hasCriteria) {
1406
+ try {
1407
+ const verResult = await verifyTaskCompletion2(
1408
+ projectRoot,
1409
+ board.id,
1410
+ input.taskId
1411
+ );
1412
+ if (verResult.report) {
1413
+ recordKanbanVerificationEvidence(ctx, verResult.report);
1414
+ }
1415
+ const verdict = verResult.report.verdict;
1416
+ if (verdict === "passed") {
1417
+ try {
1418
+ const doneResult = await transitionTask(
1419
+ projectRoot,
1420
+ board.id,
1421
+ input.taskId,
1422
+ {
1423
+ to: "done",
1424
+ actor,
1425
+ comment: "Auto-accepted: verification passed.",
1426
+ attachment: {
1427
+ url: `kanban://task/${input.taskId}/verification`,
1428
+ title: "Auto-verification result",
1429
+ type: "file"
1430
+ }
1431
+ }
1432
+ );
1433
+ transitionResult = doneResult;
1434
+ } catch (acceptErr) {
1435
+ lifecycleWarnings.push(
1436
+ `Auto-accept to Done deferred: ${acceptErr instanceof Error ? acceptErr.message : String(acceptErr)}`
1437
+ );
1438
+ }
1439
+ } else {
1440
+ lifecycleWarnings.push(
1441
+ `Verification verdict: ${verdict} \u2014 card left in Review for manual acceptance.`
1442
+ );
1443
+ }
1444
+ } catch (verifyErr) {
1445
+ lifecycleWarnings.push(
1446
+ `Auto-verification error: ${verifyErr instanceof Error ? verifyErr.message : String(verifyErr)}`
1447
+ );
1448
+ }
1449
+ } else {
1450
+ lifecycleWarnings.push(
1451
+ "No automatic success criteria \u2014 card left in Review for manual verification."
1452
+ );
1453
+ }
1454
+ }
1455
+ }
1456
+ const responseBoard = transitionResult?.board ?? board;
1457
+ const responseTask = transitionResult?.task ?? managedTask;
1458
+ const msgParts = ["Assignment updated."];
1459
+ if (transitionResult) {
1460
+ msgParts.push(`Card advanced to ${transitionResult.transition.to}.`);
1461
+ }
1462
+ for (const w of lifecycleWarnings) msgParts.push(`Warning: ${w}`);
1463
+ return okTask(responseBoard, responseTask, msgParts.join(" "));
1464
+ }
1465
+ return okBoard(board, "Assignment updated.");
776
1466
  }
777
1467
  case "heartbeat_assignment": {
778
1468
  if (!input.boardId || !input.taskId) {
@@ -844,132 +1534,11 @@ var kanbanTool = {
844
1534
  queueHealth: health
845
1535
  };
846
1536
  }
847
- case "add_dependency": {
848
- if (!input.boardId || !input.taskId || !input.dependencyTaskId) {
849
- return fail("add_dependency requires boardId, taskId, and dependencyTaskId.");
850
- }
851
- const board = await addDependency(
852
- projectRoot,
853
- input.boardId,
854
- input.taskId,
855
- input.dependencyTaskId
856
- );
857
- return board ? okBoard(board, "Dependency added.") : fail("Task not found.");
858
- }
859
- case "add_goal_metric": {
860
- if (!input.boardId || !input.taskId || !input.metricName) {
861
- return fail("add_goal_metric requires boardId, taskId, and metricName.");
862
- }
863
- const board = await addGoalMetricToTask(projectRoot, input.boardId, input.taskId, {
864
- name: input.metricName,
865
- ...input.metricStatus !== void 0 ? { status: input.metricStatus } : {},
866
- ...input.metricTarget !== void 0 ? { target: input.metricTarget } : {},
867
- ...input.metricCurrent !== void 0 ? { current: input.metricCurrent } : {},
868
- ...input.metricUnit !== void 0 ? { unit: input.metricUnit } : {},
869
- ...input.metricNotes !== void 0 ? { notes: input.metricNotes } : {}
870
- });
871
- return board ? okBoard(board, "Goal metric added.") : fail("Task not found.");
872
- }
873
- case "update_goal_metric": {
874
- if (!input.boardId || !input.taskId || !input.metricId) {
875
- return fail("update_goal_metric requires boardId, taskId, and metricId.");
876
- }
877
- const board = await updateGoalMetricOnTask(
878
- projectRoot,
879
- input.boardId,
880
- input.taskId,
881
- input.metricId,
882
- {
883
- ...input.metricName !== void 0 ? { name: input.metricName } : {},
884
- ...input.metricStatus !== void 0 ? { status: input.metricStatus } : {},
885
- ...input.metricTarget !== void 0 ? { target: input.metricTarget } : {},
886
- ...input.metricCurrent !== void 0 ? { current: input.metricCurrent } : {},
887
- ...input.metricUnit !== void 0 ? { unit: input.metricUnit } : {},
888
- ...input.metricNotes !== void 0 ? { notes: input.metricNotes } : {}
889
- }
890
- );
891
- return board ? okBoard(board, "Goal metric updated.") : fail("Metric not found.");
892
- }
893
- case "add_check": {
894
- if (!input.boardId || !input.taskId || !input.checkDescription) {
895
- return fail("add_check requires boardId, taskId, and checkDescription.");
896
- }
897
- const board = await addCheckToTask(projectRoot, input.boardId, input.taskId, {
898
- description: input.checkDescription,
899
- type: "manual",
900
- status: input.checkStatus
901
- });
902
- return board ? okBoard(board, "Check added.") : fail("Task not found.");
903
- }
904
- case "update_check": {
905
- if (!input.boardId || !input.taskId || !input.checkId) {
906
- return fail("update_check requires boardId, taskId, and checkId.");
907
- }
908
- const board = await updateCheckOnTask(
909
- projectRoot,
910
- input.boardId,
911
- input.taskId,
912
- input.checkId,
913
- {
914
- ...input.checkDescription !== void 0 ? { description: input.checkDescription } : {},
915
- ...input.checkStatus !== void 0 ? { status: input.checkStatus } : {}
916
- }
917
- );
918
- return board ? okBoard(board, "Check updated.") : fail("Check not found.");
919
- }
920
- case "add_note": {
921
- if (!input.boardId || !input.taskId || !input.note)
922
- return fail("add_note requires boardId, taskId, and note.");
923
- const board = await addNoteToTask(projectRoot, input.boardId, input.taskId, {
924
- author: input.author ?? "agent",
925
- content: input.note
926
- });
927
- return board ? okBoard(board, "Note added.") : fail("Task not found.");
928
- }
929
- case "add_link": {
930
- if (!input.boardId || !input.taskId || !input.url)
931
- return fail("add_link requires boardId, taskId, and url.");
932
- const board = await addLinkToTask(projectRoot, input.boardId, input.taskId, {
933
- url: input.url,
934
- type: input.linkType ?? "url",
935
- ...input.linkTitle !== void 0 ? { title: input.linkTitle } : {}
936
- });
937
- return board ? okBoard(board, "Link added.") : fail("Task not found.");
938
- }
939
- case "verify_completion": {
940
- if (!input.boardId || !input.taskId) {
941
- return fail("verify_completion requires boardId and taskId.");
942
- }
943
- const verResult = await verifyTaskCompletion(projectRoot, input.boardId, input.taskId);
944
- const persistedBoard = await updateTask(projectRoot, input.boardId, input.taskId, {
945
- verificationReport: verResult.report,
946
- successCriteria: verResult.task.successCriteria
947
- });
948
- if (!persistedBoard) {
949
- return {
950
- ok: false,
951
- verdict: verResult.report.verdict,
952
- message: `Verification succeeded but persist failed: ${verResult.report.markdownSummary}. Board may be stale \u2014 re-run verify_completion.`,
953
- board: verResult.board
954
- };
955
- }
956
- const freshTask = persistedBoard.tasks?.find((t) => t.id === input.taskId);
957
- const deterministicVerdicts = ["passed", "failed", "needs_human", "incomplete"];
958
- return {
959
- ok: deterministicVerdicts.includes(verResult.report.verdict),
960
- verdict: verResult.report.verdict,
961
- message: verResult.report.markdownSummary,
962
- board: persistedBoard,
963
- task: freshTask ?? verResult.task
964
- };
965
- }
966
- case "split_atomic": {
967
- if (!input.boardId || !input.taskId || !input.childTitles?.length) {
968
- return fail("split_atomic requires boardId, taskId, and childTitles (at least one).");
969
- }
970
- return handleSplitTask(projectRoot, input, { atomic: true });
971
- }
972
1537
  default:
1538
+ {
1539
+ const detailResult = await handleKanbanDetailAction(projectRoot, input);
1540
+ if (detailResult !== void 0) return detailResult;
1541
+ }
973
1542
  return fail(`Unknown kanban action: ${input.action}`);
974
1543
  }
975
1544
  })();
@@ -979,213 +1548,6 @@ var kanbanTool = {
979
1548
  }
980
1549
  }
981
1550
  };
982
- function fail(message) {
983
- return { ok: false, message };
984
- }
985
- function okBoard(board, message = "Board loaded.") {
986
- return { ok: true, message, board };
987
- }
988
- function okTask(board, task, message) {
989
- return { ok: true, message, board, task };
990
- }
991
- async function handleSplitTask(projectRoot, input, extraSplitOptions) {
992
- const boardId = input.boardId;
993
- const taskId = input.taskId;
994
- const childTitles = input.childTitles;
995
- if (!boardId || !taskId || !childTitles?.length) {
996
- return fail("split requires boardId, taskId, and at least one childTitles.");
997
- }
998
- const {
999
- targetColumnId,
1000
- inheritAssignment,
1001
- inheritLabels,
1002
- inheritSuccessCriteria,
1003
- inheritGoalMetrics,
1004
- inheritDependencies,
1005
- chainChildren,
1006
- rewireDependents
1007
- } = input;
1008
- const result = await splitTask(projectRoot, boardId, taskId, {
1009
- titles: childTitles,
1010
- ...extraSplitOptions,
1011
- ...targetColumnId !== void 0 ? { columnId: targetColumnId } : {},
1012
- ...inheritAssignment !== void 0 ? { inheritAssignment } : {},
1013
- ...inheritLabels !== void 0 ? { inheritLabels } : {},
1014
- ...inheritSuccessCriteria !== void 0 ? { inheritSuccessCriteria } : {},
1015
- ...inheritGoalMetrics !== void 0 ? { inheritGoalMetrics } : {},
1016
- ...inheritDependencies !== void 0 ? { inheritDependencies } : {},
1017
- ...chainChildren !== void 0 ? { chainChildren } : {},
1018
- ...rewireDependents !== void 0 ? { rewireDependents } : {}
1019
- });
1020
- if (!result) return fail("Task not found.");
1021
- const freshParent = result.board.tasks?.find((t) => t.id === taskId);
1022
- if (!freshParent) {
1023
- return fail(
1024
- `Split succeeded but parent ${taskId} not found in returned board. Children: [${result.children.map((c) => c.id).join(", ")}].`
1025
- );
1026
- }
1027
- return {
1028
- ok: true,
1029
- message: `${result.children.length} child task(s) created.`,
1030
- board: result.board,
1031
- task: freshParent,
1032
- children: result.children
1033
- };
1034
- }
1035
- async function requireBoard(projectRoot, boardId) {
1036
- return boardId ? getBoard2(projectRoot, boardId) : null;
1037
- }
1038
- function taskInput(input) {
1039
- const assignment = hasAssignmentInput(input) ? assignmentForTaskCreate(input) : void 0;
1040
- return {
1041
- title: input.title ?? "",
1042
- columnId: input.columnId,
1043
- description: input.description,
1044
- dueDate: input.dueDate,
1045
- priority: input.priority,
1046
- ...input.taskType !== void 0 ? { type: input.taskType } : {},
1047
- status: input.status,
1048
- labels: input.labels,
1049
- ...assignment?.agentId ?? assignment?.role ?? assignment?.name ? { assignedAgent: assignment.agentId ?? assignment.role ?? assignment.name } : {},
1050
- ...input.assignee ?? assignment?.name ?? assignment?.agentId ? { assignee: input.assignee ?? assignment?.name ?? assignment?.agentId } : {},
1051
- ...mergedDependsOn(input) ? { dependsOn: mergedDependsOn(input) } : {},
1052
- ...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {},
1053
- ...input.actualHours !== void 0 ? { actualHours: input.actualHours } : {},
1054
- ...assignment ? { assignment } : {},
1055
- ...input.order !== void 0 ? { order: input.order } : {},
1056
- // Sprint 3 durable task-level fields
1057
- ...input.retryPolicy !== void 0 ? { retryPolicy: input.retryPolicy } : {},
1058
- ...input.costCeilingUsd !== void 0 ? { costCeilingUsd: input.costCeilingUsd } : {},
1059
- // Relationships — chainId alone cannot construct a KanbanTaskChainRef,
1060
- // so chain/previousTaskId/nextTaskId must be set via update_task.
1061
- ...input.childTitles !== void 0 ? { childTaskIds: input.childTitles } : {},
1062
- // Detail objects
1063
- ...input.checkDescription !== void 0 ? {
1064
- successCriteria: [
1065
- {
1066
- id: randomUUID(),
1067
- description: input.checkDescription,
1068
- type: "manual",
1069
- status: input.checkStatus ?? "pending"
1070
- }
1071
- ]
1072
- } : {},
1073
- ...input.metricName !== void 0 ? {
1074
- goalMetrics: [
1075
- {
1076
- id: randomUUID(),
1077
- name: input.metricName,
1078
- status: input.metricStatus ?? "pending",
1079
- ...input.metricTarget !== void 0 ? { target: input.metricTarget } : {},
1080
- ...input.metricCurrent !== void 0 ? { current: input.metricCurrent } : {},
1081
- ...input.metricUnit !== void 0 ? { unit: input.metricUnit } : {},
1082
- ...input.metricNotes !== void 0 ? { notes: input.metricNotes } : {}
1083
- }
1084
- ]
1085
- } : {},
1086
- ...input.url !== void 0 ? {
1087
- links: [
1088
- {
1089
- url: input.url,
1090
- type: input.linkType ?? "url",
1091
- ...input.linkTitle !== void 0 ? { title: input.linkTitle } : {}
1092
- }
1093
- ]
1094
- } : {},
1095
- ...input.note !== void 0 ? {
1096
- notes: [
1097
- {
1098
- id: randomUUID(),
1099
- author: input.author ?? "agent",
1100
- content: input.note,
1101
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
1102
- }
1103
- ]
1104
- } : {},
1105
- // Origin tracking
1106
- ...input.graphId !== void 0 ? {
1107
- origin: {
1108
- system: input.sourceSystem ?? "kanban-tool",
1109
- ...input.graphId !== void 0 ? { graphId: input.graphId } : {},
1110
- ...input.specId !== void 0 ? { specId: input.specId } : {},
1111
- ...input.phaseId !== void 0 ? { phaseId: input.phaseId } : {}
1112
- }
1113
- } : {}
1114
- };
1115
- }
1116
- function mergedDependsOn(input) {
1117
- const ids = [
1118
- ...input.dependsOn ?? [],
1119
- ...input.dependencyTaskId !== void 0 ? [input.dependencyTaskId] : []
1120
- ].filter((id, i, arr) => id && arr.indexOf(id) === i);
1121
- return ids.length > 0 ? ids : void 0;
1122
- }
1123
- function taskPatch(input) {
1124
- return {
1125
- title: input.title,
1126
- description: input.description,
1127
- dueDate: input.dueDate,
1128
- columnId: input.columnId,
1129
- order: input.order,
1130
- priority: input.priority,
1131
- ...input.taskType !== void 0 ? { type: input.taskType } : {},
1132
- status: input.status,
1133
- labels: input.labels,
1134
- assignedAgent: input.agentId,
1135
- ...mergedDependsOn(input) ? { dependsOn: mergedDependsOn(input) } : {},
1136
- ...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {},
1137
- ...input.actualHours !== void 0 ? { actualHours: input.actualHours } : {}
1138
- };
1139
- }
1140
- function assignmentInput(input) {
1141
- return {
1142
- agentId: input.agentId,
1143
- name: input.name,
1144
- role: input.role,
1145
- provider: input.provider,
1146
- model: input.model,
1147
- fallbackProfile: input.fallbackProfile,
1148
- fallbackModels: input.fallbackModels,
1149
- tools: input.tools,
1150
- allowedCapabilities: input.allowedCapabilities,
1151
- assignee: input.assignee,
1152
- leaseId: input.leaseId,
1153
- claimedAt: input.claimedAt,
1154
- heartbeatAt: input.heartbeatAt,
1155
- leaseExpiresAt: input.leaseExpiresAt,
1156
- attempt: input.attempt,
1157
- maxAttempts: input.maxAttempts,
1158
- costCeilingUsd: input.costCeilingUsd,
1159
- retryPolicy: input.retryPolicy,
1160
- lastFailureKind: input.lastFailureKind
1161
- };
1162
- }
1163
- function hasAssignmentInput(input) {
1164
- return input.agentId !== void 0 || input.name !== void 0 || input.role !== void 0 || input.provider !== void 0 || input.model !== void 0 || input.fallbackProfile !== void 0 || input.fallbackModels !== void 0 || input.tools !== void 0 || input.allowedCapabilities !== void 0 || input.assignee !== void 0 || input.leaseId !== void 0 || input.claimedAt !== void 0 || input.heartbeatAt !== void 0 || input.leaseExpiresAt !== void 0 || input.attempt !== void 0 || input.maxAttempts !== void 0 || input.costCeilingUsd !== void 0 || input.retryPolicy !== void 0 || input.lastFailureKind !== void 0 || input.assignmentStatus !== void 0;
1165
- }
1166
- function assignmentForTaskCreate(input) {
1167
- return {
1168
- status: input.assignmentStatus ?? "assigned",
1169
- ...input.agentId !== void 0 ? { agentId: input.agentId } : {},
1170
- ...input.name !== void 0 ? { name: input.name } : {},
1171
- ...input.role !== void 0 ? { role: input.role } : {},
1172
- ...input.provider !== void 0 ? { provider: input.provider } : {},
1173
- ...input.model !== void 0 ? { model: input.model } : {},
1174
- ...input.fallbackProfile !== void 0 ? { fallbackProfile: input.fallbackProfile } : {},
1175
- ...input.fallbackModels !== void 0 ? { fallbackModels: input.fallbackModels } : {},
1176
- ...input.tools !== void 0 ? { tools: input.tools } : {},
1177
- ...input.allowedCapabilities !== void 0 ? { allowedCapabilities: input.allowedCapabilities } : {},
1178
- ...input.leaseId !== void 0 ? { leaseId: input.leaseId } : {},
1179
- ...input.claimedAt !== void 0 ? { claimedAt: input.claimedAt } : {},
1180
- ...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
1181
- ...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {},
1182
- ...input.attempt !== void 0 ? { attempt: input.attempt } : {},
1183
- ...input.maxAttempts !== void 0 ? { maxAttempts: input.maxAttempts } : {},
1184
- ...input.costCeilingUsd !== void 0 ? { costCeilingUsd: input.costCeilingUsd } : {},
1185
- ...input.retryPolicy !== void 0 ? { retryPolicy: input.retryPolicy } : {},
1186
- ...input.lastFailureKind !== void 0 ? { lastFailureKind: input.lastFailureKind } : {}
1187
- };
1188
- }
1189
1551
  export {
1190
1552
  kanbanTool
1191
1553
  };