@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
|
@@ -20,10 +20,15 @@ import {
|
|
|
20
20
|
type WriteState,
|
|
21
21
|
} from "./harnessInventory.js";
|
|
22
22
|
|
|
23
|
+
// Roo Code is the `unscoped` example: it is `scope: "project"`, so it can
|
|
24
|
+
// never hold a global entry. VS Code used to stand here and stopped being an
|
|
25
|
+
// example of anything when its per-user `mcp.json` was added — it is
|
|
26
|
+
// `scope: "both"` now, and a fixture claiming otherwise would pin a fact the
|
|
27
|
+
// registry no longer holds.
|
|
23
28
|
const REGISTRY: InventoryHarness[] = [
|
|
24
29
|
{ id: "claude-code", name: "Claude Code", inScope: true },
|
|
25
30
|
{ id: "cursor", name: "Cursor", inScope: true },
|
|
26
|
-
{ id: "
|
|
31
|
+
{ id: "roo-code", name: "Roo Code", inScope: false },
|
|
27
32
|
];
|
|
28
33
|
|
|
29
34
|
/** One harness's share of a file — its OWN write's state, never the file's. */
|
|
@@ -50,7 +55,7 @@ describe("harnessInventory — the roll-up", () => {
|
|
|
50
55
|
"cursor:detected",
|
|
51
56
|
// Not detected AND has no location in this scope at all — the two are
|
|
52
57
|
// different facts and the row says which one applies.
|
|
53
|
-
"
|
|
58
|
+
"roo-code:unscoped",
|
|
54
59
|
]);
|
|
55
60
|
expect(rows[0]?.locations).toEqual(["~/.claude.json"]);
|
|
56
61
|
expect(rows[2]?.locations).toEqual([]);
|
|
@@ -58,12 +63,14 @@ describe("harnessInventory — the roll-up", () => {
|
|
|
58
63
|
|
|
59
64
|
it("keeps 'undetected' and 'unscoped' apart", () => {
|
|
60
65
|
// Same absence, two different reasons: Cursor could be here and is not,
|
|
61
|
-
//
|
|
66
|
+
// Roo Code could never be here at all.
|
|
62
67
|
const rows = roll([]);
|
|
63
68
|
expect(rows.find((r) => r.harnessId === "cursor")?.state).toBe(
|
|
64
69
|
"undetected",
|
|
65
70
|
);
|
|
66
|
-
expect(rows.find((r) => r.harnessId === "
|
|
71
|
+
expect(rows.find((r) => r.harnessId === "roo-code")?.state).toBe(
|
|
72
|
+
"unscoped",
|
|
73
|
+
);
|
|
67
74
|
});
|
|
68
75
|
|
|
69
76
|
it("reports a drifted group as drifted, not as registered", () => {
|
|
@@ -158,11 +165,11 @@ describe("harnessInventory — the listing", () => {
|
|
|
158
165
|
expect(items.map((i) => i.label)).toEqual([
|
|
159
166
|
"Claude Code",
|
|
160
167
|
"Cursor",
|
|
161
|
-
"
|
|
168
|
+
"Roo Code",
|
|
162
169
|
]);
|
|
163
170
|
expect(items[1]?.detail).toBe("not detected");
|
|
164
171
|
// Scope-aware, and it states the FACT rather than naming a partition:
|
|
165
|
-
//
|
|
172
|
+
// Roo Code keeps its MCP config per project, so there is no global file for
|
|
166
173
|
// pragma to be in. `no Global band` named an internal word for that.
|
|
167
174
|
expect(items[2]?.detail).toBe(
|
|
168
175
|
"keeps no global config — it is per-project only",
|
|
@@ -68,9 +68,13 @@ import type { CheckItem, CheckStatus, Scope } from "../types.js";
|
|
|
68
68
|
* - `detected` — the harness is on this machine; pragma is not registered.
|
|
69
69
|
* - `undetected` — in the registry, has a location in this scope, not found.
|
|
70
70
|
* - `unscoped` — in the registry, but has NO config location in this scope at
|
|
71
|
-
* all
|
|
72
|
-
*
|
|
73
|
-
*
|
|
71
|
+
* all. Roo Code and Cline are `scope: "project"`, so neither can ever hold a
|
|
72
|
+
* global entry: Cline is a VS Code EXTENSION whose config is the workspace
|
|
73
|
+
* `.vscode/mcp.json`, and Roo Code's is `.roo/mcp.json` — neither keeps a
|
|
74
|
+
* per-user file of its own. (VS Code itself used to be the example here, and
|
|
75
|
+
* stopped being one when its per-user `mcp.json` was added: it is
|
|
76
|
+
* `scope: "both"` now.) Stated rather than omitted: a verbose listing that
|
|
77
|
+
* silently dropped a row reads as a bug in the listing.
|
|
74
78
|
*/
|
|
75
79
|
export type InventoryState =
|
|
76
80
|
| "registered"
|
|
@@ -202,7 +206,7 @@ const STATE_STATUS: Record<InventoryState, CheckStatus> = {
|
|
|
202
206
|
*
|
|
203
207
|
* The unscoped line says WHERE the harness keeps its config rather than naming
|
|
204
208
|
* an internal partition. `no Global band` told the reader nothing: "band" was
|
|
205
|
-
* this repository's word, and the fact underneath it —
|
|
209
|
+
* this repository's word, and the fact underneath it — Roo Code stores its MCP
|
|
206
210
|
* config per project, so there is no global file for pragma to be in — is the
|
|
207
211
|
* thing that explains the row.
|
|
208
212
|
*/
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pins the hand-written comparison in `isSupportedNodeVersion` to the range the
|
|
3
|
+
* package actually declares.
|
|
4
|
+
*
|
|
5
|
+
* The implementation encodes `engines.node` as a chain of comparisons rather
|
|
6
|
+
* than parsing it, so nothing structural stops the two drifting apart. This
|
|
7
|
+
* suite closes that by building a SECOND, independent predicate directly from
|
|
8
|
+
* the declared range string and requiring both to agree across a version grid.
|
|
9
|
+
* A change to `engines.node` therefore moves the oracle and fails here until the
|
|
10
|
+
* implementation is moved with it — which a table of hand-written expectations
|
|
11
|
+
* could not do, since its cheapest repair is editing the expectations.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { describe, expect, it } from "vitest";
|
|
15
|
+
import { SUPPORTED_NODE_RANGE } from "../../../constants.js";
|
|
16
|
+
import { isSupportedNodeVersion } from "./isSupportedNodeVersion.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Evaluate a version against a range of the form `>=22.18 <23 || >=23.6`.
|
|
20
|
+
*
|
|
21
|
+
* Deliberately naive and deliberately separate from the implementation: it is
|
|
22
|
+
* an oracle, not a utility, and it throws on any comparator shape it was not
|
|
23
|
+
* written for so a widened range fails loudly instead of silently passing.
|
|
24
|
+
*/
|
|
25
|
+
function satisfiesDeclaredRange(version: string, range: string): boolean {
|
|
26
|
+
// Semver excludes prereleases from a range unless a comparator opts in, and
|
|
27
|
+
// none of ours does. Encoded here too, so the oracle and the implementation
|
|
28
|
+
// are held to the same rule rather than agreeing by omission.
|
|
29
|
+
if (version.includes("-")) return false;
|
|
30
|
+
|
|
31
|
+
const rank = (major: number, minor: number) => major * 1_000_000 + minor;
|
|
32
|
+
const [major = Number.NaN, minor = Number.NaN] = version
|
|
33
|
+
.split(".")
|
|
34
|
+
.map(Number);
|
|
35
|
+
|
|
36
|
+
return range.split("||").some((clause) =>
|
|
37
|
+
clause
|
|
38
|
+
.trim()
|
|
39
|
+
.split(/\s+/)
|
|
40
|
+
.every((comparator) => {
|
|
41
|
+
// A comparator may be major-only (`<23`) or major.minor (`>=22.18`).
|
|
42
|
+
const parsed = /^(>=|<)(\d+)(?:\.(\d+))?$/.exec(comparator);
|
|
43
|
+
if (!parsed)
|
|
44
|
+
throw new Error(`oracle cannot parse comparator: ${comparator}`);
|
|
45
|
+
const [, operator, boundMajor, boundMinor] = parsed;
|
|
46
|
+
const actual = rank(major, minor);
|
|
47
|
+
const bound = rank(Number(boundMajor), Number(boundMinor ?? 0));
|
|
48
|
+
return operator === ">=" ? actual >= bound : actual < bound;
|
|
49
|
+
}),
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const GRID = [
|
|
54
|
+
"20.19.0",
|
|
55
|
+
"21.7.3",
|
|
56
|
+
"22.0.0",
|
|
57
|
+
"22.12.0",
|
|
58
|
+
"22.17.9",
|
|
59
|
+
"22.18.0",
|
|
60
|
+
"22.18.1",
|
|
61
|
+
"22.99.0",
|
|
62
|
+
"23.0.0",
|
|
63
|
+
"23.5.9",
|
|
64
|
+
"23.6.0",
|
|
65
|
+
"23.9.0",
|
|
66
|
+
"24.0.0",
|
|
67
|
+
"24.18.1",
|
|
68
|
+
"25.0.0",
|
|
69
|
+
"26.0.0",
|
|
70
|
+
// Prereleases: numerically inside the range, excluded by semver.
|
|
71
|
+
"22.18.0-pre",
|
|
72
|
+
"23.6.0-nightly",
|
|
73
|
+
"24.0.0-rc.1",
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
describe("isSupportedNodeVersion — agrees with the declared engines range", () => {
|
|
77
|
+
it.each(GRID)("matches the oracle for %s", (version) => {
|
|
78
|
+
expect(isSupportedNodeVersion(version)).toBe(
|
|
79
|
+
satisfiesDeclaredRange(version, SUPPORTED_NODE_RANGE),
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// The oracle is only trustworthy if it can disagree; prove it discriminates
|
|
84
|
+
// rather than returning a constant.
|
|
85
|
+
it("uses an oracle that actually discriminates", () => {
|
|
86
|
+
const verdicts = GRID.map((v) =>
|
|
87
|
+
satisfiesDeclaredRange(v, SUPPORTED_NODE_RANGE),
|
|
88
|
+
);
|
|
89
|
+
expect(verdicts).toContain(true);
|
|
90
|
+
expect(verdicts).toContain(false);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("rejects prerelease builds whose numeric part is in range", () => {
|
|
94
|
+
// Asserted directly, not only through the oracle: if both sides dropped the
|
|
95
|
+
// rule together the grid would still agree and prove nothing.
|
|
96
|
+
for (const pre of ["22.18.0-pre", "23.6.0-nightly", "24.0.0-rc.1"]) {
|
|
97
|
+
expect(isSupportedNodeVersion(pre)).toBe(false);
|
|
98
|
+
}
|
|
99
|
+
// The same versions without the suffix are supported, so it is the suffix
|
|
100
|
+
// doing the work and not the numbers.
|
|
101
|
+
for (const stable of ["22.18.0", "23.6.0", "24.0.0"]) {
|
|
102
|
+
expect(isSupportedNodeVersion(stable)).toBe(true);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("fails closed on a version it cannot parse", () => {
|
|
107
|
+
for (const bad of ["not-a-version", "", "v24.1.0", "24"]) {
|
|
108
|
+
expect(isSupportedNodeVersion(bad)).toBe(false);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a Node.js version satisfies the range this package declares in its
|
|
3
|
+
* own `engines.node`, surfaced as `SUPPORTED_NODE_RANGE` in `constants.ts`.
|
|
4
|
+
*
|
|
5
|
+
* The range is expressed here as a comparison rather than parsed: the CLI
|
|
6
|
+
* carries no semver dependency, and one range does not justify a range parser.
|
|
7
|
+
* What keeps the two honest is `isSupportedNodeVersion.test.ts`, which builds an
|
|
8
|
+
* independent predicate straight from the declared range string and requires
|
|
9
|
+
* the two to agree across a version grid — so a change to `engines.node` that
|
|
10
|
+
* is not mirrored here fails the suite rather than shipping.
|
|
11
|
+
*
|
|
12
|
+
* A major-only comparison cannot express this range: it cannot say "22.18 but
|
|
13
|
+
* not 22.0", and it cannot exclude the 23.0–23.5 window.
|
|
14
|
+
*
|
|
15
|
+
* @param version - A Node version string, e.g. `process.versions.node`.
|
|
16
|
+
* @returns Whether the version falls inside the declared range.
|
|
17
|
+
*/
|
|
18
|
+
export function isSupportedNodeVersion(version: string): boolean {
|
|
19
|
+
// A semver range excludes prereleases unless a comparator opts into them, and
|
|
20
|
+
// `engines.node` opts into none. So `22.18.0-pre` is outside the declared
|
|
21
|
+
// range even though its numeric part is inside it, and reporting it as
|
|
22
|
+
// supported would be doctor passing a runtime the install contract rejects.
|
|
23
|
+
if (version.includes("-")) return false;
|
|
24
|
+
|
|
25
|
+
const [rawMajor, rawMinor] = version.split(".");
|
|
26
|
+
const major = Number(rawMajor);
|
|
27
|
+
const minor = Number(rawMinor);
|
|
28
|
+
|
|
29
|
+
if (!Number.isInteger(major) || !Number.isInteger(minor)) return false;
|
|
30
|
+
if (major < 22) return false;
|
|
31
|
+
if (major === 22) return minor >= 18;
|
|
32
|
+
if (major === 23) return minor >= 6;
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
@@ -45,6 +45,12 @@ describe("commandOf — one executable, either entry shape", () => {
|
|
|
45
45
|
});
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
+
// Every test in this suite writes into a temp directory and then resolves
|
|
49
|
+
// against it, so each one is bound by filesystem latency rather than by any
|
|
50
|
+
// work of its own. Vitest's 5s default is comfortable on an idle machine and
|
|
51
|
+
// marginal on a loaded one — this suite has timed out in CI while asserting
|
|
52
|
+
// nothing slow. Ten seconds keeps the assertions meaningful without making a
|
|
53
|
+
// genuine hang cheap to ignore.
|
|
48
54
|
describe("commandResolves — the host's own resolution rules", () => {
|
|
49
55
|
const dirs: string[] = [];
|
|
50
56
|
const tmp = (): string => {
|
|
@@ -97,4 +103,4 @@ describe("commandResolves — the host's own resolution rules", () => {
|
|
|
97
103
|
await commandResolves("./local-bin", dir, hostOf("linux", dir)),
|
|
98
104
|
).toBe(true);
|
|
99
105
|
});
|
|
100
|
-
});
|
|
106
|
+
}, 10_000);
|
|
@@ -38,17 +38,26 @@ import type {
|
|
|
38
38
|
ConfigDetection,
|
|
39
39
|
} from "../../setup/operations/index.js";
|
|
40
40
|
import {
|
|
41
|
+
blockedMcpGroups,
|
|
42
|
+
describeEditorSource,
|
|
43
|
+
type FsProbe,
|
|
44
|
+
firstLspBlock,
|
|
45
|
+
firstMcpBlock,
|
|
46
|
+
installableEditors,
|
|
47
|
+
LSP_SKIP_REMEDY,
|
|
41
48
|
type LspDetection,
|
|
49
|
+
lspBlockReason,
|
|
42
50
|
lspEditorNames,
|
|
43
51
|
lspSkipReason,
|
|
44
52
|
type McpDetection,
|
|
53
|
+
mcpBlockReason,
|
|
45
54
|
mcpGroupState,
|
|
46
55
|
mcpWriteState,
|
|
47
56
|
type SkillsDetection,
|
|
48
57
|
skillsSkipReason,
|
|
49
58
|
skillsSkipRemedy,
|
|
50
59
|
} from "../../setup/operations/index.js";
|
|
51
|
-
import { shortenPath, TARGET_IDS } from "../../setup/plan.js";
|
|
60
|
+
import { type Roots, shortenPath, TARGET_IDS } from "../../setup/plan.js";
|
|
52
61
|
import { MCP_NO_LOCATION } from "../../setup/targets.js";
|
|
53
62
|
import type { CheckItem, CheckResult, Scope } from "../types.js";
|
|
54
63
|
import { checkShellCompletions } from "./checkShellCompletions.js";
|
|
@@ -88,15 +97,78 @@ const configHealth = (
|
|
|
88
97
|
return { status: "pass", detail: `${path} — valid` };
|
|
89
98
|
};
|
|
90
99
|
|
|
91
|
-
/**
|
|
92
|
-
|
|
100
|
+
/**
|
|
101
|
+
* The `lsp` row: one sub-item per editor, saying HOW it was found and where
|
|
102
|
+
* pragma stands in it.
|
|
103
|
+
*
|
|
104
|
+
* Provenance per editor is the point. A colleague whose macOS `setup lsp`
|
|
105
|
+
* reported nothing had VS Code installed the whole time, and a one-line row
|
|
106
|
+
* ("not installed in …", or a bare skip) could not tell them whether pragma had
|
|
107
|
+
* missed the editor or found it and could not act. Per the owner, 2026-09-16,
|
|
108
|
+
* this report says per editor how it was found and why a step was skipped.
|
|
109
|
+
*
|
|
110
|
+
* The row's own status keeps the report's invariant that every `available` row
|
|
111
|
+
* has a remedy: any editor that CAN be installed into makes the row
|
|
112
|
+
* `available`, whose derived fix is `pragma setup lsp`. A row where NOTHING is
|
|
113
|
+
* installed and nothing is installable is a `skip` carrying the first block's
|
|
114
|
+
* own remedy — a Nix declaration, a `chmod`, or the palette command — never
|
|
115
|
+
* the derived command, which would only reproduce the skip.
|
|
116
|
+
*
|
|
117
|
+
* An INSTALLED editor is a `pass` even when its extensions folder is blocked.
|
|
118
|
+
* The copy is there and works; the block only says this command cannot CHANGE
|
|
119
|
+
* it. Reporting a skip instead told the owner of a site-managed folder that
|
|
120
|
+
* already held the extension to chmod it and install again, and made a machine
|
|
121
|
+
* that is installed everywhere it can be a permanent `○ lsp`.
|
|
122
|
+
*/
|
|
123
|
+
const lspHealth = (d: LspDetection, roots: Roots): Health => {
|
|
93
124
|
if (d.state === "unknown") {
|
|
94
|
-
return {
|
|
125
|
+
return {
|
|
126
|
+
status: "skip",
|
|
127
|
+
detail: lspSkipReason(d),
|
|
128
|
+
remedy: LSP_SKIP_REMEDY,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
const items: CheckItem[] = d.editors.map((e): CheckItem => {
|
|
132
|
+
const reason = e.installed ? undefined : lspBlockReason(e);
|
|
133
|
+
const state = reason ?? (e.installed ? "installed" : "not installed");
|
|
134
|
+
return {
|
|
135
|
+
label: e.editor.name,
|
|
136
|
+
status: e.installed
|
|
137
|
+
? "pass"
|
|
138
|
+
: reason !== undefined
|
|
139
|
+
? "skip"
|
|
140
|
+
: "available",
|
|
141
|
+
detail: `${describeEditorSource(e, roots)} · ${state}`,
|
|
142
|
+
};
|
|
143
|
+
});
|
|
144
|
+
const installable = installableEditors(d);
|
|
145
|
+
if (installable.length > 0) {
|
|
146
|
+
return {
|
|
147
|
+
status: "available",
|
|
148
|
+
detail: `not installed in ${installable.map((e) => e.editor.name).join(", ")}`,
|
|
149
|
+
items,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
// Nothing installable. The row is the block's only when nothing is installed
|
|
153
|
+
// either — otherwise the honest headline is what IS in place.
|
|
154
|
+
const block = d.editors.some((e) => e.installed)
|
|
155
|
+
? undefined
|
|
156
|
+
: firstLspBlock(d);
|
|
157
|
+
if (block !== undefined) {
|
|
158
|
+
return {
|
|
159
|
+
status: "skip",
|
|
160
|
+
// The headline names the editor; the item's own detail does not, because
|
|
161
|
+
// its label already has.
|
|
162
|
+
detail: block.headline,
|
|
163
|
+
items,
|
|
164
|
+
remedy: block.remedy,
|
|
165
|
+
};
|
|
95
166
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
167
|
+
return {
|
|
168
|
+
status: "pass",
|
|
169
|
+
detail: `installed in ${lspEditorNames(d).join(", ")}`,
|
|
170
|
+
items,
|
|
171
|
+
};
|
|
100
172
|
};
|
|
101
173
|
|
|
102
174
|
/**
|
|
@@ -118,6 +190,12 @@ async function mcpHealth(
|
|
|
118
190
|
const items: CheckItem[] = d.groups.map((group) => {
|
|
119
191
|
const state = mcpGroupState(d, group.path);
|
|
120
192
|
const label = shortenPath(group.path, roots);
|
|
193
|
+
// A file pragma must not write is reported as a skip, not as an
|
|
194
|
+
// `available` whose `fix:` would fail the moment it ran.
|
|
195
|
+
const blocked = mcpBlockReason(d, group, roots);
|
|
196
|
+
if (blocked !== undefined) {
|
|
197
|
+
return { label, status: "skip" as const, detail: blocked };
|
|
198
|
+
}
|
|
121
199
|
if (state === "absent") {
|
|
122
200
|
return { label, status: "available", detail: "not registered" };
|
|
123
201
|
}
|
|
@@ -132,6 +210,22 @@ async function mcpHealth(
|
|
|
132
210
|
: `registered, but \`${MCP_SERVER_NAME}\` is not on PATH`,
|
|
133
211
|
};
|
|
134
212
|
});
|
|
213
|
+
// Every location blocked means the row's whole answer is the block: it
|
|
214
|
+
// carries the first one's remedy — the entry to declare by hand — rather than
|
|
215
|
+
// the derived `pragma setup mcp`, which would only reproduce the skip.
|
|
216
|
+
const blockedGroups = blockedMcpGroups(d);
|
|
217
|
+
const block =
|
|
218
|
+
blockedGroups.length === d.groups.length
|
|
219
|
+
? firstMcpBlock(d, roots)
|
|
220
|
+
: undefined;
|
|
221
|
+
if (block !== undefined) {
|
|
222
|
+
return {
|
|
223
|
+
status: "skip",
|
|
224
|
+
detail: block.reason,
|
|
225
|
+
items,
|
|
226
|
+
remedy: block.remedy,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
135
229
|
const configured = items.filter((item) => item.status === "pass").length;
|
|
136
230
|
const failing = items.filter((item) => item.status === "fail").length;
|
|
137
231
|
if (failing > 0) {
|
|
@@ -141,6 +235,7 @@ async function mcpHealth(
|
|
|
141
235
|
items,
|
|
142
236
|
};
|
|
143
237
|
}
|
|
238
|
+
const writable = items.length - blockedGroups.length;
|
|
144
239
|
if (configured === 0) {
|
|
145
240
|
// Absence in the LOCAL PROJECT is not a failure: registering there is
|
|
146
241
|
// opt-in, so a repository with no checked-in MCP config is healthy. The
|
|
@@ -153,13 +248,13 @@ async function mcpHealth(
|
|
|
153
248
|
}
|
|
154
249
|
: {
|
|
155
250
|
status: "available",
|
|
156
|
-
detail: `not registered in any of ${
|
|
251
|
+
detail: `not registered in any of ${writable} config files`,
|
|
157
252
|
items,
|
|
158
253
|
};
|
|
159
254
|
}
|
|
160
255
|
return {
|
|
161
|
-
status: configured ===
|
|
162
|
-
detail: `registered in ${configured} of ${
|
|
256
|
+
status: configured === writable ? "pass" : "available",
|
|
257
|
+
detail: `registered in ${configured} of ${writable} config files`,
|
|
163
258
|
items,
|
|
164
259
|
};
|
|
165
260
|
}
|
|
@@ -267,7 +362,7 @@ async function healthOf(
|
|
|
267
362
|
};
|
|
268
363
|
}
|
|
269
364
|
case "lsp":
|
|
270
|
-
return lspHealth(row.detection as LspDetection);
|
|
365
|
+
return lspHealth(row.detection as LspDetection, roots);
|
|
271
366
|
case "mcp":
|
|
272
367
|
return mcpHealth(row.detection as McpDetection, row.scope, rt.cwd, roots);
|
|
273
368
|
default:
|
|
@@ -406,15 +501,21 @@ export const fixCommandFor = (id: string, scope: Scope, bin: string): string =>
|
|
|
406
501
|
*
|
|
407
502
|
* @param rt - The per-invocation runtime.
|
|
408
503
|
* @param bin - The binary name the `fix:` lines are derived from.
|
|
504
|
+
* @param probe - The writability filesystem seam, threaded to every row's
|
|
505
|
+
* detection. Defaults to the real one; a test injects a fixture to reach the
|
|
506
|
+
* blocked arms — a `/nix/store` path, a directory that refuses `W_OK` —
|
|
507
|
+
* through THIS entry point, rather than opening the row bodies up as exports
|
|
508
|
+
* nothing in production would call.
|
|
409
509
|
* @returns One {@link CheckResult} per (target, scope), plus one per scope.
|
|
410
510
|
* @note Impure — every target's detection reads the real filesystem.
|
|
411
511
|
*/
|
|
412
512
|
export async function scopedChecks(
|
|
413
513
|
rt: PragmaRuntime,
|
|
414
514
|
bin: string,
|
|
515
|
+
probe?: FsProbe,
|
|
415
516
|
): Promise<CheckResult[]> {
|
|
416
517
|
const roots = await resolveRoots(rt);
|
|
417
|
-
const detected = await detectTargets(rt, [...TARGET_IDS], "both");
|
|
518
|
+
const detected = await detectTargets(rt, [...TARGET_IDS], "both", probe);
|
|
418
519
|
const [targetRows, inventoryRows] = await Promise.all([
|
|
419
520
|
Promise.all(
|
|
420
521
|
detected.map(async (row): Promise<CheckResult> => {
|