context-optimizer
Version:
Context Optimizer MCP Server v1.6.0 - Advanced MCP Server with AI External Memory Features, Persistent Storage, Differential Indexing, Content Compression, and Prompt Packaging for Master/Worker AI
420 lines (368 loc) • 13 kB
JavaScript
/**
* Persistent Storage Layer for ContextOptimizer
* 永続化ストレージレイヤー - PoC版
*
* Features:
* - JSON-based storage for simplicity
* - Incremental indexing support
* - Compression and summarization metadata
* - History management with snapshots
*/
import { readFileSync, writeFileSync, existsSync, mkdirSync, statSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
export class PersistentStorage {
constructor(config = {}) {
this.config = {
storagePath: config.storagePath || join(__dirname, '../storage'),
indexFileName: config.indexFileName || 'persistent-index.json',
historyFileName: config.historyFileName || 'context-history.json',
snapshotsFileName: config.snapshotsFileName || 'snapshots.json',
maxHistoryEntries: config.maxHistoryEntries || 100,
compressionThreshold: config.compressionThreshold || 1000, // tokens
...config
};
this.indexData = {
files: new Map(), // filePath -> fileMetadata
lastUpdate: null,
version: '1.0.0'
};
this.historyData = {
entries: [],
lastCleanup: null,
compressionStats: {
totalCompressed: 0,
totalSaved: 0,
compressionRatio: 0
}
};
this.snapshotsData = {
snapshots: [],
maxSnapshots: config.maxSnapshots || 10
};
this.initializeStorage();
}
/**
* Initialize storage directories and load existing data
* ストレージディレクトリの初期化と既存データの読み込み
*/
initializeStorage() {
try {
// Create storage directory if it doesn't exist
if (!existsSync(this.config.storagePath)) {
mkdirSync(this.config.storagePath, { recursive: true });
}
// Load existing data
this.loadIndexData();
this.loadHistoryData();
this.loadSnapshotsData();
console.log(`📁 Persistent storage initialized at: ${this.config.storagePath}`);
} catch (error) {
console.error('❌ Failed to initialize persistent storage:', error);
throw error;
}
}
/**
* Load index data from JSON file
* インデックスデータをJSONファイルから読み込み
*/
loadIndexData() {
const indexPath = join(this.config.storagePath, this.config.indexFileName);
if (existsSync(indexPath)) {
try {
const data = JSON.parse(readFileSync(indexPath, 'utf8'));
this.indexData = {
files: new Map(data.files || []),
lastUpdate: data.lastUpdate,
version: data.version || '1.0.0'
};
console.log(`📖 Loaded ${this.indexData.files.size} indexed files`);
} catch (error) {
console.warn('⚠️ Failed to load index data, starting fresh:', error.message);
}
}
}
/**
* Load history data from JSON file
* 履歴データをJSONファイルから読み込み
*/
loadHistoryData() {
const historyPath = join(this.config.storagePath, this.config.historyFileName);
if (existsSync(historyPath)) {
try {
const data = JSON.parse(readFileSync(historyPath, 'utf8'));
this.historyData = {
entries: data.entries || [],
lastCleanup: data.lastCleanup,
compressionStats: data.compressionStats || {
totalCompressed: 0,
totalSaved: 0,
compressionRatio: 0
}
};
console.log(`📚 Loaded ${this.historyData.entries.length} history entries`);
} catch (error) {
console.warn('⚠️ Failed to load history data, starting fresh:', error.message);
}
}
}
/**
* Load snapshots data from JSON file
* スナップショットデータをJSONファイルから読み込み
*/
loadSnapshotsData() {
const snapshotsPath = join(this.config.storagePath, this.config.snapshotsFileName);
if (existsSync(snapshotsPath)) {
try {
const data = JSON.parse(readFileSync(snapshotsPath, 'utf8'));
this.snapshotsData = {
snapshots: data.snapshots || [],
maxSnapshots: data.maxSnapshots || this.config.maxSnapshots
};
console.log(`📸 Loaded ${this.snapshotsData.snapshots.length} snapshots`);
} catch (error) {
console.warn('⚠️ Failed to load snapshots data, starting fresh:', error.message);
}
}
}
/**
* Save index data to JSON file
* インデックスデータをJSONファイルに保存
*/
saveIndexData() {
try {
const indexPath = join(this.config.storagePath, this.config.indexFileName);
const data = {
files: Array.from(this.indexData.files.entries()),
lastUpdate: new Date().toISOString(),
version: this.indexData.version
};
writeFileSync(indexPath, JSON.stringify(data, null, 2));
console.log(`💾 Saved index data: ${this.indexData.files.size} files`);
} catch (error) {
console.error('❌ Failed to save index data:', error);
throw error;
}
}
/**
* Save history data to JSON file
* 履歴データをJSONファイルに保存
*/
saveHistoryData() {
try {
const historyPath = join(this.config.storagePath, this.config.historyFileName);
const data = {
entries: this.historyData.entries,
lastCleanup: this.historyData.lastCleanup,
compressionStats: this.historyData.compressionStats
};
writeFileSync(historyPath, JSON.stringify(data, null, 2));
console.log(`💾 Saved history data: ${this.historyData.entries.length} entries`);
} catch (error) {
console.error('❌ Failed to save history data:', error);
throw error;
}
}
/**
* Save snapshots data to JSON file
* スナップショットデータをJSONファイルに保存
*/
saveSnapshotsData() {
try {
const snapshotsPath = join(this.config.storagePath, this.config.snapshotsFileName);
const data = {
snapshots: this.snapshotsData.snapshots,
maxSnapshots: this.snapshotsData.maxSnapshots
};
writeFileSync(snapshotsPath, JSON.stringify(data, null, 2));
console.log(`💾 Saved snapshots data: ${this.snapshotsData.snapshots.length} snapshots`);
} catch (error) {
console.error('❌ Failed to save snapshots data:', error);
throw error;
}
}
/**
* Add or update file metadata in index
* ファイルメタデータをインデックスに追加または更新
*/
addFileMetadata(filePath, metadata) {
const fileMetadata = {
path: filePath,
lastModified: metadata.lastModified || new Date().toISOString(),
size: metadata.size || 0,
tokens: metadata.tokens || 0,
importance: metadata.importance || 'normal', // core, utility, test, normal
compressed: metadata.compressed || false,
summary: metadata.summary || null,
tags: metadata.tags || [],
indexedAt: new Date().toISOString(),
...metadata
};
this.indexData.files.set(filePath, fileMetadata);
this.indexData.lastUpdate = new Date().toISOString();
return fileMetadata;
}
/**
* Get file metadata from index
* インデックスからファイルメタデータを取得
*/
getFileMetadata(filePath) {
return this.indexData.files.get(filePath);
}
/**
* Check if file needs re-indexing based on modification time
* ファイルの更新時刻に基づいて再インデックスが必要かチェック
*/
needsReindexing(filePath, currentStats) {
const existingMetadata = this.getFileMetadata(filePath);
if (!existingMetadata) {
return true; // New file
}
const existingTime = new Date(existingMetadata.lastModified).getTime();
const currentTime = currentStats.mtime.getTime();
return currentTime > existingTime;
}
/**
* Add context history entry
* コンテキスト履歴エントリを追加
*/
addHistoryEntry(entry) {
const historyEntry = {
id: `entry_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
timestamp: new Date().toISOString(),
contextSize: entry.contextSize || 0,
compressed: entry.compressed || false,
compressionRatio: entry.compressionRatio || 1.0,
summary: entry.summary || null,
tags: entry.tags || [],
...entry
};
this.historyData.entries.unshift(historyEntry);
// Keep only the most recent entries
if (this.historyData.entries.length > this.config.maxHistoryEntries) {
this.historyData.entries = this.historyData.entries.slice(0, this.config.maxHistoryEntries);
}
// Update compression stats
if (entry.compressed) {
this.historyData.compressionStats.totalCompressed++;
this.historyData.compressionStats.totalSaved += entry.contextSize * (1 - entry.compressionRatio);
this.historyData.compressionStats.compressionRatio =
this.historyData.compressionStats.totalSaved /
(this.historyData.compressionStats.totalCompressed * entry.contextSize);
}
return historyEntry;
}
/**
* Create a snapshot of current state
* 現在の状態のスナップショットを作成
*/
createSnapshot(description = '') {
const snapshot = {
id: `snapshot_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
timestamp: new Date().toISOString(),
description,
stats: {
totalFiles: this.indexData.files.size,
totalHistoryEntries: this.historyData.entries.length,
compressionStats: { ...this.historyData.compressionStats }
},
metadata: {
version: this.indexData.version,
lastUpdate: this.indexData.lastUpdate
}
};
this.snapshotsData.snapshots.unshift(snapshot);
// Keep only the most recent snapshots
if (this.snapshotsData.snapshots.length > this.snapshotsData.maxSnapshots) {
this.snapshotsData.snapshots = this.snapshotsData.snapshots.slice(0, this.snapshotsData.maxSnapshots);
}
return snapshot;
}
/**
* Get files that need re-indexing based on modification time
* 更新時刻に基づいて再インデックスが必要なファイルを取得
*/
getFilesNeedingReindexing(filePaths, fileStats) {
const needsReindexing = [];
for (const filePath of filePaths) {
const stats = fileStats[filePath];
if (stats && this.needsReindexing(filePath, stats)) {
needsReindexing.push(filePath);
}
}
return needsReindexing;
}
/**
* Get compression statistics
* 圧縮統計を取得
*/
getCompressionStats() {
return {
...this.historyData.compressionStats,
totalEntries: this.historyData.entries.length,
compressedEntries: this.historyData.entries.filter(e => e.compressed).length,
averageCompressionRatio: this.historyData.entries.length > 0
? this.historyData.entries.reduce((sum, e) => sum + (e.compressionRatio || 1), 0) / this.historyData.entries.length
: 1
};
}
/**
* Cleanup old data based on retention policy
* 保持ポリシーに基づいて古いデータをクリーンアップ
*/
cleanupOldData(retentionDays = 7) {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - retentionDays);
// Cleanup old history entries
const originalCount = this.historyData.entries.length;
this.historyData.entries = this.historyData.entries.filter(entry =>
new Date(entry.timestamp) > cutoffDate
);
const removedCount = originalCount - this.historyData.entries.length;
this.historyData.lastCleanup = new Date().toISOString();
console.log(`🧹 Cleaned up ${removedCount} old history entries`);
return removedCount;
}
/**
* Get storage statistics
* ストレージ統計を取得
*/
getStorageStats() {
return {
index: {
totalFiles: this.indexData.files.size,
lastUpdate: this.indexData.lastUpdate,
version: this.indexData.version
},
history: {
totalEntries: this.historyData.entries.length,
lastCleanup: this.historyData.lastCleanup,
compressionStats: this.getCompressionStats()
},
snapshots: {
totalSnapshots: this.snapshotsData.snapshots.length,
maxSnapshots: this.snapshotsData.maxSnapshots
}
};
}
/**
* Save all data to disk
* すべてのデータをディスクに保存
*/
saveAll() {
this.saveIndexData();
this.saveHistoryData();
this.saveSnapshotsData();
}
/**
* Close storage and save all data
* ストレージを閉じてすべてのデータを保存
*/
close() {
this.saveAll();
console.log('🔒 Persistent storage closed and data saved');
}
}
export default PersistentStorage;