UNPKG

oneie

Version:

šŸ¤ ONE Personal Collaborative Intelligence - Creates personalized AI workspace from your me.md profile. Simple: npx oneie → edit me.md → generate personalized agents, workflows & missions. From students to enterprises, ONE adapts to your context.

466 lines (398 loc) • 13.1 kB
#!/usr/bin/env node /** * Content Validator - R.O.C.K.E.T. Framework * * Validates content according to R.O.C.K.E.T. standards: * - Role: Clear role and purpose definition * - Objective: Well-defined goals and outcomes * - Context: Sufficient background and requirements * - Key Instructions: Actionable, specific guidance * - Examples: Concrete illustrations * - Tone: Consistent voice and personality */ const fs = require('fs').promises; const path = require('path'); const yaml = require('js-yaml'); class ContentValidator { constructor() { this.validationRules = { // R.O.C.K.E.T. Framework Rules role: { required: true, patterns: [ /role:/i, /# Role/i, /## Role/i, /persona:/i, /responsibility:/i ], minLength: 10, description: "Clear role and purpose definition" }, objective: { required: true, patterns: [ /objective:/i, /# Objective/i, /## Objective/i, /goal:/i, /purpose:/i, /success criteria:/i ], minLength: 20, description: "Well-defined goals and outcomes" }, context: { required: true, patterns: [ /context:/i, /# Context/i, /## Context/i, /background:/i, /requirements:/i, /situation:/i ], minLength: 30, description: "Sufficient background and requirements" }, keyInstructions: { required: true, patterns: [ /instructions:/i, /# Instructions/i, /## Instructions/i, /steps:/i, /actions:/i, /tasks:/i ], minLength: 50, description: "Actionable, specific guidance" }, examples: { required: false, patterns: [ /example:/i, /# Example/i, /## Example/i, /```/, /for example/i, /such as/i ], minLength: 20, description: "Concrete illustrations" }, tone: { required: true, patterns: [ /tone:/i, /voice:/i, /style:/i, /personality:/i, /communication/i ], minLength: 10, description: "Consistent voice and personality" } }; this.qualityThresholds = { minimum: 6, // 60% - Basic pass good: 7, // 70% - Good quality excellent: 8, // 80% - Excellent quality perfect: 10 // 100% - Perfect score }; } /** * Validate content file */ async validateFile(filePath) { try { const content = await fs.readFile(filePath, 'utf8'); const result = await this.validateContent(content, filePath); // Log results this.logValidationResult(filePath, result); return result; } catch (error) { return { filePath, isValid: false, score: 0, errors: [`Failed to read file: ${error.message}`], warnings: [], suggestions: [] }; } } /** * Validate content string */ async validateContent(content, filePath = 'unknown') { const result = { filePath, isValid: false, score: 0, maxScore: 10, errors: [], warnings: [], suggestions: [], details: {}, rocketScore: { role: 0, objective: 0, context: 0, keyInstructions: 0, examples: 0, tone: 0 } }; // Parse content (handle YAML frontmatter) const { frontmatter, body } = this.parseContent(content); const fullContent = content.toLowerCase(); // Validate each R.O.C.K.E.T. component for (const [component, rules] of Object.entries(this.validationRules)) { const componentResult = this.validateComponent(component, rules, fullContent, frontmatter, body); result.rocketScore[component] = componentResult.score; result.details[component] = componentResult; if (componentResult.errors.length > 0) { result.errors.push(...componentResult.errors); } if (componentResult.warnings.length > 0) { result.warnings.push(...componentResult.warnings); } if (componentResult.suggestions.length > 0) { result.suggestions.push(...componentResult.suggestions); } } // Calculate overall score const totalScore = Object.values(result.rocketScore).reduce((sum, score) => sum + score, 0); const maxPossibleScore = Object.keys(this.validationRules).length * 2; // Max 2 points per component result.score = Math.round((totalScore / maxPossibleScore) * 10); // Determine if valid result.isValid = result.score >= this.qualityThresholds.minimum; // Add quality assessment result.quality = this.getQualityLevel(result.score); // Add improvement suggestions if (result.score < this.qualityThresholds.excellent) { result.suggestions.push(...this.generateImprovementSuggestions(result)); } return result; } /** * Parse content to separate frontmatter and body */ parseContent(content) { const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/); if (frontmatterMatch) { try { const frontmatter = yaml.load(frontmatterMatch[1]); const body = frontmatterMatch[2]; return { frontmatter, body }; } catch (error) { // Invalid YAML, treat as regular content return { frontmatter: {}, body: content }; } } return { frontmatter: {}, body: content }; } /** * Validate individual R.O.C.K.E.T. component */ validateComponent(component, rules, content, frontmatter, body) { const result = { component, score: 0, maxScore: 2, found: false, errors: [], warnings: [], suggestions: [] }; // Check if component exists in frontmatter const frontmatterHit = frontmatter[component] || frontmatter[component.toLowerCase()]; // Check if component exists in content patterns const patternHit = rules.patterns.some(pattern => pattern.test(content)); result.found = frontmatterHit || patternHit; if (rules.required && !result.found) { result.errors.push(`Missing required ${component}: ${rules.description}`); return result; } if (result.found) { result.score += 1; // Base point for presence // Check content quality if (frontmatterHit) { const frontmatterContent = String(frontmatterHit); if (frontmatterContent.length >= rules.minLength) { result.score += 1; // Quality point } else { result.warnings.push(`${component} content too brief (${frontmatterContent.length} chars, need ${rules.minLength}+)`); } } else if (patternHit) { // Estimate content length around pattern matches const contentLength = this.estimateComponentContentLength(content, rules.patterns); if (contentLength >= rules.minLength) { result.score += 1; // Quality point } else { result.warnings.push(`${component} content appears too brief (estimated ${contentLength} chars, need ${rules.minLength}+)`); } } } else if (!rules.required) { // Optional component not found - suggest adding it result.suggestions.push(`Consider adding ${component}: ${rules.description}`); } return result; } /** * Estimate content length for a component based on pattern matching */ estimateComponentContentLength(content, patterns) { let totalLength = 0; for (const pattern of patterns) { const matches = content.match(new RegExp(pattern.source + '[\\s\\S]{0,200}', 'gi')); if (matches) { totalLength += matches.reduce((sum, match) => sum + match.length, 0); } } return totalLength; } /** * Get quality level description */ getQualityLevel(score) { if (score >= this.qualityThresholds.perfect) return 'Perfect'; if (score >= this.qualityThresholds.excellent) return 'Excellent'; if (score >= this.qualityThresholds.good) return 'Good'; if (score >= this.qualityThresholds.minimum) return 'Acceptable'; return 'Needs Improvement'; } /** * Generate improvement suggestions */ generateImprovementSuggestions(result) { const suggestions = []; // Component-specific suggestions for (const [component, score] of Object.entries(result.rocketScore)) { if (score < 2) { const rules = this.validationRules[component]; suggestions.push(`Improve ${component}: ${rules.description}`); } } // Overall suggestions if (result.score < this.qualityThresholds.good) { suggestions.push('Consider adding more detailed examples and context'); suggestions.push('Ensure all R.O.C.K.E.T. components are clearly defined'); } return suggestions; } /** * Log validation results */ logValidationResult(filePath, result) { const relativePath = path.relative(process.cwd(), filePath); const stars = '⭐'.repeat(Math.floor(result.score / 2)); const quality = result.quality; console.log(`\nšŸ“„ ${relativePath}`); console.log(`${stars} ${result.score}/10 - ${quality}`); if (result.errors.length > 0) { console.log('āŒ Errors:'); result.errors.forEach(error => console.log(` • ${error}`)); } if (result.warnings.length > 0) { console.log('āš ļø Warnings:'); result.warnings.forEach(warning => console.log(` • ${warning}`)); } if (result.suggestions.length > 0 && result.score < this.qualityThresholds.excellent) { console.log('šŸ’” Suggestions:'); result.suggestions.forEach(suggestion => console.log(` • ${suggestion}`)); } } /** * Validate directory of files */ async validateDirectory(dirPath) { const results = []; try { const files = await fs.readdir(dirPath, { recursive: true }); for (const file of files) { const filePath = path.join(dirPath, file); const stat = await fs.stat(filePath); if (stat.isFile() && this.shouldValidateFile(filePath)) { const result = await this.validateFile(filePath); results.push(result); } } } catch (error) { console.error(`Error validating directory ${dirPath}:`, error.message); } // Summary this.logDirectorySummary(dirPath, results); return results; } /** * Check if file should be validated */ shouldValidateFile(filePath) { const ext = path.extname(filePath); const validExtensions = ['.md', '.yaml', '.yml']; return validExtensions.includes(ext); } /** * Log directory validation summary */ logDirectorySummary(dirPath, results) { if (results.length === 0) return; const totalFiles = results.length; const validFiles = results.filter(r => r.isValid).length; const avgScore = results.reduce((sum, r) => sum + r.score, 0) / totalFiles; console.log(`\nšŸ“Š Directory Summary: ${path.relative(process.cwd(), dirPath)}`); console.log(`šŸ“ Files: ${totalFiles}`); console.log(`āœ… Valid: ${validFiles} (${Math.round(validFiles/totalFiles*100)}%)`); console.log(`šŸ“ˆ Average Score: ${avgScore.toFixed(1)}/10`); // Quality distribution const qualityDist = {}; results.forEach(r => { const quality = r.quality; qualityDist[quality] = (qualityDist[quality] || 0) + 1; }); console.log('šŸ“Š Quality Distribution:'); Object.entries(qualityDist).forEach(([quality, count]) => { console.log(` ${quality}: ${count} files`); }); } } // CLI usage if (require.main === module) { const validator = new ContentValidator(); const target = process.argv[2]; if (!target) { console.log(` šŸš€ R.O.C.K.E.T. Content Validator Usage: node content-validator.js <file|directory> Examples: node content-validator.js story-1.md node content-validator.js .one/stories/ node content-validator.js . R.O.C.K.E.T. Framework: Role: Clear role and purpose definition Objective: Well-defined goals and outcomes Context: Sufficient background and requirements Key Instructions: Actionable, specific guidance Examples: Concrete illustrations Tone: Consistent voice and personality `); process.exit(1); } (async () => { try { const stat = await fs.stat(target); if (stat.isFile()) { await validator.validateFile(target); } else if (stat.isDirectory()) { await validator.validateDirectory(target); } } catch (error) { console.error(`āŒ Error: ${error.message}`); process.exit(1); } })(); } module.exports = ContentValidator;