@astryxdesign/cli 0.7.2 → 0.7.3-canary.06872b4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/README.md +99 -66
  2. package/api/build/_adapter.d.mts +58 -0
  3. package/api/build/_adapter.mjs +115 -3
  4. package/api/build/blocks/blocks.d.mts +42 -0
  5. package/api/build/blocks/blocks.mjs +169 -0
  6. package/api/build/blocks/blocks.test.mjs +108 -0
  7. package/api/build/blocks/blocks.weights.json +1 -0
  8. package/api/build/build.mjs +25 -6
  9. package/api/build/kit/answer.d.mts +14 -0
  10. package/api/build/kit/answer.mjs +85 -0
  11. package/api/build/kit/answer.test.mjs +38 -0
  12. package/api/build/kit/kind.d.mts +25 -0
  13. package/api/build/kit/kind.mjs +170 -0
  14. package/api/build/kit/kit.d.mts +2 -4
  15. package/api/build/kit/kit.mjs +8 -398
  16. package/api/build/kit/rank.d.mts +3 -32
  17. package/api/build/kit/rank.mjs +25 -342
  18. package/api/build/kit/rank.test.mjs +2 -1
  19. package/api/build/kit/signals.d.mts +60 -0
  20. package/api/build/kit/signals.mjs +284 -0
  21. package/api/build/kit/start.d.mts +32 -0
  22. package/api/build/kit/start.mjs +163 -0
  23. package/api/build/kit/words.d.mts +72 -0
  24. package/api/build/kit/words.mjs +202 -0
  25. package/api/build/tree.d.mts +20 -0
  26. package/api/build/tree.mjs +52 -0
  27. package/api/build/tree.test.mjs +177 -0
  28. package/api/build/tree.weights.json +1 -0
  29. package/api/component/component.mjs +16 -2
  30. package/api/component/component.test.mjs +15 -0
  31. package/api/discover/_adapter.mjs +36 -9
  32. package/api/discover/_adapter.test.mjs +42 -0
  33. package/api/docs/_adapter.mjs +17 -5
  34. package/api/docs/docs.test.mjs +7 -1
  35. package/api/doctor/doctor.mjs +86 -12
  36. package/api/doctor/doctor.test.mjs +28 -3
  37. package/api/init/init.doc.mjs +20 -6
  38. package/api/init/init.test.mjs +18 -2
  39. package/api/init/init.type.d.mts +3 -3
  40. package/api/init/init.type.mjs +3 -3
  41. package/api/init/remove/remove.mjs +17 -4
  42. package/api/init/remove/remove.test.mjs +114 -2
  43. package/api/init/run/run.mjs +69 -6
  44. package/api/integration/authoring-checks.mjs +83 -9
  45. package/api/integration/pack-check.mjs +123 -4
  46. package/api/integration/pack-check.test.mjs +87 -0
  47. package/api/integration/validate-integration-case-disk.test.mjs +111 -0
  48. package/api/integration/validate-integration-gaps.test.mjs +1964 -0
  49. package/api/integration/validate-integration.d.mts +11 -2
  50. package/api/integration/validate-integration.mjs +226 -32
  51. package/api/integration/validate-integration.type.d.mts +58 -1
  52. package/api/integration/validate-integration.type.mjs +45 -2
  53. package/api/integration/validateIntegration.doc.mjs +1 -1
  54. package/api/search/ORDERING.md +28 -0
  55. package/api/search/_adapter.d.mts +66 -2
  56. package/api/search/_adapter.mjs +118 -10
  57. package/api/search/_candidate-score.d.mts +40 -0
  58. package/api/search/_candidate-score.mjs +238 -0
  59. package/api/search/_cards.json +6098 -0
  60. package/api/search/_compound.d.mts +50 -0
  61. package/api/search/_compound.mjs +170 -0
  62. package/api/search/_doc-searcher.d.mts +4 -0
  63. package/api/search/_doc-searcher.mjs +23 -0
  64. package/api/search/_doc-searcher.test.mjs +36 -0
  65. package/api/search/_doc-searcher.weights.json +1 -0
  66. package/api/search/_doc-text.d.mts +30 -0
  67. package/api/search/_doc-text.mjs +87 -0
  68. package/api/search/_entries.d.mts +52 -0
  69. package/api/search/_entries.mjs +158 -0
  70. package/api/search/_features.d.mts +35 -0
  71. package/api/search/_features.mjs +155 -0
  72. package/api/search/_gather-components.d.mts +26 -0
  73. package/api/search/_gather-components.mjs +284 -0
  74. package/api/search/_gather-docs.d.mts +20 -0
  75. package/api/search/_gather-docs.mjs +254 -0
  76. package/api/search/_gather-hooks.d.mts +12 -0
  77. package/api/search/_gather-hooks.mjs +52 -0
  78. package/api/search/_gather-templates.d.mts +25 -0
  79. package/api/search/_gather-templates.mjs +107 -0
  80. package/api/search/_lexical.d.mts +135 -0
  81. package/api/search/_lexical.mjs +351 -0
  82. package/api/search/_main-searcher.d.mts +19 -0
  83. package/api/search/_main-searcher.mjs +115 -0
  84. package/api/search/_main-searcher.test.mjs +106 -0
  85. package/api/search/_main-searcher.weights.json +1 -0
  86. package/api/search/_module-doc.d.mts +32 -0
  87. package/api/search/_module-doc.mjs +78 -0
  88. package/api/search/_order.d.mts +32 -0
  89. package/api/search/_order.mjs +162 -0
  90. package/api/search/_order.test.mjs +72 -0
  91. package/api/search/_priority.d.mts +50 -0
  92. package/api/search/_priority.mjs +139 -0
  93. package/api/search/_query-words.d.mts +25 -0
  94. package/api/search/_query-words.mjs +92 -0
  95. package/api/search/_query.d.mts +65 -0
  96. package/api/search/_query.mjs +176 -0
  97. package/api/search/_result.d.mts +97 -0
  98. package/api/search/_result.mjs +150 -0
  99. package/api/search/_score.d.mts +60 -0
  100. package/api/search/_score.mjs +283 -0
  101. package/api/search/_synonyms.d.mts +21 -0
  102. package/api/search/_synonyms.mjs +67 -0
  103. package/api/search/_template-searcher.d.mts +4 -0
  104. package/api/search/_template-searcher.mjs +23 -0
  105. package/api/search/_template-searcher.test.mjs +40 -0
  106. package/api/search/_template-searcher.weights.json +1 -0
  107. package/api/search/_titles.d.mts +33 -0
  108. package/api/search/_titles.mjs +65 -0
  109. package/api/search/_words.d.mts +64 -0
  110. package/api/search/_words.mjs +157 -0
  111. package/api/search/search.d.mts +22 -305
  112. package/api/search/search.doc.mjs +7 -5
  113. package/api/search/search.mjs +53 -2483
  114. package/api/search/search.type.d.mts +6 -1
  115. package/api/search/search.type.mjs +6 -1
  116. package/api/search/tree.d.mts +15 -0
  117. package/api/search/tree.mjs +40 -0
  118. package/api/search/tree.test.mjs +66 -0
  119. package/api/search/tree.weights.json +1 -0
  120. package/api/theme/build/build.packed-old-core.test.mjs +1 -0
  121. package/api/upgrade/_adapter.d.mts +57 -9
  122. package/api/upgrade/_adapter.mjs +198 -28
  123. package/api/upgrade/registry/registry.d.mts +10 -4
  124. package/api/upgrade/registry/registry.mjs +33 -16
  125. package/api/upgrade/registry/registry.test.mjs +85 -0
  126. package/api/upgrade/run/run.d.mts +4 -2
  127. package/api/upgrade/run/run.mjs +13 -2
  128. package/api/upgrade/run/run.test.mjs +417 -3
  129. package/api/upgrade/upgrade.doc.mjs +10 -5
  130. package/api/upgrade/upgrade.mjs +13 -2
  131. package/api/upgrade/upgrade.test.mjs +194 -0
  132. package/api/upgrade/upgrade.type.d.mts +5 -3
  133. package/api/upgrade/upgrade.type.mjs +8 -6
  134. package/assets/codemods/transforms/v0.7.0/__tests__/validate-data-token-ownership.test.mjs +11 -0
  135. package/assets/codemods/transforms/v0.7.0/validate-data-token-ownership.mjs +3 -3
  136. package/assets/docs/is-my-code-good.doc.mjs +287 -0
  137. package/assets/docs/tree/api.doc.mjs +6 -0
  138. package/assets/docs/tree/host-entry.doc.mjs +128 -0
  139. package/assets/docs/tree/template-grading-rubric.doc.mjs +16 -37
  140. package/assets/docs/tree/tokens-data.doc.mjs +2 -2
  141. package/assets/docs/working-with-ai.doc.mjs +6 -2
  142. package/assets/templates/themes/butter/butterTheme.ts +4 -2
  143. package/assets/templates/themes/stone/stoneTheme.ts +4 -2
  144. package/assets/templates/themes/y2k/y2kTheme.ts +4 -2
  145. package/authoring/debug/parse.d.mts +1 -1
  146. package/clients/cli/bin/astryx.mjs +7 -78
  147. package/clients/cli/commands/doctor.mjs +30 -0
  148. package/clients/cli/commands/doctor.test.mjs +65 -0
  149. package/clients/cli/commands/init.behavior.test.mjs +230 -1
  150. package/clients/cli/commands/init.doc.mjs +28 -9
  151. package/clients/cli/commands/init.flag-help.test.mjs +1 -0
  152. package/clients/cli/commands/init.mjs +2 -2
  153. package/clients/cli/commands/outside-project.test.mjs +2 -1
  154. package/clients/cli/commands/setup-nudge.test.mjs +61 -0
  155. package/clients/cli/commands/upgrade.doc.mjs +4 -2
  156. package/clients/cli/commands/upgrade.mjs +9 -2
  157. package/clients/cli/commands/upgrade.subfolder-path.test.mjs +97 -0
  158. package/clients/cli/index.mjs +19 -1
  159. package/clients/cli/run.mjs +108 -0
  160. package/foundation/agent-docs/agent-doc-state.d.mts +20 -7
  161. package/foundation/agent-docs/agent-doc-state.mjs +35 -8
  162. package/foundation/agent-docs/agent-docs.d.mts +100 -12
  163. package/foundation/agent-docs/agent-docs.mjs +1010 -99
  164. package/foundation/agent-docs/agent-docs.path-safety.test.mjs +1239 -17
  165. package/foundation/agent-docs/agent-docs.test.mjs +678 -35
  166. package/foundation/config/project.mjs +20 -22
  167. package/foundation/config/project.test.mjs +56 -0
  168. package/foundation/discovery/component-discovery.d.mts +37 -0
  169. package/foundation/discovery/component-discovery.mjs +80 -5
  170. package/foundation/discovery/component-loader.d.mts +1 -1
  171. package/foundation/discovery/docs-discovery.mjs +14 -1
  172. package/foundation/discovery/module-exports.d.mts +41 -0
  173. package/foundation/discovery/module-exports.mjs +253 -0
  174. package/foundation/discovery/template-adapter.mjs +18 -1
  175. package/foundation/discovery/template-needs.mjs +1 -44
  176. package/foundation/discovery/theme-discovery.mjs +147 -3
  177. package/foundation/doc-compiler/doc-loads.test.mjs +8 -0
  178. package/foundation/doc-compiler/read.mjs +59 -1
  179. package/foundation/doc-compiler/read.test.mjs +11 -2
  180. package/foundation/fs/paths.d.mts +70 -8
  181. package/foundation/fs/paths.mjs +556 -38
  182. package/foundation/fs/paths.test.mjs +430 -54
  183. package/foundation/integrations/autolink.d.mts +2 -1
  184. package/foundation/integrations/autolink.mjs +4 -13
  185. package/foundation/integrations/computer-integrations.d.mts +6 -3
  186. package/foundation/integrations/computer-integrations.mjs +38 -9
  187. package/foundation/integrations/contribution-inventory.d.mts +15 -20
  188. package/foundation/integrations/contribution-inventory.mjs +41 -5
  189. package/foundation/integrations/integrations.d.mts +27 -1
  190. package/foundation/integrations/integrations.mjs +61 -2
  191. package/foundation/integrations/validate-contributions.d.mts +115 -0
  192. package/foundation/integrations/validate-contributions.mjs +1168 -13
  193. package/foundation/release/handoff.d.mts +23 -4
  194. package/foundation/release/handoff.mjs +175 -8
  195. package/foundation/release/run-context.mjs +14 -0
  196. package/foundation/response/response-types.doc.mjs +1 -1
  197. package/foundation/route/route.d.mts +285 -0
  198. package/foundation/route/route.mjs +324 -0
  199. package/foundation/route/route.test.mjs +153 -0
  200. package/foundation/runtime/hosted-runtime.test.mjs +403 -0
  201. package/foundation/runtime/locked-versions.d.mts +32 -0
  202. package/foundation/runtime/locked-versions.mjs +65 -0
  203. package/foundation/runtime/package-root.d.mts +91 -0
  204. package/foundation/runtime/package-root.mjs +174 -0
  205. package/foundation/runtime/package-set.d.mts +62 -0
  206. package/foundation/runtime/package-set.mjs +130 -0
  207. package/foundation/runtime/package-set.test.mjs +432 -0
  208. package/host/door-conformance.test.mjs +274 -0
  209. package/host/index.d.ts +116 -0
  210. package/host/index.mjs +295 -0
  211. package/host/index.test.mjs +302 -0
  212. package/package.json +14 -9
