blume 2.1.2 → 2.2.0

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 (377) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +133 -0
  3. package/README.md +4 -4
  4. package/dist/cli/{chunk-74wfhv79.js → chunk-05f21r8h.js} +1 -1
  5. package/dist/cli/{chunk-9qzgg6mj.js → chunk-1cm0h819.js} +3 -3
  6. package/dist/cli/{chunk-nbyvgnnm.js → chunk-1rkxzg3n.js} +104 -47
  7. package/dist/cli/chunk-1rkxzg3n.js.map +36 -0
  8. package/dist/cli/{chunk-desvm72z.js → chunk-2nr29y95.js} +22 -26
  9. package/dist/cli/chunk-2nr29y95.js.map +13 -0
  10. package/dist/cli/{chunk-32khwcb6.js → chunk-49r370mb.js} +42 -69
  11. package/dist/cli/{chunk-32khwcb6.js.map → chunk-49r370mb.js.map} +9 -10
  12. package/dist/cli/{chunk-ms9z66nz.js → chunk-4rftmttv.js} +2 -2
  13. package/dist/cli/{chunk-s0bdnc7v.js → chunk-79bzzkx4.js} +5 -4
  14. package/dist/cli/chunk-79bzzkx4.js.map +10 -0
  15. package/dist/cli/{chunk-hvcxng5f.js → chunk-7tekknev.js} +6 -6
  16. package/dist/cli/{chunk-dv2z0rzf.js → chunk-8b489v1b.js} +38 -13
  17. package/dist/cli/{chunk-dv2z0rzf.js.map → chunk-8b489v1b.js.map} +5 -4
  18. package/dist/cli/{chunk-0pmb4tr4.js → chunk-8f8xw4x2.js} +1195 -222
  19. package/dist/cli/chunk-8f8xw4x2.js.map +44 -0
  20. package/dist/cli/chunk-96snx8yy.js +329 -0
  21. package/dist/cli/chunk-96snx8yy.js.map +13 -0
  22. package/dist/cli/{chunk-ckrcm08h.js → chunk-a3ydrqny.js} +48 -62
  23. package/dist/cli/{chunk-ckrcm08h.js.map → chunk-a3ydrqny.js.map} +4 -5
  24. package/dist/cli/{chunk-bxwhtvw8.js → chunk-ahc5gc9h.js} +5 -5
  25. package/dist/cli/{chunk-dpy8qdjf.js → chunk-as09pafp.js} +73 -182
  26. package/dist/cli/chunk-as09pafp.js.map +18 -0
  27. package/dist/cli/{chunk-tfycddbt.js → chunk-bjk964fp.js} +2 -2
  28. package/dist/cli/{chunk-gfc5fqvf.js → chunk-br0n8n3e.js} +18 -18
  29. package/dist/cli/{chunk-gfc5fqvf.js.map → chunk-br0n8n3e.js.map} +1 -1
  30. package/dist/cli/{chunk-297ajdcv.js → chunk-c69tw29j.js} +6 -6
  31. package/dist/cli/{chunk-297ajdcv.js.map → chunk-c69tw29j.js.map} +3 -3
  32. package/dist/cli/chunk-cn3dy3hz.js +146 -0
  33. package/dist/cli/chunk-cn3dy3hz.js.map +11 -0
  34. package/dist/cli/{chunk-124fvmzq.js → chunk-dm4mg9bj.js} +11064 -8742
  35. package/dist/cli/{chunk-124fvmzq.js.map → chunk-dm4mg9bj.js.map} +78 -64
  36. package/dist/cli/{chunk-dqhg54mr.js → chunk-dmx6cezx.js} +1 -1
  37. package/dist/cli/{chunk-60hz265r.js → chunk-dpcahcrb.js} +24 -11
  38. package/dist/cli/chunk-dpcahcrb.js.map +10 -0
  39. package/dist/cli/{chunk-a57yh0h9.js → chunk-dyk3r7jw.js} +7 -7
  40. package/dist/cli/{chunk-a57yh0h9.js.map → chunk-dyk3r7jw.js.map} +1 -1
  41. package/dist/cli/{chunk-twy7psdz.js → chunk-enk76qd5.js} +14 -10
  42. package/dist/cli/chunk-enk76qd5.js.map +10 -0
  43. package/dist/cli/{chunk-k3gr1nm9.js → chunk-fqy4n3q2.js} +13 -16
  44. package/dist/cli/{chunk-k3gr1nm9.js.map → chunk-fqy4n3q2.js.map} +3 -3
  45. package/dist/cli/{chunk-sc93apfs.js → chunk-frk2wdq1.js} +17 -17
  46. package/dist/cli/{chunk-sc93apfs.js.map → chunk-frk2wdq1.js.map} +2 -2
  47. package/dist/cli/{chunk-kdpzw38a.js → chunk-gprwej7h.js} +95 -7
  48. package/dist/cli/chunk-gprwej7h.js.map +11 -0
  49. package/dist/cli/chunk-kkrteb15.js +157 -0
  50. package/dist/cli/chunk-kkrteb15.js.map +10 -0
  51. package/dist/cli/{chunk-ave1wcbr.js → chunk-m9bp2hc1.js} +1 -1
  52. package/dist/cli/{chunk-58bt3hag.js → chunk-mrvh4c88.js} +4 -4
  53. package/dist/cli/{chunk-5cmts6dv.js → chunk-p8zg3a2h.js} +7 -7
  54. package/dist/cli/{chunk-5cmts6dv.js.map → chunk-p8zg3a2h.js.map} +1 -1
  55. package/dist/cli/{chunk-0w6hs479.js → chunk-rkqae7kh.js} +30 -18
  56. package/dist/cli/chunk-rkqae7kh.js.map +10 -0
  57. package/dist/cli/{chunk-bqxaqn5p.js → chunk-t5xx74rd.js} +8 -8
  58. package/dist/cli/{chunk-bqxaqn5p.js.map → chunk-t5xx74rd.js.map} +1 -1
  59. package/dist/cli/{chunk-1f882900.js → chunk-th0pgaj5.js} +2 -2
  60. package/dist/cli/{chunk-dhwtqnnd.js → chunk-vnbvhgmd.js} +2 -2
  61. package/dist/cli/{chunk-gxtg6ed2.js → chunk-vzy9rh6y.js} +21 -21
  62. package/dist/cli/{chunk-gxtg6ed2.js.map → chunk-vzy9rh6y.js.map} +3 -3
  63. package/dist/cli/{chunk-j1x92mx9.js → chunk-w6e455pz.js} +1 -1
  64. package/dist/cli/chunk-wehv26ry.js +155 -0
  65. package/dist/cli/chunk-wehv26ry.js.map +11 -0
  66. package/dist/cli/{chunk-y4msrwm9.js → chunk-xgq4eytj.js} +15 -15
  67. package/dist/cli/{chunk-36bxs9c7.js → chunk-xt0qvz1t.js} +27 -23
  68. package/dist/cli/chunk-xt0qvz1t.js.map +10 -0
  69. package/dist/cli/index.js +78 -42
  70. package/dist/cli/index.js.map +7 -6
  71. package/dist/types/ai/changelog-components.d.ts +18 -0
  72. package/dist/types/ai/changelog-markdown.d.ts +12 -0
  73. package/dist/types/ai/openapi-components.d.ts +7 -7
  74. package/dist/types/ai/operation-details.d.ts +39 -0
  75. package/dist/types/ai/relative-links.d.ts +3 -1
  76. package/dist/types/ai/serializers.d.ts +6 -4
  77. package/dist/types/analytics/head.d.ts +4 -2
  78. package/dist/types/analytics/script.d.ts +8 -2
  79. package/dist/types/components/content/youtube.d.ts +20 -0
  80. package/dist/types/components/layout/nav-utils.d.ts +8 -0
  81. package/dist/types/components/openapi/example-panels.d.ts +33 -0
  82. package/dist/types/components/openapi/helpers.d.ts +53 -11
  83. package/dist/types/components/openapi/snippets.d.ts +7 -1
  84. package/dist/types/core/alert-diagnostics.d.ts +10 -0
  85. package/dist/types/core/builtin-tags.d.ts +16 -0
  86. package/dist/types/core/changelog-list.d.ts +48 -0
  87. package/dist/types/core/closest-match.d.ts +6 -0
  88. package/dist/types/core/code-fence-diagnostics.d.ts +18 -0
  89. package/dist/types/core/code-fences.d.ts +8 -1
  90. package/dist/types/core/config-input.d.ts +1 -1
  91. package/dist/types/core/content-assets.d.ts +16 -18
  92. package/dist/types/core/diagnostics.d.ts +14 -0
  93. package/dist/types/core/directive-diagnostics.d.ts +22 -8
  94. package/dist/types/core/former-routes.d.ts +14 -0
  95. package/dist/types/core/github.d.ts +5 -4
  96. package/dist/types/core/heading-markers.d.ts +65 -2
  97. package/dist/types/core/hidden-pages.d.ts +3 -0
  98. package/dist/types/core/includes.d.ts +23 -0
  99. package/dist/types/core/last-modified.d.ts +18 -6
  100. package/dist/types/core/links.d.ts +42 -2
  101. package/dist/types/core/load-module.d.ts +19 -4
  102. package/dist/types/core/manifest.d.ts +14 -1
  103. package/dist/types/core/markdown-mirrors.d.ts +22 -0
  104. package/dist/types/core/mdx-elements.d.ts +32 -0
  105. package/dist/types/core/mdx-syntax.d.ts +100 -0
  106. package/dist/types/core/meta-diagnostics.d.ts +7 -0
  107. package/dist/types/core/meta.d.ts +32 -5
  108. package/dist/types/core/nav-diagnostics.d.ts +9 -2
  109. package/dist/types/core/navigation.d.ts +13 -0
  110. package/dist/types/core/ordering-prefix.d.ts +6 -0
  111. package/dist/types/core/pinned-ids.d.ts +23 -0
  112. package/dist/types/core/project-graph.d.ts +9 -1
  113. package/dist/types/core/redirect-patterns.d.ts +18 -0
  114. package/dist/types/core/relative-files.d.ts +31 -0
  115. package/dist/types/core/schema.d.ts +267 -7
  116. package/dist/types/core/sources/normalize.d.ts +101 -9
  117. package/dist/types/core/sources/types.d.ts +16 -0
  118. package/dist/types/core/static-files.d.ts +10 -0
  119. package/dist/types/core/static-host.d.ts +42 -0
  120. package/dist/types/core/svg-images.d.ts +26 -0
  121. package/dist/types/core/syntax-diagnostics.d.ts +14 -0
  122. package/dist/types/core/types.d.ts +42 -1
  123. package/dist/types/deploy/generated-files.d.ts +41 -0
  124. package/dist/types/deploy/redirects.d.ts +21 -7
  125. package/dist/types/markdown/code-title.d.ts +9 -5
  126. package/dist/types/markdown/directives.d.ts +8 -0
  127. package/dist/types/markdown/fence-language.d.ts +51 -0
  128. package/dist/types/markdown/fence-meta.d.ts +5 -0
  129. package/dist/types/markdown/github-alerts.d.ts +35 -0
  130. package/dist/types/openapi/asyncapi.d.ts +6 -5
  131. package/dist/types/openapi/checks.d.ts +10 -5
  132. package/dist/types/openapi/model.d.ts +55 -8
  133. package/dist/types/openapi/parse.d.ts +4 -1
  134. package/dist/types/sources/filesystem.d.ts +14 -4
  135. package/dist/types/sources/mdx-remote.d.ts +6 -3
  136. package/dist/types/sources/registry.d.ts +3 -3
  137. package/dist/types/theme/color.d.ts +33 -0
  138. package/dist/types/theme/palette.d.ts +122 -0
  139. package/docs/01-quickstart.mdx +1 -1
  140. package/docs/02-deployment.mdx +23 -8
  141. package/docs/04-migrating.mdx +16 -4
  142. package/docs/advanced/changelog.mdx +27 -1
  143. package/docs/advanced/skills.mdx +1 -1
  144. package/docs/cli/audit.mdx +1 -1
  145. package/docs/cli/doctor.mdx +2 -2
  146. package/docs/cli/evals.mdx +1 -1
  147. package/docs/cli/index.mdx +4 -2
  148. package/docs/cli/validate.mdx +10 -7
  149. package/docs/configuration/analytics.mdx +2 -0
  150. package/docs/configuration/customization.mdx +3 -1
  151. package/docs/configuration/index.mdx +2 -2
  152. package/docs/configuration/theming.mdx +5 -1
  153. package/docs/content/components.mdx +26 -6
  154. package/docs/content/frontmatter.mdx +1 -1
  155. package/docs/content/includes.mdx +6 -2
  156. package/docs/content/index.mdx +11 -1
  157. package/docs/content/islands.mdx +2 -0
  158. package/docs/content/meta.mdx +9 -3
  159. package/docs/content/navigation.mdx +14 -6
  160. package/docs/content/sources/obsidian.mdx +1 -1
  161. package/docs/content/sources/remote-mdx.mdx +3 -1
  162. package/docs/content/syntax.mdx +76 -8
  163. package/docs/content/variables.mdx +3 -1
  164. package/docs/discoverability/agent-discovery.mdx +1 -1
  165. package/docs/discoverability/json-api.mdx +1 -1
  166. package/docs/discoverability/llms-txt.mdx +1 -1
  167. package/docs/discoverability/markdown.mdx +2 -2
  168. package/docs/discoverability/open-graph.mdx +1 -1
  169. package/docs/discoverability/sitemap-and-robots.mdx +1 -1
  170. package/docs/references/graphql.mdx +2 -0
  171. package/docs/references/openapi.mdx +45 -7
  172. package/package.json +10 -9
  173. package/skills/blume/SKILL.md +2 -2
  174. package/skills/blume-migrate/SKILL.md +49 -27
  175. package/skills/blume-migrate/references/docsify.md +262 -0
  176. package/skills/blume-migrate/references/docus.md +298 -0
  177. package/skills/blume-migrate/references/docusaurus.md +3 -3
  178. package/skills/blume-migrate/references/fern.md +327 -0
  179. package/skills/blume-migrate/references/fumadocs.md +1 -1
  180. package/skills/blume-migrate/references/gitbook.md +267 -0
  181. package/skills/blume-migrate/references/github-wiki.md +259 -0
  182. package/skills/blume-migrate/references/jekyll.md +253 -0
  183. package/skills/blume-migrate/references/mdbook.md +256 -0
  184. package/skills/blume-migrate/references/mintlify.md +5 -5
  185. package/skills/blume-migrate/references/mkdocs.md +236 -0
  186. package/skills/blume-migrate/references/monorepo.md +20 -8
  187. package/skills/blume-migrate/references/nextra.md +2 -2
  188. package/skills/blume-migrate/references/readme.md +257 -0
  189. package/skills/blume-migrate/references/redocly.md +198 -0
  190. package/skills/blume-migrate/references/starlight.md +3 -3
  191. package/skills/blume-migrate/references/vitepress.md +204 -0
  192. package/skills/blume-migrate/references/vuepress.md +271 -0
  193. package/skills/blume-migrate/scripts/docsify-codemod.mjs +2387 -0
  194. package/skills/blume-migrate/scripts/docus-codemod.mjs +2542 -0
  195. package/skills/blume-migrate/scripts/fern-codemod.mjs +2633 -0
  196. package/skills/blume-migrate/scripts/github-wiki-codemod.mjs +2808 -0
  197. package/skills/blume-migrate/scripts/include-excerpts.mjs +649 -0
  198. package/skills/blume-migrate/scripts/jekyll-codemod.mjs +3649 -0
  199. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +17 -13
  200. package/skills/blume-migrate/scripts/mkdocs-codemod.mjs +2526 -0
  201. package/skills/blume-migrate/scripts/operation-routes.mjs +508 -0
  202. package/skills/blume-migrate/scripts/pin-heading-ids.mjs +1049 -0
  203. package/skills/blume-migrate/scripts/readme-codemod.mjs +4139 -0
  204. package/skills/blume-migrate/scripts/vitepress-codemod.mjs +1511 -0
  205. package/src/ai/changelog-components.ts +24 -0
  206. package/src/ai/changelog-markdown.ts +45 -41
  207. package/src/ai/component-markdown.ts +22 -9
  208. package/src/ai/llms.ts +36 -5
  209. package/src/ai/markdown.ts +2 -2
  210. package/src/ai/openapi-components.ts +25 -9
  211. package/src/ai/operation-details.ts +208 -0
  212. package/src/ai/relative-links.ts +48 -8
  213. package/src/ai/serializers.ts +7 -6
  214. package/src/ai/site-skill.ts +6 -2
  215. package/src/analytics/head.ts +11 -5
  216. package/src/analytics/script.ts +13 -3
  217. package/src/astro/generate.ts +32 -6
  218. package/src/astro/index.ts +1 -0
  219. package/src/astro/integration.ts +27 -1
  220. package/src/astro/mdx-source-errors.ts +46 -0
  221. package/src/astro/templates.ts +89 -185
  222. package/src/audit/checks/redirects.ts +5 -1
  223. package/src/audit/redirects.ts +36 -19
  224. package/src/audit/report.ts +22 -9
  225. package/src/audit/run.ts +12 -28
  226. package/src/audit/terms.ts +23 -0
  227. package/src/audit/url.ts +2 -2
  228. package/src/cli/build-failure.ts +256 -7
  229. package/src/cli/command-meta.ts +1 -1
  230. package/src/cli/commands/audit.ts +1 -1
  231. package/src/cli/commands/build.ts +12 -3
  232. package/src/cli/commands/check.ts +13 -2
  233. package/src/cli/commands/dev.ts +1 -1
  234. package/src/cli/commands/doctor.ts +4 -2
  235. package/src/cli/commands/init.ts +3 -3
  236. package/src/cli/commands/preview.ts +17 -1
  237. package/src/cli/commands/validate.ts +22 -10
  238. package/src/cli/prepare.ts +1 -1
  239. package/src/cli/preview-pages.ts +161 -0
  240. package/src/cli/unknown-flags.ts +1 -42
  241. package/src/components/code-copy.ts +51 -0
  242. package/src/components/code-language.ts +75 -0
  243. package/src/components/content/AccordionItem.astro +16 -1
  244. package/src/components/content/Callout.astro +11 -1
  245. package/src/components/content/Changelog.astro +189 -0
  246. package/src/components/content/CodeBlock.astro +8 -11
  247. package/src/components/content/Tabs.astro +16 -2
  248. package/src/components/content/YouTube.astro +6 -4
  249. package/src/components/content/code-block.ts +33 -0
  250. package/src/components/content/content-strings.ts +5 -1
  251. package/src/components/content/inline-markdown.ts +35 -8
  252. package/src/components/content/youtube.ts +49 -7
  253. package/src/components/layout/Analytics.astro +5 -1
  254. package/src/components/layout/NavTree.astro +8 -3
  255. package/src/components/layout/RootLayout.astro +18 -31
  256. package/src/components/layout/nav-utils.ts +14 -0
  257. package/src/components/layout/search/pagefind.ts +2 -1
  258. package/src/components/openapi/Operation.astro +4 -0
  259. package/src/components/openapi/PanelTabs.astro +9 -3
  260. package/src/components/openapi/RequestBody.astro +42 -8
  261. package/src/components/openapi/RequestPanel.astro +34 -53
  262. package/src/components/openapi/example-panels.ts +110 -0
  263. package/src/components/openapi/helpers.ts +195 -20
  264. package/src/components/openapi/operation-model.ts +63 -26
  265. package/src/components/openapi/request.ts +13 -0
  266. package/src/components/openapi/snippets.ts +14 -2
  267. package/src/core/alert-diagnostics.ts +89 -0
  268. package/src/core/builtin-tags.ts +76 -0
  269. package/src/core/changelog-list.ts +87 -0
  270. package/src/core/closest-match.ts +41 -0
  271. package/src/core/code-fence-diagnostics.ts +267 -0
  272. package/src/core/code-fences.ts +43 -7
  273. package/src/core/component-diagnostics.ts +65 -12
  274. package/src/core/config-input.ts +1 -1
  275. package/src/core/config.ts +46 -1
  276. package/src/core/content-assets.ts +64 -144
  277. package/src/core/diagnostics.ts +36 -3
  278. package/src/core/directive-diagnostics.ts +265 -47
  279. package/src/core/former-routes.ts +46 -0
  280. package/src/core/git-ignored.ts +53 -0
  281. package/src/core/github.ts +5 -4
  282. package/src/core/heading-markers.ts +156 -2
  283. package/src/core/hidden-pages.ts +64 -0
  284. package/src/core/includes.ts +181 -41
  285. package/src/core/last-modified.ts +47 -10
  286. package/src/core/links.ts +438 -83
  287. package/src/core/load-module.ts +98 -9
  288. package/src/core/manifest.ts +12 -9
  289. package/src/core/markdown-mirrors.ts +52 -0
  290. package/src/core/mdx-elements.ts +153 -0
  291. package/src/core/mdx-syntax.ts +377 -0
  292. package/src/core/meta-diagnostics.ts +134 -0
  293. package/src/core/meta.ts +154 -55
  294. package/src/core/nav-diagnostics.ts +16 -4
  295. package/src/core/navigation.ts +70 -10
  296. package/src/core/ordering-prefix.ts +22 -2
  297. package/src/core/pinned-ids.ts +141 -0
  298. package/src/core/project-graph.ts +221 -29
  299. package/src/core/redirect-patterns.ts +41 -0
  300. package/src/core/relative-files.ts +145 -0
  301. package/src/core/schema.ts +17 -3
  302. package/src/core/sources/mdx-remote.ts +75 -14
  303. package/src/core/sources/normalize.ts +885 -179
  304. package/src/core/sources/notion.ts +5 -4
  305. package/src/core/sources/obsidian.ts +2 -5
  306. package/src/core/sources/types.ts +17 -0
  307. package/src/core/static-files.ts +40 -0
  308. package/src/core/static-host.ts +125 -0
  309. package/src/core/svg-images.ts +98 -0
  310. package/src/core/syntax-diagnostics.ts +491 -0
  311. package/src/core/types.ts +43 -1
  312. package/src/deploy/artifacts.ts +2 -28
  313. package/src/deploy/generated-files.ts +128 -0
  314. package/src/deploy/platforms/vercel.ts +25 -1
  315. package/src/deploy/redirects.ts +43 -16
  316. package/src/deploy/sitemap.ts +2 -1
  317. package/src/markdown/base-links.ts +87 -6
  318. package/src/markdown/code-title.ts +29 -13
  319. package/src/markdown/content-assets.ts +210 -0
  320. package/src/markdown/directives.ts +69 -5
  321. package/src/markdown/fence-language.ts +106 -0
  322. package/src/markdown/fence-meta.ts +42 -0
  323. package/src/markdown/github-alerts.ts +172 -0
  324. package/src/markdown/heading-anchors.ts +265 -56
  325. package/src/markdown/include.ts +30 -3
  326. package/src/markdown/index.ts +145 -40
  327. package/src/markdown/prompt-text.ts +95 -0
  328. package/src/markdown/relative-links.ts +37 -2
  329. package/src/markdown/route-snapshot.ts +22 -12
  330. package/src/markdown/svg-fallback.ts +51 -0
  331. package/src/markdown/title-heading.ts +134 -0
  332. package/src/migrate/migrate.ts +127 -2
  333. package/src/og/card.ts +14 -4
  334. package/src/openapi/asyncapi.ts +14 -6
  335. package/src/openapi/checks.ts +160 -15
  336. package/src/openapi/model.ts +172 -33
  337. package/src/openapi/parse.ts +13 -4
  338. package/src/openapi/render-mdx.ts +55 -9
  339. package/src/openapi/source.ts +87 -6
  340. package/src/registry/eject.ts +0 -1
  341. package/src/search/documents.ts +10 -2
  342. package/src/sources/filesystem.ts +28 -3
  343. package/src/sources/mdx-remote.ts +7 -15
  344. package/src/theme/color.ts +14 -0
  345. package/src/theme/entry.ts +22 -1
  346. package/src/theme/palette.ts +74 -1
  347. package/dist/cli/chunk-0pmb4tr4.js.map +0 -35
  348. package/dist/cli/chunk-0w6hs479.js.map +0 -10
  349. package/dist/cli/chunk-150jk7km.js +0 -52
  350. package/dist/cli/chunk-150jk7km.js.map +0 -10
  351. package/dist/cli/chunk-36bxs9c7.js.map +0 -10
  352. package/dist/cli/chunk-53jqw8z0.js +0 -89
  353. package/dist/cli/chunk-53jqw8z0.js.map +0 -10
  354. package/dist/cli/chunk-60hz265r.js.map +0 -10
  355. package/dist/cli/chunk-desvm72z.js.map +0 -14
  356. package/dist/cli/chunk-dpy8qdjf.js.map +0 -17
  357. package/dist/cli/chunk-es8h0v3j.js +0 -441
  358. package/dist/cli/chunk-es8h0v3j.js.map +0 -11
  359. package/dist/cli/chunk-kdpzw38a.js.map +0 -11
  360. package/dist/cli/chunk-nbyvgnnm.js.map +0 -36
  361. package/dist/cli/chunk-s0bdnc7v.js.map +0 -10
  362. package/dist/cli/chunk-twy7psdz.js.map +0 -10
  363. package/dist/cli/chunk-ydp3065p.js +0 -64
  364. package/dist/cli/chunk-ydp3065p.js.map +0 -10
  365. /package/dist/cli/{chunk-74wfhv79.js.map → chunk-05f21r8h.js.map} +0 -0
  366. /package/dist/cli/{chunk-9qzgg6mj.js.map → chunk-1cm0h819.js.map} +0 -0
  367. /package/dist/cli/{chunk-ms9z66nz.js.map → chunk-4rftmttv.js.map} +0 -0
  368. /package/dist/cli/{chunk-hvcxng5f.js.map → chunk-7tekknev.js.map} +0 -0
  369. /package/dist/cli/{chunk-bxwhtvw8.js.map → chunk-ahc5gc9h.js.map} +0 -0
  370. /package/dist/cli/{chunk-tfycddbt.js.map → chunk-bjk964fp.js.map} +0 -0
  371. /package/dist/cli/{chunk-dqhg54mr.js.map → chunk-dmx6cezx.js.map} +0 -0
  372. /package/dist/cli/{chunk-ave1wcbr.js.map → chunk-m9bp2hc1.js.map} +0 -0
  373. /package/dist/cli/{chunk-58bt3hag.js.map → chunk-mrvh4c88.js.map} +0 -0
  374. /package/dist/cli/{chunk-1f882900.js.map → chunk-th0pgaj5.js.map} +0 -0
  375. /package/dist/cli/{chunk-dhwtqnnd.js.map → chunk-vnbvhgmd.js.map} +0 -0
  376. /package/dist/cli/{chunk-j1x92mx9.js.map → chunk-w6e455pz.js.map} +0 -0
  377. /package/dist/cli/{chunk-y4msrwm9.js.map → chunk-xgq4eytj.js.map} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blume",
