@astryxdesign/cli 0.7.2 → 0.7.3-canary.02f5fe4

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 (373) hide show
  1. package/README.md +104 -71
  2. package/api/build/_adapter.d.mts +58 -0
  3. package/api/build/_adapter.mjs +127 -3
  4. package/api/build/blocks/blocks.d.mts +92 -0
  5. package/api/build/blocks/blocks.mjs +528 -0
  6. package/api/build/blocks/blocks.test.mjs +238 -0
  7. package/api/build/blocks/blocks.weights.json +1 -0
  8. package/api/build/build.mjs +25 -6
  9. package/api/build/build.type.mjs +1 -1
  10. package/api/build/kit/answer.d.mts +18 -0
  11. package/api/build/kit/answer.mjs +195 -0
  12. package/api/build/kit/answer.test.mjs +108 -0
  13. package/api/build/kit/kind.d.mts +25 -0
  14. package/api/build/kit/kind.mjs +170 -0
  15. package/api/build/kit/kit.d.mts +2 -4
  16. package/api/build/kit/kit.mjs +8 -398
  17. package/api/build/kit/rank.d.mts +3 -32
  18. package/api/build/kit/rank.mjs +25 -342
  19. package/api/build/kit/rank.test.mjs +2 -1
  20. package/api/build/kit/signals.d.mts +79 -0
  21. package/api/build/kit/signals.mjs +295 -0
  22. package/api/build/kit/start.d.mts +32 -0
  23. package/api/build/kit/start.mjs +163 -0
  24. package/api/build/kit/words.d.mts +72 -0
  25. package/api/build/kit/words.mjs +202 -0
  26. package/api/build/tree.d.mts +20 -0
  27. package/api/build/tree.mjs +53 -0
  28. package/api/build/tree.test.mjs +185 -0
  29. package/api/build/tree.weights.json +1 -0
  30. package/api/component/_adapter.mjs +39 -5
  31. package/api/component/component-replacement.test.mjs +1 -0
  32. package/api/component/component.mjs +17 -3
  33. package/api/component/component.test.mjs +15 -0
  34. package/api/component/doc-links.test.mjs +2 -2
  35. package/api/discover/_adapter.d.mts +1 -2
  36. package/api/discover/_adapter.mjs +42 -17
  37. package/api/discover/_adapter.test.mjs +42 -0
  38. package/api/discover/discover.mjs +0 -1
  39. package/api/docs/_adapter.d.mts +2 -2
  40. package/api/docs/_adapter.mjs +82 -23
  41. package/api/docs/docs-homes.test.mjs +127 -0
  42. package/api/docs/docs.doc.mjs +3 -3
  43. package/api/docs/docs.test.mjs +35 -19
  44. package/api/docs/integration-tree.test.mjs +20 -20
  45. package/api/docs/integrationDocs.test.mjs +1 -1
  46. package/api/docs/namespace-section.test.mjs +6 -6
  47. package/api/docs/reference-blocks.test.mjs +2 -2
  48. package/api/doctor/_adapter.d.mts +33 -13
  49. package/api/doctor/_adapter.mjs +55 -25
  50. package/api/doctor/doctor.d.mts +23 -5
  51. package/api/doctor/doctor.mjs +183 -40
  52. package/api/doctor/doctor.test.mjs +95 -12
  53. package/api/hook/hook.mjs +1 -1
  54. package/api/init/init.doc.mjs +20 -6
  55. package/api/init/init.test.mjs +18 -2
  56. package/api/init/init.type.d.mts +3 -3
  57. package/api/init/init.type.mjs +3 -3
  58. package/api/init/remove/remove.mjs +17 -4
  59. package/api/init/remove/remove.test.mjs +114 -2
  60. package/api/init/run/run.mjs +69 -6
  61. package/api/integration/_adapter.d.mts +8 -0
  62. package/api/integration/_adapter.mjs +21 -1
  63. package/api/integration/authoring-checks.mjs +83 -9
  64. package/api/integration/authoring-checks.test.mjs +220 -0
  65. package/api/integration/pack-check.mjs +123 -4
  66. package/api/integration/pack-check.test.mjs +87 -0
  67. package/api/integration/validate-integration-case-disk.test.mjs +111 -0
  68. package/api/integration/validate-integration-fixes.test.mjs +6 -5
  69. package/api/integration/validate-integration-gaps.test.mjs +1975 -0
  70. package/api/integration/validate-integration.d.mts +15 -2
  71. package/api/integration/validate-integration.mjs +298 -39
  72. package/api/integration/validate-integration.type.d.mts +90 -1
  73. package/api/integration/validate-integration.type.mjs +67 -2
  74. package/api/integration/validateIntegration.doc.mjs +1 -1
  75. package/api/search/ORDERING.md +28 -0
  76. package/api/search/_adapter.d.mts +82 -2
  77. package/api/search/_adapter.mjs +157 -10
  78. package/api/search/_adapter.test.mjs +173 -0
  79. package/api/search/_candidate-score.d.mts +40 -0
  80. package/api/search/_candidate-score.mjs +238 -0
  81. package/api/search/_cards.json +6098 -0
  82. package/api/search/_compound.d.mts +50 -0
  83. package/api/search/_compound.mjs +170 -0
  84. package/api/search/_doc-searcher.d.mts +4 -0
  85. package/api/search/_doc-searcher.mjs +23 -0
  86. package/api/search/_doc-searcher.test.mjs +36 -0
  87. package/api/search/_doc-searcher.weights.json +1 -0
  88. package/api/search/_doc-text.d.mts +30 -0
  89. package/api/search/_doc-text.mjs +87 -0
  90. package/api/search/_entries.d.mts +63 -0
  91. package/api/search/_entries.mjs +232 -0
  92. package/api/search/_entries.test.mjs +48 -0
  93. package/api/search/_features.d.mts +35 -0
  94. package/api/search/_features.mjs +155 -0
  95. package/api/search/_gather-components.d.mts +26 -0
  96. package/api/search/_gather-components.mjs +284 -0
  97. package/api/search/_gather-docs.d.mts +20 -0
  98. package/api/search/_gather-docs.mjs +254 -0
  99. package/api/search/_gather-hooks.d.mts +12 -0
  100. package/api/search/_gather-hooks.mjs +52 -0
  101. package/api/search/_gather-templates.d.mts +25 -0
  102. package/api/search/_gather-templates.mjs +107 -0
  103. package/api/search/_lexical.d.mts +135 -0
  104. package/api/search/_lexical.mjs +351 -0
  105. package/api/search/_main-searcher.d.mts +19 -0
  106. package/api/search/_main-searcher.mjs +115 -0
  107. package/api/search/_main-searcher.test.mjs +106 -0
  108. package/api/search/_main-searcher.weights.json +1 -0
  109. package/api/search/_module-doc.d.mts +32 -0
  110. package/api/search/_module-doc.mjs +78 -0
  111. package/api/search/_order.d.mts +32 -0
  112. package/api/search/_order.mjs +162 -0
  113. package/api/search/_order.test.mjs +72 -0
  114. package/api/search/_priority.d.mts +50 -0
  115. package/api/search/_priority.mjs +139 -0
  116. package/api/search/_query-words.d.mts +25 -0
  117. package/api/search/_query-words.mjs +92 -0
  118. package/api/search/_query.d.mts +65 -0
  119. package/api/search/_query.mjs +176 -0
  120. package/api/search/_result.d.mts +97 -0
  121. package/api/search/_result.mjs +150 -0
  122. package/api/search/_score.d.mts +60 -0
  123. package/api/search/_score.mjs +283 -0
  124. package/api/search/_synonyms.d.mts +21 -0
  125. package/api/search/_synonyms.mjs +67 -0
  126. package/api/search/_template-searcher.d.mts +4 -0
  127. package/api/search/_template-searcher.mjs +23 -0
  128. package/api/search/_template-searcher.test.mjs +40 -0
  129. package/api/search/_template-searcher.weights.json +1 -0
  130. package/api/search/_titles.d.mts +33 -0
  131. package/api/search/_titles.mjs +65 -0
  132. package/api/search/_words.d.mts +64 -0
  133. package/api/search/_words.mjs +157 -0
  134. package/api/search/search.d.mts +22 -305
  135. package/api/search/search.doc.mjs +8 -6
  136. package/api/search/search.mjs +56 -2482
  137. package/api/search/search.test.mjs +14 -14
  138. package/api/search/search.type.d.mts +6 -1
  139. package/api/search/search.type.mjs +6 -1
  140. package/api/search/tree.d.mts +15 -0
  141. package/api/search/tree.mjs +40 -0
  142. package/api/search/tree.test.mjs +66 -0
  143. package/api/search/tree.weights.json +1 -0
  144. package/api/template/template.mjs +5 -1
  145. package/api/theme/build/build.packed-old-core.test.mjs +1 -0
  146. package/api/upgrade/_adapter.d.mts +57 -9
  147. package/api/upgrade/_adapter.mjs +198 -28
  148. package/api/upgrade/registry/registry.d.mts +10 -4
  149. package/api/upgrade/registry/registry.mjs +33 -16
  150. package/api/upgrade/registry/registry.test.mjs +85 -0
  151. package/api/upgrade/run/run.d.mts +4 -2
  152. package/api/upgrade/run/run.mjs +13 -2
  153. package/api/upgrade/run/run.test.mjs +417 -3
  154. package/api/upgrade/upgrade.doc.mjs +10 -5
  155. package/api/upgrade/upgrade.mjs +13 -2
  156. package/api/upgrade/upgrade.test.mjs +194 -0
  157. package/api/upgrade/upgrade.type.d.mts +5 -3
  158. package/api/upgrade/upgrade.type.mjs +8 -6
  159. package/assets/codemods/transforms/v0.7.0/__tests__/validate-data-token-ownership.test.mjs +11 -0
  160. package/assets/codemods/transforms/v0.7.0/validate-data-token-ownership.mjs +3 -3
  161. package/assets/docs/author-a-theme.doc.mjs +2 -2
  162. package/assets/docs/is-my-code-good.doc.mjs +287 -0
  163. package/assets/docs/tree/add-a-component.doc.mjs +1 -1
  164. package/assets/docs/tree/add-a-theme.doc.mjs +1 -1
  165. package/assets/docs/tree/add-a-topic.doc.mjs +2 -2
  166. package/assets/docs/tree/agent-guidance.doc.mjs +1 -1
  167. package/assets/docs/tree/api.doc.mjs +6 -0
  168. package/assets/docs/tree/{integrations.doc.mjs → authoring.doc.mjs} +15 -6
  169. package/assets/docs/tree/block-template.doc.mjs +1 -1
  170. package/assets/docs/tree/build-the-template.doc.mjs +1 -1
  171. package/assets/docs/tree/building-blocks.doc.mjs +2 -2
  172. package/assets/docs/tree/check-the-props.doc.mjs +1 -1
  173. package/assets/docs/tree/check-your-docs.doc.mjs +2 -2
  174. package/assets/docs/tree/checks.doc.mjs +3 -3
  175. package/assets/docs/tree/ci-and-containers.doc.mjs +13 -0
  176. package/assets/docs/tree/cli-command.doc.mjs +1 -1
  177. package/assets/docs/tree/cli.doc.mjs +1 -1
  178. package/assets/docs/tree/codemods.doc.mjs +1 -1
  179. package/assets/docs/tree/component-family.doc.mjs +1 -1
  180. package/assets/docs/tree/component-imports.doc.mjs +1 -1
  181. package/assets/docs/tree/components.doc.mjs +1 -1
  182. package/assets/docs/tree/configuration.doc.mjs +1 -1
  183. package/assets/docs/tree/debug-and-gap-reports.doc.mjs +1 -1
  184. package/assets/docs/tree/define-the-theme.doc.mjs +1 -1
  185. package/assets/docs/tree/describe-the-component.doc.mjs +1 -1
  186. package/assets/docs/tree/docs.doc.mjs +1 -1
  187. package/assets/docs/tree/document-the-template.doc.mjs +1 -1
  188. package/assets/docs/tree/document-the-theme.doc.mjs +1 -1
  189. package/assets/docs/tree/export-template-assets.doc.mjs +1 -1
  190. package/assets/docs/tree/extend-or-replace.doc.mjs +1 -1
  191. package/assets/docs/{authoring.doc.mjs → tree/field-reference.doc.mjs} +4 -3
  192. package/assets/docs/tree/fonts-and-assets.doc.mjs +1 -1
  193. package/assets/docs/tree/generate-a-palette.doc.mjs +1 -1
  194. package/assets/docs/tree/global-integrations.doc.mjs +2 -2
  195. package/assets/docs/tree/grade-template-with-agent.doc.mjs +4 -4
  196. package/assets/docs/tree/help.doc.mjs +2 -2
  197. package/assets/docs/tree/host-entry.doc.mjs +128 -0
  198. package/assets/docs/tree/links.doc.mjs +1 -1
  199. package/assets/docs/tree/package-and-test.doc.mjs +1 -1
  200. package/assets/docs/tree/page-template.doc.mjs +1 -1
  201. package/assets/docs/tree/publishing.doc.mjs +1 -1
  202. package/assets/docs/tree/quick-start.doc.mjs +1 -1
  203. package/assets/docs/tree/replace-a-core-component.doc.mjs +1 -1
  204. package/assets/docs/tree/replace-a-core-template.doc.mjs +1 -1
  205. package/assets/docs/tree/sections-and-placement.doc.mjs +1 -1
  206. package/assets/docs/tree/see-it-in-an-app.doc.mjs +1 -1
  207. package/assets/docs/tree/ship.doc.mjs +2 -2
  208. package/assets/docs/tree/short-and-findable.doc.mjs +1 -1
  209. package/assets/docs/tree/single-component.doc.mjs +1 -1
  210. package/assets/docs/tree/start-a-template.doc.mjs +1 -1
  211. package/assets/docs/tree/subcomponent.doc.mjs +1 -1
  212. package/assets/docs/tree/template-assets.doc.mjs +1 -1
  213. package/assets/docs/tree/template-doc-overview.doc.mjs +2 -2
  214. package/assets/docs/tree/template-fonts.doc.mjs +1 -1
  215. package/assets/docs/tree/template-grading-rubric.doc.mjs +17 -38
  216. package/assets/docs/tree/template-icons.doc.mjs +1 -1
  217. package/assets/docs/tree/template-images-media.doc.mjs +1 -1
  218. package/assets/docs/tree/template-styles.doc.mjs +1 -1
  219. package/assets/docs/tree/templates.doc.mjs +1 -1
  220. package/assets/docs/tree/test-in-an-app.doc.mjs +1 -1
  221. package/assets/docs/tree/test-template-in-app.doc.mjs +1 -1
  222. package/assets/docs/tree/themes.doc.mjs +1 -1
  223. package/assets/docs/tree/tokens-data.doc.mjs +2 -2
  224. package/assets/docs/tree/troubleshooting.doc.mjs +1 -1
  225. package/assets/docs/tree/upgrading.doc.mjs +1 -1
  226. package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +1 -1
  227. package/assets/docs/tree/verify-packed-template.doc.mjs +1 -1
  228. package/assets/docs/tree/versioning.doc.mjs +5 -1
  229. package/assets/docs/tree/write-good-templates.doc.mjs +1 -1
  230. package/assets/docs/tree/write-the-template-file.doc.mjs +1 -1
  231. package/assets/docs/use-integrations.doc.mjs +120 -0
  232. package/assets/docs/working-with-ai.doc.mjs +6 -2
  233. package/assets/templates/themes/butter/butterTheme.ts +4 -2
  234. package/assets/templates/themes/stone/stoneTheme.ts +4 -2
  235. package/assets/templates/themes/y2k/y2kTheme.ts +4 -2
  236. package/authoring/debug/parse.d.mts +1 -1
  237. package/authoring/doctypes/schema/schema.doc.mjs +1 -1
  238. package/authoring/integration/integration.doc.mjs +1 -1
  239. package/authoring/integration/type.ts +1 -1
  240. package/clients/cli/bin/astryx.mjs +7 -78
  241. package/clients/cli/command-result-coverage.test.mjs +6 -5
  242. package/clients/cli/commands/build-theme.color-scheme.test.mjs +1 -1
  243. package/clients/cli/commands/{component/index.mjs → component.mjs} +37 -37
  244. package/clients/cli/commands/component.test.mjs +6 -6
  245. package/clients/cli/commands/docs.doc.mjs +2 -2
  246. package/clients/cli/commands/docs.mjs +1 -1
  247. package/clients/cli/commands/docs.test.mjs +14 -14
  248. package/clients/cli/commands/doctor-integration.test.mjs +49 -1
  249. package/clients/cli/commands/doctor.doc.mjs +1 -1
  250. package/clients/cli/commands/doctor.mjs +40 -0
  251. package/clients/cli/commands/doctor.test.mjs +65 -0
  252. package/clients/cli/commands/{hook/index.mjs → hook.mjs} +22 -22
  253. package/clients/cli/commands/import-hint-correctness.test.mjs +1 -1
  254. package/clients/cli/commands/init.behavior.test.mjs +230 -1
  255. package/clients/cli/commands/init.doc.mjs +28 -9
  256. package/clients/cli/commands/init.flag-help.test.mjs +1 -0
  257. package/clients/cli/commands/init.mjs +2 -2
  258. package/clients/cli/commands/integration-check.test.mjs +48 -0
  259. package/clients/cli/commands/integration.doc.mjs +1 -1
  260. package/clients/cli/commands/manifest.doc.mjs +1 -1
  261. package/clients/cli/commands/manifest.mjs +67 -0
  262. package/clients/cli/commands/outside-project.test.mjs +101 -1
  263. package/clients/cli/commands/postinstall.doc.mjs +23 -0
  264. package/clients/cli/commands/postinstall.mjs +31 -0
  265. package/clients/cli/commands/setup-nudge.test.mjs +78 -0
  266. package/clients/cli/commands/{build-theme.mjs → theme.mjs} +1 -1
  267. package/clients/cli/commands/upgrade.doc.mjs +4 -2
  268. package/clients/cli/commands/upgrade.mjs +9 -2
  269. package/clients/cli/commands/upgrade.subfolder-path.test.mjs +97 -0
  270. package/clients/cli/index.mjs +54 -94
  271. package/clients/cli/lib/component-format.mjs +1 -1
  272. package/clients/cli/lib/define-command.mjs +12 -7
  273. package/clients/cli/lib/exit-codes.test.mjs +5 -0
  274. package/clients/cli/lib/manifest.test.mjs +12 -0
  275. package/clients/cli/lib/welcome-box.mjs +50 -0
  276. package/clients/cli/run.mjs +108 -0
  277. package/foundation/agent-docs/agent-doc-state.d.mts +32 -7
  278. package/foundation/agent-docs/agent-doc-state.mjs +58 -9
  279. package/foundation/agent-docs/agent-docs.d.mts +100 -12
  280. package/foundation/agent-docs/agent-docs.mjs +1017 -105
  281. package/foundation/agent-docs/agent-docs.path-safety.test.mjs +1239 -17
  282. package/foundation/agent-docs/agent-docs.test.mjs +683 -39
  283. package/foundation/agent-docs/computer-agent-text.d.mts +39 -2
  284. package/foundation/agent-docs/computer-agent-text.mjs +135 -8
  285. package/foundation/agent-docs/computer-agent-text.test.mjs +99 -2
  286. package/foundation/config/project.mjs +20 -22
  287. package/foundation/config/project.test.mjs +56 -0
  288. package/foundation/discovery/authoring-self-docs.d.mts +7 -0
  289. package/foundation/discovery/authoring-self-docs.mjs +16 -4
  290. package/foundation/discovery/authoring-self-docs.test.mjs +15 -3
  291. package/foundation/discovery/authoring-surface.d.mts +1 -1
  292. package/foundation/discovery/authoring-surface.mjs +2 -2
  293. package/foundation/discovery/authoring-surface.test.mjs +1 -1
  294. package/foundation/discovery/cli-self-docs.d.mts +2 -1
  295. package/foundation/discovery/cli-self-docs.mjs +4 -3
  296. package/foundation/discovery/cli-self-docs.test.mjs +2 -2
  297. package/foundation/discovery/component-discovery.d.mts +37 -0
  298. package/foundation/discovery/component-discovery.mjs +80 -5
  299. package/foundation/discovery/component-loader.d.mts +1 -1
  300. package/foundation/discovery/docs-discovery.mjs +14 -1
  301. package/foundation/discovery/module-exports.d.mts +41 -0
  302. package/foundation/discovery/module-exports.mjs +253 -0
  303. package/foundation/discovery/runner-allowlist.d.mts +2 -4
  304. package/foundation/discovery/runner-allowlist.mjs +2 -4
  305. package/foundation/discovery/runner-commands.d.mts +77 -0
  306. package/foundation/discovery/runner-commands.mjs +294 -0
  307. package/foundation/discovery/runner-commands.test.mjs +128 -0
  308. package/foundation/discovery/stray-pointers.test.mjs +1 -1
  309. package/foundation/discovery/template-adapter.mjs +18 -1
  310. package/foundation/discovery/template-needs.mjs +1 -44
  311. package/foundation/discovery/theme-discovery.mjs +147 -3
  312. package/foundation/doc-compiler/doc-loads.test.mjs +33 -9
  313. package/foundation/doc-compiler/read.mjs +59 -1
  314. package/foundation/doc-compiler/read.test.mjs +11 -2
  315. package/foundation/doc-compiler/split-topic-keywords.test.mjs +3 -2
  316. package/foundation/doc-compiler/tree.d.mts +32 -6
  317. package/foundation/doc-compiler/tree.mjs +77 -43
  318. package/foundation/doc-compiler/tree.test.mjs +7 -7
  319. package/foundation/fs/paths.d.mts +70 -8
  320. package/foundation/fs/paths.mjs +556 -38
  321. package/foundation/fs/paths.test.mjs +430 -54
  322. package/foundation/fs/read-record.d.mts +18 -0
  323. package/foundation/fs/read-record.mjs +131 -0
  324. package/foundation/fs/read-record.test.mjs +77 -0
  325. package/foundation/integrations/autolink.d.mts +2 -1
  326. package/foundation/integrations/autolink.mjs +4 -13
  327. package/foundation/integrations/cli-peer-version.d.mts +36 -0
  328. package/foundation/integrations/cli-peer-version.mjs +132 -0
  329. package/foundation/integrations/cli-peer-version.test.mjs +354 -0
  330. package/foundation/integrations/computer-integrations.d.mts +6 -3
  331. package/foundation/integrations/computer-integrations.mjs +38 -9
  332. package/foundation/integrations/contribution-inventory.d.mts +15 -20
  333. package/foundation/integrations/contribution-inventory.mjs +41 -5
  334. package/foundation/integrations/exports-subpaths.d.mts +57 -0
  335. package/foundation/integrations/exports-subpaths.mjs +113 -0
  336. package/foundation/integrations/integrations.d.mts +27 -1
  337. package/foundation/integrations/integrations.mjs +61 -2
  338. package/foundation/integrations/public-imports.d.mts +118 -0
  339. package/foundation/integrations/public-imports.mjs +1769 -0
  340. package/foundation/integrations/public-imports.test.mjs +1793 -0
  341. package/foundation/integrations/validate-contributions.d.mts +115 -0
  342. package/foundation/integrations/validate-contributions.mjs +1168 -13
  343. package/foundation/release/cache.test.mjs +24 -21
  344. package/foundation/release/global-state.d.mts +8 -4
  345. package/foundation/release/global-state.mjs +4 -2
  346. package/foundation/release/handoff.d.mts +23 -4
  347. package/foundation/release/handoff.mjs +175 -8
  348. package/foundation/release/notices.d.mts +62 -8
  349. package/foundation/release/notices.mjs +136 -25
  350. package/foundation/release/notices.test.mjs +21 -0
  351. package/foundation/release/run-context.mjs +14 -0
  352. package/foundation/response/response-types.doc.mjs +1 -1
  353. package/foundation/route/route.d.mts +285 -0
  354. package/foundation/route/route.mjs +324 -0
  355. package/foundation/route/route.test.mjs +153 -0
  356. package/foundation/runtime/hosted-runtime.test.mjs +403 -0
  357. package/foundation/runtime/locked-versions.d.mts +32 -0
  358. package/foundation/runtime/locked-versions.mjs +65 -0
  359. package/foundation/runtime/package-root.d.mts +91 -0
  360. package/foundation/runtime/package-root.mjs +174 -0
  361. package/foundation/runtime/package-set.d.mts +62 -0
  362. package/foundation/runtime/package-set.mjs +130 -0
  363. package/foundation/runtime/package-set.test.mjs +432 -0
  364. package/foundation/text/string-utils.d.mts +2 -1
  365. package/foundation/text/string-utils.mjs +16 -5
  366. package/foundation/text/suggest-order.d.mts +72 -0
  367. package/foundation/text/suggest-order.mjs +363 -0
  368. package/foundation/text/suggest-order.test.mjs +259 -0
  369. package/host/door-conformance.test.mjs +274 -0
  370. package/host/index.d.ts +116 -0
  371. package/host/index.mjs +295 -0
  372. package/host/index.test.mjs +302 -0
  373. package/package.json +14 -9