@@ -0,0 +1,65 @@
1
+ // @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
2
+ // DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
3
+
4
+ /**
5
+ * Split a query into meaningful content tokens (lowercased, stopwords + very
6
+ * short words removed). Empty for single-word queries (callers fall back to
7
+ * whole-phrase scoring).
8
+ * @param {string} term - Already-lowercased query.
9
+ * @returns {string[]}
10
+ */
11
+ export function tokenizeQuery(term: string): string[];
12
+ /**
13
+ * Score a candidate against a query, handling multi-word natural language.
14
+ * Tries the whole phrase (so exact/near matches still win) AND a per-token
15
+ * pass (so "data table with filters" matches `table-page` via table+filter),
16
+ * and returns whichever is stronger.
17
+ *
18
+ * @param {string} term - Lowercased full query.
19
+ * @param {string[]} tokens - Content tokens from tokenizeQuery(term).
20
+ * @param {object} candidate
21
+ * @returns {{score: number, reason: string} | null}
22
+ */
23
+ /**
24
+ * The words of a title or query, lowercased, without punctuation or code ticks.
25
+ * @param {string} text
26
+ * @returns {string[]}
27
+ */
28
+ export function phraseWords(text: string): string[];
29
+ /**
30
+ * Whether two words are the same word, allowing a plural on either side, so
31
+ * `data attributes selector` still reads "Data attribute selectors".
32
+ * @param {string} a
33
+ * @param {string} b
34
+ */
35
+ export function samePhraseWord(a: string, b: string): boolean;
36
+ /**
37
+ * Whether two phrases are the same words, in order, ignoring case,
38
+ * punctuation, and a plural on any word: `side panel` is "Side panels", and
39
+ * `headers and footers` is `header-and-footer`.
40
+ * @param {string} a
41
+ * @param {string} b
42
+ * @returns {boolean}
43
+ */
44
+ export function samePhrase(a: string, b: string): boolean;
45
+ /**
46
+ * Whether `plural` is the plural of `word`: `integrations` of `integration`,
47
+ * `boxes` of `box`. `es` only follows s, x, z, ch, or sh, so `notes` is not a
48
+ * plural of `not`.
49
+ * @param {string} plural
50
+ * @param {string} word
51
+ */
52
+ export function pluralOf(plural: string, word: string): boolean;
53
+ /** Valid domain filters for `--type`. */
54
+ export const SEARCH_DOMAINS: string[];
55
+ /**
56
+ * The domains a search reads without @astryxdesign/core. An open search outside
57
+ * an app covers these alone.
58
+ */
59
+ export const CORELESS_DOMAINS: string[];
60
+ /**
61
+ * Filler words stripped from multi-word queries so natural-language phrasing
62
+ * ("a page where you can see business stats") ranks on its content words.
63
+ * Exported for `build`, whose page ranker strips the same words.
64
+ */
65
+ export const STOPWORDS: Set<string>;
@@ -0,0 +1,176 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Reading a search query: its domains, stopwords, content tokens, and
5
+ * phrase words.
6
+ */
7
+
8
+ import {unlinkText} from '../../foundation/doc-compiler/links.mjs';
9
+ import {sameWord} from './_words.mjs';
10
+
11
+ /** Valid domain filters for `--type`. */
12
+ export const SEARCH_DOMAINS = ['component', 'hook', 'doc', 'template', 'theme'];
13
+
14
+ /**
15
+ * The domains a search reads without @astryxdesign/core. An open search outside
16
+ * an app covers these alone.
17
+ */
18
+ export const CORELESS_DOMAINS = ['doc', 'theme'];
19
+
20
+ /**
21
+ * Filler words stripped from multi-word queries so natural-language phrasing
22
+ * ("a page where you can see business stats") ranks on its content words.
23
+ * Exported for `build`, whose page ranker strips the same words.
24
+ */
25
+ export const STOPWORDS = new Set([
26
+ 'a',
27
+ 'an',
28
+ 'the',
29
+ 'of',
30
+ 'for',
31
+ 'to',
32
+ 'with',
33
+ 'and',
34
+ 'or',
35
+ 'in',
36
+ 'on',
37
+ 'at',
38
+ 'by',
39
+ 'that',
40
+ 'this',
41
+ 'my',
42
+ 'your',
43
+ 'our',
44
+ 'their',
45
+ 'is',
46
+ 'are',
47
+ 'be',
48
+ 'it',
49
+ 'its',
50
+ 'as',
51
+ 'from',
52
+ 'page',
53
+ 'screen',
54
+ 'app',
55
+ 'application',
56
+ 'view',
57
+ 'where',
58
+ 'you',
59
+ 'can',
60
+ 'some',
61
+ 'like',
62
+ 'just',
63
+ 'basically',
64
+ 'kinda',
65
+ 'want',
66
+ 'wants',
67
+ 'need',
68
+ 'needs',
69
+ 'something',
70
+ 'thing',
71
+ 'things',
72
+ 'build',
73
+ 'make',
74
+ 'create',
75
+ 'i',
76
+ 'me',
77
+ 'we',
78
+ 'us',
79
+ 'so',
80
+ 'up',
81
+ 'out',
82
+ 'over',
83
+ 'side',
84
+ 'one',
85
+ 'big',
86
+ ]);
87
+
88
+ /**
89
+ * Split a query into meaningful content tokens (lowercased, stopwords + very
90
+ * short words removed). Empty for single-word queries (callers fall back to
91
+ * whole-phrase scoring).
92
+ * @param {string} term - Already-lowercased query.
93
+ * @returns {string[]}
94
+ */
95
+ export function tokenizeQuery(term) {
96
+ return (
97
+ term
98
+ .split(/\s+/)
99
+ // Strip only leading/trailing punctuation; keep joined identifiers intact
100
+ // (e.g. "foo_bar" stays one token) so gibberish stays gibberish.
101
+ .map(t => t.replace(/^[^a-z0-9]+|[^a-z0-9]+$/g, ''))
102
+ .filter(t => t.length >= 2 && !STOPWORDS.has(t))
103
+ );
104
+ }
105
+
106
+ /**
107
+ * Score a candidate against a query, handling multi-word natural language.
108
+ * Tries the whole phrase (so exact/near matches still win) AND a per-token
109
+ * pass (so "data table with filters" matches `table-page` via table+filter),
110
+ * and returns whichever is stronger.
111
+ *
112
+ * @param {string} term - Lowercased full query.
113
+ * @param {string[]} tokens - Content tokens from tokenizeQuery(term).
114
+ * @param {object} candidate
115
+ * @returns {{score: number, reason: string} | null}
116
+ */
117
+
118
+ /**
119
+ * The words of a title or query, lowercased, without punctuation or code ticks.
120
+ * @param {string} text
121
+ * @returns {string[]}
122
+ */
123
+ export function phraseWords(text) {
124
+ return (
125
+ unlinkText(text)
126
+ .toLowerCase()
127
+ .match(/[a-z0-9]+/g) ?? []
128
+ );
129
+ }
130
+
131
+ /**
132
+ * Whether two words are the same word, allowing a plural on either side, so
133
+ * `data attributes selector` still reads "Data attribute selectors".
134
+ * @param {string} a
135
+ * @param {string} b
136
+ */
137
+ export function samePhraseWord(a, b) {
138
+ return (
139
+ a === b ||
140
+ `${a}s` === b ||
141
+ `${b}s` === a ||
142
+ `${a}es` === b ||
143
+ `${b}es` === a
144
+ );
145
+ }
146
+
147
+ /**
148
+ * Whether two phrases are the same words, in order, ignoring case,
149
+ * punctuation, and a plural on any word: `side panel` is "Side panels", and
150
+ * `headers and footers` is `header-and-footer`.
151
+ * @param {string} a
152
+ * @param {string} b
153
+ * @returns {boolean}
154
+ */
155
+ export function samePhrase(a, b) {
156
+ const x = phraseWords(a);
157
+ const y = phraseWords(b);
158
+ return (
159
+ x.length > 0 &&
160
+ x.length === y.length &&
161
+ x.every((w, k) => samePhraseWord(w, y[k]) || sameWord(w, y[k]))
162
+ );
163
+ }
164
+
165
+ /**
166
+ * Whether `plural` is the plural of `word`: `integrations` of `integration`,
167
+ * `boxes` of `box`. `es` only follows s, x, z, ch, or sh, so `notes` is not a
168
+ * plural of `not`.
169
+ * @param {string} plural
170
+ * @param {string} word
171
+ */
172
+ export function pluralOf(plural, word) {
173
+ if (word.length < 3) return false;
174
+ if (plural === `${word}s`) return true;
175
+ return /(?:s|x|z|ch|sh)$/.test(word) && plural === `${word}es`;
176
+ }
@@ -0,0 +1,97 @@
1
+ // @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
2
+ // DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
3
+
4
+ /** @typedef {import('./_lexical.mjs').Candidate} Candidate */
5
+ /**
6
+ * Map a scored candidate to its public, actionable result shape. Each result
7
+ * carries enough to act on it: the domain, name, a one-line description, and
8
+ * the follow-up command (and import path where relevant).
9
+ *
10
+ * @param {Candidate} c - candidate
11
+ * @param {number} score
12
+ * @param {string} reason
13
+ * @param {number} matchedTerms - Query concepts this result answered.
14
+ * @param {number} queryTerms - Query concepts there were to answer.
15
+ * @param {boolean} [outside] - The search runs outside a project, where a
16
+ * theme cannot be added to an app, so its command forks the source instead
17
+ * (spec AST-081 FR3).
18
+ */
19
+ export function toResult(c: Candidate, score: number, reason: string, matchedTerms: number, queryTerms: number, outside?: boolean): {
20
+ domain: "component" | "hook" | "theme" | "doc" | "template";
21
+ name: string;
22
+ package: string | undefined;
23
+ score: number;
24
+ reason: string;
25
+ description: string;
26
+ } | {
27
+ import: string | undefined;
28
+ command: string;
29
+ domain: "component" | "hook" | "theme" | "doc" | "template";
30
+ name: string;
31
+ package: string | undefined;
32
+ score: number;
33
+ reason: string;
34
+ description: string;
35
+ } | {
36
+ parent?: string | undefined;
37
+ title: string | undefined;
38
+ command: string;
39
+ section?: string | undefined;
40
+ name: string;
41
+ domain: "component" | "hook" | "theme" | "doc" | "template";
42
+ package: string | undefined;
43
+ score: number;
44
+ reason: string;
45
+ description: string;
46
+ } | {
47
+ displayName: string | undefined;
48
+ kind: "page" | "block" | undefined;
49
+ command: string;
50
+ domain: "component" | "hook" | "theme" | "doc" | "template";
51
+ name: string;
52
+ package: string | undefined;
53
+ score: number;
54
+ reason: string;
55
+ description: string;
56
+ } | {
57
+ displayName: string | undefined;
58
+ command: string;
59
+ domain: "component" | "hook" | "theme" | "doc" | "template";
60
+ name: string;
61
+ package: string | undefined;
62
+ score: number;
63
+ reason: string;
64
+ description: string;
65
+ };
66
+ /**
67
+ * Throws the search argument errors: an empty query, an unknown type, a limit
68
+ * that is not a positive integer.
69
+ *
70
+ * @param {string} query
71
+ * @param {{type?: string, limit?: number}} options
72
+ */
73
+ export function checkSearchArgs(query: string, options: {
74
+ type?: string;
75
+ limit?: number;
76
+ }): void;
77
+ /**
78
+ * The components (name and keywords) a search response was scored against, or
79
+ * null when that search was narrowed away from components.
80
+ * @param {object} response
81
+ * @returns {{name: string, keywords: string[]}[] | null}
82
+ */
83
+ export function searchedComponents(response: object): {
84
+ name: string;
85
+ keywords: string[];
86
+ }[] | null;
87
+ /**
88
+ * The components each search response was scored against, by response:
89
+ * `build` reads them to tell a part of a page from a page without gathering
90
+ * them again. Module-private, so they never enter search's JSON.
91
+ * @type {WeakMap<object, {name: string, keywords: string[]}[]>}
92
+ */
93
+ export const searchedComponentsOf: WeakMap<object, {
94
+ name: string;
95
+ keywords: string[];
96
+ }[]>;
97
+ export type Candidate = import("./_lexical.mjs").Candidate;
@@ -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;