@diviops/mcp-server 1.5.14 → 1.5.17

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.
Files changed (36) hide show
  1. package/README.md +10 -1
  2. package/data/verified-attrs-backlog.json +5 -5
  3. package/data/verified-attrs.json +8 -8
  4. package/dist/compatibility.d.ts +25 -0
  5. package/dist/index.js +349 -4
  6. package/dist/preset-cli/button-emitter.d.ts +1 -1
  7. package/dist/preset-cli/button-emitter.js +1 -1
  8. package/dist/preset-cli/cli.d.ts +4 -3
  9. package/dist/preset-cli/cli.js +11 -10
  10. package/dist/preset-cli/heading-font-emitter.d.ts +1 -1
  11. package/dist/preset-cli/heading-font-emitter.js +3 -3
  12. package/dist/preset-cli/spacing-emitter.d.ts +13 -13
  13. package/dist/preset-cli/spacing-emitter.js +23 -23
  14. package/dist/preset-cli/write-path.d.ts +3 -3
  15. package/dist/preset-cli/write-path.js +3 -3
  16. package/dist/wp-cli-fs-validator.d.ts +1 -1
  17. package/dist/wp-cli-fs-validator.js +1 -1
  18. package/dist/wp-cli.js +1 -2
  19. package/dist/wp-client.js +17 -0
  20. package/package.json +3 -2
  21. package/dist/preset-cli/__tests__/button-emitter.test.d.ts +0 -8
  22. package/dist/preset-cli/__tests__/button-emitter.test.js +0 -188
  23. package/dist/preset-cli/__tests__/cli.test.d.ts +0 -9
  24. package/dist/preset-cli/__tests__/cli.test.js +0 -791
  25. package/dist/preset-cli/__tests__/heading-font-emitter.test.d.ts +0 -12
  26. package/dist/preset-cli/__tests__/heading-font-emitter.test.js +0 -249
  27. package/dist/preset-cli/__tests__/preset-create-unchanged.test.d.ts +0 -13
  28. package/dist/preset-cli/__tests__/preset-create-unchanged.test.js +0 -64
  29. package/dist/preset-cli/__tests__/registry.test.d.ts +0 -5
  30. package/dist/preset-cli/__tests__/registry.test.js +0 -175
  31. package/dist/preset-cli/__tests__/spacing-emitter.test.d.ts +0 -20
  32. package/dist/preset-cli/__tests__/spacing-emitter.test.js +0 -409
  33. package/dist/preset-cli/__tests__/text-body-font-emitter.test.d.ts +0 -14
  34. package/dist/preset-cli/__tests__/text-body-font-emitter.test.js +0 -191
  35. package/dist/preset-cli/__tests__/write-path.test.d.ts +0 -8
  36. package/dist/preset-cli/__tests__/write-path.test.js +0 -287
