@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,150 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Search's arguments and results: argument checks, the result each
5
+ * candidate becomes, and the components a search read.
6
+ */
7
+
8
+ import {AstryxError} from '../error.mjs';
9
+ import {ERROR_CODES} from '../../foundation/response/error-codes.mjs';
10
+ import {setResultCoverage} from './coverage.mjs';
11
+ import {SEARCH_DOMAINS} from './_query.mjs';
12
+
13
+ /** @typedef {import('./_lexical.mjs').Candidate} Candidate */
14
+
15
+ /**
16
+ * Map a scored candidate to its public, actionable result shape. Each result
17
+ * carries enough to act on it: the domain, name, a one-line description, and
18
+ * the follow-up command (and import path where relevant).
19
+ *
20
+ * @param {Candidate} c - candidate
21
+ * @param {number} score
22
+ * @param {string} reason
23
+ * @param {number} matchedTerms - Query concepts this result answered.
24
+ * @param {number} queryTerms - Query concepts there were to answer.
25
+ * @param {boolean} [outside] - The search runs outside a project, where a
26
+ * theme cannot be added to an app, so its command forks the source instead
27
+ * (spec AST-081 FR3).
28
+ */
29
+ export function toResult(
30
+ c,
31
+ score,
32
+ reason,
33
+ matchedTerms,
34
+ queryTerms,
35
+ outside = false,
36
+ ) {
37
+ const base = {
38
+ domain: c.domain,
39
+ name: c._resultName ?? c.name,
40
+ package: c._package,
41
+ score,
42
+ reason,
43
+ description: c.description || '',
44
+ };
45
+ let result;
46
+ switch (c.domain) {
47
+ case 'component':
48
+ result = {
49
+ ...base,
50
+ import: c._import,
51
+ command: c._command ?? `astryx component ${c.name}`,
52
+ };
53
+ break;
54
+ case 'hook':
55
+ result = {
56
+ ...base,
57
+ import: c._import,
58
+ command: `astryx hook ${c.name}`,
59
+ };
60
+ break;
61
+ case 'doc':
62
+ // A doc result names its topic or route, plus the section when the
63
+ // hit is one section; its command reads exactly that part.
64
+ result = {
65
+ ...base,
66
+ name: c._topic ?? c.name,
67
+ ...(c._section ? {section: c._section} : {}),
68
+ title: c._title,
69
+ command: c._command ?? `astryx docs ${c.name}`,
70
+ ...(c._parent ? {parent: c._parent} : {}),
71
+ };
72
+ break;
73
+ case 'template':
74
+ result = {
75
+ ...base,
76
+ displayName: c._displayName,
77
+ kind: c._kind,
78
+ command: `astryx template ${c._commandName ?? c.name} --type ${c._kind}`,
79
+ };
80
+ break;
81
+ case 'theme':
82
+ result = {
83
+ ...base,
84
+ displayName: c._displayName,
85
+ command: outside
86
+ ? `astryx theme eject ${c.name}`
87
+ : `astryx theme add ${c.name}`,
88
+ };
89
+ break;
90
+ default:
91
+ result = base;
92
+ }
93
+ return setResultCoverage(result, matchedTerms, queryTerms);
94
+ }
95
+
96
+ /**
97
+ * Throws the search argument errors: an empty query, an unknown type, a limit
98
+ * that is not a positive integer.
99
+ *
100
+ * @param {string} query
101
+ * @param {{type?: string, limit?: number}} options
102
+ */
103
+ export function checkSearchArgs(query, options) {
104
+ const {type, limit = 20} = options;
105
+
106
+ if (!query || !String(query).trim()) {
107
+ throw new AstryxError(
108
+ 'A search query is required',
109
+ [{name: 'astryx search button', reason: 'example'}],
110
+ ERROR_CODES.ERR_INVALID_ARGUMENT,
111
+ );
112
+ }
113
+
114
+ if (type && !SEARCH_DOMAINS.includes(type)) {
115
+ throw new AstryxError(
116
+ `Unknown --type "${type}"`,
117
+ SEARCH_DOMAINS.map(d => ({name: d, reason: 'valid type'})),
118
+ ERROR_CODES.ERR_INVALID_ARGUMENT,
119
+ );
120
+ }
121
+
122
+ // Validate limit here (not just in the CLI) so direct API callers get the same
123
+ // contract: a non-positive or non-integer limit is an error, never a silent
124
+ // "return everything". (Previously `limit <= 0` fell through to the full set.)
125
+ if (limit != null && (!Number.isInteger(limit) || limit <= 0)) {
126
+ throw new AstryxError(
127
+ `Invalid limit "${limit}". Must be a positive integer.`,
128
+ undefined,
129
+ ERROR_CODES.ERR_INVALID_ARGUMENT,
130
+ );
131
+ }
132
+ }
133
+
134
+ /**
135
+ * The components each search response was scored against, by response:
136
+ * `build` reads them to tell a part of a page from a page without gathering
137
+ * them again. Module-private, so they never enter search's JSON.
138
+ * @type {WeakMap<object, {name: string, keywords: string[]}[]>}
139
+ */
140
+ export const searchedComponentsOf = new WeakMap();
141
+
142
+ /**
143
+ * The components (name and keywords) a search response was scored against, or
144
+ * null when that search was narrowed away from components.
145
+ * @param {object} response
146
+ * @returns {{name: string, keywords: string[]}[] | null}
147
+ */
148
+ export function searchedComponents(response) {
149
+ return searchedComponentsOf.get(response) ?? null;
150
+ }
@@ -0,0 +1,60 @@
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
+ * Best score for a token against a candidate, fanning out through synonyms
6
+ * (synonym hits are discounted so a direct hit always wins).
7
+ * @param {string} tok
8
+ * @param {Candidate} candidate
9
+ * @param {{fuzzy?: boolean}} [opts]
10
+ * @returns {{score: number, reason: string} | null}
11
+ */
12
+ export function bestForToken(tok: string, candidate: Candidate, opts?: {
13
+ fuzzy?: boolean;
14
+ }): {
15
+ score: number;
16
+ reason: string;
17
+ } | null;
18
+ /**
19
+ * @param {string} term - Lowercased full query.
20
+ * @param {string[]} tokens - Content tokens from tokenizeQuery(term).
21
+ * @param {Candidate} candidate
22
+ * @param {string[]} [words] - The query's words for compound names (see
23
+ * compoundHit); empty when the query names an entry outright.
24
+ * @param {{plain?: boolean}} [opts] - `plain`: score without matches on part
25
+ * of a name or a joined keyword.
26
+ * @returns {{score: number, reason: string, matched: number, total: number, alias?: 'partial' | 'keyword', aliasWords?: number} | null}
27
+ * `matched`/`total` are the query concepts this candidate answered, out of
28
+ * the concepts the query had. Callers that must distinguish "matched one word
29
+ * of three" from "matched all three" — `build`, which gates its pages group
30
+ * on coverage — cannot recover that from the score, because a single strong
31
+ * hit and a broad weak one land on the same number.
32
+ */
33
+ export function scoreQuery(term: string, tokens: string[], candidate: Candidate, words?: string[], { plain }?: {
34
+ plain?: boolean;
35
+ }): {
36
+ score: number;
37
+ reason: string;
38
+ matched: number;
39
+ total: number;
40
+ alias?: "partial" | "keyword";
41
+ aliasWords?: number;
42
+ } | null;
43
+ /**
44
+ * The score of a candidate that matches every content word of a multi-word
45
+ * query, before a bonus of up to 8 for how strong its strongest match is: just
46
+ * above anything that matches only some of the words. The token-sum path tops
47
+ * out near 150 for a partial match (a 100 on one word, the per-word bonus, and
48
+ * the coverage term), so an AND-match with one keyword-strength hit (see
49
+ * {@link STRONG_TOKEN_SCORE}) always outranks an OR-match, and stays below the
50
+ * title tiers.
51
+ */
52
+ export const FULL_COVERAGE_SCORE: 151;
53
+ /**
54
+ * The strongest single-word hit an every-word match needs to take that tier: a
55
+ * keyword substring. Two passing mentions in prose, or the components a page
56
+ * happens to render, are breadth, not relevance; they stay on the token sum,
57
+ * below an exact name or keyword hit on one of the words.
58
+ */
59
+ export const STRONG_TOKEN_SCORE: 70;
60
+ export type Candidate = import("./_lexical.mjs").Candidate;
@@ -0,0 +1,283 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file A whole query's score against one candidate: the reserved top tier
5
+ * for multi-word queries, then the per-token scores (see _candidate-score.mjs).
6
+ */
7
+
8
+ import {SYNONYM_INDEX} from './_synonyms.mjs';
9
+ import {joined} from './_words.mjs';
10
+ import {phraseWords} from './_query.mjs';
11
+ import {compoundHit, compoundOf} from './_compound.mjs';
12
+ import {scoreCandidate} from './_candidate-score.mjs';
13
+ import {
14
+ TITLE_PHRASE_SCORE,
15
+ TITLE_IN_QUERY_SCORE,
16
+ headingWithPhrase,
17
+ titleInQuery,
18
+ } from './_titles.mjs';
19
+
20
+ /** @typedef {import('./_lexical.mjs').Candidate} Candidate */
21
+
22
+ /**
23
+ * Minimum per-token score (in the multi-word pass) to count as a real match.
24
+ * 50 = a genuine name/keyword/description hit; below that is loose Levenshtein
25
+ * fuzz that would otherwise turn gibberish queries into noise.
26
+ *
27
+ * Guidance (45) is deliberately BELOW this floor, so it never counts as one of
28
+ * the matched concepts in a multi-word query. Measured: letting it count moved
29
+ * `nested menu` from SideNav to List and `explain why a field is required` from
30
+ * Field to TextInput — in both cases a component whose guidance happens to
31
+ * mention the other word displaced the one that IS the answer. Breadth is not
32
+ * relevance, the same reason `weakKeywords` are capped. Guidance still decides
33
+ * single-word queries and still breaks ties, which is where it earns its place.
34
+ */
35
+ const MIN_TOKEN_SCORE = 50;
36
+
37
+ /**
38
+ * Best score for a token against a candidate, fanning out through synonyms
39
+ * (synonym hits are discounted so a direct hit always wins).
40
+ * @param {string} tok
41
+ * @param {Candidate} candidate
42
+ * @param {{fuzzy?: boolean}} [opts]
43
+ * @returns {{score: number, reason: string} | null}
44
+ */
45
+ export function bestForToken(tok, candidate, opts = {}) {
46
+ let best = scoreCandidate(tok, candidate, opts);
47
+ const syns = SYNONYM_INDEX.get(tok);
48
+ if (syns) {
49
+ for (const s of syns) {
50
+ const h = scoreCandidate(s, candidate, opts);
51
+ if (h) {
52
+ const score = Math.round(h.score * 0.85);
53
+ if (!best || score > best.score)
54
+ best = {score, reason: `${h.reason} (~${tok})`};
55
+ }
56
+ }
57
+ }
58
+ return best;
59
+ }
60
+
61
+ /**
62
+ * The score of a candidate that matches every content word of a multi-word
63
+ * query, before a bonus of up to 8 for how strong its strongest match is: just
64
+ * above anything that matches only some of the words. The token-sum path tops
65
+ * out near 150 for a partial match (a 100 on one word, the per-word bonus, and
66
+ * the coverage term), so an AND-match with one keyword-strength hit (see
67
+ * {@link STRONG_TOKEN_SCORE}) always outranks an OR-match, and stays below the
68
+ * title tiers.
69
+ */
70
+ export const FULL_COVERAGE_SCORE = 151;
71
+
72
+ /**
73
+ * The strongest single-word hit an every-word match needs to take that tier: a
74
+ * keyword substring. Two passing mentions in prose, or the components a page
75
+ * happens to render, are breadth, not relevance; they stay on the token sum,
76
+ * below an exact name or keyword hit on one of the words.
77
+ */
78
+ export const STRONG_TOKEN_SCORE = 70;
79
+
80
+ /**
81
+ * @param {string} term - Lowercased full query.
82
+ * @param {string[]} tokens - Content tokens from tokenizeQuery(term).
83
+ * @param {Candidate} candidate
84
+ * @param {string[]} [words] - The query's words for compound names (see
85
+ * compoundHit); empty when the query names an entry outright.
86
+ * @param {{plain?: boolean}} [opts] - `plain`: score without matches on part
87
+ * of a name or a joined keyword.
88
+ * @returns {{score: number, reason: string, matched: number, total: number, alias?: 'partial' | 'keyword', aliasWords?: number} | null}
89
+ * `matched`/`total` are the query concepts this candidate answered, out of
90
+ * the concepts the query had. Callers that must distinguish "matched one word
91
+ * of three" from "matched all three" — `build`, which gates its pages group
92
+ * on coverage — cannot recover that from the score, because a single strong
93
+ * hit and a broad weak one land on the same number.
94
+ */
95
+ export function scoreQuery(
96
+ term,
97
+ tokens,
98
+ candidate,
99
+ words = phraseWords(term),
100
+ {plain = false} = {},
101
+ ) {
102
+ const total = Math.max(tokens.length, 1);
103
+ // A whole-phrase hit answered the whole query by definition.
104
+ const asFull = (/** @type {{score: number, reason: string}} */ hit) => ({
105
+ ...hit,
106
+ matched: total,
107
+ total,
108
+ });
109
+ // Typo tolerance is for one-word lookups. In a multi-word query a near miss
110
+ // is usually a different word, not a typo. One typed identifier is one
111
+ // word, whatever words it spells.
112
+ const fuzzy = tokens.length <= 1 || !/\s/.test(term);
113
+ const full = scoreCandidate(term, candidate, {
114
+ fuzzy,
115
+ joins: !plain && words.length > 0,
116
+ });
117
+ // A query of several words keeps its phrase tiers below even when stopwords
118
+ // leave one content word: "make an integration" is still the phrase an
119
+ // author declares as a keyword, and "build an integration" still names a
120
+ // title outright, though each tokenizes to `integration` alone.
121
+ const phrase = phraseWords(term).length >= 2 || words.length >= 2;
122
+ let compound = compoundHit(words, candidate);
123
+ if (plain) {
124
+ if (compound?.alias) compound = null;
125
+ } else {
126
+ compoundOf.set(candidate, compound);
127
+ }
128
+
129
+ /** 0–1 content tokens: whole-phrase fuzzy matching (typo tolerance for
130
+ * single words), but if stopwords left exactly one DIFFERENT token (e.g.
131
+ * "pricing page" → "pricing"), score that token too and take the stronger. */
132
+ const fewTokens = () => {
133
+ const single =
134
+ tokens.length === 1 ? bestForToken(tokens[0], candidate, {fuzzy}) : null;
135
+ if (full && (!single || full.score >= single.score)) return asFull(full);
136
+ return single ? asFull(single) : null;
137
+ };
138
+ if (tokens.length <= 1 && !phrase) return fewTokens();
139
+
140
+ // The full (untokenized) query matching a candidate's name or a declared
141
+ // keyword VERBATIM — full.score 90 or 100, the only two scoreCandidate
142
+ // outcomes at or above that mark — is a deliberate, explicit label the
143
+ // author chose for exactly this multi-word concept. Promote it to a
144
+ // reserved top tier, safely above the token-sum path's ceiling below
145
+ // (~151: 100 avg + 36 bonus + 15 coverage), so it always outranks a
146
+ // candidate that merely happens to contain several of the query's
147
+ // individual words. Without this, "table of contents" never surfaces
148
+ // Outline (which declares that exact phrase as a keyword) because dozens
149
+ // of Table-related templates each match "table" and "contents" separately
150
+ // and accumulate a higher raw score (#5239).
151
+ if (full && full.score >= 90) {
152
+ return asFull({
153
+ score: full.score + 100,
154
+ reason: full.reason,
155
+ ...(full.alias ? {alias: full.alias, aliasWords: words.length} : {}),
156
+ });
157
+ }
158
+
159
+ // The whole query standing as a phrase in a doc's title or one of its
160
+ // headings is the next tier down, and still above the token-sum path. The
161
+ // reader named what the section is about, in order: `dark mode` is the
162
+ // "Light/Dark Mode" section. Without this, the title scores a keyword
163
+ // substring (70) and loses to a doc that happens to name `mode` exactly in
164
+ // a code tick (90 on one token, 98 with coverage), so API enum docs outrank
165
+ // the guide section.
166
+ const heading = headingWithPhrase(term, candidate.titles);
167
+ if (heading != null) {
168
+ return asFull({
169
+ score: TITLE_PHRASE_SCORE,
170
+ reason: `title "${heading}" holds the whole query`,
171
+ });
172
+ }
173
+ if (tokens.length <= 1) return fewTokens();
174
+
175
+ // Multi-word natural language: score each content token, counting only
176
+ // strong hits, then reward coverage so candidates matching more terms win.
177
+ let strongest = 0;
178
+ let matched = 0;
179
+ let tokenSum = 0;
180
+ /** @type {string[]} */
181
+ const hitTerms = [];
182
+ // Adjacent words that join into a name or keyword answer each word they
183
+ // join, as strongly as the joined word matches (see compoundHit).
184
+ // Whether a counted word matched only through part of a name or a keyword
185
+ // read joined, not through the entry's own name or a word of it.
186
+ /** @type {'partial' | 'keyword' | false} */
187
+ let alias = false;
188
+ let aliasWords = 0;
189
+ for (const tok of tokens) {
190
+ let h = bestForToken(tok, candidate, {fuzzy});
191
+ if (
192
+ compound?.covered.has(joined(tok)) &&
193
+ compound.score > (h?.score ?? 0)
194
+ ) {
195
+ h = compound;
196
+ if (compound.alias && compound.score >= MIN_TOKEN_SCORE) {
197
+ alias = alias === 'partial' ? alias : compound.alias;
198
+ aliasWords = compound.aliasWords ?? 0;
199
+ }
200
+ }
201
+ if (h && h.score >= MIN_TOKEN_SCORE) {
202
+ if (h.score > strongest) strongest = h.score;
203
+ matched++;
204
+ hitTerms.push(tok);
205
+ tokenSum += h.score;
206
+ }
207
+ }
208
+ // The reverse of the title tier, a step lower: the query holds a whole title
209
+ // of two words or more, so the reader asked a question around the section's
210
+ // name ("how do I add dark mode"). Coverage breaks ties between sections
211
+ // that share a title such as "Best Practices".
212
+ const named = titleInQuery(term, candidate.titles);
213
+ if (named != null) {
214
+ return {
215
+ score: TITLE_IN_QUERY_SCORE + Math.min(matched, 9),
216
+ reason: `the query names the title "${named}"`,
217
+ matched,
218
+ total,
219
+ };
220
+ }
221
+ if (matched === 0) return full ? asFull(full) : null;
222
+
223
+ const reason = `matches ${matched}/${tokens.length} terms: ${hitTerms.join(', ')}`;
224
+
225
+ // Every word matched is its own tier. Summed per word, a doc that matches
226
+ // both words of `troubleshoot integration` in its title and description
227
+ // (50 + bonus + coverage = 77) lost to thirty docs that each match
228
+ // `integration` alone, by name or in a code tick (98-108). The reader asked for
229
+ // both; a candidate that has both comes first, ordered among its peers by
230
+ // how strong its matches are. It needs one keyword-strength hit:
231
+ // every word mentioned in prose, or rendered by a page, is breadth, and
232
+ // stays on the token sum below an exact hit on one word.
233
+ //
234
+ // The TOTAL quality of matches orders candidates within this tier, not
235
+ // just the strongest single hit. A doc matching both words by keyword
236
+ // (90 + 90 = 180) outranks one matching keyword + prose (90 + 50 = 140).
237
+ // Before this, every all-word match whose strongest hit was a keyword (90)
238
+ // scored 157, burying the better match among dozens of ties.
239
+ if (matched === tokens.length && strongest >= STRONG_TOKEN_SCORE) {
240
+ return {
241
+ score:
242
+ FULL_COVERAGE_SCORE +
243
+ Math.min(
244
+ Math.floor((tokenSum - matched * MIN_TOKEN_SCORE) / (matched * 5)),
245
+ 8,
246
+ ),
247
+ reason,
248
+ matched,
249
+ total,
250
+ ...(alias ? {alias, aliasWords} : {}),
251
+ };
252
+ }
253
+
254
+ // Base the score on the STRONGEST concept that matched, plus a bonus per
255
+ // additional matched concept and a coverage term.
256
+ //
257
+ // Deliberately not the mean, and deliberately not divided by total query
258
+ // length. Dividing by total length penalizes verbose / low-fidelity prompts.
259
+ // Dividing by the number of MATCHED tokens (what this used to do) made the
260
+ // score non-monotonic: a second, weaker hit could drag the mean down by more
261
+ // than the coverage bonus added it back, so matching fewer terms well beat
262
+ // matching more terms partially. Concretely, `build "file browser"` scored
263
+ // two form wizards matching only "file" at 98, above the actual file browser
264
+ // matching both terms at 97 — and 98 clears the PAGE_DIRECT gate, so the
265
+ // wrong template was returned as a confident match.
266
+ //
267
+ // Taking the max keeps both properties: a verbose prompt is still scored on
268
+ // the concepts it did hit, and matching a superset of another candidate's
269
+ // terms can never score lower, since every term of the expression is
270
+ // non-decreasing in the set of matched tokens.
271
+ const coverage = matched / tokens.length;
272
+ const tokenScore = Math.round(
273
+ strongest + Math.min(matched - 1, 3) * 12 + coverage * 15,
274
+ );
275
+ if (full && full.score >= tokenScore) return asFull(full);
276
+ return {
277
+ score: tokenScore,
278
+ reason,
279
+ matched,
280
+ total,
281
+ ...(alias ? {alias, aliasWords} : {}),
282
+ };
283
+ }
@@ -0,0 +1,21 @@
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
+ export namespace SYNONYMS {
5
+ let dashboard: string[];
6
+ let login: string[];
7
+ let signup: string[];
8
+ let payment: string[];
9
+ let pricing: string[];
10
+ let chat: string[];
11
+ let settings: string[];
12
+ let calendar: string[];
13
+ let table: string[];
14
+ let gallery: string[];
15
+ let hero: string[];
16
+ let form: string[];
17
+ let profile: string[];
18
+ let documentation: string[];
19
+ let navigation: string[];
20
+ }
21
+ export const SYNONYM_INDEX: Map<string, Set<string>>;
@@ -0,0 +1,67 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Search's synonym map: product words an agent types, mapped to the
5
+ * catalog's vocabulary in both directions.
6
+ */
7
+
8
+ /**
9
+ * Synonym / intent map: product-language terms an agent is likely to type,
10
+ * expanded to the catalog's vocabulary so oblique queries still rank. Keys and
11
+ * values are matched bidirectionally (typing any value also pulls in the key
12
+ * and its siblings). Lowercase, single words or short phrases. Exported for
13
+ * `build`, whose page ranker expands a query with the same vocabulary.
14
+ */
15
+ export const SYNONYMS = {
16
+ dashboard: [
17
+ 'overview',
18
+ 'analytics',
19
+ 'kpi',
20
+ 'kpis',
21
+ 'metrics',
22
+ 'stats',
23
+ 'reporting',
24
+ 'insights',
25
+ 'control',
26
+ ],
27
+ login: ['signin', 'auth', 'authentication', 'sso', 'credentials', 'account'],
28
+ signup: ['register', 'registration', 'onboarding'],
29
+ payment: ['checkout', 'billing', 'card', 'pay', 'purchase', 'order'],
30
+ pricing: ['plans', 'plan', 'tiers', 'tier', 'subscription', 'subscriptions'],
31
+ chat: ['messaging', 'message', 'messages', 'conversation', 'inbox', 'dm'],
32
+ settings: ['preferences', 'config', 'configuration', 'account'],
33
+ calendar: ['schedule', 'scheduling', 'events', 'event', 'month', 'agenda'],
34
+ table: ['list', 'rows', 'records', 'grid', 'spreadsheet', 'datatable'],
35
+ gallery: ['photos', 'photo', 'images', 'image', 'pictures'],
36
+ hero: ['banner', 'splash', 'headline', 'landing'],
37
+ form: ['fields', 'input', 'inputs', 'survey'],
38
+ profile: ['bio', 'avatar', 'user'],
39
+ documentation: ['docs', 'reference', 'guide', 'api'],
40
+ navigation: ['nav', 'menu', 'sidebar'],
41
+ };
42
+
43
+ // Flatten into a token -> Set(expansions) lookup (bidirectional).
44
+ export const SYNONYM_INDEX = (() => {
45
+ /** @type {Map<string, Set<string>>} */
46
+ const idx = new Map();
47
+ /**
48
+ * @param {string} a
49
+ * @param {string} b
50
+ */
51
+ const add = (a, b) => {
52
+ let set = idx.get(a);
53
+ if (!set) {
54
+ set = new Set();
55
+ idx.set(a, set);
56
+ }
57
+ set.add(b);
58
+ };
59
+ for (const [key, vals] of Object.entries(SYNONYMS)) {
60
+ for (const v of vals) {
61
+ add(key, v);
62
+ add(v, key);
63
+ for (const v2 of vals) if (v2 !== v) add(v, v2);
64
+ }
65
+ }
66
+ return idx;
67
+ })();
@@ -0,0 +1,4 @@
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
+ export const template: import("../../foundation/route/route.mjs").Searcher<import("./tree.mjs").SearchRequest, import("./_adapter.mjs").SearchCatalog, import("./search.type.mjs").SearchResponse>;
@@ -0,0 +1,23 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file The template searcher: the main searcher's signals with its own
5
+ * weights, in _template-searcher.weights.json.
6
+ * @input {query, options} and the gathered catalog with its static data
7
+ * @output the search response, the lexical response's shape and size
8
+ * @position api/search; a topic child of the root dispatcher in tree.mjs
9
+ */
10
+
11
+ import {routeKit} from '../../foundation/route/route.mjs';
12
+ import {asWeights, rankWith} from './_main-searcher.mjs';
13
+
14
+ /** @type {import('./tree.mjs').SearchKit} */
15
+ const {defineSearcher} = routeKit;
16
+
17
+ export const template = defineSearcher({
18
+ id: 'template',
19
+ summary:
20
+ 'Ranks entries for queries that ask for a template, with weights of its own.',
21
+ run: (request, catalog, weights) =>
22
+ rankWith(request, catalog, asWeights(weights) ?? catalog.data.template),
23
+ });
@@ -0,0 +1,40 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Tests for the template searcher (_template-searcher.mjs): with its own weights
5
+ * it ranks a template first for a query that asks for one. Run against the real
6
+ * @astryxdesign/core.
7
+ */
8
+
9
+ import {describe, it, expect} from 'vitest';
10
+ import * as path from 'node:path';
11
+ import {fileURLToPath} from 'node:url';
12
+ import {gatherCandidates, loadSearchData} from './_adapter.mjs';
13
+ import {template} from './_template-searcher.mjs';
14
+
15
+ const REPO = path.resolve(
16
+ path.dirname(fileURLToPath(import.meta.url)),
17
+ '../../../..',
18
+ );
19
+
20
+ describe('template searcher', () => {
21
+ it('ranks a template near the top for a template query', async () => {
22
+ const catalog = {
23
+ ...(await gatherCandidates({cwd: REPO})),
24
+ data: loadSearchData(),
25
+ };
26
+ const answer = /** @type {any} */ (
27
+ await template.run(
28
+ {query: 'page of settings with a side menu', options: {cwd: REPO}},
29
+ catalog,
30
+ null,
31
+ )
32
+ );
33
+ expect(
34
+ answer.data.results
35
+ .slice(0, 3)
36
+ .some((/** @type {any} */ r) => r.domain === 'template'),
37
+ ).toBe(true);
38
+ expect(answer.data.results.length).toBeLessThanOrEqual(20);
39
+ });
40
+ });
@@ -0,0 +1 @@
1
+ {"w":[0.26388,-0.013032,0.516431,0.121331,0.254512,-0.261785,-0.688666,0.112076,-0.376705,0.111327,0.097715,0.065046,0.095694,-0.016049,-0.236365,0.094383,0.0,-0.003606,0.003684,0.067567,0.024298,-0.030137,0.324971,-0.059765,-0.008739,0.015668,0.011897,0.016649,-0.066041,0.056844,-0.055073,0.054001,0.137491,0.0,0.002283,-0.360264,0.210105,0.137841,0.14848,0.051643,0.401352,0.0,0.0,0.0,0.17537,-0.278616,0.138193,0.041566,0.02038,0.043262,0.134831,0.272071,-0.024414,-0.000472,0.013668,0.0,0.034693,0.004465,0.0],"mu":[0.164885,0.00511,0.225783,0.017021,0.147234,0.050213,0.178723,0.617872,0.005957,0.260431,0.758487,0.118373,0.062975,0.000497,0.08043,0.415077,0.054719,0.001375,4e-06,0.016249,0.004739,0.048162,0.095679,5.7e-05,0.00111,0.000198,0.001298,0.002502,1e-06,0.022962,0.00435,0.075387,0.157731,0.0,0.132838,0.026582,0.222958,0.37547,0.00064,0.098856,0.019517,0.0,0.0,0.0,0.014202,0.002142,0.017938,0.028668,2.6e-05,0.030184,0.001323,0.02033,0.363165,7.4e-05,0.000775,0.0,0.000152,0.000448,0.0],"sd":[0.30267,0.037002,0.399695,0.129352,0.35434,0.218385,0.383122,0.485909,0.076955,1.016246,1.780214,0.698764,0.119979,0.022138,0.262291,0.251643,1e-06,0.037042,0.002117,0.107524,0.058464,0.180391,0.248041,0.00567,0.021262,0.005551,0.018508,0.023617,0.00015,0.335998,0.139746,0.538946,0.804633,1e-06,0.783314,0.348833,1.042717,1.321848,0.07162,0.646141,0.275325,1e-06,1e-06,1e-06,0.066632,0.023566,0.076235,0.079854,0.002538,0.085732,0.008484,0.056206,0.296331,0.001967,0.027817,1e-06,0.012337,0.021154,1e-06]}