rafters 0.0.28 → 0.0.29

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 +239 -15
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -53297,6 +53297,21 @@ Color.extend(interpolation);
53297
53297
  Color.extend(contrastMethods);
53298
53298
 
53299
53299
  // ../color-utils/src/conversion.ts
53300
+ function hexToOKLCH(hex3) {
53301
+ try {
53302
+ const color = new Color(hex3);
53303
+ const oklch2 = color.to("oklch");
53304
+ return {
53305
+ l: oklch2.coords[0] ?? 0,
53306
+ c: oklch2.coords[1] ?? 0,
53307
+ h: oklch2.coords[2] ?? 0,
53308
+ // Handle undefined hue for achromatic colors
53309
+ alpha: oklch2.alpha ?? 1
53310
+ };
53311
+ } catch (_error) {
53312
+ throw new Error(`Invalid hex color: ${hex3}`);
53313
+ }
53314
+ }
53300
53315
  function roundOKLCH(oklch2) {
53301
53316
  return {
53302
53317
  l: Math.round(oklch2.l * 1e3) / 1e3,
@@ -59683,10 +59698,13 @@ When onboarding an existing project, do NOT skip the learning step.
59683
59698
  1. Call rafters_vocabulary and explore token shapes first. Understand namespaces, dependency graph, the two primitives.
59684
59699
  2. Call rafters_onboard analyze to see what existing design decisions are in the CSS.
59685
59700
  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.
59701
+ 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.
59702
+ 5. Every token mutation needs a reason that captures design INTENT, not just origin.
59687
59703
  Bad: "imported from globals.css"
59688
59704
  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.
59705
+ 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.
59706
+ 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.
59707
+ 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
59708
 
59691
59709
  When in doubt: less code, not more. Rafters has already made the design decision.`;
59692
59710
  var CONSUMER_QUICKSTART = {
@@ -60107,14 +60125,18 @@ var TOOL_DEFINITIONS = [
60107
60125
  },
60108
60126
  {
60109
60127
  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.',
60128
+ 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
60129
  inputSchema: {
60112
60130
  type: "object",
60113
60131
  properties: {
60114
60132
  action: {
60115
60133
  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)."
60134
+ enum: ["analyze", "map", "status"],
60135
+ 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)."
60136
+ },
60137
+ confirmed: {
60138
+ type: "boolean",
60139
+ 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
60140
  },
60119
60141
  mappings: {
60120
60142
  type: "array",
@@ -60135,7 +60157,7 @@ var TOOL_DEFINITIONS = [
60135
60157
  },
60136
60158
  reason: {
60137
60159
  type: "string",
60138
- description: "Why this mapping makes sense (design intent)"
60160
+ description: "The designer explains why this mapping makes sense. This must come from the human, not from you. Ask them."
60139
60161
  },
60140
60162
  namespace: {
60141
60163
  type: "string",
@@ -60977,6 +60999,23 @@ var RaftersToolHandler = class _RaftersToolHandler {
60977
60999
  "app/globals.css"
60978
61000
  ]
60979
61001
  };
61002
+ /**
61003
+ * The 11 semantic color families every design system needs.
61004
+ * Defaults are not wrong. Unexamined defaults are wrong.
61005
+ */
61006
+ static SEMANTIC_FAMILIES = [
61007
+ "primary",
61008
+ "secondary",
61009
+ "tertiary",
61010
+ "accent",
61011
+ "neutral",
61012
+ "success",
61013
+ "warning",
61014
+ "destructive",
61015
+ "info",
61016
+ "highlight",
61017
+ "muted"
61018
+ ];
60980
61019
  /**
60981
61020
  * Handle rafters_onboard tool calls
60982
61021
  */
@@ -60985,8 +61024,32 @@ var RaftersToolHandler = class _RaftersToolHandler {
60985
61024
  switch (action) {
60986
61025
  case "analyze":
60987
61026
  return this.analyzeProject();
60988
- case "map":
61027
+ case "status":
61028
+ return this.getOnboardStatus();
61029
+ case "map": {
61030
+ const confirmed = args.confirmed;
61031
+ if (!confirmed) {
61032
+ return {
61033
+ content: [
61034
+ {
61035
+ type: "text",
61036
+ text: JSON.stringify({
61037
+ error: "Human confirmation required.",
61038
+ action: "STOP. Do not call this tool again until the designer has reviewed and approved every mapping.",
61039
+ instructions: [
61040
+ "Show each proposed mapping to the designer: source -> target, value, and your proposed reason.",
61041
+ 'Ask the designer: "Is this the right semantic role? Why did you choose this color for this purpose?"',
61042
+ "The designer must provide or approve the reason. Do not write reasons yourself.",
61043
+ "Once the designer has confirmed all mappings, call rafters_onboard map again with confirmed: true."
61044
+ ]
61045
+ })
61046
+ }
61047
+ ],
61048
+ isError: true
61049
+ };
61050
+ }
60989
61051
  return this.mapTokens(args.mappings);
61052
+ }
60990
61053
  default:
60991
61054
  return {
60992
61055
  content: [
@@ -61055,17 +61118,61 @@ var RaftersToolHandler = class _RaftersToolHandler {
61055
61118
  } catch {
61056
61119
  }
61057
61120
  let existingTokenCount = 0;
61121
+ const familyStatus = {};
61058
61122
  if (this.adapter) {
61059
61123
  const tokens = await this.adapter.load();
61060
61124
  existingTokenCount = tokens.length;
61125
+ for (const family of _RaftersToolHandler.SEMANTIC_FAMILIES) {
61126
+ const token = tokens.find((t2) => t2.name === family);
61127
+ if (!token) {
61128
+ familyStatus[family] = { status: "unmapped" };
61129
+ } else if (token.userOverride?.reason) {
61130
+ familyStatus[family] = { status: "designer", reason: token.userOverride.reason };
61131
+ } else {
61132
+ familyStatus[family] = { status: "default" };
61133
+ }
61134
+ }
61135
+ }
61136
+ const detectedFamilies = [];
61137
+ const allProps = cssFindings.flatMap((f) => f.customProperties);
61138
+ const familyMap = /* @__PURE__ */ new Map();
61139
+ for (const prop of allProps) {
61140
+ const scaleMatch = prop.name.match(
61141
+ /^--(color-)?(.+?)-(50|100|200|300|400|500|600|700|800|900|950)$/
61142
+ );
61143
+ if (scaleMatch?.[2] && scaleMatch[3]) {
61144
+ const family = scaleMatch[2];
61145
+ if (!familyMap.has(family)) familyMap.set(family, []);
61146
+ familyMap.get(family)?.push({ position: scaleMatch[3], value: prop.value });
61147
+ }
61148
+ }
61149
+ for (const [family, positions] of familyMap) {
61150
+ if (positions.length >= 3) {
61151
+ const base = positions.find((p2) => p2.position === "500") ?? positions.find((p2) => p2.position === "600") ?? positions[0];
61152
+ if (base) {
61153
+ detectedFamilies.push({
61154
+ family,
61155
+ positions: positions.map((p2) => p2.position),
61156
+ baseValue: base.value,
61157
+ source: `--color-${family}-*`
61158
+ });
61159
+ }
61160
+ }
61061
61161
  }
61162
+ const designerCount = Object.values(familyStatus).filter(
61163
+ (s) => s.status === "designer"
61164
+ ).length;
61165
+ const totalFamilies = _RaftersToolHandler.SEMANTIC_FAMILIES.length;
61062
61166
  const result = {
61063
61167
  framework,
61064
61168
  cssFiles: cssFindings,
61169
+ colorFamilies: detectedFamilies.length > 0 ? detectedFamilies : void 0,
61170
+ familyStatus,
61171
+ familyCoverage: `${designerCount}/${totalFamilies} semantic families have designer decisions`,
61065
61172
  shadcn,
61066
61173
  designDependencies: designDeps,
61067
61174
  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.'
61175
+ 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
61176
  };
61070
61177
  return {
61071
61178
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
@@ -61074,6 +61181,63 @@ var RaftersToolHandler = class _RaftersToolHandler {
61074
61181
  return this.handleError("analyzeProject", error48);
61075
61182
  }
61076
61183
  }
61184
+ /**
61185
+ * Check onboarding completeness -- which families have designer decisions
61186
+ */
61187
+ async getOnboardStatus() {
61188
+ if (!this.adapter || !this.projectRoot) {
61189
+ return { content: [{ type: "text", text: NO_PROJECT_ERROR }], isError: true };
61190
+ }
61191
+ try {
61192
+ const tokens = await this.adapter.load();
61193
+ const mapped = [];
61194
+ const defaultsRemaining = [];
61195
+ const unmapped = [];
61196
+ for (const family of _RaftersToolHandler.SEMANTIC_FAMILIES) {
61197
+ const token = tokens.find((t2) => t2.name === family);
61198
+ if (!token) {
61199
+ unmapped.push(family);
61200
+ } else if (token.userOverride?.reason) {
61201
+ mapped.push(family);
61202
+ } else {
61203
+ defaultsRemaining.push(family);
61204
+ }
61205
+ }
61206
+ const semanticSet = new Set(_RaftersToolHandler.SEMANTIC_FAMILIES);
61207
+ const customFamilies = tokens.filter(
61208
+ (t2) => t2.namespace === "color" && !semanticSet.has(t2.name) && t2.userOverride?.reason && !t2.name.includes("-")
61209
+ ).map((t2) => t2.name);
61210
+ const complete = defaultsRemaining.length === 0 && unmapped.length === 0;
61211
+ const coverage = `${mapped.length}/${_RaftersToolHandler.SEMANTIC_FAMILIES.length}`;
61212
+ let guidance;
61213
+ if (complete) {
61214
+ guidance = "All semantic families have designer decisions. Onboarding is complete.";
61215
+ } else if (defaultsRemaining.length > 0) {
61216
+ 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.`;
61217
+ } else {
61218
+ guidance = `${unmapped.length} families are unmapped: ${unmapped.join(", ")}. These need designer decisions.`;
61219
+ }
61220
+ return {
61221
+ content: [
61222
+ {
61223
+ type: "text",
61224
+ text: JSON.stringify(
61225
+ {
61226
+ complete,
61227
+ coverage,
61228
+ families: { mapped, defaultsRemaining, unmapped, customFamilies },
61229
+ guidance
61230
+ },
61231
+ null,
61232
+ 2
61233
+ )
61234
+ }
61235
+ ]
61236
+ };
61237
+ } catch (error48) {
61238
+ return this.handleError("getOnboardStatus", error48);
61239
+ }
61240
+ }
61077
61241
  /**
61078
61242
  * Parse CSS content into structured findings
61079
61243
  */
