UNPKG

mcp-decisive

Version:

MCP server for WRAP decision-making framework with structured output

390 lines (386 loc) 15.3 kB
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // Event Projections - Building State from Events // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ /** * Projects a Plan from a stream of events * This is the core of event sourcing - rebuilding state from events */ const projectPlanFromEvents = (events) => { let currentPlan = null; for (const event of events) { switch (event.type) { case 'PlanCreated': currentPlan = event.plan; break; case 'PlanUpdated': currentPlan = event.plan; break; case 'TaskStatusChanged': if (currentPlan) { currentPlan = updateTaskStatusInPlan(currentPlan, event.taskId, event.newStatus); } break; case 'TasksAdded': if (currentPlan) { currentPlan = addTasksToPlan(currentPlan, event.taskIds); } break; case 'DependenciesChanged': if (currentPlan) { currentPlan = updateTaskDependenciesInPlan(currentPlan, event.taskId, event.newDeps); } break; default: // Exhaustive check - TypeScript will error if we miss a case const _exhaustive = event; throw new Error(`Unhandled event type: ${_exhaustive}`); } } return currentPlan; }; /** * Derives LineViews from a Plan * Lines represent parallel execution branches (feature branches) */ const deriveLineViewsFromPlan = (plan) => { const tasksByBranch = groupTasksByBranch(plan.tasks); const lines = createLinesFromTaskGroups(tasksByBranch); const linesWithDependencies = calculateLineDependencies(lines, plan.tasks); return linesWithDependencies.map(line => enrichLineWithAnalysis(line)); }; /** * Calculates execution constraints for a line * Determines if a line can be executed in parallel */ const calculateLineExecutability = (line, allLines) => { const completedDependencies = line.dependencies.filter(depId => allLines.find(l => l.id === depId)?.state.type === 'Completed'); const isExecutable = completedDependencies.length === line.dependencies.length && line.state.type !== 'Completed' && line.state.type !== 'Abandoned'; const isAssigned = line.tasks.some(task => task.assignedWorktree); const isCompleted = line.state.type === 'Completed'; const blockedBy = line.dependencies.filter(depId => allLines.find(l => l.id === depId)?.state.type !== 'Completed'); return { isExecutable, isAssigned, isCompleted, blockedBy }; }; // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // Statistics Projections // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ const calculateCorePlanStats = (plan, lines) => { const tasksByStatus = { 'ToBeRefined': 0, 'Refined': 0, 'Implemented': 0, 'Reviewed': 0, 'Merged': 0, 'Blocked': 0, 'Abandoned': 0 }; const tasksByBranch = {}; for (const task of plan.tasks) { tasksByStatus[task.status.type] = (tasksByStatus[task.status.type] || 0) + 1; tasksByBranch[task.branch] = (tasksByBranch[task.branch] || 0) + 1; } return { totalTasks: plan.tasks.length, totalLines: lines.length, tasksByStatus, tasksByBranch }; }; const calculateParallelExecutionStats = (lines) => { const completedLines = getCompletedLineIds(lines); const executableLines = lines.filter(line => isLineExecutable(line, completedLines)); const unassignedLines = lines.filter(line => isLineUnassigned(line)); const executableUnassignedLines = executableLines.filter(line => isLineUnassigned(line)); const blockedLines = lines.filter(line => line.state.type === 'Blocked' || (line.dependencies.length > 0 && !isLineExecutable(line, completedLines))); return { executableLines: executableLines.length, unassignedLines: unassignedLines.length, executableUnassignedLines: executableUnassignedLines.length, blockedLines: blockedLines.length, completedLines: completedLines.size }; }; // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // Query Functions - The Read Model Interface // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ /** * Creates a basic plan view from a plan entity * Optimized for general plan viewing */ const createPlanViewFromPlan = (plan, config = {}) => { const lines = deriveEnrichedLines(plan); const filteredLines = applyLineFilters(lines, config); const stats = calculateCorePlanStats(plan, filteredLines); return { plan, lines: filteredLines, stats, lastUpdated: new Date() }; }; /** * Creates a plan view from event stream * Demonstrates event sourcing for read models */ const createPlanViewFromEvents = (events, config = {}) => { const plan = projectPlanFromEvents(events); if (!plan) { return null; } return createPlanViewFromPlan(plan, config); }; /** * Creates a tracking view with parallel execution analysis * Optimized for project management and resource allocation */ const createTrackViewFromPlan = (plan, config = {}) => { const lines = deriveEnrichedLines(plan); const filteredLines = applyTrackingFilters(lines, config); const coreStats = calculateCorePlanStats(plan, filteredLines); const parallelExecutionStats = calculateParallelExecutionStats(filteredLines); return { plan, lines: filteredLines, stats: { ...coreStats, parallelExecutionStats }, lastUpdated: new Date() }; }; /** * Creates a tracking view from event stream * Combines event sourcing with specialized tracking projections */ const createTrackViewFromEvents = (events, config = {}) => { const plan = projectPlanFromEvents(events); if (!plan) { return null; } return createTrackViewFromPlan(plan, config); }; // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // Public API - Expose Read Model Operations // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ /** * Plan View Queries - General purpose plan viewing */ export const planViewQueries = { fromPlan: createPlanViewFromPlan, fromEvents: createPlanViewFromEvents }; /** * Track View Queries - Project tracking and parallel execution analysis */ export const trackViewQueries = { fromPlan: createTrackViewFromPlan, fromEvents: createTrackViewFromEvents }; /** * Projection Utilities - For building custom read models */ export const projectionUtils = { projectPlanFromEvents, deriveLineViewsFromPlan, calculateLineExecutability, calculateCorePlanStats, calculateParallelExecutionStats }; // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // Helper Functions // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ const deriveEnrichedLines = (plan) => { const basicLines = deriveLineViewsFromPlan(plan); return basicLines.map(line => ({ ...line, executability: calculateLineExecutability(line, basicLines) })); }; const applyLineFilters = (lines, config) => { let filteredLines = lines; if (!config.includeCompletedLines) { filteredLines = filteredLines.filter(line => line.state.type !== 'Completed'); } return filteredLines; }; const applyTrackingFilters = (lines, config) => { let filteredLines = lines; if (config.includeCompletedLines === false) { filteredLines = filteredLines.filter(line => line.state.type !== 'Completed'); } return filteredLines; }; const isLineCompleted = (line) => { if (line.tasks.length === 0) return false; return line.tasks.every(task => ['Merged'].includes(task.status.type)); }; const isLineExecutable = (line, completedLines) => { if (completedLines.has(line.id)) return false; return line.dependencies.every(depId => completedLines.has(depId)); }; const isLineUnassigned = (line) => { if (line.tasks.length === 0) return true; return line.tasks.every(task => !task.assignedWorktree); }; const getCompletedLineIds = (lines) => { return new Set(lines .filter(line => isLineCompleted(line)) .map(line => line.id)); }; // Event projection helpers const updateTaskStatusInPlan = (plan, taskId, newStatus) => { const updatedTasks = plan.tasks.map(task => { if (task.id === taskId) { return { ...task, status: newStatus, updatedAt: new Date() }; } return task; }); return { ...plan, tasks: updatedTasks, updatedAt: new Date() }; }; const addTasksToPlan = (plan, taskIds) => { // Implementation would add tasks to plan return { ...plan, updatedAt: new Date() }; }; const updateTaskDependenciesInPlan = (plan, taskId, newDeps) => { const updatedTasks = plan.tasks.map(task => { if (task.id === taskId) { return { ...task, dependencies: newDeps, updatedAt: new Date() }; } return task; }); return { ...plan, tasks: updatedTasks, updatedAt: new Date() }; }; const groupTasksByBranch = (tasks) => { const tasksByBranch = new Map(); for (const task of tasks) { const branchTasks = tasksByBranch.get(task.branch) || []; branchTasks.push(task); tasksByBranch.set(task.branch, branchTasks); } return tasksByBranch; }; const createLinesFromTaskGroups = (tasksByBranch) => { const lines = []; tasksByBranch.forEach((tasks, branch) => { const lineId = generateLineId(); lines.push({ id: lineId, name: branch, branch, tasks, dependencies: [], state: { type: 'NotStarted' }, executability: { isExecutable: false, isAssigned: false, isCompleted: false, blockedBy: [] } }); }); return lines; }; const calculateLineDependencies = (lines, allTasks) => { const branchToLineId = new Map(); for (const line of lines) { branchToLineId.set(line.branch, line.id); } return lines.map(line => { const lineDeps = new Set(); for (const task of line.tasks) { for (const depTaskId of task.dependencies) { const depTask = allTasks.find(t => t.id === depTaskId); if (depTask && depTask.branch !== line.branch) { const depLineId = branchToLineId.get(depTask.branch); if (depLineId) { lineDeps.add(depLineId); } } } } return { ...line, dependencies: Array.from(lineDeps) }; }); }; const enrichLineWithAnalysis = (line) => { const state = calculateLineState(line.tasks); return { ...line, state }; }; const calculateLineState = (tasks) => { if (tasks.length === 0) { return { type: 'NotStarted' }; } const completedTasks = tasks.filter(task => task.status.type === 'Merged'); const abandonedTasks = tasks.filter(task => task.status.type === 'Abandoned'); const blockedTasks = tasks.filter(task => task.status.type === 'Blocked'); if (completedTasks.length === tasks.length) { return { type: 'Completed' }; } if (abandonedTasks.length > 0) { return { type: 'Abandoned' }; } if (blockedTasks.length > 0) { return { type: 'Blocked' }; } const startedTasks = tasks.filter(task => task.status.type !== 'ToBeRefined' && task.status.type !== 'Refined'); if (startedTasks.length > 0) { return { type: 'InProgress' }; } return { type: 'NotStarted' }; }; const generateLineId = () => { return `line_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; }; // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // Usage Examples // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ /* // Creating a basic plan view const planView = planViewQueries.fromPlan(plan, { includeCompletedLines: false }); // Creating a tracking view for parallel execution analysis const trackingView = trackViewQueries.fromPlan(plan, { includeCompletedLines: true, includeDetailedStats: true }); // Building from event stream (event sourcing) const eventSources = [ { type: 'PlanCreated', planId: 'plan-1', plan: myPlan }, { type: 'TaskStatusChanged', taskId: 'task-1', planId: 'plan-1', newStatus: { type: 'InProgress' } } ]; const reconstructedView = planViewQueries.fromEvents(eventSources); // Using parallel execution stats if (trackingView) { const { parallelExecutionStats } = trackingView.stats; console.log(`Executable lines: ${parallelExecutionStats.executableLines}`); console.log(`Unassigned lines: ${parallelExecutionStats.unassignedLines}`); } */ //# sourceMappingURL=read-model-example.js.map