rafters 0.0.38 → 0.0.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +242 -37
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -60352,6 +60352,10 @@ var TOOL_DEFINITIONS = [
60352
60352
  category: {
60353
60353
  type: "string",
60354
60354
  description: 'Token category (required for create). E.g., "color", "spacing", "font".'
60355
+ },
60356
+ dark: {
60357
+ type: "string",
60358
+ description: 'Dark mode color reference as "family-position" (e.g., "neutral-950"). For semantic tokens, sets dependsOn[1] so the CSS dark mode layer uses this reference instead of the light value.'
60355
60359
  }
60356
60360
  },
60357
60361
  required: ["name"]
@@ -61050,6 +61054,19 @@ var RaftersToolHandler = class _RaftersToolHandler {
61050
61054
  }
61051
61055
  }
61052
61056
  // ==================== Token Write Operations ====================
61057
+ static VALID_SCALE_POSITIONS = /^(50|100|200|300|400|500|600|700|800|900|950)$/;
61058
+ /**
61059
+ * Parse "family-position" string (e.g., "neutral-950") into { family, position }.
61060
+ * Returns null if the string is not a valid color reference.
61061
+ */
61062
+ static parseColorRef(ref) {
61063
+ const lastDash = ref.lastIndexOf("-");
61064
+ if (lastDash <= 0) return null;
61065
+ const position = ref.slice(lastDash + 1);
61066
+ const family = ref.slice(0, lastDash);
61067
+ if (!_RaftersToolHandler.VALID_SCALE_POSITIONS.test(position)) return null;
61068
+ return { family, position };
61069
+ }
61053
61070
  /**
61054
61071
  * Handle set, create, and reset actions for rafters_token.
61055
61072
  * Loads the full registry, mutates, cascades, persists, and regenerates outputs.
@@ -61058,6 +61075,7 @@ var RaftersToolHandler = class _RaftersToolHandler {
61058
61075
  const name2 = args.name;
61059
61076
  const reason = args.reason;
61060
61077
  const value2 = args.value;
61078
+ const dark = args.dark;
61061
61079
  if (!reason) {
61062
61080
  return {
61063
61081
  content: [
@@ -61103,14 +61121,64 @@ var RaftersToolHandler = class _RaftersToolHandler {
61103
61121
  isError: true
61104
61122
  };
61105
61123
  }
61124
+ if (dark) {
61125
+ if (existing.namespace !== "semantic") {
61126
+ return {
61127
+ content: [
61128
+ {
61129
+ type: "text",
61130
+ text: JSON.stringify({
61131
+ error: `The "dark" parameter only applies to semantic color tokens. Token "${name2}" is in the "${existing.namespace}" namespace.`
61132
+ })
61133
+ }
61134
+ ],
61135
+ isError: true
61136
+ };
61137
+ }
61138
+ if (!_RaftersToolHandler.parseColorRef(dark)) {
61139
+ return {
61140
+ content: [
61141
+ {
61142
+ type: "text",
61143
+ text: JSON.stringify({
61144
+ error: `Invalid dark reference "${dark}". Use format "family-position" (e.g., "neutral-950"). Valid positions: 50, 100-900 by 100, 950.`
61145
+ })
61146
+ }
61147
+ ],
61148
+ isError: true
61149
+ };
61150
+ }
61151
+ }
61106
61152
  const previousValue = existing.value;
61107
61153
  existing.userOverride = {
61108
61154
  previousValue: typeof previousValue === "string" ? previousValue : JSON.stringify(previousValue),
61109
61155
  reason
61110
61156
  };
61111
- await registry2.set(name2, value2);
61157
+ const parsed = existing.namespace === "semantic" && typeof value2 === "string" ? _RaftersToolHandler.parseColorRef(value2) : null;
61158
+ if (existing.namespace === "semantic" && typeof value2 === "string" && !parsed) {
61159
+ return {
61160
+ content: [
61161
+ {
61162
+ type: "text",
61163
+ text: JSON.stringify({
61164
+ error: `Semantic token "${name2}" requires a color reference in "family-position" format (e.g., "neutral-50"), not "${value2}". Use rafters_vocabulary to find available color families.`
61165
+ })
61166
+ }
61167
+ ],
61168
+ isError: true
61169
+ };
61170
+ }
61171
+ if (parsed) {
61172
+ existing.value = parsed;
61173
+ const lightRefStr = `${parsed.family}-${parsed.position}`;
61174
+ const darkRef = dark ?? existing.dependsOn?.[1] ?? lightRefStr;
61175
+ existing.dependsOn = [lightRefStr, darkRef];
61176
+ } else {
61177
+ existing.value = value2;
61178
+ }
61179
+ await registry2.setToken(existing);
61112
61180
  const affected = this.getAffectedTokens(registry2, name2);
61113
- await this.regenerateOutputs(registry2);
61181
+ const outputFiles = await this.regenerateOutputs(registry2);
61114
61182
  return {
61115
61183
  content: [
61116
61184
  {
@@ -61120,7 +61188,13 @@ var RaftersToolHandler = class _RaftersToolHandler {
61120
61188
  action: "set",
61121
61189
  name: name2,
61122
61190
  reason,
61123
- cascaded: affected
61191
+ persisted: {
61192
+ value: existing.value,
61193
+ dependsOn: existing.dependsOn,
61194
+ namespace: existing.namespace
61195
+ },
61196
+ cascaded: affected,
61197
+ outputFiles
61124
61198
  })
61125
61199
  }
61126
61200
  ]
@@ -61177,7 +61251,7 @@ var RaftersToolHandler = class _RaftersToolHandler {
61177
61251
  };
61178
61252
  registry2.add(newToken);
61179
61253
  await this.adapter.save(registry2.list());
61180
- await this.regenerateOutputs(registry2);
61254
+ const outputFiles = await this.regenerateOutputs(registry2);
61181
61255
  return {
61182
61256
  content: [
61183
61257
  {
@@ -61187,7 +61261,13 @@ var RaftersToolHandler = class _RaftersToolHandler {
61187
61261
  action: "create",
61188
61262
  name: name2,
61189
61263
  namespace,
61190
- reason
61264
+ reason,
61265
+ persisted: {
61266
+ value: newToken.value,
61267
+ dependsOn: newToken.dependsOn,
61268
+ namespace: newToken.namespace
61269
+ },
61270
+ outputFiles
61191
61271
  })
61192
61272
  }
61193
61273
  ]
@@ -61204,8 +61284,9 @@ var RaftersToolHandler = class _RaftersToolHandler {
61204
61284
  };
61205
61285
  }
61206
61286
  await registry2.set(name2, COMPUTED);
61287
+ const resolved = registry2.get(name2);
61207
61288
  const affected = this.getAffectedTokens(registry2, name2);
61208
- await this.regenerateOutputs(registry2);
61289
+ const outputFiles = await this.regenerateOutputs(registry2);
61209
61290
  return {
61210
61291
  content: [
61211
61292
  {
@@ -61215,7 +61296,13 @@ var RaftersToolHandler = class _RaftersToolHandler {
61215
61296
  action: "reset",
61216
61297
  name: name2,
61217
61298
  reason,
61218
- cascaded: affected
61299
+ persisted: {
61300
+ value: resolved?.value,
61301
+ dependsOn: resolved?.dependsOn,
61302
+ namespace: resolved?.namespace
61303
+ },
61304
+ cascaded: affected,
61305
+ outputFiles
61219
61306
  })
61220
61307
  }
61221
61308
  ]
@@ -61251,10 +61338,11 @@ var RaftersToolHandler = class _RaftersToolHandler {
61251
61338
  return affected;
61252
61339
  }
61253
61340
  /**
61254
- * Regenerate output files (CSS, TS, DTCG) from registry state
61341
+ * Regenerate output files (CSS, TS, DTCG) from registry state.
61342
+ * Returns list of files written so callers can report what changed.
61255
61343
  */
