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
JavaScript
/**
* 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;