apple-hig-mcp
Version:
High-performance MCP server providing instant access to Apple's Human Interface Guidelines via hybrid static/dynamic content delivery
856 lines (809 loc) ⢠71.2 kB
JavaScript
#!/usr/bin/env node
/**
* Content Generator
* Follows SOLID principles with proper separation of concerns and semantic search capabilities
*/
import path from 'path';
import { HIGCache } from '../cache.js';
import { CrawleeHIGService } from '../services/crawlee-hig.service.js';
import { HIGContentExtractor } from '../services/hig-content-extractor.service.js';
import { ComprehensiveHIGDiscoveryService } from '../services/comprehensive-hig-discovery.service.js';
// Services (Dependency Injection)
import { FileSystemService } from '../services/file-system.service.js';
import { ContentProcessorService } from '../services/content-processor.service.js';
import { ContentQualityValidatorService } from '../services/content-quality-validator.service.js';
import { SearchIndexerService } from '../services/search-indexer.service.js';
import { CrossReferenceGeneratorService } from '../services/cross-reference-generator.service.js';
/**
* Main Content Generator Class
* Single Responsibility: Orchestrate the content generation process
*/
export class ContentGenerator {
config;
fileSystem;
contentProcessor;
searchIndexer;
crossReferenceGenerator;
crawleeService;
contentExtractor;
cache;
sections = [];
startTime = Date.now();
extractionStats = {
totalSections: 0,
successfulExtractions: 0,
fallbackUsage: 0,
averageQuality: 0,
averageConfidence: 0,
extractionSuccessRate: 0
};
qualityValidator;
comprehensiveDiscovery;
constructor(config, fileSystem, contentProcessor, searchIndexer, crossReferenceGenerator, crawleeService, contentExtractor, cache) {
this.config = config;
this.fileSystem = fileSystem;
this.contentProcessor = contentProcessor;
this.searchIndexer = searchIndexer;
this.crossReferenceGenerator = crossReferenceGenerator;
this.crawleeService = crawleeService;
this.contentExtractor = contentExtractor;
this.cache = cache;
// Initialize quality validator with enhanced thresholds
this.qualityValidator = new ContentQualityValidatorService({
minQualityScore: 0.6, // Higher than default
minConfidence: 0.7, // Higher than default
minContentLength: 300, // Higher than default
maxFallbackRate: 3, // Lower than default (97%+ real content)
minStructureScore: 0.5, // Higher than default
minAppleTermsScore: 0.2 // Higher than default
});
// Initialize comprehensive discovery service
this.comprehensiveDiscovery = new ComprehensiveHIGDiscoveryService(this.cache);
}
/**
* Main generation process - orchestrates all steps
*/
async generate() {
console.log('š Starting Apple HIG content generation...');
console.log(`š Output directory: ${this.config.outputDirectory}`);
try {
// Step 1: Discovery
await this.discoverSections();
// Step 2: Setup
await this.createDirectoryStructure();
// Step 3: Content generation
await this.generateContent();
// Step 4: Metadata generation
await this.generateMetadata();
const duration = Date.now() - this.startTime;
console.log(`ā
Content generation completed in ${duration}ms`);
console.log(`š Generated ${this.sections.length} sections`);
// Log extraction statistics and SLA compliance
this.logExtractionResults();
// Generate and log comprehensive quality report
this.logQualityReport();
}
catch (error) {
console.error('ā Content generation failed:', error);
throw error;
}
}
/**
* Discover all HIG sections from Apple's website using dynamic discovery
*/
async discoverSections() {
// Use fast discovery when semantic search is disabled (offline mode)
if (process.env.DISABLE_SEMANTIC_SEARCH === 'true') {
console.log('š Using comprehensive static section discovery (offline mode)...');
this.sections = this.getStaticSections();
}
else {
console.log('š Discovering HIG sections dynamically from Apple website...');
try {
// Use comprehensive discovery combining known patterns + HTTP discovery
this.sections = await this.comprehensiveDiscovery.discoverSections();
if (this.sections.length < 50) {
throw new Error(`Insufficient sections discovered: ${this.sections.length}`);
}
console.log(`ā
Comprehensive discovery found ${this.sections.length} sections`);
}
catch (error) {
console.warn('ā ļø Comprehensive discovery failed, falling back to static sections:', error);
console.log('š Using static section discovery as fallback...');
this.sections = this.getStaticSections();
}
}
this.extractionStats.totalSections = this.sections.length;
// Log discovery results
const platformCounts = this.sections.reduce((acc, section) => {
acc[section.platform] = (acc[section.platform] || 0) + 1;
return acc;
}, {});
console.log(`š Discovered ${this.sections.length} sections:`);
Object.entries(platformCounts).forEach(([platform, count]) => {
console.log(` ${platform}: ${count} sections`);
});
// Log extraction method distribution
console.log('šÆ Target: High-quality Apple HIG content for AI assistant use');
}
/**
* Get comprehensive static list of all major HIG sections
* This serves as a reliable fallback when dynamic discovery fails
* and provides much better coverage than the previous 14 sections
*/
getStaticSections() {
const comprehensiveHIGSections = [
// Universal/Foundations - Core design principles
{ id: 'accessibility', title: 'Accessibility', platform: 'universal', category: 'foundations', url: 'https://developer.apple.com/design/human-interface-guidelines/accessibility' },
{ id: 'inclusion', title: 'Inclusion', platform: 'universal', category: 'foundations', url: 'https://developer.apple.com/design/human-interface-guidelines/inclusion' },
{ id: 'privacy', title: 'Privacy', platform: 'universal', category: 'foundations', url: 'https://developer.apple.com/design/human-interface-guidelines/privacy' },
{ id: 'branding', title: 'Branding', platform: 'universal', category: 'foundations', url: 'https://developer.apple.com/design/human-interface-guidelines/branding' },
// Layout and Organization
{ id: 'layout', title: 'Layout', platform: 'universal', category: 'layout', url: 'https://developer.apple.com/design/human-interface-guidelines/layout' },
{ id: 'collections', title: 'Collections', platform: 'universal', category: 'layout', url: 'https://developer.apple.com/design/human-interface-guidelines/collections' },
{ id: 'lists-and-tables', title: 'Lists and Tables', platform: 'universal', category: 'layout', url: 'https://developer.apple.com/design/human-interface-guidelines/lists-and-tables' },
{ id: 'split-views', title: 'Split Views', platform: 'universal', category: 'layout', url: 'https://developer.apple.com/design/human-interface-guidelines/split-views' },
{ id: 'scroll-views', title: 'Scroll Views', platform: 'universal', category: 'layout', url: 'https://developer.apple.com/design/human-interface-guidelines/scroll-views' },
// Navigation and Search
{ id: 'navigation-bars', title: 'Navigation Bars', platform: 'universal', category: 'navigation', url: 'https://developer.apple.com/design/human-interface-guidelines/navigation-bars' },
{ id: 'tab-bars', title: 'Tab Bars', platform: 'universal', category: 'navigation', url: 'https://developer.apple.com/design/human-interface-guidelines/tab-bars' },
{ id: 'sidebars', title: 'Sidebars', platform: 'universal', category: 'navigation', url: 'https://developer.apple.com/design/human-interface-guidelines/sidebars' },
{ id: 'search-fields', title: 'Search Fields', platform: 'universal', category: 'navigation', url: 'https://developer.apple.com/design/human-interface-guidelines/search-fields' },
{ id: 'searching', title: 'Searching', platform: 'universal', category: 'navigation', url: 'https://developer.apple.com/design/human-interface-guidelines/searching' },
// Presentation
{ id: 'alerts', title: 'Alerts', platform: 'universal', category: 'presentation', url: 'https://developer.apple.com/design/human-interface-guidelines/alerts' },
{ id: 'action-sheets', title: 'Action Sheets', platform: 'universal', category: 'presentation', url: 'https://developer.apple.com/design/human-interface-guidelines/action-sheets' },
{ id: 'sheets', title: 'Sheets', platform: 'universal', category: 'presentation', url: 'https://developer.apple.com/design/human-interface-guidelines/sheets' },
{ id: 'popovers', title: 'Popovers', platform: 'universal', category: 'presentation', url: 'https://developer.apple.com/design/human-interface-guidelines/popovers' },
{ id: 'modality', title: 'Modality', platform: 'universal', category: 'presentation', url: 'https://developer.apple.com/design/human-interface-guidelines/modality' },
{ id: 'notifications', title: 'Notifications', platform: 'universal', category: 'presentation', url: 'https://developer.apple.com/design/human-interface-guidelines/notifications' },
// Selection and Input
{ id: 'buttons', title: 'Buttons', platform: 'universal', category: 'selection-and-input', url: 'https://developer.apple.com/design/human-interface-guidelines/buttons' },
{ id: 'text-fields', title: 'Text Fields', platform: 'universal', category: 'selection-and-input', url: 'https://developer.apple.com/design/human-interface-guidelines/text-fields' },
{ id: 'text-views', title: 'Text Views', platform: 'universal', category: 'selection-and-input', url: 'https://developer.apple.com/design/human-interface-guidelines/text-views' },
{ id: 'pickers', title: 'Pickers', platform: 'universal', category: 'selection-and-input', url: 'https://developer.apple.com/design/human-interface-guidelines/pickers' },
{ id: 'segmented-controls', title: 'Segmented Controls', platform: 'universal', category: 'selection-and-input', url: 'https://developer.apple.com/design/human-interface-guidelines/segmented-controls' },
{ id: 'sliders', title: 'Sliders', platform: 'universal', category: 'selection-and-input', url: 'https://developer.apple.com/design/human-interface-guidelines/sliders' },
{ id: 'toggles', title: 'Toggles', platform: 'universal', category: 'selection-and-input', url: 'https://developer.apple.com/design/human-interface-guidelines/toggles' },
{ id: 'steppers', title: 'Steppers', platform: 'universal', category: 'selection-and-input', url: 'https://developer.apple.com/design/human-interface-guidelines/steppers' },
// Visual Design
{ id: 'menus', title: 'Menus', platform: 'universal', category: 'visual-design', url: 'https://developer.apple.com/design/human-interface-guidelines/menus' },
{ id: 'toolbars', title: 'Toolbars', platform: 'universal', category: 'visual-design', url: 'https://developer.apple.com/design/human-interface-guidelines/toolbars' },
{ id: 'context-menus', title: 'Context Menus', platform: 'universal', category: 'visual-design', url: 'https://developer.apple.com/design/human-interface-guidelines/context-menus' },
{ id: 'pull-down-buttons', title: 'Pull-down Buttons', platform: 'universal', category: 'visual-design', url: 'https://developer.apple.com/design/human-interface-guidelines/pull-down-buttons' },
{ id: 'pop-up-buttons', title: 'Pop-up Buttons', platform: 'universal', category: 'visual-design', url: 'https://developer.apple.com/design/human-interface-guidelines/pop-up-buttons' },
// Icons and Images
{ id: 'app-icons', title: 'App Icons', platform: 'universal', category: 'icons-and-images', url: 'https://developer.apple.com/design/human-interface-guidelines/app-icons' },
{ id: 'sf-symbols', title: 'SF Symbols', platform: 'universal', category: 'icons-and-images', url: 'https://developer.apple.com/design/human-interface-guidelines/sf-symbols' },
{ id: 'icons', title: 'Icons', platform: 'universal', category: 'icons-and-images', url: 'https://developer.apple.com/design/human-interface-guidelines/icons' },
{ id: 'images', title: 'Images', platform: 'universal', category: 'icons-and-images', url: 'https://developer.apple.com/design/human-interface-guidelines/images' },
// Color and Materials
{ id: 'color', title: 'Color', platform: 'universal', category: 'color-and-materials', url: 'https://developer.apple.com/design/human-interface-guidelines/color' },
{ id: 'materials', title: 'Materials', platform: 'universal', category: 'color-and-materials', url: 'https://developer.apple.com/design/human-interface-guidelines/materials' },
{ id: 'dark-mode', title: 'Dark Mode', platform: 'universal', category: 'color-and-materials', url: 'https://developer.apple.com/design/human-interface-guidelines/dark-mode' },
// Typography
{ id: 'typography', title: 'Typography', platform: 'universal', category: 'typography', url: 'https://developer.apple.com/design/human-interface-guidelines/typography' },
{ id: 'writing', title: 'Writing', platform: 'universal', category: 'typography', url: 'https://developer.apple.com/design/human-interface-guidelines/writing' },
// Motion
{ id: 'motion', title: 'Motion', platform: 'universal', category: 'motion', url: 'https://developer.apple.com/design/human-interface-guidelines/motion' },
// iOS Specific
{ id: 'designing-for-ios', title: 'Designing for iOS', platform: 'iOS', category: 'foundations', url: 'https://developer.apple.com/design/human-interface-guidelines/designing-for-ios' },
{ id: 'home-screen-quick-actions', title: 'Home Screen Quick Actions', platform: 'iOS', category: 'system-capabilities', url: 'https://developer.apple.com/design/human-interface-guidelines/home-screen-quick-actions' },
{ id: 'widgets', title: 'Widgets', platform: 'iOS', category: 'system-capabilities', url: 'https://developer.apple.com/design/human-interface-guidelines/widgets' },
{ id: 'live-activities', title: 'Live Activities', platform: 'iOS', category: 'system-capabilities', url: 'https://developer.apple.com/design/human-interface-guidelines/live-activities' },
// macOS Specific
{ id: 'designing-for-macos', title: 'Designing for macOS', platform: 'macOS', category: 'foundations', url: 'https://developer.apple.com/design/human-interface-guidelines/designing-for-macos' },
{ id: 'windows', title: 'Windows', platform: 'macOS', category: 'layout', url: 'https://developer.apple.com/design/human-interface-guidelines/windows' },
{ id: 'panels', title: 'Panels', platform: 'macOS', category: 'presentation', url: 'https://developer.apple.com/design/human-interface-guidelines/panels' },
{ id: 'the-menu-bar', title: 'The Menu Bar', platform: 'macOS', category: 'system-capabilities', url: 'https://developer.apple.com/design/human-interface-guidelines/the-menu-bar' },
// watchOS Specific
{ id: 'designing-for-watchos', title: 'Designing for watchOS', platform: 'watchOS', category: 'foundations', url: 'https://developer.apple.com/design/human-interface-guidelines/designing-for-watchos' },
{ id: 'complications', title: 'Complications', platform: 'watchOS', category: 'system-capabilities', url: 'https://developer.apple.com/design/human-interface-guidelines/complications' },
{ id: 'digital-crown', title: 'Digital Crown', platform: 'watchOS', category: 'selection-and-input', url: 'https://developer.apple.com/design/human-interface-guidelines/digital-crown' },
{ id: 'always-on', title: 'Always On', platform: 'watchOS', category: 'system-capabilities', url: 'https://developer.apple.com/design/human-interface-guidelines/always-on' },
// tvOS Specific
{ id: 'designing-for-tvos', title: 'Designing for tvOS', platform: 'tvOS', category: 'foundations', url: 'https://developer.apple.com/design/human-interface-guidelines/designing-for-tvos' },
{ id: 'focus-and-selection', title: 'Focus and Selection', platform: 'tvOS', category: 'foundations', url: 'https://developer.apple.com/design/human-interface-guidelines/focus-and-selection' },
{ id: 'top-shelf', title: 'Top Shelf', platform: 'tvOS', category: 'system-capabilities', url: 'https://developer.apple.com/design/human-interface-guidelines/top-shelf' },
// visionOS Specific
{ id: 'designing-for-visionos', title: 'Designing for visionOS', platform: 'visionOS', category: 'foundations', url: 'https://developer.apple.com/design/human-interface-guidelines/designing-for-visionos' },
{ id: 'spatial-layout', title: 'Spatial Layout', platform: 'visionOS', category: 'layout', url: 'https://developer.apple.com/design/human-interface-guidelines/spatial-layout' },
{ id: 'eyes', title: 'Eyes', platform: 'visionOS', category: 'selection-and-input', url: 'https://developer.apple.com/design/human-interface-guidelines/eyes' },
{ id: 'gestures', title: 'Gestures', platform: 'visionOS', category: 'selection-and-input', url: 'https://developer.apple.com/design/human-interface-guidelines/gestures' },
{ id: 'immersive-experiences', title: 'Immersive Experiences', platform: 'visionOS', category: 'system-capabilities', url: 'https://developer.apple.com/design/human-interface-guidelines/immersive-experiences' },
// Key Technologies
{ id: 'apple-pay', title: 'Apple Pay', platform: 'universal', category: 'technologies', url: 'https://developer.apple.com/design/human-interface-guidelines/apple-pay' },
{ id: 'sign-in-with-apple', title: 'Sign in with Apple', platform: 'universal', category: 'technologies', url: 'https://developer.apple.com/design/human-interface-guidelines/sign-in-with-apple' },
{ id: 'siri', title: 'Siri', platform: 'universal', category: 'technologies', url: 'https://developer.apple.com/design/human-interface-guidelines/siri' },
{ id: 'carplay', title: 'CarPlay', platform: 'universal', category: 'technologies', url: 'https://developer.apple.com/design/human-interface-guidelines/carplay' },
{ id: 'augmented-reality', title: 'Augmented Reality', platform: 'universal', category: 'technologies', url: 'https://developer.apple.com/design/human-interface-guidelines/augmented-reality' }
];
return comprehensiveHIGSections.map(section => ({
...section,
platform: section.platform,
category: section.category,
content: this.generateBasicContentForSection(section.title, section.platform, section.category),
lastUpdated: new Date()
}));
}
/**
* Generate basic content for a section (used for static sections)
*/
generateBasicContentForSection(title, platform, _category) {
return `# ${title}
This section is part of Apple's Human Interface Guidelines for ${platform}.
**Note**: This is a placeholder. For complete and up-to-date information, please visit Apple's official documentation.
## Overview
${title} guidelines provide essential design principles for creating great user experiences on ${platform}.
---
**Attribution Notice**
This content is sourced from Apple's Human Interface Guidelines.
Ā© Apple Inc. All rights reserved. For the most up-to-date and official information, please refer to Apple's official documentation.`;
}
/**
* Get the file path for a topic based on topic-first organization
*/
getTopicFilePath(section, filename) {
// Universal topics go at root level
if (section.platform === 'universal' || this.isUniversalTopic(section)) {
return path.join(this.config.outputDirectory, filename);
}
// Platform-specific topics go in platforms/{platform}/
return path.join(this.config.outputDirectory, 'platforms', section.platform.toLowerCase(), filename);
}
/**
* Determine if a topic should be treated as universal
*/
isUniversalTopic(section) {
// Topics that should be universal based on their nature
const universalTopics = new Set([
'accessibility', 'privacy', 'inclusion', 'branding',
'layout', 'spatial-layout', 'typography', 'color', 'icons', 'images', 'motion',
'inputs', 'gestures', 'feedback', 'loading', 'onboarding', 'launching',
'navigation-and-search', 'searching', 'modality',
'alerts', 'action-sheets', 'activity-views', 'sheets', 'popovers',
'buttons', 'menus', 'toolbars', 'tab-bars', 'navigation-bars', 'sliders',
'steppers', 'toggles', 'pickers', 'progress-indicators', 'labels',
'text-fields', 'text-views', 'lists-and-tables', 'collections', 'scroll-views',
'split-views', 'boxes', 'gauges', 'charts', 'rating-indicators', 'materials',
'app-clips', 'app-shortcuts', 'apple-pay', 'carplay', 'healthkit', 'homekit',
'icloud', 'in-app-purchase', 'machine-learning', 'maps', 'nfc', 'siri',
'wallet', 'augmented-reality', 'game-center', 'live-activities',
'live-photos', 'notifications', 'shareplay', 'sign-in-with-apple',
'tap-to-pay-on-iphone', 'widgets'
]);
// Extract topic name from section ID or title
const topicName = section.id.replace(/-\w+$/, ''); // Remove platform suffix
return universalTopics.has(topicName) || section.platform === 'universal';
}
/**
* Create necessary directory structure (topic-first organization)
*/
async createDirectoryStructure() {
console.log('š Creating topic-first directory structure...');
const directories = [
'platforms/ios',
'platforms/macos',
'platforms/watchos',
'platforms/tvos',
'platforms/visionos',
// Note: No platforms/universal - universal topics go at root
'metadata'
];
// Create root output directory
await this.fileSystem.mkdir(this.config.outputDirectory, { recursive: true });
// Create platform-specific subdirectories
for (const dir of directories) {
await this.fileSystem.mkdir(path.join(this.config.outputDirectory, dir), { recursive: true });
}
}
/**
* Generate content for all sections
*/
async generateContent() {
console.log('š Generating section content with high-quality processing...');
console.log(' ā” Processing Apple HIG content for AI assistant use');
// Process in batches to avoid overwhelming the system
const batches = this.chunkArray(this.sections, this.config.batchSize);
for (let i = 0; i < batches.length; i++) {
const batch = batches[i];
console.log(`š Processing batch ${i + 1}/${batches.length} (${batch.length} sections)`);
// Process batch in parallel
await Promise.all(batch.map(section => this.processSection(section)));
// Rate limiting between batches
if (i < batches.length - 1) {
await this.delay(this.config.rateLimitDelay);
}
}
}
/**
* Process a single section with enhanced quality monitoring
*/
async processSection(section) {
try {
// Fetch content using Crawlee service
const sectionWithContent = await this.crawleeService.fetchSectionContent(section);
if (!sectionWithContent.content) {
console.warn(`ā ļø No content for section: ${section.title}`);
this.extractionStats.fallbackUsage++;
return;
}
// Use our enhanced content processor to process HTML content
const processingResult = await this.contentProcessor.processContent(sectionWithContent.content, sectionWithContent.url);
// Validate content quality
const validationResult = await this.qualityValidator.validateContent(processingResult.cleanedMarkdown, { ...sectionWithContent, quality: processingResult.quality });
// Record extraction for monitoring
this.qualityValidator.recordExtraction(sectionWithContent, processingResult.quality);
// Update extraction statistics
this.updateExtractionStats(processingResult.quality);
// Log quality validation results
if (!validationResult.isValid && validationResult.issues.length > 0) {
console.warn(`ā ļø Quality issues for ${section.title}:`, validationResult.issues);
if (validationResult.recommendations.length > 0) {
console.log(`š” Recommendations:`, validationResult.recommendations);
}
}
// Generate final markdown with structured content and enhanced front matter
const finalContent = this.generateStructuredMarkdown(sectionWithContent, processingResult);
// Write to file using topic-first structure
const filename = this.generateFilename(section);
const filePath = this.getTopicFilePath(section, filename);
await this.fileSystem.writeFile(filePath, finalContent);
// Update section with quality info and structured content
const enrichedSection = {
...sectionWithContent,
quality: processingResult.quality,
extractionMethod: processingResult.quality.extractionMethod,
structuredContent: processingResult.structuredContent,
processingMetrics: processingResult.processingMetrics
};
// Add to indices with enhanced keyword extraction
const keywords = this.contentProcessor.extractKeywords(processingResult.cleanedMarkdown, sectionWithContent);
this.searchIndexer.addSection({ ...enrichedSection, keywords });
this.crossReferenceGenerator.addSection(enrichedSection);
// Log quality information with enhanced metrics
const qualityEmoji = processingResult.quality.score >= 0.8 ? 'š¢' :
processingResult.quality.score >= 0.5 ? 'š”' : 'š“';
const structureEmoji = processingResult.processingMetrics.structureScore >= 0.8 ? 'šļø' : 'š';
// Determine display path for logging
const displayPath = this.isUniversalTopic(section) || section.platform === 'universal'
? filename
: `${section.platform}/${filename}`;
console.log(`${qualityEmoji}${structureEmoji} Generated: ${displayPath} (quality: ${processingResult.quality.score.toFixed(3)}, structure: ${processingResult.processingMetrics.structureScore.toFixed(3)}, method: ${processingResult.quality.extractionMethod})`);
}
catch (error) {
console.error(`ā Failed to process section ${section.title}:`, error);
this.extractionStats.fallbackUsage++;
}
}
/**
* Update extraction statistics
*/
updateExtractionStats(quality) {
this.extractionStats.successfulExtractions++;
if (quality.isFallbackContent || quality.extractionMethod === 'fallback') {
this.extractionStats.fallbackUsage++;
}
// Update running averages
const currentCount = this.extractionStats.successfulExtractions;
this.extractionStats.averageQuality =
((this.extractionStats.averageQuality * (currentCount - 1)) + quality.score) / currentCount;
this.extractionStats.averageConfidence =
((this.extractionStats.averageConfidence * (currentCount - 1)) + quality.confidence) / currentCount;
// Calculate success rate
this.extractionStats.extractionSuccessRate =
((this.extractionStats.successfulExtractions - this.extractionStats.fallbackUsage) /
this.extractionStats.successfulExtractions) * 100;
}
/**
* Generate structured markdown with enhanced front matter and organized content
*/
generateStructuredMarkdown(section, processingResult) {
// Enhanced front matter with quality metrics and structured data
const frontMatter = [
'---',
`title: "${section.title}"`,
`platform: ${section.platform}`,
`category: ${section.category}`,
`url: ${section.url}`,
`id: ${section.id}`,
`lastUpdated: ${section.lastUpdated?.toISOString() || new Date().toISOString()}`,
`extractionMethod: ${processingResult.quality.extractionMethod}`,
`qualityScore: ${processingResult.quality.score.toFixed(3)}`,
`confidence: ${processingResult.quality.confidence.toFixed(3)}`,
`contentLength: ${processingResult.quality.length}`,
`structureScore: ${processingResult.processingMetrics.structureScore.toFixed(3)}`,
`cleaningScore: ${processingResult.processingMetrics.cleaningScore.toFixed(3)}`,
`hasCodeExamples: ${processingResult.quality.codeExamplesCount > 0}`,
`hasImages: false`, // We remove images per requirements
`keywords: [${this.contentProcessor.extractKeywords(processingResult.cleanedMarkdown, section).slice(0, 10).map((k) => `"${k}"`).join(', ')}]`,
'---',
''
].join('\n');
// Structured content organization
let structuredContent = '';
// Add overview section
if (processingResult.structuredContent.overview && processingResult.structuredContent.overview !== 'No overview available') {
structuredContent += `## Overview\n\n${processingResult.structuredContent.overview}\n\n`;
}
// Add guidelines section
if (processingResult.structuredContent.guidelines.length > 0 &&
processingResult.structuredContent.guidelines[0] !== 'No specific guidelines identified') {
structuredContent += `## Guidelines\n\n`;
processingResult.structuredContent.guidelines.forEach((guideline) => {
structuredContent += `- ${guideline}\n`;
});
structuredContent += '\n';
}
// Add examples section
if (processingResult.structuredContent.examples.length > 0 &&
processingResult.structuredContent.examples[0] !== 'No examples provided') {
structuredContent += `## Examples\n\n`;
processingResult.structuredContent.examples.forEach((example) => {
structuredContent += `- ${example}\n`;
});
structuredContent += '\n';
}
// Add specifications section if available
if (processingResult.structuredContent.specifications) {
structuredContent += `## Specifications\n\n`;
const specs = processingResult.structuredContent.specifications;
if (specs.dimensions) {
structuredContent += `### Dimensions\n`;
Object.entries(specs.dimensions).forEach(([key, value]) => {
structuredContent += `- **${key}**: ${value}\n`;
});
structuredContent += '\n';
}
if (specs.spacing) {
structuredContent += `### Spacing\n`;
Object.entries(specs.spacing).forEach(([key, value]) => {
structuredContent += `- **${key}**: ${value}\n`;
});
structuredContent += '\n';
}
}
// Add related concepts section
if (processingResult.structuredContent.relatedConcepts.length > 0) {
structuredContent += `## Related Concepts\n\n`;
structuredContent += processingResult.structuredContent.relatedConcepts.map((concept) => `- ${concept}`).join('\n') + '\n\n';
}
// Fallback to cleaned markdown if structured content is minimal
let finalContent = structuredContent;
if (structuredContent.trim().length < 200) {
finalContent = `## Content\n\n${processingResult.cleanedMarkdown}\n\n`;
}
// Enhanced attribution with quality notice
const qualityNotice = processingResult.quality.score >= 0.8 ?
'This content was successfully extracted and structured from Apple\'s official documentation.' :
processingResult.quality.score >= 0.5 ?
'This content was extracted with good confidence. Structure and guidelines have been enhanced for better usability.' :
'This content was extracted with moderate confidence. Please verify important details with the official Apple documentation.';
const attribution = [
'---',
'',
'**Attribution Notice**',
'',
`This content is sourced from Apple's Human Interface Guidelines: ${section.url}`,
'',
qualityNotice,
'',
'Ā© Apple Inc. All rights reserved. This content is provided for educational and development purposes under fair use. This MCP server is not affiliated with Apple Inc. and does not claim ownership of Apple\'s content.',
'',
'For the most up-to-date and official information, please refer to Apple\'s official documentation.',
''
].join('\n');
return frontMatter + finalContent + attribution;
}
/**
* Generate enhanced markdown with quality metadata (legacy method)
*/
generateEnhancedMarkdown(section, content, processedContent) {
// Enhanced front matter with quality metrics
const frontMatter = [
'---',
`title: "${section.title}"`,
`platform: ${section.platform}`,
`category: ${section.category}`,
`url: ${section.url}`,
`id: ${section.id}`,
`lastUpdated: ${section.lastUpdated?.toISOString() || new Date().toISOString()}`,
`extractionMethod: ${processedContent.quality.extractionMethod}`,
`qualityScore: ${processedContent.quality.score.toFixed(3)}`,
`confidence: ${processedContent.quality.confidence.toFixed(3)}`,
`contentLength: ${processedContent.quality.length}`,
`hasCodeExamples: ${processedContent.quality.codeExamplesCount > 0}`,
`hasImages: ${processedContent.quality.imageReferencesCount > 0}`,
`keywords: [${processedContent.keywords.slice(0, 10).map((k) => `"${k}"`).join(', ')}]`,
'---',
''
].join('\n');
// Enhanced table of contents (if available)
let tableOfContents = '';
if (processedContent.tableOfContents) {
tableOfContents = processedContent.tableOfContents + '\n';
}
// Content summary (if available and high quality)
let summary = '';
if (processedContent.summary && processedContent.quality.score > 0.5) {
summary = `## Summary\n\n${processedContent.summary}\n\n`;
}
// Enhanced attribution with quality notice
const qualityNotice = processedContent.quality.score >= 0.8 ?
'This content was successfully extracted from Apple\'s official documentation.' :
processedContent.quality.isFallbackContent ?
'ā ļø This content uses fallback information. For the most accurate and complete information, please visit the official Apple documentation.' :
'This content was extracted with moderate confidence. Please verify important details with the official Apple documentation.';
const attribution = [
'',
'---',
'',
'**Attribution Notice**',
'',
`This content is sourced from Apple's Human Interface Guidelines: ${section.url}`,
'',
qualityNotice,
'',
'Ā© Apple Inc. All rights reserved. This content is provided for educational and development purposes under fair use. This MCP server is not affiliated with Apple Inc. and does not claim ownership of Apple\'s content.',
'',
'For the most up-to-date and official information, please refer to Apple\'s official documentation.',
''
].join('\n');
return frontMatter + tableOfContents + summary + content + attribution;
}
/**
* Generate final markdown with front matter and attribution (legacy method)
*/
generateFinalMarkdown(section, content) {
// Front matter
const frontMatter = [
'---',
`title: "${section.title}"`,
`platform: ${section.platform}`,
`category: ${section.category}`,
`url: ${section.url}`,
`id: ${section.id}`,
`lastUpdated: ${section.lastUpdated?.toISOString() || new Date().toISOString()}`,
'---',
''
].join('\n');
// Table of contents
const toc = this.generateTableOfContents(content);
// Attribution
const attribution = [
'',
'---',
'',
'**Attribution Notice**',
'',
`This content is sourced from Apple's Human Interface Guidelines: ${section.url}`,
'',
'Ā© Apple Inc. All rights reserved. This content is provided for educational and development purposes under fair use. This MCP server is not affiliated with Apple Inc. and does not claim ownership of Apple\'s content.',
'',
'For the most up-to-date and official information, please refer to Apple\'s official documentation.',
''
].join('\n');
return frontMatter + toc + content + attribution;
}
/**
* Generate table of contents for content
*/
generateTableOfContents(content) {
const headings = content.match(/^#+\s+.+$/gm);
if (!headings || headings.length < 3) {
return ''; // Don't add TOC for short content
}
const toc = ['## Table of Contents', ''];
headings.forEach(heading => {
const level = heading.match(/^#+/)?.[0].length || 1;
const text = heading.replace(/^#+\s+/, '');
const anchor = text.toLowerCase().replace(/[^a-z0-9\s]/g, '').replace(/\s+/g, '-');
const indent = ' '.repeat(Math.max(0, level - 2));
toc.push(`${indent}- [${text}](#${anchor})`);
});
toc.push('', '');
return toc.join('\n');
}
/**
* Generate metadata files with extraction statistics
*/
async generateMetadata() {
console.log('š Generating metadata...');
// Generate search index
const searchIndex = this.searchIndexer.generateIndex();
await this.fileSystem.writeFile(path.join(this.config.outputDirectory, 'metadata', 'search-index.json'), JSON.stringify(searchIndex, null, 2));
console.log(`ā Search index generated with ${Object.keys(searchIndex).length} entries`);
// Generate cross-references
const crossReferences = this.crossReferenceGenerator.generateReferences();
await this.fileSystem.writeFile(path.join(this.config.outputDirectory, 'metadata', 'cross-references.json'), JSON.stringify(crossReferences, null, 2));
console.log('ā Cross-references generated');
// Generate enhanced generation info with extraction statistics
const totalSize = await this.fileSystem.calculateDirectorySize(this.config.outputDirectory);
const enhancedMetadata = {
lastUpdated: new Date().toISOString(),
totalSections: this.sections.length,
sectionsByPlatform: this.groupByPlatform(),
sectionsByCategory: this.groupByCategory(),
totalSize,
generationDuration: Date.now() - this.startTime,
extractionStatistics: this.extractionStats,
qualityMetrics: {
fallbackUsageRate: (this.extractionStats.fallbackUsage / this.extractionStats.totalSections) * 100,
slaCompliance: this.extractionStats.extractionSuccessRate >= 95,
averageQuality: this.extractionStats.averageQuality,
averageConfidence: this.extractionStats.averageConfidence
},
generatedWith: 'Content-Generator-v2.0'
};
await this.fileSystem.writeFile(path.join(this.config.outputDirectory, 'metadata', 'generation-info.json'), JSON.stringify(enhancedMetadata, null, 2));
// Generate extraction statistics file
await this.fileSystem.writeFile(path.join(this.config.outputDirectory, 'metadata', 'extraction-statistics.json'), JSON.stringify(this.extractionStats, null, 2));
console.log('ā Enhanced metadata generated');
console.log(` Total size: ${(totalSize / 1024 / 1024).toFixed(2)} MB`);
console.log(` Extraction success rate: ${this.extractionStats.extractionSuccessRate.toFixed(1)}%`);
}
/**
* Log extraction results and SLA compliance
*/
logExtractionResults() {
const fallbackRate = (this.extractionStats.fallbackUsage / this.extractionStats.totalSections) * 100;
const slaCompliant = this.extractionStats.extractionSuccessRate >= 95;
console.log('\nšÆ Extraction Results Summary:');
console.log('================================');
console.log(`š Total sections: ${this.extractionStats.totalSections}`);
console.log(`ā
Successful extractions: ${this.extractionStats.successfulExtractions}`);
console.log(`š Fallback content used: ${this.extractionStats.fallbackUsage} (${fallbackRate.toFixed(1)}%)`);
console.log(`šØ Real content rate: ${this.extractionStats.extractionSuccessRate.toFixed(1)}%`);
console.log(`ā Average quality score: ${this.extractionStats.averageQuality.toFixed(3)}`);
console.log(`š Average confidence: ${this.extractionStats.averageConfidence.toFixed(3)}`);
console.log(`\nšÆ SLA Target: ā„95% real content extraction`);
if (slaCompliant) {
console.log('ā
SLA ACHIEVED! Extraction rate exceeds 95% target.');
}
else {
console.log(`ā SLA NOT MET. Current rate: ${this.extractionStats.extractionSuccessRate.toFixed(1)}% (target: ā„95%)`);
console.log('š§ Consider reviewing extraction patterns or Apple website changes.');
}
console.log('\nš Content Generation Summary:');
console.log(` ⢠${this.extractionStats.totalSections} sections processed successfully`);
console.log(` ⢠${((this.extractionStats.successfulExtractions - this.extractionStats.fallbackUsage) / this.extractionStats.totalSections * 100).toFixed(1)}% high-quality Apple content extracted`);
console.log(` ⢠Quality monitoring and validation enabled`);
console.log(' ⢠Content optimized for MCP and AI assistant use');
}
/**
* Log clean quality validation summary
*/
logQualityReport() {
const report = this.qualityValidator.generateReport();
console.log(report);
}
// Utility methods
generateFilename(section) {
const cleanTitle = section.title
.replace(/^(iOS|macOS|watchOS|tvOS|visionOS)\s+/i, '')
.toLowerCase()
.replace(/[^a-z0-9\s-]/g, '')
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '');
return `${cleanTitle}.md`;
}
chunkArray(array, chunkSize) {
const chunks = [];
for (let i = 0; i < array.length; i += chunkSize) {
chunks.push(array.slice(i, i + chunkSize));
}
return chunks;
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
groupByPlatform() {
return this.sections.reduce((acc, section) => {
acc[section.platform] = (acc[section.platform] || 0) + 1;
return acc;
}, {});
}
groupByCategory() {
return this.sections.reduce((acc, section) => {
acc[section.category] = (acc[section.category] || 0) + 1;
return acc;
}, {});
}
// High-quality fallback content methods
getHighQualityButtonContent(platform = 'iOS') {
return `# Buttons
Buttons initiate app-specific actions, have customizable backgrounds, and can include a title or an icon. The system provides several button styles, each with specific best practices.
## Overview
Buttons are interactive elements that trigger actions when tapped. In ${platform}, buttons should be immediately recognizable as interactive elements and provide clear feedback when pressed.
## Guidelines
- Use clear, concise button text that describes the action
- Make buttons large enough for easy tapping (minimum 44pt touch target)
- Choose appropriate button styles for the context
- Provide visual feedback when buttons are pressed
- Consider button placement and grouping for optimal user flow
## Button Types
### Primary Buttons
Use for the most important action in a view. Limit to one primary button per screen.
### Secondary Buttons
Use for secondary actions. These can appear alongside primary buttons.
### Destructive Buttons
Use for actions that delete or remove content. Use red styling to indicate destructive nature.
## Specifications
- Minimum touch target: 44pt Ć 44pt
- Recommended padding: 8pt-16pt horizontal, 6pt-12pt vertical
- Corner radius: Varies by platform design language
- Text should be legible with sufficient contrast
## Best Practices
- Test buttons with various text lengths and localizations
- Ensure buttons work well in both light and dark modes
- Consider accessibility requirements for color contrast and VoiceOver support
- Group related buttons logically
- Use consistent button styling throughout your app`;
}
getHighQualityNavigationContent() {
return `# Navigation Bars
A navigation bar appears at the top of an app screen, enabling navigation through a hierarchy of content. Navigation bars are translucent by default and can be configured to hide or show when people scroll.
## Overview
Navigation bars provide hierarchical navigation and display the current location within your app's information architecture. They help users understand where they are and how to get back to previous screens.
## Guidelines
- Show the current location clearly with a descriptive title
- Provide clear navigation paths with back buttons
- Use navigation bars consistently throughout your app
- Consider the content behind translucent navigation bars
- Keep navigation bar content relevant to the current screen
## Elements
### Title
Display a descriptive title that represents the current screen or section.
### Back Button
Automatically provided by the system, shows the title of the previous screen.
### Navigation Controls
Additional buttons for actions relevant to the current screen.
## Specifications
- Standard height: 44pt (compact) or 96pt (large title)
- Title should be centered and legible
- Back button appears on the leading edge
- Additional controls on the trailing edge
## Best Practices
- Use large titles when appropriate to create visual hierarchy
- Ensure navigation bar content is accessible
- Test with different device orientations
- Consider how the navigation bar behaves during scrolling
- Maintain consistency with platform conventions`;
}
getHighQualityTabBarContent() {
return `# Tab Bars
A tab bar appears at the bottom of an app screen and lets people quickly switch among different sections of an app. Tab bars are translucent by default.
## Overview
Tab bars provide persistent access to top-level navigation in your app. They help users understand your app's structure and make it easy to move between primary content areas.
## Guidelines
- Use tab bars for top-level navigation only
- Limit to 5 tabs maximum for optimal usability
- Use clear, recognizable icons and labels
- Maintain tab order consistency
- Ensure the selected tab is clearly indicated
## Tab Configuration
### Icon Requirements
- Use simple, recognizable symbols
- Ensure icons work at small sizes
- Provide both selected and unselected states
- Consider using SF Symbols for consistency
### Labels
- Keep labels short and descriptive
- Use title case for tab labels
- Ensure labels are localized appropriately
## Specifications
- Standard height: 49pt (83pt with safe area)
- Maximum 5 tabs (more items move to "More" tab)
- Icons should be approximately 30Ć30pt
- Text should be legible and accessible
## Best Practices
- Start with the most important tab selected
- Use badges sparingly for notifications
- Ensure tab content loads quickly
- Test with longer localized text
- Consider accessibility for users with visual impairments`;
}
getHighQualityAlertContent() {
return `# Alerts
An al