@wrongstack/tools 0.293.0 → 0.295.1

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 (258) hide show
  1. package/dist/_env.d.ts +1 -1
  2. package/dist/_env.d.ts.map +1 -1
  3. package/dist/_fetch-guard.d.ts.map +1 -1
  4. package/dist/_spawn-stream.d.ts +1 -1
  5. package/dist/_spawn-stream.d.ts.map +1 -1
  6. package/dist/_util.d.ts +1 -1
  7. package/dist/_util.d.ts.map +1 -1
  8. package/dist/audit.d.ts +1 -1
  9. package/dist/audit.d.ts.map +1 -1
  10. package/dist/audit.js +81 -16
  11. package/dist/audit.js.map +2 -2
  12. package/dist/auto-proceed-loop-guard.d.ts +31 -0
  13. package/dist/auto-proceed-loop-guard.d.ts.map +1 -1
  14. package/dist/auto-proceed-loop-guard.js +24 -1
  15. package/dist/auto-proceed-loop-guard.js.map +2 -2
  16. package/dist/bash.d.ts +1 -1
  17. package/dist/bash.d.ts.map +1 -1
  18. package/dist/bash.js +105 -75
  19. package/dist/bash.js.map +3 -3
  20. package/dist/batch-tool-use.d.ts +1 -1
  21. package/dist/batch-tool-use.d.ts.map +1 -1
  22. package/dist/batch-tool-use.js +1 -1
  23. package/dist/batch-tool-use.js.map +1 -1
  24. package/dist/browser/index.js +10 -1
  25. package/dist/browser/index.js.map +2 -2
  26. package/dist/browser/manager.d.ts +2 -0
  27. package/dist/browser/manager.d.ts.map +1 -1
  28. package/dist/browser/tools.d.ts +1 -1
  29. package/dist/browser/tools.d.ts.map +1 -1
  30. package/dist/builtin.d.ts +1 -1
  31. package/dist/builtin.d.ts.map +1 -1
  32. package/dist/builtin.js +1650 -640
  33. package/dist/builtin.js.map +4 -4
  34. package/dist/codebase-index/background-indexer.d.ts +2 -2
  35. package/dist/codebase-index/background-indexer.d.ts.map +1 -1
  36. package/dist/codebase-index/bm25.d.ts +3 -1
  37. package/dist/codebase-index/bm25.d.ts.map +1 -1
  38. package/dist/codebase-index/codebase-index-tool.d.ts +1 -1
  39. package/dist/codebase-index/codebase-index-tool.d.ts.map +1 -1
  40. package/dist/codebase-index/codebase-search-tool.d.ts +1 -1
  41. package/dist/codebase-index/codebase-search-tool.d.ts.map +1 -1
  42. package/dist/codebase-index/codebase-stats-tool.d.ts +1 -1
  43. package/dist/codebase-index/codebase-stats-tool.d.ts.map +1 -1
  44. package/dist/codebase-index/generic-parser.d.ts +34 -0
  45. package/dist/codebase-index/generic-parser.d.ts.map +1 -0
  46. package/dist/codebase-index/go-parser.d.ts +1 -1
  47. package/dist/codebase-index/go-parser.d.ts.map +1 -1
  48. package/dist/codebase-index/index.d.ts +4 -2
  49. package/dist/codebase-index/index.d.ts.map +1 -1
  50. package/dist/codebase-index/index.js +1068 -372
  51. package/dist/codebase-index/index.js.map +4 -4
  52. package/dist/codebase-index/indexer.d.ts +6 -1
  53. package/dist/codebase-index/indexer.d.ts.map +1 -1
  54. package/dist/codebase-index/json-parser.d.ts +1 -1
  55. package/dist/codebase-index/languages.d.ts +25 -0
  56. package/dist/codebase-index/languages.d.ts.map +1 -0
  57. package/dist/codebase-index/py-parser.d.ts +9 -1
  58. package/dist/codebase-index/py-parser.d.ts.map +1 -1
  59. package/dist/codebase-index/refs-extractor.d.ts.map +1 -1
  60. package/dist/codebase-index/rs-parser.d.ts +1 -1
  61. package/dist/codebase-index/rs-parser.d.ts.map +1 -1
  62. package/dist/codebase-index/schema.d.ts +9 -2
  63. package/dist/codebase-index/schema.d.ts.map +1 -1
  64. package/dist/codebase-index/spawn-gate.d.ts +15 -0
  65. package/dist/codebase-index/spawn-gate.d.ts.map +1 -0
  66. package/dist/codebase-index/ts-parser.d.ts +2 -2
  67. package/dist/codebase-index/ts-parser.d.ts.map +1 -1
  68. package/dist/codebase-index/worker.js +993 -348
  69. package/dist/codebase-index/worker.js.map +4 -4
  70. package/dist/codebase-index/writer.d.ts +83 -4
  71. package/dist/codebase-index/writer.d.ts.map +1 -1
  72. package/dist/codebase-index/yaml-parser.d.ts +1 -1
  73. package/dist/design.d.ts +5 -2
  74. package/dist/design.d.ts.map +1 -1
  75. package/dist/design.js +36 -3
  76. package/dist/design.js.map +2 -2
  77. package/dist/diff.d.ts +1 -1
  78. package/dist/diff.d.ts.map +1 -1
  79. package/dist/diff.js +2 -2
  80. package/dist/diff.js.map +1 -1
  81. package/dist/document.d.ts +1 -1
  82. package/dist/document.d.ts.map +1 -1
  83. package/dist/document.js +1 -1
  84. package/dist/document.js.map +1 -1
  85. package/dist/e2e.d.ts +1 -1
  86. package/dist/e2e.d.ts.map +1 -1
  87. package/dist/e2e.js +1 -1
  88. package/dist/e2e.js.map +1 -1
  89. package/dist/edit.d.ts +1 -1
  90. package/dist/edit.d.ts.map +1 -1
  91. package/dist/edit.js +3 -3
  92. package/dist/edit.js.map +2 -2
  93. package/dist/exec.d.ts +1 -1
  94. package/dist/exec.d.ts.map +1 -1
  95. package/dist/exec.js +99 -44
  96. package/dist/exec.js.map +3 -3
  97. package/dist/fetch.d.ts +1 -1
  98. package/dist/fetch.d.ts.map +1 -1
  99. package/dist/fetch.js +4 -5
  100. package/dist/fetch.js.map +2 -2
  101. package/dist/format.d.ts +1 -1
  102. package/dist/format.d.ts.map +1 -1
  103. package/dist/format.js +81 -16
  104. package/dist/format.js.map +2 -2
  105. package/dist/git.d.ts +1 -1
  106. package/dist/git.d.ts.map +1 -1
  107. package/dist/git.js +3 -2
  108. package/dist/git.js.map +2 -2
  109. package/dist/glob.d.ts +1 -1
  110. package/dist/glob.d.ts.map +1 -1
  111. package/dist/glob.js +81 -31
  112. package/dist/glob.js.map +4 -4
  113. package/dist/grep.d.ts +1 -1
  114. package/dist/grep.d.ts.map +1 -1
  115. package/dist/grep.js +90 -22
  116. package/dist/grep.js.map +4 -4
  117. package/dist/index.d.ts +5 -4
  118. package/dist/index.d.ts.map +1 -1
  119. package/dist/index.js +1895 -771
  120. package/dist/index.js.map +4 -4
  121. package/dist/install.d.ts +1 -1
  122. package/dist/install.d.ts.map +1 -1
  123. package/dist/install.js +85 -17
  124. package/dist/install.js.map +2 -2
  125. package/dist/json.d.ts +1 -1
  126. package/dist/json.d.ts.map +1 -1
  127. package/dist/json.js +2 -2
  128. package/dist/json.js.map +2 -2
  129. package/dist/kanban-evidence-bridge.d.ts +21 -0
  130. package/dist/kanban-evidence-bridge.d.ts.map +1 -0
  131. package/dist/kanban.d.ts +20 -3
  132. package/dist/kanban.d.ts.map +1 -1
  133. package/dist/kanban.js +256 -32
  134. package/dist/kanban.js.map +3 -3
  135. package/dist/languages/execute-tool.d.ts +1 -1
  136. package/dist/languages/execute-tool.d.ts.map +1 -1
  137. package/dist/languages/execute.d.ts +1 -1
  138. package/dist/languages/execute.d.ts.map +1 -1
  139. package/dist/languages/index.d.ts +1 -1
  140. package/dist/languages/index.d.ts.map +1 -1
  141. package/dist/languages/index.js +99 -17
  142. package/dist/languages/index.js.map +2 -2
  143. package/dist/languages/package-tool.d.ts +3 -4
  144. package/dist/languages/package-tool.d.ts.map +1 -1
  145. package/dist/languages/plan.d.ts.map +1 -1
  146. package/dist/languages/profiles/additional.d.ts.map +1 -1
  147. package/dist/languages/profiles/primary.d.ts.map +1 -1
  148. package/dist/languages/tool.d.ts +1 -1
  149. package/dist/languages/tool.d.ts.map +1 -1
  150. package/dist/lint.d.ts +1 -1
  151. package/dist/lint.d.ts.map +1 -1
  152. package/dist/lint.js +81 -16
  153. package/dist/lint.js.map +2 -2
  154. package/dist/logs.d.ts +1 -1
  155. package/dist/logs.d.ts.map +1 -1
  156. package/dist/logs.js +2 -2
  157. package/dist/logs.js.map +1 -1
  158. package/dist/memory.d.ts +1 -1
  159. package/dist/memory.d.ts.map +1 -1
  160. package/dist/memory.js +1 -1
  161. package/dist/memory.js.map +1 -1
  162. package/dist/mode.d.ts +1 -1
  163. package/dist/mode.d.ts.map +1 -1
  164. package/dist/mode.js.map +1 -1
  165. package/dist/outdated.d.ts +1 -1
  166. package/dist/outdated.d.ts.map +1 -1
  167. package/dist/outdated.js +101 -19
  168. package/dist/outdated.js.map +2 -2
  169. package/dist/pack.d.ts +1 -1
  170. package/dist/pack.js +1650 -640
  171. package/dist/pack.js.map +4 -4
  172. package/dist/patch.d.ts +1 -1
  173. package/dist/patch.d.ts.map +1 -1
  174. package/dist/patch.js +2 -2
  175. package/dist/patch.js.map +1 -1
  176. package/dist/plan.d.ts +1 -1
  177. package/dist/plan.d.ts.map +1 -1
  178. package/dist/plan.js +7 -7
  179. package/dist/plan.js.map +2 -2
  180. package/dist/process-guardian.d.ts +7 -0
  181. package/dist/process-guardian.d.ts.map +1 -1
  182. package/dist/process-registry-persistent.d.ts +5 -0
  183. package/dist/process-registry-persistent.d.ts.map +1 -1
  184. package/dist/process-registry.d.ts +9 -1
  185. package/dist/process-registry.d.ts.map +1 -1
  186. package/dist/process-registry.js +18 -3
  187. package/dist/process-registry.js.map +2 -2
  188. package/dist/ps-slash.js +93 -38
  189. package/dist/ps-slash.js.map +2 -2
  190. package/dist/read.d.ts +1 -1
  191. package/dist/read.d.ts.map +1 -1
  192. package/dist/read.js +3 -2
  193. package/dist/read.js.map +2 -2
  194. package/dist/replace.d.ts +1 -1
  195. package/dist/replace.d.ts.map +1 -1
  196. package/dist/replace.js +4 -4
  197. package/dist/replace.js.map +2 -2
  198. package/dist/scaffold.d.ts +1 -1
  199. package/dist/scaffold.d.ts.map +1 -1
  200. package/dist/scaffold.js +2 -2
  201. package/dist/scaffold.js.map +1 -1
  202. package/dist/search.d.ts +1 -1
  203. package/dist/search.d.ts.map +1 -1
  204. package/dist/search.js +4 -4
  205. package/dist/search.js.map +2 -2
  206. package/dist/session-kanban.d.ts +4 -1
  207. package/dist/session-kanban.d.ts.map +1 -1
  208. package/dist/session-kanban.js +23 -15
  209. package/dist/session-kanban.js.map +2 -2
  210. package/dist/set-working-dir.d.ts +1 -1
  211. package/dist/set-working-dir.d.ts.map +1 -1
  212. package/dist/skill.d.ts +1 -1
  213. package/dist/skill.d.ts.map +1 -1
  214. package/dist/skill.js +2 -5
  215. package/dist/skill.js.map +2 -2
  216. package/dist/spawn-background.d.ts.map +1 -1
  217. package/dist/task.d.ts +3 -2
  218. package/dist/task.d.ts.map +1 -1
  219. package/dist/task.js +6 -8
  220. package/dist/task.js.map +2 -2
  221. package/dist/test.d.ts +1 -1
  222. package/dist/test.d.ts.map +1 -1
  223. package/dist/test.js +81 -16
  224. package/dist/test.js.map +2 -2
  225. package/dist/todo.d.ts +2 -1
  226. package/dist/todo.d.ts.map +1 -1
  227. package/dist/todo.js +4 -4
  228. package/dist/todo.js.map +2 -2
  229. package/dist/tool-help.d.ts +1 -1
  230. package/dist/tool-help.d.ts.map +1 -1
  231. package/dist/tool-help.js.map +1 -1
  232. package/dist/tool-search.d.ts +1 -1
  233. package/dist/tool-search.d.ts.map +1 -1
  234. package/dist/tool-search.js.map +1 -1
  235. package/dist/tool-tier.d.ts +16 -0
  236. package/dist/tool-tier.d.ts.map +1 -0
  237. package/dist/tool-tier.js +22402 -0
  238. package/dist/tool-tier.js.map +7 -0
  239. package/dist/tool-use.d.ts +1 -1
  240. package/dist/tool-use.d.ts.map +1 -1
  241. package/dist/tool-use.js.map +1 -1
  242. package/dist/tree.d.ts +1 -1
  243. package/dist/tree.d.ts.map +1 -1
  244. package/dist/tree.js +3 -15
  245. package/dist/tree.js.map +2 -2
  246. package/dist/typecheck.d.ts +1 -1
  247. package/dist/typecheck.d.ts.map +1 -1
  248. package/dist/typecheck.js +81 -16
  249. package/dist/typecheck.js.map +2 -2
  250. package/dist/win32.d.ts +10 -0
  251. package/dist/win32.d.ts.map +1 -0
  252. package/dist/win32.js +67 -0
  253. package/dist/win32.js.map +7 -0
  254. package/dist/write.d.ts +1 -1
  255. package/dist/write.d.ts.map +1 -1
  256. package/dist/write.js +3 -2
  257. package/dist/write.js.map +2 -2
  258. package/package.json +11 -3
