@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,528 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file build's block picker: ranks every block in the catalog for a request
5
+ * by meaning, from each block's own docs, and keeps build's own count.
6
+ *
7
+ * @input The request, build's signals for it (`kitSignals`: the blocks the kit
8
+ * names, search's block results with their scores and coverage, the block
9
+ * catalog and the components), and the picker weights
10
+ * (`blocks.weights.json` beside this file, read by the adapter): a
11
+ * vocabulary of stemmed words and word pairs with their idf (x1024), two
12
+ * signed byte matrices that map request words and doc words into one space, the
13
+ * lexical weight, the blocks the matrices were fitted with, and the ranking
14
+ * coefficients.
15
+ * @output `pickBlocks(...)`: block names, best first, as many as build's own
16
+ * list, and an entry (real score and reason) for each listed block search
17
+ * did not return.
18
+ * @position api/build/blocks; tree.mjs's main searcher calls it between
19
+ * kitSignals (kit/signals.mjs) and kitAnswer (kit/answer.mjs). Pure: no file,
20
+ * network, or environment access.
21
+ *
22
+ * How it scores. A block is described by its own docs only: its name, display
23
+ * name and description, and its component's name and keywords. The request
24
+ * and each block's docs become weighted word vectors (stemmed content words
25
+ * and adjacent pairs, (1 + ln count) x idf, unit length). Their match is the
26
+ * request's vector through the request matrix, against the docs' vector
27
+ * through the doc matrix, plus the lexical weight times their plain overlap
28
+ * (cosine), so a block the weights were never fitted with, such as a new one,
29
+ * is scored from its docs the same way. The candidates are the best matches,
30
+ * build's own blocks, search's first results, and the best plain overlaps.
31
+ * Each candidate's final score adds the ranking coefficients times: its match
32
+ * relative to the best, its place among the matches, its lead over the
33
+ * second-best match, whether the weights know it, its plain overlap,
34
+ * search's score and share of the request matched and place, whether build
35
+ * names it and where, how strongly the request names its component, and
36
+ * whether it is its component's general example. A block the request writes
37
+ * out by its exact name, and a block whose package is not Core's, keep the
38
+ * slot build gave them; the best of the others fill the remaining slots.
39
+ */
40
+
41
+ import {STOPWORDS} from '../../search/_query.mjs';
42
+
43
+ /**
44
+ * @typedef {object} BlockPickerWeights
45
+ * @property {string[]} vocab stemmed words and `a_b` word pairs
46
+ * @property {number[]} idf one per vocab entry, x1024
47
+ * @property {number} dims the width of the shared space
48
+ * @property {string} request base64 bytes, vocab.length x dims, row-major, each a signed weight offset by 128
49
+ * @property {number} requestScale
50
+ * @property {string} doc base64 bytes, vocab.length x dims, row-major, each a signed weight offset by 128
51
+ * @property {number} docScale
52
+ * @property {number} lexical the weight of the plain overlap
53
+ * @property {string[]} known the blocks the matrices were fitted with
54
+ * @property {Record<string, number>} theta the ranking coefficients
55
+ *
56
+ * @typedef {import('../kit/signals.mjs').BlockScore} BlockScore
57
+ * @typedef {{name: string, package?: string, displayName?: string, description?: string, command?: string}} CatalogBlock
58
+ * @typedef {{name: string, keywords?: string[]}} CatalogComponent
59
+ * @typedef {import('../../search/search.type.mjs').SearchResultEntry} SearchResultEntry
60
+ */
61
+
62
+ /**
63
+ * Generic English function words, search's stopwords, and words every build
64
+ * request or block shares: never a reason to tie a block to a request.
65
+ */
66
+ const STOP = new Set([
67
+ ...STOPWORDS,
68
+ ...(
69
+ 'about above across after again against all along already also although ' +
70
+ 'always am among amongst an and another any anyone anything anywhere are ' +
71
+ 'around as at be because been before behind being below beneath beside ' +
72
+ 'besides between beyond both but by can could despite did do does doing ' +
73
+ 'down during each either else enough even ever every everyone everything ' +
74
+ 'everywhere except few following for from further had has have having he ' +
75
+ 'her here hers herself him himself his how however if in inside instead ' +
76
+ 'into is it its itself just least less little lot lots many may maybe me ' +
77
+ 'might more most much must my myself near neither never no nobody none ' +
78
+ 'nor not nothing now nowhere of off often on once one ones only onto or ' +
79
+ 'other others ought our ours ourselves out outside over own past per ' +
80
+ 'perhaps quite rather same several shall she should since so some someone ' +
81
+ 'something sometimes somewhat somewhere still such than that the their ' +
82
+ 'theirs them themselves then there these they this those though through ' +
83
+ 'throughout thus till to together too toward towards under underneath ' +
84
+ 'unless unlike until up upon usually very via was we were what whatever ' +
85
+ 'when whenever where whereas whether which whichever while who whoever ' +
86
+ 'whom whomever whose why will with within without would yet you your ' +
87
+ 'yours yourself yourselves'
88
+ ).split(' '),
89
+ ...(
90
+ 'a an the and or of for to in on with by at from as is are be this that ' +
91
+ 'it its my our your their me we you i into via per each all any some page ' +
92
+ 'app screen ui view build make create need want like show shows showing ' +
93
+ 'using use one two new add list item items basic simple showcase example ' +
94
+ 'default variant'
95
+ ).split(' '),
96
+ ]);
97
+
98
+ /**
99
+ * Words after which the rest of a request is where the new piece goes, not
100
+ * the piece itself ("a filter bar ABOVE the table").
101
+ */
102
+ const CONTEXT = new Set(
103
+ (
104
+ 'in on inside within into above below under beneath next beside to for ' +
105
+ 'with and from of near over across along around after before at'
106
+ ).split(' '),
107
+ );
108
+
109
+ /** How many of each kind of candidate the picker weighs. */
110
+ const POOL = {matches: 60, searched: 60, overlaps: 20};
111
+
112
+ /** How many matches carry a place; the rest share the last one's match. */
113
+ const TOP = 80;
114
+
115
+ /**
116
+ * A word with a plural or verb ending removed (words over four letters):
117
+ * "-ies" becomes "-y"; "-es" goes only after s, x, z, ch, or sh; otherwise a
118
+ * final "s" goes, but never after another "s"; then "-ing" or "-ed".
119
+ * @param {string} w
120
+ * @returns {string}
121
+ */
122
+ export function stemOf(w) {
123
+ if (w.length <= 4) return w;
124
+ if (w.endsWith('ies')) return `${w.slice(0, -3)}y`;
125
+ if (/(?:s|x|z|ch|sh)es$/.test(w)) return w.slice(0, -2);
126
+ if (w.endsWith('s') && !w.endsWith('ss')) return w.slice(0, -1);
127
+ if (w.endsWith('ing')) return w.slice(0, -3);
128
+ if (w.endsWith('ed')) return w.slice(0, -2);
129
+ return w;
130
+ }
131
+
132
+ /**
133
+ * An identifier's words, lowercase: "SideNavNestedItems" -> side, nav,
134
+ * nested, items.
135
+ * @param {string} text
136
+ * @returns {string[]}
137
+ */
138
+ export function wordsOf(text) {
139
+ return String(text)
140
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
141
+ .replace(/([A-Z])([A-Z][a-z])/g, '$1 $2')
142
+ .toLowerCase()
143
+ .split(/[^a-z0-9]+/)
144
+ .filter(Boolean);
145
+ }
146
+
147
+ /**
148
+ * A text's stemmed content words, then its adjacent pairs as `a_b`.
149
+ * @param {string} text
150
+ * @returns {string[]}
151
+ */
152
+ export function termsOf(text) {
153
+ const words = (
154
+ String(text)
155
+ .toLowerCase()
156
+ .match(/[a-z0-9]{2,}/g) ?? []
157
+ )
158
+ .filter(w => !STOP.has(w))
159
+ .map(stemOf);
160
+ const out = [...words];
161
+ for (let i = 0; i + 1 < words.length; i++)
162
+ out.push(`${words[i]}_${words[i + 1]}`);
163
+ return out;
164
+ }
165
+
166
+ /**
167
+ * The weights, decoded once per weights object.
168
+ * @type {WeakMap<BlockPickerWeights, {index: Map<string, number>, idf: Float64Array, request: Int8Array, doc: Int8Array}>}
169
+ */
170
+ const decoded = new WeakMap();
171
+
172
+ /** @param {BlockPickerWeights} weights */
173
+ function decode(weights) {
174
+ let d = decoded.get(weights);
175
+ if (!d) {
176
+ // Each byte is a signed weight stored offset by 128.
177
+ const bytes = (/** @type {string} */ s) =>
178
+ Int8Array.from(Buffer.from(s, 'base64'), b => b - 128);
179
+ d = {
180
+ index: new Map(weights.vocab.map((w, i) => [w, i])),
181
+ idf: Float64Array.from(weights.idf, x => x / 1024),
182
+ request: bytes(weights.request),
183
+ doc: bytes(weights.doc),
184
+ };
185
+ decoded.set(weights, d);
186
+ }
187
+ return d;
188
+ }
189
+
190
+ /**
191
+ * A text's sparse unit vector over the vocabulary: (1 + ln count) x idf.
192
+ * @param {string[]} terms
193
+ * @param {ReturnType<typeof decode>} d
194
+ * @returns {Map<number, number>}
195
+ */
196
+ function vectorOf(terms, d) {
197
+ /** @type {Map<number, number>} */
198
+ const counts = new Map();
199
+ for (const t of terms) {
200
+ const i = d.index.get(t);
201
+ if (i !== undefined) counts.set(i, (counts.get(i) ?? 0) + 1);
202
+ }
203
+ /** @type {Map<number, number>} */
204
+ const v = new Map();
205
+ let sq = 0;
206
+ for (const [i, c] of counts) {
207
+ const x = (1 + Math.log(c)) * d.idf[i];
208
+ v.set(i, x);
209
+ sq += x * x;
210
+ }
211
+ const norm = Math.sqrt(sq) || 1;
212
+ for (const [i, x] of v) v.set(i, x / norm);
213
+ return v;
214
+ }
215
+
216
+ /**
217
+ * A sparse vector through a signed byte matrix.
218
+ * @param {Map<number, number>} v
219
+ * @param {Int8Array} m
220
+ * @param {number} scale
221
+ * @param {number} dims
222
+ */
223
+ function project(v, m, scale, dims) {
224
+ const out = new Float64Array(dims);
225
+ for (const [i, x] of v) {
226
+ const row = i * dims;
227
+ for (let k = 0; k < dims; k++) out[k] += x * m[row + k];
228
+ }
229
+ for (let k = 0; k < dims; k++) out[k] *= scale;
230
+ return out;
231
+ }
232
+
233
+ /**
234
+ * Each block's component: the longest component name its name starts with,
235
+ * else the display name's part before " — ", without spaces.
236
+ * @param {CatalogBlock} block
237
+ * @param {string[]} componentNames longest first
238
+ */
239
+ function componentOf(block, componentNames) {
240
+ for (const c of componentNames) if (block.name.startsWith(c)) return c;
241
+ const display = block.displayName ?? block.name;
242
+ return display.split(' — ')[0].replace(/ /g, '');
243
+ }
244
+
245
+ /**
246
+ * The catalog, read once per weights and catalog contents: each block's
247
+ * component, docs vector and projection, and each component's name phrases.
248
+ * @type {WeakMap<BlockPickerWeights, {key: string, blocks: Map<string, {component: string, vector: Map<number, number>, projected: Float64Array}>, phrases: Map<string, [string[], number][]>}>}
249
+ */
250
+ const catalogs = new WeakMap();
251
+
252
+ /**
253
+ * @param {CatalogBlock[]} catalog
254
+ * @param {CatalogComponent[]} components
255
+ * @param {BlockPickerWeights} weights
256
+ */
257
+ function readCatalog(catalog, components, weights) {
258
+ const key = JSON.stringify([
259
+ catalog.map(b => [b.name, b.displayName, b.description]),
260
+ components.map(c => [c.name, c.keywords]),
261
+ ]);
262
+ const cached = catalogs.get(weights);
263
+ if (cached && cached.key === key) return cached;
264
+ const d = decode(weights);
265
+ const names = components.map(c => c.name).sort((a, b) => b.length - a.length);
266
+ const keywords = new Map(components.map(c => [c.name, c.keywords ?? []]));
267
+ /** @type {Map<string, {component: string, vector: Map<number, number>, projected: Float64Array}>} */
268
+ const blocks = new Map();
269
+ for (const b of catalog) {
270
+ if (blocks.has(b.name)) continue;
271
+ const component = componentOf(b, names);
272
+ const text = [
273
+ wordsOf(b.name).join(' '),
274
+ b.displayName ?? '',
275
+ b.description ?? '',
276
+ wordsOf(component).join(' '),
277
+ (keywords.get(component) ?? []).join(' '),
278
+ ].join(' ');
279
+ const vector = vectorOf(termsOf(text), d);
280
+ blocks.set(b.name, {
281
+ component,
282
+ vector,
283
+ projected: project(vector, d.doc, weights.docScale, weights.dims),
284
+ });
285
+ }
286
+ // A component is named by its own words, or by a keyword at most three
287
+ // components share (weighted by how few share it).
288
+ /** @type {Map<string, number>} */
289
+ const shared = new Map();
290
+ for (const c of components)
291
+ for (const k of new Set((c.keywords ?? []).map(k => k.replace(/-/g, ' '))))
292
+ shared.set(k, (shared.get(k) ?? 0) + 1);
293
+ const phrase = (/** @type {string} */ s) =>
294
+ s.toLowerCase().split(/\s+/).filter(Boolean).map(stemOf);
295
+ /** @type {Map<string, [string[], number][]>} */
296
+ const phrases = new Map();
297
+ const withBlocks = new Set([...blocks.values()].map(b => b.component));
298
+ for (const c of withBlocks) {
299
+ /** @type {[string[], number][]} */
300
+ const list = [[phrase(wordsOf(c).join(' ')), 3]];
301
+ for (const k of (keywords.get(c) ?? []).map(k => k.replace(/-/g, ' ')))
302
+ if ((shared.get(k) ?? 0) <= 3)
303
+ list.push([phrase(k), 1 / (shared.get(k) ?? 1)]);
304
+ phrases.set(c, list);
305
+ }
306
+ const read = {key, blocks, phrases};
307
+ catalogs.set(weights, read);
308
+ return read;
309
+ }
310
+
311
+ /**
312
+ * How strongly the request names each component: its best phrase, longer
313
+ * phrases and earlier ones stronger, and half as strong once the request has
314
+ * moved on to where the piece goes.
315
+ * @param {string} request
316
+ * @param {Map<string, [string[], number][]>} phrases
317
+ * @returns {Map<string, number>}
318
+ */
319
+ function namedComponents(request, phrases) {
320
+ const raw =
321
+ String(request)
322
+ .toLowerCase()
323
+ .match(/[a-z0-9]+/g) ?? [];
324
+ const stems = raw.map(stemOf);
325
+ let context = raw.findIndex((w, k) => k > 0 && CONTEXT.has(w));
326
+ if (context < 0) context = raw.length;
327
+ /** @type {Map<string, number>} */
328
+ const out = new Map();
329
+ for (const [c, list] of phrases) {
330
+ let best = 0;
331
+ for (const [p, w] of list) {
332
+ if (p.length === 0) continue;
333
+ let at = -1;
334
+ for (let i = 0; i + p.length <= stems.length && at < 0; i++)
335
+ if (p.every((x, k) => stems[i + k] === x)) at = i;
336
+ if (at < 0) continue;
337
+ const s =
338
+ w *
339
+ (1 + 0.3 * (p.length - 1)) *
340
+ (at < context ? 1 : 0.5) *
341
+ (1 - 0.01 * at);
342
+ if (s > best) best = s;
343
+ }
344
+ if (best) out.set(c, best);
345
+ }
346
+ return out;
347
+ }
348
+
349
+ /**
350
+ * Whether the request writes out the block's exact name (its words joined).
351
+ * @param {string[]} words the request's identifier words
352
+ * @param {string} block
353
+ */
354
+ function writesName(words, block) {
355
+ const name = block.toLowerCase();
356
+ const length = wordsOf(block).length;
357
+ for (let i = 0; i < words.length; i++) {
358
+ let run = '';
359
+ for (let n = 0; n < length && i + n < words.length; n++) {
360
+ run += words[i + n];
361
+ if (run === name) return true;
362
+ }
363
+ }
364
+ return false;
365
+ }
366
+
367
+ /**
368
+ * The blocks build names for a request, ranked by the picker.
369
+ * @param {string} request
370
+ * @param {{kitBlocks: string[], searched: BlockScore[], foreign: string[], catalog: CatalogBlock[], components: CatalogComponent[]}} signals
371
+ * @param {BlockPickerWeights} weights
372
+ * @returns {{names: string[], entries: Map<string, SearchResultEntry>}} as
373
+ * many names as build's own list, best first; an entry for each listed
374
+ * block search did not return
375
+ */
376
+ export function pickBlocks(request, signals, weights) {
377
+ const own = signals.kitBlocks;
378
+ /** @type {Map<string, SearchResultEntry>} */
379
+ const entries = new Map();
380
+ if (own.length === 0) return {names: [], entries};
381
+ const d = decode(weights);
382
+ const cat = readCatalog(signals.catalog, signals.components, weights);
383
+ const q = vectorOf(termsOf(request), d);
384
+ const eq = project(q, d.request, weights.requestScale, weights.dims);
385
+ const dot = (/** @type {Map<number, number>} */ v) => {
386
+ let s = 0;
387
+ for (const [i, x] of q) {
388
+ const y = v.get(i);
389
+ if (y !== undefined) s += x * y;
390
+ }
391
+ return s;
392
+ };
393
+ /** @type {Map<string, {match: number, overlap: number}>} */
394
+ const scored = new Map();
395
+ for (const [name, b] of cat.blocks) {
396
+ let s = 0;
397
+ for (let k = 0; k < weights.dims; k++) s += eq[k] * b.projected[k];
398
+ const overlap = dot(b.vector);
399
+ scored.set(name, {match: s + weights.lexical * overlap, overlap});
400
+ }
401
+ const get = (/** @type {string} */ b) =>
402
+ scored.get(b) ?? {match: -Infinity, overlap: 0};
403
+ const ranked = [...scored.keys()].sort((a, b) => get(b).match - get(a).match);
404
+ const top = ranked.slice(0, TOP);
405
+ const best = top.length ? get(top[0]).match : 0;
406
+ const second = top.length > 1 ? get(top[1]).match : 0;
407
+ const floor = top.length ? get(top[top.length - 1]).match : 0;
408
+ const place = new Map(top.map((b, k) => [b, k]));
409
+ /** @type {Map<string, [BlockScore, number]>} */
410
+ const found = new Map();
411
+ signals.searched.forEach((s, k) => {
412
+ if (!found.has(s[0])) found.set(s[0], [s, k]);
413
+ });
414
+ const overlaps = [...scored.keys()]
415
+ .filter(b => get(b).overlap > 0)
416
+ .sort((a, b) => get(b).overlap - get(a).overlap || (a < b ? -1 : 1));
417
+ const pool = [
418
+ ...new Set([
419
+ ...own,
420
+ ...top.slice(0, POOL.matches),
421
+ ...signals.searched.slice(0, POOL.searched).map(s => s[0]),
422
+ ...overlaps.slice(0, POOL.overlaps),
423
+ ]),
424
+ ].filter(b => scored.has(b) || found.has(b));
425
+ const known = new Set(weights.known);
426
+ const named = namedComponents(request, cat.phrases);
427
+ const t = weights.theta;
428
+ const final = pool.map((b, pos) => {
429
+ const m = place.has(b) ? get(b).match : floor;
430
+ const f = found.get(b);
431
+ const s = f?.[0];
432
+ const component = cat.blocks.get(b)?.component;
433
+ const score =
434
+ t.g * (m - best) +
435
+ t.grank * (place.get(b) ?? 99) +
436
+ t.gmargin * (m - second) +
437
+ t.seen * (known.has(b) ? 1 : 0) +
438
+ t.lex * get(b).overlap +
439
+ t.sscore * (s ? s[1] / 100 : 0) +
440
+ t.scov * (s ? (s[2] ?? 0) / Math.max(s[3] ?? 0, 1) : 0) +
441
+ t.srank * (f ? f[1] : 300) +
442
+ t.inkit * (own.includes(b) ? 1 : 0) +
443
+ t.kitpos * (own.includes(b) ? own.indexOf(b) : 9) +
444
+ t.mention * (component ? (named.get(component) ?? 0) : 0) +
445
+ t.show * (/Showcase|Basic/.test(b) ? 1 : 0);
446
+ return {b, pos, score};
447
+ });
448
+ final.sort((a, b) => b.score - a.score || a.pos - b.pos);
449
+ const foreign = new Set(signals.foreign);
450
+ const words = wordsOf(request);
451
+ const keeps = own.map(b => foreign.has(b) || writesName(words, b));
452
+ const kept = new Set(own.filter((_, k) => keeps[k]));
453
+ const order = [
454
+ ...final.map(x => x.b).filter(b => !kept.has(b) && !foreign.has(b)),
455
+ ...own.filter(b => !kept.has(b)),
456
+ ];
457
+ /** @type {string[]} */
458
+ const out = [];
459
+ let next = 0;
460
+ for (let k = 0; k < own.length; k++) {
461
+ if (keeps[k]) {
462
+ out.push(own[k]);
463
+ continue;
464
+ }
465
+ while (out.includes(order[next])) next++;
466
+ out.push(order[next++]);
467
+ }
468
+ // A listed block search did not return gets the picker's own entry: its
469
+ // score (100 for the best candidate, less by how far it trails, never
470
+ // below 1) and the request words that tie it to its docs.
471
+ const max = Math.max(...final.map(x => x.score));
472
+ const byName = new Map(signals.catalog.map(b => [b.name, b]));
473
+ for (const name of out) {
474
+ if (found.has(name) || own.includes(name)) continue;
475
+ const block = byName.get(name);
476
+ const x = final.find(f => f.b === name);
477
+ if (!block || !x) continue;
478
+ entries.set(name, {
479
+ domain: 'template',
480
+ name,
481
+ package: block.package ?? '',
482
+ score: Math.max(1, Math.round(100 * Math.exp(x.score - max))),
483
+ reason: reasonFor(request, cat.blocks.get(name), d, weights),
484
+ description: block.description ?? '',
485
+ displayName: block.displayName,
486
+ kind: 'block',
487
+ command: block.command ?? '',
488
+ });
489
+ }
490
+ return {names: out, entries};
491
+ }
492
+
493
+ /**
494
+ * Why a block fits: the request words that tie it to its docs most.
495
+ * @param {string} request
496
+ * @param {{vector: Map<number, number>, projected: Float64Array} | undefined} block
497
+ * @param {ReturnType<typeof decode>} d
498
+ * @param {BlockPickerWeights} weights
499
+ */
500
+ function reasonFor(request, block, d, weights) {
501
+ if (!block) return 'fits by meaning';
502
+ const raw =
503
+ String(request)
504
+ .toLowerCase()
505
+ .match(/[a-z0-9]{2,}/g) ?? [];
506
+ /** @type {Map<string, string>} */
507
+ const wordOf = new Map();
508
+ for (const w of raw)
509
+ if (!STOP.has(w) && !wordOf.has(stemOf(w))) wordOf.set(stemOf(w), w);
510
+ const q = vectorOf(termsOf(request), d);
511
+ /** @type {[string, number][]} */
512
+ const parts = [];
513
+ for (const [i, x] of q) {
514
+ const term = weights.vocab[i];
515
+ if (term.includes('_')) continue;
516
+ let s = 0;
517
+ const row = i * weights.dims;
518
+ for (let k = 0; k < weights.dims; k++)
519
+ s += d.request[row + k] * block.projected[k];
520
+ s =
521
+ x * s * weights.requestScale +
522
+ weights.lexical * x * (block.vector.get(i) ?? 0);
523
+ if (s > 0) parts.push([wordOf.get(term) ?? term, s]);
524
+ }
525
+ parts.sort((a, b) => b[1] - a[1]);
526
+ const top = parts.slice(0, 3).map(p => p[0]);
527
+ return top.length ? `fits by meaning: ${top.join(', ')}` : 'fits by meaning';
528
+ }