feat(12-04): add Plan tRPC procedures

- Import PlanRepository type
- Add requirePlanRepository helper
- Add createPlan procedure (auto-assigns number if not provided)
- Add listPlans procedure (returns plans ordered by number)
- Add getPlan procedure (throws NOT_FOUND if not found)
- Add updatePlan procedure (name, description, status)
This commit is contained in:
Lukas May
2026-02-01 11:45:32 +01:00
parent c98e9df486
commit 612744580e

View File

@@ -13,9 +13,10 @@ import type { TaskRepository } from '../db/repositories/task-repository.js';
import type { MessageRepository } from '../db/repositories/message-repository.js';
import type { InitiativeRepository } from '../db/repositories/initiative-repository.js';
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 } from '../db/schema.js';
import type { Phase, Plan } from '../db/schema.js';
import { buildDiscussPrompt, buildBreakdownPrompt } from '../agent/prompts.js';
/**
@@ -264,6 +265,19 @@ function requirePhaseRepository(ctx: TRPCContext): PhaseRepository {
return ctx.phaseRepository;
}
/**
* Helper to ensure planRepository is available in context.
*/
function requirePlanRepository(ctx: TRPCContext): PlanRepository {
if (!ctx.planRepository) {
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Plan repository not available',
});
}
return ctx.planRepository;
}
// =============================================================================
// Application Router with Procedures
// =============================================================================
@@ -841,6 +855,79 @@ export const appRouter = router({
return created;
}),
// ===========================================================================
// Plan Procedures
// ===========================================================================
/**
* Create a new plan for a phase.
* Auto-assigns the next plan number if not provided.
*/
createPlan: publicProcedure
.input(z.object({
phaseId: z.string().min(1),
number: z.number().int().positive().optional(),
name: z.string().min(1),
description: z.string().optional(),
}))
.mutation(async ({ ctx, input }) => {
const repo = requirePlanRepository(ctx);
const number = input.number ?? await repo.getNextNumber(input.phaseId);
return repo.create({
phaseId: input.phaseId,
number,
name: input.name,
description: input.description ?? null,
status: 'pending',
});
}),
/**
* List plans for a phase.
* Returns plans ordered by number.
*/
listPlans: publicProcedure
.input(z.object({ phaseId: z.string().min(1) }))
.query(async ({ ctx, input }) => {
const repo = requirePlanRepository(ctx);
return repo.findByPhaseId(input.phaseId);
}),
/**
* Get a plan by ID.
* Throws NOT_FOUND if plan doesn't exist.
*/
getPlan: publicProcedure
.input(z.object({ id: z.string().min(1) }))
.query(async ({ ctx, input }) => {
const repo = requirePlanRepository(ctx);
const plan = await repo.findById(input.id);
if (!plan) {
throw new TRPCError({
code: 'NOT_FOUND',
message: `Plan '${input.id}' not found`,
});
}
return plan;
}),
/**
* Update a plan.
* Returns the updated plan.
*/
updatePlan: publicProcedure
.input(z.object({
id: z.string().min(1),
name: z.string().min(1).optional(),
description: z.string().optional(),
status: z.enum(['pending', 'in_progress', 'completed']).optional(),
}))
.mutation(async ({ ctx, input }) => {
const repo = requirePlanRepository(ctx);
const { id, ...data } = input;
return repo.update(id, data);
}),
// ===========================================================================
// Architect Spawn Procedures
// ===========================================================================