Skip to main content
Glama
dependency_manager.js18.1 kB
const logger = require('./logger'); /** * Advanced Dependency Management System * Provides sophisticated dependency-aware priority calculations including * cascade effects, blocking task detection, critical path analysis, and priority inheritance. */ class DependencyManager { constructor() { this.config = { // Priority inheritance settings inheritanceDecayFactor: 0.8, // How much priority decays through dependency chain maxInheritanceDepth: 5, // Maximum depth for priority inheritance minInheritedPriority: 50, // Minimum priority that can be inherited // Critical path settings criticalPathBoost: 100, // Priority boost for critical path tasks criticalPathThreshold: 0.8, // Threshold for considering a path critical // Blocking detection settings blockingTaskBoost: 150, // Priority boost for tasks blocking many others blockingThreshold: 3, // Minimum blocked tasks to be considered blocking // Cascade settings cascadeEnabled: true, // Enable priority cascade effects cascadeDecayRate: 0.7, // How much priority decays in cascade maxCascadeDepth: 3 // Maximum cascade depth }; } /** * Analyze and enhance task priorities based on dependency relationships * @param {Array} tasks - Array of tasks * @returns {Array} Array of priority adjustments made */ enhancePrioritiesWithDependencies(tasks) { const adjustments = []; // Build dependency graph const dependencyGraph = this.buildDependencyGraph(tasks); // Step 1: Apply priority inheritance const inheritanceAdjustments = this.applyPriorityInheritance(tasks, dependencyGraph); adjustments.push(...inheritanceAdjustments); // Step 2: Detect and boost blocking tasks const blockingAdjustments = this.detectAndBoostBlockingTasks(tasks, dependencyGraph); adjustments.push(...blockingAdjustments); // Step 3: Analyze critical paths const criticalPathAdjustments = this.analyzeCriticalPaths(tasks, dependencyGraph); adjustments.push(...criticalPathAdjustments); // Step 4: Apply cascade effects if (this.config.cascadeEnabled) { const cascadeAdjustments = this.applyCascadeEffects(tasks, dependencyGraph); adjustments.push(...cascadeAdjustments); } return adjustments; } /** * Build a comprehensive dependency graph * @param {Array} tasks - Array of tasks * @returns {Object} Dependency graph with forward and reverse mappings */ buildDependencyGraph(tasks) { const graph = { forward: new Map(), // task -> tasks that depend on it reverse: new Map(), // task -> tasks it depends on taskMap: new Map() // id -> task object }; // Initialize maps tasks.forEach(task => { graph.forward.set(task.id, new Set()); graph.reverse.set(task.id, new Set()); graph.taskMap.set(task.id, task); }); // Build relationships tasks.forEach(task => { if (task.dependsOn && Array.isArray(task.dependsOn)) { task.dependsOn.forEach(depId => { if (graph.forward.has(depId)) { graph.forward.get(depId).add(task.id); graph.reverse.get(task.id).add(depId); } }); } }); return graph; } /** * Apply priority inheritance through dependency chains * @param {Array} tasks - Array of tasks * @param {Object} dependencyGraph - Dependency graph * @returns {Array} Array of adjustments made */ applyPriorityInheritance(tasks, dependencyGraph) { const adjustments = []; const visited = new Set(); // Process tasks in topological order (dependencies first) const sortedTasks = this.topologicalSort(tasks, dependencyGraph); sortedTasks.forEach(task => { if (visited.has(task.id)) return; const inheritedPriority = this.calculateInheritedPriority( task, dependencyGraph, visited, 0 ); if (inheritedPriority > task.priority) { const oldPriority = task.priority; task.priority = Math.min(1000, inheritedPriority); adjustments.push({ taskId: task.id, type: 'priority_inheritance', oldPriority, newPriority: task.priority, reason: `Priority inherited from dependent tasks (${oldPriority} → ${task.priority})` }); } visited.add(task.id); }); return adjustments; } /** * Calculate inherited priority for a task based on its dependents * @param {Object} task - Task object * @param {Object} dependencyGraph - Dependency graph * @param {Set} visited - Set of visited task IDs * @param {number} depth - Current inheritance depth * @returns {number} Calculated inherited priority */ calculateInheritedPriority(task, dependencyGraph, visited, depth) { if (depth >= this.config.maxInheritanceDepth) { return task.priority; } let maxInheritedPriority = task.priority; const dependents = dependencyGraph.forward.get(task.id) || new Set(); dependents.forEach(dependentId => { if (!visited.has(dependentId)) { const dependentTask = dependencyGraph.taskMap.get(dependentId); if (dependentTask) { // Recursively calculate inherited priority const dependentInheritedPriority = this.calculateInheritedPriority( dependentTask, dependencyGraph, visited, depth + 1 ); // Apply decay factor for inheritance const inheritedFromDependent = Math.max( this.config.minInheritedPriority, Math.round(dependentInheritedPriority * Math.pow(this.config.inheritanceDecayFactor, depth + 1)) ); maxInheritedPriority = Math.max(maxInheritedPriority, inheritedFromDependent); } } }); return maxInheritedPriority; } /** * Detect tasks that are blocking many others and boost their priority * @param {Array} tasks - Array of tasks * @param {Object} dependencyGraph - Dependency graph * @returns {Array} Array of adjustments made */ detectAndBoostBlockingTasks(tasks, dependencyGraph) { const adjustments = []; tasks.forEach(task => { if (task.status === 'done') return; // Skip completed tasks const blockedTasks = this.getBlockedTasks(task.id, dependencyGraph); if (blockedTasks.size >= this.config.blockingThreshold) { const oldPriority = task.priority; const boost = Math.min( this.config.blockingTaskBoost, blockedTasks.size * 25 // 25 points per blocked task ); task.priority = Math.min(1000, task.priority + boost); adjustments.push({ taskId: task.id, type: 'blocking_task_boost', oldPriority, newPriority: task.priority, reason: `Priority boosted by ${boost} for blocking ${blockedTasks.size} task(s)` }); } }); return adjustments; } /** * Get all tasks that are blocked by a given task * @param {number} taskId - ID of the potentially blocking task * @param {Object} dependencyGraph - Dependency graph * @returns {Set} Set of blocked task IDs */ getBlockedTasks(taskId, dependencyGraph) { const blocked = new Set(); const visited = new Set(); const queue = [taskId]; while (queue.length > 0) { const currentId = queue.shift(); if (visited.has(currentId)) continue; visited.add(currentId); const dependents = dependencyGraph.forward.get(currentId) || new Set(); dependents.forEach(dependentId => { const dependentTask = dependencyGraph.taskMap.get(dependentId); if (dependentTask && dependentTask.status !== 'done') { blocked.add(dependentId); queue.push(dependentId); } }); } return blocked; } /** * Analyze critical paths and boost priorities accordingly * @param {Array} tasks - Array of tasks * @param {Object} dependencyGraph - Dependency graph * @returns {Array} Array of adjustments made */ analyzeCriticalPaths(tasks, dependencyGraph) { const adjustments = []; const criticalPaths = this.findCriticalPaths(tasks, dependencyGraph); criticalPaths.forEach(path => { const criticalityScore = this.calculateCriticalityScore(path, dependencyGraph); if (criticalityScore >= this.config.criticalPathThreshold) { path.forEach(taskId => { const task = dependencyGraph.taskMap.get(taskId); if (task && task.status !== 'done') { const oldPriority = task.priority; const boost = Math.round(this.config.criticalPathBoost * criticalityScore); task.priority = Math.min(1000, task.priority + boost); adjustments.push({ taskId: task.id, type: 'critical_path_boost', oldPriority, newPriority: task.priority, reason: `Priority boosted by ${boost} for being on critical path (score: ${criticalityScore.toFixed(2)})` }); } }); } }); return adjustments; } /** * Find critical paths in the dependency graph * @param {Array} tasks - Array of tasks * @param {Object} dependencyGraph - Dependency graph * @returns {Array} Array of critical paths (each path is an array of task IDs) */ findCriticalPaths(tasks, dependencyGraph) { const paths = []; const visited = new Set(); // Find root tasks (tasks with no dependencies) const rootTasks = tasks.filter(task => !task.dependsOn || task.dependsOn.length === 0 ); // Traverse from each root to find all paths rootTasks.forEach(rootTask => { this.findPathsFromTask(rootTask.id, dependencyGraph, [], paths, visited); }); return paths; } /** * Recursively find all paths from a given task * @param {number} taskId - Starting task ID * @param {Object} dependencyGraph - Dependency graph * @param {Array} currentPath - Current path being built * @param {Array} allPaths - Array to store all found paths * @param {Set} visited - Set of visited tasks in current path */ findPathsFromTask(taskId, dependencyGraph, currentPath, allPaths, visited) { if (visited.has(taskId)) return; // Avoid cycles const newPath = [...currentPath, taskId]; const dependents = dependencyGraph.forward.get(taskId) || new Set(); if (dependents.size === 0) { // Leaf node - end of path allPaths.push(newPath); } else { // Continue exploring const newVisited = new Set(visited); newVisited.add(taskId); dependents.forEach(dependentId => { this.findPathsFromTask(dependentId, dependencyGraph, newPath, allPaths, newVisited); }); } } /** * Calculate criticality score for a path * @param {Array} path - Array of task IDs in the path * @param {Object} dependencyGraph - Dependency graph * @returns {number} Criticality score (0-1) */ calculateCriticalityScore(path, dependencyGraph) { let totalPriority = 0; let totalTasks = path.length; let incompleteTasks = 0; path.forEach(taskId => { const task = dependencyGraph.taskMap.get(taskId); if (task) { totalPriority += task.priority; if (task.status !== 'done') { incompleteTasks++; } } }); const avgPriority = totalPriority / totalTasks; const completionRatio = incompleteTasks / totalTasks; const lengthFactor = Math.min(1, totalTasks / 10); // Longer paths are more critical // Combine factors to get criticality score return (avgPriority / 1000) * completionRatio * lengthFactor; } /** * Apply cascade effects for priority changes * @param {Array} tasks - Array of tasks * @param {Object} dependencyGraph - Dependency graph * @returns {Array} Array of adjustments made */ applyCascadeEffects(tasks, dependencyGraph) { const adjustments = []; // Find tasks with recently increased priorities (this would need to be tracked) // For now, we'll apply cascade to high-priority tasks const highPriorityTasks = tasks.filter(task => task.priority >= 800); highPriorityTasks.forEach(task => { const cascadeAdjustments = this.cascadePriorityToDependent( task.id, task.priority, dependencyGraph, new Set(), 0 ); adjustments.push(...cascadeAdjustments); }); return adjustments; } /** * Cascade priority to dependent tasks * @param {number} taskId - Source task ID * @param {number} sourcePriority - Source task priority * @param {Object} dependencyGraph - Dependency graph * @param {Set} visited - Set of visited task IDs * @param {number} depth - Current cascade depth * @returns {Array} Array of adjustments made */ cascadePriorityToDependent(taskId, sourcePriority, dependencyGraph, visited, depth) { const adjustments = []; if (depth >= this.config.maxCascadeDepth || visited.has(taskId)) { return adjustments; } visited.add(taskId); const dependents = dependencyGraph.forward.get(taskId) || new Set(); dependents.forEach(dependentId => { const dependentTask = dependencyGraph.taskMap.get(dependentId); if (dependentTask && dependentTask.status !== 'done') { const cascadedPriority = Math.round( sourcePriority * Math.pow(this.config.cascadeDecayRate, depth + 1) ); if (cascadedPriority > dependentTask.priority) { const oldPriority = dependentTask.priority; dependentTask.priority = Math.min(1000, cascadedPriority); adjustments.push({ taskId: dependentTask.id, type: 'cascade_effect', oldPriority, newPriority: dependentTask.priority, reason: `Priority cascaded from task ${taskId} (${oldPriority} → ${dependentTask.priority})` }); // Continue cascading const furtherAdjustments = this.cascadePriorityToDependent( dependentId, dependentTask.priority, dependencyGraph, new Set(visited), depth + 1 ); adjustments.push(...furtherAdjustments); } } }); return adjustments; } /** * Perform topological sort on tasks * @param {Array} tasks - Array of tasks * @param {Object} dependencyGraph - Dependency graph * @returns {Array} Topologically sorted tasks */ topologicalSort(tasks, dependencyGraph) { const sorted = []; const visited = new Set(); const visiting = new Set(); const visit = (taskId) => { if (visiting.has(taskId)) { logger.warn(`Circular dependency detected involving task ${taskId}`); return; } if (visited.has(taskId)) return; visiting.add(taskId); const dependencies = dependencyGraph.reverse.get(taskId) || new Set(); dependencies.forEach(depId => { visit(depId); }); visiting.delete(taskId); visited.add(taskId); const task = dependencyGraph.taskMap.get(taskId); if (task) { sorted.push(task); } }; tasks.forEach(task => { if (!visited.has(task.id)) { visit(task.id); } }); return sorted; } /** * Get dependency analysis report * @param {Array} tasks - Array of tasks * @returns {Object} Dependency analysis report */ getDependencyAnalysis(tasks) { const dependencyGraph = this.buildDependencyGraph(tasks); const criticalPaths = this.findCriticalPaths(tasks, dependencyGraph); const analysis = { totalTasks: tasks.length, tasksWithDependencies: tasks.filter(t => t.dependsOn && t.dependsOn.length > 0).length, rootTasks: tasks.filter(t => !t.dependsOn || t.dependsOn.length === 0).length, leafTasks: tasks.filter(t => { const dependents = dependencyGraph.forward.get(t.id) || new Set(); return dependents.size === 0; }).length, criticalPaths: criticalPaths.length, longestPath: criticalPaths.reduce((max, path) => Math.max(max, path.length), 0), blockingTasks: [], circularDependencies: this.detectCircularDependencies(dependencyGraph) }; // Find blocking tasks tasks.forEach(task => { const blockedTasks = this.getBlockedTasks(task.id, dependencyGraph); if (blockedTasks.size >= this.config.blockingThreshold) { analysis.blockingTasks.push({ taskId: task.id, title: task.title, blockedCount: blockedTasks.size }); } }); return analysis; } /** * Detect circular dependencies in the graph * @param {Object} dependencyGraph - Dependency graph * @returns {Array} Array of circular dependency chains */ detectCircularDependencies(dependencyGraph) { const cycles = []; const visited = new Set(); const recursionStack = new Set(); const detectCycle = (taskId, path) => { if (recursionStack.has(taskId)) { // Found a cycle const cycleStart = path.indexOf(taskId); cycles.push(path.slice(cycleStart)); return; } if (visited.has(taskId)) return; visited.add(taskId); recursionStack.add(taskId); const dependencies = dependencyGraph.reverse.get(taskId) || new Set(); dependencies.forEach(depId => { detectCycle(depId, [...path, taskId]); }); recursionStack.delete(taskId); }; dependencyGraph.taskMap.forEach((task, taskId) => { if (!visited.has(taskId)) { detectCycle(taskId, []); } }); return cycles; } } module.exports = DependencyManager;

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/FutureAtoms/agentic-control-framework'

If you have feedback or need assistance with the MCP directory API, please join our Discord server