@comfanion/workflow 4.38.2 → 4.38.3-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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.0",
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.0",
3
+ "buildDate": "2026-01-28T10:28:17.932Z",
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, get_by_id } 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,37 @@ describe("usethis_todo tool", () => {
99
102
  expect(output).toContain("Task content")
100
103
  expect(output).toContain("Available Now")
101
104
  })
105
+
106
+ it("get_by_id returns single task", async () => {
107
+ const ctx = { sessionID: "sess-get", directory: tempDir } as any
108
+ await write.execute(
109
+ {
110
+ todos: [
111
+ {
112
+ id: "A1",
113
+ content: "Task content",
114
+ description: "More details",
115
+ status: "ready",
116
+ priority: "HIGH",
117
+ blockedBy: ["B1"],
118
+ },
119
+ ],
120
+ },
121
+ ctx
122
+ )
123
+
124
+ const out = await get_by_id.execute({ id: "A1" }, ctx)
125
+ expect(out).toContain("id: A1")
126
+ expect(out).toContain("content:")
127
+ expect(out).toContain("Task content")
128
+ expect(out).toContain("blockedBy: B1")
129
+ expect(out).toContain("description:")
130
+ expect(out).toContain("More details")
131
+ })
132
+
133
+ it("get_by_id returns not found", async () => {
134
+ const ctx = { sessionID: "sess-miss", directory: tempDir } as any
135
+ const out = await get_by_id.execute({ id: "NOPE" }, ctx)
136
+ expect(out).toContain("not found")
137
+ })
102
138
  })
@@ -30,7 +30,8 @@ import fs from "fs/promises"
30
30
 
