const fs = require('fs'); const path = require('path'); /** * Phase 7E: Diagram Generator * Auto-generates Mermaid diagrams from graph analysis outputs. */ /** * Generate a subsystem dependency diagram from the dependency matrix. * @param {object} subsystemMap - Result from buildSubsystems * @returns {string} Mermaid graph TD source */ function generateDependencyDiagram(subsystemMap) { const lines = ['graph TD']; const { subsystems, crossCutting, dependencyMatrix } = subsystemMap; // Define nodes for (const sub of subsystems) { const label = sub.name.charAt(0).toUpperCase() + sub.name.slice(1); if (sub.kind === 'cross-cutting') { lines.push(` ${sub.name}["${label} (shared)"]:::shared`); } else { lines.push(` ${sub.name}["${label}"]`); } } // Define edges (skip cross-cutting → cross-cutting to reduce noise) for (const [key, val] of Object.entries(dependencyMatrix)) { const [from, to] = key.split('→'); const weight = val.calls + val.imports; if (weight === 0) continue; // Skip edges between two cross-cutting subsystems if (crossCutting.includes(from) && crossCutting.includes(to)) continue; const label = weight > 5 ? `|${weight}|` : ''; lines.push(` ${from} -->${label} ${to}`); } // Style lines.push(' classDef shared fill:#f9f,stroke:#333,stroke-dasharray: 5 5'); return lines.join('\n'); } /** * Generate a sequence diagram from a flow trace. * @param {object} flowResult - Result from traceFlow * @returns {string} Mermaid sequenceDiagram source */ function generateFlowDiagram(flowResult) { const lines = ['sequenceDiagram']; // Participants in order for (const sub of flowResult.subsystemSequence) { const label = sub.charAt(0).toUpperCase() + sub.slice(1); lines.push(` participant ${sub} as ${label}`); } // Messages from flow steps let prevSub = null; for (const step of flowResult.flow) { if (step.crossedVia && prevSub && prevSub !== step.subsystem) { const funcName = step.entity.includes(':') ? step.entity.split(':')[1] : step.entity.split('/').pop(); lines.push(` ${prevSub}->>${step.subsystem}: ${funcName}()`); } prevSub = step.subsystem; } // Note cycles for (const cycle of flowResult.cyclesDetected) { const sub = null; // We'd need subsystem lookup, just note it const atFunc = cycle.at.includes(':') ? cycle.at.split(':')[1] : cycle.at; const backFunc = cycle.backEdgeTo.includes(':') ? cycle.backEdgeTo.split(':')[1] : cycle.backEdgeTo; lines.push(` Note right of ${flowResult.subsystemSequence[flowResult.subsystemSequence.length - 1]}: Cycle: ${atFunc} ↔ ${backFunc}`); } return lines.join('\n'); } /** * Generate a contract/class diagram from extracted contracts. * @param {Array} contracts - Array of contract objects with fields * @returns {string} Mermaid classDiagram source */ function generateContractDiagram(contracts) { const lines = ['classDiagram']; for (const c of contracts) { if (c.type === 'Interface' || c.type === 'TypeAlias') { lines.push(` class ${c.name} {`); if (c.fields) { for (const f of c.fields) { lines.push(` +${f.name}: ${f.type}`); } } lines.push(' }'); lines.push(` <<${c.type}>> ${c.name}`); if (c.extends) { for (const ext of c.extends) { lines.push(` ${ext} <|-- ${c.name}`); } } } else if (c.type === 'Enum') { lines.push(` class ${c.name} {`); lines.push(' <>'); if (c.members) { for (const m of c.members) { lines.push(` ${m}`); } } lines.push(' }'); } } return lines.join('\n'); } if (require.main === module) { const cmd = process.argv[2]; const inputPath = process.argv[3]; if (!cmd || !inputPath) { console.error('Usage: node diagrams.js '); process.exit(1); } const data = JSON.parse(fs.readFileSync(inputPath, 'utf8')); if (cmd === 'deps') { console.log(generateDependencyDiagram(data)); } else if (cmd === 'flow') { console.log(generateFlowDiagram(data)); } else if (cmd === 'contracts') { console.log(generateContractDiagram(data.contracts || data)); } else { console.error('Unknown command:', cmd); process.exit(1); } } module.exports = { generateDependencyDiagram, generateFlowDiagram, generateContractDiagram };