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