UNPKG

mcp-adr-analysis-server

Version:

MCP server for analyzing Architectural Decision Records and project architecture

336 lines 11.8 kB
import { promises as fs } from 'fs'; import * as path from 'path'; import * as yaml from 'js-yaml'; import { EnhancedLogger } from './enhanced-logging.js'; /** * Manages SystemCard creation, updates, and persistence */ export class SystemCardManager { systemCardPath; logger; currentCard = null; constructor(projectPath) { this.systemCardPath = path.join(projectPath, 'systemcard.yaml'); this.logger = new EnhancedLogger(); } /** * Initialize a new SystemCard */ async initialize(options) { try { this.currentCard = { version: '1.0', systemId: options.systemId, created: options.created, lastUpdated: options.created, platform: options.platform, bootstrapContext: options.bootstrapContext, resources: {}, cleanup: { strategy: 'label-based', labelSelector: { 'managed-by': 'mcp-adr-server', 'bootstrap-id': options.bootstrapContext.bootstrapId, }, phases: [], }, ...(options.metadata && { metadata: options.metadata }), }; await this.save(); this.logger.info(`SystemCard initialized: ${this.systemCardPath}`, 'SystemCardManager'); } catch (error) { this.logger.error('Failed to initialize SystemCard', 'SystemCardManager', error); throw error; } } /** * Load existing SystemCard from disk */ async load() { try { const content = await fs.readFile(this.systemCardPath, 'utf-8'); this.currentCard = yaml.load(content); this.logger.info('SystemCard loaded successfully', 'SystemCardManager'); return this.currentCard; } catch (error) { if (error.code === 'ENOENT') { this.logger.warn('SystemCard not found', 'SystemCardManager'); return null; } this.logger.error('Failed to load SystemCard', 'SystemCardManager', error); throw error; } } /** * Save current SystemCard to disk */ async save() { if (!this.currentCard) { throw new Error('No SystemCard initialized. Call initialize() first.'); } try { this.currentCard.lastUpdated = new Date().toISOString(); const yamlContent = yaml.dump(this.currentCard, { indent: 2, lineWidth: -1, noRefs: true, }); await fs.writeFile(this.systemCardPath, yamlContent, 'utf-8'); this.logger.info('SystemCard saved successfully', 'SystemCardManager'); } catch (error) { this.logger.error('Failed to save SystemCard', 'SystemCardManager', error); throw error; } } /** * Add resources to the SystemCard */ async addResources(resources, context) { if (!this.currentCard) { throw new Error('No SystemCard loaded. Call load() or initialize() first.'); } try { for (const resource of resources) { // Add phase/task context if provided if (context?.phase) { resource.createdByPhase = context.phase; } if (context?.taskId && !resource.createdByTask) { resource.createdByTask = context.taskId; } // Categorize by type const category = this.getResourceCategory(resource.type); if (!this.currentCard.resources[category]) { this.currentCard.resources[category] = []; } // Check for duplicates const exists = this.currentCard.resources[category].some(r => r.name === resource.name && r.namespace === resource.namespace); if (!exists) { this.currentCard.resources[category].push(resource); this.logger.info(`Added resource: ${resource.type}/${resource.name}`, 'SystemCardManager'); } } await this.save(); } catch (error) { this.logger.error('Failed to add resources', 'SystemCardManager', error); throw error; } } /** * Remove a resource from the SystemCard */ async removeResource(type, name, namespace) { if (!this.currentCard) { throw new Error('No SystemCard loaded.'); } try { const category = this.getResourceCategory(type); const resources = this.currentCard.resources[category]; if (resources) { const index = resources.findIndex(r => r.name === name && (namespace ? r.namespace === namespace : true)); if (index !== -1) { resources.splice(index, 1); this.logger.info(`Removed resource: ${type}/${name}`, 'SystemCardManager'); await this.save(); } } } catch (error) { this.logger.error('Failed to remove resource', 'SystemCardManager', error); throw error; } } /** * Generate cleanup phases from current resources * Resources are ordered in reverse of typical deployment order */ async generateCleanupPhases() { if (!this.currentCard) { throw new Error('No SystemCard loaded.'); } try { const phases = []; let order = 1; // Order of cleanup (reverse of typical deployment) const cleanupOrder = [ 'ingresses', 'services', 'deployments', 'statefulsets', 'pods', 'configmaps', 'secrets', 'persistentvolumeclaims', 'namespaces', 'custom', ]; for (const category of cleanupOrder) { const resources = this.currentCard.resources[category]; if (resources && resources.length > 0) { const phase = { order: order++, name: `Delete ${category}`, description: `Remove all ${category} created during bootstrap`, commands: resources.map(r => ({ description: `Delete ${r.type} ${r.name}`, command: r.cleanupCommand, expectedExitCode: 0, continueOnError: false, })), canParallelize: true, estimatedDuration: `${resources.length * 2}-${resources.length * 5} seconds`, }; phases.push(phase); } } // Update SystemCard with generated cleanup phases this.currentCard.cleanup.phases = phases; await this.save(); this.logger.info(`Generated ${phases.length} cleanup phases`, 'SystemCardManager'); return phases; } catch (error) { this.logger.error('Failed to generate cleanup phases', 'SystemCardManager', error); throw error; } } /** * Create a state snapshot for rollback capability */ async createSnapshot(beforeAction) { if (!this.currentCard) { throw new Error('No SystemCard loaded.'); } try { const resourceCount = Object.values(this.currentCard.resources) .filter(arr => Array.isArray(arr)) .reduce((sum, arr) => sum + arr.length, 0); const checksum = this.calculateChecksum(this.currentCard); const snapshot = { timestamp: new Date().toISOString(), beforeAction, resourceCount, checksum, }; if (!this.currentCard.snapshots) { this.currentCard.snapshots = []; } this.currentCard.snapshots.push(snapshot); await this.save(); this.logger.info(`Snapshot created: ${beforeAction}`, 'SystemCardManager'); return snapshot; } catch (error) { this.logger.error('Failed to create snapshot', 'SystemCardManager', error); throw error; } } /** * Get all resources of a specific type */ getResourcesByType(type) { if (!this.currentCard) { return []; } const category = this.getResourceCategory(type); return this.currentCard.resources[category] || []; } /** * Get all resources created by a specific task */ getResourcesByTask(taskId) { if (!this.currentCard) { return []; } const allResources = []; for (const category of Object.keys(this.currentCard.resources)) { const resources = this.currentCard.resources[category]; if (resources) { allResources.push(...resources.filter(r => r.createdByTask === taskId)); } } return allResources; } /** * Get total count of all resources */ getResourceCount() { if (!this.currentCard) { return 0; } return Object.values(this.currentCard.resources) .filter(arr => Array.isArray(arr)) .reduce((sum, arr) => sum + arr.length, 0); } /** * Get current SystemCard */ getCurrentCard() { return this.currentCard; } /** * Check if SystemCard exists on disk */ async exists() { try { await fs.access(this.systemCardPath); return true; } catch { return false; } } /** * Delete SystemCard file from disk */ async delete() { try { await fs.unlink(this.systemCardPath); this.currentCard = null; this.logger.info('SystemCard deleted', 'SystemCardManager'); } catch (error) { if (error.code !== 'ENOENT') { this.logger.error('Failed to delete SystemCard', 'SystemCardManager', error); throw error; } } } /** * Map resource type to storage category */ getResourceCategory(type) { const typeMap = { namespace: 'namespaces', deployment: 'deployments', service: 'services', configmap: 'configmaps', secret: 'secrets', ingress: 'ingresses', statefulset: 'statefulsets', pod: 'pods', persistentvolumeclaim: 'persistentvolumeclaims', custom: 'custom', }; return typeMap[type] || 'custom'; } /** * Calculate checksum for state verification */ calculateChecksum(card) { const contentString = JSON.stringify(card.resources); // Simple hash function (in production, use crypto.createHash) let hash = 0; for (let i = 0; i < contentString.length; i++) { const char = contentString.charCodeAt(i); hash = (hash << 5) - hash + char; hash = hash & hash; // Convert to 32-bit integer } return hash.toString(16); } } //# sourceMappingURL=system-card-manager.js.map