blume 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (454) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +110 -0
  3. package/README.md +2 -2
  4. package/dist/cli/{chunk-f2972sbt.js → chunk-00gs3wqs.js} +1 -1
  5. package/dist/cli/{chunk-fa25z98p.js → chunk-273ygyr4.js} +4 -3
  6. package/dist/cli/chunk-273ygyr4.js.map +11 -0
  7. package/dist/cli/{chunk-pat2zzwc.js → chunk-3yce002v.js} +21 -8
  8. package/dist/cli/{chunk-pat2zzwc.js.map → chunk-3yce002v.js.map} +4 -4
  9. package/dist/cli/{chunk-d1tadaw7.js → chunk-4e9b9ra6.js} +17 -7
  10. package/dist/cli/chunk-4e9b9ra6.js.map +10 -0
  11. package/dist/cli/{chunk-zg2gtj10.js → chunk-5r8g91qn.js} +352 -676
  12. package/dist/cli/chunk-5r8g91qn.js.map +34 -0
  13. package/dist/cli/{chunk-11j0384y.js → chunk-6dtt0zfn.js} +14 -14
  14. package/dist/cli/{chunk-11j0384y.js.map → chunk-6dtt0zfn.js.map} +3 -3
  15. package/dist/cli/{chunk-bw22s759.js → chunk-6k4ftwze.js} +38 -19
  16. package/dist/cli/{chunk-bw22s759.js.map → chunk-6k4ftwze.js.map} +4 -4
  17. package/dist/cli/{chunk-sqn5t4q0.js → chunk-7mbqtmgb.js} +16 -7
  18. package/dist/cli/chunk-7mbqtmgb.js.map +10 -0
  19. package/dist/cli/{chunk-79njf86q.js → chunk-7vtckvaw.js} +21 -19
  20. package/dist/cli/chunk-7vtckvaw.js.map +11 -0
  21. package/dist/cli/{chunk-fh5hj5jt.js → chunk-8g8ytmgx.js} +17 -18
  22. package/dist/cli/{chunk-fh5hj5jt.js.map → chunk-8g8ytmgx.js.map} +2 -2
  23. package/dist/cli/{chunk-1w8dp3qb.js → chunk-91ws1n6j.js} +18 -15
  24. package/dist/cli/chunk-91ws1n6j.js.map +10 -0
  25. package/dist/cli/{chunk-7ez8ny0t.js → chunk-bbnwccaz.js} +2 -2
  26. package/dist/cli/{chunk-5a2z0198.js → chunk-bfwp9vp6.js} +18 -10
  27. package/dist/cli/chunk-bfwp9vp6.js.map +10 -0
  28. package/dist/cli/{chunk-ernrthtr.js → chunk-d1v5rhy0.js} +14 -15
  29. package/dist/cli/{chunk-ernrthtr.js.map → chunk-d1v5rhy0.js.map} +2 -2
  30. package/dist/cli/{chunk-a9kptbw5.js → chunk-ddndchfr.js} +21 -6
  31. package/dist/cli/chunk-ddndchfr.js.map +14 -0
  32. package/dist/cli/{chunk-zxccj738.js → chunk-esh98wmb.js} +1 -1
  33. package/dist/cli/{chunk-88cpgt6h.js → chunk-fsmrqk8a.js} +1 -1
  34. package/dist/cli/{chunk-b5aj94ah.js → chunk-g698a744.js} +5 -5
  35. package/dist/cli/{chunk-jts8mvcz.js → chunk-gs7r695n.js} +9 -3
  36. package/dist/cli/{chunk-jts8mvcz.js.map → chunk-gs7r695n.js.map} +3 -3
  37. package/dist/cli/{chunk-41za066z.js → chunk-h2ez8dzb.js} +4 -4
  38. package/dist/cli/{chunk-by2290sx.js → chunk-h7k3nq3v.js} +2 -2
  39. package/dist/cli/{chunk-j8mw0za6.js → chunk-hqp2ajnh.js} +252 -123
  40. package/dist/cli/chunk-hqp2ajnh.js.map +35 -0
  41. package/dist/cli/{chunk-mnqj32sj.js → chunk-hr8ne106.js} +119 -50
  42. package/dist/cli/chunk-hr8ne106.js.map +13 -0
  43. package/dist/cli/{chunk-mwt1k8n7.js → chunk-j85scx15.js} +74 -30
  44. package/dist/cli/chunk-j85scx15.js.map +10 -0
  45. package/dist/cli/{chunk-z01ze5c1.js → chunk-k7pj68a8.js} +63 -22
  46. package/dist/cli/chunk-k7pj68a8.js.map +11 -0
  47. package/dist/cli/{chunk-2q1dwty4.js → chunk-mqc662a6.js} +8 -8
  48. package/dist/cli/{chunk-2q1dwty4.js.map → chunk-mqc662a6.js.map} +4 -4
  49. package/dist/cli/{chunk-y3e45rc8.js → chunk-n1yg3tj3.js} +4 -4
  50. package/dist/cli/{chunk-bctazmbk.js → chunk-nfcyttvj.js} +17 -6
  51. package/dist/cli/chunk-nfcyttvj.js.map +10 -0
  52. package/dist/cli/{chunk-6k8vp3ta.js → chunk-pbg5a4s3.js} +60 -28
  53. package/dist/cli/chunk-pbg5a4s3.js.map +19 -0
  54. package/dist/cli/{chunk-d80hr03s.js → chunk-ppzjqwx2.js} +24 -17
  55. package/dist/cli/{chunk-d80hr03s.js.map → chunk-ppzjqwx2.js.map} +4 -4
  56. package/dist/cli/{chunk-beat36xx.js → chunk-qkb5a8sa.js} +25 -10
  57. package/dist/cli/chunk-qkb5a8sa.js.map +10 -0
  58. package/dist/cli/{chunk-bnbmcwfb.js → chunk-sqw4ekg1.js} +5 -5
  59. package/dist/cli/{chunk-bnbmcwfb.js.map → chunk-sqw4ekg1.js.map} +3 -3
  60. package/dist/cli/{chunk-xaz13gwg.js → chunk-v6ya5kcb.js} +3268 -1150
  61. package/dist/cli/chunk-v6ya5kcb.js.map +189 -0
  62. package/dist/cli/{chunk-tzne8qfq.js → chunk-w4bxdvsa.js} +18 -15
  63. package/dist/cli/chunk-w4bxdvsa.js.map +10 -0
  64. package/dist/cli/{chunk-f7t03s3g.js → chunk-wdrt2k2v.js} +2 -2
  65. package/dist/cli/{chunk-z1f5arsg.js → chunk-xh43dwgw.js} +170 -55
  66. package/dist/cli/chunk-xh43dwgw.js.map +36 -0
  67. package/dist/cli/{chunk-pnnvybbk.js → chunk-zp79m0ts.js} +5 -5
  68. package/dist/cli/{chunk-pnnvybbk.js.map → chunk-zp79m0ts.js.map} +2 -2
  69. package/dist/cli/index.js +160 -35
  70. package/dist/cli/index.js.map +4 -4
  71. package/dist/types/ai/agent-readability.d.ts +1 -1
  72. package/dist/types/ai/agent-surface.d.ts +32 -0
  73. package/dist/types/ai/api/paths.d.ts +1 -1
  74. package/dist/types/ai/api-catalog.d.ts +7 -1
  75. package/dist/types/ai/ask-context.d.ts +14 -7
  76. package/dist/types/ai/ask.d.ts +43 -43
  77. package/dist/types/ai/component-markdown.d.ts +4 -4
  78. package/dist/types/ai/index.d.ts +3 -3
  79. package/dist/types/ai/openapi-components.d.ts +1 -1
  80. package/dist/types/ai/relative-links.d.ts +9 -4
  81. package/dist/types/ai/serializers.d.ts +1 -1
  82. package/dist/types/ai/static-expression.d.ts +28 -0
  83. package/dist/types/ai/visibility.d.ts +1 -1
  84. package/dist/types/analytics/databuddy.d.ts +43 -0
  85. package/dist/types/analytics/index.d.ts +2 -0
  86. package/dist/types/analytics/schema.d.ts +14 -0
  87. package/dist/types/cli/env.d.ts +5 -0
  88. package/dist/types/cli/init/scaffold.d.ts +19 -3
  89. package/dist/types/cli/init/starter-spec.d.ts +11 -0
  90. package/dist/types/core/base-path.d.ts +9 -0
  91. package/dist/types/core/config-input.d.ts +21 -21
  92. package/dist/types/core/config.d.ts +5 -5
  93. package/dist/types/core/data.d.ts +3 -3
  94. package/dist/types/core/graph.d.ts +2 -0
  95. package/dist/types/core/i18n-ui.d.ts +37 -8
  96. package/dist/types/core/i18n.d.ts +7 -1
  97. package/dist/types/core/links.d.ts +3 -1
  98. package/dist/types/core/load-module.d.ts +10 -0
  99. package/dist/types/core/locale-links.d.ts +12 -2
  100. package/dist/types/core/meta.d.ts +5 -1
  101. package/dist/types/core/nav-diagnostics.d.ts +10 -0
  102. package/dist/types/core/navigation.d.ts +38 -0
  103. package/dist/types/core/ordering-prefix.d.ts +4 -0
  104. package/dist/types/core/safe-links.d.ts +3 -1
  105. package/dist/types/core/schema.d.ts +57 -18
  106. package/dist/types/core/sources/github-releases.d.ts +5 -0
  107. package/dist/types/core/sources/lower.d.ts +14 -2
  108. package/dist/types/core/sources/normalize.d.ts +10 -1
  109. package/dist/types/core/sources/remote.d.ts +11 -1
  110. package/dist/types/core/sources/resolve.d.ts +12 -0
  111. package/dist/types/core/sources/types.d.ts +31 -0
  112. package/dist/types/core/types.d.ts +9 -0
  113. package/dist/types/core/unrecognized-keys.d.ts +1 -1
  114. package/dist/types/deploy/adapters/node.d.ts +5 -2
  115. package/dist/types/deploy/adapters/types.d.ts +7 -0
  116. package/dist/types/deploy/cloudflare-negotiation.d.ts +3 -2
  117. package/dist/types/deploy/headers.d.ts +31 -7
  118. package/dist/types/deploy/node-headers.d.ts +43 -8
  119. package/dist/types/deploy/platforms/netlify.d.ts +27 -2
  120. package/dist/types/deploy/platforms/node.d.ts +6 -5
  121. package/dist/types/deploy/platforms/types.d.ts +7 -0
  122. package/dist/types/deploy/platforms/vercel.d.ts +3 -2
  123. package/dist/types/deploy/redirects.d.ts +7 -2
  124. package/dist/types/deploy/vercel-negotiation.d.ts +3 -2
  125. package/dist/types/openapi/model.d.ts +20 -6
  126. package/dist/types/search/documents.d.ts +1 -1
  127. package/dist/types/search/orama-index.d.ts +1 -1
  128. package/dist/types/search/sync/algolia.d.ts +3 -1
  129. package/docs/01-quickstart.mdx +3 -2
  130. package/docs/02-deployment.mdx +21 -12
  131. package/docs/03-upgrading.mdx +22 -9
  132. package/docs/04-migrating.mdx +4 -4
  133. package/docs/08-faq.mdx +12 -5
  134. package/docs/advanced/changelog.mdx +1 -1
  135. package/docs/advanced/custom-pages.mdx +5 -3
  136. package/docs/advanced/skills.mdx +1 -1
  137. package/docs/cli/audit.mdx +5 -5
  138. package/docs/cli/doctor.mdx +4 -4
  139. package/docs/cli/evals.mdx +9 -9
  140. package/docs/cli/index.mdx +6 -3
  141. package/docs/cli/translate.mdx +8 -8
  142. package/docs/configuration/analytics.mdx +23 -2
  143. package/docs/configuration/{ask-ai.mdx → assistant.mdx} +20 -20
  144. package/docs/configuration/customization.mdx +5 -4
  145. package/docs/configuration/index.mdx +7 -5
  146. package/docs/configuration/meta.ts +1 -1
  147. package/docs/configuration/search.mdx +2 -2
  148. package/docs/content/components.mdx +1 -1
  149. package/docs/content/frontmatter.mdx +5 -1
  150. package/docs/content/i18n.mdx +3 -3
  151. package/docs/content/index.mdx +4 -2
  152. package/docs/content/islands.mdx +1 -1
  153. package/docs/content/meta.mdx +5 -3
  154. package/docs/content/navigation.mdx +29 -4
  155. package/docs/content/sources.mdx +18 -12
  156. package/docs/content/versioning.mdx +1 -0
  157. package/docs/discoverability/agent-discovery.mdx +22 -8
  158. package/docs/discoverability/index.mdx +3 -3
  159. package/docs/discoverability/llms-txt.mdx +2 -5
  160. package/docs/discoverability/markdown.mdx +5 -3
  161. package/docs/discoverability/mcp.mdx +4 -0
  162. package/docs/discoverability/metadata.mdx +3 -2
  163. package/docs/discoverability/open-graph.mdx +6 -4
  164. package/docs/discoverability/rss.mdx +3 -1
  165. package/docs/index.mdx +2 -2
  166. package/docs/references/asyncapi.mdx +1 -1
  167. package/docs/references/graphql.mdx +2 -2
  168. package/docs/references/openapi.mdx +3 -3
  169. package/package.json +1 -1
  170. package/skills/blume/SKILL.md +5 -5
  171. package/skills/blume-migrate/SKILL.md +6 -6
  172. package/skills/blume-migrate/references/docusaurus.md +6 -6
  173. package/skills/blume-migrate/references/mintlify.md +3 -3
  174. package/skills/blume-migrate/references/monorepo.md +1 -1
  175. package/skills/blume-migrate/references/nextra.md +1 -1
  176. package/skills/blume-migrate/references/starlight.md +5 -5
  177. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +2 -2
  178. package/skills/blume-update-docs/references/audit-checklist.md +1 -1
  179. package/src/ai/agent-readability.ts +4 -4
  180. package/src/ai/agent-surface.ts +56 -0
  181. package/src/ai/api/paths.ts +1 -1
  182. package/src/ai/api/spec.ts +15 -2
  183. package/src/ai/api-catalog.ts +7 -1
  184. package/src/ai/ask-context.ts +21 -9
  185. package/src/ai/ask-data.ts +28 -14
  186. package/src/ai/ask.ts +84 -71
  187. package/src/ai/component-markdown.ts +43 -37
  188. package/src/ai/cors.ts +3 -3
  189. package/src/ai/index.ts +16 -16
  190. package/src/ai/link-headers.ts +8 -3
  191. package/src/ai/llms.ts +4 -3
  192. package/src/ai/markdown.ts +23 -11
  193. package/src/ai/mcp/server.ts +78 -7
  194. package/src/ai/openapi-components.ts +1 -1
  195. package/src/ai/relative-links.ts +78 -10
  196. package/src/ai/serializers.ts +1 -1
  197. package/src/ai/static-expression.ts +416 -0
  198. package/src/ai/visibility.ts +46 -15
  199. package/src/analytics/databuddy.ts +67 -0
  200. package/src/analytics/head.ts +4 -0
  201. package/src/analytics/index.ts +2 -0
  202. package/src/analytics/posthog.ts +21 -3
  203. package/src/analytics/schema.ts +2 -0
  204. package/src/astro/generate.ts +90 -45
  205. package/src/astro/module-types.ts +1 -1
  206. package/src/astro/runtime-deps.ts +6 -6
  207. package/src/astro/templates.ts +87 -49
  208. package/src/audit/catalog.ts +2 -2
  209. package/src/audit/checks/assets.ts +25 -5
  210. package/src/audit/checks/content.ts +20 -2
  211. package/src/audit/checks/i18n.ts +46 -8
  212. package/src/audit/checks/indexability.ts +39 -19
  213. package/src/audit/checks/links.ts +14 -0
  214. package/src/audit/checks/llms.ts +6 -3
  215. package/src/audit/checks/network.ts +3 -1
  216. package/src/audit/checks/og-image.ts +10 -0
  217. package/src/audit/checks/robots.ts +6 -1
  218. package/src/audit/checks/sitemap.ts +56 -31
  219. package/src/audit/checks/social.ts +21 -2
  220. package/src/audit/crawl.ts +88 -15
  221. package/src/audit/report.ts +54 -17
  222. package/src/audit/run.ts +1 -0
  223. package/src/audit/types.ts +18 -0
  224. package/src/audit/url.ts +37 -6
  225. package/src/blume-modules.d.ts +2 -2
  226. package/src/cli/build-failure.ts +50 -0
  227. package/src/cli/commands/audit.ts +8 -1
  228. package/src/cli/commands/build.ts +19 -5
  229. package/src/cli/commands/check.ts +2 -0
  230. package/src/cli/commands/doctor.ts +7 -5
  231. package/src/cli/commands/eval.ts +4 -10
  232. package/src/cli/commands/init.ts +15 -5
  233. package/src/cli/commands/migrate.ts +2 -2
  234. package/src/cli/commands/preview.ts +15 -0
  235. package/src/cli/commands/sync.ts +2 -0
  236. package/src/cli/commands/translate.ts +12 -9
  237. package/src/cli/commands/upgrade.ts +13 -2
  238. package/src/cli/eject-scripts.ts +32 -7
  239. package/src/cli/env.ts +12 -1
  240. package/src/cli/init/scaffold.ts +117 -11
  241. package/src/cli/init/starter-spec.ts +235 -0
  242. package/src/cli/required-secrets.ts +3 -3
  243. package/src/components/content/AccordionItem.astro +26 -22
  244. package/src/components/content/Badge.astro +2 -9
  245. package/src/components/content/Card.astro +2 -2
  246. package/src/components/content/Component.astro +9 -3
  247. package/src/components/content/Expandable.astro +5 -1
  248. package/src/components/content/Frame.astro +2 -7
  249. package/src/components/content/GithubInfo.astro +12 -2
  250. package/src/components/content/Prompt.astro +2 -7
  251. package/src/components/content/Tabs.astro +54 -5
  252. package/src/components/content/Tile.astro +1 -1
  253. package/src/components/content/Tooltip.astro +69 -7
  254. package/src/components/content/Tree.astro +7 -2
  255. package/src/components/content/TypeTable.astro +10 -5
  256. package/src/components/content/Update.astro +8 -2
  257. package/src/components/content/auto-type-table.ts +4 -1
  258. package/src/components/content/badge-color.ts +17 -0
  259. package/src/components/content/base-href.ts +18 -3
  260. package/src/components/content/inline-markdown.ts +27 -7
  261. package/src/components/copy-feedback.ts +36 -9
  262. package/src/components/islands/{AskAI.astro → Assistant.astro} +10 -10
  263. package/src/components/islands/{ask-ai.tsx → assistant.tsx} +39 -25
  264. package/src/components/islands/hooks.ts +21 -14
  265. package/src/components/islands/webmcp.ts +12 -8
  266. package/src/components/layout/Banner.astro +23 -4
  267. package/src/components/layout/Header.astro +32 -19
  268. package/src/components/layout/Logo.astro +5 -0
  269. package/src/components/layout/NavSelector.astro +6 -1
  270. package/src/components/layout/NavTabMenu.astro +133 -0
  271. package/src/components/layout/NavTree.astro +15 -6
  272. package/src/components/layout/NavTreeCache.astro +5 -2
  273. package/src/components/layout/NavTreeScript.astro +45 -5
  274. package/src/components/layout/PageLayout.astro +28 -14
  275. package/src/components/layout/Pagination.astro +7 -7
  276. package/src/components/layout/ReferenceLayout.astro +5 -2
  277. package/src/components/layout/RootLayout.astro +27 -14
  278. package/src/components/layout/Search.astro +18 -14
  279. package/src/components/layout/analytics-client.ts +3 -1
  280. package/src/components/layout/drawer-inert.ts +1 -1
  281. package/src/components/openapi/GraphqlType.astro +11 -3
  282. package/src/components/openapi/MessageComposer.astro +1 -1
  283. package/src/components/openapi/Operation.astro +21 -5
  284. package/src/components/openapi/PanelTabs.astro +4 -1
  285. package/src/components/openapi/Playground.astro +8 -2
  286. package/src/components/openapi/RequestPanel.astro +9 -5
  287. package/src/components/openapi/SchemaProperty.astro +11 -33
  288. package/src/components/openapi/SchemaTable.astro +27 -52
  289. package/src/components/openapi/description.ts +2 -2
  290. package/src/components/openapi/helpers.ts +90 -12
  291. package/src/components/openapi/message-composer.ts +8 -0
  292. package/src/components/openapi/message-model.ts +12 -2
  293. package/src/components/openapi/message.ts +4 -1
  294. package/src/components/openapi/operation-model.ts +40 -8
  295. package/src/components/openapi/panel.ts +29 -5
  296. package/src/components/openapi/playground-client.ts +37 -7
  297. package/src/components/openapi/playground-schema.ts +25 -6
  298. package/src/components/openapi/request.ts +2 -0
  299. package/src/components/openapi/schema-tree.ts +203 -0
  300. package/src/components/openapi/snippets.ts +30 -15
  301. package/src/components/openapi/validate-json.ts +1 -1
  302. package/src/core/base-path.ts +15 -0
  303. package/src/core/code-fences.ts +1 -1
  304. package/src/core/config-input.ts +23 -23
  305. package/src/core/config.ts +21 -7
  306. package/src/core/data.ts +3 -3
  307. package/src/core/diagnostics.ts +217 -30
  308. package/src/core/graph.ts +120 -11
  309. package/src/core/i18n-ui.ts +74 -11
  310. package/src/core/i18n.ts +13 -2
  311. package/src/core/links.ts +7 -4
  312. package/src/core/load-module.ts +20 -0
  313. package/src/core/locale-links.ts +17 -17
  314. package/src/core/manifest.ts +3 -2
  315. package/src/core/meta.ts +69 -6
  316. package/src/core/nav-diagnostics.ts +56 -1
  317. package/src/core/navigation.ts +245 -76
  318. package/src/core/ordering-prefix.ts +27 -0
  319. package/src/core/project-graph.ts +23 -1
  320. package/src/core/request-body.ts +1 -1
  321. package/src/core/safe-href.ts +53 -1
  322. package/src/core/safe-links.ts +11 -2
  323. package/src/core/schema.ts +95 -34
  324. package/src/core/server-features.ts +2 -2
  325. package/src/core/sources/assets.ts +83 -41
  326. package/src/core/sources/contentful-rich-text.ts +3 -2
  327. package/src/core/sources/contentful.ts +25 -12
  328. package/src/core/sources/filesystem.ts +5 -1
  329. package/src/core/sources/github-releases.ts +106 -5
  330. package/src/core/sources/lexical.ts +9 -4
  331. package/src/core/sources/lower.ts +79 -26
  332. package/src/core/sources/mdx-remote.ts +1 -0
  333. package/src/core/sources/normalize.ts +132 -30
  334. package/src/core/sources/notion.ts +26 -10
  335. package/src/core/sources/obsidian.ts +17 -3
  336. package/src/core/sources/payload.ts +1 -0
  337. package/src/core/sources/portable-text.ts +63 -44
  338. package/src/core/sources/remote.ts +18 -2
  339. package/src/core/sources/resolve.ts +52 -33
  340. package/src/core/sources/sanity.ts +1 -0
  341. package/src/core/sources/strapi-blocks.ts +9 -4
  342. package/src/core/sources/strapi.ts +1 -0
  343. package/src/core/sources/types.ts +31 -0
  344. package/src/core/types.ts +9 -0
  345. package/src/core/ui-packs/ar.ts +17 -5
  346. package/src/core/ui-packs/bg.ts +17 -5
  347. package/src/core/ui-packs/bn.ts +17 -5
  348. package/src/core/ui-packs/ca.ts +17 -5
  349. package/src/core/ui-packs/cs.ts +17 -5
  350. package/src/core/ui-packs/da.ts +17 -5
  351. package/src/core/ui-packs/de.ts +17 -5
  352. package/src/core/ui-packs/el.ts +17 -5
  353. package/src/core/ui-packs/es.ts +17 -5
  354. package/src/core/ui-packs/fa.ts +17 -5
  355. package/src/core/ui-packs/fi.ts +17 -5
  356. package/src/core/ui-packs/fr.ts +17 -5
  357. package/src/core/ui-packs/he.ts +17 -5
  358. package/src/core/ui-packs/hi.ts +17 -5
  359. package/src/core/ui-packs/hr.ts +17 -5
  360. package/src/core/ui-packs/hu.ts +17 -5
  361. package/src/core/ui-packs/id.ts +17 -5
  362. package/src/core/ui-packs/it.ts +17 -5
  363. package/src/core/ui-packs/ja.ts +17 -5
  364. package/src/core/ui-packs/ko.ts +17 -5
  365. package/src/core/ui-packs/nl.ts +17 -5
  366. package/src/core/ui-packs/no.ts +17 -5
  367. package/src/core/ui-packs/pl.ts +17 -5
  368. package/src/core/ui-packs/pt-br.ts +17 -5
  369. package/src/core/ui-packs/pt.ts +17 -5
  370. package/src/core/ui-packs/ro.ts +17 -5
  371. package/src/core/ui-packs/ru.ts +17 -5
  372. package/src/core/ui-packs/sk.ts +17 -5
  373. package/src/core/ui-packs/sr.ts +17 -5
  374. package/src/core/ui-packs/sv.ts +17 -5
  375. package/src/core/ui-packs/th.ts +17 -5
  376. package/src/core/ui-packs/tr.ts +17 -5
  377. package/src/core/ui-packs/uk.ts +17 -5
  378. package/src/core/ui-packs/vi.ts +17 -5
  379. package/src/core/ui-packs/zh-tw.ts +17 -5
  380. package/src/core/ui-packs/zh.ts +17 -5
  381. package/src/core/unrecognized-keys.ts +1 -1
  382. package/src/core/version-cut.ts +17 -2
  383. package/src/core/versions.ts +4 -1
  384. package/src/deploy/adapters/node.ts +5 -2
  385. package/src/deploy/adapters/registry.ts +2 -1
  386. package/src/deploy/adapters/types.ts +13 -1
  387. package/src/deploy/artifacts.ts +25 -5
  388. package/src/deploy/cloudflare-negotiation.ts +23 -4
  389. package/src/deploy/headers.ts +67 -51
  390. package/src/deploy/node-headers.ts +148 -27
  391. package/src/deploy/platforms/cloudflare.ts +1 -0
  392. package/src/deploy/platforms/netlify.ts +82 -5
  393. package/src/deploy/platforms/node.ts +7 -5
  394. package/src/deploy/platforms/static.ts +1 -0
  395. package/src/deploy/platforms/types.ts +7 -0
  396. package/src/deploy/platforms/vercel.ts +9 -3
  397. package/src/deploy/redirects.ts +14 -3
  398. package/src/deploy/vercel-negotiation.ts +35 -3
  399. package/src/eval/agents.ts +10 -2
  400. package/src/eval/run.ts +25 -0
  401. package/src/markdown/base-links.ts +55 -8
  402. package/src/markdown/index.ts +6 -3
  403. package/src/markdown/relative-links.ts +3 -23
  404. package/src/markdown/route-snapshot.ts +37 -0
  405. package/src/og/card.ts +20 -4
  406. package/src/og/derive.ts +145 -4
  407. package/src/openapi/graphql-build.ts +28 -2
  408. package/src/openapi/model.ts +77 -13
  409. package/src/openapi/render-mdx.ts +10 -1
  410. package/src/registry/eject.ts +135 -37
  411. package/src/search/documents.ts +24 -3
  412. package/src/search/orama-index.ts +1 -1
  413. package/src/search/sync/algolia.ts +36 -2
  414. package/src/sources/registry.ts +5 -0
  415. package/src/theme/entry.ts +11 -4
  416. package/src/translate/agents.ts +6 -1
  417. package/src/translate/ledger.ts +26 -3
  418. package/src/translate/meta.ts +11 -3
  419. package/src/translate/report.ts +1 -1
  420. package/src/translate/run.ts +11 -5
  421. package/src/translate/validate.ts +10 -1
  422. package/src/translate/work-list.ts +36 -3
  423. package/src/upgrade/upgrade.ts +37 -5
  424. package/dist/cli/chunk-1w8dp3qb.js.map +0 -10
  425. package/dist/cli/chunk-5a2z0198.js.map +0 -10
  426. package/dist/cli/chunk-6k8vp3ta.js.map +0 -19
  427. package/dist/cli/chunk-79njf86q.js.map +0 -11
  428. package/dist/cli/chunk-a9kptbw5.js.map +0 -14
  429. package/dist/cli/chunk-abh8yjkn.js +0 -31
  430. package/dist/cli/chunk-abh8yjkn.js.map +0 -10
  431. package/dist/cli/chunk-bctazmbk.js.map +0 -10
  432. package/dist/cli/chunk-beat36xx.js.map +0 -10
  433. package/dist/cli/chunk-d1tadaw7.js.map +0 -10
  434. package/dist/cli/chunk-fa25z98p.js.map +0 -11
  435. package/dist/cli/chunk-j8mw0za6.js.map +0 -35
  436. package/dist/cli/chunk-mnqj32sj.js.map +0 -12
  437. package/dist/cli/chunk-mwt1k8n7.js.map +0 -10
  438. package/dist/cli/chunk-nk3ts2xk.js +0 -51
  439. package/dist/cli/chunk-nk3ts2xk.js.map +0 -10
  440. package/dist/cli/chunk-sqn5t4q0.js.map +0 -10
  441. package/dist/cli/chunk-tzne8qfq.js.map +0 -10
  442. package/dist/cli/chunk-xaz13gwg.js.map +0 -182
  443. package/dist/cli/chunk-z01ze5c1.js.map +0 -10
  444. package/dist/cli/chunk-z1f5arsg.js.map +0 -36
  445. package/dist/cli/chunk-zg2gtj10.js.map +0 -35
  446. /package/dist/cli/{chunk-f2972sbt.js.map → chunk-00gs3wqs.js.map} +0 -0
  447. /package/dist/cli/{chunk-7ez8ny0t.js.map → chunk-bbnwccaz.js.map} +0 -0
  448. /package/dist/cli/{chunk-zxccj738.js.map → chunk-esh98wmb.js.map} +0 -0
  449. /package/dist/cli/{chunk-88cpgt6h.js.map → chunk-fsmrqk8a.js.map} +0 -0
  450. /package/dist/cli/{chunk-b5aj94ah.js.map → chunk-g698a744.js.map} +0 -0
  451. /package/dist/cli/{chunk-41za066z.js.map → chunk-h2ez8dzb.js.map} +0 -0
  452. /package/dist/cli/{chunk-by2290sx.js.map → chunk-h7k3nq3v.js.map} +0 -0
  453. /package/dist/cli/{chunk-y3e45rc8.js.map → chunk-n1yg3tj3.js.map} +0 -0
  454. /package/dist/cli/{chunk-f7t03s3g.js.map → chunk-wdrt2k2v.js.map} +0 -0
