vibe-coder-mcp
Version:
Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.
124 lines (123 loc) • 4.71 kB
JavaScript
import fs from 'fs/promises';
import path from 'path';
import logger from '../../../logger.js';
import { FileChangeDetector } from './fileChangeDetector.js';
import { getCacheDirectory } from '../directoryUtils.js';
export class IncrementalProcessor {
fileChangeDetector;
config;
baseDir;
previousFilesListPath;
static DEFAULT_CONFIG = {
useFileHashes: true,
useFileMetadata: true,
maxCachedHashes: 10000,
maxHashAge: 24 * 60 * 60 * 1000,
previousFilesListPath: '',
saveProcessedFilesList: true
};
constructor(fileContentManager, config) {
this.config = {
...IncrementalProcessor.DEFAULT_CONFIG,
...config.processing?.incrementalConfig
};
this.fileChangeDetector = new FileChangeDetector(fileContentManager, {
useFileHashes: this.config.useFileHashes,
useFileMetadata: this.config.useFileMetadata,
maxCachedHashes: this.config.maxCachedHashes,
maxHashAge: this.config.maxHashAge
});
this.baseDir = config.allowedMappingDirectory;
if (this.config.previousFilesListPath) {
this.previousFilesListPath = this.config.previousFilesListPath;
}
else {
const cacheDir = getCacheDirectory(config);
this.previousFilesListPath = path.join(cacheDir, 'processed-files.json');
}
logger.debug(`IncrementalProcessor created with config: ${JSON.stringify(this.config)}`);
}
async processIncrementally(filePaths) {
const previousFiles = await this.loadPreviousFiles();
if (previousFiles.length === 0) {
logger.info('No previously processed files found, processing all files');
if (this.config.saveProcessedFilesList) {
await this.savePreviousFiles(filePaths);
}
return {
changedFiles: filePaths,
unchangedFiles: [],
totalFiles: filePaths.length,
changePercentage: 100
};
}
this.fileChangeDetector.setProcessedFiles(previousFiles);
const changedFiles = [];
const unchangedFiles = [];
for (const filePath of filePaths) {
const wasProcessed = this.fileChangeDetector.wasFileProcessed(filePath);
if (!wasProcessed) {
changedFiles.push(filePath);
continue;
}
const result = await this.fileChangeDetector.detectChange(filePath, this.baseDir);
if (result.changed) {
changedFiles.push(filePath);
}
else {
unchangedFiles.push(filePath);
}
}
const totalFiles = filePaths.length;
const changePercentage = totalFiles > 0 ? (changedFiles.length / totalFiles) * 100 : 0;
logger.info(`Incremental processing: ${changedFiles.length} changed files, ${unchangedFiles.length} unchanged files (${changePercentage.toFixed(2)}% changed)`);
if (this.config.saveProcessedFilesList) {
await this.savePreviousFiles(filePaths);
}
return {
changedFiles,
unchangedFiles,
totalFiles,
changePercentage
};
}
async loadPreviousFiles() {
try {
try {
await fs.access(this.previousFilesListPath);
}
catch {
return [];
}
const content = await fs.readFile(this.previousFilesListPath, 'utf-8');
const data = JSON.parse(content);
if (Array.isArray(data)) {
logger.debug(`Loaded ${data.length} previously processed files`);
return data;
}
logger.warn('Invalid format for previously processed files list');
return [];
}
catch (error) {
logger.error({ err: error }, 'Error loading previously processed files');
return [];
}
}
async savePreviousFiles(filePaths) {
try {
const dir = path.dirname(this.previousFilesListPath);
await fs.mkdir(dir, { recursive: true });
await fs.writeFile(this.previousFilesListPath, JSON.stringify(filePaths), 'utf-8');
logger.debug(`Saved ${filePaths.length} processed files for the next run`);
}
catch (error) {
logger.error({ err: error }, 'Error saving processed files list');
}
}
getFileChangeDetector() {
return this.fileChangeDetector;
}
clearCache() {
this.fileChangeDetector.clearCache();
}
}