@wrongstack/tools 0.313.0 → 0.316.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 (182) hide show
  1. package/dist/_edit-match.d.ts +3 -2
  2. package/dist/_output-spool.d.ts +4 -3
  3. package/dist/_shell-pick.d.ts +2 -1
  4. package/dist/_spawn-stream.d.ts +2 -1
  5. package/dist/_syntax-check.d.ts +2 -1
  6. package/dist/audit.js +10 -4055
  7. package/dist/bash-kill-guard.d.ts +3 -6
  8. package/dist/bash.js +9 -2308
  9. package/dist/batch-tool-use.js +2 -187
  10. package/dist/browser/index.js +25 -1339
  11. package/dist/browser/network-guard-proxy.d.ts +2 -1
  12. package/dist/builtin.js +69 -33949
  13. package/dist/chunk-22B7GE5T.js +260 -0
  14. package/dist/chunk-2VSZC6QM.js +113 -0
  15. package/dist/chunk-3PVYQFRQ.js +472 -0
  16. package/dist/chunk-42BHI7TN.js +337 -0
  17. package/dist/chunk-4BHUSMNL.js +104 -0
  18. package/dist/chunk-4I4LWFOO.js +431 -0
  19. package/dist/chunk-4UESMQDP.js +122 -0
  20. package/dist/chunk-553IQUMO.js +478 -0
  21. package/dist/chunk-5H4GSJAI.js +223 -0
  22. package/dist/chunk-6RNIFLG4.js +305 -0
  23. package/dist/chunk-7EY5DCRO.js +434 -0
  24. package/dist/chunk-A6BRWIXN.js +317 -0
  25. package/dist/chunk-AHHDWNXC.js +200 -0
  26. package/dist/chunk-BSLVR56S.js +199 -0
  27. package/dist/chunk-BTSFTGIL.js +7 -0
  28. package/dist/chunk-D33S4RQG.js +222 -0
  29. package/dist/chunk-DA4KNE2H.js +4412 -0
  30. package/dist/chunk-DR7JOQTC.js +520 -0
  31. package/dist/chunk-EEOFQDT6.js +898 -0
  32. package/dist/chunk-EJWSCQ6D.js +137 -0
  33. package/dist/chunk-EMRGN4KM.js +170 -0
  34. package/dist/chunk-FQZOHLG3.js +89 -0
  35. package/dist/chunk-GOIZNNOA.js +221 -0
  36. package/dist/chunk-HFXSNKCV.js +196 -0
  37. package/dist/chunk-HYX2TZ7O.js +1272 -0
  38. package/dist/chunk-I64BS3VC.js +15 -0
  39. package/dist/chunk-IVWV2UM6.js +955 -0
  40. package/dist/chunk-IXMNTNW5.js +238 -0
  41. package/dist/chunk-IYK7XGDZ.js +892 -0
  42. package/dist/chunk-J5SKD6LN.js +235 -0
  43. package/dist/chunk-JNNVRHMV.js +2111 -0
  44. package/dist/chunk-JOHXDF3D.js +295 -0
  45. package/dist/chunk-JRJJX3L5.js +424 -0
  46. package/dist/chunk-K3WNSHUF.js +1371 -0
  47. package/dist/chunk-L25CVUVR.js +2830 -0
  48. package/dist/chunk-LIJQMYYX.js +59 -0
  49. package/dist/chunk-LWKUTMD5.js +197 -0
  50. package/dist/chunk-MQX2KXVW.js +54 -0
  51. package/dist/chunk-MZZN7VCD.js +359 -0
  52. package/dist/chunk-NBDZMMG2.js +169 -0
  53. package/dist/chunk-NKJW5GFO.js +366 -0
  54. package/dist/chunk-NKNOBUBU.js +522 -0
  55. package/dist/chunk-O4MBV4AX.js +134 -0
  56. package/dist/chunk-OUBDL3WH.js +3515 -0
  57. package/dist/chunk-PQKK5JH7.js +101 -0
  58. package/dist/chunk-PY2IOS7C.js +1000 -0
  59. package/dist/chunk-Q5JPPCZC.js +12 -0
  60. package/dist/chunk-QBV725YG.js +128 -0
  61. package/dist/chunk-QZQSPUBF.js +315 -0
  62. package/dist/chunk-RGISBZBD.js +231 -0
  63. package/dist/chunk-RJZQWEMS.js +84 -0
  64. package/dist/chunk-RXISBFR5.js +315 -0
  65. package/dist/chunk-S5CCLZL3.js +99 -0
  66. package/dist/chunk-SDLVH5RZ.js +178 -0
  67. package/dist/chunk-T2F4S7U4.js +1723 -0
  68. package/dist/chunk-TEBQ4VPZ.js +163 -0
  69. package/dist/chunk-U4D5S3VQ.js +21 -0
  70. package/dist/chunk-U4VAATKP.js +193 -0
  71. package/dist/chunk-UCYQTZ7F.js +113 -0
  72. package/dist/chunk-UKU2ZX4C.js +203 -0
  73. package/dist/chunk-UNSKG6DC.js +159 -0
  74. package/dist/chunk-V43EQ6VR.js +68 -0
  75. package/dist/chunk-VB3MJGCV.js +610 -0
  76. package/dist/chunk-VFKPWSOX.js +154 -0
  77. package/dist/chunk-VJNHFZ7J.js +190 -0
  78. package/dist/chunk-VXECAJJ7.js +1091 -0
  79. package/dist/chunk-WHIIIREL.js +223 -0
  80. package/dist/chunk-Z6DAKQ3U.js +1 -0
  81. package/dist/chunk-ZJQ4IGXK.js +474 -0
  82. package/dist/chunk-ZLTFLC25.js +385 -0
  83. package/dist/circuit-breaker.js +3 -191
  84. package/dist/codebase-index/ast-invariant-engine.d.ts +5 -4
  85. package/dist/codebase-index/binary-frame.d.ts +0 -2
  86. package/dist/codebase-index/bm25.d.ts +1 -1
  87. package/dist/codebase-index/circuit-breaker.d.ts +2 -1
  88. package/dist/codebase-index/codebase-ast-replace-tool.d.ts +3 -2
  89. package/dist/codebase-index/codebase-impact-analysis-tool.d.ts +4 -3
  90. package/dist/codebase-index/codebase-invariant-check-tool.d.ts +3 -2
  91. package/dist/codebase-index/codebase-repo-map-tool.d.ts +3 -2
  92. package/dist/codebase-index/codebase-skeleton-tool.d.ts +3 -2
  93. package/dist/codebase-index/codebase-targeted-test-tool.d.ts +3 -2
  94. package/dist/codebase-index/content-hash.d.ts +0 -2
  95. package/dist/codebase-index/import-extractor.d.ts +0 -13
  96. package/dist/codebase-index/index-service.d.ts +1 -1
  97. package/dist/codebase-index/index.js +84 -13688
  98. package/dist/codebase-index/languages.d.ts +2 -1
  99. package/dist/codebase-index/module-roots.d.ts +3 -2
  100. package/dist/codebase-index/parser-batch.d.ts +0 -5
  101. package/dist/codebase-index/parser-dispatch.d.ts +2 -1
  102. package/dist/codebase-index/parser-output.d.ts +4 -3
  103. package/dist/codebase-index/parser-worker-pool.d.ts +0 -2
  104. package/dist/codebase-index/parser-worker-script.d.ts +1 -16
  105. package/dist/codebase-index/parser-worker-script.js +8 -3995
  106. package/dist/codebase-index/project-server-client-state.d.ts +2 -2
  107. package/dist/codebase-index/project-server-endpoint.d.ts +0 -15
  108. package/dist/codebase-index/project-server.js +60 -8548
  109. package/dist/codebase-index/tree-sitter/visitor.d.ts +2 -1
  110. package/dist/codebase-index/tree-sitter-parser.d.ts +0 -2
  111. package/dist/codebase-index/ts-parser.d.ts +1 -1
  112. package/dist/codebase-index/vector-search.d.ts +2 -1
  113. package/dist/codebase-index/wal-maintenance.d.ts +3 -2
  114. package/dist/codebase-index/worker.js +20 -8416
  115. package/dist/codebase-index/writer-admin.d.ts +0 -1
  116. package/dist/codebase-index/writer-search.d.ts +0 -9
  117. package/dist/design.js +2 -299
  118. package/dist/diff.js +4 -256
  119. package/dist/document.js +4 -222
  120. package/dist/e2e.js +5 -476
  121. package/dist/edit.js +14 -10200
  122. package/dist/exec.js +16 -3131
  123. package/dist/fetch.js +8 -398
  124. package/dist/format.js +10 -3750
  125. package/dist/generic-parser-VUXDIAOZ.js +13 -0
  126. package/dist/git.js +4 -380
  127. package/dist/glob.js +6 -280
  128. package/dist/go-parser-JXYKJHRC.js +469 -0
  129. package/dist/grep.js +7 -604
  130. package/dist/index.js +318 -35387
  131. package/dist/install.js +10 -4091
  132. package/dist/json-parser-IBHZ4BZC.js +213 -0
  133. package/dist/json.js +4 -665
  134. package/dist/kanban-contract-actions.d.ts +1 -1
  135. package/dist/kanban-detail-actions.d.ts +2 -1
  136. package/dist/kanban-presence.d.ts +2 -1
  137. package/dist/kanban-serializer.d.ts +0 -8
  138. package/dist/kanban-split-task-handler.d.ts +2 -2
  139. package/dist/kanban-tool-types.d.ts +2 -1
  140. package/dist/kanban.js +3 -2096
  141. package/dist/languages/diagnostics.d.ts +2 -1
  142. package/dist/languages/index.js +23 -4664
  143. package/dist/languages/legacy-bridge.d.ts +3 -2
  144. package/dist/languages/types.d.ts +2 -1
  145. package/dist/legacy-bridge-7M6QLUQN.js +18 -0
  146. package/dist/lint.js +10 -3706
  147. package/dist/logs.js +4 -287
  148. package/dist/memory.js +6 -215
  149. package/dist/mode.js +3 -99
  150. package/dist/outdated.js +4 -5170
  151. package/dist/pack.js +65 -33897
  152. package/dist/parser-dispatch-QH4XPGG6.js +14 -0
  153. package/dist/patch.js +13 -9935
  154. package/dist/plan.js +5 -3286
  155. package/dist/process-registry.js +8 -651
  156. package/dist/ps-slash.js +12 -1439
  157. package/dist/pwsh.d.ts +0 -1
  158. package/dist/py-parser-IS4HKYMF.js +16 -0
  159. package/dist/read.js +13 -9816
  160. package/dist/replace.js +15 -10014
  161. package/dist/rs-parser-XHUUO2KZ.js +81 -0
  162. package/dist/scaffold.js +4 -253
  163. package/dist/search.js +5 -585
  164. package/dist/session-kanban-sync.d.ts +0 -6
  165. package/dist/session-kanban.js +30 -972
  166. package/dist/skill.js +2 -215
  167. package/dist/spawn-background.d.ts +1 -1
  168. package/dist/task.js +5 -3410
  169. package/dist/test.js +10 -3773
  170. package/dist/todo.js +4 -2891
  171. package/dist/tool-help.js +3 -123
  172. package/dist/tool-search.js +3 -79
  173. package/dist/tool-tier.js +68 -33984
  174. package/dist/tool-use.js +2 -93
  175. package/dist/tree-sitter-parser-AIERW3YI.js +15 -0
  176. package/dist/tree.js +4 -288
  177. package/dist/ts-parser-RCTYKSVI.js +232 -0
  178. package/dist/typecheck.js +10 -3753
  179. package/dist/win32.js +6 -60
  180. package/dist/write.js +14 -9882
  181. package/dist/yaml-parser-4SAXV3PL.js +157 -0
  182. package/package.json +5 -5