@@ -28,7 +28,15 @@ import * as fs from 'node:fs';
28
28
  import * as path from 'node:path';
29
29
 
30
30
  import {MIN_NODE_VERSION, isNodeVersionSupported} from '../../foundation/env/node-version.mjs';
31
- import {AGENT_DOC_PATHS} from '../../foundation/agent-docs/agent-doc-state.mjs';
31
+ import {
32
+ AGENT_DOC_PATHS,
33
+ holdsAstryxBlock,
34
+ } from '../../foundation/agent-docs/agent-doc-state.mjs';
35
+ import {
36
+ agentDocsTargetDir,
37
+ assertAgentDocsTarget,
38
+ resolveAgentDocsRoot,
39
+ } from '../../foundation/agent-docs/agent-docs.mjs';
32
40
  import {CLI_ROOT, findProjectCoreDir, findInstalledPackage} from '../../foundation/fs/paths.mjs';
33
41
  import {explainPackageManager, formatCliCommand, getCliInvocation, guessCliInvocation} from '../../foundation/env/package-manager.mjs';
34
42
  import {chooseDeclaredCliCommand, declaredCliCommandsOf} from '../../foundation/env/cli-command.mjs';
@@ -57,8 +65,9 @@ import {typedEdges} from '../docs/node/node.mjs';
57
65
  import {detailView, indexView} from '../../foundation/doc-compiler/lenses.mjs';
