apple-hig-mcp
Version:
High-performance MCP server providing instant access to Apple's Human Interface Guidelines via hybrid static/dynamic content delivery
232 lines ⢠11 kB
JavaScript
/**
* Content Validation Script
* Validates generated content using Phase 1 quality validation system
*/
import { promises as fs } from 'fs';
import path from 'path';
import { ContentQualityValidatorService } from '../services/content-quality-validator.service.js';
import { ContentProcessorService } from '../services/content-processor.service.js';
class ContentValidator {
qualityValidator;
contentProcessor;
validationResults = [];
constructor() {
this.qualityValidator = new ContentQualityValidatorService({
minQualityScore: 0.5, // Reasonable threshold for generated content
minConfidence: 0.6, // Decent confidence requirement
minContentLength: 200, // Minimum meaningful content
maxFallbackRate: 10, // Allow some fallback content
minStructureScore: 0.4, // Basic structure requirement
minAppleTermsScore: 0.1 // Some Apple terminology expected
});
this.contentProcessor = new ContentProcessorService();
}
async validateContent() {
console.log('š Starting content validation with Phase 1 quality checks...\n');
const contentDir = 'content';
// Check if content directory exists
try {
await fs.access(contentDir);
}
catch {
console.log('ā ļø Content directory not found - generating content first...');
console.log(' Run: npm run generate-content');
process.exit(1);
}
// Validate platforms directory
await this.validatePlatformsDirectory(contentDir);
// Validate metadata
await this.validateMetadata(contentDir);
// Generate validation report
this.generateValidationReport();
// Exit with appropriate code
const hasFailures = this.validationResults.some(r => !r.isValid);
if (hasFailures) {
console.log('\nā Content validation failed - some files do not meet quality standards');
process.exit(1);
}
else {
console.log('\nā
Content validation passed - all files meet quality standards');
}
}
async validatePlatformsDirectory(contentDir) {
const platformsDir = path.join(contentDir, 'platforms');
try {
const platforms = await fs.readdir(platformsDir);
console.log(`š Found ${platforms.length} platform directories: ${platforms.join(', ')}`);
for (const platform of platforms) {
await this.validatePlatformFiles(platformsDir, platform);
}
}
catch (error) {
console.error(`ā Error reading platforms directory: ${error}`);
throw error;
}
}
async validatePlatformFiles(platformsDir, platform) {
const platformDir = path.join(platformsDir, platform);
try {
const files = await fs.readdir(platformDir);
const mdFiles = files.filter(f => f.endsWith('.md'));
console.log(` š ${platform}: ${mdFiles.length} markdown files`);
for (const file of mdFiles) {
await this.validateMarkdownFile(platformDir, file, platform);
}
}
catch (error) {
console.error(`ā Error reading platform directory ${platform}: ${error}`);
throw error;
}
}
async validateMarkdownFile(platformDir, filename, platform) {
const filePath = path.join(platformDir, filename);
try {
const content = await fs.readFile(filePath, 'utf-8');
// Extract front matter and content
const { frontMatter, markdownContent } = this.parseFrontMatter(content);
// Create mock section for validation
const section = {
id: path.basename(filename, '.md'),
title: frontMatter.title || path.basename(filename, '.md'),
url: frontMatter.url || 'https://developer.apple.com/design/human-interface-guidelines/',
platform: platform,
category: frontMatter.category || 'visual-design',
quality: frontMatter.quality
};
// Validate content quality
const validationResult = await this.qualityValidator.validateContent(markdownContent, section);
// Calculate quality score for the content
const qualityScore = this.qualityValidator.calculateQualityScore(markdownContent);
const quality = {
score: qualityScore,
confidence: frontMatter.quality?.confidence || 0.7,
length: markdownContent.length,
structureScore: frontMatter.quality?.structureScore || 0.5,
appleTermsScore: frontMatter.quality?.appleTermsScore || 0.2,
codeExamplesCount: (markdownContent.match(/```/g) || []).length / 2,
imageReferencesCount: (markdownContent.match(/!\[.*?\]/g) || []).length,
headingCount: (markdownContent.match(/^#+\s/gm) || []).length,
isFallbackContent: frontMatter.quality?.isFallbackContent || false,
extractionMethod: frontMatter.quality?.extractionMethod || 'crawlee'
};
this.validationResults.push({
file: `${platform}/${filename}`,
isValid: validationResult.isValid,
quality,
issues: validationResult.issues
});
const statusEmoji = validationResult.isValid ? 'ā
' : 'ā';
const qualityEmoji = quality.score >= 0.7 ? 'š¢' : quality.score >= 0.4 ? 'š”' : 'š“';
console.log(` ${statusEmoji}${qualityEmoji} ${filename} (quality: ${quality.score.toFixed(3)}, length: ${quality.length})`);
if (!validationResult.isValid && validationResult.issues.length > 0) {
console.log(` ā ļø Issues: ${validationResult.issues.slice(0, 2).join(', ')}`);
}
}
catch (error) {
console.error(`ā Error validating file ${filename}: ${error}`);
this.validationResults.push({
file: `${platform}/${filename}`,
isValid: false,
quality: {},
issues: [`File read error: ${error}`]
});
}
}
async validateMetadata(contentDir) {
console.log('\nš Validating metadata files...');
const metadataDir = path.join(contentDir, 'metadata');
const requiredFiles = ['search-index.json', 'cross-references.json', 'generation-info.json'];
for (const file of requiredFiles) {
const filePath = path.join(metadataDir, file);
try {
await fs.access(filePath);
const content = await fs.readFile(filePath, 'utf-8');
JSON.parse(content); // Validate JSON
console.log(` ā
${file}`);
}
catch (error) {
console.log(` ā ${file} - ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
}
parseFrontMatter(content) {
const lines = content.split('\n');
if (lines[0] !== '---') {
return { frontMatter: {}, markdownContent: content };
}
const endIndex = lines.slice(1).findIndex(line => line === '---') + 1;
if (endIndex === 0) {
return { frontMatter: {}, markdownContent: content };
}
const frontMatterText = lines.slice(1, endIndex).join('\n');
const markdownContent = lines.slice(endIndex + 1).join('\n');
try {
// Simple YAML-like parsing for basic front matter
const frontMatter = {};
frontMatterText.split('\n').forEach(line => {
const colonIndex = line.indexOf(':');
if (colonIndex > 0) {
const key = line.slice(0, colonIndex).trim();
const value = line.slice(colonIndex + 1).trim();
try {
frontMatter[key] = JSON.parse(value);
}
catch {
frontMatter[key] = value.replace(/^["']|["']$/g, '');
}
}
});
return { frontMatter, markdownContent };
}
catch {
return { frontMatter: {}, markdownContent: content };
}
}
generateValidationReport() {
console.log('\nš Validation Summary Report:');
console.log('ā'.repeat(50));
const totalFiles = this.validationResults.length;
const validFiles = this.validationResults.filter(r => r.isValid).length;
const invalidFiles = totalFiles - validFiles;
console.log(`š Total files: ${totalFiles}`);
console.log(`ā
Valid files: ${validFiles} (${((validFiles / totalFiles) * 100).toFixed(1)}%)`);
console.log(`ā Invalid files: ${invalidFiles} (${((invalidFiles / totalFiles) * 100).toFixed(1)}%)`);
if (this.validationResults.length > 0) {
const averageQuality = this.validationResults
.filter(r => r.quality.score !== undefined)
.reduce((sum, r) => sum + r.quality.score, 0) / this.validationResults.length;
const averageLength = this.validationResults
.filter(r => r.quality.length !== undefined)
.reduce((sum, r) => sum + r.quality.length, 0) / this.validationResults.length;
console.log(`š Average quality score: ${averageQuality.toFixed(3)}`);
console.log(`š Average content length: ${Math.round(averageLength)} characters`);
}
// Show worst performing files
if (invalidFiles > 0) {
console.log('\nš Files needing attention:');
this.validationResults
.filter(r => !r.isValid)
.slice(0, 5)
.forEach(r => {
console.log(` ā ${r.file}: ${r.issues.slice(0, 1).join(', ')}`);
});
}
// Show SLA compliance
const stats = this.qualityValidator.getStatistics();
console.log('\nš SLA Compliance:');
console.log(`š Extraction success rate: ${stats.extractionSuccessRate.toFixed(1)}% (target: 95%+)`);
console.log(`šÆ Average quality: ${stats.averageQuality.toFixed(3)} (target: 0.6+)`);
console.log(`š Fallback usage: ${stats.fallbackUsage}% (target: <10%)`);
}
}
// Run validation if called directly
if (import.meta.url === `file://${process.argv[1]}`) {
const validator = new ContentValidator();
validator.validateContent().catch(error => {
console.error('ā Content validation failed:', error);
process.exit(1);
});
}
export { ContentValidator };
//# sourceMappingURL=validate-content.js.map