blume 2.1.3 → 2.2.1

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 (397) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +134 -0
  3. package/README.md +4 -4
  4. package/dist/cli/{chunk-y4msrwm9.js → chunk-080mfch6.js} +15 -15
  5. package/dist/cli/{chunk-gfc5fqvf.js → chunk-1ch3ct3r.js} +18 -18
  6. package/dist/cli/{chunk-gfc5fqvf.js.map → chunk-1ch3ct3r.js.map} +1 -1
  7. package/dist/cli/{chunk-32khwcb6.js → chunk-2ssh60qh.js} +42 -69
  8. package/dist/cli/{chunk-32khwcb6.js.map → chunk-2ssh60qh.js.map} +9 -10
  9. package/dist/cli/{chunk-9qzgg6mj.js → chunk-34wap3ed.js} +3 -3
  10. package/dist/cli/{chunk-k3gr1nm9.js → chunk-40vk8kgf.js} +13 -16
  11. package/dist/cli/{chunk-k3gr1nm9.js.map → chunk-40vk8kgf.js.map} +3 -3
  12. package/dist/cli/chunk-487b95qy.js +329 -0
  13. package/dist/cli/chunk-487b95qy.js.map +13 -0
  14. package/dist/cli/chunk-4zxh25hc.js +157 -0
  15. package/dist/cli/chunk-4zxh25hc.js.map +10 -0
  16. package/dist/cli/chunk-69rjf786.js +146 -0
  17. package/dist/cli/chunk-69rjf786.js.map +11 -0
  18. package/dist/cli/{chunk-dqhg54mr.js → chunk-9h8mh2tk.js} +3 -3
  19. package/dist/cli/{chunk-dqhg54mr.js.map → chunk-9h8mh2tk.js.map} +2 -2
  20. package/dist/cli/{chunk-bqxaqn5p.js → chunk-9jqmq5hv.js} +8 -8
  21. package/dist/cli/{chunk-bqxaqn5p.js.map → chunk-9jqmq5hv.js.map} +1 -1
  22. package/dist/cli/{chunk-dv2z0rzf.js → chunk-aafhwx74.js} +38 -13
  23. package/dist/cli/{chunk-dv2z0rzf.js.map → chunk-aafhwx74.js.map} +5 -4
  24. package/dist/cli/{chunk-desvm72z.js → chunk-b680sv30.js} +22 -26
  25. package/dist/cli/chunk-b680sv30.js.map +13 -0
  26. package/dist/cli/{chunk-0w6hs479.js → chunk-beb0z983.js} +30 -18
  27. package/dist/cli/chunk-beb0z983.js.map +10 -0
  28. package/dist/cli/{chunk-1f882900.js → chunk-c5bjxq8v.js} +2 -2
  29. package/dist/cli/chunk-e1n41w9m.js +155 -0
  30. package/dist/cli/chunk-e1n41w9m.js.map +11 -0
  31. package/dist/cli/{chunk-sc93apfs.js → chunk-etckvbrh.js} +17 -17
  32. package/dist/cli/{chunk-sc93apfs.js.map → chunk-etckvbrh.js.map} +2 -2
  33. package/dist/cli/{chunk-bxwhtvw8.js → chunk-f260jdrn.js} +5 -5
  34. package/dist/cli/{chunk-74wfhv79.js → chunk-fnxgtjeq.js} +1 -1
  35. package/dist/cli/{chunk-ms9z66nz.js → chunk-gwy55cx3.js} +2 -2
  36. package/dist/cli/{chunk-ms9z66nz.js.map → chunk-gwy55cx3.js.map} +2 -2
  37. package/dist/cli/{chunk-dhwtqnnd.js → chunk-gxhcf2hs.js} +2 -2
  38. package/dist/cli/{chunk-tfycddbt.js → chunk-h0qwdbem.js} +2 -2
  39. package/dist/cli/{chunk-nbyvgnnm.js → chunk-hktq4j5r.js} +109 -49
  40. package/dist/cli/chunk-hktq4j5r.js.map +36 -0
  41. package/dist/cli/{chunk-twy7psdz.js → chunk-hn3g2b89.js} +14 -10
  42. package/dist/cli/chunk-hn3g2b89.js.map +10 -0
  43. package/dist/cli/{chunk-j1x92mx9.js → chunk-j4nfgn2j.js} +1 -1
  44. package/dist/cli/{chunk-dpy8qdjf.js → chunk-k45sq16q.js} +74 -183
  45. package/dist/cli/chunk-k45sq16q.js.map +18 -0
  46. package/dist/cli/{chunk-5cmts6dv.js → chunk-kmswjxj9.js} +7 -7
  47. package/dist/cli/{chunk-5cmts6dv.js.map → chunk-kmswjxj9.js.map} +1 -1
  48. package/dist/cli/{chunk-36bxs9c7.js → chunk-nqna8dzp.js} +27 -23
  49. package/dist/cli/chunk-nqna8dzp.js.map +10 -0
  50. package/dist/cli/{chunk-a57yh0h9.js → chunk-p834rkcn.js} +7 -7
  51. package/dist/cli/{chunk-a57yh0h9.js.map → chunk-p834rkcn.js.map} +1 -1
  52. package/dist/cli/{chunk-60hz265r.js → chunk-ppa6mg4d.js} +24 -11
  53. package/dist/cli/chunk-ppa6mg4d.js.map +10 -0
  54. package/dist/cli/{chunk-s0bdnc7v.js → chunk-q1wwrxzw.js} +5 -4
  55. package/dist/cli/chunk-q1wwrxzw.js.map +10 -0
  56. package/dist/cli/{chunk-ave1wcbr.js → chunk-qcyxkj44.js} +1 -1
  57. package/dist/cli/{chunk-124fvmzq.js → chunk-r5nm9w0c.js} +11183 -8806
  58. package/dist/cli/{chunk-124fvmzq.js.map → chunk-r5nm9w0c.js.map} +82 -66
  59. package/dist/cli/{chunk-ckrcm08h.js → chunk-rtvgmg2j.js} +48 -62
  60. package/dist/cli/{chunk-ckrcm08h.js.map → chunk-rtvgmg2j.js.map} +4 -5
  61. package/dist/cli/{chunk-297ajdcv.js → chunk-t2h8pwy1.js} +6 -6
  62. package/dist/cli/{chunk-297ajdcv.js.map → chunk-t2h8pwy1.js.map} +3 -3
  63. package/dist/cli/{chunk-gxtg6ed2.js → chunk-x60e15st.js} +21 -21
  64. package/dist/cli/{chunk-gxtg6ed2.js.map → chunk-x60e15st.js.map} +3 -3
  65. package/dist/cli/{chunk-58bt3hag.js → chunk-x7bh6d9j.js} +4 -4
  66. package/dist/cli/{chunk-hvcxng5f.js → chunk-ydqzkhjn.js} +6 -6
  67. package/dist/cli/{chunk-kdpzw38a.js → chunk-yg8ckc3g.js} +95 -7
  68. package/dist/cli/chunk-yg8ckc3g.js.map +11 -0
  69. package/dist/cli/{chunk-0pmb4tr4.js → chunk-zxd5jrgx.js} +1205 -222
  70. package/dist/cli/chunk-zxd5jrgx.js.map +44 -0
  71. package/dist/cli/index.js +78 -42
  72. package/dist/cli/index.js.map +7 -6
  73. package/dist/types/ai/changelog-components.d.ts +18 -0
  74. package/dist/types/ai/changelog-markdown.d.ts +12 -0
  75. package/dist/types/ai/openapi-components.d.ts +7 -7
  76. package/dist/types/ai/operation-details.d.ts +39 -0
  77. package/dist/types/ai/relative-links.d.ts +3 -1
  78. package/dist/types/ai/serializers.d.ts +6 -4
  79. package/dist/types/analytics/head.d.ts +4 -2
  80. package/dist/types/analytics/script.d.ts +8 -2
  81. package/dist/types/components/content/youtube.d.ts +20 -0
  82. package/dist/types/components/layout/nav-utils.d.ts +8 -0
  83. package/dist/types/components/openapi/example-panels.d.ts +33 -0
  84. package/dist/types/components/openapi/helpers.d.ts +53 -11
  85. package/dist/types/components/openapi/snippets.d.ts +7 -1
  86. package/dist/types/core/alert-diagnostics.d.ts +10 -0
  87. package/dist/types/core/builtin-tags.d.ts +16 -0
  88. package/dist/types/core/changelog-list.d.ts +48 -0
  89. package/dist/types/core/closest-match.d.ts +6 -0
  90. package/dist/types/core/code-fence-diagnostics.d.ts +18 -0
  91. package/dist/types/core/code-fences.d.ts +8 -1
  92. package/dist/types/core/config-input.d.ts +38 -6
  93. package/dist/types/core/content-assets.d.ts +16 -18
  94. package/dist/types/core/data.d.ts +6 -2
  95. package/dist/types/core/diagnostics.d.ts +14 -0
  96. package/dist/types/core/directive-diagnostics.d.ts +22 -8
  97. package/dist/types/core/former-routes.d.ts +14 -0
  98. package/dist/types/core/github.d.ts +5 -4
  99. package/dist/types/core/heading-markers.d.ts +65 -2
  100. package/dist/types/core/hidden-pages.d.ts +3 -0
  101. package/dist/types/core/includes.d.ts +23 -0
  102. package/dist/types/core/last-modified.d.ts +18 -6
  103. package/dist/types/core/links.d.ts +42 -2
  104. package/dist/types/core/load-module.d.ts +19 -4
  105. package/dist/types/core/manifest.d.ts +14 -1
  106. package/dist/types/core/markdown-mirrors.d.ts +22 -0
  107. package/dist/types/core/mcp-clients.d.ts +47 -0
  108. package/dist/types/core/mdx-elements.d.ts +32 -0
  109. package/dist/types/core/mdx-syntax.d.ts +100 -0
  110. package/dist/types/core/meta-diagnostics.d.ts +7 -0
  111. package/dist/types/core/meta.d.ts +32 -5
  112. package/dist/types/core/nav-diagnostics.d.ts +16 -2
  113. package/dist/types/core/navigation.d.ts +13 -0
  114. package/dist/types/core/ordering-prefix.d.ts +6 -0
  115. package/dist/types/core/pinned-ids.d.ts +23 -0
  116. package/dist/types/core/project-graph.d.ts +9 -1
  117. package/dist/types/core/redirect-patterns.d.ts +18 -0
  118. package/dist/types/core/relative-files.d.ts +31 -0
  119. package/dist/types/core/schema.d.ts +322 -11
  120. package/dist/types/core/sources/normalize.d.ts +101 -9
  121. package/dist/types/core/sources/types.d.ts +16 -0
  122. package/dist/types/core/static-files.d.ts +10 -0
  123. package/dist/types/core/static-host.d.ts +42 -0
  124. package/dist/types/core/svg-images.d.ts +26 -0
  125. package/dist/types/core/syntax-diagnostics.d.ts +14 -0
  126. package/dist/types/core/types.d.ts +42 -1
  127. package/dist/types/deploy/generated-files.d.ts +41 -0
  128. package/dist/types/deploy/redirects.d.ts +21 -7
  129. package/dist/types/markdown/code-title.d.ts +9 -5
  130. package/dist/types/markdown/directives.d.ts +8 -0
  131. package/dist/types/markdown/fence-language.d.ts +51 -0
  132. package/dist/types/markdown/fence-meta.d.ts +5 -0
  133. package/dist/types/markdown/github-alerts.d.ts +35 -0
  134. package/dist/types/openapi/asyncapi.d.ts +6 -5
  135. package/dist/types/openapi/checks.d.ts +10 -5
  136. package/dist/types/openapi/model.d.ts +55 -8
  137. package/dist/types/openapi/parse.d.ts +4 -1
  138. package/dist/types/ratelimit/index.d.ts +2 -0
  139. package/dist/types/ratelimit/memory.d.ts +1 -1
  140. package/dist/types/ratelimit/schema.d.ts +40 -0
  141. package/dist/types/ratelimit/unkey.d.ts +48 -0
  142. package/dist/types/sources/filesystem.d.ts +14 -4
  143. package/dist/types/sources/mdx-remote.d.ts +6 -3
  144. package/dist/types/sources/registry.d.ts +3 -3
  145. package/dist/types/theme/color.d.ts +33 -0
  146. package/dist/types/theme/palette.d.ts +122 -0
  147. package/docs/01-quickstart.mdx +1 -1
  148. package/docs/02-deployment.mdx +24 -8
  149. package/docs/04-migrating.mdx +16 -4
  150. package/docs/advanced/changelog.mdx +27 -1
  151. package/docs/advanced/skills.mdx +1 -1
  152. package/docs/cli/audit.mdx +1 -1
  153. package/docs/cli/doctor.mdx +2 -2
  154. package/docs/cli/evals.mdx +1 -1
  155. package/docs/cli/index.mdx +4 -2
  156. package/docs/cli/validate.mdx +10 -7
  157. package/docs/configuration/analytics.mdx +2 -0
  158. package/docs/configuration/customization.mdx +3 -1
  159. package/docs/configuration/index.mdx +7 -6
  160. package/docs/configuration/rate-limiting.mdx +20 -1
  161. package/docs/configuration/theming.mdx +5 -1
  162. package/docs/content/components.mdx +26 -6
  163. package/docs/content/frontmatter.mdx +1 -1
  164. package/docs/content/i18n.mdx +1 -1
  165. package/docs/content/includes.mdx +6 -2
  166. package/docs/content/index.mdx +11 -1
  167. package/docs/content/islands.mdx +2 -0
  168. package/docs/content/meta.mdx +9 -3
  169. package/docs/content/navigation.mdx +14 -6
  170. package/docs/content/sources/obsidian.mdx +1 -1
  171. package/docs/content/sources/remote-mdx.mdx +3 -1
  172. package/docs/content/syntax.mdx +76 -8
  173. package/docs/content/variables.mdx +3 -1
  174. package/docs/discoverability/agent-discovery.mdx +1 -1
  175. package/docs/discoverability/json-api.mdx +1 -1
  176. package/docs/discoverability/llms-txt.mdx +1 -1
  177. package/docs/discoverability/markdown.mdx +2 -2
  178. package/docs/discoverability/mcp.mdx +40 -8
  179. package/docs/discoverability/open-graph.mdx +1 -1
  180. package/docs/discoverability/sitemap-and-robots.mdx +1 -1
  181. package/docs/references/graphql.mdx +2 -0
  182. package/docs/references/openapi.mdx +45 -7
  183. package/package.json +4 -4
  184. package/skills/blume/SKILL.md +2 -2
  185. package/skills/blume-migrate/SKILL.md +51 -29
  186. package/skills/blume-migrate/references/docsify.md +262 -0
  187. package/skills/blume-migrate/references/docus.md +298 -0
  188. package/skills/blume-migrate/references/docusaurus.md +5 -5
  189. package/skills/blume-migrate/references/fern.md +327 -0
  190. package/skills/blume-migrate/references/fumadocs.md +1 -1
  191. package/skills/blume-migrate/references/gitbook.md +267 -0
  192. package/skills/blume-migrate/references/github-wiki.md +259 -0
  193. package/skills/blume-migrate/references/jekyll.md +253 -0
  194. package/skills/blume-migrate/references/mdbook.md +256 -0
  195. package/skills/blume-migrate/references/mintlify.md +5 -5
  196. package/skills/blume-migrate/references/mkdocs.md +236 -0
  197. package/skills/blume-migrate/references/monorepo.md +20 -8
  198. package/skills/blume-migrate/references/nextra.md +5 -5
  199. package/skills/blume-migrate/references/readme.md +257 -0
  200. package/skills/blume-migrate/references/redocly.md +198 -0
  201. package/skills/blume-migrate/references/starlight.md +3 -3
  202. package/skills/blume-migrate/references/vitepress.md +204 -0
  203. package/skills/blume-migrate/references/vuepress.md +271 -0
  204. package/skills/blume-migrate/scripts/docsify-codemod.mjs +2387 -0
  205. package/skills/blume-migrate/scripts/docus-codemod.mjs +2542 -0
  206. package/skills/blume-migrate/scripts/fern-codemod.mjs +2633 -0
  207. package/skills/blume-migrate/scripts/github-wiki-codemod.mjs +2808 -0
  208. package/skills/blume-migrate/scripts/include-excerpts.mjs +649 -0
  209. package/skills/blume-migrate/scripts/jekyll-codemod.mjs +3649 -0
  210. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +17 -13
  211. package/skills/blume-migrate/scripts/mkdocs-codemod.mjs +2526 -0
  212. package/skills/blume-migrate/scripts/operation-routes.mjs +508 -0
  213. package/skills/blume-migrate/scripts/pin-heading-ids.mjs +1049 -0
  214. package/skills/blume-migrate/scripts/readme-codemod.mjs +4139 -0
  215. package/skills/blume-migrate/scripts/vitepress-codemod.mjs +1511 -0
  216. package/src/ai/changelog-components.ts +24 -0
  217. package/src/ai/changelog-markdown.ts +45 -41
  218. package/src/ai/component-markdown.ts +22 -9
  219. package/src/ai/llms.ts +36 -5
  220. package/src/ai/markdown.ts +2 -2
  221. package/src/ai/openapi-components.ts +25 -9
  222. package/src/ai/operation-details.ts +208 -0
  223. package/src/ai/relative-links.ts +48 -8
  224. package/src/ai/serializers.ts +7 -6
  225. package/src/ai/site-skill.ts +6 -2
  226. package/src/analytics/head.ts +11 -5
  227. package/src/analytics/script.ts +13 -3
  228. package/src/astro/generate.ts +47 -11
  229. package/src/astro/index.ts +1 -0
  230. package/src/astro/integration.ts +27 -1
  231. package/src/astro/mdx-source-errors.ts +46 -0
  232. package/src/astro/templates.ts +93 -189
  233. package/src/audit/checks/redirects.ts +5 -1
  234. package/src/audit/redirects.ts +36 -19
  235. package/src/audit/report.ts +22 -9
  236. package/src/audit/run.ts +12 -28
  237. package/src/audit/terms.ts +23 -0
  238. package/src/audit/url.ts +2 -2
  239. package/src/cli/build-failure.ts +256 -7
  240. package/src/cli/command-meta.ts +1 -1
  241. package/src/cli/commands/audit.ts +1 -1
  242. package/src/cli/commands/build.ts +12 -3
  243. package/src/cli/commands/check.ts +13 -2
  244. package/src/cli/commands/dev.ts +1 -1
  245. package/src/cli/commands/doctor.ts +4 -2
  246. package/src/cli/commands/init.ts +3 -3
  247. package/src/cli/commands/preview.ts +17 -1
  248. package/src/cli/commands/validate.ts +22 -10
  249. package/src/cli/prepare.ts +1 -1
  250. package/src/cli/preview-pages.ts +161 -0
  251. package/src/cli/required-secrets.ts +0 -1
  252. package/src/cli/unknown-flags.ts +1 -42
  253. package/src/components/code-copy.ts +51 -0
  254. package/src/components/code-language.ts +75 -0
  255. package/src/components/content/AccordionItem.astro +16 -1
  256. package/src/components/content/Callout.astro +11 -1
  257. package/src/components/content/Changelog.astro +189 -0
  258. package/src/components/content/CodeBlock.astro +8 -11
  259. package/src/components/content/Tabs.astro +16 -2
  260. package/src/components/content/YouTube.astro +6 -4
  261. package/src/components/content/code-block.ts +33 -0
  262. package/src/components/content/content-strings.ts +5 -1
  263. package/src/components/content/inline-markdown.ts +35 -8
  264. package/src/components/content/youtube.ts +49 -7
  265. package/src/components/layout/Analytics.astro +5 -1
  266. package/src/components/layout/NavTree.astro +8 -3
  267. package/src/components/layout/PageActions.astro +81 -23
  268. package/src/components/layout/RootLayout.astro +35 -32
  269. package/src/components/layout/SiteFooter.astro +31 -8
  270. package/src/components/layout/nav-utils.ts +14 -0
  271. package/src/components/layout/search/pagefind.ts +2 -1
  272. package/src/components/openapi/Operation.astro +4 -0
  273. package/src/components/openapi/PanelTabs.astro +9 -3
  274. package/src/components/openapi/RequestBody.astro +42 -8
  275. package/src/components/openapi/RequestPanel.astro +34 -53
  276. package/src/components/openapi/example-panels.ts +110 -0
  277. package/src/components/openapi/helpers.ts +195 -20
  278. package/src/components/openapi/operation-model.ts +63 -26
  279. package/src/components/openapi/request.ts +13 -0
  280. package/src/components/openapi/snippets.ts +14 -2
  281. package/src/core/alert-diagnostics.ts +89 -0
  282. package/src/core/builtin-tags.ts +76 -0
  283. package/src/core/changelog-list.ts +87 -0
  284. package/src/core/closest-match.ts +41 -0
  285. package/src/core/code-fence-diagnostics.ts +267 -0
  286. package/src/core/code-fences.ts +43 -7
  287. package/src/core/component-diagnostics.ts +65 -12
  288. package/src/core/config-input.ts +39 -5
  289. package/src/core/config.ts +46 -1
  290. package/src/core/content-assets.ts +64 -144
  291. package/src/core/data.ts +10 -3
  292. package/src/core/diagnostics.ts +36 -3
  293. package/src/core/directive-diagnostics.ts +265 -47
  294. package/src/core/former-routes.ts +46 -0
  295. package/src/core/git-ignored.ts +53 -0
  296. package/src/core/github.ts +5 -4
  297. package/src/core/heading-markers.ts +156 -2
  298. package/src/core/hidden-pages.ts +64 -0
  299. package/src/core/includes.ts +181 -41
  300. package/src/core/last-modified.ts +47 -10
  301. package/src/core/links.ts +438 -83
  302. package/src/core/load-module.ts +98 -9
  303. package/src/core/manifest.ts +12 -9
  304. package/src/core/markdown-mirrors.ts +52 -0
  305. package/src/core/mcp-clients.ts +87 -0
  306. package/src/core/mdx-elements.ts +153 -0
  307. package/src/core/mdx-syntax.ts +377 -0
  308. package/src/core/meta-diagnostics.ts +134 -0
  309. package/src/core/meta.ts +154 -55
  310. package/src/core/nav-diagnostics.ts +41 -4
  311. package/src/core/navigation.ts +70 -10
  312. package/src/core/ordering-prefix.ts +22 -2
  313. package/src/core/pinned-ids.ts +141 -0
  314. package/src/core/project-graph.ts +221 -29
  315. package/src/core/redirect-patterns.ts +41 -0
  316. package/src/core/relative-files.ts +145 -0
  317. package/src/core/schema.ts +110 -6
  318. package/src/core/sources/mdx-remote.ts +75 -14
  319. package/src/core/sources/normalize.ts +885 -179
  320. package/src/core/sources/notion.ts +5 -4
  321. package/src/core/sources/obsidian.ts +2 -5
  322. package/src/core/sources/types.ts +17 -0
  323. package/src/core/static-files.ts +40 -0
  324. package/src/core/static-host.ts +125 -0
  325. package/src/core/svg-images.ts +98 -0
  326. package/src/core/syntax-diagnostics.ts +491 -0
  327. package/src/core/types.ts +43 -1
  328. package/src/deploy/artifacts.ts +2 -28
  329. package/src/deploy/generated-files.ts +128 -0
  330. package/src/deploy/platforms/vercel.ts +25 -1
  331. package/src/deploy/redirects.ts +43 -16
  332. package/src/deploy/sitemap.ts +2 -1
  333. package/src/markdown/base-links.ts +87 -6
  334. package/src/markdown/code-title.ts +29 -13
  335. package/src/markdown/content-assets.ts +210 -0
  336. package/src/markdown/directives.ts +69 -5
  337. package/src/markdown/fence-language.ts +106 -0
  338. package/src/markdown/fence-meta.ts +42 -0
  339. package/src/markdown/github-alerts.ts +172 -0
  340. package/src/markdown/heading-anchors.ts +265 -56
  341. package/src/markdown/include.ts +30 -3
  342. package/src/markdown/index.ts +145 -40
  343. package/src/markdown/prompt-text.ts +95 -0
  344. package/src/markdown/relative-links.ts +37 -2
  345. package/src/markdown/route-snapshot.ts +22 -12
  346. package/src/markdown/svg-fallback.ts +51 -0
  347. package/src/markdown/title-heading.ts +134 -0
  348. package/src/migrate/migrate.ts +127 -2
  349. package/src/og/card.ts +14 -4
  350. package/src/openapi/asyncapi.ts +14 -6
  351. package/src/openapi/checks.ts +160 -15
  352. package/src/openapi/model.ts +172 -33
  353. package/src/openapi/parse.ts +13 -4
  354. package/src/openapi/render-mdx.ts +55 -9
  355. package/src/openapi/source.ts +87 -6
  356. package/src/ratelimit/index.ts +2 -0
  357. package/src/ratelimit/memory.ts +1 -1
  358. package/src/ratelimit/runtime.ts +81 -0
  359. package/src/ratelimit/schema.ts +2 -0
  360. package/src/ratelimit/unkey.ts +61 -0
  361. package/src/registry/eject.ts +0 -1
  362. package/src/registry/registry.ts +1 -1
  363. package/src/search/documents.ts +10 -2
  364. package/src/sources/filesystem.ts +28 -3
  365. package/src/sources/mdx-remote.ts +7 -15
  366. package/src/theme/color.ts +14 -0
  367. package/src/theme/entry.ts +22 -1
  368. package/src/theme/palette.ts +74 -1
  369. package/dist/cli/chunk-0pmb4tr4.js.map +0 -35
  370. package/dist/cli/chunk-0w6hs479.js.map +0 -10
  371. package/dist/cli/chunk-150jk7km.js +0 -52
  372. package/dist/cli/chunk-150jk7km.js.map +0 -10
  373. package/dist/cli/chunk-36bxs9c7.js.map +0 -10
  374. package/dist/cli/chunk-53jqw8z0.js +0 -89
  375. package/dist/cli/chunk-53jqw8z0.js.map +0 -10
  376. package/dist/cli/chunk-60hz265r.js.map +0 -10
  377. package/dist/cli/chunk-desvm72z.js.map +0 -14
  378. package/dist/cli/chunk-dpy8qdjf.js.map +0 -17
  379. package/dist/cli/chunk-es8h0v3j.js +0 -441
  380. package/dist/cli/chunk-es8h0v3j.js.map +0 -11
  381. package/dist/cli/chunk-kdpzw38a.js.map +0 -11
  382. package/dist/cli/chunk-nbyvgnnm.js.map +0 -36
  383. package/dist/cli/chunk-s0bdnc7v.js.map +0 -10
  384. package/dist/cli/chunk-twy7psdz.js.map +0 -10
  385. package/dist/cli/chunk-ydp3065p.js +0 -64
  386. package/dist/cli/chunk-ydp3065p.js.map +0 -10
  387. /package/dist/cli/{chunk-y4msrwm9.js.map → chunk-080mfch6.js.map} +0 -0
  388. /package/dist/cli/{chunk-9qzgg6mj.js.map → chunk-34wap3ed.js.map} +0 -0
  389. /package/dist/cli/{chunk-1f882900.js.map → chunk-c5bjxq8v.js.map} +0 -0
  390. /package/dist/cli/{chunk-bxwhtvw8.js.map → chunk-f260jdrn.js.map} +0 -0
  391. /package/dist/cli/{chunk-74wfhv79.js.map → chunk-fnxgtjeq.js.map} +0 -0
  392. /package/dist/cli/{chunk-dhwtqnnd.js.map → chunk-gxhcf2hs.js.map} +0 -0
  393. /package/dist/cli/{chunk-tfycddbt.js.map → chunk-h0qwdbem.js.map} +0 -0
  394. /package/dist/cli/{chunk-j1x92mx9.js.map → chunk-j4nfgn2j.js.map} +0 -0
  395. /package/dist/cli/{chunk-ave1wcbr.js.map → chunk-qcyxkj44.js.map} +0 -0
  396. /package/dist/cli/{chunk-58bt3hag.js.map → chunk-x7bh6d9j.js.map} +0 -0
  397. /package/dist/cli/{chunk-hvcxng5f.js.map → chunk-ydqzkhjn.js.map} +0 -0
