@comfanion/workflow 4.38.2 → 4.38.3-dev.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@comfanion/workflow",
3
- "version": "4.38.2",
3
+ "version": "4.38.3-dev.1",
4
4
  "description": "Initialize OpenCode Workflow system for AI-assisted development with semantic code search",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "4.38.2",
3
- "buildDate": "2026-01-28T10:05:17.810Z",
2
+ "version": "4.38.3-dev.1",
3
+ "buildDate": "2026-01-28T11:01:04.180Z",
4
4
  "files": [
5
5
  ".gitignore",
6
6
  "config.yaml",
@@ -123,7 +123,7 @@ permission:
123
123
  </size-awareness>
124
124
 
125
125
  <phase name="2. Planning">
126
- <action>Create tasklist with todowrite()</action>
126
+ <action>Create tasklist with todowrite() (TODOv2)</action>
127
127
  <action>Present plan to user with specific files/changes</action>
128
128
  <action>Ask for confirmation with question() tool</action>
129
129
  <action>WAIT for user approval before proceeding</action>
@@ -4,7 +4,7 @@ mode: all # Can be primary agent or invoked via @dev
4
4
  temperature: 0.2
5
5
 
6
6
  model: anthropic/claude-opus-4-5 # Strong
7
- #model: z.ai/glm-4.7 # Can break
7
+ #model: zai-coding-plan/glm-4.7 # Can break
8
8
  #model: openai/gpt-5.2-codex
9
9
 
10
10
  # Tools - FULL ACCESS for implementation
@@ -40,6 +40,7 @@ permission:
40
40
  <step n="2">IMMEDIATE: store {user_name}, {communication_language} from .opencode/config.yaml</step>
41
41
  <step n="3">Greet user by {user_name}, communicate in {communication_language}</step>
42
42
  <step n="4">Understand user request and select appropriate skill</step>
43
+ <step n="5">Create tasklist with todowrite() (TODOv2)</step>
43
44
 
44
45
  <search-first critical="MANDATORY - DO THIS BEFORE GLOB/GREP">
45
46
  BEFORE using glob or grep, you MUST call search() first:
@@ -68,23 +69,6 @@ permission:
68
69
  <r critical="MANDATORY">🔍 SEARCH FIRST: Call search() BEFORE glob when exploring codebase.
69
70
  search({ query: "feature pattern", index: "code" }) → THEN glob if needed</r>
70
71
  </rules>
71
-
72
- <todo-usage hint="How to use TODO for tracking">
73
- <create>
74
- todowrite([
75
- { id: "story-task-1", content: "Task 1: Create entity", status: "pending", priority: "high" },
76
- { id: "story-task-2", content: "Task 2: Add repository", status: "pending", priority: "medium" },
77
- ...
78
- ])
79
- </create>
80
- <update-progress>
81
- todoread() → get current list
82
- todowrite([...list with task.status = "in_progress"])
83
- </update-progress>
84
- <mark-complete>
85
- todowrite([...list with task.status = "completed"])
86
- </mark-complete>
87
- </todo-usage>
88
72
  </activation>
89
73
 
90
74
  <persona>
@@ -105,7 +105,7 @@ permission:
105
105
  </phase>
106
106
 
107
107
  <phase name="2. Planning">
108
- <action>Create tasklist with todowrite()</action>
108
+ <action>Create tasklist with todowrite() (TODOv2)</action>
109
109
  <action>Present plan to user with specific deliverables</action>
110
110
  <action>Ask for confirmation with question() tool</action>
111
111
  <action>WAIT for user approval before proceeding</action>
@@ -211,7 +211,7 @@ epic_workflow:
211
211
  # Run integration tests after each story
212
212
  # When true: story done → run integration tests → continue
213
213
  # When false: skip per-story integration tests
214
- test_after_each_story: false
214
+ test_after_each_story: true
215
215
 
216
216
  # Run integration tests after epic complete
217
217
  # When true: all stories done → run epic integration tests
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach } from "bun:test"
2
2
  import { readFile } from "fs/promises"
3
3
  import { join } from "path"
4
4
  import { createTempDir, cleanupTempDir } from "./helpers/mock-ctx"
5
- import { write, update, read } from "../../tools/usethis_todo"
5
+ import { write, update, read, read_by_id, read_five } from "../../tools/usethis_todo"
6
6
 