@@ -61088,6 +61252,7 @@ var RaftersToolHandler = class _RaftersToolHandler {
61088
61252
  const themeMatches = content.matchAll(/@theme\s*\{([^}]+)\}/g);
61089
61253
  for (const match2 of themeMatches) {
61090
61254
  if (match2[0]) themeBlocks.push(match2[0].trim());
61255
+ if (match2[1]) this.extractCustomProperties(match2[1], "@theme", customProperties);
61091
61256
  }
61092
61257
  const rootMatch = content.match(/:root\s*\{([^}]+)\}/);
61093
61258
  if (rootMatch?.[1]) {
@@ -61117,7 +61282,51 @@ var RaftersToolHandler = class _RaftersToolHandler {
61117
61282
  }
61118
61283
  }
61119
61284
  /**
61120
- * Execute a mapping plan -- calls writeToken for each mapping
61285
+ * Detect if a string value is a CSS color (not a size, calc, or var reference)
61286
+ */
61287
+ static isColorValue(value2) {
61288
+ const v = value2.trim().toLowerCase();
61289
+ if (/^\d/.test(v) || v.startsWith("var(") || v.startsWith("calc(")) return false;
61290
+ if (/\d+(rem|px|em|%|vw|vh|dvh|svh|ch|ex)$/.test(v)) return false;
61291
+ 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");
61292
+ }
61293
+ /**
61294
+ * Parse any CSS color to OKLCH. hexToOKLCH uses colorjs.io internally
61295
+ * which accepts all CSS color formats, not just hex.
61296
+ */
61297
+ static parseToOKLCH(value2) {
61298
+ try {
61299
+ return hexToOKLCH(value2.trim());
61300
+ } catch {
61301
+ return null;
61302
+ }
61303
+ }
61304
+ /**
61305
+ * Fire api.rafters.studio enrichment for a color (non-blocking)
61306
+ */
61307
+ static fireEnrichment(oklch2) {
61308
+ const l = oklch2.l.toFixed(3);
61309
+ const c4 = oklch2.c.toFixed(3);
61310
+ const h = Math.round(oklch2.h);
61311
+ return fetch(`https://api.rafters.studio/color/${l}-${c4}-${h}?sync=true`).then((r) => r.ok ? r.json() : null).catch(() => null);
61312
+ }
61313
+ /**
61314
+ * Build enriched ColorValue: local math + API intelligence
61315
+ */
61316
+ static async buildEnrichedColor(oklch2, tokenName) {
61317
+ const enrichmentPromise = _RaftersToolHandler.fireEnrichment(oklch2);
61318
+ const colorValue = buildColorValue(oklch2, { token: tokenName });
61319
+ const enrichment = await enrichmentPromise;
61320
+ if (enrichment && typeof enrichment === "object" && "color" in enrichment) {
61321
+ const apiColor = enrichment.color;
61322
+ if (apiColor?.intelligence) {
61323
+ colorValue.intelligence = apiColor.intelligence;
61324
+ }
61325
+ }
61326
+ return colorValue;
61327
+ }
61328
+ /**
61329
+ * Execute a mapping plan -- enriches colors, writes tokens
61121
61330
  */
