Files
Codewalkers/apps/server/coordination/conflict-resolution-service.ts
Lukas May 34578d39c6 refactor: Restructure monorepo to apps/server/ and apps/web/ layout
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
2026-03-03 11:22:53 +01:00

159 lines
5.3 KiB
TypeScript

/**
* 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}`,
'',
];
if (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);
}
}
}