@astryxdesign/cli 0.6.4 → 0.6.5-canary.01972bc

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 (405) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +103 -95
  3. package/api/build/_adapter.d.mts +36 -2
  4. package/api/build/_adapter.mjs +41 -10
  5. package/api/build/build.doc.mjs +8 -3
  6. package/api/build/build.test.mjs +60 -2
  7. package/api/build/kit/kit.mjs +109 -26
  8. package/api/build/kit/rank.d.mts +24 -8
  9. package/api/build/kit/rank.mjs +277 -97
  10. package/api/build/kit/rank.test.mjs +231 -48
  11. package/api/component/_adapter.d.mts +25 -0
  12. package/api/component/_adapter.mjs +59 -5
  13. package/api/component/component.d.mts +6 -3
  14. package/api/component/component.doc.mjs +37 -17
  15. package/api/component/component.mjs +249 -9
  16. package/api/component/component.type.d.mts +25 -0
  17. package/api/component/component.type.mjs +44 -0
  18. package/api/discover/_adapter.d.mts +114 -6
  19. package/api/discover/_adapter.mjs +372 -17
  20. package/api/discover/_adapter.test.mjs +215 -0
  21. package/api/discover/_catalog-view.d.mts +115 -0
  22. package/api/discover/_catalog-view.mjs +203 -0
  23. package/api/discover/_catalog-view.test.mjs +128 -0
  24. package/api/discover/detail/detail.d.mts +18 -6
  25. package/api/discover/detail/detail.mjs +67 -13
  26. package/api/discover/detail/detail.test.mjs +85 -0
  27. package/api/discover/detail/item/item.d.mts +26 -0
  28. package/api/discover/detail/item/item.mjs +78 -0
  29. package/api/discover/detail/item/item.test.mjs +73 -0
  30. package/api/discover/discover.d.mts +3 -9
  31. package/api/discover/discover.doc.mjs +61 -18
  32. package/api/discover/discover.mjs +220 -36
  33. package/api/discover/discover.test.mjs +11 -2
  34. package/api/discover/discover.type.d.mts +147 -8
  35. package/api/discover/discover.type.mjs +102 -12
  36. package/api/discover/list/list.d.mts +20 -6
  37. package/api/discover/list/list.mjs +45 -12
  38. package/api/discover/list/list.test.mjs +46 -0
  39. package/api/discover/search/search.d.mts +18 -16
  40. package/api/discover/search/search.mjs +102 -56
  41. package/api/discover/search/search.test.mjs +144 -10
  42. package/api/docs/_adapter.d.mts +8 -3
  43. package/api/docs/_adapter.mjs +14 -6
  44. package/api/docs/docOverlays.test.mjs +27 -1
  45. package/api/docs/docs.doc.mjs +2 -2
  46. package/api/docs/docs.test.mjs +54 -18
  47. package/api/docs/integration-tree.test.mjs +17 -0
  48. package/api/docs/integrationDocs.test.mjs +27 -5
  49. package/api/doctor/doctor.d.mts +8 -3
  50. package/api/doctor/doctor.doc.mjs +17 -8
  51. package/api/doctor/doctor.mjs +90 -9
  52. package/api/doctor/doctor.test.mjs +122 -10
  53. package/api/doctor/doctor.type.d.mts +1 -1
  54. package/api/doctor/doctor.type.mjs +1 -1
  55. package/api/error.d.mts +22 -0
  56. package/api/error.mjs +42 -0
  57. package/api/gap-report/gap-report.doc.mjs +19 -10
  58. package/api/hook/hook.doc.mjs +6 -3
  59. package/api/index.d.mts +1 -0
  60. package/api/index.mjs +5 -3
  61. package/api/init/init.doc.mjs +17 -12
  62. package/api/integration/add-contribution.d.mts +2 -1
  63. package/api/integration/add-contribution.mjs +7 -3
  64. package/api/integration/add-contribution.test.mjs +3 -3
  65. package/api/integration/add-helpers.d.mts +5 -2
  66. package/api/integration/add-helpers.mjs +36 -9
  67. package/api/integration/add-theme.mjs +266 -23
  68. package/api/integration/add-theme.test.mjs +247 -0
  69. package/api/integration/authoring-checks.mjs +12 -9
  70. package/api/integration/authoring-checks.type.d.mts +8 -0
  71. package/api/integration/authoring-checks.type.mjs +7 -3
  72. package/api/integration/integration-authoring.type.d.mts +4 -1
  73. package/api/integration/integration-authoring.type.mjs +6 -1
  74. package/api/integration/integrationAdd.doc.mjs +6 -0
  75. package/api/integration/integrationAddTheme.doc.mjs +10 -0
  76. package/api/integration/integrationComponentConflicts.doc.mjs +1 -1
  77. package/api/integration/integrationDocConflicts.doc.mjs +1 -1
  78. package/api/integration/integrationPackCheck.doc.mjs +3 -3
  79. package/api/integration/integrationTemplateConflicts.doc.mjs +1 -1
  80. package/api/integration/pack-check.lifecycle-output.test.mjs +107 -0
  81. package/api/integration/pack-check.mjs +92 -10
  82. package/api/integration/pack-check.test.mjs +140 -1
  83. package/api/integration/pack-check.type.mjs +1 -1
  84. package/api/integration/validate-integration.d.mts +4 -2
  85. package/api/integration/validate-integration.mjs +7 -2
  86. package/api/integration/validate-integration.test.mjs +55 -0
  87. package/api/integration/validate-integration.type.d.mts +5 -0
  88. package/api/integration/validate-integration.type.mjs +5 -1
  89. package/api/integration/validateIntegration.doc.mjs +1 -1
  90. package/api/json/assertResponse.doc.mjs +1 -1
  91. package/api/json/isError.doc.mjs +1 -1
  92. package/api/layout/expand/expand.mjs +12 -7
  93. package/api/layout/expand/expand.receipt.test.mjs +74 -0
  94. package/api/layout/layout.type.d.mts +1 -0
  95. package/api/layout/layout.type.mjs +1 -0
  96. package/api/layout/layoutExpand.doc.mjs +1 -1
  97. package/api/search/search.d.mts +51 -1
  98. package/api/search/search.doc.mjs +2 -2
  99. package/api/search/search.mjs +299 -17
  100. package/api/search/search.test.mjs +216 -18
  101. package/api/swizzle/copy/copy.mjs +66 -3
  102. package/api/swizzle/swizzle.doc.mjs +11 -5
  103. package/api/template/copy/copy.mjs +15 -9
  104. package/api/template/copy/copy.receipt.test.mjs +77 -0
  105. package/api/template/copy/copy.test.mjs +9 -0
  106. package/api/template/show/show.mjs +15 -4
  107. package/api/template/show/show.test.mjs +76 -0
  108. package/api/template/template-integration.test.mjs +14 -0
  109. package/api/template/template.d.mts +1 -1
  110. package/api/template/template.doc.mjs +8 -3
  111. package/api/template/template.mjs +1 -0
  112. package/api/template/template.type.d.mts +2 -0
  113. package/api/template/template.type.mjs +2 -0
  114. package/api/theme/add/add.mjs +17 -25
  115. package/api/theme/add/add.rollback.test.mjs +158 -0
  116. package/api/theme/add/add.staging.test.mjs +40 -23
  117. package/api/theme/build/build.d.mts +24 -0
  118. package/api/theme/build/build.family.test.mjs +7 -12
  119. package/api/theme/build/build.mjs +243 -26
  120. package/api/theme/build/build.project-core.test.mjs +165 -0
  121. package/api/theme/build/build.rollback.test.mjs +148 -0
  122. package/api/theme/generateTonalPalette.doc.mjs +1 -2
  123. package/api/theme/listThemes.doc.mjs +1 -1
  124. package/api/theme/themeAdd.doc.mjs +9 -10
  125. package/api/theme/themeBuild.doc.mjs +13 -13
  126. package/api/theme/themeList.doc.mjs +1 -1
  127. package/api/theme/themeListAvailable.doc.mjs +2 -1
  128. package/api/theme/themePaletteGenerate.doc.mjs +15 -8
  129. package/api/theme/themeTargets.doc.mjs +3 -2
  130. package/api/theme/themeTemplate.doc.mjs +2 -1
  131. package/api/upgrade/run/files-changed.test.mjs +111 -0
  132. package/api/upgrade/run/run.mjs +25 -6
  133. package/api/upgrade/run/run.test.mjs +45 -1
  134. package/api/upgrade/upgrade.doc.mjs +24 -22
  135. package/api/upgrade/upgrade.type.d.mts +1 -0
  136. package/api/upgrade/upgrade.type.mjs +3 -2
  137. package/assets/codemods/__tests__/runner.test.mjs +3 -1
  138. package/assets/codemods/file-count.test.mjs +163 -0
  139. package/assets/codemods/integration-runner.mjs +3 -3
  140. package/assets/codemods/runner.mjs +5 -4
  141. package/assets/docs/README.md +4 -2
  142. package/assets/docs/browser-support.doc.mjs +11 -11
  143. package/assets/docs/color.doc.mjs +8 -2
  144. package/assets/docs/elevation.doc.mjs +6 -4
  145. package/assets/docs/getting-started.doc.mjs +5 -16
  146. package/assets/docs/icons.doc.mjs +3 -21
  147. package/assets/docs/illustrations.doc.mjs +7 -15
  148. package/assets/docs/internationalization.doc.mjs +7 -5
  149. package/assets/docs/layout.doc.dense.mjs +130 -82
  150. package/assets/docs/layout.doc.mjs +133 -77
  151. package/assets/docs/migration.doc.mjs +19 -21
  152. package/assets/docs/motion.doc.mjs +16 -3
  153. package/assets/docs/principles.doc.dense.mjs +5 -5
  154. package/assets/docs/principles.doc.mjs +8 -0
  155. package/assets/docs/principles.doc.zh.mjs +6 -6
  156. package/assets/docs/shape.doc.mjs +8 -3
  157. package/assets/docs/spacing.doc.mjs +7 -2
  158. package/assets/docs/styling-libraries.doc.mjs +6 -2
  159. package/assets/docs/styling.doc.mjs +19 -23
  160. package/assets/docs/theme.doc.dense.mjs +58 -18
  161. package/assets/docs/theme.doc.mjs +57 -47
  162. package/assets/docs/theme.doc.zh.mjs +9 -8
  163. package/assets/docs/tokens.doc.dense.mjs +2 -2
  164. package/assets/docs/tokens.doc.mjs +389 -8
  165. package/assets/docs/tokens.doc.zh.mjs +2 -2
  166. package/assets/docs/tree/add-a-component.doc.mjs +75 -0
  167. package/assets/docs/tree/add-a-theme.doc.mjs +85 -0
  168. package/assets/docs/tree/add-a-topic.doc.mjs +144 -0
  169. package/assets/docs/tree/agent-guidance.doc.mjs +138 -0
  170. package/assets/docs/tree/block-template.doc.mjs +130 -0
  171. package/assets/docs/tree/build-the-template.doc.mjs +28 -0
  172. package/assets/docs/tree/building-blocks.doc.mjs +46 -0
  173. package/assets/docs/tree/check-your-docs.doc.mjs +137 -0
  174. package/assets/docs/tree/checks.doc.mjs +119 -0
  175. package/assets/docs/tree/codemods.doc.mjs +147 -0
  176. package/assets/docs/tree/component-family.doc.mjs +113 -0
  177. package/assets/docs/tree/component-imports.doc.mjs +69 -0
  178. package/assets/docs/tree/component-lookups.doc.mjs +149 -0
  179. package/assets/docs/tree/components.doc.mjs +23 -0
  180. package/assets/docs/tree/configuration.doc.mjs +23 -0
  181. package/assets/docs/tree/debug-and-gap-reports.doc.mjs +182 -0
  182. package/assets/docs/tree/define-the-theme.doc.mjs +118 -0
  183. package/assets/docs/tree/describe-the-component.doc.mjs +57 -0
  184. package/assets/docs/tree/docs.doc.mjs +21 -0
  185. package/assets/docs/tree/document-the-template.doc.mjs +28 -0
  186. package/assets/docs/tree/document-the-theme.doc.mjs +68 -0
  187. package/assets/docs/tree/export-template-assets.doc.mjs +147 -0
  188. package/assets/docs/tree/extend-or-replace.doc.mjs +103 -0
  189. package/assets/docs/tree/fonts-and-assets.doc.mjs +106 -0
  190. package/assets/docs/tree/generate-a-palette.doc.mjs +66 -0
  191. package/assets/docs/tree/grade-template-with-agent.doc.mjs +105 -0
  192. package/assets/docs/tree/help.doc.mjs +16 -0
  193. package/assets/docs/tree/integrations.doc.mjs +25 -451
  194. package/assets/docs/tree/links.doc.mjs +98 -0
  195. package/assets/docs/tree/package-and-test.doc.mjs +32 -0
  196. package/assets/docs/tree/page-template.doc.mjs +71 -0
  197. package/assets/docs/tree/publishing.doc.mjs +111 -0
  198. package/assets/docs/tree/quick-start.doc.mjs +272 -0
  199. package/assets/docs/tree/replace-a-core-component.doc.mjs +104 -0
  200. package/assets/docs/tree/replace-a-core-template.doc.mjs +172 -0
  201. package/assets/docs/tree/sections-and-placement.doc.mjs +108 -0
  202. package/assets/docs/tree/see-it-in-an-app.doc.mjs +59 -0
  203. package/assets/docs/tree/ship.doc.mjs +16 -0
  204. package/assets/docs/tree/short-and-findable.doc.mjs +108 -0
  205. package/assets/docs/tree/single-component.doc.mjs +165 -0
  206. package/assets/docs/tree/start-a-template.doc.mjs +143 -0
  207. package/assets/docs/tree/subcomponent.doc.mjs +115 -0
  208. package/assets/docs/tree/template-assets.doc.mjs +64 -0
  209. package/assets/docs/tree/template-doc-overview.doc.mjs +121 -0
  210. package/assets/docs/tree/template-fonts.doc.mjs +102 -0
  211. package/assets/docs/tree/template-grading-rubric.doc.mjs +452 -0
  212. package/assets/docs/tree/template-icons.doc.mjs +97 -0
  213. package/assets/docs/tree/template-images-media.doc.mjs +127 -0
  214. package/assets/docs/tree/template-styles.doc.mjs +93 -0
  215. package/assets/docs/tree/templates.doc.mjs +34 -0
  216. package/assets/docs/tree/test-in-an-app.doc.mjs +115 -0
  217. package/assets/docs/tree/test-template-in-app.doc.mjs +128 -0
  218. package/assets/docs/tree/themes.doc.mjs +39 -0
  219. package/assets/docs/tree/troubleshooting.doc.mjs +153 -0
  220. package/assets/docs/tree/upgrading.doc.mjs +103 -0
  221. package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +51 -0
  222. package/assets/docs/tree/verify-packed-template.doc.mjs +77 -0
  223. package/assets/docs/tree/versioning.doc.mjs +162 -0
  224. package/assets/docs/tree/write-good-templates.doc.mjs +64 -0
  225. package/assets/docs/tree/write-the-template-file.doc.mjs +154 -0
  226. package/assets/docs/typography.doc.mjs +24 -4
  227. package/assets/docs/working-with-ai.doc.mjs +30 -22
  228. package/assets/templates/blocks/components/InternationalizationProvider/InternationalizationProvider01ShippedLocale.tsx +1 -1
  229. package/assets/templates/pages/ai-chat/template.doc.mjs +16 -1
  230. package/assets/templates/pages/ai-chat-landing/template.doc.mjs +9 -1
  231. package/assets/templates/pages/blank/template.doc.mjs +3 -1
  232. package/assets/templates/pages/canvas-editor/template.doc.mjs +9 -1
  233. package/assets/templates/pages/centered-hero/template.doc.mjs +3 -1
  234. package/assets/templates/pages/checkout-wizard/template.doc.mjs +1 -0
  235. package/assets/templates/pages/classic-gallery/template.doc.mjs +3 -1
  236. package/assets/templates/pages/contact-form/template.doc.mjs +16 -1
  237. package/assets/templates/pages/dashboard/template.doc.mjs +15 -1
  238. package/assets/templates/pages/dashboard-alert-rail/template.doc.mjs +23 -1
  239. package/assets/templates/pages/dashboard-cohort-funnel/template.doc.mjs +10 -1
  240. package/assets/templates/pages/dashboard-comparison/template.doc.mjs +9 -1
  241. package/assets/templates/pages/dashboard-composition/template.doc.mjs +10 -1
  242. package/assets/templates/pages/dashboard-progress/template.doc.mjs +17 -1
  243. package/assets/templates/pages/dashboard-scorecard/template.doc.mjs +2 -1
  244. package/assets/templates/pages/detail-page/template.doc.mjs +8 -0
  245. package/assets/templates/pages/documentation/template.doc.mjs +10 -1
  246. package/assets/templates/pages/documentation-design/template.doc.mjs +10 -1
  247. package/assets/templates/pages/documentation-technical/template.doc.mjs +10 -1
  248. package/assets/templates/pages/editor/template.doc.mjs +9 -1
  249. package/assets/templates/pages/file-explorer/template.doc.mjs +3 -1
  250. package/assets/templates/pages/form-two-column/template.doc.mjs +17 -1
  251. package/assets/templates/pages/form-wizard/template.doc.mjs +14 -1
  252. package/assets/templates/pages/form-wizard-dialog/template.doc.mjs +1 -0
  253. package/assets/templates/pages/gallery-hero/template.doc.mjs +10 -1
  254. package/assets/templates/pages/ide/template.doc.mjs +9 -1
  255. package/assets/templates/pages/incident-console/template.doc.mjs +10 -1
  256. package/assets/templates/pages/kanban-board/template.doc.mjs +11 -1
  257. package/assets/templates/pages/library/template.doc.mjs +15 -1
  258. package/assets/templates/pages/login/template.doc.mjs +10 -1
  259. package/assets/templates/pages/login-card/template.doc.mjs +11 -1
  260. package/assets/templates/pages/login-split/template.doc.mjs +10 -1
  261. package/assets/templates/pages/login-sso/template.doc.mjs +10 -1
  262. package/assets/templates/pages/messaging-shell/template.doc.mjs +11 -1
  263. package/assets/templates/pages/mixed-gallery/template.doc.mjs +10 -1
  264. package/assets/templates/pages/payment-form/template.doc.mjs +3 -1
  265. package/assets/templates/pages/product-detail/template.doc.mjs +9 -1
  266. package/assets/templates/pages/product-gallery/template.doc.mjs +10 -1
  267. package/assets/templates/pages/settings/template.doc.mjs +3 -1
  268. package/assets/templates/pages/settings-dialog/template.doc.mjs +9 -1
  269. package/assets/templates/pages/settings-sidebar/template.doc.mjs +9 -1
  270. package/assets/templates/pages/shell-nav/template.doc.mjs +10 -1
  271. package/assets/templates/pages/shell-side-nav/template.doc.mjs +14 -1
  272. package/assets/templates/pages/shell-top-nav/template.doc.mjs +11 -1
  273. package/assets/templates/pages/side-gallery/template.doc.mjs +3 -1
  274. package/assets/templates/pages/table/template.doc.mjs +11 -1
  275. package/assets/templates/pages/table-filter/template.doc.mjs +21 -1
  276. package/assets/templates/pages/table-grouped/template.doc.mjs +15 -1
  277. package/assets/templates/pages/table-inbox/template.doc.mjs +18 -6
  278. package/assets/templates/pages/table-page/template.doc.mjs +20 -1
  279. package/assets/templates/pages/table-tree/template.doc.mjs +14 -1
  280. package/assets/templates/pages/theme-showcase/template.doc.mjs +10 -1
  281. package/assets/templates/pages/work-item-detail/template.doc.mjs +10 -0
  282. package/assets/templates/themes/butter/icons.tsx +2 -0
  283. package/assets/templates/themes/chocolate/icons.tsx +2 -0
  284. package/assets/templates/themes/gothic/icons.tsx +2 -0
  285. package/assets/templates/themes/matcha/icons.tsx +2 -0
  286. package/assets/templates/themes/neutral/icons.tsx +2 -0
  287. package/assets/templates/themes/stone/icons.tsx +2 -0
  288. package/assets/templates/themes/y2k/icons.tsx +2 -0
  289. package/authoring/config/config.doc.mjs +9 -1
  290. package/authoring/config/parse.d.mts +2 -0
  291. package/authoring/config/parse.mjs +19 -0
  292. package/authoring/config/parse.test.mjs +8 -0
  293. package/authoring/config/type.ts +11 -0
  294. package/authoring/discover/discover.doc.d.mts +13 -0
  295. package/authoring/discover/discover.doc.mjs +138 -0
  296. package/authoring/discover/parse.d.mts +24 -0
  297. package/authoring/discover/parse.mjs +128 -0
  298. package/authoring/discover/parse.test.mjs +124 -0
  299. package/authoring/discover/type.ts +87 -0
  300. package/authoring/doctypes/_schema.d.mts +3 -2
  301. package/authoring/doctypes/_schema.mjs +6 -0
  302. package/authoring/doctypes/base/graph-fields.doc.mjs +3 -3
  303. package/authoring/doctypes/base/type.ts +4 -2
  304. package/authoring/doctypes/component/component.doc.mjs +6 -0
  305. package/authoring/doctypes/component/type.ts +8 -0
  306. package/authoring/doctypes/reference/reference.doc.mjs +7 -0
  307. package/authoring/doctypes/reference/type.ts +5 -0
  308. package/authoring/doctypes/schema/schema.doc.mjs +2 -2
  309. package/authoring/doctypes/template/parse.d.mts +2 -0
  310. package/authoring/doctypes/template/parse.mjs +1 -0
  311. package/authoring/doctypes/template/parse.test.mjs +21 -0
  312. package/authoring/doctypes/template/template.doc.mjs +7 -1
  313. package/authoring/doctypes/template/type.ts +12 -2
  314. package/authoring/index.d.mts +1 -0
  315. package/authoring/index.d.ts +10 -0
  316. package/authoring/index.mjs +1 -0
  317. package/authoring/integration/integration.doc.mjs +12 -10
  318. package/clients/cli/commands/component/index.mjs +152 -55
  319. package/clients/cli/commands/component-batch.test.mjs +341 -0
  320. package/clients/cli/commands/component-ownership.test.mjs +89 -0
  321. package/clients/cli/commands/component.doc.mjs +27 -9
  322. package/clients/cli/commands/discover.doc.mjs +53 -9
  323. package/clients/cli/commands/discover.mjs +393 -118
  324. package/clients/cli/commands/discover.sources.test.mjs +267 -0
  325. package/clients/cli/commands/docs.doc.mjs +1 -1
  326. package/clients/cli/commands/docs.mjs +60 -17
  327. package/clients/cli/commands/docs.test.mjs +113 -24
  328. package/clients/cli/commands/doctor-integration-docs.doc.mjs +3 -2
  329. package/clients/cli/commands/doctor-integration.test.mjs +53 -0
  330. package/clients/cli/commands/doctor.doc.mjs +3 -1
  331. package/clients/cli/commands/doctor.mjs +53 -9
  332. package/clients/cli/commands/gap-report.doc.mjs +10 -9
  333. package/clients/cli/commands/init.doc.mjs +9 -6
  334. package/clients/cli/commands/integration-add.doc.mjs +17 -7
  335. package/clients/cli/commands/integration-authoring.test.mjs +70 -9
  336. package/clients/cli/commands/integration-pack.doc.mjs +5 -9
  337. package/clients/cli/commands/integration-real-world.test.mjs +1 -1
  338. package/clients/cli/commands/integration-verify.doc.mjs +22 -0
  339. package/clients/cli/commands/integration.doc.mjs +4 -4
  340. package/clients/cli/commands/integration.mjs +76 -43
  341. package/clients/cli/commands/layout-expand.doc.mjs +3 -1
  342. package/clients/cli/commands/layout.expand-receipt.test.mjs +94 -0
  343. package/clients/cli/commands/layout.mjs +16 -0
  344. package/clients/cli/commands/manifest.doc.mjs +1 -1
  345. package/clients/cli/commands/search.doc.mjs +10 -3
  346. package/clients/cli/commands/search.mjs +21 -2
  347. package/clients/cli/commands/search.test.mjs +21 -4
  348. package/clients/cli/commands/swizzle.doc.mjs +1 -1
  349. package/clients/cli/commands/template.copy-receipt.test.mjs +60 -0
  350. package/clients/cli/commands/template.doc.mjs +1 -1
  351. package/clients/cli/commands/template.mjs +19 -5
  352. package/clients/cli/commands/template.show-media.test.mjs +62 -0
  353. package/clients/cli/commands/text-json-parity.test.mjs +7 -1
  354. package/clients/cli/commands/theme-add.doc.mjs +1 -1
  355. package/clients/cli/commands/theme-palette-generate.doc.mjs +3 -2
  356. package/clients/cli/commands/theme-palette.doc.mjs +1 -2
  357. package/clients/cli/commands/theme-targets.doc.mjs +2 -2
  358. package/clients/cli/commands/theme.doc.mjs +2 -1
  359. package/clients/cli/commands/upgrade.ascii-output.test.mjs +14 -0
  360. package/clients/cli/commands/upgrade.doc.mjs +62 -3
  361. package/clients/cli/commands/write-failure.test.mjs +175 -0
  362. package/clients/cli/index.mjs +28 -6
  363. package/clients/cli/lib/define-command.mjs +28 -4
  364. package/clients/cli/lib/define-command.test.mjs +54 -0
  365. package/clients/cli/lib/exit-codes.test.mjs +17 -1
  366. package/clients/cli/lib/json-shim.mjs +24 -14
  367. package/clients/cli/lib/manifest.mjs +48 -156
  368. package/clients/cli/lib/manifest.test.mjs +103 -9
  369. package/clients/cli/lib/parse-error-format.test.mjs +81 -0
  370. package/foundation/agent-docs/agent-docs.mjs +1 -1
  371. package/foundation/agent-docs/agent-docs.test.mjs +3 -2
  372. package/foundation/discovery/authoring-self-docs.mjs +1 -0
  373. package/foundation/discovery/authoring-self-docs.test.mjs +6 -2
  374. package/foundation/discovery/cli-self-docs.mjs +16 -2
  375. package/foundation/discovery/cli-self-docs.test.mjs +20 -0
  376. package/foundation/discovery/docs-discovery.mjs +5 -1
  377. package/foundation/discovery/docs-discovery.test.mjs +21 -0
  378. package/foundation/discovery/docs-section-key.d.mts +1 -1
  379. package/foundation/discovery/docs-section-key.mjs +1 -1
  380. package/foundation/discovery/template-adapter.d.mts +14 -0
  381. package/foundation/discovery/template-adapter.fixture-refs.test.mjs +37 -1
  382. package/foundation/discovery/template-adapter.mjs +21 -1
  383. package/foundation/doc-compiler/doc-loads.test.mjs +5 -4
  384. package/foundation/doc-compiler/inputs.test.mjs +0 -1
  385. package/foundation/doc-compiler/tree.d.mts +4 -0
  386. package/foundation/doc-compiler/tree.mjs +6 -1
  387. package/foundation/doc-compiler/tree.test.mjs +65 -14
  388. package/foundation/integrations/cli-requirement.d.mts +75 -11
  389. package/foundation/integrations/cli-requirement.mjs +120 -23
  390. package/foundation/integrations/cli-requirement.test.mjs +141 -9
  391. package/foundation/integrations/contribution-inventory.mjs +1 -1
  392. package/foundation/integrations/integrations.d.mts +14 -1
  393. package/foundation/integrations/integrations.mjs +41 -1
  394. package/foundation/integrations/integrations.test.mjs +31 -0
  395. package/foundation/response/batch.type.d.mts +33 -0
  396. package/foundation/response/batch.type.mjs +34 -0
  397. package/foundation/response/error-codes.doc.mjs +6 -8
  398. package/foundation/response/error-codes.test.mjs +30 -5
  399. package/foundation/response/response-types.doc.d.mts +5 -4
  400. package/foundation/response/response-types.doc.mjs +49 -19
  401. package/foundation/response/response-types.doc.test.mjs +23 -0
  402. package/foundation/response/response.doc.mjs +11 -10
  403. package/package.json +9 -9
  404. package/assets/docs/tree/integrations.test.mjs +0 -62
  405. package/assets/docs/tree/writing-docs.doc.mjs +0 -286
