diff --git a/src/test/e2e/decompose-workflow.test.ts b/src/test/e2e/decompose-workflow.test.ts index 077dcb3..84df8a2 100644 --- a/src/test/e2e/decompose-workflow.test.ts +++ b/src/test/e2e/decompose-workflow.test.ts @@ -163,6 +163,90 @@ describe('Decompose Workflow E2E', () => { }); }); + describe('decompose conflict detection', () => { + it('should reject if a decompose agent is already running for the same phase', async () => { + vi.useFakeTimers(); + + const initiative = await harness.createInitiative('Test Project'); + const phases = await harness.createPhasesFromBreakdown(initiative.id, [ + { name: 'Phase 1' }, + ]); + + // Long-running decompose agent + harness.setAgentScenario('decomposer-1', { status: 'done', delay: 999999 }); + + await harness.caller.spawnArchitectDecompose({ + name: 'decomposer-1', + phaseId: phases[0].id, + }); + + // Second decompose for same phase should be rejected + await expect( + harness.caller.spawnArchitectDecompose({ + name: 'decomposer-2', + phaseId: phases[0].id, + }), + ).rejects.toThrow(/already running/); + }); + + it('should auto-dismiss stale decompose agents before checking', async () => { + vi.useFakeTimers(); + + const initiative = await harness.createInitiative('Test Project'); + const phases = await harness.createPhasesFromBreakdown(initiative.id, [ + { name: 'Phase 1' }, + ]); + + // Decompose agent that crashes immediately + harness.setAgentScenario('stale-decomposer', { status: 'error', error: 'crashed' }); + + await harness.caller.spawnArchitectDecompose({ + name: 'stale-decomposer', + phaseId: phases[0].id, + }); + await harness.advanceTimers(); + + // New decompose should succeed + harness.setArchitectDecomposeComplete('new-decomposer', [ + { number: 1, name: 'Task 1', content: 'Do it', type: 'auto', dependencies: [] }, + ]); + + const agent = await harness.caller.spawnArchitectDecompose({ + name: 'new-decomposer', + phaseId: phases[0].id, + }); + expect(agent.mode).toBe('decompose'); + }); + + it('should allow decompose for different phases simultaneously', async () => { + vi.useFakeTimers(); + + const initiative = await harness.createInitiative('Test Project'); + const phases = await harness.createPhasesFromBreakdown(initiative.id, [ + { name: 'Phase 1' }, + { name: 'Phase 2' }, + ]); + + // Long-running agent on phase 1 + harness.setAgentScenario('decomposer-p1', { status: 'done', delay: 999999 }); + await harness.caller.spawnArchitectDecompose({ + name: 'decomposer-p1', + phaseId: phases[0].id, + }); + + // Decompose on phase 2 should succeed + harness.setArchitectDecomposeComplete('decomposer-p2', [ + { number: 1, name: 'Task 1', content: 'Do it', type: 'auto', dependencies: [] }, + ]); + + const agent = await harness.caller.spawnArchitectDecompose({ + name: 'decomposer-p2', + phaseId: phases[1].id, + }); + expect(agent.mode).toBe('decompose'); + }); + }); + describe('task persistence', () => { it('should create tasks from decomposition output', async () => { const initiative = await harness.createInitiative('Test Project');