From 497cdd1eb3b65c5acfffdaa3db683e03faef1cf1 Mon Sep 17 00:00:00 2001 From: LayerDynamics Date: Thu, 11 Jun 2026 15:41:07 -0500 Subject: [PATCH] docs(site): single-source metrics + MLX command paths; standardize terminology Centralize the figures and CLI invocations that were duplicated across the docs, to stop metric/CLI drift (code-review feedback). A build-time remark plugin (site/src/plugins/remark-metrics.mjs, registered in astro.config.mjs) substitutes {{metric.*}} tokens from src/data/metrics.mjs and {{script.*}} tokens from src/data/scripts.mjs across the narrative pages; an unresolved token fails the build (fail-loud). Rendered output is byte-identical to what users copy-paste, including the column-aligned quickstart block. Also: standardize the measured (0.914) vs sampled-z (0.934) validity distinction and the "measured-valid CAD" category noun; add DeepCAD-trained scope anchors to time-sensitive claims; fix a typo. Descriptive prose mentions of filenames are intentionally left literal (not duplicated snippets). Co-Authored-By: Claude Opus 4.8 (1M context) --- site/astro.config.mjs | 8 ++ .../how-neural-cad-generation-works.md | 5 +- site/src/content/docs/concepts/index.md | 2 +- .../the-reality-of-ai-cad-generation.md | 5 +- .../content/docs/get-started/quickstart.md | 12 +-- site/src/content/docs/index.mdx | 6 +- site/src/content/docs/ll_brepnet/overview.md | 2 +- site/src/content/docs/ll_gen/overview.md | 25 +++--- site/src/content/docs/ll_gen/usage.md | 12 +-- site/src/content/docs/ll_ocadr/overview.md | 4 +- site/src/content/docs/ll_stepnet/usage.md | 4 +- .../content/docs/tutorials/generate-cad.md | 16 ++-- site/src/data/metrics.mjs | 39 ++++++++++ site/src/data/scripts.mjs | 32 ++++++++ site/src/plugins/remark-metrics.mjs | 76 +++++++++++++++++++ 15 files changed, 204 insertions(+), 44 deletions(-) create mode 100644 site/src/data/metrics.mjs create mode 100644 site/src/data/scripts.mjs create mode 100644 site/src/plugins/remark-metrics.mjs diff --git a/site/astro.config.mjs b/site/astro.config.mjs index d7104cb..1513cb7 100644 --- a/site/astro.config.mjs +++ b/site/astro.config.mjs @@ -2,12 +2,20 @@ import { defineConfig } from 'astro/config'; import starlight from '@astrojs/starlight'; import starlightLinksValidator from 'starlight-links-validator'; +import remarkMetrics from './src/plugins/remark-metrics.mjs'; // https://astro.build/config // Deployed as a GitHub Pages project site: https://latticelabsai.github.io/ll_toolkit/ export default defineConfig({ site: 'https://latticelabsai.github.io', base: '/ll_toolkit/', + // Inject canonical model metrics (src/data/metrics.mjs) and MLX command paths + // (src/data/scripts.mjs) at build time via src/plugins/remark-metrics.mjs. + // @astrojs/mdx extends this markdown config by default, so the plugin applies + // to `.md` and `.mdx` alike. + markdown: { + remarkPlugins: [remarkMetrics], + }, integrations: [ starlight({ // Fails the build on any broken internal link (SPEC-2 FR-20). diff --git a/site/src/content/docs/concepts/how-neural-cad-generation-works.md b/site/src/content/docs/concepts/how-neural-cad-generation-works.md index 89ce14b..dc2bfea 100644 --- a/site/src/content/docs/concepts/how-neural-cad-generation-works.md +++ b/site/src/content/docs/concepts/how-neural-cad-generation-works.md @@ -78,8 +78,9 @@ This shows up concretely in [ll_gen](/ll_toolkit/ll_gen/overview/): a diffusion denoises **independent face grids** and sews them reaches **0** valid solids on the honest solid+volume gate — the sampled faces never mate. Re-targeting the same idea to diffuse a **construction-program** latent and decode it autoregressively (so the kernel -*builds* the solid) reaches **0.934** sampled-z validity, and the autoregressive command -model reaches **0.914** — both measured through the real kernel. These are the toolkit's +*builds* the solid) reaches **{{metric.ll_gen.latentDiffusion.sampledZValidity}}** sampled-z +validity, and the autoregressive command model reaches **{{metric.ll_gen.ar.validity}}** — +both measured through the real kernel. These are the toolkit's own numbers, not the literature figures above. ::: diff --git a/site/src/content/docs/concepts/index.md b/site/src/content/docs/concepts/index.md index a407b0c..4eab845 100644 --- a/site/src/content/docs/concepts/index.md +++ b/site/src/content/docs/concepts/index.md @@ -16,7 +16,7 @@ Text2CAD, and others) and describe what the *field* has achieved. They are kept distinct from **the toolkit's own measured results**, which are now real and reproducible — e.g. [ll_brepnet](/ll_toolkit/ll_brepnet/overview/) segmentation at test mIoU 0.828, and [ll_gen](/ll_toolkit/ll_gen/overview/)'s program-based generators -producing measured-valid CAD (0.914 / 0.934, gated on real non-degenerate solids). Where +producing measured-valid CAD ({{metric.ll_gen.ar.validity}} / {{metric.ll_gen.latentDiffusion.sampledZValidity}}, gated on real non-degenerate solids). Where a page shows a LatticeLabs number it is labeled as such; field numbers frame what is realistic, package pages report what this codebase outputs. diff --git a/site/src/content/docs/concepts/the-reality-of-ai-cad-generation.md b/site/src/content/docs/concepts/the-reality-of-ai-cad-generation.md index acd2107..c5ee9e2 100644 --- a/site/src/content/docs/concepts/the-reality-of-ai-cad-generation.md +++ b/site/src/content/docs/concepts/the-reality-of-ai-cad-generation.md @@ -77,8 +77,9 @@ These are architecturally unrelated and routinely confused: The toolkit now ships **trained** generators — and they confirm the thesis above. The [ll_gen](/ll_toolkit/ll_gen/overview/) generators that produce valid CAD are the ones that generate the **construction program** and execute it: an autoregressive command -model (**0.914** valid) and a latent diffusion over a program autoencoder (**0.934** -valid), both measured through the real kernel and gated on a non-degenerate solid. The +model (**{{metric.ll_gen.ar.validity}}** valid) and a latent diffusion over a program +autoencoder (**{{metric.ll_gen.latentDiffusion.sampledZValidity}}** sampled-z valid), both +measured through the real kernel and gated on a non-degenerate solid. The route that *doesn't* work is the one that generates raw B-rep faces to be sewn — its independently-sampled faces never mate, so honest validity is **0**. That is the "code → kernel → validate" lesson made concrete in this codebase: validity comes from diff --git a/site/src/content/docs/get-started/quickstart.md b/site/src/content/docs/get-started/quickstart.md index c26d110..5f22f12 100644 --- a/site/src/content/docs/get-started/quickstart.md +++ b/site/src/content/docs/get-started/quickstart.md @@ -69,7 +69,7 @@ embedding = encoder(token_ids, topology_data=topology) # [1, 1024] Several models now ship **trained** with reproducible, honest metrics: **ll_brepnet** (B-Rep segmentation, test mIoU 0.828), **ll_stepnet** (face-count classifier, val acc 0.976), **ll_ocadr** (geometry-grounded, 0.919 vs 0.313 shuffled), -and **ll_gen**'s program-based generators (valid CAD: AR 0.914 / latent diffusion 0.934). +and **ll_gen**'s program-based generators (valid CAD: AR {{metric.ll_gen.ar.validity}} / latent diffusion {{metric.ll_gen.latentDiffusion.sampledZValidity}}). The neural models train and run natively in **MLX on Apple Silicon** as well as PyTorch. Remaining task heads (e.g. ll_stepnet property prediction/QA) ship as architectures — train them on your data before relying on their outputs. @@ -81,11 +81,11 @@ Each neural package has an `mlx/` trainer that runs on Apple Silicon. The ones w existing PyTorch checkpoints convert the real weights and prove parity: ```bash -python ll_stepnet/mlx/train_classification_mlx.py --mode parity # acc 0.976, argmax 1.0 vs PyTorch -python ll_brepnet/mlx/train_brepnet_mlx.py --mode parity # mIoU parity vs PyTorch -python ll_gen/mlx/ar_generator_mlx.py --mode train # valid CAD generation 0.914 -python ll_gen/mlx/latent_diffusion_mlx.py --mode train # latent-diffusion generation 0.934 -python ll_ocadr/mlx/train_ocadr_mlx.py --mode train # geometry-grounded multimodal +python {{script.ll_stepnet.classification}} --mode parity # acc 0.976, argmax 1.0 vs PyTorch +python {{script.ll_brepnet.train}} --mode parity # mIoU parity vs PyTorch +python {{script.ll_gen.arGenerator}} --mode train # valid CAD generation {{metric.ll_gen.ar.validity}} +python {{script.ll_gen.latentDiffusion}} --mode train # latent-diffusion generation {{metric.ll_gen.latentDiffusion.sampledZValidity}} +python {{script.ll_ocadr.train}} --mode train # geometry-grounded multimodal ``` ## Where to go next diff --git a/site/src/content/docs/index.mdx b/site/src/content/docs/index.mdx index 240d1b7..4667963 100644 --- a/site/src/content/docs/index.mdx +++ b/site/src/content/docs/index.mdx @@ -59,7 +59,7 @@ what is planned. /> Roadmap. **ll_gen generation** now produces measured-valid CAD via the construction-program - route (autoregressive command generator + latent diffusion), gated on real - non-degenerate solids. + route (autoregressive command generator + latent diffusion), trained on the DeepCAD + distribution and gated on real non-degenerate solids. Still experimental: **vLLM serving** for ll_ocadr (the HF-native path works today). diff --git a/site/src/content/docs/ll_brepnet/overview.md b/site/src/content/docs/ll_brepnet/overview.md index 3216aac..20f1091 100644 --- a/site/src/content/docs/ll_brepnet/overview.md +++ b/site/src/content/docs/ll_brepnet/overview.md @@ -89,7 +89,7 @@ running on Apple Silicon (the conversion handles Conv `OIHW→OHWI`/`OIW→OWI` inference-mode BatchNorm running stats): ```bash -python ll_brepnet/mlx/train_brepnet_mlx.py --mode parity # convert real weights + verify +python {{script.ll_brepnet.train}} --mode parity # convert real weights + verify ``` :::note[Scope] diff --git a/site/src/content/docs/ll_gen/overview.md b/site/src/content/docs/ll_gen/overview.md index c913c31..281294c 100644 --- a/site/src/content/docs/ll_gen/overview.md +++ b/site/src/content/docs/ll_gen/overview.md @@ -1,6 +1,6 @@ --- title: ll_gen — Overview -description: Generation orchestration for CAD — neural propose, deterministic dispose in a sandbox. Ships trained generators that produce measured-valid CAD via the construction-program route. +description: Generation orchestration for CAD — neural propose, deterministic dispose in a sandbox. Ships DeepCAD-trained generators that produce measured-valid CAD via the construction-program route. sidebar: label: Overview order: 1 @@ -37,13 +37,15 @@ trained generators take this route and run natively in **MLX on Apple Silicon**: - **Autoregressive command generator** (`ll_gen/mlx/ar_generator_mlx.py`) — a causal transformer over the CAD command vocabulary, trained on ~38k real DeepCAD programs, - sampled token-by-token → executed. **Measured validity 0.914** (234/256), **104 - distinct** shapes. + sampled token-by-token → executed. **Measured validity {{metric.ll_gen.ar.validity}}** + ({{metric.ll_gen.ar.validFraction}}), **{{metric.ll_gen.ar.distinct}} distinct** shapes. - **Latent diffusion** (`ll_gen/mlx/latent_diffusion_mlx.py`) — diffuses the latent of - a program autoencoder and decodes autoregressively. **Sampled-z validity 0.934** - (239/256), **138 distinct**. The validity comes from the execution-respecting - decoder; the diffusion contributes the diverse latent prior (138 distinct vs a - predict-the-mean baseline's 14). + a program autoencoder and decodes autoregressively. **Sampled-z validity + {{metric.ll_gen.latentDiffusion.sampledZValidity}}** ({{metric.ll_gen.latentDiffusion.validFraction}}), + **{{metric.ll_gen.latentDiffusion.distinct}} distinct**. The validity comes from the + execution-respecting decoder; the diffusion contributes the diverse latent prior + ({{metric.ll_gen.latentDiffusion.distinct}} distinct vs a predict-the-mean baseline's + {{metric.ll_gen.latentDiffusion.baselineDistinct}}). Two earlier routes are superseded because of their *representation*, not their training: the command-VAE's parallel (non-autoregressive) decoder is primitive-limited @@ -64,10 +66,10 @@ is visible, not hidden. (`GenerationMetrics.is_valid_solid`.) ```bash # train + measure the autoregressive command generator (Apple Silicon / MLX) -python ll_gen/mlx/ar_generator_mlx.py --mode train +python {{script.ll_gen.arGenerator}} --mode train # train the latent-diffusion generator and measure sampled-z validity -python ll_gen/mlx/latent_diffusion_mlx.py --mode train +python {{script.ll_gen.latentDiffusion}} --mode train # the orchestration / RL training entry point python -m ll_gen.training.run --help @@ -78,8 +80,9 @@ python -m ll_gen.training.run --help :::tip[Generators trained; validity measured through the real kernel] The orchestration, dispose sandbox, verification, and RL loop run end-to-end, and ll_gen now ships **trained generators that produce measured-valid CAD** on the DeepCAD -distribution — the autoregressive command generator (0.914 valid) and the latent -diffusion (0.934 valid), each gated on real non-degenerate solids. Native-MLX trainers +distribution — the autoregressive command generator ({{metric.ll_gen.ar.validity}} valid) +and the latent diffusion ({{metric.ll_gen.latentDiffusion.sampledZValidity}} valid), each +gated on real non-degenerate solids. Native-MLX trainers run on Apple Silicon. Scope is stated honestly: these are trained on DeepCAD parametric command sequences (sketch + extrude), and validity is measured on that distribution. ::: diff --git a/site/src/content/docs/ll_gen/usage.md b/site/src/content/docs/ll_gen/usage.md index b1bfa55..29108d6 100644 --- a/site/src/content/docs/ll_gen/usage.md +++ b/site/src/content/docs/ll_gen/usage.md @@ -69,15 +69,15 @@ non-degenerate solid (closed solid with positive volume): ```bash # Autoregressive command generator — trained on real DeepCAD programs. -# Result: validity 0.914 (234/256), 104 distinct, non-degenerate. -python ll_gen/mlx/ar_generator_mlx.py --mode train +# Result: validity {{metric.ll_gen.ar.validity}} ({{metric.ll_gen.ar.validFraction}}), {{metric.ll_gen.ar.distinct}} distinct, non-degenerate. +python {{script.ll_gen.arGenerator}} --mode train # Latent diffusion over a program autoencoder. -# Result: sampled-z validity 0.934 (239/256), 138 distinct. -python ll_gen/mlx/latent_diffusion_mlx.py --mode train +# Result: sampled-z validity {{metric.ll_gen.latentDiffusion.sampledZValidity}} ({{metric.ll_gen.latentDiffusion.validFraction}}), {{metric.ll_gen.latentDiffusion.distinct}} distinct. +python {{script.ll_gen.latentDiffusion}} --mode train ``` -For the latent diffusion the headline metric is **sampled-z** validity +For the latent diffusion, the headline metric is **sampled-z** validity (noise → denoise → decode → execute), reported against a `z=0` predict-the-mean baseline so a diverse generator is distinguishable from one that repeats the mean shape. A faithful MLX port of the command-VAE (`python ll_gen/mlx/vae_mlx.py --mode parity`) @@ -120,7 +120,7 @@ per-epoch curve. :::note[Two generator generations — know which you're running] The **program-based** generators (`ar_generator_mlx.py`, `latent_diffusion_mlx.py`) are -**trained** and produce measured-valid CAD (0.914 / 0.934 valid). The **legacy** +**trained** and produce measured-valid CAD ({{metric.ll_gen.ar.validity}} / {{metric.ll_gen.latentDiffusion.sampledZValidity}} valid). The **legacy** neural generators reachable from the orchestrator (`vae`, `vqvae`, `diffusion` via the REINFORCE loop) are randomly initialized out of the box — their prior samples are mostly invalid until trained, and the raw-geometry diffusion is limited by representation diff --git a/site/src/content/docs/ll_ocadr/overview.md b/site/src/content/docs/ll_ocadr/overview.md index 2112e43..fec05d8 100644 --- a/site/src/content/docs/ll_ocadr/overview.md +++ b/site/src/content/docs/ll_ocadr/overview.md @@ -68,8 +68,8 @@ control of 0.313** (majority 0.374) — i.e. the model genuinely *reads the geom verbalizes it, rather than guessing from the text prior. ```bash -python ll_ocadr/mlx/faithful_tower_mlx.py --mode parity # prove the tower == real encoders -python ll_ocadr/mlx/train_ocadr_mlx.py --mode train # train encoder + projector + LoRA +python {{script.ll_ocadr.faithfulTower}} --mode parity # prove the tower == real encoders +python {{script.ll_ocadr.train}} --mode train # train encoder + projector + LoRA ``` ## Status diff --git a/site/src/content/docs/ll_stepnet/usage.md b/site/src/content/docs/ll_stepnet/usage.md index 4962b85..58a4f14 100644 --- a/site/src/content/docs/ll_stepnet/usage.md +++ b/site/src/content/docs/ll_stepnet/usage.md @@ -96,8 +96,8 @@ converts the real PyTorch checkpoint and **proves parity** (100% argmax agreemen identical 0.976 accuracy), and can also train the faithful architecture from scratch: ```bash -python ll_stepnet/mlx/train_classification_mlx.py --mode parity # convert + verify vs PyTorch -python ll_stepnet/mlx/train_classification_mlx.py --mode train # native-MLX training +python {{script.ll_stepnet.classification}} --mode parity # convert + verify vs PyTorch +python {{script.ll_stepnet.classification}} --mode train # native-MLX training ``` ## Generative models diff --git a/site/src/content/docs/tutorials/generate-cad.md b/site/src/content/docs/tutorials/generate-cad.md index 5881283..bfee576 100644 --- a/site/src/content/docs/tutorials/generate-cad.md +++ b/site/src/content/docs/tutorials/generate-cad.md @@ -15,7 +15,7 @@ This tutorial walks the **orchestrator + RL loop** with a from-scratch generator can see the propose→dispose loop and a before/after validity measurement end to end. For generators that already **produce valid CAD**, jump to [Generate valid CAD (trained, MLX)](#5-generate-valid-cad-trained-mlx) below — the -autoregressive command generator (0.914 valid) and latent diffusion (0.934 valid). Read +autoregressive command generator ({{metric.ll_gen.ar.validity}} valid) and latent diffusion ({{metric.ll_gen.latentDiffusion.sampledZValidity}} valid). Read [The reality of AI CAD generation](/ll_toolkit/concepts/the-reality-of-ai-cad-generation/) for why generating the program and executing it is the reliable route. ::: @@ -106,18 +106,18 @@ They train and run natively in MLX on Apple Silicon: ```bash # Autoregressive command generator: trains on real DeepCAD programs, then samples + executes. # Reports validity through the real kernel, gated on a non-degenerate solid. -python ll_gen/mlx/ar_generator_mlx.py --mode train -# -> validity 0.914 (234/256), distinct 104, non-degenerate +python {{script.ll_gen.arGenerator}} --mode train +# -> validity {{metric.ll_gen.ar.validity}} ({{metric.ll_gen.ar.validFraction}}), distinct {{metric.ll_gen.ar.distinct}}, non-degenerate # Latent diffusion over a program autoencoder: sample z -> decode -> execute. -python ll_gen/mlx/latent_diffusion_mlx.py --mode train -# -> sampled-z validity 0.934 (239/256), distinct 138 (vs a z=0 mean baseline: 14 distinct) +python {{script.ll_gen.latentDiffusion}} --mode train +# -> sampled-z validity {{metric.ll_gen.latentDiffusion.sampledZValidity}} ({{metric.ll_gen.latentDiffusion.validFraction}}), distinct {{metric.ll_gen.latentDiffusion.distinct}} (vs a z=0 mean baseline: {{metric.ll_gen.latentDiffusion.baselineDistinct}} distinct) ``` Both report `num_distinct` alongside validity, so a high rate from one repeated shape -(mode collapse) is visible. The latent-diffusion run prints **sampled-z** validity (noise -→ denoise → decode → execute) against a `z=0` predict-the-mean baseline — the comparison -that proves the diffusion adds diversity rather than repeating the mean shape. +(mode collapse) is visible. The latent-diffusion run prints **sampled-z** validity against +a `z=0` predict-the-mean baseline — see [ll_gen Usage](/ll_toolkit/ll_gen/usage/) for what +that metric means and why the baseline matters. ## Where to next diff --git a/site/src/data/metrics.mjs b/site/src/data/metrics.mjs new file mode 100644 index 0000000..b513e6f --- /dev/null +++ b/site/src/data/metrics.mjs @@ -0,0 +1,39 @@ +// Single source of truth for published model metrics cited across the docs. +// +// These are the headline numbers the narrative pages report. They are injected +// at build time by `src/plugins/remark-metrics.mjs`, which replaces +// `{{metric.}}` tokens in any `.md` / `.mdx` page with the value +// below. Change a number HERE and every page that cites it updates; an unknown +// token fails the build loudly (same fail-loud philosophy as scripts/gen_api.py). +// +// Provenance: the ll_gen figures are measured through the real OCC/CadQuery +// kernel by the native-MLX trainers, gated on a non-degenerate solid: +// - ar.* `python ll_gen/mlx/ar_generator_mlx.py --mode train` +// - latentDiffusion.* `python ll_gen/mlx/latent_diffusion_mlx.py --mode train` +// They are DeepCAD-trained results and change only on retrain. When they do, +// update this file (and only this file). + +export const metrics = { + ll_gen: { + // Autoregressive command generator: causal transformer over the CAD command + // vocabulary, sampled token-by-token then executed. Plain prior-sampling + // validity ("measured validity"). + ar: { + validity: '0.914', + validFraction: '234/256', + distinct: '104', + }, + // Latent diffusion over a program autoencoder: sample z -> decode + // autoregressively -> execute. The headline metric is SAMPLED-Z validity + // (noise -> denoise -> decode -> execute), reported against a z=0 + // predict-the-mean baseline whose diverse-shape count is `baselineDistinct`. + latentDiffusion: { + sampledZValidity: '0.934', + validFraction: '239/256', + distinct: '138', + baselineDistinct: '14', + }, + }, +}; + +export default metrics; diff --git a/site/src/data/scripts.mjs b/site/src/data/scripts.mjs new file mode 100644 index 0000000..0d87263 --- /dev/null +++ b/site/src/data/scripts.mjs @@ -0,0 +1,32 @@ +// Single source of truth for the MLX trainer script PATHS cited in runnable +// command snippets across the docs (e.g. `python ll_gen/mlx/ar_generator_mlx.py +// --mode train`). Injected at build time by `src/plugins/remark-metrics.mjs`, +// which replaces `{{script.}}` tokens with the value below. +// +// Only the script PATH is centralized — not the `python` prefix or the `--mode` +// flag. The path is the thing that drifts when a script is renamed or moved; the +// flags are page-contextual (train vs parity) and stay literal per snippet. +// Change a path HERE and every runnable command that references it updates. +// +// NOTE: descriptive prose mentions of these filenames (e.g. "the generator +// (`ll_gen/mlx/ar_generator_mlx.py`) …") are intentionally left literal — they +// are single contextual descriptions, not copy-pasted command snippets. + +export const scripts = { + ll_gen: { + arGenerator: 'll_gen/mlx/ar_generator_mlx.py', + latentDiffusion: 'll_gen/mlx/latent_diffusion_mlx.py', + }, + ll_brepnet: { + train: 'll_brepnet/mlx/train_brepnet_mlx.py', + }, + ll_ocadr: { + faithfulTower: 'll_ocadr/mlx/faithful_tower_mlx.py', + train: 'll_ocadr/mlx/train_ocadr_mlx.py', + }, + ll_stepnet: { + classification: 'll_stepnet/mlx/train_classification_mlx.py', + }, +}; + +export default scripts; diff --git a/site/src/plugins/remark-metrics.mjs b/site/src/plugins/remark-metrics.mjs new file mode 100644 index 0000000..289ad5c --- /dev/null +++ b/site/src/plugins/remark-metrics.mjs @@ -0,0 +1,76 @@ +// Build-time data injection for the docs. +// +// Replaces `{{.}}` tokens in Markdown/MDX with the +// canonical value from the matching data module: +// - `{{metric.…}}` -> src/data/metrics.mjs (published model numbers) +// - `{{script.…}}` -> src/data/scripts.mjs (MLX trainer script paths) +// +// Runs on the mdast (before syntax highlighting / mdast->hast), so substitutions +// apply to prose, inline code, fenced code blocks, AND MDX JSX string attributes +// (e.g. `description="..."`). +// +// The separator after the namespace is a dot, NOT a colon: Starlight enables +// remark-directive (for `:::note` asides), which parses an inline `:name` +// sequence as a text directive and would split a `{{ns:…}}` token across several +// text nodes before this plugin runs. A dot is inert to inline parsing, so the +// whole token survives in one text node and matches cleanly. +// +// Fail-loud: a token whose path does not resolve to a scalar throws and fails +// the build, so a typo can never ship as a literal `{{…}}`. + +import { metrics } from '../data/metrics.mjs'; +import { scripts } from '../data/scripts.mjs'; + +// Maps a token namespace to its data root and source file (for error messages). +const REGISTRY = { + metric: { root: metrics, source: 'src/data/metrics.mjs' }, + script: { root: scripts, source: 'src/data/scripts.mjs' }, +}; + +// Matches e.g. `{{metric.ll_gen.ar.validity}}` or `{{script.ll_gen.arGenerator}}` +// with optional surrounding whitespace. +const TOKEN_RE = /\{\{\s*(metric|script)\.([A-Za-z0-9_.]+?)\s*\}\}/g; + +function resolvePath(root, path) { + return path + .split('.') + .reduce((obj, key) => (obj == null ? undefined : obj[key]), root); +} + +function substitute(text, filePath) { + return text.replace(TOKEN_RE, (match, namespace, path) => { + const { root, source } = REGISTRY[namespace]; + const value = resolvePath(root, path); + if (value === undefined || value === null || typeof value === 'object') { + throw new Error( + `remark-metrics: unresolved token "${match}" ` + + `(namespace "${namespace}", path "${path}") in ${filePath ?? ''}. ` + + `Add it to ${source} or fix the token.`, + ); + } + return String(value); + }); +} + +// Recursive walk: unist-util-visit only traverses `children`, but MDX JSX +// attributes live on `node.attributes`, so we descend both. Any node carrying a +// string `value` (text, inlineCode, code, mdxJsxAttribute) is a substitution +// site; attribute-expression values are objects and are left untouched. +function walk(node, filePath) { + if (node == null || typeof node !== 'object') return; + if (typeof node.value === 'string') { + node.value = substitute(node.value, filePath); + } + if (Array.isArray(node.attributes)) { + for (const attr of node.attributes) walk(attr, filePath); + } + if (Array.isArray(node.children)) { + for (const child of node.children) walk(child, filePath); + } +} + +export default function remarkMetrics() { + return function transformer(tree, file) { + walk(tree, file?.path); + }; +}