assets/build.mjs
assets/build.mjsBrowse 7 files
1,763 tokens
5,680 bytes
Token encoding: o200k_base
Snapshot 24fd22b
← Back to SKILL.md
1// Builds <outDir>/SYSTEM.md and <outDir>/atlas.html from data.mjs (same folder).2// Usage: bun <atlasDir>/build.mjs — outDir = parent of atlasDir by default, or META.outDir3import { readFileSync, writeFileSync } from 'node:fs';4import { dirname, join } from 'node:path';5import { fileURLToPath } from 'node:url';6import { META, DECISIONS, GROUPS, NODES, FLOWS, CH, HOW_HTML } from './data.mjs';7 8const here = dirname(fileURLToPath(import.meta.url));9const outDir = META.outDir ? join(here, META.outDir) : join(here, '..');10 11// ---------- shared helpers ----------12const Q = (c) => (typeof c === 'string' ? { q: c } : c);13const md = (s) =>14 String(s)15 .replace(/<code>(.*?)<\/code>/g, '`$1`')16 .replace(/<mark>(.*?)<\/mark>/g, '**$1**')17 .replace(/<em>(.*?)<\/em>/g, '_$1_')18 .replace(/<b>(.*?)<\/b>/g, '**$1**')19 .replace(/<\/p>\s*<p>/g, '\n\n')20 .replace(/<[^>]+>/g, '')21 .replace(/ /g, ' ')22 .replace(/&/g, '&')23 .replace(/</g, '<')24 .replace(/>/g, '>')25 .trim();26const groupTitle = Object.fromEntries(GROUPS.map((g) => [g.id, g.title]));27const cnt = { open: 0, res: 0 };28NODES.forEach((n) => (n.cond || []).map(Q).forEach((c) => (c.r || c.to ? cnt.res++ : cnt.open++)));29 30// ---------- SYSTEM.md ----------31function buildSystemMd() {32 const out = [];33 out.push(`# ${META.title} — System Definition`, '');34 out.push(META.intro, '');35 out.push(`_Question status: **${cnt.open} open · ${cnt.res} resolved**._`, '');36 out.push('## One paragraph', '', META.onePara, '');37 out.push('## Decisions locked', '', '| Axis | Decision | ADR |', '|---|---|---|');38 DECISIONS.forEach((d) => out.push(`| ${d.axis} | ${d.decision} | ${d.adr} |`));39 out.push('');40 out.push('## Cost model', '');41 META.costModel.forEach((l) => out.push(l));42 if (META.deepDive) out.push('## Deep dives', '', META.deepDive, '');43 out.push('## Reading order (the atlas chapters)', '');44 CH.forEach((c, i) => out.push(`${i + 1}. **${c.title}** — ${md(c.lede)}${c.reveal.length ? ` _(adds ${c.reveal.join(', ')})_` : ''}`));45 out.push('');46 out.push('## Structures', '');47 const index = [];48 for (const g of GROUPS) {49 out.push(`### ${g.title}${g.id === 'off' ? ' (designed for, not built)' : ''}`, '');50 for (const n of NODES.filter((n) => n.group === g.id)) {51 out.push(`#### ${n.code} · ${n.name}${n.ghost ? ' _(not switched on)_' : ''}`, '');52 out.push(`**In one line.** ${md(n.one)}`, '');53 out.push(`**What it does.** ${md(n.what)}`, '');54 out.push(`**How it's built.** ${md(n.how)}`, '');55 if (n.steps) {56 out.push('**Steps in execution.**', '');57 n.steps.forEach((s, i) => out.push(`${i + 1}. **${s[0]}** — ${s[1]}`));58 out.push('');59 }60 const cs = (n.cond || []).map(Q);61 if (cs.length) {62 out.push('**Questions.**', '');63 cs.forEach((c, i) => {64 const id = `Q-${n.code}${i + 1}`;65 out.push(c.r ? `- ~~**${id}** ${md(c.q)}~~ ✓ ${md(c.r)}` : c.to ? `- **${id}** ${md(c.q)} → _${md(c.to)}_` : `- **${id}** ${md(c.q)}`);66 index.push([id, n.code, c]);67 });68 out.push('');69 }70 }71 }72 out.push('## Flows (representative packets)', '', 'Payload shapes are what the design implies, not measured traffic.', '');73 for (const f of FLOWS) {74 out.push(`### ${f.name}`, '', '| # | From → To | Packet | Representative payload |', '|---|---|---|---|');75 f.hops.forEach((h, i) => out.push(`| ${i + 1} | ${h[0]} → ${h[1]} | ${h[2]} | \`${JSON.stringify(h[3]).replace(/\|/g, '\\|')}\` |`));76 out.push('');77 }78 out.push('## Questions — index', '', 'Reference by ID. ✓ resolved (with date) · otherwise open.', '');79 index.forEach(([id, code, c]) => out.push(c.r ? `- ~~**${id}**~~ (${code}) ✓ ${md(c.r)}` : `- **${id}** (${code}) ${md(c.q)}`));80 out.push('');81 if (META.platformGives || META.weOwn) out.push('## What the platform gives vs what we own', '', `**Platform gives:** ${META.platformGives||''}`, '', `**We own:** ${META.weOwn||''}`, '');82 if (META.filesystem) out.push('## Planned filesystem', '', '```', META.filesystem.trimEnd(), '```', '');83 out.push('## How this file is maintained', '', `Generated from \`${META.sourcePath||'atlas/data.mjs'}\` by \`${META.buildCmd||'bun atlas/build.mjs'}\`, which also builds the interactive atlas (\`atlas.html\`${META.artifactUrl?`, published at ${META.artifactUrl}`:''}). Edit the data file, rebuild, republish — never edit this file by hand.`, '');84 return out.join('\n');85}86 87// ---------- atlas.html ----------88function buildAtlasHtml() {89 const tpl = readFileSync(join(here, 'template.html'), 'utf8');90 const decisionsHtml = DECISIONS.map((d) => `<li><b>${d.axis}.</b> ${md(d.decision).replace(/\*\*(.*?)\*\*/g, '<b>$1</b>').replace(/`(.*?)`/g, '<code>$1</code>').replace(/\[(.*?)\]\((.*?)\)/g, '$1')}</li>`).join('');91 const data = [92 `const GROUPS = ${JSON.stringify(GROUPS)};`,93 `const NODES = ${JSON.stringify(NODES)};`,94 `const FLOWS = ${JSON.stringify(FLOWS)};`,95 `const CH = ${JSON.stringify(CH)};`,96 `const HOW_HTML = ${JSON.stringify(HOW_HTML)};`,97 `const DECISIONS_HTML = ${JSON.stringify(decisionsHtml)};`,98 ].join('\n');99 return tpl.replace('__TITLE__', META.title + ' Atlas').replace('/*__DATA__*/', data + `\nconst STATS = ${JSON.stringify(META.stats||[])};\nconst TITLE = ${JSON.stringify(META.title||'System')};`);100}101 102writeFileSync(join(outDir, 'SYSTEM.md'), buildSystemMd());103writeFileSync(join(outDir, 'atlas.html'), buildAtlasHtml());104console.log(`built SYSTEM.md + atlas.html · ${cnt.open} open · ${cnt.res} resolved · ${NODES.length} structures · ${DECISIONS.length} decisions`);105