@astryxdesign/cli 0.7.2 → 0.7.3-canary.0bf23bd
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 +104 -71
- 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 +17 -3
- package/api/component/component.test.mjs +15 -0
- package/api/component/doc-links.test.mjs +2 -2
- package/api/discover/_adapter.mjs +36 -9
- package/api/discover/_adapter.test.mjs +42 -0
- package/api/docs/_adapter.d.mts +2 -2
- package/api/docs/_adapter.mjs +78 -23
- package/api/docs/docs-homes.test.mjs +127 -0
- package/api/docs/docs.doc.mjs +3 -3
- package/api/docs/docs.test.mjs +35 -19
- package/api/docs/integration-tree.test.mjs +20 -20
- package/api/docs/integrationDocs.test.mjs +1 -1
- package/api/docs/namespace-section.test.mjs +6 -6
- package/api/docs/reference-blocks.test.mjs +2 -2
- package/api/doctor/doctor.mjs +102 -24
- package/api/doctor/doctor.test.mjs +37 -12
- package/api/hook/hook.mjs +1 -1
- 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/_adapter.d.mts +8 -0
- package/api/integration/_adapter.mjs +21 -1
- package/api/integration/authoring-checks.mjs +83 -9
- package/api/integration/authoring-checks.test.mjs +220 -0
- 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 +250 -32
- package/api/integration/validate-integration.type.d.mts +59 -1
- package/api/integration/validate-integration.type.mjs +46 -2
- package/api/integration/validateIntegration.doc.mjs +1 -1
- package/api/search/ORDERING.md +28 -0
- package/api/search/_adapter.d.mts +82 -2
- package/api/search/_adapter.mjs +157 -10
- package/api/search/_adapter.test.mjs +173 -0
- 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 +214 -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 +8 -6
- package/api/search/search.mjs +56 -2482
- package/api/search/search.test.mjs +14 -14
- 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/author-a-theme.doc.mjs +2 -2
- package/assets/docs/is-my-code-good.doc.mjs +287 -0
- package/assets/docs/tree/add-a-component.doc.mjs +1 -1
- package/assets/docs/tree/add-a-theme.doc.mjs +1 -1
- package/assets/docs/tree/add-a-topic.doc.mjs +2 -2
- package/assets/docs/tree/agent-guidance.doc.mjs +1 -1
- package/assets/docs/tree/api.doc.mjs +6 -0
- package/assets/docs/tree/{integrations.doc.mjs → authoring.doc.mjs} +15 -6
- package/assets/docs/tree/block-template.doc.mjs +1 -1
- package/assets/docs/tree/build-the-template.doc.mjs +1 -1
- package/assets/docs/tree/building-blocks.doc.mjs +2 -2
- package/assets/docs/tree/check-the-props.doc.mjs +1 -1
- package/assets/docs/tree/check-your-docs.doc.mjs +2 -2
- package/assets/docs/tree/checks.doc.mjs +2 -2
- package/assets/docs/tree/cli-command.doc.mjs +1 -1
- package/assets/docs/tree/cli.doc.mjs +1 -1
- package/assets/docs/tree/codemods.doc.mjs +1 -1
- package/assets/docs/tree/component-family.doc.mjs +1 -1
- package/assets/docs/tree/component-imports.doc.mjs +1 -1
- package/assets/docs/tree/components.doc.mjs +1 -1
- package/assets/docs/tree/configuration.doc.mjs +1 -1
- package/assets/docs/tree/debug-and-gap-reports.doc.mjs +1 -1
- package/assets/docs/tree/define-the-theme.doc.mjs +1 -1
- package/assets/docs/tree/describe-the-component.doc.mjs +1 -1
- package/assets/docs/tree/docs.doc.mjs +1 -1
- package/assets/docs/tree/document-the-template.doc.mjs +1 -1
- package/assets/docs/tree/document-the-theme.doc.mjs +1 -1
- package/assets/docs/tree/export-template-assets.doc.mjs +1 -1
- package/assets/docs/tree/extend-or-replace.doc.mjs +1 -1
- package/assets/docs/{authoring.doc.mjs → tree/field-reference.doc.mjs} +4 -3
- package/assets/docs/tree/fonts-and-assets.doc.mjs +1 -1
- package/assets/docs/tree/generate-a-palette.doc.mjs +1 -1
- package/assets/docs/tree/global-integrations.doc.mjs +2 -2
- package/assets/docs/tree/grade-template-with-agent.doc.mjs +4 -4
- package/assets/docs/tree/help.doc.mjs +2 -2
- package/assets/docs/tree/host-entry.doc.mjs +128 -0
- package/assets/docs/tree/links.doc.mjs +1 -1
- package/assets/docs/tree/package-and-test.doc.mjs +1 -1
- package/assets/docs/tree/page-template.doc.mjs +1 -1
- package/assets/docs/tree/publishing.doc.mjs +1 -1
- package/assets/docs/tree/quick-start.doc.mjs +1 -1
- package/assets/docs/tree/replace-a-core-component.doc.mjs +1 -1
- package/assets/docs/tree/replace-a-core-template.doc.mjs +1 -1
- package/assets/docs/tree/sections-and-placement.doc.mjs +1 -1
- package/assets/docs/tree/see-it-in-an-app.doc.mjs +1 -1
- package/assets/docs/tree/ship.doc.mjs +2 -2
- package/assets/docs/tree/short-and-findable.doc.mjs +1 -1
- package/assets/docs/tree/single-component.doc.mjs +1 -1
- package/assets/docs/tree/start-a-template.doc.mjs +1 -1
- package/assets/docs/tree/subcomponent.doc.mjs +1 -1
- package/assets/docs/tree/template-assets.doc.mjs +1 -1
- package/assets/docs/tree/template-doc-overview.doc.mjs +2 -2
- package/assets/docs/tree/template-fonts.doc.mjs +1 -1
- package/assets/docs/tree/template-grading-rubric.doc.mjs +17 -38
- package/assets/docs/tree/template-icons.doc.mjs +1 -1
- package/assets/docs/tree/template-images-media.doc.mjs +1 -1
- package/assets/docs/tree/template-styles.doc.mjs +1 -1
- package/assets/docs/tree/templates.doc.mjs +1 -1
- package/assets/docs/tree/test-in-an-app.doc.mjs +1 -1
- package/assets/docs/tree/test-template-in-app.doc.mjs +1 -1
- package/assets/docs/tree/themes.doc.mjs +1 -1
- package/assets/docs/tree/tokens-data.doc.mjs +2 -2
- package/assets/docs/tree/troubleshooting.doc.mjs +1 -1
- package/assets/docs/tree/upgrading.doc.mjs +1 -1
- package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +1 -1
- package/assets/docs/tree/verify-packed-template.doc.mjs +1 -1
- package/assets/docs/tree/versioning.doc.mjs +1 -1
- package/assets/docs/tree/write-good-templates.doc.mjs +1 -1
- package/assets/docs/tree/write-the-template-file.doc.mjs +1 -1
- package/assets/docs/use-integrations.doc.mjs +120 -0
- 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/authoring/doctypes/schema/schema.doc.mjs +1 -1
- package/authoring/integration/integration.doc.mjs +1 -1
- package/authoring/integration/type.ts +1 -1
- package/clients/cli/bin/astryx.mjs +7 -78
- package/clients/cli/command-result-coverage.test.mjs +6 -5
- package/clients/cli/commands/build-theme.color-scheme.test.mjs +1 -1
- package/clients/cli/commands/{component/index.mjs → component.mjs} +37 -37
- package/clients/cli/commands/component.test.mjs +6 -6
- package/clients/cli/commands/docs.doc.mjs +2 -2
- package/clients/cli/commands/docs.mjs +1 -1
- package/clients/cli/commands/docs.test.mjs +14 -14
- package/clients/cli/commands/doctor.mjs +30 -0
- package/clients/cli/commands/doctor.test.mjs +65 -0
- package/clients/cli/commands/{hook/index.mjs → hook.mjs} +22 -22
- package/clients/cli/commands/import-hint-correctness.test.mjs +1 -1
- 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/integration-check.test.mjs +48 -0
- package/clients/cli/commands/integration.doc.mjs +1 -1
- package/clients/cli/commands/manifest.doc.mjs +1 -1
- package/clients/cli/commands/manifest.mjs +67 -0
- package/clients/cli/commands/outside-project.test.mjs +2 -1
- package/clients/cli/commands/postinstall.doc.mjs +23 -0
- package/clients/cli/commands/postinstall.mjs +31 -0
- package/clients/cli/commands/setup-nudge.test.mjs +61 -0
- package/clients/cli/commands/{build-theme.mjs → theme.mjs} +1 -1
- 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 +29 -86
- package/clients/cli/lib/component-format.mjs +1 -1
- package/clients/cli/lib/define-command.mjs +12 -7
- package/clients/cli/lib/exit-codes.test.mjs +5 -0
- package/clients/cli/lib/manifest.test.mjs +12 -0
- package/clients/cli/lib/welcome-box.mjs +50 -0
- 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 +1012 -100
- package/foundation/agent-docs/agent-docs.path-safety.test.mjs +1239 -17
- package/foundation/agent-docs/agent-docs.test.mjs +683 -39
- package/foundation/config/project.mjs +20 -22
- package/foundation/config/project.test.mjs +56 -0
- package/foundation/discovery/authoring-self-docs.d.mts +7 -0
- package/foundation/discovery/authoring-self-docs.mjs +16 -4
- package/foundation/discovery/authoring-self-docs.test.mjs +15 -3
- package/foundation/discovery/authoring-surface.d.mts +1 -1
- package/foundation/discovery/authoring-surface.mjs +2 -2
- package/foundation/discovery/authoring-surface.test.mjs +1 -1
- package/foundation/discovery/cli-self-docs.d.mts +2 -1
- package/foundation/discovery/cli-self-docs.mjs +4 -3
- package/foundation/discovery/cli-self-docs.test.mjs +2 -2
- 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/runner-allowlist.d.mts +2 -4
- package/foundation/discovery/runner-allowlist.mjs +2 -4
- package/foundation/discovery/runner-commands.d.mts +77 -0
- package/foundation/discovery/runner-commands.mjs +294 -0
- package/foundation/discovery/runner-commands.test.mjs +128 -0
- package/foundation/discovery/stray-pointers.test.mjs +1 -1
- 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 +33 -9
- package/foundation/doc-compiler/read.mjs +59 -1
- package/foundation/doc-compiler/read.test.mjs +11 -2
- package/foundation/doc-compiler/split-topic-keywords.test.mjs +3 -2
- package/foundation/doc-compiler/tree.d.mts +32 -6
- package/foundation/doc-compiler/tree.mjs +77 -43
- package/foundation/doc-compiler/tree.test.mjs +7 -7
- 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/fs/read-record.d.mts +18 -0
- package/foundation/fs/read-record.mjs +131 -0
- package/foundation/fs/read-record.test.mjs +77 -0
- 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
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Search ordering data
|
|
2
|
+
|
|
3
|
+
`astryx search` decides **what matches**, and each match's score and reason, with the lexical ranker (`_lexical.mjs` and the files it imports). The files in this folder only decide **the order of matches inside one score band**: they never add or drop a match, change a score, or move a match past one of another band or kind (doc or not). The bands are scores 195 and up, 188–194, 170–187, 160–169, 151–159, 100–150, 55–99, and under 55. A band can be wide (55–99 holds keyword matches beside one-word description hits), so inside a band a lower score can lead a higher one.
|
|
4
|
+
|
|
5
|
+
## Rule
|
|
6
|
+
|
|
7
|
+
`spec:AST-083` owns search's result order; this file describes the data that implements it. Inside one score band, a searcher's static weights decide the order. Matches with the same score move together as one block, in the lexical ranker's order, placed by the learned position of the block's first match: moving each one on its own breaks search's existing rules for tied matches. Matches never cross bands or kinds. There is no model runtime: every number is a plain static value read at load time.
|
|
8
|
+
|
|
9
|
+
## Files
|
|
10
|
+
|
|
11
|
+
| File | What it holds |
|
|
12
|
+
| ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
13
|
+
| `tree.weights.json` | The root classifier: for each query word or word pair, one number per class (component, hook, doc, template, theme, none). The query's tokens vote, and a topic searcher takes the query only when its class wins by the floor in `tree.mjs`. |
|
|
14
|
+
| `_main-searcher.weights.json`, `_doc-searcher.weights.json`, `_template-searcher.weights.json` | One weight, mean and spread per signal for each searcher (see `SIGNALS` and `PER_DOMAIN` in `_features.mjs`). A match's place inside its band is the weighted sum of its standardized signals. |
|
|
15
|
+
| `_cards.json` | One card per Core component and hook: what it is, the words people use for it, and its main props, written from the item's own source. Cards feed one signal, word overlap with the card. |
|
|
16
|
+
|
|
17
|
+
The signals are counts over the query and each entry's own text: the lexical score and rank, BM25 over the entry's name, docs text and card, how many query words the entry covers, whether the query holds the entry's name, and the classifier's guess of the domain.
|
|
18
|
+
|
|
19
|
+
## Status
|
|
20
|
+
|
|
21
|
+
These files are static and reviewed. They are not computed when the CLI runs, and nothing at run time writes them.
|
|
22
|
+
|
|
23
|
+
## Refreshing
|
|
24
|
+
|
|
25
|
+
- A Core component or hook without a card is still matched and scored as usual; inside its band it is ordered by its other signals.
|
|
26
|
+
- When Core adds or renames items, a reviewed change adds or updates their cards and, if needed, refreshed weights.
|
|
27
|
+
- Any refresh must keep every test that pins search's behavior passing, including the ordering invariants in `_order.test.mjs`. It must also keep the `astryx search` output's matches, scores and reasons identical to the lexical ranker's.
|
|
28
|
+
- Any refresh must also be measured as `spec:AST-083` FR6 requires: against the held-out, fresh and synthetic evaluation sets, with no row worse than the previous order (a row is worse when the paired 95% confidence interval of its change lies entirely below zero).
|
|
@@ -1,11 +1,91 @@
|
|
|
1
1
|
// @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
|
|
2
2
|
// DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
|
|
3
3
|
|
|
4
|
+
/** @typedef {import('./_lexical.mjs').Candidate} Candidate */
|
|
4
5
|
/**
|
|
5
6
|
* The Core a typed search reads: the project's, else the release's own,
|
|
6
|
-
* installed into the per-user cache first when needed
|
|
7
|
-
* requireCoreDir does.
|
|
7
|
+
* installed into the per-user cache first when needed (a network install and
|
|
8
|
+
* an npm subprocess; spec AST-081 FR3). Throws as requireCoreDir does.
|
|
8
9
|
* @param {string} cwd
|
|
9
10
|
* @returns {string}
|
|
10
11
|
*/
|
|
11
12
|
export function requireSearchCoreDir(cwd: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Gathers every candidate the query's domains hold.
|
|
15
|
+
*
|
|
16
|
+
* @param {{cwd?: string, type?: string}} options
|
|
17
|
+
* @returns {Promise<{all: Candidate[], components: Candidate[], searchedComponents: boolean, outside: boolean, coreSkipped: string | null}>}
|
|
18
|
+
*/
|
|
19
|
+
export function gatherCandidates(options: {
|
|
20
|
+
cwd?: string;
|
|
21
|
+
type?: string;
|
|
22
|
+
}): Promise<{
|
|
23
|
+
all: Candidate[];
|
|
24
|
+
components: Candidate[];
|
|
25
|
+
searchedComponents: boolean;
|
|
26
|
+
outside: boolean;
|
|
27
|
+
coreSkipped: string | null;
|
|
28
|
+
}>;
|
|
29
|
+
/**
|
|
30
|
+
* gatherCandidates, reused within one process: a later search in the same
|
|
31
|
+
* folder, for the same `type`, ranks the candidates an earlier search
|
|
32
|
+
* gathered when none of the files that gather read, and none of the folders
|
|
33
|
+
* it listed or probed, changed since (each is checked by its change time and
|
|
34
|
+
* size, so an edited doc in a linked package is seen on the next call). A
|
|
35
|
+
* long-running caller (a docs site's search, an agent harness) then pays the
|
|
36
|
+
* gather once. A one-shot `astryx search` gathers once, as before.
|
|
37
|
+
* @param {{cwd?: string, type?: string}} options
|
|
38
|
+
* @returns {Promise<Gathered>}
|
|
39
|
+
*/
|
|
40
|
+
export function gatherCandidatesReused(options: {
|
|
41
|
+
cwd?: string;
|
|
42
|
+
type?: string;
|
|
43
|
+
}): Promise<Gathered>;
|
|
44
|
+
/**
|
|
45
|
+
* The search tree's static data, read once: the entry cards and each node's
|
|
46
|
+
* weights file.
|
|
47
|
+
*
|
|
48
|
+
* These files are static, reviewed data that only order matches inside a score
|
|
49
|
+
* band; ORDERING.md beside them says what each holds and how
|
|
50
|
+
* they are refreshed.
|
|
51
|
+
* @returns {SearchData}
|
|
52
|
+
*/
|
|
53
|
+
export function loadSearchData(): SearchData;
|
|
54
|
+
export { componentKeywords } from "./_gather-components.mjs";
|
|
55
|
+
export type Candidate = import("./_lexical.mjs").Candidate;
|
|
56
|
+
export type Gathered = Awaited<ReturnType<typeof gatherCandidates>>;
|
|
57
|
+
export type SearcherWeights = {
|
|
58
|
+
w: number[];
|
|
59
|
+
mu: number[];
|
|
60
|
+
sd: number[];
|
|
61
|
+
};
|
|
62
|
+
export type SearchData = {
|
|
63
|
+
/**
|
|
64
|
+
* Each Core component's and hook's source-built card: what it is, the words
|
|
65
|
+
* people type for it, its main props.
|
|
66
|
+
*/
|
|
67
|
+
cards: Record<string, {
|
|
68
|
+
what?: string;
|
|
69
|
+
synonyms?: string[];
|
|
70
|
+
props?: string[];
|
|
71
|
+
}>;
|
|
72
|
+
/**
|
|
73
|
+
* The root classifier's weights.
|
|
74
|
+
*/
|
|
75
|
+
router: import("../../foundation/route/route.mjs").ClassifierWeights;
|
|
76
|
+
/**
|
|
77
|
+
* The main searcher's weights.
|
|
78
|
+
*/
|
|
79
|
+
main: SearcherWeights;
|
|
80
|
+
/**
|
|
81
|
+
* The doc searcher's weights.
|
|
82
|
+
*/
|
|
83
|
+
doc: SearcherWeights;
|
|
84
|
+
/**
|
|
85
|
+
* The template searcher's weights.
|
|
86
|
+
*/
|
|
87
|
+
template: SearcherWeights;
|
|
88
|
+
};
|
|
89
|
+
export type SearchCatalog = Awaited<ReturnType<typeof gatherCandidates>> & {
|
|
90
|
+
data: SearchData;
|
|
91
|
+
};
|
package/api/search/_adapter.mjs
CHANGED
|
@@ -1,25 +1,172 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @file
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* INV21).
|
|
8
|
-
*
|
|
9
|
-
* @input the folder a search runs in
|
|
10
|
-
* @output the Core folder a search reads
|
|
11
|
-
* @position packages/cli/api/search — imported by search.mjs only
|
|
4
|
+
* @file Search's adapter: the only place search reads the environment. It
|
|
5
|
+
* gathers every candidate from the domains a query asks for (one file per
|
|
6
|
+
* domain, _gather-*.mjs) and loads the tree's static data before it runs.
|
|
12
7
|
*/
|
|
13
8
|
|
|
9
|
+
import {readFileSync} from 'node:fs';
|
|
10
|
+
import * as path from 'node:path';
|
|
14
11
|
import {requireCoreDir} from '../../foundation/fs/paths.mjs';
|
|
12
|
+
import {recordReads, unchangedSince} from '../../foundation/fs/read-record.mjs';
|
|
13
|
+
import {isOutsideProject} from '../../foundation/env/project-scope.mjs';
|
|
14
|
+
import {coreDirOrThrow} from '../error.mjs';
|
|
15
|
+
import {CORELESS_DOMAINS} from './_query.mjs';
|
|
16
|
+
import {gatherComponents} from './_gather-components.mjs';
|
|
17
|
+
import {gatherHooks} from './_gather-hooks.mjs';
|
|
18
|
+
import {gatherDocs} from './_gather-docs.mjs';
|
|
19
|
+
import {gatherTemplates, gatherThemes} from './_gather-templates.mjs';
|
|
20
|
+
|
|
21
|
+
export {componentKeywords} from './_gather-components.mjs';
|
|
22
|
+
|
|
23
|
+
/** @typedef {import('./_lexical.mjs').Candidate} Candidate */
|
|
15
24
|
|
|
16
25
|
/**
|
|
17
26
|
* The Core a typed search reads: the project's, else the release's own,
|
|
18
|
-
* installed into the per-user cache first when needed
|
|
19
|
-
* requireCoreDir does.
|
|
27
|
+
* installed into the per-user cache first when needed (a network install and
|
|
28
|
+
* an npm subprocess; spec AST-081 FR3). Throws as requireCoreDir does.
|
|
20
29
|
* @param {string} cwd
|
|
21
30
|
* @returns {string}
|
|
22
31
|
*/
|
|
23
32
|
export function requireSearchCoreDir(cwd) {
|
|
24
33
|
return requireCoreDir(cwd);
|
|
25
34
|
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Gathers every candidate the query's domains hold.
|
|
38
|
+
*
|
|
39
|
+
* @param {{cwd?: string, type?: string}} options
|
|
40
|
+
* @returns {Promise<{all: Candidate[], components: Candidate[], searchedComponents: boolean, outside: boolean, coreSkipped: string | null}>}
|
|
41
|
+
*/
|
|
42
|
+
export async function gatherCandidates(options) {
|
|
43
|
+
const {cwd = process.cwd(), type} = options;
|
|
44
|
+
// `astryx docs` reads docs without @astryxdesign/core, and `astryx theme
|
|
45
|
+
// list` reads themes without it (bundled themes need no project), so a
|
|
46
|
+
// search of either must too. Every other domain reads core: asked for by
|
|
47
|
+
// name, it is an error without core; an open search then covers the docs
|
|
48
|
+
// and themes alone.
|
|
49
|
+
const needsCore = !type || !CORELESS_DOMAINS.includes(type);
|
|
50
|
+
// A domain that reads Core gets it as `component` does: the project's, else
|
|
51
|
+
// the answering release's own, installed into the per-user cache on first
|
|
52
|
+
// need (spec AST-081 FR3). Asked for by name, a Core that cannot be had is
|
|
53
|
+
// the error; an open search answers from the rest and says what it skipped
|
|
54
|
+
// and why, never silently.
|
|
55
|
+
/** @type {string | null} */
|
|
56
|
+
let coreDir = null;
|
|
57
|
+
/** @type {string | null} */
|
|
58
|
+
let coreSkipped = null;
|
|
59
|
+
if (needsCore && type) {
|
|
60
|
+
coreDir = coreDirOrThrow(requireSearchCoreDir, cwd);
|
|
61
|
+
} else if (needsCore) {
|
|
62
|
+
try {
|
|
63
|
+
coreDir = requireSearchCoreDir(cwd);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
coreSkipped = /** @type {Error} */ (error).message;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Gather candidates from each requested domain in parallel.
|
|
70
|
+
/** @param {string} d */
|
|
71
|
+
const wants = d =>
|
|
72
|
+
(!type && (coreDir != null || CORELESS_DOMAINS.includes(d))) || type === d;
|
|
73
|
+
const [components, hooks, docTopics, templates, themes] = await Promise.all([
|
|
74
|
+
wants('component')
|
|
75
|
+
? gatherComponents(/** @type {string} */ (coreDir), cwd)
|
|
76
|
+
: [],
|
|
77
|
+
wants('hook') ? gatherHooks(/** @type {string} */ (coreDir)) : [],
|
|
78
|
+
wants('doc') ? gatherDocs(cwd) : [],
|
|
79
|
+
wants('template') ? gatherTemplates(cwd) : [],
|
|
80
|
+
wants('theme') ? gatherThemes(cwd) : [],
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
const all = [...components, ...hooks, ...docTopics, ...templates, ...themes];
|
|
84
|
+
// Outside a project a theme cannot be added to an app, so its command forks
|
|
85
|
+
// the source instead (spec AST-081 FR3).
|
|
86
|
+
const outside = themes.length > 0 && isOutsideProject(cwd);
|
|
87
|
+
return {
|
|
88
|
+
all,
|
|
89
|
+
components,
|
|
90
|
+
searchedComponents: wants('component'),
|
|
91
|
+
outside,
|
|
92
|
+
coreSkipped,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @typedef {Awaited<ReturnType<typeof gatherCandidates>>} Gathered
|
|
98
|
+
* @type {Map<string, {reads: Map<string, string>, value: Promise<Gathered>}>}
|
|
99
|
+
*/
|
|
100
|
+
const gathered = new Map();
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* gatherCandidates, reused within one process: a later search in the same
|
|
104
|
+
* folder, for the same `type`, ranks the candidates an earlier search
|
|
105
|
+
* gathered when none of the files that gather read, and none of the folders
|
|
106
|
+
* it listed or probed, changed since (each is checked by its change time and
|
|
107
|
+
* size, so an edited doc in a linked package is seen on the next call). A
|
|
108
|
+
* long-running caller (a docs site's search, an agent harness) then pays the
|
|
109
|
+
* gather once. A one-shot `astryx search` gathers once, as before.
|
|
110
|
+
* @param {{cwd?: string, type?: string}} options
|
|
111
|
+
* @returns {Promise<Gathered>}
|
|
112
|
+
*/
|
|
113
|
+
export async function gatherCandidatesReused(options) {
|
|
114
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
115
|
+
const key = `${cwd}\0${options.type ?? ''}`;
|
|
116
|
+
const hit = gathered.get(key);
|
|
117
|
+
if (hit && unchangedSince(hit.reads)) return hit.value;
|
|
118
|
+
/** @type {Map<string, string>} */
|
|
119
|
+
const seen = new Map();
|
|
120
|
+
const value = recordReads(seen, () => gatherCandidates({...options, cwd}));
|
|
121
|
+
try {
|
|
122
|
+
await value;
|
|
123
|
+
} catch (error) {
|
|
124
|
+
if (gathered.get(key)?.value === value) gathered.delete(key);
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
// Each path's state was taken as it was first read, so an edit made while
|
|
128
|
+
// the gather ran shows as a change on the next call.
|
|
129
|
+
gathered.set(key, {reads: seen, value});
|
|
130
|
+
return value;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* @typedef {{w: number[], mu: number[], sd: number[]}} SearcherWeights
|
|
135
|
+
* @typedef {object} SearchData
|
|
136
|
+
* @property {Record<string, {what?: string, synonyms?: string[], props?: string[]}>} cards
|
|
137
|
+
* Each Core component's and hook's source-built card: what it is, the words
|
|
138
|
+
* people type for it, its main props.
|
|
139
|
+
* @property {import('../../foundation/route/route.mjs').ClassifierWeights} router The root classifier's weights.
|
|
140
|
+
* @property {SearcherWeights} main The main searcher's weights.
|
|
141
|
+
* @property {SearcherWeights} doc The doc searcher's weights.
|
|
142
|
+
* @property {SearcherWeights} template The template searcher's weights.
|
|
143
|
+
* @typedef {Awaited<ReturnType<typeof gatherCandidates>> & {data: SearchData}} SearchCatalog
|
|
144
|
+
*/
|
|
145
|
+
|
|
146
|
+
/** @type {SearchData | null} */
|
|
147
|
+
let searchData = null;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* The search tree's static data, read once: the entry cards and each node's
|
|
151
|
+
* weights file.
|
|
152
|
+
*
|
|
153
|
+
* These files are static, reviewed data that only order matches inside a score
|
|
154
|
+
* band; ORDERING.md beside them says what each holds and how
|
|
155
|
+
* they are refreshed.
|
|
156
|
+
* @returns {SearchData}
|
|
157
|
+
*/
|
|
158
|
+
export function loadSearchData() {
|
|
159
|
+
if (!searchData) {
|
|
160
|
+
/** @param {string} file */
|
|
161
|
+
const read = file =>
|
|
162
|
+
JSON.parse(readFileSync(new URL(file, import.meta.url), 'utf8'));
|
|
163
|
+
searchData = {
|
|
164
|
+
cards: read('./_cards.json'),
|
|
165
|
+
router: read('./tree.weights.json'),
|
|
166
|
+
main: read('./_main-searcher.weights.json'),
|
|
167
|
+
doc: read('./_doc-searcher.weights.json'),
|
|
168
|
+
template: read('./_template-searcher.weights.json'),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return searchData;
|
|
172
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Tests for search's adapter: a long-running caller reuses the gathered
|
|
5
|
+
* candidates while their inputs are unchanged (gatherCandidatesReused), and
|
|
6
|
+
* gathers again when an input changes or for another folder or `type`.
|
|
7
|
+
*
|
|
8
|
+
* Whether inputs changed is read-record's answer (tested in
|
|
9
|
+
* read-record.test.mjs). Other tests running at the same time change shared
|
|
10
|
+
* folders a gather reads (the temp folder, the repository root), so here the
|
|
11
|
+
* reuse case holds that answer at "unchanged", and every other case only
|
|
12
|
+
* checks that a change forces a new gather, which concurrent changes cannot
|
|
13
|
+
* undo.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {describe, it, expect, vi} from 'vitest';
|
|
17
|
+
import * as fs from 'node:fs';
|
|
18
|
+
import * as os from 'node:os';
|
|
19
|
+
import * as path from 'node:path';
|
|
20
|
+
import * as readRecord from '../../foundation/fs/read-record.mjs';
|
|
21
|
+
import {gatherCandidates, gatherCandidatesReused} from './_adapter.mjs';
|
|
22
|
+
|
|
23
|
+
vi.mock('../../foundation/fs/read-record.mjs', async original => {
|
|
24
|
+
const real = /** @type {typeof readRecord} */ (await original());
|
|
25
|
+
return {...real, unchangedSince: vi.fn(real.unchangedSince)};
|
|
26
|
+
});
|
|
27
|
+
const unchangedSince = vi.mocked(readRecord.unchangedSince);
|
|
28
|
+
|
|
29
|
+
const SLOW = 60_000;
|
|
30
|
+
|
|
31
|
+
/** A folder with only a package.json: a docs search there reads the CLI's docs. */
|
|
32
|
+
function scratchProject() {
|
|
33
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-gather-reuse-'));
|
|
34
|
+
fs.writeFileSync(path.join(dir, 'package.json'), '{"name":"app"}');
|
|
35
|
+
return dir;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe('gatherCandidatesReused', () => {
|
|
39
|
+
it(
|
|
40
|
+
'reuses the gathered candidates while the inputs are unchanged',
|
|
41
|
+
async () => {
|
|
42
|
+
const cwd = scratchProject();
|
|
43
|
+
const first = await gatherCandidatesReused({cwd, type: 'doc'});
|
|
44
|
+
unchangedSince.mockReturnValueOnce(true);
|
|
45
|
+
expect(await gatherCandidatesReused({cwd, type: 'doc'})).toBe(first);
|
|
46
|
+
expect(first.all.length).toBeGreaterThan(0);
|
|
47
|
+
// The same candidates a fresh gather finds.
|
|
48
|
+
expect(JSON.parse(JSON.stringify(first))).toEqual(
|
|
49
|
+
JSON.parse(JSON.stringify(await gatherCandidates({cwd, type: 'doc'}))),
|
|
50
|
+
);
|
|
51
|
+
},
|
|
52
|
+
SLOW,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
it(
|
|
56
|
+
'gathers again when an input file changes',
|
|
57
|
+
async () => {
|
|
58
|
+
const cwd = scratchProject();
|
|
59
|
+
const first = await gatherCandidatesReused({cwd, type: 'doc'});
|
|
60
|
+
const later = new Date(Date.now() + 5_000);
|
|
61
|
+
fs.utimesSync(path.join(cwd, 'package.json'), later, later);
|
|
62
|
+
expect(await gatherCandidatesReused({cwd, type: 'doc'})).not.toBe(first);
|
|
63
|
+
fs.writeFileSync(
|
|
64
|
+
path.join(cwd, 'astryx.config.mjs'),
|
|
65
|
+
'export default {};\n',
|
|
66
|
+
);
|
|
67
|
+
expect(await gatherCandidatesReused({cwd, type: 'doc'})).not.toBe(first);
|
|
68
|
+
},
|
|
69
|
+
SLOW,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
it(
|
|
73
|
+
'keeps one entry per folder and type',
|
|
74
|
+
async () => {
|
|
75
|
+
const cwd = scratchProject();
|
|
76
|
+
const docs = await gatherCandidatesReused({cwd, type: 'doc'});
|
|
77
|
+
const themes = await gatherCandidatesReused({cwd, type: 'theme'});
|
|
78
|
+
expect(themes).not.toBe(docs);
|
|
79
|
+
expect(themes.all.every(c => c.domain === 'theme')).toBe(true);
|
|
80
|
+
expect(
|
|
81
|
+
await gatherCandidatesReused({cwd: scratchProject(), type: 'doc'}),
|
|
82
|
+
).not.toBe(docs);
|
|
83
|
+
},
|
|
84
|
+
SLOW,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
it(
|
|
88
|
+
'gathers again when a file the gather read changes, in a linked package too',
|
|
89
|
+
async () => {
|
|
90
|
+
// A consumer whose integration is a symlink to a package folder
|
|
91
|
+
// elsewhere, as a workspace package is.
|
|
92
|
+
const dir = fs.mkdtempSync(
|
|
93
|
+
path.join(process.cwd(), '.astryx-gather-reuse-'),
|
|
94
|
+
);
|
|
95
|
+
const pkg = fs.mkdtempSync(
|
|
96
|
+
path.join(os.tmpdir(), 'astryx-linked-widgets-'),
|
|
97
|
+
);
|
|
98
|
+
try {
|
|
99
|
+
fs.writeFileSync(path.join(dir, 'package.json'), '{"name":"consumer"}');
|
|
100
|
+
fs.writeFileSync(
|
|
101
|
+
path.join(dir, 'astryx.config.mjs'),
|
|
102
|
+
"export default { integrations: ['@acme/widgets'] };\n",
|
|
103
|
+
);
|
|
104
|
+
fs.mkdirSync(
|
|
105
|
+
path.join(dir, 'node_modules', '@astryxdesign', 'core', 'src'),
|
|
106
|
+
{
|
|
107
|
+
recursive: true,
|
|
108
|
+
},
|
|
109
|
+
);
|
|
110
|
+
fs.mkdirSync(path.join(pkg, 'components'));
|
|
111
|
+
fs.writeFileSync(
|
|
112
|
+
path.join(pkg, 'package.json'),
|
|
113
|
+
'{"name":"@acme/widgets","version":"1.0.0"}',
|
|
114
|
+
);
|
|
115
|
+
fs.writeFileSync(
|
|
116
|
+
path.join(pkg, 'astryx.integration.mjs'),
|
|
117
|
+
"export default { components: './components' };\n",
|
|
118
|
+
);
|
|
119
|
+
fs.writeFileSync(
|
|
120
|
+
path.join(pkg, 'components', 'FancyGizmo.doc.mjs'),
|
|
121
|
+
`export default {
|
|
122
|
+
type: 'component',
|
|
123
|
+
name: 'FancyGizmo',
|
|
124
|
+
keywords: ['gizmo'],
|
|
125
|
+
usage: {description: 'A fancy part.'},
|
|
126
|
+
props: [],
|
|
127
|
+
};\n`,
|
|
128
|
+
);
|
|
129
|
+
fs.writeFileSync(
|
|
130
|
+
path.join(pkg, 'components', 'FancyGizmo.tsx'),
|
|
131
|
+
'export function FancyGizmo() { return null; }\n',
|
|
132
|
+
);
|
|
133
|
+
fs.mkdirSync(path.join(dir, 'node_modules', '@acme'));
|
|
134
|
+
fs.symlinkSync(
|
|
135
|
+
pkg,
|
|
136
|
+
path.join(dir, 'node_modules', '@acme', 'widgets'),
|
|
137
|
+
'dir',
|
|
138
|
+
);
|
|
139
|
+
const options = {cwd: dir, type: 'component'};
|
|
140
|
+
const first = await gatherCandidatesReused(options);
|
|
141
|
+
expect(first.all.map(c => c.name)).toContain('FancyGizmo');
|
|
142
|
+
|
|
143
|
+
// A new component in the linked package: its folder changed.
|
|
144
|
+
fs.writeFileSync(
|
|
145
|
+
path.join(pkg, 'components', 'TinySprocket.doc.mjs'),
|
|
146
|
+
`export default {
|
|
147
|
+
type: 'component',
|
|
148
|
+
name: 'TinySprocket',
|
|
149
|
+
keywords: ['sprocket'],
|
|
150
|
+
usage: {description: 'A small part.'},
|
|
151
|
+
props: [],
|
|
152
|
+
};\n`,
|
|
153
|
+
);
|
|
154
|
+
fs.writeFileSync(
|
|
155
|
+
path.join(pkg, 'components', 'TinySprocket.tsx'),
|
|
156
|
+
'export function TinySprocket() { return null; }\n',
|
|
157
|
+
);
|
|
158
|
+
const second = await gatherCandidatesReused(options);
|
|
159
|
+
expect(second).not.toBe(first);
|
|
160
|
+
expect(second.all.map(c => c.name)).toContain('TinySprocket');
|
|
161
|
+
|
|
162
|
+
// An edited file the gather read: the linked package's manifest.
|
|
163
|
+
const later = new Date(Date.now() + 5_000);
|
|
164
|
+
fs.utimesSync(path.join(pkg, 'package.json'), later, later);
|
|
165
|
+
expect(await gatherCandidatesReused(options)).not.toBe(second);
|
|
166
|
+
} finally {
|
|
167
|
+
fs.rmSync(dir, {recursive: true, force: true});
|
|
168
|
+
fs.rmSync(pkg, {recursive: true, force: true});
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
SLOW,
|
|
172
|
+
);
|
|
173
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
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
|
+
* Score a single candidate against the search term across name, keywords,
|
|
6
|
+
* and prose signals. Returns the best (highest) score plus a human reason,
|
|
7
|
+
* or null if nothing matched above the floor.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} term - Lowercased search term.
|
|
10
|
+
* @param {object} candidate
|
|
11
|
+
* @param {string} candidate.name - Primary identifier (component/hook name, topic, template name).
|
|
12
|
+
* @param {string[]} [candidate.aliases] - Other names, scored like the name (a theme's display name).
|
|
13
|
+
* @param {string} [candidate.domain] - A component, hook, or template name
|
|
14
|
+
* also matches typed as words: `command palette` is CommandPalette.
|
|
15
|
+
* @param {string[]} [candidate.keywords] - Authored intent (componentsUsed, category words).
|
|
16
|
+
* @param {string[]} [candidate.weakKeywords] - Derived signal (components a page renders).
|
|
17
|
+
* @param {string} [candidate.description]
|
|
18
|
+
* @param {string[]} [candidate.prose] - Extra free-text blobs (doc section text, best practices).
|
|
19
|
+
* @param {string[]} [candidate.guidance] - Usage guidance (features, best practices) — scored a tier below description.
|
|
20
|
+
* @param {{fuzzy?: boolean, joins?: boolean}} [opts] - `fuzzy`: allow edit-distance (typo) matches. Default true; multi-word queries pass false. `joins`: read a spaced query joined into a name or keyword (default true); off when the query names an entry outright.
|
|
21
|
+
* @returns {{score: number, reason: string, alias?: 'partial' | 'keyword'} | null}
|
|
22
|
+
* `alias` marks a hit that read the query's words joined.
|
|
23
|
+
*/
|
|
24
|
+
export function scoreCandidate(term: string, { name, aliases, domain, keywords, weakKeywords, description, prose, guidance, }: {
|
|
25
|
+
name: string;
|
|
26
|
+
aliases?: string[] | undefined;
|
|
27
|
+
domain?: string | undefined;
|
|
28
|
+
keywords?: string[] | undefined;
|
|
29
|
+
weakKeywords?: string[] | undefined;
|
|
30
|
+
description?: string | undefined;
|
|
31
|
+
prose?: string[] | undefined;
|
|
32
|
+
guidance?: string[] | undefined;
|
|
33
|
+
}, { fuzzy, joins }?: {
|
|
34
|
+
fuzzy?: boolean;
|
|
35
|
+
joins?: boolean;
|
|
36
|
+
}): {
|
|
37
|
+
score: number;
|
|
38
|
+
reason: string;
|
|
39
|
+
alias?: "partial" | "keyword";
|
|
40
|
+
} | null;
|