const fs = require('fs'); const path = require('path'); const STATE_SERVICES_REGEX = /(redis|postgres|mysql|mongo|kafka|sqs|rabbit|elasticsearch|cassandra|db|cache|queue|database|aurora)/; /** * Dynamically extract system configurations and state boundaries using generic heuristics. * Replaces extract-deep.js and extract-patterns.js. * This MUST be deterministic (no LLM calls). */ function extractDynamic(graph, archetype, repoRoot) { const result = { configs: [], stateServices: [], deploymentPatterns: [], networkTopology: [] }; if (!graph) return result; const files = graph.files || []; const entities = graph.entities || {}; // 1. Config Surfaces (find generic config structures: Helm, JSON, YAML) const configFiles = files.filter(f => f.match(/\.(yaml|yml|json|toml|ini)$/i)); // Extract a sample of configs deterministically from generic entities for (const [id, entity] of Object.entries(entities)) { if (entity.type && (entity.type.includes('Config') || entity.type.includes('Params') || entity.type.includes('HelmValues'))) { result.configs.push({ name: entity.name, file: entity.file, type: entity.type }); } // 2. State Services (databases, caches, queues) const lowerName = entity.name ? entity.name.toLowerCase() : ''; if (lowerName.match(STATE_SERVICES_REGEX)) { if (!result.stateServices.some(s => s.name === entity.name)) { result.stateServices.push({ name: entity.name, type: entity.type || 'State/Database' }); } } // 4. Network Topology (if infra archetype) if (archetype.toLowerCase().includes('infra')) { if (lowerName.includes('vpc') || lowerName.includes('cidr') || lowerName.includes('subnet') || lowerName.includes('route') || lowerName.includes('nat')) { result.networkTopology.push({ name: entity.name, file: entity.file, type: 'Network Resource' }); } } } // Look for imports or dependencies that match state services if (graph.dependencies) { for (const dep of Object.keys(graph.dependencies)) { const lowerDep = dep.toLowerCase(); if (lowerDep.match(STATE_SERVICES_REGEX)) { if (!result.stateServices.some(s => s.name === dep)) { result.stateServices.push({ name: dep, type: 'External Dependency' }); } } } } // Look at package.json dependencies for state services try { const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8')); const deps = Object.keys(pkg.dependencies || {}); for (const dep of deps) { if (dep.match(STATE_SERVICES_REGEX)) { if (!result.stateServices.some(s => s.name === dep)) { result.stateServices.push({ name: dep, type: 'NPM Dependency' }); } } } } catch (e) { console.warn('Failed to parse package.json for state services:', e.message); } // 3. Deployment Patterns (CI/CD, GitOps) const ciFiles = files.filter(f => f.match(/(\.github\/workflows|jenkinsfile|\.gitlab-ci|argocd|kustomization|dockerfile|helm)/i)); for (const cf of ciFiles) { let type = 'CI/CD'; if (cf.toLowerCase().includes('argocd')) type = 'GitOps'; if (cf.toLowerCase().includes('helm')) type = 'Helm Chart'; result.deploymentPatterns.push({ file: cf, type }); } // Graceful fallback for config files if no config entities were found if (result.configs.length === 0) { for (const cf of configFiles.slice(0, 10)) { result.configs.push({ file: cf, type: 'Configuration File' }); } } return result; } module.exports = { extractDynamic };