7
7
  describe("usethis_todo tool", () => {
8
8
  let tempDir: string
@@ -28,7 +28,7 @@ describe("usethis_todo tool", () => {
28
28
  const output = await write.execute(
29
29
  {
30
30
  todos: [
31
- { id: "A1", content: "First task", status: "ready", priority: "HIGH" },
31
+ { id: "A1", content: "First task", description: "Longer details", status: "ready", priority: "HIGH" },
32
32
  { id: "A2", content: "Second task", status: "pending", priority: "LOW" },
33
33
  ],
34
34
  },
@@ -41,6 +41,7 @@ describe("usethis_todo tool", () => {
41
41
  const enhanced = JSON.parse(await readFile(enhancedPath, "utf-8"))
42
42
  expect(enhanced.length).toBe(2)
43
43
  expect(enhanced[0].content).toBe("First task")
44
+ expect(enhanced[0].description).toBe("Longer details")
44
45
 
45
46
  const nativePath = join(
46
47
  process.env.XDG_DATA_HOME!,
@@ -52,6 +53,7 @@ describe("usethis_todo tool", () => {
52
53
  const native = JSON.parse(await readFile(nativePath, "utf-8"))
53
54
  expect(native.length).toBe(2)
54
55
  expect(native[0].content).toContain("First task")
56
+ expect(native[0].content).toContain("Longer details")
55
57
  })
56
58
 
57
59
  it("update merges by id and can add new tasks", async () => {
@@ -60,7 +62,7 @@ describe("usethis_todo tool", () => {
60
62
  await write.execute(
61
63
  {
62
64
  todos: [
63
- { id: "A1", content: "First task", status: "pending", priority: "MED" },
65
+ { id: "A1", content: "First task", description: "v1", status: "pending", priority: "MED" },
64
66
  ],
65
67
  },
66
68
  ctx
@@ -69,7 +71,7 @@ describe("usethis_todo tool", () => {
69
71
  const result = await update.execute(
70
72
  {
71
73
  todos: [
72
- { id: "A1", content: "First task", status: "done", priority: "MED" },
74
+ { id: "A1", content: "First task", description: "v2", status: "done", priority: "MED" },
73
75
  { id: "A2", content: "Second task", status: "ready", priority: "LOW" },
74
76
  ],
75
77
  },
@@ -82,6 +84,7 @@ describe("usethis_todo tool", () => {
82
84
  const enhanced = JSON.parse(await readFile(enhancedPath, "utf-8"))
83
85
  expect(enhanced.length).toBe(2)
84
86
  expect(enhanced.find((t: any) => t.id === "A1")?.status).toBe("done")
87
+ expect(enhanced.find((t: any) => t.id === "A1")?.description).toBe("v2")
85
88
  })
86
89
 
87
90
  it("read returns graph with content", async () => {
@@ -99,4 +102,78 @@ describe("usethis_todo tool", () => {
99
102
  expect(output).toContain("Task content")
100
103
  expect(output).toContain("Available Now")
101
104
  })
105
+
106
+ it("read_five returns up to 5 available tasks with description", async () => {
107
+ const ctx = { sessionID: "sess-five", directory: tempDir } as any
108
+ await write.execute(
109
+ {
110
+ todos: [
111
+ { id: "A1", content: "T1", description: "D1", status: "ready", priority: "HIGH" },
112
+ { id: "A2", content: "T2", status: "ready", priority: "MED" },
113
+ { id: "A3", content: "T3", status: "ready", priority: "LOW" },
114
+ { id: "A4", content: "T4", status: "ready", priority: "LOW" },
115
+ { id: "A5", content: "T5", status: "ready", priority: "LOW" },
116
+ { id: "A6", content: "T6", status: "ready", priority: "LOW" },
117
+ ],
118
+ },
119
+ ctx
120
+ )
121
+
122
+ const out = await read_five.execute({}, ctx)
123
+ expect(out).toContain("Next 5")
124
+ expect(out).toContain("A1")
125
+ expect(out).toContain("T1")
126
+ expect(out).toContain("D1")
127
+ expect(out).toContain("+1 more")
128
+ })
129
+
130
+ it("read_by_id returns task with resolved blockers", async () => {
131
+ const ctx = { sessionID: "sess-get", directory: tempDir } as any
132
+ await write.execute(
133
+ {
134
+ todos: [
135
+ {
136
+ id: "A1",
137
+ content: "Task content",
138
+ description: "More details",
139
+ status: "ready",
140
+ priority: "HIGH",
141
+ blockedBy: ["B1"],
142
+ },
143
+ {
144
+ id: "B1",
145
+ content: "Blocker task",
146
+ status: "done",
147
+ priority: "LOW",
148
+ blockedBy: ["C1"],
149
+ },
150
+ {
151
+ id: "C1",
152
+ content: "Root blocker",
153
+ status: "pending",
154
+ priority: "MED",
155
+ },
156
+ ],
157
+ },
158
+ ctx
159
+ )
160
+
161
+ const out = await read_by_id.execute({ id: "A1" }, ctx)
162
+ expect(out).toContain("id: A1")
163
+ expect(out).toContain("content:")
164
+ expect(out).toContain("Task content")
165
+ expect(out).toContain("blockedBy: B1")
166
+ expect(out).toContain("description:")
167
+ expect(out).toContain("More details")
168
+
169
+ expect(out).toContain("blockedBy (resolved):")
170
+ expect(out).toContain("- B1")
171
+ expect(out).toContain("- C1")
172
+ })
173
+
174
+ it("read_by_id returns not found", async () => {
175
+ const ctx = { sessionID: "sess-miss", directory: tempDir } as any
176
+ const out = await read_by_id.execute({ id: "NOPE" }, ctx)
177
+ expect(out).toContain("not found")
178
+ })
102
179
  })
@@ -9,10 +9,16 @@ import type { Plugin } from "@opencode-ai/plugin"
9
9
  export const UsethisTodoPublish: Plugin = async ({ client }) => {
10
10
  return {
11
11
  "tool.execute.after": async (input, output) => {
12
- if (input.tool !== "usethis_todo_write" && input.tool !== "usethis_todo_update") return
12
+ if (
13
+ input.tool !== "usethis_todo_write"
14
+ && input.tool !== "usethis_todo_update"
15
+ && input.tool !== "usethis_todo_read_five"
16
+ && input.tool !== "usethis_todo_read"
17
+ && input.tool !== "usethis_todo_read_by_id"
18
+ ) return
13
19
 
14
20
  const text = [
15
- `## TODO`,
21
+ `## TODO`,
16
22
  // `session: ${input.sessionID}`,
17
23
  "",
18
24
  output.output
@@ -1,16 +1,17 @@
1
1
  /**
2
2
  * TODO Tool with Dependencies & Priority — v3 (dual storage)
3
- *
3
+ *
4
4
  * 4 commands:
5
5
  * usethis_todo_write({ todos: [...] }) - create/update TODO list
6
6
  * usethis_todo_read() - read TODO with graph analysis
7
- * usethis_todo_read_next_five() - get next 5 available tasks
7
+ * usethis_todo_read_five() - get next 5 available tasks
8
+ * usethis_todo_read_by_id() - get next 5 available tasks
8
9
  * usethis_todo_update(id, field, value) - update any task field
9
- *
10
+ *
10
11
  * Storage:
11
12
  * Enhanced: .opencode/session-todos/{sid}.json (title, blockedBy, graph)
12
13
  * Native: ~/.local/share/opencode/storage/todo/{sid}.json (TUI display)
13
- *
14
+ *
14
15
  * Features:
15
16
  * - Hierarchical IDs: E01-S01-T01
16
17
  * - Dependencies: blockedBy field
@@ -30,7 +31,8 @@ import fs from "fs/promises"
30
31
 
31
32
  interface Todo {
32
33
  id: string // E01-S01-T01
33
- content: string // Full task description
34
+ content: string // Short task summary
35
+ description?: string // Full task description (optional)
34
36
  status: string // pending | ready | in_progress | waiting_review | done | cancelled
35
37
  priority: string // CRIT | HIGH | MED | LOW
36
38
  blockedBy?: string[] // IDs of blocking tasks
@@ -115,12 +117,13 @@ function toNative(todo: Todo): NativeTodo {
115
117
  MED: "medium",
116
118
  LOW: "low",
117
119
  }
118
-
120
+
119
121
  const deps = todo.blockedBy?.length ? ` [← ${todo.blockedBy.join(", ")}]` : ""
122
+ const desc = todo.description?.trim() ? ` — ${todo.description.trim()}` : ""
120
123
 
121
124
  return {
122
125
  id: todo.id,
123
- content: `${todo.content}${deps}`,
126
+ content: `${todo.content}${desc}${deps}`,
124
127
  status: statusMap[todo.status] || "pending",
125
128
  priority: prioMap[todo.priority] || "medium",
126
129
  }
@@ -139,16 +142,16 @@ async function writeTodos(todos: Todo[], sid: string, directory?: string): Promi
139
142
  const enhancedPath = getEnhancedPath(sid, directory)
140
143
  await fs.mkdir(path.dirname(enhancedPath), { recursive: true })
141
144
  await fs.writeFile(enhancedPath, JSON.stringify(todos, null, 2), "utf-8")
142
-
145
+
143
146
  // 2. Native storage (for TUI display)
144
147
  const nativeTodos = todos.map(toNative)
145
148
  try {
146
149
  const nativePaths = await getNativePaths(sid)
147
150
  await Promise.allSettled(
148
- nativePaths.map(async (nativePath) => {
149
- await fs.mkdir(path.dirname(nativePath), { recursive: true })
150
- await fs.writeFile(nativePath, JSON.stringify(nativeTodos, null, 2), "utf-8")
151
- }),
151
+ nativePaths.map(async (nativePath) => {
152
+ await fs.mkdir(path.dirname(nativePath), { recursive: true })
153
+ await fs.writeFile(nativePath, JSON.stringify(nativeTodos, null, 2), "utf-8")
154
+ }),
152
155
  )
153
156
  } catch {
154
157
  // Native write failure is non-fatal
@@ -162,7 +165,7 @@ async function writeTodos(todos: Todo[], sid: string, directory?: string): Promi
162
165
  function analyzeGraph(todos: Todo[]): TodoGraph {
163
166
  const blocked: Record<string, string[]> = {}
164
167
  const availableTodos: Todo[] = []
165
-
168
+
166
169
  for (const todo of todos) {
167
170
  if (todo.status !== "ready") continue
168
171
  const activeBlockers = (todo.blockedBy || []).filter(id => {
@@ -175,11 +178,11 @@ function analyzeGraph(todos: Todo[]): TodoGraph {
175
178
  blocked[todo.id] = activeBlockers
176
179
  }
177
180
  }
178
-
181
+
179
182
  const P: Record<string, number> = { CRIT: 0, HIGH: 1, MED: 2, LOW: 3 }
180
183
  availableTodos.sort((a, b) => (P[a.priority] ?? 2) - (P[b.priority] ?? 2))
181
184
  const available = availableTodos.map(t => t.id)
182
-
185
+
183
186
  // Parallel groups
184
187
  const parallel: string[][] = []
185
188
  const seen = new Set<string>()
@@ -198,7 +201,7 @@ function analyzeGraph(todos: Todo[]): TodoGraph {
198
201
  }
199
202
  if (group.length > 0) parallel.push(group)
200
203
  }
201
-
204
+
202
205
  return { todos, available, parallel, blocked }
203
206
  }
204
207
 
@@ -214,42 +217,45 @@ function formatGraph(graph: TodoGraph): string {
214
217
  const total = todos.length
215
218
  const done = todos.filter(t => t.status === "done").length
216
219
  const wip = todos.filter(t => t.status === "in_progress").length
217
-
220
+
218
221
  const lines: string[] = [`═══ TODO Graph [${done}/${total} done, ${wip} in progress] ═══`, ""]
219
-
222
+
220
223
  lines.push("All Tasks:")
221
224
  for (const t of todos) {
222
225
  const deps = t.blockedBy?.length ? ` ← ${t.blockedBy.join(", ")}` : ""
223
- lines.push(` ${SI(t.status)} ${PE(t.priority)} ${t.id}: ${t.content}${deps}`)
226
+ const desc = t.description?.trim() ? `${t.description.trim()}` : ""
227
+ lines.push(` ${SI(t.status)} ${PE(t.priority)} ${t.id}: ${t.content}${desc}${deps}`)
224
228
  }
225
229
  lines.push("")
226
-
230
+
227
231
  if (graph.available.length > 0) {
228
232
  lines.push("Available Now:")
229
233
  for (const id of graph.available) {
230
234
  const t = todos.find(x => x.id === id)
231
- lines.push(` ${PE(t?.priority)} ${id}: ${t?.content}`)
235
+ const desc = t?.description?.trim() ? ` ${t.description.trim()}` : ""
236
+ lines.push(` → ${PE(t?.priority)} ${id}: ${t?.content}${desc}`)
232
237
  }
233
238
  } else {
234
239
  lines.push("Available Now: none")
235
240
  }
236
241
  lines.push("")
237
-
242
+
238
243
  const multi = graph.parallel.filter(g => g.length > 1)
239
244
  if (multi.length > 0) {
240
245
  lines.push("Parallel Groups:")
241
246
  multi.forEach((g, i) => lines.push(` Group ${i + 1}: ${g.join(", ")}`))
242
247
  lines.push("")
243
248
  }
244
-
249
+
245
250
  if (Object.keys(graph.blocked).length > 0) {
246
251
  lines.push("Blocked:")
247
252
  for (const [id, blockers] of Object.entries(graph.blocked)) {
248
253
  const t = todos.find(x => x.id === id)
249
- lines.push(` ⊗ ${id}: ${t?.content} waiting: ${blockers.join(", ")}`)
254
+ const desc = t?.description?.trim() ? ` ${t.description.trim()}` : ""
255
+ lines.push(` ⊗ ${id}: ${t?.content}${desc} ← waiting: ${blockers.join(", ")}`)
250
256
  }
251
257
  }
252
-
258
+
253
259
  return lines.join("\n")
254
260
  }
255
261
 
@@ -258,27 +264,28 @@ function formatGraph(graph: TodoGraph): string {
258
264
  // ============================================================================
259
265
 
260
266
  export const write = tool({
261
- description: "Create or update TODO list. TODOv2",
267
+ description: "Create or update TODO list. TODOv2 (Prefer this instead of TODO)",
262
268
  args: {
263
269
  todos: tool.schema.array(
264
- tool.schema.object({
265
- id: tool.schema.string().describe("Task ID in concat format: E01-S01-T01"),
266
- content: tool.schema.string().describe("Full task description"),
267
- status: tool.schema.string().describe("pending | ready | in_progress | waiting_review | done | cancelled"),
268
- priority: tool.schema.string().describe("CRIT | HIGH | MED | LOW"),
269
- blockedBy: tool.schema.array(tool.schema.string()).optional().describe("IDs of blocking tasks"),
270
- })
270
+ tool.schema.object({
271
+ id: tool.schema.string().describe("Task ID in concat format: E01-S01-T01"),
272
+ content: tool.schema.string().describe("Short task summary"),
273
+ description: tool.schema.string().optional().describe("Full task description"),
274
+ status: tool.schema.string().describe("pending | ready | in_progress | waiting_review | done | cancelled"),
275
+ priority: tool.schema.string().describe("CRIT | HIGH | MED | LOW"),
276
+ blockedBy: tool.schema.array(tool.schema.string()).optional().describe("IDs of blocking tasks"),
277
+ })
271
278
  ).describe("Array of todos"),
272
279
  },
273
280
  async execute(args, context) {
274
281
  const now = Date.now()
275
- const todos = args.todos.map(t => ({ ...t, createdAt: t.createdAt || now, updatedAt: now }))
282
+ const todos = args.todos.map((t: any) => ({ ...t, createdAt: t.createdAt || now, updatedAt: now }))
276
283
  await writeTodos(todos, context.sessionID, context.directory)
277
284
  return formatGraph(analyzeGraph(todos))
278
285
  },
279
286
  })
280
287
 
281
- export const read_next_five = tool({
288
+ export const read_five = tool({
282
289
  description: "Read current TODO list. Shows Next 5 tasks.",
283
290
  args: {},
284
291
  async execute(_args, context) {
@@ -290,7 +297,8 @@ export const read_next_five = tool({
290
297
  for (const id of next5) {
291
298
  const t = graph.todos.find(x => x.id === id)
292
299
  if (t) {
293
- lines.push(`${PE(t.priority)} ${id}: ${t.content}`)
300
+ const desc = t.description?.trim() ? `\n ${t.description.trim()}` : ""
301
+ lines.push(`${PE(t.priority)} ${id}: ${t.content}${desc}`)
294
302
  lines.push("")
295
303
  }
296
304
  }
@@ -309,17 +317,88 @@ export const read = tool({
309
317
  },
310
318
  })
311
319
 
320
+ export const read_by_id = tool({
321
+ description: "Read task by id.",
322
+ args: {
323
+ id: tool.schema.string().describe("Task ID"),
324
+ },
325
+ async execute(args, context) {
326
+ const todos = await readTodos(context.sessionID, context.directory)
327
+ const todo = todos.find(t => t.id === args.id)
328
+ if (!todo) return `❌ Task ${args.id} not found`
329
+
330
+ const byId = new Map(todos.map(t => [t.id, t]))
331
+
332
+ // Resolve blockers transitively (task -> blockedBy -> blockedBy ...)
333
+ const blockers: Todo[] = []
334
+ const missing: string[] = []
335
+ const seen = new Set<string>()
336
+ const stack = [...(todo.blockedBy || [])]
337
+
338
+ while (stack.length > 0) {
339
+ const id = stack.shift()!
340
+ if (seen.has(id)) continue
341
+ seen.add(id)
342
+
343
+ const t = byId.get(id)
344
+ if (!t) {
345
+ missing.push(id)
346
+ continue
347
+ }
348
+
349
+ blockers.push(t)
350
+ if (t.blockedBy?.length) stack.push(...t.blockedBy)
351
+ }
352
+
353
+ const lines: string[] = []
354
+ lines.push(`id: ${todo.id}`)
355
+ lines.push(`priority: ${todo.priority}`)
356
+ lines.push(`status: ${todo.status}`)
357
+
358
+ if (todo.blockedBy?.length) {
359
+ lines.push(`blockedBy: ${todo.blockedBy.join(", ")}`)
360
+ }
361
+
362
+ lines.push("")
363
+ lines.push("content:")
364
+ lines.push(todo.content)
365
+
366
+ if (todo.description?.trim()) {
367
+ lines.push("")
368
+ lines.push("description:")
369
+ lines.push(todo.description.trim())
370
+ }
371
+
372
+ if (blockers.length) {
373
+ lines.push("")
374
+ lines.push("blockedBy (resolved):")
375
+ for (const b of blockers) {
376
+ const d = b.description?.trim() ? ` — ${b.description.trim()}` : ""
377
+ lines.push(`- ${b.id} [${b.status}] ${b.priority}: ${b.content}${d}`)
378
+ }
379
+ }
380
+
381
+ if (missing.length) {
382
+ lines.push("")
383
+ lines.push(`blockedBy missing: ${missing.join(", ")}`)
384
+ }
385
+
386
+ return lines.join("\n")
387
+ },
388
+ })
389
+
312
390
  export const update = tool({
313
- description: "Update tasks. Same interface as write, but merges by id.",
391
+ description: "Update task(s). Send 1 or many for update",
314
392
  args: {
315
393
  todos: tool.schema.array(
316
- tool.schema.object({
317
- id: tool.schema.string().describe("Task ID in concat format: E01-S01-T01"),
318
- content: tool.schema.string().describe("Full task description"),
319
- status: tool.schema.string().describe("pending | ready | in_progress | waiting_review | done | cancelled"),
320
- priority: tool.schema.string().describe("CRIT | HIGH | MED | LOW"),
321
- blockedBy: tool.schema.array(tool.schema.string()).optional().describe("IDs of blocking tasks"),
322
- })
394
+ tool.schema.object({
395
+ id: tool.schema.string().describe("Task ID in concat format: E01-S01-T01"),
396
+ content: tool.schema.string().describe("Short task summary"),
397
+ description: tool.schema.string().optional().describe("Full task description"),
398
+ status: tool.schema.string().describe("pending | ready | in_progress | waiting_review | done | cancelled"),
399
+ priority: tool.schema.string().describe("CRIT | HIGH | MED | LOW"),
400
+ blockedBy: tool.schema.array(tool.schema.string()).optional().describe("IDs of blocking tasks"),
401
+ })
323
402
  ).describe("Array of todos to update"),
324
403
  },
325
404
  async execute(args, context) {