@@ -0,0 +1,267 @@
1
+ # GitBook → Blume
2
+
3
+ GitBook is a hosted editor. This reference covers the Markdown it writes when a space syncs to GitHub or GitLab (Git Sync): `SUMMARY.md` (a space's navigation), `.gitbook.yaml` (where its files live, plus redirects), and, on site-wide Git Sync, `gitbook-docs.yaml` (which space maps to which directory). Theme, logo, header and footer links, custom domain, site redirects, and access control live in the GitBook app, not the repo. Two things set this migration apart:
4
+
5
+ - **URLs follow `SUMMARY.md`, not the file tree.** Moving a page in the editor changes its URL but not its file. Blume derives route _and_ sidebar from the file path, so the core move is putting each file at its old URL.
6
+ - **Blocks are Liquid-style tags (`{% hint %}`) and raw HTML inside `.md`.** Almost every page becomes `.mdx`, and MDX rejects several things GitBook writes.
7
+
8
+ ## Detect
9
+
10
+ - **`.gitbook.yaml`** or **`.gitbook.yml`** (GitBook reads both) at the root or in a space directory; **`gitbook-docs.yaml`** for site-wide Git Sync.
11
+ - A **`SUMMARY.md`** (GitBook writes it opening `# Table of contents`; a hand-written one may open `# Summary`), with `* [Title](path.md)` items and `## Group` headings, beside a **`.gitbook/`** folder (`assets/`, `includes/`, maybe `vars.yaml`). `.gitbook.yaml` is optional, so this pair can be the only sign.
12
+ - Pages that open with a `# Title` line and use `{% hint %}`, `{% tabs %}`, `{% content-ref %}`; sync commits titled `GITBOOK-<n>: …`.
13
+ - **Not this format:** `book.toml` + `src/SUMMARY.md` is mdBook. `book.json` + `SUMMARY.md` is the legacy GitBook CLI or HonKit — see [Legacy GitBook CLI](#legacy-gitbook-cli-bookjson).
14
+
15
+ ## First, collect what the repo doesn't hold
16
+
17
+ Do this while the GitBook site is still published. Nothing moves before step 1.
18
+
19
+ 1. **Old URLs.** `<site>/sitemap.xml` indexes one `sitemap-pages.xml` per published space (`/sitemap-pages.xml`, `/de/sitemap-pages.xml`, …); fetch them all. They list every canonical page, hidden and generated OpenAPI pages included, but not `noIndex`/`noRobotsIndex` pages (take those from `SUMMARY.md`). `<site>/llms.txt` lists the same pages with their `.md` URLs. Save the paths, one per line, outside the content root.
20
+ 2. **Site redirects** (Settings → Redirects). The app has no export: `GET https://api.gitbook.com/v1/orgs/{organizationId}/sites/{siteId}/redirects?limit=1000` with a token (paginated: while the response has `next.page`, fetch again with `&page=<it>`), or ask the user. With neither, report it as a follow-up.
21
+ 3. **Site customization.** `GET https://api.gitbook.com/v1/orgs/{organizationId}/sites/{siteId}/customization` with a token. Without one, read the published HTML: logo and favicon URLs go through `~gitbook/image?url=<original>` (decode `url` and download it), the header carries a light logo (`class="block dark:hidden …"`) and, when one is set, a dark one (`hidden dark:block`), and the inline CSS declares the primary color as an RGB triplet, `--primary-original`.
22
+ 4. **Spaces not in the repo.** Compare the sitemap index with the repo's directories. A space with `content.directory: null`, or a language or version the site serves but the repo lacks (often GitBook-made translations), has no Markdown here. Ask: export it (Git Sync that space into a branch, or `POST https://api.gitbook.com/v1/spaces/{spaceId}/git/export`) and migrate it; regenerate it with `blume translate` once `i18n` is set; or drop it and redirect its URLs (see URLs and redirects). Report the choice.
23
+
24
+ ## Config
25
+
26
+ Map what step 3 found or the user supplies; leave the rest at Blume's defaults.
27
+
28
+ | GitBook | Blume |
29
+ | --- | --- |
30
+ | Site title (`gitbook-docs.yaml` `site.title`) | `title` |
31
+ | Logo (light and dark) | `logo: { image: { light, dark } }`, files in `public/` (SKILL.md covers monochrome SVGs and wordmarks) |
32
+ | Icon (favicon) | `public/icon.svg` or `icon.png` (a dark one → `icon-dark.*`); no config field. An emoji icon has no file: make one or report it |
33
+ | Primary color | `theme.accent` |
34
+ | Default mode, fonts, code theme | `theme.mode`, `theme.fonts.{body,display,mono}`, `markdown.code.theme` (nearest Shiki themes) |
35
+ | Theme style, tint and semantic colors, corner, depth, link, and sidebar styles | drop (report); a root `theme.css` if one matters |
36
+ | Header links; a button-styled one | `navigation.actions`; `navigation.cta` |
37
+ | Announcement | `banner` (`{ content, link, dismissible, id }`) |
38
+ | Footer | `footer.links`, flattened into one row; copyright → `footer.copyright`; group titles and logo drop (report) |
39
+ | Social accounts | `footer.socials` |
40
+ | Social preview image | `seo.image` on the landing page (Blume renders a card for every other page) |
41
+ | Git Sync repository and branch | `github: { owner, repo, branch }`, plus `dir` or `host` when needed; GitLab has no equivalent (report) |
42
+ | Page actions: Markdown and AI providers, MCP, PDF | defaults; `agents.mcp.enabled` plus server output (follow-up); `export` |
43
+ | GitBook Assistant | `ai.assistant` plus server output (follow-up), or drop |
44
+ | Integrations | adapters in `analytics` from `blume/analytics`; others `script()` or report |
45
+ | Custom domain | the DNS record moves to the new host; keep the URL as `deployment.site` unless the host is Vercel or Netlify |
46
+ | Custom subdirectory (`example.com/docs`) | `deployment: { base: "/docs" }` |
47
+ | Share links, authenticated access | host-level protection (the deployment docs' **Private docs**); report |
48
+ | `.gitbook/vars.yaml` | `variables` (see Variables) |
49
+
50
+ **Content root.** `root: ./docs/` is Blume's default `content.root`. With `root: ./`, `git mv` the space (pages, `SUMMARY.md`, `.gitbook/`) into `docs/` — routes are relative to the content root, so they don't change — or scope `content.include` (`references/monorepo.md` §1). Left unscoped, the repo's `README.md` and every stray `.md` publish.
51
+
52
+ ## Navigation: `SUMMARY.md` → folders
53
+
54
+ ### How GitBook builds a URL
55
+
56
+ - The first `SUMMARY.md` item (`structure.readme`, `README.md` by default) is served at the space root; its own slug (`/readme`) redirects there.
57
+ - Every other page: the space's prefix (see Site structure), then its `## Group`'s slug, each ancestor page's slug, and its own. A page's slug comes from its file name at import (a `README.md` → its folder's name), lowercased; a group's, from its heading (emoji dropped) or the `id` of an `<a id="…">` inside it.
58
+ - **The file's own path doesn't count.** A page filed at `configuration/post-install.md` but listed under group "Deployment" → parent `deployment/permissions/README.md` publishes at `/deployment/permissions/post-install`.
59
+
60
+ Compute every page's URL from `SUMMARY.md`, then check them against the sitemap list; the sitemap wins.
61
+
62
+ ### Put every file at its old URL
63
+
64
+ One `git mv` per page keeps both the route and the sidebar:
65
+
66
+ - A page with no children → `<url>.mdx`; with children → `<url>/index.mdx`; the landing page → `index.mdx` at the content root (`.md` for a page that stays plain). Lowercase every segment: Blume keeps a file name's case, so `OCR.md` would publish at `/OCR` where GitBook served `/ocr`.
67
+ - **`README.md` isn't an index in Blume** (`guides/README.md` publishes at `/guides/README`): every README becomes `index`.
68
+ - Each `## Group` becomes a top-level folder named for its slug.
69
+ - Keep a table of old file → new file → route: links, includes, and image paths are rewritten from it.
70
+ - A file not in `SUMMARY.md` was never published. Delete it, or prefix it with `_` to keep it unpublished, and report it.
71
+ - Delete `SUMMARY.md` once the folders and `meta.ts` files carry its structure; it would publish as a page.
72
+
73
+ ### Order, labels, and display → `meta.ts`
74
+
75
+ - **Order.** Each folder's `meta.ts` lists its children in `SUMMARY.md` order (`pages`); the content root's lists the loose pages, then the group folders.
76
+ - **Labels.** A page's title is its `SUMMARY.md` link text, and a link title (`[Title](page.md "Label")`) → `sidebar.label`. A group folder's `meta.ts` `title` is the heading text, and **a parent page's folder gets `title: <its SUMMARY text>` too** — otherwise the row shows the humanized folder name (`deployment-guides` → "Deployment Guides" for a page titled "Getting started").
77
+ - **Display.** GitBook shows groups as plain headings and each parent page as one expandable row. Set `navigation: { sidebar: { display: "group" } }`, `display: "flat"` in each group folder's `meta.ts`, and `sidebar: { hidden: true }` on each parent's `index`: the folder row already links to it, so without this the parent lists twice.
78
+ - **Contents pages.** A parent whose body is empty or only content-refs to its own children → delete those and set `directory: "card"` in the folder's `meta.ts`. It inherits: set `directory: "none"` on a nested folder that isn't a contents page.
79
+ - **Order Blume can't match.** Loose top-level pages always list above groups and folders. A parent page among loose pages (Overview, Editions ▸, Use Cases) drops below them, and pages after a `***` divider in `SUMMARY.md` (top-level again: `/uninstall`, not `/other/uninstall`) rise above the groups. Move such pages into a parenthesized group folder (`(more)/`, which adds no URL segment) ordered in the root `meta.ts`, or accept the order; report either.
80
+ - **External links** (`* [Website](https://…)`) → `navigation.featured` or `navigation.actions`; report the move.
81
+ - **Hidden pages.** GitBook's `hidden: true` hides a page's whole subtree; Blume's hides one page, so set it on the index and every descendant (a folder whose pages are all hidden leaves the sidebar).
82
+ - A list item that is a YAML block with `type: builtin:openapi` is a generated API reference (see OpenAPI).
83
+
84
+ ### Site structure: `gitbook-docs.yaml`
85
+
86
+ `site.structure` nests `section-group`, `section`, `space`, and `external-link` entries. Every entry has a `key` (meaningless to Blume), a `title`, and maybe `localizedTitle`; most can carry `draft` or `condition`. Sections and spaces have a `path` and maybe `default: true` (a section group has no `path`); section groups, sections, and external links can have an `icon`; a space can be `hidden` and has `content.directory` (`null` when not synced) and maybe `content.language`. Skip `draft` entries, and treat a `condition` like adaptive content (report it).
87
+
88
+ - **URL prefixes.** The default section's default space is at the site root. Another space → `/<section>/<space>`; a non-default section's default space → `/<section>`. A site without sections serves its default space at the root and the rest at `/<space>`.
89
+ - **Sections** → a top-level folder each plus a `navigation.tabs` entry (`icon` → Lucide). The default section stays at the content root; add `{ label, path: "/" }` to show it as a tab.
90
+ - **Section groups** → a dropdown tab scopes the sidebar by one `path`, so give each section its own tab (the grouping is lost; report) or move them under one prefix (URLs change; pattern redirects).
91
+ - **Spaces within a section:** languages → `i18n` (Blume puts the locale first, site-wide: `/payments/fr/x` → `/fr/payments/x`); versions → `versions` (top-level `/<id>/…`, ids start with a letter); products → a folder each and a `navigation.selectors` dropdown. Redirect the old prefixes.
92
+ - A space's directory can sit inside another's (`./home/fr` in `./home`): separate them, then do the `SUMMARY.md` work per space. A `hidden: true` space is still published but left out of the space switcher: keep its pages, leave it out of `navigation.selectors`, and report it. `localizedTitle` → a per-locale tab label.
93
+ - **External links** (`external-link`, at the root or in a section group) → `navigation.actions` entries (`{ label, href }`; `localizedTitle` → a per-locale `label`, the `icon` drops).
94
+
95
+ ## Content and components
96
+
97
+ ### `.md` or `.mdx`
98
+
99
+ Rename to `.mdx` every page with a `{% … %}` block, `$$` math, a mermaid fence, or a component after conversion. Directives, components, math, mermaid, `package-install`, and `ts2js` are MDX-only: in `.md`, a leftover `{% hint %}` or `:::note` is literal text with a green build (each `{% … %}` tag warns `BLUME_TEMPLATE_TAG` at its line, a `:::note` `BLUME_MD_DIRECTIVE`), and in `.mdx` a leftover `{% … %}` fails it (`BLUME_MDX_SYNTAX`, which `blume validate` and `blume check` report too). Plain pages can stay `.md`; tables, footnotes, fence meta, `<include>`, `{{variables}}`, `[#id]`, and raw HTML work in both. An include is spliced as text, so it's parsed in the including page's format.
100
+
101
+ ### MDX hazards
102
+
103
+ Each of these fails `blume build` in `.mdx` (`blume check` and `validate` name the ones MDX can't parse as `BLUME_MDX_SYNTAX`, at their line):
104
+
105
+ - **Unclosed `<img …>` and `<br>`** (`BLUME_MDX_UNCLOSED_ELEMENT` names each). Close them — better, convert images to Markdown.
106
+ - **HTML comments** → `{/* */}`.
107
+ - **A `{` outside code.** GitBook's `\{{x\}}` isn't enough; write `` `{{x}}` `` or `\{\{x\}\}`. Braces inside raw HTML (`<code>CN={{DeviceId}}</code>`, `<td>${EMAIL}</td>`) compile, then fail the render: `blume build` names each as `BLUME_MDX_UNDEFINED_NAME` at its line. Turn `<code>`, `<strong>`, `<a>`, `<p>`, and `<br>` inside table cells into Markdown.
108
+ - **Markdown inside JSX** (`<Tab>`, `<Step>`, `<Frame>`) needs blank lines around it.
109
+
110
+ GitBook's `\<` and entities (`&#x20;`, `&#x3C;`) are fine (an escaped `\<Name` can still warn `BLUME_UNKNOWN_COMPONENT`; the page renders correctly). Strip the U+200B/U+200C characters GitBook leaves at the start of some code lines.
111
+
112
+ ### Blocks
113
+
114
+ | GitBook | Blume |
115
+ | --- | --- |
116
+ | `# Title` (the first line) | frontmatter `title`; delete the line |
117
+ | `{% hint style="info" %}` (`success`, `warning`, `danger`) | `:::info` (`:::success`, `:::warning`, `:::danger`); legacy plugin `tip` → `:::tip`, `working` → `:::note`. A directive takes no `icon`: keep one that matters as `<Callout type="<type>" icon="<lucide>">`, else drop it; inside another directive, give the outer one a longer fence (`::::`) |
118
+ | `{% tabs %}` + `{% tab title="X" icon="y" %}` | `<Tabs>` + `<Tab title="X" icon="<lucide>">`; `fullWidth` drops. Blume switches same-titled tabs together page-wide (`sync={false}` opts a group out) |
119
+ | `{% code title="a.js" lineNumbers="true" overflow="wrap" expandable="true" %}` | fence meta: ` ```js title="a.js" lineNumbers wrap expandable `; `fullWidth` drops. **A fence with no language takes `text`** (` ```text lineNumbers `), or the first option becomes the language (`BLUME_UNKNOWN_CODE_LANGUAGE`) |
120
+ | `<pre class="language-x" data-title="…" data-line-numbers data-overflow="wrap"><code>` | a fence built from those attributes (others drop), entities decoded, `<strong>`/`<em>` stripped |
121
+ | `{% stepper %}` + `{% step %}` | `<Steps>` + `<Step>`. **A step that opens with a heading keeps it as the first line inside `<Step>`**: GitBook publishes it as an anchored heading in the outline, and `title` renders a plain paragraph with no anchor. A bold-line opener → `title` |
122
+ | `{% columns %}` + `{% column %}` | `<Columns cols={n}>` + `<Column>`; widths drop |
123
+ | `<details><summary>T</summary>` (`open`) | `<Expandable title="T">` (`defaultOpen`); a run → `<Accordion>` + `<AccordionItem title="T">`. A title renders inline Markdown, so write a summary's `<code>`, `<strong>`, and `<a>` as Markdown in it: raw HTML there shows as text |
124
+ | `{% content-ref url="x.md" %}`; legacy `{% page-ref page="x.md" %}` | `<Card title="<x's title>" href="<x's route>" />` (the link text is a file name, so look the title up); a run → `<CardGroup>`; links to a parent's own children → `directory: "card"` |
125
+ | `[text](x.md#h "mention")` | a plain link without `"mention"`, its text replaced by the target's title |
126
+ | `{% embed url="…" %}`, often with no `{% endembed %}` | YouTube → `<YouTube url="…" />` (`?t=122` → `start={122}`; `url` ignores `t`); anything else → `<Card title="…" href="…" />`. A caption → `<Frame caption="…">` around it (YouTube's `title` only reaches screen readers) |
127
+ | `{% file src="…" %}caption{% endfile %}` | `[caption](../assets/x.pdf)`, relative to the page: Blume publishes a file a relative link names with the page. For a URL that never changes, put the file in `public/files/` and link `/files/x.pdf` |
128
+ | `<figure><img src alt><figcaption>` | `![alt](relative/path.png)` (optimized); a caption → `<Frame caption="…">` around it; `<div data-with-frame="true">` → `<Frame>`. A raw `<img>` with a relative `src` works too, but unoptimized |
129
+ | `<img class="gitbook-drawing">` | a Markdown image |
130
+ | inline `<img data-size="line">` | a Markdown image (renders at its own size), or `<img src="/<public path>" alt="" height="20" />` to stay text-height (report) |
131
+ | `<picture>` with a dark-mode source | the light image (report) |
132
+ | `<table data-view="cards">` | `<CardGroup cols={3}>` (`data-card-size="large"` → `cols={2}`), a `<Card>` per row: first text cell → `title`, other text → body, the `data-card-target` link → `href`, the `data-card-cover` file → `img`, relative to the page like a Markdown image (`img="../assets/cover.png"`), an `<i class="fa-…">` cell → `icon` |
133
+ | other `<table>` (`data-header-hidden`, `data-search`, cells with `<p>`/`<br>`) | a GFM table (a hidden header's first row becomes the header); a cell's `<ul><li>` can stay inline in `.mdx` |
134
+ | `$$…$$`, a ` ```mermaid ` fence | unchanged, in `.mdx` |
135
+ | Emoji: `:rocket:`, escaped `:ballot\_box\_with\_check:`, `<span data-gb-custom-inline data-tag="emoji">☑️</span>` | the character itself: Blume renders no shortcodes |
136
+ | `<i class="fa-x">:x:</i>` | `<Icon icon="<lucide>" />`, or delete it |
137
+ | `<a class="button primary">`, `<button data-action="…">` | a link or a `<Card>` with `cta`; search and ask buttons drop |
138
+ | `<mark style="color:…">` | `<mark>` or bold; report colors that carried meaning |
139
+ | `## Heading <a href="#x" id="x"></a>` | works as written: the heading takes `x` as its id. `## Heading [#x]` is the tidier spelling |
140
+ | `[^1]` annotations | unchanged: footnotes at the page's end, not hover popups |
141
+ | `{% updates %}` + `{% update date="…" %}` | `type: changelog` pages (SKILL.md "Changelogs"), or dated `##` sections |
142
+ | `{% prompt description="…" %}` | `<Prompt description="…">` |
143
+ | `{% if visitor… %}` | keep what the public saw, drop the tags (report) |
144
+
145
+ ### Links
146
+
147
+ - Every moved file breaks the relative links into and out of it. Rewrite internal links from your table as root-absolute routes (`/deployment/permissions#roles`): Markdown links, content-ref URLs, card `href`s, and `href`s in HTML. A `README.md` target → its folder's route; absolute links to the old domain → routes.
148
+ - Cross-space links `https://app.gitbook.com/s/<spaceId>/<path>` (or `…/o/<orgId>/s/…`) → the target space's prefix plus `<path>`; `GET https://api.gitbook.com/v1/spaces/{spaceId}` gives its `urls.published`. Links into `…/~/changes/…` or to `/broken/pages/<id>` were already broken: fix or report them.
149
+
150
+ ### Images and assets
151
+
152
+ - Move `.gitbook/assets/` to `assets/` at the space root and replace the string `.gitbook/assets/` with `assets/`, then recompute the paths in every moved page.
153
+ - File names have spaces and parentheses. `![](<../assets/image (1).png>)` and `../assets/image%20(1).png` both work, in a page and in an `_includes/` partial alike: a partial's images are rebased onto the including page in any form.
154
+ - GitBook keeps every upload, so many files in `assets/` may be referenced by nothing. Report them and offer to prune.
155
+
156
+ ### Variables and expressions
157
+
158
+ - `.gitbook/vars.yaml` → `variables`; `<code class="expression">space.vars.NAME</code>` → `{{NAME}}`.
159
+ - `page.vars.NAME` (from `vars:` frontmatter) has no page scope: write the value inline. Read inside an include, it becomes a prop: `<include name="Computer">/_includes/x.md</include>` with `{{name}}` in the partial (prop names are lowercase).
160
+ - An expression with logic (`a ? "x" : "y"`) → evaluate it and write the result.
161
+ - Once `variables` is set, an undefined `{{name}}` in prose fails the build: placeholders that are content (`{{DeviceId}}`) go in inline code.
162
+
163
+ ### Includes (reusable content)
164
+
165
+ - `.gitbook/includes/x.md` → `_includes/x.md` at the content root, and `{% include "../../.gitbook/includes/x.md" %}` → `<include>/_includes/x.md</include>` on its own line (a leading `/` resolves from the content root).
166
+ - **Demote every heading in an include one level:** GitBook renders an include's `#` at a page's `##`.
167
+ - Delete includes nothing references. One referenced by ID (`{% include "/reusable-content/…" %}`) isn't in the repo: copy it from the app.
168
+
169
+ ### OpenAPI
170
+
171
+ GitBook has five forms: a `SUMMARY.md` YAML item `type: builtin:openapi` naming an organization spec (`dependencies.spec.ref.spec: <slug>`); `{% openapi src="…" path method %}`; `{% openapi-operation spec="<slug>" path method %}` and `{% openapi-schemas %}`; the older `{% swagger src="…" path method %}`; and hand-written `{% swagger method path baseUrl %}` blocks with `swagger-parameter` and `swagger-response` (oldest: `{% api-method %}`).
172
+
173
+ - **Specs.** Vendor each one into the repo — a `<slug>`'s file comes from the app's OpenAPI panel or `GET https://api.gitbook.com/v1/orgs/{organizationId}/openapi/{specSlug}/versions/latest/content/raw` — and add `openapi({ spec })` from `blume/reference` with a `navigation.tabs` entry (SKILL.md "OpenAPI").
174
+ - **`builtin:openapi`** → set `route` to the old reference's path and keep its parent page's prose under it. Operation URLs change, so redirect each old one in the sitemap (SKILL.md "OpenAPI" covers generating them).
175
+ - **Inline operations** → a link or `<Card>` to the generated page. `{% openapi-schemas %}` has no counterpart (report).
176
+ - **Hand-written blocks** → a `<ParamField query|path|header|body="…">` per parameter (a `cookie` has no slot), the responses as titled fences in `<ResponseExample>`, and `api: "GET /path"` frontmatter when it's the page's only endpoint (`baseUrl` → `api.server`).
177
+ - `x-codeSamples` carries over; GitBook's other `x-*` extensions are ignored (report).
178
+
179
+ ## Frontmatter
180
+
181
+ | GitBook | Blume |
182
+ | --- | --- |
183
+ | `# Title` / `SUMMARY.md` link text | `title`; where they differ, GitBook published the `SUMMARY.md` text |
184
+ | `SUMMARY.md` link title | `sidebar.label` |
185
+ | `description` (often folded `>-`) | `description` (quote a plain value containing `: `) |
186
+ | `icon` (Font Awesome) | `icon`, in Lucide (see Icons) |
187
+ | `hidden: true` | `hidden: true`, on the page and every descendant |
188
+ | `noIndex: true` | `search.exclude: true` + `noindex: true`, on the page and every descendant |
189
+ | `noRobotsIndex: true` | `noindex: true`, on the page and every descendant |
190
+ | `cover`, `coverY`, `coverHeight`, `tags`, `if` | drop (report); a cover worth keeping → an image at the top of the body |
191
+ | `vars` | inline the values, then drop |
192
+ | keys left by older tooling (`category`, `order`) | drop: GitBook ignored them |
193
+
194
+ `layout`:
195
+
196
+ | GitBook `layout` | Blume |
197
+ | --- | --- |
198
+ | every part visible, `width: default` | delete the block |
199
+ | `tableOfContents.visible: false` | `mode: center`; with `title.visible: false` too, `mode: custom` |
200
+ | `outline.visible: false`, or `width: wide` | `mode: wide` |
201
+ | `title.visible: false`, sidebar shown | `mode: frame` |
202
+ | `pagination.visible: false` | `pagination: false` |
203
+ | other parts hidden; a preset (`layout: landing`) | drop (report); for a preset, match the published page |
204
+
205
+ A Blume mode hides more than one part: `center` and `wide` also hide the outline, and `frame` hides it and the page-end links and footer. Report it where GitBook kept those.
206
+
207
+ Blume's frontmatter is strict: remove every key the tables drop.
208
+
209
+ ## Icons
210
+
211
+ GitBook uses Font Awesome names in frontmatter `icon`, `<i class="fa-…">`, tab, hint, and button icons, and section icons. Blume is Lucide-only.
212
+
213
+ - **Frontmatter:** `node <skill>/scripts/mintlify-codemod.mjs <content-dir>`, then `--write`. Its icon pass is the same Font Awesome → Lucide map, and GitBook frontmatter has none of the keys its other rules touch.
214
+ - **Everything else by hand,** with the table in `references/mintlify.md`. Common GitBook names: `circle-info` → `info`, `clock-rotate-left` → `history`, `sliders` → `sliders-horizontal`, `handshake-angle` → `handshake`, `angles-up` → `chevrons-up`, `puzzle-piece` → `puzzle`, `life-ring` → `life-buoy`, `shield-halved` → `shield`, `file-contract` → `file-text`.
215
+ - **Check every name** against the set Blume bundles; an unknown name renders nothing:
216
+
217
+ ```bash
218
+ node -e 'const b=require.resolve("blume/package.json");const s=require(require.resolve("@iconify-json/lucide/icons.json",{paths:[b]}));for(const n of process.argv.slice(1))console.log(n,!!(s.icons[n]||s.aliases?.[n]))' zap history sliders-horizontal
219
+ ```
220
+
221
+ ## URLs and redirects
222
+
223
+ With every file at its old URL, pages need no redirects. The rest are below. GitBook answers all of them with a 307, except a permanent site redirect (308). Blume's default `status` is 301: keep it for a settled move (`.gitbook.yaml` redirects, recovered automatic redirects), and set `status: 307` where the target may change (group paths, dropped translations).
224
+
225
+ - **`.gitbook.yaml` `redirects`** (`old/path: new/file.md`, relative to the space) → `{ from: "/<space prefix>/old/path", to: "<new/file.md's route>" }`.
226
+ - **Site redirects** (`source`, `destination`, `permanent`, `captureWildcard`, `draft`) → `{ from: source, to, status: permanent ? 308 : 307 }`. `destination` is an object, not a URL: `kind: "external"` carries `url`; `site-page` (`siteSpaceId`, `pageId`), `site-space`, and `site-section` name ids. Resolve those to the old URL — `GET …/sites/{siteId}/site-spaces` gives each site space's `urls.published` and `space.id`, `…/sites/{siteId}/sections` each section's `urls.published`, and `GET /v1/spaces/{spaceId}/content` each page's `path` within its space — then to the Blume route at that URL. A trailing `*` with `captureWildcard` → `{ from: "/old/:path*", to: "/new/:path*" }`; without it, every match goes to the one `to`. Skip drafts (the list leaves them out by default), and write `from` without the site base.
227
+ - **Automatic redirects.** GitBook quietly 307s every former URL of a moved or renamed page, and lists them nowhere. Recover them: run every historical `SUMMARY.md` (`git log --format=%h -- SUMMARY.md`, then `git show <sha>:SUMMARY.md`) through the URL rule above, add the route each file's path implies, and request each candidate from the live site (`curl -s -o /dev/null -w '%{http_code} %{redirect_url}'`). Each 307 to a page you kept becomes a redirect.
228
+ - **Group paths and the landing slug.** GitBook redirects a group's own path to its first page, and `/readme` to `/`. Add both (`status: 307` for the group paths).
229
+ - **Dropped translations.** GitBook translates group slugs (`/de/zahlungen/…` for group "Payments"), so `/de/:path*` → `/:path*` sends most translated URLs to 404s. Diff each language's `sitemap-pages.xml` against the default one, add a pattern per translated group (`{ from: "/de/zahlungen/:path*", to: "/payments/:path*", status: 307 }`), then each language's catch-all last: patterns are tried in order.
230
+ - **Case.** GitBook matched URLs case-insensitively; Blume doesn't. Lowercased files cover the canonical URLs; report mixed-case inbound links.
231
+ - **Markdown URLs.** GitBook serves `<url>.md`, and so does Blume; redirects move the `.md` URL with its page.
232
+ - Rewrite internal links **before** adding redirects: `blume validate` accepts a redirect's `from` as a link target.
233
+
234
+ ### Heading anchors
235
+
236
+ GitBook makes heading ids its own way: colons, slashes, and a spaced dash become one hyphen each, dots stay, an id that would start with a digit gets `id-`, and ids stop at 100 characters (`AppConfig:OCSP:UseAuthorizedResponder` → `appconfig-ocsp-useauthorizedresponder`, where Blume writes `appconfigocspuseauthorizedresponder`). Every `#anchor` link GitBook wrote to such a heading fails `blume validate`, and links from other sites land at the top of the page. A rule predicted only 890 of 934 ids on a real site, so read them instead. After `blume build`, from the project:
237
+
238
+ ```bash
239
+ node <skill>/scripts/pin-heading-ids.mjs --old https://docs.example.com # report
240
+ node <skill>/scripts/pin-heading-ids.mjs --old https://docs.example.com --write # pin
241
+ ```
242
+
243
+ It fetches each old page once (one request at a time, cached in `.pin-heading-ids-cache/`; delete it after), pairs its headings with the build's by text, and appends the old id to each differing source heading — `[#id]` in `.mdx`, `{#id}` in `.md`, `\{#id\}` in a partial. Rebuild and rerun until it reports 0. A `NOTE … no source line` is a heading the source doesn't spell out (a component renders it, or it was reworded): pin it by hand. `--only-linked` pins only ids that content links to. A page whose URL changed (a space under a new prefix) is looked up on the old site through `--map`, a JSON list of exact `{ from, to }` entries (patterns are skipped): `dist/blume-redirects.json` works as it is, but a build writes it only when no host adapter is set. Any `BLUME_BROKEN_ANCHOR` left after that was already dead on GitBook: retarget the obvious ones, drop the fragment from the rest, and report them.
244
+
245
+ ### Final check
246
+
247
+ Request each old URL from `blume dev` or `blume preview` (both apply pattern redirects) with `curl -sL`, or check it against `dist/` and `dist/blume-redirects.json` (written only when no host adapter is set), then run `blume audit --only redirects` after the build.
248
+
249
+ ## Legacy GitBook CLI (`book.json`)
250
+
251
+ The old `gitbook-cli` toolchain and its fork HonKit:
252
+
253
+ - **`book.json`:** `root`, `structure`, `title`, `description`, `variables`, `plugins`. Map `title` and `description`; `root` → `content.root`; plugins → whatever Blume has built in, else report.
254
+ - **URLs are file paths:** `a/b.md` → `/a/b.html`, a `README.md` → its folder's `index.html`. Keep the files where they are, rename READMEs to `index`, and add an exact redirect from each `.html` URL (a pattern can't strip the extension).
255
+ - **`SUMMARY.md`** nests the same way, but `### Part` headings and `---` lines add no URL segment → `(part)/` group folders. Entries pointing at anchors (`page.md#x`) aren't pages.
256
+ - **Still applies:** `{% hint %}` → directives; `{% include "./x.md" %}` → `<include>`; `{{ book.name }}` → `variables` and `{{name}}`; other Nunjucks logic → evaluate and inline; `LANGS.md` → `i18n`, with the default language at the content root; `GLOSSARY.md` → no equivalent (report).
257
+
258
+ ## Teardown
259
+
260
+ - **Turn off Git Sync** for every mapped space before the converted content reaches the synced branch: GitBook imports each push to it and commits its own edits back. Keep the GitBook site published until the domain moves.
261
+ - Delete `SUMMARY.md`, `.gitbook.yaml`/`.gitbook.yml`, `gitbook-docs.yaml`, and the emptied `.gitbook/`.
262
+ - Check CI and scripts that read `.md` files or `.gitbook/assets/`: a Markdown link checker breaks on root-absolute routes and skips `.mdx` (replace it with `blume validate --strict`), and an image de-duplication script that greps `*.md` would now delete images in use. Remove GitBook agent instructions (an `AGENTS.md` block, a vendored GitBook skill).
263
+ - A synced repo usually has no `package.json`: scaffold one (SKILL.md step 6).
264
+
265
+ ## Dropped — report these
266
+
267
+ Site themes and styles beyond accent, fonts, and mode; footer group titles and logo; page covers; page and update tags; adaptive content and visitor authentication; GitBook Assistant unless the user adds `ai.assistant`; hint icons you didn't keep; button styles and search/ask buttons; `<mark>` colors; `fullWidth` and column widths; non-YouTube embeds as players; dark card covers and theme-aware images; inline image sizing; `{% openapi-schemas %}` and GitBook `x-*` spec extensions; automatic redirects you couldn't recover; case-insensitive URLs; page-group icons; sidebar positions Blume can't match (parent pages among loose pages, `***` dividers, external links); section groups flattened into tabs; spaces and translations not in the repo; integrations with no adapter; site redirects nobody could export; anything set only in the app that the user didn't supply.
@@ -0,0 +1,259 @@
1
+ # GitHub Wiki → Blume
2
+
3
+ A GitHub wiki is a git repository beside the main one (`https://github.com/<owner>/<repo>.wiki.git`) that GitHub renders at `https://github.com/<owner>/<repo>/wiki`. It has no config file and no build: every file in a markup format GitHub renders is a page, `_Sidebar.md` and `_Footer.md` add the chrome, and pages link each other with `[[Page Name]]`. Five things set this migration apart:
4
+
5
+ - **Folders don't count.** A page's URL is its file name, wherever the file sits (`codebase/compiler/Codebase-Compiler-Binder.md` is `/wiki/Codebase-Compiler-Binder`), and relative links and images resolve from `/<owner>/<repo>/wiki/`, never from the file's folder.
6
+ - **`[[Link text|Page Name]]` puts the text first** in Markdown pages. GitHub's docs show the reverse, which is right only in `.mediawiki` pages, and Obsidian's `[[Page|text]]` is the reverse too, so don't point the `obsidian()` source at a wiki.
7
+ - **Blume renders `[[…]]` as literal text.** It warns `BLUME_WIKILINK_UNSUPPORTED` only for a wiki link that names one of the site's pages or has a `|`, so `[[Page that doesn't exist]]` ships with a green build. Every wiki link has to be converted.
8
+ - **The old URLs live on github.com,** which you can't redirect. What you can do there is in URLs and the old wiki below. Never tell the user the old links will redirect, and add no `redirects` for them.
9
+ - **Nothing pins the routes,** so the new site gets clean ones: one flat lowercase route per page, as the wiki had one flat namespace, with the sidebar groups as `(group)/` folders.
10
+
11
+ Scope: Markdown pages fully. Pages in the other formats GitHub renders get converted to Markdown first (Other markup formats). Checked against GitHub's wiki docs, the live rendering of public wikis (microsoft/TypeScript, microsoft/vscode, koreader, ruTorrent, qBittorrent, mpv, gollum, fzf, AdGuardHome) in October 2026, and an end-to-end migration of the microsoft/TypeScript wiki (77 pages). GitHub Enterprise Server wikis use the same files but weren't checked.
12
+
13
+ ## Detect
14
+
15
+ `blume migrate` can't detect a wiki: name it, `npx blume migrate github-wiki`. A wiki clone has:
16
+
17
+ - a remote ending in `.wiki.git` (`git remote -v`);
18
+ - `Home.md` (or `Home` in another format) at the root, often `_Sidebar.md` and `_Footer.md`;
19
+ - page files named like URLs (`Getting-Started.md`, `Standalone-Server-(tsserver).md`, sometimes `Roadmap‐2021‐2022.md` with U+2010 hyphens), `[[Page Name]]` links, no config, and usually no `package.json`.
20
+
21
+ **Not this format:** Docsify keeps a lowercase `_sidebar.md` beside an `index.html` that sets `window.$docsify` (`references/docsify.md`). On macOS and Windows the file system ignores case, so `_Sidebar.md` and `_sidebar.md` are the same name to `ls`, `test -e`, and Node's `existsSync`: check the real case with `git ls-files`.
22
+
23
+ **Get the pages from the default branch.** `git clone https://github.com/<owner>/<repo>.wiki.git`, then `git remote show origin` names the HEAD branch. GitHub publishes only that branch, so other branches never showed. If the wiki is mirrored from a normal repository (a sync workflow, or a footer saying "fork it and send a pull request"), the wiki clone is still what readers saw.
24
+
25
+ ## Where the Blume project goes
26
+
27
+ The wiki repository can't host the new site: GitHub runs no Actions in it, serves no Pages from it, and accepts no pull requests. Ask the user where the docs will live:
28
+
29
+ - **A folder in the main repository** (the usual choice: the docs then change in the same pull requests as the code). Follow `references/monorepo.md` for a code repo's root `package.json`, `dist/`, and workspace.
30
+ - **A new repository**, for docs with their own maintainers.
31
+
32
+ Without an answer, convert a copy of the wiki clone in place: `blume.config.ts` and `package.json` at its root, the pages moved into `docs/`, so the user can move the folder as one unit. **Before you start, run `git remote remove origin` in the copy.** Pushing the migrated tree to the wiki would publish `blume.config.ts`, every moved page, and the stubs you prepare later as wiki pages.
33
+
34
+ Wiki history doesn't come along with a plain copy. To keep it for `git log --follow` and blame, bring the wiki in with `git subtree add --prefix=<folder> https://github.com/<owner>/<repo>.wiki.git <branch>` and say so in the report. Every page gets moved and edited either way, so `lastModified: "git"` would date every page to the migration commit until it's edited again (Blume follows a rename only when the file didn't change).
35
+
36
+ ## Run the codemod
37
+
38
+ From the copy of the wiki clone (when the docs go into another folder, copy the wiki's files there first and run it from there), before any hand edits:
39
+
40
+ ```bash
41
+ node <skill>/scripts/github-wiki-codemod.mjs routes --repo <owner>/<repo> # report
42
+ node <skill>/scripts/github-wiki-codemod.mjs routes --repo <owner>/<repo> --write # write the route table
43
+ # classify the pages in wiki-routes.json (next section)
44
+ node <skill>/scripts/github-wiki-codemod.mjs convert --repo <owner>/<repo> # dry run
45
+ node <skill>/scripts/github-wiki-codemod.mjs convert --repo <owner>/<repo> --write # apply
46
+ ```
47
+
48
+ It's zero-dependency, deterministic, and idempotent (a second `convert` skips the pages it already moved). Add `--mirror <owner>/<repo>-wiki` when a repository mirrors the wiki, and `--emoji emojis.json` (from `curl -s https://api.github.com/emojis`) when pages use `:shortcodes:`.
49
+
50
+ - **`routes`** lists the pages the way GitHub serves them and writes `wiki-routes.json` (page name → new route) and `wiki-files.json` (page name → file) beside `blume.config.ts`. It prints the files GitHub never served, routes that clash, digit-led routes, non-Markdown pages, and its guesses at moved notices and non-content pages. It never overwrites an existing table: it lists what differs instead.
51
+ - **`convert`** does everything in the Navigation, Content, and Images sections below that the table and `_Sidebar.md` decide: moves each kept Markdown page to `docs/(group)/<route>.md` (`.mdx` when it needs MDX) with its frontmatter, converts wiki links, wiki URLs, alerts, fences, headings, named anchors, and duplicate link definitions, moves the wiki's images beside the pages, and writes every `meta.ts`. It prints the `navigation.featured` entries for `blume.config.ts`, the footer's text, and a `REVIEW` line for everything that needs judgment. **Every `REVIEW` line is a to-do**; the sections below say what to do with each.
52
+ - **`check`** (after `blume build`) and **`stubs`** (for the old wiki) are in URLs and the old wiki.
53
+
54
+ The sections below are the rules the codemod applies, for checking its output and for anything you do by hand.
55
+
56
+ ## The page list and the route table
57
+
58
+ **Which files are pages.** Every file whose extension GitHub renders: `.md`, `.markdown`, `.mdown`, `.mkdn`, `.mediawiki`, `.wiki`, `.textile`, `.rst`, `.rest`, `.org`, `.creole`, `.asciidoc`, `.adoc`, `.asc`, `.pod`, `.rdoc`. The page name is the file name without its last extension (`README.html.md.md` is the page `README.html.md`), with spaces read as `-`. Matching is case-insensitive, `-` and space are the same, and U+2010 is not `-`.
59
+
60
+ - **Not pages:** `_Sidebar.*` and `_Footer.*`, in any folder (GitHub even took `_Sidebar.html.md` as a sidebar). Every other `_`-prefixed file is a page (`_ [obsolete] Config.rest`), and Blume skips `_` files by default, so the rename matters.
61
+ - **Two files with the same name** in different folders share one URL, and GitHub serves one of them (the root one, on the wiki checked). The other was never reachable: leave it out and report it.
62
+ - **Every other file** (images, PDFs, `LICENSE`) is served raw at `/wiki/<path>`, which redirects to `https://raw.githubusercontent.com/wiki/<owner>/<repo>/<path>`.
63
+ - **GitHub's own list** is `https://github.com/<owner>/<repo>/wiki/_pages` (every page, each linked at its URL). Compare the table with it.
64
+
65
+ **Old URLs.** `Home` is `https://github.com/<owner>/<repo>/wiki` (`/wiki/Home` 301s there); every other page is `https://github.com/<owner>/<repo>/wiki/<Page-Name>`, percent-encoded.
66
+
67
+ **New routes.** Each page's route is its name, lowercased, with apostrophes dropped and every other run of characters that aren't letters or digits turned into one `-` (`Standalone-Server-(tsserver)` → `/standalone-server-tsserver`, `What's-new-in-TypeScript` → `/whats-new-in-typescript`, `tsconfig.json` → `/tsconfig-json`); non-Latin letters stay. `Home` becomes `/`. Give one of two clashing pages another route by hand. A route that starts with digits and a separator (`/5-0-release-notes`) would lose them as an ordering prefix, so `convert` pins it with `slug`.
68
+
69
+ **Classify every page** by editing its entry in `wiki-routes.json`:
70
+
71
+ - **Content:** keep the route.
72
+ - **A moved notice** (the whole page says it moved: "This page has moved to <URL>"): set its entry to that URL, or to the target page's route (with its `#anchor`) when the URL is another page of this wiki. It becomes no page; links to it go straight to the target. A short page that points elsewhere without saying it moved ("An introduction can be found in the handbook") is content: keep it.
73
+ - **Not content:** a mirror repository's `README.md`, `SECURITY.md`, or `CONTRIBUTING.md`, test pages, script fixtures, and pages that only say "deprecated" with nowhere to point. Set the entry to `null` and list it in the report (SKILL.md step 2).
74
+
75
+ `routes` prints its guesses for the moved notices and the non-content pages; confirm each one.
76
+
77
+ ## Config
78
+
79
+ Wikis have no config: map what the repository and the special pages say.
80
+
81
+ | GitHub wiki | Blume |
82
+ | --- | --- |
83
+ | Repository name | `title` (the project's name, or "`<Project>` wiki") |
84
+ | Repository description (About) | `description` |
85
+ | The repository | `navigation.repo: "https://github.com/<owner>/<repo>"` for the footer link. Set `github: { owner, repo, branch, dir }` only once the pages live in a repository, so **Edit on GitHub** opens the new file. Leave it unset while the home is unknown, and report it |
86
+ | An image at the top of `_Sidebar.md` | `logo`, with the file in `public/` |
87
+ | External links in `_Sidebar.md` | `navigation.featured`: `convert` prints the array |
88
+ | `_Footer.md` links | `footer.links` |
89
+ | `_Footer.md` text | a copyright or license line → `footer.copyright`, as plain text (Markdown stripped); other text → drop (report), or a `PageFooter` layout slot (`defineComponents`). A "contribute to this wiki" footer drops with its link: edit links replace it |
90
+ | "edited this page … · N revisions" under each title | drop (report). `lastModified: "git"` would show the migration date on every converted page |
91
+ | Wiki search, the Pages list, the per-page outline, the clone URL box | built in, or drop |
92
+ | Site URL | none existed. Ask where the site deploys and set `deployment` (SKILL.md). GitHub Pages: https://useblume.dev/guides/markdown-docs-github-pages, with `deployment: { site: "https://<owner>.github.io", base: "/<repo>" }` for a project site |
93
+
94
+ ## Navigation: `_Sidebar.md` → `(group)/` folders
95
+
96
+ GitHub showed two things in the right rail: the **Pages** list (every page, alphabetical) and, under it, `_Sidebar.md` rendered as Markdown. The sidebar becomes folders, and every page from the Pages list stays reachable.
97
+
98
+ - **Each sidebar group becomes a `(group)/` folder** (SKILL.md): it adds the sidebar group and no URL segment, so every route stays the flat one from the table. Its `meta.ts` has `title` (the label as written) and `pages` (every child in sidebar order). The content root's `meta.ts` lists the groups in sidebar order.
99
+ - **Group labels** are whatever heads a list: a bold line (`**News**`), a heading (`## Contributing`), or an unlinked list item with children (`* Testing`, a nested `(group)/` folder with `display: "group"`). A heading that links a page (`# [Tips and Tricks](…/wiki/Tips-and-Tricks)`) makes that page the group's own: a real folder with the page as its `index.md`. A heading that links outside (`## [User documentation](https://…)`) is a `navigation.featured` link.
100
+ - **A page with nested pages under it** becomes a real folder named for its route, holding the page as `index.md`, its children, and a `meta.ts` with `pages` and `display: "group"`. Each child gets `slug: <its route without the slash>` so its route stays flat. Without `display: "group"`, a nested folder lists after every loose page of its parent group, not where the sidebar had it. Blume shows the parent both as the group header and as its first row; that's expected, so don't hide it.
101
+ - **External links** can't sit inside a generated group, so they move to `navigation.featured` (above the sidebar, on every page); report the move. A link to a heading (`[Installation](…/User-patches#installation)`) has no row in Blume: drop it (the page's outline lists it) and report it.
102
+ - **A page listed twice** stays in its first group; report the other entry. `[[Home]]` in the sidebar drops: the home page is `docs/index.md`, listed first, outside every group.
103
+ - **Pages the sidebar doesn't list** were still in the Pages list: they go in a last group, `(more)/` with `title: "More pages"`, `display: "group"`, `collapsed: true`, and a `pages` list sorted by title (Blume's own order goes by file name, which differs for names like `'this' in TypeScript`). Pages that shared a wiki folder can get their own collapsed group instead (`(codebase)/`); report the groups you made.
104
+ - **No `_Sidebar.md`:** GitHub showed only the Pages list, so the pages sit at the content root (Blume sorts them by title), or in groups by the wiki's folders if it had any; report the choice.
105
+ - **A `_Sidebar.md` inside a folder** replaced the root one for that folder's pages. Usually it's a translation (`ru/_Sidebar.md` beside `ru/Home.ru.md`): map the folder to `i18n` when the pages pair up, else to a `navigation.tabs` entry, and report it.
106
+ - Prose, images, and HTML lists in the sidebar have no place in Blume's sidebar: move a logo to `logo`, put useful prose on the home page, and report the rest.
107
+
108
+ ## Content
109
+
110
+ ### Page files and titles
111
+
112
+ - **Each kept page** lands at `docs/(group)/<route>.md`, `Home` at `docs/index.md`. It stays `.md` unless it needs MDX (GitHub alerts, mermaid, math), then it's `.mdx` (MDX hazards below).
113
+ - **`title`** is the page name as GitHub showed it above the page: `-` → space, U+2010 → `-` (`Standalone-Server-(tsserver)` → `Standalone Server (tsserver)`, `'this'-in-TypeScript` → `'this' in TypeScript`), quoted in YAML where needed.
114
+ - **Body H1s.** GitHub drew the name as the title and every `#` in the body below it, so a page's H1s are sections, not its title (`# TypeScript 5.3` on a page named "API Breaking Changes"). A leading H1 that repeats the page name (ignoring case and punctuation) is deleted. If the page still has an H1, every heading is demoted one level (`#` → `##`, `##` → `###`, …; `######` stays), and a one-line setext heading becomes ATX one level down (`===` → `##`, `---` → `###`). A setext heading over several lines has no ATX form: `convert` reports it, so rewrite it as one `###` line by hand. Heading ids come from text, so demoting changes none.
115
+ - **`sidebar.label`** is the sidebar's text for the page, when it differs from `title` by more than case and `-` versus space.
116
+
117
+ ### Wiki links
118
+
119
+ Every `[[…]]` outside code converts. GitHub leaves `[[ … ]]` in fenced code alone (bash tests), and so does `convert`: it skips fenced (in a quote too) and indented code, inline code spans, `<code>` and `<tt>` elements, and HTML comments. An indented block that holds `[[` gets a `REVIEW` line, since list continuation and code look alike there. A code span that runs across lines isn't seen as code, so check any `REVIEW` line that quotes shell syntax (`[[ -f x ]]`).
120
+
121
+ - **Inside an HTML block** (a line opening with `<div>`, `<p>`, `<table>`, `<details>`, and the like, up to the next blank line), Markdown shows as text, but gollum, the engine behind GitHub's wikis, resolves `[[…]]` before the Markdown is parsed, so it made a link there: `convert` writes `<a href="/page-name">text</a>` there, and a bare wiki URL too. An image tag there gets a `REVIEW` line: write it as `<img src="…" />` with the relative path the line gives (Blume publishes the file with the page), or move it out of the block. A `#` line inside the block is text, not a heading, so it isn't demoted.
122
+ - **`'[[Page]]`** (an apostrophe right before, none right after) is gollum's escape for literal brackets: `convert` leaves it and reports it. Check the live page, and drop the apostrophe if it showed the brackets.
123
+ - **`\[\[Page\]\]`** converts as a wiki link, because pandoc writes them that way (Other markup formats), with a `REVIEW` line: on a page that was always Markdown, GitHub showed those brackets, so restore them there.
124
+
125
+ | GitHub (`.md` pages) | Blume |
126
+ | --- | --- |
127
+ | `[[Page Name]]` | `[Page Name](/page-name)`: the text as written, the route from the table |
128
+ | `[[Link text\|Page Name]]` | `[Link text](/page-name)` |
129
+ | `[[Page Name#anchor]]`, `[[text\|Page Name#anchor]]` | `[…](/page-name#anchor)`, the anchor as written |
130
+ | `[[text\|https://…]]` | `[text](https://…)` |
131
+ | `[[Home]]` | `[Home](/)` |
132
+ | a page the wiki doesn't have (GitHub drew it as a link to create the page), or one set to `null` | plain text; reported |
133
+ | a moved notice | a link to its target |
134
+ | `[[image.png]]`, `[[/images/x.png\|alt=Text]]`, `[[x.png\|align=center,frame,alt=Text]]`, `[[https://…/x.png\|alt=Text]]` | an image: `![Text](…)` (Images and files). Any first part with an image extension is an image, whatever follows; options are separated by `,` or `\|`. A leading `/` is the wiki root. A path the root doesn't have but the page's own folder does resolves there, reported. Options other than `alt=` (`width=`, `align=`, `frame`, stray text) drop; reported |
135
+ | `[[text\|docs/spec.pdf]]` (a file stored in the wiki) | left as is and reported: copy the file into `public/` and link its root path |
136
+ | `[[_TOC_]]` | deleted: GitHub lists a table of contents among unsupported syntax, and Blume's outline shows the headings |
137
+
138
+ (In this table `\|` is the table's escape for a single pipe.) GitHub trims spaces around the pipe (`[[Mailing list | https://…]]`), and inside a Markdown table cell the pipe may be written `\|`. Pages are looked up the way GitHub did: by name, ignoring case, with `-` and space interchangeable and folders ignored. **Anchors are case-sensitive in Blume** and weren't on GitHub, wherever they appear: when `blume validate` reports one (`#TSDeclFiles` for a heading pinned `[#TsDeclFiles]`), match the id's case.
139
+
140
+ ### Markdown links
141
+
142
+ | Link | Blume |
143
+ | --- | --- |
144
+ | `https://github.com/<owner>/<repo>/wiki/Page-Name` (any case of owner and repo, `http`, `www.`), `/<owner>/<repo>/wiki/Page-Name`, or bare `Page-Name` / `./Page-Name` | the page's route through the table, keeping `#anchor`, percent-decoded (`%28` is `(`) |
145
+ | `…/wiki`, `…/wiki/`, `…/wiki/Home` | `/` |
146
+ | a mirror repository's copy of a page (`https://github.com/<owner>/<repo>-wiki/blob/<branch>/Page.md`) | the page's route (`--mirror`): the mirror goes stale with the wiki |
147
+ | a bare wiki URL in prose, or `<https://…/wiki/Page>` | `[Page title](/route)` |
148
+ | a page's file by its path (`dir/Page.md`, `…/wiki/dir/Page.md`), which GitHub served raw | the page's route; reported |
149
+ | `#user-content-x` | `#x` (GitHub's ids carry that prefix, so both forms worked there) |
150
+ | relative links that leave the wiki (`../issues/12`, `../blob/main/src/x.ts`, `../../other-repo`) | the absolute URL each resolved to from `https://github.com/<owner>/<repo>/wiki/<page>` (`../issues/12` → `https://github.com/<owner>/<repo>/issues/12`) |
151
+ | a link to a page that doesn't exist (`./formatter.md`, `GLOSSARY.md#x`: GitHub served nothing there) | left as is and reported: it was broken on GitHub too, so retarget it (often the page moved to another repository) or unlink it |
152
+ | `…/wiki/_pages`, `…/wiki/<Page>/_history`, revision URLs | left and reported: they point at the wiki's UI and stop working with it |
153
+ | another repository's wiki | unchanged |
154
+
155
+ `#123` and `@user` are plain text in wikis (GitHub doesn't autolink them there), so they stay. Bare URLs autolink in both. A raw HTML `<a href>` to a wiki page gets the route too (and a wiki link in an HTML block becomes one); Blume adds `deployment.base` to its root path like a Markdown link's.
156
+
157
+ ### Link definitions
158
+
159
+ `blume validate` checks every link reference definition the page uses, including ones nothing cites; when a label is defined twice it checks only the first, which CommonMark uses. Wikis kept by scripts collect definitions that never rendered:
160
+
161
+ - **A later definition of a label** never rendered: `convert` deletes it.
162
+ - **A Foam or VS Code block** of autogenerated definitions (`[//begin]: # "Autogenerated link references…"` … `[//end]: #`) goes too: once the wiki links are converted, nothing uses it.
163
+ - **A line like `[1]: <src/file.ts - function x(`** with no closing `>` isn't a definition at all: it shows as text, on GitHub and in Blume alike. Leave it, or delete it if it's leftover tooling syntax, and report it.
164
+
165
+ ### GitHub Markdown
166
+
167
+ | GitHub | Blume |
168
+ | --- | --- |
169
+ | `> [!NOTE]`, `[!TIP]`, `[!IMPORTANT]`, `[!WARNING]`, `[!CAUTION]` at the start of a line | `:::note`, `:::tip`, `:::note`, `:::warning`, `:::danger`, in `.mdx`. Blume renders an alert itself as the same callout in `.mdx`, so `convert` leaves one indented in a list, or with lazy lines, as written and makes the page `.mdx`. One with text after its marker, or no body, is reported: convert it by hand. In a `.md` page an alert renders as a quote with a literal `[!NOTE]` and warns `BLUME_MD_GITHUB_ALERT`: rename the page to `.mdx`. The older `> **Note**` form is a plain quote on GitHub today: leave it |
170
+ | ` ```mermaid ` | the same fence, in `.mdx` |
171
+ | ` ```math ` | a `$$` block, in `.mdx` |
172
+ | `$…$`, ``$`…`$``, `$$…$$` | by hand: `$$…$$` in `.mdx` (inline ones stay in their sentence; SKILL.md "Math") |
173
+ | ` ```geojson `, `topojson`, `stl` | a code block: maps and 3D models drop; reported |
174
+ | Fence languages | lowercased (`TypeScript` → `typescript`), which only tidies: Blume reads them in any case, as GitHub did. `posh` → `powershell`. Any other name Shiki lacks renders as plain text and warns `BLUME_UNKNOWN_CODE_LANGUAGE`: change it to a Shiki language, or `text` |
175
+ | `:emoji:` shortcodes | the character, with `--emoji`. GitHub slugged a heading's shortcode name, so a heading with one is reported: convert it and pin the old id (`## ⚠️ Known issues [#warning-known-issues]`) |
176
+ | `<a name="x"></a>` in a heading | deleted, with `[#x]` pinned on the heading (Blume would give the heading the id `x` anyway). Elsewhere it stays: links to `#x` work, and `blume validate` resolves them |
177
+ | task lists, tables, footnotes, strikethrough, autolinks, `<details>` | unchanged |
178
+ | `style=`, `<script>`, `<iframe>`, `<form>`, `<input>` | by hand: GitHub's sanitizer removed them, so readers never saw them, while Blume renders raw HTML as written. Delete them, or keep one deliberately and report it |
179
+
180
+ Heading ids need no pinning beyond the rows above: GitHub and Blume both slug with `github-slugger` (identical ids for all 710 headings on the wiki pages checked). The exception is a heading that ends in an emoji or symbol after a space: GitHub keeps the trailing dash (`## Features ✨` → `#features-`), Blume drops it (`#features`); `blume validate` reports the links to fix. Deleting a leading H1 can shift the `-1` suffix of a later heading with the same text; `blume validate` reports the links that break. `pin-heading-ids.mjs` can't read a wiki anyway: GitHub puts each id on a link beside the heading, not on the heading.
181
+
182
+ ### MDX hazards
183
+
184
+ MDX rejects things GitHub's Markdown accepts, and a page renamed to `.mdx` fails `blume build` on them. In prose (outside code), `convert` fixes the mechanical ones in the pages it renames: a `<` that doesn't start a tag (`TypeScript >=1.6, <7` → `&lt;7`), bare `{` and `}` (escaped `\{`), HTML comments (`{/* */}` on one line), unclosed void tags (`<br>` → `<br />`), `<https://…>` autolinks, and indented code (MDX has none, so a top-level indented block gets a fence; a backslash-escaped backtick doesn't start inline code, so braces after it get escaped too). It reports the rest:
185
+
186
+ - **A tag-like word** (`Array<string>` outside code, `<placeholder>`): MDX reads it as an unclosed JSX element. Put it in backticks.
187
+ - **Raw HTML blocks** (`<details>`, `<div>`, `<table>`) with Markdown inside: MDX parses that Markdown only with blank lines around it, and fails on tags interleaved with list items. Add the blank lines, or turn the block into Markdown.
188
+ - **Indented code inside a list:** fence it by hand, at the list item's indentation.
189
+
190
+ ### Other markup formats
191
+
192
+ Convert each non-Markdown page to Markdown before `convert` (which skips and lists them): `pandoc -f <format> -t gfm-raw_html --wrap=preserve page.<ext> -o page.md` (`mediawiki`, `textile`, `rst`, `org`, `creole`, `asciidoc`, `pod`). `.rdoc` has no pandoc reader: convert it by hand. Delete the original, point the page's entry in `wiki-files.json` at the new `.md` file, and rerun `convert`.
193
+
194
+ - **MediaWiki pages put the target first,** `[[Page Name|text]]`, as MediaWiki does. Pandoc turns them into `[text](Page_Name "text")`, with `_` for spaces, and `convert` maps those to routes.
195
+ - **In the other formats** pandoc escapes `[[…]]` to `\[\[…\]\]`, and `convert` reads those as Markdown wiki links (text first).
196
+ - Heading ids differ in these formats. Compare each converted page with the live one, and list every conversion in the report.
197
+
198
+ ## Frontmatter
199
+
200
+ GitHub reads no frontmatter, so wiki pages have none. Each page gets `title`, plus `sidebar.label` and `slug` where the sections above say so. If a page opens with a `---` block anyway, check the live page for what readers saw, keep that as content or drop it, and report it.
201
+
202
+ ## Images and files
203
+
204
+ - **Files stored in the wiki** (`https://raw.githubusercontent.com/wiki/<owner>/<repo>/<path>`, `https://github.com/<owner>/<repo>/wiki/<path>`, or a relative `<path>`, which resolves from the wiki root whatever the page's folder) go away with the wiki. `convert` moves each referenced one into `docs/` at the same path (`docs/images/x.png`) and points the page at it relatively (`../images/x.png` from a `(group)/` page): Blume optimizes and validates relative Markdown images.
205
+ - **A relative image that pointed at nothing** (it climbs out of the wiki root, like `../../screenshots/x.png`, or names a file the wiki doesn't have) was broken on GitHub too, and fails `blume build`: `convert` drops it and reports it.
206
+ - **Uploaded attachments** (`user-images.githubusercontent.com`, `github.com/user-attachments/assets/…`, `private-user-images.githubusercontent.com`) are served outside the wiki and keep working for a public repository. `convert` reports each one: download and vendor it next to the pages (one request at a time) unless the user objects. From a private or internal repository they need a GitHub login, so vendoring is required.
207
+ - **`https://github.com/<owner>/<repo>/blob/<ref>/<path>`** shown as an image is an HTML page anywhere but GitHub: `convert` uses `https://raw.githubusercontent.com/<owner>/<repo>/<ref>/<path>`.
208
+ - **Raw `<img>` alone on its line** becomes a Markdown image; its `width`/`height`/`align` drop and are reported. A raw `<img>` of a wiki file inside other HTML (a table cell, a `<p>`) is reported: its `src` still reads from the wiki root, which isn't the page's new folder. Copy the file into `docs/` at the same path, as `convert` does for the others, and point `src` at it relative to the page (`../images/x.png`), which Blume publishes with the page, unoptimized; or make it a Markdown image.
209
+ - **Linked files** (PDFs, archives, an image opened as a link) are reported: copy each into `docs/` at the same path and link it relative to the page, which publishes it with the page, or into `public/` and link its root path for a URL that never changes.
210
+ - `#gh-dark-mode-only` / `#gh-light-mode-only` pairs: keep the light image (report).
211
+ - Files nothing references stay in the wiki (other sites may hotlink them): report them, don't copy them.
212
+
213
+ ## URLs and the old wiki
214
+
215
+ Every old URL is on github.com. GitHub has no redirect setting for wiki pages, and it sanitizes their HTML (no `<script>`, no `<meta>` refresh), so nothing on the old wiki can forward a reader. Don't add Blume `redirects` for them either: they would only match paths on the new site. The route table is the record instead: each old page name maps to its new route, a target URL, or `null`.
216
+
217
+ **Check the table against the build** after `blume build`: `node <skill>/scripts/github-wiki-codemod.mjs check` confirms every route in the table exists in `dist/` (`dist/client/` on a server build), anchors included. Given the old URLs instead (every line of a saved `_pages` list, for one), it prints each one's new route: `ok`, `moved` for a moved notice's outside target, `DROPPED` for a page set to `null`, or `MISSING` for a page the table lacks.
218
+
219
+ **What the user can do on the old wiki** (prepare it, but never commit or push it, even with push access: `stubs` only writes files, and whether and when anything goes to `<repo>.wiki.git` is the user's decision, after the new site is live):
220
+
221
+ 1. **Restrict editing** (Settings → Features, under Wikis: **Restrict editing to collaborators only**) as soon as the migration starts, so the content stops drifting.
222
+ 2. **Stop anything that syncs the wiki, before pushing anything to it.** Otherwise it reverts the stubs or copies them on. Look for a workflow triggered `on: gollum` (wiki edits) in the main repository, a mirror repository (`<repo>-wiki`) whose workflow pushes to `<repo>.wiki.git` on push or on a schedule, a copy of that workflow inside the wiki itself, and wiki-publishing actions. List every one you find.
223
+ 3. **Point readers at the new site:** a notice at the top of `Home` and `_Sidebar.md` (the sidebar shows on every page). In a fresh clone of the wiki (not the Blume project: `stubs` refuses to run beside `blume.config.ts`), with `wiki-routes.json` and `wiki-files.json` copied in (don't commit them there): `node <skill>/scripts/github-wiki-codemod.mjs stubs --site https://docs.example.com --notice-only --write`.
224
+ 4. **Or replace every page with a link stub,** so deep links from issues, search results, and bookmarks land one click from the new page: the same command without `--notice-only`. Each page keeps its file name, so its URL keeps working; `_Footer` files go, `_Sidebar.md` becomes the notice, images stay for sites that hotlink them, and pages set to `null` are listed for the user to decide. `--site` is the new site's URL with its base path, so regenerate the stubs once the real URL is known.
225
+ 5. **Disable the wiki** (Settings → Features → Wikis), once the repository's own links point at the new site. GitHub hides every page, stubs included, without erasing them. It's the user's call; it's never required.
226
+
227
+ Whether search engines matter: `curl -sI https://github.com/<owner>/<repo>/wiki | grep -i x-robots-tag` prints `none` for a wiki GitHub keeps out of search results (GitHub indexes only wikis with 500+ stars and editing restricted to collaborators). Without it, search results keep sending readers to the old pages for a while, and the stubs are what they find.
228
+
229
+ **Inbound links in the main repository.** Search its clone with `git grep -n -i -E 'github\.com/<owner>/<repo>/wiki'`: README, CONTRIBUTING, `docs/`, `.github/ISSUE_TEMPLATE/`, `SECURITY.md`, code comments and test fixtures, `package.json` `homepage`. A large repository doesn't need a full clone: `git clone --depth 1 --filter=blob:none --sparse <url>`, then `git sparse-checkout set --no-cone '/*' '!/tests/baselines/'` (excluding its biggest generated folders). Map each URL through the table with `check`, which also verifies its anchor against the build and suggests ids for one that's gone:
230
+
231
+ ```bash
232
+ node <skill>/scripts/github-wiki-codemod.mjs check https://github.com/<owner>/<repo>/wiki/FAQ#common-bugs
233
+ # MISSING ANCHOR …/wiki/FAQ#common-bugs → /faq#common-bugs (ids like: common-bugs-that-arent-bugs)
234
+ ```
235
+
236
+ An old link can carry an anchor that was already dead on GitHub: fix it to a real id, don't copy it. Rewrite each link with the new site's origin (or ask first, when the user only wants a report), and set the repository's About → Website to the new site if it pointed at the wiki. Links in issues, pull requests, and other repositories can't be rewritten: the stubs catch them. List the files you changed or would change.
237
+
238
+ ### Leftover check
239
+
240
+ Rerun until it prints only code samples you mean to keep:
241
+
242
+ ```bash
243
+ grep -rnE '\[\[|user-content-|(^|\s):[a-z0-9_+][a-z0-9_+-]*:(\s|$)' docs
244
+ grep -rniE 'raw\.githubusercontent\.com/wiki/|github\.com/<owner>/<repo>/wiki' docs # any case of owner and repo
245
+ ```
246
+
247
+ An alert left in a `.md` page warns `BLUME_MD_GITHUB_ALERT`, and an unknown fence language `BLUME_UNKNOWN_CODE_LANGUAGE`, so `blume build` lists those.
248
+
249
+ ## Teardown
250
+
251
+ - **Files left behind.** Converting in place leaves the pages you didn't keep where they were: moved notices, `null` pages, the files GitHub never served, and unreferenced images. `convert` lists them. Delete them from the project (nothing under the root publishes, but they confuse later edits): the wiki keeps them, and `wiki-files.json` records their paths for the stubs. Delete `_Sidebar.md` and `_Footer.md` once `convert` has harvested them (a later `convert` then leaves the `meta.ts` files as they are).
252
+ - **Wiki tooling** (a `package.json` for a Markdown generator, link-rewriting scripts, sync scripts and workflows) stays only if it still applies to the pages: run each script in its dry or check mode against `docs/` and see whether any page still uses the syntax it converts. If none does, it goes, with the dependencies only it used. Say which you kept.
253
+ - **Lockfiles:** a mirror repository's `.gitignore` often ignores `package-lock.json` or `yarn.lock`. Remove those lines, so the lockfile SKILL.md step 6 regenerates gets committed.
254
+ - Keep `wiki-routes.json` and `wiki-files.json` beside `blume.config.ts` until the stubs are pushed and the inbound links are rewritten.
255
+ - Scaffold `package.json` (SKILL.md step 6) if the docs' new home has none.
256
+
257
+ ## Dropped — report these
258
+
259
+ The Pages list and the clone box; footer text other than a copyright line, and its link; sidebar prose, images, anchor links, and duplicate entries; external sidebar links moved to `navigation.featured`; the per-page "edited … · N revisions" line; image sizes and wiki image options; images that were broken on GitHub; maps and 3D models; theme-aware image pairs; raw HTML GitHub had stripped; links to missing pages (GitHub's create-page links) and links that were already broken; shadowed link definitions; pages left out (mirror files, tests, fixtures, deprecated stubs) and moved notices mapped to their targets; pages GitHub never served (same-name shadows); non-Markdown pages converted with pandoc; wiki-UI links (history, revisions, `_pages`); and every old URL, which stays on github.com: say what you prepared for it (notice, stubs, or neither), which sync workflows must stop first, and that nothing redirects.