@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.
- package/README.md +99 -66
- package/api/build/_adapter.d.mts +58 -0
- package/api/build/_adapter.mjs +115 -3
- package/api/build/blocks/blocks.d.mts +42 -0
- package/api/build/blocks/blocks.mjs +169 -0
- package/api/build/blocks/blocks.test.mjs +108 -0
- package/api/build/blocks/blocks.weights.json +1 -0
- package/api/build/build.mjs +25 -6
- package/api/build/kit/answer.d.mts +14 -0
- package/api/build/kit/answer.mjs +85 -0
- package/api/build/kit/answer.test.mjs +38 -0
- package/api/build/kit/kind.d.mts +25 -0
- package/api/build/kit/kind.mjs +170 -0
- package/api/build/kit/kit.d.mts +2 -4
- package/api/build/kit/kit.mjs +8 -398
- package/api/build/kit/rank.d.mts +3 -32
- package/api/build/kit/rank.mjs +25 -342
- package/api/build/kit/rank.test.mjs +2 -1
- package/api/build/kit/signals.d.mts +60 -0
- package/api/build/kit/signals.mjs +284 -0
- package/api/build/kit/start.d.mts +32 -0
- package/api/build/kit/start.mjs +163 -0
- package/api/build/kit/words.d.mts +72 -0
- package/api/build/kit/words.mjs +202 -0
- package/api/build/tree.d.mts +20 -0
- package/api/build/tree.mjs +52 -0
- package/api/build/tree.test.mjs +177 -0
- package/api/build/tree.weights.json +1 -0
- package/api/component/component.mjs +16 -2
- package/api/component/component.test.mjs +15 -0
- package/api/discover/_adapter.mjs +36 -9
- package/api/discover/_adapter.test.mjs +42 -0
- package/api/docs/_adapter.mjs +17 -5
- package/api/docs/docs.test.mjs +7 -1
- package/api/doctor/doctor.mjs +86 -12
- package/api/doctor/doctor.test.mjs +28 -3
- package/api/init/init.doc.mjs +20 -6
- package/api/init/init.test.mjs +18 -2
- package/api/init/init.type.d.mts +3 -3
- package/api/init/init.type.mjs +3 -3
- package/api/init/remove/remove.mjs +17 -4
- package/api/init/remove/remove.test.mjs +114 -2
- package/api/init/run/run.mjs +69 -6
- package/api/integration/authoring-checks.mjs +83 -9
- package/api/integration/pack-check.mjs +123 -4
- package/api/integration/pack-check.test.mjs +87 -0
- package/api/integration/validate-integration-case-disk.test.mjs +111 -0
- package/api/integration/validate-integration-gaps.test.mjs +1964 -0
- package/api/integration/validate-integration.d.mts +11 -2
- package/api/integration/validate-integration.mjs +226 -32
- package/api/integration/validate-integration.type.d.mts +58 -1
- package/api/integration/validate-integration.type.mjs +45 -2
- package/api/integration/validateIntegration.doc.mjs +1 -1
- package/api/search/ORDERING.md +28 -0
- package/api/search/_adapter.d.mts +66 -2
- package/api/search/_adapter.mjs +118 -10
- package/api/search/_candidate-score.d.mts +40 -0
- package/api/search/_candidate-score.mjs +238 -0
- package/api/search/_cards.json +6098 -0
- package/api/search/_compound.d.mts +50 -0
- package/api/search/_compound.mjs +170 -0
- package/api/search/_doc-searcher.d.mts +4 -0
- package/api/search/_doc-searcher.mjs +23 -0
- package/api/search/_doc-searcher.test.mjs +36 -0
- package/api/search/_doc-searcher.weights.json +1 -0
- package/api/search/_doc-text.d.mts +30 -0
- package/api/search/_doc-text.mjs +87 -0
- package/api/search/_entries.d.mts +52 -0
- package/api/search/_entries.mjs +158 -0
- package/api/search/_features.d.mts +35 -0
- package/api/search/_features.mjs +155 -0
- package/api/search/_gather-components.d.mts +26 -0
- package/api/search/_gather-components.mjs +284 -0
- package/api/search/_gather-docs.d.mts +20 -0
- package/api/search/_gather-docs.mjs +254 -0
- package/api/search/_gather-hooks.d.mts +12 -0
- package/api/search/_gather-hooks.mjs +52 -0
- package/api/search/_gather-templates.d.mts +25 -0
- package/api/search/_gather-templates.mjs +107 -0
- package/api/search/_lexical.d.mts +135 -0
- package/api/search/_lexical.mjs +351 -0
- package/api/search/_main-searcher.d.mts +19 -0
- package/api/search/_main-searcher.mjs +115 -0
- package/api/search/_main-searcher.test.mjs +106 -0
- package/api/search/_main-searcher.weights.json +1 -0
- package/api/search/_module-doc.d.mts +32 -0
- package/api/search/_module-doc.mjs +78 -0
- package/api/search/_order.d.mts +32 -0
- package/api/search/_order.mjs +162 -0
- package/api/search/_order.test.mjs +72 -0
- package/api/search/_priority.d.mts +50 -0
- package/api/search/_priority.mjs +139 -0
- package/api/search/_query-words.d.mts +25 -0
- package/api/search/_query-words.mjs +92 -0
- package/api/search/_query.d.mts +65 -0
- package/api/search/_query.mjs +176 -0
- package/api/search/_result.d.mts +97 -0
- package/api/search/_result.mjs +150 -0
- package/api/search/_score.d.mts +60 -0
- package/api/search/_score.mjs +283 -0
- package/api/search/_synonyms.d.mts +21 -0
- package/api/search/_synonyms.mjs +67 -0
- package/api/search/_template-searcher.d.mts +4 -0
- package/api/search/_template-searcher.mjs +23 -0
- package/api/search/_template-searcher.test.mjs +40 -0
- package/api/search/_template-searcher.weights.json +1 -0
- package/api/search/_titles.d.mts +33 -0
- package/api/search/_titles.mjs +65 -0
- package/api/search/_words.d.mts +64 -0
- package/api/search/_words.mjs +157 -0
- package/api/search/search.d.mts +22 -305
- package/api/search/search.doc.mjs +7 -5
- package/api/search/search.mjs +53 -2483
- package/api/search/search.type.d.mts +6 -1
- package/api/search/search.type.mjs +6 -1
- package/api/search/tree.d.mts +15 -0
- package/api/search/tree.mjs +40 -0
- package/api/search/tree.test.mjs +66 -0
- package/api/search/tree.weights.json +1 -0
- package/api/theme/build/build.packed-old-core.test.mjs +1 -0
- package/api/upgrade/_adapter.d.mts +57 -9
- package/api/upgrade/_adapter.mjs +198 -28
- package/api/upgrade/registry/registry.d.mts +10 -4
- package/api/upgrade/registry/registry.mjs +33 -16
- package/api/upgrade/registry/registry.test.mjs +85 -0
- package/api/upgrade/run/run.d.mts +4 -2
- package/api/upgrade/run/run.mjs +13 -2
- package/api/upgrade/run/run.test.mjs +417 -3
- package/api/upgrade/upgrade.doc.mjs +10 -5
- package/api/upgrade/upgrade.mjs +13 -2
- package/api/upgrade/upgrade.test.mjs +194 -0
- package/api/upgrade/upgrade.type.d.mts +5 -3
- package/api/upgrade/upgrade.type.mjs +8 -6
- package/assets/codemods/transforms/v0.7.0/__tests__/validate-data-token-ownership.test.mjs +11 -0
- package/assets/codemods/transforms/v0.7.0/validate-data-token-ownership.mjs +3 -3
- package/assets/docs/is-my-code-good.doc.mjs +287 -0
- package/assets/docs/tree/api.doc.mjs +6 -0
- package/assets/docs/tree/host-entry.doc.mjs +128 -0
- package/assets/docs/tree/template-grading-rubric.doc.mjs +16 -37
- package/assets/docs/tree/tokens-data.doc.mjs +2 -2
- package/assets/docs/working-with-ai.doc.mjs +6 -2
- package/assets/templates/themes/butter/butterTheme.ts +4 -2
- package/assets/templates/themes/stone/stoneTheme.ts +4 -2
- package/assets/templates/themes/y2k/y2kTheme.ts +4 -2
- package/authoring/debug/parse.d.mts +1 -1
- package/clients/cli/bin/astryx.mjs +7 -78
- package/clients/cli/commands/doctor.mjs +30 -0
- package/clients/cli/commands/doctor.test.mjs +65 -0
- package/clients/cli/commands/init.behavior.test.mjs +230 -1
- package/clients/cli/commands/init.doc.mjs +28 -9
- package/clients/cli/commands/init.flag-help.test.mjs +1 -0
- package/clients/cli/commands/init.mjs +2 -2
- package/clients/cli/commands/outside-project.test.mjs +2 -1
- package/clients/cli/commands/setup-nudge.test.mjs +61 -0
- package/clients/cli/commands/upgrade.doc.mjs +4 -2
- package/clients/cli/commands/upgrade.mjs +9 -2
- package/clients/cli/commands/upgrade.subfolder-path.test.mjs +97 -0
- package/clients/cli/index.mjs +19 -1
- package/clients/cli/run.mjs +108 -0
- package/foundation/agent-docs/agent-doc-state.d.mts +20 -7
- package/foundation/agent-docs/agent-doc-state.mjs +35 -8
- package/foundation/agent-docs/agent-docs.d.mts +100 -12
- package/foundation/agent-docs/agent-docs.mjs +1010 -99
- package/foundation/agent-docs/agent-docs.path-safety.test.mjs +1239 -17
- package/foundation/agent-docs/agent-docs.test.mjs +678 -35
- package/foundation/config/project.mjs +20 -22
- package/foundation/config/project.test.mjs +56 -0
- package/foundation/discovery/component-discovery.d.mts +37 -0
- package/foundation/discovery/component-discovery.mjs +80 -5
- package/foundation/discovery/component-loader.d.mts +1 -1
- package/foundation/discovery/docs-discovery.mjs +14 -1
- package/foundation/discovery/module-exports.d.mts +41 -0
- package/foundation/discovery/module-exports.mjs +253 -0
- package/foundation/discovery/template-adapter.mjs +18 -1
- package/foundation/discovery/template-needs.mjs +1 -44
- package/foundation/discovery/theme-discovery.mjs +147 -3
- package/foundation/doc-compiler/doc-loads.test.mjs +8 -0
- package/foundation/doc-compiler/read.mjs +59 -1
- package/foundation/doc-compiler/read.test.mjs +11 -2
- package/foundation/fs/paths.d.mts +70 -8
- package/foundation/fs/paths.mjs +556 -38
- package/foundation/fs/paths.test.mjs +430 -54
- package/foundation/integrations/autolink.d.mts +2 -1
- package/foundation/integrations/autolink.mjs +4 -13
- package/foundation/integrations/computer-integrations.d.mts +6 -3
- package/foundation/integrations/computer-integrations.mjs +38 -9
- package/foundation/integrations/contribution-inventory.d.mts +15 -20
- package/foundation/integrations/contribution-inventory.mjs +41 -5
- package/foundation/integrations/integrations.d.mts +27 -1
- package/foundation/integrations/integrations.mjs +61 -2
- package/foundation/integrations/validate-contributions.d.mts +115 -0
- package/foundation/integrations/validate-contributions.mjs +1168 -13
- package/foundation/release/handoff.d.mts +23 -4
- package/foundation/release/handoff.mjs +175 -8
- package/foundation/release/run-context.mjs +14 -0
- package/foundation/response/response-types.doc.mjs +1 -1
- package/foundation/route/route.d.mts +285 -0
- package/foundation/route/route.mjs +324 -0
- package/foundation/route/route.test.mjs +153 -0
- package/foundation/runtime/hosted-runtime.test.mjs +403 -0
- package/foundation/runtime/locked-versions.d.mts +32 -0
- package/foundation/runtime/locked-versions.mjs +65 -0
- package/foundation/runtime/package-root.d.mts +91 -0
- package/foundation/runtime/package-root.mjs +174 -0
- package/foundation/runtime/package-set.d.mts +62 -0
- package/foundation/runtime/package-set.mjs +130 -0
- package/foundation/runtime/package-set.test.mjs +432 -0
- package/host/door-conformance.test.mjs +274 -0
- package/host/index.d.ts +116 -0
- package/host/index.mjs +295 -0
- package/host/index.test.mjs +302 -0
- package/package.json +14 -9
package/api/build/kit/rank.mjs
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
|
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
|
+
};
|