61256
61344
  async regenerateOutputs(registry2) {
61257
- if (!this.projectRoot) return;
61345
+ if (!this.projectRoot) return [];
61258
61346
  const paths = getRaftersPaths(this.projectRoot);
61259
61347
  const config3 = await this.loadConfig();
61260
61348
  const exports = config3?.exports ?? {
@@ -61264,20 +61352,28 @@ var RaftersToolHandler = class _RaftersToolHandler {
61264
61352
  compiled: false
61265
61353
  };
61266
61354
  const shadcn = config3?.shadcn ?? false;
61355
+ const written = [];
61267
61356
  await mkdir4(paths.output, { recursive: true });
61268
61357
  if (exports.tailwind) {
61269
61358
  const darkMode = config3?.darkMode ?? "class";
61270
61359
  const css3 = registryToTailwind(registry2, { includeImport: !shadcn, darkMode });
61271
- await writeFile4(join10(paths.output, "rafters.css"), css3);
61360
+ const cssPath = join10(paths.output, "rafters.css");
61361
+ await writeFile4(cssPath, css3);
61362
+ written.push(relative2(this.projectRoot, cssPath));
61272
61363
  }
61273
61364
  if (exports.typescript) {
61274
61365
  const ts = registryToTypeScript(registry2, { includeJSDoc: true });
61275
- await writeFile4(join10(paths.output, "rafters.ts"), ts);
61366
+ const tsPath = join10(paths.output, "rafters.ts");
61367
+ await writeFile4(tsPath, ts);
61368
+ written.push(relative2(this.projectRoot, tsPath));
61276
61369
  }
61277
61370
  if (exports.dtcg) {
61278
61371
  const json3 = toDTCG(registry2.list());
61279
- await writeFile4(join10(paths.output, "rafters.json"), JSON.stringify(json3, null, 2));
61372
+ const jsonPath = join10(paths.output, "rafters.json");
61373
+ await writeFile4(jsonPath, JSON.stringify(json3, null, 2));
61374
+ written.push(relative2(this.projectRoot, jsonPath));
61280
61375
  }
61376
+ return written;
61281
61377
  }
61282
61378
  // ==================== Tool 6: Onboard ====================
61283
61379
  /**
@@ -61316,6 +61412,107 @@ var RaftersToolHandler = class _RaftersToolHandler {
61316
61412
  "highlight",
61317
61413
  "muted"
61318
61414
  ];
61415
+ static SEMANTIC_FAMILY_SET = new Set(
61416
+ _RaftersToolHandler.SEMANTIC_FAMILIES
61417
+ );
61418
+ /** Scale positions indexed 0-10, matching ColorValue.scale array indices */
61419
+ static SCALE_POSITIONS = [
61420
+ "50",
61421
+ "100",
61422
+ "200",
61423
+ "300",
61424
+ "400",
61425
+ "500",
61426
+ "600",
61427
+ "700",
61428
+ "800",
61429
+ "900",
61430
+ "950"
61431
+ ];
61432
+ static positionToIndex(position) {
61433
+ const idx = _RaftersToolHandler.SCALE_POSITIONS.indexOf(
61434
+ position
61435
+ );
61436
+ return idx >= 0 ? idx : 5;
61437
+ }
61438
+ /**
61439
+ * Find an accessible foreground position against a given background position.
61440
+ * Tries AAA first (7:1), falls back to AA (4.5:1), keeps default if both pass.
61441
+ */
61442
+ static findAccessibleFgPosition(bgPosition, defaultFgPosition, aaaPairs, aaPairs) {
61443
+ const bgIdx = _RaftersToolHandler.positionToIndex(bgPosition);
61444
+ const fgIdx = _RaftersToolHandler.positionToIndex(defaultFgPosition);
61445
+ const pairValid = (pairs) => pairs.some(([a2, b2]) => a2 === bgIdx && b2 === fgIdx || a2 === fgIdx && b2 === bgIdx);
61446
+ if (pairValid(aaaPairs) || pairValid(aaPairs)) return defaultFgPosition;
61447
+ const bestPair = aaaPairs.find(([a2]) => a2 === bgIdx) ?? aaPairs.find(([a2]) => a2 === bgIdx);
61448
+ if (bestPair && bestPair[1] !== void 0) {
61449
+ return _RaftersToolHandler.SCALE_POSITIONS[bestPair[1]] ?? defaultFgPosition;
61450
+ }
61451
+ return defaultFgPosition;
61452
+ }
61453
+ /**
61454
+ * When a semantic family is mapped to a color, cascade to all surface tokens
61455
+ * that reference that family. Uses precomputed accessibility data from the
61456
+ * ColorValue to verify WCAG AAA (fall back to AA) compliance for fg/bg pairs.
61457
+ */
61458
+ cascadeSemanticFamily(registry2, familyName, colorFamilyName, results) {
61459
+ const tokensToUpdate = [];
61460
+ const colorFamilyToken = registry2.get(colorFamilyName);
61461
+ const colorValue = colorFamilyToken?.value && typeof colorFamilyToken.value === "object" && "scale" in colorFamilyToken.value ? colorFamilyToken.value : null;
61462
+ const aaaPairs = colorValue?.accessibility?.wcagAAA?.normal ?? [];
61463
+ const aaPairs = colorValue?.accessibility?.wcagAA?.normal ?? [];
61464
+ for (const [name2, mapping] of Object.entries(DEFAULT_SEMANTIC_COLOR_MAPPINGS)) {
61465
+ const belongsToFamily = familyName === "neutral" ? mapping.light.family === "neutral" && mapping.dark.family === "neutral" : name2 === familyName || name2.startsWith(`${familyName}-`);
61466
+ if (!belongsToFamily) continue;
61467
+ const existing = registry2.get(name2);
61468
+ if (!existing) continue;
61469
+ if (existing.userOverride?.reason && !existing.userOverride.reason.startsWith("Auto-cascaded")) {
61470
+ continue;
61471
+ }
61472
+ let lightPos = mapping.light.position;
61473
+ let darkPos = mapping.dark.position;
61474
+ const isForeground = name2.endsWith("-foreground");
61475
+ const tokenFamily = isForeground ? "neutral" : colorFamilyName;
61476
+ if (isForeground && colorValue) {
61477
+ const bgName = name2.replace(/-foreground$/, "");
61478
+ const bgMapping = DEFAULT_SEMANTIC_COLOR_MAPPINGS[bgName];
61479
+ if (bgMapping) {
61480
+ lightPos = _RaftersToolHandler.findAccessibleFgPosition(
61481
+ bgMapping.light.position,
61482
+ lightPos,
61483
+ aaaPairs,
61484
+ aaPairs
61485
+ );
61486
+ darkPos = _RaftersToolHandler.findAccessibleFgPosition(
61487
+ bgMapping.dark.position,
61488
+ darkPos,
61489
+ aaaPairs,
61490
+ aaPairs
61491
+ );
61492
+ }
61493
+ }
61494
+ const lightRef = { family: tokenFamily, position: lightPos };
61495
+ const lightTokenName = `${tokenFamily}-${lightPos}`;
61496
+ const darkTokenName = `${tokenFamily}-${darkPos}`;
61497
+ tokensToUpdate.push({
61498
+ ...existing,
61499
+ value: lightRef,
61500
+ dependsOn: [lightTokenName, darkTokenName],
61501
+ userOverride: {
61502
+ previousValue: typeof existing.value === "string" ? existing.value : JSON.stringify(existing.value),
61503
+ reason: `Auto-cascaded from ${familyName} -> ${colorFamilyName}`
61504
+ }
61505
+ });
61506
+ results.push({
61507
+ source: familyName,
61508
+ target: name2,
61509
+ action: "cascade",
61510
+ ok: true,
61511
+ persisted: { value: lightRef, dependsOn: [lightTokenName, darkTokenName] }
61512
+ });
61513
+ }
61514
+ return tokensToUpdate;
61515
+ }
61319
61516
  /**
61320
61517
  * Handle rafters_onboard tool calls
61321
61518
  */
@@ -61666,14 +61863,8 @@ var RaftersToolHandler = class _RaftersToolHandler {
61666
61863
  const registry2 = new TokenRegistry(allTokens);
61667
61864
  registry2.setAdapter(this.adapter);
61668
61865
  const results = [];
61669
- const parseRef = (ref) => {
61670
- const lastDash = ref.lastIndexOf("-");
61671
- if (lastDash <= 0) return null;
61672
- const position = ref.slice(lastDash + 1);
61673
- const family = ref.slice(0, lastDash);
61674
- if (!/^(50|100|200|300|400|500|600|700|800|900|950)$/.test(position)) return null;
61675
- return { family, position };
61676
- };
61866
+ const parseRef = _RaftersToolHandler.parseColorRef;
61867
+ const allCascadeTokens = [];
61677
61868
  for (const mapping of mappings) {
61678
61869
  const { source, target, value: value2, reason, namespace, category } = mapping;
61679
61870
  const lightRef = mapping.light;
@@ -61776,25 +61967,20 @@ var RaftersToolHandler = class _RaftersToolHandler {
61776
61967
  reason: `Remapped from ${source}: ${reason}`
61777
61968
  };
61778
61969
  const newColorRef = { family: newLight.family, position: newLight.position };
61779
- await registry2.set(target, newColorRef);
61780
- const updated = registry2.get(target);
61781
- if (updated) {
61782
- updated.dependsOn = [lightTokenName, darkTokenName];
61783
- } else {
61784
- results.push({
61785
- source,
61786
- target,
61787
- action: "skipped",
61788
- ok: false,
61789
- error: `Token "${target}" was set but could not be retrieved from registry to update dependencies.`
61790
- });
61791
- continue;
61792
- }
61970
+ await registry2.setToken({
61971
+ ...existing,
61972
+ value: newColorRef,
61973
+ dependsOn: [lightTokenName, darkTokenName]
61974
+ });
61793
61975
  results.push({
61794
61976
  source,
61795
61977
  target,
61796
61978
  action: "remap",
61797
- ok: true
61979
+ ok: true,
61980
+ persisted: {
61981
+ value: newColorRef,
61982
+ dependsOn: [lightTokenName, darkTokenName]
61983
+ }
61798
61984
  });
61799
61985
  continue;
61800
61986
  }
@@ -61854,12 +62040,29 @@ var RaftersToolHandler = class _RaftersToolHandler {
61854
62040
  registry2.add(newToken);
61855
62041
  results.push({ source, target, action: "create", ok: true, enriched });
61856
62042
  }
62043
+ if (_RaftersToolHandler.SEMANTIC_FAMILY_SET.has(target)) {
62044
+ if (!enriched) {
62045
+ results.push({
62046
+ source,
62047
+ target,
62048
+ action: "skipped",
62049
+ ok: false,
62050
+ error: `Cascade skipped for "${target}": value was not enriched. Provide a CSS color value so the color family can be created with accessibility data.`
62051
+ });
62052
+ } else {
62053
+ allCascadeTokens.push(...this.cascadeSemanticFamily(registry2, target, target, results));
62054
+ }
62055
+ }
62056
+ }
62057
+ if (allCascadeTokens.length > 0) {
62058
+ await registry2.setTokens(allCascadeTokens);
61857
62059
  }
61858
62060
  await this.adapter.save(registry2.list());
61859
- await this.regenerateOutputs(registry2);
62061
+ const outputFiles = await this.regenerateOutputs(registry2);
61860
62062
  const setCount = results.filter((r) => r.action === "set" && r.ok).length;
61861
62063
  const createCount = results.filter((r) => r.action === "create" && r.ok).length;
61862
62064
  const remapCount = results.filter((r) => r.action === "remap" && r.ok).length;
62065
+ const cascadeCount = results.filter((r) => r.action === "cascade" && r.ok).length;
61863
62066
  const enrichedCount = results.filter((r) => r.enriched).length;
61864
62067
  const failCount = results.filter((r) => !r.ok).length;
61865
62068
  return {
@@ -61873,10 +62076,12 @@ var RaftersToolHandler = class _RaftersToolHandler {
61873
62076
  set: setCount,
61874
62077
  created: createCount,
61875
62078
  remapped: remapCount,
62079
+ cascaded: cascadeCount,
61876
62080
  enriched: enrichedCount,
61877
62081
  failed: failCount
61878
62082
  },
61879
- results
62083
+ results,
62084
+ outputFiles
61880
62085
  },
61881
62086
  null,
61882
62087
  2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rafters",
3
- "version": "0.0.38",
3
+ "version": "0.0.40",
4
4
  "description": "CLI for Rafters design system - scaffold tokens and add components",
5
5
  "license": "MIT",
6
6
  "type": "module",