@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.
Files changed (40) hide show
  1. package/dist/builtin.js +2799 -609
  2. package/dist/codebase-index/binary-frame.d.ts +43 -0
  3. package/dist/codebase-index/codebase-incoming-calls-tool.d.ts +1 -0
  4. package/dist/codebase-index/codebase-outgoing-calls-tool.d.ts +1 -0
  5. package/dist/codebase-index/content-hash.d.ts +66 -0
  6. package/dist/codebase-index/index.js +1569 -148
  7. package/dist/codebase-index/parser-worker-pool.d.ts +63 -0
  8. package/dist/codebase-index/parser-worker-script.d.ts +42 -0
  9. package/dist/codebase-index/project-server-protocol.d.ts +2 -0
  10. package/dist/codebase-index/project-server.js +1455 -99
  11. package/dist/codebase-index/schema.d.ts +7 -0
  12. package/dist/codebase-index/tree-sitter/queries.d.ts +48 -0
  13. package/dist/codebase-index/tree-sitter/util.d.ts +31 -0
  14. package/dist/codebase-index/tree-sitter/visitor.d.ts +47 -0
  15. package/dist/codebase-index/tree-sitter-parser.d.ts +58 -0
  16. package/dist/codebase-index/vector-search.d.ts +62 -0
  17. package/dist/codebase-index/worker-protocol.d.ts +2 -0
  18. package/dist/codebase-index/worker.js +1424 -68
  19. package/dist/codebase-index/writer-bulk-insert.d.ts +5 -0
  20. package/dist/codebase-index/writer-graph-reader.d.ts +39 -0
  21. package/dist/codebase-index/writer-schema.d.ts +9 -2
  22. package/dist/codebase-index/writer.d.ts +36 -0
  23. package/dist/index.d.ts +1 -0
  24. package/dist/index.js +2785 -634
  25. package/dist/kanban-task-inputs.d.ts +1 -0
  26. package/dist/kanban-tool-schema.d.ts +2 -2
  27. package/dist/kanban-tool-types.d.ts +18 -2
  28. package/dist/kanban.js +392 -126
  29. package/dist/pack.js +2799 -609
  30. package/dist/plan.d.ts +4 -1
  31. package/dist/plan.js +2380 -9
  32. package/dist/read.js +1531 -98
  33. package/dist/session-kanban.d.ts +8 -0
  34. package/dist/session-kanban.js +111 -17
  35. package/dist/task.d.ts +5 -4
  36. package/dist/task.js +2418 -43
  37. package/dist/todo.d.ts +10 -1
  38. package/dist/todo.js +2152 -20
  39. package/dist/tool-tier.js +2799 -609
  40. package/package.json +8 -4
package/dist/kanban.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // src/kanban.ts
2
- import { deserializeTaskGraph as deserializeTaskGraph2, serializeTaskGraph } from "@wrongstack/core/tasking";
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
- createBoardFromText,
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
- finalizeTaskCompletion
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-board-inputs.ts
348
- function agentSettableGate(enforcement) {
349
- if (enforcement === void 0 || enforcement === "off") return {};
350
- return { completionGate: { enforcement } };
351
- }
352
- function boardCreateInput(input, title) {
353
- return {
354
- title,
355
- ...input.description !== void 0 ? { description: input.description } : {},
356
- ...input.tags !== void 0 ? { tags: input.tags } : {},
357
- ...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
358
- ...input.atomicityMode !== void 0 ? {
359
- atomicity: {
360
- mode: input.atomicityMode,
361
- decomposition: input.atomicityDecomposition ?? "propose"
362
- }
363
- } : {},
364
- ...agentSettableGate(input.gateEnforcement)
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. 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.";
547
- 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.";
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: `session:${sessionId}`,
836
- specId: `session:${sessionId}`,
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(projectRoot, input.boardId, duplicateBoardOptions(input));
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) return fail("add_column requires boardId and 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) return fail("get_task requires boardId and 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(projectRoot, input.boardId, taskBefore.id, {
1195
- persist: false
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(projectRoot, input.boardId, input.taskId, {
1229
- actor: input.author,
1230
- comment: input.transitionComment
1231
- });
1232
- return result2 ? okTask(result2.board, result2.task, "Managed card projection repaired from lifecycle history.") : fail("Board or task not found.");
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
- projectRoot,
1380
- board.id,
1381
- input.taskId,
1382
- { to: "running", actor, comment: "Work started." }
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
- projectRoot,
1395
- board.id,
1396
- input.taskId,
1397
- {
1398
- to: "review",
1399
- actor,
1400
- comment,
1401
- attachment: {
1402
- url: `kanban://task/${input.taskId}/result`,
1403
- title: "Worker completion result",
1404
- type: "file"
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`,