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
JavaScript
/**
* 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