rafters 0.0.28 → 0.0.30

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 +250 -21
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -13599,6 +13599,8 @@ async function add(componentArgs, options) {
13599
13599
  const installed = [];
13600
13600
  const skipped = [];
13601
13601
  const installedItems = [];
13602
+ const filteredItems = [];
13603
+ const target = getComponentTarget(config3);
13602
13604
  for (const item of allItems) {
13603
13605
  if (!options.overwrite && isAlreadyInstalled(config3, item)) {
13604
13606
  log({
@@ -13614,6 +13616,12 @@ async function add(componentArgs, options) {
13614
13616
  if (result.installed) {
13615
13617
  installed.push(item.name);
13616
13618
  installedItems.push(item);
13619
+ if (item.type === "ui") {
13620
+ const selection = selectFilesForFramework(item.files, target);
13621
+ filteredItems.push({ ...item, files: selection.files });
13622
+ } else {
13623
+ filteredItems.push(item);
13624
+ }
13617
13625
  log({
13618
13626
  event: "add:installed",
13619
13627
  component: item.name,
@@ -13642,7 +13650,7 @@ async function add(componentArgs, options) {
13642
13650
  };
13643
13651
  let depsResult = emptyDeps;
13644
13652
  try {
13645
- depsResult = await installRegistryDependencies(allItems, cwd);
13653
+ depsResult = await installRegistryDependencies(filteredItems, cwd);
13646
13654
  } catch (err) {
13647
13655
  const message = err instanceof Error ? err.message : String(err);
13648
13656
  log({
@@ -53297,16 +53305,28 @@ Color.extend(interpolation);
53297
53305
  Color.extend(contrastMethods);
53298
53306
 
53299
53307
  // ../color-utils/src/conversion.ts
53308
+ function hexToOKLCH(hex3) {
53309
+ try {
53310
+ const color = new Color(hex3);
53311
+ const oklch2 = color.to("oklch");
53312
+ return {
53313
+ l: oklch2.coords[0] ?? 0,
53314
+ c: oklch2.coords[1] ?? 0,
53315
+ // Achromatic colors (grays) get NaN hue from colorjs.io, not undefined
53316
+ h: Number.isNaN(oklch2.coords[2]) ? 0 : oklch2.coords[2] ?? 0,
53317
+ alpha: oklch2.alpha ?? 1
53318
+ };
53319
+ } catch (_error) {
53320
+ throw new Error(`Invalid hex color: ${hex3}`);
53321
+ }
53322
+ }
53300
53323
  function roundOKLCH(oklch2) {
53301
53324
  return {
53302
53325
  l: Math.round(oklch2.l * 1e3) / 1e3,
53303
- // 3 decimal places for lightness
53304
53326
  c: Math.round(oklch2.c * 1e3) / 1e3,
53305
- // 3 decimal places for chroma
53306
- h: Math.round(oklch2.h),
53307
- // Whole degrees for hue
53327
+ // NaN hue from achromatic colors defaults to 0
53328
+ h: Number.isNaN(oklch2.h) ? 0 : Math.round(oklch2.h),
53308
53329
  alpha: oklch2.alpha !== void 0 ? Math.round(oklch2.alpha * 100) / 100 : 1
53309
- // 2 decimal places for alpha
53310
53330
  };
53311
53331
  }
53312
53332
 
@@ -59683,10 +59703,13 @@ When onboarding an existing project, do NOT skip the learning step.
59683
59703
  1. Call rafters_vocabulary and explore token shapes first. Understand namespaces, dependency graph, the two primitives.
59684
59704
  2. Call rafters_onboard analyze to see what existing design decisions are in the CSS.
59685
59705
  3. Do NOT automatically map colors. Ask the designer about each ambiguous decision.
59686
- 4. Every token mutation needs a reason that captures design INTENT, not just origin.
59706
+ 4. CHALLENGE SEMANTIC ASSIGNMENTS. Do not accept "this is primary" without asking why. "What makes this the primary identity color? Where is it used? What role does it play?" The designer must justify every family assignment.
59707
+ 5. Every token mutation needs a reason that captures design INTENT, not just origin.
59687
59708
  Bad: "imported from globals.css"
59688
59709
  Good: "Brand blue from --brand-primary, primary identity color used on nav and CTAs per designer"
59689
- 5. Call rafters_onboard map to execute the migration once the designer confirms.
59710
+ 6. The 11 semantic families (primary, secondary, tertiary, accent, neutral, success, warning, destructive, info, highlight, muted) MUST all be mapped. Extra colors are custom families in the color namespace.
59711
+ 7. If analyze detects color scale patterns (e.g., --color-blaze-50 through --color-blaze-950), map the family using its base color. Do NOT create 11 individual tokens.
59712
+ 8. Call rafters_onboard map to execute the migration once the designer confirms. Colors are automatically enriched with full OKLCH scales, harmonies, accessibility data, and API intelligence.
59690
59713
 
59691
59714
  When in doubt: less code, not more. Rafters has already made the design decision.`;
59692
59715
  var CONSUMER_QUICKSTART = {
@@ -60107,14 +60130,18 @@ var TOOL_DEFINITIONS = [
60107
60130
  },
60108
60131
  {
60109
60132
  name: "rafters_onboard",
60110
- description: 'Analyze an existing project for design decisions (CSS custom properties, @theme blocks, shadcn colors) and map them into Rafters tokens. Use "analyze" to surface raw findings. Use "map" to execute token writes from a mapping plan. The agent interprets the findings, asks the designer about ambiguous decisions, then maps with intent.',
60133
+ description: 'Analyze an existing project for design decisions and map them into Rafters tokens. Use "analyze" to surface raw findings. Use "map" to execute -- but map REQUIRES the designer to confirm every mapping first. The tool will reject unconfirmed mappings and instruct you to ask the designer. This is an intentional system, not an automatic one.',
60111
60134
  inputSchema: {
60112
60135
  type: "object",
60113
60136
  properties: {
60114
60137
  action: {
60115
60138
  type: "string",
60116
- enum: ["analyze", "map"],
60117
- description: "analyze: scan project CSS and return structured findings. map: execute a mapping plan (array of source->target->reason)."
60139
+ enum: ["analyze", "map", "status"],
60140
+ description: "analyze: scan CSS and return findings + family checklist. status: show which of the 11 families have designer decisions vs defaults. map: execute confirmed mappings (requires confirmed: true after designer review)."
60141
+ },
60142
+ confirmed: {
60143
+ type: "boolean",
60144
+ description: "Set to true ONLY after the designer has reviewed and approved every mapping. The reasons must come from the designer, not from you."
60118
60145
  },
60119
60146
  mappings: {
60120
60147
  type: "array",
@@ -60135,7 +60162,7 @@ var TOOL_DEFINITIONS = [
60135
60162
  },
60136
60163
  reason: {
60137
60164
  type: "string",
60138
- description: "Why this mapping makes sense (design intent)"
60165
+ description: "The designer explains why this mapping makes sense. This must come from the human, not from you. Ask them."
60139
60166
  },
60140
60167
  namespace: {
60141
60168
  type: "string",
@@ -60977,6 +61004,23 @@ var RaftersToolHandler = class _RaftersToolHandler {
60977
61004
  "app/globals.css"
60978
61005
  ]
60979
61006
  };
61007
+ /**
61008
+ * The 11 semantic color families every design system needs.
61009
+ * Defaults are not wrong. Unexamined defaults are wrong.
61010
+ */
61011
+ static SEMANTIC_FAMILIES = [
61012
+ "primary",
61013
+ "secondary",
61014
+ "tertiary",
61015
+ "accent",
61016
+ "neutral",
61017
+ "success",
61018
+ "warning",
61019
+ "destructive",
61020
+ "info",
61021
+ "highlight",
61022
+ "muted"
61023
+ ];
60980
61024
  /**
60981
61025
  * Handle rafters_onboard tool calls
60982
61026
  */
@@ -60985,8 +61029,32 @@ var RaftersToolHandler = class _RaftersToolHandler {
60985
61029
  switch (action) {
60986
61030
  case "analyze":
60987
61031
  return this.analyzeProject();
60988
- case "map":
61032
+ case "status":
61033
+ return this.getOnboardStatus();
61034
+ case "map": {
61035
+ const confirmed = args.confirmed;
61036
+ if (!confirmed) {
61037
+ return {
61038
+ content: [
61039
+ {
61040
+ type: "text",
61041
+ text: JSON.stringify({
61042
+ error: "Human confirmation required.",
61043
+ action: "STOP. Do not call this tool again until the designer has reviewed and approved every mapping.",
61044
+ instructions: [
61045
+ "Show each proposed mapping to the designer: source -> target, value, and your proposed reason.",
61046
+ 'Ask the designer: "Is this the right semantic role? Why did you choose this color for this purpose?"',
61047
+ "The designer must provide or approve the reason. Do not write reasons yourself.",
61048
+ "Once the designer has confirmed all mappings, call rafters_onboard map again with confirmed: true."
61049
+ ]
61050
+ })
61051
+ }
61052
+ ],
61053
+ isError: true
61054
+ };
61055
+ }
60989
61056
  return this.mapTokens(args.mappings);
61057
+ }
60990
61058
  default:
60991
61059
  return {
60992
61060
  content: [
@@ -61055,17 +61123,61 @@ var RaftersToolHandler = class _RaftersToolHandler {
61055
61123
  } catch {
61056
61124
  }
61057
61125
  let existingTokenCount = 0;
61126
+ const familyStatus = {};
61058
61127
  if (this.adapter) {
61059
61128
  const tokens = await this.adapter.load();
61060
61129
  existingTokenCount = tokens.length;
61130
+ for (const family of _RaftersToolHandler.SEMANTIC_FAMILIES) {
61131
+ const token = tokens.find((t2) => t2.name === family);
61132
+ if (!token) {
61133
+ familyStatus[family] = { status: "unmapped" };
61134
+ } else if (token.userOverride?.reason) {
61135
+ familyStatus[family] = { status: "designer", reason: token.userOverride.reason };
61136
+ } else {
61137
+ familyStatus[family] = { status: "default" };
61138
+ }
61139
+ }
61061
61140
  }
61141
+ const detectedFamilies = [];
61142
+ const allProps = cssFindings.flatMap((f) => f.customProperties);
61143
+ const familyMap = /* @__PURE__ */ new Map();
61144
+ for (const prop of allProps) {
61145
+ const scaleMatch = prop.name.match(
61146
+ /^--(color-)?(.+?)-(50|100|200|300|400|500|600|700|800|900|950)$/
61147
+ );
61148
+ if (scaleMatch?.[2] && scaleMatch[3]) {
61149
+ const family = scaleMatch[2];
61150
+ if (!familyMap.has(family)) familyMap.set(family, []);
61151
+ familyMap.get(family)?.push({ position: scaleMatch[3], value: prop.value });
61152
+ }
61153
+ }
61154
+ for (const [family, positions] of familyMap) {
61155
+ if (positions.length >= 3) {
61156
+ const base = positions.find((p2) => p2.position === "500") ?? positions.find((p2) => p2.position === "600") ?? positions[0];
61157
+ if (base) {
61158
+ detectedFamilies.push({
61159
+ family,
61160
+ positions: positions.map((p2) => p2.position),
61161
+ baseValue: base.value,
61162
+ source: `--color-${family}-*`
61163
+ });
61164
+ }
61165
+ }
61166
+ }
61167
+ const designerCount = Object.values(familyStatus).filter(
61168
+ (s) => s.status === "designer"
61169
+ ).length;
61170
+ const totalFamilies = _RaftersToolHandler.SEMANTIC_FAMILIES.length;
61062
61171
  const result = {
61063
61172
  framework,
61064
61173
  cssFiles: cssFindings,
61174
+ colorFamilies: detectedFamilies.length > 0 ? detectedFamilies : void 0,
61175
+ familyStatus,
61176
+ familyCoverage: `${designerCount}/${totalFamilies} semantic families have designer decisions`,
61065
61177
  shadcn,
61066
61178
  designDependencies: designDeps,
61067
61179
  existingTokenCount,
61068
- guidance: 'Review the custom properties above. Map each to a rafters token using rafters_onboard with action: "map". For ambiguous decisions, ask the designer.'
61180
+ guidance: detectedFamilies.length > 0 ? `Found ${detectedFamilies.length} color scale pattern(s): ${detectedFamilies.map((f) => f.family).join(", ")}. Map each family using its base color (position 500/600), not individual scale positions. buildColorValue() will regenerate the full 11-step scale.` : 'Review the custom properties above. Map each to a rafters token using rafters_onboard with action: "map". For ambiguous decisions, ask the designer.'
61069
61181
  };
61070
61182
  return {
61071
61183
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
@@ -61074,6 +61186,63 @@ var RaftersToolHandler = class _RaftersToolHandler {
61074
61186
  return this.handleError("analyzeProject", error48);
61075
61187
  }
61076
61188
  }
61189
+ /**
61190
+ * Check onboarding completeness -- which families have designer decisions
61191
+ */
61192
+ async getOnboardStatus() {
61193
+ if (!this.adapter || !this.projectRoot) {
61194
+ return { content: [{ type: "text", text: NO_PROJECT_ERROR }], isError: true };
61195
+ }
61196
+ try {
61197
+ const tokens = await this.adapter.load();
61198
+ const mapped = [];
61199
+ const defaultsRemaining = [];
61200
+ const unmapped = [];
61201
+ for (const family of _RaftersToolHandler.SEMANTIC_FAMILIES) {
61202
+ const token = tokens.find((t2) => t2.name === family);
61203
+ if (!token) {
61204
+ unmapped.push(family);
61205
+ } else if (token.userOverride?.reason) {
61206
+ mapped.push(family);
61207
+ } else {
61208
+ defaultsRemaining.push(family);
61209
+ }
61210
+ }
61211
+ const semanticSet = new Set(_RaftersToolHandler.SEMANTIC_FAMILIES);
61212
+ const customFamilies = tokens.filter(
61213
+ (t2) => t2.namespace === "color" && !semanticSet.has(t2.name) && t2.userOverride?.reason && !t2.name.includes("-")
61214
+ ).map((t2) => t2.name);
61215
+ const complete = defaultsRemaining.length === 0 && unmapped.length === 0;
61216
+ const coverage = `${mapped.length}/${_RaftersToolHandler.SEMANTIC_FAMILIES.length}`;
61217
+ let guidance;
61218
+ if (complete) {
61219
+ guidance = "All semantic families have designer decisions. Onboarding is complete.";
61220
+ } else if (defaultsRemaining.length > 0) {
61221
+ guidance = `${defaultsRemaining.length} families still use generated defaults: ${defaultsRemaining.join(", ")}. Ask the designer: do these defaults work, or should we choose specific colors? If the defaults are intentional, confirm them with a reason.`;
61222
+ } else {
61223
+ guidance = `${unmapped.length} families are unmapped: ${unmapped.join(", ")}. These need designer decisions.`;
61224
+ }
61225
+ return {
61226
+ content: [
61227
+ {
61228
+ type: "text",
61229
+ text: JSON.stringify(
61230
+ {
61231
+ complete,
61232
+ coverage,
61233
+ families: { mapped, defaultsRemaining, unmapped, customFamilies },
61234
+ guidance
61235
+ },
61236
+ null,
61237
+ 2
61238
+ )
61239
+ }
61240
+ ]
61241
+ };
61242
+ } catch (error48) {
61243
+ return this.handleError("getOnboardStatus", error48);
61244
+ }
61245
+ }
61077
61246
  /**
61078
61247
  * Parse CSS content into structured findings
61079
61248
  */
@@ -61088,6 +61257,7 @@ var RaftersToolHandler = class _RaftersToolHandler {
61088
61257
  const themeMatches = content.matchAll(/@theme\s*\{([^}]+)\}/g);
61089
61258
  for (const match2 of themeMatches) {
61090
61259
  if (match2[0]) themeBlocks.push(match2[0].trim());
61260
+ if (match2[1]) this.extractCustomProperties(match2[1], "@theme", customProperties);
61091
61261
  }
61092
61262
  const rootMatch = content.match(/:root\s*\{([^}]+)\}/);
61093
61263
  if (rootMatch?.[1]) {
@@ -61117,7 +61287,51 @@ var RaftersToolHandler = class _RaftersToolHandler {
61117
61287
  }
61118
61288
  }
61119
61289
  /**
61120
- * Execute a mapping plan -- calls writeToken for each mapping
61290
+ * Detect if a string value is a CSS color (not a size, calc, or var reference)
61291
+ */
61292
+ static isColorValue(value2) {
61293
+ const v = value2.trim().toLowerCase();
61294
+ if (/^\d/.test(v) || v.startsWith("var(") || v.startsWith("calc(")) return false;
61295
+ if (/\d+(rem|px|em|%|vw|vh|dvh|svh|ch|ex)$/.test(v)) return false;
61296
+ return v.startsWith("#") || v.startsWith("rgb") || v.startsWith("hsl") || v.startsWith("oklch") || v.startsWith("oklab") || v.startsWith("lch") || v.startsWith("lab") || v.startsWith("color(") || v.startsWith("hwb");
61297
+ }
61298
+ /**
61299
+ * Parse any CSS color to OKLCH. hexToOKLCH uses colorjs.io internally
61300
+ * which accepts all CSS color formats, not just hex.
61301
+ */
61302
+ static parseToOKLCH(value2) {
61303
+ try {
61304
+ return hexToOKLCH(value2.trim());
61305
+ } catch {
61306
+ return null;
61307
+ }
61308
+ }
61309
+ /**
61310
+ * Fire api.rafters.studio enrichment for a color (non-blocking)
61311
+ */
61312
+ static fireEnrichment(oklch2) {
61313
+ const l = oklch2.l.toFixed(3);
61314
+ const c4 = oklch2.c.toFixed(3);
61315
+ const h = Math.round(oklch2.h);
61316
+ return fetch(`https://api.rafters.studio/color/${l}-${c4}-${h}?sync=true`).then((r) => r.ok ? r.json() : null).catch(() => null);
61317
+ }
61318
+ /**
61319
+ * Build enriched ColorValue: local math + API intelligence
61320
+ */
61321
+ static async buildEnrichedColor(oklch2, tokenName) {
61322
+ const enrichmentPromise = _RaftersToolHandler.fireEnrichment(oklch2);
61323
+ const colorValue = buildColorValue(oklch2, { token: tokenName });
61324
+ const enrichment = await enrichmentPromise;
61325
+ if (enrichment && typeof enrichment === "object" && "color" in enrichment) {
61326
+ const apiColor = enrichment.color;
61327
+ if (apiColor?.intelligence) {
61328
+ colorValue.intelligence = apiColor.intelligence;
61329
+ }
61330
+ }
61331
+ return colorValue;
61332
+ }
61333
+ /**
61334
+ * Execute a mapping plan -- enriches colors, writes tokens
61121
61335
  */
61122
61336
  async mapTokens(mappings) {
61123
61337
  if (!mappings || mappings.length === 0) {
@@ -61154,6 +61368,15 @@ var RaftersToolHandler = class _RaftersToolHandler {
61154
61368
  });
61155
61369
  continue;
61156
61370
  }
61371
+ let tokenValue = value2;
61372
+ let enriched = false;
61373
+ if (_RaftersToolHandler.isColorValue(value2)) {
61374
+ const oklch2 = _RaftersToolHandler.parseToOKLCH(value2);
61375
+ if (oklch2) {
61376
+ tokenValue = await _RaftersToolHandler.buildEnrichedColor(oklch2, target);
61377
+ enriched = true;
61378
+ }
61379
+ }
61157
61380
  const exists = registry2.has(target);
61158
61381
  if (exists) {
61159
61382
  const existing = registry2.get(target);
@@ -61163,17 +61386,17 @@ var RaftersToolHandler = class _RaftersToolHandler {
61163
61386
  previousValue: typeof previousValue === "string" ? previousValue : JSON.stringify(previousValue),
61164
61387
  reason: `Onboarded from ${source}: ${reason}`
61165
61388
  };
61166
- await registry2.set(target, value2);
61167
- results.push({ source, target, action: "set", ok: true });
61389
+ await registry2.set(target, tokenValue);
61390
+ results.push({ source, target, action: "set", ok: true, enriched });
61168
61391
  }
61169
61392
  } else {
61170
- const ns = namespace ?? "color";
61393
+ const ns = namespace ?? (enriched ? "color" : "custom");
61171
61394
  const cat = category ?? ns;
61172
61395
  const newToken = {
61173
61396
  name: target,
61174
61397
  namespace: ns,
61175
61398
  category: cat,
61176
- value: value2,
61399
+ value: tokenValue,
61177
61400
  containerQueryAware: true,
61178
61401
  userOverride: {
61179
61402
  previousValue: "",
@@ -61181,13 +61404,14 @@ var RaftersToolHandler = class _RaftersToolHandler {
61181
61404
  }
61182
61405
  };
61183
61406
  registry2.add(newToken);
61184
- results.push({ source, target, action: "create", ok: true });
61407
+ results.push({ source, target, action: "create", ok: true, enriched });
61185
61408
  }
61186
61409
  }
61187
61410
  await this.adapter.save(registry2.list());
61188
61411
  await this.regenerateOutputs(registry2);
61189
61412
  const setCount = results.filter((r) => r.action === "set" && r.ok).length;
61190
61413
  const createCount = results.filter((r) => r.action === "create" && r.ok).length;
61414
+ const enrichedCount = results.filter((r) => r.enriched).length;
61191
61415
  const failCount = results.filter((r) => !r.ok).length;
61192
61416
  return {
61193
61417
  content: [
@@ -61196,7 +61420,12 @@ var RaftersToolHandler = class _RaftersToolHandler {
61196
61420
  text: JSON.stringify(
61197
61421
  {
61198
61422
  ok: true,
61199
- summary: { set: setCount, created: createCount, failed: failCount },
61423
+ summary: {
61424
+ set: setCount,
61425
+ created: createCount,
61426
+ enriched: enrichedCount,
61427
+ failed: failCount
61428
+ },
61200
61429
  results
61201
61430
  },
61202
61431
  null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rafters",
3
- "version": "0.0.28",
3
+ "version": "0.0.30",
4
4
  "description": "CLI for Rafters design system - scaffold tokens and add components",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -40,6 +40,7 @@
40
40
  },
41
41
  "devDependencies": {
42
42
  "@playwright/test": "catalog:",
43
+ "@rafters/color-utils": "workspace:*",
43
44
  "@rafters/design-tokens": "workspace:*",
44
45
  "@rafters/shared": "workspace:*",
45
46
  "@rafters/studio": "workspace:*",