@astryxdesign/cli 0.7.2-canary.fe2c655 → 0.7.2

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 (509) hide show
  1. package/CHANGELOG.md +119 -0
  2. package/README.md +175 -154
  3. package/api/build/_adapter.d.mts +8 -0
  4. package/api/build/_adapter.mjs +12 -1
  5. package/api/build/build.doc.mjs +4 -0
  6. package/api/build/help/help.mjs +1 -1
  7. package/api/build/kit/kit.mjs +3 -11
  8. package/api/component/_adapter.d.mts +72 -1
  9. package/api/component/_adapter.mjs +212 -18
  10. package/api/component/component.doc.mjs +6 -1
  11. package/api/component/component.mjs +82 -15
  12. package/api/component/component.test.mjs +80 -0
  13. package/api/component/doc-links.test.mjs +386 -0
  14. package/api/component/integration-export-name.test.mjs +644 -0
  15. package/api/component/list/list.mjs +42 -4
  16. package/api/discover/_adapter.d.mts +132 -12
  17. package/api/discover/_adapter.mjs +488 -27
  18. package/api/discover/_adapter.test.mjs +599 -17
  19. package/api/discover/_catalog-view.d.mts +16 -5
  20. package/api/discover/_catalog-view.mjs +32 -8
  21. package/api/discover/detail/doc/doc.mjs +9 -1
  22. package/api/discover/discover.d.mts +11 -0
  23. package/api/discover/discover.doc.mjs +9 -7
  24. package/api/discover/discover.mjs +121 -19
  25. package/api/discover/discover.type.d.mts +20 -7
  26. package/api/discover/discover.type.mjs +17 -7
  27. package/api/discover/list/list.d.mts +4 -3
  28. package/api/discover/list/list.mjs +17 -10
  29. package/api/docs/_adapter.d.mts +74 -0
  30. package/api/docs/_adapter.mjs +553 -16
  31. package/api/docs/docs.doc.mjs +2 -1
  32. package/api/docs/docs.mjs +2 -1
  33. package/api/docs/docs.test.mjs +5 -2
  34. package/api/docs/integration-tree.test.mjs +22 -0
  35. package/api/docs/integrationDocs.test.mjs +124 -0
  36. package/api/docs/list/list.mjs +12 -2
  37. package/api/docs/reference-blocks.test.mjs +1 -3
  38. package/api/doctor/_adapter.d.mts +108 -0
  39. package/api/doctor/_adapter.mjs +223 -4
  40. package/api/doctor/doctor.d.mts +199 -0
  41. package/api/doctor/doctor.doc.mjs +3 -2
  42. package/api/doctor/doctor.mjs +530 -34
  43. package/api/doctor/doctor.test.mjs +180 -14
  44. package/api/doctor/release-checks.test.mjs +156 -0
  45. package/api/doctor/theme-checks.mjs +17 -4
  46. package/api/doctor/theme-checks.test.mjs +223 -7
  47. package/api/error.d.mts +10 -0
  48. package/api/error.mjs +25 -0
  49. package/api/gap-report/gap-report.d.mts +3 -0
  50. package/api/gap-report/gap-report.doc.mjs +11 -3
  51. package/api/gap-report/gap-report.mjs +219 -52
  52. package/api/gap-report/gap-report.test.mjs +233 -0
  53. package/api/gap-report/gap-report.type.d.mts +41 -0
  54. package/api/gap-report/gap-report.type.mjs +18 -0
  55. package/api/hook/_adapter.mjs +3 -11
  56. package/api/hook/hook.doc.mjs +4 -0
  57. package/api/hook/list/list.d.mts +1 -1
  58. package/api/index.d.mts +2 -0
  59. package/api/index.mjs +2 -0
  60. package/api/init/_adapter.d.mts +22 -0
  61. package/api/init/_adapter.mjs +59 -0
  62. package/api/init/init.d.mts +7 -5
  63. package/api/init/init.doc.mjs +28 -7
  64. package/api/init/init.mjs +28 -4
  65. package/api/init/init.type.d.mts +48 -3
  66. package/api/init/init.type.mjs +37 -3
  67. package/api/init/run/run.d.mts +1 -1
  68. package/api/init/run/run.mjs +17 -6
  69. package/api/init/user/user.d.mts +14 -0
  70. package/api/init/user/user.mjs +87 -0
  71. package/api/init/user/user.test.mjs +232 -0
  72. package/api/integration/_adapter.d.mts +45 -0
  73. package/api/integration/_adapter.mjs +112 -0
  74. package/api/integration/add-contribution.component-names.test.mjs +66 -0
  75. package/api/integration/add-contribution.mjs +8 -4
  76. package/api/integration/add-contribution.test.mjs +4 -2
  77. package/api/integration/authoring-checks.mjs +110 -6
  78. package/api/integration/authoring-checks.test.mjs +808 -0
  79. package/api/integration/authoring-checks.type.d.mts +143 -0
  80. package/api/integration/authoring-checks.type.mjs +74 -2
  81. package/api/integration/check/check.d.mts +96 -0
  82. package/api/integration/check/check.mjs +357 -0
  83. package/api/integration/check/check.test.mjs +489 -0
  84. package/api/integration/integration-check.type.d.mts +118 -0
  85. package/api/integration/integration-check.type.mjs +84 -0
  86. package/api/integration/integrationCheck.doc.d.mts +10 -0
  87. package/api/integration/integrationCheck.doc.mjs +73 -0
  88. package/api/integration/integrationComponentConflicts.doc.mjs +45 -6
  89. package/api/integration/integrationDocConflicts.doc.mjs +22 -5
  90. package/api/integration/pack-check.mjs +364 -120
  91. package/api/integration/pack-check.test.mjs +730 -10
  92. package/api/integration/validate-integration-fixes.test.mjs +67 -0
  93. package/api/integration/validate-integration.d.mts +8 -0
  94. package/api/integration/validate-integration.mjs +63 -9
  95. package/api/integration/validate-integration.test.mjs +88 -8
  96. package/api/integration/validate-integration.type.d.mts +16 -0
  97. package/api/integration/validate-integration.type.mjs +14 -1
  98. package/api/integration/validate-unread-theme-folders.test.mjs +3 -2
  99. package/api/integration/validateIntegration.doc.mjs +19 -3
  100. package/api/search/_adapter.d.mts +11 -0
  101. package/api/search/_adapter.mjs +25 -0
  102. package/api/search/search.d.mts +82 -13
  103. package/api/search/search.doc.mjs +8 -3
  104. package/api/search/search.domain-priority.test.mjs +108 -18
  105. package/api/search/search.integration-check.test.mjs +24 -0
  106. package/api/search/search.mjs +645 -64
  107. package/api/search/search.test.mjs +179 -1
  108. package/api/search/search.type.d.mts +14 -1
  109. package/api/search/search.type.mjs +6 -1
  110. package/api/swizzle/_adapter.mjs +2 -2
  111. package/api/swizzle/copy/copy.mjs +29 -15
  112. package/api/template/template-integration.test.mjs +1 -1
  113. package/api/template/template.test.mjs +120 -3
  114. package/api/theme/_adapter.d.mts +16 -0
  115. package/api/theme/_adapter.mjs +32 -5
  116. package/api/theme/build/build.d.mts +2 -1
  117. package/api/theme/build/build.family.test.mjs +64 -0
  118. package/api/theme/build/build.mjs +250 -38
  119. package/api/theme/build/build.packed-old-core.test.mjs +20 -3
  120. package/api/theme/build/font-warning.mjs +5 -3
  121. package/api/theme/build/font-warning.test.mjs +2 -1
  122. package/api/theme/remove/remove.test.mjs +2 -2
  123. package/api/theme/targets/targets.mjs +3 -10
  124. package/api/theme/themeBuild.doc.mjs +4 -4
  125. package/api/theme/themeTargets.doc.mjs +4 -0
  126. package/api/theme/use/use.test.mjs +1 -1
  127. package/api/upgrade/_adapter.d.mts +40 -2
  128. package/api/upgrade/_adapter.mjs +118 -5
  129. package/api/upgrade/run/cli-behind-core.test.mjs +438 -0
  130. package/api/upgrade/run/run.mjs +35 -2
  131. package/api/upgrade/status/status.d.mts +21 -5
  132. package/api/upgrade/status/status.mjs +45 -8
  133. package/api/upgrade/upgrade.doc.mjs +6 -3
  134. package/api/upgrade/upgrade.mjs +5 -1
  135. package/api/upgrade/upgrade.type.d.mts +23 -0
  136. package/api/upgrade/upgrade.type.mjs +16 -1
  137. package/assets/codemods/__tests__/registry.test.mjs +1 -0
  138. package/assets/codemods/registry.mjs +1 -0
  139. package/assets/codemods/runner.mjs +7 -1
  140. package/assets/codemods/transforms/v0.7.2/__tests__/migrate-list-edge-compensation-to-edge.test.mjs +120 -0
  141. package/assets/codemods/transforms/v0.7.2/index.mjs +20 -0
  142. package/assets/codemods/transforms/v0.7.2/migrate-list-edge-compensation-to-edge.mjs +198 -0
  143. package/assets/discover/official-packages.json +367 -0
  144. package/assets/docs/README.md +10 -0
  145. package/assets/docs/author-a-theme.doc.mjs +3 -3
  146. package/assets/docs/browser-support.doc.mjs +1 -1
  147. package/assets/docs/getting-started.doc.mjs +26 -6
  148. package/assets/docs/styling-overview.doc.mjs +1 -1
  149. package/assets/docs/theme.doc.dense.mjs +1 -1
  150. package/assets/docs/theme.doc.mjs +2 -2
  151. package/assets/docs/theme.doc.zh.mjs +1 -1
  152. package/assets/docs/tree/add-a-component.doc.mjs +22 -2
  153. package/assets/docs/tree/add-a-theme.doc.mjs +13 -3
  154. package/assets/docs/tree/add-a-topic.doc.mjs +54 -9
  155. package/assets/docs/tree/agent-guidance.doc.mjs +19 -5
  156. package/assets/docs/tree/block-template.doc.mjs +8 -20
  157. package/assets/docs/tree/build-the-template.doc.mjs +1 -1
  158. package/assets/docs/tree/building-blocks.doc.mjs +4 -0
  159. package/assets/docs/tree/check-the-props.doc.mjs +201 -0
  160. package/assets/docs/tree/check-your-docs.doc.mjs +69 -11
  161. package/assets/docs/tree/checks.doc.mjs +32 -24
  162. package/assets/docs/tree/ci-and-containers.doc.mjs +88 -0
  163. package/assets/docs/tree/cli-command.doc.mjs +142 -0
  164. package/assets/docs/tree/cli.doc.mjs +6 -0
  165. package/assets/docs/tree/codemods.doc.mjs +17 -7
  166. package/assets/docs/tree/commands.doc.mjs +7 -1
  167. package/assets/docs/tree/component-family.doc.mjs +8 -6
  168. package/assets/docs/tree/component-imports.doc.mjs +21 -3
  169. package/assets/docs/tree/components.doc.mjs +7 -1
  170. package/assets/docs/tree/computer-agent-guide.doc.mjs +160 -0
  171. package/assets/docs/tree/computer-integrations.doc.mjs +93 -0
  172. package/assets/docs/tree/configuration.doc.mjs +7 -3
  173. package/assets/docs/tree/debug-and-gap-reports.doc.mjs +41 -22
  174. package/assets/docs/tree/define-the-theme.doc.mjs +6 -5
  175. package/assets/docs/tree/describe-the-component.doc.mjs +3 -3
  176. package/assets/docs/tree/docs.doc.mjs +9 -3
  177. package/assets/docs/tree/document-the-template.doc.mjs +1 -1
  178. package/assets/docs/tree/document-the-theme.doc.mjs +3 -3
  179. package/assets/docs/tree/export-template-assets.doc.mjs +1 -1
  180. package/assets/docs/tree/extend-or-replace.doc.mjs +19 -9
  181. package/assets/docs/tree/fonts-and-assets.doc.mjs +2 -2
  182. package/assets/docs/tree/generate-a-palette.doc.mjs +7 -5
  183. package/assets/docs/tree/global-integrations.doc.mjs +124 -0
  184. package/assets/docs/tree/global.doc.mjs +131 -0
  185. package/assets/docs/tree/grade-template-with-agent.doc.mjs +5 -5
  186. package/assets/docs/tree/install-once.doc.mjs +105 -0
  187. package/assets/docs/tree/integrations.doc.mjs +13 -1
  188. package/assets/docs/tree/layout-spacing.doc.mjs +2 -3
  189. package/assets/docs/tree/layout.doc.mjs +6 -6
  190. package/assets/docs/tree/links.doc.mjs +7 -6
  191. package/assets/docs/tree/migration-setup.doc.mjs +3 -2
  192. package/assets/docs/tree/migration-verification.doc.mjs +0 -1
  193. package/assets/docs/tree/migration.doc.mjs +0 -1
  194. package/assets/docs/tree/monorepos.doc.mjs +84 -0
  195. package/assets/docs/tree/package-and-test.doc.mjs +1 -1
  196. package/assets/docs/tree/page-template.doc.mjs +4 -8
  197. package/assets/docs/tree/problems.doc.mjs +205 -0
  198. package/assets/docs/tree/project-global-or-both.doc.mjs +131 -0
  199. package/assets/docs/tree/publishing.doc.mjs +6 -6
  200. package/assets/docs/tree/quick-start.doc.mjs +53 -27
  201. package/assets/docs/tree/reference.doc.mjs +192 -0
  202. package/assets/docs/tree/replace-a-core-component.doc.mjs +15 -7
  203. package/assets/docs/tree/replace-a-core-template.doc.mjs +10 -12
  204. package/assets/docs/tree/scaffold.doc.mjs +1 -1
  205. package/assets/docs/tree/sections-and-placement.doc.mjs +9 -11
  206. package/assets/docs/tree/see-it-in-an-app.doc.mjs +17 -5
  207. package/assets/docs/tree/short-and-findable.doc.mjs +9 -8
  208. package/assets/docs/tree/single-component.doc.mjs +15 -6
  209. package/assets/docs/tree/start-a-template.doc.mjs +18 -8
  210. package/assets/docs/tree/subcomponent.doc.mjs +6 -6
  211. package/assets/docs/tree/template-assets.doc.mjs +2 -2
  212. package/assets/docs/tree/template-doc-overview.doc.mjs +6 -17
  213. package/assets/docs/tree/template-fonts.doc.mjs +1 -1
  214. package/assets/docs/tree/template-icons.doc.mjs +1 -1
  215. package/assets/docs/tree/template-images-media.doc.mjs +1 -1
  216. package/assets/docs/tree/template-styles.doc.mjs +2 -2
  217. package/assets/docs/tree/templates.doc.mjs +7 -1
  218. package/assets/docs/tree/test-in-an-app.doc.mjs +7 -7
  219. package/assets/docs/tree/test-template-in-app.doc.mjs +5 -5
  220. package/assets/docs/tree/themes.doc.mjs +4 -0
  221. package/assets/docs/tree/tokens-and-setup.doc.mjs +19 -15
  222. package/assets/docs/tree/troubleshooting.doc.mjs +29 -13
  223. package/assets/docs/tree/type-scale.doc.mjs +3 -3
  224. package/assets/docs/tree/uninstall.doc.mjs +109 -0
  225. package/assets/docs/tree/updates.doc.mjs +92 -0
  226. package/assets/docs/tree/upgrading.doc.mjs +6 -6
  227. package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +14 -2
  228. package/assets/docs/tree/verify-packed-template.doc.mjs +3 -3
  229. package/assets/docs/tree/versioning.doc.mjs +10 -9
  230. package/assets/docs/tree/who-answers.doc.mjs +119 -0
  231. package/assets/docs/tree/write-the-template-file.doc.mjs +7 -6
  232. package/assets/docs/use-a-theme.doc.mjs +2 -2
  233. package/assets/docs/working-with-ai.doc.mjs +66 -13
  234. package/assets/theme.template.ts +1 -1
  235. package/authoring/codemod/codemod.doc.mjs +7 -3
  236. package/authoring/config/config.doc.mjs +9 -6
  237. package/authoring/config/type.ts +4 -2
  238. package/authoring/debug/parse.d.mts +5 -5
  239. package/authoring/discover/discover.doc.mjs +7 -1
  240. package/authoring/doctypes/_schema.d.mts +8 -3
  241. package/authoring/doctypes/_schema.mjs +1 -0
  242. package/authoring/doctypes/base/graph-fields.doc.mjs +6 -4
  243. package/authoring/doctypes/command/command.doc.mjs +20 -12
  244. package/authoring/doctypes/command/type.ts +14 -0
  245. package/authoring/doctypes/component/component.doc.mjs +30 -27
  246. package/authoring/doctypes/component/type.ts +21 -8
  247. package/authoring/doctypes/enum/enum.doc.mjs +7 -4
  248. package/authoring/doctypes/function/function.doc.mjs +9 -8
  249. package/authoring/doctypes/hook/hook.doc.mjs +5 -4
  250. package/authoring/doctypes/hook/type.ts +4 -0
  251. package/authoring/doctypes/namespace/namespace.doc.mjs +7 -6
  252. package/authoring/doctypes/reference/reference.doc.mjs +12 -11
  253. package/authoring/doctypes/schema/schema.doc.mjs +9 -9
  254. package/authoring/doctypes/template/integration-template.doc.d.mts +11 -0
  255. package/authoring/doctypes/template/integration-template.doc.mjs +49 -0
  256. package/authoring/doctypes/template/parse.d.mts +6 -2
  257. package/authoring/doctypes/template/parse.mjs +3 -1
  258. package/authoring/doctypes/template/template.doc.mjs +3 -3
  259. package/authoring/doctypes/template/type.ts +22 -0
  260. package/authoring/doctypes/theme/theme.doc.mjs +2 -2
  261. package/authoring/gap-report/gap-report.doc.mjs +2 -2
  262. package/authoring/identity/identity.doc.mjs +3 -3
  263. package/authoring/index.d.ts +2 -0
  264. package/authoring/integration/integration.doc.mjs +30 -6
  265. package/authoring/integration/parse.test.mjs +21 -0
  266. package/authoring/integration/schema.d.mts +16 -3
  267. package/authoring/integration/schema.mjs +34 -2
  268. package/authoring/integration/type.ts +12 -0
  269. package/clients/cli/bin/astryx.mjs +20 -3
  270. package/clients/cli/cli-command.test.mjs +166 -0
  271. package/clients/cli/commands/blog.doc.mjs +1 -0
  272. package/clients/cli/commands/build-theme.batch-report.test.mjs +17 -3
  273. package/clients/cli/commands/build-theme.font-warning.test.mjs +3 -3
  274. package/clients/cli/commands/build-theme.mjs +26 -13
  275. package/clients/cli/commands/build-theme.path-safety.test.mjs +224 -6
  276. package/clients/cli/commands/build-theme.registry.test.mjs +5 -2
  277. package/clients/cli/commands/build.doc.mjs +1 -0
  278. package/clients/cli/commands/build.exit-codes-doc.test.mjs +4 -0
  279. package/clients/cli/commands/cli-postinstall.test.mjs +49 -1
  280. package/clients/cli/commands/component.doc.mjs +1 -0
  281. package/clients/cli/commands/discover.broken-integration.test.mjs +11 -7
  282. package/clients/cli/commands/discover.doc.mjs +9 -1
  283. package/clients/cli/commands/discover.mjs +144 -93
  284. package/clients/cli/commands/discover.no-source.test.mjs +448 -33
  285. package/clients/cli/commands/discover.sources.test.mjs +20 -1
  286. package/clients/cli/commands/docs.doc.mjs +1 -0
  287. package/clients/cli/commands/docs.mjs +49 -19
  288. package/clients/cli/commands/docs.test.mjs +2 -2
  289. package/clients/cli/commands/doctor-integration-components.doc.mjs +19 -4
  290. package/clients/cli/commands/doctor-integration-docs.doc.mjs +20 -5
  291. package/clients/cli/commands/doctor-integration-templates.doc.mjs +1 -0
  292. package/clients/cli/commands/doctor-integration-validate.doc.mjs +22 -2
  293. package/clients/cli/commands/doctor-integration.test.mjs +888 -3
  294. package/clients/cli/commands/doctor.doc.mjs +12 -2
  295. package/clients/cli/commands/doctor.mjs +292 -7
  296. package/clients/cli/commands/doctor.test.mjs +10 -4
  297. package/clients/cli/commands/gap-report.doc.mjs +15 -2
  298. package/clients/cli/commands/gap-report.mjs +24 -2
  299. package/clients/cli/commands/gap-report.test.mjs +79 -0
  300. package/clients/cli/commands/hook.doc.mjs +1 -0
  301. package/clients/cli/commands/init.behavior.test.mjs +5 -0
  302. package/clients/cli/commands/init.doc.mjs +29 -3
  303. package/clients/cli/commands/integration-add.doc.mjs +7 -0
  304. package/clients/cli/commands/integration-authoring.test.mjs +49 -3
  305. package/clients/cli/commands/integration-check.doc.mjs +93 -0
  306. package/clients/cli/commands/integration-check.test.mjs +441 -0
  307. package/clients/cli/commands/integration-pack.doc.mjs +8 -1
  308. package/clients/cli/commands/integration-verify.doc.mjs +8 -1
  309. package/clients/cli/commands/integration.doc.mjs +6 -2
  310. package/clients/cli/commands/integration.mjs +119 -7
  311. package/clients/cli/commands/json-contract.test.mjs +11 -2
  312. package/clients/cli/commands/manifest.doc.mjs +1 -0
  313. package/clients/cli/commands/outside-project.test.mjs +774 -0
  314. package/clients/cli/commands/search.doc.mjs +1 -0
  315. package/clients/cli/commands/search.mjs +16 -11
  316. package/clients/cli/commands/search.test.mjs +429 -296
  317. package/clients/cli/commands/setup-nudge.test.mjs +102 -2
  318. package/clients/cli/commands/swizzle.doc.mjs +7 -0
  319. package/clients/cli/commands/swizzle.mjs +5 -3
  320. package/clients/cli/commands/swizzle.routing.test.mjs +19 -2
  321. package/clients/cli/commands/template.doc.mjs +1 -0
  322. package/clients/cli/commands/text-json-parity.test.mjs +5 -0
  323. package/clients/cli/commands/theme-add-lifecycle.test.mjs +3 -3
  324. package/clients/cli/commands/theme-add.doc.mjs +8 -1
  325. package/clients/cli/commands/theme-build.doc.mjs +3 -2
  326. package/clients/cli/commands/theme-eject.doc.mjs +1 -0
  327. package/clients/cli/commands/theme-list.behavior.test.mjs +6 -0
  328. package/clients/cli/commands/theme-list.doc.mjs +1 -0
  329. package/clients/cli/commands/theme-palette-generate.doc.mjs +1 -0
  330. package/clients/cli/commands/theme-remove.doc.mjs +7 -0
  331. package/clients/cli/commands/theme-targets.doc.mjs +1 -0
  332. package/clients/cli/commands/theme-template.doc.mjs +1 -0
  333. package/clients/cli/commands/theme-use.doc.mjs +7 -0
  334. package/clients/cli/commands/upgrade.config-ordering.test.mjs +2 -1
  335. package/clients/cli/commands/upgrade.doc.mjs +22 -1
  336. package/clients/cli/commands/upgrade.file-protection.test.mjs +3 -2
  337. package/clients/cli/commands/upgrade.hook-output.test.mjs +2 -1
  338. package/clients/cli/commands/upgrade.older-cli.test.mjs +160 -0
  339. package/clients/cli/index.mjs +112 -16
  340. package/clients/cli/lib/cli-error.mjs +4 -1
  341. package/clients/cli/lib/component-format.test.mjs +1 -1
  342. package/clients/cli/lib/define-command.mjs +2 -2
  343. package/clients/cli/lib/json-shim.mjs +3 -1
  344. package/clients/cli/lib/manifest.d.ts +9 -0
  345. package/clients/cli/lib/manifest.mjs +17 -1
  346. package/clients/cli/lib/manifest.test.mjs +25 -0
  347. package/foundation/agent-docs/agent-doc-state.d.mts +35 -6
  348. package/foundation/agent-docs/agent-doc-state.mjs +66 -6
  349. package/foundation/agent-docs/agent-docs.d.mts +4 -3
  350. package/foundation/agent-docs/agent-docs.mjs +11 -9
  351. package/foundation/agent-docs/computer-agent-text.d.mts +133 -0
  352. package/foundation/agent-docs/computer-agent-text.mjs +623 -0
  353. package/foundation/agent-docs/computer-agent-text.test.mjs +602 -0
  354. package/foundation/config/project.d.mts +35 -3
  355. package/foundation/config/project.mjs +226 -7
  356. package/foundation/config/project.test.mjs +66 -0
  357. package/foundation/config/theme-state.mjs +5 -4
  358. package/foundation/discovery/authoring-self-docs.mjs +2 -1
  359. package/foundation/discovery/cli-self-docs.mjs +20 -3
  360. package/foundation/discovery/cli-self-docs.test.mjs +3 -2
  361. package/foundation/discovery/component-discovery.d.mts +190 -10
  362. package/foundation/discovery/component-discovery.export-names.test.mjs +760 -0
  363. package/foundation/discovery/component-discovery.mjs +864 -56
  364. package/foundation/discovery/component-discovery.test.mjs +96 -0
  365. package/foundation/discovery/component-replacement.mjs +20 -1
  366. package/foundation/discovery/component-replacement.test.mjs +40 -0
  367. package/foundation/discovery/copied-passages.d.mts +216 -0
  368. package/foundation/discovery/copied-passages.mjs +720 -0
  369. package/foundation/discovery/copied-passages.test.mjs +124 -0
  370. package/foundation/discovery/doc-consistency.d.mts +34 -0
  371. package/foundation/discovery/doc-consistency.mjs +513 -0
  372. package/foundation/discovery/doc-consistency.test.mjs +347 -0
  373. package/foundation/discovery/doc-fluff.d.mts +112 -0
  374. package/foundation/discovery/doc-fluff.mjs +328 -0
  375. package/foundation/discovery/doc-fluff.test.mjs +142 -0
  376. package/foundation/discovery/doc-types.d.mts +157 -0
  377. package/foundation/discovery/doc-types.mjs +1231 -0
  378. package/foundation/discovery/doc-types.test.mjs +744 -0
  379. package/foundation/discovery/docs-discovery.d.mts +88 -0
  380. package/foundation/discovery/docs-discovery.mjs +83 -2
  381. package/foundation/discovery/integration-doc-files.d.mts +22 -0
  382. package/foundation/discovery/integration-doc-files.mjs +231 -0
  383. package/foundation/discovery/integration-doc-files.test.mjs +246 -0
  384. package/foundation/discovery/module-exports.d.mts +27 -0
  385. package/foundation/discovery/module-exports.mjs +356 -31
  386. package/foundation/discovery/module-exports.test.mjs +172 -0
  387. package/foundation/discovery/package-tsconfig.d.mts +14 -0
  388. package/foundation/discovery/package-tsconfig.mjs +228 -0
  389. package/foundation/discovery/package-tsconfig.test.mjs +210 -0
  390. package/foundation/discovery/project-typescript.d.mts +32 -0
  391. package/foundation/discovery/project-typescript.mjs +89 -0
  392. package/foundation/discovery/prop-drift.d.mts +297 -0
  393. package/foundation/discovery/prop-drift.mjs +2428 -0
  394. package/foundation/discovery/prop-drift.test.mjs +2337 -0
  395. package/foundation/discovery/runner-allowlist.d.mts +40 -0
  396. package/foundation/discovery/runner-allowlist.mjs +53 -0
  397. package/foundation/discovery/stray-pointers.d.mts +52 -0
  398. package/foundation/discovery/stray-pointers.mjs +293 -0
  399. package/foundation/discovery/stray-pointers.test.mjs +86 -0
  400. package/foundation/discovery/template-adapter.mjs +74 -5
  401. package/foundation/discovery/template-needs.mjs +5 -2
  402. package/foundation/discovery/theme-imports.mjs +8 -4
  403. package/foundation/discovery/theme-imports.test.mjs +1 -1
  404. package/foundation/discovery/type-match.d.mts +53 -0
  405. package/foundation/discovery/type-match.mjs +660 -0
  406. package/foundation/discovery/type-match.test.mjs +169 -0
  407. package/foundation/discovery/type-text.d.mts +68 -0
  408. package/foundation/discovery/type-text.mjs +658 -0
  409. package/foundation/discovery/type-text.test.mjs +196 -0
  410. package/foundation/discovery/unresolved-references.d.mts +62 -0
  411. package/foundation/discovery/unresolved-references.mjs +790 -0
  412. package/foundation/discovery/unresolved-references.test.mjs +567 -0
  413. package/foundation/doc-compiler/doc-loads.test.mjs +51 -1
  414. package/foundation/doc-compiler/inputs.mjs +27 -1
  415. package/foundation/doc-compiler/inputs.test.mjs +0 -1
  416. package/foundation/doc-compiler/links.d.mts +41 -5
  417. package/foundation/doc-compiler/links.mjs +167 -28
  418. package/foundation/doc-compiler/links.test.mjs +97 -4
  419. package/foundation/doc-compiler/read.d.mts +1 -1
  420. package/foundation/doc-compiler/tree.mjs +30 -1
  421. package/foundation/doc-compiler/tree.test.mjs +8 -1
  422. package/foundation/env/cli-command.d.mts +83 -0
  423. package/foundation/env/cli-command.mjs +152 -0
  424. package/foundation/env/cli-command.test.mjs +129 -0
  425. package/foundation/env/node-version.test.mjs +15 -3
  426. package/foundation/env/package-manager.d.mts +93 -9
  427. package/foundation/env/package-manager.global.test.mjs +66 -0
  428. package/foundation/env/package-manager.mjs +184 -13
  429. package/foundation/env/package-manager.test.mjs +341 -15
  430. package/foundation/env/project-scope.d.mts +9 -0
  431. package/foundation/env/project-scope.mjs +33 -0
  432. package/foundation/env/search-path.d.mts +42 -0
  433. package/foundation/env/search-path.mjs +205 -0
  434. package/foundation/env/search-path.test.mjs +122 -0
  435. package/foundation/env/user-dirs.d.mts +39 -0
  436. package/foundation/env/user-dirs.mjs +93 -0
  437. package/foundation/env/user-dirs.test.mjs +72 -0
  438. package/foundation/env/user-settings.d.mts +46 -0
  439. package/foundation/env/user-settings.mjs +106 -0
  440. package/foundation/env/user-settings.test.mjs +64 -0
  441. package/foundation/fs/file-protection.mjs +15 -1
  442. package/foundation/fs/module-loader.d.mts +2 -1
  443. package/foundation/fs/module-loader.mjs +60 -4
  444. package/foundation/fs/module-loader.stdout.test.mjs +3 -2
  445. package/foundation/fs/module-loader.test.mjs +194 -0
  446. package/foundation/fs/path-compare.d.mts +33 -0
  447. package/foundation/fs/path-compare.mjs +73 -0
  448. package/foundation/fs/paths.d.mts +57 -1
  449. package/foundation/fs/paths.mjs +131 -3
  450. package/foundation/fs/paths.test.mjs +10 -1
  451. package/foundation/integrations/computer-integrations.d.mts +78 -0
  452. package/foundation/integrations/computer-integrations.mjs +316 -0
  453. package/foundation/integrations/computer-integrations.test.mjs +292 -0
  454. package/foundation/integrations/contribution-inventory.d.mts +11 -0
  455. package/foundation/integrations/contribution-inventory.mjs +56 -3
  456. package/foundation/integrations/integration-warnings.mjs +3 -2
  457. package/foundation/integrations/integration-warnings.test.mjs +8 -9
  458. package/foundation/integrations/integrations.d.mts +25 -1
  459. package/foundation/integrations/integrations.mjs +46 -3
  460. package/foundation/integrations/issue.ts +6 -0
  461. package/foundation/integrations/validate-contributions.d.mts +2 -0
  462. package/foundation/integrations/validate-contributions.mjs +41 -1
  463. package/foundation/release/agent-text-worker.d.mts +4 -0
  464. package/foundation/release/agent-text-worker.mjs +52 -0
  465. package/foundation/release/cache.d.mts +310 -0
  466. package/foundation/release/cache.mjs +1216 -0
  467. package/foundation/release/cache.test.mjs +920 -0
  468. package/foundation/release/choose.d.mts +207 -0
  469. package/foundation/release/choose.mjs +677 -0
  470. package/foundation/release/choose.test.mjs +427 -0
  471. package/foundation/release/core-worker.d.mts +4 -0
  472. package/foundation/release/core-worker.mjs +38 -0
  473. package/foundation/release/global-state.d.mts +259 -0
  474. package/foundation/release/global-state.mjs +472 -0
  475. package/foundation/release/global-state.test.mjs +381 -0
  476. package/foundation/release/handoff.d.mts +60 -0
  477. package/foundation/release/handoff.e2e.test.mjs +278 -0
  478. package/foundation/release/handoff.mjs +357 -0
  479. package/foundation/release/lock-lease.d.mts +23 -0
  480. package/foundation/release/lock-lease.mjs +80 -0
  481. package/foundation/release/notices.d.mts +253 -0
  482. package/foundation/release/notices.mjs +596 -0
  483. package/foundation/release/notices.test.mjs +565 -0
  484. package/foundation/release/npm-guard.d.mts +4 -0
  485. package/foundation/release/npm-guard.mjs +126 -0
  486. package/foundation/release/project.d.mts +141 -0
  487. package/foundation/release/project.mjs +466 -0
  488. package/foundation/release/project.test.mjs +288 -0
  489. package/foundation/release/refresh-worker.d.mts +4 -0
  490. package/foundation/release/refresh-worker.mjs +27 -0
  491. package/foundation/release/run-context.d.mts +47 -0
  492. package/foundation/release/run-context.mjs +149 -0
  493. package/foundation/release/update-notice-env.d.mts +28 -0
  494. package/foundation/release/update-notice-env.mjs +39 -0
  495. package/foundation/response/base.d.ts +26 -3
  496. package/foundation/response/error-codes.d.mts +3 -1
  497. package/foundation/response/error-codes.doc.mjs +12 -0
  498. package/foundation/response/error-codes.mjs +17 -0
  499. package/foundation/response/error-codes.test.mjs +2 -0
  500. package/foundation/response/json-contract.test.mjs +7 -2
  501. package/foundation/response/json.mjs +4 -1
  502. package/foundation/response/response-types.doc.mjs +22 -11
  503. package/foundation/response/response-types.doc.test.mjs +46 -0
  504. package/foundation/response/response.doc.mjs +3 -3
  505. package/foundation/xle/browser-imports.test.mjs +74 -0
  506. package/foundation/xle/registry.mjs +4 -0
  507. package/foundation/xle/registry.test.mjs +28 -0
  508. package/package.json +11 -11
  509. package/scripts/postinstall.mjs +24 -5
