@wrongstack/tools 0.295.1 → 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 (126) hide show
  1. package/dist/auto-proceed-loop-guard.d.ts +67 -0
  2. package/dist/auto-proceed-loop-guard.d.ts.map +1 -1
  3. package/dist/auto-proceed-loop-guard.js +95 -0
  4. package/dist/auto-proceed-loop-guard.js.map +2 -2
  5. package/dist/bash.js +53 -18
  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 +8937 -7250
  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/index-service.d.ts +4 -3
  20. package/dist/codebase-index/index-service.d.ts.map +1 -1
  21. package/dist/codebase-index/index.d.ts +11 -8
  22. package/dist/codebase-index/index.d.ts.map +1 -1
  23. package/dist/codebase-index/index.js +5038 -3573
  24. package/dist/codebase-index/index.js.map +4 -4
  25. package/dist/codebase-index/indexer.d.ts +2 -0
  26. package/dist/codebase-index/indexer.d.ts.map +1 -1
  27. package/dist/codebase-index/parser-dispatch.d.ts +8 -0
  28. package/dist/codebase-index/parser-dispatch.d.ts.map +1 -0
  29. package/dist/codebase-index/project-server-cache.d.ts +11 -0
  30. package/dist/codebase-index/project-server-cache.d.ts.map +1 -0
  31. package/dist/codebase-index/project-server-client.d.ts +74 -0
  32. package/dist/codebase-index/project-server-client.d.ts.map +1 -0
  33. package/dist/codebase-index/project-server-endpoint.d.ts +24 -0
  34. package/dist/codebase-index/project-server-endpoint.d.ts.map +1 -0
  35. package/dist/codebase-index/project-server-protocol.d.ts +88 -0
  36. package/dist/codebase-index/project-server-protocol.d.ts.map +1 -0
  37. package/dist/codebase-index/project-server.d.ts +11 -0
  38. package/dist/codebase-index/project-server.d.ts.map +1 -0
  39. package/dist/codebase-index/project-server.js +5061 -0
  40. package/dist/codebase-index/project-server.js.map +7 -0
  41. package/dist/codebase-index/refs-extractor.d.ts +1 -0
  42. package/dist/codebase-index/refs-extractor.d.ts.map +1 -1
  43. package/dist/codebase-index/schema.d.ts +11 -0
  44. package/dist/codebase-index/schema.d.ts.map +1 -1
  45. package/dist/codebase-index/sqlite-runtime.d.ts +9 -0
  46. package/dist/codebase-index/sqlite-runtime.d.ts.map +1 -0
  47. package/dist/codebase-index/ts-parser.d.ts +5 -1
  48. package/dist/codebase-index/ts-parser.d.ts.map +1 -1
  49. package/dist/codebase-index/worker-protocol.d.ts +19 -1
  50. package/dist/codebase-index/worker-protocol.d.ts.map +1 -1
  51. package/dist/codebase-index/worker.js +3860 -3415
  52. package/dist/codebase-index/worker.js.map +4 -4
  53. package/dist/codebase-index/writer-admin.d.ts +16 -0
  54. package/dist/codebase-index/writer-admin.d.ts.map +1 -0
  55. package/dist/codebase-index/writer-bulk-insert.d.ts +25 -0
  56. package/dist/codebase-index/writer-bulk-insert.d.ts.map +1 -0
  57. package/dist/codebase-index/writer-graph-helpers.d.ts +52 -0
  58. package/dist/codebase-index/writer-graph-helpers.d.ts.map +1 -0
  59. package/dist/codebase-index/writer-graph-reader.d.ts +11 -0
  60. package/dist/codebase-index/writer-graph-reader.d.ts.map +1 -0
  61. package/dist/codebase-index/writer-helpers.d.ts +15 -0
  62. package/dist/codebase-index/writer-helpers.d.ts.map +1 -0
  63. package/dist/codebase-index/writer-pragmas.d.ts +3 -0
  64. package/dist/codebase-index/writer-pragmas.d.ts.map +1 -0
  65. package/dist/codebase-index/writer-ref-mapper.d.ts +11 -0
  66. package/dist/codebase-index/writer-ref-mapper.d.ts.map +1 -0
  67. package/dist/codebase-index/writer-schema.d.ts +7 -0
  68. package/dist/codebase-index/writer-schema.d.ts.map +1 -0
  69. package/dist/codebase-index/writer-search-helpers.d.ts +28 -0
  70. package/dist/codebase-index/writer-search-helpers.d.ts.map +1 -0
  71. package/dist/codebase-index/writer-store-pool.d.ts +45 -0
  72. package/dist/codebase-index/writer-store-pool.d.ts.map +1 -0
  73. package/dist/codebase-index/writer.d.ts +53 -88
  74. package/dist/codebase-index/writer.d.ts.map +1 -1
  75. package/dist/exec.js +53 -18
  76. package/dist/exec.js.map +2 -2
  77. package/dist/index.d.ts +2 -2
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +9318 -7635
  80. package/dist/index.js.map +4 -4
  81. package/dist/kanban-board-inputs.d.ts +34 -0
  82. package/dist/kanban-board-inputs.d.ts.map +1 -0
  83. package/dist/kanban-decomposition-actions.d.ts +4 -0
  84. package/dist/kanban-decomposition-actions.d.ts.map +1 -0
  85. package/dist/kanban-detail-actions.d.ts +3 -0
  86. package/dist/kanban-detail-actions.d.ts.map +1 -0
  87. package/dist/kanban-presence.d.ts +10 -0
  88. package/dist/kanban-presence.d.ts.map +1 -0
  89. package/dist/kanban-split-task-handler.d.ts +6 -0
  90. package/dist/kanban-split-task-handler.d.ts.map +1 -0
  91. package/dist/kanban-task-inputs.d.ts +73 -0
  92. package/dist/kanban-task-inputs.d.ts.map +1 -0
  93. package/dist/kanban-tool-results.d.ts +14 -0
  94. package/dist/kanban-tool-results.d.ts.map +1 -0
  95. package/dist/kanban-tool-schema.d.ts +5 -0
  96. package/dist/kanban-tool-schema.d.ts.map +1 -0
  97. package/dist/kanban-tool-types.d.ts +135 -0
  98. package/dist/kanban-tool-types.d.ts.map +1 -0
  99. package/dist/kanban.d.ts +2 -146
  100. package/dist/kanban.d.ts.map +1 -1
  101. package/dist/kanban.js +1030 -825
  102. package/dist/kanban.js.map +4 -4
  103. package/dist/pack.js +8936 -7250
  104. package/dist/pack.js.map +4 -4
  105. package/dist/plan.js +2 -2
  106. package/dist/plan.js.map +2 -2
  107. package/dist/process-registry-persistent.d.ts +1 -0
  108. package/dist/process-registry-persistent.d.ts.map +1 -1
  109. package/dist/ps-slash.js +53 -18
  110. package/dist/ps-slash.js.map +2 -2
  111. package/dist/replace.js +8 -0
  112. package/dist/replace.js.map +2 -2
  113. package/dist/session-kanban.d.ts.map +1 -1
  114. package/dist/session-kanban.js +15 -20
  115. package/dist/session-kanban.js.map +2 -2
  116. package/dist/task.d.ts.map +1 -1
  117. package/dist/task.js +48 -6
  118. package/dist/task.js.map +2 -2
  119. package/dist/todo.d.ts.map +1 -1
  120. package/dist/todo.js +47 -5
  121. package/dist/todo.js.map +2 -2
  122. package/dist/tool-tier.js +8938 -7251
  123. package/dist/tool-tier.js.map +4 -4
  124. package/package.json +5 -5
  125. package/dist/shim/node-sqlite.d.ts +0 -3
  126. 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,27 +27,22 @@ 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,
