@nicolasmirson/plan-mode-claude
Version:
Structured planning workflow for AI assistants with MCP integration
99 lines • 4.03 kB
JavaScript
export class ValidationError extends Error {
constructor(message, field) {
super(message);
this.field = field;
this.name = 'ValidationError';
}
}
export class PlanValidator {
static validatePlan(plan) {
if (!plan.title || plan.title.trim().length === 0) {
throw new ValidationError('Plan title is required', 'title');
}
if (plan.title.length > 100) {
throw new ValidationError('Plan title must be less than 100 characters', 'title');
}
if (!plan.description || plan.description.trim().length === 0) {
throw new ValidationError('Plan description is required', 'description');
}
if (plan.description.length > 1000) {
throw new ValidationError('Plan description must be less than 1000 characters', 'description');
}
if (plan.steps) {
plan.steps.forEach((step, index) => {
this.validateStep(step, index);
});
// Check for circular dependencies
this.validateDependencies(plan.steps);
}
}
static validateStep(step, index) {
const prefix = index !== undefined ? `Step ${index + 1}: ` : '';
if (!step.content || step.content.trim().length === 0) {
throw new ValidationError(`${prefix}Step content is required`, 'content');
}
if (step.content.length > 500) {
throw new ValidationError(`${prefix}Step content must be less than 500 characters`, 'content');
}
if (step.priority && !['high', 'medium', 'low'].includes(step.priority)) {
throw new ValidationError(`${prefix}Step priority must be 'high', 'medium', or 'low'`, 'priority');
}
if (step.status && !['pending', 'in_progress', 'completed'].includes(step.status)) {
throw new ValidationError(`${prefix}Step status must be 'pending', 'in_progress', or 'completed'`, 'status');
}
}
static validateDependencies(steps) {
const stepIds = new Set(steps.map(s => s.id));
// Check that all dependency IDs exist
for (const step of steps) {
if (step.dependencies) {
for (const depId of step.dependencies) {
if (!stepIds.has(depId)) {
throw new ValidationError(`Step "${step.content}" has invalid dependency: ${depId}`, 'dependencies');
}
}
}
}
// Check for circular dependencies using DFS
const visited = new Set();
const recursionStack = new Set();
const hasCycle = (stepId) => {
if (recursionStack.has(stepId)) {
return true;
}
if (visited.has(stepId)) {
return false;
}
visited.add(stepId);
recursionStack.add(stepId);
const step = steps.find(s => s.id === stepId);
if (step?.dependencies) {
for (const depId of step.dependencies) {
if (hasCycle(depId)) {
return true;
}
}
}
recursionStack.delete(stepId);
return false;
};
for (const step of steps) {
if (hasCycle(step.id)) {
throw new ValidationError('Circular dependency detected in plan steps', 'dependencies');
}
}
}
static validateMarkdown(markdown) {
if (!markdown || markdown.trim().length === 0) {
throw new ValidationError('Markdown content is required');
}
if (markdown.length > 10000) {
throw new ValidationError('Markdown content is too long (max 10000 characters)');
}
// Basic markdown structure validation
if (!markdown.includes('#')) {
throw new ValidationError('Markdown should include at least one header');
}
}
}
//# sourceMappingURL=validation.js.map