All files / src/coordination conflict-resolution-service.ts

96% Statements 24/25
83.33% Branches 10/12
100% Functions 4/4
96% Lines 24/25

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159                                                                                                                115x 115x 115x 115x                 24x 24x 2x       22x 22x 2x       20x     28x           20x             20x         20x     20x                           20x     20x 19x       27x             19x                       20x 19x                 19x      
/**
 * ConflictResolutionService
 *
 * Service responsible for handling merge conflicts by:
 * - Creating conflict resolution tasks
 * - Updating original task status
 * - Notifying agents via messages
 * - Emitting appropriate events
 *
 * This service is used by the CoordinationManager when merge conflicts occur.
 */
 
import type { EventBus, TaskQueuedEvent } from '../events/index.js';
import type { TaskRepository } from '../db/repositories/task-repository.js';
import type { AgentRepository } from '../db/repositories/agent-repository.js';
import type { MessageRepository } from '../db/repositories/message-repository.js';
 
// =============================================================================
// ConflictResolutionService Interface (Port)
// =============================================================================
 
/**
 * Service interface for handling merge conflicts.
 * This is the PORT - implementations are ADAPTERS.
 */
/**
 * Branch context for merge conflicts from the branch hierarchy.
 */
export interface MergeContext {
  sourceBranch: string;
  targetBranch: string;
}
 
export interface ConflictResolutionService {
  /**
   * Handle a merge conflict by creating resolution task and notifying agent.
   *
   * @param taskId - ID of the task that conflicted
   * @param conflicts - List of conflicting file paths
   * @param mergeContext - Optional branch context for branch hierarchy merges
   */
  handleConflict(taskId: string, conflicts: string[], mergeContext?: MergeContext): Promise<void>;
}
 
// =============================================================================
// DefaultConflictResolutionService Implementation (Adapter)
// =============================================================================
 
/**
 * Default implementation of ConflictResolutionService.
 *
 * Creates conflict resolution tasks, updates task statuses, sends messages
 * to agents, and emits events when merge conflicts occur.
 */
export class DefaultConflictResolutionService implements ConflictResolutionService {
  constructor(
    private taskRepository: TaskRepository,
    private agentRepository: AgentRepository,
    private messageRepository?: MessageRepository,
    private eventBus?: EventBus
  ) {}
 
  /**
   * Handle a merge conflict.
   * Creates a conflict-resolution task and notifies the agent via message.
   */
  async handleConflict(taskId: string, conflicts: string[], mergeContext?: MergeContext): Promise<void> {
    // Get original task for context
    const originalTask = await this.taskRepository.findById(taskId);
    if (!originalTask) {
      throw new Error(`Original task not found: ${taskId}`);
    }
 
    // Get agent that was working on the task
    const agent = await this.agentRepository.findByTaskId(taskId);
    if (!agent) {
      throw new Error(`No agent found for task: ${taskId}`);
    }
 
    // Build conflict description
    const descriptionLines = [
      'Merge conflicts detected. Resolve conflicts in the following files:',
      '',
      ...conflicts.map((f) => `- ${f}`),
      '',
      `Original task: ${originalTask.name}`,
      '',
    ];
 
    Iif (mergeContext) {
      descriptionLines.push(
        `Resolve merge conflicts between branch "${mergeContext.sourceBranch}" and "${mergeContext.targetBranch}".`,
        `Run: git merge ${mergeContext.sourceBranch} --no-edit`,
        'Resolve all conflicts, then: git add . && git commit',
      );
    } else {
      descriptionLines.push(
        'Instructions: Resolve merge conflicts in the listed files, then mark task complete.',
      );
    }
 
    const conflictDescription = descriptionLines.join('\n');
 
    // Create new conflict-resolution task
    const conflictTask = await this.taskRepository.create({
      parentTaskId: originalTask.parentTaskId,
      phaseId: originalTask.phaseId,
      initiativeId: originalTask.initiativeId,
      name: `Resolve conflicts: ${originalTask.name}`,
      description: conflictDescription,
      category: mergeContext ? 'merge' : 'execute',
      type: 'auto',
      priority: 'high',
      status: 'pending',
      order: originalTask.order + 1,
    });
 
    // Update original task status to blocked
    await this.taskRepository.update(taskId, { status: 'blocked' });
 
    // Create message to agent if messageRepository is configured
    if (this.messageRepository) {
      const messageContent = [
        `Merge conflict detected for task: ${originalTask.name}`,
        '',
        'Conflicting files:',
        ...conflicts.map((f) => `- ${f}`),
        '',
        `A new task has been created to resolve these conflicts: ${conflictTask.name}`,
        '',
        'Please resolve the merge conflicts in the listed files and mark the resolution task as complete.',
      ].join('\n');
 
      await this.messageRepository.create({
        senderType: 'user', // System-generated messages appear as from user
        senderId: null,
        recipientType: 'agent',
        recipientId: agent.id,
        type: 'info',
        content: messageContent,
        requiresResponse: false,
      });
    }
 
    // Emit TaskQueuedEvent for the new conflict-resolution task
    if (this.eventBus) {
      const event: TaskQueuedEvent = {
        type: 'task:queued',
        timestamp: new Date(),
        payload: {
          taskId: conflictTask.id,
          priority: 'high',
          dependsOn: [],
        },
      };
      this.eventBus.emit(event);
    }
  }
}