@nicolasmirson/plan-mode-claude
Version:
Structured planning workflow for AI assistants with MCP integration
181 lines • 5.62 kB
JavaScript
import { PlanValidator, ValidationError } from './validation.js';
export class PlanManager {
constructor() {
this.state = {
isInPlanMode: false,
history: []
};
}
/**
* Enter plan mode - switches to planning state
*/
enterPlanMode() {
this.state.isInPlanMode = true;
}
/**
* Create a new plan with the given title and description
*/
createPlan(title, description) {
const plan = {
id: this.generateId(),
title,
description,
steps: [],
created: new Date(),
updated: new Date(),
status: 'draft'
};
try {
PlanValidator.validatePlan(plan);
}
catch (error) {
if (error instanceof ValidationError) {
throw error;
}
throw new Error(`Plan validation failed: ${error}`);
}
this.state.currentPlan = plan;
return plan;
}
/**
* Add a step to the current plan
*/
addStep(content, priority = 'medium', dependencies) {
if (!this.state.currentPlan) {
throw new Error('No active plan. Create a plan first.');
}
const step = {
id: this.generateId(),
content,
status: 'pending',
priority,
dependencies
};
try {
PlanValidator.validateStep(step);
}
catch (error) {
if (error instanceof ValidationError) {
throw error;
}
throw new Error(`Step validation failed: ${error}`);
}
this.state.currentPlan.steps.push(step);
this.state.currentPlan.updated = new Date();
// Validate dependencies after adding the step
try {
PlanValidator.validateDependencies(this.state.currentPlan.steps);
}
catch (error) {
// Remove the step if dependency validation fails
this.state.currentPlan.steps.pop();
throw error;
}
return step;
}
/**
* Convert plan to markdown format
*/
planToMarkdown(plan) {
let markdown = `## ${plan.title}\n\n`;
markdown += `${plan.description}\n\n`;
const highPrioritySteps = plan.steps.filter(s => s.priority === 'high');
const mediumPrioritySteps = plan.steps.filter(s => s.priority === 'medium');
const lowPrioritySteps = plan.steps.filter(s => s.priority === 'low');
if (highPrioritySteps.length > 0) {
markdown += `### High Priority\n`;
highPrioritySteps.forEach((step, index) => {
markdown += `${index + 1}. ${step.content}\n`;
});
markdown += '\n';
}
if (mediumPrioritySteps.length > 0) {
markdown += `### Medium Priority\n`;
mediumPrioritySteps.forEach((step, index) => {
markdown += `${index + 1}. ${step.content}\n`;
});
markdown += '\n';
}
if (lowPrioritySteps.length > 0) {
markdown += `### Low Priority\n`;
lowPrioritySteps.forEach((step, index) => {
markdown += `${index + 1}. ${step.content}\n`;
});
markdown += '\n';
}
return markdown;
}
/**
* Exit plan mode - present plan for approval
*/
async exitPlanMode() {
if (!this.state.currentPlan) {
throw new Error('No active plan to present.');
}
if (!this.approvalCallback) {
throw new Error('No approval callback set. Use setApprovalCallback() first.');
}
const request = {
plan: this.state.currentPlan,
markdown: this.planToMarkdown(this.state.currentPlan)
};
const response = await this.approvalCallback(request);
if (response.approved) {
this.state.currentPlan.status = 'approved';
this.state.history.push({ ...this.state.currentPlan });
this.state.isInPlanMode = false;
}
else if (response.modifications) {
// Apply modifications
Object.assign(this.state.currentPlan, response.modifications);
this.state.currentPlan.updated = new Date();
}
return response;
}
/**
* Update step status
*/
updateStepStatus(stepId, status) {
if (!this.state.currentPlan) {
throw new Error('No active plan.');
}
const step = this.state.currentPlan.steps.find(s => s.id === stepId);
if (!step) {
throw new Error(`Step with id ${stepId} not found.`);
}
step.status = status;
this.state.currentPlan.updated = new Date();
// Check if all steps are completed
if (this.state.currentPlan.steps.every(s => s.status === 'completed')) {
this.state.currentPlan.status = 'completed';
}
}
/**
* Set the approval callback function
*/
setApprovalCallback(callback) {
this.approvalCallback = callback;
}
/**
* Get current state
*/
getState() {
return { ...this.state };
}
/**
* Get current plan
*/
getCurrentPlan() {
return this.state.currentPlan;
}
/**
* Check if in plan mode
*/
isInPlanMode() {
return this.state.isInPlanMode;
}
generateId() {
return Math.random().toString(36).substr(2, 9);
}
}
//# sourceMappingURL=PlanManager.js.map