Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion packages/opencode/src/agent/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,15 @@ If the spec is clear enough to decompose:
"labels": ["module:config", "file:src/config/config.ts"],
"depends_on": [],
"priority": 0
},
{
"title": "Write OAuth2 login handler",
"description": "Implement the login route using the config schema from the previous task",
"acceptance_criteria": "Handler validates token, returns 401 on failure. Tests pass.",
"task_type": "implementation",
"labels": ["module:auth", "file:src/auth/login.ts"],
"depends_on": ["Add OAuth2 config schema"], // use exact title strings from this batch, never numeric indexes
"priority": 1
}
]
}
Expand All @@ -250,7 +259,8 @@ RULES FOR GOOD TASK DECOMPOSITION:
5. Tasks with no shared module:/file: labels can run in parallel
6. Do not create tasks for work not explicitly required by the issue
7. Validate your own output: check that no depends_on creates a cycle before responding
8. Respond with ONLY the JSON object — no markdown, no explanation, no code blocks`,
8. Respond with ONLY the JSON object — no markdown, no explanation, no code blocks
9. depends_on values must be the EXACT title string of another task in this batch — never use numbers, indexes, or abbreviations`,
},
"developer-pipeline": {
name: "developer-pipeline",
Expand Down
2 changes: 1 addition & 1 deletion packages/opencode/src/tasks/composer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const ComposerTasksSchema = z.object({
acceptance_criteria: z.string().min(1),
task_type: z.enum(["implementation", "test", "research"]),
labels: z.array(z.string().max(100)).default([]),
depends_on: z.array(z.string().min(1).max(200)).default([]),
depends_on: z.array(z.string().min(1).max(200)).default([]),
priority: z.union([z.literal(0), z.literal(1), z.literal(2), z.literal(3), z.literal(4)]),
})

Expand Down
52 changes: 52 additions & 0 deletions packages/opencode/test/tasks/composer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -852,4 +852,56 @@ test("runComposer rolls back tasks on partial creation failure", async () => {
}

Store.createTask = originalCreate
})

test("runComposer rejects numeric depends_on values", async () => {
const mockSpawn = async () =>
JSON.stringify({
status: "ready",
tasks: [
{
title: "Task A",
description: "desc",
acceptance_criteria: "criteria",
task_type: "implementation" as const,
labels: ["module:test"],
depends_on: [],
priority: 0,
},
{
title: "Task B",
description: "desc",
acceptance_criteria: "criteria",
task_type: "implementation" as const,
labels: ["module:test"],
depends_on: [1], // numeric — should be rejected
priority: 1,
},
],
})

let threw = false
try {
await runComposer(
{
jobId: "job-1",
projectId: "test-project",
pmSessionId: "session-1",
issueNumber: 123,
issueTitle: "Add feature",
issueBody: "Please add a feature.",
},
mockSpawn,
)
} catch (e) {
threw = true
const msg = (e as Error).message
if (!msg.includes("validation failed")) {
throw new Error(`Expected validation error for numeric depends_on, got: ${msg}`)
}
}

if (!threw) {
throw new Error("Expected runComposer to throw validation error for numeric depends_on")
}
})