@canonical/pragma-cli 0.32.0 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -0
- package/dist/pragma +0 -0
- package/package.json +14 -13
- package/src/bin.ts +2 -2
- package/src/capabilities/block/blockList.test.ts +79 -0
- package/src/capabilities/block/blockList.verb.ts +18 -5
- package/src/capabilities/block/pack.ts +12 -1
- package/src/capabilities/block/parity.test.ts +9 -2
- package/src/capabilities/capabilities/capabilities.test.ts +10 -1
- package/src/capabilities/capabilities/capabilities.verb.ts +1 -1
- package/src/capabilities/capabilities/catalog.ts +7 -3
- package/src/capabilities/capabilities/hints.ts +8 -17
- package/src/capabilities/capabilities/types.ts +2 -0
- package/src/capabilities/colophon/colophon.render.test.ts +31 -4
- package/src/capabilities/colophon/colophon.render.ts +13 -4
- package/src/capabilities/colophon/colophon.test.ts +3 -3
- package/src/capabilities/colophon/colophon.verb.ts +4 -1
- package/src/capabilities/colophon/markdownTerminal.ts +33 -14
- package/src/capabilities/colophon/pragmaColophon.ts +4 -4
- package/src/capabilities/colophon/types.ts +1 -1
- package/src/capabilities/config/fields.ts +21 -92
- package/src/capabilities/config/set.test.ts +6 -6
- package/src/capabilities/config/set.verb.ts +5 -5
- package/src/capabilities/config/show.render.test.ts +65 -0
- package/src/capabilities/config/show.render.ts +56 -10
- package/src/capabilities/config/show.verb.ts +5 -5
- package/src/capabilities/create/byteEquality.subprocess.test.ts +1 -1
- package/src/capabilities/create/byteEquality.test.ts +7 -4
- package/src/capabilities/create/compiledCreate.subprocess.test.ts +1 -1
- package/src/capabilities/create/create.test.ts +32 -3
- package/src/capabilities/create/create.verb.ts +69 -11
- package/src/capabilities/doctor/checks/checkConfigFile.ts +1 -1
- package/src/capabilities/doctor/checks/checkMcpCommands.ts +6 -0
- package/src/capabilities/doctor/checks/checkMcpConfigured.ts +10 -4
- package/src/capabilities/doctor/checks/deriveBand.test.ts +42 -0
- package/src/capabilities/doctor/checks/deriveBand.ts +28 -0
- package/src/capabilities/doctor/doctor.render.test.ts +170 -0
- package/src/capabilities/doctor/doctor.render.ts +116 -34
- package/src/capabilities/doctor/doctor.test.ts +31 -1
- package/src/capabilities/doctor/runChecks.ts +28 -4
- package/src/capabilities/doctor/types.ts +15 -0
- package/src/capabilities/ontology/index.ts +10 -3
- package/src/capabilities/ontology/ontology.test.ts +157 -5
- package/src/capabilities/ontology/verbs.ts +192 -93
- package/src/capabilities/prompt/prompt.render.test.ts +94 -0
- package/src/capabilities/prompt/prompt.render.ts +23 -6
- package/src/capabilities/prompt/prompt.test.ts +61 -0
- package/src/capabilities/reference.test.ts +32 -0
- package/src/capabilities/resources/provider.ts +28 -14
- package/src/capabilities/resources/resources.test.ts +99 -0
- package/src/capabilities/scopeTypeSync.test.ts +37 -0
- package/src/capabilities/setup/index.ts +7 -1
- package/src/capabilities/setup/operations/setupCompletions.ts +49 -16
- package/src/capabilities/setup/operations/setupGenerator.ts +157 -61
- package/src/capabilities/setup/operations/setupLsp.ts +56 -12
- package/src/capabilities/setup/operations/setupMcp.ts +216 -39
- package/src/capabilities/setup/operations/setupSkills.ts +7 -1
- package/src/capabilities/setup/setup.render.test.ts +109 -0
- package/src/capabilities/setup/setup.render.ts +61 -14
- package/src/capabilities/setup/setup.test.ts +352 -1
- package/src/capabilities/setup/setup.verb.ts +57 -5
- package/src/capabilities/setup/types.ts +59 -2
- package/src/capabilities/shared/bands.ts +16 -0
- package/src/capabilities/sources/collectStatus.ts +19 -4
- package/src/capabilities/sources/runUpdate.ts +58 -1
- package/src/capabilities/sources/sources.test.ts +187 -1
- package/src/capabilities/sources/status.render.test.ts +65 -0
- package/src/capabilities/sources/status.render.ts +50 -23
- package/src/capabilities/surface.test.ts +34 -9
- package/src/capabilities/tier/lookup.render.test.ts +72 -0
- package/src/capabilities/tier/lookup.render.ts +33 -8
- package/src/capabilities/tier/lookup.test.ts +15 -0
- package/src/capabilities/tier/runLookup.ts +28 -0
- package/src/constants.test.ts +1 -1
- package/src/constants.ts +2 -2
- package/src/kernel/completion/__snapshots__/emitScripts.test.ts.snap +13 -14
- package/src/kernel/completion/bashDrive.test.ts +3 -3
- package/src/kernel/completion/complete.test.ts +3 -5
- package/src/kernel/completion/entitySource.ts +14 -6
- package/src/kernel/completion/model.test.ts +2 -2
- package/src/kernel/completion/model.ts +0 -1
- package/src/kernel/completion/parse.test.ts +2 -4
- package/src/kernel/error/PragmaError.test.ts +4 -4
- package/src/kernel/error/renderError.ts +1 -1
- package/src/kernel/packs/compile.ts +1 -1
- package/src/kernel/project/cli/buildProgram.test.ts +51 -7
- package/src/kernel/project/cli/buildProgram.ts +11 -0
- package/src/kernel/project/cli/globalFlags.test.ts +12 -4
- package/src/kernel/project/cli/globalFlags.ts +14 -14
- package/src/kernel/project/cli/rootHelp.ts +4 -2
- package/src/kernel/project/mcp/instructions.ts +1 -1
- package/src/kernel/project/mcp/mcpError.ts +52 -0
- package/src/kernel/project/mcp/prompts/prompts.test.ts +8 -0
- package/src/kernel/project/mcp/prompts/provider.ts +36 -6
- package/src/kernel/render/formatters.ts +5 -4
- package/src/kernel/render/renderers.test.ts +34 -0
- package/src/kernel/render/renderers.ts +25 -7
- package/src/kernel/render/style.test.ts +31 -0
- package/src/kernel/render/style.ts +74 -0
- package/src/kernel/runtime/graphpack/build.ts +28 -0
- package/src/kernel/runtime/graphpack/buildIndex.test.ts +103 -0
- package/src/kernel/runtime/graphpack/buildIndex.ts +124 -36
- package/src/kernel/runtime/graphpack/embedded/pack.generated.ts +3 -6
- package/src/kernel/runtime/graphpack/embedded/pack.index.generated.ts +1 -2
- package/src/kernel/runtime/graphpack/graphpack.test.ts +69 -1
- package/src/kernel/runtime/graphpack/read.ts +28 -13
- package/src/kernel/runtime/graphpack/types.ts +14 -0
- package/src/kernel/runtime/loadSession.ts +4 -12
- package/src/kernel/runtime/refs/resolve.ts +63 -3
- package/src/kernel/runtime/storeReadiness.ts +61 -0
- package/src/kernel/runtime/types.ts +1 -1
- package/src/kernel/runtime/wasmEmbed.test.ts +3 -1
- package/src/kernel/spec/emitReference.test.ts +196 -0
- package/src/kernel/spec/emitReference.ts +491 -0
- package/src/kernel/spec/emitSurface.ts +4 -2
- package/src/kernel/spec/index.ts +2 -0
- package/src/kernel/spec/surfaceConformance.test.ts +2 -2
- package/src/testing/PARITY_GAPS.ts +1 -1
- package/src/testing/behavioral/autoLlm.e2e.test.ts +12 -2
- package/src/testing/behavioral/completion.test.ts +2 -2
- package/src/testing/behavioral/disclosure.test.ts +12 -0
- package/src/testing/behavioral/docExamples.test.ts +283 -0
- package/src/testing/behavioral/errorMatrix.storeless.test.ts +2 -1
- package/src/testing/behavioral/journeys.cli.test.ts +3 -1
- package/src/testing/behavioral/journeys.defaultPack.test.ts +571 -0
- package/src/testing/behavioral/journeys.interactiveTty.e2e.test.ts +66 -0
- package/src/testing/behavioral/journeys.livePack.e2e.test.ts +129 -0
- package/src/testing/behavioral/journeys.mcpLifecycle.mcp.test.ts +176 -0
- package/src/testing/behavioral/mcpSurface.test.ts +1 -1
- package/src/testing/behavioral/staleCommands.test.ts +76 -0
- package/src/testing/eval/__snapshots__/eval.test.ts.snap +2 -2
- package/src/testing/eval/cases/assembly.ts +3 -3
- package/src/testing/eval/cases/effectful.ts +6 -3
- package/src/testing/eval/cases/readNouns.ts +4 -1
- package/src/testing/fixtures/fixtureCapability.ts +156 -0
- package/src/testing/fixtures/graph/defaultPack.ts +178 -0
- package/src/testing/fixtures/graph/owlExport.ts +95 -0
- package/src/testing/helpers/extractSnippets.ts +29 -0
- package/src/testing/helpers/golden.ts +1 -1
- package/src/capabilities/config/field.test.ts +0 -188
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The hermetic, vendored "default-pack" fixture — a SMALL but REAL multi-typed
|
|
3
|
+
* design-system pack, shaped the way a shipped default pack actually is, not the
|
|
4
|
+
* way the 6-entity single-typed canonical graph was.
|
|
5
|
+
*
|
|
6
|
+
* WHY A SECOND GRAPH (the R1 "one graph" discipline notwithstanding): the
|
|
7
|
+
* canonical fixture (`canonical.ts`) is deliberately clean — every individual is
|
|
8
|
+
* single-typed (`a ds:Component`), every block carries a tier, every label is a
|
|
9
|
+
* bare string. That cleanliness is exactly what masked the real-data counter and
|
|
10
|
+
* classifier bugs the journey harness (AV-231, Backlog E) exists to catch. This
|
|
11
|
+
* fixture is the ADVERSARIAL default pack: it reproduces the four shapes a real
|
|
12
|
+
* vendored pack has that the clean graph does not —
|
|
13
|
+
*
|
|
14
|
+
* 1. `owl:NamedIndividual` co-typing — every block is
|
|
15
|
+
* `a owl:NamedIndividual, ds:<Class>`, the way exported OWL individuals are.
|
|
16
|
+
* This is what doubles the per-type instance tally (A1 `entityTotal` 2×): a
|
|
17
|
+
* block counts once under `owl:NamedIndividual` AND once under its domain
|
|
18
|
+
* class.
|
|
19
|
+
* 2. Two+ domain classes (`ds:Component` and `ds:Pattern`) so a read that keys
|
|
20
|
+
* off the primary domain type is exercised across more than one class.
|
|
21
|
+
* 3. An UNTIERED block (`ds:orphanWidget` — a `ds:Component` with a `ds:name`
|
|
22
|
+
* but NO `ds:tier`). `block list`'s SELECT inner-joins `?c ds:tier ?t`, so an
|
|
23
|
+
* untiered block is silently dropped from every listing (A2), even though
|
|
24
|
+
* `graph query` finds it — the fixture pins both halves of that divergence.
|
|
25
|
+
* 4. A multilingual `rdfs:label "Button"@en, "Bouton"@fr` — two same-predicate
|
|
26
|
+
* labels whose winner in the single-valued index is store-order-arbitrary
|
|
27
|
+
* (A7), a shape a bare-string fixture can never produce.
|
|
28
|
+
*
|
|
29
|
+
* The vocabulary is the live `ds:` design-system namespace (same as
|
|
30
|
+
* `blockGraph.ts`/`canonical.ts`), so `block list`, `ontology list/show`, and
|
|
31
|
+
* `graph query` all run against real terms — the hand-written `block list` verb
|
|
32
|
+
* hard-codes `ds:Component`/`ds:name`/`ds:tier`/`ds:release`.
|
|
33
|
+
*
|
|
34
|
+
* Stable anchors this fixture guarantees (pinned by the journey suite):
|
|
35
|
+
* - Exactly 4 blocks in the graph: Button (`ds:Component`, global), Card
|
|
36
|
+
* (`ds:Pattern`, global), Beta Badge (`ds:Component`, apps tier, beta
|
|
37
|
+
* channel), Orphan Widget (`ds:Component`, UNTIERED).
|
|
38
|
+
* - 5 TBox classes, 4 TBox properties, 2 tiers (`global`, `apps`), 2 release
|
|
39
|
+
* channels (`stable`, `beta`).
|
|
40
|
+
* - `ds:button` carries the multilingual label; every other subject is a bare
|
|
41
|
+
* string, so the multilingual path is isolated to one entity.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/** The prefixes the default-pack fixture store is built and queried with. */
|
|
45
|
+
export const DEFAULT_PACK_PREFIXES: Readonly<Record<string, string>> = {
|
|
46
|
+
ds: "https://ds.canonical.com/",
|
|
47
|
+
owl: "http://www.w3.org/2002/07/owl#",
|
|
48
|
+
rdfs: "http://www.w3.org/2000/01/rdf-schema#",
|
|
49
|
+
xsd: "http://www.w3.org/2001/XMLSchema#",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The vendored default pack as one Turtle document: TBox (5 classes, 4
|
|
54
|
+
* properties) + ABox (2 tiers, 2 channels, 4 blocks). Every block is co-typed
|
|
55
|
+
* `owl:NamedIndividual` — the real-export shape the clean canonical graph omits.
|
|
56
|
+
*/
|
|
57
|
+
export const DEFAULT_PACK_TTL = `
|
|
58
|
+
@prefix ds: <https://ds.canonical.com/> .
|
|
59
|
+
@prefix owl: <http://www.w3.org/2002/07/owl#> .
|
|
60
|
+
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
|
61
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
62
|
+
|
|
63
|
+
# ---- Ontology (TBox): 5 classes, 4 properties ----
|
|
64
|
+
ds:UIBlock a owl:Class ; rdfs:label "UI Block" .
|
|
65
|
+
ds:Component a owl:Class ; rdfs:subClassOf ds:UIBlock ; rdfs:label "Component" .
|
|
66
|
+
ds:Pattern a owl:Class ; rdfs:subClassOf ds:UIBlock ; rdfs:label "Pattern" .
|
|
67
|
+
ds:Tier a owl:Class ; rdfs:label "Tier" .
|
|
68
|
+
ds:ReleaseChannel a owl:Class ; rdfs:label "Release Channel" .
|
|
69
|
+
|
|
70
|
+
ds:name a owl:DatatypeProperty ; rdfs:domain ds:UIBlock ; rdfs:range xsd:string .
|
|
71
|
+
# Functional (one tier per block), matching the live ontology — a non-functional
|
|
72
|
+
# ds:tier compiles to a multi-valued GraphQL field the bundled block lookup rejects.
|
|
73
|
+
ds:tier a owl:ObjectProperty, owl:FunctionalProperty ; rdfs:domain ds:UIBlock ; rdfs:range ds:Tier .
|
|
74
|
+
ds:release a owl:ObjectProperty, owl:FunctionalProperty ; rdfs:domain ds:UIBlock ; rdfs:range ds:ReleaseChannel .
|
|
75
|
+
ds:summary a owl:DatatypeProperty ; rdfs:domain ds:UIBlock ; rdfs:range xsd:string .
|
|
76
|
+
|
|
77
|
+
# ---- Tiers (a real parent chain: apps -> global) ----
|
|
78
|
+
ds:global a ds:Tier ; ds:name "global" .
|
|
79
|
+
ds:apps a ds:Tier ; ds:name "apps" .
|
|
80
|
+
|
|
81
|
+
# ---- Release channels ----
|
|
82
|
+
ds:stable a ds:ReleaseChannel ; ds:name "stable" .
|
|
83
|
+
ds:beta a ds:ReleaseChannel ; ds:name "beta" .
|
|
84
|
+
|
|
85
|
+
# ---- Blocks (ABox) — every one co-typed owl:NamedIndividual (the export shape) ----
|
|
86
|
+
|
|
87
|
+
# A component on the global tier, carrying a MULTILINGUAL label (A7 shape).
|
|
88
|
+
ds:button a owl:NamedIndividual, ds:Component ;
|
|
89
|
+
ds:name "Button" ;
|
|
90
|
+
rdfs:label "Button"@en, "Bouton"@fr ;
|
|
91
|
+
ds:tier ds:global ;
|
|
92
|
+
ds:summary "A clickable button control." .
|
|
93
|
+
|
|
94
|
+
# A second DOMAIN CLASS (ds:Pattern), on the global tier.
|
|
95
|
+
ds:card a owl:NamedIndividual, ds:Pattern ;
|
|
96
|
+
ds:name "Card" ;
|
|
97
|
+
rdfs:label "Card" ;
|
|
98
|
+
ds:tier ds:global ;
|
|
99
|
+
ds:summary "A content card layout pattern." .
|
|
100
|
+
|
|
101
|
+
# A component gated to the BETA channel and the non-global apps tier — exercises
|
|
102
|
+
# both channel visibility and tier-chain inheritance.
|
|
103
|
+
ds:betaBadge a owl:NamedIndividual, ds:Component ;
|
|
104
|
+
ds:name "Beta Badge" ;
|
|
105
|
+
rdfs:label "Beta Badge" ;
|
|
106
|
+
ds:tier ds:apps ;
|
|
107
|
+
ds:release ds:beta ;
|
|
108
|
+
ds:summary "An experimental badge, gated to the prerelease channel." .
|
|
109
|
+
|
|
110
|
+
# An UNTIERED component — a real ds:Component with a ds:name but NO ds:tier.
|
|
111
|
+
# It IS in the graph (graph query finds it) but block list's mandatory
|
|
112
|
+
# tier join (?c ds:tier ?t) drops it (A2).
|
|
113
|
+
ds:orphanWidget a owl:NamedIndividual, ds:Component ;
|
|
114
|
+
ds:name "Orphan Widget" ;
|
|
115
|
+
rdfs:label "Orphan Widget" ;
|
|
116
|
+
ds:summary "A component shipped without a tier assignment." .
|
|
117
|
+
`;
|
|
118
|
+
|
|
119
|
+
/** No-tier viewing config, `normal` channel — hides the beta-only Beta Badge. */
|
|
120
|
+
export const DEFAULT_PACK_CONFIG = { channel: "normal" as const };
|
|
121
|
+
|
|
122
|
+
/** `prerelease` channel, no tier — the config where every channel-gated block shows. */
|
|
123
|
+
export const DEFAULT_PACK_ALL_VISIBLE_CONFIG = {
|
|
124
|
+
channel: "prerelease" as const,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/** The names of the 4 blocks the fixture declares, sorted (SPARQL `ORDER BY ?name`). */
|
|
128
|
+
export const DEFAULT_PACK_BLOCK_NAMES = [
|
|
129
|
+
"Beta Badge",
|
|
130
|
+
"Button",
|
|
131
|
+
"Card",
|
|
132
|
+
"Orphan Widget",
|
|
133
|
+
] as const;
|
|
134
|
+
|
|
135
|
+
/** The block excluded from `block list` for want of a `ds:tier` (A2 anchor). */
|
|
136
|
+
export const UNTIERED_BLOCK_NAME = "Orphan Widget";
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* A second, ontology-only pack with NO tiered blocks — drives the EMPTY
|
|
140
|
+
* `block list` (a calm exit-0 empty result, not an error). It still declares the
|
|
141
|
+
* `ds:` TBox, so `ontology list` is POPULATED here (the populated/empty pair for
|
|
142
|
+
* the two list verbs is split across this and {@link INSTANCE_ONLY_TTL}).
|
|
143
|
+
*/
|
|
144
|
+
export const NO_BLOCKS_TTL = `
|
|
145
|
+
@prefix ds: <https://ds.canonical.com/> .
|
|
146
|
+
@prefix owl: <http://www.w3.org/2002/07/owl#> .
|
|
147
|
+
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
|
148
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
149
|
+
|
|
150
|
+
ds:UIBlock a owl:Class .
|
|
151
|
+
ds:Component a owl:Class ; rdfs:subClassOf ds:UIBlock .
|
|
152
|
+
ds:Tier a owl:Class .
|
|
153
|
+
ds:name a owl:DatatypeProperty ; rdfs:domain ds:UIBlock ; rdfs:range xsd:string .
|
|
154
|
+
ds:tier a owl:ObjectProperty ; rdfs:domain ds:UIBlock ; rdfs:range ds:Tier .
|
|
155
|
+
|
|
156
|
+
ds:global a ds:Tier ; ds:name "global" .
|
|
157
|
+
`;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* A pack of pure instance data with NO `owl:Class`/`owl:*Property` declarations —
|
|
161
|
+
* drives the EMPTY `ontology list` (no namespace has any classes or properties
|
|
162
|
+
* to group), while `graph query` still resolves its triples.
|
|
163
|
+
*/
|
|
164
|
+
export const INSTANCE_ONLY_TTL = `
|
|
165
|
+
@prefix ex: <https://ex.test/#> .
|
|
166
|
+
ex:alpha ex:relatesTo ex:beta .
|
|
167
|
+
ex:beta ex:name "Beta" .
|
|
168
|
+
`;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* A malformed Turtle source — a subject/predicate with no object — drives the
|
|
172
|
+
* `sources update` data-error classification path (a NAMED `CONFIG_ERROR`, not
|
|
173
|
+
* an INTERNAL "please report this issue").
|
|
174
|
+
*/
|
|
175
|
+
export const MALFORMED_TTL = `@prefix ex: <https://ex.test/#> .
|
|
176
|
+
ex:One a ex:Widget .
|
|
177
|
+
ex:Two ex:danglingPredicate .
|
|
178
|
+
`;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A realistic multi-typed OWL / Protégé-export fixture graph.
|
|
3
|
+
*
|
|
4
|
+
* The embedded `sample.ttl` (Button/Card/Dialog, each typed by exactly ONE
|
|
5
|
+
* domain class, every block tiered, every label untagged) is a TOY: it never
|
|
6
|
+
* exercises the counters/classifiers the way a real vocabulary does. This graph
|
|
7
|
+
* deliberately carries the shapes that only bite real packs (audit backlog A):
|
|
8
|
+
*
|
|
9
|
+
* - Protégé DUAL-typing: an individual asserted as its domain class AND
|
|
10
|
+
* `owl:NamedIndividual` — the multiset that made `entityTotal` run ~2× (A1).
|
|
11
|
+
* - A MULTI-DOMAIN individual (`ex:toggle`: two domain classes) — so the
|
|
12
|
+
* primary-type tie-break must be deterministic, not store-order (A3).
|
|
13
|
+
* - An UNTIERED block (`ds:datePicker`: a `ds:Component` with no `ds:tier`) —
|
|
14
|
+
* which `block list --all-tiers` must surface, not hide (A2).
|
|
15
|
+
* - A blank-node `rdf:type` (`ex:field`'s anonymous SHACL-style class) — which
|
|
16
|
+
* must NOT become a `type:"_:b0"` primary key (A6).
|
|
17
|
+
* - An OWL-PUNNED subject (`ex:Slider`: a class that is ALSO a metaclass
|
|
18
|
+
* individual) — whose abox facet must survive alongside its tbox one (A8).
|
|
19
|
+
* - MULTILINGUAL labels (`"Button"@en` / `"Bouton"@fr`) — resolved to a stable
|
|
20
|
+
* language, never whichever row the store returned first (A7).
|
|
21
|
+
*
|
|
22
|
+
* The `ds:` half is a compact but faithful design-system slice (so `block list`
|
|
23
|
+
* runs against it); the `ex:` half is generic OWL, proving the index is
|
|
24
|
+
* ontology-agnostic. One document, so a single store answers every read.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** The prefixes the OWL-export fixture store is built and queried with. */
|
|
28
|
+
export const OWL_EXPORT_PREFIXES: Readonly<Record<string, string>> = {
|
|
29
|
+
ds: "https://ds.canonical.com/",
|
|
30
|
+
ex: "https://ex.test/vocab#",
|
|
31
|
+
rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
|
|
32
|
+
rdfs: "http://www.w3.org/2000/01/rdf-schema#",
|
|
33
|
+
owl: "http://www.w3.org/2002/07/owl#",
|
|
34
|
+
xsd: "http://www.w3.org/2001/XMLSchema#",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** The multi-typed OWL-export fixture ontology + individuals as Turtle. */
|
|
38
|
+
export const OWL_EXPORT_TTL = `
|
|
39
|
+
@prefix ds: <https://ds.canonical.com/> .
|
|
40
|
+
@prefix ex: <https://ex.test/vocab#> .
|
|
41
|
+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
|
42
|
+
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
|
43
|
+
@prefix owl: <http://www.w3.org/2002/07/owl#> .
|
|
44
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
45
|
+
|
|
46
|
+
# ---- Design-system TBox (so \`block list\` has real block types) ----
|
|
47
|
+
ds:UIBlock a owl:Class .
|
|
48
|
+
ds:Component a owl:Class ; rdfs:subClassOf ds:UIBlock ; rdfs:label "Component" .
|
|
49
|
+
ds:Tier a owl:Class .
|
|
50
|
+
ds:name a owl:DatatypeProperty ; rdfs:domain ds:UIBlock ; rdfs:range xsd:string .
|
|
51
|
+
ds:tier a owl:ObjectProperty ; rdfs:domain ds:UIBlock ; rdfs:range ds:Tier .
|
|
52
|
+
ds:global a owl:NamedIndividual, ds:Tier ; ds:name "global" .
|
|
53
|
+
|
|
54
|
+
# ---- Generic OWL domain classes (multi-typing lives here) ----
|
|
55
|
+
ex:Interactive a owl:Class ; rdfs:label "Interactive" .
|
|
56
|
+
ex:FormControl a owl:Class ; rdfs:label "Form Control" .
|
|
57
|
+
ex:Category a owl:Class ; rdfs:label "Category" .
|
|
58
|
+
|
|
59
|
+
# ---- A tiered block, Protégé-style DUAL-typed (domain class + owl:NamedIndividual),
|
|
60
|
+
# with a multilingual label (A1 + A7). ----
|
|
61
|
+
ds:button a owl:NamedIndividual, ds:Component ;
|
|
62
|
+
ds:name "Button" ;
|
|
63
|
+
ds:tier ds:global ;
|
|
64
|
+
rdfs:label "Button"@en , "Bouton"@fr .
|
|
65
|
+
|
|
66
|
+
# ---- An UNTIERED block: a ds:Component with NO ds:tier (A2). ----
|
|
67
|
+
ds:datePicker a owl:NamedIndividual, ds:Component ;
|
|
68
|
+
ds:name "Date Picker" ;
|
|
69
|
+
rdfs:label "Date Picker"@en , "Sélecteur de date"@fr .
|
|
70
|
+
|
|
71
|
+
# ---- A MULTI-DOMAIN individual: two domain classes + owl:NamedIndividual (A1 + A3).
|
|
72
|
+
# The abox primary must be the lexically-smallest: ex:FormControl < ex:Interactive. ----
|
|
73
|
+
ex:toggle a owl:NamedIndividual, ex:Interactive, ex:FormControl ;
|
|
74
|
+
rdfs:label "Toggle"@en , "Bascule"@fr .
|
|
75
|
+
|
|
76
|
+
# ---- An OWL-PUNNED subject: a class that is ALSO a metaclass individual (A8).
|
|
77
|
+
# Must emit BOTH a tbox facet (owl:Class) and an abox facet (ex:Category). ----
|
|
78
|
+
ex:Slider a owl:Class, owl:NamedIndividual, ex:Category ;
|
|
79
|
+
rdfs:label "Slider" .
|
|
80
|
+
|
|
81
|
+
# ---- A subject with a BLANK-NODE rdf:type: an anonymous class (A6).
|
|
82
|
+
# The blank node must NOT become ex:field's primary type. ----
|
|
83
|
+
ex:field a owl:NamedIndividual, ex:FormControl, [ a owl:Class ] ;
|
|
84
|
+
rdfs:label "Field" .
|
|
85
|
+
`;
|
|
86
|
+
|
|
87
|
+
/** The distinct abox individuals this fixture declares (the `entityTotal` oracle). */
|
|
88
|
+
export const OWL_EXPORT_ABOX_SUBJECTS: readonly string[] = [
|
|
89
|
+
"ds:button",
|
|
90
|
+
"ds:datePicker",
|
|
91
|
+
"ds:global",
|
|
92
|
+
"ex:Slider",
|
|
93
|
+
"ex:field",
|
|
94
|
+
"ex:toggle",
|
|
95
|
+
];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pull the runnable `pragma` command lines out of a Markdown document.
|
|
3
|
+
*
|
|
4
|
+
* Scans fenced `bash` / `sh` / `console` blocks, strips a leading shell prompt
|
|
5
|
+
* (`$ `), and keeps the lines that invoke `pragma`. Used by the doc-example
|
|
6
|
+
* tests to prove every command shown in the docs is real grammar and — for the
|
|
7
|
+
* curated read set — actually runs.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Fenced `bash`/`sh`/`console` blocks, capturing the body between the fences. */
|
|
11
|
+
const FENCE_RE = /```(?:bash|sh|console)\r?\n(?<body>[\s\S]*?)```/g;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Extract every `pragma …` command line from a Markdown string.
|
|
15
|
+
*
|
|
16
|
+
* @param markdown - The Markdown source to scan.
|
|
17
|
+
* @returns The `pragma` command lines, in document order, prompt-stripped.
|
|
18
|
+
*/
|
|
19
|
+
export function extractPragmaCommands(markdown: string): string[] {
|
|
20
|
+
const commands: string[] = [];
|
|
21
|
+
for (const match of markdown.matchAll(FENCE_RE)) {
|
|
22
|
+
const body = match.groups?.body ?? "";
|
|
23
|
+
for (const rawLine of body.split("\n")) {
|
|
24
|
+
const line = rawLine.replace(/^\s*\$\s+/, "").trim();
|
|
25
|
+
if (line.startsWith("pragma ")) commands.push(line);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return commands;
|
|
29
|
+
}
|
|
@@ -14,7 +14,7 @@ import { fileURLToPath } from "node:url";
|
|
|
14
14
|
// biome-ignore lint/suspicious/noControlCharactersInRegex: matching the literal ESC byte is the point
|
|
15
15
|
const ANSI_PATTERN = /\x1b\[[0-9;]*m/g;
|
|
16
16
|
|
|
17
|
-
/** The package repo root (`packages/cli/
|
|
17
|
+
/** The package repo root (`packages/cli/pragma`), for tokenizing absolute paths. */
|
|
18
18
|
const REPO_ROOT = fileURLToPath(new URL("../../../", import.meta.url)).replace(
|
|
19
19
|
/\/$/,
|
|
20
20
|
"",
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `config tier` / `channel` / `detail` — storeless, covenant-exact setters.
|
|
3
|
-
*
|
|
4
|
-
* Pins the emitted shape against the golden slice, exercises set / reset-sentinel
|
|
5
|
-
* / enum-reject / `--dry-run` plan / MCP plan-first-vs-confirm, and holds the
|
|
6
|
-
* storeless invariant (a config write never boots the store).
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
10
|
-
import { tmpdir } from "node:os";
|
|
11
|
-
import { join } from "node:path";
|
|
12
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
13
|
-
import { globalConfigPath } from "../../kernel/config/paths.js";
|
|
14
|
-
import { CHANNELS } from "../../kernel/config/types.js";
|
|
15
|
-
import { PragmaError } from "../../kernel/error/PragmaError.js";
|
|
16
|
-
import {
|
|
17
|
-
executeVerb,
|
|
18
|
-
extractParams,
|
|
19
|
-
} from "../../kernel/project/cli/dispatch.js";
|
|
20
|
-
import { bootRuntime } from "../../kernel/runtime/boot.js";
|
|
21
|
-
import type { GlobalFlags, PragmaRuntime } from "../../kernel/runtime/types.js";
|
|
22
|
-
import { emitVerb } from "../../kernel/spec/emitSurface.js";
|
|
23
|
-
import type { VerbSpec } from "../../kernel/spec/types.js";
|
|
24
|
-
import { projectMcp } from "../../testing/helpers/projectMcp.js";
|
|
25
|
-
import { CONFIG_FIELDS } from "./fields.js";
|
|
26
|
-
import { configModule } from "./index.js";
|
|
27
|
-
import { runField } from "./runField.js";
|
|
28
|
-
|
|
29
|
-
const FLAGS: GlobalFlags = {
|
|
30
|
-
llm: false,
|
|
31
|
-
autoLlm: false,
|
|
32
|
-
format: "plain",
|
|
33
|
-
verbose: false,
|
|
34
|
-
};
|
|
35
|
-
const REAL = { dryRun: false, undo: false, yes: false };
|
|
36
|
-
const DRY = { dryRun: true, undo: false, yes: false };
|
|
37
|
-
|
|
38
|
-
const verbOf = (field: string): VerbSpec =>
|
|
39
|
-
configModule.verbs.find((v) => v.path[1] === field) as VerbSpec;
|
|
40
|
-
const tierVerb = verbOf("tier");
|
|
41
|
-
const channelVerb = verbOf("channel");
|
|
42
|
-
const detailVerb = verbOf("detail");
|
|
43
|
-
|
|
44
|
-
let prevXdg: string | undefined;
|
|
45
|
-
const roots: string[] = [];
|
|
46
|
-
const tmp = (prefix: string): string => {
|
|
47
|
-
const dir = mkdtempSync(join(tmpdir(), prefix));
|
|
48
|
-
roots.push(dir);
|
|
49
|
-
return dir;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
beforeEach(() => {
|
|
53
|
-
prevXdg = process.env.XDG_CONFIG_HOME;
|
|
54
|
-
process.env.XDG_CONFIG_HOME = tmp("pragma-cfgfield-xdg-");
|
|
55
|
-
});
|
|
56
|
-
afterEach(() => {
|
|
57
|
-
process.env.XDG_CONFIG_HOME = prevXdg;
|
|
58
|
-
for (const dir of roots) rmSync(dir, { recursive: true, force: true });
|
|
59
|
-
roots.length = 0;
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
function readGlobal(): Record<string, unknown> {
|
|
63
|
-
return JSON.parse(readFileSync(globalConfigPath(), "utf-8"));
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
describe("config setters — covenant-exact emission (PROTECTED)", () => {
|
|
67
|
-
it("emits the golden slice for each field", () => {
|
|
68
|
-
expect(emitVerb(tierVerb)).toEqual({
|
|
69
|
-
v: "tier",
|
|
70
|
-
args: ["<path>"],
|
|
71
|
-
mutates: true,
|
|
72
|
-
mcp: "config_tier",
|
|
73
|
-
});
|
|
74
|
-
expect(emitVerb(channelVerb)).toEqual({
|
|
75
|
-
v: "channel",
|
|
76
|
-
args: ["<name>"],
|
|
77
|
-
mutates: true,
|
|
78
|
-
mcp: "config_channel",
|
|
79
|
-
});
|
|
80
|
-
expect(emitVerb(detailVerb)).toEqual({
|
|
81
|
-
v: "detail",
|
|
82
|
-
args: ["<level>"],
|
|
83
|
-
mutates: true,
|
|
84
|
-
mcp: "config_detail",
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
describe("config setters — write & reset", () => {
|
|
90
|
-
it("set writes the field to the GLOBAL config and never boots the store", async () => {
|
|
91
|
-
const rt: PragmaRuntime = bootRuntime(FLAGS, tmp("pragma-proj-"));
|
|
92
|
-
const outcome = await executeVerb(tierVerb, { path: "apps/lxd" }, REAL, rt);
|
|
93
|
-
|
|
94
|
-
expect(outcome.exitCode).toBe(0);
|
|
95
|
-
expect(outcome.stdout).toContain("Set tier = apps/lxd");
|
|
96
|
-
expect(readGlobal().tier).toBe("apps/lxd");
|
|
97
|
-
// Storeless invariant: a config write never touches the store factory.
|
|
98
|
-
expect(rt.store.booted).toBe(false);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("channel/detail write their enum value", async () => {
|
|
102
|
-
const rt = bootRuntime(FLAGS, tmp("pragma-proj-"));
|
|
103
|
-
await executeVerb(channelVerb, { name: "experimental" }, REAL, rt);
|
|
104
|
-
await executeVerb(detailVerb, { level: "detailed" }, REAL, rt);
|
|
105
|
-
const written = readGlobal();
|
|
106
|
-
expect(written.channel).toBe("experimental");
|
|
107
|
-
expect(written.detail).toBe("detailed");
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it("a reset sentinel removes the tier field", async () => {
|
|
111
|
-
const cwd = tmp("pragma-proj-");
|
|
112
|
-
await executeVerb(
|
|
113
|
-
tierVerb,
|
|
114
|
-
{ path: "apps/lxd" },
|
|
115
|
-
REAL,
|
|
116
|
-
bootRuntime(FLAGS, cwd),
|
|
117
|
-
);
|
|
118
|
-
expect(readGlobal().tier).toBe("apps/lxd");
|
|
119
|
-
|
|
120
|
-
const outcome = await executeVerb(
|
|
121
|
-
tierVerb,
|
|
122
|
-
{ path: "none" },
|
|
123
|
-
REAL,
|
|
124
|
-
bootRuntime(FLAGS, cwd),
|
|
125
|
-
);
|
|
126
|
-
expect(outcome.stdout).toContain("Reset tier");
|
|
127
|
-
expect("tier" in readGlobal()).toBe(false);
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
describe("config setters — enum rejection", () => {
|
|
132
|
-
it("the CLI coerce layer rejects a non-member with INVALID_INPUT + validOptions", () => {
|
|
133
|
-
let caught: unknown;
|
|
134
|
-
try {
|
|
135
|
-
extractParams(channelVerb.params, ["bogus"], {});
|
|
136
|
-
} catch (error) {
|
|
137
|
-
caught = error;
|
|
138
|
-
}
|
|
139
|
-
expect(caught).toBeInstanceOf(PragmaError);
|
|
140
|
-
expect((caught as PragmaError).code).toBe("INVALID_INPUT");
|
|
141
|
-
expect((caught as PragmaError).validOptions).toEqual([...CHANNELS]);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it("runField backstops a bad enum value before it reaches disk", () => {
|
|
145
|
-
const channelSpec = CONFIG_FIELDS.find((s) => s.field === "channel");
|
|
146
|
-
expect(() =>
|
|
147
|
-
runField(channelSpec as (typeof CONFIG_FIELDS)[number], {
|
|
148
|
-
name: "bogus",
|
|
149
|
-
}),
|
|
150
|
-
).toThrow(/Invalid channel/);
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
describe("config setters — dry-run plan", () => {
|
|
155
|
-
it("--dry-run shows the WriteFile plan and writes nothing", async () => {
|
|
156
|
-
const rt = bootRuntime(FLAGS, tmp("pragma-proj-"));
|
|
157
|
-
const outcome = await executeVerb(tierVerb, { path: "apps/lxd" }, DRY, rt);
|
|
158
|
-
|
|
159
|
-
expect(outcome.exitCode).toBe(0);
|
|
160
|
-
expect(outcome.stdout).toContain("Write file");
|
|
161
|
-
expect(outcome.stdout).toContain(globalConfigPath());
|
|
162
|
-
expect(existsSync(globalConfigPath())).toBe(false);
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
describe("config setters — MCP plan-first / confirm parity", () => {
|
|
167
|
-
it("config_tier returns a plan without confirm, writes with confirm:true", async () => {
|
|
168
|
-
const cwd = tmp("pragma-proj-");
|
|
169
|
-
const mcp = await projectMcp([configModule], cwd);
|
|
170
|
-
|
|
171
|
-
const plan = await mcp.callTool("config_tier", { path: "apps/lxd" });
|
|
172
|
-
expect(plan.ok).toBe(true);
|
|
173
|
-
expect(plan.meta).toMatchObject({ planOnly: true, confirmRequired: true });
|
|
174
|
-
const planLines = (plan.data as { plan: string[] }).plan;
|
|
175
|
-
expect(planLines.some((line) => line.includes("Write file"))).toBe(true);
|
|
176
|
-
expect(existsSync(globalConfigPath())).toBe(false);
|
|
177
|
-
|
|
178
|
-
const done = await mcp.callTool("config_tier", {
|
|
179
|
-
path: "apps/lxd",
|
|
180
|
-
confirm: true,
|
|
181
|
-
});
|
|
182
|
-
await mcp.cleanup();
|
|
183
|
-
|
|
184
|
-
expect(done.ok).toBe(true);
|
|
185
|
-
expect(done.data).toMatchObject({ field: "tier", value: "apps/lxd" });
|
|
186
|
-
expect(readGlobal().tier).toBe("apps/lxd");
|
|
187
|
-
});
|
|
188
|
-
});
|