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
JavaScript
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');
}
}