vibe-coder-mcp
Version:
Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.
1,131 lines (1,130 loc) • 64.4 kB
JavaScript
import { EnhancedError, ConfigurationError, TaskExecutionError, ValidationError, createErrorContext } from '../utils/enhanced-errors.js';
import logger from '../../../logger.js';
export const DEFAULT_SCHEDULING_CONFIG = {
algorithm: 'hybrid_optimal',
resources: {
maxConcurrentTasks: 10,
maxMemoryMB: 4096,
maxCpuUtilization: 0.8,
availableAgents: 3,
taskTypeResources: new Map([
['development', { memoryMB: 512, cpuWeight: 0.7, agentCount: 1 }],
['testing', { memoryMB: 256, cpuWeight: 0.5, agentCount: 1 }],
['documentation', { memoryMB: 128, cpuWeight: 0.3, agentCount: 1 }],
['research', { memoryMB: 256, cpuWeight: 0.4, agentCount: 1 }],
['deployment', { memoryMB: 1024, cpuWeight: 0.9, agentCount: 1 }],
['review', { memoryMB: 128, cpuWeight: 0.2, agentCount: 1 }]
])
},
weights: {
dependencies: 0.35,
deadline: 0.25,
systemLoad: 0.20,
complexity: 0.10,
businessImpact: 0.05,
agentAvailability: 0.05,
priority: 0.0,
resources: 0.0,
duration: 0.0
},
deadlineBuffer: 2,
rescheduleSensitivity: 'medium',
enableDynamicOptimization: true,
optimizationInterval: 30
};
export class TaskScheduler {
config;
currentSchedule = null;
optimizationTimer = null;
scheduleVersion = 0;
static currentInstance = null;
constructor(config = {}) {
this.config = { ...DEFAULT_SCHEDULING_CONFIG, ...config };
if (this.config.enableDynamicOptimization) {
this.startOptimizationTimer();
}
TaskScheduler.currentInstance = this;
logger.info('TaskScheduler initialized', {
algorithm: this.config.algorithm,
maxConcurrentTasks: this.config.resources.maxConcurrentTasks,
enableDynamicOptimization: this.config.enableDynamicOptimization
});
}
static getCurrentInstance() {
return TaskScheduler.currentInstance;
}
static resetCurrentInstance() {
TaskScheduler.currentInstance = null;
}
static hasCurrentInstance() {
return TaskScheduler.currentInstance !== null;
}
async generateSchedule(tasks, dependencyGraph, projectId) {
const startTime = Date.now();
try {
logger.info('Generating execution schedule', {
taskCount: tasks.length,
algorithm: this.config.algorithm,
projectId
});
this.validateSchedulingInputs(tasks, dependencyGraph);
const taskScores = this.calculateTaskScores(tasks, dependencyGraph);
const parallelBatches = dependencyGraph.getParallelBatches();
const scheduledTasks = await this.applySchedulingAlgorithm(tasks, taskScores, parallelBatches, dependencyGraph);
const timeline = this.calculateTimeline(scheduledTasks, parallelBatches);
const resourceUtilization = this.calculateResourceUtilization(scheduledTasks);
const schedule = {
id: `schedule_${projectId}_${Date.now()}`,
projectId,
scheduledTasks,
executionBatches: parallelBatches,
timeline,
resourceUtilization,
metadata: {
algorithm: this.config.algorithm,
config: { ...this.config },
generatedAt: new Date(),
optimizedAt: new Date(),
version: ++this.scheduleVersion,
isOptimal: false
}
};
const optimizedSchedule = await this.optimizeSchedule(schedule);
this.currentSchedule = optimizedSchedule;
const generationTime = Date.now() - startTime;
logger.info('Schedule generated successfully', {
scheduleId: schedule.id,
taskCount: tasks.length,
batchCount: parallelBatches.length,
generationTime,
algorithm: this.config.algorithm
});
return optimizedSchedule;
}
catch (error) {
const context = createErrorContext('TaskScheduler', 'generateSchedule')
.projectId(projectId)
.metadata({
taskCount: tasks.length,
algorithm: this.config.algorithm,
generationTime: Date.now() - startTime
})
.build();
if (error instanceof EnhancedError) {
throw error;
}
if (error instanceof Error) {
if (error.message.includes('validation') || error.message.includes('invalid')) {
throw new ValidationError(`Schedule generation validation failed: ${error.message}`, context, {
cause: error,
field: 'tasks',
expectedFormat: 'Array of valid AtomicTask objects'
});
}
if (error.message.includes('algorithm') || error.message.includes('config')) {
throw new ConfigurationError(`Schedule generation configuration error: ${error.message}`, context, {
cause: error,
configKey: 'algorithm',
actualValue: this.config.algorithm
});
}
throw new TaskExecutionError(`Schedule generation failed: ${error.message}`, context, {
cause: error,
retryable: true
});
}
throw new TaskExecutionError(`Schedule generation failed with unknown error: ${String(error)}`, context, {
retryable: false
});
}
}
async updateSchedule(updatedTasks, dependencyGraph) {
const context = createErrorContext('TaskScheduler', 'updateSchedule')
.metadata({
updatedTaskCount: updatedTasks.length,
hasCurrentSchedule: !!this.currentSchedule
})
.build();
if (!this.currentSchedule) {
throw new ValidationError('No current schedule exists to update. Generate a schedule first.', context, {
field: 'currentSchedule',
expectedFormat: 'Valid ExecutionSchedule object'
});
}
try {
logger.info('Updating existing schedule', {
scheduleId: this.currentSchedule.id,
updatedTaskCount: updatedTasks.length
});
if (!Array.isArray(updatedTasks) || updatedTasks.length === 0) {
throw new ValidationError('Updated tasks must be a non-empty array of AtomicTask objects', context, {
field: 'updatedTasks',
expectedFormat: 'Array<AtomicTask>',
actualValue: updatedTasks
});
}
const needsReschedule = this.shouldReschedule(updatedTasks);
if (needsReschedule) {
return this.generateSchedule(updatedTasks, dependencyGraph, this.currentSchedule.projectId);
}
else {
return this.incrementalUpdate(updatedTasks, dependencyGraph);
}
}
catch (error) {
if (error instanceof EnhancedError) {
throw error;
}
throw new TaskExecutionError(`Schedule update failed: ${error instanceof Error ? error.message : String(error)}`, context, {
cause: error instanceof Error ? error : undefined,
retryable: true
});
}
}
getCurrentSchedule() {
return this.currentSchedule;
}
getReadyTasks() {
if (!this.currentSchedule) {
return [];
}
const now = new Date();
const readyTasks = [];
for (const scheduledTask of this.currentSchedule.scheduledTasks.values()) {
if (scheduledTask.scheduledStart <= now &&
scheduledTask.task.status === 'pending' &&
this.arePrerequisitesComplete(scheduledTask)) {
readyTasks.push(scheduledTask);
}
}
return readyTasks.sort((a, b) => b.metadata.priorityScore - a.metadata.priorityScore);
}
getNextExecutionBatch() {
if (!this.currentSchedule) {
return null;
}
const readyTasks = this.getReadyTasks();
if (readyTasks.length === 0) {
return null;
}
for (const batch of this.currentSchedule.executionBatches) {
const batchTasks = batch.taskIds.map(id => this.currentSchedule.scheduledTasks.get(id)).filter(task => task !== undefined);
const allReady = batchTasks.every(task => readyTasks.some(ready => ready.task.id === task.task.id));
if (allReady) {
return batch;
}
}
return null;
}
async markTaskCompleted(taskId) {
if (!this.currentSchedule) {
return;
}
const scheduledTask = this.currentSchedule.scheduledTasks.get(taskId);
if (!scheduledTask) {
logger.warn('Task not found in current schedule', { taskId });
return;
}
scheduledTask.task.status = 'completed';
scheduledTask.task.actualHours = this.calculateActualHours(scheduledTask);
logger.info('Task marked as completed', {
taskId,
actualHours: scheduledTask.task.actualHours,
estimatedHours: scheduledTask.task.estimatedHours
});
if (this.config.enableDynamicOptimization) {
await this.checkForOptimization();
}
}
async executeScheduledTasks() {
if (!this.currentSchedule) {
return {
success: false,
executedTasks: [],
queuedTasks: [],
errors: [{ taskId: 'N/A', error: 'No current schedule available' }]
};
}
await this.waitForExecutionDependencies();
const executedTasks = [];
const queuedTasks = [];
const errors = [];
try {
const readyTasks = this.getReadyTasks();
if (readyTasks.length === 0) {
logger.debug('No ready tasks for execution');
return {
success: true,
executedTasks,
queuedTasks,
errors
};
}
const { AgentOrchestrator } = await import('./agent-orchestrator.js');
const orchestrator = AgentOrchestrator.getInstance();
logger.info(`Executing ${readyTasks.length} ready tasks`);
for (const scheduledTask of readyTasks) {
try {
const projectContext = {
projectId: scheduledTask.task.projectId,
projectPath: process.cwd(),
projectName: scheduledTask.task.projectId,
description: `Scheduled task execution for ${scheduledTask.task.title}`,
languages: ['typescript', 'javascript'],
frameworks: [],
buildTools: ['npm'],
tools: [],
configFiles: [],
entryPoints: [],
architecturalPatterns: [],
existingTasks: [],
codebaseSize: 'medium',
teamSize: 1,
complexity: 'medium',
codebaseContext: {
relevantFiles: [],
contextSummary: `Scheduled task execution for ${scheduledTask.task.title}`,
gatheringMetrics: {
searchTime: 0,
readTime: 0,
scoringTime: 0,
totalTime: 0,
cacheHitRate: 0
},
totalContextSize: 0,
averageRelevance: 0
},
structure: {
sourceDirectories: ['src'],
testDirectories: ['test', 'tests'],
docDirectories: ['docs'],
buildDirectories: ['build', 'dist']
},
dependencies: {
production: [],
development: [],
external: []
},
metadata: {
createdAt: new Date(),
updatedAt: new Date(),
version: '1.0.0',
source: 'auto-detected'
}
};
const result = await orchestrator.executeTask(scheduledTask.task, projectContext, {
priority: this.mapTaskPriorityToExecutionPriority(scheduledTask.task.priority),
timeout: scheduledTask.assignedResources.memoryMB * 1000,
enableMonitoring: true
});
if (result.success) {
executedTasks.push(scheduledTask.task.id);
logger.info(`Task ${scheduledTask.task.id} executed successfully`);
}
else if (result.queued) {
queuedTasks.push(scheduledTask.task.id);
logger.info(`Task ${scheduledTask.task.id} queued for later execution`);
}
else {
errors.push({
taskId: scheduledTask.task.id,
error: result.error || result.message
});
logger.warn(`Task ${scheduledTask.task.id} execution failed: ${result.error || result.message}`);
}
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
errors.push({
taskId: scheduledTask.task.id,
error: errorMessage
});
logger.error({ err: error, taskId: scheduledTask.task.id }, 'Task execution failed with exception');
}
}
logger.info({
executed: executedTasks.length,
queued: queuedTasks.length,
errors: errors.length
}, 'Scheduled task execution completed');
return {
success: errors.length === 0,
executedTasks,
queuedTasks,
errors
};
}
catch (error) {
logger.error({ err: error }, 'Failed to execute scheduled tasks');
return {
success: false,
executedTasks,
queuedTasks,
errors: [{ taskId: 'N/A', error: error instanceof Error ? error.message : 'Unknown error' }]
};
}
}
mapTaskPriorityToExecutionPriority(taskPriority) {
return taskPriority;
}
dispose() {
if (this.isDisposed) {
return;
}
this.isDisposed = true;
if (this.optimizationTimer) {
clearInterval(this.optimizationTimer);
this.optimizationTimer = null;
}
this.currentSchedule = null;
if (TaskScheduler.currentInstance === this) {
TaskScheduler.currentInstance = null;
}
logger.info('TaskScheduler disposed');
}
isDisposed = false;
async waitForExecutionDependencies() {
const maxWaitTime = 15000;
const checkInterval = 500;
const startTime = Date.now();
while (Date.now() - startTime < maxWaitTime) {
try {
try {
const { ExecutionCoordinator } = await import('./execution-coordinator.js');
const coordinator = await ExecutionCoordinator.getInstance();
if (!coordinator.getRunningStatus()) {
throw new Error('ExecutionCoordinator not running');
}
}
catch {
logger.debug('ExecutionCoordinator not available, continuing without it');
}
try {
const { TransportManager } = await import('../../../services/transport-manager/index.js');
const transportManager = TransportManager.getInstance();
const status = transportManager.getStatus();
if (status.isConfigured && !status.isStarted) {
throw new Error('Transport Manager not ready');
}
}
catch {
logger.debug('Transport Manager not available, continuing without it');
}
logger.debug('All execution dependencies ready for TaskScheduler');
return;
}
catch {
await new Promise(resolve => setTimeout(resolve, checkInterval));
}
}
logger.warn('Timeout waiting for execution dependencies, proceeding anyway');
}
validateSchedulingInputs(tasks, dependencyGraph) {
if (tasks.length === 0) {
throw new Error('Cannot schedule empty task list');
}
for (const task of tasks) {
if (!task.id || !task.title || task.estimatedHours === undefined || task.estimatedHours < 0) {
throw new Error(`Invalid task: ${task.id} - missing required fields`);
}
}
const graphNodes = dependencyGraph.getNodes();
const taskIds = new Set(tasks.map(t => t.id));
for (const taskId of taskIds) {
if (!graphNodes.has(taskId)) {
logger.warn('Task not found in dependency graph', { taskId });
}
}
}
calculateTaskScores(tasks, dependencyGraph) {
const scores = new Map();
const criticalPath = dependencyGraph.getCriticalPath();
const criticalPathSet = new Set(criticalPath);
for (const task of tasks) {
const priorityScore = this.calculatePriorityScore(task.priority);
const deadlineScore = this.calculateDeadlineScore(task);
const dependencyScore = this.calculateDependencyScore(task, dependencyGraph, criticalPathSet);
const resourceScore = this.calculateResourceScore(task);
const durationScore = this.calculateDurationScore(task);
const systemLoadScore = this.calculateSystemLoadScore(task);
const complexityScore = this.calculateComplexityScore(task);
const businessImpactScore = this.calculateBusinessImpactScore(task);
const agentAvailabilityScore = this.calculateAgentAvailabilityScore(task);
const totalScore = priorityScore * this.config.weights.priority +
deadlineScore * this.config.weights.deadline +
dependencyScore * this.config.weights.dependencies +
resourceScore * this.config.weights.resources +
durationScore * this.config.weights.duration +
systemLoadScore * this.config.weights.systemLoad +
complexityScore * this.config.weights.complexity +
businessImpactScore * this.config.weights.businessImpact +
agentAvailabilityScore * this.config.weights.agentAvailability;
scores.set(task.id, {
priorityScore,
deadlineScore,
dependencyScore,
resourceScore,
durationScore,
systemLoadScore,
complexityScore,
businessImpactScore,
agentAvailabilityScore,
totalScore
});
}
return scores;
}
async applySchedulingAlgorithm(tasks, taskScores, parallelBatches, dependencyGraph) {
switch (this.config.algorithm) {
case 'priority_first':
return this.priorityFirstScheduling(tasks, taskScores, parallelBatches);
case 'earliest_deadline':
return this.earliestDeadlineScheduling(tasks, taskScores, parallelBatches);
case 'critical_path':
return this.criticalPathScheduling(tasks, taskScores, parallelBatches, dependencyGraph);
case 'resource_balanced':
return this.resourceBalancedScheduling(tasks, taskScores, parallelBatches);
case 'shortest_job':
return this.shortestJobScheduling(tasks, taskScores, parallelBatches);
case 'hybrid_optimal':
return this.hybridOptimalScheduling(tasks, taskScores, parallelBatches, dependencyGraph);
default:
throw new Error(`Unknown scheduling algorithm: ${this.config.algorithm}`);
}
}
priorityFirstScheduling(tasks, taskScores, parallelBatches) {
const scheduledTasks = new Map();
const sortedTasks = tasks.sort((a, b) => {
const scoreA = taskScores.get(a.id)?.priorityScore || 0;
const scoreB = taskScores.get(b.id)?.priorityScore || 0;
return scoreB - scoreA;
});
let currentTime = new Date();
let batchId = 0;
for (const batch of parallelBatches) {
const batchTasks = batch.taskIds
.map(id => sortedTasks.find(t => t.id === id))
.filter(task => task !== undefined);
for (const task of batchTasks) {
const scores = taskScores.get(task.id);
const resources = this.allocateResources(task);
const scheduledTask = {
task,
scheduledStart: new Date(currentTime),
scheduledEnd: new Date(currentTime.getTime() + task.estimatedHours * 60 * 60 * 1000),
assignedResources: resources,
batchId,
prerequisiteTasks: task.dependencies,
dependentTasks: task.dependents,
metadata: {
algorithm: 'priority_first',
priorityScore: scores?.priorityScore || 0,
resourceScore: scores?.resourceScore || 0,
deadlineScore: scores?.deadlineScore || 0,
dependencyScore: scores?.dependencyScore || 0,
durationScore: scores?.durationScore || 0,
systemLoadScore: scores?.systemLoadScore || 0,
complexityScore: scores?.complexityScore || 0,
businessImpactScore: scores?.businessImpactScore || 0,
agentAvailabilityScore: scores?.agentAvailabilityScore || 0,
totalScore: scores?.totalScore || 0,
scheduledAt: new Date(),
lastOptimized: new Date()
}
};
scheduledTasks.set(task.id, scheduledTask);
}
currentTime = new Date(currentTime.getTime() + batch.estimatedDuration * 60 * 60 * 1000);
batchId++;
}
return scheduledTasks;
}
hybridOptimalScheduling(tasks, taskScores, parallelBatches, dependencyGraph) {
const scheduledTasks = new Map();
const criticalPath = dependencyGraph.getCriticalPath();
const criticalPathSet = new Set(criticalPath);
const sortedTasks = tasks.sort((a, b) => {
const scoreA = taskScores.get(a.id);
const scoreB = taskScores.get(b.id);
const aCritical = criticalPathSet.has(a.id);
const bCritical = criticalPathSet.has(b.id);
if (aCritical !== bCritical) {
return bCritical ? 1 : -1;
}
const totalA = scoreA?.totalScore || 0;
const totalB = scoreB?.totalScore || 0;
if (Math.abs(totalA - totalB) > 0.1) {
return totalB - totalA;
}
const priorityOrder = { 'critical': 4, 'high': 3, 'medium': 2, 'low': 1 };
const priorityA = priorityOrder[a.priority] || 0;
const priorityB = priorityOrder[b.priority] || 0;
if (priorityA !== priorityB) {
return priorityB - priorityA;
}
return a.estimatedHours - b.estimatedHours;
});
let currentTime = new Date();
let batchId = 0;
for (const batch of parallelBatches) {
const batchTasks = this.optimizeBatchOrder(batch, sortedTasks, taskScores);
const batchStartTime = new Date(currentTime);
for (const task of batchTasks) {
const scores = taskScores.get(task.id);
const resources = this.allocateOptimalResources(task, batchTasks);
const scheduledTask = {
task,
scheduledStart: batchStartTime,
scheduledEnd: new Date(batchStartTime.getTime() + task.estimatedHours * 60 * 60 * 1000),
assignedResources: resources,
batchId,
prerequisiteTasks: task.dependencies,
dependentTasks: task.dependents,
metadata: {
algorithm: 'hybrid_optimal',
priorityScore: scores?.priorityScore || 0,
resourceScore: scores?.resourceScore || 0,
deadlineScore: scores?.deadlineScore || 0,
dependencyScore: scores?.dependencyScore || 0,
durationScore: scores?.durationScore || 0,
systemLoadScore: scores?.systemLoadScore || 0,
complexityScore: scores?.complexityScore || 0,
businessImpactScore: scores?.businessImpactScore || 0,
agentAvailabilityScore: scores?.agentAvailabilityScore || 0,
totalScore: scores?.totalScore || 0,
scheduledAt: new Date(),
lastOptimized: new Date()
}
};
scheduledTasks.set(task.id, scheduledTask);
}
const maxTaskDuration = Math.max(...batchTasks.map(t => t.estimatedHours));
const bufferTime = maxTaskDuration * 0.1;
currentTime = new Date(currentTime.getTime() + (maxTaskDuration + bufferTime) * 60 * 60 * 1000);
batchId++;
}
return scheduledTasks;
}
optimizeBatchOrder(batch, sortedTasks, taskScores) {
const batchTasks = batch.taskIds
.map(id => sortedTasks.find(t => t.id === id))
.filter(task => task !== undefined);
return batchTasks.sort((a, b) => {
const scoreA = taskScores.get(a.id);
const scoreB = taskScores.get(b.id);
const resourceA = scoreA?.resourceScore || 0;
const resourceB = scoreB?.resourceScore || 0;
if (Math.abs(resourceA - resourceB) > 0.1) {
return resourceB - resourceA;
}
const totalA = scoreA?.totalScore || 0;
const totalB = scoreB?.totalScore || 0;
return totalB - totalA;
});
}
initializeResourceTracker() {
return {
memoryUsed: 0,
cpuUsed: 0,
agentsAssigned: new Set()
};
}
updateResourceTracker(tracker, resources) {
tracker.memoryUsed += resources.memoryMB;
tracker.cpuUsed += resources.cpuWeight;
if (resources.agentId) {
tracker.agentsAssigned.add(resources.agentId);
}
}
calculateOptimalBatchStartTime(batchTasks, scheduledTasks, defaultStartTime) {
let latestPrerequisiteEnd = defaultStartTime;
for (const task of batchTasks) {
for (const depId of task.dependencies) {
const depTask = scheduledTasks.get(depId);
if (depTask && depTask.scheduledEnd > latestPrerequisiteEnd) {
latestPrerequisiteEnd = depTask.scheduledEnd;
}
}
}
return latestPrerequisiteEnd;
}
calculateTimeline(scheduledTasks, _parallelBatches) {
const tasks = Array.from(scheduledTasks.values());
if (tasks.length === 0) {
const now = new Date();
return {
startTime: now,
endTime: now,
totalDuration: 0,
criticalPath: [],
parallelismFactor: 1
};
}
const startTime = new Date(Math.min(...tasks.map(t => t.scheduledStart.getTime())));
const endTime = new Date(Math.max(...tasks.map(t => t.scheduledEnd.getTime())));
const totalDuration = (endTime.getTime() - startTime.getTime()) / (1000 * 60 * 60);
const criticalPath = this.findCriticalPath(scheduledTasks);
const totalTaskHours = tasks.reduce((sum, t) => sum + t.task.estimatedHours, 0);
const parallelismFactor = totalTaskHours / totalDuration;
return {
startTime,
endTime,
totalDuration,
criticalPath,
parallelismFactor
};
}
calculateResourceUtilization(scheduledTasks) {
const tasks = Array.from(scheduledTasks.values());
if (tasks.length === 0) {
return {
peakMemoryMB: 0,
averageCpuUtilization: 0,
agentUtilization: 0,
resourceEfficiency: 0
};
}
const peakMemoryMB = Math.max(...tasks.map(t => t.assignedResources.memoryMB));
const totalCpuWeight = tasks.reduce((sum, t) => sum + t.assignedResources.cpuWeight, 0);
const averageCpuUtilization = totalCpuWeight / tasks.length;
const assignedAgents = new Set(tasks.map(t => t.assignedResources.agentId).filter(id => id !== undefined)).size;
const agentUtilization = assignedAgents / this.config.resources.availableAgents;
const memoryEfficiency = peakMemoryMB / this.config.resources.maxMemoryMB;
const cpuEfficiency = averageCpuUtilization / this.config.resources.maxCpuUtilization;
const resourceEfficiency = (memoryEfficiency + cpuEfficiency + agentUtilization) / 3;
return {
peakMemoryMB,
averageCpuUtilization,
agentUtilization,
resourceEfficiency
};
}
async optimizeSchedule(schedule) {
let optimizedSchedule = { ...schedule };
optimizedSchedule = this.optimizeResourceAllocation(optimizedSchedule);
optimizedSchedule = this.optimizeParallelExecution(optimizedSchedule);
optimizedSchedule = this.optimizeDeadlineCompliance(optimizedSchedule);
optimizedSchedule.metadata.isOptimal = true;
optimizedSchedule.metadata.optimizedAt = new Date();
return optimizedSchedule;
}
calculatePriorityScore(priority) {
const priorityMap = {
'critical': 1.0,
'high': 0.8,
'medium': 0.6,
'low': 0.4
};
return priorityMap[priority] || 0.5;
}
calculateDeadlineScore(task) {
const now = new Date();
const priorityMultipliers = {
'critical': 1.0,
'high': 2.0,
'medium': 4.0,
'low': 8.0
};
const multiplier = priorityMultipliers[task.priority] || 4.0;
const impliedDeadlineHours = task.estimatedHours * multiplier;
const impliedDeadline = new Date(now.getTime() + impliedDeadlineHours * 60 * 60 * 1000);
const timeToDeadline = impliedDeadline.getTime() - now.getTime();
const maxTimeWindow = 7 * 24 * 60 * 60 * 1000;
const urgencyScore = Math.max(0, 1 - (timeToDeadline / maxTimeWindow));
if (task.priority === 'critical') {
return Math.min(1.0, urgencyScore * 1.5);
}
return Math.min(1.0, urgencyScore);
}
calculateDependencyScore(task, dependencyGraph, criticalPathSet) {
let score = 0.5;
if (criticalPathSet.has(task.id)) {
score += 0.3;
}
const dependentCount = task.dependents.length;
score += Math.min(dependentCount * 0.1, 0.2);
return Math.min(score, 1.0);
}
calculateResourceScore(task) {
const taskTypeResources = this.config.resources.taskTypeResources.get(task.type);
if (!taskTypeResources) {
return 0.5;
}
const memoryRatio = taskTypeResources.memoryMB / this.config.resources.maxMemoryMB;
const cpuRatio = taskTypeResources.cpuWeight / this.config.resources.maxCpuUtilization;
return 1.0 - Math.min((memoryRatio + cpuRatio) / 2, 0.5);
}
calculateDurationScore(task) {
const maxHours = 8;
return 1.0 - Math.min(task.estimatedHours / maxHours, 0.8);
}
calculateSystemLoadScore(task) {
const currentMemoryUtilization = this.getCurrentMemoryUtilization();
const currentCpuUtilization = this.getCurrentCpuUtilization();
const currentTaskLoad = this.getCurrentTaskLoad();
const taskTypeResources = this.config.resources.taskTypeResources.get(task.type);
const taskMemoryRatio = taskTypeResources ?
taskTypeResources.memoryMB / this.config.resources.maxMemoryMB : 0.1;
const taskCpuRatio = taskTypeResources ?
taskTypeResources.cpuWeight / this.config.resources.maxCpuUtilization : 0.1;
const memoryAvailability = Math.max(0, 1.0 - currentMemoryUtilization - taskMemoryRatio);
const cpuAvailability = Math.max(0, 1.0 - currentCpuUtilization - taskCpuRatio);
const taskSlotAvailability = Math.max(0, (this.config.resources.maxConcurrentTasks - currentTaskLoad) / this.config.resources.maxConcurrentTasks);
return (memoryAvailability * 0.4 + cpuAvailability * 0.4 + taskSlotAvailability * 0.2);
}
calculateComplexityScore(task) {
let complexityFactor = 0;
complexityFactor += Math.min(task.filePaths.length * 0.1, 0.3);
const testingComplexity = task.testingRequirements.unitTests.length * 0.05 +
task.testingRequirements.integrationTests.length * 0.1 +
task.testingRequirements.performanceTests.length * 0.15;
complexityFactor += Math.min(testingComplexity, 0.2);
complexityFactor += Math.min(task.acceptanceCriteria.length * 0.05, 0.2);
complexityFactor += Math.min(task.dependencies.length * 0.1, 0.2);
const typeComplexity = {
'development': 0.6,
'research': 0.8,
'deployment': 0.7,
'testing': 0.4,
'documentation': 0.3,
'review': 0.2
};
complexityFactor += typeComplexity[task.type] || 0.5;
return Math.max(0, 1.0 - Math.min(complexityFactor, 1.0));
}
calculateBusinessImpactScore(task) {
let impactScore = 0.5;
const priorityImpact = {
'critical': 1.0,
'high': 0.8,
'medium': 0.6,
'low': 0.4
};
impactScore = priorityImpact[task.priority] || 0.5;
const typeImpact = {
'deployment': 0.3,
'development': 0.2,
'testing': 0.1,
'research': 0.1,
'documentation': 0.05,
'review': 0.05
};
impactScore += typeImpact[task.type] || 0.1;
const businessCriticalTags = ['critical-path', 'customer-facing', 'revenue-impact', 'security'];
const hasBusinessCriticalTag = task.tags?.some(tag => businessCriticalTags.some(criticalTag => tag.toLowerCase().includes(criticalTag)));
if (hasBusinessCriticalTag) {
impactScore += 0.2;
}
return Math.min(impactScore, 1.0);
}
calculateAgentAvailabilityScore(task) {
const totalAgents = this.config.resources.availableAgents;
const busyAgents = this.getCurrentBusyAgents();
const availableAgents = Math.max(0, totalAgents - busyAgents);
const availabilityRatio = totalAgents > 0 ? availableAgents / totalAgents : 0;
const taskTypeResources = this.config.resources.taskTypeResources.get(task.type);
const requiredAgents = taskTypeResources?.agentCount || 1;
if (availableAgents >= requiredAgents) {
return Math.min(availabilityRatio + 0.2, 1.0);
}
else {
return availabilityRatio * 0.5;
}
}
getCurrentMemoryUtilization() {
if (this.currentSchedule) {
return Math.min(this.currentSchedule.resourceUtilization.peakMemoryMB / this.config.resources.maxMemoryMB, 1.0);
}
return 0.3;
}
getCurrentCpuUtilization() {
if (this.currentSchedule) {
return Math.min(this.currentSchedule.resourceUtilization.averageCpuUtilization, 1.0);
}
return 0.4;
}
getCurrentTaskLoad() {
if (this.currentSchedule) {
const now = new Date();
let runningTasks = 0;
for (const [, scheduledTask] of this.currentSchedule.scheduledTasks) {
if (scheduledTask.scheduledStart <= now && scheduledTask.scheduledEnd > now) {
runningTasks++;
}
}
return runningTasks;
}
return 0;
}
getCurrentBusyAgents() {
if (this.currentSchedule) {
const busyAgents = new Set();
const now = new Date();
for (const [, scheduledTask] of this.currentSchedule.scheduledTasks) {
if (scheduledTask.assignedResources.agentId &&
scheduledTask.scheduledStart <= now &&
scheduledTask.scheduledEnd > now) {
busyAgents.add(scheduledTask.assignedResources.agentId);
}
}
return busyAgents.size;
}
return 0;
}
calculateTaskDeadline(task) {
const now = new Date();
const priorityMultipliers = {
'critical': 0.5,
'high': 1.0,
'medium': 2.0,
'low': 3.0
};
const multiplier = priorityMultipliers[task.priority] || 2.0;
const deadlineHours = task.estimatedHours * multiplier + 24;
return new Date(now.getTime() + deadlineHours * 60 * 60 * 1000);
}
allocateResources(task) {
const taskTypeResources = this.config.resources.taskTypeResources.get(task.type);
let defaultMemory = 256;
let defaultCpu = 0.5;
if (task.type === 'deployment') {
defaultMemory = 1024;
defaultCpu = 0.9;
}
else if (task.type === 'development') {
defaultMemory = 512;
defaultCpu = 0.7;
}
return {
memoryMB: taskTypeResources?.memoryMB || defaultMemory,
cpuWeight: taskTypeResources?.cpuWeight || defaultCpu,
agentId: this.assignAgent(task)
};
}
allocateOptimalResources(task, batchTasks) {
const baseResources = this.allocateResources(task);
const batchMemoryTotal = batchTasks.reduce((sum, t) => {
const taskRes = this.config.resources.taskTypeResources.get(t.type);
return sum + (taskRes?.memoryMB || 256);
}, 0);
if (batchMemoryTotal > this.config.resources.maxMemoryMB) {
const scaleFactor = this.config.resources.maxMemoryMB / batchMemoryTotal;
baseResources.memoryMB = Math.floor(baseResources.memoryMB * scaleFactor);
}
baseResources.agentId = this.assignAgent(task);
return baseResources;
}
assignAgent(task) {
const agentCount = this.config.resources.availableAgents;
if (agentCount === 0)
return undefined;
if (!task) {
const agentId = `agent_${(this.scheduleVersion % agentCount) + 1}`;
return agentId;
}
const agentCapabilities = new Map([
['agent_1', ['development', 'testing', 'review']],
['agent_2', ['deployment', 'documentation', 'research']],
['agent_3', ['development', 'testing', 'deployment']]
]);
const availableAgents = Array.from({ length: agentCount }, (_, i) => `agent_${i + 1}`);
const capableAgents = availableAgents.filter(agentId => {
const capabilities = agentCapabilities.get(agentId) || ['development', 'testing'];
return capabilities.includes(task.type);
});
if (capableAgents.length === 0) {
const agentId = `agent_${(this.scheduleVersion % agentCount) + 1}`;
return agentId;
}
const agentLoads = new Map(availableAgents.map(agentId => [agentId, Math.floor(Math.random() * 5)]));
const selectedAgent = capableAgents.reduce((best, current) => {
const currentLoad = agentLoads.get(current) || 0;
const bestLoad = agentLoads.get(best) || 0;
return currentLoad < bestLoad ? current : best;
});
return selectedAgent;
}
findCriticalPath(scheduledTasks) {
const tasks = Array.from(scheduledTasks.values());
let longestPath = [];
let maxDuration = 0;
for (const task of tasks) {
const path = this.findPathFromTask(task, scheduledTasks);
const pathDuration = path.reduce((sum, taskId) => {
const t = scheduledTasks.get(taskId);
return sum + (t?.task.estimatedHours || 0);
}, 0);
if (pathDuration > maxDuration) {
maxDuration = pathDuration;
longestPath = path;
}
}
return longestPath;
}
findPathFromTask(startTask, scheduledTasks) {
const path = [startTask.task.id];
const visited = new Set([startTask.task.id]);
let currentTask = startTask;
while (currentTask.dependentTasks.length > 0) {
let nextTask;
let maxDuration = 0;
for (const dependentId of currentTask.dependentTasks) {
if (visited.has(dependentId))
continue;
const dependent = scheduledTasks.get(dependentId);
if (dependent && dependent.task.estimatedHours > maxDuration) {
maxDuration = dependent.task.estimatedHours;
nextTask = dependent;
}
}
if (!nextTask)
break;
path.push(nextTask.task.id);
visited.add(nextTask.task.id);
currentTask = nextTask;
}
return path;
}
optimizeResourceAllocation(schedule) {
return schedule;
}
optimizeParallelExecution(schedule) {
return schedule;
}
optimizeDeadlineCompliance(schedule) {
return schedule;
}
shouldReschedule(updatedTasks) {
if (!this.currentSchedule)
return true;
const sensitivity = this.config.rescheduleSensitivity;
const thresholds = {
'low': 0.3,
'medium': 0.2,
'high': 0.1
};
const changeRatio = updatedTasks.length / this.currentSchedule.scheduledTasks.size;
return changeRatio > thresholds[sensitivity];
}
async incrementalUpdate(_updatedTasks, _dependencyGraph) {
return this.currentSchedule;
}
arePrerequisitesComplete(scheduledTask) {
if (!this.currentSchedule)
return false;
return scheduledTask.prerequisiteTasks.every(prereqId => {
const prereq = this.currentSchedule.scheduledTasks.get(prereqId);
return prereq?.task.status === 'completed';
});
}
calculateActualHours(scheduledTask) {
const now = new Date();
const startTime = scheduledTask.scheduledStart;
const elapsedMs = now.getTime() - startTime.getTime();
const elapsedHours = elapsedMs / (1000 * 60 * 60);
return Math.max(elapsedHours, 0.1);
}
async checkForOptimization() {
if (!this.currentSchedule || !this.config.enableDynamicOptimization) {
return;
}
const resourceEfficiency = this.currentSchedule.resourceUtilization.resourceEfficiency;
if (resourceEfficiency < 0.7) {
logger.info('Triggering schedule optimization due to low resource utilization');
setTimeout(() => this.optimizeCurrentSchedule(), 1000);
}
}
async optimizeCurrentSchedule() {
if (!this.currentSchedule)
return;
try {
const optimized = await this.optimizeSchedule(this.currentSchedule);
this.currentSchedule = optimized;
logger.info('Schedule optimized successfully');
}
catch (error) {
logger.error('Failed to optimize schedule', { error });
}
}
startOptimizationTimer() {
if (this.optimizationTimer) {
clearInterval(this.optimizationTimer);
}
this.optimizationTimer = setInterval(() => this.checkForOptimization(), this.config.optimizationInterval * 60 * 1000);
}
earliestDeadlineScheduling(tasks, taskScores, parallelBatches) {
const scheduledTasks = new Map();
const sortedTasks = tasks.sort((a, b) => {
const deadlineA = this.calculateTaskDeadline(a);
const deadlineB = this.calculateTaskDeadline(b);
return deadlineA.getTime() - deadlineB.getTime();
});
let currentTime = new Date();
let batchId = 0;
for (const batch of parallelBatches) {
const batchTasks = batch.taskIds
.map(id => sortedTasks.find(t => t.id === id))
.filter(task => task !== undefined);
for (const task of batchTasks) {
const scores = taskScores.get(task.id);
const resources = this.allocateResources(task);
const scheduledTask = {
task,
scheduledStart: new Date(currentTime),
scheduledEnd: new Date(currentTime.getTime() + task.estimatedHours * 60 * 60 * 1000),
assignedResources: resources,
batchId,
prerequisiteTasks: task.dependencies,
dependentTasks: task.dependents,
metadata: {
algorithm: 'earliest_deadline',
priorityScore: scores?.priorityScore || 0,
resourceScore: scores?.resourceScore || 0,
deadlineScore: scores?.deadlineScore || 0,
dependencyScore: scores?.dependencyScore || 0,
durationScore: scores?.durationScore || 0,
systemLoadScore: scores?.systemLoadScore || 0,
complexityScore: scores?.complexityScore || 0,
businessImpactScore: scores?.businessImpactScore || 0,
agentAvailabilityScore: scores?.agentAvailabilityScore || 0,
totalScore: scores?.totalScore || 0,
scheduledAt: new Date(),
lastOptimized: new Date()
}
};
scheduledTasks.set(task.id, scheduledTask);
}
currentTime = new Date(currentTime.getTime() + batch.estimatedDuration * 60 * 60 * 1000);
batchId++;
}
return scheduledTasks;
}
criticalPathScheduling(tasks, taskScores, parallelBatches, dependencyGraph) {
const scheduledTasks = new Map();
const criticalPath = dependencyGraph.getCriticalPath();
const criticalPathSet = new Set(criticalPath);
const sortedTasks = tasks.sort((a, b) => {
const aOnCriticalPath = criticalPathSet.has(a.id);
const bOnCriticalPath = criticalPathSet.has(b.id);
if (aOnCriticalPath && !bOnCriticalPath)
return -1;
if (!aOnCriticalPath && bOnCriticalPath)
return 1;
const scoreA = taskScores.get(a.id)?.totalScore || 0;
const scoreB = taskScores.get(b.id)?.totalScore || 0;
return scoreB - scoreA;
});
let currentTime = new Date();
let batchId = 0;
for (const batch of parallelBatches) {
const batchTasks = this.optimizeBatchOrder(batch, sortedTasks, taskScores);
const batchStartTime = new Date(currentTime);
for (const task of batchTasks) {
const scores = taskScores.get(task.id);
const resources = this.allocateOptimalResources(task, batchTasks);
const scheduledTask = {
task,
scheduledStart: batchStartTime,
scheduledEnd: new Date(batchStartTime.getTime() + task.estimatedHours * 60 * 60 * 1000),
assignedResources: resources,
batchId,