package/src/ai/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Ask AI provider adapters for `blume.config.ts`:
2
+ * Assistant provider adapters for `blume.config.ts`:
3
3
  *
4
4
  * ```ts
5
5
  * import { defineConfig } from "blume";
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * export default defineConfig({
9
9
  * ai: {
10
- * ask: {
10
+ * assistant: {
11
11
  * enabled: true,
12
12
  * provider: openrouter({ model: "anthropic/claude-sonnet-4-5" }),
13
13
  * },
@@ -27,19 +27,19 @@ export {
27
27
  openrouter,
28
28
  } from "./ask.ts";
29
29
  export type {
30
- AskAdapter,
31
- AskAdapterOptions,
32
- AskGatewayAdapter,
33
- AskGatewayOptions,
34
- AskInkeepAdapter,
35
- AskInkeepOptions,
36
- AskLlmGatewayAdapter,
37
- AskLlmGatewayOptions,
38
- AskOpenAICompatibleAdapter,
39
- AskOpenAICompatibleOptions,
40
- AskOpenRouterAdapter,
41
- AskOpenRouterOptions,
42
- AskProviderOptions,
43
- AskReasoning,
30
+ AssistantAdapter,
31
+ AssistantAdapterOptions,
32
+ AssistantGatewayAdapter,
33
+ AssistantGatewayOptions,
34
+ AssistantInkeepAdapter,
35
+ AssistantInkeepOptions,
36
+ AssistantLlmGatewayAdapter,
37
+ AssistantLlmGatewayOptions,
38
+ AssistantOpenAICompatibleAdapter,
39
+ AssistantOpenAICompatibleOptions,
40
+ AssistantOpenRouterAdapter,
41
+ AssistantOpenRouterOptions,
42
+ AssistantProviderOptions,
43
+ AssistantReasoning,
44
44
  } from "./ask.ts";
45
45
  export type { AdapterDescriptor } from "../core/adapter.ts";
@@ -5,7 +5,11 @@ import {
5
5
  AI_CATALOG_TYPE,
6
6
  hasAiCatalog,
7
7
  } from "./ai-catalog.ts";
8
- import { API_CATALOG_PATH, hasApiCatalog } from "./api-catalog.ts";
8
+ import {
9
+ API_CATALOG_PATH,
10
+ API_CATALOG_TYPE,
11
+ hasApiCatalog,
12
+ } from "./api-catalog.ts";
9
13
  import { OPENAPI_PATH } from "./api/paths.ts";
10
14
 
11
15
  /**
@@ -37,10 +41,11 @@ export const buildHomeLinkHeader = (
37
41
  const deployBase = normalizeBasePath(config.deployment.options.base);
38
42
  const links: string[] = [];
39
43
  // RFC 9727 §3: the api-catalog relation is how a homepage advertises the
40
- // well-known catalog.
44
+ // well-known catalog. Its type carries the RFC 9727 profile, whose quotes
45
+ // are escaped inside the quoted `type` value (RFC 8288 quoted-string).
41
46
  if (hasApiCatalog(config)) {
42
47
  links.push(
43
- `<${deployBase}${API_CATALOG_PATH}>; rel="api-catalog"; type="application/linkset+json"`
48
+ `<${deployBase}${API_CATALOG_PATH}>; rel="api-catalog"; type="${API_CATALOG_TYPE.replaceAll('"', String.raw`\"`)}"`
44
49
  );
