rafters 0.0.81 → 0.0.83

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
@@ -5,13 +5,7 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
9
- get: (a2, b2) => (typeof require !== "undefined" ? require : a2)[b2]
10
- }) : x)(function(x) {
11
- if (typeof require !== "undefined") return require.apply(this, arguments);
12
- throw Error('Dynamic require of "' + x + '" is not supported');
13
- });
14
- var __commonJS = (cb, mod) => function __require2() {
8
+ var __commonJS = (cb, mod) => function __require() {
15
9
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
16
10
  };
17
11
  var __export = (target, all) => {
@@ -316,6 +310,7 @@ function toDTCG(tokens, options = {}) {
316
310
  }
317
311
 
318
312
  // ../design-tokens/src/exporters/tailwind.ts
313
+ import * as csstree from "css-tree";
319
314
  var SHADOW_PART_SUFFIX = /-(offset-x|offset-y|blur|spread|color)$/;
320
315
  var MOTION_NAMESPACE_PROPERTY = {
321
316
  duration: "transition-duration",
@@ -1259,19 +1254,18 @@ ${themeBody}`;
1259
1254
  }
1260
1255
  }
1261
1256
  function postProcessDocSheet(css) {
1262
- const csstree3 = __require("css-tree");
1263
- const ast = csstree3.parse(css);
1257
+ const ast = csstree.parse(css);
1264
1258
  const parts = [];
1265
1259
  let hasHostContainer = false;
1266
1260
  const atruleNames = /* @__PURE__ */ new Set(["layer", "property", "keyframes"]);
1267
- csstree3.walk(ast, {
1261
+ csstree.walk(ast, {
1268
1262
  visit: "Atrule",
1269
1263
  enter(node) {
1270
1264
  if (!atruleNames.has(node.name)) return;
1271
1265
  if (node.name === "layer") {
1272
- const prelude = node.prelude ? csstree3.generate(node.prelude) : "";
1266
+ const prelude = node.prelude ? csstree.generate(node.prelude) : "";
1273
1267
  if (prelude === "theme" && node.block) {
1274
- let content = csstree3.generate(node.block);
1268
+ let content = csstree.generate(node.block);
1275
1269
  content = content.replace(/^\{/, "").replace(/\}$/, "");
1276
1270
  content = rewriteRootToHost(content);
1277
1271
  if (!hasHostContainer) {
@@ -1281,22 +1275,22 @@ function postProcessDocSheet(css) {
1281
1275
  parts.push(content);
1282
1276
  }
1283
1277
  } else if (/^(base|components|utilities)/.test(prelude) && node.block) {
1284
- parts.push(csstree3.generate(node));
1278
+ parts.push(csstree.generate(node));
1285
1279
  } else if (prelude.startsWith("properties") && node.block) {
1286
- let block = csstree3.generate(node);
1280
+ let block = csstree.generate(node);
1287
1281
  block = rewritePropertiesSelector(block);
1288
1282
  parts.push(block);
1289
1283
  }
1290
1284
  } else {
1291
- parts.push(csstree3.generate(node));
1285
+ parts.push(csstree.generate(node));
1292
1286
  }
1293
1287
  }
1294
1288
  });
1295
- csstree3.walk(ast, {
1289
+ csstree.walk(ast, {
1296
1290
  visit: "Rule",
1297
1291
  enter(node, _item, list) {
1298
1292
  if (list !== ast.children) return;
1299
- let block = csstree3.generate(node);
1293
+ let block = csstree.generate(node);
1300
1294
  block = rewriteRootToHost(block);
1301
1295
  parts.push(block);
1302
1296
  }
@@ -2105,7 +2099,7 @@ function coerceCoords(space, format, name, coords) {
2105
2099
  });
2106
2100
  return types;
2107
2101
  }
2108
- function parse(str, { meta } = {}) {
2102
+ function parse2(str, { meta } = {}) {
2109
2103
  let env = { "str": String(str)?.trim() };
2110
2104
  hooks.run("parse-start", env);
2111
2105
  if (env.color) {
@@ -2205,7 +2199,7 @@ function getColor(color) {
2205
2199
  throw new TypeError("Empty color reference");
2206
2200
  }
2207
2201
  if (isString(color)) {
2208
- color = parse(color);
2202
+ color = parse2(color);
2209
2203
  }
2210
2204
  let space = color.space || color.spaceId;
2211
2205
  if (!(space instanceof ColorSpace)) {
@@ -5700,7 +5694,7 @@ Object.assign(Color, {
5700
5694
  WHITES,
5701
5695
  Space: ColorSpace,
5702
5696
  spaces: ColorSpace.registry,
5703
- parse,
5697
+ parse: parse2,
5704
5698
  // Global defaults one may want to configure
5705
5699
  defaults
5706
5700
  });
@@ -5950,7 +5944,7 @@ __export(external_exports, {
5950
5944
  object: () => object,
5951
5945
  optional: () => optional,
5952
5946
  overwrite: () => _overwrite,
5953
- parse: () => parse3,
5947
+ parse: () => parse4,
5954
5948
  parseAsync: () => parseAsync2,
5955
5949
  partialRecord: () => partialRecord,
5956
5950
  pipe: () => pipe,
@@ -6254,7 +6248,7 @@ __export(core_exports2, {
6254
6248
  isValidBase64URL: () => isValidBase64URL,
6255
6249
  isValidJWT: () => isValidJWT,
6256
6250
  locales: () => locales_exports,
6257
- parse: () => parse2,
6251
+ parse: () => parse3,
6258
6252
  parseAsync: () => parseAsync,
6259
6253
  prettifyError: () => prettifyError,
6260
6254
  regexes: () => regexes_exports,
@@ -7122,7 +7116,7 @@ var _parse = (_Err) => (schema, value, _ctx, _params) => {
7122
7116
  }
7123
7117
  return result.value;
7124
7118
  };
7125
- var parse2 = /* @__PURE__ */ _parse($ZodRealError);
7119
+ var parse3 = /* @__PURE__ */ _parse($ZodRealError);
7126
7120
  var _parseAsync = (_Err) => async (schema, value, _ctx, params) => {
7127
7121
  const ctx = _ctx ? Object.assign(_ctx, { async: true }) : { async: true };
7128
7122
  let result = schema._zod.run({ value, issues: [] }, ctx);
@@ -9658,10 +9652,10 @@ var $ZodFunction = /* @__PURE__ */ $constructor("$ZodFunction", (inst, def) => {
9658
9652
  throw new Error("implement() must be called with a function");
9659
9653
  }
9660
9654
  return function(...args) {
9661
- const parsedArgs = inst._def.input ? parse2(inst._def.input, args) : args;
9655
+ const parsedArgs = inst._def.input ? parse3(inst._def.input, args) : args;
9662
9656
  const result = Reflect.apply(func, this, parsedArgs);
9663
9657
  if (inst._def.output) {
9664
- return parse2(inst._def.output, result);
9658
+ return parse3(inst._def.output, result);
9665
9659
  }
9666
9660
  return result;
9667
9661
  };
@@ -17283,7 +17277,7 @@ var ZodRealError = $constructor("ZodError", initializer2, {
17283
17277
  });
17284
17278
 
17285
17279
  // ../../node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/parse.js
17286
- var parse3 = /* @__PURE__ */ _parse(ZodRealError);
17280
+ var parse4 = /* @__PURE__ */ _parse(ZodRealError);
17287
17281
  var parseAsync2 = /* @__PURE__ */ _parseAsync(ZodRealError);
17288
17282
  var safeParse2 = /* @__PURE__ */ _safeParse(ZodRealError);
17289
17283
  var safeParseAsync2 = /* @__PURE__ */ _safeParseAsync(ZodRealError);
@@ -17316,7 +17310,7 @@ var ZodType = /* @__PURE__ */ $constructor("ZodType", (inst, def) => {
17316
17310
  reg.add(inst, meta);
17317
17311
  return inst;
17318
17312
  });
17319
- inst.parse = (data, params) => parse3(inst, data, params, { callee: inst.parse });
17313
+ inst.parse = (data, params) => parse4(inst, data, params, { callee: inst.parse });
17320
17314
  inst.safeParse = (data, params) => safeParse2(inst, data, params);
17321
17315
  inst.parseAsync = async (data, params) => parseAsync2(inst, data, params, { callee: inst.parseAsync });
17322
17316
  inst.safeParseAsync = async (data, params) => safeParseAsync2(inst, data, params);
@@ -27313,8 +27307,8 @@ function generateBaseSystem(config2 = {}) {
27313
27307
  const byNamespace = /* @__PURE__ */ new Map();
27314
27308
  const allTokens = [];
27315
27309
  const generators = createGeneratorDefs(mergedConfig.colorPaletteBases);
27316
- for (const { generate: generate3 } of generators) {
27317
- const result = generate3(resolvedConfig);
27310
+ for (const { generate: generate4 } of generators) {
27311
+ const result = generate4(resolvedConfig);
27318
27312
  byNamespace.set(result.namespace, result.tokens);
27319
27313
  allTokens.push(...result.tokens);
27320
27314
  }
@@ -27349,22 +27343,22 @@ function getAvailableAdapters() {
27349
27343
  }
27350
27344
 
27351
27345
  // ../design-tokens/src/importers/shadcn.ts
27352
- import * as csstree from "css-tree";
27346
+ import * as csstree2 from "css-tree";
27353
27347
  function extractShadcnRoot(css) {
27354
- const ast = csstree.parse(css);
27348
+ const ast = csstree2.parse(css);
27355
27349
  const out = [];
27356
- csstree.walk(ast, {
27350
+ csstree2.walk(ast, {
27357
27351
  visit: "Rule",
27358
27352
  enter(rule) {
27359
- const selectors = csstree.generate(rule.prelude).split(",").map((s) => s.trim());
27353
+ const selectors = csstree2.generate(rule.prelude).split(",").map((s) => s.trim());
27360
27354
  if (!selectors.includes(":root")) return;
27361
- csstree.walk(rule.block, {
27355
+ csstree2.walk(rule.block, {
27362
27356
  visit: "Declaration",
27363
27357
  enter(decl) {
27364
27358
  if (!decl.property.startsWith("--")) return;
27365
27359
  out.push({
27366
27360
  name: decl.property.slice(2),
27367
- value: csstree.generate(decl.value).trim()
27361
+ value: csstree2.generate(decl.value).trim()
27368
27362
  });
27369
27363
  }
27370
27364
  });
@@ -27393,13 +27387,13 @@ function extractThemeBlocks2(css) {
27393
27387
 
27394
27388
  // ../design-tokens/src/importers/bases.ts
27395
27389
  var LENGTH = /^(-?\d+(?:\.\d+)?)\s*(rem|px|em)$/;
27396
- function detectBase(css, names, parse7) {
27390
+ function detectBase(css, names, parse8) {
27397
27391
  const decls = [...extractShadcnRoot(css), ...extractThemeBlocks2(css)];
27398
27392
  let last2 = null;
27399
27393
  for (const decl of decls) {
27400
27394
  if (names.includes(decl.name)) last2 = decl.value.trim();
27401
27395
  }
27402
- return last2 === null ? null : parse7(last2);
27396
+ return last2 === null ? null : parse8(last2);
27403
27397
  }
27404
27398
  function parseLength(raw) {
27405
27399
  const match = raw.match(LENGTH);
@@ -27527,7 +27521,7 @@ function colorsFromClassification(classification) {
27527
27521
  }
27528
27522
 
27529
27523
  // ../design-tokens/src/importers/fonts.ts
27530
- import * as csstree2 from "css-tree";
27524
+ import * as csstree3 from "css-tree";
27531
27525
  var GENERIC_KEYWORDS = /* @__PURE__ */ new Set([
27532
27526
  "sans-serif",
27533
27527
  "serif",
@@ -27622,19 +27616,19 @@ function extractFontFaceFamilies(css) {
27622
27616
  const out = [];
27623
27617
  let ast;
27624
27618
  try {
27625
- ast = csstree2.parse(css);
27619
+ ast = csstree3.parse(css);
27626
27620
  } catch {
27627
27621
  return out;
27628
27622
  }
27629
- csstree2.walk(ast, {
27623
+ csstree3.walk(ast, {
27630
27624
  visit: "Atrule",
27631
27625
  enter(atrule) {
27632
27626
  if (atrule.name !== "font-face" || atrule.block === null) return;
27633
- csstree2.walk(atrule.block, {
27627
+ csstree3.walk(atrule.block, {
27634
27628
  visit: "Declaration",
27635
27629
  enter(decl) {
27636
27630
  if (decl.property.toLowerCase() !== "font-family") return;
27637
- const raw = csstree2.generate(decl.value).trim();
27631
+ const raw = csstree3.generate(decl.value).trim();
27638
27632
  const name = stripQuotes(raw);
27639
27633
  if (name !== "") out.push(name);
27640
27634
  }
@@ -28386,6 +28380,41 @@ var RegistryItemIntelligenceSchema = external_exports.object({
28386
28380
  nevers: external_exports.array(external_exports.string()).default([])
28387
28381
  }).optional()
28388
28382
  });
28383
+ var ComponentTargetSchema = external_exports.enum(["react", "astro", "vue", "svelte", "wc"]);
28384
+ var PropFieldSchema = external_exports.discriminatedUnion("type", [
28385
+ external_exports.object({
28386
+ type: external_exports.literal("enum"),
28387
+ values: external_exports.array(external_exports.string()),
28388
+ // verbatim literal union members -- never "string"
28389
+ default: external_exports.string().optional(),
28390
+ required: external_exports.boolean().optional(),
28391
+ constraint: external_exports.object({
28392
+ when: external_exports.object({ prop: external_exports.string(), matches: external_exports.string() }),
28393
+ requires: external_exports.object({ prop: external_exports.string() })
28394
+ }).optional()
28395
+ }),
28396
+ external_exports.object({
28397
+ // Matches #2072's PropNode 'grammar' arm exactly, so graph.ts's
28398
+ // describe(<id>.props.<name>.vocab) drill has real shape data.
28399
+ type: external_exports.literal("grammar"),
28400
+ grammar: external_exports.array(external_exports.string()).min(1),
28401
+ // grammar shape tokens, e.g. ['word', 'word/alpha']
28402
+ vocab: external_exports.string(),
28403
+ // drillable addr, e.g. 'container.props.fill.vocab' -- never inlined
28404
+ onInvalid: external_exports.literal("silent-noop"),
28405
+ default: external_exports.string().optional()
28406
+ }),
28407
+ external_exports.object({
28408
+ type: external_exports.literal("deprecated"),
28409
+ deprecatedFor: external_exports.string()
28410
+ })
28411
+ ]);
28412
+ var FacetSchema = external_exports.object({
28413
+ props: external_exports.record(external_exports.string(), PropFieldSchema),
28414
+ slots: external_exports.array(external_exports.string()).optional(),
28415
+ events: external_exports.array(external_exports.string()).optional(),
28416
+ snippet: external_exports.string()
28417
+ });
28389
28418
  var RegistryItemSchema = external_exports.object({
28390
28419
  name: external_exports.string(),
28391
28420
  type: RegistryItemTypeSchema,
@@ -28394,7 +28423,11 @@ var RegistryItemSchema = external_exports.object({
28394
28423
  files: external_exports.array(RegistryFileSchema),
28395
28424
  rules: external_exports.array(external_exports.string()).default([]),
28396
28425
  composites: external_exports.array(external_exports.string()).default([]),
28397
- intelligence: RegistryItemIntelligenceSchema.optional()
28426
+ intelligence: RegistryItemIntelligenceSchema.optional(),
28427
+ // Per-target facets. zod v4's `z.record(enum, ...)` demands EVERY enum key be
28428
+ // present; a component built for only some targets must parse, so this is a
28429
+ // partial record (only the built targets appear).
28430
+ facets: external_exports.partialRecord(ComponentTargetSchema, FacetSchema).default({})
28398
28431
  });
28399
28432
  var RegistryIndexSchema = external_exports.object({
28400
28433
  name: external_exports.string(),
@@ -28482,6 +28515,46 @@ var RegistryClient = class {
28482
28515
  }
28483
28516
  throw new Error(`"${name}" not found in registry (component, primitive, composite, or rule)`);
28484
28517
  }
28518
+ /**
28519
+ * Fetch every registry item in one call, for the whole-catalog load the MCP
28520
+ * intel graph needs at startup.
28521
+ *
28522
+ * Prefers a single bulk round-trip against `/registry/items.json`. A registry
28523
+ * that does not serve that route -- an older deploy, or a third-party
28524
+ * `registryUrl` (which is user-configurable, see ConfigWiringSchema) -- returns
28525
+ * a non-2xx, and this falls back to looping `fetchItem` over `fetchIndex()`'s
28526
+ * name list, retaining today's per-item cost so no workspace's `describe` ever
28527
+ * hard-fails on a registry that predates the bulk endpoint.
28528
+ */
28529
+ async fetchAllItems() {
28530
+ const response = await fetch(`${this.baseUrl}/registry/items.json`);
28531
+ if (response.ok) {
28532
+ return external_exports.array(RegistryItemSchema).parse(await response.json());
28533
+ }
28534
+ return this.fetchAllItemsByIndex();
28535
+ }
28536
+ /**
28537
+ * The fallback whole-catalog load: one `fetchItem` per name in the index,
28538
+ * across every describe-relevant kind. Deduped so a name appearing in more
28539
+ * than one index list is fetched once.
28540
+ */
28541
+ async fetchAllItemsByIndex() {
28542
+ const index = await this.fetchIndex();
28543
+ const names = [
28544
+ ...index.components,
28545
+ ...index.primitives,
28546
+ ...index.composites,
28547
+ ...index.substrate
28548
+ ];
28549
+ const seen = /* @__PURE__ */ new Set();
28550
+ const items = [];
28551
+ for (const name of names) {
28552
+ if (seen.has(name)) continue;
28553
+ seen.add(name);
28554
+ items.push(await this.fetchItem(name));
28555
+ }
28556
+ return items;
28557
+ }
28485
28558
  /**
28486
28559
  * List all available components.
28487
28560
  */
@@ -30880,8 +30953,8 @@ function readVersion() {
30880
30953
  var VERSION = readVersion();
30881
30954
 
30882
30955
  // src/mcp/tools.ts
30883
- import { readFile as readFile6 } from "fs/promises";
30884
- import { join as join12 } from "path";
30956
+ import { readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
30957
+ import { isAbsolute as isAbsolute3, join as join12 } from "path";
30885
30958
 
30886
30959
  // ../composites/src/built-in-rules/email.ts
30887
30960
  var email3 = external_exports.string().email();
@@ -31031,9 +31104,6 @@ function register3(composite) {
31031
31104
  }
31032
31105
  composites.set(id, composite);
31033
31106
  }
31034
- function get2(id) {
31035
- return composites.get(id);
31036
- }
31037
31107
  function getAll2() {
31038
31108
  return Array.from(composites.values());
31039
31109
  }
@@ -31244,7 +31314,323 @@ function resolveWorkspace(workspaces, defaultWorkspace, name) {
31244
31314
  return defaultWorkspace;
31245
31315
  }
31246
31316
 
31317
+ // src/mcp/graph.ts
31318
+ function assembleGraph(items) {
31319
+ const nodes = /* @__PURE__ */ new Map();
31320
+ for (const item of items) {
31321
+ const kind = kindOf(item.type);
31322
+ if (kind === null) continue;
31323
+ const src = item.intelligence;
31324
+ const intel = {
31325
+ dos: src?.usagePatterns?.dos ?? [],
31326
+ nevers: src?.usagePatterns?.nevers ?? []
31327
+ };
31328
+ if (src?.cognitiveLoad !== void 0) intel.cognitiveLoad = src.cognitiveLoad;
31329
+ if (src?.semanticMeaning !== void 0) intel.semanticMeaning = src.semanticMeaning;
31330
+ if (src?.accessibility !== void 0) intel.accessibility = src.accessibility;
31331
+ if (src?.attentionEconomics !== void 0) intel.attentionEconomics = src.attentionEconomics;
31332
+ if (src?.trustBuilding !== void 0) intel.trustBuilding = src.trustBuilding;
31333
+ nodes.set(item.name, {
31334
+ id: item.name,
31335
+ kind,
31336
+ intel,
31337
+ facets: item.facets ?? {},
31338
+ composesWith: item.composites
31339
+ });
31340
+ }
31341
+ for (const node of nodes.values()) {
31342
+ for (const target of node.composesWith) {
31343
+ if (!nodes.has(target)) {
31344
+ throw new Error(`graph: node "${node.id}" composesWith unknown id "${target}"`);
31345
+ }
31346
+ }
31347
+ }
31348
+ return { nodes };
31349
+ }
31350
+ function kindOf(type2) {
31351
+ if (type2 === "composite") return "composite";
31352
+ if (type2 === "ui" || type2 === "primitive") return "component";
31353
+ return null;
31354
+ }
31355
+ function describe(addr, graph, target) {
31356
+ const parts = addr === "" ? [] : addr.split(".");
31357
+ if (parts.length === 0) {
31358
+ return {
31359
+ kinds: [
31360
+ { addr: "components", type: "edge" },
31361
+ { addr: "composites", type: "edge" }
31362
+ ],
31363
+ nodeCount: graph.nodes.size
31364
+ };
31365
+ }
31366
+ const [head, ...rest] = parts;
31367
+ if (head === void 0) return { error: `cannot resolve: ${addr}` };
31368
+ if (head === "components" || head === "composites") {
31369
+ const kind = head === "components" ? "component" : "composite";
31370
+ const roster = [];
31371
+ for (const node2 of graph.nodes.values()) {
31372
+ if (node2.kind === kind) roster.push({ id: node2.id });
31373
+ }
31374
+ return roster;
31375
+ }
31376
+ const node = graph.nodes.get(head);
31377
+ if (!node) return { error: `unknown node: ${head}` };
31378
+ if (rest.length === 0) return layer0(node, target);
31379
+ if (rest.length === 1 && rest[0] === "composesWith") {
31380
+ return resolveEdges(node, graph, target);
31381
+ }
31382
+ if (rest[0] === "props") {
31383
+ const facet = target === void 0 ? void 0 : node.facets[target];
31384
+ const propName = rest[1];
31385
+ const prop = facet && propName !== void 0 ? facet.props[propName] : void 0;
31386
+ if (!prop) return { error: `cannot resolve: ${addr}` };
31387
+ if (rest.length === 2) return toAgentProp(prop);
31388
+ return { error: `cannot resolve: ${addr}` };
31389
+ }
31390
+ return { error: `cannot resolve: ${addr}` };
31391
+ }
31392
+ function toAgentProp(prop) {
31393
+ if (prop.type === "grammar") {
31394
+ const { vocab: _vocab, ...agentView } = prop;
31395
+ return agentView;
31396
+ }
31397
+ return prop;
31398
+ }
31399
+ function layer0(node, target) {
31400
+ const facet = target === void 0 ? void 0 : node.facets[target];
31401
+ const children = [];
31402
+ if (facet) {
31403
+ for (const [name, prop] of Object.entries(facet.props)) {
31404
+ const child = { addr: `${node.id}.props.${name}`, type: prop.type };
31405
+ if (prop.type === "deprecated") child.deprecatedFor = prop.deprecatedFor;
31406
+ children.push(child);
31407
+ }
31408
+ }
31409
+ if (node.composesWith.length > 0) {
31410
+ children.push({ addr: `${node.id}.composesWith`, type: "edge" });
31411
+ }
31412
+ const result = { id: node.id, kind: node.kind, intel: node.intel, children };
31413
+ if (facet) {
31414
+ result.snippet = facet.snippet;
31415
+ if (facet.slots !== void 0) result.slots = facet.slots;
31416
+ if (facet.events !== void 0) result.events = facet.events;
31417
+ }
31418
+ return result;
31419
+ }
31420
+ function resolveEdges(node, graph, target) {
31421
+ const seen = /* @__PURE__ */ new Set();
31422
+ const results = [];
31423
+ for (const edgeId of node.composesWith) {
31424
+ if (seen.has(edgeId)) continue;
31425
+ seen.add(edgeId);
31426
+ const edgeNode = graph.nodes.get(edgeId);
31427
+ if (edgeNode) results.push(layer0(edgeNode, target));
31428
+ }
31429
+ return results;
31430
+ }
31431
+
31432
+ // src/mcp/intent.ts
31433
+ var INTENT_TAGS = {
31434
+ modal: ["attention", "blocking", "above-all", "focus-trap"],
31435
+ alert: ["attention", "inline", "passive"],
31436
+ tooltip: ["hint", "passive", "inline", "hover"]
31437
+ };
31438
+ var INTENT_AXES = [
31439
+ {
31440
+ tag: "above-all",
31441
+ keywords: [
31442
+ "above everything",
31443
+ "above all",
31444
+ "on top of everything",
31445
+ "sits on top",
31446
+ "on top",
31447
+ "over everything",
31448
+ "over all",
31449
+ "topmost",
31450
+ "highest layer"
31451
+ ]
31452
+ },
31453
+ {
31454
+ tag: "blocking",
31455
+ keywords: [
31456
+ "above everything",
31457
+ "on top of everything",
31458
+ "over everything",
31459
+ "block",
31460
+ "blocking",
31461
+ "must dismiss",
31462
+ "interrupt",
31463
+ "take over",
31464
+ "stops everything",
31465
+ "requires a response",
31466
+ "can't ignore",
31467
+ "cannot ignore"
31468
+ ]
31469
+ },
31470
+ {
31471
+ tag: "inline",
31472
+ keywords: ["inline", "in place", "in the flow", "within the page", "non-blocking"]
31473
+ },
31474
+ {
31475
+ tag: "passive",
31476
+ keywords: ["passive", "non-intrusive", "subtle", "quietly", "in the background"]
31477
+ },
31478
+ {
31479
+ tag: "attention",
31480
+ keywords: ["attention", "notice", "get noticed", "important message"]
31481
+ },
31482
+ {
31483
+ tag: "hint",
31484
+ keywords: ["hint", "a tip", "explain", "extra info", "more info"]
31485
+ },
31486
+ {
31487
+ tag: "hover",
31488
+ keywords: ["hover", "on hover", "mouse over"]
31489
+ }
31490
+ ];
31491
+ function isNaturalLanguageQuery(input) {
31492
+ return input.includes(" ");
31493
+ }
31494
+ function matchIntent(query, graph) {
31495
+ const normalized = query.toLowerCase();
31496
+ const matchedTags = collectMatchedTags(normalized);
31497
+ if (matchedTags.size === 0) return noMatch();
31498
+ const winner = selectUse(matchedTags, graph);
31499
+ if (winner === void 0) return noMatch();
31500
+ const winningTags = winner.tags.filter((tag) => matchedTags.has(tag));
31501
+ const decisive = new Set(winningTags);
31502
+ const nearMiss = selectNearMiss(winner, decisive, matchedTags, graph);
31503
+ if (nearMiss === void 0) return noMatch();
31504
+ const use = describeNode(winner.id, graph);
31505
+ const not = describeNode(nearMiss.id, graph);
31506
+ if (use === void 0 || not === void 0) return noMatch();
31507
+ const divergingTags = nearMiss.tags.filter((tag) => !winner.tags.includes(tag));
31508
+ const because = `${winner.id} is ${winningTags.join("/")}; ${nearMiss.id} is ${divergingTags.join("/")}`;
31509
+ return { use, not, because };
31510
+ }
31511
+ function collectMatchedTags(normalizedQuery) {
31512
+ const matched = /* @__PURE__ */ new Set();
31513
+ for (const axis of INTENT_AXES) {
31514
+ for (const keyword of axis.keywords) {
31515
+ if (normalizedQuery.includes(keyword)) {
31516
+ matched.add(axis.tag);
31517
+ break;
31518
+ }
31519
+ }
31520
+ }
31521
+ return matched;
31522
+ }
31523
+ function selectUse(matchedTags, graph) {
31524
+ let best;
31525
+ let bestScore = 0;
31526
+ for (const [id, tags] of Object.entries(INTENT_TAGS)) {
31527
+ if (!graph.nodes.has(id)) continue;
31528
+ const score = tags.reduce((sum, tag) => matchedTags.has(tag) ? sum + 1 : sum, 0);
31529
+ if (score > bestScore) {
31530
+ bestScore = score;
31531
+ best = { id, tags };
31532
+ }
31533
+ }
31534
+ return best;
31535
+ }
31536
+ function selectNearMiss(use, decisive, matchedTags, graph) {
31537
+ let best;
31538
+ let bestOverlap = 0;
31539
+ for (const [id, tags] of Object.entries(INTENT_TAGS)) {
31540
+ if (id === use.id || !graph.nodes.has(id)) continue;
31541
+ if (tags.some((tag) => decisive.has(tag))) continue;
31542
+ const overlap = tags.filter((tag) => use.tags.includes(tag));
31543
+ const sharesOutsideMatched = overlap.some((tag) => !matchedTags.has(tag));
31544
+ if (!sharesOutsideMatched) continue;
31545
+ if (overlap.length > bestOverlap) {
31546
+ bestOverlap = overlap.length;
31547
+ best = { id, tags };
31548
+ }
31549
+ }
31550
+ return best;
31551
+ }
31552
+ function describeNode(id, graph) {
31553
+ const result = describe(id, graph);
31554
+ if (!Array.isArray(result) && "children" in result) return result;
31555
+ return void 0;
31556
+ }
31557
+ function noMatch() {
31558
+ return { note: "no route matched; describe(components) or describe(composites) to browse" };
31559
+ }
31560
+
31561
+ // src/mcp/overlay.ts
31562
+ function buildInstalledSet(config2) {
31563
+ return {
31564
+ components: new Set(config2.installed?.components ?? []),
31565
+ composites: new Set(config2.installed?.composites ?? [])
31566
+ };
31567
+ }
31568
+ function describeWithOverlay(addr, graph, ctx) {
31569
+ const result = describe(addr, graph, ctx.target);
31570
+ if (addr === "components" || addr === "composites") {
31571
+ const set3 = addr === "components" ? ctx.installed.components : ctx.installed.composites;
31572
+ const roster = result;
31573
+ return roster.map((entry) => ({ id: entry.id, presence: presenceOf(set3, entry.id) }));
31574
+ }
31575
+ 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
+ };
31583
+ }
31584
+ return result;
31585
+ }
31586
+ function presenceOf(set3, id) {
31587
+ return set3.has(id) ? "installed" : "available";
31588
+ }
31589
+ function isNodeResult(result) {
31590
+ return typeof result === "object" && result !== null && !Array.isArray(result) && "id" in result && "kind" in result && "children" in result;
31591
+ }
31592
+
31247
31593
  // src/mcp/tools.ts
31594
+ function isSafeRelPath(p2) {
31595
+ if (isAbsolute3(p2)) return false;
31596
+ return !p2.split(/[/\\]/).includes("..");
31597
+ }
31598
+ var SafePathFieldSchema = PathFieldSchema.refine(
31599
+ (field) => {
31600
+ const entries = typeof field === "string" ? [field] : field;
31601
+ return entries.every((e) => isSafeRelPath(typeof e === "string" ? e : e.path));
31602
+ },
31603
+ { message: "path must be relative and stay inside the workspace (no absolute or `..` paths)" }
31604
+ );
31605
+ var ConfigWiringSchema = external_exports.object({
31606
+ // Closed set -- excludes 'unknown', which is a detection sentinel, not a
31607
+ // valid target a caller may set.
31608
+ framework: external_exports.enum(["next", "vite", "remix", "react-router", "astro", "wc", "vanilla"]),
31609
+ // Must be a valid http(s) URL. (Blocking private/link-local/metadata hosts
31610
+ // is tracked as a follow-up -- see the registryUrl SSRF issue.)
31611
+ registryUrl: external_exports.string().url().refine((u) => /^https?:$/.test(new URL(u).protocol), {
31612
+ message: "registryUrl must be an http(s) URL"
31613
+ }),
31614
+ // Closed set derived from the framework (see ComponentTarget in detect.ts).
31615
+ componentTarget: external_exports.enum(["react", "astro", "vue", "svelte", "wc"]),
31616
+ source: external_exports.string().min(1),
31617
+ cssPath: external_exports.union([external_exports.string(), external_exports.null()]).refine((v) => v === null || isSafeRelPath(v), {
31618
+ message: "cssPath must be relative and stay inside the workspace"
31619
+ }),
31620
+ componentsPath: SafePathFieldSchema,
31621
+ primitivesPath: SafePathFieldSchema,
31622
+ compositesPath: SafePathFieldSchema,
31623
+ rulesPath: SafePathFieldSchema,
31624
+ exports: external_exports.object({
31625
+ tailwind: external_exports.boolean(),
31626
+ typescript: external_exports.boolean(),
31627
+ dtcg: external_exports.boolean(),
31628
+ compiled: external_exports.boolean(),
31629
+ documentation: external_exports.boolean()
31630
+ }).partial()
31631
+ }).partial().strict();
31632
+ var STUDIO_OWNED_KEYS = ["intent", "darkMode", "fonts"];
31633
+ var ADD_MANAGED_KEYS = ["installed"];
31248
31634
  var WORKSPACE_PARAM = {
31249
31635
  workspace: {
31250
31636
  type: "string",
@@ -31254,16 +31640,76 @@ var WORKSPACE_PARAM = {
31254
31640
  var TOOL_DEFINITIONS = [
31255
31641
  {
31256
31642
  name: "rafters_workspaces",
31257
- description: "List rafters workspaces visible to this MCP session. Returns name, path, and which one is the default for unscoped tool calls. Call this first when the project might be a monorepo.",
31643
+ description: "List rafters workspaces, or update a workspace's WIRING config. Called with no arguments (or just `workspace`): returns each workspace name, path, and which is the default for unscoped tool calls -- call this first when the project might be a monorepo. Called with any wiring field: updates that workspace's .rafters/config.rafters.json -- framework, registryUrl, componentTarget, source, cssPath, and the path fields (componentsPath, primitivesPath, compositesPath, rulesPath) and exports; only fields you pass change. Cannot set designer decisions (intent, darkMode, fonts) -- those are set in Studio -- and cannot set installed -- that is managed by `rafters add`.",
31258
31644
  inputSchema: {
31259
31645
  type: "object",
31260
- properties: {},
31646
+ properties: {
31647
+ ...WORKSPACE_PARAM,
31648
+ framework: { type: "string", description: 'Target framework (e.g., "react", "astro")' },
31649
+ registryUrl: {
31650
+ type: "string",
31651
+ description: "Registry to install from / query. Point at your own internal registry."
31652
+ },
31653
+ componentTarget: { type: "string", description: "Where installed components are written" },
31654
+ source: { type: "string", description: "Design system this project was imported from" },
31655
+ cssPath: { type: ["string", "null"], description: "Path to the main CSS file, or null" },
31656
+ componentsPath: {
31657
+ oneOf: [{ type: "string" }, { type: "array" }],
31658
+ description: "Folder(s) to read/write components. String or array of entries."
31659
+ },
31660
+ primitivesPath: {
31661
+ oneOf: [{ type: "string" }, { type: "array" }],
31662
+ description: "Folder(s) to read/write primitives. String or array of entries."
31663
+ },
31664
+ compositesPath: {
31665
+ oneOf: [{ type: "string" }, { type: "array" }],
31666
+ description: "Folder(s) to read/write composites. String or array of entries."
31667
+ },
31668
+ rulesPath: {
31669
+ oneOf: [{ type: "string" }, { type: "array" }],
31670
+ description: "Folder(s) to read/write rules. String or array of entries."
31671
+ },
31672
+ exports: {
31673
+ type: "object",
31674
+ description: "Which output formats to emit (tailwind, typescript, dtcg, compiled, documentation)."
31675
+ }
31676
+ },
31261
31677
  required: []
31262
31678
  }
31263
31679
  },
31680
+ {
31681
+ 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.',
31683
+ inputSchema: {
31684
+ type: "object",
31685
+ properties: {
31686
+ ...WORKSPACE_PARAM,
31687
+ address: {
31688
+ type: "string",
31689
+ description: 'A dot-address ("button.props.variant") or a natural-language question.'
31690
+ }
31691
+ },
31692
+ required: ["address"]
31693
+ }
31694
+ },
31695
+ {
31696
+ 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.",
31698
+ inputSchema: {
31699
+ type: "object",
31700
+ properties: {
31701
+ ...WORKSPACE_PARAM,
31702
+ intent: { type: "string", description: "What to generate" }
31703
+ },
31704
+ required: ["intent"]
31705
+ }
31706
+ },
31707
+ // Deprecated aliases -- kept for one minor release, then removed (tracked as a
31708
+ // follow-up). Input schemas unchanged; every response carries a `deprecated`
31709
+ // field pointing at rafters_describe.
31264
31710
  {
31265
31711
  name: "rafters_composite",
31266
- description: "Query composites by ID, search term, or category. Returns designer intent (solves, appliesWhen, do/never), I/O rules for chaining, and block structure.",
31712
+ description: "[DEPRECATED -- use rafters_describe] Query composites by ID, search term, or category. Returns designer intent (solves, appliesWhen, do/never), I/O rules for chaining, and block structure.",
31267
31713
  inputSchema: {
31268
31714
  type: "object",
31269
31715
  properties: {
@@ -31277,7 +31723,7 @@ var TOOL_DEFINITIONS = [
31277
31723
  },
31278
31724
  {
31279
31725
  name: "rafters_pattern",
31280
- description: 'Get design pattern guidance by querying composites. Search by what the pattern solves (e.g., "authentication", "data entry", "navigation") to get do/never rules, cognitive load, and designer intent.',
31726
+ description: '[DEPRECATED -- use rafters_describe] Get design pattern guidance by querying composites. Search by what the pattern solves (e.g., "authentication", "data entry", "navigation") to get do/never rules, cognitive load, and designer intent.',
31281
31727
  inputSchema: {
31282
31728
  type: "object",
31283
31729
  properties: {
@@ -31296,7 +31742,7 @@ var TOOL_DEFINITIONS = [
31296
31742
  },
31297
31743
  {
31298
31744
  name: "rafters_component",
31299
- description: "Get component intelligence: cognitive load, accessibility, do/never guidance, variants, sizes.",
31745
+ description: "[DEPRECATED -- use rafters_describe] Get component intelligence: cognitive load, accessibility, do/never guidance, variants, sizes.",
31300
31746
  inputSchema: {
31301
31747
  type: "object",
31302
31748
  properties: {
@@ -31310,6 +31756,7 @@ var TOOL_DEFINITIONS = [
31310
31756
  }
31311
31757
  }
31312
31758
  ];
31759
+ var DEPRECATED_MSG = "use rafters_describe instead";
31313
31760
  var RaftersToolHandler = class {
31314
31761
  workspaces;
31315
31762
  defaultWorkspace;
@@ -31317,32 +31764,52 @@ var RaftersToolHandler = class {
31317
31764
  compositesLoadedFor = /* @__PURE__ */ new Set();
31318
31765
  /** Tracks built-in composite loading separately (loaded once globally). */
31319
31766
  builtInCompositesLoaded = false;
31320
- constructor(workspaces, defaultWorkspace) {
31767
+ /**
31768
+ * Registry clients keyed by base URL, so each workspace's `registryUrl` gets
31769
+ * its own fetch cache. Workspaces without a configured URL share the default
31770
+ * singleton (which points at the public registry).
31771
+ */
31772
+ registryClients = /* @__PURE__ */ new Map();
31773
+ /**
31774
+ * Per-workspace-root cache: the intel graph, built once, lazily, on the first
31775
+ * describe/generate call that touches that root. The graph is universal and
31776
+ * target-free (a node carries all its per-target facets); the workspace's
31777
+ * target is applied per query by the overlay, not baked into the cache. Same
31778
+ * per-workspace caching shape as `compositesLoadedFor`/`registryClients`. A
31779
+ * failed build is never inserted, so the next call retries rather than
31780
+ * permanently wedging that workspace.
31781
+ */
31782
+ graphsByWorkspace = /* @__PURE__ */ new Map();
31783
+ /**
31784
+ * The whole-catalog item source `ensureGraph` builds from. Defaults to the
31785
+ * workspace's registry client `fetchAllItems()` (bulk endpoint with per-item
31786
+ * fallback). Injectable so the dispatch can be unit-tested against a fixture
31787
+ * catalog without a network round-trip.
31788
+ */
31789
+ itemsSource;
31790
+ constructor(workspaces, defaultWorkspace, itemsSource) {
31321
31791
  this.workspaces = workspaces;
31322
31792
  this.defaultWorkspace = defaultWorkspace;
31793
+ this.itemsSource = itemsSource ?? (async (ws) => (await this.registryClientFor(ws)).fetchAllItems());
31323
31794
  }
31324
31795
  async handleToolCall(name, args) {
31325
31796
  switch (name) {
31326
31797
  case "rafters_workspaces":
31327
- return this.handleWorkspaces();
31798
+ return this.handleWorkspaces(args);
31799
+ case "rafters_describe":
31800
+ return this.handleDescribe(args.address, args.workspace);
31801
+ case "rafters_generate":
31802
+ return this.handleGenerate(args.intent, args.workspace);
31328
31803
  case "rafters_composite":
31329
31804
  return this.handleComposite(args);
31330
31805
  case "rafters_pattern":
31331
31806
  return this.handlePattern(args);
31332
31807
  case "rafters_component":
31333
- return this.handleComponent(args.name);
31808
+ return this.handleComponent(args.name, args.workspace);
31334
31809
  default:
31335
- return {
31336
- content: [
31337
- {
31338
- type: "text",
31339
- text: JSON.stringify({
31340
- error: `Unknown tool: ${name}`,
31341
- suggestion: "Available tools: rafters_workspaces, rafters_composite, rafters_pattern, rafters_component."
31342
- })
31343
- }
31344
- ]
31345
- };
31810
+ return this.errorResult(`Unknown tool: ${name}`, {
31811
+ suggestion: "Available tools: rafters_workspaces, rafters_describe, rafters_generate. Deprecated: rafters_composite, rafters_pattern, rafters_component."
31812
+ });
31346
31813
  }
31347
31814
  }
31348
31815
  /**
@@ -31359,42 +31826,84 @@ var RaftersToolHandler = class {
31359
31826
  * Use this when a tool requires a workspace and the agent didn't pick one.
31360
31827
  */
31361
31828
  workspaceRequiredError() {
31362
- return {
31363
- content: [
31364
- {
31365
- type: "text",
31366
- text: JSON.stringify({
31367
- error: "workspace parameter required",
31368
- suggestion: "Multiple workspaces are available. Pass `workspace` with one of the names below.",
31369
- workspaces: this.workspaces.map((w) => ({
31370
- name: w.name,
31371
- root: w.root
31372
- }))
31373
- })
31374
- }
31375
- ]
31376
- };
31829
+ return this.errorResult("workspace parameter required", {
31830
+ suggestion: "Multiple workspaces are available. Pass `workspace` with one of the names below.",
31831
+ workspaces: this.workspaces.map((w) => ({ name: w.name, root: w.root }))
31832
+ });
31377
31833
  }
31378
- async handleWorkspaces() {
31379
- return {
31380
- content: [
31381
- {
31382
- type: "text",
31383
- text: JSON.stringify(
31384
- {
31385
- workspaces: this.workspaces.map((w) => ({
31386
- name: w.name,
31387
- root: w.root,
31388
- isDefault: w.name === this.defaultWorkspace?.name
31389
- })),
31390
- defaultWorkspace: this.defaultWorkspace?.name ?? null
31391
- },
31392
- null,
31393
- 2
31394
- )
31395
- }
31396
- ]
31397
- };
31834
+ /** Wrap a JSON-serialisable payload as a text tool result. */
31835
+ jsonResult(payload) {
31836
+ return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
31837
+ }
31838
+ /** Wrap an error string (with optional extra fields) as a tool result. */
31839
+ errorResult(error47, extra) {
31840
+ return this.jsonResult({ error: error47, ...extra });
31841
+ }
31842
+ /**
31843
+ * List workspaces, or -- when any wiring field is present -- update the
31844
+ * target workspace's config. `workspace` selects which; every other key is
31845
+ * treated as a wiring patch. No wiring keys means the list query.
31846
+ */
31847
+ async handleWorkspaces(args) {
31848
+ const { workspace, ...patch } = args;
31849
+ if (Object.keys(patch).length === 0) {
31850
+ return this.jsonResult({
31851
+ workspaces: this.workspaces.map((w) => ({
31852
+ name: w.name,
31853
+ root: w.root,
31854
+ isDefault: w.name === this.defaultWorkspace?.name
31855
+ })),
31856
+ defaultWorkspace: this.defaultWorkspace?.name ?? null
31857
+ });
31858
+ }
31859
+ return this.updateWorkspaceConfig(workspace, patch);
31860
+ }
31861
+ /**
31862
+ * Write a WIRING patch to a workspace's config.rafters.json. Rejects the
31863
+ * designer-owned keys (intent, darkMode, fonts -> Studio) and add-managed
31864
+ * keys (installed -> `rafters add`) with a pointer to the right surface, so
31865
+ * this path structurally cannot remove designer choice. Only the fields in
31866
+ * the patch change; everything else in the config is preserved.
31867
+ */
31868
+ async updateWorkspaceConfig(workspaceName, patch) {
31869
+ const resolved = this.resolve(workspaceName);
31870
+ if (!resolved) {
31871
+ return this.workspaceRequiredError();
31872
+ }
31873
+ const studioKeys = STUDIO_OWNED_KEYS.filter((k) => k in patch);
31874
+ if (studioKeys.length > 0) {
31875
+ return this.errorResult(
31876
+ `${studioKeys.join(", ")} ${studioKeys.length > 1 ? "are" : "is"} a designer decision owned by Rafters Studio, not the MCP. Set ${studioKeys.length > 1 ? "them" : "it"} in Studio.`
31877
+ );
31878
+ }
31879
+ const addKeys = ADD_MANAGED_KEYS.filter((k) => k in patch);
31880
+ if (addKeys.length > 0) {
31881
+ return this.errorResult(`${addKeys.join(", ")} is managed by \`rafters add\`, not the MCP.`);
31882
+ }
31883
+ const result = ConfigWiringSchema.safeParse(patch);
31884
+ if (!result.success) {
31885
+ return this.errorResult(`invalid wiring patch: ${result.error.message}`);
31886
+ }
31887
+ if (Object.keys(result.data).length === 0) {
31888
+ return this.errorResult("no writable wiring fields provided");
31889
+ }
31890
+ const config2 = await this.readConfig(resolved.root);
31891
+ if (!config2) {
31892
+ return this.errorResult(
31893
+ `no config at ${getRaftersPaths(resolved.root).config} -- run \`rafters init\` first`
31894
+ );
31895
+ }
31896
+ const updated = { ...config2, ...result.data };
31897
+ if (result.data.exports !== void 0) {
31898
+ updated.exports = { ...config2.exports, ...result.data.exports };
31899
+ }
31900
+ try {
31901
+ await this.writeConfig(resolved.root, updated);
31902
+ } catch (err) {
31903
+ const message = err instanceof Error ? err.message : "Unknown error";
31904
+ return this.errorResult(`failed to write config: ${message}`);
31905
+ }
31906
+ return this.jsonResult({ ok: true, workspace: resolved.name, updated: result.data });
31398
31907
  }