@@ -1,9 +1,10 @@
1
1
  /**
2
- * `diviops-preset` — standalone preset-emitter CLI (Track 4 scaffold).
2
+ * `diviops-preset` — standalone preset-emitter CLI.
3
3
  *
4
4
  * Emits byte-canonical Divi 5.5.x preset JSON, gated by the verified-attrs
5
- * registry and routed through the existing storage-path contract. Track 4
6
- * ships one emitter: `divi/button` group presets.
5
+ * registry and routed through the existing storage-path contract. The
6
+ * initial slice shipped the `divi/button` group emitter; subsequent slices
7
+ * added the heading/font, color, and spacing emitters.
7
8
  *
8
9
  * Usage:
9
10
  * diviops-preset button [options] Emit a divi/button group preset
@@ -129,13 +130,13 @@ text-body-font OPTIONS (all styling fields optional; emit-on-specification only)
129
130
  spacing OPTIONS (sparse-emit per axis; paired sync flags per axis)
130
131
  --name <string> Preset display name (required).
131
132
  --module <name> Required. Currently only divi/section is wired
132
- (Track 7a verified only the divi/section cell).
133
- Other modules (divi/heading, divi/text,
134
- divi/button, etc.) resolve to the registry gate
135
- and are refused with EvidenceGateError —
136
- promoting them requires a Track-7a-style
137
- canonical capture PR plus a follow-up
138
- implementation/docs PR (NOT a free dispatch-
133
+ (the canonical capture verified only the
134
+ divi/section cell). Other modules (divi/heading,
135
+ divi/text, divi/button, etc.) resolve to the
136
+ registry gate and are refused with
137
+ EvidenceGateError — promoting them requires a
138
+ canonical-capture change plus a follow-up
139
+ implementation/docs change (NOT a free dispatch-
139
140
  clear via the gate alone).
140
141
  --padding-top <v> Desktop padding corners. Pass any subset; only
141
142
  --padding-right <v> passed corners emit (sparse-emit per axis). v1
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `divi/font` heading group-preset emitter — Track 5 vertical slice.
2
+ * `divi/font` heading group-preset emitter.
3
3
  *
4
4
  * Emits a byte-canonical Divi 5.5.x `type: "group"` `divi/font` preset
5
5
  * targeting `divi/heading` at `attrs.title.decoration.font.font.desktop.value.*`,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `divi/font` heading group-preset emitter — Track 5 vertical slice.
2
+ * `divi/font` heading group-preset emitter.
3
3
  *
4
4
  * Emits a byte-canonical Divi 5.5.x `type: "group"` `divi/font` preset
5
5
  * targeting `divi/heading` at `attrs.title.decoration.font.font.desktop.value.*`,
@@ -109,8 +109,8 @@ export function emitHeadingFontGroupPreset(input, registry = loadRegistry()) {
109
109
  throw new Error(`Heading-font emitter requires \`pattern\` to be "google" or "local"; got ${JSON.stringify(input.pattern)}. ` +
110
110
  `There is no default — Pattern A (google) and Pattern B (local) are distinct registry/evidence variants.`);
111
111
  }
112
- // Pattern B + explicit weight is out-of-scope for Track 5. The verified
113
- // Pattern B capture proves `weight` is absent; no registry entry
112
+ // Pattern B + explicit weight is out-of-scope for the current emitter. The
113
+ // verified Pattern B capture proves `weight` is absent; no registry entry
114
114
  // currently authorizes a local-hosted-with-explicit-weight shape.
115
115
  if (input.pattern === "local" && input.weight !== undefined) {
116
116
  throw new UnsupportedVariantCombinationError(`Pattern B (local-hosted) does not support an explicit \`weight\` — the verified ` +
@@ -1,10 +1,10 @@
1
1
  /**
2
- * `divi/spacing` section group-preset emitter — Track 7b vertical slice.
2
+ * `divi/spacing` section group-preset emitter.
3
3
  *
4
4
  * Emits a byte-canonical Divi 5.6.0 `type: "group"` `divi/spacing` preset
5
5
  * targeting `divi/section` at
6
6
  * `attrs.module.decoration.spacing.desktop.value.{padding|margin}.*`,
7
- * gated by the verified-attrs registry. Canonical shape:
7
+ * gated by the verified-attrs registry. Canonical shape captured under
8
8
  * `docs/verification/evidence/canonical-shape-dumps-2026-05-23/round-5-spacing-section.json`.
9
9
  *
10
10
  * Scope: `divi/section` ONLY. Heading / text / button spacing cells remain
@@ -23,14 +23,14 @@
23
23
  * - `attrs == styleAttrs == renderAttrs` at the route layer (the plugin's
24
24
  * `/preset/create` route mirrors `attrs` into all three buckets to match
25
25
  * VB save semantics); the CLI request body only carries `attrs`. The
26
- * Track 7a fixture captures the post-write storage shape — do NOT add
27
- * `styleAttrs` / `renderAttrs` keys to the emitter output.
26
+ * canonical capture fixture documents the post-write storage shape — do
27
+ * NOT add `styleAttrs` / `renderAttrs` keys to the emitter output.
28
28
  * - `groupId: "designSpacing"` — the Composable Settings panel id, NOT a
29
29
  * dotted attr path (the prior schema-inferred `module.decoration.spacing`
30
- * note was a misread, corrected in PR #751).
30
+ * note was a misread, now corrected).
31
31
  * - `primaryAttrName: "module"` for section spacing.
32
32
  * - Variable tokens (`$variable(...)` / bare `gvid-*`) in length flags are
33
- * REFUSED — Track 7a capture exercised literal CSS lengths only;
33
+ * REFUSED — the canonical capture exercised literal CSS lengths only;
34
34
  * variable-token shape needs its own capture before this emitter writes
35
35
  * it.
36
36
  */
@@ -46,8 +46,8 @@ export declare const SPACING_PATTERN_FAMILY = "divi/spacing";
46
46
  * `divi/section` is wired with fixtures + tests + canonical shape today.
47
47
  *
48
48
  * Promoting another module is NOT a free dispatch-clear via the registry
49
- * gate — each new cell needs a Track-7a-style canonical capture PR landing
50
- * first AND a follow-up implementation/docs PR.
49
+ * gate — each new cell needs a canonical-capture change landing first AND
50
+ * a follow-up implementation/docs change.
51
51
  */
