diff --git a/apps/server/agent/prompts/detail.ts b/apps/server/agent/prompts/detail.ts index bb16a27..2b20e39 100644 --- a/apps/server/agent/prompts/detail.ts +++ b/apps/server/agent/prompts/detail.ts @@ -13,7 +13,7 @@ ${CODEBASE_EXPLORATION} Write one file per task to \`.cw/output/tasks/{id}.md\`: -- Frontmatter: \`title\`, \`category\` (execute|research|discuss|plan|detail|refine|verify|merge|review), \`type\` (auto|checkpoint:human-verify|checkpoint:decision|checkpoint:human-action), \`dependencies\` (list of task IDs that must complete before this task can start) +- Frontmatter: \`title\`, \`category\` (execute|research|discuss|plan|detail|refine|verify|merge|review), \`dependencies\` (list of task IDs that must complete before this task can start) - Body: Detailed task description @@ -92,14 +92,6 @@ Each task is handled by a separate agent that must load the full codebase contex Bundle related changes into one task. "Add user validation" + "Add user API route" + "Add user route tests" is ONE task ("Add user creation endpoint with validation and tests"), not three. - -- \`checkpoint:human-verify\`: Visual changes, migrations, API contracts -- \`checkpoint:decision\`: Architecture choices affecting multiple phases -- \`checkpoint:human-action\`: External setup (DNS, credentials, third-party config) - -~90% of tasks should be \`auto\`. - - - Read ALL \`context/tasks/\` files before generating output - Only create tasks for THIS phase (\`phase.md\`) diff --git a/apps/server/db/repositories/drizzle/task.test.ts b/apps/server/db/repositories/drizzle/task.test.ts index 316027c..5f19065 100644 --- a/apps/server/db/repositories/drizzle/task.test.ts +++ b/apps/server/db/repositories/drizzle/task.test.ts @@ -71,13 +71,13 @@ describe('DrizzleTaskRepository', () => { it('should accept custom type and priority', async () => { const task = await taskRepo.create({ phaseId: testPhaseId, - name: 'Checkpoint Task', - type: 'checkpoint:human-verify', + name: 'High Priority Task', + type: 'auto', priority: 'high', order: 1, }); - expect(task.type).toBe('checkpoint:human-verify'); + expect(task.type).toBe('auto'); expect(task.priority).toBe('high'); }); }); diff --git a/apps/server/db/schema.ts b/apps/server/db/schema.ts index 1e371db..61ae1e2 100644 --- a/apps/server/db/schema.ts +++ b/apps/server/db/schema.ts @@ -137,7 +137,7 @@ export const tasks = sqliteTable('tasks', { name: text('name').notNull(), description: text('description'), type: text('type', { - enum: ['auto', 'checkpoint:human-verify', 'checkpoint:decision', 'checkpoint:human-action'], + enum: ['auto'], }) .notNull() .default('auto'), diff --git a/apps/server/dispatch/manager.ts b/apps/server/dispatch/manager.ts index 4fc7b7e..026be74 100644 --- a/apps/server/dispatch/manager.ts +++ b/apps/server/dispatch/manager.ts @@ -79,7 +79,6 @@ export class DefaultDispatchManager implements DispatchManager { /** * Queue a task for dispatch. * Fetches task dependencies and adds to internal queue. - * Checkpoint tasks are queued but won't auto-dispatch. */ async queue(taskId: string): Promise { // Fetch task to verify it exists and get priority @@ -100,7 +99,7 @@ export class DefaultDispatchManager implements DispatchManager { this.taskQueue.set(taskId, queuedTask); - log.info({ taskId, priority: task.priority, isCheckpoint: this.isCheckpointTask(task) }, 'task queued'); + log.info({ taskId, priority: task.priority }, 'task queued'); // Emit TaskQueuedEvent const event: TaskQueuedEvent = { @@ -118,7 +117,6 @@ export class DefaultDispatchManager implements DispatchManager { /** * Get next dispatchable task. * Returns task with all dependencies complete, highest priority first. - * Checkpoint tasks are excluded (require human action). */ async getNextDispatchable(): Promise { const queuedTasks = Array.from(this.taskQueue.values()); @@ -127,7 +125,7 @@ export class DefaultDispatchManager implements DispatchManager { return null; } - // Filter to only tasks with all dependencies complete and not checkpoint tasks + // Filter to only tasks with all dependencies complete const readyTasks: QueuedTask[] = []; log.debug({ queueSize: queuedTasks.length }, 'evaluating dispatchable tasks'); @@ -139,14 +137,8 @@ export class DefaultDispatchManager implements DispatchManager { continue; } - // Check if this is a checkpoint task (requires human action) - const task = await this.taskRepository.findById(qt.taskId); - if (task && this.isCheckpointTask(task)) { - log.debug({ taskId: qt.taskId, type: task.type }, 'skipping checkpoint task'); - continue; - } - // Skip planning-category tasks (handled by architect flow) + const task = await this.taskRepository.findById(qt.taskId); if (task && isPlanningCategory(task.category)) { log.debug({ taskId: qt.taskId, category: task.category }, 'skipping planning-category task'); continue; @@ -478,14 +470,6 @@ export class DefaultDispatchManager implements DispatchManager { return true; } - /** - * Check if a task is a checkpoint task. - * Checkpoint tasks require human action and don't auto-dispatch. - */ - private isCheckpointTask(task: Task): boolean { - return task.type.startsWith('checkpoint:'); - } - /** * Store the completing agent's result summary on the task record. */ diff --git a/apps/server/test/e2e/decompose-workflow.test.ts b/apps/server/test/e2e/decompose-workflow.test.ts index 8598945..6ff5fe6 100644 --- a/apps/server/test/e2e/decompose-workflow.test.ts +++ b/apps/server/test/e2e/decompose-workflow.test.ts @@ -143,7 +143,7 @@ describe('Detail Workflow E2E', () => { harness.setArchitectDetailComplete('detailer', [ { number: 1, name: 'Task 1', content: 'First task', type: 'auto', dependencies: [] }, { number: 2, name: 'Task 2', content: 'Second task', type: 'auto', dependencies: [1] }, - { number: 3, name: 'Verify', content: 'Verify all', type: 'checkpoint:human-verify', dependencies: [2] }, + { number: 3, name: 'Verify', content: 'Verify all', type: 'auto', dependencies: [2] }, ]); // Resume with all answers @@ -261,7 +261,7 @@ describe('Detail Workflow E2E', () => { tasks: [ { number: 1, name: 'Schema', description: 'Create tables', type: 'auto', dependencies: [] }, { number: 2, name: 'API', description: 'Create endpoints', type: 'auto', dependencies: [1] }, - { number: 3, name: 'Verify', description: 'Test flow', type: 'checkpoint:human-verify', dependencies: [2] }, + { number: 3, name: 'Verify', description: 'Test flow', type: 'auto', dependencies: [2] }, ], }); @@ -271,33 +271,31 @@ describe('Detail Workflow E2E', () => { expect(tasks[0].name).toBe('Schema'); expect(tasks[1].name).toBe('API'); expect(tasks[2].name).toBe('Verify'); - expect(tasks[2].type).toBe('checkpoint:human-verify'); + expect(tasks[2].type).toBe('auto'); }); - it('should handle all task types', async () => { + it('should create tasks with auto type', async () => { const initiative = await harness.createInitiative('Task Types Test'); const phases = await harness.createPhasesFromPlan(initiative.id, [ { name: 'Phase 1' }, ]); const detailTask = await harness.createDetailTask(phases[0].id, 'Mixed Tasks'); - // Create tasks with all types await harness.caller.createChildTasks({ parentTaskId: detailTask.id, tasks: [ { number: 1, name: 'Auto Task', description: 'Automated work', type: 'auto' }, - { number: 2, name: 'Human Verify', description: 'Visual check', type: 'checkpoint:human-verify', dependencies: [1] }, - { number: 3, name: 'Decision', description: 'Choose approach', type: 'checkpoint:decision', dependencies: [2] }, - { number: 4, name: 'Human Action', description: 'Manual step', type: 'checkpoint:human-action', dependencies: [3] }, + { number: 2, name: 'Second Task', description: 'More work', type: 'auto', dependencies: [1] }, + { number: 3, name: 'Third Task', description: 'Even more', type: 'auto', dependencies: [2] }, + { number: 4, name: 'Final Task', description: 'Last step', type: 'auto', dependencies: [3] }, ], }); const tasks = await harness.getChildTasks(detailTask.id); expect(tasks).toHaveLength(4); - expect(tasks[0].type).toBe('auto'); - expect(tasks[1].type).toBe('checkpoint:human-verify'); - expect(tasks[2].type).toBe('checkpoint:decision'); - expect(tasks[3].type).toBe('checkpoint:human-action'); + for (const task of tasks) { + expect(task.type).toBe('auto'); + } }); it('should create task dependencies', async () => { @@ -346,7 +344,7 @@ describe('Detail Workflow E2E', () => { { number: 1, name: 'Create user schema', content: 'Define User model', type: 'auto', dependencies: [] }, { number: 2, name: 'Implement JWT', content: 'Token generation', type: 'auto', dependencies: [1] }, { number: 3, name: 'Protected routes', content: 'Middleware', type: 'auto', dependencies: [2] }, - { number: 4, name: 'Verify auth', content: 'Test login flow', type: 'checkpoint:human-verify', dependencies: [3] }, + { number: 4, name: 'Verify auth', content: 'Test login flow', type: 'auto', dependencies: [3] }, ]); await harness.caller.spawnArchitectDetail({ @@ -367,7 +365,7 @@ describe('Detail Workflow E2E', () => { { number: 1, name: 'Create user schema', description: 'Define User model', type: 'auto', dependencies: [] }, { number: 2, name: 'Implement JWT', description: 'Token generation', type: 'auto', dependencies: [1] }, { number: 3, name: 'Protected routes', description: 'Middleware', type: 'auto', dependencies: [2] }, - { number: 4, name: 'Verify auth', description: 'Test login flow', type: 'checkpoint:human-verify', dependencies: [3] }, + { number: 4, name: 'Verify auth', description: 'Test login flow', type: 'auto', dependencies: [3] }, ], }); @@ -375,7 +373,7 @@ describe('Detail Workflow E2E', () => { const tasks = await harness.getChildTasks(detailTask.id); expect(tasks).toHaveLength(4); expect(tasks[0].name).toBe('Create user schema'); - expect(tasks[3].type).toBe('checkpoint:human-verify'); + expect(tasks[3].type).toBe('auto'); // Agent should be idle const finalAgent = await harness.caller.getAgent({ name: 'detailer' }); diff --git a/apps/server/trpc/routers/phase-dispatch.ts b/apps/server/trpc/routers/phase-dispatch.ts index 3ccb0c8..4524390 100644 --- a/apps/server/trpc/routers/phase-dispatch.ts +++ b/apps/server/trpc/routers/phase-dispatch.ts @@ -53,7 +53,7 @@ export function phaseDispatchProcedures(publicProcedure: ProcedureBuilder) { number: z.number().int().positive(), name: z.string().min(1), description: z.string(), - type: z.enum(['auto', 'checkpoint:human-verify', 'checkpoint:decision', 'checkpoint:human-action']).default('auto'), + type: z.enum(['auto']).default('auto'), dependencies: z.array(z.number().int().positive()).optional(), })), })) diff --git a/apps/server/trpc/routers/task.ts b/apps/server/trpc/routers/task.ts index 48eedcc..534f7d7 100644 --- a/apps/server/trpc/routers/task.ts +++ b/apps/server/trpc/routers/task.ts @@ -58,7 +58,7 @@ export function taskProcedures(publicProcedure: ProcedureBuilder) { name: z.string().min(1), description: z.string().optional(), category: z.enum(['execute', 'research', 'discuss', 'plan', 'detail', 'refine', 'verify', 'merge', 'review']).optional(), - type: z.enum(['auto', 'checkpoint:human-verify', 'checkpoint:decision', 'checkpoint:human-action']).optional(), + type: z.enum(['auto']).optional(), })) .mutation(async ({ ctx, input }) => { const taskRepository = requireTaskRepository(ctx); @@ -88,7 +88,7 @@ export function taskProcedures(publicProcedure: ProcedureBuilder) { name: z.string().min(1), description: z.string().optional(), category: z.enum(['execute', 'research', 'discuss', 'plan', 'detail', 'refine', 'verify', 'merge', 'review']).optional(), - type: z.enum(['auto', 'checkpoint:human-verify', 'checkpoint:decision', 'checkpoint:human-action']).optional(), + type: z.enum(['auto']).optional(), })) .mutation(async ({ ctx, input }) => { const taskRepository = requireTaskRepository(ctx); diff --git a/apps/web/src/components/TaskRow.tsx b/apps/web/src/components/TaskRow.tsx index 2fdc909..bea31ff 100644 --- a/apps/web/src/components/TaskRow.tsx +++ b/apps/web/src/components/TaskRow.tsx @@ -12,7 +12,7 @@ export interface SerializedTask { parentTaskId: string | null; name: string; description: string | null; - type: "auto" | "checkpoint:human-verify" | "checkpoint:decision" | "checkpoint:human-action"; + type: "auto"; category: string; priority: "low" | "medium" | "high"; status: "pending" | "in_progress" | "completed" | "blocked"; diff --git a/docs/agent.md b/docs/agent.md index 560ca1a..5529f63 100644 --- a/docs/agent.md +++ b/docs/agent.md @@ -236,7 +236,7 @@ All prompts follow a consistent tag ordering: |------|------|--------------------| | **execute** | `execute.ts` | ``, ``, ``, `` | | **plan** | `plan.ts` | ``, ``, ``, ``, `` | -| **detail** | `detail.ts` | ``, ``, ``, ``, `` | +| **detail** | `detail.ts` | ``, ``, ``, `` | | **discuss** | `discuss.ts` | ``, ``, ``, ``, `` | | **refine** | `refine.ts` | ``, `` | | **chat** | `chat.ts` | ``, `` — iterative refinement loop, uses action field (create/update/delete) in output files, signals "questions" after each change to stay alive | diff --git a/docs/database.md b/docs/database.md index a32cac5..c4bfc59 100644 --- a/docs/database.md +++ b/docs/database.md @@ -44,7 +44,7 @@ All adapters use nanoid() for IDs, auto-manage timestamps, and use Drizzle's `.r | parentTaskId | text nullable self-ref FK (cascade) | decomposition hierarchy | | name | text NOT NULL | | | description | text nullable | | -| type | text enum | 'auto' \| 'checkpoint:human-verify' \| 'checkpoint:decision' \| 'checkpoint:human-action' | +| type | text enum | 'auto' | | category | text enum | 'execute' \| 'research' \| 'discuss' \| 'plan' \| 'detail' \| 'refine' \| 'verify' \| 'merge' \| 'review' | | priority | text enum | 'low' \| 'medium' \| 'high' | | status | text enum | 'pending' \| 'in_progress' \| 'completed' \| 'blocked' | diff --git a/docs/dispatch-events.md b/docs/dispatch-events.md index e2b81a6..dca044b 100644 --- a/docs/dispatch-events.md +++ b/docs/dispatch-events.md @@ -64,8 +64,7 @@ InitiativeReviewApprovedEvent { initiativeId, branch, strategy: 'push_branch' | 2. **Dispatch** — `dispatchNext()` finds highest-priority task with all deps complete 3. **Context gathering** — Before spawn, `dispatchNext()` gathers initiative context (initiative, phase, tasks, pages) and passes as `inputContext` to the agent. Agents receive `.cw/input/task.md`, `initiative.md`, `phase.md`, `context/tasks/`, `context/phases/`, and `pages/`. 4. **Priority order**: high > medium > low, then oldest first (FIFO within priority) -5. **Checkpoint skip** — Tasks with type starting with `checkpoint:` skip auto-dispatch -6. **Planning skip** — Planning-category tasks (research, discuss, plan, detail, refine) skip auto-dispatch — they use the architect flow +5. **Planning skip** — Planning-category tasks (research, discuss, plan, detail, refine) skip auto-dispatch — they use the architect flow 7. **Summary propagation** — `completeTask()` reads the completing agent's `result.message` and stores it on the task's `summary` column. Dependent tasks see this summary in `context/tasks/.md` frontmatter. 8. **Spawn failure** — If `agentManager.spawn()` throws, the task is blocked via `blockTask()` with the error message. The dispatch cycle continues instead of crashing. 9. **Retry blocked** — `retryBlockedTask(taskId)` resets a blocked task to pending and re-queues it. Exposed via tRPC `retryBlockedTask` mutation. The UI shows a Retry button in the task slide-over when status is `blocked`.