feat(14-05): add phase dependency and dispatch procedures

- Import PhaseDispatchManager type from dispatch module
- Add requirePhaseDispatchManager helper function
- Add createPhaseDependency: creates dependency between two phases
- Add getPhaseDependencies: returns phase IDs that a phase depends on
- Add queuePhase: queues phase for dispatch
- Add dispatchNextPhase: dispatches next available phase
- Add getPhaseQueueState: returns phase queue state

All procedures follow existing naming conventions and error handling patterns.
This commit is contained in:
Lukas May
2026-02-02 13:40:51 +01:00
parent 053168f6cf
commit 5e6d507eb8

View File

@@ -14,7 +14,7 @@ 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 { DispatchManager, PhaseDispatchManager } from '../dispatch/types.js';
import type { CoordinationManager } from '../coordination/types.js';
import type { Phase, Plan, Task } from '../db/schema.js';
import { buildDiscussPrompt, buildBreakdownPrompt, buildDecomposePrompt } from '../agent/prompts.js';
@@ -278,6 +278,19 @@ function requirePlanRepository(ctx: TRPCContext): PlanRepository {
return ctx.planRepository;
}
/**
* Helper to ensure phaseDispatchManager is available in context.
*/
function requirePhaseDispatchManager(ctx: TRPCContext): PhaseDispatchManager {
if (!ctx.phaseDispatchManager) {
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Phase dispatch manager not available',
});
}
return ctx.phaseDispatchManager;
}
// =============================================================================
// Application Router with Procedures
// =============================================================================
@@ -855,6 +868,87 @@ export const appRouter = router({
return created;
}),
/**
* Create a dependency between two phases.
* The phase with phaseId depends on the phase with dependsOnPhaseId.
*/
createPhaseDependency: publicProcedure
.input(z.object({
phaseId: z.string().min(1),
dependsOnPhaseId: z.string().min(1),
}))
.mutation(async ({ ctx, input }) => {
const repo = requirePhaseRepository(ctx);
// Validate both phases exist
const phase = await repo.findById(input.phaseId);
if (!phase) {
throw new TRPCError({
code: 'NOT_FOUND',
message: `Phase '${input.phaseId}' not found`,
});
}
const dependsOnPhase = await repo.findById(input.dependsOnPhaseId);
if (!dependsOnPhase) {
throw new TRPCError({
code: 'NOT_FOUND',
message: `Phase '${input.dependsOnPhaseId}' not found`,
});
}
await repo.createDependency(input.phaseId, input.dependsOnPhaseId);
return { success: true };
}),
/**
* Get dependencies for a phase.
* Returns IDs of phases that this phase depends on.
*/
getPhaseDependencies: publicProcedure
.input(z.object({ phaseId: z.string().min(1) }))
.query(async ({ ctx, input }) => {
const repo = requirePhaseRepository(ctx);
const dependencies = await repo.getDependencies(input.phaseId);
return { dependencies };
}),
// ===========================================================================
// Phase Dispatch Procedures
// ===========================================================================
/**
* Queue a phase for dispatch.
* Phase will be dispatched when all dependencies complete.
*/
queuePhase: publicProcedure
.input(z.object({ phaseId: z.string().min(1) }))
.mutation(async ({ ctx, input }) => {
const phaseDispatchManager = requirePhaseDispatchManager(ctx);
await phaseDispatchManager.queuePhase(input.phaseId);
return { success: true };
}),
/**
* Dispatch next available phase.
* Returns dispatch result with phase info.
*/
dispatchNextPhase: publicProcedure
.mutation(async ({ ctx }) => {
const phaseDispatchManager = requirePhaseDispatchManager(ctx);
return phaseDispatchManager.dispatchNextPhase();
}),
/**
* Get current phase queue state.
* Returns queued, ready, and blocked phase counts.
*/
getPhaseQueueState: publicProcedure
.query(async ({ ctx }) => {
const phaseDispatchManager = requirePhaseDispatchManager(ctx);
return phaseDispatchManager.getPhaseQueueState();
}),
// ===========================================================================
// Plan Procedures
// ===========================================================================