@adobe/design-data-agent-mcp 1.7.1 → 1.8.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/package.json +3 -3
- package/skills/design-data/SKILL.md +1 -1
- package/src/cli.js +6 -2
- package/src/index.js +2 -0
- package/src/tools/authoring.js +617 -42
- package/src/tools/data.js +162 -0
- package/src/tools/write.js +7 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/design-data-agent-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "MCP server and Claude Code skill for the design-data agent surface — read tools run in-process via wasm",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
33
|
-
"@adobe/design-data": "
|
|
33
|
+
"@adobe/design-data": "3.0.0",
|
|
34
34
|
"@adobe/design-data-wasm": "0.4.0",
|
|
35
|
-
"@adobe/spectrum-design-data": "0.
|
|
35
|
+
"@adobe/spectrum-design-data": "0.8.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"ava": "^6.0.1"
|
|
@@ -6,7 +6,7 @@ description: >
|
|
|
6
6
|
system, token lookup, spec-conformance, drift detection, or token authoring on custom data.
|
|
7
7
|
metadata:
|
|
8
8
|
author: adobe
|
|
9
|
-
version: "1.
|
|
9
|
+
version: "1.8.0"
|
|
10
10
|
when_to_use: >
|
|
11
11
|
Trigger on: design system, design tokens, spec-conformant, drift, validate tokens, token
|
|
12
12
|
authoring, custom dataset, DESIGN_DATA_PATH, design-data validate, design-data diff,
|
package/src/cli.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { spawn } from "child_process";
|
|
12
12
|
import { config } from "./config.js";
|
|
13
13
|
|
|
14
|
-
export function runCli(args, { timeout = 10_000 } = {}) {
|
|
14
|
+
export function runCli(args, { timeout = 10_000, stdin } = {}) {
|
|
15
15
|
return new Promise((resolve, reject) => {
|
|
16
16
|
const proc = spawn(config.bin, args, {
|
|
17
17
|
// Anchor the CLI's working directory to the resolved data root so its own
|
|
@@ -19,8 +19,12 @@ export function runCli(args, { timeout = 10_000 } = {}) {
|
|
|
19
19
|
// even when Claude Code launched the server from a monorepo subdirectory.
|
|
20
20
|
cwd: config.dataRoot,
|
|
21
21
|
// isolates CLI stdout from the MCP JSON-RPC stream on the parent's stdout
|
|
22
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
22
|
+
stdio: [stdin != null ? "pipe" : "ignore", "pipe", "pipe"],
|
|
23
23
|
});
|
|
24
|
+
if (stdin != null) {
|
|
25
|
+
proc.stdin.write(stdin);
|
|
26
|
+
proc.stdin.end();
|
|
27
|
+
}
|
|
24
28
|
let stdout = "";
|
|
25
29
|
let stderr = "";
|
|
26
30
|
proc.stdout.on("data", (d) => {
|
package/src/index.js
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
ListToolsRequestSchema,
|
|
20
20
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
21
21
|
import { createAuthoringTools } from "./tools/authoring.js";
|
|
22
|
+
import { createDataTools } from "./tools/data.js";
|
|
22
23
|
import { createReadTools } from "./tools/read.js";
|
|
23
24
|
import { createValidateTools } from "./tools/validate.js";
|
|
24
25
|
import { createDiffTools } from "./tools/diff.js";
|
|
@@ -31,6 +32,7 @@ export function createAllTools() {
|
|
|
31
32
|
...createDiffTools(),
|
|
32
33
|
...createWriteTools(),
|
|
33
34
|
...createAuthoringTools(),
|
|
35
|
+
...createDataTools(),
|
|
34
36
|
];
|
|
35
37
|
}
|
|
36
38
|
|
package/src/tools/authoring.js
CHANGED
|
@@ -9,28 +9,45 @@
|
|
|
9
9
|
// governing permissions and limitations under the License.
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Authoring-session tools for design-data-agent-mcp.
|
|
12
|
+
* Authoring-session, lifecycle, and mode-set tools for design-data-agent-mcp.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* All operations delegate to the `design-data` CLI, which is already a hard
|
|
15
|
+
* prerequisite of this server (every read tool shells out the same way). This
|
|
16
|
+
* guarantees output byte-identical to the CLI by construction and avoids a
|
|
17
|
+
* parallel JS reimplementation of the Rust cascade write path.
|
|
18
|
+
*
|
|
19
|
+
* The one exception — authoring_session_step_intent — was the original CLI
|
|
20
|
+
* shell-out; it is left unchanged.
|
|
21
|
+
*
|
|
22
|
+
* CLI surface used:
|
|
23
|
+
* design-data authoring-session {start | step intent|classification|values |
|
|
24
|
+
* commit | cancel | get | list}
|
|
25
|
+
* design-data lifecycle {edit | deprecate | rename | rewire-alias | remove}
|
|
26
|
+
* design-data mode-set {add-mode | rename-mode | remove-mode |
|
|
27
|
+
* create-mode-set | remove-mode-set}
|
|
18
28
|
*/
|
|
19
29
|
|
|
20
|
-
import {
|
|
21
|
-
startSession,
|
|
22
|
-
getSession,
|
|
23
|
-
listSessions,
|
|
24
|
-
stepClassification,
|
|
25
|
-
stepValues,
|
|
26
|
-
commitSession,
|
|
27
|
-
cancelSession,
|
|
28
|
-
} from "@adobe/design-data/session";
|
|
29
30
|
import { runCli } from "../cli.js";
|
|
30
31
|
import { config } from "../config.js";
|
|
31
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Run CLI args, throw on non-zero exit, return parsed JSON.
|
|
35
|
+
*
|
|
36
|
+
* @param {string[]} args
|
|
37
|
+
* @param {{ timeout?: number }} [opts]
|
|
38
|
+
* @returns {Promise<unknown>}
|
|
39
|
+
*/
|
|
40
|
+
async function runJson(args, { timeout = 30_000 } = {}) {
|
|
41
|
+
const { exitCode, stdout, stderr } = await runCli(args, { timeout });
|
|
42
|
+
if (exitCode !== 0)
|
|
43
|
+
throw new Error(stderr || `design-data ${args[0]} exited ${exitCode}`);
|
|
44
|
+
return JSON.parse(stdout);
|
|
45
|
+
}
|
|
46
|
+
|
|
32
47
|
export function createAuthoringTools() {
|
|
33
48
|
return [
|
|
49
|
+
// ── Authoring session ──────────────────────────────────────────────────
|
|
50
|
+
|
|
34
51
|
{
|
|
35
52
|
name: "start_authoring_session",
|
|
36
53
|
description:
|
|
@@ -54,7 +71,7 @@ export function createAuthoringTools() {
|
|
|
54
71
|
"dataset_path is required (or set DESIGN_DATA_PATH in the environment)",
|
|
55
72
|
);
|
|
56
73
|
}
|
|
57
|
-
return
|
|
74
|
+
return runJson(["authoring-session", "start", path]);
|
|
58
75
|
},
|
|
59
76
|
},
|
|
60
77
|
|
|
@@ -77,9 +94,8 @@ export function createAuthoringTools() {
|
|
|
77
94
|
additionalProperties: false,
|
|
78
95
|
},
|
|
79
96
|
async handler({ session_id, intent }) {
|
|
80
|
-
// step_intent
|
|
81
|
-
|
|
82
|
-
const { exitCode, stdout, stderr } = await runCli(
|
|
97
|
+
// step_intent uses CLI for NLP suggest ranking (not yet on wasm surface).
|
|
98
|
+
return runJson(
|
|
83
99
|
[
|
|
84
100
|
"authoring-session",
|
|
85
101
|
"step",
|
|
@@ -91,11 +107,6 @@ export function createAuthoringTools() {
|
|
|
91
107
|
],
|
|
92
108
|
{ timeout: 30_000 },
|
|
93
109
|
);
|
|
94
|
-
if (exitCode !== 0)
|
|
95
|
-
throw new Error(
|
|
96
|
-
stderr || `authoring-session step intent exited ${exitCode}`,
|
|
97
|
-
);
|
|
98
|
-
return JSON.parse(stdout);
|
|
99
110
|
},
|
|
100
111
|
},
|
|
101
112
|
|
|
@@ -103,7 +114,8 @@ export function createAuthoringTools() {
|
|
|
103
114
|
name: "authoring_session_step_classification",
|
|
104
115
|
description:
|
|
105
116
|
"Set the layer, property, and name-object fields for the token. " +
|
|
106
|
-
"name_fields is an array of {key, value} objects."
|
|
117
|
+
"name_fields is an array of {key, value} objects. " +
|
|
118
|
+
"Classification is validated against the active fields catalog.",
|
|
107
119
|
inputSchema: {
|
|
108
120
|
type: "object",
|
|
109
121
|
required: ["session_id", "layer", "property"],
|
|
@@ -134,11 +146,22 @@ export function createAuthoringTools() {
|
|
|
134
146
|
additionalProperties: false,
|
|
135
147
|
},
|
|
136
148
|
async handler({ session_id, layer, property, name_fields = [] }) {
|
|
137
|
-
|
|
149
|
+
const args = [
|
|
150
|
+
"authoring-session",
|
|
151
|
+
"step",
|
|
152
|
+
"classification",
|
|
153
|
+
"--session-id",
|
|
154
|
+
session_id,
|
|
155
|
+
"--layer",
|
|
138
156
|
layer,
|
|
157
|
+
"--property",
|
|
139
158
|
property,
|
|
140
|
-
|
|
141
|
-
})
|
|
159
|
+
];
|
|
160
|
+
for (const { key, value } of name_fields) {
|
|
161
|
+
// ponytail: CLI parse_name_fields uses split_once('=') — values with '=' are safe.
|
|
162
|
+
args.push("--name-field", `${key}=${value}`);
|
|
163
|
+
}
|
|
164
|
+
return runJson(args);
|
|
142
165
|
},
|
|
143
166
|
},
|
|
144
167
|
|
|
@@ -180,15 +203,24 @@ export function createAuthoringTools() {
|
|
|
180
203
|
additionalProperties: false,
|
|
181
204
|
},
|
|
182
205
|
async handler({ session_id, rows }) {
|
|
183
|
-
return
|
|
206
|
+
return runJson([
|
|
207
|
+
"authoring-session",
|
|
208
|
+
"step",
|
|
209
|
+
"values",
|
|
210
|
+
"--session-id",
|
|
211
|
+
session_id,
|
|
212
|
+
"--rows",
|
|
213
|
+
JSON.stringify(rows),
|
|
214
|
+
]);
|
|
184
215
|
},
|
|
185
216
|
},
|
|
186
217
|
|
|
187
218
|
{
|
|
188
219
|
name: "authoring_session_commit",
|
|
189
220
|
description:
|
|
190
|
-
"Build and write the token to disk, then remove the session. " +
|
|
191
|
-
"Requires schema_url (the JSON Schema URL for the token type) and target
|
|
221
|
+
"Build and write the token to disk as a cascade array element, then remove the session. " +
|
|
222
|
+
"Requires schema_url (the JSON Schema URL for the token type) and target " +
|
|
223
|
+
"(the *.tokens.json cascade file to write into).",
|
|
192
224
|
inputSchema: {
|
|
193
225
|
type: "object",
|
|
194
226
|
required: ["session_id", "schema_url", "target"],
|
|
@@ -202,7 +234,7 @@ export function createAuthoringTools() {
|
|
|
202
234
|
target: {
|
|
203
235
|
type: "string",
|
|
204
236
|
description:
|
|
205
|
-
"Target
|
|
237
|
+
"Target *.tokens.json cascade file to write into (created if absent, upserted if present).",
|
|
206
238
|
},
|
|
207
239
|
rationale: {
|
|
208
240
|
type: "string",
|
|
@@ -216,7 +248,6 @@ export function createAuthoringTools() {
|
|
|
216
248
|
type: "string",
|
|
217
249
|
description:
|
|
218
250
|
"Path to schemas directory containing token-types/ and token-file.json. " +
|
|
219
|
-
"Used for Layer-1 JSON-Schema validation before writing. " +
|
|
220
251
|
"Defaults to @adobe/spectrum-tokens schemas when omitted.",
|
|
221
252
|
},
|
|
222
253
|
is_override: {
|
|
@@ -236,15 +267,22 @@ export function createAuthoringTools() {
|
|
|
236
267
|
schema_path,
|
|
237
268
|
is_override = false,
|
|
238
269
|
}) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
270
|
+
const args = [
|
|
271
|
+
"authoring-session",
|
|
272
|
+
"commit",
|
|
273
|
+
"--session-id",
|
|
274
|
+
session_id,
|
|
275
|
+
"--schema-url",
|
|
276
|
+
schema_url,
|
|
277
|
+
"--target",
|
|
242
278
|
target,
|
|
279
|
+
"--rationale",
|
|
243
280
|
rationale,
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
281
|
+
];
|
|
282
|
+
if (schema_path) args.push("--schema-path", schema_path);
|
|
283
|
+
if (product_context) args.push("--product-context", product_context);
|
|
284
|
+
if (is_override) args.push("--is-override");
|
|
285
|
+
return runJson(args, { timeout: 60_000 });
|
|
248
286
|
},
|
|
249
287
|
},
|
|
250
288
|
|
|
@@ -258,7 +296,12 @@ export function createAuthoringTools() {
|
|
|
258
296
|
additionalProperties: false,
|
|
259
297
|
},
|
|
260
298
|
async handler({ session_id }) {
|
|
261
|
-
return
|
|
299
|
+
return runJson([
|
|
300
|
+
"authoring-session",
|
|
301
|
+
"cancel",
|
|
302
|
+
"--session-id",
|
|
303
|
+
session_id,
|
|
304
|
+
]);
|
|
262
305
|
},
|
|
263
306
|
},
|
|
264
307
|
|
|
@@ -272,7 +315,12 @@ export function createAuthoringTools() {
|
|
|
272
315
|
additionalProperties: false,
|
|
273
316
|
},
|
|
274
317
|
async handler({ session_id }) {
|
|
275
|
-
return
|
|
318
|
+
return runJson([
|
|
319
|
+
"authoring-session",
|
|
320
|
+
"get",
|
|
321
|
+
"--session-id",
|
|
322
|
+
session_id,
|
|
323
|
+
]);
|
|
276
324
|
},
|
|
277
325
|
},
|
|
278
326
|
|
|
@@ -285,7 +333,534 @@ export function createAuthoringTools() {
|
|
|
285
333
|
additionalProperties: false,
|
|
286
334
|
},
|
|
287
335
|
async handler() {
|
|
288
|
-
return
|
|
336
|
+
return runJson(["authoring-session", "list"]);
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
|
|
340
|
+
// ── Token lifecycle ────────────────────────────────────────────────────
|
|
341
|
+
|
|
342
|
+
{
|
|
343
|
+
name: "edit_token",
|
|
344
|
+
description:
|
|
345
|
+
"Merge field updates into an already-committed cascade token, then re-validate. " +
|
|
346
|
+
"The token's uuid is always preserved and silently ignored if included in updates.",
|
|
347
|
+
inputSchema: {
|
|
348
|
+
type: "object",
|
|
349
|
+
required: ["uuid", "target", "updates"],
|
|
350
|
+
properties: {
|
|
351
|
+
uuid: { type: "string", description: "UUID of the token to edit." },
|
|
352
|
+
target: {
|
|
353
|
+
type: "string",
|
|
354
|
+
description:
|
|
355
|
+
"Path to the *.tokens.json cascade file containing the token.",
|
|
356
|
+
},
|
|
357
|
+
updates: {
|
|
358
|
+
type: "object",
|
|
359
|
+
description:
|
|
360
|
+
'Fields to merge into the token object, e.g. { "value": "#ff0000" }.',
|
|
361
|
+
},
|
|
362
|
+
rationale: { type: "string", description: "Why the token changed." },
|
|
363
|
+
tokens_root: {
|
|
364
|
+
type: "string",
|
|
365
|
+
description:
|
|
366
|
+
"Root tokens directory — required when updates contains a $ref.",
|
|
367
|
+
},
|
|
368
|
+
schema_path: {
|
|
369
|
+
type: "string",
|
|
370
|
+
description:
|
|
371
|
+
"Schemas directory for validation (default: auto-resolved from target).",
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
additionalProperties: false,
|
|
375
|
+
},
|
|
376
|
+
async handler({
|
|
377
|
+
uuid,
|
|
378
|
+
target,
|
|
379
|
+
updates,
|
|
380
|
+
rationale = "",
|
|
381
|
+
tokens_root,
|
|
382
|
+
schema_path,
|
|
383
|
+
}) {
|
|
384
|
+
const args = [
|
|
385
|
+
"lifecycle",
|
|
386
|
+
"edit",
|
|
387
|
+
"--uuid",
|
|
388
|
+
uuid,
|
|
389
|
+
"--target",
|
|
390
|
+
target,
|
|
391
|
+
"--updates",
|
|
392
|
+
JSON.stringify(updates),
|
|
393
|
+
"--rationale",
|
|
394
|
+
rationale,
|
|
395
|
+
];
|
|
396
|
+
if (tokens_root) args.push("--tokens-root", tokens_root);
|
|
397
|
+
if (schema_path) args.push("--schema-path", schema_path);
|
|
398
|
+
return runJson(args);
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
|
|
402
|
+
{
|
|
403
|
+
name: "deprecate_token",
|
|
404
|
+
description:
|
|
405
|
+
"Stamp a deprecation marker onto a cascade token. Optionally record a replacement UUID " +
|
|
406
|
+
"and a planned-removal spec version.",
|
|
407
|
+
inputSchema: {
|
|
408
|
+
type: "object",
|
|
409
|
+
required: ["uuid", "target", "spec_version"],
|
|
410
|
+
properties: {
|
|
411
|
+
uuid: {
|
|
412
|
+
type: "string",
|
|
413
|
+
description: "UUID of the token to deprecate.",
|
|
414
|
+
},
|
|
415
|
+
target: {
|
|
416
|
+
type: "string",
|
|
417
|
+
description:
|
|
418
|
+
"Path to the *.tokens.json cascade file containing the token.",
|
|
419
|
+
},
|
|
420
|
+
spec_version: {
|
|
421
|
+
type: "string",
|
|
422
|
+
description:
|
|
423
|
+
"Dataset specVersion string to stamp into the deprecated value, e.g. '1.0.0'.",
|
|
424
|
+
},
|
|
425
|
+
deprecated_comment: {
|
|
426
|
+
type: "string",
|
|
427
|
+
description:
|
|
428
|
+
"Human-readable deprecation explanation / migration guidance.",
|
|
429
|
+
},
|
|
430
|
+
replaced_by: {
|
|
431
|
+
type: "string",
|
|
432
|
+
description:
|
|
433
|
+
"Replacement token UUID (string) or JSON array of UUIDs.",
|
|
434
|
+
},
|
|
435
|
+
planned_removal: {
|
|
436
|
+
type: "string",
|
|
437
|
+
description: "Spec version at which the token will be removed.",
|
|
438
|
+
},
|
|
439
|
+
rationale: {
|
|
440
|
+
type: "string",
|
|
441
|
+
description: "Why the token was deprecated.",
|
|
442
|
+
},
|
|
443
|
+
schema_path: {
|
|
444
|
+
type: "string",
|
|
445
|
+
description: "Schemas directory for validation.",
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
additionalProperties: false,
|
|
449
|
+
},
|
|
450
|
+
async handler({
|
|
451
|
+
uuid,
|
|
452
|
+
target,
|
|
453
|
+
spec_version,
|
|
454
|
+
deprecated_comment,
|
|
455
|
+
replaced_by,
|
|
456
|
+
planned_removal,
|
|
457
|
+
rationale = "",
|
|
458
|
+
schema_path,
|
|
459
|
+
}) {
|
|
460
|
+
const args = [
|
|
461
|
+
"lifecycle",
|
|
462
|
+
"deprecate",
|
|
463
|
+
"--uuid",
|
|
464
|
+
uuid,
|
|
465
|
+
"--target",
|
|
466
|
+
target,
|
|
467
|
+
"--spec-version",
|
|
468
|
+
spec_version,
|
|
469
|
+
"--rationale",
|
|
470
|
+
rationale,
|
|
471
|
+
];
|
|
472
|
+
if (deprecated_comment)
|
|
473
|
+
args.push("--deprecated-comment", deprecated_comment);
|
|
474
|
+
if (replaced_by) args.push("--replaced-by", replaced_by);
|
|
475
|
+
if (planned_removal) args.push("--planned-removal", planned_removal);
|
|
476
|
+
if (schema_path) args.push("--schema-path", schema_path);
|
|
477
|
+
return runJson(args);
|
|
478
|
+
},
|
|
479
|
+
},
|
|
480
|
+
|
|
481
|
+
{
|
|
482
|
+
name: "rename_token",
|
|
483
|
+
description:
|
|
484
|
+
"Assign a new name object to a cascade token, preserving its UUID. " +
|
|
485
|
+
"Optionally set a replaced_by pointer on the token being renamed.",
|
|
486
|
+
inputSchema: {
|
|
487
|
+
type: "object",
|
|
488
|
+
required: ["uuid", "target", "new_name"],
|
|
489
|
+
properties: {
|
|
490
|
+
uuid: { type: "string", description: "UUID of the token to rename." },
|
|
491
|
+
target: {
|
|
492
|
+
type: "string",
|
|
493
|
+
description:
|
|
494
|
+
"Path to the *.tokens.json cascade file containing the token.",
|
|
495
|
+
},
|
|
496
|
+
new_name: {
|
|
497
|
+
description:
|
|
498
|
+
'New name — either a JSON object ({"property":"…","component":"…"}) or a plain string.',
|
|
499
|
+
},
|
|
500
|
+
replaced_by_target: {
|
|
501
|
+
type: "string",
|
|
502
|
+
description:
|
|
503
|
+
"UUID to set as the replaced_by pointer (for retiring the old name).",
|
|
504
|
+
},
|
|
505
|
+
rationale: {
|
|
506
|
+
type: "string",
|
|
507
|
+
description: "Why the token was renamed.",
|
|
508
|
+
},
|
|
509
|
+
schema_path: {
|
|
510
|
+
type: "string",
|
|
511
|
+
description: "Schemas directory for validation.",
|
|
512
|
+
},
|
|
513
|
+
},
|
|
514
|
+
additionalProperties: false,
|
|
515
|
+
},
|
|
516
|
+
async handler({
|
|
517
|
+
uuid,
|
|
518
|
+
target,
|
|
519
|
+
new_name,
|
|
520
|
+
replaced_by_target,
|
|
521
|
+
rationale = "",
|
|
522
|
+
schema_path,
|
|
523
|
+
}) {
|
|
524
|
+
if (
|
|
525
|
+
new_name === null ||
|
|
526
|
+
new_name === undefined ||
|
|
527
|
+
(typeof new_name !== "string" && typeof new_name !== "object")
|
|
528
|
+
) {
|
|
529
|
+
throw new Error(
|
|
530
|
+
"new_name must be a string or object, got: " +
|
|
531
|
+
JSON.stringify(new_name),
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
const newNameArg =
|
|
535
|
+
typeof new_name === "object" ? JSON.stringify(new_name) : new_name;
|
|
536
|
+
const args = [
|
|
537
|
+
"lifecycle",
|
|
538
|
+
"rename",
|
|
539
|
+
"--uuid",
|
|
540
|
+
uuid,
|
|
541
|
+
"--target",
|
|
542
|
+
target,
|
|
543
|
+
"--new-name",
|
|
544
|
+
newNameArg,
|
|
545
|
+
"--rationale",
|
|
546
|
+
rationale,
|
|
547
|
+
];
|
|
548
|
+
if (replaced_by_target)
|
|
549
|
+
args.push("--replaced-by-target", replaced_by_target);
|
|
550
|
+
if (schema_path) args.push("--schema-path", schema_path);
|
|
551
|
+
return runJson(args);
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
|
|
555
|
+
{
|
|
556
|
+
name: "rewire_alias",
|
|
557
|
+
description:
|
|
558
|
+
"Change the $ref target on an alias token. The new ref target must resolve in the cascade. " +
|
|
559
|
+
"Rejects if the token is not an alias.",
|
|
560
|
+
inputSchema: {
|
|
561
|
+
type: "object",
|
|
562
|
+
required: ["uuid", "target", "new_ref", "tokens_root"],
|
|
563
|
+
properties: {
|
|
564
|
+
uuid: {
|
|
565
|
+
type: "string",
|
|
566
|
+
description: "UUID of the alias token to rewire.",
|
|
567
|
+
},
|
|
568
|
+
target: {
|
|
569
|
+
type: "string",
|
|
570
|
+
description:
|
|
571
|
+
"Path to the *.tokens.json cascade file containing the token.",
|
|
572
|
+
},
|
|
573
|
+
new_ref: {
|
|
574
|
+
type: "string",
|
|
575
|
+
description:
|
|
576
|
+
"New $ref value — must be a UUID that resolves in the cascade.",
|
|
577
|
+
},
|
|
578
|
+
tokens_root: {
|
|
579
|
+
type: "string",
|
|
580
|
+
description:
|
|
581
|
+
"Root tokens directory for ref-resolution verification.",
|
|
582
|
+
},
|
|
583
|
+
rationale: {
|
|
584
|
+
type: "string",
|
|
585
|
+
description: "Why the alias target changed.",
|
|
586
|
+
},
|
|
587
|
+
schema_path: {
|
|
588
|
+
type: "string",
|
|
589
|
+
description: "Schemas directory for validation.",
|
|
590
|
+
},
|
|
591
|
+
},
|
|
592
|
+
additionalProperties: false,
|
|
593
|
+
},
|
|
594
|
+
async handler({
|
|
595
|
+
uuid,
|
|
596
|
+
target,
|
|
597
|
+
new_ref,
|
|
598
|
+
tokens_root,
|
|
599
|
+
rationale = "",
|
|
600
|
+
schema_path,
|
|
601
|
+
}) {
|
|
602
|
+
const args = [
|
|
603
|
+
"lifecycle",
|
|
604
|
+
"rewire-alias",
|
|
605
|
+
"--uuid",
|
|
606
|
+
uuid,
|
|
607
|
+
"--target",
|
|
608
|
+
target,
|
|
609
|
+
"--new-ref",
|
|
610
|
+
new_ref,
|
|
611
|
+
"--tokens-root",
|
|
612
|
+
tokens_root,
|
|
613
|
+
"--rationale",
|
|
614
|
+
rationale,
|
|
615
|
+
];
|
|
616
|
+
if (schema_path) args.push("--schema-path", schema_path);
|
|
617
|
+
return runJson(args);
|
|
618
|
+
},
|
|
619
|
+
},
|
|
620
|
+
|
|
621
|
+
{
|
|
622
|
+
name: "remove_token",
|
|
623
|
+
description:
|
|
624
|
+
"Delete a token from a cascade file. Aborts if any other token in the dataset " +
|
|
625
|
+
"holds a $ref pointing at this token's UUID.",
|
|
626
|
+
inputSchema: {
|
|
627
|
+
type: "object",
|
|
628
|
+
required: ["uuid", "target", "tokens_root"],
|
|
629
|
+
properties: {
|
|
630
|
+
uuid: { type: "string", description: "UUID of the token to remove." },
|
|
631
|
+
target: {
|
|
632
|
+
type: "string",
|
|
633
|
+
description:
|
|
634
|
+
"Path to the *.tokens.json cascade file containing the token.",
|
|
635
|
+
},
|
|
636
|
+
tokens_root: {
|
|
637
|
+
type: "string",
|
|
638
|
+
description: "Root tokens directory for inbound-reference guard.",
|
|
639
|
+
},
|
|
640
|
+
},
|
|
641
|
+
additionalProperties: false,
|
|
642
|
+
},
|
|
643
|
+
async handler({ uuid, target, tokens_root }) {
|
|
644
|
+
return runJson([
|
|
645
|
+
"lifecycle",
|
|
646
|
+
"remove",
|
|
647
|
+
"--uuid",
|
|
648
|
+
uuid,
|
|
649
|
+
"--target",
|
|
650
|
+
target,
|
|
651
|
+
"--tokens-root",
|
|
652
|
+
tokens_root,
|
|
653
|
+
]);
|
|
654
|
+
},
|
|
655
|
+
},
|
|
656
|
+
|
|
657
|
+
// ── Mode-set ───────────────────────────────────────────────────────────
|
|
658
|
+
|
|
659
|
+
{
|
|
660
|
+
name: "add_mode",
|
|
661
|
+
description: "Append a new mode to an existing mode-set file.",
|
|
662
|
+
inputSchema: {
|
|
663
|
+
type: "object",
|
|
664
|
+
required: ["mode_set_file", "mode"],
|
|
665
|
+
properties: {
|
|
666
|
+
mode_set_file: {
|
|
667
|
+
type: "string",
|
|
668
|
+
description: "Path to the mode-set JSON file.",
|
|
669
|
+
},
|
|
670
|
+
mode: {
|
|
671
|
+
type: "string",
|
|
672
|
+
description: "Name of the new mode to add.",
|
|
673
|
+
},
|
|
674
|
+
make_default: {
|
|
675
|
+
type: "boolean",
|
|
676
|
+
description: "Make this the default mode.",
|
|
677
|
+
},
|
|
678
|
+
},
|
|
679
|
+
additionalProperties: false,
|
|
680
|
+
},
|
|
681
|
+
async handler({ mode_set_file, mode, make_default = false }) {
|
|
682
|
+
const args = [
|
|
683
|
+
"mode-set",
|
|
684
|
+
"add-mode",
|
|
685
|
+
"--mode-set-file",
|
|
686
|
+
mode_set_file,
|
|
687
|
+
"--mode",
|
|
688
|
+
mode,
|
|
689
|
+
];
|
|
690
|
+
if (make_default) args.push("--make-default");
|
|
691
|
+
return runJson(args);
|
|
692
|
+
},
|
|
693
|
+
},
|
|
694
|
+
|
|
695
|
+
{
|
|
696
|
+
name: "rename_mode",
|
|
697
|
+
description:
|
|
698
|
+
"Rename a mode in a mode-set file and propagate the change to all token files " +
|
|
699
|
+
"under tokens_root that use that mode value.",
|
|
700
|
+
inputSchema: {
|
|
701
|
+
type: "object",
|
|
702
|
+
required: ["mode_set_file", "tokens_root", "old", "new"],
|
|
703
|
+
properties: {
|
|
704
|
+
mode_set_file: {
|
|
705
|
+
type: "string",
|
|
706
|
+
description: "Path to the mode-set JSON file.",
|
|
707
|
+
},
|
|
708
|
+
tokens_root: {
|
|
709
|
+
type: "string",
|
|
710
|
+
description: "Root tokens directory for propagation.",
|
|
711
|
+
},
|
|
712
|
+
old: {
|
|
713
|
+
type: "string",
|
|
714
|
+
description: "Existing mode name to rename.",
|
|
715
|
+
},
|
|
716
|
+
new: {
|
|
717
|
+
type: "string",
|
|
718
|
+
description: "Replacement mode name.",
|
|
719
|
+
},
|
|
720
|
+
},
|
|
721
|
+
additionalProperties: false,
|
|
722
|
+
},
|
|
723
|
+
async handler({ mode_set_file, tokens_root, old, new: new_mode }) {
|
|
724
|
+
return runJson([
|
|
725
|
+
"mode-set",
|
|
726
|
+
"rename-mode",
|
|
727
|
+
"--mode-set-file",
|
|
728
|
+
mode_set_file,
|
|
729
|
+
"--tokens-root",
|
|
730
|
+
tokens_root,
|
|
731
|
+
"--old",
|
|
732
|
+
old,
|
|
733
|
+
"--new",
|
|
734
|
+
new_mode,
|
|
735
|
+
]);
|
|
736
|
+
},
|
|
737
|
+
},
|
|
738
|
+
|
|
739
|
+
{
|
|
740
|
+
name: "remove_mode",
|
|
741
|
+
description:
|
|
742
|
+
"Remove a mode from a mode-set file. Aborts if any token references that mode value " +
|
|
743
|
+
"or if the mode is the current default.",
|
|
744
|
+
inputSchema: {
|
|
745
|
+
type: "object",
|
|
746
|
+
required: ["mode_set_file", "tokens_root", "mode"],
|
|
747
|
+
properties: {
|
|
748
|
+
mode_set_file: {
|
|
749
|
+
type: "string",
|
|
750
|
+
description: "Path to the mode-set JSON file.",
|
|
751
|
+
},
|
|
752
|
+
tokens_root: {
|
|
753
|
+
type: "string",
|
|
754
|
+
description: "Root tokens directory for reference guard.",
|
|
755
|
+
},
|
|
756
|
+
mode: {
|
|
757
|
+
type: "string",
|
|
758
|
+
description: "Mode name to remove.",
|
|
759
|
+
},
|
|
760
|
+
},
|
|
761
|
+
additionalProperties: false,
|
|
762
|
+
},
|
|
763
|
+
async handler({ mode_set_file, tokens_root, mode }) {
|
|
764
|
+
return runJson([
|
|
765
|
+
"mode-set",
|
|
766
|
+
"remove-mode",
|
|
767
|
+
"--mode-set-file",
|
|
768
|
+
mode_set_file,
|
|
769
|
+
"--tokens-root",
|
|
770
|
+
tokens_root,
|
|
771
|
+
"--mode",
|
|
772
|
+
mode,
|
|
773
|
+
]);
|
|
774
|
+
},
|
|
775
|
+
},
|
|
776
|
+
|
|
777
|
+
{
|
|
778
|
+
name: "create_mode_set",
|
|
779
|
+
description:
|
|
780
|
+
"Author a new mode-set file for a new cascade dimension. " +
|
|
781
|
+
"The file must not already exist. default must be a member of modes.",
|
|
782
|
+
inputSchema: {
|
|
783
|
+
type: "object",
|
|
784
|
+
required: ["mode_set_file", "name", "modes", "default"],
|
|
785
|
+
properties: {
|
|
786
|
+
mode_set_file: {
|
|
787
|
+
type: "string",
|
|
788
|
+
description:
|
|
789
|
+
"Destination path for the new mode-set JSON file (must not exist).",
|
|
790
|
+
},
|
|
791
|
+
name: {
|
|
792
|
+
type: "string",
|
|
793
|
+
description:
|
|
794
|
+
"Logical name used as the key in token name objects, e.g. 'colorScheme'.",
|
|
795
|
+
},
|
|
796
|
+
modes: {
|
|
797
|
+
type: "array",
|
|
798
|
+
items: { type: "string" },
|
|
799
|
+
description: "Ordered list of mode names, e.g. ['light', 'dark'].",
|
|
800
|
+
},
|
|
801
|
+
default: {
|
|
802
|
+
type: "string",
|
|
803
|
+
description: "Default mode — must be a member of modes.",
|
|
804
|
+
},
|
|
805
|
+
description: {
|
|
806
|
+
type: "string",
|
|
807
|
+
description: "Human-readable description embedded in the file.",
|
|
808
|
+
},
|
|
809
|
+
},
|
|
810
|
+
additionalProperties: false,
|
|
811
|
+
},
|
|
812
|
+
async handler({
|
|
813
|
+
mode_set_file,
|
|
814
|
+
name,
|
|
815
|
+
modes,
|
|
816
|
+
default: defaultMode,
|
|
817
|
+
description = "",
|
|
818
|
+
}) {
|
|
819
|
+
return runJson([
|
|
820
|
+
"mode-set",
|
|
821
|
+
"create-mode-set",
|
|
822
|
+
"--mode-set-file",
|
|
823
|
+
mode_set_file,
|
|
824
|
+
"--name",
|
|
825
|
+
name,
|
|
826
|
+
"--modes",
|
|
827
|
+
JSON.stringify(modes),
|
|
828
|
+
"--default",
|
|
829
|
+
defaultMode,
|
|
830
|
+
"--description",
|
|
831
|
+
description,
|
|
832
|
+
]);
|
|
833
|
+
},
|
|
834
|
+
},
|
|
835
|
+
|
|
836
|
+
{
|
|
837
|
+
name: "remove_mode_set",
|
|
838
|
+
description:
|
|
839
|
+
"Delete a mode-set file. Aborts if any token in the dataset references this dimension.",
|
|
840
|
+
inputSchema: {
|
|
841
|
+
type: "object",
|
|
842
|
+
required: ["mode_set_file", "tokens_root"],
|
|
843
|
+
properties: {
|
|
844
|
+
mode_set_file: {
|
|
845
|
+
type: "string",
|
|
846
|
+
description: "Path to the mode-set JSON file to remove.",
|
|
847
|
+
},
|
|
848
|
+
tokens_root: {
|
|
849
|
+
type: "string",
|
|
850
|
+
description: "Root tokens directory for reference guard.",
|
|
851
|
+
},
|
|
852
|
+
},
|
|
853
|
+
additionalProperties: false,
|
|
854
|
+
},
|
|
855
|
+
async handler({ mode_set_file, tokens_root }) {
|
|
856
|
+
return runJson([
|
|
857
|
+
"mode-set",
|
|
858
|
+
"remove-mode-set",
|
|
859
|
+
"--mode-set-file",
|
|
860
|
+
mode_set_file,
|
|
861
|
+
"--tokens-root",
|
|
862
|
+
tokens_root,
|
|
863
|
+
]);
|
|
289
864
|
},
|
|
290
865
|
},
|
|
291
866
|
];
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// Copyright 2026 Adobe. All rights reserved.
|
|
2
|
+
// This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
// you may not use this file except in compliance with the License. You may obtain a copy
|
|
4
|
+
// of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
5
|
+
//
|
|
6
|
+
// Unless required by applicable law or agreed to in writing, software distributed under
|
|
7
|
+
// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
8
|
+
// OF ANY KIND, either express or implied. See the License for the specific language
|
|
9
|
+
// governing permissions and limitations under the License.
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* create/edit tools for non-token data categories (components, fields, registry,
|
|
13
|
+
* mode-sets, guidelines) — Phase C authoring surface.
|
|
14
|
+
*
|
|
15
|
+
* All operations delegate to `design-data data create|edit`, which validates
|
|
16
|
+
* against the JSON schema before writing. Output is byte-identical to the CLI.
|
|
17
|
+
*
|
|
18
|
+
* CLI surface used:
|
|
19
|
+
* design-data data create --category <c> --file - [--dataset <dir>] [--spec-schemas <dir>]
|
|
20
|
+
* design-data data edit --category <c> --file - [--dataset <dir>] [--spec-schemas <dir>]
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { resolve } from "path";
|
|
24
|
+
import { runCli } from "../cli.js";
|
|
25
|
+
import { config } from "../config.js";
|
|
26
|
+
|
|
27
|
+
const CATEGORIES = [
|
|
28
|
+
"components",
|
|
29
|
+
"fields",
|
|
30
|
+
"registry",
|
|
31
|
+
"mode-sets",
|
|
32
|
+
"guidelines",
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
/** Run CLI, throw on non-zero exit, return parsed JSON. */
|
|
36
|
+
async function runJson(args, stdin, { timeout = 30_000 } = {}) {
|
|
37
|
+
const { exitCode, stdout, stderr } = await runCli(args, { timeout, stdin });
|
|
38
|
+
if (exitCode !== 0)
|
|
39
|
+
throw new Error(stderr || `design-data ${args[0]} exited ${exitCode}`);
|
|
40
|
+
return JSON.parse(stdout);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Build the base args for a data create/edit call, resolving dataset root and
|
|
45
|
+
* spec schemas dir from config / env.
|
|
46
|
+
*/
|
|
47
|
+
function baseArgs(subcommand, category, datasetOverride, specSchemasOverride) {
|
|
48
|
+
// Only use config.dataPath when it ends in /tokens — the bare-cwd fallback
|
|
49
|
+
// (anchorPath(".")) does not end in tokens and must not be stripped.
|
|
50
|
+
const dataPathTokens = config.dataPath?.match(/^(.*?)\/tokens\/?$/)
|
|
51
|
+
? config.dataPath.replace(/\/tokens\/?$/, "")
|
|
52
|
+
: null;
|
|
53
|
+
const dataset =
|
|
54
|
+
datasetOverride ??
|
|
55
|
+
dataPathTokens ??
|
|
56
|
+
resolve(config.dataRoot, "packages/design-data");
|
|
57
|
+
|
|
58
|
+
const args = [
|
|
59
|
+
"data",
|
|
60
|
+
subcommand,
|
|
61
|
+
"--category",
|
|
62
|
+
category,
|
|
63
|
+
"--file",
|
|
64
|
+
"-",
|
|
65
|
+
"--dataset",
|
|
66
|
+
dataset,
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
const specSchemas =
|
|
70
|
+
specSchemasOverride ??
|
|
71
|
+
(process.env.DESIGN_DATA_SPEC_SCHEMAS
|
|
72
|
+
? resolve(process.env.DESIGN_DATA_SPEC_SCHEMAS)
|
|
73
|
+
: null);
|
|
74
|
+
if (specSchemas) args.push("--spec-schemas", specSchemas);
|
|
75
|
+
|
|
76
|
+
return { args, dataset };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function createDataTools() {
|
|
80
|
+
return [
|
|
81
|
+
{
|
|
82
|
+
name: "data_create",
|
|
83
|
+
description:
|
|
84
|
+
"Create a new non-token data object (component, field, registry entry, mode-set, or " +
|
|
85
|
+
"guideline). Validates the JSON document against the category schema before writing. " +
|
|
86
|
+
"Fails if the file already exists — use data_edit to overwrite.",
|
|
87
|
+
inputSchema: {
|
|
88
|
+
type: "object",
|
|
89
|
+
required: ["category", "document"],
|
|
90
|
+
properties: {
|
|
91
|
+
category: {
|
|
92
|
+
type: "string",
|
|
93
|
+
enum: CATEGORIES,
|
|
94
|
+
description: "Data category to create in.",
|
|
95
|
+
},
|
|
96
|
+
document: {
|
|
97
|
+
type: "object",
|
|
98
|
+
description:
|
|
99
|
+
"Full JSON document to write. Must include the category's required fields " +
|
|
100
|
+
"(e.g. `name`, `$schema`, `specVersion`). The filename is derived from the `name` field.",
|
|
101
|
+
additionalProperties: true,
|
|
102
|
+
},
|
|
103
|
+
dataset: {
|
|
104
|
+
type: "string",
|
|
105
|
+
description:
|
|
106
|
+
"Path to the dataset root (packages/design-data). Defaults to DESIGN_DATA_PATH minus /tokens.",
|
|
107
|
+
},
|
|
108
|
+
spec_schemas: {
|
|
109
|
+
type: "string",
|
|
110
|
+
description:
|
|
111
|
+
"Path to the design-data-spec schemas dir. Defaults to DESIGN_DATA_SPEC_SCHEMAS or auto-probed.",
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
additionalProperties: false,
|
|
115
|
+
},
|
|
116
|
+
async handler({ category, document, dataset, spec_schemas }) {
|
|
117
|
+
const { args } = baseArgs("create", category, dataset, spec_schemas);
|
|
118
|
+
return runJson(args, JSON.stringify(document));
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
{
|
|
123
|
+
name: "data_edit",
|
|
124
|
+
description:
|
|
125
|
+
"Replace an existing non-token data object with a new JSON document. Validates the " +
|
|
126
|
+
"document against the category schema before writing. Fails if the file does not exist " +
|
|
127
|
+
"— use data_create to create it. Edit is a full document replace, not a patch.",
|
|
128
|
+
inputSchema: {
|
|
129
|
+
type: "object",
|
|
130
|
+
required: ["category", "document"],
|
|
131
|
+
properties: {
|
|
132
|
+
category: {
|
|
133
|
+
type: "string",
|
|
134
|
+
enum: CATEGORIES,
|
|
135
|
+
description: "Data category to edit in.",
|
|
136
|
+
},
|
|
137
|
+
document: {
|
|
138
|
+
type: "object",
|
|
139
|
+
description:
|
|
140
|
+
"Full replacement JSON document. The `name` field must match the existing file.",
|
|
141
|
+
additionalProperties: true,
|
|
142
|
+
},
|
|
143
|
+
dataset: {
|
|
144
|
+
type: "string",
|
|
145
|
+
description:
|
|
146
|
+
"Path to the dataset root (packages/design-data). Defaults to DESIGN_DATA_PATH minus /tokens.",
|
|
147
|
+
},
|
|
148
|
+
spec_schemas: {
|
|
149
|
+
type: "string",
|
|
150
|
+
description:
|
|
151
|
+
"Path to the design-data-spec schemas dir. Defaults to DESIGN_DATA_SPEC_SCHEMAS or auto-probed.",
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
additionalProperties: false,
|
|
155
|
+
},
|
|
156
|
+
async handler({ category, document, dataset, spec_schemas }) {
|
|
157
|
+
const { args } = baseArgs("edit", category, dataset, spec_schemas);
|
|
158
|
+
return runJson(args, JSON.stringify(document));
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
];
|
|
162
|
+
}
|
package/src/tools/write.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// governing permissions and limitations under the License.
|
|
10
10
|
|
|
11
11
|
import { join } from "path";
|
|
12
|
-
import {
|
|
12
|
+
import { runCli } from "../cli.js";
|
|
13
13
|
import { config } from "../config.js";
|
|
14
14
|
|
|
15
15
|
export function createWriteTools() {
|
|
@@ -36,7 +36,12 @@ export function createWriteTools() {
|
|
|
36
36
|
async handler({ output, rationale } = {}) {
|
|
37
37
|
const resolvedOutput =
|
|
38
38
|
output ?? join(config.dataPath, "product-context.json");
|
|
39
|
-
|
|
39
|
+
const args = ["write", "--output", resolvedOutput];
|
|
40
|
+
if (rationale) args.push("--rationale", rationale);
|
|
41
|
+
const { exitCode, stdout, stderr } = await runCli(args);
|
|
42
|
+
if (exitCode !== 0)
|
|
43
|
+
throw new Error(stderr || `design-data write exited ${exitCode}`);
|
|
44
|
+
return stdout;
|
|
40
45
|
},
|
|
41
46
|
},
|
|
42
47
|
];
|