31399
31908
  /**
31400
31909
  * Recursively discover composites under the given directories via the shared
@@ -31423,6 +31932,138 @@ var RaftersToolHandler = class {
31423
31932
  this.compositesLoadedFor.add(workspace.root);
31424
31933
  }
31425
31934
  }
31935
+ /**
31936
+ * Read and migrate a workspace's `.rafters/config.rafters.json`. Returns null
31937
+ * when the file is absent or unparseable. The single config read the MCP
31938
+ * tools share -- composite paths and the registry URL both come through here.
31939
+ */
31940
+ async readConfig(workspaceRoot) {
31941
+ const paths = getRaftersPaths(workspaceRoot);
31942
+ try {
31943
+ return migrateConfig(
31944
+ JSON.parse(await readFile6(paths.config, "utf-8"))
31945
+ );
31946
+ } catch {
31947
+ return null;
31948
+ }
31949
+ }
31950
+ /** Persist a workspace's config back to `.rafters/config.rafters.json`. */
31951
+ async writeConfig(workspaceRoot, config2) {
31952
+ const paths = getRaftersPaths(workspaceRoot);
31953
+ await writeFile4(paths.config, `${JSON.stringify(config2, null, 2)}
31954
+ `);
31955
+ }
31956
+ /**
31957
+ * Resolve the registry client for a workspace, honoring its configured
31958
+ * `registryUrl`. Workspaces with no config, or no `registryUrl`, share the
31959
+ * default singleton. Clients are cached per URL so each keeps its fetch cache.
31960
+ */
31961
+ async registryClientFor(workspace) {
31962
+ if (!workspace) return registryClient;
31963
+ const url3 = (await this.readConfig(workspace.root))?.registryUrl;
31964
+ if (!url3) return registryClient;
31965
+ let client = this.registryClients.get(url3);
31966
+ if (!client) {
31967
+ client = new RegistryClient(url3);
31968
+ this.registryClients.set(url3, client);
31969
+ }
31970
+ return client;
31971
+ }
31972
+ /**
31973
+ * Build (once, lazily) and cache the intel graph for a workspace root.
31974
+ * `assembleGraph` (#2072/#2090) consumes the whole-catalog `RegistryItem[]` and
31975
+ * carries each node's complete intelligence and all per-target facets onto the
31976
+ * universal graph. Throws when the catalog can't be loaded (both the bulk
31977
+ * endpoint and the per-item fallback failed) or the graph is structurally
31978
+ * broken (a dangling `composesWith` edge -- #2072's deliberate fail-fast); the
31979
+ * caller converts that into a structured error result. A failed build is never
31980
+ * cached, so the next call retries.
31981
+ */
31982
+ async ensureGraph(workspace) {
31983
+ const key = workspace?.root ?? "";
31984
+ const cached2 = this.graphsByWorkspace.get(key);
31985
+ if (cached2) return cached2;
31986
+ const graph = assembleGraph(await this.itemsSource(workspace));
31987
+ this.graphsByWorkspace.set(key, graph);
31988
+ return graph;
31989
+ }
31990
+ /**
31991
+ * Resolve a workspace's overlay context: the configured `componentTarget`
31992
+ * (echoed as-is, `undefined` in degraded mode) and its installed set. Per the
31993
+ * integration note on #2074, `installed.primitives` folds into the components
31994
+ * set -- a `primitive`-kind item maps to graph kind `component`, so without the
31995
+ * fold every installed primitive would misreport as `available`. Built here
31996
+ * rather than by mutating `buildInstalledSet`'s output, leaving overlay.ts
31997
+ * untouched.
31998
+ *
31999
+ * `componentTarget` comes off unvalidated on-disk config (`readConfig` is a raw
32000
+ * `JSON.parse`), so it is run through `ComponentTargetSchema` here rather than
32001
+ * trusted as a `ComponentTarget` -- a stale or typo'd value falls back to
32002
+ * degraded mode (`undefined` target) instead of silently forcing
32003
+ * `rendersForTarget` false for every node.
32004
+ */
32005
+ async overlayContext(workspace) {
32006
+ 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 ?? []]);
32009
+ const parsedTarget = ComponentTargetSchema.safeParse(config2?.componentTarget);
32010
+ return {
32011
+ target: parsedTarget.success ? parsedTarget.data : void 0,
32012
+ installed: { components, composites: base.composites }
32013
+ };
32014
+ }
32015
+ /**
32016
+ * The `rafters_describe` dispatcher -- the one seam that composes #2072/#2074/
32017
+ * #2075. A natural-language question routes through the intent door
32018
+ * (`matchIntent`); a dot-address resolves through the workspace overlay
32019
+ * (`describeWithOverlay`, which delegates to #2072's `describe`).
32020
+ */
32021
+ async handleDescribe(address, workspaceName) {
32022
+ const resolved = this.resolve(workspaceName);
32023
+ if (workspaceName && !resolved) {
32024
+ return this.workspaceRequiredError();
32025
+ }
32026
+ let graph;
32027
+ try {
32028
+ graph = await this.ensureGraph(resolved);
32029
+ } catch (err) {
32030
+ const message = err instanceof Error ? err.message : "Unknown error";
32031
+ return this.errorResult(`failed to build intel graph: ${message}`);
32032
+ }
32033
+ if (isNaturalLanguageQuery(address)) {
32034
+ return this.jsonResult(matchIntent(address, graph));
32035
+ }
32036
+ const ctx = await this.overlayContext(resolved);
32037
+ return this.jsonResult(describeWithOverlay(address, graph, ctx));
32038
+ }
32039
+ /**
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.
32045
+ */
32046
+ async handleGenerate(intent, workspaceName) {
32047
+ const resolved = this.resolve(workspaceName);
32048
+ if (workspaceName && !resolved) {
32049
+ return this.workspaceRequiredError();
32050
+ }
32051
+ return this.jsonResult({
32052
+ implemented: false,
32053
+ note: `generate is a spike -- see issue E; not yet implemented (requested: ${intent})`
32054
+ });
32055
+ }
32056
+ /**
32057
+ * Stamp the deprecated marker as a top-level field. Objects gain a sibling
32058
+ * `deprecated` key; the rare array/leaf result is wrapped so the marker still
32059
+ * rides at top level.
32060
+ */
32061
+ withDeprecated(payload) {
32062
+ if (typeof payload === "object" && payload !== null && !Array.isArray(payload)) {
32063
+ return { ...payload, deprecated: DEPRECATED_MSG };
32064
+ }
32065
+ return { result: payload, deprecated: DEPRECATED_MSG };
32066
+ }
31426
32067
  /**
31427
32068
  * Resolve the set of folders to scan for composite manifests in a workspace.
31428
32069
  * Reads `.rafters/config.rafters.json` and applies the workspace's
@@ -31432,13 +32073,7 @@ var RaftersToolHandler = class {
31432
32073
  */
