@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
@@ -42,32 +42,25 @@
42
42
  * matched terms of its own ("a small comparison note" does not make a
43
43
  * dashboard the comparison dashboard), and a base too weak to start on its
44
44
  * own displaces no variant ("a funnel for a dashboard" is the funnel).
45
- *
46
- * Parts (spec:AST-048/FR3). "A date range picker" asks for a part of a page,
47
- * not a page, and the system's own components say what a part is: an idea
48
- * whose head noun is a word of a component's name or keywords, Core's or an
49
- * integration's, asks for that part, unless the noun is a family word ("a data
50
- * table") or the idea lists a page's worth of pieces. Words search drops as
51
- * stopwords ("page", "screen", "app", "view") name no component, so "a calendar
52
- * page" asks for a page and "a calendar" for a part. A part starts from the
53
- * base template of the family the idea names ("an empty state for a settings
54
- * page"), else from the app shell. An idea that changes a page or part the
55
- * builder already has starts from the app shell too: that page is the
56
- * builder's to keep (FR9), and no template scaffolds it. "Existing" marks one
57
- * when its phrase names a page family or a component ("add a column to the
58
- * existing table"), unless the idea asks for a new page ("a new dashboard like
59
- * the existing dashboard", "clone the existing dashboard as a new page"). A
60
- * bare "existing" phrase that names a page ("existing reports dashboard with a
61
- * date filter") reads as the builder's own page.
62
- *
63
- * Family words come from the templates' own ids — a word in the ids of two or
64
- * more templates of one family, plus the family's name when a template carries
65
- * it — never from a list kept here or from synonyms, so an integration's
66
- * templates join the vocabulary by being named like their family, and "a
67
- * landing page" is not a hero because "landing" is a synonym of "hero".
68
45
  */
69
46
 
70
- import {stem, STOPWORDS, SYNONYMS} from '../../search/search.mjs';
47
+ import {
48
+ HEAD_END,
49
+ SHELL_FAMILY,
50
+ terms,
51
+ modifiersOf,
52
+ pairsOf,
53
+ containersOf,
54
+ normalized,
55
+ SYNONYMS_OF,
56
+ familyWordsOf,
57
+ } from './words.mjs';
58
+
59
+ /**
60
+ * @typedef {import('./words.mjs').PageTemplate} PageTemplate
61
+ * @typedef {import('./words.mjs').RankedPage} RankedPage
62
+ * @typedef {import('./words.mjs').IdeaKind} IdeaKind
63
+ */
71
64
 
72
65
  /**
73
66
  * How much a term counts by where the template names it. The id and category
@@ -82,37 +75,44 @@ const FIELD_WEIGHT = {
82
75
  keywords: 2,
83
76
  description: 1,
84
77
  };
78
+
85
79
  /**
86
80
  * A synonym hit counts half a direct hit: a synonym says the idea is near the
87
81
  * page, not that it names it.
88
82
  */
89
83
  const SYNONYM_SHARE = 0.5;
84
+
90
85
  /**
91
86
  * A container word ("in a modal") counts double: it names the page's frame,
92
87
  * which is what a template is.
93
88
  */
94
89
  const CONTAINER = 2;
90
+
95
91
  /**
96
92
  * A word that only modifies another ("product" in "product response") counts
97
93
  * half: it describes its head, not the page.
98
94
  */
99
95
  const MODIFIER = 0.5;
96
+
100
97
  /**
101
98
  * Added when the idea's head names the template's family: more than any one
102
99
  * word scores (an id word only one template carries is worth about ten), so the
103
100
  * head's family outranks a rarer word among the parts.
104
101
  */
105
102
  const HEAD_FAMILY = 12;
103
+
106
104
  /**
107
105
  * Added when the family is named anywhere else in the idea: about a description
108
106
  * word, enough to settle a near tie.
109
107
  */
110
108
  const BODY_FAMILY = 2;
109
+
111
110
  /**
112
111
  * Added to a family's base template when the idea names that family: settles a
113
112
  * near tie toward the family's default page.
114
113
  */
115
114
  const FAMILY_BASE = 2;
115
+
116
116
  /**
117
117
  * The least a start must score, and the evidence it needs: two matched terms,
118
118
  * or the idea naming its family. One rare word alone is how a tic-tac-toe game
@@ -120,213 +120,8 @@ const FAMILY_BASE = 2;
120
120
  * displace its family's base template.
121
121
  */
122
122
  const START_SCORE = 10;
123
- const START_TERMS = 2;
124
- /**
125
- * How many listed pieces make an idea a page: a part is one thing with a detail
126
- * or two ("a status pill with a tooltip"), while three pieces, two commas
127
- * apart, describe a page.
128
- */
129
- const PAGE_PIECES = 3;
130
-
131
- /**
132
- * Words search drops that name layout: "side navigation", "side by side".
133
- */
134
- const LAYOUT_WORDS = new Set(['side']);
135
-
136
- /**
137
- * Where an idea's head ends: its first clause, before the parts it lists.
138
- */
139
- const HEAD_END =
140
- /:|,|;|\(|\s[-\u2013\u2014]\s|\b(?:with|showing|listing|for|that|where|plus|including|containing|featuring|which|to|from|of)\b/i;
141
-
142
- /**
143
- * Frames a page can live in, above whatever is underneath. A container phrase
144
- * counts only when it names one of these.
145
- */
146
- const OVERLAY_FRAMES = new Set([
147
- 'modal',
148
- 'dialog',
149
- 'drawer',
150
- 'sheet',
151
- 'popup',
152
- 'overlay',
153
- ]);
154
-
155
- /**
156
- * The word that marks a page or part the builder already has, when its phrase
157
- * names one (see `changesExistingPage`).
158
- */
159
- const EXISTING = /\bexisting\b/i;
160
-
161
- /**
162
- * The word that marks an idea asking for a new page ("a new dashboard", "as a
163
- * new page"), which is never a change to one the builder has.
164
- */
165
- const NEW = /\bnew\b/;
166
-
167
- /**
168
- * The family whose templates are app chrome rather than page content: the
169
- * explicit `Shell -` category (architecture:template-authoring/INV7).
170
- */
171
- const SHELL_FAMILY = 'Shell';
172
-
173
- /** A container phrase: "in a modal", "inside the side panel". */
174
- const CONTAINER_PHRASE =
175
- /\b(?:in|inside|within)\s+(?:a|an|the)\s+([a-z-]+)(?:\s+([a-z-]+))?/gi;
176
-
177
- /**
178
- * Where a phrase ends, for finding the word that heads it: punctuation and
179
- * the words that join phrases.
180
- */
181
- const PHRASE_END =
182
- /[^a-z0-9\s-]+|\s(?:and|or|with|for|of|to|in|on|at|by|from|the|a|an|plus)\s/;
183
-
184
- /** @param {string} t */
185
- const isContentWord = t =>
186
- t.length >= 2 && (LAYOUT_WORDS.has(t) || !STOPWORDS.has(t));
187
-
188
- /**
189
- * Content terms of a text: lowercase alphanumeric words, stopwords removed,
190
- * stemmed.
191
- * @param {string} text
192
- * @returns {string[]}
193
- */
194
- function terms(text) {
195
- return (
196
- String(text)
197
- .toLowerCase()
198
- .match(/[a-z0-9]+/g) ?? []
199
- )
200
- .filter(isContentWord)
201
- .map(stem);
202
- }
203
-
204
- /**
205
- * The terms of an idea that only ever modify another word, each with the words
206
- * it modifies: in each phrase the last content word is its head, and the ones
207
- * before it describe the next.
208
- * @param {string} query
209
- * @returns {Map<string, Set<string>>}
210
- */
211
- function modifiersOf(query) {
212
- const heads = new Set();
213
- /** @type {Map<string, Set<string>>} */
214
- const modifiers = new Map();
215
- for (const phrase of String(query).toLowerCase().split(PHRASE_END)) {
216
- const words = terms(phrase);
217
- words.forEach((w, i) => {
218
- if (i === words.length - 1) heads.add(w);
219
- else modifiers.set(w, (modifiers.get(w) ?? new Set()).add(words[i + 1]));
220
- });
221
- }
222
- for (const h of heads) modifiers.delete(h);
223
- return modifiers;
224
- }
225
-
226
- /**
227
- * Adjacent term pairs a text names, "a b", for telling a template that names
228
- * a whole compound ("executive summary") from one that names only its parts.
229
- * @param {string[]} texts
230
- * @returns {Set<string>}
231
- */
232
- function pairsOf(texts) {
233
- const pairs = new Set();
234
- for (const text of texts) {
235
- for (const phrase of String(text).toLowerCase().split(PHRASE_END)) {
236
- const words = terms(phrase);
237
- for (let i = 1; i < words.length; i++)
238
- pairs.add(`${words[i - 1]} ${words[i]}`);
239
- }
240
- }
241
- return pairs;
242
- }
243
-
244
- /**
245
- * The terms of an idea that name its frame: "modal" in "in a modal". A card, a
246
- * table or an existing page after "in a" is where a part goes, not a frame.
247
- * @param {string} query
248
- * @returns {Set<string>}
249
- */
250
- function containersOf(query) {
251
- const found = new Set();
252
- for (const m of String(query).matchAll(CONTAINER_PHRASE)) {
253
- for (const t of terms(`${m[1]} ${m[2] ?? ''}`)) {
254
- if (OVERLAY_FRAMES.has(t)) found.add(t);
255
- }
256
- }
257
- return found;
258
- }
259
123
 
260
- /** @param {string} text */
261
- const normalized = text =>
262
- (
263
- String(text)
264
- .toLowerCase()
265
- .match(/[a-z0-9]+/g) ?? []
266
- ).join(' ');
267
-
268
- /** Stemmed synonym lookup built from search's vocabulary, both directions. */
269
- const SYNONYMS_OF = (() => {
270
- /** @type {Map<string, Set<string>>} */
271
- const index = new Map();
272
- /** @param {string} a @param {string} b */
273
- const add = (a, b) => {
274
- const set = index.get(a) ?? new Set();
275
- set.add(b);
276
- index.set(a, set);
277
- };
278
- for (const [key, values] of Object.entries(SYNONYMS)) {
279
- const k = stem(key);
280
- for (const value of values) {
281
- const v = stem(value);
282
- add(k, v);
283
- add(v, k);
284
- for (const other of values) if (other !== value) add(v, stem(other));
285
- }
286
- }
287
- return index;
288
- })();
289
-
290
- /**
291
- * @typedef {import('../_adapter.mjs').PageTemplate} PageTemplate
292
- * @typedef {import('../_adapter.mjs').ComponentWords} ComponentWords
293
- * @typedef {{name: string, score: number, hits: number, familyNamed: boolean, containerMatched: boolean, family: string, base: boolean, matched: Set<string>}} RankedPage
294
- * @typedef {'page' | 'part' | 'edit'} IdeaKind
295
- */
296
-
297
- /** @param {PageTemplate} page */
298
- const familyOf = page => (page.category.split(' - ')[0] ?? '').trim();
299
-
300
- /**
301
- * Family words, from the templates' own ids: a word in the ids of two or more
302
- * templates of one family, plus the family's name when a template carries it.
303
- * Each word maps to its family.
304
- * @param {PageTemplate[]} pages
305
- * @returns {Map<string, string>}
306
- */
307
- function familyWordsOf(pages) {
308
- /** @type {Map<string, Map<string, number>>} */
309
- const idWords = new Map();
310
- for (const page of pages) {
311
- const family = familyOf(page);
312
- const counts = idWords.get(family) ?? new Map();
313
- for (const w of new Set(page.name.split('-'))) {
314
- if (w.length >= 3) counts.set(stem(w), (counts.get(stem(w)) ?? 0) + 1);
315
- }
316
- idWords.set(family, counts);
317
- }
318
- /** @type {Map<string, string>} */
319
- const familyOfWord = new Map();
320
- for (const [family, counts] of idWords) {
321
- const heads = [...counts].filter(([, c]) => c >= 2).map(([w]) => w);
322
- const name = normalized(family).split(' ').pop() ?? '';
323
- if (name.length >= 3 && counts.has(stem(name))) heads.push(stem(name));
324
- for (const head of heads) {
325
- if (!familyOfWord.has(head)) familyOfWord.set(head, family);
326
- }
327
- }
328
- return familyOfWord;
329
- }
124
+ const START_TERMS = 2;
330
125
 
331
126
  /**
332
127
  * Rank page templates against an idea, best first. Ties go to the template
@@ -466,118 +261,6 @@ function baseFirst(ranked) {
466
261
  : [base, ...ranked.filter(r => r !== base)];
467
262
  }
468
263
 
469
- /**
470
- * What an idea asks for (spec:AST-048/FR3): a whole `page`; a `part` of one,
471
- * when its head noun names one of the system's components and it lists fewer
472
- * than PAGE_PIECES pieces; or an `edit` of a page the builder already has.
473
- *
474
- * @param {string} query
475
- * @param {PageTemplate[]} pages
476
- * @param {ComponentWords[]} components
477
- * @returns {IdeaKind}
478
- */
479
- export function ideaKind(query, pages, components) {
480
- const text = String(query);
481
- const familyWords = familyWordsOf(pages);
482
- if (changesExistingPage(text, familyWords, components)) return 'edit';
483
- // The head's last word, page words included: "calendar" is the noun of "a
484
- // calendar", "page" the noun of "a calendar page". An overlay frame names
485
- // where the thing lives, not the thing: "drafts" is the noun of "drafts in
486
- // a modal".
487
- const head = (text.split(HEAD_END)[0] ?? '').replace(CONTAINER_PHRASE, m =>
488
- containersOf(m).size > 0 ? ' ' : m,
489
- );
490
- const words = (head.toLowerCase().match(/[a-z0-9]+/g) ?? []).filter(
491
- w => w.length >= 2,
492
- );
493
- const noun = stem(words[words.length - 1] ?? '');
494
- if (familyWords.has(noun)) return 'page';
495
- const pieces = (text.match(/,/g) ?? []).length + 1;
496
- if (pieces >= PAGE_PIECES) return 'page';
497
- return components.some(c => componentTerms(c).includes(noun))
498
- ? 'part'
499
- : 'page';
500
- }
501
-
502
- /**
503
- * Whether an idea changes a page or part the builder already has: "existing"
504
- * in the same phrase as a word that names a family of page templates or one of
505
- * the system's components ("the existing incidents table", "the existing
506
- * banner"). A new page "inspired by the existing one", or "existing users" on a
507
- * new page, names neither, and an idea that asks for a new page is no change
508
- * whatever it names.
509
- * @param {string} text
510
- * @param {Map<string, string>} familyWords
511
- * @param {ComponentWords[]} components
512
- * @returns {boolean}
513
- */
514
- function changesExistingPage(text, familyWords, components) {
515
- const componentWords = new Set(components.flatMap(nameTerms));
516
- const phrases = text.toLowerCase().split(PHRASE_END);
517
- if (asksNewPage(phrases, familyWords)) return false;
518
- return phrases.some(phrase => {
519
- const at = phrase.search(EXISTING);
520
- return (
521
- at >= 0 &&
522
- terms(phrase.slice(at)).some(
523
- t => familyWords.has(t) || componentWords.has(t),
524
- )
525
- );
526
- });
527
- }
528
-
529
- /**
530
- * Whether an idea asks for a new page: "new" before a word that names a family
531
- * of page templates ("a new dashboard"), or before only words search drops,
532
- * such as "page" ("as a new page"). "A new column" asks for a part.
533
- * @param {string[]} phrases
534
- * @param {Map<string, string>} familyWords
535
- * @returns {boolean}
536
- */
537
- function asksNewPage(phrases, familyWords) {
538
- return phrases.some(phrase => {
539
- const at = phrase.search(NEW);
540
- if (at < 0) return false;
541
- const after = terms(phrase.slice(at + 'new'.length));
542
- return after.length === 0 || familyWords.has(after[0]);
543
- });
544
- }
545
-
546
- /**
547
- * Whether an idea asks for a new page (see `asksNewPage`), from the idea and
548
- * the project's page templates.
549
- * @param {string} query
550
- * @param {PageTemplate[]} pages
551
- * @returns {boolean}
552
- */
553
- export function asksForNewPage(query, pages) {
554
- return asksNewPage(
555
- String(query).toLowerCase().split(PHRASE_END),
556
- familyWordsOf(pages),
557
- );
558
- }
559
-
560
- /**
561
- * The words of a component's name: "DateRangeInput" is date, range, input.
562
- * @param {ComponentWords} component
563
- * @returns {string[]}
564
- */
565
- function nameTerms(component) {
566
- return terms(component.name.replace(/([a-z0-9])([A-Z])/g, '$1 $2'));
567
- }
568
-
569
- /**
570
- * The terms a component answers to: the words of its name and of its keywords.
571
- * @param {ComponentWords} component
572
- * @returns {string[]}
573
- */
574
- function componentTerms(component) {
575
- return [
576
- ...nameTerms(component),
577
- ...component.keywords.flatMap(keyword => terms(keyword)),
578
- ];
579
- }
580
-
581
264
  /**
582
265
  * The next closest templates after the start, best first: the ones a reader
583
266
  * should check the idea against when the start's shape is wrong. Each matched
@@ -9,7 +9,8 @@
9
9
  import {describe, it, expect} from 'vitest';
10
10
  import * as path from 'node:path';
11
11
  import {fileURLToPath} from 'node:url';
12
- import {ideaKind, pickStart, rankPages} from './rank.mjs';
12
+ import {ideaKind} from './kind.mjs';
13
+ import {pickStart, rankPages} from './rank.mjs';
13
14
  import {loadComponents, loadPageTemplates} from '../_adapter.mjs';
14
15
 
15
16
  // api/build/kit/ -> up 5 = repo root (has packages/core and the templates).
@@ -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
+ * Build's signals for one request (see {@link KitSignals}).
6
+ *
7
+ * @param {string} query what you're building (e.g. "analytics dashboard")
8
+ * @param {{cwd?: string, type?: import('../../search/search.type.mjs').SearchDomain, limit?: number}} [options]
9
+ * @returns {Promise<KitSignals>}
10
+ */
11
+ export function kitSignals(query: string, options?: {
12
+ cwd?: string;
13
+ type?: import("../../search/search.type.mjs").SearchDomain;
14
+ limit?: number;
15
+ }): Promise<KitSignals>;
16
+ /**
17
+ * Always-surfaced primitives. Every page needs a shell + layout/typography/
18
+ * action atoms, but these never keyword-match an idea ("dashboard" != "Stack"),
19
+ * so search alone never returns them. Kept here (not the renderer) because they
20
+ * are ALSO used to exclude these names from the idea-specific `domain` group.
21
+ * Every page template already uses them.
22
+ */
23
+ export const FRAME: string[];
24
+ export const FOUNDATION: string[];
25
+ export type SearchResultEntry = import("../../search/search.type.mjs").SearchResultEntry;
26
+ export type BuildStart = import("../build.type.mjs").BuildStart;
27
+ export type BlockScore = [name: string, score: number, matched: number | null, total: number | null];
28
+ /**
29
+ * Everything build weighs for one request.
30
+ */
31
+ export type KitSignals = {
32
+ /**
33
+ * search's echo of the query
34
+ */
35
+ query: string;
36
+ matchCount: number;
37
+ directMatch: boolean;
38
+ /**
39
+ * the start, its alternatives, and its notes
40
+ */
41
+ start: BuildStart | null;
42
+ pages: SearchResultEntry[];
43
+ /**
44
+ * the blocks build names itself
45
+ */
46
+ blocks: SearchResultEntry[];
47
+ domain: SearchResultEntry[];
48
+ /**
49
+ * the names of `blocks`
50
+ */
51
+ kitBlocks: string[];
52
+ /**
53
+ * every block search returned, in its order
54
+ */
55
+ searched: BlockScore[];
56
+ /**
57
+ * the blocks, named, searched or scaffoldable, whose package is not Core's
58
+ */
59
+ foreign: string[];
60
+ };