package/dist/kanban.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // src/kanban.ts
2
2
  import { randomUUID } from "node:crypto";
3
- import { deserializeTaskGraph as deserializeTaskGraph2, loadTasks as loadTasks2, serializeTaskGraph } from "@wrongstack/core";
3
+ import { deserializeTaskGraph as deserializeTaskGraph2, serializeTaskGraph } from "@wrongstack/core/tasking";
4
+ import { loadTasks as loadTasks2 } from "@wrongstack/core/storage";
4
5
  import {
5
6
  addCheckToTask,
6
7
  addColumn,
@@ -47,18 +48,45 @@ import {
47
48
  updateColumn,
48
49
  updateGoalMetricOnTask,
49
50
  updateTask,
50
- updateTaskAssignment
51
+ updateTaskAssignment,
52
+ verifyTaskCompletion,
53
+ finalizeTaskCompletion,
54
+ assessTaskAtomicity,
55
+ proposeTaskDecomposition
51
56
  } from "@wrongstack/kanban";
52
57
 
58
+ // src/kanban-evidence-bridge.ts
59
+ import { recordCompletedWorkEvidence } from "@wrongstack/core/utils";
60
+ function kanbanEvidenceKey(boardId, taskId) {
61
+ return `kanban:${boardId}:${taskId}`;
62
+ }
63
+ function kanbanEvidencePointer(boardId, taskId) {
64
+ return `kanban://${boardId}/${taskId}#verificationReport`;
65
+ }
66
+ function recordKanbanVerificationEvidence(ctx, report) {
67
+ try {
68
+ const passed = report.checks.filter((check) => check.status === "passed").length;
69
+ const completedAt = Date.parse(report.completedAt);
70
+ recordCompletedWorkEvidence(ctx, {
71
+ key: kanbanEvidenceKey(report.boardId, report.taskId),
72
+ source: "verification",
73
+ summary: `${report.taskTitle} \u2014 verification ${report.verdict} (${passed}/${report.checks.length} checks)`,
74
+ ...Number.isFinite(completedAt) ? { completedAt } : {},
75
+ evidence: kanbanEvidencePointer(report.boardId, report.taskId)
76
+ });
77
+ } catch {
78
+ }
79
+ }
80
+
53
81
  // src/session-kanban.ts
82
+ import { GlobalMailbox } from "@wrongstack/core/coordination";
54
83
  import {
55
- deserializeTaskGraph,
56
- GlobalMailbox,
57
84
  loadPlan,
58
85
  loadTasks,
59
86
  mutatePlan,
60
87
  mutateTasks
61
- } from "@wrongstack/core";
88
+ } from "@wrongstack/core/storage";
89
+ import { deserializeTaskGraph } from "@wrongstack/core/tasking";
62
90
  import { resolveWstackPaths } from "@wrongstack/core/utils";
