UNPKG

context-crystallizer

Version:

AI Crystallization Engineering for Large Repositories - Transform massive repositories into crystallized, AI-consumable knowledge through systematic analysis and optimization. Crystallization extracts meaningful context from any readable files.

453 lines 19.5 kB
import { promises as fs } from 'fs'; import path from 'path'; export class ContextValidator { repoPath; contextBasePath; constructor(repoPath) { this.repoPath = path.resolve(repoPath); this.contextBasePath = path.join(this.repoPath, '.context-crystallizer'); } async validateContext(context) { const issues = []; const metrics = { completeness: 0, specificity: 0, aiReadability: 0, tokenEfficiency: 0, crossReferenceQuality: 0, }; // Check completeness metrics.completeness = this.assessCompleteness(context, issues); // Check specificity and usefulness metrics.specificity = this.assessSpecificity(context, issues); // Check AI readability metrics.aiReadability = this.assessAIReadability(context, issues); // Check token efficiency metrics.tokenEfficiency = this.assessTokenEfficiency(context, issues); // Check cross-reference quality metrics.crossReferenceQuality = this.assessCrossReferences(context, issues); // Calculate overall score const score = Math.round((metrics.completeness * 0.25) + (metrics.specificity * 0.25) + (metrics.aiReadability * 0.25) + (metrics.tokenEfficiency * 0.15) + (metrics.crossReferenceQuality * 0.10)); // Generate suggestions const suggestions = this.generateSuggestions(context, metrics, issues); return { isValid: issues.filter(i => i.severity === 'error').length === 0, score, issues, suggestions, metrics, }; } async generateProjectQualityReport() { const metadataDir = path.join(this.contextBasePath, 'ai-metadata'); try { const metadataFiles = await fs.readdir(metadataDir); const results = []; const categoryScores = {}; const issueFrequency = {}; for (const file of metadataFiles) { if (!file.endsWith('.json')) continue; const metadataPath = path.join(metadataDir, file); const metadata = JSON.parse(await fs.readFile(metadataPath, 'utf-8')); // Load full context for validation const context = await this.loadContext(metadata.relativePath); if (context) { const result = await this.validateContext(context); results.push(result); // Track category scores if (!categoryScores[context.category]) { categoryScores[context.category] = []; } categoryScores[context.category].push(result.score); // Track issue frequency result.issues.forEach(issue => { const key = `${issue.category}: ${issue.message}`; issueFrequency[key] = (issueFrequency[key] || 0) + 1; }); } } // Calculate averages const overallScore = results.length > 0 ? Math.round(results.reduce((sum, r) => sum + r.score, 0) / results.length) : 0; const categoryAvgs = {}; Object.entries(categoryScores).forEach(([category, scores]) => { categoryAvgs[category] = Math.round(scores.reduce((a, b) => a + b, 0) / scores.length); }); // Find common issues const commonIssues = Object.entries(issueFrequency) .map(([issue, count]) => ({ issue, count })) .sort((a, b) => b.count - a.count) .slice(0, 5); // Calculate metrics const avgTokens = results.length > 0 ? results.reduce((sum, r) => sum + (r.metrics.tokenEfficiency * 20), 0) / results.length // Rough estimate : 0; const tokenEfficiencyRating = this.getTokenEfficiencyRating(avgTokens); const crossReferenceRatio = results.length > 0 ? results.reduce((sum, r) => sum + r.metrics.crossReferenceQuality, 0) / results.length / 100 : 0; // Generate recommendations const recommendations = this.generateProjectRecommendations(results, commonIssues, overallScore); return { overallScore, totalContexts: results.length, categoryScores: categoryAvgs, commonIssues, recommendations, metrics: { avgTokensPerContext: Math.round(avgTokens), tokenEfficiencyRating, coverageCompleteness: this.calculateCoverageCompleteness(results), crossReferenceRatio: Math.round(crossReferenceRatio * 100) / 100, }, }; } catch (_error) { console.error('Error generating quality report:', _error); return { overallScore: 0, totalContexts: 0, categoryScores: {}, commonIssues: [], recommendations: ['Unable to generate report - check context storage'], metrics: { avgTokensPerContext: 0, tokenEfficiencyRating: 'poor', coverageCompleteness: 0, crossReferenceRatio: 0, }, }; } } assessCompleteness(context, issues) { let score = 100; if (!context.purpose || context.purpose.length < 20) { issues.push({ severity: 'error', category: 'completeness', message: 'Purpose is missing or too brief (minimum 20 characters)', field: 'purpose', }); score -= 30; } if (context.keyTerms.length === 0) { issues.push({ severity: 'warning', category: 'completeness', message: 'No key terms defined - consider adding main concepts/entities', field: 'keyTerms', }); score -= 20; } if (context.template === 'extended') { if (!context.aiGuidance) { issues.push({ severity: 'warning', category: 'completeness', message: 'Extended template should include AI guidance', field: 'aiGuidance', }); score -= 15; } if (context.patterns.length === 0) { issues.push({ severity: 'info', category: 'completeness', message: 'Consider adding implementation patterns for AI guidance', field: 'patterns', }); score -= 10; } } return Math.max(0, score); } assessSpecificity(context, issues) { let score = 100; // Check for generic/vague descriptions const genericTerms = ['handles', 'manages', 'deals with', 'contains', 'provides']; const purposeLower = context.purpose.toLowerCase(); if (genericTerms.some(term => purposeLower.includes(term)) && context.purpose.length < 100) { issues.push({ severity: 'warning', category: 'usefulness', message: 'Purpose uses generic terms - be more specific about functionality', field: 'purpose', }); score -= 20; } // Check term specificity const vagueTerms = context.keyTerms.filter(term => term.length < 3 || /^(function|method|class|item|thing)\s*$/i.test(term)); if (vagueTerms.length > 0) { issues.push({ severity: 'warning', category: 'usefulness', message: `${vagueTerms.length} key terms are too vague`, field: 'keyTerms', }); score -= 15; } return Math.max(0, score); } assessAIReadability(context, issues) { let score = 100; // Check sentence structure const sentences = context.purpose.split(/[.!?]+/).filter(s => s.trim().length > 0); if (sentences.length === 1 && context.purpose.length > 200) { issues.push({ severity: 'info', category: 'format', message: 'Purpose is one long sentence - consider breaking into multiple sentences', field: 'purpose', }); score -= 10; } // Check for code in purpose (should be in separate sections) if (/```|`\w+`/.test(context.purpose)) { issues.push({ severity: 'info', category: 'format', message: 'Code found in purpose - consider moving to patterns or examples', field: 'purpose', }); score -= 5; } // Check term formatting const poorlyFormattedTerms = context.keyTerms.filter(term => term.length > 30 || /^\s|\s$/.test(term)); if (poorlyFormattedTerms.length > 0) { issues.push({ severity: 'info', category: 'format', message: 'Some key terms are too long or have formatting issues', field: 'keyTerms', }); score -= 10; } return Math.max(0, score); } assessTokenEfficiency(context, issues) { let score = 100; if (!context.tokenCount) return 50; // Unknown efficiency const template = context.template; const maxTokens = template === 'short' ? 200 : 2000; const efficiency = (context.tokenCount / maxTokens) * 100; if (efficiency > 120) { issues.push({ severity: 'warning', category: 'format', message: `Context exceeds template limit (${context.tokenCount}/${maxTokens} tokens)`, }); score -= 30; } else if (efficiency < 30) { issues.push({ severity: 'info', category: 'usefulness', message: 'Context is very brief - could include more detail', }); score -= 10; } // Optimal range is 60-90% of template limit if (efficiency >= 60 && efficiency <= 90) { score = 100; } else if (efficiency >= 40 && efficiency <= 110) { score = 85; } else { score = Math.max(40, 100 - Math.abs(efficiency - 75)); } return score; } assessCrossReferences(context, issues) { let score = 100; if (context.crossReferences.length === 0 && context.category === 'source') { issues.push({ severity: 'info', category: 'usefulness', message: 'No cross-references found - file may be isolated or analysis incomplete', }); score -= 20; } // Check for broken or unclear references const vagueReferences = context.crossReferences.filter(ref => !ref.description || ref.description.length < 5); if (vagueReferences.length > 0) { issues.push({ severity: 'info', category: 'usefulness', message: `${vagueReferences.length} cross-references lack descriptions`, }); score -= 10; } return Math.max(0, score); } generateSuggestions(context, metrics, _issues) { const suggestions = []; if (metrics.completeness < 80) { suggestions.push('Add more detail to the purpose section explaining the specific functionality'); if (context.keyTerms.length < 3 && context.category === 'source') { suggestions.push('Include more key terms, concepts, or important elements'); } } if (metrics.specificity < 70) { suggestions.push('Replace generic terms with specific technical details'); suggestions.push('Include more specific details, types, or usage examples for key terms'); } if (metrics.tokenEfficiency < 60) { if (context.tokenCount && context.tokenCount < 50) { suggestions.push('Consider adding implementation patterns or error handling details'); } else { suggestions.push('Optimize content to fit within template token limits'); } } if (metrics.crossReferenceQuality < 50 && context.category === 'source') { suggestions.push('Add cross-references to related files and dependencies'); } // Specific suggestions based on category if (context.category === 'config' && !context.patterns.length) { suggestions.push('Document configuration patterns and required/optional settings'); } if (context.category === 'test' && !context.integrationPoints?.length) { suggestions.push('Describe what functionality is being tested and test patterns'); } return suggestions; } generateProjectRecommendations(results, commonIssues, overallScore) { const recommendations = []; if (overallScore < 70) { recommendations.push('Focus on improving context completeness and specificity across the project'); } if (commonIssues.length > 0) { const topIssue = commonIssues[0]; if (topIssue.count > results.length * 0.3) { recommendations.push(`Address common issue: ${topIssue.issue} (affects ${topIssue.count} contexts)`); } } const avgCompleteness = results.reduce((sum, r) => sum + r.metrics.completeness, 0) / results.length; if (avgCompleteness < 75) { recommendations.push('Improve context completeness by adding more detailed purposes and key term descriptions'); } const avgEfficiency = results.reduce((sum, r) => sum + r.metrics.tokenEfficiency, 0) / results.length; if (avgEfficiency < 60) { recommendations.push('Optimize token usage - some contexts are too brief or too verbose'); } const highComplexityContexts = results.filter(r => r.metrics.specificity < 60 && r.score < 70).length; if (highComplexityContexts > results.length * 0.2) { recommendations.push('Review contexts for complex files to ensure they provide clear AI guidance'); } return recommendations; } getTokenEfficiencyRating(avgTokens) { if (avgTokens >= 80 && avgTokens <= 150) return 'excellent'; if (avgTokens >= 60 && avgTokens <= 200) return 'good'; if (avgTokens >= 40 && avgTokens <= 300) return 'fair'; return 'poor'; } calculateCoverageCompleteness(results) { const completenessScores = results.map(r => r.metrics.completeness); const avgCompleteness = completenessScores.reduce((a, b) => a + b, 0) / completenessScores.length; return Math.round(avgCompleteness); } async loadContext(relativePath) { try { const contextPath = path.join(this.contextBasePath, 'context', `${relativePath}.context.md`); const markdown = await fs.readFile(contextPath, 'utf-8'); // Use the same parser as in context-search.ts return this.parseContextFromMarkdown(markdown, relativePath); } catch (_error) { return null; } } parseContextFromMarkdown(markdown, relativePath) { // Extract metadata from comments const templateMatch = markdown.match(/<!-- Template: (\w+) -->/); const categoryMatch = markdown.match(/<!-- Category: (\w+) -->/); const complexityMatch = markdown.match(/<!-- Complexity: (\w+) -->/); const tokenMatch = markdown.match(/<!-- Tokens: (\d+) -->/); const context = { filePath: relativePath, // Store only relative paths for portability relativePath, purpose: '', keyTerms: [], dependencies: [], patterns: [], relatedContexts: [], lastModified: new Date(), template: templateMatch?.[1] || 'short', complexity: complexityMatch?.[1] || 'medium', category: categoryMatch?.[1] || 'other', crossReferences: [], tokenCount: tokenMatch ? parseInt(tokenMatch[1]) : undefined, }; // Parse sections const sections = markdown.split(/^## /m); sections.forEach(section => { const lines = section.trim().split('\n'); const title = lines[0]?.toLowerCase(); if (title?.includes('purpose')) { context.purpose = lines.slice(1).join('\n').trim(); } else if (title?.includes('key apis')) { context.keyTerms = lines.slice(1) .filter(line => line.startsWith('- ')) .map(line => line.substring(2)); } else if (title?.includes('dependencies')) { context.dependencies = lines.slice(1) .filter(line => line.startsWith('- ')) .map(line => line.substring(2)); } else if (title?.includes('patterns')) { context.patterns = lines.slice(1) .filter(line => line.startsWith('- ')) .map(line => line.substring(2)); } else if (title?.includes('guidance')) { context.aiGuidance = lines.slice(1).join('\n').trim(); } else if (title?.includes('integration')) { context.integrationPoints = lines.slice(1) .filter(line => line.startsWith('- ')) .map(line => line.substring(2)); } else if (title?.includes('cross references')) { context.crossReferences = lines.slice(1) .filter(line => line.startsWith('- ')) .map(line => { const match = line.match(/\*\*(\w+)\*\*:\s*`([^`]+)`(.*)/); if (match) { return { type: match[1], target: match[2], description: match[3]?.replace(/^\s*-\s*/, '').trim(), }; } return null; }) .filter(Boolean); } else if (title?.includes('related')) { context.relatedContexts = lines.slice(1) .filter(line => line.startsWith('- ')) .map(line => { const match = line.match(/\[(.*?)\]/); return match ? match[1] : ''; }) .filter(Boolean); } }); return context; } } //# sourceMappingURL=context-validator.js.map