@astryxdesign/cli 0.6.4-canary.f0355e3 → 0.6.4

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 (331) hide show
  1. package/README.md +96 -99
  2. package/api/build/build.doc.mjs +1 -6
  3. package/api/build/build.test.mjs +0 -22
  4. package/api/build/kit/kit.mjs +5 -44
  5. package/api/component/_adapter.d.mts +0 -25
  6. package/api/component/_adapter.mjs +5 -59
  7. package/api/component/component.d.mts +3 -6
  8. package/api/component/component.doc.mjs +17 -37
  9. package/api/component/component.mjs +9 -249
  10. package/api/component/component.type.d.mts +0 -25
  11. package/api/component/component.type.mjs +0 -44
  12. package/api/discover/_adapter.d.mts +6 -114
  13. package/api/discover/_adapter.mjs +17 -372
  14. package/api/discover/detail/detail.d.mts +6 -18
  15. package/api/discover/detail/detail.mjs +13 -67
  16. package/api/discover/detail/detail.test.mjs +0 -85
  17. package/api/discover/discover.d.mts +9 -3
  18. package/api/discover/discover.doc.mjs +18 -61
  19. package/api/discover/discover.mjs +36 -220
  20. package/api/discover/discover.test.mjs +2 -11
  21. package/api/discover/discover.type.d.mts +8 -147
  22. package/api/discover/discover.type.mjs +12 -102
  23. package/api/discover/list/list.d.mts +6 -20
  24. package/api/discover/list/list.mjs +12 -45
  25. package/api/discover/list/list.test.mjs +0 -46
  26. package/api/discover/search/search.d.mts +16 -18
  27. package/api/discover/search/search.mjs +56 -102
  28. package/api/discover/search/search.test.mjs +10 -144
  29. package/api/docs/_adapter.d.mts +3 -8
  30. package/api/docs/_adapter.mjs +6 -14
  31. package/api/docs/docOverlays.test.mjs +1 -27
  32. package/api/docs/docs.doc.mjs +2 -2
  33. package/api/docs/docs.test.mjs +243 -0
  34. package/api/docs/integration-tree.test.mjs +555 -0
  35. package/api/docs/integrationDocs.test.mjs +314 -0
  36. package/api/doctor/doctor.d.mts +3 -8
  37. package/api/doctor/doctor.doc.mjs +8 -17
  38. package/api/doctor/doctor.mjs +9 -90
  39. package/api/doctor/doctor.test.mjs +10 -122
  40. package/api/doctor/doctor.type.d.mts +1 -1
  41. package/api/doctor/doctor.type.mjs +1 -1
  42. package/api/gap-report/gap-report.doc.mjs +10 -19
  43. package/api/hook/hook.doc.mjs +3 -6
  44. package/api/index.d.mts +2 -1
  45. package/api/index.mjs +5 -5
  46. package/api/init/init.doc.mjs +12 -17
  47. package/api/integration/add-helpers.d.mts +2 -5
  48. package/api/integration/add-helpers.mjs +9 -36
  49. package/api/integration/add-theme.mjs +1 -22
  50. package/api/integration/add-theme.test.mjs +0 -34
  51. package/api/integration/authoring-checks.mjs +2 -2
  52. package/api/integration/integrationPackCheck.doc.mjs +3 -3
  53. package/api/integration/pack-check.mjs +9 -82
  54. package/api/integration/pack-check.test.mjs +0 -90
  55. package/api/integration/pack-check.type.mjs +1 -1
  56. package/api/json/assertResponse.doc.mjs +1 -1
  57. package/api/json/index.ts +1 -0
  58. package/api/json/isError.doc.mjs +1 -1
  59. package/api/layout/_adapter.d.mts +34 -0
  60. package/api/layout/_adapter.mjs +148 -0
  61. package/api/layout/check/check.d.mts +16 -0
  62. package/api/layout/check/check.mjs +40 -0
  63. package/api/layout/expand/expand.d.mts +22 -0
  64. package/api/layout/expand/expand.mjs +155 -0
  65. package/api/layout/expand/expand.path-safety.test.mjs +53 -0
  66. package/api/layout/grammar/grammar.d.mts +13 -0
  67. package/api/layout/grammar/grammar.mjs +87 -0
  68. package/api/layout/layout.d.mts +6 -0
  69. package/api/layout/layout.mjs +17 -0
  70. package/api/layout/layout.test.mjs +297 -0
  71. package/api/layout/layout.type.d.mts +89 -0
  72. package/api/layout/layout.type.mjs +103 -0
  73. package/api/layout/layoutCheck.doc.d.mts +11 -0
  74. package/api/layout/layoutCheck.doc.mjs +85 -0
  75. package/api/layout/layoutExpand.doc.d.mts +11 -0
  76. package/api/layout/layoutExpand.doc.mjs +107 -0
  77. package/api/layout/layoutGrammar.doc.d.mts +11 -0
  78. package/api/layout/layoutGrammar.doc.mjs +57 -0
  79. package/api/search/search.d.mts +1 -27
  80. package/api/search/search.doc.mjs +2 -2
  81. package/api/search/search.mjs +16 -228
  82. package/api/search/search.test.mjs +512 -0
  83. package/api/swizzle/swizzle.doc.mjs +5 -7
  84. package/api/template/copy/copy.mjs +1 -1
  85. package/api/template/copy/copy.test.mjs +0 -9
  86. package/api/template/template-integration.test.mjs +65 -1
  87. package/api/template/template.doc.mjs +1 -2
  88. package/api/template/template.mjs +1 -1
  89. package/api/theme/add/add.mjs +25 -17
  90. package/api/theme/add/add.staging.test.mjs +23 -40
  91. package/api/theme/build/build.family.test.mjs +12 -7
  92. package/api/theme/build/build.mjs +18 -8
  93. package/api/theme/generateTonalPalette.doc.mjs +2 -1
  94. package/api/theme/listThemes.doc.mjs +1 -1
  95. package/api/theme/themeAdd.doc.mjs +10 -9
  96. package/api/theme/themeBuild.doc.mjs +13 -13
  97. package/api/theme/themeList.doc.mjs +1 -1
  98. package/api/theme/themeListAvailable.doc.mjs +1 -2
  99. package/api/theme/themePaletteGenerate.doc.mjs +8 -15
  100. package/api/theme/themeTargets.doc.mjs +2 -3
  101. package/api/theme/themeTemplate.doc.mjs +1 -2
  102. package/api/upgrade/run/run.mjs +4 -6
  103. package/api/upgrade/upgrade.doc.mjs +22 -24
  104. package/api/upgrade/upgrade.type.mjs +2 -2
  105. package/assets/codemods/__tests__/runner.test.mjs +1 -3
  106. package/assets/codemods/integration-runner.mjs +3 -3
  107. package/assets/codemods/runner.mjs +4 -5
  108. package/assets/docs/README.md +2 -4
  109. package/assets/docs/browser-support.doc.mjs +11 -11
  110. package/assets/docs/color.doc.mjs +2 -8
  111. package/assets/docs/elevation.doc.mjs +4 -6
  112. package/assets/docs/getting-started.doc.mjs +16 -5
  113. package/assets/docs/icons.doc.mjs +21 -2
  114. package/assets/docs/illustrations.doc.mjs +15 -7
  115. package/assets/docs/internationalization.doc.mjs +5 -7
  116. package/assets/docs/layout.doc.dense.mjs +82 -130
  117. package/assets/docs/layout.doc.mjs +77 -133
  118. package/assets/docs/migration.doc.mjs +21 -19
  119. package/assets/docs/motion.doc.mjs +3 -16
  120. package/assets/docs/principles.doc.dense.mjs +5 -5
  121. package/assets/docs/principles.doc.mjs +0 -8
  122. package/assets/docs/principles.doc.zh.mjs +6 -6
  123. package/assets/docs/shape.doc.mjs +3 -8
  124. package/assets/docs/spacing.doc.mjs +2 -7
  125. package/assets/docs/styling-libraries.doc.mjs +2 -6
  126. package/assets/docs/styling.doc.mjs +23 -19
  127. package/assets/docs/theme.doc.dense.mjs +18 -58
  128. package/assets/docs/theme.doc.mjs +46 -56
  129. package/assets/docs/theme.doc.zh.mjs +8 -9
  130. package/assets/docs/tokens.doc.dense.mjs +2 -2
  131. package/assets/docs/tokens.doc.mjs +8 -389
  132. package/assets/docs/tokens.doc.zh.mjs +2 -2
  133. package/assets/docs/tree/integrations.doc.mjs +451 -25
  134. package/assets/docs/tree/integrations.test.mjs +62 -0
  135. package/assets/docs/tree/writing-docs.doc.mjs +286 -0
  136. package/assets/docs/typography.doc.mjs +4 -24
  137. package/assets/docs/working-with-ai.doc.mjs +22 -30
  138. package/assets/templates/blocks/components/InternationalizationProvider/InternationalizationProvider01ShippedLocale.tsx +1 -1
  139. package/authoring/config/config.doc.mjs +2 -10
  140. package/authoring/config/parse.d.mts +0 -2
  141. package/authoring/config/parse.mjs +0 -19
  142. package/authoring/config/parse.test.mjs +0 -8
  143. package/authoring/config/type.ts +2 -13
  144. package/authoring/doctypes/_schema.d.mts +2 -3
  145. package/authoring/doctypes/_schema.mjs +0 -6
  146. package/authoring/doctypes/base/graph-fields.doc.mjs +3 -3
  147. package/authoring/doctypes/base/type.ts +2 -4
  148. package/authoring/doctypes/command/command.doc.mjs +1 -1
  149. package/authoring/doctypes/command/type.ts +1 -1
  150. package/authoring/doctypes/component/component.doc.mjs +0 -6
  151. package/authoring/doctypes/component/type.ts +0 -8
  152. package/authoring/doctypes/reference/reference.doc.mjs +0 -7
  153. package/authoring/doctypes/reference/type.ts +0 -5
  154. package/authoring/doctypes/schema/schema.doc.mjs +2 -2
  155. package/authoring/doctypes/template/template.doc.mjs +1 -1
  156. package/authoring/doctypes/template/type.ts +2 -2
  157. package/authoring/index.d.mts +0 -1
  158. package/authoring/index.d.ts +0 -10
  159. package/authoring/index.mjs +0 -1
  160. package/authoring/integration/integration.doc.mjs +10 -12
  161. package/clients/cli/command-result-coverage.test.mjs +7 -7
  162. package/clients/cli/commands/component/index.mjs +55 -152
  163. package/clients/cli/commands/component-ownership.test.mjs +0 -89
  164. package/clients/cli/commands/component.doc.mjs +9 -27
  165. package/clients/cli/commands/debug-result-summary.test.mjs +2 -2
  166. package/clients/cli/commands/discover.doc.mjs +9 -53
  167. package/clients/cli/commands/discover.mjs +118 -393
  168. package/clients/cli/commands/docs.doc.mjs +1 -1
  169. package/clients/cli/commands/docs.mjs +17 -60
  170. package/clients/cli/commands/docs.test.mjs +294 -0
  171. package/clients/cli/commands/doctor-integration-docs.doc.mjs +2 -3
  172. package/clients/cli/commands/doctor-integration.test.mjs +0 -53
  173. package/clients/cli/commands/doctor.doc.mjs +1 -3
  174. package/clients/cli/commands/doctor.mjs +5 -49
  175. package/clients/cli/commands/gap-report.doc.mjs +9 -10
  176. package/clients/cli/commands/init.doc.mjs +6 -9
  177. package/clients/cli/commands/integration-add.doc.mjs +9 -9
  178. package/clients/cli/commands/integration-authoring.test.mjs +10 -61
  179. package/clients/cli/commands/integration-pack.doc.mjs +9 -5
  180. package/clients/cli/commands/integration-real-world.test.mjs +1 -1
  181. package/clients/cli/commands/integration.doc.mjs +4 -4
  182. package/clients/cli/commands/integration.mjs +43 -74
  183. package/clients/cli/commands/layout-check.doc.mjs +65 -0
  184. package/clients/cli/commands/layout-expand.doc.mjs +83 -0
  185. package/clients/cli/commands/layout-grammar.doc.mjs +30 -0
  186. package/clients/cli/commands/layout.doc.mjs +34 -0
  187. package/clients/cli/commands/layout.error-codes.test.mjs +66 -0
  188. package/clients/cli/commands/layout.exit-parity.test.mjs +41 -0
  189. package/clients/cli/commands/layout.mjs +275 -0
  190. package/clients/cli/commands/layout.path-help.test.mjs +33 -0
  191. package/clients/cli/commands/layout.stdin-cap.test.mjs +47 -0
  192. package/clients/cli/commands/layout.text-fields.test.mjs +39 -0
  193. package/clients/cli/commands/manifest.doc.mjs +1 -1
  194. package/clients/cli/commands/search.doc.mjs +3 -10
  195. package/clients/cli/commands/search.mjs +2 -21
  196. package/clients/cli/commands/search.test.mjs +4 -21
  197. package/clients/cli/commands/swizzle.doc.mjs +1 -1
  198. package/clients/cli/commands/template.doc.mjs +1 -1
  199. package/clients/cli/commands/text-json-parity.test.mjs +16 -5
  200. package/clients/cli/commands/theme-add.doc.mjs +1 -1
  201. package/clients/cli/commands/theme-palette-generate.doc.mjs +2 -3
  202. package/clients/cli/commands/theme-palette.doc.mjs +2 -1
  203. package/clients/cli/commands/theme-targets.doc.mjs +2 -2
  204. package/clients/cli/commands/theme.doc.mjs +1 -2
  205. package/clients/cli/commands/upgrade.doc.mjs +3 -62
  206. package/clients/cli/index.mjs +10 -28
  207. package/clients/cli/lib/define-command.mjs +4 -28
  208. package/clients/cli/lib/define-command.test.mjs +0 -54
  209. package/clients/cli/lib/exit-codes.test.mjs +9 -18
  210. package/clients/cli/lib/json-shim.mjs +14 -24
  211. package/clients/cli/lib/json-shim.test.mjs +20 -6
  212. package/clients/cli/lib/manifest.mjs +13 -18
  213. package/clients/cli/lib/manifest.test.mjs +2 -5
  214. package/foundation/agent-docs/agent-docs.mjs +1 -1
  215. package/foundation/agent-docs/agent-docs.test.mjs +1159 -0
  216. package/foundation/discovery/authoring-self-docs.mjs +0 -1
  217. package/foundation/discovery/authoring-self-docs.test.mjs +2 -6
  218. package/foundation/discovery/cli-self-docs.mjs +2 -16
  219. package/foundation/discovery/cli-self-docs.test.mjs +0 -20
  220. package/foundation/discovery/docs-discovery.mjs +1 -5
  221. package/foundation/discovery/docs-discovery.test.mjs +0 -21
  222. package/foundation/discovery/docs-section-key.d.mts +1 -1
  223. package/foundation/discovery/docs-section-key.mjs +1 -1
  224. package/foundation/discovery/template-adapter.mjs +1 -1
  225. package/foundation/doc-compiler/doc-loads.test.mjs +14 -3
  226. package/foundation/doc-compiler/tree.d.mts +0 -4
  227. package/foundation/doc-compiler/tree.mjs +1 -6
  228. package/foundation/doc-compiler/tree.test.mjs +598 -0
  229. package/foundation/integrations/cli-requirement.d.mts +6 -26
  230. package/foundation/integrations/cli-requirement.mjs +11 -46
  231. package/foundation/integrations/cli-requirement.test.mjs +2 -7
  232. package/foundation/integrations/contribution-inventory.mjs +1 -1
  233. package/foundation/integrations/integrations.d.mts +1 -14
  234. package/foundation/integrations/integrations.mjs +1 -41
  235. package/foundation/integrations/integrations.test.mjs +0 -31
  236. package/foundation/response/error-codes.doc.mjs +8 -6
  237. package/foundation/response/error-codes.test.mjs +5 -30
  238. package/foundation/response/response-types.doc.d.mts +3 -4
  239. package/foundation/response/response-types.doc.mjs +27 -40
  240. package/foundation/response/response-types.doc.test.mjs +0 -23
  241. package/foundation/response/response.doc.mjs +10 -11
  242. package/foundation/xle/browser.d.mts +3 -3
  243. package/foundation/xle/browser.mjs +3 -3
  244. package/foundation/xle/expand.mjs +2 -2
  245. package/foundation/xle/parse.mjs +1 -1
  246. package/foundation/xle/print.mjs +2 -2
  247. package/foundation/xle/splice.mjs +1 -1
  248. package/package.json +9 -9
  249. package/api/discover/_adapter.test.mjs +0 -215
  250. package/api/discover/_catalog-view.d.mts +0 -115
  251. package/api/discover/_catalog-view.mjs +0 -203
  252. package/api/discover/_catalog-view.test.mjs +0 -128
  253. package/api/discover/detail/item/item.d.mts +0 -26
  254. package/api/discover/detail/item/item.mjs +0 -78
  255. package/api/discover/detail/item/item.test.mjs +0 -73
  256. package/api/integration/pack-check.lifecycle-output.test.mjs +0 -107
  257. package/api/theme/add/add.rollback.test.mjs +0 -158
  258. package/api/theme/build/build.rollback.test.mjs +0 -148
  259. package/api/upgrade/run/files-changed.test.mjs +0 -111
  260. package/assets/codemods/file-count.test.mjs +0 -163
  261. package/assets/docs/tree/add-a-component.doc.mjs +0 -75
  262. package/assets/docs/tree/add-a-theme.doc.mjs +0 -85
  263. package/assets/docs/tree/add-a-topic.doc.mjs +0 -144
  264. package/assets/docs/tree/agent-guidance.doc.mjs +0 -138
  265. package/assets/docs/tree/block-template.doc.mjs +0 -130
  266. package/assets/docs/tree/build-the-template.doc.mjs +0 -28
  267. package/assets/docs/tree/building-blocks.doc.mjs +0 -46
  268. package/assets/docs/tree/check-your-docs.doc.mjs +0 -137
  269. package/assets/docs/tree/checks.doc.mjs +0 -119
  270. package/assets/docs/tree/codemods.doc.mjs +0 -147
  271. package/assets/docs/tree/component-family.doc.mjs +0 -113
  272. package/assets/docs/tree/component-imports.doc.mjs +0 -69
  273. package/assets/docs/tree/component-lookups.doc.mjs +0 -149
  274. package/assets/docs/tree/components.doc.mjs +0 -23
  275. package/assets/docs/tree/configuration.doc.mjs +0 -23
  276. package/assets/docs/tree/debug-and-gap-reports.doc.mjs +0 -182
  277. package/assets/docs/tree/define-the-theme.doc.mjs +0 -118
  278. package/assets/docs/tree/describe-the-component.doc.mjs +0 -57
  279. package/assets/docs/tree/docs.doc.mjs +0 -21
  280. package/assets/docs/tree/document-the-template.doc.mjs +0 -28
  281. package/assets/docs/tree/document-the-theme.doc.mjs +0 -68
  282. package/assets/docs/tree/export-template-assets.doc.mjs +0 -147
  283. package/assets/docs/tree/extend-or-replace.doc.mjs +0 -103
  284. package/assets/docs/tree/fonts-and-assets.doc.mjs +0 -106
  285. package/assets/docs/tree/generate-a-palette.doc.mjs +0 -66
  286. package/assets/docs/tree/grade-template-with-agent.doc.mjs +0 -105
  287. package/assets/docs/tree/help.doc.mjs +0 -16
  288. package/assets/docs/tree/links.doc.mjs +0 -98
  289. package/assets/docs/tree/package-and-test.doc.mjs +0 -32
  290. package/assets/docs/tree/page-template.doc.mjs +0 -71
  291. package/assets/docs/tree/publishing.doc.mjs +0 -111
  292. package/assets/docs/tree/quick-start.doc.mjs +0 -272
  293. package/assets/docs/tree/replace-a-core-component.doc.mjs +0 -104
  294. package/assets/docs/tree/replace-a-core-template.doc.mjs +0 -172
  295. package/assets/docs/tree/sections-and-placement.doc.mjs +0 -108
  296. package/assets/docs/tree/see-it-in-an-app.doc.mjs +0 -59
  297. package/assets/docs/tree/ship.doc.mjs +0 -16
  298. package/assets/docs/tree/short-and-findable.doc.mjs +0 -108
  299. package/assets/docs/tree/single-component.doc.mjs +0 -165
  300. package/assets/docs/tree/start-a-template.doc.mjs +0 -143
  301. package/assets/docs/tree/subcomponent.doc.mjs +0 -115
  302. package/assets/docs/tree/template-assets.doc.mjs +0 -64
  303. package/assets/docs/tree/template-doc-overview.doc.mjs +0 -109
  304. package/assets/docs/tree/template-fonts.doc.mjs +0 -102
  305. package/assets/docs/tree/template-grading-rubric.doc.mjs +0 -452
  306. package/assets/docs/tree/template-icons.doc.mjs +0 -97
  307. package/assets/docs/tree/template-images-media.doc.mjs +0 -127
  308. package/assets/docs/tree/template-styles.doc.mjs +0 -93
  309. package/assets/docs/tree/templates.doc.mjs +0 -34
  310. package/assets/docs/tree/test-in-an-app.doc.mjs +0 -115
  311. package/assets/docs/tree/test-template-in-app.doc.mjs +0 -128
  312. package/assets/docs/tree/themes.doc.mjs +0 -39
  313. package/assets/docs/tree/troubleshooting.doc.mjs +0 -149
  314. package/assets/docs/tree/upgrading.doc.mjs +0 -103
  315. package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +0 -51
  316. package/assets/docs/tree/verify-packed-template.doc.mjs +0 -77
  317. package/assets/docs/tree/versioning.doc.mjs +0 -161
  318. package/assets/docs/tree/write-good-templates.doc.mjs +0 -64
  319. package/assets/docs/tree/write-the-template-file.doc.mjs +0 -154
  320. package/authoring/discover/discover.doc.d.mts +0 -13
  321. package/authoring/discover/discover.doc.mjs +0 -138
  322. package/authoring/discover/parse.d.mts +0 -24
  323. package/authoring/discover/parse.mjs +0 -128
  324. package/authoring/discover/parse.test.mjs +0 -124
  325. package/authoring/discover/type.ts +0 -87
  326. package/clients/cli/commands/component-batch.test.mjs +0 -341
  327. package/clients/cli/commands/discover.sources.test.mjs +0 -267
  328. package/clients/cli/commands/integration-verify.doc.mjs +0 -22
  329. package/clients/cli/lib/parse-error-format.test.mjs +0 -81
  330. package/foundation/response/batch.type.d.mts +0 -33
  331. package/foundation/response/batch.type.mjs +0 -34
