@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.
- package/dist/auto-proceed-loop-guard.d.ts +67 -0
- package/dist/auto-proceed-loop-guard.d.ts.map +1 -1
- package/dist/auto-proceed-loop-guard.js +95 -0
- package/dist/auto-proceed-loop-guard.js.map +2 -2
- package/dist/bash.js +53 -18
- package/dist/bash.js.map +2 -2
- package/dist/browser/index.js +17 -7
- package/dist/browser/index.js.map +2 -2
- package/dist/browser/network-guard-proxy.d.ts.map +1 -1
- package/dist/builtin.d.ts +2 -2
- package/dist/builtin.d.ts.map +1 -1
- package/dist/builtin.js +8937 -7250
- package/dist/builtin.js.map +4 -4
- package/dist/codebase-index/background-indexer.d.ts +28 -2
- package/dist/codebase-index/background-indexer.d.ts.map +1 -1
- package/dist/codebase-index/codebase-index-tool.d.ts.map +1 -1
- package/dist/codebase-index/dead-code-scan.d.ts +77 -0
- package/dist/codebase-index/dead-code-scan.d.ts.map +1 -0
- package/dist/codebase-index/index-service.d.ts +4 -3
- package/dist/codebase-index/index-service.d.ts.map +1 -1
- package/dist/codebase-index/index.d.ts +11 -8
- package/dist/codebase-index/index.d.ts.map +1 -1
- package/dist/codebase-index/index.js +5038 -3573
- package/dist/codebase-index/index.js.map +4 -4
- package/dist/codebase-index/indexer.d.ts +2 -0
- package/dist/codebase-index/indexer.d.ts.map +1 -1
- package/dist/codebase-index/parser-dispatch.d.ts +8 -0
- package/dist/codebase-index/parser-dispatch.d.ts.map +1 -0
- package/dist/codebase-index/project-server-cache.d.ts +11 -0
- package/dist/codebase-index/project-server-cache.d.ts.map +1 -0
- package/dist/codebase-index/project-server-client.d.ts +74 -0
- package/dist/codebase-index/project-server-client.d.ts.map +1 -0
- package/dist/codebase-index/project-server-endpoint.d.ts +24 -0
- package/dist/codebase-index/project-server-endpoint.d.ts.map +1 -0
- package/dist/codebase-index/project-server-protocol.d.ts +88 -0
- package/dist/codebase-index/project-server-protocol.d.ts.map +1 -0
- package/dist/codebase-index/project-server.d.ts +11 -0
- package/dist/codebase-index/project-server.d.ts.map +1 -0
- package/dist/codebase-index/project-server.js +5061 -0
- package/dist/codebase-index/project-server.js.map +7 -0
- package/dist/codebase-index/refs-extractor.d.ts +1 -0
- package/dist/codebase-index/refs-extractor.d.ts.map +1 -1
- package/dist/codebase-index/schema.d.ts +11 -0
- package/dist/codebase-index/schema.d.ts.map +1 -1
- package/dist/codebase-index/sqlite-runtime.d.ts +9 -0
- package/dist/codebase-index/sqlite-runtime.d.ts.map +1 -0
- package/dist/codebase-index/ts-parser.d.ts +5 -1
- package/dist/codebase-index/ts-parser.d.ts.map +1 -1
- package/dist/codebase-index/worker-protocol.d.ts +19 -1
- package/dist/codebase-index/worker-protocol.d.ts.map +1 -1
- package/dist/codebase-index/worker.js +3860 -3415
- package/dist/codebase-index/worker.js.map +4 -4
- package/dist/codebase-index/writer-admin.d.ts +16 -0
- package/dist/codebase-index/writer-admin.d.ts.map +1 -0
- package/dist/codebase-index/writer-bulk-insert.d.ts +25 -0
- package/dist/codebase-index/writer-bulk-insert.d.ts.map +1 -0
- package/dist/codebase-index/writer-graph-helpers.d.ts +52 -0
- package/dist/codebase-index/writer-graph-helpers.d.ts.map +1 -0
- package/dist/codebase-index/writer-graph-reader.d.ts +11 -0
- package/dist/codebase-index/writer-graph-reader.d.ts.map +1 -0
- package/dist/codebase-index/writer-helpers.d.ts +15 -0
- package/dist/codebase-index/writer-helpers.d.ts.map +1 -0
- package/dist/codebase-index/writer-pragmas.d.ts +3 -0
- package/dist/codebase-index/writer-pragmas.d.ts.map +1 -0
- package/dist/codebase-index/writer-ref-mapper.d.ts +11 -0
- package/dist/codebase-index/writer-ref-mapper.d.ts.map +1 -0
- package/dist/codebase-index/writer-schema.d.ts +7 -0
- package/dist/codebase-index/writer-schema.d.ts.map +1 -0
- package/dist/codebase-index/writer-search-helpers.d.ts +28 -0
- package/dist/codebase-index/writer-search-helpers.d.ts.map +1 -0
- package/dist/codebase-index/writer-store-pool.d.ts +45 -0
- package/dist/codebase-index/writer-store-pool.d.ts.map +1 -0
- package/dist/codebase-index/writer.d.ts +53 -88
- package/dist/codebase-index/writer.d.ts.map +1 -1
- package/dist/exec.js +53 -18
- package/dist/exec.js.map +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9318 -7635
- package/dist/index.js.map +4 -4
- package/dist/kanban-board-inputs.d.ts +34 -0
- package/dist/kanban-board-inputs.d.ts.map +1 -0
- package/dist/kanban-decomposition-actions.d.ts +4 -0
- package/dist/kanban-decomposition-actions.d.ts.map +1 -0
- package/dist/kanban-detail-actions.d.ts +3 -0
- package/dist/kanban-detail-actions.d.ts.map +1 -0
- package/dist/kanban-presence.d.ts +10 -0
- package/dist/kanban-presence.d.ts.map +1 -0
- package/dist/kanban-split-task-handler.d.ts +6 -0
- package/dist/kanban-split-task-handler.d.ts.map +1 -0
- package/dist/kanban-task-inputs.d.ts +73 -0
- package/dist/kanban-task-inputs.d.ts.map +1 -0
- package/dist/kanban-tool-results.d.ts +14 -0
- package/dist/kanban-tool-results.d.ts.map +1 -0
- package/dist/kanban-tool-schema.d.ts +5 -0
- package/dist/kanban-tool-schema.d.ts.map +1 -0
- package/dist/kanban-tool-types.d.ts +135 -0
- package/dist/kanban-tool-types.d.ts.map +1 -0
- package/dist/kanban.d.ts +2 -146
- package/dist/kanban.d.ts.map +1 -1
- package/dist/kanban.js +1030 -825
- package/dist/kanban.js.map +4 -4
- package/dist/pack.js +8936 -7250
- package/dist/pack.js.map +4 -4
- package/dist/plan.js +2 -2
- package/dist/plan.js.map +2 -2
- package/dist/process-registry-persistent.d.ts +1 -0
- package/dist/process-registry-persistent.d.ts.map +1 -1
- package/dist/ps-slash.js +53 -18
- package/dist/ps-slash.js.map +2 -2
- package/dist/replace.js +8 -0
- package/dist/replace.js.map +2 -2
- package/dist/session-kanban.d.ts.map +1 -1
- package/dist/session-kanban.js +15 -20
- package/dist/session-kanban.js.map +2 -2
- package/dist/task.d.ts.map +1 -1
- package/dist/task.js +48 -6
- package/dist/task.js.map +2 -2
- package/dist/todo.d.ts.map +1 -1
- package/dist/todo.js +47 -5
- package/dist/todo.js.map +2 -2
- package/dist/tool-tier.js +8938 -7251
- package/dist/tool-tier.js.map +4 -4
- package/package.json +5 -5
- package/dist/shim/node-sqlite.d.ts +0 -3
- 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
|
|
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
|
-
|
|
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/
|
|
82
|
-
import { GlobalMailbox } from "@wrongstack/core/coordination";
|
|
71
|
+
// src/kanban-decomposition-actions.ts
|
|
83
72
|
import {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
} from "@wrongstack/
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
updateBoard
|
|
182
|
+
addCheckToTask,
|
|
183
|
+
addDependency,
|
|
184
|
+
addGoalMetricToTask,
|
|
185
|
+
addLinkToTask,
|
|
186
|
+
addNoteToTask,
|
|
187
|
+
updateCheckOnTask,
|
|
188
|
+
updateGoalMetricOnTask
|
|
100
189
|
} from "@wrongstack/kanban";
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
if (!
|
|
366
|
-
|
|
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
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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
|
|
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
|
|
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
|
|
1179
|
+
const preGate = await verifyTaskCompletion2(projectRoot, input.boardId, taskBefore.id, {
|
|
717
1180
|
persist: false
|
|
718
1181
|
});
|
|
719
|
-
await
|
|
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
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
result2.
|
|
1056
|
-
|
|
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 "
|
|
1061
|
-
if (!input.boardId
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
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 "
|
|
1087
|
-
|
|
1088
|
-
|
|
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:
|
|
1108
|
-
|
|
1109
|
-
|
|
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
|
};
|