@canonical/pragma-cli 0.33.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 +7 -7
- package/dist/pragma +0 -0
- package/package.json +18 -15
- package/pragma.conf.ts +664 -0
- package/src/bin.ts +13 -4
- package/src/capabilities/block.parity.test.ts +250 -0
- package/src/capabilities/capabilities/capabilities.render.ts +3 -2
- package/src/capabilities/capabilities/capabilities.verb.ts +31 -13
- package/src/capabilities/capabilities/catalog.ts +18 -10
- package/src/capabilities/capabilities/hints.ts +13 -19
- package/src/capabilities/colophon/collectColophon.ts +34 -19
- package/src/capabilities/colophon/colophon.verb.ts +8 -4
- 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 +0 -27
- package/src/capabilities/config/set.verb.ts +7 -3
- package/src/capabilities/config/show.render.test.ts +77 -5
- package/src/capabilities/config/show.render.ts +7 -12
- package/src/capabilities/config/show.verb.ts +4 -3
- package/src/capabilities/config/types.ts +8 -0
- package/src/capabilities/create/byteEquality.test.ts +78 -100
- package/src/capabilities/create/compiledCreate.subprocess.test.ts +52 -15
- 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 +162 -15
- package/src/capabilities/create/create.verb.ts +140 -69
- 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/checkMcpConfigured.ts +7 -6
- 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/doctor.render.test.ts +6 -6
- package/src/capabilities/doctor/doctor.render.ts +2 -1
- package/src/capabilities/doctor/doctor.test.ts +37 -8
- package/src/capabilities/doctor/doctor.verb.ts +6 -2
- package/src/capabilities/doctor/runChecks.ts +5 -5
- package/src/capabilities/doctor/types.ts +1 -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/verbs.ts +21 -21
- package/src/capabilities/prompt/prompt.test.ts +12 -6
- package/src/capabilities/prompt/verbs.ts +24 -11
- package/src/capabilities/resources/provider.ts +30 -11
- package/src/capabilities/resources/resources.test.ts +85 -29
- package/src/capabilities/setup/operations/setupGenerator.ts +16 -9
- package/src/capabilities/setup/operations/setupMcp.ts +2 -2
- package/src/capabilities/setup/operations/setupSkills.ts +3 -3
- package/src/capabilities/setup/setup.verb.ts +12 -5
- package/src/capabilities/setup/shell.ts +29 -3
- 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 +55 -50
- package/src/capabilities/sources/index.ts +1 -1
- package/src/capabilities/sources/installSkills.ts +2 -2
- package/src/capabilities/sources/runUpdate.ts +113 -122
- package/src/capabilities/sources/sources.test.ts +111 -117
- package/src/capabilities/sources/status.render.test.ts +53 -24
- package/src/capabilities/sources/status.render.ts +24 -29
- 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 +33 -10
- package/src/capabilities/tier.parity.test.ts +144 -0
- package/src/capabilities/upgrade/upgrade.verb.ts +7 -3
- package/src/constants.test.ts +8 -6
- package/src/constants.ts +47 -21
- package/src/identity.test.ts +333 -0
- package/src/index.ts +2 -2
- package/src/kernel/completion/__snapshots__/emitScripts.test.ts.snap +5 -2
- 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 +117 -107
- package/src/kernel/completion/model.test.ts +1 -1
- package/src/kernel/completion/model.ts +11 -0
- 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 +1 -1
- 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/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 +9 -4
- package/src/kernel/project/cli/dispatch.test.ts +67 -0
- package/src/kernel/project/cli/dispatch.ts +34 -12
- package/src/kernel/project/cli/helpFormat.ts +1 -1
- package/src/kernel/project/cli/rootHelp.ts +105 -105
- package/src/kernel/project/mcp/buildServer.ts +11 -6
- package/src/kernel/project/mcp/instructions.ts +11 -6
- package/src/kernel/project/mcp/parity.test.ts +39 -1
- package/src/kernel/project/mcp/prompts/prompts.test.ts +120 -5
- package/src/kernel/project/mcp/prompts/provider.ts +22 -8
- 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/prefixes.test.ts +36 -0
- package/src/kernel/render/prefixes.ts +40 -13
- package/src/kernel/render/renderers.test.ts +1 -1
- package/src/kernel/runtime/graphpack/build.ts +29 -3
- package/src/kernel/runtime/graphpack/buildIndex.test.ts +46 -0
- package/src/kernel/runtime/graphpack/buildIndex.ts +52 -17
- package/src/kernel/runtime/graphpack/embedded/pack.generated.ts +5 -5
- package/src/kernel/runtime/graphpack/embedded/pack.index.generated.ts +3 -3
- 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 +205 -5
- 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 +4 -9
- 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 +61 -48
- 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 +78 -62
- package/src/kernel/runtime/resolveSources.test.ts +63 -24
- package/src/kernel/runtime/resolveSources.ts +36 -25
- package/src/kernel/runtime/storeReadiness.ts +9 -8
- package/src/kernel/runtime/storeless.test.ts +1 -1
- package/src/kernel/runtime/wasmEmbed.test.ts +22 -19
- package/src/kernel/spec/emitReference.test.ts +3 -3
- package/src/kernel/spec/emitReference.ts +144 -11
- package/src/kernel/spec/emitSurface.ts +9 -5
- 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 +8 -2
- package/src/testing/behavioral/README.md +3 -3
- package/src/testing/behavioral/completion.test.ts +19 -6
- package/src/testing/behavioral/errorMatrix.mcp.test.ts +4 -3
- package/src/testing/behavioral/errorMatrix.storeless.test.ts +1 -1
- 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 -75
- package/src/testing/behavioral/journeys.defaultPack.test.ts +82 -176
- package/src/testing/behavioral/journeys.firstInstall.test.ts +167 -0
- package/src/testing/behavioral/journeys.mcpLifecycle.mcp.test.ts +9 -6
- 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 +9 -4
- package/src/testing/behavioral/staleCommands.test.ts +10 -0
- package/src/testing/eval/__snapshots__/eval.test.ts.snap +7 -7
- package/src/testing/eval/cases/assembly.ts +13 -6
- package/src/testing/eval/cases/effectful.ts +25 -13
- package/src/testing/eval/cases/readNouns.ts +22 -20
- package/src/testing/eval/cases/stable.ts +5 -1
- package/src/testing/eval/eval.test.ts +7 -1
- package/src/testing/fixtures/fixtureCapability.ts +45 -1
- package/src/testing/fixtures/graph/canonical.ts +6 -3
- package/src/testing/fixtures/graph/defaultPack.ts +6 -5
- package/src/testing/fixtures/graph/owlExport.ts +7 -5
- package/src/testing/helpers/fixtureGraph.ts +33 -16
- 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.test.ts +0 -79
- package/src/capabilities/block/blockList.verb.ts +0 -163
- package/src/capabilities/block/index.ts +0 -25
- package/src/capabilities/block/pack.ts +0 -166
- package/src/capabilities/block/parity.test.ts +0 -187
- package/src/capabilities/block/tierChain.ts +0 -78
- package/src/capabilities/colophon/pragmaColophon.ts +0 -66
- 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.test.ts +0 -72
- package/src/capabilities/tier/lookup.render.ts +0 -59
- package/src/capabilities/tier/lookup.test.ts +0 -83
- package/src/capabilities/tier/lookup.verb.ts +0 -44
- package/src/capabilities/tier/pack.ts +0 -28
- package/src/capabilities/tier/runLookup.ts +0 -87
- 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
|
@@ -1,73 +1,107 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Doctor check: report
|
|
2
|
+
* Doctor check: report which pack answers this project's reads.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* It switches on the ONE boot decision ({@link resolveSources}) rather than
|
|
5
|
+
* re-deriving it from config, so doctor can never call an install healthy that
|
|
6
|
+
* every read would fail. That is the whole point of the check: a project that
|
|
7
|
+
* configured its own packs and never built them reports `fail` with the update
|
|
8
|
+
* remedy — not a pass listing the distribution's packs it is not reading.
|
|
9
|
+
* Storeless throughout: the pointer, the manifest, and the entity index are
|
|
10
|
+
* read off disk, never through a store session.
|
|
11
|
+
*
|
|
12
|
+
* It also names every read story that pack carries but cannot use. Those are
|
|
13
|
+
* DROPPED at dispatch rather than thrown (a third-party story must never break
|
|
14
|
+
* a command), so doctor is where the user finds out — and it goes through the
|
|
15
|
+
* same `validateStories` the dispatch path uses, so the two can never disagree.
|
|
9
16
|
*/
|
|
10
17
|
|
|
18
|
+
import { BIN_NAME } from "../../../constants.js";
|
|
11
19
|
import {
|
|
12
20
|
entityTotal,
|
|
13
21
|
readPackIndex,
|
|
14
22
|
} from "../../../kernel/completion/entitySource.js";
|
|
15
|
-
import
|
|
16
|
-
import {
|
|
23
|
+
import { validateStories } from "../../../kernel/packs/collect.js";
|
|
24
|
+
import { embeddedManifest } from "../../../kernel/runtime/graphpack/embedded.js";
|
|
25
|
+
import { readManifest } from "../../../kernel/runtime/graphpack/manifest.js";
|
|
26
|
+
import { activeStories } from "../../../kernel/runtime/graphpack/stories.js";
|
|
27
|
+
import type { SourcesDecision } from "../../../kernel/runtime/resolveSources.js";
|
|
28
|
+
import { resolveSources } from "../../../kernel/runtime/resolveSources.js";
|
|
17
29
|
import type { PragmaRuntime } from "../../../kernel/runtime/types.js";
|
|
18
30
|
import type { CheckItem, CheckResult } from "../types.js";
|
|
19
31
|
|
|
20
|
-
|
|
21
|
-
|
|
32
|
+
/**
|
|
33
|
+
* The stories the answering pack carries but cannot use, as check items.
|
|
34
|
+
*
|
|
35
|
+
* The registry is reached through a RUNTIME dynamic import (as
|
|
36
|
+
* `collectColophon` does) so the static `capabilities/index → doctorModule →
|
|
37
|
+
* checkPackageRefs → capabilities/index` cycle can never form.
|
|
38
|
+
*
|
|
39
|
+
* @param decision - The boot decision the check already resolved.
|
|
40
|
+
* @returns One failing item per ignored story, in declaration order.
|
|
41
|
+
* @note Impure — reads the answering pack's `stories.json`.
|
|
42
|
+
*/
|
|
43
|
+
async function ignoredStoryItems(
|
|
44
|
+
decision: SourcesDecision,
|
|
45
|
+
): Promise<CheckItem[]> {
|
|
46
|
+
const { capabilities } = await import("../../index.js");
|
|
47
|
+
const { problems } = validateStories(activeStories(decision), capabilities);
|
|
48
|
+
return problems.map((problem) => ({
|
|
49
|
+
label: problem.source,
|
|
50
|
+
detail: problem.message,
|
|
51
|
+
status: "fail" as const,
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** ` · N story/ies ignored` for a headline, or nothing when all are usable. */
|
|
56
|
+
function ignoredSuffix(items: readonly CheckItem[]): string {
|
|
57
|
+
if (items.length === 0) return "";
|
|
58
|
+
return ` · ${items.length} ${items.length === 1 ? "story" : "stories"} ignored`;
|
|
59
|
+
}
|
|
22
60
|
|
|
23
61
|
/**
|
|
24
|
-
* Report
|
|
62
|
+
* Report which pack answers reads, its provenance, and its entity total.
|
|
25
63
|
*
|
|
26
64
|
* @param rt - The per-invocation runtime.
|
|
27
|
-
* @returns A CheckResult
|
|
28
|
-
* @note Impure — reads config, the
|
|
65
|
+
* @returns A CheckResult naming the answering pack, or the failure to build one.
|
|
66
|
+
* @note Impure — reads config, the active-pack pointer, and the pack index.
|
|
29
67
|
*/
|
|
30
68
|
export async function checkPackageRefs(
|
|
31
69
|
rt: PragmaRuntime,
|
|
32
70
|
): Promise<CheckResult> {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
71
|
+
const decision = resolveSources(await rt.loadConfig(), rt.cwd);
|
|
72
|
+
// The display says "packs" (config declarations); the file/function keep
|
|
73
|
+
// "Package" (npm/git resolution vocabulary).
|
|
74
|
+
const name = "pack refs";
|
|
37
75
|
|
|
38
|
-
if (
|
|
76
|
+
if (decision.kind === "unavailable") {
|
|
39
77
|
return {
|
|
40
|
-
name
|
|
41
|
-
status: "
|
|
42
|
-
detail:
|
|
78
|
+
name,
|
|
79
|
+
status: "fail",
|
|
80
|
+
detail: decision.reason,
|
|
81
|
+
remedy: `${BIN_NAME} sources update`,
|
|
43
82
|
};
|
|
44
83
|
}
|
|
45
84
|
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
items.
|
|
59
|
-
|
|
60
|
-
}
|
|
85
|
+
const index = readPackIndex(decision);
|
|
86
|
+
const entities = (index ? entityTotal(index) : 0).toLocaleString();
|
|
87
|
+
// The pack DOES answer reads, so the check itself passes; the ignored stories
|
|
88
|
+
// are failing sub-items under it. (A `warn` status would ripple through the
|
|
89
|
+
// renderer and the DoctorData counts to buy an icon.)
|
|
90
|
+
const items = await ignoredStoryItems(decision);
|
|
91
|
+
const suffix = ignoredSuffix(items);
|
|
92
|
+
if (decision.kind === "embedded") {
|
|
93
|
+
return {
|
|
94
|
+
name,
|
|
95
|
+
status: "pass",
|
|
96
|
+
detail: `embedded snapshot @ ${embeddedManifest().sourceRef} — ${entities} entities${suffix} · \`${BIN_NAME} sources update\` to build from the configured packs`,
|
|
97
|
+
...(items.length > 0 ? { items } : {}),
|
|
98
|
+
};
|
|
61
99
|
}
|
|
62
|
-
|
|
63
|
-
const ok = lock !== undefined && unresolved === 0;
|
|
100
|
+
const manifest = readManifest(decision.dir);
|
|
64
101
|
return {
|
|
65
|
-
name
|
|
66
|
-
status:
|
|
67
|
-
detail:
|
|
68
|
-
|
|
69
|
-
: `${unresolved} of ${entries.length} package(s) not resolved`,
|
|
70
|
-
items,
|
|
71
|
-
...(ok ? {} : { remedy: "pragma sources update" }),
|
|
102
|
+
name,
|
|
103
|
+
status: "pass",
|
|
104
|
+
detail: `${manifest?.sourceRef ?? decision.contentHash.slice(0, 12)} — ${entities} entities, built ${manifest?.createdAt ?? "?"}${suffix}`,
|
|
105
|
+
...(items.length > 0 ? { items } : {}),
|
|
72
106
|
};
|
|
73
107
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { BIN_NAME } from "../../../constants.js";
|
|
1
2
|
import type { PragmaRuntime } from "../../../kernel/runtime/types.js";
|
|
2
3
|
import { detectInstallSource } from "../../shared/packageManager.js";
|
|
3
4
|
import type { CheckResult } from "../types.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
|
-
* Report the
|
|
7
|
+
* Report the CLI version and how it was installed. Always passes — purely
|
|
7
8
|
* informational.
|
|
8
9
|
*
|
|
9
10
|
* @param rt - The per-invocation runtime (for `version`).
|
|
@@ -15,7 +16,7 @@ export async function checkPragmaVersion(
|
|
|
15
16
|
): Promise<CheckResult> {
|
|
16
17
|
const install = detectInstallSource();
|
|
17
18
|
return {
|
|
18
|
-
name:
|
|
19
|
+
name: `${BIN_NAME} version`,
|
|
19
20
|
status: "pass",
|
|
20
21
|
detail: `v${rt.version} (installed via ${install.label})`,
|
|
21
22
|
};
|
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `checkShellCompletions` — the doctor check that verifies completions are
|
|
3
|
-
* installed AND functional.
|
|
3
|
+
* installed AND up to date AND functional.
|
|
4
4
|
*
|
|
5
5
|
* Runs against an isolated HOME and a controlled `$SHELL` so the three gates —
|
|
6
|
-
* the resolver effect test, the
|
|
7
|
-
*
|
|
6
|
+
* the resolver effect test, the install probe, and the zsh fpath activation
|
|
7
|
+
* gate — are deterministic. The effect test (gate 1) drives the real
|
|
8
8
|
* `runComplete` resolver; the install/fpath gates write real files under the
|
|
9
9
|
* temp HOME rather than mocking the fs.
|
|
10
|
+
*
|
|
11
|
+
* Gate 2 compares BYTES, not existence, so the "installed" cases here write the
|
|
12
|
+
* real `emitScripts(capabilities)[shell]` body. A file that merely exists is
|
|
13
|
+
* now the failing case, and it has its own test — as does the boundary on the
|
|
14
|
+
* other side, where a per-project `completion` config must NOT be able to
|
|
15
|
+
* condemn the one global script.
|
|
10
16
|
*/
|
|
11
17
|
|
|
12
18
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
13
19
|
import { tmpdir } from "node:os";
|
|
14
20
|
import { dirname, join } from "node:path";
|
|
15
21
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
16
|
-
import {
|
|
22
|
+
import { emitScripts } from "../../../kernel/completion/emitScripts.js";
|
|
23
|
+
import { capabilities } from "../../index.js";
|
|
24
|
+
import { detectCompletions } from "../../setup/operations/setupCompletions.js";
|
|
25
|
+
import { completionScriptPath, type ShellId } from "../../setup/shell.js";
|
|
17
26
|
import { checkShellCompletions } from "./checkShellCompletions.js";
|
|
18
27
|
|
|
19
28
|
const roots: string[] = [];
|
|
@@ -38,11 +47,16 @@ afterEach(() => {
|
|
|
38
47
|
roots.length = 0;
|
|
39
48
|
});
|
|
40
49
|
|
|
41
|
-
/** Write
|
|
42
|
-
function
|
|
50
|
+
/** Write `body` to the shell's real install path. */
|
|
51
|
+
function writeScript(shell: ShellId, body: string): void {
|
|
43
52
|
const path = completionScriptPath(shell);
|
|
44
53
|
mkdirSync(dirname(path), { recursive: true });
|
|
45
|
-
writeFileSync(path,
|
|
54
|
+
writeFileSync(path, body);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Install the script `setup completions` would write for this shell. */
|
|
58
|
+
function installScript(shell: ShellId): void {
|
|
59
|
+
writeScript(shell, emitScripts(capabilities)[shell]);
|
|
46
60
|
}
|
|
47
61
|
|
|
48
62
|
/** Write a `.zshrc` that puts ~/.zfunc on fpath. */
|
|
@@ -73,20 +87,56 @@ describe("checkShellCompletions — install probe (gate 2)", () => {
|
|
|
73
87
|
expect(result.remedy).toBe("pragma setup completions");
|
|
74
88
|
});
|
|
75
89
|
|
|
76
|
-
it("passes for bash once the script
|
|
90
|
+
it("passes for bash once the up-to-date script is at its real path", async () => {
|
|
77
91
|
process.env.SHELL = "/usr/bin/bash";
|
|
78
92
|
installScript("bash");
|
|
79
93
|
const result = await checkShellCompletions(tmp());
|
|
80
94
|
expect(result.status).toBe("pass");
|
|
81
|
-
expect(result.detail).toMatch(/bash
|
|
95
|
+
expect(result.detail).toMatch(/bash up to date and resolving/);
|
|
82
96
|
});
|
|
83
97
|
|
|
84
|
-
it("passes for fish once the script
|
|
98
|
+
it("passes for fish once the up-to-date script is at its real path", async () => {
|
|
85
99
|
process.env.SHELL = "/usr/bin/fish";
|
|
86
100
|
installScript("fish");
|
|
87
101
|
const result = await checkShellCompletions(tmp());
|
|
88
102
|
expect(result.status).toBe("pass");
|
|
89
103
|
});
|
|
104
|
+
|
|
105
|
+
it("fails when the installed script is out of date", async () => {
|
|
106
|
+
// Reproduced against the compiled binary before this was fixed: overwrite
|
|
107
|
+
// the installed script with junk and `doctor` still reported
|
|
108
|
+
// "✓ Shell completions: bash installed and resolving (19 nouns)", while
|
|
109
|
+
// `setup completions` in the same breath said "Updating bash completions".
|
|
110
|
+
// Every user upgrading across a grammar change lands here: the old script
|
|
111
|
+
// offers the old nouns and misses the new ones, and doctor is the one
|
|
112
|
+
// command whose job is to say so.
|
|
113
|
+
process.env.SHELL = "/usr/bin/bash";
|
|
114
|
+
writeScript("bash", "# STALE JUNK\n");
|
|
115
|
+
const result = await checkShellCompletions(tmp());
|
|
116
|
+
expect(result.status).toBe("fail");
|
|
117
|
+
expect(result.detail).toMatch(/out of date/);
|
|
118
|
+
expect(result.remedy).toBe("pragma setup completions");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("does NOT condemn the global script for one project's `completion` config", async () => {
|
|
122
|
+
// The script is ONE file per user; `completion` config is layered per
|
|
123
|
+
// project. Comparing the global file against only THIS directory's body
|
|
124
|
+
// made `doctor`'s verdict flip with `cd`: reproduced against the real
|
|
125
|
+
// check, a project declaring `minChars: 5` passed in its own directory and
|
|
126
|
+
// failed in every other, and running the offered remedy there moved the
|
|
127
|
+
// failure back rather than closing it. The first assertion is the
|
|
128
|
+
// discriminator — this project really does ask for a different body — so
|
|
129
|
+
// deleting the config write below turns the test red.
|
|
130
|
+
process.env.SHELL = "/usr/bin/bash";
|
|
131
|
+
installScript("bash");
|
|
132
|
+
const cwd = tmp();
|
|
133
|
+
writeFileSync(
|
|
134
|
+
join(cwd, "pragma.config.ts"),
|
|
135
|
+
"export default { completion: { minChars: 5 } };\n",
|
|
136
|
+
);
|
|
137
|
+
expect((await detectCompletions(cwd)).state).toBe("stale");
|
|
138
|
+
expect((await checkShellCompletions(cwd)).status).toBe("pass");
|
|
139
|
+
});
|
|
90
140
|
});
|
|
91
141
|
|
|
92
142
|
describe("checkShellCompletions — zsh fpath activation (gate 3)", () => {
|
|
@@ -106,6 +156,6 @@ describe("checkShellCompletions — zsh fpath activation (gate 3)", () => {
|
|
|
106
156
|
wireZfunc();
|
|
107
157
|
const result = await checkShellCompletions(tmp());
|
|
108
158
|
expect(result.status).toBe("pass");
|
|
109
|
-
expect(result.detail).toMatch(/zsh
|
|
159
|
+
expect(result.detail).toMatch(/zsh up to date and resolving/);
|
|
110
160
|
});
|
|
111
161
|
});
|
|
@@ -1,20 +1,48 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { BIN_NAME } from "../../../constants.js";
|
|
4
5
|
import {
|
|
6
|
+
emitScripts,
|
|
5
7
|
indexCompletionEnv,
|
|
6
8
|
runComplete,
|
|
7
9
|
} from "../../../kernel/completion/index.js";
|
|
8
10
|
import { capabilities } from "../../index.js";
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
completionScriptPath,
|
|
12
|
-
detectShell,
|
|
13
|
-
} from "../../setup/shell.js";
|
|
11
|
+
import { detectCompletions } from "../../setup/operations/setupCompletions.js";
|
|
12
|
+
import { activationHint, type ShellId } from "../../setup/shell.js";
|
|
14
13
|
import type { CheckResult } from "../types.js";
|
|
15
14
|
|
|
16
15
|
const NAME = "Shell completions";
|
|
17
16
|
|
|
17
|
+
/** The one remedy for an absent or out-of-date script. */
|
|
18
|
+
const INSTALL_REMEDY = `${BIN_NAME} setup completions`;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Whether the installed script is the config-free emit — the body
|
|
22
|
+
* `setup completions` writes when no `completion` config tunes it.
|
|
23
|
+
*
|
|
24
|
+
* The completion script is ONE file per user, but the `completion` config it is
|
|
25
|
+
* rendered from is layered per project. So `detectCompletions(cwd)`'s `stale`
|
|
26
|
+
* means only "not what THIS directory would write", and on its own it would let
|
|
27
|
+
* any project that tunes `minChars` call a globally-correct script out of date
|
|
28
|
+
* from every other directory — a `doctor` verdict that flips with `cd`, and a
|
|
29
|
+
* remedy that just moves the failure to the project you came from. A script
|
|
30
|
+
* that matches neither this project's body nor the untuned one is the one that
|
|
31
|
+
* is genuinely stale.
|
|
32
|
+
*
|
|
33
|
+
* @param path - The install path.
|
|
34
|
+
* @param shell - The detected shell.
|
|
35
|
+
* @returns Whether the file there is byte-identical to the untuned emit.
|
|
36
|
+
* @note Impure — reads the install path. An unreadable file counts as `false`.
|
|
37
|
+
*/
|
|
38
|
+
function isUntunedScript(path: string, shell: ShellId): boolean {
|
|
39
|
+
try {
|
|
40
|
+
return readFileSync(path, "utf-8") === emitScripts(capabilities)[shell];
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
18
46
|
/**
|
|
19
47
|
* Whether the user's `.zshrc` puts `~/.zfunc` on `$fpath` — the activation step
|
|
20
48
|
* `setup completions` cannot perform for the user. zsh loads nothing from
|
|
@@ -53,23 +81,28 @@ async function completeProbe(cwd: string): Promise<number> {
|
|
|
53
81
|
}
|
|
54
82
|
|
|
55
83
|
/**
|
|
56
|
-
* Check that shell completions are installed AND functional.
|
|
84
|
+
* Check that shell completions are installed AND up to date AND functional.
|
|
57
85
|
*
|
|
58
86
|
* Three gates, in order:
|
|
59
87
|
* 1. The resolver answers (`completeProbe` drives `runComplete` end to end) —
|
|
60
88
|
* the effect every installed script depends on. A failure here is a real
|
|
61
89
|
* regression, so it fails even when nothing is installed.
|
|
62
|
-
* 2. The script
|
|
63
|
-
*
|
|
64
|
-
*
|
|
90
|
+
* 2. The installed script is present AND is a script `setup completions` would
|
|
91
|
+
* write. Bytes, not existence: a file that merely exists proves nothing — a
|
|
92
|
+
* user upgrading across a grammar change keeps the old script, loses the
|
|
93
|
+
* moved nouns from TAB, and used to be told everything was fine. The path
|
|
94
|
+
* and this project's body come from `detectCompletions`, the ONE owner of
|
|
95
|
+
* that decision; {@link isUntunedScript} is what keeps a per-project
|
|
96
|
+
* `completion` config from condemning a global file (see its docblock).
|
|
65
97
|
* 3. For zsh, `~/.zfunc` is on `$fpath` — the activation step setup can only
|
|
66
98
|
* hint. Installed-but-unwired reports a distinct remedy.
|
|
67
99
|
*
|
|
68
|
-
* @param cwd - The project directory (
|
|
69
|
-
*
|
|
100
|
+
* @param cwd - The project directory (the resolver's entity seam, and the
|
|
101
|
+
* `completion` config gate 2 renders this project's body from).
|
|
102
|
+
* @returns A CheckResult: pass (up to date + wired + answering), fail (with the
|
|
70
103
|
* attributable remedy), or skip (shell undetected).
|
|
71
|
-
* @note Impure — reads `$SHELL`, the install path, `.zshrc`,
|
|
72
|
-
* storeless resolver.
|
|
104
|
+
* @note Impure — reads `$SHELL`, the install path, the config layers, `.zshrc`,
|
|
105
|
+
* and drives the storeless resolver.
|
|
73
106
|
*/
|
|
74
107
|
export async function checkShellCompletions(cwd: string): Promise<CheckResult> {
|
|
75
108
|
// 1. Effect test: the resolver the scripts delegate to must actually answer.
|
|
@@ -82,34 +115,41 @@ export async function checkShellCompletions(cwd: string): Promise<CheckResult> {
|
|
|
82
115
|
name: NAME,
|
|
83
116
|
status: "fail",
|
|
84
117
|
detail: `completion resolver failed: ${reason}`,
|
|
85
|
-
remedy:
|
|
118
|
+
remedy: `Report this as a bug — \`${BIN_NAME} __complete\` should never throw.`,
|
|
86
119
|
};
|
|
87
120
|
}
|
|
88
121
|
if (candidates === 0) {
|
|
89
122
|
return {
|
|
90
123
|
name: NAME,
|
|
91
124
|
status: "fail",
|
|
92
|
-
detail:
|
|
125
|
+
detail: `completion resolver returned no candidates for \`${BIN_NAME} <TAB>\``,
|
|
93
126
|
remedy: "Report this as a bug — the noun context is always non-empty.",
|
|
94
127
|
};
|
|
95
128
|
}
|
|
96
129
|
|
|
97
|
-
// 2. The script
|
|
98
|
-
const shell =
|
|
99
|
-
if (
|
|
130
|
+
// 2. The installed script is a script `setup completions` would write.
|
|
131
|
+
const { shell, path, state } = await detectCompletions(cwd);
|
|
132
|
+
if (shell === null || path === null) {
|
|
100
133
|
return {
|
|
101
134
|
name: NAME,
|
|
102
135
|
status: "skip",
|
|
103
136
|
detail: "resolver OK; shell not detected ($SHELL unset)",
|
|
104
137
|
};
|
|
105
138
|
}
|
|
106
|
-
|
|
107
|
-
if (!existsSync(path)) {
|
|
139
|
+
if (state === "absent") {
|
|
108
140
|
return {
|
|
109
141
|
name: NAME,
|
|
110
142
|
status: "fail",
|
|
111
143
|
detail: `resolver OK; ${shell} script not installed`,
|
|
112
|
-
remedy:
|
|
144
|
+
remedy: INSTALL_REMEDY,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (state === "stale" && !isUntunedScript(path, shell)) {
|
|
148
|
+
return {
|
|
149
|
+
name: NAME,
|
|
150
|
+
status: "fail",
|
|
151
|
+
detail: `resolver OK; ${shell} script at ${path} is out of date`,
|
|
152
|
+
remedy: INSTALL_REMEDY,
|
|
113
153
|
};
|
|
114
154
|
}
|
|
115
155
|
|
|
@@ -126,6 +166,6 @@ export async function checkShellCompletions(cwd: string): Promise<CheckResult> {
|
|
|
126
166
|
return {
|
|
127
167
|
name: NAME,
|
|
128
168
|
status: "pass",
|
|
129
|
-
detail: `${shell}
|
|
169
|
+
detail: `${shell} up to date and resolving (${candidates} nouns)`,
|
|
130
170
|
};
|
|
131
171
|
}
|
|
@@ -2,6 +2,7 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import type { DetectedHarness } from "@canonical/harnesses";
|
|
3
3
|
import { detectHarnesses } from "@canonical/harnesses";
|
|
4
4
|
import { runTask } from "@canonical/task/node";
|
|
5
|
+
import { BIN_NAME } from "../../../constants.js";
|
|
5
6
|
import type { CheckResult } from "../types.js";
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -21,7 +22,7 @@ export async function checkSkillsSymlinked(cwd: string): Promise<CheckResult> {
|
|
|
21
22
|
name: "Skills symlinked",
|
|
22
23
|
status: "fail",
|
|
23
24
|
detail: "harness detection failed",
|
|
24
|
-
remedy:
|
|
25
|
+
remedy: `${BIN_NAME} setup skills`,
|
|
25
26
|
};
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -55,6 +56,6 @@ export async function checkSkillsSymlinked(cwd: string): Promise<CheckResult> {
|
|
|
55
56
|
name: "Skills symlinked",
|
|
56
57
|
status: "fail",
|
|
57
58
|
detail: `missing for ${missing.join(", ")}`,
|
|
58
|
-
remedy:
|
|
59
|
+
remedy: `${BIN_NAME} setup skills`,
|
|
59
60
|
};
|
|
60
61
|
}
|
|
@@ -24,7 +24,7 @@ const BANDED_DATA: DoctorData = {
|
|
|
24
24
|
{
|
|
25
25
|
name: "Shell completions",
|
|
26
26
|
status: "pass",
|
|
27
|
-
detail: "zsh
|
|
27
|
+
detail: "zsh up to date and resolving",
|
|
28
28
|
band: "global",
|
|
29
29
|
},
|
|
30
30
|
{
|
|
@@ -51,12 +51,12 @@ const COLOR_DATA: DoctorData = {
|
|
|
51
51
|
checks: [
|
|
52
52
|
{ name: "Node version", status: "pass", detail: "v24" },
|
|
53
53
|
{
|
|
54
|
-
name: "
|
|
54
|
+
name: "pack refs",
|
|
55
55
|
status: "fail",
|
|
56
|
-
detail: "
|
|
56
|
+
detail: "packs are configured but the store has not been built",
|
|
57
57
|
items: [
|
|
58
|
-
{ label: "core", status: "fail", detail: "
|
|
59
|
-
{ label: "ui", status: "fail", detail: "
|
|
58
|
+
{ label: "core", status: "fail", detail: "not built" },
|
|
59
|
+
{ label: "ui", status: "fail", detail: "not built" },
|
|
60
60
|
],
|
|
61
61
|
remedy: "pragma sources update",
|
|
62
62
|
},
|
|
@@ -151,7 +151,7 @@ describe("doctor render — piped output is ANSI-free (F1)", () => {
|
|
|
151
151
|
// The structural, color-free content still renders (glyphs, names, remedy).
|
|
152
152
|
expect(out).toContain("pragma doctor");
|
|
153
153
|
expect(out).toContain("✓ Node version");
|
|
154
|
-
expect(out).toContain("✗
|
|
154
|
+
expect(out).toContain("✗ pack refs");
|
|
155
155
|
expect(out).toContain("○ Skills symlinked");
|
|
156
156
|
expect(out).toContain("↳ fix: pragma sources update");
|
|
157
157
|
expect(out).toContain(" 1 passed · 1 failed · 1 skipped");
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import chalk from "chalk";
|
|
14
|
+
import { BIN_NAME } from "../../constants.js";
|
|
14
15
|
import { defaultStyle, type RenderStyle } from "../../kernel/render/style.js";
|
|
15
16
|
import type { Formatters } from "../../kernel/spec/types.js";
|
|
16
17
|
import { BAND_LABELS } from "../shared/bands.js";
|
|
@@ -134,7 +135,7 @@ export const doctorFormatters: Formatters<DoctorData> = {
|
|
|
134
135
|
plain(data) {
|
|
135
136
|
const style = doctorStyle();
|
|
136
137
|
const nameWidth = Math.max(...data.checks.map((c) => c.name.length), 0);
|
|
137
|
-
const lines: string[] = [style.bold(
|
|
138
|
+
const lines: string[] = [style.bold(`${BIN_NAME} doctor`), ""];
|
|
138
139
|
const { environment, machine, project } = partitionByBand(data.checks);
|
|
139
140
|
const section = (heading: string, checks: CheckResult[]): void => {
|
|
140
141
|
if (checks.length === 0) return;
|
|
@@ -79,10 +79,9 @@ const defaultLayers: ConfigLayers = {
|
|
|
79
79
|
tier: "default",
|
|
80
80
|
channel: "default",
|
|
81
81
|
detail: "default",
|
|
82
|
-
|
|
82
|
+
packs: "default",
|
|
83
83
|
stories: "default",
|
|
84
84
|
prefixes: "default",
|
|
85
|
-
prompts: "default",
|
|
86
85
|
},
|
|
87
86
|
global: { path: "", exists: false },
|
|
88
87
|
project: { exists: false },
|
|
@@ -122,12 +121,12 @@ describe("doctor — shape & spread", () => {
|
|
|
122
121
|
}
|
|
123
122
|
// Deterministic under the isolated env.
|
|
124
123
|
expect(byName(data, "Node version")?.status).toBe("pass");
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
const pkgRefs = byName(data, "
|
|
128
|
-
expect(pkgRefs?.status).toBe("
|
|
129
|
-
expect(pkgRefs?.
|
|
130
|
-
expect(pkgRefs?.
|
|
124
|
+
// Nothing built, but the packs are the distribution's own — so the embedded
|
|
125
|
+
// snapshot answers reads and the check passes, naming what it is reading.
|
|
126
|
+
const pkgRefs = byName(data, "pack refs");
|
|
127
|
+
expect(pkgRefs?.status).toBe("pass");
|
|
128
|
+
expect(pkgRefs?.detail).toContain("embedded snapshot @ ");
|
|
129
|
+
expect(pkgRefs?.remedy).toBeUndefined();
|
|
131
130
|
// No harnesses in an empty HOME/cwd — attributable fail + skips.
|
|
132
131
|
expect(byName(data, "MCP configured")?.status).toBe("fail");
|
|
133
132
|
expect(byName(data, "Skills symlinked")?.status).toBe("skip");
|
|
@@ -137,6 +136,36 @@ describe("doctor — shape & spread", () => {
|
|
|
137
136
|
});
|
|
138
137
|
});
|
|
139
138
|
|
|
139
|
+
describe("doctor — the pack-refs check", () => {
|
|
140
|
+
it("a project with its OWN packs and nothing built is an attributable fail", async () => {
|
|
141
|
+
// The install that must never read healthy: `origins.packs` is "project",
|
|
142
|
+
// so the embedded snapshot is a DIFFERENT graph and every read throws
|
|
143
|
+
// STORE_UNAVAILABLE. Doctor has to say so and name the fix, rather than
|
|
144
|
+
// pass by listing packs it is not actually reading from.
|
|
145
|
+
const cwd = tmp("pragma-unbuilt-");
|
|
146
|
+
writeFileSync(
|
|
147
|
+
join(cwd, "pragma.config.ts"),
|
|
148
|
+
'export default { packs: [{ name: "unbuilt", source: "file:///pragma-never-built" }] };\n',
|
|
149
|
+
);
|
|
150
|
+
const data = await runChecks(bootRuntime(FLAGS, cwd));
|
|
151
|
+
const pkgRefs = byName(data, "pack refs");
|
|
152
|
+
expect(pkgRefs?.status).toBe("fail");
|
|
153
|
+
expect(pkgRefs?.detail).toContain("the store has not been built");
|
|
154
|
+
expect(pkgRefs?.remedy).toBe("pragma sources update");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("a project pointed at its own built pack passes, naming that pack", async () => {
|
|
158
|
+
const fixture = await bootFixtureRuntime({ ttl: CANONICAL_TTL });
|
|
159
|
+
const data = await runChecks(fixture.runtime);
|
|
160
|
+
const pkgRefs = byName(data, "pack refs");
|
|
161
|
+
expect(pkgRefs?.status).toBe("pass");
|
|
162
|
+
// The fixture pack's own provenance label, not the embedded snapshot's.
|
|
163
|
+
expect(pkgRefs?.detail).toContain("fixture");
|
|
164
|
+
expect(pkgRefs?.detail).not.toContain("embedded snapshot");
|
|
165
|
+
await fixture.dispose();
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
140
169
|
describe("doctor — the store check", () => {
|
|
141
170
|
it("a store that fails to boot is an attributable fail, not a crash", async () => {
|
|
142
171
|
const data = await runChecks(throwingStoreRuntime(tmp("pragma-proj-")));
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* `doctor-exit-zero-with-failures`.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import { BIN_NAME } from "../../constants.js";
|
|
15
16
|
import { asVerb } from "../../kernel/spec/asVerb.js";
|
|
16
17
|
import type { CapabilityModule, VerbSpec } from "../../kernel/spec/types.js";
|
|
17
18
|
import { doctorFormatters } from "./doctor.render.js";
|
|
@@ -24,8 +25,11 @@ const doctorVerb: VerbSpec<Record<string, unknown>, DoctorData> = {
|
|
|
24
25
|
params: [],
|
|
25
26
|
output: { formatters: doctorFormatters },
|
|
26
27
|
examples: [
|
|
27
|
-
{ cmd:
|
|
28
|
-
{
|
|
28
|
+
{ cmd: `${BIN_NAME} doctor` },
|
|
29
|
+
{
|
|
30
|
+
cmd: `${BIN_NAME} doctor --format json`,
|
|
31
|
+
note: "machine-readable checks",
|
|
32
|
+
},
|
|
29
33
|
],
|
|
30
34
|
capability: {
|
|
31
35
|
needsStore: false,
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* neither the checks nor `@canonical/harnesses` land on the fast path.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { BIN_NAME } from "../../constants.js";
|
|
11
12
|
import type { PragmaRuntime } from "../../kernel/runtime/types.js";
|
|
12
13
|
import { checkConfigFile } from "./checks/checkConfigFile.js";
|
|
13
14
|
import { checkKeStore } from "./checks/checkKeStore.js";
|
|
@@ -55,9 +56,9 @@ function buildChecks(
|
|
|
55
56
|
): readonly [string, Promise<CheckResult>][] {
|
|
56
57
|
return [
|
|
57
58
|
["Node version", checkNodeVersion()],
|
|
58
|
-
[
|
|
59
|
-
[
|
|
60
|
-
["
|
|
59
|
+
[`${BIN_NAME} version`, checkPragmaVersion(rt)],
|
|
60
|
+
[`${BIN_NAME} config`, checkConfigFile(rt)],
|
|
61
|
+
["pack refs", checkPackageRefs(rt)],
|
|
61
62
|
["ke store", checkKeStore(rt)],
|
|
62
63
|
["Shell completions", checkShellCompletions(rt.cwd)],
|
|
63
64
|
["MCP configured", checkMcpConfigured(rt.cwd)],
|
|
@@ -84,8 +85,7 @@ export async function runChecks(rt: PragmaRuntime): Promise<DoctorData> {
|
|
|
84
85
|
name,
|
|
85
86
|
status: "fail",
|
|
86
87
|
detail: `Check threw an unexpected error: ${reason}`,
|
|
87
|
-
remedy:
|
|
88
|
-
"Re-run `pragma doctor`; if it persists, report this as a bug.",
|
|
88
|
+
remedy: `Re-run \`${BIN_NAME} doctor\`; if it persists, report this as a bug.`,
|
|
89
89
|
});
|
|
90
90
|
}
|
|
91
91
|
}),
|