Files
dev-intel-v2/diagrams.js

161 lines
5.2 KiB
JavaScript
Raw Normal View History

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) {
// Sanitize name for Mermaid (no spaces, parens, special chars)
const safeName = c.name.replace(/[^a-zA-Z0-9_]/g, '_').replace(/_+/g, '_').replace(/^_|_$/g, '');
if (!safeName) continue;
if (c.type === 'Interface' || c.type === 'TypeAlias') {
lines.push(` class ${safeName} {`);
if (c.fields) {
for (const f of c.fields) {
lines.push(` +${f.name}: ${f.type}`);
}
}
lines.push(' }');
lines.push(` <<${c.type}>> ${safeName}`);
if (c.extends) {
for (const ext of c.extends) {
const safeExt = ext.replace(/[^a-zA-Z0-9_]/g, '_');
lines.push(` ${safeExt} <|-- ${safeName}`);
}
}
} else if (c.type === 'Enum') {
lines.push(` class ${safeName} {`);
lines.push(' <<enumeration>>');
if (c.members) {
for (const m of c.members) {
lines.push(` ${m}`);
}
}
lines.push(' }');
} else if (c.type === 'HelmValues' || c.type === 'HelmServices' || c.type === 'HelmWorkloads' || c.type === 'HelmDependencies') {
lines.push(` class ${safeName} {`);
lines.push(` <<${c.type}>>`);
if (c.fields) {
for (const f of c.fields) {
const safeField = f.name.replace(/[^a-zA-Z0-9_:.-]/g, '_');
const safeType = (f.type || 'unknown').replace(/[^a-zA-Z0-9_-]/g, '_');
lines.push(` +${safeField}: ${safeType}`);
}
}
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 <deps|flow|contracts> <input.json>');
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 };