61122
61331
  async mapTokens(mappings) {
61123
61332
  if (!mappings || mappings.length === 0) {
@@ -61154,6 +61363,15 @@ var RaftersToolHandler = class _RaftersToolHandler {
61154
61363
  });
61155
61364
  continue;
61156
61365
  }
61366
+ let tokenValue = value2;
61367
+ let enriched = false;
61368
+ if (_RaftersToolHandler.isColorValue(value2)) {
61369
+ const oklch2 = _RaftersToolHandler.parseToOKLCH(value2);
61370
+ if (oklch2) {
61371
+ tokenValue = await _RaftersToolHandler.buildEnrichedColor(oklch2, target);
61372
+ enriched = true;
61373
+ }
61374
+ }
61157
61375
  const exists = registry2.has(target);
61158
61376
  if (exists) {
61159
61377
  const existing = registry2.get(target);
@@ -61163,17 +61381,17 @@ var RaftersToolHandler = class _RaftersToolHandler {
61163
61381
  previousValue: typeof previousValue === "string" ? previousValue : JSON.stringify(previousValue),
61164
61382
  reason: `Onboarded from ${source}: ${reason}`
61165
61383
  };
61166
- await registry2.set(target, value2);
61167
- results.push({ source, target, action: "set", ok: true });
61384
+ await registry2.set(target, tokenValue);
61385
+ results.push({ source, target, action: "set", ok: true, enriched });
61168
61386
  }
