@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
@@ -15,6 +15,9 @@
15
15
  * always-on frame + foundation names. `pages` and `directMatch` keep search's
16
16
  * own view for callers that read them.
17
17
  *
18
+ * signals.mjs computes what the kit is built from and answer.mjs shapes the
19
+ * response; buildKit is the two with build's own blocks.
20
+ *
18
21
  * The kit carries RAW `SearchResultEntry` objects and static name arrays only —
19
22
  * never pre-formatted command strings. All CLI prefixing (formatCliCommand /
20
23
  * getCliInvocation) and the section prose live in the command renderer, so the
@@ -27,410 +30,17 @@
27
30
  * the invocation stays the renderer's job.
28
31
  */
29
32
 
30
- import {search, searchedComponents} from '../../search/search.mjs';
31
- import {coreDirOrThrow} from '../../error.mjs';
32
- import {getResultCoverage} from '../../search/coverage.mjs';
33
- import {loadComponents, loadPageTemplates, loadWeights, requireBuildCoreDir, templateSetupNotes} from '../_adapter.mjs';
34
- import {
35
- asksForNewPage,
36
- ideaKind,
37
- pickAlternatives,
38
- pickStart,
39
- rankPages,
40
- } from './rank.mjs';
41
- import {weighStart} from './weights.mjs';
33
+ import {kitAnswer} from './answer.mjs';
34
+ import {kitSignals} from './signals.mjs';
42
35
 