31433
32074
  async compositeReadRoots(workspaceRoot) {
31434
32075
  const paths = getRaftersPaths(workspaceRoot);
31435
- let config2 = null;
31436
- try {
31437
- config2 = migrateConfig(
31438
- JSON.parse(await readFile6(paths.config, "utf-8"))
31439
- );
31440
- } catch {
31441
- }
32076
+ const config2 = await this.readConfig(workspaceRoot);
31442
32077
  if (!config2?.compositesPath) {
31443
32078
  return [join12(paths.root, "composites")];
31444
32079
  }
@@ -31446,16 +32081,16 @@ var RaftersToolHandler = class {
31446
32081
  }
31447
32082
  async handleComposite(args) {
31448
32083
  const { id, query, category, workspace } = args;
32084
+ if (id) {
32085
+ return this.describeById(id, workspace);
32086
+ }
31449
32087
  const resolved = this.resolve(workspace);
31450
32088
  if (workspace && !resolved) {
31451
32089
  return this.workspaceRequiredError();
31452
32090
  }
31453
32091
  await this.ensureCompositesLoaded(resolved);
31454
32092
  let composites2;
31455
- if (id) {
31456
- const c4 = get2(id);
31457
- composites2 = c4 ? [c4] : [];
31458
- } else if (query) {
32093
+ if (query) {
31459
32094
  composites2 = search(query);
31460
32095
  } else if (category) {
31461
32096
  composites2 = getByCategory(category);
@@ -31480,9 +32115,7 @@ var RaftersToolHandler = class {
31480
32115
  // are listed -- the array is empty when nothing is constrained.
31481
32116
  blockRules: c4.blocks.filter((b2) => b2.rules && b2.rules.length > 0).map((b2) => ({ id: b2.id, type: b2.type, rules: b2.rules }))
31482
32117
  }));
31483
- return {
31484
- content: [{ type: "text", text: JSON.stringify({ composites: result }, null, 2) }]
31485
- };
32118
+ return this.jsonResult({ composites: result, deprecated: DEPRECATED_MSG });
31486
32119
  }
31487
32120
  async handlePattern(args) {
31488
32121
  const { solves, query, workspace } = args;
@@ -31509,17 +32142,10 @@ var RaftersToolHandler = class {
31509
32142
  id: c4.manifest.id,
31510
32143
  solves: c4.manifest.solves
31511
32144
  }));
