Phase 7B, 7E, 7D: Contracts, Diagrams, Sysdoc
This commit is contained in:
146
sysdoc.js
Normal file
146
sysdoc.js
Normal file
@@ -0,0 +1,146 @@
|
||||
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 <snapshot.json> <srcRoot> <outDir> [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 };
|
||||
Reference in New Issue
Block a user