UNPKG

mcp-decisive

Version:

MCP server for WRAP decision-making framework with structured output

382 lines (377 loc) 15.9 kB
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