feat: Remove checkpoint task types — per-phase review is sufficient

Checkpoint tasks (human-verify, decision, human-action) silently blocked
auto-dispatch with no UI to resolve them. Per-phase review + initiative
review already cover human verification, making checkpoints redundant.

Removed from: schema, dispatch manager, tRPC validators, detail prompt,
frontend types, tests, and docs.
This commit is contained in:
Lukas May
2026-03-05 21:30:22 +01:00
parent 39bb03e30b
commit 7b93cfe7d7
11 changed files with 28 additions and 55 deletions

View File

@@ -13,7 +13,7 @@ ${CODEBASE_EXPLORATION}
<output_format>
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
</output_format>
@@ -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.
</task_sizing>
<checkpoint_tasks>
- \`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\`.
</checkpoint_tasks>
<existing_context>
- Read ALL \`context/tasks/\` files before generating output
- Only create tasks for THIS phase (\`phase.md\`)

View File

@@ -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');
});
});

View File

@@ -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'),

View File

@@ -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<void> {
// 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<QueuedTask | null> {
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.
*/

View File

@@ -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' });

View File

@@ -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(),
})),
}))

View File

@@ -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);