mcp-decisive
Version:
MCP server for WRAP decision-making framework with structured output
382 lines (377 loc) • 15.9 kB
JavaScript
import { Result, ok, err } from 'neverthrow';
const WorkPlanId = {
generate: () => {
return `WorkPlan_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
},
fromString: (value) => {
if (!value || value.trim().length === 0) {
return err({ type: 'InvalidFormat', message: 'WorkPlanId cannot be empty' });
}
if (!value.startsWith('WorkPlan_')) {
return err({ type: 'InvalidFormat', message: 'WorkPlanId must start with "WorkPlan_"' });
}
return ok(value);
},
toString: (id) => id
};
const TaskId = {
generate: () => {
return `Task_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
},
fromString: (value) => {
if (!value || value.trim().length === 0) {
return err({ type: 'InvalidFormat', message: 'TaskId cannot be empty' });
}
return ok(value);
},
toString: (id) => id
};
const TaskStatus = {
toBeRefined: () => ({ type: 'ToBeRefined' }),
refined: () => ({ type: 'Refined' }),
implemented: () => ({ type: 'Implemented' }),
reviewed: () => ({ type: 'Reviewed' }),
merged: () => ({ type: 'Merged' }),
blocked: (reason) => ({ type: 'Blocked', reason }),
abandoned: (reason) => ({ type: 'Abandoned', reason }),
canTransition: (current, next) => {
const transitions = {
'ToBeRefined': ['Refined', 'Blocked', 'Abandoned'],
'Refined': ['Implemented', 'Blocked', 'Abandoned'],
'Implemented': ['Reviewed', 'Blocked', 'Abandoned'],
'Reviewed': ['Merged', 'Implemented', 'Blocked', 'Abandoned'],
'Merged': [],
'Blocked': ['ToBeRefined', 'Refined', 'Implemented', 'Reviewed', 'Abandoned'],
'Abandoned': []
};
return transitions[current.type]?.includes(next.type) ?? false;
},
toString: (status) => {
switch (status.type) {
case 'ToBeRefined': return 'To Be Refined';
case 'Refined': return 'Refined';
case 'Implemented': return 'Implemented';
case 'Reviewed': return 'Reviewed';
case 'Merged': return 'Merged';
case 'Blocked': return `Blocked: ${status.reason}`;
case 'Abandoned': return `Abandoned: ${status.reason}`;
default:
const _exhaustive = status;
throw new Error(`Unknown status type: ${_exhaustive}`);
}
}
};
const AcceptanceCriterion = {
create: (params) => {
const errors = [];
if (!params.scenario || params.scenario.trim().length === 0) {
errors.push({ type: 'InvalidFormat', message: 'Scenario is required' });
}
if (!params.given || params.given.length === 0) {
errors.push({ type: 'InvalidFormat', message: 'Given conditions are required' });
}
if (!params.when || params.when.length === 0) {
errors.push({ type: 'InvalidFormat', message: 'When conditions are required' });
}
if (!params.then || params.then.length === 0) {
errors.push({ type: 'InvalidFormat', message: 'Then conditions are required' });
}
if (errors.length > 0) {
return err(errors);
}
return ok({
scenario: params.scenario.trim(),
given: params.given,
when: params.when,
then: params.then
});
}
};
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Implementation Section - Business Logic
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Main workflow - demonstrates function composition
const constructTask = (params) => validateTaskParams(params)
.andThen(validParams => createAcceptanceCriteriaFromParams(validParams.acceptanceCriteria)
.andThen(criteria => validateDefinitionOfReady(validParams.definitionOfReady)
.andThen(validatedDoR => ok(createTask({
...validParams,
acceptanceCriteria: criteria,
definitionOfReady: validatedDoR
})))));
const updateTaskStatus = (task, newStatus) => TaskStatus.canTransition(task.status, newStatus)
? ok({ ...task, status: newStatus })
: err([TaskError.create('InvalidStatusTransition', `Cannot transition from ${TaskStatus.toString(task.status)} to ${TaskStatus.toString(newStatus)}`)]);
const assignWorktree = (task, worktreeName) => {
const errors = validateWorktreeName(worktreeName);
return errors.length > 0
? err(errors)
: ok({ ...task, assignedWorktree: worktreeName });
};
// Helper functions
const validateTaskParams = (params) => {
const errors = [
...validateTitle(params.title),
...validateDescription(params.description),
...validateAcceptanceCriteria(params.acceptanceCriteria)
];
return errors.length > 0 ? err(errors) : ok(params);
};
const createTask = (params) => {
return {
id: TaskId.generate(),
title: params.title,
description: params.description,
branch: `feature/${params.id}`,
worktree: '',
status: TaskStatus.toBeRefined(),
dependencies: params.dependencies ?? [],
acceptanceCriteria: params.acceptanceCriteria,
definitionOfReady: params.definitionOfReady
};
};
const createAcceptanceCriteriaFromParams = (criteriaParams) => {
const results = [];
const errors = [];
for (const param of criteriaParams) {
const result = AcceptanceCriterion.create(param);
if (result.isOk()) {
results.push(result.value);
}
else {
const criterionErrors = result.error;
for (const criterionError of criterionErrors) {
errors.push(TaskError.create('AcceptanceCriteriaCreationFailed', `Failed to create acceptance criterion: ${criterionError.message}`));
}
}
}
return errors.length > 0 ? err(errors) : ok(results);
};
const validateDefinitionOfReady = (definitionOfReady) => {
const errors = [];
if (definitionOfReady.length === 0) {
errors.push(TaskError.create('DefinitionOfReadyValidationFailed', 'Definition of ready cannot be empty'));
}
for (const item of definitionOfReady) {
if (!item || item.trim().length === 0) {
errors.push(TaskError.create('DefinitionOfReadyValidationFailed', 'Definition of ready items cannot be empty'));
}
}
return errors.length > 0 ? err(errors) : ok(definitionOfReady);
};
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Business Rules - Domain Policies
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const validateTitle = (title) => {
if (!title || title.trim().length === 0) {
return [TaskError.create('InvalidTitle', 'Task title is required')];
}
if (title.length < 5) {
return [TaskError.create('InvalidTitle', 'Task title must be at least 5 characters')];
}
return [];
};
const validateDescription = (description) => {
if (!description || description.trim().length === 0) {
return [TaskError.create('InvalidDescription', 'Task description is required')];
}
return [];
};
const validateAcceptanceCriteria = (criteria) => {
if (criteria.length === 0) {
return [TaskError.create('AcceptanceCriteriaCreationFailed', 'At least one acceptance criterion is required')];
}
return [];
};
const validateWorktreeName = (worktreeName) => {
if (!worktreeName || worktreeName.trim().length === 0) {
return [TaskError.create('InvalidWorktreeName', 'Worktree name is required and cannot be empty')];
}
return [];
};
// Complex workflow with multiple validation steps
const constructWorkPlan = (params) => validateWorkPlanParams(params)
.andThen(() => createTasksFromParams(params.tasks))
.andThen(tasks => validateTaskDependencies(params.tasks, tasks)
.map(() => tasks))
.map(tasks => linkTaskDependencies(params.tasks, tasks))
.map(tasks => createWorkPlan(params, tasks));
// Sub-workflows
const validateWorkPlanParams = (params) => {
const errors = [
...validateWorkPlanName(params.name),
...validateWorkPlanTasks(params.tasks)
];
return errors.length > 0 ? err(errors) : ok(null);
};
const createTasksFromParams = (taskParams) => {
const taskResults = taskParams.map((taskParam, index) => constructTask(taskParam)
.mapErr(taskErrors => ({
type: 'TaskCreationFailed',
message: `Task ${index + 1}: ${taskErrors.map(e => e.message).join(', ')}`
})));
return Result.combine(taskResults).mapErr(errors => [errors]);
};
const validateTaskDependencies = (taskParams, tasks) => {
const errors = findDependencyViolations(taskParams);
return errors.length > 0 ? err(errors) : ok(null);
};
const linkTaskDependencies = (taskParams, tasks) => {
const paramIdToTask = new Map(taskParams.map((param, index) => [param.id, tasks[index]]));
return taskParams.map((param, index) => {
const task = tasks[index];
const depIds = param.dependencies || [];
const resolvedDeps = depIds
.map(depId => paramIdToTask.get(TaskId.toString(depId)).id);
return { ...task, dependencies: resolvedDeps };
});
};
const createWorkPlan = (params, tasks) => {
const now = new Date();
return {
id: WorkPlanId.generate(),
name: params.name,
featureBranch: params.featureBranch,
originWorktreePath: params.originWorktreePath,
evolvingPRDPath: params.evolvingPRDPath,
evolvingDesignDocPath: params.evolvingDesignDocPath,
description: params.description,
tasks,
createdAt: now,
updatedAt: now,
};
};
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Business Rules for WorkPlan
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const validateWorkPlanName = (name) => {
if (!name || name.trim().length === 0) {
return [{ type: 'InvalidName', message: 'WorkPlan name is required' }];
}
if (name.length < 3) {
return [{ type: 'InvalidName', message: 'WorkPlan name must be at least 3 characters' }];
}
return [];
};
const validateWorkPlanTasks = (tasks) => !tasks || tasks.length < 1
? [{ type: 'NoTask', message: 'WorkPlan must have at least 1 task' }]
: [];
const findDependencyViolations = (taskParams) => {
const taskIdSet = new Set(taskParams.map(p => p.id));
return taskParams.flatMap(param => {
const depIds = param.dependencies || [];
return depIds
.filter(depId => !taskIdSet.has(TaskId.toString(depId)))
.map(depId => ({
type: 'DependencyNotFound',
message: `Task "${param.id}": Dependency not found: ${TaskId.toString(depId)}`
}));
});
};
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Error Handling Utilities
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const TaskError = {
create: (type, message) => ({ type, message })
};
const WorkPlanError = {
create: (type, message) => ({ type, message })
};
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Public API - Term Model Interface
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
/**
* Task Term Model
*/
export const TaskModel = {
create: constructTask,
updateStatus: updateTaskStatus,
assignWorktree: assignWorktree
};
/**
* WorkPlan Term Model
*/
export const WorkPlanModel = {
create: constructWorkPlan
};
/**
* Value Object Constructors
*/
export const Values = {
WorkPlanId,
TaskId,
TaskStatus,
AcceptanceCriterion
};
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Usage Examples
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
/*
// Creating a task with validation
const taskResult = TaskModel.create({
id: 'task-001',
title: 'Implement user authentication',
description: 'Add OAuth2 authentication to the system',
acceptanceCriteria: [
{
scenario: 'User can log in with Google',
given: ['User has a Google account'],
when: ['User clicks "Sign in with Google"'],
then: ['User is authenticated and redirected to dashboard']
}
],
definitionOfReady: [
'Requirements are clearly defined',
'Acceptance criteria are written',
'Dependencies are identified'
]
});
// Handle the result
taskResult
.map(task => {
console.log('Task created:', task.id);
// Use the task...
})
.mapErr(errors => {
console.error('Task creation failed:', errors.map(e => e.message));
});
// Creating a work plan
const workPlanResult = WorkPlanModel.create({
name: 'User Authentication Feature',
featureBranch: 'feature/user-auth',
originWorktreePath: '/project/main',
evolvingPRDPath: '/docs/prd/user-auth.md',
evolvingDesignDocPath: '/docs/design/user-auth.md',
description: 'Implement comprehensive user authentication system',
tasks: [
{
id: 'task-001',
title: 'Implement OAuth2 flow',
description: 'Add OAuth2 authentication',
acceptanceCriteria: [
{
scenario: 'OAuth2 login',
given: ['User visits login page'],
when: ['User clicks OAuth2 provider'],
then: ['User is authenticated']
}
],
definitionOfReady: ['OAuth2 provider configured']
}
]
});
// Status transitions
if (taskResult.isOk()) {
const task = taskResult.value;
// Transition to refined status
const refinedResult = TaskModel.updateStatus(task, Values.TaskStatus.refined());
refinedResult
.map(updatedTask => {
console.log('Task refined:', updatedTask.id);
})
.mapErr(errors => {
console.error('Status transition failed:', errors.map(e => e.message));
});
}
*/
//# sourceMappingURL=term-model-example.js.map