63
91
  import {
64
92
  createBoard,
@@ -111,8 +139,8 @@ function taskFileToSerializedGraph(tasks, sessionId) {
111
139
  var kanbanTool = {
112
140
  name: "kanban",
113
141
  category: "Project",
114
- description: "Manage and audit project-scoped multi-kanban boards stored under .wrongstack/kanbans. Managed cards enforce fully specified details and adjacent Backlog \u2192 Todo \u2192 Running \u2192 Review \u2192 Done transitions with persistent comments and evidence. Successful board access records live agent/session presence.",
115
- usageHint: "Use this for durable project kanban state. Reassess with get_board whenever evidence changes; agents may add, update, split, merge, reprioritize, or remove tasks so the board remains a live plan. Presence includes active/last-seen session and agent metadata. For managed boards, fully fill card details, use transition_task after every material step, attach truthful evidence, and never use update_task/move_task to bypass lifecycle guards. Worker completion enters Review; only passed acceptance criteria plus review evidence allow Done.",
142
+ description: "Manage and audit project-scoped multi-kanban boards stored under .wrongstack/kanbans. Managed cards enforce fully specified details and adjacent Backlog \u2192 Todo \u2192 Running \u2192 Review \u2192 Done transitions with persistent comments and evidence. Successful board access records live agent/session presence. Use verify_completion to validate a task against its success criteria and persist the verification report. Use split_atomic to split a task into child tasks with parent.atomic=true in a single atomic board mutation, enforcing subtree verification before the parent can complete.",
143
+ usageHint: "Use this for durable project kanban state. Reassess with get_board whenever evidence changes; agents may add, update, split, merge, reprioritize, or remove tasks so the board remains a live plan. Presence includes active/last-seen session and agent metadata. For managed boards, fully fill card details, use transition_task after every material step, attach truthful evidence, and never use update_task/move_task to bypass lifecycle guards. Worker completion enters Review; only passed acceptance criteria plus review evidence allow Done. Use verify_completion to generate a verification report (persisted automatically) and split_atomic to atomically create child subtasks with the atomic flag pre-set.",
116
144
  permission: "confirm",
117
145
  mutating: true,
118
146
  capabilities: ["fs.write"],
@@ -168,7 +196,11 @@ var kanbanTool = {
168
196
  "add_check",
169
197
  "update_check",
170
198
  "add_note",
171
- "add_link"
199
+ "add_link",
200
+ "verify_completion",
201
+ "split_atomic",
202
+ "assess_atomicity",
203
+ "propose_decomposition"
172
204
  ]
173
205
  },
174
206
  boardId: { type: "string" },
@@ -223,6 +255,7 @@ var kanbanTool = {
223
255
  runTaskId: { type: "string" },
224
256
  lastResult: { type: "string" },
225
257
  error: { type: "string" },
258
+ expectedLeaseId: { type: "string" },
226
259
  assignmentStatus: {
227
260
  type: "string",
228
261
  enum: ["assigned", "queued", "running", "completed", "failed", "cancelled"]
@@ -257,6 +290,7 @@ var kanbanTool = {
257
290
  lastFailureKind: { type: "string" },
258
291
  dependsOn: { type: "array", items: { type: "string" } },
259
292
  estimatedHours: { type: "number" },
293
+ actualHours: { type: "number" },
260
294
  taskGraph: { type: "object" },
261
295
  graphId: { type: "string" },
262
296
  specId: { type: "string" },
@@ -302,7 +336,24 @@ var kanbanTool = {
302
336
  includeCompletedTasks: { type: "boolean" },
303
337
  preserveAssignment: { type: "boolean" },
304
338
  preserveDependencies: { type: "boolean" },
305
- moveTasksToColumnId: { type: "string" }
339
+ moveTasksToColumnId: { type: "string" },
340
+ atomicityMode: { type: "string", enum: ["off", "assess", "enforce"] },
341
+ atomicityDecomposition: { type: "string", enum: ["auto", "propose"] },
342
+ gateEnforcement: { type: "string", enum: ["strict", "soft", "off"] },
343
+ subtasks: {
344
+ type: "array",
345
+ minItems: 2,
346
+ items: {
347
+ type: "object",
348
+ properties: {
349
+ title: { type: "string" },
350
+ description: { type: "string" },
351
+ successCriteria: { type: "array", items: { type: "string" } },
352
+ dependsOnIndex: { type: "array", items: { type: "number" } }
353
+ },
354
+ required: ["title"]
355
+ }
356
+ }
306
357
  },
307
358
  required: ["action"]
308
359
  },
@@ -342,7 +393,14 @@ var kanbanTool = {
342
393
  title: input.title,
343
394
  ...input.description !== void 0 ? { description: input.description } : {},
344
395
  ...input.tags !== void 0 ? { tags: input.tags } : {},
345
- ...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {}
396
+ ...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
397
+ ...input.atomicityMode !== void 0 ? {
398
+ atomicity: {
399
+ mode: input.atomicityMode,
400
+ decomposition: input.atomicityDecomposition ?? "propose"
401
+ }
402
+ } : {},
403
+ ...input.gateEnforcement !== void 0 ? { completionGate: { enforcement: input.gateEnforcement } } : {}
346
404
  });
347
405
  return { ok: true, message: `Board created: ${board.title}`, board };
348
406
  }
@@ -351,7 +409,14 @@ var kanbanTool = {
351
409
  const board = await updateBoard2(projectRoot, input.boardId, {
352
410
  ...input.title !== void 0 ? { title: input.title } : {},
353
411
  ...input.description !== void 0 ? { description: input.description } : {},
354
- ...input.tags !== void 0 ? { tags: input.tags } : {}
412
+ ...input.tags !== void 0 ? { tags: input.tags } : {},
413
+ ...input.atomicityMode !== void 0 ? {
414
+ atomicity: {
415
+ mode: input.atomicityMode,
416
+ decomposition: input.atomicityDecomposition ?? "propose"
417
+ }
418
+ } : {},
419
+ ...input.gateEnforcement !== void 0 ? { completionGate: { enforcement: input.gateEnforcement } } : {}
355
420
  });
356
421
  return board ? okBoard(board, "Board updated.") : fail("Board not found.");
357
422
  }
@@ -559,30 +624,18 @@ var kanbanTool = {
559
624
  case "add_task": {
560
625
  if (!input.boardId || !input.title) return fail("add_task requires boardId and title.");
561
626
  const result2 = await addTask(projectRoot, input.boardId, taskInput(input));
562
- return result2 ? okTask(result2.board, result2.task, "Task added.") : fail("Board not found.");
627
+ if (!result2) return fail("Board not found.");
628
+ return okTask(
629
+ result2.board,
630
+ result2.task,
631
+ `Task added.${atomicityNudge(result2.task)}`
632
+ );
563
633
  }
564
634
  case "split_task": {
565
635
  if (!input.boardId || !input.taskId || !input.childTitles?.length) {
566
636
  return fail("split_task requires boardId, taskId, and childTitles.");
567
637
  }
568
- const result2 = await splitTask(projectRoot, input.boardId, input.taskId, {
569
- titles: input.childTitles,
570
- ...input.targetColumnId !== void 0 ? { columnId: input.targetColumnId } : {},
571
- ...input.inheritAssignment !== void 0 ? { inheritAssignment: input.inheritAssignment } : {},
572
- ...input.inheritLabels !== void 0 ? { inheritLabels: input.inheritLabels } : {},
573
- ...input.inheritSuccessCriteria !== void 0 ? { inheritSuccessCriteria: input.inheritSuccessCriteria } : {},
574
- ...input.inheritGoalMetrics !== void 0 ? { inheritGoalMetrics: input.inheritGoalMetrics } : {},
575
- ...input.inheritDependencies !== void 0 ? { inheritDependencies: input.inheritDependencies } : {},
576
- ...input.chainChildren !== void 0 ? { chainChildren: input.chainChildren } : {},
577
- ...input.rewireDependents !== void 0 ? { rewireDependents: input.rewireDependents } : {}
578
- });
579
- return result2 ? {
580
- ok: true,
581
- message: `${result2.children.length} child task(s) created.`,
582
- board: result2.board,
583
- task: result2.parent,
584
- children: result2.children
585
- } : fail("Task not found.");
638
+ return handleSplitTask(projectRoot, input, {});
586
639
  }
587
640
  case "merge_tasks": {
588
641
  if (!input.boardId || !input.taskIds?.length || !input.title) {
@@ -656,6 +709,19 @@ var kanbanTool = {
656
709
  "transition_task requires boardId, taskId, lifecycleStage, author, and transitionComment."
657
710
  );
658
711
  }
712
+ if (input.lifecycleStage === "done") {
713
+ const boardBefore = await getBoard2(projectRoot, input.boardId);
714
+ const taskBefore = boardBefore ? await getTask(projectRoot, input.boardId, input.taskId) : null;
715
+ if (boardBefore && taskBefore && !taskBefore.verificationReport && (taskBefore.atomic || Boolean(taskBefore.successCriteria?.length))) {
716
+ const preGate = await verifyTaskCompletion(projectRoot, input.boardId, taskBefore.id, {
717
+ persist: false
718
+ });
719
+ await updateTask(projectRoot, input.boardId, taskBefore.id, {
720
+ verificationReport: preGate.report,
721
+ successCriteria: preGate.task.successCriteria
722
+ });
723
+ }
724
+ }
659
725
  const result2 = await transitionTask(projectRoot, input.boardId, input.taskId, {
660
726
  to: input.lifecycleStage,
661
727
  actor: input.author,
@@ -670,6 +736,9 @@ var kanbanTool = {
670
736
  } : {},
671
737
  patch: taskPatch(input)
672
738
  });
739
+ if (result2 && input.lifecycleStage === "done" && result2.task.verificationReport) {
740
+ recordKanbanVerificationEvidence(ctx, result2.task.verificationReport);
741
+ }
673
742
  return result2 ? okTask(result2.board, result2.task, `Task advanced to ${result2.transition.to}.`) : fail("Board or task not found.");
674
743
  }
675
744
  case "move_task": {
@@ -783,7 +852,31 @@ var kanbanTool = {
783
852
  // is atomic inside updateTaskAssignment's mutateBoard lock.
784
853
  input.expectedLeaseId !== void 0 ? { expectedLeaseId: input.expectedLeaseId } : {}
785
854
  );
786
- return board ? okBoard(board, "Assignment updated.") : fail("Task not found.");
855
+ if (!board) return fail("Task not found.");
856
+ if (assignmentStatus === "completed") {
857
+ const envGate = readEnvGateEnforcement();
858
+ const finalized = await finalizeTaskCompletion(projectRoot, board.id, input.taskId, {
859
+ ...board.completionGate === void 0 && envGate !== void 0 ? { enforcement: envGate } : {},
860
+ ...ctx.agentId !== void 0 ? { eventContext: { actor: ctx.agentId } } : {}
861
+ });
862
+ if (finalized) {
863
+ if (finalized.gate.report) {
864
+ recordKanbanVerificationEvidence(ctx, finalized.gate.report);
865
+ }
866
+ const gateSummary = {
867
+ enforcement: finalized.gate.enforcement,
868
+ allowed: finalized.gate.allowed,
869
+ verdict: finalized.gate.verdict,
870
+ issues: finalized.gate.issues.map((issue) => issue.message)
871
+ };
872
+ 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(" | ")}`;
873
+ return {
874
+ ...okTask(finalized.board, finalized.task, `Assignment updated. ${gateMessage}`),
875
+ gate: gateSummary
876
+ };
877
+ }
878
+ }
879
+ return okBoard(board, "Assignment updated.");
787
880
  }
788
881
  case "heartbeat_assignment": {
789
882
  if (!input.boardId || !input.taskId) {
@@ -947,6 +1040,83 @@ var kanbanTool = {
947
1040
  });
948
1041
  return board ? okBoard(board, "Link added.") : fail("Task not found.");
949
1042
  }
1043
+ case "assess_atomicity": {
1044
+ if (!input.boardId || !input.taskId) {
1045
+ return fail("assess_atomicity requires boardId and taskId.");
1046
+ }
1047
+ const result2 = await assessTaskAtomicity(projectRoot, input.boardId, input.taskId, {
1048
+ assessedBy: "agent",
1049
+ ...ctx.agentId !== void 0 ? { eventContext: { actor: ctx.agentId } } : {}
1050
+ });
1051
+ if (!result2) return fail("Task not found.");
1052
+ const failing = result2.assessment.criteria.filter((entry) => entry.score < 1).map((entry) => entry.reason);
1053
+ const guidance = result2.assessment.verdict === "needs_decomposition" ? ` This task should be split before dispatch \u2014 call propose_decomposition with 2+ subtasks (each with one verifiable success criterion). Reasons: ${failing.join(" | ")}` : result2.assessment.verdict === "composite" ? " Container task: work happens in its children; it is verified via subtask aggregation." : "";
1054
+ return okTask(
1055
+ result2.board,
1056
+ result2.task,
1057
+ `Atomicity verdict: ${result2.assessment.verdict} (score ${result2.assessment.score}).${guidance}`
1058
+ );
1059
+ }
1060
+ case "propose_decomposition": {
1061
+ if (!input.boardId || !input.taskId || !input.subtasks?.length) {
1062
+ return fail("propose_decomposition requires boardId, taskId, and subtasks (2+).");
1063
+ }
1064
+ if (input.subtasks.length < 2) {
1065
+ return fail("propose_decomposition requires at least two subtasks.");
1066
+ }
1067
+ const invalid = input.subtasks.find(
1068
+ (subtask) => typeof subtask?.title !== "string" || !subtask.title.trim()
1069
+ );
1070
+ if (invalid) return fail("Every proposed subtask needs a non-blank title.");
1071
+ const result2 = await proposeTaskDecomposition(
1072
+ projectRoot,
1073
+ input.boardId,
1074
+ input.taskId,
1075
+ {
1076
+ subtasks: input.subtasks,
1077
+ ...input.note !== void 0 ? { rationale: input.note } : {},
1078
+ ...ctx.agentId !== void 0 ? { proposedBy: ctx.agentId } : {}
1079
+ },
1080
+ ctx.agentId !== void 0 ? { actor: ctx.agentId } : {}
1081
+ );
1082
+ if (!result2) return fail("Task not found.");
1083
+ const message = result2.proposal.status === "applied" ? `Decomposition applied: ${result2.proposal.appliedChildTaskIds?.length ?? 0} child tasks created (parent marked atomic).` : 'Decomposition proposal recorded \u2014 awaiting approval (board policy is "propose"). It can be approved from the WebUI or via update_task.';
1084
+ return okTask(result2.board, result2.task, message);
1085
+ }
1086
+ case "verify_completion": {
1087
+ if (!input.boardId || !input.taskId) {
1088
+ return fail("verify_completion requires boardId and taskId.");
1089
+ }
1090
+ const verResult = await verifyTaskCompletion(projectRoot, input.boardId, input.taskId);
1091
+ const persistedBoard = await updateTask(projectRoot, input.boardId, input.taskId, {
1092
+ verificationReport: verResult.report,
1093
+ successCriteria: verResult.task.successCriteria
1094
+ });
1095
+ if (!persistedBoard) {
1096
+ return {
1097
+ ok: false,
1098
+ verdict: verResult.report.verdict,
1099
+ message: `Verification succeeded but persist failed: ${verResult.report.markdownSummary}. Board may be stale \u2014 re-run verify_completion.`,
1100
+ board: verResult.board
1101
+ };
1102
+ }
1103
+ recordKanbanVerificationEvidence(ctx, verResult.report);
1104
+ const freshTask = persistedBoard.tasks?.find((t) => t.id === input.taskId);
1105
+ const deterministicVerdicts = ["passed", "failed", "needs_human", "incomplete"];
1106
+ return {
1107
+ ok: deterministicVerdicts.includes(verResult.report.verdict),
1108
+ verdict: verResult.report.verdict,
1109
+ message: verResult.report.markdownSummary,
1110
+ board: persistedBoard,
1111
+ task: freshTask ?? verResult.task
1112
+ };
1113
+ }
1114
+ case "split_atomic": {
1115
+ if (!input.boardId || !input.taskId || !input.childTitles?.length) {
1116
+ return fail("split_atomic requires boardId, taskId, and childTitles (at least one).");
1117
+ }
1118
+ return handleSplitTask(projectRoot, input, { atomic: true });
1119
+ }
950
1120
  default:
951
1121
  return fail(`Unknown kanban action: ${input.action}`);
952
1122
  }
@@ -957,6 +1127,15 @@ var kanbanTool = {
957
1127
  }
958
1128
  }
959
1129
  };
1130
+ function atomicityNudge(task) {
1131
+ if (task.atomicityAssessment?.verdict !== "needs_decomposition") return "";
1132
+ const reasons = task.atomicityAssessment.criteria.filter((entry) => entry.score < 1).map((entry) => entry.reason).join(" | ");
1133
+ return ` Atomicity: needs_decomposition (score ${task.atomicityAssessment.score}) \u2014 call propose_decomposition with 2+ subtasks before dispatch. Reasons: ${reasons}`;
1134
+ }
1135
+ function readEnvGateEnforcement() {
1136
+ const raw = process.env["WRONGSTACK_KANBAN_GATE"]?.trim().toLowerCase();
1137
+ return raw === "strict" || raw === "soft" || raw === "off" ? raw : void 0;
1138
+ }
960
1139
  function fail(message) {
961
1140
  return { ok: false, message };
962
1141
  }
@@ -966,6 +1145,50 @@ function okBoard(board, message = "Board loaded.") {
966
1145
  function okTask(board, task, message) {
967
1146
  return { ok: true, message, board, task };
968
1147
  }
1148
+ async function handleSplitTask(projectRoot, input, extraSplitOptions) {
1149
+ const boardId = input.boardId;
1150
+ const taskId = input.taskId;
1151
+ const childTitles = input.childTitles;
1152
+ if (!boardId || !taskId || !childTitles?.length) {
1153
+ return fail("split requires boardId, taskId, and at least one childTitles.");
1154
+ }
1155
+ const {
1156
+ targetColumnId,
1157
+ inheritAssignment,
1158
+ inheritLabels,
1159
+ inheritSuccessCriteria,
1160
+ inheritGoalMetrics,
1161
+ inheritDependencies,
1162
+ chainChildren,
1163
+ rewireDependents
1164
+ } = input;
1165
+ const result = await splitTask(projectRoot, boardId, taskId, {
1166
+ titles: childTitles,
1167
+ ...extraSplitOptions,
1168
+ ...targetColumnId !== void 0 ? { columnId: targetColumnId } : {},
1169
+ ...inheritAssignment !== void 0 ? { inheritAssignment } : {},
1170
+ ...inheritLabels !== void 0 ? { inheritLabels } : {},
1171
+ ...inheritSuccessCriteria !== void 0 ? { inheritSuccessCriteria } : {},
1172
+ ...inheritGoalMetrics !== void 0 ? { inheritGoalMetrics } : {},
1173
+ ...inheritDependencies !== void 0 ? { inheritDependencies } : {},
1174
+ ...chainChildren !== void 0 ? { chainChildren } : {},
1175
+ ...rewireDependents !== void 0 ? { rewireDependents } : {}
1176
+ });
1177
+ if (!result) return fail("Task not found.");
1178
+ const freshParent = result.board.tasks?.find((t) => t.id === taskId);
1179
+ if (!freshParent) {
1180
+ return fail(
1181
+ `Split succeeded but parent ${taskId} not found in returned board. Children: [${result.children.map((c) => c.id).join(", ")}].`
1182
+ );
1183
+ }
1184
+ return {
1185
+ ok: true,
1186
+ message: `${result.children.length} child task(s) created.`,
1187
+ board: result.board,
1188
+ task: freshParent,
1189
+ children: result.children
1190
+ };
1191
+ }
969
1192
  async function requireBoard(projectRoot, boardId) {
970
1193
  return boardId ? getBoard2(projectRoot, boardId) : null;
971
1194
  }
@@ -1067,7 +1290,8 @@ function taskPatch(input) {
1067
1290
  labels: input.labels,
1068
1291
  assignedAgent: input.agentId,
1069
1292
  ...mergedDependsOn(input) ? { dependsOn: mergedDependsOn(input) } : {},
1070
- ...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {}
1293
+ ...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {},
1294
+ ...input.actualHours !== void 0 ? { actualHours: input.actualHours } : {}
1071
1295
  };
1072
1296
  }
1073
1297
  function assignmentInput(input) {