UNPKG

apple-dev-mcp

Version:

Complete Apple development guidance: Human Interface Guidelines (design) + Technical Documentation for iOS, macOS, watchOS, tvOS, and visionOS

231 lines 9.95 kB
/** * Search Indexer Service * * Generates search indices and metadata for static HIG content */ export class SearchIndexerService { searchIndex = []; crossReferences = []; /** * Generate search index from processed sections */ generateSearchIndex(sections, processedContent) { console.log('📊 Generating search index...'); this.searchIndex = []; for (const section of sections) { const processed = processedContent.get(section.id); if (!processed) continue; const entry = { id: section.id, title: section.title, platform: section.platform, category: section.category, url: section.url, filename: this.generateFilename(section), keywords: processed.keywords || [], snippet: processed.cleanedMarkdown.slice(0, 200) + '...', // Simple preview for quick scanning quality: processed.quality, lastUpdated: section.lastUpdated.toISOString(), hasStructuredContent: this.hasStructuredContent(processed.cleanedMarkdown), hasGuidelines: this.hasGuidelines(processed.cleanedMarkdown), hasExamples: this.hasExamples(processed.cleanedMarkdown), hasSpecifications: this.hasSpecifications(processed.cleanedMarkdown), conceptCount: this.countConcepts(processed.cleanedMarkdown) }; this.searchIndex.push(entry); } console.log(`📋 Generated search index with ${this.searchIndex.length} entries`); return this.searchIndex; } /** * Generate cross-references between sections */ generateCrossReferences(sections, processedContent) { console.log('🔗 Generating cross-references...'); this.crossReferences = []; for (const section of sections) { const processed = processedContent.get(section.id); if (!processed?.relatedSections) continue; for (const relatedTitle of processed.relatedSections) { const relatedSection = sections.find(s => s.title.toLowerCase() === relatedTitle.toLowerCase()); if (relatedSection) { this.crossReferences.push({ fromSection: section.id, toSection: relatedSection.id, relationshipType: 'related', relevanceScore: this.calculateRelevanceScore(section, relatedSection) }); } } } // Add platform-based relationships this.addPlatformRelationships(sections); // Add category-based relationships this.addCategoryRelationships(sections); console.log(`🔗 Generated ${this.crossReferences.length} cross-references`); return this.crossReferences; } /** * Generate metadata about the content generation process */ generateMetadata(sections, processedContent) { const successful = Array.from(processedContent.values()).filter(p => p.quality && !p.quality.isFallbackContent).length; const totalQuality = Array.from(processedContent.values()) .filter(p => p.quality) .reduce((sum, p) => sum + p.quality.score, 0); const avgQuality = totalQuality / processedContent.size; const platforms = [...new Set(sections.map(s => s.platform))]; const categories = [...new Set(sections.map(s => s.category))]; return { generatedAt: new Date().toISOString(), totalSections: sections.length, successfulExtractions: successful, averageQuality: Math.round(avgQuality * 100) / 100, platforms, categories, version: '2.0.0' }; } /** * Search the index for matching entries */ search(query, filters) { const queryLower = query.toLowerCase(); const results = this.searchIndex.filter(entry => { // Text matching const titleMatch = entry.title.toLowerCase().includes(queryLower); const keywordMatch = entry.keywords.some(k => k.includes(queryLower)); const snippetMatch = entry.snippet.toLowerCase().includes(queryLower); if (!titleMatch && !keywordMatch && !snippetMatch) { return false; } // Apply filters if (filters?.platform && entry.platform !== filters.platform) { return false; } if (filters?.category && entry.category !== filters.category) { return false; } if (filters?.minQuality && entry.quality && entry.quality.score < filters.minQuality) { return false; } return true; }); // Sort by relevance results.sort((a, b) => { let scoreA = 0, scoreB = 0; // Title matches get highest score if (a.title.toLowerCase().includes(queryLower)) scoreA += 10; if (b.title.toLowerCase().includes(queryLower)) scoreB += 10; // Keyword matches scoreA += a.keywords.filter(k => k.includes(queryLower)).length * 5; scoreB += b.keywords.filter(k => k.includes(queryLower)).length * 5; // Quality bonus if (a.quality) scoreA += a.quality.score; if (b.quality) scoreB += b.quality.score; return scoreB - scoreA; }); return results; } generateFilename(section) { const slug = section.title.toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, ''); return `${slug}.md`; } hasStructuredContent(content) { return content.includes('##') && (content.includes('###') || content.includes('####')); } hasGuidelines(content) { const guidelineKeywords = ['should', 'must', 'avoid', 'ensure', 'consider', 'guideline', 'best practice']; return guidelineKeywords.some(keyword => content.toLowerCase().includes(keyword)); } hasExamples(content) { return content.includes('```') || content.toLowerCase().includes('example'); } hasSpecifications(content) { const specKeywords = ['size', 'dimension', 'pixel', 'point', 'pt', 'px', 'minimum', 'maximum']; return specKeywords.some(keyword => content.toLowerCase().includes(keyword)); } countConcepts(content) { // Count major headings as concepts const headings = content.match(/^#+\s/gm) || []; return Math.max(headings.length, 1); } calculateRelevanceScore(sectionA, sectionB) { let score = 0; // Same platform bonus if (sectionA.platform === sectionB.platform) score += 0.3; // Same category bonus if (sectionA.category === sectionB.category) score += 0.5; // Title similarity (basic) const wordsA = sectionA.title.toLowerCase().split(' '); const wordsB = sectionB.title.toLowerCase().split(' '); const commonWords = wordsA.filter(word => wordsB.includes(word)).length; score += (commonWords / Math.max(wordsA.length, wordsB.length)) * 0.2; return Math.min(1.0, score); } addPlatformRelationships(sections) { // Link universal content to platform-specific versions const universalSections = sections.filter(s => s.platform === 'universal'); const platformSections = sections.filter(s => s.platform !== 'universal'); for (const universal of universalSections) { for (const platform of platformSections) { if (this.areTopicallySimilar(universal, platform)) { this.crossReferences.push({ fromSection: universal.id, toSection: platform.id, relationshipType: 'related', relevanceScore: 0.6 }); } } } } addCategoryRelationships(sections) { // Group by category and create relationships const categoryGroups = new Map(); for (const section of sections) { if (!categoryGroups.has(section.category)) { categoryGroups.set(section.category, []); } const categoryList = categoryGroups.get(section.category); if (categoryList) { categoryList.push(section); } } // Add relationships within categories for (const [, sectionList] of categoryGroups) { for (let i = 0; i < sectionList.length; i++) { for (let j = i + 1; j < sectionList.length; j++) { const score = this.calculateRelevanceScore(sectionList[i], sectionList[j]); if (score > 0.3) { this.crossReferences.push({ fromSection: sectionList[i].id, toSection: sectionList[j].id, relationshipType: 'related', relevanceScore: score }); } } } } } areTopicallySimilar(sectionA, sectionB) { const titleA = sectionA.title.toLowerCase(); const titleB = sectionB.title.toLowerCase(); // Check if they share significant words const wordsA = titleA.split(' ').filter(w => w.length > 3); const wordsB = titleB.split(' ').filter(w => w.length > 3); return wordsA.some(word => wordsB.includes(word)); } } //# sourceMappingURL=search-indexer.service.js.map