45
50
  }
46
51
  // The ai-catalog spec's own relation for its well-known document, the
package/src/ai/llms.ts CHANGED
@@ -312,10 +312,11 @@ const buildFull = async (project: BlumeProject): Promise<string> => {
312
312
  source: raw,
313
313
  sourcePath: page.sourcePath,
314
314
  });
315
- // Relative page links too: a reader of one flat file has no page URL
316
- // to resolve `./install` against.
317
- raw = rewriteLinks(raw, page);
318
315
  }
316
+ // Page links too: a reader of one flat file has no page URL to resolve
317
+ // `./install` against, and `/install` needs the base the site is
318
+ // served under.
319
+ raw = rewriteLinks(raw, page);
319
320
  // Resolve `<Visibility>` audiences (web-only content omitted from the
320
321
  // agent-facing output, agents-only unwrapped), then downlevel supported
321
322
  // components to plain Markdown.
@@ -8,6 +8,7 @@ import matter from "../core/frontmatter.ts";
8
8
  import type { BlumeProject } from "../core/project-graph.ts";
9
9
  import { readExpandedEntryText } from "../core/sources/read.ts";
10
10
  import type { RouteManifestEntry } from "../core/types.ts";
11
+ import { advertisedConfig, servesMcp } from "./agent-surface.ts";
11
12
  import { buildChangelogIndexMarkdown } from "./changelog-markdown.ts";
