@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,284 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file What build weighs for one request, computed once: search's matches
5
+ * grouped into pages, blocks, and domain components; the page template to
6
+ * start from and its alternatives (start.mjs, rank.mjs); and search's whole
7
+ * block list with its scores, for the block picker. answer.mjs turns these and
8
+ * a block list into the build.kit response.
9
+ * @input A request and build's options; templates and components through the
10
+ * adapter.
11
+ * @output {@link KitSignals}
12
+ * @position api/build/kit, beside kit.mjs and answer.mjs; the main searcher in
13
+ * tree.mjs calls it.
14
+ */
15
+
16
+ import {
17
+ searchCore as search,
18
+ searchedComponents,
19
+ } from '../../search/search.mjs';
20
+ import {coreDirOrThrow} from '../../error.mjs';
21
+ import {getResultCoverage} from '../../search/coverage.mjs';
22
+ import {CORE_PROVIDER_ID} from '../../../foundation/identity/providers.mjs';
23
+ import {
24
+ loadComponents,
25
+ loadTemplates,
26
+ requireBuildCoreDir,
27
+ templateSetupNotes,
28
+ } from '../_adapter.mjs';
29
+ import {ideaKind} from './kind.mjs';
30
+ import {pickAlternatives, rankPages} from './rank.mjs';
31
+ import {asTemplate, chooseStart} from './start.mjs';
32
+
33
+ /** A page at/above this score is a confident direct match. */
34
+ const PAGE_DIRECT = 95;
35
+
36
+ /** Below this a page is too weak to offer at all. */
37
+ const PAGE_FLOOR = 50;
38
+
39
+ /**
40
+ * Below this a block/domain-component match is incidental noise. A single
41
+ * description word in a multi-word idea scores 50 plus at most 7.5 of coverage
42
+ * garnish, so it stays below; a name or keyword hit clears it. `build "weekly
43
+ * brief"` used to offer Toast, Popover and TextInput because their
44
+ * descriptions say "brief".
45
+ */
46
+ const DOMAIN_FLOOR = 60;
47
+
48
+ /**
49
+ * How much of a multi-word query a page must cover to be offered on breadth.
50
+ *
51
+ * Score alone cannot carry this. A page's derived keywords include every
52
+ * component its source renders, so `build "actionable warning banner"` once
53
+ * scored `login`, `contact-form` and `documentation-design` at 95 apiece — an
54
+ * exact hit on "banner" alone, plus the coverage garnish. Matching one of
55
+ * three concepts is not the same claim as matching three.
56
+ */
57
+ const PAGE_COVERAGE = 0.5;
58
+
59
+ /**
60
+ * Always-surfaced primitives. Every page needs a shell + layout/typography/
61
+ * action atoms, but these never keyword-match an idea ("dashboard" != "Stack"),
62
+ * so search alone never returns them. Kept here (not the renderer) because they
63
+ * are ALSO used to exclude these names from the idea-specific `domain` group.
64
+ * Every page template already uses them.
65
+ */
66
+ export const FRAME = ['AppShell', 'TopNav', 'SideNav', 'Layout'];
67
+
68
+ export const FOUNDATION = [
69
+ 'VStack',
70
+ 'HStack',
71
+ 'Grid',
72
+ 'StackItem',
73
+ 'Card',
74
+ 'Section',
75
+ 'Text',
76
+ 'Heading',
77
+ 'Button',
78
+ 'Icon',
79
+ 'Badge',
80
+ 'Divider',
81
+ ];
82
+
83
+ const ALWAYS = new Set([...FRAME, ...FOUNDATION]);
84
+
85
+ /**
86
+ * @typedef {import('../../search/search.type.mjs').SearchResultEntry} SearchResultEntry
87
+ * @typedef {import('../build.type.mjs').BuildStart} BuildStart
88
+ * @typedef {[name: string, score: number, matched: number | null, total: number | null]} BlockScore
89
+ */
90
+
91
+ /**
92
+ * Everything build weighs for one request.
93
+ * @typedef {object} KitSignals
94
+ * @property {string} query search's echo of the query
95
+ * @property {number} matchCount
96
+ * @property {boolean} directMatch
97
+ * @property {BuildStart | null} start the start, its alternatives, and its notes
98
+ * @property {SearchResultEntry[]} pages
99
+ * @property {SearchResultEntry[]} blocks the blocks build names itself
100
+ * @property {SearchResultEntry[]} domain
101
+ * @property {string[]} kitBlocks the names of `blocks`
102
+ * @property {BlockScore[]} searched every block search returned, in its order
103
+ * @property {string[]} foreign the blocks, named, searched or scaffoldable, whose package is not Core's
104
+ */
105
+
106
+ /**
107
+ * A block result's name, score, and how much of the query it matched.
108
+ * @param {SearchResultEntry} r
109
+ * @returns {BlockScore}
110
+ */
111
+ function blockScore(r) {
112
+ const coverage = getResultCoverage(r);
113
+ return [r.name, r.score, coverage?.matched ?? null, coverage?.total ?? null];
114
+ }
115
+
116
+ /**
117
+ * Build's signals for one request (see {@link KitSignals}).
118
+ *
119
+ * @param {string} query what you're building (e.g. "analytics dashboard")
120
+ * @param {{cwd?: string, type?: import('../../search/search.type.mjs').SearchDomain, limit?: number}} [options]
121
+ * @returns {Promise<KitSignals>}
122
+ */
123
+ export async function kitSignals(query, options = {}) {
124
+ const {cwd = process.cwd(), type, limit = 60} = options;
125
+ // A kit is built from Core's components, hooks, and templates. An open
126
+ // search without core covers the docs alone, so the kit asks for core here.
127
+ if (type !== 'doc') coreDirOrThrow(requireBuildCoreDir, cwd);
128
+ // search()'s JSDoc @returns widens results to object[]; the SearchResponse
129
+ // shape is the contract (api/search/search.type.mjs). Cast locally rather than
130
+ // tightening the search @returns (a separate follow-up).
131
+ const result =
132
+ /** @type {import('../../search/search.type.mjs').SearchResponse} */ (
133
+ await search(query, {
134
+ cwd,
135
+ type,
136
+ // Search wider than the surfaced kit so a flood of doc matches cannot
137
+ // bury the page templates past the cutoff; the caller's `limit` still
138
+ // caps the kit below. A non-positive or non-integer limit is passed
139
+ // through unchanged so search rejects it (ERR_INVALID_ARGUMENT).
140
+ limit:
141
+ Number.isInteger(limit) && limit > 0 ? Math.max(limit, 200) : limit,
142
+ })
143
+ );
144
+ const results = result.data.results;
145
+ // The TOTAL number of matches, not the number that survived `limit`. The kit
146
+ // below is deliberately small (≤3 pages, ≤5 blocks, ≤6 components) and
147
+ // `results` is itself capped, so every other count here is a cap; this is the
148
+ // one field that says how much the query actually matched.
149
+ const matchCount = result.data.matchCount;
150
+
151
+ /**
152
+ * Did this result answer enough of the query to stand as a page on breadth?
153
+ * Single-concept queries have nothing to cover, so they always pass. Coverage
154
+ * stays in a module-private WeakMap and never enters public search/build JSON.
155
+ * @param {object} r
156
+ */
157
+ const covers = r => {
158
+ const coverage = getResultCoverage(r);
159
+ const total = coverage?.total ?? 1;
160
+ if (total <= 1) return true;
161
+ return (coverage?.matched ?? 0) / total >= PAGE_COVERAGE;
162
+ };
163
+
164
+ const matchedPages = results
165
+ .filter(
166
+ r =>
167
+ r.domain === 'template' &&
168
+ r.kind !== 'block' &&
169
+ r.score >= PAGE_FLOOR &&
170
+ covers(r),
171
+ )
172
+ .slice(0, 3);
173
+ const searchedBlocks = results.filter(
174
+ r => r.domain === 'template' && r.kind === 'block',
175
+ );
176
+ const blocks = searchedBlocks
177
+ .filter(r => r.score >= DOMAIN_FLOOR)
178
+ .slice(0, 5);
179
+ const domain = results
180
+ .filter(
181
+ r =>
182
+ (r.domain === 'component' || r.domain === 'hook') &&
183
+ r.score >= DOMAIN_FLOOR &&
184
+ !ALWAYS.has(r.name),
185
+ )
186
+ .slice(0, 6);
187
+ const directMatch =
188
+ matchedPages.length > 0 && matchedPages[0].score >= PAGE_DIRECT;
189
+
190
+ /**
191
+ * On a loose match, a page entry's `command` previews the layout rather
192
+ * than printing the whole template: `--skeleton` gives the shape without
193
+ * presenting the page as the thing to build. The recommendation itself is
194
+ * `start`, which always scaffolds. Copied rather than mutated: these entries
195
+ * come from `search()` and are not this function's to modify.
196
+ */
197
+ const pages = directMatch
198
+ ? matchedPages
199
+ : matchedPages.map(page => ({
200
+ ...page,
201
+ command: `${page.command} --skeleton`,
202
+ }));
203
+
204
+ // The caller's `limit` caps the surfaced kit, even though the search above
205
+ // ran wider to find templates that a flood of doc matches would otherwise
206
+ // bury past the cutoff. Keep pages first, then blocks, then components.
207
+ let budget = limit;
208
+ /**
209
+ * @template T
210
+ * @param {T[]} arr
211
+ * @returns {T[]}
212
+ */
213
+ const toLimit = arr => {
214
+ const out = arr.slice(0, Math.max(0, budget));
215
+ budget -= out.length;
216
+ return out;
217
+ };
218
+ const pagesKept = toLimit(pages);
219
+ const blocksKept = toLimit(blocks);
220
+ const domainKept = toLimit(domain);
221
+
222
+ // A kit narrowed to components or hooks has no page to start from; every
223
+ // other kit does, so the reader is never left to compose a page from scratch.
224
+ const wantsPages = !type || type === 'template';
225
+ const templates = wantsPages
226
+ ? await loadTemplates(cwd)
227
+ : {pages: [], blocks: []};
228
+ const catalog = templates.pages;
229
+ const ranked = wantsPages ? rankPages(query, catalog) : [];
230
+ // A part of a page starts where it lives (spec:AST-048/FR3); the project's
231
+ // own components say what a part is. The search above already gathered them
232
+ // unless it was narrowed to templates.
233
+ const kind = wantsPages
234
+ ? ideaKind(
235
+ query,
236
+ catalog,
237
+ searchedComponents(result) ?? (await loadComponents(cwd)),
238
+ )
239
+ : 'page';
240
+ const chosen = wantsPages
241
+ ? chooseStart(ranked, kind, matchedPages, directMatch, catalog, query)
242
+ : null;
243
+ // Name the ranker's next two templates beside the start: the reader judges
244
+ // meaning better than keywords do, and an acceptable template is in these
245
+ // three far more often than it is the start alone.
246
+ /** @type {BuildStart | null} */
247
+ const start = chosen && {
248
+ ...chosen,
249
+ alternatives: pickAlternatives(ranked, chosen.name).flatMap(r => {
250
+ const t = catalog.find(c => c.name === r.name);
251
+ return t ? [asTemplate(t)] : [];
252
+ }),
253
+ };
254
+
255
+ // Analyze what the start template needs that the project lacks (external
256
+ // packages, StyleX compiler). Only the start — alternatives are suggestions,
257
+ // not commitments, so reporting their needs would be noise.
258
+ if (start) {
259
+ const startTemplate = catalog.find(t => t.name === start.name);
260
+ if (startTemplate) {
261
+ const notes = templateSetupNotes(startTemplate, cwd);
262
+ if (notes.length > 0) start.notes = notes;
263
+ }
264
+ }
265
+
266
+ return {
267
+ query: result.data.query,
268
+ matchCount,
269
+ directMatch,
270
+ start,
271
+ pages: pagesKept,
272
+ blocks: blocksKept,
273
+ domain: domainKept,
274
+ kitBlocks: blocksKept.map(r => r.name),
275
+ searched: searchedBlocks.map(blockScore),
276
+ foreign: [
277
+ ...new Set(
278
+ [...blocksKept, ...searchedBlocks, ...templates.blocks]
279
+ .filter(b => b.package !== CORE_PROVIDER_ID)
280
+ .map(b => b.name),
281
+ ),
282
+ ],
283
+ };
284
+ }
@@ -0,0 +1,32 @@
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
+ * The template to start from: the ready page the ranker puts first when it
6
+ * has the evidence to lead, else the first fallback shell the project can
7
+ * scaffold. Null only when the project has no page template to offer at all.
8
+ *
9
+ * `direct` means two independent signals agree: the ranker's pick is also
10
+ * search's direct match. The ranker sees only ready templates, so a template
11
+ * still marked not ready is never the start; when search matched one directly
12
+ * the reason names it, so the reader knows why the kit starts elsewhere.
13
+ *
14
+ * @param {import('./rank.mjs').RankedPage[]} ranked
15
+ * @param {import('./rank.mjs').IdeaKind} kind
16
+ * @param {SearchResultEntry[]} pages
17
+ * @param {boolean} directMatch
18
+ * @param {PageTemplate[]} catalog
19
+ * @param {string} idea
20
+ * @returns {Omit<BuildStart, 'alternatives'> | null}
21
+ */
22
+ export function chooseStart(ranked: import("./rank.mjs").RankedPage[], kind: import("./rank.mjs").IdeaKind, pages: SearchResultEntry[], directMatch: boolean, catalog: PageTemplate[], idea: string): Omit<BuildStart, "alternatives"> | null;
23
+ export function asTemplate(t: PageTemplate): {
24
+ name: string;
25
+ package: string;
26
+ displayName: string;
27
+ description: string;
28
+ command: string;
29
+ };
30
+ export type SearchResultEntry = import("../../search/search.type.mjs").SearchResultEntry;
31
+ export type BuildStart = import("../build.type.mjs").BuildStart;
32
+ export type PageTemplate = import("../_adapter.mjs").PageTemplate;
@@ -0,0 +1,163 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file The page template a kit starts from: the page ranker's pick when it has
5
+ * the evidence to lead, the template a part lives in, else the app shell.
6
+ */
7
+
8
+ import {loadWeights} from '../_adapter.mjs';
9
+ import {asksForNewPage} from './kind.mjs';
10
+ import {pickStart} from './rank.mjs';
11
+ import {weighStart} from './weights.mjs';
12
+
13
+ /**
14
+ * @typedef {import('../../search/search.type.mjs').SearchResultEntry} SearchResultEntry
15
+ * @typedef {import('../build.type.mjs').BuildStart} BuildStart
16
+ * @typedef {import('../_adapter.mjs').PageTemplate} PageTemplate
17
+ */
18
+
19
+ /**
20
+ * Where a page starts when no page template matched: the first of these the
21
+ * project can scaffold. A top nav over empty, full-width content is the least
22
+ * opinionated frame that still has navigation; `blank` is the floor, with no
23
+ * chrome at all. Either one still hands the reader a page frame and its
24
+ * padding, which composing from components does not.
25
+ */
26
+ const FALLBACK_STARTS = ['shell-top-nav', 'blank'];
27
+
28
+ /**
29
+ * A page template as the kit names it: the command that selects exactly that
30
+ * template, scaffolding into `<path>`, a placeholder for the file or folder to
31
+ * write it to.
32
+ * @param {PageTemplate} t
33
+ */
34
+ export const asTemplate = t => ({
35
+ name: t.name,
36
+ package: t.package,
37
+ displayName: t.displayName,
38
+ description: t.description,
39
+ command: `${t.command} <path>`,
40
+ });
41
+
42
+ /**
43
+ * Why a part of a page, or a change to a page the builder already has, starts
44
+ * where it does (spec:AST-048/FR3, FR9): the rest of the start's reason, or
45
+ * null for a whole page.
46
+ * @param {import('./rank.mjs').IdeaKind} kind
47
+ * @param {boolean} inPage whether the start is the page the idea names
48
+ * @returns {string | null}
49
+ */
50
+ function placement(kind, inPage) {
51
+ if (kind === 'edit')
52
+ return 'the idea changes a page you already have, so keep it and add blocks to it; a new page starts from the app shell.';
53
+ if (kind === 'part')
54
+ return inPage
55
+ ? 'the idea is a part of a page, so it starts from the page it names.'
56
+ : 'the idea is a part of a page and names no page, so it starts from the app shell.';
57
+ return null;
58
+ }
59
+
60
+ /**
61
+ * The template to start from: the ready page the ranker puts first when it
62
+ * has the evidence to lead, else the first fallback shell the project can
63
+ * scaffold. Null only when the project has no page template to offer at all.
64
+ *
65
+ * `direct` means two independent signals agree: the ranker's pick is also
66
+ * search's direct match. The ranker sees only ready templates, so a template
67
+ * still marked not ready is never the start; when search matched one directly
68
+ * the reason names it, so the reader knows why the kit starts elsewhere.
69
+ *
70
+ * @param {import('./rank.mjs').RankedPage[]} ranked
71
+ * @param {import('./rank.mjs').IdeaKind} kind
72
+ * @param {SearchResultEntry[]} pages
73
+ * @param {boolean} directMatch
74
+ * @param {PageTemplate[]} catalog
75
+ * @param {string} idea
76
+ * @returns {Omit<BuildStart, 'alternatives'> | null}
77
+ */
78
+ export function chooseStart(ranked, kind, pages, directMatch, catalog, idea) {
79
+ const direct = directMatch ? pages[0].name : null;
80
+ const unready =
81
+ direct && !catalog.some(t => t.name === direct) ? direct : null;
82
+ // The reason never denies a match the same response reports: a direct
83
+ // match the ranker outweighed is named, and so are the loose page matches
84
+ // search listed when the kit falls back to the shell.
85
+ const loose = pages.map(p => `\`${p.name}\``).join(', ');
86
+ /**
87
+ * A part's or an edit's reason, naming a direct match that is not the start.
88
+ * @param {string} place
89
+ * @param {string} startName
90
+ */
91
+ const placed = (place, startName) =>
92
+ direct && direct !== startName
93
+ ? `Search matched \`${direct}\` by name, but ${place}`
94
+ : place[0].toUpperCase() + place.slice(1);
95
+ const proposed = pickStart(ranked, kind);
96
+ // The checked-in word weights (weights.mjs), blended with the ranker's
97
+ // scores, decide the start of a whole page. A part or an edit starts where
98
+ // the ranker's placement rules put it (spec:AST-048/FR3).
99
+ const weighed =
100
+ kind === 'page'
101
+ ? weighStart(idea, ranked, proposed, catalog, {
102
+ weights: loadWeights(),
103
+ newPage: asksForNewPage(idea, catalog),
104
+ })
105
+ : undefined;
106
+ // A shell start keeps the shell the ranker named, if any, and never replaces
107
+ // the template the ranker chose for a page search matched directly.
108
+ const pick =
109
+ weighed === undefined
110
+ ? proposed
111
+ : weighed === null
112
+ ? proposed?.family === 'Shell' || (proposed && direct && !unready)
113
+ ? proposed
114
+ : null
115
+ : (ranked.find(r => r.name === weighed) ?? proposed);
116
+ const closest = pick && catalog.find(t => t.name === pick.name);
117
+ if (pick && closest) {
118
+ const agrees = closest.name === direct;
119
+ const place = placement(kind, pick.base && pick.familyNamed);
120
+ return {
121
+ ...asTemplate(closest),
122
+ basis: agrees ? 'direct' : 'closest',
123
+ reason: unready
124
+ ? `\`${unready}\` matches but is not ready yet; this is the closest ready template.`
125
+ : place
126
+ ? placed(place, closest.name)
127
+ : agrees
128
+ ? 'Matches the idea.'
129
+ : direct
130
+ ? `Search matched \`${direct}\` by name, but this template fits more of the idea.`
131
+ : 'The closest template; none is exactly this page.',
132
+ };
133
+ }
134
+ for (const id of FALLBACK_STARTS) {
135
+ const shell = catalog.find(t => t.name === id);
136
+ if (shell) {
137
+ // The shell can also be the ranker's best guess without the evidence to
138
+ // lead ("horizontal site navigation"); say so rather than "no match".
139
+ const nearest =
140
+ (ranked[0]?.name === shell.name && ranked[0].hits > 0) ||
141
+ (weighed === null && !!proposed && proposed.family !== 'Shell');
142
+ const place = placement(kind, false);
143
+ return {
144
+ ...asTemplate(shell),
145
+ basis: 'fallback',
146
+ reason: unready
147
+ ? `\`${unready}\` matches but is not ready yet, so start from the app shell.`
148
+ : place
149
+ ? placed(place, shell.name)
150
+ : direct
151
+ ? weighed === null
152
+ ? `Search matched \`${direct}\` by name, but the app shell is the closer start.`
153
+ : `Search matched \`${direct}\` by name, but too little of the idea fits it, so start from the app shell.`
154
+ : nearest
155
+ ? 'No template is a clear match; the app shell is the closest.'
156
+ : loose
157
+ ? `Search matched ${loose} only loosely, so start from the app shell.`
158
+ : 'No template matched, so start from the app shell.',
159
+ };
160
+ }
161
+ }
162
+ return null;
163
+ }
@@ -0,0 +1,72 @@
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
+ * Content terms of a text: lowercase alphanumeric words, stopwords removed,
6
+ * stemmed.
7
+ * @param {string} text
8
+ * @returns {string[]}
9
+ */
10
+ export function terms(text: string): string[];
11
+ /**
12
+ * The terms of an idea that only ever modify another word, each with the words
13
+ * it modifies: in each phrase the last content word is its head, and the ones
14
+ * before it describe the next.
15
+ * @param {string} query
16
+ * @returns {Map<string, Set<string>>}
17
+ */
18
+ export function modifiersOf(query: string): Map<string, Set<string>>;
19
+ /**
20
+ * Adjacent term pairs a text names, "a b", for telling a template that names
21
+ * a whole compound ("executive summary") from one that names only its parts.
22
+ * @param {string[]} texts
23
+ * @returns {Set<string>}
24
+ */
25
+ export function pairsOf(texts: string[]): Set<string>;
26
+ /**
27
+ * The terms of an idea that name its frame: "modal" in "in a modal". A card, a
28
+ * table or an existing page after "in a" is where a part goes, not a frame.
29
+ * @param {string} query
30
+ * @returns {Set<string>}
31
+ */
32
+ export function containersOf(query: string): Set<string>;
33
+ /**
34
+ * Family words, from the templates' own ids: a word in the ids of two or more
35
+ * templates of one family, plus the family's name when a template carries it.
36
+ * Each word maps to its family.
37
+ * @param {PageTemplate[]} pages
38
+ * @returns {Map<string, string>}
39
+ */
40
+ export function familyWordsOf(pages: PageTemplate[]): Map<string, string>;
41
+ /**
42
+ * Where an idea's head ends: its first clause, before the parts it lists.
43
+ */
44
+ export const HEAD_END: RegExp;
45
+ /**
46
+ * The family whose templates are app chrome rather than page content: the
47
+ * explicit `Shell -` category (architecture:template-authoring/INV7).
48
+ */
49
+ export const SHELL_FAMILY: "Shell";
50
+ /** A container phrase: "in a modal", "inside the side panel". */
51
+ export const CONTAINER_PHRASE: RegExp;
52
+ /**
53
+ * Where a phrase ends, for finding the word that heads it: punctuation and
54
+ * the words that join phrases.
55
+ */
56
+ export const PHRASE_END: RegExp;
57
+ export function normalized(text: string): string;
58
+ /** Stemmed synonym lookup built from search's vocabulary, both directions. */
59
+ export const SYNONYMS_OF: Map<string, Set<string>>;
60
+ export type PageTemplate = import("../_adapter.mjs").PageTemplate;
61
+ export type ComponentWords = import("../_adapter.mjs").ComponentWords;
62
+ export type RankedPage = {
63
+ name: string;
64
+ score: number;
65
+ hits: number;
66
+ familyNamed: boolean;
67
+ containerMatched: boolean;
68
+ family: string;
69
+ base: boolean;
70
+ matched: Set<string>;
71
+ };
72
+ export type IdeaKind = "page" | "part" | "edit";