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
159 lines
5.3 KiB
TypeScript
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);
|
|
}
|
|
}
|
|
} |