@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/_adapter.d.mts
CHANGED
|
@@ -32,6 +32,27 @@
|
|
|
32
32
|
* @returns {Promise<PageTemplate[]>}
|
|
33
33
|
*/
|
|
34
34
|
export function loadPageTemplates(cwd: string): Promise<PageTemplate[]>;
|
|
35
|
+
/**
|
|
36
|
+
* A block the kit can name, as search names a block result.
|
|
37
|
+
* @typedef {object} BlockTemplate
|
|
38
|
+
* @property {string} name The block's own id, as search reports it.
|
|
39
|
+
* @property {string} package The npm package that owns this block.
|
|
40
|
+
* @property {string} displayName Human-facing name.
|
|
41
|
+
* @property {string} description What the block is.
|
|
42
|
+
* @property {string} command `astryx template <id> --type block`, as search prints it.
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* Every ready page template and every block the project can scaffold, from one
|
|
46
|
+
* discovery: pages as {@link loadPageTemplates} returns them, blocks as
|
|
47
|
+
* {@link BlockTemplate}. Discovery failures leave both empty.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} cwd
|
|
50
|
+
* @returns {Promise<{pages: PageTemplate[], blocks: BlockTemplate[]}>}
|
|
51
|
+
*/
|
|
52
|
+
export function loadTemplates(cwd: string): Promise<{
|
|
53
|
+
pages: PageTemplate[];
|
|
54
|
+
blocks: BlockTemplate[];
|
|
55
|
+
}>;
|
|
35
56
|
/**
|
|
36
57
|
* The components the project can use, Core's and its integrations', each with
|
|
37
58
|
* the keywords its own doc declares: what the ranker reads to tell a part of a
|
|
@@ -48,6 +69,18 @@ export function loadComponents(cwd: string): Promise<ComponentWords[]>;
|
|
|
48
69
|
* @returns {import('./kit/weights.mjs').WeightsFile | null}
|
|
49
70
|
*/
|
|
50
71
|
export function loadWeights(): import("./kit/weights.mjs").WeightsFile | null;
|
|
72
|
+
/**
|
|
73
|
+
* The classifier weights of build's root (tree.weights.json), read once.
|
|
74
|
+
* @returns {import('../../foundation/route/route.mjs').ClassifierWeights}
|
|
75
|
+
*/
|
|
76
|
+
export function loadTreeWeights(): import("../../foundation/route/route.mjs").ClassifierWeights;
|
|
77
|
+
/**
|
|
78
|
+
* The block picker's weights (blocks/blocks.weights.json), read once; null
|
|
79
|
+
* when the file is absent, unreadable, or not the shape the picker reads, in
|
|
80
|
+
* which case build names its own blocks.
|
|
81
|
+
* @returns {import('./blocks/blocks.mjs').BlockPickerWeights | null}
|
|
82
|
+
*/
|
|
83
|
+
export function loadBlockPicker(): import("./blocks/blocks.mjs").BlockPickerWeights | null;
|
|
51
84
|
/**
|
|
52
85
|
* Whether a parsed weights file has the shape the kit reads: every row has a
|
|
53
86
|
* weight per candidate, every bias a number per candidate, and three blend
|
|
@@ -125,3 +158,28 @@ export type ComponentWords = {
|
|
|
125
158
|
*/
|
|
126
159
|
keywords: string[];
|
|
127
160
|
};
|
|
161
|
+
/**
|
|
162
|
+
* A block the kit can name, as search names a block result.
|
|
163
|
+
*/
|
|
164
|
+
export type BlockTemplate = {
|
|
165
|
+
/**
|
|
166
|
+
* The block's own id, as search reports it.
|
|
167
|
+
*/
|
|
168
|
+
name: string;
|
|
169
|
+
/**
|
|
170
|
+
* The npm package that owns this block.
|
|
171
|
+
*/
|
|
172
|
+
package: string;
|
|
173
|
+
/**
|
|
174
|
+
* Human-facing name.
|
|
175
|
+
*/
|
|
176
|
+
displayName: string;
|
|
177
|
+
/**
|
|
178
|
+
* What the block is.
|
|
179
|
+
*/
|
|
180
|
+
description: string;
|
|
181
|
+
/**
|
|
182
|
+
* `astryx template <id> --type block`, as search prints it.
|
|
183
|
+
*/
|
|
184
|
+
command: string;
|
|
185
|
+
};
|
package/api/build/_adapter.mjs
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
* contribute.
|
|
10
10
|
* @output Ready page templates as `{name, displayName, description, category,
|
|
11
11
|
* keywords, command}`, where `command` is the `astryx template` command that
|
|
12
|
-
* selects exactly that template;
|
|
12
|
+
* selects exactly that template; blocks as search names them; components as
|
|
13
|
+
* `{name, keywords}`; the kit's matcher weights, the root's classifier
|
|
14
|
+
* weights, and the block picker's weights.
|
|
13
15
|
* @position Beside build.mjs (api/build/). The kit leaf reads templates and
|
|
14
16
|
* components only through here, because a subject's `_adapter.mjs` is its
|
|
15
17
|
* only environment access. This adds no discovery of its own: templates come
|
|
@@ -63,13 +65,35 @@ export {withListingCache} from '../../foundation/fs/listing-cache.mjs';
|
|
|
63
65
|
* @returns {Promise<PageTemplate[]>}
|
|
64
66
|
*/
|
|
65
67
|
export async function loadPageTemplates(cwd) {
|
|
68
|
+
return (await loadTemplates(cwd)).pages;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* A block the kit can name, as search names a block result.
|
|
73
|
+
* @typedef {object} BlockTemplate
|
|
74
|
+
* @property {string} name The block's own id, as search reports it.
|
|
75
|
+
* @property {string} package The npm package that owns this block.
|
|
76
|
+
* @property {string} displayName Human-facing name.
|
|
77
|
+
* @property {string} description What the block is.
|
|
78
|
+
* @property {string} command `astryx template <id> --type block`, as search prints it.
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Every ready page template and every block the project can scaffold, from one
|
|
83
|
+
* discovery: pages as {@link loadPageTemplates} returns them, blocks as
|
|
84
|
+
* {@link BlockTemplate}. Discovery failures leave both empty.
|
|
85
|
+
*
|
|
86
|
+
* @param {string} cwd
|
|
87
|
+
* @returns {Promise<{pages: PageTemplate[], blocks: BlockTemplate[]}>}
|
|
88
|
+
*/
|
|
89
|
+
export async function loadTemplates(cwd) {
|
|
66
90
|
let templates;
|
|
67
91
|
try {
|
|
68
92
|
templates = await discoverTemplates(cwd);
|
|
69
93
|
} catch {
|
|
70
|
-
return [];
|
|
94
|
+
return {pages: [], blocks: []};
|
|
71
95
|
}
|
|
72
|
-
|
|
96
|
+
const pages = templates
|
|
73
97
|
.filter(t => t.type === 'page' && t.isReady !== false)
|
|
74
98
|
.map(t => ({
|
|
75
99
|
name: t.dirName,
|
|
@@ -83,6 +107,16 @@ export async function loadPageTemplates(cwd) {
|
|
|
83
107
|
// owns the Core id it names, so that id selects it, not its own.
|
|
84
108
|
command: `astryx template ${t.replaces ?? t.dirName} --type page`,
|
|
85
109
|
}));
|
|
110
|
+
const blocks = templates
|
|
111
|
+
.filter(t => t.type === 'block')
|
|
112
|
+
.map(t => ({
|
|
113
|
+
name: t.dirName,
|
|
114
|
+
package: t.package ?? '@astryxdesign/core',
|
|
115
|
+
displayName: t.name,
|
|
116
|
+
description: t.description || '',
|
|
117
|
+
command: `astryx template ${t.replaces ?? t.dirName} --type block`,
|
|
118
|
+
}));
|
|
119
|
+
return {pages, blocks};
|
|
86
120
|
}
|
|
87
121
|
|
|
88
122
|
/**
|
|
@@ -126,6 +160,48 @@ export function loadWeights() {
|
|
|
126
160
|
return weights ?? null;
|
|
127
161
|
}
|
|
128
162
|
|
|
163
|
+
/** @type {import('../../foundation/route/route.mjs').ClassifierWeights | undefined} */
|
|
164
|
+
let treeWeights;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* The classifier weights of build's root (tree.weights.json), read once.
|
|
168
|
+
* @returns {import('../../foundation/route/route.mjs').ClassifierWeights}
|
|
169
|
+
*/
|
|
170
|
+
export function loadTreeWeights() {
|
|
171
|
+
treeWeights ??= JSON.parse(
|
|
172
|
+
fs.readFileSync(new URL('./tree.weights.json', import.meta.url), 'utf8'),
|
|
173
|
+
);
|
|
174
|
+
return /** @type {import('../../foundation/route/route.mjs').ClassifierWeights} */ (
|
|
175
|
+
treeWeights
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** @type {import('./blocks/blocks.mjs').BlockPickerWeights | null | undefined} */
|
|
180
|
+
let blockPicker;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* The block picker's weights (blocks/blocks.weights.json), read once; null
|
|
184
|
+
* when the file is absent, unreadable, or not the shape the picker reads, in
|
|
185
|
+
* which case build names its own blocks.
|
|
186
|
+
* @returns {import('./blocks/blocks.mjs').BlockPickerWeights | null}
|
|
187
|
+
*/
|
|
188
|
+
export function loadBlockPicker() {
|
|
189
|
+
if (blockPicker === undefined) {
|
|
190
|
+
try {
|
|
191
|
+
const file = JSON.parse(
|
|
192
|
+
fs.readFileSync(
|
|
193
|
+
new URL('./blocks/blocks.weights.json', import.meta.url),
|
|
194
|
+
'utf8',
|
|
195
|
+
),
|
|
196
|
+
);
|
|
197
|
+
blockPicker = isBlockPickerWeights(file) ? file : null;
|
|
198
|
+
} catch {
|
|
199
|
+
blockPicker = null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return blockPicker ?? null;
|
|
203
|
+
}
|
|
204
|
+
|
|
129
205
|
/**
|
|
130
206
|
* Whether a parsed weights file has the shape the kit reads: every row has a
|
|
131
207
|
* weight per candidate, every bias a number per candidate, and three blend
|
|
@@ -159,6 +235,42 @@ export function isWeightsFile(file) {
|
|
|
159
235
|
);
|
|
160
236
|
}
|
|
161
237
|
|
|
238
|
+
/**
|
|
239
|
+
* Whether a parsed block picker file has the shape the picker reads: a
|
|
240
|
+
* vocabulary weight per word, a bias per block, sparse rows of
|
|
241
|
+
* [block index, weight] pairs, and five combiner numbers.
|
|
242
|
+
* @param {any} w
|
|
243
|
+
* @returns {w is import('./blocks/blocks.mjs').BlockPickerWeights}
|
|
244
|
+
*/
|
|
245
|
+
function isBlockPickerWeights(w) {
|
|
246
|
+
const n = Array.isArray(w?.classes) ? w.classes.length : 0;
|
|
247
|
+
return (
|
|
248
|
+
n > 0 &&
|
|
249
|
+
w.classes.every((/** @type {unknown} */ c) => typeof c === 'string') &&
|
|
250
|
+
Array.isArray(w.bias) &&
|
|
251
|
+
w.bias.length === n &&
|
|
252
|
+
w.bias.every((/** @type {unknown} */ b) => Number.isFinite(b)) &&
|
|
253
|
+
!!w.vocab &&
|
|
254
|
+
typeof w.vocab === 'object' &&
|
|
255
|
+
Object.values(w.vocab).every(v => Number.isFinite(v)) &&
|
|
256
|
+
!!w.words &&
|
|
257
|
+
typeof w.words === 'object' &&
|
|
258
|
+
Object.values(w.words).every(
|
|
259
|
+
row =>
|
|
260
|
+
Array.isArray(row) &&
|
|
261
|
+
row.length % 2 === 0 &&
|
|
262
|
+
row.every(
|
|
263
|
+
(/** @type {number} */ x, /** @type {number} */ i) =>
|
|
264
|
+
Number.isFinite(x) &&
|
|
265
|
+
(i % 2 === 1 || (Number.isInteger(x) && x >= 0 && x < n)),
|
|
266
|
+
),
|
|
267
|
+
) &&
|
|
268
|
+
Array.isArray(w.theta) &&
|
|
269
|
+
w.theta.length === 5 &&
|
|
270
|
+
w.theta.every((/** @type {unknown} */ x) => Number.isFinite(x))
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
162
274
|
/**
|
|
163
275
|
* Analyze what a start template needs that the project lacks. The kit leaf
|
|
164
276
|
* calls this through the adapter rather than reading the file itself, because
|
|
@@ -0,0 +1,42 @@
|
|
|
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 blocks build names for a request, re-ranked by the picker.
|
|
6
|
+
* @param {string} request
|
|
7
|
+
* @param {{kitBlocks: string[], searched: BlockScore[], foreign: string[]}} signals
|
|
8
|
+
* build's signals: the kit's blocks, search's block results [id, score,
|
|
9
|
+
* matched, total] in order, and the blocks whose package is not Core's
|
|
10
|
+
* @param {BlockPickerWeights} weights
|
|
11
|
+
* @returns {string[]} the kit's blocks in a new order: a block the request
|
|
12
|
+
* names, one the weights do not know, or one an integration ships keeps its
|
|
13
|
+
* slot; the others, best first, fill the remaining slots
|
|
14
|
+
*/
|
|
15
|
+
export function pickBlocks(request: string, signals: {
|
|
16
|
+
kitBlocks: string[];
|
|
17
|
+
searched: BlockScore[];
|
|
18
|
+
foreign: string[];
|
|
19
|
+
}, weights: BlockPickerWeights): string[];
|
|
20
|
+
export type BlockPickerWeights = {
|
|
21
|
+
/**
|
|
22
|
+
* word or word pair -> weight x1024
|
|
23
|
+
*/
|
|
24
|
+
vocab: Record<string, number>;
|
|
25
|
+
/**
|
|
26
|
+
* the block ids the words score
|
|
27
|
+
*/
|
|
28
|
+
classes: string[];
|
|
29
|
+
/**
|
|
30
|
+
* one per class, x64
|
|
31
|
+
*/
|
|
32
|
+
bias: number[];
|
|
33
|
+
/**
|
|
34
|
+
* word -> [class index, weight x64, ...]
|
|
35
|
+
*/
|
|
36
|
+
words: Record<string, number[]>;
|
|
37
|
+
/**
|
|
38
|
+
* the five combiner numbers
|
|
39
|
+
*/
|
|
40
|
+
theta: number[];
|
|
41
|
+
};
|
|
42
|
+
export type BlockScore = import("../kit/signals.mjs").BlockScore;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file build's block picker: re-ranks the blocks build would name for a
|
|
5
|
+
* request with checked-in word weights, and keeps build's own count.
|
|
6
|
+
*
|
|
7
|
+
* @input The request, build's signals for it (`kitSignals`: the blocks the kit
|
|
8
|
+
* names, search's block results with their scores and coverage), the block
|
|
9
|
+
* ids the project can scaffold, and the picker weights (`blocks.weights.json`
|
|
10
|
+
* beside this file, read by the adapter): a vocabulary of words and word
|
|
11
|
+
* pairs with their weight (x1024), the block ids it scores, a bias per block
|
|
12
|
+
* and a sparse row of weights per word (x64), and five combiner numbers.
|
|
13
|
+
* @output `pickBlocks(...)`: block ids, best first, as many as build's own list.
|
|
14
|
+
* @position api/build/blocks; tree.mjs's main searcher calls it between
|
|
15
|
+
* kitSignals (kit/signals.mjs) and kitAnswer (kit/answer.mjs). Pure: no file, network, or environment access.
|
|
16
|
+
*
|
|
17
|
+
* How it scores. The request's words and adjacent word pairs, each weighted
|
|
18
|
+
* (1 + ln count) x its vocabulary weight and scaled to unit length, give every
|
|
19
|
+
* block a linear score (its bias plus the weighted words' rows). Only the
|
|
20
|
+
* blocks build already names are re-ranked: never another block, never more.
|
|
21
|
+
* Each one's final score combines five numbers: search's score / 100, the
|
|
22
|
+
* share of the request search matched, whether the kit names it, 1 / (1 + its
|
|
23
|
+
* place in build's list), and the block's log-share of the linear scores.
|
|
24
|
+
* Ties keep build's order. A block the request names (its name, or the
|
|
25
|
+
* component its name starts with, written out, joined, or plural), a block the
|
|
26
|
+
* weights do not know, and a block whose package is not Core's keep the slot
|
|
27
|
+
* build gave them; the picker re-orders the rest within the other slots.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import {pluralOf} from '../../search/_query.mjs';
|
|
31
|
+
import {wordsOf} from '../../search/_words.mjs';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @typedef {object} BlockPickerWeights
|
|
35
|
+
* @property {Record<string, number>} vocab word or word pair -> weight x1024
|
|
36
|
+
* @property {string[]} classes the block ids the words score
|
|
37
|
+
* @property {number[]} bias one per class, x64
|
|
38
|
+
* @property {Record<string, number[]>} words word -> [class index, weight x64, ...]
|
|
39
|
+
* @property {number[]} theta the five combiner numbers
|
|
40
|
+
*
|
|
41
|
+
* @typedef {import('../kit/signals.mjs').BlockScore} BlockScore
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/** The linear score of a block the words do not score. */
|
|
45
|
+
const UNSCORED = -30;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The words and adjacent word pairs of a request, with their weights, in the
|
|
49
|
+
* order they first appear; only those in the vocabulary.
|
|
50
|
+
* @param {string} request
|
|
51
|
+
* @param {BlockPickerWeights} weights
|
|
52
|
+
* @returns {Map<string, number>}
|
|
53
|
+
*/
|
|
54
|
+
function features(request, weights) {
|
|
55
|
+
const tokens =
|
|
56
|
+
String(request ?? '')
|
|
57
|
+
.toLowerCase()
|
|
58
|
+
.match(/[a-z0-9]{2,}/g) ?? [];
|
|
59
|
+
const all = [...tokens];
|
|
60
|
+
for (let i = 0; i + 1 < tokens.length; i++)
|
|
61
|
+
all.push(`${tokens[i]} ${tokens[i + 1]}`);
|
|
62
|
+
/** @type {Map<string, number>} */
|
|
63
|
+
const counts = new Map();
|
|
64
|
+
for (const f of all) {
|
|
65
|
+
if (Object.hasOwn(weights.vocab, f))
|
|
66
|
+
counts.set(f, (counts.get(f) ?? 0) + 1);
|
|
67
|
+
}
|
|
68
|
+
/** @type {Map<string, number>} */
|
|
69
|
+
const x = new Map();
|
|
70
|
+
for (const [f, c] of counts)
|
|
71
|
+
x.set(f, ((1 + Math.log(c)) * weights.vocab[f]) / 1024);
|
|
72
|
+
return x;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Every class's linear score for a request, and the log of the normalizer.
|
|
77
|
+
* @param {string} request
|
|
78
|
+
* @param {BlockPickerWeights} weights
|
|
79
|
+
* @returns {{z: number[], lse: number}}
|
|
80
|
+
*/
|
|
81
|
+
function linearScores(request, weights) {
|
|
82
|
+
const x = features(request, weights);
|
|
83
|
+
let sq = 0;
|
|
84
|
+
for (const v of x.values()) sq += v * v;
|
|
85
|
+
const norm = Math.sqrt(sq);
|
|
86
|
+
const z = weights.bias.slice();
|
|
87
|
+
if (norm > 0) {
|
|
88
|
+
for (const [f, v] of x) {
|
|
89
|
+
const row = Object.hasOwn(weights.words, f) ? weights.words[f] : null;
|
|
90
|
+
if (!row) continue;
|
|
91
|
+
for (let k = 0; k < row.length; k += 2)
|
|
92
|
+
z[row[k]] += (v / norm) * row[k + 1];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
for (let c = 0; c < z.length; c++) z[c] /= 64;
|
|
96
|
+
let max = -Infinity;
|
|
97
|
+
for (const v of z) if (v > max) max = v;
|
|
98
|
+
let sum = 0;
|
|
99
|
+
for (const v of z) sum += Math.exp(v - max);
|
|
100
|
+
return {z, lse: max + Math.log(sum)};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Whether a request names a block: a run of one to three adjacent words of
|
|
105
|
+
* the request, joined, is the block's name, the start of its name that names
|
|
106
|
+
* its component (at least four letters, whole words), or the plural of either.
|
|
107
|
+
* @param {string[]} words the request's lowercase words
|
|
108
|
+
* @param {string} block
|
|
109
|
+
* @returns {boolean}
|
|
110
|
+
*/
|
|
111
|
+
function names(words, block) {
|
|
112
|
+
const parts = wordsOf(block);
|
|
113
|
+
/** @type {Set<string>} */
|
|
114
|
+
const forms = new Set();
|
|
115
|
+
for (let n = 1; n <= parts.length; n++) {
|
|
116
|
+
const form = parts.slice(0, n).join('');
|
|
117
|
+
if (form.length >= 4) forms.add(form);
|
|
118
|
+
}
|
|
119
|
+
for (let i = 0; i < words.length; i++) {
|
|
120
|
+
let run = '';
|
|
121
|
+
for (let n = 0; n < 3 && i + n < words.length; n++) {
|
|
122
|
+
run += words[i + n];
|
|
123
|
+
for (const form of forms)
|
|
124
|
+
if (run === form || pluralOf(run, form)) return true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The blocks build names for a request, re-ranked by the picker.
|
|
132
|
+
* @param {string} request
|
|
133
|
+
* @param {{kitBlocks: string[], searched: BlockScore[], foreign: string[]}} signals
|
|
134
|
+
* build's signals: the kit's blocks, search's block results [id, score,
|
|
135
|
+
* matched, total] in order, and the blocks whose package is not Core's
|
|
136
|
+
* @param {BlockPickerWeights} weights
|
|
137
|
+
* @returns {string[]} the kit's blocks in a new order: a block the request
|
|
138
|
+
* names, one the weights do not know, or one an integration ships keeps its
|
|
139
|
+
* slot; the others, best first, fill the remaining slots
|
|
140
|
+
*/
|
|
141
|
+
export function pickBlocks(request, signals, weights) {
|
|
142
|
+
const own = signals.kitBlocks;
|
|
143
|
+
if (own.length === 0) return [];
|
|
144
|
+
const {z, lse} = linearScores(request, weights);
|
|
145
|
+
const classIndex = new Map(weights.classes.map((c, i) => [c, i]));
|
|
146
|
+
/** @type {Map<string, BlockScore>} */
|
|
147
|
+
const found = new Map();
|
|
148
|
+
for (const s of signals.searched) if (!found.has(s[0])) found.set(s[0], s);
|
|
149
|
+
const foreign = new Set(signals.foreign);
|
|
150
|
+
const words = wordsOf(request);
|
|
151
|
+
const keeps = own.map(
|
|
152
|
+
b => !classIndex.has(b) || foreign.has(b) || names(words, b),
|
|
153
|
+
);
|
|
154
|
+
const [t0, t1, t2, t3, t4] = weights.theta;
|
|
155
|
+
const scored = own
|
|
156
|
+
.map((b, pos) => ({b, pos}))
|
|
157
|
+
.filter((_, k) => !keeps[k])
|
|
158
|
+
.map(({b, pos}) => {
|
|
159
|
+
const s = found.get(b);
|
|
160
|
+
const c = classIndex.get(b);
|
|
161
|
+
const f0 = s ? s[1] / 100 : 0;
|
|
162
|
+
const f1 = s ? (s[2] ?? 0) / Math.max(s[3] ?? 0, 1) : 0;
|
|
163
|
+
const f4 = (c === undefined ? UNSCORED : z[c]) - lse;
|
|
164
|
+
return {b, pos, s: t0 * f0 + t1 * f1 + t2 + t3 / (1 + pos) + t4 * f4};
|
|
165
|
+
})
|
|
166
|
+
.sort((a, b) => b.s - a.s || a.pos - b.pos);
|
|
167
|
+
let next = 0;
|
|
168
|
+
return own.map((b, k) => (keeps[k] ? b : scored[next++].b));
|
|
169
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Unit tests for build's block picker on small hand-written weights: it
|
|
5
|
+
* only re-orders the blocks build names (never another block, never more), a
|
|
6
|
+
* block the request names keeps its slot, as does a block the weights do not
|
|
7
|
+
* know or an integration ships, and ties keep build's order.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {describe, expect, it} from 'vitest';
|
|
11
|
+
import {pickBlocks} from './blocks.mjs';
|
|
12
|
+
|
|
13
|
+
/** @type {import('./blocks.mjs').BlockPickerWeights} */
|
|
14
|
+
const WORDS = {
|
|
15
|
+
vocab: {chart: 1024, table: 2048, sales: 1536, 'sales chart': 3072, row: 512},
|
|
16
|
+
classes: ['ChartBar', 'TableBasic', 'CardStat', 'ListPlain', 'CalendarBasic'],
|
|
17
|
+
bias: [10, -20, 0, -64, 0],
|
|
18
|
+
words: {
|
|
19
|
+
chart: [0, 90, 2, 30],
|
|
20
|
+
table: [1, 120, 3, -40],
|
|
21
|
+
'sales chart': [0, 60, 2, 50],
|
|
22
|
+
row: [2, 80],
|
|
23
|
+
},
|
|
24
|
+
theta: [0.5, 0.25, 0.5, 0.4, 1.5],
|
|
25
|
+
};
|
|
26
|
+
/** @type {[string, number, number, number][]} */
|
|
27
|
+
const SEARCHED = [
|
|
28
|
+
['TableBasic', 96, 1, 3],
|
|
29
|
+
['ListPlain', 64, 1, 3],
|
|
30
|
+
['ChartBar', 50, 1, 3],
|
|
31
|
+
['CardStat', 40, 1, 3],
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
/** @type {import('./blocks.mjs').BlockPickerWeights} */
|
|
35
|
+
const TIES = {
|
|
36
|
+
vocab: {alpha: 1024},
|
|
37
|
+
classes: ['X', 'Y', 'Z'],
|
|
38
|
+
bias: [0, 0, 0],
|
|
39
|
+
words: {},
|
|
40
|
+
theta: [0, 0, 0, 0, 1],
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {string[]} kitBlocks
|
|
45
|
+
* @param {string[]} [foreign]
|
|
46
|
+
*/
|
|
47
|
+
const signals = (kitBlocks, foreign = []) => ({
|
|
48
|
+
kitBlocks,
|
|
49
|
+
searched: SEARCHED,
|
|
50
|
+
foreign,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('pickBlocks', () => {
|
|
54
|
+
it('re-orders only the blocks build names, never adding one', () => {
|
|
55
|
+
const own = ['TableBasic', 'ListPlain', 'CardStat'];
|
|
56
|
+
const picks = pickBlocks('sales chart with a row', signals(own), WORDS);
|
|
57
|
+
expect([...picks].sort()).toEqual([...own].sort());
|
|
58
|
+
expect(picks).toEqual(['CardStat', 'TableBasic', 'ListPlain']);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('never names a block the words score highly that build did not name', () => {
|
|
62
|
+
const picks = pickBlocks(
|
|
63
|
+
'sales chart chart chart',
|
|
64
|
+
signals(['TableBasic', 'ListPlain']),
|
|
65
|
+
WORDS,
|
|
66
|
+
);
|
|
67
|
+
expect(picks).not.toContain('ChartBar');
|
|
68
|
+
expect(picks).toHaveLength(2);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('keeps a block the request names in its slot', () => {
|
|
72
|
+
const own = ['TableBasic', 'CalendarBasic', 'CardStat'];
|
|
73
|
+
for (const request of [
|
|
74
|
+
'sales chart with a calendar',
|
|
75
|
+
'sales chart with two calendars',
|
|
76
|
+
'sales chart, calendar basic',
|
|
77
|
+
]) {
|
|
78
|
+
expect(pickBlocks(request, signals(own), WORDS)[1]).toBe('CalendarBasic');
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('keeps a block the weights do not know or an integration ships in its slot', () => {
|
|
83
|
+
const own = ['TableBasic', 'AcmeCallout', 'CardStat', 'ListPlain'];
|
|
84
|
+
const picks = pickBlocks(
|
|
85
|
+
'sales chart with a row',
|
|
86
|
+
signals(own, ['ListPlain']),
|
|
87
|
+
WORDS,
|
|
88
|
+
);
|
|
89
|
+
expect(picks[1]).toBe('AcmeCallout');
|
|
90
|
+
expect(picks[3]).toBe('ListPlain');
|
|
91
|
+
expect([...picks].sort()).toEqual([...own].sort());
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('keeps build’s order on ties and when no word is known', () => {
|
|
95
|
+
const own = ['X', 'Y', 'Z'];
|
|
96
|
+
expect(
|
|
97
|
+
pickBlocks('??', {kitBlocks: own, searched: [], foreign: []}, TIES),
|
|
98
|
+
).toEqual(own);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('returns nothing when build names no block, and does not change its input', () => {
|
|
102
|
+
expect(pickBlocks('sales chart', signals([]), WORDS)).toEqual([]);
|
|
103
|
+
const own = ['TableBasic', 'ListPlain'];
|
|
104
|
+
const copy = [...own];
|
|
105
|
+
pickBlocks('sales chart', signals(own), WORDS);
|
|
106
|
+
expect(own).toEqual(copy);
|
|
107
|
+
});
|
|
108
|
+
});
|