@astryxdesign/cli 0.7.2 → 0.7.3-canary.06872b4

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 (212) hide show
  1. package/README.md +99 -66
  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 +16 -2
  30. package/api/component/component.test.mjs +15 -0
  31. package/api/discover/_adapter.mjs +36 -9
  32. package/api/discover/_adapter.test.mjs +42 -0
  33. package/api/docs/_adapter.mjs +17 -5
  34. package/api/docs/docs.test.mjs +7 -1
  35. package/api/doctor/doctor.mjs +86 -12
  36. package/api/doctor/doctor.test.mjs +28 -3
  37. package/api/init/init.doc.mjs +20 -6
  38. package/api/init/init.test.mjs +18 -2
  39. package/api/init/init.type.d.mts +3 -3
  40. package/api/init/init.type.mjs +3 -3
  41. package/api/init/remove/remove.mjs +17 -4
  42. package/api/init/remove/remove.test.mjs +114 -2
  43. package/api/init/run/run.mjs +69 -6
  44. package/api/integration/authoring-checks.mjs +83 -9
  45. package/api/integration/pack-check.mjs +123 -4
  46. package/api/integration/pack-check.test.mjs +87 -0
  47. package/api/integration/validate-integration-case-disk.test.mjs +111 -0
  48. package/api/integration/validate-integration-gaps.test.mjs +1964 -0
  49. package/api/integration/validate-integration.d.mts +11 -2
  50. package/api/integration/validate-integration.mjs +226 -32
  51. package/api/integration/validate-integration.type.d.mts +58 -1
  52. package/api/integration/validate-integration.type.mjs +45 -2
  53. package/api/integration/validateIntegration.doc.mjs +1 -1
  54. package/api/search/ORDERING.md +28 -0
  55. package/api/search/_adapter.d.mts +66 -2
  56. package/api/search/_adapter.mjs +118 -10
  57. package/api/search/_candidate-score.d.mts +40 -0
  58. package/api/search/_candidate-score.mjs +238 -0
  59. package/api/search/_cards.json +6098 -0
  60. package/api/search/_compound.d.mts +50 -0
  61. package/api/search/_compound.mjs +170 -0
  62. package/api/search/_doc-searcher.d.mts +4 -0
  63. package/api/search/_doc-searcher.mjs +23 -0
  64. package/api/search/_doc-searcher.test.mjs +36 -0
  65. package/api/search/_doc-searcher.weights.json +1 -0
  66. package/api/search/_doc-text.d.mts +30 -0
  67. package/api/search/_doc-text.mjs +87 -0
  68. package/api/search/_entries.d.mts +52 -0
  69. package/api/search/_entries.mjs +158 -0
  70. package/api/search/_features.d.mts +35 -0
  71. package/api/search/_features.mjs +155 -0
  72. package/api/search/_gather-components.d.mts +26 -0
  73. package/api/search/_gather-components.mjs +284 -0
  74. package/api/search/_gather-docs.d.mts +20 -0
  75. package/api/search/_gather-docs.mjs +254 -0
  76. package/api/search/_gather-hooks.d.mts +12 -0
  77. package/api/search/_gather-hooks.mjs +52 -0
  78. package/api/search/_gather-templates.d.mts +25 -0
  79. package/api/search/_gather-templates.mjs +107 -0
  80. package/api/search/_lexical.d.mts +135 -0
  81. package/api/search/_lexical.mjs +351 -0
  82. package/api/search/_main-searcher.d.mts +19 -0
  83. package/api/search/_main-searcher.mjs +115 -0
  84. package/api/search/_main-searcher.test.mjs +106 -0
  85. package/api/search/_main-searcher.weights.json +1 -0
  86. package/api/search/_module-doc.d.mts +32 -0
  87. package/api/search/_module-doc.mjs +78 -0
  88. package/api/search/_order.d.mts +32 -0
  89. package/api/search/_order.mjs +162 -0
  90. package/api/search/_order.test.mjs +72 -0
  91. package/api/search/_priority.d.mts +50 -0
  92. package/api/search/_priority.mjs +139 -0
  93. package/api/search/_query-words.d.mts +25 -0
  94. package/api/search/_query-words.mjs +92 -0
  95. package/api/search/_query.d.mts +65 -0
  96. package/api/search/_query.mjs +176 -0
  97. package/api/search/_result.d.mts +97 -0
  98. package/api/search/_result.mjs +150 -0
  99. package/api/search/_score.d.mts +60 -0
  100. package/api/search/_score.mjs +283 -0
  101. package/api/search/_synonyms.d.mts +21 -0
  102. package/api/search/_synonyms.mjs +67 -0
  103. package/api/search/_template-searcher.d.mts +4 -0
  104. package/api/search/_template-searcher.mjs +23 -0
  105. package/api/search/_template-searcher.test.mjs +40 -0
  106. package/api/search/_template-searcher.weights.json +1 -0
  107. package/api/search/_titles.d.mts +33 -0
  108. package/api/search/_titles.mjs +65 -0
  109. package/api/search/_words.d.mts +64 -0
  110. package/api/search/_words.mjs +157 -0
  111. package/api/search/search.d.mts +22 -305
  112. package/api/search/search.doc.mjs +7 -5
  113. package/api/search/search.mjs +53 -2483
  114. package/api/search/search.type.d.mts +6 -1
  115. package/api/search/search.type.mjs +6 -1
  116. package/api/search/tree.d.mts +15 -0
  117. package/api/search/tree.mjs +40 -0
  118. package/api/search/tree.test.mjs +66 -0
  119. package/api/search/tree.weights.json +1 -0
  120. package/api/theme/build/build.packed-old-core.test.mjs +1 -0
  121. package/api/upgrade/_adapter.d.mts +57 -9
  122. package/api/upgrade/_adapter.mjs +198 -28
  123. package/api/upgrade/registry/registry.d.mts +10 -4
  124. package/api/upgrade/registry/registry.mjs +33 -16
  125. package/api/upgrade/registry/registry.test.mjs +85 -0
  126. package/api/upgrade/run/run.d.mts +4 -2
  127. package/api/upgrade/run/run.mjs +13 -2
  128. package/api/upgrade/run/run.test.mjs +417 -3
  129. package/api/upgrade/upgrade.doc.mjs +10 -5
  130. package/api/upgrade/upgrade.mjs +13 -2
  131. package/api/upgrade/upgrade.test.mjs +194 -0
  132. package/api/upgrade/upgrade.type.d.mts +5 -3
  133. package/api/upgrade/upgrade.type.mjs +8 -6
  134. package/assets/codemods/transforms/v0.7.0/__tests__/validate-data-token-ownership.test.mjs +11 -0
  135. package/assets/codemods/transforms/v0.7.0/validate-data-token-ownership.mjs +3 -3
  136. package/assets/docs/is-my-code-good.doc.mjs +287 -0
  137. package/assets/docs/tree/api.doc.mjs +6 -0
  138. package/assets/docs/tree/host-entry.doc.mjs +128 -0
  139. package/assets/docs/tree/template-grading-rubric.doc.mjs +16 -37
  140. package/assets/docs/tree/tokens-data.doc.mjs +2 -2
  141. package/assets/docs/working-with-ai.doc.mjs +6 -2
  142. package/assets/templates/themes/butter/butterTheme.ts +4 -2
  143. package/assets/templates/themes/stone/stoneTheme.ts +4 -2
  144. package/assets/templates/themes/y2k/y2kTheme.ts +4 -2
  145. package/authoring/debug/parse.d.mts +1 -1
  146. package/clients/cli/bin/astryx.mjs +7 -78
  147. package/clients/cli/commands/doctor.mjs +30 -0
  148. package/clients/cli/commands/doctor.test.mjs +65 -0
  149. package/clients/cli/commands/init.behavior.test.mjs +230 -1
  150. package/clients/cli/commands/init.doc.mjs +28 -9
  151. package/clients/cli/commands/init.flag-help.test.mjs +1 -0
  152. package/clients/cli/commands/init.mjs +2 -2
  153. package/clients/cli/commands/outside-project.test.mjs +2 -1
  154. package/clients/cli/commands/setup-nudge.test.mjs +61 -0
  155. package/clients/cli/commands/upgrade.doc.mjs +4 -2
  156. package/clients/cli/commands/upgrade.mjs +9 -2
  157. package/clients/cli/commands/upgrade.subfolder-path.test.mjs +97 -0
  158. package/clients/cli/index.mjs +19 -1
  159. package/clients/cli/run.mjs +108 -0
  160. package/foundation/agent-docs/agent-doc-state.d.mts +20 -7
  161. package/foundation/agent-docs/agent-doc-state.mjs +35 -8
  162. package/foundation/agent-docs/agent-docs.d.mts +100 -12
  163. package/foundation/agent-docs/agent-docs.mjs +1010 -99
  164. package/foundation/agent-docs/agent-docs.path-safety.test.mjs +1239 -17
  165. package/foundation/agent-docs/agent-docs.test.mjs +678 -35
  166. package/foundation/config/project.mjs +20 -22
  167. package/foundation/config/project.test.mjs +56 -0
  168. package/foundation/discovery/component-discovery.d.mts +37 -0
  169. package/foundation/discovery/component-discovery.mjs +80 -5
  170. package/foundation/discovery/component-loader.d.mts +1 -1
  171. package/foundation/discovery/docs-discovery.mjs +14 -1
  172. package/foundation/discovery/module-exports.d.mts +41 -0
  173. package/foundation/discovery/module-exports.mjs +253 -0
  174. package/foundation/discovery/template-adapter.mjs +18 -1
  175. package/foundation/discovery/template-needs.mjs +1 -44
  176. package/foundation/discovery/theme-discovery.mjs +147 -3
  177. package/foundation/doc-compiler/doc-loads.test.mjs +8 -0
  178. package/foundation/doc-compiler/read.mjs +59 -1
  179. package/foundation/doc-compiler/read.test.mjs +11 -2
  180. package/foundation/fs/paths.d.mts +70 -8
  181. package/foundation/fs/paths.mjs +556 -38
  182. package/foundation/fs/paths.test.mjs +430 -54
  183. package/foundation/integrations/autolink.d.mts +2 -1
  184. package/foundation/integrations/autolink.mjs +4 -13
  185. package/foundation/integrations/computer-integrations.d.mts +6 -3
  186. package/foundation/integrations/computer-integrations.mjs +38 -9
  187. package/foundation/integrations/contribution-inventory.d.mts +15 -20
  188. package/foundation/integrations/contribution-inventory.mjs +41 -5
  189. package/foundation/integrations/integrations.d.mts +27 -1
  190. package/foundation/integrations/integrations.mjs +61 -2
  191. package/foundation/integrations/validate-contributions.d.mts +115 -0
  192. package/foundation/integrations/validate-contributions.mjs +1168 -13
  193. package/foundation/release/handoff.d.mts +23 -4
  194. package/foundation/release/handoff.mjs +175 -8
  195. package/foundation/release/run-context.mjs +14 -0
  196. package/foundation/response/response-types.doc.mjs +1 -1
  197. package/foundation/route/route.d.mts +285 -0
  198. package/foundation/route/route.mjs +324 -0
  199. package/foundation/route/route.test.mjs +153 -0
  200. package/foundation/runtime/hosted-runtime.test.mjs +403 -0
  201. package/foundation/runtime/locked-versions.d.mts +32 -0
  202. package/foundation/runtime/locked-versions.mjs +65 -0
  203. package/foundation/runtime/package-root.d.mts +91 -0
  204. package/foundation/runtime/package-root.mjs +174 -0
  205. package/foundation/runtime/package-set.d.mts +62 -0
  206. package/foundation/runtime/package-set.mjs +130 -0
  207. package/foundation/runtime/package-set.test.mjs +432 -0
  208. package/host/door-conformance.test.mjs +274 -0
  209. package/host/index.d.ts +116 -0
  210. package/host/index.mjs +295 -0
  211. package/host/index.test.mjs +302 -0
  212. package/package.json +14 -9
