feat(12-04): add createTasksFromDecomposition procedure
- Add createDependency method to TaskRepository interface - Implement createDependency in DrizzleTaskRepository - Add createTasksFromDecomposition procedure for bulk task creation - Procedure verifies plan exists before creating tasks - Creates tasks in order, building number-to-ID map - Creates task dependencies after all tasks exist - Dependencies mapped from task numbers to IDs
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
import { eq, asc } from 'drizzle-orm';
|
||||
import { nanoid } from 'nanoid';
|
||||
import type { DrizzleDatabase } from '../../index.js';
|
||||
import { tasks, type Task } from '../../schema.js';
|
||||
import { tasks, taskDependencies, type Task } from '../../schema.js';
|
||||
import type {
|
||||
TaskRepository,
|
||||
CreateTaskData,
|
||||
@@ -85,4 +85,16 @@ export class DrizzleTaskRepository implements TaskRepository {
|
||||
|
||||
await this.db.delete(tasks).where(eq(tasks.id, id));
|
||||
}
|
||||
|
||||
async createDependency(taskId: string, dependsOnTaskId: string): Promise<void> {
|
||||
const id = nanoid();
|
||||
const now = new Date();
|
||||
|
||||
await this.db.insert(taskDependencies).values({
|
||||
id,
|
||||
taskId,
|
||||
dependsOnTaskId,
|
||||
createdAt: now,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,4 +58,11 @@ export interface TaskRepository {
|
||||
* Throws if task not found.
|
||||
*/
|
||||
delete(id: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Create a dependency between two tasks.
|
||||
* The task identified by taskId will depend on dependsOnTaskId.
|
||||
* Both tasks must exist.
|
||||
*/
|
||||
createDependency(taskId: string, dependsOnTaskId: string): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import type { PhaseRepository } from '../db/repositories/phase-repository.js';
|
||||
import type { PlanRepository } from '../db/repositories/plan-repository.js';
|
||||
import type { DispatchManager } from '../dispatch/types.js';
|
||||
import type { CoordinationManager } from '../coordination/types.js';
|
||||
import type { Phase, Plan } from '../db/schema.js';
|
||||
import type { Phase, Plan, Task } from '../db/schema.js';
|
||||
import { buildDiscussPrompt, buildBreakdownPrompt } from '../agent/prompts.js';
|
||||
|
||||
/**
|
||||
@@ -928,6 +928,67 @@ export const appRouter = router({
|
||||
return repo.update(id, data);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Create tasks from decomposition agent output.
|
||||
* Creates all tasks in order, then creates dependencies from number mappings.
|
||||
*/
|
||||
createTasksFromDecomposition: publicProcedure
|
||||
.input(z.object({
|
||||
planId: z.string().min(1),
|
||||
tasks: z.array(z.object({
|
||||
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'),
|
||||
dependencies: z.array(z.number().int().positive()).optional(),
|
||||
})),
|
||||
}))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const taskRepo = requireTaskRepository(ctx);
|
||||
const planRepo = requirePlanRepository(ctx);
|
||||
|
||||
// Verify plan exists
|
||||
const plan = await planRepo.findById(input.planId);
|
||||
if (!plan) {
|
||||
throw new TRPCError({
|
||||
code: 'NOT_FOUND',
|
||||
message: `Plan '${input.planId}' not found`,
|
||||
});
|
||||
}
|
||||
|
||||
// Create tasks in order, building number-to-ID map
|
||||
const numberToId = new Map<number, string>();
|
||||
const created: Task[] = [];
|
||||
|
||||
for (const taskInput of input.tasks) {
|
||||
const task = await taskRepo.create({
|
||||
planId: input.planId,
|
||||
name: taskInput.name,
|
||||
description: taskInput.description,
|
||||
type: taskInput.type,
|
||||
order: taskInput.number,
|
||||
status: 'pending',
|
||||
});
|
||||
numberToId.set(taskInput.number, task.id);
|
||||
created.push(task);
|
||||
}
|
||||
|
||||
// Create dependencies after all tasks exist
|
||||
for (const taskInput of input.tasks) {
|
||||
if (taskInput.dependencies && taskInput.dependencies.length > 0) {
|
||||
const taskId = numberToId.get(taskInput.number)!;
|
||||
for (const depNumber of taskInput.dependencies) {
|
||||
const dependsOnTaskId = numberToId.get(depNumber);
|
||||
if (dependsOnTaskId) {
|
||||
await taskRepo.createDependency(taskId, dependsOnTaskId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return created;
|
||||
}),
|
||||
|
||||
// ===========================================================================
|
||||
// Architect Spawn Procedures
|
||||
// ===========================================================================
|
||||
|
||||
Reference in New Issue
Block a user