@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 +1 -1
- package/src/build-info.json +2 -2
- package/src/opencode/agents/architect.md +1 -1
- package/src/opencode/agents/dev.md +2 -18
- package/src/opencode/agents/pm.md +1 -1
- package/src/opencode/config.yaml +1 -1
- package/src/opencode/plugins/__tests__/usethis-todo.test.ts +40 -4
- package/src/opencode/tools/usethis_todo.ts +41 -10
package/package.json
CHANGED
package/src/build-info.json
CHANGED
|
@@ -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:
|
|
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>
|
package/src/opencode/config.yaml
CHANGED
|
@@ -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:
|
|
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 //
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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("
|
|
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
|
-
|
|
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("
|
|
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"),
|