@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
@@ -10,6 +10,14 @@
10
10
  * @returns {string}
11
11
  */
12
12
  export function whoAnsweredLine(choice: import("./choose.mjs").ReleaseChoice, computer?: boolean): string;
13
+ /**
14
+ * The line for a run this copy answers in place of the project's own CLI,
15
+ * which a host declined, when this copy is that CLI's release (FR2).
16
+ * @param {string} version
17
+ * @param {string} reason the reason, as {@link declinedReasonText} words it
18
+ * @returns {string}
19
+ */
20
+ export function inPlaceOfProjectLine(version: string, reason: string): string;
13
21
  /**
14
22
  * The line for a run handed to the project's own CLI.
15
23
  * @param {string} projectVersion
@@ -41,15 +49,26 @@ export function progressText(event: "install" | "wait", version: string): string
41
49
  * @returns {string}
42
50
  */
43
51
  export function fallbackLine(wanted: string, reason: string, answering: string): string;
52
+ /**
53
+ * The fallback warning's reason for a project CLI a host declined.
54
+ * @param {string} reason the host's own words
55
+ * @returns {string}
56
+ */
57
+ export function declinedReasonText(reason: string): string;
44
58
  /**
45
59
  * Choose who answers and hand off when it is not this copy.
46
- * @param {{cliRoot: string, argv: string[]}} input argv after `node astryx`
47
- * @returns {Promise<void>} resolves when this copy answers
60
+ * @param {{cliRoot: string, argv: string[], declinedProjectCli?: string}} input
61
+ * argv after `node astryx`; the reason a programmatic host declined the
62
+ * project's own CLI, when it did
63
+ * @returns {Promise<import('../../host/index.js').HostedCliResult | undefined>}
64
+ * resolves with nothing when this copy answers, and with that release's
65
+ * result when another package set's host entry answered a hosted run
48
66
  */
49
- export function startRun({ cliRoot, argv }: {
67
+ export function startRun({ cliRoot, argv, declinedProjectCli }: {
50
68
  cliRoot: string;
51
69
  argv: string[];
52
- }): Promise<void>;
70
+ declinedProjectCli?: string;
71
+ }): Promise<import("../../host/index.js").HostedCliResult | undefined>;
53
72
  /**
54
73
  * How long a hand-off waits after an interrupt before passing it on to a CLI
55
74
  * that is still running. An interrupt sent to the whole process group (a
@@ -19,9 +19,26 @@
19
19
  * (notices.mjs); a global copy also starts the daily update check, which
20
20
  * installs nothing (global-state.mjs).
21
21
  *
22
- * @input the running copy, argv, the working folder
23
- * @output returns when this copy answers; otherwise never returns
24
- * @position foundation/release — called once by clients/cli/bin/astryx.mjs.
22
+ * A programmatic host that found the project's own CLI and did not run it
23
+ * passes the reason (spec AST-081 FR1, FR2). That run is never handed to the
24
+ * project's CLI: this copy answers in its place when it is that CLI's release,
25
+ * saying so in a line of its own; a package set of the host's whose CLI is
26
+ * that release answers through its own host entry; and otherwise this copy
27
+ * answers as for a release that cannot be obtained, with the warning naming
28
+ * the reason.
29
+ *
30
+ * A hosted run installs nothing (architecture:cli-surface INV30): a release
31
+ * other than this copy comes only from the first of the host's package sets
32
+ * that qualifies. When that CLI ships a host entry, the entry runs in this
33
+ * process with the same inputs, discover sources included, and the host's
34
+ * call settles with its result; otherwise its bin takes the run. With no such
35
+ * set, this copy answers, announced.
36
+ *
37
+ * @input the running copy, argv, the working folder, and a host's
38
+ * declined-project-CLI reason
39
+ * @output returns when this copy answers, or with another release's result
40
+ * when its host entry answered; never returns after a hand-off to a bin
41
+ * @position foundation/release — called once per run by clients/cli/run.mjs.
25
42
  */
26
43
 
