@astryxdesign/cli 0.7.2 → 0.7.3-canary.02f5fe4

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 (373) hide show
  1. package/README.md +104 -71
  2. package/api/build/_adapter.d.mts +58 -0
  3. package/api/build/_adapter.mjs +127 -3
  4. package/api/build/blocks/blocks.d.mts +92 -0
  5. package/api/build/blocks/blocks.mjs +528 -0
  6. package/api/build/blocks/blocks.test.mjs +238 -0
  7. package/api/build/blocks/blocks.weights.json +1 -0
  8. package/api/build/build.mjs +25 -6
  9. package/api/build/build.type.mjs +1 -1
  10. package/api/build/kit/answer.d.mts +18 -0
  11. package/api/build/kit/answer.mjs +195 -0
  12. package/api/build/kit/answer.test.mjs +108 -0
  13. package/api/build/kit/kind.d.mts +25 -0
  14. package/api/build/kit/kind.mjs +170 -0
  15. package/api/build/kit/kit.d.mts +2 -4
  16. package/api/build/kit/kit.mjs +8 -398
  17. package/api/build/kit/rank.d.mts +3 -32
  18. package/api/build/kit/rank.mjs +25 -342
  19. package/api/build/kit/rank.test.mjs +2 -1
  20. package/api/build/kit/signals.d.mts +79 -0
  21. package/api/build/kit/signals.mjs +295 -0
  22. package/api/build/kit/start.d.mts +32 -0
  23. package/api/build/kit/start.mjs +163 -0
  24. package/api/build/kit/words.d.mts +72 -0
  25. package/api/build/kit/words.mjs +202 -0
  26. package/api/build/tree.d.mts +20 -0
  27. package/api/build/tree.mjs +53 -0
  28. package/api/build/tree.test.mjs +185 -0
  29. package/api/build/tree.weights.json +1 -0
  30. package/api/component/_adapter.mjs +39 -5
  31. package/api/component/component-replacement.test.mjs +1 -0
  32. package/api/component/component.mjs +17 -3
  33. package/api/component/component.test.mjs +15 -0
  34. package/api/component/doc-links.test.mjs +2 -2
  35. package/api/discover/_adapter.d.mts +1 -2
  36. package/api/discover/_adapter.mjs +42 -17
  37. package/api/discover/_adapter.test.mjs +42 -0
  38. package/api/discover/discover.mjs +0 -1
  39. package/api/docs/_adapter.d.mts +2 -2
  40. package/api/docs/_adapter.mjs +82 -23
  41. package/api/docs/docs-homes.test.mjs +127 -0
  42. package/api/docs/docs.doc.mjs +3 -3
  43. package/api/docs/docs.test.mjs +35 -19
  44. package/api/docs/integration-tree.test.mjs +20 -20
  45. package/api/docs/integrationDocs.test.mjs +1 -1
  46. package/api/docs/namespace-section.test.mjs +6 -6
  47. package/api/docs/reference-blocks.test.mjs +2 -2
  48. package/api/doctor/_adapter.d.mts +33 -13
  49. package/api/doctor/_adapter.mjs +55 -25
  50. package/api/doctor/doctor.d.mts +23 -5
  51. package/api/doctor/doctor.mjs +183 -40
  52. package/api/doctor/doctor.test.mjs +95 -12
  53. package/api/hook/hook.mjs +1 -1
  54. package/api/init/init.doc.mjs +20 -6
  55. package/api/init/init.test.mjs +18 -2
  56. package/api/init/init.type.d.mts +3 -3
  57. package/api/init/init.type.mjs +3 -3
  58. package/api/init/remove/remove.mjs +17 -4
  59. package/api/init/remove/remove.test.mjs +114 -2
  60. package/api/init/run/run.mjs +69 -6
  61. package/api/integration/_adapter.d.mts +8 -0
  62. package/api/integration/_adapter.mjs +21 -1
  63. package/api/integration/authoring-checks.mjs +83 -9
  64. package/api/integration/authoring-checks.test.mjs +220 -0
  65. package/api/integration/pack-check.mjs +123 -4
  66. package/api/integration/pack-check.test.mjs +87 -0
  67. package/api/integration/validate-integration-case-disk.test.mjs +111 -0
  68. package/api/integration/validate-integration-fixes.test.mjs +6 -5
  69. package/api/integration/validate-integration-gaps.test.mjs +1975 -0
  70. package/api/integration/validate-integration.d.mts +15 -2
  71. package/api/integration/validate-integration.mjs +298 -39
  72. package/api/integration/validate-integration.type.d.mts +90 -1
  73. package/api/integration/validate-integration.type.mjs +67 -2
  74. package/api/integration/validateIntegration.doc.mjs +1 -1
  75. package/api/search/ORDERING.md +28 -0
  76. package/api/search/_adapter.d.mts +82 -2
  77. package/api/search/_adapter.mjs +157 -10
  78. package/api/search/_adapter.test.mjs +173 -0
  79. package/api/search/_candidate-score.d.mts +40 -0
  80. package/api/search/_candidate-score.mjs +238 -0
  81. package/api/search/_cards.json +6098 -0
  82. package/api/search/_compound.d.mts +50 -0
  83. package/api/search/_compound.mjs +170 -0
  84. package/api/search/_doc-searcher.d.mts +4 -0
  85. package/api/search/_doc-searcher.mjs +23 -0
  86. package/api/search/_doc-searcher.test.mjs +36 -0
  87. package/api/search/_doc-searcher.weights.json +1 -0
  88. package/api/search/_doc-text.d.mts +30 -0
  89. package/api/search/_doc-text.mjs +87 -0
  90. package/api/search/_entries.d.mts +63 -0
  91. package/api/search/_entries.mjs +232 -0
  92. package/api/search/_entries.test.mjs +48 -0
  93. package/api/search/_features.d.mts +35 -0
  94. package/api/search/_features.mjs +155 -0
  95. package/api/search/_gather-components.d.mts +26 -0
  96. package/api/search/_gather-components.mjs +284 -0
  97. package/api/search/_gather-docs.d.mts +20 -0
  98. package/api/search/_gather-docs.mjs +254 -0
  99. package/api/search/_gather-hooks.d.mts +12 -0
  100. package/api/search/_gather-hooks.mjs +52 -0
  101. package/api/search/_gather-templates.d.mts +25 -0
  102. package/api/search/_gather-templates.mjs +107 -0
  103. package/api/search/_lexical.d.mts +135 -0
  104. package/api/search/_lexical.mjs +351 -0
  105. package/api/search/_main-searcher.d.mts +19 -0
  106. package/api/search/_main-searcher.mjs +115 -0
  107. package/api/search/_main-searcher.test.mjs +106 -0
  108. package/api/search/_main-searcher.weights.json +1 -0
  109. package/api/search/_module-doc.d.mts +32 -0
  110. package/api/search/_module-doc.mjs +78 -0
  111. package/api/search/_order.d.mts +32 -0
  112. package/api/search/_order.mjs +162 -0
  113. package/api/search/_order.test.mjs +72 -0
  114. package/api/search/_priority.d.mts +50 -0
  115. package/api/search/_priority.mjs +139 -0
  116. package/api/search/_query-words.d.mts +25 -0
  117. package/api/search/_query-words.mjs +92 -0
  118. package/api/search/_query.d.mts +65 -0
  119. package/api/search/_query.mjs +176 -0
  120. package/api/search/_result.d.mts +97 -0
  121. package/api/search/_result.mjs +150 -0
  122. package/api/search/_score.d.mts +60 -0
  123. package/api/search/_score.mjs +283 -0
  124. package/api/search/_synonyms.d.mts +21 -0
  125. package/api/search/_synonyms.mjs +67 -0
  126. package/api/search/_template-searcher.d.mts +4 -0
  127. package/api/search/_template-searcher.mjs +23 -0
  128. package/api/search/_template-searcher.test.mjs +40 -0
  129. package/api/search/_template-searcher.weights.json +1 -0
  130. package/api/search/_titles.d.mts +33 -0
  131. package/api/search/_titles.mjs +65 -0
  132. package/api/search/_words.d.mts +64 -0
  133. package/api/search/_words.mjs +157 -0
  134. package/api/search/search.d.mts +22 -305
  135. package/api/search/search.doc.mjs +8 -6
  136. package/api/search/search.mjs +56 -2482
  137. package/api/search/search.test.mjs +14 -14
  138. package/api/search/search.type.d.mts +6 -1
  139. package/api/search/search.type.mjs +6 -1
  140. package/api/search/tree.d.mts +15 -0
  141. package/api/search/tree.mjs +40 -0
  142. package/api/search/tree.test.mjs +66 -0
  143. package/api/search/tree.weights.json +1 -0
  144. package/api/template/template.mjs +5 -1
  145. package/api/theme/build/build.packed-old-core.test.mjs +1 -0
  146. package/api/upgrade/_adapter.d.mts +57 -9
  147. package/api/upgrade/_adapter.mjs +198 -28
  148. package/api/upgrade/registry/registry.d.mts +10 -4
  149. package/api/upgrade/registry/registry.mjs +33 -16
  150. package/api/upgrade/registry/registry.test.mjs +85 -0
  151. package/api/upgrade/run/run.d.mts +4 -2
  152. package/api/upgrade/run/run.mjs +13 -2
  153. package/api/upgrade/run/run.test.mjs +417 -3
  154. package/api/upgrade/upgrade.doc.mjs +10 -5
  155. package/api/upgrade/upgrade.mjs +13 -2
  156. package/api/upgrade/upgrade.test.mjs +194 -0
  157. package/api/upgrade/upgrade.type.d.mts +5 -3
  158. package/api/upgrade/upgrade.type.mjs +8 -6
  159. package/assets/codemods/transforms/v0.7.0/__tests__/validate-data-token-ownership.test.mjs +11 -0
  160. package/assets/codemods/transforms/v0.7.0/validate-data-token-ownership.mjs +3 -3
  161. package/assets/docs/author-a-theme.doc.mjs +2 -2
  162. package/assets/docs/is-my-code-good.doc.mjs +287 -0
  163. package/assets/docs/tree/add-a-component.doc.mjs +1 -1
  164. package/assets/docs/tree/add-a-theme.doc.mjs +1 -1
  165. package/assets/docs/tree/add-a-topic.doc.mjs +2 -2
  166. package/assets/docs/tree/agent-guidance.doc.mjs +1 -1
  167. package/assets/docs/tree/api.doc.mjs +6 -0
  168. package/assets/docs/tree/{integrations.doc.mjs → authoring.doc.mjs} +15 -6
  169. package/assets/docs/tree/block-template.doc.mjs +1 -1
  170. package/assets/docs/tree/build-the-template.doc.mjs +1 -1
  171. package/assets/docs/tree/building-blocks.doc.mjs +2 -2
  172. package/assets/docs/tree/check-the-props.doc.mjs +1 -1
  173. package/assets/docs/tree/check-your-docs.doc.mjs +2 -2
  174. package/assets/docs/tree/checks.doc.mjs +3 -3
  175. package/assets/docs/tree/ci-and-containers.doc.mjs +13 -0
  176. package/assets/docs/tree/cli-command.doc.mjs +1 -1
  177. package/assets/docs/tree/cli.doc.mjs +1 -1
  178. package/assets/docs/tree/codemods.doc.mjs +1 -1
  179. package/assets/docs/tree/component-family.doc.mjs +1 -1
  180. package/assets/docs/tree/component-imports.doc.mjs +1 -1
  181. package/assets/docs/tree/components.doc.mjs +1 -1
  182. package/assets/docs/tree/configuration.doc.mjs +1 -1
  183. package/assets/docs/tree/debug-and-gap-reports.doc.mjs +1 -1
  184. package/assets/docs/tree/define-the-theme.doc.mjs +1 -1
  185. package/assets/docs/tree/describe-the-component.doc.mjs +1 -1
  186. package/assets/docs/tree/docs.doc.mjs +1 -1
  187. package/assets/docs/tree/document-the-template.doc.mjs +1 -1
  188. package/assets/docs/tree/document-the-theme.doc.mjs +1 -1
  189. package/assets/docs/tree/export-template-assets.doc.mjs +1 -1
  190. package/assets/docs/tree/extend-or-replace.doc.mjs +1 -1
  191. package/assets/docs/{authoring.doc.mjs → tree/field-reference.doc.mjs} +4 -3
  192. package/assets/docs/tree/fonts-and-assets.doc.mjs +1 -1
  193. package/assets/docs/tree/generate-a-palette.doc.mjs +1 -1
  194. package/assets/docs/tree/global-integrations.doc.mjs +2 -2
  195. package/assets/docs/tree/grade-template-with-agent.doc.mjs +4 -4
  196. package/assets/docs/tree/help.doc.mjs +2 -2
  197. package/assets/docs/tree/host-entry.doc.mjs +128 -0
  198. package/assets/docs/tree/links.doc.mjs +1 -1
  199. package/assets/docs/tree/package-and-test.doc.mjs +1 -1
  200. package/assets/docs/tree/page-template.doc.mjs +1 -1
  201. package/assets/docs/tree/publishing.doc.mjs +1 -1
  202. package/assets/docs/tree/quick-start.doc.mjs +1 -1
  203. package/assets/docs/tree/replace-a-core-component.doc.mjs +1 -1
  204. package/assets/docs/tree/replace-a-core-template.doc.mjs +1 -1
  205. package/assets/docs/tree/sections-and-placement.doc.mjs +1 -1
  206. package/assets/docs/tree/see-it-in-an-app.doc.mjs +1 -1
  207. package/assets/docs/tree/ship.doc.mjs +2 -2
  208. package/assets/docs/tree/short-and-findable.doc.mjs +1 -1
  209. package/assets/docs/tree/single-component.doc.mjs +1 -1
  210. package/assets/docs/tree/start-a-template.doc.mjs +1 -1
  211. package/assets/docs/tree/subcomponent.doc.mjs +1 -1
  212. package/assets/docs/tree/template-assets.doc.mjs +1 -1
  213. package/assets/docs/tree/template-doc-overview.doc.mjs +2 -2
  214. package/assets/docs/tree/template-fonts.doc.mjs +1 -1
  215. package/assets/docs/tree/template-grading-rubric.doc.mjs +17 -38
  216. package/assets/docs/tree/template-icons.doc.mjs +1 -1
  217. package/assets/docs/tree/template-images-media.doc.mjs +1 -1
  218. package/assets/docs/tree/template-styles.doc.mjs +1 -1
  219. package/assets/docs/tree/templates.doc.mjs +1 -1
  220. package/assets/docs/tree/test-in-an-app.doc.mjs +1 -1
  221. package/assets/docs/tree/test-template-in-app.doc.mjs +1 -1
  222. package/assets/docs/tree/themes.doc.mjs +1 -1
  223. package/assets/docs/tree/tokens-data.doc.mjs +2 -2
  224. package/assets/docs/tree/troubleshooting.doc.mjs +1 -1
  225. package/assets/docs/tree/upgrading.doc.mjs +1 -1
  226. package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +1 -1
  227. package/assets/docs/tree/verify-packed-template.doc.mjs +1 -1
  228. package/assets/docs/tree/versioning.doc.mjs +5 -1
  229. package/assets/docs/tree/write-good-templates.doc.mjs +1 -1
  230. package/assets/docs/tree/write-the-template-file.doc.mjs +1 -1
  231. package/assets/docs/use-integrations.doc.mjs +120 -0
  232. package/assets/docs/working-with-ai.doc.mjs +6 -2
  233. package/assets/templates/themes/butter/butterTheme.ts +4 -2
  234. package/assets/templates/themes/stone/stoneTheme.ts +4 -2
  235. package/assets/templates/themes/y2k/y2kTheme.ts +4 -2
  236. package/authoring/debug/parse.d.mts +1 -1
  237. package/authoring/doctypes/schema/schema.doc.mjs +1 -1
  238. package/authoring/integration/integration.doc.mjs +1 -1
  239. package/authoring/integration/type.ts +1 -1
  240. package/clients/cli/bin/astryx.mjs +7 -78
  241. package/clients/cli/command-result-coverage.test.mjs +6 -5
  242. package/clients/cli/commands/build-theme.color-scheme.test.mjs +1 -1
  243. package/clients/cli/commands/{component/index.mjs → component.mjs} +37 -37
  244. package/clients/cli/commands/component.test.mjs +6 -6
  245. package/clients/cli/commands/docs.doc.mjs +2 -2
  246. package/clients/cli/commands/docs.mjs +1 -1
  247. package/clients/cli/commands/docs.test.mjs +14 -14
  248. package/clients/cli/commands/doctor-integration.test.mjs +49 -1
  249. package/clients/cli/commands/doctor.doc.mjs +1 -1
  250. package/clients/cli/commands/doctor.mjs +40 -0
  251. package/clients/cli/commands/doctor.test.mjs +65 -0
  252. package/clients/cli/commands/{hook/index.mjs → hook.mjs} +22 -22
  253. package/clients/cli/commands/import-hint-correctness.test.mjs +1 -1
  254. package/clients/cli/commands/init.behavior.test.mjs +230 -1
  255. package/clients/cli/commands/init.doc.mjs +28 -9
  256. package/clients/cli/commands/init.flag-help.test.mjs +1 -0
  257. package/clients/cli/commands/init.mjs +2 -2
  258. package/clients/cli/commands/integration-check.test.mjs +48 -0
  259. package/clients/cli/commands/integration.doc.mjs +1 -1
  260. package/clients/cli/commands/manifest.doc.mjs +1 -1
  261. package/clients/cli/commands/manifest.mjs +67 -0
  262. package/clients/cli/commands/outside-project.test.mjs +101 -1
  263. package/clients/cli/commands/postinstall.doc.mjs +23 -0
  264. package/clients/cli/commands/postinstall.mjs +31 -0
  265. package/clients/cli/commands/setup-nudge.test.mjs +78 -0
  266. package/clients/cli/commands/{build-theme.mjs → theme.mjs} +1 -1
  267. package/clients/cli/commands/upgrade.doc.mjs +4 -2
  268. package/clients/cli/commands/upgrade.mjs +9 -2
  269. package/clients/cli/commands/upgrade.subfolder-path.test.mjs +97 -0
  270. package/clients/cli/index.mjs +54 -94
  271. package/clients/cli/lib/component-format.mjs +1 -1
  272. package/clients/cli/lib/define-command.mjs +12 -7
  273. package/clients/cli/lib/exit-codes.test.mjs +5 -0
  274. package/clients/cli/lib/manifest.test.mjs +12 -0
  275. package/clients/cli/lib/welcome-box.mjs +50 -0
  276. package/clients/cli/run.mjs +108 -0
  277. package/foundation/agent-docs/agent-doc-state.d.mts +32 -7
  278. package/foundation/agent-docs/agent-doc-state.mjs +58 -9
  279. package/foundation/agent-docs/agent-docs.d.mts +100 -12
  280. package/foundation/agent-docs/agent-docs.mjs +1017 -105
  281. package/foundation/agent-docs/agent-docs.path-safety.test.mjs +1239 -17
  282. package/foundation/agent-docs/agent-docs.test.mjs +683 -39
  283. package/foundation/agent-docs/computer-agent-text.d.mts +39 -2
  284. package/foundation/agent-docs/computer-agent-text.mjs +135 -8
  285. package/foundation/agent-docs/computer-agent-text.test.mjs +99 -2
  286. package/foundation/config/project.mjs +20 -22
  287. package/foundation/config/project.test.mjs +56 -0
  288. package/foundation/discovery/authoring-self-docs.d.mts +7 -0
  289. package/foundation/discovery/authoring-self-docs.mjs +16 -4
  290. package/foundation/discovery/authoring-self-docs.test.mjs +15 -3
  291. package/foundation/discovery/authoring-surface.d.mts +1 -1
  292. package/foundation/discovery/authoring-surface.mjs +2 -2
  293. package/foundation/discovery/authoring-surface.test.mjs +1 -1
  294. package/foundation/discovery/cli-self-docs.d.mts +2 -1
  295. package/foundation/discovery/cli-self-docs.mjs +4 -3
  296. package/foundation/discovery/cli-self-docs.test.mjs +2 -2
  297. package/foundation/discovery/component-discovery.d.mts +37 -0
  298. package/foundation/discovery/component-discovery.mjs +80 -5
  299. package/foundation/discovery/component-loader.d.mts +1 -1
  300. package/foundation/discovery/docs-discovery.mjs +14 -1
  301. package/foundation/discovery/module-exports.d.mts +41 -0
  302. package/foundation/discovery/module-exports.mjs +253 -0
  303. package/foundation/discovery/runner-allowlist.d.mts +2 -4
  304. package/foundation/discovery/runner-allowlist.mjs +2 -4
  305. package/foundation/discovery/runner-commands.d.mts +77 -0
  306. package/foundation/discovery/runner-commands.mjs +294 -0
  307. package/foundation/discovery/runner-commands.test.mjs +128 -0
  308. package/foundation/discovery/stray-pointers.test.mjs +1 -1
  309. package/foundation/discovery/template-adapter.mjs +18 -1
  310. package/foundation/discovery/template-needs.mjs +1 -44
  311. package/foundation/discovery/theme-discovery.mjs +147 -3
  312. package/foundation/doc-compiler/doc-loads.test.mjs +33 -9
  313. package/foundation/doc-compiler/read.mjs +59 -1
  314. package/foundation/doc-compiler/read.test.mjs +11 -2
  315. package/foundation/doc-compiler/split-topic-keywords.test.mjs +3 -2
  316. package/foundation/doc-compiler/tree.d.mts +32 -6
  317. package/foundation/doc-compiler/tree.mjs +77 -43
  318. package/foundation/doc-compiler/tree.test.mjs +7 -7
  319. package/foundation/fs/paths.d.mts +70 -8
  320. package/foundation/fs/paths.mjs +556 -38
  321. package/foundation/fs/paths.test.mjs +430 -54
  322. package/foundation/fs/read-record.d.mts +18 -0
  323. package/foundation/fs/read-record.mjs +131 -0
  324. package/foundation/fs/read-record.test.mjs +77 -0
  325. package/foundation/integrations/autolink.d.mts +2 -1
  326. package/foundation/integrations/autolink.mjs +4 -13
  327. package/foundation/integrations/cli-peer-version.d.mts +36 -0
  328. package/foundation/integrations/cli-peer-version.mjs +132 -0
  329. package/foundation/integrations/cli-peer-version.test.mjs +354 -0
  330. package/foundation/integrations/computer-integrations.d.mts +6 -3
  331. package/foundation/integrations/computer-integrations.mjs +38 -9
  332. package/foundation/integrations/contribution-inventory.d.mts +15 -20
  333. package/foundation/integrations/contribution-inventory.mjs +41 -5
  334. package/foundation/integrations/exports-subpaths.d.mts +57 -0
  335. package/foundation/integrations/exports-subpaths.mjs +113 -0
  336. package/foundation/integrations/integrations.d.mts +27 -1
  337. package/foundation/integrations/integrations.mjs +61 -2
  338. package/foundation/integrations/public-imports.d.mts +118 -0
  339. package/foundation/integrations/public-imports.mjs +1769 -0
  340. package/foundation/integrations/public-imports.test.mjs +1793 -0
  341. package/foundation/integrations/validate-contributions.d.mts +115 -0
  342. package/foundation/integrations/validate-contributions.mjs +1168 -13
  343. package/foundation/release/cache.test.mjs +24 -21
  344. package/foundation/release/global-state.d.mts +8 -4
  345. package/foundation/release/global-state.mjs +4 -2
  346. package/foundation/release/handoff.d.mts +23 -4
  347. package/foundation/release/handoff.mjs +175 -8
  348. package/foundation/release/notices.d.mts +62 -8
  349. package/foundation/release/notices.mjs +136 -25
  350. package/foundation/release/notices.test.mjs +21 -0
  351. package/foundation/release/run-context.mjs +14 -0
  352. package/foundation/response/response-types.doc.mjs +1 -1
  353. package/foundation/route/route.d.mts +285 -0
  354. package/foundation/route/route.mjs +324 -0
  355. package/foundation/route/route.test.mjs +153 -0
  356. package/foundation/runtime/hosted-runtime.test.mjs +403 -0
  357. package/foundation/runtime/locked-versions.d.mts +32 -0
  358. package/foundation/runtime/locked-versions.mjs +65 -0
  359. package/foundation/runtime/package-root.d.mts +91 -0
  360. package/foundation/runtime/package-root.mjs +174 -0
  361. package/foundation/runtime/package-set.d.mts +62 -0
  362. package/foundation/runtime/package-set.mjs +130 -0
  363. package/foundation/runtime/package-set.test.mjs +432 -0
  364. package/foundation/text/string-utils.d.mts +2 -1
  365. package/foundation/text/string-utils.mjs +16 -5
  366. package/foundation/text/suggest-order.d.mts +72 -0
  367. package/foundation/text/suggest-order.mjs +363 -0
  368. package/foundation/text/suggest-order.test.mjs +259 -0
  369. package/host/door-conformance.test.mjs +274 -0
  370. package/host/index.d.ts +116 -0
  371. package/host/index.mjs +295 -0
  372. package/host/index.test.mjs +302 -0
  373. package/package.json +14 -9
