From 429b55cabd5ba3bccf835fb6bdca8db73deb940e Mon Sep 17 00:00:00 2001 From: yashau Date: Tue, 4 Aug 2026 23:16:42 +0500 Subject: [PATCH] Add agent-readable docs discovery --- apps/docs/astro.config.mjs | 19 ++ apps/docs/eslint.config.mjs | 2 +- apps/docs/package.json | 9 +- apps/docs/public/robots.txt | 6 + apps/docs/remark-doc-links.mjs | 4 +- apps/docs/scripts/generate-agent-markdown.mjs | 47 +++++ apps/docs/scripts/verify-discovery.mjs | 61 ++++++ apps/docs/worker/index.ts | 57 ++++- apps/docs/wrangler.jsonc | 4 + docs/operations/releases.md | 24 +++ pnpm-lock.yaml | 194 ++++++++++++++++++ 11 files changed, 419 insertions(+), 8 deletions(-) create mode 100644 apps/docs/public/robots.txt create mode 100644 apps/docs/scripts/generate-agent-markdown.mjs create mode 100644 apps/docs/scripts/verify-discovery.mjs diff --git a/apps/docs/astro.config.mjs b/apps/docs/astro.config.mjs index f83e4bb6..ddd63054 100644 --- a/apps/docs/astro.config.mjs +++ b/apps/docs/astro.config.mjs @@ -2,6 +2,7 @@ import { defineConfig } from "astro/config"; import { unified } from "@astrojs/markdown-remark"; import starlight from "@astrojs/starlight"; +import starlightLlmsTxt from "starlight-llms-txt"; import remarkDocLinks from "./remark-doc-links.mjs"; import remarkMermaid from "./remark-mermaid.mjs"; @@ -72,6 +73,24 @@ export default defineConfig({ }, // Collapse the now-empty Starlight title panel left by the PageTitle override. customCss: ["./src/styles/docs.css"], + plugins: [ + starlightLlmsTxt({ + projectName: "Rakkr", + description: + "Documentation for Rakkr, a local-only, centrally managed Linux audio recording platform for reliable room recording.", + details: + "Use the public documentation sets below for installation, operation, architecture, and development guidance. Internal status ledgers and verification baselines are intentionally excluded.", + promote: ["index*", "getting-started/**", "how-to/**"], + demote: ["contributing/**"], + optionalLinks: [ + { + label: "Rakkr source repository", + url: "https://github.com/yashau/Rakkr", + description: "source code, issue tracking, and release history", + }, + ], + }), + ], social: [ { icon: "github", diff --git a/apps/docs/eslint.config.mjs b/apps/docs/eslint.config.mjs index 35820b5e..aa9822c1 100644 --- a/apps/docs/eslint.config.mjs +++ b/apps/docs/eslint.config.mjs @@ -4,7 +4,7 @@ import eslintPluginAstro from "eslint-plugin-astro"; export default [ // The Cloudflare Worker is TypeScript with Workers globals; it is typechecked // via worker/tsconfig.json and bundled by wrangler, not by this Astro lint. - { ignores: ["dist/**", ".astro/**", "worker/**"] }, + { ignores: ["dist/**", ".astro/**", ".wrangler/**", "worker/**"] }, js.configs.recommended, ...eslintPluginAstro.configs["flat/recommended"], ]; diff --git a/apps/docs/package.json b/apps/docs/package.json index 9f2be43b..9000819d 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "astro dev", - "build": "astro build", + "build": "astro build && node scripts/generate-agent-markdown.mjs && node scripts/verify-discovery.mjs", "preview": "astro preview", "check": "astro check", "lint": "eslint .", @@ -16,7 +16,8 @@ "@astrojs/markdown-remark": "^7.2.2", "@astrojs/starlight": "^0.41.6", "astro": "^7.1.6", - "mermaid": "^11.16.0" + "mermaid": "^11.16.0", + "starlight-llms-txt": "^0.11.0" }, "devDependencies": { "@astrojs/check": "^0.9.10", @@ -24,7 +25,11 @@ "@eslint/js": "^10.0.1", "eslint": "^10.8.0", "eslint-plugin-astro": "^3.1.0", + "remark-frontmatter": "^5.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", "typescript": "^6.0.3", + "unified": "^11.0.5", "wrangler": "^4.118.0" } } diff --git a/apps/docs/public/robots.txt b/apps/docs/public/robots.txt new file mode 100644 index 00000000..4bdc26dd --- /dev/null +++ b/apps/docs/public/robots.txt @@ -0,0 +1,6 @@ +User-agent: * +Allow: / + +Sitemap: https://docs.rakkr.org/sitemap-index.xml + +# Machine-readable documentation: https://docs.rakkr.org/llms.txt diff --git a/apps/docs/remark-doc-links.mjs b/apps/docs/remark-doc-links.mjs index 4e07be83..6c1814f5 100644 --- a/apps/docs/remark-doc-links.mjs +++ b/apps/docs/remark-doc-links.mjs @@ -12,11 +12,11 @@ const DOCS_ROOT = path.resolve(import.meta.dirname, "../../docs"); const REPO_ROOT = path.resolve(import.meta.dirname, "../.."); const GITHUB_BASE = "https://github.com/yashau/Rakkr"; -function isExcluded(relFromDocs) { +export function isExcluded(relFromDocs) { return relFromDocs === "RAKKR_SOURCE_OF_TRUTH.md" || relFromDocs.startsWith("internal/"); } -function toSlug(relFromDocs) { +export function toSlug(relFromDocs) { const noExt = relFromDocs.replace(/\.(md|mdx)$/i, ""); const normalized = noExt.replace(/(^|\/)readme$/i, "$1index"); const trimmed = normalized.replace(/\/index$/i, ""); diff --git a/apps/docs/scripts/generate-agent-markdown.mjs b/apps/docs/scripts/generate-agent-markdown.mjs new file mode 100644 index 00000000..53860e04 --- /dev/null +++ b/apps/docs/scripts/generate-agent-markdown.mjs @@ -0,0 +1,47 @@ +import { mkdir, readFile, readdir, writeFile } from "node:fs/promises"; +import path from "node:path"; +import remarkFrontmatter from "remark-frontmatter"; +import remarkParse from "remark-parse"; +import remarkStringify from "remark-stringify"; +import { unified } from "unified"; +import remarkDocLinks, { isExcluded, toSlug } from "../remark-doc-links.mjs"; + +const docsRoot = path.resolve(import.meta.dirname, "../../../docs"); +const distRoot = path.resolve(import.meta.dirname, "../dist"); + +const processor = unified() + .use(remarkParse) + .use(remarkFrontmatter, ["yaml"]) + .use(remarkDocLinks) + .use(remarkStringify, { bullet: "-", fences: true }); + +for (const sourcePath of await markdownFiles(docsRoot)) { + const relativePath = toPosix(path.relative(docsRoot, sourcePath)); + if (isExcluded(relativePath)) continue; + + const source = await readFile(sourcePath, "utf8"); + const markdown = String(await processor.process({ path: sourcePath, value: source })); + const outputPath = path.join(distRoot, toSlug(relativePath), "index.md"); + + await mkdir(path.dirname(outputPath), { recursive: true }); + await writeFile(outputPath, markdown, "utf8"); +} + +async function markdownFiles(directory) { + const paths = []; + + for (const entry of await readdir(directory, { withFileTypes: true })) { + const entryPath = path.join(directory, entry.name); + if (entry.isDirectory()) { + paths.push(...(await markdownFiles(entryPath))); + } else if (/\.(md|mdx)$/iu.test(entry.name)) { + paths.push(entryPath); + } + } + + return paths; +} + +function toPosix(value) { + return value.split(path.sep).join("/"); +} diff --git a/apps/docs/scripts/verify-discovery.mjs b/apps/docs/scripts/verify-discovery.mjs new file mode 100644 index 00000000..0ec5908e --- /dev/null +++ b/apps/docs/scripts/verify-discovery.mjs @@ -0,0 +1,61 @@ +import assert from "node:assert/strict"; +import { readFile, readdir } from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath, URL } from "node:url"; + +const dist = new URL("../dist/", import.meta.url); +const readDistFile = (path) => readFile(new URL(path, dist), "utf8"); + +const [robots, llms, llmsFull, llmsSmall, sitemapIndex, sitemap, pageMarkdown] = await Promise.all([ + readDistFile("robots.txt"), + readDistFile("llms.txt"), + readDistFile("llms-full.txt"), + readDistFile("llms-small.txt"), + readDistFile("sitemap-index.xml"), + readDistFile("sitemap-0.xml"), + readDistFile("architecture/overview/index.md"), +]); + +assert.match(robots, /^User-agent: \*$/mu); +assert.match(robots, /^Allow: \/$/mu); +assert.match(robots, /^Sitemap: https:\/\/docs\.rakkr\.org\/sitemap-index\.xml$/mu); +assert.match(robots, /https:\/\/docs\.rakkr\.org\/llms\.txt/u); + +assert.match(llms, /^# Rakkr$/mu); +assert.match(llms, /https:\/\/docs\.rakkr\.org\/llms-small\.txt/u); +assert.match(llms, /https:\/\/docs\.rakkr\.org\/llms-full\.txt/u); +assert.match(llmsFull, /^# Rakkr Documentation$/mu); +assert.match(llmsSmall, /^# Rakkr Documentation$/mu); + +assert.match(sitemapIndex, /https:\/\/docs\.rakkr\.org\/sitemap-0\.xml/u); +assert.match(sitemap, /https:\/\/docs\.rakkr\.org\/<\/loc>/u); +assert.match(sitemap, /https:\/\/docs\.rakkr\.org\/getting-started\/introduction\/<\/loc>/u); +assert.doesNotMatch(sitemap, /\/internal\//u); +assert.doesNotMatch(sitemap, /RAKKR_SOURCE_OF_TRUTH/u); + +assert.match(pageMarkdown, /^title: Architecture overview$/mu); +assert.match(pageMarkdown, /\[Controller API\]\(\/architecture\/controller-api\/\)/u); +assert.doesNotMatch(pageMarkdown, /\]\(controller-api\.md\)/u); + +const sitemapPageCount = [...sitemap.matchAll(//gu)].length; +const markdownPaths = await findNamedFiles(fileURLToPath(dist), "index.md"); +assert.equal(markdownPaths.length, sitemapPageCount); +assert.equal( + markdownPaths.some((file) => file.includes(`${path.sep}internal${path.sep}`)), + false, +); + +async function findNamedFiles(directory, fileName) { + const paths = []; + + for (const entry of await readdir(directory, { withFileTypes: true })) { + const entryPath = path.join(directory, entry.name); + if (entry.isDirectory()) { + paths.push(...(await findNamedFiles(entryPath, fileName))); + } else if (entry.name === fileName) { + paths.push(entryPath); + } + } + + return paths; +} diff --git a/apps/docs/worker/index.ts b/apps/docs/worker/index.ts index a1829e65..84515062 100644 --- a/apps/docs/worker/index.ts +++ b/apps/docs/worker/index.ts @@ -4,9 +4,10 @@ // // The Starlight build is uploaded as static assets (see `wrangler.jsonc`). // Cloudflare serves a matching asset before invoking this Worker, so the handler -// only runs for non-asset routes. It answers `/version.json` (so the deployed -// release is verifiable from the edge) and delegates everything else back to the -// static assets, including Starlight's generated `404.html`. +// runs first for canonical page paths so agents can negotiate Markdown, while +// CSS, JavaScript, images, and other static files still use asset-first routing. +// It also answers `/version.json` so the deployed release is verifiable from the +// edge and delegates everything else back to the static assets. interface Env { ASSETS: Fetcher; @@ -28,6 +29,56 @@ export default { ); } + if (acceptsMarkdown(request) && (request.method === "GET" || request.method === "HEAD")) { + const markdownResponse = await getPageMarkdown(request, env, url); + if (markdownResponse) return markdownResponse; + } + return env.ASSETS.fetch(request); }, } satisfies ExportedHandler; + +function acceptsMarkdown(request: Request): boolean { + const accept = request.headers.get("accept"); + if (!accept) return false; + + return accept.split(",").some((range) => { + const [mediaType, ...parameters] = range.split(";").map((part) => part.trim().toLowerCase()); + if (mediaType !== "text/markdown") return false; + + const quality = parameters.find((parameter) => parameter.startsWith("q=")); + return quality ? Number(quality.slice(2)) > 0 : true; + }); +} + +async function getPageMarkdown( + request: Request, + env: Env, + url: URL, +): Promise { + if (!url.pathname.endsWith("/")) return; + + const markdownUrl = new URL(`${url.pathname}index.md`, url); + const assetResponse = await env.ASSETS.fetch( + new Request(markdownUrl, { method: request.method, headers: request.headers }), + ); + if (!assetResponse.ok) return; + + const headers = new Headers(assetResponse.headers); + headers.set("content-type", "text/markdown; charset=utf-8"); + headers.set("content-signal", "ai-train=yes, search=yes, ai-input=yes"); + headers.set("vary", appendVary(headers.get("vary"), "Accept")); + + return new Response(request.method === "HEAD" ? null : assetResponse.body, { + status: assetResponse.status, + statusText: assetResponse.statusText, + headers, + }); +} + +function appendVary(current: string | null, value: string): string { + const values = current?.split(",").map((entry) => entry.trim()) ?? []; + if (values.includes("*")) return "*"; + if (!values.some((entry) => entry.toLowerCase() === value.toLowerCase())) values.push(value); + return values.join(", "); +} diff --git a/apps/docs/wrangler.jsonc b/apps/docs/wrangler.jsonc index 4b7281f3..0cbe715d 100644 --- a/apps/docs/wrangler.jsonc +++ b/apps/docs/wrangler.jsonc @@ -13,6 +13,10 @@ "assets": { "directory": "./dist", "binding": "ASSETS", + // Invoke the Worker only for canonical page URLs. This enables + // `Accept: text/markdown` negotiation without putting hashed CSS, JS, image, + // sitemap, robots, or llms.txt asset requests on the Worker invocation path. + "run_worker_first": ["/", "/*/"], }, // Overridden at deploy time with the release version: `wrangler deploy --var // RAKKR_DOCS_VERSION: --var RAKKR_DOCS_COMMIT:`. diff --git a/docs/operations/releases.md b/docs/operations/releases.md index 44adf2d5..0b3c3e84 100644 --- a/docs/operations/releases.md +++ b/docs/operations/releases.md @@ -75,6 +75,30 @@ Worker as a variable and is verifiable at `https://docs.rakkr.org/version.json`. There is no GitHub release — Cloudflare stores the deployment. Requires the `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID` repository secrets. +The docs build also publishes machine-readable discovery artifacts: + +- `/llms.txt` points agents to complete and abridged Markdown documentation sets; +- `/llms-full.txt` and `/llms-small.txt` are generated from the same public + Starlight content collection as the HTML site; +- each public page has a source-aligned Markdown asset at its trailing + `/index.md` path; +- `/robots.txt` allows crawling and advertises `/sitemap-index.xml`; +- Starlight generates `/sitemap-index.xml` and its sitemap shard from the public + routes. + +The docs Worker implements the **Markdown for Agents** content-negotiation +contract directly, so it also works on Cloudflare's Free plan: a request with +`Accept: text/markdown` returns the matching page's generated Markdown with +`Content-Type: text/markdown`, `Vary: Accept`, and the site's content-use signal. +Static scripts, styles, images, sitemaps, and discovery files remain on +Cloudflare's asset-first path. Verify both discovery paths after a docs release: + +```powershell +Invoke-WebRequest https://docs.rakkr.org/llms.txt +Invoke-WebRequest https://docs.rakkr.org/getting-started/introduction/ ` + -Headers @{ Accept = "text/markdown" } +``` + ### Controller (`controller-v*`) `release-controller.yml` builds and pushes versioned images to GHCR: diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index def42878..4365fc7d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -81,6 +81,9 @@ importers: mermaid: specifier: ^11.16.0 version: 11.16.0 + starlight-llms-txt: + specifier: ^0.11.0 + version: 0.11.0(@astrojs/markdown-satteri@0.3.5)(@astrojs/starlight@0.41.6(@astrojs/markdown-remark@7.2.2(supports-color@10.2.2))(astro@7.1.6(@astrojs/markdown-remark@7.2.2(supports-color@10.2.2))(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.1.2)(jiti@2.7.0)(tsx@4.23.5)(yaml@2.9.0))(supports-color@10.2.2)(typescript@6.0.3))(astro@7.1.6(@astrojs/markdown-remark@7.2.2(supports-color@10.2.2))(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.1.2)(jiti@2.7.0)(tsx@4.23.5)(yaml@2.9.0))(supports-color@10.2.2) devDependencies: '@astrojs/check': specifier: ^0.9.10 @@ -97,9 +100,21 @@ importers: eslint-plugin-astro: specifier: ^3.1.0 version: 3.1.0(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2) + remark-frontmatter: + specifier: ^5.0.0 + version: 5.0.0(supports-color@10.2.2) + remark-parse: + specifier: ^11.0.0 + version: 11.0.0(supports-color@10.2.2) + remark-stringify: + specifier: ^11.0.0 + version: 11.0.0 typescript: specifier: ^6.0.3 version: 6.0.3 + unified: + specifier: ^11.0.5 + version: 11.0.5 wrangler: specifier: ^4.118.0 version: 4.118.0(@cloudflare/workers-types@5.20260801.1) @@ -2229,6 +2244,9 @@ packages: '@tybys/wasm-util@0.10.3': resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + '@types/braces@3.0.5': + resolution: {integrity: sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w==} + '@types/d3-array@3.2.2': resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} @@ -2352,6 +2370,9 @@ packages: '@types/mdx@2.0.14': resolution: {integrity: sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==} + '@types/micromatch@4.0.10': + resolution: {integrity: sha512-5jOhFDElqr4DKTrTEbnW8DZ4Hz5LRUEmyrGpCMrD/NphYv3nUnaF08xmSLx1rGGnyEs/kFnhiw6dCgcDqMr5PQ==} + '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} @@ -2691,6 +2712,10 @@ packages: resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} engines: {node: 20 || >=22} + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -3311,6 +3336,9 @@ packages: fast-wrap-ansi@0.2.2: resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + fault@2.0.1: + resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -3324,6 +3352,10 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -3346,6 +3378,10 @@ packages: resolution: {integrity: sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==} engines: {node: '>=20'} + format@0.2.2: + resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} + engines: {node: '>=0.4.x'} + fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -3436,6 +3472,9 @@ packages: hast-util-to-jsx-runtime@2.3.6: resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + hast-util-to-mdast@10.1.2: + resolution: {integrity: sha512-FiCRI7NmOvM4y+f5w32jPRzcxDIz+PUqDwEqn1A+1q2cdp3B8Gx7aVrXORdOKjMNDQsD1ogOr896+0jJHW1EFQ==} + hast-util-to-parse5@8.0.1: resolution: {integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==} @@ -3528,6 +3567,10 @@ packages: is-hexadecimal@2.0.1: resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + is-plain-obj@4.1.0: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} @@ -3796,6 +3839,9 @@ packages: mdast-util-from-markdown@2.0.3: resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} + mdast-util-frontmatter@2.0.1: + resolution: {integrity: sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==} + mdast-util-gfm-autolink-literal@2.0.1: resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} @@ -3853,6 +3899,9 @@ packages: micromark-extension-directive@4.0.0: resolution: {integrity: sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==} + micromark-extension-frontmatter@2.0.0: + resolution: {integrity: sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==} + micromark-extension-gfm-autolink-literal@2.1.0: resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} @@ -3955,6 +4004,10 @@ packages: micromark@4.0.2: resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + miniflare@5.20260730.0-alpha: resolution: {integrity: sha512-8/dspSXDshP6nSkCpjKO7BYc2qZoYSXm7iM+QxY7qJyJpAB3onnQSaiu0cvKJlfuMGwULl55hG69FJCcCMXU1Q==} engines: {node: '>=22.0.0'} @@ -4265,6 +4318,9 @@ packages: rehype-format@5.0.1: resolution: {integrity: sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ==} + rehype-minify-whitespace@6.0.2: + resolution: {integrity: sha512-Zk0pyQ06A3Lyxhe9vGtOtzz3Z0+qZ5+7icZ/PL/2x1SHPbKao5oB/g/rlc6BCTajqBb33JcOe71Ye1oFsuYbnw==} + rehype-parse@9.0.1: resolution: {integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==} @@ -4274,6 +4330,9 @@ packages: rehype-recma@1.0.0: resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==} + rehype-remark@10.0.1: + resolution: {integrity: sha512-EmDndlb5NVwXGfUa4c9GPK+lXeItTilLhE6ADSaQuHr4JUlKw9MidzGzx4HpqZrNCt6vnHmEifXQiiA+CEnjYQ==} + rehype-stringify@10.0.1: resolution: {integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==} @@ -4283,6 +4342,9 @@ packages: remark-directive@4.0.0: resolution: {integrity: sha512-7sxn4RfF1o3izevPV1DheyGDD6X4c9hrGpfdUpm7uC++dqrnJxIZVkk7CoKqcLm0VUMAuOol7Mno3m6g8cfMuA==} + remark-frontmatter@5.0.0: + resolution: {integrity: sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==} + remark-gfm@4.0.1: resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} @@ -4440,6 +4502,12 @@ packages: space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + starlight-llms-txt@0.11.0: + resolution: {integrity: sha512-83TU5zPgJhL9fXIWAOWjQjyQ6EKocshEjJyA4zOJjOqu/oxQsrTpYGYxjHLyfZe4LRD52N9eCuykSkaIYwUyDw==} + peerDependencies: + '@astrojs/starlight': '>=0.41.0' + astro: ^7.0.0 + stream-replace-string@2.0.0: resolution: {integrity: sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==} @@ -4510,9 +4578,16 @@ packages: resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} engines: {node: ^20.0.0 || >=22.0.0} + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + trim-trailing-lines@2.1.0: + resolution: {integrity: sha512-5UR5Biq4VlVOtzqkm2AZlgvSlDJtME46uV0br0gENbwN4l5+mMKT4b9gJKqWtuL2zAIqajGJGuvbCbcAJUZqBg==} + trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} @@ -4594,6 +4669,9 @@ packages: unist-util-remove-position@5.0.0: resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} + unist-util-remove@4.0.0: + resolution: {integrity: sha512-b4gokeGId57UVRX/eVKej5gXqGlc9+trkORhFJpu9raqZkZhU0zm8Doi05+HaiBsMEIJowL+2WtQ5ItjsngPXg==} + unist-util-stringify-position@4.0.0: resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} @@ -6532,6 +6610,8 @@ snapshots: tslib: 2.8.1 optional: true + '@types/braces@3.0.5': {} + '@types/d3-array@3.2.2': {} '@types/d3-axis@3.0.6': @@ -6677,6 +6757,10 @@ snapshots: '@types/mdx@2.0.14': {} + '@types/micromatch@4.0.10': + dependencies: + '@types/braces': 3.0.5 + '@types/ms@2.1.0': {} '@types/nlcst@2.0.3': @@ -7044,6 +7128,10 @@ snapshots: dependencies: balanced-match: 4.0.4 + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + buffer-from@1.1.2: {} ccount@2.0.1: {} @@ -7678,6 +7766,10 @@ snapshots: dependencies: fast-string-width: 3.0.2 + fault@2.0.1: + dependencies: + format: 0.2.2 + fdir@6.5.0(picomatch@4.0.5): optionalDependencies: picomatch: 4.0.5 @@ -7686,6 +7778,10 @@ snapshots: dependencies: flat-cache: 4.0.1 + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -7708,6 +7804,8 @@ snapshots: dependencies: tiny-inflate: 1.0.3 + format@0.2.2: {} + fsevents@2.3.2: optional: true @@ -7906,6 +8004,23 @@ snapshots: transitivePeerDependencies: - supports-color + hast-util-to-mdast@10.1.2: + dependencies: + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.3 + hast-util-phrasing: 3.0.1 + hast-util-to-html: 9.0.5 + hast-util-to-text: 4.0.2 + hast-util-whitespace: 3.0.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-hast: 13.2.1 + mdast-util-to-string: 4.0.0 + rehype-minify-whitespace: 6.0.2 + trim-trailing-lines: 2.1.0 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + hast-util-to-parse5@8.0.1: dependencies: '@types/hast': 3.0.5 @@ -7990,6 +8105,8 @@ snapshots: is-hexadecimal@2.0.1: {} + is-number@7.0.0: {} + is-plain-obj@4.1.0: {} isbot@5.2.1: {} @@ -8215,6 +8332,17 @@ snapshots: transitivePeerDependencies: - supports-color + mdast-util-frontmatter@2.0.1(supports-color@10.2.2): + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + escape-string-regexp: 5.0.0 + mdast-util-from-markdown: 2.0.3(supports-color@10.2.2) + mdast-util-to-markdown: 2.1.2 + micromark-extension-frontmatter: 2.0.0 + transitivePeerDependencies: + - supports-color + mdast-util-gfm-autolink-literal@2.0.1: dependencies: '@types/mdast': 4.0.4 @@ -8411,6 +8539,13 @@ snapshots: micromark-util-types: 2.0.2 parse-entities: 4.0.2 + micromark-extension-frontmatter@2.0.0: + dependencies: + fault: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + micromark-extension-gfm-autolink-literal@2.1.0: dependencies: micromark-util-character: 2.1.1 @@ -8656,6 +8791,11 @@ snapshots: transitivePeerDependencies: - supports-color + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + miniflare@5.20260730.0-alpha: dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -8991,6 +9131,11 @@ snapshots: '@types/hast': 3.0.5 hast-util-format: 1.1.0 + rehype-minify-whitespace@6.0.2: + dependencies: + '@types/hast': 3.0.5 + hast-util-minify-whitespace: 1.0.1 + rehype-parse@9.0.1: dependencies: '@types/hast': 3.0.5 @@ -9011,6 +9156,14 @@ snapshots: transitivePeerDependencies: - supports-color + rehype-remark@10.0.1: + dependencies: + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + hast-util-to-mdast: 10.1.2 + unified: 11.0.5 + vfile: 6.0.3 + rehype-stringify@10.0.1: dependencies: '@types/hast': 3.0.5 @@ -9033,6 +9186,15 @@ snapshots: transitivePeerDependencies: - supports-color + remark-frontmatter@5.0.0(supports-color@10.2.2): + dependencies: + '@types/mdast': 4.0.4 + mdast-util-frontmatter: 2.0.1(supports-color@10.2.2) + micromark-extension-frontmatter: 2.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + remark-gfm@4.0.1(supports-color@10.2.2): dependencies: '@types/mdast': 4.0.4 @@ -9294,6 +9456,26 @@ snapshots: space-separated-tokens@2.0.2: {} + starlight-llms-txt@0.11.0(@astrojs/markdown-satteri@0.3.5)(@astrojs/starlight@0.41.6(@astrojs/markdown-remark@7.2.2(supports-color@10.2.2))(astro@7.1.6(@astrojs/markdown-remark@7.2.2(supports-color@10.2.2))(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.1.2)(jiti@2.7.0)(tsx@4.23.5)(yaml@2.9.0))(supports-color@10.2.2)(typescript@6.0.3))(astro@7.1.6(@astrojs/markdown-remark@7.2.2(supports-color@10.2.2))(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.1.2)(jiti@2.7.0)(tsx@4.23.5)(yaml@2.9.0))(supports-color@10.2.2): + dependencies: + '@astrojs/mdx': 7.0.5(@astrojs/markdown-satteri@0.3.5)(astro@7.1.6(@astrojs/markdown-remark@7.2.2(supports-color@10.2.2))(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.1.2)(jiti@2.7.0)(tsx@4.23.5)(yaml@2.9.0))(supports-color@10.2.2) + '@astrojs/starlight': 0.41.6(@astrojs/markdown-remark@7.2.2(supports-color@10.2.2))(astro@7.1.6(@astrojs/markdown-remark@7.2.2(supports-color@10.2.2))(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.1.2)(jiti@2.7.0)(tsx@4.23.5)(yaml@2.9.0))(supports-color@10.2.2)(typescript@6.0.3) + '@types/hast': 3.0.5 + '@types/micromatch': 4.0.10 + astro: 7.1.6(@astrojs/markdown-remark@7.2.2(supports-color@10.2.2))(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.1.2)(jiti@2.7.0)(tsx@4.23.5)(yaml@2.9.0) + github-slugger: 2.0.0 + hast-util-select: 6.0.4 + micromatch: 4.0.8 + rehype-parse: 9.0.1 + rehype-remark: 10.0.1 + remark-gfm: 4.0.1(supports-color@10.2.2) + remark-stringify: 11.0.0 + unified: 11.0.5 + unist-util-remove: 4.0.0 + transitivePeerDependencies: + - '@astrojs/markdown-satteri' + - supports-color + stream-replace-string@2.0.0: {} string-width@7.2.0: @@ -9361,8 +9543,14 @@ snapshots: tinypool@2.1.0: {} + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + trim-lines@3.0.1: {} + trim-trailing-lines@2.1.0: {} + trough@2.2.0: {} ts-dedent@2.3.0: {} @@ -9469,6 +9657,12 @@ snapshots: '@types/unist': 3.0.3 unist-util-visit: 5.1.0 + unist-util-remove@4.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + unist-util-stringify-position@4.0.0: dependencies: '@types/unist': 3.0.3