#!/usr/bin/env node // Generates docs/complexity-map.html — interactive D3 treemap. // Area = LOC per file. Color = git change frequency (last 6 months). // Groups: frontend modules, API modules, services. import { readdirSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs'; import { join, relative, extname } from 'node:path'; import { execSync } from 'node:child_process'; const ROOT = new URL('..', import.meta.url).pathname.replace(/\/$/, ''); const SINCE = '6.months'; const CODE_EXT = new Set(['.ts', '.tsx', '.js', '.mjs', '.svelte', '.go', '.py']); const IGNORE = new Set([ 'node_modules', '.turbo', '.svelte-kit', 'dist', 'build', '.next', 'coverage', '__snapshots__', ]); const TARGETS = [ { label: 'web', root: 'apps/mana/apps/web/src/lib/modules' }, { label: 'api', root: 'apps/api/src/modules' }, { label: 'services', root: 'services' }, ]; function walk(dir) { const out = []; let entries; try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return out; } for (const e of entries) { if (IGNORE.has(e.name)) continue; const p = join(dir, e.name); if (e.isDirectory()) out.push(...walk(p)); else if (e.isFile() && CODE_EXT.has(extname(e.name))) out.push(p); } return out; } function loc(path) { try { return readFileSync(path, 'utf8').split('\n').length; } catch { return 0; } } // Batch git log across many files: one call per module (fast enough). function changeCountForFile(relPath) { try { const out = execSync(`git log --since=${SINCE} --pretty=format:%H -- "${relPath}" | wc -l`, { cwd: ROOT, }) .toString() .trim(); return Number(out) || 0; } catch { return 0; } } const tree = { name: 'mana', children: [] }; for (const t of TARGETS) { const group = { name: t.label, children: [] }; const rootAbs = join(ROOT, t.root); let modules; try { modules = readdirSync(rootAbs, { withFileTypes: true }); } catch { continue; } for (const m of modules) { if (!m.isDirectory() || IGNORE.has(m.name)) continue; const modAbs = join(rootAbs, m.name); const files = walk(modAbs); if (files.length === 0) continue; const modNode = { name: m.name, children: [] }; for (const f of files) { const l = loc(f); if (l === 0) continue; const rel = relative(ROOT, f); modNode.children.push({ name: f.split('/').slice(-2).join('/'), path: rel, value: l, changes: changeCountForFile(rel), }); } if (modNode.children.length > 0) group.children.push(modNode); } tree.children.push(group); } const html = ` Mana — Complexity Map

Mana Complexity Map

Area = LOC · Color = git changes (last ${SINCE}) coldhot
`; const outputs = [ join(ROOT, 'docs', 'complexity-map.html'), join(ROOT, 'apps/mana/apps/web/static/admin/complexity-map.html'), ]; for (const p of outputs) { mkdirSync(join(p, '..'), { recursive: true }); writeFileSync(p, html); console.log(`Wrote ${relative(ROOT, p)}`); }