52
52
  export declare const SPACING_SUPPORTED_MODULES: readonly ["divi/section"];
53
53
  export type SpacingCornerInput = {
@@ -111,8 +111,8 @@ export declare function composeSpacingAttrs(input: SpacingEmitterInput): {
111
111
  *
112
112
  * `styleAttrs` and `renderAttrs` are intentionally NOT part of this entry:
113
113
  * the plugin's `/preset/create` route mirrors the single `attrs` bag into
114
- * all three buckets at write time. The Track 7a fixture captures the
115
- * post-write storage shape (which is why both `attrs` and `styleAttrs`
114
+ * all three buckets at write time. The canonical capture fixture documents
115
+ * the post-write storage shape (which is why both `attrs` and `styleAttrs`
116
116
  * appear there byte-identical); the CLI emits the request shape only.
117
117
  */
118
118
  export declare function emitSpacingGroupPreset(input: SpacingEmitterInput, registry?: VerifiedAttrsRegistry): SpacingPresetEntry;
@@ -123,9 +123,9 @@ export declare function emitSpacingGroupPreset(input: SpacingEmitterInput, regis
123
123
  *
124
124
  * `primary_attr_name` IS sent on the wire (the plugin's `/preset/create`
125
125
  * route accepts it as an optional snake_case param and stores it as
126
- * `primaryAttrName` in the preset). Tracks 4/5/6 emitters omit it because
127
- * their preset types do not carry it; the divi/spacing cell does, per the
128
- * Track 7a capture's load-bearing finding #2.
126
+ * `primaryAttrName` in the preset). The button / font / color emitters
127
+ * omit it because their preset types do not carry it; the divi/spacing
128
+ * cell does, per the canonical capture's load-bearing finding #2.
129
129
  */
130
130
  export declare function buildSpacingPresetCreateBody(entry: SpacingPresetEntry, opts?: {
131
131
  dry_run?: boolean;
@@ -1,10 +1,10 @@
1
1
  /**
2
- * `divi/spacing` section group-preset emitter — Track 7b vertical slice.
2
+ * `divi/spacing` section group-preset emitter.
3
3
  *
4
4
  * Emits a byte-canonical Divi 5.6.0 `type: "group"` `divi/spacing` preset
5
5
  * targeting `divi/section` at
6
6
  * `attrs.module.decoration.spacing.desktop.value.{padding|margin}.*`,
7
- * gated by the verified-attrs registry. Canonical shape:
7
+ * gated by the verified-attrs registry. Canonical shape captured under
8
8
  * `docs/verification/evidence/canonical-shape-dumps-2026-05-23/round-5-spacing-section.json`.
9
9
  *
10
10
  * Scope: `divi/section` ONLY. Heading / text / button spacing cells remain
@@ -23,14 +23,14 @@
23
23
  * - `attrs == styleAttrs == renderAttrs` at the route layer (the plugin's
24
24
  * `/preset/create` route mirrors `attrs` into all three buckets to match
25
25
  * VB save semantics); the CLI request body only carries `attrs`. The
26
- * Track 7a fixture captures the post-write storage shape — do NOT add
27
- * `styleAttrs` / `renderAttrs` keys to the emitter output.
26
+ * canonical capture fixture documents the post-write storage shape — do
27
+ * NOT add `styleAttrs` / `renderAttrs` keys to the emitter output.
28
28
  * - `groupId: "designSpacing"` — the Composable Settings panel id, NOT a
29
29
  * dotted attr path (the prior schema-inferred `module.decoration.spacing`
30
- * note was a misread, corrected in PR #751).
30
+ * note was a misread, now corrected).
31
31
  * - `primaryAttrName: "module"` for section spacing.
32
32
  * - Variable tokens (`$variable(...)` / bare `gvid-*`) in length flags are
33
- * REFUSED — Track 7a capture exercised literal CSS lengths only;
33
+ * REFUSED — the canonical capture exercised literal CSS lengths only;
34
34
  * variable-token shape needs its own capture before this emitter writes
35
35
  * it.
36
36
  */
@@ -46,8 +46,8 @@ export const SPACING_PATTERN_FAMILY = "divi/spacing";
46
46
  * `divi/section` is wired with fixtures + tests + canonical shape today.
47
47
  *
48
48
  * Promoting another module is NOT a free dispatch-clear via the registry
49
- * gate — each new cell needs a Track-7a-style canonical capture PR landing
50
- * first AND a follow-up implementation/docs PR.
49
+ * gate — each new cell needs a canonical-capture change landing first AND
50
+ * a follow-up implementation/docs change.
51
51
  */
52
52
  export const SPACING_SUPPORTED_MODULES = ["divi/section"];
53
53
  /** Detect bare `gvid-*` / `gcid-*` variable token names. */
@@ -56,7 +56,7 @@ const BARE_VARIABLE_TOKEN_RE = /^(gvid|gcid)-/;
56
56
  * Positive validator for CSS length values. Accepts only the v1 unit set
57
57
  * from the brief: integer or decimal numbers (including a leading minus
58
58
  * sign and bare `0`) followed by `px`, `rem`, `em`, `%`, `vw`, or `vh`.
59
- * Track 7a capture exercised literal values only — variable tokens,
59
+ * The canonical capture exercised literal values only — variable tokens,
60
60
  * `var(...)`, `calc(...)`, and free-form strings like `banana` are all
61
61
  * refused before emission. Adding any new unit requires a brief update
62
62
  * (canonical-capture re-test if the unit changes server-side rendering).
@@ -71,7 +71,7 @@ const LITERAL_CSS_LENGTH_RE = /^-?(?:\d+\.?\d*|\.\d+)(?:px|rem|em|%|vw|vh)$/;
71
71
  * 2. Bare `gvid-*` / `gcid-*` token name — same deferred shape.
72
72
  * 3. Anything else that does NOT match the v1 literal CSS length grammar
73
73
  * (`px`, `rem`, `em`, `%`, `vw`, `vh`). Catches `banana`, `var(--x)`,
74
- * `calc(8px + 2vw)`, etc. — none of which Track 7a verified.
74
+ * `calc(8px + 2vw)`, etc. — none of which the canonical capture verified.
75
75
  *
76
76
  * The variable-token branches fire BEFORE the generic literal check so
77
77
  * the operator gets the precise "variable-token support deferred" hint
@@ -86,17 +86,17 @@ function assertLiteralLength(value, flagLabel) {
86
86
  if (trimmed.startsWith("$variable(")) {
87
87
  throw new Error(`${flagLabel} value ${JSON.stringify(value)} is a $variable() token. ` +
88
88
  `Variable-token support deferred — pending canonical capture. ` +
89
- `Track 7a exercised literal CSS length values only (px / rem / em / % / vw / vh).`);
89
+ `Canonical capture exercised literal CSS length values only (px / rem / em / % / vw / vh).`);
90
90
  }
91
91
  if (BARE_VARIABLE_TOKEN_RE.test(trimmed)) {
92
92
  throw new Error(`${flagLabel} value ${JSON.stringify(value)} is a bare ${trimmed.split("-")[0]}-* variable token. ` +
93
93
  `Variable-token support deferred — pending canonical capture. ` +
94
- `Track 7a exercised literal CSS length values only (px / rem / em / % / vw / vh).`);
94
+ `Canonical capture exercised literal CSS length values only (px / rem / em / % / vw / vh).`);
95
95
  }
96
96
  if (!LITERAL_CSS_LENGTH_RE.test(trimmed)) {
97
97
  throw new Error(`${flagLabel} value ${JSON.stringify(value)} is not a literal CSS length. ` +
98
98
  `v1 accepts only \`<number><unit>\` where unit is one of px / rem / em / % / vw / vh ` +
99
- `(e.g. "40px", "1.5rem", "100%"). Track 7a canonical capture exercised literal ` +
99
+ `(e.g. "40px", "1.5rem", "100%"). The canonical capture exercised literal ` +
100
100
  `values only; broader value grammars (var(...), calc(...), etc.) need their own capture.`);
101
101
  }
102
102
  }
@@ -190,8 +190,8 @@ export function composeSpacingAttrs(input) {
190
190
  *
191
191
  * `styleAttrs` and `renderAttrs` are intentionally NOT part of this entry:
192
192
  * the plugin's `/preset/create` route mirrors the single `attrs` bag into
193
- * all three buckets at write time. The Track 7a fixture captures the
194
- * post-write storage shape (which is why both `attrs` and `styleAttrs`
193
+ * all three buckets at write time. The canonical capture fixture documents
194
+ * the post-write storage shape (which is why both `attrs` and `styleAttrs`
195
195
  * appear there byte-identical); the CLI emits the request shape only.
196
196
  */
197
197
  export function emitSpacingGroupPreset(input, registry = loadRegistry()) {
@@ -201,14 +201,14 @@ export function emitSpacingGroupPreset(input, registry = loadRegistry()) {
201
201
  if (!input.module || typeof input.module !== "string") {
202
202
  throw new Error("spacing emitter requires `module` — currently only `divi/section` is " +
203
203
  "wired (other module cells are SCHEMA_OBSERVED in the registry; " +
204
- "promoting them requires a Track-7a-style canonical capture PR + a " +
205
- "follow-up implementation/docs PR).");
204
+ "promoting them requires a canonical-capture change + a follow-up " +
205
+ "implementation/docs change).");
206
206
  }
207
207
  const { attrs, value } = composeSpacingAttrs(input);
208
208
  // Empty-input rejection: at least one corner across either axis MUST be
209
209
  // specified. An empty value bag is a usage error — there is nothing to
210
210
  // write, and the resulting preset would be the empty-shell VB authoring
211
- // footgun documented in the Track 7a capture.
211
+ // footgun documented in the canonical capture.
212
212
  if (Object.keys(value).length === 0) {
213
213
  throw new Error("spacing emitter produced an empty preset — pass at least one " +
214
214
  "padding or margin corner (--padding-top, --margin-bottom, etc.).");
@@ -235,8 +235,8 @@ export function emitSpacingGroupPreset(input, registry = loadRegistry()) {
235
235
  `(this emitter hard-codes the divi/section wrapper "module" and ` +
236
236
  `primaryAttrName "module"). Supported modules: ` +
237
237
  `${SPACING_SUPPORTED_MODULES.join(", ")}. Adding a new module ` +
238
- `requires a Track-7a-style canonical capture PR landing first AND ` +
239
- `a follow-up implementation PR extending this constant alongside ` +
238
+ `requires a canonical-capture change landing first AND ` +
239
+ `a follow-up implementation change extending this constant alongside ` +
240
240
  `fixtures + tests + README updates.`);
241
241
  }
242
242
  return {
@@ -256,9 +256,9 @@ export function emitSpacingGroupPreset(input, registry = loadRegistry()) {
256
256
  *
257
257
  * `primary_attr_name` IS sent on the wire (the plugin's `/preset/create`
258
258
  * route accepts it as an optional snake_case param and stores it as
259
- * `primaryAttrName` in the preset). Tracks 4/5/6 emitters omit it because
260
- * their preset types do not carry it; the divi/spacing cell does, per the
261
- * Track 7a capture's load-bearing finding #2.
259
+ * `primaryAttrName` in the preset). The button / font / color emitters
260
+ * omit it because their preset types do not carry it; the divi/spacing
261
+ * cell does, per the canonical capture's load-bearing finding #2.
262
262
  */
263
263
  export function buildSpacingPresetCreateBody(entry, opts = {}) {
264
264
  const body = {
@@ -96,9 +96,9 @@ export declare function applyTextBodyFontPreset(client: PresetWriteClient, entry
96
96
  * existing storage-routed route (no plugin route is added).
97
97
  *
98
98
  * Unlike the font emitters, the spacing entry carries `primary_attr_name`
99
- * (`"module"` for the section cell per Track 7a), which IS sent on the
100
- * wire — the `/preset/create` route accepts it as an optional snake_case
101
- * param and stores it as `primaryAttrName` in the preset.
99
+ * (`"module"` for the section cell per the canonical capture), which IS
100
+ * sent on the wire — the `/preset/create` route accepts it as an optional
101
+ * snake_case param and stores it as `primaryAttrName` in the preset.
102
102
  */
103
103
  export declare function applySpacingPreset(client: PresetWriteClient, entry: SpacingPresetEntry, opts: {
104
104
  serverVersion: string;
@@ -138,9 +138,9 @@ export async function applyTextBodyFontPreset(client, entry, opts) {
138
138
  * existing storage-routed route (no plugin route is added).
139
139
  *
140
140
  * Unlike the font emitters, the spacing entry carries `primary_attr_name`
141
- * (`"module"` for the section cell per Track 7a), which IS sent on the
142
- * wire — the `/preset/create` route accepts it as an optional snake_case
143
- * param and stores it as `primaryAttrName` in the preset.
141
+ * (`"module"` for the section cell per the canonical capture), which IS
142
+ * sent on the wire — the `/preset/create` route accepts it as an optional
143
+ * snake_case param and stores it as `primaryAttrName` in the preset.
144
144
  */
145
145
  export async function applySpacingPreset(client, entry, opts) {
146
146
  await assertStorageCapability(client, opts.serverVersion);
@@ -11,7 +11,7 @@
11
11
  * This module adds a second-pass validator specifically for DEFAULT-tier
12
12
  * FS commands. EXTENDED-tier FS commands (`import`, `eval-file`) are
13
13
  * already opt-in via DIVIOPS_WP_CLI_ALLOW; their validation is tracked
14
- * separately (see dev-repo #271 scope discussion).
14
+ * separately as a future scope expansion.
15
15
  *
16
16
  * Guarantees:
17
17
  * - All path arguments must resolve under SAFE_FS_ROOT after canonicalization
@@ -11,7 +11,7 @@
11
11
  * This module adds a second-pass validator specifically for DEFAULT-tier
12
12
  * FS commands. EXTENDED-tier FS commands (`import`, `eval-file`) are
13
13
  * already opt-in via DIVIOPS_WP_CLI_ALLOW; their validation is tracked
14
- * separately (see dev-repo #271 scope discussion).
14
+ * separately as a future scope expansion.
15
15
  *
16
16
  * Guarantees:
17
17
  * - All path arguments must resolve under SAFE_FS_ROOT after canonicalization
package/dist/wp-cli.js CHANGED
@@ -83,8 +83,7 @@ const DEFAULT_COMMANDS = [
83
83
  'core verify-checksums',
84
84
  'core language list',
85
85
  // DB (read-only introspection only — `db query` stays out; it's arbitrary SQL with no scoping
86
- // and belongs in a higher-risk tier, tracked separately). See dev-repo #361 Chunk B for the
87
- // opt-in design discussion.
86
+ // and belongs in a higher-risk tier, tracked separately).
88
87
  'db columns',
89
88
  'db size',
90
89
  'db tables',
package/dist/wp-client.js CHANGED
@@ -361,6 +361,23 @@ export class WPClient {
361
361
  typeof result.capabilities !== 'object') {
362
362
  result.capabilities = {};
363
363
  }
364
+ // ADR-003 / ADR-007 Pro-extension fields. Free-only sites omit
365
+ // them entirely; pre-V1 Pro plugins might emit partial shapes.
366
+ // Normalize defensively so downstream gates can read uniform
367
+ // types without per-call shape checks.
368
+ if (typeof result.pro_active !== 'boolean') {
369
+ result.pro_active = false;
370
+ }
371
+ if (result.available_targets === null ||
372
+ typeof result.available_targets !== 'object' ||
373
+ Array.isArray(result.available_targets)) {
374
+ result.available_targets = {};
375
+ }
376
+ if (result.active_modules === null ||
377
+ typeof result.active_modules !== 'object' ||
378
+ Array.isArray(result.active_modules)) {
379
+ result.active_modules = {};
380
+ }
364
381
  return result;
365
382
  }
366
383
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diviops/mcp-server",
3
- "version": "1.5.14",
3
+ "version": "1.5.17",
4
4
  "description": "MCP server exposing Divi 5 Visual Builder as tools for Claude",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -9,7 +9,8 @@
9
9
  "diviops-preset": "dist/preset-cli/bin.js"
10
10
  },
11
11
  "files": [
12
- "dist",
12
+ "dist/**/*",
13
+ "!dist/**/__tests__/**",
13
14
  "templates",
14
15
  "data/verified-attrs.json",
15
16
  "data/verified-attrs-backlog.json",
@@ -1,8 +0,0 @@
1
- /**
2
- * `divi/button` emitter shape coverage:
3
- * - fixture-based shape assertion against round-3-button-canonical-complete.json
4
- * - emit-on-specification discriminator (with vs without font.weight)
5
- * - hover shape (NO `value` wrapper), radius vocabulary, variable tokens,
6
- * the do-not-emit list, and the opt-in bypass corner.
7
- */
8
- export {};
@@ -1,188 +0,0 @@
1
- /**
2
- * `divi/button` emitter shape coverage:
3
- * - fixture-based shape assertion against round-3-button-canonical-complete.json
4
- * - emit-on-specification discriminator (with vs without font.weight)
5
- * - hover shape (NO `value` wrapper), radius vocabulary, variable tokens,
6
- * the do-not-emit list, and the opt-in bypass corner.
7
- */
8
- import { test } from "node:test";
9
- import assert from "node:assert/strict";
10
- import { readFileSync } from "node:fs";
11
- import { fileURLToPath } from "node:url";
12
- import { dirname, join } from "node:path";
13
- import { emitButtonGroupPreset, composeButtonAttrs, buildPresetCreateBody, } from "../button-emitter.js";
14
- import { loadRegistry } from "../registry.js";
15
- const __dirname = dirname(fileURLToPath(import.meta.url));
16
- const REPO_ROOT = join(__dirname, "..", "..", "..", "..");
17
- const FIXTURE = join(REPO_ROOT, "docs/verification/evidence/canonical-shape-dumps-2026-05-18/round-3-button-canonical-complete.json");
18
- const registry = loadRegistry();
19
- test("emitter byte-matches the round-3 canonical button fixture attrs", () => {
20
- const fixture = JSON.parse(readFileSync(FIXTURE, "utf8"));
21
- const canonicalAttrs = fixture.canonical_button_primary_preset.attrs;
22
- const entry = emitButtonGroupPreset({
23
- name: "canonical-button-primary-vb-2026-05-18",
24
- bg_color: "gcid-primary-color",
25
- bg_color_hover: "gcid-secondary-color",
26
- radius: {
27
- topLeft: "8px",
28
- topRight: "8px",
29
- bottomLeft: "8px",
30
- bottomRight: "8px",
31
- },
32
- font: {
33
- family: "Inter",
34
- weight: "600",
35
- color: "gcid-body-color",
36
- },
37
- }, registry);
38
- assert.deepEqual(entry.attrs, canonicalAttrs, "emitted attrs must byte-match the canonical capture");
39
- assert.equal(entry.type, "group");
40
- assert.equal(entry.group_name, "divi/button");
41
- assert.equal(entry.group_id, "button");
42
- assert.equal(entry.module_name, "divi/button");
43
- });
44
- test("hover bg color emits at desktop.hover.color with NO value wrapper", () => {
45
- const attrs = composeButtonAttrs({
46
- name: "hover-only",
47
- bg_color: "#111111",
48
- bg_color_hover: "#222222",
49
- });
50
- const bg = attrs.button.decoration.background;
51
- assert.equal(bg.desktop.value.color, "#111111", "desktop is 3-level (value.color)");
52
- assert.equal(bg.desktop.hover.color, "#222222", "hover is 2-level (hover.color)");
53
- assert.equal("value" in bg.desktop.hover, false, "hover must NOT carry a `value` wrapper");
54
- });
55
- test("emit-on-specification discriminator: font.weight present vs absent", () => {
56
- const withWeight = composeButtonAttrs({
57
- name: "with-weight",
58
- font: { family: "Inter", weight: "600" },
59
- });
60
- const withoutWeight = composeButtonAttrs({
61
- name: "without-weight",
62
- font: { family: "Inter" },
63
- });
64
- const wv = withWeight.button.decoration.font.font.desktop.value;
65
- const wov = withoutWeight.button.decoration.font.font.desktop.value;
66
- assert.equal(wv.weight, "600", "weight emitted when specified");
67
- assert.equal("weight" in wv, true);
68
- assert.equal("weight" in wov, false, "weight absent when not specified");
69
- assert.deepEqual(Object.keys(wov), ["family"], "ONLY the touched key emitted");
70
- });
71
- test("radius vocabulary is {topLeft,topRight,bottomLeft,bottomRight,sync}", () => {
72
- const attrs = composeButtonAttrs({
73
- name: "r",
74
- radius: { topLeft: "8px", topRight: "8px", bottomLeft: "8px", bottomRight: "8px" },
75
- });
76
- const radius = attrs.button.decoration.border.desktop.value.radius;
77
- assert.deepEqual(Object.keys(radius).sort(), [
78
- "bottomLeft",
79
- "bottomRight",
80
- "sync",
81
- "topLeft",
82
- "topRight",
83
- ]);
84
- assert.equal(radius.sync, "on", "equal corners derive sync=on");
85
- });
86
- test("radius sync derives off when corners differ; partial corners stay absent", () => {
87
- const attrs = composeButtonAttrs({
88
- name: "r",
89
- radius: { topLeft: "8px", bottomRight: "4px" },
90
- });
91
- const radius = attrs.button.decoration.border.desktop.value.radius;
92
- assert.equal(radius.sync, "off");
93
- assert.equal("topRight" in radius, false, "untouched corner absent");
94
- assert.equal("bottomLeft" in radius, false, "untouched corner absent");
95
- });
96
- test("radius sync derives off for a single specified corner (Rule 1a)", () => {
97
- // Per Rule 1a in canonical-preset-shapes: a partial corner set must derive
98
- // sync="off". A 1-element corner array is trivially "all equal" — auto-
99
- // deriving "on" is only correct when all 4 corners are present and identical.
100
- const attrs = composeButtonAttrs({
101
- name: "r",
102
- radius: { topLeft: "8px" },
103
- });
104
- const radius = attrs.button.decoration.border.desktop.value.radius;
105
- assert.equal(radius.sync, "off", "single corner must NOT derive sync=on");
106
- assert.equal("topRight" in radius, false, "untouched corner absent");
107
- });
108
- test("radius sync stays off for 3 identical corners; on only with all 4", () => {
109
- const three = composeButtonAttrs({
110
- name: "r",
111
- radius: { topLeft: "8px", topRight: "8px", bottomLeft: "8px" },
112
- });
113
- assert.equal(three.button.decoration.border.desktop.value.radius.sync, "off", "3 identical corners is still a partial set — sync=off");
114
- const all = composeButtonAttrs({
115
- name: "r",
116
- radius: { topLeft: "8px", topRight: "8px", bottomLeft: "8px", bottomRight: "8px" },
117
- });
118
- assert.equal(all.button.decoration.border.desktop.value.radius.sync, "on", "all 4 identical corners derive sync=on");
119
- });
120
- test("explicit radius.sync wins over derivation", () => {
121
- const attrs = composeButtonAttrs({
122
- name: "r",
123
- radius: { topLeft: "8px", sync: "on" },
124
- });
125
- assert.equal(attrs.button.decoration.border.desktop.value.radius.sync, "on", "an explicitly-passed sync flag is honored regardless of corner count");
126
- });
127
- test("variable tokens get the {name,settings} object shape + trailing )$", () => {
128
- const attrs = composeButtonAttrs({
129
- name: "v",
130
- bg_color: "gcid-primary-color",
131
- });
132
- const color = attrs.button.decoration.background.desktop.value.color;
133
- assert.ok(color.endsWith(")$"), "token must end with )$");
134
- assert.match(color, /^\$variable\(/);
135
- const payload = JSON.parse(color.slice("$variable(".length, -2));
136
- assert.deepEqual(payload, {
137
- type: "color",
138
- value: { name: "gcid-primary-color", settings: {} },
139
- });
140
- });
141
- test("literal hex color is emitted verbatim, not wrapped", () => {
142
- const attrs = composeButtonAttrs({ name: "h", bg_color: "#2563eb" });
143
- assert.equal(attrs.button.decoration.background.desktop.value.color, "#2563eb");
144
- });
145
- test("do-not-emit list: no attrs.font/spacing top-level, no renderAttrs, no button slot", () => {
146
- const entry = emitButtonGroupPreset({
147
- name: "clean",
148
- bg_color: "#111",
149
- font: { family: "Inter" },
150
- }, registry);
151
- assert.equal("font" in entry.attrs, false, "no top-level attrs.font");
152
- assert.equal("spacing" in entry.attrs, false, "no top-level attrs.spacing");
153
- assert.equal("renderAttrs" in entry, false, "no renderAttrs");
154
- const decoration = entry.attrs.button.decoration;
155
- assert.equal("button" in decoration, false, "no button.decoration.button slot by default");
156
- });
157
- test("bypass_hover_padding_gate emits ONLY the padding.top corner", () => {
158
- const attrs = composeButtonAttrs({
159
- name: "bypass",
160
- bg_color: "#111",
161
- bypass_hover_padding_gate: true,
162
- });
163
- const buttonSlot = attrs.button.decoration.button;
164
- assert.deepEqual(buttonSlot, {
165
- desktop: { value: { padding: { top: "0px" } } },
166
- });
167
- });
168
- test("emitter rejects an empty preset (no styling specified)", () => {
169
- assert.throws(() => emitButtonGroupPreset({ name: "empty" }, registry), /empty preset/);
170
- });
171
- test("emitter rejects a missing name", () => {
172
- assert.throws(() => emitButtonGroupPreset({ name: "" }, registry), /requires a non-empty `name`/);
173
- });
174
- test("buildPresetCreateBody mirrors the diviops_preset_create body shape", () => {
175
- const entry = emitButtonGroupPreset({ name: "Primary", bg_color: "#111" }, registry);
176
- const body = buildPresetCreateBody(entry, { dry_run: true });
177
- assert.deepEqual(body, {
178
- module_name: "divi/button",
179
- name: "Primary",
180
- attrs: entry.attrs,
181
- type: "group",
182
- group_name: "divi/button",
183
- group_id: "button",
184
- dry_run: true,
185
- });
186
- const noDry = buildPresetCreateBody(entry);
187
- assert.equal("dry_run" in noDry, false, "dry_run omitted when not requested");
188
- });
@@ -1,9 +0,0 @@
1
- /**
2
- * CLI integration coverage: --help, arg parsing, structured exit codes,
3
- * dry-run output, evidence-gate exit, capability-missing exit.
4
- *
5
- * Apply-mode here is exercised through the credential-missing path only
6
- * (no live write — #725 AC #8). The mocked write-path proper lives in
7
- * write-path.test.ts.
8
- */
9
- export {};