vibe-coder-mcp
Version:
Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.
443 lines (442 loc) • 17.5 kB
JavaScript
import fs from 'fs-extra';
import path from 'path';
import { promisify } from 'util';
import { gzip, gunzip } from 'zlib';
import { TaskManagerMemoryManager } from '../utils/memory-manager-integration.js';
import { UnifiedSecurityEngine, createDefaultSecurityConfig } from './unified-security-engine.js';
import { AppError } from '../../../utils/errors.js';
import logger from '../../../logger.js';
const gzipAsync = promisify(gzip);
const gunzipAsync = promisify(gunzip);
export class TaskFileManager {
static instance = null;
config;
fileIndex = new Map();
loadedTasks = new Map();
lazyLoadCache = new Map();
memoryManager = null;
securityEngine;
indexFilePath;
dataDirectory;
constructor(config, dataDirectory) {
this.config = config;
this.dataDirectory = dataDirectory;
this.indexFilePath = path.join(dataDirectory, '.file-index.json');
this.memoryManager = TaskManagerMemoryManager.getInstance();
this.memoryManager?.registerCleanupCallback('task-file-manager', () => this.performCleanup());
const securityConfig = createDefaultSecurityConfig();
this.securityEngine = UnifiedSecurityEngine.getInstance(securityConfig);
logger.info({ config, dataDirectory }, 'Task File Manager initialized');
}
async validateSecurePath(filePath, operation) {
const result = await this.securityEngine.validatePath(filePath, operation);
if (result.success) {
return {
valid: result.data.isValid,
error: result.data.error,
violationType: result.data.isValid ? undefined : 'path_security',
normalizedPath: result.data.normalizedPath
};
}
else {
return {
valid: false,
error: result.error.message,
violationType: 'path_security',
normalizedPath: filePath
};
}
}
static getInstance(config, dataDirectory) {
if (!TaskFileManager.instance) {
if (!config || !dataDirectory) {
throw new AppError('Configuration and data directory required for first initialization');
}
TaskFileManager.instance = new TaskFileManager(config, dataDirectory);
}
return TaskFileManager.instance;
}
async initialize() {
try {
await fs.ensureDir(this.dataDirectory);
await this.loadFileIndex();
logger.info('Task File Manager initialized successfully');
return {
success: true,
metadata: {
filePath: this.dataDirectory,
operation: 'initialize',
timestamp: new Date()
}
};
}
catch (error) {
logger.error({ err: error }, 'Failed to initialize Task File Manager');
return {
success: false,
error: error instanceof Error ? error.message : String(error),
metadata: {
filePath: this.dataDirectory,
operation: 'initialize',
timestamp: new Date()
}
};
}
}
async loadFileIndex() {
try {
if (await fs.pathExists(this.indexFilePath)) {
const indexData = await fs.readJson(this.indexFilePath);
this.fileIndex = new Map(Object.entries(indexData));
logger.debug({ entriesLoaded: this.fileIndex.size }, 'File index loaded');
}
}
catch (error) {
logger.warn({ err: error }, 'Failed to load file index, starting with empty index');
this.fileIndex = new Map();
}
}
async saveFileIndex() {
try {
const indexData = Object.fromEntries(this.fileIndex);
const startTime = Date.now();
logger.debug({
indexPath: this.indexFilePath,
entries: this.fileIndex.size,
operation: 'index_write_start'
}, 'Starting file index write');
await fs.writeJson(this.indexFilePath, indexData, { spaces: 2 });
logger.info({
indexPath: this.indexFilePath,
entriesSaved: this.fileIndex.size,
operation: 'index_write_complete',
duration: Date.now() - startTime
}, 'File index saved successfully');
}
catch (error) {
logger.error({
err: error,
indexPath: this.indexFilePath,
operation: 'index_write_failed'
}, 'Failed to save file index');
}
}
async saveTask(task) {
try {
const filePath = this.getTaskFilePath(task.id);
const pathValidation = await this.validateSecurePath(filePath, 'write');
if (!pathValidation.valid) {
logger.error({
taskId: task.id,
filePath,
violation: pathValidation.violationType,
error: pathValidation.error
}, 'Path security validation failed for task save');
return {
success: false,
error: `Path security validation failed: ${pathValidation.error}`,
metadata: {
filePath,
operation: 'save_task',
timestamp: new Date()
}
};
}
const content = JSON.stringify(task, null, 2);
await fs.ensureDir(path.dirname(filePath));
const writeStartTime = Date.now();
logger.info({
taskId: task.id,
filePath,
operation: 'file_write_start',
size: Buffer.byteLength(content),
compressed: this.config.enableCompression
}, 'Starting file write operation');
if (this.config.enableCompression) {
const compressed = await gzipAsync(Buffer.from(content));
const compressedPath = filePath + '.gz';
await fs.writeFile(compressedPath, compressed);
logger.info({
taskId: task.id,
filePath: compressedPath,
originalSize: Buffer.byteLength(content),
compressedSize: compressed.length,
compressionRatio: (1 - compressed.length / Buffer.byteLength(content)) * 100,
operation: 'file_write_complete',
duration: Date.now() - writeStartTime
}, 'Task file written successfully (compressed)');
this.updateFileIndex(task.id, compressedPath, compressed.length, true);
}
else {
await fs.writeFile(filePath, content);
logger.info({
taskId: task.id,
filePath,
size: Buffer.byteLength(content),
operation: 'file_write_complete',
duration: Date.now() - writeStartTime
}, 'Task file written successfully');
this.updateFileIndex(task.id, filePath, Buffer.byteLength(content), false);
}
if (this.loadedTasks.size < 1000) {
this.loadedTasks.set(task.id, task);
}
await this.saveFileIndex();
logger.debug({ taskId: task.id, compressed: this.config.enableCompression }, 'Task saved');
return {
success: true,
metadata: {
filePath,
operation: 'save_task',
timestamp: new Date()
}
};
}
catch (error) {
logger.error({ err: error, taskId: task.id }, 'Failed to save task');
return {
success: false,
error: error instanceof Error ? error.message : String(error),
metadata: {
filePath: this.getTaskFilePath(task.id),
operation: 'save_task',
timestamp: new Date()
}
};
}
}
async loadTask(taskId) {
try {
const cachedTask = this.loadedTasks.get(taskId);
if (cachedTask) {
logger.debug({ taskId }, 'Task loaded from memory cache');
return {
success: true,
data: cachedTask,
metadata: {
filePath: 'memory-cache',
operation: 'load_task',
timestamp: new Date()
}
};
}
const indexEntry = this.fileIndex.get(taskId);
if (!indexEntry) {
return {
success: false,
error: 'Task not found in index',
metadata: {
filePath: this.getTaskFilePath(taskId),
operation: 'load_task',
timestamp: new Date()
}
};
}
const pathValidation = await this.validateSecurePath(indexEntry.filePath, 'read');
if (!pathValidation.valid) {
logger.error({
taskId,
filePath: indexEntry.filePath,
violation: pathValidation.violationType,
error: pathValidation.error
}, 'Path security validation failed for task load');
return {
success: false,
error: `Path security validation failed: ${pathValidation.error}`,
metadata: {
filePath: indexEntry.filePath,
operation: 'load_task',
timestamp: new Date()
}
};
}
let content;
if (indexEntry.compressed) {
const compressed = await fs.readFile(indexEntry.filePath);
const decompressed = await gunzipAsync(compressed);
content = decompressed.toString();
}
else {
content = await fs.readFile(indexEntry.filePath, 'utf-8');
}
const task = JSON.parse(content);
if (this.loadedTasks.size < 1000) {
this.loadedTasks.set(taskId, task);
}
logger.debug({ taskId, compressed: indexEntry.compressed }, 'Task loaded from file');
return {
success: true,
data: task,
metadata: {
filePath: indexEntry.filePath,
operation: 'load_task',
timestamp: new Date()
}
};
}
catch (error) {
logger.error({ err: error, taskId }, 'Failed to load task');
return {
success: false,
error: error instanceof Error ? error.message : String(error),
metadata: {
filePath: this.getTaskFilePath(taskId),
operation: 'load_task',
timestamp: new Date()
}
};
}
}
async batchSaveTasks(tasks) {
const startTime = Date.now();
const startMemory = process.memoryUsage().heapUsed;
const results = [];
const errors = [];
try {
for (let i = 0; i < tasks.length; i += this.config.batchSize) {
const batch = tasks.slice(i, i + this.config.batchSize);
const batchPromises = batch.map(async (task) => {
try {
const result = await this.saveTask(task);
if (result.success) {
results.push(undefined);
}
else {
errors.push({ id: task.id, error: result.error || 'Unknown error' });
}
}
catch (error) {
errors.push({
id: task.id,
error: error instanceof Error ? error.message : String(error)
});
}
});
const chunks = [];
for (let j = 0; j < batchPromises.length; j += this.config.concurrentOperations) {
chunks.push(batchPromises.slice(j, j + this.config.concurrentOperations));
}
for (const chunk of chunks) {
await Promise.all(chunk);
}
logger.debug({
batchNumber: Math.floor(i / this.config.batchSize) + 1,
processed: Math.min(i + this.config.batchSize, tasks.length),
total: tasks.length
}, 'Batch processed');
}
const duration = Date.now() - startTime;
const memoryUsage = process.memoryUsage().heapUsed - startMemory;
logger.info({
totalTasks: tasks.length,
successful: results.length,
errors: errors.length,
duration: `${duration}ms`,
memoryUsage: `${Math.round(memoryUsage / 1024 / 1024)}MB`
}, 'Batch save completed');
return {
success: errors.length === 0,
results,
errors,
totalProcessed: results.length,
duration,
memoryUsage
};
}
catch (error) {
logger.error({ err: error }, 'Batch save failed');
return {
success: false,
results,
errors: [...errors, { id: 'batch', error: error instanceof Error ? error.message : String(error) }],
totalProcessed: results.length,
duration: Date.now() - startTime,
memoryUsage: process.memoryUsage().heapUsed - startMemory
};
}
}
getTaskFilePath(taskId) {
return path.join(this.dataDirectory, 'tasks', `${taskId}.json`);
}
updateFileIndex(id, filePath, size, compressed) {
this.fileIndex.set(id, {
id,
filePath,
size,
lastModified: new Date(),
compressed
});
}
async performCleanup() {
const startTime = Date.now();
const initialMemory = process.memoryUsage().heapUsed;
try {
const tasksRemoved = this.loadedTasks.size;
this.loadedTasks.clear();
const lazyPagesRemoved = this.lazyLoadCache.size;
this.lazyLoadCache.clear();
if (global.gc) {
global.gc();
}
await new Promise(resolve => setTimeout(resolve, 1));
const finalMemory = process.memoryUsage().heapUsed;
const memoryFreed = Math.max(0, initialMemory - finalMemory);
const duration = Date.now() - startTime;
logger.info({
tasksRemoved,
lazyPagesRemoved,
memoryFreed: `${Math.round(memoryFreed / 1024 / 1024)}MB`,
duration: `${duration}ms`
}, 'Task File Manager cleanup completed');
return {
success: true,
memoryFreed,
itemsRemoved: tasksRemoved + lazyPagesRemoved,
duration: Math.max(1, duration)
};
}
catch (error) {
const duration = Date.now() - startTime;
logger.error({ err: error }, 'Task File Manager cleanup failed');
return {
success: false,
memoryFreed: 0,
itemsRemoved: 0,
duration: Math.max(1, duration),
error: error instanceof Error ? error.message : String(error)
};
}
}
getStatistics() {
const totalFileSize = Array.from(this.fileIndex.values())
.reduce((sum, entry) => sum + entry.size, 0);
const compressedFiles = Array.from(this.fileIndex.values())
.filter(entry => entry.compressed).length;
const compressionRatio = this.fileIndex.size > 0
? compressedFiles / this.fileIndex.size
: 0;
return {
indexedFiles: this.fileIndex.size,
memoryCache: this.loadedTasks.size,
lazyLoadCache: this.lazyLoadCache.size,
totalFileSize,
compressionRatio
};
}
async shutdown() {
await this.saveFileIndex();
this.loadedTasks.clear();
this.lazyLoadCache.clear();
this.fileIndex.clear();
this.memoryManager?.unregisterCleanupCallback('task-file-manager');
logger.info('Task File Manager shutdown');
}
}
export function getTaskFileManager() {
try {
return TaskFileManager.getInstance();
}
catch {
return null;
}
}