27
44
  import {spawn} from 'node:child_process';
@@ -32,8 +49,10 @@ import {
32
49
  isGlobalCopy,
33
50
  namesWhoAnswered,
34
51
  packageRootOf,
52
+ projectRootOf,
35
53
  runningCopy,
36
54
  } from './choose.mjs';
55
+ import {readReleaseFacts} from './project.mjs';
37
56
  import {ensureRelease, userCacheDir} from './cache.mjs';
38
57
  import {
39
58
  maybeFetchCore,
@@ -43,6 +62,8 @@ import {
43
62
  import {recordRunChoice} from './run-context.mjs';
44
63
  import {noticeLines} from './notices.mjs';
45
64
  import {cliDoor, runWithCliDoor} from '../env/cli-command.mjs';
65
+ import {hostedRun} from '../runtime/package-root.mjs';
66
+ import {qualifyingRoot} from '../runtime/package-set.mjs';
46
67
 
47
68
  /** Where each release's notes are published. */
48
69
 
@@ -63,6 +84,17 @@ export function whoAnsweredLine(choice, computer = false) {
63
84
  : `astryx: answered by CLI ${choice.version} with Core ${core}, outside a project`;
64
85
  }
65
86
 
87
+ /**
88
+ * The line for a run this copy answers in place of the project's own CLI,
89
+ * which a host declined, when this copy is that CLI's release (FR2).
90
+ * @param {string} version
91
+ * @param {string} reason the reason, as {@link declinedReasonText} words it
92
+ * @returns {string}
93
+ */
94
+ export function inPlaceOfProjectLine(version, reason) {
95
+ return `astryx: answered by CLI ${version}, the release of this project's CLI, in its place (${reason})`;
96
+ }
97
+
66
98
  /**
67
99
  * The line for a run handed to the project's own CLI.
68
100
  * @param {string} projectVersion
@@ -131,16 +163,32 @@ function failUnavailable(message, json) {
131
163
  process.exit(1);
132
164
  }
133
165
 
166
+ /**
167
+ * The fallback warning's reason for a project CLI a host declined.
168
+ * @param {string} reason the host's own words
169
+ * @returns {string}
170
+ */
171
+ export function declinedReasonText(reason) {
172
+ return `project CLI skipped: ${reason}`;
173
+ }
174
+
134
175
  /**
135
176
  * Choose who answers and hand off when it is not this copy.
136
- * @param {{cliRoot: string, argv: string[]}} input argv after `node astryx`
137
- * @returns {Promise<void>} resolves when this copy answers
177
+ * @param {{cliRoot: string, argv: string[], declinedProjectCli?: string}} input
178
+ * argv after `node astryx`; the reason a programmatic host declined the
179
+ * project's own CLI, when it did
180
+ * @returns {Promise<import('../../host/index.js').HostedCliResult | undefined>}
181
+ * resolves with nothing when this copy answers, and with that release's
182
+ * result when another package set's host entry answered a hosted run
138
183
  */
139
- export async function startRun({cliRoot, argv}) {
184
+ export async function startRun({cliRoot, argv, declinedProjectCli}) {
140
185
  const json = argv.includes('--json');
141
186
  const self = runningCopy(cliRoot);
142
187
  const cacheDir = userCacheDir();
143
- const global = isGlobalCopy(cliRoot, cacheDir);
188
+ // A host carries its own CLI and keeps it current itself: a hosted copy
189
+ // runs none of the global copy's background work, and installs nothing.
190
+ const hosted = hostedRun();
191
+ const global = !hosted && isGlobalCopy(cliRoot, cacheDir);
144
192
  if (global) {
145
193
  try {
146
194
  maybeStartCheck();
@@ -189,7 +237,46 @@ export async function startRun({cliRoot, argv}) {
189
237
  };
190
238
  }
191
239
 
192
- if (choice.unavailable && choice.copy === 'project') {
240
+ // A host declined the project's own CLI (FR2): the run is never handed to
241
+ // it. The release equal to that CLI's version answers in its place when
242
+ // this copy is that release, or when a package set of the host's holds it
243
+ // with a host entry; otherwise this copy answers, announced as for a release
244
+ // that cannot be obtained, naming the reason.
245
+ let declined = null;
246
+ /** Set when this copy answers in place of the declined CLI, its release. */
247
+ let inPlace = null;
248
+ if (
249
+ declinedProjectCli &&
250
+ choice.copy === 'project' &&
251
+ choice.answer !== 'self'
252
+ ) {
253
+ if (choice.version === self.version) {
254
+ // Still the project CLI's release (`copy` stays `project`), never the
255
+ // release matched to the project's Core, which can be another.
256
+ choice = {...choice, answer: 'self', bin: null, unavailable: null};
257
+ inPlace = declinedReasonText(declinedProjectCli);
258
+ } else {
259
+ const served = hosted
260
+ ? qualifyingRoot({
261
+ release: choice.version,
262
+ projectDir: projectDirOf(process.cwd()),
263
+ roots: hosted.roots,
264
+ needsHostEntry: true,
265
+ })
266
+ : null;
267
+ if (served && 'root' in served && served.hostEntry) {
268
+ const answered = await answerInPackageSet(
269
+ served.hostEntry,
270
+ served.root,
271
+ {argv, declinedProjectCli},
272
+ );
273
+ if (answered) return answered;
274
+ }
275
+ declined = declinedReasonText(declinedProjectCli);
276
+ }
277
+ }
278
+
279
+ if (!declined && choice.unavailable && choice.copy === 'project') {
193
280
  failUnavailable(
194
281
  `This project's CLI ${choice.version} could not be started: ${choice.unavailable}. Reinstall the project's dependencies.`,
195
282
  json,
@@ -245,6 +332,11 @@ export async function startRun({cliRoot, argv}) {
245
332
  });
246
333
  };
247
334
 
335
+ if (declined) {
336
+ await fallBack(declined);
337
+ return;
338
+ }
339
+
248
340
  if (choice.unavailable) {
249
341
  await fallBack(choice.unavailable);
250
342
  return;
@@ -256,6 +348,8 @@ export async function startRun({cliRoot, argv}) {
256
348
  // line; a hint after the command, once a day, says how to match it.
257
349
  if (!json && (choice.copy === 'matched' || choice.copy === 'carried'))
258
350
  console.error(whoAnsweredLine(choice, computer));
351
+ else if (!json && inPlace)
352
+ console.error(inPlaceOfProjectLine(choice.version, inPlace));
259
353
  process.once('exit', code => {
260
354
  if (code === 0) notify();
261
355
  });
@@ -263,6 +357,32 @@ export async function startRun({cliRoot, argv}) {
263
357
  }
264
358
 
265
359
  let bin = choice.bin;
360
+ if (!bin && hosted) {
361
+ const served = qualifyingRoot({
362
+ release: choice.version,
363
+ projectDir: projectDirOf(process.cwd()),
364
+ roots: hosted.roots,
365
+ });
366
+ if (!('root' in served)) {
367
+ await fallBack(served.reason);
368
+ return;
369
+ }
370
+ if (served.hostEntry) {
371
+ const answered = await answerInPackageSet(served.hostEntry, served.root, {
372
+ argv,
373
+ });
374
+ if (answered) return answered;
375
+ }
376
+ // A hosted run installs nothing (INV30): a set whose CLI starts through
377
+ // neither its host entry nor a bin leaves this copy to answer.
378
+ if (!served.bin) {
379
+ await fallBack(
380
+ `the CLI ${choice.version} in the package set at ${served.root} could not be started`,
381
+ );
382
+ return;
383
+ }
384
+ bin = served.bin;
385
+ }
266
386
  if (!bin) {
267
387
  const release = ensureRelease(choice.version, {
268
388
  cacheDir,
@@ -295,6 +415,53 @@ export async function startRun({cliRoot, argv}) {
295
415
  */
296
416
  export const INTERRUPT_GRACE_MS = 5000;
297
417
 
418
+ /**
419
+ * The project folder a run in `cwd` belongs to, or null outside a project.
420
+ * @param {string} cwd
421
+ * @returns {string | null}
422
+ */
423
+ function projectDirOf(cwd) {
424
+ try {
425
+ return projectRootOf(readReleaseFacts(cwd));
426
+ } catch {
427
+ return null;
428
+ }
429
+ }
430
+
431
+ /**
432
+ * Hand a hosted run over to the CLI of one of the host's package sets,
433
+ * through that CLI's own host entry, in this process, with the inputs this run
434
+ * got: the arguments, the folder, the command name, the approved integrations,
435
+ * the discover sources, and the reason the host declined the project's CLI.
436
+ * Its roots are that package set alone. That CLI prints its own who-answered
437
+ * line and notices, and the host's call settles with its result.
438
+ * @param {string} hostEntry the package set's `host/index.mjs`
439
+ * @param {string} root the package set
440
+ * @param {{argv: string[], declinedProjectCli?: string}} run
441
+ * @returns {Promise<import('../../host/index.js').HostedCliResult | null>} null
442
+ * when the entry cannot be loaded, before anything ran
443
+ */
444
+ async function answerInPackageSet(hostEntry, root, {argv, declinedProjectCli}) {
445
+ const hosted = hostedRun();
446
+ if (!hosted?.loadHostEntry) return null;
447
+ let answer;
448
+ try {
449
+ answer = await hosted.loadHostEntry(hostEntry);
450
+ } catch {
451
+ return null;
452
+ }
453
+ const door = cliDoor();
454
+ return answer({
455
+ argv,
456
+ cwd: process.cwd(),
457
+ roots: [root],
458
+ integrations: [...hosted.integrations],
459
+ discoverSources: [...hosted.discoverSources],
460
+ ...(door ? {cliCommand: door} : {}),
461
+ ...(declinedProjectCli ? {declinedProjectCli} : {}),
462
+ });
463
+ }
464
+
298
465
  /**
299
466
  * Run `bin` as this run and end with its exit status.
300
467
  * @param {string} bin
@@ -22,6 +22,7 @@ import {progressLine} from './handoff.mjs';
22
22
  import {isJsonMode} from '../response/json.mjs';
23
23
  import {cachedRelease, ensureRelease, userCacheDir} from './cache.mjs';
24
24
  import {readJson, findReadCore, CORE_PACKAGE_NAME} from './project.mjs';
25
+ import {hostedRun} from '../runtime/package-root.mjs';
25
26
 
26
27
  /** The running CLI package's folder. */
27
28
  const CLI_ROOT = path.resolve(
@@ -105,6 +106,19 @@ export function carriedCore(options = {}) {
105
106
  if (besidePkg?.name === CORE_PACKAGE_NAME && besidePkg.version === version) {
106
107
  return {dir: beside, unavailable: null};
107
108
  }
109
+ // A hosted run resolves from its package set alone and installs nothing
110
+ // (architecture:cli-surface INV30): the Core beside the CLI, or none.
111
+ if (hostedRun()) {
112
+ if (!fetch) return {dir: null, unavailable: null};
113
+ const reason =
114
+ "a hosted run installs nothing, and the host's package set holds no Core beside its CLI";
115
+ return {
116
+ dir: null,
117
+ unavailable: `Core ${version} could not be found: ${reason}`,
118
+ version,
119
+ reason,
120
+ };
121
+ }
108
122
  const cached = cachedRelease(version, cacheDir);
109
123
  if (cached) return {dir: cached.coreDir, unavailable: null};
110
124
  if (!fetch) return {dir: null, unavailable: null};
@@ -338,7 +338,7 @@ export const doc = {
338
338
  {
339
339
  value: 'integration.validate',
340
340
  description:
341
- 'The validation result: validated (false when nothing was validated: with no package.json the issues list carries one no_package error so the command exits 1, and with a package.json but no integration manifest the issues list is empty and proves nothing), the package name and version (both null when validated is false) plus issues, an AstryxIntegrationIssue[] of {code, severity: warning | error, message, fix?, file?, line?} (fix: the change that resolves it; file and line: where, relative to the package), and notes when there are any: {code, message} information that fails nothing, such as invalid_cli_command for a malformed cliCommand. When the manifest loads, docTypes checks each doc against its published authoring type: status (checked | skipped), reason, typescript, docs, findings (all warnings, each naming the field and a fix), unchecked, and fixtures (test fixture docs not checked).',
341
+ 'The validation result: validated (false when nothing was validated: with no package.json the issues list carries one no_package error so the command exits 1, and with a package.json but no integration manifest the issues list is empty and proves nothing), the package name and version (both null when validated is false) plus issues, an AstryxIntegrationIssue[] of {code, severity: warning | error, message, fix?, file?, line?} (fix: the change that resolves it; file and line: where, relative to the package), and notes when there are any: {code, message} information that fails nothing, such as invalid_cli_command for a malformed cliCommand, and the package layout findings added after the last minor release, each with its fix in the message (duplicate_component_doc, symlinked_root, unreadable_file, invalid_package_json, no_provider_id, source_without_component_doc where a doc and its source still pair), and doc_name_mismatch and unknown_block_component, until they become warnings in the next minor release. When the manifest loads, docTypes checks each doc against its published authoring type: status (checked | skipped), reason, typescript, docs, findings (all warnings, each naming the field and a fix), unchecked, and fixtures (test fixture docs not checked). When the manifest loads, docNames lists the component doc names it could not judge: status (checked), findings (always empty; a doc name the module its import names provably does not export is a doc_name_mismatch note, with its fix in the message) and unchecked ({file, name, reason}: a doc whose name differs from the name it is listed under and whose source exports cannot all be read, such as a re-export from another package). When the manifest loads, blockComponents lists the component names templates give in exampleFor or componentsUsed that could not be judged: status (checked), findings (always empty; a name neither the package nor Core has, and the template does not import, is an unknown_block_component note, with its fix in the message) and unchecked ({file, name, reason}: a name the template may reach through anything other than named imports and re-exports from Core, React, StyleX, local helpers or the entry of the package itself, such as an import from another package, a default or namespace import, an alias of or member read off an imported binding, require() or a dynamic import(), or a local module that cannot be read, or any name Core would have to resolve when Core is not installed).',
342
342
  },
343
343
  {
344
344
  value: 'integration.template-conflicts',
@@ -0,0 +1,285 @@
1
+ // @generated by scripts/sync-api-types.mjs from the JSDoc in foundation/**/*.mjs.
2
+ // DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
3
+
4
+ /**
5
+ * @file The small routing helper the commands that pick among candidates
6
+ * share (build, search): two node kinds and the runner that walks them.
7
+ *
8
+ * A searcher answers a request in its command's own result shape, or returns
9
+ * null to pass. A dispatcher scores nothing: it names the children that should
10
+ * answer, best first, with a confidence from 0 to 1. Below its floor, or when
11
+ * none of the children it named answers, its fallback searcher answers. A
12
+ * dispatcher's children may be dispatchers, so trees nest. A classifier is a
13
+ * dispatcher whose route is a static word-weight classifier: the request's
14
+ * tokens vote for a class, and a class with a searcher takes the request when
15
+ * the classifier is sure enough.
16
+ *
17
+ * A tree file types the helpers for its command once, by taking them from
18
+ * `routeKit` typed as `RouteKit<Request, Context, Answer>` (see
19
+ * api/search/tree.mjs); each node is then plain `{id, summary, run}`.
20
+ *
21
+ * Every node gets the same context, so any node can be run, measured, and
22
+ * tuned on its own: `runTree` takes an optional `tuning` with weights that
23
+ * replace a node's shipped weights for one call, and a trace callback that
24
+ * sees every step. Neither ever reaches a command's output.
25
+ *
26
+ * @input a tree of nodes, a request, and the context the command's adapter
27
+ * gathered
28
+ * @output the first answer a searcher gives
29
+ * @position foundation; api/build and api/search build their trees with it
30
+ */
31
+ /**
32
+ * @template Request, Context, Answer
33
+ * @typedef {object} Searcher
34
+ * @property {'searcher'} kind
35
+ * @property {string} id
36
+ * @property {string} summary One plain line: what it answers.
37
+ * @property {(request: Request, ctx: Context, weights: unknown) => Answer | null | Promise<Answer | null>} run
38
+ * `weights` is a tuning override for this node, or null for its shipped weights.
39
+ */
40
+ /**
41
+ * @typedef {object} Route
42
+ * @property {readonly string[]} ids The children to try, best first.
43
+ * @property {number} confidence From 0 to 1.
44
+ */
45
+ /**
46
+ * @template Request, Context
47
+ * @typedef {object} Dispatcher
48
+ * @property {'dispatcher'} kind
49
+ * @property {string} id
50
+ * @property {string} summary
51
+ * @property {readonly string[]} children
52
+ * @property {number} floor Below this confidence the fallback answers.
53
+ * @property {string} fallback A searcher's id.
54
+ * @property {(request: Request, ctx: Context, weights: unknown) => Route | Promise<Route>} route
55
+ */
56
+ /**
57
+ * @template Request, Context, Answer
58
+ * @typedef {Searcher<Request, Context, Answer> | Dispatcher<Request, Context>} TreeNode
59
+ */
60
+ /**
61
+ * @template Request, Context, Answer
62
+ * @typedef {object} Tree
63
+ * @property {string} root
64
+ * @property {ReadonlyMap<string, TreeNode<Request, Context, Answer>>} nodes
65
+ */
66
+ /**
67
+ * One step the runner took.
68
+ * @typedef {object} TraceStep
69
+ * @property {string} id
70
+ * @property {'searcher' | 'dispatcher'} kind
71
+ * @property {number} depth
72
+ * @property {boolean} fallback Whether this step ran as a fallback.
73
+ * @property {Route} [route] A dispatcher's route.
74
+ * @property {boolean} [answered] Whether a searcher answered.
75
+ */
76
+ /**
77
+ * @typedef {object} Tuning
78
+ * @property {Readonly<Record<string, unknown>>} [weights] Weights by node id, replacing that node's shipped weights for this call.
79
+ * @property {(step: TraceStep) => void} [onTrace]
80
+ */
81
+ /**
82
+ * Static classifier weights: one row of numbers per token, one number per
83
+ * class. A request's score for a class is the sum of its tokens' numbers.
84
+ * @typedef {object} ClassifierWeights
85
+ * @property {readonly string[]} classes
86
+ * @property {Readonly<Record<string, readonly number[]>>} tokens
87
+ */
88
+ /**
89
+ * @template Request, Context
90
+ * @typedef {object} ClassifierSpec
91
+ * @property {string} id
92
+ * @property {string} summary
93
+ * @property {Readonly<Record<string, string>>} searchers Class name -> the
94
+ * searcher that answers it. A class with no searcher goes to the fallback.
95
+ * @property {string} fallback The searcher that keeps every other request.
96
+ * @property {number} floor A searcher takes a request only when its class's
97
+ * probability is at least this.
98
+ * @property {(request: Request, ctx: Context) => readonly string[]} features
99
+ * The request's tokens.
100
+ * @property {(ctx: Context) => ClassifierWeights} weights The shipped weights,
101
+ * which the command's adapter put in the context.
102
+ */
103
+ /**
104
+ * The helpers, typed for one command's request, context, and answer.
105
+ * @template Request, Context, Answer
106
+ * @typedef {object} RouteKit
107
+ * @property {(node: Omit<Searcher<Request, Context, Answer>, 'kind'>) => Searcher<Request, Context, Answer>} defineSearcher
108
+ * @property {(node: Omit<Dispatcher<Request, Context>, 'kind'>) => Dispatcher<Request, Context>} defineDispatcher
109
+ * @property {(spec: ClassifierSpec<Request, Context>) => Dispatcher<Request, Context>} defineClassifier
110
+ * @property {(root: string, nodes: readonly TreeNode<Request, Context, Answer>[]) => Tree<Request, Context, Answer>} defineTree
111
+ */
112
+ /**
113
+ * The probability of each class for these tokens: a softmax over the summed
114
+ * rows of the tokens the weights know.
115
+ * @param {ClassifierWeights} weights
116
+ * @param {readonly string[]} tokens
117
+ * @returns {number[]}
118
+ */
119
+ export function classify(weights: ClassifierWeights, tokens: readonly string[]): number[];
120
+ /**
121
+ * A dispatcher whose route is a static classifier. The most probable class
122
+ * (the first on a tie) is handed to its searcher; a class with no searcher,
123
+ * or the fallback's own class, stays with the fallback. Tuning weights for
124
+ * this node replace its shipped weights.
125
+ * @template Request, Context
126
+ * @param {ClassifierSpec<Request, Context>} spec
127
+ * @returns {Dispatcher<Request, Context>}
128
+ */
129
+ export function defineClassifier<Request, Context>(spec: ClassifierSpec<Request, Context>): Dispatcher<Request, Context>;
130
+ /**
131
+ * @template Request, Context, Answer
132
+ * @param {Omit<Searcher<Request, Context, Answer>, 'kind'>} node
133
+ * @returns {Searcher<Request, Context, Answer>}
134
+ */
135
+ export function defineSearcher<Request, Context, Answer>(node: Omit<Searcher<Request, Context, Answer>, "kind">): Searcher<Request, Context, Answer>;
136
+ /**
137
+ * @template Request, Context
138
+ * @param {Omit<Dispatcher<Request, Context>, 'kind'>} node
139
+ * @returns {Dispatcher<Request, Context>}
140
+ */
141
+ export function defineDispatcher<Request, Context>(node: Omit<Dispatcher<Request, Context>, "kind">): Dispatcher<Request, Context>;
142
+ /**
143
+ * A tree from its nodes. Throws on a duplicate id, a missing root, child, or
144
+ * fallback, a fallback that is not a searcher, or a dispatcher cycle.
145
+ * @template Request, Context, Answer
146
+ * @param {string} root
147
+ * @param {readonly TreeNode<Request, Context, Answer>[]} list
148
+ * @returns {Tree<Request, Context, Answer>}
149
+ */
150
+ export function defineTree<Request, Context, Answer>(root: string, list: readonly TreeNode<Request, Context, Answer>[]): Tree<Request, Context, Answer>;
151
+ /**
152
+ * Runs the tree from its root, or from `start` to run one node alone: a
153
+ * dispatcher below its floor runs its fallback; otherwise the children it
154
+ * names run in order and the first answer wins; when none answers, the
155
+ * fallback runs.
156
+ * @template Request, Context, Answer
157
+ * @param {Tree<Request, Context, Answer>} tree
158
+ * @param {Request} request
159
+ * @param {Context} ctx
160
+ * @param {{tuning?: Tuning, start?: string}} [options]
161
+ * @returns {Promise<Answer | null>}
162
+ */
163
+ export function runTree<Request, Context, Answer>(tree: Tree<Request, Context, Answer>, request: Request, ctx: Context, options?: {
164
+ tuning?: Tuning;
165
+ start?: string;
166
+ }): Promise<Answer | null>;
167
+ /**
168
+ * The helpers in one object, for a tree file to type once (see the file
169
+ * comment).
170
+ * @type {RouteKit<any, any, any>}
171
+ */
172
+ export const routeKit: RouteKit<any, any, any>;
173
+ export type Searcher<Request, Context, Answer> = {
174
+ kind: "searcher";
175
+ id: string;
176
+ /**
177
+ * One plain line: what it answers.
178
+ */
179
+ summary: string;
180
+ /**
181
+ * `weights` is a tuning override for this node, or null for its shipped weights.
182
+ */
183
+ run: (request: Request, ctx: Context, weights: unknown) => Answer | null | Promise<Answer | null>;
184
+ };
185
+ export type Route = {
186
+ /**
187
+ * The children to try, best first.
188
+ */
189
+ ids: readonly string[];
190
+ /**
191
+ * From 0 to 1.
192
+ */
193
+ confidence: number;
194
+ };
195
+ export type Dispatcher<Request, Context> = {
196
+ kind: "dispatcher";
197
+ id: string;
198
+ summary: string;
199
+ children: readonly string[];
200
+ /**
201
+ * Below this confidence the fallback answers.
202
+ */
203
+ floor: number;
204
+ /**
205
+ * A searcher's id.
206
+ */
207
+ fallback: string;
208
+ route: (request: Request, ctx: Context, weights: unknown) => Route | Promise<Route>;
209
+ };
210
+ export type TreeNode<Request, Context, Answer> = Searcher<Request, Context, Answer> | Dispatcher<Request, Context>;
211
+ export type Tree<Request, Context, Answer> = {
212
+ root: string;
213
+ nodes: ReadonlyMap<string, TreeNode<Request, Context, Answer>>;
214
+ };
215
+ /**
216
+ * One step the runner took.
217
+ */
218
+ export type TraceStep = {
219
+ id: string;
220
+ kind: "searcher" | "dispatcher";
221
+ depth: number;
222
+ /**
223
+ * Whether this step ran as a fallback.
224
+ */
225
+ fallback: boolean;
226
+ /**
227
+ * A dispatcher's route.
228
+ */
229
+ route?: Route | undefined;
230
+ /**
231
+ * Whether a searcher answered.
232
+ */
233
+ answered?: boolean | undefined;
234
+ };
235
+ export type Tuning = {
236
+ /**
237
+ * Weights by node id, replacing that node's shipped weights for this call.
238
+ */
239
+ weights?: Readonly<Record<string, unknown>> | undefined;
240
+ onTrace?: ((step: TraceStep) => void) | undefined;
241
+ };
242
+ /**
243
+ * Static classifier weights: one row of numbers per token, one number per
244
+ * class. A request's score for a class is the sum of its tokens' numbers.
245
+ */
246
+ export type ClassifierWeights = {
247
+ classes: readonly string[];
248
+ tokens: Readonly<Record<string, readonly number[]>>;
249
+ };
250
+ export type ClassifierSpec<Request, Context> = {
251
+ id: string;
252
+ summary: string;
253
+ /**
254
+ * Class name -> the
255
+ * searcher that answers it. A class with no searcher goes to the fallback.
256
+ */
257
+ searchers: Readonly<Record<string, string>>;
258
+ /**
259
+ * The searcher that keeps every other request.
260
+ */
261
+ fallback: string;
262
+ /**
263
+ * A searcher takes a request only when its class's
264
+ * probability is at least this.
265
+ */
266
+ floor: number;
267
+ /**
268
+ * The request's tokens.
269
+ */
270
+ features: (request: Request, ctx: Context) => readonly string[];
271
+ /**
272
+ * The shipped weights,
273
+ * which the command's adapter put in the context.
274
+ */
275
+ weights: (ctx: Context) => ClassifierWeights;
276
+ };
277
+ /**
278
+ * The helpers, typed for one command's request, context, and answer.
279
+ */
280
+ export type RouteKit<Request, Context, Answer> = {
281
+ defineSearcher: (node: Omit<Searcher<Request, Context, Answer>, "kind">) => Searcher<Request, Context, Answer>;
282
+ defineDispatcher: (node: Omit<Dispatcher<Request, Context>, "kind">) => Dispatcher<Request, Context>;
283
+ defineClassifier: (spec: ClassifierSpec<Request, Context>) => Dispatcher<Request, Context>;
284
+ defineTree: (root: string, nodes: readonly TreeNode<Request, Context, Answer>[]) => Tree<Request, Context, Answer>;
285
+ };