deviant/scripts/build-snapshot-index.mjs
walerikus 6ea6279d13 docs(snapshots): backfill domain/summary/tags frontmatter + enhance index
Phase 1 of the docs-by-domain reorg. Every snapshot now carries YAML
frontmatter (domain + summary + tags); the auto-guess was hand-corrected
against the full index (T6/interchain -> cross-chain, CDP/MBA/stablecoin
-> fiatm, CitizenToken/Ballot -> governance, etc.). No file renamed/moved.

build-snapshot-index.mjs now reads the frontmatter and emits a greppable
INDEX.md: a top Quick-find table (No | domain | tags | summary) plus
per-domain sections with summary + tags. 224/224 tagged, 8 domains, zero
uncategorised.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 20:12:24 +02:00

116 lines
6.3 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
// build-snapshot-index.mjs — regenerate snapshots/INDEX.md from snapshot frontmatter.
//
// Each snapshot carries YAML frontmatter:
// ---
// domain: wallet|asm|fiatm|cross-chain|games-tote|governance|onramp|infra
// summary: "one concise line"
// tags: comma, separated, keywords (optional)
// ---
// Domain source, in priority order:
// 1. the explicit `domain:` tag (the durable path — every snapshot has one);
// 2. else keyword-bucketing of the filename slug (fallback for any stray
// untagged file — add a tag + rerun to fix it).
//
// Usage: node scripts/build-snapshot-index.mjs (run from repo root)
// The index is a DERIVED artifact — never hand-edit it; edit the tags + rerun.
import { readdirSync, readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
const DIR = 'snapshots';
// Ordered domain buckets. `key` is the value the `domain:` tag must use.
// `kw` is the fallback slug-keyword bucketing for any untagged file.
const DOMAINS = [
{ key: 'wallet', title: 'Wallet UI (shells/app)', kw: ['wallet', 'shell', 'cosmogenesis', 'keystore', 'agora', 'artemision', 'hero', 'send', 'receive', 'sweep', 'pwa', 'ui', 'apps-tab', 'quickaction', 'onboarding', 'unlock', 'sign-message', 'connector', 'walletconnect', 'wizardconnect', 'cashconnect'] },
{ key: 'asm', title: 'ASM / Orbital Swap / Potidaea / Anthemus', kw: ['asm', 'swap', 'orbital', 'potidaea', 'anthemus', 'claim', 'reservoir', 'covenant', 'olympia', 'listing', 'sharepair', 'cauldron', 'clob', 'limit-order'] },
{ key: 'fiatm', title: 'FIATm / BCHm / MBA (backed currency)', kw: ['fiatm', 'bchm', 'mba', 'dea', 'dra', 'stablecoin', 'usdm', 'eurm', 'gbpm', 'jpym', 'inrm', 'arsm', 'trym', 'oracle', 'controller', 'dart', 'metal', 'dusd', 'paryon', 'peg', 'catalytic', 'backtest', 'elevation', 'equilibrium', 'redeem'] },
{ key: 'cross-chain', title: 'Cross-chain / interchain hub / T6', kw: ['cross-chain', 'crosschain', 'interchain', 'wrap', 'bridge', 'btc-', 'tron', 'ttrx', 'sepolia', 'deposit', 'lock-and-mint', 't6', 'htlc', 'ln-'] },
{ key: 'games-tote', title: 'Games / Tote / AnyHedge / Pari-mutuel', kw: ['tote', 'anyhedge', 'pari', 'prediction', 'football', 'game', 'checkers', 'event', 'oracle-tote', 'escrow'] },
{ key: 'governance', title: 'Governance (Athenae Politeia)', kw: ['voting', 'vote', 'athenae', 'politeia', 'ballot', 'citizen', 'complaint', 'application-covenant', 'sortition', 'plutocrat', 'elder', 'endorsement'] },
{ key: 'onramp', title: 'On-ramp / fiat rails', kw: ['onramp', 'on-ramp', 'ramp', 'provider'] },
{ key: 'infra', title: 'Infra / build / deploy / tooling', kw: ['deploy', 'beta', 'native', 'cli', 'test', 'consolidat', 'script', 'dashboard', 'registry', 'verify', 'migrat', 'refactor', 'adr', 'spec'] },
];
const MISC = { key: 'misc', title: 'Uncategorised (add a `domain:` tag)' };
const TITLE_OF = new Map([...DOMAINS, MISC].map((d) => [d.key, d.title]));
function frontmatter(text) {
const m = text.match(/^?---\r?\n([\s\S]*?)\r?\n---/);
if (!m) return {};
const body = m[1];
const domain = body.match(/^\s*domain:\s*([a-z0-9-]+)\s*$/im)?.[1]?.toLowerCase() ?? null;
let summary = body.match(/^\s*summary:\s*(.+?)\s*$/im)?.[1] ?? null;
if (summary) summary = summary.replace(/^"(.*)"$/s, '$1').replace(/\\"/g, '"').replace(/\\\\/g, '\\');
let tags = body.match(/^\s*tags:\s*(.*?)\s*$/im)?.[1] ?? '';
const tagList = tags.split(',').map((t) => t.trim()).filter(Boolean);
return { domain, summary, tags: tagList };
}
function h1(text, file) {
const m = text.match(/^#\s+(.+)$/m);
return (m ? m[1] : file.replace(/\.md$/, '')).trim().replace(/^Snapshot\s+\S+\s*[:—-]\s+/i, '');
}
function bucketOf(domain, file) {
if (domain && (TITLE_OF.has(domain))) return domain;
const slug = file.toLowerCase();
for (const d of DOMAINS) if (d.kw.some((k) => slug.includes(k))) return d.key;
return MISC.key;
}
const numOf = (file) => (file.match(/^(\d+)/)?.[1] ?? '');
const esc = (s) => s.replace(/\|/g, '\\|');
const files = readdirSync(DIR).filter((f) => f.endsWith('.md') && f !== 'INDEX.md').sort();
const groups = new Map([...DOMAINS, MISC].map((d) => [d.key, []]));
const all = [];
let tagged = 0;
for (const f of files) {
const text = readFileSync(join(DIR, f), 'utf8');
const fm = frontmatter(text);
if (fm.domain) tagged++;
const rec = {
file: f,
stem: f.replace(/\.md$/, ''),
num: numOf(f),
summary: fm.summary || h1(text, f),
tags: fm.tags || [],
domain: bucketOf(fm.domain, f),
};
groups.get(rec.domain).push(rec);
all.push(rec);
}
const L = [
'# Snapshots — index by domain',
'',
'> **Generated file — do not hand-edit.** Rebuild with `node scripts/build-snapshot-index.mjs`.',
'> Snapshots stay a flat, chronological changelog; this index is the domain view over them.',
'>',
'> **To place a snapshot:** set `domain: <wallet|asm|fiatm|cross-chain|games-tote|governance|onramp|infra>`',
'> (plus `summary:` and optional `tags:`) in its YAML frontmatter, then rerun this script.',
'> Untagged files fall back to filename-keyword bucketing.',
'>',
'> Duplicate snapshot numbers exist (0018, 0019, 0022, 0027, 0053, 0054, 0057, 0062, 0083, 0084,',
'> 0144, 0145, 0153, 0164) — they are distinct files, disambiguated below by slug.',
'',
`_${files.length} snapshots · ${tagged} tagged · ${[...groups].filter(([, v]) => v.length).length} domains · regenerate after adding one._`,
'',
'## Quick-find — all snapshots (Ctrl-F by №, domain, tag, or word)',
'',
'| № | domain | tags | summary |',
'| --- | --- | --- | --- |',
];
for (const r of all) {
L.push(`| [${r.num}](${r.file}) | ${r.domain} | ${esc(r.tags.join(' '))} | ${esc(r.summary)} |`);
}
L.push('');
for (const d of [...DOMAINS, MISC]) {
const items = groups.get(d.key);
if (!items.length) continue;
L.push(`## ${d.title} \`(${items.length})\``, '');
for (const r of items) {
const tags = r.tags.length ? ` · _${r.tags.join(', ')}_` : '';
L.push(`- [${r.stem}](${r.file}) — ${r.summary}${tags}`);
}
L.push('');
}
writeFileSync(join(DIR, 'INDEX.md'), L.join('\n'));
console.log(`Wrote ${DIR}/INDEX.md — ${files.length} snapshots (${tagged} tagged) across ${[...groups].filter(([, v]) => v.length).length} groups.`);