@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
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
* existed to turn that crash into a clean refusal.
|
|
18
18
|
*
|
|
19
19
|
* There is no virtual filesystem now. Every generator's templates are real
|
|
20
|
-
* files under its own package, so all
|
|
21
|
-
* all
|
|
20
|
+
* files under its own package, so all four nouns run from the shipped entry and
|
|
21
|
+
* all four are asserted here. THE REFUSAL CASES ARE GONE ON PURPOSE: a test
|
|
22
22
|
* asserting `create package` refuses would now be asserting a defect.
|
|
23
23
|
*
|
|
24
24
|
* The svelte + lit cases stay load-bearing: `types.ts.ejs` / `index.ts.ejs` /
|
|
@@ -28,7 +28,13 @@
|
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
30
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
31
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
mkdirSync,
|
|
33
|
+
mkdtempSync,
|
|
34
|
+
readdirSync,
|
|
35
|
+
readFileSync,
|
|
36
|
+
writeFileSync,
|
|
37
|
+
} from "node:fs";
|
|
32
38
|
import { tmpdir } from "node:os";
|
|
33
39
|
import { dirname, join, resolve } from "node:path";
|
|
34
40
|
import { fileURLToPath } from "node:url";
|
|
@@ -70,7 +76,12 @@ function snapshot(dir: string): Map<string, string> {
|
|
|
70
76
|
* No case installs dependencies (`runInstall` defaults false), so every tree is
|
|
71
77
|
* generator output alone and byte-comparable.
|
|
72
78
|
*/
|
|
73
|
-
const CASES: ReadonlyArray<{
|
|
79
|
+
const CASES: ReadonlyArray<{
|
|
80
|
+
label: string;
|
|
81
|
+
args: readonly string[];
|
|
82
|
+
/** Files that must already exist in the cwd (`page` adds to a domain). */
|
|
83
|
+
seed?: Readonly<Record<string, string>>;
|
|
84
|
+
}> = [
|
|
74
85
|
...(["react", "svelte", "lit"] as const).map((framework) => ({
|
|
75
86
|
label: `component ${framework}`,
|
|
76
87
|
// The framework is a PATH SEGMENT, not a flag — `create` mirrors summon's
|
|
@@ -88,6 +99,11 @@ const CASES: ReadonlyArray<{ label: string; args: readonly string[] }> = [
|
|
|
88
99
|
// `application/react`, and the tree mirrors summon's.
|
|
89
100
|
args: ["create", "application", "react", "probeapp", "--yes"],
|
|
90
101
|
},
|
|
102
|
+
{
|
|
103
|
+
label: "page",
|
|
104
|
+
args: ["create", "page", "probe/detail", "--yes"],
|
|
105
|
+
seed: { "src/domains/probe/routes.ts": "export default {};\n" },
|
|
106
|
+
},
|
|
91
107
|
];
|
|
92
108
|
|
|
93
109
|
/** Run one create invocation in its own cwd and snapshot what it wrote. */
|
|
@@ -95,19 +111,24 @@ function create(
|
|
|
95
111
|
command: string,
|
|
96
112
|
prefix: readonly string[],
|
|
97
113
|
args: readonly string[],
|
|
114
|
+
seed: Readonly<Record<string, string>> = {},
|
|
98
115
|
): Map<string, string> {
|
|
99
116
|
const dir = freshCwd();
|
|
117
|
+
for (const [file, content] of Object.entries(seed)) {
|
|
118
|
+
mkdirSync(dirname(join(dir, file)), { recursive: true });
|
|
119
|
+
writeFileSync(join(dir, file), content);
|
|
120
|
+
}
|
|
100
121
|
execFileSync(command, [...prefix, ...args], { cwd: dir, stdio: "pipe" });
|
|
101
122
|
return snapshot(dir);
|
|
102
123
|
}
|
|
103
124
|
|
|
104
125
|
describe("shipped pragma create (PROTECTED)", () => {
|
|
105
|
-
for (const { label, args } of CASES) {
|
|
126
|
+
for (const { label, args, seed } of CASES) {
|
|
106
127
|
it(`${label}: shipped entry ≡ source run, byte-for-byte`, () => {
|
|
107
128
|
// (1) The shipped entry, exactly as a consumer's `pragma` runs it.
|
|
108
|
-
const shipped = create(process.execPath, [shippedEntry], args);
|
|
129
|
+
const shipped = create(process.execPath, [shippedEntry], args, seed);
|
|
109
130
|
// (2) A source run — the reference output.
|
|
110
|
-
const source = create("bun", [sourceEntry], args);
|
|
131
|
+
const source = create("bun", [sourceEntry], args, seed);
|
|
111
132
|
|
|
112
133
|
// Wrote something. Before the distribution stopped shipping a compiled
|
|
113
134
|
// binary, `package` and `application` wrote NOTHING here — they refused.
|
|
@@ -63,7 +63,7 @@ describe("the distribution's declared stories (PROTECTED)", () => {
|
|
|
63
63
|
},
|
|
64
64
|
);
|
|
65
65
|
|
|
66
|
-
it('every declared filter names "values" or a "
|
|
66
|
+
it('every declared filter names "values", a "vocabulary" or a "noun"', () => {
|
|
67
67
|
// The rule the round-trip above already enforces, said OUT LOUD. It was
|
|
68
68
|
// only implied there — a story satisfies the whole grammar or it does not,
|
|
69
69
|
// and a reader looking for this particular property found no assertion
|
|
@@ -87,6 +87,7 @@ describe("the distribution's declared stories (PROTECTED)", () => {
|
|
|
87
87
|
"implementation list --library",
|
|
88
88
|
"implementation list --platform",
|
|
89
89
|
"standard list --category",
|
|
90
|
+
"token consumers --block",
|
|
90
91
|
"token consumers --key",
|
|
91
92
|
"token consumers --state",
|
|
92
93
|
"token consumers --symbol",
|
|
@@ -105,8 +106,10 @@ describe("the distribution's declared stories (PROTECTED)", () => {
|
|
|
105
106
|
]);
|
|
106
107
|
for (const [label, filter] of filters) {
|
|
107
108
|
expect(
|
|
108
|
-
filter.values !== undefined ||
|
|
109
|
-
|
|
109
|
+
filter.values !== undefined ||
|
|
110
|
+
filter.vocabulary !== undefined ||
|
|
111
|
+
filter.noun !== undefined,
|
|
112
|
+
`${label} declares no "values", "vocabulary" or "noun"`,
|
|
110
113
|
).toBe(true);
|
|
111
114
|
// And never both — a declared set IS the vocabulary.
|
|
112
115
|
expect(
|
|
@@ -22,6 +22,7 @@ import conf from "../../pragma.conf.js";
|
|
|
22
22
|
import { compileStoryModule } from "../kernel/packs/compile.js";
|
|
23
23
|
import {
|
|
24
24
|
distributionSource,
|
|
25
|
+
type NounLookups,
|
|
25
26
|
type PackDefinition,
|
|
26
27
|
} from "../kernel/packs/types.js";
|
|
27
28
|
import { DEFAULT_PREFIX_MAP } from "../kernel/render/index.js";
|
|
@@ -34,6 +35,10 @@ export const declaredStories: ReadonlyMap<string, PackDefinition> = new Map(
|
|
|
34
35
|
.map((story): readonly [string, PackDefinition] => [story.noun, story]),
|
|
35
36
|
);
|
|
36
37
|
|
|
38
|
+
/** The lookup each declared noun resolves names through, for a filter naming it. */
|
|
39
|
+
export const declaredLookups: NounLookups = (noun) =>
|
|
40
|
+
declaredStories.get(noun)?.lookup;
|
|
41
|
+
|
|
37
42
|
/**
|
|
38
43
|
* Those stories compiled to capability modules, by noun.
|
|
39
44
|
*
|
|
@@ -49,6 +54,7 @@ export const storyModules: ReadonlyMap<string, CapabilityModule> = new Map(
|
|
|
49
54
|
story,
|
|
50
55
|
distributionSource("pragma.conf.ts"),
|
|
51
56
|
DEFAULT_PREFIX_MAP,
|
|
57
|
+
declaredLookups,
|
|
52
58
|
),
|
|
53
59
|
]),
|
|
54
60
|
);
|
|
@@ -28,7 +28,10 @@ import {
|
|
|
28
28
|
import { activeStories } from "../../../kernel/runtime/graphpack/stories.js";
|
|
29
29
|
import type { PragmaRuntime } from "../../../kernel/runtime/index.js";
|
|
30
30
|
import type { SourcesDecision } from "../../../kernel/runtime/resolveSources.js";
|
|
31
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
describeIgnoredPack,
|
|
33
|
+
resolveSources,
|
|
34
|
+
} from "../../../kernel/runtime/resolveSources.js";
|
|
32
35
|
import type { CheckItem, CheckResult } from "../types.js";
|
|
33
36
|
|
|
34
37
|
/**
|
|
@@ -148,6 +151,15 @@ export async function checkPackageRefs(
|
|
|
148
151
|
const composition = `${packs.length} ${packs.length === 1 ? "pack" : "packs"}, ${entities} entities${suffix}`;
|
|
149
152
|
|
|
150
153
|
if (decision.kind === "embedded") {
|
|
154
|
+
// A pack this project built, passed over because an older CLI built it. The
|
|
155
|
+
// check still PASSES — the snapshot is answering and its answers are the
|
|
156
|
+
// current ones — so the account of the pack goes in the detail, next to the
|
|
157
|
+
// account of what IS answering, and the remedy stays unset: there is
|
|
158
|
+
// nothing to fix, only two ways to change it, and the sentence names both.
|
|
159
|
+
const ignored =
|
|
160
|
+
decision.ignoredPack === undefined
|
|
161
|
+
? ""
|
|
162
|
+
: ` · ${describeIgnoredPack(decision.ignoredPack)}`;
|
|
151
163
|
return {
|
|
152
164
|
name,
|
|
153
165
|
status: "pass",
|
|
@@ -157,7 +169,7 @@ export async function checkPackageRefs(
|
|
|
157
169
|
// neither. "shipped with the CLI" rather than "embedded snapshot" — the
|
|
158
170
|
// reader needs to know where these packs came from, not what the build
|
|
159
171
|
// calls the artefact.
|
|
160
|
-
detail: `shipped with the CLI — ${composition} · run \`${BIN_NAME} sources update\` to build from your own configured packs instead`,
|
|
172
|
+
detail: `shipped with the CLI — ${composition} · run \`${BIN_NAME} sources update\` to build from your own configured packs instead${ignored}`,
|
|
161
173
|
...(items.length > 0 ? { items } : {}),
|
|
162
174
|
};
|
|
163
175
|
}
|
|
@@ -16,12 +16,13 @@ import {
|
|
|
16
16
|
writeFileSync,
|
|
17
17
|
} from "node:fs";
|
|
18
18
|
import { tmpdir } from "node:os";
|
|
19
|
-
import { join } from "node:path";
|
|
19
|
+
import { dirname, join } from "node:path";
|
|
20
20
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
21
21
|
import type { ConfigLayers } from "../../kernel/config/types.js";
|
|
22
22
|
import { executeVerb } from "../../kernel/project/cli/dispatch.js";
|
|
23
23
|
import { bootRuntime } from "../../kernel/runtime/boot.js";
|
|
24
24
|
import { createQueryFacade } from "../../kernel/runtime/facade.js";
|
|
25
|
+
import { activePackPath, packDir } from "../../kernel/runtime/paths.js";
|
|
25
26
|
import type {
|
|
26
27
|
GlobalFlags,
|
|
27
28
|
LazyStore,
|
|
@@ -212,6 +213,42 @@ describe("doctor — the pack-refs check", () => {
|
|
|
212
213
|
}
|
|
213
214
|
});
|
|
214
215
|
|
|
216
|
+
it("still passes when a pack an older CLI built is passed over — and names it", async () => {
|
|
217
|
+
// The upgrade state. The check PASSES: the snapshot is answering and its
|
|
218
|
+
// answers are the current ones, so there is nothing to fix — only a pack to
|
|
219
|
+
// account for, and two ways to change it. Hence a detail, not a remedy.
|
|
220
|
+
const cwd = tmp("pragma-proj-");
|
|
221
|
+
const hash = "b".repeat(64);
|
|
222
|
+
const dir = packDir(hash);
|
|
223
|
+
mkdirSync(dir, { recursive: true });
|
|
224
|
+
writeFileSync(join(dir, "data.nq"), "<urn:s> <urn:p> <urn:o> .\n");
|
|
225
|
+
writeFileSync(join(dir, "schema.json"), "{}");
|
|
226
|
+
writeFileSync(join(dir, "index.json"), "{}");
|
|
227
|
+
writeFileSync(join(dir, "stories.json"), "[]");
|
|
228
|
+
writeFileSync(
|
|
229
|
+
join(dir, "manifest.json"),
|
|
230
|
+
JSON.stringify({
|
|
231
|
+
name: "pragma",
|
|
232
|
+
version: "0.1.0",
|
|
233
|
+
sourceRef: "t",
|
|
234
|
+
contentHash: hash,
|
|
235
|
+
prefixes: {},
|
|
236
|
+
createdAt: "2026-09-10T21:49:00.411Z",
|
|
237
|
+
}),
|
|
238
|
+
);
|
|
239
|
+
mkdirSync(dirname(activePackPath(cwd)), { recursive: true });
|
|
240
|
+
writeFileSync(activePackPath(cwd), hash);
|
|
241
|
+
|
|
242
|
+
const pkgRefs = await checkPackageRefs(bootRuntime(FLAGS, cwd));
|
|
243
|
+
expect(pkgRefs.status).toBe("pass");
|
|
244
|
+
expect(pkgRefs.detail).toContain("shipped with the CLI");
|
|
245
|
+
expect(pkgRefs.detail).toContain(
|
|
246
|
+
"a pack built by pragma 0.1.0 on 2026-09-10 is ignored",
|
|
247
|
+
);
|
|
248
|
+
expect(pkgRefs.detail).toContain("pragma sources reset");
|
|
249
|
+
expect(pkgRefs.remedy).toBeUndefined();
|
|
250
|
+
});
|
|
251
|
+
|
|
215
252
|
it("a project with its OWN packs and nothing built is an attributable fail", async () => {
|
|
216
253
|
// The install that must never read healthy: `origins.packs` is "project",
|
|
217
254
|
// so the embedded snapshot is a DIFFERENT graph and every read throws
|
|
@@ -27,6 +27,8 @@ const doctorVerb: VerbSpec<Record<string, unknown>, DoctorData> = {
|
|
|
27
27
|
// (`toolDescriptions.test.ts`), so the inventory's widening is described as
|
|
28
28
|
// "the verbose global flag" rather than by its spelling.
|
|
29
29
|
doc: "Reports the environment checks first, then one row per setup target — once for your home directory, once for this project. Each row is pass, fail, available (an optional integration you have not set up yet), or skip (nothing to do here, and the row says why), with the next step printed inline. Every row is named after the setup target that repairs it, except `harnesses`: a listing, per scope, of the AI harnesses found on this machine and whether this CLI's MCP server is registered in each — the ones actually found, or every harness it knows about under the verbose global flag. Needs no store; the store check boots lazily and never fails the run.",
|
|
30
|
+
useWhen: "when something is not working, to find out what",
|
|
31
|
+
category: "diagnostic",
|
|
30
32
|
params: [],
|
|
31
33
|
output: { formatters: doctorFormatters },
|
|
32
34
|
examples: [
|
|
@@ -174,6 +174,11 @@ describe("pack list empty-state (U5, PROTECTED)", () => {
|
|
|
174
174
|
const notice = formatters.notice?.(empty);
|
|
175
175
|
expect(notice).toContain(`No ${pack.noun} entries found.`);
|
|
176
176
|
expect(notice).toMatch(hint);
|
|
177
|
+
// Over MCP the same hint ends in a TOOL CALL: an agent cannot run a
|
|
178
|
+
// command, and `meta.notice` is the only place it reads this.
|
|
179
|
+
const overMcp = formatters.notice?.(empty, "mcp");
|
|
180
|
+
expect(overMcp).toMatch(/Call `[a-z]+_[a-z]+ \{.*\}`\./);
|
|
181
|
+
expect(overMcp).not.toMatch(hint);
|
|
177
182
|
// JSON is the uniform empty array — unchanged by the message.
|
|
178
183
|
expect(formatters.json(empty)).toBe("[]");
|
|
179
184
|
// The llm view stays non-blank too (the `(0)` heading plus the message).
|
|
@@ -103,6 +103,8 @@ const connectVerb: VerbSpec<Record<string, unknown>, ConnectResult> = {
|
|
|
103
103
|
path: ["graph", "connect"],
|
|
104
104
|
summary: "Show the shortest relation paths between two entities.",
|
|
105
105
|
doc: "Finds every shortest path of RELATION edges between two entities, and says which kind of nothing it found when there is no path. An edge counts as a relation only where it does not fan out into a roster — membership of a class, a tier or a family is answered by that noun's list verb, not by a path — so most pairs are correctly reported as unconnected. Address each endpoint by prefixed name (ds:global.component.button) or absolute IRI. Every answer states the hop limit, the fan-in threshold, and the packs searched.",
|
|
106
|
+
useWhen: "when asked whether and how two things are related",
|
|
107
|
+
example: { a: "ds:global.component.button", b: "dt:color.text" },
|
|
106
108
|
params: [
|
|
107
109
|
{
|
|
108
110
|
kind: "string",
|
|
@@ -11,6 +11,7 @@ import { toTurtle } from "../../kernel/render/turtle.js";
|
|
|
11
11
|
import type {
|
|
12
12
|
InboundGroup,
|
|
13
13
|
InspectResult,
|
|
14
|
+
PredicateGroup,
|
|
14
15
|
ReadTerm,
|
|
15
16
|
} from "../../kernel/runtime/readEntity.js";
|
|
16
17
|
import type { Formatters } from "../../kernel/spec/index.js";
|
|
@@ -39,6 +40,13 @@ function inboundHeading(group: InboundGroup): string {
|
|
|
39
40
|
return `${short(group.predicate)} (${suffix})`;
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
/** `predicate (24 objects, showing 10)` — a cut group says what it was cut from. */
|
|
44
|
+
function outboundHeading(group: PredicateGroup): string {
|
|
45
|
+
return group.truncated
|
|
46
|
+
? `${short(group.predicate)} (${group.count} objects, showing ${group.objects.length})`
|
|
47
|
+
: short(group.predicate);
|
|
48
|
+
}
|
|
49
|
+
|
|
42
50
|
export const inspectFormatters: Formatters<InspectResult> = {
|
|
43
51
|
plain(data) {
|
|
44
52
|
const title = data.label
|
|
@@ -46,7 +54,7 @@ export const inspectFormatters: Formatters<InspectResult> = {
|
|
|
46
54
|
: (data.prefixed ?? data.uri);
|
|
47
55
|
const lines = [title, "═".repeat(Math.max(title.length, 24)), ""];
|
|
48
56
|
for (const group of data.groups) {
|
|
49
|
-
lines.push(` ${
|
|
57
|
+
lines.push(` ${outboundHeading(group)}:`);
|
|
50
58
|
for (const object of group.objects) lines.push(` ${named(object)}`);
|
|
51
59
|
}
|
|
52
60
|
for (const [via, rows] of Object.entries(data.nested)) {
|
|
@@ -333,3 +333,76 @@ describe("bounds and losslessness the review surfaced", () => {
|
|
|
333
333
|
});
|
|
334
334
|
});
|
|
335
335
|
});
|
|
336
|
+
|
|
337
|
+
describe("an outbound predicate is cut below `detailed`, and says so", () => {
|
|
338
|
+
// One subject asserting a wide predicate both ways: 14 named objects, and 12
|
|
339
|
+
// blank ones that inline as records.
|
|
340
|
+
const indexes = Array.from({ length: 14 }, (_, index) => index);
|
|
341
|
+
const WIDE_TTL = [
|
|
342
|
+
"@prefix ds: <https://ds.canonical.com/> .",
|
|
343
|
+
`ds:wide ds:name "Wide" ;`,
|
|
344
|
+
` ds:uses ${indexes.map((index) => `ds:part.${index}`).join(", ")} ;`,
|
|
345
|
+
` ds:binds ${indexes
|
|
346
|
+
.slice(0, 12)
|
|
347
|
+
.map((index) => `[ ds:rank ${index} ]`)
|
|
348
|
+
.join(", ")} .`,
|
|
349
|
+
].join("\n");
|
|
350
|
+
|
|
351
|
+
const readAt = async (
|
|
352
|
+
detail: "summary" | "standard" | "detailed",
|
|
353
|
+
): Promise<InspectResult> => {
|
|
354
|
+
const fixture = await buildFixtureRuntime({
|
|
355
|
+
ttl: WIDE_TTL,
|
|
356
|
+
prefixes: BLOCK_PREFIXES,
|
|
357
|
+
detail,
|
|
358
|
+
});
|
|
359
|
+
try {
|
|
360
|
+
return (await inspectVerb.run(
|
|
361
|
+
{ uri: "ds:wide" },
|
|
362
|
+
fixture.rt,
|
|
363
|
+
)) as InspectResult;
|
|
364
|
+
} finally {
|
|
365
|
+
(await fixture.rt.store.get()).store.dispose();
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
const group = (result: InspectResult, local: string) =>
|
|
369
|
+
result.groups.find((g) => g.predicate.value === `${DS}${local}`);
|
|
370
|
+
|
|
371
|
+
it("lists the head and states the true count at standard", async () => {
|
|
372
|
+
const result = await readAt("standard");
|
|
373
|
+
expect(group(result, "uses")).toMatchObject({ truncated: true, count: 14 });
|
|
374
|
+
expect(group(result, "uses")?.objects).toHaveLength(10);
|
|
375
|
+
// The inlined records follow the objects that survived the cut.
|
|
376
|
+
expect(group(result, "binds")?.count).toBe(12);
|
|
377
|
+
expect(result.nested["ds:binds"]).toHaveLength(10);
|
|
378
|
+
// A predicate under the cap carries neither marker.
|
|
379
|
+
expect(group(result, "name")).not.toHaveProperty("truncated");
|
|
380
|
+
expect(group(result, "name")).not.toHaveProperty("count");
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it("keeps a few at summary rather than none", async () => {
|
|
384
|
+
expect(group(await readAt("summary"), "uses")?.objects).toHaveLength(3);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it("cuts nothing at detailed", async () => {
|
|
388
|
+
const result = await readAt("detailed");
|
|
389
|
+
expect(group(result, "uses")?.objects).toHaveLength(14);
|
|
390
|
+
expect(group(result, "uses")).not.toHaveProperty("truncated");
|
|
391
|
+
expect(result.nested["ds:binds"]).toHaveLength(12);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it("prints the count in the plain and the Turtle forms", async () => {
|
|
395
|
+
const result = await readAt("standard");
|
|
396
|
+
const formatters = inspectVerb.output.formatters;
|
|
397
|
+
expect(formatters.plain(result)).toContain(
|
|
398
|
+
"ds:uses (14 objects, showing 10):",
|
|
399
|
+
);
|
|
400
|
+
const turtle = formatters.llm(result);
|
|
401
|
+
expect(turtle).toContain(
|
|
402
|
+
" # ds:uses — 14 objects, showing 10\n ds:uses ",
|
|
403
|
+
);
|
|
404
|
+
expect(turtle).toContain(
|
|
405
|
+
" # ds:binds — 12 objects, showing 10\n ds:binds\n",
|
|
406
|
+
);
|
|
407
|
+
});
|
|
408
|
+
});
|
|
@@ -25,6 +25,8 @@ const inspectVerb: VerbSpec<Record<string, unknown>, InspectResult> = {
|
|
|
25
25
|
summary:
|
|
26
26
|
"Show every triple where a URI is the subject, grouped by predicate.",
|
|
27
27
|
doc: "Inspect one entity: all predicate/object pairs asserted on the subject. Address it by prefixed name (ds:global.component.button) or absolute IRI.",
|
|
28
|
+
useWhen: "when asked for everything recorded about one identifier",
|
|
29
|
+
example: { uri: "ds:global.component.button" },
|
|
28
30
|
params: [
|
|
29
31
|
{
|
|
30
32
|
kind: "string",
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { queryFormatters } from "./query.render.js";
|
|
3
|
+
|
|
4
|
+
const EMPTY = { type: "select", bindings: [] } as never;
|
|
5
|
+
|
|
6
|
+
describe("graph query — zero rows names the next call", () => {
|
|
7
|
+
it("says the query ran, and points at the catalogue and the namespaces as commands on the CLI", () => {
|
|
8
|
+
const notice = queryFormatters.notice?.(EMPTY) as string;
|
|
9
|
+
expect(notice).toContain("the query ran and matched nothing");
|
|
10
|
+
expect(notice).toContain("`pragma capabilities`");
|
|
11
|
+
expect(notice).toContain("`pragma ontology list`");
|
|
12
|
+
// The condensed form is one stream, so it carries the same sentence.
|
|
13
|
+
expect(queryFormatters.llm(EMPTY)).toBe(notice);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("spells them as tool calls over MCP, and names no domain tool", () => {
|
|
17
|
+
const notice = queryFormatters.notice?.(EMPTY, "mcp") as string;
|
|
18
|
+
expect(notice).toContain("`capabilities {}`");
|
|
19
|
+
expect(notice).toContain("`ontology_list {}`");
|
|
20
|
+
expect(notice).not.toContain("pragma ");
|
|
21
|
+
// Which tool answers the question is the catalogue's to say, not this
|
|
22
|
+
// renderer's: the only tools it may name are the two it points at.
|
|
23
|
+
const named = [...notice.matchAll(/`([a-z_]+) \{/g)].map((m) => m[1]);
|
|
24
|
+
expect(named.sort()).toEqual(["capabilities", "ontology_list"]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("says nothing when there are rows", () => {
|
|
28
|
+
expect(
|
|
29
|
+
queryFormatters.notice?.({
|
|
30
|
+
type: "select",
|
|
31
|
+
bindings: [{ s: "ds:x" }],
|
|
32
|
+
} as never),
|
|
33
|
+
).toBeUndefined();
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
* - json: the serialized {@link QueryResult}.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import
|
|
12
|
+
import { quoteCall } from "../../kernel/spec/call.js";
|
|
13
|
+
import type { Call, Formatters, Surface } from "../../kernel/spec/index.js";
|
|
13
14
|
|
|
14
15
|
// Inline `import("…")` type (no `from`) — keeps the ke types off the static
|
|
15
16
|
// import graph the lazy-dispatch probe walks (see query.verb.ts).
|
|
16
|
-
type
|
|
17
|
+
type QueryAnswer = import("./runQuery.js").QueryAnswer;
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Render CONSTRUCT triples as readable `subject\tpredicate\tobject` lines,
|
|
@@ -35,13 +36,46 @@ function formatTriples(
|
|
|
35
36
|
.join("\n");
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Where an empty SELECT points. Deliberately no domain tool: which one answers
|
|
41
|
+
* the caller's question is the catalogue's to say, so the pointer is to the
|
|
42
|
+
* catalogue — and to the namespaces that exist, since an invented prefix parses
|
|
43
|
+
* and matches nothing.
|
|
44
|
+
*/
|
|
45
|
+
export const EMPTY_QUERY_CALLS = {
|
|
46
|
+
catalogue: { verb: "capabilities" },
|
|
47
|
+
namespaces: { verb: "ontology list" },
|
|
48
|
+
} as const satisfies Record<string, Call>;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* What zero rows says for itself. "No results." read as a failed tool to an
|
|
52
|
+
* agent with nowhere to go next. The query RAN; when its own text declares a
|
|
53
|
+
* prefix this graph does not have, that is the reason and it is said exactly.
|
|
54
|
+
*/
|
|
55
|
+
function describeEmptySelect(result: QueryAnswer, surface: Surface): string {
|
|
56
|
+
const { catalogue, namespaces } = EMPTY_QUERY_CALLS;
|
|
57
|
+
const invented = (result.unknownPrefixes ?? [])
|
|
58
|
+
.map(
|
|
59
|
+
({ prefix, iri }) =>
|
|
60
|
+
`Prefix \`${prefix}:\` <${iri}> is not a namespace of this graph, so nothing under it can match.`,
|
|
61
|
+
)
|
|
62
|
+
.join(" ");
|
|
63
|
+
return [
|
|
64
|
+
"No results: the query ran and matched nothing.",
|
|
65
|
+
invented,
|
|
66
|
+
`The namespaces that exist are listed by ${quoteCall(namespaces, surface)} (an invented prefix matches nothing). A domain tool may answer this without SPARQL: ${quoteCall(catalogue, surface)} lists the tools by the question each answers.`,
|
|
67
|
+
]
|
|
68
|
+
.filter(Boolean)
|
|
69
|
+
.join(" ");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const queryFormatters: Formatters<QueryAnswer> = {
|
|
39
73
|
// Zero rows/triples: plain stdout stays empty — the notice is
|
|
40
74
|
// `notice`, routed to stderr (exit 0) by the dispatcher so a pipe
|
|
41
75
|
// reads no prose. ASK always has a result and never goes empty.
|
|
42
|
-
notice(result) {
|
|
76
|
+
notice(result, surface = "cli") {
|
|
43
77
|
if (result.type === "select" && result.bindings.length === 0) {
|
|
44
|
-
return
|
|
78
|
+
return describeEmptySelect(result, surface);
|
|
45
79
|
}
|
|
46
80
|
if (result.type === "construct" && result.triples.length === 0) {
|
|
47
81
|
return "No triples.";
|
|
@@ -65,7 +99,9 @@ export const queryFormatters: Formatters<QueryResult> = {
|
|
|
65
99
|
|
|
66
100
|
llm(result) {
|
|
67
101
|
if (result.type === "select") {
|
|
68
|
-
if (result.bindings.length === 0)
|
|
102
|
+
if (result.bindings.length === 0) {
|
|
103
|
+
return describeEmptySelect(result, "cli");
|
|
104
|
+
}
|
|
69
105
|
const cols = Object.keys(result.bindings[0] ?? {});
|
|
70
106
|
const header = `| ${cols.join(" | ")} |`;
|
|
71
107
|
const divider = `| ${cols.map(() => "---").join(" | ")} |`;
|
|
@@ -84,6 +120,8 @@ export const queryFormatters: Formatters<QueryResult> = {
|
|
|
84
120
|
},
|
|
85
121
|
|
|
86
122
|
json(result) {
|
|
87
|
-
|
|
123
|
+
// The payload is the engine's own shape; the prefix finding is the notice's.
|
|
124
|
+
const { unknownPrefixes: _unknownPrefixes, ...data } = result;
|
|
125
|
+
return JSON.stringify(data);
|
|
88
126
|
},
|
|
89
127
|
};
|
|
@@ -64,3 +64,47 @@ describe("a name pragma printed can be pasted back (PROTECTED)", () => {
|
|
|
64
64
|
expect(prefixed).toEqual(absolute);
|
|
65
65
|
});
|
|
66
66
|
});
|
|
67
|
+
|
|
68
|
+
describe("an invented namespace is named, not answered with silence", () => {
|
|
69
|
+
// The recorded failure: nine queries against a namespace that does not
|
|
70
|
+
// exist, nine "No results", and a guess. A declared PREFIX always parses, so
|
|
71
|
+
// only a check against the store's own prefix map can say why nothing matched.
|
|
72
|
+
const INVENTED =
|
|
73
|
+
"PREFIX tok: <https://example.invalid/tokens#> SELECT ?c WHERE { ?c tok:uses ?t }";
|
|
74
|
+
|
|
75
|
+
it("says exactly which prefix is not a namespace of this graph, on both surfaces", async () => {
|
|
76
|
+
const result = await queryVerb.run({ sparql: INVENTED }, rt);
|
|
77
|
+
const { notice, json } = queryVerb.output.formatters;
|
|
78
|
+
const cli = notice?.(result, "cli") as string;
|
|
79
|
+
expect(cli).toContain(
|
|
80
|
+
"Prefix `tok:` <https://example.invalid/tokens#> is not a namespace of this graph",
|
|
81
|
+
);
|
|
82
|
+
expect(cli).toContain("`pragma ontology list`");
|
|
83
|
+
expect(notice?.(result, "mcp")).toContain("`ontology_list {}`");
|
|
84
|
+
// The finding explains the emptiness; the payload stays the engine's shape.
|
|
85
|
+
const payload = JSON.parse(json(result));
|
|
86
|
+
expect(payload).toMatchObject({ type: "select", bindings: [] });
|
|
87
|
+
expect(payload).not.toHaveProperty("unknownPrefixes");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("says nothing about prefixes when the query declares only real ones, or has rows", async () => {
|
|
91
|
+
const real = await queryVerb.run(
|
|
92
|
+
{
|
|
93
|
+
sparql:
|
|
94
|
+
"PREFIX ds: <https://ds.canonical.com/> SELECT ?s WHERE { ?s a ds:NoSuchClass }",
|
|
95
|
+
},
|
|
96
|
+
rt,
|
|
97
|
+
);
|
|
98
|
+
const notice = queryVerb.output.formatters.notice?.(real, "cli") as string;
|
|
99
|
+
expect(notice).toContain("the query ran and matched nothing");
|
|
100
|
+
expect(notice).not.toContain("is not a namespace");
|
|
101
|
+
const hit = await queryVerb.run(
|
|
102
|
+
{
|
|
103
|
+
sparql: `${INVENTED.replace("WHERE { ?c tok:uses ?t }", "WHERE { ?c ?p ?t } LIMIT 1")}`,
|
|
104
|
+
},
|
|
105
|
+
rt,
|
|
106
|
+
);
|
|
107
|
+
expect(queryVerb.output.formatters.notice?.(hit, "cli")).toBeUndefined();
|
|
108
|
+
expect(hit).not.toHaveProperty("unknownPrefixes");
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -24,6 +24,8 @@ const queryVerb: VerbSpec<Record<string, unknown>, QueryResult> = {
|
|
|
24
24
|
path: ["graph", "query"],
|
|
25
25
|
summary: "Run a raw SPARQL query against the loaded graph.",
|
|
26
26
|
doc: "Executes an arbitrary SPARQL query (SELECT / ASK / CONSTRUCT) against the store. Prefixes are applied automatically from the pack's namespace map; list the ontology namespaces to discover the available prefixes.",
|
|
27
|
+
useWhen: "only when no other tool answers: SPARQL joins or counts",
|
|
28
|
+
example: { sparql: "SELECT ?s WHERE { ?s a ds:Component } LIMIT 5" },
|
|
27
29
|
params: [
|
|
28
30
|
{
|
|
29
31
|
kind: "string",
|
|
@@ -10,7 +10,11 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { describe, expect, it } from "vitest";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
expandPrefixedNames,
|
|
15
|
+
findUnknownPrefixes,
|
|
16
|
+
trimQueryError,
|
|
17
|
+
} from "./queryText.js";
|
|
14
18
|
|
|
15
19
|
const PREFIXES = {
|
|
16
20
|
ds: "https://ds.canonical.com/",
|
|
@@ -119,3 +123,53 @@ describe("trimQueryError — the caller's own position", () => {
|
|
|
119
123
|
);
|
|
120
124
|
});
|
|
121
125
|
});
|
|
126
|
+
|
|
127
|
+
describe("findUnknownPrefixes — only a prefix that can explain the emptiness", () => {
|
|
128
|
+
const KNOWN = { ds: "https://ds.canonical.com/" };
|
|
129
|
+
const BAD = "https://example.invalid/t#";
|
|
130
|
+
|
|
131
|
+
it("reports a declared, used prefix whose IRI is no namespace of the graph", () => {
|
|
132
|
+
expect(
|
|
133
|
+
findUnknownPrefixes(
|
|
134
|
+
`PREFIX tok: <${BAD}> PREFIX ds: <https://ds.canonical.com/> SELECT ?c WHERE { ?c tok:uses ds:x }`,
|
|
135
|
+
KNOWN,
|
|
136
|
+
),
|
|
137
|
+
).toEqual([{ prefix: "tok", iri: BAD }]);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("ignores a PREFIX in a comment or inside a string literal", () => {
|
|
141
|
+
expect(
|
|
142
|
+
findUnknownPrefixes(
|
|
143
|
+
`# PREFIX tok: <${BAD}>\nSELECT ?s WHERE { ?s ds:label "PREFIX tok: <${BAD}> tok:x" }`,
|
|
144
|
+
KNOWN,
|
|
145
|
+
),
|
|
146
|
+
).toEqual([]);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("ignores a declared prefix the body never uses", () => {
|
|
150
|
+
expect(
|
|
151
|
+
findUnknownPrefixes(
|
|
152
|
+
`PREFIX tok: <${BAD}> SELECT ?s WHERE { ?s a ds:Component }`,
|
|
153
|
+
KNOWN,
|
|
154
|
+
),
|
|
155
|
+
).toEqual([]);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("does not judge a relative IRI, which BASE resolves", () => {
|
|
159
|
+
expect(
|
|
160
|
+
findUnknownPrefixes(
|
|
161
|
+
"BASE <https://ds.canonical.com/> PREFIX rel: <tokens/> SELECT ?s WHERE { ?s rel:p ?o }",
|
|
162
|
+
KNOWN,
|
|
163
|
+
),
|
|
164
|
+
).toEqual([]);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("reports the default prefix too, when the body uses it", () => {
|
|
168
|
+
expect(
|
|
169
|
+
findUnknownPrefixes(
|
|
170
|
+
`PREFIX : <${BAD}> SELECT ?s WHERE { ?s :p ?o }`,
|
|
171
|
+
KNOWN,
|
|
172
|
+
),
|
|
173
|
+
).toEqual([{ prefix: "", iri: BAD }]);
|
|
174
|
+
});
|
|
175
|
+
});
|