12
13
  import { downlevelComponents } from "./component-markdown.ts";
13
14
  import { buildLlmsIndex } from "./llms.ts";
@@ -80,13 +81,14 @@ export const buildRawMarkdown = async (
80
81
  source: text,
81
82
  sourcePath: route.sourcePath,
82
83
  });
83
- // `./install` means the page's sibling, not whatever the `.md` URL
84
- // an agent fetched resolves it to.
85
- text = rewriteLinks(text, {
86
- route: route.path,
87
- sourcePath: route.sourcePath,
88
- });
89
84
  }
85
+ // `./install` means the page's sibling, not whatever the `.md` URL an
86
+ // agent fetched resolves it to, and `/install` gains the base and the
87
+ // page's locale the rendered link has — remote content included.
88
+ text = rewriteLinks(text, {
89
+ route: route.path,
90
+ sourcePath: route.sourcePath,
91
+ });
90
92
  const source = applyAgentVisibility(text);
91
93
  // The `.md` variant keeps the front-matter block in the output, but its
92
94
  // data must also be in scope for `prop={frontmatter.*}` expressions.
@@ -97,19 +99,29 @@ export const buildRawMarkdown = async (
97
99
  })
98
100
  );
99
101
  const map = Object.fromEntries(entries);
102
+ const userPages = project.context.pagesRoot
103
+ ? await discoverPages(project.context.pagesRoot)
104
+ : [];
100
105
  // A landing-page homepage (user `.astro` page, or no home route at all) has