@@ -0,0 +1,139 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Domain priority: when a component, hook, template, or theme ranks
5
+ * ahead of docs the reader did not ask for.
6
+ */
7
+
8
+ import {sameWord} from './_words.mjs';
9
+ import {phraseWords, samePhraseWord, samePhrase} from './_query.mjs';
10
+ import {compoundHit, compoundOf} from './_compound.mjs';
11
+ import {
12
+ bestForToken,
13
+ FULL_COVERAGE_SCORE,
14
+ STRONG_TOKEN_SCORE,
15
+ } from './_score.mjs';
16
+ import {TITLE_PHRASE_SCORE, TITLE_IN_QUERY_SCORE} from './_titles.mjs';
17
+ import {scoreCandidate} from './_candidate-score.mjs';
18
+
19
+ /** @typedef {import('./_lexical.mjs').Candidate} Candidate */
20
+
21
+ /**
22
+ * A match's domain priority: 0 (none), 1, or 2 (a doc the whole query names).
23
+ * A component, hook, template, or theme with priority ranks ahead of any doc
24
+ * without it, whatever the two scores are. Docs among themselves rank by
25
+ * priority, then score; everything else ranks among itself by score.
26
+ *
27
+ * A component, hook, template, or theme has priority (1) when one of the
28
+ * query's words, or the whole query, hits its name or an authored keyword at
29
+ * {@link STRONG_TOKEN_SCORE} or above, or when it matches every word.
30
+ *
31
+ * A doc has priority when the reader asked for that doc:
32
+ * - 2: the whole query is the topic's name, its last route segment, or its
33
+ * own title, as words and whatever the plural (`font setup` is
34
+ * typography/font-setup, `side panel` is "Side panels");
35
+ * - 1: a word of the query is the topic's name (`illustration` in a longer
36
+ * question is the illustrations guide);
37
+ * - 1: a query of two or more words is one of the topic's own declared
38
+ * keywords, whatever the plural (`code review` on a guide that declares it);
39
+ * - 1: the query holds one of the doc's titles whole (`resizable side panels`
40
+ * names "Side panels"; `light dark mode button` names "Light/Dark Mode");
41
+ * - 1: the doc matches every word of the query and one of them is a word of
42
+ * its title (`switch to a dark theme` and "Use a theme").
43
+ * A section answers to its topic's name.
44
+ *
45
+ * A doc that matched only by a heading holding the query, by one word, or by
46
+ * words spread through its text has no priority. Docs are split into many
47
+ * small topics, each with its own headings, so a common phrase such as
48
+ * `font size` hits a heading exactly (170) while the component the reader is
49
+ * after matches one word by keyword (`Text`, 98). Ranked on text alone, every
50
+ * split adds another doc above the component. Broad reference pages match every word of many queries
51
+ * in their text the same way. The score stays the text-match strength the
52
+ * result reports, so callers that gate on it (`build`) see the same numbers.
53
+ *
54
+ * @param {string} term - Lowercased search term.
55
+ * @param {string[]} tokens - Content tokens from tokenizeQuery(term).
56
+ * @param {Candidate} candidate
57
+ * @param {{score: number, matched: number, total: number}} hit - The
58
+ * candidate's scoreQuery result.
59
+ * @returns {0 | 1 | 2}
60
+ */
61
+ export function domainPriority(
62
+ term,
63
+ tokens,
64
+ candidate,
65
+ hit,
66
+ queryWords = phraseWords(term),
67
+ ) {
68
+ const words = [term, ...tokens];
69
+ if (candidate.domain === 'doc') {
70
+ const topic = String(candidate._topic ?? candidate.name).toLowerCase();
71
+ // The whole query, read as words, is the topic's route, its last
72
+ // segment, or its own title, whatever the plural: `side panel` is
73
+ // layout/side-panels, and `header and footer` is "Headers and footers".
74
+ // A section's title is a heading inside a topic, not the topic's name.
75
+ const named = [topic, topic.slice(topic.lastIndexOf('/') + 1)];
76
+ if (candidate._section == null && candidate.titles?.[0]) {
77
+ named.push(candidate.titles[0]);
78
+ }
79
+ if (named.some(n => samePhrase(term, n))) {
80
+ return 2;
81
+ }
82
+ // A query of two or more words that is one of the doc's own declared
83
+ // keywords, read as words whatever the plural, is the author naming the
84
+ // doc for that phrase: `code review` on a guide that declares it. Only
85
+ // what the author declared counts: the code terms and table cells a
86
+ // doc's other keywords add (`missing_field`) name what it mentions, not
87
+ // what it is for. One word is left out too, and a section declares
88
+ // nothing.
89
+ if (
90
+ phraseWords(term).length >= 2 &&
91
+ candidate._section == null &&
92
+ (candidate._declared ?? []).some(k => samePhrase(term, String(k)))
93
+ ) {
94
+ return 1;
95
+ }
96
+ if (
97
+ words.some(
98
+ w =>
99
+ (scoreCandidate(w, {name: topic}, {fuzzy: false})?.score ?? 0) >= 95,
100
+ )
101
+ ) {
102
+ return 1;
103
+ }
104
+ // A whole title inside the query is the only path to 160-169.
105
+ if (hit.score >= TITLE_IN_QUERY_SCORE && hit.score < TITLE_PHRASE_SCORE) {
106
+ return 1;
107
+ }
108
+ // Every word matched, and one of them names the doc in its title.
109
+ if (
110
+ hit.score >= FULL_COVERAGE_SCORE &&
111
+ hit.score < TITLE_IN_QUERY_SCORE &&
112
+ hit.matched === hit.total
113
+ ) {
114
+ const titleWords = phraseWords(candidate.titles?.[0] ?? '');
115
+ if (
116
+ (tokens.length ? tokens : [term]).some(t =>
117
+ titleWords.some(w => samePhraseWord(t, w) || sameWord(t, w)),
118
+ )
119
+ ) {
120
+ return 1;
121
+ }
122
+ }
123
+ return 0;
124
+ }
125
+ if (hit.score >= FULL_COVERAGE_SCORE) return 1;
126
+ const compound = compoundOf.has(candidate)
127
+ ? compoundOf.get(candidate)
128
+ : compoundHit(queryWords, candidate);
129
+ if ((compound?.score ?? 0) >= STRONG_TOKEN_SCORE) {
130
+ return 1;
131
+ }
132
+ const fuzzy = tokens.length <= 1;
133
+ return words.some(
134
+ w =>
135
+ (bestForToken(w, candidate, {fuzzy})?.score ?? 0) >= STRONG_TOKEN_SCORE,
136
+ )
137
+ ? 1
138
+ : 0;
139
+ }
@@ -0,0 +1,25 @@
1
+ // @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
2
+ // DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
3
+
4
+ /** Plural folding: one fixed rule per suffix. @param {string} w */
5
+ export function fold(w: string): string;
6
+ /** A name's words: `DateRangeInput` is date, range, input. @param {string} name */
7
+ export function nameWords(name: string): string[];
8
+ /** @param {string} q @returns {string[]} unique words, then adjacent pairs */
9
+ export function queryTokens(q: string): string[];
10
+ /** The router's inputs: the query's tokens plus four shape flags. @param {string} q */
11
+ export function routerTokens(q: string): string[];
12
+ /**
13
+ * The router's probability for each class: the shared classifier over the
14
+ * query's tokens.
15
+ * @param {import('../../foundation/route/route.mjs').ClassifierWeights} weights
16
+ * @param {string} q
17
+ * @returns {number[]}
18
+ */
19
+ export function routeProbs(weights: import("../../foundation/route/route.mjs").ClassifierWeights, q: string): number[];
20
+ /** The domains, in the order the weights files index them. */
21
+ export const DOMAINS: string[];
22
+ /** The router's classes: the domain a query wants, or none. */
23
+ export const ROUTES: string[];
24
+ export function words(text: string): string[];
25
+ export function queryWords(q: string): string[];
@@ -0,0 +1,92 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file How search's searchers and classifier read a query: its words,
5
+ * folded forms, word pairs, and the classifier's tokens and class
6
+ * probabilities.
7
+ */
8
+
9
+ import {classify} from '../../foundation/route/route.mjs';
10
+
11
+ /** The domains, in the order the weights files index them. */
12
+ export const DOMAINS = ['component', 'hook', 'doc', 'template', 'theme'];
13
+
14
+ /** The router's classes: the domain a query wants, or none. */
15
+ export const ROUTES = [...DOMAINS, 'NONE'];
16
+
17
+ const STOP = new Set(
18
+ 'a an the of for to with and or in on by is it my me i we you this that be as at from use using how what which show make add create get set any can do does not no into via'.split(
19
+ ' ',
20
+ ),
21
+ );
22
+
23
+ const QUESTION = new Set([
24
+ 'how',
25
+ 'what',
26
+ 'why',
27
+ 'when',
28
+ 'where',
29
+ 'can',
30
+ 'does',
31
+ 'is',
32
+ ]);
33
+
34
+ /** @param {string} text @returns {string[]} */
35
+ export const words = text =>
36
+ (text || '').toLowerCase().match(/[a-z0-9]+/g) ?? [];
37
+
38
+ /** Plural folding: one fixed rule per suffix. @param {string} w */
39
+ export function fold(w) {
40
+ if (w.length > 4 && w.endsWith('ies')) return `${w.slice(0, -3)}y`;
41
+ if (w.length > 3 && w.endsWith('es') && 'sxz'.includes(w[w.length - 3])) {
42
+ return w.slice(0, -2);
43
+ }
44
+ if (w.length > 3 && w.endsWith('s') && !w.endsWith('ss'))
45
+ return w.slice(0, -1);
46
+ return w;
47
+ }
48
+
49
+ /** A name's words: `DateRangeInput` is date, range, input. @param {string} name */
50
+ export function nameWords(name) {
51
+ const s = name
52
+ .replace(/[/-]/g, ' ')
53
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
54
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2');
55
+ return words(s);
56
+ }
57
+
58
+ /** The query's content words, folded. @param {string} q */
59
+ export const queryWords = q =>
60
+ words(q)
61
+ .filter(w => !STOP.has(w))
62
+ .map(fold);
63
+
64
+ /** @param {string} q @returns {string[]} unique words, then adjacent pairs */
65
+ export function queryTokens(q) {
66
+ const qw = queryWords(q);
67
+ const pairs = qw.slice(1).map((w, i) => `${qw[i]}_${w}`);
68
+ return [...new Set([...qw, ...pairs])];
69
+ }
70
+
71
+ /** The router's inputs: the query's tokens plus four shape flags. @param {string} q */
72
+ export function routerTokens(q) {
73
+ const ws = words(q);
74
+ const flags = [`#n${Math.min(ws.length, 6)}`];
75
+ if (ws.length && QUESTION.has(ws[0])) flags.push('#question');
76
+ if (q.split(/\s+/).some(w => /^use[a-z]+/.test(w)) || /\buse[A-Z]/.test(q)) {
77
+ flags.push('#usehook');
78
+ }
79
+ if (/[A-Z][a-z]+[A-Z]/.test(q)) flags.push('#camel');
80
+ return [...queryTokens(q), ...flags, '#bias'];
81
+ }
82
+
83
+ /**
84
+ * The router's probability for each class: the shared classifier over the
85
+ * query's tokens.
86
+ * @param {import('../../foundation/route/route.mjs').ClassifierWeights} weights
87
+ * @param {string} q
88
+ * @returns {number[]}
89
+ */
90
+ export function routeProbs(weights, q) {
91
+ return classify(weights, routerTokens(q));
92
+ }
@@ -0,0 +1,65 @@
1
+ // @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
2
+ // DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
3
+
4
+ /**
5
+ * Split a query into meaningful content tokens (lowercased, stopwords + very
6
+ * short words removed). Empty for single-word queries (callers fall back to
7
+ * whole-phrase scoring).
8
+ * @param {string} term - Already-lowercased query.
9
+ * @returns {string[]}
10
+ */
11
+ export function tokenizeQuery(term: string): string[];
12
+ /**
13
+ * Score a candidate against a query, handling multi-word natural language.
14
+ * Tries the whole phrase (so exact/near matches still win) AND a per-token
15
+ * pass (so "data table with filters" matches `table-page` via table+filter),
16
+ * and returns whichever is stronger.
17
+ *
18
+ * @param {string} term - Lowercased full query.
19
+ * @param {string[]} tokens - Content tokens from tokenizeQuery(term).
20
+ * @param {object} candidate
21
+ * @returns {{score: number, reason: string} | null}
22
+ */
23
+ /**
24
+ * The words of a title or query, lowercased, without punctuation or code ticks.
25
+ * @param {string} text
26
+ * @returns {string[]}
27
+ */
28
+ export function phraseWords(text: string): string[];
29
+ /**
30
+ * Whether two words are the same word, allowing a plural on either side, so
31
+ * `data attributes selector` still reads "Data attribute selectors".
32
+ * @param {string} a
33
+ * @param {string} b
34
+ */
35
+ export function samePhraseWord(a: string, b: string): boolean;
36
+ /**
37
+ * Whether two phrases are the same words, in order, ignoring case,
38
+ * punctuation, and a plural on any word: `side panel` is "Side panels", and
39
+ * `headers and footers` is `header-and-footer`.
40
+ * @param {string} a
41
+ * @param {string} b
42
+ * @returns {boolean}
43
+ */
44
+ export function samePhrase(a: string, b: string): boolean;
45
+ /**
46
+ * Whether `plural` is the plural of `word`: `integrations` of `integration`,
47
+ * `boxes` of `box`. `es` only follows s, x, z, ch, or sh, so `notes` is not a
48
+ * plural of `not`.
49
+ * @param {string} plural
50
+ * @param {string} word
51
+ */
52
+ export function pluralOf(plural: string, word: string): boolean;
53
+ /** Valid domain filters for `--type`. */
54
+ export const SEARCH_DOMAINS: string[];
55
+ /**
56
+ * The domains a search reads without @astryxdesign/core. An open search outside
57
+ * an app covers these alone.
58
+ */
59
+ export const CORELESS_DOMAINS: string[];
60
+ /**
61
+ * Filler words stripped from multi-word queries so natural-language phrasing
62
+ * ("a page where you can see business stats") ranks on its content words.
63
+ * Exported for `build`, whose page ranker strips the same words.
64
+ */
65
+ export const STOPWORDS: Set<string>;
@@ -0,0 +1,176 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Reading a search query: its domains, stopwords, content tokens, and
5
+ * phrase words.
6
+ */
7
+
8
+ import {unlinkText} from '../../foundation/doc-compiler/links.mjs';
9
+ import {sameWord} from './_words.mjs';
10
+
11
+ /** Valid domain filters for `--type`. */
12
+ export const SEARCH_DOMAINS = ['component', 'hook', 'doc', 'template', 'theme'];
13
+
14
+ /**
15
+ * The domains a search reads without @astryxdesign/core. An open search outside
16
+ * an app covers these alone.
17
+ */
18
+ export const CORELESS_DOMAINS = ['doc', 'theme'];
19
+
20
+ /**
21
+ * Filler words stripped from multi-word queries so natural-language phrasing
22
+ * ("a page where you can see business stats") ranks on its content words.
23
+ * Exported for `build`, whose page ranker strips the same words.
24
+ */
25
+ export const STOPWORDS = new Set([
26
+ 'a',
27
+ 'an',
28
+ 'the',
29
+ 'of',
30
+ 'for',
31
+ 'to',
32
+ 'with',
33
+ 'and',
34
+ 'or',
35
+ 'in',
36
+ 'on',
37
+ 'at',
38
+ 'by',
39
+ 'that',
40
+ 'this',
41
+ 'my',
42
+ 'your',
43
+ 'our',
44
+ 'their',
45
+ 'is',
46
+ 'are',
47
+ 'be',
48
+ 'it',
49
+ 'its',
50
+ 'as',
51
+ 'from',
52
+ 'page',
53
+ 'screen',
54
+ 'app',
55
+ 'application',
56
+ 'view',
57
+ 'where',
58
+ 'you',
59
+ 'can',
60
+ 'some',
61
+ 'like',
62
+ 'just',
63
+ 'basically',
64
+ 'kinda',
65
+ 'want',
66
+ 'wants',
67
+ 'need',
68
+ 'needs',
69
+ 'something',
70
+ 'thing',
71
+ 'things',
72
+ 'build',
73
+ 'make',
74
+ 'create',
75
+ 'i',
76
+ 'me',
77
+ 'we',
78
+ 'us',
79
+ 'so',
80
+ 'up',
81
+ 'out',
82
+ 'over',
83
+ 'side',
84
+ 'one',
85
+ 'big',
86
+ ]);
87
+
88
+ /**
89
+ * Split a query into meaningful content tokens (lowercased, stopwords + very
90
+ * short words removed). Empty for single-word queries (callers fall back to
91
+ * whole-phrase scoring).
92
+ * @param {string} term - Already-lowercased query.
93
+ * @returns {string[]}
94
+ */
95
+ export function tokenizeQuery(term) {
96
+ return (
97
+ term
98
+ .split(/\s+/)
99
+ // Strip only leading/trailing punctuation; keep joined identifiers intact
100
+ // (e.g. "foo_bar" stays one token) so gibberish stays gibberish.
101
+ .map(t => t.replace(/^[^a-z0-9]+|[^a-z0-9]+$/g, ''))
102
+ .filter(t => t.length >= 2 && !STOPWORDS.has(t))
103
+ );
104
+ }
105
+
106
+ /**
107
+ * Score a candidate against a query, handling multi-word natural language.
108
+ * Tries the whole phrase (so exact/near matches still win) AND a per-token
109
+ * pass (so "data table with filters" matches `table-page` via table+filter),
110
+ * and returns whichever is stronger.
111
+ *
112
+ * @param {string} term - Lowercased full query.
113
+ * @param {string[]} tokens - Content tokens from tokenizeQuery(term).
114
+ * @param {object} candidate
115
+ * @returns {{score: number, reason: string} | null}
116
+ */
117
+
118
+ /**
119
+ * The words of a title or query, lowercased, without punctuation or code ticks.
120
+ * @param {string} text
121
+ * @returns {string[]}
122
+ */
123
+ export function phraseWords(text) {
124
+ return (
125
+ unlinkText(text)
126
+ .toLowerCase()
127
+ .match(/[a-z0-9]+/g) ?? []
128
+ );
129
+ }
130
+
131
+ /**
132
+ * Whether two words are the same word, allowing a plural on either side, so
133
+ * `data attributes selector` still reads "Data attribute selectors".
134
+ * @param {string} a
135
+ * @param {string} b
136
+ */
137
+ export function samePhraseWord(a, b) {
138
+ return (
139
+ a === b ||
140
+ `${a}s` === b ||
141
+ `${b}s` === a ||
142
+ `${a}es` === b ||
143
+ `${b}es` === a
144
+ );
145
+ }
146
+
147
+ /**
148
+ * Whether two phrases are the same words, in order, ignoring case,
149
+ * punctuation, and a plural on any word: `side panel` is "Side panels", and
150
+ * `headers and footers` is `header-and-footer`.
151
+ * @param {string} a
152
+ * @param {string} b
153
+ * @returns {boolean}
154
+ */
155
+ export function samePhrase(a, b) {
156
+ const x = phraseWords(a);
157
+ const y = phraseWords(b);
158
+ return (
159
+ x.length > 0 &&
160
+ x.length === y.length &&
161
+ x.every((w, k) => samePhraseWord(w, y[k]) || sameWord(w, y[k]))
162
+ );
163
+ }
164
+
165
+ /**
166
+ * Whether `plural` is the plural of `word`: `integrations` of `integration`,
167
+ * `boxes` of `box`. `es` only follows s, x, z, ch, or sh, so `notes` is not a
168
+ * plural of `not`.
169
+ * @param {string} plural
170
+ * @param {string} word
171
+ */
172
+ export function pluralOf(plural, word) {
173
+ if (word.length < 3) return false;
174
+ if (plural === `${word}s`) return true;
175
+ return /(?:s|x|z|ch|sh)$/.test(word) && plural === `${word}es`;
176
+ }
@@ -0,0 +1,97 @@
1
+ // @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
2
+ // DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
3
+
4
+ /** @typedef {import('./_lexical.mjs').Candidate} Candidate */
5
+ /**
6
+ * Map a scored candidate to its public, actionable result shape. Each result
7
+ * carries enough to act on it: the domain, name, a one-line description, and
8
+ * the follow-up command (and import path where relevant).
9
+ *
10
+ * @param {Candidate} c - candidate
11
+ * @param {number} score
12
+ * @param {string} reason
13
+ * @param {number} matchedTerms - Query concepts this result answered.
14
+ * @param {number} queryTerms - Query concepts there were to answer.
15
+ * @param {boolean} [outside] - The search runs outside a project, where a
16
+ * theme cannot be added to an app, so its command forks the source instead
17
+ * (spec AST-081 FR3).
18
+ */
19
+ export function toResult(c: Candidate, score: number, reason: string, matchedTerms: number, queryTerms: number, outside?: boolean): {
20
+ domain: "component" | "hook" | "theme" | "doc" | "template";
21
+ name: string;
22
+ package: string | undefined;
23
+ score: number;
24
+ reason: string;
25
+ description: string;
26
+ } | {
27
+ import: string | undefined;
28
+ command: string;
29
+ domain: "component" | "hook" | "theme" | "doc" | "template";
30
+ name: string;
31
+ package: string | undefined;
32
+ score: number;
33
+ reason: string;
34
+ description: string;
35
+ } | {
36
+ parent?: string | undefined;
37
+ title: string | undefined;
38
+ command: string;
39
+ section?: string | undefined;
40
+ name: string;
41
+ domain: "component" | "hook" | "theme" | "doc" | "template";
42
+ package: string | undefined;
43
+ score: number;
44
+ reason: string;
45
+ description: string;
46
+ } | {
47
+ displayName: string | undefined;
48
+ kind: "page" | "block" | undefined;
49
+ command: string;
50
+ domain: "component" | "hook" | "theme" | "doc" | "template";
51
+ name: string;
52
+ package: string | undefined;
53
+ score: number;
54
+ reason: string;
55
+ description: string;
56
+ } | {
57
+ displayName: string | undefined;
58
+ command: string;
59
+ domain: "component" | "hook" | "theme" | "doc" | "template";
60
+ name: string;
61
+ package: string | undefined;
62
+ score: number;
63
+ reason: string;
64
+ description: string;
65
+ };
66
+ /**
67
+ * Throws the search argument errors: an empty query, an unknown type, a limit
68
+ * that is not a positive integer.
69
+ *
70
+ * @param {string} query
71
+ * @param {{type?: string, limit?: number}} options
72
+ */
73
+ export function checkSearchArgs(query: string, options: {
74
+ type?: string;
75
+ limit?: number;
76
+ }): void;
77
+ /**
78
+ * The components (name and keywords) a search response was scored against, or
79
+ * null when that search was narrowed away from components.
80
+ * @param {object} response
81
+ * @returns {{name: string, keywords: string[]}[] | null}
82
+ */
83
+ export function searchedComponents(response: object): {
84
+ name: string;
85
+ keywords: string[];
86
+ }[] | null;
87
+ /**
88
+ * The components each search response was scored against, by response:
89
+ * `build` reads them to tell a part of a page from a page without gathering
90
+ * them again. Module-private, so they never enter search's JSON.
91
+ * @type {WeakMap<object, {name: string, keywords: string[]}[]>}
92
+ */
93
+ export const searchedComponentsOf: WeakMap<object, {
94
+ name: string;
95
+ keywords: string[];
96
+ }[]>;
97
+ export type Candidate = import("./_lexical.mjs").Candidate;