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 { TRPCError } from '@trpc/server';
|
||||||
import { z } from 'zod';
|
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 type { ProcedureBuilder } from '../trpc.js';
|
||||||
import { requirePhaseDispatchManager, requirePhaseRepository, requireTaskRepository } from './_helpers.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) {
|
export function phaseDispatchProcedures(publicProcedure: ProcedureBuilder) {
|
||||||
return {
|
return {
|
||||||
queuePhase: publicProcedure
|
queuePhase: publicProcedure
|
||||||
@@ -23,7 +48,40 @@ export function phaseDispatchProcedures(publicProcedure: ProcedureBuilder) {
|
|||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
const phaseDispatchManager = requirePhaseDispatchManager(ctx);
|
const phaseDispatchManager = requirePhaseDispatchManager(ctx);
|
||||||
const phaseRepo = requirePhaseRepository(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;
|
let queued = 0;
|
||||||
for (const phase of phases) {
|
for (const phase of phases) {
|
||||||
if (phase.status === 'approved') {
|
if (phase.status === 'approved') {
|
||||||
|
|||||||
@@ -93,6 +93,18 @@ InitiativeChangesRequestedEvent { initiativeId, phaseId, taskId }
|
|||||||
4. **Auto-queue tasks** — When phase starts (branches confirmed), pending execution tasks are queued (planning-category tasks excluded)
|
4. **Auto-queue tasks** — When phase starts (branches confirmed), pending execution tasks are queued (planning-category tasks excluded)
|
||||||
5. **Events** — `phase:queued`, `phase:started`, `phase:completed`, `phase:blocked`
|
5. **Events** — `phase:queued`, `phase:started`, `phase:completed`, `phase:blocked`
|
||||||
|
|
||||||
|
### Auto-Integration Phase
|
||||||
|
|
||||||
|
When `queueAllPhases` is called (i.e. the user clicks "Execute"), it auto-creates an **Integration** phase if the initiative has multiple end phases (leaf nodes with no dependents). This catches cross-phase incompatibilities before the initiative reaches review.
|
||||||
|
|
||||||
|
- **Trigger**: `queueAllPhases` in `apps/server/trpc/routers/phase-dispatch.ts`
|
||||||
|
- **Guard**: Only created when `endPhaseIds.length > 1` and no existing "Integration" phase
|
||||||
|
- **Status**: Created as `approved` (same pattern as Finalization in orchestrator.ts)
|
||||||
|
- **Dependencies**: Integration depends on all end phases — dispatched last
|
||||||
|
- **Task**: A single `verify` category task instructs the agent to build, run tests, check types, and review cross-phase imports
|
||||||
|
- **Idempotency**: Name-based check prevents duplicates on re-execution
|
||||||
|
- **Coexistence**: Independent of the Finalization phase (different purpose, different trigger)
|
||||||
|
|
||||||
### PhaseDispatchManager Methods
|
### PhaseDispatchManager Methods
|
||||||
|
|
||||||
| Method | Purpose |
|
| Method | Purpose |
|
||||||
|
|||||||
Reference in New Issue
Block a user