@diviops/mcp-server 1.5.10 → 1.5.11

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.
@@ -0,0 +1,326 @@
1
+ /**
2
+ * `diviops-preset` — standalone preset-emitter CLI (Track 4 scaffold).
3
+ *
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.
7
+ *
8
+ * Usage:
9
+ * diviops-preset button [options] Emit a divi/button group preset
10
+ * diviops-preset --help Show help
11
+ *
12
+ * Modes:
13
+ * --dry-run (default) Compose and print canonical JSON. No credentials,
14
+ * no handshake, no network.
15
+ * --apply Capability-gate + POST to /preset/create. Reuses
16
+ * WP_URL / WP_USER / WP_APP_PASSWORD env vars.
17
+ *
18
+ * Exit codes:
19
+ * 0 success
20
+ * 1 invalid input / usage error
21
+ * 2 evidence-gate refusal (attr below VB_PRESET_STORAGE_VERIFIED)
22
+ * 3 capability-missing (plugin lacks storage_multipath_probe_v1)
23
+ * 4 write / network error
24
+ */
25
+ import { emitButtonGroupPreset, buildPresetCreateBody, } from "./button-emitter.js";
26
+ import { EvidenceGateError } from "./registry.js";
27
+ import { applyButtonPreset, buildClientFromEnv, CredentialsMissingError, CapabilityMissingError, } from "./write-path.js";
28
+ export const EXIT = {
29
+ OK: 0,
30
+ INVALID_INPUT: 1,
31
+ EVIDENCE_GATE: 2,
32
+ CAPABILITY_MISSING: 3,
33
+ WRITE_ERROR: 4,
34
+ };
35
+ const realIO = {
36
+ out: (t) => process.stdout.write(t + "\n"),
37
+ err: (t) => process.stderr.write(t + "\n"),
38
+ };
39
+ const HELP = `diviops-preset — Divi 5.5.x canonical preset-emitter CLI
40
+
41
+ USAGE
42
+ diviops-preset button [options] Emit a divi/button group preset
43
+ diviops-preset --help Show this help
44
+
45
+ MODE
46
+ --dry-run Compose + print canonical JSON only (DEFAULT).
47
+ No credentials, no handshake, no network.
48
+ --apply Capability-gate, then POST to /preset/create.
49
+ Requires WP_URL / WP_USER / WP_APP_PASSWORD.
50
+
51
+ button OPTIONS (all styling fields optional; emit-on-specification only)
52
+ --name <string> Preset display name (required).
53
+ --bg-color <value> Desktop background color. Hex literal, or a
54
+ bare gcid-*/gvid-* token, or a $variable(...)$ token.
55
+ --bg-color-hover <value> Hover background color (same value forms).
56
+ --radius-top-left <v> Border radius corner. Any subset of the four
57
+ --radius-top-right <v> corners; the radius widget emits a sync flag
58
+ --radius-bottom-left <v> alongside any corner ("on" only when all four
59
+ --radius-bottom-right <v> corners are given and equal, else "off";
60
+ override with --radius-sync).
61
+ --radius-sync <on|off> Explicit radius sync flag.
62
+ --radius <v> Shorthand: sets all four corners to <v>.
63
+ --border-width <v> Outline-button border width.
64
+ --border-style <v> Outline-button border style (solid|dashed|...).
65
+ --border-color <value> Outline-button border color (same value forms).
66
+ --font-family <string> Button font family (literal string).
67
+ --font-weight <string> Button font weight (e.g. "600").
68
+ --font-color <value> Button font color (same value forms).
69
+ --font-size <v> Button font size (e.g. "16px").
70
+ --bypass-hover-padding-gate
71
+ Opt-in: emit button.decoration.button.desktop
72
+ .value.padding.top="0px" (hover-padding-gate
73
+ workaround). Off by default.
74
+
75
+ EXIT CODES
76
+ 0 success 1 invalid input 2 evidence-gate refusal
77
+ 3 capability missing 4 write error
78
+
79
+ EXAMPLES
80
+ diviops-preset button --name "Primary" --bg-color gcid-primary-color \\
81
+ --bg-color-hover gcid-secondary-color --radius 8px \\
82
+ --font-family Inter --font-weight 600 --font-color gcid-body-color
83
+
84
+ diviops-preset button --name "Primary" --bg-color "#2563eb" --apply
85
+ `;
86
+ const VALUE_FLAGS = new Set([
87
+ "--name",
88
+ "--bg-color",
89
+ "--bg-color-hover",
90
+ "--radius",
91
+ "--radius-top-left",
92
+ "--radius-top-right",
93
+ "--radius-bottom-left",
94
+ "--radius-bottom-right",
95
+ "--radius-sync",
96
+ "--border-width",
97
+ "--border-style",
98
+ "--border-color",
99
+ "--font-family",
100
+ "--font-weight",
101
+ "--font-color",
102
+ "--font-size",
103
+ ]);
104
+ /** Parse argv (after `node script`) into a structured shape. Throws on unknown flags. */
105
+ export function parseArgs(argv) {
106
+ const parsed = {
107
+ command: null,
108
+ help: false,
109
+ apply: false,
110
+ dryRun: false,
111
+ options: new Map(),
112
+ };
113
+ let i = 0;
114
+ // First non-flag token is the command.
115
+ if (argv.length > 0 && !argv[0].startsWith("-")) {
116
+ parsed.command = argv[0];
117
+ i = 1;
118
+ }
119
+ for (; i < argv.length; i++) {
120
+ const tok = argv[i];
121
+ if (tok === "--help" || tok === "-h") {
122
+ parsed.help = true;
123
+ continue;
124
+ }
125
+ if (tok === "--apply") {
126
+ parsed.apply = true;
127
+ continue;
128
+ }
129
+ if (tok === "--dry-run") {
130
+ parsed.dryRun = true;
131
+ continue;
132
+ }
133
+ if (tok === "--bypass-hover-padding-gate") {
134
+ parsed.options.set(tok, true);
135
+ continue;
136
+ }
137
+ if (VALUE_FLAGS.has(tok)) {
138
+ const val = argv[i + 1];
139
+ if (val === undefined || val.startsWith("--")) {
140
+ throw new UsageError(`Flag ${tok} requires a value.`);
141
+ }
142
+ parsed.options.set(tok, val);
143
+ i++;
144
+ continue;
145
+ }
146
+ if (!tok.startsWith("-") && parsed.command === null) {
147
+ parsed.command = tok;
148
+ continue;
149
+ }
150
+ throw new UsageError(`Unknown flag or argument: ${tok}`);
151
+ }
152
+ if (parsed.apply && parsed.dryRun) {
153
+ throw new UsageError("--apply and --dry-run are mutually exclusive.");
154
+ }
155
+ // dry-run is the default-safe mode when neither is given.
156
+ if (!parsed.apply)
157
+ parsed.dryRun = true;
158
+ return parsed;
159
+ }
160
+ export class UsageError extends Error {
161
+ constructor(message) {
162
+ super(message);
163
+ this.name = "UsageError";
164
+ }
165
+ }
166
+ /** Map parsed `button` options into the emitter input shape. */
167
+ export function buildButtonInput(parsed) {
168
+ const opt = (k) => {
169
+ const v = parsed.options.get(k);
170
+ return typeof v === "string" ? v : undefined;
171
+ };
172
+ const name = opt("--name");
173
+ if (!name) {
174
+ throw new UsageError("button command requires --name <string>.");
175
+ }
176
+ const input = { name };
177
+ const bg = opt("--bg-color");
178
+ if (bg !== undefined)
179
+ input.bg_color = bg;
180
+ const bgh = opt("--bg-color-hover");
181
+ if (bgh !== undefined)
182
+ input.bg_color_hover = bgh;
183
+ // Radius — shorthand --radius sets all four corners.
184
+ const radius = {};
185
+ const radiusAll = opt("--radius");
186
+ if (radiusAll !== undefined) {
187
+ radius.topLeft = radiusAll;
188
+ radius.topRight = radiusAll;
189
+ radius.bottomLeft = radiusAll;
190
+ radius.bottomRight = radiusAll;
191
+ }
192
+ const rtl = opt("--radius-top-left");
193
+ if (rtl !== undefined)
194
+ radius.topLeft = rtl;
195
+ const rtr = opt("--radius-top-right");
196
+ if (rtr !== undefined)
197
+ radius.topRight = rtr;
198
+ const rbl = opt("--radius-bottom-left");
199
+ if (rbl !== undefined)
200
+ radius.bottomLeft = rbl;
201
+ const rbr = opt("--radius-bottom-right");
202
+ if (rbr !== undefined)
203
+ radius.bottomRight = rbr;
204
+ const rsync = opt("--radius-sync");
205
+ if (rsync !== undefined) {
206
+ if (rsync !== "on" && rsync !== "off") {
207
+ throw new UsageError('--radius-sync must be "on" or "off".');
208
+ }
209
+ radius.sync = rsync;
210
+ }
211
+ if (Object.keys(radius).length > 0)
212
+ input.radius = radius;
213
+ // Outline border styles.
214
+ const border = {};
215
+ const bw = opt("--border-width");
216
+ if (bw !== undefined)
217
+ border.width = bw;
218
+ const bs = opt("--border-style");
219
+ if (bs !== undefined)
220
+ border.style = bs;
221
+ const bc = opt("--border-color");
222
+ if (bc !== undefined)
223
+ border.color = bc;
224
+ if (Object.keys(border).length > 0)
225
+ input.border = border;
226
+ // Font.
227
+ const font = {};
228
+ const ff = opt("--font-family");
229
+ if (ff !== undefined)
230
+ font.family = ff;
231
+ const fw = opt("--font-weight");
232
+ if (fw !== undefined)
233
+ font.weight = fw;
234
+ const fc = opt("--font-color");
235
+ if (fc !== undefined)
236
+ font.color = fc;
237
+ const fs = opt("--font-size");
238
+ if (fs !== undefined)
239
+ font.size = fs;
240
+ if (Object.keys(font).length > 0)
241
+ input.font = font;
242
+ if (parsed.options.get("--bypass-hover-padding-gate") === true) {
243
+ input.bypass_hover_padding_gate = true;
244
+ }
245
+ return input;
246
+ }
247
+ /**
248
+ * Run the CLI. Returns the structured exit code (does NOT call
249
+ * `process.exit` — the thin bin wrapper does). `io` is injectable so
250
+ * tests capture output without touching real stdio.
251
+ */
252
+ export async function run(argv, io = realIO, serverVersion) {
253
+ let parsed;
254
+ try {
255
+ parsed = parseArgs(argv);
256
+ }
257
+ catch (err) {
258
+ io.err(err instanceof Error ? err.message : String(err));
259
+ io.err("");
260
+ io.err("Run `diviops-preset --help` for usage.");
261
+ return EXIT.INVALID_INPUT;
262
+ }
263
+ if (parsed.help || (parsed.command === null && parsed.options.size === 0)) {
264
+ io.out(HELP);
265
+ return EXIT.OK;
266
+ }
267
+ if (parsed.command !== "button") {
268
+ io.err(`Unknown command: ${parsed.command ?? "(none)"}. ` +
269
+ `Track 4 ships one command: "button".`);
270
+ io.err("Run `diviops-preset --help` for usage.");
271
+ return EXIT.INVALID_INPUT;
272
+ }
273
+ // --- compose + gate -------------------------------------------------
274
+ let entry;
275
+ try {
276
+ const input = buildButtonInput(parsed);
277
+ entry = emitButtonGroupPreset(input);
278
+ }
279
+ catch (err) {
280
+ if (err instanceof EvidenceGateError) {
281
+ io.err(err.message);
282
+ return EXIT.EVIDENCE_GATE;
283
+ }
284
+ if (err instanceof UsageError) {
285
+ io.err(err.message);
286
+ io.err("Run `diviops-preset --help` for usage.");
287
+ return EXIT.INVALID_INPUT;
288
+ }
289
+ io.err(err instanceof Error ? err.message : String(err));
290
+ return EXIT.INVALID_INPUT;
291
+ }
292
+ // --- dry-run --------------------------------------------------------
293
+ if (parsed.dryRun) {
294
+ const body = buildPresetCreateBody(entry, { dry_run: true });
295
+ io.out(JSON.stringify(body, null, 2));
296
+ return EXIT.OK;
297
+ }
298
+ // --- apply ----------------------------------------------------------
299
+ try {
300
+ const client = buildClientFromEnv();
301
+ // Apply mode requires the server version for the plugin handshake; the
302
+ // /handshake route gates on `mcp_server_version`. The bin entrypoint
303
+ // always supplies it — guard here so apply mode can never reach the
304
+ // handshake with an undefined/empty version.
305
+ if (!serverVersion) {
306
+ io.err("Apply mode requires the server version for the plugin handshake. " +
307
+ "This is an internal error — invoke via the `diviops-preset` bin.");
308
+ return EXIT.WRITE_ERROR;
309
+ }
310
+ const result = await applyButtonPreset(client, entry, { serverVersion });
311
+ io.out(JSON.stringify(result, null, 2));
312
+ return EXIT.OK;
313
+ }
314
+ catch (err) {
315
+ if (err instanceof CapabilityMissingError) {
316
+ io.err(err.message);
317
+ return EXIT.CAPABILITY_MISSING;
318
+ }
319
+ if (err instanceof CredentialsMissingError) {
320
+ io.err(err.message);
321
+ return EXIT.INVALID_INPUT;
322
+ }
323
+ io.err(`Write failed: ${err instanceof Error ? err.message : String(err)}`);
324
+ return EXIT.WRITE_ERROR;
325
+ }
326
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Verified-attrs registry consumption for the preset-emitter CLI.
3
+ *
4
+ * Loads `diviops-server/data/verified-attrs.json` and resolves the
5
+ * EFFECTIVE evidence level per `(module, pattern-family)` cell using the
6
+ * `min()` rule documented in `docs/verification/README.md`:
7
+ *
8
+ * effective_evidence = min(pattern_evidence_level,
9
+ * applicability[<module>].cell_evidence_level)
10
+ *
11
+ * A missing `applicability[<module>]` resolves to `UNVERIFIED` (0) — never
12
+ * to the pattern level. No invisible inheritance crosses the write
13
+ * threshold.
14
+ *
15
+ * This module is the consumer side of the verified-attrs registry
16
+ * contract. It does NOT mutate the registry; if a needed cell is absent
17
+ * or under-verified, the caller fails and the gap is filed as a backlog
18
+ * candidate.
19
+ */
20
+ /**
21
+ * Effective evidence required for a write emitter to emit an attr.
22
+ * `VB_PRESET_STORAGE_VERIFIED` has numeric ordering 4.
23
+ */
24
+ export declare const WRITE_EMITTER_THRESHOLD_LEVEL = "VB_PRESET_STORAGE_VERIFIED";
25
+ export interface ApplicabilityCell {
26
+ wrapper?: string;
27
+ preset_type?: string;
28
+ group_name?: string;
29
+ group_id?: string;
30
+ cell_evidence_level?: string;
31
+ cell_divi_version?: string;
32
+ verified_at?: string;
33
+ source?: string;
34
+ caveats?: string[];
35
+ }
36
+ export interface Tier12Entry {
37
+ pattern_family: string;
38
+ pattern_variant?: string;
39
+ pattern_evidence_level: string;
40
+ pattern_evidence_source?: string;
41
+ applicability?: Record<string, ApplicabilityCell>;
42
+ }
43
+ export interface VerifiedAttrsRegistry {
44
+ schema_version?: string;
45
+ registry_version?: string;
46
+ evidence_level_ordering: Record<string, number>;
47
+ tier1?: Tier12Entry[];
48
+ tier2?: Tier12Entry[];
49
+ tier3?: unknown[];
50
+ }
51
+ /** Resolution of a single `(module, pattern-family)` cell against the registry. */
52
+ export interface EvidenceResolution {
53
+ patternFamily: string;
54
+ module: string;
55
+ /** Numeric pattern-level evidence (0–5). */
56
+ patternLevel: number;
57
+ patternLevelName: string;
58
+ /** Numeric cell-level evidence (0–5); 0 when applicability[<module>] is absent. */
59
+ cellLevel: number;
60
+ cellLevelName: string;
61
+ /** min(patternLevel, cellLevel). */
62
+ effectiveLevel: number;
63
+ effectiveLevelName: string;
64
+ /** True when applicability[<module>] is missing — cellLevel is the 0-fallback. */
65
+ applicabilityMissing: boolean;
66
+ /** Best-available durable source string for error messages. */
67
+ source: string;
68
+ /** The applicability cell, if present (carries group_name / group_id / wrapper). */
69
+ cell?: ApplicabilityCell;
70
+ }
71
+ /**
72
+ * Load and cache `data/verified-attrs.json`. The path is resolved relative
73
+ * to the compiled module location (`dist/preset-cli/registry.js` →
74
+ * `dist/../data/verified-attrs.json`), and also tolerates the `src/` layout
75
+ * for direct ts-node-style execution / tests.
76
+ */
77
+ export declare function loadRegistry(explicitPath?: string): VerifiedAttrsRegistry;
78
+ /** Reset the module-level cache. Used by tests. */
79
+ export declare function resetRegistryCache(): void;
80
+ /**
81
+ * Find a Tier 1 / Tier 2 entry by `pattern_family`. Tier 2 is searched
82
+ * first (the button families live there), then Tier 1.
83
+ */
84
+ export declare function findPatternEntry(registry: VerifiedAttrsRegistry, patternFamily: string): Tier12Entry | undefined;
85
+ /**
86
+ * Resolve effective evidence for a `(module, pattern-family)` cell.
87
+ *
88
+ * Throws if the pattern family is entirely absent from the registry —
89
+ * that is an unrecoverable gap (the CLI must fail, not guess).
90
+ */
91
+ export declare function resolveEvidence(registry: VerifiedAttrsRegistry, module: string, patternFamily: string): EvidenceResolution;
92
+ /** Numeric ordering of the write-emitter threshold against a registry. */
93
+ export declare function writeThresholdNumber(registry: VerifiedAttrsRegistry): number;
94
+ /**
95
+ * Gate a write-targeted attr. Returns the resolution when it clears the
96
+ * `VB_PRESET_STORAGE_VERIFIED` threshold; throws an `EvidenceGateError`
97
+ * naming the attr, its effective level, and the registry source otherwise.
98
+ */
99
+ export declare class EvidenceGateError extends Error {
100
+ readonly patternFamily: string;
101
+ readonly module: string;
102
+ readonly resolution: EvidenceResolution;
103
+ readonly thresholdNumber: number;
104
+ readonly thresholdName: string;
105
+ constructor(patternFamily: string, module: string, resolution: EvidenceResolution, thresholdNumber: number, thresholdName: string);
106
+ }
107
+ export declare function gateWriteAttr(registry: VerifiedAttrsRegistry, module: string, patternFamily: string): EvidenceResolution;
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Verified-attrs registry consumption for the preset-emitter CLI.
3
+ *
4
+ * Loads `diviops-server/data/verified-attrs.json` and resolves the
5
+ * EFFECTIVE evidence level per `(module, pattern-family)` cell using the
6
+ * `min()` rule documented in `docs/verification/README.md`:
7
+ *
8
+ * effective_evidence = min(pattern_evidence_level,
9
+ * applicability[<module>].cell_evidence_level)
10
+ *
11
+ * A missing `applicability[<module>]` resolves to `UNVERIFIED` (0) — never
12
+ * to the pattern level. No invisible inheritance crosses the write
13
+ * threshold.
14
+ *
15
+ * This module is the consumer side of the verified-attrs registry
16
+ * contract. It does NOT mutate the registry; if a needed cell is absent
17
+ * or under-verified, the caller fails and the gap is filed as a backlog
18
+ * candidate.
19
+ */
20
+ import { readFileSync } from "node:fs";
21
+ import { fileURLToPath } from "node:url";
22
+ import { dirname, join } from "node:path";
23
+ const __dirname = dirname(fileURLToPath(import.meta.url));
24
+ /**
25
+ * Effective evidence required for a write emitter to emit an attr.
26
+ * `VB_PRESET_STORAGE_VERIFIED` has numeric ordering 4.
27
+ */
28
+ export const WRITE_EMITTER_THRESHOLD_LEVEL = "VB_PRESET_STORAGE_VERIFIED";
29
+ let cachedRegistry = null;
30
+ /**
31
+ * Load and cache `data/verified-attrs.json`. The path is resolved relative
32
+ * to the compiled module location (`dist/preset-cli/registry.js` →
33
+ * `dist/../data/verified-attrs.json`), and also tolerates the `src/` layout
34
+ * for direct ts-node-style execution / tests.
35
+ */
36
+ export function loadRegistry(explicitPath) {
37
+ if (!explicitPath && cachedRegistry)
38
+ return cachedRegistry;
39
+ const candidates = explicitPath
40
+ ? [explicitPath]
41
+ : [
42
+ // dist/preset-cli/registry.js -> dist/../data
43
+ join(__dirname, "..", "..", "data", "verified-attrs.json"),
44
+ // src/preset-cli/registry.ts -> src/../data
45
+ join(__dirname, "..", "data", "verified-attrs.json"),
46
+ ];
47
+ let lastErr = null;
48
+ for (const p of candidates) {
49
+ try {
50
+ const raw = readFileSync(p, "utf8");
51
+ const parsed = JSON.parse(raw);
52
+ if (!explicitPath)
53
+ cachedRegistry = parsed;
54
+ return parsed;
55
+ }
56
+ catch (err) {
57
+ lastErr = err;
58
+ }
59
+ }
60
+ throw new Error(`Could not load verified-attrs.json (tried: ${candidates.join(", ")}). ${lastErr instanceof Error ? lastErr.message : String(lastErr)}`);
61
+ }
62
+ /** Reset the module-level cache. Used by tests. */
63
+ export function resetRegistryCache() {
64
+ cachedRegistry = null;
65
+ }
66
+ function levelNumber(registry, name) {
67
+ if (!name)
68
+ return 0;
69
+ const n = registry.evidence_level_ordering[name];
70
+ return typeof n === "number" ? n : 0;
71
+ }
72
+ function levelName(registry, num) {
73
+ for (const [name, n] of Object.entries(registry.evidence_level_ordering)) {
74
+ if (n === num)
75
+ return name;
76
+ }
77
+ return "UNVERIFIED";
78
+ }
79
+ /**
80
+ * Find a Tier 1 / Tier 2 entry by `pattern_family`. Tier 2 is searched
81
+ * first (the button families live there), then Tier 1.
82
+ */
83
+ export function findPatternEntry(registry, patternFamily) {
84
+ const tiers = [registry.tier2 ?? [], registry.tier1 ?? []];
85
+ for (const tier of tiers) {
86
+ const hit = tier.find((e) => e.pattern_family === patternFamily);
87
+ if (hit)
88
+ return hit;
89
+ }
90
+ return undefined;
91
+ }
92
+ /**
93
+ * Resolve effective evidence for a `(module, pattern-family)` cell.
94
+ *
95
+ * Throws if the pattern family is entirely absent from the registry —
96
+ * that is an unrecoverable gap (the CLI must fail, not guess).
97
+ */
98
+ export function resolveEvidence(registry, module, patternFamily) {
99
+ const entry = findPatternEntry(registry, patternFamily);
100
+ if (!entry) {
101
+ throw new Error(`Pattern family "${patternFamily}" is absent from verified-attrs.json. ` +
102
+ `The CLI cannot emit an unregistered attr. File this as a registry-gap backlog candidate.`);
103
+ }
104
+ const patternLevel = levelNumber(registry, entry.pattern_evidence_level);
105
+ // Missing applicability[<module>] resolves to UNVERIFIED (0) — no
106
+ // invisible inheritance from the pattern level.
107
+ const cell = entry.applicability?.[module];
108
+ const applicabilityMissing = !cell;
109
+ const cellLevel = cell ? levelNumber(registry, cell.cell_evidence_level) : 0;
110
+ const effectiveLevel = Math.min(patternLevel, cellLevel);
111
+ const source = cell?.source ??
112
+ entry.pattern_evidence_source ??
113
+ "verified-attrs.json (no source field)";
114
+ return {
115
+ patternFamily,
116
+ module,
117
+ patternLevel,
118
+ patternLevelName: entry.pattern_evidence_level,
119
+ cellLevel,
120
+ cellLevelName: cell?.cell_evidence_level ?? "UNVERIFIED",
121
+ effectiveLevel,
122
+ effectiveLevelName: levelName(registry, effectiveLevel),
123
+ applicabilityMissing,
124
+ source,
125
+ cell,
126
+ };
127
+ }
128
+ /** Numeric ordering of the write-emitter threshold against a registry. */
129
+ export function writeThresholdNumber(registry) {
130
+ return levelNumber(registry, WRITE_EMITTER_THRESHOLD_LEVEL);
131
+ }
132
+ /**
133
+ * Gate a write-targeted attr. Returns the resolution when it clears the
134
+ * `VB_PRESET_STORAGE_VERIFIED` threshold; throws an `EvidenceGateError`
135
+ * naming the attr, its effective level, and the registry source otherwise.
136
+ */
137
+ export class EvidenceGateError extends Error {
138
+ patternFamily;
139
+ module;
140
+ resolution;
141
+ thresholdNumber;
142
+ thresholdName;
143
+ constructor(patternFamily, module, resolution, thresholdNumber, thresholdName) {
144
+ super(`Evidence-gate refusal: attr family "${patternFamily}" on module "${module}" ` +
145
+ `has EFFECTIVE evidence ${resolution.effectiveLevelName} (${resolution.effectiveLevel}), ` +
146
+ `below the write threshold ${thresholdName} (${thresholdNumber}). ` +
147
+ (resolution.applicabilityMissing
148
+ ? `applicability["${module}"] is absent from the registry entry — resolved to UNVERIFIED (0). `
149
+ : `pattern=${resolution.patternLevelName}(${resolution.patternLevel}), ` +
150
+ `cell=${resolution.cellLevelName}(${resolution.cellLevel}). `) +
151
+ `Registry source: ${resolution.source}. ` +
152
+ `The CLI will not emit an under-verified attr — file a registry-gap backlog candidate.`);
153
+ this.patternFamily = patternFamily;
154
+ this.module = module;
155
+ this.resolution = resolution;
156
+ this.thresholdNumber = thresholdNumber;
157
+ this.thresholdName = thresholdName;
158
+ this.name = "EvidenceGateError";
159
+ }
160
+ }
161
+ export function gateWriteAttr(registry, module, patternFamily) {
162
+ const resolution = resolveEvidence(registry, module, patternFamily);
163
+ const thresholdNumber = writeThresholdNumber(registry);
164
+ if (resolution.effectiveLevel < thresholdNumber) {
165
+ throw new EvidenceGateError(patternFamily, module, resolution, thresholdNumber, WRITE_EMITTER_THRESHOLD_LEVEL);
166
+ }
167
+ return resolution;
168
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * `$variable()` color-token helpers for the preset-emitter CLI.
3
+ *
4
+ * Divi 5.5.x color bindings use the token grammar:
5
+ *
6
+ * $variable({"type":"color","value":{"name":"gcid-heading-color","settings":{}}})$
7
+ *
8
+ * Two load-bearing rules (both from VB-verified memory):
9
+ * - The token MUST end with `)$` — a missing trailing `$` causes silent
10
+ * render failure (`feedback_variable_trailing_dollar`).
11
+ * - The `value` payload MUST be an object `{name, settings}`, not a flat
12
+ * `gcid-*` string — a flat string crashes PHP 8 in Divi's DynamicData
13
+ * resolver (`feedback_variable_color_value_object_shape`).
14
+ *
15
+ * The CLI accepts a color param that is EITHER a literal (a hex string,
16
+ * or an already-formed `$variable(...)$` token) OR a bare `gcid-*` /
17
+ * `gvid-*` token name, which this module wraps into the canonical shape.
18
+ */
19
+ /** An already-formed `$variable(...)$` token. */
20
+ export declare function isVariableToken(value: string): boolean;
21
+ /** A bare token name like `gcid-primary-color`. */
22
+ export declare function isBareTokenName(value: string): boolean;
23
+ /**
24
+ * Build a canonical `$variable()` color token from a bare token name.
25
+ *
26
+ * The inner JSON uses single-escape `\"` when the whole preset is later
27
+ * `JSON.stringify`-d — that is handled by JSON.stringify itself; here we
28
+ * return the raw string value (un-stringified), which is what belongs in
29
+ * the in-memory preset object.
30
+ */
31
+ export declare function buildColorVariableToken(tokenName: string): string;
32
+ /**
33
+ * Normalize a CLI color param to the value that belongs in the preset.
34
+ *
35
+ * - An already-formed `$variable(...)$` token → returned verbatim (the
36
+ * caller is responsible for its correctness; the trailing `)$` is
37
+ * asserted).
38
+ * - A bare `gcid-*`/`gvid-*` name → wrapped into the canonical token.
39
+ * - Anything else (a hex string, `rgba(...)`, etc.) → returned verbatim
40
+ * as a literal color value.
41
+ */
42
+ export declare function normalizeColorValue(value: string): string;