@@ -0,0 +1,128 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file `astryx docs cli/host-entry`: run the CLI from a tool that carries it,
5
+ * through `@astryxdesign/cli/host` (spec AST-081 FR1,
6
+ * architecture:cli-surface INV30).
7
+ */
8
+
9
+ /** @type {import('@astryxdesign/cli/authoring').ReferenceDoc} */
10
+ export const docs = {
11
+ type: 'generic',
12
+ name: 'host-entry',
13
+ placement: {parent: 'namespace:cli', slot: 'guides', order: 15},
14
+ title: 'Host entry',
15
+ category: 'guide',
16
+ keywords: [
17
+ 'host entry',
18
+ 'runHostedCli',
19
+ '@astryxdesign/cli/host',
20
+ 'carry the cli',
21
+ 'embed the cli',
22
+ 'programmatic run',
23
+ 'package sets',
24
+ 'cliCommand',
25
+ ],
26
+ description:
27
+ 'Run the Astryx CLI from your own command-line tool: the inputs `runHostedCli` takes, what it returns, and when the process ends instead.',
28
+ sections: [
29
+ {
30
+ id: 'start',
31
+ title: 'Start a run',
32
+ content: [
33
+ {
34
+ type: 'prose',
35
+ text: "A tool that carries the CLI for its own users starts one run per process with `runHostedCli` from `@astryxdesign/cli/host`. The run behaves as the CLI's own command does with the same arguments in the same folder, and the commands it prints start with your tool's command.",
36
+ },
37
+ {
38
+ type: 'code',
39
+ lang: 'javascript',
40
+ code: "import {runHostedCli} from '@astryxdesign/cli/host';\n\nconst {status} = await runHostedCli({\n argv: process.argv.slice(2),\n cwd: process.cwd(),\n cliCommand: 'acme astryx',\n});\nprocess.exitCode = status;",
41
+ },
42
+ ],
43
+ },
44
+ {
45
+ id: 'options',
46
+ title: 'Options',
47
+ content: [
48
+ {
49
+ type: 'table',
50
+ headers: ['Option', 'Default', 'Meaning'],
51
+ rows: [
52
+ [
53
+ '`argv`',
54
+ 'none',
55
+ 'The command-line arguments, without the command name.',
56
+ ],
57
+ [
58
+ '`cwd`',
59
+ "the process's working folder",
60
+ 'The folder the run works in. It must exist.',
61
+ ],
62
+ [
63
+ '`cliCommand`',
64
+ "the package manager's name",
65
+ 'The command your users type, such as `acme astryx`: a non-blank single line of at most 120 characters, with no surrounding spaces.',
66
+ ],
67
+ [
68
+ '`declinedProjectCli`',
69
+ 'none',
70
+ "Why you did not run the project's own CLI, such as `untrusted`: a non-blank single line of at most 120 characters. That CLI then never runs.",
71
+ ],
72
+ [
73
+ '`roots`',
74
+ 'none',
75
+ 'Your package sets, in order of preference: folders whose `node_modules` each hold a CLI, the Core of the same release, and integrations. A run never installs a release; it takes the release a project needs from the first set that holds it.',
76
+ ],
77
+ [
78
+ '`integrations`',
79
+ 'none',
80
+ 'Integrations you approve for use outside a project, by package name.',
81
+ ],
82
+ [
83
+ '`discoverSources`',
84
+ 'none',
85
+ "Your own discover sources. `astryx discover` reports each as the host's.",
86
+ ],
87
+ ],
88
+ },
89
+ ],
90
+ },
91
+ {
92
+ id: 'result',
93
+ title: 'Result and errors',
94
+ content: [
95
+ {
96
+ type: 'list',
97
+ style: 'unordered',
98
+ items: [
99
+ "It resolves with `{status, packages}`: the exit status the CLI's own command would exit with, and each package the run used, with its version and whether it came from the project or a package set.",
100
+ 'When another release answers from one of your package sets through its own host entry, that entry runs in the same process with the same inputs, and the call resolves with its result.',
101
+ 'It rejects with a `TypeError`, before anything runs, for an unknown option or an invalid value, and with an `Error` when a run already started in the process. Start each run in a new process.',
102
+ ],
103
+ },
104
+ ],
105
+ },
106
+ {
107
+ id: 'process-ends',
108
+ title: 'When the process ends instead',
109
+ content: [
110
+ {
111
+ type: 'prose',
112
+ text: "Like the CLI's own command, a run ends the process with its exit status, and the call never resolves, when it:",
113
+ },
114
+ {
115
+ type: 'list',
116
+ style: 'unordered',
117
+ items: [
118
+ 'prints help or the version (`--help`, `-h`, `help`, `--version`, `-V`, `--version --json`);',
119
+ 'rejects the command line, such as an unknown command or option;',
120
+ 'reports an error it stops on, including every error in `--json` mode;',
121
+ 'meets an unexpected error;',
122
+ "hands the run to another CLI's own command: the project's CLI when you pass no `declinedProjectCli`, or a package set whose CLI has no host entry.",
123
+ ],
124
+ },
125
+ ],
126
+ },
127
+ ],
128
+ };
@@ -102,7 +102,11 @@ const gradeRows = TEMPLATE_RUBRIC.grades.map(({grade, min, max, meaning}) => [
102
102
  export const docs = {
103
103
  type: 'generic',
104
104
  name: 'template-grading-rubric',
105
- placement: {parent: 'namespace:write-good-templates', slot: 'guides', order: 10},
105
+ placement: {
106
+ parent: 'namespace:write-good-templates',
107
+ slot: 'guides',
108
+ order: 10,
109
+ },
106
110
  title: 'Full grading rubric',
107
111
  category: 'guide',
108
112
  description:
@@ -112,6 +116,10 @@ export const docs = {
112
116
  id: 'understand-the-score',
113
117
  title: 'Understand the score',
114
118
  content: [
119
+ {
120
+ type: 'prose',
121
+ text: 'Start with {@link generic:is-my-code-good}: it owns the shared Astryx quality criteria. This rubric adds template weights, counting rules, and template-only checks, and each category that scores a shared criterion names it.',
122
+ },
115
123
  {
116
124
  type: 'prose',
117
125
  text: `Template rubric ${TEMPLATE_RUBRIC.version} scores seven categories for 100 points. Aim for 100; ${TEMPLATE_RUBRIC.minimumGrade} (${TEMPLATE_RUBRIC.minimumScore}) is the publication floor, not the target. Each category below lists its weight. Never award points for anything you did not inspect.`,
@@ -137,11 +145,11 @@ export const docs = {
137
145
  content: [
138
146
  {
139
147
  type: 'prose',
140
- text: 'Count every JSX opening tag in the copied `.tsx` source. An Astryx element is a component imported from `@astryxdesign/core` or from a public component export of the integration package. A raw HTML element is any lowercase intrinsic JSX tag. Count occurrences, not only unique tag names.',
148
+ text: 'This category scores the Astryx components criterion in {@link generic:is-my-code-good}. Count every JSX opening tag in the copied `.tsx` source. An Astryx element is a component imported from `@astryxdesign/core` or from a public component export of the integration package. A raw HTML element is any lowercase intrinsic JSX tag. Count occurrences, not only unique tag names.',
141
149
  },
142
150
  {
143
151
  type: 'prose',
144
- text: 'Do not count fragments or a PascalCase helper defined in the same file. Inspect that helper and count the raw HTML inside it. For each raw element, decide whether it is necessary because Astryx has no equivalent, or unnecessary because an Astryx component can replace it.',
152
+ text: 'Do not count fragments or a PascalCase helper defined in the same file. Inspect that helper and count the raw HTML inside it. For each raw element, decide with that guide whether it is necessary because Astryx has no equivalent, or unnecessary because an Astryx component can replace it.',
145
153
  },
146
154
  {
147
155
  type: 'table',
@@ -156,35 +164,6 @@ export const docs = {
156
164
  ['21 or more', '0'],
157
165
  ],
158
166
  },
159
- {
160
- type: 'table',
161
- headers: ['Raw HTML use', 'Astryx replacement'],
162
- rows: [
163
- [
164
- '`div` for layout',
165
- '`VStack`, `HStack`, `Card`, `Section`, or `Center`',
166
- ],
167
- ['`div` for a grid', '`Grid`'],
168
- ['`span` or `p` for text', '`Text`'],
169
- ['`h1` through `h6`', '`Heading level={N}`'],
170
- ['`button`', '`Button` or `IconButton`'],
171
- ['`a`', '`Link`'],
172
- ['In-page `nav` or `aside`', '`LayoutPanel` in the start slot'],
173
- [
174
- '`header` or `main`',
175
- '`LayoutHeader` or `LayoutContent` in `Layout`',
176
- ],
177
- ['`ul`, `ol`, or `li`', '`List` and `ListItem`'],
178
- [
179
- '`input`, `textarea`, or `select`',
180
- 'The matching Astryx form control',
181
- ],
182
- ['`table`, `tr`, or `td`', '`Table`'],
183
- ['`hr`', '`Divider`'],
184
- ['`dialog`', '`Dialog`'],
185
- ['`details` or `summary`', '`Collapsible`'],
186
- ],
187
- },
188
167
  {
189
168
  type: 'list',
190
169
  style: 'unordered',
@@ -202,7 +181,7 @@ export const docs = {
202
181
  content: [
203
182
  {
204
183
  type: 'prose',
205
- text: 'Count raw icon markup in the copied file. {@link generic:template-icons} shows how to render icons through Astryx instead.',
184
+ text: 'This category scores the Icons criterion in {@link generic:is-my-code-good}. Count raw icon markup in the copied file. {@link generic:template-icons} shows how to render icons through Astryx instead.',
206
185
  },
207
186
  {
208
187
  type: 'list',
@@ -231,7 +210,7 @@ export const docs = {
231
210
  content: [
232
211
  {
233
212
  type: 'prose',
234
- text: 'Prefer Astryx component props and design tokens. Count individual CSS properties inside `stylex.create` and inline `style` objects. Count each `className` and `stylex.props` use once. Do not count Astryx props such as `gap`, `padding`, `variant`, `size`, `color`, `level`, `columns`, `contentPadding`, or `height`.',
213
+ text: 'This category scores the Styling criterion in {@link generic:is-my-code-good}. Count individual CSS properties inside `stylex.create` and inline `style` objects. Count each `className` and `stylex.props` use once. Do not count Astryx props such as `gap`, `padding`, `variant`, `size`, `color`, `level`, `columns`, `contentPadding`, or `height`.',
235
214
  },
236
215
  {
237
216
  type: 'table',
@@ -260,7 +239,7 @@ export const docs = {
260
239
  type: 'list',
261
240
  style: 'ordered',
262
241
  items: [
263
- 'Use `Layout` or `Center` as the page root. A template whose category starts with `Shell -` uses `AppShell` because global chrome is its purpose.',
242
+ 'Choose the page root with the Layout criterion in {@link generic:is-my-code-good}. In a template, only a category that starts with `Shell -` owns global chrome, so it uses `AppShell`; every other page template uses `Layout` or `Center`.',
264
243
  'Outside a `Shell -` template, leave global navigation to the host app. Put in-page navigation in a `LayoutPanel` and page headings in `LayoutHeader`.',
265
244
  'Use `Grid` with `columns={{minWidth: 280}}` for responsive collections. Do not fix the column count or rebuild the grid in raw CSS.',
266
245
  'Use `Center` for centered content instead of custom flexbox workarounds.',
@@ -385,7 +364,7 @@ export const docs = {
385
364
  content: [
386
365
  {
387
366
  type: 'prose',
388
- text: 'Inspect every image reference in the copied file against {@link generic:template-images-media}.',
367
+ text: 'This category scores the Images and assets criterion in {@link generic:is-my-code-good}. Inspect every image reference in the copied file against {@link generic:template-images-media}.',
389
368
  },
390
369
  {
391
370
  type: 'table',
@@ -413,7 +392,7 @@ export const docs = {
413
392
  content: [
414
393
  {
415
394
  type: 'prose',
416
- text: 'Award 2 points for each condition. Grade the copied file, not only the package source.',
395
+ text: 'This category scores the Code health criterion in {@link generic:is-my-code-good}, plus conditions only a template has. Award 2 points for each condition. Grade the copied file, not only the package source.',
417
396
  },
418
397
  {
419
398
  type: 'table',
@@ -17,14 +17,14 @@ export const docs = {
17
17
  "order": 20
18
18
  },
19
19
  "category": "foundations",
20
- "description": "Colors for charts and graphs: one categorical accent per series, a neutral for labels and reference lines, and sequential ramps from 5 (darkest) to 1 (lightest) for ordered scales and heatmaps. Import dataVars from @astryxdesign/core/theme/dataTokens.stylex for CSS-capable code. Retaining any member emits the complete 56-variable group; an unused graph emits none.",
20
+ "description": "Colors for charts and graphs: one categorical accent per series, a neutral for labels and reference lines, and sequential ramps from 5 (darkest) to 1 (lightest) for ordered scales and heatmaps. Import dataVars from @astryxdesign/core/theme/dataTokens.stylex for CSS-capable code. Source compilation emits the complete 56-variable group when any member is retained and none for an unused graph. Prebuilt CSS consumers load @astryxdesign/core/astryx.css, which defines the complete group for the already-compiled dataVars entry.",
21
21
  "sections": [
22
22
  {
23
23
  "title": "Data Visualization Tokens",
24
24
  "content": [
25
25
  {
26
26
  "type": "prose",
27
- "text": "Colors for charts and graphs: one categorical accent per series, a neutral for labels and reference lines, and sequential ramps from 5 (darkest) to 1 (lightest) for ordered scales and heatmaps. Import dataVars from @astryxdesign/core/theme/dataTokens.stylex for CSS-capable code. Retaining any member emits the complete 56-variable group; an unused graph emits none."
27
+ "text": "Colors for charts and graphs: one categorical accent per series, a neutral for labels and reference lines, and sequential ramps from 5 (darkest) to 1 (lightest) for ordered scales and heatmaps. Import dataVars from @astryxdesign/core/theme/dataTokens.stylex for CSS-capable code. Source compilation emits the complete 56-variable group when any member is retained and none for an unused graph. Prebuilt CSS consumers load @astryxdesign/core/astryx.css, which defines the complete group for the already-compiled dataVars entry."
28
28
  },
29
29
  {
30
30
  "type": "table",
@@ -44,7 +44,7 @@ export const docs = {
44
44
  },
45
45
  {
46
46
  type: 'prose',
47
- text: "By default this creates `AGENTS.md` (the tool-agnostic standard most agents read). To target a specific tool's file instead:",
47
+ text: "By default this writes the instructions to `AGENTS.md` (the tool-agnostic standard most agents read), in the project root: the nearest folder whose `package.json` declares an Astryx package, else the nearest with any `package.json`. The search stops at the repository root. In the home folder, or in a settings folder in it outside a project, init writes no agent docs: every folder of the home folder whose name starts with a dot (`~/.claude`, `~/.gemini`, `~/.codex`) is a settings folder, and such a folder itself never counts as a project, even with a `package.json`; a project below it with its own `package.json`, such as an agent worktree, is set up normally. Claude Code skips `AGENTS.md` when the project has a `CLAUDE.md` or `.claude/CLAUDE.md`, so init also writes them to that file, even when it imports `AGENTS.md`: Claude Code does not follow that import in a session started in a subfolder. When both files exist, root `CLAUDE.md` gets the copy and a copy in `.claude/CLAUDE.md` is removed, unless the project ignores `CLAUDE.md`, in which case a `.claude/CLAUDE.md` the project commits keeps a copy. Gemini CLI reads only `GEMINI.md`, so init adds a one-line import of `AGENTS.md` there, unless `GEMINI.md` already imports it. `upgrade` keeps the copies in sync. The block between the Astryx markers belongs to Astryx: init and `upgrade` write, refresh, and remove it whatever the project's ignore rules or generated markers say, and never change the text around it. To target a specific tool's file instead:",
48
48
  },
49
49
  {
50
50
  type: 'code',
@@ -55,7 +55,11 @@ astryx init --features agents --agent cursor # .cursorrules if present, else
55
55
  astryx init --features agents --agent codex # AGENTS.md (Copilot, Codex, etc.)
56
56
  astryx init --features agents --agent hermes # .hermes.md or HERMES.md if present, else AGENTS.md
57
57
  astryx init --features agents --agent muse # AGENTS.md (Muse)
58
- astryx init --features agents --agent all # every agent file present, else AGENTS.md and .claude/CLAUDE.md`,
58
+ astryx init --features agents --agent all # every agent file present except GEMINI.md, else AGENTS.md and .claude/CLAUDE.md`,
59
+ },
60
+ {
61
+ type: 'prose',
62
+ text: 'With `--agent all`, `GEMINI.md` keeps its one-line import of `AGENTS.md`. With `--agent claude` or `--agent all`, as by default, when `CLAUDE.md` gets the instructions, `.claude/CLAUDE.md` gets none and a copy already there is removed, unless the project ignores `CLAUDE.md`: a fresh clone does not have it, so a `.claude/CLAUDE.md` the project commits gets a copy too, with `--agent claude` and `--agent all` alike. `init --remove-agents` removes them at the project root and in the folder it runs in.',
59
63
  },
60
64
  {
61
65
  type: 'prose',
@@ -81,7 +81,8 @@ const SECONDARY_SELECTOR = {
81
81
 
82
82
  /**
83
83
  * The secondary Button treatment on ComplexSelector triggers (AST-079 FR2), with
84
- * the hover kept off a disabled trigger, whose inner button is disabled.
84
+ * the hover kept off a disabled trigger, whose inner button is disabled
85
+ * (natively or, keeping its tab stop, with `aria-disabled`).
85
86
  */
86
87
  const SECONDARY_COMPLEX_SELECTOR = {
87
88
  backgroundColor: SECONDARY_BUTTON.backgroundColor,
@@ -89,7 +90,8 @@ const SECONDARY_COMPLEX_SELECTOR = {
89
90
  borderStyle: SECONDARY_BUTTON.borderStyle,
90
91
  borderColor: SECONDARY_BUTTON.borderColor,
91
92
  color: SECONDARY_BUTTON.color,
92
- ':hover:not(:has(> :disabled))': SECONDARY_BUTTON[':hover'],
93
+ ':hover:not(:has(> :is(:disabled, [aria-disabled="true"])))':
94
+ SECONDARY_BUTTON[':hover'],
93
95
  } as const;
94
96
 
95
97
  export const butterTheme = defineTheme({
@@ -100,14 +100,16 @@ const SECONDARY_SELECTOR = {
100
100
 
101
101
  /**
102
102
  * The secondary Button treatment on ComplexSelector triggers (AST-079 FR2), with
103
- * the hover kept off a disabled trigger, whose inner button is disabled.
103
+ * the hover kept off a disabled trigger, whose inner button is disabled
104
+ * (natively or, keeping its tab stop, with `aria-disabled`).
104
105
  */
105
106
  const SECONDARY_COMPLEX_SELECTOR = {
106
107
  backgroundColor: SECONDARY_BUTTON.backgroundColor,
107
108
  borderWidth: SECONDARY_BUTTON.borderWidth,
108
109
  borderStyle: SECONDARY_BUTTON.borderStyle,
109
110
  borderColor: SECONDARY_BUTTON.borderColor,
110
- ':hover:not(:has(> :disabled))': SECONDARY_BUTTON[':hover'],
111
+ ':hover:not(:has(> :is(:disabled, [aria-disabled="true"])))':
112
+ SECONDARY_BUTTON[':hover'],
111
113
  } as const;
112
114
 
113
115
  export const stoneTheme = defineTheme({
@@ -76,7 +76,8 @@ const SECONDARY_SELECTOR = {
76
76
 
77
77
  /**
78
78
  * The secondary Button treatment on ComplexSelector triggers (AST-079 FR2), with
79
- * the hover kept off a disabled trigger, whose inner button is disabled.
79
+ * the hover kept off a disabled trigger, whose inner button is disabled
80
+ * (natively or, keeping its tab stop, with `aria-disabled`).
80
81
  */
81
82
  const SECONDARY_COMPLEX_SELECTOR = {
82
83
  backgroundColor: SECONDARY_BUTTON.backgroundColor,
@@ -84,7 +85,8 @@ const SECONDARY_COMPLEX_SELECTOR = {
84
85
  borderStyle: SECONDARY_BUTTON.borderStyle,
85
86
  borderColor: SECONDARY_BUTTON.borderColor,
86
87
  color: SECONDARY_BUTTON.color,
87
- ':hover:not(:has(> :disabled))': SECONDARY_BUTTON[':hover'],
88
+ ':hover:not(:has(> :is(:disabled, [aria-disabled="true"])))':
89
+ SECONDARY_BUTTON[':hover'],
88
90
  } as const;
89
91
 
90
92
  export const y2kTheme = defineTheme({
@@ -79,9 +79,9 @@ declare const eventSchema: z.ZodObject<{
79
79
  none: "none";
80
80
  component: "component";
81
81
  hook: "hook";
82
+ theme: "theme";
82
83
  doc: "doc";
83
84
  template: "template";
84
- theme: "theme";
85
85
  integration: "integration";
86
86
  migration: "migration";
87
87
  mixed: "mixed";
@@ -53,84 +53,13 @@ if (!isNodeVersionSupported(process.versions.node)) {
53
53
  process.exit(1);
54
54
  }
55
55
 
56
- // Global mode: choose the release that answers this folder and, when it is
57
- // not this copy, hand the run to it before anything the project provides (its
58
- // astryx.config, integrations, or Core) is loaded. Returns only when this copy
59
- // answers. See foundation/release/handoff.mjs and spec AST-081 FR2 and FR9.
60
- const {startRun} = await importSrc('../../foundation/release/handoff.mjs');
61
- await startRun({
56
+ // Global mode: the run chooses the release that answers this folder and, when
57
+ // it is not this copy, hands the run to it before anything the project
58
+ // provides (its astryx.config, integrations, or Core) is loaded; only then
59
+ // does it load and dispatch the command. See clients/cli/run.mjs,
60
+ // foundation/release/handoff.mjs, and spec AST-081 FR2 and FR9.
61
+ const {runCli} = await importSrc('run.mjs');
62
+ await runCli({
62
63
  cliRoot: join(binDir, '..', '..', '..'),
63
64
  argv: process.argv.slice(2),
64
65
  });
65
-
66
- // Imports that transitively load `styleText` must happen AFTER the gate above,
67
- // so they are dynamically imported here rather than at the top of the module.
68
- const {
69
- program,
70
- loadProjectDebugHandler,
71
- loadProjectForHelp,
72
- handleVersionJsonPreflight,
73
- } = await importSrc('index.mjs');
74
- const {isJsonMode, toErrorEnvelope} = await importSrc('../../foundation/response/json.mjs');
75
- const {handleCommanderError} = await importSrc('lib/json-shim.mjs');
76
- const {setOutcome} = await importSrc('../../foundation/debug/index.mjs');
77
-
78
- /**
79
- * Top-level error boundary (contract guarantee #4): an uncaught throw must
80
- * never reach a --json consumer as a raw stack trace. If we're in JSON mode,
81
- * convert it to the contract's error envelope on stdout and exit 1; otherwise
82
- * preserve the normal human-facing behavior.
83
- *
84
- * We detect JSON mode two ways: the global flag (set once root options are
85
- * parsed) and a raw argv check, since a throw can occur during parsing/command
86
- * load — before the preAction hook engages the flag.
87
- */
88
- function inJsonMode() {
89
- return isJsonMode() || process.argv.slice(2).includes('--json');
90
- }
91
-
92
- /** @param {unknown} err */
93
- function handleFatal(err) {
94
- // CommanderError (parse errors, --help, unknown command) routes
95
- // through the JSON shim so that a --json consumer always gets a
96
- // valid envelope and the right exit code. handleCommanderError
97
- // calls process.exit when it owns the error.
98
- if (handleCommanderError(err)) return;
99
-
100
- // Anything still here is an uncaught throw or an unhandled rejection —
101
- // a bug rather than a user error, and the class of failure most worth
102
- // having a record of.
103
- setOutcome('fatal', {exitCode: 1, error: err});
104
-
105
- if (inJsonMode()) {
106
- // Only emit if a command didn't already produce an envelope.
107
- if (!process.__xdsJsonHandled) {
108
- process.__xdsJsonHandled = true;
109
- console.log(JSON.stringify(toErrorEnvelope(err), null, 2));
110
- }
111
- process.exit(1);
112
- }
113
- // Human mode: preserve Commander/Node default-ish behavior.
114
- console.error(err instanceof Error ? err.stack || err.message : String(err));
115
- process.exit(1);
116
- }
117
-
118
- process.on('unhandledRejection', handleFatal);
119
- process.on('uncaughtException', handleFatal);
120
-
121
- // Pick up the project's `debug` function before parsing, so parse errors and
122
- // `--help` — which short-circuit before any hook runs — are reported too.
123
- await loadProjectDebugHandler();
124
- // Help prints its commands while Commander parses, so a help run reads the
125
- // project's declared `cliCommand` here.
126
- await loadProjectForHelp();
127
-
128
- // `--version --json` answers and exits without Commander, so it runs here —
129
- // after the handler exists, or the run would go unrecorded.
130
- handleVersionJsonPreflight();
131
-
132
- try {
133
- await program.parseAsync(process.argv);
134
- } catch (err) {
135
- handleFatal(err);
136
- }
@@ -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');