ConfigurationManager.js•9.53 kB
import { promises as fs } from 'fs';
import { join } from 'path';
import { homedir } from 'os';
export class ConfigurationManager {
constructor() {
this.configDir = join(homedir(), '.mcp_sequential_thinking', 'config');
this.config = {
factTypes: null,
scoringWeights: null,
settings: null,
};
this.initialized = false;
}
async initialize() {
try {
await this.ensureConfigDirectory();
await this.loadConfigurations();
this.initialized = true;
console.log('ConfigurationManager initialized');
} catch (error) {
console.error('Failed to initialize ConfigurationManager:', error);
throw error;
}
}
async ensureConfigDirectory() {
try {
await fs.mkdir(this.configDir, { recursive: true });
await fs.mkdir(join(this.configDir, 'backups'), { recursive: true });
} catch (error) {
console.error('Failed to create config directory:', error);
throw error;
}
}
async loadConfigurations() {
await Promise.all([
this.loadFactTypes(),
this.loadScoringWeights(),
this.loadSettings(),
]);
}
async loadFactTypes() {
try {
const factTypesPath = join(this.configDir, 'fact-types.json');
const data = await fs.readFile(factTypesPath, 'utf-8');
this.config.factTypes = JSON.parse(data);
} catch (error) {
this.config.factTypes = null;
}
}
async loadScoringWeights() {
try {
const weightsPath = join(this.configDir, 'scoring-weights.json');
const data = await fs.readFile(weightsPath, 'utf-8');
this.config.scoringWeights = JSON.parse(data);
} catch (error) {
this.config.scoringWeights = null;
}
}
async loadSettings() {
try {
const settingsPath = join(this.configDir, 'settings.json');
const data = await fs.readFile(settingsPath, 'utf-8');
this.config.settings = JSON.parse(data);
} catch (error) {
this.config.settings = null;
}
}
getFactTypes() {
return this.config.factTypes;
}
async saveFactTypes(factTypes) {
await this.backupConfig('fact-types.json');
const factTypesPath = join(this.configDir, 'fact-types.json');
await fs.writeFile(factTypesPath, JSON.stringify(factTypes, null, 2));
this.config.factTypes = factTypes;
return true;
}
async ensureFactTypesConfig(defaultFactTypes) {
if (!this.config.factTypes) {
await this.saveFactTypes(defaultFactTypes);
}
}
getScoringWeights() {
return this.config.scoringWeights;
}
async saveScoringWeights(weights) {
this.validateScoringWeights(weights);
await this.backupConfig('scoring-weights.json');
const weightsPath = join(this.configDir, 'scoring-weights.json');
await fs.writeFile(weightsPath, JSON.stringify(weights, null, 2));
this.config.scoringWeights = weights;
return true;
}
validateScoringWeights(weights) {
const requiredDimensions = ['novelty', 'generalizability', 'specificity', 'validation', 'impact'];
for (const dimension of requiredDimensions) {
if (!(dimension in weights)) {
throw new Error(`Missing required scoring dimension: ${dimension}`);
}
if (typeof weights[dimension] !== 'number' || weights[dimension] < 0 || weights[dimension] > 1) {
throw new Error(`Invalid weight for ${dimension}: must be a number between 0 and 1`);
}
}
const sum = Object.values(weights).reduce((total, weight) => total + weight, 0);
if (Math.abs(sum - 1.0) > 0.001) {
throw new Error(`Scoring weights must sum to 1.0, got ${sum}`);
}
}
getSettings() {
return this.config.settings || this.getDefaultSettings();
}
getDefaultSettings() {
return {
qualityThreshold: 60,
maxFactsPerSession: 100,
retentionCheckInterval: 24 * 60 * 60 * 1000,
autoBackup: true,
backupRetention: 30,
debugMode: false,
enableHooks: true,
processingDelay: 2000,
};
}
async saveSettings(settings) {
await this.backupConfig('settings.json');
const settingsPath = join(this.configDir, 'settings.json');
await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2));
this.config.settings = settings;
return true;
}
async updateSetting(key, value) {
const currentSettings = this.getSettings();
const updatedSettings = {
...currentSettings,
[key]: value,
};
return await this.saveSettings(updatedSettings);
}
async backupConfig(filename) {
try {
const sourcePath = join(this.configDir, filename);
const backupPath = join(
this.configDir,
'backups',
`${filename}.${Date.now()}.backup`
);
const data = await fs.readFile(sourcePath, 'utf-8').catch(() => null);
if (data) {
await fs.writeFile(backupPath, data);
}
await this.cleanupOldBackups(filename);
} catch (error) {
console.warn(`Failed to backup ${filename}:`, error);
}
}
async cleanupOldBackups(filename) {
try {
const backupsDir = join(this.configDir, 'backups');
const files = await fs.readdir(backupsDir);
const backupFiles = files
.filter(file => file.startsWith(filename) && file.endsWith('.backup'))
.map(file => ({
name: file,
path: join(backupsDir, file),
timestamp: parseInt(file.split('.')[1], 10),
}))
.sort((a, b) => b.timestamp - a.timestamp);
const settings = this.getSettings();
const maxBackups = settings.backupRetention || 30;
if (backupFiles.length > maxBackups) {
const filesToDelete = backupFiles.slice(maxBackups);
for (const file of filesToDelete) {
await fs.unlink(file.path);
}
}
} catch (error) {
console.warn('Failed to cleanup old backups:', error);
}
}
async exportConfiguration() {
const exportData = {
factTypes: this.config.factTypes,
scoringWeights: this.config.scoringWeights,
settings: this.config.settings,
exportedAt: new Date().toISOString(),
version: '1.0.0',
};
return JSON.stringify(exportData, null, 2);
}
async importConfiguration(configData) {
let config;
try {
config = typeof configData === 'string' ? JSON.parse(configData) : configData;
} catch (error) {
throw new Error('Invalid configuration data: not valid JSON');
}
if (config.factTypes) {
await this.saveFactTypes(config.factTypes);
}
if (config.scoringWeights) {
await this.saveScoringWeights(config.scoringWeights);
}
if (config.settings) {
await this.saveSettings(config.settings);
}
return {
imported: {
factTypes: !!config.factTypes,
scoringWeights: !!config.scoringWeights,
settings: !!config.settings,
},
timestamp: new Date().toISOString(),
};
}
async resetConfiguration(component = 'all') {
const backupTimestamp = Date.now();
if (component === 'all' || component === 'factTypes') {
await this.backupConfig('fact-types.json');
const factTypesPath = join(this.configDir, 'fact-types.json');
await fs.unlink(factTypesPath).catch(() => {});
this.config.factTypes = null;
}
if (component === 'all' || component === 'scoringWeights') {
await this.backupConfig('scoring-weights.json');
const weightsPath = join(this.configDir, 'scoring-weights.json');
await fs.unlink(weightsPath).catch(() => {});
this.config.scoringWeights = null;
}
if (component === 'all' || component === 'settings') {
await this.backupConfig('settings.json');
const settingsPath = join(this.configDir, 'settings.json');
await fs.unlink(settingsPath).catch(() => {});
this.config.settings = null;
}
return {
reset: component,
backedUp: true,
timestamp: new Date().toISOString(),
};
}
async validateConfiguration() {
const issues = [];
if (this.config.scoringWeights) {
try {
this.validateScoringWeights(this.config.scoringWeights);
} catch (error) {
issues.push(`Scoring weights: ${error.message}`);
}
}
if (this.config.factTypes) {
for (const [typeName, typeConfig] of Object.entries(this.config.factTypes)) {
if (!typeConfig.name || !typeConfig.description) {
issues.push(`Fact type '${typeName}': missing name or description`);
}
if (typeof typeConfig.priority !== 'number' || typeConfig.priority < 1 || typeConfig.priority > 10) {
issues.push(`Fact type '${typeName}': priority must be between 1 and 10`);
}
if (typeof typeConfig.retentionMonths !== 'number' || typeConfig.retentionMonths < 1) {
issues.push(`Fact type '${typeName}': retentionMonths must be a positive number`);
}
}
}
return {
valid: issues.length === 0,
issues,
checkedAt: new Date().toISOString(),
};
}
getConfigurationPaths() {
return {
configDir: this.configDir,
factTypesPath: join(this.configDir, 'fact-types.json'),
scoringWeightsPath: join(this.configDir, 'scoring-weights.json'),
settingsPath: join(this.configDir, 'settings.json'),
backupsDir: join(this.configDir, 'backups'),
};
}
}