101
106
  // no Markdown source, but agents negotiating `Accept: text/markdown` on `/`
102
107
  // still expect a Markdown answer. The llms.txt index — the machine-readable
103
108
  // representation of the site a landing page fronts — becomes its mirror, so
104
- // `/index.md` always exists (see `markdownRoutePaths`).
109
+ // `/index.md` always exists (see `markdownRoutePaths`). Like llms.txt, it
110
+ // leaves out an MCP server a page's route kept from being generated.
105
111
  if (!map["/"]) {
106
- map["/"] = { mdx: buildLlmsIndex(project) };
112
+ map["/"] = {
113
+ mdx: buildLlmsIndex({
114
+ ...project,
115
+ config: advertisedConfig(project.config, {
116
+ mcp: servesMcp(project, userPages),
117
+ // Skills are collected at the end of the build, after this runs.
118
+ skills: Boolean(project.config.agents.skills),
119
+ }),
120
+ }),
121
+ };
107
122
  }
108
123
  // The generated changelog index has no source either; its release list is
109
124
  // its mirror, so `/changelog.md` and `get_page` answer as the page does.
110
- const userPages = project.context.pagesRoot
111
- ? await discoverPages(project.context.pagesRoot)
112
- : [];
113
125
  if (hasGeneratedChangelog(project, userPages)) {
114
126
  map[CHANGELOG_INDEX_ROUTE] = { mdx: buildChangelogIndexMarkdown(project) };
115
127
  }