43
- /** A page at/above this score is a confident direct match. */
44
- const PAGE_DIRECT = 95;
45
- /** Below this a page is too weak to offer at all. */
46
- const PAGE_FLOOR = 50;
47
- /**
48
- * Below this a block/domain-component match is incidental noise. A single
49
- * description word in a multi-word idea scores 50 plus at most 7.5 of coverage
50
- * garnish, so it stays below; a name or keyword hit clears it. `build "weekly
51
- * brief"` used to offer Toast, Popover and TextInput because their
52
- * descriptions say "brief".
53
- */
54
- const DOMAIN_FLOOR = 60;
55
- /**
56
- * How much of a multi-word query a page must cover to be offered on breadth.
57
- *
58
- * Score alone cannot carry this. A page's derived keywords include every
59
- * component its source renders, so `build "actionable warning banner"` once
60
- * scored `login`, `contact-form` and `documentation-design` at 95 apiece — an
61
- * exact hit on "banner" alone, plus the coverage garnish. Matching one of
62
- * three concepts is not the same claim as matching three.
63
- */
64
- const PAGE_COVERAGE = 0.5;
65
36
  /**
66
- * Fewer offerable results than this and the kit says how to look further.
67
- *
68
- * Three is the point below which a kit stops being a starting point. An agent
69
- * that reads a near-empty kit does not conclude "my wording was wrong" — it
70
- * concludes the package has nothing and falls back on its own memory of what
71
- * Astryx contains, which is exactly the failure `build` exists to prevent.
72
- */
73
- const THIN_KIT = 3;
74
- /**
75
- * Where a page starts when no page template matched: the first of these the
76
- * project can scaffold. A top nav over empty, full-width content is the least
77
- * opinionated frame that still has navigation; `blank` is the floor, with no
78
- * chrome at all. Either one still hands the reader a page frame and its
79
- * padding, which composing from components does not.
80
- */
81
- const FALLBACK_STARTS = ['shell-top-nav', 'blank'];
82
-
83
- /**
84
- * Always-surfaced primitives. Every page needs a shell + layout/typography/
85
- * action atoms, but these never keyword-match an idea ("dashboard" != "Stack"),
86
- * so search alone never returns them. Kept here (not the renderer) because they
87
- * are ALSO used to exclude these names from the idea-specific `domain` group.
88
- * Every page template already uses them.
89
- */
90
- const FRAME = ['AppShell', 'TopNav', 'SideNav', 'Layout'];
91
- const FOUNDATION = [
92
- 'VStack',
93
- 'HStack',
94
- 'Grid',
95
- 'StackItem',
96
- 'Card',
97
- 'Section',
98
- 'Text',
99
- 'Heading',
100
- 'Button',
101
- 'Icon',
102
- 'Badge',
103
- 'Divider',
104
- ];
105
- const ALWAYS = new Set([...FRAME, ...FOUNDATION]);
106
-
107
- /**
108
- * @typedef {import('../../search/search.type.mjs').SearchResultEntry} SearchResultEntry
109
- * @typedef {import('../build.type.mjs').BuildStart} BuildStart
110
- * @typedef {import('../_adapter.mjs').PageTemplate} PageTemplate
111
- */
112
-
113
- /**
114
- * A page template as the kit names it: the command that selects exactly that
115
- * template, scaffolding into `<path>`, a placeholder for the file or folder to
116
- * write it to.
117
- * @param {PageTemplate} t
118
- */
119
- const asTemplate = t => ({
120
- name: t.name,
121
- package: t.package,
122
- displayName: t.displayName,
123
- description: t.description,
124
- command: `${t.command} <path>`,
125
- });
126
-
127
- /**
128
- * Why a part of a page, or a change to a page the builder already has, starts
129
- * where it does (spec:AST-048/FR3, FR9): the rest of the start's reason, or
130
- * null for a whole page.
131
- * @param {import('./rank.mjs').IdeaKind} kind
132
- * @param {boolean} inPage whether the start is the page the idea names
133
- * @returns {string | null}
134
- */
135
- function placement(kind, inPage) {
136
- if (kind === 'edit')
137
- 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.';
138
- if (kind === 'part')
139
- return inPage
140
- ? 'the idea is a part of a page, so it starts from the page it names.'
141
- : 'the idea is a part of a page and names no page, so it starts from the app shell.';
142
- return null;
143
- }
144
-
145
- /**
146
- * The template to start from: the ready page the ranker puts first when it
147
- * has the evidence to lead, else the first fallback shell the project can
148
- * scaffold. Null only when the project has no page template to offer at all.
149
- *
150
- * `direct` means two independent signals agree: the ranker's pick is also
151
- * search's direct match. The ranker sees only ready templates, so a template
152
- * still marked not ready is never the start; when search matched one directly
153
- * the reason names it, so the reader knows why the kit starts elsewhere.
154
- *
155
- * @param {import('./rank.mjs').RankedPage[]} ranked
156
- * @param {import('./rank.mjs').IdeaKind} kind
157
- * @param {SearchResultEntry[]} pages
158
- * @param {boolean} directMatch
159
- * @param {PageTemplate[]} catalog
160
- * @param {string} idea
161
- * @returns {Omit<BuildStart, 'alternatives'> | null}
162
- */
163
- function chooseStart(ranked, kind, pages, directMatch, catalog, idea) {
164
- const direct = directMatch ? pages[0].name : null;
165
- const unready =
166
- direct && !catalog.some(t => t.name === direct) ? direct : null;
167
- // The reason never denies a match the same response reports: a direct
168
- // match the ranker outweighed is named, and so are the loose page matches
169
- // search listed when the kit falls back to the shell.
170
- const loose = pages.map(p => `\`${p.name}\``).join(', ');
171
- /**
172
- * A part's or an edit's reason, naming a direct match that is not the start.
173
- * @param {string} place
174
- * @param {string} startName
175
- */
176
- const placed = (place, startName) =>
177
- direct && direct !== startName
178
- ? `Search matched \`${direct}\` by name, but ${place}`
179
- : place[0].toUpperCase() + place.slice(1);
180
- const proposed = pickStart(ranked, kind);
181
- // The checked-in word weights (weights.mjs), blended with the ranker's
182
- // scores, decide the start of a whole page. A part or an edit starts where
183
- // the ranker's placement rules put it (spec:AST-048/FR3).
184
- const weighed =
185
- kind === 'page'
186
- ? weighStart(idea, ranked, proposed, catalog, {
187
- weights: loadWeights(),
188
- newPage: asksForNewPage(idea, catalog),
189
- })
190
- : undefined;
191
- // A shell start keeps the shell the ranker named, if any, and never replaces
192
- // the template the ranker chose for a page search matched directly.
193
- const pick =
194
- weighed === undefined
195
- ? proposed
196
- : weighed === null
197
- ? proposed?.family === 'Shell' || (proposed && direct && !unready)
198
- ? proposed
199
- : null
200
- : (ranked.find(r => r.name === weighed) ?? proposed);
201
- const closest = pick && catalog.find(t => t.name === pick.name);
202
- if (pick && closest) {
203
- const agrees = closest.name === direct;
204
- const place = placement(kind, pick.base && pick.familyNamed);
205
- return {
206
- ...asTemplate(closest),
207
- basis: agrees ? 'direct' : 'closest',
208
- reason: unready
209
- ? `\`${unready}\` matches but is not ready yet; this is the closest ready template.`
210
- : place
211
- ? placed(place, closest.name)
212
- : agrees
213
- ? 'Matches the idea.'
214
- : direct
215
- ? `Search matched \`${direct}\` by name, but this template fits more of the idea.`
216
- : 'The closest template; none is exactly this page.',
217
- };
218
- }
219
- for (const id of FALLBACK_STARTS) {
220
- const shell = catalog.find(t => t.name === id);
221
- if (shell) {
222
- // The shell can also be the ranker's best guess without the evidence to
223
- // lead ("horizontal site navigation"); say so rather than "no match".
224
- const nearest =
225
- (ranked[0]?.name === shell.name && ranked[0].hits > 0) ||
226
- (weighed === null && !!proposed && proposed.family !== 'Shell');
227
- const place = placement(kind, false);
228
- return {
229
- ...asTemplate(shell),
230
- basis: 'fallback',
231
- reason: unready
232
- ? `\`${unready}\` matches but is not ready yet, so start from the app shell.`
233
- : place
234
- ? placed(place, shell.name)
235
- : direct
236
- ? weighed === null
237
- ? `Search matched \`${direct}\` by name, but the app shell is the closer start.`
238
- : `Search matched \`${direct}\` by name, but too little of the idea fits it, so start from the app shell.`
239
- : nearest
240
- ? 'No template is a clear match; the app shell is the closest.'
241
- : loose
242
- ? `Search matched ${loose} only loosely, so start from the app shell.`
243
- : 'No template matched, so start from the app shell.',
244
- };
245
- }
246
- }
247
- return null;
248
- }
249
-
250
- /**
251
- * The page template to start from, and the kit around it.
37
+ * The page template to start from, and the kit around it, with the blocks
38
+ * build names itself.
252
39
  *
253
40
  * @param {string} query what you're building (e.g. "analytics dashboard")
254
41
  * @param {{cwd?: string, type?: import('../../search/search.type.mjs').SearchDomain, limit?: number}} [options]
255
42
  * @returns {Promise<import('../build.type.mjs').BuildKitResponse>}
256
43
  */
