@astryxdesign/cli 0.7.2 → 0.7.3-canary.0bf23bd

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 (337) hide show
  1. package/README.md +104 -71
  2. package/api/build/_adapter.d.mts +58 -0
  3. package/api/build/_adapter.mjs +115 -3
  4. package/api/build/blocks/blocks.d.mts +42 -0
  5. package/api/build/blocks/blocks.mjs +169 -0
  6. package/api/build/blocks/blocks.test.mjs +108 -0
  7. package/api/build/blocks/blocks.weights.json +1 -0
  8. package/api/build/build.mjs +25 -6
  9. package/api/build/kit/answer.d.mts +14 -0
  10. package/api/build/kit/answer.mjs +85 -0
  11. package/api/build/kit/answer.test.mjs +38 -0
  12. package/api/build/kit/kind.d.mts +25 -0
  13. package/api/build/kit/kind.mjs +170 -0
  14. package/api/build/kit/kit.d.mts +2 -4
  15. package/api/build/kit/kit.mjs +8 -398
  16. package/api/build/kit/rank.d.mts +3 -32
  17. package/api/build/kit/rank.mjs +25 -342
  18. package/api/build/kit/rank.test.mjs +2 -1
  19. package/api/build/kit/signals.d.mts +60 -0
  20. package/api/build/kit/signals.mjs +284 -0
  21. package/api/build/kit/start.d.mts +32 -0
  22. package/api/build/kit/start.mjs +163 -0
  23. package/api/build/kit/words.d.mts +72 -0
  24. package/api/build/kit/words.mjs +202 -0
  25. package/api/build/tree.d.mts +20 -0
  26. package/api/build/tree.mjs +52 -0
  27. package/api/build/tree.test.mjs +177 -0
  28. package/api/build/tree.weights.json +1 -0
  29. package/api/component/component.mjs +17 -3
  30. package/api/component/component.test.mjs +15 -0
  31. package/api/component/doc-links.test.mjs +2 -2
  32. package/api/discover/_adapter.mjs +36 -9
  33. package/api/discover/_adapter.test.mjs +42 -0
  34. package/api/docs/_adapter.d.mts +2 -2
  35. package/api/docs/_adapter.mjs +78 -23
  36. package/api/docs/docs-homes.test.mjs +127 -0
  37. package/api/docs/docs.doc.mjs +3 -3
  38. package/api/docs/docs.test.mjs +35 -19
  39. package/api/docs/integration-tree.test.mjs +20 -20
  40. package/api/docs/integrationDocs.test.mjs +1 -1
  41. package/api/docs/namespace-section.test.mjs +6 -6
  42. package/api/docs/reference-blocks.test.mjs +2 -2
  43. package/api/doctor/doctor.mjs +102 -24
  44. package/api/doctor/doctor.test.mjs +37 -12
  45. package/api/hook/hook.mjs +1 -1
  46. package/api/init/init.doc.mjs +20 -6
  47. package/api/init/init.test.mjs +18 -2
  48. package/api/init/init.type.d.mts +3 -3
  49. package/api/init/init.type.mjs +3 -3
  50. package/api/init/remove/remove.mjs +17 -4
  51. package/api/init/remove/remove.test.mjs +114 -2
  52. package/api/init/run/run.mjs +69 -6
  53. package/api/integration/_adapter.d.mts +8 -0
  54. package/api/integration/_adapter.mjs +21 -1
  55. package/api/integration/authoring-checks.mjs +83 -9
  56. package/api/integration/authoring-checks.test.mjs +220 -0
  57. package/api/integration/pack-check.mjs +123 -4
  58. package/api/integration/pack-check.test.mjs +87 -0
  59. package/api/integration/validate-integration-case-disk.test.mjs +111 -0
  60. package/api/integration/validate-integration-gaps.test.mjs +1964 -0
  61. package/api/integration/validate-integration.d.mts +11 -2
  62. package/api/integration/validate-integration.mjs +250 -32
  63. package/api/integration/validate-integration.type.d.mts +59 -1
  64. package/api/integration/validate-integration.type.mjs +46 -2
  65. package/api/integration/validateIntegration.doc.mjs +1 -1
  66. package/api/search/ORDERING.md +28 -0
  67. package/api/search/_adapter.d.mts +82 -2
  68. package/api/search/_adapter.mjs +157 -10
  69. package/api/search/_adapter.test.mjs +173 -0
  70. package/api/search/_candidate-score.d.mts +40 -0
  71. package/api/search/_candidate-score.mjs +238 -0
  72. package/api/search/_cards.json +6098 -0
  73. package/api/search/_compound.d.mts +50 -0
  74. package/api/search/_compound.mjs +170 -0
  75. package/api/search/_doc-searcher.d.mts +4 -0
  76. package/api/search/_doc-searcher.mjs +23 -0
  77. package/api/search/_doc-searcher.test.mjs +36 -0
  78. package/api/search/_doc-searcher.weights.json +1 -0
  79. package/api/search/_doc-text.d.mts +30 -0
  80. package/api/search/_doc-text.mjs +87 -0
  81. package/api/search/_entries.d.mts +52 -0
  82. package/api/search/_entries.mjs +214 -0
  83. package/api/search/_features.d.mts +35 -0
  84. package/api/search/_features.mjs +155 -0
  85. package/api/search/_gather-components.d.mts +26 -0
  86. package/api/search/_gather-components.mjs +284 -0
  87. package/api/search/_gather-docs.d.mts +20 -0
  88. package/api/search/_gather-docs.mjs +254 -0
  89. package/api/search/_gather-hooks.d.mts +12 -0
  90. package/api/search/_gather-hooks.mjs +52 -0
  91. package/api/search/_gather-templates.d.mts +25 -0
  92. package/api/search/_gather-templates.mjs +107 -0
  93. package/api/search/_lexical.d.mts +135 -0
  94. package/api/search/_lexical.mjs +351 -0
  95. package/api/search/_main-searcher.d.mts +19 -0
  96. package/api/search/_main-searcher.mjs +115 -0
  97. package/api/search/_main-searcher.test.mjs +106 -0
  98. package/api/search/_main-searcher.weights.json +1 -0
  99. package/api/search/_module-doc.d.mts +32 -0
  100. package/api/search/_module-doc.mjs +78 -0
  101. package/api/search/_order.d.mts +32 -0
  102. package/api/search/_order.mjs +162 -0
  103. package/api/search/_order.test.mjs +72 -0
  104. package/api/search/_priority.d.mts +50 -0
  105. package/api/search/_priority.mjs +139 -0
  106. package/api/search/_query-words.d.mts +25 -0
  107. package/api/search/_query-words.mjs +92 -0
  108. package/api/search/_query.d.mts +65 -0
  109. package/api/search/_query.mjs +176 -0
  110. package/api/search/_result.d.mts +97 -0
  111. package/api/search/_result.mjs +150 -0
  112. package/api/search/_score.d.mts +60 -0
  113. package/api/search/_score.mjs +283 -0
  114. package/api/search/_synonyms.d.mts +21 -0
  115. package/api/search/_synonyms.mjs +67 -0
  116. package/api/search/_template-searcher.d.mts +4 -0
  117. package/api/search/_template-searcher.mjs +23 -0
  118. package/api/search/_template-searcher.test.mjs +40 -0
  119. package/api/search/_template-searcher.weights.json +1 -0
  120. package/api/search/_titles.d.mts +33 -0
  121. package/api/search/_titles.mjs +65 -0
  122. package/api/search/_words.d.mts +64 -0
  123. package/api/search/_words.mjs +157 -0
  124. package/api/search/search.d.mts +22 -305
  125. package/api/search/search.doc.mjs +8 -6
  126. package/api/search/search.mjs +56 -2482
  127. package/api/search/search.test.mjs +14 -14
  128. package/api/search/search.type.d.mts +6 -1
  129. package/api/search/search.type.mjs +6 -1
  130. package/api/search/tree.d.mts +15 -0
  131. package/api/search/tree.mjs +40 -0
  132. package/api/search/tree.test.mjs +66 -0
  133. package/api/search/tree.weights.json +1 -0
  134. package/api/theme/build/build.packed-old-core.test.mjs +1 -0
  135. package/api/upgrade/_adapter.d.mts +57 -9
  136. package/api/upgrade/_adapter.mjs +198 -28
  137. package/api/upgrade/registry/registry.d.mts +10 -4
  138. package/api/upgrade/registry/registry.mjs +33 -16
  139. package/api/upgrade/registry/registry.test.mjs +85 -0
  140. package/api/upgrade/run/run.d.mts +4 -2
  141. package/api/upgrade/run/run.mjs +13 -2
  142. package/api/upgrade/run/run.test.mjs +417 -3
  143. package/api/upgrade/upgrade.doc.mjs +10 -5
  144. package/api/upgrade/upgrade.mjs +13 -2
  145. package/api/upgrade/upgrade.test.mjs +194 -0
  146. package/api/upgrade/upgrade.type.d.mts +5 -3
  147. package/api/upgrade/upgrade.type.mjs +8 -6
  148. package/assets/codemods/transforms/v0.7.0/__tests__/validate-data-token-ownership.test.mjs +11 -0
  149. package/assets/codemods/transforms/v0.7.0/validate-data-token-ownership.mjs +3 -3
  150. package/assets/docs/author-a-theme.doc.mjs +2 -2
  151. package/assets/docs/is-my-code-good.doc.mjs +287 -0
  152. package/assets/docs/tree/add-a-component.doc.mjs +1 -1
  153. package/assets/docs/tree/add-a-theme.doc.mjs +1 -1
  154. package/assets/docs/tree/add-a-topic.doc.mjs +2 -2
  155. package/assets/docs/tree/agent-guidance.doc.mjs +1 -1
  156. package/assets/docs/tree/api.doc.mjs +6 -0
  157. package/assets/docs/tree/{integrations.doc.mjs → authoring.doc.mjs} +15 -6
  158. package/assets/docs/tree/block-template.doc.mjs +1 -1
  159. package/assets/docs/tree/build-the-template.doc.mjs +1 -1
  160. package/assets/docs/tree/building-blocks.doc.mjs +2 -2
  161. package/assets/docs/tree/check-the-props.doc.mjs +1 -1
  162. package/assets/docs/tree/check-your-docs.doc.mjs +2 -2
  163. package/assets/docs/tree/checks.doc.mjs +2 -2
  164. package/assets/docs/tree/cli-command.doc.mjs +1 -1
  165. package/assets/docs/tree/cli.doc.mjs +1 -1
  166. package/assets/docs/tree/codemods.doc.mjs +1 -1
  167. package/assets/docs/tree/component-family.doc.mjs +1 -1
  168. package/assets/docs/tree/component-imports.doc.mjs +1 -1
  169. package/assets/docs/tree/components.doc.mjs +1 -1
  170. package/assets/docs/tree/configuration.doc.mjs +1 -1
  171. package/assets/docs/tree/debug-and-gap-reports.doc.mjs +1 -1
  172. package/assets/docs/tree/define-the-theme.doc.mjs +1 -1
  173. package/assets/docs/tree/describe-the-component.doc.mjs +1 -1
  174. package/assets/docs/tree/docs.doc.mjs +1 -1
  175. package/assets/docs/tree/document-the-template.doc.mjs +1 -1
  176. package/assets/docs/tree/document-the-theme.doc.mjs +1 -1
  177. package/assets/docs/tree/export-template-assets.doc.mjs +1 -1
  178. package/assets/docs/tree/extend-or-replace.doc.mjs +1 -1
  179. package/assets/docs/{authoring.doc.mjs → tree/field-reference.doc.mjs} +4 -3
  180. package/assets/docs/tree/fonts-and-assets.doc.mjs +1 -1
  181. package/assets/docs/tree/generate-a-palette.doc.mjs +1 -1
  182. package/assets/docs/tree/global-integrations.doc.mjs +2 -2
  183. package/assets/docs/tree/grade-template-with-agent.doc.mjs +4 -4
  184. package/assets/docs/tree/help.doc.mjs +2 -2
  185. package/assets/docs/tree/host-entry.doc.mjs +128 -0
  186. package/assets/docs/tree/links.doc.mjs +1 -1
  187. package/assets/docs/tree/package-and-test.doc.mjs +1 -1
  188. package/assets/docs/tree/page-template.doc.mjs +1 -1
  189. package/assets/docs/tree/publishing.doc.mjs +1 -1
  190. package/assets/docs/tree/quick-start.doc.mjs +1 -1
  191. package/assets/docs/tree/replace-a-core-component.doc.mjs +1 -1
  192. package/assets/docs/tree/replace-a-core-template.doc.mjs +1 -1
  193. package/assets/docs/tree/sections-and-placement.doc.mjs +1 -1
  194. package/assets/docs/tree/see-it-in-an-app.doc.mjs +1 -1
  195. package/assets/docs/tree/ship.doc.mjs +2 -2
  196. package/assets/docs/tree/short-and-findable.doc.mjs +1 -1
  197. package/assets/docs/tree/single-component.doc.mjs +1 -1
  198. package/assets/docs/tree/start-a-template.doc.mjs +1 -1
  199. package/assets/docs/tree/subcomponent.doc.mjs +1 -1
  200. package/assets/docs/tree/template-assets.doc.mjs +1 -1
  201. package/assets/docs/tree/template-doc-overview.doc.mjs +2 -2
  202. package/assets/docs/tree/template-fonts.doc.mjs +1 -1
  203. package/assets/docs/tree/template-grading-rubric.doc.mjs +17 -38
  204. package/assets/docs/tree/template-icons.doc.mjs +1 -1
  205. package/assets/docs/tree/template-images-media.doc.mjs +1 -1
  206. package/assets/docs/tree/template-styles.doc.mjs +1 -1
  207. package/assets/docs/tree/templates.doc.mjs +1 -1
  208. package/assets/docs/tree/test-in-an-app.doc.mjs +1 -1
  209. package/assets/docs/tree/test-template-in-app.doc.mjs +1 -1
  210. package/assets/docs/tree/themes.doc.mjs +1 -1
  211. package/assets/docs/tree/tokens-data.doc.mjs +2 -2
  212. package/assets/docs/tree/troubleshooting.doc.mjs +1 -1
  213. package/assets/docs/tree/upgrading.doc.mjs +1 -1
  214. package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +1 -1
  215. package/assets/docs/tree/verify-packed-template.doc.mjs +1 -1
  216. package/assets/docs/tree/versioning.doc.mjs +1 -1
  217. package/assets/docs/tree/write-good-templates.doc.mjs +1 -1
  218. package/assets/docs/tree/write-the-template-file.doc.mjs +1 -1
  219. package/assets/docs/use-integrations.doc.mjs +120 -0
  220. package/assets/docs/working-with-ai.doc.mjs +6 -2
  221. package/assets/templates/themes/butter/butterTheme.ts +4 -2
  222. package/assets/templates/themes/stone/stoneTheme.ts +4 -2
  223. package/assets/templates/themes/y2k/y2kTheme.ts +4 -2
  224. package/authoring/debug/parse.d.mts +1 -1
  225. package/authoring/doctypes/schema/schema.doc.mjs +1 -1
  226. package/authoring/integration/integration.doc.mjs +1 -1
  227. package/authoring/integration/type.ts +1 -1
  228. package/clients/cli/bin/astryx.mjs +7 -78
  229. package/clients/cli/command-result-coverage.test.mjs +6 -5
  230. package/clients/cli/commands/build-theme.color-scheme.test.mjs +1 -1
  231. package/clients/cli/commands/{component/index.mjs → component.mjs} +37 -37
  232. package/clients/cli/commands/component.test.mjs +6 -6
  233. package/clients/cli/commands/docs.doc.mjs +2 -2
  234. package/clients/cli/commands/docs.mjs +1 -1
  235. package/clients/cli/commands/docs.test.mjs +14 -14
  236. package/clients/cli/commands/doctor.mjs +30 -0
  237. package/clients/cli/commands/doctor.test.mjs +65 -0
  238. package/clients/cli/commands/{hook/index.mjs → hook.mjs} +22 -22
  239. package/clients/cli/commands/import-hint-correctness.test.mjs +1 -1
  240. package/clients/cli/commands/init.behavior.test.mjs +230 -1
  241. package/clients/cli/commands/init.doc.mjs +28 -9
  242. package/clients/cli/commands/init.flag-help.test.mjs +1 -0
  243. package/clients/cli/commands/init.mjs +2 -2
  244. package/clients/cli/commands/integration-check.test.mjs +48 -0
  245. package/clients/cli/commands/integration.doc.mjs +1 -1
  246. package/clients/cli/commands/manifest.doc.mjs +1 -1
  247. package/clients/cli/commands/manifest.mjs +67 -0
  248. package/clients/cli/commands/outside-project.test.mjs +2 -1
  249. package/clients/cli/commands/postinstall.doc.mjs +23 -0
  250. package/clients/cli/commands/postinstall.mjs +31 -0
  251. package/clients/cli/commands/setup-nudge.test.mjs +61 -0
  252. package/clients/cli/commands/{build-theme.mjs → theme.mjs} +1 -1
  253. package/clients/cli/commands/upgrade.doc.mjs +4 -2
  254. package/clients/cli/commands/upgrade.mjs +9 -2
  255. package/clients/cli/commands/upgrade.subfolder-path.test.mjs +97 -0
  256. package/clients/cli/index.mjs +29 -86
  257. package/clients/cli/lib/component-format.mjs +1 -1
  258. package/clients/cli/lib/define-command.mjs +12 -7
  259. package/clients/cli/lib/exit-codes.test.mjs +5 -0
  260. package/clients/cli/lib/manifest.test.mjs +12 -0
  261. package/clients/cli/lib/welcome-box.mjs +50 -0
  262. package/clients/cli/run.mjs +108 -0
  263. package/foundation/agent-docs/agent-doc-state.d.mts +20 -7
  264. package/foundation/agent-docs/agent-doc-state.mjs +35 -8
  265. package/foundation/agent-docs/agent-docs.d.mts +100 -12
  266. package/foundation/agent-docs/agent-docs.mjs +1012 -100
  267. package/foundation/agent-docs/agent-docs.path-safety.test.mjs +1239 -17
  268. package/foundation/agent-docs/agent-docs.test.mjs +683 -39
  269. package/foundation/config/project.mjs +20 -22
  270. package/foundation/config/project.test.mjs +56 -0
  271. package/foundation/discovery/authoring-self-docs.d.mts +7 -0
  272. package/foundation/discovery/authoring-self-docs.mjs +16 -4
  273. package/foundation/discovery/authoring-self-docs.test.mjs +15 -3
  274. package/foundation/discovery/authoring-surface.d.mts +1 -1
  275. package/foundation/discovery/authoring-surface.mjs +2 -2
  276. package/foundation/discovery/authoring-surface.test.mjs +1 -1
  277. package/foundation/discovery/cli-self-docs.d.mts +2 -1
  278. package/foundation/discovery/cli-self-docs.mjs +4 -3
  279. package/foundation/discovery/cli-self-docs.test.mjs +2 -2
  280. package/foundation/discovery/component-discovery.d.mts +37 -0
  281. package/foundation/discovery/component-discovery.mjs +80 -5
  282. package/foundation/discovery/component-loader.d.mts +1 -1
  283. package/foundation/discovery/docs-discovery.mjs +14 -1
  284. package/foundation/discovery/module-exports.d.mts +41 -0
  285. package/foundation/discovery/module-exports.mjs +253 -0
  286. package/foundation/discovery/runner-allowlist.d.mts +2 -4
  287. package/foundation/discovery/runner-allowlist.mjs +2 -4
  288. package/foundation/discovery/runner-commands.d.mts +77 -0
  289. package/foundation/discovery/runner-commands.mjs +294 -0
  290. package/foundation/discovery/runner-commands.test.mjs +128 -0
  291. package/foundation/discovery/stray-pointers.test.mjs +1 -1
  292. package/foundation/discovery/template-adapter.mjs +18 -1
  293. package/foundation/discovery/template-needs.mjs +1 -44
  294. package/foundation/discovery/theme-discovery.mjs +147 -3
  295. package/foundation/doc-compiler/doc-loads.test.mjs +33 -9
  296. package/foundation/doc-compiler/read.mjs +59 -1
  297. package/foundation/doc-compiler/read.test.mjs +11 -2
  298. package/foundation/doc-compiler/split-topic-keywords.test.mjs +3 -2
  299. package/foundation/doc-compiler/tree.d.mts +32 -6
  300. package/foundation/doc-compiler/tree.mjs +77 -43
  301. package/foundation/doc-compiler/tree.test.mjs +7 -7
  302. package/foundation/fs/paths.d.mts +70 -8
  303. package/foundation/fs/paths.mjs +556 -38
  304. package/foundation/fs/paths.test.mjs +430 -54
  305. package/foundation/fs/read-record.d.mts +18 -0
  306. package/foundation/fs/read-record.mjs +131 -0
  307. package/foundation/fs/read-record.test.mjs +77 -0
  308. package/foundation/integrations/autolink.d.mts +2 -1
  309. package/foundation/integrations/autolink.mjs +4 -13
  310. package/foundation/integrations/computer-integrations.d.mts +6 -3
  311. package/foundation/integrations/computer-integrations.mjs +38 -9
  312. package/foundation/integrations/contribution-inventory.d.mts +15 -20
  313. package/foundation/integrations/contribution-inventory.mjs +41 -5
  314. package/foundation/integrations/integrations.d.mts +27 -1
  315. package/foundation/integrations/integrations.mjs +61 -2
  316. package/foundation/integrations/validate-contributions.d.mts +115 -0
  317. package/foundation/integrations/validate-contributions.mjs +1168 -13
  318. package/foundation/release/handoff.d.mts +23 -4
  319. package/foundation/release/handoff.mjs +175 -8
  320. package/foundation/release/run-context.mjs +14 -0
  321. package/foundation/response/response-types.doc.mjs +1 -1
  322. package/foundation/route/route.d.mts +285 -0
  323. package/foundation/route/route.mjs +324 -0
  324. package/foundation/route/route.test.mjs +153 -0
  325. package/foundation/runtime/hosted-runtime.test.mjs +403 -0
  326. package/foundation/runtime/locked-versions.d.mts +32 -0
  327. package/foundation/runtime/locked-versions.mjs +65 -0
  328. package/foundation/runtime/package-root.d.mts +91 -0
  329. package/foundation/runtime/package-root.mjs +174 -0
  330. package/foundation/runtime/package-set.d.mts +62 -0
  331. package/foundation/runtime/package-set.mjs +130 -0
  332. package/foundation/runtime/package-set.test.mjs +432 -0
  333. package/host/door-conformance.test.mjs +274 -0
  334. package/host/index.d.ts +116 -0
  335. package/host/index.mjs +295 -0
  336. package/host/index.test.mjs +302 -0
  337. package/package.json +14 -9
