feat(task-system): add experimental task system with Claude Code spec alignment (#1415)
* feat(hooks): add tasks-todowrite-disabler hook to block TodoRead/TodoWrite Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * feat(task-tools): add parallel execution guidance to descriptions Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * refactor(index): migrate task system to experimental.task_system flag Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * docs: update AGENTS.md for experimental task system Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * fix(task-tests): align test field names with Claude Code spec (subject, blockedBy, addBlockedBy) * fix: address Cubic review feedback Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * fix: add optional chaining for tasksTodowriteDisabler null check --------- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -253,6 +253,8 @@ export const ExperimentalConfigSchema = z.object({
|
||||
truncate_all_tool_outputs: z.boolean().optional(),
|
||||
/** Dynamic context pruning configuration */
|
||||
dynamic_context_pruning: DynamicContextPruningConfigSchema.optional(),
|
||||
/** Enable experimental task system for Todowrite disabler hook */
|
||||
task_system: z.boolean().optional(),
|
||||
})
|
||||
|
||||
export const SkillSourceSchema = z.union([
|
||||
|
||||
@@ -37,3 +37,4 @@ export { createStopContinuationGuardHook, type StopContinuationGuard } from "./s
|
||||
export { createCompactionContextInjector, type SummarizeContext } from "./compaction-context-injector";
|
||||
export { createUnstableAgentBabysitterHook } from "./unstable-agent-babysitter";
|
||||
export { createPreemptiveCompactionHook } from "./preemptive-compaction";
|
||||
export { createTasksTodowriteDisablerHook } from "./tasks-todowrite-disabler";
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
export const HOOK_NAME = "tasks-todowrite-disabler"
|
||||
export const BLOCKED_TOOLS = ["TodoWrite", "TodoRead"]
|
||||
export const REPLACEMENT_MESSAGE = `TodoRead/TodoWrite are disabled because experimental.task_system is enabled.
|
||||
Use the new task tools instead:
|
||||
- TaskCreate: Create new tasks with auto-generated IDs
|
||||
- TaskUpdate: Update task status, add dependencies
|
||||
- TaskList: List active tasks with dependency info
|
||||
- TaskGet: Get full task details
|
||||
|
||||
IMPORTANT: 1 task = 1 delegate_task. Maximize parallel execution by running independent tasks concurrently.`
|
||||
@@ -0,0 +1,137 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
|
||||
const { createTasksTodowriteDisablerHook } = await import("./index")
|
||||
|
||||
describe("tasks-todowrite-disabler", () => {
|
||||
describe("when experimental.task_system is enabled", () => {
|
||||
test("should block TodoWrite tool", async () => {
|
||||
// given
|
||||
const hook = createTasksTodowriteDisablerHook({ experimental: { task_system: true } })
|
||||
const input = {
|
||||
tool: "TodoWrite",
|
||||
sessionID: "test-session",
|
||||
callID: "call-1",
|
||||
}
|
||||
const output = {
|
||||
args: {},
|
||||
}
|
||||
|
||||
// when / then
|
||||
await expect(
|
||||
hook["tool.execute.before"](input, output)
|
||||
).rejects.toThrow("TodoRead/TodoWrite are disabled")
|
||||
})
|
||||
|
||||
test("should block TodoRead tool", async () => {
|
||||
// given
|
||||
const hook = createTasksTodowriteDisablerHook({ experimental: { task_system: true } })
|
||||
const input = {
|
||||
tool: "TodoRead",
|
||||
sessionID: "test-session",
|
||||
callID: "call-1",
|
||||
}
|
||||
const output = {
|
||||
args: {},
|
||||
}
|
||||
|
||||
// when / then
|
||||
await expect(
|
||||
hook["tool.execute.before"](input, output)
|
||||
).rejects.toThrow("TodoRead/TodoWrite are disabled")
|
||||
})
|
||||
|
||||
test("should not block other tools", async () => {
|
||||
// given
|
||||
const hook = createTasksTodowriteDisablerHook({ experimental: { task_system: true } })
|
||||
const input = {
|
||||
tool: "Read",
|
||||
sessionID: "test-session",
|
||||
callID: "call-1",
|
||||
}
|
||||
const output = {
|
||||
args: {},
|
||||
}
|
||||
|
||||
// when / then
|
||||
await expect(
|
||||
hook["tool.execute.before"](input, output)
|
||||
).resolves.toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe("when experimental.task_system is disabled or undefined", () => {
|
||||
test("should not block TodoWrite when flag is false", async () => {
|
||||
// given
|
||||
const hook = createTasksTodowriteDisablerHook({ experimental: { task_system: false } })
|
||||
const input = {
|
||||
tool: "TodoWrite",
|
||||
sessionID: "test-session",
|
||||
callID: "call-1",
|
||||
}
|
||||
const output = {
|
||||
args: {},
|
||||
}
|
||||
|
||||
// when / then
|
||||
await expect(
|
||||
hook["tool.execute.before"](input, output)
|
||||
).resolves.toBeUndefined()
|
||||
})
|
||||
|
||||
test("should not block TodoWrite when experimental is undefined", async () => {
|
||||
// given
|
||||
const hook = createTasksTodowriteDisablerHook({})
|
||||
const input = {
|
||||
tool: "TodoWrite",
|
||||
sessionID: "test-session",
|
||||
callID: "call-1",
|
||||
}
|
||||
const output = {
|
||||
args: {},
|
||||
}
|
||||
|
||||
// when / then
|
||||
await expect(
|
||||
hook["tool.execute.before"](input, output)
|
||||
).resolves.toBeUndefined()
|
||||
})
|
||||
|
||||
test("should not block TodoRead when flag is false", async () => {
|
||||
// given
|
||||
const hook = createTasksTodowriteDisablerHook({ experimental: { task_system: false } })
|
||||
const input = {
|
||||
tool: "TodoRead",
|
||||
sessionID: "test-session",
|
||||
callID: "call-1",
|
||||
}
|
||||
const output = {
|
||||
args: {},
|
||||
}
|
||||
|
||||
// when / then
|
||||
await expect(
|
||||
hook["tool.execute.before"](input, output)
|
||||
).resolves.toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe("error message content", () => {
|
||||
test("should include replacement message with task tools info", async () => {
|
||||
// given
|
||||
const hook = createTasksTodowriteDisablerHook({ experimental: { task_system: true } })
|
||||
const input = {
|
||||
tool: "TodoWrite",
|
||||
sessionID: "test-session",
|
||||
callID: "call-1",
|
||||
}
|
||||
const output = {
|
||||
args: {},
|
||||
}
|
||||
|
||||
// when / then
|
||||
await expect(
|
||||
hook["tool.execute.before"](input, output)
|
||||
).rejects.toThrow(/TaskCreate|TaskUpdate|TaskList|TaskGet/)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,29 @@
|
||||
import { BLOCKED_TOOLS, REPLACEMENT_MESSAGE } from "./constants";
|
||||
|
||||
export interface TasksTodowriteDisablerConfig {
|
||||
experimental?: {
|
||||
task_system?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export function createTasksTodowriteDisablerHook(
|
||||
config: TasksTodowriteDisablerConfig,
|
||||
) {
|
||||
const isTaskSystemEnabled = config.experimental?.task_system ?? false;
|
||||
|
||||
return {
|
||||
"tool.execute.before": async (
|
||||
input: { tool: string; sessionID: string; callID: string },
|
||||
output: { args: Record<string, unknown> },
|
||||
) => {
|
||||
if (!isTaskSystemEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toolName = input.tool as string;
|
||||
if (BLOCKED_TOOLS.some((blocked) => blocked.toLowerCase() === toolName.toLowerCase())) {
|
||||
throw new Error(REPLACEMENT_MESSAGE);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
+11
-3
@@ -36,6 +36,7 @@ import {
|
||||
createCompactionContextInjector,
|
||||
createUnstableAgentBabysitterHook,
|
||||
createPreemptiveCompactionHook,
|
||||
createTasksTodowriteDisablerHook,
|
||||
} from "./hooks";
|
||||
import {
|
||||
contextCollector,
|
||||
@@ -269,6 +270,12 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
||||
? createSisyphusJuniorNotepadHook(ctx)
|
||||
: null;
|
||||
|
||||
const tasksTodowriteDisabler = isHookEnabled("tasks-todowrite-disabler")
|
||||
? createTasksTodowriteDisablerHook({
|
||||
experimental: pluginConfig.experimental,
|
||||
})
|
||||
: null;
|
||||
|
||||
const questionLabelTruncator = createQuestionLabelTruncatorHook();
|
||||
const subagentQuestionBlocker = createSubagentQuestionBlockerHook();
|
||||
|
||||
@@ -464,8 +471,8 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
||||
modelCacheState,
|
||||
});
|
||||
|
||||
const newTaskSystemEnabled = pluginConfig.new_task_system_enabled ?? false;
|
||||
const taskToolsRecord: Record<string, ToolDefinition> = newTaskSystemEnabled
|
||||
const taskSystemEnabled = pluginConfig.experimental?.task_system ?? false;
|
||||
const taskToolsRecord: Record<string, ToolDefinition> = taskSystemEnabled
|
||||
? {
|
||||
task_create: createTaskCreateTool(pluginConfig, ctx),
|
||||
task_get: createTaskGetTool(pluginConfig),
|
||||
@@ -714,10 +721,11 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
||||
await questionLabelTruncator["tool.execute.before"]?.(input, output);
|
||||
await claudeCodeHooks["tool.execute.before"](input, output);
|
||||
await nonInteractiveEnv?.["tool.execute.before"](input, output);
|
||||
await commentChecker?.["tool.execute.before"](input, output);
|
||||
await commentChecker?.["tool.execute.before"]?.(input, output);
|
||||
await directoryAgentsInjector?.["tool.execute.before"]?.(input, output);
|
||||
await directoryReadmeInjector?.["tool.execute.before"]?.(input, output);
|
||||
await rulesInjector?.["tool.execute.before"]?.(input, output);
|
||||
await tasksTodowriteDisabler?.["tool.execute.before"]?.(input, output);
|
||||
await prometheusMdOnly?.["tool.execute.before"]?.(input, output);
|
||||
await sisyphusJuniorNotepad?.["tool.execute.before"]?.(input, output);
|
||||
await atlasHook?.["tool.execute.before"]?.(input, output);
|
||||
|
||||
@@ -123,7 +123,6 @@ export function loadPluginConfig(
|
||||
|
||||
config = {
|
||||
...config,
|
||||
new_task_system_enabled: config.new_task_system_enabled ?? false,
|
||||
};
|
||||
|
||||
log("Final merged config", {
|
||||
|
||||
@@ -405,7 +405,7 @@ export function createConfigHandler(deps: ConfigHandlerDeps) {
|
||||
LspCodeActionResolve: false,
|
||||
"task_*": false,
|
||||
teammate: false,
|
||||
...(pluginConfig.new_task_system_enabled ? { todowrite: false, todoread: false } : {}),
|
||||
...(pluginConfig.experimental?.task_system ? { todowrite: false, todoread: false } : {}),
|
||||
};
|
||||
|
||||
type AgentWithPermission = { permission?: Record<string, unknown> };
|
||||
|
||||
@@ -213,6 +213,13 @@ export function migrateConfigFile(configPath: string, rawConfig: Record<string,
|
||||
}
|
||||
}
|
||||
|
||||
if (rawConfig.experimental && typeof rawConfig.experimental === "object") {
|
||||
const exp = rawConfig.experimental as Record<string, unknown>
|
||||
if ("task_system" in exp && exp.task_system !== undefined) {
|
||||
needsWrite = true
|
||||
}
|
||||
}
|
||||
|
||||
if (needsWrite) {
|
||||
try {
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, "-")
|
||||
|
||||
@@ -16,12 +16,19 @@ export function createTaskCreateTool(
|
||||
config: Partial<OhMyOpenCodeConfig>,
|
||||
ctx?: PluginInput,
|
||||
): ToolDefinition {
|
||||
return tool({
|
||||
description: `Create a new task with auto-generated ID and threadID recording.
|
||||
return tool({
|
||||
description: `Create a new task with auto-generated ID and threadID recording.
|
||||
|
||||
Auto-generates T-{uuid} ID, records threadID from context, sets status to "pending".
|
||||
Returns minimal response with task ID and subject.`,
|
||||
args: {
|
||||
Returns minimal response with task ID and subject.
|
||||
|
||||
**IMPORTANT - Dependency Planning for Parallel Execution:**
|
||||
Use \`blockedBy\` to specify task IDs that must complete before this task can start.
|
||||
Calculate dependencies carefully to maximize parallel execution:
|
||||
- Tasks with no dependencies can run simultaneously
|
||||
- Only block a task if it truly depends on another's output
|
||||
- Minimize dependency chains to reduce sequential bottlenecks`,
|
||||
args: {
|
||||
subject: tool.schema.string().describe("Task subject (required)"),
|
||||
description: tool.schema.string().optional().describe("Task description"),
|
||||
activeForm: tool.schema
|
||||
|
||||
@@ -70,7 +70,10 @@ Returns summary format: id, subject, status, owner, blockedBy (not full descript
|
||||
}
|
||||
})
|
||||
|
||||
return JSON.stringify({ tasks: summaries })
|
||||
return JSON.stringify({
|
||||
tasks: summaries,
|
||||
reminder: "1 task = 1 delegate_task. Maximize parallel execution by running independent tasks (tasks with empty blockedBy) concurrently."
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -23,14 +23,18 @@ export function createTaskUpdateTool(
|
||||
config: Partial<OhMyOpenCodeConfig>,
|
||||
ctx?: PluginInput,
|
||||
): ToolDefinition {
|
||||
return tool({
|
||||
description: `Update an existing task with new values.
|
||||
return tool({
|
||||
description: `Update an existing task with new values.
|
||||
|
||||
Supports updating: subject, description, status, activeForm, owner, metadata.
|
||||
For blocks/blockedBy: use addBlocks/addBlockedBy to append (additive, not replacement).
|
||||
For metadata: merge with existing, set key to null to delete.
|
||||
Syncs to OpenCode Todo API after update.`,
|
||||
args: {
|
||||
Syncs to OpenCode Todo API after update.
|
||||
|
||||
**IMPORTANT - Dependency Management:**
|
||||
Use \`addBlockedBy\` to declare dependencies on other tasks.
|
||||
Properly managed dependencies enable maximum parallel execution.`,
|
||||
args: {
|
||||
id: tool.schema.string().describe("Task ID (required)"),
|
||||
subject: tool.schema.string().optional().describe("Task subject"),
|
||||
description: tool.schema.string().optional().describe("Task description"),
|
||||
|
||||
+45
-45
@@ -7,7 +7,7 @@ import { createTask } from "./task"
|
||||
const TEST_STORAGE = ".test-task-tool"
|
||||
const TEST_DIR = join(process.cwd(), TEST_STORAGE)
|
||||
const TEST_CONFIG = {
|
||||
new_task_system_enabled: true,
|
||||
experimental: { task_system: true },
|
||||
sisyphus: {
|
||||
tasks: {
|
||||
storage_path: TEST_STORAGE,
|
||||
@@ -35,10 +35,10 @@ describe("task_tool", () => {
|
||||
taskTool = createTask(TEST_CONFIG)
|
||||
})
|
||||
|
||||
async function createTestTask(title: string, overrides: Partial<Parameters<typeof taskTool.execute>[0]> = {}): Promise<string> {
|
||||
async function createTestTask(subject: string, overrides: Partial<Parameters<typeof taskTool.execute>[0]> = {}): Promise<string> {
|
||||
const args = {
|
||||
action: "create" as const,
|
||||
title,
|
||||
subject,
|
||||
...overrides,
|
||||
}
|
||||
const resultStr = await taskTool.execute(args, TEST_CONTEXT)
|
||||
@@ -61,7 +61,7 @@ describe("task_tool", () => {
|
||||
//#given
|
||||
const args = {
|
||||
action: "create" as const,
|
||||
title: "Implement authentication",
|
||||
subject: "Implement authentication",
|
||||
}
|
||||
|
||||
//#when
|
||||
@@ -71,15 +71,15 @@ describe("task_tool", () => {
|
||||
//#then
|
||||
expect(result).toHaveProperty("task")
|
||||
expect(result.task).toHaveProperty("id")
|
||||
expect(result.task.title).toBe("Implement authentication")
|
||||
expect(result.task.status).toBe("open")
|
||||
expect(result.task.subject).toBe("Implement authentication")
|
||||
expect(result.task.status).toBe("pending")
|
||||
})
|
||||
|
||||
test("auto-generates T-{uuid} format ID", async () => {
|
||||
//#given
|
||||
const args = {
|
||||
action: "create" as const,
|
||||
title: "Test task",
|
||||
subject: "Test task",
|
||||
}
|
||||
|
||||
//#when
|
||||
@@ -94,7 +94,7 @@ describe("task_tool", () => {
|
||||
//#given
|
||||
const args = {
|
||||
action: "create" as const,
|
||||
title: "Test task",
|
||||
subject: "Test task",
|
||||
}
|
||||
|
||||
//#when
|
||||
@@ -110,7 +110,7 @@ describe("task_tool", () => {
|
||||
//#given
|
||||
const args = {
|
||||
action: "create" as const,
|
||||
title: "Test task",
|
||||
subject: "Test task",
|
||||
}
|
||||
|
||||
//#when
|
||||
@@ -118,14 +118,14 @@ describe("task_tool", () => {
|
||||
const result = JSON.parse(resultStr)
|
||||
|
||||
//#then
|
||||
expect(result.task.status).toBe("open")
|
||||
expect(result.task.status).toBe("pending")
|
||||
})
|
||||
|
||||
test("stores optional description field", async () => {
|
||||
//#given
|
||||
const args = {
|
||||
action: "create" as const,
|
||||
title: "Test task",
|
||||
subject: "Test task",
|
||||
description: "Detailed description of the task",
|
||||
}
|
||||
|
||||
@@ -141,8 +141,8 @@ describe("task_tool", () => {
|
||||
//#given
|
||||
const args = {
|
||||
action: "create" as const,
|
||||
title: "Test task",
|
||||
dependsOn: ["T-dep1", "T-dep2"],
|
||||
subject: "Test task",
|
||||
blockedBy: ["T-dep1", "T-dep2"],
|
||||
}
|
||||
|
||||
//#when
|
||||
@@ -150,14 +150,14 @@ describe("task_tool", () => {
|
||||
const result = JSON.parse(resultStr)
|
||||
|
||||
//#then
|
||||
expect(result.task.dependsOn).toEqual(["T-dep1", "T-dep2"])
|
||||
expect(result.task.blockedBy).toEqual(["T-dep1", "T-dep2"])
|
||||
})
|
||||
|
||||
test("stores parentID when provided", async () => {
|
||||
//#given
|
||||
const args = {
|
||||
action: "create" as const,
|
||||
title: "Subtask",
|
||||
subject: "Subtask",
|
||||
parentID: "T-parent123",
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ describe("task_tool", () => {
|
||||
//#given
|
||||
const args = {
|
||||
action: "create" as const,
|
||||
title: "Test task",
|
||||
subject: "Test task",
|
||||
repoURL: "https://github.com/code-yeongyu/oh-my-opencode",
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ describe("task_tool", () => {
|
||||
//#given
|
||||
const args = {
|
||||
action: "create" as const,
|
||||
title: "Test task",
|
||||
subject: "Test task",
|
||||
}
|
||||
|
||||
//#when
|
||||
@@ -205,7 +205,7 @@ describe("task_tool", () => {
|
||||
//#given
|
||||
const args = {
|
||||
action: "create" as const,
|
||||
title: "Test task",
|
||||
subject: "Test task",
|
||||
}
|
||||
|
||||
//#when
|
||||
@@ -213,7 +213,7 @@ describe("task_tool", () => {
|
||||
const result = JSON.parse(resultStr)
|
||||
|
||||
//#then
|
||||
expect(result.task.dependsOn).toEqual([])
|
||||
expect(result.task.blockedBy).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -398,7 +398,7 @@ describe("task_tool", () => {
|
||||
//#then
|
||||
if (result.task !== null) {
|
||||
expect(result.task).toHaveProperty("id")
|
||||
expect(result.task).toHaveProperty("title")
|
||||
expect(result.task).toHaveProperty("subject")
|
||||
expect(result.task).toHaveProperty("status")
|
||||
expect(result.task).toHaveProperty("threadID")
|
||||
}
|
||||
@@ -416,7 +416,7 @@ describe("task_tool", () => {
|
||||
const args = {
|
||||
action: "update" as const,
|
||||
id: testId,
|
||||
title: "Updated title",
|
||||
subject: "Updated subject",
|
||||
}
|
||||
|
||||
//#when
|
||||
@@ -425,7 +425,7 @@ describe("task_tool", () => {
|
||||
|
||||
//#then
|
||||
expect(result).toHaveProperty("task")
|
||||
expect(result.task.title).toBe("Updated title")
|
||||
expect(result.task.subject).toBe("Updated subject")
|
||||
})
|
||||
|
||||
test("updates task description", async () => {
|
||||
@@ -462,13 +462,13 @@ describe("task_tool", () => {
|
||||
expect(result.task.status).toBe("in_progress")
|
||||
})
|
||||
|
||||
test("updates dependsOn array", async () => {
|
||||
test("updates blockedBy array additively", async () => {
|
||||
//#given
|
||||
const testId = await createTestTask("Test task")
|
||||
const args = {
|
||||
action: "update" as const,
|
||||
id: testId,
|
||||
dependsOn: ["T-dep1", "T-dep2", "T-dep3"],
|
||||
addBlockedBy: ["T-dep1", "T-dep2", "T-dep3"],
|
||||
}
|
||||
|
||||
//#when
|
||||
@@ -476,7 +476,7 @@ describe("task_tool", () => {
|
||||
const result = JSON.parse(resultStr)
|
||||
|
||||
//#then
|
||||
expect(result.task.dependsOn).toEqual(["T-dep1", "T-dep2", "T-dep3"])
|
||||
expect(result.task.blockedBy).toEqual(["T-dep1", "T-dep2", "T-dep3"])
|
||||
})
|
||||
|
||||
test("returns error for non-existent task", async () => {
|
||||
@@ -484,7 +484,7 @@ describe("task_tool", () => {
|
||||
const args = {
|
||||
action: "update" as const,
|
||||
id: "T-nonexistent",
|
||||
title: "New title",
|
||||
subject: "New subject",
|
||||
}
|
||||
|
||||
//#when
|
||||
@@ -501,7 +501,7 @@ describe("task_tool", () => {
|
||||
const args = {
|
||||
action: "update" as const,
|
||||
id: "../package",
|
||||
title: "New title",
|
||||
subject: "New subject",
|
||||
}
|
||||
|
||||
//#when
|
||||
@@ -519,7 +519,7 @@ describe("task_tool", () => {
|
||||
const args = {
|
||||
action: "update" as const,
|
||||
id: "T-nonexistent",
|
||||
title: "New title",
|
||||
subject: "New subject",
|
||||
}
|
||||
|
||||
//#when
|
||||
@@ -537,7 +537,7 @@ describe("task_tool", () => {
|
||||
const args = {
|
||||
action: "update" as const,
|
||||
id: testId,
|
||||
title: "Updated",
|
||||
subject: "Updated",
|
||||
}
|
||||
|
||||
//#when
|
||||
@@ -555,7 +555,7 @@ describe("task_tool", () => {
|
||||
const args = {
|
||||
action: "update" as const,
|
||||
id: testId,
|
||||
title: "New title",
|
||||
subject: "New subject",
|
||||
description: "New description",
|
||||
status: "completed" as const,
|
||||
}
|
||||
@@ -565,7 +565,7 @@ describe("task_tool", () => {
|
||||
const result = JSON.parse(resultStr)
|
||||
|
||||
//#then
|
||||
expect(result.task.title).toBe("New title")
|
||||
expect(result.task.subject).toBe("New subject")
|
||||
expect(result.task.description).toBe("New description")
|
||||
expect(result.task.status).toBe("completed")
|
||||
})
|
||||
@@ -668,8 +668,8 @@ describe("task_tool", () => {
|
||||
//#given
|
||||
const args = {
|
||||
action: "create" as const,
|
||||
title: "Task A",
|
||||
dependsOn: ["T-taskB"],
|
||||
subject: "Task A",
|
||||
blockedBy: ["T-taskB"],
|
||||
}
|
||||
|
||||
//#when
|
||||
@@ -685,8 +685,8 @@ describe("task_tool", () => {
|
||||
//#given
|
||||
const args = {
|
||||
action: "create" as const,
|
||||
title: "Task with missing dependency",
|
||||
dependsOn: ["T-nonexistent"],
|
||||
subject: "Task with missing dependency",
|
||||
blockedBy: ["T-nonexistent"],
|
||||
}
|
||||
|
||||
//#when
|
||||
@@ -710,7 +710,7 @@ describe("task_tool", () => {
|
||||
const result = JSON.parse(resultStr)
|
||||
|
||||
//#then
|
||||
const tasksWithNoDeps = result.tasks.filter((t: TaskObject) => t.dependsOn.length === 0)
|
||||
const tasksWithNoDeps = result.tasks.filter((t: TaskObject) => t.blockedBy.length === 0)
|
||||
expect(tasksWithNoDeps.length).toBeGreaterThanOrEqual(0)
|
||||
})
|
||||
|
||||
@@ -748,7 +748,7 @@ describe("task_tool", () => {
|
||||
//#given
|
||||
const args = {
|
||||
action: "create" as const,
|
||||
title: "",
|
||||
subject: "",
|
||||
}
|
||||
|
||||
//#when
|
||||
@@ -762,10 +762,10 @@ describe("task_tool", () => {
|
||||
|
||||
test("handles very long title", async () => {
|
||||
//#given
|
||||
const longTitle = "A".repeat(1000)
|
||||
const longSubject = "A".repeat(1000)
|
||||
const args = {
|
||||
action: "create" as const,
|
||||
title: longTitle,
|
||||
subject: longSubject,
|
||||
}
|
||||
|
||||
//#when
|
||||
@@ -780,7 +780,7 @@ describe("task_tool", () => {
|
||||
//#given
|
||||
const args = {
|
||||
action: "create" as const,
|
||||
title: "Task with special chars: !@#$%^&*()",
|
||||
subject: "Task with special chars: !@#$%^&*()",
|
||||
}
|
||||
|
||||
//#when
|
||||
@@ -795,7 +795,7 @@ describe("task_tool", () => {
|
||||
//#given
|
||||
const args = {
|
||||
action: "create" as const,
|
||||
title: "任務 🚀 Tâche",
|
||||
subject: "任務 🚀 Tâche",
|
||||
}
|
||||
|
||||
//#when
|
||||
@@ -810,9 +810,9 @@ describe("task_tool", () => {
|
||||
//#given
|
||||
const args = {
|
||||
action: "create" as const,
|
||||
title: "Test task",
|
||||
subject: "Test task",
|
||||
description: "Test description",
|
||||
dependsOn: ["T-dep1"],
|
||||
blockedBy: ["T-dep1"],
|
||||
parentID: "T-parent",
|
||||
repoURL: "https://example.com",
|
||||
}
|
||||
@@ -823,10 +823,10 @@ describe("task_tool", () => {
|
||||
|
||||
//#then
|
||||
expect(result.task).toHaveProperty("id")
|
||||
expect(result.task).toHaveProperty("title")
|
||||
expect(result.task).toHaveProperty("subject")
|
||||
expect(result.task).toHaveProperty("description")
|
||||
expect(result.task).toHaveProperty("status")
|
||||
expect(result.task).toHaveProperty("dependsOn")
|
||||
expect(result.task).toHaveProperty("blockedBy")
|
||||
expect(result.task).toHaveProperty("parentID")
|
||||
expect(result.task).toHaveProperty("repoURL")
|
||||
expect(result.task).toHaveProperty("threadID")
|
||||
|
||||
Reference in New Issue
Block a user