@astryxdesign/cli 0.7.2 → 0.7.3-canary.04c7468
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 +127 -3
- package/api/build/blocks/blocks.d.mts +92 -0
- package/api/build/blocks/blocks.mjs +528 -0
- package/api/build/blocks/blocks.test.mjs +238 -0
- package/api/build/blocks/blocks.weights.json +1 -0
- package/api/build/build.mjs +25 -6
- package/api/build/build.type.mjs +1 -1
- package/api/build/kit/answer.d.mts +18 -0
- package/api/build/kit/answer.mjs +195 -0
- package/api/build/kit/answer.test.mjs +108 -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 +79 -0
- package/api/build/kit/signals.mjs +295 -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 +53 -0
- package/api/build/tree.test.mjs +185 -0
- package/api/build/tree.weights.json +1 -0
- package/api/component/_adapter.mjs +39 -5
- package/api/component/component-replacement.test.mjs +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.d.mts +1 -2
- package/api/discover/_adapter.mjs +42 -17
- package/api/discover/_adapter.test.mjs +42 -0
- package/api/discover/discover.mjs +0 -1
- package/api/docs/_adapter.d.mts +2 -2
- package/api/docs/_adapter.mjs +82 -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/_adapter.d.mts +33 -13
- package/api/doctor/_adapter.mjs +55 -25
- package/api/doctor/doctor.d.mts +23 -5
- package/api/doctor/doctor.mjs +183 -40
- package/api/doctor/doctor.test.mjs +95 -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-fixes.test.mjs +6 -5
- package/api/integration/validate-integration-gaps.test.mjs +1975 -0
- package/api/integration/validate-integration.d.mts +15 -2
- package/api/integration/validate-integration.mjs +298 -39
- package/api/integration/validate-integration.type.d.mts +90 -1
- package/api/integration/validate-integration.type.mjs +67 -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 +63 -0
- package/api/search/_entries.mjs +232 -0
- package/api/search/_entries.test.mjs +48 -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/template/template.mjs +5 -1
- 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 +3 -3
- package/assets/docs/tree/ci-and-containers.doc.mjs +13 -0
- 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 +5 -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-integration.test.mjs +49 -1
- package/clients/cli/commands/doctor.doc.mjs +1 -1
- package/clients/cli/commands/doctor.mjs +40 -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 +101 -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 +78 -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 +54 -94
- 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 +32 -7
- package/foundation/agent-docs/agent-doc-state.mjs +58 -9
- package/foundation/agent-docs/agent-docs.d.mts +100 -12
- package/foundation/agent-docs/agent-docs.mjs +1017 -105
- package/foundation/agent-docs/agent-docs.path-safety.test.mjs +1239 -17
- package/foundation/agent-docs/agent-docs.test.mjs +683 -39
- package/foundation/agent-docs/computer-agent-text.d.mts +39 -2
- package/foundation/agent-docs/computer-agent-text.mjs +135 -8
- package/foundation/agent-docs/computer-agent-text.test.mjs +99 -2
- 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/cli-peer-version.d.mts +36 -0
- package/foundation/integrations/cli-peer-version.mjs +132 -0
- package/foundation/integrations/cli-peer-version.test.mjs +354 -0
- 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/exports-subpaths.d.mts +57 -0
- package/foundation/integrations/exports-subpaths.mjs +113 -0
- package/foundation/integrations/integrations.d.mts +27 -1
- package/foundation/integrations/integrations.mjs +61 -2
- package/foundation/integrations/public-imports.d.mts +118 -0
- package/foundation/integrations/public-imports.mjs +1769 -0
- package/foundation/integrations/public-imports.test.mjs +1793 -0
- package/foundation/integrations/validate-contributions.d.mts +115 -0
- package/foundation/integrations/validate-contributions.mjs +1168 -13
- package/foundation/release/cache.test.mjs +24 -21
- package/foundation/release/global-state.d.mts +8 -4
- package/foundation/release/global-state.mjs +4 -2
- package/foundation/release/handoff.d.mts +23 -4
- package/foundation/release/handoff.mjs +175 -8
- package/foundation/release/notices.d.mts +62 -8
- package/foundation/release/notices.mjs +136 -25
- package/foundation/release/notices.test.mjs +21 -0
- 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/foundation/text/string-utils.d.mts +2 -1
- package/foundation/text/string-utils.mjs +16 -5
- package/foundation/text/suggest-order.d.mts +72 -0
- package/foundation/text/suggest-order.mjs +363 -0
- package/foundation/text/suggest-order.test.mjs +259 -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
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
resolveImportPath,
|
|
22
22
|
discoverComponents,
|
|
23
23
|
findComponentSource,
|
|
24
|
-
} from './component
|
|
24
|
+
} from './component.mjs';
|
|
25
25
|
import {findCoreDir} from '../../../foundation/fs/paths.mjs';
|
|
26
26
|
import {runCli} from '../../../test-utils/run-cli.mjs';
|
|
27
27
|
|
|
@@ -70,10 +70,12 @@ describe('astryx init — default (no flags)', () => {
|
|
|
70
70
|
});
|
|
71
71
|
|
|
72
72
|
describe('astryx init --features', () => {
|
|
73
|
-
it('--features agents writes AGENTS.md
|
|
73
|
+
it('--features agents writes AGENTS.md and the GEMINI.md import of it', async () => {
|
|
74
74
|
const {status} = await runCli(['init', '--features', 'agents'], {cwd: tmpDir});
|
|
75
75
|
expect(status).toBe(0);
|
|
76
76
|
expect(exists('AGENTS.md')).toBe(true);
|
|
77
|
+
expect(read('GEMINI.md')).toBe(`${MARKER_START}\n@./AGENTS.md\n<!-- ASTRYX:END -->\n`);
|
|
78
|
+
expect(exists('theme.template.ts')).toBe(false);
|
|
77
79
|
});
|
|
78
80
|
|
|
79
81
|
it('--features theme writes the annotated theme template', async () => {
|
|
@@ -126,7 +128,234 @@ describe('astryx init --agent <preset>', () => {
|
|
|
126
128
|
});
|
|
127
129
|
});
|
|
128
130
|
|
|
131
|
+
describe('astryx init from a subfolder', () => {
|
|
132
|
+
/** @param {string} dir */
|
|
133
|
+
const astryxProject = dir =>
|
|
134
|
+
fs.writeFileSync(
|
|
135
|
+
path.join(dir, 'package.json'),
|
|
136
|
+
JSON.stringify({name: 'app', dependencies: {'@astryxdesign/core': '0.7.0'}}),
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
it('writes the agent docs at the project root and reports them from where it ran', async () => {
|
|
140
|
+
astryxProject(tmpDir);
|
|
141
|
+
const sub = path.join(tmpDir, 'src', 'app');
|
|
142
|
+
fs.mkdirSync(sub, {recursive: true});
|
|
143
|
+
|
|
144
|
+
const json = await runCli(['init', '--json'], {cwd: sub});
|
|
145
|
+
expect(json.status).toBe(0);
|
|
146
|
+
expect(JSON.parse(json.stdout).data.docsWritten).toEqual([
|
|
147
|
+
'../../AGENTS.md',
|
|
148
|
+
'../../GEMINI.md',
|
|
149
|
+
]);
|
|
150
|
+
expect(read('AGENTS.md')).toContain(MARKER_START);
|
|
151
|
+
expect(fs.readdirSync(sub)).toEqual([]);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('keeps --agent-docs-path relative to where it ran', async () => {
|
|
155
|
+
astryxProject(tmpDir);
|
|
156
|
+
const sub = path.join(tmpDir, 'docs');
|
|
157
|
+
fs.mkdirSync(sub);
|
|
158
|
+
|
|
159
|
+
const json = await runCli(['init', '--agent-docs-path', 'AI.md', '--json'], {cwd: sub});
|
|
160
|
+
expect(JSON.parse(json.stdout).data.docsWritten).toEqual(['AI.md']);
|
|
161
|
+
expect(fs.readFileSync(path.join(sub, 'AI.md'), 'utf8')).toContain(MARKER_START);
|
|
162
|
+
expect(exists('AGENTS.md')).toBe(false);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe('astryx init in the home folder', () => {
|
|
167
|
+
const saved = {HOME: process.env.HOME, USERPROFILE: process.env.USERPROFILE};
|
|
168
|
+
const personal = '# My notes\n\nAlways use tabs.\n';
|
|
169
|
+
|
|
170
|
+
beforeEach(() => {
|
|
171
|
+
process.env.HOME = tmpDir;
|
|
172
|
+
process.env.USERPROFILE = tmpDir;
|
|
173
|
+
fs.mkdirSync(path.join(tmpDir, '.claude'), {recursive: true});
|
|
174
|
+
fs.writeFileSync(path.join(tmpDir, '.claude', 'CLAUDE.md'), personal);
|
|
175
|
+
});
|
|
176
|
+
afterEach(() => {
|
|
177
|
+
for (const [key, value] of Object.entries(saved)) {
|
|
178
|
+
if (value === undefined) delete process.env[key];
|
|
179
|
+
else process.env[key] = value;
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('writes no agent docs, says why, and keeps the soft-failure exit code', async () => {
|
|
184
|
+
const json = await runCli(['init', '--json'], {cwd: tmpDir});
|
|
185
|
+
expect(json.status).toBe(0);
|
|
186
|
+
const env = JSON.parse(json.stdout);
|
|
187
|
+
expect(env.data.docsWritten).toEqual([]);
|
|
188
|
+
expect(env.data.docsError).toEqual({
|
|
189
|
+
kind: 'install-failed',
|
|
190
|
+
message: expect.stringMatching(/not written into the home folder/),
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const text = await runCli(['init'], {cwd: tmpDir});
|
|
194
|
+
expect(text.status).toBe(0);
|
|
195
|
+
expect(text.stderr).toMatch(/not written into the home folder/);
|
|
196
|
+
expect(text.stderr).not.toMatch(/Error:/);
|
|
197
|
+
|
|
198
|
+
expect(read('.claude/CLAUDE.md')).toBe(personal);
|
|
199
|
+
expect(fs.readdirSync(tmpDir)).toEqual(['.claude']);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('does not let a package.json in the home folder pull the docs there', async () => {
|
|
203
|
+
fs.writeFileSync(
|
|
204
|
+
path.join(tmpDir, 'package.json'),
|
|
205
|
+
JSON.stringify({dependencies: {'@astryxdesign/core': '0.7.0'}}),
|
|
206
|
+
);
|
|
207
|
+
const scratch = path.join(tmpDir, 'scratch');
|
|
208
|
+
fs.mkdirSync(scratch);
|
|
209
|
+
|
|
210
|
+
const json = await runCli(['init', '--json'], {cwd: scratch});
|
|
211
|
+
expect(json.status).toBe(0);
|
|
212
|
+
expect(JSON.parse(json.stdout).data.docsWritten).toEqual(['AGENTS.md', 'GEMINI.md']);
|
|
213
|
+
expect(fs.existsSync(path.join(scratch, 'AGENTS.md'))).toBe(true);
|
|
214
|
+
expect(exists('AGENTS.md')).toBe(false);
|
|
215
|
+
expect(read('.claude/CLAUDE.md')).toBe(personal);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('--remove-agents in a folder in home never reaches the home folder', async () => {
|
|
219
|
+
fs.writeFileSync(
|
|
220
|
+
path.join(tmpDir, '.claude', 'CLAUDE.md'),
|
|
221
|
+
`${personal}\n<!-- ASTRYX:START -->\nAstryx v0.7.0\n<!-- ASTRYX:END -->\n`,
|
|
222
|
+
);
|
|
223
|
+
fs.writeFileSync(
|
|
224
|
+
path.join(tmpDir, 'package.json'),
|
|
225
|
+
JSON.stringify({dependencies: {'@astryxdesign/core': '0.7.0'}}),
|
|
226
|
+
);
|
|
227
|
+
const scratch = path.join(tmpDir, 'scratch');
|
|
228
|
+
fs.mkdirSync(scratch);
|
|
229
|
+
|
|
230
|
+
const json = await runCli(['init', '--remove-agents', '--json'], {cwd: scratch});
|
|
231
|
+
expect(JSON.parse(json.stdout).data.removed).toBe(false);
|
|
232
|
+
expect(read('.claude/CLAUDE.md')).toContain('<!-- ASTRYX:START -->');
|
|
233
|
+
|
|
234
|
+
// Run in the home folder itself, it still removes the block there.
|
|
235
|
+
const home = await runCli(['init', '--remove-agents', '--json'], {cwd: tmpDir});
|
|
236
|
+
expect(JSON.parse(home.stdout).data.removed).toBe(true);
|
|
237
|
+
expect(read('.claude/CLAUDE.md')).toBe(personal);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it.each(['.gemini', '.claude'])('refuses the settings folder ~/%s', async folder => {
|
|
241
|
+
const dir = path.join(tmpDir, folder);
|
|
242
|
+
fs.mkdirSync(dir, {recursive: true});
|
|
243
|
+
const before = fs.readdirSync(dir);
|
|
244
|
+
|
|
245
|
+
const json = await runCli(['init', '--json'], {cwd: dir});
|
|
246
|
+
expect(json.status).toBe(0);
|
|
247
|
+
const env = JSON.parse(json.stdout);
|
|
248
|
+
expect(env.data.docsWritten).toEqual([]);
|
|
249
|
+
expect(env.data.docsError.message).toMatch(/settings folder in it outside a project/);
|
|
250
|
+
expect(fs.readdirSync(dir)).toEqual(before);
|
|
251
|
+
expect(read('.claude/CLAUDE.md')).toBe(personal);
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
describe('astryx init in an agent worktree inside a home settings folder', () => {
|
|
256
|
+
const saved = {HOME: process.env.HOME, USERPROFILE: process.env.USERPROFILE};
|
|
257
|
+
|
|
258
|
+
beforeEach(() => {
|
|
259
|
+
process.env.HOME = tmpDir;
|
|
260
|
+
process.env.USERPROFILE = tmpDir;
|
|
261
|
+
});
|
|
262
|
+
afterEach(() => {
|
|
263
|
+
for (const [key, value] of Object.entries(saved)) {
|
|
264
|
+
if (value === undefined) delete process.env[key];
|
|
265
|
+
else process.env[key] = value;
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it.each([
|
|
270
|
+
['Codex', path.join('.codex', 'worktrees', 'a1b2c3', 'web')],
|
|
271
|
+
['Cursor', path.join('.cursor', 'worktrees', 'web', 'x9y8')],
|
|
272
|
+
])('writes at the project root of a %s worktree', async (_tool, rel) => {
|
|
273
|
+
const project = path.join(tmpDir, rel);
|
|
274
|
+
fs.mkdirSync(path.join(project, 'src'), {recursive: true});
|
|
275
|
+
fs.writeFileSync(path.join(project, '.git'), 'gitdir: /elsewhere/.git/worktrees/web\n');
|
|
276
|
+
fs.writeFileSync(
|
|
277
|
+
path.join(project, 'package.json'),
|
|
278
|
+
JSON.stringify({name: 'web', dependencies: {'@astryxdesign/core': '0.7.0'}}),
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
const json = await runCli(['init', '--json'], {cwd: path.join(project, 'src')});
|
|
282
|
+
expect(json.status).toBe(0);
|
|
283
|
+
const env = JSON.parse(json.stdout);
|
|
284
|
+
expect(env.data.docsWritten).toEqual(['../AGENTS.md', '../GEMINI.md']);
|
|
285
|
+
expect(env.data.docsError).toBeNull();
|
|
286
|
+
|
|
287
|
+
const explicit = await runCli(['init', '--agent-docs-path', 'AI.md', '--json'], {
|
|
288
|
+
cwd: path.join(project, 'src'),
|
|
289
|
+
});
|
|
290
|
+
expect(JSON.parse(explicit.stdout).data.docsWritten).toEqual(['AI.md']);
|
|
291
|
+
|
|
292
|
+
// Nothing reaches the settings folder itself.
|
|
293
|
+
const settings = path.join(tmpDir, rel.split(path.sep)[0]);
|
|
294
|
+
expect(fs.readdirSync(settings)).toEqual(['worktrees']);
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
describe('astryx init and the project root', () => {
|
|
299
|
+
it('writes where it runs when no folder above has a package.json, as before', async () => {
|
|
300
|
+
const notes = path.join(tmpDir, 'notes');
|
|
301
|
+
fs.mkdirSync(notes);
|
|
302
|
+
|
|
303
|
+
const json = await runCli(['init', '--json'], {cwd: notes});
|
|
304
|
+
expect(json.status).toBe(0);
|
|
305
|
+
const env = JSON.parse(json.stdout);
|
|
306
|
+
expect(env.data.docsWritten).toEqual(['AGENTS.md', 'GEMINI.md']);
|
|
307
|
+
expect(env.data.docsError).toBeNull();
|
|
308
|
+
expect(fs.readdirSync(notes).sort()).toEqual(['AGENTS.md', 'GEMINI.md']);
|
|
309
|
+
expect(fs.readdirSync(tmpDir)).toEqual(['notes']);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('writes at the project root that declares Astryx, past a nested package that does not', async () => {
|
|
313
|
+
fs.writeFileSync(
|
|
314
|
+
path.join(tmpDir, 'package.json'),
|
|
315
|
+
JSON.stringify({name: 'app', dependencies: {'@astryxdesign/core': '0.7.0'}}),
|
|
316
|
+
);
|
|
317
|
+
const functions = path.join(tmpDir, 'functions');
|
|
318
|
+
fs.mkdirSync(path.join(functions, 'src'), {recursive: true});
|
|
319
|
+
fs.writeFileSync(path.join(functions, 'package.json'), '{"name":"functions"}\n');
|
|
320
|
+
|
|
321
|
+
const json = await runCli(['init', '--json'], {cwd: path.join(functions, 'src')});
|
|
322
|
+
expect(JSON.parse(json.stdout).data.docsWritten).toEqual([
|
|
323
|
+
'../../AGENTS.md',
|
|
324
|
+
'../../GEMINI.md',
|
|
325
|
+
]);
|
|
326
|
+
expect(fs.readdirSync(functions).sort()).toEqual(['package.json', 'src']);
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
129
330
|
describe('astryx init --remove-agents', () => {
|
|
331
|
+
it('run in a subfolder, removes what init wrote at the project root, with the same JSON shape', async () => {
|
|
332
|
+
fs.writeFileSync(
|
|
333
|
+
path.join(tmpDir, 'package.json'),
|
|
334
|
+
JSON.stringify({name: 'app', dependencies: {'@astryxdesign/core': '0.7.0'}}),
|
|
335
|
+
);
|
|
336
|
+
const sub = path.join(tmpDir, 'src', 'app');
|
|
337
|
+
fs.mkdirSync(sub, {recursive: true});
|
|
338
|
+
await runCli(['init'], {cwd: sub});
|
|
339
|
+
expect(exists('AGENTS.md')).toBe(true);
|
|
340
|
+
|
|
341
|
+
const text = await runCli(['init', '--remove-agents'], {cwd: sub});
|
|
342
|
+
expect(text.status).toBe(0);
|
|
343
|
+
expect(text.stdout).toMatch(/Removed empty \.\.\/\.\.\/AGENTS\.md/);
|
|
344
|
+
expect(exists('AGENTS.md')).toBe(false);
|
|
345
|
+
expect(exists('GEMINI.md')).toBe(false);
|
|
346
|
+
|
|
347
|
+
await runCli(['init'], {cwd: sub});
|
|
348
|
+
const json = await runCli(['init', '--remove-agents', '--json'], {cwd: sub});
|
|
349
|
+
expect(json.status).toBe(0);
|
|
350
|
+
// meta.answeredBy names the release that answered (spec AST-081).
|
|
351
|
+
expect(JSON.parse(json.stdout)).toEqual({
|
|
352
|
+
apiVersion: 1,
|
|
353
|
+
type: 'init.remove',
|
|
354
|
+
data: {removed: true},
|
|
355
|
+
meta: {answeredBy: expect.any(Object)},
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
130
359
|
it('removes the Astryx section it previously installed', async () => {
|
|
131
360
|
await runCli(['init', '--features', 'agents'], {cwd: tmpDir});
|
|
132
361
|
expect(exists('AGENTS.md')).toBe(true);
|
|
@@ -16,10 +16,24 @@ export const doc = {
|
|
|
16
16
|
summary: 'Initialize the design system in your project',
|
|
17
17
|
description:
|
|
18
18
|
'Non-interactive project setup (no prompts, so it behaves the same for humans, ' +
|
|
19
|
-
'agents, and CI). By default it
|
|
20
|
-
'
|
|
21
|
-
|
|
22
|
-
'
|
|
19
|
+
'agents, and CI). By default it writes the agent-docs block, including guidance ' +
|
|
20
|
+
'from configured integrations, to AGENTS.md and to the Claude Code file the project ' +
|
|
21
|
+
'has (CLAUDE.md, else .claude/CLAUDE.md; when CLAUDE.md exists, a block in ' +
|
|
22
|
+
'.claude/CLAUDE.md is removed, unless the project ignores CLAUDE.md, in which case a .claude/CLAUDE.md the ' +
|
|
23
|
+
'project commits keeps a copy), and a one-line import of AGENTS.md to GEMINI.md ' +
|
|
24
|
+
'unless GEMINI.md already imports it. It then prints getting-started steps. Agent ' +
|
|
25
|
+
'docs go to the project root: the nearest folder at or above this one whose ' +
|
|
26
|
+
'package.json declares an @astryxdesign package, else the nearest with any ' +
|
|
27
|
+
'package.json, else this folder. The search stops at a repository root and never ' +
|
|
28
|
+
'counts the home folder. In the home folder, or in a settings folder in it ' +
|
|
29
|
+
'such as ~/.gemini outside a project, no agent docs are written. Every folder of the ' +
|
|
30
|
+
'home folder whose name starts with a dot is a settings folder, and such a folder ' +
|
|
31
|
+
'itself never counts as a project, even with a package.json; a project inside one, ' +
|
|
32
|
+
'such as an agent worktree with its own package.json, works. The managed block is ' +
|
|
33
|
+
'written whatever ignore rules or generated markers say; the text around it is never changed. ' +
|
|
34
|
+
'--features and --all print only ' +
|
|
35
|
+
"the chosen features' " +
|
|
36
|
+
'guidance (no getting-started steps); the theme feature also writes an annotated theme template. ' +
|
|
23
37
|
'Outside a project it writes the same files but prints no step that sets up a project: ' +
|
|
24
38
|
'no getting-started steps, and no step that adds a theme to an app. ' +
|
|
25
39
|
'--user writes the computer-level agent text instead (see the flag), in any folder, and touches nothing in the folder. ' +
|
|
@@ -44,8 +58,10 @@ export const doc = {
|
|
|
44
58
|
flag: '--remove-agents',
|
|
45
59
|
param: 'options.removeAgents',
|
|
46
60
|
description:
|
|
47
|
-
'Remove the managed block from AGENTS.md, CLAUDE.md, .claude/CLAUDE.md, .cursorrules, .hermes.md and HERMES.md ' +
|
|
48
|
-
|
|
61
|
+
'Remove the managed block from AGENTS.md, CLAUDE.md, .claude/CLAUDE.md, GEMINI.md, .cursorrules, .hermes.md and HERMES.md, ' +
|
|
62
|
+
'at the project root (where init writes them) and in this folder ' +
|
|
63
|
+
"(deleting AGENTS.md or .claude/CLAUDE.md when only init's heading is left, and GEMINI.md when nothing is left) and do nothing else. " +
|
|
64
|
+
'Below the project root, a project-root agent file whose block resolves outside the project is left as it is, with a note. ' +
|
|
49
65
|
'--features, --all, --agent and --agent-docs-path are refused; a file written with --agent-docs-path keeps its block',
|
|
50
66
|
},
|
|
51
67
|
{
|
|
@@ -66,7 +82,9 @@ export const doc = {
|
|
|
66
82
|
choices: ['claude', 'cursor', 'codex', 'hermes', 'muse', 'all'],
|
|
67
83
|
description:
|
|
68
84
|
'Target AI tool for agent docs: claude (CLAUDE.md or .claude/CLAUDE.md, else creates .claude/CLAUDE.md), cursor (.cursorrules if present, else AGENTS.md), ' +
|
|
69
|
-
'codex and muse (AGENTS.md), hermes (.hermes.md or HERMES.md if present, else AGENTS.md), all (every existing agent doc
|
|
85
|
+
'codex and muse (AGENTS.md), hermes (.hermes.md or HERMES.md if present, else AGENTS.md), all (every existing agent doc except GEMINI.md, which keeps its ' +
|
|
86
|
+
'one-line import of AGENTS.md, else AGENTS.md and .claude/CLAUDE.md). With claude and all, when CLAUDE.md gets the block, .claude/CLAUDE.md gets none and ' +
|
|
87
|
+
'a block already there is removed, unless the project ignores CLAUDE.md: then a .claude/CLAUDE.md the project commits gets a copy too, with claude and all alike. ' +
|
|
70
88
|
'An unknown tool exits 1 with ERR_UNKNOWN_AGENT. ' +
|
|
71
89
|
'Used only when agent docs are installed (the default, --all, or --features agents); --agent-docs-path takes precedence.',
|
|
72
90
|
},
|
|
@@ -76,6 +94,7 @@ export const doc = {
|
|
|
76
94
|
description:
|
|
77
95
|
'Explicit file path(s) for agent docs, inside the project; takes precedence over --agent. ' +
|
|
78
96
|
'If any path is outside the project, no agent docs are written and the command exits 1. ' +
|
|
97
|
+
'A path in the home folder itself, or in a settings folder in it outside a project (such as ~/.claude or ~/.claude/hooks), is refused with a note (exit 0), and nothing is written. ' +
|
|
79
98
|
'Used only when agent docs are installed',
|
|
80
99
|
},
|
|
81
100
|
],
|
|
@@ -90,11 +109,11 @@ export const doc = {
|
|
|
90
109
|
exitCodes: [
|
|
91
110
|
{
|
|
92
111
|
code: 0,
|
|
93
|
-
when:
|
|
112
|
+
when: "success, including agent docs left unwritten in five soft cases, each reported as docsError with one note: running in the home folder, or in a settings folder in it outside a project; a GEMINI.md that resolves outside the project; when init runs below the project root, a project-root agent file that resolves outside the project; the project's Claude Code file (root CLAUDE.md, or .claude/CLAUDE.md when there is no root one) when it resolves outside the project and is a block-less import of an existing AGENTS.md, which 0.7.1 left alone; and a .claude/CLAUDE.md that resolves outside the project and that 0.7.1 did not write: beside a CLAUDE.md the project ignores, the one --agent claude adds or a block-less import of an existing AGENTS.md; with --agent claude beside a CLAUDE.md, one whose block it would remove",
|
|
94
113
|
},
|
|
95
114
|
{
|
|
96
115
|
code: 1,
|
|
97
|
-
|
|
116
|
+
when: 'an unknown --agent (ERR_UNKNOWN_AGENT) or feature, an --agent-docs-path outside the project, --remove-agents combined with install options, --user combined with --features, --all, --agent or --agent-docs-path, or --all with --features (ERR_INVALID_ARGUMENT); or, when init runs at the project root, an agent file 0.7.1 also wrote there that resolves outside the project (ERR_PATH_TRAVERSAL, docsError path-safety, nothing written)',
|
|
98
117
|
},
|
|
99
118
|
],
|
|
100
119
|
related: ['doctor', 'upgrade', 'build', 'theme'],
|
|
@@ -67,6 +67,7 @@ describe('astryx init flag interactions', () => {
|
|
|
67
67
|
expect(help).toContain(file);
|
|
68
68
|
}
|
|
69
69
|
expect(help).toMatch(/--agent-docs-path are refused/);
|
|
70
|
+
expect(help).toMatch(/at the project root \(where init writes them\) and in this folder/);
|
|
70
71
|
|
|
71
72
|
await initJson(['--agent-docs-path', 'docs/AI.md']);
|
|
72
73
|
await initJson([]);
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @file init command — thin wrapper around the init API.
|
|
5
5
|
*
|
|
6
|
-
* `astryx init` is non-interactive by default: it installs the
|
|
7
|
-
*
|
|
6
|
+
* `astryx init` is non-interactive by default: it installs the agent-docs
|
|
7
|
+
* cheat sheet with NO prompts, so it behaves identically for humans,
|
|
8
8
|
* AI agents, CI, and piped I/O — it never hangs or errors on a missing TTY.
|
|
9
9
|
* Non-interactive feature install: `astryx init --features agents,theme,template`
|
|
10
10
|
* Re-runnable: safe to run multiple times, idempotent.
|
|
@@ -439,3 +439,51 @@ describe('a stray doc pointer', () => {
|
|
|
439
439
|
expect(strict.stdout).toContain('stray_doc_pointer');
|
|
440
440
|
});
|
|
441
441
|
});
|
|
442
|
+
describe('a command written with a runner', () => {
|
|
443
|
+
it('is a note in doctor integration validate, and integration check --strict still passes', async () => {
|
|
444
|
+
fs.writeFileSync(
|
|
445
|
+
path.join(tmpDir, 'package.json'),
|
|
446
|
+
JSON.stringify({name: '@acme/widgets', version: '1.2.3'}),
|
|
447
|
+
);
|
|
448
|
+
fs.writeFileSync(
|
|
449
|
+
path.join(tmpDir, 'astryx.integration.mjs'),
|
|
450
|
+
`export default {docs: './docs'};\n`,
|
|
451
|
+
);
|
|
452
|
+
fs.mkdirSync(path.join(tmpDir, 'docs'));
|
|
453
|
+
const doc = {
|
|
454
|
+
type: 'generic',
|
|
455
|
+
name: 'acme-setup',
|
|
456
|
+
title: 'Fixture',
|
|
457
|
+
description: 'Fixture topic.',
|
|
458
|
+
sections: [
|
|
459
|
+
{
|
|
460
|
+
title: 'Overview',
|
|
461
|
+
content: [{type: 'code', lang: 'bash', code: 'npx astryx doctor'}],
|
|
462
|
+
},
|
|
463
|
+
],
|
|
464
|
+
};
|
|
465
|
+
fs.writeFileSync(
|
|
466
|
+
path.join(tmpDir, 'docs', 'acme-setup.doc.mjs'),
|
|
467
|
+
`export default ${JSON.stringify(doc)};\n`,
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
const doctor = await runCli(['doctor', 'integration', 'validate'], tmpDir);
|
|
471
|
+
expect(doctor.status).toBe(0);
|
|
472
|
+
expect(doctor.stdout).toContain(
|
|
473
|
+
'note (runner_command): docs/acme-setup.doc.mjs:',
|
|
474
|
+
);
|
|
475
|
+
expect(doctor.stdout).toContain('Write `astryx doctor`');
|
|
476
|
+
|
|
477
|
+
const strict = await runCli(
|
|
478
|
+
['--json', 'integration', 'check', '--strict'],
|
|
479
|
+
tmpDir,
|
|
480
|
+
);
|
|
481
|
+
expect(strict.status).toBe(0);
|
|
482
|
+
const validate = JSON.parse(strict.stdout).data.checks.find(
|
|
483
|
+
(/** @type {any} */ each) => each.check === 'validate',
|
|
484
|
+
).response.data;
|
|
485
|
+
expect(validate.notes.map((/** @type {any} */ note) => note.code)).toEqual([
|
|
486
|
+
'runner_command',
|
|
487
|
+
]);
|
|
488
|
+
});
|
|
489
|
+
});
|
|
@@ -8,7 +8,7 @@ export const doc = {
|
|
|
8
8
|
namespace: 'cli/commands',
|
|
9
9
|
summary: 'Author and verify an Astryx integration package',
|
|
10
10
|
description:
|
|
11
|
-
'Add contributions to your package, check them on every build with `integration check --strict`, then check the packed package the way an app receives it. The guides start at {@link namespace:
|
|
11
|
+
'Add contributions to your package, check them on every build with `integration check --strict`, then check the packed package the way an app receives it. The guides start at {@link namespace:authoring}.',
|
|
12
12
|
subcommands: ['add', 'check', 'verify', 'pack'],
|
|
13
13
|
examples: [
|
|
14
14
|
{
|
|
@@ -13,7 +13,7 @@ export const doc = {
|
|
|
13
13
|
name: 'manifest',
|
|
14
14
|
displayName: 'astryx manifest',
|
|
15
15
|
namespace: 'cli/commands',
|
|
16
|
-
summary: 'Print the full CLI capability manifest (use with --json)
|
|
16
|
+
summary: 'Print the full CLI capability manifest (use with --json)',
|
|
17
17
|
description:
|
|
18
18
|
'Emits the complete CLI surface: every command with its arguments, options, ' +
|
|
19
19
|
'subcommands, and JSON response types, as a single typed envelope for agents and ' +
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file manifest command — `astryx manifest`, the CLI capability manifest.
|
|
5
|
+
*
|
|
6
|
+
* A single, self-describing view of the whole CLI so agents can discover every
|
|
7
|
+
* command, argument, flag, and response type without scraping `--help`.
|
|
8
|
+
* `astryx manifest --json` is the dedicated surface; the bare `astryx --json`
|
|
9
|
+
* embeds the same payload under data.manifest for convenience.
|
|
10
|
+
*
|
|
11
|
+
* Intentionally CLI-special — no `api/manifest`. It introspects the live
|
|
12
|
+
* Commander `program`, so extracting it to `api/` would create an `api → cli`
|
|
13
|
+
* cycle. `buildManifest(program)` lives in lib/; see its header. Register it
|
|
14
|
+
* after every other command: the manifest lists commands in registration order.
|
|
15
|
+
*
|
|
16
|
+
* The command surface comes from the colocated CommandDoc via `defineCommand`.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import {getCliInvocation} from '../../../foundation/env/package-manager.mjs';
|
|
20
|
+
import {jsonOut} from '../../../foundation/response/json.mjs';
|
|
21
|
+
import {emit, section, text, records} from '../formatters/index.mjs';
|
|
22
|
+
import {buildManifest} from '../lib/manifest.mjs';
|
|
23
|
+
import {defineCommand} from '../lib/define-command.mjs';
|
|
24
|
+
import {resultSet} from '../../../foundation/debug/index.mjs';
|
|
25
|
+
import {doc as manifestCommand} from './manifest.doc.mjs';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {import('commander').Command} program the root program
|
|
29
|
+
* @param {{jsonSupported: Set<string>, version: string}} context the commands
|
|
30
|
+
* that accept --json, and the CLI's version
|
|
31
|
+
*/
|
|
32
|
+
export function registerManifest(program, {jsonSupported, version}) {
|
|
33
|
+
defineCommand(program, manifestCommand, {
|
|
34
|
+
action: () => {
|
|
35
|
+
const manifest = buildManifest(program, {jsonSupported, version});
|
|
36
|
+
// The manifest IS a result set: one entry per command the CLI ships.
|
|
37
|
+
const answered = resultSet({
|
|
38
|
+
count: manifest.commands.length,
|
|
39
|
+
resultKind: 'command',
|
|
40
|
+
});
|
|
41
|
+
if (program.opts().json) {
|
|
42
|
+
jsonOut({type: 'manifest', data: manifest});
|
|
43
|
+
return answered;
|
|
44
|
+
}
|
|
45
|
+
// Human-readable summary as greppable records (agents should use --json).
|
|
46
|
+
// One record per command: name, whether it supports --json, and the
|
|
47
|
+
// description. Field names are the manifest entry's own keys.
|
|
48
|
+
emit(
|
|
49
|
+
section(
|
|
50
|
+
`${manifest.name} v${manifest.version} (${manifest.commands.length} commands)`,
|
|
51
|
+
),
|
|
52
|
+
records(
|
|
53
|
+
manifest.commands.map(c => ({
|
|
54
|
+
name: c.name,
|
|
55
|
+
json: c.json ? 'yes' : '',
|
|
56
|
+
description: c.description || '',
|
|
57
|
+
})),
|
|
58
|
+
{fields: ['name', 'json', 'description']},
|
|
59
|
+
),
|
|
60
|
+
text(
|
|
61
|
+
`Run \`${getCliInvocation()} manifest --json\` for the full structured manifest.`,
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
return answered;
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
@@ -392,6 +392,97 @@ describe('doctor: the computer-level agent text', () => {
|
|
|
392
392
|
const A = '/home/me/.agents/skills/astryx/SKILL.md';
|
|
393
393
|
const C = '/home/me/.claude/skills/astryx';
|
|
394
394
|
|
|
395
|
+
it('a skill naming `astryx` with no Astryx CLI on the search path: says whether it is current, and names the step it records', () => {
|
|
396
|
+
const check = (/** @type {Record<string, any>} */ over) =>
|
|
397
|
+
checkComputerAgentText({
|
|
398
|
+
nodeVersion: '22.13.0',
|
|
399
|
+
answeredBy: {cli: '1.2.0', core: '1.2.0', copy: 'carried'},
|
|
400
|
+
latest: null,
|
|
401
|
+
agentText: [{path: A, state: 'current'}],
|
|
402
|
+
agentTextCommand: 'astryx',
|
|
403
|
+
searchPathCommands: [],
|
|
404
|
+
cwd: os.tmpdir(),
|
|
405
|
+
...over,
|
|
406
|
+
});
|
|
407
|
+
const recorded = check({agentTextInstall: 'pnpm add -g @astryxdesign/cli'});
|
|
408
|
+
// A package runner's own copy first on the search path does not count.
|
|
409
|
+
expect(
|
|
410
|
+
check({
|
|
411
|
+
agentTextInstall: 'pnpm add -g @astryxdesign/cli',
|
|
412
|
+
searchPathCommands: [
|
|
413
|
+
/** @type {any} */ ({
|
|
414
|
+
file: '/tmp/_npx/r/node_modules/.bin/astryx',
|
|
415
|
+
astryx: true,
|
|
416
|
+
runner: true,
|
|
417
|
+
}),
|
|
418
|
+
],
|
|
419
|
+
}).fix,
|
|
420
|
+
).toBe(
|
|
421
|
+
'Run `pnpm add -g @astryxdesign/cli`, the step the skill names for when `astryx` is not found.',
|
|
422
|
+
);
|
|
423
|
+
expect(recorded.status).toBe('info');
|
|
424
|
+
expect(recorded.message).toContain(
|
|
425
|
+
`The computer-level agent text matches this release: ${A}. It tells agents to run \`astryx\`, but no \`astryx\` command is on the search path`,
|
|
426
|
+
);
|
|
427
|
+
expect(recorded.fix).toBe(
|
|
428
|
+
'Run `pnpm add -g @astryxdesign/cli`, the step the skill names for when `astryx` is not found.',
|
|
429
|
+
);
|
|
430
|
+
// A skill from an earlier release records no step: npm's, worded as
|
|
431
|
+
// cli-on-path words it.
|
|
432
|
+
const older = check({
|
|
433
|
+
agentText: [{path: A, state: 'differs'}],
|
|
434
|
+
agentTextInstall: null,
|
|
435
|
+
});
|
|
436
|
+
expect(older.message).toContain(
|
|
437
|
+
"The computer-level agent text differs from this release's",
|
|
438
|
+
);
|
|
439
|
+
expect(older.fix).toBe(
|
|
440
|
+
'Install it for the computer: `npm i -g @astryxdesign/cli`.',
|
|
441
|
+
);
|
|
442
|
+
// With the Astryx CLI anywhere on the search path, even behind another
|
|
443
|
+
// program (cli-on-path's to report), or a skill naming a host's command,
|
|
444
|
+
// there is nothing to restore.
|
|
445
|
+
for (const over of [
|
|
446
|
+
{
|
|
447
|
+
searchPathCommands: [
|
|
448
|
+
/** @type {any} */ ({
|
|
449
|
+
file: '/usr/bin/astryx',
|
|
450
|
+
astryx: true,
|
|
451
|
+
version: '1.2.0',
|
|
452
|
+
}),
|
|
453
|
+
],
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
searchPathCommands: [
|
|
457
|
+
/** @type {any} */ ({file: '/opt/other/astryx', astryx: false}),
|
|
458
|
+
/** @type {any} */ ({
|
|
459
|
+
file: '/usr/bin/astryx',
|
|
460
|
+
astryx: true,
|
|
461
|
+
version: '1.2.0',
|
|
462
|
+
}),
|
|
463
|
+
],
|
|
464
|
+
},
|
|
465
|
+
{agentTextCommand: 'acme astryx'},
|
|
466
|
+
// An `astryx` the scan cannot trace (a Volta, asdf or mise shim, or
|
|
467
|
+
// another program): it may run the CLI, so no report here.
|
|
468
|
+
{
|
|
469
|
+
searchPathCommands: [
|
|
470
|
+
/** @type {any} */ ({
|
|
471
|
+
file: '/home/me/.volta/bin/astryx',
|
|
472
|
+
astryx: false,
|
|
473
|
+
runner: false,
|
|
474
|
+
}),
|
|
475
|
+
],
|
|
476
|
+
},
|
|
477
|
+
// Only a package runner's own copy: the CLI is still gone.
|
|
478
|
+
]) {
|
|
479
|
+
const other = check(over);
|
|
480
|
+
expect(other.message).toBe(
|
|
481
|
+
`The computer-level agent text matches this release: ${A}.`,
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
|
|
395
486
|
it('present and current, linked: no fix', () => {
|
|
396
487
|
const check = at([
|
|
397
488
|
{path: A, state: 'current'},
|
|
@@ -460,6 +551,14 @@ describe('doctor: the computer-level agent text', () => {
|
|
|
460
551
|
const home = tmpDir('home');
|
|
461
552
|
vi.stubEnv('HOME', home);
|
|
462
553
|
vi.stubEnv('USERPROFILE', home);
|
|
554
|
+
// The CLI installed for the computer, first on the search path, as the
|
|
555
|
+
// skill expects.
|
|
556
|
+
const bin = tmpDir('bin');
|
|
557
|
+
fs.symlinkSync(
|
|
558
|
+
path.join(CLI_ROOT, 'clients', 'cli', 'bin', 'astryx.mjs'),
|
|
559
|
+
path.join(bin, 'astryx'),
|
|
560
|
+
);
|
|
561
|
+
vi.stubEnv('PATH', `${bin}${path.delimiter}${process.env.PATH ?? ''}`);
|
|
463
562
|
try {
|
|
464
563
|
const find = r =>
|
|
465
564
|
r.envelope.data.checks.find(c => c.id === 'computer-agent-text');
|
|
@@ -541,7 +640,8 @@ describe('init outside a project (own)', () => {
|
|
|
541
640
|
const dir = tmpDir('init');
|
|
542
641
|
const json = await answer(['--json', 'init'], dir);
|
|
543
642
|
expect(json.status).toBe(0);
|
|
544
|
-
|
|
643
|
+
// GEMINI.md gets its one-line import of AGENTS.md (spec cli-surface INV31).
|
|
644
|
+
expect(json.written).toEqual(['AGENTS.md', 'GEMINI.md']);
|
|
545
645
|
expect(json.envelope.data.nextSteps).toBe(false);
|
|
546
646
|
const text = await runCli(['init'], tmpDir('init-text'));
|
|
547
647
|
expect(text.stdout).not.toContain('Next steps');
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file CommandDoc for `astryx postinstall`. A hidden, CLI-only hook with no
|
|
5
|
+
* wrapped API function (so no `fn`): it prints the welcome box.
|
|
6
|
+
* @position packages/cli/clients/cli/commands — command documentation
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** @type {import('@astryxdesign/cli/authoring').CommandDoc} */
|
|
10
|
+
export const doc = {
|
|
11
|
+
type: 'command',
|
|
12
|
+
name: 'postinstall',
|
|
13
|
+
displayName: 'astryx postinstall',
|
|
14
|
+
namespace: 'cli/commands',
|
|
15
|
+
summary: 'Print the welcome message shown after install',
|
|
16
|
+
description:
|
|
17
|
+
'Prints a short welcome box that names the first commands to run. It is hidden ' +
|
|
18
|
+
'from help and the manifest, looks nothing up, and does not support --json.',
|
|
19
|
+
examples: [{label: 'Print the welcome box', cli: 'astryx postinstall'}],
|
|
20
|
+
outsideProject: 'same',
|
|
21
|
+
exitCodes: [{code: 0, when: 'success'}],
|
|
22
|
+
related: ['init'],
|
|
23
|
+
};
|