@canonical/pragma-cli 0.38.0 → 0.40.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 +1 -1
- package/dist/package.json +14 -14
- 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/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.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 +2 -2
- package/dist/src/capabilities/create/create.verb.js +14 -4
- package/dist/src/capabilities/create/create.verb.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/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/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/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/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/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/renderers.js +3 -3
- package/dist/src/kernel/render/turtle.js +21 -4
- package/dist/src/kernel/render/turtle.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/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 +14 -14
- 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 +2 -2
- package/src/capabilities/create/create.verb.ts +16 -0
- package/src/capabilities/create/createGrammar.test.ts +1 -1
- 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 +2 -2
- 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/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/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
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
quoteArgument,
|
|
4
|
+
quoteCall,
|
|
5
|
+
renderCall,
|
|
6
|
+
renderNextStep,
|
|
7
|
+
} from "./call.js";
|
|
8
|
+
|
|
9
|
+
describe("a call is spelled for the surface it is printed on", () => {
|
|
10
|
+
const call = { verb: "token lookup", params: { name: ["color.text"] } };
|
|
11
|
+
|
|
12
|
+
it("ends a dead end with a command on the CLI and a tool call over MCP", () => {
|
|
13
|
+
expect(renderNextStep(call, "cli")).toBe(
|
|
14
|
+
"Run `pragma token lookup color.text`.",
|
|
15
|
+
);
|
|
16
|
+
expect(renderNextStep(call, "mcp")).toBe(
|
|
17
|
+
'Call `token_lookup { name: ["color.text"] }`.',
|
|
18
|
+
);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("a next step to a mutating tool never confirms: the tool is plan-first, and its plan says how to proceed", () => {
|
|
22
|
+
const update = { verb: "sources update" };
|
|
23
|
+
expect(renderNextStep(update, "mcp")).toBe("Call `sources_update {}`.");
|
|
24
|
+
expect(renderNextStep(update, "cli")).toBe("Run `pragma sources update`.");
|
|
25
|
+
expect(renderCall(update, "mcp")).toBe("sources_update {}");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("a verb withheld from MCP is spelled as the command it is, on both surfaces", () => {
|
|
29
|
+
expect(renderNextStep({ verb: "setup skills" }, "mcp")).toBe(
|
|
30
|
+
"Run `pragma setup skills`.",
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("carries global CLI flags on the command only", () => {
|
|
35
|
+
const verbose = { verb: "sources update", cliFlags: ["--verbose"] };
|
|
36
|
+
expect(renderCall(verbose, "cli")).toBe("pragma sources update --verbose");
|
|
37
|
+
expect(renderCall(verbose, "mcp")).toBe("sources_update {}");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("spells flags the way the CLI parses them", () => {
|
|
41
|
+
// A default-true boolean is negated; a default-false one is simply absent.
|
|
42
|
+
expect(
|
|
43
|
+
renderCall(
|
|
44
|
+
{
|
|
45
|
+
verb: "create package",
|
|
46
|
+
params: { name: "x", runInstall: false, withCli: false },
|
|
47
|
+
},
|
|
48
|
+
"cli",
|
|
49
|
+
),
|
|
50
|
+
).toBe("pragma create package --name x --no-run-install");
|
|
51
|
+
// `undefined` is not an argument, on either surface.
|
|
52
|
+
const sparse = {
|
|
53
|
+
verb: "ontology lookup",
|
|
54
|
+
params: { prefix: "ds", class: undefined },
|
|
55
|
+
};
|
|
56
|
+
expect(renderCall(sparse, "mcp")).toBe('ontology_lookup { prefix: "ds" }');
|
|
57
|
+
expect(renderCall(sparse, "cli")).toBe("pragma ontology lookup ds");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("fences a flag-like positional, and quotes a call containing a backtick safely", () => {
|
|
61
|
+
expect(
|
|
62
|
+
quoteCall({ verb: "graph inspect", params: { uri: "-x" } }, "cli"),
|
|
63
|
+
).toBe("`pragma graph inspect -- -x`");
|
|
64
|
+
expect(
|
|
65
|
+
quoteCall(
|
|
66
|
+
{ verb: "graph query", params: { sparql: 'ASK { ?s ?p "`" }' } },
|
|
67
|
+
"mcp",
|
|
68
|
+
),
|
|
69
|
+
).toMatch(/^`` graph_query .* ``$/);
|
|
70
|
+
expect(quoteArgument("channelOf", "x", "cli")).toBe("`--channel-of x`");
|
|
71
|
+
// Shell-quoted like any other word, so the printed flag can be pasted.
|
|
72
|
+
expect(quoteArgument("search", "two words; rm", "cli")).toBe(
|
|
73
|
+
"`--search 'two words; rm'`",
|
|
74
|
+
);
|
|
75
|
+
expect(quoteArgument("channelOf", "x", "mcp")).toBe('`channelOf: "x"`');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("outside the suite's checking, renders an undeclared verb's params as flags rather than throwing", async () => {
|
|
79
|
+
// A shipped process never enables checking, and this runs inside error
|
|
80
|
+
// rendering — a second failure there helps no one. A fresh module instance
|
|
81
|
+
// is the unchecked, undeclared state a shipped process starts in.
|
|
82
|
+
vi.resetModules();
|
|
83
|
+
const fresh = await import("./call.js");
|
|
84
|
+
expect(
|
|
85
|
+
fresh.renderCall(
|
|
86
|
+
{ verb: "widget list", params: { kind: "input" } },
|
|
87
|
+
"cli",
|
|
88
|
+
),
|
|
89
|
+
).toBe("pragma widget list --kind input");
|
|
90
|
+
expect(fresh.callTool({ verb: "widget list" })).toEqual({
|
|
91
|
+
tool: "widget_list",
|
|
92
|
+
params: {},
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A call: one verb plus the params to make it with — the single type behind
|
|
3
|
+
* every example and every recovery.
|
|
4
|
+
*
|
|
5
|
+
* An author names the call ONCE and both spellings derive from it:
|
|
6
|
+
* `token_lookup { name: ["color.text"] }` for an agent on MCP,
|
|
7
|
+
* `pragma token lookup color.text` for a person at a terminal. A hand-typed
|
|
8
|
+
* pair can disagree, and a hand-typed string cannot be checked against the
|
|
9
|
+
* verb it names, where a call can (`capabilities/callRule.test.ts`).
|
|
10
|
+
*
|
|
11
|
+
* The CLI spelling needs facts the call does not carry — which params are
|
|
12
|
+
* positional, which booleans default to true, whether the verb mutates. They
|
|
13
|
+
* live on the verb, so the projectors declare their verbs here as they boot
|
|
14
|
+
* ({@link declareVerbs}). A call naming a verb nobody declared renders every
|
|
15
|
+
* param as a flag rather than throwing: this runs inside error rendering,
|
|
16
|
+
* where a second failure helps no one.
|
|
17
|
+
*
|
|
18
|
+
* NOT derivable here: a noun whose CLI is mounted by its own projection
|
|
19
|
+
* (`create`, whose commands carry a generator segment the verb's params do
|
|
20
|
+
* not). Its calls spell correctly as tool calls only.
|
|
21
|
+
*
|
|
22
|
+
* Zod-free: it is reachable from the `--help` path.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { RECOVERY_CLI_PREFIX } from "../../constants.js";
|
|
26
|
+
import { kebabCase, toolName } from "./emitSurface.js";
|
|
27
|
+
import type { Call, Surface, VerbSpec } from "./types.js";
|
|
28
|
+
|
|
29
|
+
const declared = new Map<string, VerbSpec>();
|
|
30
|
+
let findProblem:
|
|
31
|
+
| ((call: Call, verb: VerbSpec | undefined) => string | undefined)
|
|
32
|
+
| undefined;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Declare the verbs a call may name. Additive, so a fixture's verbs join the
|
|
36
|
+
* distribution's rather than replacing them.
|
|
37
|
+
*
|
|
38
|
+
* @note Impure — fills the module-level index {@link renderCall} reads. A call
|
|
39
|
+
* is rendered deep inside error construction and formatters, which hold no
|
|
40
|
+
* registry to pass in.
|
|
41
|
+
*/
|
|
42
|
+
export function declareVerbs(verbs: readonly VerbSpec[]): void {
|
|
43
|
+
for (const verb of verbs) declared.set(verb.path.join(" "), verb);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Make every rendered call prove itself. The test setup installs the ONE
|
|
48
|
+
* validator — the verb's real MCP input schema — so a recovery built on any
|
|
49
|
+
* line a test runs is checked exactly as the conformance test checks examples.
|
|
50
|
+
* It is handed the DECLARED verb the call names, so a fixture that declares its
|
|
51
|
+
* verbs is held to them too.
|
|
52
|
+
*
|
|
53
|
+
* @note Impure — sets module-level state; never installed in a shipped process.
|
|
54
|
+
*/
|
|
55
|
+
export function checkCallsWith(
|
|
56
|
+
validator: (call: Call, verb: VerbSpec | undefined) => string | undefined,
|
|
57
|
+
): void {
|
|
58
|
+
findProblem = validator;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The tool name a call addresses (`"sources update"` → `sources_update`). */
|
|
62
|
+
function callToolName(call: Call): string {
|
|
63
|
+
return toolName(call.verb.split(" ") as [string, string?]);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Build (or rebuild) the store from the configured packs — the one call the
|
|
68
|
+
* kernel itself points at, from every unavailable store and every empty list.
|
|
69
|
+
* Named once, here, because the kernel owns the verb's name.
|
|
70
|
+
*/
|
|
71
|
+
export const BUILD_STORE_CALL: Call = { verb: "sources update" };
|
|
72
|
+
|
|
73
|
+
/** The MCP tool a recovery names, or nothing when its verb is withheld from MCP. */
|
|
74
|
+
export function callTool(
|
|
75
|
+
call: Call,
|
|
76
|
+
): { tool: string; params: Record<string, unknown> } | undefined {
|
|
77
|
+
if (declared.get(call.verb)?.capability.mcp.expose === false) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
return { tool: callToolName(call), params: { ...call.params } };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Quote a CLI word unless the shell would read it bare. */
|
|
84
|
+
function quoteWord(value: unknown): string {
|
|
85
|
+
const word = String(value);
|
|
86
|
+
return /^[\w.:/@=,+-]+$/.test(word)
|
|
87
|
+
? word
|
|
88
|
+
: `'${word.replaceAll("'", "'\\''")}'`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** The CLI spelling: global flags, then flags, then positionals in the verb's order. */
|
|
92
|
+
function renderCliCall(call: Call): string {
|
|
93
|
+
const params = call.params ?? {};
|
|
94
|
+
const specs = declared.get(call.verb)?.params ?? [];
|
|
95
|
+
const positional = specs.filter((p) => p.positional).map((p) => p.name);
|
|
96
|
+
const flags: string[] = [];
|
|
97
|
+
for (const [name, value] of Object.entries(params)) {
|
|
98
|
+
if (positional.includes(name) || value === undefined) continue;
|
|
99
|
+
const flag = `--${kebabCase(name)}`;
|
|
100
|
+
if (typeof value === "boolean") {
|
|
101
|
+
const spec = specs.find((p) => p.name === name);
|
|
102
|
+
const defaultsTrue = spec?.kind === "boolean" && spec.default === true;
|
|
103
|
+
if (value) flags.push(flag);
|
|
104
|
+
else if (defaultsTrue) flags.push(`--no-${kebabCase(name)}`);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
// A list-valued flag is repeated, one value each.
|
|
108
|
+
for (const item of [value].flat()) flags.push(`${flag} ${quoteWord(item)}`);
|
|
109
|
+
}
|
|
110
|
+
const words = positional
|
|
111
|
+
.filter((name) => params[name] !== undefined)
|
|
112
|
+
.flatMap((name) => [params[name]].flat().map(quoteWord));
|
|
113
|
+
// A positional that looks like a flag needs the end-of-options marker.
|
|
114
|
+
const guard = words.some((word) => word.startsWith("-")) ? ["--"] : [];
|
|
115
|
+
return [
|
|
116
|
+
`${RECOVERY_CLI_PREFIX}${call.verb}`,
|
|
117
|
+
...(call.cliFlags ?? []),
|
|
118
|
+
...flags,
|
|
119
|
+
...guard,
|
|
120
|
+
...words,
|
|
121
|
+
].join(" ");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** The MCP spelling: the tool name and its argument bag. */
|
|
125
|
+
function renderMcpCall(call: Call): string {
|
|
126
|
+
const body = Object.entries(call.params ?? {})
|
|
127
|
+
.filter(([, value]) => value !== undefined)
|
|
128
|
+
.map(([name, value]) => `${name}: ${JSON.stringify(value)}`)
|
|
129
|
+
.join(", ");
|
|
130
|
+
return `${callToolName(call)} ${body ? `{ ${body} }` : "{}"}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Spell a call for the surface it is about to be printed on.
|
|
135
|
+
*
|
|
136
|
+
* @param call - The verb and params to make.
|
|
137
|
+
* @param surface - `"cli"` for a command line, `"mcp"` for a tool call.
|
|
138
|
+
* @returns `pragma token lookup color.text`, or `token_lookup { name: ["color.text"] }`.
|
|
139
|
+
* @throws Error when a validator is installed (tests only) and rejects the call.
|
|
140
|
+
*/
|
|
141
|
+
export function renderCall(call: Call, surface: Surface): string {
|
|
142
|
+
const problem = findProblem?.(call, declared.get(call.verb));
|
|
143
|
+
if (problem) throw new Error(`Unsound call ${call.verb}: ${problem}`);
|
|
144
|
+
return spellingFor(call, surface) === "mcp"
|
|
145
|
+
? renderMcpCall(call)
|
|
146
|
+
: renderCliCall(call);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* A verb withheld from MCP has no tool to name, so even over MCP it is spelled
|
|
151
|
+
* as the command it is — something an agent can hand to its user.
|
|
152
|
+
*/
|
|
153
|
+
function spellingFor(call: Call, surface: Surface): Surface {
|
|
154
|
+
return declared.get(call.verb)?.capability.mcp.expose === false
|
|
155
|
+
? "cli"
|
|
156
|
+
: surface;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* One argument, quoted as code, in the surface's own spelling: `--tier all` on
|
|
161
|
+
* the CLI, `tier: "all"` over MCP. For the sentences that name an argument to
|
|
162
|
+
* change rather than a whole call to make. The CLI value is shell-quoted like
|
|
163
|
+
* any other word, so what is printed can be pasted.
|
|
164
|
+
*/
|
|
165
|
+
export function quoteArgument(
|
|
166
|
+
name: string,
|
|
167
|
+
value: string | number,
|
|
168
|
+
surface: Surface,
|
|
169
|
+
): string {
|
|
170
|
+
return surface === "mcp"
|
|
171
|
+
? `\`${name}: ${JSON.stringify(value)}\``
|
|
172
|
+
: `\`--${kebabCase(name)} ${quoteWord(value)}\``;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** A call quoted as code; one containing a backtick takes the doubled fence. */
|
|
176
|
+
export function quoteCall(call: Call, surface: Surface): string {
|
|
177
|
+
const text = renderCall(call, surface);
|
|
178
|
+
return text.includes("`") ? `\`\` ${text} \`\`` : `\`${text}\``;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** The sentence that ends a dead end: the exact next call, for this surface. */
|
|
182
|
+
export function renderNextStep(call: Call, surface: Surface): string {
|
|
183
|
+
return spellingFor(call, surface) === "mcp"
|
|
184
|
+
? `Call ${quoteCall(call, surface)}.`
|
|
185
|
+
: `Run ${quoteCall(call, surface)}.`;
|
|
186
|
+
}
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
toolName,
|
|
25
25
|
verbLabel,
|
|
26
26
|
} from "./emitSurface.js";
|
|
27
|
+
import { describeTool, useWhenSentence } from "./guidance.js";
|
|
27
28
|
import type {
|
|
28
29
|
CapabilityModule,
|
|
29
30
|
Example,
|
|
@@ -31,6 +32,7 @@ import type {
|
|
|
31
32
|
ReferenceCliSyntax,
|
|
32
33
|
VerbSpec,
|
|
33
34
|
} from "./types.js";
|
|
35
|
+
import { formatWireType, wireType } from "./wireType.js";
|
|
34
36
|
|
|
35
37
|
/** The reference doc set: relative path under `docs/reference/` → file content. */
|
|
36
38
|
export type ReferenceDocs = ReadonlyMap<string, string>;
|
|
@@ -122,19 +124,11 @@ function formatFlagValue(param: ParamSpec): string {
|
|
|
122
124
|
}
|
|
123
125
|
|
|
124
126
|
/**
|
|
125
|
-
* The tool-schema type label a param projects to
|
|
126
|
-
*
|
|
127
|
-
* a Markdown table cell without escaping.
|
|
127
|
+
* The tool-schema type label a param projects to: the SAME {@link wireType}
|
|
128
|
+
* `buildZodSchema` validates against, printed — never a mirror of it.
|
|
128
129
|
*/
|
|
129
130
|
function formatParamType(param: ParamSpec): string {
|
|
130
|
-
|
|
131
|
-
case "enum":
|
|
132
|
-
return `enum(${param.values.join(", ")})`;
|
|
133
|
-
case "string[]":
|
|
134
|
-
return "string[]";
|
|
135
|
-
default:
|
|
136
|
-
return param.kind;
|
|
137
|
-
}
|
|
131
|
+
return formatWireType(wireType(param));
|
|
138
132
|
}
|
|
139
133
|
|
|
140
134
|
/** Render a default value for prose (strings verbatim, everything else stringified). */
|
|
@@ -260,6 +254,7 @@ function renderCommandSection(
|
|
|
260
254
|
`### ${formatInvocation(verb)}`,
|
|
261
255
|
verb.summary,
|
|
262
256
|
verb.doc ?? "",
|
|
257
|
+
useWhenSentence(verb) ?? "",
|
|
263
258
|
`\`\`\`\n${syntax ? `${BIN_NAME} ${syntax.usage}` : formatUsage(verb)}\n\`\`\``,
|
|
264
259
|
formatArgsTable(verb.params, syntax?.positionalTokens),
|
|
265
260
|
formatFlagsTable(verb.params, syntax?.flagTokens),
|
|
@@ -410,7 +405,8 @@ function formatToolAnnotations(verb: VerbSpec): string {
|
|
|
410
405
|
function renderToolSection(verb: VerbSpec): string {
|
|
411
406
|
const blocks = [
|
|
412
407
|
`### ${toolName(verb.path)}`,
|
|
413
|
-
|
|
408
|
+
// The description an agent is actually served, from the one generator.
|
|
409
|
+
describeTool(verb),
|
|
414
410
|
formatToolAnnotations(verb),
|
|
415
411
|
"**Input**",
|
|
416
412
|
formatToolParams(verb),
|
|
@@ -451,7 +447,7 @@ function renderNonToolSurface(modules: readonly CapabilityModule[]): string {
|
|
|
451
447
|
);
|
|
452
448
|
}
|
|
453
449
|
bullets.push(
|
|
454
|
-
"- **Instructions**: the server always sends handshake instructions
|
|
450
|
+
"- **Instructions**: the server always sends handshake instructions: the conventions, and a generated index of tools by the question each answers, fitted to 2,000 characters.",
|
|
455
451
|
);
|
|
456
452
|
return `## Non-tool surface\n\n${bullets.join("\n")}`;
|
|
457
453
|
}
|
|
@@ -16,6 +16,14 @@ export function kebabCase(name: string): string {
|
|
|
16
16
|
return name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* The ceiling on the whole MCP tool catalogue (name + description + input
|
|
21
|
+
* schema per tool, ~4 characters a token). ONE constant: the emitted surface
|
|
22
|
+
* states it, the covenant freezes it, and the eval case enforces it. Set from
|
|
23
|
+
* measurement plus about a tenth — `BUDGETS.md` records each raise.
|
|
24
|
+
*/
|
|
25
|
+
export const CONDENSED_SDL_TOKEN_BUDGET = 9600;
|
|
26
|
+
|
|
19
27
|
/** The MCP tool name for a verb path (`["config","show"]` -> `config_show`). */
|
|
20
28
|
export function toolName(path: readonly [string, string?]): string {
|
|
21
29
|
const [noun, verb] = path;
|
|
@@ -147,7 +155,7 @@ export const FIXED_SURFACE = {
|
|
|
147
155
|
projectConfigLoad: "<10ms",
|
|
148
156
|
warmStoreVerb: "<300ms",
|
|
149
157
|
mcpP95Warm: "<100ms",
|
|
150
|
-
condensedSDL:
|
|
158
|
+
condensedSDL: `<=${CONDENSED_SDL_TOKEN_BUDGET} tokens`,
|
|
151
159
|
},
|
|
152
160
|
} as const;
|
|
153
161
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { capabilities } from "../../capabilities/index.js";
|
|
3
|
+
import { describeTool, useWhenSentence, verbCategory } from "./guidance.js";
|
|
4
|
+
import type { VerbSpec } from "./types.js";
|
|
5
|
+
|
|
6
|
+
const find = (path: string): VerbSpec =>
|
|
7
|
+
capabilities
|
|
8
|
+
.flatMap((module) => module.verbs)
|
|
9
|
+
.find((verb) => verb.path.join(" ") === path) as VerbSpec;
|
|
10
|
+
|
|
11
|
+
describe("the one generator of tool guidance", () => {
|
|
12
|
+
it("leads with the question, adds authored prose, ends with the example", () => {
|
|
13
|
+
const lookup = find("token lookup");
|
|
14
|
+
const description = describeTool(lookup);
|
|
15
|
+
expect(description.startsWith(`Use ${lookup.useWhen}.`)).toBe(true);
|
|
16
|
+
expect(description).toContain(lookup.doc);
|
|
17
|
+
expect(
|
|
18
|
+
description.endsWith(
|
|
19
|
+
'Example: token_lookup { name: ["color.text","color.border"] }.',
|
|
20
|
+
),
|
|
21
|
+
).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("does not repeat the one-line summary after the question, and shows no example for a verb callable bare", () => {
|
|
25
|
+
const list = find("skill list");
|
|
26
|
+
expect(list.doc).toBeUndefined();
|
|
27
|
+
expect(describeTool(list)).toBe(useWhenSentence(list));
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("falls back to doc or summary for a verb declaring no question (a third-party story may)", () => {
|
|
31
|
+
const { useWhen: _useWhen, doc: _doc, ...bare } = find("skill list");
|
|
32
|
+
expect(describeTool(bare as VerbSpec)).toBe(bare.summary);
|
|
33
|
+
expect(useWhenSentence(bare as VerbSpec)).toBeUndefined();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("does not add a second example to a description that still hand-types one", () => {
|
|
37
|
+
const legacy = {
|
|
38
|
+
...find("token lookup"),
|
|
39
|
+
doc: 'Get a token. Example: token_lookup { name: ["x"] }.',
|
|
40
|
+
} as VerbSpec;
|
|
41
|
+
expect(describeTool(legacy).match(/Example:/g)).toHaveLength(1);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("derives write from mutates, and reads the rest off the verb", () => {
|
|
45
|
+
expect(verbCategory(find("sources update"))).toBe("write");
|
|
46
|
+
expect(verbCategory(find("doctor"))).toBe("diagnostic");
|
|
47
|
+
expect(verbCategory(find("block list"))).toBe("read");
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE generator of tool guidance.
|
|
3
|
+
*
|
|
4
|
+
* A verb declares the question it answers (`useWhen`) and one real call
|
|
5
|
+
* (`example`) once. Everything a caller reads when choosing a tool is built
|
|
6
|
+
* here from those two and the summary: the MCP tool description, the
|
|
7
|
+
* `capabilities` catalogue entry, the verb's help and the reference. A sentence
|
|
8
|
+
* that lives in one place cannot be true in the catalogue and missing from the
|
|
9
|
+
* description, which is how an agent that never called `capabilities` came to
|
|
10
|
+
* choose between fifty tools without it.
|
|
11
|
+
*
|
|
12
|
+
* It names no tool: what the distribution says about its own tools arrives as
|
|
13
|
+
* data (a verb's fields, a module's `mcpOrientation`).
|
|
14
|
+
*
|
|
15
|
+
* Zod-free: the catalogue and help read it on the fast path.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { renderCall } from "./call.js";
|
|
19
|
+
import type { Call, ToolCategory, VerbSpec } from "./types.js";
|
|
20
|
+
|
|
21
|
+
/** A verb's category: `write` ⟺ it mutates, else what it declares, else `read`. */
|
|
22
|
+
export function verbCategory(verb: VerbSpec): ToolCategory {
|
|
23
|
+
return verb.capability.mutates ? "write" : (verb.category ?? "read");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A verb's declared example as a call. A verb with no required param may
|
|
28
|
+
* declare none — called bare, its only call is its own name — and then none
|
|
29
|
+
* is rendered.
|
|
30
|
+
*/
|
|
31
|
+
export function exampleCall(verb: VerbSpec): Call | undefined {
|
|
32
|
+
return verb.example
|
|
33
|
+
? { verb: verb.path.join(" "), params: verb.example }
|
|
34
|
+
: undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* `useWhen` as a sentence. It is stored as the bare clause ("when asked which
|
|
39
|
+
* components use a token") because the catalogue's field is already named
|
|
40
|
+
* `use_when`; prose adds the lead.
|
|
41
|
+
*/
|
|
42
|
+
export function useWhenSentence(verb: VerbSpec): string | undefined {
|
|
43
|
+
return verb.useWhen ? `Use ${verb.useWhen}.` : undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The agent-facing MCP tool description: the question first — it is what a
|
|
48
|
+
* caller scanning fifty descriptions matches its task against — then what the
|
|
49
|
+
* tool returns when a richer `doc` is authored, then one call to copy. The
|
|
50
|
+
* one-line summary is the fallback for a verb declaring no question, never a
|
|
51
|
+
* second sentence after one: it says the same thing less usefully.
|
|
52
|
+
*
|
|
53
|
+
* @param verb - The verb to describe.
|
|
54
|
+
* @returns The description.
|
|
55
|
+
*/
|
|
56
|
+
export function describeTool(verb: VerbSpec): string {
|
|
57
|
+
const question = useWhenSentence(verb);
|
|
58
|
+
const example = exampleCall(verb);
|
|
59
|
+
const rendered = example && `Example: ${renderCall(example, "mcp")}.`;
|
|
60
|
+
return [
|
|
61
|
+
question,
|
|
62
|
+
verb.doc ?? (question ? undefined : verb.summary),
|
|
63
|
+
// A third-party description may still end in its own hand-typed example.
|
|
64
|
+
rendered && !verb.doc?.includes("Example:") ? rendered : undefined,
|
|
65
|
+
]
|
|
66
|
+
.filter(Boolean)
|
|
67
|
+
.join(" ");
|
|
68
|
+
}
|
package/src/kernel/spec/index.ts
CHANGED
|
@@ -23,14 +23,18 @@ export {
|
|
|
23
23
|
export type { Covenant } from "./surfaceConformance.js";
|
|
24
24
|
export { assertConforms, deepEqual } from "./surfaceConformance.js";
|
|
25
25
|
export type {
|
|
26
|
+
Call,
|
|
26
27
|
Capability,
|
|
27
28
|
CapabilityModule,
|
|
28
29
|
DisclosureSpec,
|
|
29
30
|
Example,
|
|
30
31
|
Formatters,
|
|
31
32
|
McpAnnotations,
|
|
33
|
+
McpOrientation,
|
|
32
34
|
McpResourceProvider,
|
|
33
35
|
ParamComplete,
|
|
34
36
|
ParamSpec,
|
|
37
|
+
Surface,
|
|
38
|
+
ToolCategory,
|
|
35
39
|
VerbSpec,
|
|
36
40
|
} from "./types.js";
|
|
@@ -84,8 +84,8 @@ describe("surface conformance (PROTECTED)", () => {
|
|
|
84
84
|
);
|
|
85
85
|
});
|
|
86
86
|
|
|
87
|
-
it("freezes exactly
|
|
88
|
-
expect(golden.mcpSurface.tools).toHaveLength(
|
|
87
|
+
it("freezes exactly 50 designed MCP tools including info, the config family, colophon", () => {
|
|
88
|
+
expect(golden.mcpSurface.tools).toHaveLength(50);
|
|
89
89
|
expect(golden.mcpSurface.tools).toContain("info");
|
|
90
90
|
expect(golden.mcpSurface.tools).toContain("config_show");
|
|
91
91
|
expect(golden.mcpSurface.tools).toContain("config_get");
|
package/src/kernel/spec/types.ts
CHANGED
|
@@ -12,7 +12,28 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import type { Effect, Task } from "@canonical/task";
|
|
15
|
-
import type { PragmaRuntime } from "../runtime/types.js";
|
|
15
|
+
import type { InteractionRuntime, PragmaRuntime } from "../runtime/types.js";
|
|
16
|
+
|
|
17
|
+
/** Where text is about to be printed — the projector driving the run. */
|
|
18
|
+
export type Surface = InteractionRuntime["transport"];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A verb path (`"sources update"`) and the params to call it with — the one
|
|
22
|
+
* type behind every example and every recovery (`call.ts` spells it).
|
|
23
|
+
*/
|
|
24
|
+
export interface Call {
|
|
25
|
+
readonly verb: string;
|
|
26
|
+
readonly params?: Readonly<Record<string, unknown>>;
|
|
27
|
+
/**
|
|
28
|
+
* GLOBAL CLI flags the command should carry (`--verbose`). They are not
|
|
29
|
+
* params of the verb and have no MCP counterpart, so only the CLI spelling
|
|
30
|
+
* renders them.
|
|
31
|
+
*/
|
|
32
|
+
readonly cliFlags?: readonly string[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** A tool's behavioural category, used for grouping + counts. */
|
|
36
|
+
export type ToolCategory = "read" | "write" | "orientation" | "diagnostic";
|
|
16
37
|
|
|
17
38
|
/** MCP tool annotations mirrored onto exposed verbs. */
|
|
18
39
|
export type McpAnnotations = {
|
|
@@ -115,8 +136,8 @@ export type ParamSpec =
|
|
|
115
136
|
* A repeatable flag ACCUMULATES: `--category css --category git` is
|
|
116
137
|
* the union, never last-wins (repetition is the sanctioned multi-value
|
|
117
138
|
* form, and silently dropping all but the last value is data loss).
|
|
118
|
-
*
|
|
119
|
-
* run body accepts one value or many.
|
|
139
|
+
* Over MCP the same param is an ARRAY that also takes one bare value
|
|
140
|
+
* (`spec/wireType.ts`); the run body accepts one value or many.
|
|
120
141
|
*/
|
|
121
142
|
repeatable?: true;
|
|
122
143
|
}
|
|
@@ -173,8 +194,9 @@ export type ParamSpec =
|
|
|
173
194
|
* mutes it (success-path guidance).
|
|
174
195
|
* - `--format json` AND the MCP tool result: it rides the envelope as
|
|
175
196
|
* `meta.notice` (`project/cli/dispatch.ts#renderData`,
|
|
176
|
-
* `project/mcp/registerVerb.ts#
|
|
177
|
-
*
|
|
197
|
+
* `project/mcp/registerVerb.ts#noticeMeta` — the same key from the same
|
|
198
|
+
* seam; the sentence differs only in how a next step inside it is spelled
|
|
199
|
+
* for its surface). `data` keeps its uniform empty
|
|
178
200
|
* shape; `[]` stays `[]`. Without it an agent could not tell an unbuilt store,
|
|
179
201
|
* a mistyped filter and a genuinely empty result apart — all three were
|
|
180
202
|
* `{"ok":true,"data":[],"meta":{}}`.
|
|
@@ -191,7 +213,12 @@ export interface Formatters<T> {
|
|
|
191
213
|
) => string;
|
|
192
214
|
readonly llm: (d: T) => string;
|
|
193
215
|
readonly json: (d: T) => string;
|
|
194
|
-
|
|
216
|
+
/**
|
|
217
|
+
* `surface` says where the sentence is about to be printed, so a notice that
|
|
218
|
+
* ends in a next call spells it as a tool call on MCP and a command on the
|
|
219
|
+
* CLI (the default).
|
|
220
|
+
*/
|
|
221
|
+
readonly notice?: (d: T, surface?: Surface) => string | undefined;
|
|
195
222
|
/**
|
|
196
223
|
* The MACHINE half of the notice seam: facts about the read that belong in
|
|
197
224
|
* the envelope's `meta` as DATA, merged there by both projectors under the
|
|
@@ -200,7 +227,7 @@ export interface Formatters<T> {
|
|
|
200
227
|
* `notice` is a sentence, and a sentence is the right shape for a human
|
|
201
228
|
* reading stderr and the wrong shape for an agent deciding what to do next.
|
|
202
229
|
* The standing case is the tier scope: `meta.scope` names the tiers a list
|
|
203
|
-
* answered from, so a caller that wants the rest can widen the read without
|
|
230
|
+
* answered from and counts the whole filtered answer per tier, so a caller that wants the rest can widen the read without
|
|
204
231
|
* parsing prose for tier names. Both machine surfaces read this ONE seam, the
|
|
205
232
|
* way they already read one `notice`, so CLI-JSON and MCP stay byte-equal.
|
|
206
233
|
*
|
|
@@ -248,6 +275,24 @@ export interface VerbSpec<P = Record<string, unknown>, R = unknown> {
|
|
|
248
275
|
readonly path: readonly [noun: string, verb?: string];
|
|
249
276
|
readonly summary: string;
|
|
250
277
|
readonly doc?: string;
|
|
278
|
+
/**
|
|
279
|
+
* The question a person would ask that this verb answers, in THEIR words, as
|
|
280
|
+
* a bare clause ("when asked which components use a token" — prose adds the
|
|
281
|
+
* "Use" lead, the catalogue's `use_when` field needs none). With {@link example} it is
|
|
282
|
+
* the one source of guidance: `guidance.ts` builds the MCP tool description,
|
|
283
|
+
* the `capabilities` catalogue and verb help from it, so none is typed twice.
|
|
284
|
+
* Optional in the type because a third-party story may omit it; every verb
|
|
285
|
+
* this distribution registers is held to it by `callRule.test.ts`.
|
|
286
|
+
*/
|
|
287
|
+
readonly useWhen?: string;
|
|
288
|
+
/**
|
|
289
|
+
* One real call's params — the verb is this one, and both spellings derive.
|
|
290
|
+
* Required of a verb with a required param; a verb callable with no arguments
|
|
291
|
+
* declares none, because `tool {}` teaches nothing.
|
|
292
|
+
*/
|
|
293
|
+
readonly example?: Readonly<Record<string, unknown>>;
|
|
294
|
+
/** The catalogue group, where it is not the default: a mutating verb is a `write`, any other a `read`. */
|
|
295
|
+
readonly category?: "orientation" | "diagnostic";
|
|
251
296
|
readonly params: readonly ParamSpec[];
|
|
252
297
|
readonly output: {
|
|
253
298
|
schema?: unknown;
|
|
@@ -505,6 +550,16 @@ export interface CliProjection {
|
|
|
505
550
|
) => ReferenceCliSyntax | undefined;
|
|
506
551
|
}
|
|
507
552
|
|
|
553
|
+
/**
|
|
554
|
+
* What a distribution tells an agent at the MCP handshake, as DATA: the words
|
|
555
|
+
* that name its own tiers and nouns. The kernel adds the generated question →
|
|
556
|
+
* tool index and holds the whole to a hard length ceiling.
|
|
557
|
+
*/
|
|
558
|
+
export interface McpOrientation {
|
|
559
|
+
/** The sentences the instructions open with, in order. */
|
|
560
|
+
readonly conventions: readonly string[];
|
|
561
|
+
}
|
|
562
|
+
|
|
508
563
|
/** A capability module: a named bundle of verbs with optional boot/resources/prompts hooks. */
|
|
509
564
|
export interface CapabilityModule {
|
|
510
565
|
readonly name: string;
|
|
@@ -527,6 +582,8 @@ export interface CapabilityModule {
|
|
|
527
582
|
*/
|
|
528
583
|
readonly story?: true;
|
|
529
584
|
readonly boot?: (rt: PragmaRuntime) => void;
|
|
585
|
+
/** The handshake orientation (a module hook; one module declares it). */
|
|
586
|
+
readonly mcpOrientation?: McpOrientation;
|
|
530
587
|
/** An optional MCP resource surface (NOT a VerbSpec field — a module hook). */
|
|
531
588
|
readonly mcpResources?: McpResourceProvider;
|
|
532
589
|
/**
|