52
- verifyTaskCompletion,
53
- finalizeTaskCompletion,
54
- assessTaskAtomicity,
55
- proposeTaskDecomposition
44
+ verifyTaskCompletion as verifyTaskCompletion2,
45
+ finalizeTaskCompletion
56
46
  } from "@wrongstack/kanban";
57
47
 
58
48
  // src/kanban-evidence-bridge.ts
@@ -78,409 +68,882 @@ function recordKanbanVerificationEvidence(ctx, report) {
78
68
  }
79
69
  }
80
70
 
81
- // src/session-kanban.ts
82
- import { GlobalMailbox } from "@wrongstack/core/coordination";
71
+ // src/kanban-decomposition-actions.ts
83
72
  import {
84
- loadPlan,
85
- loadTasks,
86
- mutatePlan,
87
- mutateTasks
88
- } from "@wrongstack/core/storage";
89
- import { deserializeTaskGraph } from "@wrongstack/core/tasking";
90
- import { resolveWstackPaths } from "@wrongstack/core/utils";
73
+ assessTaskAtomicity,
74
+ proposeTaskDecomposition,
75
+ updateTask,
76
+ verifyTaskCompletion
77
+ } from "@wrongstack/kanban";
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
91
181
  import {
92
- createBoard,
93
- getBoard,
94
- getKanbanDir,
95
- listBoards,
96
- removeBoard,
97
- syncBoardFromTaskGraph,
98
- touchKanbanPresence,
99
- updateBoard
182
+ addCheckToTask,
183
+ addDependency,
184
+ addGoalMetricToTask,
185
+ addLinkToTask,
186
+ addNoteToTask,
187
+ updateCheckOnTask,
188
+ updateGoalMetricOnTask
100
189
  } from "@wrongstack/kanban";
101
- function taskFileToSerializedGraph(tasks, sessionId) {
102
- const ids = new Set(tasks.map((task) => task.id));
103
- const nodes = tasks.map((task, index) => ({
104
- id: task.id,
105
- title: task.title,
106
- description: task.description ?? "",
107
- type: task.type,
108
- priority: task.priority,
109
- status: task.status,
110
- ...task.assignee ? { assignee: task.assignee } : {},
111
- ...task.estimateHours !== void 0 ? { estimateHours: task.estimateHours } : {},
112
- createdAt: index,
113
- updatedAt: index
114
- }));
115
- const edges = tasks.flatMap(
116
- (task) => (task.dependsOn ?? []).filter((dependency) => ids.has(dependency)).map((dependency) => ({
117
- id: `${dependency}->${task.id}`,
118
- from: dependency,
119
- to: task.id,
120
- type: "depends_on"
121
- }))
122
- );
123
- const hasIncoming = new Set(edges.map((edge) => edge.to));
124
- const rootNodes = nodes.filter((node) => !hasIncoming.has(node.id)).map((node) => node.id);
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
+ }
125
229
  return {
126
- // Keep the historical graph id so existing mirrored task cards are reused.
127
- id: `session:${sessionId}`,
128
- specId: `session:${sessionId}`,
129
- title: "Session tasks",
130
- nodes,
131
- edges,
132
- rootNodes: rootNodes.length ? rootNodes : nodes[0] ? [nodes[0].id] : [],
133
- createdAt: 0,
134
- updatedAt: 0
230
+ ok: true,
231
+ message: `${result.children.length} child task(s) created.`,
232
+ board: result.board,
233
+ task: freshParent,
234
+ children: result.children
135
235
  };
136
236
  }
237
+ async function requireBoard(projectRoot, boardId) {
238
+ return boardId ? getBoard(projectRoot, boardId) : null;
239
+ }
137
240
 
