@shoalrun/cli 0.1.0-alpha.11 → 0.1.0-alpha.13

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 (602) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +59 -17
  3. package/agent-docs/.well-known/shoalrun/navigation/examples-tab-50f26fe22670.txt +22 -0
  4. package/agent-docs/.well-known/shoalrun/navigation/project-updates-50f26fe22670.txt +33 -3
  5. package/agent-docs/.well-known/shoalrun/navigation/reference-tab-50f26fe22670.txt +22 -0
  6. package/agent-docs/SNAPSHOT.json +5 -5
  7. package/agent-docs/api-docs/focused-contracts.md +2 -2
  8. package/agent-docs/api-docs/generate.md +6 -6
  9. package/agent-docs/api-docs/generate.sections/add-useful-examples.md +2 -2
  10. package/agent-docs/api-docs/generate.sections/choose-generated-references-or-mdx.md +5 -5
  11. package/agent-docs/api-docs/generate.sections/describe-filters-that-do-not-use-their-openapi-parameter-name.md +2 -2
  12. package/agent-docs/api-docs/generate.sections/generate-many-pages-selectively.md +2 -2
  13. package/agent-docs/api-docs/generate.sections/link-guides-to-api-references.md +2 -2
  14. package/agent-docs/api-docs/generate.sections/map-extension-values-to-shoalrun-status.md +2 -2
  15. package/agent-docs/api-docs/generate.sections/override-generated-reference-order-in-navigation.md +3 -3
  16. package/agent-docs/api-docs/generate.sections/related-reading.md +2 -2
  17. package/agent-docs/api-docs/generate.sections/use-a-template-for-editorial-context.md +2 -2
  18. package/agent-docs/api-docs/support.md +14 -13
  19. package/agent-docs/author/add-page.md +8 -6
  20. package/agent-docs/author/agent-overview.md +1 -1
  21. package/agent-docs/author/agent-workflow.md +1 -1
  22. package/agent-docs/author/analytics.md +1 -1
  23. package/agent-docs/author/analytics.sections/adapter-exports-and-consent.md +1 -1
  24. package/agent-docs/author/analytics.sections/built-in-events.md +1 -1
  25. package/agent-docs/author/analytics.sections/configure-an-adapter.md +1 -1
  26. package/agent-docs/author/analytics.sections/custom-interactions.md +1 -1
  27. package/agent-docs/author/analytics.sections/event-format.md +1 -1
  28. package/agent-docs/author/analytics.sections/html-markdown-and-agents.md +1 -1
  29. package/agent-docs/author/analytics.sections/inspect-events-locally.md +1 -1
  30. package/agent-docs/author/analytics.sections/map-events-to-your-service.md +1 -1
  31. package/agent-docs/author/analytics.sections/posthog-example.md +1 -1
  32. package/agent-docs/author/analytics.sections/related-reading.md +1 -1
  33. package/agent-docs/author/authoring-agent.md +1 -1
  34. package/agent-docs/author/coding-agent.md +1 -1
  35. package/agent-docs/author/components/action-link.md +9 -3
  36. package/agent-docs/author/components/api-embeds.md +11 -3
  37. package/agent-docs/author/components/api-embeds.sections/apiexample.md +7 -3
  38. package/agent-docs/author/components/api-embeds.sections/html-markdown-and-agents.md +3 -3
  39. package/agent-docs/author/components/api-embeds.sections/related-reading.md +3 -3
  40. package/agent-docs/author/components/badge.md +8 -4
  41. package/agent-docs/author/components/breadcrumbs.md +1 -1
  42. package/agent-docs/author/components/callout.md +8 -4
  43. package/agent-docs/author/components/cards.md +2 -2
  44. package/agent-docs/author/components/cards.sections/add-a-lucide-icon.md +2 -2
  45. package/agent-docs/author/components/cards.sections/build-a-collection.md +2 -2
  46. package/agent-docs/author/components/cards.sections/curate-a-reading-list.md +2 -2
  47. package/agent-docs/author/components/cards.sections/html-markdown-and-agents.md +2 -2
  48. package/agent-docs/author/components/cards.sections/link-to-one-document.md +2 -2
  49. package/agent-docs/author/components/cards.sections/override-card-text.md +2 -2
  50. package/agent-docs/author/components/cards.sections/related-reading.md +2 -2
  51. package/agent-docs/author/components/code-blocks.md +8 -4
  52. package/agent-docs/author/components/collapsible.md +7 -3
  53. package/agent-docs/author/components/cookbook-chooser.md +7 -3
  54. package/agent-docs/author/components/custom/react.md +1 -1
  55. package/agent-docs/author/components/custom/react.sections/html-markdown-and-agents.md +1 -1
  56. package/agent-docs/author/components/custom/react.sections/interactive-react-example.md +1 -1
  57. package/agent-docs/author/components/custom/react.sections/registration.md +1 -1
  58. package/agent-docs/author/components/custom/react.sections/related-reading.md +1 -1
  59. package/agent-docs/author/components/custom/react.sections/share-the-model-with-the-browser.md +1 -1
  60. package/agent-docs/author/components/custom/react.sections/where-the-content-lives.md +1 -1
  61. package/agent-docs/author/components/custom.md +1 -1
  62. package/agent-docs/author/components/definition-list.md +5 -3
  63. package/agent-docs/author/components/diagrams/d2-gallery.md +2 -2
  64. package/agent-docs/author/components/diagrams/d2-gallery.sections/basic-shapes.md +2 -2
  65. package/agent-docs/author/components/diagrams/d2-gallery.sections/flows-and-structures.md +2 -2
  66. package/agent-docs/author/components/diagrams/d2-gallery.sections/html-markdown-and-agents.md +2 -2
  67. package/agent-docs/author/components/diagrams/d2-gallery.sections/layout-engines.md +2 -2
  68. package/agent-docs/author/components/diagrams/d2-gallery.sections/related-reading.md +2 -2
  69. package/agent-docs/author/components/diagrams/d2-gallery.sections/sequences-and-grids.md +2 -2
  70. package/agent-docs/author/components/diagrams/d2-gallery.sections/styles-and-code.md +2 -2
  71. package/agent-docs/author/components/diagrams/pele-gallery.md +2 -2
  72. package/agent-docs/author/components/diagrams/pele-gallery.sections/charts-and-planning.md +2 -2
  73. package/agent-docs/author/components/diagrams/pele-gallery.sections/flowchart-shapes.md +2 -2
  74. package/agent-docs/author/components/diagrams/pele-gallery.sections/flows-and-behavior.md +2 -2
  75. package/agent-docs/author/components/diagrams/pele-gallery.sections/html-markdown-and-agents.md +2 -2
  76. package/agent-docs/author/components/diagrams/pele-gallery.sections/related-reading.md +2 -2
  77. package/agent-docs/author/components/diagrams/pele-gallery.sections/specialized-diagrams.md +2 -2
  78. package/agent-docs/author/components/diagrams/pele-gallery.sections/systems-and-structures.md +2 -2
  79. package/agent-docs/author/components/diagrams.md +13 -5
  80. package/agent-docs/author/components/doc-link.md +7 -3
  81. package/agent-docs/author/components/fields.md +6 -4
  82. package/agent-docs/author/components/file-tree.md +7 -3
  83. package/agent-docs/author/components/images.md +5 -3
  84. package/agent-docs/author/components/info-hint.md +7 -3
  85. package/agent-docs/author/components/kbd.md +5 -3
  86. package/agent-docs/author/components/math.md +7 -3
  87. package/agent-docs/author/components/only.md +9 -5
  88. package/agent-docs/author/components/progress.md +7 -3
  89. package/agent-docs/author/components/related-reading.md +7 -3
  90. package/agent-docs/author/components/steps.md +9 -3
  91. package/agent-docs/author/components/tables.md +7 -3
  92. package/agent-docs/author/components/tabs.md +7 -3
  93. package/agent-docs/author/components/timeline.md +10 -4
  94. package/agent-docs/author/components/tooltips.md +7 -3
  95. package/agent-docs/author/components/walkthroughs.md +5 -3
  96. package/agent-docs/author/components/walkthroughs.sections/attributes.md +3 -3
  97. package/agent-docs/author/components/walkthroughs.sections/build-up-code.md +3 -3
  98. package/agent-docs/author/components/walkthroughs.sections/choose-a-layout.md +3 -3
  99. package/agent-docs/author/components/walkthroughs.sections/explain-one-file.md +3 -3
  100. package/agent-docs/author/components/walkthroughs.sections/html-markdown-and-agents.md +3 -3
  101. package/agent-docs/author/components/walkthroughs.sections/related-reading.md +3 -3
  102. package/agent-docs/author/components/walkthroughs.sections/syntax.md +51 -0
  103. package/agent-docs/author/components/walkthroughs.sections/take-code-from-a-file.md +3 -22
  104. package/agent-docs/author/components.md +19 -4
  105. package/agent-docs/author/css.md +13 -10
  106. package/agent-docs/author/css.sections/borders-fields-and-navigation-tokens.md +3 -3
  107. package/agent-docs/author/css.sections/core-color-tokens.md +3 -3
  108. package/agent-docs/author/css.sections/html-markdown-and-agents.md +54 -0
  109. package/agent-docs/author/css.sections/page-attributes.md +9 -9
  110. package/agent-docs/author/css.sections/page-transitions.md +3 -3
  111. package/agent-docs/author/css.sections/responsive-behavior.md +3 -3
  112. package/agent-docs/author/css.sections/status-code-and-callout-tokens.md +3 -3
  113. package/agent-docs/author/css.sections/typography-shape-and-width-tokens.md +3 -3
  114. package/agent-docs/author/custom-metadata.md +2 -2
  115. package/agent-docs/author/custom-metadata.sections/add-a-custom-page-field.md +2 -2
  116. package/agent-docs/author/custom-metadata.sections/choose-text-for-each-surface.md +2 -2
  117. package/agent-docs/author/custom-metadata.sections/preview-changes-to-metadata-providers.md +2 -2
  118. package/agent-docs/author/custom-metadata.sections/read-metadata-from-a-project-module.md +2 -2
  119. package/agent-docs/author/custom-metadata.sections/related-reading.md +2 -2
  120. package/agent-docs/author/design-templates.md +1 -1
  121. package/agent-docs/author/design-writing-guides.md +1 -1
  122. package/agent-docs/author/frontmatter-reference.md +34 -27
  123. package/agent-docs/author/frontmatter-reference.sections/choose-a-publication-and-visibility.md +5 -3
  124. package/agent-docs/author/frontmatter-reference.sections/control-output-details.md +5 -3
  125. package/agent-docs/author/frontmatter-reference.sections/global-defaults-and-enforced-values.md +5 -3
  126. package/agent-docs/author/frontmatter-reference.sections/html-markdown-and-agents.md +33 -0
  127. package/agent-docs/author/frontmatter-reference.sections/identify-and-route-a-page.md +5 -3
  128. package/agent-docs/author/frontmatter-reference.sections/point-agents-to-it.md +5 -3
  129. package/agent-docs/author/frontmatter-reference.sections/project-defined-fields.md +5 -3
  130. package/agent-docs/author/frontmatter-reference.sections/related-reading.md +5 -3
  131. package/agent-docs/author/frontmatter-reference.sections/use-the-local-files-while-authoring.md +5 -3
  132. package/agent-docs/author/frontmatter-reference.sections/when-the-files-update.md +5 -3
  133. package/agent-docs/author/frontmatter.md +7 -4
  134. package/agent-docs/author/frontmatter.sections/audience-badges.md +3 -3
  135. package/agent-docs/author/frontmatter.sections/give-deprecation-guidance.md +3 -3
  136. package/agent-docs/author/frontmatter.sections/global-defaults-and-enforced-values.md +3 -3
  137. package/agent-docs/author/frontmatter.sections/optional-native-fields.md +7 -4
  138. package/agent-docs/author/frontmatter.sections/publication-fields.md +3 -3
  139. package/agent-docs/author/frontmatter.sections/publishing-policy.md +3 -3
  140. package/agent-docs/author/frontmatter.sections/related-metadata-tasks.md +3 -3
  141. package/agent-docs/author/frontmatter.sections/related-reading.md +3 -3
  142. package/agent-docs/author/frontmatter.sections/required-after-inheritance.md +3 -3
  143. package/agent-docs/author/frontmatter.sections/seo-fields.md +3 -3
  144. package/agent-docs/author/frontmatter.sections/validation-rules.md +3 -3
  145. package/agent-docs/author/markdown-metadata.md +17 -9
  146. package/agent-docs/author/navigation.md +13 -2
  147. package/agent-docs/author/navigation.sections/arrange-authored-pages.md +2 -2
  148. package/agent-docs/author/navigation.sections/fill-an-api-tag-group.md +2 -2
  149. package/agent-docs/author/navigation.sections/hide-group-page-counts.md +13 -2
  150. package/agent-docs/author/navigation.sections/html-markdown-and-agents.md +2 -2
  151. package/agent-docs/author/navigation.sections/local-api-preview.md +2 -2
  152. package/agent-docs/author/navigation.sections/mix-guides-and-api-references.md +2 -2
  153. package/agent-docs/author/navigation.sections/optionally-split-a-large-menu-into-files.md +2 -2
  154. package/agent-docs/author/navigation.sections/reach-navigation-while-reading.md +2 -2
  155. package/agent-docs/author/navigation.sections/related-reading.md +2 -2
  156. package/agent-docs/author/navigation.sections/split-navigation-into-tabs.md +2 -2
  157. package/agent-docs/author/navigation.sections/subject-lifecycle-labels.md +2 -2
  158. package/agent-docs/author/recipe-drafts.md +16 -16
  159. package/agent-docs/author/recipe-drafts.sections/1-write-one-reusable-step.md +5 -5
  160. package/agent-docs/author/recipe-drafts.sections/2-list-the-steps-for-one-complete-goal.md +3 -3
  161. package/agent-docs/author/recipe-drafts.sections/3-generate-a-draft-and-review-it.md +4 -4
  162. package/agent-docs/author/recipe-drafts.sections/4-maintain-shoalrun-s-published-recipe-snapshots.md +3 -3
  163. package/agent-docs/author/recipe-drafts.sections/related-reading.md +1 -1
  164. package/agent-docs/author/recipe-drafts.sections/start-an-ingredient-from-an-api-example.md +5 -5
  165. package/agent-docs/author/recipes.md +4 -4
  166. package/agent-docs/author/recipes.sections/how-agents-use-this.md +1 -1
  167. package/agent-docs/author/recipes.sections/one-example-from-shoalrun.md +4 -4
  168. package/agent-docs/author/recipes.sections/related-reading.md +1 -1
  169. package/agent-docs/author/recipes.sections/try-a-complete-workflow.md +1 -1
  170. package/agent-docs/author/recipes.sections/use-recipes-for-task-oriented-how-tos.md +1 -1
  171. package/agent-docs/author/recipes.sections/what-changing-an-ingredient-updates.md +1 -1
  172. package/agent-docs/author/recipes.sections/what-each-file-does.md +1 -1
  173. package/agent-docs/author/recipes.sections/what-happens-and-when.md +1 -1
  174. package/agent-docs/author/related-reading.md +5 -1
  175. package/agent-docs/author/related-reading.sections/automatic-footers.md +1 -1
  176. package/agent-docs/author/related-reading.sections/declare-relationships.md +1 -1
  177. package/agent-docs/author/related-reading.sections/display-related-documents.md +1 -1
  178. package/agent-docs/author/related-reading.sections/html-markdown-and-agents.md +28 -0
  179. package/agent-docs/author/related-reading.sections/inspect-output-and-changes.md +1 -1
  180. package/agent-docs/author/related-reading.sections/reading-paths-in-section-markdown.md +1 -1
  181. package/agent-docs/author/related-reading.sections/related-reading.md +1 -1
  182. package/agent-docs/author/related-reading.sections/where-relationship-explanations-come-from.md +1 -1
  183. package/agent-docs/author/search.md +1 -1
  184. package/agent-docs/author/search.sections/choose-a-provider.md +1 -1
  185. package/agent-docs/author/search.sections/cli-and-mcp-ranking-details.md +1 -1
  186. package/agent-docs/author/search.sections/hosted-semantic-search-limits.md +1 -1
  187. package/agent-docs/author/search.sections/html-markdown-and-agents.md +1 -1
  188. package/agent-docs/author/search.sections/lexical-search.md +1 -1
  189. package/agent-docs/author/search.sections/local-semantic-preview.md +1 -1
  190. package/agent-docs/author/search.sections/read-a-search-result-with-an-agent.md +1 -1
  191. package/agent-docs/author/search.sections/related-reading.md +1 -1
  192. package/agent-docs/author/search.sections/reuse-the-lexical-index.md +1 -1
  193. package/agent-docs/author/search.sections/turn-search-off.md +1 -1
  194. package/agent-docs/author/search.sections/use-the-search-box.md +1 -1
  195. package/agent-docs/author/search.sections/write-a-custom-provider.md +1 -1
  196. package/agent-docs/author/snippets.md +5 -1
  197. package/agent-docs/author/taxonomy.md +1 -1
  198. package/agent-docs/author/templates.md +16 -8
  199. package/agent-docs/author/templates.sections/built-in-feature-templates.md +3 -3
  200. package/agent-docs/author/templates.sections/built-in-templates.md +3 -3
  201. package/agent-docs/author/templates.sections/choose-formatting-enforcement-for-your-project.md +3 -3
  202. package/agent-docs/author/templates.sections/create-or-edit-a-project-template.md +12 -4
  203. package/agent-docs/author/templates.sections/keep-the-four-shapes-distinct.md +3 -3
  204. package/agent-docs/author/templates.sections/project-added-templates.md +3 -3
  205. package/agent-docs/author/templates.sections/related-reading.md +3 -3
  206. package/agent-docs/author/templates.sections/scaffold-a-page.md +7 -7
  207. package/agent-docs/author/templates.sections/short-cli-and-derived-values.md +3 -3
  208. package/agent-docs/author/templates.sections/what-every-template-gives-you.md +3 -3
  209. package/agent-docs/author/workspace/commands.md +2 -2
  210. package/agent-docs/author/workspace.md +8 -2
  211. package/agent-docs/author/writing-style.md +27 -5
  212. package/agent-docs/author/writing-style.sections/adapt-this-example.md +3 -3
  213. package/agent-docs/author/writing-style.sections/choose-components-by-reader-need.md +6 -4
  214. package/agent-docs/author/writing-style.sections/evidence-and-uncertainty.md +3 -3
  215. package/agent-docs/author/writing-style.sections/example-audience-and-scope.md +3 -3
  216. package/agent-docs/author/writing-style.sections/feature-availability.md +36 -0
  217. package/agent-docs/author/writing-style.sections/ingredients-recipes-and-cookbooks.md +3 -3
  218. package/agent-docs/author/writing-style.sections/page-purpose-and-structure.md +12 -4
  219. package/agent-docs/author/writing-style.sections/procedures-and-examples.md +3 -3
  220. package/agent-docs/author/writing-style.sections/related-reading.md +3 -3
  221. package/agent-docs/author/writing-style.sections/review-a-draft.md +3 -3
  222. package/agent-docs/author/writing-style.sections/reviewed-writing-examples.md +3 -3
  223. package/agent-docs/author/writing-style.sections/voice-and-explanations.md +3 -3
  224. package/agent-docs/changelog/2026-10-14-agent-readiness.md +2 -2
  225. package/agent-docs/changelog/2026-10-15-evaluation-builds.md +2 -2
  226. package/agent-docs/changelog/2026-10-16-navigation-layouts.md +2 -2
  227. package/agent-docs/changelog/2026-10-17-mcp-hosting.md +2 -2
  228. package/agent-docs/changelog/2026-10-18-search-and-tooltips.md +2 -2
  229. package/agent-docs/changelog/2026-10-19-visual-navigation.md +2 -2
  230. package/agent-docs/changelog/2026-10-25.md +2 -2
  231. package/agent-docs/changelog/2026-10-26.md +2 -2
  232. package/agent-docs/changelog/2026-10-27.md +2 -2
  233. package/agent-docs/changelog/2026-10-28.md +89 -0
  234. package/agent-docs/changelog/authoring-preview.md +2 -2
  235. package/agent-docs/changelog/authoring-search.md +3 -3
  236. package/agent-docs/changelog/authoring-tools.md +2 -2
  237. package/agent-docs/changelog/working-tree-skill.md +2 -2
  238. package/agent-docs/changelog.md +11 -3
  239. package/agent-docs/chat/checks.md +1 -1
  240. package/agent-docs/chat/checks.sections/answer-ahead-of-time.md +1 -1
  241. package/agent-docs/chat/checks.sections/answer-for-one-audience.md +1 -1
  242. package/agent-docs/chat/checks.sections/answer-without-a-model.md +1 -1
  243. package/agent-docs/chat/checks.sections/follow-up-questions.md +1 -1
  244. package/agent-docs/chat/checks.sections/how-an-answer-is-checked.md +1 -1
  245. package/agent-docs/chat/checks.sections/questions-with-several-parts.md +1 -1
  246. package/agent-docs/chat/checks.sections/related-reading.md +1 -1
  247. package/agent-docs/chat/checks.sections/reuse-answers.md +1 -1
  248. package/agent-docs/chat/host.md +1 -1
  249. package/agent-docs/chat/host.sections/cap-the-daily-spend.md +1 -1
  250. package/agent-docs/chat/host.sections/keep-a-record-of-questions.md +1 -1
  251. package/agent-docs/chat/host.sections/related-reading.md +1 -1
  252. package/agent-docs/chat/passage-index.md +2 -2
  253. package/agent-docs/chat/passage-index.sections/build-the-index.md +2 -2
  254. package/agent-docs/chat/passage-index.sections/embed-passages.md +2 -2
  255. package/agent-docs/chat/passage-index.sections/how-the-default-chunker-splits-a-page.md +2 -2
  256. package/agent-docs/chat/passage-index.sections/limits.md +2 -2
  257. package/agent-docs/chat/passage-index.sections/passage-ids-and-links.md +2 -2
  258. package/agent-docs/chat/passage-index.sections/related-reading.md +2 -2
  259. package/agent-docs/chat/passage-index.sections/what-a-passage-contains.md +2 -2
  260. package/agent-docs/chat/prepared-answers.md +1 -1
  261. package/agent-docs/chat/settings.md +2 -2
  262. package/agent-docs/chat/settings.sections/choose-the-scorer.md +2 -2
  263. package/agent-docs/chat/settings.sections/configure-chat.md +2 -2
  264. package/agent-docs/chat/settings.sections/configure-retrieval.md +2 -2
  265. package/agent-docs/chat/settings.sections/related-reading.md +2 -2
  266. package/agent-docs/chat/settings.sections/send-extra-request-fields.md +2 -2
  267. package/agent-docs/chat/settings.sections/use-more-than-one-model.md +2 -2
  268. package/agent-docs/chat/settings.sections/when-the-scorer-is-down.md +2 -2
  269. package/agent-docs/chat/setup.md +1 -1
  270. package/agent-docs/chat.md +1 -1
  271. package/agent-docs/cli.md +2 -2
  272. package/agent-docs/cli.sections/audit-a-project-and-api-sources.md +2 -2
  273. package/agent-docs/cli.sections/author-pages.md +2 -2
  274. package/agent-docs/cli.sections/build-and-serve.md +2 -2
  275. package/agent-docs/cli.sections/inspect-config-and-operations.md +2 -2
  276. package/agent-docs/cli.sections/npm-scripts-in-this-repository.md +2 -2
  277. package/agent-docs/cli.sections/related-reading.md +2 -2
  278. package/agent-docs/cli.sections/search-and-chat.md +2 -2
  279. package/agent-docs/cli.sections/start-a-project.md +2 -2
  280. package/agent-docs/cli.sections/working-tree-authoring-tools.md +2 -2
  281. package/agent-docs/config/apis.md +2 -2
  282. package/agent-docs/config/content.md +2 -2
  283. package/agent-docs/config/content.sections/authoring.md +2 -2
  284. package/agent-docs/config/content.sections/badges.md +2 -2
  285. package/agent-docs/config/content.sections/callouts.md +2 -2
  286. package/agent-docs/config/content.sections/cookbooks.md +2 -2
  287. package/agent-docs/config/content.sections/frontmatter.md +2 -2
  288. package/agent-docs/config/content.sections/readerfilters.md +2 -2
  289. package/agent-docs/config/content.sections/related-reading.md +2 -2
  290. package/agent-docs/config/content.sections/relationships.md +2 -2
  291. package/agent-docs/config/content.sections/snippets.md +2 -2
  292. package/agent-docs/config/content.sections/templates.md +2 -2
  293. package/agent-docs/config/content.sections/variables.md +2 -2
  294. package/agent-docs/config/examples.md +22 -14
  295. package/agent-docs/config/examples.sections/feature-showcase.md +21 -13
  296. package/agent-docs/config/examples.sections/minimal-site.md +15 -10
  297. package/agent-docs/config/examples.sections/multilingual-help-center.md +15 -10
  298. package/agent-docs/config/examples.sections/parcel-api-documentation.md +15 -10
  299. package/agent-docs/config/examples.sections/related-reading.md +15 -10
  300. package/agent-docs/config/examples.sections/shared-company-preset.md +16 -11
  301. package/agent-docs/config/integrations.md +2 -2
  302. package/agent-docs/config/outputs.md +17 -13
  303. package/agent-docs/config/outputs.sections/exports.md +16 -13
  304. package/agent-docs/config/outputs.sections/metadata.md +3 -3
  305. package/agent-docs/config/outputs.sections/related-reading.md +3 -3
  306. package/agent-docs/config/publishing.md +2 -2
  307. package/agent-docs/config/publishing.sections/navigation.md +2 -2
  308. package/agent-docs/config/publishing.sections/redirects.md +2 -2
  309. package/agent-docs/config/publishing.sections/related-reading.md +2 -2
  310. package/agent-docs/config/site.md +2 -2
  311. package/agent-docs/config/site.sections/related-reading.md +2 -2
  312. package/agent-docs/config/site.sections/theme.md +2 -2
  313. package/agent-docs/config.md +2 -2
  314. package/agent-docs/configuration.md +22 -20
  315. package/agent-docs/configuration.sections/build-several-sites-from-shared-content.md +2 -2
  316. package/agent-docs/configuration.sections/change-inherited-lists-deliberately.md +5 -5
  317. package/agent-docs/configuration.sections/extract-a-section-with-include.md +4 -4
  318. package/agent-docs/configuration.sections/inspect-and-validate-changes.md +6 -6
  319. package/agent-docs/configuration.sections/related-reading.md +2 -2
  320. package/agent-docs/configuration.sections/separate-config-inheritance-from-page-metadata.md +2 -2
  321. package/agent-docs/configuration.sections/share-defaults-through-extends.md +7 -7
  322. package/agent-docs/configuration.sections/start-with-one-working-file.md +8 -6
  323. package/agent-docs/configuration.sections/understand-the-three-stages.md +2 -2
  324. package/agent-docs/configuration.sections/understand-where-paths-point.md +2 -2
  325. package/agent-docs/connect/mcp.md +1 -1
  326. package/agent-docs/content-delivery/agents.md +86 -0
  327. package/agent-docs/content-delivery/api.md +130 -0
  328. package/agent-docs/content-delivery/client.md +79 -0
  329. package/agent-docs/content-delivery/http-api/operations/get-content-representation-fa1d2e3f.md +394 -0
  330. package/agent-docs/content-delivery/http-api/operations/get-content-representation-head-4052375c.md +256 -0
  331. package/agent-docs/content-delivery/http-api/operations/get-content-resource-849f1c49.md +911 -0
  332. package/agent-docs/content-delivery/http-api/operations/get-content-resource-head-0a352acd.md +246 -0
  333. package/agent-docs/content-delivery/http-api/operations/list-content-resources-8e977ec7.md +913 -0
  334. package/agent-docs/content-delivery/http-api/operations/list-content-resources-head-b67cbad9.md +236 -0
  335. package/agent-docs/content-delivery/http-api.md +78 -0
  336. package/agent-docs/content-delivery/manifest.md +79 -0
  337. package/agent-docs/content-delivery/publish.md +72 -0
  338. package/agent-docs/content-delivery/render.md +73 -0
  339. package/agent-docs/content-delivery/rest.md +108 -0
  340. package/agent-docs/content-delivery/sections.md +110 -0
  341. package/agent-docs/content-delivery/select.md +100 -0
  342. package/agent-docs/content-delivery.md +79 -0
  343. package/agent-docs/contribute/agent-skills.md +1 -1
  344. package/agent-docs/cookbooks/authoring.md +7 -3
  345. package/agent-docs/cookbooks/site-workflows.md +8 -4
  346. package/agent-docs/cookbooks/welcome-email.md +7 -3
  347. package/agent-docs/documents.json +7707 -6283
  348. package/agent-docs/example-api/cedar/create-enrollment-7af3e5d2.md +2 -2
  349. package/agent-docs/example-api/cedar/get-legacy-parcel-6758d051.md +2 -2
  350. package/agent-docs/example-api/cedar/get-parcel-edee10e6.md +2 -2
  351. package/agent-docs/example-api/cedar/list-parcels-0345fb5f.md +2 -2
  352. package/agent-docs/example-api/cedar/parcel-delivered-7d1ba6db.md +2 -2
  353. package/agent-docs/example-api/cedar/upload-manifest-1af17891.md +2 -2
  354. package/agent-docs/example-api/cedar.md +2 -2
  355. package/agent-docs/example-api/create-note.md +2 -2
  356. package/agent-docs/example-api/generated/get-note-cbedb319.md +2 -2
  357. package/agent-docs/example-api/write-note.md +2 -2
  358. package/agent-docs/explore.md +2 -2
  359. package/agent-docs/glossary.md +3 -7
  360. package/agent-docs/ingredients/audit-site.md +2 -2
  361. package/agent-docs/ingredients/build-site.md +2 -2
  362. package/agent-docs/ingredients/compile-framework.md +2 -2
  363. package/agent-docs/ingredients/complete-page.md +2 -2
  364. package/agent-docs/ingredients/inspect-agent-bundle.md +2 -2
  365. package/agent-docs/ingredients/inspect-build.md +2 -2
  366. package/agent-docs/ingredients/preview-site.md +2 -2
  367. package/agent-docs/ingredients/refresh-agent-bundle.md +2 -2
  368. package/agent-docs/ingredients/scaffold-page.md +2 -2
  369. package/agent-docs/non-goals.md +2 -2
  370. package/agent-docs/provenance.json +4 -4
  371. package/agent-docs/publish/agent-discovery.md +1 -1
  372. package/agent-docs/publish/agent-friendliness.md +1 -1
  373. package/agent-docs/publish/agent-friendliness.sections/discover-a-page-with-a-small-context-budget.md +1 -1
  374. package/agent-docs/publish/agent-friendliness.sections/distinguish-examples-from-a-real-service.md +1 -1
  375. package/agent-docs/publish/agent-friendliness.sections/evidence-has-a-scope.md +1 -1
  376. package/agent-docs/publish/agent-friendliness.sections/keep-shared-instructions-complete.md +1 -1
  377. package/agent-docs/publish/agent-friendliness.sections/related-reading.md +1 -1
  378. package/agent-docs/publish/agent-friendliness.sections/secret-handshake.md +1 -1
  379. package/agent-docs/publish/agent-friendliness.sections/try-the-page-controls.md +1 -1
  380. package/agent-docs/publish/agent-friendliness.sections/use-the-transport-your-client-supports.md +1 -1
  381. package/agent-docs/publish/agent-friendliness.sections/verify-the-page-and-detect-changes.md +1 -1
  382. package/agent-docs/publish/agent-friendliness.sections/what-an-agent-reads.md +1 -1
  383. package/agent-docs/publish/agent-friendliness.sections/what-happens-to-an-unsupported-component.md +1 -1
  384. package/agent-docs/publish/api-references.md +10 -8
  385. package/agent-docs/publish/api-references.sections/author-the-reference-page.md +1 -1
  386. package/agent-docs/publish/api-references.sections/build-and-verify-the-output.md +1 -1
  387. package/agent-docs/publish/api-references.sections/inspect-the-machine-readable-contract.md +1 -1
  388. package/agent-docs/publish/api-references.sections/{generate-many-pages-selectively.md → next-steps.md} +28 -2
  389. package/agent-docs/publish/api-references.sections/preview-a-small-api-reference.md +1 -1
  390. package/agent-docs/publish/api-references.sections/put-the-spec-in-your-project.md +1 -1
  391. package/agent-docs/publish/api-references.sections/register-the-operation.md +1 -1
  392. package/agent-docs/publish/api-references.sections/related-reading.md +1 -1
  393. package/agent-docs/publish/api-references.sections/review-imported-content-before-publishing.md +1 -1
  394. package/agent-docs/publish/api-references.sections/what-readers-get.md +1 -1
  395. package/agent-docs/publish/audit.md +1 -1
  396. package/agent-docs/publish/audit.sections/add-project-tests.md +1 -1
  397. package/agent-docs/publish/audit.sections/check-agent-evaluation-reports.md +1 -1
  398. package/agent-docs/publish/audit.sections/check-before-pushing.md +1 -1
  399. package/agent-docs/publish/audit.sections/connect-a-documentation-test-suite.md +1 -1
  400. package/agent-docs/publish/audit.sections/read-the-report.md +1 -1
  401. package/agent-docs/publish/audit.sections/related-reading.md +1 -1
  402. package/agent-docs/publish/content-export.md +33 -17
  403. package/agent-docs/publish/content-export.sections/assets-downloads-and-notices.md +4 -4
  404. package/agent-docs/publish/content-export.sections/check-the-result.md +3 -3
  405. package/agent-docs/publish/content-export.sections/export-selected-pages.md +13 -10
  406. package/agent-docs/publish/content-export.sections/inspect-the-manifest.md +6 -4
  407. package/agent-docs/publish/content-export.sections/prerequisites.md +4 -4
  408. package/agent-docs/publish/content-export.sections/related-reading.md +3 -3
  409. package/agent-docs/publish/content-export.sections/review-exported-api-material.md +4 -4
  410. package/agent-docs/publish/content-export.sections/try-the-help-drawer-example.md +4 -4
  411. package/agent-docs/publish/content-export.sections/upgrade-an-existing-integration.md +34 -0
  412. package/agent-docs/publish/content-export.sections/what-the-representations-retain.md +5 -5
  413. package/agent-docs/publish/content-negotiation.md +2 -2
  414. package/agent-docs/publish/content-negotiation.sections/hosting-contract.md +2 -2
  415. package/agent-docs/publish/content-negotiation.sections/missing-pages.md +2 -2
  416. package/agent-docs/publish/content-negotiation.sections/related-reading.md +2 -2
  417. package/agent-docs/publish/content-negotiation.sections/validators-and-manifest-links.md +2 -2
  418. package/agent-docs/publish/content-negotiation.sections/verify-the-deployed-url.md +2 -2
  419. package/agent-docs/publish/content-negotiation.sections/what-if-the-host-cannot-negotiate.md +2 -2
  420. package/agent-docs/publish/content-negotiation.sections/what-shoalrun-builds-and-what-the-host-does.md +2 -2
  421. package/agent-docs/publish/content-negotiation.sections/why-this-matters.md +2 -2
  422. package/agent-docs/publish/deployment-check.md +2 -2
  423. package/agent-docs/publish/evals.md +1 -1
  424. package/agent-docs/publish/evals.sections/add-the-audit-hook.md +1 -1
  425. package/agent-docs/publish/evals.sections/check-the-report.md +1 -1
  426. package/agent-docs/publish/evals.sections/define-a-task-file.md +1 -1
  427. package/agent-docs/publish/evals.sections/diagnose-a-different-build.md +1 -1
  428. package/agent-docs/publish/evals.sections/prepare-a-report-for-the-built-site.md +1 -1
  429. package/agent-docs/publish/evals.sections/related-reading.md +1 -1
  430. package/agent-docs/publish/evals.sections/run-and-review-the-tasks.md +1 -1
  431. package/agent-docs/publish/evals.sections/shoalrun-s-own-example.md +1 -1
  432. package/agent-docs/publish/host-adapters.md +1 -1
  433. package/agent-docs/publish/manni.md +1 -1
  434. package/agent-docs/publish/manni.sections/check-the-result.md +1 -1
  435. package/agent-docs/publish/manni.sections/how-this-site-uses-manni.md +1 -1
  436. package/agent-docs/publish/manni.sections/optional-custom-policies.md +1 -1
  437. package/agent-docs/publish/manni.sections/prerequisites.md +1 -1
  438. package/agent-docs/publish/mcp-analytics.md +1 -1
  439. package/agent-docs/publish/mcp-analytics.sections/adapter-exports.md +1 -1
  440. package/agent-docs/publish/mcp-analytics.sections/enable-analytics.md +1 -1
  441. package/agent-docs/publish/mcp-analytics.sections/event-fields-and-service-mappings.md +1 -1
  442. package/agent-docs/publish/mcp-analytics.sections/optionally-collect-intent-and-group-calls.md +1 -1
  443. package/agent-docs/publish/mcp-analytics.sections/optionally-identify-the-client-and-model.md +1 -1
  444. package/agent-docs/publish/mcp-analytics.sections/related-reading.md +1 -1
  445. package/agent-docs/publish/mcp-analytics.sections/safeguards-and-limits.md +1 -1
  446. package/agent-docs/publish/mcp.md +12 -10
  447. package/agent-docs/publish/mcp.sections/check-budgets-and-errors.md +2 -2
  448. package/agent-docs/publish/mcp.sections/choose-the-documentation-source.md +7 -7
  449. package/agent-docs/publish/mcp.sections/configure-the-client.md +5 -5
  450. package/agent-docs/publish/mcp.sections/generate-a-vercel-endpoint.md +2 -2
  451. package/agent-docs/publish/mcp.sections/related-reading.md +4 -2
  452. package/agent-docs/publish/mcp.sections/search-then-read.md +2 -2
  453. package/agent-docs/publish/mcp.sections/serve-a-public-endpoint-on-node.md +2 -2
  454. package/agent-docs/publish/mcp.sections/verify-the-connection.md +2 -2
  455. package/agent-docs/publish/mcp.sections/why-offer-mcp-for-public-docs.md +2 -2
  456. package/agent-docs/publish/openapi-extension-mappings.md +2 -2
  457. package/agent-docs/publish/output.md +14 -14
  458. package/agent-docs/publish/output.sections/api-contracts.md +3 -3
  459. package/agent-docs/publish/output.sections/code-highlighting.md +3 -3
  460. package/agent-docs/publish/output.sections/current-limits.md +4 -4
  461. package/agent-docs/publish/output.sections/incremental-sync.md +3 -3
  462. package/agent-docs/publish/output.sections/manifest-records-and-sections.md +3 -3
  463. package/agent-docs/publish/output.sections/navigation-section-indexes.md +3 -3
  464. package/agent-docs/publish/output.sections/page-formats.md +3 -3
  465. package/agent-docs/publish/output.sections/published-output-contract.md +3 -3
  466. package/agent-docs/publish/output.sections/reader-appearance-and-controls.md +3 -3
  467. package/agent-docs/publish/output.sections/related-reading.md +3 -3
  468. package/agent-docs/publish/output.sections/response-identity.md +3 -3
  469. package/agent-docs/publish/output.sections/site-files.md +3 -3
  470. package/agent-docs/publish/output.sections/validate-an-existing-build-locally.md +5 -5
  471. package/agent-docs/publish/output.sections/where-files-go.md +11 -11
  472. package/agent-docs/publish/publications.md +1 -1
  473. package/agent-docs/publish/revisions.md +1 -1
  474. package/agent-docs/publish/seo.md +17 -3
  475. package/agent-docs/publish/seo.sections/related-reading.md +28 -0
  476. package/agent-docs/publish/seo.sections/set-the-public-address.md +36 -0
  477. package/agent-docs/publish/seo.sections/what-agents-receive.md +28 -0
  478. package/agent-docs/publish/seo.sections/what-the-build-adds.md +42 -0
  479. package/agent-docs/publish/seo.sections/write-the-page-title-and-summary.md +43 -0
  480. package/agent-docs/publish/single-source-sites.md +1 -1
  481. package/agent-docs/publish/troubleshooting.md +1 -1
  482. package/agent-docs/publish/vercel.md +1 -1
  483. package/agent-docs/publish/vercel.sections/check-the-deployment.md +1 -1
  484. package/agent-docs/publish/vercel.sections/include-an-mcp-endpoint.md +1 -1
  485. package/agent-docs/publish/vercel.sections/prepare-your-project.md +1 -1
  486. package/agent-docs/publish/vercel.sections/related-reading.md +1 -1
  487. package/agent-docs/publish.md +1 -1
  488. package/agent-docs/recipes/add-page.md +3 -3
  489. package/agent-docs/recipes/add-page.sections/audit-the-shoalrun-docs-project.md +2 -2
  490. package/agent-docs/recipes/add-page.sections/before-you-start.md +2 -3
  491. package/agent-docs/recipes/add-page.sections/build-the-shoalrun-docs-site.md +2 -2
  492. package/agent-docs/recipes/add-page.sections/check-agent-search-and-page-lookup.md +2 -2
  493. package/agent-docs/recipes/add-page.sections/check-the-built-page-artifacts.md +2 -2
  494. package/agent-docs/recipes/add-page.sections/check-the-outcome.md +2 -2
  495. package/agent-docs/recipes/add-page.sections/complete-the-guide-and-add-navigation.md +2 -2
  496. package/agent-docs/recipes/add-page.sections/install-the-site-dependencies.md +2 -2
  497. package/agent-docs/recipes/add-page.sections/regenerate-the-agent-documentation-bundle.md +3 -2
  498. package/agent-docs/recipes/add-page.sections/related-reading.md +2 -2
  499. package/agent-docs/recipes/add-page.sections/scaffold-a-new-guide.md +2 -2
  500. package/agent-docs/recipes/audit-docs.md +2 -3
  501. package/agent-docs/recipes/build-docs.md +2 -3
  502. package/agent-docs/recipes/preview-hybrid-search.md +2 -2
  503. package/agent-docs/recipes/preview-page.md +2 -3
  504. package/agent-docs/recipes/queue-email.md +2 -2
  505. package/agent-docs/recipes/refresh-agent-docs.md +21 -3
  506. package/agent-docs/recipes/send-welcome-email.md +2 -2
  507. package/agent-docs/reference/agent-skills.md +2 -2
  508. package/agent-docs/search-and-chat.md +1 -1
  509. package/agent-docs/search-index.json +1 -1
  510. package/agent-docs/start.md +7 -3
  511. package/agent-docs/start.sections/build-this-site.md +1 -1
  512. package/agent-docs/start.sections/migrating-from-github-packages.md +35 -0
  513. package/agent-docs/start.sections/prerequisites.md +1 -1
  514. package/agent-docs/start.sections/start-your-own-project.md +1 -1
  515. package/agent-docs/status.md +2 -2
  516. package/agent-docs/styling/color-modes.md +6 -2
  517. package/agent-docs/styling/footer.md +10 -8
  518. package/agent-docs/styling/header.md +5 -3
  519. package/agent-docs/styling/layouts.md +27 -5
  520. package/agent-docs/styling/layouts.sections/build-a-landing-page.md +7 -4
  521. package/agent-docs/styling/layouts.sections/build-a-splash-page.md +7 -4
  522. package/agent-docs/styling/layouts.sections/control-navigation-tabs.md +7 -4
  523. package/agent-docs/styling/layouts.sections/customize-the-hero.md +7 -4
  524. package/agent-docs/styling/layouts.sections/hero.md +8 -5
  525. package/agent-docs/styling/layouts.sections/hide-the-table-of-contents-or-give-content-more-room.md +54 -0
  526. package/agent-docs/styling/layouts.sections/html-markdown-and-agents.md +43 -0
  527. package/agent-docs/styling/layouts.sections/related-reading.md +7 -4
  528. package/agent-docs/styling/palettes.md +6 -2
  529. package/agent-docs/styling/share-images.md +6 -2
  530. package/agent-docs/styling/themes.md +6 -3
  531. package/agent-docs/styling/try.md +3 -15
  532. package/dist/api.js +70 -3
  533. package/dist/authoring-hints.d.ts +3 -0
  534. package/dist/authoring-hints.js +35 -0
  535. package/dist/authoring-project.d.ts +1 -1
  536. package/dist/authoring-project.js +2 -0
  537. package/dist/authoring-validation.js +2 -0
  538. package/dist/code-samples.js +22 -11
  539. package/dist/compiler-config.d.ts +1 -0
  540. package/dist/compiler-config.js +13 -0
  541. package/dist/compiler.js +2 -1
  542. package/dist/component-names.js +1 -1
  543. package/dist/component-transform.js +7 -1
  544. package/dist/content-delivery-contract.d.ts +108 -0
  545. package/dist/content-delivery-contract.js +15 -0
  546. package/dist/content-delivery-transport.d.ts +3 -0
  547. package/dist/content-delivery-transport.js +47 -0
  548. package/dist/content-delivery-validation.d.ts +7 -0
  549. package/dist/content-delivery-validation.js +122 -0
  550. package/dist/content-delivery.d.ts +21 -0
  551. package/dist/content-delivery.js +119 -0
  552. package/dist/content-export-anchors.d.ts +11 -0
  553. package/dist/content-export-anchors.js +79 -0
  554. package/dist/content-export-command.d.ts +4 -2
  555. package/dist/content-export-command.js +15 -2
  556. package/dist/content-export-comparison.d.ts +16 -0
  557. package/dist/content-export-comparison.js +48 -0
  558. package/dist/content-export-config.d.ts +17 -0
  559. package/dist/content-export-config.js +47 -3
  560. package/dist/content-export-render.js +20 -1
  561. package/dist/content-export.d.ts +5 -1
  562. package/dist/content-export.js +193 -40
  563. package/dist/content-mount.d.ts +11 -0
  564. package/dist/content-mount.js +73 -0
  565. package/dist/content-rest-client.d.ts +12 -0
  566. package/dist/content-rest-client.js +162 -0
  567. package/dist/content-rest-contract.d.ts +27 -0
  568. package/dist/content-rest-contract.js +9 -0
  569. package/dist/content-rest-server.d.ts +7 -0
  570. package/dist/content-rest-server.js +99 -0
  571. package/dist/content-source.d.ts +3 -1
  572. package/dist/frontmatter.js +8 -7
  573. package/dist/html-renderer.js +25 -0
  574. package/dist/markdown-sections.d.ts +2 -1
  575. package/dist/markdown-sections.js +8 -18
  576. package/dist/page-renderer.js +5 -3
  577. package/dist/publication-plan.js +2 -1
  578. package/dist/scaffold-placeholders.d.ts +2 -0
  579. package/dist/scaffold-placeholders.js +19 -0
  580. package/dist/section-identity.d.ts +24 -0
  581. package/dist/section-identity.js +137 -0
  582. package/examples/content-delivery/README.md +87 -0
  583. package/examples/content-delivery/app.js +91 -0
  584. package/examples/content-delivery/content/billing.mdx +22 -0
  585. package/examples/content-delivery/docs.config.yaml +36 -0
  586. package/examples/content-delivery/index.html +25 -0
  587. package/examples/content-delivery/preview.mjs +86 -0
  588. package/examples/content-delivery/snippets/refund-window.mdx +3 -0
  589. package/examples/help-drawer/README.md +2 -2
  590. package/examples/help-drawer/app.js +3 -3
  591. package/package.json +6 -3
  592. package/schemas/content-export.schema.json +469 -33
  593. package/schemas/content-rest.openapi.json +1627 -0
  594. package/skills/shoalrun-author/SKILL.md +2 -0
  595. package/styles/site.css +4 -1
  596. package/agent-docs/.well-known/shoalrun/navigation/changelog-tab-50f26fe22670.txt +0 -33
  597. package/agent-docs/publish/api-references.sections/add-useful-examples.md +0 -36
  598. package/agent-docs/publish/api-references.sections/describe-filters-that-do-not-use-their-openapi-parameter-name.md +0 -36
  599. package/agent-docs/publish/api-references.sections/link-guides-to-api-references.md +0 -36
  600. package/agent-docs/publish/api-references.sections/map-extension-values-to-shoalrun-status.md +0 -36
  601. package/agent-docs/publish/api-references.sections/publish-several-versions.md +0 -36
  602. package/agent-docs/publish/api-references.sections/use-a-template-for-editorial-context.md +0 -36