3
- "version": "2.1.2",
3
+ "version": "2.2.0",
4
4
  "description": "The open-source docs framework for humans and agents.",
5
5
  "keywords": [
6
6
  "agents",
@@ -113,19 +113,19 @@
113
113
  "dependencies": {
114
114
  "@astrojs/check": "^0.9.10",
115
115
  "@astrojs/markdown-satteri": "^0.4.1",
116
- "@astrojs/mdx": "^8.0.1",
117
- "@astrojs/node": "^11.1.5",
116
+ "@astrojs/mdx": "^8.0.2",
117
+ "@astrojs/node": "^11.1.6",
118
118
  "@astrojs/react": "^7.0.0",
119
119
  "@astrojs/vercel": "^11.0.11",
120
120
  "@clack/prompts": "^1.8.0",
121
- "@iconify-json/lucide": "^1.2.131",
121
+ "@iconify-json/lucide": "^1.2.140",
122
122
  "@iconify/types": "^2.0.0",
123
123
  "@iconify/utils": "^3.1.7",
124
124
  "@modelcontextprotocol/sdk": "^1.30.0",
125
125
  "@orama/orama": "^3.1.18",
126
126
  "@pierre/diffs": "^1.5.1",
127
127
  "@scalar/astro": "^0.4.26",
128
- "@scalar/openapi-parser": "^0.29.1",
128
+ "@scalar/openapi-parser": "^0.29.11",
129
129
  "@scalar/openapi-types": "^0.9.5",
130
130
  "@shikijs/transformers": "^4.4.3",
131
131
  "@shikijs/twoslash": "^4.4.3",
@@ -134,6 +134,7 @@
134
134
  "@types/mdast": "^4.0.4",
135
135
  "@types/node": "^22.20.2",
136
136
  "@vercel/analytics": "^2.0.1",
137
+ "@vitejs/plugin-react": "6.1.1",
137
138
  "ai": "^7.0.99",
138
139
  "astro": "^7.3.2",
139
140
  "chokidar": "^5.0.0",
@@ -141,7 +142,7 @@
141
142
  "consola": "^3.4.2",
142
143
  "cross-spawn": "^7.0.6",
143
144
  "dompurify": "^3.4.16",
144
- "dotenv": "^17.4.2",
145
+ "dotenv": "^18.0.5",
145
146
  "epub-gen-memory": "^1.1.2",
146
147
  "es-module-lexer": "^3.0.2",
147
148
  "fast-xml-parser": "^5.11.1",
@@ -178,7 +179,7 @@
178
179
  "robots-parser": "^3.0.1",
179
180
  "satteri": "^0.10.5",
180
181
  "semver": "^7.8.5",
181
- "sharp": "^0.35.4",
182
+ "sharp": "^0.35.5",
182
183
  "shiki": "^4.4.3",
183
184
  "simple-icons": "^16.30.0",
184
185
  "string-width": "^8.2.2",
@@ -189,7 +190,7 @@
189
190
  "twoslash": "^0.3.9",
190
191
  "typescript": "^6.0.3",
191
192
  "ufo": "^1.6.4",
192
- "undici": "^7.29.1",
193
+ "undici": "^8.11.2",
193
194
  "write-file-atomic": "^7.0.1",
194
195
  "zod": "^4.6.2"
195
196
  },
@@ -296,6 +297,6 @@
296
297
  }
