@astryxdesign/cli 0.6.3-canary.7ada42e → 0.6.3-canary.7ec7be0

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 (221) hide show
  1. package/README.md +21 -19
  2. package/api/build/_adapter.d.mts +50 -0
  3. package/api/build/_adapter.mjs +60 -0
  4. package/api/build/build.doc.mjs +13 -7
  5. package/api/build/build.test.mjs +184 -6
  6. package/api/build/build.type.d.mts +57 -2
  7. package/api/build/build.type.mjs +30 -7
  8. package/api/build/help/help.d.mts +4 -0
  9. package/api/build/help/help.mjs +24 -11
  10. package/api/build/kit/kit.d.mts +4 -1
  11. package/api/build/kit/kit.mjs +165 -49
  12. package/api/build/kit/rank.d.mts +44 -0
  13. package/api/build/kit/rank.mjs +432 -0
  14. package/api/build/kit/rank.test.mjs +196 -0
  15. package/api/component/_adapter.d.mts +6 -12
  16. package/api/component/_adapter.mjs +20 -10
  17. package/api/component/component.mjs +60 -12
  18. package/api/component/list/list.mjs +3 -2
  19. package/api/docs/_adapter.d.mts +251 -28
  20. package/api/docs/_adapter.mjs +918 -35
  21. package/api/docs/detail/detail.mjs +11 -3
  22. package/api/docs/detail/section/section.mjs +24 -13
  23. package/api/docs/detail/section/section.test.mjs +15 -6
  24. package/api/docs/docs.d.mts +5 -3
  25. package/api/docs/docs.doc.mjs +38 -17
  26. package/api/docs/docs.mjs +44 -8
  27. package/api/docs/docs.test.mjs +158 -4
  28. package/api/docs/docs.type.d.mts +181 -2
  29. package/api/docs/docs.type.mjs +117 -3
  30. package/api/docs/index/index.mjs +11 -3
  31. package/api/docs/index/index.test.mjs +1 -1
  32. package/api/docs/integration-tree.test.mjs +555 -0
  33. package/api/docs/integrationDocs.test.mjs +14 -14
  34. package/api/docs/list/list.mjs +25 -5
  35. package/api/docs/node/node.d.mts +43 -0
  36. package/api/docs/node/node.mjs +192 -0
  37. package/api/docs/reference-blocks.test.mjs +406 -0
  38. package/api/doctor/doctor.d.mts +30 -2
  39. package/api/doctor/doctor.mjs +151 -8
  40. package/api/doctor/doctor.test.mjs +95 -7
  41. package/api/index.d.mts +1 -1
  42. package/api/index.mjs +1 -0
  43. package/api/init/init.doc.mjs +1 -1
  44. package/api/integration/add-contribution.d.mts +2 -1
  45. package/api/integration/add-contribution.mjs +107 -5
  46. package/api/integration/add-contribution.test.mjs +137 -0
  47. package/api/integration/authoring-checks.mjs +138 -28
  48. package/api/integration/authoring-checks.test.mjs +171 -3
  49. package/api/integration/authoring-checks.type.mjs +6 -1
  50. package/api/integration/integration-authoring.type.d.mts +2 -0
  51. package/api/integration/integration-authoring.type.mjs +2 -0
  52. package/api/integration/integrationAdd.doc.mjs +6 -0
  53. package/api/integration/integrationAddDoc.doc.mjs +7 -1
  54. package/api/integration/integrationDocConflicts.doc.mjs +1 -1
  55. package/api/integration/integrationPackCheck.doc.mjs +1 -1
  56. package/api/integration/integrationTemplateConflicts.doc.d.mts +3 -0
  57. package/api/integration/integrationTemplateConflicts.doc.mjs +3 -0
  58. package/api/integration/pack-check.mjs +34 -0
  59. package/api/integration/pack-check.test.mjs +121 -0
  60. package/api/integration/pack-check.type.d.mts +26 -2
  61. package/api/integration/pack-check.type.mjs +14 -1
  62. package/api/integration/template-conflict-compatibility.test.mjs +73 -0
  63. package/api/integration/validate-integration.test.mjs +3 -3
  64. package/api/integration/validateIntegration.doc.mjs +1 -1
  65. package/api/json/index.ts +2 -0
  66. package/api/layout/_adapter.mjs +20 -5
  67. package/api/search/search.d.mts +59 -1
  68. package/api/search/search.doc.mjs +4 -3
  69. package/api/search/search.mjs +442 -71
  70. package/api/search/search.test.mjs +91 -2
  71. package/api/search/search.type.d.mts +13 -1
  72. package/api/search/search.type.mjs +4 -1
  73. package/api/template/list/list.mjs +1 -0
  74. package/api/template/table-floating-bulk-actions.test.mjs +66 -0
  75. package/api/template/template-integration.test.mjs +1072 -3
  76. package/api/template/template.doc.mjs +27 -7
  77. package/api/template/template.mjs +45 -8
  78. package/api/template/template.type.d.mts +6 -8
  79. package/api/template/template.type.mjs +3 -2
  80. package/api/theme/template/template.test.mjs +5 -0
  81. package/api/theme/themeTemplate.doc.mjs +1 -1
  82. package/assets/docs/getting-started.doc.mjs +2 -2
  83. package/assets/docs/layout.doc.dense.mjs +2 -2
  84. package/assets/docs/layout.doc.mjs +1 -1
  85. package/assets/docs/principles.doc.mjs +6 -6
  86. package/assets/docs/styling-libraries.doc.mjs +3 -3
  87. package/assets/docs/styling.doc.mjs +4 -4
  88. package/assets/docs/theme.doc.mjs +4 -4
  89. package/assets/docs/tokens.doc.mjs +1 -1
  90. package/assets/docs/tree/api.doc.mjs +30 -0
  91. package/assets/docs/tree/cli.doc.mjs +23 -0
  92. package/assets/docs/tree/commands.doc.mjs +25 -0
  93. package/assets/docs/{cli-integrations.doc.mjs → tree/integrations.doc.mjs} +37 -9
  94. package/assets/docs/tree/integrations.test.mjs +62 -0
  95. package/assets/docs/tree/writing-docs.doc.mjs +286 -0
  96. package/assets/docs/working-with-ai.doc.mjs +3 -3
  97. package/assets/templates/blocks/components/CheckboxList/CheckboxListSelectAllPattern.doc.mjs +1 -1
  98. package/assets/templates/blocks/components/CheckboxList/CheckboxListSelectAllPattern.tsx +1 -3
  99. package/assets/templates/blocks/components/Table/TableBulkActionsTable.doc.mjs +14 -0
  100. package/assets/templates/blocks/components/Table/TableBulkActionsTable.tsx +71 -0
  101. package/assets/templates/blocks/components/Table/TableFloatingBulkActionsTable.doc.mjs +19 -0
  102. package/assets/templates/blocks/components/Table/TableFloatingBulkActionsTable.tsx +139 -0
  103. package/authoring/debug/parse.d.mts +3 -3
  104. package/authoring/doctypes/_schema.d.mts +119 -117
  105. package/authoring/doctypes/_schema.mjs +54 -3
  106. package/authoring/doctypes/base/graph-fields.doc.mjs +7 -5
  107. package/authoring/doctypes/base/type.ts +6 -5
  108. package/authoring/doctypes/command/command.doc.mjs +1 -1
  109. package/authoring/doctypes/command/type.ts +2 -2
  110. package/authoring/doctypes/enum/enum.doc.mjs +1 -1
  111. package/authoring/doctypes/enum/type.ts +1 -1
  112. package/authoring/doctypes/function/function.doc.mjs +3 -2
  113. package/authoring/doctypes/function/type.ts +3 -2
  114. package/authoring/doctypes/load-contract.test.mjs +26 -1
  115. package/authoring/doctypes/namespace/namespace.doc.mjs +6 -10
  116. package/authoring/doctypes/namespace/parse.test.mjs +23 -25
  117. package/authoring/doctypes/namespace/type.ts +5 -2
  118. package/authoring/doctypes/parse.mjs +3 -2
  119. package/authoring/doctypes/reference/reference.doc.mjs +34 -5
  120. package/authoring/doctypes/reference/type.ts +28 -11
  121. package/authoring/doctypes/schema/schema.doc.mjs +1 -1
  122. package/authoring/doctypes/schema/type.ts +1 -1
  123. package/authoring/doctypes/template/parse.d.mts +2 -0
  124. package/authoring/doctypes/template/parse.mjs +4 -0
  125. package/authoring/doctypes/template/parse.test.mjs +18 -0
  126. package/authoring/doctypes/template/template.doc.mjs +6 -0
  127. package/authoring/doctypes/template/type.ts +8 -0
  128. package/authoring/identity/identity.doc.mjs +2 -2
  129. package/authoring/index.d.ts +1 -0
  130. package/authoring/integration/type.ts +1 -1
  131. package/clients/cli/commands/build-theme.adaptations.test.mjs +100 -1
  132. package/clients/cli/commands/build-theme.mjs +1 -1
  133. package/clients/cli/commands/build.doc.mjs +11 -6
  134. package/clients/cli/commands/build.mjs +105 -67
  135. package/clients/cli/commands/build.text-fields.test.mjs +41 -0
  136. package/clients/cli/commands/component/index.mjs +1 -1
  137. package/clients/cli/commands/component-ownership.test.mjs +3 -3
  138. package/clients/cli/commands/discover.broken-integration.test.mjs +7 -8
  139. package/clients/cli/commands/discover.mjs +1 -1
  140. package/clients/cli/commands/docs.doc.mjs +20 -8
  141. package/clients/cli/commands/docs.mjs +183 -69
  142. package/clients/cli/commands/docs.test.mjs +120 -16
  143. package/clients/cli/commands/doctor-integration-docs.doc.mjs +2 -2
  144. package/clients/cli/commands/doctor-integration-templates.doc.mjs +14 -7
  145. package/clients/cli/commands/doctor-integration.test.mjs +75 -1
  146. package/clients/cli/commands/doctor.mjs +53 -16
  147. package/clients/cli/commands/integration-add.doc.mjs +10 -0
  148. package/clients/cli/commands/integration.mjs +1 -0
  149. package/clients/cli/commands/search.mjs +11 -2
  150. package/clients/cli/commands/setup-nudge.test.mjs +6 -0
  151. package/clients/cli/commands/template.doc.mjs +23 -6
  152. package/clients/cli/commands/template.mjs +4 -91
  153. package/clients/cli/index.mjs +8 -0
  154. package/clients/cli/lib/manifest.mjs +9 -2
  155. package/foundation/agent-docs/agent-docs.d.mts +3 -2
  156. package/foundation/agent-docs/agent-docs.mjs +13 -9
  157. package/foundation/agent-docs/agent-docs.test.mjs +19 -1
  158. package/foundation/config/project.d.mts +12 -11
  159. package/foundation/config/project.mjs +73 -59
  160. package/foundation/config/project.test.mjs +141 -5
  161. package/foundation/discovery/authoring-self-docs.d.mts +12 -0
  162. package/foundation/discovery/authoring-self-docs.mjs +20 -8
  163. package/foundation/discovery/authoring-self-docs.test.mjs +25 -9
  164. package/foundation/discovery/cli-self-docs.d.mts +40 -34
  165. package/foundation/discovery/cli-self-docs.mjs +91 -115
  166. package/foundation/discovery/cli-self-docs.test.mjs +104 -166
  167. package/foundation/discovery/component-discovery.d.mts +38 -0
  168. package/foundation/discovery/component-discovery.mjs +48 -0
  169. package/foundation/discovery/docs-discovery.d.mts +109 -9
  170. package/foundation/discovery/docs-discovery.mjs +223 -24
  171. package/foundation/discovery/docs-discovery.test.mjs +103 -27
  172. package/foundation/discovery/docs-output-budget.d.mts +2 -2
  173. package/foundation/discovery/docs-output-budget.mjs +1 -1
  174. package/foundation/discovery/docs-section-key.d.mts +28 -10
  175. package/foundation/discovery/docs-section-key.mjs +134 -33
  176. package/foundation/discovery/docs-section-key.test.mjs +41 -19
  177. package/foundation/discovery/template-adapter.d.mts +92 -6
  178. package/foundation/discovery/template-adapter.mjs +453 -17
  179. package/foundation/discovery/template-adapter.test.mjs +42 -0
  180. package/foundation/discovery/template-conflict-release.d.mts +13 -0
  181. package/foundation/discovery/template-conflict-release.mjs +40 -0
  182. package/foundation/discovery/template-conflict-release.test.mjs +40 -0
  183. package/foundation/doc-compiler/bundle.mjs +61 -1
  184. package/foundation/doc-compiler/bundle.test.mjs +21 -10
  185. package/foundation/doc-compiler/compile.d.mts +113 -132
  186. package/foundation/doc-compiler/compile.mjs +57 -10
  187. package/foundation/doc-compiler/diagnostics.mjs +28 -0
  188. package/foundation/doc-compiler/doc-loads.test.mjs +2 -2
  189. package/foundation/doc-compiler/inputs.d.mts +2 -2
  190. package/foundation/doc-compiler/inputs.mjs +6 -1
  191. package/foundation/doc-compiler/ir.mjs +7 -0
  192. package/foundation/doc-compiler/lenses.d.mts +8 -5
  193. package/foundation/doc-compiler/lenses.mjs +50 -4
  194. package/foundation/doc-compiler/links.d.mts +162 -0
  195. package/foundation/doc-compiler/links.mjs +294 -0
  196. package/foundation/doc-compiler/links.test.mjs +192 -0
  197. package/foundation/doc-compiler/lower-doc.test.mjs +5 -2
  198. package/foundation/doc-compiler/read.d.mts +3 -2
  199. package/foundation/doc-compiler/read.mjs +6 -1
  200. package/foundation/doc-compiler/tree.d.mts +288 -0
  201. package/foundation/doc-compiler/tree.mjs +876 -0
  202. package/foundation/doc-compiler/tree.test.mjs +598 -0
  203. package/foundation/integrations/autolink.test.mjs +1 -1
  204. package/foundation/integrations/cli-requirement.d.mts +45 -0
  205. package/foundation/integrations/cli-requirement.mjs +154 -0
  206. package/foundation/integrations/cli-requirement.test.mjs +84 -0
  207. package/foundation/integrations/contribution-inventory.d.mts +2 -1
  208. package/foundation/integrations/contribution-inventory.mjs +10 -4
  209. package/foundation/integrations/contribution-inventory.test.mjs +19 -0
  210. package/foundation/integrations/integration-warnings.d.mts +9 -2
  211. package/foundation/integrations/integration-warnings.mjs +51 -26
  212. package/foundation/integrations/integration-warnings.test.mjs +74 -1
  213. package/foundation/integrations/validate-contributions.mjs +25 -27
  214. package/foundation/response/response-types.doc.d.mts +5 -1
  215. package/foundation/response/response-types.doc.mjs +19 -10
  216. package/foundation/text/string-utils.d.mts +8 -0
  217. package/foundation/text/string-utils.mjs +18 -0
  218. package/foundation/xle/expand.mjs +1 -1
  219. package/foundation/xle/xle.test.mjs +13 -0
  220. package/package.json +9 -9
  221. package/assets/docs/cli.doc.mjs +0 -15