@@ -0,0 +1,434 @@
1
+ import {
2
+ kanbanTool
3
+ } from "./chunk-JNNVRHMV.js";
4
+ import {
5
+ applyManagedKanbanBoardToTodos,
6
+ blockingTitles,
7
+ mirrorSessionTodosToKanban,
8
+ takeSessionMirrorFailure
9
+ } from "./chunk-PY2IOS7C.js";
10
+
11
+ // src/todo.ts
12
+ import {
13
+ loadPlan,
14
+ loadTasks,
15
+ savePlan,
16
+ saveTasks,
17
+ setPlanItemStatus
18
+ } from "@wrongstack/core/storage";
19
+ import { addTask, getBoard } from "@wrongstack/kanban";
20
+ function normalizedTitle(value) {
21
+ return value.trim().toLocaleLowerCase().replace(/\s+/g, " ");
22
+ }
23
+ function activeBoardId(items, ctx) {
24
+ const metaKanban = ctx.meta?.["kanban"];
25
+ const metaBoardId = metaKanban && typeof metaKanban === "object" ? metaKanban["boardId"] : void 0;
26
+ return ctx.currentKanbanBoardId ?? (typeof metaBoardId === "string" ? metaBoardId : void 0) ?? items.find((item) => item.kanbanBoardId)?.kanbanBoardId ?? "";
27
+ }
28
+ function bindTodosToBoard(items, previous, board) {
29
+ const previousById = new Map(previous.map((item) => [item.id, item]));
30
+ const available = board.tasks.filter(
31
+ (task) => task.status !== "archived" && task.mergedIntoTaskId === void 0 && (!task.childTaskIds || task.childTaskIds.length === 0)
32
+ ).sort(
33
+ (left, right) => left.createdAt.localeCompare(right.createdAt) || left.order - right.order
34
+ );
35
+ const used = /* @__PURE__ */ new Set();
36
+ return items.map((item) => {
37
+ const previousItem = previousById.get(item.id);
38
+ const requestedTaskId = item.kanbanBoardId === board.id ? item.kanbanTaskId : previousItem?.kanbanBoardId === board.id ? previousItem.kanbanTaskId : void 0;
39
+ const title = normalizedTitle(item.content);
40
+ const candidates = [
41
+ requestedTaskId ? board.tasks.find((task2) => task2.id === requestedTaskId) : void 0,
42
+ board.tasks.find((task2) => task2.id === item.id),
43
+ board.tasks.find((task2) => task2.origin?.taskId === item.id),
44
+ available.find((task2) => !used.has(task2.id) && normalizedTitle(task2.title) === title)
45
+ ];
46
+ const task = candidates.find((candidate) => candidate && !used.has(candidate.id));
47
+ if (!task) {
48
+ const { blockedBy: _discarded, ...rest } = item;
49
+ return { ...rest };
50
+ }
51
+ used.add(task.id);
52
+ const blockedBy = blockingTitles(board, task);
53
+ return {
54
+ ...item,
55
+ kanbanBoardId: board.id,
56
+ kanbanTaskId: task.id,
57
+ ...blockedBy.length ? { blockedBy } : { blockedBy: void 0 }
58
+ };
59
+ });
60
+ }
61
+ function demoteBlockedInProgress(items, warnings) {
62
+ return items.map((item) => {
63
+ if (item.status !== "in_progress" || !item.blockedBy?.length) return item;
64
+ warnings.push(
65
+ `"${item.content}" cannot start yet \u2014 it waits on: ${item.blockedBy.join("; ")}. Kept as pending; complete the blocking work first.`
66
+ );
67
+ return { ...item, status: "pending" };
68
+ });
69
+ }
70
+ async function createMissingManagedCards(items, board, ctx, warnings) {
71
+ const created = /* @__PURE__ */ new Map();
72
+ for (const item of items) {
73
+ if (item.kanbanBoardId === board.id && item.kanbanTaskId) continue;
74
+ try {
75
+ const result = await addTask(
76
+ ctx.projectRoot,
77
+ board.id,
78
+ {
79
+ title: item.content,
80
+ description: item.activeForm?.trim() || `Added from the session todo list: ${item.content}`
81
+ },
82
+ // The card mirrors this session's todo row, so the session owns it.
83
+ { sessionId: ctx.eventSessionId(), actor: "todo" }
84
+ );
85
+ if (!result) {
86
+ warnings.push(`Could not open a Kanban card for "${item.content}": board not found.`);
87
+ continue;
88
+ }
89
+ created.set(item.id, result.task.id);
90
+ } catch (error) {
91
+ warnings.push(
92
+ `Could not open a Kanban card for "${item.content}": ${error instanceof Error ? error.message : String(error)}`
93
+ );
94
+ }
95
+ }
96
+ return created;
97
+ }
98
+ async function synchronizeManagedKanban(items, board, ctx, signal) {
99
+ let synced = 0;
100
+ const warnings = [];
101
+ const actor = ctx.agentId?.trim() || ctx.agentName?.trim() || "kanban-agent";
102
+ const execute = async (input) => {
103
+ const result = await kanbanTool.execute(input, ctx, { signal });
104
+ if (!result.ok) warnings.push(result.message);
105
+ else {
106
+ synced++;
107
+ if (result.message.includes("Warning:")) warnings.push(result.message);
108
+ }
109
+ return result;
110
+ };
111
+ for (const item of items) {
112
+ if (item.status !== "pending" || item.kanbanBoardId !== board.id || !item.kanbanTaskId) {
113
+ continue;
114
+ }
115
+ const task = board.tasks.find((candidate) => candidate.id === item.kanbanTaskId);
116
+ if (task?.lifecycle?.currentStage !== "running") continue;
117
+ const released = await execute({
118
+ action: "mark_assignment",
119
+ boardId: board.id,
120
+ taskId: task.id,
121
+ assignmentStatus: "assigned",
122
+ agentId: actor,
123
+ lastResult: `Todo returned to queue: ${item.content}`
124
+ });
125
+ if (!released.ok) continue;
126
+ await execute({
127
+ action: "transition_task",
128
+ boardId: board.id,
129
+ taskId: task.id,
130
+ lifecycleStage: "todo",
131
+ author: actor,
132
+ transitionComment: `Todo returned to queue: ${item.content}`
133
+ });
134
+ }
135
+ for (const item of items) {
136
+ if (item.status === "completed" || item.kanbanBoardId !== board.id || !item.kanbanTaskId) {
137
+ continue;
138
+ }
139
+ const task = board.tasks.find((candidate) => candidate.id === item.kanbanTaskId);
140
+ if (task?.status !== "completed") continue;
141
+ warnings.push(
142
+ `"${item.content}" is already Done on the Kanban board and a completed card cannot be reopened; the row stays completed. Create a follow-up card for any remaining work.`
143
+ );
144
+ }
145
+ for (const item of items) {
146
+ if (item.status !== "completed" || item.kanbanBoardId !== board.id || !item.kanbanTaskId) {
147
+ continue;
148
+ }
149
+ const task = board.tasks.find((candidate) => candidate.id === item.kanbanTaskId);
150
+ if (!task || task.status === "completed") continue;
151
+ const stage = task.lifecycle?.currentStage;
152
+ if (stage === "backlog" || stage === "todo") {
153
+ const started = await execute({
154
+ action: "start_task",
155
+ boardId: board.id,
156
+ taskId: task.id,
157
+ author: actor,
158
+ agentId: actor,
159
+ transitionComment: `Auto-started for completion: ${item.content}`
160
+ });
161
+ if (!started.ok) {
162
+ continue;
163
+ }
164
+ }
165
+ await execute({
166
+ action: "mark_assignment",
167
+ boardId: board.id,
168
+ taskId: task.id,
169
+ assignmentStatus: "completed",
170
+ agentId: actor,
171
+ lastResult: `Todo completed: ${item.content}`
172
+ });
173
+ }
174
+ const attemptedParents = /* @__PURE__ */ new Set();
175
+ let afterCompletions = await getBoard(ctx.projectRoot, board.id);
176
+ while (afterCompletions) {
177
+ const parent = afterCompletions.tasks.find(
178
+ (task) => task.atomic === true && task.status !== "completed" && Boolean(task.childTaskIds?.length) && !attemptedParents.has(task.id) && task.childTaskIds?.every(
179
+ (childId) => afterCompletions?.tasks.find((candidate) => candidate.id === childId)?.status === "completed"
180
+ )
181
+ );
182
+ if (!parent) break;
183
+ attemptedParents.add(parent.id);
184
+ const started = await execute({
185
+ action: "start_task",
186
+ boardId: board.id,
187
+ taskId: parent.id,
188
+ author: actor,
189
+ agentId: actor,
190
+ transitionComment: "All child tasks completed; validating composite parent."
191
+ });
192
+ if (started.ok) {
193
+ await execute({
194
+ action: "mark_assignment",
195
+ boardId: board.id,
196
+ taskId: parent.id,
197
+ assignmentStatus: "completed",
198
+ agentId: actor,
199
+ lastResult: "All child tasks completed; composite result ready for verification."
200
+ });
201
+ }
202
+ afterCompletions = await getBoard(ctx.projectRoot, board.id);
203
+ }
204
+ const completionPending = items.some(
205
+ (item) => item.status === "completed" && item.kanbanBoardId === board.id && Boolean(item.kanbanTaskId) && afterCompletions?.tasks.find((task) => task.id === item.kanbanTaskId)?.status !== "completed"
206
+ );
207
+ const active = items.find(
208
+ (item) => item.status === "in_progress" && item.kanbanBoardId === board.id && Boolean(item.kanbanTaskId)
209
+ );
210
+ const activeStage = active?.kanbanTaskId ? afterCompletions?.tasks.find((task) => task.id === active.kanbanTaskId)?.lifecycle?.currentStage : void 0;
211
+ if (active?.kanbanTaskId) {
212
+ if (activeStage === "review" || activeStage === "done") {
213
+ warnings.push(
214
+ `"${active.content}" is in ${activeStage === "review" ? "Review" : "Done"} awaiting acceptance; not re-activating it from the todo list. ` + (activeStage === "review" ? "Call kanban start_task explicitly to reopen it as a repair." : "Done is terminal; reopen only by creating a follow-up card.")
215
+ );
216
+ } else {
217
+ await execute({
218
+ action: "start_task",
219
+ boardId: board.id,
220
+ taskId: active.kanbanTaskId,
221
+ author: actor,
222
+ agentId: actor,
223
+ transitionComment: `Todo activated: ${active.content}`
224
+ });
225
+ }
226
+ }
227
+ if (active?.kanbanTaskId && activeStage === "review") {
228
+ } else if (active?.kanbanTaskId && completionPending) {
229
+ warnings.push(
230
+ "A completed todo is still awaiting acceptance; the next independent Kanban task was started."
231
+ );
232
+ } else if (!active && ctx.currentKanbanBoardId === board.id) {
233
+ ctx.setCurrentKanbanTask(void 0, board.id);
234
+ }
235
+ return { synced, warnings };
236
+ }
237
+ var todoTool = {
238
+ name: "todo",
239
+ category: "Session",
240
+ description: "Manage the compact active-work list. With a managed Kanban binding, every row resolves to a real card and status changes advance that card; without Kanban it remains session-only state. Each call replaces ordering and supplied fields, but unfinished omitted rows are retained until completed.",
241
+ usageHint: "BEST PRACTICE for complex tasks:\n- At the beginning of a non-trivial task, create a clear todo list with specific, actionable items.\n- Only **one** item should be `in_progress` at any time.\n- Update the list frequently as work progresses (mark items done, add new ones, change status).\n- **Re-order items** to reflect current priorities. Omission is not cancellation: unfinished rows are retained until completed.\n- When all items are completed the board auto-clears \u2014 you do NOT need to send an empty list.\n- The system and user can see this list, so keep it honest and up-to-date.\nThis tool is extremely valuable for maintaining focus and giving the user visibility into your plan.",
242
+ permission: "confirm",
243
+ mutating: true,
244
+ timeoutMs: 3e4,
245
+ capabilities: ["session.todo", "fs.write"],
246
+ subjectKey: "todos",
247
+ icon: "todo",
248
+ inputSchema: {
249
+ type: "object",
250
+ properties: {
251
+ todos: {
252
+ type: "array",
253
+ items: {
254
+ type: "object",
255
+ properties: {
256
+ id: {
257
+ type: "string",
258
+ description: 'Unique identifier for the todo item (e.g. "1", "auth-flow").'
259
+ },
260
+ content: {
261
+ type: "string",
262
+ description: "Clear, actionable description of the task."
263
+ },
264
+ status: {
265
+ type: "string",
266
+ enum: ["pending", "in_progress", "completed"],
267
+ description: 'Current status. Only one item should be "in_progress" at a time.'
268
+ },
269
+ activeForm: {
270
+ type: "string",
271
+ description: 'Optional present-tense form shown while the task is active (e.g. "Fixing auth bug").'
272
+ },
273
+ kanbanBoardId: {
274
+ type: "string",
275
+ description: "Kanban board that owns this UI row when Kanban is active."
276
+ },
277
+ kanbanTaskId: {
278
+ type: "string",
279
+ description: "Real Kanban task represented by this UI row."
280
+ }
281
+ },
282
+ required: ["id", "content", "status"]
283
+ },
284
+ description: "The desired todo list. Supplied rows are replaced/reordered; unfinished omitted rows are retained."
285
+ }
286
+ },
287
+ required: ["todos"]
288
+ },
289
+ async execute(input, ctx, call) {
290
+ if (!Array.isArray(input?.todos)) {
291
+ throw new Error("todo: todos must be an array");
292
+ }
293
+ const items = input.todos.filter((t) => Boolean(t?.id && t.content));
294
+ const todoIdentity = (item) => item.kanbanBoardId && item.kanbanTaskId ? `kanban:${item.kanbanBoardId}:${item.kanbanTaskId}` : item.promotedFromTask ? `task:${item.promotedFromTask}` : item.promotedFromPlan ? `plan:${item.promotedFromPlan}` : `todo:${item.id}`;
295
+ const requestedIdentities = new Set(items.map(todoIdentity));
296
+ for (const previous of ctx.todos ?? []) {
297
+ const identity = todoIdentity(previous);
298
+ if (previous.status === "completed" || requestedIdentities.has(identity)) {
299
+ continue;
300
+ }
301
+ items.push({ ...previous });
302
+ requestedIdentities.add(identity);
303
+ }
304
+ const inProgress = items.filter((t) => t.status === "in_progress");
305
+ if (inProgress.length > 1) {
306
+ let seenInProgress = false;
307
+ for (const item of items) {
308
+ if (item.status === "in_progress") {
309
+ if (seenInProgress) item.status = "pending";
310
+ seenInProgress = true;
311
+ }
312
+ }
313
+ }
314
+ const boardId = activeBoardId(items, ctx);
315
+ let board = boardId ? await getBoard(ctx.projectRoot, boardId) : null;
316
+ const managed = board?.lifecycle?.mode === "managed";
317
+ let boundItems = managed && board ? bindTodosToBoard(items, ctx.todos ?? [], board) : items;
318
+ const creationWarnings = [];
319
+ if (managed && board) {
320
+ const managedBoardId = board.id;
321
+ const created = await createMissingManagedCards(boundItems, board, ctx, creationWarnings);
322
+ if (created.size > 0) {
323
+ boundItems = boundItems.map((item) => {
324
+ const taskId = created.get(item.id);
325
+ return taskId ? { ...item, kanbanBoardId: managedBoardId, kanbanTaskId: taskId } : item;
326
+ });
327
+ board = await getBoard(ctx.projectRoot, managedBoardId) ?? board;
328
+ boundItems = bindTodosToBoard(boundItems, ctx.todos ?? [], board);
329
+ }
330
+ boundItems = demoteBlockedInProgress(boundItems, creationWarnings);
331
+ }
332
+ ctx.state.replaceTodos(boundItems);
333
+ const kanbanSync = managed && board ? await synchronizeManagedKanban(boundItems, board, ctx, call.signal) : { synced: 0, warnings: [] };
334
+ kanbanSync.warnings.unshift(...creationWarnings);
335
+ if (managed && board) {
336
+ const unresolved = boundItems.filter(
337
+ (item) => item.kanbanBoardId !== board.id || !item.kanbanTaskId
338
+ );
339
+ if (unresolved.length > 0) {
340
+ kanbanSync.warnings.push(
341
+ `${unresolved.length} Todo row(s) could not be bound to a Kanban task and were not applied. Preserve kanbanBoardId/kanbanTaskId when updating the projection.`
342
+ );
343
+ }
344
+ }
345
+ const mirrorFailure = takeSessionMirrorFailure(ctx.projectRoot, ctx.session?.id ?? "");
346
+ if (mirrorFailure) kanbanSync.warnings.push(mirrorFailure);
347
+ let projectedBoard = board;
348
+ if (managed && board) {
349
+ const refreshed = await getBoard(ctx.projectRoot, board.id);
350
+ if (refreshed) {
351
+ projectedBoard = refreshed;
352
+ applyManagedKanbanBoardToTodos(ctx, refreshed);
353
+ }
354
+ }
355
+ if (!managed) {
356
+ mirrorSessionTodosToKanban(ctx.projectRoot, items, ctx.session?.id ?? "session");
357
+ }
358
+ const completedPlanIds = /* @__PURE__ */ new Set();
359
+ const completedTaskIds = /* @__PURE__ */ new Set();
360
+ const pendingPlanIds = /* @__PURE__ */ new Set();
361
+ const pendingTaskIds = /* @__PURE__ */ new Set();
362
+ for (const item of items) {
363
+ if (item.promotedFromPlan) {
364
+ (item.status === "completed" ? completedPlanIds : pendingPlanIds).add(
365
+ item.promotedFromPlan
366
+ );
367
+ }
368
+ if (item.promotedFromTask) {
369
+ (item.status === "completed" ? completedTaskIds : pendingTaskIds).add(
370
+ item.promotedFromTask
371
+ );
372
+ }
373
+ }
374
+ for (const planId of completedPlanIds) {
375
+ if (pendingPlanIds.has(planId)) continue;
376
+ const meta = ctx.meta;
377
+ const planPath = meta["plan.path.resolved"] ?? meta["plan.path"];
378
+ if (typeof planPath !== "string" || !planPath) continue;
379
+ try {
380
+ const plan = await loadPlan(planPath);
381
+ if (plan) {
382
+ const updated = setPlanItemStatus(plan, planId, "done");
383
+ await savePlan(planPath, updated);
384
+ }
385
+ } catch {
386
+ }
387
+ }
388
+ for (const taskId of completedTaskIds) {
389
+ if (pendingTaskIds.has(taskId)) continue;
390
+ const meta = ctx.meta;
391
+ const taskPath = meta["task.path.resolved"] ?? meta["task.path"];
392
+ if (typeof taskPath !== "string" || !taskPath) continue;
393
+ try {
394
+ const file = await loadTasks(taskPath);
395
+ if (file) {
396
+ const task = file.tasks.find((t) => t.id === taskId);
397
+ if (task && task.status !== "completed") {
398
+ task.status = "completed";
399
+ task.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
400
+ await saveTasks(taskPath, file);
401
+ }
402
+ }
403
+ } catch {
404
+ }
405
+ }
406
+ return {
407
+ count: items.length,
408
+ in_progress: (ctx.todos ?? boundItems).filter((t) => t.status === "in_progress").length,
409
+ ...kanbanSync.synced > 0 ? { kanban_synced: kanbanSync.synced } : {},
410
+ ...kanbanSync.warnings.length > 0 ? { kanban_warnings: kanbanSync.warnings } : {},
411
+ ...projectedBoard?.lifecycle?.mode === "managed" ? {
412
+ kanban_bindings: boundItems.flatMap((item) => {
413
+ if (item.kanbanBoardId !== projectedBoard.id || !item.kanbanTaskId) return [];
414
+ const task = projectedBoard.tasks.find(
415
+ (candidate) => candidate.id === item.kanbanTaskId
416
+ );
417
+ return task ? [
418
+ {
419
+ todoId: item.id,
420
+ boardId: projectedBoard.id,
421
+ taskId: task.id,
422
+ taskStatus: task.status
423
+ }
424
+ ] : [];
425
+ })
426
+ } : {}
427
+ };
428
+ }
429
+ };
430
+
431
+ export {
432
+ todoTool
433
+ };
434
+ //# sourceMappingURL=chunk-7EY5DCRO.js.map