31512
- return {
31513
- content: [
31514
- {
31515
- type: "text",
31516
- text: JSON.stringify({
31517
- error: "No patterns found matching query",
31518
- available
31519
- })
31520
- }
31521
- ]
31522
- };
32145
+ return this.errorResult("No patterns found matching query", {
32146
+ available,
32147
+ deprecated: DEPRECATED_MSG
32148
+ });
31523
32149
  }
31524
32150
  const patterns = composites2.map((c4) => ({
31525
32151
  id: c4.manifest.id,
@@ -31529,51 +32155,38 @@ var RaftersToolHandler = class {
31529
32155
  cognitiveLoad: c4.manifest.cognitiveLoad,
31530
32156
  usagePatterns: c4.manifest.usagePatterns
31531
32157
  }));
31532
- return {
31533
- content: [{ type: "text", text: JSON.stringify({ patterns }, null, 2) }]
31534
- };
32158
+ return this.jsonResult({ patterns, deprecated: DEPRECATED_MSG });
32159
+ }
32160
+ /**
32161
+ * DEPRECATED alias for `rafters_describe`. A component resolves by id through
32162
+ * the overlay exactly as `describe(<id>)` does (#2072's resolver has no
32163
+ * separate component/composite address form -- `describe(<id>)` resolves either
32164
+ * kind), so this is a direct, lossless forward with the deprecated marker added.
32165
+ */
32166
+ async handleComponent(componentName, workspaceName) {
32167
+ return this.describeById(componentName, workspaceName);
31535
32168
  }
