@canonical/pragma-cli 0.36.0 → 0.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/bundled-skills/adoption-a1-styles/SKILL.md +24 -0
- package/bundled-skills/adoption-a2-components/SKILL.md +22 -6
- package/bundled-skills/anatomy-author/ANATOMY_DSL_SPEC.md +1397 -327
- package/bundled-skills/anatomy-author/SKILL.md +840 -137
- package/bundled-skills/design-auditor/SKILL.md +133 -9
- package/bundled-skills/implement-component/SKILL.md +192 -23
- package/bundled-skills/specify-component/SKILL.md +124 -12
- package/bundled-skills/specify-pattern/SKILL.md +47 -8
- package/dist/package.json +23 -20
- package/dist/pragma.conf.js +963 -40
- package/dist/pragma.conf.js.map +1 -1
- package/dist/src/capabilities/capabilities/catalog.js +14 -5
- package/dist/src/capabilities/capabilities/catalog.js.map +1 -1
- package/dist/src/capabilities/capabilities/hints.js +33 -5
- package/dist/src/capabilities/capabilities/hints.js.map +1 -1
- package/dist/src/capabilities/config/show.render.js +30 -2
- package/dist/src/capabilities/config/show.render.js.map +1 -1
- package/dist/src/capabilities/create/create.verb.js +2 -1
- package/dist/src/capabilities/create/create.verb.js.map +1 -1
- package/dist/src/capabilities/create/createSurface.generated.js +23 -1
- package/dist/src/capabilities/create/createSurface.generated.js.map +1 -1
- package/dist/src/capabilities/doctor/checks/checkNodeVersion.js +11 -6
- package/dist/src/capabilities/doctor/checks/checkNodeVersion.js.map +1 -1
- package/dist/src/capabilities/doctor/checks/harnessInventory.js +1 -1
- package/dist/src/capabilities/doctor/checks/harnessInventory.js.map +1 -1
- package/dist/src/capabilities/doctor/checks/isSupportedNodeVersion.js +38 -0
- package/dist/src/capabilities/doctor/checks/isSupportedNodeVersion.js.map +1 -0
- package/dist/src/capabilities/doctor/checks/targetHealth.js +104 -14
- package/dist/src/capabilities/doctor/checks/targetHealth.js.map +1 -1
- package/dist/src/capabilities/graph/connect.render.js +101 -0
- package/dist/src/capabilities/graph/connect.render.js.map +1 -0
- package/dist/src/capabilities/graph/connect.types.js +21 -0
- package/dist/src/capabilities/graph/connect.types.js.map +1 -0
- package/dist/src/capabilities/graph/connect.verb.js +147 -0
- package/dist/src/capabilities/graph/connect.verb.js.map +1 -0
- package/dist/src/capabilities/graph/index.js +5 -3
- package/dist/src/capabilities/graph/index.js.map +1 -1
- package/dist/src/capabilities/graph/queryText.js +206 -0
- package/dist/src/capabilities/graph/queryText.js.map +1 -0
- package/dist/src/capabilities/graph/relationIndex.js +302 -0
- package/dist/src/capabilities/graph/relationIndex.js.map +1 -0
- package/dist/src/capabilities/graph/runConnect.js +456 -0
- package/dist/src/capabilities/graph/runConnect.js.map +1 -0
- package/dist/src/capabilities/graph/runQuery.js +21 -4
- package/dist/src/capabilities/graph/runQuery.js.map +1 -1
- package/dist/src/capabilities/setup/buildPlan.js +6 -2
- package/dist/src/capabilities/setup/buildPlan.js.map +1 -1
- package/dist/src/capabilities/setup/operations/index.js +7 -2
- package/dist/src/capabilities/setup/operations/index.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupCompletions.js +6 -2
- package/dist/src/capabilities/setup/operations/setupCompletions.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupConfig.js +4 -2
- package/dist/src/capabilities/setup/operations/setupConfig.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupGenerator.js +72 -14
- package/dist/src/capabilities/setup/operations/setupGenerator.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupLsp.js +372 -62
- package/dist/src/capabilities/setup/operations/setupLsp.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupMcp.js +150 -5
- package/dist/src/capabilities/setup/operations/setupMcp.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupSkills.js +4 -2
- package/dist/src/capabilities/setup/operations/setupSkills.js.map +1 -1
- package/dist/src/capabilities/setup/operations/writability.js +114 -0
- package/dist/src/capabilities/setup/operations/writability.js.map +1 -0
- package/dist/src/capabilities/setup/plan.js.map +1 -1
- package/dist/src/capabilities/setup/setup.verb.js +80 -31
- package/dist/src/capabilities/setup/setup.verb.js.map +1 -1
- package/dist/src/capabilities/setup/targets.js +124 -42
- package/dist/src/capabilities/setup/targets.js.map +1 -1
- package/dist/src/capabilities/shared/index.js +1 -1
- package/dist/src/capabilities/shared/index.js.map +1 -1
- package/dist/src/constants.js +7 -1
- package/dist/src/constants.js.map +1 -1
- package/dist/src/kernel/error/renderError.js +3 -2
- package/dist/src/kernel/error/renderError.js.map +1 -1
- package/dist/src/kernel/error/validOptions.js +41 -0
- package/dist/src/kernel/error/validOptions.js.map +1 -0
- package/dist/src/kernel/packs/compile.js +90 -6
- package/dist/src/kernel/packs/compile.js.map +1 -1
- package/dist/src/kernel/packs/cursor.js +135 -0
- package/dist/src/kernel/packs/cursor.js.map +1 -0
- package/dist/src/kernel/packs/graphql/buildLookupDocument.js +9 -2
- package/dist/src/kernel/packs/graphql/buildLookupDocument.js.map +1 -1
- package/dist/src/kernel/packs/paging.js +108 -0
- package/dist/src/kernel/packs/paging.js.map +1 -0
- package/dist/src/kernel/packs/renderPack.js +154 -5
- package/dist/src/kernel/packs/renderPack.js.map +1 -1
- package/dist/src/kernel/packs/resolveEntity.js +118 -19
- package/dist/src/kernel/packs/resolveEntity.js.map +1 -1
- package/dist/src/kernel/packs/runBodies.js +156 -18
- package/dist/src/kernel/packs/runBodies.js.map +1 -1
- package/dist/src/kernel/packs/schema.js +160 -12
- package/dist/src/kernel/packs/schema.js.map +1 -1
- package/dist/src/kernel/packs/sparql/authorQuery.js +227 -0
- package/dist/src/kernel/packs/sparql/authorQuery.js.map +1 -0
- package/dist/src/kernel/packs/sparql/buildListQuery.js +251 -0
- package/dist/src/kernel/packs/sparql/buildListQuery.js.map +1 -0
- package/dist/src/kernel/packs/sparql/buildLookupQuery.js +125 -22
- package/dist/src/kernel/packs/sparql/buildLookupQuery.js.map +1 -1
- package/dist/src/kernel/packs/sparql/filterValues.js +145 -0
- package/dist/src/kernel/packs/sparql/filterValues.js.map +1 -0
- package/dist/src/kernel/packs/sparql/index.js +24 -14
- package/dist/src/kernel/packs/sparql/index.js.map +1 -1
- package/dist/src/kernel/packs/sparql/runSelect.js +10 -4
- package/dist/src/kernel/packs/sparql/runSelect.js.map +1 -1
- package/dist/src/kernel/packs/sparql/searchTerm.js +25 -0
- package/dist/src/kernel/packs/sparql/searchTerm.js.map +1 -0
- package/dist/src/kernel/packs/storyRules.js +151 -0
- package/dist/src/kernel/packs/storyRules.js.map +1 -0
- package/dist/src/kernel/packs/tierScope.js +310 -0
- package/dist/src/kernel/packs/tierScope.js.map +1 -0
- package/dist/src/kernel/packs/types.js +80 -0
- package/dist/src/kernel/packs/types.js.map +1 -1
- package/dist/src/kernel/project/cli/buildProgram.js +10 -1
- package/dist/src/kernel/project/cli/buildProgram.js.map +1 -1
- package/dist/src/kernel/project/cli/dispatch.js +104 -3
- package/dist/src/kernel/project/cli/dispatch.js.map +1 -1
- package/dist/src/kernel/project/cli/rootHelp.js +8 -5
- package/dist/src/kernel/project/cli/rootHelp.js.map +1 -1
- package/dist/src/kernel/project/cli/suggestNames.js +31 -7
- package/dist/src/kernel/project/cli/suggestNames.js.map +1 -1
- package/dist/src/kernel/project/mcp/registerVerb.js +5 -1
- package/dist/src/kernel/project/mcp/registerVerb.js.map +1 -1
- package/dist/src/kernel/render/renderers.js +34 -2
- package/dist/src/kernel/render/renderers.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/embedded/pack.generated.js +2 -3
- package/dist/src/kernel/runtime/graphpack/embedded/pack.generated.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/embedded/pack.index.generated.js +1 -1
- package/dist/src/kernel/runtime/graphpack/embedded/pack.index.generated.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.js +4 -0
- package/dist/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.js.map +1 -0
- package/dist/src/kernel/runtime/graphpack/embedded.js +13 -6
- package/dist/src/kernel/runtime/graphpack/embedded.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/read.js +17 -10
- package/dist/src/kernel/runtime/graphpack/read.js.map +1 -1
- package/dist/src/kernel/runtime/loadSession.js +3 -1
- package/dist/src/kernel/runtime/loadSession.js.map +1 -1
- package/dist/src/kernel/runtime/probe.js +1 -1
- package/dist/src/kernel/runtime/probe.js.map +1 -1
- package/dist/src/kernel/runtime/readEntity.js +19 -4
- package/dist/src/kernel/runtime/readEntity.js.map +1 -1
- package/dist/src/kernel/runtime/refs/parseRef.js +17 -1
- package/dist/src/kernel/runtime/refs/parseRef.js.map +1 -1
- package/dist/src/kernel/runtime/refs/resolve.js +38 -5
- package/dist/src/kernel/runtime/refs/resolve.js.map +1 -1
- package/dist/src/kernel/spec/emitReference.js +1 -1
- package/dist/src/kernel/spec/emitReference.js.map +1 -1
- package/package.json +23 -20
- package/pragma.conf.ts +983 -40
- package/src/capabilities/block.parity.test.ts +207 -27
- package/src/capabilities/block.shipped.exec.test.ts +156 -14
- package/src/capabilities/block.tierRank.exec.test.ts +46 -9
- package/src/capabilities/capabilities/catalog.ts +14 -5
- package/src/capabilities/capabilities/hints.ts +40 -5
- package/src/capabilities/config/collectConfigShow.test.ts +1 -0
- package/src/capabilities/config/set.test.ts +24 -25
- package/src/capabilities/config/show.render.test.ts +9 -4
- package/src/capabilities/config/show.render.ts +30 -2
- package/src/capabilities/create/byteEquality.test.ts +28 -1
- package/src/capabilities/create/create.test.ts +1 -0
- package/src/capabilities/create/create.verb.ts +2 -1
- package/src/capabilities/create/createGrammar.test.ts +4 -1
- package/src/capabilities/create/createSurface.generated.ts +23 -1
- package/src/capabilities/create/crossCli.subprocess.test.ts +10 -3
- package/src/capabilities/distribution.parity.test.ts +12 -11
- package/src/capabilities/distribution.test.ts +77 -7
- package/src/capabilities/doctor/checks/checkNodeVersion.test.ts +49 -0
- package/src/capabilities/doctor/checks/checkNodeVersion.ts +11 -7
- package/src/capabilities/doctor/checks/harnessInventory.test.ts +13 -6
- package/src/capabilities/doctor/checks/harnessInventory.ts +8 -4
- package/src/capabilities/doctor/checks/isSupportedNodeVersion.test.ts +111 -0
- package/src/capabilities/doctor/checks/isSupportedNodeVersion.ts +34 -0
- package/src/capabilities/doctor/checks/mcpCommand.test.ts +7 -1
- package/src/capabilities/doctor/checks/targetHealth.ts +114 -13
- package/src/capabilities/doctor/doctor.test.ts +327 -36
- package/src/capabilities/emptyRecovery.test.ts +84 -28
- package/src/capabilities/graph/connect.render.ts +141 -0
- package/src/capabilities/graph/connect.shipped.exec.test.ts +370 -0
- package/src/capabilities/graph/connect.test.ts +361 -0
- package/src/capabilities/graph/connect.types.ts +136 -0
- package/src/capabilities/graph/connect.verb.ts +165 -0
- package/src/capabilities/graph/index.ts +5 -3
- package/src/capabilities/graph/query.shipped.exec.test.ts +66 -0
- package/src/capabilities/graph/query.test.ts +17 -0
- package/src/capabilities/graph/queryText.test.ts +121 -0
- package/src/capabilities/graph/queryText.ts +240 -0
- package/src/capabilities/graph/relationIndex.ts +372 -0
- package/src/capabilities/graph/runConnect.ts +577 -0
- package/src/capabilities/graph/runQuery.ts +21 -6
- package/src/capabilities/lazy.test.ts +32 -2
- package/src/capabilities/listBudget.shipped.exec.test.ts +239 -0
- package/src/capabilities/listLookup.shipped.exec.test.ts +387 -0
- package/src/capabilities/listQuery.shipped.exec.test.ts +400 -0
- package/src/capabilities/resources/resources.test.ts +38 -2
- package/src/capabilities/setup/buildPlan.ts +7 -1
- package/src/capabilities/setup/operations/index.ts +14 -0
- package/src/capabilities/setup/operations/setupCompletions.ts +9 -2
- package/src/capabilities/setup/operations/setupConfig.ts +7 -2
- package/src/capabilities/setup/operations/setupGenerator.ts +93 -15
- package/src/capabilities/setup/operations/setupLsp.ts +544 -85
- package/src/capabilities/setup/operations/setupMcp.ts +212 -4
- package/src/capabilities/setup/operations/setupSkills.ts +7 -2
- package/src/capabilities/setup/operations/writability.test.ts +151 -0
- package/src/capabilities/setup/operations/writability.ts +135 -0
- package/src/capabilities/setup/plan.ts +11 -0
- package/src/capabilities/setup/setup.test.ts +992 -16
- package/src/capabilities/setup/setup.verb.ts +84 -36
- package/src/capabilities/setup/targets.ts +152 -48
- package/src/capabilities/setup/types.ts +41 -5
- package/src/capabilities/shared/index.ts +1 -0
- package/src/capabilities/sources/sources.test.ts +55 -0
- package/src/capabilities/surface.test.ts +59 -11
- package/src/capabilities/tier.parity.test.ts +2 -2
- package/src/capabilities/token.parity.test.ts +784 -0
- package/src/capabilities/tokenGraph.cli.test.ts +279 -0
- package/src/capabilities/tokenGraph.shipped.exec.test.ts +653 -0
- package/src/capabilities/variable.parity.test.ts +448 -0
- package/src/constants.ts +8 -0
- package/src/kernel/completion/shellDrive.test.ts +32 -30
- package/src/kernel/config/defaults.test.ts +21 -2
- package/src/kernel/config/readConfig.test.ts +1 -1
- package/src/kernel/error/renderError.ts +3 -2
- package/src/kernel/error/validOptions.test.ts +57 -0
- package/src/kernel/error/validOptions.ts +45 -0
- package/src/kernel/packs/collect.test.ts +2 -0
- package/src/kernel/packs/compile.test.ts +188 -15
- package/src/kernel/packs/compile.ts +109 -14
- package/src/kernel/packs/cursor.ts +135 -0
- package/src/kernel/packs/filteredEmpty.test.ts +213 -0
- package/src/kernel/packs/graphql/buildLookupDocument.ts +16 -9
- package/src/kernel/packs/graphqlSource.test.ts +152 -0
- package/src/kernel/packs/listNotice.test.ts +156 -0
- package/src/kernel/packs/paging.ts +110 -0
- package/src/kernel/packs/renderPack.ts +186 -13
- package/src/kernel/packs/resolveEntity.test.ts +62 -3
- package/src/kernel/packs/resolveEntity.ts +186 -26
- package/src/kernel/packs/runBodies.ts +196 -26
- package/src/kernel/packs/schema.ts +180 -13
- package/src/kernel/packs/sparql/authorQuery.test.ts +179 -0
- package/src/kernel/packs/sparql/authorQuery.ts +257 -0
- package/src/kernel/packs/sparql/buildListQuery.test.ts +344 -0
- package/src/kernel/packs/sparql/buildListQuery.ts +344 -0
- package/src/kernel/packs/sparql/buildLookupQuery.test.ts +138 -0
- package/src/kernel/packs/sparql/buildLookupQuery.ts +140 -20
- package/src/kernel/packs/sparql/filterValues.test.ts +86 -0
- package/src/kernel/packs/sparql/filterValues.ts +182 -0
- package/src/kernel/packs/sparql/index.ts +30 -14
- package/src/kernel/packs/sparql/runSelect.test.ts +63 -0
- package/src/kernel/packs/sparql/runSelect.ts +42 -2
- package/src/kernel/packs/sparql/searchTerm.ts +29 -0
- package/src/kernel/packs/storyRules.ts +180 -0
- package/src/kernel/packs/tierScope.test.ts +487 -0
- package/src/kernel/packs/tierScope.ts +409 -0
- package/src/kernel/packs/toolDescription.test.ts +13 -10
- package/src/kernel/packs/types.ts +311 -9
- package/src/kernel/project/cli/buildProgram.ts +17 -1
- package/src/kernel/project/cli/dispatch.test.ts +87 -0
- package/src/kernel/project/cli/dispatch.ts +114 -4
- package/src/kernel/project/cli/rootHelp.ts +8 -5
- package/src/kernel/project/cli/suggest.test.ts +35 -0
- package/src/kernel/project/cli/suggestNames.ts +34 -7
- package/src/kernel/project/mcp/registerVerb.ts +5 -1
- package/src/kernel/render/contracts.ts +2 -0
- package/src/kernel/render/prefixes.test.ts +2 -0
- package/src/kernel/render/renderers.test.ts +43 -0
- package/src/kernel/render/renderers.ts +34 -1
- package/src/kernel/runtime/graphpack/embedded/pack.generated.ts +2 -3
- package/src/kernel/runtime/graphpack/embedded/pack.index.generated.ts +1 -1
- package/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.ts +3 -0
- package/src/kernel/runtime/graphpack/embedded.ts +14 -6
- package/src/kernel/runtime/graphpack/graphpack.test.ts +31 -5
- package/src/kernel/runtime/graphpack/read.ts +17 -13
- package/src/kernel/runtime/loadSession.ts +3 -1
- package/src/kernel/runtime/probe.ts +1 -1
- package/src/kernel/runtime/readEntity.ts +19 -4
- package/src/kernel/runtime/refs/parseRef.test.ts +49 -1
- package/src/kernel/runtime/refs/parseRef.ts +25 -1
- package/src/kernel/runtime/refs/resolve.ts +39 -5
- package/src/kernel/runtime/types.ts +30 -0
- package/src/kernel/spec/emitReference.ts +1 -1
- package/src/kernel/spec/surfaceConformance.test.ts +2 -2
- package/src/kernel/spec/types.ts +17 -0
- package/src/testing/PARITY_GAPS.ts +4 -4
- package/src/testing/behavioral/agentSession.mcp.test.ts +43 -39
- package/src/testing/behavioral/completion.test.ts +7 -6
- package/src/testing/behavioral/disclosure.test.ts +39 -37
- package/src/testing/behavioral/errorMatrix.mcp.test.ts +82 -64
- package/src/testing/behavioral/errorMatrix.storeless.test.ts +11 -10
- package/src/testing/behavioral/journeys.cli.test.ts +71 -32
- package/src/testing/behavioral/journeys.defaultPack.test.ts +6 -4
- package/src/testing/behavioral/journeys.firstInstall.test.ts +70 -5
- package/src/testing/behavioral/journeys.mcpLifecycle.mcp.test.ts +4 -3
- package/src/testing/behavioral/journeys.packageStories.test.ts +2 -1
- package/src/testing/behavioral/mcpSurface.test.ts +28 -25
- package/src/testing/behavioral/parity.test.ts +25 -23
- package/src/testing/eval/__snapshots__/eval.test.ts.snap +20 -0
- package/src/testing/eval/cases/effectful.ts +7 -1
- package/src/testing/eval/cases/readNouns.ts +100 -11
- package/src/testing/fixtures/blockGraph.ts +89 -5
- package/src/testing/fixtures/connectGraph.ts +159 -0
- package/src/testing/fixtures/graph/canonical.ts +128 -32
- package/src/testing/helpers/fsProbe.ts +88 -0
- package/src/testing/helpers/runCli.test.ts +44 -0
- package/src/testing/helpers/runCli.ts +27 -3
- package/src/testing/perf/budgets.test.ts +3 -1
- package/src/testing/perf/budgets.ts +138 -54
- package/dist/src/kernel/packs/sparql/applyFilters.js +0 -167
- package/dist/src/kernel/packs/sparql/applyFilters.js.map +0 -1
- package/dist/src/kernel/packs/sparql/applySearch.js +0 -25
- package/dist/src/kernel/packs/sparql/applySearch.js.map +0 -1
- package/src/kernel/packs/sparql/applyFilters.ts +0 -203
- package/src/kernel/packs/sparql/applySearch.ts +0 -33
|
@@ -33,7 +33,10 @@ import {
|
|
|
33
33
|
CANONICAL_TTL,
|
|
34
34
|
} from "../../testing/fixtures/graph/canonical.js";
|
|
35
35
|
import { bootFixtureRuntime } from "../../testing/helpers/fixtureGraph.js";
|
|
36
|
+
import { storeProbe } from "../../testing/helpers/fsProbe.js";
|
|
36
37
|
import { projectMcp } from "../../testing/helpers/projectMcp.js";
|
|
38
|
+
import type { FsProbe } from "../setup/operations/writability.js";
|
|
39
|
+
import { checkPackageRefs } from "./checks/checkPackageRefs.js";
|
|
37
40
|
import { scopedChecks } from "./checks/targetHealth.js";
|
|
38
41
|
import { doctorModule } from "./index.js";
|
|
39
42
|
import { runChecks } from "./runChecks.js";
|
|
@@ -119,7 +122,15 @@ const byName = (data: DoctorData, name: string) =>
|
|
|
119
122
|
|
|
120
123
|
describe("doctor — shape & spread", () => {
|
|
121
124
|
it("returns the environment checks plus one row per scoped target", async () => {
|
|
122
|
-
|
|
125
|
+
// A fixture pack, not the distribution's own: the shape under test is the
|
|
126
|
+
// row table, and the store check booting the embedded snapshot cold costs
|
|
127
|
+
// three seconds on an idle machine (65,000 triples) and more than the
|
|
128
|
+
// five-second budget when every package's suite runs at once, which is how
|
|
129
|
+
// the release workflow runs them. What the embedded snapshot proves — that
|
|
130
|
+
// a first install's pack refs pass naming it — has its own case below,
|
|
131
|
+
// through the check that never boots the store.
|
|
132
|
+
const fixture = await bootFixtureRuntime({ ttl: CANONICAL_TTL });
|
|
133
|
+
const data = await runChecks(fixture.runtime);
|
|
123
134
|
// Four unscoped environment checks, then the target table in both scopes
|
|
124
135
|
// (all five targets are global, mcp and skills are also project) = 11,
|
|
125
136
|
// plus the two `harnesses` inventory rows, one per scope = 13.
|
|
@@ -130,24 +141,6 @@ describe("doctor — shape & spread", () => {
|
|
|
130
141
|
}
|
|
131
142
|
// Deterministic under the isolated env.
|
|
132
143
|
expect(byName(data, "Node version")?.status).toBe("pass");
|
|
133
|
-
// Nothing built, but the packs are the distribution's own — so the embedded
|
|
134
|
-
// snapshot answers reads and the check passes, naming what it is reading.
|
|
135
|
-
const pkgRefs = byName(data, "pack refs");
|
|
136
|
-
expect(pkgRefs?.status).toBe("pass");
|
|
137
|
-
expect(pkgRefs?.detail).toContain("shipped with the CLI");
|
|
138
|
-
expect(pkgRefs?.remedy).toBeUndefined();
|
|
139
|
-
// Provenance is one ITEM PER PACK, not a comma-joined string in the
|
|
140
|
-
// headline: four packs and two forty-character SHAs on one line was
|
|
141
|
-
// unreadable, and every other multi-part check here already uses items.
|
|
142
|
-
// The headline counts them; the items say which revision each one is.
|
|
143
|
-
expect(pkgRefs?.detail).toContain("packs");
|
|
144
|
-
expect((pkgRefs?.items?.length ?? 0) > 1).toBe(true);
|
|
145
|
-
for (const item of pkgRefs?.items ?? []) {
|
|
146
|
-
expect(item.label).not.toContain(",");
|
|
147
|
-
// A git hash is cut to seven — the length every other tool in this
|
|
148
|
-
// workflow prints, and short enough that four rows stay scannable.
|
|
149
|
-
expect(item.detail ?? "").not.toMatch(/[0-9a-f]{40}/);
|
|
150
|
-
}
|
|
151
144
|
|
|
152
145
|
// The scoped rows carry the TARGET IDS verbatim — `mcp`, not "MCP
|
|
153
146
|
// configured" — because the row name IS the fix command's argument. The
|
|
@@ -188,10 +181,37 @@ describe("doctor — shape & spread", () => {
|
|
|
188
181
|
// No global config in the isolated XDG — an opt-in that is not set up yet.
|
|
189
182
|
expect(byName(data, "config")?.status).toBe("available");
|
|
190
183
|
expect(byName(data, "config")?.remedy).toBe("pragma setup config");
|
|
184
|
+
await fixture.dispose();
|
|
191
185
|
});
|
|
192
186
|
});
|
|
193
187
|
|
|
194
188
|
describe("doctor — the pack-refs check", () => {
|
|
189
|
+
it("a first install reads the distribution's own packs, and says so", async () => {
|
|
190
|
+
// Nothing built, but the packs are the distribution's own — so the embedded
|
|
191
|
+
// snapshot answers reads and the check passes, naming what it is reading.
|
|
192
|
+
// The check alone, not the whole doctor: pack refs never boot the store, and
|
|
193
|
+
// booting the embedded snapshot cold is the one expensive thing a doctor
|
|
194
|
+
// run does.
|
|
195
|
+
const pkgRefs = await checkPackageRefs(
|
|
196
|
+
bootRuntime(FLAGS, tmp("pragma-proj-")),
|
|
197
|
+
);
|
|
198
|
+
expect(pkgRefs.status).toBe("pass");
|
|
199
|
+
expect(pkgRefs.detail).toContain("shipped with the CLI");
|
|
200
|
+
expect(pkgRefs.remedy).toBeUndefined();
|
|
201
|
+
// Provenance is one ITEM PER PACK, not a comma-joined string in the
|
|
202
|
+
// headline: four packs and two forty-character SHAs on one line was
|
|
203
|
+
// unreadable, and every other multi-part check here already uses items.
|
|
204
|
+
// The headline counts them; the items say which revision each one is.
|
|
205
|
+
expect(pkgRefs.detail).toContain("packs");
|
|
206
|
+
expect((pkgRefs.items?.length ?? 0) > 1).toBe(true);
|
|
207
|
+
for (const item of pkgRefs.items ?? []) {
|
|
208
|
+
expect(item.label).not.toContain(",");
|
|
209
|
+
// A git hash is cut to seven — the length every other tool in this
|
|
210
|
+
// workflow prints, and short enough that four rows stay scannable.
|
|
211
|
+
expect(item.detail ?? "").not.toMatch(/[0-9a-f]{40}/);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
195
215
|
it("a project with its OWN packs and nothing built is an attributable fail", async () => {
|
|
196
216
|
// The install that must never read healthy: `origins.packs` is "project",
|
|
197
217
|
// so the embedded snapshot is a DIFFERENT graph and every read throws
|
|
@@ -249,17 +269,20 @@ describe("doctor — the store check", () => {
|
|
|
249
269
|
|
|
250
270
|
describe("doctor — dispatch & MCP", () => {
|
|
251
271
|
it("exits 0 even when checks fail (failures live in the envelope)", async () => {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
bootRuntime(FLAGS, tmp("pragma-proj-")),
|
|
257
|
-
);
|
|
272
|
+
// On the fixture pack, for the same reason the shape case is: a whole doctor
|
|
273
|
+
// run boots the store, and the embedded snapshot is the expensive one.
|
|
274
|
+
const fixture = await bootFixtureRuntime({ ttl: CANONICAL_TTL });
|
|
275
|
+
const outcome = await executeVerb(doctorVerb, {}, NO_MUT, fixture.runtime);
|
|
258
276
|
expect(outcome.exitCode).toBe(0);
|
|
277
|
+
await fixture.dispose();
|
|
259
278
|
});
|
|
260
279
|
|
|
261
280
|
it("MCP doctor is read-only and returns the checks envelope", async () => {
|
|
262
|
-
|
|
281
|
+
// The MCP boots its own runtime from the cwd; the fixture's cwd carries a
|
|
282
|
+
// config that points at the built fixture pack, so the embedded snapshot is
|
|
283
|
+
// never parsed here either.
|
|
284
|
+
const fixture = await bootFixtureRuntime({ ttl: CANONICAL_TTL });
|
|
285
|
+
const mcp = await projectMcp([doctorModule], fixture.cwd);
|
|
263
286
|
const tools = await mcp.listTools();
|
|
264
287
|
const doctorTool = tools.find((t) => t.name === "doctor");
|
|
265
288
|
expect(
|
|
@@ -271,6 +294,7 @@ describe("doctor — dispatch & MCP", () => {
|
|
|
271
294
|
await mcp.cleanup();
|
|
272
295
|
expect(envelope.ok).toBe(true);
|
|
273
296
|
expect((envelope.data as DoctorData).checks).toHaveLength(13);
|
|
297
|
+
await fixture.dispose();
|
|
274
298
|
});
|
|
275
299
|
});
|
|
276
300
|
|
|
@@ -334,10 +358,45 @@ describe("doctor — the harness inventory", () => {
|
|
|
334
358
|
expect(project?.items?.[0]?.detail).toContain("detected, not registered");
|
|
335
359
|
expect(project?.items?.[0]?.detail).toContain(".vscode/mcp.json");
|
|
336
360
|
|
|
337
|
-
//
|
|
361
|
+
// The GLOBAL scope holds nothing. VS Code is dual-scope now, so the row
|
|
362
|
+
// CAN write a per-user file — but a committed `.vscode/` is evidence about
|
|
363
|
+
// this repository, not about this machine, and the global band has to be
|
|
364
|
+
// earned by a user-level signal. Without one there is no global location
|
|
365
|
+
// to report and nothing for `setup mcp` to do there.
|
|
338
366
|
expect(global?.status).toBe("skip");
|
|
339
|
-
expect(global?.
|
|
340
|
-
|
|
367
|
+
expect(global?.items ?? []).toEqual([]);
|
|
368
|
+
const globalMcp = rows.find(
|
|
369
|
+
(r) => r.name === "mcp" && r.scope === "global",
|
|
370
|
+
);
|
|
371
|
+
expect(globalMcp?.status).toBe("skip");
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it("holds the global hit once a USER-LEVEL signal has earned it", async () => {
|
|
375
|
+
// The same repository, on a machine that really has VS Code: its per-user
|
|
376
|
+
// directory exists, so the global band is earned and the row reports the
|
|
377
|
+
// per-user `mcp.json` it would write — detected, not registered — with the
|
|
378
|
+
// `mcp` row beside it naming the command that settles it.
|
|
379
|
+
const cwd = tmp("pragma-doctor-proj-");
|
|
380
|
+
mkdirSync(join(cwd, ".vscode"), { recursive: true });
|
|
381
|
+
mkdirSync(join(process.env.XDG_CONFIG_HOME as string, "Code", "User"), {
|
|
382
|
+
recursive: true,
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
const rows = await scopedChecks(bootRuntime(FLAGS, cwd), "pragma");
|
|
386
|
+
const { global } = inventory(rows);
|
|
387
|
+
|
|
388
|
+
expect(global?.status).toBe("pass");
|
|
389
|
+
expect(global?.detail).toBe("1 detected · 0 registered");
|
|
390
|
+
expect(global?.items?.map((i) => i.label)).toEqual(["VS Code"]);
|
|
391
|
+
expect(global?.items?.[0]?.status).toBe("available");
|
|
392
|
+
expect(global?.items?.[0]?.detail).toContain("detected, not registered");
|
|
393
|
+
expect(global?.items?.[0]?.detail).toContain("Code/User/mcp.json");
|
|
394
|
+
|
|
395
|
+
const globalMcp = rows.find(
|
|
396
|
+
(r) => r.name === "mcp" && r.scope === "global",
|
|
397
|
+
);
|
|
398
|
+
expect(globalMcp?.status).toBe("available");
|
|
399
|
+
expect(globalMcp?.remedy).toBe("pragma setup mcp");
|
|
341
400
|
});
|
|
342
401
|
|
|
343
402
|
it("--verbose lists every registry harness, undetected ones as a NON-failing skip", async () => {
|
|
@@ -348,9 +407,9 @@ describe("doctor — the harness inventory", () => {
|
|
|
348
407
|
const { global, project } = inventory(rows);
|
|
349
408
|
|
|
350
409
|
// Every harness the registry knows, in both scopes.
|
|
351
|
-
expect(project?.items).toHaveLength(
|
|
352
|
-
expect(global?.items).toHaveLength(
|
|
353
|
-
expect(project?.detail).toBe("1 detected · 0 registered ·
|
|
410
|
+
expect(project?.items).toHaveLength(16);
|
|
411
|
+
expect(global?.items).toHaveLength(16);
|
|
412
|
+
expect(project?.detail).toBe("1 detected · 0 registered · 16 known");
|
|
354
413
|
|
|
355
414
|
// `types.ts` forbids inflating the failure count: a machine that simply
|
|
356
415
|
// does not have Cursor is not a broken machine.
|
|
@@ -368,11 +427,15 @@ describe("doctor — the harness inventory", () => {
|
|
|
368
427
|
});
|
|
369
428
|
|
|
370
429
|
it("says where a harness DOES keep its config, rather than omitting it", async () => {
|
|
371
|
-
// `
|
|
430
|
+
// `roo-code` is `scope: "project"`, so it can NEVER carry a global entry. A
|
|
372
431
|
// verbose global listing that silently dropped it would read as a bug in
|
|
373
432
|
// the listing; the row says why instead. The mirror case is Windsurf, which
|
|
374
433
|
// is `scope: "global"` and keeps nothing per project.
|
|
375
434
|
//
|
|
435
|
+
// VS Code used to be this example and stopped being one when its per-user
|
|
436
|
+
// `mcp.json` was added: it is `scope: "both"` now, and asserting otherwise
|
|
437
|
+
// would pin a fact the registry no longer holds.
|
|
438
|
+
//
|
|
376
439
|
// The sentence states the FACT, not the partition: `no Global band` named
|
|
377
440
|
// an internal word for the answer instead of giving it.
|
|
378
441
|
const rows = await scopedChecks(
|
|
@@ -381,11 +444,11 @@ describe("doctor — the harness inventory", () => {
|
|
|
381
444
|
);
|
|
382
445
|
const { global, project } = inventory(rows);
|
|
383
446
|
|
|
384
|
-
const
|
|
385
|
-
expect(
|
|
447
|
+
const rooGlobal = global?.items?.find((i) => i.label === "Roo Code");
|
|
448
|
+
expect(rooGlobal?.detail).toBe(
|
|
386
449
|
"keeps no global config — it is per-project only",
|
|
387
450
|
);
|
|
388
|
-
expect(
|
|
451
|
+
expect(rooGlobal?.status).toBe("skip");
|
|
389
452
|
|
|
390
453
|
const windsurfProject = project?.items?.find((i) => i.label === "Windsurf");
|
|
391
454
|
expect(windsurfProject?.detail).toBe(
|
|
@@ -537,3 +600,231 @@ describe("doctor — an unconfigured opt-in integration is available, not a faul
|
|
|
537
600
|
expect(check?.remedy).toBe("pragma setup mcp");
|
|
538
601
|
});
|
|
539
602
|
});
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* The `lsp` row's per-editor provenance, and the rows that say a step was
|
|
606
|
+
* skipped rather than failing at it.
|
|
607
|
+
*
|
|
608
|
+
* Per the owner, 2026-09-16: doctor says per editor HOW it was found and WHY a
|
|
609
|
+
* step was skipped. A colleague whose macOS run reported no editor had VS Code
|
|
610
|
+
* installed the whole time, and a one-line row could not tell them whether
|
|
611
|
+
* pragma had missed the editor or found it and could not act.
|
|
612
|
+
*
|
|
613
|
+
* The blocked arms read a filesystem no CI host has, so they are driven over
|
|
614
|
+
* `scopedChecks`'s own probe seam — the same entry point `doctor` runs, with
|
|
615
|
+
* the filesystem answer injected, rather than the row bodies opened up as
|
|
616
|
+
* exports nothing in production would call.
|
|
617
|
+
*/
|
|
618
|
+
describe("doctor — the lsp row's per-editor provenance", () => {
|
|
619
|
+
/** The `lsp` row of a report, optionally over an injected filesystem. */
|
|
620
|
+
const lspRow = async (probe?: FsProbe) => {
|
|
621
|
+
const rows = await scopedChecks(
|
|
622
|
+
bootRuntime(FLAGS, tmp("pragma-doctor-proj-")),
|
|
623
|
+
"pragma",
|
|
624
|
+
probe,
|
|
625
|
+
);
|
|
626
|
+
return rows.find((r) => r.name === "lsp");
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
it("names one item per editor, with `via PATH` and its standing", async () => {
|
|
630
|
+
const stubDir = tmp("pragma-doctor-editors-");
|
|
631
|
+
writeFileSync(join(stubDir, "codium"), "");
|
|
632
|
+
process.env.PATH = stubDir;
|
|
633
|
+
|
|
634
|
+
const lsp = await lspRow();
|
|
635
|
+
// Nothing installed yet, so the row is the optional integration it is —
|
|
636
|
+
// and its derived fix keeps the "every available row has a remedy" rule.
|
|
637
|
+
expect(lsp?.status).toBe("available");
|
|
638
|
+
expect(lsp?.remedy).toBe("pragma setup lsp");
|
|
639
|
+
expect(lsp?.items?.map((i) => i.label)).toEqual(["VSCodium"]);
|
|
640
|
+
expect(lsp?.items?.[0]?.status).toBe("available");
|
|
641
|
+
expect(lsp?.items?.[0]?.detail).toContain("via PATH");
|
|
642
|
+
expect(lsp?.items?.[0]?.detail).toContain(join(stubDir, "codium"));
|
|
643
|
+
expect(lsp?.items?.[0]?.detail).toContain("not installed");
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
it("reports an editor found only by its user directory as a skipped item", async () => {
|
|
647
|
+
// Installed, and nothing to install WITH. The item says both.
|
|
648
|
+
mkdirSync(join(process.env.XDG_CONFIG_HOME as string, "Code", "User"), {
|
|
649
|
+
recursive: true,
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
const lsp = await lspRow();
|
|
653
|
+
expect(lsp?.status).toBe("skip");
|
|
654
|
+
expect(lsp?.detail).toContain("no command-line launcher");
|
|
655
|
+
expect(lsp?.items?.[0]?.label).toBe("VS Code");
|
|
656
|
+
expect(lsp?.items?.[0]?.status).toBe("skip");
|
|
657
|
+
expect(lsp?.items?.[0]?.detail).toContain("via user directory");
|
|
658
|
+
// The remedy is the editor's own palette command — NOT the derived
|
|
659
|
+
// `pragma setup lsp`, which would only reproduce the skip.
|
|
660
|
+
expect(lsp?.remedy).toContain("Install 'code' command in PATH");
|
|
661
|
+
expect(lsp?.remedy).not.toContain("\n");
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
it("a store-managed extensions folder is a skip carrying the Nix declaration", async () => {
|
|
665
|
+
const stubDir = tmp("pragma-doctor-nix-path-");
|
|
666
|
+
writeFileSync(join(stubDir, "codium"), "");
|
|
667
|
+
process.env.PATH = stubDir;
|
|
668
|
+
const extensions = join(
|
|
669
|
+
process.env.HOME as string,
|
|
670
|
+
".vscode-oss",
|
|
671
|
+
"extensions",
|
|
672
|
+
);
|
|
673
|
+
mkdirSync(extensions, { recursive: true });
|
|
674
|
+
|
|
675
|
+
const lsp = await lspRow(storeProbe(extensions, "9z8y7x-vscodium"));
|
|
676
|
+
expect(lsp?.status).toBe("skip");
|
|
677
|
+
expect(lsp?.detail).toContain("managed by Nix");
|
|
678
|
+
expect(lsp?.items?.[0]?.status).toBe("skip");
|
|
679
|
+
expect(lsp?.items?.[0]?.detail).toContain("via PATH");
|
|
680
|
+
expect(lsp?.items?.[0]?.detail).toContain("managed by Nix");
|
|
681
|
+
// There is no marketplace listing, so the line gives both halves a
|
|
682
|
+
// home-manager user needs: where the VSIX comes from, and how to declare it.
|
|
683
|
+
expect(lsp?.remedy).toContain("buildVscodeExtension");
|
|
684
|
+
expect(lsp?.remedy).toContain("terrazzo-lsp.vsix");
|
|
685
|
+
expect(lsp?.remedy).not.toContain("\n");
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
it("a blocked editor beside an installable one keeps the row available", async () => {
|
|
689
|
+
// The invariant the row must not break: any editor that CAN be installed
|
|
690
|
+
// into makes the row `available`, whose derived fix is the setup command.
|
|
691
|
+
// A blocked sibling is reported as its own skipped item, not as the row's
|
|
692
|
+
// verdict.
|
|
693
|
+
const stubDir = tmp("pragma-doctor-mixed-path-");
|
|
694
|
+
writeFileSync(join(stubDir, "code"), "");
|
|
695
|
+
writeFileSync(join(stubDir, "codium"), "");
|
|
696
|
+
process.env.PATH = stubDir;
|
|
697
|
+
const extensions = join(
|
|
698
|
+
process.env.HOME as string,
|
|
699
|
+
".vscode-oss",
|
|
700
|
+
"extensions",
|
|
701
|
+
);
|
|
702
|
+
mkdirSync(extensions, { recursive: true });
|
|
703
|
+
|
|
704
|
+
const lsp = await lspRow(storeProbe(extensions, "9z8y7x-vscodium"));
|
|
705
|
+
expect(lsp?.status).toBe("available");
|
|
706
|
+
expect(lsp?.detail).toBe("not installed in VS Code");
|
|
707
|
+
expect(lsp?.items?.map((i) => [i.label, i.status])).toEqual([
|
|
708
|
+
["VS Code", "available"],
|
|
709
|
+
["VSCodium", "skip"],
|
|
710
|
+
]);
|
|
711
|
+
// The derived fix stands, because there is still something to install.
|
|
712
|
+
expect(lsp?.remedy).toBe("pragma setup lsp");
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
it("reports an installed editor as a pass, provenance included", async () => {
|
|
716
|
+
const stubDir = tmp("pragma-doctor-installed-path-");
|
|
717
|
+
writeFileSync(join(stubDir, "codium"), "");
|
|
718
|
+
process.env.PATH = stubDir;
|
|
719
|
+
mkdirSync(
|
|
720
|
+
join(
|
|
721
|
+
process.env.HOME as string,
|
|
722
|
+
".vscode-oss",
|
|
723
|
+
"extensions",
|
|
724
|
+
"canonical.terrazzo-lsp-extension-1.2.3",
|
|
725
|
+
),
|
|
726
|
+
{ recursive: true },
|
|
727
|
+
);
|
|
728
|
+
|
|
729
|
+
const lsp = await lspRow();
|
|
730
|
+
expect(lsp?.status).toBe("pass");
|
|
731
|
+
expect(lsp?.detail).toBe("installed in VSCodium");
|
|
732
|
+
expect(lsp?.items?.[0]?.status).toBe("pass");
|
|
733
|
+
expect(lsp?.items?.[0]?.detail).toContain("via PATH");
|
|
734
|
+
expect(lsp?.items?.[0]?.detail).toContain("· installed");
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
it("an INSTALLED editor in a blocked folder is a pass, not a skip", async () => {
|
|
738
|
+
// A site-managed (or Nix-managed) extensions folder that ALREADY holds the
|
|
739
|
+
// extension is a working machine. Reporting a skip there told its owner to
|
|
740
|
+
// chmod the folder and install an extension they already have, and made
|
|
741
|
+
// the row a permanent `○ lsp` nothing could clear.
|
|
742
|
+
const stubDir = tmp("pragma-doctor-blocked-installed-");
|
|
743
|
+
writeFileSync(join(stubDir, "codium"), "");
|
|
744
|
+
process.env.PATH = stubDir;
|
|
745
|
+
const extensions = join(
|
|
746
|
+
process.env.HOME as string,
|
|
747
|
+
".vscode-oss",
|
|
748
|
+
"extensions",
|
|
749
|
+
);
|
|
750
|
+
mkdirSync(join(extensions, "canonical.terrazzo-lsp-extension-1.2.3"), {
|
|
751
|
+
recursive: true,
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
const lsp = await lspRow(storeProbe(extensions, "9z8y7x-vscodium"));
|
|
755
|
+
expect(lsp?.status).toBe("pass");
|
|
756
|
+
expect(lsp?.detail).toBe("installed in VSCodium");
|
|
757
|
+
expect(lsp?.items?.[0]?.status).toBe("pass");
|
|
758
|
+
expect(lsp?.items?.[0]?.detail).toContain("· installed");
|
|
759
|
+
expect(lsp?.remedy).toBeUndefined();
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
it("no editor anywhere is a skip naming every place that was looked", async () => {
|
|
763
|
+
process.env.PATH = tmp("pragma-doctor-empty-path-");
|
|
764
|
+
const lsp = await lspRow();
|
|
765
|
+
expect(lsp?.status).toBe("skip");
|
|
766
|
+
expect(lsp?.detail).toContain("no VS Code-family editor found");
|
|
767
|
+
expect(lsp?.detail).toContain("under /Applications or ~/Applications");
|
|
768
|
+
expect(lsp?.detail).toContain("by its user directory");
|
|
769
|
+
expect(lsp?.remedy).toContain("no action is possible on this machine");
|
|
770
|
+
});
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* The `mcp` row over a location pragma must not write. It used to be an
|
|
775
|
+
* `available` row whose `fix:` would have failed the moment it ran.
|
|
776
|
+
*/
|
|
777
|
+
describe("doctor — an unwritable MCP location", () => {
|
|
778
|
+
it("reports a store-managed file as a skip whose remedy IS the entry", async () => {
|
|
779
|
+
const cwd = tmp("pragma-doctor-proj-");
|
|
780
|
+
mkdirSync(join(cwd, ".vscode"), { recursive: true });
|
|
781
|
+
|
|
782
|
+
const rows = await scopedChecks(
|
|
783
|
+
bootRuntime(FLAGS, cwd),
|
|
784
|
+
"pragma",
|
|
785
|
+
storeProbe(cwd, "4b5c6d-project"),
|
|
786
|
+
);
|
|
787
|
+
const mcp = rows.find((r) => r.name === "mcp" && r.scope === "project");
|
|
788
|
+
|
|
789
|
+
expect(mcp?.status).toBe("skip");
|
|
790
|
+
expect(mcp?.detail).toContain("managed by Nix");
|
|
791
|
+
expect(mcp?.items?.every((i) => i.status === "skip")).toBe(true);
|
|
792
|
+
// The exact entry a write would have emitted, so what the user declares
|
|
793
|
+
// classifies as registered next time rather than as drift.
|
|
794
|
+
expect(mcp?.remedy).toContain('"servers"');
|
|
795
|
+
expect(mcp?.remedy).toContain('"pragma"');
|
|
796
|
+
expect(mcp?.remedy).not.toContain("\n");
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
it("counts only the WRITABLE files in the row's denominator", async () => {
|
|
800
|
+
// Two locations, one of them managed: "registered in 1 of 1" is the honest
|
|
801
|
+
// headline, not "1 of 2" over a file this command will never write.
|
|
802
|
+
const cwd = tmp("pragma-doctor-proj-");
|
|
803
|
+
// A `pragma` on PATH, so the registered entry reads `pass` rather than
|
|
804
|
+
// "registered, but `pragma` is not on PATH" — this case is about the
|
|
805
|
+
// DENOMINATOR, and a failing sibling would decide the row instead.
|
|
806
|
+
const binDir = tmp("pragma-doctor-bin-");
|
|
807
|
+
writeFileSync(join(binDir, "pragma"), "");
|
|
808
|
+
process.env.PATH = binDir;
|
|
809
|
+
mkdirSync(join(cwd, ".vscode"), { recursive: true });
|
|
810
|
+
mkdirSync(join(cwd, ".roo"), { recursive: true });
|
|
811
|
+
writeFileSync(
|
|
812
|
+
join(cwd, ".roo", "mcp.json"),
|
|
813
|
+
JSON.stringify({
|
|
814
|
+
mcpServers: {
|
|
815
|
+
pragma: { command: "pragma", args: ["mcp", "serve"], cwd },
|
|
816
|
+
},
|
|
817
|
+
}),
|
|
818
|
+
);
|
|
819
|
+
|
|
820
|
+
const rows = await scopedChecks(
|
|
821
|
+
bootRuntime(FLAGS, cwd),
|
|
822
|
+
"pragma",
|
|
823
|
+
storeProbe(join(cwd, ".vscode"), "7e8f9a-vscode"),
|
|
824
|
+
);
|
|
825
|
+
const mcp = rows.find((r) => r.name === "mcp" && r.scope === "project");
|
|
826
|
+
|
|
827
|
+
expect(mcp?.detail).toBe("registered in 1 of 1 config files");
|
|
828
|
+
expect(mcp?.items?.filter((i) => i.status === "skip")).toHaveLength(1);
|
|
829
|
+
});
|
|
830
|
+
});
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
17
17
|
import { compilePack } from "../kernel/packs/compile.js";
|
|
18
|
+
import { DEFAULT_LIST_LIMIT } from "../kernel/packs/paging.js";
|
|
18
19
|
import type { PackDefinition } from "../kernel/packs/types.js";
|
|
19
20
|
import { distributionSource } from "../kernel/packs/types.js";
|
|
20
21
|
import { verbKey } from "../kernel/packs/uniqueness.js";
|
|
@@ -41,28 +42,82 @@ const PREFIXES = {
|
|
|
41
42
|
owl: "http://www.w3.org/2002/07/owl#",
|
|
42
43
|
rdfs: "http://www.w3.org/2000/01/rdf-schema#",
|
|
43
44
|
xsd: "http://www.w3.org/2001/XMLSchema#",
|
|
45
|
+
dt: "https://dt.canonical.com/",
|
|
46
|
+
"dt-web": "https://dt.canonical.com/platform/web/",
|
|
47
|
+
"w3c-tokens": "https://dt.canonical.com/w3c-tokens/",
|
|
48
|
+
anatomy: "https://anatomy.canonical.com/",
|
|
49
|
+
sh: "http://www.w3.org/ns/shacl#",
|
|
50
|
+
rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
|
|
44
51
|
};
|
|
45
52
|
|
|
46
53
|
// The ontology (TBox) with NO individuals — a booted-but-empty store.
|
|
47
54
|
const TTL = `
|
|
48
55
|
@prefix ds: <https://ds.canonical.com/> .
|
|
49
56
|
@prefix cs: <https://ds.canonical.com/code-standards/> .
|
|
57
|
+
@prefix dt: <https://dt.canonical.com/> .
|
|
58
|
+
@prefix dt-web: <https://dt.canonical.com/platform/web/> .
|
|
59
|
+
@prefix w3c-tokens: <https://dt.canonical.com/w3c-tokens/> .
|
|
60
|
+
@prefix anatomy: <https://anatomy.canonical.com/> .
|
|
61
|
+
@prefix sh: <http://www.w3.org/ns/shacl#> .
|
|
50
62
|
@prefix owl: <http://www.w3.org/2002/07/owl#> .
|
|
51
63
|
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
|
52
64
|
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
53
65
|
ds:ModifierFamily a owl:Class .
|
|
54
66
|
ds:Modifier a owl:Class .
|
|
55
|
-
ds:Token a owl:Class .
|
|
56
|
-
ds:TokenType a owl:Class .
|
|
57
67
|
cs:CodeStandard a owl:Class .
|
|
58
68
|
cs:Category a owl:Class .
|
|
59
69
|
ds:name a owl:DatatypeProperty ; rdfs:domain ds:ModifierFamily ; rdfs:range xsd:string .
|
|
60
70
|
ds:hasModifier a owl:ObjectProperty ; rdfs:domain ds:ModifierFamily ; rdfs:range ds:Modifier .
|
|
61
71
|
ds:modifierFamily a owl:ObjectProperty ; rdfs:domain ds:Modifier ; rdfs:range ds:ModifierFamily .
|
|
62
|
-
ds:tokenId a owl:DatatypeProperty ; rdfs:domain ds:Token ; rdfs:range xsd:string .
|
|
63
|
-
ds:tokenType a owl:ObjectProperty ; rdfs:domain ds:Token ; rdfs:range ds:TokenType .
|
|
64
72
|
cs:description a owl:DatatypeProperty ; rdfs:domain cs:CodeStandard ; rdfs:range xsd:string .
|
|
65
73
|
cs:slug a owl:DatatypeProperty ; rdfs:domain cs:Category ; rdfs:range xsd:string .
|
|
74
|
+
# The token strata's TBox, which replaces the retired ds:Token declarations: the
|
|
75
|
+
# story reads dt: and w3c-tokens: now, and a booted-but-empty store has to BIND
|
|
76
|
+
# the terms a declared read names or the read fails as an unbuilt store instead
|
|
77
|
+
# of answering the empty list this suite is about. Declarations only — no
|
|
78
|
+
# individuals — which is the whole point of the fixture.
|
|
79
|
+
dt:TokenSymbol a owl:Class .
|
|
80
|
+
dt:ResolvedValue a owl:Class .
|
|
81
|
+
dt:Variable a owl:Class .
|
|
82
|
+
dt:Declaration a owl:Class .
|
|
83
|
+
dt:Coordinate a owl:Class .
|
|
84
|
+
dt:Tier a owl:Class .
|
|
85
|
+
dt:Visibility a owl:Class .
|
|
86
|
+
w3c-tokens:Token a owl:Class .
|
|
87
|
+
w3c-tokens:TokenType a owl:Class .
|
|
88
|
+
dt:channelOf a owl:ObjectProperty ; rdfs:range dt:TokenSymbol .
|
|
89
|
+
dt:symbol a owl:ObjectProperty ; rdfs:range dt:TokenSymbol .
|
|
90
|
+
dt:tokenType a owl:ObjectProperty ; rdfs:range w3c-tokens:TokenType .
|
|
91
|
+
dt:covers a owl:ObjectProperty ; rdfs:range dt:TokenSymbol .
|
|
92
|
+
dt:forSymbol a owl:ObjectProperty ; rdfs:range dt:TokenSymbol .
|
|
93
|
+
dt:coordinate a owl:ObjectProperty ; rdfs:range dt:Coordinate .
|
|
94
|
+
dt:derivedFrom a owl:ObjectProperty ; rdfs:range dt:TokenSymbol .
|
|
95
|
+
dt:resolutionChain a owl:ObjectProperty .
|
|
96
|
+
dt:resolvesTo a owl:DatatypeProperty ; rdfs:range xsd:string .
|
|
97
|
+
dt:ofSymbol a owl:ObjectProperty ; rdfs:range dt:TokenSymbol .
|
|
98
|
+
dt:tier a owl:ObjectProperty ; rdfs:range dt:Tier .
|
|
99
|
+
dt:visibility a owl:ObjectProperty ; rdfs:range dt:Visibility .
|
|
100
|
+
dt:declaredAt a owl:ObjectProperty ; rdfs:range dt:Declaration .
|
|
101
|
+
dt:under a owl:ObjectProperty .
|
|
102
|
+
dt:emits a owl:DatatypeProperty ; rdfs:range xsd:string .
|
|
103
|
+
dt:at a owl:DatatypeProperty ; rdfs:range xsd:string .
|
|
104
|
+
dt:alsoAt a owl:ObjectProperty ; rdfs:range dt:Coordinate .
|
|
105
|
+
dt:derives a owl:ObjectProperty .
|
|
106
|
+
dt:references a owl:ObjectProperty .
|
|
107
|
+
dt:selectsCoordinate a owl:ObjectProperty ; rdfs:range dt:Coordinate .
|
|
108
|
+
dt-web:selector a owl:DatatypeProperty ; rdfs:range xsd:string .
|
|
109
|
+
dt-web:inAtRule a owl:ObjectProperty .
|
|
110
|
+
w3c-tokens:description a owl:DatatypeProperty ; rdfs:range xsd:string .
|
|
111
|
+
w3c-tokens:inFile a owl:ObjectProperty .
|
|
112
|
+
w3c-tokens:path a owl:DatatypeProperty ; rdfs:range xsd:string .
|
|
113
|
+
anatomy:StyleKey a owl:Class .
|
|
114
|
+
anatomy:styleKey a owl:DatatypeProperty ; rdfs:range xsd:string .
|
|
115
|
+
anatomy:styleState a owl:DatatypeProperty ; rdfs:range xsd:string .
|
|
116
|
+
ds:hasTokenBinding a owl:ObjectProperty .
|
|
117
|
+
ds:consumesSymbol a owl:ObjectProperty ; rdfs:range dt:TokenSymbol .
|
|
118
|
+
ds:rank a owl:DatatypeProperty .
|
|
119
|
+
ds:node a owl:DatatypeProperty .
|
|
120
|
+
ds:viaBlock a owl:ObjectProperty .
|
|
66
121
|
`;
|
|
67
122
|
|
|
68
123
|
let rt: PragmaRuntime;
|
|
@@ -99,31 +154,32 @@ const PACK_LISTS: readonly { pack: PackDefinition; hint: RegExp }[] = [
|
|
|
99
154
|
const REAL = { dryRun: false, undo: false, yes: false };
|
|
100
155
|
|
|
101
156
|
describe("pack list empty-state (U5, PROTECTED)", () => {
|
|
102
|
-
it.each(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
157
|
+
it.each(PACK_LISTS)(
|
|
158
|
+
"$pack.noun list on an empty store returns [] — never throws",
|
|
159
|
+
async ({ pack }) => {
|
|
160
|
+
await expect(listVerb(pack).run({}, rt)).resolves.toEqual({
|
|
161
|
+
rows: [],
|
|
162
|
+
limit: DEFAULT_LIST_LIMIT,
|
|
163
|
+
});
|
|
164
|
+
},
|
|
165
|
+
);
|
|
109
166
|
|
|
110
|
-
it.each(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
});
|
|
167
|
+
it.each(PACK_LISTS)(
|
|
168
|
+
"$pack.noun list declares a non-blank empty notice (+ hint), JSON stays []",
|
|
169
|
+
({ pack, hint }) => {
|
|
170
|
+
const { formatters } = listVerb(pack).output;
|
|
171
|
+
// The message + runnable hint live on the empty-state seam — the
|
|
172
|
+
// dispatcher routes them to stderr, keeping plain stdout pure data.
|
|
173
|
+
const empty = { rows: [], limit: DEFAULT_LIST_LIMIT };
|
|
174
|
+
const notice = formatters.notice?.(empty);
|
|
175
|
+
expect(notice).toContain(`No ${pack.noun} entries found.`);
|
|
176
|
+
expect(notice).toMatch(hint);
|
|
177
|
+
// JSON is the uniform empty array — unchanged by the message.
|
|
178
|
+
expect(formatters.json(empty)).toBe("[]");
|
|
179
|
+
// The llm view stays non-blank too (the `(0)` heading plus the message).
|
|
180
|
+
expect(formatters.llm(empty).trim().length).toBeGreaterThan(0);
|
|
181
|
+
},
|
|
182
|
+
);
|
|
127
183
|
|
|
128
184
|
it("dispatch routes the empty message to stderr and exits 0 (end-to-end)", async () => {
|
|
129
185
|
const outcome = await executeVerb(
|