UNPKG

vibe-coder-mcp

Version:

Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.

153 lines (152 loc) 5.44 kB
import crypto from 'crypto'; import path from 'path'; import logger from '../../../logger.js'; import { readFileSecure, statSecure } from '../fsUtils.js'; import { FileCache } from './fileCache.js'; import { LRUCache } from './lruCache.js'; export class FileContentManager { fileMetadataCache = new Map(); contentCache; fileCache = null; initialized = false; constructor(options = {}) { const maxEntries = options.maxCachedFiles !== undefined ? options.maxCachedFiles : 100; this.contentCache = new LRUCache({ name: 'file-content-cache', maxEntries: maxEntries, maxAge: options.maxAge || 5 * 60 * 1000, sizeCalculator: (content) => content.length, maxSize: maxEntries > 0 ? 50 * 1024 * 1024 : 0, }); if (options.cacheDir && options.useFileCache !== false) { this.fileCache = new FileCache({ name: 'file-metadata', cacheDir: path.join(options.cacheDir, 'file-metadata'), maxEntries: 100000, maxAge: 30 * 24 * 60 * 60 * 1000, }); } logger.info(`FileContentManager created (in-memory caching ${maxEntries > 0 ? 'enabled' : 'disabled'})`); } async init() { if (this.initialized) { return; } if (this.fileCache) { try { await this.fileCache.init(); logger.info('File metadata cache initialized'); } catch (error) { logger.warn({ err: error }, 'Failed to initialize file metadata cache, continuing without it'); this.fileCache = null; } } this.initialized = true; } async getContent(filePath, allowedDir) { if (!this.initialized) { await this.init(); } const cacheKey = this.getCacheKey(filePath); if (this.contentCache.getMaxEntries() > 0) { if (this.contentCache.has(cacheKey)) { const content = this.contentCache.get(cacheKey); if (content !== undefined) { logger.debug(`Using in-memory cached content for ${filePath}`); return content; } } } try { const content = await readFileSecure(filePath, allowedDir); await this.updateMetadata(filePath, content, allowedDir); if (this.contentCache.getMaxEntries() > 0) { this.contentCache.set(cacheKey, content); } return content; } catch (error) { logger.error({ err: error, filePath }, `Error reading file: ${filePath}`); throw error; } } async getMetadata(filePath) { if (!this.initialized) { await this.init(); } const cacheKey = this.getCacheKey(filePath); const memoryMetadata = this.fileMetadataCache.get(filePath); if (memoryMetadata) { return memoryMetadata; } if (this.fileCache) { try { const fileMetadata = await this.fileCache.get(cacheKey); if (fileMetadata) { this.fileMetadataCache.set(filePath, fileMetadata); return fileMetadata; } } catch (error) { logger.debug(`Error getting metadata from file cache: ${error}`); } } return undefined; } async updateMetadata(filePath, content, allowedDir) { try { const hash = crypto.createHash('md5').update(content).digest('hex'); const stats = await statSecure(filePath, allowedDir); const metadata = { path: filePath, hash, size: stats.size, mtime: stats.mtime.getTime(), lastAccessed: Date.now() }; this.fileMetadataCache.set(filePath, metadata); if (this.fileCache) { const cacheKey = this.getCacheKey(filePath); await this.fileCache.set(cacheKey, metadata); } return metadata; } catch (error) { logger.error({ err: error, filePath }, `Error updating metadata for ${filePath}`); throw error; } } async hasFileChanged(filePath, allowedDir) { try { const stats = await statSecure(filePath, allowedDir); const metadata = await this.getMetadata(filePath); if (!metadata) { return true; } if (metadata.size !== stats.size || metadata.mtime !== stats.mtime.getTime()) { return true; } return false; } catch (error) { logger.warn(`Error checking if file ${filePath} has changed: ${error}`); return true; } } getCacheKey(filePath) { return crypto.createHash('md5').update(filePath).digest('hex'); } clearCache() { this.contentCache.clear(); this.fileMetadataCache.clear(); logger.debug('Cleared in-memory file caches'); } close() { this.clearCache(); if (this.fileCache) { this.fileCache.close(); } logger.debug('FileContentManager closed'); } }