31536
- async handleComponent(componentName) {
32169
+ /**
32170
+ * Shared by the deprecated `rafters_component` and `rafters_composite({id})`
32171
+ * paths: resolve one id through the overlay, stamp the deprecated marker.
32172
+ */
32173
+ async describeById(id, workspaceName) {
32174
+ const resolved = this.resolve(workspaceName);
32175
+ if (workspaceName && !resolved) {
32176
+ return this.workspaceRequiredError();
32177
+ }
32178
+ let graph;
31537
32179
  try {
31538
- const item = await registryClient.fetchComponent(componentName);
31539
- return {
31540
- content: [
31541
- {
31542
- type: "text",
31543
- text: JSON.stringify(
31544
- {
31545
- name: item.name,
31546
- type: item.type,
31547
- description: item.description,
31548
- primitives: item.primitives,
31549
- rules: item.rules,
31550
- composites: item.composites,
31551
- files: item.files,
31552
- // The intelligence field carries the WHY of the component:
31553
- // cognitive load, accessibility, do/never, semantic meaning.
31554
- // Extracted from JSDoc by the registry generator and present
31555
- // on every component JSON. Previously stripped by the schema
31556
- // (not declared) and not referenced by the handler -- the
31557
- // tool's whole reason for existing went missing somewhere.
31558
- intelligence: item.intelligence
31559
- },
31560
- null,
31561
- 2
31562
- )
31563
- }
31564
- ]
31565
- };
32180
+ graph = await this.ensureGraph(resolved);
31566
32181
  } catch (err) {
31567
32182
  const message = err instanceof Error ? err.message : "Unknown error";
31568
- return {
31569
- content: [
31570
- {
31571
- type: "text",
31572
- text: JSON.stringify({ error: message })
31573
- }
31574
- ]
31575
- };
32183
+ return this.errorResult(`failed to build intel graph: ${message}`, {
32184
+ deprecated: DEPRECATED_MSG
32185
+ });
31576
32186
  }
32187
+ const ctx = await this.overlayContext(resolved);
32188
+ const out = describeWithOverlay(id, graph, ctx);
32189
+ return this.jsonResult(this.withDeprecated(out));
31577
32190
  }
31578
32191
  };
