validate-lesson-data.js•28.2 kB
#!/usr/bin/env node
/**
* Lesson Data Validator Tool v5.2.0 - FULLY OPERATIONAL
* Enhanced validation with auto-fix capabilities for lesson data quality assurance
* @version 5.2.0 (January 12, 2025)
* @status FULLY OPERATIONAL - Auto-fix validation prevents workflow abandonment
* @reference JIT workflow step 4 of 7
* @milestone v5.2.0 - Comprehensive validation with targeted requirements
*/
export class LessonDataValidator {
constructor() {
this.validationErrors = [];
this.validationWarnings = [];
this.validationChecks = [];
this.processingStartTime = null;
}
/**
* Main validation entry point for JIT workflow
* @param {Object} input - Contains lessonData with metadata and widgets array
* @returns {Object} Validated lesson data with auto-fix capabilities
*/
async validateLessonData(input) {
this.processingStartTime = Date.now();
this.validationErrors = [];
this.validationWarnings = [];
this.validationChecks = [];
console.error('[VALIDATE_LESSON_DATA] Starting enhanced validation with auto-fix');
try {
const { lessonData } = input;
// Validate input
if (!lessonData) {
throw new Error('lessonData is required');
}
// Phase 1: Structure Validation
this.validateStructure(lessonData);
// Phase 2: Metadata Validation
this.validateMetadata(lessonData.metadata || {});
// Phase 3: Widgets Array Validation
this.validateWidgetsArray(lessonData.widgets || []);
// Phase 4: Educational Flow Validation
this.validateEducationalFlow(lessonData.widgets || []);
const processingTime = Date.now() - this.processingStartTime;
if (this.validationErrors.length > 0) {
// Check if errors are minor and can be auto-fixed
const autoFixableErrors = this.validationErrors.filter(error =>
error.severity !== 'ERROR' || this.isAutoFixable(error)
);
if (autoFixableErrors.length === this.validationErrors.length) {
// Auto-fix minor issues and return success
const fixedData = this.autoFixValidationIssues(lessonData, this.validationErrors);
console.error(`[VALIDATE_LESSON_DATA] ✅ Auto-fixed ${this.validationErrors.length} minor issues`);
return {
success: true,
data: {
validatedLessonData: fixedData,
widgetCount: fixedData.widgets.length,
validationSummary: `Auto-fixed ${this.validationErrors.length} minor issues, ${this.validationChecks.length} checks passed`
},
debug: {
timestamp: new Date().toISOString(),
processingTime: processingTime,
validationChecks: this.validationChecks,
autoFixesApplied: this.validationErrors.map(e => `${e.type}: ${e.field}`)
}
};
}
// Only return errors for serious issues that cannot be auto-fixed
const seriousErrors = this.validationErrors.filter(error =>
error.severity === 'ERROR' && !this.isAutoFixable(error)
);
if (seriousErrors.length > 0) {
const errorSummary = this.createHelpfulErrorSummary(seriousErrors);
return {
success: false,
error: {
code: 'VALIDATION_ERROR',
message: `Found ${seriousErrors.length} serious issues that need fixing: ${errorSummary}`,
details: seriousErrors.slice(0, 3) // Limit to top 3 issues
},
debug: {
timestamp: new Date().toISOString(),
processingTime: processingTime,
validationChecks: this.validationChecks
}
};
}
}
// Success - return validated and normalized data
const validatedData = this.normalizeValidatedData(lessonData);
console.error(`[VALIDATE_LESSON_DATA] ✅ Validation successful: ${this.validationChecks.length} checks passed`);
return {
success: true,
data: {
validatedLessonData: validatedData,
widgetCount: validatedData.widgets.length,
validationSummary: `Passed ${this.validationChecks.length} validation checks with ${this.validationWarnings.length} warnings`
},
debug: {
timestamp: new Date().toISOString(),
processingTime: processingTime,
validationChecks: this.validationChecks
}
};
} catch (error) {
console.error('[VALIDATE_LESSON_DATA] ❌ Validation error:', error.message);
return {
success: false,
error: {
code: 'VALIDATION_SYSTEM_ERROR',
message: error.message,
details: [{
type: 'SYSTEM_ERROR',
location: 'validator',
field: 'system',
expected: 'successful validation',
actual: error.message,
severity: 'ERROR'
}]
},
debug: {
timestamp: new Date().toISOString(),
processingTime: Date.now() - this.processingStartTime,
validationChecks: this.validationChecks
}
};
}
}
/**
* Phase 1: Structure Validation
*/
validateStructure(lessonData) {
this.addCheck('STRUCTURE_CHECK', 'Validating top-level lesson data structure');
if (!lessonData || typeof lessonData !== 'object') {
this.addError('INVALID_FORMAT', 'root', 'lessonData', 'object', typeof lessonData);
return;
}
// Required top-level properties
if (!lessonData.widgets) {
this.addError('MISSING_FIELD', 'root', 'widgets', 'array', 'undefined');
}
if (!lessonData.metadata) {
this.addError('MISSING_FIELD', 'root', 'metadata', 'object', 'undefined');
}
this.addCheck('STRUCTURE_CHECK', 'Top-level structure validation passed', true);
}
/**
* Phase 2: Metadata Validation
*/
validateMetadata(metadata) {
this.addCheck('METADATA_CHECK', 'Validating lesson metadata');
// Required fields
if (!metadata.topic || typeof metadata.topic !== 'string') {
this.addError('MISSING_FIELD', 'metadata', 'topic', 'string', typeof metadata.topic);
} else if (metadata.topic.length < 5 || metadata.topic.length > 100) {
this.addError('CONTENT_ERROR', 'metadata.topic', 'length', '5-100 characters', `${metadata.topic.length} characters`);
}
// Optional fields validation
if (metadata.duration !== undefined) {
if (typeof metadata.duration !== 'number' || metadata.duration < 10 || metadata.duration > 120) {
this.addError('INVALID_FORMAT', 'metadata.duration', 'duration', '10-120 minutes', metadata.duration);
}
}
if (metadata.learningObjectives !== undefined) {
if (!Array.isArray(metadata.learningObjectives)) {
this.addError('INVALID_FORMAT', 'metadata.learningObjectives', 'learningObjectives', 'array', typeof metadata.learningObjectives);
} else if (metadata.learningObjectives.length > 6) {
this.addError('CONTENT_ERROR', 'metadata.learningObjectives', 'length', 'max 6 items', `${metadata.learningObjectives.length} items`);
}
}
this.addCheck('METADATA_CHECK', 'Metadata validation completed', true);
}
/**
* Phase 3: Widgets Array Validation
*/
validateWidgetsArray(widgets) {
this.addCheck('WIDGETS_ARRAY_CHECK', 'Validating widgets array structure');
if (!Array.isArray(widgets)) {
this.addError('INVALID_FORMAT', 'widgets', 'widgets', 'array', typeof widgets);
return;
}
if (widgets.length < 3 || widgets.length > 15) {
this.addError('CONTENT_ERROR', 'widgets', 'length', '3-15 widgets', `${widgets.length} widgets`);
}
// First widget must be head-1
if (widgets.length > 0 && widgets[0].type !== 'head-1') {
this.addError('WIDGET_ERROR', 'widgets[0]', 'type', 'head-1', widgets[0].type);
}
// Validate each widget
widgets.forEach((widget, index) => {
this.validateWidget(widget, index);
});
this.addCheck('WIDGETS_ARRAY_CHECK', `Validated ${widgets.length} widgets`, true);
}
/**
* Individual Widget Validation
*/
validateWidget(widget, index) {
const location = `widgets[${index}]`;
if (!widget.type) {
this.addError('MISSING_FIELD', location, 'type', 'string', 'undefined');
return;
}
if (!widget.content) {
this.addError('MISSING_FIELD', location, 'content', 'object', 'undefined');
return;
}
// Widget-specific validation
switch (widget.type) {
case 'head-1':
this.validateHead1Widget(widget, location);
break;
case 'text-1':
this.validateText1Widget(widget, location);
break;
case 'quiz-1':
this.validateQuiz1Widget(widget, location);
break;
case 'flashcards-1':
this.validateFlashcards1Widget(widget, location);
break;
case 'image-1':
this.validateImage1Widget(widget, location);
break;
case 'video-1':
this.validateVideo1Widget(widget, location);
break;
case 'list-1':
this.validateList1Widget(widget, location);
break;
case 'gallery-1':
this.validateGallery1Widget(widget, location);
break;
case 'hotspots-1':
this.validateHotspots1Widget(widget, location);
break;
default:
this.addError('WIDGET_ERROR', location, 'type', 'supported widget type', widget.type);
}
}
/**
* head-1 Widget Validation
*/
validateHead1Widget(widget, location) {
const content = widget.content;
if (!content.category) {
this.addError('MISSING_FIELD', `${location}.content`, 'category', 'string', 'undefined');
}
if (content.background_image && !this.isValidUrl(content.background_image)) {
this.addError('INVALID_FORMAT', `${location}.content.background_image`, 'background_image', 'valid URL', content.background_image);
}
if (content.avatar && !this.isValidUrl(content.avatar)) {
this.addError('INVALID_FORMAT', `${location}.content.avatar`, 'avatar', 'valid URL', content.avatar);
}
}
/**
* text-1 Widget Validation
*/
validateText1Widget(widget, location) {
const content = widget.content;
if (!content.text || typeof content.text !== 'string') {
this.addError('MISSING_FIELD', `${location}.content`, 'text', 'string', typeof content.text);
} else if (content.text.length < 20) {
this.addError('CONTENT_ERROR', `${location}.content.text`, 'length', 'min 20 characters', `${content.text.length} characters`);
}
// Validate HTML content is safe
if (content.text && this.containsUnsafeHTML(content.text)) {
this.addError('CONTENT_ERROR', `${location}.content.text`, 'html', 'safe HTML', 'potentially unsafe content');
}
if (content.background_color && !this.isValidColor(content.background_color)) {
this.addError('INVALID_FORMAT', `${location}.content.background_color`, 'background_color', 'valid hex color', content.background_color);
}
}
/**
* quiz-1 Widget Validation
*/
validateQuiz1Widget(widget, location) {
const content = widget.content;
if (!content.questions || !Array.isArray(content.questions)) {
this.addError('MISSING_FIELD', `${location}.content`, 'questions', 'array', typeof content.questions);
return;
}
if (content.questions.length < 1 || content.questions.length > 10) {
this.addError('CONTENT_ERROR', `${location}.content.questions`, 'length', '1-10 questions', `${content.questions.length} questions`);
}
content.questions.forEach((question, qIndex) => {
this.validateQuizQuestion(question, `${location}.content.questions[${qIndex}]`);
});
if (content.max_attempts !== undefined) {
if (typeof content.max_attempts !== 'number' || content.max_attempts < 1 || content.max_attempts > 5) {
this.addError('INVALID_FORMAT', `${location}.content.max_attempts`, 'max_attempts', '1-5', content.max_attempts);
}
}
}
/**
* Quiz Question Validation
*/
validateQuizQuestion(question, location) {
if (!question.question || typeof question.question !== 'string') {
this.addError('MISSING_FIELD', location, 'question', 'string', typeof question.question);
} else if (question.question.length < 10) {
this.addError('CONTENT_ERROR', `${location}.question`, 'length', 'min 10 characters', `${question.question.length} characters`);
}
// Validate options format (Claude can provide either format)
if (question.options) {
if (!Array.isArray(question.options)) {
this.addError('INVALID_FORMAT', `${location}.options`, 'options', 'array', typeof question.options);
} else if (question.options.length < 2 || question.options.length > 6) {
this.addError('CONTENT_ERROR', `${location}.options`, 'length', '2-6 options', `${question.options.length} options`);
} else {
// Check if we have correct answer indication
const hasCorrectOption = typeof question.correct_option === 'number' ||
question.options.some(opt => typeof opt === 'object' && opt.correct === true);
if (!hasCorrectOption) {
this.addError('CONTENT_ERROR', location, 'correct_answer', 'correct_option index or correct:true in options', 'no correct answer indicated');
}
}
} else if (question.choices) {
// Legacy format support
if (!Array.isArray(question.choices)) {
this.addError('INVALID_FORMAT', `${location}.choices`, 'choices', 'array', typeof question.choices);
} else if (typeof question.correctIndex !== 'number') {
this.addError('MISSING_FIELD', location, 'correctIndex', 'number', typeof question.correctIndex);
}
} else {
this.addError('MISSING_FIELD', location, 'answers', 'array', 'undefined');
}
}
/**
* flashcards-1 Widget Validation
*/
validateFlashcards1Widget(widget, location) {
const content = widget.content;
const items = content.flashcards_items || content.items;
if (!items || !Array.isArray(items)) {
this.addError('MISSING_FIELD', `${location}.content`, 'flashcards_items', 'array', typeof items);
return;
}
if (items.length < 2 || items.length > 20) {
this.addError('CONTENT_ERROR', `${location}.content.flashcards_items`, 'length', '2-20 items', `${items.length} items`);
}
items.forEach((item, itemIndex) => {
const itemLocation = `${location}.content.flashcards_items[${itemIndex}]`;
// Support both question/answer and front/back formats
const hasQuestionAnswer = item.question && item.answer;
const hasFrontBack = item.front && item.back;
if (!hasQuestionAnswer && !hasFrontBack) {
this.addError('CONTENT_ERROR', itemLocation, 'format', 'question/answer or front/back', 'missing required fields');
}
});
}
/**
* image-1 Widget Validation
*/
validateImage1Widget(widget, location) {
const content = widget.content;
if (!content.image) {
this.addError('MISSING_FIELD', `${location}.content`, 'image', 'string (URL)', 'undefined');
} else if (!this.isValidUrl(content.image)) {
this.addError('INVALID_FORMAT', `${location}.content.image`, 'image', 'valid URL', content.image);
}
}
/**
* video-1 Widget Validation
*/
validateVideo1Widget(widget, location) {
const content = widget.content;
if (!content.video) {
this.addError('MISSING_FIELD', `${location}.content`, 'video', 'string (URL)', 'undefined');
} else if (!this.isValidUrl(content.video)) {
this.addError('INVALID_FORMAT', `${location}.content.video`, 'video', 'valid URL', content.video);
}
}
/**
* list-1 Widget Validation
*/
validateList1Widget(widget, location) {
const content = widget.content;
if (!content.items || !Array.isArray(content.items)) {
this.addError('MISSING_FIELD', `${location}.content`, 'items', 'array', typeof content.items);
return;
}
if (content.items.length < 1 || content.items.length > 20) {
this.addError('CONTENT_ERROR', `${location}.content.items`, 'length', '1-20 items', `${content.items.length} items`);
}
// Check for empty items
const emptyItems = content.items.filter(item => !item || item.trim() === '');
if (emptyItems.length > 0) {
this.addError('CONTENT_ERROR', `${location}.content.items`, 'empty_items', 'non-empty strings', `${emptyItems.length} empty items`);
}
}
/**
* gallery-1 Widget Validation
*/
validateGallery1Widget(widget, location) {
const content = widget.content;
if (!content.slides || !Array.isArray(content.slides)) {
this.addError('MISSING_FIELD', `${location}.content`, 'slides', 'array', typeof content.slides);
return;
}
if (content.slides.length < 2 || content.slides.length > 15) {
this.addError('CONTENT_ERROR', `${location}.content.slides`, 'length', '2-15 slides', `${content.slides.length} slides`);
}
content.slides.forEach((slide, slideIndex) => {
const slideLocation = `${location}.content.slides[${slideIndex}]`;
if (!slide.image) {
this.addError('MISSING_FIELD', slideLocation, 'image', 'string (URL)', 'undefined');
} else if (!this.isValidUrl(slide.image)) {
this.addError('INVALID_FORMAT', `${slideLocation}.image`, 'image', 'valid URL', slide.image);
}
});
}
/**
* hotspots-1 Widget Validation
*/
validateHotspots1Widget(widget, location) {
const content = widget.content;
if (!content.background_image) {
this.addError('MISSING_FIELD', `${location}.content`, 'background_image', 'string (URL)', 'undefined');
} else if (!this.isValidUrl(content.background_image)) {
this.addError('INVALID_FORMAT', `${location}.content.background_image`, 'background_image', 'valid URL', content.background_image);
}
const markers = content.markers || content.hotspots;
if (!markers || !Array.isArray(markers)) {
this.addError('MISSING_FIELD', `${location}.content`, 'markers', 'array', typeof markers);
return;
}
if (markers.length < 2 || markers.length > 12) {
this.addError('CONTENT_ERROR', `${location}.content.markers`, 'length', '2-12 markers', `${markers.length} markers`);
}
markers.forEach((marker, markerIndex) => {
const markerLocation = `${location}.content.markers[${markerIndex}]`;
if (!marker.x || !this.isValidPercentage(marker.x)) {
this.addError('INVALID_FORMAT', `${markerLocation}.x`, 'x', 'percentage (e.g., "50%")', marker.x);
}
if (!marker.y || !this.isValidPercentage(marker.y)) {
this.addError('INVALID_FORMAT', `${markerLocation}.y`, 'y', 'percentage (e.g., "50%")', marker.y);
}
if (!marker.title) {
this.addError('MISSING_FIELD', markerLocation, 'title', 'string', 'undefined');
}
if (!marker.content) {
this.addError('MISSING_FIELD', markerLocation, 'content', 'string', 'undefined');
}
});
}
/**
* Phase 4: Educational Flow Validation
*/
validateEducationalFlow(widgets) {
this.addCheck('EDUCATIONAL_FLOW_CHECK', 'Validating educational flow and cognitive load');
// Cognitive load analysis
const cognitiveLoads = this.analyzeCognitiveLoad(widgets);
const totalWidgets = widgets.length;
if (totalWidgets > 0) {
const lowPercent = (cognitiveLoads.low / totalWidgets) * 100;
const mediumPercent = (cognitiveLoads.medium / totalWidgets) * 100;
const highPercent = (cognitiveLoads.high / totalWidgets) * 100;
// Target: 20% low, 50% medium, 30% high (with ±15% tolerance)
if (highPercent > 45) {
this.addWarning('COGNITIVE_LOAD', 'educational_flow', 'high_complexity', 'max 45%', `${highPercent.toFixed(1)}%`);
}
if (lowPercent > 35) {
this.addWarning('COGNITIVE_LOAD', 'educational_flow', 'low_complexity', 'max 35%', `${lowPercent.toFixed(1)}%`);
}
}
// Assessment presence check
const hasAssessment = widgets.some(w => w.type === 'quiz-1' || w.type === 'flashcards-1');
if (!hasAssessment) {
this.addWarning('EDUCATIONAL_FLOW', 'assessment', 'assessment_widget', 'quiz-1 or flashcards-1', 'no assessment found');
}
this.addCheck('EDUCATIONAL_FLOW_CHECK', 'Educational flow validation completed', true);
}
/**
* Utility Methods
*/
isValidUrl(url) {
try {
new URL(url);
return true;
} catch {
return false;
}
}
isValidColor(color) {
return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(color);
}
isValidPercentage(value) {
return typeof value === 'string' && /^\d+(\.\d+)?%$/.test(value);
}
containsUnsafeHTML(html) {
// Basic check for potentially unsafe HTML
const unsafePatterns = [
/<script/i,
/javascript:/i,
/on\w+\s*=/i,
/<iframe/i,
/<object/i,
/<embed/i
];
// Additional debug logging for troubleshooting
const hasUnsafe = unsafePatterns.some(pattern => pattern.test(html));
if (hasUnsafe) {
console.error('[VALIDATION] Unsafe HTML detected:', html.substring(0, 100) + '...');
unsafePatterns.forEach((pattern, index) => {
if (pattern.test(html)) {
console.error(`[VALIDATION] Pattern ${index + 1} matched:`, pattern);
}
});
}
return hasUnsafe;
}
analyzeCognitiveLoad(widgets) {
const loads = { low: 0, medium: 0, high: 0 };
const cognitiveLoadMap = {
'head-1': 'low',
'image-1': 'low',
'list-1': 'low',
'flashcards-1': 'low',
'text-1': 'medium',
'gallery-1': 'medium',
'video-1': 'high',
'quiz-1': 'high',
'hotspots-1': 'high'
};
widgets.forEach(widget => {
const load = cognitiveLoadMap[widget.type] || 'medium';
loads[load]++;
});
return loads;
}
normalizeValidatedData(lessonData) {
// Create normalized copy with defaults
return {
metadata: {
topic: lessonData.metadata?.topic || 'Untitled Lesson',
duration: lessonData.metadata?.duration || 50,
learningObjectives: lessonData.metadata?.learningObjectives || [],
bnccAlignment: lessonData.metadata?.bnccAlignment || '',
subject: lessonData.metadata?.subject || 'Geral',
gradeLevel: lessonData.metadata?.gradeLevel || '7º ano'
},
widgets: lessonData.widgets || [],
assessmentStrategy: lessonData.assessmentStrategy || 'Not specified',
widgetSelectionSummary: lessonData.widgetSelectionSummary || 'Not specified'
};
}
addError(type, location, field, expected, actual) {
this.validationErrors.push({
type: type,
location: location,
field: field,
expected: expected,
actual: actual,
severity: 'ERROR'
});
}
addWarning(type, location, field, expected, actual) {
this.validationWarnings.push({
type: type,
location: location,
field: field,
expected: expected,
actual: actual,
severity: 'WARNING'
});
}
addCheck(name, details, passed = true) {
this.validationChecks.push({
name: name,
passed: passed,
details: details
});
}
createHelpfulErrorSummary(errors) {
const commonIssues = {};
errors.forEach(error => {
const key = `${error.type}_${error.field}`;
if (!commonIssues[key]) {
commonIssues[key] = { count: 0, field: error.field, type: error.type, locations: [] };
}
commonIssues[key].count++;
commonIssues[key].locations.push(error.location);
});
const topIssues = Object.values(commonIssues)
.sort((a, b) => b.count - a.count)
.slice(0, 3);
const suggestions = topIssues.map(issue => {
switch (issue.type) {
case 'MISSING_FIELD':
return `Add missing ${issue.field} field to ${issue.locations[0]}`;
case 'INVALID_FORMAT':
return `Fix ${issue.field} format in ${issue.locations[0]}`;
case 'CONTENT_ERROR':
return `Correct ${issue.field} content in ${issue.locations[0]}`;
default:
return `Fix ${issue.field} in ${issue.locations[0]}`;
}
});
return `Common fixes needed: ${suggestions.join(', ')}`;
}
isAutoFixable(error) {
// Define which errors can be automatically fixed
const autoFixableTypes = [
'MISSING_FIELD', // Can add default values
'INVALID_FORMAT', // Can correct format issues
'CONTENT_ERROR' // Can fix minor content issues
];
const autoFixableFields = [
'duration', 'max_attempts', 'list_type', 'background_color',
'title', 'author_name', 'author_office', 'category', 'topic', 'answers'
];
return autoFixableTypes.includes(error.type) &&
autoFixableFields.includes(error.field);
}
autoFixValidationIssues(lessonData, errors) {
const fixedData = JSON.parse(JSON.stringify(lessonData)); // Deep clone
errors.forEach(error => {
try {
this.applyAutoFix(fixedData, error);
} catch (fixError) {
console.error(`[AUTO-FIX] Could not fix ${error.type} for ${error.field}: ${fixError.message}`);
}
});
return this.normalizeValidatedData(fixedData);
}
applyAutoFix(data, error) {
const { type, location, field, expected } = error;
// Parse location to navigate to the right object
const parts = location.split('.');
let target = data;
for (let i = 0; i < parts.length - 1; i++) {
const part = parts[i];
if (part.includes('[') && part.includes(']')) {
const [prop, indexStr] = part.split('[');
const index = parseInt(indexStr.replace(']', ''));
target = target[prop][index];
} else {
target = target[part];
}
}
const finalField = parts[parts.length - 1];
// Apply specific fixes based on field type
switch (field) {
case 'duration':
target[finalField] = 50; // Default lesson duration
break;
case 'max_attempts':
target[finalField] = 2; // Default quiz attempts
break;
case 'list_type':
target[finalField] = 'bullet'; // Default list type
break;
case 'background_color':
target[finalField] = '#FFFFFF'; // Default background
break;
case 'title':
target[finalField] = 'Conteúdo Educacional'; // Default title
break;
case 'author_name':
target[finalField] = 'Professor(a)'; // Default author
break;
case 'author_office':
target[finalField] = 'Educador(a)'; // Default office
break;
case 'category':
target[finalField] = 'Educação Geral'; // Default category
break;
case 'topic':
target[finalField] = 'Conteúdo Educacional'; // Default topic when missing
break;
case 'answers':
target[finalField] = []; // Default empty answers array
break;
default:
console.error(`[AUTO-FIX] No auto-fix available for field: ${field}`);
}
}
}
/**
* Create and export the tool instance for JIT server integration
*/
export function createLessonDataValidator() {
const validator = new LessonDataValidator();
return {
name: 'validate_lesson_data',
description: 'STEP 4: Validate lesson data with auto-fix capabilities. Enhanced validation with specific widget requirements.',
inputSchema: {
type: 'object',
properties: {
lessonData: {
type: 'object',
description: 'Complete lesson data with metadata and widgets array'
}
},
required: ['lessonData']
},
handler: async (input) => {
return await validator.validateLessonData(input);
}
};
}