@astryxdesign/cli 0.7.2-canary.caec7ca → 0.7.2-canary.caf5673

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/README.md +15 -14
  2. package/api/build/help/help.mjs +1 -1
  3. package/api/component/_adapter.d.mts +41 -1
  4. package/api/component/_adapter.mjs +93 -7
  5. package/api/component/component.mjs +22 -5
  6. package/api/component/component.test.mjs +80 -0
  7. package/api/component/integration-export-name.test.mjs +644 -0
  8. package/api/component/list/list.mjs +42 -4
  9. package/api/docs/_adapter.d.mts +11 -0
  10. package/api/docs/_adapter.mjs +244 -1
  11. package/api/docs/integrationDocs.test.mjs +124 -0
  12. package/api/doctor/doctor.mjs +27 -3
  13. package/api/doctor/doctor.test.mjs +43 -0
  14. package/api/doctor/theme-checks.mjs +1 -1
  15. package/api/gap-report/gap-report.mjs +67 -4
  16. package/api/index.d.mts +2 -0
  17. package/api/index.mjs +2 -0
  18. package/api/init/run/run.mjs +2 -2
  19. package/api/integration/_adapter.d.mts +12 -0
  20. package/api/integration/_adapter.mjs +54 -0
  21. package/api/integration/add-contribution.component-names.test.mjs +66 -0
  22. package/api/integration/add-contribution.mjs +8 -4
  23. package/api/integration/add-contribution.test.mjs +4 -2
  24. package/api/integration/authoring-checks.mjs +11 -2
  25. package/api/integration/authoring-checks.test.mjs +39 -0
  26. package/api/integration/authoring-checks.type.d.mts +9 -0
  27. package/api/integration/authoring-checks.type.mjs +14 -1
  28. package/api/integration/check/check.d.mts +96 -0
  29. package/api/integration/check/check.mjs +357 -0
  30. package/api/integration/check/check.test.mjs +489 -0
  31. package/api/integration/integration-check.type.d.mts +118 -0
  32. package/api/integration/integration-check.type.mjs +84 -0
  33. package/api/integration/integrationCheck.doc.d.mts +10 -0
  34. package/api/integration/integrationCheck.doc.mjs +73 -0
  35. package/api/integration/integrationComponentConflicts.doc.mjs +43 -6
  36. package/api/integration/pack-check.mjs +61 -107
  37. package/api/integration/pack-check.test.mjs +359 -0
  38. package/api/integration/validate-integration.mjs +8 -0
  39. package/api/integration/validate-integration.test.mjs +11 -0
  40. package/api/integration/validate-integration.type.d.mts +8 -0
  41. package/api/integration/validate-integration.type.mjs +12 -1
  42. package/api/integration/validateIntegration.doc.mjs +19 -3
  43. package/api/search/search.d.mts +81 -12
  44. package/api/search/search.domain-priority.test.mjs +108 -18
  45. package/api/search/search.integration-check.test.mjs +24 -0
  46. package/api/search/search.mjs +608 -53
  47. package/api/search/search.test.mjs +175 -0
  48. package/api/swizzle/copy/copy.mjs +28 -14
  49. package/api/theme/build/build.family.test.mjs +64 -0
  50. package/api/theme/build/build.mjs +180 -24
  51. package/api/theme/themeBuild.doc.mjs +4 -4
  52. package/assets/codemods/transforms/next/__tests__/migrate-list-edge-compensation-to-edge.test.mjs +120 -0
  53. package/assets/codemods/transforms/next/index.mjs +11 -1
  54. package/assets/codemods/transforms/next/migrate-list-edge-compensation-to-edge.mjs +198 -0
  55. package/assets/docs/README.md +10 -0
  56. package/assets/docs/author-a-theme.doc.mjs +1 -1
  57. package/assets/docs/getting-started.doc.mjs +3 -3
  58. package/assets/docs/styling-overview.doc.mjs +1 -1
  59. package/assets/docs/theme.doc.dense.mjs +1 -1
  60. package/assets/docs/theme.doc.mjs +1 -1
  61. package/assets/docs/theme.doc.zh.mjs +1 -1
  62. package/assets/docs/tree/add-a-component.doc.mjs +11 -1
  63. package/assets/docs/tree/add-a-theme.doc.mjs +1 -1
  64. package/assets/docs/tree/add-a-topic.doc.mjs +10 -7
  65. package/assets/docs/tree/agent-guidance.doc.mjs +2 -2
  66. package/assets/docs/tree/block-template.doc.mjs +8 -20
  67. package/assets/docs/tree/build-the-template.doc.mjs +1 -1
  68. package/assets/docs/tree/check-the-props.doc.mjs +196 -0
  69. package/assets/docs/tree/check-your-docs.doc.mjs +51 -5
  70. package/assets/docs/tree/checks.doc.mjs +24 -16
  71. package/assets/docs/tree/codemods.doc.mjs +4 -4
  72. package/assets/docs/tree/component-family.doc.mjs +8 -6
  73. package/assets/docs/tree/component-imports.doc.mjs +10 -2
  74. package/assets/docs/tree/components.doc.mjs +1 -1
  75. package/assets/docs/tree/debug-and-gap-reports.doc.mjs +7 -18
  76. package/assets/docs/tree/define-the-theme.doc.mjs +6 -5
  77. package/assets/docs/tree/describe-the-component.doc.mjs +2 -2
  78. package/assets/docs/tree/docs.doc.mjs +3 -3
  79. package/assets/docs/tree/document-the-template.doc.mjs +1 -1
  80. package/assets/docs/tree/document-the-theme.doc.mjs +1 -1
  81. package/assets/docs/tree/export-template-assets.doc.mjs +1 -1
  82. package/assets/docs/tree/extend-or-replace.doc.mjs +6 -6
  83. package/assets/docs/tree/fonts-and-assets.doc.mjs +2 -2
  84. package/assets/docs/tree/generate-a-palette.doc.mjs +5 -3
  85. package/assets/docs/tree/grade-template-with-agent.doc.mjs +1 -1
  86. package/assets/docs/tree/integrations.doc.mjs +8 -0
  87. package/assets/docs/tree/links.doc.mjs +6 -5
  88. package/assets/docs/tree/migration-setup.doc.mjs +1 -0
  89. package/assets/docs/tree/migration-verification.doc.mjs +0 -1
  90. package/assets/docs/tree/migration.doc.mjs +0 -1
  91. package/assets/docs/tree/package-and-test.doc.mjs +1 -1
  92. package/assets/docs/tree/page-template.doc.mjs +4 -8
  93. package/assets/docs/tree/publishing.doc.mjs +1 -1
  94. package/assets/docs/tree/quick-start.doc.mjs +37 -21
  95. package/assets/docs/tree/replace-a-core-component.doc.mjs +4 -6
  96. package/assets/docs/tree/replace-a-core-template.doc.mjs +6 -8
  97. package/assets/docs/tree/sections-and-placement.doc.mjs +4 -6
  98. package/assets/docs/tree/see-it-in-an-app.doc.mjs +4 -2
  99. package/assets/docs/tree/short-and-findable.doc.mjs +6 -5
  100. package/assets/docs/tree/single-component.doc.mjs +15 -6
  101. package/assets/docs/tree/start-a-template.doc.mjs +3 -3
  102. package/assets/docs/tree/subcomponent.doc.mjs +6 -6
  103. package/assets/docs/tree/template-assets.doc.mjs +2 -2
  104. package/assets/docs/tree/template-doc-overview.doc.mjs +4 -15
  105. package/assets/docs/tree/template-fonts.doc.mjs +1 -1
  106. package/assets/docs/tree/template-icons.doc.mjs +1 -1
  107. package/assets/docs/tree/template-images-media.doc.mjs +1 -1
  108. package/assets/docs/tree/template-styles.doc.mjs +2 -2
  109. package/assets/docs/tree/templates.doc.mjs +1 -1
  110. package/assets/docs/tree/test-in-an-app.doc.mjs +3 -3
  111. package/assets/docs/tree/test-template-in-app.doc.mjs +1 -1
  112. package/assets/docs/tree/tokens-and-setup.doc.mjs +19 -15
  113. package/assets/docs/tree/troubleshooting.doc.mjs +13 -5
  114. package/assets/docs/tree/type-scale.doc.mjs +3 -3
  115. package/assets/docs/tree/upgrading.doc.mjs +3 -3
  116. package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +3 -1
  117. package/assets/docs/tree/verify-packed-template.doc.mjs +1 -1
  118. package/assets/docs/tree/versioning.doc.mjs +7 -6
  119. package/assets/docs/tree/write-the-template-file.doc.mjs +4 -3
  120. package/assets/docs/use-a-theme.doc.mjs +2 -2
  121. package/assets/docs/working-with-ai.doc.mjs +43 -4
  122. package/assets/theme.template.ts +1 -1
  123. package/authoring/codemod/codemod.doc.mjs +7 -3
  124. package/authoring/config/config.doc.mjs +9 -6
  125. package/authoring/config/type.ts +4 -2
  126. package/authoring/discover/discover.doc.mjs +1 -1
  127. package/authoring/doctypes/base/graph-fields.doc.mjs +6 -4
  128. package/authoring/doctypes/command/command.doc.mjs +14 -12
  129. package/authoring/doctypes/component/component.doc.mjs +29 -26
  130. package/authoring/doctypes/component/type.ts +13 -8
  131. package/authoring/doctypes/enum/enum.doc.mjs +7 -4
  132. package/authoring/doctypes/function/function.doc.mjs +9 -8
  133. package/authoring/doctypes/hook/hook.doc.mjs +5 -4
  134. package/authoring/doctypes/namespace/namespace.doc.mjs +7 -6
  135. package/authoring/doctypes/reference/reference.doc.mjs +11 -10
  136. package/authoring/doctypes/schema/schema.doc.mjs +9 -9
  137. package/authoring/doctypes/template/integration-template.doc.d.mts +11 -0
  138. package/authoring/doctypes/template/integration-template.doc.mjs +49 -0
  139. package/authoring/doctypes/template/parse.d.mts +6 -2
  140. package/authoring/doctypes/template/parse.mjs +3 -1
  141. package/authoring/doctypes/template/template.doc.mjs +3 -3
  142. package/authoring/doctypes/template/type.ts +22 -0
  143. package/authoring/doctypes/theme/theme.doc.mjs +2 -2
  144. package/authoring/gap-report/gap-report.doc.mjs +2 -2
  145. package/authoring/identity/identity.doc.mjs +3 -3
  146. package/authoring/index.d.ts +1 -0
  147. package/authoring/integration/integration.doc.mjs +12 -4
  148. package/authoring/integration/parse.test.mjs +10 -0
  149. package/authoring/integration/schema.d.mts +2 -0
  150. package/authoring/integration/schema.mjs +1 -0
  151. package/authoring/integration/type.ts +4 -0
  152. package/clients/cli/commands/build-theme.batch-report.test.mjs +3 -1
  153. package/clients/cli/commands/build-theme.mjs +1 -1
  154. package/clients/cli/commands/build-theme.path-safety.test.mjs +224 -6
  155. package/clients/cli/commands/build-theme.registry.test.mjs +5 -2
  156. package/clients/cli/commands/cli-postinstall.test.mjs +49 -1
  157. package/clients/cli/commands/doctor-integration-components.doc.mjs +17 -4
  158. package/clients/cli/commands/doctor-integration-validate.doc.mjs +13 -2
  159. package/clients/cli/commands/doctor-integration.test.mjs +837 -0
  160. package/clients/cli/commands/doctor.mjs +190 -0
  161. package/clients/cli/commands/integration-check.doc.mjs +85 -0
  162. package/clients/cli/commands/integration-check.test.mjs +267 -0
  163. package/clients/cli/commands/integration.doc.mjs +6 -2
  164. package/clients/cli/commands/integration.mjs +107 -1
  165. package/clients/cli/commands/setup-nudge.test.mjs +79 -1
  166. package/clients/cli/commands/swizzle.mjs +5 -3
  167. package/clients/cli/commands/swizzle.routing.test.mjs +18 -1
  168. package/clients/cli/commands/text-json-parity.test.mjs +5 -0
  169. package/clients/cli/commands/theme-build.doc.mjs +2 -2
  170. package/clients/cli/index.mjs +30 -5
  171. package/foundation/agent-docs/agent-doc-state.d.mts +14 -0
  172. package/foundation/agent-docs/agent-doc-state.mjs +38 -0
  173. package/foundation/agent-docs/agent-docs.d.mts +2 -1
  174. package/foundation/agent-docs/agent-docs.mjs +8 -6
  175. package/foundation/config/project.mjs +82 -0
  176. package/foundation/discovery/authoring-self-docs.mjs +2 -1
  177. package/foundation/discovery/component-discovery.d.mts +190 -10
  178. package/foundation/discovery/component-discovery.export-names.test.mjs +760 -0
  179. package/foundation/discovery/component-discovery.mjs +864 -56
  180. package/foundation/discovery/component-discovery.test.mjs +96 -0
  181. package/foundation/discovery/component-replacement.mjs +18 -0
  182. package/foundation/discovery/component-replacement.test.mjs +40 -0
  183. package/foundation/discovery/doc-types.d.mts +157 -0
  184. package/foundation/discovery/doc-types.mjs +1224 -0
  185. package/foundation/discovery/doc-types.test.mjs +700 -0
  186. package/foundation/discovery/docs-discovery.d.mts +81 -0
  187. package/foundation/discovery/docs-discovery.mjs +57 -0
  188. package/foundation/discovery/integration-doc-files.d.mts +22 -0
  189. package/foundation/discovery/integration-doc-files.mjs +231 -0
  190. package/foundation/discovery/integration-doc-files.test.mjs +246 -0
  191. package/foundation/discovery/module-exports.d.mts +27 -0
  192. package/foundation/discovery/module-exports.mjs +356 -31
  193. package/foundation/discovery/module-exports.test.mjs +172 -0
  194. package/foundation/discovery/package-tsconfig.d.mts +14 -0
  195. package/foundation/discovery/package-tsconfig.mjs +228 -0
  196. package/foundation/discovery/package-tsconfig.test.mjs +210 -0
  197. package/foundation/discovery/project-typescript.d.mts +32 -0
  198. package/foundation/discovery/project-typescript.mjs +89 -0
  199. package/foundation/discovery/prop-drift.d.mts +278 -0
  200. package/foundation/discovery/prop-drift.mjs +2263 -0
  201. package/foundation/discovery/prop-drift.test.mjs +2272 -0
  202. package/foundation/discovery/type-match.d.mts +53 -0
  203. package/foundation/discovery/type-match.mjs +660 -0
  204. package/foundation/discovery/type-match.test.mjs +169 -0
  205. package/foundation/discovery/type-text.d.mts +68 -0
  206. package/foundation/discovery/type-text.mjs +658 -0
  207. package/foundation/discovery/type-text.test.mjs +196 -0
  208. package/foundation/discovery/unresolved-references.d.mts +62 -0
  209. package/foundation/discovery/unresolved-references.mjs +790 -0
  210. package/foundation/discovery/unresolved-references.test.mjs +567 -0
  211. package/foundation/doc-compiler/doc-loads.test.mjs +14 -0
  212. package/foundation/doc-compiler/inputs.mjs +23 -0
  213. package/foundation/doc-compiler/inputs.test.mjs +0 -1
  214. package/foundation/doc-compiler/links.d.mts +9 -5
  215. package/foundation/doc-compiler/links.mjs +4 -2
  216. package/foundation/integrations/contribution-inventory.mjs +6 -1
  217. package/foundation/response/response-types.doc.mjs +7 -2
  218. package/foundation/xle/registry.mjs +4 -0
  219. package/foundation/xle/registry.test.mjs +28 -0
  220. package/package.json +9 -9
  221. package/scripts/postinstall.mjs +3 -3
