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.

181 lines 7.82 kB
import { promises as fs } from 'fs'; import path from 'path'; export class ContextUpdater { repoPath; fileScanner; contextStorage; changeDetector; queueManager; constructor(repoPath, fileScanner, contextStorage, changeDetector, queueManager) { this.repoPath = repoPath; this.fileScanner = fileScanner; this.contextStorage = contextStorage; this.changeDetector = changeDetector; this.queueManager = queueManager; } async updateContexts(options = {}) { const result = { summary: { filesScanned: 0, changesDetected: 0, contextsUpdated: 0, contextsAdded: 0, contextsRemoved: 0, errors: 0, }, changes: [], updatedContexts: [], errors: [], }; try { // Scan repository for current files const currentFiles = await this.fileScanner.scanRepository(); result.summary.filesScanned = currentFiles.length; // Detect changes const changeResult = await this.changeDetector.detectChanges(currentFiles); result.changes = changeResult.changes; result.summary.changesDetected = changeResult.stats.totalChanges; // Get files needing context (new files without context) const filesNeedingContext = await this.changeDetector.getFilesNeedingContext(currentFiles); // Prepare update queue const updateQueue = []; // Add new files for (const change of changeResult.changes) { if (change.type === 'added') { const fileItem = currentFiles.find(f => f.path === change.path); if (fileItem) { updateQueue.push(fileItem); } } } // Add modified files if (!options.forceUpdate) { for (const change of changeResult.changes) { if (change.type === 'modified') { const fileItem = currentFiles.find(f => f.path === change.path); if (fileItem) { updateQueue.push(fileItem); } } } } else { // Force update all files updateQueue.push(...currentFiles); } // Add files without context if requested if (options.includeUnchanged) { for (const file of filesNeedingContext) { if (!updateQueue.some(f => f.path === file.path)) { updateQueue.push(file); } } } // Initialize queue for processing await this.queueManager.initializeQueue(updateQueue, this.repoPath, []); // Process queue (this would normally be done by AI agents) // For now, we'll just mark them as ready for processing result.summary.contextsAdded = updateQueue.filter(f => changeResult.changes.some(c => c.path === f.path && c.type === 'added')).length; result.summary.contextsUpdated = updateQueue.filter(f => changeResult.changes.some(c => c.path === f.path && c.type === 'modified')).length; result.updatedContexts = updateQueue.map(f => f.relativePath); // Cleanup deleted files if requested if (options.cleanupDeleted) { const deletedFiles = changeResult.changes .filter(c => c.type === 'deleted') .map(c => c.relativePath); const cleanedCount = await this.changeDetector.cleanupObsoleteContexts(deletedFiles); result.summary.contextsRemoved = cleanedCount; } // Regenerate index after updates // Note: Index will be regenerated when contexts are actually stored } catch (_error) { result.errors.push({ file: 'general', error: _error instanceof Error ? _error.message : 'Unknown error', }); result.summary.errors++; } return result; } async getUpdateStatus() { const changeSummary = await this.changeDetector.getChangesSummary(); const currentFiles = await this.fileScanner.scanRepository(); // Quick check for obvious changes const needsUpdate = changeSummary.totalTrackedFiles !== currentFiles.length || changeSummary.contextCoverage < 90; // Estimate outdated contexts (this is a rough estimate) const estimatedOutdated = Math.max(0, currentFiles.length - changeSummary.filesWithContext); return { needsUpdate, summary: { lastScan: changeSummary.lastScan, totalFiles: currentFiles.length, filesWithContext: changeSummary.filesWithContext, contextCoverage: changeSummary.contextCoverage, estimatedOutdated, }, }; } async validateContextFreshness(relativePath) { try { const fullPath = path.join(this.repoPath, relativePath); const stats = await fs.stat(fullPath); const context = await this.contextStorage.getContext(relativePath); if (!context) { return { isFresh: false, reason: 'No context exists for this file', }; } const contextAge = Date.now() - context.lastModified.getTime(); const fileAge = Date.now() - stats.mtime.getTime(); if (fileAge < contextAge) { return { isFresh: false, reason: 'File has been modified since context was generated', lastModified: stats.mtime, contextAge, }; } return { isFresh: true, lastModified: stats.mtime, contextAge, }; } catch (_error) { return { isFresh: false, reason: 'Error checking file status', }; } } async generateUpdateReport() { const updateStatus = await this.getUpdateStatus(); const changeSummary = await this.changeDetector.getChangesSummary(); const report = [ '# Context Update Report', '', '## Current Status', `- Last scan: ${changeSummary.lastScan?.toISOString() || 'Never'}`, `- Total files: ${updateStatus.summary.totalFiles}`, `- Files with context: ${updateStatus.summary.filesWithContext}`, `- Context coverage: ${updateStatus.summary.contextCoverage}%`, `- Estimated outdated: ${updateStatus.summary.estimatedOutdated}`, '', '## Recommendation', updateStatus.needsUpdate ? '⚠️ **Update recommended** - Context coverage is below 90% or file count has changed' : '✅ **Context is up to date** - No significant changes detected', '', '## Update Options', '- `update_context()` - Update only changed files', '- `update_context(forceUpdate: true)` - Regenerate all contexts', '- `update_context(includeUnchanged: true)` - Include files without context', '- `update_context(cleanupDeleted: true)` - Remove contexts for deleted files', ]; return report.join('\n'); } } //# sourceMappingURL=context-updater.js.map