61169
61387
  } else {
61170
- const ns = namespace ?? "color";
61388
+ const ns = namespace ?? (enriched ? "color" : "custom");
61171
61389
  const cat = category ?? ns;
61172
61390
  const newToken = {
61173
61391
  name: target,
61174
61392
  namespace: ns,
61175
61393
  category: cat,
61176
- value: value2,
61394
+ value: tokenValue,
61177
61395
  containerQueryAware: true,
61178
61396
  userOverride: {
61179
61397
  previousValue: "",
@@ -61181,13 +61399,14 @@ var RaftersToolHandler = class _RaftersToolHandler {
61181
61399
  }
61182
61400
  };
61183
61401
  registry2.add(newToken);
61184
- results.push({ source, target, action: "create", ok: true });
61402
+ results.push({ source, target, action: "create", ok: true, enriched });
61185
61403
  }
61186
61404
  }
61187
61405
  await this.adapter.save(registry2.list());
61188
61406
  await this.regenerateOutputs(registry2);
61189
61407
  const setCount = results.filter((r) => r.action === "set" && r.ok).length;
61190
61408
  const createCount = results.filter((r) => r.action === "create" && r.ok).length;
61409
+ const enrichedCount = results.filter((r) => r.enriched).length;
61191
61410
  const failCount = results.filter((r) => !r.ok).length;
61192
61411
  return {
61193
61412
  content: [
@@ -61196,7 +61415,12 @@ var RaftersToolHandler = class _RaftersToolHandler {
61196
61415
  text: JSON.stringify(
61197
61416
  {
61198
61417
  ok: true,
61199
- summary: { set: setCount, created: createCount, failed: failCount },
61418
+ summary: {
61419
+ set: setCount,
61420
+ created: createCount,
61421
+ enriched: enrichedCount,
61422
+ failed: failCount
61423
+ },
61200
61424
  results
61201
61425
  },
61202
61426
  null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rafters",
3
- "version": "0.0.28",
3
+ "version": "0.0.29",
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:*",