mcp-adr-analysis-server
Version:
MCP server for analyzing Architectural Decision Records and project architecture
365 lines • 12.6 kB
JavaScript
/**
* 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