@@ -7,8 +7,7 @@
7
7
  * open one section by its key, and `--full` prints the whole topic. `--json`
8
8
  * keeps the docs() contract: a topic returns its whole doc, and `--index` its
9
9
  * sections.
10
- * Supports --detail (full|compact|brief) and --lang (en|zh|dense). A code
11
- * block's label prints above its fence, and table cells escape their pipes.
10
+ * Supports --detail (full|compact|brief) and --lang (en|zh|dense).
12
11
  *
13
12
  * Usage:
14
13
  * astryx docs List available topics
@@ -43,35 +42,21 @@ import {doc as docsFn} from '../../../api/docs/docs.doc.mjs';
43
42
 
44
43
  // ─── Formatting ──────────────────────────────────────────────────────────────
45
44
 
46
- /**
47
- * A table cell with its pipes escaped. Columns are separated by ` | `, and a
48
- * union type such as `'light' | 'dark'` is spelled with the same character,
49
- * so an unescaped cell reads as extra columns. `astryx component` escapes its
50
- * prop tables the same way.
51
- * @param {string | undefined} cell
52
- * @returns {string}
53
- */
54
- function tableCell(cell) {
55
- return (cell || '').replaceAll('|', '\\|');
56
- }
57
-
58
45
  /**
59
46
  * @param {string[]} headers
60
47
  * @param {string[][]} rows
61
48
  * @returns {string}
62
49
  */
