feat: auto-create Integration phase for multi-leaf initiatives
When an initiative has multiple end phases (leaf nodes with no dependents), queueAllPhases now auto-creates an Integration phase that depends on all of them. This catches cross-phase incompatibilities (type mismatches, conflicting exports, broken tests) before review.
This commit is contained in:
@@ -4,10 +4,35 @@
|
||||
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { z } from 'zod';
|
||||
import type { Task } from '../../db/schema.js';
|
||||
import type { Phase, Task } from '../../db/schema.js';
|
||||
import type { ProcedureBuilder } from '../trpc.js';
|
||||
import { requirePhaseDispatchManager, requirePhaseRepository, requireTaskRepository } from './_helpers.js';
|
||||
|
||||
const INTEGRATION_PHASE_NAME = 'Integration';
|
||||
|
||||
const INTEGRATION_TASK_DESCRIPTION = `Verify that all phase branches integrate correctly after merging into the initiative branch.
|
||||
|
||||
Steps:
|
||||
1. Build the project — fix any compilation errors
|
||||
2. Run the full test suite — fix any failing tests
|
||||
3. Run type checking and linting — fix any errors
|
||||
4. Review cross-phase imports and shared interfaces for compatibility
|
||||
5. Smoke test key user flows affected by the merged changes
|
||||
|
||||
Only fix integration issues (type mismatches, conflicting exports, broken tests). Do not refactor or improve existing code.`;
|
||||
|
||||
/**
|
||||
* Find phase IDs that have no dependents (no other phase depends on them).
|
||||
* These are the "end" / "leaf" phases in the dependency graph.
|
||||
*/
|
||||
function findEndPhaseIds(
|
||||
phases: Phase[],
|
||||
edges: Array<{ phaseId: string; dependsOnPhaseId: string }>,
|
||||
): string[] {
|
||||
const dependedOn = new Set(edges.map((e) => e.dependsOnPhaseId));
|
||||
return phases.filter((p) => !dependedOn.has(p.id)).map((p) => p.id);
|
||||
}
|
||||
|
||||
export function phaseDispatchProcedures(publicProcedure: ProcedureBuilder) {
|
||||
return {
|
||||
queuePhase: publicProcedure
|
||||
@@ -23,7 +48,40 @@ export function phaseDispatchProcedures(publicProcedure: ProcedureBuilder) {
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const phaseDispatchManager = requirePhaseDispatchManager(ctx);
|
||||
const phaseRepo = requirePhaseRepository(ctx);
|
||||
const phases = await phaseRepo.findByInitiativeId(input.initiativeId);
|
||||
const taskRepo = requireTaskRepository(ctx);
|
||||
|
||||
let phases = await phaseRepo.findByInitiativeId(input.initiativeId);
|
||||
const edges = await phaseRepo.findDependenciesByInitiativeId(input.initiativeId);
|
||||
|
||||
// Auto-create Integration phase if multiple end phases exist
|
||||
const existingIntegration = phases.find((p) => p.name === INTEGRATION_PHASE_NAME);
|
||||
if (!existingIntegration) {
|
||||
const endPhaseIds = findEndPhaseIds(phases, edges);
|
||||
if (endPhaseIds.length > 1) {
|
||||
const integrationPhase = await phaseRepo.create({
|
||||
initiativeId: input.initiativeId,
|
||||
name: INTEGRATION_PHASE_NAME,
|
||||
status: 'approved',
|
||||
});
|
||||
|
||||
for (const endPhaseId of endPhaseIds) {
|
||||
await phaseRepo.createDependency(integrationPhase.id, endPhaseId);
|
||||
}
|
||||
|
||||
await taskRepo.create({
|
||||
phaseId: integrationPhase.id,
|
||||
initiativeId: input.initiativeId,
|
||||
name: 'Verify integration',
|
||||
description: INTEGRATION_TASK_DESCRIPTION,
|
||||
category: 'verify',
|
||||
status: 'pending',
|
||||
});
|
||||
|
||||
// Re-fetch so the new phase gets queued in the loop below
|
||||
phases = await phaseRepo.findByInitiativeId(input.initiativeId);
|
||||
}
|
||||
}
|
||||
|
||||
let queued = 0;
|
||||
for (const phase of phases) {
|
||||
if (phase.status === 'approved') {
|
||||
|
||||
Reference in New Issue
Block a user