@canonical/pragma-cli 0.32.0 → 0.34.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 +72 -0
- package/dist/pragma +0 -0
- package/package.json +19 -15
- package/pragma.conf.ts +664 -0
- package/src/bin.ts +15 -6
- package/src/capabilities/block.parity.test.ts +250 -0
- package/src/capabilities/capabilities/capabilities.render.ts +3 -2
- package/src/capabilities/capabilities/capabilities.test.ts +10 -1
- package/src/capabilities/capabilities/capabilities.verb.ts +32 -14
- package/src/capabilities/capabilities/catalog.ts +20 -8
- package/src/capabilities/capabilities/hints.ts +21 -36
- package/src/capabilities/capabilities/types.ts +2 -0
- package/src/capabilities/colophon/collectColophon.ts +34 -19
- package/src/capabilities/colophon/colophon.render.test.ts +31 -4
- package/src/capabilities/colophon/colophon.render.ts +13 -4
- package/src/capabilities/colophon/colophon.test.ts +3 -3
- package/src/capabilities/colophon/colophon.verb.ts +11 -4
- package/src/capabilities/colophon/markdownTerminal.ts +33 -14
- package/src/capabilities/colophon/types.ts +16 -7
- package/src/capabilities/config/collectConfigShow.test.ts +57 -0
- package/src/capabilities/config/collectConfigShow.ts +41 -4
- package/src/capabilities/config/fields.ts +10 -108
- package/src/capabilities/config/set.test.ts +6 -6
- package/src/capabilities/config/set.verb.ts +12 -8
- package/src/capabilities/config/show.render.test.ts +137 -0
- package/src/capabilities/config/show.render.ts +57 -16
- package/src/capabilities/config/show.verb.ts +9 -8
- package/src/capabilities/config/types.ts +8 -0
- package/src/capabilities/create/byteEquality.subprocess.test.ts +1 -1
- package/src/capabilities/create/byteEquality.test.ts +82 -101
- package/src/capabilities/create/compiledCreate.subprocess.test.ts +53 -16
- package/src/capabilities/create/constants.ts +75 -0
- package/src/capabilities/create/create.mcp.test.ts +2 -2
- package/src/capabilities/create/create.test.ts +192 -16
- package/src/capabilities/create/create.verb.ts +208 -79
- package/src/capabilities/create/index.ts +2 -10
- package/src/capabilities/create/pickGenerator.ts +34 -28
- package/src/capabilities/create/templates.embedded.generated.ts +4 -29
- package/src/capabilities/create/types.ts +10 -6
- package/src/capabilities/{standard/parity.test.ts → distribution.parity.test.ts} +24 -14
- package/src/capabilities/distribution.test.ts +120 -0
- package/src/capabilities/distribution.ts +59 -0
- package/src/capabilities/doctor/checks/checkConfigFile.ts +6 -5
- package/src/capabilities/doctor/checks/checkKeStore.ts +2 -2
- package/src/capabilities/doctor/checks/checkMcpCommands.ts +6 -0
- package/src/capabilities/doctor/checks/checkMcpConfigured.ts +15 -8
- package/src/capabilities/doctor/checks/checkPackageRefs.ts +79 -45
- package/src/capabilities/doctor/checks/checkPragmaVersion.ts +3 -2
- package/src/capabilities/doctor/checks/checkShellCompletions.test.ts +61 -11
- package/src/capabilities/doctor/checks/checkShellCompletions.ts +63 -23
- package/src/capabilities/doctor/checks/checkSkillsSymlinked.ts +3 -2
- package/src/capabilities/doctor/checks/deriveBand.test.ts +42 -0
- package/src/capabilities/doctor/checks/deriveBand.ts +28 -0
- package/src/capabilities/doctor/doctor.render.test.ts +170 -0
- package/src/capabilities/doctor/doctor.render.ts +117 -34
- package/src/capabilities/doctor/doctor.test.ts +68 -9
- package/src/capabilities/doctor/doctor.verb.ts +6 -2
- package/src/capabilities/doctor/runChecks.ts +33 -9
- package/src/capabilities/doctor/types.ts +16 -1
- package/src/capabilities/emptyRecovery.test.ts +32 -15
- package/src/capabilities/graph/inspect.verb.ts +3 -2
- package/src/capabilities/graph/query.verb.ts +3 -2
- package/src/capabilities/graph/runQuery.ts +8 -5
- package/src/capabilities/index.ts +22 -18
- package/src/capabilities/info/collectInfo.ts +10 -6
- package/src/capabilities/info/info.render.ts +3 -2
- package/src/capabilities/info/info.verb.ts +3 -2
- package/src/capabilities/lazy.test.ts +93 -2
- package/src/capabilities/ontology/index.ts +10 -3
- package/src/capabilities/ontology/ontology.test.ts +157 -5
- package/src/capabilities/ontology/verbs.ts +203 -104
- package/src/capabilities/prompt/prompt.render.test.ts +94 -0
- package/src/capabilities/prompt/prompt.render.ts +23 -6
- package/src/capabilities/prompt/prompt.test.ts +68 -1
- package/src/capabilities/prompt/verbs.ts +24 -11
- package/src/capabilities/reference.test.ts +32 -0
- package/src/capabilities/resources/provider.ts +58 -25
- package/src/capabilities/resources/resources.test.ts +176 -21
- package/src/capabilities/scopeTypeSync.test.ts +37 -0
- package/src/capabilities/setup/index.ts +7 -1
- package/src/capabilities/setup/operations/setupCompletions.ts +49 -16
- package/src/capabilities/setup/operations/setupGenerator.ts +166 -63
- package/src/capabilities/setup/operations/setupLsp.ts +56 -12
- package/src/capabilities/setup/operations/setupMcp.ts +216 -39
- package/src/capabilities/setup/operations/setupSkills.ts +10 -4
- package/src/capabilities/setup/setup.render.test.ts +109 -0
- package/src/capabilities/setup/setup.render.ts +61 -14
- package/src/capabilities/setup/setup.test.ts +352 -1
- package/src/capabilities/setup/setup.verb.ts +68 -9
- package/src/capabilities/setup/shell.ts +29 -3
- package/src/capabilities/setup/types.ts +59 -2
- package/src/capabilities/shared/bands.ts +16 -0
- package/src/capabilities/skill/discover.ts +11 -5
- package/src/capabilities/skill/render.ts +4 -5
- package/src/capabilities/skill/verbs.ts +7 -7
- package/src/capabilities/sources/collectStatus.ts +69 -49
- package/src/capabilities/sources/index.ts +1 -1
- package/src/capabilities/sources/installSkills.ts +2 -2
- package/src/capabilities/sources/runUpdate.ts +146 -98
- package/src/capabilities/sources/sources.test.ts +264 -84
- package/src/capabilities/sources/status.render.test.ts +94 -0
- package/src/capabilities/sources/status.render.ts +55 -33
- package/src/capabilities/sources/status.verb.ts +15 -8
- package/src/capabilities/sources/types.ts +18 -25
- package/src/capabilities/sources/update.render.ts +9 -2
- package/src/capabilities/sources/update.verb.ts +12 -23
- package/src/capabilities/surface.test.ts +65 -17
- package/src/capabilities/tier.parity.test.ts +144 -0
- package/src/capabilities/upgrade/upgrade.verb.ts +7 -3
- package/src/constants.test.ts +9 -7
- package/src/constants.ts +49 -23
- package/src/identity.test.ts +333 -0
- package/src/index.ts +2 -2
- package/src/kernel/completion/__snapshots__/emitScripts.test.ts.snap +18 -16
- package/src/kernel/completion/complete.test.ts +3 -5
- package/src/kernel/completion/emitScripts.test.ts +1 -1
- package/src/kernel/completion/emitScripts.ts +4 -2
- package/src/kernel/completion/entitySource.test.ts +185 -41
- package/src/kernel/completion/entitySource.ts +131 -113
- package/src/kernel/completion/model.test.ts +3 -3
- package/src/kernel/completion/model.ts +11 -1
- package/src/kernel/completion/parse.test.ts +2 -4
- package/src/kernel/completion/safety.test.ts +82 -36
- package/src/kernel/completion/shellDrive.test.ts +592 -0
- package/src/kernel/completion/templates/fish.ts +28 -6
- package/src/kernel/completion/types.ts +3 -3
- package/src/kernel/config/__snapshots__/firstRun.test.ts.snap +1 -1
- package/src/kernel/config/defaults.test.ts +85 -0
- package/src/kernel/config/defaults.ts +12 -23
- package/src/kernel/config/evaluateProjectConfig.ts +2 -3
- package/src/kernel/config/findProjectConfig.ts +4 -3
- package/src/kernel/config/firstRun.ts +12 -5
- package/src/kernel/config/globalConfig.ts +2 -2
- package/src/kernel/config/index.ts +4 -5
- package/src/kernel/config/paths.ts +17 -17
- package/src/kernel/config/readConfig.test.ts +242 -6
- package/src/kernel/config/readConfig.ts +8 -8
- package/src/kernel/config/schema.test.ts +50 -0
- package/src/kernel/config/schema.ts +97 -10
- package/src/kernel/config/types.ts +73 -19
- package/src/kernel/copy.test.ts +453 -0
- package/src/kernel/error/PragmaError.test.ts +5 -5
- package/src/kernel/error/constants.ts +3 -2
- package/src/kernel/error/index.ts +1 -1
- package/src/kernel/error/recovery.test.ts +22 -45
- package/src/kernel/error/recovery.ts +22 -27
- package/src/kernel/error/renderError.ts +1 -1
- package/src/kernel/error/types.ts +5 -4
- package/src/kernel/packs/collect.test.ts +195 -11
- package/src/kernel/packs/collect.ts +213 -58
- package/src/kernel/packs/compile.test.ts +163 -7
- package/src/kernel/packs/compile.ts +15 -12
- package/src/kernel/packs/disclosure.test.ts +5 -2
- package/src/kernel/packs/graphql/fetchGraphqlLookup.ts +11 -10
- package/src/kernel/packs/graphqlSource.test.ts +6 -3
- package/src/kernel/packs/iri.ts +5 -7
- package/src/kernel/packs/renderPack.ts +5 -3
- package/src/kernel/packs/resolveEntity.ts +21 -17
- package/src/kernel/packs/runBodies.ts +15 -14
- package/src/kernel/packs/schema.ts +96 -10
- package/src/kernel/packs/sparql/runSelect.test.ts +104 -10
- package/src/kernel/packs/sparql/runSelect.ts +73 -34
- package/src/kernel/packs/toolDescription.test.ts +10 -2
- package/src/kernel/packs/types.ts +39 -4
- package/src/kernel/packs/uniqueness.ts +4 -4
- package/src/kernel/project/cli/buildProgram.test.ts +60 -11
- package/src/kernel/project/cli/buildProgram.ts +11 -0
- package/src/kernel/project/cli/dispatch.test.ts +67 -0
- package/src/kernel/project/cli/dispatch.ts +34 -12
- package/src/kernel/project/cli/globalFlags.test.ts +12 -4
- package/src/kernel/project/cli/globalFlags.ts +14 -14
- package/src/kernel/project/cli/helpFormat.ts +1 -1
- package/src/kernel/project/cli/rootHelp.ts +109 -107
- package/src/kernel/project/mcp/buildServer.ts +11 -6
- package/src/kernel/project/mcp/instructions.ts +12 -7
- package/src/kernel/project/mcp/mcpError.ts +52 -0
- package/src/kernel/project/mcp/parity.test.ts +39 -1
- package/src/kernel/project/mcp/prompts/prompts.test.ts +128 -5
- package/src/kernel/project/mcp/prompts/provider.ts +51 -7
- package/src/kernel/project/mcp/prompts/source.ts +68 -67
- package/src/kernel/project/mcp/registerVerb.ts +25 -6
- package/src/kernel/project/mcp/serve.ts +11 -3
- package/src/kernel/project/mcp/toolDescriptions.test.ts +6 -2
- package/src/kernel/render/disclosure.ts +4 -2
- package/src/kernel/render/formatters.ts +5 -4
- package/src/kernel/render/prefixes.test.ts +36 -0
- package/src/kernel/render/prefixes.ts +40 -13
- package/src/kernel/render/renderers.test.ts +35 -1
- package/src/kernel/render/renderers.ts +25 -7
- package/src/kernel/render/style.test.ts +31 -0
- package/src/kernel/render/style.ts +74 -0
- package/src/kernel/runtime/graphpack/build.ts +57 -3
- package/src/kernel/runtime/graphpack/buildIndex.test.ts +149 -0
- package/src/kernel/runtime/graphpack/buildIndex.ts +173 -50
- package/src/kernel/runtime/graphpack/embedded/pack.generated.ts +5 -8
- package/src/kernel/runtime/graphpack/embedded/pack.index.generated.ts +3 -4
- package/src/kernel/runtime/graphpack/embedded/pack.stories.generated.ts +3 -0
- package/src/kernel/runtime/graphpack/embedded.ts +37 -11
- package/src/kernel/runtime/graphpack/graphpack.test.ts +274 -6
- package/src/kernel/runtime/graphpack/hash.ts +3 -2
- package/src/kernel/runtime/graphpack/index.ts +1 -1
- package/src/kernel/runtime/graphpack/manifest.ts +114 -12
- package/src/kernel/runtime/graphpack/read.ts +32 -22
- package/src/kernel/runtime/graphpack/schemas.ts +74 -0
- package/src/kernel/runtime/graphpack/stories.ts +84 -0
- package/src/kernel/runtime/graphpack/types.ts +72 -45
- package/src/kernel/runtime/loadSession.ts +4 -12
- package/src/kernel/runtime/paths.ts +64 -15
- package/src/kernel/runtime/probe.test.ts +8 -4
- package/src/kernel/runtime/readEntity.ts +6 -5
- package/src/kernel/runtime/refs/gitOps.ts +1 -19
- package/src/kernel/runtime/refs/parseRef.ts +7 -7
- package/src/kernel/runtime/refs/resolve.ts +141 -65
- package/src/kernel/runtime/resolveSources.test.ts +63 -24
- package/src/kernel/runtime/resolveSources.ts +36 -25
- package/src/kernel/runtime/storeReadiness.ts +62 -0
- package/src/kernel/runtime/storeless.test.ts +1 -1
- package/src/kernel/runtime/types.ts +1 -1
- package/src/kernel/runtime/wasmEmbed.test.ts +25 -20
- package/src/kernel/spec/emitReference.test.ts +196 -0
- package/src/kernel/spec/emitReference.ts +624 -0
- package/src/kernel/spec/emitSurface.ts +13 -7
- package/src/kernel/spec/index.ts +2 -0
- package/src/kernel/spec/surfaceConformance.test.ts +2 -2
- package/src/kernel/spec/surfaceConformance.ts +8 -0
- package/src/kernel/spec/types.ts +29 -9
- package/src/kernel/spec/validate.ts +2 -1
- package/src/kernel/vocabulary.test.ts +164 -0
- package/src/kernel/vocabulary.ts +146 -0
- package/src/testing/PARITY_GAPS.ts +9 -3
- package/src/testing/behavioral/README.md +3 -3
- package/src/testing/behavioral/autoLlm.e2e.test.ts +12 -2
- package/src/testing/behavioral/completion.test.ts +21 -8
- package/src/testing/behavioral/disclosure.test.ts +12 -0
- package/src/testing/behavioral/docExamples.test.ts +283 -0
- package/src/testing/behavioral/errorMatrix.mcp.test.ts +4 -3
- package/src/testing/behavioral/errorMatrix.storeless.test.ts +3 -2
- package/src/testing/behavioral/exitCodes.e2e.test.ts +6 -5
- package/src/testing/behavioral/harness.foundation.test.ts +14 -5
- package/src/testing/behavioral/journeys.cli.test.ts +116 -73
- package/src/testing/behavioral/journeys.defaultPack.test.ts +477 -0
- package/src/testing/behavioral/journeys.firstInstall.test.ts +167 -0
- package/src/testing/behavioral/journeys.interactiveTty.e2e.test.ts +66 -0
- package/src/testing/behavioral/journeys.livePack.e2e.test.ts +129 -0
- package/src/testing/behavioral/journeys.mcpLifecycle.mcp.test.ts +179 -0
- package/src/testing/behavioral/journeys.packageStories.test.ts +188 -0
- package/src/testing/behavioral/liveReadSurface.ts +4 -3
- package/src/testing/behavioral/mcpSurface.test.ts +10 -5
- package/src/testing/behavioral/staleCommands.test.ts +86 -0
- package/src/testing/eval/__snapshots__/eval.test.ts.snap +9 -9
- package/src/testing/eval/cases/assembly.ts +16 -9
- package/src/testing/eval/cases/effectful.ts +31 -16
- package/src/testing/eval/cases/readNouns.ts +23 -18
- package/src/testing/eval/cases/stable.ts +5 -1
- package/src/testing/eval/eval.test.ts +7 -1
- package/src/testing/fixtures/fixtureCapability.ts +201 -1
- package/src/testing/fixtures/graph/canonical.ts +6 -3
- package/src/testing/fixtures/graph/defaultPack.ts +179 -0
- package/src/testing/fixtures/graph/owlExport.ts +97 -0
- package/src/testing/helpers/extractSnippets.ts +29 -0
- package/src/testing/helpers/fixtureGraph.ts +33 -16
- package/src/testing/helpers/golden.ts +1 -1
- package/src/testing/helpers/packRuntime.ts +2 -3
- package/src/testing/helpers/projectCli.ts +2 -1
- package/src/testing/helpers/projectMcp.ts +6 -0
- package/src/testing/perf/budgets.test.ts +4 -0
- package/src/testing/perf/budgets.ts +22 -7
- package/src/testing/perf/globalSetup.ts +121 -6
- package/src/types.ts +2 -2
- package/src/capabilities/block/blockList.verb.ts +0 -150
- package/src/capabilities/block/index.ts +0 -25
- package/src/capabilities/block/pack.ts +0 -155
- package/src/capabilities/block/parity.test.ts +0 -180
- package/src/capabilities/block/tierChain.ts +0 -78
- package/src/capabilities/colophon/pragmaColophon.ts +0 -66
- package/src/capabilities/config/field.test.ts +0 -188
- package/src/capabilities/modifier/index.ts +0 -15
- package/src/capabilities/modifier/pack.ts +0 -64
- package/src/capabilities/standard/index.ts +0 -18
- package/src/capabilities/standard/pack.ts +0 -128
- package/src/capabilities/tier/index.ts +0 -19
- package/src/capabilities/tier/lookup.render.ts +0 -34
- package/src/capabilities/tier/lookup.test.ts +0 -68
- package/src/capabilities/tier/lookup.verb.ts +0 -44
- package/src/capabilities/tier/pack.ts +0 -28
- package/src/capabilities/tier/runLookup.ts +0 -59
- package/src/capabilities/token/addConfig.render.ts +0 -27
- package/src/capabilities/token/addConfig.test.ts +0 -62
- package/src/capabilities/token/addConfig.verb.ts +0 -40
- package/src/capabilities/token/index.ts +0 -19
- package/src/capabilities/token/pack.ts +0 -58
- package/src/capabilities/token/runAddConfig.ts +0 -95
- package/src/kernel/completion/bashDrive.test.ts +0 -73
- package/src/kernel/runtime/graphpack/embedded/sample.ttl +0 -39
- package/src/kernel/runtime/lock.ts +0 -90
|
@@ -61,6 +61,50 @@ describe("buildProgram — flags", () => {
|
|
|
61
61
|
});
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
+
describe("buildProgram — default-true boolean negation (B9)", () => {
|
|
65
|
+
const negatableVerb: VerbSpec = {
|
|
66
|
+
path: ["thing", "do"],
|
|
67
|
+
summary: "thing do summary",
|
|
68
|
+
params: [
|
|
69
|
+
{ kind: "boolean", name: "ssr", doc: "Include SSR.", default: true },
|
|
70
|
+
{
|
|
71
|
+
kind: "boolean",
|
|
72
|
+
name: "runInstall",
|
|
73
|
+
doc: "Install now.",
|
|
74
|
+
default: false,
|
|
75
|
+
},
|
|
76
|
+
{ kind: "boolean", name: "withHistory", doc: "Seed history." },
|
|
77
|
+
],
|
|
78
|
+
output: {
|
|
79
|
+
formatters: {
|
|
80
|
+
plain: (d) => String(d),
|
|
81
|
+
llm: (d) => String(d),
|
|
82
|
+
json: (d) => JSON.stringify(d),
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
capability: { needsStore: false, mutates: false, mcp: { expose: true } },
|
|
86
|
+
run: async () => null,
|
|
87
|
+
};
|
|
88
|
+
const program = projectCli([{ name: "thing", verbs: [negatableVerb] }]);
|
|
89
|
+
const doCmd = program.commands
|
|
90
|
+
.find((c) => c.name() === "thing")
|
|
91
|
+
?.commands.find((c) => c.name() === "do");
|
|
92
|
+
|
|
93
|
+
it("pairs a `--no-` negation onto a default-true boolean so it is disableable", () => {
|
|
94
|
+
const longs = doCmd?.options.map((o) => o.long) ?? [];
|
|
95
|
+
expect(longs).toContain("--ssr");
|
|
96
|
+
expect(longs).toContain("--no-ssr");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("leaves default-false / defaultless booleans un-negated (no Commander default-flip)", () => {
|
|
100
|
+
const longs = doCmd?.options.map((o) => o.long) ?? [];
|
|
101
|
+
expect(longs).toContain("--run-install");
|
|
102
|
+
expect(longs).not.toContain("--no-run-install");
|
|
103
|
+
expect(longs).toContain("--with-history");
|
|
104
|
+
expect(longs).not.toContain("--no-with-history");
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
64
108
|
describe("buildProgram — early exits", () => {
|
|
65
109
|
it("throws commander.version for --version, writing the version", async () => {
|
|
66
110
|
const program = projectCli([fixtureModule]);
|
|
@@ -334,9 +378,15 @@ describe("formatRootHelp — grouping", () => {
|
|
|
334
378
|
makeVerb(["block", "list"]),
|
|
335
379
|
]);
|
|
336
380
|
|
|
337
|
-
it("
|
|
338
|
-
|
|
381
|
+
it("leads with the uncurated nouns, untitled, under the usage line", () => {
|
|
382
|
+
// `block` is not in the kernel's curated table, so it leads the page with
|
|
383
|
+
// no heading of its own — the header already carries the blurb, and the
|
|
384
|
+
// kernel has no name for the domain. The kernel's own nouns keep their
|
|
385
|
+
// task-oriented sections.
|
|
386
|
+
expect(help).toMatch(/^Usage: pragma .*\n\n {2}block\b/m);
|
|
339
387
|
expect(help).toContain("Set up & maintain");
|
|
388
|
+
// The blurb appears exactly once: in the header.
|
|
389
|
+
expect(help.match(/pragma test/g)).toHaveLength(1);
|
|
340
390
|
});
|
|
341
391
|
|
|
342
392
|
it("lists the live nouns and always-available mcp", () => {
|
|
@@ -353,7 +403,8 @@ describe("formatRootHelp — grouping", () => {
|
|
|
353
403
|
|
|
354
404
|
it("shows the global flags block", () => {
|
|
355
405
|
expect(help).toContain("Global flags");
|
|
356
|
-
expect(help).toContain("--llm");
|
|
406
|
+
expect(help).not.toContain("--llm");
|
|
407
|
+
expect(help).toContain("--format <plain|llm|json>");
|
|
357
408
|
expect(help).toContain("--detail <level>");
|
|
358
409
|
});
|
|
359
410
|
|
|
@@ -363,8 +414,7 @@ describe("formatRootHelp — grouping", () => {
|
|
|
363
414
|
|
|
364
415
|
Usage: pragma <command> [subcommand] [flags]
|
|
365
416
|
|
|
366
|
-
|
|
367
|
-
block Inspect components & patterns and their anatomy
|
|
417
|
+
block block list summary
|
|
368
418
|
|
|
369
419
|
Set up & maintain
|
|
370
420
|
config Read and write pragma configuration
|
|
@@ -374,12 +424,11 @@ describe("formatRootHelp — grouping", () => {
|
|
|
374
424
|
mcp Start the MCP server over stdio
|
|
375
425
|
|
|
376
426
|
Global flags
|
|
377
|
-
--llm
|
|
378
|
-
--
|
|
379
|
-
--
|
|
380
|
-
--
|
|
381
|
-
--
|
|
382
|
-
--version Show the CLI version
|
|
427
|
+
--format <plain|llm|json> Select output format (llm = condensed Markdown for agents)
|
|
428
|
+
--detail <level> Progressive-disclosure level (summary, standard, detailed)
|
|
429
|
+
--verbose Diagnostic output on stderr
|
|
430
|
+
--help Show help (works on any command)
|
|
431
|
+
--version Show the CLI version
|
|
383
432
|
|
|
384
433
|
Run \`pragma <command> --help\` for details, or \`pragma capabilities\` to get oriented."
|
|
385
434
|
`);
|
|
@@ -70,6 +70,17 @@ function registerParams(command: Command, verb: VerbSpec): void {
|
|
|
70
70
|
} else {
|
|
71
71
|
command.option(spec, param.doc);
|
|
72
72
|
}
|
|
73
|
+
// B9: a default-`true` boolean is otherwise undisableable — the grammar has
|
|
74
|
+
// no negation form, so `create`'s sibling booleans (--ssr/--with-styles/…)
|
|
75
|
+
// could never be turned off. Register a paired `--no-<flag>` so they can:
|
|
76
|
+
// Commander keeps the `true` default and lets `--no-<flag>` set it false
|
|
77
|
+
// (verified: an unset default-false boolean is left alone). This is a CLI
|
|
78
|
+
// PARSE convenience, never a ParamSpec — `emitSurface`/MCP/the covenant are
|
|
79
|
+
// untouched — so the frozen flag list stands and negation stays additive.
|
|
80
|
+
if (param.kind === "boolean" && param.default === true) {
|
|
81
|
+
const flag = kebabCase(param.name);
|
|
82
|
+
command.option(`--no-${flag}`, `Disable --${flag} (on by default).`);
|
|
83
|
+
}
|
|
73
84
|
}
|
|
74
85
|
|
|
75
86
|
if (verb.capability.mutates) {
|
|
@@ -1,11 +1,18 @@
|
|
|
1
|
+
import { existsSync, mkdtempSync, readdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
1
4
|
import { fail, succeed } from "@canonical/task";
|
|
2
5
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { fixturePreviewModule } from "../../../testing/fixtures/fixtureCapability.js";
|
|
3
7
|
import { PragmaError } from "../../error/PragmaError.js";
|
|
4
8
|
import { bootRuntime } from "../../runtime/boot.js";
|
|
5
9
|
import type { GlobalFlags } from "../../runtime/types.js";
|
|
6
10
|
import type { ParamSpec, VerbSpec } from "../../spec/types.js";
|
|
7
11
|
import { dispatch, executeVerb, extractParams } from "./dispatch.js";
|
|
8
12
|
|
|
13
|
+
/** The read-then-write mutation the honest-preview guards drive. */
|
|
14
|
+
const graft = fixturePreviewModule.verbs[0] as VerbSpec;
|
|
15
|
+
|
|
9
16
|
const PLAIN: GlobalFlags = {
|
|
10
17
|
llm: false,
|
|
11
18
|
autoLlm: false,
|
|
@@ -233,6 +240,66 @@ describe("dispatch — errors", () => {
|
|
|
233
240
|
});
|
|
234
241
|
});
|
|
235
242
|
|
|
243
|
+
describe("--dry-run is honest (PRA-104)", () => {
|
|
244
|
+
const savedExit = process.exitCode;
|
|
245
|
+
afterEach(() => {
|
|
246
|
+
process.exitCode = savedExit;
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
/** Capture stderr for one dispatch. */
|
|
250
|
+
const dispatchCapturing = async (
|
|
251
|
+
positionals: readonly string[],
|
|
252
|
+
opts: Record<string, unknown>,
|
|
253
|
+
): Promise<string> => {
|
|
254
|
+
const errs: string[] = [];
|
|
255
|
+
const spy = vi
|
|
256
|
+
.spyOn(process.stderr, "write")
|
|
257
|
+
.mockImplementation((chunk: string | Uint8Array) => {
|
|
258
|
+
errs.push(String(chunk));
|
|
259
|
+
return true;
|
|
260
|
+
});
|
|
261
|
+
process.exitCode = 0;
|
|
262
|
+
try {
|
|
263
|
+
await dispatch(graft, positionals, opts, PLAIN);
|
|
264
|
+
} finally {
|
|
265
|
+
spy.mockRestore();
|
|
266
|
+
}
|
|
267
|
+
return errs.join("");
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
it("exits NONZERO when the mutation's first read would fail for real", async () => {
|
|
271
|
+
// The structural false positive: this same call used to print a full plan
|
|
272
|
+
// and exit 0, while the run without --dry-run died on the missing file.
|
|
273
|
+
const missing = join(mkdtempSync(join(tmpdir(), "pragma-graft-")), "gone");
|
|
274
|
+
const out = await dispatchCapturing([missing], { dryRun: true });
|
|
275
|
+
expect(process.exitCode).toBe(1);
|
|
276
|
+
expect(out).toContain(missing);
|
|
277
|
+
expect(existsSync(`${missing}.grafted`)).toBe(false);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("still plans, exits 0, and writes nothing when the read would succeed", async () => {
|
|
281
|
+
const dir = mkdtempSync(join(tmpdir(), "pragma-graft-"));
|
|
282
|
+
const source = join(dir, "present.txt");
|
|
283
|
+
writeFileSync(source, "content\n");
|
|
284
|
+
const outs: string[] = [];
|
|
285
|
+
const spy = vi
|
|
286
|
+
.spyOn(process.stdout, "write")
|
|
287
|
+
.mockImplementation((chunk: string | Uint8Array) => {
|
|
288
|
+
outs.push(String(chunk));
|
|
289
|
+
return true;
|
|
290
|
+
});
|
|
291
|
+
process.exitCode = 0;
|
|
292
|
+
try {
|
|
293
|
+
await dispatch(graft, [source], { dryRun: true }, PLAIN);
|
|
294
|
+
} finally {
|
|
295
|
+
spy.mockRestore();
|
|
296
|
+
}
|
|
297
|
+
expect(process.exitCode).toBe(0);
|
|
298
|
+
expect(outs.join("")).toContain(`Write file: ${source}.grafted`);
|
|
299
|
+
expect(readdirSync(dir)).toEqual(["present.txt"]);
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
236
303
|
describe("executeVerb — interactivity gate (H3)", () => {
|
|
237
304
|
const setTTY = (
|
|
238
305
|
stream: NodeJS.ReadStream | NodeJS.WriteStream,
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
* exit code.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { describeEffect,
|
|
15
|
-
import { runTask, runUndo } from "@canonical/task/node";
|
|
14
|
+
import { describeEffect, type Task } from "@canonical/task";
|
|
15
|
+
import { runPreview, runTask, runUndo } from "@canonical/task/node";
|
|
16
16
|
import {
|
|
17
17
|
asPragmaError,
|
|
18
18
|
CANCELLED_MESSAGE,
|
|
@@ -221,8 +221,11 @@ export async function executeVerb(
|
|
|
221
221
|
// Also hand it the interaction context so an interactive verb can pick its
|
|
222
222
|
// prompt strategy. The verb's `run` sets `mutationRuntime.exec` (the runner
|
|
223
223
|
// options) as its last act; the projector reads it back on the real-run
|
|
224
|
-
// branch
|
|
225
|
-
//
|
|
224
|
+
// branch AND on the dry-run branch, which takes `cwd` (so the preview reads
|
|
225
|
+
// the tree the run would write into) and `onEffectStart` (so the stamping
|
|
226
|
+
// transform runs, and planned byte counts match written ones). It never
|
|
227
|
+
// takes the prompt handler: a preview auto-answers prompts and so can never
|
|
228
|
+
// block on input. `--undo` stays handler-free and untouched by this seam.
|
|
226
229
|
const controller = new AbortController();
|
|
227
230
|
const interaction: InteractionRuntime = {
|
|
228
231
|
// Gate on STDERR (H3): the Ink wizard renders to stderr and reads stdin,
|
|
@@ -252,14 +255,33 @@ export async function executeVerb(
|
|
|
252
255
|
| Promise<Task<unknown>>,
|
|
253
256
|
);
|
|
254
257
|
if (mutation.dryRun) {
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
258
|
+
// The HONEST preview (PR7): reads hit the real filesystem, writes are
|
|
259
|
+
// recorded and never executed. A mutation whose real run would die on its
|
|
260
|
+
// first template read now fails HERE too, so `--dry-run` exits nonzero
|
|
261
|
+
// exactly when the run would — the plan is a prediction, not a wish.
|
|
262
|
+
// `exec.cwd` is the same write root the real run resolves paths against,
|
|
263
|
+
// and `exec.onEffectStart` carries summon's stamping transform, so the
|
|
264
|
+
// planned byte counts are the bytes the run would actually write.
|
|
265
|
+
const previewExec = mutationRuntime.exec ?? {};
|
|
266
|
+
try {
|
|
267
|
+
const { effects } = await runPreview(task, {
|
|
268
|
+
cwd: previewExec.cwd,
|
|
269
|
+
onEffectStart: previewExec.onEffectStart,
|
|
270
|
+
onLog: logToStderr,
|
|
271
|
+
});
|
|
272
|
+
// A plan is the effects a mutation WOULD apply — a `Prompt` is not one,
|
|
273
|
+
// so the interactive confirm gate / answer prompts never clutter it.
|
|
274
|
+
return renderPlan(
|
|
275
|
+
flags,
|
|
276
|
+
effects
|
|
277
|
+
.filter((effect) => effect._tag !== "Prompt")
|
|
278
|
+
.map(describeEffect),
|
|
279
|
+
);
|
|
280
|
+
} finally {
|
|
281
|
+
// A verb that mounted an interactive session before returning its Task
|
|
282
|
+
// must be torn down on this branch too, exactly as on the real run.
|
|
283
|
+
await previewExec.dispose?.();
|
|
284
|
+
}
|
|
263
285
|
}
|
|
264
286
|
if (mutation.undo) {
|
|
265
287
|
const { undoCount } = await runUndo(task, { onLog: logToStderr });
|
|
@@ -9,10 +9,18 @@ const TTY = { isTty: true, noAutoLlm: false };
|
|
|
9
9
|
const PIPE = { isTty: false, noAutoLlm: false };
|
|
10
10
|
|
|
11
11
|
describe("parseGlobalFlags", () => {
|
|
12
|
-
it("honours an explicit --llm", () => {
|
|
13
|
-
expect(parseGlobalFlags(["--llm"], TTY)).toMatchObject({
|
|
12
|
+
it("honours an explicit --format llm (condensed even on a TTY)", () => {
|
|
13
|
+
expect(parseGlobalFlags(["--format", "llm"], TTY)).toMatchObject({
|
|
14
14
|
llm: true,
|
|
15
15
|
autoLlm: false,
|
|
16
|
+
format: "llm",
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("forces human output with --format plain down a pipe", () => {
|
|
21
|
+
expect(parseGlobalFlags(["--format", "plain"], PIPE)).toMatchObject({
|
|
22
|
+
llm: false,
|
|
23
|
+
autoLlm: false,
|
|
16
24
|
format: "plain",
|
|
17
25
|
});
|
|
18
26
|
});
|
|
@@ -69,7 +77,7 @@ describe("stripGlobalFlags", () => {
|
|
|
69
77
|
"list",
|
|
70
78
|
]);
|
|
71
79
|
expect(
|
|
72
|
-
stripGlobalFlags(["block", "--
|
|
80
|
+
stripGlobalFlags(["block", "--verbose", "list", "--detail", "detailed"]),
|
|
73
81
|
).toEqual(["block", "list"]);
|
|
74
82
|
expect(stripGlobalFlags(["--format=json", "config", "show"])).toEqual([
|
|
75
83
|
"config",
|
|
@@ -90,7 +98,7 @@ describe("readRawFormat", () => {
|
|
|
90
98
|
|
|
91
99
|
it("reports a valueless --format as empty string", () => {
|
|
92
100
|
expect(readRawFormat(["--format"])).toBe("");
|
|
93
|
-
expect(readRawFormat(["--format", "--
|
|
101
|
+
expect(readRawFormat(["--format", "--verbose"])).toBe("");
|
|
94
102
|
});
|
|
95
103
|
|
|
96
104
|
it("returns undefined when --format is absent", () => {
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Global-flag pre-parsing, ahead of Commander.
|
|
3
3
|
*
|
|
4
|
-
* `--
|
|
5
|
-
*
|
|
4
|
+
* `--format`, `--verbose`, and `--detail` may appear anywhere on the line, so
|
|
5
|
+
* they are scanned and stripped before Commander sees argv — otherwise
|
|
6
6
|
* `enablePositionalOptions()` scoping would reject a flag placed after a verb.
|
|
7
7
|
* Ported from the v1 `parseGlobalFlags`, with two v2 changes: a new `--detail`
|
|
8
8
|
* flag and the `--format text` value renamed to `plain` (the kernel's
|
|
9
|
-
* {@link OutputFormat}).
|
|
10
|
-
*
|
|
9
|
+
* {@link OutputFormat}). The dedicated `--llm` flag was folded into
|
|
10
|
+
* `--format llm`, leaving auto-detection as the sole implicit trigger. Both the
|
|
11
|
+
* space (`--format json`) and equals (`--format=json`) forms are recognized.
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
import { DETAIL_LEVELS, type DetailLevel } from "../../../constants.js";
|
|
@@ -86,10 +87,11 @@ function readDetail(argv: readonly string[]): DetailLevel | undefined {
|
|
|
86
87
|
/**
|
|
87
88
|
* Extract the global flags from raw argv (the user's arguments, argv[2:]).
|
|
88
89
|
*
|
|
89
|
-
* Auto-LLM: with no `--
|
|
90
|
+
* Auto-LLM: with no explicit `--format` and a non-interactive stdout (piped or
|
|
90
91
|
* redirected — the shape an agent captures), `llm` defaults to true so agents
|
|
91
|
-
* get condensed Markdown without a flag.
|
|
92
|
-
* `
|
|
92
|
+
* get condensed Markdown without a flag. Any explicit `--format` overrides it —
|
|
93
|
+
* `--format plain` forces human output down a pipe, `--format llm` forces the
|
|
94
|
+
* condensed form even on a TTY — as does `PRAGMA_NO_AUTO_LLM`.
|
|
93
95
|
*
|
|
94
96
|
* @param argv - The user's arguments (no `node`/script prefix).
|
|
95
97
|
* @param env - Output environment probe (defaults to the real process).
|
|
@@ -100,15 +102,14 @@ export function parseGlobalFlags(
|
|
|
100
102
|
argv: readonly string[],
|
|
101
103
|
env: OutputEnvironment = readOutputEnvironment(),
|
|
102
104
|
): GlobalFlags {
|
|
103
|
-
const explicitLlm = argv.includes("--llm");
|
|
104
105
|
const rawFormat = readFlagValue(argv, "--format");
|
|
105
|
-
const format =
|
|
106
|
+
const format =
|
|
107
|
+
rawFormat === "json" ? "json" : rawFormat === "llm" ? "llm" : "plain";
|
|
106
108
|
const formatRequested = rawFormat !== undefined;
|
|
107
|
-
const autoLlm =
|
|
108
|
-
!explicitLlm && !formatRequested && !env.isTty && !env.noAutoLlm;
|
|
109
|
+
const autoLlm = !formatRequested && !env.isTty && !env.noAutoLlm;
|
|
109
110
|
const detail = readDetail(argv);
|
|
110
111
|
return {
|
|
111
|
-
llm:
|
|
112
|
+
llm: format === "llm" || autoLlm,
|
|
112
113
|
autoLlm,
|
|
113
114
|
format,
|
|
114
115
|
verbose: argv.includes("--verbose"),
|
|
@@ -130,10 +131,9 @@ export function stripGlobalFlags(argv: readonly string[]): string[] {
|
|
|
130
131
|
const result: string[] = [];
|
|
131
132
|
for (let i = 0; i < argv.length; i++) {
|
|
132
133
|
const arg = argv[i];
|
|
133
|
-
if (arg === "--
|
|
134
|
+
if (arg === "--verbose") continue;
|
|
134
135
|
if (
|
|
135
136
|
arg?.startsWith("--format=") ||
|
|
136
|
-
arg?.startsWith("--llm=") ||
|
|
137
137
|
arg?.startsWith("--verbose=") ||
|
|
138
138
|
arg?.startsWith("--detail=")
|
|
139
139
|
) {
|
|
@@ -17,7 +17,7 @@ const INDENT = " ";
|
|
|
17
17
|
/** Two-space gap between a term column and its description. */
|
|
18
18
|
const GAP = " ";
|
|
19
19
|
|
|
20
|
-
/** A bold section header (`Flags`, `Verbs`, `
|
|
20
|
+
/** A bold section header (`Flags`, `Verbs`, `Set up & maintain`). */
|
|
21
21
|
export function helpHeading(text: string): string {
|
|
22
22
|
return chalk.bold(text);
|
|
23
23
|
}
|