@@ -754,6 +754,285 @@ describe('integrationPackCheck', () => {
754
754
  );
755
755
  });
756
756
 
757
+ it('checks the export a component doc names when its file is named otherwise', async () => {
758
+ fs.writeFileSync(
759
+ path.join(tmpDir, 'package.json'),
760
+ `${JSON.stringify({
761
+ name: '@acme/widgets',
762
+ version: '1.0.0',
763
+ files: ['astryx.integration.mjs', 'components'],
764
+ exports: {'./components/Slides': './components/Slides.tsx'},
765
+ })}\n`,
766
+ );
767
+ fs.writeFileSync(
768
+ path.join(tmpDir, 'astryx.integration.mjs'),
769
+ "export default {components: './components'};\n",
770
+ );
771
+ fs.mkdirSync(path.join(tmpDir, 'components'));
772
+ // Slides.tsx exports AcmeCarousel and nothing named Slides.
773
+ fs.writeFileSync(
774
+ path.join(tmpDir, 'components', 'Slides.tsx'),
775
+ 'export function AcmeCarousel() { return null; }\n',
776
+ );
777
+ fs.writeFileSync(
778
+ path.join(tmpDir, 'components', 'Slides.doc.mjs'),
779
+ "export default {type: 'component', name: 'AcmeCarousel', import: '@acme/widgets/components/Slides', description: 'x', props: []};\n",
780
+ );
781
+
782
+ const result = await integrationPackCheck({cwd: tmpDir});
783
+ expect(result.data.packable).toBe(true);
784
+ expect(result.data.contributions.packed.components).toEqual([
785
+ 'AcmeCarousel',
786
+ ]);
787
+ });
788
+
789
+ it('checks each family member against the module its family doc imports', async () => {
790
+ fs.writeFileSync(
791
+ path.join(tmpDir, 'package.json'),
792
+ `${JSON.stringify({
793
+ name: '@acme/widgets',
794
+ version: '1.0.0',
795
+ files: ['astryx.integration.mjs', 'components'],
796
+ exports: {'./tabs': './components/tabs/index.ts'},
797
+ })}\n`,
798
+ );
799
+ fs.writeFileSync(
800
+ path.join(tmpDir, 'astryx.integration.mjs'),
801
+ "export default {components: './components'};\n",
802
+ );
803
+ const dir = path.join(tmpDir, 'components', 'tabs');
804
+ fs.mkdirSync(dir, {recursive: true});
805
+ fs.writeFileSync(
806
+ path.join(dir, 'AcmeTabs.tsx'),
807
+ 'export function AcmeTabs() { return null; }\nexport function AcmeTabsLegacy() { return null; }\n',
808
+ );
809
+ fs.writeFileSync(
810
+ path.join(dir, 'tab.tsx'),
811
+ 'export function AcmeTab() { return null; }\n',
812
+ );
813
+ // The module apps import exports AcmeTabs and AcmeTab, not AcmeTabsLegacy.
814
+ fs.writeFileSync(
815
+ path.join(dir, 'index.ts'),
816
+ "export {AcmeTabs} from './AcmeTabs';\nexport {AcmeTab} from './tab';\n",
817
+ );
818
+ fs.writeFileSync(
819
+ path.join(dir, 'AcmeTabs.doc.mjs'),
820
+ "export default {type: 'component', name: 'AcmeTabs', import: '@acme/widgets/tabs', description: 'x', components: [{name: 'AcmeTabs', description: 'x', props: []}, {name: 'AcmeTab', description: 'x', props: []}, {name: 'AcmeTabsLegacy', description: 'x', props: []}]};\n",
821
+ );
822
+
823
+ const result = await integrationPackCheck({cwd: tmpDir});
824
+
825
+ expect(result.data.issues).toEqual([]);
826
+ expect(result.data.packable).toBe(true);
827
+ expect(result.data.contributions.packed.components).toEqual([
828
+ 'AcmeTab',
829
+ 'AcmeTabs',
830
+ ]);
831
+ });
832
+
833
+ it.each([
834
+ 'a clean checkout',
835
+ 'a stale build',
836
+ 'a stale build that exports more',
837
+ ])(
838
+ 'reads members a family imports from output its pack lifecycle builds, from %s',
839
+ async start => {
840
+ fs.writeFileSync(
841
+ path.join(tmpDir, 'package.json'),
842
+ `${JSON.stringify({
843
+ name: '@acme/widgets',
844
+ version: '1.0.0',
845
+ files: ['astryx.integration.mjs', 'components', 'dist'],
846
+ exports: {'./tabs': './dist/tabs/index.mjs'},
847
+ scripts: {prepack: 'node build.mjs'},
848
+ })}\n`,
849
+ );
850
+ fs.writeFileSync(
851
+ path.join(tmpDir, 'astryx.integration.mjs'),
852
+ "export default {components: './components'};\n",
853
+ );
854
+ // The build writes the module the family imports; a clean checkout has none.
855
+ fs.writeFileSync(
856
+ path.join(tmpDir, 'build.mjs'),
857
+ "import fs from 'node:fs';\nfs.mkdirSync('dist/tabs', {recursive: true});\nfs.writeFileSync('dist/tabs/index.mjs', \"export {AcmeTabs, AcmeTab} from '../../components/tabs/AcmeTabs.tsx';\\n\");\n",
858
+ );
859
+ const dir = path.join(tmpDir, 'components', 'tabs');
860
+ fs.mkdirSync(dir, {recursive: true});
861
+ fs.writeFileSync(
862
+ path.join(dir, 'AcmeTabs.tsx'),
863
+ 'export function AcmeTabs() { return null; }\nexport function AcmeTab() { return null; }\nexport function AcmeTabLegacy() { return null; }\n',
864
+ );
865
+ fs.writeFileSync(
866
+ path.join(dir, 'AcmeTabs.doc.mjs'),
867
+ "export default {type: 'component', name: 'AcmeTabs', import: '@acme/widgets/tabs', description: 'x', components: [{name: 'AcmeTabs', description: 'x', props: []}, {name: 'AcmeTab', description: 'x', props: []}, {name: 'AcmeTabLegacy', description: 'x', props: []}]};\n",
868
+ );
869
+
870
+ if (start !== 'a clean checkout') {
871
+ // An earlier build: before AcmeTab was exported, or while AcmeTabLegacy
872
+ // still was.
873
+ const names =
874
+ start === 'a stale build'
875
+ ? 'AcmeTabs'
876
+ : 'AcmeTabs, AcmeTab, AcmeTabLegacy';
877
+ fs.mkdirSync(path.join(tmpDir, 'dist', 'tabs'), {recursive: true});
878
+ fs.writeFileSync(
879
+ path.join(tmpDir, 'dist', 'tabs', 'index.mjs'),
880
+ `export {${names}} from '../../components/tabs/AcmeTabs.tsx';\n`,
881
+ );
882
+ }
883
+
884
+ const result = await integrationPackCheck({cwd: tmpDir});
885
+
886
+ expect(result.data.issues).toEqual([]);
887
+ expect(result.data.packable).toBe(true);
888
+ expect(result.data.contributions.local.components).toEqual(
889
+ result.data.contributions.packed.components,
890
+ );
891
+ },
892
+ );
893
+
894
+ it.each(['a clean checkout', 'an incremental build'])(
895
+ 'reads members built output re-exports from compiled files, from %s',
896
+ async start => {
897
+ fs.writeFileSync(
898
+ path.join(tmpDir, 'package.json'),
899
+ `${JSON.stringify({
900
+ name: '@acme/widgets',
901
+ version: '1.0.0',
902
+ files: ['astryx.integration.mjs', 'components', 'dist'],
903
+ exports: {'./tabs': './dist/tabs/index.mjs'},
904
+ scripts: {prepack: 'node build.mjs'},
905
+ })}\n`,
906
+ );
907
+ fs.writeFileSync(
908
+ path.join(tmpDir, 'astryx.integration.mjs'),
909
+ "export default {components: './components'};\n",
910
+ );
911
+ // The build compiles each source to its own file and, like an
912
+ // incremental build, rewrites only the files whose output changed.
913
+ const output = {
914
+ 'index.mjs':
915
+ "export {AcmeTabs} from './tabs.mjs';\nexport * from './tab.mjs';\n",
916
+ 'tabs.mjs': 'export function AcmeTabs() { return null; }\n',
917
+ 'tab.mjs': 'export function AcmeTab() { return null; }\n',
918
+ };
919
+ fs.writeFileSync(
920
+ path.join(tmpDir, 'build.mjs'),
921
+ `import fs from 'node:fs';\nconst output = ${JSON.stringify(output)};\nfs.mkdirSync('dist/tabs', {recursive: true});\nfor (const [file, text] of Object.entries(output)) {\n const target = 'dist/tabs/' + file;\n if (!fs.existsSync(target) || fs.readFileSync(target, 'utf-8') !== text) fs.writeFileSync(target, text);\n}\n`,
922
+ );
923
+ const dir = path.join(tmpDir, 'components', 'tabs');
924
+ fs.mkdirSync(dir, {recursive: true});
925
+ fs.writeFileSync(
926
+ path.join(dir, 'AcmeTabs.tsx'),
927
+ 'export function AcmeTabs() { return null; }\n',
928
+ );
929
+ fs.writeFileSync(
930
+ path.join(dir, 'AcmeTab.tsx'),
931
+ 'export function AcmeTab() { return null; }\n',
932
+ );
933
+ fs.writeFileSync(
934
+ path.join(dir, 'AcmeTabs.doc.mjs'),
935
+ "export default {type: 'component', name: 'AcmeTabs', import: '@acme/widgets/tabs', description: 'x', components: [{name: 'AcmeTabs', description: 'x', props: []}, {name: 'AcmeTab', description: 'x', props: []}]};\n",
936
+ );
937
+ if (start === 'an incremental build') {
938
+ // An earlier build from before AcmeTab existed: the file the import
939
+ // names is already current, so the build leaves it untouched.
940
+ fs.mkdirSync(path.join(tmpDir, 'dist', 'tabs'), {recursive: true});
941
+ for (const [file, text] of Object.entries({...output, 'tab.mjs': ''})) {
942
+ fs.writeFileSync(path.join(tmpDir, 'dist', 'tabs', file), text);
943
+ }
944
+ }
945
+
946
+ const result = await integrationPackCheck({cwd: tmpDir});
947
+
948
+ expect(result.data.issues).toEqual([]);
949
+ expect(result.data.packable).toBe(true);
950
+ expect(result.data.contributions.packed.components).toEqual([
951
+ 'AcmeTab',
952
+ 'AcmeTabs',
953
+ ]);
954
+ expect(result.data.contributions.local.components).toEqual(
955
+ result.data.contributions.packed.components,
956
+ );
957
+ },
958
+ );
959
+
960
+ it('passes a member another folder declares, which has no source', async () => {
961
+ fs.writeFileSync(
962
+ path.join(tmpDir, 'package.json'),
963
+ `${JSON.stringify({
964
+ name: '@acme/widgets',
965
+ version: '1.0.0',
966
+ files: ['astryx.integration.mjs', 'components'],
967
+ exports: {'./menu': './components/menu/index.ts'},
968
+ })}\n`,
969
+ );
970
+ fs.writeFileSync(
971
+ path.join(tmpDir, 'astryx.integration.mjs'),
972
+ "export default {components: './components'};\n",
973
+ );
974
+ const menu = path.join(tmpDir, 'components', 'menu');
975
+ const shared = path.join(tmpDir, 'components', 'shared');
976
+ fs.mkdirSync(menu, {recursive: true});
977
+ fs.mkdirSync(shared, {recursive: true});
978
+ fs.writeFileSync(
979
+ path.join(menu, 'AcmeMenu.doc.mjs'),
980
+ "export default {type: 'component', name: 'AcmeMenu', import: '@acme/widgets/menu', description: 'x', components: [{name: 'AcmeMenuItem', description: 'x', props: []}]};\n",
981
+ );
982
+ fs.writeFileSync(
983
+ path.join(menu, 'AcmeMenu.tsx'),
984
+ 'export function AcmeMenu() { return null; }\n',
985
+ );
986
+ fs.writeFileSync(
987
+ path.join(menu, 'index.ts'),
988
+ "export {AcmeMenu} from './AcmeMenu';\nexport {AcmeMenuItem} from '../shared/AcmeMenuItem';\n",
989
+ );
990
+ fs.writeFileSync(
991
+ path.join(shared, 'AcmeMenuItem.tsx'),
992
+ 'export function AcmeMenuItem() { return null; }\n',
993
+ );
994
+
995
+ const result = await integrationPackCheck({cwd: tmpDir});
996
+
997
+ expect(result.data.issues).toEqual([]);
998
+ expect(result.data.packable).toBe(true);
999
+ expect(result.data.contributions.packed.components).toEqual([
1000
+ 'AcmeMenu',
1001
+ 'AcmeMenuItem',
1002
+ ]);
1003
+ });
1004
+
1005
+ it('reports a family doc the loader rejects once, not once per member', async () => {
1006
+ fs.writeFileSync(
1007
+ path.join(tmpDir, 'package.json'),
1008
+ `${JSON.stringify({
1009
+ name: '@acme/widgets',
1010
+ version: '1.0.0',
1011
+ files: ['astryx.integration.mjs', 'components'],
1012
+ })}\n`,
1013
+ );
1014
+ fs.writeFileSync(
1015
+ path.join(tmpDir, 'astryx.integration.mjs'),
1016
+ "export default {components: './components'};\n",
1017
+ );
1018
+ fs.mkdirSync(path.join(tmpDir, 'components'));
1019
+ fs.writeFileSync(
1020
+ path.join(tmpDir, 'components', 'AcmeTabs.tsx'),
1021
+ 'export function AcmeTabs() { return null; }\nexport function AcmeTab() { return null; }\nexport function AcmeTabPanel() { return null; }\n',
1022
+ );
1023
+ // A displayName that is not a string makes the loader reject the doc.
1024
+ fs.writeFileSync(
1025
+ path.join(tmpDir, 'components', 'AcmeTabs.doc.mjs'),
1026
+ "export default {type: 'component', name: 'AcmeTabs', displayName: 1, description: 'x', components: [{name: 'AcmeTab', description: 'x', props: []}, {name: 'AcmeTabPanel', description: 'x', props: []}]};\n",
1027
+ );
1028
+
1029
+ const result = await integrationPackCheck({cwd: tmpDir});
1030
+ const codes = result.data.issues.map(issue => issue.code);
1031
+
1032
+ expect(codes).toContain('component_doc_unloadable');
1033
+ expect(codes.length).toBe(new Set(codes).size);
1034
+ });
1035
+
757
1036
  it('passes a generated component through its exact packed export', async () => {
758
1037
  fs.writeFileSync(
759
1038
  path.join(tmpDir, 'package.json'),
@@ -811,6 +1090,86 @@ describe('integrationPackCheck', () => {
811
1090
  );
812
1091
  });
813
1092
 
1093
+ it('does not count a type-only re-export as the component', async () => {
1094
+ fs.writeFileSync(
1095
+ path.join(tmpDir, 'package.json'),
1096
+ `${JSON.stringify({
1097
+ name: '@acme/widgets',
1098
+ version: '1.0.0',
1099
+ files: ['astryx.integration.mjs', 'src'],
1100
+ exports: {'.': './src/index.ts'},
1101
+ })}\n`,
1102
+ );
1103
+ fs.writeFileSync(
1104
+ path.join(tmpDir, 'astryx.integration.mjs'),
1105
+ "export default {components: './src'};\n",
1106
+ );
1107
+ fs.mkdirSync(path.join(tmpDir, 'src'));
1108
+ fs.writeFileSync(
1109
+ path.join(tmpDir, 'src', 'index.ts'),
1110
+ "export type {AcmeWidget} from './AcmeWidget';\n",
1111
+ );
1112
+ fs.writeFileSync(
1113
+ path.join(tmpDir, 'src', 'AcmeWidget.tsx'),
1114
+ 'export function AcmeWidget() { return null; }\n',
1115
+ );
1116
+ fs.writeFileSync(
1117
+ path.join(tmpDir, 'src', 'AcmeWidget.doc.mjs'),
1118
+ "export default {type: 'component', name: 'AcmeWidget', import: '@acme/widgets', description: 'Widget.', props: []};\n",
1119
+ );
1120
+
1121
+ const result = await integrationPackCheck({cwd: tmpDir});
1122
+
1123
+ expect(result.data.issues).toContainEqual(
1124
+ expect.objectContaining({code: 'component_export_missing'}),
1125
+ );
1126
+ });
1127
+
1128
+ it('follows a re-export of a folder to its index file', async () => {
1129
+ // `./primitives` names a folder: TypeScript and bundlers load its index file.
1130
+ fs.writeFileSync(
1131
+ path.join(tmpDir, 'package.json'),
1132
+ `${JSON.stringify({
1133
+ name: '@acme/widgets',
1134
+ version: '1.0.0',
1135
+ files: ['astryx.integration.mjs', 'src'],
1136
+ exports: {'.': './src/index.ts'},
1137
+ })}\n`,
1138
+ );
1139
+ fs.writeFileSync(
1140
+ path.join(tmpDir, 'astryx.integration.mjs'),
1141
+ "export default {components: './src'};\n",
1142
+ );
1143
+ const primitives = path.join(tmpDir, 'src', 'primitives');
1144
+ fs.mkdirSync(primitives, {recursive: true});
1145
+ fs.writeFileSync(
1146
+ path.join(tmpDir, 'src', 'index.ts'),
1147
+ "export * from './primitives';\n",
1148
+ );
1149
+ fs.writeFileSync(
1150
+ path.join(primitives, 'index.ts'),
1151
+ "export * from './AcmeWidget';\n",
1152
+ );
1153
+ fs.writeFileSync(
1154
+ path.join(primitives, 'AcmeWidget.tsx'),
1155
+ 'export function AcmeWidget() { return null; }\n',
1156
+ );
1157
+ fs.writeFileSync(
1158
+ path.join(primitives, 'AcmeWidget.doc.mjs'),
1159
+ "export default {type: 'component', name: 'AcmeWidget', import: '@acme/widgets', description: 'Widget.', props: []};\n",
1160
+ );
1161
+
1162
+ const result = await integrationPackCheck({cwd: tmpDir});
1163
+
1164
+ expect(result.data.issues).not.toContainEqual(
1165
+ expect.objectContaining({code: 'component_export_missing'}),
1166
+ );
1167
+ expect(
1168
+ result.data.packable,
1169
+ JSON.stringify(result.data.issues, null, 2),
1170
+ ).toBe(true);
1171
+ });
1172
+
814
1173
  it('fails when packed template source is hidden by package exports', async () => {
815
1174
  writePackage({
816
1175
  manifest: "export default {templates: './templates'};\n",
@@ -34,6 +34,7 @@
34
34
  import * as fs from 'node:fs';
35
35
  import * as path from 'node:path';
36
36
  import {assertWithin} from '../../foundation/fs/path-safety.mjs';
37
+ import {integrationDocTypes} from '../docs/_adapter.mjs';
37
38
  import {
38
39
  THEME_SLUG_RE,
39
40
  unreadThemeFolders,
@@ -461,6 +462,12 @@ export async function validateIntegration(pkg, options = {}) {
461
462
  const result = pkg
462
463
  ? await validateInstalledIntegration(pkg, cwd)
463
464
  : await validateLocalIntegration(cwd);
465
+ // Each doc the package ships, against the published authoring type for its
466
+ // kind, read with the project's own compiler. Findings are warnings only.
467
+ const integration = result.found ? result.integration : undefined;
468
+ const docTypes = integration
469
+ ? await integrationDocTypes(integration, cwd)
470
+ : undefined;
464
471
  return {
465
472
  type: 'integration.validate',
466
473
  data: {
@@ -472,6 +479,7 @@ export async function validateIntegration(pkg, options = {}) {
472
479
  issues: result.reason === 'no_package_json'
473
480
  ? [{code: 'no_package', severity: 'error', message: 'No package.json found. Run this command inside a package directory.'}]
474
481
  : result.issues,
482
+ ...(docTypes ? {docTypes} : {}),
475
483
  },
476
484
  };
477
485
  }
@@ -176,6 +176,17 @@ describe('validate-integration API', () => {
176
176
  expect(agentDocsIssues[0].severity).toBe('error');
177
177
  });
178
178
 
179
+ it('reports nothing for a docsiteUrl', async () => {
180
+ const pkgDir = path.join(tmpDir, 'pkg');
181
+ writePackage(pkgDir, {
182
+ manifest: `export default {docsiteUrl: 'https://widgets.example.com'};\n`,
183
+ });
184
+
185
+ const result = await validateLocalIntegration(pkgDir);
186
+
187
+ expect(result.issues).toEqual([]);
188
+ });
189
+
179
190
  it('flags a broken codemod as invalid_codemod error', async () => {
180
191
  const pkgDir = path.join(tmpDir, 'pkg');
181
192
  writePackage(pkgDir, {
@@ -39,5 +39,13 @@ export type ValidateIntegrationResponse = {
39
39
  name: string | null;
40
40
  version: string | null;
41
41
  issues: import("../../foundation/integrations/issue").AstryxIntegrationIssue[];
42
+ docTypes?: DocTypesReport;
42
43
  };
43
44
  };
45
+ /**
46
+ * `fixtures`: how many test fixture docs (under `__tests__` in the
47
+ * components or docs root, which those readers skip) were not checked
48
+ */
49
+ export type DocTypesReport = import("../../foundation/discovery/doc-types.mjs").DocTypesReport & {
50
+ fixtures?: number;
51
+ };
@@ -38,7 +38,18 @@
38
38
  * "healthy" — the two are otherwise byte-identical.
39
39
  * @typedef {object} ValidateIntegrationResponse
40
40
  * @property {'integration.validate'} type
41
- * @property {{validated: boolean, name: string | null, version: string | null, issues: import('../../foundation/integrations/issue').AstryxIntegrationIssue[]}} data
41
+ * @property {{validated: boolean, name: string | null, version: string | null, issues: import('../../foundation/integrations/issue').AstryxIntegrationIssue[], docTypes?: DocTypesReport}} data
42
+ */
43
+
44
+ /**
45
+ * Each doc the package ships, checked against the published authoring type
46
+ * for its kind. Present when the manifest loads. `status: 'skipped'`
47
+ * means nothing was checked (no TypeScript in the project) and `reason` says
48
+ * why. Every finding is a warning.
49
+ *
50
+ * @typedef {import('../../foundation/discovery/doc-types.mjs').DocTypesReport & {fixtures?: number}} DocTypesReport
51
+ * `fixtures`: how many test fixture docs (under `__tests__` in the
52
+ * components or docs root, which those readers skip) were not checked
42
53
  */
43
54
 
44
55
  export {};
@@ -23,11 +23,27 @@ export const doc = {
23
23
  'declared contribution root exists, and runs the codemod/template/component/' +
24
24
  'docs/theme validators, returning every finding as an AstryxIntegrationIssue. A missing ' +
25
25
  'local manifest is guidance (name null, no issues), not an error, so callers ' +
26
- 'can stay exit-0 in a non-integration directory.',
26
+ 'can stay exit-0 in a non-integration directory. It also checks every doc the ' +
27
+ 'package ships under its roots (component, template, docs topic, theme, and translation, served or not) against ' +
28
+ "the published authoring type for its kind, as `tsc` would, with the project's " +
29
+ 'own `typescript` (or, where `typescript` is missing or has no JavaScript compiler ' +
30
+ 'API, as TypeScript 7 does, the `@typescript/typescript6` it installs): an unknown ' +
31
+ 'field, a missing required field, or a value outside ' +
32
+ "a field's type is a warning in `docTypes` naming the field and a fix. A test " +
33
+ 'fixture under `__tests__` in the components or docs root is counted, not checked. ' +
34
+ 'Without TypeScript, `docTypes` is `skipped` with a reason.',
27
35
  importPath: '@astryxdesign/cli/api',
28
36
  signature:
29
37
  'validateIntegration(pkg?: string, options?: ValidateIntegrationOptions): Promise<ValidateIntegrationResponse>',
30
- keywords: ['integration', 'validate', 'manifest', 'lint', 'check'],
38
+ keywords: [
39
+ 'integration',
40
+ 'validate',
41
+ 'manifest',
42
+ 'lint',
43
+ 'check',
44
+ 'types',
45
+ 'typecheck',
46
+ ],
31
47
  params: [
32
48
  {
33
49
  name: 'pkg',
@@ -46,7 +62,7 @@ export const doc = {
46
62
  {
47
63
  type: 'integration.validate',
48
64
  description:
49
- 'The result envelope: `data.validated`, false when nothing was validated — with no `package.json` at all, `data.issues` carries one `no_package` error-severity issue so the command exits 1; with a `package.json` but no integration manifest, `data.issues` is empty, which means "not looked at", not "healthy"; `data.name` and `data.version` of the validated package (both null when `data.validated` is false; `data.name` is `(local package)` when the local package.json has no readable name), plus `data.issues`, an AstryxIntegrationIssue[] of {code, severity: `warning` | `error`, message}.',
65
+ 'The result envelope: `data.validated`, false when nothing was validated — with no `package.json` at all, `data.issues` carries one `no_package` error-severity issue so the command exits 1; with a `package.json` but no integration manifest, `data.issues` is empty, which means "not looked at", not "healthy"; `data.name` and `data.version` of the validated package (both null when `data.validated` is false; `data.name` is `(local package)` when the local package.json has no readable name), plus `data.issues`, an AstryxIntegrationIssue[] of {code, severity: `warning` | `error`, message}. When the manifest loads, `data.docTypes` holds the doc type check: `status` (`checked` | `skipped`), `reason` when skipped, `typescript`, `docs` (doc values checked), `findings` ({doc, line, export, type, field, problem: `unknown_field` | `missing_field` | `wrong_type`, severity: `warning`, message, fix}), `unchecked` ({doc, reason}: a doc that could not be fully checked, or a root its reader cannot read), and `fixtures` when the components or docs root holds test fixture docs under `__tests__`: how many, none of them checked.',
50
66
  },
51
67
  ],
52
68
  examples: [
@@ -16,6 +16,48 @@ export function stem(w: string): string;
16
16
  * @returns {boolean}
17
17
  */
18
18
  export function sameWord(a: string, b: string): boolean;
19
+ /**
20
+ * A query with its camelCase and PascalCase identifiers spelled as words, so
21
+ * `SidebarToggle` reads "sidebar toggle" and `useTablePaginationState` "use
22
+ * table pagination state". Plain words, and identifiers joined with `_` or `-`, are
23
+ * unchanged: a joined token that matches nothing stays unmatched.
24
+ * @param {string} query - The query as typed, before lowercasing.
25
+ * @returns {string} Lowercased.
26
+ */
27
+ export function spellIdentifiers(query: string): string;
28
+ /**
29
+ * The strongest hit a run of two or more adjacent query words has when it is
30
+ * read as one joined word, and the query words it covers. Readers type a
31
+ * compound name as separate words (`table header cell` is TableHeaderCell,
32
+ * `number field` the keyword `numberfield`), and no single word of the run
33
+ * matches on its own.
34
+ *
35
+ * - 100: the run joins to a component, hook, or template name, or its plural
36
+ * (`table header cells` is TableHeaderCell), or a hook name without its
37
+ * `use` prefix (`table sticky columns` is useTableStickyColumns).
38
+ * - 90: the run joins to a one-word keyword (`number field` is
39
+ * `numberfield`).
40
+ * - 95: the run joins to most of a name, cut short (`dropdown menu radio` is
41
+ * DropdownMenuRadioItem); 92: it is a name's leading words, covering at least half
42
+ * of it (`top nav mega` is TopNavMegaMenu); 70: it is two or more adjacent
43
+ * words of a name that cover at least half of it (`header cell` in
44
+ * TableHeaderCell), the same coverage a word prefix needs. These three
45
+ * are for components and hooks only.
46
+ *
47
+ * A doc's name is a route or key, so docs are left out.
48
+ * @param {string[]} words - The query's words, stopwords included.
49
+ * @param {Candidate} candidate
50
+ * @returns {{score: number, reason: string, covered: Set<string>, alias?: 'partial' | 'keyword', aliasWords?: number} | null}
51
+ * `covered` holds the run's words and every stretch of them joined. `alias`
52
+ * marks a hit on part of a name or on a keyword, not on the whole name.
53
+ */
54
+ export function compoundHit(words: string[], candidate: Candidate): {
55
+ score: number;
56
+ reason: string;
57
+ covered: Set<string>;
58
+ alias?: "partial" | "keyword";
59
+ aliasWords?: number;
60
+ } | null;
19
61
  /**
20
62
  * Split a query into meaningful content tokens (lowercased, stopwords + very
21
63
  * short words removed). Empty for single-word queries (callers fall back to
@@ -40,19 +82,20 @@ export function tokenizeQuery(term: string): string[];
40
82
  * typography/font-setup, `side panel` is "Side panels");
41
83
  * - 1: a word of the query is the topic's name (`illustration` in a longer
42
84
  * question is the illustrations guide);
85
+ * - 1: a query of two or more words is one of the topic's own declared
86
+ * keywords, whatever the plural (`code review` on a guide that declares it);
43
87
  * - 1: the query holds one of the doc's titles whole (`resizable side panels`
44
88
  * names "Side panels"; `light dark mode button` names "Light/Dark Mode");
45
89
  * - 1: the doc matches every word of the query and one of them is a word of
46
90
  * its title (`switch to a dark theme` and "Use a theme").
47
91
  * A section answers to its topic's name.
48
92
  *
49
- * A doc that matched only by keyword, by a heading holding the query, or by
93
+ * A doc that matched only by a heading holding the query, by one word, or by
50
94
  * words spread through its text has no priority. Docs are split into many
51
- * small topics, and each declares its own keywords and headings, so a common
52
- * phrase such as `font size` hits a guide's keyword or heading exactly
53
- * (170-190) while the component the reader is after matches one word by
54
- * keyword (`Text`, 98). Ranked on text alone, every split adds another doc
55
- * above the component. Broad reference pages match every word of many queries
95
+ * small topics, each with its own headings, so a common phrase such as
96
+ * `font size` hits a heading exactly (170) while the component the reader is
97
+ * after matches one word by keyword (`Text`, 98). Ranked on text alone, every
98
+ * split adds another doc above the component. Broad reference pages match every word of many queries
56
99
  * in their text the same way. The score stays the text-match strength the
57
100
  * result reports, so callers that gate on it (`build`) see the same numbers.
58
101
  *
@@ -67,7 +110,7 @@ export function domainPriority(term: string, tokens: string[], candidate: Candid
67
110
  score: number;
68
111
  matched: number;
69
112
  total: number;
70
- }): 0 | 1 | 2;
113
+ }, queryWords?: string[]): 0 | 1 | 2;
71
114
  /**
72
115
  * The first title or heading that holds every word of the query, in order and
73
116
  * side by side, or null.
@@ -89,18 +132,26 @@ export function titleInQuery(term: string, titles: string[] | undefined): string
89
132
  * @param {string} term - Lowercased full query.
90
133
  * @param {string[]} tokens - Content tokens from tokenizeQuery(term).
91
134
  * @param {Candidate} candidate
92
- * @returns {{score: number, reason: string, matched: number, total: number} | null}
135
+ * @param {string[]} [words] - The query's words for compound names (see
136
+ * compoundHit); empty when the query names an entry outright.
137
+ * @param {{plain?: boolean}} [opts] - `plain`: score without matches on part
138
+ * of a name or a joined keyword.
139
+ * @returns {{score: number, reason: string, matched: number, total: number, alias?: 'partial' | 'keyword', aliasWords?: number} | null}
93
140
  * `matched`/`total` are the query concepts this candidate answered, out of
94
141
  * the concepts the query had. Callers that must distinguish "matched one word
95
142
  * of three" from "matched all three" — `build`, which gates its pages group
96
143
  * on coverage — cannot recover that from the score, because a single strong
97
144
  * hit and a broad weak one land on the same number.
98
145
  */
99
- export function scoreQuery(term: string, tokens: string[], candidate: Candidate): {
146
+ export function scoreQuery(term: string, tokens: string[], candidate: Candidate, words?: string[], { plain }?: {
147
+ plain?: boolean;
148
+ }): {
100
149
  score: number;
101
150
  reason: string;
102
151
  matched: number;
103
152
  total: number;
153
+ alias?: "partial" | "keyword";
154
+ aliasWords?: number;
104
155
  } | null;
105
156
  /**
106
157
  * Score a single candidate against the search term across name, keywords,
@@ -118,8 +169,9 @@ export function scoreQuery(term: string, tokens: string[], candidate: Candidate)
118
169
  * @param {string} [candidate.description]
119
170
  * @param {string[]} [candidate.prose] - Extra free-text blobs (doc section text, best practices).
120
171
  * @param {string[]} [candidate.guidance] - Usage guidance (features, best practices) — scored a tier below description.
121
- * @param {{fuzzy?: boolean}} [opts] - `fuzzy`: allow edit-distance (typo) matches. Default true; multi-word queries pass false.
122
- * @returns {{score: number, reason: string} | null}
172
+ * @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.
173
+ * @returns {{score: number, reason: string, alias?: 'partial' | 'keyword'} | null}
174
+ * `alias` marks a hit that read the query's words joined.
123
175
  */
124
176
  export function scoreCandidate(term: string, { name, aliases, domain, keywords, weakKeywords, description, prose, guidance, }: {
125
177
  name: string;
@@ -130,11 +182,13 @@ export function scoreCandidate(term: string, { name, aliases, domain, keywords,
130
182
  description?: string | undefined;
131
183
  prose?: string[] | undefined;
132
184
  guidance?: string[] | undefined;
133
- }, { fuzzy }?: {
185
+ }, { fuzzy, joins }?: {
134
186
  fuzzy?: boolean;
187
+ joins?: boolean;
135
188
  }): {
136
189
  score: number;
137
190
  reason: string;
191
+ alias?: "partial" | "keyword";
138
192
  } | null;
139
193
  /**
140
194
  * The components (name and keywords) a search response was scored against, or
@@ -213,6 +267,12 @@ export type Candidate = {
213
267
  */
214
268
  aliases?: string[] | undefined;
215
269
  keywords?: string[] | undefined;
270
+ /**
271
+ * - A doc's own declared keywords: what its
272
+ * author lists in `keywords`, without the code terms and table cells its
273
+ * other keywords add.
274
+ */
275
+ _declared?: string[] | undefined;
216
276
  weakKeywords?: string[] | undefined;
217
277
  description?: string | undefined;
218
278
  prose?: string[] | undefined;
@@ -252,4 +312,13 @@ export type Candidate = {
252
312
  _kind?: "page" | "block" | undefined;
253
313
  _resultName?: string | undefined;
254
314
  _commandName?: string | undefined;
315
+ /**
316
+ * - How an integration component holds its
317
+ * name, for the owner rule.
318
+ */
319
+ _hold?: "file" | "doc" | undefined;
320
+ /**
321
+ * - An integration component's doc.
322
+ */
323
+ _docPath?: string | undefined;
255
324
  };