@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/kit.mjs
CHANGED
|
@@ -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 {
|
|
31
|
-
import {
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
}
|
package/api/build/kit/rank.d.mts
CHANGED
|
@@ -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("
|
|
57
|
-
export type
|
|
58
|
-
export type
|
|
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;
|