@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
@@ -24,6 +24,7 @@ import {
24
24
  } from '../../foundation/discovery/template-adapter.mjs';
25
25
  import {AstryxError} from '../error.mjs';
26
26
  import {ERROR_CODES} from '../../foundation/response/error-codes.mjs';
27
+ import {orderByAffinity} from '../../foundation/text/suggest-order.mjs';
27
28
  import {templateList} from './list/list.mjs';
28
29
  import {templateShow} from './show/show.mjs';
29
30
  import {templateSkeleton} from './skeleton/skeleton.mjs';
@@ -188,7 +189,10 @@ export async function template(name, options = {}) {
188
189
  if (name && candidates.length === 0) {
189
190
  throw new AstryxError(
190
191
  `Unknown template "${name}"`,
191
- templates.map(t => ({name: t.dirName, reason: `${t.type} template`})),
192
+ orderByAffinity(name, templates, t => [t.dirName, t.name]).map(t => ({
193
+ name: t.dirName,
194
+ reason: `${t.type} template`,
195
+ })),
192
196
  ERROR_CODES.ERR_UNKNOWN_TEMPLATE,
193
197
  );
194
198
  }
@@ -51,6 +51,7 @@ const PACKED_DIRS = ['clients', 'api', 'assets', 'authoring', 'foundation'];
51
51
  const RUNTIME_DEPS = [
52
52
  '@babel/parser',
53
53
  'commander',
54
+ 'ignore',
54
55
  'jiti',
55
56
  'jscodeshift',
56
57
  'zod',
@@ -23,7 +23,55 @@
23
23
  * @property {boolean} [registry]
24
24
  */
25
25
  /**
26
- * Detect the installed target version from node_modules.
26
+ * Where an upgrade runs: the root of the Astryx project `cwd` belongs to, the
27
+ * nearest folder at or above it whose package.json declares an Astryx
28
+ * package or that an Astryx workspace lists as a member, so a run from any
29
+ * folder inside a project upgrades that project exactly as a run from its
30
+ * root does. Its source, config, agent docs and
31
+ * protection declarations all live there. The search stops at a repository
32
+ * root and ends at the home folder without counting it (see
33
+ * `findAstryxProject`). A `path` the caller names, in any spelling (`./src`,
34
+ * `src`, `src/`), is read from `cwd`, where they typed it. With no `path`,
35
+ * the upgrade scans `./src` at the project root. With no Astryx project, the
36
+ * upgrade runs in `cwd` as released versions did.
37
+ * @param {UpgradeOptions} options
38
+ * @param {string} cwd
39
+ * @returns {{options: UpgradeOptions, cwd: string, origin?: string}} `origin`
40
+ * is the folder the command ran in, when the project root is above it
41
+ */
42
+ export function scopeToProject(options: UpgradeOptions, cwd: string): {
43
+ options: UpgradeOptions;
44
+ cwd: string;
45
+ origin?: string;
46
+ };
47
+ /**
48
+ * The result of an upgrade that ran at a project root above the folder the
49
+ * command ran in, with its file paths made relative to that folder, so each
50
+ * resolves from where the command ran: `modifiedFiles`, `agentDocs.files`, and
51
+ * the `file` of each `protectedFiles`, `declinedCandidates` and `errors`
52
+ * entry. The registry step names copied-composition paths from that folder
53
+ * itself (see `reconcileRegistryCompositions`), so they pass through. A
54
+ * result of a run at the project root is returned as it is.
55
+ * @template {{type: string, data: object}} T
56
+ * @param {T} result
57
+ * @param {{cwd: string, origin?: string}} scope from `scopeToProject`
58
+ * @returns {T}
59
+ */
60
+ export function relativeToOrigin<T extends {
61
+ type: string;
62
+ data: object;
63
+ }>(result: T, { cwd: root, origin }: {
64
+ cwd: string;
65
+ origin?: string;
66
+ }): T;
67
+ /**
68
+ * Detect the installed target version: the Core the Astryx project of `cwd`
69
+ * installed, in its own node_modules or, for a member its workspace lists, in
70
+ * a folder up to the workspace root. The nearest folder wins, the current
71
+ * name before the legacy one, then Yarn Plug'n'Play. A Core above a project
72
+ * no workspace lists does not count, so a nested project that has not
73
+ * installed its own reports nothing installed. With no Astryx project, only
74
+ * the node_modules of `cwd` itself is read, as released versions did.
27
75
  * @param {string} cwd
28
76
  * @returns {{version: string, packageName: string}|null}
29
77
  */
@@ -84,25 +132,20 @@ export function runPostCodemodHooks(hooks: import("../../authoring/config/type")
84
132
  files: string[];
85
133
  apply: boolean;
86
134
  }): Promise<void>;
87
- /**
88
- * @typedef {object} AgentDocsRefreshPlan
89
- * @property {string} cwd
90
- * @property {string|null} renderedBlock
91
- * @property {import('./upgrade.type.mjs').AgentDocsSummary} summary
92
- */
93
135
  /**
94
136
  * Compute the expected managed block and report staleness without writing.
95
137
  * Apply-mode callers commit the prepared bytes only after codemods and hooks
96
138
  * succeed.
97
139
  *
98
- * @param {{cwd: string, installedVersion: string, apply: boolean, fresh?: boolean}} ctx
140
+ * @param {{cwd: string, installedVersion: string, apply: boolean, fresh?: boolean, origin?: string}} ctx
99
141
  * @returns {Promise<AgentDocsRefreshPlan>}
100
142
  */
101
- export function prepareAgentDocsRefresh({ cwd, installedVersion, apply, fresh, }: {
143
+ export function prepareAgentDocsRefresh({ cwd, installedVersion, apply, fresh, origin, }: {
102
144
  cwd: string;
103
145
  installedVersion: string;
104
146
  apply: boolean;
105
147
  fresh?: boolean;
148
+ origin?: string;
106
149
  }): Promise<AgentDocsRefreshPlan>;
107
150
  /**
108
151
  * Commit a prepared agent-doc refresh.
@@ -288,6 +331,11 @@ export type UpgradeOptions = {
288
331
  };
289
332
  export type AgentDocsRefreshPlan = {
290
333
  cwd: string;
334
+ /**
335
+ * the folder the command ran in, when the upgrade
336
+ * runs at a project root above it
337
+ */
338
+ origin?: string | undefined;
291
339
  renderedBlock: string | null;
292
340
  summary: import("./upgrade.type.mjs").AgentDocsSummary;
293
341
  };
@@ -30,6 +30,8 @@ import {
30
30
  } from '../../assets/codemods/integration-discovery.mjs';
31
31
  import {runIntegrationCodemods} from '../../assets/codemods/integration-runner.mjs';
32
32
  import {
33
+ AgentDocsHomeError,
34
+ assertAgentDocsTarget,
33
35
  installAgentDocs,
34
36
  inspectAgentDocs,
35
37
  renderAgentDocsBlock,
@@ -42,7 +44,11 @@ import {
42
44
  quoteCommandArgument,
43
45
  } from '../../foundation/env/package-manager.mjs';
44
46
  import {isValidSemver, semverGt} from '../../foundation/env/semver.mjs';
45
- import {CLI_ROOT} from '../../foundation/fs/paths.mjs';
47
+ import {
48
+ CLI_ROOT,
49
+ findAstryxProject,
50
+ findProjectPackageOf,
51
+ } from '../../foundation/fs/paths.mjs';
46
52
  import {Project, providerLedgerOf} from '../../foundation/config/project.mjs';
47
53
  import {loadIntegrations} from '../../foundation/integrations/integrations.mjs';
48
54
  import {resolveProviders} from '../../foundation/integrations/provider-resolution.mjs';
@@ -82,26 +88,125 @@ const execFileAsync = promisify(execFile);
82
88
  */
83
89
 
84
90
  /**
85
- * Detect the installed target version from node_modules.
91
+ * Where an upgrade runs: the root of the Astryx project `cwd` belongs to, the
92
+ * nearest folder at or above it whose package.json declares an Astryx
93
+ * package or that an Astryx workspace lists as a member, so a run from any
94
+ * folder inside a project upgrades that project exactly as a run from its
95
+ * root does. Its source, config, agent docs and
96
+ * protection declarations all live there. The search stops at a repository
97
+ * root and ends at the home folder without counting it (see
98
+ * `findAstryxProject`). A `path` the caller names, in any spelling (`./src`,
99
+ * `src`, `src/`), is read from `cwd`, where they typed it. With no `path`,
100
+ * the upgrade scans `./src` at the project root. With no Astryx project, the
101
+ * upgrade runs in `cwd` as released versions did.
102
+ * @param {UpgradeOptions} options
103
+ * @param {string} cwd
104
+ * @returns {{options: UpgradeOptions, cwd: string, origin?: string}} `origin`
105
+ * is the folder the command ran in, when the project root is above it
106
+ */
107
+ export function scopeToProject(options, cwd) {
108
+ const here = path.resolve(cwd);
109
+ const root = findAstryxProject(here) ?? here;
110
+ if (root === here) return {options, cwd};
111
+ const namedPath =
112
+ options.path == null ? {} : {path: path.resolve(here, options.path)};
113
+ return {options: {...options, ...namedPath}, cwd: root, origin: here};
114
+ }
115
+
116
+ /**
117
+ * The result of an upgrade that ran at a project root above the folder the
118
+ * command ran in, with its file paths made relative to that folder, so each
119
+ * resolves from where the command ran: `modifiedFiles`, `agentDocs.files`, and
120
+ * the `file` of each `protectedFiles`, `declinedCandidates` and `errors`
121
+ * entry. The registry step names copied-composition paths from that folder
122
+ * itself (see `reconcileRegistryCompositions`), so they pass through. A
123
+ * result of a run at the project root is returned as it is.
124
+ * @template {{type: string, data: object}} T
125
+ * @param {T} result
126
+ * @param {{cwd: string, origin?: string}} scope from `scopeToProject`
127
+ * @returns {T}
128
+ */
129
+ export function relativeToOrigin(result, {cwd: root, origin}) {
130
+ if (origin == null) return result;
131
+ /** @param {string} file */
132
+ const rebase = file =>
133
+ path.relative(origin, path.resolve(root, file)).split(path.sep).join('/') ||
134
+ '.';
135
+ /** @type {Record<string, any>} */
136
+ const data = {...result.data};
137
+ if (data.agentDocs) {
138
+ data.agentDocs = {
139
+ ...data.agentDocs,
140
+ files: data.agentDocs.files.map(rebase),
141
+ };
142
+ }
143
+ if (data.modifiedFiles) data.modifiedFiles = data.modifiedFiles.map(rebase);
144
+ for (const key of ['protectedFiles', 'declinedCandidates', 'errors']) {
145
+ if (data[key]) {
146
+ data[key] = data[key].map(
147
+ (/** @type {{file: string, declarations?: string[]}} */ entry) => ({
148
+ ...entry,
149
+ file: rebase(entry.file),
150
+ ...(Array.isArray(entry.declarations)
151
+ ? {declarations: entry.declarations.map(rebaseDeclaration(rebase))}
152
+ : {}),
153
+ }),
154
+ );
155
+ }
156
+ }
157
+ return /** @type {T} */ ({...result, data});
158
+ }
159
+
160
+ /**
161
+ * The project-relative file a protection declaration names, and the text
162
+ * around it: the ignore or attributes file that protects a file (and its
163
+ * line), or the symbolic link on its path. Other declarations name no file.
164
+ */
165
+ const DECLARATION_PATHS = [
166
+ /^(excluded by )(.+?)(:\d+)?$/u,
167
+ /^(linguist-(?:generated|vendored) in )(.+?)(:\d+)$/u,
168
+ /^(symbolic link at )(.+)()$/u,
169
+ ];
170
+
171
+ /**
172
+ * A protection declaration with the file it names rebased, as `rebase` does
173
+ * for result paths.
174
+ * @param {(file: string) => string} rebase
175
+ * @returns {(declaration: string) => string}
176
+ */
177
+ function rebaseDeclaration(rebase) {
178
+ return declaration => {
179
+ for (const pattern of DECLARATION_PATHS) {
180
+ const match = pattern.exec(declaration);
181
+ if (match) return `${match[1]}${rebase(match[2])}${match[3] ?? ''}`;
182
+ }
183
+ return declaration;
184
+ };
185
+ }
186
+
187
+ /**
188
+ * Detect the installed target version: the Core the Astryx project of `cwd`
189
+ * installed, in its own node_modules or, for a member its workspace lists, in
190
+ * a folder up to the workspace root. The nearest folder wins, the current
191
+ * name before the legacy one, then Yarn Plug'n'Play. A Core above a project
192
+ * no workspace lists does not count, so a nested project that has not
193
+ * installed its own reports nothing installed. With no Astryx project, only
194
+ * the node_modules of `cwd` itself is read, as released versions did.
86
195
  * @param {string} cwd
87
196
  * @returns {{version: string, packageName: string}|null}
88
197
  */
89
198
  export function detectInstalledTargetVersion(cwd) {
90
- for (const packageName of ['@astryxdesign/core', '@xds/core']) {
91
- const pkgPath = path.resolve(
92
- cwd,
93
- 'node_modules',
94
- ...packageName.split('/'),
95
- 'package.json',
199
+ const found = findProjectPackageOf(cwd, ['@astryxdesign/core', '@xds/core']);
200
+ if (!found) return null;
201
+ try {
202
+ const pkg = JSON.parse(
203
+ fs.readFileSync(path.join(found.packageDir, 'package.json'), 'utf-8'),
96
204
  );
97
- try {
98
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
99
- if (pkg.version) return {version: pkg.version, packageName};
100
- } catch {
101
- // Missing/unreadable — try the next supported package name.
102
- }
205
+ return pkg.version ? {version: pkg.version, packageName: found.name} : null;
206
+ } catch {
207
+ // Unreadable package.json: no version to target.
208
+ return null;
103
209
  }
104
- return null;
105
210
  }
106
211
 
107
212
  /**
@@ -282,16 +387,47 @@ export async function runPostCodemodHooks(hooks, context) {
282
387
  /**
283
388
  * @typedef {object} AgentDocsRefreshPlan
284
389
  * @property {string} cwd
390
+ * @property {string} [origin] the folder the command ran in, when the upgrade
391
+ * runs at a project root above it
285
392
  * @property {string|null} renderedBlock
286
393
  * @property {import('./upgrade.type.mjs').AgentDocsSummary} summary
287
394
  */
288
395
 
396
+ /**
397
+ * The advice to run `init`, phrased for where the command ran: when the
398
+ * upgrade runs at a project root above that folder, it names the root, so the
399
+ * agent docs are installed beside the project's package.json.
400
+ * @param {string} cwd the folder the upgrade runs at
401
+ * @param {string} [origin] the folder the command ran in
402
+ * @returns {string}
403
+ */
404
+ function initAdvice(cwd, origin) {
405
+ const command = `\`${formatCliCommand('astryx init --features agents')}\``;
406
+ if (origin == null) return command;
407
+ return `${command} in ${path.relative(origin, cwd) || '.'} (the project root)`;
408
+ }
409
+
410
+ /**
411
+ * Why upgrade leaves the agent docs of a project at the home folder or the
412
+ * top of a settings folder, and what to do instead. init refuses the same
413
+ * folder, so the line never points at it.
414
+ * @param {AgentDocsHomeError} err
415
+ * @param {'stale' | 'missing'} [state] whether a block is there to refresh
416
+ * @returns {string}
417
+ */
418
+ function homeRefusalLine(err, state = 'stale') {
419
+ const where = `${err.dir} is the home folder or a settings folder in it, which every project's agents read.`;
420
+ return state === 'missing'
421
+ ? `Agent docs not installed: ${where} To have agent docs, move the project into a folder of its own, or add the Astryx block to its agent file by hand.`
422
+ : `Agent docs not refreshed: ${where} To keep agent docs, move the project into a folder of its own; otherwise update or remove the Astryx block there by hand.`;
423
+ }
424
+
289
425
  /**
290
426
  * Compute the expected managed block and report staleness without writing.
291
427
  * Apply-mode callers commit the prepared bytes only after codemods and hooks
292
428
  * succeed.
293
429
  *
294
- * @param {{cwd: string, installedVersion: string, apply: boolean, fresh?: boolean}} ctx
430
+ * @param {{cwd: string, installedVersion: string, apply: boolean, fresh?: boolean, origin?: string}} ctx
295
431
  * @returns {Promise<AgentDocsRefreshPlan>}
296
432
  */
297
433
  export async function prepareAgentDocsRefresh({
@@ -299,6 +435,7 @@ export async function prepareAgentDocsRefresh({
299
435
  installedVersion,
300
436
  apply,
301
437
  fresh = false,
438
+ origin,
302
439
  }) {
303
440
  const initial = inspectAgentDocs(cwd, installedVersion);
304
441
  /** @type {import('./upgrade.type.mjs').AgentDocsSummary} */
@@ -312,11 +449,21 @@ export async function prepareAgentDocsRefresh({
312
449
  };
313
450
 
314
451
  if (initial.status === 'missing') {
452
+ // init writes nothing in a project at the home folder or the top of a
453
+ // settings folder, so it is not the remedy there (INV31).
454
+ try {
455
+ assertAgentDocsTarget(cwd);
456
+ } catch (err) {
457
+ if (!(err instanceof AgentDocsHomeError)) throw err;
458
+ summary.action = 'error';
459
+ logger.warn(homeRefusalLine(err, 'missing'));
460
+ return {cwd, origin, renderedBlock: null, summary};
461
+ }
315
462
  summary.action = 'nudge-init';
316
463
  logger.warn(
317
- `No Astryx agent-docs block found - AI agents have no component index. Run \`${formatCliCommand('astryx init --features agents')}\` to install it.`,
464
+ `No Astryx agent-docs block found - AI agents have no component index. Run ${initAdvice(cwd, origin)} to install it.`,
318
465
  );
319
- return {cwd, renderedBlock: null, summary};
466
+ return {cwd, origin, renderedBlock: null, summary};
320
467
  }
321
468
 
322
469
  let renderedBlock;
@@ -329,16 +476,30 @@ export async function prepareAgentDocsRefresh({
329
476
  summary.action = 'error';
330
477
  summary.files = initial.files.map(file => file.path);
331
478
  logger.warn(
332
- `Could not render the expected agent docs. Run \`${formatCliCommand('astryx init --features agents')}\` to update them manually.`,
479
+ `Could not render the expected agent docs. Run ${initAdvice(cwd, origin)} to update them manually.`,
333
480
  );
334
- return {cwd, renderedBlock: null, summary};
481
+ return {cwd, origin, renderedBlock: null, summary};
335
482
  }
336
483
 
337
484
  const inspection = inspectAgentDocs(cwd, installedVersion, renderedBlock);
338
485
  summary.status = inspection.status;
339
486
  summary.fromVersions = inspection.blockVersions;
340
487
  summary.files = inspection.staleFiles;
341
- if (inspection.status === 'current') return {cwd, renderedBlock, summary};
488
+ if (inspection.status === 'current') {
489
+ return {cwd, origin, renderedBlock, summary};
490
+ }
491
+
492
+ // A project at the home folder or the top of a settings folder is never
493
+ // refreshed (INV31): the dry run says so, as --apply does, rather than
494
+ // offering a refresh that would be refused.
495
+ try {
496
+ assertAgentDocsTarget(cwd);
497
+ } catch (err) {
498
+ if (!(err instanceof AgentDocsHomeError)) throw err;
499
+ summary.action = 'error';
500
+ logger.warn(homeRefusalLine(err));
501
+ return {cwd, origin, renderedBlock: null, summary};
502
+ }
342
503
 
343
504
  summary.action = 'would-refresh';
344
505
  if (!apply) {
@@ -346,7 +507,7 @@ export async function prepareAgentDocsRefresh({
346
507
  `Agent docs differ from the installed Astryx and integration configuration. Re-run with --apply to refresh (${summary.files.join(', ')}).`,
347
508
  );
348
509
  }
349
- return {cwd, renderedBlock, summary};
510
+ return {cwd, origin, renderedBlock, summary};
350
511
  }
351
512
 
352
513
  /**
@@ -355,7 +516,7 @@ export async function prepareAgentDocsRefresh({
355
516
  * @returns {import('./upgrade.type.mjs').AgentDocsSummary}
356
517
  */
357
518
  export function applyAgentDocsRefresh(plan) {
358
- const {cwd, renderedBlock, summary} = plan;
519
+ const {cwd, origin, renderedBlock, summary} = plan;
359
520
  if (summary.action !== 'would-refresh' || renderedBlock == null)
360
521
  return summary;
361
522
 
@@ -363,6 +524,9 @@ export function applyAgentDocsRefresh(plan) {
363
524
  const written = installAgentDocs(cwd, {
364
525
  onlyReplace: true,
365
526
  renderedBlock,
527
+ // A GEMINI.md that resolves outside the project is left as it is.
528
+ onSkipped: (file, reason) =>
529
+ logger.warn(`Note: ${file} left as it is (${reason}).`),
366
530
  });
367
531
  summary.refreshed = written.length > 0;
368
532
  summary.files = written;
@@ -371,15 +535,21 @@ export function applyAgentDocsRefresh(plan) {
371
535
  logger.log(`[ok] Agent docs refreshed -> ${written.join(', ')}`);
372
536
  } else {
373
537
  logger.warn(
374
- `Agent docs look stale but couldn't be refreshed. Run \`${formatCliCommand('astryx init --features agents')}\` to reinstall the block.`,
538
+ `Agent docs look stale but couldn't be refreshed. Run ${initAdvice(cwd, origin)} to reinstall the block.`,
375
539
  );
376
540
  }
377
- } catch {
541
+ } catch (err) {
378
542
  summary.action = 'error';
379
543
  summary.refreshed = false;
380
- logger.warn(
381
- `Could not refresh agent docs. Run \`${formatCliCommand('astryx init --features agents')}\` to update them manually.`,
382
- );
544
+ if (err instanceof AgentDocsHomeError) {
545
+ // init refuses this folder too, so pointing at it would loop: say why,
546
+ // and what to do instead.
547
+ logger.warn(homeRefusalLine(err));
548
+ } else {
549
+ logger.warn(
550
+ `Could not refresh agent docs. Run ${initAdvice(cwd, origin)} to update them manually.`,
551
+ );
552
+ }
383
553
  }
384
554
  return summary;
385
555
  }
@@ -4,14 +4,18 @@
4
4
  /** @param {string} root */
5
5
  export function discoverRegistryReceipts(root: string): string[];
6
6
  /**
7
+ * Reported paths are relative to the scanned source folder, or, when `origin`
8
+ * names the folder the command ran in below the project root `cwd`, to that
9
+ * folder, so they resolve from where the command ran.
7
10
  * @param {{apply?: boolean, path?: string}} options
8
- * @param {{cwd?: string, fetchImpl?: typeof fetch, registryOrigin?: string, expectedVersion?: string, requireExpectedVersion?: boolean}} [ctx]
11
+ * @param {{cwd?: string, origin?: string, fetchImpl?: typeof fetch, registryOrigin?: string, expectedVersion?: string, requireExpectedVersion?: boolean}} [ctx]
9
12
  */
10
13
  export function reconcileRegistryCompositions(options?: {
11
14
  apply?: boolean;
12
15
  path?: string;
13
- }, { cwd, fetchImpl, registryOrigin, expectedVersion, requireExpectedVersion, }?: {
16
+ }, { cwd, origin, fetchImpl, registryOrigin, expectedVersion, requireExpectedVersion, }?: {
14
17
  cwd?: string;
18
+ origin?: string;
15
19
  fetchImpl?: typeof fetch;
16
20
  registryOrigin?: string;
17
21
  expectedVersion?: string;
@@ -23,9 +27,10 @@ export function reconcileRegistryCompositions(options?: {
23
27
  /** @param {import('../upgrade.type.mjs').RegistryCompositionSummary} summary */
24
28
  export function logRegistryCompositionSummary(summary: import("../upgrade.type.mjs").RegistryCompositionSummary): void;
25
29
  /**
26
- * Standalone `astryx upgrade --registry` leaf.
30
+ * Standalone `astryx upgrade --registry` leaf. `ctx.origin` is the folder the
31
+ * command ran in, when `ctx.cwd` is the project root above it.
27
32
  * @param {{apply?: boolean, path?: string}} options
28
- * @param {{cwd?: string, fetchImpl?: typeof fetch, registryOrigin?: string, expectedVersion?: string, requireExpectedVersion?: boolean}} [ctx]
33
+ * @param {{cwd?: string, origin?: string, fetchImpl?: typeof fetch, registryOrigin?: string, expectedVersion?: string, requireExpectedVersion?: boolean}} [ctx]
29
34
  * @returns {Promise<import('../upgrade.type.mjs').UpgradeRegistryResponse>}
30
35
  */
31
36
  export function registryUpgrade(options?: {
@@ -33,6 +38,7 @@ export function registryUpgrade(options?: {
33
38
  path?: string;
34
39
  }, ctx?: {
35
40
  cwd?: string;
41
+ origin?: string;
36
42
  fetchImpl?: typeof fetch;
37
43
  registryOrigin?: string;
38
44
  expectedVersion?: string;
@@ -414,8 +414,9 @@ async function fetchLatestReceipt(
414
414
  /**
415
415
  * @param {string} receiptPath
416
416
  * @param {string} root
417
+ * @param {string} [display] the folder reported paths are relative to
417
418
  */
418
- function loadInstalledReceipt(receiptPath, root) {
419
+ function loadInstalledReceipt(receiptPath, root, display = root) {
419
420
  let receipt;
420
421
  let receiptText;
421
422
  try {
@@ -436,7 +437,7 @@ function loadInstalledReceipt(receiptPath, root) {
436
437
  }
437
438
  } catch (error) {
438
439
  throw new Error(
439
- `Invalid receipt ${relativePath(receiptPath, root)}: ${error instanceof Error ? error.message : String(error)}`,
440
+ `Invalid receipt ${relativePath(receiptPath, display)}: ${error instanceof Error ? error.message : String(error)}`,
440
441
  {cause: error},
441
442
  );
442
443
  }
@@ -445,7 +446,7 @@ function loadInstalledReceipt(receiptPath, root) {
445
446
  const candidates = receiptFileCandidates(receipt, file).map(candidate => {
446
447
  if (registryContentHash(candidate.content) !== candidate.sha256) {
447
448
  throw new Error(
448
- `Invalid receipt ${relativePath(receiptPath, root)}: stored hash does not match ${file.registryPath} (${candidate.format})`,
449
+ `Invalid receipt ${relativePath(receiptPath, display)}: stored hash does not match ${file.registryPath} (${candidate.format})`,
449
450
  );
450
451
  }
451
452
  let sourcePath;
@@ -458,7 +459,7 @@ function loadInstalledReceipt(receiptPath, root) {
458
459
  } catch (error) {
459
460
  if (error instanceof PathSafetyError) {
460
461
  throw new Error(
461
- `Invalid receipt ${relativePath(receiptPath, root)}: ${error.message}`,
462
+ `Invalid receipt ${relativePath(receiptPath, display)}: ${error.message}`,
462
463
  {cause: error},
463
464
  );
464
465
  }
@@ -471,7 +472,7 @@ function loadInstalledReceipt(receiptPath, root) {
471
472
  );
472
473
  if (existing.length > 1) {
473
474
  throw new Error(
474
- `Invalid receipt ${relativePath(receiptPath, root)}: multiple install variants exist for ${file.registryPath}`,
475
+ `Invalid receipt ${relativePath(receiptPath, display)}: multiple install variants exist for ${file.registryPath}`,
475
476
  );
476
477
  }
477
478
  return existing[0] ?? candidates[0];
@@ -483,12 +484,14 @@ function loadInstalledReceipt(receiptPath, root) {
483
484
  /**
484
485
  * @param {string} receiptPath
485
486
  * @param {string} root
486
- * @param {{fetchImpl: typeof fetch, registryOrigin: string, expectedVersion?: string}} options
487
+ * @param {{fetchImpl: typeof fetch, registryOrigin: string, expectedVersion?: string, display?: string}} options
488
+ * `display` is the folder reported paths are relative to, `root` by default
487
489
  */
488
490
  async function planReceipt(receiptPath, root, options) {
491
+ const display = options.display ?? root;
489
492
  let installed;
490
493
  try {
491
- installed = loadInstalledReceipt(receiptPath, root);
494
+ installed = loadInstalledReceipt(receiptPath, root, display);
492
495
  } catch (error) {
493
496
  return {
494
497
  receiptPath,
@@ -584,7 +587,7 @@ async function planReceipt(receiptPath, root, options) {
584
587
  oldReceiptText,
585
588
  };
586
589
  }
587
- const displayPath = relativePath(sourcePath, root);
590
+ const displayPath = relativePath(sourcePath, display);
588
591
  try {
589
592
  if (!fs.existsSync(sourcePath)) {
590
593
  plannedFiles.push({
@@ -636,7 +639,7 @@ async function planReceipt(receiptPath, root, options) {
636
639
  const newFile = newReceiptFile
637
640
  ? receiptFileForFormat(newReceipt, newReceiptFile, oldFile.format)
638
641
  : null;
639
- const displayPath = relativePath(oldFile.sourcePath, root);
642
+ const displayPath = relativePath(oldFile.sourcePath, display);
640
643
  if (!newReceiptFile) {
641
644
  plannedFiles.push({
642
645
  id: oldFile.id,
@@ -856,8 +859,12 @@ function publicAction(action, apply) {
856
859
  return action;
857
860
  }
858
861
 
859
- /** @param {any} item @param {string} root @param {boolean} apply */
860
- function publicItem(item, root, apply) {
862
+ /**
863
+ * @param {any} item
864
+ * @param {string} display the folder reported paths are relative to
865
+ * @param {boolean} apply
866
+ */
867
+ function publicItem(item, display, apply) {
861
868
  return {
862
869
  item: item.item,
863
870
  path: item.itemPath,
@@ -870,7 +877,7 @@ function publicItem(item, root, apply) {
870
877
  action: publicAction(file.action, apply),
871
878
  ...(file.message ? {message: file.message} : {}),
872
879
  ...(file.action === 'conflict' && file.conflictPath
873
- ? {conflictFile: relativePath(file.conflictPath, root)}
880
+ ? {conflictFile: relativePath(file.conflictPath, display)}
874
881
  : {}),
875
882
  }),
876
883
  ),
@@ -968,13 +975,17 @@ function applyPlan(plan) {
968
975
  }
969
976
 
970
977
  /**
978
+ * Reported paths are relative to the scanned source folder, or, when `origin`
979
+ * names the folder the command ran in below the project root `cwd`, to that
980
+ * folder, so they resolve from where the command ran.
971
981
  * @param {{apply?: boolean, path?: string}} options
972
- * @param {{cwd?: string, fetchImpl?: typeof fetch, registryOrigin?: string, expectedVersion?: string, requireExpectedVersion?: boolean}} [ctx]
982
+ * @param {{cwd?: string, origin?: string, fetchImpl?: typeof fetch, registryOrigin?: string, expectedVersion?: string, requireExpectedVersion?: boolean}} [ctx]
973
983
  */
974
984
  export async function reconcileRegistryCompositions(
975
985
  options = {},
976
986
  {
977
987
  cwd = process.cwd(),
988
+ origin,
978
989
  fetchImpl = globalThis.fetch,
979
990
  registryOrigin = PUBLIC_SHADCN_REGISTRY_ORIGIN,
980
991
  expectedVersion,
@@ -998,6 +1009,7 @@ export async function reconcileRegistryCompositions(
998
1009
  throw error;
999
1010
  }
1000
1011
 
1012
+ const display = origin ?? root;
1001
1013
  const apply = options.apply ?? false;
1002
1014
  const receipts = discoverRegistryReceipts(root);
1003
1015
  const summary = emptySummary(apply);
@@ -1019,6 +1031,7 @@ export async function reconcileRegistryCompositions(
1019
1031
  fetchImpl,
1020
1032
  registryOrigin,
1021
1033
  expectedVersion,
1034
+ display,
1022
1035
  }),
1023
1036
  );
1024
1037
 
@@ -1031,7 +1044,7 @@ export async function reconcileRegistryCompositions(
1031
1044
  summary.invalid++;
1032
1045
  }
1033
1046
  }
1034
- recordItem(summary, publicItem(plan, root, apply), plan.action, apply);
1047
+ recordItem(summary, publicItem(plan, display, apply), plan.action, apply);
1035
1048
  if (!apply) continue;
1036
1049
 
1037
1050
  try {
@@ -1119,13 +1132,17 @@ export function logRegistryCompositionSummary(summary) {
1119
1132
  }
1120
1133
 
1121
1134
  /**
1122
- * Standalone `astryx upgrade --registry` leaf.
1135
+ * Standalone `astryx upgrade --registry` leaf. `ctx.origin` is the folder the
1136
+ * command ran in, when `ctx.cwd` is the project root above it.
1123
1137
  * @param {{apply?: boolean, path?: string}} options
1124
- * @param {{cwd?: string, fetchImpl?: typeof fetch, registryOrigin?: string, expectedVersion?: string, requireExpectedVersion?: boolean}} [ctx]
1138
+ * @param {{cwd?: string, origin?: string, fetchImpl?: typeof fetch, registryOrigin?: string, expectedVersion?: string, requireExpectedVersion?: boolean}} [ctx]
1125
1139
  * @returns {Promise<import('../upgrade.type.mjs').UpgradeRegistryResponse>}
1126
1140
  */
1127
1141
  export async function registryUpgrade(options = {}, ctx = {}) {
1128
1142
  const cwd = ctx.cwd ?? process.cwd();
1143
+ if (ctx.origin != null) {
1144
+ logger.log(`Project root: ${path.relative(ctx.origin, cwd) || '.'}`);
1145
+ }
1129
1146
  const installed = detectInstalledTargetVersion(cwd);
1130
1147
  const expectedVersion =
1131
1148
  ctx.expectedVersion ??