@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
@@ -0,0 +1,623 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file The computer-level agent text (spec AST-081 FR8): one skill file,
5
+ * `~/.agents/skills/astryx/SKILL.md`, which most coding agents read, linked
6
+ * into the skill folder of each agent that reads only its own
7
+ * (`SKILL_LINKS`), so every agent on the computer knows Astryx is there.
8
+ *
9
+ * - One file is written. Each other folder gets a link to its folder: a
10
+ * directory symlink, or a junction on Windows, which needs no
11
+ * administrator rights. A refresh rewrites only the file; the links follow.
12
+ * - `SKILL_LINKS` is the one table of link targets: a folder relative to the
13
+ * home folder, and the folder that must already exist for it to be linked
14
+ * (`null`: always). The writer, the remover, doctor, and the docs all read it.
15
+ * - The text is version-free and names only stable entry points
16
+ * (`STABLE_COMMANDS`), so an older file never misleads. It is short: the
17
+ * skill only has to say that Astryx is here, when to use it, and where to
18
+ * start; the CLI's own docs carry the rest.
19
+ * - The file keeps the Astryx markers, which tell a file Astryx wrote from one
20
+ * a person wrote, and records inside them a hash of its text, which tells an
21
+ * edited file from an older rendering. A file without the markers, an
22
+ * edited file, and a symlinked file are never changed. A link target that
23
+ * holds anything Astryx did not create is never replaced; doctor names it.
24
+ * - The links Astryx made are recorded in the settings file, so removal
25
+ * removes exactly those.
26
+ *
27
+ * `astryx init --user` writes and removes them, and the global copy's
28
+ * background step (`foundation/release/agent-text-worker.mjs`) keeps the file
29
+ * on its release (FR7, FR8). Both honor the settings file
30
+ * (`foundation/env/user-settings.mjs`). This module names fixed paths in the
31
+ * home folder and nothing else.
32
+ *
33
+ * @input the home folder, the host's door (if any), the settings file
34
+ * @output the rendered text, its state on disk, and the write/remove receipt rows
35
+ * @position foundation/agent-docs — read by `init --user`, by the background
36
+ * step, and by `doctor`.
37
+ */
38
+
39
+ import * as fs from 'node:fs';
40
+ import * as os from 'node:os';
41
+ import * as path from 'node:path';
42
+ import {createHash} from 'node:crypto';
43
+ import {cliDoor} from '../env/cli-command.mjs';
44
+ import {astryxCommandsOnSearchPath} from '../env/search-path.mjs';
45
+ import {samePath} from '../fs/path-compare.mjs';
46
+ import {MARKER_START, MARKER_END} from './agent-doc-state.mjs';
47
+
48
+ /** The command the computer-level text uses when no host door is set. */
49
+ export const COMPUTER_COMMAND = 'astryx';
50
+
51
+ /** The install step for the CLI copy the computer-level text relies on. */
52
+ export const COMPUTER_INSTALL_STEP = 'npm i -g @astryxdesign/cli';
53
+
54
+ /** The skill folder, relative to the home folder, that holds the one file. */
55
+ const SKILL_FOLDER = path.join('.agents', 'skills', 'astryx');
56
+
57
+ /**
58
+ * Where the skill folder reaches agents that read only their own skills
59
+ * folder, each relative to the home folder: the agent's skills folder, the
60
+ * folder that must already exist for it to be used (`null`: always), and how:
61
+ * `link`, a folder link to the skill folder with a relative target, or `copy`,
62
+ * a managed copy of the file for an agent whose scanner skips links. Public
63
+ * agents only. Never a store another tool manages, such as `~/.codex/skills`.
64
+ * @type {ReadonlyArray<{skills: string, when: string | null, mode: 'link' | 'copy'}>}
65
+ */
66
+ export const SKILL_LINKS = Object.freeze([
67
+ {skills: path.join('.claude', 'skills'), when: null, mode: 'link'},
68
+ {skills: path.join('.kiro', 'skills'), when: '.kiro', mode: 'link'},
69
+ {
70
+ skills: path.join('.codeium', 'windsurf', 'skills'),
71
+ when: path.join('.codeium', 'windsurf'),
72
+ mode: 'link',
73
+ },
74
+ {skills: path.join('.letta', 'skills'), when: '.letta', mode: 'link'},
75
+ {
76
+ skills: path.join(
77
+ 'Library',
78
+ 'Developer',
79
+ 'Xcode',
80
+ 'CodingAssistant',
81
+ 'ClaudeAgentConfig',
82
+ 'skills',
83
+ ),
84
+ when: path.join('Library', 'Developer', 'Xcode', 'CodingAssistant'),
85
+ mode: 'link',
86
+ },
87
+ // Kimi reads only the first of `~/.config/agents/skills` and
88
+ // `~/.agents/skills` that exists.
89
+ {
90
+ skills: path.join('.config', 'agents', 'skills'),
91
+ when: path.join('.config', 'agents', 'skills'),
92
+ mode: 'link',
93
+ },
94
+ {skills: path.join('.continue', 'skills'), when: '.continue', mode: 'copy'},
95
+ ]);
96
+
97
+ /**
98
+ * The commands the text may name: stable entry points every release keeps.
99
+ * computer-agent-text.test.mjs fails on any other.
100
+ */
101
+ export const STABLE_COMMANDS = Object.freeze([
102
+ 'search',
103
+ 'docs',
104
+ 'component',
105
+ 'template',
106
+ 'build',
107
+ 'theme',
108
+ 'init',
109
+ 'doctor',
110
+ 'help',
111
+ ]);
112
+
113
+ /**
114
+ * When an agent should load the skill; the frontmatter `description`. One
115
+ * sentence.
116
+ */
117
+ const SKILL_DESCRIPTION =
118
+ 'Astryx, a design system and UI library, is available on this computer through the astryx command; use it when building or changing UI, choosing components, layout, styling or theming, working with a design system, or in any project that depends on @astryxdesign packages or mentions Astryx.';
119
+
120
+ /**
121
+ * A home-folder path as a person reads it: `~/.claude/skills/astryx`.
122
+ * @param {string} file
123
+ * @param {string} [home]
124
+ * @returns {string}
125
+ */
126
+ export function displayHomePath(file, home = os.homedir()) {
127
+ const rel = path.relative(home, file);
128
+ return rel && !rel.startsWith('..') && !path.isAbsolute(rel)
129
+ ? `~${path.sep}${rel}`
130
+ : file;
131
+ }
132
+
133
+ /**
134
+ * The skill file's absolute path.
135
+ * @param {string} [home]
136
+ * @returns {string}
137
+ */
138
+ export function computerSkillFile(home = os.homedir()) {
139
+ return path.join(home, SKILL_FOLDER, 'SKILL.md');
140
+ }
141
+
142
+ /**
143
+ * The absolute path of each link the table names, and whether it is wanted
144
+ * on this computer (its condition folder exists).
145
+ * @param {string} [home]
146
+ * @returns {Array<{path: string, wanted: boolean, mode: 'link' | 'copy'}>}
147
+ */
148
+ export function computerSkillLinks(home = os.homedir()) {
149
+ return SKILL_LINKS.map(({skills, when, mode}) => ({
150
+ path: path.join(home, skills, 'astryx'),
151
+ wanted: when == null || fs.existsSync(path.join(home, when)),
152
+ mode,
153
+ }));
154
+ }
155
+
156
+ /**
157
+ * The command the computer-level text tells agents to run: the host's door
158
+ * when one started this run, else `astryx`. An integration's `cliCommand`
159
+ * never applies: no project, so no integration, is in scope.
160
+ * @returns {string}
161
+ */
162
+ export function computerAgentCommand() {
163
+ return cliDoor() ?? COMPUTER_COMMAND;
164
+ }
165
+
166
+ /**
167
+ * The body lines, with `run` as the command.
168
+ * @param {string} run
169
+ * @returns {string[]}
170
+ */
171
+ function bodyLines(run) {
172
+ return [
173
+ 'Astryx, a design system and UI library, is available on this computer.',
174
+ 'Use it when you build or change UI, pick components, or style or theme an app.',
175
+ `Its docs, components, templates, and themes are all reachable through \`${run}\`. Start with \`${run} search '<what you need>'\`.`,
176
+ `Then read \`${run} component <Name>\` or \`${run} docs <topic>\`, not the package's files.`,
177
+ "In a project, the project's version of Astryx answers automatically.",
178
+ ];
179
+ }
180
+
181
+ /**
182
+ * The full skill file.
183
+ * @param {{command?: string}} [options]
184
+ * @returns {string}
185
+ */
186
+ export function renderComputerAgentText({
187
+ command = computerAgentCommand(),
188
+ } = {}) {
189
+ return withHash(
190
+ [
191
+ '---',
192
+ 'name: astryx',
193
+ // Quoted: the description holds `: ` and `;`, which strict YAML parsers
194
+ // can misread, and an agent tool that fails to parse drops the skill.
195
+ `description: ${JSON.stringify(SKILL_DESCRIPTION)}`,
196
+ '---',
197
+ '',
198
+ MARKER_START,
199
+ ...bodyLines(command),
200
+ MARKER_END,
201
+ '',
202
+ ].join('\n'),
203
+ );
204
+ }
205
+
206
+ /** The line, right after the start marker, that records the file's hash. */
207
+ const HASH_LINE = /^<!-- astryx-managed sha256:([0-9a-f]{64}) -->\n/mu;
208
+
209
+ /**
210
+ * The sha256 of a file's text without its hash line.
211
+ * @param {string} text
212
+ * @returns {string}
213
+ */
214
+ function hashOf(text) {
215
+ return createHash('sha256').update(text).digest('hex');
216
+ }
217
+
218
+ /**
219
+ * Insert the hash line after the start marker.
220
+ * @param {string} text a rendering without a hash line
221
+ * @returns {string}
222
+ */
223
+ function withHash(text) {
224
+ const at = text.indexOf(MARKER_START);
225
+ if (at === -1) return text;
226
+ const lineEnd = text.indexOf('\n', at);
227
+ const cut = lineEnd === -1 ? text.length : lineEnd + 1;
228
+ return `${text.slice(0, cut)}<!-- astryx-managed sha256:${hashOf(text)} -->\n${text.slice(cut)}`;
229
+ }
230
+
231
+ /**
232
+ * Whether a file with the markers is unedited: it carries a hash line, and
233
+ * the hash matches the rest of its text.
234
+ * @param {string} content
235
+ * @returns {boolean}
236
+ */
237
+ function isUnedited(content) {
238
+ const match = HASH_LINE.exec(content);
239
+ if (!match) return false;
240
+ return hashOf(content.replace(HASH_LINE, '')) === match[1];
241
+ }
242
+
243
+ /**
244
+ * What is at one path of the computer-level text.
245
+ * - `absent`: nothing there.
246
+ * - `current`: an Astryx file with exactly this release's rendering.
247
+ * - `differs`: an unedited Astryx file (its recorded hash matches) with
248
+ * another rendering, such as another release's.
249
+ * - `edited`: an Astryx file (it has the markers) whose text no longer
250
+ * matches its recorded hash, or that records none: a person changed it.
251
+ * Never changed.
252
+ * - `foreign`: a file without the Astryx markers, a symlink, or something
253
+ * other than a regular file. Never changed.
254
+ * - `linked`, `dangling` (a link only): a link to the skill folder, or one
255
+ * whose folder is missing (see linkStateOf).
256
+ * @typedef {'absent' | 'current' | 'differs' | 'edited' | 'foreign' | 'linked' | 'dangling'} ComputerAgentTextState
257
+ */
258
+
259
+ /**
260
+ * @param {string} content
261
+ * @returns {boolean}
262
+ */
263
+ function hasMarkers(content) {
264
+ const start = content.indexOf(MARKER_START);
265
+ return start !== -1 && content.indexOf(MARKER_END, start) !== -1;
266
+ }
267
+
268
+ /**
269
+ * The state of one path, read without following a symlinked file.
270
+ * @param {string} file
271
+ * @param {string} expected
272
+ * @returns {ComputerAgentTextState}
273
+ */
274
+ function stateOf(file, expected) {
275
+ /** @type {fs.Stats} */
276
+ let stat;
277
+ try {
278
+ stat = fs.lstatSync(file);
279
+ } catch {
280
+ return 'absent';
281
+ }
282
+ if (!stat.isFile()) return 'foreign';
283
+ let content;
284
+ try {
285
+ content = fs.readFileSync(file, 'utf8');
286
+ } catch {
287
+ return 'foreign';
288
+ }
289
+ if (!hasMarkers(content)) return 'foreign';
290
+ if (content === expected) return 'current';
291
+ return isUnedited(content) ? 'differs' : 'edited';
292
+ }
293
+
294
+ /**
295
+ * The state of one link target.
296
+ * - `linked`: a link to the skill folder.
297
+ * - `dangling`: a link to the skill folder, which is not there, as in a
298
+ * container that mounts only the agent's own folder.
299
+ * - `absent`: nothing there.
300
+ * - `differs`: a real folder holding only an unedited Astryx skill file, as a
301
+ * copy left it; replaced by the link.
302
+ * - `foreign`: anything else. Never changed.
303
+ * @param {string} at
304
+ * @param {string} folder the skill folder
305
+ * @returns {ComputerAgentTextState}
306
+ */
307
+ function linkStateOf(at, folder) {
308
+ /** @type {fs.Stats} */
309
+ let stat;
310
+ try {
311
+ stat = fs.lstatSync(at);
312
+ } catch {
313
+ return 'absent';
314
+ }
315
+ if (stat.isSymbolicLink()) {
316
+ const leads = pointsAt(at, folder);
317
+ if (leads == null) return 'foreign';
318
+ // A link the system cannot follow to the folder is broken, whatever its
319
+ // text says.
320
+ if (leads === 'names') return 'dangling';
321
+ return fs.existsSync(path.join(folder, 'SKILL.md')) ? 'linked' : 'dangling';
322
+ }
323
+ if (!stat.isDirectory()) return 'foreign';
324
+ return copyStateOf(at, '') === 'differs' ? 'differs' : 'foreign';
325
+ }
326
+
327
+ /**
328
+ * The state of a managed copy: a real folder at `at` holding only the skill
329
+ * file, read like the skill file itself.
330
+ * @param {string} at
331
+ * @param {string} expected
332
+ * @returns {ComputerAgentTextState}
333
+ */
334
+ function copyStateOf(at, expected) {
335
+ /** @type {fs.Stats} */
336
+ let stat;
337
+ try {
338
+ stat = fs.lstatSync(at);
339
+ } catch {
340
+ return 'absent';
341
+ }
342
+ if (!stat.isDirectory()) return 'foreign';
343
+ let names;
344
+ try {
345
+ names = fs.readdirSync(at);
346
+ } catch {
347
+ return 'foreign';
348
+ }
349
+ if (names.length === 0) return 'absent';
350
+ if (names.length !== 1 || names[0] !== 'SKILL.md') return 'foreign';
351
+ const state = stateOf(path.join(at, 'SKILL.md'), expected);
352
+ return state === 'current' && expected === '' ? 'differs' : state;
353
+ }
354
+
355
+ /**
356
+ * Where the link at `at` leads `folder`'s way, as the system follows it: a
357
+ * relative target resolves from the folder the link really sits in, which
358
+ * differs from its path when a folder above it is itself a link (such as
359
+ * `~/.claude` linked to a dotfiles folder). `reaches`: the system reaches
360
+ * `folder` (which may be missing). `names`: only the target's text, read from
361
+ * the link's path, names `folder`, as a link an earlier release made under a
362
+ * linked folder does; the system cannot follow it. Otherwise null.
363
+ * @param {string} at
364
+ * @param {string} folder
365
+ * @returns {'reaches' | 'names' | null}
366
+ */
367
+ function pointsAt(at, folder) {
368
+ try {
369
+ const text = fs.readlinkSync(at);
370
+ const followed = path.resolve(realPath(path.dirname(at)), text);
371
+ const wanted = realPath(folder);
372
+ if (samePath(followed.replace(/[\\/]+$/u, ''), wanted)) return 'reaches';
373
+ const named = path.resolve(path.dirname(at), text);
374
+ return samePath(named.replace(/[\\/]+$/u, ''), folder) ? 'names' : null;
375
+ } catch {
376
+ return null;
377
+ }
378
+ }
379
+
380
+ /**
381
+ * `p` with every link in it resolved, as far as it exists: a missing tail is
382
+ * kept as written.
383
+ * @param {string} p
384
+ * @returns {string}
385
+ */
386
+ function realPath(p) {
387
+ try {
388
+ return fs.realpathSync(p);
389
+ } catch {
390
+ const parent = path.dirname(p);
391
+ return parent === p ? p : path.join(realPath(parent), path.basename(p));
392
+ }
393
+ }
394
+
395
+ /**
396
+ * Read-only: the state of the skill file against this release's rendering,
397
+ * then of each place the table wants on this computer, or that Astryx linked.
398
+ * @param {{home?: string, command?: string, links?: readonly string[]}} [options]
399
+ * `links`: the links Astryx made, from the settings file
400
+ * @returns {Array<{path: string, state: ComputerAgentTextState, link?: true}>}
401
+ */
402
+ export function inspectComputerAgentText({
403
+ home = os.homedir(),
404
+ command,
405
+ links = [],
406
+ } = {}) {
407
+ const expected = renderComputerAgentText({command});
408
+ const folder = path.join(home, SKILL_FOLDER);
409
+ const made = new Set(links);
410
+ return [
411
+ {
412
+ path: computerSkillFile(home),
413
+ state: stateOf(computerSkillFile(home), expected),
414
+ },
415
+ ...computerSkillLinks(home)
416
+ .filter(place => place.wanted || made.has(place.path))
417
+ .map(place => {
418
+ const linkState = linkStateOf(place.path, folder);
419
+ const state =
420
+ place.mode === 'copy' && linkState !== 'linked'
421
+ ? copyStateOf(place.path, expected)
422
+ : linkState;
423
+ return {path: place.path, state, link: /** @type {const} */ (true)};
424
+ }),
425
+ ];
426
+ }
427
+
428
+ /**
429
+ * Write `content` to `file` without following a symlink planted at it.
430
+ * @param {string} file
431
+ * @param {string} content
432
+ */
433
+ function writeNoFollow(file, content) {
434
+ const flags =
435
+ fs.constants.O_WRONLY |
436
+ fs.constants.O_CREAT |
437
+ fs.constants.O_TRUNC |
438
+ (fs.constants.O_NOFOLLOW ?? 0);
439
+ const fd = fs.openSync(file, flags, 0o644);
440
+ try {
441
+ fs.writeFileSync(fd, content);
442
+ } finally {
443
+ fs.closeSync(fd);
444
+ }
445
+ }
446
+
447
+ /**
448
+ * Why a path was kept: a person edited it, or Astryx did not create it.
449
+ * @param {ComputerAgentTextState} state
450
+ * @returns {'edited' | 'not-astryx'}
451
+ */
452
+ const keptReason = state => (state === 'edited' ? 'edited' : 'not-astryx');
453
+
454
+ /**
455
+ * Remove an unedited Astryx file, or a folder holding only one, and its
456
+ * emptied folder.
457
+ * @param {string} file
458
+ */
459
+ function removeFile(file) {
460
+ fs.unlinkSync(file);
461
+ try {
462
+ fs.rmdirSync(path.dirname(file));
463
+ } catch {
464
+ // The folder holds something else; leave it.
465
+ }
466
+ }
467
+
468
+ /**
469
+ * Link `at` to `folder`: a folder symlink with a relative target, which
470
+ * survives a moved or synced home folder, or a junction (absolute) on Windows.
471
+ * The target is relative to the folder the link really sits in, because the
472
+ * system resolves it from there: with `~/.claude` linked to
473
+ * `~/dotfiles/.claude`, a target worked out from `~/.claude/skills` would
474
+ * dangle. With no common base between the two, the target is absolute.
475
+ * @param {string} at
476
+ * @param {string} folder
477
+ */
478
+ function makeLink(at, folder) {
479
+ fs.mkdirSync(path.dirname(at), {recursive: true});
480
+ if (process.platform === 'win32') {
481
+ fs.symlinkSync(folder, at, 'junction');
482
+ return;
483
+ }
484
+ const realFolder = fs.realpathSync(folder);
485
+ const relative = path.relative(fs.realpathSync(path.dirname(at)), realFolder);
486
+ fs.symlinkSync(path.isAbsolute(relative) ? realFolder : relative, at, 'dir');
487
+ }
488
+
489
+ /**
490
+ * Write or refresh a managed copy of the skill file in the folder `at`.
491
+ * @param {string} at
492
+ * @param {string} expected
493
+ * @returns {import('../../api/init/init.type.mjs').InitUserFile}
494
+ */
495
+ function writeCopy(at, expected) {
496
+ const state = copyStateOf(at, expected);
497
+ if (state === 'current') return {path: at, action: 'unchanged'};
498
+ if (state === 'foreign' || state === 'edited') {
499
+ return {path: at, action: 'kept', reason: keptReason(state)};
500
+ }
501
+ try {
502
+ fs.mkdirSync(at, {recursive: true});
503
+ writeNoFollow(path.join(at, 'SKILL.md'), expected);
504
+ return {path: at, action: 'written'};
505
+ } catch {
506
+ return {path: at, action: 'kept', reason: 'not-astryx'};
507
+ }
508
+ }
509
+
510
+ /**
511
+ * Write or refresh the skill file, and link it into every folder the table
512
+ * wants on this computer. A file without the Astryx markers, an edited one, a
513
+ * symlinked one, and a link target holding anything Astryx did not create are
514
+ * kept as they are. Returns the receipt rows and the links that now point at
515
+ * the skill folder, for the settings file.
516
+ * @param {{home?: string, command?: string}} [options]
517
+ * @returns {{rows: import('../../api/init/init.type.mjs').InitUserFile[], links: string[]}}
518
+ */
519
+ export function writeComputerAgentText({home = os.homedir(), command} = {}) {
520
+ const expected = renderComputerAgentText({command});
521
+ const file = computerSkillFile(home);
522
+ const folder = path.dirname(file);
523
+ /** @type {import('../../api/init/init.type.mjs').InitUserFile[]} */
524
+ const rows = [];
525
+ const state = stateOf(file, expected);
526
+ if (state === 'foreign' || state === 'edited') {
527
+ rows.push({path: file, action: 'kept', reason: keptReason(state)});
528
+ } else if (state === 'current') {
529
+ rows.push({path: file, action: 'unchanged'});
530
+ } else {
531
+ fs.mkdirSync(folder, {recursive: true});
532
+ writeNoFollow(file, expected);
533
+ rows.push({path: file, action: 'written'});
534
+ }
535
+ /** @type {string[]} */
536
+ const links = [];
537
+ for (const place of computerSkillLinks(home)) {
538
+ if (!place.wanted) continue;
539
+ const at = place.path;
540
+ const linkState = linkStateOf(at, folder);
541
+ if (linkState === 'linked') {
542
+ links.push(at);
543
+ rows.push({path: at, action: 'unchanged'});
544
+ continue;
545
+ }
546
+ if (place.mode === 'copy') {
547
+ rows.push(writeCopy(at, expected));
548
+ continue;
549
+ }
550
+ if (linkState === 'foreign') {
551
+ rows.push({path: at, action: 'kept', reason: 'not-astryx'});
552
+ continue;
553
+ }
554
+ if (linkState === 'differs') removeFile(path.join(at, 'SKILL.md'));
555
+ // The skill folder exists now, so a link still dangling here is one
556
+ // whose target the system resolves from a linked folder above it: an
557
+ // Astryx link, remade so that it resolves.
558
+ if (linkState === 'dangling') fs.unlinkSync(at);
559
+ try {
560
+ makeLink(at, folder);
561
+ links.push(at);
562
+ rows.push({path: at, action: 'linked'});
563
+ } catch {
564
+ // A folder link that cannot be made (Windows without a junction, a
565
+ // file system without links) gets a managed copy instead.
566
+ rows.push(writeCopy(at, expected));
567
+ }
568
+ }
569
+ return {rows, links};
570
+ }
571
+
572
+ /**
573
+ * Remove the skill file and every link Astryx made that still points at its
574
+ * folder. A file without the Astryx markers, an edited one, or a symlinked
575
+ * one, and any link Astryx did not make, are kept.
576
+ * @param {{home?: string, links?: readonly string[]}} [options]
577
+ * @returns {import('../../api/init/init.type.mjs').InitUserFile[]}
578
+ */
579
+ export function removeComputerAgentText({
580
+ home = os.homedir(),
581
+ links = [],
582
+ } = {}) {
583
+ const file = computerSkillFile(home);
584
+ const folder = path.dirname(file);
585
+ /** @type {import('../../api/init/init.type.mjs').InitUserFile[]} */
586
+ const rows = [];
587
+ for (const at of links) {
588
+ const linkState = linkStateOf(at, folder);
589
+ if (linkState !== 'linked' && linkState !== 'dangling') continue;
590
+ try {
591
+ fs.unlinkSync(at);
592
+ rows.push({path: at, action: 'removed'});
593
+ } catch {
594
+ rows.push({path: at, action: 'kept', reason: 'not-astryx'});
595
+ }
596
+ }
597
+ // Managed copies are told apart by their markers and hash, like the file.
598
+ for (const place of computerSkillLinks(home)) {
599
+ if (copyStateOf(place.path, '') !== 'differs') continue;
600
+ removeFile(path.join(place.path, 'SKILL.md'));
601
+ rows.push({path: place.path, action: 'removed'});
602
+ }
603
+ const state = stateOf(file, '');
604
+ if (state === 'absent') {
605
+ rows.unshift({path: file, action: 'absent'});
606
+ } else if (state === 'foreign' || state === 'edited') {
607
+ rows.unshift({path: file, action: 'kept', reason: keptReason(state)});
608
+ } else {
609
+ removeFile(file);
610
+ rows.unshift({path: file, action: 'removed'});
611
+ }
612
+ return rows;
613
+ }
614
+
615
+ /**
616
+ * Whether the `astryx` a shell would run from this computer's search path is
617
+ * the Astryx CLI: the first `astryx` found is the one that runs.
618
+ * @param {{searchPath?: string, platform?: NodeJS.Platform}} [options]
619
+ * @returns {boolean}
620
+ */
621
+ export function astryxOnSearchPath(options = {}) {
622
+ return astryxCommandsOnSearchPath(options)[0]?.astryx ?? false;
623
+ }