31
31
  interface Todo {
32
32
  id: string // E01-S01-T01
33
- content: string // Full task description
33
+ content: string // Short task summary
34
+ description?: string // Full task description (optional)
34
35
  status: string // pending | ready | in_progress | waiting_review | done | cancelled
35
36
  priority: string // CRIT | HIGH | MED | LOW
36
37
  blockedBy?: string[] // IDs of blocking tasks
@@ -117,10 +118,11 @@ function toNative(todo: Todo): NativeTodo {
117
118
  }
118
119
 
119
120
  const deps = todo.blockedBy?.length ? ` [← ${todo.blockedBy.join(", ")}]` : ""
121
+ const desc = todo.description?.trim() ? ` — ${todo.description.trim()}` : ""
120
122
 
121
123
  return {
122
124
  id: todo.id,
123
- content: `${todo.content}${deps}`,
125
+ content: `${todo.content}${desc}${deps}`,
124
126
  status: statusMap[todo.status] || "pending",
125
127
  priority: prioMap[todo.priority] || "medium",
126
128
  }
@@ -220,7 +222,8 @@ function formatGraph(graph: TodoGraph): string {
220
222
  lines.push("All Tasks:")
221
223
  for (const t of todos) {
222
224
  const deps = t.blockedBy?.length ? ` ← ${t.blockedBy.join(", ")}` : ""
223
- lines.push(` ${SI(t.status)} ${PE(t.priority)} ${t.id}: ${t.content}${deps}`)
225
+ const desc = t.description?.trim() ? `${t.description.trim()}` : ""
226
+ lines.push(` ${SI(t.status)} ${PE(t.priority)} ${t.id}: ${t.content}${desc}${deps}`)
224
227
  }
225
228
  lines.push("")
226
229
 
@@ -228,7 +231,8 @@ function formatGraph(graph: TodoGraph): string {
228
231
  lines.push("Available Now:")
229
232
  for (const id of graph.available) {
230
233
  const t = todos.find(x => x.id === id)
231
- lines.push(` ${PE(t?.priority)} ${id}: ${t?.content}`)
234
+ const desc = t?.description?.trim() ? ` ${t.description.trim()}` : ""
235
+ lines.push(` → ${PE(t?.priority)} ${id}: ${t?.content}${desc}`)
232
236
  }
233
237
  } else {
234
238
  lines.push("Available Now: none")
@@ -246,7 +250,8 @@ function formatGraph(graph: TodoGraph): string {
246
250
  lines.push("Blocked:")
247
251
  for (const [id, blockers] of Object.entries(graph.blocked)) {
248
252
  const t = todos.find(x => x.id === id)
249
- lines.push(` ⊗ ${id}: ${t?.content} waiting: ${blockers.join(", ")}`)
253
+ const desc = t?.description?.trim() ? ` ${t.description.trim()}` : ""
254
+ lines.push(` ⊗ ${id}: ${t?.content}${desc} ← waiting: ${blockers.join(", ")}`)
250
255
  }
251
256
  }
252
257
 
@@ -258,12 +263,13 @@ function formatGraph(graph: TodoGraph): string {
258
263
  // ============================================================================
259
264
 
260
265
  export const write = tool({
261
- description: "Create or update TODO list. TODOv2",
266
+ description: "Create or update TODO list. TODOv2 (Prefer this instead of TODO)",
262
267
  args: {
263
268
  todos: tool.schema.array(
264
269
  tool.schema.object({
265
270
  id: tool.schema.string().describe("Task ID in concat format: E01-S01-T01"),
266
- content: tool.schema.string().describe("Full task description"),
271
+ content: tool.schema.string().describe("Short task summary"),
272
+ description: tool.schema.string().optional().describe("Full task description"),
267
273
  status: tool.schema.string().describe("pending | ready | in_progress | waiting_review | done | cancelled"),
268
274
  priority: tool.schema.string().describe("CRIT | HIGH | MED | LOW"),
269
275
  blockedBy: tool.schema.array(tool.schema.string()).optional().describe("IDs of blocking tasks"),
@@ -272,7 +278,7 @@ export const write = tool({
272
278
  },
273
279
  async execute(args, context) {
274
280
  const now = Date.now()
275
- const todos = args.todos.map(t => ({ ...t, createdAt: t.createdAt || now, updatedAt: now }))
281
+ const todos = args.todos.map((t: any) => ({ ...t, createdAt: t.createdAt || now, updatedAt: now }))
276
282
  await writeTodos(todos, context.sessionID, context.directory)
277
283
  return formatGraph(analyzeGraph(todos))
278
284
  },
@@ -290,7 +296,8 @@ export const read_next_five = tool({
290
296
  for (const id of next5) {
291
297
  const t = graph.todos.find(x => x.id === id)
292
298
  if (t) {
293
- lines.push(`${PE(t.priority)} ${id}: ${t.content}`)
299
+ const desc = t.description?.trim() ? `\n ${t.description.trim()}` : ""
300
+ lines.push(`${PE(t.priority)} ${id}: ${t.content}${desc}`)
294
301
  lines.push("")
295
302
  }
296
303
  }
@@ -309,13 +316,37 @@ export const read = tool({
309
316
  },
310
317
  })
311
318
 
319
+ export const get_by_id = tool({
320
+ description: "Get one task by id.",
321
+ args: {
322
+ id: tool.schema.string().describe("Task ID"),
323
+ },
324
+ async execute(args, context) {
325
+ const todos = await readTodos(context.sessionID, context.directory)
326
+ const todo = todos.find(t => t.id === args.id)
327
+ if (!todo) return `❌ Task ${args.id} not found`
328
+
329
+ const deps = todo.blockedBy?.length ? `\nblockedBy: ${todo.blockedBy.join(", ")}` : ""
330
+ const desc = todo.description?.trim() ? `\n\ndescription:\n${todo.description.trim()}` : ""
331
+ return [
332
+ `id: ${todo.id}`,
333
+ `priority: ${todo.priority}`,
334
+ `status: ${todo.status}`,
335
+ deps,
336
+ `\ncontent:\n${todo.content}`,
337
+ desc,
338
+ ].filter(Boolean).join("\n")
339
+ },
340
+ })
341
+
312
342
  export const update = tool({
313
343
  description: "Update tasks. Same interface as write, but merges by id.",
314
344
  args: {
315
345
  todos: tool.schema.array(
316
346
  tool.schema.object({
317
347
  id: tool.schema.string().describe("Task ID in concat format: E01-S01-T01"),
318
- content: tool.schema.string().describe("Full task description"),
348
+ content: tool.schema.string().describe("Short task summary"),
349
+ description: tool.schema.string().optional().describe("Full task description"),
319
350
  status: tool.schema.string().describe("pending | ready | in_progress | waiting_review | done | cancelled"),
320
351
  priority: tool.schema.string().describe("CRIT | HIGH | MED | LOW"),
321
352
  blockedBy: tool.schema.array(tool.schema.string()).optional().describe("IDs of blocking tasks"),