58
66
  import {semverCompare, isValidSemver, satisfiesRange} from '../../foundation/env/semver.mjs';
59
67
  import {checkAppThemes, checkThemes} from './theme-checks.mjs';
60
- import {availableLine, upgradeLine} from '../../foundation/release/notices.mjs';
61
- import {alignCommand, addCliCommandFor, computerAgentTextState, installCommandFor, projectCoreVersion, projectHasOwnCli, computerIntegrationsOutside, computerIntegrationsToAdd, displayHomePath, outsideProject, releaseFacts} from './_adapter.mjs';
68
+ import {availableLine, RUN_FROM_PROJECT, upgradeLine} from '../../foundation/release/notices.mjs';
69
+ import {COMPUTER_COMMAND, COMPUTER_INSTALL_STEP} from '../../foundation/agent-docs/computer-agent-text.mjs';
70
+ import {alignCommand, addCliCommandFor, computerAgentTextState, installCommandFor, insideProjectElsewhere, projectCoreVersion, projectHasOwnCli, computerIntegrationsOutside, computerIntegrationsToAdd, displayHomePath, outsideProject, releaseFacts} from './_adapter.mjs';
62
71
 
63
72
  /**
64
73
  * @typedef {'pass'|'warn'|'fail'|'info'} DoctorStatus
@@ -278,15 +287,25 @@ export function checkVersionAlignment(ctx) {
278
287
  // the run (spec AST-081 FR2): say so, and how to add the CLI at Core's
279
288
  // version.
280
289
  if (ctx.ownCli === false) {
290
+ // The same comparison as the daily hint: the CLI against the Core the
291
+ // run reads (spec AST-081 FR2). The command adds the CLI in the project
292
+ // spec:AST-040 FR9 defines; from a folder inside it whose package.json
293
+ // is not the project's, or in no project, the fix says how to reach one.
294
+ const add = addCliCommandFor(ctx.cwd, coreVersion);
295
+ const matches = coreVersion === cliVersion;
296
+ const fix = add
297
+ ? `${matches ? 'To pin it for the team and CI' : 'To match'}, run: ${add.command}.`
298
+ : matches
299
+ ? null
300
+ : NO_PROJECT_FIX;
281
301
  return {
282
302
  id: 'version-alignment',
283
303
  label: '@astryxdesign/core <-> @astryxdesign/cli alignment',
284
- status: coreVersion === cliVersion ? 'info' : 'warn',
285
- message:
286
- coreVersion === cliVersion
287
- ? `This project has no Astryx CLI installed; CLI ${cliVersion}, the one you started, answered and matches its Core.`
288
- : `This project has no Astryx CLI installed; CLI ${cliVersion}, the one you started, answered while its Core is ${coreVersion}.`,
289
- fix: `${coreVersion === cliVersion ? 'To pin it for the team and CI' : 'To match'}, run: ${addCliCommandFor(ctx.cwd, coreVersion)}.`,
304
+ status: matches ? 'info' : 'warn',
305
+ message: matches
306
+ ? `This project has no Astryx CLI installed; CLI ${cliVersion}, the one you started, answered and matches its Core.`
307
+ : `This project has no Astryx CLI installed; CLI ${cliVersion}, the one you started, answered while its Core is ${coreVersion}.`,
308
+ ...(fix ? {fix} : {}),
290
309
  };
291
310
  }
292
311
  // The project's own CLI answers this project whatever its version
@@ -300,10 +319,31 @@ export function checkVersionAlignment(ctx) {
300
319
  message: drift
301
320
  ? `This project's CLI ${cliVersion} answers it, but its Core is ${coreVersion}: templates, docs, and agent text come from CLI ${cliVersion}, components from Core ${coreVersion}.`
302
321
  : `@astryxdesign/core v${coreVersion} is in step with @astryxdesign/cli v${cliVersion}.`,
303
- ...(drift ? {fix: `To align them, run: ${alignCommand(ctx.cwd, cliVersion, coreVersion)}.`} : {}),
322
+ ...(drift ? alignFix(ctx.cwd, cliVersion, coreVersion) : {}),
304
323
  };
305
324
  }
306
325
 
326
+ /**
327
+ * The fix for a CLI and Core on different releases, when a command fits.
328
+ * @param {string} cwd
329
+ * @param {string} cli
330
+ * @param {string} core
331
+ * @returns {{fix?: string}}
332
+ */
333
+ function alignFix(cwd, cli, core) {
334
+ // From a folder inside a project whose package.json is not the project's,
335
+ // aligning the project would leave this warning in place, so no command
336
+ // fits there (alignCommand returns none): point at the project instead.
337
+ const command = alignCommand(cwd, cli, core);
338
+ return {fix: command ? `To align them, run: ${command}.` : NO_PROJECT_FIX};
339
+ }
340
+
341
+ /**
342
+ * The fix where no install command fits: the folder's package.json is not an
343
+ * Astryx project's (spec AST-040 FR9), and adding there would make it one.
344
+ */
345
+ const NO_PROJECT_FIX = RUN_FROM_PROJECT;
346
+
307
347
  /**
308
348
  * Check 5 — astryx.config.mjs (if present) loads and has a valid shape.
309
349
  * @param {DoctorContext} ctx
@@ -610,6 +650,21 @@ export function checkConfiguredIntegrations(ctx) {
610
650
  };
611
651
  }
612
652
 
653
+ /**
654
+ * Why init writes no agent docs for `cwd`, or null when it does: the folder
655
+ * it would write to is the home folder or the top of a settings folder in it.
656
+ * @param {string} cwd
657
+ * @returns {string | null}
658
+ */
659
+ function agentDocsRefusal(cwd) {
660
+ try {
661
+ assertAgentDocsTarget(agentDocsTargetDir(cwd));
662
+ return null;
663
+ } catch (err) {
664
+ return err instanceof Error && err.name === 'AgentDocsHomeError' ? err.message : null;
665
+ }
666
+ }
667
+
613
668
  /**
614
669
  * Check 7 — agent docs exist and contain the Astryx section markers.
615
670
  * @param {DoctorContext} ctx
@@ -617,8 +672,15 @@ export function checkConfiguredIntegrations(ctx) {
617
672
  */
