rafters 0.0.83 → 0.0.85

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/dist/index.js CHANGED
@@ -526,6 +526,10 @@ function generateThemeBlock(groups) {
526
526
  if (token.lineHeight) {
527
527
  lines.push(` --${token.name}--line-height: ${token.lineHeight};`);
528
528
  }
529
+ if (token.name.startsWith("line-height-")) {
530
+ const key = token.name.replace(/^line-height-/, "");
531
+ lines.push(` --leading-${key}: ${value};`);
532
+ }
529
533
  }
530
534
  lines.push("");
531
535
  }
@@ -1182,6 +1186,8 @@ function deriveCandidates(themeCSS) {
1182
1186
  candidates.add(`font-${name.slice(12)}`);
1183
1187
  } else if (name.startsWith("ease-")) {
1184
1188
  candidates.add(name);
1189
+ } else if (name.startsWith("leading-")) {
1190
+ candidates.add(name);
1185
1191
  } else if (name.startsWith("animate-")) {
1186
1192
  candidates.add(name);
1187
1193
  } else if (name.startsWith("text-") && !name.includes("--")) {
@@ -27811,17 +27817,26 @@ var TokenGraph = class {
27811
27817
  this.snapshot = /* @__PURE__ */ new Map();
27812
27818
  for (const [k, v] of this.nodes) this.snapshot.set(k, structuredClone(v));
27813
27819
  }
27820
+ // The caller (set/bind) takes a snapshot before invoking this. If any
27821
+ // dependent's transform throws partway through, we roll the whole graph
27822
+ // back to that snapshot and re-throw -- cascade is all-or-nothing, never
27823
+ // half-applied.
27814
27824
  cascadeFrom(changed) {
27815
- const dependents = this.collectDependents(changed);
27816
- if (dependents.size === 0) return;
27817
- const ordered = this.topoSort(dependents);
27818
- for (const name of ordered) {
27819
- const node = this.nodes.get(name);
27820
- if (!node || node.userOverride || !node.binding) continue;
27821
- const plugin = this.requirePlugin(node.binding.plugin);
27822
- const value = plugin.transform(node.binding.input, (n2) => this.get(n2));
27823
- plugin.outputSchema.parse(value);
27824
- this.nodes.set(name, { ...node, value });
27825
+ try {
27826
+ const dependents = this.collectDependents(changed);
27827
+ if (dependents.size === 0) return;
27828
+ const ordered = this.topoSort(dependents);
27829
+ for (const name of ordered) {
27830
+ const node = this.nodes.get(name);
27831
+ if (!node || node.userOverride || !node.binding) continue;
27832
+ const plugin = this.requirePlugin(node.binding.plugin);
27833
+ const value = plugin.transform(node.binding.input, (n2) => this.get(n2));
27834
+ plugin.outputSchema.parse(value);
27835
+ this.nodes.set(name, { ...node, value });
27836
+ }
27837
+ } catch (error47) {
27838
+ this.undo();
27839
+ throw error47;
27825
27840
  }
27826
27841
  }
27827
27842
  collectDependents(changed) {
@@ -27941,8 +27956,15 @@ function senseShadcnCss(css) {
27941
27956
 
27942
27957
  // ../design-tokens/src/outputs.ts
27943
27958
  import { existsSync, realpathSync } from "fs";
27944
- import { mkdir, writeFile } from "fs/promises";
27959
+ import { mkdir, unlink, writeFile } from "fs/promises";
27945
27960
  import { isAbsolute, join as join2, resolve } from "path";
27961
+ var KNOWN_OUTPUT_FILENAMES = [
27962
+ "rafters.css",
27963
+ "rafters.ts",
27964
+ "rafters.json",
27965
+ "rafters.standalone.css",
27966
+ "rafters.documentation.css"
27967
+ ];
27946
27968
  function fieldToPaths(field) {
27947
27969
  if (!field) return [];
27948
27970
  if (typeof field === "string") return [field];
@@ -28007,6 +28029,16 @@ async function regenerateOutputs(registry2, input, hooks2 = {}) {
28007
28029
  await writeFile(join2(outputDir2, "rafters.documentation.css"), doc);
28008
28030
  written.push("rafters.documentation.css");
28009
28031
  }
28032
+ const writtenSet = new Set(written);
28033
+ for (const filename of KNOWN_OUTPUT_FILENAMES) {
28034
+ if (writtenSet.has(filename)) continue;
28035
+ const filePath = join2(outputDir2, filename);
28036
+ if (!existsSync(filePath)) continue;
28037
+ try {
28038
+ await unlink(filePath);
28039
+ } catch {
28040
+ }
28041
+ }
28010
28042
  hooks2.notify?.();
28011
28043
  return written;
28012
28044
  }
@@ -28040,10 +28072,9 @@ var TokenRegistry = class {
28040
28072
  ...t.binding ? { binding: t.binding } : {}
28041
28073
  });
28042
28074
  }
28043
- for (const t of parsed) {
28044
- if (!t.binding) continue;
28045
- if (t.userOverride) continue;
28046
- this.graph.bind(t.name, t.binding.plugin, t.binding.input);
28075
+ const ordered = topoSortPass2(parsed, this.plugins);
28076
+ for (const entry of ordered) {
28077
+ this.graph.bind(entry.name, entry.binding.plugin, entry.binding.input);
28047
28078
  }
28048
28079
  }
28049
28080
  registerPlugin(plugin) {
@@ -28152,6 +28183,39 @@ function toUserOverrideField(override, baseValue) {
28152
28183
  if (override.kind) result.kind = override.kind;
28153
28184
  return result;
28154
28185
  }
28186
+ function topoSortPass2(parsed, plugins) {
28187
+ const entries = [];
28188
+ for (const t of parsed) {
28189
+ if (!t.binding || t.userOverride) continue;
28190
+ entries.push({ name: t.name, binding: t.binding });
28191
+ }
28192
+ const entryByName = /* @__PURE__ */ new Map();
28193
+ for (const e of entries) entryByName.set(e.name, e);
28194
+ const sorted = [];
28195
+ const visited = /* @__PURE__ */ new Set();
28196
+ const visiting = /* @__PURE__ */ new Set();
28197
+ const visit = (name, path) => {
28198
+ if (visited.has(name)) return;
28199
+ if (visiting.has(name)) {
28200
+ throw new CircularDependencyError([...path, name]);
28201
+ }
28202
+ visiting.add(name);
28203
+ const entry = entryByName.get(name);
28204
+ if (entry) {
28205
+ const plugin = plugins.get(entry.binding.plugin);
28206
+ if (plugin) {
28207
+ for (const dep of plugin.dependsOn(entry.binding.input)) {
28208
+ if (entryByName.has(dep)) visit(dep, [...path, name]);
28209
+ }
28210
+ }
28211
+ }
28212
+ visiting.delete(name);
28213
+ visited.add(name);
28214
+ if (entry) sorted.push(entry);
28215
+ };
28216
+ for (const e of entries) visit(e.name, []);
28217
+ return sorted;
28218
+ }
28155
28219
 
28156
28220
  // ../design-tokens/src/persistence.ts
28157
28221
  function loadRegistryFromDir(dir, plugins = []) {
@@ -28427,7 +28491,8 @@ var RegistryItemSchema = external_exports.object({
28427
28491
  // Per-target facets. zod v4's `z.record(enum, ...)` demands EVERY enum key be
28428
28492
  // present; a component built for only some targets must parse, so this is a
28429
28493
  // partial record (only the built targets appear).
28430
- facets: external_exports.partialRecord(ComponentTargetSchema, FacetSchema).default({})
28494
+ facets: external_exports.partialRecord(ComponentTargetSchema, FacetSchema).default({}),
28495
+ parent: external_exports.string().optional()
28431
28496
  });
28432
28497
  var RegistryIndexSchema = external_exports.object({
28433
28498
  name: external_exports.string(),
@@ -31315,6 +31380,8 @@ function resolveWorkspace(workspaces, defaultWorkspace, name) {
31315
31380
  }
31316
31381
 
31317
31382
  // src/mcp/graph.ts
31383
+ var EXPAND_OP = "*";
31384
+ var PROBE_OP = "?";
31318
31385
  function assembleGraph(items) {
31319
31386
  const nodes = /* @__PURE__ */ new Map();
31320
31387
  for (const item of items) {
@@ -31330,13 +31397,45 @@ function assembleGraph(items) {
31330
31397
  if (src?.accessibility !== void 0) intel.accessibility = src.accessibility;
31331
31398
  if (src?.attentionEconomics !== void 0) intel.attentionEconomics = src.attentionEconomics;
31332
31399
  if (src?.trustBuilding !== void 0) intel.trustBuilding = src.trustBuilding;
31333
- nodes.set(item.name, {
31400
+ const gn = {
31334
31401
  id: item.name,
31335
31402
  kind,
31336
31403
  intel,
31337
31404
  facets: item.facets ?? {},
31338
- composesWith: item.composites
31339
- });
31405
+ composesWith: item.composites,
31406
+ parts: []
31407
+ };
31408
+ if (item.parent !== void 0) gn.parent = item.parent;
31409
+ nodes.set(item.name, gn);
31410
+ }
31411
+ for (const node of nodes.values()) {
31412
+ if (node.parent === void 0) continue;
31413
+ if (node.parent === node.id) {
31414
+ throw new Error(`graph: node "${node.id}" parent references itself`);
31415
+ }
31416
+ if (!nodes.has(node.parent)) {
31417
+ throw new Error(`graph: node "${node.id}" parent names unknown id "${node.parent}"`);
31418
+ }
31419
+ }
31420
+ for (const node of nodes.values()) {
31421
+ if (node.parent === void 0) continue;
31422
+ const visited = /* @__PURE__ */ new Set();
31423
+ visited.add(node.id);
31424
+ let current = node.parent;
31425
+ while (current !== void 0) {
31426
+ if (visited.has(current)) {
31427
+ throw new Error(
31428
+ `graph: node "${node.id}" has a circular parent chain through "${current}"`
31429
+ );
31430
+ }
31431
+ visited.add(current);
31432
+ current = nodes.get(current)?.parent;
31433
+ }
31434
+ }
31435
+ for (const node of nodes.values()) {
31436
+ if (node.parent === void 0) continue;
31437
+ const parentNode = nodes.get(node.parent);
31438
+ if (parentNode) parentNode.parts.push(node.id);
31340
31439
  }
31341
31440
  for (const node of nodes.values()) {
31342
31441
  for (const target of node.composesWith) {
@@ -31366,6 +31465,7 @@ function describe(addr, graph, target) {
31366
31465
  const [head, ...rest] = parts;
31367
31466
  if (head === void 0) return { error: `cannot resolve: ${addr}` };
31368
31467
  if (head === "components" || head === "composites") {
31468
+ if (rest.length > 0) return { error: `cannot expand: ${addr}` };
31369
31469
  const kind = head === "components" ? "component" : "composite";
31370
31470
  const roster = [];
31371
31471
  for (const node2 of graph.nodes.values()) {
@@ -31373,9 +31473,24 @@ function describe(addr, graph, target) {
31373
31473
  }
31374
31474
  return roster;
31375
31475
  }
31476
+ if (parts[parts.length - 1] === PROBE_OP) {
31477
+ const baseAddr = parts.slice(0, -1).join(".");
31478
+ const result = describe(baseAddr, graph, target);
31479
+ if (typeof result === "object" && result !== null && "error" in result) return null;
31480
+ return result;
31481
+ }
31482
+ if (head === EXPAND_OP || head === PROBE_OP) {
31483
+ return { error: `cannot expand at root: use 'components' or 'composites'` };
31484
+ }
31376
31485
  const node = graph.nodes.get(head);
31377
31486
  if (!node) return { error: `unknown node: ${head}` };
31378
- if (rest.length === 0) return layer0(node, target);
31487
+ if (rest.length === 0) return layer0(node, graph, target);
31488
+ if (rest[rest.length - 1] === EXPAND_OP) {
31489
+ const prefix = rest.slice(0, -1);
31490
+ if (prefix.length === 0) return expandNode(node, target);
31491
+ if (prefix.length === 1 && prefix[0] === "props") return expandProps(node, target);
31492
+ return { error: `cannot expand: ${addr}` };
31493
+ }
31379
31494
  if (rest.length === 1 && rest[0] === "composesWith") {
31380
31495
  return resolveEdges(node, graph, target);
31381
31496
  }
@@ -31396,7 +31511,7 @@ function toAgentProp(prop) {
31396
31511
  }
31397
31512
  return prop;
31398
31513
  }
31399
- function layer0(node, target) {
31514
+ function layer0(node, graph, target) {
31400
31515
  const facet = target === void 0 ? void 0 : node.facets[target];
31401
31516
  const children = [];
31402
31517
  if (facet) {
@@ -31409,12 +31524,23 @@ function layer0(node, target) {
31409
31524
  if (node.composesWith.length > 0) {
31410
31525
  children.push({ addr: `${node.id}.composesWith`, type: "edge" });
31411
31526
  }
31527
+ for (const partId of node.parts) {
31528
+ children.push({ addr: partId, type: "part" });
31529
+ }
31412
31530
  const result = { id: node.id, kind: node.kind, intel: node.intel, children };
31413
31531
  if (facet) {
31414
31532
  result.snippet = facet.snippet;
31415
31533
  if (facet.slots !== void 0) result.slots = facet.slots;
31416
31534
  if (facet.events !== void 0) result.events = facet.events;
31417
31535
  }
31536
+ if (node.parent !== void 0) {
31537
+ result.parent = node.parent;
31538
+ const parentNode = graph.nodes.get(node.parent);
31539
+ if (parentNode) {
31540
+ const siblings = parentNode.parts.filter((id) => id !== node.id);
31541
+ if (siblings.length > 0) result.siblings = siblings;
31542
+ }
31543
+ }
31418
31544
  return result;
31419
31545
  }
31420
31546
  function resolveEdges(node, graph, target) {
@@ -31424,10 +31550,43 @@ function resolveEdges(node, graph, target) {
31424
31550
  if (seen.has(edgeId)) continue;
31425
31551
  seen.add(edgeId);
31426
31552
  const edgeNode = graph.nodes.get(edgeId);
31427
- if (edgeNode) results.push(layer0(edgeNode, target));
31553
+ if (edgeNode) results.push(layer0(edgeNode, graph, target));
31428
31554
  }
31429
31555
  return results;
31430
31556
  }
31557
+ function expandProps(node, target) {
31558
+ const facet = target === void 0 ? void 0 : node.facets[target];
31559
+ const props = {};
31560
+ if (facet) {
31561
+ for (const [name, prop] of Object.entries(facet.props)) {
31562
+ props[name] = toAgentProp(prop);
31563
+ }
31564
+ }
31565
+ return { expanded: true, props };
31566
+ }
31567
+ function expandNode(node, target) {
31568
+ const facet = target === void 0 ? void 0 : node.facets[target];
31569
+ const props = {};
31570
+ if (facet) {
31571
+ for (const [name, prop] of Object.entries(facet.props)) {
31572
+ props[name] = toAgentProp(prop);
31573
+ }
31574
+ }
31575
+ const result = {
31576
+ id: node.id,
31577
+ kind: node.kind,
31578
+ intel: node.intel,
31579
+ props
31580
+ };
31581
+ if (node.composesWith.length > 0) result.composesWith = node.composesWith;
31582
+ if (node.parts.length > 0) result.parts = node.parts;
31583
+ if (facet) {
31584
+ result.snippet = facet.snippet;
31585
+ if (facet.slots !== void 0) result.slots = facet.slots;
31586
+ if (facet.events !== void 0) result.events = facet.events;
31587
+ }
31588
+ return result;
31589
+ }
31431
31590
 
31432
31591
  // src/mcp/intent.ts
31433
31592
  var INTENT_TAGS = {
@@ -31551,7 +31710,7 @@ function selectNearMiss(use, decisive, matchedTags, graph) {
31551
31710
  }
31552
31711
  function describeNode(id, graph) {
31553
31712
  const result = describe(id, graph);
31554
- if (!Array.isArray(result) && "children" in result) return result;
31713
+ if (result !== null && !Array.isArray(result) && "children" in result) return result;
31555
31714
  return void 0;
31556
31715
  }
31557
31716
  function noMatch() {
@@ -31573,22 +31732,32 @@ function describeWithOverlay(addr, graph, ctx) {
31573
31732
  return roster.map((entry) => ({ id: entry.id, presence: presenceOf(set3, entry.id) }));
31574
31733
  }
31575
31734
  if (addr !== "" && !addr.includes(".") && isNodeResult(result)) {
31576
- const set3 = result.kind === "component" ? ctx.installed.components : ctx.installed.composites;
31577
- return {
31578
- ...result,
31579
- presence: presenceOf(set3, result.id),
31580
- target: ctx.target,
31581
- rendersForTarget: ctx.target !== void 0 && graph.nodes.get(result.id)?.facets[ctx.target] !== void 0
31582
- };
31735
+ return { ...result, ...stampOf(result.id, result.kind, graph, ctx) };
31736
+ }
31737
+ if (isExpandedNodeResult(result)) {
31738
+ return { ...result, ...stampOf(result.id, result.kind, graph, ctx) };
31583
31739
  }
31584
31740
  return result;
31585
31741
  }
31586
31742
  function presenceOf(set3, id) {
31587
31743
  return set3.has(id) ? "installed" : "available";
31588
31744
  }
31745
+ function stampOf(id, kind, graph, ctx) {
31746
+ const set3 = kind === "component" ? ctx.installed.components : ctx.installed.composites;
31747
+ return {
31748
+ presence: presenceOf(set3, id),
31749
+ target: ctx.target,
31750
+ // rendersForTarget reads the node's own facets (on the universal graph):
31751
+ // does a facet for the resolved target exist?
31752
+ rendersForTarget: ctx.target !== void 0 && graph.nodes.get(id)?.facets[ctx.target] !== void 0
31753
+ };
31754
+ }
31589
31755
  function isNodeResult(result) {
31590
31756
  return typeof result === "object" && result !== null && !Array.isArray(result) && "id" in result && "kind" in result && "children" in result;
31591
31757
  }
31758
+ function isExpandedNodeResult(result) {
31759
+ return typeof result === "object" && result !== null && !Array.isArray(result) && "id" in result && "kind" in result && "props" in result && !("children" in result);
31760
+ }
31592
31761
 
31593
31762
  // src/mcp/tools.ts
31594
31763
  function isSafeRelPath(p2) {
@@ -31637,6 +31806,39 @@ var WORKSPACE_PARAM = {
31637
31806
  description: "Workspace name (directory basename). Required when the MCP session has multiple workspaces and none matches cwd. Call rafters_workspaces to list options."
31638
31807
  }
31639
31808
  };
31809
+ var GENERATE_LEADING_FILLER = [
31810
+ "give me a ",
31811
+ "give me an ",
31812
+ "give me ",
31813
+ "i need a ",
31814
+ "i need an ",
31815
+ "i need ",
31816
+ "i want a ",
31817
+ "i want an ",
31818
+ "i want ",
31819
+ "create a ",
31820
+ "create an ",
31821
+ "create ",
31822
+ "make me a ",
31823
+ "make me an ",
31824
+ "make a ",
31825
+ "make an ",
31826
+ "make ",
31827
+ "a ",
31828
+ "an ",
31829
+ "the "
31830
+ ];
31831
+ function normalizeGenerateQuery(intent) {
31832
+ const lowered = intent.trim().toLowerCase();
31833
+ let candidate = lowered;
31834
+ for (const filler of GENERATE_LEADING_FILLER) {
31835
+ if (candidate.startsWith(filler)) {
31836
+ candidate = candidate.slice(filler.length);
31837
+ break;
31838
+ }
31839
+ }
31840
+ return candidate.trim().replace(/[.,!?]+$/, "");
31841
+ }
31640
31842
  var TOOL_DEFINITIONS = [
31641
31843
  {
31642
31844
  name: "rafters_workspaces",
@@ -31679,7 +31881,7 @@ var TOOL_DEFINITIONS = [
31679
31881
  },
31680
31882
  {
31681
31883
  name: "rafters_describe",
31682
- description: 'Recursively introspect the component/composite intel graph. describe() returns the installed surface; describe(components)/describe(composites) list the kind roster; describe(button) returns a node -- intel plus type-marked, drillable children; describe(button.props.fill) drills into a prop; describe(button.props.fill.vocab) returns the real token values. A natural-language question (e.g. "what do I use when it needs to be above everything") routes to the best-matching node plus a near-miss counter-example instead of an address.',
31884
+ description: 'Recursively introspect the component/composite intel graph. describe() returns the installed surface; describe(components)/describe(composites) list the kind roster; describe(button) returns a node -- intel plus type-marked, drillable children; describe(button.props.fill) drills into a prop; describe(button.props.fill.vocab) returns the real token values. describe(button.*) expands all props inline in one call (no more drill-per-prop round trips); describe(button.props.fill.?) probes safely (null on miss, not an error). A natural-language question (e.g. "what do I use when it needs to be above everything") routes to the best-matching node plus a near-miss counter-example instead of an address.',
31683
31885
  inputSchema: {
31684
31886
  type: "object",
31685
31887
  properties: {
@@ -31694,12 +31896,12 @@ var TOOL_DEFINITIONS = [
31694
31896
  },
31695
31897
  {
31696
31898
  name: "rafters_generate",
31697
- description: "STUB (Issue E). Will produce a rafters-correct composition with visible placeholders for app-specific fields/actions/copy. Currently returns a structured not-implemented result.",
31899
+ description: 'Resolve a prose query to ONE registry component and return its verbatim, target-correct snippet with open content slots. A bare component name (e.g. "button", "give me a modal") resolves directly; a semantic question (e.g. "what do I use when it needs to be above everything") falls back to the intent door. Returns { component, target, snippet, slots } where snippet is the registry facet verbatim and each slot is left for the caller to fill. v1 serves single components only -- no parameterization, no composites, no writes.',
31698
31900
  inputSchema: {
31699
31901
  type: "object",
31700
31902
  properties: {
31701
31903
  ...WORKSPACE_PARAM,
31702
- intent: { type: "string", description: "What to generate" }
31904
+ intent: { type: "string", description: "A component name or a natural-language request" }
31703
31905
  },
31704
31906
  required: ["intent"]
31705
31907
  }
@@ -31757,6 +31959,11 @@ var TOOL_DEFINITIONS = [
31757
31959
  }
31758
31960
  ];
31759
31961
  var DEPRECATED_MSG = "use rafters_describe instead";
31962
+ var OverlayInstalledSchema = external_exports.object({
31963
+ components: external_exports.array(external_exports.string()).optional(),
31964
+ primitives: external_exports.array(external_exports.string()).optional(),
31965
+ composites: external_exports.array(external_exports.string()).optional()
31966
+ });
31760
31967
  var RaftersToolHandler = class {
31761
31968
  workspaces;
31762
31969
  defaultWorkspace;
@@ -32004,8 +32211,22 @@ var RaftersToolHandler = class {
32004
32211
  */
32005
32212
  async overlayContext(workspace) {
32006
32213
  const config2 = workspace ? await this.readConfig(workspace.root) : null;
32007
- const base = buildInstalledSet(config2 ?? {});
32008
- const components = /* @__PURE__ */ new Set([...base.components, ...config2?.installed?.primitives ?? []]);
32214
+ const parsedInstalled = OverlayInstalledSchema.safeParse(config2?.installed ?? {});
32215
+ if (!parsedInstalled.success) {
32216
+ const issue2 = parsedInstalled.error.issues[0];
32217
+ const field = issue2 && issue2.path.length > 0 ? `installed.${issue2.path.join(".")}` : "installed";
32218
+ const configPath2 = workspace ? getRaftersPaths(workspace.root).config : "config.rafters.json";
32219
+ return {
32220
+ configError: `malformed config at ${configPath2}: ${field} ${issue2?.message ?? "is invalid"}`
32221
+ };
32222
+ }
32223
+ const base = buildInstalledSet({
32224
+ installed: {
32225
+ components: parsedInstalled.data.components ?? [],
32226
+ composites: parsedInstalled.data.composites ?? []
32227
+ }
32228
+ });
32229
+ const components = /* @__PURE__ */ new Set([...base.components, ...parsedInstalled.data.primitives ?? []]);
32009
32230
  const parsedTarget = ComponentTargetSchema.safeParse(config2?.componentTarget);
32010
32231
  return {
32011
32232
  target: parsedTarget.success ? parsedTarget.data : void 0,
@@ -32034,23 +32255,79 @@ var RaftersToolHandler = class {
32034
32255
  return this.jsonResult(matchIntent(address, graph));
32035
32256
  }
32036
32257
  const ctx = await this.overlayContext(resolved);
32258
+ if ("configError" in ctx) {
32259
+ return this.errorResult(ctx.configError);
32260
+ }
32037
32261
  return this.jsonResult(describeWithOverlay(address, graph, ctx));
32038
32262
  }
32039
32263
  /**
32040
- * The `rafters_generate` stub (Issue E). Registered now so the surface shape
32041
- * (name, input schema) stabilizes before the generator lands; it never
32042
- * attempts real composition. Deliberately does NOT build the graph: nothing
32043
- * here reads it, and a build failure must not turn an honest stub into an
32044
- * error result.
32264
+ * The `rafters_generate` handler. A prose query resolves -- first through a
32265
+ * light, deterministic direct-name lookup (tier b), then through the existing
32266
+ * intent door (tier c) -- to a single registry component, and the tool returns
32267
+ * that component's authoritative, target-correct snippet verbatim with its
32268
+ * content slots marked open for the caller. Both tiers re-resolve the winning
32269
+ * id through `describe(id, graph, ctx.target)`, so the response always carries
32270
+ * the workspace target's facet, never the intent door's untargeted fields.
32271
+ * v1 serves components only: composites, parameterization, composition,
32272
+ * writes, and MCP-side validation are all out of scope.
32045
32273
  */
32046
32274
  async handleGenerate(intent, workspaceName) {
32047
32275
  const resolved = this.resolve(workspaceName);
32048
32276
  if (workspaceName && !resolved) {
32049
32277
  return this.workspaceRequiredError();
32050
32278
  }
32279
+ let graph;
32280
+ try {
32281
+ graph = await this.ensureGraph(resolved);
32282
+ } catch (err) {
32283
+ const message = err instanceof Error ? err.message : "Unknown error";
32284
+ return this.errorResult(`failed to build intel graph: ${message}`);
32285
+ }
32286
+ const ctx = await this.overlayContext(resolved);
32287
+ if ("configError" in ctx) {
32288
+ return this.errorResult(ctx.configError);
32289
+ }
32290
+ const candidate = normalizeGenerateQuery(intent);
32291
+ const directHit = graph.nodes.get(candidate);
32292
+ let nodeId;
32293
+ if (directHit && directHit.kind === "component") {
32294
+ nodeId = candidate;
32295
+ } else {
32296
+ const match = matchIntent(intent, graph);
32297
+ if ("use" in match) nodeId = match.use.id;
32298
+ }
32299
+ if (nodeId === void 0) {
32300
+ return this.errorResult("no registry component matches this query");
32301
+ }
32302
+ const result = describe(nodeId, graph, ctx.target);
32303
+ if (result === null || Array.isArray(result) || !("children" in result)) {
32304
+ return this.errorResult("no registry component matches this query");
32305
+ }
32306
+ const node = result;
32307
+ if (node.kind !== "component") {
32308
+ return this.errorResult(
32309
+ `${node.id} resolved, but is a composite -- rafters_generate v1 returns single components only`
32310
+ );
32311
+ }
32312
+ if (ctx.target === void 0) {
32313
+ return this.errorResult(
32314
+ `${node.id} resolved, but no componentTarget is configured for this workspace -- generate cannot choose a snippet without one`
32315
+ );
32316
+ }
32317
+ if (node.snippet === void 0) {
32318
+ return this.errorResult(
32319
+ `${node.id} resolved, but has no ${ctx.target} facet -- nothing to generate for this target`
32320
+ );
32321
+ }
32051
32322
  return this.jsonResult({
32052
- implemented: false,
32053
- note: `generate is a spike -- see issue E; not yet implemented (requested: ${intent})`
32323
+ component: node.id,
32324
+ target: ctx.target,
32325
+ snippet: node.snippet,
32326
+ slots: (node.slots ?? []).map((slot) => ({
32327
+ slot,
32328
+ ownedBy: "caller",
32329
+ status: "open"
32330
+ }))
32054
32331
  });
32055
32332
  }
32056
32333
  /**
@@ -32185,6 +32462,9 @@ var RaftersToolHandler = class {
32185
32462
  });
32186
32463
  }
32187
32464
  const ctx = await this.overlayContext(resolved);
32465
+ if ("configError" in ctx) {
32466
+ return this.errorResult(ctx.configError, { deprecated: DEPRECATED_MSG });
32467
+ }
32188
32468
  const out = describeWithOverlay(id, graph, ctx);
32189
32469
  return this.jsonResult(this.withDeprecated(out));
32190
32470
  }
@@ -209,6 +209,7 @@ declare const RegistryItemSchema: z.ZodObject<{
209
209
  events: z.ZodOptional<z.ZodArray<z.ZodString>>;
210
210
  snippet: z.ZodString;
211
211
  }, z.core.$strip>>>;
212
+ parent: z.ZodOptional<z.ZodString>;
212
213
  }, z.core.$strip>;
213
214
  type RegistryItem = z.infer<typeof RegistryItemSchema>;
214
215
  /**
@@ -67,7 +67,8 @@ var RegistryItemSchema = z.object({
67
67
  // Per-target facets. zod v4's `z.record(enum, ...)` demands EVERY enum key be
68
68
  // present; a component built for only some targets must parse, so this is a
69
69
  // partial record (only the built targets appear).
70
- facets: z.partialRecord(ComponentTargetSchema, FacetSchema).default({})
70
+ facets: z.partialRecord(ComponentTargetSchema, FacetSchema).default({}),
71
+ parent: z.string().optional()
71
72
  });
72
73
  var RegistryIndexSchema = z.object({
73
74
  name: z.string(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rafters",
3
- "version": "0.0.83",
3
+ "version": "0.0.85",
4
4
  "description": "Design Intelligence CLI. Scaffold tokens, import existing shadcn/Tailwind v4 sources, add components, and serve an MCP server so AI agents read decisions instead of guessing.",
5
5
  "homepage": "https://rafters.studio",
6
6
  "license": "MIT",