feat(task): refactor to Claude Code style individual tools
- Split unified Task tool into individual tools (TaskCreate, TaskGet, TaskList, TaskUpdate) - Update schema to Claude Code field names (subject, blockedBy, blocks, activeForm, owner, metadata) - Add OpenCode Todo API sync layer (todo-sync.ts) - Implement Todo sync on task create/update for continuation enforcement - Add comprehensive tests for all tools (96 tests total) - Update AGENTS.md documentation Breaking Changes: - Field names changed: title→subject, dependsOn→blockedBy, open→pending - Tool names changed: task→task_create, task_get, task_list, task_update Closes: todo-continuation-enforcer now sees Task-created items
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool"
|
||||
import { join } from "path"
|
||||
import { existsSync, readdirSync } from "fs"
|
||||
import type { OhMyOpenCodeConfig } from "../../config/schema"
|
||||
import type { TaskObject, TaskStatus } from "./types"
|
||||
import { TaskObjectSchema } from "./types"
|
||||
import { readJsonSafe } from "../../features/claude-tasks/storage"
|
||||
|
||||
interface TaskSummary {
|
||||
id: string
|
||||
subject: string
|
||||
status: TaskStatus
|
||||
owner?: string
|
||||
blockedBy: string[]
|
||||
}
|
||||
|
||||
export function createTaskList(config: Partial<OhMyOpenCodeConfig>): ToolDefinition {
|
||||
return tool({
|
||||
description: `List all active tasks with summary information.
|
||||
|
||||
Returns tasks excluding completed and deleted statuses by default.
|
||||
For each task's blockedBy field, filters to only include unresolved (non-completed) blockers.
|
||||
Returns summary format: id, subject, status, owner, blockedBy (not full description).`,
|
||||
args: {},
|
||||
execute: async (): Promise<string> => {
|
||||
const tasksConfig = config.sisyphus?.tasks
|
||||
const storagePath = tasksConfig?.storage_path ?? ".sisyphus/tasks"
|
||||
const taskDir = storagePath.startsWith("/") ? storagePath : join(process.cwd(), storagePath)
|
||||
|
||||
if (!existsSync(taskDir)) {
|
||||
return JSON.stringify({ tasks: [] })
|
||||
}
|
||||
|
||||
const files = readdirSync(taskDir)
|
||||
.filter((f) => f.endsWith(".json") && f.startsWith("T-"))
|
||||
.map((f) => f.replace(".json", ""))
|
||||
|
||||
if (files.length === 0) {
|
||||
return JSON.stringify({ tasks: [] })
|
||||
}
|
||||
|
||||
const allTasks: TaskObject[] = []
|
||||
for (const fileId of files) {
|
||||
const task = readJsonSafe(join(taskDir, `${fileId}.json`), TaskObjectSchema)
|
||||
if (task) {
|
||||
allTasks.push(task)
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out completed and deleted tasks
|
||||
const activeTasks = allTasks.filter(
|
||||
(task) => task.status !== "completed" && task.status !== "deleted"
|
||||
)
|
||||
|
||||
// Build summary with filtered blockedBy
|
||||
const summaries: TaskSummary[] = activeTasks.map((task) => {
|
||||
// Filter blockedBy to only include unresolved (non-completed) blockers
|
||||
const unresolvedBlockers = task.blockedBy.filter((blockerId) => {
|
||||
const blockerTask = allTasks.find((t) => t.id === blockerId)
|
||||
// Include if blocker doesn't exist (missing) or if it's not completed
|
||||
return !blockerTask || blockerTask.status !== "completed"
|
||||
})
|
||||
|
||||
return {
|
||||
id: task.id,
|
||||
subject: task.subject,
|
||||
status: task.status,
|
||||
owner: task.owner,
|
||||
blockedBy: unresolvedBlockers,
|
||||
}
|
||||
})
|
||||
|
||||
return JSON.stringify({ tasks: summaries })
|
||||
},
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user