@@ -12,7 +12,7 @@ import {
12
12
  findComponentSource,
13
13
  levenshteinDistance,
14
14
  findClosestComponents,
15
- } from './component/index.mjs';
15
+ } from './component.mjs';
16
16
  import {runCli} from '../../../test-utils/run-cli.mjs';
17
17
 
18
18
  const REPO_ROOT = path.resolve(import.meta.dirname, '../../../../..');
@@ -528,7 +528,7 @@ describe('findExternalComponentDoc', () => {
528
528
  describe('searchComponents', () => {
529
529
  it('finds Collapsible when searching "accordion"', async () => {
530
530
  const {searchComponents, discoverComponents} =
531
- await import('./component/index.mjs');
531
+ await import('./component.mjs');
532
532
  const components = discoverComponents('packages/core');
533
533
  const results = await searchComponents(
534
534
  'accordion',
@@ -542,7 +542,7 @@ describe('searchComponents', () => {
542
542
 
543
543
  it('finds Dialog when searching "modal"', async () => {
544
544
  const {searchComponents, discoverComponents} =
545
- await import('./component/index.mjs');
545
+ await import('./component.mjs');
546
546
  const components = discoverComponents('packages/core');
547
547
  const results = await searchComponents(
548
548
  'modal',
@@ -556,7 +556,7 @@ describe('searchComponents', () => {
556
556
 
557
557
  it('returns multiple matches for ambiguous terms like "select"', async () => {
558
558
  const {searchComponents, discoverComponents} =
559
- await import('./component/index.mjs');
559
+ await import('./component.mjs');
560
560
  const components = discoverComponents('packages/core');
561
561
  const results = await searchComponents(
562
562
  'select',
@@ -571,7 +571,7 @@ describe('searchComponents', () => {
571
571
 
572
572
  it('finds exact name matches with score 100', async () => {
573
573
  const {searchComponents, discoverComponents} =
574
- await import('./component/index.mjs');
574
+ await import('./component.mjs');
575
575
  const components = discoverComponents('packages/core');
576
576
  const results = await searchComponents(
577
577
  'Button',
@@ -584,7 +584,7 @@ describe('searchComponents', () => {
584
584
 
585
585
  it('returns empty array for complete gibberish', async () => {
586
586
  const {searchComponents, discoverComponents} =
587
- await import('./component/index.mjs');
587
+ await import('./component.mjs');
588
588
  const components = discoverComponents('packages/core');
589
589
  const results = await searchComponents(
590
590
  'zzzzzzzzzzz',
@@ -58,10 +58,10 @@ export const doc = {
58
58
  {label: 'Every CLI doc, one line each', cli: 'astryx docs cli --depth all'},
59
59
  {
60
60
  label: 'The integration guides as one read',
61
- cli: 'astryx docs cli/integrations --depth all --detail full',
61
+ cli: 'astryx docs authoring --depth all --detail full',
62
62
  },
63
63
  {label: 'One API function', cli: 'astryx docs cli/api/functions/search'},
64
- {label: 'A whole guide', cli: 'astryx docs cli/integrations/quick-start --full'},
64
+ {label: 'A whole guide', cli: 'astryx docs authoring/quick-start --full'},
65
65
  ],
66
66
  outsideProject: 'same',
67
67
  exitCodes: [
@@ -535,7 +535,7 @@ function emitNode(node, detail, run) {
535
535
  text(
536
536
  [
537
537
  // A child's route is its parent's route and its name, except a flat
538
- // topic in the Unorganized level, which keeps its own name.
538
+ // topic in a topic level, which keeps its own name.
539
539
  node.slots.every(slot =>
540
540
  slot.children.every(child => child.route.startsWith(`${node.route}/`)),
541
541
  )
@@ -227,7 +227,7 @@ describe('the docs tree, one level at a time', () => {
227
227
  it('wraps namespace summaries without discarding searchable words', async () => {
228
228
  const {status, stdout} = await runCli([
229
229
  'docs',
230
- 'cli/integrations/building-blocks/templates',
230
+ 'authoring/building-blocks/templates',
231
231
  ]);
232
232
  expect(status).toBe(0);
233
233
  expect(stdout).toMatch(/^start-a-template +Help others build apps faster/m);
@@ -274,8 +274,8 @@ describe('the docs tree, one level at a time', () => {
274
274
  }, SLOW);
275
275
 
276
276
  it('reads the integration guides by their routes, and not by the old name', async () => {
277
- // cli/integrations is a namespace: one level, its guides by slot.
278
- const level = await runCli(['docs', 'cli/integrations']);
277
+ // authoring is a namespace: one level, its guides by slot.
278
+ const level = await runCli(['docs', 'authoring']);
279
279
  expect(level.status).toBe(0);
280
280
  expect(level.stdout).toMatch(
281
281
  /^quick-start +Create a new integration package/m,
@@ -283,42 +283,42 @@ describe('the docs tree, one level at a time', () => {
283
283
  expect(level.stdout).toMatch(/^building-blocks +/m);
284
284
  const guide = await runCli([
285
285
  'docs',
286
- 'cli/integrations/building-blocks/codemods',
286
+ 'authoring/building-blocks/codemods',
287
287
  '--index',
288
288
  ]);
289
289
  expect(guide.status).toBe(0);
290
290
  expect(guide.stdout).toMatch(
291
- /Read one section: .*docs cli\/integrations\/building-blocks\/codemods <section>/,
291
+ /Read one section: .*docs authoring\/building-blocks\/codemods <section>/,
292
292
  );
293
293
  // A bare read is one level too: the sections, and how to read it all.
294
294
  const bare = await runCli([
295
295
  'docs',
296
- 'cli/integrations/building-blocks/codemods',
296
+ 'authoring/building-blocks/codemods',
297
297
  ]);
298
298
  expect(bare.status).toBe(0);
299
299
  expect(bare.stdout).toBe(guide.stdout);
300
300
  expect(bare.stdout).toMatch(
301
- /Read everything: +.*docs cli\/integrations\/building-blocks\/codemods --full$/m,
301
+ /Read everything: +.*docs authoring\/building-blocks\/codemods --full$/m,
302
302
  );
303
303
  const one = await runCli([
304
304
  'docs',
305
- 'cli/integrations/building-blocks/codemods',
305
+ 'authoring/building-blocks/codemods',
306
306
  'which-codemods-run',
307
307
  ]);
308
308
  expect(one.status).toBe(0);
309
309
  expect(one.stdout).toMatch(/^## Choose when a codemod runs$/m);
310
310
  expect(one.stdout).toMatch(
311
- /^Up: .*docs cli\/integrations\/building-blocks\/codemods --index$/m,
311
+ /^Up: .*docs authoring\/building-blocks\/codemods --index$/m,
312
312
  );
313
313
  expect(one.stdout).toMatch(
314
- /^Previous: .*docs cli\/integrations\/building-blocks\/codemods write-the-transform$/m,
314
+ /^Previous: .*docs authoring\/building-blocks\/codemods write-the-transform$/m,
315
315
  );
316
316
  expect(one.stdout).toMatch(
317
- /^Next: .*docs cli\/integrations\/building-blocks\/codemods run-codemods-in-an-app$/m,
317
+ /^Next: .*docs authoring\/building-blocks\/codemods run-codemods-in-an-app$/m,
318
318
  );
319
319
  const full = await runCli([
320
320
  'docs',
321
- 'cli/integrations/building-blocks/codemods',
321
+ 'authoring/building-blocks/codemods',
322
322
  '--full',
323
323
  ]);
324
324
  expect(full.status).toBe(0);
@@ -343,11 +343,11 @@ describe('the docs tree, one level at a time', () => {
343
343
  type: 'docs.detail.section',
344
344
  data: {id: 'side-panels', title: 'Side panels'},
345
345
  });
346
- const missing = await runCli(['docs', 'cli', 'zzzz-nope', '--json']);
346
+ const missing = await runCli(['docs', 'authoring', 'zzzz-nope', '--json']);
347
347
  expect(missing.status).toBe(1);
348
348
  const error = JSON.parse(missing.stdout);
349
349
  expect(error).toMatchObject({code: 'ERR_UNKNOWN_SECTION'});
350
- expect(error.suggestions.map(s => s.name)).toContain('cli/integrations/quick-start');
350
+ expect(error.suggestions.map(s => s.name)).toContain('authoring/quick-start');
351
351
  }, SLOW);
352
352
  });
353
353
 
@@ -179,6 +179,34 @@ function docTypesBlocks(report, failed) {
179
179
  return blocks;
180
180
  }
181
181
 
182
+ /**
183
+ * The component doc names that could not be judged against their sources.
184
+ * @param {import('../../../api/integration/validate-integration.type.mjs').DocNamesReport | undefined} report
185
+ */
186
+ function docNamesBlocks(report) {
187
+ if (!report || report.unchecked.length === 0) return [];
188
+ return [
189
+ text(
190
+ `[info] Component names not checked: ${report.unchecked.length} doc(s) whose source exports this check cannot read:`,
191
+ ),
192
+ records(report.unchecked, {fields: ['file', 'name', 'reason']}),
193
+ ];
194
+ }
195
+
196
+ /**
197
+ * The block component names this check could not judge.
198
+ * @param {import('../../../api/integration/validate-integration.type.mjs').BlockComponentsReport | undefined} report
199
+ */
200
+ function blockComponentsBlocks(report) {
201
+ if (!report || report.unchecked.length === 0) return [];
202
+ return [
203
+ text(
204
+ `[info] Block component names not checked: ${report.unchecked.length} name(s) this check could not judge:`,
205
+ ),
206
+ records(report.unchecked, {fields: ['file', 'name', 'reason']}),
207
+ ];
208
+ }
209
+
182
210
  /**
183
211
  * @param {import('../../../api/integration/validate-integration.type.mjs').ValidateIntegrationResponse['data']} data
184
212
  */
@@ -204,6 +232,8 @@ function printIntegrationValidation(data) {
204
232
  `${warnings} warning${warnings === 1 ? '' : 's'}`,
205
233
  ),
206
234
  ...docTypesBlocks(data.docTypes, errors > 0),
235
+ ...docNamesBlocks(data.docNames),
236
+ ...blockComponentsBlocks(data.blockComponents),
207
237
  );
208
238
  }
209
239
 
@@ -191,6 +191,31 @@ describe('doctor — individual checks', () => {
191
191
  expect(res.fix).toContain('`npx astryx init --features agents`');
192
192
  });
193
193
 
194
+ it('agent-docs: at the top of a home settings folder, the fix moves the project or edits by hand, never init', () => {
195
+ const saved = {HOME: process.env.HOME, USERPROFILE: process.env.USERPROFILE};
196
+ process.env.HOME = tmpDir;
197
+ process.env.USERPROFILE = tmpDir;
198
+ try {
199
+ const project = path.join(tmpDir, '.dotfiles');
200
+ fs.mkdirSync(project);
201
+ fs.writeFileSync(path.join(project, 'package.json'), '{"name":"dots"}');
202
+ for (const content of [null, '# Agents\nno markers here\n']) {
203
+ if (content != null) fs.writeFileSync(path.join(project, 'AGENTS.md'), content);
204
+ const res = checkAgentDocs({cwd: project});
205
+ expect(res.fix).toContain('Agent docs are not written into the home folder');
206
+ expect(res.fix).toContain(
207
+ 'Move the project into a folder of its own, or edit the Astryx block in its agent file by hand.',
208
+ );
209
+ expect(res.fix).not.toContain('init --features agents');
210
+ }
211
+ } finally {
212
+ for (const [key, value] of Object.entries(saved)) {
213
+ if (value === undefined) delete process.env[key];
214
+ else process.env[key] = value;
215
+ }
216
+ }
217
+ });
218
+
194
219
  it('agent-docs: WARN when docs exist without XDS markers', () => {
195
220
  fs.writeFileSync(path.join(tmpDir, 'AGENTS.md'), '# AGENTS\nno markers here');
196
221
  const res = checkAgentDocs({cwd: tmpDir});
@@ -221,6 +246,25 @@ describe('doctor — individual checks', () => {
221
246
  );
222
247
  });
223
248
 
249
+ it('agent-docs: PASS from a nested package, reading the root that declares Astryx', () => {
250
+ fs.writeFileSync(
251
+ path.join(tmpDir, 'package.json'),
252
+ JSON.stringify({dependencies: {'@astryxdesign/core': '0.7.0'}}),
253
+ );
254
+ fs.writeFileSync(
255
+ path.join(tmpDir, 'AGENTS.md'),
256
+ '<!-- ASTRYX:START -->\nstuff\n<!-- ASTRYX:END -->\n',
257
+ );
258
+ const functions = path.join(tmpDir, 'functions');
259
+ fs.mkdirSync(path.join(functions, 'src'), {recursive: true});
260
+ fs.writeFileSync(path.join(functions, 'package.json'), '{"name":"functions"}');
261
+
262
+ expect(checkAgentDocs({cwd: path.join(functions, 'src')})).toMatchObject({
263
+ status: 'pass',
264
+ message: `Astryx agent docs section present in ${path.join('..', '..', 'AGENTS.md')}.`,
265
+ });
266
+ });
267
+
224
268
  it('agent-docs: reads every file init can write, Hermes included', () => {
225
269
  fs.writeFileSync(
226
270
  path.join(tmpDir, 'HERMES.md'),
@@ -232,6 +276,27 @@ describe('doctor — individual checks', () => {
232
276
  });
233
277
  });
234
278
 
279
+ it('agent-docs: FAIL when GEMINI.md imports an AGENTS.md that holds no block', () => {
280
+ const geminiImport = '<!-- ASTRYX:START -->\n@./AGENTS.md\n<!-- ASTRYX:END -->\n';
281
+ fs.writeFileSync(path.join(tmpDir, 'GEMINI.md'), geminiImport);
282
+ expect(checkAgentDocs({cwd: tmpDir})).toMatchObject({
283
+ status: 'fail',
284
+ message: expect.stringMatching(/^GEMINI\.md imports AGENTS\.md, which holds no Astryx block/),
285
+ fix: expect.stringMatching(/init --features agents/),
286
+ });
287
+
288
+ // A copy elsewhere does not mend it: Codex and Gemini CLI still get none.
289
+ fs.writeFileSync(path.join(tmpDir, 'CLAUDE.md'), '<!-- ASTRYX:START -->\nstuff\n<!-- ASTRYX:END -->\n');
290
+ fs.writeFileSync(path.join(tmpDir, 'AGENTS.md'), '# Agents\n');
291
+ expect(checkAgentDocs({cwd: tmpDir}).status).toBe('fail');
292
+
293
+ fs.writeFileSync(path.join(tmpDir, 'AGENTS.md'), '<!-- ASTRYX:START -->\nstuff\n<!-- ASTRYX:END -->\n');
294
+ expect(checkAgentDocs({cwd: tmpDir})).toMatchObject({
295
+ status: 'pass',
296
+ message: 'Astryx agent docs section present in AGENTS.md, CLAUDE.md.',
297
+ });
298
+ });
299
+
235
300
  it('agent-docs: INFO names what it looked for and where', () => {
236
301
  const res = checkAgentDocs({cwd: tmpDir});
237
302
  expect(res.status).toBe('info');
@@ -11,17 +11,17 @@ import {
11
11
  formatHookCompact,
12
12
  formatHookBrief,
13
13
  formatHookParams,
14
- } from '../../lib/hook-format.mjs';
15
- import {getCliInvocation} from '../../../../foundation/env/package-manager.mjs';
16
- import {jsonOut} from '../../../../foundation/response/json.mjs';
17
- import {emit, section, text, list, records, code, record} from '../../formatters/index.mjs';
18
- import {cliError} from '../../lib/cli-error.mjs';
19
- import {defineCommand} from '../../lib/define-command.mjs';
20
- import {resultSet} from '../../../../foundation/debug/index.mjs';
21
- import {ERROR_CODES} from '../../../../foundation/response/error-codes.mjs';
22
- import {hook as hookApi} from '../../../../api/hook/hook.mjs';
23
- import {doc as hookCommand} from '../hook.doc.mjs';
24
- import {doc as hookFn} from '../../../../api/hook/hook.doc.mjs';
14
+ } from '../lib/hook-format.mjs';
15
+ import {getCliInvocation} from '../../../foundation/env/package-manager.mjs';
16
+ import {jsonOut} from '../../../foundation/response/json.mjs';
17
+ import {emit, section, text, list, records, code, record} from '../formatters/index.mjs';
18
+ import {cliError} from '../lib/cli-error.mjs';
19
+ import {defineCommand} from '../lib/define-command.mjs';
20
+ import {resultSet} from '../../../foundation/debug/index.mjs';
21
+ import {ERROR_CODES} from '../../../foundation/response/error-codes.mjs';
22
+ import {hook as hookApi} from '../../../api/hook/hook.mjs';
23
+ import {doc as hookCommand} from './hook.doc.mjs';
24
+ import {doc as hookFn} from '../../../api/hook/hook.doc.mjs';
25
25
 
26
26
  /**
27
27
  * The api layer's hook() widens its return to `{type: string, data: unknown}`,
@@ -29,9 +29,9 @@ import {doc as hookFn} from '../../../../api/hook/hook.doc.mjs';
29
29
  * the colocated api/hook/hook.type.mjs to get narrowing + typed data.
30
30
  *
31
31
  * @typedef {(
32
- * | import('../../../../api/hook/hook.type.mjs').HookListResponse
33
- * | import('../../../../api/hook/hook.type.mjs').HookDetailResponse
34
- * | import('../../../../api/hook/hook.type.mjs').HookDetailParamsResponse
32
+ * | import('../../../api/hook/hook.type.mjs').HookListResponse
33
+ * | import('../../../api/hook/hook.type.mjs').HookDetailResponse
34
+ * | import('../../../api/hook/hook.type.mjs').HookDetailParamsResponse
35
35
  * )} HookResult
36
36
  */
37
37
 
@@ -40,7 +40,7 @@ import {doc as hookFn} from '../../../../api/hook/hook.doc.mjs';
40
40
  * count is every hook across the groups — names or docs, depending on detail.
41
41
  *
42
42
  * @param {HookResult} result
43
- * @returns {import('../../../../foundation/debug/command-result.mjs').CommandResult}
43
+ * @returns {import('../../../foundation/debug/command-result.mjs').CommandResult}
44
44
  */
45
45
  function summarize(result) {
46
46
  if (result.type === 'hook.list') {
@@ -90,7 +90,7 @@ export function registerHook(program) {
90
90
  lang, zh,
91
91
  }));
92
92
  } catch (e) {
93
- const err = /** @type {import('../../../../api/error.mjs').AstryxError} */ (e);
93
+ const err = /** @type {import('../../../api/error.mjs').AstryxError} */ (e);
94
94
  return cliError(err.message, {suggestions: err.suggestions, code: err.code});
95
95
  }
96
96
 
@@ -112,7 +112,7 @@ export function registerHook(program) {
112
112
  // The whole view is one markdown document: a `## <category>` heading
113
113
  // over each category's concatenated hook docs.
114
114
  const groups = result.data.components;
115
- /** @type {import('../../formatters/index.mjs').Block[]} */
115
+ /** @type {import('../formatters/index.mjs').Block[]} */
116
116
  const out = [];
117
117
  for (const [cat, items] of Object.entries(groups)) {
118
118
  const body = items
@@ -130,7 +130,7 @@ export function registerHook(program) {
130
130
  // --detail compact — one record (name + description) per hook,
131
131
  // grouped by category.
132
132
  const groups = result.data.components;
133
- /** @type {import('../../formatters/index.mjs').Block[]} */
133
+ /** @type {import('../formatters/index.mjs').Block[]} */
134
134
  const out = [];
135
135
  for (const [cat, items] of Object.entries(groups)) {
136
136
  out.push(section(cat), records(items, {fields: ['name', 'description']}));
@@ -147,7 +147,7 @@ export function registerHook(program) {
147
147
  const [cat, hookNames] = Object.entries(groups)[0];
148
148
  emit(section(`${cat}:`), list(hookNames));
149
149
  } else {
150
- /** @type {import('../../formatters/index.mjs').Block[]} */
150
+ /** @type {import('../formatters/index.mjs').Block[]} */
151
151
  const out = [];
152
152
  for (const [category, hookNames] of Object.entries(groups)) {
153
153
  out.push(section(category), list(hookNames));
@@ -197,12 +197,12 @@ export function registerHook(program) {
197
197
  }
198
198
 
199
199
  // Re-export lib functions for external consumers
200
- export {discoverHooks, findHookDoc, getAllHookNames} from '../../../../foundation/discovery/hook-discovery.mjs';
201
- export {loadDocs} from '../../../../foundation/discovery/component-loader.mjs';
200
+ export {discoverHooks, findHookDoc, getAllHookNames} from '../../../foundation/discovery/hook-discovery.mjs';
201
+ export {loadDocs} from '../../../foundation/discovery/component-loader.mjs';
202
202
  export {
203
203
  formatHookFull,
204
204
  formatHookCompact,
205
205
  formatHookBrief,
206
206
  formatHookBriefAll,
207
207
  formatHookParams,
208
- } from '../../lib/hook-format.mjs';
208
+ } from '../lib/hook-format.mjs';
@@ -21,7 +21,7 @@ import {
21
21
  resolveImportPath,
22
22
  discoverComponents,
23
23
  findComponentSource,
24
- } from './component/index.mjs';
24
+ } from './component.mjs';
25
25
  import {findCoreDir} from '../../../foundation/fs/paths.mjs';
26
26
  import {runCli} from '../../../test-utils/run-cli.mjs';
27
27
 
@@ -70,10 +70,12 @@ describe('astryx init — default (no flags)', () => {
70
70
  });
71
71
 
72
72
  describe('astryx init --features', () => {
73
- it('--features agents writes AGENTS.md only', async () => {
73
+ it('--features agents writes AGENTS.md and the GEMINI.md import of it', async () => {
74
74
  const {status} = await runCli(['init', '--features', 'agents'], {cwd: tmpDir});
75
75
  expect(status).toBe(0);
76
76
  expect(exists('AGENTS.md')).toBe(true);
77
+ expect(read('GEMINI.md')).toBe(`${MARKER_START}\n@./AGENTS.md\n<!-- ASTRYX:END -->\n`);
78
+ expect(exists('theme.template.ts')).toBe(false);
77
79
  });
78
80
 
79
81
  it('--features theme writes the annotated theme template', async () => {
@@ -126,7 +128,234 @@ describe('astryx init --agent <preset>', () => {
126
128
  });
127
129
  });
128
130
 
131
+ describe('astryx init from a subfolder', () => {
132
+ /** @param {string} dir */
133
+ const astryxProject = dir =>
134
+ fs.writeFileSync(
135
+ path.join(dir, 'package.json'),
136
+ JSON.stringify({name: 'app', dependencies: {'@astryxdesign/core': '0.7.0'}}),
137
+ );
138
+
139
+ it('writes the agent docs at the project root and reports them from where it ran', async () => {
140
+ astryxProject(tmpDir);
141
+ const sub = path.join(tmpDir, 'src', 'app');
142
+ fs.mkdirSync(sub, {recursive: true});
143
+
144
+ const json = await runCli(['init', '--json'], {cwd: sub});
145
+ expect(json.status).toBe(0);
146
+ expect(JSON.parse(json.stdout).data.docsWritten).toEqual([
147
+ '../../AGENTS.md',
148
+ '../../GEMINI.md',
149
+ ]);
150
+ expect(read('AGENTS.md')).toContain(MARKER_START);
151
+ expect(fs.readdirSync(sub)).toEqual([]);
152
+ });
153
+
154
+ it('keeps --agent-docs-path relative to where it ran', async () => {
155
+ astryxProject(tmpDir);
156
+ const sub = path.join(tmpDir, 'docs');
157
+ fs.mkdirSync(sub);
158
+
159
+ const json = await runCli(['init', '--agent-docs-path', 'AI.md', '--json'], {cwd: sub});
160
+ expect(JSON.parse(json.stdout).data.docsWritten).toEqual(['AI.md']);
161
+ expect(fs.readFileSync(path.join(sub, 'AI.md'), 'utf8')).toContain(MARKER_START);
162
+ expect(exists('AGENTS.md')).toBe(false);
163
+ });
164
+ });
165
+
166
+ describe('astryx init in the home folder', () => {
167
+ const saved = {HOME: process.env.HOME, USERPROFILE: process.env.USERPROFILE};
168
+ const personal = '# My notes\n\nAlways use tabs.\n';
169
+
170
+ beforeEach(() => {
171
+ process.env.HOME = tmpDir;
172
+ process.env.USERPROFILE = tmpDir;
173
+ fs.mkdirSync(path.join(tmpDir, '.claude'), {recursive: true});
174
+ fs.writeFileSync(path.join(tmpDir, '.claude', 'CLAUDE.md'), personal);
175
+ });
176
+ afterEach(() => {
177
+ for (const [key, value] of Object.entries(saved)) {
178
+ if (value === undefined) delete process.env[key];
179
+ else process.env[key] = value;
180
+ }
181
+ });
182
+
183
+ it('writes no agent docs, says why, and keeps the soft-failure exit code', async () => {
184
+ const json = await runCli(['init', '--json'], {cwd: tmpDir});
185
+ expect(json.status).toBe(0);
186
+ const env = JSON.parse(json.stdout);
187
+ expect(env.data.docsWritten).toEqual([]);
188
+ expect(env.data.docsError).toEqual({
189
+ kind: 'install-failed',
190
+ message: expect.stringMatching(/not written into the home folder/),
191
+ });
192
+
193
+ const text = await runCli(['init'], {cwd: tmpDir});
194
+ expect(text.status).toBe(0);
195
+ expect(text.stderr).toMatch(/not written into the home folder/);
196
+ expect(text.stderr).not.toMatch(/Error:/);
197
+
198
+ expect(read('.claude/CLAUDE.md')).toBe(personal);
199
+ expect(fs.readdirSync(tmpDir)).toEqual(['.claude']);
200
+ });
201
+
202
+ it('does not let a package.json in the home folder pull the docs there', async () => {
203
+ fs.writeFileSync(
204
+ path.join(tmpDir, 'package.json'),
205
+ JSON.stringify({dependencies: {'@astryxdesign/core': '0.7.0'}}),
206
+ );
207
+ const scratch = path.join(tmpDir, 'scratch');
208
+ fs.mkdirSync(scratch);
209
+
210
+ const json = await runCli(['init', '--json'], {cwd: scratch});
211
+ expect(json.status).toBe(0);
212
+ expect(JSON.parse(json.stdout).data.docsWritten).toEqual(['AGENTS.md', 'GEMINI.md']);
213
+ expect(fs.existsSync(path.join(scratch, 'AGENTS.md'))).toBe(true);
214
+ expect(exists('AGENTS.md')).toBe(false);
215
+ expect(read('.claude/CLAUDE.md')).toBe(personal);
216
+ });
217
+
218
+ it('--remove-agents in a folder in home never reaches the home folder', async () => {
219
+ fs.writeFileSync(
220
+ path.join(tmpDir, '.claude', 'CLAUDE.md'),
221
+ `${personal}\n<!-- ASTRYX:START -->\nAstryx v0.7.0\n<!-- ASTRYX:END -->\n`,
222
+ );
223
+ fs.writeFileSync(
224
+ path.join(tmpDir, 'package.json'),
225
+ JSON.stringify({dependencies: {'@astryxdesign/core': '0.7.0'}}),
226
+ );
227
+ const scratch = path.join(tmpDir, 'scratch');
228
+ fs.mkdirSync(scratch);
229
+
230
+ const json = await runCli(['init', '--remove-agents', '--json'], {cwd: scratch});
231
+ expect(JSON.parse(json.stdout).data.removed).toBe(false);
232
+ expect(read('.claude/CLAUDE.md')).toContain('<!-- ASTRYX:START -->');
233
+
234
+ // Run in the home folder itself, it still removes the block there.
235
+ const home = await runCli(['init', '--remove-agents', '--json'], {cwd: tmpDir});
236
+ expect(JSON.parse(home.stdout).data.removed).toBe(true);
237
+ expect(read('.claude/CLAUDE.md')).toBe(personal);
238
+ });
239
+
240
+ it.each(['.gemini', '.claude'])('refuses the settings folder ~/%s', async folder => {
241
+ const dir = path.join(tmpDir, folder);
242
+ fs.mkdirSync(dir, {recursive: true});
243
+ const before = fs.readdirSync(dir);
244
+
245
+ const json = await runCli(['init', '--json'], {cwd: dir});
246
+ expect(json.status).toBe(0);
247
+ const env = JSON.parse(json.stdout);
248
+ expect(env.data.docsWritten).toEqual([]);
249
+ expect(env.data.docsError.message).toMatch(/settings folder in it outside a project/);
250
+ expect(fs.readdirSync(dir)).toEqual(before);
251
+ expect(read('.claude/CLAUDE.md')).toBe(personal);
252
+ });
253
+ });
254
+
255
+ describe('astryx init in an agent worktree inside a home settings folder', () => {
256
+ const saved = {HOME: process.env.HOME, USERPROFILE: process.env.USERPROFILE};
257
+
258
+ beforeEach(() => {
259
+ process.env.HOME = tmpDir;
260
+ process.env.USERPROFILE = tmpDir;
261
+ });
262
+ afterEach(() => {
263
+ for (const [key, value] of Object.entries(saved)) {
264
+ if (value === undefined) delete process.env[key];
265
+ else process.env[key] = value;
266
+ }
267
+ });
268
+
269
+ it.each([
270
+ ['Codex', path.join('.codex', 'worktrees', 'a1b2c3', 'web')],
271
+ ['Cursor', path.join('.cursor', 'worktrees', 'web', 'x9y8')],
272
+ ])('writes at the project root of a %s worktree', async (_tool, rel) => {
273
+ const project = path.join(tmpDir, rel);
274
+ fs.mkdirSync(path.join(project, 'src'), {recursive: true});
275
+ fs.writeFileSync(path.join(project, '.git'), 'gitdir: /elsewhere/.git/worktrees/web\n');
276
+ fs.writeFileSync(
277
+ path.join(project, 'package.json'),
278
+ JSON.stringify({name: 'web', dependencies: {'@astryxdesign/core': '0.7.0'}}),
279
+ );
280
+
281
+ const json = await runCli(['init', '--json'], {cwd: path.join(project, 'src')});
282
+ expect(json.status).toBe(0);
283
+ const env = JSON.parse(json.stdout);
284
+ expect(env.data.docsWritten).toEqual(['../AGENTS.md', '../GEMINI.md']);
285
+ expect(env.data.docsError).toBeNull();
286
+
287
+ const explicit = await runCli(['init', '--agent-docs-path', 'AI.md', '--json'], {
288
+ cwd: path.join(project, 'src'),
289
+ });
290
+ expect(JSON.parse(explicit.stdout).data.docsWritten).toEqual(['AI.md']);
291
+
292
+ // Nothing reaches the settings folder itself.
293
+ const settings = path.join(tmpDir, rel.split(path.sep)[0]);
294
+ expect(fs.readdirSync(settings)).toEqual(['worktrees']);
295
+ });
296
+ });
297
+
298
+ describe('astryx init and the project root', () => {
299
+ it('writes where it runs when no folder above has a package.json, as before', async () => {
300
+ const notes = path.join(tmpDir, 'notes');
301
+ fs.mkdirSync(notes);
302
+
303
+ const json = await runCli(['init', '--json'], {cwd: notes});
304
+ expect(json.status).toBe(0);
305
+ const env = JSON.parse(json.stdout);
306
+ expect(env.data.docsWritten).toEqual(['AGENTS.md', 'GEMINI.md']);
307
+ expect(env.data.docsError).toBeNull();
308
+ expect(fs.readdirSync(notes).sort()).toEqual(['AGENTS.md', 'GEMINI.md']);
309
+ expect(fs.readdirSync(tmpDir)).toEqual(['notes']);
310
+ });
311
+
312
+ it('writes at the project root that declares Astryx, past a nested package that does not', async () => {
313
+ fs.writeFileSync(
314
+ path.join(tmpDir, 'package.json'),
315
+ JSON.stringify({name: 'app', dependencies: {'@astryxdesign/core': '0.7.0'}}),
316
+ );
317
+ const functions = path.join(tmpDir, 'functions');
318
+ fs.mkdirSync(path.join(functions, 'src'), {recursive: true});
319
+ fs.writeFileSync(path.join(functions, 'package.json'), '{"name":"functions"}\n');
320
+
321
+ const json = await runCli(['init', '--json'], {cwd: path.join(functions, 'src')});
322
+ expect(JSON.parse(json.stdout).data.docsWritten).toEqual([
323
+ '../../AGENTS.md',
324
+ '../../GEMINI.md',
325
+ ]);
326
+ expect(fs.readdirSync(functions).sort()).toEqual(['package.json', 'src']);
327
+ });
328
+ });
329
+
129
330
  describe('astryx init --remove-agents', () => {
331
+ it('run in a subfolder, removes what init wrote at the project root, with the same JSON shape', async () => {
332
+ fs.writeFileSync(
333
+ path.join(tmpDir, 'package.json'),
334
+ JSON.stringify({name: 'app', dependencies: {'@astryxdesign/core': '0.7.0'}}),
335
+ );
336
+ const sub = path.join(tmpDir, 'src', 'app');
337
+ fs.mkdirSync(sub, {recursive: true});
338
+ await runCli(['init'], {cwd: sub});
339
+ expect(exists('AGENTS.md')).toBe(true);
340
+
341
+ const text = await runCli(['init', '--remove-agents'], {cwd: sub});
342
+ expect(text.status).toBe(0);
343
+ expect(text.stdout).toMatch(/Removed empty \.\.\/\.\.\/AGENTS\.md/);
344
+ expect(exists('AGENTS.md')).toBe(false);
345
+ expect(exists('GEMINI.md')).toBe(false);
346
+
347
+ await runCli(['init'], {cwd: sub});
348
+ const json = await runCli(['init', '--remove-agents', '--json'], {cwd: sub});
349
+ expect(json.status).toBe(0);
350
+ // meta.answeredBy names the release that answered (spec AST-081).
351
+ expect(JSON.parse(json.stdout)).toEqual({
352
+ apiVersion: 1,
353
+ type: 'init.remove',
354
+ data: {removed: true},
355
+ meta: {answeredBy: expect.any(Object)},
356
+ });
357
+ });
358
+
130
359
  it('removes the Astryx section it previously installed', async () => {
131
360
  await runCli(['init', '--features', 'agents'], {cwd: tmpDir});
132
361
  expect(exists('AGENTS.md')).toBe(true);