UNPKG

mcp-adr-analysis-server

Version:

MCP server for analyzing Architectural Decision Records and project architecture

365 lines 12.6 kB
/** * MCP Tasks Integration for Bootstrap Validation Loop * * This module provides standardized task tracking for the bootstrap validation loop tool, * implementing ADR-020: MCP Tasks Integration Strategy. * * Key features: * - Creates MCP Tasks for bootstrap operations * - Tracks progress through phases (platform_detection, infrastructure, application, validation) * - Supports cancellation between phases * - Migrates from custom executionId to standardized taskId * * @see ADR-020: MCP Tasks Integration Strategy * @see https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks */ import { getTaskManager } from './task-manager.js'; import { createComponentLogger } from './enhanced-logging.js'; const logger = createComponentLogger('BootstrapTaskIntegration'); /** * Bootstrap phases that map to MCP Task phases */ export const BOOTSTRAP_PHASES = [ 'platform_detection', 'infrastructure_setup', 'application_deployment', 'validation', 'cleanup', ]; /** * Bootstrap Task Manager - Provides MCP Tasks integration for bootstrap validation loop * * This class wraps the TaskManager to provide bootstrap-specific functionality: * - Creates tasks with bootstrap-specific phases * - Tracks iteration progress * - Supports cancellation checks between phases * - Converts between executionId and taskId patterns */ export class BootstrapTaskManager { taskManager; activeContexts = new Map(); constructor(taskManager) { this.taskManager = taskManager ?? getTaskManager(); } /** * Initialize the bootstrap task manager */ async initialize() { await this.taskManager.initialize(); logger.info('BootstrapTaskManager initialized'); } /** * Create a new bootstrap task * * @returns The created task and context */ async createBootstrapTask(options) { const { projectPath, adrDirectory, maxIterations } = options; const task = await this.taskManager.createTask({ type: 'bootstrap', tool: 'bootstrap_validation_loop', phases: [...BOOTSTRAP_PHASES], projectPath, adrDirectory, ttl: 1800000, // 30 minute TTL for bootstrap operations pollInterval: 2000, // 2 second poll interval }); const context = { taskId: task.taskId, currentPhase: 'platform_detection', iteration: 0, maxIterations, cancelled: false, }; this.activeContexts.set(task.taskId, context); logger.info('Bootstrap task created', { taskId: task.taskId, projectPath, maxIterations, }); return { task, context }; } /** * Get bootstrap task context */ getContext(taskId) { return this.activeContexts.get(taskId); } /** * Start a bootstrap phase */ async startPhase(taskId, phase, message) { const context = this.activeContexts.get(taskId); if (!context) { logger.warn('No context found for task', { taskId }); return; } // Check for cancellation if (context.cancelled) { throw new Error('Task was cancelled'); } context.currentPhase = phase; await this.taskManager.startPhase(taskId, phase); // Calculate overall progress based on phase const phaseIndex = BOOTSTRAP_PHASES.indexOf(phase); const phaseProgress = phaseIndex >= 0 ? Math.floor((phaseIndex / BOOTSTRAP_PHASES.length) * 100) : 0; await this.taskManager.updateProgress({ taskId, progress: phaseProgress, phase, phaseProgress: 0, message: message ?? `Starting phase: ${phase}`, }); logger.info('Bootstrap phase started', { taskId, phase, progress: phaseProgress }); } /** * Update phase progress */ async updatePhaseProgress(taskId, phase, phaseProgress, message) { const context = this.activeContexts.get(taskId); if (!context) { return; } // Calculate overall progress const phaseIndex = BOOTSTRAP_PHASES.indexOf(phase); const phaseFraction = 100 / BOOTSTRAP_PHASES.length; const baseProgress = phaseIndex * phaseFraction; const overallProgress = Math.floor(baseProgress + (phaseProgress / 100) * phaseFraction); await this.taskManager.updateProgress({ taskId, progress: overallProgress, phase, phaseProgress, ...(message !== undefined && { message }), }); } /** * Complete a bootstrap phase */ async completePhase(taskId, phase, _message) { const context = this.activeContexts.get(taskId); if (!context) { return; } await this.taskManager.completePhase(taskId, phase); logger.info('Bootstrap phase completed', { taskId, phase }); } /** * Fail a bootstrap phase */ async failPhase(taskId, phase, error) { const context = this.activeContexts.get(taskId); if (!context) { return; } await this.taskManager.failPhase(taskId, phase, error); logger.warn('Bootstrap phase failed', { taskId, phase, error }); } /** * Update iteration progress */ async updateIteration(taskId, iteration) { const context = this.activeContexts.get(taskId); if (!context) { return; } context.iteration = iteration; await this.taskManager.updateProgress({ taskId, progress: this.taskManager.getProgress(taskId) ?? 0, message: `Iteration ${iteration}/${context.maxIterations}`, }); logger.info('Bootstrap iteration updated', { taskId, iteration, maxIterations: context.maxIterations, }); } /** * Store platform detection result */ async storePlatformDetection(taskId, platform, confidence) { const context = this.activeContexts.get(taskId); if (!context) { return; } context.platformDetected = platform; await this.taskManager.updateProgress({ taskId, progress: 20, // Platform detection is ~20% of bootstrap message: `Platform detected: ${platform} (${(confidence * 100).toFixed(0)}% confidence)`, }); logger.info('Platform detection stored', { taskId, platform, confidence }); } /** * Store infrastructure result */ async storeInfrastructureResult(taskId, result) { const context = this.activeContexts.get(taskId); if (!context) { return; } context.infrastructureResult = result; const progress = result.success ? 50 : 40; await this.taskManager.updateProgress({ taskId, progress, message: result.success ? `Infrastructure setup complete (${result.executedTasks.length} tasks executed)` : `Infrastructure setup failed (${result.failedTasks.length} tasks failed)`, }); logger.info('Infrastructure result stored', { taskId, success: result.success, executed: result.executedTasks.length, failed: result.failedTasks.length, }); } /** * Check if task is cancelled */ async isCancelled(taskId) { const context = this.activeContexts.get(taskId); if (!context) { return false; } // Check if task was cancelled externally const task = await this.taskManager.getTask(taskId); if (task?.status === 'cancelled') { context.cancelled = true; } return context.cancelled; } /** * Cancel a bootstrap task */ async cancelTask(taskId, reason) { const context = this.activeContexts.get(taskId); if (context) { context.cancelled = true; } await this.taskManager.cancelTask(taskId, reason ?? 'Bootstrap cancelled by user'); this.activeContexts.delete(taskId); logger.info('Bootstrap task cancelled', { taskId, reason }); } /** * Complete a bootstrap task successfully */ async completeTask(taskId, result) { await this.taskManager.completeTask(taskId, result); this.activeContexts.delete(taskId); logger.info('Bootstrap task completed', { taskId, success: result.success }); } /** * Fail a bootstrap task */ async failTask(taskId, error) { await this.taskManager.failTask(taskId, error); this.activeContexts.delete(taskId); logger.error('Bootstrap task failed', undefined, { taskId, error }); } /** * Get task status */ async getTaskStatus(taskId) { const task = await this.taskManager.getTask(taskId); const context = this.activeContexts.get(taskId); return { task, context }; } } /** * Get the global BootstrapTaskManager instance */ let globalBootstrapTaskManager = null; export function getBootstrapTaskManager() { if (!globalBootstrapTaskManager) { globalBootstrapTaskManager = new BootstrapTaskManager(); } return globalBootstrapTaskManager; } /** * Reset the global BootstrapTaskManager (for testing) */ export async function resetBootstrapTaskManager() { globalBootstrapTaskManager = null; } /** * Helper function to wrap bootstrap execution with task tracking * * This can be used by the bootstrap_validation_loop tool to automatically * track progress through MCP Tasks. * * @example * ```typescript * const result = await executeWithTaskTracking( * { * projectPath: '/path/to/project', * adrDirectory: 'docs/adrs', * targetEnvironment: 'development', * maxIterations: 5, * autoFix: true, * updateAdrsWithLearnings: true, * }, * async (tracker) => { * // Platform detection phase * await tracker.startPhase('platform_detection'); * const platform = await detectPlatform(); * await tracker.storePlatformDetection(platform.name, platform.confidence); * await tracker.completePhase('platform_detection'); * * // Infrastructure phase * await tracker.startPhase('infrastructure_setup'); * const infraResult = await runInfrastructure(); * await tracker.storeInfrastructureResult(infraResult); * await tracker.completePhase('infrastructure_setup'); * * // Return final result * return { success: true, iterations: 3 }; * } * ); * ``` */ export async function executeWithTaskTracking(options, executor) { const btm = getBootstrapTaskManager(); await btm.initialize(); const { task, context } = await btm.createBootstrapTask(options); const taskId = task.taskId; // Create a tracker interface for the executor const tracker = { taskId, startPhase: (phase, message) => btm.startPhase(taskId, phase, message), updatePhaseProgress: (phase, progress, message) => btm.updatePhaseProgress(taskId, phase, progress, message), completePhase: (phase, message) => btm.completePhase(taskId, phase, message), failPhase: (phase, error) => btm.failPhase(taskId, phase, error), updateIteration: iteration => btm.updateIteration(taskId, iteration), storePlatformDetection: (platform, confidence) => btm.storePlatformDetection(taskId, platform, confidence), storeInfrastructureResult: result => btm.storeInfrastructureResult(taskId, result), isCancelled: () => btm.isCancelled(taskId), getContext: () => context, }; try { const resultData = await executor(tracker); const result = { success: resultData?.success ?? true, data: resultData, }; await btm.completeTask(taskId, result); return { taskId, result }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); await btm.failTask(taskId, errorMessage); return { taskId, result: { success: false, error: { code: 'BOOTSTRAP_ERROR', message: errorMessage, recoverable: false, }, }, }; } } //# sourceMappingURL=bootstrap-task-integration.js.map