297
298
  },
298
299
  "engines": {
299
- "node": ">=22.12.0"
300
+ "node": ">=22.19.0"
300
301
  }
301
302
  }
@@ -18,7 +18,7 @@ The core idea: **the framework _is_ the template.** There's no starter to clone
18
18
 
19
19
  ## Quickstart
20
20
 
21
- Blume needs **Node.js 22.12 or newer**. From an empty folder:
21
+ Blume needs **Node.js 22.19 or newer**. From an empty folder:
22
22
 
23
23
  ```bash
24
24
  npx blume init # scaffold docs/index.mdx, blume.config.ts, and package.json scripts, then install
@@ -59,7 +59,7 @@ It bumps `blume` in `package.json`, installs, and lists every config change stil
59
59
 
60
60
  ## Migrating from another framework
61
61
 
62
- To move a Mintlify, Fumadocs, Docusaurus, Starlight, or Nextra site to Blume, the user runs `npx blume migrate [source] --codex` (or `--claude`) from that project, which opens an agent on the `blume-migrate` skill. When you are that agent, or the user asks you to migrate directly, follow `skills/blume-migrate/SKILL.md` in the installed package instead of this file.
62
+ To move a Mintlify, Docusaurus, GitBook, MkDocs, ReadMe, or other docs site to Blume, the user runs `npx blume migrate [source] --codex` (or `--claude`) from that project, which opens an agent on the `blume-migrate` skill. When you are that agent, or the user asks you to migrate directly, follow `skills/blume-migrate/SKILL.md` in the installed package instead of this file.
63
63
 
64
64
  Every build publishes a generated agent skill for the site at `/skill.md` (`agents.skillMd`). To write a richer one from the docs, the user runs `blume skill --codex` (or `--claude`), which opens an agent on the `blume-write-skill` skill. When you are that agent, or the user asks you to write the site's skill directly, follow `skills/blume-write-skill/SKILL.md` in the installed package.
65
65
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: blume-migrate
3
- description: Migrate an existing documentation site (Mintlify, Docusaurus, Fumadocs, Nextra, Starlight, or any docs framework) to Blume, the markdown-first docs framework on Astro. Translate the source config to blume.config.ts, restructure content into Blume's filesystem-derived navigation, rewrite JSX callouts to directives, convert icons to Lucide, and inline snippets. Use when the user asks to migrate/convert/port a docs repo to Blume, or when the repo has a docs.json/mint.json, docusaurus.config.*, meta.json with fumadocs, _meta.* with nextra, or an astro.config.* with starlight().
3
+ description: Migrate an existing documentation site (Mintlify, Docusaurus, Fumadocs, Nextra, Starlight, VitePress, VuePress, Docus, MkDocs, mdBook, Fern, GitBook, ReadMe, Redocly, Docsify, Jekyll (Just the Docs), GitHub Wiki, or any docs framework) to Blume, the markdown-first docs framework on Astro. Translate the source config to blume.config.ts, restructure content into Blume's filesystem-derived navigation, rewrite JSX callouts to directives, convert icons to Lucide, and inline snippets. Use when the user asks to migrate/convert/port a docs repo to Blume, or when the repo has a docs.json/mint.json, docusaurus.config.*, meta.json with fumadocs, _meta.* with nextra, an astro.config.* with starlight(), a .vitepress/config.* or .vuepress/config.*, an mkdocs.yml or zensical.toml, a book.toml (mdBook), a fern/docs.yml, a GitBook .gitbook.yaml/gitbook-docs.yaml, ReadMe _order.yaml files in docs/ or reference/, a Redocly sidebars.yaml or @redocly/realm dependency, an index.html that sets window.$docsify, a docus or @nuxt-themes/docus dependency, a Jekyll _config.yml (Just the Docs theme), or a GitHub wiki clone (Home.md with _Sidebar.md, [[Page Name]] links).
4
4
  ---
5
5
 
6
6
  # Migrate to Blume
@@ -26,15 +26,27 @@ Throughout this skill (including the `references/` files), **`<skill>` means the
26
26
  - `meta.json` + `fumadocs-*` deps (content under `content/docs/`) → **Fumadocs** (`references/fumadocs.md`).
27
27
  - `_meta.{js,ts,json}` + `nextra` deps → **Nextra** (`references/nextra.md`).
28
28
  - `astro.config.*` calling `starlight({…})` → **Starlight** (`references/starlight.md`).
29
+ - `.vitepress/config.{js,ts,mjs,mts}` (often `docs/.vitepress/`) or a `vitepress` dependency → **VitePress** (`references/vitepress.md`). `.vuepress/` is VuePress (next bullet).
30
+ - `.vuepress/config.{js,ts,mjs}` (v1 also `config.yml`/`config.toml`; often `docs/.vuepress/`), a `vuepress.config.*`, or a `vuepress` dependency → **VuePress** (`references/vuepress.md`). It builds on `references/vitepress.md` for the Markdown layer the two share, so read both.
31
+ - A `docus` dependency (often in `docs/package.json` beside a `nuxt.config.ts` with `extends: ['docus']`), or `@nuxt-themes/docus` for 1.x → **Docus** (`references/docus.md`). Pages are MDC, Nuxt Content's component syntax: `::note` … `::` is not a Blume directive, and Blume prints it as text (`BLUME_MDC_SYNTAX` warns at its line).
32
+ - `mkdocs.yml`/`mkdocs.yaml` (also Zensical's `zensical.toml` and ProperDocs' `properdocs.yml`) → **MkDocs / Material for MkDocs** (`references/mkdocs.md`): nav declared in config, Python-Markdown extension syntax (`!!!` admonitions, `===` tabs, `--8<--` snippets), and Python deps, so usually no `package.json`. Run `node <skill>/scripts/mkdocs-codemod.mjs --write <content-dir>` first, then `pin-heading-ids.mjs` after the first build.
33
+ - `fern/docs.yml` (beside `fern/fern.config.json`) → **Fern** (`references/fern.md`). A `fern/` folder without `docs.yml` only generates SDKs: the docs are elsewhere.
34
+ - `.gitbook.yaml`/`.gitbook.yml`, `gitbook-docs.yaml`, or a `SUMMARY.md` beside a `.gitbook/` folder → **GitBook** (`references/gitbook.md`). `book.json` + `SUMMARY.md` is the legacy GitBook CLI or HonKit (same reference, its own section); `book.toml` + `src/SUMMARY.md` is mdBook (`references/mdbook.md`).
35
+ - `book.toml` (at the book root, often `docs/`, `book/`, or `guide/` in a code repo; chapters in its `[book] src`, beside a `SUMMARY.md`) → **mdBook** (`references/mdbook.md`). Every URL ends in `.html`, `SUMMARY.md` is the whole nav, and `{{#include}}` pulls anchored code from across the repo: build the old book first, since it's the URL list, the old ids, and the only record of what the includes showed.
36
+ - `sidebars.yaml` or an `@redocly/realm`-family dependency, usually beside a `redocly.yaml` and with Markdoc `{% … %}` tags in pages → **Redocly** (`references/redocly.md`). GitBook pages use `{% … %}` tags too, so check for GitBook's files first. A `redocly.yaml` holding only lint/bundle keys (`extends`, `rules`, `decorators`, `apis`) is Redocly CLI config, not a docs site: keep it and detect the framework from the other signals.
37
+ - `_order.yaml` files in `docs/`, `reference/`, `recipes/`, or `custom_pages/` (ReadMe's bi-directional Git sync), or a CI step running `readmeio/rdme` beside pages with `category`/`parentDoc` (or `category.uri`/`parent.uri`) frontmatter (`rdme` uploads) → **ReadMe** (`references/readme.md`). ReadMe keeps settings, redirects, the glossary, and variables in its dashboard, not the repo, and changelogs only on the `main` branch.
38
+ - An `index.html` setting `window.$docsify` (usually in `docs/`, beside `_sidebar.md`), or `docsify-cli` in `package.json` → **Docsify** (`references/docsify.md`). Run `node <skill>/scripts/docsify-codemod.mjs --write --snapshot .docsify-old --group-folders <docs-dir>` first, then `pin-heading-ids.mjs --old .docsify-old --map docsify-routes.json` after the first build. Its default URLs live after `#` (`/#/page`), which no server-side redirect can match; the reference adds a client-side script.
39
+ - `_config.yml` (root or `docs/`) → **Jekyll** (`references/jekyll.md`); Just the Docs is `just-the-docs` in `_config.yml` or the `Gemfile`. Build the old site first with a project-local bundle: the nav comes from front matter (`parent`/`nav_order`) and is rebuilt from the old build's sidebar, and URLs end in `/` or `.html`. A `_toc.yml` beside it is Jupyter Book, not Jekyll. Run `node <skill>/scripts/jekyll-codemod.mjs --old <old build> --write .` from the Jekyll source dir first, then `pin-heading-ids.mjs --old <old build>` after the first build.
40
+ - A clone of `<repo>.wiki.git` (`Home.md` at the root, often `_Sidebar.md` and `_Footer.md`, `[[Page Name]]` links, no config) → **GitHub Wiki** (`references/github-wiki.md`). Never detected: the source is named. Its old URLs stay on github.com, which can't redirect, so the reference writes a route table and link stubs for the old wiki instead of `redirects`. Run `node <skill>/scripts/github-wiki-codemod.mjs routes --repo <owner>/<repo> --write` first, classify `wiki-routes.json`, then `convert --repo <owner>/<repo> --write`.
29
41
  - Anything else → apply this file's mental model directly; there's no framework-specific reference, so inventory by hand.
30
42
  - **Also note the host repo, independent of source framework:** a pnpm/Turbo workspace, a non-`docs/` content layout, or a Vercel deploy each need integration steps (`content.root` scoping, `minimumReleaseAge`, lockfile, `vercel.json`, an Astro/Vite patch) — all in `references/monorepo.md`. Read it whenever the target isn't a bare single-package docs folder.
31
- 2. **Inventory the repo** before changing anything: the config file(s), the content tree, the nav definition, snippets/partials/includes, static assets, OpenAPI/AsyncAPI specs and GraphQL schemas, redirects, i18n locales, custom components, and icon usage. Note what's declared vs. defaulted.
43
+ 2. **Inventory the repo** before changing anything (a page whose body is only a `<meta http-equiv="refresh">` is a redirect, not a page: add the redirect from its old URL and report it): the config file(s), the content tree, the nav definition, snippets/partials/includes, static assets, OpenAPI/AsyncAPI specs and GraphQL schemas, redirects, i18n locales, custom components, and icon usage. Note what's declared vs. defaulted. Pages the old site published by accident (a `.github/` template, files under dot-folders, planning notes) aren't content: leave them out, add no redirects, and list them in the report.
32
44
  3. **Write `blume.config.ts`** with `defineConfig` from `blume`. Map only declared fields (see the reference's mapping table); rely on defaults everywhere else. A minimal result is `defineConfig({ title: "…" })`.
33
- 4. **Restructure content.** Choose `content.root` (default `docs`) — **detect where `.md`/`.mdx` actually live, don't assume a `docs/` folder.** Many repos keep content directly under an app dir (`apps/docs/api/`, `.../getting-started/`) with no `docs/` subfolder; when so, set `content.root` to that dir and scope `content.include` to the real content folders rather than leaving a bare `content.root: "."` that scans everything (see `references/monorepo.md` §1). Order with numeric prefixes (`01-intro.mdx`), group without a URL segment via `(group)/` folders, and add a `meta.ts` (`defineMeta`) only where filesystem order isn't enough. **A source that already declares per-folder navigation in a sidecar file — Fumadocs `meta.json`, Nextra `_meta.*` — _is_ that case: convert each one to a `meta.ts`, carrying over its title/icon/order/collapse, rather than dropping it and hoping filenames reproduce the intent. Filesystem inference is the fallback for folders that declare no per-folder nav, never a reason to discard one that does.** Reach for an explicit `navigation.sidebar` only when the source nav genuinely can't be expressed by files. **Reshaping into folder-per-tab moves URLs** — track every old→new path as you go; you'll turn them into `redirects` in step 5.
34
- 5. **Rewrite pages.** Map frontmatter to Blume's strict schema; convert callout JSX to `:::` directives — **directives (and math/mermaid/package-install fences) are MDX-only, so rename any `.md` page that needs them to `.mdx`**; rename components; turn snippets/partials into `<include>` statements or inline them (Blume has no import-based includes: `import Snippet from "…"` plus `<Snippet />` has to become one or the other); fix asset paths; **rewrite internal links** to their new routes (including OpenAPI/GraphQL operation links — see the API-reference sections, their slugs differ from most sources); **add a `redirects` entry for every route you moved** in step 4; **convert every icon name to Lucide** (Blume is Lucide-only — no FontAwesome/Tabler). Remove any duplicated H1 in the body (`title` renders the H1; bodies start at `##`). **If the source has a hand-maintained changelog and the repo is open source on GitHub, offer to swap it for the `githubReleases()` source** (see "Changelogs" below) rather than porting the entries. For **Mintlify**, run the bundled codemod first — `node <skill>/scripts/mintlify-codemod.mjs --write <content-dir>` deterministically remaps icons and drops/renames unsupported frontmatter keys, and reports the rest (unknown icons, OpenAPI-stub flags) for you to finish by hand (see `references/mintlify.md`).
35
- 6. **Adopt `package.json`.** Repoint `dev`/`build`/`start` → `blume dev`/`blume build`/`blume preview`, remove the old framework's deps, add `blume`. A config-only source (e.g. a bare Mintlify `docs.json`) has no manifest — scaffold one. **In a pnpm workspace:** if `pnpm-workspace.yaml`/`.npmrc` sets `minimumReleaseAge`, add **only** `blume` to `minimumReleaseAgeExclude` (don't disable the guard) so the just-published version installs. **Always regenerate the lockfile in the same change:** after editing deps run a plain `pnpm install` (from the workspace root) and commit `pnpm-lock.yaml` alongside `package.json` — CI/Vercel use `--frozen-lockfile`, so a stale lockfile fails the build before it starts. **If the repo uses (or the user wants) [Ultracite](https://www.ultracite.ai) for formatting:** its oxfmt formatter mangles the `:::` directives you just wrote unless you ship the bundled `assets/oxfmt@0.71.0.patch` and register it under `patchedDependencies` — see `references/monorepo.md` §6. See `references/monorepo.md` §2–3.
36
- 7. **Wire up the host repo & deploy (non-trivial repos).** For a monorepo on Vercel, emit the root-aware install/build recipe and `apps/docs/vercel.json`, and tell the user the two settings you can't commit (Vercel Root Directory, Node 22). If the workspace pins Vite and `blume build` crashes inside Astro/Vite, apply the pnpm-patch workaround. All copy-pasteable in `references/monorepo.md` §4–5.
37
- 8. **Verify.** Run `blume build` (frontmatter schema, duplicate routes, config — it fails on any error diagnostic by default; **never pass `--no-strict`**, which builds anyway and silently drops invalid pages) and `blume validate --strict` (internal links, heading anchors, assets — the link checker lives in `validate`, not `build`), fix diagnostics, then `blume dev` for a visual pass. End with a written summary of what was migrated, dropped, and approximated — **and every repo-specific edit you made** (pnpm-workspace, vercel.json, config globs) with the reason, plus any manual step left to the user (the Astro patch, Vercel dashboard settings).
45
+ 4. **Restructure content.** Put `blume.config.ts` in the directory whose `package.json` runs the old docs build, which may be a workspace package such as `packages/docs` rather than the repo root. Choose `content.root` (default `docs`) — **detect where `.md`/`.mdx` actually live, don't assume a `docs/` folder.** Many repos keep content directly under an app dir (`apps/docs/api/`, `.../getting-started/`) with no `docs/` subfolder; when so, set `content.root` to that dir and scope `content.include` to the real content folders rather than leaving a bare `content.root: "."` that scans everything (see `references/monorepo.md` §1). Order with numeric prefixes (`01-intro.mdx`), group without a URL segment via `(group)/` folders, and add a `meta.ts` (`defineMeta`) only where filesystem order isn't enough. **A source that already declares per-folder navigation in a sidecar file — Fumadocs `meta.json`, Nextra `_meta.*` — _is_ that case: convert each one to a `meta.ts`, carrying over its title/icon/order/collapse, rather than dropping it and hoping filenames reproduce the intent. Filesystem inference is the fallback for folders that declare no per-folder nav, never a reason to discard one that does.** A group that exists only in the source's nav config, with its pages flat on disk, becomes a **`(group)/` folder**: it adds the sidebar group and keeps every URL, with no redirects (moving pages a folder deeper instead changes their URLs and breaks their relative asset paths). Reach for an explicit `navigation.sidebar` only when the source nav genuinely can't be expressed by files. **Reshaping into folder-per-tab moves URLs** — track every old→new path as you go; you'll turn them into `redirects` in step 5.
46
+ 5. **Rewrite pages.** Map frontmatter to Blume's strict schema; convert callout JSX to `:::` directives — **directives (and math/mermaid/package-install fences) are MDX-only, so rename any `.md` page that needs them to `.mdx`**; rename components; turn snippets/partials into `<include>` statements or inline them (Blume has no import-based includes: `import Snippet from "…"` plus `<Snippet />` has to become one or the other; when a source includes part of a file (mdBook `ANCHOR`s, VitePress `#region`s, MkDocs snippet line ranges) or a file outside the content root, copy `<skill>/scripts/include-excerpts.mjs` into the project and run it before every `dev`, `build`, and `validate` rather than pasting code the project compiles: it reports every missing anchor or region in one pass, and a restored marker in the source beats a line range, which goes stale silently); fix asset paths; **rewrite internal links** to their new routes (including OpenAPI/GraphQL operation links — see the API-reference sections, their slugs differ from most sources — and links inside OpenAPI descriptions, which Blume renders and validates, though a redirect `from` hides a stale one; rewrite links that hardcode the old site's own origin to root-relative paths); **add a `redirects` entry for every route you moved** in step 4 (a hash-routed source like Docsify also needs its reference's client-side redirect, because `redirects` only see paths); **convert every icon name to Lucide** (Blume is Lucide-only — no FontAwesome/Tabler). Remove any duplicated H1 in the body (`title` renders the H1; bodies start at `##`), and fix links to its anchor (`#<h1-slug>`), which no longer exists: drop the fragment. `blume validate --strict` also reports fragments the old site never checked; fix each or drop it, and report it. A page with no frontmatter `title` takes its opening `# H1` as the title and shows it once, so that one can stay; moving it into `title` is still the idiomatic form. **If the source has a hand-maintained changelog and the repo is open source on GitHub, offer to swap it for the `githubReleases()` source** (see "Changelogs" below) rather than porting the entries. For **Mintlify**, run the bundled codemod first — `node <skill>/scripts/mintlify-codemod.mjs --write <content-dir>` deterministically remaps icons and drops/renames unsupported frontmatter keys, and reports the rest (unknown icons, OpenAPI-stub flags) for you to finish by hand (see `references/mintlify.md`). For **Docus**, run the bundled codemod first: `node <skill>/scripts/docus-codemod.mjs --write content` pairs MDC blocks as remark-mdc does, converts the Nuxt UI prose components, fence labels, icons, frontmatter, and `.navigation.yml` files, makes the MDX prose safe, and lists every `REVIEW` item (custom and landing-page components stay as written; check them against the live site, not only a local build; see `references/docus.md`). For **VitePress**, run the bundled codemod first: `node <skill>/scripts/vitepress-codemod.mjs --write <srcDir>` converts containers (paired as markdown-it pairs them), code groups, alerts, fence labels, `<<<` imports and includes, `.html` links, badges, and H1 titles, and lists every `REVIEW` item for you to finish (see `references/vitepress.md`). Before converting any source's components, check its built output: a component its theme never registered rendered nothing there (`<!---->`), so converting it shows content readers never saw; report it. For **mdBook**, convert every `{{#…}}` helper before anything else: anchored, line-range, and out-of-root code becomes generated excerpts, one-line files become `variables`; then strip hidden `# ` lines and `rust,…` fence attributes, and convert admonish blocks (see `references/mdbook.md`). For **Jekyll**, convert every Kramdown IAL (`{: .note }`) and Liquid tag: in `.md` they ship as literal text (a Liquid tag warns `BLUME_TEMPLATE_TAG`, an IAL `BLUME_MD_ATTRIBUTE_LIST`), and in `.mdx` they fail the build (`blume check` names each IAL's line as `BLUME_MDX_ATTRIBUTE_LIST` and a Liquid tag's as `BLUME_MDX_SYNTAX`, at the partial's own line for one in an included partial). Run the bundled codemod first: it converts callout IALs, the in-page TOC, H1s, front matter, Markdown includes, and Liquid links and variables, rebuilds the Just the Docs sidebar as folders, `slug`s, and `meta.ts`, and writes the redirects and variables to `jekyll-migration.json` (see `references/jekyll.md`). For a **GitHub Wiki**, run the bundled codemod (`routes`, then `convert`): it builds the route table (one flat route per page name) and converts every `[[…]]` link outside code, which Blume would otherwise render as literal text (`BLUME_WIKILINK_UNSUPPORTED` warns only for one that names a page or has a `|`; Markdown pages put the link text first: `[[text|Page]]`). For **Redocly**, convert every Markdoc `{% … %}` tag: Blume never parses them, so an unconverted tag ships as literal text in `.md` (`BLUME_TEMPLATE_TAG` warns at its line) and fails an `.mdx` build. For **VuePress**, rename every `README.md` to `index` first, then run the VitePress codemod (its limits on VuePress content are in `references/vuepress.md`), then grep for hyphenated theme tags (`<code-group>`, `<code-block>`, `<router-link>`): they pass through `.mdx` as unknown HTML elements with no diagnostic. For **MkDocs**, decide `.md` vs `.mdx` per page before converting: MDX rejects HTML comments, `<https://…>` autolinks, unclosed void tags, and `{…}` attribute lists that Python-Markdown accepts (`blume check` names each at its line: `BLUME_MDX_SYNTAX` for what MDX can't parse, and the last two as `BLUME_MDX_UNCLOSED_ELEMENT` and `BLUME_MDX_ATTRIBUTE_LIST`), and snippet paths resolve from the snippets `base_path` (default: where MkDocs runs), not the page (see `references/mkdocs.md`). For **Fern**, URLs come from `docs.yml`, not file paths: run `node <skill>/scripts/fern-codemod.mjs --sitemap old-urls.txt` before moving anything (dry run, then `--write`), and after the first build its `endpoints` step, which joins `operation-routes.mjs` output to Fern's old API URLs and writes `fern-redirects.json`; export a Fern Definition to OpenAPI with `fern export` (see `references/fern.md`). For **GitBook**, put each page at its old URL first (GitBook's URLs follow `SUMMARY.md`, not the file tree), then convert every `{% … %}` tag: in `.md` an unconverted tag ships as literal text (`BLUME_TEMPLATE_TAG` warns), and in `.mdx` it fails the build. For **ReadMe**, run the bundled codemod first: `node <skill>/scripts/readme-codemod.mjs --write --site <hub origin> --glossary <saved hub page> .` keeps every page at its flat URL, converts callouts, `doc:`/`ref:` links (`blume validate` names any left as `BLUME_UNSUPPORTED_LINK_SCHEME`), ReadMe components, and frontmatter, writes `meta.ts`, overlays, and redirects, and lists what needs a person; after the first build, its `--routes` pass adds the endpoint redirects and per-tag order (see `references/readme.md`). A pasted widget's JSX event handler (`onClick={…}`) builds green and never runs on a static page: `BLUME_MDX_EVENT_HANDLER` names each one, so move the widget into an island (`islands/`) or drop it and report it.
47
+ 6. **Adopt `package.json`.** Repoint `dev`/`build`/`start` → `blume dev`/`blume build`/`blume preview`, remove the old framework's deps, add `blume`. A config-only source (e.g. a bare Mintlify `docs.json`) has no manifest — scaffold one. **In a pnpm workspace:** if `pnpm-workspace.yaml`/`.npmrc` sets `minimumReleaseAge`, add **only** `blume` to `minimumReleaseAgeExclude` (don't disable the guard) so the just-published version installs. Yarn's `npmMinimalAgeGate` refuses a fresh `blume` the same way: add it to `npmPreapprovedPackages`. **Always regenerate the lockfile in the same change** with the repo's own package manager (`pnpm install` from the workspace root, `yarn install`, `npm install`, or `bun install`; check `packageManager` and the lockfile's name) and commit it alongside `package.json` — CI and hosts install from a frozen lockfile, so a stale one fails the build before it starts. After creating the first new file in the content root (a `meta.ts`, a partial), run `git check-ignore -v <content root>/meta.ts`: a repo `.gitignore` rule that covers the content root leaves every new file out of `git add` while the old pages stay tracked, so fix the rule or report that each new file needs `git add -f`. Blume needs **Node 22.19 or later**: update `.nvmrc`/`.node-version`, `engines.node`, and any CI `node-version` in the same change. pnpm 11 and later run a dependency's build script only once `pnpm-workspace.yaml` approves it: add `allowBuilds: { esbuild: true }` (plus `"@scarf/scarf": false`, as `blume init` writes) unless the workspace already lists `esbuild`, keeping its other entries. Add `.blume/`, `dist/`, and `.env.local` to the docs package's own `.gitignore` when it has one, else the root's (as `blume init` does; a `vercel()` build appends `.vercel/` itself), and remove any line that ignores `public/` before moving assets into it. **`blume build` empties `<project>/dist`:** if the host repo already uses `dist/` for something else (Python wheels, a library build), put the Blume project in its own folder (`docs/`). If the repo formats with Prettier, extend its `*.md` overrides (`.prettierrc`, `.editorconfig`) to `*.mdx` and keep `{/* */}` comments on one line. After renaming or moving pages, grep the whole repo (release config, CI, bot and ignore files) for their old paths. Check any `tsconfig.json` in the Blume project dir too: Blume's build reads it, so an `extends` that resolves only after a root workspace install, or `include`s of the old framework's folders, fail `blume build`. Install the workspace first, fix the paths, or delete the file if only the old framework used it (a Nuxt `tsconfig.json` that `extends` or `references` `./.nuxt/*` fails with `BLUME_TSCONFIG_EXTENDS`, and a `postinstall: nuxt prepare` script goes with the Nuxt dependency). **If the repo uses (or the user wants) [Ultracite](https://www.ultracite.ai) for formatting:** its oxfmt formatter mangles the `:::` directives you just wrote unless you ship the bundled `assets/oxfmt@0.71.0.patch` and register it under `patchedDependencies` — see `references/monorepo.md` §6. See `references/monorepo.md` §2–3.
48
+ 7. **Wire up the host repo & deploy (non-trivial repos).** Settings a host keeps in its dashboard (Netlify's publish directory, build command, `NODE_VERSION`) can't be committed: list them in the summary. In `.gitignore`, replace only the docs' own entries and keep ones other packages use. For a monorepo on Vercel, emit the root-aware install/build recipe and `apps/docs/vercel.json`: with `outputDirectory: "dist"` for a static build, and with no `outputDirectory` for `deployment: vercel()`, whose build writes `.vercel/output` (add `.vercel/output/**` to Turborepo outputs). Tell the user the settings you can't commit (Vercel Root Directory, Node 22, and server secrets such as `AI_GATEWAY_API_KEY`). If the workspace pins Vite and `blume build` crashes inside Astro/Vite, apply the pnpm-patch workaround. All copy-pasteable in `references/monorepo.md` §4–5.
49
+ 8. **Verify.** Run `blume build` (frontmatter schema, duplicate routes, config — it fails on any error diagnostic by default; **never pass `--no-strict`**, which builds anyway and silently drops pages with invalid front matter or `.mdx` that doesn't parse) and `blume validate --strict` (internal links, heading anchors, assets, redirect targets — the link checker lives in `validate`, not `build`), fix diagnostics, then `blume dev` for a visual pass. End with a written summary of what was migrated, dropped, and approximated — **and every repo-specific edit you made** (pnpm-workspace, vercel.json, config globs) with the reason, plus any manual step left to the user (the Astro patch, Vercel dashboard settings).
38
50
 
39
51
  ## The Blume mental model
40
52
 
@@ -44,10 +56,10 @@ The single biggest shift for most sources — especially Mintlify — is that **
44
56
 
45
57
  - **Folders become groups, files become pages.** A page's sidebar label is its frontmatter `title`; a group's label is the humanized folder name.
46
58
  - **Ordering resolves highest-priority-first:** an explicit `navigation.sidebar` (replaces the whole tree) → a folder's `meta.ts` `pages` array → a page's frontmatter `sidebar.order` → the filesystem (`index` first, then numeric filename prefix like `01-`, then alphabetical).
47
- - **`meta.ts` refines one folder** (`defineMeta({ title, icon, order, collapsed, pages, display, directory })`). The `pages` array lists children by slug (numeric prefix and parentheses stripped); children you omit fall back to their own `sidebar.order`, then filesystem order (`index` still sorts first) — so a partial `pages` list is safe, but list every child when the source declared a complete order.
59
+ - **`meta.ts` refines one folder** (`defineMeta({ title, icon, order, collapsed, pages, display, directory })`). The `pages` array lists children by slug (numeric prefix, parentheses, and extension stripped; an entry that names nothing warns `BLUME_META_UNKNOWN_PAGE` with the slug it likely meant, so write that one); children you omit fall back to their own `sidebar.order`, then filesystem order (`index` still sorts first) — so a partial `pages` list is safe, but list every child when the source declared a complete order. `directory` inherits down the tree: set `directory: "none"` on nested folders that shouldn't list cards. `pages` can't interleave loose pages with subgroups inside a `flat` group, or at the top of the sidebar in any mode: when a source interleaves them (mdBook, MkDocs `navigation.sections`), use `display: "group"`, where the order holds inside groups, and say what moved at the top level.
48
60
  - **Sidebar render mode: a global default with per-folder overrides.** `navigation.sidebar.display` in `blume.config.ts` is `"flat"` (default), `"group"` (collapsible), or `"page"` (drill-in sub-panel) and sets the mode for every group at once. A folder can override its **own** group: `display` in its `meta.ts`, or — sugar when the folder has an `index` page — `sidebar.display` in that index page's frontmatter. Precedence: index frontmatter → `meta.ts` → global config → `flat`; an override applies to that one group only (nested subgroups resolve their own chain). So a source's per-category collapse/drill-in modes migrate per folder — only reach for the global mode when the whole sidebar changes. Under an explicit `navigation.sidebar`, the config item's own `display` field is the only per-group control (frontmatter/meta `display` is ignored there, with a `BLUME_SIDEBAR_DISPLAY_IGNORED` warning).
49
61
  - **An explicit `navigation.sidebar` replaces filesystem generation entirely.** Use it only for a nav shape files can't express. Its items are a page route string, a group (`{ label, items }`), or a link (`{ label, href }`).
50
- - **Config-declared nesting has no on-disk counterpart — materialize it or it flattens silently.** When a source (Mintlify `groups`, Nextra `_meta`, a Docusaurus sidebar…) declares a nested group, its pages usually sit **flat in one folder** and the grouping lives only in config. Filesystem-derived nav sees the flat folder and drops the inner group. To keep the nesting you must **either** move those pages into a real subfolder (`meta.ts` for label/`collapsed`) — which changes their URLs, so add `redirects` — **or** declare the group in an explicit `navigation.sidebar`, which nests the existing routes without moving a file. Walk config `pages`/nav arrays **recursively** during inventory and record where config nesting depth exceeds on-disk depth; that gap is exactly what gets lost.
62
+ - **Config-declared nesting has no on-disk counterpart — materialize it or it flattens silently.** When a source (Mintlify `groups`, Nextra `_meta`, a Docusaurus sidebar…) declares a nested group, its pages usually sit **flat in one folder** and the grouping lives only in config. Filesystem-derived nav sees the flat folder and drops the inner group. To keep the nesting, **use a `(group)/` folder first**: move the pages into `<folder>/(<group>)/` with a `meta.ts` for the label, order, and `collapsed`. A parenthesized folder adds the sidebar group but no URL segment, so no route changes and no redirects are needed, and a page from another folder can join with a `slug` that keeps its old route. A group whose landing page is a sibling file keeps every URL too: move the landing page to `(group)/index.md` with `slug: <its old route>`, and the slugged index becomes the group's link. Moving a page a folder deeper breaks its relative links and assets (`./img.png`, `../other.md`), so re-base them. (Docsify is the exception: its old `#/` URLs are remapped by script whatever the paths, so its reference moves pages into real folders.) Only when that can't express the shape, move the pages into a real subfolder (URLs change: add `redirects`) or declare the group in an explicit `navigation.sidebar`, which nests the existing routes without moving a file. Walk config `pages`/nav arrays **recursively** during inventory and record where config nesting depth exceeds on-disk depth; that gap is exactly what gets lost.
51
63
 
52
64
  ### Tabs and selectors
53
65
 
@@ -58,27 +70,28 @@ The single biggest shift for most sources — especially Mintlify — is that **
58
70
 
59
71
  ### Routes and pathing
60
72
 
61
- - A route is the content path relative to `content.root`, with **numeric prefixes stripped** (`01-intro.mdx` → `/intro`) and **`(group)/` folders adding no segment**. An `index` file maps to its folder's route. Frontmatter `slug` overrides the generated route.
73
+ - A route is the content path relative to `content.root`, with **numeric prefixes stripped** (`01-intro.mdx` → `/intro`; a version or a name that starts with a date, `YYYY-MM-DD`, `D-M-YYYY`/`M-D-YYYY`, or `YYYY-MM`, stays whole: `12-05-2022.mdx` → `/12-05-2022`; pin `slug` for any other digit-led name the old site kept whole) and **`(group)/` folders adding no segment**. An `index` file maps to its folder's route. Frontmatter `slug` overrides the generated route. A `README.md` is an ordinary page at `/README`, not its folder's index: rename it `index.md` (Docsify, MkDocs, GitBook, and VuePress sources all use README as the index).
62
74
 
63
75
  ### `blume.config.ts` shape
64
76
 
65
77
  `defineConfig({...})` — every field optional, all with defaults:
66
78
 
67
79
  - **Site:** `title`, `description`, `logo` (string SVG, or `{ image: string | { light, dark, alt }, text, href }`), `banner` (a string, or `{ content, link: { href, text }, dismissible, id }` — no color/type). A logo renders beside `title` in the header, so a **wordmark logo doubles the brand** ("Acme Acme") — set `text: ""` to render the mark alone. **Prefer the string form over `{ light, dark }`:** if you have the logo SVG locally and it's monochrome (solid black or white), rewrite its `fill`/`stroke` to `currentColor` and use `logo: "/logo.svg"` — it then inherits the theme's text color and adapts to light/dark automatically, so you don't need separate light/dark files.
68
- - **`theme`:** `accent` (a color string for both modes, or `{ light, dark }` per mode), `action` (color), `mode` (`light`/`dark`/`system`), `radius`, `fonts` (`{ body, display, mono }` — each a curated Google-font slug, a `{ name, provider?, weights? }` object for any Google/Fontsource/Bunny/Fontshare family, or `{ name, variants: [{ src, weight?, style? }] }` for local font files), `background` and `backgroundImage` (each a string, or `{ light, dark }` per mode). The old `accentDark`/`backgroundDark`/`backgroundImageDark` fields were **merged into these per-mode objects** — a bare string still applies to both modes, so only reach for `{ light, dark }` when the two modes differ. There is **no** `theme.strict` and **no** `theme.css` config field — custom CSS goes in a project-root **`theme.css` file** (auto-picked-up), and a source's "strict appearance" flags drop.
69
- - **`content`:** `root` (default `"docs"`, relative to the project dir where `blume` runs), `include`/`exclude` (arrays of globs **relative to `content.root`**; defaults `["**/*.{md,mdx}"]` / `["**/_*", "**/.*"]`), `sources` (an array of **adapters imported from `blume/sources`**: `filesystem({ root, include, exclude })`, `obsidian({ vault })`, `githubReleases({ owner, repo })`, `notion({ database })`, `sanity({ projectId, dataset, query })`, `contentful({ space, contentType })`, `payload({ url, collection })`, `strapi({ url, contentType })`, `mdxRemote({ github })`, `custom(source)`; every factory with an options object also takes `prefix` and `pollInterval`, while `custom(source)` takes a `ContentSource` instance that sets its own `prefix`. The 1.x `{ type: "…" }` objects were removed — rename `type` to the factory call and pass the other fields as its options — except `{ type: "custom", source }`, which becomes `custom(source)` with the instance as the only argument. `root`/`include`/`exclude` are shorthand for a single `filesystem()` and are **rejected beside `sources`** — move them into the `filesystem()` entry. OpenAPI/AsyncAPI/GraphQL are **not** among these; they're adapters in the top-level `reference` list), `pages` (custom `.astro` dir), `defaultType`. When docs sit directly under the project dir (no `docs/` subfolder), set `root` there and **scope `include` to the real content folders** instead of scanning everything — `references/monorepo.md` §1.
80
+ - **`theme`:** `accent` (a color string for both modes, or `{ light, dark }` per mode), `action` (color), `mode` (`light`/`dark`/`system`), `radius`, `fonts` (`{ body, display, mono }` — each a curated Google-font slug, a `{ name, provider?, weights? }` object for any Google/Fontsource/Bunny/Fontshare family, or `{ name, variants: [{ src, weight?, style? }] }` for local font files), `background` and `backgroundImage` (each a string, or `{ light, dark }` per mode). The old `accentDark`/`backgroundDark`/`backgroundImageDark` fields were **merged into these per-mode objects** — a bare string still applies to both modes, so only reach for `{ light, dark }` when the two modes differ. Each color must be a CSS color or an accent preset (a source's bare `"3b82f6"` needs its `#`), or `BLUME_THEME_COLOR_INVALID` warns and browsers ignore it. There is **no** `theme.strict` and **no** `theme.css` config field — custom CSS goes in a project-root **`theme.css` file** (auto-picked-up), and a source's "strict appearance" flags drop.
81
+ - **`content`:** `root` (default `"docs"`, relative to the project dir where `blume` runs), `include`/`exclude` (arrays of globs **relative to `content.root`**; defaults `["**/*.{md,mdx}"]` / `["**/_*", "**/.*"]` — a set `exclude` **adds to** those defaults, so partials and dot-folders stay unpublished; to publish `_`-prefixed or dot-files on purpose, list the default with a `!`: `exclude: ["!**/_*"]`), `sources` (an array of **adapters imported from `blume/sources`**: `filesystem({ root, include, exclude })`, `obsidian({ vault })`, `githubReleases({ owner, repo })`, `notion({ database })`, `sanity({ projectId, dataset, query })`, `contentful({ space, contentType })`, `payload({ url, collection })`, `strapi({ url, contentType })`, `mdxRemote({ github })`, `custom(source)`; every factory with an options object also takes `prefix` and `pollInterval`, while `custom(source)` takes a `ContentSource` instance that sets its own `prefix`. The 1.x `{ type: "…" }` objects were removed — rename `type` to the factory call and pass the other fields as its options — except `{ type: "custom", source }`, which becomes `custom(source)` with the instance as the only argument. `root`/`include`/`exclude` are shorthand for a single `filesystem()` and are **rejected beside `sources`** — move them into the `filesystem()` entry. OpenAPI/AsyncAPI/GraphQL are **not** among these; they're adapters in the top-level `reference` list), `pages` (custom `.astro` dir), `defaultType`. When docs sit directly under the project dir (no `docs/` subfolder), set `root` there and **scope `include` to the real content folders** instead of scanning everything — `references/monorepo.md` §1.
70
82
  - **`basePath`** (top-level): a site-wide mount point (e.g. `"/docs"`) prepended to **every** route while staying invisible to the sidebar (no wrapper group). This is the right target for a source that served all docs under a prefix (Docusaurus `routeBasePath`, a Fumadocs `baseUrl` of `/docs`) — distinct from a per-source `prefix` (which adds a nav group) and from `deployment.base` (host subdirectory).
71
- - **`navigation`:** `tabs`, `selectors`, `actions` and `cta` (header links and the one filled button), `featured` (links pinned above the sidebar on every route), `sidebar` (`{ display, items }` — `display` is the global render mode above; `items` is an explicit tree), `repo` (`true`/`false`, or an absolute GitHub URL for the header mark when the docs repo is private and `github` must stay unset). **Avoid an explicit `navigation.sidebar` unless you have to** — lean on the filesystem-derived sidebar. It only works when the file tree roughly matches the intended sidebar layout, so reshape folders to match first; reach for `sidebar.items` only for a shape files genuinely can't express (see "Config-declared nesting" above).
83
+ - **`navigation`:** `tabs`, `selectors`, `actions` and `cta` (header links and the one filled button), `featured` (links pinned above the sidebar on every route), `sidebar` (`{ display, items }` — `display` is the global render mode above; `items` is an explicit tree), `repo` (`true`/`false`, or an absolute GitHub URL for the footer's GitHub link when the docs repo is private and `github` must stay unset). **Avoid an explicit `navigation.sidebar` unless you have to** — lean on the filesystem-derived sidebar. It only works when the file tree roughly matches the intended sidebar layout, so reshape folders to match first; reach for `sidebar.items` only for a shape files genuinely can't express (see "Config-declared nesting" above).
72
84
  - **`search`** — an adapter imported from `blume/search`: `orama()` (default, so omit `search` entirely for it), `flexsearch()`, `pagefind()`, `algolia({ appId, apiKey, indexName })`, `oramaCloud({ endpoint, apiKey, indexId? })`, `typesense({ host, collection, apiKey })`, `mixedbread({ storeId })`, or `false` to disable. Pass the adapter directly (`search: algolia({…})`) or as `search: { provider, popular, indexing }` when you also set curated links or indexing options. **Blume 1.x's `search.provider` string and its `search.algolia`/`oramaCloud`/`typesense`/`mixedbread` credential blocks are gone** — when a source config (or an older `blume.config.ts`) has `provider: "algolia", algolia: { appId, indexName, searchApiKey }`, rewrite it as `search: algolia({ appId, indexName, apiKey: searchApiKey })` (the search-only key is now `apiKey` in every adapter that takes one; admin keys stay in env vars). **`ai`** (the assistant, Open in chat — `ai.assistant.provider` takes an **adapter descriptor** imported from `blume/ai`: `gateway({ model })` (the default, `openai/gpt-5.5`), `openai({ model })` (or `openai({ baseUrl, name, model, apiKeyEnv })` for any OpenAI-compatible endpoint), `anthropic({ model })`, `gemini({ model })`, `grok({ model })`, `openrouter({ model, reasoning })`, `llmgateway({ model })`, or `inkeep({ model })`. Every adapter takes `model`, `apiKeyEnv`, `headers`, and a verbatim `providerOptions` passthrough (`headers` values are written into the generated route source as-is, so they are for non-secret static headers only — a bearer token or any other credential belongs in the env var `apiKeyEnv` names, never in `headers`). Every adapter except `inkeep()` also takes `reasoning`: Inkeep runs its own answer pipeline, so `inkeep({ reasoning })` fails validation with an unrecognized key — drop a source's reasoning setting there and report it. There are **no** flat `provider`/`model`/`apiKeyEnv`/`baseUrl`/`headers`/`reasoning` fields on `ai.assistant` — a source that configured an AI assistant that way (Blume < 2.0 included) maps onto one adapter call. `enabled`, `instructions`, `retrieval`, `suggestions`, `cors`, and `endpoint` sit on `ai.assistant` itself; there is **no** `ai.ask` — Blume 2.0.0 and earlier used that name, so an older `blume.config.ts` moves the whole block to `ai.assistant`), **`agents`** (llms.txt, the JSON API, the MCP server, published skills, discovery manifests, robots content signals), **`reference`** (a list of adapters imported from `blume/reference` — `openapi({ spec | sources, route, … })`, `asyncapi({ … })`, `graphql({ spec, endpoint, … })` — never the 1.x `openapi`/`asyncapi`/`graphql` blocks), **`redirects`**, **`seo`**, **`markdown`**, **`analytics`** (a list of adapters imported from `blume/analytics` — one factory per provider (`posthog({ key, host })`, `googleAnalytics({ id })`, `plausible({ domain, host })`, `mixpanel({ token, region })`, `segment({ key })`, …; the docs page lists them all), `vercel()`, and `script({ src | content, strategy, attributes })` for anything without one — never an object keyed by provider), **`deployment`**, **`i18n`**, **`toc`**, **`lastModified`**, **`github`** (`{ owner, repo, branch?, dir?, host?, api? }` — set `host` whenever the source's edit URL is on a GitHub Enterprise origin rather than `github.com`).
85
+ - **`analytics`** — adapters from `blume/analytics`, including `script()` for a source's custom head scripts. A known analytics snippet in the source's head scripts (the gtag loader plus `gtag('config')` pair, GTM, Plausible, …) becomes that provider's factory, not `script()`. Adapters load **only in production builds and wait for `consent`** when it's configured, so a script a page needs to work (a support widget, a form embed) goes in a `PageFooter` layout slot component instead (`defineComponents`).
73
86
  - **`deployment`** is a host adapter from `blume/deploy` — `vercel()`, `netlify()`, `cloudflare()`, or `node()` — or a plain `{ site, base }` for a static build on any host (the default is a static build). Naming a host adapter switches the build to **server output** on that host (pass `output: "static"` to stay static there); `site`, `base`, and `output` are the named options, and anything else is forwarded verbatim to the underlying `@astrojs/*` adapter. There is **no** `deployment.adapter` string and **no** `deployment.output` field: a source that needs server rendering (the assistant, the MCP server, Mixedbread search, the API playground proxy) gets `import { vercel } from "blume/deploy"` and `deployment: vercel()`; a static source gets nothing — unless it served its docs under a subpath, which becomes `deployment: { base: "/docs" }` (see below for when to add `site`). Note that `cloudflare` and `vercel` are also exported from `blume/analytics` — alias one (`import { cloudflare as cloudflareDeploy } from "blume/deploy"`) when a config uses both.
74
- - **Keep the source's site URL as `deployment.site` unless the target host is one Blume auto-detects.** Blume fills `site` in from the platform's build environment only on **Vercel, Netlify, and Cloudflare Pages**, and uses the dev server's `localhost` URL during `blume dev`; on those three hosts leave it unset and let detection pick the deployed URL. Everywhere else — GitHub Pages, S3 or another static host, a custom CDN, Cloudflare Workers, a `node()` server — nothing detects it, and an unset `site` silently drops the sitemap, OG images, RSS feeds, the AI catalog, and absolute canonical URLs. So when the source config had a `url`/`site` field and the target isn't one of those three hosts, carry it over: `deployment: { site: "https://…" }` for a static build, or the `site` option of a host adapter (`node({ site })`). If you can't tell where the site will deploy, keep it and say so in the report.
75
- - **Favicon is a filename convention, not config.** Drop `icon.{svg,png,ico}` or `favicon.{svg,png,ico}` (and `apple-icon.png`) in the project root or `public/` — Blume auto-detects it. There is **no** `favicon` config field. A source favicon given as `{ light, dark }` maps to a filename pair: copy the light file to a conventional name (e.g. `public/icon.png`) and the dark file to its `-dark` sibling — same directory and extension, `-dark` before the extension (`public/icon-dark.png`). If the two files have different formats, convert one so the extensions match; only an exact sibling of the resolved icon is picked up.
87
+ - **Keep the source's site URL as `deployment.site` unless the target host is one Blume auto-detects.** Blume fills `site` in from the platform's build environment only on **Vercel and Netlify** (their stable production domain), and uses the dev server's `localhost` URL during `blume dev`; on those two hosts leave it unset and let detection pick the deployed URL. Everywhere else — Cloudflare Pages (it exposes only the per-deploy `CF_PAGES_URL`), GitHub Pages, S3 or another static host, a custom CDN, Cloudflare Workers, a `node()` server — nothing detects it, and an unset `site` silently drops the sitemap, OG images, RSS feeds, the AI catalog, and absolute canonical URLs. So when the source config had a `url`/`site` field and the target isn't one of those two hosts, carry it over: `deployment: { site: "https://…" }` for a static build, or the `site` option of a host adapter (`node({ site })`). If you can't tell where the site will deploy, keep it and say so in the report.
88
+ - **Favicon is a filename convention, not config.** Drop `icon.{svg,png,ico}` or `favicon.{svg,png,ico}` (and `apple-icon.png` or `apple-touch-icon.png`) in the project root or `public/` — Blume auto-detects it. There is **no** `favicon` config field. A source favicon given as `{ light, dark }` maps to a filename pair: copy the light file to a conventional name (e.g. `public/icon.png`) and the dark file to its `-dark` sibling — same directory and extension, `-dark` before the extension (`public/icon-dark.png`). If the two files have different formats, convert one so the extensions match; only an exact sibling of the resolved icon is picked up.
76
89
 
77
90
  The schema is exported from `blume/schema`; the full field reference is in the `docs/configuration/` directory of the installed `blume` package (see "Full documentation" below for how to locate it).
78
91
 
79
92
  ### Icons are Lucide, period
80
93
 
81
- Blume resolves **bare kebab-case [Lucide](https://lucide.dev) names** everywhere an icon is accepted — frontmatter `icon`, `sidebar.icon`, `meta.ts` `icon`, `navigation.tabs`/`selectors` icons, and `Card`/`Step`/`Icon`/etc. props. There is **no** FontAwesome or Tabler support and **no** `iconType` prop. Names must be **kebab-case** (`book-open`, not `BookOpen`) — a PascalCase React-component name (common in Fumadocs/lucide-react sources) does not resolve and renders nothing. When migrating a source that uses another icon set (Mintlify defaults to FontAwesome), **map each name to its closest Lucide equivalent**; where none exists, drop the icon and report it. Verify a name exists at [lucide.dev/icons](https://lucide.dev/icons) before writing it.
94
+ Blume resolves **bare kebab-case [Lucide](https://lucide.dev) names** everywhere an icon is accepted — frontmatter `icon`, `sidebar.icon`, `meta.ts` `icon`, `navigation.tabs`/`selectors` icons, and `Card`/`Step`/`Icon`/etc. props. There is **no** FontAwesome or Tabler support and **no** `iconType` prop. Names must be **kebab-case** (`book-open`, not `BookOpen`) — a PascalCase React-component name (common in Fumadocs/lucide-react sources) does not resolve and renders nothing. When migrating a source that uses another icon set (Mintlify and Fern use FontAwesome), **map each name to its closest Lucide equivalent**; where none exists, drop the icon and report it. Verify a name exists at [lucide.dev/icons](https://lucide.dev/icons) before writing it.
82
95
 
83
96
  ### Page frontmatter (strict — unknown keys are build errors)
84
97
 
@@ -108,22 +121,29 @@ lastModified: 2026-06-20 # pin the "last updated" date
108
121
  ---
109
122
  ```
110
123
 
111
- Also valid: `date`/`authors` (blog/changelog feeds), `changelog` (changelog metadata), `deprecated`, `hidden`, `noindex`.
124
+ Also valid: `date`/`authors` (blog/changelog feeds), `changelog` (changelog metadata), `deprecated`, and the top-level shorthands `hidden` (for `sidebar.hidden`) and `noindex` (for `seo.noindex`).
112
125
 
113
126
  ### Authoring features (no imports needed in `.mdx`)
114
127
 
115
- - **The rich features are MDX-only.** Directives, `package-install`, mermaid, and math are wired into the MDX processor; in a plain `.md` file a `:::note` stays **literal text** — and the build stays green. **Rename any `.md` file that uses (or should use) these to `.mdx` during migration.** This bites hardest on Docusaurus/Starlight sources, whose `.md` content is full of `:::` admonitions. Plain Markdown (headings, tables, fenced code with titles/highlighting) is fine in `.md`.
116
- - **Callouts as directives:** `:::note`, `:::tip`, `:::warning`, `:::danger`, `:::info`, `:::success`, with an optional title in brackets: `:::warning[Heads up]`. Aliases `caution`→warning, `error`→danger, `important`→note, `warn`→warning.
117
- - **No-import MDX components:** `Callout`, `Card`/`CardGroup`, `Columns`/`Column`, `Steps`/`Step`, `Tabs`/`Tab`, `Accordion`/`AccordionItem`, `Expandable`, `FileTree`, `Tree`/`Tree.Folder`/`Tree.File`, `CodeGroup`, `Frame`, `Panel`, `Tooltip`, `Tile`, `Badge`, `Icon`, `TypeTable`/`AutoTypeTable`, `Color`, `YouTube`, `Visibility`, `GithubInfo`, `Component`, `CodeBlock`, `Diff`, `Prompt`, `Math`. (**Not** shipped — convert away: `<Warning>` → the `:::warning` directive, and the `ParamField`/`ResponseField`/`RequestField` field family → `TypeTable` rows or the OpenAPI reference. See the reference files for targets.)
128
+ - **The rich features are MDX-only.** Directives, `package-install`, mermaid, and math are wired into the MDX processor; in a plain `.md` file a `:::note` stays **literal text** — and the build stays green, with only a `BLUME_MD_DIRECTIVE` warning at its line. **Rename any `.md` file that uses (or should use) these to `.mdx` during migration.** MDX is stricter prose than Markdown: any `<` followed by a non-space character that doesn't open a tag (`<7`, `<50 ms`, `<= 5`, `List<T>`) and a bare `{` or `}` fail the build, so escape them (`&lt;` or `\<`, and `\{`) in any page you rename. With `lastModified: "git"`, every page the migration edits shows the migration commit's date, so say so in the report when the site shows dates; a page it only renames or moves keeps its date (Blume follows renames). This bites hardest on Docusaurus/Starlight sources, whose `.md` content is full of `:::` admonitions. Plain Markdown (headings, tables, fenced code with titles/highlighting) is fine in `.md`.
129
+ - **Callouts as directives:** `:::note`, `:::tip`, `:::warning`, `:::danger`, `:::info`, `:::success`, with an optional title in brackets, which renders its inline Markdown: `:::warning[Heads up]`. Aliases `caution`→warning, `error`→danger, `important`→note, `warn`→warning. A callout that needs its own icon or tint is `<Callout type icon color title>` (a Lucide `icon`, any CSS `color`), which a directive can't set. **markdown-it sources (VitePress, VuePress, Docusaurus v2) write `::: tip Title`: with a space after the colons it's never a directive, even in `.mdx`, and renders as literal text (`.mdx` warns `BLUME_DIRECTIVE_SPACED_NAME` only for a callout name); inside an open `:::` container it closes that container and its text is dropped (`BLUME_DIRECTIVE_CLOSING_TEXT`); a title after the name (`:::tip Title`) is dropped too (`BLUME_DIRECTIVE_OPENING_TEXT`).** Normalize to `:::tip[Title]`. MDC's two-colon blocks (Docus, any Nuxt Content site: `::note` … `::`) are leaf directives to Blume and print as text (`BLUME_MDC_SYNTAX`), and MDC `[text]{.class}` spans fail the MDX build (`BLUME_MDX_ATTRIBUTE_LIST` names the line). GitHub alerts (`> [!NOTE]`, any of GitHub's five, `[!CAUTION]` as danger) render as callouts in `.mdx`, text after the marker as the title; in `.md` they stay quotes that show the marker (`BLUME_MD_GITHUB_ALERT`), so rename those pages to `.mdx`.
130
+ - **No-import MDX components:** `Callout`, `Card`/`CardGroup`, `Columns`/`Column`, `Steps`/`Step`, `Tabs`/`Tab`, `Accordion`/`AccordionItem`, `Expandable`, `FileTree`, `Tree`/`Tree.Folder`/`Tree.File`, `CodeGroup`, `Frame`, `Panel`, `Tooltip`, `Tile`, `Badge`, `Icon`, `TypeTable`/`AutoTypeTable`, `Color`, `YouTube`, `Visibility`, `GithubInfo`, `Component`, `CodeBlock`, `Diff`, `Prompt`, `Math`, `Changelog`, and the hand-written endpoint fields `ParamField`/`ResponseField`/`RequestExample`/`ResponseExample` (Mintlify's semantics: `<ParamField path|query|header|body="name">` names the field by its location; see `docs/references/api-pages.mdx`). (**Not** shipped — convert away: `<Warning>`/`<Note>`/`<Info>`/`<Tip>`/`<Check>`/`<Error>` → the matching `:::` directive, and `RequestField`. A `<CodeBlock>` with no `code` prop, used as a wrapper around a fence as Fern and Mintlify do, renders the fence as written but ignores its own `title`: unwrap the fence and put the title in its meta. A component that shows its children, used with none and another tool's props (`<Badge type="tip" text="beta" />`), renders empty and warns `BLUME_UNKNOWN_PROP`: put the content between the tags. See the reference files for targets.)
118
131
  - **Fenced-code superpowers:** ` ```package-install ` → package-manager tabs; ` ```mermaid ` → a rendered diagram; code-block titles (` ```ts server.ts `), line numbers (`lineNumbers`), and highlighting (`{1,4-5}`, `// [!code ++]`).
132
+ - **Fences and titles that trip migrations:** a fence language Shiki doesn't know renders as plain text and warns `BLUME_UNKNOWN_CODE_LANGUAGE` (case doesn't matter: `JSON` highlights like `json`): fix the id, or write `text` for plain text. A language glued to attributes with a comma (` ```rust,ignore `) is one of those; with a space instead, rustdoc's and mdBook's keywords (`ignore`, `no_run`, `should_panic`, `edition2021`, `editable`, …) warn `BLUME_CODE_FENCE_OPTION` and stay out of the title, but any other second word becomes the block's title: keep only the language. Other tools' fence options (`hl_lines="2 3"`, `linenums="1"`, `showLineNumbers`, Mintlify's `lines`, `wordWrap`, `filename="…"`) do nothing, stay out of the title, and warn `BLUME_CODE_FENCE_OPTION` with the Blume spelling (`{2,3}`, `lineNumbers`, `wrap`, `title="…"`). A fence with options but no language needs one (` ```text title="x" lineNumbers `), or the first option is read as the language. A title is every word after the language, and a bracketed one (` ```ts [file.ts] `, from VitePress and MDC) drops its brackets, with a range glued after them (`[file.ts]{2}`) highlighting its lines. `Expandable`, `AccordionItem`, and callout titles render inline Markdown (code, emphasis, links); a `Step` title is a plain string that shows Markdown as written and has no anchor, so keep an anchored heading as the first line inside `<Step>`. Raw HTML `<img src>` and `<a href>` are never optimized: a relative one naming a file beside the page publishes that file with the page (as `[spec](./spec.pdf)` and `<Card img="./cover.png">` do), a root path gains `deployment.base` like a Markdown link, and `blume validate` checks both, but a relative `<a href>` to a page resolves against the page's URL, not its folder. Convert images to Markdown so they're optimized, and page links to Markdown links or root routes.
119
133
  - **Math:** `$$…$$` renders in `.mdx` with **no config** (there is no `markdown.math` field) — as a block on lines of its own, or inline within a sentence. A single `$` always stays literal text, so convert each inline `$…$` to `$$…$$` in place, inside its sentence (not onto a line of its own), checking every pair is math and not currency or a shell variable.
120
134
 
121
135
  ### OpenAPI
122
136
 
123
137
  `reference: [openapi({ sources: [{ spec, label?, route? }] })]` — the `openapi()` adapter imported from `blume/reference` (`spec` is the single-source shorthand) — generates **one real page per operation** — with routing, sidebar, search, and OG images for free. **The reference does not get a header tab automatically** — add a `navigation.tabs` entry pointing at the adapter's `route` (reference routes are valid tab targets) or the API reference is unreachable from the header. **Never hand-migrate generated API-reference pages** (per-endpoint stub pages in the source): delete them and point `openapi()` at the spec. To keep a source's **Scalar embed** instead, list `scalar({ spec, theme?, …scalarOptions })` (also from `blume/reference`) in `reference` in place of `openapi()`: it renders an OpenAPI or AsyncAPI document as one embedded page per source, forwards every key it doesn't name verbatim to Scalar, and doesn't take the native display options (`codeSamples`, `expandSchemas`, `playground`). There is **no** `renderer` option on `openapi()`/`asyncapi()` — it fails validation. **Blume 1.x's top-level `openapi`/`asyncapi`/`graphql` blocks are gone** — when a source config (or an older `blume.config.ts`) has `openapi: { enabled: true, ... }`, rewrite it as an entry in `reference` and drop `enabled`; a 1.x block with `renderer: "scalar"` becomes its own `scalar({ … })` entry instead, keeping the block's `route`, `sources`, and `noindex`, with its `theme` and the keys of its `scalar: { … }` object passed straight to `scalar()` (`scalar({ spec, theme: "purple", localization })`).
124
138
 
125
- - **Vendor the spec by default.** A remote `spec:` URL makes every build depend on fetching it at build time — a single point of failure in CI, offline, or behind a proxy, and a failed fetch skips the whole reference. Prefer committing the spec into the repo (`openapi/<name>.json`) and pointing `spec` at the local path; if you keep the URL, say so and consider a `prebuild` step that refreshes the local copy with a fallback.
126
- - **Operation routes have their own slug scheme** — `<route>/<slugified-tag>/<slugified-operationId>` (e.g. tag `Models`, id `listModels` → `/api-reference/models/list-models`). A camelCase operation id is split at its word boundaries into kebab-case before slugifying (`getHTTPResponse` → `get-http-response`), so don't just lowercase it; an operation with no `operationId` takes its method and path instead (`GET /pets/{id}` → `get-pets-id`). This rarely matches the source's endpoint links (Mintlify/others kebab-case differently), so **rewrite every inbound link to an operation**. `blume validate` resolves operation pages like any other route, so it catches the ones you miss.
139
+ - **Vendor the spec by default.** A remote `spec:` URL makes every build depend on fetching it at build time — a single point of failure in CI, offline, or behind a proxy, and a failed fetch skips the whole reference. Prefer committing the spec into the repo (`openapi/<name>.json`) and pointing `spec` at the local path; if the source shipped it as `public/openapi.json`, move it (`public/specs/openapi.json` if readers download it): that URL is where Blume publishes its own docs API description (`BLUME_PUBLIC_OPENAPI_JSON`). If you keep the URL, say so and consider a `prebuild` step that refreshes the local copy with a fallback.
140
+ - **Operation routes have their own slug scheme** — `<route>/<slugified-tag>/<slugified-operationId>` (e.g. tag `Models`, id `listModels` → `/api-reference/models/list-models`). A camelCase or PascalCase tag or operation id is split at its word boundaries into kebab-case before slugifying (`InboxesThreads` → `inboxes-threads`, `getHTTPResponse` → `get-http-response`), so don't just lowercase it; an operation with no `operationId` takes its method and path instead (`GET /pets/{id}` → `get-pets-id`). A slug only has to be unique within its tag: a second `list` in the same tag gets its method added (`list-post`). This rarely matches the source's endpoint links (Mintlify/others kebab-case differently), so **rewrite every inbound link to an operation**. `blume validate` resolves operation pages like any other route, so it catches the ones you miss.
141
+ - **A spec served at its own path** (Redocly serves `apis/x/openapi.yaml` at `/apis/x/openapi`): set the source's `route` so the overview keeps its old URL where the sidebar allows it (the source's reference says when a folder route reads better); operation, tag, and section URLs still need redirects.
142
+ - **Operation URL redirects** come from the old sitemap, one exact entry per operation, generated rather than typed: build once, then `node <skill>/scripts/operation-routes.mjs dist` prints each reference's endpoints (`GET /pets/{id}`) mapped to their Blume routes, read from the build (`--spec <route>=<file.json>` adds operationIds); match each old URL to its endpoint through the spec. Rewrite inbound links **before** adding them: `blume validate` accepts any redirect `from` as a valid link target, so it stops flagging old-shaped links once the redirects exist.
143
+ - **Tag labels and order come from the spec.** A tag's `x-displayName` labels its group; tags follow the spec's `tags` list, and operations its paths and methods as written. `x-tagGroups` isn't read: when the source ordered by it, give each tag `<route>/<tag-slug>/meta.ts` → `defineMeta({ order })` with its position in the flattened `x-tagGroups`. A tag folder's `meta.ts` merges over the generated settings, so set only what differs (`title`, `order`, or a `pages` list to reorder operations). Under a scoped `content.include`, add a glob that reaches the tag folder, or the file is never read (`BLUME_META_OUTSIDE_INCLUDE`).
144
+ - **Prose a source attached to a generated endpoint page** (ReadMe and Mintlify allow it) belongs in that operation's `description`, set with an overlay: Markdown and `:::` directives render on the operation page, but JSX doesn't (`<` and `{` are escaped).
145
+ - **An API defined in a non-OpenAPI format** (a Fern Definition) is exported to OpenAPI first; the source's reference says how.
146
+ - **Try it requests go straight from the reader's browser.** Probe each API server for CORS (`curl -si -X OPTIONS -H 'Origin: https://<docs-host>' -H 'Access-Control-Request-Method: GET' <server>/<path>`); list the ones without a matching `access-control-allow-origin` as a follow-up (`playground: { proxy: true }` fixes them on a server-output host), or turn the playground off for them.
127
147
  - **Keep hand-written conceptual pages.** Sources often pair a written "Introduction/Authentication" page with the endpoint group in the same tab. A normal content page placed under the `openapi()` adapter's `route` merges into the reference tab's sidebar — so keep those (auth, errors, rate limits) and delete only the per-endpoint stubs.
128
148
 
129
149
  ### GraphQL
@@ -136,7 +156,7 @@ Also valid: `date`/`authors` (blog/changelog feeds), `changelog` (changelog meta
136
156
 
137
157
  ### Changelogs
138
158
 
139
- If the source ships a **hand-maintained changelog** (a `changelog.mdx`, a folder of dated entries, Mintlify `<Update>` blocks) **and the project is open source on GitHub**, offer to replace it with the **`githubReleases()`** content source — release notes become the changelog automatically, with no files to maintain. It's an offer, not an automatic rewrite: some teams keep a curated changelog that doesn't map 1:1 to GitHub releases, so confirm the release notes are the source of truth before deleting their pages.
159
+ If the source ships a **hand-maintained changelog** (a `changelog.mdx`, a folder of dated entries, Mintlify `<Update>` blocks, a ReadMe `changelogs/` folder) **and its release notes live in that repository's GitHub releases**, offer to replace it with the **`githubReleases()`** content source — release notes become the changelog automatically, with no files to maintain. It's an offer, not an automatic rewrite: some teams keep a curated changelog that doesn't map 1:1 to GitHub releases, so confirm the release notes are the source of truth before deleting their pages.
140
160
 
141
161
  Add it under `content.sources` alongside the filesystem source (both imported from `blume/sources`; with `sources` present, the content root moves into `filesystem()`):
142
162
 
@@ -158,21 +178,23 @@ content: {
158
178
  - Each release materializes as a `type: changelog` page under `/<prefix>/` (`prefix: "changelog"` → `/changelog/…`); omit `prefix` to mount at the root.
159
179
  - Optional fields: `limit` (cap materialized releases, newest-first, default 100), `prereleases` (include prereleases), `drafts` (include drafts — needs a token with repo write access), `pollInterval` (dev polling seconds; omit to freeze for the session).
160
180
  - A **private** repo reads a token from `GITHUB_TOKEN`; it is never inlined in config. The adapter declares it, so `blume dev`/`build` warn when it is unset; a public repo still works without it.
161
- - **Delete the old changelog pages** once the source is wired (and add `redirects` from their old routes to the new `/<prefix>/…` slugs). Pin a header/sidebar link with `navigation.featured` if the source had one.
181
+ - **Delete the old changelog pages** once the source is wired (and add `redirects` from their old routes to the new `/<prefix>/…` slugs). Pin a header/sidebar link with `navigation.featured` if the source had one. To keep the old changelog's introduction above the list, write `changelog/index.mdx` (`mode: center`) with the intro and `<Changelog />`, which renders the generated release list.
162
182
 
163
183
  ### Redirects are static
164
184
 
165
185
  A `redirects: [{ from, to, status? }]` array **in `blume.config.ts`** maps old URLs when you restructure routes — Blume serves these itself, so any reorganization that moves a page (folder-per-tab, materialized nested groups, renamed slugs, index promotion) is fixed by adding an entry there; no host config needed. **Restructuring is the main source of these:** every page you moved in step 4 (folder-per-tab, renamed slugs, index promotion) needs an entry, or old URLs 404. `status` defaults to **301 (permanent — browsers cache it indefinitely)**; that's correct for genuine moves, but never use 301/308 for redirects you might reverse. Patterns carry over as written: `:name` for one segment, `:name*` or a trailing `*` for the rest of the path (`{ from: "/beta/:slug*", to: "/v2/:slug*" }`). A pattern can't also match a page, so the build fails (`BLUME_REDIRECT_MATCHES_PAGE`) if one covers a page you kept; narrow it. Rules a pattern can't express (regex params, header or cookie conditions) move to host-level config (`_redirects`, `vercel.json`); report them.
166
186
 
187
+ A redirect whose `from` equals a page's route (a generated reference overview included) warns `BLUME_REDIRECT_MATCHES_PAGE` and wins: that page never publishes. Drop the redirect, or move the page. The same goes for a page's Markdown mirror (`/x.md`, `/x.mdx`, the home page's `/index.md`): agents lose it, so drop the redirect. Never write a redirect from a page's own folder URL either (`/x/`, common when the source served trailing-slash URLs): it takes the page over the same way. One from `/x/index.html` (a source that served `.html` URLs) is harmless but unneeded: static hosts serve the page there from `x/index.html`, so a static build writes no redirect page for it, and only the host files (`_redirects`, `vercel.json`) carry it. And `redirects` match paths only: a URL whose page sits after `#` never reaches them (see `references/docsify.md`). When the old site lives on a host you don't control (a GitHub wiki), no redirect can reach its URLs: don't promise any (see `references/github-wiki.md` for what can be done there). Trailing slashes need no redirects of their own: `blume dev` and `blume preview` answer `/a/` with a 301 to `/a` (query kept), for pages and redirect sources alike, as Vercel does; other static hosts serve `/a/` from `a/index.html`, and server builds (`vercel()`, `node()`) match `/a/` for a redirect `from`. Whether a static host's redirect rules match a slashed `from` depends on the host, so when old links used trailing slashes, check one on a preview deployment. **On Netlify, set `deployment: netlify({ output: "static" })`**: only that build's `_redirects` rules are forced (`301!`), so without it Netlify serves the meta-refresh page Astro writes at each `from` with a 200 instead of a real 301 (a bare `netlify()` switches to server output).
188
+
167
189
  ## Verification & reporting
168
190
 
169
- 1. Run **`blume build`** — it validates the frontmatter schema, duplicate routes, and config, and fails on any error diagnostic by default (**don't pass `--no-strict`**: that builds anyway and silently drops invalid pages). Then run **`blume validate --strict`** — links, heading anchors, and assets live here, not in `build` (add `--external` to also check outbound HTTP links). OpenAPI operation pages are real routes to `validate`, so dead links to them are caught too. Iterate until both are clean.
170
- 2. Run `blume dev` and review the site visually — nav structure, tabs, theme, rendered components.
191
+ 1. Run **`blume build`** — it validates the frontmatter schema, duplicate routes, and config, and fails on any error diagnostic by default (**don't pass `--no-strict`**: that builds anyway and silently drops pages with invalid front matter or `.mdx` that doesn't parse). Then run **`blume validate --strict`** — links, heading anchors, assets, and redirect targets live here, not in `build` (add `--external` to also check outbound HTTP links). OpenAPI operation pages are real routes to `validate`, so dead links to them are caught too. Iterate until both are clean. Treat every warning `build` (or `blume check`) prints about another tool's syntax left in a page (`BLUME_TEMPLATE_TAG`, `BLUME_MDC_SYNTAX`, `BLUME_WIKILINK_UNSUPPORTED`, `BLUME_MD_*`, `BLUME_CODE_FENCE_OPTION`, `BLUME_MDX_*`, …) as a conversion you missed: each names the file and line and the Blume spelling, and `validate --strict` fails on them. `validate` reads links as the page renders them: a link definition shadowed by an earlier one with the same label isn't checked (it's dead text; delete it), and neither are links in comments. `validate --strict` also fails on OpenAPI spec warnings (`BLUME_OPENAPI_*`, such as an undeclared path parameter, a `localhost` server, or `nullable` in a 3.1 spec), which no content edit clears: fix them with an OpenAPI Overlay in the source's `overlays` so a generated or third-party spec stays untouched, and report them for a fix at the source; a `codeSamples` id Blume has no generator for (`BLUME_OPENAPI_UNKNOWN_CODE_SAMPLE`) is dropped from the config instead. **If you added redirects, run `blume audit --only redirects` after the build**: `validate` warns about a redirect whose `to` leads nowhere (`BLUME_BROKEN_REDIRECT`, at its line in `blume.config.ts`), but accepts a `to` that is another redirect's `from`, so only `audit` follows each one through the build and reports loops and chains. To check every old URL, request them against `blume dev` or `blume preview` (`curl -sL`; both apply every redirect, patterns included, with its status), or check them against `dist/` plus the redirect rules (`dist/blume-redirects.json` when no host adapter is set, else the host's `_redirects` or `vercel.json`). **Old heading anchors:** every source slugs heading ids differently from Blume. After the first build, run `node <skill>/scripts/pin-heading-ids.mjs --old <old site URL or its built-HTML dir>` (`--map dist/blume-redirects.json` when pages moved, built with no host adapter, `--write` to apply, `--only-linked` to pin only linked ids) and rebuild until it reports 0; on a server build it reads `dist/client` itself (skip it for ReadMe and a GitHub wiki: their heading ids already match Blume's, a ReadMe hub rate-limits a crawl, and the script can't read a wiki's pages); any `BLUME_BROKEN_ANCHOR` left was already dead on the old site, so retarget the obvious ones, drop the fragment from the rest, and report them.
192
+ 2. Run `blume dev` and review the site visually (`blume dev`/`preview --port N` move to the next free port when N is taken, so read the `Local http://…` line they print before requesting URLs) — nav structure, tabs, theme, rendered components.
171
193
  3. **Write a migration summary** covering: what was migrated (config, N pages, nav, API references), what was **dropped** (navbar CTAs, footer copyright text, custom theming, conditional redirects, unmappable icons, unsupported components), and suggested follow-ups (`blume eject` for full control, `blume add` to vendor a component for customization).
172
194
 
173
195
  ## Full documentation
174
196
 
175
- The mapping details live in `references/`: one file per source framework (`mintlify.md`, `docusaurus.md`, `fumadocs.md`, `nextra.md`, `starlight.md`), plus **`monorepo.md`** for host-repo integration (content-layout detection, pnpm `minimumReleaseAge`, frozen-lockfile regeneration, the Vercel monorepo recipe, and the Astro/Vite patch). The Mintlify icon + frontmatter pass is automated by **`scripts/mintlify-codemod.mjs`** (zero-dependency, deterministic, idempotent; `--write` to apply). The authoritative Blume docs are bundled in the installed package's **`docs/` directory** — not necessarily at the repository root: in a workspace monorepo (pnpm especially) the package lives in the depending workspace's `node_modules` (e.g. `apps/docs/node_modules/blume/docs`); `node -e "console.log(require.resolve('blume/package.json'))"` run from the depending package prints the exact location. (In a repo checkout of Blume itself, the docs source is `apps/docs/content/docs`.) The most relevant pages:
197
+ The mapping details live in `references/`: one file per source framework (`mintlify.md`, `docusaurus.md`, `fumadocs.md`, `nextra.md`, `starlight.md`, `vitepress.md`, `vuepress.md`, `mkdocs.md`, `mdbook.md`, `docus.md`, `fern.md`, `gitbook.md`, `readme.md`, `redocly.md`, `docsify.md`, `jekyll.md`, `github-wiki.md`), plus **`monorepo.md`** for host-repo integration (content-layout detection, pnpm `minimumReleaseAge`, frozen-lockfile regeneration, the Vercel monorepo recipe, and the Astro/Vite patch). The Mintlify icon + frontmatter pass is automated by **`scripts/mintlify-codemod.mjs`** (zero-dependency, deterministic, idempotent; `--write` to apply). The Docsify content pass is automated by **`scripts/docsify-codemod.mjs`** (zero-dependency, deterministic, idempotent; `--write` to apply, `--snapshot <dir>` to record the old heading ids for `pin-heading-ids.mjs`, `--group-folders` for a folder per sidebar group and the route table). The Fern placement, frontmatter, component, and redirect pass is automated by **`scripts/fern-codemod.mjs`** (zero-dependency, deterministic, idempotent; `--write` to apply, `endpoints` after the first build). The ReadMe content pass is automated by **`scripts/readme-codemod.mjs`** (zero-dependency, deterministic, idempotent; `--write` to apply, `--routes <file>` for endpoint redirects after a build). The MkDocs content pass is automated by **`scripts/mkdocs-codemod.mjs`** (zero-dependency, deterministic, idempotent; `--write` to apply). The Jekyll content and navigation pass is automated by **`scripts/jekyll-codemod.mjs`** (zero-dependency, deterministic; it runs once, writing `jekyll-migration.json` and refusing a folder that already has one; `--write` to apply, `--old <build>` for URLs and the sidebar, `--callouts` for a site whose `_config.yml` names none). The GitHub wiki pass is automated by **`scripts/github-wiki-codemod.mjs`** (zero-dependency, deterministic, idempotent; `routes` writes the route table, `convert --write` applies, `check` verifies the table and old URLs against a build, `stubs` writes link stubs for the old wiki). The Docus content pass is automated by **`scripts/docus-codemod.mjs`** (zero-dependency, deterministic, idempotent; `--write` to apply). The VitePress content pass is automated by **`scripts/vitepress-codemod.mjs`** (zero-dependency, deterministic, idempotent; `--write` to apply, `--redirects <old build>` for the `.html` redirect list). Operation routes for redirects come from **`scripts/operation-routes.mjs`** (zero-dependency, read-only; reads a built `dist/`), partial-file and out-of-root includes are generated by **`scripts/include-excerpts.mjs`** (zero-dependency, deterministic; copied into the project, it runs before every build), and old heading ids are pinned by **`scripts/pin-heading-ids.mjs`** (zero-dependency; reports by default, `--write` to apply). The authoritative Blume docs are bundled in the installed package's **`docs/` directory** — not necessarily at the repository root: in a workspace monorepo (pnpm especially) the package lives in the depending workspace's `node_modules` (e.g. `apps/docs/node_modules/blume/docs`); `node -e "console.log(require.resolve('blume/package.json'))"` run from the depending package prints the exact location. (In a repo checkout of Blume itself, the docs source is `apps/docs/content/docs`.) The most relevant pages:
176
198
 
177
199
  - `configuration/index.mdx` — every `blume.config.ts` field.
178
200
  - `content/navigation.mdx` — the sidebar/tabs/selectors model.