diff --git a/src/trpc/router.ts b/src/trpc/router.ts index 86a9b2b..a9603de 100644 --- a/src/trpc/router.ts +++ b/src/trpc/router.ts @@ -11,8 +11,11 @@ import type { TRPCContext } from './context.js'; import type { AgentInfo, AgentResult } from '../agent/types.js'; 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 { DispatchManager } from '../dispatch/types.js'; import type { CoordinationManager } from '../coordination/types.js'; +import type { Phase } from '../db/schema.js'; /** * Initialize tRPC with our context type. @@ -232,6 +235,32 @@ function requireCoordinationManager(ctx: TRPCContext): CoordinationManager { return ctx.coordinationManager; } +/** + * Helper to ensure initiativeRepository is available in context. + */ +function requireInitiativeRepository(ctx: TRPCContext): InitiativeRepository { + if (!ctx.initiativeRepository) { + throw new TRPCError({ + code: 'INTERNAL_SERVER_ERROR', + message: 'Initiative repository not available', + }); + } + return ctx.initiativeRepository; +} + +/** + * Helper to ensure phaseRepository is available in context. + */ +function requirePhaseRepository(ctx: TRPCContext): PhaseRepository { + if (!ctx.phaseRepository) { + throw new TRPCError({ + code: 'INTERNAL_SERVER_ERROR', + message: 'Phase repository not available', + }); + } + return ctx.phaseRepository; +} + // ============================================================================= // Application Router with Procedures // ============================================================================= @@ -633,6 +662,79 @@ export const appRouter = router({ const coordinationManager = requireCoordinationManager(ctx); return coordinationManager.getNextMergeable(); }), + + // =========================================================================== + // Initiative Procedures + // =========================================================================== + + /** + * Create a new initiative. + * Returns the created initiative with generated ID. + */ + createInitiative: publicProcedure + .input(z.object({ + name: z.string().min(1), + description: z.string().optional(), + })) + .mutation(async ({ ctx, input }) => { + const repo = requireInitiativeRepository(ctx); + return repo.create({ + name: input.name, + description: input.description ?? null, + status: 'active', + }); + }), + + /** + * List all initiatives with optional status filter. + * Returns initiatives ordered by creation time. + */ + listInitiatives: publicProcedure + .input(z.object({ + status: z.enum(['active', 'completed', 'archived']).optional(), + }).optional()) + .query(async ({ ctx, input }) => { + const repo = requireInitiativeRepository(ctx); + if (input?.status) { + return repo.findByStatus(input.status); + } + return repo.findAll(); + }), + + /** + * Get an initiative by ID. + * Throws NOT_FOUND if initiative doesn't exist. + */ + getInitiative: publicProcedure + .input(z.object({ id: z.string().min(1) })) + .query(async ({ ctx, input }) => { + const repo = requireInitiativeRepository(ctx); + const initiative = await repo.findById(input.id); + if (!initiative) { + throw new TRPCError({ + code: 'NOT_FOUND', + message: `Initiative '${input.id}' not found`, + }); + } + return initiative; + }), + + /** + * Update an initiative. + * Returns the updated initiative. + */ + updateInitiative: publicProcedure + .input(z.object({ + id: z.string().min(1), + name: z.string().min(1).optional(), + description: z.string().optional(), + status: z.enum(['active', 'completed', 'archived']).optional(), + })) + .mutation(async ({ ctx, input }) => { + const repo = requireInitiativeRepository(ctx); + const { id, ...data } = input; + return repo.update(id, data); + }), }); /**