Move src/ → apps/server/ and packages/web/ → apps/web/ to adopt standard monorepo conventions (apps/ for runnable apps, packages/ for reusable libraries). Update all config files, shared package imports, test fixtures, and documentation to reflect new paths. Key fixes: - Update workspace config to ["apps/*", "packages/*"] - Update tsconfig.json rootDir/include for apps/server/ - Add apps/web/** to vitest exclude list - Update drizzle.config.ts schema path - Fix ensure-schema.ts migration path detection (3 levels up in dev, 2 levels up in dist) - Fix tests/integration/cli-server.test.ts import paths - Update packages/shared imports to apps/server/ paths - Update all docs/ files with new paths
552 lines
21 KiB
TypeScript
552 lines
21 KiB
TypeScript
/**
|
|
* E2E Tests for Extended Scenarios
|
|
*
|
|
* Tests extended scenarios in dispatch/coordination flow:
|
|
* - Conflict hand-back round-trip (conflict -> agent resolves -> merge succeeds)
|
|
* - Multi-agent parallel work and completion
|
|
*
|
|
* Uses TestHarness from src/test/ for full system wiring.
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
import {
|
|
createTestHarness,
|
|
SIMPLE_FIXTURE,
|
|
PARALLEL_FIXTURE,
|
|
COMPLEX_FIXTURE,
|
|
type TestHarness,
|
|
} from '../index.js';
|
|
import type {
|
|
MergeConflictedEvent,
|
|
MergeCompletedEvent,
|
|
TaskQueuedEvent,
|
|
AgentStoppedEvent,
|
|
AgentCrashedEvent,
|
|
} from '../../events/types.js';
|
|
|
|
describe('E2E Extended Scenarios', () => {
|
|
let harness: TestHarness;
|
|
|
|
beforeEach(() => {
|
|
harness = createTestHarness();
|
|
});
|
|
|
|
afterEach(() => {
|
|
harness.cleanup();
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
// ===========================================================================
|
|
// Conflict Hand-back Round-trip
|
|
// ===========================================================================
|
|
|
|
describe('Conflict hand-back round-trip', () => {
|
|
it('conflict triggers resolution task, agent resolves, merge succeeds', async () => {
|
|
vi.useFakeTimers();
|
|
const seeded = await harness.seedFixture(SIMPLE_FIXTURE);
|
|
const taskAId = seeded.tasks.get('Task A')!;
|
|
|
|
// Step 1: Complete Task A
|
|
await harness.taskRepository.update(taskAId, { status: 'completed' });
|
|
|
|
// Step 2: Create agent in agentRepository with worktreeId
|
|
const worktreeId = `wt-${taskAId.slice(0, 6)}`;
|
|
await harness.agentRepository.create({
|
|
name: `agent-${taskAId.slice(0, 6)}`,
|
|
worktreeId,
|
|
taskId: taskAId,
|
|
status: 'idle',
|
|
});
|
|
|
|
// Step 3: Create worktree via MockWorktreeManager
|
|
await harness.worktreeManager.create(worktreeId, 'feature-task-a');
|
|
|
|
// Step 4: Set merge conflict result for first merge attempt
|
|
harness.worktreeManager.setMergeResult(worktreeId, {
|
|
success: false,
|
|
conflicts: ['src/shared.ts', 'src/types.ts'],
|
|
message: 'Merge conflict in 2 files',
|
|
});
|
|
|
|
// Step 5: Queue and process merge (should fail with conflict)
|
|
await harness.coordinationManager.queueMerge(taskAId);
|
|
harness.clearEvents();
|
|
const conflictResults = await harness.coordinationManager.processMerges('main');
|
|
|
|
// Verify: merge failed with conflict
|
|
expect(conflictResults.length).toBe(1);
|
|
expect(conflictResults[0].success).toBe(false);
|
|
expect(conflictResults[0].conflicts).toEqual(['src/shared.ts', 'src/types.ts']);
|
|
|
|
// Verify: merge:conflicted event emitted
|
|
const conflictedEvents = harness.getEventsByType('merge:conflicted');
|
|
expect(conflictedEvents.length).toBe(1);
|
|
const conflictPayload = (conflictedEvents[0] as MergeConflictedEvent).payload;
|
|
expect(conflictPayload.taskId).toBe(taskAId);
|
|
expect(conflictPayload.conflictingFiles).toEqual(['src/shared.ts', 'src/types.ts']);
|
|
|
|
// Verify: original task marked blocked
|
|
const originalTask = await harness.taskRepository.findById(taskAId);
|
|
expect(originalTask?.status).toBe('blocked');
|
|
|
|
// Note: CoordinationManager.handleConflict updates task status to blocked
|
|
// but does not emit task:blocked event (that's emitted by DispatchManager.blockTask)
|
|
|
|
// Verify: task:queued event emitted for resolution task
|
|
const queuedEvents = harness.getEventsByType('task:queued');
|
|
const resolutionTaskEvent = queuedEvents.find(
|
|
(e) => (e as TaskQueuedEvent).payload.taskId !== taskAId
|
|
);
|
|
expect(resolutionTaskEvent).toBeDefined();
|
|
|
|
// Step 6: Clear the merge conflict (setMergeResult to success)
|
|
harness.worktreeManager.setMergeResult(worktreeId, {
|
|
success: true,
|
|
message: 'Merged successfully',
|
|
});
|
|
|
|
// Step 7: Re-queue original task for merge (simulating resolution completed)
|
|
// In a real system, the resolution task would fix conflicts and re-queue
|
|
// Here we simulate by clearing conflict and re-queuing
|
|
await harness.taskRepository.update(taskAId, { status: 'completed' });
|
|
harness.clearEvents();
|
|
|
|
await harness.coordinationManager.queueMerge(taskAId);
|
|
const successResults = await harness.coordinationManager.processMerges('main');
|
|
|
|
// Verify: merge succeeded
|
|
expect(successResults.length).toBe(1);
|
|
expect(successResults[0].taskId).toBe(taskAId);
|
|
expect(successResults[0].success).toBe(true);
|
|
|
|
// Verify: merge:completed event for original task
|
|
const completedEvents = harness.getEventsByType('merge:completed');
|
|
expect(completedEvents.length).toBe(1);
|
|
const completedPayload = (completedEvents[0] as MergeCompletedEvent).payload;
|
|
expect(completedPayload.taskId).toBe(taskAId);
|
|
});
|
|
|
|
it('conflict resolution preserves original task context', async () => {
|
|
vi.useFakeTimers();
|
|
const seeded = await harness.seedFixture(SIMPLE_FIXTURE);
|
|
const taskAId = seeded.tasks.get('Task A')!;
|
|
|
|
// Complete Task A
|
|
await harness.taskRepository.update(taskAId, { status: 'completed' });
|
|
|
|
// Create agent and worktree
|
|
const worktreeId = `wt-${taskAId.slice(0, 6)}`;
|
|
await harness.agentRepository.create({
|
|
name: `agent-${taskAId.slice(0, 6)}`,
|
|
worktreeId,
|
|
taskId: taskAId,
|
|
status: 'idle',
|
|
});
|
|
await harness.worktreeManager.create(worktreeId, 'feature-task-a');
|
|
|
|
// Set conflict
|
|
harness.worktreeManager.setMergeResult(worktreeId, {
|
|
success: false,
|
|
conflicts: ['src/conflict-file.ts'],
|
|
message: 'Merge conflict',
|
|
});
|
|
|
|
// Process merge to trigger conflict handling
|
|
await harness.coordinationManager.queueMerge(taskAId);
|
|
harness.clearEvents();
|
|
await harness.coordinationManager.processMerges('main');
|
|
|
|
// Get the resolution task from task:queued events
|
|
const queuedEvents = harness.getEventsByType('task:queued');
|
|
expect(queuedEvents.length).toBeGreaterThan(0);
|
|
|
|
// Find resolution task (the one that isn't the original task)
|
|
const resolutionTaskQueuedEvent = queuedEvents.find(
|
|
(e) => (e as TaskQueuedEvent).payload.taskId !== taskAId
|
|
);
|
|
expect(resolutionTaskQueuedEvent).toBeDefined();
|
|
|
|
// Resolution task should exist and link back to original task
|
|
const resolutionTaskId = (resolutionTaskQueuedEvent as TaskQueuedEvent).payload.taskId;
|
|
const resolutionTask = await harness.taskRepository.findById(resolutionTaskId);
|
|
expect(resolutionTask).toBeDefined();
|
|
|
|
// Resolution task description should contain conflict file info
|
|
expect(resolutionTask?.description).toContain('conflict');
|
|
});
|
|
|
|
it('multiple sequential conflicts resolved in order', async () => {
|
|
vi.useFakeTimers();
|
|
const seeded = await harness.seedFixture(SIMPLE_FIXTURE);
|
|
const taskAId = seeded.tasks.get('Task A')!;
|
|
const taskBId = seeded.tasks.get('Task B')!;
|
|
|
|
// Complete both tasks
|
|
await harness.taskRepository.update(taskAId, { status: 'completed' });
|
|
await harness.taskRepository.update(taskBId, { status: 'completed' });
|
|
|
|
// Set up worktrees and agents for both tasks
|
|
const worktreeIdA = `wt-${taskAId.slice(0, 6)}`;
|
|
const worktreeIdB = `wt-${taskBId.slice(0, 6)}`;
|
|
|
|
await harness.agentRepository.create({
|
|
name: `agent-${taskAId.slice(0, 6)}`,
|
|
worktreeId: worktreeIdA,
|
|
taskId: taskAId,
|
|
status: 'idle',
|
|
});
|
|
await harness.agentRepository.create({
|
|
name: `agent-${taskBId.slice(0, 6)}`,
|
|
worktreeId: worktreeIdB,
|
|
taskId: taskBId,
|
|
status: 'idle',
|
|
});
|
|
|
|
await harness.worktreeManager.create(worktreeIdA, 'feature-task-a');
|
|
await harness.worktreeManager.create(worktreeIdB, 'feature-task-b');
|
|
|
|
// Set conflicts for both
|
|
harness.worktreeManager.setMergeResult(worktreeIdA, {
|
|
success: false,
|
|
conflicts: ['src/shared-a.ts'],
|
|
message: 'Conflict A',
|
|
});
|
|
harness.worktreeManager.setMergeResult(worktreeIdB, {
|
|
success: false,
|
|
conflicts: ['src/shared-b.ts'],
|
|
message: 'Conflict B',
|
|
});
|
|
|
|
// Queue both for merge
|
|
await harness.coordinationManager.queueMerge(taskAId);
|
|
await harness.coordinationManager.queueMerge(taskBId);
|
|
harness.clearEvents();
|
|
|
|
// Process merges - both should fail
|
|
const conflictResults = await harness.coordinationManager.processMerges('main');
|
|
expect(conflictResults.filter((r) => !r.success).length).toBe(2);
|
|
|
|
// Verify both are in conflicted state
|
|
const queueState = await harness.coordinationManager.getQueueState();
|
|
expect(queueState.conflicted.length).toBe(2);
|
|
|
|
// Resolve Task A's conflict
|
|
harness.worktreeManager.setMergeResult(worktreeIdA, {
|
|
success: true,
|
|
message: 'Merged A',
|
|
});
|
|
await harness.taskRepository.update(taskAId, { status: 'completed' });
|
|
await harness.coordinationManager.queueMerge(taskAId);
|
|
harness.clearEvents();
|
|
|
|
const resultA = await harness.coordinationManager.processMerges('main');
|
|
expect(resultA.length).toBe(1);
|
|
expect(resultA[0].taskId).toBe(taskAId);
|
|
expect(resultA[0].success).toBe(true);
|
|
|
|
// Verify merge:completed for A
|
|
const completedEventsA = harness.getEventsByType('merge:completed');
|
|
expect(completedEventsA.length).toBe(1);
|
|
expect((completedEventsA[0] as MergeCompletedEvent).payload.taskId).toBe(taskAId);
|
|
|
|
// Resolve Task B's conflict
|
|
harness.worktreeManager.setMergeResult(worktreeIdB, {
|
|
success: true,
|
|
message: 'Merged B',
|
|
});
|
|
await harness.taskRepository.update(taskBId, { status: 'completed' });
|
|
await harness.coordinationManager.queueMerge(taskBId);
|
|
harness.clearEvents();
|
|
|
|
const resultB = await harness.coordinationManager.processMerges('main');
|
|
expect(resultB.length).toBe(1);
|
|
expect(resultB[0].taskId).toBe(taskBId);
|
|
expect(resultB[0].success).toBe(true);
|
|
|
|
// Verify merge:completed for B
|
|
const completedEventsB = harness.getEventsByType('merge:completed');
|
|
expect(completedEventsB.length).toBe(1);
|
|
expect((completedEventsB[0] as MergeCompletedEvent).payload.taskId).toBe(taskBId);
|
|
|
|
// Verify final merged list has both
|
|
const finalState = await harness.coordinationManager.getQueueState();
|
|
expect(finalState.merged).toContain(taskAId);
|
|
expect(finalState.merged).toContain(taskBId);
|
|
});
|
|
});
|
|
|
|
// ===========================================================================
|
|
// Multi-agent Parallel Work
|
|
// ===========================================================================
|
|
|
|
describe('Multi-agent parallel work', () => {
|
|
it('multiple agents complete tasks in parallel', async () => {
|
|
vi.useFakeTimers();
|
|
const seeded = await harness.seedFixture(PARALLEL_FIXTURE);
|
|
const taskXId = seeded.tasks.get('Task X')!;
|
|
const taskYId = seeded.tasks.get('Task Y')!;
|
|
const taskPId = seeded.tasks.get('Task P')!;
|
|
const taskQId = seeded.tasks.get('Task Q')!;
|
|
|
|
// Pre-seed 3 idle agents
|
|
await harness.agentManager.spawn({
|
|
name: 'pool-agent-1',
|
|
taskId: 'placeholder-1',
|
|
prompt: 'placeholder',
|
|
});
|
|
await harness.agentManager.spawn({
|
|
name: 'pool-agent-2',
|
|
taskId: 'placeholder-2',
|
|
prompt: 'placeholder',
|
|
});
|
|
await harness.agentManager.spawn({
|
|
name: 'pool-agent-3',
|
|
taskId: 'placeholder-3',
|
|
prompt: 'placeholder',
|
|
});
|
|
await harness.advanceTimers();
|
|
harness.clearEvents();
|
|
|
|
// Queue all 4 tasks
|
|
await harness.dispatchManager.queue(taskXId);
|
|
await harness.dispatchManager.queue(taskYId);
|
|
await harness.dispatchManager.queue(taskPId);
|
|
await harness.dispatchManager.queue(taskQId);
|
|
harness.clearEvents();
|
|
|
|
// Dispatch 3 tasks in parallel (3 agents working)
|
|
const result1 = await harness.dispatchManager.dispatchNext();
|
|
const result2 = await harness.dispatchManager.dispatchNext();
|
|
const result3 = await harness.dispatchManager.dispatchNext();
|
|
|
|
expect(result1.success).toBe(true);
|
|
expect(result2.success).toBe(true);
|
|
expect(result3.success).toBe(true);
|
|
|
|
// All 3 should be dispatched to different agents
|
|
const dispatchedIds = [result1.agentId, result2.agentId, result3.agentId];
|
|
expect(new Set(dispatchedIds).size).toBe(3);
|
|
|
|
// Advance timers to complete all 3 agents
|
|
await harness.advanceTimers();
|
|
|
|
// Verify: 3 agent:stopped events
|
|
const stoppedEvents = harness.getEventsByType('agent:stopped');
|
|
expect(stoppedEvents.length).toBe(3);
|
|
|
|
// Complete all 3 tasks
|
|
await harness.dispatchManager.completeTask(result1.taskId!);
|
|
await harness.dispatchManager.completeTask(result2.taskId!);
|
|
await harness.dispatchManager.completeTask(result3.taskId!);
|
|
|
|
// Dispatch remaining task (Task Q)
|
|
const result4 = await harness.dispatchManager.dispatchNext();
|
|
expect(result4.success).toBe(true);
|
|
|
|
await harness.advanceTimers();
|
|
await harness.dispatchManager.completeTask(result4.taskId!);
|
|
|
|
// Verify: all 4 tasks completed in database
|
|
const tasks = await Promise.all([
|
|
harness.taskRepository.findById(taskXId),
|
|
harness.taskRepository.findById(taskYId),
|
|
harness.taskRepository.findById(taskPId),
|
|
harness.taskRepository.findById(taskQId),
|
|
]);
|
|
expect(tasks.every((t) => t?.status === 'completed')).toBe(true);
|
|
});
|
|
|
|
it('parallel merges process in correct dependency order', async () => {
|
|
vi.useFakeTimers();
|
|
const seeded = await harness.seedFixture(COMPLEX_FIXTURE);
|
|
|
|
const task1AId = seeded.tasks.get('Task 1A')!;
|
|
const task1BId = seeded.tasks.get('Task 1B')!;
|
|
const task2AId = seeded.tasks.get('Task 2A')!;
|
|
const task3AId = seeded.tasks.get('Task 3A')!;
|
|
const task4AId = seeded.tasks.get('Task 4A')!;
|
|
|
|
// Complete Task 1A and Task 1B (no dependencies)
|
|
await harness.taskRepository.update(task1AId, { status: 'completed' });
|
|
await harness.taskRepository.update(task1BId, { status: 'completed' });
|
|
|
|
// Set up worktrees and agents for both
|
|
const wt1A = `wt-${task1AId.slice(0, 6)}`;
|
|
const wt1B = `wt-${task1BId.slice(0, 6)}`;
|
|
|
|
await harness.agentRepository.create({
|
|
name: `agent-${task1AId.slice(0, 6)}`,
|
|
worktreeId: wt1A,
|
|
taskId: task1AId,
|
|
status: 'idle',
|
|
});
|
|
await harness.agentRepository.create({
|
|
name: `agent-${task1BId.slice(0, 6)}`,
|
|
worktreeId: wt1B,
|
|
taskId: task1BId,
|
|
status: 'idle',
|
|
});
|
|
|
|
await harness.worktreeManager.create(wt1A, 'feature-1a');
|
|
await harness.worktreeManager.create(wt1B, 'feature-1b');
|
|
|
|
// Queue both for merge
|
|
await harness.coordinationManager.queueMerge(task1AId);
|
|
await harness.coordinationManager.queueMerge(task1BId);
|
|
harness.clearEvents();
|
|
|
|
// Process merges - both should succeed (no dependencies between them)
|
|
const results1 = await harness.coordinationManager.processMerges('main');
|
|
expect(results1.length).toBe(2);
|
|
expect(results1.every((r) => r.success)).toBe(true);
|
|
|
|
// Verify: merge:completed for both in same batch
|
|
const completed1 = harness.getEventsByType('merge:completed');
|
|
expect(completed1.length).toBe(2);
|
|
|
|
// Complete Task 2A (depends on 1A) and Task 3A (depends on 1B)
|
|
await harness.taskRepository.update(task2AId, { status: 'completed' });
|
|
await harness.taskRepository.update(task3AId, { status: 'completed' });
|
|
|
|
const wt2A = `wt-${task2AId.slice(0, 6)}`;
|
|
const wt3A = `wt-${task3AId.slice(0, 6)}`;
|
|
|
|
await harness.agentRepository.create({
|
|
name: `agent-${task2AId.slice(0, 6)}`,
|
|
worktreeId: wt2A,
|
|
taskId: task2AId,
|
|
status: 'idle',
|
|
});
|
|
await harness.agentRepository.create({
|
|
name: `agent-${task3AId.slice(0, 6)}`,
|
|
worktreeId: wt3A,
|
|
taskId: task3AId,
|
|
status: 'idle',
|
|
});
|
|
|
|
await harness.worktreeManager.create(wt2A, 'feature-2a');
|
|
await harness.worktreeManager.create(wt3A, 'feature-3a');
|
|
|
|
// Queue and merge
|
|
await harness.coordinationManager.queueMerge(task2AId);
|
|
await harness.coordinationManager.queueMerge(task3AId);
|
|
harness.clearEvents();
|
|
|
|
const results2 = await harness.coordinationManager.processMerges('main');
|
|
expect(results2.length).toBe(2);
|
|
expect(results2.every((r) => r.success)).toBe(true);
|
|
|
|
// Complete Task 4A (depends on 2A and 3A)
|
|
await harness.taskRepository.update(task4AId, { status: 'completed' });
|
|
|
|
const wt4A = `wt-${task4AId.slice(0, 6)}`;
|
|
await harness.agentRepository.create({
|
|
name: `agent-${task4AId.slice(0, 6)}`,
|
|
worktreeId: wt4A,
|
|
taskId: task4AId,
|
|
status: 'idle',
|
|
});
|
|
await harness.worktreeManager.create(wt4A, 'feature-4a');
|
|
|
|
// Queue and merge
|
|
await harness.coordinationManager.queueMerge(task4AId);
|
|
harness.clearEvents();
|
|
|
|
const results3 = await harness.coordinationManager.processMerges('main');
|
|
expect(results3.length).toBe(1);
|
|
expect(results3[0].taskId).toBe(task4AId);
|
|
expect(results3[0].success).toBe(true);
|
|
|
|
// Verify: final merge order respects dependency graph
|
|
const finalState = await harness.coordinationManager.getQueueState();
|
|
expect(finalState.merged).toContain(task1AId);
|
|
expect(finalState.merged).toContain(task1BId);
|
|
expect(finalState.merged).toContain(task2AId);
|
|
expect(finalState.merged).toContain(task3AId);
|
|
expect(finalState.merged).toContain(task4AId);
|
|
});
|
|
|
|
it('parallel dispatch with mixed outcomes', async () => {
|
|
vi.useFakeTimers();
|
|
const seeded = await harness.seedFixture(PARALLEL_FIXTURE);
|
|
const taskXId = seeded.tasks.get('Task X')!;
|
|
const taskYId = seeded.tasks.get('Task Y')!;
|
|
|
|
// Pre-seed 2 agents
|
|
await harness.agentManager.spawn({
|
|
name: 'pool-agent-1',
|
|
taskId: 'placeholder-1',
|
|
prompt: 'placeholder',
|
|
});
|
|
await harness.agentManager.spawn({
|
|
name: 'pool-agent-2',
|
|
taskId: 'placeholder-2',
|
|
prompt: 'placeholder',
|
|
});
|
|
await harness.advanceTimers();
|
|
|
|
// Set Task X to succeed, Task Y to crash
|
|
harness.setAgentDone(`agent-${taskXId.slice(0, 6)}`, 'Task X completed');
|
|
harness.setAgentError(`agent-${taskYId.slice(0, 6)}`, 'Out of memory error');
|
|
|
|
// Queue both tasks
|
|
await harness.dispatchManager.queue(taskXId);
|
|
await harness.dispatchManager.queue(taskYId);
|
|
harness.clearEvents();
|
|
|
|
// Dispatch both tasks
|
|
const result1 = await harness.dispatchManager.dispatchNext();
|
|
const result2 = await harness.dispatchManager.dispatchNext();
|
|
|
|
// Both should dispatch successfully
|
|
expect(result1.success).toBe(true);
|
|
expect(result2.success).toBe(true);
|
|
|
|
// Run timers to complete agents
|
|
await harness.advanceTimers();
|
|
|
|
// Verify: one agent:stopped, one agent:crashed
|
|
const stoppedEvents = harness.getEventsByType('agent:stopped');
|
|
const crashedEvents = harness.getEventsByType('agent:crashed');
|
|
|
|
expect(stoppedEvents.length).toBe(1);
|
|
expect(crashedEvents.length).toBe(1);
|
|
|
|
// Identify which task succeeded and which crashed
|
|
const stoppedPayload = (stoppedEvents[0] as AgentStoppedEvent).payload;
|
|
const crashedPayload = (crashedEvents[0] as AgentCrashedEvent).payload;
|
|
|
|
// Find the successful task
|
|
const successTaskId = stoppedPayload.taskId;
|
|
const crashedTaskId = crashedPayload.taskId;
|
|
|
|
// Complete the successful task
|
|
await harness.dispatchManager.completeTask(successTaskId!);
|
|
|
|
// Verify: completed task is actually completed
|
|
const completedTask = await harness.taskRepository.findById(successTaskId!);
|
|
expect(completedTask?.status).toBe('completed');
|
|
|
|
// Verify: crashed task stays in_progress
|
|
const inProgressTask = await harness.taskRepository.findById(crashedTaskId!);
|
|
expect(inProgressTask?.status).toBe('in_progress');
|
|
|
|
// Verify: completed task can merge (set up infrastructure)
|
|
const wtSuccess = `wt-${successTaskId!.slice(0, 6)}`;
|
|
await harness.agentRepository.create({
|
|
name: `merge-agent-${successTaskId!.slice(0, 6)}`,
|
|
worktreeId: wtSuccess,
|
|
taskId: successTaskId!,
|
|
status: 'idle',
|
|
});
|
|
await harness.worktreeManager.create(wtSuccess, 'feature-success');
|
|
|
|
await harness.coordinationManager.queueMerge(successTaskId!);
|
|
const mergeResults = await harness.coordinationManager.processMerges('main');
|
|
|
|
expect(mergeResults.length).toBe(1);
|
|
expect(mergeResults[0].success).toBe(true);
|
|
});
|
|
});
|
|
});
|