257
44
  export async function buildKit(query, options = {}) {
258
- const {cwd = process.cwd(), type, limit = 60} = options;
259
- // A kit is built from Core's components, hooks, and templates. An open
260
- // search without core covers the docs alone, so the kit asks for core here.
261
- if (type !== 'doc') coreDirOrThrow(requireBuildCoreDir, cwd);
262
- // search()'s JSDoc @returns widens results to object[]; the SearchResponse
263
- // shape is the contract (api/search/search.type.mjs). Cast locally rather than
264
- // tightening the search @returns (a separate follow-up).
265
- const result =
266
- /** @type {import('../../search/search.type.mjs').SearchResponse} */ (
267
- await search(query, {
268
- cwd,
269
- type,
270
- // Search wider than the surfaced kit so a flood of doc matches cannot
271
- // bury the page templates past the cutoff; the caller's `limit` still
272
- // caps the kit below. A non-positive or non-integer limit is passed
273
- // through unchanged so search rejects it (ERR_INVALID_ARGUMENT).
274
- limit:
275
- Number.isInteger(limit) && limit > 0 ? Math.max(limit, 200) : limit,
276
- })
277
- );
278
- const results = result.data.results;
279
- // The TOTAL number of matches, not the number that survived `limit`. The kit
280
- // below is deliberately small (≤3 pages, ≤5 blocks, ≤6 components) and
281
- // `results` is itself capped, so every other count here is a cap; this is the
282
- // one field that says how much the query actually matched.
283
- const matchCount = result.data.matchCount;
284
-
285
- /**
286
- * Did this result answer enough of the query to stand as a page on breadth?
287
- * Single-concept queries have nothing to cover, so they always pass. Coverage
288
- * stays in a module-private WeakMap and never enters public search/build JSON.
289
- * @param {object} r
290
- */
291
- const covers = r => {
292
- const coverage = getResultCoverage(r);
293
- const total = coverage?.total ?? 1;
294
- if (total <= 1) return true;
295
- return (coverage?.matched ?? 0) / total >= PAGE_COVERAGE;
296
- };
297
-
298
- const matchedPages = results
299
- .filter(
300
- r =>
301
- r.domain === 'template' &&
302
- r.kind !== 'block' &&
303
- r.score >= PAGE_FLOOR &&
304
- covers(r),
305
- )
306
- .slice(0, 3);
307
- const blocks = results
308
- .filter(
309
- r =>
310
- r.domain === 'template' &&
311
- r.kind === 'block' &&
312
- r.score >= DOMAIN_FLOOR,
313
- )
314
- .slice(0, 5);
315
- const domain = results
316
- .filter(
317
- r =>
318
- (r.domain === 'component' || r.domain === 'hook') &&
319
- r.score >= DOMAIN_FLOOR &&
320
- !ALWAYS.has(r.name),
321
- )
322
- .slice(0, 6);
323
- const directMatch =
324
- matchedPages.length > 0 && matchedPages[0].score >= PAGE_DIRECT;
325
-
326
- /**
327
- * On a loose match, a page entry's `command` previews the layout rather
328
- * than printing the whole template: `--skeleton` gives the shape without
329
- * presenting the page as the thing to build. The recommendation itself is
330
- * `start`, which always scaffolds. Copied rather than mutated: these entries
331
- * come from `search()` and are not this function's to modify.
332
- */
333
- const pages = directMatch
334
- ? matchedPages
335
- : matchedPages.map(page => ({
336
- ...page,
337
- command: `${page.command} --skeleton`,
338
- }));
339
-
340
- // The caller's `limit` caps the surfaced kit, even though the search above
341
- // ran wider to find templates that a flood of doc matches would otherwise
342
- // bury past the cutoff. Keep pages first, then blocks, then components.
343
- let budget = limit;
344
- /**
345
- * @template T
346
- * @param {T[]} arr
347
- * @returns {T[]}
348
- */
349
- const toLimit = arr => {
350
- const out = arr.slice(0, Math.max(0, budget));
351
- budget -= out.length;
352
- return out;
353
- };
354
- const pagesKept = toLimit(pages);
355
- const blocksKept = toLimit(blocks);
356
- const domainKept = toLimit(domain);
357
-
358
- // A kit narrowed to components or hooks has no page to start from; every
359
- // other kit does, so the reader is never left to compose a page from scratch.
360
- const wantsPages = !type || type === 'template';
361
- const catalog = wantsPages ? await loadPageTemplates(cwd) : [];
362
- const ranked = wantsPages ? rankPages(query, catalog) : [];
363
- // A part of a page starts where it lives (spec:AST-048/FR3); the project's
364
- // own components say what a part is. The search above already gathered them
365
- // unless it was narrowed to templates.
366
- const kind = wantsPages
367
- ? ideaKind(
368
- query,
369
- catalog,
370
- searchedComponents(result) ?? (await loadComponents(cwd)),
371
- )
372
- : 'page';
373
- const chosen = wantsPages
374
- ? chooseStart(ranked, kind, matchedPages, directMatch, catalog, query)
375
- : null;
376
- // Name the ranker's next two templates beside the start: the reader judges
377
- // meaning better than keywords do, and an acceptable template is in these
378
- // three far more often than it is the start alone.
379
- /** @type {BuildStart | null} */
380
- const start = chosen && {
381
- ...chosen,
382
- alternatives: pickAlternatives(ranked, chosen.name).flatMap(r => {
383
- const t = catalog.find(c => c.name === r.name);
384
- return t ? [asTemplate(t)] : [];
385
- }),
386
- };
387
-
388
- // Analyze what the start template needs that the project lacks (external
389
- // packages, StyleX compiler). Only the start — alternatives are suggestions,
390
- // not commitments, so reporting their needs would be noise.
391
- if (start) {
392
- const startTemplate = catalog.find(t => t.name === start.name);
393
- if (startTemplate) {
394
- const notes = templateSetupNotes(startTemplate, cwd);
395
- if (notes.length > 0) start.notes = notes;
396
- }
397
- }
398
-
399
- // What to try when the kit comes back thin. Keyword search over a design
400
- // system misses in a predictable way — the reader's words and the package's
401
- // often do not overlap — so name the two commands that browse rather than
402
- // search, and say plainly that this is not semantic matching.
403
- //
404
- // STRUCTURED, not prose: `commands` are bare subcommands, because the API
405
- // cannot know how the caller invokes the CLI. Baking `astryx component
406
- // --list` into the text hands a pnpm-workspace reader a command that does
407
- // not resolve — the same defect `getCliInvocation` exists to prevent, and
408
- // the renderer applies it. A JSON caller gets the parts, not a sentence.
409
- const hint =
410
- pagesKept.length + blocksKept.length + domainKept.length < THIN_KIT
411
- ? {
412
- reason:
413
- 'Few matches. This is keyword search, not semantic — try other wordings.',
414
- commands: ['component --list', 'template --list'],
415
- }
416
- : undefined;
417
-
418
- return {
419
- type: 'build.kit',
420
- data: {
421
- query: result.data.query,
422
- // Distinguishes "search found nothing" from a weak-but-non-empty result
423
- // set. Either way the kit still names a template to start from.
424
- hasResults: matchCount > 0,
425
- matchCount,
426
- directMatch,
427
- start,
428
- pages: pagesKept,
429
- blocks: blocksKept,
430
- domain: domainKept,
431
- frame: FRAME,
432
- foundation: FOUNDATION,
433
- hint,
434
- },
435
- };
45
+ return kitAnswer(await kitSignals(query, options));
436
46
  }