@@ -2,9 +2,16 @@
2
2
 
3
3
  /**
4
4
  * @file Command-level coverage for `astryx doctor integration` authoring checks.
5
+ * @input Integration fixtures and the post-0.7 conflict projection.
6
+ * @output Assertions for human and typed command responses.
7
+ * @position CLI adapter coverage; 0.6.x API shape is verified separately.
5
8
  */
6
9
 
7
10
  import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
11
+
12
+ vi.mock('../../../foundation/discovery/template-conflict-release.mjs', () => ({
13
+ expandedTemplateConflictSchemaActive: () => true,
14
+ }));
8
15
  import * as fs from 'node:fs';
9
16
  import * as path from 'node:path';
10
17
  import {Command} from 'commander';
@@ -31,6 +38,8 @@ function writeIntegration({
31
38
  id,
32
39
  name = 'Integration template',
33
40
  missingRoot = false,
41
+ replacements,
42
+ type = 'page',
34
43
  }) {
35
44
  fs.writeFileSync(
36
45
  path.join(tmpDir, 'package.json'),
@@ -45,7 +54,7 @@ function writeIntegration({
45
54
  fs.mkdirSync(path.dirname(stem), {recursive: true});
46
55
  fs.writeFileSync(
47
56
  `${stem}.doc.mjs`,
48
- `export default {type: 'page', name: ${JSON.stringify(name)}, description: 'fixture'};\n`,
57
+ `export default {type: '${type}', name: ${JSON.stringify(name)}, description: 'fixture'${replacements?.[id] == null ? '' : `, replaces: ${JSON.stringify(replacements[id])}`}};\n`,
49
58
  );
50
59
  fs.writeFileSync(
51
60
  `${stem}.tsx`,
@@ -249,6 +258,71 @@ describe('doctor integration — command', () => {
249
258
  expect(process.exitCode).toBeUndefined();
250
259
  });
251
260
 
261
+ it('templates reports an intentional replacement and the Core-original command', async () => {
262
+ const core = (await discoverCoreTemplates()).find(
263
+ template => template.type === 'page',
264
+ );
265
+ expect(core).toBeDefined();
266
+ writeIntegration({
267
+ id: 'acme-app-shell',
268
+ type: core.type,
269
+ replacements: {'acme-app-shell': core.dirName},
270
+ });
271
+ process.chdir(tmpDir);
272
+
273
+ await createProgram().parseAsync([
274
+ 'node',
275
+ 'astryx',
276
+ '--json',
277
+ 'doctor',
278
+ 'integration',
279
+ 'templates',
280
+ ]);
281
+
282
+ const parsed = JSON.parse(logCalls.join('\n'));
283
+ expect(parsed.data.issues).toEqual([]);
284
+ expect(parsed.data.conflicts).toEqual([
285
+ expect.objectContaining({
286
+ id: 'acme-app-shell',
287
+ relationship: 'replaces',
288
+ replaces: core.dirName,
289
+ severity: 'info',
290
+ }),
291
+ ]);
292
+ expect(parsed.data.conflicts[0].command).toContain(
293
+ `--package @astryxdesign/core`,
294
+ );
295
+ expect(process.exitCode).toBeUndefined();
296
+ });
297
+
298
+ it('templates exits 1 for a missing replacement target', async () => {
299
+ writeIntegration({
300
+ id: 'acme-app-shell',
301
+ replacements: {'acme-app-shell': 'missing-core-shell'},
302
+ });
303
+ process.chdir(tmpDir);
304
+
305
+ await createProgram().parseAsync([
306
+ 'node',
307
+ 'astryx',
308
+ '--json',
309
+ 'doctor',
310
+ 'integration',
311
+ 'templates',
312
+ ]);
313
+
314
+ const parsed = JSON.parse(logCalls.join('\n'));
315
+ expect(parsed.data.issues).toEqual(
316
+ expect.arrayContaining([
317
+ expect.objectContaining({
318
+ code: 'missing_template_replacement_target',
319
+ severity: 'error',
320
+ }),
321
+ ]),
322
+ );
323
+ expect(process.exitCode).toBe(1);
324
+ });
325
+
252
326
  it('templates exits 1 when structural errors prevent a trustworthy check', async () => {
253
327
  writeIntegration({missingRoot: true});
254
328
  process.chdir(tmpDir);
@@ -3,7 +3,12 @@
3
3
  /**
4
4
  * @file `astryx doctor` project health and integration-authoring diagnostics.
5
5
  * Human output uses plain stable tokens; every leaf also has a typed JSON
6
- * response. Exit 1 means a check found an error, while warnings remain exit 0.
6
+ * response. Template-conflict fields follow the package release boundary.
7
+ * Exit 1 means a check found an error, while warnings remain exit 0.
8
+ *
9
+ * @input Typed Doctor and integration-authoring API responses.
10
+ * @output Human-readable records or pass-through typed JSON.
11
+ * @position CLI presentation adapter for project and integration health.
7
12
  */
8
13
 
9
14
  import {runChecks} from '../../../api/doctor/doctor.mjs';
@@ -125,25 +130,41 @@ function printTemplateConflicts(data) {
125
130
  ),
126
131
  ...issueBlocks(data.issues),
127
132
  ];
128
- if (data.conflicts.length === 0) {
133
+ if (data.conflicts.length === 0 && data.issues.length === 0) {
129
134
  output.push(text('[ok] No template ids conflict with Core.'));
130
- } else {
135
+ } else if (data.conflicts.length > 0) {
136
+ const expanded = data.conflicts.some(
137
+ conflict => 'relationship' in conflict,
138
+ );
131
139
  output.push(
132
140
  records(data.conflicts, {
133
- fields: [
134
- 'severity',
135
- 'id',
136
- 'integrationPackage',
137
- 'integrationType',
138
- 'integrationName',
139
- 'message',
140
- 'command',
141
- ],
141
+ fields: expanded
142
+ ? [
143
+ 'severity',
144
+ 'relationship',
145
+ 'id',
146
+ 'replaces',
147
+ 'integrationPackage',
148
+ 'integrationType',
149
+ 'integrationName',
150
+ 'message',
151
+ 'command',
152
+ ]
153
+ : [
154
+ 'severity',
155
+ 'id',
156
+ 'integrationPackage',
157
+ 'integrationType',
158
+ 'integrationName',
159
+ 'message',
160
+ 'command',
161
+ ],
142
162
  format: {severity: statusToken},
143
163
  }),
144
164
  text(
145
- `${data.conflicts.length} Core template conflict(s). ` +
146
- 'Renaming is recommended but optional; keep the package-qualified command if the overlap is intentional.',
165
+ expanded
166
+ ? `${data.conflicts.length} Core template relationship(s).`
167
+ : `${data.conflicts.length} Core template conflict(s). Renaming is recommended but optional; keep the package-qualified command if the overlap is intentional.`,
147
168
  ),
148
169
  );
149
170
  }
@@ -165,7 +186,13 @@ function printComponentConflicts(data) {
165
186
  } else {
166
187
  output.push(
167
188
  records(data.conflicts, {
168
- fields: ['severity', 'name', 'integrationPackage', 'message', 'command'],
189
+ fields: [
190
+ 'severity',
191
+ 'name',
192
+ 'integrationPackage',
193
+ 'message',
194
+ 'command',
195
+ ],
169
196
  format: {severity: statusToken},
170
197
  }),
171
198
  text(
@@ -187,6 +214,15 @@ function printDocConflicts(data) {
187
214
  ),
188
215
  ...issueBlocks(data.issues),
189
216
  ];
217
+ if (
218
+ !data.issues.some(
219
+ issue => issue.severity === 'error' || issue.code === 'invalid_doc_graph',
220
+ )
221
+ ) {
222
+ output.push(
223
+ text('[ok] The docs tree and every link in these docs check out.'),
224
+ );
225
+ }
190
226
  if (data.findings.length === 0) {
191
227
  output.push(text('[ok] No doc topics overlap with Core.'));
192
228
  } else {
@@ -272,7 +308,8 @@ async function runAuthoringCheck(program, pkg, kind) {
272
308
  const structuralErrors = summarizeIssues(result.data.issues).errors;
273
309
  const docErrors =
274
310
  result.type === 'integration.doc-conflicts'
275
- ? result.data.findings.filter(finding => finding.severity === 'error').length
311
+ ? result.data.findings.filter(finding => finding.severity === 'error')
312
+ .length
276
313
  : 0;
277
314
  if (structuralErrors > 0 || docErrors > 0) process.exitCode = 1;
278
315
  return NO_RESULT_SET;
@@ -50,6 +50,12 @@ export const doc = {
50
50
  description:
51
51
  'Existing doc topic this one extends (letters, digits, _ and -); only valid for doc and not with --replaces',
52
52
  },
53
+ {
54
+ flag: '--parent <namespace>',
55
+ param: 'options.parent',
56
+ description:
57
+ "Namespace of this package to place the doc in, as a guide in its `guides` slot; writes the namespace doc when it is missing, and declares the CLI that reads it as an optional `@astryxdesign/cli` peer. Only valid for doc, and not with --replaces or --extends",
58
+ },
53
59
  {
54
60
  flag: '--to <version>',
55
61
  param: 'options.to',
@@ -79,6 +85,10 @@ export const doc = {
79
85
  label: 'Extend a doc topic',
80
86
  cli: 'astryx integration add doc acme-theming --extends theme',
81
87
  },
88
+ {
89
+ label: 'Add a guide to the package\'s own docs section',
90
+ cli: 'astryx integration add doc deploying --parent acme',
91
+ },
82
92
  {
83
93
  label: 'Add a codemod',
84
94
  cli: 'astryx integration add codemod rename-widget --to 1.2.0',
@@ -58,6 +58,7 @@ export function registerIntegration(program) {
58
58
  templateType: options.type,
59
59
  replaces: options.replaces,
60
60
  extends: options.extends,
61
+ parent: options.parent,
61
62
  to: options.to,
62
63
  });
63
64
  } catch (error) {
@@ -49,7 +49,7 @@ export function registerSearch(program) {
49
49
 
50
50
  try {
51
51
  const project = await Project.load(process.cwd());
52
- await warnOnIntegrationIssues(project.loadedIntegrations, {json});
52
+ await warnOnIntegrationIssues(project, {json});
53
53
  } catch {
54
54
  // Never let the nudge break the command.
55
55
  }
@@ -115,7 +115,9 @@ export function registerSearch(program) {
115
115
  const fields = options.verbose
116
116
  ? [
117
117
  'name',
118
+ 'section',
118
119
  'domain',
120
+ 'package',
119
121
  'title',
120
122
  'displayName',
121
123
  'kind',
@@ -124,16 +126,20 @@ export function registerSearch(program) {
124
126
  'import',
125
127
  'description',
126
128
  'command',
129
+ 'parent',
127
130
  ]
128
131
  : [
129
132
  'name',
133
+ 'section',
130
134
  'domain',
135
+ 'package',
131
136
  'title',
132
137
  'displayName',
133
138
  'kind',
134
139
  'import',
135
140
  'description',
136
141
  'command',
142
+ 'parent',
137
143
  ];
138
144
 
139
145
  // The heading mirrors the JSON: `matchCount` is what matched, and the
@@ -145,7 +151,10 @@ export function registerSearch(program) {
145
151
  ? `Results for "${q}" (${results.length} of ${matchCount})`
146
152
  : `Results for "${q}" (${results.length})`,
147
153
  ),
148
- records(results, {fields, format: {command: formatCliCommand}}),
154
+ records(results, {
155
+ fields,
156
+ format: {command: formatCliCommand, parent: formatCliCommand},
157
+ }),
149
158
  );
150
159
  return answered;
151
160
  },
@@ -89,6 +89,12 @@ describe('enforcement layer 3 — per-command setup nudge', () => {
89
89
  expect((await runCli(['docs', 'tokens'], tmp)).stderr).not.toMatch(NUDGE);
90
90
  });
91
91
 
92
+ it('is quiet in an integration package, which is not an app', async () => {
93
+ asProject();
94
+ write('astryx.integration.mjs', 'export default {};\n');
95
+ expect((await runCli(['docs', 'tokens'], tmp)).stderr).not.toMatch(NUDGE);
96
+ });
97
+
92
98
  it('does not nudge for the installer command itself', async () => {
93
99
  asProject();
94
100
  expect((await runCli(['init'], tmp)).stderr).not.toMatch(NUDGE);
@@ -18,9 +18,11 @@ export const doc = {
18
18
  description:
19
19
  'One entry point for the template family: with no name it lists the discovered ' +
20
20
  'templates; with a name it shows the source or a layout skeleton, or scaffolds it ' +
21
- 'into the project at a target path. Narrow an ambiguous name with --type and/or --package. ' +
22
- '--cdn writes the no-build-step CDN starter page, which ships as an asset rather than as ' +
23
- 'a discovered template.',
21
+ 'into the project at a target path. A configured integration replacement is the ' +
22
+ 'default for its Core id; use --package @astryxdesign/core for the original. ' +
23
+ 'Narrow other ambiguous names with --type and/or --package. --cdn writes the ' +
24
+ 'no-build-step CDN starter page, which ships as an asset rather than as a ' +
25
+ 'discovered template.',
24
26
  fn: 'template',
25
27
  args: [
26
28
  {
@@ -55,7 +57,8 @@ export const doc = {
55
57
  {
56
58
  flag: '--package <pkg>',
57
59
  param: 'options.package',
58
- description: 'Narrow to templates from a specific package',
60
+ description:
61
+ 'Narrow to templates from a specific package. Use @astryxdesign/core to select an original hidden by an integration replacement.',
59
62
  },
60
63
  {
61
64
  flag: '--skeleton',
@@ -81,15 +84,29 @@ export const doc = {
81
84
  ],
82
85
  examples: [
83
86
  {label: 'List templates', cli: 'astryx template --json'},
87
+ {
88
+ label: 'List exact Core ids',
89
+ cli: 'astryx --json template --list --package @astryxdesign/core',
90
+ },
84
91
  {
85
92
  label: 'Scaffold into the app',
86
93
  cli: 'astryx template dashboard ./src/app',
87
94
  },
95
+ {
96
+ label: 'Select a replaced Core original',
97
+ cli: 'astryx template shell-side-nav ./src/app --package @astryxdesign/core',
98
+ },
88
99
  {label: 'CDN starter page', cli: 'astryx template --cdn'},
89
- {label: 'CDN starter page, elsewhere', cli: 'astryx template --cdn public/demo.html'},
100
+ {
101
+ label: 'CDN starter page, elsewhere',
102
+ cli: 'astryx template --cdn public/demo.html',
103
+ },
90
104
  ],
91
105
  exitCodes: [
92
- {code: 0, when: 'success, including a CDN page left untouched because it already exists'},
106
+ {
107
+ code: 0,
108
+ when: 'success, including a CDN page left untouched because it already exists',
109
+ },
93
110
  {
94
111
  code: 1,
95
112
  when: 'unknown or ambiguous template, no source, a path escape, or an existing target without --overwrite',
@@ -4,12 +4,9 @@
4
4
  * @file template command — thin CLI wrapper around api/template.mjs
5
5
  */
6
6
 
7
- import * as path from 'node:path';
8
- import * as fs from 'node:fs';
9
7
  import {jsonOut} from '../../../foundation/response/json.mjs';
10
8
  import {emit, section, text, records, code} from '../formatters/index.mjs';
11
9
  import {cliError} from '../lib/cli-error.mjs';
12
- import {ERROR_CODES} from '../../../foundation/response/error-codes.mjs';
13
10
  import {template as templateApi} from '../../../api/template/template.mjs';
14
11
  import {Project} from '../../../foundation/config/project.mjs';
15
12
  import {warnOnIntegrationIssues} from '../../../foundation/integrations/integration-warnings.mjs';
@@ -21,18 +18,6 @@ import {doc as templateFn} from '../../../api/template/template.doc.mjs';
21
18
 
22
19
  export {discoverTemplates, listTemplates} from '../../../api/template/template.mjs';
23
20
 
24
- /**
25
- * A template entry as produced by the discover* helpers in api/template.mjs.
26
- * The api layer returns these as `object[]`; this local shape captures the
27
- * fields the command consumes. Remove once api/template.mjs exports a precise
28
- * type for its discovered templates.
29
- * @typedef {object} DiscoveredTemplate
30
- * @property {'page' | 'block'} type
31
- * @property {string} dirName
32
- * @property {string} name
33
- * @property {string} filePath
34
- */
35
-
36
21
  /**
37
22
  * The discriminated response returned by the template API. The api layer
38
23
  * currently declares `{type: string, data: unknown}`; narrow it here to the
@@ -89,36 +74,11 @@ export function registerTemplate(program) {
89
74
  // doctor integration validate. Best-effort; suppressed in --json mode.
90
75
  try {
91
76
  const project = await Project.load(process.cwd());
92
- await warnOnIntegrationIssues(
93
- /** @type {Array<import('../../../foundation/integrations/integrations.mjs').LoadedIntegration>} */ (
94
- project.loadedIntegrations
95
- ),
96
- {json},
97
- );
77
+ await warnOnIntegrationIssues(project, {json});
98
78
  } catch {
99
79
  // Never let the nudge break the command.
100
80
  }
101
81
 
102
- // Pre-flight overwrite check (only when we'd actually copy a file).
103
- // The API resolves the destination; we need to mirror its logic
104
- // narrowly enough to detect collisions before invoking it.
105
- if (
106
- name &&
107
- targetPath &&
108
- !options.list &&
109
- !options.skeleton &&
110
- !options.cdn
111
- ) {
112
- const collision = await detectTemplateCollision(name, targetPath);
113
- if (collision && !options.overwrite) {
114
- const rel = path.relative(process.cwd(), collision) || collision;
115
- const msg =
116
- `Refusing to overwrite existing file ${rel}. ` +
117
- `Re-run with --overwrite (or -f) to replace it.`;
118
- return cliError(msg, {code: ERROR_CODES.ERR_FILE_EXISTS});
119
- }
120
- }
121
-
122
82
  /** @type {TemplateResponse} */
123
83
  let result;
124
84
  try {
@@ -156,12 +116,14 @@ export function registerTemplate(program) {
156
116
  // isn't the built-in core package.
157
117
  /** @param {import('../../../api/template/template.type.mjs').TemplateListEntry} t */
158
118
  const toRow = t => ({
119
+ id: t.id,
159
120
  name: t.isReady ? t.name : `${t.name} (WIP)`,
160
121
  description: t.description,
161
122
  package:
162
123
  t.package && t.package !== '@astryxdesign/core' ? t.package : '',
124
+ replaces: t.replaces ?? '',
163
125
  });
164
- const fields = ['name', 'description', 'package'];
126
+ const fields = ['id', 'name', 'description', 'package', 'replaces'];
165
127
  emit(
166
128
  pages.length > 0 && section('Page Templates'),
167
129
  pages.length > 0 && records(pages.map(toRow), {fields}),
@@ -229,52 +191,3 @@ export function registerTemplate(program) {
229
191
  },
230
192
  });
231
193
  }
232
-
233
- /**
234
- * Mirror the destination-resolution logic in api/template.mjs so we can
235
- * detect a clobber before invoking the API.
236
- *
237
- * Returns the absolute destination file path if it already exists, or
238
- * `null` otherwise (template missing, no collision, or list/skeleton mode).
239
- *
240
- * Path-safety enforcement happens inside the API; here we only need
241
- * enough precision to check existence — a false negative just means the
242
- * API will catch the issue and abort.
243
- *
244
- * @param {string} name
245
- * @param {string} targetPath
246
- * @returns {Promise<string | null>}
247
- */
248
- async function detectTemplateCollision(name, targetPath) {
249
- const {discoverTemplates} = await import('../../../api/template/template.mjs');
250
- /** @type {DiscoveredTemplate[]} */
251
- let templates;
252
- try {
253
- templates = /** @type {DiscoveredTemplate[]} */ (
254
- await discoverTemplates(process.cwd())
255
- );
256
- } catch {
257
- return null;
258
- }
259
- const match = templates.find(t => t.dirName === name);
260
- if (!match) return null;
261
-
262
- // Resolve target as the API will. We can't call assertWithin here
263
- // because the API does it itself; we just need to know "is there a
264
- // file at the destination?".
265
- const resolved = path.resolve(process.cwd(), targetPath);
266
-
267
- // File-arg branch: targetPath looks like `./foo.tsx`.
268
- const {isFilePathArg} = await import('../../../foundation/fs/path-safety.mjs');
269
- let dest;
270
- if (isFilePathArg(targetPath)) {
271
- dest = resolved;
272
- } else {
273
- const fileName = match.type === 'block'
274
- ? path.basename(match.filePath)
275
- : 'page.tsx';
276
- dest = path.join(resolved, fileName);
277
- }
278
-
279
- return fs.existsSync(dest) ? dest : null;
280
- }
@@ -276,6 +276,12 @@ const commands = [
276
276
  ];
277
277
 
278
278
  const SETUP_NUDGE_EXEMPT = new Set(['init', 'agent-docs']);
279
+ /** An integration package's manifest: the package is not an app to set up. */
280
+ const INTEGRATION_MANIFEST_FILES = [
281
+ 'astryx.integration.ts',
282
+ 'astryx.integration.mjs',
283
+ 'astryx.integration.js',
284
+ ];
279
285
 
280
286
  /**
281
287
  * Build a fresh, fully-wired Astryx CLI program (root options, hooks, all
@@ -519,6 +525,8 @@ export async function createProgram() {
519
525
  if (SETUP_NUDGE_EXEMPT.has(actionCommand.name())) return;
520
526
  const cwd = process.cwd();
521
527
  if (!fs.existsSync(path.join(cwd, 'package.json'))) return; // not a project
528
+ // An integration package is not an app: `init` is not its next step.
529
+ if (INTEGRATION_MANIFEST_FILES.some(file => fs.existsSync(path.join(cwd, file)))) return;
522
530
  if (isAstryxInitialized(cwd)) return; // already set up — stay quiet
523
531
  // Same wording as the core/cli postinstall nudges. #4151's getCliInvocation()
524
532
  // renders the correct form for THIS project — scoped `npx @astryxdesign/cli`
@@ -57,7 +57,13 @@ export const RESPONSE_TYPES = {
57
57
  'component.detail.showcase',
58
58
  'component.detail.blocks',
59
59
  ],
60
- docs: ['docs.list', 'docs.index', 'docs.detail', 'docs.detail.section'],
60
+ docs: [
61
+ 'docs.list',
62
+ 'docs.index',
63
+ 'docs.detail',
64
+ 'docs.detail.section',
65
+ 'docs.node',
66
+ ],
61
67
  blog: ['blog.list', 'blog.detail'],
62
68
  discover: [
63
69
  'discover.list',
@@ -110,8 +116,9 @@ const EXAMPLES = {
110
116
  docs: [
111
117
  'astryx docs',
112
118
  'astryx docs spacing --json',
113
- 'astryx docs theme --index',
119
+ 'astryx docs theme',
114
120
  'astryx docs theme quick-start',
121
+ 'astryx docs cli/integrations --full',
115
122
  ],
116
123
  discover: ['astryx discover --json'],
117
124
  search: [
@@ -78,8 +78,9 @@ export function detectStylingSystem(targetDir: string): "stylex" | "tailwind" |
78
78
  * Generate the agent cheat sheet from live CLI metadata.
79
79
  *
80
80
  * Structured as: workflow (behavioral) → rules (error prevention) → CLI reference.
81
- * Templates are positioned first in the workflow to teach agents the
82
- * "look at reference code" reflex before writing any UI.
81
+ * Templates lead the workflow: every page starts from a scaffolded template,
82
+ * because the template already carries the frame and spacing that an agent
83
+ * composing from components would have to re-derive, and usually gets wrong.
83
84
  *
84
85
  * `stylingSystem` tailors the custom-styling guidance to what the project has
85
86
  * configured (see {@link detectStylingSystem}) so the agent never reaches for a
@@ -354,8 +354,9 @@ export function detectStylingSystem(targetDir) {
354
354
  * Generate the agent cheat sheet from live CLI metadata.
355
355
  *
356
356
  * Structured as: workflow (behavioral) → rules (error prevention) → CLI reference.
357
- * Templates are positioned first in the workflow to teach agents the
358
- * "look at reference code" reflex before writing any UI.
357
+ * Templates lead the workflow: every page starts from a scaffolded template,
358
+ * because the template already carries the frame and spacing that an agent
359
+ * composing from components would have to re-derive, and usually gets wrong.
359
360
  *
360
361
  * `stylingSystem` tailors the custom-styling guidance to what the project has
361
362
  * configured (see {@link detectStylingSystem}) so the agent never reaches for a
@@ -428,17 +429,19 @@ export function generateCompressedIndex(
428
429
  lines.push(' import "@astryxdesign/core/astryx.css";');
429
430
  lines.push('');
430
431
 
431
- // Workflow — `build` is the front door; discover before writing UI.
432
- lines.push("WORKFLOW — discover, don't guess. Before writing UI:");
433
- lines.push('1. `astryx build "<idea>"` — START HERE: returns a kit (closest [page] + [block]s + [component]s). No args = full playbook.');
434
- lines.push('2. `astryx template <name> [--skeleton]` — scaffold the [page]/[block]s it named, or study their layout. Templates are reference code.');
435
- lines.push('3. `astryx component <Name>` — props + examples for every component you use.');
432
+ // Workflow — `build` is the front door, and every page starts from the
433
+ // template it names.
434
+ lines.push('WORKFLOW — start every page from a template. Never lay out a page from scratch:');
435
+ lines.push('1. `astryx build "<idea>"` — START HERE: names the [page] template to start from (always one: the closest match, or the app shell), two other templates, and the [block]s + [component]s for parts it lacks. No args = full playbook.');
436
+ lines.push('2. `astryx template <name> <path>` — scaffold that template into your project. Keep its frame, gap and padding; replace its data, copy and sections; delete sections you do not need.');
437
+ lines.push('3. `astryx template <Block>` for a part the template lacks; `astryx component <Name>` for props + examples before you use or change a component.');
438
+ lines.push('Changing a page you already have? Keep it: skip step 2 and add blocks and components inside its sections.');
436
439
  lines.push('');
437
440
 
438
441
  // Rules — the top error-preventers.
439
442
  lines.push('RULES:');
440
443
  lines.push('- No <div> — components do all layout/spacing, page frame included.');
441
- lines.push('- Frame first: read `astryx docs layout` before writing any page or screen — page frame, region widths, breakpoint behavior.');
444
+ lines.push('- Frame first: the template you scaffold sets the page frame. Read `astryx docs layout` before you change it — region widths, breakpoint behavior.');
442
445
  lines.push('- Dense data = rows (Table, List/Item), never Card-wrapped list items; Card is for standalone widgets. Status = StatusDot/Token; Badge = counts only.');
443
446
  // Styling guidance tailored to the project's configured system — never
444
447
  // recommend a path that isn't compiled here (xstyle needs the StyleX compiler;
@@ -463,7 +466,7 @@ export function generateCompressedIndex(
463
466
  css: 'replace any raw <div>/<span> layout, imported .css/@apply, or hardcoded value (#hex, 16px) with the component or a token (var(--color-*|--spacing-*|…))',
464
467
  };
465
468
  lines.push(
466
- `- SELF-CHECK before you finish: re-read the file and ${selfCheckFix[stylingSystem] ?? selfCheckFix.css}. If unsure a component/prop exists, run \`astryx component <Name>\` / \`astryx search "<thing>"\`; don't hand-roll CSS.`,
469
+ `- SELF-CHECK before you finish: re-read the file and ${selfCheckFix[stylingSystem] ?? selfCheckFix.css}. Confirm the page kept its template's frame, gap and padding. If unsure a component/prop exists, run \`astryx component <Name>\` / \`astryx search "<thing>"\`; don't hand-roll CSS.`,
467
470
  );
468
471
  lines.push('');
469
472
 
@@ -486,6 +489,7 @@ export function generateCompressedIndex(
486
489
  if (resolvedTopics.length > 0) {
487
490
  lines.push(` docs <topic> ${resolvedTopics.join(', ')}`);
488
491
  }
492
+ lines.push(' docs cli commands, API reference, integration authoring (one level at a time)');
489
493
  lines.push(' swizzle <Name> eject component source for deep customization');
490
494
  lines.push(' upgrade --apply run after any Astryx or integration dependency bump');
491
495
  const appendCount = agentDocs.reduce(