@canonical/pragma-cli 0.38.0 → 0.42.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 +3 -3
- package/dist/package.json +16 -16
- package/dist/pragma.conf.js +273 -176
- package/dist/pragma.conf.js.map +1 -1
- package/dist/src/capabilities/capabilities/capabilities.verb.js +2 -0
- package/dist/src/capabilities/capabilities/capabilities.verb.js.map +1 -1
- package/dist/src/capabilities/capabilities/catalog.js +47 -51
- package/dist/src/capabilities/capabilities/catalog.js.map +1 -1
- package/dist/src/capabilities/capabilities/index.js +2 -0
- package/dist/src/capabilities/capabilities/index.js.map +1 -1
- package/dist/src/capabilities/capabilities/types.js +5 -5
- package/dist/src/capabilities/colophon/colophon.render.js +7 -4
- package/dist/src/capabilities/colophon/colophon.render.js.map +1 -1
- package/dist/src/capabilities/colophon/colophon.verb.js +1 -0
- package/dist/src/capabilities/colophon/colophon.verb.js.map +1 -1
- package/dist/src/capabilities/colophon/markdownTerminal.js.map +1 -1
- package/dist/src/capabilities/config/get.verb.js +2 -0
- package/dist/src/capabilities/config/get.verb.js.map +1 -1
- package/dist/src/capabilities/config/runField.js +2 -2
- package/dist/src/capabilities/config/runField.js.map +1 -1
- package/dist/src/capabilities/config/set.verb.js +2 -0
- package/dist/src/capabilities/config/set.verb.js.map +1 -1
- package/dist/src/capabilities/config/show.render.js.map +1 -1
- package/dist/src/capabilities/config/show.verb.js +1 -0
- package/dist/src/capabilities/config/show.verb.js.map +1 -1
- package/dist/src/capabilities/config/unset.verb.js +2 -0
- package/dist/src/capabilities/config/unset.verb.js.map +1 -1
- package/dist/src/capabilities/create/constants.js +8 -5
- package/dist/src/capabilities/create/constants.js.map +1 -1
- package/dist/src/capabilities/create/create.render.js.map +1 -1
- package/dist/src/capabilities/create/create.verb.js +36 -7
- package/dist/src/capabilities/create/create.verb.js.map +1 -1
- package/dist/src/capabilities/create/createSurface.generated.js +22 -0
- package/dist/src/capabilities/create/createSurface.generated.js.map +1 -1
- package/dist/src/capabilities/create/index.js +1 -1
- package/dist/src/capabilities/create/pickGenerator.js +1 -0
- package/dist/src/capabilities/create/pickGenerator.js.map +1 -1
- package/dist/src/capabilities/distribution.js +3 -1
- package/dist/src/capabilities/distribution.js.map +1 -1
- package/dist/src/capabilities/doctor/checks/checkPackageRefs.js +10 -2
- package/dist/src/capabilities/doctor/checks/checkPackageRefs.js.map +1 -1
- package/dist/src/capabilities/doctor/checks/targetHealth.js.map +1 -1
- package/dist/src/capabilities/doctor/doctor.verb.js +2 -0
- package/dist/src/capabilities/doctor/doctor.verb.js.map +1 -1
- package/dist/src/capabilities/graph/connect.verb.js +2 -0
- package/dist/src/capabilities/graph/connect.verb.js.map +1 -1
- package/dist/src/capabilities/graph/inspect.render.js +7 -1
- package/dist/src/capabilities/graph/inspect.render.js.map +1 -1
- package/dist/src/capabilities/graph/inspect.verb.js +2 -0
- package/dist/src/capabilities/graph/inspect.verb.js.map +1 -1
- package/dist/src/capabilities/graph/query.render.js +37 -5
- package/dist/src/capabilities/graph/query.render.js.map +1 -1
- package/dist/src/capabilities/graph/query.verb.js +2 -0
- package/dist/src/capabilities/graph/query.verb.js.map +1 -1
- package/dist/src/capabilities/graph/queryText.js +28 -0
- package/dist/src/capabilities/graph/queryText.js.map +1 -1
- package/dist/src/capabilities/graph/runConnect.js +3 -3
- package/dist/src/capabilities/graph/runConnect.js.map +1 -1
- package/dist/src/capabilities/graph/runQuery.js +8 -6
- package/dist/src/capabilities/graph/runQuery.js.map +1 -1
- package/dist/src/capabilities/info/info.verb.js +2 -0
- package/dist/src/capabilities/info/info.verb.js.map +1 -1
- package/dist/src/capabilities/meta/mcp.verb.js +1 -0
- package/dist/src/capabilities/meta/mcp.verb.js.map +1 -1
- package/dist/src/capabilities/ontology/verbs.js +7 -10
- package/dist/src/capabilities/ontology/verbs.js.map +1 -1
- package/dist/src/capabilities/prompt/verbs.js +5 -4
- package/dist/src/capabilities/prompt/verbs.js.map +1 -1
- package/dist/src/capabilities/resources/provider.js +6 -3
- package/dist/src/capabilities/resources/provider.js.map +1 -1
- package/dist/src/capabilities/setup/operations/writability.js.map +1 -1
- package/dist/src/capabilities/setup/plan.render.js.map +1 -1
- package/dist/src/capabilities/setup/setup.verb.js +13 -5
- package/dist/src/capabilities/setup/setup.verb.js.map +1 -1
- package/dist/src/capabilities/shared/calls.js +16 -0
- package/dist/src/capabilities/shared/calls.js.map +1 -0
- package/dist/src/capabilities/shared/packageManager.js.map +1 -1
- package/dist/src/capabilities/skill/discover.js.map +1 -1
- package/dist/src/capabilities/skill/render.js +7 -5
- package/dist/src/capabilities/skill/render.js.map +1 -1
- package/dist/src/capabilities/skill/verbs.js +5 -4
- package/dist/src/capabilities/skill/verbs.js.map +1 -1
- package/dist/src/capabilities/sources/collectStatus.js +6 -1
- package/dist/src/capabilities/sources/collectStatus.js.map +1 -1
- package/dist/src/capabilities/sources/index.js +4 -3
- package/dist/src/capabilities/sources/index.js.map +1 -1
- package/dist/src/capabilities/sources/reset.render.js +37 -0
- package/dist/src/capabilities/sources/reset.render.js.map +1 -0
- package/dist/src/capabilities/sources/reset.verb.js +51 -0
- package/dist/src/capabilities/sources/reset.verb.js.map +1 -0
- package/dist/src/capabilities/sources/runReset.js +59 -0
- package/dist/src/capabilities/sources/runReset.js.map +1 -0
- package/dist/src/capabilities/sources/runUpdate.js +7 -6
- package/dist/src/capabilities/sources/runUpdate.js.map +1 -1
- package/dist/src/capabilities/sources/status.render.js +24 -8
- package/dist/src/capabilities/sources/status.render.js.map +1 -1
- package/dist/src/capabilities/sources/status.verb.js +1 -0
- package/dist/src/capabilities/sources/status.verb.js.map +1 -1
- package/dist/src/capabilities/sources/update.verb.js +2 -0
- package/dist/src/capabilities/sources/update.verb.js.map +1 -1
- package/dist/src/capabilities/upgrade/upgrade.verb.js +1 -0
- package/dist/src/capabilities/upgrade/upgrade.verb.js.map +1 -1
- package/dist/src/capabilities/version/version.verb.js +1 -0
- package/dist/src/capabilities/version/version.verb.js.map +1 -1
- package/dist/src/constants.js +4 -3
- package/dist/src/constants.js.map +1 -1
- package/dist/src/kernel/completion/complete.js.map +1 -1
- package/dist/src/kernel/completion/emitScripts.js.map +1 -1
- package/dist/src/kernel/completion/parse.js.map +1 -1
- package/dist/src/kernel/completion/rank.js.map +1 -1
- package/dist/src/kernel/completion/resolve.js.map +1 -1
- package/dist/src/kernel/config/readConfig.js.map +1 -1
- package/dist/src/kernel/error/PragmaError.js.map +1 -1
- package/dist/src/kernel/error/index.js +1 -1
- package/dist/src/kernel/error/index.js.map +1 -1
- package/dist/src/kernel/error/recovery.js +24 -42
- package/dist/src/kernel/error/recovery.js.map +1 -1
- package/dist/src/kernel/packs/collect.js +123 -9
- package/dist/src/kernel/packs/collect.js.map +1 -1
- package/dist/src/kernel/packs/compile.js +74 -15
- package/dist/src/kernel/packs/compile.js.map +1 -1
- package/dist/src/kernel/packs/graphql/fetchGraphqlLookup.js +3 -3
- package/dist/src/kernel/packs/graphql/fetchGraphqlLookup.js.map +1 -1
- package/dist/src/kernel/packs/iri.js +2 -4
- package/dist/src/kernel/packs/iri.js.map +1 -1
- package/dist/src/kernel/packs/renderPack.js +90 -50
- package/dist/src/kernel/packs/renderPack.js.map +1 -1
- package/dist/src/kernel/packs/resolveEntity.js +202 -90
- package/dist/src/kernel/packs/resolveEntity.js.map +1 -1
- package/dist/src/kernel/packs/runBodies.js +89 -30
- package/dist/src/kernel/packs/runBodies.js.map +1 -1
- package/dist/src/kernel/packs/schema.js +66 -12
- package/dist/src/kernel/packs/schema.js.map +1 -1
- package/dist/src/kernel/packs/sparql/buildListQuery.js +80 -13
- package/dist/src/kernel/packs/sparql/buildListQuery.js.map +1 -1
- package/dist/src/kernel/packs/sparql/buildLookupQuery.js +41 -13
- package/dist/src/kernel/packs/sparql/buildLookupQuery.js.map +1 -1
- package/dist/src/kernel/packs/sparql/filterValues.js +32 -23
- package/dist/src/kernel/packs/sparql/filterValues.js.map +1 -1
- package/dist/src/kernel/packs/sparql/runSelect.js +3 -5
- package/dist/src/kernel/packs/sparql/runSelect.js.map +1 -1
- package/dist/src/kernel/packs/storyRules.js +31 -4
- package/dist/src/kernel/packs/storyRules.js.map +1 -1
- package/dist/src/kernel/packs/tierScope.js +3 -3
- package/dist/src/kernel/packs/tierScope.js.map +1 -1
- package/dist/src/kernel/packs/types.js +4 -0
- package/dist/src/kernel/packs/types.js.map +1 -1
- package/dist/src/kernel/project/cli/buildProgram.js +4 -0
- package/dist/src/kernel/project/cli/buildProgram.js.map +1 -1
- package/dist/src/kernel/project/cli/dispatch.js +32 -8
- package/dist/src/kernel/project/cli/dispatch.js.map +1 -1
- package/dist/src/kernel/project/cli/globalFlags.js.map +1 -1
- package/dist/src/kernel/project/cli/suggestNames.js +44 -1
- package/dist/src/kernel/project/cli/suggestNames.js.map +1 -1
- package/dist/src/kernel/project/cli/verbHelp.js +17 -18
- package/dist/src/kernel/project/cli/verbHelp.js.map +1 -1
- package/dist/src/kernel/project/mcp/buildServer.js +11 -4
- package/dist/src/kernel/project/mcp/buildServer.js.map +1 -1
- package/dist/src/kernel/project/mcp/envelope.js +3 -2
- package/dist/src/kernel/project/mcp/envelope.js.map +1 -1
- package/dist/src/kernel/project/mcp/instructions.js +101 -31
- package/dist/src/kernel/project/mcp/instructions.js.map +1 -1
- package/dist/src/kernel/project/mcp/registerVerb.js +59 -23
- package/dist/src/kernel/project/mcp/registerVerb.js.map +1 -1
- package/dist/src/kernel/render/envelope.js.map +1 -1
- package/dist/src/kernel/render/renderers.js +3 -3
- package/dist/src/kernel/render/renderers.js.map +1 -1
- package/dist/src/kernel/render/turtle.js +21 -4
- package/dist/src/kernel/render/turtle.js.map +1 -1
- package/dist/src/kernel/runtime/boot.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/build.js +54 -1
- package/dist/src/kernel/runtime/graphpack/build.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/manifest.js +14 -0
- package/dist/src/kernel/runtime/graphpack/manifest.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/read.js +3 -5
- package/dist/src/kernel/runtime/graphpack/read.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/schemas.js +1 -0
- package/dist/src/kernel/runtime/graphpack/schemas.js.map +1 -1
- package/dist/src/kernel/runtime/loadSession.js +8 -1
- package/dist/src/kernel/runtime/loadSession.js.map +1 -1
- package/dist/src/kernel/runtime/packVersion.js +75 -0
- package/dist/src/kernel/runtime/packVersion.js.map +1 -0
- package/dist/src/kernel/runtime/readEntity.js +43 -4
- package/dist/src/kernel/runtime/readEntity.js.map +1 -1
- package/dist/src/kernel/runtime/refs/resolve.js.map +1 -1
- package/dist/src/kernel/runtime/resolveSources.js +61 -9
- package/dist/src/kernel/runtime/resolveSources.js.map +1 -1
- package/dist/src/kernel/runtime/storeReadiness.js +3 -2
- package/dist/src/kernel/runtime/storeReadiness.js.map +1 -1
- package/dist/src/kernel/spec/call.js +167 -0
- package/dist/src/kernel/spec/call.js.map +1 -0
- package/dist/src/kernel/spec/emitReference.js +9 -13
- package/dist/src/kernel/spec/emitReference.js.map +1 -1
- package/dist/src/kernel/spec/emitSurface.js +8 -1
- package/dist/src/kernel/spec/emitSurface.js.map +1 -1
- package/dist/src/kernel/spec/guidance.js +63 -0
- package/dist/src/kernel/spec/guidance.js.map +1 -0
- package/dist/src/kernel/spec/wireType.js +36 -0
- package/dist/src/kernel/spec/wireType.js.map +1 -0
- package/package.json +16 -16
- package/pragma.conf.ts +294 -177
- package/src/capabilities/block.parity.test.ts +1 -1
- package/src/capabilities/callRule.test.ts +147 -0
- package/src/capabilities/capabilities/capabilities.test.ts +47 -19
- package/src/capabilities/capabilities/capabilities.verb.ts +2 -0
- package/src/capabilities/capabilities/catalog.ts +60 -55
- package/src/capabilities/capabilities/index.ts +2 -0
- package/src/capabilities/capabilities/types.ts +16 -19
- package/src/capabilities/colophon/colophon.render.ts +9 -5
- package/src/capabilities/colophon/colophon.verb.ts +1 -0
- package/src/capabilities/config/get.verb.ts +2 -0
- package/src/capabilities/config/runField.ts +3 -3
- package/src/capabilities/config/set.verb.ts +3 -0
- package/src/capabilities/config/show.verb.ts +2 -0
- package/src/capabilities/config/unset.verb.ts +2 -0
- package/src/capabilities/create/constants.ts +8 -5
- package/src/capabilities/create/create.mcp.test.ts +173 -1
- package/src/capabilities/create/create.test.ts +4 -2
- package/src/capabilities/create/create.verb.ts +45 -3
- package/src/capabilities/create/createGrammar.test.ts +41 -5
- package/src/capabilities/create/createSurface.generated.ts +22 -0
- package/src/capabilities/create/crossCli.subprocess.test.ts +53 -14
- package/src/capabilities/create/index.ts +1 -1
- package/src/capabilities/create/pickGenerator.ts +1 -0
- package/src/capabilities/create/shippedCreate.subprocess.test.ts +28 -7
- package/src/capabilities/distribution.test.ts +6 -3
- package/src/capabilities/distribution.ts +6 -0
- package/src/capabilities/doctor/checks/checkPackageRefs.ts +14 -2
- package/src/capabilities/doctor/doctor.test.ts +38 -1
- package/src/capabilities/doctor/doctor.verb.ts +2 -0
- package/src/capabilities/emptyRecovery.test.ts +5 -0
- package/src/capabilities/graph/connect.verb.ts +2 -0
- package/src/capabilities/graph/inspect.render.ts +9 -1
- package/src/capabilities/graph/inspect.test.ts +73 -0
- package/src/capabilities/graph/inspect.verb.ts +2 -0
- package/src/capabilities/graph/query.render.test.ts +35 -0
- package/src/capabilities/graph/query.render.ts +45 -7
- package/src/capabilities/graph/query.shipped.exec.test.ts +44 -0
- package/src/capabilities/graph/query.verb.ts +2 -0
- package/src/capabilities/graph/queryText.test.ts +55 -1
- package/src/capabilities/graph/queryText.ts +47 -0
- package/src/capabilities/graph/runConnect.ts +4 -5
- package/src/capabilities/graph/runQuery.ts +23 -9
- package/src/capabilities/info/info.verb.ts +2 -0
- package/src/capabilities/links.shipped.exec.test.ts +398 -0
- package/src/capabilities/listBudget.shipped.exec.test.ts +2 -2
- package/src/capabilities/listLookup.shipped.exec.test.ts +122 -13
- package/src/capabilities/listQuery.shipped.exec.test.ts +39 -16
- package/src/capabilities/lookupBudget.shipped.exec.test.ts +208 -0
- package/src/capabilities/meta/mcp.verb.ts +2 -0
- package/src/capabilities/ontology/ontology.test.ts +24 -0
- package/src/capabilities/ontology/verbs.ts +16 -12
- package/src/capabilities/prompt/verbs.ts +10 -4
- package/src/capabilities/resources/provider.ts +7 -3
- package/src/capabilities/resources/resources.test.ts +2 -1
- package/src/capabilities/setup/setup.verb.ts +16 -0
- package/src/capabilities/shared/calls.ts +20 -0
- package/src/capabilities/skill/render.ts +9 -6
- package/src/capabilities/skill/verbs.ts +8 -4
- package/src/capabilities/sources/collectStatus.ts +6 -1
- package/src/capabilities/sources/index.ts +4 -3
- package/src/capabilities/sources/reset.render.test.ts +33 -0
- package/src/capabilities/sources/reset.render.ts +43 -0
- package/src/capabilities/sources/reset.verb.ts +59 -0
- package/src/capabilities/sources/runReset.ts +65 -0
- package/src/capabilities/sources/runUpdate.ts +12 -11
- package/src/capabilities/sources/sources.test.ts +158 -3
- package/src/capabilities/sources/status.render.test.ts +63 -1
- package/src/capabilities/sources/status.render.ts +25 -8
- package/src/capabilities/sources/status.verb.ts +2 -0
- package/src/capabilities/sources/types.ts +26 -0
- package/src/capabilities/sources/update.verb.ts +3 -0
- package/src/capabilities/surface.test.ts +29 -4
- package/src/capabilities/token.parity.test.ts +3 -2
- package/src/capabilities/tokenGraph.shipped.exec.test.ts +14 -2
- package/src/capabilities/upgrade/upgrade.verb.ts +1 -0
- package/src/capabilities/variable.parity.test.ts +2 -1
- package/src/capabilities/version/version.verb.ts +1 -0
- package/src/capabilities/wireContract.test.ts +110 -0
- package/src/constants.ts +4 -3
- package/src/kernel/completion/shellDrive.test.ts +2 -2
- package/src/kernel/copy.test.ts +1 -1
- package/src/kernel/error/PragmaError.test.ts +7 -3
- package/src/kernel/error/index.ts +1 -1
- package/src/kernel/error/recovery.test.ts +52 -18
- package/src/kernel/error/recovery.ts +25 -46
- package/src/kernel/error/types.ts +1 -1
- package/src/kernel/packs/collect.test.ts +79 -0
- package/src/kernel/packs/collect.ts +151 -11
- package/src/kernel/packs/compile.test.ts +107 -10
- package/src/kernel/packs/compile.ts +100 -11
- package/src/kernel/packs/filteredEmpty.test.ts +22 -5
- package/src/kernel/packs/graphql/fetchGraphqlLookup.ts +5 -6
- package/src/kernel/packs/graphqlSource.test.ts +42 -0
- package/src/kernel/packs/iri.ts +5 -4
- package/src/kernel/packs/listNotice.test.ts +1 -1
- package/src/kernel/packs/nounLink.test.ts +385 -0
- package/src/kernel/packs/renderPack.ts +137 -57
- package/src/kernel/packs/resolveEntity.test.ts +170 -1
- package/src/kernel/packs/resolveEntity.ts +281 -101
- package/src/kernel/packs/runBodies.ts +133 -31
- package/src/kernel/packs/schema.ts +77 -12
- package/src/kernel/packs/sparql/buildListQuery.test.ts +78 -1
- package/src/kernel/packs/sparql/buildListQuery.ts +106 -16
- package/src/kernel/packs/sparql/buildLookupQuery.ts +44 -13
- package/src/kernel/packs/sparql/filterValues.test.ts +36 -4
- package/src/kernel/packs/sparql/filterValues.ts +50 -31
- package/src/kernel/packs/sparql/runSelect.ts +4 -6
- package/src/kernel/packs/storyRules.ts +37 -4
- package/src/kernel/packs/tierScope.test.ts +120 -6
- package/src/kernel/packs/tierScope.ts +5 -7
- package/src/kernel/packs/toolDescription.test.ts +59 -74
- package/src/kernel/packs/types.ts +93 -11
- package/src/kernel/project/cli/buildProgram.ts +4 -0
- package/src/kernel/project/cli/dispatch.ts +34 -8
- package/src/kernel/project/cli/suggest.test.ts +77 -0
- package/src/kernel/project/cli/suggestNames.ts +47 -1
- package/src/kernel/project/cli/verbHelp.ts +16 -19
- package/src/kernel/project/mcp/buildServer.ts +15 -4
- package/src/kernel/project/mcp/envelope.ts +3 -2
- package/src/kernel/project/mcp/instructions.ts +123 -35
- package/src/kernel/project/mcp/prompts/prompts.test.ts +136 -20
- package/src/kernel/project/mcp/registerVerb.test.ts +28 -0
- package/src/kernel/project/mcp/registerVerb.ts +67 -29
- package/src/kernel/render/__snapshots__/renderers.test.ts.snap +1 -1
- package/src/kernel/render/renderers.test.ts +2 -2
- package/src/kernel/render/renderers.ts +3 -3
- package/src/kernel/render/turtle.ts +24 -4
- package/src/kernel/runtime/graphpack/build.ts +58 -1
- package/src/kernel/runtime/graphpack/graphpack.test.ts +20 -0
- package/src/kernel/runtime/graphpack/manifest.ts +15 -0
- package/src/kernel/runtime/graphpack/read.ts +4 -6
- package/src/kernel/runtime/graphpack/schemas.ts +1 -0
- package/src/kernel/runtime/graphpack/types.ts +13 -0
- package/src/kernel/runtime/loadSession.ts +8 -1
- package/src/kernel/runtime/packVersion.test.ts +61 -0
- package/src/kernel/runtime/packVersion.ts +84 -0
- package/src/kernel/runtime/readEntity.ts +61 -6
- package/src/kernel/runtime/resolveSources.test.ts +90 -7
- package/src/kernel/runtime/resolveSources.ts +91 -10
- package/src/kernel/runtime/storeReadiness.ts +4 -4
- package/src/kernel/runtime/types.ts +12 -0
- package/src/kernel/spec/call.test.ts +95 -0
- package/src/kernel/spec/call.ts +186 -0
- package/src/kernel/spec/emitReference.ts +9 -13
- package/src/kernel/spec/emitSurface.ts +9 -1
- package/src/kernel/spec/guidance.test.ts +49 -0
- package/src/kernel/spec/guidance.ts +68 -0
- package/src/kernel/spec/index.ts +4 -0
- package/src/kernel/spec/surfaceConformance.test.ts +2 -2
- package/src/kernel/spec/types.ts +64 -7
- package/src/kernel/spec/wireType.ts +53 -0
- package/src/testing/behavioral/README.md +1 -1
- package/src/testing/behavioral/errorMatrix.mcp.test.ts +2 -1
- package/src/testing/behavioral/journeys.firstInstall.test.ts +72 -2
- package/src/testing/behavioral/parity.test.ts +40 -0
- package/src/testing/eval/__snapshots__/eval.test.ts.snap +1 -1
- package/src/testing/eval/cases/assembly.ts +4 -3
- package/src/testing/eval/cases/stable.ts +2 -3
- package/src/testing/helpers/callSchema.ts +40 -0
- package/src/testing/helpers/parity.ts +35 -2
- package/src/testing/setupCallChecking.ts +30 -0
- package/dist/src/capabilities/capabilities/hints.js +0 -223
- package/dist/src/capabilities/capabilities/hints.js.map +0 -1
- package/src/capabilities/capabilities/hints.ts +0 -266
|
@@ -3,13 +3,16 @@ import { tmpdir } from "node:os";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { GetPromptRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
5
5
|
import { afterEach, describe, expect, it } from "vitest";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
BLOCKS_SENTENCE,
|
|
8
|
+
CONVENTIONS,
|
|
9
|
+
} from "../../../../capabilities/capabilities/catalog.js";
|
|
7
10
|
import { capabilities } from "../../../../capabilities/index.js";
|
|
8
11
|
import type { McpHarness } from "../../../../testing/helpers/projectMcp.js";
|
|
9
12
|
import { projectMcp } from "../../../../testing/helpers/projectMcp.js";
|
|
10
13
|
import type { PragmaRuntime } from "../../../runtime/types.js";
|
|
11
|
-
import { emitSurface } from "../../../spec/emitSurface.js";
|
|
12
|
-
import type { CapabilityModule } from "../../../spec/types.js";
|
|
14
|
+
import { emitSurface, toolName } from "../../../spec/emitSurface.js";
|
|
15
|
+
import type { CapabilityModule, VerbSpec } from "../../../spec/types.js";
|
|
13
16
|
import { buildInstructions, INSTRUCTIONS_MAX_CHARS } from "../instructions.js";
|
|
14
17
|
import { fillTemplate, promptProvider } from "./provider.js";
|
|
15
18
|
import { readPrompts } from "./source.js";
|
|
@@ -31,21 +34,123 @@ afterEach(async () => {
|
|
|
31
34
|
});
|
|
32
35
|
|
|
33
36
|
describe("instructions — handshake orientation (PROTECTED)", () => {
|
|
34
|
-
|
|
37
|
+
// A client may defer tools: the agent sees tool NAMES and this text, and a
|
|
38
|
+
// description it never loads cannot steer it. So the text carries a question →
|
|
39
|
+
// tool index GENERATED from each verb's `useWhen` (it replaced the prose
|
|
40
|
+
// "discovery sequence" this case used to look for).
|
|
41
|
+
const reads = capabilities
|
|
42
|
+
.flatMap((module) => module.verbs)
|
|
43
|
+
.filter(
|
|
44
|
+
(verb) =>
|
|
45
|
+
!verb.hidden && verb.capability.mcp.expose && !verb.capability.mutates,
|
|
46
|
+
);
|
|
47
|
+
const isTrio = (verb: VerbSpec): boolean =>
|
|
48
|
+
["list", "lookup", "sample"].includes(verb.path[1] ?? "");
|
|
49
|
+
|
|
50
|
+
it("indexes every read tool outside a list/lookup/sample trio by the question it answers", () => {
|
|
51
|
+
const text = buildInstructions(capabilities);
|
|
52
|
+
const standalone = reads.filter((verb) => !isTrio(verb));
|
|
53
|
+
expect(standalone.length).toBeGreaterThan(10);
|
|
54
|
+
for (const verb of standalone) {
|
|
55
|
+
const clause = (verb.useWhen as string).replace(/^when asked /, "");
|
|
56
|
+
expect(text).toContain(`${toolName(verb.path)} — ${clause}`);
|
|
57
|
+
}
|
|
58
|
+
// The story that failed: asked which components use a token.
|
|
59
|
+
expect(text).toMatch(/token_consumers — which components use a token/);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("explains the trio once, promising no noun a verb it lacks", () => {
|
|
35
63
|
const text = buildInstructions(capabilities);
|
|
36
|
-
expect(text.
|
|
37
|
-
expect(text).
|
|
38
|
-
expect(text
|
|
64
|
+
expect(text.match(/<noun>_list/g)).toHaveLength(1);
|
|
65
|
+
expect(text).toMatch(/block\*/); // has a sample
|
|
66
|
+
expect(text).toMatch(/\btier,/); // list + lookup, no sample
|
|
67
|
+
expect(text).toMatch(/implementation†/); // list only
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("is generated: a new read verb adds its own line, a new write joins the plan-first list", () => {
|
|
71
|
+
const base = capabilities.find((m) => m.name === "info")
|
|
72
|
+
?.verbs[0] as VerbSpec;
|
|
73
|
+
const added: CapabilityModule = {
|
|
74
|
+
name: "weather",
|
|
75
|
+
verbs: [
|
|
76
|
+
{
|
|
77
|
+
...base,
|
|
78
|
+
path: ["weather", "today"],
|
|
79
|
+
useWhen: "when asked if it rains",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
...base,
|
|
83
|
+
path: ["weather", "seed"],
|
|
84
|
+
capability: { ...base.capability, mutates: true },
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
const text = buildInstructions([...capabilities, added]);
|
|
89
|
+
expect(text).toMatch(/^weather_today — if it rains$/m);
|
|
90
|
+
expect(text).toMatch(/plan-first[^\n]*weather_seed/);
|
|
91
|
+
expect(buildInstructions([added])).toBe(""); // no module declares an orientation
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("fits the HARD ceiling clients cut server instructions at", () => {
|
|
95
|
+
// Unlike the catalogue budget this is not raised on measurement: text past
|
|
96
|
+
// about 2 KB is text no agent reads. Tighten a sentence instead.
|
|
97
|
+
expect(INSTRUCTIONS_MAX_CHARS).toBe(2000);
|
|
98
|
+
const dropped: number[] = [];
|
|
99
|
+
const text = buildInstructions(capabilities, (count) =>
|
|
100
|
+
dropped.push(count),
|
|
101
|
+
);
|
|
102
|
+
expect(text.length).toBeLessThanOrEqual(INSTRUCTIONS_MAX_CHARS);
|
|
103
|
+
// Fitting is the safety net for a project's packs, not for the
|
|
104
|
+
// distribution: its own registry must fit WHOLE.
|
|
105
|
+
expect(
|
|
106
|
+
dropped,
|
|
107
|
+
"the distribution's own index no longer fits — tighten a useWhen sentence; the 2,000 ceiling is what clients keep",
|
|
108
|
+
).toEqual([]);
|
|
39
109
|
});
|
|
40
110
|
|
|
41
|
-
it("
|
|
42
|
-
|
|
43
|
-
|
|
111
|
+
it("when a project's packs overflow it, drops index lines from the end and says so", () => {
|
|
112
|
+
const base = capabilities.find((m) => m.name === "info")
|
|
113
|
+
?.verbs[0] as VerbSpec;
|
|
114
|
+
const crowd: CapabilityModule = {
|
|
115
|
+
name: "crowd",
|
|
116
|
+
verbs: Array.from({ length: 12 }, (_, n) => ({
|
|
117
|
+
...base,
|
|
118
|
+
path: ["crowd", `probe${n}`] as [string, string],
|
|
119
|
+
category: undefined,
|
|
120
|
+
useWhen: `when asked a long question number ${n} about the crowd`,
|
|
121
|
+
})),
|
|
122
|
+
};
|
|
123
|
+
const dropped: number[] = [];
|
|
124
|
+
const text = buildInstructions([...capabilities, crowd], (count) =>
|
|
125
|
+
dropped.push(count),
|
|
126
|
+
);
|
|
127
|
+
expect(text.length).toBeLessThanOrEqual(INSTRUCTIONS_MAX_CHARS);
|
|
128
|
+
expect(dropped).toHaveLength(1);
|
|
129
|
+
expect(text.endsWith(`… and ${dropped[0]} more: call capabilities.`)).toBe(
|
|
130
|
+
true,
|
|
44
131
|
);
|
|
132
|
+
// What is one line whatever the registry holds is never what gets cut …
|
|
133
|
+
expect(text).toContain(BLOCKS_SENTENCE);
|
|
134
|
+
expect(text).toMatch(/plan-first/);
|
|
135
|
+
expect(text).toMatch(/<noun>_list/);
|
|
136
|
+
// … and the verbs that read the store lead the index, so they survive.
|
|
137
|
+
expect(text).toMatch(/^token_consumers — /m);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("emits no heading for an empty group, and skips a verb declaring no useWhen", () => {
|
|
141
|
+
const base = capabilities.find(
|
|
142
|
+
(m) => m.name === "capabilities",
|
|
143
|
+
) as CapabilityModule;
|
|
144
|
+
const { useWhen: _useWhen, ...silent } = base.verbs[0] as VerbSpec;
|
|
145
|
+
const text = buildInstructions([{ ...base, verbs: [silent as VerbSpec] }]);
|
|
146
|
+
expect(text).not.toContain("When asked:");
|
|
147
|
+
expect(text).not.toContain("Also:");
|
|
148
|
+
expect(text).not.toContain("undefined");
|
|
45
149
|
});
|
|
46
150
|
|
|
47
151
|
it("opens with the shared catalog's conventions, verbatim and once", () => {
|
|
48
|
-
// The conventions are authored once (capabilities/catalog.ts)
|
|
152
|
+
// The conventions are authored once (capabilities/catalog.ts) and handed to
|
|
153
|
+
// the kernel as the module's `mcpOrientation`; instructions
|
|
49
154
|
// must OPEN with them, so a second hand-written preamble cannot creep back
|
|
50
155
|
// in and drift from the `capabilities` tool the same handshake carries.
|
|
51
156
|
expect(buildInstructions(capabilities).startsWith(CONVENTIONS.system)).toBe(
|
|
@@ -53,6 +158,17 @@ describe("instructions — handshake orientation (PROTECTED)", () => {
|
|
|
53
158
|
);
|
|
54
159
|
});
|
|
55
160
|
|
|
161
|
+
it("says which tools read components, patterns, layouts and subcomponents", () => {
|
|
162
|
+
// People ask about "components"; the tools are named for blocks. An agent
|
|
163
|
+
// that went looking for a component tool found none and fell to raw SPARQL.
|
|
164
|
+
const text = buildInstructions(capabilities);
|
|
165
|
+
expect(text).toContain(BLOCKS_SENTENCE);
|
|
166
|
+
expect(BLOCKS_SENTENCE).toMatch(/components.*block_list/i);
|
|
167
|
+
// Said in the handshake and in `block_list`'s own description — not a third
|
|
168
|
+
// time in the catalogue's conventions.
|
|
169
|
+
expect(Object.values(CONVENTIONS)).not.toContain(BLOCKS_SENTENCE);
|
|
170
|
+
});
|
|
171
|
+
|
|
56
172
|
it("quotes the resource templates the MCP surface actually advertises", () => {
|
|
57
173
|
// The `pragma:` scheme is covenant-frozen protocol identity
|
|
58
174
|
// (surface.v2.json). The orientation DERIVES it from the emitted surface
|
|
@@ -71,16 +187,16 @@ describe("instructions — handshake orientation (PROTECTED)", () => {
|
|
|
71
187
|
expect(text).toContain("confirm: true");
|
|
72
188
|
});
|
|
73
189
|
|
|
74
|
-
it("
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
//
|
|
190
|
+
it("tells an agent which tool explains empty or stale answers, and which rebuilds", () => {
|
|
191
|
+
// This case used to pin POSITION (`sources_status` before any `_sample`),
|
|
192
|
+
// from when a cold store failed every read and the text was a numbered
|
|
193
|
+
// sequence. A fresh install now answers from the shipped snapshot, and the
|
|
194
|
+
// text is an index whose fixed lines come first so an overflow can only
|
|
195
|
+
// cut the index. What must still hold: the store check is indexed by the
|
|
196
|
+
// situation that calls for it, and the rebuild is among the writes.
|
|
78
197
|
const text = buildInstructions(capabilities);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
expect(storeCheck).toBeGreaterThanOrEqual(0);
|
|
82
|
-
expect(text).toContain("sources_update");
|
|
83
|
-
expect(storeCheck).toBeLessThan(sample);
|
|
198
|
+
expect(text).toMatch(/^sources_status — when results look empty or stale/m);
|
|
199
|
+
expect(text).toMatch(/plan-first[^\n]*sources_update/);
|
|
84
200
|
});
|
|
85
201
|
});
|
|
86
202
|
|
|
@@ -74,6 +74,34 @@ describe("buildZodSchema (params → zod)", () => {
|
|
|
74
74
|
).toBe(true);
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
+
it("a list param takes one bare value, and coercion does not skip the item type", () => {
|
|
78
|
+
const lists = z.object(
|
|
79
|
+
buildZodSchema([
|
|
80
|
+
{ kind: "string[]", name: "name", doc: "n", required: true },
|
|
81
|
+
{
|
|
82
|
+
kind: "enum",
|
|
83
|
+
name: "kind",
|
|
84
|
+
doc: "k",
|
|
85
|
+
values: ["a", "b"],
|
|
86
|
+
repeatable: true,
|
|
87
|
+
},
|
|
88
|
+
]),
|
|
89
|
+
);
|
|
90
|
+
expect(lists.parse({ name: "x", kind: "a" })).toEqual({
|
|
91
|
+
name: ["x"],
|
|
92
|
+
kind: ["a"],
|
|
93
|
+
});
|
|
94
|
+
expect(lists.parse({ name: ["x", "y"] })).toEqual({ name: ["x", "y"] });
|
|
95
|
+
expect(lists.safeParse({}).success).toBe(false);
|
|
96
|
+
expect(lists.safeParse({ name: "x", kind: "z" }).success).toBe(false);
|
|
97
|
+
expect(lists.safeParse({ name: "x", kind: ["a", 7] }).success).toBe(false);
|
|
98
|
+
// Only a STRING is coerced, so a wrong type is refused at the param itself.
|
|
99
|
+
for (const name of [5, { a: 1 }]) {
|
|
100
|
+
const refused = lists.safeParse({ name });
|
|
101
|
+
expect(refused.error?.issues.at(0)?.path).toEqual(["name"]);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
77
105
|
it("applies a declared default when the field is omitted (CLI parity)", () => {
|
|
78
106
|
const withDefault = z.object(
|
|
79
107
|
buildZodSchema([
|
|
@@ -22,8 +22,10 @@ import { z } from "zod";
|
|
|
22
22
|
import { asPragmaError } from "../../error/fromTaskError.js";
|
|
23
23
|
import { PragmaError } from "../../error/index.js";
|
|
24
24
|
import type { InteractionRuntime, PragmaRuntime } from "../../runtime/types.js";
|
|
25
|
+
import { describeTool } from "../../spec/guidance.js";
|
|
25
26
|
import type { McpAnnotations, ParamSpec, VerbSpec } from "../../spec/index.js";
|
|
26
27
|
import { toolName } from "../../spec/index.js";
|
|
28
|
+
import { type WireItem, wireType } from "../../spec/wireType.js";
|
|
27
29
|
import { toolError, toolSuccess } from "./envelope.js";
|
|
28
30
|
|
|
29
31
|
/**
|
|
@@ -57,9 +59,9 @@ function resolveEffectiveCwd(rawCwd: unknown, fallback: string): string {
|
|
|
57
59
|
return rawCwd;
|
|
58
60
|
}
|
|
59
61
|
|
|
60
|
-
/** The
|
|
61
|
-
function
|
|
62
|
-
switch (
|
|
62
|
+
/** The zod type of ONE value of a param. */
|
|
63
|
+
function zodForItem(item: WireItem): z.ZodTypeAny {
|
|
64
|
+
switch (item.kind) {
|
|
63
65
|
case "string":
|
|
64
66
|
return z.string();
|
|
65
67
|
case "number":
|
|
@@ -67,12 +69,30 @@ function zodForParam(param: ParamSpec): z.ZodTypeAny {
|
|
|
67
69
|
case "boolean":
|
|
68
70
|
return z.boolean();
|
|
69
71
|
case "enum":
|
|
70
|
-
return z.enum(
|
|
71
|
-
case "string[]":
|
|
72
|
-
return z.array(z.string());
|
|
72
|
+
return z.enum(item.values as unknown as [string, ...string[]]);
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
/**
|
|
77
|
+
* The base zod type for a param, before `.describe()`/`.optional()`, built
|
|
78
|
+
* from the param's {@link wireType} — the statement the reference prints too.
|
|
79
|
+
*
|
|
80
|
+
* A list is ADVERTISED as a plain array and a bare value is COERCED into one
|
|
81
|
+
* before validation. A preprocess rather than a `string | string[]` union on
|
|
82
|
+
* purpose: the emitted JSON Schema stays `type: "array"`, where a union emits
|
|
83
|
+
* `anyOf`, which the weaker models that most need this read badly. The run
|
|
84
|
+
* bodies already take one value or many, so nothing downstream changes.
|
|
85
|
+
*/
|
|
86
|
+
function zodForParam(param: ParamSpec): z.ZodTypeAny {
|
|
87
|
+
const wire = wireType(param);
|
|
88
|
+
const item = zodForItem(wire.item);
|
|
89
|
+
if (!wire.list) return item;
|
|
90
|
+
return z.preprocess(
|
|
91
|
+
(value) => (typeof value === "string" ? [value] : value),
|
|
92
|
+
z.array(item),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
76
96
|
/**
|
|
77
97
|
* Build the zod input shape for a verb's params.
|
|
78
98
|
*
|
|
@@ -152,10 +172,11 @@ function withDetail(
|
|
|
152
172
|
* `{"ok":true,"data":[],"meta":{}}` — and, later, could not tell an unambiguous
|
|
153
173
|
* lookup hit from one of three blocks sharing a name. Riding in `meta` keeps
|
|
154
174
|
* `data` its uniform shape while making both of those ≠ silence, and the CLI's
|
|
155
|
-
* `--format json` carries the same key
|
|
175
|
+
* `--format json` carries the same key. The sentence is the ONE place the two
|
|
176
|
+
* envelopes may differ: a next step inside it is spelled for its surface.
|
|
156
177
|
*/
|
|
157
178
|
function noticeMeta(verb: VerbSpec, data: unknown): Record<string, unknown> {
|
|
158
|
-
const notice = verb.output.formatters.notice?.(data as never);
|
|
179
|
+
const notice = verb.output.formatters.notice?.(data as never, "mcp");
|
|
159
180
|
// The seam's machine half, merged here so an agent gets the tier scope as
|
|
160
181
|
// DATA (`meta.scope`) and not only as the sentence `notice` carries. The CLI
|
|
161
182
|
// merges the same seam into the same keys (`cli/dispatch.ts#renderData`).
|
|
@@ -167,15 +188,22 @@ function noticeMeta(verb: VerbSpec, data: unknown): Record<string, unknown> {
|
|
|
167
188
|
function readHandler(verb: VerbSpec, runtime: PragmaRuntime) {
|
|
168
189
|
return async (args: Record<string, unknown>): Promise<CallToolResult> => {
|
|
169
190
|
try {
|
|
170
|
-
|
|
191
|
+
const session = verb.capability.needsStore
|
|
192
|
+
? await runtime.store.get()
|
|
193
|
+
: undefined;
|
|
171
194
|
const params = paramsFromArgs(verb, args);
|
|
172
195
|
const result = await Promise.resolve(
|
|
173
196
|
verb.run(params, withDetail(verb, runtime, args)) as Promise<unknown>,
|
|
174
197
|
);
|
|
175
|
-
return toolSuccess(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
198
|
+
return toolSuccess(JSON.parse(verb.output.formatters.json(result)), {
|
|
199
|
+
// The store fact this read was answered under, from the session that
|
|
200
|
+
// answered it: a pack this project built that the boot passed over
|
|
201
|
+
// because an older CLI built it. The CLI's `--format json` merges the
|
|
202
|
+
// same key from the same place (`cli/dispatch.ts#storeMeta`), so the
|
|
203
|
+
// two machine surfaces carry the same fact under the same key.
|
|
204
|
+
...(session?.ignoredPack ? { ignoredPack: session.ignoredPack } : {}),
|
|
205
|
+
...noticeMeta(verb, result),
|
|
206
|
+
});
|
|
179
207
|
} catch (error) {
|
|
180
208
|
return toolError(asPragmaError(error));
|
|
181
209
|
}
|
|
@@ -297,18 +325,11 @@ function mutateHandler(verb: VerbSpec, runtime: PragmaRuntime) {
|
|
|
297
325
|
}
|
|
298
326
|
|
|
299
327
|
/**
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
* @param verb - The verb to expose (caller ensures `mcp.expose === true`).
|
|
304
|
-
* @param runtime - The runtime handed to the verb's `run`.
|
|
305
|
-
* @note Impure — mutates the server's tool registry.
|
|
328
|
+
* The whole input shape a verb's tool accepts: its declared params plus the
|
|
329
|
+
* arguments the projector injects. Exported so the call rule validates every
|
|
330
|
+
* example and recovery against the schema an agent's call actually meets.
|
|
306
331
|
*/
|
|
307
|
-
export function
|
|
308
|
-
server: McpServer,
|
|
309
|
-
verb: VerbSpec,
|
|
310
|
-
runtime: PragmaRuntime,
|
|
311
|
-
): void {
|
|
332
|
+
export function buildToolShape(verb: VerbSpec): z.ZodRawShape {
|
|
312
333
|
const shape = buildZodSchema(verb.params);
|
|
313
334
|
// A verb with progressive disclosure gains a `detail` enum param derived from
|
|
314
335
|
// its DisclosureSpec (Risk2 — NO new VerbSpec field). The handler seeds
|
|
@@ -337,17 +358,34 @@ export function registerVerb(
|
|
|
337
358
|
"Absolute project directory to write into; defaults to the server's working directory.",
|
|
338
359
|
);
|
|
339
360
|
}
|
|
361
|
+
return shape;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Register one exposed verb as an MCP tool on the server.
|
|
366
|
+
*
|
|
367
|
+
* @param server - The MCP server to register onto.
|
|
368
|
+
* @param verb - The verb to expose (caller ensures `mcp.expose === true`).
|
|
369
|
+
* @param runtime - The runtime handed to the verb's `run`.
|
|
370
|
+
* @note Impure — mutates the server's tool registry.
|
|
371
|
+
*/
|
|
372
|
+
export function registerVerb(
|
|
373
|
+
server: McpServer,
|
|
374
|
+
verb: VerbSpec,
|
|
375
|
+
runtime: PragmaRuntime,
|
|
376
|
+
): void {
|
|
377
|
+
const shape = buildToolShape(verb);
|
|
340
378
|
|
|
341
379
|
const config: {
|
|
342
380
|
description: string;
|
|
343
381
|
inputSchema?: z.ZodRawShape;
|
|
344
382
|
annotations: McpAnnotations;
|
|
345
383
|
} = {
|
|
346
|
-
//
|
|
347
|
-
// (pack `toolDescription`s compile into it
|
|
348
|
-
//
|
|
349
|
-
//
|
|
350
|
-
description: verb
|
|
384
|
+
// Generated, never typed: the question the verb answers, its richer `doc`
|
|
385
|
+
// (pack `toolDescription`s compile into it) or one-line `summary`, and one
|
|
386
|
+
// example call. Tool descriptions are NOT part of the frozen surface, so
|
|
387
|
+
// this stays covenant-safe.
|
|
388
|
+
description: describeTool(verb),
|
|
351
389
|
annotations: annotationsFor(verb),
|
|
352
390
|
};
|
|
353
391
|
if (Object.keys(shape).length > 0) config.inputSchema = shape;
|
|
@@ -17,7 +17,7 @@ Active filters:
|
|
|
17
17
|
Valid options: stable, beta",
|
|
18
18
|
},
|
|
19
19
|
"notFound": {
|
|
20
|
-
"json": "{"ok":false,"error":{"code":"ENTITY_NOT_FOUND","message":"block \\"Buton\\" not found.","suggestions":["Button"],"recovery":{"message":"List available blocks.","cli":"pragma block list"}}}",
|
|
20
|
+
"json": "{"ok":false,"error":{"code":"ENTITY_NOT_FOUND","message":"block \\"Buton\\" not found.","suggestions":["Button"],"recovery":{"message":"List available blocks.","cli":"pragma block list","mcp":{"tool":"block_list","params":{}}}}}",
|
|
21
21
|
"llm": "## Error: ENTITY_NOT_FOUND
|
|
22
22
|
block "Buton" not found.
|
|
23
23
|
Suggestions: Button
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import { PragmaError } from "../error/PragmaError.js";
|
|
3
|
-
import {
|
|
3
|
+
import { callRecovery } from "../error/recovery.js";
|
|
4
4
|
import {
|
|
5
5
|
renderErrorJson,
|
|
6
6
|
renderErrorLlm,
|
|
@@ -293,7 +293,7 @@ describe("error render matrix (× plain/llm/json)", () => {
|
|
|
293
293
|
it("renders each error across the three formats", () => {
|
|
294
294
|
const notFound = PragmaError.notFound("block", "Buton", {
|
|
295
295
|
suggestions: ["Button"],
|
|
296
|
-
recovery:
|
|
296
|
+
recovery: callRecovery({ verb: "block list" }, "List available blocks."),
|
|
297
297
|
});
|
|
298
298
|
const empty = PragmaError.emptyResults("token", {
|
|
299
299
|
filters: { channel: "stable", tier: "core" },
|
|
@@ -201,9 +201,9 @@ function renderHeaderRow<T>(
|
|
|
201
201
|
*
|
|
202
202
|
* The SCOPE is in the heading for the same reason, and it is the more serious
|
|
203
203
|
* of the two omissions: a truncated page at least ends where it ends, while a
|
|
204
|
-
* scoped read looks exactly like a complete one. `## Block (
|
|
205
|
-
* global, apps
|
|
206
|
-
*
|
|
204
|
+
* scoped read looks exactly like a complete one. `## Block (176, tier scope:
|
|
205
|
+
* global 121, apps 39, …)` says which part of the design system the 176 are,
|
|
206
|
+
* in the words `--tier` accepts. The caller composes that text.
|
|
207
207
|
*
|
|
208
208
|
* @param items - The rows on this page.
|
|
209
209
|
* @param options - Columns and empty-state copy.
|
|
@@ -30,6 +30,7 @@ import type {
|
|
|
30
30
|
InboundGroup,
|
|
31
31
|
InspectResult,
|
|
32
32
|
NestedRecord,
|
|
33
|
+
PredicateGroup,
|
|
33
34
|
ReadTerm,
|
|
34
35
|
} from "../runtime/readEntity.js";
|
|
35
36
|
|
|
@@ -120,6 +121,13 @@ function truncationNote(predicate: string, term: ReadTerm): string | undefined {
|
|
|
120
121
|
return ` # ${predicate} — ${total} chars, showing ${term.value.length}`;
|
|
121
122
|
}
|
|
122
123
|
|
|
124
|
+
/** `# ds:hasTokenBinding — 24 objects, showing 10` — a cut group, stated as one. */
|
|
125
|
+
function cutNote(group: PredicateGroup): string | undefined {
|
|
126
|
+
if (!group.truncated || group.count === undefined) return undefined;
|
|
127
|
+
const total = group.count.toLocaleString("en-US");
|
|
128
|
+
return ` # ${renderTerm(group.predicate)} — ${total} objects, showing ${group.objects.length}`;
|
|
129
|
+
}
|
|
130
|
+
|
|
123
131
|
/** Render one inlined blank node as a Turtle `[ … ]` record. */
|
|
124
132
|
function renderNested(record: NestedRecord): string {
|
|
125
133
|
const fields = Object.entries(record).map(([field, value]) => {
|
|
@@ -188,21 +196,33 @@ export function toTurtle(
|
|
|
188
196
|
// Blank objects are written as their inlined records instead, so they are
|
|
189
197
|
// skipped here — emitting both would assert the same edge twice.
|
|
190
198
|
const objects = group.objects.filter((o) => o.termType !== "BlankNode");
|
|
199
|
+
// A group of blank objects is written further down, as its records — and
|
|
200
|
+
// says there that it was cut.
|
|
191
201
|
if (objects.length === 0) continue;
|
|
192
202
|
const keyword = predicate === "rdf:type" ? "a" : predicate;
|
|
193
203
|
// Notes are folded INTO the element, above its statement, so the `;` the
|
|
194
204
|
// join appends always lands on the statement line. Pushed as siblings they
|
|
195
205
|
// would each take a separator of their own — into a comment, which runs to
|
|
196
206
|
// end of line and eats it.
|
|
197
|
-
const notes =
|
|
198
|
-
|
|
199
|
-
.
|
|
207
|
+
const notes = [
|
|
208
|
+
cutNote(group),
|
|
209
|
+
...objects.map((o) => truncationNote(keyword, o)),
|
|
210
|
+
].filter((n): n is string => n !== undefined);
|
|
200
211
|
const rendered = objects.map(renderTerm).join(", ");
|
|
201
212
|
predicates.push([...notes, ` ${keyword} ${rendered}`].join("\n"));
|
|
202
213
|
}
|
|
203
214
|
for (const [via, records] of Object.entries(result.nested)) {
|
|
215
|
+
const group = result.groups.find(
|
|
216
|
+
(g) => (g.predicate.prefixed ?? g.predicate.value) === via,
|
|
217
|
+
);
|
|
218
|
+
const hasNamed = group?.objects.some((o) => o.termType !== "BlankNode");
|
|
219
|
+
// Said once per predicate: a group with named objects too said it above.
|
|
220
|
+
const note = group && !hasNamed ? cutNote(group) : undefined;
|
|
204
221
|
predicates.push(
|
|
205
|
-
|
|
222
|
+
[
|
|
223
|
+
...(note ? [note] : []),
|
|
224
|
+
` ${via}\n ${records.map(renderNested).join(",\n ")}`,
|
|
225
|
+
].join("\n"),
|
|
206
226
|
);
|
|
207
227
|
}
|
|
208
228
|
if (predicates.length > 0) {
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
createStoreQueryFn,
|
|
30
30
|
serializeExtraction,
|
|
31
31
|
} from "@canonical/ke-graphql";
|
|
32
|
+
import { packIsOlderThanCli } from "../packVersion.js";
|
|
32
33
|
import { packDir, packsCacheDir } from "../paths.js";
|
|
33
34
|
import { buildIndex } from "./buildIndex.js";
|
|
34
35
|
import { contentHash, hashSources } from "./hash.js";
|
|
@@ -53,6 +54,7 @@ export interface BuildPackInput {
|
|
|
53
54
|
/** Provenance and options for a pack build. */
|
|
54
55
|
export interface BuildPackOptions {
|
|
55
56
|
readonly name: string;
|
|
57
|
+
/** The building CLI's version, recorded in the manifest (see `Manifest`). */
|
|
56
58
|
readonly version: string;
|
|
57
59
|
/** The config `packs` ref (verbatim) or a label. */
|
|
58
60
|
readonly sourceRef: string;
|
|
@@ -104,7 +106,12 @@ export async function buildPack(
|
|
|
104
106
|
if (packIsComplete(dir)) {
|
|
105
107
|
const cached = readManifest(dir);
|
|
106
108
|
if (cached)
|
|
107
|
-
return {
|
|
109
|
+
return {
|
|
110
|
+
dir,
|
|
111
|
+
contentHash: hash,
|
|
112
|
+
manifest: stampBuilderVersion(dir, cached, options.version),
|
|
113
|
+
reused: true,
|
|
114
|
+
};
|
|
108
115
|
}
|
|
109
116
|
|
|
110
117
|
mkdirSync(packsCacheDir(), { recursive: true });
|
|
@@ -185,6 +192,56 @@ export async function buildPack(
|
|
|
185
192
|
}
|
|
186
193
|
}
|
|
187
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Refresh a REUSED pack's recorded builder version to the CLI doing the reuse.
|
|
197
|
+
*
|
|
198
|
+
* Without this, `sources update` could not get a project out of the state the
|
|
199
|
+
* boot decision's upgrade row describes. A pack is content-addressed, so an
|
|
200
|
+
* update whose sources have not moved resolves to the same hash and reuses the
|
|
201
|
+
* directory untouched — manifest and all. The manifest would therefore keep
|
|
202
|
+
* naming the OLD CLI as its builder, the boot would go on passing the pack over,
|
|
203
|
+
* and the `pragma sources update` the notice recommends would change nothing:
|
|
204
|
+
* a loop with no way out but `sources reset`.
|
|
205
|
+
*
|
|
206
|
+
* Only the version moves. `createdAt` and `sourceRef` describe the build that
|
|
207
|
+
* produced these bytes and stay as they were — a reuse is not a new build, and
|
|
208
|
+
* `sources status` reporting today's date for a pack from last month would be
|
|
209
|
+
* the same kind of lie in the other direction. What the stamp claims is exactly
|
|
210
|
+
* what happened: THIS CLI resolved these sources and found this pack already
|
|
211
|
+
* holding what it would have built.
|
|
212
|
+
*
|
|
213
|
+
* Best-effort, and deliberately so: the write is a provenance refresh, not part
|
|
214
|
+
* of the build, so a read-only or racing cache returns the manifest as found
|
|
215
|
+
* rather than failing an update that otherwise succeeded (the user keeps the
|
|
216
|
+
* notice, which is recoverable, instead of losing the update, which is worse).
|
|
217
|
+
*
|
|
218
|
+
* @param dir - The reused pack directory.
|
|
219
|
+
* @param cached - Its manifest, as read.
|
|
220
|
+
* @param version - The running CLI's version.
|
|
221
|
+
* @returns The manifest as it now stands on disk.
|
|
222
|
+
* @note Impure — rewrites `manifest.json` in place (temp file + atomic rename,
|
|
223
|
+
* so the completeness marker is never observed half-written).
|
|
224
|
+
*/
|
|
225
|
+
function stampBuilderVersion(
|
|
226
|
+
dir: string,
|
|
227
|
+
cached: Manifest,
|
|
228
|
+
version: string,
|
|
229
|
+
): Manifest {
|
|
230
|
+
// Upward only: the cache directory is shared by every project built from the
|
|
231
|
+
// same sources, so an older (or dev) CLI reusing it must not make a newer
|
|
232
|
+
// CLI start passing the pack over.
|
|
233
|
+
if (!packIsOlderThanCli(cached.version, version)) return cached;
|
|
234
|
+
const stamped: Manifest = { ...cached, version };
|
|
235
|
+
try {
|
|
236
|
+
const temp = join(dir, `.manifest-${process.pid}.json`);
|
|
237
|
+
writeFileSync(temp, JSON.stringify(stamped));
|
|
238
|
+
renameSync(temp, join(dir, MANIFEST_FILE));
|
|
239
|
+
return stamped;
|
|
240
|
+
} catch {
|
|
241
|
+
return cached;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
188
245
|
/**
|
|
189
246
|
* Count a store's triples (a cheap aggregate over the union graph).
|
|
190
247
|
*
|
|
@@ -445,3 +445,23 @@ describe("graphpack read — incomplete pack recovery (CLI + MCP)", () => {
|
|
|
445
445
|
}
|
|
446
446
|
});
|
|
447
447
|
});
|
|
448
|
+
|
|
449
|
+
describe("the builder version recorded on reuse", () => {
|
|
450
|
+
const stampedBy = async (version: string) =>
|
|
451
|
+
(
|
|
452
|
+
await buildPack([{ path: "stamp.ttl", content: `${TTL}\n# stamp` }], {
|
|
453
|
+
name: "test-pack",
|
|
454
|
+
version,
|
|
455
|
+
sourceRef: "test:inline",
|
|
456
|
+
prefixes: PREFIXES,
|
|
457
|
+
})
|
|
458
|
+
).manifest.version;
|
|
459
|
+
|
|
460
|
+
it("moves up to a newer CLI and never down to an older one", async () => {
|
|
461
|
+
expect(await stampedBy("2.2.0")).toBe("2.2.0");
|
|
462
|
+
// The cache directory is shared between projects, so an older CLI reusing
|
|
463
|
+
// it must leave the record alone.
|
|
464
|
+
expect(await stampedBy("2.1.0")).toBe("2.2.0");
|
|
465
|
+
expect(await stampedBy("2.3.0")).toBe("2.3.0");
|
|
466
|
+
});
|
|
467
|
+
});
|
|
@@ -108,6 +108,21 @@ export function readManifest(dir: string): Manifest | undefined {
|
|
|
108
108
|
* — see {@link storiesAreReadable} for why it, and only it, is parsed here. */
|
|
109
109
|
export function packIsComplete(dir: string): boolean {
|
|
110
110
|
if (readManifest(dir) === undefined) return false;
|
|
111
|
+
return packArtifactsPresent(dir);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The second half of {@link packIsComplete}: every non-manifest artifact,
|
|
116
|
+
* present and usable. Exported so a caller that ALREADY holds the manifest can
|
|
117
|
+
* finish the completeness check without reading the file twice —
|
|
118
|
+
* {@link resolveSources} is one, because it now reads the manifest's recorded
|
|
119
|
+
* builder version as part of the boot decision.
|
|
120
|
+
*
|
|
121
|
+
* @param dir - The pack directory.
|
|
122
|
+
* @returns Whether the four content artifacts are present and readable.
|
|
123
|
+
* @note Impure — stats (and, for stories, parses) the pack's files.
|
|
124
|
+
*/
|
|
125
|
+
export function packArtifactsPresent(dir: string): boolean {
|
|
111
126
|
for (const file of [DATA_FILE, SCHEMA_FILE, INDEX_FILE, STORIES_FILE]) {
|
|
112
127
|
try {
|
|
113
128
|
if (statSync(join(dir, file)).size <= 0) return false;
|
|
@@ -16,7 +16,8 @@ import { readFileSync } from "node:fs";
|
|
|
16
16
|
import { join } from "node:path";
|
|
17
17
|
import { createStore } from "@canonical/ke";
|
|
18
18
|
import { compileFromExtraction } from "@canonical/ke-graphql";
|
|
19
|
-
import {
|
|
19
|
+
import { callRecovery, PragmaError } from "../../error/index.js";
|
|
20
|
+
import { BUILD_STORE_CALL } from "../../spec/call.js";
|
|
20
21
|
import type { StoreSession } from "../types.js";
|
|
21
22
|
import { readManifest } from "./manifest.js";
|
|
22
23
|
import { packIndexSchema } from "./schemas.js";
|
|
@@ -25,12 +26,9 @@ import { DATA_FILE, INDEX_FILE, SCHEMA_FILE } from "./types.js";
|
|
|
25
26
|
/** STORE_UNAVAILABLE with the canonical `pragma sources update` recovery (CLI + MCP). */
|
|
26
27
|
function packUnavailable(reason: string): PragmaError {
|
|
27
28
|
return PragmaError.storeUnavailable(reason, {
|
|
28
|
-
recovery:
|
|
29
|
-
|
|
29
|
+
recovery: callRecovery(
|
|
30
|
+
BUILD_STORE_CALL,
|
|
30
31
|
"Rebuild the local store from the configured packs.",
|
|
31
|
-
// An agent recovers by calling the tool, then retrying (PR9 C1 cold-store
|
|
32
|
-
// retry makes the post-update retry succeed).
|
|
33
|
-
{ tool: "sources_update" },
|
|
34
32
|
),
|
|
35
33
|
});
|
|
36
34
|
}
|