138
- // src/kanban.ts
139
- var kanbanTool = {
140
- name: "kanban",
141
- category: "Project",
142
- 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.",
143
- 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.",
144
- permission: "confirm",
145
- mutating: true,
146
- capabilities: ["fs.write"],
147
- icon: "task",
148
- timeoutMs: 5e3,
149
- inputSchema: {
150
- type: "object",
151
- properties: {
152
- action: {
153
- type: "string",
154
- enum: [
155
- "list_boards",
156
- "get_board",
157
- "create_board",
158
- "duplicate_board",
159
- "update_board",
160
- "delete_board",
161
- "generate_board",
162
- "export_markdown",
163
- "export_task_graph",
164
- "sync_task_graph",
165
- "create_from_graph",
166
- "import_session_tasks",
167
- "search_tasks",
168
- "ready_tasks",
169
- "snapshot",
170
- "add_column",
171
- "update_column",
172
- "delete_column",
173
- "add_task",
174
- "split_task",
175
- "merge_tasks",
176
- "copy_task",
177
- "transfer_task",
178
- "get_task",
179
- "update_task",
180
- "transition_task",
181
- "move_task",
182
- "delete_task",
183
- "set_chain",
184
- "get_chain",
185
- "claim_task",
186
- "release_task",
187
- "assign_task",
188
- "mark_assignment",
189
- "heartbeat_assignment",
190
- "recover_stale",
191
- "events",
192
- "queue_health",
193
- "add_dependency",
194
- "add_goal_metric",
195
- "update_goal_metric",
196
- "add_check",
197
- "update_check",
198
- "add_note",
199
- "add_link",
200
- "verify_completion",
201
- "split_atomic",
202
- "assess_atomicity",
203
- "propose_decomposition"
204
- ]
205
- },
206
- boardId: { type: "string" },
207
- taskId: { type: "string" },
208
- taskIds: { type: "array", items: { type: "string" } },
209
- chainId: { type: "string" },
210
- columnId: { type: "string" },
211
- targetBoardId: { type: "string" },
212
- targetColumnId: { type: "string" },
213
- title: { type: "string" },
214
- description: { type: "string" },
215
- dueDate: { type: "string" },
216
- tags: { type: "array", items: { type: "string" } },
217
- labels: { type: "array", items: { type: "string" } },
218
- priority: { type: "string", enum: ["critical", "high", "medium", "low"] },
219
- taskType: {
220
- type: "string",
221
- enum: ["feature", "bugfix", "refactor", "docs", "test", "chore"]
222
- },
223
- status: {
224
- type: "string",
225
- enum: [
226
- "pending",
227
- "ready",
228
- "in_progress",
229
- "blocked",
230
- "review",
231
- "completed",
232
- "failed",
233
- "archived"
234
- ]
235
- },
236
- order: { type: "number" },
237
- query: { type: "string" },
238
- limit: { type: "number" },
239
- agentId: { type: "string" },
240
- name: { type: "string" },
241
- role: { type: "string" },
242
- provider: { type: "string" },
243
- model: { type: "string" },
244
- fallbackProfile: { type: "string" },
245
- fallbackModels: { type: "array", items: { type: "string" } },
246
- tools: { type: "array", items: { type: "string" } },
247
- allowedCapabilities: { type: "array", items: { type: "string" } },
248
- leaseId: { type: "string" },
249
- claimedAt: { type: "string" },
250
- heartbeatAt: { type: "string" },
251
- leaseExpiresAt: { type: "string" },
252
- attempt: { type: "number" },
253
- maxAttempts: { type: "number" },
254
- subagentId: { type: "string" },
255
- runTaskId: { type: "string" },
256
- lastResult: { type: "string" },
257
- error: { type: "string" },
258
- expectedLeaseId: { type: "string" },
259
- assignmentStatus: {
260
- type: "string",
261
- enum: ["assigned", "queued", "running", "completed", "failed", "cancelled"]
262
- },
263
- lifecycleStage: {
264
- type: "string",
265
- enum: ["backlog", "todo", "running", "review", "done"]
266
- },
267
- transitionAction: { type: "string" },
268
- transitionComment: { type: "string" },
269
- attachmentUrl: { type: "string" },
270
- attachmentTitle: { type: "string" },
271
- attachmentType: {
272
- type: "string",
273
- enum: ["issue", "pr", "doc", "commit", "design", "file", "url", "other"]
274
- },
275
- releaseStatus: { type: "string", enum: ["pending", "ready", "blocked"] },
276
- releaseReason: { type: "string" },
277
- clearAssignee: { type: "boolean" },
278
- recoveryMode: { type: "string", enum: ["auto", "release", "retry", "fail"] },
279
- recoveryNow: { type: "string" },
280
- recoveryPolicyFailOnCostCeiling: { type: "boolean" },
281
- recoveryPolicyReleaseOnFailureKinds: { type: "array", items: { type: "string" } },
282
- recoveryPolicyReleaseOnHeartbeatDue: { type: "boolean" },
283
- recoveryPolicyRetryPolicyOverride: {
284
- type: "string",
285
- enum: ["off", "incremental", "exponential"]
286
- },
287
- assignee: { type: "string" },
288
- costCeilingUsd: { type: "number" },
289
- retryPolicy: { type: "string", enum: ["off", "incremental", "exponential"] },
290
- lastFailureKind: { type: "string" },
291
- dependsOn: { type: "array", items: { type: "string" } },
292
- estimatedHours: { type: "number" },
293
- actualHours: { type: "number" },
294
- taskGraph: { type: "object" },
295
- graphId: { type: "string" },
296
- specId: { type: "string" },
297
- sourceSystem: { type: "string" },
298
- phaseId: { type: "string" },
299
- preserveOriginTaskIds: { type: "boolean" },
300
- includeArchived: { type: "boolean" },
301
- archiveMissingTasks: { type: "boolean" },
302
- preserveManualDependencies: { type: "boolean" },
303
- dependencyTaskId: { type: "string" },
304
- enforceDependencies: { type: "boolean" },
305
- childTitles: { type: "array", items: { type: "string" } },
306
- inheritAssignment: { type: "boolean" },
307
- inheritLabels: { type: "boolean" },
308
- inheritSuccessCriteria: { type: "boolean" },
309
- inheritGoalMetrics: { type: "boolean" },
310
- inheritDependencies: { type: "boolean" },
311
- chainChildren: { type: "boolean" },
312
- rewireDependents: { type: "boolean" },
313
- closeSourceTasks: { type: "boolean" },
314
- metricId: { type: "string" },
315
- metricName: { type: "string" },
316
- metricTarget: { oneOf: [{ type: "string" }, { type: "number" }] },
317
- metricCurrent: { oneOf: [{ type: "string" }, { type: "number" }] },
318
- metricUnit: { type: "string" },
319
- metricStatus: { type: "string", enum: ["pending", "met", "missed", "waived"] },
320
- metricNotes: { type: "string" },
321
- checkId: { type: "string" },
322
- checkDescription: { type: "string" },
323
- checkStatus: { type: "string", enum: ["pending", "passed", "failed", "skipped"] },
324
- note: { type: "string" },
325
- author: { type: "string" },
326
- url: { type: "string" },
327
- linkTitle: { type: "string" },
328
- linkType: {
329
- type: "string",
330
- enum: ["issue", "pr", "doc", "commit", "design", "file", "url", "other"]
331
- },
332
- context: { type: "string" },
333
- columns: { type: "array", items: { type: "string" } },
334
- generatedBy: { type: "string" },
335
- includeTasks: { type: "boolean" },
336
- includeCompletedTasks: { type: "boolean" },
337
- preserveAssignment: { type: "boolean" },
338
- preserveDependencies: { type: "boolean" },
339
- moveTasksToColumnId: { type: "string" },
340
- atomicityMode: { type: "string", enum: ["off", "assess", "enforce"] },
341
- atomicityDecomposition: { type: "string", enum: ["auto", "propose"] },
342
- gateEnforcement: { type: "string", enum: ["strict", "soft", "off"] },
343
- subtasks: {
344
- type: "array",
345
- minItems: 2,
346
- items: {
347
- type: "object",
348
- properties: {
349
- title: { type: "string" },
350
- description: { type: "string" },
351
- successCriteria: { type: "array", items: { type: "string" } },
352
- dependsOnIndex: { type: "array", items: { type: "number" } }
353
- },
354
- required: ["title"]
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 } : {}
355
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.");
356
293
  }
357
- },
358
- required: ["action"]
359
- },
360
- async execute(input, ctx) {
361
- const projectRoot = ctx.projectRoot;
362
- if (!projectRoot) return fail("No project root is available.");
363
- const withPresence = async (result) => {
364
- const boardId = result.board?.id ?? input.boardId;
365
- if (!result.ok || !boardId || !ctx.session?.id || !ctx.agentId) return result;
366
- try {
367
- const board = await touchKanbanPresence2(projectRoot, boardId, {
368
- sessionId: ctx.session.id,
369
- agentId: ctx.agentId,
370
- agentName: ctx.agentName,
371
- taskId: input.taskId ?? result.task?.id,
372
- runTaskId: input.runTaskId
373
- });
374
- return board ? { ...result, board } : result;
375
- } catch {
376
- return result;
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.");
377
304
  }
378
- };
379
- try {
380
- const result = await (async () => {
381
- switch (input.action) {
382
- case "list_boards": {
383
- const boards = await listBoards2(projectRoot);
384
- return { ok: true, message: `${boards.length} board(s).`, boards };
385
- }
386
- case "get_board": {
387
- const board = await requireBoard(projectRoot, input.boardId);
388
- return board ? okBoard(board) : fail("Board not found.");
389
- }
390
- case "create_board": {
391
- if (!input.title) return fail("create_board requires title.");
392
- const board = await createBoard2(projectRoot, {
393
- title: input.title,
394
- ...input.description !== void 0 ? { description: input.description } : {},
395
- ...input.tags !== void 0 ? { tags: input.tags } : {},
396
- ...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
397
- ...input.atomicityMode !== void 0 ? {
398
- atomicity: {
399
- mode: input.atomicityMode,
400
- decomposition: input.atomicityDecomposition ?? "propose"
401
- }
402
- } : {},
403
- ...input.gateEnforcement !== void 0 ? { completionGate: { enforcement: input.gateEnforcement } } : {}
404
- });
405
- return { ok: true, message: `Board created: ${board.title}`, board };
406
- }
407
- case "update_board": {
408
- if (!input.boardId) return fail("update_board requires boardId.");
409
- const board = await updateBoard2(projectRoot, input.boardId, {
410
- ...input.title !== void 0 ? { title: input.title } : {},
411
- ...input.description !== void 0 ? { description: input.description } : {},
412
- ...input.tags !== void 0 ? { tags: input.tags } : {},
413
- ...input.atomicityMode !== void 0 ? {
414
- atomicity: {
415
- mode: input.atomicityMode,
416
- decomposition: input.atomicityDecomposition ?? "propose"
417
- }
418
- } : {},
419
- ...input.gateEnforcement !== void 0 ? { completionGate: { enforcement: input.gateEnforcement } } : {}
420
- });
421
- return board ? okBoard(board, "Board updated.") : fail("Board not found.");
422
- }
423
- case "duplicate_board": {
424
- if (!input.boardId) return fail("duplicate_board requires boardId.");
425
- const board = await duplicateBoard(projectRoot, input.boardId, {
426
- ...input.title !== void 0 ? { title: input.title } : {},
427
- ...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
428
- ...input.includeTasks !== void 0 ? { includeTasks: input.includeTasks } : {},
429
- ...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {},
430
- ...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {}
431
- });
432
- return board ? okBoard(board, "Board duplicated.") : fail("Board not found.");
433
- }
434
- case "delete_board": {
435
- if (!input.boardId) return fail("delete_board requires boardId.");
436
- const removed = await removeBoard2(projectRoot, input.boardId);
437
- return { ok: removed, message: removed ? "Board deleted." : "Board not found." };
438
- }
439
- case "generate_board": {
440
- if (!input.description) return fail("generate_board requires description.");
441
- const boardInput = createBoardFromText({
442
- description: input.description,
443
- ...input.title !== void 0 ? { title: input.title } : {},
444
- ...input.context !== void 0 ? { context: input.context } : {},
445
- ...input.columns !== void 0 ? { columns: input.columns } : {}
446
- });
447
- const board = await createBoard2(projectRoot, boardInput);
448
- for (const taskInput2 of parseLinesIntoTasks(
449
- input.description,
450
- board.columns[0]?.id ?? "backlog"
451
- )) {
452
- await addTask(projectRoot, board.id, taskInput2);
453
- }
454
- return okBoard(await getBoard2(projectRoot, board.id) ?? board, "Board generated.");
455
- }
456
- case "export_markdown": {
457
- const board = await requireBoard(projectRoot, input.boardId);
458
- if (!board) return fail("Board not found.");
459
- return {
460
- ok: true,
461
- message: "Board exported.",
462
- board,
463
- markdown: exportBoardAsMarkdown(board)
464
- };
465
- }
466
- case "export_task_graph": {
467
- if (!input.boardId) return fail("export_task_graph requires boardId.");
468
- const exported = await exportBoardToTaskGraph(projectRoot, input.boardId, {
469
- ...input.graphId !== void 0 ? { graphId: input.graphId } : {},
470
- ...input.specId !== void 0 ? { specId: input.specId } : {},
471
- ...input.title !== void 0 ? { title: input.title } : {},
472
- ...input.preserveOriginTaskIds !== void 0 ? { preserveOriginTaskIds: input.preserveOriginTaskIds } : {},
473
- ...input.includeArchived !== void 0 ? { includeArchived: input.includeArchived } : {}
474
- });
475
- if (!exported) return fail("Board not found.");
476
- return {
477
- ok: true,
478
- message: `Task graph exported with ${exported.graph.nodes.size} node(s).`,
479
- board: exported.board,
480
- taskGraph: serializeTaskGraph(exported.graph)
481
- };
482
- }
483
- case "sync_task_graph": {
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
+
774
+ // src/session-kanban.ts
775
+ import { getSharedProjectMailbox } from "@wrongstack/core/coordination";
776
+ import {
777
+ loadPlan,
778
+ loadTasks,
779
+ mutatePlan,
780
+ mutateTasks
781
+ } from "@wrongstack/core/storage";
782
+ import { deserializeTaskGraph } from "@wrongstack/core/tasking";
783
+ import { resolveWstackPaths } from "@wrongstack/core/utils";
784
+ import {
785
+ bridgeKanbanSupervisor,
786
+ createBoard,
787
+ getBoard as getBoard2,
788
+ listBoards,
789
+ removeBoard,
790
+ syncBoardFromTaskGraph,
791
+ touchKanbanPresence as touchKanbanPresence2,
792
+ updateBoard
793
+ } from "@wrongstack/kanban";
794
+ function taskFileToSerializedGraph(tasks, sessionId) {
795
+ const ids = new Set(tasks.map((task) => task.id));
796
+ const nodes = tasks.map((task, index) => ({
797
+ id: task.id,
798
+ title: task.title,
799
+ description: task.description ?? "",
800
+ type: task.type,
801
+ priority: task.priority,
802
+ status: task.status,
803
+ ...task.assignee ? { assignee: task.assignee } : {},
804
+ ...task.estimateHours !== void 0 ? { estimateHours: task.estimateHours } : {},
805
+ createdAt: index,
806
+ updatedAt: index
807
+ }));
808
+ const edges = tasks.flatMap(
809
+ (task) => (task.dependsOn ?? []).filter((dependency) => ids.has(dependency)).map((dependency) => ({
810
+ id: `${dependency}->${task.id}`,
811
+ from: dependency,
812
+ to: task.id,
813
+ type: "depends_on"
814
+ }))
815
+ );
816
+ const hasIncoming = new Set(edges.map((edge) => edge.to));
817
+ const rootNodes = nodes.filter((node) => !hasIncoming.has(node.id)).map((node) => node.id);
818
+ return {
819
+ // Keep the historical graph id so existing mirrored task cards are reused.
820
+ id: `session:${sessionId}`,
821
+ specId: `session:${sessionId}`,
822
+ title: "Session tasks",
823
+ nodes,
824
+ edges,
825
+ rootNodes: rootNodes.length ? rootNodes : nodes[0] ? [nodes[0].id] : [],
826
+ createdAt: 0,
827
+ updatedAt: 0
828
+ };
829
+ }
830
+
831
+ // src/kanban.ts
832
+ var kanbanTool = {
833
+ name: "kanban",
834
+ category: "Project",
835
+ description: KANBAN_TOOL_DESCRIPTION,
836
+ usageHint: KANBAN_TOOL_USAGE_HINT,
837
+ permission: "confirm",
838
+ mutating: true,
839
+ capabilities: ["fs.write"],
840
+ icon: "task",
841
+ timeoutMs: 3e4,
842
+ inputSchema: KANBAN_INPUT_SCHEMA,
843
+ async execute(input, ctx) {
844
+ const projectRoot = ctx.projectRoot;
845
+ if (!projectRoot) return fail("No project root is available.");
846
+ const withPresence = createKanbanPresenceWrapper(projectRoot, input, ctx);
847
+ try {
848
+ const result = await (async () => {
849
+ const decompositionResult = await handleKanbanDecompositionAction(projectRoot, input, ctx);
850
+ if (decompositionResult !== void 0) return decompositionResult;
851
+ switch (input.action) {
852
+ case "list_boards": {
853
+ const boards = await listBoards2(projectRoot);
854
+ return { ok: true, message: `${boards.length} board(s).`, boards };
855
+ }
856
+ case "get_board": {
857
+ const board = await requireBoard(projectRoot, input.boardId);
858
+ return board ? okBoard(board) : fail("Board not found.");
859
+ }
860
+ case "create_board": {
861
+ if (!input.title) return fail("create_board requires title.");
862
+ const board = await createBoard2(projectRoot, boardCreateInput(input, input.title));
863
+ return { ok: true, message: `Board created: ${board.title}`, board };
864
+ }
865
+ case "update_board": {
866
+ if (!input.boardId) return fail("update_board requires boardId.");
867
+ const board = await updateBoard2(projectRoot, input.boardId, boardUpdatePatch(input));
868
+ return board ? okBoard(board, "Board updated.") : fail("Board not found.");
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
+ }
892
+ case "duplicate_board": {
893
+ if (!input.boardId) return fail("duplicate_board requires boardId.");
894
+ const board = await duplicateBoard(projectRoot, input.boardId, duplicateBoardOptions(input));
895
+ return board ? okBoard(board, "Board duplicated.") : fail("Board not found.");
896
+ }
897
+ case "delete_board": {
898
+ if (!input.boardId) return fail("delete_board requires boardId.");
899
+ const removed = await removeBoard2(projectRoot, input.boardId);
900
+ return { ok: removed, message: removed ? "Board deleted." : "Board not found." };
901
+ }
902
+ case "generate_board": {
903
+ if (!input.description) return fail("generate_board requires description.");
904
+ const boardInput = createBoardFromText({
905
+ description: input.description,
906
+ ...input.title !== void 0 ? { title: input.title } : {},
907
+ ...input.context !== void 0 ? { context: input.context } : {},
908
+ ...input.columns !== void 0 ? { columns: input.columns } : {}
909
+ });
910
+ const board = await createBoard2(projectRoot, boardInput);
911
+ for (const taskInput2 of parseLinesIntoTasks(
912
+ input.description,
913
+ board.columns[0]?.id ?? "backlog"
914
+ )) {
915
+ await addTask(projectRoot, board.id, taskInput2);
916
+ }
917
+ return okBoard(await getBoard3(projectRoot, board.id) ?? board, "Board generated.");
918
+ }
919
+ case "export_markdown": {
920
+ const board = await requireBoard(projectRoot, input.boardId);
921
+ if (!board) return fail("Board not found.");
922
+ return {
923
+ ok: true,
924
+ message: "Board exported.",
925
+ board,
926
+ markdown: exportBoardAsMarkdown(board)
927
+ };
928
+ }
929
+ case "export_task_graph": {
930
+ if (!input.boardId) return fail("export_task_graph requires boardId.");
931
+ const exported = await exportBoardToTaskGraph(projectRoot, input.boardId, {
932
+ ...input.graphId !== void 0 ? { graphId: input.graphId } : {},
933
+ ...input.specId !== void 0 ? { specId: input.specId } : {},
934
+ ...input.title !== void 0 ? { title: input.title } : {},
935
+ ...input.preserveOriginTaskIds !== void 0 ? { preserveOriginTaskIds: input.preserveOriginTaskIds } : {},
936
+ ...input.includeArchived !== void 0 ? { includeArchived: input.includeArchived } : {}
937
+ });
938
+ if (!exported) return fail("Board not found.");
939
+ return {
940
+ ok: true,
941
+ message: `Task graph exported with ${exported.graph.nodes.size} node(s).`,
942
+ board: exported.board,
943
+ taskGraph: serializeTaskGraph(exported.graph)
944
+ };
945
+ }
946
+ case "sync_task_graph": {
484
947
  if (!input.boardId || !input.taskGraph) {
485
948
  return fail("sync_task_graph requires boardId and taskGraph.");
486
949
  }
@@ -695,7 +1158,7 @@ var kanbanTool = {
695
1158
  case "update_task": {
696
1159
  if (!input.boardId || !input.taskId)
697
1160
  return fail("update_task requires boardId and taskId.");
698
- const board = await updateTask(
1161
+ const board = await updateTask2(
699
1162
  projectRoot,
700
1163
  input.boardId,
701
1164
  input.taskId,
@@ -710,13 +1173,13 @@ var kanbanTool = {
710
1173
  );
711
1174
  }
712
1175
  if (input.lifecycleStage === "done") {
713
- const boardBefore = await getBoard2(projectRoot, input.boardId);
1176
+ const boardBefore = await getBoard3(projectRoot, input.boardId);
714
1177
  const taskBefore = boardBefore ? await getTask(projectRoot, input.boardId, input.taskId) : null;
715
1178
  if (boardBefore && taskBefore && !taskBefore.verificationReport && (taskBefore.atomic || Boolean(taskBefore.successCriteria?.length))) {
716
- const preGate = await verifyTaskCompletion(projectRoot, input.boardId, taskBefore.id, {
1179
+ const preGate = await verifyTaskCompletion2(projectRoot, input.boardId, taskBefore.id, {
717
1180
  persist: false
718
1181
  });
719
- await updateTask(projectRoot, input.boardId, taskBefore.id, {
1182
+ await updateTask2(projectRoot, input.boardId, taskBefore.id, {
720
1183
  verificationReport: preGate.report,
721
1184
  successCriteria: preGate.task.successCriteria
722
1185
  });
@@ -741,6 +1204,18 @@ var kanbanTool = {
741
1204
  }
742
1205
  return result2 ? okTask(result2.board, result2.task, `Task advanced to ${result2.transition.to}.`) : fail("Board or task not found.");
743
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.");
1218
+ }
744
1219
  case "move_task": {
745
1220
  if (!input.boardId || !input.taskId || !input.targetColumnId) {
746
1221
  return fail("move_task requires boardId, taskId, and targetColumnId.");
@@ -853,7 +1328,7 @@ var kanbanTool = {
853
1328
  input.expectedLeaseId !== void 0 ? { expectedLeaseId: input.expectedLeaseId } : {}
854
1329
  );
855
1330
  if (!board) return fail("Task not found.");
856
- if (assignmentStatus === "completed") {
1331
+ if (assignmentStatus === "completed" && board.lifecycle?.mode !== "managed") {
857
1332
  const envGate = readEnvGateEnforcement();
858
1333
  const finalized = await finalizeTaskCompletion(projectRoot, board.id, input.taskId, {
859
1334
  ...board.completionGate === void 0 && envGate !== void 0 ? { enforcement: envGate } : {},
@@ -875,6 +1350,117 @@ var kanbanTool = {
875
1350
  gate: gateSummary
876
1351
  };
877
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(" "));
878
1464
  }
879
1465
  return okBoard(board, "Assignment updated.");
880
1466
  }
@@ -915,209 +1501,44 @@ var kanbanTool = {
915
1501
  } : {},
916
1502
  ...input.recoveryPolicyReleaseOnHeartbeatDue !== void 0 ? {
917
1503
  releaseOnHeartbeatDue: input.recoveryPolicyReleaseOnHeartbeatDue
918
- } : {},
919
- ...input.recoveryPolicyRetryPolicyOverride !== void 0 ? {
920
- retryPolicyOverride: input.recoveryPolicyRetryPolicyOverride
921
- } : {}
922
- }
923
- } : {}
924
- });
925
- return result2 ? {
926
- ok: true,
927
- message: `Recovered ${result2.tasks.length} stale assignment(s).`,
928
- board: result2.board,
929
- recoveredTasks: result2.tasks
930
- } : { ok: true, message: "No stale assignment matched.", recoveredTasks: [] };
931
- }
932
- case "events": {
933
- if (!input.boardId) return fail("events requires boardId.");
934
- const eventList = await listKanbanEvents(projectRoot, input.boardId);
935
- return {
936
- ok: true,
937
- message: `${eventList.length} event(s).`,
938
- events: eventList
939
- };
940
- }
941
- case "queue_health": {
942
- const health = await getKanbanQueueHealth(projectRoot, {
943
- ...input.boardId !== void 0 ? { boardId: input.boardId } : {}
944
- });
945
- return {
946
- ok: true,
947
- message: `Counts: ready=${health.counts.ready}, running=${health.counts.running}, stale=${health.staleAssignments.count}.`,
948
- queueHealth: health
949
- };
950
- }
951
- case "add_dependency": {
952
- if (!input.boardId || !input.taskId || !input.dependencyTaskId) {
953
- return fail("add_dependency requires boardId, taskId, and dependencyTaskId.");
954
- }
955
- const board = await addDependency(
956
- projectRoot,
957
- input.boardId,
958
- input.taskId,
959
- input.dependencyTaskId
960
- );
961
- return board ? okBoard(board, "Dependency added.") : fail("Task not found.");
962
- }
963
- case "add_goal_metric": {
964
- if (!input.boardId || !input.taskId || !input.metricName) {
965
- return fail("add_goal_metric requires boardId, taskId, and metricName.");
966
- }
967
- const board = await addGoalMetricToTask(projectRoot, input.boardId, input.taskId, {
968
- name: input.metricName,
969
- ...input.metricStatus !== void 0 ? { status: input.metricStatus } : {},
970
- ...input.metricTarget !== void 0 ? { target: input.metricTarget } : {},
971
- ...input.metricCurrent !== void 0 ? { current: input.metricCurrent } : {},
972
- ...input.metricUnit !== void 0 ? { unit: input.metricUnit } : {},
973
- ...input.metricNotes !== void 0 ? { notes: input.metricNotes } : {}
974
- });
975
- return board ? okBoard(board, "Goal metric added.") : fail("Task not found.");
976
- }
977
- case "update_goal_metric": {
978
- if (!input.boardId || !input.taskId || !input.metricId) {
979
- return fail("update_goal_metric requires boardId, taskId, and metricId.");
980
- }
981
- const board = await updateGoalMetricOnTask(
982
- projectRoot,
983
- input.boardId,
984
- input.taskId,
985
- input.metricId,
986
- {
987
- ...input.metricName !== void 0 ? { name: input.metricName } : {},
988
- ...input.metricStatus !== void 0 ? { status: input.metricStatus } : {},
989
- ...input.metricTarget !== void 0 ? { target: input.metricTarget } : {},
990
- ...input.metricCurrent !== void 0 ? { current: input.metricCurrent } : {},
991
- ...input.metricUnit !== void 0 ? { unit: input.metricUnit } : {},
992
- ...input.metricNotes !== void 0 ? { notes: input.metricNotes } : {}
993
- }
994
- );
995
- return board ? okBoard(board, "Goal metric updated.") : fail("Metric not found.");
996
- }
997
- case "add_check": {
998
- if (!input.boardId || !input.taskId || !input.checkDescription) {
999
- return fail("add_check requires boardId, taskId, and checkDescription.");
1000
- }
1001
- const board = await addCheckToTask(projectRoot, input.boardId, input.taskId, {
1002
- description: input.checkDescription,
1003
- type: "manual",
1004
- status: input.checkStatus
1005
- });
1006
- return board ? okBoard(board, "Check added.") : fail("Task not found.");
1007
- }
1008
- case "update_check": {
1009
- if (!input.boardId || !input.taskId || !input.checkId) {
1010
- return fail("update_check requires boardId, taskId, and checkId.");
1011
- }
1012
- const board = await updateCheckOnTask(
1013
- projectRoot,
1014
- input.boardId,
1015
- input.taskId,
1016
- input.checkId,
1017
- {
1018
- ...input.checkDescription !== void 0 ? { description: input.checkDescription } : {},
1019
- ...input.checkStatus !== void 0 ? { status: input.checkStatus } : {}
1020
- }
1021
- );
1022
- return board ? okBoard(board, "Check updated.") : fail("Check not found.");
1023
- }
1024
- case "add_note": {
1025
- if (!input.boardId || !input.taskId || !input.note)
1026
- return fail("add_note requires boardId, taskId, and note.");
1027
- const board = await addNoteToTask(projectRoot, input.boardId, input.taskId, {
1028
- author: input.author ?? "agent",
1029
- content: input.note
1030
- });
1031
- return board ? okBoard(board, "Note added.") : fail("Task not found.");
1032
- }
1033
- case "add_link": {
1034
- if (!input.boardId || !input.taskId || !input.url)
1035
- return fail("add_link requires boardId, taskId, and url.");
1036
- const board = await addLinkToTask(projectRoot, input.boardId, input.taskId, {
1037
- url: input.url,
1038
- type: input.linkType ?? "url",
1039
- ...input.linkTitle !== void 0 ? { title: input.linkTitle } : {}
1040
- });
1041
- return board ? okBoard(board, "Link added.") : fail("Task not found.");
1042
- }
1043
- case "assess_atomicity": {
1044
- if (!input.boardId || !input.taskId) {
1045
- return fail("assess_atomicity requires boardId and taskId.");
1046
- }
1047
- const result2 = await assessTaskAtomicity(projectRoot, input.boardId, input.taskId, {
1048
- assessedBy: "agent",
1049
- ...ctx.agentId !== void 0 ? { eventContext: { actor: ctx.agentId } } : {}
1504
+ } : {},
1505
+ ...input.recoveryPolicyRetryPolicyOverride !== void 0 ? {
1506
+ retryPolicyOverride: input.recoveryPolicyRetryPolicyOverride
1507
+ } : {}
1508
+ }
1509
+ } : {}
1050
1510
  });
1051
- if (!result2) return fail("Task not found.");
1052
- const failing = result2.assessment.criteria.filter((entry) => entry.score < 1).map((entry) => entry.reason);
1053
- const guidance = result2.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(" | ")}` : result2.assessment.verdict === "composite" ? " Container task: work happens in its children; it is verified via subtask aggregation." : "";
1054
- return okTask(
1055
- result2.board,
1056
- result2.task,
1057
- `Atomicity verdict: ${result2.assessment.verdict} (score ${result2.assessment.score}).${guidance}`
1058
- );
1511
+ return result2 ? {
1512
+ ok: true,
1513
+ message: `Recovered ${result2.tasks.length} stale assignment(s).`,
1514
+ board: result2.board,
1515
+ recoveredTasks: result2.tasks
1516
+ } : { ok: true, message: "No stale assignment matched.", recoveredTasks: [] };
1059
1517
  }
1060
- case "propose_decomposition": {
1061
- if (!input.boardId || !input.taskId || !input.subtasks?.length) {
1062
- return fail("propose_decomposition requires boardId, taskId, and subtasks (2+).");
1063
- }
1064
- if (input.subtasks.length < 2) {
1065
- return fail("propose_decomposition requires at least two subtasks.");
1066
- }
1067
- const invalid = input.subtasks.find(
1068
- (subtask) => typeof subtask?.title !== "string" || !subtask.title.trim()
1069
- );
1070
- if (invalid) return fail("Every proposed subtask needs a non-blank title.");
1071
- const result2 = await proposeTaskDecomposition(
1072
- projectRoot,
1073
- input.boardId,
1074
- input.taskId,
1075
- {
1076
- subtasks: input.subtasks,
1077
- ...input.note !== void 0 ? { rationale: input.note } : {},
1078
- ...ctx.agentId !== void 0 ? { proposedBy: ctx.agentId } : {}
1079
- },
1080
- ctx.agentId !== void 0 ? { actor: ctx.agentId } : {}
1081
- );
1082
- if (!result2) return fail("Task not found.");
1083
- const message = result2.proposal.status === "applied" ? `Decomposition applied: ${result2.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.';
1084
- return okTask(result2.board, result2.task, message);
1518
+ case "events": {
1519
+ if (!input.boardId) return fail("events requires boardId.");
1520
+ const eventList = await listKanbanEvents(projectRoot, input.boardId);
1521
+ return {
1522
+ ok: true,
1523
+ message: `${eventList.length} event(s).`,
1524
+ events: eventList
1525
+ };
1085
1526
  }
1086
- case "verify_completion": {
1087
- if (!input.boardId || !input.taskId) {
1088
- return fail("verify_completion requires boardId and taskId.");
1089
- }
1090
- const verResult = await verifyTaskCompletion(projectRoot, input.boardId, input.taskId);
1091
- const persistedBoard = await updateTask(projectRoot, input.boardId, input.taskId, {
1092
- verificationReport: verResult.report,
1093
- successCriteria: verResult.task.successCriteria
1527
+ case "queue_health": {
1528
+ const health = await getKanbanQueueHealth(projectRoot, {
1529
+ ...input.boardId !== void 0 ? { boardId: input.boardId } : {}
1094
1530
  });
1095
- if (!persistedBoard) {
1096
- return {
1097
- ok: false,
1098
- verdict: verResult.report.verdict,
1099
- message: `Verification succeeded but persist failed: ${verResult.report.markdownSummary}. Board may be stale \u2014 re-run verify_completion.`,
1100
- board: verResult.board
1101
- };
1102
- }
1103
- recordKanbanVerificationEvidence(ctx, verResult.report);
1104
- const freshTask = persistedBoard.tasks?.find((t) => t.id === input.taskId);
1105
- const deterministicVerdicts = ["passed", "failed", "needs_human", "incomplete"];
1106
1531
  return {
1107
- ok: deterministicVerdicts.includes(verResult.report.verdict),
1108
- verdict: verResult.report.verdict,
1109
- message: verResult.report.markdownSummary,
1110
- board: persistedBoard,
1111
- task: freshTask ?? verResult.task
1532
+ ok: true,
1533
+ message: `Counts: ready=${health.counts.ready}, running=${health.counts.running}, stale=${health.staleAssignments.count}.`,
1534
+ queueHealth: health
1112
1535
  };
1113
1536
  }
1114
- case "split_atomic": {
1115
- if (!input.boardId || !input.taskId || !input.childTitles?.length) {
1116
- return fail("split_atomic requires boardId, taskId, and childTitles (at least one).");
1117
- }
1118
- return handleSplitTask(projectRoot, input, { atomic: true });
1119
- }
1120
1537
  default:
1538
+ {
1539
+ const detailResult = await handleKanbanDetailAction(projectRoot, input);
1540
+ if (detailResult !== void 0) return detailResult;
1541
+ }
1121
1542
  return fail(`Unknown kanban action: ${input.action}`);
1122
1543
  }
1123
1544
  })();
@@ -1127,222 +1548,6 @@ var kanbanTool = {
1127
1548
  }
1128
1549
  }
1129
1550
  };
1130
- function atomicityNudge(task) {
1131
- if (task.atomicityAssessment?.verdict !== "needs_decomposition") return "";
1132
- const reasons = task.atomicityAssessment.criteria.filter((entry) => entry.score < 1).map((entry) => entry.reason).join(" | ");
1133
- return ` Atomicity: needs_decomposition (score ${task.atomicityAssessment.score}) \u2014 call propose_decomposition with 2+ subtasks before dispatch. Reasons: ${reasons}`;
1134
- }
1135
- function readEnvGateEnforcement() {
1136
- const raw = process.env["WRONGSTACK_KANBAN_GATE"]?.trim().toLowerCase();
1137
- return raw === "strict" || raw === "soft" || raw === "off" ? raw : void 0;
1138
- }
1139
- function fail(message) {
1140
- return { ok: false, message };
1141
- }
1142
- function okBoard(board, message = "Board loaded.") {
1143
- return { ok: true, message, board };
1144
- }
1145
- function okTask(board, task, message) {
1146
- return { ok: true, message, board, task };
1147
- }
1148
- async function handleSplitTask(projectRoot, input, extraSplitOptions) {
1149
- const boardId = input.boardId;
1150
- const taskId = input.taskId;
1151
- const childTitles = input.childTitles;
1152
- if (!boardId || !taskId || !childTitles?.length) {
1153
- return fail("split requires boardId, taskId, and at least one childTitles.");
1154
- }
1155
- const {
1156
- targetColumnId,
1157
- inheritAssignment,
1158
- inheritLabels,
1159
- inheritSuccessCriteria,
1160
- inheritGoalMetrics,
1161
- inheritDependencies,
1162
- chainChildren,
1163
- rewireDependents
1164
- } = input;
1165
- const result = await splitTask(projectRoot, boardId, taskId, {
1166
- titles: childTitles,
1167
- ...extraSplitOptions,
1168
- ...targetColumnId !== void 0 ? { columnId: targetColumnId } : {},
1169
- ...inheritAssignment !== void 0 ? { inheritAssignment } : {},
1170
- ...inheritLabels !== void 0 ? { inheritLabels } : {},
1171
- ...inheritSuccessCriteria !== void 0 ? { inheritSuccessCriteria } : {},
1172
- ...inheritGoalMetrics !== void 0 ? { inheritGoalMetrics } : {},
1173
- ...inheritDependencies !== void 0 ? { inheritDependencies } : {},
1174
- ...chainChildren !== void 0 ? { chainChildren } : {},
1175
- ...rewireDependents !== void 0 ? { rewireDependents } : {}
1176
- });
1177
- if (!result) return fail("Task not found.");
1178
- const freshParent = result.board.tasks?.find((t) => t.id === taskId);
1179
- if (!freshParent) {
1180
- return fail(
1181
- `Split succeeded but parent ${taskId} not found in returned board. Children: [${result.children.map((c) => c.id).join(", ")}].`
1182
- );
1183
- }
1184
- return {
1185
- ok: true,
1186
- message: `${result.children.length} child task(s) created.`,
1187
- board: result.board,
1188
- task: freshParent,
1189
- children: result.children
1190
- };
1191
- }
1192
- async function requireBoard(projectRoot, boardId) {
1193
- return boardId ? getBoard2(projectRoot, boardId) : null;
1194
- }
1195
- function taskInput(input) {
1196
- const assignment = hasAssignmentInput(input) ? assignmentForTaskCreate(input) : void 0;
1197
- return {
1198
- title: input.title ?? "",
1199
- columnId: input.columnId,
1200
- description: input.description,
1201
- dueDate: input.dueDate,
1202
- priority: input.priority,
1203
- ...input.taskType !== void 0 ? { type: input.taskType } : {},
1204
- status: input.status,
1205
- labels: input.labels,
1206
- ...assignment?.agentId ?? assignment?.role ?? assignment?.name ? { assignedAgent: assignment.agentId ?? assignment.role ?? assignment.name } : {},
1207
- ...input.assignee ?? assignment?.name ?? assignment?.agentId ? { assignee: input.assignee ?? assignment?.name ?? assignment?.agentId } : {},
1208
- ...mergedDependsOn(input) ? { dependsOn: mergedDependsOn(input) } : {},
1209
- ...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {},
1210
- ...input.actualHours !== void 0 ? { actualHours: input.actualHours } : {},
1211
- ...assignment ? { assignment } : {},
1212
- ...input.order !== void 0 ? { order: input.order } : {},
1213
- // Sprint 3 durable task-level fields
1214
- ...input.retryPolicy !== void 0 ? { retryPolicy: input.retryPolicy } : {},
1215
- ...input.costCeilingUsd !== void 0 ? { costCeilingUsd: input.costCeilingUsd } : {},
1216
- // Relationships — chainId alone cannot construct a KanbanTaskChainRef,
1217
- // so chain/previousTaskId/nextTaskId must be set via update_task.
1218
- ...input.childTitles !== void 0 ? { childTaskIds: input.childTitles } : {},
1219
- // Detail objects
1220
- ...input.checkDescription !== void 0 ? {
1221
- successCriteria: [
1222
- {
1223
- id: randomUUID(),
1224
- description: input.checkDescription,
1225
- type: "manual",
1226
- status: input.checkStatus ?? "pending"
1227
- }
1228
- ]
1229
- } : {},
1230
- ...input.metricName !== void 0 ? {
1231
- goalMetrics: [
1232
- {
1233
- id: randomUUID(),
1234
- name: input.metricName,
1235
- status: input.metricStatus ?? "pending",
1236
- ...input.metricTarget !== void 0 ? { target: input.metricTarget } : {},
1237
- ...input.metricCurrent !== void 0 ? { current: input.metricCurrent } : {},
1238
- ...input.metricUnit !== void 0 ? { unit: input.metricUnit } : {},
1239
- ...input.metricNotes !== void 0 ? { notes: input.metricNotes } : {}
1240
- }
1241
- ]
1242
- } : {},
1243
- ...input.url !== void 0 ? {
1244
- links: [
1245
- {
1246
- url: input.url,
1247
- type: input.linkType ?? "url",
1248
- ...input.linkTitle !== void 0 ? { title: input.linkTitle } : {}
1249
- }
1250
- ]
1251
- } : {},
1252
- ...input.note !== void 0 ? {
1253
- notes: [
1254
- {
1255
- id: randomUUID(),
1256
- author: input.author ?? "agent",
1257
- content: input.note,
1258
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
1259
- }
1260
- ]
1261
- } : {},
1262
- // Origin tracking
1263
- ...input.graphId !== void 0 ? {
1264
- origin: {
1265
- system: input.sourceSystem ?? "kanban-tool",
1266
- ...input.graphId !== void 0 ? { graphId: input.graphId } : {},
1267
- ...input.specId !== void 0 ? { specId: input.specId } : {},
1268
- ...input.phaseId !== void 0 ? { phaseId: input.phaseId } : {}
1269
- }
1270
- } : {}
1271
- };
1272
- }
1273
- function mergedDependsOn(input) {
1274
- const ids = [
1275
- ...input.dependsOn ?? [],
1276
- ...input.dependencyTaskId !== void 0 ? [input.dependencyTaskId] : []
1277
- ].filter((id, i, arr) => id && arr.indexOf(id) === i);
1278
- return ids.length > 0 ? ids : void 0;
1279
- }
1280
- function taskPatch(input) {
1281
- return {
1282
- title: input.title,
1283
- description: input.description,
1284
- dueDate: input.dueDate,
1285
- columnId: input.columnId,
1286
- order: input.order,
1287
- priority: input.priority,
1288
- ...input.taskType !== void 0 ? { type: input.taskType } : {},
1289
- status: input.status,
1290
- labels: input.labels,
1291
- assignedAgent: input.agentId,
1292
- ...mergedDependsOn(input) ? { dependsOn: mergedDependsOn(input) } : {},
1293
- ...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {},
1294
- ...input.actualHours !== void 0 ? { actualHours: input.actualHours } : {}
1295
- };
1296
- }
1297
- function assignmentInput(input) {
1298
- return {
1299
- agentId: input.agentId,
1300
- name: input.name,
1301
- role: input.role,
1302
- provider: input.provider,
1303
- model: input.model,
1304
- fallbackProfile: input.fallbackProfile,
1305
- fallbackModels: input.fallbackModels,
1306
- tools: input.tools,
1307
- allowedCapabilities: input.allowedCapabilities,
1308
- assignee: input.assignee,
1309
- leaseId: input.leaseId,
1310
- claimedAt: input.claimedAt,
1311
- heartbeatAt: input.heartbeatAt,
1312
- leaseExpiresAt: input.leaseExpiresAt,
1313
- attempt: input.attempt,
1314
- maxAttempts: input.maxAttempts,
1315
- costCeilingUsd: input.costCeilingUsd,
1316
- retryPolicy: input.retryPolicy,
1317
- lastFailureKind: input.lastFailureKind
1318
- };
1319
- }
1320
- function hasAssignmentInput(input) {
1321
- 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;
1322
- }
1323
- function assignmentForTaskCreate(input) {
1324
- return {
1325
- status: input.assignmentStatus ?? "assigned",
1326
- ...input.agentId !== void 0 ? { agentId: input.agentId } : {},
1327
- ...input.name !== void 0 ? { name: input.name } : {},
1328
- ...input.role !== void 0 ? { role: input.role } : {},
1329
- ...input.provider !== void 0 ? { provider: input.provider } : {},
1330
- ...input.model !== void 0 ? { model: input.model } : {},
1331
- ...input.fallbackProfile !== void 0 ? { fallbackProfile: input.fallbackProfile } : {},
1332
- ...input.fallbackModels !== void 0 ? { fallbackModels: input.fallbackModels } : {},
1333
- ...input.tools !== void 0 ? { tools: input.tools } : {},
1334
- ...input.allowedCapabilities !== void 0 ? { allowedCapabilities: input.allowedCapabilities } : {},
1335
- ...input.leaseId !== void 0 ? { leaseId: input.leaseId } : {},
1336
- ...input.claimedAt !== void 0 ? { claimedAt: input.claimedAt } : {},
1337
- ...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
1338
- ...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {},
1339
- ...input.attempt !== void 0 ? { attempt: input.attempt } : {},
1340
- ...input.maxAttempts !== void 0 ? { maxAttempts: input.maxAttempts } : {},
1341
- ...input.costCeilingUsd !== void 0 ? { costCeilingUsd: input.costCeilingUsd } : {},
1342
- ...input.retryPolicy !== void 0 ? { retryPolicy: input.retryPolicy } : {},
1343
- ...input.lastFailureKind !== void 0 ? { lastFailureKind: input.lastFailureKind } : {}
1344
- };
1345
- }
1346
1551
  export {
1347
1552
  kanbanTool
1348
1553
  };