const fs = require('fs'); const path = require('path'); const GraphStore = require('./graph.js'); const { buildSubsystems } = require('./subsystem.js'); const { extractAllContracts } = require('./contracts.js'); const { buildFlowIndex, traceFlow } = require('./flow.js'); const { generateDependencyDiagram, generateFlowDiagram, generateContractDiagram } = require('./diagrams.js'); /** * Phase 7D: Hierarchical Doc Generator * Orchestrates 7A, 7B, 7C, and 7E to generate a Divio-structured documentation site. */ function generateDocs(graph, srcRoot, outDir, opts = {}) { const entryPoints = opts.entryPoints || []; // 1. Build Subsystems (7A) const subs = buildSubsystems(graph, { srcDir: opts.srcDir || '/src/', minTraffic: opts.minTraffic || 3, crossCuttingThreshold: opts.crossCuttingThreshold || 0.6 }); // 2. Extract Contracts (7B) const contractsResult = extractAllContracts(subs, srcRoot); // 3. Trace Flows (7C) const flowIndex = buildFlowIndex(graph, subs); const flowResults = entryPoints.map(ep => traceFlow(ep, flowIndex)); // Initialize output directory structure (Divio) const dirs = [ 'reference/subsystems', 'reference/contracts', 'reference/modules', 'explanation', 'tutorials', 'how-to', 'diagrams' ]; for (const d of dirs) { fs.mkdirSync(path.join(outDir, d), { recursive: true }); } // Generate Reference: System Architecture const sysArchPath = path.join(outDir, 'reference/system-architecture.md'); const depDiag = generateDependencyDiagram(subs); const depDiagPath = 'diagrams/system-deps.mmd'; fs.writeFileSync(path.join(outDir, depDiagPath), depDiag); const sysArchContent = `# System Architecture ## Subsystems ${subs.subsystems.map(s => `- **${s.name}** (${s.kind}): ${s.entities.modules} modules, ${s.entities.functions} functions`).join('\n')} ## Cross-Cutting Concerns ${subs.crossCutting.map(c => `- **${c}**`).join('\n')} ## Dependency Map \`\`\`mermaid ${depDiag} \`\`\` `; fs.writeFileSync(sysArchPath, sysArchContent); // Generate Reference: Subsystem Docs for (const sub of subs.subsystems) { const subDocPath = path.join(outDir, `reference/subsystems/${sub.name}.md`); const subContracts = contractsResult.bySubsystem[sub.name] || []; let contractSection = ''; if (subContracts.length > 0) { const contractDiag = generateContractDiagram(subContracts); fs.writeFileSync(path.join(outDir, `diagrams/${sub.name}-contracts.mmd`), contractDiag); contractSection = `\n## Contracts\n\`\`\`mermaid\n${contractDiag}\n\`\`\`\n`; } const subContent = `# Subsystem: ${sub.name} **Kind:** ${sub.kind} **Files:** ${sub.files.length} ## Public Exports ${sub.publicExports.length > 0 ? sub.publicExports.map(e => `- \`${e}\``).join('\n') : '*None*'} ${contractSection} ## Files ${sub.files.map(f => `- \`${f}\``).join('\n')} `; fs.writeFileSync(subDocPath, subContent); } // Generate Reference: Contracts const contractDocPath = path.join(outDir, 'reference/contracts/index.md'); const allContractsDiag = generateContractDiagram(contractsResult.contracts); fs.writeFileSync(path.join(outDir, 'diagrams/all-contracts.mmd'), allContractsDiag); fs.writeFileSync(contractDocPath, `# System Contracts\n\n\`\`\`mermaid\n${allContractsDiag}\n\`\`\`\n`); // Generate Explanation: Data Flows const flowsPath = path.join(outDir, 'explanation/data-flows.md'); let flowsContent = '# Data Flows\n\n'; for (let i = 0; i < flowResults.length; i++) { const fr = flowResults[i]; if (fr.error) { flowsContent += `## Flow: ${fr.entryPoint}\n**Error:** ${fr.error}\n\n`; continue; } const flowDiag = generateFlowDiagram(fr); const diagName = `flow-${i}.mmd`; fs.writeFileSync(path.join(outDir, `diagrams/${diagName}`), flowDiag); flowsContent += `## Flow: ${fr.entryPoint}\n`; flowsContent += `**Subsystem Sequence:** ${fr.subsystemSequence.join(' → ')}\n\n`; flowsContent += `\`\`\`mermaid\n${flowDiag}\n\`\`\`\n\n`; } fs.writeFileSync(flowsPath, flowsContent); return { subsystems: subs.subsystems.length, contracts: contractsResult.contracts.length, flows: flowResults.length, outDir }; } if (require.main === module) { const snapshotPath = process.argv[2]; const srcRoot = process.argv[3]; const outDir = process.argv[4]; const entryPoints = process.argv.slice(5); if (!snapshotPath || !srcRoot || !outDir) { console.error('Usage: node sysdoc.js [entryPoint1] [entryPoint2] ...'); process.exit(1); } const graph = GraphStore.loadSnapshot(snapshotPath); const result = generateDocs(graph, srcRoot, outDir, { entryPoints }); console.log(`Generated docs in ${result.outDir}`); console.log(`- ${result.subsystems} subsystems`); console.log(`- ${result.contracts} contracts`); console.log(`- ${result.flows} flows`); } module.exports = { generateDocs };