@@ -17,7 +17,18 @@
17
17
  import {Worker} from 'node:worker_threads';
18
18
  import {AstryxError} from '../error.mjs';
19
19
  import {Project} from '../../foundation/config/project.mjs';
20
- import {CORE_PACKAGE} from '../../foundation/discovery/component-discovery.mjs';
20
+ import {
21
+ CORE_PACKAGE,
22
+ discoverIntegrationComponents,
23
+ docStem,
24
+ findComponentReadme,
25
+ findIntegrationComponent,
26
+ integrationRecordHold,
27
+ isIntegrationMember,
28
+ unscopedComponentOwners,
29
+ } from '../../foundation/discovery/component-discovery.mjs';
30
+ import {legacyExternalKeeps} from '../component/_adapter.mjs';
31
+ import {findCoreDir} from '../../foundation/fs/paths.mjs';
21
32
  import {captureEnv} from '../../foundation/debug/event.mjs';
22
33
  import {ERROR_CODES} from '../../foundation/response/error-codes.mjs';
23
34
  import {parseGapReportReceipt} from '../../authoring/gap-report/parse.mjs';
@@ -44,6 +55,14 @@ export const GAP_REPORT_CATEGORIES = Object.freeze([
44
55
  export const PUBLIC_CONFIRMATION_MESSAGE =
45
56
  'Rerun with --confirm-public to file this report.';
46
57
 
58
+ /** What an agent does when more than one destination exists. */
59
+ export const ASK_DESTINATION_MESSAGE =
60
+ 'Ask the person where to file this report, then pass their choice with --to <package>. Never pick a package destination yourself; leave --to out to use the default.';
61
+
62
+ /**
63
+ * @typedef {import('./gap-report.type.mjs').GapReportDestination} GapReportDestination
64
+ */
65
+
47
66
  /**
48
67
  * @typedef {object} ReportTarget
49
68
  * @property {string} package
@@ -178,13 +197,65 @@ async function selectTarget(project, component, packageName) {
178
197
  coreName,
179
198
  );
180
199
  if (replacement) return targetForPackage(project, replacement.package);
200
+ // The owner rule runs over every owner discovery finds, loadable or not
201
+ // (a doc recorded under another export still answers to its file's name);
202
+ // then only owners whose docs load route a report.
203
+ const wanted = bareComponent.toLowerCase();
204
+ const loads = new Set(records.map(r => `${r.package}\0${r.docPath}`));
205
+ const recorded = project.loadedIntegrations.flatMap(integration =>
206
+ discoverIntegrationComponents(integration).flatMap(r =>
207
+ r.name.toLowerCase() === wanted
208
+ ? [{...r, hold: integrationRecordHold(r)}]
209
+ : docStem(r.docPath).toLowerCase() === wanted && !isIntegrationMember(r)
210
+ ? [{...r, hold: 'doc'}]
211
+ : [],
212
+ ),
213
+ );
214
+ // The checks below find files by name, so they run on every spelling the
215
+ // case-blind match above found, as well as the one typed.
216
+ const spellings = new Set([
217
+ bareComponent,
218
+ ...recorded.flatMap(r =>
219
+ [r.name, docStem(r.docPath)].filter(n => n.toLowerCase() === wanted),
220
+ ),
221
+ ]);
222
+ // Core also holds a name only its doc lookup finds, as in detail: a name a
223
+ // doc records yields to it, though it routes no report itself.
224
+ const coreDir = findCoreDir(project.cwd);
225
+ const coreDocs =
226
+ coreDir != null &&
227
+ [...spellings].some(name => findComponentReadme(coreDir, name) != null);
228
+ const ruled = unscopedComponentOwners([
229
+ ...records
230
+ .filter(
231
+ r => r.package === CORE_PACKAGE && r.name.toLowerCase() === wanted,
232
+ )
233
+ .map(r => ({...r, hold: 'core'})),
234
+ ...recorded,
235
+ // A hidden doc named after its file still holds that name, as the
236
+ // component lookup finds it; it routes no report.
237
+ ...project.loadedIntegrations.flatMap(integration =>
238
+ [...spellings].flatMap(spelling => {
239
+ const found = findIntegrationComponent(integration, spelling);
240
+ return found?.hold === 'file'
241
+ ? [{...found, package: integration.name}]
242
+ : [];
243
+ }),
244
+ ),
245
+ ]).filter(r => !coreDocs || r.hold !== 'doc');
246
+ // A legacy docs package keeps an export name a doc records, as in detail.
181
247
  const owners = [
182
248
  ...new Set(
183
- records
249
+ ([...spellings].some(name =>
250
+ legacyExternalKeeps(project.cwd, name, ruled),
251
+ )
252
+ ? []
253
+ : ruled
254
+ )
184
255
  .filter(
185
- record => record.name.toLowerCase() === bareComponent.toLowerCase(),
256
+ r => r.hold === 'core' || loads.has(`${r.package}\0${r.docPath}`),
186
257
  )
187
- .map(record => record.package),
258
+ .map(r => r.package),
188
259
  ),
189
260
  ];
190
261
 
@@ -474,15 +545,14 @@ function githubRoute(issuesUrl) {
474
545
  }
475
546
 
476
547
  /**
477
- * Built-in GitHub fallback — runs only when the effective handler set is empty.
478
- * Requires confirmPublic.
548
+ * File a report on GitHub with `gh` — for the zero-handler fallback and for a
549
+ * destination the caller names. Requires confirmPublic.
479
550
  *
480
- * @param {ReportTarget} target
481
551
  * @param {{component: string, categoryLabel: string, intention: string, detail?: string, source: string, timestamp: string}} report
482
552
  * @param {{repo: string}} github
483
553
  * @returns {Promise<Pick<import('./gap-report.type.mjs').GapReportDelivery, 'status'|'url'|'message'>>}
484
554
  */
485
- async function runGithubFallback(target, report, github) {
555
+ async function runGithubFallback(report, github) {
486
556
  const {execFile} = await import('node:child_process');
487
557
  const title = `[gap] ${report.component}: ${report.intention.slice(0, 70)}`;
488
558
  const body = [
@@ -544,6 +614,87 @@ async function runGithubFallback(target, report, github) {
544
614
  }
545
615
  }
546
616
 
617
+ /**
618
+ * Send a report to an issues URL: a GitHub issues URL is filed with `gh` only
619
+ * with consent; any other URL comes back as routed_only. Serves the
620
+ * zero-handler fallback (spec:AST-031/FR8) and a destination named with `to`
621
+ * (architecture:cli-surface INV36).
622
+ *
623
+ * @param {string} issuesUrl
624
+ * @param {{component: string, categoryLabel: string, intention: string, detail?: string, source: string, timestamp: string}} report
625
+ * @param {boolean} confirmPublic
626
+ * @returns {Promise<import('./gap-report.type.mjs').GapReportDelivery>}
627
+ */
628
+ async function deliverToIssuesUrl(issuesUrl, report, confirmPublic) {
629
+ const github = githubRoute(issuesUrl);
630
+ if (github != null && !confirmPublic) {
631
+ return {
632
+ handlerType: 'fallback',
633
+ handler: 'github',
634
+ audience: 'public',
635
+ status: 'consent_required',
636
+ url: null,
637
+ message: PUBLIC_CONFIRMATION_MESSAGE,
638
+ };
639
+ }
640
+ if (github) {
641
+ return {
642
+ handlerType: 'fallback',
643
+ handler: 'github',
644
+ audience: 'public',
645
+ ...(await runGithubFallback(report, github)),
646
+ };
647
+ }
648
+ return {
649
+ handlerType: 'fallback',
650
+ handler: 'issuesUrl',
651
+ audience: null,
652
+ status: 'routed_only',
653
+ url: issuesUrl,
654
+ message:
655
+ 'No automatic handler is available; open the routed URL to file this report.',
656
+ };
657
+ }
658
+
659
+ /**
660
+ * Where a report can go: the default (the handlers, or with none the owning
661
+ * package's issues URL), then each loaded integration that names its own
662
+ * issues URL (architecture:cli-surface INV36).
663
+ *
664
+ * @param {Project} project
665
+ * @returns {GapReportDestination[]}
666
+ */
667
+ function listDestinations(project) {
668
+ /** @type {GapReportDestination[]} */
669
+ const destinations = [{to: null, default: true, issuesUrl: null}];
670
+ for (const integration of project.loadedIntegrations) {
671
+ if (integration.__loadError || !integration.issuesUrl) continue;
672
+ if (destinations.some(destination => destination.to === integration.name))
673
+ continue;
674
+ destinations.push({
675
+ to: integration.name,
676
+ default: false,
677
+ issuesUrl: integration.issuesUrl,
678
+ });
679
+ }
680
+ return destinations;
681
+ }
682
+
683
+ /**
684
+ * The destinations of the project at `cwd`, or only the default when no
685
+ * project loads there.
686
+ * @param {string|undefined} cwd
687
+ * @returns {Promise<GapReportDestination[]>}
688
+ */
689
+ async function destinationsAt(cwd) {
690
+ try {
691
+ const {resolve} = await import('node:path');
692
+ return listDestinations(await Project.load(resolve(cwd ?? process.cwd())));
693
+ } catch {
694
+ return [{to: null, default: true, issuesUrl: null}];
695
+ }
696
+ }
697
+
547
698
  /**
548
699
  * Compute the aggregate status from the deliveries.
549
700
  * @param {import('./gap-report.type.mjs').GapReportDelivery[]} deliveries
@@ -602,6 +753,7 @@ export async function gapReport(component, options = {}) {
602
753
  options.detail ? '--additional-context' : null,
603
754
  options.package ? '--package' : null,
604
755
  options.confirmPublic ? '--confirm-public' : null,
756
+ options.to != null ? '--to' : null,
605
757
  ].filter(Boolean);
606
758
  if (ignored.length > 0) {
607
759
  throw new AstryxError(
@@ -610,9 +762,13 @@ export async function gapReport(component, options = {}) {
610
762
  ERROR_CODES.ERR_INVALID_ARGUMENT,
611
763
  );
612
764
  }
765
+ const destinations = await destinationsAt(options.cwd);
613
766
  return {
614
767
  type: 'gap-report.categories',
615
768
  data: GAP_REPORT_CATEGORIES.map(category => ({...category})),
769
+ ...(destinations.length > 1
770
+ ? {meta: {destinations, ask: ASK_DESTINATION_MESSAGE}}
771
+ : {}),
616
772
  };
617
773
  }
618
774
 
@@ -641,7 +797,33 @@ export async function gapReport(component, options = {}) {
641
797
  options.package,
642
798
  );
643
799
 
644
- const entries = collectGapReportHandlers(project);
800
+ const destinations = listDestinations(project);
801
+ /** @type {GapReportDestination | undefined} */
802
+ let chosen;
803
+ // `to: null` is absent, like `package: null`; any other value is matched
804
+ // against the destinations as given, so an empty or unknown one lists them.
805
+ if (options.to != null) {
806
+ const to = options.to;
807
+ chosen = destinations.find(
808
+ destination => !destination.default && destination.to === to,
809
+ );
810
+ if (!chosen) {
811
+ const choices = destinations.filter(destination => !destination.default);
812
+ const named = JSON.stringify(String(to).slice(0, 214));
813
+ throw new AstryxError(
814
+ choices.length > 0
815
+ ? `No gap-report destination ${named}. Choose one of: ${choices.map(destination => destination.to).join(', ')}; or leave out --to for the default.`
816
+ : `No gap-report destination ${named}: no loaded integration names an issues URL. Leave out --to for the default.`,
817
+ choices.map(destination => ({
818
+ name: /** @type {string} */ (destination.to),
819
+ reason: /** @type {string} */ (destination.issuesUrl),
820
+ })),
821
+ ERROR_CODES.ERR_UNKNOWN_PACKAGE,
822
+ );
823
+ }
824
+ }
825
+
826
+ const entries = chosen ? [] : collectGapReportHandlers(project);
645
827
 
646
828
  const timestamp = new Date().toISOString();
647
829
  const source = captureEnv().invocationSource;
@@ -668,8 +850,24 @@ export async function gapReport(component, options = {}) {
668
850
 
669
851
  /** @type {import('./gap-report.type.mjs').GapReportDelivery[]} */
670
852
  const deliveries = [];
853
+ const routeReport = {
854
+ component: resolvedComponent,
855
+ categoryLabel: categoryEntry.label,
856
+ intention,
857
+ detail,
858
+ source,
859
+ timestamp,
860
+ };
671
861
 
672
- if (entries.length > 0) {
862
+ if (chosen) {
863
+ deliveries.push(
864
+ await deliverToIssuesUrl(
865
+ /** @type {string} */ (chosen.issuesUrl),
866
+ routeReport,
867
+ options.confirmPublic === true,
868
+ ),
869
+ );
870
+ } else if (entries.length > 0) {
673
871
  deliveries.push(
674
872
  ...(await deliverGapReportHandlers(entries, report, {
675
873
  confirmPublic: options.confirmPublic === true,
@@ -683,47 +881,13 @@ export async function gapReport(component, options = {}) {
683
881
  ERROR_CODES.ERR_NOT_FOUND,
684
882
  );
685
883
  }
686
-
687
- const github = githubRoute(target.issuesUrl);
688
- if (github != null && options.confirmPublic !== true) {
689
- deliveries.push({
690
- handlerType: 'fallback',
691
- handler: 'github',
692
- audience: 'public',
693
- status: 'consent_required',
694
- url: null,
695
- message: PUBLIC_CONFIRMATION_MESSAGE,
696
- });
697
- } else if (github) {
698
- const outcome = await runGithubFallback(
699
- target,
700
- {
701
- component: resolvedComponent,
702
- categoryLabel: categoryEntry.label,
703
- intention,
704
- detail,
705
- source,
706
- timestamp,
707
- },
708
- github,
709
- );
710
- deliveries.push({
711
- handlerType: 'fallback',
712
- handler: 'github',
713
- audience: 'public',
714
- ...outcome,
715
- });
716
- } else {
717
- deliveries.push({
718
- handlerType: 'fallback',
719
- handler: 'issuesUrl',
720
- audience: null,
721
- status: 'routed_only',
722
- url: target.issuesUrl,
723
- message:
724
- 'No automatic handler is available; open the routed URL to file this report.',
725
- });
726
- }
884
+ deliveries.push(
885
+ await deliverToIssuesUrl(
886
+ target.issuesUrl,
887
+ routeReport,
888
+ options.confirmPublic === true,
889
+ ),
890
+ );
727
891
  }
728
892
 
729
893
  const filedCount = deliveries.filter(d => d.status === 'filed').length;
@@ -737,10 +901,13 @@ export async function gapReport(component, options = {}) {
737
901
  data: {
738
902
  status,
739
903
  package: target.package,
740
- issuesUrl: target.issuesUrl,
904
+ issuesUrl: chosen ? chosen.issuesUrl : target.issuesUrl,
741
905
  deliveries,
742
906
  filedCount,
743
907
  routedOnlyCount,
908
+ ...(!chosen && destinations.length > 1
909
+ ? {destinations, ask: ASK_DESTINATION_MESSAGE}
910
+ : {}),
744
911
  },
745
912
  };
746
913
  }
@@ -7,6 +7,7 @@ import * as path from 'node:path';
7
7
  import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
8
8
  import {Project} from '../../foundation/config/project.mjs';
9
9
  import {
10
+ ASK_DESTINATION_MESSAGE,
10
11
  GAP_REPORT_CATEGORIES,
11
12
  PUBLIC_CONFIRMATION_MESSAGE,
12
13
  collectGapReportHandlers,
@@ -1022,6 +1023,238 @@ console.log('https://github.com/acme/widgets/issues/42');
1022
1023
  });
1023
1024
  });
1024
1025
 
1026
+ describe('gapReport destinations and --to', () => {
1027
+ /** A shared internal handler that records each call in a marker file. */
1028
+ function addSharedHandler() {
1029
+ const marker = path.join(projectDir, 'shared-called');
1030
+ addIntegration({
1031
+ name: '@test/shared',
1032
+ audience: 'internal',
1033
+ handleBody: `(await import('node:fs')).writeFileSync(${JSON.stringify(marker)}, 'x'); return {status: 'filed', message: 'shared'};`,
1034
+ });
1035
+ return marker;
1036
+ }
1037
+
1038
+ function addWidgets(issuesUrl = 'https://github.com/acme/widgets/issues') {
1039
+ addIntegration({name: '@test/widgets', handler: false, issuesUrl});
1040
+ }
1041
+
1042
+ it('keeps the default route and lists the destinations with the ask line', async () => {
1043
+ const marker = addSharedHandler();
1044
+ addWidgets();
1045
+ configure(['@test/shared', '@test/widgets']);
1046
+ const argsFile = stubGh();
1047
+
1048
+ const result = await gapReport('General', {
1049
+ ...reportOptions,
1050
+ confirmPublic: true,
1051
+ cwd: projectDir,
1052
+ });
1053
+
1054
+ expect(result.data.deliveries).toEqual([
1055
+ expect.objectContaining({handler: '@test/shared', status: 'filed'}),
1056
+ ]);
1057
+ expect(fs.existsSync(marker)).toBe(true);
1058
+ expect(fs.existsSync(argsFile)).toBe(false);
1059
+ expect(result.data.destinations).toEqual([
1060
+ {to: null, default: true, issuesUrl: null},
1061
+ {
1062
+ to: '@test/widgets',
1063
+ default: false,
1064
+ issuesUrl: 'https://github.com/acme/widgets/issues',
1065
+ },
1066
+ ]);
1067
+ expect(result.data.ask).toBe(ASK_DESTINATION_MESSAGE);
1068
+ expect(ASK_DESTINATION_MESSAGE).toMatch(/ask the person/i);
1069
+ });
1070
+
1071
+ it('lists no destinations when no integration names an issuesUrl', async () => {
1072
+ addSharedHandler();
1073
+ configure(['@test/shared']);
1074
+
1075
+ const result = await gapReport('General', {
1076
+ ...reportOptions,
1077
+ cwd: projectDir,
1078
+ });
1079
+
1080
+ expect(result.data).not.toHaveProperty('destinations');
1081
+ expect(result.data).not.toHaveProperty('ask');
1082
+ });
1083
+
1084
+ it('files with the chosen integration GitHub issuesUrl only with consent, and runs no handler', async () => {
1085
+ const marker = addSharedHandler();
1086
+ addWidgets();
1087
+ configure(['@test/shared', '@test/widgets']);
1088
+ const argsFile = stubGh();
1089
+
1090
+ const pending = await gapReport('General', {
1091
+ ...reportOptions,
1092
+ to: '@test/widgets',
1093
+ cwd: projectDir,
1094
+ });
1095
+ expect(pending.data.deliveries).toEqual([
1096
+ {
1097
+ handlerType: 'fallback',
1098
+ handler: 'github',
1099
+ audience: 'public',
1100
+ status: 'consent_required',
1101
+ url: null,
1102
+ message: PUBLIC_CONFIRMATION_MESSAGE,
1103
+ },
1104
+ ]);
1105
+ expect(pending.data.issuesUrl).toBe(
1106
+ 'https://github.com/acme/widgets/issues',
1107
+ );
1108
+ expect(pending.data).not.toHaveProperty('destinations');
1109
+ expect(fs.existsSync(argsFile)).toBe(false);
1110
+
1111
+ const filed = await gapReport('General', {
1112
+ ...reportOptions,
1113
+ to: '@test/widgets',
1114
+ confirmPublic: true,
1115
+ cwd: projectDir,
1116
+ });
1117
+ expect(filed.data.deliveries).toHaveLength(1);
1118
+ expect(filed.data.deliveries[0]).toMatchObject({
1119
+ handlerType: 'fallback',
1120
+ handler: 'github',
1121
+ status: 'filed',
1122
+ });
1123
+ const args = JSON.parse(fs.readFileSync(argsFile, 'utf8'));
1124
+ expect(args.slice(0, 4)).toEqual([
1125
+ 'issue',
1126
+ 'create',
1127
+ '--repo',
1128
+ 'acme/widgets',
1129
+ ]);
1130
+ expect(fs.existsSync(marker)).toBe(false);
1131
+ });
1132
+
1133
+ it('routes a chosen non-GitHub issuesUrl as routed_only', async () => {
1134
+ addSharedHandler();
1135
+ addWidgets('https://tracker.example/widgets');
1136
+ configure(['@test/shared', '@test/widgets']);
1137
+
1138
+ const result = await gapReport('General', {
1139
+ ...reportOptions,
1140
+ to: '@test/widgets',
1141
+ cwd: projectDir,
1142
+ });
1143
+
1144
+ expect(result.data.deliveries).toEqual([
1145
+ expect.objectContaining({
1146
+ handlerType: 'fallback',
1147
+ handler: 'issuesUrl',
1148
+ status: 'routed_only',
1149
+ url: 'https://tracker.example/widgets',
1150
+ }),
1151
+ ]);
1152
+ expect(result.data.status).toBe('routed_only');
1153
+ });
1154
+
1155
+ it.each([
1156
+ ['a package that is not loaded', '@test/nope'],
1157
+ ['a loaded integration without an issuesUrl', '@test/shared'],
1158
+ ])(
1159
+ 'refuses --to naming %s, lists the choices, and sends nothing',
1160
+ async (_label, to) => {
1161
+ const marker = addSharedHandler();
1162
+ addWidgets();
1163
+ configure(['@test/shared', '@test/widgets']);
1164
+
1165
+ await expect(
1166
+ gapReport('General', {...reportOptions, to, cwd: projectDir}),
1167
+ ).rejects.toMatchObject({
1168
+ code: 'ERR_UNKNOWN_PACKAGE',
1169
+ suggestions: [
1170
+ {
1171
+ name: '@test/widgets',
1172
+ reason: 'https://github.com/acme/widgets/issues',
1173
+ },
1174
+ ],
1175
+ });
1176
+ expect(fs.existsSync(marker)).toBe(false);
1177
+ },
1178
+ );
1179
+
1180
+ it.each([
1181
+ ['an empty value', ''],
1182
+ ['a blank value', ' '],
1183
+ ['an over-long value', `@test/${'a'.repeat(300)}`],
1184
+ ])('refuses %s for --to with the destination list', async (_label, to) => {
1185
+ const marker = addSharedHandler();
1186
+ addWidgets();
1187
+ configure(['@test/shared', '@test/widgets']);
1188
+
1189
+ await expect(
1190
+ gapReport('General', {...reportOptions, to, cwd: projectDir}),
1191
+ ).rejects.toMatchObject({
1192
+ code: 'ERR_UNKNOWN_PACKAGE',
1193
+ suggestions: [{name: '@test/widgets'}],
1194
+ });
1195
+ expect(fs.existsSync(marker)).toBe(false);
1196
+ });
1197
+
1198
+ it('treats to: null as absent', async () => {
1199
+ const marker = addSharedHandler();
1200
+ addWidgets();
1201
+ configure(['@test/shared', '@test/widgets']);
1202
+
1203
+ const result = await gapReport('General', {
1204
+ ...reportOptions,
1205
+ to: /** @type {any} */ (null),
1206
+ cwd: projectDir,
1207
+ });
1208
+
1209
+ expect(result.data.deliveries).toHaveLength(1);
1210
+ expect(fs.existsSync(marker)).toBe(true);
1211
+ expect(result.data.destinations).toHaveLength(2);
1212
+ });
1213
+
1214
+ it('lists the destinations with the categories', async () => {
1215
+ addSharedHandler();
1216
+ addWidgets();
1217
+ configure(['@test/shared', '@test/widgets']);
1218
+
1219
+ const listed = await gapReport(undefined, {
1220
+ listCategories: true,
1221
+ cwd: projectDir,
1222
+ });
1223
+
1224
+ expect(listed.data).toEqual(GAP_REPORT_CATEGORIES);
1225
+ expect(listed.meta).toEqual({
1226
+ destinations: [
1227
+ {to: null, default: true, issuesUrl: null},
1228
+ {
1229
+ to: '@test/widgets',
1230
+ default: false,
1231
+ issuesUrl: 'https://github.com/acme/widgets/issues',
1232
+ },
1233
+ ],
1234
+ ask: ASK_DESTINATION_MESSAGE,
1235
+ });
1236
+ });
1237
+
1238
+ it('adds no meta to the categories when there is only the default', async () => {
1239
+ configure([]);
1240
+ const listed = await gapReport(undefined, {
1241
+ listCategories: true,
1242
+ cwd: projectDir,
1243
+ });
1244
+ expect(listed).not.toHaveProperty('meta');
1245
+ });
1246
+
1247
+ it('refuses --to with --list-categories', async () => {
1248
+ await expect(
1249
+ gapReport(undefined, {
1250
+ listCategories: true,
1251
+ to: '@test/widgets',
1252
+ cwd: projectDir,
1253
+ }),
1254
+ ).rejects.toMatchObject({code: 'ERR_INVALID_ARGUMENT'});
1255
+ });
1256
+ });
1257
+
1025
1258
  describe('gapReport target selection', () => {
1026
1259
  it('routes to the unique component owner', async () => {
1027
1260
  addIntegration({
@@ -35,10 +35,42 @@ export type GapReportOptions = {
35
35
  * Return the category vocabulary without routing or writing.
36
36
  */
37
37
  listCategories?: boolean | undefined;
38
+ /**
39
+ * A destination the person chose: a loaded integration
40
+ * whose manifest names an issues URL. The report goes only there.
41
+ */
42
+ to?: string | undefined;
43
+ };
44
+ /**
45
+ * Where a report can go. The default entry (`to: null`) is the configured
46
+ * handlers, or with none the owning package's issues URL.
47
+ */
48
+ export type GapReportDestination = {
49
+ /**
50
+ * The value to pass as `to`; null for the default.
51
+ */
52
+ to: string | null;
53
+ /**
54
+ * Whether this is the default destination.
55
+ */
56
+ default: boolean;
57
+ /**
58
+ * The integration's issues URL; null for the
59
+ * default.
60
+ */
61
+ issuesUrl: string | null;
38
62
  };
39
63
  export type GapReportCategoriesResponse = {
40
64
  type: "gap-report.categories";
41
65
  data: GapReportCategoryEntry[];
66
+ /**
67
+ * Present when a destination besides the default exists: every destination,
68
+ * and the line telling an agent to ask the person.
69
+ */
70
+ meta?: {
71
+ destinations: GapReportDestination[];
72
+ ask: string;
73
+ } | undefined;
42
74
  };
43
75
  export type GapReportAggregateStatus = "filed" | "partial" | "failed" | "routed_only" | "consent_required" | "skipped";
44
76
  export type GapReportDeliveryStatus = "filed" | "routed_only" | "skipped" | "failed" | "consent_required";
@@ -81,6 +113,15 @@ export type GapReportReceipt = {
81
113
  * Count of handlers that routed only.
82
114
  */
83
115
  routedOnlyCount: number;
116
+ /**
117
+ * For a run without `to`,
118
+ * when a destination besides the default exists: every destination.
119
+ */
120
+ destinations?: GapReportDestination[] | undefined;
121
+ /**
122
+ * With `destinations`: ask the person where to file.
123
+ */
124
+ ask?: string | undefined;
84
125
  };
85
126
  export type GapReportReceiptResponse = {
86
127
  type: "gap-report.file";
@@ -23,12 +23,27 @@
23
23
  * @property {string} [package] Explicit owner package for ambiguous routes.
24
24
  * @property {boolean} [confirmPublic] Explicit consent for public handlers or GitHub issue creation.
25
25
  * @property {boolean} [listCategories] Return the category vocabulary without routing or writing.
26
+ * @property {string} [to] A destination the person chose: a loaded integration
27
+ * whose manifest names an issues URL. The report goes only there.
28
+ */
29
+
30
+ /**
31
+ * Where a report can go. The default entry (`to: null`) is the configured
32
+ * handlers, or with none the owning package's issues URL.
33
+ * @typedef {object} GapReportDestination
34
+ * @property {string|null} to The value to pass as `to`; null for the default.
35
+ * @property {boolean} default Whether this is the default destination.
36
+ * @property {string|null} issuesUrl The integration's issues URL; null for the
37
+ * default.
26
38
  */
27
39
 
28
40
  /**
29
41
  * @typedef {object} GapReportCategoriesResponse
30
42
  * @property {'gap-report.categories'} type
31
43
  * @property {GapReportCategoryEntry[]} data
44
+ * @property {{destinations: GapReportDestination[], ask: string}} [meta]
45
+ * Present when a destination besides the default exists: every destination,
46
+ * and the line telling an agent to ask the person.
32
47
  */
33
48
 
34
49
  /**
@@ -58,6 +73,9 @@
58
73
  * @property {GapReportDelivery[]} deliveries Ordered handler outcomes.
59
74
  * @property {number} filedCount Count of handlers that filed the report.
60
75
  * @property {number} routedOnlyCount Count of handlers that routed only.
76
+ * @property {GapReportDestination[]} [destinations] For a run without `to`,
77
+ * when a destination besides the default exists: every destination.
78
+ * @property {string} [ask] With `destinations`: ask the person where to file.
61
79
  */
62
80
 
63
81
  /**