@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
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filters, search and pagination compiled into the query, EXECUTED against the
|
|
3
|
+
* shipped pack, for every list-shaped body the distribution declares
|
|
4
|
+
* (PROTECTED).
|
|
5
|
+
*
|
|
6
|
+
* The kernel change these assertions guard moved a story's declared filters from
|
|
7
|
+
* a predicate over returned rows into the generated SPARQL, and gave every
|
|
8
|
+
* list-shaped verb a page applied inside that same query. Both halves are
|
|
9
|
+
* semantics claims about a corpus, so a fixture cannot falsify them: a fixture
|
|
10
|
+
* obligingly contains whatever the assertion expects, while the shipped pack
|
|
11
|
+
* contains 252 blocks, 147 standards with a `GROUP_CONCAT`-ed category set, and
|
|
12
|
+
* an ordering with 25 tied names.
|
|
13
|
+
*
|
|
14
|
+
* Four claims, each on the whole corpus rather than a sample:
|
|
15
|
+
*
|
|
16
|
+
* - PAGES PARTITION. Walking a body in small pages returns exactly the rows the
|
|
17
|
+
* unpaginated read returns, in exactly that order, with exactly those keys.
|
|
18
|
+
* This is what makes the page a page OF the answer, and it is the assertion
|
|
19
|
+
* that would catch a store whose sub-select ordering is not stable.
|
|
20
|
+
* - THE QUERY AGREES WITH THE ROW PREDICATE. Every declared filter's admissible
|
|
21
|
+
* values, and a search term, answered by the compiled query, equal what the
|
|
22
|
+
* retired row predicate would have answered over the whole population —
|
|
23
|
+
* computed here, independently, as the oracle. Exhaustively for a
|
|
24
|
+
* choice-sized vocabulary; at a deterministic spread for a catalogue-sized
|
|
25
|
+
* one, because the sweep costs a query per value and two of these
|
|
26
|
+
* vocabularies are catalogues of 745 and 1,156. See {@link sweep}.
|
|
27
|
+
* - A REFUSAL IS STILL A REFUSAL. A value the graph's vocabulary does not admit
|
|
28
|
+
* is INVALID_INPUT naming the admissible values.
|
|
29
|
+
* - AN EMPTY ANSWER IS STILL CALM. A value the vocabulary admits that no row
|
|
30
|
+
* carries is an empty page at exit 0, and so is a cursor past the last row.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
34
|
+
import { MAX_LISTED_OPTIONS } from "../kernel/error/validOptions.js";
|
|
35
|
+
import { compileStoryModule } from "../kernel/packs/compile.js";
|
|
36
|
+
import { MAX_LIST_WINDOW } from "../kernel/packs/paging.js";
|
|
37
|
+
import {
|
|
38
|
+
distributionSource,
|
|
39
|
+
type PackFilter,
|
|
40
|
+
type PackList,
|
|
41
|
+
type PackPage,
|
|
42
|
+
type PackRow,
|
|
43
|
+
} from "../kernel/packs/types.js";
|
|
44
|
+
import { verbKey } from "../kernel/packs/uniqueness.js";
|
|
45
|
+
import { bootRuntime } from "../kernel/runtime/boot.js";
|
|
46
|
+
import type { PragmaRuntime } from "../kernel/runtime/types.js";
|
|
47
|
+
import { kebabCase } from "../kernel/spec/emitSurface.js";
|
|
48
|
+
import type { VerbSpec } from "../kernel/spec/types.js";
|
|
49
|
+
import { TEST_FLAGS } from "../testing/helpers/projectCli.js";
|
|
50
|
+
import { declaredStories } from "./distribution.js";
|
|
51
|
+
|
|
52
|
+
const SOURCE = distributionSource("pragma.conf.ts");
|
|
53
|
+
|
|
54
|
+
/** One list-shaped body of a declared story, with the verb that runs it. */
|
|
55
|
+
interface Body {
|
|
56
|
+
readonly label: string;
|
|
57
|
+
readonly shape: PackList;
|
|
58
|
+
readonly verb: VerbSpec;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Every list-shaped body the distribution declares — `list` and each extra
|
|
63
|
+
* verb — DERIVED from the config rather than listed, so a story added tomorrow
|
|
64
|
+
* is covered by the author who declares it and without their help.
|
|
65
|
+
*/
|
|
66
|
+
const BODIES: readonly Body[] = [...declaredStories].flatMap(
|
|
67
|
+
([noun, story]) => {
|
|
68
|
+
const module = compileStoryModule(story, SOURCE, {});
|
|
69
|
+
const find = (verb: string): VerbSpec => {
|
|
70
|
+
const found = module.verbs.find(
|
|
71
|
+
(v) => verbKey(v.path) === `${noun} ${verb}`,
|
|
72
|
+
);
|
|
73
|
+
if (!found) throw new Error(`no compiled "${noun} ${verb}" verb`);
|
|
74
|
+
return found;
|
|
75
|
+
};
|
|
76
|
+
const bodies: Body[] = [];
|
|
77
|
+
if (story.list) {
|
|
78
|
+
bodies.push({
|
|
79
|
+
label: `${noun} list`,
|
|
80
|
+
shape: story.list,
|
|
81
|
+
verb: find("list"),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
for (const verb of story.verbs ?? []) {
|
|
85
|
+
bodies.push({
|
|
86
|
+
label: `${noun} ${verb.verb}`,
|
|
87
|
+
shape: verb,
|
|
88
|
+
verb: find(verb.verb),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return bodies;
|
|
92
|
+
},
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
/** Bodies whose story declares at least one filter or a search. */
|
|
96
|
+
const NARROWABLE = BODIES.filter(
|
|
97
|
+
(body) => body.shape.filters !== undefined || body.shape.search !== undefined,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
let rt: PragmaRuntime;
|
|
101
|
+
beforeAll(async () => {
|
|
102
|
+
rt = bootRuntime(TEST_FLAGS);
|
|
103
|
+
await rt.store.get();
|
|
104
|
+
}, 60_000);
|
|
105
|
+
|
|
106
|
+
afterAll(async () => {
|
|
107
|
+
(await rt.store.get()).store.dispose();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
/** Run one body and return its page. */
|
|
111
|
+
const page = (body: Body, params: Record<string, unknown> = {}) =>
|
|
112
|
+
body.verb.run(params, rt) as Promise<PackPage>;
|
|
113
|
+
|
|
114
|
+
/** Every row of a body, in one call — the population the oracles read. */
|
|
115
|
+
async function population(body: Body): Promise<readonly PackRow[]> {
|
|
116
|
+
// The kernel ceiling, which is far above every declared story's row count —
|
|
117
|
+
// so this is the whole answer rather than a page of it, asked for the way a
|
|
118
|
+
// caller who wants everything has to ask.
|
|
119
|
+
return (await page(body, { limit: MAX_LIST_WINDOW })).rows;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** The values a value-free filter's declared vocabulary admits. */
|
|
123
|
+
async function vocabulary(filter: PackFilter): Promise<readonly string[]> {
|
|
124
|
+
if (filter.values) return filter.values;
|
|
125
|
+
const declared = filter.vocabulary;
|
|
126
|
+
if (!declared) throw new Error(`filter --${filter.param} declares neither`);
|
|
127
|
+
const result = await rt.query.sparql(declared.query);
|
|
128
|
+
if (result.type !== "select") throw new Error("vocabulary must be a SELECT");
|
|
129
|
+
const variable = declared.variable ?? filter.variable;
|
|
130
|
+
return result.bindings
|
|
131
|
+
.map((row) => row[variable] ?? "")
|
|
132
|
+
.filter((value) => value !== "");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The values this suite actually sweeps for one filter.
|
|
137
|
+
*
|
|
138
|
+
* The promise is per-value, so a sweep costs one query per admissible value.
|
|
139
|
+
* Every filter that predates the token graph had a vocabulary of at most 36 —
|
|
140
|
+
* a set of choices — and exhausting it cost seconds. Two of the token graph's
|
|
141
|
+
* filters have vocabularies that are catalogues instead: 745 symbols and 1,156
|
|
142
|
+
* variables, measured at 1.25s and 0.36s per filtered query. Exhausting those
|
|
143
|
+
* two took 22 minutes between them, and the suite could not finish at all.
|
|
144
|
+
*
|
|
145
|
+
* So a choice-sized vocabulary is still swept exhaustively, and a
|
|
146
|
+
* catalogue-sized one is swept at a deterministic spread of that same size —
|
|
147
|
+
* first, last, and evenly spaced between — which stays reproducible across
|
|
148
|
+
* runs and still exercises every shape of value the filter admits. The
|
|
149
|
+
* boundary is {@link MAX_LISTED_OPTIONS}, the constant that also decides when
|
|
150
|
+
* an error stops naming values and starts counting them, because it is the
|
|
151
|
+
* same distinction: a choice is read, a catalogue is listed.
|
|
152
|
+
*
|
|
153
|
+
* @param admissible - Every value the filter's declared vocabulary admits.
|
|
154
|
+
* @returns The values to sweep, in the vocabulary's own order.
|
|
155
|
+
*/
|
|
156
|
+
function sweep(admissible: readonly string[]): readonly string[] {
|
|
157
|
+
if (admissible.length <= MAX_LISTED_OPTIONS) return admissible;
|
|
158
|
+
const step = (admissible.length - 1) / (MAX_LISTED_OPTIONS - 1);
|
|
159
|
+
const picked: string[] = [];
|
|
160
|
+
for (let index = 0; index < MAX_LISTED_OPTIONS; index += 1) {
|
|
161
|
+
const value = admissible[Math.round(index * step)];
|
|
162
|
+
if (value !== undefined) picked.push(value);
|
|
163
|
+
}
|
|
164
|
+
return [...new Set(picked)];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* The RETIRED row predicate, reimplemented here as the oracle the compiled
|
|
169
|
+
* query is held to: whole-cell equality for `exact`, membership of a
|
|
170
|
+
* whitespace-separated set for `set`, case-insensitive either way, and a cell
|
|
171
|
+
* that is missing or empty never matches.
|
|
172
|
+
*/
|
|
173
|
+
function rowPredicate(
|
|
174
|
+
rows: readonly PackRow[],
|
|
175
|
+
filter: PackFilter,
|
|
176
|
+
value: string,
|
|
177
|
+
): PackRow[] {
|
|
178
|
+
const wanted = value.normalize("NFC").toLowerCase();
|
|
179
|
+
return rows.filter((row) => {
|
|
180
|
+
const cell = row[filter.variable];
|
|
181
|
+
if (cell === undefined || cell === "") return false;
|
|
182
|
+
const normalized = cell.normalize("NFC").toLowerCase();
|
|
183
|
+
return filter.match === "set"
|
|
184
|
+
? normalized.split(/\s+/).includes(wanted)
|
|
185
|
+
: normalized === wanted;
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** The retired search predicate, likewise. */
|
|
190
|
+
function searchPredicate(
|
|
191
|
+
rows: readonly PackRow[],
|
|
192
|
+
variables: readonly string[],
|
|
193
|
+
term: string,
|
|
194
|
+
): PackRow[] {
|
|
195
|
+
const wanted = term.normalize("NFC").toLowerCase();
|
|
196
|
+
return rows.filter((row) =>
|
|
197
|
+
variables.some((variable) =>
|
|
198
|
+
row[variable]?.normalize("NFC").toLowerCase().includes(wanted),
|
|
199
|
+
),
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
describe("pages partition the answer, whole corpus (PROTECTED)", () => {
|
|
204
|
+
it("derives every list-shaped body from the config", () => {
|
|
205
|
+
// A guard, not a no-op: an empty derivation would make every sweep below
|
|
206
|
+
// vacuously green.
|
|
207
|
+
expect(BODIES.map((body) => body.label)).toContain("standard list");
|
|
208
|
+
expect(BODIES.length).toBeGreaterThanOrEqual(9);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it.each(BODIES.map((body) => [body.label, body] as const))(
|
|
212
|
+
"%s: a paged walk is the unpaginated answer, in order",
|
|
213
|
+
async (_label, body) => {
|
|
214
|
+
const whole = await population(body);
|
|
215
|
+
const walked: PackRow[] = [];
|
|
216
|
+
let after: string | undefined;
|
|
217
|
+
// Deliberately not a divisor of any story's row count, so the last page
|
|
218
|
+
// is a partial one and the walk has to notice it is the last.
|
|
219
|
+
const limit = 7;
|
|
220
|
+
for (let guard = 0; guard < 500; guard += 1) {
|
|
221
|
+
const current: PackPage = await page(body, {
|
|
222
|
+
limit,
|
|
223
|
+
...(after ? { after } : {}),
|
|
224
|
+
});
|
|
225
|
+
expect(current.rows.length).toBeLessThanOrEqual(limit);
|
|
226
|
+
walked.push(...current.rows);
|
|
227
|
+
if (current.nextAfter === undefined) break;
|
|
228
|
+
after = current.nextAfter;
|
|
229
|
+
}
|
|
230
|
+
// Byte-identical, keys included: a row's shape is part of the answer, and
|
|
231
|
+
// the generated wrapper projects the author's own variables in the
|
|
232
|
+
// author's own order precisely so it stays so.
|
|
233
|
+
expect(JSON.stringify(walked)).toBe(JSON.stringify(whole));
|
|
234
|
+
},
|
|
235
|
+
60_000,
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
it("standard list: a cursor past the last row is a calm empty page", async () => {
|
|
239
|
+
const body = BODIES.find((b) => b.label === "standard list");
|
|
240
|
+
if (!body) throw new Error("no standard list");
|
|
241
|
+
const whole = await population(body);
|
|
242
|
+
const limit = whole.length - 1;
|
|
243
|
+
const first = await page(body, { limit });
|
|
244
|
+
expect(first.nextAfter).toBeTypeOf("string");
|
|
245
|
+
const last = await page(body, { limit, after: first.nextAfter });
|
|
246
|
+
// The tail, then nothing — and nothing is a success, not an error.
|
|
247
|
+
expect(last.rows).toHaveLength(1);
|
|
248
|
+
expect(last.nextAfter).toBeUndefined();
|
|
249
|
+
}, 60_000);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
describe("the compiled query agrees with the retired row predicate (PROTECTED)", () => {
|
|
253
|
+
it("has a narrowable body to sweep", () => {
|
|
254
|
+
expect([...NARROWABLE.map((body) => body.label)].sort()).toEqual([
|
|
255
|
+
"concept list",
|
|
256
|
+
"implementation list",
|
|
257
|
+
"standard list",
|
|
258
|
+
"token consumers",
|
|
259
|
+
"token list",
|
|
260
|
+
"token values",
|
|
261
|
+
"variable chain",
|
|
262
|
+
"variable list",
|
|
263
|
+
]);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it.each(NARROWABLE.map((body) => [body.label, body] as const))(
|
|
267
|
+
"%s: every admissible filter value answers what the row predicate would",
|
|
268
|
+
async (_label, body) => {
|
|
269
|
+
const whole = await population(body);
|
|
270
|
+
for (const filter of body.shape.filters ?? []) {
|
|
271
|
+
const admissible = await vocabulary(filter);
|
|
272
|
+
expect(admissible.length).toBeGreaterThan(0);
|
|
273
|
+
for (const value of sweep(admissible)) {
|
|
274
|
+
const answered = await page(body, {
|
|
275
|
+
[filter.param]: value,
|
|
276
|
+
limit: MAX_LIST_WINDOW,
|
|
277
|
+
});
|
|
278
|
+
expect(JSON.stringify(answered.rows)).toBe(
|
|
279
|
+
JSON.stringify(rowPredicate(whole, filter, value)),
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
120_000,
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
it.each(NARROWABLE.map((body) => [body.label, body] as const))(
|
|
288
|
+
"%s: a search term answers what the row predicate would",
|
|
289
|
+
async (_label, body) => {
|
|
290
|
+
const search = body.shape.search;
|
|
291
|
+
if (!search) return;
|
|
292
|
+
const whole = await population(body);
|
|
293
|
+
for (const term of ["a", "Button", "TEST"]) {
|
|
294
|
+
const answered = await page(body, {
|
|
295
|
+
search: term,
|
|
296
|
+
limit: MAX_LIST_WINDOW,
|
|
297
|
+
});
|
|
298
|
+
expect(JSON.stringify(answered.rows)).toBe(
|
|
299
|
+
JSON.stringify(searchPredicate(whole, search.variables, term)),
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
120_000,
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
it("standard list: a repeated filter is a union, without duplicating a row", async () => {
|
|
307
|
+
const body = BODIES.find((b) => b.label === "standard list");
|
|
308
|
+
if (!body) throw new Error("no standard list");
|
|
309
|
+
const whole = await population(body);
|
|
310
|
+
const filter = body.shape.filters?.[0];
|
|
311
|
+
if (!filter) throw new Error("no category filter");
|
|
312
|
+
const answered = await page(body, {
|
|
313
|
+
[filter.param]: ["testing", "react"],
|
|
314
|
+
limit: MAX_LIST_WINDOW,
|
|
315
|
+
});
|
|
316
|
+
const union = whole.filter(
|
|
317
|
+
(row) =>
|
|
318
|
+
rowPredicate([row], filter, "testing").length > 0 ||
|
|
319
|
+
rowPredicate([row], filter, "react").length > 0,
|
|
320
|
+
);
|
|
321
|
+
expect(JSON.stringify(answered.rows)).toBe(JSON.stringify(union));
|
|
322
|
+
// The case a `VALUES` block joined into the group would get wrong: a
|
|
323
|
+
// standard filed under a `testing` descendant AND under `react` would come
|
|
324
|
+
// back twice.
|
|
325
|
+
expect(new Set(answered.rows.map((row) => row.uri)).size).toBe(
|
|
326
|
+
answered.rows.length,
|
|
327
|
+
);
|
|
328
|
+
}, 60_000);
|
|
329
|
+
|
|
330
|
+
it("standard list: two filters are a conjunction, narrowing each other", async () => {
|
|
331
|
+
const body = BODIES.find((b) => b.label === "implementation list");
|
|
332
|
+
if (!body) throw new Error("no implementation list");
|
|
333
|
+
const whole = await population(body);
|
|
334
|
+
const [first, second] = body.shape.filters ?? [];
|
|
335
|
+
if (!first || !second) throw new Error("expected two filters");
|
|
336
|
+
const a = (await vocabulary(first))[0] as string;
|
|
337
|
+
const b = (await vocabulary(second))[0] as string;
|
|
338
|
+
const answered = await page(body, {
|
|
339
|
+
[first.param]: a,
|
|
340
|
+
[second.param]: b,
|
|
341
|
+
limit: MAX_LIST_WINDOW,
|
|
342
|
+
});
|
|
343
|
+
expect(JSON.stringify(answered.rows)).toBe(
|
|
344
|
+
JSON.stringify(rowPredicate(rowPredicate(whole, first, a), second, b)),
|
|
345
|
+
);
|
|
346
|
+
}, 60_000);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
describe("a refusal and a calm empty answer stay distinguishable (PROTECTED)", () => {
|
|
350
|
+
it.each(NARROWABLE.map((body) => [body.label, body] as const))(
|
|
351
|
+
"%s: a value the vocabulary does not admit is INVALID_INPUT, naming the ones it does",
|
|
352
|
+
async (_label, body) => {
|
|
353
|
+
for (const filter of body.shape.filters ?? []) {
|
|
354
|
+
const admissible = await vocabulary(filter);
|
|
355
|
+
await expect(
|
|
356
|
+
page(body, { [filter.param]: "zzz-definitely-not-a-value" }),
|
|
357
|
+
).rejects.toMatchObject({
|
|
358
|
+
code: "INVALID_INPUT",
|
|
359
|
+
validOptions: [...admissible].sort(),
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
60_000,
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
it("a value the vocabulary admits that no row carries is an empty page, not an error", async () => {
|
|
367
|
+
// The case that separates "the graph is the vocabulary" from "the returned
|
|
368
|
+
// rows are the vocabulary", found in the corpus rather than named: today it
|
|
369
|
+
// is a `ds:ConceptType` the ontology declares and no concept uses. Under
|
|
370
|
+
// the retired rows-as-vocabulary fallback such a value was INVALID_INPUT;
|
|
371
|
+
// under a PAGE it would have been INVALID_INPUT for merely sorting outside
|
|
372
|
+
// the window. It is neither — it is the empty answer the graph actually has.
|
|
373
|
+
const unpopulated: { body: Body; param: string; value: string }[] = [];
|
|
374
|
+
for (const body of NARROWABLE) {
|
|
375
|
+
const whole = await population(body);
|
|
376
|
+
for (const filter of body.shape.filters ?? []) {
|
|
377
|
+
for (const value of sweep(await vocabulary(filter))) {
|
|
378
|
+
if (rowPredicate(whole, filter, value).length === 0) {
|
|
379
|
+
unpopulated.push({ body, param: filter.param, value });
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
expect(unpopulated.length).toBeGreaterThan(0);
|
|
385
|
+
for (const { body, param, value } of unpopulated) {
|
|
386
|
+
const answered = await page(body, { [param]: value });
|
|
387
|
+
expect(answered.rows).toEqual([]);
|
|
388
|
+
expect(answered.nextAfter).toBeUndefined();
|
|
389
|
+
// And the calm sentence rides the same seam it always did — naming the
|
|
390
|
+
// filter that narrowed to nothing, since a filtered empty answer is a
|
|
391
|
+
// statement about the filter and not about the population.
|
|
392
|
+
expect(body.verb.output.formatters.notice?.(answered as never)).toContain(
|
|
393
|
+
// In the flag's own spelling: a story's `channelOf` param is typed
|
|
394
|
+
// `--channel-of`, and an answer that named the param would be naming
|
|
395
|
+
// something no caller can pass.
|
|
396
|
+
`matches \`--${kebabCase(param)} ${value}\`.`,
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
}, 120_000);
|
|
400
|
+
});
|
|
@@ -61,8 +61,31 @@ const EVERYTHING: McpListable = { sources: [{ as: "entities" }] };
|
|
|
61
61
|
* only. Payload SIZE is the property that actually regressed (712 entries /
|
|
62
62
|
* ~155 KB on every connect), and it is the one an agent pays for twice: once in
|
|
63
63
|
* transfer, once in the context window it can no longer spend on the task.
|
|
64
|
+
*
|
|
65
|
+
* **Raised from 60,000 to 100,000 on 2026-09-01**, when the
|
|
66
|
+
* `@canonical/token-ontology` pack took the payload to 65,119 bytes. The
|
|
67
|
+
* listing was kept whole rather than trimmed to fit: the per-class counts are
|
|
68
|
+
* how an agent gets the shape of the graph before spending a single lookup,
|
|
69
|
+
* and that is worth more than the bytes.
|
|
70
|
+
*
|
|
71
|
+
* **Cost scales with the number of T-Box terms and their metadata, not with
|
|
72
|
+
* the number of packs.** The `ontology` noun emits one resource per matching
|
|
73
|
+
* T-Box entity (`provider.ts`), so a pack costs what its own vocabulary
|
|
74
|
+
* costs — this one happened to add ~23 KB, which is a fact about this
|
|
75
|
+
* ontology and not a rate. The headroom here does not convert into "one more
|
|
76
|
+
* pack" of any size.
|
|
77
|
+
*
|
|
78
|
+
* It is also the only listing that grows this way: every other noun lists
|
|
79
|
+
* collections with their counts, so those grow with the number of nouns. When
|
|
80
|
+
* this ceiling is next reached, the fix is to scope the T-Box slice or
|
|
81
|
+
* paginate it — not to raise the number again, which would turn a gate into a
|
|
82
|
+
* formality.
|
|
83
|
+
*
|
|
84
|
+
* It is deliberately not removed. The regression it was written for is still
|
|
85
|
+
* worth catching, and a ceiling with headroom catches it while a deleted one
|
|
86
|
+
* catches nothing.
|
|
64
87
|
*/
|
|
65
|
-
const LISTING_BUDGET_BYTES =
|
|
88
|
+
const LISTING_BUDGET_BYTES = 100_000;
|
|
66
89
|
|
|
67
90
|
describe("resource listing (storeless, over the pack index)", () => {
|
|
68
91
|
it("degrades to a recovery entry on a missing or legacy index", () => {
|
|
@@ -322,7 +345,20 @@ describe("resource surface over the server (embedded pack)", () => {
|
|
|
322
345
|
let harness: Awaited<ReturnType<typeof projectMcp>>;
|
|
323
346
|
beforeAll(async () => {
|
|
324
347
|
harness = await projectMcp(capabilities);
|
|
325
|
-
|
|
348
|
+
// Boot the store HERE, not in whichever test happens to read first.
|
|
349
|
+
//
|
|
350
|
+
// `createLazyStore` memoises the boot PROMISE, and vitest's per-test
|
|
351
|
+
// timeout aborts the test without aborting the work — so a cold boot that
|
|
352
|
+
// overruns is charged partly to the test that triggered it and partly to
|
|
353
|
+
// the next one, which merely awaits the same promise. That reads as a
|
|
354
|
+
// sudden per-read regression and is nothing of the kind; it cost a real
|
|
355
|
+
// investigation to find that out.
|
|
356
|
+
//
|
|
357
|
+
// Paying it in the hook attributes it where it belongs, keeps every `it`
|
|
358
|
+
// in this file measuring what it claims to, and bounds it by hookTimeout
|
|
359
|
+
// rather than by a 5 s default that pack growth will keep outgrowing.
|
|
360
|
+
await harness.readResource(BUTTON_URI);
|
|
361
|
+
}, 60_000);
|
|
326
362
|
afterAll(async () => {
|
|
327
363
|
await harness.cleanup();
|
|
328
364
|
});
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import type { PragmaRuntime } from "../../kernel/runtime/index.js";
|
|
14
|
+
import type { FsProbe } from "./operations/writability.js";
|
|
14
15
|
import {
|
|
15
16
|
defaultSelected,
|
|
16
17
|
type PlanRow,
|
|
@@ -88,6 +89,10 @@ const messageOf = (reason: unknown): string =>
|
|
|
88
89
|
* @param rt - The per-invocation runtime.
|
|
89
90
|
* @param ids - The targets to plan (all five for the run-all).
|
|
90
91
|
* @param scope - The resolved scope selection.
|
|
92
|
+
* @param probe - The writability filesystem seam, handed to every row's
|
|
93
|
+
* `detect`. Defaults to the real one; a test injects a fixture to drive the
|
|
94
|
+
* two arms no CI host has — a `/nix/store` path, and (as root) a directory
|
|
95
|
+
* that refuses `W_OK` — without opening a second way in to the row bodies.
|
|
91
96
|
* @returns One entry per (target, scope) the scope actually runs.
|
|
92
97
|
* @note Impure — every target's `detect` reads the real filesystem.
|
|
93
98
|
*/
|
|
@@ -95,6 +100,7 @@ export async function detectTargets(
|
|
|
95
100
|
rt: PragmaRuntime,
|
|
96
101
|
ids: readonly TargetId[],
|
|
97
102
|
scope: ScopeSelection,
|
|
103
|
+
probe?: FsProbe,
|
|
98
104
|
): Promise<DetectedRow[]> {
|
|
99
105
|
const wanted = TARGETS.filter((target) => ids.includes(target.id));
|
|
100
106
|
const pairs = scopesForSelection(scope).flatMap((scope) =>
|
|
@@ -106,7 +112,7 @@ export async function detectTargets(
|
|
|
106
112
|
})),
|
|
107
113
|
);
|
|
108
114
|
const settled = await Promise.allSettled(
|
|
109
|
-
pairs.map(({ target, scope }) => target.detect(rt, scope)),
|
|
115
|
+
pairs.map(({ target, scope }) => target.detect(rt, scope, probe)),
|
|
110
116
|
);
|
|
111
117
|
return pairs.map(({ target, scope }, index) => {
|
|
112
118
|
const outcome = settled[index] as PromiseSettledResult<unknown>;
|
|
@@ -26,6 +26,11 @@
|
|
|
26
26
|
* path — a consumer that routes it through this barrel statically would undo
|
|
27
27
|
* that, and the setup guard test would say so.
|
|
28
28
|
*
|
|
29
|
+
* Every line here has an importer that goes THROUGH it. The modules also
|
|
30
|
+
* import each other directly (`targets.ts` reaches `setupLsp.js`, and its
|
|
31
|
+
* siblings reach `writability.js`), and re-exporting those edges as well would
|
|
32
|
+
* make this file a second, wider surface that nothing asked for.
|
|
33
|
+
*
|
|
29
34
|
* Deliberately internal: the config seed literal, the per-target message
|
|
30
35
|
* builders, and the MCP entry writer. They are inputs to the composers above,
|
|
31
36
|
* not operations a caller performs.
|
|
@@ -49,8 +54,12 @@ export type { LspDetection } from "./setupLsp.js";
|
|
|
49
54
|
export {
|
|
50
55
|
composeLsp,
|
|
51
56
|
composeLspRemoval,
|
|
57
|
+
describeEditorSource,
|
|
52
58
|
detectLsp,
|
|
59
|
+
firstLspBlock,
|
|
60
|
+
installableEditors,
|
|
53
61
|
LSP_SKIP_REMEDY,
|
|
62
|
+
lspBlockReason,
|
|
54
63
|
lspEditorNames,
|
|
55
64
|
lspSkipReason,
|
|
56
65
|
lspUninstallRemedy,
|
|
@@ -58,9 +67,13 @@ export {
|
|
|
58
67
|
} from "./setupLsp.js";
|
|
59
68
|
export type { McpDetection } from "./setupMcp.js";
|
|
60
69
|
export {
|
|
70
|
+
blockedMcpGroups,
|
|
61
71
|
composeMcp,
|
|
62
72
|
composeMcpRemoval,
|
|
63
73
|
detectMcp,
|
|
74
|
+
firstMcpBlock,
|
|
75
|
+
mcpBlockReason,
|
|
76
|
+
mcpGroupBlock,
|
|
64
77
|
mcpGroupState,
|
|
65
78
|
mcpWriteState,
|
|
66
79
|
ownedMcpGroups,
|
|
@@ -77,3 +90,4 @@ export {
|
|
|
77
90
|
staleSkillLinks,
|
|
78
91
|
withinRoot,
|
|
79
92
|
} from "./setupSkills.js";
|
|
93
|
+
export type { FsProbe } from "./writability.js";
|
|
@@ -204,11 +204,18 @@ export function composeCompletions(d: CompletionsDetection): Task<void> {
|
|
|
204
204
|
* machine that never installed one reverses zero steps and says so.
|
|
205
205
|
*
|
|
206
206
|
* @param d - The detection gathered up front.
|
|
207
|
+
* @param undoKey - Correlation key stamped on the reversal(s), echoed on
|
|
208
|
+
* their undo outcomes so the caller can read results back per row.
|
|
207
209
|
* @returns A Task whose undo deletes the installed script.
|
|
208
210
|
*/
|
|
209
|
-
export function composeCompletionsRemoval(
|
|
211
|
+
export function composeCompletionsRemoval(
|
|
212
|
+
d: CompletionsDetection,
|
|
213
|
+
undoKey?: string,
|
|
214
|
+
): Task<void> {
|
|
210
215
|
if (d.path === null || d.script === null || d.state === "absent") {
|
|
211
216
|
return sequence_([]);
|
|
212
217
|
}
|
|
213
|
-
return sequence_([
|
|
218
|
+
return sequence_([
|
|
219
|
+
writeFile(d.path, d.script, { undo: deleteFile(d.path), undoKey }),
|
|
220
|
+
]);
|
|
214
221
|
}
|
|
@@ -110,12 +110,17 @@ export function composeConfigFile(d: ConfigDetection): Task<void> {
|
|
|
110
110
|
* delete as its `undo`, which is what the undo interpreter executes.
|
|
111
111
|
*
|
|
112
112
|
* @param d - The detection gathered up front.
|
|
113
|
+
* @param undoKey - Correlation key stamped on the reversal(s), echoed on
|
|
114
|
+
* their undo outcomes so the caller can read results back per row.
|
|
113
115
|
* @returns A Task whose undo removes a seed-only config, or an empty Task.
|
|
114
116
|
*/
|
|
115
|
-
export function composeConfigRemoval(
|
|
117
|
+
export function composeConfigRemoval(
|
|
118
|
+
d: ConfigDetection,
|
|
119
|
+
undoKey?: string,
|
|
120
|
+
): Task<void> {
|
|
116
121
|
if (!d.exists || !d.isSeed) return sequence_([]);
|
|
117
122
|
return sequence_([
|
|
118
|
-
writeFile(d.path, SEED_CONFIG, { undo: deleteFile(d.path) }),
|
|
123
|
+
writeFile(d.path, SEED_CONFIG, { undo: deleteFile(d.path), undoKey }),
|
|
119
124
|
]);
|
|
120
125
|
}
|
|
121
126
|
|