@@ -2,7 +2,7 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
2
  import type { ServerOptions } from "@modelcontextprotocol/sdk/server/index.js";
3
3
  import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
4
4
  import {
5
- CallToolRequestSchema,
5
+ ErrorCode,
6
6
  ListResourcesRequestSchema,
7
7
  ListToolsRequestSchema,
8
8
  McpError,
@@ -11,6 +11,7 @@ import {
11
11
  import { z } from "zod";
12
12
 
13
13
  import { withBasePath } from "../../core/base-path.ts";
14
+ import { readCappedText } from "../../core/request-body.ts";
14
15
  import type { McpData } from "./data.ts";
15
16
  import {
16
17
  createIndexProvider,
@@ -42,6 +43,12 @@ const RESOURCE_MIME_TYPE = "text/markdown";
42
43
  const RESOURCE_NOT_FOUND = -32_002;
43
44
  /** URI scheme for page resources when no `deployment.site` is configured. */
44
45
  const LOCAL_RESOURCE_SCHEME = "blume:";
46
+ /**
47
+ * The largest request body the HTTP endpoint reads. A JSON-RPC call to these
48
+ * tools is a few hundred bytes; the cap keeps one oversized POST from being
49
+ * buffered whole, as the assistant route's does.
50
+ */
51
+ const MCP_BODY_LIMIT_BYTES = 65_536;
45
52
 
46
53
  const CORS_HEADERS = {
47
54
  "Access-Control-Allow-Headers":
@@ -108,6 +115,20 @@ const resourceRoute = (uri: string, data: McpData): string =>
108
115
  data
109
116
  );
110
117
 
118
+ /**
119
+ * The `tools/call` params, as far as the server reads them before dispatch:
120
+ * each tool validates its own `arguments` against `TOOL_INPUTS`.
121
+ */
122
+ const TOOL_CALL_PARAMS = z.object({
123
+ arguments: z.record(z.string(), z.unknown()).optional(),
124
+ name: z.string(),
125
+ });
126
+
127
+ /** A tool call's `arguments` object, before the tool's own validation. */
128
+ type ToolArguments = NonNullable<
129
+ z.output<typeof TOOL_CALL_PARAMS>["arguments"]
130
+ >;
131
+
111
132
  /** A tool call's text result, marked as an error when `isError` is set. */
112
133
  const text = (value: string, isError = false) => {
113
134
  const content = [{ text: value, type: "text" as const }];
@@ -165,9 +186,7 @@ export const buildServer = (
165
186
  };
166
187
  });
167
188
 
168
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
169
- const { arguments: args = {}, name } = request.params;
170
-
189
+ const callTool = async (name: string, args: ToolArguments) => {
171
190
  if (name === "search_docs") {
172
191
  const results = await searchDocs(
173
192
  data,
@@ -206,12 +225,44 @@ export const buildServer = (
206
225
  return text(JSON.stringify(result.navigation, null, 2));
207
226
  }
208
227
 
209
- return text(`Unknown tool: ${name}`, true);
210
- });
228
+ // The MCP spec answers an unknown tool with a protocol error, not a
229
+ // failed tool result.
230
+ throw new McpError(ErrorCode.InvalidParams, `Unknown tool: ${name}`);
231
+ };
232
+
233
+ // `tools/call` goes through the fallback handler rather than a registered
234
+ // one: the SDK parses a registered handler's request against its own
235
+ // schema first and reports a malformed call (`arguments` that aren't an
236
+ // object) as -32603 Internal error carrying a raw Zod dump. Reading the
237
+ // params here answers it as -32602 Invalid params with a short message.
238
+ server.fallbackRequestHandler = async (request) => {
239
+ if (request.method !== "tools/call") {
240
+ throw new McpError(ErrorCode.MethodNotFound, "Method not found");
241
+ }
242
+ const params = TOOL_CALL_PARAMS.safeParse(request.params);
243
+ if (!params.success) {
244
+ throw new McpError(
245
+ ErrorCode.InvalidParams,
246
+ "Invalid tools/call params: `name` must be a string and `arguments`, when given, an object."
247
+ );
248
+ }
249
+ return await callTool(params.data.name, params.data.arguments ?? {});
250
+ };
211
251
 
212
252
  return server;
213
253
  };
214
254
 
255
+ /** A JSON-RPC error response the transport would send, for errors raised before it runs. */
256
+ const jsonRpcError = (
257
+ status: number,
258
+ code: number,
259
+ message: string
260
+ ): Response =>
261
+ Response.json(
262
+ { error: { code, message }, id: null, jsonrpc: "2.0" },
263
+ { headers: CORS_HEADERS, status }
264
+ );
265
+
215
266
  /**
216
267
  * Build a stateless Streamable-HTTP MCP request handler from a data snapshot.
217
268
  *
@@ -239,6 +290,26 @@ export const createMcpFetchHandler = (
239
290
  });
240
291
  }
241
292
 
293
+ // The transport would read the body with `request.json()`, buffering
294
+ // whatever a client sends; read it under the cap first and hand the
295
+ // transport a copy.
296
+ let forwarded = request;
297
+ if (request.method === "POST") {
298
+ const body = await readCappedText(request, MCP_BODY_LIMIT_BYTES);
299
+ if (body === undefined) {
300
+ return jsonRpcError(
301
+ 413,
302
+ ErrorCode.InvalidRequest,
303
+ "Request too large: the body must be at most 64 KB."
304
+ );
305
+ }
306
+ forwarded = new Request(request.url, {
307
+ body,
308
+ headers: request.headers,
309
+ method: "POST",
310
+ });
311
+ }
312
+
242
313
  const server = buildServer(data, index);
243
314
  const transport = new WebStandardStreamableHTTPServerTransport({
244
315
  enableJsonResponse: true,
@@ -248,7 +319,7 @@ export const createMcpFetchHandler = (
248
319
  sessionIdGenerator: undefined,
249
320
  });
250
321
  await server.connect(transport);
251
- const response = await transport.handleRequest(request);
322
+ const response = await transport.handleRequest(forwarded);
252
323
 
253
324
  const headers = new Headers(response.headers);
254
325
  for (const [key, value] of Object.entries(CORS_HEADERS)) {
@@ -44,7 +44,7 @@ const listItem = (
44
44
  * the body plus one of these components, deliberately: the structured UI is the
45
45
  * component's job, and the prose stays Markdown so it indexes. That split is
46
46
  * right for the rendered page and lossy everywhere else — `<route>.md`,
47
- * llms-full.txt, MCP `get_page` and the Ask AI corpus all downlevel components
47
+ * llms-full.txt, MCP `get_page` and the assistant corpus all downlevel components
48
48
  * to Markdown, and these three had no serializer, so an operation page reached
49
49
  * an agent as its description followed by a bare tag. On a site whose reference
50
50
  * is most of the corpus, that is most of the corpus: measured on one 449-page
@@ -1,8 +1,14 @@
1
1
  import { basename } from "pathe";
2
2
 
3
- import { normalizeBasePath, withBasePath } from "../core/base-path.ts";
3
+ import {
4
+ isInternalPath,
5
+ normalizeBasePath,
6
+ withBasePath,
7
+ withComposedBasePath,
8
+ } from "../core/base-path.ts";
4
9
  import { nextFenceState } from "../core/code-fences.ts";
5
10
  import type { FenceState } from "../core/code-fences.ts";
11
+ import { localePrefix } from "../core/i18n.ts";
6
12
  import {
7
13
  buildFileRouteIndex,
8
14
  isIndexFileName,
@@ -10,19 +16,25 @@ import {
10
16
  routeOfLinkedFile,
11
17
  } from "../core/links.ts";
12
18
  import type { RelativeLinkBase } from "../core/links.ts";
19
+ import { localizeHref } from "../core/locale-links.ts";
13
20
  import type { BlumeProject } from "../core/project-graph.ts";
14
21
  import { extractLinks } from "../core/sources/normalize.ts";
15
22
 
16
23
  /**
17
- * Relative page links on the agent surfaces — the `.md`/`.mdx` mirrors,
24
+ * Page links on the agent surfaces — the `.md`/`.mdx` mirrors,
18
25
  * llms-full.txt, MCP `get_page` and `resources/read`. An agent resolves
19
26
  * `[Install](./install)` against the URL it fetched (`/guides.md`), so the
20
27
  * link lands on `/install`; the rendered page rewrites it to the route it
21
28
  * means (`markdown/relative-links.ts`), and the Markdown an agent reads gets
22
29
  * the same rewrite from the same resolver (`resolveRelativeHref`), so both
23
- * point at one page. Inline links and reference definitions are rewritten;
24
- * fenced and inline code, images, and anything not a relative page link are
25
- * left as written.
30
+ * point at one page. Root-relative page links (`[x](/guide)`,
31
+ * `<Card href="/guide">`) get what the rendered page gives them too: the
32
+ * `deployment.base` + `basePath` prefix (`markdown/base-links.ts`,
33
+ * `components/content/base-href.ts`) and, on a page in a prefixed locale,
34
+ * that locale's copy of the route when it is served
35
+ * (`components/layout/LocaleLinks.astro`). Inline links, component `href`s,
36
+ * and reference definitions are rewritten; fenced and inline code, images,
37
+ * asset links, and external URLs are left as written.
26
38
  */
27
39
 
28
40
  /** The page a Markdown text was written for. */
@@ -47,6 +59,21 @@ const DEFINITION =
47
59
  const MAYBE_RELATIVE =
48
60
  /\]\([\t ]*<?(?![a-z][\d+.a-z-]*:|\/|#)|^ {0,3}\[[^\]\n]+\]:[\t ]*<?(?![a-z][\d+.a-z-]*:|\/|#)|\shref=["'](?![a-z][\d+.a-z-]*:|\/|#)/imu;
49
61
 
62
+ /**
63
+ * {@link MAYBE_RELATIVE}, or a root-relative target (`/x`, never `//host`):
64
+ * the gate when root-relative links are rewritten too — a base or basePath is
65
+ * set, or the page sits under a locale prefix.
66
+ */
67
+ const MAYBE_PAGE_LINK =
68
+ /\]\([\t ]*<?(?![a-z][\d+.a-z-]*:|\/\/|#)|^ {0,3}\[[^\]\n]+\]:[\t ]*<?(?![a-z][\d+.a-z-]*:|\/\/|#)|\shref=["'](?![a-z][\d+.a-z-]*:|\/\/|#)/imu;
69
+
70
+ /**
71
+ * A path whose final segment carries a file extension — a `public/` asset or
72
+ * a raw `.md` twin, which the rendered page neither bases nor localizes (see
73
+ * `markdown/base-links.ts`).
74
+ */
75
+ const ASSET_PATH = /\.[\da-z]+$/iu;
76
+
50
77
  interface Splice {
51
78
  column: number;
52
79
  length: number;
@@ -124,7 +151,7 @@ const collectSplices = (
124
151
  export const relativeLinkRewriter = (
125
152
  project: BlumeProject
126
153
  ): RelativeLinkRewriter => {
127
- const { deployment, i18n } = project.config;
154
+ const { basePath, deployment, i18n } = project.config;
128
155
  const deployBase = normalizeBasePath(deployment.options.base);
129
156
  const localeTokens = i18n
130
157
  ? ["$", ...i18n.locales.map((locale) => locale.code)]
@@ -137,11 +164,20 @@ export const relativeLinkRewriter = (
137
164
  ])
138
165
  );
139
166
  const routes = new Set(project.manifest.routes.map((route) => route.path));
167
+ const localeByRoute = new Map(
168
+ project.manifest.routes.map((route) => [route.path, route.locale])
169
+ );
140
170
 
141
- return (text, page) => {
171
+ /**
172
+ * A page's relative-link resolver; `undefined` when the page has no file
173
+ * to resolve its links from.
174
+ */
175
+ const relativeRoute = (
176
+ page: LinkedPage
177
+ ): ((target: string) => string | undefined) | undefined => {
142
178
  const { sourcePath } = page;
143
- if (!sourcePath || !MAYBE_RELATIVE.test(text)) {
144
- return text;
179
+ if (!sourcePath) {
180
+ return;
145
181
  }
146
182
  const from: RelativeLinkBase = {
147
183
  isIndex: isIndexFileName(basename(sourcePath), localeTokens),
@@ -150,12 +186,44 @@ export const relativeLinkRewriter = (
150
186
  const navPath = navPathBySource.get(sourcePath) ?? basename(sourcePath);
151
187
  const resolveFile = (path: string): string | undefined =>
152
188
  routeOfLinkedFile(fileRoutes, { navPath, sourcePath }, path);
153
- const routed = (target: string): string | undefined => {
189
+ return (target) => {
154
190
  const route = resolveRelativeHref(target, from, resolveFile, (path) =>
155
191
  routes.has(path)
156
192
  );
157
193
  return route === undefined ? undefined : withBasePath(deployBase, route);
158
194
  };
195
+ };
196
+
197
+ return (text, page) => {
198
+ const locale = localeByRoute.get(page.route);
199
+ // The page's locale moves root links only when it has a URL prefix.
200
+ const localize =
201
+ i18n && locale !== undefined && localePrefix(locale, i18n) !== ""
202
+ ? { basePath, deployBase, i18n, locale, routes }
203
+ : undefined;
204
+ // A root-relative link only changes when the site is mounted under a
205
+ // prefix or the page reads in a prefixed locale.
206
+ const rootLinks =
207
+ deployBase !== "" || basePath !== "" || localize !== undefined;
208
+ if (
209
+ !(page.sourcePath || rootLinks) ||
210
+ !(rootLinks ? MAYBE_PAGE_LINK : MAYBE_RELATIVE).test(text)
211
+ ) {
212
+ return text;
213
+ }
214
+ const relative = relativeRoute(page);
215
+ // What the rendered page makes of `/guide`: based, then moved into the
216
+ // page's locale when that route is served.
217
+ const rooted = (target: string): string | undefined => {
218
+ if (ASSET_PATH.test(target.replace(/[#?].*$/u, ""))) {
219
+ return undefined;
220
+ }
221
+ const based = withComposedBasePath(deployBase, basePath, target);
222
+ const href = localize ? localizeHref(based, localize) : based;
223
+ return href === target ? undefined : href;
224
+ };
225
+ const routed = (target: string): string | undefined =>
226
+ isInternalPath(target) ? rooted(target) : relative?.(target);
159
227
 
160
228
  const lines = text.split("\n");
161
229
  const splices = collectSplices(text, lines, routed);
@@ -6,7 +6,7 @@ import { openapiComponentSerializers } from "./openapi-components.ts";
6
6
 
7
7
  /**
8
8
  * Every serializer a project brings to a downlevel pass, layered once for all
9
- * the agent surfaces (`<route>.md`, llms-full.txt, the search and Ask AI
9
+ * the agent surfaces (`<route>.md`, llms-full.txt, the search and assistant
10
10
  * corpora): the built-in families that read project data — examples, then the
11
11
  * API reference — under the user's `agents.markdownComponents`, which is spread
12
12
  * last so an entry of the same name still wins. One layering, so a new family