@@ -243,7 +243,7 @@ describe('integration-contributed topics', () => {
243
243
  const english = await docs('theme', undefined, {full: true});
244
244
  const englishTitles = english.data.sections.map(section => section.title);
245
245
  expect(englishTitles).toEqual(
246
- expect.arrayContaining(['Quick Start', 'Theme Props']),
246
+ expect.arrayContaining(['Available Themes', 'Theme Props']),
247
247
  );
248
248
  scaffold({
249
249
  'theme-internal.doc.mjs': topic({
@@ -251,9 +251,9 @@ describe('integration-contributed topics', () => {
251
251
  extends: 'theme',
252
252
  sections: [
253
253
  {
254
- id: 'acme-quick-start',
255
- title: 'Quick Start',
256
- content: [{type: 'prose', text: 'Acme quick start.'}],
254
+ id: 'acme-available-themes',
255
+ title: 'Available Themes',
256
+ content: [{type: 'prose', text: 'Acme themes.'}],
257
257
  },
258
258
  {
259
259
  title: 'Theme Props',
@@ -271,7 +271,7 @@ describe('integration-contributed topics', () => {
271
271
  expect(extended.data.sections).toHaveLength(base.data.sections.length);
272
272
  expect(
273
273
  extended.data.sections.filter(
274
- section => section.id === 'acme-quick-start',
274
+ section => section.id === 'acme-available-themes',
275
275
  ),
276
276
  ).toHaveLength(1);
277
277
  expect(
@@ -305,6 +305,28 @@ describe('integration-contributed topics', () => {
305
305
  });
306
306
  }, SLOW);
307
307
 
308
+ it('ranks the topics that match every word of the query first', async () => {
309
+ // Dozens of CLI docs match `integration` alone, by name or in a code
310
+ // tick; the topics that hold both words, this one and the CLI's own
311
+ // troubleshooting guide, must still come first.
312
+ scaffold({
313
+ 'troubleshooting.doc.mjs': topic({
314
+ name: 'troubleshooting',
315
+ title: 'Troubleshooting',
316
+ description: 'What to check when an integration does not load.',
317
+ }),
318
+ });
319
+ const {data} = await search('troubleshoot integration', {cwd: tmpDir, type: 'doc'});
320
+ const top = data.results.slice(0, 2).map(result => result.name);
321
+ // This topic, and the CLI's own troubleshooting guide wherever the tree
322
+ // places it.
323
+ expect(top).toContain('troubleshooting');
324
+ expect(
325
+ top.some(name => /^cli\/integrations\/(?:.+\/)?troubleshooting$/.test(name)),
326
+ top.join(', '),
327
+ ).toBe(true);
328
+ }, SLOW);
329
+
308
330
  it("falls back to the CLI's own topics when the project config is unreadable", async () => {
309
331
  scaffold({'deploying.doc.mjs': topic()}, {config: 'export default {integrations: 42};\n'});
310
332
  const catalog = await loadDocsCatalog(tmpDir);
@@ -257,15 +257,20 @@ export type DoctorContext = {
257
257
  * could not be built, when it could not.
258
258
  */
259
259
  docsCatalogError?: string | null | undefined;
260
- /**
261
- * Combined project-level integration issues, including cross-package template replacement warnings.
262
- */
263
260
  integrationIssues?: {
264
261
  package: string;
265
262
  code: string;
266
263
  severity: "warning" | "error";
267
264
  message: string;
268
265
  }[] | null | undefined;
266
+ /**
267
+ * installed dependencies whose integration manifest could not be loaded
268
+ * Combined project-level integration issues, including cross-package template replacement warnings.
269
+ */
270
+ autolinkFailures?: {
271
+ spec: string;
272
+ error: string;
273
+ }[] | null | undefined;
269
274
  /**
270
275
  * - Error thrown while resolving the config
271
276
  * path (e.g. multiple config files present), surfaced by checkConfig as a FAIL.
@@ -13,14 +13,18 @@ export const doc = {
13
13
  name: 'doctor',
14
14
  namespace: 'cli/api',
15
15
  displayName: 'doctor()',
16
- summary: 'Read-only project + environment health check.',
16
+ summary:
17
+ "Check a project's Astryx setup and get a pass/warn/fail report per check. Use it as a CI gate or before debugging a broken install.",
17
18
  description:
18
- 'Runs a series of side-effect-free diagnostics: Node version, ' +
19
+ 'Runs a series of diagnostics: Node version, ' +
19
20
  '@astryxdesign/core install and version alignment with the CLI, installed ' +
20
21
  'themes and wiring, astryx.config validity, integrations linked from ' +
21
- 'package.json without a config entry, agent docs, core peer ' +
22
- 'dependencies, and the detected package manager, and returns a structured ' +
23
- 'report. It only reads (never installs, writes, or mutates), so it is safe ' +
22
+ 'package.json without a config entry, core peer dependencies, ' +
23
+ 'integration provider identity and contribution issues, agent docs, the ' +
24
+ 'detected package manager, and the health of the docs the CLI reads (authoring ' +
25
+ 'and CLI docs, the docs tree, doc size), and returns a structured ' +
26
+ 'report. It only reads (never installs, writes, or mutates), apart from ' +
27
+ "importing astryx.config, which runs that file's top-level code, so it is safe " +
24
28
  'as a CI gate and for agents to invoke.',
25
29
  importPath: '@astryxdesign/cli/api',
26
30
  signature: 'doctor(options?: DoctorOptions): Promise<DoctorResponse>',
@@ -29,18 +33,23 @@ export const doc = {
29
33
  {
30
34
  name: 'options.cwd',
31
35
  type: 'string',
32
- description: 'Directory to diagnose.',
36
+ description:
37
+ 'Directory to diagnose. A missing directory is not an error; it shows up in the checks (e.g. core-installed: fail).',
38
+ default: 'process.cwd()',
33
39
  },
34
40
  ],
35
41
  returns: [
36
42
  {
37
43
  type: 'doctor',
38
44
  description:
39
- 'The diagnostic report: `data.checks`, each with a stable id, label, `status` (`pass` | `warn` | `fail` | `info`), a one-line message, and a `fix` when the status is not `pass`; plus `data.summary` with counts per status.',
45
+ 'The diagnostic report: `data.checks`, each with a stable id, label, `status` (`pass` | `warn` | `fail` | `info`), a one-line message, and an optional `fix` (always present on `warn` and `fail`; some `info` checks carry one too); plus `data.summary` with counts per status.',
40
46
  },
41
47
  ],
42
48
  examples: [
43
- {label: 'Run diagnostics', code: 'const r = await doctor();'},
49
+ {
50
+ label: 'Fail a CI step on any failed check',
51
+ code: 'const r = await doctor();\nif (r.data.summary.fail > 0) process.exitCode = 1;',
52
+ },
44
53
  {
45
54
  label: 'Diagnose a directory',
46
55
  code: "await doctor({cwd: '/path/to/app'});",
@@ -26,7 +26,11 @@ import * as path from 'node:path';
26
26
  import {MIN_NODE_VERSION, isNodeVersionSupported} from '../../foundation/env/node-version.mjs';
27
27
  import {CLI_ROOT, findCoreDir, findInstalledPackage} from '../../foundation/fs/paths.mjs';
28
28
  import {explainPackageManager, getCliInvocation} from '../../foundation/env/package-manager.mjs';
29
- import {findConfigPath, Project} from '../../foundation/config/project.mjs';
29
+ import {
30
+ findConfigPath,
31
+ Project,
32
+ providerLedgerOf,
33
+ } from '../../foundation/config/project.mjs';
30
34
  import {DocsCatalog} from '../../foundation/discovery/docs-discovery.mjs';
31
35
  import {buildDocsIndexData} from '../../foundation/discovery/docs-section-key.mjs';
32
36
  import {
@@ -76,6 +80,8 @@ import {semverCompare, isValidSemver, satisfiesRange} from '../../foundation/env
76
80
  * @property {string|null} [docsCatalogError] - Why the project's docs catalog
77
81
  * could not be built, when it could not.
78
82
  * @property {Array<{package: string, code: string, severity: 'warning'|'error', message: string}>|null} [integrationIssues]
83
+ * @property {Array<{spec: string, error: string}>|null} [autolinkFailures]
84
+ * installed dependencies whose integration manifest could not be loaded
79
85
  * Combined project-level integration issues, including cross-package template replacement warnings.
80
86
  * @property {Error|null} [configError] - Error thrown while resolving the config
81
87
  * path (e.g. multiple config files present), surfaced by checkConfig as a FAIL.
@@ -417,6 +423,9 @@ export function checkImplicitIntegrations(ctx) {
417
423
  const implicit = ctx.integrations.filter(
418
424
  integration => integration.__autolinked,
419
425
  );
426
+ // A dependency whose manifest cannot be loaded leaves no loaded record, so
427
+ // without this line doctor would say no dependency ships a manifest at all.
428
+ const unreadable = describeUnreadableManifests(ctx.autolinkFailures ?? []);
420
429
 
421
430
  if (implicit.length === 0) {
422
431
  return {
@@ -424,9 +433,12 @@ export function checkImplicitIntegrations(ctx) {
424
433
  label,
425
434
  status: 'info',
426
435
  message:
427
- ctx.integrations.length > 0
436
+ (ctx.integrations.length > 0
428
437
  ? 'None — every loaded integration is named in astryx.config.'
429
- : 'None — no installed dependency ships an astryx.integration.* manifest.',
438
+ : unreadable
439
+ ? 'None loaded.'
440
+ : 'None — no installed dependency ships an astryx.integration.* manifest.') +
441
+ unreadable,
430
442
  };
431
443
  }
432
444
 
@@ -439,11 +451,25 @@ export function checkImplicitIntegrations(ctx) {
439
451
  integration.__spec && integration.__spec !== integration.name
440
452
  ? ` (declared as "${integration.__spec}")`
441
453
  : '';
442
- const roots = ['components', 'templates', 'themes', 'docs', 'codemods'].filter(
454
+ // A declared root counts only when it exists: the manifest's keys are a
455
+ // claim, and `integration-issues` reports the ones that are not true.
456
+ const declared = ['components', 'templates', 'themes', 'docs', 'codemods'].filter(
443
457
  root => integration[/** @type {'components'} */ (root)],
444
458
  );
459
+ const missing = declared.filter(root => {
460
+ const dir = integration[/** @type {'components'} */ (root)];
461
+ if (typeof dir !== 'string') return false;
462
+ const base =
463
+ typeof integration.__packageDir === 'string'
464
+ ? integration.__packageDir
465
+ : (ctx.cwd ?? process.cwd());
466
+ return !fs.existsSync(path.isAbsolute(dir) ? dir : path.resolve(base, dir));
467
+ });
468
+ const roots = declared.filter(root => !missing.includes(root));
445
469
  const contributes = roots.length > 0 ? roots.join(', ') : 'nothing';
446
- return `${integration.name}${version}${alias} from ${integration.__dependencyField}, contributing ${contributes}`;
470
+ const absent =
471
+ missing.length > 0 ? ` (declared ${missing.join(', ')} missing on disk)` : '';
472
+ return `${integration.name}${version}${alias} from ${integration.__dependencyField}, contributing ${contributes}${absent}`;
447
473
  });
448
474
 
449
475
  const plural = implicit.length === 1 ? '' : 's';
@@ -453,7 +479,8 @@ export function checkImplicitIntegrations(ctx) {
453
479
  status: 'info',
454
480
  message:
455
481
  `${implicit.length} integration${plural} loaded from installed ` +
456
- `dependencies with no astryx.config entry: ${described.join('; ')}.`,
482
+ `dependencies with no astryx.config entry: ${described.join('; ')}.` +
483
+ unreadable,
457
484
  fix:
458
485
  'Nothing to fix. Keep these dependencies installed. The CLI links them ' +
459
486
  'from package.json, so an unused-dependency check that looks only for ' +
@@ -462,6 +489,30 @@ export function checkImplicitIntegrations(ctx) {
462
489
  };
463
490
  }
464
491
 
492
+ /**
493
+ * The sentence `implicit-integrations` adds for dependencies whose manifest
494
+ * could not be loaded, or '' when there are none. Still informational: the
495
+ * package is a dependency's own bug, which `doctor integration validate`
496
+ * diagnoses, but doctor must not report it as absent.
497
+ * @param {Array<{spec: string, error: string}>} failures
498
+ * @returns {string}
499
+ */
500
+ function describeUnreadableManifests(failures) {
501
+ if (failures.length === 0) return '';
502
+ const one = failures.length === 1;
503
+ const listed = failures
504
+ .map(({spec, error}) => {
505
+ const reason = String(error).split('\n')[0].slice(0, 160);
506
+ return `${spec} (${reason})`;
507
+ })
508
+ .join('; ');
509
+ return (
510
+ ` ${failures.length} installed ${one ? 'dependency ships' : 'dependencies ship'} ` +
511
+ `an astryx.integration.* manifest that could not be loaded, so ${one ? 'it contributes' : 'they contribute'} ` +
512
+ `nothing: ${listed}. Run \`astryx doctor integration validate <package>\` for details.`
513
+ );
514
+ }
515
+
465
516
  /**
466
517
  * Check 7 — agent docs exist and contain the Astryx section markers.
467
518
  * @param {DoctorContext} ctx
@@ -738,12 +789,26 @@ export function checkProviderIdentity(ctx) {
738
789
  integration =>
739
790
  integration.providerId != null && integration.__loadError == null,
740
791
  ).length;
792
+ // An integration that could not be read has no provider ID to check, so the
793
+ // count above is not a complete survey. Say so instead of counting silently.
794
+ const unread = ctx.integrations.filter(
795
+ integration => integration.__loadError != null,
796
+ ).length;
797
+ const unreadNote =
798
+ unread === 0
799
+ ? ''
800
+ : unread === 1
801
+ ? ' 1 loaded integration could not be read, so its provider ID is unknown.'
802
+ : ` ${unread} loaded integrations could not be read, so their provider IDs are unknown.`;
741
803
  if (count === 0) {
742
804
  return {
743
805
  id,
744
806
  label,
745
807
  status: 'info',
746
- message: 'None — no loaded integration has a provider identity.',
808
+ message:
809
+ (unread === 0
810
+ ? 'None — no loaded integration has a provider identity.'
811
+ : 'No readable integration has a provider identity.') + unreadNote,
747
812
  };
748
813
  }
749
814
  return {
@@ -751,9 +816,10 @@ export function checkProviderIdentity(ctx) {
751
816
  label,
752
817
  status: 'pass',
753
818
  message:
754
- count === 1
819
+ (count === 1
755
820
  ? '1 loaded integration has its own provider ID.'
756
- : `${count} loaded integrations each have their own provider ID.`,
821
+ : `${count} loaded integrations each have their own provider ID.`) +
822
+ unreadNote,
757
823
  };
758
824
  }
759
825
 
@@ -1198,11 +1264,25 @@ export async function runChecks(options = {}) {
1198
1264
  let docsCatalogError = null;
1199
1265
  /** @type {Array<{package: string, code: string, severity: 'warning'|'error', message: string}>|null} */
1200
1266
  let integrationIssues = null;
1267
+ /** @type {Array<{spec: string, error: string}>|null} */
1268
+ let autolinkFailures = null;
1201
1269
  try {
1202
1270
  const project = await Project.load(cwd);
1203
1271
  configTheme =
1204
1272
  /** @type {{theme?: string}} */ (project.config ?? {}).theme ?? null;
1205
1273
  integrations = project.loadedIntegrations;
1274
+ // An installed dependency whose manifest cannot be loaded is kept out of
1275
+ // loadedIntegrations on purpose. The provider ledger still records it.
1276
+ autolinkFailures = [...providerLedgerOf(project).values()]
1277
+ .filter(
1278
+ entry =>
1279
+ entry.outcome === 'load-failed' &&
1280
+ entry.candidate.source === 'autolinked',
1281
+ )
1282
+ .map(entry => ({
1283
+ spec: entry.candidate.spec ?? entry.label,
1284
+ error: entry.error ?? 'its manifest could not be loaded',
1285
+ }));
1206
1286
  try {
1207
1287
  docsCatalog = await project.docs();
1208
1288
  docsCatalogIssues = (await project.issues()).filter(
@@ -1229,6 +1309,7 @@ export async function runChecks(options = {}) {
1229
1309
  docsCatalogIssues,
1230
1310
  docsCatalogError,
1231
1311
  integrationIssues,
1312
+ autolinkFailures,
1232
1313
  configError,
1233
1314
  };
1234
1315
 
@@ -354,16 +354,31 @@ describe('checkImplicitIntegrations', () => {
354
354
  });
355
355
 
356
356
  it('names the package, the field, and what it contributes', () => {
357
- const c = checkImplicitIntegrations({
358
- integrations: [
359
- autolinked({templates: '/abs/templates', themes: '/abs/themes'}),
360
- ],
361
- });
362
- expect(c.message).toContain('@acme/widgets@1.0.0');
363
- expect(c.message).toContain('from dependencies');
364
- expect(c.message).toContain(
365
- 'contributing components, templates, themes',
366
- );
357
+ // Roots count only when they exist, so this one gives them real folders.
358
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-implicit-'));
359
+ try {
360
+ const dir = name => {
361
+ fs.mkdirSync(path.join(root, name));
362
+ return path.join(root, name);
363
+ };
364
+ const c = checkImplicitIntegrations({
365
+ integrations: [
366
+ autolinked({
367
+ components: dir('components'),
368
+ templates: dir('templates'),
369
+ themes: dir('themes'),
370
+ }),
371
+ ],
372
+ });
373
+ expect(c.message).toContain('@acme/widgets@1.0.0');
374
+ expect(c.message).toContain('from dependencies');
375
+ expect(c.message).toContain(
376
+ 'contributing components, templates, themes',
377
+ );
378
+ expect(c.message).not.toContain('missing on disk');
379
+ } finally {
380
+ fs.rmSync(root, {recursive: true, force: true});
381
+ }
367
382
  });
368
383
 
369
384
  it('names the declared key too when an npm alias makes them differ', () => {
@@ -906,3 +921,100 @@ describe('checkDocsProgressiveDisclosure languages', () => {
906
921
  expect(c.message).not.toMatch(/deploying overview:/);
907
922
  });
908
923
  });
924
+
925
+ describe('doctor says what it could not check', () => {
926
+ const CORE = {
927
+ 'node_modules/@astryxdesign/core/package.json': JSON.stringify({
928
+ name: '@astryxdesign/core',
929
+ version: '0.6.3',
930
+ }),
931
+ };
932
+
933
+ /** Installed dependency whose manifest declares roots that do not exist. */
934
+ const dangling = {
935
+ 'node_modules/@acme/dangling/package.json': JSON.stringify({
936
+ name: '@acme/dangling',
937
+ version: '2.0.0',
938
+ }),
939
+ 'node_modules/@acme/dangling/astryx.integration.mjs':
940
+ "export default {providerId: 'acme-dangling', components: './components', templates: './templates', docs: './docs'};\n",
941
+ };
942
+
943
+ /** Installed dependency whose manifest cannot be parsed at all. */
944
+ const broken = {
945
+ 'node_modules/@acme/broken/package.json': JSON.stringify({
946
+ name: '@acme/broken',
947
+ version: '1.0.0',
948
+ }),
949
+ 'node_modules/@acme/broken/astryx.integration.mjs':
950
+ 'export default { this is not valid javascript ((\n',
951
+ };
952
+
953
+ /** @param {Record<string, string>} extra @param {string[]} deps */
954
+ const project = (extra, deps) =>
955
+ mkProject({
956
+ 'package.json': JSON.stringify({
957
+ name: 'consumer',
958
+ version: '1.0.0',
959
+ dependencies: Object.fromEntries(
960
+ ['@astryxdesign/core', ...deps].map(d => [d, '1.0.0']),
961
+ ),
962
+ }),
963
+ ...CORE,
964
+ ...extra,
965
+ });
966
+
967
+ // An unparseable manifest is kept out of the loaded set on purpose, and
968
+ // doctor used to report that no installed dependency ships a manifest.
969
+ it('names an installed dependency whose manifest cannot be loaded', async () => {
970
+ const report = (await doctor({cwd: project(broken, ['@acme/broken'])})).data;
971
+ const check = report.checks.find(c => c.id === 'implicit-integrations');
972
+
973
+ expect(check.status).toBe('info');
974
+ expect(check.message).toContain('@acme/broken');
975
+ expect(check.message).toContain('could not be loaded');
976
+ expect(check.message).not.toContain('no installed dependency ships');
977
+ expect(report.summary.fail).toBe(0);
978
+ }, SLOW);
979
+
980
+ it('does not claim contributions from roots that are missing on disk', async () => {
981
+ const report = (await doctor({cwd: project(dangling, ['@acme/dangling'])}))
982
+ .data;
983
+ const implicit = report.checks.find(c => c.id === 'implicit-integrations');
984
+ const issues = report.checks.find(c => c.id === 'integration-issues');
985
+
986
+ expect(implicit.message).toContain('contributing nothing');
987
+ expect(implicit.message).toContain('missing on disk');
988
+ expect(implicit.message).not.toContain('contributing components');
989
+ expect(issues.status).toBe('warn');
990
+ expect(issues.message).toContain('@acme/dangling');
991
+ }, SLOW);
992
+
993
+ it('still says no dependency ships a manifest when none does', async () => {
994
+ const report = (await doctor({cwd: project({}, [])})).data;
995
+ const check = report.checks.find(c => c.id === 'implicit-integrations');
996
+
997
+ expect(check.message).toBe(
998
+ 'None — no installed dependency ships an astryx.integration.* manifest.',
999
+ );
1000
+ }, SLOW);
1001
+
1002
+ it('says how many integrations could not be read, rather than counting silently', () => {
1003
+ /** @type {any} */
1004
+ const ctx = {
1005
+ cwd: '/x',
1006
+ nodeVersion: process.versions.node,
1007
+ coreDir: null,
1008
+ configPath: null,
1009
+ configTheme: null,
1010
+ integrations: [
1011
+ {name: '@acme/ok', __spec: '@acme/ok', providerId: 'ok'},
1012
+ {name: '@acme/bad', __spec: '@acme/bad', __loadError: 'boom'},
1013
+ ],
1014
+ };
1015
+ const check = checkProviderIdentity(ctx);
1016
+
1017
+ expect(check.message).toContain('1 loaded integration has its own provider ID.');
1018
+ expect(check.message).toContain('could not be read');
1019
+ });
1020
+ });
@@ -23,7 +23,7 @@ export type DoctorCheck = {
23
23
  */
24
24
  message: string;
25
25
  /**
26
- * - Actionable remediation, present when status is not 'pass'.
26
+ * - Actionable remediation: always present on 'warn' and 'fail'; some 'info' checks carry one too.
27
27
  */
28
28
  fix?: string | undefined;
29
29
  };
@@ -19,7 +19,7 @@
19
19
  * @property {string} label - Human-readable check name.
20
20
  * @property {DoctorStatus} status
21
21
  * @property {string} message - One-line result summary.
22
- * @property {string} [fix] - Actionable remediation, present when status is not 'pass'.
22
+ * @property {string} [fix] - Actionable remediation: always present on 'warn' and 'fail'; some 'info' checks carry one too.
23
23
  */
24
24
 
25
25
  /**
package/api/error.d.mts CHANGED
@@ -1,6 +1,28 @@
1
1
  // @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
2
2
  // DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
3
3
 
4
+ /**
5
+ * The error for a write that failed on the filesystem: no permission, a
6
+ * read-only mount, a full disk, a path component that is not a directory.
7
+ *
8
+ * A raw Node errno error reached the envelope as
9
+ * `{"error": "EACCES: permission denied, open '/home/you/p/readonly/x.tsx'",
10
+ * "code": "ERR_UNKNOWN"}` — the wrong code (ERR_WRITE_FAILED is in the frozen
11
+ * registry for exactly this) and an absolute host path in the message. This
12
+ * keeps the errno, which is the part that tells you what to fix, and reports
13
+ * the target the way every other Astryx message does: relative to the project.
14
+ *
15
+ * A caller that wrote files before the failure must undo them first and pass
16
+ * the ones it could not restore as `unrestored`, so the message never says
17
+ * nothing was written when something was.
18
+ *
19
+ * @param {string} target absolute path the write was aimed at
20
+ * @param {string} cwd project root, for the relative form
21
+ * @param {unknown} cause the error the filesystem call threw
22
+ * @param {string[]} [unrestored] absolute paths an undo could not put back
23
+ * @returns {AstryxError}
24
+ */
25
+ export function writeFailed(target: string, cwd: string, cause: unknown, unrestored?: string[]): AstryxError;
4
26
  export class AstryxError extends Error {
5
27
  /**
6
28
  * @param {string} message
package/api/error.mjs CHANGED
@@ -12,6 +12,7 @@
12
12
  */
13
13
 
14
14
  import {ERROR_CODES} from '../foundation/response/error-codes.mjs';
15
+ import * as path from 'node:path';
15
16
 
16
17
  export class AstryxError extends Error {
17
18
  /** @type {import('../foundation/response/base').Suggestion[] | undefined} */
@@ -36,3 +37,44 @@ export class AstryxError extends Error {
36
37
  if (Array.isArray(suggestions) && suggestions.length) this.suggestions = suggestions;
37
38
  }
38
39
  }
40
+
41
+ /**
42
+ * The error for a write that failed on the filesystem: no permission, a
43
+ * read-only mount, a full disk, a path component that is not a directory.
44
+ *
45
+ * A raw Node errno error reached the envelope as
46
+ * `{"error": "EACCES: permission denied, open '/home/you/p/readonly/x.tsx'",
47
+ * "code": "ERR_UNKNOWN"}` — the wrong code (ERR_WRITE_FAILED is in the frozen
48
+ * registry for exactly this) and an absolute host path in the message. This
49
+ * keeps the errno, which is the part that tells you what to fix, and reports
50
+ * the target the way every other Astryx message does: relative to the project.
51
+ *
52
+ * A caller that wrote files before the failure must undo them first and pass
53
+ * the ones it could not restore as `unrestored`, so the message never says
54
+ * nothing was written when something was.
55
+ *
56
+ * @param {string} target absolute path the write was aimed at
57
+ * @param {string} cwd project root, for the relative form
58
+ * @param {unknown} cause the error the filesystem call threw
59
+ * @param {string[]} [unrestored] absolute paths an undo could not put back
60
+ * @returns {AstryxError}
61
+ */
62
+ export function writeFailed(target, cwd, cause, unrestored = []) {
63
+ const rel = path.relative(cwd, target) || target;
64
+ const errno =
65
+ typeof (/** @type {any} */ (cause)?.code) === 'string'
66
+ ? /** @type {any} */ (cause).code
67
+ : null;
68
+ const why = errno === 'EACCES' || errno === 'EPERM' ? ' (no permission)' : '';
69
+ const outcome =
70
+ unrestored.length === 0
71
+ ? 'Nothing was written.'
72
+ : `Earlier writes could not all be undone: ${unrestored
73
+ .map(file => path.relative(cwd, file) || file)
74
+ .join(', ')}.`;
75
+ return new AstryxError(
76
+ `Could not write ${rel}${errno ? `: ${errno}` : ''}${why}. ${outcome}`,
77
+ undefined,
78
+ ERROR_CODES.ERR_WRITE_FAILED,
79
+ );
80
+ }
@@ -12,12 +12,16 @@ export const doc = {
12
12
  name: 'gapReport',
13
13
  namespace: 'cli/api',
14
14
  displayName: 'gapReport()',
15
- summary: 'Route a design-system gap through the fan-out handler composition.',
15
+ summary:
16
+ 'Report a missing or hard-to-use design-system capability to the package that owns it.',
16
17
  description:
17
- 'Creates a normalized gap report and fans it out to every effective handler: the project config handler first, then each loaded integration handler in config order, deduplicated by handle function identity. Each handler receives a structuredClone of the report and an AbortSignal, then runs in its own worker with a 30 s timeout and stdout redirected to stderr. A timed-out worker is terminated before the next handler starts, so process.exit, process.exitCode, and late continuations cannot affect the CLI process. A public handler requires confirmPublic per handler; internal handlers always run. When no handlers exist, a built-in GitHub/routed-only fallback runs. The aggregate response carries ordered deliveries with per-handler outcomes.',
18
+ 'Sends a gap report to every configured handler: the project config handler first, then each integration handler in config order. ' +
19
+ 'Each handler has 30 s to finish, and its output goes to stderr. Public handlers run only with confirmPublic; internal handlers always run. ' +
20
+ "With no handler, it files a GitHub issue for the owning package only with confirmPublic (without it nothing is sent), or returns the package's issues URL when that is not on GitHub. " +
21
+ 'The report records whether an agent or a person ran it, and the response lists each handler outcome in order.',
18
22
  importPath: '@astryxdesign/cli/api',
19
23
  signature:
20
- 'gapReport(component?: string, options?: GapReportOptions): Promise<GapReportCategoriesResponse | GapReportReceiptResponse>',
24
+ 'gapReport(component: string | undefined, options?: GapReportOptions): Promise<GapReportCategoriesResponse | GapReportReceiptResponse>',
21
25
  keywords: [
22
26
  'gap',
23
27
  'report',
@@ -56,13 +60,13 @@ export const doc = {
56
60
  name: 'options.package',
57
61
  type: 'string',
58
62
  description:
59
- 'Explicit owning package when automatic routing is ambiguous.',
63
+ 'Package that owns the gap: @astryxdesign/core, or a loaded integration by package name or config entry. Overrides automatic owner routing; required when more than one package provides the component.',
60
64
  },
61
65
  {
62
66
  name: 'options.confirmPublic',
63
67
  type: 'boolean',
64
68
  description:
65
- 'Explicitly consent to invoking public handlers or creating a GitHub issue.',
69
+ 'Allow public delivery: public handlers run, and with no handler a GitHub issue is filed through the gh CLI.',
66
70
  default: 'false',
67
71
  },
68
72
  {
@@ -77,6 +81,7 @@ export const doc = {
77
81
  type: 'string',
78
82
  description:
79
83
  'Directory used to load project config and component ownership.',
84
+ default: 'process.cwd()',
80
85
  },
81
86
  ],
82
87
  returns: [
@@ -87,17 +92,21 @@ export const doc = {
87
92
  {
88
93
  type: 'gap-report.file',
89
94
  description:
90
- 'An aggregate receipt with per-handler deliveries, filedCount/routedOnlyCount totals, and overall status.',
95
+ 'Receipt: status (filed, partial, failed, routed_only, consent_required, skipped), package, issuesUrl, ordered deliveries (handlerType, handler, audience, status, url, message), filedCount, and routedOnlyCount.',
91
96
  },
92
97
  ],
93
98
  throws: [
99
+ {
100
+ code: 'ERR_MISSING_ARGUMENT',
101
+ when: 'component, category, or reason is missing, blank, or not a string (unless listCategories is true)',
102
+ },
94
103
  {
95
104
  code: 'ERR_UNKNOWN_CATEGORY',
96
105
  when: 'category is not one of the fixed gap-report values',
97
106
  },
98
107
  {
99
108
  code: 'ERR_INVALID_ARGUMENT',
100
- when: 'a field value is invalid',
109
+ when: 'component is over 120 characters, category is over 80, reason is over 2000, or detail is not a string or is over 8000 characters',
101
110
  },
102
111
  {
103
112
  code: 'ERR_AMBIGUOUS_COMPONENT',
@@ -109,7 +118,7 @@ export const doc = {
109
118
  },
110
119
  {
111
120
  code: 'ERR_NOT_FOUND',
112
- when: 'no handler and no issues URL available',
121
+ when: 'no report handler is configured and the owning package has no issues URL',
113
122
  },
114
123
  ],
115
124
  examples: [
@@ -122,8 +131,8 @@ export const doc = {
122
131
  code: "const receipt = await gapReport('Button', {category: 'missing_variant', reason: 'Need a compact size'});",
123
132
  },
124
133
  {
125
- label: 'Confirm public filing',
126
- code: "await gapReport('Button', {category: 'docs_gap', reason: 'Missing keyboard example', confirmPublic: true});",
134
+ label: 'Name the owning package',
135
+ code: "const receipt = await gapReport('Button', {category: 'docs_gap', reason: 'Missing keyboard example', package: '@astryxdesign/core'});",
127
136
  },
128
137
  ],
129
138
  command: 'gap-report',