UNPKG

mcp-codesentry

Version:

CodeSentry MCP - AI-powered code review assistant with 5 specialized review tools for security, best practices, and comprehensive code analysis

129 lines 5.36 kB
/** * Storage Manager for handling code snapshots and task contexts */ import * as fs from 'fs-extra'; import * as path from 'path'; import * as crypto from 'crypto'; import * as os from 'os'; import { logger } from '../utils/logger.js'; export class StorageManager { basePath; config; encryptionKey; constructor(config) { this.config = config; this.basePath = config.basePath; if (config.encrypt) { // Generate a consistent encryption key based on system info const systemId = `${os.hostname()}-${os.userInfo().username}`; this.encryptionKey = crypto.scryptSync(systemId, 'software-architect-mcp', 32); } } async initialize() { await fs.ensureDir(this.basePath); await fs.ensureDir(path.join(this.basePath, 'snapshots')); await fs.ensureDir(path.join(this.basePath, 'tasks')); logger.info(`Storage initialized at: ${this.basePath}`); } async storeSnapshot(taskId, content, type) { const snapshotId = `${taskId}-${type}-${Date.now()}`; const filePath = path.join(this.basePath, 'snapshots', `${snapshotId}.txt`); // Check size limit const sizeInBytes = Buffer.byteLength(content, 'utf8'); const maxSizeBytes = (this.config.maxSizeMB || 100) * 1024 * 1024; if (sizeInBytes > maxSizeBytes) { throw new Error(`Content size exceeds maximum size of ${this.config.maxSizeMB}MB`); } let dataToStore = content; if (this.config.encrypt && this.encryptionKey) { dataToStore = this.encrypt(content); } await fs.writeFile(filePath, dataToStore); logger.info(`Stored ${type} snapshot for task ${taskId}: ${snapshotId}`); return snapshotId; } async retrieveSnapshot(snapshotId) { const filePath = path.join(this.basePath, 'snapshots', `${snapshotId}.txt`); if (!await fs.pathExists(filePath)) { throw new Error(`Snapshot not found: ${snapshotId}`); } let content = await fs.readFile(filePath, 'utf8'); if (this.config.encrypt && this.encryptionKey) { content = this.decrypt(content); } return content; } async storeTaskContext(taskId, context) { const filePath = path.join(this.basePath, 'tasks', `${taskId}.json`); let dataToStore = JSON.stringify(context, null, 2); if (this.config.encrypt && this.encryptionKey) { dataToStore = this.encrypt(dataToStore); } await fs.writeFile(filePath, dataToStore); logger.info(`Stored task context for: ${taskId}`); } async retrieveTaskContext(taskId) { const filePath = path.join(this.basePath, 'tasks', `${taskId}.json`); if (!await fs.pathExists(filePath)) { return null; } let content = await fs.readFile(filePath, 'utf8'); if (this.config.encrypt && this.encryptionKey) { content = this.decrypt(content); } return JSON.parse(content); } encrypt(text) { if (!this.encryptionKey) { throw new Error('Encryption key not available'); } const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv('aes-256-cbc', this.encryptionKey, iv); let encrypted = cipher.update(text, 'utf8', 'hex'); encrypted += cipher.final('hex'); return iv.toString('hex') + ':' + encrypted; } decrypt(encryptedText) { if (!this.encryptionKey) { throw new Error('Encryption key not available'); } const [ivHex, encrypted] = encryptedText.split(':'); const iv = Buffer.from(ivHex, 'hex'); const decipher = crypto.createDecipheriv('aes-256-cbc', this.encryptionKey, iv); let decrypted = decipher.update(encrypted, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } async cleanup(olderThanDays = 7) { const snapshots = await fs.readdir(path.join(this.basePath, 'snapshots')); const cutoffTime = Date.now() - (olderThanDays * 24 * 60 * 60 * 1000); for (const file of snapshots) { const filePath = path.join(this.basePath, 'snapshots', file); const stats = await fs.stat(filePath); if (stats.mtimeMs < cutoffTime) { await fs.remove(filePath); logger.info(`Cleaned up old snapshot: ${file}`); } } } async getStorageStats() { const snapshots = await fs.readdir(path.join(this.basePath, 'snapshots')); const tasks = await fs.readdir(path.join(this.basePath, 'tasks')); let totalSize = 0; for (const file of [...snapshots, ...tasks]) { const filePath = path.join(this.basePath, file.includes('.json') ? 'tasks' : 'snapshots', file); const stats = await fs.stat(filePath); totalSize += stats.size; } return { totalSnapshots: snapshots.length, totalTasks: tasks.length, totalSizeMB: totalSize / (1024 * 1024) }; } sanitizePath(inputPath) { // Remove any directory traversal attempts return path.basename(inputPath).replace(/[^a-zA-Z0-9._-]/g, ''); } } //# sourceMappingURL=manager.js.map