63
50
  function formatTable(headers, rows) {
64
- const head = headers.map(tableCell);
65
- const cells = rows.map(r => r.map(tableCell));
66
- const widths = head.map((h, i) =>
67
- Math.max(h.length, ...cells.map(r => (r[i] || '').length)),
51
+ const widths = headers.map((h, i) =>
52
+ Math.max(h.length, ...rows.map(r => (r[i] || '').length)),
68
53
  );
69
54
  const sep = widths.map(w => '-'.repeat(w)).join(' | ');
70
- const top = head.map((h, i) => h.padEnd(widths[i])).join(' | ');
71
- const body = cells
72
- .map(r => r.map((c, i) => c.padEnd(widths[i])).join(' | '))
55
+ const head = headers.map((h, i) => h.padEnd(widths[i])).join(' | ');
56
+ const body = rows
57
+ .map(r => r.map((c, i) => (c || '').padEnd(widths[i])).join(' | '))
73
58
  .join('\n');
74
- return `${top}\n${sep}\n${body}`;
59
+ return `${head}\n${sep}\n${body}`;
75
60
  }
76
61
 
77
62
  /**
@@ -80,19 +65,15 @@ function formatTable(headers, rows) {
80
65
  * @returns {string}
81
66
  */
82
67
  function formatTableCompact(headers, rows) {
83
- // An empty cell, such as a Default with none, adds nothing to the line.
84
- return rows
85
- .map(r => r.filter(cell => String(cell ?? '').trim() !== '').join(' = '))
86
- .join('\n');
68
+ return rows.map(r => r.join(' = ')).join('\n');
87
69
  }
88
70
 
89
71
  /**
90
- * One content block as text. Exported for its tests.
91
72
  * @param {import('@astryxdesign/cli/authoring').ReferenceContentBlock} block
92
73
  * @param {'full' | 'compact' | 'brief'} detail
93
74
  * @returns {string | null}
94
75
  */
95
- export function formatBlock(block, detail) {
76
+ function formatBlock(block, detail) {
96
77
  switch (block.type) {
97
78
  case 'prose':
98
79
  return block.text;
@@ -103,20 +84,13 @@ export function formatBlock(block, detail) {
103
84
  case 'code':
104
85
  if (detail === 'compact' || detail === 'brief') return null;
105
86
  {
106
- // The label names the block, so it prints above the fence, not
107
- // inside it: `// label` is not a comment in bash, CSS, JSON, or
108
- // HTML, and a reader who copies the block would copy it too.
109
- const label = block.label
110
- ? `${block.label.replace(/:\s*$/, '')}:\n`
111
- : '';
112
- return `${label}\`\`\`${block.lang}\n${block.code}\n\`\`\``;
87
+ const label = block.label ? `// ${block.label}\n` : '';
88
+ return `\`\`\`${block.lang}\n${label}${block.code}\n\`\`\``;
113
89
  }
114
90
 
115
91
  case 'table':
116
92
  if (detail === 'brief') {
117
- return block.rows
118
- .map(r => r.slice(0, 2).map(tableCell).join('='))
119
- .join(' | ');
93
+ return block.rows.map(r => r.slice(0, 2).join('=')).join(' | ');
120
94
  }
121
95
  if (detail === 'compact') {
122
96
  return formatTableCompact(block.headers, block.rows);
@@ -228,23 +202,16 @@ function emitIndex(index, run) {
228
202
  }
229
203
 
230
204
  /**
231
- * One child row of a namespace. Namespace and guide route names are already
232
- * readable, so repeating their titles adds noise (`start-a-template Start a
233
- * template`). Typed docs keep a distinct title when it carries the real symbol
234
- * name (`assert-response assertResponse()`).
205
+ * One child row of a namespace: its route name, then the doc's own title when
206
+ * it is not the route name (`assertResponse()`, `search()`), then its summary.
235
207
  * @param {import('../../../api/docs/docs.type.mjs').DocsNodeChild} child
236
208
  * @returns {{name: string, summary: string}}
237
209
  */
238
210
  function childRow(child) {
239
- const titleAddsIdentity =
240
- child.kind !== 'namespace' &&
241
- child.kind !== 'generic' &&
242
- child.title !== child.name;
243
211
  return {
244
212
  name: child.name,
245
- summary: titleAddsIdentity
246
- ? `${child.title}: ${child.summary}`
247
- : child.summary,
213
+ summary:
214
+ child.title === child.name ? child.summary : `${child.title}: ${child.summary}`,
248
215
  };
249
216
  }
250
217
 
@@ -260,17 +227,6 @@ function emitNode(node, detail, run) {
260
227
  if (node.kind === 'namespace') {
261
228
  emit(
262
229
  section(node.title, wrapText(node.summary)),
263
- // A namespace may author intro `blocks`; they render above its children.
264
- ...(node.content?.length
265
- ? [
266
- text(
267
- node.content
268
- .map(b => formatBlock(b, detail))
269
- .filter(Boolean)
270
- .join('\n\n'),
271
- ),
272
- ]
273
- : []),
274
230
  ...node.slots.flatMap(slot => [
275
231
  // A namespace with one slot titled like itself needs no second heading.
276
232
  ...(node.slots.length === 1 && slot.title === node.title
@@ -279,6 +235,7 @@ function emitNode(node, detail, run) {
279
235
  records(slot.children.map(childRow), {
280
236
  fields: ['name', 'summary'],
281
237
  layout: 'inline',
238
+ overflow: 'truncate',
282
239
  }),
283
240
  ]),
284
241
  text(
@@ -0,0 +1,294 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ import {describe, it, expect, beforeEach, afterEach, vi} from 'vitest';
4
+ import * as fs from 'node:fs';
5
+ import * as path from 'node:path';
6
+ import * as os from 'node:os';
7
+ import {Command} from 'commander';
8
+ import {registerDocs} from './docs.mjs';
9
+ import {runCli} from '../../../test-utils/run-cli.mjs';
10
+ import {displayWidth} from '../formatters/index.mjs';
11
+
12
+ let tmpDir;
13
+
14
+ beforeEach(() => {
15
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-docs-test-'));
16
+ vi.spyOn(console, 'log').mockImplementation(() => {});
17
+ vi.spyOn(console, 'error').mockImplementation(() => {});
18
+ });
19
+
20
+ afterEach(() => {
21
+ fs.rmSync(tmpDir, {recursive: true, force: true});
22
+ vi.restoreAllMocks();
23
+ });
24
+
25
+ function createProgram() {
26
+ const program = new Command();
27
+ program.exitOverride(); // Throw instead of calling process.exit
28
+ registerDocs(program);
29
+ return program;
30
+ }
31
+
32
+ describe('registerDocs', () => {
33
+ it('lists available topics when no topic given', async () => {
34
+ const program = createProgram();
35
+ await program.parseAsync(['node', 'astryx', 'docs']);
36
+
37
+ const output = console.log.mock.calls.map(c => c[0]).join('\n');
38
+ expect(output).toContain('principles');
39
+ expect(output).toContain('tokens');
40
+ expect(output).not.toContain('shadcn-compatibility');
41
+ });
42
+
43
+ it('errors for unknown topic', async () => {
44
+ const program = createProgram();
45
+ vi.spyOn(process, 'exit').mockImplementation(code => {
46
+ throw new Error(`exit ${code}`);
47
+ });
48
+
49
+ await expect(
50
+ program.parseAsync(['node', 'astryx', 'docs', 'nonexistent']),
51
+ ).rejects.toThrow('exit 1');
52
+
53
+ const errorOutput = console.error.mock.calls.map(c => c[0]).join('\n');
54
+ expect(errorOutput).toContain('Unknown topic');
55
+ });
56
+ });
57
+
58
+ describe('hyphenated doc filenames', () => {
59
+ it('lists hyphenated topics like getting-started', async () => {
60
+ const program = createProgram();
61
+ await program.parseAsync(['node', 'astryx', 'docs']);
62
+
63
+ const output = console.log.mock.calls.map(c => c[0]).join('\n');
64
+ expect(output).toContain('getting-started');
65
+ });
66
+
67
+ it('loads a hyphenated topic by name', async () => {
68
+ const program = createProgram();
69
+ await program.parseAsync(['node', 'astryx', 'docs', 'getting-started']);
70
+
71
+ const output = console.log.mock.calls.map(c => c[0]).join('\n');
72
+ expect(output.length).toBeGreaterThan(0);
73
+ expect(console.error).not.toHaveBeenCalled();
74
+ });
75
+
76
+ it('returns docs.detail via API for hyphenated topic', async () => {
77
+ const {docs: docsApi} = await import('../../../api/docs/docs.mjs');
78
+ const result = await docsApi('getting-started', undefined, {full: true});
79
+ expect(result.type).toBe('docs.detail');
80
+ expect(result.data).toBeDefined();
81
+ expect(result.data.description).toBeDefined();
82
+ });
83
+ });
84
+
85
+ describe('migration docs', () => {
86
+ it('lists the migration topic', async () => {
87
+ const program = createProgram();
88
+ await program.parseAsync(['node', 'astryx', 'docs']);
89
+
90
+ const output = console.log.mock.calls.map(c => c[0]).join('\n');
91
+ expect(output).toContain('migration');
92
+ expect(output).toContain('Tailwind');
93
+ });
94
+
95
+ it('loads migration docs by topic name', async () => {
96
+ const program = createProgram();
97
+ await program.parseAsync(['node', 'astryx', 'docs', 'migration']);
98
+
99
+ const output = console.log.mock.calls.map(c => c[0]).join('\n');
100
+ expect(output).toContain('Migration Guide');
101
+ expect(output).toContain('Recommended Order');
102
+ expect(output).toContain('Map shadcn and Radix Primitives');
103
+ });
104
+ });
105
+
106
+ describe('progressive reads', () => {
107
+ const SLOW = 60_000;
108
+ /** @param {string} out */
109
+ const widest = out => Math.max(...out.split('\n').map(line => line.length));
110
+
111
+ it('lists every topic on one line each', async () => {
112
+ const {status, stdout} = await runCli(['docs']);
113
+ expect(status).toBe(0);
114
+ expect(stdout).toMatch(/^principles +\S/m);
115
+ expect(widest(stdout)).toBeLessThanOrEqual(120);
116
+ expect(stdout).toContain('Usage: pnpm exec astryx docs <topic>');
117
+ }, SLOW);
118
+
119
+ it("prints a topic's section index with the keys to read by", async () => {
120
+ const {status, stdout} = await runCli(['docs', 'theme', '--index']);
121
+ expect(status).toBe(0);
122
+ expect(stdout).toMatch(/^quick-start +Quick Start/m);
123
+ expect(stdout).toContain('docs theme <section>');
124
+ expect(stdout).toMatch(/Read everything: +\S.* docs theme --full$/m);
125
+ expect(widest(stdout)).toBeLessThanOrEqual(120);
126
+ }, SLOW);
127
+
128
+ it('prints one section by its key', async () => {
129
+ const {status, stdout} = await runCli(['docs', 'theme', 'quick-start']);
130
+ expect(status).toBe(0);
131
+ expect(stdout).toMatch(/^## Quick Start/m);
132
+ }, SLOW);
133
+
134
+ it("lists a topic's sections by default; --full prints the whole topic", async () => {
135
+ const index = await runCli(['docs', 'theme', '--index']);
136
+ expect((await runCli(['docs', 'theme'])).stdout).toBe(index.stdout);
137
+ const full = await runCli(['docs', 'theme', '--full']);
138
+ expect(full.status).toBe(0);
139
+ expect(full.stdout).toMatch(/^## Quick Start/m);
140
+ expect(full.stdout.length).toBeGreaterThan(index.stdout.length * 3);
141
+ expect((await runCli(['--detail', 'full', 'docs', 'theme', '--full'])).stdout).toBe(
142
+ full.stdout,
143
+ );
144
+ }, SLOW);
145
+
146
+ it('returns the matching envelopes as JSON', async () => {
147
+ const envelope = async args => JSON.parse((await runCli([...args, '--json'])).stdout);
148
+ // JSON keeps docs(): a bare read is the whole topic.
149
+ expect((await envelope(['docs', 'theme'])).type).toBe('docs.detail');
150
+ expect((await envelope(['docs', 'theme', '--full'])).type).toBe('docs.detail');
151
+ expect((await envelope(['docs', 'theme', '--index'])).type).toBe(
152
+ 'docs.index',
153
+ );
154
+ expect((await envelope(['docs', 'theme', 'quick-start'])).type).toBe(
155
+ 'docs.detail.section',
156
+ );
157
+ }, SLOW);
158
+ });
159
+
160
+ describe('the docs tree, one level at a time', () => {
161
+ const SLOW = 60_000;
162
+ /** @param {string} out */
163
+ const widest = out => Math.max(...out.split('\n').map(line => line.length));
164
+
165
+ it('lists the docs tree first, under its own heading, then the topics', async () => {
166
+ const {status, stdout} = await runCli(['docs']);
167
+ expect(status).toBe(0);
168
+ expect(stdout).toMatch(/^Docs tree$/m);
169
+ expect(stdout).toMatch(/^Topics$/m);
170
+ expect(stdout).toMatch(/^cli +Commands, programmatic APIs/m);
171
+ expect(stdout.indexOf('\ncli ')).toBeLessThan(stdout.indexOf('\nprinciples '));
172
+ expect(stdout).not.toMatch(/^cli-integrations /m);
173
+ }, SLOW);
174
+
175
+ it("shows a child's own name when its route name does not spell it", async () => {
176
+ const {status, stdout} = await runCli(['docs', 'cli/api/functions']);
177
+ expect(status).toBe(0);
178
+ expect(stdout).toMatch(/^assert-response +assertResponse\(\): \S/m);
179
+ expect(stdout).toMatch(/^search +search\(\): Unified ranked search/m);
180
+ }, SLOW);
181
+
182
+ it('prints a namespace: each slot, its children, and how to go down and up', async () => {
183
+ const {status, stdout} = await runCli(['docs', 'cli/api']);
184
+ expect(status).toBe(0);
185
+ expect(stdout).toMatch(/^API$/m);
186
+ expect(stdout).toMatch(/^Reference$/m);
187
+ expect(stdout).toMatch(/^functions +Functions: Every function/m);
188
+ expect(stdout).toMatch(/^schemas +/m);
189
+ expect(stdout).toMatch(/^enums +/m);
190
+ // One level only: no function is listed on the api page.
191
+ expect(stdout).not.toMatch(/^search +/m);
192
+ expect(stdout).toMatch(/Open one: .*docs cli\/api\/<name>$/m);
193
+ expect(stdout).toMatch(/Up: .*docs cli$/m);
194
+ expect(widest(stdout)).toBeLessThanOrEqual(120);
195
+ }, SLOW);
196
+
197
+ it('prints a typed doc and the way back up', async () => {
198
+ const {status, stdout} = await runCli(['docs', 'cli/api/functions/search']);
199
+ expect(status).toBe(0);
200
+ expect(stdout).toMatch(/^## search\(\)/m);
201
+ expect(stdout).toContain('Read it with `astryx docs cli/commands/search`.');
202
+ expect(stdout).toMatch(/Up: .*docs cli\/api\/functions$/m);
203
+ }, SLOW);
204
+
205
+ it('reads the dense variant whole, asked with --dense or with --lang dense', async () => {
206
+ for (const args of [['--dense', 'docs', 'theme'], ['--lang', 'dense', 'docs', 'theme']]) {
207
+ const {status, stdout} = await runCli(args);
208
+ expect(status).toBe(0);
209
+ expect(stdout).not.toContain('Read one section:');
210
+ }
211
+ }, SLOW);
212
+
213
+ it('refuses --index with --full', async () => {
214
+ const both = await runCli(['docs', 'theme', '--index', '--full', '--json']);
215
+ expect(both.status).toBe(1);
216
+ expect(JSON.parse(both.stdout).code).toBe('ERR_INVALID_ARGUMENT');
217
+ }, SLOW);
218
+
219
+ it('reads the integration guide by its route, and not by its old name', async () => {
220
+ const guide = await runCli(['docs', 'cli/integrations', '--index']);
221
+ expect(guide.status).toBe(0);
222
+ expect(guide.stdout).toMatch(/Read one section: .*docs cli\/integrations <section>/);
223
+ // A bare read is one level too: the sections, and how to read it all.
224
+ const bare = await runCli(['docs', 'cli/integrations']);
225
+ expect(bare.status).toBe(0);
226
+ expect(bare.stdout).toBe(guide.stdout);
227
+ expect(bare.stdout).toMatch(/Read everything: +.*docs cli\/integrations --full$/m);
228
+ const one = await runCli(['docs', 'cli/integrations', 'codemods']);
229
+ expect(one.status).toBe(0);
230
+ expect(one.stdout).toMatch(/^## Codemods$/m);
231
+ expect(one.stdout).toMatch(/^Up: .*docs cli\/integrations --index$/m);
232
+ expect(one.stdout).toMatch(/^Previous: .*docs cli\/integrations agent-docs$/m);
233
+ expect(one.stdout).toMatch(/^Next: .*docs cli\/integrations recording-runs$/m);
234
+ const full = await runCli(['docs', 'cli/integrations', '--full']);
235
+ expect(full.status).toBe(0);
236
+ expect(full.stdout).toMatch(/^## Overview$/m);
237
+ expect(full.stdout.length).toBeGreaterThan(bare.stdout.length * 4);
238
+ const old = await runCli(['docs', 'cli-integrations']);
239
+ expect(old.status).toBe(1);
240
+ expect(old.stderr).toContain('Unknown topic "cli-integrations"');
241
+ }, SLOW);
242
+
243
+ it('returns docs.node as JSON, and fails a section of a namespace', async () => {
244
+ const node = JSON.parse((await runCli(['docs', 'cli', '--json'])).stdout);
245
+ expect(node).toMatchObject({
246
+ type: 'docs.node',
247
+ data: {route: 'cli', kind: 'namespace', breadcrumb: []},
248
+ });
249
+ expect(node.data.slots.map(slot => slot.name)).toEqual(['guides', 'reference']);
250
+ const section = await runCli(['docs', 'cli', 'commands', '--json']);
251
+ expect(section.status).toBe(1);
252
+ const error = JSON.parse(section.stdout);
253
+ expect(error).toMatchObject({code: 'ERR_UNKNOWN_SECTION'});
254
+ expect(error.suggestions.map(s => s.name)).toEqual([
255
+ 'cli/integrations',
256
+ 'cli/writing-docs',
257
+ 'cli/commands',
258
+ 'cli/api',
259
+ ]);
260
+ }, SLOW);
261
+ });
262
+
263
+ describe('text width in every language', () => {
264
+ const SLOW = 60_000;
265
+ /** Widest line outside code blocks, in terminal columns. */
266
+ const widest = out => {
267
+ let inCode = false;
268
+ let max = 0;
269
+ for (const line of out.split('\n')) {
270
+ if (/^\s*```/.test(line)) {
271
+ inCode = !inCode;
272
+ continue;
273
+ }
274
+ // A single unbreakable token (a long URL) cannot wrap without breaking it.
275
+ const oneToken = !/\s/.test(line.trim());
276
+ if (!inCode && !line.startsWith('#') && !oneToken) {
277
+ max = Math.max(max, displayWidth(line));
278
+ }
279
+ }
280
+ return max;
281
+ };
282
+
283
+ it('keeps the additive section index within 120 columns', async () => {
284
+ const {status, stdout} = await runCli([
285
+ 'docs',
286
+ 'theme',
287
+ '--index',
288
+ '--lang',
289
+ 'zh',
290
+ ]);
291
+ expect(status).toBe(0);
292
+ expect(widest(stdout)).toBeLessThanOrEqual(120);
293
+ }, SLOW);
294
+ });
@@ -10,10 +10,9 @@ export const doc = {
10
10
  namespace: 'cli/commands',
11
11
  summary: 'Check an integration\'s docs: the docs tree they add, every link, and overlaps with Core topics',
12
12
  description:
13
- 'Checks the docs tree the package adds, every link in its docs, and overlaps with Core topics. ' +
14
- 'Intentional replacements and extensions are information. ' +
13
+ 'Reports intentional Core topic replacements and extensions as information. ' +
15
14
  'A same-name topic without `replaces` or `extends` is an accidental conflict ' +
16
- 'and fails until the author declares the relationship or renames it. See {@link generic:check-your-docs}.',
15
+ 'and fails until the author declares the relationship or renames it.',
17
16
  fn: 'integrationDocConflicts',
18
17
  args: [
19
18
  {
@@ -13,7 +13,6 @@ vi.mock('../../../foundation/discovery/template-conflict-release.mjs', () => ({
13
13
  expandedTemplateConflictSchemaActive: () => true,
14
14
  }));
15
15
  import * as fs from 'node:fs';
16
- import * as os from 'node:os';
17
16
  import * as path from 'node:path';
18
17
  import {Command} from 'commander';
19
18
  import {discoverCoreTemplates} from '../../../foundation/discovery/template-adapter.mjs';
@@ -345,35 +344,6 @@ describe('doctor integration — command', () => {
345
344
  expect(process.exitCode).toBe(1);
346
345
  });
347
346
 
348
- it('components exits 1 when Core is missing, with no [ok] after the failure', async () => {
349
- // Outside the repo, so nothing above the package resolves Core.
350
- const outside = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-no-core-'));
351
- const previousTmp = tmpDir;
352
- tmpDir = outside;
353
- try {
354
- writeComponentIntegration('AcmeCarousel');
355
- expect(findCoreDir(outside)).toBeNull();
356
- process.chdir(outside);
357
-
358
- await createProgram().parseAsync([
359
- 'node',
360
- 'astryx',
361
- 'doctor',
362
- 'integration',
363
- 'components',
364
- ]);
365
-
366
- const printed = logCalls.join('\n');
367
- expect(printed).toContain('core_not_found');
368
- expect(printed).not.toContain('[ok]');
369
- expect(process.exitCode).toBe(1);
370
- } finally {
371
- tmpDir = previousTmp;
372
- process.chdir(previousCwd);
373
- fs.rmSync(outside, {recursive: true, force: true});
374
- }
375
- });
376
-
377
347
  it('components warns with the exact package-qualified command', async () => {
378
348
  const coreDir = findCoreDir(tmpDir);
379
349
  expect(coreDir).not.toBeNull();
@@ -463,29 +433,6 @@ describe('doctor integration — command', () => {
463
433
  expect(process.exitCode).toBeUndefined();
464
434
  });
465
435
 
466
- it('docs exits 1 for a placement that hides a guide, with no [ok] after the failure', async () => {
467
- writeDocIntegration({name: 'deploying'});
468
- fs.writeFileSync(
469
- path.join(tmpDir, 'docs', 'deploying.doc.mjs'),
470
- "export default {type: 'generic', name: 'deploying', title: 'Deploying', description: 'Deploy.', placement: {parent: 'namespace:nope', slot: 'guides'}, sections: [{title: 'Deploy', content: [{type: 'prose', text: 'Deploy.'}]}]};\n",
471
- );
472
- process.chdir(tmpDir);
473
-
474
- await createProgram().parseAsync([
475
- 'node',
476
- 'astryx',
477
- 'doctor',
478
- 'integration',
479
- 'docs',
480
- ]);
481
-
482
- const printed = logCalls.join('\n');
483
- expect(printed).toContain('[fail]');
484
- expect(printed).toContain('invalid_doc_graph');
485
- expect(printed).not.toContain('[ok]');
486
- expect(process.exitCode).toBe(1);
487
- });
488
-
489
436
  it('docs exits 1 for an accidental same-name Core topic', async () => {
490
437
  const [coreTopic] = Object.keys(discoverBuiltinTopics());
491
438
  writeDocIntegration({name: coreTopic});
@@ -16,9 +16,7 @@ export const doc = {
16
16
  summary: 'Diagnose Astryx projects and integration packages',
17
17
  description:
18
18
  'Runs read-only project health diagnostics by default: Node version, @astryxdesign/core ' +
19
- 'install and version alignment, themes, config, integrations (linked without a config entry, ' +
20
- 'provider identity, contribution issues), agent docs, core peer dependencies, package manager, ' +
21
- "and the docs the CLI reads. It writes nothing, but loading astryx.config runs that file's code. " +
19
+ 'install and version alignment, themes, config, agent docs, and package manager. ' +
22
20
  'The `integration` subcommands provide authoring checks for one integration package.',
23
21
  fn: 'doctor',
24
22
  subcommands: ['integration'],
@@ -36,8 +36,6 @@ import {doc as integrationTemplateConflictsFn} from '../../../api/integration/in
36
36
  import {doc as integrationComponentConflictsFn} from '../../../api/integration/integrationComponentConflicts.doc.mjs';
37
37
  import {doc as integrationDocConflictsFn} from '../../../api/integration/integrationDocConflicts.doc.mjs';
38
38
  import {NO_RESULT_SET} from '../../../foundation/debug/index.mjs';
39
- import {cliError} from '../lib/cli-error.mjs';
40
- import {ERROR_CODES} from '../../../foundation/response/error-codes.mjs';
41
39
 
42
40
  const STATUS = {
43
41
  pass: '[ok]',
@@ -183,10 +181,8 @@ function printComponentConflicts(data) {
183
181
  ),
184
182
  ...issueBlocks(data.issues),
185
183
  ];
186
- // An [ok] after a failed check reads as a pass: say nothing it could not check.
187
- const failed = data.issues.some(issue => issue.severity === 'error');
188
184
  if (data.conflicts.length === 0) {
189
- if (!failed) output.push(text('[ok] No component names conflict with Core.'));
185
+ output.push(text('[ok] No component names conflict with Core.'));
190
186
  } else {
191
187
  output.push(
192
188
  records(data.conflicts, {
@@ -228,9 +224,7 @@ function printDocConflicts(data) {
228
224
  );
229
225
  }
230
226
  if (data.findings.length === 0) {
231
- if (!data.issues.some(issue => issue.severity === 'error')) {
232
- output.push(text('[ok] No doc topics overlap with Core.'));
233
- }
227
+ output.push(text('[ok] No doc topics overlap with Core.'));
234
228
  } else {
235
229
  output.push(
236
230
  records(data.findings, {
@@ -321,57 +315,19 @@ async function runAuthoringCheck(program, pkg, kind) {
321
315
  return NO_RESULT_SET;
322
316
  }
323
317
 
324
- /**
325
- * The first word after a command group, which names a subcommand it does not
326
- * have, or null.
327
- * @param {import('commander').Command | undefined} invoked
328
- * @returns {string | null}
329
- */
330
- function unknownWord(invoked) {
331
- const word = (invoked?.args ?? []).find(arg => !String(arg).startsWith('-'));
332
- return word == null ? null : String(word);
333
- }
334
-
335
- /**
336
- * Report an unknown subcommand, in text as in JSON, with the ones the group has.
337
- * @param {import('commander').Command} group
338
- * @param {string} label the group's full name
339
- * @param {string} word
340
- */
341
- function unknownSubcommand(group, label, word) {
342
- return cliError(`unknown subcommand '${label} ${word}'`, {
343
- suggestions: group.commands.map(child => ({
344
- name: child.name(),
345
- reason: 'available subcommand',
346
- })),
347
- code: ERROR_CODES.ERR_UNKNOWN_SUBCOMMAND,
348
- });
349
- }
350
-
351
318
  /**
352
319
  * Register `astryx doctor` and its integration-authoring leaves.
353
320
  * @param {import('commander').Command} program
354
321
  */
355
322
  export function registerDoctor(program) {
356
- /** @type {import('commander').Command} */
357
- let doctorCmd;
358
- doctorCmd = defineCommand(program, doctorCommand, {
323
+ const doctorCmd = defineCommand(program, doctorCommand, {
359
324
  fn: doctorFn,
360
- // `doctor integrations` is a mistyped subcommand, not a project check.
361
- action: async (options, invoked) => {
362
- const word = unknownWord(invoked);
363
- if (word != null) return unknownSubcommand(doctorCmd, 'doctor', word);
364
- return runProjectDoctor(program);
365
- },
325
+ action: async () => runProjectDoctor(program),
366
326
  });
367
327
  /** @type {import('commander').Command} */
368
328
  let integrationCmd;
369
329
  integrationCmd = defineCommand(doctorCmd, doctorIntegrationGroup, {
370
- action: (options, invoked) => {
371
- const word = unknownWord(invoked);
372
- if (word != null) {
373
- return unknownSubcommand(integrationCmd, 'doctor integration', word);
374
- }
330
+ action: () => {
375
331
  integrationCmd.outputHelp();
376
332
  return NO_RESULT_SET;
377
333
  },