mcp-decisive
Version:
MCP server for WRAP decision-making framework with structured output
390 lines (386 loc) • 15.3 kB
JavaScript
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 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