@flashdsm/genome 0.1.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.
@@ -0,0 +1,851 @@
1
+ // ../core/dist/schemas.js
2
+ import { z } from "zod";
3
+ var TokenTypeSchema = z.enum([
4
+ "color",
5
+ "font",
6
+ "size",
7
+ "radius",
8
+ "shadow",
9
+ "motion",
10
+ "gradient",
11
+ "border",
12
+ "effect",
13
+ "icon",
14
+ // Motion sub-types mapping onto the DTCG duration/cubicBezier/transition
15
+ // types. "motion" above stays valid for tokens authored before the split.
16
+ "duration",
17
+ "cubicBezier",
18
+ "transition"
19
+ ]);
20
+ var TokenCategorySchema = z.enum([
21
+ "colors",
22
+ "spacing",
23
+ "typography",
24
+ "radius",
25
+ "motion",
26
+ "shadow",
27
+ "border",
28
+ "effects",
29
+ "icons"
30
+ ]);
31
+ var TokenStatusSchema = z.enum(["active", "deprecated", "legacy"]);
32
+ var TokenSchema = z.object({
33
+ name: z.string(),
34
+ value: z.string(),
35
+ type: TokenTypeSchema,
36
+ category: TokenCategorySchema,
37
+ fallbackValue: z.string().optional(),
38
+ derivedFrom: z.string().optional(),
39
+ status: TokenStatusSchema.optional(),
40
+ description: z.string().optional(),
41
+ aliases: z.array(z.string()).optional(),
42
+ usedBy: z.array(z.string()).optional(),
43
+ foundationDetachedRowIds: z.array(z.string()).optional(),
44
+ foundationServingRowIds: z.array(z.string()).optional(),
45
+ metadata: z.record(z.any()).optional(),
46
+ documentation: z.string().optional(),
47
+ lastModified: z.number().optional(),
48
+ lastSynced: z.number().optional(),
49
+ id: z.string().optional(),
50
+ layer: z.enum(["primitive", "alias"]).optional(),
51
+ colorGroup: z.enum([
52
+ "primary",
53
+ "secondary",
54
+ "neutral",
55
+ "semantic",
56
+ "text",
57
+ "background",
58
+ "border",
59
+ // Semantic layer groups. Kept in lockstep with Token["colorGroup"] in
60
+ // tokens.ts - a value this schema rejects is a token the MCP server
61
+ // drops on the floor.
62
+ "brand",
63
+ "control",
64
+ "status",
65
+ "focus"
66
+ ]).optional(),
67
+ referencedTokenId: z.string().optional(),
68
+ tokenId: z.number().int().optional()
69
+ });
70
+ var DesignTokenSchema = z.object({
71
+ id: z.string(),
72
+ name: z.string(),
73
+ type: z.enum([
74
+ "color",
75
+ "spacing",
76
+ "typography",
77
+ "shadow",
78
+ "border",
79
+ "opacity",
80
+ "other"
81
+ ]),
82
+ value: z.union([z.string(), z.number()]),
83
+ description: z.string().optional(),
84
+ category: z.string().optional(),
85
+ metadata: z.record(z.any()).optional()
86
+ });
87
+ var DetectedComponentSchema = z.object({
88
+ name: z.string(),
89
+ category: z.string(),
90
+ variants: z.number().optional(),
91
+ states: z.number().optional(),
92
+ description: z.string().optional(),
93
+ detectedFrom: z.enum(["figma", "storybook", "url", "image", "github"]),
94
+ tokens: z.record(z.string()).optional()
95
+ });
96
+ var SimpleTokenSchema = z.object({
97
+ name: z.string(),
98
+ value: z.string(),
99
+ type: z.string(),
100
+ category: z.string(),
101
+ description: z.string().optional()
102
+ });
103
+ var NormalizedTokenSetSchema = z.object({
104
+ colors: z.array(SimpleTokenSchema).optional(),
105
+ spacing: z.array(SimpleTokenSchema).optional(),
106
+ typography: z.array(SimpleTokenSchema).optional(),
107
+ radius: z.array(SimpleTokenSchema).optional(),
108
+ shadows: z.array(SimpleTokenSchema).optional()
109
+ });
110
+ var JsonValueSchema = z.union([
111
+ z.record(z.any()),
112
+ z.array(z.any()),
113
+ z.null()
114
+ ]);
115
+ var DesignSystemApiCreateSchema = z.object({
116
+ name: z.string().min(1).max(256),
117
+ version: z.string().max(64).default("0.1.0"),
118
+ schema_version: z.string().max(64).default("1.0.0"),
119
+ project_id: z.number().int().nullable().optional(),
120
+ tokens: JsonValueSchema.optional(),
121
+ components: JsonValueSchema.optional()
122
+ });
123
+ var DesignSystemApiUpdateSchema = z.object({
124
+ name: z.string().min(1).max(256).optional(),
125
+ version: z.string().max(64).optional(),
126
+ schema_version: z.string().max(64).optional(),
127
+ project_id: z.number().int().nullable().optional(),
128
+ tokens: JsonValueSchema.optional(),
129
+ components: JsonValueSchema.optional()
130
+ });
131
+ var DesignSystemApiOutSchema = z.object({
132
+ id: z.number().int(),
133
+ user_id: z.number().int(),
134
+ project_id: z.number().int().nullable(),
135
+ name: z.string(),
136
+ version: z.string(),
137
+ schema_version: z.string(),
138
+ tokens: JsonValueSchema,
139
+ components: JsonValueSchema,
140
+ created_at: z.string().datetime({ offset: true }).or(z.string()),
141
+ updated_at: z.string().datetime({ offset: true }).or(z.string())
142
+ });
143
+
144
+ // ../core/dist/atomic-document.js
145
+ import { z as z2 } from "zod";
146
+ var ComponentPreviewStateSchema = z2.enum([
147
+ "default",
148
+ "hover",
149
+ "pressed",
150
+ "loading",
151
+ "error",
152
+ "open",
153
+ "disabled"
154
+ ]);
155
+ var AtomicComponentCategorySchema = z2.enum([
156
+ "actions",
157
+ "inputs",
158
+ "controls",
159
+ "navigation",
160
+ "containers",
161
+ "feedback",
162
+ "media"
163
+ ]);
164
+ var BindingSlotSchema = z2.enum([
165
+ "background",
166
+ "foreground",
167
+ "accent",
168
+ "border",
169
+ "padding",
170
+ "margin",
171
+ "gap",
172
+ "borderRadius",
173
+ "fontSize",
174
+ "fontWeight",
175
+ "lineHeight",
176
+ "letterSpacing",
177
+ "color",
178
+ "boxShadow",
179
+ "minHeight",
180
+ "transition"
181
+ ]);
182
+ var TokenRelationTypeSchema = z2.enum(["alias_of", "semantic_of"]);
183
+ var ImportSourceTypeSchema = z2.enum([
184
+ "figma",
185
+ "github",
186
+ "storybook",
187
+ "website",
188
+ "image",
189
+ "mcp",
190
+ "scratch",
191
+ "template"
192
+ ]);
193
+ var AtomicTokenSchema = TokenSchema.extend({
194
+ tokenId: z2.number().int().optional()
195
+ });
196
+ var AtomicVariantSchema = z2.object({
197
+ id: z2.string().min(1),
198
+ name: z2.string().min(1)
199
+ });
200
+ var AtomicBindingSchema = z2.object({
201
+ variantId: z2.string().optional(),
202
+ stateId: ComponentPreviewStateSchema.optional(),
203
+ slot: BindingSlotSchema.or(z2.string().min(1)),
204
+ tokenId: z2.string().min(1)
205
+ });
206
+ var AtomicComponentProvenanceSchema = z2.object({
207
+ figmaNodeId: z2.string().optional(),
208
+ gitPath: z2.string().optional()
209
+ });
210
+ var AtomicComponentSchema = z2.object({
211
+ id: z2.string().min(1),
212
+ name: z2.string().min(1),
213
+ category: AtomicComponentCategorySchema,
214
+ description: z2.string().optional(),
215
+ host: z2.string().optional(),
216
+ variants: z2.array(AtomicVariantSchema).default([]),
217
+ states: z2.array(ComponentPreviewStateSchema).default([]),
218
+ bindings: z2.array(AtomicBindingSchema).default([]),
219
+ provenance: AtomicComponentProvenanceSchema.optional(),
220
+ needsReview: z2.boolean().optional()
221
+ });
222
+ var AtomicModulePartSchema = z2.object({
223
+ slotId: z2.string().min(1),
224
+ componentId: z2.string().min(1),
225
+ variantId: z2.string().optional()
226
+ });
227
+ var AtomicModuleSchema = z2.object({
228
+ id: z2.string().min(1),
229
+ name: z2.string().min(1),
230
+ parts: z2.array(AtomicModulePartSchema).default([]),
231
+ bindings: z2.array(AtomicBindingSchema).default([])
232
+ });
233
+ var AtomicPageSlotSchema = z2.object({
234
+ moduleId: z2.string().min(1)
235
+ });
236
+ var AtomicPageBandSchema = z2.object({
237
+ slots: z2.array(AtomicPageSlotSchema).default([])
238
+ });
239
+ var AtomicPageSchema = z2.object({
240
+ id: z2.string().min(1),
241
+ name: z2.string().min(1),
242
+ bands: z2.array(AtomicPageBandSchema).default([])
243
+ });
244
+ var AtomicFoundationsSchema = z2.object({
245
+ colors: z2.array(AtomicTokenSchema).default([]),
246
+ typography: z2.array(AtomicTokenSchema).default([]),
247
+ spacing: z2.array(AtomicTokenSchema).default([]),
248
+ radius: z2.array(AtomicTokenSchema).default([]),
249
+ shadows: z2.array(AtomicTokenSchema).default([]),
250
+ border: z2.array(AtomicTokenSchema).default([]),
251
+ motion: z2.array(AtomicTokenSchema).default([]),
252
+ effects: z2.array(AtomicTokenSchema).default([]),
253
+ icons: z2.array(AtomicTokenSchema).default([])
254
+ });
255
+ var AtomicTokenRelationSchema = z2.object({
256
+ source: z2.string().min(1),
257
+ target: z2.string().min(1),
258
+ type: TokenRelationTypeSchema
259
+ });
260
+ var AtomicRelationsSchema = z2.object({
261
+ tokens: z2.array(AtomicTokenRelationSchema).default([])
262
+ });
263
+ var AtomicImportSourceSchema = z2.object({
264
+ type: ImportSourceTypeSchema,
265
+ ref: z2.string().min(1),
266
+ contentHash: z2.string().optional(),
267
+ label: z2.string().optional()
268
+ });
269
+ var AtomicDocumentMetaSchema = z2.object({
270
+ stack: z2.string().optional(),
271
+ sources: z2.array(AtomicImportSourceSchema).default([]),
272
+ brandDna: z2.record(z2.unknown()).optional(),
273
+ designStrategy: z2.record(z2.unknown()).optional()
274
+ });
275
+ var FLASH_ATOMIC_SCHEMA_VERSION = "2.0.0";
276
+ var FlashAtomicDocumentSchema = z2.object({
277
+ schemaVersion: z2.literal(FLASH_ATOMIC_SCHEMA_VERSION),
278
+ id: z2.union([z2.string(), z2.number()]).optional(),
279
+ name: z2.string().min(1),
280
+ version: z2.string().min(1).default("0.1.0"),
281
+ foundations: AtomicFoundationsSchema.default({}),
282
+ semantics: z2.array(AtomicTokenSchema).default([]),
283
+ components: z2.array(AtomicComponentSchema).default([]),
284
+ modules: z2.array(AtomicModuleSchema).default([]),
285
+ pages: z2.array(AtomicPageSchema).optional(),
286
+ relations: AtomicRelationsSchema.default({ tokens: [] }),
287
+ meta: AtomicDocumentMetaSchema.optional()
288
+ }).strict();
289
+ function parseFlashAtomicDocument(input) {
290
+ return FlashAtomicDocumentSchema.parse(input);
291
+ }
292
+
293
+ // ../core/dist/genome-config.js
294
+ import { z as z3 } from "zod";
295
+ var GENOME_CONFIG_FILENAME = "genome.config.json";
296
+ var LEGACY_CONFIG_FILENAME = "flash-ds.config.json";
297
+ var GenomeInputsSchema = z3.object({
298
+ /** Token files the repo authors: DTCG JSON and CSS custom properties. */
299
+ tokens: z3.array(z3.string().min(1)).default([]),
300
+ /** A Tailwind config to read the theme from, when there is one. */
301
+ tailwind: z3.string().min(1).optional(),
302
+ /** Directories whose components the scan should read. */
303
+ components: z3.array(z3.string().min(1)).default([])
304
+ });
305
+ var GenomeOutputsSchema = z3.object({
306
+ tokensDtcg: z3.string().min(1).default("design-tokens.json"),
307
+ tokensCss: z3.string().min(1).default("tokens.css"),
308
+ agentsMd: z3.string().min(1).default("AGENTS.md"),
309
+ cursorRules: z3.string().min(1).default(".cursor/rules/flash-genome.mdc"),
310
+ /** Directory for the kit, tokens, components mirror and the lock file. */
311
+ genomeDir: z3.string().min(1).default("genome/"),
312
+ /**
313
+ * Where the icons folder lands: SVG drawings, the Material Symbols
314
+ * stylesheet and the licence notice.
315
+ *
316
+ * Optional rather than defaulted, so an unset value can follow `genomeDir`
317
+ * (`<genomeDir>/icons/`) instead of pinning a path that ignores it. Kept out
318
+ * of the repo root on purpose: plenty of repos already have an `icons/`,
319
+ * and Flash's glyphs arriving in the middle of the team's own would be
320
+ * worse than one line of config to move them.
321
+ */
322
+ icons: z3.string().min(1).optional()
323
+ });
324
+ var GenomeCodebaseSchema = z3.object({
325
+ id: z3.string().min(1),
326
+ name: z3.string().min(1)
327
+ });
328
+ var GenomeConfigSchema = z3.object({
329
+ version: z3.literal(2),
330
+ /** The design system id in Flash Web this repo is bound to. */
331
+ genomeId: z3.number().int().positive(),
332
+ name: z3.string().min(1),
333
+ apiUrl: z3.string().url().default("https://flashdsm.com"),
334
+ /** The web revision the repo last pulled; drift is measured against it. */
335
+ revision: z3.union([z3.string(), z3.number()]).optional(),
336
+ /** Hash of the last pulled projection, as `documentToRepoBundle` computes it. */
337
+ contentHash: z3.string().optional(),
338
+ inputs: GenomeInputsSchema.default({}),
339
+ outputs: GenomeOutputsSchema.default({}),
340
+ framework: z3.string().optional(),
341
+ /** Absent only in configs written before codebase identity existed. */
342
+ codebase: GenomeCodebaseSchema.optional()
343
+ });
344
+
345
+ // ../core/dist/change-plan.js
346
+ import { z as z4 } from "zod";
347
+ var ChangeActionSchema = z4.enum(["create", "update", "skip", "flag"]);
348
+ var ChangeTargetKindSchema = z4.enum([
349
+ "file",
350
+ "token",
351
+ "variable",
352
+ "style",
353
+ "component"
354
+ ]);
355
+ var ChangePlanEntrySchema = z4.object({
356
+ action: ChangeActionSchema,
357
+ targetKind: ChangeTargetKindSchema,
358
+ /** Repo path, token name, variable path, style name, component id. */
359
+ path: z4.string().min(1),
360
+ /** The value on the target before; null when it did not exist. */
361
+ before: z4.string().nullable().optional(),
362
+ /** The value after; null when the entry removes the target. */
363
+ after: z4.string().nullable().optional(),
364
+ /** Why an entry is flagged or skipped, in a sentence a person can act on. */
365
+ reason: z4.string().optional(),
366
+ /** Machine code for the reason: `alias-missing`, `path-collision`, … */
367
+ code: z4.string().optional()
368
+ });
369
+ var ChangePlanCountsSchema = z4.object({
370
+ create: z4.number().int().nonnegative(),
371
+ update: z4.number().int().nonnegative(),
372
+ skip: z4.number().int().nonnegative(),
373
+ flag: z4.number().int().nonnegative()
374
+ });
375
+ var ChangePlanSchema = z4.object({
376
+ /** True when nothing was written; the entries say what would be. */
377
+ dryRun: z4.boolean(),
378
+ entries: z4.array(ChangePlanEntrySchema),
379
+ counts: ChangePlanCountsSchema
380
+ });
381
+ function countChangePlan(entries) {
382
+ const counts = { create: 0, update: 0, skip: 0, flag: 0 };
383
+ for (const entry of entries)
384
+ counts[entry.action] += 1;
385
+ return counts;
386
+ }
387
+ function makeChangePlan(entries, dryRun) {
388
+ return { dryRun, entries: [...entries], counts: countChangePlan(entries) };
389
+ }
390
+ function tokenChangeToPlanEntry(change) {
391
+ const base = {
392
+ targetKind: "token",
393
+ path: change.name,
394
+ before: change.before ?? null,
395
+ after: change.after ?? null
396
+ };
397
+ switch (change.kind) {
398
+ case "added":
399
+ return { ...base, action: "create" };
400
+ case "changed":
401
+ return { ...base, action: "update" };
402
+ case "removed":
403
+ return {
404
+ ...base,
405
+ action: "flag",
406
+ code: "removed",
407
+ reason: "Not in the incoming set; applying removes it from the system."
408
+ };
409
+ }
410
+ }
411
+
412
+ // ../core/dist/graph.js
413
+ var FOUNDATION_ORDER = [
414
+ "colors",
415
+ "typography",
416
+ "spacing",
417
+ "radius",
418
+ "shadows",
419
+ "border",
420
+ "motion",
421
+ "effects",
422
+ "icons"
423
+ ];
424
+ var isObject = (v) => !!v && typeof v === "object" && !Array.isArray(v);
425
+ var str = (v) => typeof v === "string" && v ? v : void 0;
426
+ var list = (v) => Array.isArray(v) ? v : [];
427
+ var byId = (a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
428
+ function plural(n, one, many) {
429
+ return `${n} ${n === 1 ? one : many}`;
430
+ }
431
+ function tokensOf(doc) {
432
+ const out = /* @__PURE__ */ new Map();
433
+ const foundations = isObject(doc.foundations) ? doc.foundations : {};
434
+ const lists = [
435
+ ...FOUNDATION_ORDER.map((key) => list(foundations[key])),
436
+ list(doc.semantics)
437
+ ];
438
+ for (const tokens of lists) {
439
+ for (const token of tokens) {
440
+ if (!isObject(token))
441
+ continue;
442
+ const key = str(token.id) ?? str(token.name);
443
+ if (key && !out.has(key))
444
+ out.set(key, token);
445
+ }
446
+ }
447
+ return out;
448
+ }
449
+ function resolver(tokens) {
450
+ const byName = /* @__PURE__ */ new Map();
451
+ const byLower = /* @__PURE__ */ new Map();
452
+ for (const [key, token] of tokens) {
453
+ const name = str(token.name);
454
+ if (name && !byName.has(name))
455
+ byName.set(name, key);
456
+ for (const candidate of [key, name]) {
457
+ if (candidate && !byLower.has(candidate.toLowerCase())) {
458
+ byLower.set(candidate.toLowerCase(), key);
459
+ }
460
+ }
461
+ }
462
+ return (ref) => {
463
+ const wanted = str(ref);
464
+ if (!wanted)
465
+ return void 0;
466
+ if (tokens.has(wanted))
467
+ return wanted;
468
+ return byName.get(wanted) ?? byLower.get(wanted.toLowerCase());
469
+ };
470
+ }
471
+ function edgesOf(doc, tokens, resolve2) {
472
+ const seen = /* @__PURE__ */ new Set();
473
+ const edges = [];
474
+ const add = (source, target, type = "alias_of") => {
475
+ if (!source || !target || source === target)
476
+ return;
477
+ const key = `${source}\0${target}`;
478
+ if (seen.has(key))
479
+ return;
480
+ seen.add(key);
481
+ edges.push({ source, target, type });
482
+ };
483
+ for (const relation of list(doc.relations?.tokens)) {
484
+ if (!isObject(relation))
485
+ continue;
486
+ add(resolve2(relation.source), resolve2(relation.target), str(relation.type) ?? "alias_of");
487
+ }
488
+ for (const [key, token] of tokens) {
489
+ add(key, resolve2(token.referencedTokenId));
490
+ }
491
+ return edges.sort((a, b) => a.source !== b.source ? a.source < b.source ? -1 : 1 : a.target < b.target ? -1 : a.target > b.target ? 1 : 0);
492
+ }
493
+ function bindingsOf(owner) {
494
+ const out = [];
495
+ for (const binding of list(owner.bindings)) {
496
+ if (!isObject(binding))
497
+ continue;
498
+ const slot = str(binding.slot);
499
+ const tokenId = str(binding.tokenId);
500
+ if (!slot || !tokenId)
501
+ continue;
502
+ const entry = { slot, tokenId };
503
+ const variantId = str(binding.variantId);
504
+ const stateId = str(binding.stateId);
505
+ if (variantId)
506
+ entry.variantId = variantId;
507
+ if (stateId)
508
+ entry.stateId = stateId;
509
+ out.push(entry);
510
+ }
511
+ return out;
512
+ }
513
+ function ownersOf(items) {
514
+ return items.filter((item) => isObject(item) && !!str(item.id) && !!str(item.name));
515
+ }
516
+ function tokenSummary(key, token) {
517
+ const out = { id: key, name: str(token.name) ?? key };
518
+ const layer = str(token.layer);
519
+ const category = str(token.category);
520
+ if (layer)
521
+ out.layer = layer;
522
+ if (category)
523
+ out.category = category;
524
+ return out;
525
+ }
526
+ function listModules(doc) {
527
+ const components = new Map(ownersOf(list(doc.components)).map((c) => [str(c.id), str(c.name)]));
528
+ return ownersOf(list(doc.modules)).map((module) => {
529
+ const used = [];
530
+ const seen = /* @__PURE__ */ new Set();
531
+ const parts = list(module.parts).filter(isObject);
532
+ for (const part of parts) {
533
+ const id = str(part.componentId);
534
+ if (!id || seen.has(id))
535
+ continue;
536
+ seen.add(id);
537
+ used.push({ id, name: components.get(id) ?? null });
538
+ }
539
+ return {
540
+ id: str(module.id),
541
+ name: str(module.name),
542
+ parts: parts.length,
543
+ components: used,
544
+ bindings: bindingsOf(module).length
545
+ };
546
+ });
547
+ }
548
+ function getModule(doc, ref) {
549
+ const modules = ownersOf(list(doc.modules));
550
+ const wanted = ref.trim().toLowerCase();
551
+ const module = modules.find((m) => str(m.id) === ref) ?? modules.find((m) => str(m.name) === ref) ?? modules.find((m) => str(m.id).toLowerCase() === wanted || str(m.name).toLowerCase() === wanted);
552
+ if (!module) {
553
+ return {
554
+ found: false,
555
+ available: modules.map((m) => str(m.name))
556
+ };
557
+ }
558
+ const components = new Map(ownersOf(list(doc.components)).map((c) => [str(c.id), str(c.name)]));
559
+ const parts = list(module.parts).filter(isObject).flatMap((part) => {
560
+ const slotId = str(part.slotId);
561
+ const componentId = str(part.componentId);
562
+ if (!slotId || !componentId)
563
+ return [];
564
+ const entry = {
565
+ slotId,
566
+ componentId,
567
+ componentName: components.get(componentId) ?? null
568
+ };
569
+ const variantId = str(part.variantId);
570
+ if (variantId)
571
+ entry.variantId = variantId;
572
+ return [entry];
573
+ });
574
+ return {
575
+ found: true,
576
+ module: {
577
+ id: str(module.id),
578
+ name: str(module.name),
579
+ parts,
580
+ bindings: bindingsOf(module)
581
+ }
582
+ };
583
+ }
584
+ function getRelations(doc, ref) {
585
+ const tokens = tokensOf(doc);
586
+ const resolve2 = resolver(tokens);
587
+ const edges = edgesOf(doc, tokens, resolve2);
588
+ const components = ownersOf(list(doc.components));
589
+ const modules = ownersOf(list(doc.modules));
590
+ if (ref === void 0 || ref === "") {
591
+ const bindings = [];
592
+ for (const [kind, owners2] of [
593
+ ["component", components],
594
+ ["module", modules]
595
+ ]) {
596
+ for (const owner of owners2) {
597
+ for (const binding of bindingsOf(owner)) {
598
+ bindings.push({ owner: kind, ownerId: str(owner.id), ...binding });
599
+ }
600
+ }
601
+ }
602
+ const parts = [];
603
+ for (const module of modules) {
604
+ for (const part of list(module.parts).filter(isObject)) {
605
+ const slotId = str(part.slotId);
606
+ const componentId = str(part.componentId);
607
+ if (!slotId || !componentId)
608
+ continue;
609
+ parts.push({ moduleId: str(module.id), slotId, componentId });
610
+ }
611
+ }
612
+ return { tokens: edges, bindings, parts };
613
+ }
614
+ const key = resolve2(ref);
615
+ if (!key)
616
+ return { found: false, token: ref };
617
+ const boundHere = (owner) => bindingsOf(owner).filter((b) => resolve2(b.tokenId) === key);
618
+ const owners = (items) => items.map((owner) => ({
619
+ id: str(owner.id),
620
+ name: str(owner.name),
621
+ bindings: boundHere(owner)
622
+ })).filter((o) => o.bindings.length).sort(byId);
623
+ return {
624
+ found: true,
625
+ token: tokenSummary(key, tokens.get(key)),
626
+ aliasOf: edges.filter((e) => e.source === key).map((e) => ({ id: e.target, type: e.type })),
627
+ aliasedBy: edges.filter((e) => e.target === key).map((e) => ({ id: e.source, type: e.type })),
628
+ components: owners(components),
629
+ modules: owners(modules)
630
+ };
631
+ }
632
+ function getImpact(doc, ref) {
633
+ const tokens = tokensOf(doc);
634
+ const resolve2 = resolver(tokens);
635
+ const root = resolve2(ref);
636
+ if (!root)
637
+ return { found: false, token: ref };
638
+ const edges = edgesOf(doc, tokens, resolve2);
639
+ const pointedAtBy = /* @__PURE__ */ new Map();
640
+ for (const edge of edges) {
641
+ const sources = pointedAtBy.get(edge.target) ?? [];
642
+ sources.push(edge.source);
643
+ pointedAtBy.set(edge.target, sources);
644
+ }
645
+ const reached = /* @__PURE__ */ new Map();
646
+ let frontier = [root];
647
+ let depth = 0;
648
+ while (frontier.length) {
649
+ depth += 1;
650
+ const next = [];
651
+ for (const target of frontier) {
652
+ for (const source of (pointedAtBy.get(target) ?? []).slice().sort()) {
653
+ if (source === root || reached.has(source))
654
+ continue;
655
+ reached.set(source, { depth, via: target });
656
+ next.push(source);
657
+ }
658
+ }
659
+ frontier = next.sort();
660
+ }
661
+ const affected = /* @__PURE__ */ new Set([root, ...reached.keys()]);
662
+ const hits = (owner) => bindingsOf(owner).filter((b) => {
663
+ const key = resolve2(b.tokenId);
664
+ return key !== void 0 && affected.has(key);
665
+ });
666
+ const components = ownersOf(list(doc.components)).map((c) => ({ id: str(c.id), name: str(c.name), bindings: hits(c) })).filter((c) => c.bindings.length).sort(byId);
667
+ const affectedComponents = new Set(components.map((c) => c.id));
668
+ const modules = ownersOf(list(doc.modules)).map((m) => {
669
+ const reasons = hits(m).map((b) => ({ kind: "binding", ...b }));
670
+ for (const part of list(m.parts).filter(isObject)) {
671
+ const componentId = str(part.componentId);
672
+ const slotId = str(part.slotId);
673
+ if (componentId && slotId && affectedComponents.has(componentId)) {
674
+ reasons.push({ kind: "part", slotId, componentId });
675
+ }
676
+ }
677
+ return { id: str(m.id), name: str(m.name), reasons };
678
+ }).filter((m) => m.reasons.length).sort(byId);
679
+ const reachedTokens = [...reached.entries()].map(([id, r]) => ({
680
+ ...tokenSummary(id, tokens.get(id)),
681
+ depth: r.depth,
682
+ via: r.via
683
+ })).sort((a, b) => a.depth !== b.depth ? a.depth - b.depth : byId(a, b));
684
+ return {
685
+ found: true,
686
+ token: tokenSummary(root, tokens.get(root)),
687
+ tokens: reachedTokens,
688
+ components,
689
+ modules,
690
+ summary: `Changing ${root} reaches ${plural(reachedTokens.length, "token", "tokens")}, ${plural(components.length, "component", "components")} and ${plural(modules.length, "module", "modules")}.`
691
+ };
692
+ }
693
+
694
+ // src/load-tokens.ts
695
+ import { readFile } from "fs/promises";
696
+ import { dirname, isAbsolute, resolve } from "path";
697
+ var CATEGORY_KEYS = TokenCategorySchema.options;
698
+ function isRecord(value) {
699
+ return typeof value === "object" && value !== null && !Array.isArray(value);
700
+ }
701
+ function parseToken(value) {
702
+ const result = TokenSchema.safeParse(value);
703
+ return result.success ? result.data : null;
704
+ }
705
+ function normalizeTokenDocument(raw) {
706
+ if (Array.isArray(raw)) {
707
+ return {
708
+ tokens: raw.map(parseToken).filter((t) => t !== null)
709
+ };
710
+ }
711
+ if (!isRecord(raw)) {
712
+ return { tokens: [] };
713
+ }
714
+ const name = typeof raw.name === "string" ? raw.name : void 0;
715
+ if (Array.isArray(raw.tokens)) {
716
+ return {
717
+ name,
718
+ tokens: raw.tokens.map(parseToken).filter((t) => t !== null)
719
+ };
720
+ }
721
+ const tokens = [];
722
+ for (const category of CATEGORY_KEYS) {
723
+ const bucket = raw[category];
724
+ if (!Array.isArray(bucket)) continue;
725
+ for (const item of bucket) {
726
+ if (!isRecord(item)) continue;
727
+ const withCategory = { ...item, category };
728
+ const token = parseToken(withCategory);
729
+ if (token) tokens.push(token);
730
+ }
731
+ }
732
+ const aliases = {
733
+ shadows: "shadow",
734
+ borderRadius: "radius",
735
+ color: "colors"
736
+ };
737
+ for (const [key, category] of Object.entries(aliases)) {
738
+ const bucket = raw[key];
739
+ if (!Array.isArray(bucket)) continue;
740
+ for (const item of bucket) {
741
+ if (!isRecord(item)) continue;
742
+ const withCategory = { ...item, category };
743
+ const token = parseToken(withCategory);
744
+ if (token) tokens.push(token);
745
+ }
746
+ }
747
+ return { name, tokens };
748
+ }
749
+ function configCandidates(explicit, cwd) {
750
+ if (explicit) {
751
+ return [isAbsolute(explicit) ? explicit : resolve(cwd, explicit)];
752
+ }
753
+ return [
754
+ resolve(cwd, GENOME_CONFIG_FILENAME),
755
+ resolve(cwd, LEGACY_CONFIG_FILENAME)
756
+ ];
757
+ }
758
+ async function loadConfig(configPath) {
759
+ try {
760
+ const text = await readFile(configPath, "utf8");
761
+ const parsed = JSON.parse(text);
762
+ if (!isRecord(parsed)) return null;
763
+ if (parsed.version === 2) {
764
+ const genome = GenomeConfigSchema.safeParse(parsed);
765
+ if (!genome.success) return null;
766
+ return {
767
+ version: 2,
768
+ name: genome.data.name,
769
+ tokenFile: genome.data.outputs.tokensDtcg
770
+ };
771
+ }
772
+ if (typeof parsed.tokenFile !== "string") return null;
773
+ return {
774
+ version: typeof parsed.version === "number" ? parsed.version : void 0,
775
+ name: typeof parsed.name === "string" ? parsed.name : void 0,
776
+ tokenFile: parsed.tokenFile
777
+ };
778
+ } catch {
779
+ return null;
780
+ }
781
+ }
782
+ async function resolveTokenFilePath(options) {
783
+ const cwd = options.cwd ?? process.cwd();
784
+ if (options.tokensPath) {
785
+ return isAbsolute(options.tokensPath) ? options.tokensPath : resolve(cwd, options.tokensPath);
786
+ }
787
+ for (const configPath of configCandidates(options.configPath, cwd)) {
788
+ const config = await loadConfig(configPath);
789
+ if (!config) continue;
790
+ const base = dirname(configPath);
791
+ return isAbsolute(config.tokenFile) ? config.tokenFile : resolve(base, config.tokenFile);
792
+ }
793
+ return resolve(cwd, "design-tokens.json");
794
+ }
795
+ async function loadTokensFromFile(tokenFile) {
796
+ const text = await readFile(tokenFile, "utf8");
797
+ const raw = JSON.parse(text);
798
+ const { tokens, name } = normalizeTokenDocument(raw);
799
+ return { source: tokenFile, tokens, name };
800
+ }
801
+ function filterTokensByCategory(tokens, category) {
802
+ if (!category) return tokens;
803
+ return tokens.filter((t) => t.category === category);
804
+ }
805
+
806
+ // src/tools/get-design-tokens.ts
807
+ function parseCategoryInput(value) {
808
+ if (value === void 0 || value === null || value === "") {
809
+ return void 0;
810
+ }
811
+ const parsed = TokenCategorySchema.safeParse(value);
812
+ if (!parsed.success) {
813
+ throw new Error(
814
+ `Invalid category "${String(value)}". Expected one of: ${TokenCategorySchema.options.join(", ")}`
815
+ );
816
+ }
817
+ return parsed.data;
818
+ }
819
+ async function getDesignTokens(tokenFile, args = {}) {
820
+ const loaded = await loadTokensFromFile(tokenFile);
821
+ const tokens = filterTokensByCategory(loaded.tokens, args.category);
822
+ return {
823
+ source: loaded.source,
824
+ name: loaded.name,
825
+ category: args.category,
826
+ count: tokens.length,
827
+ tokens
828
+ };
829
+ }
830
+
831
+ export {
832
+ TokenTypeSchema,
833
+ TokenCategorySchema,
834
+ TokenSchema,
835
+ parseFlashAtomicDocument,
836
+ makeChangePlan,
837
+ tokenChangeToPlanEntry,
838
+ GENOME_CONFIG_FILENAME,
839
+ LEGACY_CONFIG_FILENAME,
840
+ GenomeConfigSchema,
841
+ listModules,
842
+ getModule,
843
+ getRelations,
844
+ getImpact,
845
+ normalizeTokenDocument,
846
+ configCandidates,
847
+ resolveTokenFilePath,
848
+ loadTokensFromFile,
849
+ parseCategoryInput,
850
+ getDesignTokens
851
+ };