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);
+ };
+}