@@ -10,25 +10,6 @@
10
10
  * @returns {RankedPage[]}
11
11
  */
12
12
  export function rankPages(query: string, pages: PageTemplate[]): RankedPage[];
13
- /**
14
- * What an idea asks for (spec:AST-048/FR3): a whole `page`; a `part` of one,
15
- * when its head noun names one of the system's components and it lists fewer
16
- * than PAGE_PIECES pieces; or an `edit` of a page the builder already has.
17
- *
18
- * @param {string} query
19
- * @param {PageTemplate[]} pages
20
- * @param {ComponentWords[]} components
21
- * @returns {IdeaKind}
22
- */
23
- export function ideaKind(query: string, pages: PageTemplate[], components: ComponentWords[]): IdeaKind;
24
- /**
25
- * Whether an idea asks for a new page (see `asksNewPage`), from the idea and
26
- * the project's page templates.
27
- * @param {string} query
28
- * @param {PageTemplate[]} pages
29
- * @returns {boolean}
30
- */
31
- export function asksForNewPage(query: string, pages: PageTemplate[]): boolean;
32
13
  /**
33
14
  * The next closest templates after the start, best first: the ones a reader
34
15
  * should check the idea against when the start's shape is wrong. Each matched
@@ -53,16 +34,6 @@ export function pickAlternatives(ranked: RankedPage[], startName: string, count?
53
34
  * @returns {RankedPage | null}
54
35
  */
55
36
  export function pickStart(ranked: RankedPage[], kind?: IdeaKind): RankedPage | null;
56
- export type PageTemplate = import("../_adapter.mjs").PageTemplate;
57
- export type ComponentWords = import("../_adapter.mjs").ComponentWords;
58
- export type RankedPage = {
59
- name: string;
60
- score: number;
61
- hits: number;
62
- familyNamed: boolean;
63
- containerMatched: boolean;
64
- family: string;
65
- base: boolean;
66
- matched: Set<string>;
67
- };
68
- export type IdeaKind = "page" | "part" | "edit";
37
+ export type PageTemplate = import("./words.mjs").PageTemplate;
38
+ export type RankedPage = import("./words.mjs").RankedPage;
39
+ export type IdeaKind = import("./words.mjs").IdeaKind;