31579
32192
 
@@ -31650,8 +32263,8 @@ import { existsSync as existsSync8 } from "fs";
31650
32263
  import { resolve as resolve6 } from "path";
31651
32264
 
31652
32265
  // ../studio/src/api/vite-plugin.ts
31653
- import { readFile as readFile7, writeFile as writeFile4 } from "fs/promises";
31654
- import { isAbsolute as isAbsolute3, join as join14 } from "path";
32266
+ import { readFile as readFile7, writeFile as writeFile5 } from "fs/promises";
32267
+ import { isAbsolute as isAbsolute4, join as join14 } from "path";
31655
32268
  var REGISTRY_PLUGINS3 = [scalePlugin, contrastPlugin, statePlugin, invertPlugin];
31656
32269
  var STUDIO_REASON_DEFAULT = "studio interactive edit";
31657
32270
  var TokenResponseSchema = external_exports.object({
@@ -31712,7 +32325,7 @@ async function readRaftersConfig() {
31712
32325
  }
31713
32326
  function validateFontsPath(path) {
31714
32327
  if (path === null || path === void 0) return null;
31715
- if (isAbsolute3(path)) return "fonts path must be relative";
32328
+ if (isAbsolute4(path)) return "fonts path must be relative";
31716
32329
  return null;
31717
32330
  }
31718
32331
  function validateIntent(intent) {
@@ -31731,6 +32344,17 @@ var SetTokenMessageSchema = external_exports.object({
31731
32344
  persist: external_exports.boolean().optional(),
31732
32345
  reason: external_exports.string().optional()
31733
32346
  });
32347
+ var ConfigPatchSchema = external_exports.object({
32348
+ intent: external_exports.string().min(1).optional(),
32349
+ darkMode: external_exports.enum(["class", "media"]).optional(),
32350
+ fonts: FontsConfigSchema.optional()
32351
+ }).refine((patch) => Object.keys(patch).length > 0, {
32352
+ message: "patch must set at least one of: intent, darkMode, fonts"
32353
+ });
32354
+ function ridOf(rawData) {
32355
+ const rid = rawData?.__rid;
32356
+ return typeof rid === "string" ? rid : void 0;
32357
+ }
31734
32358
  var TokenPatchSchema = TokenSchema.pick({
31735
32359
  value: true,
31736
32360
  trustLevel: true,
@@ -32241,83 +32865,60 @@ function studioApiPlugin() {
32241
32865
  client.send("rafters:token-updated", { ok: false, error: String(error47) });
32242
32866
  }
32243
32867
  });
32244
- server.ws.on("rafters:get-config", async (_rawData, client) => {
32868
+ server.ws.on("rafters:get-config", async (rawData, client) => {
32869
+ const __rid = ridOf(rawData);
32870
+ const reply = (msg) => client.send("rafters:config", { ...msg, __rid });
32245
32871
  const config2 = await readRaftersConfig();
32246
32872
  if (!config2) {
32247
- client.send("rafters:config", {
32248
- ok: false,
32249
- error: `config not found at ${configPath}`
32250
- });
32873
+ reply({ ok: false, error: `config not found at ${configPath}` });
32251
32874
  return;
32252
32875
  }
32253
- client.send("rafters:config", { ok: true, config: config2 });
32876
+ reply({ ok: true, config: config2 });
32254
32877
  });
32255
- server.ws.on("rafters:set-intent", async (rawData, client) => {
32256
- const parsed = external_exports.object({ intent: external_exports.string().min(1) }).safeParse(rawData);
32878
+ server.ws.on("rafters:set-config", async (rawData, client) => {
32879
+ const __rid = ridOf(rawData);
32880
+ const reply = (msg) => client.send("rafters:config", { ...msg, __rid });
32881
+ const parsed = ConfigPatchSchema.safeParse(rawData);
32257
32882
  if (!parsed.success) {
32258
- client.send("rafters:intent-updated", {
32259
- ok: false,
32260
- error: `Invalid message: ${parsed.error.message}`
32261
- });
32883
+ reply({ ok: false, error: `Invalid patch: ${parsed.error.message}` });
32262
32884
  return;
32263
32885
  }
32264
- const { intent } = parsed.data;
32265
- const intentError = validateIntent(intent);
32266
- if (intentError) {
32267
- client.send("rafters:intent-updated", { ok: false, error: intentError });
32268
- return;
32269
- }
32270
- const config2 = await readRaftersConfig();
32271
- if (!config2) {
32272
- client.send("rafters:intent-updated", {
32273
- ok: false,
32274
- error: `config not found at ${configPath}`
32275
- });
32276
- return;
32277
- }
32278
- const updated = { ...config2, intent };
32279
- try {
32280
- await writeFile4(configPath, JSON.stringify(updated, null, 2));
32281
- client.send("rafters:intent-updated", { ok: true, intent });
32282
- } catch (error47) {
32283
- console.log(`[rafters] Intent update failed: ${error47}`);
32284
- client.send("rafters:intent-updated", { ok: false, error: String(error47) });
32285
- }
32286
- });
32287
- server.ws.on("rafters:set-fonts", async (rawData, client) => {
32288
- const parsed = FontsConfigSchema.safeParse(rawData);
32289
- if (!parsed.success) {
32290
- client.send("rafters:fonts-updated", {
32291
- ok: false,
32292
- error: `Invalid message: ${parsed.error.message}`
32293
- });
32294
- return;
32886
+ const patch = parsed.data;
32887
+ if (patch.intent !== void 0) {
32888
+ const intentError = validateIntent(patch.intent);
32889
+ if (intentError) {
32890
+ reply({ ok: false, error: intentError });
32891
+ return;
32892
+ }
32295
32893
  }
32296
- const fontsData = parsed.data;
32297
- const pathError = validateFontsPath(fontsData.path);
32298
- if (pathError) {
32299
- client.send("rafters:fonts-updated", { ok: false, error: pathError });
32300
- return;
32894
+ if (patch.fonts?.path !== void 0) {
32895
+ const pathError = validateFontsPath(patch.fonts.path);
32896
+ if (pathError) {
32897
+ reply({ ok: false, error: pathError });
32898
+ return;
32899
+ }
32301
32900
  }
32302
32901
  const config2 = await readRaftersConfig();
32303
32902
  if (!config2) {
32304
- client.send("rafters:fonts-updated", {
32305
- ok: false,
32306
- error: `config not found at ${configPath}`
32307
- });
32903
+ reply({ ok: false, error: `config not found at ${configPath}` });
32308
32904
  return;
32309
32905
  }
32310
- const existing = config2.fonts ?? {};
32311
- const fonts = { ...existing };
32312
- if (fontsData.path !== void 0) fonts.path = fontsData.path;
32313
- if (fontsData.imports !== void 0) fonts.imports = fontsData.imports;
32314
- const updated = { ...config2, fonts };
32906
+ const updated = { ...config2 };
32907
+ if (patch.intent !== void 0) updated.intent = patch.intent;
32908
+ if (patch.darkMode !== void 0) updated.darkMode = patch.darkMode;
32909
+ if (patch.fonts !== void 0) {
32910
+ const merged = { ...config2.fonts };
32911
+ if (patch.fonts.path !== void 0) merged.path = patch.fonts.path;
32912
+ if (patch.fonts.imports !== void 0) merged.imports = patch.fonts.imports;
32913
+ updated.fonts = merged;
32914
+ }
32315
32915
  try {
32316
- await writeFile4(configPath, JSON.stringify(updated, null, 2));
32317
- client.send("rafters:fonts-updated", { ok: true, fonts });
32916
+ await writeFile5(configPath, `${JSON.stringify(updated, null, 2)}
32917
+ `);
32918
+ reply({ ok: true, config: updated });
32318
32919
  } catch (error47) {
32319
- console.log(`[rafters] Fonts update failed: ${error47}`);
32320
- client.send("rafters:fonts-updated", { ok: false, error: String(error47) });
32920
+ console.log(`[rafters] Config update failed: ${error47}`);
32921
+ reply({ ok: false, error: String(error47) });
32321
32922
  }
32322
32923
  });
32323
32924
  server.middlewares.use(async (req, res, next) => {