618
673
  export function checkAgentDocs(ctx) {
619
674
  // Every file init can write (one shared list), looked for in the working
620
- // directory and in the project root: run from src/, the docs sit at the root.
621
- const dirs = [...new Set([ctx.cwd, projectRootOf(ctx)])];
675
+ // directory, the project root, and the folder init writes them to: run from
676
+ // src/ or a nested package, the docs sit at the root.
677
+ const dirs = [
678
+ ...new Set(
679
+ [ctx.cwd, projectRootOf(ctx), resolveAgentDocsRoot(ctx.cwd) ?? ctx.cwd].map(dir =>
680
+ path.resolve(dir),
681
+ ),
682
+ ),
683
+ ];
622
684
  const present = dirs.flatMap(dir =>
623
685
  AGENT_DOC_PATHS.map(rel => path.join(dir, rel)).filter(abs =>
624
686
  fs.existsSync(abs),
@@ -626,6 +688,15 @@ export function checkAgentDocs(ctx) {
626
688
  );
627
689
  /** @param {string} abs */
628
690
  const shown = abs => path.relative(ctx.cwd, abs) || path.basename(abs);
691
+ // In the home folder, or at the top of a settings folder in it, init
692
+ // writes no agent docs (INV31), so the fix is moving the project or a hand
693
+ // edit, never init.
694
+ const refusal = agentDocsRefusal(ctx.cwd);
695
+ /** @param {string} initFix */
696
+ const fixFor = initFix =>
697
+ refusal
698
+ ? `${refusal} Move the project into a folder of its own, or edit the Astryx block in its agent file by hand.`
699
+ : initFix;
629
700
 
630
701
  if (present.length === 0) {
631
702
  return {
@@ -633,29 +704,64 @@ export function checkAgentDocs(ctx) {
633
704
  label: 'AI agent docs',
634
705
  status: 'info',
635
706
  message: `No agent docs found: looked for ${AGENT_DOC_PATHS.join(', ')} in ${dirs.map(dir => path.relative(ctx.cwd, dir) || '.').join(' and ')}.`,
636
- fix: `Generate agent docs with \`${getCliInvocation(ctx.cwd)} init --features agents\`.`,
707
+ fix: fixFor(
708
+ `Generate agent docs with \`${getCliInvocation(ctx.cwd)} init --features agents\`.`,
709
+ ),
637
710
  };
638
711
  }
639
712
 
640
- const withMarkers = present.filter(abs => {
713
+ /** @param {string} abs */
714
+ const read = abs => {
641
715
  try {
642
- const content = fs.readFileSync(abs, 'utf-8');
643
- return (
644
- (content.includes('<!-- ASTRYX:START -->') || content.includes('<!-- XDS:START -->')) &&
645
- (content.includes('<!-- ASTRYX:END -->') || content.includes('<!-- XDS:END -->'))
646
- );
716
+ return fs.readFileSync(abs, 'utf-8');
647
717
  } catch {
648
- return false;
718
+ return '';
649
719
  }
720
+ };
721
+ const withMarkers = present.filter(abs => {
722
+ const content = read(abs);
723
+ return (
724
+ (content.includes('<!-- ASTRYX:START -->') || content.includes('<!-- XDS:START -->')) &&
725
+ (content.includes('<!-- ASTRYX:END -->') || content.includes('<!-- XDS:END -->')) &&
726
+ holdsAstryxBlock(content)
727
+ );
650
728
  });
651
729
 
730
+ // A managed block that only imports another file (init's GEMINI.md import
731
+ // of AGENTS.md) is broken when that file holds no block: the agents that
732
+ // read either file get no Astryx docs.
733
+ const dangling = present.flatMap(abs => {
734
+ const match = /<!-- ASTRYX:START -->\s*@(\S+)\s*<!-- ASTRYX:END -->/.exec(read(abs));
735
+ if (match == null) return [];
736
+ const target = path.resolve(path.dirname(abs), match[1]);
737
+ return holdsAstryxBlock(read(target)) ? [] : [{abs, target}];
738
+ });
739
+ if (dangling.length > 0) {
740
+ return {
741
+ id: 'agent-docs',
742
+ label: 'AI agent docs',
743
+ status: 'fail',
744
+ message: dangling
745
+ .map(
746
+ ({abs, target}) =>
747
+ `${shown(abs)} imports ${shown(target)}, which holds no Astryx block, so agents that read them get no Astryx docs.`,
748
+ )
749
+ .join(' '),
750
+ fix: fixFor(
751
+ `Write the block again with \`${getCliInvocation(ctx.cwd)} init --features agents\`.`,
752
+ ),
753
+ };
754
+ }
755
+
652
756
  if (withMarkers.length === 0) {
653
757
  return {
654
758
  id: 'agent-docs',
655
759
  label: 'AI agent docs',
656
760
  status: 'warn',
657
761
  message: `Agent docs present (${present.map(shown).join(', ')}) but no Astryx section markers found.`,
658
- fix: `Add the Astryx section to your agent docs with \`${getCliInvocation(ctx.cwd)} init --features agents\`.`,
762
+ fix: fixFor(
763
+ `Add the Astryx section to your agent docs with \`${getCliInvocation(ctx.cwd)} init --features agents\`.`,
764
+ ),
659
765
  };
660
766
  }
661
767
 
@@ -974,16 +1080,20 @@ function nameTypes(names) {
974
1080
  }
975
1081
 
976
1082
  /**
977
- * The section keys `astryx docs authoring --index` lists, read the way that
978
- * command reads them.
1083
+ * The section keys the field reference lists (`astryx docs
1084
+ * authoring/field-reference --index`), read the way that command reads them.
979
1085
  * @returns {Promise<{keys: Set<string>} | {keys: null, error: string}>}
980
1086
  */
981
1087
  async function authoringTopicKeys() {
982
1088
  try {
1089
+ const {AUTHORING_REFERENCE_ROUTE} =
1090
+ await import('../../foundation/discovery/authoring-self-docs.mjs');
983
1091
  const catalog = DocsCatalog.fromBuiltins();
984
- const entry = catalog.resolve('authoring');
985
- if (!entry) return {keys: null, error: 'it is not a built-in topic'};
986
- const index = indexView(await lowerTopic(catalog, entry));
1092
+ const node = (await projectTree(catalog)).get(AUTHORING_REFERENCE_ROUTE);
1093
+ if (!node?.ref?.topicFile) {
1094
+ return {keys: null, error: 'the docs tree has no field reference'};
1095
+ }
1096
+ const index = indexView(await lowerTopic(catalog, guideEntry(node)));
987
1097
  return {keys: new Set(index.sections.map(section => section.id))};
988
1098
  } catch (err) {
989
1099
  return {
@@ -1002,7 +1112,7 @@ function surfaceProblems(surface) {
1002
1112
  const problems = [];
1003
1113
  if (surface.types === 0 && surface.untraced.length === 0) {
1004
1114
  problems.push(
1005
- `@astryxdesign/cli/authoring exports no types, so nothing was compared with \`${formatCliCommand('docs authoring')}\``,
1115
+ `@astryxdesign/cli/authoring exports no types, so nothing was compared with \`${formatCliCommand('docs authoring/field-reference')}\``,
1006
1116
  );
1007
1117
  }
1008
1118
  for (const {module, names, reason, source, key} of surface.unreadable) {
@@ -1015,7 +1125,7 @@ function surfaceProblems(surface) {
1015
1125
  ? `${source} does not load`
1016
1126
  : `${source} renders section "${key}", which the topic's index does not list`;
1017
1127
  problems.push(
1018
- `${nameTypes(names)} from ${module} ${names.length === 1 ? 'has' : 'have'} no doc in \`${formatCliCommand('docs authoring')}\`: ${why}`,
1128
+ `${nameTypes(names)} from ${module} ${names.length === 1 ? 'has' : 'have'} no doc in \`${formatCliCommand('docs authoring/field-reference')}\`: ${why}`,
1019
1129
  );
1020
1130
  }
1021
1131
  for (const {name, reason} of surface.untraced) {
@@ -1056,7 +1166,7 @@ export async function checkAuthoringDocs(_ctx, options = {}) {
1056
1166
  const audit = await auditAuthoringSelfDocs({root, sources});
1057
1167
  const problems = [
1058
1168
  ...audit.unreachable.map(
1059
- source => `${source} is not in \`${formatCliCommand('docs authoring')}\``,
1169
+ source => `${source} is not in \`${formatCliCommand('docs authoring/field-reference')}\``,
1060
1170
  ),
1061
1171
  ...audit.failed.map(
1062
1172
  ({source, error}) => `${source} failed to load: ${error}`,
@@ -1072,7 +1182,7 @@ export async function checkAuthoringDocs(_ctx, options = {}) {
1072
1182
  : {keys: options.topicKeys};
1073
1183
  const surface = [
1074
1184
  ...('error' in topic
1075
- ? [`\`${formatCliCommand('docs authoring')}\` could not be read: ${topic.error}`]
1185
+ ? [`\`${formatCliCommand('docs authoring/field-reference')}\` could not be read: ${topic.error}`]
1076
1186
  : []),
1077
1187
  ...surfaceProblems(
1078
1188
  await auditAuthoringSurface({root, sources, topicKeys: topic.keys}),
@@ -1096,7 +1206,7 @@ export async function checkAuthoringDocs(_ctx, options = {}) {
1096
1206
  id,
1097
1207
  label,
1098
1208
  status: 'pass',
1099
- message: `All ${audit.sections} authoring schemas are readable in \`${formatCliCommand('docs authoring')}\`.`,
1209
+ message: `All ${audit.sections} authoring schemas are readable in \`${formatCliCommand('docs authoring/field-reference')}\`.`,
1100
1210
  };
1101
1211
  } catch (err) {
1102
1212
  return {
@@ -1164,7 +1274,7 @@ export async function checkCliDocs(_ctx, options = {}) {
1164
1274
  id,
1165
1275
  label,
1166
1276
  status: 'pass',
1167
- message: `All ${audit.docs} CLI docs are readable: ${audit.tree} in the \`${formatCliCommand('docs cli')}\` tree and ${audit.authoring} in \`${formatCliCommand('docs authoring')}\`.`,
1277
+ message: `All ${audit.docs} CLI docs are readable: ${audit.tree} in the \`${formatCliCommand('docs cli')}\` tree and ${audit.authoring} in \`${formatCliCommand('docs authoring/field-reference')}\`.`,
1168
1278
  };
1169
1279
  } catch (err) {
1170
1280
  return {
@@ -1281,7 +1391,7 @@ export async function checkDocsProgressiveDisclosure(ctx) {
1281
1391
  // A guide the docs tree places is a topic read by its route; it is held
1282
1392
  // to the same budget as every flat topic.
1283
1393
  const guides = [...(await projectTree(catalog)).nodes.values()]
1284
- // A flat topic in the Unorganized level is checked above, as a topic.
1394
+ // A flat topic in a topic level is checked above, as a topic.
1285
1395
  .filter(node => node.kind === 'generic' && !node.ref?.flatTopic)
1286
1396
  .map(guideEntry);
1287
1397
  for (const entry of [...catalog.entries(), ...guides]) {
@@ -1382,6 +1492,10 @@ export function checkCliCommand(ctx) {
1382
1492
  * each file of the computer-level agent text against this release's rendering
1383
1493
  * @property {import('../../foundation/env/user-settings.mjs').ComputerAgentTextSettings} [agentTextSettings]
1384
1494
  * the person's choices about the computer-level agent text
1495
+ * @property {string | null} [agentTextCommand] the command the skill file
1496
+ * tells agents to run, or null
1497
+ * @property {string | null} [agentTextInstall] the install step the skill
1498
+ * file records for when that command is not found, or null
1385
1499
  * @property {string} [cwd] the folder doctor runs in, for the printed fix
1386
1500
  * @property {import('../../foundation/release/notices.mjs').GlobalBehind | null} [globalBehind]
1387
1501
  * the CLI installed for the computer, when it is older than the newest
@@ -1394,7 +1508,7 @@ export function checkCliCommand(ctx) {
1394
1508
  * the newest update notice that did not print, and why (diagnostic only)
1395
1509
  * @property {{pid: number | null, host: string | null, since: number} | null} [stateLock]
1396
1510
  * who holds the state file's lock now (diagnostic only)
1397
- * @property {Array<import('../../foundation/env/search-path.mjs').SearchPathCommand & {upgrade?: string | null}>} [searchPathCommands]
1511
+ * @property {Array<import('../../foundation/env/search-path.mjs').SearchPathCommand & {upgrade?: string | null, runner?: boolean}>} [searchPathCommands]
1398
1512
  * every `astryx` on the search path, with the command that upgrades it
1399
1513
  * the `astryx` commands on the search path, in order
1400
1514
  * @property {import('../../foundation/integrations/computer-integrations.mjs').ComputerIntegrationCandidate[]} [computerIntegrations]
@@ -1481,6 +1595,7 @@ function checkFailureNote(failed) {
1481
1595
  const SKIP_REASONS = {
1482
1596
  'already-shown': 'it had already printed that day',
1483
1597
  'no-newer': 'no newer release was known',
1598
+ 'no-project': 'the folder belongs to no Astryx project, so no install command fits',
1484
1599
  };
1485
1600
 
1486
1601
  /**
@@ -1604,6 +1719,8 @@ export function checkComputerAgentText(ctx) {
1604
1719
  };
1605
1720
  }
1606
1721
  const entries = ctx.agentText ?? /** @type {NonNullable<typeof state>} */ (state).entries;
1722
+ const command = ctx.agentText ? (ctx.agentTextCommand ?? null) : /** @type {NonNullable<typeof state>} */ (state).command;
1723
+ const recorded = (ctx.agentText ? ctx.agentTextInstall : /** @type {NonNullable<typeof state>} */ (state).install) ?? null;
1607
1724
  const fix = `Run \`${formatCliCommand('astryx init --user', ctx.cwd)}\`.`;
1608
1725
  const [file, ...links] = entries;
1609
1726
  const home = (/** @type {{path: string}} */ e) => displayHomePath(e.path);
@@ -1632,9 +1749,29 @@ export function checkComputerAgentText(ctx) {
1632
1749
  };
1633
1750
  }
1634
1751
  const current = file.state === 'current';
1752
+ const status = `${current ? 'The computer-level agent text matches this release' : "The computer-level agent text differs from this release's"}: ${home(file)}.`;
1753
+ // The skill outlives the CLI it names when the home folder outlasts the
1754
+ // folder the CLI is installed in, as on a machine whose system folders
1755
+ // reset on restart. Reported only when no `astryx` command is on the search
1756
+ // path at all, apart from a package runner's own copy, which it puts first
1757
+ // only for the run it starts (the way back when the global CLI is gone).
1758
+ // Any other `astryx`, even one the scan cannot trace to the package, such
1759
+ // as the shim of Volta, asdf or mise, may well run the CLI, so doctor stays
1760
+ // quiet here and cli-on-path reports what it can; never a false "gone".
1761
+ // The reinstalled copy refreshes the text too.
1762
+ const rows = ctx.searchPathCommands;
1763
+ if (command === COMPUTER_COMMAND && rows && !rows.some(r => !r.runner)) {
1764
+ return {
1765
+ ...base,
1766
+ message: `${status} It tells agents to run \`${COMPUTER_COMMAND}\`, but no \`${COMPUTER_COMMAND}\` command is on the search path, so agents that read it find no CLI to run (as when a restart removes the CLI installed for the computer and keeps the home folder).${linkNote}`,
1767
+ fix: recorded
1768
+ ? `Run \`${recorded}\`, the step the skill names for when \`${COMPUTER_COMMAND}\` is not found.`
1769
+ : `Install it for the computer: \`${COMPUTER_INSTALL_STEP}\`.`,
1770
+ };
1771
+ }
1635
1772
  return {
1636
1773
  ...base,
1637
- message: `${current ? 'The computer-level agent text matches this release' : "The computer-level agent text differs from this release's"}: ${home(file)}.${linkNote}`,
1774
+ message: `${status}${linkNote}`,
1638
1775
  ...(!current || missing.length > 0 || dangling.length > 0 ? {fix} : {}),
1639
1776
  };
1640
1777
  }
@@ -1686,12 +1823,14 @@ export function checkComputerIntegrations(ctx) {
1686
1823
  * Inside a project, the integrations installed for the computer that the
1687
1824
  * project does not install: available, never used (spec AST-081 FR12).
1688
1825
  * Always info.
1689
- * @param {{installed: Set<string>, candidates: import('../../foundation/integrations/computer-integrations.mjs').ComputerIntegrationCandidate[], install: (name: string) => string}} input
1826
+ * @param {{installed: Set<string>, candidates: import('../../foundation/integrations/computer-integrations.mjs').ComputerIntegrationCandidate[], install: ((name: string) => string) | null, elsewhere?: boolean}} input
1690
1827
  * `install`: the command that adds a package with the project's package
1691
- * manager (read by the adapter)
1828
+ * manager (read by the adapter); null when the folder belongs to no
1829
+ * project, or `elsewhere`: its package.json is not the project's, where the
1830
+ * fix points at the project's folder
1692
1831
  * @returns {DoctorCheck}
1693
1832
  */
1694
- export function checkComputerIntegrationsAvailable({installed, candidates, install}) {
1833
+ export function checkComputerIntegrationsAvailable({installed, candidates, install, elsewhere = false}) {
1695
1834
  const available = candidates.filter(c => !installed.has(c.name));
1696
1835
  const base = {id: 'computer-integrations', label: 'Integrations installed for this computer', status: /** @type {const} */ ('info')};
1697
1836
  if (available.length === 0) {
@@ -1701,7 +1840,11 @@ export function checkComputerIntegrationsAvailable({installed, candidates, insta
1701
1840
  return {
1702
1841
  ...base,
1703
1842
  message: `Installed for this computer but not in this project, so not used here: ${available.map(c => c.name).join(', ')}.`,
1704
- fix: `To use one in this project, run ${available.map(c => `\`${add(c.name)}\``).join(' or ')}.`,
1843
+ ...(add
1844
+ ? {fix: `To use one in this project, run ${available.map(c => `\`${add(c.name)}\``).join(' or ')}.`}
1845
+ : elsewhere
1846
+ ? {fix: NO_PROJECT_FIX}
1847
+ : {}),
1705
1848
  };
1706
1849
  }
1707
1850
 
@@ -1960,7 +2103,7 @@ export async function runChecks(options = {}) {
1960
2103
  {
1961
2104
  const loadedNames = (project?.loadedIntegrations ?? []).map((/** @type {{name: string}} */ i) => i.name);
1962
2105
  const candidates = await computerIntegrationsToAdd(cwd, loadedNames);
1963
- checks.push(checkComputerIntegrationsAvailable({installed: new Set(loadedNames), candidates, install: installCommandFor(cwd)}));
2106
+ checks.push(checkComputerIntegrationsAvailable({installed: new Set(loadedNames), candidates, install: installCommandFor(cwd), elsewhere: insideProjectElsewhere(cwd)}));
1964
2107
  }
1965
2108
  checks.push(await checkAuthoringDocs(ctx));
1966
2109
  checks.push(await checkCliDocs(ctx));
@@ -31,7 +31,9 @@ import {
31
31
  checkIntegrationIssues,
32
32
  checkVersionAlignment,
33
33
  checkPackageManager,
34
+ checkComputerIntegrationsAvailable,
34
35
  } from './doctor.mjs';
36
+ import {installCommandFor} from './_adapter.mjs';
35
37
  import {formatCliCommand} from '../../foundation/env/package-manager.mjs';
36
38
  import {Project} from '../../foundation/config/project.mjs';
37
39
 
@@ -223,7 +225,13 @@ describe("doctor — the project's own CLI and Core on different releases", () =
223
225
  ).version;
224
226
  /** @param {string} core */
225
227
  const at = core => {
228
+ // A project as spec:AST-040 FR9 defines it: its package.json declares
229
+ // an Astryx package.
226
230
  const dir = mkProject({
231
+ 'package.json': JSON.stringify({
232
+ name: 'app',
233
+ dependencies: {'@astryxdesign/core': core},
234
+ }),
227
235
  'package-lock.json': '{}',
228
236
  'node_modules/@astryxdesign/core/package.json': JSON.stringify({
229
237
  name: '@astryxdesign/core',
@@ -257,6 +265,10 @@ describe("doctor — the project's own CLI and Core on different releases", () =
257
265
 
258
266
  it('in a project with no CLI of its own, warns that the started copy answered and names the command that adds the CLI at Core', () => {
259
267
  const dir = mkProject({
268
+ 'package.json': JSON.stringify({
269
+ name: 'app',
270
+ dependencies: {'@astryxdesign/core': '0.0.1'},
271
+ }),
260
272
  'package-lock.json': '{}',
261
273
  'node_modules/@astryxdesign/core/package.json': JSON.stringify({
262
274
  name: '@astryxdesign/core',
@@ -279,6 +291,52 @@ describe("doctor — the project's own CLI and Core on different releases", () =
279
291
  });
280
292
  });
281
293
 
294
+ describe('the command that adds an integration installed for the computer', () => {
295
+ const app = () =>
296
+ mkProject({
297
+ 'package.json': JSON.stringify({
298
+ name: 'app',
299
+ dependencies: {'@astryxdesign/core': '0.7.0'},
300
+ }),
301
+ 'pnpm-lock.yaml': '',
302
+ 'tools/codegen/package.json': JSON.stringify({name: 'codegen'}),
303
+ });
304
+
305
+ it('adds it in the project, with its package manager', () => {
306
+ const dir = app();
307
+ expect(installCommandFor(dir)?.('@acme/a')).toBe('pnpm add @acme/a');
308
+ });
309
+
310
+ // A package.json that is not the project's (spec AST-040 FR9): a run
311
+ // there loads integrations from it, so a package added to the project
312
+ // stays unused there. No command; the fix points at the project.
313
+ it("from a tool folder inside the project, names no command and points at the project's folder", () => {
314
+ const dir = app();
315
+ expect(installCommandFor(path.join(dir, 'tools', 'codegen'))).toBeNull();
316
+ const c = checkComputerIntegrationsAvailable({
317
+ installed: new Set(),
318
+ candidates: [/** @type {any} */ ({name: '@acme/a'})],
319
+ install: null,
320
+ elsewhere: true,
321
+ });
322
+ expect(c.fix).toBe(
323
+ "Run this from the Astryx project's folder, or declare an Astryx package in this folder's package.json to make it a project of its own.",
324
+ );
325
+ });
326
+
327
+ it('in a folder that belongs to no project, names no command', () => {
328
+ const dir = mkProject({'package.json': JSON.stringify({name: 'tool'})});
329
+ expect(installCommandFor(dir)).toBeNull();
330
+ const c = checkComputerIntegrationsAvailable({
331
+ installed: new Set(),
332
+ candidates: [/** @type {any} */ ({name: '@acme/a'})],
333
+ install: null,
334
+ });
335
+ expect(c.status).toBe('info');
336
+ expect(c).not.toHaveProperty('fix');
337
+ });
338
+ });
339
+
282
340
  describe('checkCliCommand', () => {
283
341
  it('is informational when no integration declares a command', () => {
284
342
  const dir = mkProject({'pnpm-lock.yaml': ''});
@@ -652,7 +710,7 @@ describe('checkAuthoringDocs', () => {
652
710
  it('passes when every authoring self-doc is reachable and fits one read', async () => {
653
711
  const c = await checkAuthoringDocs();
654
712
  expect(c).toMatchObject({id: 'authoring-docs', status: 'pass'});
655
- expect(c.message).toContain(formatCliCommand('docs authoring'));
713
+ expect(c.message).toContain(formatCliCommand('docs authoring/field-reference'));
656
714
  }, SLOW);
657
715
  });
658
716
 
@@ -680,7 +738,7 @@ describe('checkCliDocs', () => {
680
738
  id: 'cli-docs',
681
739
  label: 'CLI docs',
682
740
  status: 'pass',
683
- message: `All ${audit.docs} CLI docs are readable: ${audit.tree} in the \`${formatCliCommand('docs cli')}\` tree and ${audit.authoring} in \`${formatCliCommand('docs authoring')}\`.`,
741
+ message: `All ${audit.docs} CLI docs are readable: ${audit.tree} in the \`${formatCliCommand('docs cli')}\` tree and ${audit.authoring} in \`${formatCliCommand('docs authoring/field-reference')}\`.`,
684
742
  });
685
743
  },
686
744
  SLOW,
@@ -790,7 +848,7 @@ describe('checkDocsTree', () => {
790
848
  describe('checkAuthoringDocs against the public authoring surface', () => {
791
849
  const NAMESPACE = 'doctypes/namespace/namespace.doc.mjs';
792
850
  const NAMESPACE_TYPES =
793
- `NamespaceDoc, NamespaceProviderScope, NamespaceSlotAcceptance, NamespaceSlot, NamespaceAdoptionSource, NamespaceAdoptionRule from doctypes/namespace/type.ts have no doc in \`${formatCliCommand('docs authoring')}\``;
851
+ `NamespaceDoc, NamespaceProviderScope, NamespaceSlotAcceptance, NamespaceSlot, NamespaceAdoptionSource, NamespaceAdoptionRule from doctypes/namespace/type.ts have no doc in \`${formatCliCommand('docs authoring/field-reference')}\``;
794
852
  const SURFACE_FIX =
795
853
  'Put a self-doc beside each module whose types @astryxdesign/cli/authoring exports and list it in AUTHORING_SELF_DOCS; export what each listed self-doc documents, or remove that self-doc.';
796
854
 
@@ -820,7 +878,7 @@ describe('checkAuthoringDocs against the public authoring surface', () => {
820
878
  id: 'authoring-docs',
821
879
  label: 'Authoring docs',
822
880
  status: 'pass',
823
- message: `All ${AUTHORING_SELF_DOCS.length} authoring schemas are readable in \`${formatCliCommand('docs authoring')}\`.`,
881
+ message: `All ${AUTHORING_SELF_DOCS.length} authoring schemas are readable in \`${formatCliCommand('docs authoring/field-reference')}\`.`,
824
882
  });
825
883
  },
826
884
  SLOW,
@@ -849,7 +907,7 @@ describe('checkAuthoringDocs against the public authoring surface', () => {
849
907
  });
850
908
  expect(c.status).toBe('fail');
851
909
  expect(c.message).toBe(
852
- `2 problems: ${NAMESPACE} is not in \`${formatCliCommand('docs authoring')}\`; ${NAMESPACE_TYPES}: ${NAMESPACE} is not listed in AUTHORING_SELF_DOCS`,
910
+ `2 problems: ${NAMESPACE} is not in \`${formatCliCommand('docs authoring/field-reference')}\`; ${NAMESPACE_TYPES}: ${NAMESPACE} is not listed in AUTHORING_SELF_DOCS`,
853
911
  );
854
912
  },
855
913
  SLOW,
@@ -858,7 +916,7 @@ describe('checkAuthoringDocs against the public authoring surface', () => {
858
916
  it(
859
917
  "fails when the topic's index no longer exposes the NamespaceDoc section",
860
918
  async () => {
861
- const index = await docs('authoring', undefined, {index: true});
919
+ const index = await docs('authoring/field-reference', undefined, {index: true});
862
920
  const topicKeys = new Set(
863
921
  index.data.sections
864
922
  .map((/** @type {any} */ s) => s.id)
@@ -947,7 +1005,7 @@ describe('checkAuthoringDocs against the public authoring surface', () => {
947
1005
  expect(c.fix).toBe(SURFACE_FIX);
948
1006
  expect(c.message).toMatch(
949
1007
  new RegExp(
950
- `^[A-Za-z, ]+ from ${escape(module)} have no doc in \`${escape(formatCliCommand('docs authoring'))}\`: no self-doc sits beside ${escape(module)}$`,
1008
+ `^[A-Za-z, ]+ from ${escape(module)} have no doc in \`${escape(formatCliCommand('docs authoring/field-reference'))}\`: no self-doc sits beside ${escape(module)}$`,
951
1009
  ),
952
1010
  );
953
1011
  for (const name of names) {
@@ -990,7 +1048,7 @@ describe('checkAuthoringDocs against the public authoring surface', () => {
990
1048
  expect(await checkAuthoringDocs()).toMatchObject({
991
1049
  status: 'fail',
992
1050
  message:
993
- `\`${formatCliCommand('docs authoring')}\` could not be read: no built-in docs`,
1051
+ `\`${formatCliCommand('docs authoring/field-reference')}\` could not be read: no built-in docs`,
994
1052
  fix: SURFACE_FIX,
995
1053
  });
996
1054
  } finally {
@@ -1011,7 +1069,7 @@ describe('checkAuthoringDocs against the public authoring surface', () => {
1011
1069
  const c = await checkAuthoringDocs(undefined, {root});
1012
1070
  expect(c.status).toBe('fail');
1013
1071
  expect(c.message).toContain(
1014
- `@astryxdesign/cli/authoring exports no types, so nothing was compared with \`${formatCliCommand('docs authoring')}\``,
1072
+ `@astryxdesign/cli/authoring exports no types, so nothing was compared with \`${formatCliCommand('docs authoring/field-reference')}\``,
1015
1073
  );
1016
1074
  },
1017
1075
  SLOW,
@@ -1317,8 +1375,12 @@ describe('a configured integration that cannot load fails the gate', () => {
1317
1375
  'astryx.config.mjs': "export default { integrations: ['@acme/widgets'] };\n",
1318
1376
  };
1319
1377
 
1320
- /** Run doctor on a project under the working directory. */
1321
- async function report(files) {
1378
+ /**
1379
+ * Run doctor on a project under the working directory, in its folder `at`.
1380
+ * @param {Record<string, string>} files
1381
+ * @param {string} [at]
1382
+ */
1383
+ async function report(files, at = '.') {
1322
1384
  const dir = fs.mkdtempSync(path.join(process.cwd(), '.astryx-doctor-gate-'));
1323
1385
  tmpDirs.push(dir);
1324
1386
  for (const [rel, content] of Object.entries(files)) {
@@ -1326,7 +1388,7 @@ describe('a configured integration that cannot load fails the gate', () => {
1326
1388
  fs.mkdirSync(path.dirname(abs), {recursive: true});
1327
1389
  fs.writeFileSync(abs, content);
1328
1390
  }
1329
- const {checks, summary} = (await doctor({cwd: dir})).data;
1391
+ const {checks, summary} = (await doctor({cwd: path.join(dir, at)})).data;
1330
1392
  return {by: Object.fromEntries(checks.map(c => [c.id, c])), summary};
1331
1393
  }
1332
1394
 
@@ -1354,6 +1416,27 @@ describe('a configured integration that cannot load fails the gate', () => {
1354
1416
  SLOW,
1355
1417
  );
1356
1418
 
1419
+ it(
1420
+ 'passes in a workspace member whose configured integration is hoisted to the root',
1421
+ async () => {
1422
+ const {by} = await report(
1423
+ {
1424
+ 'package.json': JSON.stringify({name: 'root', private: true, workspaces: ['apps/*']}),
1425
+ ...CORE_STUB,
1426
+ ...widgets(),
1427
+ 'apps/web/package.json': '{"name":"web"}',
1428
+ 'apps/web/astryx.config.mjs': "export default { integrations: ['@acme/widgets'] };\n",
1429
+ },
1430
+ 'apps/web',
1431
+ );
1432
+ expect(by['configured-integrations'].status).toBe('pass');
1433
+ expect(by['configured-integrations'].message).toBe(
1434
+ '1 integration named in astryx.config loaded: @acme/widgets.',
1435
+ );
1436
+ },
1437
+ SLOW,
1438
+ );
1439
+
1357
1440
  it(
1358
1441
  'counts the package being authored when its own config names it',
1359
1442
  async () => {
package/api/hook/hook.mjs CHANGED
@@ -43,7 +43,7 @@ export async function hook(name, options = {}) {
43
43
  zh = false,
44
44
  } = options;
45
45
 
46
- // Default detail level mirrors the CLI (see commands/hook/index.mjs):
46
+ // Default detail level mirrors the CLI (see commands/hook.mjs):
47
47
  // single-hook views default to 'full', list-style views (--list,
48
48
  // --category, or no name) default to 'brief' (scannable name lists).
49
49
  // Keeping this in sync with the CLI is what the API↔CLI parity test checks.