@canonical/pragma-cli 0.34.0 → 0.35.0
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 +153 -30
- package/dist/.build-ok +0 -0
- package/dist/package.json +83 -0
- package/dist/pragma.conf.js +814 -0
- package/dist/pragma.conf.js.map +1 -0
- package/dist/src/bin.js +356 -0
- package/dist/src/bin.js.map +1 -0
- package/dist/src/capabilities/capabilities/capabilities.render.js +70 -0
- package/dist/src/capabilities/capabilities/capabilities.render.js.map +1 -0
- package/dist/src/capabilities/capabilities/capabilities.verb.js +59 -0
- package/dist/src/capabilities/capabilities/capabilities.verb.js.map +1 -0
- package/dist/src/capabilities/capabilities/catalog.js +130 -0
- package/dist/src/capabilities/capabilities/catalog.js.map +1 -0
- package/dist/src/capabilities/capabilities/hints.js +195 -0
- package/dist/src/capabilities/capabilities/hints.js.map +1 -0
- package/dist/src/capabilities/capabilities/index.js +11 -0
- package/dist/src/capabilities/capabilities/index.js.map +1 -0
- package/dist/src/capabilities/capabilities/types.js +11 -0
- package/dist/src/capabilities/capabilities/types.js.map +1 -0
- package/dist/src/capabilities/colophon/collectColophon.js +83 -0
- package/dist/src/capabilities/colophon/collectColophon.js.map +1 -0
- package/dist/src/capabilities/colophon/colophon.render.js +59 -0
- package/dist/src/capabilities/colophon/colophon.render.js.map +1 -0
- package/dist/src/capabilities/colophon/colophon.verb.js +43 -0
- package/dist/src/capabilities/colophon/colophon.verb.js.map +1 -0
- package/dist/src/capabilities/colophon/index.js +5 -0
- package/dist/src/capabilities/colophon/index.js.map +1 -0
- package/dist/src/capabilities/colophon/markdownTerminal.js +90 -0
- package/dist/src/capabilities/colophon/markdownTerminal.js.map +1 -0
- package/dist/src/capabilities/colophon/types.js +10 -0
- package/dist/src/capabilities/colophon/types.js.map +1 -0
- package/dist/src/capabilities/config/collectConfigShow.js +59 -0
- package/dist/src/capabilities/config/collectConfigShow.js.map +1 -0
- package/dist/src/capabilities/config/field.render.js +22 -0
- package/dist/src/capabilities/config/field.render.js.map +1 -0
- package/dist/src/capabilities/config/fields.js +46 -0
- package/dist/src/capabilities/config/fields.js.map +1 -0
- package/dist/src/capabilities/config/get.verb.js +61 -0
- package/dist/src/capabilities/config/get.verb.js.map +1 -0
- package/dist/src/capabilities/config/index.js +5 -0
- package/dist/src/capabilities/config/index.js.map +1 -0
- package/dist/src/capabilities/config/runField.js +59 -0
- package/dist/src/capabilities/config/runField.js.map +1 -0
- package/dist/src/capabilities/config/runGet.js +28 -0
- package/dist/src/capabilities/config/runGet.js.map +1 -0
- package/dist/src/capabilities/config/runSet.js +53 -0
- package/dist/src/capabilities/config/runSet.js.map +1 -0
- package/dist/src/capabilities/config/set.verb.js +66 -0
- package/dist/src/capabilities/config/set.verb.js.map +1 -0
- package/dist/src/capabilities/config/show.render.js +84 -0
- package/dist/src/capabilities/config/show.render.js.map +1 -0
- package/dist/src/capabilities/config/show.verb.js +44 -0
- package/dist/src/capabilities/config/show.verb.js.map +1 -0
- package/dist/src/capabilities/config/types.js +5 -0
- package/dist/src/capabilities/config/types.js.map +1 -0
- package/dist/src/capabilities/config/unset.verb.js +49 -0
- package/dist/src/capabilities/config/unset.verb.js.map +1 -0
- package/dist/src/capabilities/create/cliProjection.js +195 -0
- package/dist/src/capabilities/create/cliProjection.js.map +1 -0
- package/dist/src/capabilities/create/constants.js +47 -0
- package/dist/src/capabilities/create/constants.js.map +1 -0
- package/dist/src/capabilities/create/create.render.js +100 -0
- package/dist/src/capabilities/create/create.render.js.map +1 -0
- package/dist/src/capabilities/create/create.verb.js +438 -0
- package/dist/src/capabilities/create/create.verb.js.map +1 -0
- package/dist/src/capabilities/create/createHelp.js +47 -0
- package/dist/src/capabilities/create/createHelp.js.map +1 -0
- package/dist/src/capabilities/create/createSurface.generated.js +394 -0
- package/dist/src/capabilities/create/createSurface.generated.js.map +1 -0
- package/dist/src/capabilities/create/generatorToVerbSpec.js +106 -0
- package/dist/src/capabilities/create/generatorToVerbSpec.js.map +1 -0
- package/dist/src/capabilities/create/index.js +18 -0
- package/dist/src/capabilities/create/index.js.map +1 -0
- package/dist/src/capabilities/create/messages.js +16 -0
- package/dist/src/capabilities/create/messages.js.map +1 -0
- package/dist/src/capabilities/create/mount.js +293 -0
- package/dist/src/capabilities/create/mount.js.map +1 -0
- package/dist/src/capabilities/create/pathJail.js +65 -0
- package/dist/src/capabilities/create/pathJail.js.map +1 -0
- package/dist/src/capabilities/create/pickGenerator.js +53 -0
- package/dist/src/capabilities/create/pickGenerator.js.map +1 -0
- package/dist/src/capabilities/create/types.js +2 -0
- package/dist/src/capabilities/create/types.js.map +1 -0
- package/dist/src/capabilities/distribution.js +40 -0
- package/dist/src/capabilities/distribution.js.map +1 -0
- package/dist/src/capabilities/doctor/checks/checkKeStore.js +40 -0
- package/dist/src/capabilities/doctor/checks/checkKeStore.js.map +1 -0
- package/dist/src/capabilities/doctor/checks/checkNodeVersion.js +21 -0
- package/dist/src/capabilities/doctor/checks/checkNodeVersion.js.map +1 -0
- package/dist/src/capabilities/doctor/checks/checkPackageRefs.js +92 -0
- package/dist/src/capabilities/doctor/checks/checkPackageRefs.js.map +1 -0
- package/dist/src/capabilities/doctor/checks/checkPragmaVersion.js +19 -0
- package/dist/src/capabilities/doctor/checks/checkPragmaVersion.js.map +1 -0
- package/dist/src/capabilities/doctor/checks/checkShellCompletions.js +199 -0
- package/dist/src/capabilities/doctor/checks/checkShellCompletions.js.map +1 -0
- package/dist/src/capabilities/doctor/checks/index.js +27 -0
- package/dist/src/capabilities/doctor/checks/index.js.map +1 -0
- package/dist/src/capabilities/doctor/checks/mcpCommand.js +57 -0
- package/dist/src/capabilities/doctor/checks/mcpCommand.js.map +1 -0
- package/dist/src/capabilities/doctor/checks/targetHealth.js +205 -0
- package/dist/src/capabilities/doctor/checks/targetHealth.js.map +1 -0
- package/dist/src/capabilities/doctor/doctor.render.js +185 -0
- package/dist/src/capabilities/doctor/doctor.render.js.map +1 -0
- package/dist/src/capabilities/doctor/doctor.verb.js +45 -0
- package/dist/src/capabilities/doctor/doctor.verb.js.map +1 -0
- package/dist/src/capabilities/doctor/index.js +5 -0
- package/dist/src/capabilities/doctor/index.js.map +1 -0
- package/dist/src/capabilities/doctor/runChecks.js +83 -0
- package/dist/src/capabilities/doctor/runChecks.js.map +1 -0
- package/dist/src/capabilities/doctor/types.js +5 -0
- package/dist/src/capabilities/doctor/types.js.map +1 -0
- package/dist/src/capabilities/graph/index.js +19 -0
- package/dist/src/capabilities/graph/index.js.map +1 -0
- package/dist/src/capabilities/graph/inspect.render.js +76 -0
- package/dist/src/capabilities/graph/inspect.render.js.map +1 -0
- package/dist/src/capabilities/graph/inspect.verb.js +53 -0
- package/dist/src/capabilities/graph/inspect.verb.js.map +1 -0
- package/dist/src/capabilities/graph/query.render.js +73 -0
- package/dist/src/capabilities/graph/query.render.js.map +1 -0
- package/dist/src/capabilities/graph/query.verb.js +49 -0
- package/dist/src/capabilities/graph/query.verb.js.map +1 -0
- package/dist/src/capabilities/graph/runQuery.js +41 -0
- package/dist/src/capabilities/graph/runQuery.js.map +1 -0
- package/dist/src/capabilities/index.js +56 -0
- package/dist/src/capabilities/index.js.map +1 -0
- package/dist/src/capabilities/info/collectInfo.js +66 -0
- package/dist/src/capabilities/info/collectInfo.js.map +1 -0
- package/dist/src/capabilities/info/index.js +5 -0
- package/dist/src/capabilities/info/index.js.map +1 -0
- package/dist/src/capabilities/info/info.render.js +53 -0
- package/dist/src/capabilities/info/info.render.js.map +1 -0
- package/dist/src/capabilities/info/info.verb.js +42 -0
- package/dist/src/capabilities/info/info.verb.js.map +1 -0
- package/dist/src/capabilities/info/types.js +5 -0
- package/dist/src/capabilities/info/types.js.map +1 -0
- package/dist/src/capabilities/meta/complete.verb.js +43 -0
- package/dist/src/capabilities/meta/complete.verb.js.map +1 -0
- package/dist/src/capabilities/meta/index.js +13 -0
- package/dist/src/capabilities/meta/index.js.map +1 -0
- package/dist/src/capabilities/meta/mcp.verb.js +34 -0
- package/dist/src/capabilities/meta/mcp.verb.js.map +1 -0
- package/dist/src/capabilities/ontology/index.js +21 -0
- package/dist/src/capabilities/ontology/index.js.map +1 -0
- package/dist/src/capabilities/ontology/queries.js +123 -0
- package/dist/src/capabilities/ontology/queries.js.map +1 -0
- package/dist/src/capabilities/ontology/render.js +66 -0
- package/dist/src/capabilities/ontology/render.js.map +1 -0
- package/dist/src/capabilities/ontology/verbs.js +181 -0
- package/dist/src/capabilities/ontology/verbs.js.map +1 -0
- package/dist/src/capabilities/prompt/index.js +16 -0
- package/dist/src/capabilities/prompt/index.js.map +1 -0
- package/dist/src/capabilities/prompt/prompt.render.js +78 -0
- package/dist/src/capabilities/prompt/prompt.render.js.map +1 -0
- package/dist/src/capabilities/prompt/types.js +10 -0
- package/dist/src/capabilities/prompt/types.js.map +1 -0
- package/dist/src/capabilities/prompt/verbs.js +93 -0
- package/dist/src/capabilities/prompt/verbs.js.map +1 -0
- package/dist/src/capabilities/resources/index.js +10 -0
- package/dist/src/capabilities/resources/index.js.map +1 -0
- package/dist/src/capabilities/resources/provider.js +423 -0
- package/dist/src/capabilities/resources/provider.js.map +1 -0
- package/dist/src/capabilities/setup/buildPlan.js +168 -0
- package/dist/src/capabilities/setup/buildPlan.js.map +1 -0
- package/dist/src/capabilities/setup/index.js +5 -0
- package/dist/src/capabilities/setup/index.js.map +1 -0
- package/dist/src/capabilities/setup/mode.js +53 -0
- package/dist/src/capabilities/setup/mode.js.map +1 -0
- package/dist/src/capabilities/setup/operations/index.js +39 -0
- package/dist/src/capabilities/setup/operations/index.js.map +1 -0
- package/dist/src/capabilities/setup/operations/setupCompletions.js +160 -0
- package/dist/src/capabilities/setup/operations/setupCompletions.js.map +1 -0
- package/dist/src/capabilities/setup/operations/setupConfig.js +101 -0
- package/dist/src/capabilities/setup/operations/setupConfig.js.map +1 -0
- package/dist/src/capabilities/setup/operations/setupGenerator.js +343 -0
- package/dist/src/capabilities/setup/operations/setupGenerator.js.map +1 -0
- package/dist/src/capabilities/setup/operations/setupLsp.js +277 -0
- package/dist/src/capabilities/setup/operations/setupLsp.js.map +1 -0
- package/dist/src/capabilities/setup/operations/setupMcp.js +207 -0
- package/dist/src/capabilities/setup/operations/setupMcp.js.map +1 -0
- package/dist/src/capabilities/setup/operations/setupSkills.js +316 -0
- package/dist/src/capabilities/setup/operations/setupSkills.js.map +1 -0
- package/dist/src/capabilities/setup/plan.js +128 -0
- package/dist/src/capabilities/setup/plan.js.map +1 -0
- package/dist/src/capabilities/setup/plan.render.js +193 -0
- package/dist/src/capabilities/setup/plan.render.js.map +1 -0
- package/dist/src/capabilities/setup/setup.render.js +54 -0
- package/dist/src/capabilities/setup/setup.render.js.map +1 -0
- package/dist/src/capabilities/setup/setup.verb.js +334 -0
- package/dist/src/capabilities/setup/setup.verb.js.map +1 -0
- package/dist/src/capabilities/setup/shell.js +196 -0
- package/dist/src/capabilities/setup/shell.js.map +1 -0
- package/dist/src/capabilities/setup/targets.js +263 -0
- package/dist/src/capabilities/setup/targets.js.map +1 -0
- package/dist/src/capabilities/setup/types.js +5 -0
- package/dist/src/capabilities/setup/types.js.map +1 -0
- package/dist/src/capabilities/shared/assertExecOk.js +137 -0
- package/dist/src/capabilities/shared/assertExecOk.js.map +1 -0
- package/dist/src/capabilities/shared/bands.js +16 -0
- package/dist/src/capabilities/shared/bands.js.map +1 -0
- package/dist/src/capabilities/shared/index.js +26 -0
- package/dist/src/capabilities/shared/index.js.map +1 -0
- package/dist/src/capabilities/shared/packageManager.js +103 -0
- package/dist/src/capabilities/shared/packageManager.js.map +1 -0
- package/dist/src/capabilities/shared/registry.js +55 -0
- package/dist/src/capabilities/shared/registry.js.map +1 -0
- package/dist/src/capabilities/skill/discover.js +195 -0
- package/dist/src/capabilities/skill/discover.js.map +1 -0
- package/dist/src/capabilities/skill/index.js +10 -0
- package/dist/src/capabilities/skill/index.js.map +1 -0
- package/dist/src/capabilities/skill/render.js +49 -0
- package/dist/src/capabilities/skill/render.js.map +1 -0
- package/dist/src/capabilities/skill/verbs.js +83 -0
- package/dist/src/capabilities/skill/verbs.js.map +1 -0
- package/dist/src/capabilities/sources/collectStatus.js +103 -0
- package/dist/src/capabilities/sources/collectStatus.js.map +1 -0
- package/dist/src/capabilities/sources/index.js +13 -0
- package/dist/src/capabilities/sources/index.js.map +1 -0
- package/dist/src/capabilities/sources/installSkills.js +167 -0
- package/dist/src/capabilities/sources/installSkills.js.map +1 -0
- package/dist/src/capabilities/sources/runUpdate.js +394 -0
- package/dist/src/capabilities/sources/runUpdate.js.map +1 -0
- package/dist/src/capabilities/sources/status.render.js +64 -0
- package/dist/src/capabilities/sources/status.render.js.map +1 -0
- package/dist/src/capabilities/sources/status.verb.js +40 -0
- package/dist/src/capabilities/sources/status.verb.js.map +1 -0
- package/dist/src/capabilities/sources/types.js +6 -0
- package/dist/src/capabilities/sources/types.js.map +1 -0
- package/dist/src/capabilities/sources/update.render.js +34 -0
- package/dist/src/capabilities/sources/update.render.js.map +1 -0
- package/dist/src/capabilities/sources/update.verb.js +55 -0
- package/dist/src/capabilities/sources/update.verb.js.map +1 -0
- package/dist/src/capabilities/upgrade/index.js +5 -0
- package/dist/src/capabilities/upgrade/index.js.map +1 -0
- package/dist/src/capabilities/upgrade/runUpgrade.js +99 -0
- package/dist/src/capabilities/upgrade/runUpgrade.js.map +1 -0
- package/dist/src/capabilities/upgrade/types.js +5 -0
- package/dist/src/capabilities/upgrade/types.js.map +1 -0
- package/dist/src/capabilities/upgrade/upgrade.render.js +44 -0
- package/dist/src/capabilities/upgrade/upgrade.render.js.map +1 -0
- package/dist/src/capabilities/upgrade/upgrade.verb.js +52 -0
- package/dist/src/capabilities/upgrade/upgrade.verb.js.map +1 -0
- package/dist/src/capabilities/version/index.js +10 -0
- package/dist/src/capabilities/version/index.js.map +1 -0
- package/dist/src/capabilities/version/version.verb.js +36 -0
- package/dist/src/capabilities/version/version.verb.js.map +1 -0
- package/dist/src/constants.js +82 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/index.js +12 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/kernel/completion/complete.js +66 -0
- package/dist/src/kernel/completion/complete.js.map +1 -0
- package/dist/src/kernel/completion/emitScripts.js +92 -0
- package/dist/src/kernel/completion/emitScripts.js.map +1 -0
- package/dist/src/kernel/completion/entitySource.js +285 -0
- package/dist/src/kernel/completion/entitySource.js.map +1 -0
- package/dist/src/kernel/completion/index.js +14 -0
- package/dist/src/kernel/completion/index.js.map +1 -0
- package/dist/src/kernel/completion/model.js +301 -0
- package/dist/src/kernel/completion/model.js.map +1 -0
- package/dist/src/kernel/completion/parse.js +253 -0
- package/dist/src/kernel/completion/parse.js.map +1 -0
- package/dist/src/kernel/completion/rank.js +101 -0
- package/dist/src/kernel/completion/rank.js.map +1 -0
- package/dist/src/kernel/completion/resolve.js +100 -0
- package/dist/src/kernel/completion/resolve.js.map +1 -0
- package/dist/src/kernel/completion/templates/bash.js +209 -0
- package/dist/src/kernel/completion/templates/bash.js.map +1 -0
- package/dist/src/kernel/completion/templates/fish.js +214 -0
- package/dist/src/kernel/completion/templates/fish.js.map +1 -0
- package/dist/src/kernel/completion/templates/index.js +23 -0
- package/dist/src/kernel/completion/templates/index.js.map +1 -0
- package/dist/src/kernel/completion/templates/shared.js +98 -0
- package/dist/src/kernel/completion/templates/shared.js.map +1 -0
- package/dist/src/kernel/completion/templates/zsh.js +201 -0
- package/dist/src/kernel/completion/templates/zsh.js.map +1 -0
- package/dist/src/kernel/completion/types.js +13 -0
- package/dist/src/kernel/completion/types.js.map +1 -0
- package/dist/src/kernel/config/defaults.js +17 -0
- package/dist/src/kernel/config/defaults.js.map +1 -0
- package/dist/src/kernel/config/evaluateProjectConfig.js +126 -0
- package/dist/src/kernel/config/evaluateProjectConfig.js.map +1 -0
- package/dist/src/kernel/config/findProjectConfig.js +43 -0
- package/dist/src/kernel/config/findProjectConfig.js.map +1 -0
- package/dist/src/kernel/config/firstRun.js +34 -0
- package/dist/src/kernel/config/firstRun.js.map +1 -0
- package/dist/src/kernel/config/globalConfig.js +79 -0
- package/dist/src/kernel/config/globalConfig.js.map +1 -0
- package/dist/src/kernel/config/index.js +16 -0
- package/dist/src/kernel/config/index.js.map +1 -0
- package/dist/src/kernel/config/paths.js +35 -0
- package/dist/src/kernel/config/paths.js.map +1 -0
- package/dist/src/kernel/config/readConfig.js +78 -0
- package/dist/src/kernel/config/readConfig.js.map +1 -0
- package/dist/src/kernel/config/schema.js +130 -0
- package/dist/src/kernel/config/schema.js.map +1 -0
- package/dist/src/kernel/config/types.js +19 -0
- package/dist/src/kernel/config/types.js.map +1 -0
- package/dist/src/kernel/config/writeConfigField.js +59 -0
- package/dist/src/kernel/config/writeConfigField.js.map +1 -0
- package/dist/src/kernel/copy.js +43 -0
- package/dist/src/kernel/copy.js.map +1 -0
- package/dist/src/kernel/error/PragmaError.js +149 -0
- package/dist/src/kernel/error/PragmaError.js.map +1 -0
- package/dist/src/kernel/error/constants.js +40 -0
- package/dist/src/kernel/error/constants.js.map +1 -0
- package/dist/src/kernel/error/fromTaskError.js +96 -0
- package/dist/src/kernel/error/fromTaskError.js.map +1 -0
- package/dist/src/kernel/error/index.js +13 -0
- package/dist/src/kernel/error/index.js.map +1 -0
- package/dist/src/kernel/error/recovery.js +52 -0
- package/dist/src/kernel/error/recovery.js.map +1 -0
- package/dist/src/kernel/error/renderError.js +115 -0
- package/dist/src/kernel/error/renderError.js.map +1 -0
- package/dist/src/kernel/error/serialize.js +25 -0
- package/dist/src/kernel/error/serialize.js.map +1 -0
- package/dist/src/kernel/error/types.js +9 -0
- package/dist/src/kernel/error/types.js.map +1 -0
- package/dist/src/kernel/index.js +25 -0
- package/dist/src/kernel/index.js.map +1 -0
- package/dist/src/kernel/packs/collect.js +187 -0
- package/dist/src/kernel/packs/collect.js.map +1 -0
- package/dist/src/kernel/packs/compile.js +304 -0
- package/dist/src/kernel/packs/compile.js.map +1 -0
- package/dist/src/kernel/packs/disclosure.js +76 -0
- package/dist/src/kernel/packs/disclosure.js.map +1 -0
- package/dist/src/kernel/packs/glob.js +38 -0
- package/dist/src/kernel/packs/glob.js.map +1 -0
- package/dist/src/kernel/packs/graphql/buildLookupDocument.js +258 -0
- package/dist/src/kernel/packs/graphql/buildLookupDocument.js.map +1 -0
- package/dist/src/kernel/packs/graphql/fetchGraphqlLookup.js +157 -0
- package/dist/src/kernel/packs/graphql/fetchGraphqlLookup.js.map +1 -0
- package/dist/src/kernel/packs/graphql/index.js +20 -0
- package/dist/src/kernel/packs/graphql/index.js.map +1 -0
- package/dist/src/kernel/packs/graphql/nameMap.js +63 -0
- package/dist/src/kernel/packs/graphql/nameMap.js.map +1 -0
- package/dist/src/kernel/packs/index.js +34 -0
- package/dist/src/kernel/packs/index.js.map +1 -0
- package/dist/src/kernel/packs/iri.js +110 -0
- package/dist/src/kernel/packs/iri.js.map +1 -0
- package/dist/src/kernel/packs/renderPack.js +129 -0
- package/dist/src/kernel/packs/renderPack.js.map +1 -0
- package/dist/src/kernel/packs/resolveEntity.js +223 -0
- package/dist/src/kernel/packs/resolveEntity.js.map +1 -0
- package/dist/src/kernel/packs/runBodies.js +87 -0
- package/dist/src/kernel/packs/runBodies.js.map +1 -0
- package/dist/src/kernel/packs/sample.js +56 -0
- package/dist/src/kernel/packs/sample.js.map +1 -0
- package/dist/src/kernel/packs/schema.js +369 -0
- package/dist/src/kernel/packs/schema.js.map +1 -0
- package/dist/src/kernel/packs/sparql/applyFilters.js +68 -0
- package/dist/src/kernel/packs/sparql/applyFilters.js.map +1 -0
- package/dist/src/kernel/packs/sparql/applySearch.js +25 -0
- package/dist/src/kernel/packs/sparql/applySearch.js.map +1 -0
- package/dist/src/kernel/packs/sparql/buildLookupQuery.js +217 -0
- package/dist/src/kernel/packs/sparql/buildLookupQuery.js.map +1 -0
- package/dist/src/kernel/packs/sparql/escape.js +21 -0
- package/dist/src/kernel/packs/sparql/escape.js.map +1 -0
- package/dist/src/kernel/packs/sparql/index.js +31 -0
- package/dist/src/kernel/packs/sparql/index.js.map +1 -0
- package/dist/src/kernel/packs/sparql/runSelect.js +101 -0
- package/dist/src/kernel/packs/sparql/runSelect.js.map +1 -0
- package/dist/src/kernel/packs/types.js +30 -0
- package/dist/src/kernel/packs/types.js.map +1 -0
- package/dist/src/kernel/packs/uniqueness.js +30 -0
- package/dist/src/kernel/packs/uniqueness.js.map +1 -0
- package/dist/src/kernel/project/cli/buildProgram.js +263 -0
- package/dist/src/kernel/project/cli/buildProgram.js.map +1 -0
- package/dist/src/kernel/project/cli/constants.js +27 -0
- package/dist/src/kernel/project/cli/constants.js.map +1 -0
- package/dist/src/kernel/project/cli/dispatch.js +420 -0
- package/dist/src/kernel/project/cli/dispatch.js.map +1 -0
- package/dist/src/kernel/project/cli/exitCodes.js +66 -0
- package/dist/src/kernel/project/cli/exitCodes.js.map +1 -0
- package/dist/src/kernel/project/cli/globalFlags.js +216 -0
- package/dist/src/kernel/project/cli/globalFlags.js.map +1 -0
- package/dist/src/kernel/project/cli/helpFormat.js +59 -0
- package/dist/src/kernel/project/cli/helpFormat.js.map +1 -0
- package/dist/src/kernel/project/cli/index.js +14 -0
- package/dist/src/kernel/project/cli/index.js.map +1 -0
- package/dist/src/kernel/project/cli/rootHelp.js +197 -0
- package/dist/src/kernel/project/cli/rootHelp.js.map +1 -0
- package/dist/src/kernel/project/cli/suggest.js +75 -0
- package/dist/src/kernel/project/cli/suggest.js.map +1 -0
- package/dist/src/kernel/project/cli/suggestNames.js +96 -0
- package/dist/src/kernel/project/cli/suggestNames.js.map +1 -0
- package/dist/src/kernel/project/cli/verbHelp.js +131 -0
- package/dist/src/kernel/project/cli/verbHelp.js.map +1 -0
- package/dist/src/kernel/project/mcp/buildServer.js +67 -0
- package/dist/src/kernel/project/mcp/buildServer.js.map +1 -0
- package/dist/src/kernel/project/mcp/envelope.js +37 -0
- package/dist/src/kernel/project/mcp/envelope.js.map +1 -0
- package/dist/src/kernel/project/mcp/index.js +8 -0
- package/dist/src/kernel/project/mcp/index.js.map +1 -0
- package/dist/src/kernel/project/mcp/instructions.js +44 -0
- package/dist/src/kernel/project/mcp/instructions.js.map +1 -0
- package/dist/src/kernel/project/mcp/mcpError.js +42 -0
- package/dist/src/kernel/project/mcp/mcpError.js.map +1 -0
- package/dist/src/kernel/project/mcp/prompts/index.js +36 -0
- package/dist/src/kernel/project/mcp/prompts/index.js.map +1 -0
- package/dist/src/kernel/project/mcp/prompts/provider.js +98 -0
- package/dist/src/kernel/project/mcp/prompts/provider.js.map +1 -0
- package/dist/src/kernel/project/mcp/prompts/source.js +154 -0
- package/dist/src/kernel/project/mcp/prompts/source.js.map +1 -0
- package/dist/src/kernel/project/mcp/registerVerb.js +300 -0
- package/dist/src/kernel/project/mcp/registerVerb.js.map +1 -0
- package/dist/src/kernel/project/mcp/serve.js +35 -0
- package/dist/src/kernel/project/mcp/serve.js.map +1 -0
- package/dist/src/kernel/render/compactUri.js +24 -0
- package/dist/src/kernel/render/compactUri.js.map +1 -0
- package/dist/src/kernel/render/contracts.js +10 -0
- package/dist/src/kernel/render/contracts.js.map +1 -0
- package/dist/src/kernel/render/disclosure.js +34 -0
- package/dist/src/kernel/render/disclosure.js.map +1 -0
- package/dist/src/kernel/render/envelope.js +30 -0
- package/dist/src/kernel/render/envelope.js.map +1 -0
- package/dist/src/kernel/render/formatters.js +27 -0
- package/dist/src/kernel/render/formatters.js.map +1 -0
- package/dist/src/kernel/render/index.js +15 -0
- package/dist/src/kernel/render/index.js.map +1 -0
- package/dist/src/kernel/render/prefixes.js +46 -0
- package/dist/src/kernel/render/prefixes.js.map +1 -0
- package/dist/src/kernel/render/renderers.js +468 -0
- package/dist/src/kernel/render/renderers.js.map +1 -0
- package/dist/src/kernel/render/style.js +85 -0
- package/dist/src/kernel/render/style.js.map +1 -0
- package/dist/src/kernel/render/turtle.js +194 -0
- package/dist/src/kernel/render/turtle.js.map +1 -0
- package/dist/src/kernel/render/writeStdout.js +43 -0
- package/dist/src/kernel/render/writeStdout.js.map +1 -0
- package/dist/src/kernel/runtime/boot.js +49 -0
- package/dist/src/kernel/runtime/boot.js.map +1 -0
- package/dist/src/kernel/runtime/facade.js +39 -0
- package/dist/src/kernel/runtime/facade.js.map +1 -0
- package/dist/src/kernel/runtime/graphpack/build.js +122 -0
- package/dist/src/kernel/runtime/graphpack/build.js.map +1 -0
- package/dist/src/kernel/runtime/graphpack/buildIndex.js +303 -0
- package/dist/src/kernel/runtime/graphpack/buildIndex.js.map +1 -0
- package/dist/src/kernel/runtime/graphpack/embedded/pack.generated.js +6 -0
- package/dist/src/kernel/runtime/graphpack/embedded/pack.generated.js.map +1 -0
- package/dist/src/kernel/runtime/graphpack/embedded/pack.index.generated.js +4 -0
- package/dist/src/kernel/runtime/graphpack/embedded/pack.index.generated.js.map +1 -0
- package/dist/src/kernel/runtime/graphpack/embedded/pack.stories.generated.js +4 -0
- package/dist/src/kernel/runtime/graphpack/embedded/pack.stories.generated.js.map +1 -0
- package/dist/src/kernel/runtime/graphpack/embedded.js +82 -0
- package/dist/src/kernel/runtime/graphpack/embedded.js.map +1 -0
- package/dist/src/kernel/runtime/graphpack/hash.js +38 -0
- package/dist/src/kernel/runtime/graphpack/hash.js.map +1 -0
- package/dist/src/kernel/runtime/graphpack/index.js +11 -0
- package/dist/src/kernel/runtime/graphpack/index.js.map +1 -0
- package/dist/src/kernel/runtime/graphpack/manifest.js +145 -0
- package/dist/src/kernel/runtime/graphpack/manifest.js.map +1 -0
- package/dist/src/kernel/runtime/graphpack/read.js +92 -0
- package/dist/src/kernel/runtime/graphpack/read.js.map +1 -0
- package/dist/src/kernel/runtime/graphpack/schemas.js +70 -0
- package/dist/src/kernel/runtime/graphpack/schemas.js.map +1 -0
- package/dist/src/kernel/runtime/graphpack/stories.js +68 -0
- package/dist/src/kernel/runtime/graphpack/stories.js.map +1 -0
- package/dist/src/kernel/runtime/graphpack/types.js +55 -0
- package/dist/src/kernel/runtime/graphpack/types.js.map +1 -0
- package/dist/src/kernel/runtime/index.js +8 -0
- package/dist/src/kernel/runtime/index.js.map +1 -0
- package/dist/src/kernel/runtime/loadSession.js +34 -0
- package/dist/src/kernel/runtime/loadSession.js.map +1 -0
- package/dist/src/kernel/runtime/paths.js +78 -0
- package/dist/src/kernel/runtime/paths.js.map +1 -0
- package/dist/src/kernel/runtime/probe.js +37 -0
- package/dist/src/kernel/runtime/probe.js.map +1 -0
- package/dist/src/kernel/runtime/readEntity.js +363 -0
- package/dist/src/kernel/runtime/readEntity.js.map +1 -0
- package/dist/src/kernel/runtime/refs/gitOps.js +65 -0
- package/dist/src/kernel/runtime/refs/gitOps.js.map +1 -0
- package/dist/src/kernel/runtime/refs/index.js +29 -0
- package/dist/src/kernel/runtime/refs/index.js.map +1 -0
- package/dist/src/kernel/runtime/refs/parseRef.js +70 -0
- package/dist/src/kernel/runtime/refs/parseRef.js.map +1 -0
- package/dist/src/kernel/runtime/refs/resolve.js +332 -0
- package/dist/src/kernel/runtime/refs/resolve.js.map +1 -0
- package/dist/src/kernel/runtime/resolveSources.js +62 -0
- package/dist/src/kernel/runtime/resolveSources.js.map +1 -0
- package/dist/src/kernel/runtime/store.js +75 -0
- package/dist/src/kernel/runtime/store.js.map +1 -0
- package/dist/src/kernel/runtime/storeReadiness.js +51 -0
- package/dist/src/kernel/runtime/storeReadiness.js.map +1 -0
- package/dist/src/kernel/runtime/types.js +11 -0
- package/dist/src/kernel/runtime/types.js.map +1 -0
- package/dist/src/kernel/spec/asVerb.js +21 -0
- package/dist/src/kernel/spec/asVerb.js.map +1 -0
- package/dist/src/kernel/spec/emitReference.js +578 -0
- package/dist/src/kernel/spec/emitReference.js.map +1 -0
- package/dist/src/kernel/spec/emitSurface.js +202 -0
- package/dist/src/kernel/spec/emitSurface.js.map +1 -0
- package/dist/src/kernel/spec/index.js +11 -0
- package/dist/src/kernel/spec/index.js.map +1 -0
- package/dist/src/kernel/spec/surfaceConformance.js +103 -0
- package/dist/src/kernel/spec/surfaceConformance.js.map +1 -0
- package/dist/src/kernel/spec/types.js +14 -0
- package/dist/src/kernel/spec/types.js.map +1 -0
- package/dist/src/kernel/spec/validate.js +110 -0
- package/dist/src/kernel/spec/validate.js.map +1 -0
- package/dist/src/kernel/vocabulary.js +97 -0
- package/dist/src/kernel/vocabulary.js.map +1 -0
- package/dist/src/types.js +11 -0
- package/dist/src/types.js.map +1 -0
- package/package.json +20 -22
- package/pragma.conf.ts +249 -60
- package/src/bin.ts +267 -82
- package/src/capabilities/block.parity.test.ts +6 -3
- package/src/capabilities/capabilities/capabilities.render.ts +1 -1
- package/src/capabilities/capabilities/capabilities.verb.ts +1 -1
- package/src/capabilities/capabilities/catalog.ts +3 -3
- package/src/capabilities/capabilities/hints.ts +32 -7
- package/src/capabilities/capabilities/index.ts +1 -1
- package/src/capabilities/colophon/collectColophon.ts +35 -24
- package/src/capabilities/colophon/colophon.render.ts +25 -1
- package/src/capabilities/colophon/colophon.test.ts +42 -21
- package/src/capabilities/colophon/colophon.verb.ts +4 -4
- package/src/capabilities/config/collectConfigShow.test.ts +1 -0
- package/src/capabilities/config/collectConfigShow.ts +3 -3
- package/src/capabilities/config/field.render.ts +4 -5
- package/src/capabilities/config/fields.ts +17 -10
- package/src/capabilities/config/get.verb.ts +70 -0
- package/src/capabilities/config/getUnset.test.ts +264 -0
- package/src/capabilities/config/index.ts +5 -1
- package/src/capabilities/config/runField.ts +40 -22
- package/src/capabilities/config/runGet.ts +34 -0
- package/src/capabilities/config/runSet.ts +35 -9
- package/src/capabilities/config/set.test.ts +48 -14
- package/src/capabilities/config/set.verb.ts +8 -7
- package/src/capabilities/config/show.render.ts +1 -1
- package/src/capabilities/config/show.verb.ts +9 -6
- package/src/capabilities/config/types.ts +18 -4
- package/src/capabilities/config/unset.verb.ts +58 -0
- package/src/capabilities/create/byteEquality.subprocess.test.ts +2 -3
- package/src/capabilities/create/byteEquality.test.ts +25 -11
- package/src/capabilities/create/cliProjection.ts +227 -0
- package/src/capabilities/create/constants.ts +36 -61
- package/src/capabilities/create/create.mcp.test.ts +89 -5
- package/src/capabilities/create/create.render.ts +71 -7
- package/src/capabilities/create/create.test.ts +433 -219
- package/src/capabilities/create/create.verb.ts +297 -328
- package/src/capabilities/create/createGrammar.test.ts +865 -0
- package/src/capabilities/create/createHelp.ts +71 -0
- package/src/capabilities/create/createSurface.generated.ts +400 -0
- package/src/capabilities/create/crossCli.subprocess.test.ts +745 -0
- package/src/capabilities/create/cwdJail.test.ts +24 -5
- package/src/capabilities/create/generatorToVerbSpec.test.ts +186 -0
- package/src/capabilities/create/generatorToVerbSpec.ts +13 -11
- package/src/capabilities/create/index.ts +7 -4
- package/src/capabilities/create/messages.ts +18 -0
- package/src/capabilities/create/mount.ts +391 -0
- package/src/capabilities/create/pathJail.test.ts +90 -0
- package/src/capabilities/create/pathJail.ts +8 -5
- package/src/capabilities/create/pickGenerator.ts +23 -35
- package/src/capabilities/create/shippedCreate.subprocess.test.ts +164 -0
- package/src/capabilities/create/types.ts +20 -0
- package/src/capabilities/distribution.test.ts +3 -1
- package/src/capabilities/distribution.ts +10 -15
- package/src/capabilities/doctor/checks/checkKeStore.ts +7 -5
- package/src/capabilities/doctor/checks/checkPackageRefs.ts +6 -4
- package/src/capabilities/doctor/checks/checkPragmaVersion.ts +2 -2
- package/src/capabilities/doctor/checks/checkShellCompletions.test.ts +72 -27
- package/src/capabilities/doctor/checks/checkShellCompletions.ts +59 -13
- package/src/capabilities/doctor/checks/index.ts +27 -0
- package/src/capabilities/doctor/checks/mcpCommand.test.ts +100 -0
- package/src/capabilities/doctor/checks/mcpCommand.ts +69 -0
- package/src/capabilities/doctor/checks/targetHealth.ts +274 -0
- package/src/capabilities/doctor/doctor.render.test.ts +18 -7
- package/src/capabilities/doctor/doctor.render.ts +43 -19
- package/src/capabilities/doctor/doctor.test.ts +123 -34
- package/src/capabilities/doctor/doctor.verb.ts +3 -3
- package/src/capabilities/doctor/runChecks.ts +62 -63
- package/src/capabilities/doctor/types.ts +20 -4
- package/src/capabilities/emptyRecovery.test.ts +13 -8
- package/src/capabilities/graph/examples.exec.test.ts +107 -0
- package/src/capabilities/graph/index.ts +1 -1
- package/src/capabilities/graph/inspect.render.ts +70 -22
- package/src/capabilities/graph/inspect.test.ts +260 -5
- package/src/capabilities/graph/inspect.verb.ts +18 -6
- package/src/capabilities/graph/query.render.ts +15 -3
- package/src/capabilities/graph/query.verb.ts +3 -3
- package/src/capabilities/graph/runQuery.ts +2 -3
- package/src/capabilities/index.ts +3 -1
- package/src/capabilities/info/collectInfo.ts +5 -4
- package/src/capabilities/info/info.render.ts +1 -1
- package/src/capabilities/info/info.verb.ts +1 -1
- package/src/capabilities/lazy.test.ts +32 -0
- package/src/capabilities/meta/complete.verb.ts +1 -1
- package/src/capabilities/meta/index.ts +1 -1
- package/src/capabilities/meta/mcp.verb.ts +10 -8
- package/src/capabilities/ontology/index.ts +15 -11
- package/src/capabilities/ontology/ontology.test.ts +6 -55
- package/src/capabilities/ontology/queries.ts +2 -2
- package/src/capabilities/ontology/render.ts +3 -4
- package/src/capabilities/ontology/verbs.ts +27 -68
- package/src/capabilities/prompt/index.ts +2 -2
- package/src/capabilities/prompt/prompt.render.ts +6 -2
- package/src/capabilities/prompt/types.ts +1 -1
- package/src/capabilities/prompt/verbs.ts +4 -5
- package/src/capabilities/reference.test.ts +63 -0
- package/src/capabilities/resources/provider.ts +342 -85
- package/src/capabilities/resources/resources.test.ts +355 -59
- package/src/capabilities/setup/buildPlan.ts +236 -0
- package/src/capabilities/setup/index.ts +7 -7
- package/src/capabilities/setup/mode.ts +74 -0
- package/src/capabilities/setup/operations/index.ts +74 -0
- package/src/capabilities/setup/operations/setupCompletions.ts +97 -24
- package/src/capabilities/setup/operations/setupConfig.ts +124 -0
- package/src/capabilities/setup/operations/setupGenerator.ts +396 -326
- package/src/capabilities/setup/operations/setupLsp.ts +329 -68
- package/src/capabilities/setup/operations/setupMcp.ts +137 -97
- package/src/capabilities/setup/operations/setupSkills.ts +336 -108
- package/src/capabilities/setup/plan.render.ts +241 -0
- package/src/capabilities/setup/plan.ts +236 -0
- package/src/capabilities/setup/setup.mcp.correctness.test.ts +432 -0
- package/src/capabilities/setup/setup.render.test.ts +281 -73
- package/src/capabilities/setup/setup.render.ts +45 -77
- package/src/capabilities/setup/setup.test.ts +972 -209
- package/src/capabilities/setup/setup.verb.ts +223 -71
- package/src/capabilities/setup/shell.test.ts +86 -0
- package/src/capabilities/setup/shell.ts +137 -11
- package/src/capabilities/setup/targets.ts +367 -0
- package/src/capabilities/setup/types.ts +18 -57
- package/src/capabilities/shared/assertExecOk.ts +68 -10
- package/src/capabilities/shared/index.ts +32 -0
- package/src/capabilities/shared/packageManager.test.ts +55 -0
- package/src/capabilities/shared/packageManager.ts +62 -6
- package/src/capabilities/skill/discover.ts +37 -5
- package/src/capabilities/skill/index.ts +1 -1
- package/src/capabilities/skill/render.ts +10 -9
- package/src/capabilities/skill/skill.test.ts +7 -5
- package/src/capabilities/skill/verbs.ts +3 -4
- package/src/capabilities/sources/collectStatus.ts +7 -2
- package/src/capabilities/sources/index.ts +1 -1
- package/src/capabilities/sources/installSkills.ts +84 -7
- package/src/capabilities/sources/runUpdate.ts +53 -21
- package/src/capabilities/sources/sources.test.ts +154 -2
- package/src/capabilities/sources/status.render.ts +1 -1
- package/src/capabilities/sources/status.verb.ts +1 -1
- package/src/capabilities/sources/update.render.ts +1 -1
- package/src/capabilities/sources/update.verb.ts +1 -1
- package/src/capabilities/surface.test.ts +134 -17
- package/src/capabilities/upgrade/runUpgrade.ts +8 -8
- package/src/capabilities/upgrade/upgrade.render.ts +4 -5
- package/src/capabilities/upgrade/upgrade.test.ts +3 -3
- package/src/capabilities/upgrade/upgrade.verb.ts +1 -1
- package/src/capabilities/version/index.ts +12 -0
- package/src/capabilities/version/version.test.ts +66 -0
- package/src/capabilities/version/version.verb.ts +39 -0
- package/src/constants.ts +31 -1
- package/src/identity.test.ts +40 -25
- package/src/kernel/completion/__snapshots__/emitScripts.test.ts.snap +15 -13
- package/src/kernel/completion/complete.test.ts +12 -2
- package/src/kernel/completion/complete.ts +1 -1
- package/src/kernel/completion/emitScripts.ts +1 -1
- package/src/kernel/completion/entitySource.test.ts +1 -1
- package/src/kernel/completion/entitySource.ts +21 -8
- package/src/kernel/completion/model.test.ts +164 -12
- package/src/kernel/completion/model.ts +94 -14
- package/src/kernel/completion/parse.test.ts +4 -7
- package/src/kernel/completion/parse.ts +15 -0
- package/src/kernel/completion/resolve.test.ts +0 -1
- package/src/kernel/completion/safety.test.ts +34 -26
- package/src/kernel/completion/shellDrive.test.ts +28 -9
- package/src/kernel/completion/templates/bash.ts +7 -2
- package/src/kernel/completion/templates/index.ts +23 -0
- package/src/kernel/completion/types.ts +7 -0
- package/src/kernel/config/defaults.test.ts +25 -2
- package/src/kernel/config/defaults.ts +4 -3
- package/src/kernel/config/evaluateProjectConfig.ts +31 -8
- package/src/kernel/config/firstRun.test.ts +44 -38
- package/src/kernel/config/firstRun.ts +24 -88
- package/src/kernel/config/index.ts +1 -1
- package/src/kernel/config/readConfig.test.ts +6 -3
- package/src/kernel/config/readConfig.ts +1 -1
- package/src/kernel/config/schema.ts +3 -1
- package/src/kernel/config/types.ts +3 -1
- package/src/kernel/copy.test.ts +34 -4
- package/src/kernel/copy.ts +54 -0
- package/src/kernel/error/PragmaError.test.ts +5 -2
- package/src/kernel/error/PragmaError.ts +4 -2
- package/src/kernel/error/constants.ts +7 -5
- package/src/kernel/error/fromTaskError.ts +28 -6
- package/src/kernel/error/recovery.ts +14 -0
- package/src/kernel/error/renderError.ts +42 -1
- package/src/kernel/error/types.ts +21 -1
- package/src/kernel/index.ts +26 -0
- package/src/kernel/packs/collect.ts +12 -16
- package/src/kernel/packs/compile.test.ts +130 -1
- package/src/kernel/packs/compile.ts +93 -3
- package/src/kernel/packs/disclosure.ts +2 -2
- package/src/kernel/packs/graphql/buildLookupDocument.ts +1 -1
- package/src/kernel/packs/graphql/fetchGraphqlLookup.ts +3 -4
- package/src/kernel/packs/graphql/index.ts +20 -0
- package/src/kernel/packs/index.ts +40 -0
- package/src/kernel/packs/iri.test.ts +73 -0
- package/src/kernel/packs/iri.ts +66 -14
- package/src/kernel/packs/renderPack.ts +24 -5
- package/src/kernel/packs/resolveEntity.test.ts +213 -0
- package/src/kernel/packs/resolveEntity.ts +112 -18
- package/src/kernel/packs/runBodies.ts +2 -3
- package/src/kernel/packs/sample.ts +1 -1
- package/src/kernel/packs/schema.ts +18 -1
- package/src/kernel/packs/sparql/applyFilters.ts +16 -17
- package/src/kernel/packs/sparql/buildLookupQuery.ts +75 -3
- package/src/kernel/packs/sparql/index.ts +37 -0
- package/src/kernel/packs/sparql/runSelect.test.ts +1 -1
- package/src/kernel/packs/sparql/runSelect.ts +3 -4
- package/src/kernel/packs/toolDescription.test.ts +42 -3
- package/src/kernel/packs/types.ts +13 -0
- package/src/kernel/packs/uniqueness.ts +1 -1
- package/src/kernel/project/cli/buildProgram.test.ts +147 -12
- package/src/kernel/project/cli/buildProgram.ts +77 -18
- package/src/kernel/project/cli/constants.ts +34 -0
- package/src/kernel/project/cli/dispatch.test.ts +160 -3
- package/src/kernel/project/cli/dispatch.ts +222 -42
- package/src/kernel/project/cli/exitCodes.ts +1 -1
- package/src/kernel/project/cli/globalFlags.test.ts +117 -1
- package/src/kernel/project/cli/globalFlags.ts +130 -30
- package/src/kernel/project/cli/helpFormat.ts +13 -0
- package/src/kernel/project/cli/rootHelp.ts +41 -12
- package/src/kernel/project/cli/suggest.test.ts +33 -1
- package/src/kernel/project/cli/suggest.ts +25 -1
- package/src/kernel/project/cli/verbHelp.test.ts +30 -0
- package/src/kernel/project/cli/verbHelp.ts +23 -7
- package/src/kernel/project/mcp/buildServer.ts +7 -4
- package/src/kernel/project/mcp/envelope.ts +2 -2
- package/src/kernel/project/mcp/instructions.ts +2 -2
- package/src/kernel/project/mcp/mcpError.ts +1 -1
- package/src/kernel/project/mcp/parity.test.ts +5 -2
- package/src/kernel/project/mcp/prompts/index.ts +37 -0
- package/src/kernel/project/mcp/prompts/source.ts +2 -2
- package/src/kernel/project/mcp/registerVerb.ts +35 -9
- package/src/kernel/project/mcp/serve.ts +2 -2
- package/src/kernel/render/__snapshots__/renderers.test.ts.snap +7 -4
- package/src/kernel/render/contracts.ts +21 -0
- package/src/kernel/render/envelope.ts +2 -3
- package/src/kernel/render/formatters.ts +1 -1
- package/src/kernel/render/renderers.test.ts +243 -6
- package/src/kernel/render/renderers.ts +222 -20
- package/src/kernel/render/style.test.ts +75 -24
- package/src/kernel/render/style.ts +31 -1
- package/src/kernel/render/turtle.ts +222 -0
- package/src/kernel/runtime/graphpack/buildIndex.ts +1 -1
- package/src/kernel/runtime/graphpack/embedded/pack.generated.ts +3 -3
- package/src/kernel/runtime/graphpack/embedded/pack.index.generated.ts +1 -1
- package/src/kernel/runtime/graphpack/embedded.ts +3 -3
- package/src/kernel/runtime/graphpack/manifest.ts +1 -1
- package/src/kernel/runtime/graphpack/read.ts +1 -2
- package/src/kernel/runtime/loadSession.ts +1 -1
- package/src/kernel/runtime/probe.ts +6 -6
- package/src/kernel/runtime/readEntity.ts +472 -28
- package/src/kernel/runtime/refs/index.ts +39 -0
- package/src/kernel/runtime/refs/parseRef.ts +2 -2
- package/src/kernel/runtime/refs/resolve.ts +4 -4
- package/src/kernel/runtime/storeReadiness.ts +1 -2
- package/src/kernel/runtime/types.ts +15 -1
- package/src/kernel/runtime/wasmEmbed.test.ts +37 -39
- package/src/kernel/spec/emitReference.ts +105 -30
- package/src/kernel/spec/emitSurface.ts +48 -6
- package/src/kernel/spec/surfaceConformance.test.ts +4 -2
- package/src/kernel/spec/types.ts +248 -4
- package/src/testing/behavioral/docExamples.test.ts +14 -0
- package/src/testing/behavioral/errorMatrix.mcp.test.ts +2 -2
- package/src/testing/behavioral/firstRun.e2e.test.ts +24 -24
- package/src/testing/behavioral/journeys.cli.test.ts +1 -1
- package/src/testing/behavioral/journeys.defaultPack.test.ts +7 -7
- package/src/testing/behavioral/journeys.implementation.test.ts +206 -0
- package/src/testing/behavioral/journeys.interactiveTty.e2e.test.ts +2 -2
- package/src/testing/behavioral/journeys.packageStories.test.ts +4 -4
- package/src/testing/behavioral/mcpServe.e2e.test.ts +57 -2
- package/src/testing/behavioral/mcpSurface.test.ts +1 -1
- package/src/testing/behavioral/rootCli.e2e.test.ts +26 -2
- package/src/testing/eval/cases/effectful.ts +3 -2
- package/src/testing/fixtures/blockGraph.ts +79 -0
- package/src/testing/fixtures/graph/canonical.ts +88 -2
- package/src/testing/fixtures/graph/defaultPack.ts +1 -1
- package/src/testing/helpers/blankCanonicalRanges.ts +36 -0
- package/src/testing/helpers/projectMcp.ts +32 -6
- package/src/testing/helpers/runCli.ts +35 -18
- package/src/testing/perf/budgets.test.ts +20 -9
- package/src/testing/perf/budgets.ts +58 -9
- package/src/testing/perf/codegen.test.ts +126 -0
- package/src/testing/perf/globalSetup.test.ts +286 -0
- package/src/testing/perf/globalSetup.ts +505 -23
- package/src/testing/perf/measure.test.ts +38 -0
- package/src/testing/perf/measure.ts +23 -4
- package/src/testing/setupXdgIsolation.ts +81 -21
- package/src/testing/tempRoot.globalSetup.ts +122 -0
- package/dist/pragma +0 -0
- package/src/capabilities/create/compiledCreate.subprocess.test.ts +0 -189
- package/src/capabilities/create/templates.embedded.generated.ts +0 -28
- package/src/capabilities/doctor/checks/checkConfigFile.ts +0 -42
- package/src/capabilities/doctor/checks/checkMcpCommands.ts +0 -114
- package/src/capabilities/doctor/checks/checkMcpConfigured.ts +0 -68
- package/src/capabilities/doctor/checks/checkSkillsSymlinked.ts +0 -61
- package/src/capabilities/doctor/checks/deriveBand.test.ts +0 -42
- package/src/capabilities/doctor/checks/deriveBand.ts +0 -28
- package/src/kernel/config/__snapshots__/firstRun.test.ts.snap +0 -11
|
@@ -15,18 +15,23 @@
|
|
|
15
15
|
|
|
16
16
|
import {
|
|
17
17
|
existsSync,
|
|
18
|
+
lstatSync,
|
|
18
19
|
mkdirSync,
|
|
19
20
|
mkdtempSync,
|
|
20
21
|
readFileSync,
|
|
22
|
+
readlinkSync,
|
|
21
23
|
rmSync,
|
|
24
|
+
statSync,
|
|
25
|
+
symlinkSync,
|
|
22
26
|
writeFileSync,
|
|
23
27
|
} from "node:fs";
|
|
24
28
|
import { tmpdir } from "node:os";
|
|
25
|
-
import { join } from "node:path";
|
|
29
|
+
import { join, relative } from "node:path";
|
|
26
30
|
import { execute } from "@canonical/summon-core";
|
|
27
31
|
import { dryRun, type Effect, type Task } from "@canonical/task";
|
|
28
|
-
import { runTask } from "@canonical/task/node";
|
|
29
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
32
|
+
import { runTask, runUndo } from "@canonical/task/node";
|
|
33
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
34
|
+
import { BIN_NAME } from "../../constants.js";
|
|
30
35
|
import { emitScripts } from "../../kernel/completion/emitScripts.js";
|
|
31
36
|
import { asPragmaError } from "../../kernel/error/fromTaskError.js";
|
|
32
37
|
import { executeVerb } from "../../kernel/project/cli/dispatch.js";
|
|
@@ -36,10 +41,16 @@ import type { VerbSpec } from "../../kernel/spec/types.js";
|
|
|
36
41
|
import { projectCli } from "../../testing/helpers/projectCli.js";
|
|
37
42
|
import { projectMcp } from "../../testing/helpers/projectMcp.js";
|
|
38
43
|
import { capabilities } from "../index.js";
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
44
|
+
import { detectCompletions } from "./operations/setupCompletions.js";
|
|
45
|
+
import { buildSetupRun } from "./operations/setupGenerator.js";
|
|
46
|
+
import {
|
|
47
|
+
composeSkills,
|
|
48
|
+
composeSkillsRemoval,
|
|
49
|
+
detectSkills,
|
|
50
|
+
ownedSkillLinks,
|
|
51
|
+
} from "./operations/setupSkills.js";
|
|
41
52
|
import { setupModule } from "./setup.verb.js";
|
|
42
|
-
import { completionScriptPath } from "./shell.js";
|
|
53
|
+
import { completionScriptPath, detectShell, type ShellId } from "./shell.js";
|
|
43
54
|
|
|
44
55
|
const FLAGS: GlobalFlags = {
|
|
45
56
|
llm: false,
|
|
@@ -63,85 +74,72 @@ const tmp = (prefix: string): string => {
|
|
|
63
74
|
return dir;
|
|
64
75
|
};
|
|
65
76
|
|
|
77
|
+
/**
|
|
78
|
+
* The shell this test process is actually running in.
|
|
79
|
+
*
|
|
80
|
+
* Detection reads the PROCESS TREE now, not `$SHELL` — `$SHELL` is the login
|
|
81
|
+
* shell and forcing it proved nothing about the shell in use. So the
|
|
82
|
+
* completions cases assert against whatever shell really started the run, and
|
|
83
|
+
* stand down where that cannot be established (a CI runner with no shell
|
|
84
|
+
* ancestor), rather than asserting a shell nobody is in.
|
|
85
|
+
*/
|
|
86
|
+
const DETECTED = detectShell();
|
|
87
|
+
const SHELL = DETECTED.kind === "detected" ? DETECTED.shell : null;
|
|
88
|
+
const withShell = it.skipIf(SHELL === null);
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* A PATH directory carrying a `pragma` stub. The completions target refuses to
|
|
92
|
+
* install a script whose `pragma __complete` cannot be found — an inert script
|
|
93
|
+
* is worse than none — so every PATH these tests build has to satisfy that.
|
|
94
|
+
*/
|
|
95
|
+
const stubPath = (): string => {
|
|
96
|
+
const dir = tmp("pragma-setup-path-");
|
|
97
|
+
writeFileSync(join(dir, "pragma"), "");
|
|
98
|
+
return dir;
|
|
99
|
+
};
|
|
100
|
+
|
|
66
101
|
let prevHome: string | undefined;
|
|
67
|
-
let
|
|
102
|
+
let prevPath: string | undefined;
|
|
68
103
|
beforeEach(() => {
|
|
69
104
|
prevHome = process.env.HOME;
|
|
70
|
-
|
|
105
|
+
prevPath = process.env.PATH;
|
|
71
106
|
process.env.HOME = tmp("pragma-setup-home-");
|
|
72
|
-
process.env.
|
|
107
|
+
process.env.PATH = stubPath();
|
|
73
108
|
});
|
|
74
109
|
afterEach(() => {
|
|
75
110
|
process.env.HOME = prevHome;
|
|
76
|
-
process.env.
|
|
111
|
+
process.env.PATH = prevPath;
|
|
77
112
|
for (const dir of roots) rmSync(dir, { recursive: true, force: true });
|
|
78
113
|
roots.length = 0;
|
|
79
114
|
});
|
|
80
115
|
|
|
81
116
|
describe("setup completions", () => {
|
|
82
|
-
it("
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
expect(existsSync(path)).toBe(true);
|
|
92
|
-
expect(readFileSync(path, "utf-8")).toBe(emitScripts(capabilities).zsh);
|
|
93
|
-
});
|
|
117
|
+
it("sees a Windows `.cmd` shim, so the row does not skip on Windows", async () => {
|
|
118
|
+
// npm installs this CLI as `pragma.cmd` on Windows and never as a bare
|
|
119
|
+
// name, so joining the name onto each PATH directory found nothing: the
|
|
120
|
+
// row skipped with "not on PATH" on a machine where the command runs.
|
|
121
|
+
// Spelled to match a default `PATHEXT` entry exactly: this test runs on a
|
|
122
|
+
// case-sensitive filesystem, while Windows matches `pragma.cmd` against
|
|
123
|
+
// `.CMD` case-insensitively.
|
|
124
|
+
const dir = tmp("pragma-setup-winpath-");
|
|
125
|
+
writeFileSync(join(dir, `${BIN_NAME}.CMD`), "");
|
|
94
126
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
);
|
|
105
|
-
const path = completionScriptPath("zsh");
|
|
106
|
-
const outcome = await executeVerb(
|
|
107
|
-
completionsVerb,
|
|
108
|
-
{},
|
|
109
|
-
YES,
|
|
110
|
-
bootRuntime(FLAGS, cwd),
|
|
111
|
-
);
|
|
112
|
-
expect(outcome.exitCode).toBe(0);
|
|
113
|
-
const written = readFileSync(path, "utf-8");
|
|
114
|
-
// The installed script is EXACTLY the config-baked emit (minChars 5, the
|
|
115
|
-
// `block` family scrubbed) — the config was threaded end to end.
|
|
116
|
-
expect(written).toBe(
|
|
117
|
-
emitScripts(capabilities, {
|
|
118
|
-
minChars: 5,
|
|
119
|
-
disabledFamilies: ["block"],
|
|
120
|
-
}).zsh,
|
|
121
|
-
);
|
|
122
|
-
// Both knobs actually moved the output: it differs from the default emit
|
|
123
|
-
// (minChars), and from the minChars-only emit (the family opt-out).
|
|
124
|
-
expect(written).not.toBe(emitScripts(capabilities).zsh);
|
|
125
|
-
expect(written).not.toBe(emitScripts(capabilities, { minChars: 5 }).zsh);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it("--dry-run previews the write against the detected shell, writing nothing", async () => {
|
|
129
|
-
const path = completionScriptPath("zsh");
|
|
130
|
-
const outcome = await executeVerb(
|
|
131
|
-
completionsVerb,
|
|
132
|
-
{},
|
|
133
|
-
DRY,
|
|
134
|
-
bootRuntime(FLAGS, tmp("pragma-setup-proj-")),
|
|
127
|
+
const detected = await detectCompletions(
|
|
128
|
+
dir,
|
|
129
|
+
{ kind: "unknown" },
|
|
130
|
+
{
|
|
131
|
+
platform: "win32",
|
|
132
|
+
env: { PATH: dir },
|
|
133
|
+
home: dir,
|
|
134
|
+
isWsl: false,
|
|
135
|
+
},
|
|
135
136
|
);
|
|
136
|
-
|
|
137
|
-
expect(
|
|
138
|
-
// The confirm gate / answer prompts are never part of a plan.
|
|
139
|
-
expect(outcome.stdout).not.toContain("Prompt");
|
|
140
|
-
expect(existsSync(path)).toBe(false);
|
|
137
|
+
|
|
138
|
+
expect(detected.binOnPath).toBe(true);
|
|
141
139
|
});
|
|
142
140
|
|
|
143
|
-
|
|
144
|
-
|
|
141
|
+
withShell("writes exactly emitScripts(capabilities)[shell]", async () => {
|
|
142
|
+
const path = completionScriptPath(SHELL as ShellId);
|
|
145
143
|
const outcome = await executeVerb(
|
|
146
144
|
completionsVerb,
|
|
147
145
|
{},
|
|
@@ -149,11 +147,94 @@ describe("setup completions", () => {
|
|
|
149
147
|
bootRuntime(FLAGS, tmp("pragma-setup-proj-")),
|
|
150
148
|
);
|
|
151
149
|
expect(outcome.exitCode).toBe(0);
|
|
152
|
-
expect(existsSync(
|
|
150
|
+
expect(existsSync(path)).toBe(true);
|
|
151
|
+
expect(readFileSync(path, "utf-8")).toBe(
|
|
152
|
+
emitScripts(capabilities)[SHELL as ShellId],
|
|
153
|
+
);
|
|
153
154
|
});
|
|
154
155
|
|
|
155
|
-
|
|
156
|
-
|
|
156
|
+
withShell(
|
|
157
|
+
"bakes the completion config (minChars + per-family opt-out) into the emitted script",
|
|
158
|
+
async () => {
|
|
159
|
+
// Proves the reconciled `detectCompletions(cwd)` reads `completion` config
|
|
160
|
+
// and threads {minChars, disabledFamilies} into `emitScripts` — a silent
|
|
161
|
+
// regression if the fold had dropped autocomplete's config-threading for
|
|
162
|
+
// setup's argument-free detect.
|
|
163
|
+
const cwd = tmp("pragma-setup-proj-");
|
|
164
|
+
writeFileSync(
|
|
165
|
+
join(cwd, "pragma.config.ts"),
|
|
166
|
+
"export default { completion: { minChars: 5, families: { block: false } } };\n",
|
|
167
|
+
);
|
|
168
|
+
const path = completionScriptPath(SHELL as ShellId);
|
|
169
|
+
const outcome = await executeVerb(
|
|
170
|
+
completionsVerb,
|
|
171
|
+
{},
|
|
172
|
+
YES,
|
|
173
|
+
bootRuntime(FLAGS, cwd),
|
|
174
|
+
);
|
|
175
|
+
expect(outcome.exitCode).toBe(0);
|
|
176
|
+
const written = readFileSync(path, "utf-8");
|
|
177
|
+
// The installed script is EXACTLY the config-baked emit (minChars 5, the
|
|
178
|
+
// `block` family scrubbed) — the config was threaded end to end.
|
|
179
|
+
expect(written).toBe(
|
|
180
|
+
emitScripts(capabilities, {
|
|
181
|
+
minChars: 5,
|
|
182
|
+
disabledFamilies: ["block"],
|
|
183
|
+
})[SHELL as ShellId],
|
|
184
|
+
);
|
|
185
|
+
// Both knobs actually moved the output: it differs from the default emit
|
|
186
|
+
// (minChars), and from the minChars-only emit (the family opt-out).
|
|
187
|
+
expect(written).not.toBe(emitScripts(capabilities)[SHELL as ShellId]);
|
|
188
|
+
expect(written).not.toBe(
|
|
189
|
+
emitScripts(capabilities, { minChars: 5 })[SHELL as ShellId],
|
|
190
|
+
);
|
|
191
|
+
},
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
withShell(
|
|
195
|
+
"--dry-run previews the write against the detected shell, writing nothing",
|
|
196
|
+
async () => {
|
|
197
|
+
const path = completionScriptPath(SHELL as ShellId);
|
|
198
|
+
const outcome = await executeVerb(
|
|
199
|
+
completionsVerb,
|
|
200
|
+
{},
|
|
201
|
+
DRY,
|
|
202
|
+
bootRuntime(FLAGS, tmp("pragma-setup-proj-")),
|
|
203
|
+
);
|
|
204
|
+
// The dry-run renders the PLAN through the verb's formatPlan seam, not the
|
|
205
|
+
// kernel's raw effect dump: one row, the shell it detected, and the script
|
|
206
|
+
// path rendered against the header's roots instead of repeated absolutely.
|
|
207
|
+
expect(outcome.stdout).toContain("Setup plan — global band");
|
|
208
|
+
expect(outcome.stdout).toMatch(
|
|
209
|
+
new RegExp(`completions\\s+install\\s+${SHELL} →`),
|
|
210
|
+
);
|
|
211
|
+
// The confirm gate / answer prompts are never part of a plan.
|
|
212
|
+
expect(outcome.stdout).not.toContain("Prompt");
|
|
213
|
+
expect(existsSync(path)).toBe(false);
|
|
214
|
+
},
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
withShell(
|
|
218
|
+
"refuses to install a script whose `pragma` the shell cannot find",
|
|
219
|
+
async () => {
|
|
220
|
+
// The script delegates every name context to `pragma __complete`. With
|
|
221
|
+
// the binary unreachable the file installs cleanly, doctor goes green,
|
|
222
|
+
// and TAB does nothing — so this is a named skip, not a silent write.
|
|
223
|
+
process.env.PATH = tmp("pragma-setup-nobin-"); // no `pragma` in it
|
|
224
|
+
const outcome = await executeVerb(
|
|
225
|
+
completionsVerb,
|
|
226
|
+
{},
|
|
227
|
+
YES,
|
|
228
|
+
bootRuntime(FLAGS, tmp("pragma-setup-proj-")),
|
|
229
|
+
);
|
|
230
|
+
expect(outcome.exitCode).toBe(0);
|
|
231
|
+
expect(outcome.stdout).toContain("cannot find on PATH");
|
|
232
|
+
expect(existsSync(completionScriptPath(SHELL as ShellId))).toBe(false);
|
|
233
|
+
},
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
withShell("--undo reverses the write", async () => {
|
|
237
|
+
const path = completionScriptPath(SHELL as ShellId);
|
|
157
238
|
const cwd = tmp("pragma-setup-proj-");
|
|
158
239
|
await executeVerb(completionsVerb, {}, YES, bootRuntime(FLAGS, cwd));
|
|
159
240
|
expect(existsSync(path)).toBe(true);
|
|
@@ -168,9 +249,11 @@ describe("setup mcp — recap gate", () => {
|
|
|
168
249
|
mkdirSync(join(cwd, ".cursor"), { recursive: true }); // makes Cursor detected
|
|
169
250
|
const configPath = join(cwd, ".cursor", "mcp.json");
|
|
170
251
|
|
|
252
|
+
// `--local`: the project band is opt-in now, so a bare `setup mcp` would
|
|
253
|
+
// write the HOME config and leave this repository untouched.
|
|
171
254
|
const outcome = await executeVerb(
|
|
172
255
|
verbOf("mcp"),
|
|
173
|
-
{},
|
|
256
|
+
{ local: true },
|
|
174
257
|
YES,
|
|
175
258
|
bootRuntime(FLAGS, cwd),
|
|
176
259
|
);
|
|
@@ -185,7 +268,7 @@ describe("setup mcp — recap gate", () => {
|
|
|
185
268
|
mkdirSync(join(cwd, ".cursor"), { recursive: true });
|
|
186
269
|
const configPath = join(cwd, ".cursor", "mcp.json");
|
|
187
270
|
|
|
188
|
-
const { generator } = await
|
|
271
|
+
const { generator } = await buildSetupRun(
|
|
189
272
|
bootRuntime(FLAGS, cwd),
|
|
190
273
|
"mcp",
|
|
191
274
|
"both",
|
|
@@ -207,7 +290,7 @@ describe("setup mcp — scope & dedup", () => {
|
|
|
207
290
|
let prevPath: string | undefined;
|
|
208
291
|
beforeEach(() => {
|
|
209
292
|
prevPath = process.env.PATH;
|
|
210
|
-
process.env.PATH =
|
|
293
|
+
process.env.PATH = stubPath();
|
|
211
294
|
});
|
|
212
295
|
afterEach(() => {
|
|
213
296
|
process.env.PATH = prevPath;
|
|
@@ -230,7 +313,7 @@ describe("setup mcp — scope & dedup", () => {
|
|
|
230
313
|
writeFileSync(join(clineExt, "package.json"), "{}");
|
|
231
314
|
const outcome = await executeVerb(
|
|
232
315
|
verbOf("mcp"),
|
|
233
|
-
{},
|
|
316
|
+
{ local: true },
|
|
234
317
|
YES,
|
|
235
318
|
bootRuntime(FLAGS, cwd),
|
|
236
319
|
);
|
|
@@ -317,13 +400,93 @@ describe("setup mcp — scope & dedup", () => {
|
|
|
317
400
|
).toBe(true);
|
|
318
401
|
expect(existsSync(join(cwd, ".cursor", "mcp.json"))).toBe(false);
|
|
319
402
|
});
|
|
403
|
+
|
|
404
|
+
it("a GLOBAL entry omits cwd — registrations from two directories are byte-identical", async () => {
|
|
405
|
+
// A per-user server must not be pinned to whatever directory `setup mcp
|
|
406
|
+
// --global` happened to run from (a registration made from ~/Downloads
|
|
407
|
+
// used to serve ~/Downloads forever, and re-running from repo B flipped
|
|
408
|
+
// the machine band to repo B).
|
|
409
|
+
const home = process.env.HOME ?? "";
|
|
410
|
+
const configPath = join(home, ".codeium", "windsurf", "mcp_config.json");
|
|
411
|
+
const register = async (cwd: string): Promise<string> => {
|
|
412
|
+
mkdirSync(join(cwd, ".windsurf"), { recursive: true });
|
|
413
|
+
await executeVerb(
|
|
414
|
+
verbOf("mcp"),
|
|
415
|
+
{ global: true },
|
|
416
|
+
YES,
|
|
417
|
+
bootRuntime(FLAGS, cwd),
|
|
418
|
+
);
|
|
419
|
+
return readFileSync(configPath, "utf-8");
|
|
420
|
+
};
|
|
421
|
+
const fromA = await register(tmp("pragma-setup-projA-"));
|
|
422
|
+
const fromB = await register(tmp("pragma-setup-projB-"));
|
|
423
|
+
expect(fromA).toBe(fromB);
|
|
424
|
+
const entry = JSON.parse(fromA).mcpServers?.pragma;
|
|
425
|
+
expect(entry).toEqual({ command: "pragma", args: ["mcp", "serve"] });
|
|
426
|
+
expect(entry).not.toHaveProperty("cwd");
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it("a stale cwd-pinned GLOBAL entry converges: drifted once, configured after", async () => {
|
|
430
|
+
const home = process.env.HOME ?? "";
|
|
431
|
+
const configPath = join(home, ".codeium", "windsurf", "mcp_config.json");
|
|
432
|
+
mkdirSync(join(home, ".codeium", "windsurf"), { recursive: true });
|
|
433
|
+
writeFileSync(
|
|
434
|
+
configPath,
|
|
435
|
+
JSON.stringify({
|
|
436
|
+
mcpServers: {
|
|
437
|
+
pragma: {
|
|
438
|
+
command: "pragma",
|
|
439
|
+
args: ["mcp"],
|
|
440
|
+
cwd: "/home/u/Downloads",
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
}),
|
|
444
|
+
);
|
|
445
|
+
const cwd = tmp("pragma-setup-proj-");
|
|
446
|
+
mkdirSync(join(cwd, ".windsurf"), { recursive: true });
|
|
447
|
+
const rt = bootRuntime(FLAGS, cwd);
|
|
448
|
+
const { detectMcp, mcpGroupState } = await import(
|
|
449
|
+
"./operations/setupMcp.js"
|
|
450
|
+
);
|
|
451
|
+
// The pinned entry reads as drift (the omitted cwd is a controlled field).
|
|
452
|
+
const before = await detectMcp(rt, "global");
|
|
453
|
+
expect(mcpGroupState(before, configPath)).toBe("drifted");
|
|
454
|
+
// One write converges it...
|
|
455
|
+
await executeVerb(
|
|
456
|
+
verbOf("mcp"),
|
|
457
|
+
{ global: true },
|
|
458
|
+
YES,
|
|
459
|
+
bootRuntime(FLAGS, cwd),
|
|
460
|
+
);
|
|
461
|
+
expect(
|
|
462
|
+
JSON.parse(readFileSync(configPath, "utf-8")).mcpServers.pragma,
|
|
463
|
+
).toEqual({ command: "pragma", args: ["mcp", "serve"] });
|
|
464
|
+
// ...and it stays `configured` afterwards (no churn on every run).
|
|
465
|
+
const after = await detectMcp(bootRuntime(FLAGS, cwd), "global");
|
|
466
|
+
expect(mcpGroupState(after, configPath)).toBe("configured");
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it("a PROJECT entry still records the project root as cwd", async () => {
|
|
470
|
+
const cwd = tmp("pragma-setup-proj-");
|
|
471
|
+
mkdirSync(join(cwd, ".cursor"), { recursive: true });
|
|
472
|
+
await executeVerb(
|
|
473
|
+
verbOf("mcp"),
|
|
474
|
+
{ local: true },
|
|
475
|
+
YES,
|
|
476
|
+
bootRuntime(FLAGS, cwd),
|
|
477
|
+
);
|
|
478
|
+
const entry = JSON.parse(
|
|
479
|
+
readFileSync(join(cwd, ".cursor", "mcp.json"), "utf-8"),
|
|
480
|
+
).mcpServers?.pragma;
|
|
481
|
+
expect(entry.cwd).toBe(cwd);
|
|
482
|
+
});
|
|
320
483
|
});
|
|
321
484
|
|
|
322
485
|
describe("setup mcp — customize opt-in gate (Item 6)", () => {
|
|
323
486
|
it("gates the per-file multiselect behind an explicit customize=true", async () => {
|
|
324
487
|
const cwd = tmp("pragma-setup-proj-");
|
|
325
488
|
mkdirSync(join(cwd, ".cursor"), { recursive: true }); // ⇒ one MCP target group
|
|
326
|
-
const { generator } = await
|
|
489
|
+
const { generator } = await buildSetupRun(
|
|
327
490
|
bootRuntime(FLAGS, cwd),
|
|
328
491
|
"mcp",
|
|
329
492
|
"both",
|
|
@@ -339,20 +502,134 @@ describe("setup mcp — customize opt-in gate (Item 6)", () => {
|
|
|
339
502
|
expect(targets?.when?.({ customize: false })).toBe(false);
|
|
340
503
|
expect(targets?.when?.({ customize: true })).toBe(true);
|
|
341
504
|
});
|
|
505
|
+
|
|
506
|
+
it("recaps the files the run KEPT, not the ones it offered", async () => {
|
|
507
|
+
// The note counted every child of the plan row, so narrowing two MCP files
|
|
508
|
+
// down to one still recapped `2 added`: the plan and the result described
|
|
509
|
+
// different work.
|
|
510
|
+
const cwd = tmp("pragma-setup-proj-");
|
|
511
|
+
mkdirSync(join(cwd, ".cursor"), { recursive: true });
|
|
512
|
+
mkdirSync(join(cwd, ".gemini"), { recursive: true });
|
|
513
|
+
const run = await buildSetupRun(bootRuntime(FLAGS, cwd), "mcp", "project");
|
|
514
|
+
const row = run.plan.rows.find((r) => r.target === "mcp");
|
|
515
|
+
expect(row?.children?.length).toBe(2);
|
|
516
|
+
const kept = row?.children?.[0]?.key as string;
|
|
517
|
+
|
|
518
|
+
const applied = run.applied({
|
|
519
|
+
targets: ["project:mcp"],
|
|
520
|
+
customize: true,
|
|
521
|
+
mcpTargets: [kept],
|
|
522
|
+
});
|
|
523
|
+
expect(applied.rows.find((r) => r.target === "mcp")?.outcome?.note).toBe(
|
|
524
|
+
"1 added",
|
|
525
|
+
);
|
|
526
|
+
});
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
describe("setup lsp — per-editor multiselect (child rows)", () => {
|
|
530
|
+
let prevPath: string | undefined;
|
|
531
|
+
let stubDir = "";
|
|
532
|
+
beforeEach(() => {
|
|
533
|
+
prevPath = process.env.PATH;
|
|
534
|
+
stubDir = stubPath();
|
|
535
|
+
// Two VS Code forks on PATH, neither carrying the extension.
|
|
536
|
+
writeFileSync(join(stubDir, "code"), "");
|
|
537
|
+
writeFileSync(join(stubDir, "codium"), "");
|
|
538
|
+
process.env.PATH = stubDir;
|
|
539
|
+
});
|
|
540
|
+
afterEach(() => {
|
|
541
|
+
process.env.PATH = prevPath;
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
it("offers one child per detected editor, like the mcp row offers files", async () => {
|
|
545
|
+
const { plan } = await buildSetupRun(
|
|
546
|
+
bootRuntime(FLAGS, tmp("pragma-setup-proj-")),
|
|
547
|
+
"lsp",
|
|
548
|
+
"global",
|
|
549
|
+
);
|
|
550
|
+
const row = plan.rows.find((r) => r.target === "lsp");
|
|
551
|
+
expect(row?.action).toBe("install");
|
|
552
|
+
expect(row?.children?.map((c) => c.key)).toEqual(["code", "codium"]);
|
|
553
|
+
expect(row?.children?.every((c) => c.action === "add")).toBe(true);
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
it("marks an editor that already has the extension unchanged", async () => {
|
|
557
|
+
mkdirSync(
|
|
558
|
+
join(
|
|
559
|
+
process.env.HOME ?? "",
|
|
560
|
+
".vscode",
|
|
561
|
+
"extensions",
|
|
562
|
+
"canonical.terrazzo-lsp-extension-1.2.3",
|
|
563
|
+
),
|
|
564
|
+
{ recursive: true },
|
|
565
|
+
);
|
|
566
|
+
const { plan } = await buildSetupRun(
|
|
567
|
+
bootRuntime(FLAGS, tmp("pragma-setup-proj-")),
|
|
568
|
+
"lsp",
|
|
569
|
+
"global",
|
|
570
|
+
);
|
|
571
|
+
const children = plan.rows.find((r) => r.target === "lsp")?.children;
|
|
572
|
+
expect(children?.find((c) => c.key === "code")?.action).toBe("unchanged");
|
|
573
|
+
expect(children?.find((c) => c.key === "codium")?.action).toBe("add");
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
it("gates the per-editor multiselect behind the same customize confirm", async () => {
|
|
577
|
+
const { generator } = await buildSetupRun(
|
|
578
|
+
bootRuntime(FLAGS, tmp("pragma-setup-proj-")),
|
|
579
|
+
"lsp",
|
|
580
|
+
"global",
|
|
581
|
+
);
|
|
582
|
+
const customize = generator.prompts.find((p) => p.name === "customize");
|
|
583
|
+
const editors = generator.prompts.find((p) => p.name === "lspEditors");
|
|
584
|
+
expect(customize?.default).toBe(false);
|
|
585
|
+
// Both pending editors are pre-selected, so the default path installs into
|
|
586
|
+
// every fork without an extra question.
|
|
587
|
+
expect(editors?.default).toEqual(["code", "codium"]);
|
|
588
|
+
expect(editors?.when?.({ customize: false })).toBe(false);
|
|
589
|
+
expect(editors?.when?.({ customize: true })).toBe(true);
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
it("sideloads only into the editors the user kept", async () => {
|
|
593
|
+
const { detectLsp, composeLsp } = await import("./operations/setupLsp.js");
|
|
594
|
+
const detected = await detectLsp(tmp("pragma-setup-proj-"));
|
|
595
|
+
const { effects } = dryRun(composeLsp(detected, ["codium"]));
|
|
596
|
+
const execs = effects.filter((e) => e._tag === "Exec") as (Effect & {
|
|
597
|
+
_tag: "Exec";
|
|
598
|
+
command: string;
|
|
599
|
+
})[];
|
|
600
|
+
// The fetch still runs once; the sideload runs for `codium` alone.
|
|
601
|
+
expect(execs.map((e) => e.command)).toEqual(["bun", "codium"]);
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
it("composes nothing at all when every editor is deselected", async () => {
|
|
605
|
+
// Not even the package fetch: there is nothing to install it into.
|
|
606
|
+
const { detectLsp, composeLsp } = await import("./operations/setupLsp.js");
|
|
607
|
+
const detected = await detectLsp(tmp("pragma-setup-proj-"));
|
|
608
|
+
const { effects } = dryRun(composeLsp(detected, []));
|
|
609
|
+
expect(effects.some((e) => e._tag === "Exec")).toBe(false);
|
|
610
|
+
});
|
|
342
611
|
});
|
|
343
612
|
|
|
344
613
|
describe("setup (run-all wizard) — scope threading", () => {
|
|
345
614
|
// Isolate PATH so an ambient `claude`/`codex` can't inject a harness via a
|
|
346
615
|
// `process` signal — detection is driven only by the dirs each test makes.
|
|
616
|
+
// A stubbed `code` CLI makes the LSP step's editor detection deterministic.
|
|
347
617
|
let prevPath: string | undefined;
|
|
618
|
+
let editorPath = "";
|
|
348
619
|
beforeEach(() => {
|
|
349
620
|
prevPath = process.env.PATH;
|
|
350
|
-
|
|
621
|
+
editorPath = stubPath();
|
|
622
|
+
process.env.PATH = editorPath;
|
|
351
623
|
});
|
|
352
624
|
afterEach(() => {
|
|
353
625
|
process.env.PATH = prevPath;
|
|
354
626
|
});
|
|
355
627
|
|
|
628
|
+
/** Put a stub `code` on the isolated PATH so an LSP install is composable. */
|
|
629
|
+
const seedEditorCli = (): void => {
|
|
630
|
+
writeFileSync(join(editorPath, "code"), "");
|
|
631
|
+
};
|
|
632
|
+
|
|
356
633
|
/** Seed a discoverable skill so the project-band skills step is offerable. */
|
|
357
634
|
const seedSkill = (cwd: string): void => {
|
|
358
635
|
const dir = join(cwd, ".pragma", "skills", "s");
|
|
@@ -375,39 +652,56 @@ describe("setup (run-all wizard) — scope threading", () => {
|
|
|
375
652
|
);
|
|
376
653
|
expect(outcome.exitCode).toBe(0);
|
|
377
654
|
const plan = outcome.stdout ?? "";
|
|
378
|
-
//
|
|
379
|
-
|
|
380
|
-
expect(plan).
|
|
655
|
+
// A band FILTERS the run-all: the out-of-band targets are still named, as
|
|
656
|
+
// rows saying why they are not in this run, so nothing is silently absent.
|
|
657
|
+
expect(plan).toContain("completions skip");
|
|
658
|
+
expect(plan).toContain("user-level only — it has no project band");
|
|
381
659
|
// The project-band steps remain: MCP into .cursor, skills into .agents/skills.
|
|
382
660
|
expect(plan).toContain("mcp.json");
|
|
383
661
|
expect(plan).toContain(".agents/skills");
|
|
384
662
|
});
|
|
385
663
|
|
|
386
|
-
|
|
664
|
+
withShell(
|
|
665
|
+
"--global omits the project-band skills step, keeping global steps",
|
|
666
|
+
async () => {
|
|
667
|
+
const cwd = tmp("pragma-setup-proj-");
|
|
668
|
+
seedSkill(cwd); // WOULD be offered under the default `both`
|
|
669
|
+
seedEditorCli(); // vscode detected ⇒ the LSP install is in the plan
|
|
670
|
+
const outcome = await executeVerb(
|
|
671
|
+
setupSelfVerb,
|
|
672
|
+
{ global: true },
|
|
673
|
+
DRY,
|
|
674
|
+
bootRuntime(FLAGS, cwd),
|
|
675
|
+
);
|
|
676
|
+
expect(outcome.exitCode).toBe(0);
|
|
677
|
+
const plan = outcome.stdout ?? "";
|
|
678
|
+
// The global-band steps are present under --global. Paths render against
|
|
679
|
+
// the header's roots, so the row shows `~/.zfunc/_pragma`, not an absolute
|
|
680
|
+
// prefix repeated on every line.
|
|
681
|
+
expect(plan).toMatch(new RegExp(`completions\\s+install\\s+${SHELL} →`));
|
|
682
|
+
expect(plan).toContain("lsp");
|
|
683
|
+
// The project-band skills step is gone (the bug: it used to run under --global).
|
|
684
|
+
expect(plan).not.toContain(".agents/skills");
|
|
685
|
+
},
|
|
686
|
+
);
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
describe("setup skills", () => {
|
|
690
|
+
it("empty skills are a named skip at exit 0, not a failure", async () => {
|
|
691
|
+
// A skip is "nothing to do here, honestly named" — the same semantics
|
|
692
|
+
// doctor gives its skip glyph. Failing the direct sub-verb made a dotfiles
|
|
693
|
+
// script fail on a machine that simply has no skills installed yet, which
|
|
694
|
+
// teaches people to ignore the exit code.
|
|
387
695
|
const cwd = tmp("pragma-setup-proj-");
|
|
388
|
-
seedSkill(cwd); // WOULD be offered under the default `both`
|
|
389
696
|
const outcome = await executeVerb(
|
|
390
|
-
|
|
391
|
-
{
|
|
392
|
-
|
|
697
|
+
verbOf("skills"),
|
|
698
|
+
{ local: true },
|
|
699
|
+
YES,
|
|
393
700
|
bootRuntime(FLAGS, cwd),
|
|
394
701
|
);
|
|
395
702
|
expect(outcome.exitCode).toBe(0);
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
expect(plan).toContain(completionScriptPath("zsh"));
|
|
399
|
-
expect(plan).toContain("terrazzo-lsp-extension");
|
|
400
|
-
// The project-band skills step is gone (the bug: it used to run under --global).
|
|
401
|
-
expect(plan).not.toContain(".agents/skills");
|
|
402
|
-
});
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
describe("setup skills", () => {
|
|
406
|
-
it("empty skills raise EMPTY_RESULTS on the direct sub-verb", async () => {
|
|
407
|
-
const cwd = tmp("pragma-setup-proj-");
|
|
408
|
-
await expect(
|
|
409
|
-
executeVerb(verbOf("skills"), {}, YES, bootRuntime(FLAGS, cwd)),
|
|
410
|
-
).rejects.toMatchObject({ code: "EMPTY_RESULTS" });
|
|
703
|
+
expect(outcome.stdout).toContain("skills");
|
|
704
|
+
expect(outcome.stdout).toContain("no project skills");
|
|
411
705
|
});
|
|
412
706
|
|
|
413
707
|
it("detects a created action and composes a symlink carrying an undo", async () => {
|
|
@@ -419,7 +713,7 @@ describe("setup skills", () => {
|
|
|
419
713
|
"---\nname: my-skill\ndescription: A test skill.\n---\n",
|
|
420
714
|
);
|
|
421
715
|
|
|
422
|
-
const detected = await detectSkills(bootRuntime(FLAGS, cwd));
|
|
716
|
+
const detected = await detectSkills(bootRuntime(FLAGS, cwd), "project");
|
|
423
717
|
expect(detected.available).toBe(true);
|
|
424
718
|
expect(detected.skillCount).toBe(1);
|
|
425
719
|
expect(detected.actions.some((a) => a.action === "created")).toBe(true);
|
|
@@ -432,16 +726,209 @@ describe("setup skills", () => {
|
|
|
432
726
|
expect(symlinkEffect?.undo).toBeDefined();
|
|
433
727
|
expect(existsSync(join(cwd, ".agents", "skills", "my-skill"))).toBe(false);
|
|
434
728
|
});
|
|
729
|
+
|
|
730
|
+
it("classifies a DANGLING symlink as replaced and repairs it (S1-2)", async () => {
|
|
731
|
+
// `existsSync` FOLLOWS a symlink, so a dangling link used to read as
|
|
732
|
+
// "absent" → plan `created` → the real symlink() crashed EEXIST labeled
|
|
733
|
+
// INTERNAL_ERROR ("please report this issue"), leaving the broken link in
|
|
734
|
+
// place — and the dry-run previewed a clean create the run then belied.
|
|
735
|
+
const cwd = tmp("pragma-setup-proj-");
|
|
736
|
+
const skillDir = join(cwd, ".pragma", "skills", "my-skill");
|
|
737
|
+
mkdirSync(skillDir, { recursive: true });
|
|
738
|
+
writeFileSync(
|
|
739
|
+
join(skillDir, "SKILL.md"),
|
|
740
|
+
"---\nname: my-skill\ndescription: A test skill.\n---\n",
|
|
741
|
+
);
|
|
742
|
+
const linkDir = join(cwd, ".agents", "skills");
|
|
743
|
+
mkdirSync(linkDir, { recursive: true });
|
|
744
|
+
const linkPath = join(linkDir, "my-skill");
|
|
745
|
+
symlinkSync(join(cwd, "nonexistent-target"), linkPath);
|
|
746
|
+
|
|
747
|
+
const detected = await detectSkills(bootRuntime(FLAGS, cwd), "project");
|
|
748
|
+
const action = detected.actions.find((a) => a.linkPath === linkPath);
|
|
749
|
+
expect(action?.action).toBe("replaced");
|
|
750
|
+
|
|
751
|
+
// The dry-run preview now PREDICTS the repair: a delete before the relink.
|
|
752
|
+
const { effects } = dryRun(composeSkills(detected));
|
|
753
|
+
const deletes = effects.filter(
|
|
754
|
+
(e) =>
|
|
755
|
+
e._tag === "DeleteFile" && (e as { path?: string }).path === linkPath,
|
|
756
|
+
);
|
|
757
|
+
expect(deletes.length).toBe(1);
|
|
758
|
+
|
|
759
|
+
// The real run repairs the link instead of crashing EEXIST.
|
|
760
|
+
await runTask(composeSkills(detected));
|
|
761
|
+
expect(readlinkSync(linkPath)).toBe(skillDir);
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
it("skips a real (non-symlink) directory at the link path, never deleting it", async () => {
|
|
765
|
+
// A hand-placed skill directory is not this command's to delete — the
|
|
766
|
+
// sibling planner (sources/installSkills.ts) already refuses to clobber
|
|
767
|
+
// it, and the two planners must agree on classification.
|
|
768
|
+
const cwd = tmp("pragma-setup-proj-");
|
|
769
|
+
const skillDir = join(cwd, ".pragma", "skills", "my-skill");
|
|
770
|
+
mkdirSync(skillDir, { recursive: true });
|
|
771
|
+
writeFileSync(
|
|
772
|
+
join(skillDir, "SKILL.md"),
|
|
773
|
+
"---\nname: my-skill\ndescription: A test skill.\n---\n",
|
|
774
|
+
);
|
|
775
|
+
const realDir = join(cwd, ".agents", "skills", "my-skill");
|
|
776
|
+
mkdirSync(realDir, { recursive: true });
|
|
777
|
+
writeFileSync(join(realDir, "SKILL.md"), "hand-placed\n");
|
|
778
|
+
|
|
779
|
+
const detected = await detectSkills(bootRuntime(FLAGS, cwd), "project");
|
|
780
|
+
const action = detected.actions.find((a) => a.linkPath === realDir);
|
|
781
|
+
expect(action?.action).toBe("skipped");
|
|
782
|
+
|
|
783
|
+
const { effects } = dryRun(composeSkills(detected));
|
|
784
|
+
expect(
|
|
785
|
+
effects.some(
|
|
786
|
+
(e) =>
|
|
787
|
+
e._tag === "DeleteFile" && (e as { path?: string }).path === realDir,
|
|
788
|
+
),
|
|
789
|
+
).toBe(false);
|
|
790
|
+
expect(readFileSync(join(realDir, "SKILL.md"), "utf-8")).toBe(
|
|
791
|
+
"hand-placed\n",
|
|
792
|
+
);
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
it("never owns — and never removes — a link into a SIBLING of the skill root", async () => {
|
|
796
|
+
// Ownership was a string prefix, and `<sourceRoot>-backup/my-skill` starts
|
|
797
|
+
// with `<sourceRoot>`. So a link the USER made into their own backup
|
|
798
|
+
// directory was classified as ours, and `--undo` deleted it: a file this
|
|
799
|
+
// command never created and had no business touching.
|
|
800
|
+
const cwd = tmp("pragma-setup-proj-");
|
|
801
|
+
const skillDir = join(cwd, ".pragma", "skills", "my-skill");
|
|
802
|
+
mkdirSync(skillDir, { recursive: true });
|
|
803
|
+
writeFileSync(
|
|
804
|
+
join(skillDir, "SKILL.md"),
|
|
805
|
+
"---\nname: my-skill\ndescription: A test skill.\n---\n",
|
|
806
|
+
);
|
|
807
|
+
const backup = join(cwd, ".pragma", "skills-backup", "my-skill");
|
|
808
|
+
mkdirSync(backup, { recursive: true });
|
|
809
|
+
writeFileSync(join(backup, "SKILL.md"), "the user's own copy\n");
|
|
810
|
+
const linkDir = join(cwd, ".agents", "skills");
|
|
811
|
+
mkdirSync(linkDir, { recursive: true });
|
|
812
|
+
const linkPath = join(linkDir, "my-skill");
|
|
813
|
+
symlinkSync(backup, linkPath);
|
|
814
|
+
|
|
815
|
+
const detected = await detectSkills(bootRuntime(FLAGS, cwd), "project");
|
|
816
|
+
const action = detected.actions.find((a) => a.linkPath === linkPath);
|
|
817
|
+
expect(action?.owned).toBe(false);
|
|
818
|
+
expect(ownedSkillLinks(detected).map((a) => a.linkPath)).not.toContain(
|
|
819
|
+
linkPath,
|
|
820
|
+
);
|
|
821
|
+
|
|
822
|
+
// The removal is what `--undo` runs: it must leave both the link and the
|
|
823
|
+
// directory it points at exactly where they were.
|
|
824
|
+
await runUndo(composeSkillsRemoval(detected));
|
|
825
|
+
expect(readlinkSync(linkPath)).toBe(backup);
|
|
826
|
+
expect(readFileSync(join(backup, "SKILL.md"), "utf-8")).toBe(
|
|
827
|
+
"the user's own copy\n",
|
|
828
|
+
);
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
it("removes an owned link whose skill is gone from an emptied source root", async () => {
|
|
832
|
+
// Detection returned before target discovery whenever the source root held
|
|
833
|
+
// no skills, so `--undo` looked at an empty action list, reported zero work
|
|
834
|
+
// and left this command's own links on disk forever.
|
|
835
|
+
const cwd = tmp("pragma-setup-proj-");
|
|
836
|
+
const skillsRoot = join(cwd, ".pragma", "skills");
|
|
837
|
+
const skillDir = join(skillsRoot, "my-skill");
|
|
838
|
+
mkdirSync(skillDir, { recursive: true });
|
|
839
|
+
writeFileSync(
|
|
840
|
+
join(skillDir, "SKILL.md"),
|
|
841
|
+
"---\nname: my-skill\ndescription: A test skill.\n---\n",
|
|
842
|
+
);
|
|
843
|
+
const linkDir = join(cwd, ".agents", "skills");
|
|
844
|
+
mkdirSync(linkDir, { recursive: true });
|
|
845
|
+
const linkPath = join(linkDir, "my-skill");
|
|
846
|
+
symlinkSync(skillDir, linkPath);
|
|
847
|
+
// The skill is removed upstream: the link is now ours, owned, and dangling.
|
|
848
|
+
rmSync(skillDir, { recursive: true, force: true });
|
|
849
|
+
|
|
850
|
+
const detected = await detectSkills(bootRuntime(FLAGS, cwd), "project");
|
|
851
|
+
expect(detected.available).toBe(false);
|
|
852
|
+
expect(ownedSkillLinks(detected).map((a) => a.linkPath)).toEqual([
|
|
853
|
+
linkPath,
|
|
854
|
+
]);
|
|
855
|
+
|
|
856
|
+
await runUndo(composeSkillsRemoval(detected));
|
|
857
|
+
// `existsSync` follows the link, so the dangling one is read through lstat.
|
|
858
|
+
expect(() => lstatSync(linkPath)).toThrow();
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
it("owns a RELATIVE link into the skill root, resolved against the link dir", async () => {
|
|
862
|
+
// `readlink` reports the raw target, which may be relative — resolving it
|
|
863
|
+
// against the link's own directory is what makes the containment test
|
|
864
|
+
// answer about a real path rather than a string.
|
|
865
|
+
const cwd = tmp("pragma-setup-proj-");
|
|
866
|
+
const skillDir = join(cwd, ".pragma", "skills", "my-skill");
|
|
867
|
+
mkdirSync(skillDir, { recursive: true });
|
|
868
|
+
writeFileSync(
|
|
869
|
+
join(skillDir, "SKILL.md"),
|
|
870
|
+
"---\nname: my-skill\ndescription: A test skill.\n---\n",
|
|
871
|
+
);
|
|
872
|
+
const linkDir = join(cwd, ".agents", "skills");
|
|
873
|
+
mkdirSync(linkDir, { recursive: true });
|
|
874
|
+
const linkPath = join(linkDir, "my-skill");
|
|
875
|
+
symlinkSync(relative(linkDir, skillDir), linkPath);
|
|
876
|
+
|
|
877
|
+
const detected = await detectSkills(bootRuntime(FLAGS, cwd), "project");
|
|
878
|
+
const action = detected.actions.find((a) => a.linkPath === linkPath);
|
|
879
|
+
expect(action?.owned).toBe(true);
|
|
880
|
+
|
|
881
|
+
await runUndo(composeSkillsRemoval(detected));
|
|
882
|
+
expect(existsSync(linkPath)).toBe(false);
|
|
883
|
+
});
|
|
435
884
|
});
|
|
436
885
|
|
|
437
|
-
describe("setup
|
|
438
|
-
it("
|
|
439
|
-
//
|
|
440
|
-
//
|
|
441
|
-
//
|
|
442
|
-
|
|
886
|
+
describe("setup — detections settle independently", () => {
|
|
887
|
+
it("gives a rejecting detection its own failed row, leaving the rest planned", async () => {
|
|
888
|
+
// The targets are independent, and one `Promise.all` made them share a
|
|
889
|
+
// fate: a single rejecting detection prevented a run being built at all,
|
|
890
|
+
// so NONE of the other targets ran and doctor rendered no banded rows.
|
|
891
|
+
const { TARGETS } = await import("./targets.js");
|
|
892
|
+
const lsp = TARGETS.find((t) => t.id === "lsp") as (typeof TARGETS)[number];
|
|
893
|
+
const spy = vi
|
|
894
|
+
.spyOn(lsp, "detect")
|
|
895
|
+
.mockRejectedValue(new Error("EACCES: permission denied, open '/x'"));
|
|
896
|
+
try {
|
|
897
|
+
const run = await buildSetupRun(
|
|
898
|
+
bootRuntime(FLAGS, tmp("pragma-setup-proj-")),
|
|
899
|
+
"all",
|
|
900
|
+
"global",
|
|
901
|
+
);
|
|
902
|
+
const row = run.plan.rows.find((r) => r.target === "lsp");
|
|
903
|
+
expect(row?.reason).toContain("EACCES");
|
|
904
|
+
// The point of the fix: the siblings are still planned.
|
|
905
|
+
expect(
|
|
906
|
+
run.plan.rows.filter((r) => r.target !== "lsp").length,
|
|
907
|
+
).toBeGreaterThan(0);
|
|
908
|
+
// And the row is attributable, not a quiet skip: it exits non-zero.
|
|
909
|
+
const applied = run.applied({});
|
|
910
|
+
expect(
|
|
911
|
+
applied.rows.find((r) => r.target === "lsp")?.outcome?.status,
|
|
912
|
+
).toBe("failed");
|
|
913
|
+
} finally {
|
|
914
|
+
spy.mockRestore();
|
|
915
|
+
}
|
|
916
|
+
});
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
describe("setup lsp — prerequisites (bun absent / no editor CLI)", () => {
|
|
920
|
+
it("surfaces a NAMED UNSUPPORTED (not INTERNAL_ERROR) when bun is off PATH", async () => {
|
|
921
|
+
// Drives the REAL composeLsp fetch (YES — not a dry-run mock) with a
|
|
922
|
+
// stubbed `code` CLI on PATH (so the sideload is attempted) but no `bun`,
|
|
923
|
+
// so the fetch spawn REJECTS with ENOENT. The guard names it; without the
|
|
924
|
+
// guard the raw reject collapses to INTERNAL_ERROR ("please report this
|
|
925
|
+
// issue") at the boundary.
|
|
443
926
|
const prevPath = process.env.PATH;
|
|
444
|
-
process.env.
|
|
927
|
+
const prevData = process.env.XDG_DATA_HOME;
|
|
928
|
+
const stubDir = stubPath();
|
|
929
|
+
writeFileSync(join(stubDir, "code"), ""); // present ⇒ vscode is a target
|
|
930
|
+
process.env.PATH = stubDir;
|
|
931
|
+
process.env.XDG_DATA_HOME = tmp("pragma-lsp-data-"); // jail the staging dir
|
|
445
932
|
let thrown: unknown;
|
|
446
933
|
try {
|
|
447
934
|
await executeVerb(
|
|
@@ -454,16 +941,207 @@ describe("setup lsp — missing-binary guard (bunx absent)", () => {
|
|
|
454
941
|
thrown = error;
|
|
455
942
|
} finally {
|
|
456
943
|
process.env.PATH = prevPath;
|
|
944
|
+
process.env.XDG_DATA_HOME = prevData;
|
|
457
945
|
}
|
|
458
946
|
expect(thrown).toBeDefined();
|
|
459
947
|
const err = asPragmaError(thrown);
|
|
460
948
|
expect(err.code).toBe("UNSUPPORTED");
|
|
461
949
|
expect(err.code).not.toBe("INTERNAL_ERROR");
|
|
462
|
-
expect(err.message).toContain("
|
|
950
|
+
expect(err.message).toContain("bun");
|
|
463
951
|
expect(err.message).toMatch(/not found on your PATH/i);
|
|
464
952
|
// The recovery is the actionable install hint, not "report this issue".
|
|
465
953
|
expect(err.recovery?.message ?? "").not.toMatch(/report this issue/i);
|
|
466
954
|
});
|
|
955
|
+
|
|
956
|
+
it("sideloads the bundled VSIX into EACH detected editor missing it (per-editor CLI, not a hardcoded `code`)", async () => {
|
|
957
|
+
// Two editor CLIs on PATH; VS Code already has the extension (its
|
|
958
|
+
// extensions dir carries a versioned copy), VSCodium does not — so the
|
|
959
|
+
// composed plan fetches the package once (bun, into a durable staging dir)
|
|
960
|
+
// and sideloads ONLY into `codium`. This is the VSCodium fix: the editor
|
|
961
|
+
// CLI is a registry lookup, no longer the hardcoded `code`.
|
|
962
|
+
const prevPath = process.env.PATH;
|
|
963
|
+
const stubDir = tmp("pragma-forks-path-");
|
|
964
|
+
writeFileSync(join(stubDir, "code"), "");
|
|
965
|
+
writeFileSync(join(stubDir, "codium"), "");
|
|
966
|
+
process.env.PATH = stubDir;
|
|
967
|
+
try {
|
|
968
|
+
mkdirSync(
|
|
969
|
+
join(
|
|
970
|
+
process.env.HOME ?? "",
|
|
971
|
+
".vscode",
|
|
972
|
+
"extensions",
|
|
973
|
+
"canonical.terrazzo-lsp-extension-1.2.3",
|
|
974
|
+
),
|
|
975
|
+
{ recursive: true },
|
|
976
|
+
);
|
|
977
|
+
const { detectLsp, composeLsp } = await import(
|
|
978
|
+
"./operations/setupLsp.js"
|
|
979
|
+
);
|
|
980
|
+
const detected = await detectLsp(tmp("pragma-setup-proj-"));
|
|
981
|
+
expect(detected.state).toBe("absent");
|
|
982
|
+
expect(detected.editors.map((e) => [e.editor.cli, e.installed])).toEqual([
|
|
983
|
+
["code", true],
|
|
984
|
+
["codium", false],
|
|
985
|
+
]);
|
|
986
|
+
|
|
987
|
+
const { effects } = dryRun(composeLsp(detected));
|
|
988
|
+
const execs = effects.filter((e) => e._tag === "Exec") as (Effect & {
|
|
989
|
+
_tag: "Exec";
|
|
990
|
+
command: string;
|
|
991
|
+
args: string[];
|
|
992
|
+
})[];
|
|
993
|
+
// One fetch (bun add, durable staging) + one sideload per PENDING editor.
|
|
994
|
+
expect(execs.map((e) => e.command)).toEqual(["bun", "codium"]);
|
|
995
|
+
expect(execs[0].args).toEqual([
|
|
996
|
+
"add",
|
|
997
|
+
"@canonical/terrazzo-lsp-extension@latest",
|
|
998
|
+
]);
|
|
999
|
+
expect(execs[1].args[0]).toBe("--install-extension");
|
|
1000
|
+
// The VSIX path is the staging dir's — durable, not a bunx /tmp cache.
|
|
1001
|
+
expect(execs[1].args[1]).toContain(detected.stagingDir);
|
|
1002
|
+
expect(execs[1].args[1]).toContain("terrazzo-lsp.vsix");
|
|
1003
|
+
} finally {
|
|
1004
|
+
process.env.PATH = prevPath;
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
1007
|
+
|
|
1008
|
+
it("treats an extension too old to WORK as not installed", async () => {
|
|
1009
|
+
// Every VSIX before 0.8.3 ships a language server that cannot resolve its
|
|
1010
|
+
// own dependencies — it only appeared to run because the extension prefers
|
|
1011
|
+
// Bun and Bun fetches missing packages off the network at spawn time.
|
|
1012
|
+
// Matching any version meant such a machine reported `installed`, so
|
|
1013
|
+
// `setup lsp` skipped it and `doctor` called it healthy: a wrong skip that
|
|
1014
|
+
// reads as correct, which is worse than a wrong failure. Everyone who ran
|
|
1015
|
+
// setup before that release would have kept a dead server forever.
|
|
1016
|
+
const prevPath = process.env.PATH;
|
|
1017
|
+
const stubDir = tmp("pragma-stale-ext-path-");
|
|
1018
|
+
writeFileSync(join(stubDir, "code"), "");
|
|
1019
|
+
process.env.PATH = stubDir;
|
|
1020
|
+
try {
|
|
1021
|
+
mkdirSync(
|
|
1022
|
+
join(
|
|
1023
|
+
process.env.HOME ?? "",
|
|
1024
|
+
".vscode",
|
|
1025
|
+
"extensions",
|
|
1026
|
+
"canonical.terrazzo-lsp-extension-0.8.1",
|
|
1027
|
+
),
|
|
1028
|
+
{ recursive: true },
|
|
1029
|
+
);
|
|
1030
|
+
const { detectLsp } = await import("./operations/setupLsp.js");
|
|
1031
|
+
const detected = await detectLsp(tmp("pragma-setup-proj-"));
|
|
1032
|
+
expect(detected.state).not.toBe("installed");
|
|
1033
|
+
} finally {
|
|
1034
|
+
process.env.PATH = prevPath;
|
|
1035
|
+
}
|
|
1036
|
+
});
|
|
1037
|
+
|
|
1038
|
+
it("treats a version it cannot READ as too old, not as newer", async () => {
|
|
1039
|
+
// The fail-closed claim has to survive the parser. `Number.parseInt`
|
|
1040
|
+
// accepts numeric prefixes and a `|| 0` fallback turns NaN into zero, so
|
|
1041
|
+
// `1.invalid.0` and `1.0.0junk` both read as [1,0,0] — comparing NEWER
|
|
1042
|
+
// than the minimum and reporting a broken extension as installed, which
|
|
1043
|
+
// is the wrong skip this check exists to prevent.
|
|
1044
|
+
const prevPath = process.env.PATH;
|
|
1045
|
+
const stubDir = tmp("pragma-badver-path-");
|
|
1046
|
+
writeFileSync(join(stubDir, "code"), "");
|
|
1047
|
+
process.env.PATH = stubDir;
|
|
1048
|
+
try {
|
|
1049
|
+
for (const suffix of ["1.invalid.0", "1.0.0junk", "garbage", ""]) {
|
|
1050
|
+
mkdirSync(
|
|
1051
|
+
join(
|
|
1052
|
+
process.env.HOME ?? "",
|
|
1053
|
+
".vscode",
|
|
1054
|
+
"extensions",
|
|
1055
|
+
`canonical.terrazzo-lsp-extension-${suffix}`,
|
|
1056
|
+
),
|
|
1057
|
+
{ recursive: true },
|
|
1058
|
+
);
|
|
1059
|
+
}
|
|
1060
|
+
const { detectLsp } = await import("./operations/setupLsp.js");
|
|
1061
|
+
const detected = await detectLsp(tmp("pragma-setup-proj-"));
|
|
1062
|
+
expect(detected.state).not.toBe("installed");
|
|
1063
|
+
} finally {
|
|
1064
|
+
process.env.PATH = prevPath;
|
|
1065
|
+
}
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
it("reports already-installed (a true no-op) when every detected editor has the extension", async () => {
|
|
1069
|
+
const prevPath = process.env.PATH;
|
|
1070
|
+
const stubDir = tmp("pragma-installed-path-");
|
|
1071
|
+
writeFileSync(join(stubDir, "code"), "");
|
|
1072
|
+
process.env.PATH = stubDir;
|
|
1073
|
+
try {
|
|
1074
|
+
mkdirSync(
|
|
1075
|
+
join(
|
|
1076
|
+
process.env.HOME ?? "",
|
|
1077
|
+
".vscode",
|
|
1078
|
+
"extensions",
|
|
1079
|
+
"canonical.terrazzo-lsp-extension-1.2.3",
|
|
1080
|
+
),
|
|
1081
|
+
{ recursive: true },
|
|
1082
|
+
);
|
|
1083
|
+
const { detectLsp, composeLsp } = await import(
|
|
1084
|
+
"./operations/setupLsp.js"
|
|
1085
|
+
);
|
|
1086
|
+
const detected = await detectLsp(tmp("pragma-setup-proj-"));
|
|
1087
|
+
expect(detected.state).toBe("installed");
|
|
1088
|
+
const { effects } = dryRun(composeLsp(detected));
|
|
1089
|
+
expect(effects.some((e) => e._tag === "Exec")).toBe(false);
|
|
1090
|
+
} finally {
|
|
1091
|
+
process.env.PATH = prevPath;
|
|
1092
|
+
}
|
|
1093
|
+
});
|
|
1094
|
+
|
|
1095
|
+
it("matches the FULL extension id, not any terrazzo-named directory", async () => {
|
|
1096
|
+
// The old probe matched the substring "terrazzo", which any other
|
|
1097
|
+
// terrazzo-named extension would false-positive.
|
|
1098
|
+
const prevPath = process.env.PATH;
|
|
1099
|
+
const stubDir = tmp("pragma-substr-path-");
|
|
1100
|
+
writeFileSync(join(stubDir, "code"), "");
|
|
1101
|
+
process.env.PATH = stubDir;
|
|
1102
|
+
try {
|
|
1103
|
+
mkdirSync(
|
|
1104
|
+
join(
|
|
1105
|
+
process.env.HOME ?? "",
|
|
1106
|
+
".vscode",
|
|
1107
|
+
"extensions",
|
|
1108
|
+
"someoneelse.terrazzo-tools-9.9.9",
|
|
1109
|
+
),
|
|
1110
|
+
{ recursive: true },
|
|
1111
|
+
);
|
|
1112
|
+
const { detectLsp } = await import("./operations/setupLsp.js");
|
|
1113
|
+
const detected = await detectLsp(tmp("pragma-setup-proj-"));
|
|
1114
|
+
expect(detected.state).toBe("absent");
|
|
1115
|
+
} finally {
|
|
1116
|
+
process.env.PATH = prevPath;
|
|
1117
|
+
}
|
|
1118
|
+
});
|
|
1119
|
+
|
|
1120
|
+
it("skips with a NAMED message (exit 0) when no editor CLI exists at all", async () => {
|
|
1121
|
+
// A machine with no VS Code-family CLI cannot be installed into — the
|
|
1122
|
+
// honest outcome is a named skip, not the old UNSUPPORTED with a
|
|
1123
|
+
// "permissions or network" guess and a dead /tmp path (§3, S1-1's trigger).
|
|
1124
|
+
const prevPath = process.env.PATH;
|
|
1125
|
+
process.env.PATH = stubPath(); // empty ⇒ no editor CLI
|
|
1126
|
+
try {
|
|
1127
|
+
const outcome = await executeVerb(
|
|
1128
|
+
verbOf("lsp"),
|
|
1129
|
+
{},
|
|
1130
|
+
YES,
|
|
1131
|
+
bootRuntime(FLAGS, tmp("pragma-setup-proj-")),
|
|
1132
|
+
);
|
|
1133
|
+
expect(outcome.exitCode).toBe(0);
|
|
1134
|
+
expect(outcome.stdout).toContain("skipped");
|
|
1135
|
+
expect(outcome.stdout).toContain("no VS Code-family editor CLI on PATH");
|
|
1136
|
+
// The remedy names what would count, and states plainly that nothing is
|
|
1137
|
+
// possible here yet rather than offering a command this machine lacks.
|
|
1138
|
+
expect(outcome.stdout).toContain(
|
|
1139
|
+
"no action is possible on this machine yet",
|
|
1140
|
+
);
|
|
1141
|
+
} finally {
|
|
1142
|
+
process.env.PATH = prevPath;
|
|
1143
|
+
}
|
|
1144
|
+
});
|
|
467
1145
|
});
|
|
468
1146
|
|
|
469
1147
|
describe("setup — idempotent detection of already-present config", () => {
|
|
@@ -471,7 +1149,7 @@ describe("setup — idempotent detection of already-present config", () => {
|
|
|
471
1149
|
let prevPath: string | undefined;
|
|
472
1150
|
beforeEach(() => {
|
|
473
1151
|
prevPath = process.env.PATH;
|
|
474
|
-
process.env.PATH =
|
|
1152
|
+
process.env.PATH = stubPath();
|
|
475
1153
|
});
|
|
476
1154
|
afterEach(() => {
|
|
477
1155
|
process.env.PATH = prevPath;
|
|
@@ -483,42 +1161,41 @@ describe("setup — idempotent detection of already-present config", () => {
|
|
|
483
1161
|
const configPath = join(cwd, ".cursor", "mcp.json");
|
|
484
1162
|
|
|
485
1163
|
// First run writes the pragma entry.
|
|
486
|
-
await executeVerb(
|
|
1164
|
+
await executeVerb(
|
|
1165
|
+
verbOf("mcp"),
|
|
1166
|
+
{ local: true },
|
|
1167
|
+
YES,
|
|
1168
|
+
bootRuntime(FLAGS, cwd),
|
|
1169
|
+
);
|
|
487
1170
|
const firstBody = readFileSync(configPath, "utf-8");
|
|
488
1171
|
|
|
489
|
-
//
|
|
490
|
-
|
|
1172
|
+
// The plan's mcp row now reads `none` — nothing to do — and its child for
|
|
1173
|
+
// this file reads `unchanged`.
|
|
1174
|
+
const { plan } = await buildSetupRun(
|
|
491
1175
|
bootRuntime(FLAGS, cwd),
|
|
492
1176
|
"mcp",
|
|
493
|
-
"
|
|
1177
|
+
"project",
|
|
494
1178
|
);
|
|
495
|
-
const
|
|
496
|
-
expect(
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
1179
|
+
const row = plan.rows.find((r) => r.target === "mcp");
|
|
1180
|
+
expect(row?.action).toBe("none");
|
|
1181
|
+
expect(row?.children?.find((c) => c.key === configPath)?.action).toBe(
|
|
1182
|
+
"unchanged",
|
|
1183
|
+
);
|
|
1184
|
+
// An already-current row is offered DE-selected, so a re-run never proposes
|
|
1185
|
+
// to rewrite what is already correct.
|
|
1186
|
+
expect(row?.selected).toBe(false);
|
|
500
1187
|
|
|
501
|
-
//
|
|
502
|
-
//
|
|
503
|
-
const
|
|
1188
|
+
// A real second run is idempotent: the file stays byte-identical AND the
|
|
1189
|
+
// converged row composes no effect at all, so the mtime is untouched.
|
|
1190
|
+
const before = statSync(configPath).mtimeMs;
|
|
1191
|
+
await executeVerb(
|
|
1192
|
+
verbOf("mcp"),
|
|
1193
|
+
{ local: true },
|
|
1194
|
+
YES,
|
|
504
1195
|
bootRuntime(FLAGS, cwd),
|
|
505
|
-
"mcp",
|
|
506
|
-
"both",
|
|
507
1196
|
);
|
|
508
|
-
const multiselect = generator.prompts.find((p) => p.name === "mcpTargets");
|
|
509
|
-
expect(multiselect?.default).toEqual([]); // the only file is configured
|
|
510
|
-
expect(
|
|
511
|
-
(multiselect?.choices ?? []).some((c) =>
|
|
512
|
-
typeof c === "object" && "label" in c
|
|
513
|
-
? c.label.includes("already configured")
|
|
514
|
-
: false,
|
|
515
|
-
),
|
|
516
|
-
).toBe(true);
|
|
517
|
-
|
|
518
|
-
// A real second run is idempotent: the file stays byte-identical (the
|
|
519
|
-
// read-modify-write re-merges the SAME pragma entry).
|
|
520
|
-
await executeVerb(verbOf("mcp"), {}, YES, bootRuntime(FLAGS, cwd));
|
|
521
1197
|
expect(readFileSync(configPath, "utf-8")).toBe(firstBody);
|
|
1198
|
+
expect(statSync(configPath).mtimeMs).toBe(before);
|
|
522
1199
|
});
|
|
523
1200
|
|
|
524
1201
|
it("mcp: a drifted pragma entry (wrong cwd) reads as `drifted` and is updated", async () => {
|
|
@@ -539,98 +1216,179 @@ describe("setup — idempotent detection of already-present config", () => {
|
|
|
539
1216
|
)}\n`,
|
|
540
1217
|
);
|
|
541
1218
|
|
|
542
|
-
const {
|
|
1219
|
+
const { plan } = await buildSetupRun(
|
|
543
1220
|
bootRuntime(FLAGS, cwd),
|
|
544
1221
|
"mcp",
|
|
545
|
-
"
|
|
1222
|
+
"project",
|
|
546
1223
|
);
|
|
547
|
-
const
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
expect(
|
|
1224
|
+
const row = plan.rows.find((r) => r.target === "mcp");
|
|
1225
|
+
// A drifted file is an `update` child, and the row stays SELECTED by
|
|
1226
|
+
// default because it needs the write.
|
|
1227
|
+
expect(row?.children?.find((c) => c.key === configPath)?.action).toBe(
|
|
1228
|
+
"update",
|
|
1229
|
+
);
|
|
1230
|
+
expect(row?.action).toBe("update");
|
|
1231
|
+
expect(row?.selected).toBe(true);
|
|
554
1232
|
|
|
555
|
-
await executeVerb(
|
|
1233
|
+
await executeVerb(
|
|
1234
|
+
verbOf("mcp"),
|
|
1235
|
+
{ local: true },
|
|
1236
|
+
YES,
|
|
1237
|
+
bootRuntime(FLAGS, cwd),
|
|
1238
|
+
);
|
|
556
1239
|
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
557
1240
|
expect(config.mcpServers.pragma.cwd).toBe(cwd); // updated to the real cwd
|
|
558
1241
|
});
|
|
559
1242
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
1243
|
+
withShell(
|
|
1244
|
+
"completions: a second run detects the up-to-date script (state=installed)",
|
|
1245
|
+
async () => {
|
|
1246
|
+
const cwd = tmp("pragma-setup-proj-");
|
|
1247
|
+
const path = completionScriptPath(SHELL as ShellId);
|
|
1248
|
+
// First install.
|
|
1249
|
+
await executeVerb(completionsVerb, {}, YES, bootRuntime(FLAGS, cwd));
|
|
1250
|
+
const firstBody = readFileSync(path, "utf-8");
|
|
566
1251
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
1252
|
+
// The plan's completions row now reads `none` — the installed bytes are
|
|
1253
|
+
// current, so a re-run composes nothing.
|
|
1254
|
+
const { plan } = await buildSetupRun(
|
|
1255
|
+
bootRuntime(FLAGS, cwd),
|
|
1256
|
+
"completions",
|
|
1257
|
+
"global",
|
|
1258
|
+
);
|
|
1259
|
+
expect(plan.rows.find((r) => r.target === "completions")?.action).toBe(
|
|
1260
|
+
"none",
|
|
1261
|
+
);
|
|
577
1262
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
1263
|
+
// A real second run is idempotent — the byte-identical script survives.
|
|
1264
|
+
await executeVerb(completionsVerb, {}, YES, bootRuntime(FLAGS, cwd));
|
|
1265
|
+
expect(readFileSync(path, "utf-8")).toBe(firstBody);
|
|
1266
|
+
},
|
|
1267
|
+
);
|
|
582
1268
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
1269
|
+
withShell(
|
|
1270
|
+
"completions: a stale script (different body) reads as `stale`",
|
|
1271
|
+
async () => {
|
|
1272
|
+
const cwd = tmp("pragma-setup-proj-");
|
|
1273
|
+
const path = completionScriptPath(SHELL as ShellId);
|
|
1274
|
+
mkdirSync(join(path, ".."), { recursive: true });
|
|
1275
|
+
writeFileSync(path, "# stale hand-edited completion\n");
|
|
1276
|
+
const { plan } = await buildSetupRun(
|
|
1277
|
+
bootRuntime(FLAGS, cwd),
|
|
1278
|
+
"completions",
|
|
1279
|
+
"global",
|
|
1280
|
+
);
|
|
1281
|
+
// A hand-edited script is `update`, not `install`: the row says which of
|
|
1282
|
+
// the two it is, because overwriting someone's edit is worth naming.
|
|
1283
|
+
expect(plan.rows.find((r) => r.target === "completions")?.action).toBe(
|
|
1284
|
+
"update",
|
|
1285
|
+
);
|
|
1286
|
+
},
|
|
1287
|
+
);
|
|
598
1288
|
|
|
599
1289
|
it("lsp: reports `unknown` when the `code` CLI is absent from PATH", async () => {
|
|
600
1290
|
// PATH is the isolated empty dir (beforeEach), so `code` is unresolvable:
|
|
601
1291
|
// detection cannot enumerate and reports `unknown` (installer still runs).
|
|
602
|
-
const {
|
|
1292
|
+
const { plan } = await buildSetupRun(
|
|
603
1293
|
bootRuntime(FLAGS, tmp("pragma-setup-proj-")),
|
|
604
1294
|
"lsp",
|
|
605
|
-
"
|
|
1295
|
+
"global",
|
|
606
1296
|
);
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
1297
|
+
// No editor CLI resolves, so the row is a SKIP carrying its reason —
|
|
1298
|
+
// never a silent omission, and never a failure.
|
|
1299
|
+
const row = plan.rows.find((r) => r.target === "lsp");
|
|
1300
|
+
expect(row?.action).toBe("skip");
|
|
1301
|
+
expect(row?.reason).toContain("no VS Code-family editor CLI on PATH");
|
|
612
1302
|
});
|
|
613
1303
|
});
|
|
614
1304
|
|
|
615
1305
|
describe("setup (run-all wizard)", () => {
|
|
616
|
-
|
|
1306
|
+
// Pin PATH to a stub dir carrying only a `code` CLI: the LSP step's editor
|
|
1307
|
+
// detection (and the ambient-harness process signals) must not depend on
|
|
1308
|
+
// what happens to be installed on the host running the tests.
|
|
1309
|
+
let prevPath: string | undefined;
|
|
1310
|
+
beforeEach(() => {
|
|
1311
|
+
prevPath = process.env.PATH;
|
|
1312
|
+
const dir = stubPath();
|
|
1313
|
+
writeFileSync(join(dir, "code"), "");
|
|
1314
|
+
process.env.PATH = dir;
|
|
1315
|
+
});
|
|
1316
|
+
afterEach(() => {
|
|
1317
|
+
process.env.PATH = prevPath;
|
|
1318
|
+
});
|
|
1319
|
+
|
|
1320
|
+
withShell(
|
|
1321
|
+
"--dry-run previews every DETECTED step (completions + lsp + mcp), writing nothing",
|
|
1322
|
+
async () => {
|
|
1323
|
+
const cwd = tmp("pragma-setup-proj-");
|
|
1324
|
+
mkdirSync(join(cwd, ".cursor"), { recursive: true }); // harness detected
|
|
1325
|
+
const outcome = await executeVerb(
|
|
1326
|
+
setupSelfVerb,
|
|
1327
|
+
{},
|
|
1328
|
+
DRY,
|
|
1329
|
+
bootRuntime(FLAGS, cwd),
|
|
1330
|
+
);
|
|
1331
|
+
expect(outcome.exitCode).toBe(0);
|
|
1332
|
+
const plan = outcome.stdout ?? "";
|
|
1333
|
+
// EVERY target is a visible row — including the ones that will skip. The
|
|
1334
|
+
// run-all used to build its choices only from detectable steps, so a
|
|
1335
|
+
// target it could not offer vanished from the plan and from the recap.
|
|
1336
|
+
for (const id of ["config", "completions", "lsp", "mcp", "skills"]) {
|
|
1337
|
+
expect(plan).toContain(id);
|
|
1338
|
+
}
|
|
1339
|
+
expect(plan).toContain(`${SHELL} →`); // completions row
|
|
1340
|
+
expect(plan).not.toContain("Prompt"); // recap gate / multiselects filtered
|
|
1341
|
+
// Nothing is written by a preview.
|
|
1342
|
+
expect(existsSync(completionScriptPath(SHELL as ShellId))).toBe(false);
|
|
1343
|
+
expect(existsSync(join(cwd, ".cursor", "mcp.json"))).toBe(false);
|
|
1344
|
+
},
|
|
1345
|
+
);
|
|
1346
|
+
|
|
1347
|
+
it("a failing LSP step no longer aborts the run — MCP and skills still apply, exit reflects the failure (S1-1)", async () => {
|
|
1348
|
+
// The audit's headline break: with an unsatisfiable LSP prerequisite the
|
|
1349
|
+
// single advertised onboarding command configured NOTHING (no MCP write,
|
|
1350
|
+
// no skills link) and exited 1. Steps are independent — one failure must
|
|
1351
|
+
// report, let the rest proceed, and only then fail the run. Here the LSP
|
|
1352
|
+
// step has an editor (`code` stub on PATH) but no `bun`, so its fetch
|
|
1353
|
+
// fails; MCP (.cursor) and skills (a seeded skill) must still apply.
|
|
1354
|
+
const prevData = process.env.XDG_DATA_HOME;
|
|
1355
|
+
process.env.XDG_DATA_HOME = tmp("pragma-s11-data-"); // jail the staging dir
|
|
617
1356
|
const cwd = tmp("pragma-setup-proj-");
|
|
618
|
-
mkdirSync(join(cwd, ".cursor"), { recursive: true });
|
|
619
|
-
const
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
1357
|
+
mkdirSync(join(cwd, ".cursor"), { recursive: true });
|
|
1358
|
+
const skillDir = join(cwd, ".pragma", "skills", "s");
|
|
1359
|
+
mkdirSync(skillDir, { recursive: true });
|
|
1360
|
+
writeFileSync(
|
|
1361
|
+
join(skillDir, "SKILL.md"),
|
|
1362
|
+
"---\nname: s\ndescription: A skill.\n---\n",
|
|
624
1363
|
);
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
1364
|
+
let thrown: unknown;
|
|
1365
|
+
try {
|
|
1366
|
+
// `--scope both`: the failing target (lsp) is global-only, and the two
|
|
1367
|
+
// that must survive it write the project band, so the run has to cover
|
|
1368
|
+
// both. A bare run-all is the GLOBAL band now and would not touch this
|
|
1369
|
+
// repository at all.
|
|
1370
|
+
await executeVerb(
|
|
1371
|
+
setupSelfVerb,
|
|
1372
|
+
{ scope: "both" },
|
|
1373
|
+
YES,
|
|
1374
|
+
bootRuntime(FLAGS, cwd),
|
|
1375
|
+
);
|
|
1376
|
+
} catch (error) {
|
|
1377
|
+
thrown = error;
|
|
1378
|
+
} finally {
|
|
1379
|
+
process.env.XDG_DATA_HOME = prevData;
|
|
1380
|
+
}
|
|
1381
|
+
// The satisfiable steps ran to completion...
|
|
1382
|
+
expect(existsSync(join(cwd, ".cursor", "mcp.json"))).toBe(true);
|
|
1383
|
+
expect(existsSync(join(cwd, ".agents", "skills", "s"))).toBe(true);
|
|
1384
|
+
// ...and the run still failed, with the ORIGINAL step error surfaced.
|
|
1385
|
+
expect(thrown).toBeDefined();
|
|
1386
|
+
const err = asPragmaError(thrown);
|
|
1387
|
+
expect(err.code).toBe("UNSUPPORTED");
|
|
1388
|
+
// One failed row reports ITS OWN cause — a count would throw away the only
|
|
1389
|
+
// sentence that says what is wrong.
|
|
1390
|
+
expect(err.message).toContain("lsp");
|
|
1391
|
+
expect(err.message).toContain("bun");
|
|
634
1392
|
});
|
|
635
1393
|
|
|
636
1394
|
it("omits skills gracefully when none are discovered (no mid-wizard EMPTY_RESULTS)", async () => {
|
|
@@ -643,7 +1401,11 @@ describe("setup (run-all wizard)", () => {
|
|
|
643
1401
|
bootRuntime(FLAGS, tmp("pragma-setup-proj-")),
|
|
644
1402
|
);
|
|
645
1403
|
expect(outcome.exitCode).toBe(0);
|
|
646
|
-
|
|
1404
|
+
// The skills row is PRESENT and says why it cannot act, rather than being
|
|
1405
|
+
// dropped from a plan that then claims completeness.
|
|
1406
|
+
expect(outcome.stdout).toContain("skills");
|
|
1407
|
+
expect(outcome.stdout).toContain("no skills installed");
|
|
1408
|
+
expect(outcome.stdout).toContain("lsp");
|
|
647
1409
|
});
|
|
648
1410
|
});
|
|
649
1411
|
|
|
@@ -669,7 +1431,7 @@ describe("lazy-React discipline (PROTECTED)", () => {
|
|
|
669
1431
|
});
|
|
670
1432
|
|
|
671
1433
|
describe("setup — mixed-noun wiring & MCP surface", () => {
|
|
672
|
-
it("registers a single `setup` command with an action and
|
|
1434
|
+
it("registers a single `setup` command with an action and five sub-verbs", () => {
|
|
673
1435
|
const program = projectCli([setupModule]);
|
|
674
1436
|
const setups = program.commands.filter((c) => c.name() === "setup");
|
|
675
1437
|
expect(setups).toHaveLength(1);
|
|
@@ -678,9 +1440,10 @@ describe("setup — mixed-noun wiring & MCP surface", () => {
|
|
|
678
1440
|
expect(setup?.options.map((o) => o.long)).toEqual(
|
|
679
1441
|
expect.arrayContaining(["--dry-run", "--undo", "--yes"]),
|
|
680
1442
|
);
|
|
681
|
-
// The
|
|
1443
|
+
// The five CLI-only sub-verbs hang under it — one per target-table row.
|
|
682
1444
|
expect(setup?.commands.map((c) => c.name()).sort()).toEqual([
|
|
683
1445
|
"completions",
|
|
1446
|
+
"config",
|
|
684
1447
|
"lsp",
|
|
685
1448
|
"mcp",
|
|
686
1449
|
"skills",
|