@wrongstack/tools 0.302.0 → 0.303.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/builtin.js +2799 -609
- package/dist/codebase-index/binary-frame.d.ts +43 -0
- package/dist/codebase-index/codebase-incoming-calls-tool.d.ts +1 -0
- package/dist/codebase-index/codebase-outgoing-calls-tool.d.ts +1 -0
- package/dist/codebase-index/content-hash.d.ts +66 -0
- package/dist/codebase-index/index.js +1569 -148
- package/dist/codebase-index/parser-worker-pool.d.ts +63 -0
- package/dist/codebase-index/parser-worker-script.d.ts +42 -0
- package/dist/codebase-index/project-server-protocol.d.ts +2 -0
- package/dist/codebase-index/project-server.js +1455 -99
- package/dist/codebase-index/schema.d.ts +7 -0
- package/dist/codebase-index/tree-sitter/queries.d.ts +48 -0
- package/dist/codebase-index/tree-sitter/util.d.ts +31 -0
- package/dist/codebase-index/tree-sitter/visitor.d.ts +47 -0
- package/dist/codebase-index/tree-sitter-parser.d.ts +58 -0
- package/dist/codebase-index/vector-search.d.ts +62 -0
- package/dist/codebase-index/worker-protocol.d.ts +2 -0
- package/dist/codebase-index/worker.js +1424 -68
- package/dist/codebase-index/writer-bulk-insert.d.ts +5 -0
- package/dist/codebase-index/writer-graph-reader.d.ts +39 -0
- package/dist/codebase-index/writer-schema.d.ts +9 -2
- package/dist/codebase-index/writer.d.ts +36 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2785 -634
- package/dist/kanban-task-inputs.d.ts +1 -0
- package/dist/kanban-tool-schema.d.ts +2 -2
- package/dist/kanban-tool-types.d.ts +18 -2
- package/dist/kanban.js +392 -126
- package/dist/pack.js +2799 -609
- package/dist/plan.d.ts +4 -1
- package/dist/plan.js +2380 -9
- package/dist/read.js +1531 -98
- package/dist/session-kanban.d.ts +8 -0
- package/dist/session-kanban.js +111 -17
- package/dist/task.d.ts +5 -4
- package/dist/task.js +2418 -43
- package/dist/todo.d.ts +10 -1
- package/dist/todo.js +2152 -20
- package/dist/tool-tier.js +2799 -609
- package/package.json +8 -4
package/dist/kanban.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/kanban.ts
|
|
2
|
-
import {
|
|
2
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
3
3
|
import { loadTasks as loadTasks2 } from "@wrongstack/core/storage";
|
|
4
|
+
import { deserializeTaskGraph as deserializeTaskGraph2, serializeTaskGraph } from "@wrongstack/core/tasking";
|
|
4
5
|
import {
|
|
5
6
|
addColumn,
|
|
6
7
|
addTask,
|
|
@@ -10,10 +11,12 @@ import {
|
|
|
10
11
|
copyTaskToBoard,
|
|
11
12
|
createBoard as createBoard2,
|
|
12
13
|
createBoardFromTaskGraph,
|
|
14
|
+
createBoardFromText,
|
|
13
15
|
duplicateBoard,
|
|
16
|
+
evaluateContractGraphReadiness,
|
|
14
17
|
exportBoardAsMarkdown,
|
|
15
18
|
exportBoardToTaskGraph,
|
|
16
|
-
|
|
19
|
+
finalizeTaskCompletion,
|
|
17
20
|
getBoard as getBoard3,
|
|
18
21
|
getKanbanOrchestrationSnapshot,
|
|
19
22
|
getKanbanQueueHealth,
|
|
@@ -27,22 +30,73 @@ import {
|
|
|
27
30
|
moveTask,
|
|
28
31
|
parseLinesIntoTasks,
|
|
29
32
|
recoverStaleTaskAssignments,
|
|
30
|
-
repairManagedTaskProjection,
|
|
31
33
|
releaseTaskClaim,
|
|
32
34
|
removeBoard as removeBoard2,
|
|
33
35
|
removeColumn,
|
|
34
36
|
removeTask,
|
|
37
|
+
repairManagedTaskProjection,
|
|
35
38
|
searchKanban,
|
|
36
39
|
setTaskChain,
|
|
37
40
|
syncBoardFromTaskGraph as syncBoardFromTaskGraph2,
|
|
38
|
-
transitionTask,
|
|
39
41
|
transferTaskToBoard,
|
|
42
|
+
transitionTask,
|
|
40
43
|
updateBoard as updateBoard2,
|
|
41
44
|
updateColumn,
|
|
42
45
|
updateTask as updateTask2,
|
|
43
46
|
updateTaskAssignment,
|
|
44
|
-
verifyTaskCompletion as verifyTaskCompletion2
|
|
45
|
-
|
|
47
|
+
verifyTaskCompletion as verifyTaskCompletion2
|
|
48
|
+
} from "@wrongstack/kanban";
|
|
49
|
+
|
|
50
|
+
// src/kanban-board-inputs.ts
|
|
51
|
+
function agentSettableGate(enforcement) {
|
|
52
|
+
if (enforcement === void 0 || enforcement === "off") return {};
|
|
53
|
+
return { completionGate: { enforcement } };
|
|
54
|
+
}
|
|
55
|
+
function boardCreateInput(input, title) {
|
|
56
|
+
return {
|
|
57
|
+
title,
|
|
58
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
59
|
+
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
60
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
61
|
+
...input.atomicityMode !== void 0 ? {
|
|
62
|
+
atomicity: {
|
|
63
|
+
mode: input.atomicityMode,
|
|
64
|
+
decomposition: input.atomicityDecomposition ?? "propose"
|
|
65
|
+
}
|
|
66
|
+
} : {},
|
|
67
|
+
...agentSettableGate(input.gateEnforcement)
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function boardUpdatePatch(input) {
|
|
71
|
+
return {
|
|
72
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
73
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
74
|
+
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
75
|
+
...input.atomicityMode !== void 0 ? {
|
|
76
|
+
atomicity: {
|
|
77
|
+
mode: input.atomicityMode,
|
|
78
|
+
decomposition: input.atomicityDecomposition ?? "propose"
|
|
79
|
+
}
|
|
80
|
+
} : {},
|
|
81
|
+
...agentSettableGate(input.gateEnforcement)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function duplicateBoardOptions(input) {
|
|
85
|
+
return {
|
|
86
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
87
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
88
|
+
...input.includeTasks !== void 0 ? { includeTasks: input.includeTasks } : {},
|
|
89
|
+
...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {},
|
|
90
|
+
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/kanban-decomposition-actions.ts
|
|
95
|
+
import {
|
|
96
|
+
assessTaskAtomicity,
|
|
97
|
+
proposeTaskDecomposition,
|
|
98
|
+
updateTask,
|
|
99
|
+
verifyTaskCompletion
|
|
46
100
|
} from "@wrongstack/kanban";
|
|
47
101
|
|
|
48
102
|
// src/kanban-evidence-bridge.ts
|
|
@@ -68,14 +122,6 @@ function recordKanbanVerificationEvidence(ctx, report) {
|
|
|
68
122
|
}
|
|
69
123
|
}
|
|
70
124
|
|
|
71
|
-
// src/kanban-decomposition-actions.ts
|
|
72
|
-
import {
|
|
73
|
-
assessTaskAtomicity,
|
|
74
|
-
proposeTaskDecomposition,
|
|
75
|
-
updateTask,
|
|
76
|
-
verifyTaskCompletion
|
|
77
|
-
} from "@wrongstack/kanban";
|
|
78
|
-
|
|
79
125
|
// src/kanban-tool-results.ts
|
|
80
126
|
function atomicityNudge(task) {
|
|
81
127
|
if (task.atomicityAssessment?.verdict !== "needs_decomposition") return "";
|
|
@@ -180,12 +226,20 @@ async function handleKanbanDecompositionAction(projectRoot, input, ctx) {
|
|
|
180
226
|
// src/kanban-detail-actions.ts
|
|
181
227
|
import {
|
|
182
228
|
addCheckToTask,
|
|
229
|
+
addContractEdge,
|
|
183
230
|
addDependency,
|
|
184
231
|
addGoalMetricToTask,
|
|
185
232
|
addLinkToTask,
|
|
186
233
|
addNoteToTask,
|
|
234
|
+
configureContractGraph,
|
|
235
|
+
evaluateTaskContractGraph,
|
|
236
|
+
getContractGraph,
|
|
237
|
+
getKanbanWorkbench,
|
|
238
|
+
removeContractEdge,
|
|
239
|
+
removeContractNode,
|
|
187
240
|
updateCheckOnTask,
|
|
188
|
-
updateGoalMetricOnTask
|
|
241
|
+
updateGoalMetricOnTask,
|
|
242
|
+
upsertContractNode
|
|
189
243
|
} from "@wrongstack/kanban";
|
|
190
244
|
|
|
191
245
|
// src/kanban-split-task-handler.ts
|
|
@@ -241,6 +295,141 @@ async function requireBoard(projectRoot, boardId) {
|
|
|
241
295
|
// src/kanban-detail-actions.ts
|
|
242
296
|
async function handleKanbanDetailAction(projectRoot, input) {
|
|
243
297
|
switch (input.action) {
|
|
298
|
+
case "workbench": {
|
|
299
|
+
const workbench = await getKanbanWorkbench(projectRoot, {
|
|
300
|
+
...input.limit !== void 0 ? { limitPerLane: input.limit, alertLimit: input.limit } : {}
|
|
301
|
+
});
|
|
302
|
+
return {
|
|
303
|
+
ok: true,
|
|
304
|
+
message: `${workbench.totals.now} now, ${workbench.totals.next} next, ${workbench.totals.blocked} blocked, ${workbench.totals.review} review; ${workbench.alertTotal} alert(s).`,
|
|
305
|
+
workbench
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
case "get_contract_graph": {
|
|
309
|
+
if (!input.boardId) return fail("get_contract_graph requires boardId.");
|
|
310
|
+
const result = await getContractGraph(projectRoot, input.boardId);
|
|
311
|
+
return result ? {
|
|
312
|
+
ok: true,
|
|
313
|
+
message: result.graph ? `${result.graph.nodes.length} contract node(s), ${result.graph.edges.length} edge(s).` : "Contract graph is not configured.",
|
|
314
|
+
board: result.board,
|
|
315
|
+
...result.graph ? { contractGraph: result.graph } : {}
|
|
316
|
+
} : fail("Board not found.");
|
|
317
|
+
}
|
|
318
|
+
case "configure_contract_graph": {
|
|
319
|
+
if (!input.boardId || !input.contractGraphEnforcement) {
|
|
320
|
+
return fail("configure_contract_graph requires boardId and contractGraphEnforcement.");
|
|
321
|
+
}
|
|
322
|
+
const current = await getContractGraph(projectRoot, input.boardId);
|
|
323
|
+
if (!current) return fail("Board not found.");
|
|
324
|
+
if (input.contractGraphEnforcement === "strict" && current.graph?.enforcement !== "strict") {
|
|
325
|
+
return fail(
|
|
326
|
+
"Strict Contract Map enforcement is operator-owned. Autonomous agents may use advisory maps but may not turn them into an execution gate."
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
if (current.graph?.enforcement === "strict" && input.contractGraphEnforcement !== "strict") {
|
|
330
|
+
return fail("An autonomous agent may not loosen a strict contract graph.");
|
|
331
|
+
}
|
|
332
|
+
const board = await configureContractGraph(
|
|
333
|
+
projectRoot,
|
|
334
|
+
input.boardId,
|
|
335
|
+
input.contractGraphEnforcement
|
|
336
|
+
);
|
|
337
|
+
return board ? okBoard(board, "Contract graph configured.") : fail("Board not found.");
|
|
338
|
+
}
|
|
339
|
+
case "upsert_contract_node": {
|
|
340
|
+
if (!input.boardId || !input.taskId || !input.contractNodeKind || !input.title) {
|
|
341
|
+
return fail("upsert_contract_node requires boardId, taskId, contractNodeKind, and title.");
|
|
342
|
+
}
|
|
343
|
+
if (input.contractNodeState === "waived") {
|
|
344
|
+
return fail(
|
|
345
|
+
"The autonomous kanban tool may not waive contract nodes; a human-owned review surface must record that exception."
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
if (input.contractNodeId) {
|
|
349
|
+
const current = await getContractGraph(projectRoot, input.boardId);
|
|
350
|
+
const existing = current?.graph?.nodes.find((node) => node.id === input.contractNodeId);
|
|
351
|
+
if (current?.graph?.enforcement === "strict" && existing && (existing.kind !== input.contractNodeKind || input.contractEnforcement !== void 0 && input.contractEnforcement !== existing.enforcement)) {
|
|
352
|
+
return fail(
|
|
353
|
+
"The autonomous kanban tool may not change the kind or enforcement of an existing strict contract node."
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const result = await upsertContractNode(projectRoot, input.boardId, {
|
|
358
|
+
...input.contractNodeId ? { id: input.contractNodeId } : {},
|
|
359
|
+
taskId: input.taskId,
|
|
360
|
+
kind: input.contractNodeKind,
|
|
361
|
+
title: input.title,
|
|
362
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
363
|
+
...input.contractEnforcement !== void 0 ? { enforcement: input.contractEnforcement } : {},
|
|
364
|
+
...input.contractNodeState !== void 0 ? { state: input.contractNodeState } : {},
|
|
365
|
+
...input.checkId !== void 0 ? { checkId: input.checkId } : {},
|
|
366
|
+
...input.metricId !== void 0 ? { metricId: input.metricId } : {},
|
|
367
|
+
...input.baseline !== void 0 ? { baseline: input.baseline } : {},
|
|
368
|
+
...input.threshold !== void 0 ? { threshold: input.threshold } : {},
|
|
369
|
+
...input.author !== void 0 ? { createdBy: input.author } : {}
|
|
370
|
+
});
|
|
371
|
+
return result ? {
|
|
372
|
+
...okBoard(result.board, "Contract node saved."),
|
|
373
|
+
contractGraph: result.board.contractGraph
|
|
374
|
+
} : fail("Task not found.");
|
|
375
|
+
}
|
|
376
|
+
case "link_contract_nodes": {
|
|
377
|
+
if (!input.boardId || !input.fromNodeId || !input.toNodeId || !input.contractEdgeType) {
|
|
378
|
+
return fail(
|
|
379
|
+
"link_contract_nodes requires boardId, fromNodeId, toNodeId, and contractEdgeType."
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
const result = await addContractEdge(projectRoot, input.boardId, {
|
|
383
|
+
from: input.fromNodeId,
|
|
384
|
+
to: input.toNodeId,
|
|
385
|
+
type: input.contractEdgeType,
|
|
386
|
+
...input.contractEdgeId ? { id: input.contractEdgeId } : {},
|
|
387
|
+
...input.contractEnforcement ? { enforcement: input.contractEnforcement } : {},
|
|
388
|
+
...input.contractRationale ? { rationale: input.contractRationale } : {},
|
|
389
|
+
...input.author ? { createdBy: input.author } : {}
|
|
390
|
+
});
|
|
391
|
+
return result ? {
|
|
392
|
+
...okBoard(result.board, "Contract edge added."),
|
|
393
|
+
contractGraph: result.board.contractGraph
|
|
394
|
+
} : fail("Board not found.");
|
|
395
|
+
}
|
|
396
|
+
case "remove_contract_node": {
|
|
397
|
+
if (!input.boardId || !input.contractNodeId) {
|
|
398
|
+
return fail("remove_contract_node requires boardId and contractNodeId.");
|
|
399
|
+
}
|
|
400
|
+
const current = await getContractGraph(projectRoot, input.boardId);
|
|
401
|
+
const node = current?.graph?.nodes.find((candidate) => candidate.id === input.contractNodeId);
|
|
402
|
+
if (current?.graph?.enforcement === "strict" && node?.enforcement === "blocking") {
|
|
403
|
+
return fail("The autonomous kanban tool may not remove a blocking strict contract node.");
|
|
404
|
+
}
|
|
405
|
+
const board = await removeContractNode(projectRoot, input.boardId, input.contractNodeId);
|
|
406
|
+
return board ? okBoard(board, "Contract node removed.") : fail("Contract node not found.");
|
|
407
|
+
}
|
|
408
|
+
case "remove_contract_edge": {
|
|
409
|
+
if (!input.boardId || !input.contractEdgeId) {
|
|
410
|
+
return fail("remove_contract_edge requires boardId and contractEdgeId.");
|
|
411
|
+
}
|
|
412
|
+
const current = await getContractGraph(projectRoot, input.boardId);
|
|
413
|
+
const edge = current?.graph?.edges.find((candidate) => candidate.id === input.contractEdgeId);
|
|
414
|
+
if (current?.graph?.enforcement === "strict" && edge?.enforcement === "blocking") {
|
|
415
|
+
return fail("The autonomous kanban tool may not remove a blocking strict contract edge.");
|
|
416
|
+
}
|
|
417
|
+
const board = await removeContractEdge(projectRoot, input.boardId, input.contractEdgeId);
|
|
418
|
+
return board ? okBoard(board, "Contract edge removed.") : fail("Contract edge not found.");
|
|
419
|
+
}
|
|
420
|
+
case "evaluate_contract_graph": {
|
|
421
|
+
if (!input.boardId || !input.taskId) {
|
|
422
|
+
return fail("evaluate_contract_graph requires boardId and taskId.");
|
|
423
|
+
}
|
|
424
|
+
const result = await evaluateTaskContractGraph(projectRoot, input.boardId, input.taskId);
|
|
425
|
+
return result ? {
|
|
426
|
+
ok: result.evaluation.allowed,
|
|
427
|
+
message: result.evaluation.allowed ? "Contract graph is closed." : `Contract graph has ${result.evaluation.issues.length} unresolved issue(s).`,
|
|
428
|
+
board: result.board,
|
|
429
|
+
contractGraph: result.board.contractGraph,
|
|
430
|
+
contractEvaluation: result.evaluation
|
|
431
|
+
} : fail("Task not found.");
|
|
432
|
+
}
|
|
244
433
|
case "add_dependency": {
|
|
245
434
|
if (!input.boardId || !input.taskId || !input.dependencyTaskId) {
|
|
246
435
|
return fail("add_dependency requires boardId, taskId, and dependencyTaskId.");
|
|
@@ -344,47 +533,24 @@ async function handleKanbanDetailAction(projectRoot, input) {
|
|
|
344
533
|
}
|
|
345
534
|
}
|
|
346
535
|
|
|
347
|
-
// src/kanban-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
return
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
};
|
|
366
|
-
}
|
|
367
|
-
function boardUpdatePatch(input) {
|
|
368
|
-
return {
|
|
369
|
-
...input.title !== void 0 ? { title: input.title } : {},
|
|
370
|
-
...input.description !== void 0 ? { description: input.description } : {},
|
|
371
|
-
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
372
|
-
...input.atomicityMode !== void 0 ? {
|
|
373
|
-
atomicity: {
|
|
374
|
-
mode: input.atomicityMode,
|
|
375
|
-
decomposition: input.atomicityDecomposition ?? "propose"
|
|
376
|
-
}
|
|
377
|
-
} : {},
|
|
378
|
-
...agentSettableGate(input.gateEnforcement)
|
|
379
|
-
};
|
|
380
|
-
}
|
|
381
|
-
function duplicateBoardOptions(input) {
|
|
382
|
-
return {
|
|
383
|
-
...input.title !== void 0 ? { title: input.title } : {},
|
|
384
|
-
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
385
|
-
...input.includeTasks !== void 0 ? { includeTasks: input.includeTasks } : {},
|
|
386
|
-
...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {},
|
|
387
|
-
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {}
|
|
536
|
+
// src/kanban-presence.ts
|
|
537
|
+
import { touchKanbanPresence } from "@wrongstack/kanban";
|
|
538
|
+
function createKanbanPresenceWrapper(projectRoot, input, ctx) {
|
|
539
|
+
return async (result) => {
|
|
540
|
+
const boardId = result.board?.id ?? input.boardId;
|
|
541
|
+
if (!result.ok || !boardId || !ctx.session?.id || !ctx.agentId) return result;
|
|
542
|
+
try {
|
|
543
|
+
const board = await touchKanbanPresence(projectRoot, boardId, {
|
|
544
|
+
sessionId: ctx.session.id,
|
|
545
|
+
agentId: ctx.agentId,
|
|
546
|
+
agentName: ctx.agentName,
|
|
547
|
+
taskId: input.taskId ?? result.task?.id,
|
|
548
|
+
runTaskId: input.runTaskId
|
|
549
|
+
});
|
|
550
|
+
return board ? { ...result, board } : result;
|
|
551
|
+
} catch {
|
|
552
|
+
return result;
|
|
553
|
+
}
|
|
388
554
|
};
|
|
389
555
|
}
|
|
390
556
|
|
|
@@ -454,11 +620,12 @@ function taskInput(input) {
|
|
|
454
620
|
}
|
|
455
621
|
]
|
|
456
622
|
} : {},
|
|
457
|
-
...input.graphId !== void 0 ? {
|
|
623
|
+
...[input.graphId, input.specId, input.specRequirementId].some((value) => value !== void 0) ? {
|
|
458
624
|
origin: {
|
|
459
625
|
system: input.sourceSystem ?? "kanban-tool",
|
|
460
626
|
...input.graphId !== void 0 ? { graphId: input.graphId } : {},
|
|
461
627
|
...input.specId !== void 0 ? { specId: input.specId } : {},
|
|
628
|
+
...input.specRequirementId !== void 0 ? { specRequirementId: input.specRequirementId } : {},
|
|
462
629
|
...input.phaseId !== void 0 ? { phaseId: input.phaseId } : {}
|
|
463
630
|
}
|
|
464
631
|
} : {}
|
|
@@ -543,8 +710,8 @@ function assignmentForTaskCreate(input) {
|
|
|
543
710
|
}
|
|
544
711
|
|
|
545
712
|
// src/kanban-tool-schema.ts
|
|
546
|
-
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.
|
|
547
|
-
var KANBAN_TOOL_USAGE_HINT = "Use this for durable project kanban state.
|
|
713
|
+
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. Contract Map actions are optional advisory metadata unless an operator explicitly enabled strict enforcement. Use verify_completion to validate executable success criteria before Done.";
|
|
714
|
+
var KANBAN_TOOL_USAGE_HINT = "Use this for durable project kanban state. Read workbench when orienting across boards; it returns bounded Now, Next, Blocked, Review lanes and operational alerts. Before coding mutation, create a fully detailed card on a managed board with executable acceptance criteria, then call start_task. The runtime blocks product mutations until start_task binds a ready Running card. Do not create, inspect, repair, or enable a Contract Map during ordinary work; every map mode stays off the execution and completion path. Worker completion enters Review; Done requires passed acceptance criteria and review evidence. Surface existing strict-map findings for operator review without stopping work to repair them.";
|
|
548
715
|
var KANBAN_INPUT_SCHEMA = {
|
|
549
716
|
type: "object",
|
|
550
717
|
properties: {
|
|
@@ -567,6 +734,7 @@ var KANBAN_INPUT_SCHEMA = {
|
|
|
567
734
|
"search_tasks",
|
|
568
735
|
"ready_tasks",
|
|
569
736
|
"snapshot",
|
|
737
|
+
"workbench",
|
|
570
738
|
"add_column",
|
|
571
739
|
"update_column",
|
|
572
740
|
"delete_column",
|
|
@@ -576,6 +744,7 @@ var KANBAN_INPUT_SCHEMA = {
|
|
|
576
744
|
"copy_task",
|
|
577
745
|
"transfer_task",
|
|
578
746
|
"get_task",
|
|
747
|
+
"start_task",
|
|
579
748
|
"update_task",
|
|
580
749
|
"transition_task",
|
|
581
750
|
"repair_managed_projection",
|
|
@@ -583,6 +752,13 @@ var KANBAN_INPUT_SCHEMA = {
|
|
|
583
752
|
"delete_task",
|
|
584
753
|
"set_chain",
|
|
585
754
|
"get_chain",
|
|
755
|
+
"get_contract_graph",
|
|
756
|
+
"configure_contract_graph",
|
|
757
|
+
"upsert_contract_node",
|
|
758
|
+
"link_contract_nodes",
|
|
759
|
+
"remove_contract_node",
|
|
760
|
+
"remove_contract_edge",
|
|
761
|
+
"evaluate_contract_graph",
|
|
586
762
|
"claim_task",
|
|
587
763
|
"release_task",
|
|
588
764
|
"assign_task",
|
|
@@ -608,6 +784,39 @@ var KANBAN_INPUT_SCHEMA = {
|
|
|
608
784
|
taskId: { type: "string" },
|
|
609
785
|
taskIds: { type: "array", items: { type: "string" } },
|
|
610
786
|
chainId: { type: "string" },
|
|
787
|
+
contractNodeId: { type: "string" },
|
|
788
|
+
contractNodeKind: {
|
|
789
|
+
type: "string",
|
|
790
|
+
enum: ["objective", "guardrail", "risk", "component", "artifact", "verification"]
|
|
791
|
+
},
|
|
792
|
+
contractNodeState: {
|
|
793
|
+
type: "string",
|
|
794
|
+
enum: ["unknown", "active", "satisfied", "violated", "resolved"]
|
|
795
|
+
},
|
|
796
|
+
contractEnforcement: {
|
|
797
|
+
type: "string",
|
|
798
|
+
enum: ["blocking", "advisory", "informational"]
|
|
799
|
+
},
|
|
800
|
+
contractGraphEnforcement: { type: "string", enum: ["off", "advisory", "strict"] },
|
|
801
|
+
contractEdgeId: { type: "string" },
|
|
802
|
+
contractEdgeType: {
|
|
803
|
+
type: "string",
|
|
804
|
+
enum: [
|
|
805
|
+
"targets",
|
|
806
|
+
"affects",
|
|
807
|
+
"must_preserve",
|
|
808
|
+
"exposes",
|
|
809
|
+
"verified_by",
|
|
810
|
+
"conflicts_with",
|
|
811
|
+
"derived_from",
|
|
812
|
+
"relates_to"
|
|
813
|
+
]
|
|
814
|
+
},
|
|
815
|
+
fromNodeId: { type: "string" },
|
|
816
|
+
toNodeId: { type: "string" },
|
|
817
|
+
contractRationale: { type: "string" },
|
|
818
|
+
baseline: { oneOf: [{ type: "string" }, { type: "number" }] },
|
|
819
|
+
threshold: { oneOf: [{ type: "string" }, { type: "number" }] },
|
|
611
820
|
columnId: { type: "string" },
|
|
612
821
|
targetBoardId: { type: "string" },
|
|
613
822
|
targetColumnId: { type: "string" },
|
|
@@ -695,6 +904,7 @@ var KANBAN_INPUT_SCHEMA = {
|
|
|
695
904
|
taskGraph: { type: "object" },
|
|
696
905
|
graphId: { type: "string" },
|
|
697
906
|
specId: { type: "string" },
|
|
907
|
+
specRequirementId: { type: "string" },
|
|
698
908
|
sourceSystem: { type: "string" },
|
|
699
909
|
phaseId: { type: "string" },
|
|
700
910
|
preserveOriginTaskIds: { type: "boolean" },
|
|
@@ -765,27 +975,6 @@ var KANBAN_INPUT_SCHEMA = {
|
|
|
765
975
|
required: ["action"]
|
|
766
976
|
};
|
|
767
977
|
|
|
768
|
-
// src/kanban-presence.ts
|
|
769
|
-
import { touchKanbanPresence } from "@wrongstack/kanban";
|
|
770
|
-
function createKanbanPresenceWrapper(projectRoot, input, ctx) {
|
|
771
|
-
return async (result) => {
|
|
772
|
-
const boardId = result.board?.id ?? input.boardId;
|
|
773
|
-
if (!result.ok || !boardId || !ctx.session?.id || !ctx.agentId) return result;
|
|
774
|
-
try {
|
|
775
|
-
const board = await touchKanbanPresence(projectRoot, boardId, {
|
|
776
|
-
sessionId: ctx.session.id,
|
|
777
|
-
agentId: ctx.agentId,
|
|
778
|
-
agentName: ctx.agentName,
|
|
779
|
-
taskId: input.taskId ?? result.task?.id,
|
|
780
|
-
runTaskId: input.runTaskId
|
|
781
|
-
});
|
|
782
|
-
return board ? { ...result, board } : result;
|
|
783
|
-
} catch {
|
|
784
|
-
return result;
|
|
785
|
-
}
|
|
786
|
-
};
|
|
787
|
-
}
|
|
788
|
-
|
|
789
978
|
// src/session-kanban.ts
|
|
790
979
|
import { getSharedProjectMailbox } from "@wrongstack/core/coordination";
|
|
791
980
|
import {
|
|
@@ -807,6 +996,7 @@ import {
|
|
|
807
996
|
updateBoard
|
|
808
997
|
} from "@wrongstack/kanban";
|
|
809
998
|
function taskFileToSerializedGraph(tasks, sessionId) {
|
|
999
|
+
const graphId = `session:${sessionId}`;
|
|
810
1000
|
const ids = new Set(tasks.map((task) => task.id));
|
|
811
1001
|
const nodes = tasks.map((task, index) => ({
|
|
812
1002
|
id: task.id,
|
|
@@ -815,6 +1005,7 @@ function taskFileToSerializedGraph(tasks, sessionId) {
|
|
|
815
1005
|
type: task.type,
|
|
816
1006
|
priority: task.priority,
|
|
817
1007
|
status: task.status,
|
|
1008
|
+
specRequirementId: `${graphId}:${task.id}`,
|
|
818
1009
|
...task.assignee ? { assignee: task.assignee } : {},
|
|
819
1010
|
...task.estimateHours !== void 0 ? { estimateHours: task.estimateHours } : {},
|
|
820
1011
|
createdAt: index,
|
|
@@ -832,8 +1023,9 @@ function taskFileToSerializedGraph(tasks, sessionId) {
|
|
|
832
1023
|
const rootNodes = nodes.filter((node) => !hasIncoming.has(node.id)).map((node) => node.id);
|
|
833
1024
|
return {
|
|
834
1025
|
// Keep the historical graph id so existing mirrored task cards are reused.
|
|
835
|
-
id:
|
|
836
|
-
specId:
|
|
1026
|
+
id: graphId,
|
|
1027
|
+
specId: graphId,
|
|
1028
|
+
requiredRequirementIds: nodes.map((node) => node.specRequirementId),
|
|
837
1029
|
title: "Session tasks",
|
|
838
1030
|
nodes,
|
|
839
1031
|
edges,
|
|
@@ -906,7 +1098,11 @@ var kanbanTool = {
|
|
|
906
1098
|
}
|
|
907
1099
|
case "duplicate_board": {
|
|
908
1100
|
if (!input.boardId) return fail("duplicate_board requires boardId.");
|
|
909
|
-
const board = await duplicateBoard(
|
|
1101
|
+
const board = await duplicateBoard(
|
|
1102
|
+
projectRoot,
|
|
1103
|
+
input.boardId,
|
|
1104
|
+
duplicateBoardOptions(input)
|
|
1105
|
+
);
|
|
910
1106
|
return board ? okBoard(board, "Board duplicated.") : fail("Board not found.");
|
|
911
1107
|
}
|
|
912
1108
|
case "delete_board": {
|
|
@@ -1074,7 +1270,8 @@ var kanbanTool = {
|
|
|
1074
1270
|
};
|
|
1075
1271
|
}
|
|
1076
1272
|
case "add_column": {
|
|
1077
|
-
if (!input.boardId || !input.title)
|
|
1273
|
+
if (!input.boardId || !input.title)
|
|
1274
|
+
return fail("add_column requires boardId and title.");
|
|
1078
1275
|
const result2 = await addColumn(projectRoot, input.boardId, {
|
|
1079
1276
|
title: input.title,
|
|
1080
1277
|
...input.description !== void 0 ? { description: input.description } : {}
|
|
@@ -1103,11 +1300,7 @@ var kanbanTool = {
|
|
|
1103
1300
|
if (!input.boardId || !input.title) return fail("add_task requires boardId and title.");
|
|
1104
1301
|
const result2 = await addTask(projectRoot, input.boardId, taskInput(input));
|
|
1105
1302
|
if (!result2) return fail("Board not found.");
|
|
1106
|
-
return okTask(
|
|
1107
|
-
result2.board,
|
|
1108
|
-
result2.task,
|
|
1109
|
-
`Task added.${atomicityNudge(result2.task)}`
|
|
1110
|
-
);
|
|
1303
|
+
return okTask(result2.board, result2.task, `Task added.${atomicityNudge(result2.task)}`);
|
|
1111
1304
|
}
|
|
1112
1305
|
case "split_task": {
|
|
1113
1306
|
if (!input.boardId || !input.taskId || !input.childTitles?.length) {
|
|
@@ -1166,10 +1359,72 @@ var kanbanTool = {
|
|
|
1166
1359
|
return result2 ? okTask(result2.targetBoard, result2.task, "Task transferred to target board.") : fail("Board or task not found.");
|
|
1167
1360
|
}
|
|
1168
1361
|
case "get_task": {
|
|
1169
|
-
if (!input.boardId || !input.taskId)
|
|
1362
|
+
if (!input.boardId || !input.taskId)
|
|
1363
|
+
return fail("get_task requires boardId and taskId.");
|
|
1170
1364
|
const task = await getTask(projectRoot, input.boardId, input.taskId);
|
|
1171
1365
|
return task ? { ok: true, message: "Task loaded.", task } : fail("Task not found.");
|
|
1172
1366
|
}
|
|
1367
|
+
case "start_task": {
|
|
1368
|
+
if (!input.boardId || !input.taskId || !input.author || !input.transitionComment) {
|
|
1369
|
+
return fail("start_task requires boardId, taskId, author, and transitionComment.");
|
|
1370
|
+
}
|
|
1371
|
+
let board = await getBoard3(projectRoot, input.boardId);
|
|
1372
|
+
let task = board?.tasks.find((candidate) => candidate.id === input.taskId);
|
|
1373
|
+
if (!board || !task) return fail("Board or task not found.");
|
|
1374
|
+
const readiness = evaluateContractGraphReadiness(board, task.id);
|
|
1375
|
+
if (!readiness.ready) {
|
|
1376
|
+
return fail(
|
|
1377
|
+
`Task is not implementation-ready: ${readiness.issues.map((issue) => issue.message).join(" | ")}`
|
|
1378
|
+
);
|
|
1379
|
+
}
|
|
1380
|
+
let stage = task.lifecycle?.currentStage;
|
|
1381
|
+
if (stage === "backlog") {
|
|
1382
|
+
const moved = await transitionTask(projectRoot, board.id, task.id, {
|
|
1383
|
+
to: "todo",
|
|
1384
|
+
actor: input.author,
|
|
1385
|
+
comment: input.transitionComment
|
|
1386
|
+
});
|
|
1387
|
+
if (!moved) return fail("Task could not enter Todo.");
|
|
1388
|
+
board = moved.board;
|
|
1389
|
+
task = moved.task;
|
|
1390
|
+
stage = task.lifecycle?.currentStage;
|
|
1391
|
+
}
|
|
1392
|
+
if (stage === "todo" || stage === "review") {
|
|
1393
|
+
const now = /* @__PURE__ */ new Date();
|
|
1394
|
+
const leaseId = input.leaseId ?? randomUUID2();
|
|
1395
|
+
const assigned = await updateTaskAssignment(projectRoot, board.id, task.id, {
|
|
1396
|
+
status: "running",
|
|
1397
|
+
agentId: input.agentId ?? input.author,
|
|
1398
|
+
leaseId,
|
|
1399
|
+
claimedAt: input.claimedAt ?? now.toISOString(),
|
|
1400
|
+
heartbeatAt: input.heartbeatAt ?? now.toISOString(),
|
|
1401
|
+
leaseExpiresAt: input.leaseExpiresAt ?? new Date(now.getTime() + 15 * 6e4).toISOString(),
|
|
1402
|
+
attempt: input.attempt ?? 1,
|
|
1403
|
+
maxAttempts: input.maxAttempts ?? 3
|
|
1404
|
+
});
|
|
1405
|
+
if (!assigned) return fail("Task assignment could not be started.");
|
|
1406
|
+
const moved = await transitionTask(projectRoot, board.id, task.id, {
|
|
1407
|
+
to: "running",
|
|
1408
|
+
actor: input.author,
|
|
1409
|
+
comment: input.transitionComment
|
|
1410
|
+
});
|
|
1411
|
+
if (!moved) return fail("Task could not enter Running.");
|
|
1412
|
+
board = moved.board;
|
|
1413
|
+
task = moved.task;
|
|
1414
|
+
stage = task.lifecycle?.currentStage;
|
|
1415
|
+
}
|
|
1416
|
+
if (stage !== "running" || task.assignment?.status !== "running") {
|
|
1417
|
+
return fail(
|
|
1418
|
+
`start_task only accepts Backlog, Todo, Review repair, or live Running cards (current: ${stage ?? "unknown"}).`
|
|
1419
|
+
);
|
|
1420
|
+
}
|
|
1421
|
+
ctx.setCurrentKanbanTask(task.id, board.id);
|
|
1422
|
+
return okTask(
|
|
1423
|
+
board,
|
|
1424
|
+
task,
|
|
1425
|
+
"Task is active; runtime Kanban governance is now bound to this run."
|
|
1426
|
+
);
|
|
1427
|
+
}
|
|
1173
1428
|
case "update_task": {
|
|
1174
1429
|
if (!input.boardId || !input.taskId)
|
|
1175
1430
|
return fail("update_task requires boardId and taskId.");
|
|
@@ -1191,9 +1446,14 @@ var kanbanTool = {
|
|
|
1191
1446
|
const boardBefore = await getBoard3(projectRoot, input.boardId);
|
|
1192
1447
|
const taskBefore = boardBefore ? await getTask(projectRoot, input.boardId, input.taskId) : null;
|
|
1193
1448
|
if (boardBefore && taskBefore && !taskBefore.verificationReport && (taskBefore.atomic || Boolean(taskBefore.successCriteria?.length))) {
|
|
1194
|
-
const preGate = await verifyTaskCompletion2(
|
|
1195
|
-
|
|
1196
|
-
|
|
1449
|
+
const preGate = await verifyTaskCompletion2(
|
|
1450
|
+
projectRoot,
|
|
1451
|
+
input.boardId,
|
|
1452
|
+
taskBefore.id,
|
|
1453
|
+
{
|
|
1454
|
+
persist: false
|
|
1455
|
+
}
|
|
1456
|
+
);
|
|
1197
1457
|
await updateTask2(projectRoot, input.boardId, taskBefore.id, {
|
|
1198
1458
|
verificationReport: preGate.report,
|
|
1199
1459
|
successCriteria: preGate.task.successCriteria
|
|
@@ -1225,11 +1485,20 @@ var kanbanTool = {
|
|
|
1225
1485
|
"repair_managed_projection requires boardId, taskId, author, and transitionComment."
|
|
1226
1486
|
);
|
|
1227
1487
|
}
|
|
1228
|
-
const result2 = await repairManagedTaskProjection(
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1488
|
+
const result2 = await repairManagedTaskProjection(
|
|
1489
|
+
projectRoot,
|
|
1490
|
+
input.boardId,
|
|
1491
|
+
input.taskId,
|
|
1492
|
+
{
|
|
1493
|
+
actor: input.author,
|
|
1494
|
+
comment: input.transitionComment
|
|
1495
|
+
}
|
|
1496
|
+
);
|
|
1497
|
+
return result2 ? okTask(
|
|
1498
|
+
result2.board,
|
|
1499
|
+
result2.task,
|
|
1500
|
+
"Managed card projection repaired from lifecycle history."
|
|
1501
|
+
) : fail("Board or task not found.");
|
|
1233
1502
|
}
|
|
1234
1503
|
case "move_task": {
|
|
1235
1504
|
if (!input.boardId || !input.taskId || !input.targetColumnId) {
|
|
@@ -1366,21 +1635,18 @@ var kanbanTool = {
|
|
|
1366
1635
|
};
|
|
1367
1636
|
}
|
|
1368
1637
|
} else if (board.lifecycle?.mode === "managed") {
|
|
1369
|
-
const managedTask = board.tasks.find(
|
|
1370
|
-
(candidate) => candidate.id === input.taskId
|
|
1371
|
-
);
|
|
1638
|
+
const managedTask = board.tasks.find((candidate) => candidate.id === input.taskId);
|
|
1372
1639
|
const stage = managedTask?.lifecycle?.currentStage;
|
|
1373
1640
|
const actor = ctx.agentId ?? "kanban-agent";
|
|
1374
1641
|
let transitionResult = null;
|
|
1375
1642
|
const lifecycleWarnings = [];
|
|
1376
1643
|
if (assignmentStatus === "running" && stage === "todo") {
|
|
1377
1644
|
try {
|
|
1378
|
-
transitionResult = await transitionTask(
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
);
|
|
1645
|
+
transitionResult = await transitionTask(projectRoot, board.id, input.taskId, {
|
|
1646
|
+
to: "running",
|
|
1647
|
+
actor,
|
|
1648
|
+
comment: "Work started."
|
|
1649
|
+
});
|
|
1384
1650
|
} catch (err) {
|
|
1385
1651
|
lifecycleWarnings.push(
|
|
1386
1652
|
`Lifecycle transition to Running deferred: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -1390,26 +1656,21 @@ var kanbanTool = {
|
|
|
1390
1656
|
if (assignmentStatus === "completed" && stage === "running") {
|
|
1391
1657
|
const comment = typeof input.lastResult === "string" && input.lastResult.trim().length > 0 ? input.lastResult.trim().slice(0, 1e3) : "Work completed.";
|
|
1392
1658
|
try {
|
|
1393
|
-
transitionResult = await transitionTask(
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
{
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
}
|
|
1406
|
-
patch: {
|
|
1407
|
-
// Only patch non-description fields so the
|
|
1408
|
-
// original card description is preserved.
|
|
1409
|
-
...input.agentId !== void 0 ? { assignedAgent: input.agentId } : {}
|
|
1410
|
-
}
|
|
1659
|
+
transitionResult = await transitionTask(projectRoot, board.id, input.taskId, {
|
|
1660
|
+
to: "review",
|
|
1661
|
+
actor,
|
|
1662
|
+
comment,
|
|
1663
|
+
attachment: {
|
|
1664
|
+
url: `kanban://task/${input.taskId}/result`,
|
|
1665
|
+
title: "Worker completion result",
|
|
1666
|
+
type: "file"
|
|
1667
|
+
},
|
|
1668
|
+
patch: {
|
|
1669
|
+
// Only patch non-description fields so the
|
|
1670
|
+
// original card description is preserved.
|
|
1671
|
+
...input.agentId !== void 0 ? { assignedAgent: input.agentId } : {}
|
|
1411
1672
|
}
|
|
1412
|
-
);
|
|
1673
|
+
});
|
|
1413
1674
|
} catch (err) {
|
|
1414
1675
|
lifecycleWarnings.push(
|
|
1415
1676
|
`Lifecycle transition to Review failed: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -1427,6 +1688,10 @@ var kanbanTool = {
|
|
|
1427
1688
|
if (verResult.report) {
|
|
1428
1689
|
recordKanbanVerificationEvidence(ctx, verResult.report);
|
|
1429
1690
|
}
|
|
1691
|
+
await updateTask2(projectRoot, board.id, input.taskId, {
|
|
1692
|
+
verificationReport: verResult.report,
|
|
1693
|
+
successCriteria: verResult.task.successCriteria
|
|
1694
|
+
});
|
|
1430
1695
|
const verdict = verResult.report.verdict;
|
|
1431
1696
|
if (verdict === "passed") {
|
|
1432
1697
|
try {
|
|
@@ -1437,6 +1702,7 @@ var kanbanTool = {
|
|
|
1437
1702
|
{
|
|
1438
1703
|
to: "done",
|
|
1439
1704
|
actor,
|
|
1705
|
+
action: "Automated acceptance after verification",
|
|
1440
1706
|
comment: "Auto-accepted: verification passed.",
|
|
1441
1707
|
attachment: {
|
|
1442
1708
|
url: `kanban://task/${input.taskId}/verification`,
|