@@ -0,0 +1,122 @@
1
+ import { ContentDeliveryError, contentRefKey } from './content-delivery-contract.js';
2
+ const object = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
3
+ const keys = (value, allowed) => object(value) && Object.keys(value).every(key => allowed.includes(key));
4
+ const nonempty = (value) => typeof value === 'string' && value.length > 0 && value.length <= 2048;
5
+ const digestPattern = /^[a-f0-9]{64}$/;
6
+ const pathPattern = /^(?:NOTICES\.txt|(?:content|assets)\/[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)*)$/;
7
+ export const safeContentPath = (value) => typeof value === 'string' && pathPattern.test(value) && value.split('/').every(part => part !== '.' && part !== '..');
8
+ const invalid = (message) => { throw new ContentDeliveryError('invalid_manifest', message); };
9
+ export const validContentPublication = (value) => object(value) && Object.keys(value).length === 3 && ['channel', 'version', 'language'].every(key => nonempty(value[key]));
10
+ const samePublication = (left, right) => ['channel', 'version', 'language'].every(key => left[key] === right[key]);
11
+ export function validContentRef(value) {
12
+ if (!object(value))
13
+ return false;
14
+ if (value.kind === 'page')
15
+ return Object.keys(value).length === 2 && nonempty(value.documentId);
16
+ if (value.kind === 'section')
17
+ return Object.keys(value).length === 3 && nonempty(value.documentId) && typeof value.sectionId === 'string' && /^[a-z0-9][a-z0-9-]{0,511}$/.test(value.sectionId);
18
+ return value.kind === 'snippet' && Object.keys(value).length === 2 && nonempty(value.id);
19
+ }
20
+ function safeExternal(value, image = false) {
21
+ try {
22
+ const url = new URL(value);
23
+ return typeof value === 'string' && !url.username && !url.password && (image ? ['http:', 'https:'] : ['http:', 'https:', 'mailto:', 'tel:']).includes(url.protocol);
24
+ }
25
+ catch {
26
+ return false;
27
+ }
28
+ }
29
+ export function validateContentManifest(value, maxResources = 10_000, maxResourceBytes = 8_000_000) {
30
+ if (!keys(value, ['schemaVersion', 'kind', 'digest', 'publicBase', 'publication', 'review', 'resources', 'assets', 'files', 'diagnostics']) || value.schemaVersion !== 1 || value.kind !== 'shoalrun-content-export' || !digestPattern.test(value.digest ?? ''))
31
+ invalid('Unsupported content manifest.');
32
+ if (typeof value.publicBase !== 'string' || !/^\/(?:[A-Za-z0-9_~.-]+\/)*$/.test(value.publicBase) || value.publicBase.split('/').some((part) => part === '.' || part === '..'))
33
+ invalid('Invalid content mount.');
34
+ if (!validContentPublication(value.publication))
35
+ invalid('Invalid publication.');
36
+ if (!keys(value.review, ['status', 'apis']) || value.review.status !== 'ok' || !Array.isArray(value.review.apis) || value.review.apis.some((api) => !keys(api, ['apiId', 'digest']) || !nonempty(api.apiId) || !digestPattern.test(api.digest ?? '')))
37
+ invalid('Content must have approved API review metadata.');
38
+ if (!Array.isArray(value.resources) || !value.resources.length || value.resources.length > maxResources || !Array.isArray(value.files) || !Array.isArray(value.assets) || !Array.isArray(value.diagnostics))
39
+ invalid('Invalid manifest inventory.');
40
+ const files = new Map();
41
+ const checkFile = (file) => object(file) && safeContentPath(file.path) && Number.isSafeInteger(file.bytes) && file.bytes >= 0 && file.bytes <= maxResourceBytes && digestPattern.test(file.digest ?? '');
42
+ for (const file of value.files) {
43
+ if (!keys(file, ['path', 'bytes', 'digest']) || !checkFile(file) || files.has(file.path))
44
+ invalid('Invalid or duplicate file.');
45
+ files.set(file.path, file);
46
+ }
47
+ const listed = (file) => checkFile(file) && files.has(file.path) && files.get(file.path).bytes === file.bytes && files.get(file.path).digest === file.digest;
48
+ const assets = new Set();
49
+ for (const asset of value.assets) {
50
+ if (!keys(asset, ['path', 'bytes', 'digest', 'mediaType']) || !listed(asset) || !asset.path.startsWith('assets/') || !nonempty(asset.mediaType) || assets.has(asset.path))
51
+ invalid('Invalid or duplicate asset.');
52
+ assets.add(asset.path);
53
+ }
54
+ validateContentResources(value.resources, value.publication, assets, listed, value.publicBase);
55
+ if (value.diagnostics.some((item) => !keys(item, ['code', 'documentId', 'message']) || typeof item.code !== 'string' || item.documentId !== undefined && typeof item.documentId !== 'string' || typeof item.message !== 'string'))
56
+ invalid('Invalid diagnostics.');
57
+ }
58
+ /** Shared metadata validation; transport supplies its file/asset inventory policy. */
59
+ export function validateContentResources(resources, expectedPublication, assets, checkRepresentation, publicBase = '/') {
60
+ const refs = new Set();
61
+ const links = [];
62
+ for (const resource of resources) {
63
+ if (!keys(resource, ['ref', 'publication', 'title', 'summary', 'type', 'canonicalUrl', 'source', 'representations', 'assets', 'externalDependencies']) || !validContentRef(resource.ref) || !validContentPublication(resource.publication) || !samePublication(resource.publication, expectedPublication) || typeof resource.title !== 'string' || typeof resource.summary !== 'string' || typeof resource.type !== 'string')
64
+ invalid('Invalid resource metadata.');
65
+ const key = contentRefKey(resource.ref);
66
+ if (refs.has(key))
67
+ invalid('Duplicate content reference.');
68
+ refs.add(key);
69
+ if (resource.canonicalUrl !== undefined && !safeExternal(resource.canonicalUrl, true))
70
+ invalid('Invalid canonical URL.');
71
+ if (resource.source !== undefined && (!object(resource.source) || !Object.keys(resource.source).length || Object.keys(resource.source).some(key => !['documentId', 'snippetId', 'valuesDigest'].includes(key)) || Object.values(resource.source).some(value => !nonempty(value))))
72
+ invalid('Invalid resource source.');
73
+ if (resource.source?.valuesDigest !== undefined && !digestPattern.test(resource.source.valuesDigest))
74
+ invalid('Invalid snippet values digest.');
75
+ if (!Array.isArray(resource.assets) || new Set(resource.assets).size !== resource.assets.length || resource.assets.some((asset) => !assets.has(asset)))
76
+ invalid('Unknown resource asset.');
77
+ if (!Array.isArray(resource.externalDependencies) || resource.externalDependencies.some((dependency) => !keys(dependency, ['url', 'kind']) || dependency.kind !== 'image' || !safeExternal(dependency.url, true)))
78
+ invalid('Invalid external dependency.');
79
+ if (!object(resource.representations) || !Object.keys(resource.representations).length || Object.keys(resource.representations).some(format => !['html', 'markdown'].includes(format)))
80
+ invalid('Invalid representations.');
81
+ for (const [format, rep] of Object.entries(resource.representations)) {
82
+ if (!keys(rep, ['path', 'bytes', 'digest', 'mediaType', 'anchors', 'links']) || !checkRepresentation(rep) || !rep.path.startsWith('content/') || rep.mediaType !== (format === 'html' ? 'text/html' : 'text/markdown') || !Array.isArray(rep.anchors) || !Array.isArray(rep.links))
83
+ invalid('Invalid representation file.');
84
+ const ids = new Set();
85
+ const exportedIds = new Set();
86
+ const linkKeys = new Set();
87
+ for (const anchor of rep.anchors) {
88
+ if (!keys(anchor, ['id', 'exportedId', 'title']) || !nonempty(anchor.id) || !nonempty(anchor.exportedId) || ids.has(anchor.id) || exportedIds.has(anchor.exportedId) || anchor.title !== undefined && typeof anchor.title !== 'string')
89
+ invalid('Invalid representation anchor.');
90
+ ids.add(anchor.id);
91
+ exportedIds.add(anchor.exportedId);
92
+ }
93
+ for (const link of rep.links) {
94
+ if (!keys(link, ['key', 'kind', 'href', 'ref', 'fragment', 'query', 'asset']) || !nonempty(link.key) || linkKeys.has(link.key) || typeof link.href !== 'string' || !['resource', 'canonical', 'external', 'asset'].includes(link.kind))
95
+ invalid('Invalid representation link.');
96
+ linkKeys.add(link.key);
97
+ if (link.kind === 'resource' && !validContentRef(link.ref) || link.kind === 'asset' && !assets.has(link.asset) || ['canonical', 'external'].includes(link.kind) && !safeExternal(link.href))
98
+ invalid('Invalid link destination.');
99
+ if (link.fragment !== undefined && typeof link.fragment !== 'string' || link.query !== undefined && typeof link.query !== 'string')
100
+ invalid('Invalid link suffix.');
101
+ if (['resource', 'asset'].includes(link.kind)) {
102
+ const linkPath = link.href.split(/[?#]/, 1)[0];
103
+ if (!link.href.startsWith('#') && !link.href.startsWith(publicBase) || /\\|%2e|%2f|%5c/i.test(linkPath))
104
+ invalid('Link leaves content mount.');
105
+ let url;
106
+ try {
107
+ url = new URL(link.href, 'https://content.invalid' + publicBase);
108
+ }
109
+ catch {
110
+ return invalid('Invalid internal link URL.');
111
+ }
112
+ if (url.origin !== 'https://content.invalid' || !url.pathname.startsWith(publicBase))
113
+ invalid('Link leaves content mount.');
114
+ }
115
+ links.push(link);
116
+ }
117
+ }
118
+ }
119
+ for (const link of links)
120
+ if (link.kind === 'resource' && !refs.has(contentRefKey(link.ref)))
121
+ invalid('Link references absent content.');
122
+ }
@@ -0,0 +1,21 @@
1
+ import type { ContentSnapshot } from './content-delivery-contract.js';
2
+ export * from './content-delivery-contract.js';
3
+ export * from './content-rest-contract.js';
4
+ export { validateContentManifest } from './content-delivery-validation.js';
5
+ export { mountContent } from './content-mount.js';
6
+ export type { ContentMount } from './content-mount.js';
7
+ export type ContentDeliveryLimits = {
8
+ manifestBytes?: number;
9
+ resourceBytes?: number;
10
+ resources?: number;
11
+ };
12
+ export type ManifestContentSourceOptions = {
13
+ manifestUrl: string | URL;
14
+ fetch?: typeof globalThis.fetch;
15
+ signal?: AbortSignal;
16
+ limits?: ContentDeliveryLimits;
17
+ };
18
+ /** Opens one immutable manifest revision. The caller owns trust in the build/host. */
19
+ export declare function openManifestContentSource(options: ManifestContentSourceOptions): Promise<ContentSnapshot>;
20
+ export { openRestContentSource } from './content-rest-client.js';
21
+ export type { RestContentSourceOptions } from './content-rest-client.js';
@@ -0,0 +1,119 @@
1
+ import { ContentDeliveryError, contentRefKey } from './content-delivery-contract.js';
2
+ export * from './content-delivery-contract.js';
3
+ export * from './content-rest-contract.js';
4
+ import { validateContentManifest, validContentRef } from './content-delivery-validation.js';
5
+ export { validateContentManifest } from './content-delivery-validation.js';
6
+ import { freeze, sha256, readBounded } from './content-delivery-transport.js';
7
+ const invalid = (message) => { throw new ContentDeliveryError('invalid_manifest', message); };
8
+ export { mountContent } from './content-mount.js';
9
+ /** Opens one immutable manifest revision. The caller owns trust in the build/host. */
10
+ export async function openManifestContentSource(options) {
11
+ const fetcher = options.fetch ?? globalThis.fetch;
12
+ let manifestUrl;
13
+ try {
14
+ manifestUrl = new URL(options.manifestUrl, typeof location === 'undefined' ? undefined : location.href);
15
+ }
16
+ catch {
17
+ throw new ContentDeliveryError('invalid_manifest', 'Manifest URL must be a valid HTTP(S) URL.');
18
+ }
19
+ if (!['http:', 'https:'].includes(manifestUrl.protocol) || manifestUrl.username || manifestUrl.password || manifestUrl.hash || manifestUrl.search)
20
+ invalid('Manifest URL must be an HTTP(S) URL without credentials, query, or fragment.');
21
+ if (typeof location !== 'undefined' && manifestUrl.origin !== location.origin)
22
+ invalid('Browser content must be served from the application origin.');
23
+ const manifestBytes = options.limits?.manifestBytes ?? 5_000_000;
24
+ const resourceBytes = options.limits?.resourceBytes ?? 8_000_000;
25
+ const resources = options.limits?.resources ?? 10_000;
26
+ for (const limit of [manifestBytes, resourceBytes, resources])
27
+ if (!Number.isSafeInteger(limit) || limit <= 0)
28
+ invalid('Limits must be positive safe integers.');
29
+ const lifetime = new AbortController();
30
+ let disposed = false;
31
+ const openSignal = options.signal ? AbortSignal.any([options.signal, lifetime.signal]) : lifetime.signal;
32
+ async function request(url, mediaType, limit, signal, revision = false) {
33
+ signal.throwIfAborted();
34
+ let response;
35
+ try {
36
+ response = await fetcher(url.href, { signal, redirect: 'error', credentials: 'same-origin', cache: 'no-store', headers: { Accept: mediaType } });
37
+ }
38
+ catch (error) {
39
+ signal.throwIfAborted();
40
+ throw new ContentDeliveryError('network_error', 'Cannot fetch content.');
41
+ }
42
+ const rejectResponse = async (code, message) => { await response.body?.cancel().catch(() => { }); throw new ContentDeliveryError(code, message); };
43
+ if (response.status === 404)
44
+ return rejectResponse(revision ? 'stale_revision' : 'not_found', 'Content is unavailable.');
45
+ if (!response.ok)
46
+ return rejectResponse('network_error', `Content request failed (${response.status}).`);
47
+ if (response.redirected || response.url && response.url !== url.href)
48
+ return rejectResponse('invalid_content', 'Redirected content is not accepted.');
49
+ if (response.headers.get('content-type')?.split(';', 1)[0].trim().toLowerCase() !== mediaType)
50
+ return rejectResponse('invalid_content', 'Unexpected content type.');
51
+ try {
52
+ return await readBounded(response, limit, signal);
53
+ }
54
+ catch (error) {
55
+ signal.throwIfAborted();
56
+ if (error instanceof ContentDeliveryError)
57
+ throw error;
58
+ throw new ContentDeliveryError('network_error', 'Content response was interrupted.');
59
+ }
60
+ }
61
+ let manifest;
62
+ try {
63
+ manifest = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(await request(manifestUrl, 'application/json', manifestBytes, openSignal)));
64
+ }
65
+ catch (error) {
66
+ if (error instanceof ContentDeliveryError || openSignal.aborted)
67
+ throw error;
68
+ invalid('Manifest is not valid UTF-8 JSON.');
69
+ }
70
+ validateContentManifest(manifest, resources, resourceBytes);
71
+ const { digest, ...body } = manifest;
72
+ if (await sha256(new TextEncoder().encode(JSON.stringify(body))) !== digest)
73
+ invalid('Manifest digest does not match its contents.');
74
+ openSignal.throwIfAborted();
75
+ const mount = new URL(manifest.publicBase, manifestUrl.origin);
76
+ if (!manifestUrl.pathname.startsWith(mount.pathname))
77
+ invalid('Manifest is outside its declared content mount.');
78
+ freeze(manifest);
79
+ const byRef = new Map(manifest.resources.map(resource => [contentRefKey(resource.ref), resource]));
80
+ const active = () => { if (disposed)
81
+ throw new ContentDeliveryError('disposed', 'Content snapshot has been disposed.'); };
82
+ return Object.freeze({
83
+ revision: manifest.digest, publication: manifest.publication,
84
+ list() { active(); return manifest.resources; },
85
+ async get(ref, getOptions) {
86
+ active();
87
+ if (!validContentRef(ref))
88
+ throw new ContentDeliveryError('not_found', 'Invalid content reference.');
89
+ const resource = byRef.get(contentRefKey(ref));
90
+ if (!resource)
91
+ throw new ContentDeliveryError('not_found', 'No matching content in this publication.');
92
+ const format = getOptions?.format;
93
+ if (!['html', 'markdown'].includes(format))
94
+ throw new ContentDeliveryError('unsupported_format', 'The requested representation is unavailable.');
95
+ const representation = resource.representations[format];
96
+ if (!representation)
97
+ throw new ContentDeliveryError('unsupported_format', 'The requested representation is unavailable.');
98
+ const signal = getOptions.signal ? AbortSignal.any([lifetime.signal, getOptions.signal]) : lifetime.signal;
99
+ const bytes = await request(new URL(representation.path, mount), representation.mediaType, resourceBytes, signal, true);
100
+ if (bytes.byteLength !== representation.bytes || await sha256(bytes) !== representation.digest)
101
+ throw new ContentDeliveryError('stale_revision', 'Representation differs from the opened revision.');
102
+ signal.throwIfAborted();
103
+ active();
104
+ let content;
105
+ try {
106
+ content = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
107
+ }
108
+ catch {
109
+ throw new ContentDeliveryError('invalid_content', 'Content is not valid UTF-8.');
110
+ }
111
+ return Object.freeze({ resource, representation, format, content, revision: manifest.digest });
112
+ },
113
+ dispose() { if (!disposed) {
114
+ disposed = true;
115
+ lifetime.abort(new ContentDeliveryError('disposed', 'Content snapshot has been disposed.'));
116
+ } },
117
+ });
118
+ }
119
+ export { openRestContentSource } from './content-rest-client.js';
@@ -0,0 +1,11 @@
1
+ /** Expanded snippets may share nodes; give every authored occurrence its own identity. */
2
+ export declare function isolateExportTree(value: any): any;
3
+ /** Stable internal provenance survives cloning, slicing, and portable lowering. */
4
+ export declare function markExportSource(tree: any): void;
5
+ /** Map semantic heading provenance to the actual IDs assigned by the renderer. */
6
+ export declare function exportHeadingInventory(tree: any, reservedIds?: Iterable<string>): {
7
+ headings: Map<string, string>;
8
+ ids: Set<string>;
9
+ };
10
+ /** HTML keeps source IDs; Markdown keeps clean headings and maps source to local IDs. */
11
+ export declare function preserveExportHeadingIds(tree: any, source: ReturnType<typeof exportHeadingInventory>, format: 'html' | 'markdown'): Map<string, string>;
@@ -0,0 +1,79 @@
1
+ import { parseFragment } from 'parse5';
2
+ import { renderHtmlWithHeadings } from './html-renderer.js';
3
+ /** Expanded snippets may share nodes; give every authored occurrence its own identity. */
4
+ export function isolateExportTree(value) {
5
+ if (Array.isArray(value))
6
+ return value.map(isolateExportTree);
7
+ if (value && typeof value === 'object')
8
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, isolateExportTree(item)]));
9
+ return value;
10
+ }
11
+ /** Stable internal provenance survives cloning, slicing, and portable lowering. */
12
+ export function markExportSource(tree) {
13
+ let index = 0;
14
+ const walk = (node) => {
15
+ if (!node || typeof node !== 'object')
16
+ return;
17
+ if (Array.isArray(node)) {
18
+ node.forEach(walk);
19
+ return;
20
+ }
21
+ if (typeof node.type === 'string')
22
+ node.data = { ...node.data, exportSourceKey: `source-${index++}` };
23
+ for (const [key, value] of Object.entries(node))
24
+ if (!['data', 'position'].includes(key))
25
+ walk(value);
26
+ };
27
+ walk(tree);
28
+ }
29
+ /** Map semantic heading provenance to the actual IDs assigned by the renderer. */
30
+ export function exportHeadingInventory(tree, reservedIds = []) {
31
+ const copy = structuredClone(tree);
32
+ const mark = (node) => {
33
+ if (node.data?.exportSourceKey)
34
+ node.data.hProperties = { ...node.data.hProperties, 'data-export-source-key': node.data.exportSourceKey };
35
+ for (const child of node.children ?? [])
36
+ mark(child);
37
+ };
38
+ mark(copy);
39
+ const parsed = parseFragment(renderHtmlWithHeadings(copy, undefined, reservedIds).html);
40
+ const headings = new Map();
41
+ const ids = new Set();
42
+ const visit = (node) => {
43
+ const id = node.attrs?.find((attr) => attr.name === 'id')?.value;
44
+ if (id) {
45
+ ids.add(id);
46
+ const key = node.attrs?.find((attr) => attr.name === 'data-export-source-key')?.value;
47
+ if (/^h[2-6]$/.test(node.tagName ?? '') && key)
48
+ headings.set(key, id);
49
+ }
50
+ for (const child of node.childNodes ?? [])
51
+ visit(child);
52
+ };
53
+ visit(parsed);
54
+ return { headings, ids };
55
+ }
56
+ /** HTML keeps source IDs; Markdown keeps clean headings and maps source to local IDs. */
57
+ export function preserveExportHeadingIds(tree, source, format) {
58
+ const local = exportHeadingInventory(tree);
59
+ const aliases = new Map();
60
+ const walk = (node) => {
61
+ const key = node.data?.exportSourceKey;
62
+ if (key && (node.type === 'heading' || /^h[2-6]$/.test(node.data?.hName ?? '')) && local.headings.has(key)) {
63
+ const original = source.headings.get(key);
64
+ if (!original)
65
+ throw new Error('Cannot determine full-source identity for an exported heading');
66
+ aliases.set(original, format === 'html' ? original : local.headings.get(key));
67
+ if (format === 'html')
68
+ node.data.hProperties = { ...node.data.hProperties, id: original };
69
+ }
70
+ for (const child of node.children ?? [])
71
+ walk(child);
72
+ };
73
+ walk(tree);
74
+ // Non-heading anchors (explicit aliases, footnotes, component IDs) retain IDs.
75
+ for (const id of local.ids)
76
+ if (!Array.from(local.headings.values()).includes(id) && source.ids.has(id))
77
+ aliases.set(id, id);
78
+ return aliases;
79
+ }
@@ -1,11 +1,13 @@
1
- export declare const exportHelp = "Usage: shoalrun export [config] --out DIRECTORY [--target NAME]\n [--format markdown|html|both] [--document ID]...\n [--channel C --version V --language L] [--public-base /help/] [--review]\n\nExport whole pages as resolved Markdown and passive HTML, with a manifest and assets.\nNavigation, facets, and a prior website build are not required.\n--review writes an API-review candidate; status needs-review exits 2 until pinned.\n";
1
+ export declare const exportHelp = "Usage: shoalrun export [config] --out DIRECTORY [--target NAME]\n [--format markdown|html|both] [--document ID]...\n [--channel C --version V --language L] [--public-base /help/] [--review]\n [--previous-manifest FILE] [--fail-on-removed]\n\nExport selected pages, sections, and named snippets as Markdown and passive HTML.\nConfigure typed resources in an export target; --document selects whole pages.\nNavigation, facets, and a prior website build are not required.\n--review writes an API-review candidate; status needs-review exits 2 until pinned.\n--previous-manifest compares public references with an earlier reviewed export.\n--fail-on-removed requires that baseline and refuses to replace output if references disappear.\n";
2
2
  export declare function runExportCommand(args: string[]): Promise<{
3
3
  status: string;
4
4
  output: string;
5
5
  publication: import("./content-source.js").Variant;
6
6
  formats: import("./content-export-config.js").ExportFormat[];
7
+ resources: number;
7
8
  documents: number;
8
9
  documentIds: string[];
10
+ comparison: import("./content-export-comparison.js").ContentExportComparison;
9
11
  assets: number;
10
12
  digest: string;
11
13
  apiReview: {
@@ -14,7 +16,7 @@ export declare function runExportCommand(args: string[]): Promise<{
14
16
  }[];
15
17
  diagnostics: {
16
18
  code: string;
17
- documentId: string;
19
+ documentId?: string;
18
20
  message: string;
19
21
  }[];
20
22
  }>;
@@ -2,10 +2,14 @@ import { exportContent } from './content-export.js';
2
2
  export const exportHelp = `Usage: shoalrun export [config] --out DIRECTORY [--target NAME]
3
3
  [--format markdown|html|both] [--document ID]...
4
4
  [--channel C --version V --language L] [--public-base /help/] [--review]
5
+ [--previous-manifest FILE] [--fail-on-removed]
5
6
 
6
- Export whole pages as resolved Markdown and passive HTML, with a manifest and assets.
7
+ Export selected pages, sections, and named snippets as Markdown and passive HTML.
8
+ Configure typed resources in an export target; --document selects whole pages.
7
9
  Navigation, facets, and a prior website build are not required.
8
10
  --review writes an API-review candidate; status needs-review exits 2 until pinned.
11
+ --previous-manifest compares public references with an earlier reviewed export.
12
+ --fail-on-removed requires that baseline and refuses to replace output if references disappear.
9
13
  `;
10
14
  export async function runExportCommand(args) {
11
15
  const options = {};
@@ -19,8 +23,14 @@ export async function runExportCommand(args) {
19
23
  options.review = true;
20
24
  continue;
21
25
  }
26
+ if (arg === '--fail-on-removed') {
27
+ if (options.failOnRemoved)
28
+ throw new Error(exportHelp);
29
+ options.failOnRemoved = true;
30
+ continue;
31
+ }
22
32
  if (arg.startsWith('--')) {
23
- if (!['--out', '--target', '--format', '--document', '--channel', '--version', '--language', '--public-base'].includes(arg) || !args[index + 1] || args[index + 1].startsWith('--'))
33
+ if (!['--out', '--target', '--format', '--document', '--channel', '--version', '--language', '--public-base', '--previous-manifest'].includes(arg) || !args[index + 1] || args[index + 1].startsWith('--'))
24
34
  throw new Error(exportHelp);
25
35
  const value = args[++index];
26
36
  if (arg === '--document') {
@@ -53,5 +63,8 @@ export async function runExportCommand(args) {
53
63
  }
54
64
  options.target = values.get('--target');
55
65
  options.publicBase = values.get('--public-base');
66
+ options.previousManifest = values.get('--previous-manifest');
67
+ if (options.failOnRemoved && !options.previousManifest)
68
+ throw new Error('--fail-on-removed requires --previous-manifest');
56
69
  return exportContent(config ?? 'docs.config.yaml', output, options);
57
70
  }
@@ -0,0 +1,16 @@
1
+ import { type ContentPublication, type ContentRef } from './content-delivery-contract.js';
2
+ export type ContentReference = {
3
+ publication: ContentPublication;
4
+ ref: ContentRef;
5
+ };
6
+ export type ContentExportComparison = {
7
+ status: 'no-baseline' | 'compared';
8
+ added: ContentReference[];
9
+ removed: ContentReference[];
10
+ diagnostics: Array<{
11
+ code: 'no-reference-baseline' | 'removed-content-reference';
12
+ message: string;
13
+ }>;
14
+ };
15
+ /** Titles, ordering and content edits do not change a public reference. */
16
+ export declare function compareContentManifests(previous: unknown | undefined, current: unknown): ContentExportComparison;
@@ -0,0 +1,48 @@
1
+ import { contentRefKey } from './content-delivery-contract.js';
2
+ const object = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
3
+ const nonempty = (value) => typeof value === 'string' && value.length > 0 && value.trim() === value;
4
+ const keyFor = ({ publication, ref }) => JSON.stringify([publication.channel, publication.version, publication.language, contentRefKey(ref)]);
5
+ /** Validate the public identity surface, without reading files or executing source. */
6
+ function references(manifest, label, requireReviewed) {
7
+ const fail = () => { throw new Error(`${label} must be a content export manifest with valid, unique public resource references`); };
8
+ if (!object(manifest) || manifest.schemaVersion !== 1 || manifest.kind !== 'shoalrun-content-export' || !Array.isArray(manifest.resources) || !manifest.resources.length)
9
+ return fail();
10
+ const publication = manifest.publication;
11
+ if (!object(publication) || Object.keys(publication).length !== 3 || !['channel', 'version', 'language'].every(key => nonempty(publication[key])))
12
+ return fail();
13
+ if (!object(manifest.review) || !['ok', 'required'].includes(String(manifest.review.status)))
14
+ return fail();
15
+ if (requireReviewed && manifest.review.status !== 'ok')
16
+ throw new Error(`${label} must be a reviewed export, not a review candidate`);
17
+ const result = new Map();
18
+ for (const resource of manifest.resources) {
19
+ if (!object(resource) || !object(resource.ref) || !object(resource.publication))
20
+ return fail();
21
+ if (Object.keys(resource.publication).length !== 3 || !['channel', 'version', 'language'].every(key => resource.publication && resource.publication[key] === publication[key]))
22
+ return fail();
23
+ const ref = resource.ref;
24
+ const keys = ref.kind === 'page' ? ['kind', 'documentId'] : ref.kind === 'section' ? ['kind', 'documentId', 'sectionId'] : ref.kind === 'snippet' ? ['kind', 'id'] : [];
25
+ if (!keys.length || Object.keys(ref).length !== keys.length || !keys.every(key => nonempty(ref[key])))
26
+ return fail();
27
+ const entry = { publication: { channel: publication.channel, version: publication.version, language: publication.language }, ref: { ...ref } };
28
+ const key = keyFor(entry);
29
+ if (result.has(key))
30
+ return fail();
31
+ result.set(key, entry);
32
+ }
33
+ return result;
34
+ }
35
+ /** Titles, ordering and content edits do not change a public reference. */
36
+ export function compareContentManifests(previous, current) {
37
+ const next = references(current, 'Current manifest', false);
38
+ const sorted = (values) => [...values].sort((a, b) => keyFor(a) < keyFor(b) ? -1 : keyFor(a) > keyFor(b) ? 1 : 0);
39
+ if (previous === undefined)
40
+ return {
41
+ status: 'no-baseline', added: sorted(next.values()), removed: [],
42
+ diagnostics: [{ code: 'no-reference-baseline', message: 'No previous manifest supplied; removed public references were not checked.' }],
43
+ };
44
+ const prior = references(previous, 'Previous manifest', true);
45
+ const removed = sorted([...prior].filter(([key]) => !next.has(key)).map(([, value]) => value));
46
+ const added = sorted([...next].filter(([key]) => !prior.has(key)).map(([, value]) => value));
47
+ return { status: 'compared', added, removed, diagnostics: removed.length ? [{ code: 'removed-content-reference', message: `${removed.length} previously exported public reference(s) are missing. Review removed references before publishing.` }] : [] };
48
+ }
@@ -1,9 +1,25 @@
1
1
  import { type ReaderSelection } from './reader-filters.js';
2
2
  import type { Variant } from './content.js';
3
3
  export type ExportFormat = 'markdown' | 'html';
4
+ export type ExportResourceSelector = {
5
+ kind: 'page';
6
+ documentId: string;
7
+ } | {
8
+ kind: 'section';
9
+ documentId: string;
10
+ sectionId: string;
11
+ } | {
12
+ kind: 'snippet';
13
+ id: string;
14
+ snippetId: string;
15
+ title: string;
16
+ summary: string;
17
+ values?: Record<string, string | number | boolean>;
18
+ };
4
19
  export type ExportTarget = {
5
20
  formats: ExportFormat[];
6
21
  documents?: string[];
22
+ resources?: ExportResourceSelector[];
7
23
  publication?: Variant;
8
24
  contentFilter?: ReaderSelection;
9
25
  publicBase: string;
@@ -11,6 +27,7 @@ export type ExportTarget = {
11
27
  apiReview: Record<string, string>;
12
28
  notices: string[];
13
29
  };
30
+ export declare function validateExportResources(value: unknown): ExportResourceSelector[];
14
31
  export declare function exportPublicBase(value: unknown): string;
15
32
  export declare function validateExportTarget(value: unknown, readerFilters?: unknown): ExportTarget;
16
33
  export declare function validateExports(value: unknown, readerFilters?: unknown): void;
@@ -1,6 +1,48 @@
1
1
  import { isConfigMapping } from './compiler-config.js';
2
2
  import { validateContentFilter } from './content-filter.js';
3
3
  import { validateReaderFilters } from './reader-filters.js';
4
+ import { validSectionId } from './section-identity.js';
5
+ const nonempty = (value) => typeof value === 'string' && value.trim() === value && value.length > 0;
6
+ const snippetId = (value) => nonempty(value) && /^[A-Za-z_][A-Za-z0-9_.-]*$/.test(value)
7
+ && value.split('.').every(part => /^[A-Za-z_][A-Za-z0-9_-]*$/.test(part) && !['__proto__', 'prototype', 'constructor'].includes(part));
8
+ export function validateExportResources(value) {
9
+ if (!Array.isArray(value) || !value.length)
10
+ throw new Error('Export resources must be a nonempty list');
11
+ const seen = new Set();
12
+ return value.map((item, index) => {
13
+ const fail = (message) => { throw new Error(`Export resources[${index}]: ${message}`); };
14
+ if (!isConfigMapping(item))
15
+ fail('resource must be a mapping');
16
+ const resource = item;
17
+ const keys = resource.kind === 'page' ? ['kind', 'documentId'] : resource.kind === 'section' ? ['kind', 'documentId', 'sectionId'] : resource.kind === 'snippet' ? ['kind', 'id', 'snippetId', 'title', 'summary', 'values'] : undefined;
18
+ if (!keys || Object.keys(resource).some(key => !keys.includes(key)))
19
+ fail('expected a page, section, or snippet selector with only its supported fields');
20
+ let identity;
21
+ if (resource.kind === 'snippet') {
22
+ if (!snippetId(resource.id) || !snippetId(resource.snippetId))
23
+ fail('snippet id and snippetId must be valid stable snippet identifiers');
24
+ if (!nonempty(resource.title) || !nonempty(resource.summary))
25
+ fail('snippet needs a nonempty title and summary');
26
+ if (resource.values !== undefined) {
27
+ if (!isConfigMapping(resource.values) || Object.entries(resource.values).some(([key, value]) => !/^[A-Za-z_][A-Za-z0-9_-]*$/.test(key) || ['__proto__', 'prototype', 'constructor'].includes(key)
28
+ || !['string', 'number', 'boolean'].includes(typeof value) || typeof value === 'number' && !Number.isFinite(value)))
29
+ fail('snippet values must map safe variable names to finite scalar literals');
30
+ }
31
+ identity = JSON.stringify(['snippet', resource.id]);
32
+ }
33
+ else {
34
+ if (!nonempty(resource.documentId))
35
+ fail('documentId must be a nonempty trimmed string');
36
+ if (resource.kind === 'section' && !validSectionId(resource.sectionId))
37
+ fail('sectionId must be a stable lowercase anchor identifier');
38
+ identity = JSON.stringify([resource.kind, resource.documentId, ...(resource.kind === 'section' ? [resource.sectionId] : [])]);
39
+ }
40
+ if (seen.has(identity))
41
+ fail('duplicate resource reference');
42
+ seen.add(identity);
43
+ return { ...resource, ...(resource.values !== undefined ? { values: { ...resource.values } } : {}) };
44
+ });
45
+ }
4
46
  export function exportPublicBase(value) {
5
47
  if (typeof value !== 'string' || !/^\/(?:[A-Za-z0-9_~.-]+\/)*$/.test(value) || value.split('/').some(part => part === '.' || part === '..')) {
6
48
  throw new Error('Export publicBase must be a root-relative path with a trailing slash, such as /help/');
@@ -8,8 +50,10 @@ export function exportPublicBase(value) {
8
50
  return value;
9
51
  }
10
52
  export function validateExportTarget(value, readerFilters) {
11
- if (!isConfigMapping(value) || Object.keys(value).some(key => !['formats', 'documents', 'publication', 'contentFilter', 'publicBase', 'assetRoots', 'apiReview', 'notices'].includes(key)))
12
- throw new Error('Export target accepts formats, documents, publication, contentFilter, publicBase, and assetRoots');
53
+ if (!isConfigMapping(value) || Object.keys(value).some(key => !['formats', 'documents', 'resources', 'publication', 'contentFilter', 'publicBase', 'assetRoots', 'apiReview', 'notices'].includes(key)))
54
+ throw new Error('Export target accepts formats, documents or resources, publication, contentFilter, publicBase, assetRoots, apiReview, and notices');
55
+ if (value.documents !== undefined && value.resources !== undefined)
56
+ throw new Error('Export documents and resources are mutually exclusive');
13
57
  const formats = value.formats ?? ['markdown', 'html'];
14
58
  if (!Array.isArray(formats) || !formats.length || formats.some(format => !['markdown', 'html'].includes(format)) || new Set(formats).size !== formats.length)
15
59
  throw new Error('Export formats must be a nonempty unique list of markdown and/or html');
@@ -28,7 +72,7 @@ export function validateExportTarget(value, readerFilters) {
28
72
  }
29
73
  if (value.apiReview !== undefined && (!isConfigMapping(value.apiReview) || Object.values(value.apiReview).some(digest => typeof digest !== 'string' || !/^[a-f0-9]{64}$/.test(digest))))
30
74
  throw new Error('Export apiReview must map API IDs to lowercase SHA-256 digests');
31
- return { formats: [...formats].sort(), ...(value.documents !== undefined ? { documents: strings(value.documents, 'documents') } : {}), ...(publication ? { publication } : {}),
75
+ return { formats: [...formats].sort(), ...(value.documents !== undefined ? { documents: strings(value.documents, 'documents') } : {}), ...(value.resources !== undefined ? { resources: validateExportResources(value.resources) } : {}), ...(publication ? { publication } : {}),
32
76
  ...(value.contentFilter !== undefined ? { contentFilter: validateContentFilter(value.contentFilter, validateReaderFilters(readerFilters)) } : {}),
33
77
  publicBase: exportPublicBase(value.publicBase ?? '/'), assetRoots: strings(value.assetRoots, 'assetRoots') ?? [], apiReview: (value.apiReview ?? {}), notices: strings(value.notices, 'notices') ?? [] };
34
78
  }
@@ -5,7 +5,22 @@ import { heading, link, paragraph, root, text, renderMarkdown } from './content.
5
5
  import { walkthroughMarkdown } from './walkthrough.js';
6
6
  /** Lower interactions while representation-specific source blocks still exist. */
7
7
  export function portableTree(tree, format, routes, basePath) {
8
+ // Provenance is internal export metadata. Keep it when component lowering
9
+ // synthesizes headings so a slice can retain its full-source identities.
10
+ const inherit = (node, key) => {
11
+ if (!node || typeof node !== 'object')
12
+ return;
13
+ node.data = { ...node.data, exportSourceKey: node.data?.exportSourceKey ?? key };
14
+ for (const [index, child] of (node.children ?? []).entries())
15
+ inherit(child, `${node.data.exportSourceKey}/${index}`);
16
+ };
8
17
  const walk = (node) => {
18
+ const result = lower(node);
19
+ if (node.data?.exportSourceKey)
20
+ result.forEach((child, index) => inherit(child, `${node.data.exportSourceKey}:${index}`));
21
+ return result;
22
+ };
23
+ const lower = (node) => {
9
24
  if (node.type === 'docRepresentation')
10
25
  return node.representation === format ? node.children.flatMap(walk) : [];
11
26
  if (node.type === 'docComponent')
@@ -39,8 +54,12 @@ export function portableTree(tree, format, routes, basePath) {
39
54
  const lowered = publicTree(root((tree.children ?? []).flatMap(walk)), format, routes, 'light', basePath);
40
55
  const definitions = new Map();
41
56
  const collectDefinitions = (node) => {
42
- if (node.type === 'definition')
57
+ if (node.type === 'definition') {
58
+ const previous = definitions.get(node.identifier);
59
+ if (previous && (previous.url !== node.url || previous.title !== node.title))
60
+ throw new Error(`Ambiguous export definition ${node.identifier}`);
43
61
  definitions.set(node.identifier, node);
62
+ }
44
63
  for (const child of node.children ?? [])
45
64
  collectDefinitions(child);
46
65
  };