rafters 0.0.85 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +2253 -2144
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -80,9 +80,9 @@ var require_escape_html = __commonJS({
80
80
  import { Command } from "commander";
81
81
 
82
82
  // src/commands/add.ts
83
- import { existsSync as existsSync4 } from "fs";
84
- import { access, mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
85
- import { dirname, join as join8 } from "path";
83
+ import { existsSync as existsSync3 } from "fs";
84
+ import { access, mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
85
+ import { dirname, join as join7 } from "path";
86
86
 
87
87
  // ../design-tokens/src/exporters/dtcg.ts
88
88
  function categoryToDTCGType(category, token) {
@@ -1021,7 +1021,7 @@ ${sourceDirectives}
1021
1021
  ${themeBody}`;
1022
1022
  const { execFileSync } = await import("child_process");
1023
1023
  const { mkdtempSync, writeFileSync: writeFileSync2, readFileSync: readFileSync4, rmSync } = await import("fs");
1024
- const { join: join15, dirname: dirname4 } = await import("path");
1024
+ const { join: join16, dirname: dirname4 } = await import("path");
1025
1025
  const { createRequire: createRequire2 } = await import("module");
1026
1026
  const require2 = createRequire2(import.meta.url);
1027
1027
  let pkgDir;
@@ -1031,10 +1031,10 @@ ${themeBody}`;
1031
1031
  } catch {
1032
1032
  throw new Error("Failed to resolve @tailwindcss/cli");
1033
1033
  }
1034
- const binPath = join15(pkgDir, "dist", "index.mjs");
1035
- const tempDir = mkdtempSync(join15(pkgDir, ".tmp-compile-"));
1036
- const tempInput = join15(tempDir, "input.css");
1037
- const tempOutput = join15(tempDir, "output.css");
1034
+ const binPath = join16(pkgDir, "dist", "index.mjs");
1035
+ const tempDir = mkdtempSync(join16(pkgDir, ".tmp-compile-"));
1036
+ const tempInput = join16(tempDir, "input.css");
1037
+ const tempOutput = join16(tempDir, "output.css");
1038
1038
  try {
1039
1039
  writeFileSync2(tempInput, input);
1040
1040
  const args = [binPath, "-i", tempInput, "-o", tempOutput];
@@ -1218,7 +1218,7 @@ async function registryToDocumentation(registry2, options = {}) {
1218
1218
  const themeBody = registryToTailwind(registry2, { includeImport: false });
1219
1219
  const candidates = deriveCandidates(themeBody);
1220
1220
  const { mkdtempSync, writeFileSync: writeFileSync2, rmSync } = await import("fs");
1221
- const { join: join15, dirname: dirname4 } = await import("path");
1221
+ const { join: join16, dirname: dirname4 } = await import("path");
1222
1222
  const { createRequire: createRequire2 } = await import("module");
1223
1223
  const require2 = createRequire2(import.meta.url);
1224
1224
  let pkgDir;
@@ -1230,8 +1230,8 @@ async function registryToDocumentation(registry2, options = {}) {
1230
1230
  "Failed to resolve @tailwindcss/cli -- install it to generate documentation CSS"
1231
1231
  );
1232
1232
  }
1233
- const tempDir = mkdtempSync(join15(pkgDir, ".tmp-doc-compile-"));
1234
- const candidateFile = join15(tempDir, "candidates.txt");
1233
+ const tempDir = mkdtempSync(join16(pkgDir, ".tmp-doc-compile-"));
1234
+ const candidateFile = join16(tempDir, "candidates.txt");
1235
1235
  writeFileSync2(candidateFile, candidates.join("\n"));
1236
1236
  const sourceDirectives = [
1237
1237
  `@source "${candidateFile}";`,
@@ -1240,12 +1240,12 @@ async function registryToDocumentation(registry2, options = {}) {
1240
1240
  const input = `@import "tailwindcss" source(none);
1241
1241
  ${sourceDirectives}
1242
1242
  ${themeBody}`;
1243
- const tempInput = join15(tempDir, "input.css");
1244
- const tempOutput = join15(tempDir, "output.css");
1243
+ const tempInput = join16(tempDir, "input.css");
1244
+ const tempOutput = join16(tempDir, "output.css");
1245
1245
  try {
1246
1246
  writeFileSync2(tempInput, input);
1247
1247
  const { execFileSync } = await import("child_process");
1248
- const binPath = join15(pkgDir, "dist", "index.mjs");
1248
+ const binPath = join16(pkgDir, "dist", "index.mjs");
1249
1249
  const args = [binPath, "-i", tempInput, "-o", tempOutput];
1250
1250
  if (minify) args.push("--minify");
1251
1251
  execFileSync("node", args, { stdio: "pipe", timeout: 6e4, cwd: pkgDir });
@@ -6319,7 +6319,7 @@ function $constructor(name, initializer3, params) {
6319
6319
  Object.defineProperty(_, "name", { value: name });
6320
6320
  return _;
6321
6321
  }
6322
- var $brand = Symbol("zod_brand");
6322
+ var $brand = /* @__PURE__ */ Symbol("zod_brand");
6323
6323
  var $ZodAsyncError = class extends Error {
6324
6324
  constructor() {
6325
6325
  super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`);
@@ -6464,7 +6464,7 @@ function floatSafeRemainder(val, step) {
6464
6464
  const stepInt = Number.parseInt(step.toFixed(decCount).replace(".", ""));
6465
6465
  return valInt % stepInt / 10 ** decCount;
6466
6466
  }
6467
- var EVALUATING = Symbol("evaluating");
6467
+ var EVALUATING = /* @__PURE__ */ Symbol("evaluating");
6468
6468
  function defineLazy(object2, key, getter) {
6469
6469
  let value = void 0;
6470
6470
  Object.defineProperty(object2, key, {
@@ -15446,8 +15446,8 @@ function yo_default() {
15446
15446
  }
15447
15447
 
15448
15448
  // ../../node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/registries.js
15449
- var $output = Symbol("ZodOutput");
15450
- var $input = Symbol("ZodInput");
15449
+ var $output = /* @__PURE__ */ Symbol("ZodOutput");
15450
+ var $input = /* @__PURE__ */ Symbol("ZodInput");
15451
15451
  var $ZodRegistry = class {
15452
15452
  constructor() {
15453
15453
  this._map = /* @__PURE__ */ new WeakMap();
@@ -21728,9 +21728,6 @@ var RESERVED_FILL_SEGMENTS = ["to", "via", "from"];
21728
21728
  var SEPARATOR = `-${RESERVED_FILL_SEGMENTS[0]}-`;
21729
21729
 
21730
21730
  // ../shared/src/types.ts
21731
- var COMPUTED = Symbol.for(
21732
- "rafters.computed"
21733
- );
21734
21731
  var OKLCHSchema = external_exports.object({
21735
21732
  /** Lightness: 0 (black) to 1 (white) */
21736
21733
  l: external_exports.number().min(0).max(1),
@@ -29559,101 +29556,7 @@ function isPathField(value) {
29559
29556
  return typeof value === "string" || Array.isArray(value);
29560
29557
  }
29561
29558
 
29562
- // src/commands/init.ts
29563
- import { existsSync as existsSync3 } from "fs";
29564
- import { copyFile, mkdir as mkdir2, readFile as readFile3, rm, writeFile as writeFile2 } from "fs/promises";
29565
- import { createRequire } from "module";
29566
- import { join as join7, relative as relative2 } from "path";
29567
- import { checkbox, confirm, select } from "@inquirer/prompts";
29568
- var REGISTRY_PLUGINS = [scalePlugin, contrastPlugin, statePlugin, invertPlugin];
29569
- async function backupCss(cssPath) {
29570
- const backupPath = cssPath.replace(/\.css$/, ".backup.css");
29571
- await copyFile(cssPath, backupPath);
29572
- return backupPath;
29573
- }
29574
- function cleanSourceCssBlocks(content) {
29575
- function parseBlocks(src) {
29576
- const blocks = [];
29577
- let depth = 0;
29578
- const headerStack = [];
29579
- let segmentStart = 0;
29580
- for (let i = 0; i < src.length; i++) {
29581
- const ch = src.charAt(i);
29582
- if (ch === "{") {
29583
- headerStack.push(segmentStart);
29584
- depth++;
29585
- if (depth >= 1) {
29586
- blocks.push({
29587
- headerStart: segmentStart,
29588
- bodyStart: i + 1,
29589
- end: -1,
29590
- header: src.slice(segmentStart, i).trim()
29591
- });
29592
- }
29593
- segmentStart = i + 1;
29594
- } else if (ch === "}") {
29595
- depth--;
29596
- const open = blocks.slice().reverse().find((b2) => b2.end === -1);
29597
- if (open) open.end = i + 1;
29598
- headerStack.pop();
29599
- segmentStart = i + 1;
29600
- } else if (ch === ";") {
29601
- segmentStart = i + 1;
29602
- }
29603
- }
29604
- return blocks.filter((b2) => b2.end !== -1);
29605
- }
29606
- function isContentEmpty(body) {
29607
- return body.replace(/\/\*[\s\S]*?\*\//g, "").trim().length === 0;
29608
- }
29609
- let out = content;
29610
- for (let pass = 0; pass < 10; pass++) {
29611
- const blocks = parseBlocks(out);
29612
- let edited = false;
29613
- for (const block of blocks.sort((a2, b2) => b2.headerStart - a2.headerStart)) {
29614
- const header = block.header;
29615
- if (/^@theme\s+inline$/.test(header)) {
29616
- out = out.slice(0, block.headerStart) + out.slice(block.end);
29617
- edited = true;
29618
- break;
29619
- }
29620
- if (header === ":root" || /(^|,)\s*:root\s*$/.test(header)) {
29621
- const body = out.slice(block.bodyStart, block.end - 1);
29622
- const cleanedBody = body.replace(/--[\w-]+\s*:[^;{}]*;?/g, "");
29623
- if (isContentEmpty(cleanedBody)) {
29624
- out = out.slice(0, block.headerStart) + out.slice(block.end);
29625
- edited = true;
29626
- break;
29627
- }
29628
- if (cleanedBody !== body) {
29629
- out = out.slice(0, block.bodyStart) + cleanedBody + out.slice(block.end - 1);
29630
- edited = true;
29631
- break;
29632
- }
29633
- continue;
29634
- }
29635
- if (header.startsWith("@media") || header.startsWith("@supports")) {
29636
- const body = out.slice(block.bodyStart, block.end - 1);
29637
- if (isContentEmpty(body)) {
29638
- out = out.slice(0, block.headerStart) + out.slice(block.end);
29639
- edited = true;
29640
- break;
29641
- }
29642
- }
29643
- }
29644
- if (!edited) break;
29645
- }
29646
- return out.replace(/[ \t]+$/gm, "").replace(/\n{3,}/g, "\n\n");
29647
- }
29648
- async function stripImportedDeclarations(cwd, cssPath, importedNames) {
29649
- if (importedNames.length === 0) return;
29650
- const fullPath = join7(cwd, cssPath);
29651
- const content = await readFile3(fullPath, "utf-8");
29652
- const pattern = new RegExp(importedNames.map((n2) => `^\\s*--${n2}[^;]*;\\s*$`).join("|"), "gm");
29653
- const cleaned = cleanSourceCssBlocks(content.replace(pattern, ""));
29654
- const collapsed = cleaned.replace(/\n{3,}/g, "\n\n");
29655
- await writeFile2(fullPath, collapsed);
29656
- }
29559
+ // src/config/rafters-config.ts
29657
29560
  function migrateConfig(raw) {
29658
29561
  if ("shadcn" in raw && !("source" in raw)) {
29659
29562
  if (raw.shadcn === true) {
@@ -29663,2058 +29566,1247 @@ function migrateConfig(raw) {
29663
29566
  }
29664
29567
  return raw;
29665
29568
  }
29666
- async function updateMainCss(cwd, cssPath, themePath) {
29667
- const fullCssPath = join7(cwd, cssPath);
29668
- const cssContent = await readFile3(fullCssPath, "utf-8");
29669
- const cssDir = join7(cwd, cssPath, "..");
29670
- const themeFullPath = join7(cwd, themePath);
29671
- const relativeThemePath = relative2(cssDir, themeFullPath);
29672
- if (cssContent.includes(".rafters/output/rafters.css")) {
29673
- log({ event: "init:css_already_imported", cssPath });
29674
- return;
29675
- }
29676
- await backupCss(fullCssPath);
29677
- let newContent;
29678
- if (cssContent.includes('@import "tailwindcss"')) {
29679
- newContent = cssContent.replace('@import "tailwindcss";', `@import "${relativeThemePath}";`);
29680
- } else if (cssContent.includes("@import 'tailwindcss'")) {
29681
- newContent = cssContent.replace("@import 'tailwindcss';", `@import "${relativeThemePath}";`);
29682
- } else {
29683
- newContent = `@import "${relativeThemePath}";
29684
29569
 
29685
- ${cssContent}`;
29570
+ // src/commands/add.ts
29571
+ var REGISTRY_PLUGINS = [scalePlugin, contrastPlugin, statePlugin, invertPlugin];
29572
+ async function regenerateAfterInstall(cwd, config2) {
29573
+ const paths = getRaftersPaths(cwd);
29574
+ let registry2;
29575
+ try {
29576
+ registry2 = loadRegistryFromDir(paths.tokens, REGISTRY_PLUGINS);
29577
+ } catch {
29578
+ return;
29686
29579
  }
29687
- await writeFile2(fullCssPath, newContent);
29688
- log({
29689
- event: "init:css_updated",
29690
- cssPath,
29691
- themePath: relativeThemePath
29692
- });
29693
- }
29694
- function isInteractive() {
29695
- return Boolean(process.stdin.isTTY && process.stdout.isTTY);
29696
- }
29697
- function resolveSourceCssPath(cwd, framework, detectedFramework, projectCssPath, shadcnCssPath, emit) {
29698
- if (shadcnCssPath) {
29699
- if (existsSync3(join7(cwd, shadcnCssPath))) return shadcnCssPath;
29700
- const fallback = framework === detectedFramework ? projectCssPath : findCssPath(cwd, framework);
29701
- emit({
29702
- event: "init:shadcn_css_missing",
29703
- configuredPath: shadcnCssPath,
29704
- fallbackPath: fallback,
29705
- message: "components.json points at a CSS file that does not exist. Falling back to the framework canonical location."
29580
+ if (registry2.size() === 0) return;
29581
+ try {
29582
+ await regenerateOutputs(registry2, {
29583
+ outputDir: paths.output,
29584
+ exports: config2.exports,
29585
+ contentSources: resolveContentSources(cwd, config2),
29586
+ darkMode: config2.darkMode ?? "class",
29587
+ includeImport: config2.source !== "shadcn"
29588
+ });
29589
+ } catch (err) {
29590
+ log({
29591
+ event: "add:regen-failed",
29592
+ message: `Output regeneration failed: ${err instanceof Error ? err.message : String(err)}`
29706
29593
  });
29707
- return fallback;
29708
- }
29709
- return framework === detectedFramework ? projectCssPath : findCssPath(cwd, framework);
29710
- }
29711
- function slugifyFontName(name) {
29712
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
29713
- }
29714
- function isFontStackValue(value) {
29715
- if (typeof value !== "string") return false;
29716
- if (value.startsWith("var(")) return false;
29717
- if (/[,'"]/.test(value)) return true;
29718
- return /\b(sans-serif|serif|monospace|cursive|fantasy|system-ui|ui-(?:sans-serif|serif|monospace|rounded))\b/.test(
29719
- value
29720
- );
29721
- }
29722
- async function resolveFramework(detected, flag, agentMode) {
29723
- if (flag) {
29724
- if (!isSelectableFramework(flag)) {
29725
- throw new Error(
29726
- `Unknown --framework "${flag}". Valid values: ${SELECTABLE_FRAMEWORKS.join(", ")}.`
29727
- );
29728
- }
29729
- return flag;
29730
29594
  }
29731
- if (detected !== "unknown") return detected;
29732
- if (agentMode || !isInteractive()) return "unknown";
29733
- const picked = await select({
29734
- message: "Couldn't auto-detect your framework. Which one is this?",
29735
- choices: SELECTABLE_FRAMEWORKS.map((value) => ({
29736
- name: FRAMEWORK_SPECS[value].label ?? value,
29737
- value
29738
- }))
29739
- });
29740
- return picked;
29741
29595
  }
29742
- async function promptExportFormats(existingConfig) {
29743
- if (!isInteractive()) {
29744
- return existingConfig ?? DEFAULT_EXPORTS;
29596
+ async function isInitialized(cwd) {
29597
+ const paths = getRaftersPaths(cwd);
29598
+ try {
29599
+ await access(paths.root);
29600
+ return true;
29601
+ } catch {
29602
+ return false;
29745
29603
  }
29746
- const choices = EXPORT_CHOICES.map((choice) => ({
29747
- name: choice.name,
29748
- value: choice.value,
29749
- checked: existingConfig ? existingConfig[choice.value] : choice.checked
29750
- }));
29751
- const allChoices = [
29752
- ...choices,
29753
- ...FUTURE_EXPORTS.map((choice) => ({
29754
- name: `${choice.name} (${choice.disabled})`,
29755
- value: choice.value,
29756
- checked: false,
29757
- disabled: true
29758
- }))
29759
- ];
29760
- const selections = await checkbox({
29761
- message: "What would you like to export?",
29762
- choices: allChoices,
29763
- required: true
29764
- });
29765
- return selectionsToConfig(selections);
29766
29604
  }
29767
- function isTailwindCliInstalled() {
29768
- const require2 = createRequire(import.meta.url);
29605
+ async function loadConfig(cwd) {
29606
+ const paths = getRaftersPaths(cwd);
29769
29607
  try {
29770
- require2.resolve("@tailwindcss/cli/package.json");
29771
- return true;
29608
+ const content = await readFile3(paths.config, "utf-8");
29609
+ return migrateConfig(
29610
+ JSON.parse(content)
29611
+ );
29772
29612
  } catch (err) {
29773
- if (err instanceof Error && "code" in err && err.code === "MODULE_NOT_FOUND") {
29774
- return false;
29613
+ if (existsSync3(paths.config)) {
29614
+ const message = err instanceof Error ? err.message : String(err);
29615
+ log({
29616
+ event: "add:warning",
29617
+ message: `Failed to load config: ${message}`
29618
+ });
29775
29619
  }
29776
- throw err;
29620
+ return null;
29777
29621
  }
29778
29622
  }
29779
- async function ensureTailwindCli(cwd) {
29780
- if (!isInteractive() || isAgentMode()) {
29781
- throw new Error(
29782
- "Standalone CSS export requires @tailwindcss/cli. Install it as a dev dependency in your project."
29783
- );
29784
- }
29785
- const shouldInstall = await confirm({
29786
- message: "Standalone CSS requires @tailwindcss/cli. Install it now?",
29787
- default: true
29788
- });
29789
- if (!shouldInstall) {
29790
- throw new Error("Standalone CSS export requires @tailwindcss/cli.");
29791
- }
29792
- await updateDependencies([], ["@tailwindcss/cli"], { cwd });
29793
- if (!isTailwindCliInstalled()) {
29794
- throw new Error(
29795
- "@tailwindcss/cli was installed but cannot be resolved. Try installing at the workspace root."
29796
- );
29797
- }
29623
+ function resolveRegistryUrl(options, config2) {
29624
+ return options.registryUrl ?? config2?.registryUrl;
29798
29625
  }
29799
- async function generateOutputs(cwd, paths, registry2, exports, source, darkMode = "class", config2) {
29800
- const contentSources = config2 ? resolveContentSources(cwd, config2) : [];
29801
- if (exports.compiled && !isTailwindCliInstalled()) {
29802
- log({ event: "init:prompting_exports" });
29803
- await ensureTailwindCli(cwd);
29804
- }
29805
- if (exports.compiled) {
29806
- log({ event: "init:compiling_css" });
29807
- }
29808
- return regenerateOutputs(registry2, {
29809
- outputDir: paths.output,
29810
- exports,
29811
- contentSources,
29812
- darkMode,
29813
- includeImport: source !== "shadcn"
29814
- });
29626
+ async function saveConfig(cwd, config2) {
29627
+ const paths = getRaftersPaths(cwd);
29628
+ await writeFile2(paths.config, JSON.stringify(config2, null, 2));
29815
29629
  }
29816
- async function regenerateFromExisting(cwd, paths, source, isAgentMode2, framework) {
29817
- log({ event: "init:regenerate", cwd });
29818
- let existingConfig = null;
29630
+ function getInstalledNames(config2) {
29631
+ if (!config2?.installed) return [];
29632
+ const names = /* @__PURE__ */ new Set([
29633
+ ...config2.installed.components,
29634
+ ...config2.installed.primitives,
29635
+ ...config2.installed.composites ?? [],
29636
+ ...config2.installed.rules ?? [],
29637
+ ...config2.installed.substrate ?? []
29638
+ ]);
29639
+ return [...names].sort();
29640
+ }
29641
+ async function discoverUntrackedNames(cwd, config2, client, tracked) {
29642
+ let index = null;
29819
29643
  try {
29820
- const configContent = await readFile3(paths.config, "utf-8");
29821
- existingConfig = migrateConfig(
29822
- JSON.parse(configContent)
29823
- );
29824
- } catch {
29644
+ index = await client.fetchIndex();
29645
+ } catch (err) {
29646
+ const message = err instanceof Error ? err.message : String(err);
29647
+ log({
29648
+ event: "add:warning",
29649
+ message: `Could not read the registry index to reconcile on-disk components (${message}). Updating tracked components only.`
29650
+ });
29825
29651
  }
29826
- if (framework !== "unknown" && existingConfig) {
29827
- const frameworkPaths = FRAMEWORK_SPECS[framework].components;
29828
- existingConfig.framework = framework;
29829
- existingConfig.componentsPath = frameworkPaths.components;
29830
- existingConfig.primitivesPath = frameworkPaths.primitives;
29831
- existingConfig.compositesPath = frameworkPaths.composites;
29832
- existingConfig.rulesPath = frameworkPaths.rules;
29652
+ const { untracked } = buildUpdateCandidates(tracked, index, readInstallRoots(cwd, config2));
29653
+ return untracked;
29654
+ }
29655
+ function getComponentTarget(config2) {
29656
+ return resolveComponentTarget(config2);
29657
+ }
29658
+ var SHARED_EXTENSIONS = /* @__PURE__ */ new Set([".behavior.ts", ".classes.ts"]);
29659
+ function isSharedFile(path) {
29660
+ for (const ext of SHARED_EXTENSIONS) {
29661
+ if (path.endsWith(ext)) return true;
29833
29662
  }
29834
- await rm(join7(paths.tokens, "elevation.rafters.json"), { force: true });
29835
- await rm(join7(paths.tokens, "fill.rafters.json"), { force: true });
29836
- const registry2 = loadRegistryFromDir(paths.tokens, REGISTRY_PLUGINS);
29837
- if (registry2.size() === 0) {
29838
- throw new Error("No tokens found. Cannot regenerate without existing tokens.");
29663
+ return false;
29664
+ }
29665
+ var REACT_FALLBACK_TARGETS = /* @__PURE__ */ new Set(["astro", "vue", "svelte"]);
29666
+ function selectFilesForFramework(files, target) {
29667
+ const preferredExt = targetToExtension(target);
29668
+ const shared = files.filter((f) => isSharedFile(f.path));
29669
+ const matched = files.filter((f) => f.path.endsWith(preferredExt));
29670
+ if (matched.length > 0) {
29671
+ return { files: [...matched, ...shared], fallback: false };
29839
29672
  }
29840
- const namespaces = [...new Set(registry2.list().map((t) => t.namespace))];
29841
- log({
29842
- event: "init:loaded",
29843
- tokenCount: registry2.size(),
29844
- namespaces
29845
- });
29846
- let exports;
29847
- if (isAgentMode2) {
29848
- exports = existingConfig?.exports ?? DEFAULT_EXPORTS;
29849
- log({ event: "init:exports_default", exports });
29850
- } else {
29851
- if (isInteractive()) {
29852
- log({ event: "init:prompting_exports" });
29673
+ if (REACT_FALLBACK_TARGETS.has(target)) {
29674
+ const fallbackFiles = files.filter((f) => f.path.endsWith(".tsx"));
29675
+ if (fallbackFiles.length > 0) {
29676
+ return { files: [...fallbackFiles, ...shared], fallback: true };
29853
29677
  }
29854
- exports = await promptExportFormats(existingConfig?.exports);
29855
- log({ event: "init:exports_selected", exports });
29856
29678
  }
29857
- await mkdir2(paths.output, { recursive: true });
29858
- const outputs = await generateOutputs(
29859
- cwd,
29860
- paths,
29861
- registry2,
29862
- exports,
29863
- source,
29864
- "class",
29865
- existingConfig
29866
- );
29867
- if (existingConfig) {
29868
- existingConfig.exports = exports;
29869
- await writeFile2(paths.config, JSON.stringify(existingConfig, null, 2));
29870
- } else {
29871
- const frameworkPaths = FRAMEWORK_SPECS[framework].components;
29872
- const newConfig = {
29873
- framework,
29874
- componentsPath: frameworkPaths.components,
29875
- primitivesPath: frameworkPaths.primitives,
29876
- compositesPath: frameworkPaths.composites,
29877
- rulesPath: frameworkPaths.rules,
29878
- cssPath: null,
29879
- ...source ? { source } : {},
29880
- intent: "efficient",
29881
- fonts: { path: null, imports: [] },
29882
- exports,
29883
- installed: { components: [], primitives: [], composites: [], rules: [] }
29884
- };
29885
- await writeFile2(paths.config, JSON.stringify(newConfig, null, 2));
29679
+ if (target !== "react" && !REACT_FALLBACK_TARGETS.has(target)) {
29680
+ return { files: shared, fallback: false };
29886
29681
  }
29887
- log({
29888
- event: "init:complete",
29889
- outputs,
29890
- path: paths.output
29682
+ return { files, fallback: false };
29683
+ }
29684
+ var TARGET_GATED_COMPOSITE_FILES = [{ suffix: "Composite.astro", target: "astro" }];
29685
+ function selectCompositeFiles(files, target) {
29686
+ return files.filter((file2) => {
29687
+ const gate = TARGET_GATED_COMPOSITE_FILES.find((g2) => file2.path.endsWith(g2.suffix));
29688
+ return gate ? gate.target === target : true;
29891
29689
  });
29892
29690
  }
29893
- async function resetToDefaults(cwd, paths, source, isAgentMode2, framework) {
29894
- log({ event: "init:reset", cwd });
29895
- let existingConfig = null;
29896
- try {
29897
- const configContent = await readFile3(paths.config, "utf-8");
29898
- existingConfig = migrateConfig(
29899
- JSON.parse(configContent)
29900
- );
29901
- } catch {
29902
- }
29903
- if (framework !== "unknown" && existingConfig) {
29904
- const frameworkPaths = FRAMEWORK_SPECS[framework].components;
29905
- existingConfig.framework = framework;
29906
- existingConfig.componentsPath = frameworkPaths.components;
29907
- existingConfig.primitivesPath = frameworkPaths.primitives;
29908
- existingConfig.compositesPath = frameworkPaths.composites;
29909
- existingConfig.rulesPath = frameworkPaths.rules;
29910
- }
29911
- let existingTokens = [];
29912
- try {
29913
- existingTokens = loadRegistryFromDir(paths.tokens, REGISTRY_PLUGINS).list();
29914
- } catch {
29691
+ var FOLDER_NAMES = /* @__PURE__ */ new Set(["composites"]);
29692
+ function isAlreadyInstalled(config2, item) {
29693
+ if (!config2?.installed) return false;
29694
+ const { components, primitives, composites: composites2, rules, substrate } = config2.installed;
29695
+ const bucketByType = {
29696
+ ui: components,
29697
+ primitive: primitives,
29698
+ composite: composites2 ?? [],
29699
+ rule: rules ?? [],
29700
+ substrate: substrate ?? []
29701
+ };
29702
+ return bucketByType[item.type].includes(item.name);
29703
+ }
29704
+ function isInstalledOnDisk(config2, item, cwd) {
29705
+ if (!isAlreadyInstalled(config2, item)) return false;
29706
+ const filesToCheck = item.type === "ui" ? selectFilesForFramework(item.files, getComponentTarget(config2)).files : item.files;
29707
+ for (const file2 of filesToCheck) {
29708
+ if (fileExists(cwd, transformPath(file2.path, config2, cwd))) return true;
29915
29709
  }
29916
- const overriddenTokens = existingTokens.filter((t) => t.userOverride);
29917
- if (overriddenTokens.length > 0) {
29918
- const backup = {
29919
- resetAt: (/* @__PURE__ */ new Date()).toISOString(),
29920
- reason: "rafters init --reset",
29921
- overrides: overriddenTokens.map((t) => ({
29922
- name: t.name,
29923
- value: t.value,
29924
- userOverride: t.userOverride,
29925
- namespace: t.namespace
29926
- }))
29710
+ return false;
29711
+ }
29712
+ function trackInstalled(config2, items) {
29713
+ if (!config2.installed) {
29714
+ config2.installed = {
29715
+ components: [],
29716
+ primitives: [],
29717
+ composites: [],
29718
+ rules: []
29927
29719
  };
29928
- await mkdir2(paths.output, { recursive: true });
29929
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
29930
- const backupPath = join7(paths.output, `reset-${timestamp}.json`);
29931
- await writeFile2(backupPath, JSON.stringify(backup, null, 2));
29932
- log({
29933
- event: "init:reset_backup",
29934
- path: backupPath,
29935
- overrideCount: overriddenTokens.length
29936
- });
29937
29720
  }
29938
- let exports;
29939
- if (isAgentMode2) {
29940
- exports = existingConfig?.exports ?? DEFAULT_EXPORTS;
29941
- log({ event: "init:exports_default", exports });
29942
- } else {
29943
- if (isInteractive()) {
29944
- log({ event: "init:prompting_exports" });
29721
+ const installed = config2.installed;
29722
+ if (!installed.composites) installed.composites = [];
29723
+ if (!installed.rules) installed.rules = [];
29724
+ if (!installed.substrate) installed.substrate = [];
29725
+ const bucketByType = {
29726
+ ui: installed.components,
29727
+ primitive: installed.primitives,
29728
+ composite: installed.composites,
29729
+ rule: installed.rules,
29730
+ substrate: installed.substrate
29731
+ };
29732
+ for (const item of items) {
29733
+ const bucket = bucketByType[item.type];
29734
+ if (!bucket.includes(item.name)) bucket.push(item.name);
29735
+ }
29736
+ installed.components.sort();
29737
+ installed.primitives.sort();
29738
+ installed.composites.sort();
29739
+ installed.rules.sort();
29740
+ installed.substrate.sort();
29741
+ }
29742
+ function rootFor(field, cwd, fallback) {
29743
+ return field === void 0 ? fallback : resolveRoot(field, cwd, fallback);
29744
+ }
29745
+ function transformPath(registryPath, config2, cwd = process.cwd()) {
29746
+ if (!config2) return registryPath;
29747
+ const replacements = [
29748
+ ["components/ui/", config2.componentsPath, "components/ui"],
29749
+ ["lib/primitives/", config2.primitivesPath, "lib/primitives"],
29750
+ ["composites/", config2.compositesPath, "composites"],
29751
+ ["rules/", config2.rulesPath, "lib/rules"]
29752
+ ];
29753
+ for (const [prefix, field, fallback] of replacements) {
29754
+ if (registryPath.startsWith(prefix)) {
29755
+ return registryPath.replace(prefix, `${rootFor(field, cwd, fallback)}/`);
29945
29756
  }
29946
- exports = await promptExportFormats(existingConfig?.exports);
29947
- log({ event: "init:exports_selected", exports });
29948
29757
  }
29949
- const system = generateBaseSystem({});
29950
- const registry2 = new TokenRegistry(system.allTokens, REGISTRY_PLUGINS);
29951
- log({
29952
- event: "init:reset_generated",
29953
- tokenCount: registry2.size()
29954
- });
29955
- await rm(paths.tokens, { recursive: true, force: true });
29956
- await mkdir2(paths.tokens, { recursive: true });
29957
- saveRegistryToDir(paths.tokens, registry2);
29958
- const allTokensToSave = registry2.list();
29959
- const namespaceCount = new Set(allTokensToSave.map((t) => t.namespace)).size;
29960
- log({
29961
- event: "init:registry_saved",
29962
- path: paths.tokens,
29963
- namespaceCount
29964
- });
29965
- await mkdir2(paths.output, { recursive: true });
29966
- const outputs = await generateOutputs(
29967
- cwd,
29968
- paths,
29969
- registry2,
29970
- exports,
29971
- source,
29972
- "class",
29973
- existingConfig
29974
- );
29975
- if (existingConfig) {
29976
- existingConfig.exports = exports;
29977
- await writeFile2(paths.config, JSON.stringify(existingConfig, null, 2));
29978
- } else {
29979
- const frameworkPaths = FRAMEWORK_SPECS[framework].components;
29980
- const newConfig = {
29981
- framework,
29982
- componentsPath: frameworkPaths.components,
29983
- primitivesPath: frameworkPaths.primitives,
29984
- compositesPath: frameworkPaths.composites,
29985
- rulesPath: frameworkPaths.rules,
29986
- cssPath: null,
29987
- ...source ? { source } : {},
29988
- intent: "efficient",
29989
- fonts: { path: null, imports: [] },
29990
- exports,
29991
- installed: { components: [], primitives: [], composites: [], rules: [] }
29992
- };
29993
- await writeFile2(paths.config, JSON.stringify(newConfig, null, 2));
29758
+ return registryPath;
29759
+ }
29760
+ function substrateProjectPath(registryPath, config2, cwd = process.cwd()) {
29761
+ const componentsResolved = rootFor(config2?.componentsPath, cwd, "components/ui");
29762
+ const sourceRoot = componentsResolved.replace(/\/?components\/ui$/, "");
29763
+ return sourceRoot ? join7(sourceRoot, registryPath) : registryPath;
29764
+ }
29765
+ function fileExists(cwd, relativePath) {
29766
+ return existsSync3(join7(cwd, relativePath));
29767
+ }
29768
+ function transformFileContent(content, config2, fileType = "component", cwd = process.cwd(), opts = {}) {
29769
+ const { substrateKinds = [], installPath } = opts;
29770
+ let transformed = content;
29771
+ const componentsPath = rootFor(config2?.componentsPath, cwd, "components/ui");
29772
+ const primitivesPath = rootFor(config2?.primitivesPath, cwd, "lib/primitives");
29773
+ const stripSourceRoot = (p2) => p2.replace(/^(src|app)\//, "");
29774
+ const aliasComponents = stripSourceRoot(componentsPath);
29775
+ const aliasPrimitives = stripSourceRoot(primitivesPath);
29776
+ transformed = transformed.replace(
29777
+ /from\s+['"]\.\.\/\.\.\/primitives\/([^'"]+)['"]/g,
29778
+ `from '@/${aliasPrimitives}/$1'`
29779
+ );
29780
+ transformed = transformed.replace(
29781
+ /from\s+['"]\.\.\/primitives\/([^'"]+)['"]/g,
29782
+ `from '@/${aliasPrimitives}/$1'`
29783
+ );
29784
+ const substrateOwnDir = fileType === "substrate" && installPath ? stripSourceRoot(dirname(installPath)) : null;
29785
+ const aliasSibling = fileType === "primitive" ? aliasPrimitives : substrateOwnDir ?? aliasComponents;
29786
+ transformed = transformed.replace(/from\s+['"]\.\/([^'"]+)['"]/g, `from '@/${aliasSibling}/$1'`);
29787
+ if (substrateKinds.length > 0) {
29788
+ const kindAlternation = substrateKinds.join("|");
29789
+ transformed = transformed.replace(
29790
+ new RegExp(`from\\s+['"](?:\\.\\./){1,2}(${kindAlternation})/([^'"]+)['"]`, "g"),
29791
+ "from '@/$1/$2'"
29792
+ );
29994
29793
  }
29995
- log({
29996
- event: "init:complete",
29997
- outputs,
29998
- path: paths.output
29999
- });
29794
+ transformed = transformed.replace(
29795
+ /from\s+['"]\.\.\/([^/'"]+)\/\1([^'"]*)['"]/g,
29796
+ `from '@/${aliasComponents}/$1$2'`
29797
+ );
29798
+ transformed = transformed.replace(
29799
+ /from\s+['"]\.\.\/([^'"]+)['"]/g,
29800
+ `from '@/${aliasComponents}/$1'`
29801
+ );
29802
+ return transformed;
30000
29803
  }
30001
- async function init(options) {
30002
- setAgentMode(options.agent ?? false);
30003
- const isAgentMode2 = options.agent ?? false;
30004
- const cwd = process.cwd();
30005
- const paths = getRaftersPaths(cwd);
30006
- log({ event: "init:start", cwd });
30007
- const project = await detectProject(cwd);
30008
- const { framework: detectedFramework, shadcn, tailwindVersion, astroHasReact } = project;
30009
- const source = shadcn ? "shadcn" : void 0;
30010
- log({
30011
- event: "init:detected",
30012
- framework: detectedFramework,
30013
- tailwindVersion,
30014
- hasShadcn: !!shadcn
30015
- });
30016
- const framework = await resolveFramework(detectedFramework, options.framework, isAgentMode2);
30017
- if (framework !== detectedFramework) {
30018
- log({
30019
- event: "init:framework_resolved",
30020
- detected: detectedFramework,
30021
- resolved: framework,
30022
- source: options.framework ? "flag" : "prompt"
30023
- });
29804
+ async function installItem(cwd, item, options, config2, substrateKinds = []) {
29805
+ const installedFiles = [];
29806
+ let skipped = false;
29807
+ let filesToInstall = item.files;
29808
+ if (item.type === "ui") {
29809
+ const target = getComponentTarget(config2);
29810
+ const selection = selectFilesForFramework(item.files, target);
29811
+ filesToInstall = selection.files;
29812
+ if (selection.fallback) {
29813
+ log({
29814
+ event: "add:fallback",
29815
+ component: item.name,
29816
+ target,
29817
+ message: `No ${targetToExtension(target)} version available for ${item.name}. Installing React version.`
29818
+ });
29819
+ }
29820
+ const hasComponentFile = selection.files.some((f) => !isSharedFile(f.path));
29821
+ if (!hasComponentFile) {
29822
+ throw new Error(
29823
+ `No ${targetToExtension(target)} version available for "${item.name}" and no compatible fallback exists.`
29824
+ );
29825
+ }
29826
+ } else if (item.type === "composite") {
29827
+ filesToInstall = selectCompositeFiles(item.files, getComponentTarget(config2));
30024
29828
  }
30025
- if (isTailwindV3(tailwindVersion)) {
30026
- throw new Error("Tailwind v3 detected. Rafters requires Tailwind v4.");
29829
+ for (const file2 of filesToInstall) {
29830
+ const projectPath2 = item.type === "substrate" ? substrateProjectPath(file2.path, config2, cwd) : transformPath(file2.path, config2, cwd);
29831
+ const targetPath = join7(cwd, projectPath2);
29832
+ if (fileExists(cwd, projectPath2)) {
29833
+ if (!options.overwrite) {
29834
+ log({
29835
+ event: "add:skip",
29836
+ component: item.name,
29837
+ file: projectPath2,
29838
+ reason: "exists"
29839
+ });
29840
+ skipped = true;
29841
+ continue;
29842
+ }
29843
+ }
29844
+ await mkdir2(dirname(targetPath), { recursive: true });
29845
+ const fileType = item.type === "primitive" ? "primitive" : item.type === "substrate" ? "substrate" : "component";
29846
+ const transformedContent = transformFileContent(file2.content, config2, fileType, cwd, {
29847
+ substrateKinds,
29848
+ installPath: file2.path
29849
+ });
29850
+ await writeFile2(targetPath, transformedContent, "utf-8");
29851
+ installedFiles.push(projectPath2);
30027
29852
  }
30028
- const raftersExists = existsSync3(paths.root);
30029
- if (options.reset && !raftersExists) {
30030
- throw new Error("Nothing to reset. No .rafters/ directory found.");
29853
+ return {
29854
+ installed: installedFiles.length > 0,
29855
+ skipped,
29856
+ files: installedFiles
29857
+ };
29858
+ }
29859
+ async function add(componentArgs, options) {
29860
+ setAgentMode(options.agent ?? false);
29861
+ let components = componentArgs;
29862
+ const cwd = process.cwd();
29863
+ const config2 = await loadConfig(cwd);
29864
+ const client = new RegistryClient(resolveRegistryUrl(options, config2));
29865
+ let folder;
29866
+ const firstArg = components[0];
29867
+ if (firstArg && FOLDER_NAMES.has(firstArg)) {
29868
+ folder = firstArg;
29869
+ components = components.slice(1);
30031
29870
  }
30032
- if (raftersExists && options.reset) {
30033
- await resetToDefaults(cwd, paths, source, isAgentMode2, framework);
29871
+ if (options.list) {
29872
+ const availableComponents = await client.listComponents();
29873
+ const availableComposites = await client.listComposites();
29874
+ if (options.agent) {
29875
+ log({
29876
+ event: "add:list",
29877
+ components: availableComponents,
29878
+ composites: availableComposites
29879
+ });
29880
+ } else {
29881
+ console.log("Available components:\n");
29882
+ for (const comp of availableComponents) {
29883
+ console.log(` ${comp.name} ${comp.description ?? ""}`);
29884
+ }
29885
+ if (availableComposites.length > 0) {
29886
+ console.log("\nAvailable composites:\n");
29887
+ for (const comp of availableComposites) {
29888
+ console.log(` ${comp.name} ${comp.description ?? ""}`);
29889
+ }
29890
+ }
29891
+ }
30034
29892
  return;
30035
29893
  }
30036
- if (raftersExists && !options.rebuild) {
30037
- throw new Error(
30038
- ".rafters/ directory already exists. Use --rebuild to regenerate output files, or --reset to start from defaults."
30039
- );
30040
- }
30041
- if (raftersExists && options.rebuild) {
30042
- await regenerateFromExisting(cwd, paths, source, isAgentMode2, framework);
29894
+ const initialized = await isInitialized(cwd);
29895
+ if (!initialized) {
29896
+ error46("Project not initialized. Run `rafters init` first.");
29897
+ process.exitCode = 1;
30043
29898
  return;
30044
29899
  }
30045
- let exports;
30046
- if (isAgentMode2) {
30047
- exports = DEFAULT_EXPORTS;
30048
- log({ event: "init:exports_default", exports });
30049
- } else {
30050
- if (isInteractive()) {
30051
- log({ event: "init:prompting_exports" });
29900
+ if (options.update) {
29901
+ options.overwrite = true;
29902
+ }
29903
+ let untrackedNames = [];
29904
+ if (options.updateAll) {
29905
+ options.overwrite = true;
29906
+ if (!config2) {
29907
+ error46("No rafters config found. Run 'rafters init' first.");
29908
+ process.exitCode = 1;
29909
+ return;
30052
29910
  }
30053
- exports = await promptExportFormats();
30054
- log({ event: "init:exports_selected", exports });
29911
+ const trackedNames = getInstalledNames(config2);
29912
+ untrackedNames = await discoverUntrackedNames(cwd, config2, client, trackedNames);
29913
+ const candidates = [.../* @__PURE__ */ new Set([...trackedNames, ...untrackedNames])].sort();
29914
+ if (candidates.length === 0) {
29915
+ error46("No installed components found. Use 'rafters add <component>' to install first.");
29916
+ process.exitCode = 1;
29917
+ return;
29918
+ }
29919
+ if (untrackedNames.length > 0) {
29920
+ log({
29921
+ event: "add:untracked",
29922
+ components: untrackedNames,
29923
+ message: `Found ${untrackedNames.length} component(s) on disk the config never tracked: ${untrackedNames.join(", ")}. Refreshing and tracking them.`
29924
+ });
29925
+ }
29926
+ components = candidates;
30055
29927
  }
30056
- const adapter = getAdapter(source ?? "tailwind");
30057
- const baseConfig = {};
30058
- const sourceCssPathForBase = resolveSourceCssPath(
29928
+ if (folder === "composites" && components.length === 0) {
29929
+ components = ["composites"];
29930
+ }
29931
+ if (components.length === 0) {
29932
+ error46("No components specified. Usage: rafters add <component...>");
29933
+ process.exitCode = 1;
29934
+ return;
29935
+ }
29936
+ log({
29937
+ event: "add:start",
30059
29938
  cwd,
30060
- framework,
30061
- detectedFramework,
30062
- project.cssPath,
30063
- shadcn?.tailwind?.css,
30064
- log
30065
- );
30066
- if (sourceCssPathForBase !== null) {
29939
+ components,
29940
+ overwrite: options.overwrite ?? false
29941
+ });
29942
+ const allItems = [];
29943
+ const seen = /* @__PURE__ */ new Set();
29944
+ for (const itemName of components) {
30067
29945
  try {
30068
- const cssForBase = await readFile3(join7(cwd, sourceCssPathForBase), "utf-8");
30069
- const baseSlots = [
30070
- {
30071
- detect: () => adapter.detectSpacing(cssForBase).base,
30072
- field: "baseSpacingUnit",
30073
- event: "init:import_spacing_applied",
30074
- eventKey: "baseSpacingUnit"
30075
- },
30076
- {
30077
- detect: () => adapter.detectRadius(cssForBase).base,
30078
- field: "baseRadiusOverride",
30079
- event: "init:import_radius_applied",
30080
- eventKey: "baseRadius"
30081
- },
30082
- {
30083
- detect: () => adapter.detectFontSize(cssForBase).base,
30084
- field: "baseFontSizeOverride",
30085
- event: "init:import_font_size_applied",
30086
- eventKey: "baseFontSize"
30087
- },
30088
- {
30089
- detect: () => adapter.detectFocusRing(cssForBase).width,
30090
- field: "focusRingWidthOverride",
30091
- event: "init:import_focus_ring_applied",
30092
- eventKey: "focusRingWidth"
29946
+ if (folder === "composites") {
29947
+ if (!seen.has(itemName)) {
29948
+ const composite = await client.fetchComposite(itemName);
29949
+ seen.add(itemName);
29950
+ allItems.push(composite);
29951
+ for (const dep of composite.primitives) {
29952
+ if (!seen.has(dep)) {
29953
+ const depItems = await client.resolveDependencies(dep, seen);
29954
+ allItems.push(...depItems);
29955
+ }
29956
+ }
30093
29957
  }
30094
- // Motion duration is intentionally not imported -- rafters' motion
30095
- // system is research-backed and most projects' `--duration-base`
30096
- // values are unresearched defaults. See importers/bases.ts for the
30097
- // policy note; designers can `rafters set motion-duration-base ...`
30098
- // after init to override explicitly.
30099
- ];
30100
- for (const slot of baseSlots) {
30101
- const value = slot.detect();
30102
- if (value === void 0) continue;
30103
- baseConfig[slot.field] = value;
30104
- log({
30105
- event: slot.event,
30106
- cssPath: sourceCssPathForBase,
30107
- [slot.eventKey]: value
30108
- });
29958
+ } else {
29959
+ const items = await client.resolveDependencies(itemName, seen);
29960
+ allItems.push(...items);
30109
29961
  }
30110
29962
  } catch (err) {
30111
- if (!(err instanceof Error && "code" in err && err.code === "ENOENT")) throw err;
29963
+ if (err instanceof Error) {
29964
+ error46(err.message);
29965
+ } else {
29966
+ error46(`Failed to fetch "${itemName}"`);
29967
+ }
29968
+ process.exitCode = 1;
29969
+ return;
30112
29970
  }
30113
29971
  }
30114
- const system = generateBaseSystem(baseConfig);
30115
- const registry2 = new TokenRegistry(system.allTokens, REGISTRY_PLUGINS);
30116
- log({
30117
- event: "init:generated",
30118
- tokenCount: registry2.size()
30119
- });
30120
- await mkdir2(paths.tokens, { recursive: true });
30121
- await mkdir2(paths.output, { recursive: true });
30122
- saveRegistryToDir(paths.tokens, registry2);
30123
- const allTokensToSave = registry2.list();
30124
- const namespaceCount = new Set(allTokensToSave.map((t) => t.namespace)).size;
30125
- log({
30126
- event: "init:registry_saved",
30127
- path: paths.tokens,
30128
- namespaceCount
30129
- });
30130
- const outputs = await generateOutputs(cwd, paths, registry2, exports, source);
30131
- let detectedCssPath = null;
30132
- detectedCssPath = resolveSourceCssPath(
30133
- cwd,
30134
- framework,
30135
- detectedFramework,
30136
- project.cssPath,
30137
- shadcn?.tailwind?.css,
30138
- log
30139
- );
30140
- if (source !== "shadcn" && exports.tailwind) {
30141
- if (detectedCssPath) {
30142
- await updateMainCss(cwd, detectedCssPath, ".rafters/output/rafters.css");
30143
- } else {
29972
+ const substrateKinds = [
29973
+ ...new Set(
29974
+ allItems.filter((item) => item.type === "substrate").map((item) => item.files[0]?.path.split("/")[0]).filter((segment) => Boolean(segment))
29975
+ )
29976
+ ];
29977
+ const written = [];
29978
+ const skipped = [];
29979
+ const failed = [];
29980
+ const installedItems = [];
29981
+ const filteredItems = [];
29982
+ const target = getComponentTarget(config2);
29983
+ for (const item of allItems) {
29984
+ if (!options.overwrite && isAlreadyInstalled(config2, item)) {
29985
+ if (isInstalledOnDisk(config2, item, cwd)) {
29986
+ log({
29987
+ event: "add:skip",
29988
+ component: item.name,
29989
+ reason: "already installed"
29990
+ });
29991
+ skipped.push(item.name);
29992
+ continue;
29993
+ }
30144
29994
  log({
30145
- event: "init:css_not_found",
30146
- message: 'No main CSS file found. Add @import ".rafters/output/rafters.css" manually.',
30147
- searchedLocations: FRAMEWORK_SPECS[framework].cssLocations
29995
+ event: "add:warning",
29996
+ component: item.name,
29997
+ message: "Config tracks this as installed but no files found on disk -- reinstalling."
30148
29998
  });
30149
29999
  }
30150
- }
30151
- let componentTarget = frameworkToTarget(framework);
30152
- if (framework === "astro" && astroHasReact && isInteractive() && !isAgentMode2) {
30153
- componentTarget = await select({
30154
- message: "This Astro project has React integration. Install components as:",
30155
- choices: [
30156
- {
30157
- name: "Astro components (zero client JS, server-rendered)",
30158
- value: "astro"
30159
- },
30160
- {
30161
- name: "React components (client islands with client:load)",
30162
- value: "react"
30163
- }
30164
- ]
30165
- });
30166
- }
30167
- const frameworkPaths = FRAMEWORK_SPECS[framework].components;
30168
- const config2 = {
30169
- framework,
30170
- ...source ? { source } : {},
30171
- componentTarget,
30172
- componentsPath: frameworkPaths.components,
30173
- primitivesPath: frameworkPaths.primitives,
30174
- compositesPath: frameworkPaths.composites,
30175
- rulesPath: frameworkPaths.rules,
30176
- cssPath: detectedCssPath,
30177
- exports,
30178
- intent: "efficient",
30179
- fonts: { path: null, imports: [] },
30180
- installed: {
30181
- components: [],
30182
- primitives: [],
30183
- composites: [],
30184
- rules: []
30185
- }
30186
- };
30187
- await writeFile2(paths.config, JSON.stringify(config2, null, 2));
30188
- if (detectedCssPath) {
30189
- let sourceCss = null;
30190
30000
  try {
30191
- sourceCss = await readFile3(join7(cwd, detectedCssPath), "utf-8");
30001
+ const result = await installItem(cwd, item, options, config2, substrateKinds);
30002
+ if (result.installed) {
30003
+ written.push(item.name);
30004
+ installedItems.push(item);
30005
+ if (item.type === "ui") {
30006
+ const selection = selectFilesForFramework(item.files, target);
30007
+ filteredItems.push({ ...item, files: selection.files });
30008
+ } else {
30009
+ filteredItems.push(item);
30010
+ }
30011
+ log({
30012
+ event: "add:installed",
30013
+ component: item.name,
30014
+ type: item.type,
30015
+ files: result.files
30016
+ });
30017
+ }
30018
+ if (result.skipped && !result.installed) {
30019
+ skipped.push(item.name);
30020
+ installedItems.push(item);
30021
+ }
30192
30022
  } catch (err) {
30193
- if (!(err instanceof Error && "code" in err && err.code === "ENOENT")) throw err;
30194
- }
30195
- if (sourceCss !== null) {
30196
- const summary = senseShadcnCss(sourceCss);
30197
- if (summary.totalDeclarations > 0) {
30023
+ failed.push(item.name);
30024
+ if (err instanceof Error) {
30198
30025
  log({
30199
- event: "init:import_sensed",
30200
- cssPath: detectedCssPath,
30201
- ...summary
30026
+ event: "add:warning",
30027
+ component: item.name,
30028
+ message: err.message
30202
30029
  });
30203
- const classification = classifyDeclarations(extractShadcnRoot(sourceCss));
30204
- const colorDecls = adapter.detectColors(sourceCss);
30205
- const nonColorDecls = [
30206
- ...classification.byNamespace.typography,
30207
- ...classification.byNamespace.spacing,
30208
- ...classification.byNamespace.radius,
30209
- ...classification.byNamespace.shadow
30210
- ];
30211
- const toImportColors = [];
30212
- for (const color of colorDecls) {
30213
- const accept = isAgentMode2 ? true : await confirm({
30214
- message: `Import --${color.name}: ${color.value}?`,
30215
- default: true
30216
- });
30217
- if (accept) toImportColors.push(color);
30218
- }
30219
- const toImportNonColors = [];
30220
- for (const decl of nonColorDecls) {
30221
- const accept = isAgentMode2 ? true : await confirm({
30222
- message: `Import --${decl.name}: ${decl.value}?`,
30223
- default: true
30224
- });
30225
- if (accept) toImportNonColors.push(decl);
30226
- }
30227
- if (toImportColors.length + toImportNonColors.length > 0) {
30228
- for (const color of toImportColors) {
30229
- const familyName = `imported-${color.name}`;
30230
- const reason = `imported from --${color.name} in ${detectedCssPath}`;
30231
- for (const t of importColorFamily(familyName, "500", color.oklch)) {
30232
- registry2.define(t);
30233
- }
30234
- if (color.namespace === "semantic") {
30235
- registry2.set(color.name, { family: familyName, position: "500" }, { reason });
30236
- }
30237
- }
30238
- const skipped = [];
30239
- for (const decl of toImportNonColors) {
30240
- const reason = `imported from --${decl.name} in ${detectedCssPath}`;
30241
- if (registry2.has(decl.name)) {
30242
- registry2.set(decl.name, decl.value, { reason });
30243
- } else {
30244
- skipped.push(decl.name);
30245
- }
30246
- }
30247
- saveRegistryToDir(paths.tokens, registry2);
30248
- await generateOutputs(cwd, paths, registry2, exports, source);
30249
- const importedNames = [
30250
- ...toImportColors.map((c4) => c4.name),
30251
- ...toImportNonColors.map((d2) => d2.name)
30252
- ];
30253
- await stripImportedDeclarations(cwd, detectedCssPath, importedNames);
30254
- log({
30255
- event: "init:import_applied",
30256
- count: toImportColors.length + toImportNonColors.length - skipped.length,
30257
- cssPath: detectedCssPath,
30258
- ...skipped.length > 0 ? { skipped } : {}
30259
- });
30260
- }
30261
- }
30262
- {
30263
- const themeDecls = extractThemeBlocks2(sourceCss);
30264
- if (themeDecls.length > 0) {
30265
- const POSITION_SUFFIX = new RegExp(`^(.+)-(${SCALE_POSITIONS.join("|")})$`);
30266
- const familySeeds = /* @__PURE__ */ new Map();
30267
- for (const decl of themeDecls) {
30268
- if (!decl.name.startsWith("color-")) continue;
30269
- const oklch2 = tryParseColor(decl.value);
30270
- if (oklch2 === null) continue;
30271
- const m3 = decl.name.match(POSITION_SUFFIX);
30272
- const baseName = ((m3 ? m3[1] : decl.name) ?? decl.name).replace(/^color-/, "");
30273
- if (!baseName) continue;
30274
- const seedPosition = m3?.[2] ?? "500";
30275
- const existing = familySeeds.get(baseName);
30276
- if (!existing || seedPosition === "500") {
30277
- familySeeds.set(baseName, { seed: oklch2, seedPosition });
30278
- }
30279
- }
30280
- const families = Array.from(familySeeds, ([name, info]) => ({
30281
- name,
30282
- ...info
30283
- }));
30284
- if (families.length > 0) {
30285
- for (const family of families) {
30286
- for (const t of importColorFamily(family.name, family.seedPosition, family.seed)) {
30287
- registry2.define(t);
30288
- }
30289
- }
30290
- const ALL_ROLES = [
30291
- "primary",
30292
- "secondary",
30293
- "tertiary",
30294
- "accent",
30295
- "highlight",
30296
- "neutral",
30297
- "muted",
30298
- "success",
30299
- "warning",
30300
- "destructive",
30301
- "info"
30302
- ];
30303
- const ROLES = ALL_ROLES.filter((r) => {
30304
- const tok = registry2.get(r);
30305
- return tok !== void 0 && tok.namespace === "semantic";
30306
- });
30307
- const assignments = [];
30308
- for (const [i, role] of ROLES.entries()) {
30309
- let familyChoice;
30310
- if (isAgentMode2) {
30311
- familyChoice = families[i]?.name ?? null;
30312
- } else {
30313
- familyChoice = await select({
30314
- message: `Which palette is "${role}"?`,
30315
- choices: [
30316
- ...families.map((f) => ({ name: f.name, value: f.name })),
30317
- { name: "(skip -- keep rafters default)", value: null }
30318
- ]
30319
- });
30320
- }
30321
- if (familyChoice === null) continue;
30322
- const family = families.find((f) => f.name === familyChoice);
30323
- if (!family) continue;
30324
- const position = isAgentMode2 ? family.seedPosition : await select({
30325
- message: `Which position in "${familyChoice}" for "${role}"?`,
30326
- choices: SCALE_POSITIONS.map((p2) => ({
30327
- name: p2,
30328
- value: p2
30329
- })),
30330
- default: family.seedPosition
30331
- });
30332
- registry2.set(
30333
- role,
30334
- { family: familyChoice, position },
30335
- {
30336
- reason: `assigned ${familyChoice}@${position} as ${role} during import from ${detectedCssPath}`
30337
- }
30338
- );
30339
- assignments.push({ role, family: familyChoice, position });
30340
- }
30341
- saveRegistryToDir(paths.tokens, registry2);
30342
- await generateOutputs(cwd, paths, registry2, exports, source);
30343
- const themeImportedNames = families.flatMap(
30344
- (f) => SCALE_POSITIONS.map((p2) => `color-${f.name}-${p2}`).concat([`color-${f.name}`])
30345
- );
30346
- await stripImportedDeclarations(cwd, detectedCssPath, themeImportedNames);
30347
- log({
30348
- event: "init:import_palettes_applied",
30349
- cssPath: detectedCssPath,
30350
- palettesDefined: families.map((f) => f.name),
30351
- assignments
30352
- });
30353
- }
30354
- }
30355
- }
30356
- }
30357
- if (sourceCss !== null) {
30358
- const detectedFonts = adapter.detectFonts(sourceCss);
30359
- if (detectedFonts.length > 0) {
30360
- const typographyTokens = registry2.list({ namespace: "typography" });
30361
- const baseFamilyNames = new Set(
30362
- typographyTokens.filter((t) => (!t.dependsOn || t.dependsOn.length === 0) && isFontStackValue(t.value)).map((t) => t.name)
30363
- );
30364
- const fontRoles = [];
30365
- for (const tok of typographyTokens) {
30366
- const base = tok.dependsOn?.[0];
30367
- if (base === void 0 || !baseFamilyNames.has(base)) continue;
30368
- if (typeof tok.value !== "string" || !tok.value.includes(`var(--${base})`)) continue;
30369
- fontRoles.push({ role: tok.name, base });
30370
- }
30371
- const roleTokenNames = new Set(fontRoles.map((r) => r.role));
30372
- const canonicalTokenNames = /* @__PURE__ */ new Set([...baseFamilyNames, ...roleTokenNames]);
30373
- const definedFonts = [];
30374
- for (const font of detectedFonts) {
30375
- if (font.sourceDeclName && canonicalTokenNames.has(font.sourceDeclName)) continue;
30376
- const tokenName = font.sourceDeclName ?? `font-${slugifyFontName(font.name)}`;
30377
- if (registry2.has(tokenName)) continue;
30378
- registry2.define({
30379
- name: tokenName,
30380
- namespace: "typography",
30381
- category: "typography",
30382
- value: font.stack,
30383
- description: `Imported from ${detectedCssPath}. ${font.sourceDeclName ? `Declared as --${font.sourceDeclName}.` : "Loaded via @font-face or @import."}`,
30384
- userOverride: null,
30385
- containerQueryAware: false
30386
- });
30387
- definedFonts.push(tokenName);
30388
- }
30389
- const fontAssignments = [];
30390
- const declaredByBase = /* @__PURE__ */ new Map();
30391
- for (const font of detectedFonts) {
30392
- if (font.sourceDeclName && baseFamilyNames.has(font.sourceDeclName)) {
30393
- if (!declaredByBase.has(font.sourceDeclName)) {
30394
- declaredByBase.set(font.sourceDeclName, font);
30395
- }
30396
- }
30397
- }
30398
- for (const { role, base } of fontRoles) {
30399
- let choice;
30400
- if (isAgentMode2) {
30401
- const declared = declaredByBase.get(base);
30402
- if (declared !== void 0) {
30403
- choice = declared.name;
30404
- } else if (/mono/.test(base)) {
30405
- choice = detectedFonts.find((f) => /mono/i.test(f.name))?.name ?? null;
30406
- } else {
30407
- const sansLike = detectedFonts.find((f) => !/mono/i.test(f.name));
30408
- choice = sansLike?.name ?? null;
30409
- }
30410
- } else {
30411
- const prompt = role.replace(/^font-/, "");
30412
- choice = await select({
30413
- message: `Which font is "${prompt}"?`,
30414
- choices: [
30415
- ...detectedFonts.map((f) => ({ name: f.name, value: f.name })),
30416
- { name: "(skip -- keep rafters default)", value: null }
30417
- ]
30418
- });
30419
- }
30420
- if (choice === null) continue;
30421
- const font = detectedFonts.find((f) => f.name === choice);
30422
- if (font === void 0) continue;
30423
- const reason = `assigned ${font.name} as ${role} during import from ${detectedCssPath}`;
30424
- if (registry2.has(role)) {
30425
- registry2.set(role, font.stack, { reason });
30426
- }
30427
- if (registry2.has(base)) {
30428
- const current = registry2.get(base);
30429
- if (current?.value !== font.stack) {
30430
- registry2.set(base, font.stack, { reason });
30431
- }
30432
- }
30433
- fontAssignments.push({ role, family: font.name });
30434
- }
30435
- if (fontAssignments.length > 0 || definedFonts.length > 0) {
30436
- saveRegistryToDir(paths.tokens, registry2);
30437
- await generateOutputs(cwd, paths, registry2, exports, source);
30438
- log({
30439
- event: "init:import_fonts_applied",
30440
- cssPath: detectedCssPath,
30441
- fontsDetected: detectedFonts.map((f) => f.name),
30442
- assignments: fontAssignments,
30443
- ...definedFonts.length > 0 ? { definedFonts } : {}
30444
- });
30445
- }
30446
30030
  }
30447
30031
  }
30448
30032
  }
30449
- log({
30450
- event: "init:complete",
30451
- outputs: [...outputs, "config.rafters.json"],
30452
- path: paths.output
30453
- });
30454
- }
30455
-
30456
- // src/commands/add.ts
30457
- var REGISTRY_PLUGINS2 = [scalePlugin, contrastPlugin, statePlugin, invertPlugin];
30458
- async function regenerateAfterInstall(cwd, config2) {
30459
- const paths = getRaftersPaths(cwd);
30460
- let registry2;
30461
- try {
30462
- registry2 = loadRegistryFromDir(paths.tokens, REGISTRY_PLUGINS2);
30463
- } catch {
30464
- return;
30465
- }
30466
- if (registry2.size() === 0) return;
30033
+ const emptyDeps = {
30034
+ installed: [],
30035
+ skipped: [],
30036
+ devInstalled: [],
30037
+ failed: []
30038
+ };
30039
+ let depsResult = emptyDeps;
30467
30040
  try {
30468
- await regenerateOutputs(registry2, {
30469
- outputDir: paths.output,
30470
- exports: config2.exports,
30471
- contentSources: resolveContentSources(cwd, config2),
30472
- darkMode: config2.darkMode ?? "class",
30473
- includeImport: config2.source !== "shadcn"
30041
+ depsResult = await installRegistryDependencies(filteredItems, cwd, {
30042
+ target
30474
30043
  });
30475
30044
  } catch (err) {
30045
+ const message = err instanceof Error ? err.message : String(err);
30476
30046
  log({
30477
- event: "add:regen-failed",
30478
- message: `Output regeneration failed: ${err instanceof Error ? err.message : String(err)}`
30047
+ event: "add:deps:install-failed",
30048
+ message: `Failed to process dependencies: ${message}`,
30049
+ dependencies: [],
30050
+ suggestion: "Check package.json and try installing dependencies manually."
30479
30051
  });
30480
30052
  }
30481
- }
30482
- async function isInitialized(cwd) {
30483
- const paths = getRaftersPaths(cwd);
30484
- try {
30485
- await access(paths.root);
30486
- return true;
30487
- } catch {
30488
- return false;
30053
+ if (depsResult.installed.length > 0 || depsResult.skipped.length > 0) {
30054
+ log({
30055
+ event: "add:dependencies",
30056
+ dependencies: depsResult.installed,
30057
+ devDependencies: depsResult.devInstalled,
30058
+ skipped: depsResult.skipped
30059
+ });
30489
30060
  }
30490
- }
30491
- async function loadConfig(cwd) {
30492
- const paths = getRaftersPaths(cwd);
30493
- try {
30494
- const content = await readFile4(paths.config, "utf-8");
30495
- return migrateConfig(
30496
- JSON.parse(content)
30497
- );
30498
- } catch (err) {
30499
- if (existsSync4(paths.config)) {
30500
- const message = err instanceof Error ? err.message : String(err);
30501
- log({
30502
- event: "add:warning",
30503
- message: `Failed to load config: ${message}`
30504
- });
30505
- }
30506
- return null;
30061
+ if (installedItems.length > 0 && config2) {
30062
+ trackInstalled(config2, installedItems);
30063
+ await saveConfig(cwd, config2);
30064
+ await regenerateAfterInstall(cwd, config2);
30065
+ } else if (installedItems.length > 0 && !config2) {
30066
+ const newConfig = {
30067
+ framework: "unknown",
30068
+ componentsPath: "components/ui",
30069
+ primitivesPath: "lib/primitives",
30070
+ compositesPath: "composites",
30071
+ rulesPath: "lib/rules",
30072
+ cssPath: null,
30073
+ intent: "efficient",
30074
+ fonts: { path: null, imports: [] },
30075
+ exports: DEFAULT_EXPORTS,
30076
+ installed: { components: [], primitives: [], composites: [], rules: [] }
30077
+ };
30078
+ trackInstalled(newConfig, installedItems);
30079
+ await saveConfig(cwd, newConfig);
30080
+ await regenerateAfterInstall(cwd, newConfig);
30507
30081
  }
30508
- }
30509
- function resolveRegistryUrl(options, config2) {
30510
- return options.registryUrl ?? config2?.registryUrl;
30511
- }
30512
- async function saveConfig(cwd, config2) {
30513
- const paths = getRaftersPaths(cwd);
30514
- await writeFile3(paths.config, JSON.stringify(config2, null, 2));
30515
- }
30516
- function getInstalledNames(config2) {
30517
- if (!config2?.installed) return [];
30518
- const names = /* @__PURE__ */ new Set([
30519
- ...config2.installed.components,
30520
- ...config2.installed.primitives,
30521
- ...config2.installed.composites ?? [],
30522
- ...config2.installed.rules ?? [],
30523
- ...config2.installed.substrate ?? []
30524
- ]);
30525
- return [...names].sort();
30526
- }
30527
- async function discoverUntrackedNames(cwd, config2, client, tracked) {
30528
- let index = null;
30529
- try {
30530
- index = await client.fetchIndex();
30531
- } catch (err) {
30532
- const message = err instanceof Error ? err.message : String(err);
30082
+ log({
30083
+ event: "add:complete",
30084
+ written: written.length,
30085
+ skipped: skipped.length,
30086
+ untracked: untrackedNames.length,
30087
+ failed: failed.length,
30088
+ components: written,
30089
+ skippedComponents: skipped,
30090
+ untrackedComponents: untrackedNames,
30091
+ failedComponents: failed
30092
+ });
30093
+ if (failed.length > 0) {
30094
+ process.exitCode = 1;
30095
+ }
30096
+ if (!options.updateAll && skipped.length > 0 && written.length === 0) {
30533
30097
  log({
30534
- event: "add:warning",
30535
- message: `Could not read the registry index to reconcile on-disk components (${message}). Updating tracked components only.`
30098
+ event: "add:hint",
30099
+ message: "Some components were skipped. Use --update to re-fetch, or --update-all to refresh everything.",
30100
+ skipped
30536
30101
  });
30102
+ error46("Component already exists. Use --update to re-fetch from registry.");
30103
+ process.exitCode = 1;
30537
30104
  }
30538
- const { untracked } = buildUpdateCandidates(tracked, index, readInstallRoots(cwd, config2));
30539
- return untracked;
30540
30105
  }
30541
- function getComponentTarget(config2) {
30542
- return resolveComponentTarget(config2);
30543
- }
30544
- var SHARED_EXTENSIONS = /* @__PURE__ */ new Set([".behavior.ts", ".classes.ts"]);
30545
- function isSharedFile(path) {
30546
- for (const ext of SHARED_EXTENSIONS) {
30547
- if (path.endsWith(ext)) return true;
30106
+
30107
+ // src/commands/agents.ts
30108
+ import { existsSync as existsSync6 } from "fs";
30109
+ import { readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
30110
+ import { join as join12 } from "path";
30111
+
30112
+ // src/mcp/tools.ts
30113
+ import { readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
30114
+ import { isAbsolute as isAbsolute3, join as join11 } from "path";
30115
+
30116
+ // ../composites/src/built-in-rules/email.ts
30117
+ var email3 = external_exports.string().email();
30118
+
30119
+ // ../composites/src/built-in-rules/credentials.ts
30120
+ var credentials = external_exports.object({ email: email3, password: external_exports.string().min(8) });
30121
+
30122
+ // ../composites/src/built-in-rules/password.ts
30123
+ var password = external_exports.string().min(8);
30124
+
30125
+ // ../composites/src/built-in-rules/required.ts
30126
+ var required2 = external_exports.string().min(1);
30127
+
30128
+ // ../composites/src/built-in-rules/url.ts
30129
+ var url2 = external_exports.string().url();
30130
+
30131
+ // ../composites/src/manifest.ts
30132
+ var CompositeCategorySchema = external_exports.string().min(1);
30133
+ var AppliedRuleSchema = external_exports.union([
30134
+ external_exports.string(),
30135
+ external_exports.object({
30136
+ name: external_exports.string().min(1),
30137
+ config: external_exports.record(external_exports.string(), external_exports.unknown())
30138
+ })
30139
+ ]);
30140
+ var CompositeBlockSchema = external_exports.object({
30141
+ id: external_exports.string().min(1),
30142
+ type: external_exports.string().min(1),
30143
+ /** Signatures this block consumes / produces (rule names) -- the typed I/O edges.
30144
+ * Within a composite, an output named X feeds any block input named X. */
30145
+ input: external_exports.array(external_exports.string()).optional(),
30146
+ output: external_exports.array(external_exports.string()).optional(),
30147
+ content: external_exports.unknown().optional(),
30148
+ children: external_exports.array(external_exports.string()).optional(),
30149
+ parentId: external_exports.string().optional(),
30150
+ meta: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
30151
+ rules: external_exports.array(AppliedRuleSchema).optional()
30152
+ });
30153
+ var UsagePatternsSchema2 = external_exports.object({
30154
+ do: external_exports.array(external_exports.string()),
30155
+ never: external_exports.array(external_exports.string())
30156
+ });
30157
+ var CompositeManifestSchema = external_exports.object({
30158
+ id: external_exports.string().min(1).regex(/^[a-z0-9-]+$/),
30159
+ name: external_exports.string().min(1),
30160
+ category: CompositeCategorySchema,
30161
+ description: external_exports.string(),
30162
+ keywords: external_exports.array(external_exports.string()),
30163
+ cognitiveLoad: external_exports.number().int().min(1).max(10),
30164
+ // Designer intent fields (optional for backwards compatibility)
30165
+ solves: external_exports.string().optional(),
30166
+ appliesWhen: external_exports.array(external_exports.string()).optional(),
30167
+ usagePatterns: UsagePatternsSchema2.optional()
30168
+ });
30169
+ var CompositeFileSchema = external_exports.object({
30170
+ manifest: CompositeManifestSchema,
30171
+ input: external_exports.array(external_exports.string()).default([]),
30172
+ output: external_exports.array(external_exports.string()).default([]),
30173
+ blocks: external_exports.array(CompositeBlockSchema).min(1)
30174
+ });
30175
+
30176
+ // ../composites/src/discovery.ts
30177
+ function discoverComposites(entries) {
30178
+ const registry2 = /* @__PURE__ */ new Map();
30179
+ const errors = [];
30180
+ for (const entry of entries) {
30181
+ let parsed;
30182
+ try {
30183
+ parsed = JSON.parse(entry.raw);
30184
+ } catch (e) {
30185
+ errors.push({
30186
+ source: entry.source,
30187
+ error: `Invalid JSON: ${e instanceof Error ? e.message : String(e)}`
30188
+ });
30189
+ continue;
30190
+ }
30191
+ const result = CompositeFileSchema.safeParse(parsed);
30192
+ if (!result.success) {
30193
+ errors.push({ source: entry.source, error: result.error.message });
30194
+ continue;
30195
+ }
30196
+ const { id } = result.data.manifest;
30197
+ if (registry2.has(id)) {
30198
+ errors.push({
30199
+ source: entry.source,
30200
+ error: `Duplicate composite id "${id}" (already registered)`
30201
+ });
30202
+ continue;
30203
+ }
30204
+ registry2.set(id, result.data);
30548
30205
  }
30549
- return false;
30206
+ reportUnresolvedReferences(registry2, errors);
30207
+ return { registry: registry2, errors };
30550
30208
  }
30551
- var REACT_FALLBACK_TARGETS = /* @__PURE__ */ new Set(["astro", "vue", "svelte"]);
30552
- function selectFilesForFramework(files, target) {
30553
- const preferredExt = targetToExtension(target);
30554
- const shared = files.filter((f) => isSharedFile(f.path));
30555
- const matched = files.filter((f) => f.path.endsWith(preferredExt));
30556
- if (matched.length > 0) {
30557
- return { files: [...matched, ...shared], fallback: false };
30209
+ var COMPOSITE_PREFIX = "composite:";
30210
+ function reportUnresolvedReferences(registry2, errors) {
30211
+ for (const [id, composite] of registry2) {
30212
+ const missing = /* @__PURE__ */ new Set();
30213
+ for (const block of composite.blocks) {
30214
+ if (block.type.startsWith(COMPOSITE_PREFIX)) {
30215
+ const refId = block.type.slice(COMPOSITE_PREFIX.length);
30216
+ if (!registry2.has(refId)) {
30217
+ missing.add(refId);
30218
+ }
30219
+ }
30220
+ }
30221
+ if (missing.size > 0) {
30222
+ errors.push({
30223
+ source: id,
30224
+ error: `Unresolved composite reference(s): ${[...missing].map((m3) => `composite:${m3}`).join(", ")}`
30225
+ });
30226
+ }
30558
30227
  }
30559
- if (REACT_FALLBACK_TARGETS.has(target)) {
30560
- const fallbackFiles = files.filter((f) => f.path.endsWith(".tsx"));
30561
- if (fallbackFiles.length > 0) {
30562
- return { files: [...fallbackFiles, ...shared], fallback: true };
30228
+ }
30229
+
30230
+ // ../ui/src/primitives/typeahead.ts
30231
+ function fuzzyScore(query, target) {
30232
+ if (query.length === 0) return 1;
30233
+ if (query.length > target.length) return 0;
30234
+ const q = query.toLowerCase();
30235
+ const t = target.toLowerCase();
30236
+ let score = 0;
30237
+ let qi = 0;
30238
+ let prevMatchIndex = -2;
30239
+ for (let ti = 0; ti < t.length && qi < q.length; ti++) {
30240
+ if (t[ti] === q[qi]) {
30241
+ score += 1;
30242
+ if (ti === prevMatchIndex + 1) {
30243
+ score += 2;
30244
+ }
30245
+ if (ti === 0 || t[ti - 1] === " " || t[ti - 1] === "-") {
30246
+ score += 3;
30247
+ }
30248
+ prevMatchIndex = ti;
30249
+ qi++;
30563
30250
  }
30564
30251
  }
30565
- if (target !== "react" && !REACT_FALLBACK_TARGETS.has(target)) {
30566
- return { files: shared, fallback: false };
30252
+ return qi === q.length ? score : 0;
30253
+ }
30254
+
30255
+ // ../composites/src/registry.ts
30256
+ var composites = /* @__PURE__ */ new Map();
30257
+ function register3(composite) {
30258
+ const { id } = composite.manifest;
30259
+ if (composites.has(id)) {
30260
+ throw new Error(`Composite "${id}" is already registered`);
30567
30261
  }
30568
- return { files, fallback: false };
30262
+ composites.set(id, composite);
30569
30263
  }
30570
- var TARGET_GATED_COMPOSITE_FILES = [{ suffix: "Composite.astro", target: "astro" }];
30571
- function selectCompositeFiles(files, target) {
30572
- return files.filter((file2) => {
30573
- const gate = TARGET_GATED_COMPOSITE_FILES.find((g2) => file2.path.endsWith(g2.suffix));
30574
- return gate ? gate.target === target : true;
30575
- });
30264
+ function getAll2() {
30265
+ return Array.from(composites.values());
30576
30266
  }
30577
- var FOLDER_NAMES = /* @__PURE__ */ new Set(["composites"]);
30578
- function isAlreadyInstalled(config2, item) {
30579
- if (!config2?.installed) return false;
30580
- const { components, primitives, composites: composites2, rules, substrate } = config2.installed;
30581
- const bucketByType = {
30582
- ui: components,
30583
- primitive: primitives,
30584
- composite: composites2 ?? [],
30585
- rule: rules ?? [],
30586
- substrate: substrate ?? []
30587
- };
30588
- return bucketByType[item.type].includes(item.name);
30267
+ function getByCategory(category) {
30268
+ return getAll2().filter((c4) => c4.manifest.category === category);
30589
30269
  }
30590
- function isInstalledOnDisk(config2, item, cwd) {
30591
- if (!isAlreadyInstalled(config2, item)) return false;
30592
- const filesToCheck = item.type === "ui" ? selectFilesForFramework(item.files, getComponentTarget(config2)).files : item.files;
30593
- for (const file2 of filesToCheck) {
30594
- if (fileExists(cwd, transformPath(file2.path, config2, cwd))) return true;
30270
+ function search(query) {
30271
+ if (query.length === 0) {
30272
+ return getAll2();
30595
30273
  }
30596
- return false;
30274
+ return getAll2().map((c4) => {
30275
+ const { name, keywords } = c4.manifest;
30276
+ const score = Math.max(fuzzyScore(query, name), ...keywords.map((k) => fuzzyScore(query, k)));
30277
+ return { c: c4, score };
30278
+ }).filter((entry) => entry.score > 0).sort((a2, b2) => b2.score - a2.score).map((entry) => entry.c);
30597
30279
  }
30598
- function trackInstalled(config2, items) {
30599
- if (!config2.installed) {
30600
- config2.installed = {
30601
- components: [],
30602
- primitives: [],
30603
- composites: [],
30604
- rules: []
30605
- };
30606
- }
30607
- const installed = config2.installed;
30608
- if (!installed.composites) installed.composites = [];
30609
- if (!installed.rules) installed.rules = [];
30610
- if (!installed.substrate) installed.substrate = [];
30611
- const bucketByType = {
30612
- ui: installed.components,
30613
- primitive: installed.primitives,
30614
- composite: installed.composites,
30615
- rule: installed.rules,
30616
- substrate: installed.substrate
30617
- };
30618
- for (const item of items) {
30619
- const bucket = bucketByType[item.type];
30620
- if (!bucket.includes(item.name)) bucket.push(item.name);
30280
+
30281
+ // ../composites/src/to-mdx.ts
30282
+ var import_escape_html = __toESM(require_escape_html(), 1);
30283
+
30284
+ // ../composites/src/discovery-node.ts
30285
+ import { readdir, readFile as readFile4 } from "fs/promises";
30286
+ import { join as join8 } from "path";
30287
+ var COMPOSITE_SUFFIX = ".composite.json";
30288
+ async function readDir(directory) {
30289
+ const entries = [];
30290
+ let dirents;
30291
+ try {
30292
+ dirents = await readdir(directory, { withFileTypes: true });
30293
+ } catch {
30294
+ return entries;
30621
30295
  }
30622
- installed.components.sort();
30623
- installed.primitives.sort();
30624
- installed.composites.sort();
30625
- installed.rules.sort();
30626
- installed.substrate.sort();
30627
- }
30628
- function rootFor(field, cwd, fallback) {
30629
- return field === void 0 ? fallback : resolveRoot(field, cwd, fallback);
30630
- }
30631
- function transformPath(registryPath, config2, cwd = process.cwd()) {
30632
- if (!config2) return registryPath;
30633
- const replacements = [
30634
- ["components/ui/", config2.componentsPath, "components/ui"],
30635
- ["lib/primitives/", config2.primitivesPath, "lib/primitives"],
30636
- ["composites/", config2.compositesPath, "composites"],
30637
- ["rules/", config2.rulesPath, "lib/rules"]
30638
- ];
30639
- for (const [prefix, field, fallback] of replacements) {
30640
- if (registryPath.startsWith(prefix)) {
30641
- return registryPath.replace(prefix, `${rootFor(field, cwd, fallback)}/`);
30296
+ for (const dirent of dirents) {
30297
+ const fullPath = join8(directory, dirent.name);
30298
+ if (dirent.isDirectory()) {
30299
+ entries.push(...await readDir(fullPath));
30300
+ continue;
30301
+ }
30302
+ if (dirent.isFile() && dirent.name.endsWith(COMPOSITE_SUFFIX)) {
30303
+ const raw = await readFile4(fullPath, "utf-8");
30304
+ entries.push({ raw, source: fullPath });
30642
30305
  }
30643
30306
  }
30644
- return registryPath;
30307
+ return entries;
30645
30308
  }
30646
- function substrateProjectPath(registryPath, config2, cwd = process.cwd()) {
30647
- const componentsResolved = rootFor(config2?.componentsPath, cwd, "components/ui");
30648
- const sourceRoot = componentsResolved.replace(/\/?components\/ui$/, "");
30649
- return sourceRoot ? join8(sourceRoot, registryPath) : registryPath;
30309
+ function nodeFsAdapter(...directories) {
30310
+ return async () => {
30311
+ const entries = [];
30312
+ for (const directory of directories) {
30313
+ entries.push(...await readDir(directory));
30314
+ }
30315
+ return entries;
30316
+ };
30650
30317
  }
30651
- function fileExists(cwd, relativePath) {
30652
- return existsSync4(join8(cwd, relativePath));
30318
+ async function discoverFromDirs(...directories) {
30319
+ const adapter = nodeFsAdapter(...directories);
30320
+ return discoverComposites(await adapter());
30653
30321
  }
30654
- function transformFileContent(content, config2, fileType = "component", cwd = process.cwd(), opts = {}) {
30655
- const { substrateKinds = [], installPath } = opts;
30656
- let transformed = content;
30657
- const componentsPath = rootFor(config2?.componentsPath, cwd, "components/ui");
30658
- const primitivesPath = rootFor(config2?.primitivesPath, cwd, "lib/primitives");
30659
- const stripSourceRoot = (p2) => p2.replace(/^(src|app)\//, "");
30660
- const aliasComponents = stripSourceRoot(componentsPath);
30661
- const aliasPrimitives = stripSourceRoot(primitivesPath);
30662
- transformed = transformed.replace(
30663
- /from\s+['"]\.\.\/\.\.\/primitives\/([^'"]+)['"]/g,
30664
- `from '@/${aliasPrimitives}/$1'`
30665
- );
30666
- transformed = transformed.replace(
30667
- /from\s+['"]\.\.\/primitives\/([^'"]+)['"]/g,
30668
- `from '@/${aliasPrimitives}/$1'`
30669
- );
30670
- const substrateOwnDir = fileType === "substrate" && installPath ? stripSourceRoot(dirname(installPath)) : null;
30671
- const aliasSibling = fileType === "primitive" ? aliasPrimitives : substrateOwnDir ?? aliasComponents;
30672
- transformed = transformed.replace(/from\s+['"]\.\/([^'"]+)['"]/g, `from '@/${aliasSibling}/$1'`);
30673
- if (substrateKinds.length > 0) {
30674
- const kindAlternation = substrateKinds.join("|");
30675
- transformed = transformed.replace(
30676
- new RegExp(`from\\s+['"](?:\\.\\./){1,2}(${kindAlternation})/([^'"]+)['"]`, "g"),
30677
- "from '@/$1/$2'"
30678
- );
30322
+
30323
+ // src/utils/workspaces.ts
30324
+ import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
30325
+ import { basename, dirname as dirname3, join as join10, resolve as resolve4 } from "path";
30326
+
30327
+ // src/utils/discover.ts
30328
+ import { existsSync as existsSync4 } from "fs";
30329
+ import { dirname as dirname2, join as join9, resolve as resolve3 } from "path";
30330
+ function discoverProjectRoot(startDir) {
30331
+ let current = resolve3(startDir);
30332
+ for (; ; ) {
30333
+ const configPath2 = join9(current, ".rafters", "config.rafters.json");
30334
+ if (existsSync4(configPath2)) {
30335
+ return current;
30336
+ }
30337
+ const parent = dirname2(current);
30338
+ if (parent === current) {
30339
+ return null;
30340
+ }
30341
+ current = parent;
30679
30342
  }
30680
- transformed = transformed.replace(
30681
- /from\s+['"]\.\.\/([^/'"]+)\/\1([^'"]*)['"]/g,
30682
- `from '@/${aliasComponents}/$1$2'`
30683
- );
30684
- transformed = transformed.replace(
30685
- /from\s+['"]\.\.\/([^'"]+)['"]/g,
30686
- `from '@/${aliasComponents}/$1'`
30687
- );
30688
- return transformed;
30689
30343
  }
30690
- async function installItem(cwd, item, options, config2, substrateKinds = []) {
30691
- const installedFiles = [];
30692
- let skipped = false;
30693
- let filesToInstall = item.files;
30694
- if (item.type === "ui") {
30695
- const target = getComponentTarget(config2);
30696
- const selection = selectFilesForFramework(item.files, target);
30697
- filesToInstall = selection.files;
30698
- if (selection.fallback) {
30699
- log({
30700
- event: "add:fallback",
30701
- component: item.name,
30702
- target,
30703
- message: `No ${targetToExtension(target)} version available for ${item.name}. Installing React version.`
30704
- });
30344
+
30345
+ // src/utils/workspaces.ts
30346
+ function findMonorepoRoot(startDir, boundary) {
30347
+ let current = resolve4(startDir);
30348
+ const stopAt = boundary ? resolve4(boundary) : null;
30349
+ for (; ; ) {
30350
+ const pnpmWorkspace = join10(current, "pnpm-workspace.yaml");
30351
+ if (existsSync5(pnpmWorkspace)) {
30352
+ const patterns = parsePnpmWorkspaceYaml(readFileSync2(pnpmWorkspace, "utf-8"));
30353
+ if (patterns.length > 0) {
30354
+ return { root: current, patterns };
30355
+ }
30705
30356
  }
30706
- const hasComponentFile = selection.files.some((f) => !isSharedFile(f.path));
30707
- if (!hasComponentFile) {
30708
- throw new Error(
30709
- `No ${targetToExtension(target)} version available for "${item.name}" and no compatible fallback exists.`
30710
- );
30357
+ const pkgJson = join10(current, "package.json");
30358
+ if (existsSync5(pkgJson)) {
30359
+ const patterns = parsePackageJsonWorkspaces(readFileSync2(pkgJson, "utf-8"));
30360
+ if (patterns.length > 0) {
30361
+ return { root: current, patterns };
30362
+ }
30711
30363
  }
30712
- } else if (item.type === "composite") {
30713
- filesToInstall = selectCompositeFiles(item.files, getComponentTarget(config2));
30364
+ if (stopAt && current === stopAt) return null;
30365
+ const parent = dirname3(current);
30366
+ if (parent === current) return null;
30367
+ current = parent;
30714
30368
  }
30715
- for (const file2 of filesToInstall) {
30716
- const projectPath2 = item.type === "substrate" ? substrateProjectPath(file2.path, config2, cwd) : transformPath(file2.path, config2, cwd);
30717
- const targetPath = join8(cwd, projectPath2);
30718
- if (fileExists(cwd, projectPath2)) {
30719
- if (!options.overwrite) {
30720
- log({
30721
- event: "add:skip",
30722
- component: item.name,
30723
- file: projectPath2,
30724
- reason: "exists"
30725
- });
30726
- skipped = true;
30369
+ }
30370
+ function parsePnpmWorkspaceYaml(content) {
30371
+ const lines = content.split("\n");
30372
+ const patterns = [];
30373
+ let inPackages = false;
30374
+ for (const rawLine of lines) {
30375
+ const line = rawLine.replace(/#.*$/, "").trimEnd();
30376
+ if (!line.trim()) continue;
30377
+ if (/^packages\s*:/.test(line)) {
30378
+ inPackages = true;
30379
+ continue;
30380
+ }
30381
+ if (inPackages) {
30382
+ const itemMatch = line.match(/^\s*-\s*(?:["']?)([^"'\s]+)(?:["']?)\s*$/);
30383
+ if (itemMatch?.[1]) {
30384
+ patterns.push(itemMatch[1]);
30727
30385
  continue;
30728
30386
  }
30387
+ if (!/^\s/.test(line)) {
30388
+ inPackages = false;
30389
+ }
30729
30390
  }
30730
- await mkdir3(dirname(targetPath), { recursive: true });
30731
- const fileType = item.type === "primitive" ? "primitive" : item.type === "substrate" ? "substrate" : "component";
30732
- const transformedContent = transformFileContent(file2.content, config2, fileType, cwd, {
30733
- substrateKinds,
30734
- installPath: file2.path
30735
- });
30736
- await writeFile3(targetPath, transformedContent, "utf-8");
30737
- installedFiles.push(projectPath2);
30738
30391
  }
30739
- return {
30740
- installed: installedFiles.length > 0,
30741
- skipped,
30742
- files: installedFiles
30743
- };
30392
+ return patterns;
30744
30393
  }
30745
- async function add(componentArgs, options) {
30746
- setAgentMode(options.agent ?? false);
30747
- let components = componentArgs;
30748
- const cwd = process.cwd();
30749
- const config2 = await loadConfig(cwd);
30750
- const client = new RegistryClient(resolveRegistryUrl(options, config2));
30751
- let folder;
30752
- const firstArg = components[0];
30753
- if (firstArg && FOLDER_NAMES.has(firstArg)) {
30754
- folder = firstArg;
30755
- components = components.slice(1);
30394
+ function parsePackageJsonWorkspaces(content) {
30395
+ try {
30396
+ const pkg = JSON.parse(content);
30397
+ const ws = pkg.workspaces;
30398
+ if (Array.isArray(ws)) {
30399
+ return ws.filter((p2) => typeof p2 === "string");
30400
+ }
30401
+ if (ws && typeof ws === "object" && Array.isArray(ws.packages)) {
30402
+ return ws.packages.filter((p2) => typeof p2 === "string");
30403
+ }
30404
+ return [];
30405
+ } catch {
30406
+ return [];
30756
30407
  }
30757
- if (options.list) {
30758
- const availableComponents = await client.listComponents();
30759
- const availableComposites = await client.listComposites();
30760
- if (options.agent) {
30761
- log({
30762
- event: "add:list",
30763
- components: availableComponents,
30764
- composites: availableComposites
30765
- });
30766
- } else {
30767
- console.log("Available components:\n");
30768
- for (const comp of availableComponents) {
30769
- console.log(` ${comp.name} ${comp.description ?? ""}`);
30408
+ }
30409
+ function expandPattern(monorepoRoot, pattern) {
30410
+ const trimmed = pattern.replace(/\/+$/, "");
30411
+ if (trimmed.endsWith("/*")) {
30412
+ const parentRel = trimmed.slice(0, -2);
30413
+ const parent = join10(monorepoRoot, parentRel);
30414
+ if (!existsSync5(parent)) return [];
30415
+ return readdirSync3(parent).map((entry) => join10(parent, entry)).filter((path) => {
30416
+ try {
30417
+ return statSync2(path).isDirectory();
30418
+ } catch {
30419
+ return false;
30770
30420
  }
30771
- if (availableComposites.length > 0) {
30772
- console.log("\nAvailable composites:\n");
30773
- for (const comp of availableComposites) {
30774
- console.log(` ${comp.name} ${comp.description ?? ""}`);
30775
- }
30421
+ });
30422
+ }
30423
+ const literal2 = join10(monorepoRoot, trimmed);
30424
+ if (existsSync5(literal2)) {
30425
+ try {
30426
+ if (statSync2(literal2).isDirectory()) return [literal2];
30427
+ } catch {
30428
+ return [];
30429
+ }
30430
+ }
30431
+ return [];
30432
+ }
30433
+ function discoverWorkspaces(startDir = process.cwd(), options = {}) {
30434
+ const layout = findMonorepoRoot(startDir, options.boundary);
30435
+ if (!layout) {
30436
+ const single = discoverProjectRoot(startDir);
30437
+ if (!single) return [];
30438
+ if (options.boundary && !single.startsWith(resolve4(options.boundary))) return [];
30439
+ return [{ name: basename(single), root: single }];
30440
+ }
30441
+ const seen = /* @__PURE__ */ new Set();
30442
+ const workspaces = [];
30443
+ for (const pattern of layout.patterns) {
30444
+ for (const dir of expandPattern(layout.root, pattern)) {
30445
+ if (seen.has(dir)) continue;
30446
+ seen.add(dir);
30447
+ const config2 = join10(dir, ".rafters", "config.rafters.json");
30448
+ if (existsSync5(config2)) {
30449
+ workspaces.push({ name: basename(dir), root: dir });
30776
30450
  }
30777
30451
  }
30778
- return;
30779
30452
  }
30780
- const initialized = await isInitialized(cwd);
30781
- if (!initialized) {
30782
- error46("Project not initialized. Run `rafters init` first.");
30783
- process.exitCode = 1;
30784
- return;
30453
+ const rootConfig = join10(layout.root, ".rafters", "config.rafters.json");
30454
+ if (existsSync5(rootConfig) && !seen.has(layout.root)) {
30455
+ workspaces.unshift({ name: basename(layout.root), root: layout.root });
30785
30456
  }
30786
- if (options.update) {
30787
- options.overwrite = true;
30457
+ return workspaces;
30458
+ }
30459
+ function pickDefaultWorkspace(workspaces, startDir = process.cwd()) {
30460
+ if (workspaces.length === 0) return null;
30461
+ const cwd = resolve4(startDir);
30462
+ const containing = workspaces.find((ws) => cwd === ws.root || cwd.startsWith(`${ws.root}/`));
30463
+ if (containing) return containing;
30464
+ if (workspaces.length === 1) return workspaces[0] ?? null;
30465
+ return null;
30466
+ }
30467
+ function resolveWorkspace(workspaces, defaultWorkspace, name) {
30468
+ if (name) {
30469
+ return workspaces.find((ws) => ws.name === name) ?? null;
30788
30470
  }
30789
- let untrackedNames = [];
30790
- if (options.updateAll) {
30791
- options.overwrite = true;
30792
- if (!config2) {
30793
- error46("No rafters config found. Run 'rafters init' first.");
30794
- process.exitCode = 1;
30795
- return;
30796
- }
30797
- const trackedNames = getInstalledNames(config2);
30798
- untrackedNames = await discoverUntrackedNames(cwd, config2, client, trackedNames);
30799
- const candidates = [.../* @__PURE__ */ new Set([...trackedNames, ...untrackedNames])].sort();
30800
- if (candidates.length === 0) {
30801
- error46("No installed components found. Use 'rafters add <component>' to install first.");
30802
- process.exitCode = 1;
30803
- return;
30471
+ return defaultWorkspace;
30472
+ }
30473
+
30474
+ // src/mcp/graph.ts
30475
+ var EXPAND_OP = "*";
30476
+ var PROBE_OP = "?";
30477
+ function assembleGraph(items) {
30478
+ const nodes = /* @__PURE__ */ new Map();
30479
+ for (const item of items) {
30480
+ const kind = kindOf(item.type);
30481
+ if (kind === null) continue;
30482
+ const src = item.intelligence;
30483
+ const intel = {
30484
+ dos: src?.usagePatterns?.dos ?? [],
30485
+ nevers: src?.usagePatterns?.nevers ?? []
30486
+ };
30487
+ if (src?.cognitiveLoad !== void 0) intel.cognitiveLoad = src.cognitiveLoad;
30488
+ if (src?.semanticMeaning !== void 0) intel.semanticMeaning = src.semanticMeaning;
30489
+ if (src?.accessibility !== void 0) intel.accessibility = src.accessibility;
30490
+ if (src?.attentionEconomics !== void 0) intel.attentionEconomics = src.attentionEconomics;
30491
+ if (src?.trustBuilding !== void 0) intel.trustBuilding = src.trustBuilding;
30492
+ const gn = {
30493
+ id: item.name,
30494
+ kind,
30495
+ intel,
30496
+ facets: item.facets ?? {},
30497
+ composesWith: item.composites,
30498
+ parts: []
30499
+ };
30500
+ if (item.parent !== void 0) gn.parent = item.parent;
30501
+ nodes.set(item.name, gn);
30502
+ }
30503
+ for (const node of nodes.values()) {
30504
+ if (node.parent === void 0) continue;
30505
+ if (node.parent === node.id) {
30506
+ throw new Error(`graph: node "${node.id}" parent references itself`);
30804
30507
  }
30805
- if (untrackedNames.length > 0) {
30806
- log({
30807
- event: "add:untracked",
30808
- components: untrackedNames,
30809
- message: `Found ${untrackedNames.length} component(s) on disk the config never tracked: ${untrackedNames.join(", ")}. Refreshing and tracking them.`
30810
- });
30508
+ if (!nodes.has(node.parent)) {
30509
+ throw new Error(`graph: node "${node.id}" parent names unknown id "${node.parent}"`);
30811
30510
  }
30812
- components = candidates;
30813
- }
30814
- if (folder === "composites" && components.length === 0) {
30815
- components = ["composites"];
30816
- }
30817
- if (components.length === 0) {
30818
- error46("No components specified. Usage: rafters add <component...>");
30819
- process.exitCode = 1;
30820
- return;
30821
30511
  }
30822
- log({
30823
- event: "add:start",
30824
- cwd,
30825
- components,
30826
- overwrite: options.overwrite ?? false
30827
- });
30828
- const allItems = [];
30829
- const seen = /* @__PURE__ */ new Set();
30830
- for (const itemName of components) {
30831
- try {
30832
- if (folder === "composites") {
30833
- if (!seen.has(itemName)) {
30834
- const composite = await client.fetchComposite(itemName);
30835
- seen.add(itemName);
30836
- allItems.push(composite);
30837
- for (const dep of composite.primitives) {
30838
- if (!seen.has(dep)) {
30839
- const depItems = await client.resolveDependencies(dep, seen);
30840
- allItems.push(...depItems);
30841
- }
30842
- }
30843
- }
30844
- } else {
30845
- const items = await client.resolveDependencies(itemName, seen);
30846
- allItems.push(...items);
30847
- }
30848
- } catch (err) {
30849
- if (err instanceof Error) {
30850
- error46(err.message);
30851
- } else {
30852
- error46(`Failed to fetch "${itemName}"`);
30512
+ for (const node of nodes.values()) {
30513
+ if (node.parent === void 0) continue;
30514
+ const visited = /* @__PURE__ */ new Set();
30515
+ visited.add(node.id);
30516
+ let current = node.parent;
30517
+ while (current !== void 0) {
30518
+ if (visited.has(current)) {
30519
+ throw new Error(
30520
+ `graph: node "${node.id}" has a circular parent chain through "${current}"`
30521
+ );
30853
30522
  }
30854
- process.exitCode = 1;
30855
- return;
30523
+ visited.add(current);
30524
+ current = nodes.get(current)?.parent;
30856
30525
  }
30857
30526
  }
30858
- const substrateKinds = [
30859
- ...new Set(
30860
- allItems.filter((item) => item.type === "substrate").map((item) => item.files[0]?.path.split("/")[0]).filter((segment) => Boolean(segment))
30861
- )
30862
- ];
30863
- const written = [];
30864
- const skipped = [];
30865
- const failed = [];
30866
- const installedItems = [];
30867
- const filteredItems = [];
30868
- const target = getComponentTarget(config2);
30869
- for (const item of allItems) {
30870
- if (!options.overwrite && isAlreadyInstalled(config2, item)) {
30871
- if (isInstalledOnDisk(config2, item, cwd)) {
30872
- log({
30873
- event: "add:skip",
30874
- component: item.name,
30875
- reason: "already installed"
30876
- });
30877
- skipped.push(item.name);
30878
- continue;
30527
+ for (const node of nodes.values()) {
30528
+ if (node.parent === void 0) continue;
30529
+ const parentNode = nodes.get(node.parent);
30530
+ if (parentNode) parentNode.parts.push(node.id);
30531
+ }
30532
+ for (const node of nodes.values()) {
30533
+ for (const target of node.composesWith) {
30534
+ if (!nodes.has(target)) {
30535
+ throw new Error(`graph: node "${node.id}" composesWith unknown id "${target}"`);
30879
30536
  }
30880
- log({
30881
- event: "add:warning",
30882
- component: item.name,
30883
- message: "Config tracks this as installed but no files found on disk -- reinstalling."
30884
- });
30885
30537
  }
30886
- try {
30887
- const result = await installItem(cwd, item, options, config2, substrateKinds);
30888
- if (result.installed) {
30889
- written.push(item.name);
30890
- installedItems.push(item);
30891
- if (item.type === "ui") {
30892
- const selection = selectFilesForFramework(item.files, target);
30893
- filteredItems.push({ ...item, files: selection.files });
30894
- } else {
30895
- filteredItems.push(item);
30896
- }
30897
- log({
30898
- event: "add:installed",
30899
- component: item.name,
30900
- type: item.type,
30901
- files: result.files
30902
- });
30903
- }
30904
- if (result.skipped && !result.installed) {
30905
- skipped.push(item.name);
30906
- installedItems.push(item);
30907
- }
30908
- } catch (err) {
30909
- failed.push(item.name);
30910
- if (err instanceof Error) {
30911
- log({
30912
- event: "add:warning",
30913
- component: item.name,
30914
- message: err.message
30915
- });
30916
- }
30538
+ }
30539
+ return { nodes };
30540
+ }
30541
+ function kindOf(type2) {
30542
+ if (type2 === "composite") return "composite";
30543
+ if (type2 === "ui" || type2 === "primitive") return "component";
30544
+ return null;
30545
+ }
30546
+ function describe(addr, graph, target) {
30547
+ const parts = addr === "" ? [] : addr.split(".");
30548
+ if (parts.length === 0) {
30549
+ return {
30550
+ kinds: [
30551
+ { addr: "components", type: "edge" },
30552
+ { addr: "composites", type: "edge" }
30553
+ ],
30554
+ nodeCount: graph.nodes.size
30555
+ };
30556
+ }
30557
+ const [head, ...rest] = parts;
30558
+ if (head === void 0) return { error: `cannot resolve: ${addr}` };
30559
+ if (head === "components" || head === "composites") {
30560
+ if (rest.length > 0) return { error: `cannot expand: ${addr}` };
30561
+ const kind = head === "components" ? "component" : "composite";
30562
+ const roster = [];
30563
+ for (const node2 of graph.nodes.values()) {
30564
+ if (node2.kind === kind) roster.push({ id: node2.id });
30917
30565
  }
30566
+ return roster;
30918
30567
  }
30919
- const emptyDeps = {
30920
- installed: [],
30921
- skipped: [],
30922
- devInstalled: [],
30923
- failed: []
30924
- };
30925
- let depsResult = emptyDeps;
30926
- try {
30927
- depsResult = await installRegistryDependencies(filteredItems, cwd, {
30928
- target
30929
- });
30930
- } catch (err) {
30931
- const message = err instanceof Error ? err.message : String(err);
30932
- log({
30933
- event: "add:deps:install-failed",
30934
- message: `Failed to process dependencies: ${message}`,
30935
- dependencies: [],
30936
- suggestion: "Check package.json and try installing dependencies manually."
30937
- });
30568
+ if (parts[parts.length - 1] === PROBE_OP) {
30569
+ const baseAddr = parts.slice(0, -1).join(".");
30570
+ const result = describe(baseAddr, graph, target);
30571
+ if (typeof result === "object" && result !== null && "error" in result) return null;
30572
+ return result;
30938
30573
  }
30939
- if (depsResult.installed.length > 0 || depsResult.skipped.length > 0) {
30940
- log({
30941
- event: "add:dependencies",
30942
- dependencies: depsResult.installed,
30943
- devDependencies: depsResult.devInstalled,
30944
- skipped: depsResult.skipped
30945
- });
30574
+ if (head === EXPAND_OP || head === PROBE_OP) {
30575
+ return { error: `cannot expand at root: use 'components' or 'composites'` };
30946
30576
  }
30947
- if (installedItems.length > 0 && config2) {
30948
- trackInstalled(config2, installedItems);
30949
- await saveConfig(cwd, config2);
30950
- await regenerateAfterInstall(cwd, config2);
30951
- } else if (installedItems.length > 0 && !config2) {
30952
- const newConfig = {
30953
- framework: "unknown",
30954
- componentsPath: "components/ui",
30955
- primitivesPath: "lib/primitives",
30956
- compositesPath: "composites",
30957
- rulesPath: "lib/rules",
30958
- cssPath: null,
30959
- intent: "efficient",
30960
- fonts: { path: null, imports: [] },
30961
- exports: DEFAULT_EXPORTS,
30962
- installed: { components: [], primitives: [], composites: [], rules: [] }
30963
- };
30964
- trackInstalled(newConfig, installedItems);
30965
- await saveConfig(cwd, newConfig);
30966
- await regenerateAfterInstall(cwd, newConfig);
30577
+ const node = graph.nodes.get(head);
30578
+ if (!node) return { error: `unknown node: ${head}` };
30579
+ if (rest.length === 0) return layer0(node, graph, target);
30580
+ if (rest[rest.length - 1] === EXPAND_OP) {
30581
+ const prefix = rest.slice(0, -1);
30582
+ if (prefix.length === 0) return expandNode(node, target);
30583
+ if (prefix.length === 1 && prefix[0] === "props") return expandProps(node, target);
30584
+ return { error: `cannot expand: ${addr}` };
30967
30585
  }
30968
- log({
30969
- event: "add:complete",
30970
- written: written.length,
30971
- skipped: skipped.length,
30972
- untracked: untrackedNames.length,
30973
- failed: failed.length,
30974
- components: written,
30975
- skippedComponents: skipped,
30976
- untrackedComponents: untrackedNames,
30977
- failedComponents: failed
30978
- });
30979
- if (failed.length > 0) {
30980
- process.exitCode = 1;
30586
+ if (rest.length === 1 && rest[0] === "composesWith") {
30587
+ return resolveEdges(node, graph, target);
30981
30588
  }
30982
- if (!options.updateAll && skipped.length > 0 && written.length === 0) {
30983
- log({
30984
- event: "add:hint",
30985
- message: "Some components were skipped. Use --update to re-fetch, or --update-all to refresh everything.",
30986
- skipped
30987
- });
30988
- error46("Component already exists. Use --update to re-fetch from registry.");
30989
- process.exitCode = 1;
30589
+ if (rest[0] === "props") {
30590
+ const facet = target === void 0 ? void 0 : node.facets[target];
30591
+ const propName = rest[1];
30592
+ const prop = facet && propName !== void 0 ? facet.props[propName] : void 0;
30593
+ if (!prop) return { error: `cannot resolve: ${addr}` };
30594
+ if (rest.length === 2) return toAgentProp(prop);
30595
+ return { error: `cannot resolve: ${addr}` };
30990
30596
  }
30597
+ return { error: `cannot resolve: ${addr}` };
30991
30598
  }
30992
-
30993
- // src/commands/mcp.ts
30994
- import { existsSync as existsSync7 } from "fs";
30995
- import { basename as basename2, join as join13, resolve as resolve5 } from "path";
30996
-
30997
- // src/mcp/server.ts
30998
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
30999
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
31000
- import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
31001
-
31002
- // src/version.ts
31003
- import { readFileSync as readFileSync2 } from "fs";
31004
- var UNKNOWN_VERSION = "0.0.0-unknown";
31005
- function readVersion() {
31006
- try {
31007
- const parsed = JSON.parse(
31008
- readFileSync2(new URL("../package.json", import.meta.url), "utf-8")
31009
- );
31010
- if (parsed !== null && typeof parsed === "object" && "version" in parsed) {
31011
- const { version: version2 } = parsed;
31012
- if (typeof version2 === "string" && version2.length > 0) return version2;
31013
- }
31014
- } catch {
30599
+ function toAgentProp(prop) {
30600
+ if (prop.type === "grammar") {
30601
+ const { vocab: _vocab, ...agentView } = prop;
30602
+ return agentView;
31015
30603
  }
31016
- return UNKNOWN_VERSION;
30604
+ return prop;
31017
30605
  }
31018
- var VERSION = readVersion();
31019
-
31020
- // src/mcp/tools.ts
31021
- import { readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
31022
- import { isAbsolute as isAbsolute3, join as join12 } from "path";
31023
-
31024
- // ../composites/src/built-in-rules/email.ts
31025
- var email3 = external_exports.string().email();
31026
-
31027
- // ../composites/src/built-in-rules/credentials.ts
31028
- var credentials = external_exports.object({ email: email3, password: external_exports.string().min(8) });
31029
-
31030
- // ../composites/src/built-in-rules/password.ts
31031
- var password = external_exports.string().min(8);
31032
-
31033
- // ../composites/src/built-in-rules/required.ts
31034
- var required2 = external_exports.string().min(1);
31035
-
31036
- // ../composites/src/built-in-rules/url.ts
31037
- var url2 = external_exports.string().url();
31038
-
31039
- // ../composites/src/manifest.ts
31040
- var CompositeCategorySchema = external_exports.string().min(1);
31041
- var AppliedRuleSchema = external_exports.union([
31042
- external_exports.string(),
31043
- external_exports.object({
31044
- name: external_exports.string().min(1),
31045
- config: external_exports.record(external_exports.string(), external_exports.unknown())
31046
- })
31047
- ]);
31048
- var CompositeBlockSchema = external_exports.object({
31049
- id: external_exports.string().min(1),
31050
- type: external_exports.string().min(1),
31051
- /** Signatures this block consumes / produces (rule names) -- the typed I/O edges.
31052
- * Within a composite, an output named X feeds any block input named X. */
31053
- input: external_exports.array(external_exports.string()).optional(),
31054
- output: external_exports.array(external_exports.string()).optional(),
31055
- content: external_exports.unknown().optional(),
31056
- children: external_exports.array(external_exports.string()).optional(),
31057
- parentId: external_exports.string().optional(),
31058
- meta: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
31059
- rules: external_exports.array(AppliedRuleSchema).optional()
31060
- });
31061
- var UsagePatternsSchema2 = external_exports.object({
31062
- do: external_exports.array(external_exports.string()),
31063
- never: external_exports.array(external_exports.string())
31064
- });
31065
- var CompositeManifestSchema = external_exports.object({
31066
- id: external_exports.string().min(1).regex(/^[a-z0-9-]+$/),
31067
- name: external_exports.string().min(1),
31068
- category: CompositeCategorySchema,
31069
- description: external_exports.string(),
31070
- keywords: external_exports.array(external_exports.string()),
31071
- cognitiveLoad: external_exports.number().int().min(1).max(10),
31072
- // Designer intent fields (optional for backwards compatibility)
31073
- solves: external_exports.string().optional(),
31074
- appliesWhen: external_exports.array(external_exports.string()).optional(),
31075
- usagePatterns: UsagePatternsSchema2.optional()
31076
- });
31077
- var CompositeFileSchema = external_exports.object({
31078
- manifest: CompositeManifestSchema,
31079
- input: external_exports.array(external_exports.string()).default([]),
31080
- output: external_exports.array(external_exports.string()).default([]),
31081
- blocks: external_exports.array(CompositeBlockSchema).min(1)
31082
- });
31083
-
31084
- // ../composites/src/discovery.ts
31085
- function discoverComposites(entries) {
31086
- const registry2 = /* @__PURE__ */ new Map();
31087
- const errors = [];
31088
- for (const entry of entries) {
31089
- let parsed;
31090
- try {
31091
- parsed = JSON.parse(entry.raw);
31092
- } catch (e) {
31093
- errors.push({
31094
- source: entry.source,
31095
- error: `Invalid JSON: ${e instanceof Error ? e.message : String(e)}`
31096
- });
31097
- continue;
31098
- }
31099
- const result = CompositeFileSchema.safeParse(parsed);
31100
- if (!result.success) {
31101
- errors.push({ source: entry.source, error: result.error.message });
31102
- continue;
30606
+ function layer0(node, graph, target) {
30607
+ const facet = target === void 0 ? void 0 : node.facets[target];
30608
+ const children = [];
30609
+ if (facet) {
30610
+ for (const [name, prop] of Object.entries(facet.props)) {
30611
+ const child = { addr: `${node.id}.props.${name}`, type: prop.type };
30612
+ if (prop.type === "deprecated") child.deprecatedFor = prop.deprecatedFor;
30613
+ children.push(child);
31103
30614
  }
31104
- const { id } = result.data.manifest;
31105
- if (registry2.has(id)) {
31106
- errors.push({
31107
- source: entry.source,
31108
- error: `Duplicate composite id "${id}" (already registered)`
31109
- });
31110
- continue;
30615
+ }
30616
+ if (node.composesWith.length > 0) {
30617
+ children.push({ addr: `${node.id}.composesWith`, type: "edge" });
30618
+ }
30619
+ for (const partId of node.parts) {
30620
+ children.push({ addr: partId, type: "part" });
30621
+ }
30622
+ const result = { id: node.id, kind: node.kind, intel: node.intel, children };
30623
+ if (facet) {
30624
+ result.snippet = facet.snippet;
30625
+ if (facet.slots !== void 0) result.slots = facet.slots;
30626
+ if (facet.events !== void 0) result.events = facet.events;
30627
+ }
30628
+ if (node.parent !== void 0) {
30629
+ result.parent = node.parent;
30630
+ const parentNode = graph.nodes.get(node.parent);
30631
+ if (parentNode) {
30632
+ const siblings = parentNode.parts.filter((id) => id !== node.id);
30633
+ if (siblings.length > 0) result.siblings = siblings;
31111
30634
  }
31112
- registry2.set(id, result.data);
31113
30635
  }
31114
- reportUnresolvedReferences(registry2, errors);
31115
- return { registry: registry2, errors };
30636
+ return result;
31116
30637
  }
31117
- var COMPOSITE_PREFIX = "composite:";
31118
- function reportUnresolvedReferences(registry2, errors) {
31119
- for (const [id, composite] of registry2) {
31120
- const missing = /* @__PURE__ */ new Set();
31121
- for (const block of composite.blocks) {
31122
- if (block.type.startsWith(COMPOSITE_PREFIX)) {
31123
- const refId = block.type.slice(COMPOSITE_PREFIX.length);
31124
- if (!registry2.has(refId)) {
31125
- missing.add(refId);
31126
- }
31127
- }
31128
- }
31129
- if (missing.size > 0) {
31130
- errors.push({
31131
- source: id,
31132
- error: `Unresolved composite reference(s): ${[...missing].map((m3) => `composite:${m3}`).join(", ")}`
31133
- });
30638
+ function resolveEdges(node, graph, target) {
30639
+ const seen = /* @__PURE__ */ new Set();
30640
+ const results = [];
30641
+ for (const edgeId of node.composesWith) {
30642
+ if (seen.has(edgeId)) continue;
30643
+ seen.add(edgeId);
30644
+ const edgeNode = graph.nodes.get(edgeId);
30645
+ if (edgeNode) results.push(layer0(edgeNode, graph, target));
30646
+ }
30647
+ return results;
30648
+ }
30649
+ function expandProps(node, target) {
30650
+ const facet = target === void 0 ? void 0 : node.facets[target];
30651
+ const props = {};
30652
+ if (facet) {
30653
+ for (const [name, prop] of Object.entries(facet.props)) {
30654
+ props[name] = toAgentProp(prop);
31134
30655
  }
31135
30656
  }
30657
+ return { expanded: true, props };
31136
30658
  }
31137
-
31138
- // ../ui/src/primitives/typeahead.ts
31139
- function fuzzyScore(query, target) {
31140
- if (query.length === 0) return 1;
31141
- if (query.length > target.length) return 0;
31142
- const q = query.toLowerCase();
31143
- const t = target.toLowerCase();
31144
- let score = 0;
31145
- let qi = 0;
31146
- let prevMatchIndex = -2;
31147
- for (let ti = 0; ti < t.length && qi < q.length; ti++) {
31148
- if (t[ti] === q[qi]) {
31149
- score += 1;
31150
- if (ti === prevMatchIndex + 1) {
31151
- score += 2;
31152
- }
31153
- if (ti === 0 || t[ti - 1] === " " || t[ti - 1] === "-") {
31154
- score += 3;
31155
- }
31156
- prevMatchIndex = ti;
31157
- qi++;
30659
+ function expandNode(node, target) {
30660
+ const facet = target === void 0 ? void 0 : node.facets[target];
30661
+ const props = {};
30662
+ if (facet) {
30663
+ for (const [name, prop] of Object.entries(facet.props)) {
30664
+ props[name] = toAgentProp(prop);
31158
30665
  }
31159
30666
  }
31160
- return qi === q.length ? score : 0;
30667
+ const result = {
30668
+ id: node.id,
30669
+ kind: node.kind,
30670
+ intel: node.intel,
30671
+ props
30672
+ };
30673
+ if (node.composesWith.length > 0) result.composesWith = node.composesWith;
30674
+ if (node.parts.length > 0) result.parts = node.parts;
30675
+ if (facet) {
30676
+ result.snippet = facet.snippet;
30677
+ if (facet.slots !== void 0) result.slots = facet.slots;
30678
+ if (facet.events !== void 0) result.events = facet.events;
30679
+ }
30680
+ return result;
31161
30681
  }
31162
30682
 
31163
- // ../composites/src/registry.ts
31164
- var composites = /* @__PURE__ */ new Map();
31165
- function register3(composite) {
31166
- const { id } = composite.manifest;
31167
- if (composites.has(id)) {
31168
- throw new Error(`Composite "${id}" is already registered`);
30683
+ // src/mcp/intent.ts
30684
+ var INTENT_TAGS = {
30685
+ modal: ["attention", "blocking", "above-all", "focus-trap"],
30686
+ alert: ["attention", "inline", "passive"],
30687
+ tooltip: ["hint", "passive", "inline", "hover"]
30688
+ };
30689
+ var INTENT_AXES = [
30690
+ {
30691
+ tag: "above-all",
30692
+ keywords: [
30693
+ "above everything",
30694
+ "above all",
30695
+ "on top of everything",
30696
+ "sits on top",
30697
+ "on top",
30698
+ "over everything",
30699
+ "over all",
30700
+ "topmost",
30701
+ "highest layer"
30702
+ ]
30703
+ },
30704
+ {
30705
+ tag: "blocking",
30706
+ keywords: [
30707
+ "above everything",
30708
+ "on top of everything",
30709
+ "over everything",
30710
+ "block",
30711
+ "blocking",
30712
+ "must dismiss",
30713
+ "interrupt",
30714
+ "take over",
30715
+ "stops everything",
30716
+ "requires a response",
30717
+ "can't ignore",
30718
+ "cannot ignore"
30719
+ ]
30720
+ },
30721
+ {
30722
+ tag: "inline",
30723
+ keywords: ["inline", "in place", "in the flow", "within the page", "non-blocking"]
30724
+ },
30725
+ {
30726
+ tag: "passive",
30727
+ keywords: ["passive", "non-intrusive", "subtle", "quietly", "in the background"]
30728
+ },
30729
+ {
30730
+ tag: "attention",
30731
+ keywords: ["attention", "notice", "get noticed", "important message"]
30732
+ },
30733
+ {
30734
+ tag: "hint",
30735
+ keywords: ["hint", "a tip", "explain", "extra info", "more info"]
30736
+ },
30737
+ {
30738
+ tag: "hover",
30739
+ keywords: ["hover", "on hover", "mouse over"]
31169
30740
  }
31170
- composites.set(id, composite);
31171
- }
31172
- function getAll2() {
31173
- return Array.from(composites.values());
30741
+ ];
30742
+ function isNaturalLanguageQuery(input) {
30743
+ return input.includes(" ");
31174
30744
  }
31175
- function getByCategory(category) {
31176
- return getAll2().filter((c4) => c4.manifest.category === category);
30745
+ function matchIntent(query, graph) {
30746
+ const normalized = query.toLowerCase();
30747
+ const matchedTags = collectMatchedTags(normalized);
30748
+ if (matchedTags.size === 0) return noMatch();
30749
+ const winner = selectUse(matchedTags, graph);
30750
+ if (winner === void 0) return noMatch();
30751
+ const winningTags = winner.tags.filter((tag) => matchedTags.has(tag));
30752
+ const decisive = new Set(winningTags);
30753
+ const nearMiss = selectNearMiss(winner, decisive, matchedTags, graph);
30754
+ if (nearMiss === void 0) return noMatch();
30755
+ const use = describeNode(winner.id, graph);
30756
+ const not = describeNode(nearMiss.id, graph);
30757
+ if (use === void 0 || not === void 0) return noMatch();
30758
+ const divergingTags = nearMiss.tags.filter((tag) => !winner.tags.includes(tag));
30759
+ const because = `${winner.id} is ${winningTags.join("/")}; ${nearMiss.id} is ${divergingTags.join("/")}`;
30760
+ return { use, not, because };
31177
30761
  }
31178
- function search(query) {
31179
- if (query.length === 0) {
31180
- return getAll2();
30762
+ function collectMatchedTags(normalizedQuery) {
30763
+ const matched = /* @__PURE__ */ new Set();
30764
+ for (const axis of INTENT_AXES) {
30765
+ for (const keyword of axis.keywords) {
30766
+ if (normalizedQuery.includes(keyword)) {
30767
+ matched.add(axis.tag);
30768
+ break;
30769
+ }
30770
+ }
31181
30771
  }
31182
- return getAll2().map((c4) => {
31183
- const { name, keywords } = c4.manifest;
31184
- const score = Math.max(fuzzyScore(query, name), ...keywords.map((k) => fuzzyScore(query, k)));
31185
- return { c: c4, score };
31186
- }).filter((entry) => entry.score > 0).sort((a2, b2) => b2.score - a2.score).map((entry) => entry.c);
30772
+ return matched;
31187
30773
  }
31188
-
31189
- // ../composites/src/to-mdx.ts
31190
- var import_escape_html = __toESM(require_escape_html(), 1);
31191
-
31192
- // ../composites/src/discovery-node.ts
31193
- import { readdir, readFile as readFile5 } from "fs/promises";
31194
- import { join as join9 } from "path";
31195
- var COMPOSITE_SUFFIX = ".composite.json";
31196
- async function readDir(directory) {
31197
- const entries = [];
31198
- let dirents;
31199
- try {
31200
- dirents = await readdir(directory, { withFileTypes: true });
31201
- } catch {
31202
- return entries;
31203
- }
31204
- for (const dirent of dirents) {
31205
- const fullPath = join9(directory, dirent.name);
31206
- if (dirent.isDirectory()) {
31207
- entries.push(...await readDir(fullPath));
31208
- continue;
31209
- }
31210
- if (dirent.isFile() && dirent.name.endsWith(COMPOSITE_SUFFIX)) {
31211
- const raw = await readFile5(fullPath, "utf-8");
31212
- entries.push({ raw, source: fullPath });
30774
+ function selectUse(matchedTags, graph) {
30775
+ let best;
30776
+ let bestScore = 0;
30777
+ for (const [id, tags] of Object.entries(INTENT_TAGS)) {
30778
+ if (!graph.nodes.has(id)) continue;
30779
+ const score = tags.reduce((sum, tag) => matchedTags.has(tag) ? sum + 1 : sum, 0);
30780
+ if (score > bestScore) {
30781
+ bestScore = score;
30782
+ best = { id, tags };
31213
30783
  }
31214
30784
  }
31215
- return entries;
30785
+ return best;
31216
30786
  }
31217
- function nodeFsAdapter(...directories) {
31218
- return async () => {
31219
- const entries = [];
31220
- for (const directory of directories) {
31221
- entries.push(...await readDir(directory));
30787
+ function selectNearMiss(use, decisive, matchedTags, graph) {
30788
+ let best;
30789
+ let bestOverlap = 0;
30790
+ for (const [id, tags] of Object.entries(INTENT_TAGS)) {
30791
+ if (id === use.id || !graph.nodes.has(id)) continue;
30792
+ if (tags.some((tag) => decisive.has(tag))) continue;
30793
+ const overlap = tags.filter((tag) => use.tags.includes(tag));
30794
+ const sharesOutsideMatched = overlap.some((tag) => !matchedTags.has(tag));
30795
+ if (!sharesOutsideMatched) continue;
30796
+ if (overlap.length > bestOverlap) {
30797
+ bestOverlap = overlap.length;
30798
+ best = { id, tags };
31222
30799
  }
31223
- return entries;
31224
- };
30800
+ }
30801
+ return best;
31225
30802
  }
31226
- async function discoverFromDirs(...directories) {
31227
- const adapter = nodeFsAdapter(...directories);
31228
- return discoverComposites(await adapter());
30803
+ function describeNode(id, graph) {
30804
+ const result = describe(id, graph);
30805
+ if (result !== null && !Array.isArray(result) && "children" in result) return result;
30806
+ return void 0;
31229
30807
  }
31230
-
31231
- // src/utils/workspaces.ts
31232
- import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
31233
- import { basename, dirname as dirname3, join as join11, resolve as resolve4 } from "path";
31234
-
31235
- // src/utils/discover.ts
31236
- import { existsSync as existsSync5 } from "fs";
31237
- import { dirname as dirname2, join as join10, resolve as resolve3 } from "path";
31238
- function discoverProjectRoot(startDir) {
31239
- let current = resolve3(startDir);
31240
- for (; ; ) {
31241
- const configPath2 = join10(current, ".rafters", "config.rafters.json");
31242
- if (existsSync5(configPath2)) {
31243
- return current;
31244
- }
31245
- const parent = dirname2(current);
31246
- if (parent === current) {
31247
- return null;
31248
- }
31249
- current = parent;
31250
- }
31251
- }
31252
-
31253
- // src/utils/workspaces.ts
31254
- function findMonorepoRoot(startDir, boundary) {
31255
- let current = resolve4(startDir);
31256
- const stopAt = boundary ? resolve4(boundary) : null;
31257
- for (; ; ) {
31258
- const pnpmWorkspace = join11(current, "pnpm-workspace.yaml");
31259
- if (existsSync6(pnpmWorkspace)) {
31260
- const patterns = parsePnpmWorkspaceYaml(readFileSync3(pnpmWorkspace, "utf-8"));
31261
- if (patterns.length > 0) {
31262
- return { root: current, patterns };
31263
- }
31264
- }
31265
- const pkgJson = join11(current, "package.json");
31266
- if (existsSync6(pkgJson)) {
31267
- const patterns = parsePackageJsonWorkspaces(readFileSync3(pkgJson, "utf-8"));
31268
- if (patterns.length > 0) {
31269
- return { root: current, patterns };
31270
- }
31271
- }
31272
- if (stopAt && current === stopAt) return null;
31273
- const parent = dirname3(current);
31274
- if (parent === current) return null;
31275
- current = parent;
31276
- }
31277
- }
31278
- function parsePnpmWorkspaceYaml(content) {
31279
- const lines = content.split("\n");
31280
- const patterns = [];
31281
- let inPackages = false;
31282
- for (const rawLine of lines) {
31283
- const line = rawLine.replace(/#.*$/, "").trimEnd();
31284
- if (!line.trim()) continue;
31285
- if (/^packages\s*:/.test(line)) {
31286
- inPackages = true;
31287
- continue;
31288
- }
31289
- if (inPackages) {
31290
- const itemMatch = line.match(/^\s*-\s*(?:["']?)([^"'\s]+)(?:["']?)\s*$/);
31291
- if (itemMatch?.[1]) {
31292
- patterns.push(itemMatch[1]);
31293
- continue;
31294
- }
31295
- if (!/^\s/.test(line)) {
31296
- inPackages = false;
31297
- }
31298
- }
31299
- }
31300
- return patterns;
31301
- }
31302
- function parsePackageJsonWorkspaces(content) {
31303
- try {
31304
- const pkg = JSON.parse(content);
31305
- const ws = pkg.workspaces;
31306
- if (Array.isArray(ws)) {
31307
- return ws.filter((p2) => typeof p2 === "string");
31308
- }
31309
- if (ws && typeof ws === "object" && Array.isArray(ws.packages)) {
31310
- return ws.packages.filter((p2) => typeof p2 === "string");
31311
- }
31312
- return [];
31313
- } catch {
31314
- return [];
31315
- }
31316
- }
31317
- function expandPattern(monorepoRoot, pattern) {
31318
- const trimmed = pattern.replace(/\/+$/, "");
31319
- if (trimmed.endsWith("/*")) {
31320
- const parentRel = trimmed.slice(0, -2);
31321
- const parent = join11(monorepoRoot, parentRel);
31322
- if (!existsSync6(parent)) return [];
31323
- return readdirSync3(parent).map((entry) => join11(parent, entry)).filter((path) => {
31324
- try {
31325
- return statSync2(path).isDirectory();
31326
- } catch {
31327
- return false;
31328
- }
31329
- });
31330
- }
31331
- const literal2 = join11(monorepoRoot, trimmed);
31332
- if (existsSync6(literal2)) {
31333
- try {
31334
- if (statSync2(literal2).isDirectory()) return [literal2];
31335
- } catch {
31336
- return [];
31337
- }
31338
- }
31339
- return [];
31340
- }
31341
- function discoverWorkspaces(startDir = process.cwd(), options = {}) {
31342
- const layout = findMonorepoRoot(startDir, options.boundary);
31343
- if (!layout) {
31344
- const single = discoverProjectRoot(startDir);
31345
- if (!single) return [];
31346
- if (options.boundary && !single.startsWith(resolve4(options.boundary))) return [];
31347
- return [{ name: basename(single), root: single }];
31348
- }
31349
- const seen = /* @__PURE__ */ new Set();
31350
- const workspaces = [];
31351
- for (const pattern of layout.patterns) {
31352
- for (const dir of expandPattern(layout.root, pattern)) {
31353
- if (seen.has(dir)) continue;
31354
- seen.add(dir);
31355
- const config2 = join11(dir, ".rafters", "config.rafters.json");
31356
- if (existsSync6(config2)) {
31357
- workspaces.push({ name: basename(dir), root: dir });
31358
- }
31359
- }
31360
- }
31361
- const rootConfig = join11(layout.root, ".rafters", "config.rafters.json");
31362
- if (existsSync6(rootConfig) && !seen.has(layout.root)) {
31363
- workspaces.unshift({ name: basename(layout.root), root: layout.root });
31364
- }
31365
- return workspaces;
31366
- }
31367
- function pickDefaultWorkspace(workspaces, startDir = process.cwd()) {
31368
- if (workspaces.length === 0) return null;
31369
- const cwd = resolve4(startDir);
31370
- const containing = workspaces.find((ws) => cwd === ws.root || cwd.startsWith(`${ws.root}/`));
31371
- if (containing) return containing;
31372
- if (workspaces.length === 1) return workspaces[0] ?? null;
31373
- return null;
31374
- }
31375
- function resolveWorkspace(workspaces, defaultWorkspace, name) {
31376
- if (name) {
31377
- return workspaces.find((ws) => ws.name === name) ?? null;
31378
- }
31379
- return defaultWorkspace;
31380
- }
31381
-
31382
- // src/mcp/graph.ts
31383
- var EXPAND_OP = "*";
31384
- var PROBE_OP = "?";
31385
- function assembleGraph(items) {
31386
- const nodes = /* @__PURE__ */ new Map();
31387
- for (const item of items) {
31388
- const kind = kindOf(item.type);
31389
- if (kind === null) continue;
31390
- const src = item.intelligence;
31391
- const intel = {
31392
- dos: src?.usagePatterns?.dos ?? [],
31393
- nevers: src?.usagePatterns?.nevers ?? []
31394
- };
31395
- if (src?.cognitiveLoad !== void 0) intel.cognitiveLoad = src.cognitiveLoad;
31396
- if (src?.semanticMeaning !== void 0) intel.semanticMeaning = src.semanticMeaning;
31397
- if (src?.accessibility !== void 0) intel.accessibility = src.accessibility;
31398
- if (src?.attentionEconomics !== void 0) intel.attentionEconomics = src.attentionEconomics;
31399
- if (src?.trustBuilding !== void 0) intel.trustBuilding = src.trustBuilding;
31400
- const gn = {
31401
- id: item.name,
31402
- kind,
31403
- intel,
31404
- facets: item.facets ?? {},
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);
31439
- }
31440
- for (const node of nodes.values()) {
31441
- for (const target of node.composesWith) {
31442
- if (!nodes.has(target)) {
31443
- throw new Error(`graph: node "${node.id}" composesWith unknown id "${target}"`);
31444
- }
31445
- }
31446
- }
31447
- return { nodes };
31448
- }
31449
- function kindOf(type2) {
31450
- if (type2 === "composite") return "composite";
31451
- if (type2 === "ui" || type2 === "primitive") return "component";
31452
- return null;
31453
- }
31454
- function describe(addr, graph, target) {
31455
- const parts = addr === "" ? [] : addr.split(".");
31456
- if (parts.length === 0) {
31457
- return {
31458
- kinds: [
31459
- { addr: "components", type: "edge" },
31460
- { addr: "composites", type: "edge" }
31461
- ],
31462
- nodeCount: graph.nodes.size
31463
- };
31464
- }
31465
- const [head, ...rest] = parts;
31466
- if (head === void 0) return { error: `cannot resolve: ${addr}` };
31467
- if (head === "components" || head === "composites") {
31468
- if (rest.length > 0) return { error: `cannot expand: ${addr}` };
31469
- const kind = head === "components" ? "component" : "composite";
31470
- const roster = [];
31471
- for (const node2 of graph.nodes.values()) {
31472
- if (node2.kind === kind) roster.push({ id: node2.id });
31473
- }
31474
- return roster;
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
- }
31485
- const node = graph.nodes.get(head);
31486
- if (!node) return { error: `unknown node: ${head}` };
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
- }
31494
- if (rest.length === 1 && rest[0] === "composesWith") {
31495
- return resolveEdges(node, graph, target);
31496
- }
31497
- if (rest[0] === "props") {
31498
- const facet = target === void 0 ? void 0 : node.facets[target];
31499
- const propName = rest[1];
31500
- const prop = facet && propName !== void 0 ? facet.props[propName] : void 0;
31501
- if (!prop) return { error: `cannot resolve: ${addr}` };
31502
- if (rest.length === 2) return toAgentProp(prop);
31503
- return { error: `cannot resolve: ${addr}` };
31504
- }
31505
- return { error: `cannot resolve: ${addr}` };
31506
- }
31507
- function toAgentProp(prop) {
31508
- if (prop.type === "grammar") {
31509
- const { vocab: _vocab, ...agentView } = prop;
31510
- return agentView;
31511
- }
31512
- return prop;
31513
- }
31514
- function layer0(node, graph, target) {
31515
- const facet = target === void 0 ? void 0 : node.facets[target];
31516
- const children = [];
31517
- if (facet) {
31518
- for (const [name, prop] of Object.entries(facet.props)) {
31519
- const child = { addr: `${node.id}.props.${name}`, type: prop.type };
31520
- if (prop.type === "deprecated") child.deprecatedFor = prop.deprecatedFor;
31521
- children.push(child);
31522
- }
31523
- }
31524
- if (node.composesWith.length > 0) {
31525
- children.push({ addr: `${node.id}.composesWith`, type: "edge" });
31526
- }
31527
- for (const partId of node.parts) {
31528
- children.push({ addr: partId, type: "part" });
31529
- }
31530
- const result = { id: node.id, kind: node.kind, intel: node.intel, children };
31531
- if (facet) {
31532
- result.snippet = facet.snippet;
31533
- if (facet.slots !== void 0) result.slots = facet.slots;
31534
- if (facet.events !== void 0) result.events = facet.events;
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
- }
31544
- return result;
31545
- }
31546
- function resolveEdges(node, graph, target) {
31547
- const seen = /* @__PURE__ */ new Set();
31548
- const results = [];
31549
- for (const edgeId of node.composesWith) {
31550
- if (seen.has(edgeId)) continue;
31551
- seen.add(edgeId);
31552
- const edgeNode = graph.nodes.get(edgeId);
31553
- if (edgeNode) results.push(layer0(edgeNode, graph, target));
31554
- }
31555
- return results;
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
- }
31590
-
31591
- // src/mcp/intent.ts
31592
- var INTENT_TAGS = {
31593
- modal: ["attention", "blocking", "above-all", "focus-trap"],
31594
- alert: ["attention", "inline", "passive"],
31595
- tooltip: ["hint", "passive", "inline", "hover"]
31596
- };
31597
- var INTENT_AXES = [
31598
- {
31599
- tag: "above-all",
31600
- keywords: [
31601
- "above everything",
31602
- "above all",
31603
- "on top of everything",
31604
- "sits on top",
31605
- "on top",
31606
- "over everything",
31607
- "over all",
31608
- "topmost",
31609
- "highest layer"
31610
- ]
31611
- },
31612
- {
31613
- tag: "blocking",
31614
- keywords: [
31615
- "above everything",
31616
- "on top of everything",
31617
- "over everything",
31618
- "block",
31619
- "blocking",
31620
- "must dismiss",
31621
- "interrupt",
31622
- "take over",
31623
- "stops everything",
31624
- "requires a response",
31625
- "can't ignore",
31626
- "cannot ignore"
31627
- ]
31628
- },
31629
- {
31630
- tag: "inline",
31631
- keywords: ["inline", "in place", "in the flow", "within the page", "non-blocking"]
31632
- },
31633
- {
31634
- tag: "passive",
31635
- keywords: ["passive", "non-intrusive", "subtle", "quietly", "in the background"]
31636
- },
31637
- {
31638
- tag: "attention",
31639
- keywords: ["attention", "notice", "get noticed", "important message"]
31640
- },
31641
- {
31642
- tag: "hint",
31643
- keywords: ["hint", "a tip", "explain", "extra info", "more info"]
31644
- },
31645
- {
31646
- tag: "hover",
31647
- keywords: ["hover", "on hover", "mouse over"]
31648
- }
31649
- ];
31650
- function isNaturalLanguageQuery(input) {
31651
- return input.includes(" ");
31652
- }
31653
- function matchIntent(query, graph) {
31654
- const normalized = query.toLowerCase();
31655
- const matchedTags = collectMatchedTags(normalized);
31656
- if (matchedTags.size === 0) return noMatch();
31657
- const winner = selectUse(matchedTags, graph);
31658
- if (winner === void 0) return noMatch();
31659
- const winningTags = winner.tags.filter((tag) => matchedTags.has(tag));
31660
- const decisive = new Set(winningTags);
31661
- const nearMiss = selectNearMiss(winner, decisive, matchedTags, graph);
31662
- if (nearMiss === void 0) return noMatch();
31663
- const use = describeNode(winner.id, graph);
31664
- const not = describeNode(nearMiss.id, graph);
31665
- if (use === void 0 || not === void 0) return noMatch();
31666
- const divergingTags = nearMiss.tags.filter((tag) => !winner.tags.includes(tag));
31667
- const because = `${winner.id} is ${winningTags.join("/")}; ${nearMiss.id} is ${divergingTags.join("/")}`;
31668
- return { use, not, because };
31669
- }
31670
- function collectMatchedTags(normalizedQuery) {
31671
- const matched = /* @__PURE__ */ new Set();
31672
- for (const axis of INTENT_AXES) {
31673
- for (const keyword of axis.keywords) {
31674
- if (normalizedQuery.includes(keyword)) {
31675
- matched.add(axis.tag);
31676
- break;
31677
- }
31678
- }
31679
- }
31680
- return matched;
31681
- }
31682
- function selectUse(matchedTags, graph) {
31683
- let best;
31684
- let bestScore = 0;
31685
- for (const [id, tags] of Object.entries(INTENT_TAGS)) {
31686
- if (!graph.nodes.has(id)) continue;
31687
- const score = tags.reduce((sum, tag) => matchedTags.has(tag) ? sum + 1 : sum, 0);
31688
- if (score > bestScore) {
31689
- bestScore = score;
31690
- best = { id, tags };
31691
- }
31692
- }
31693
- return best;
31694
- }
31695
- function selectNearMiss(use, decisive, matchedTags, graph) {
31696
- let best;
31697
- let bestOverlap = 0;
31698
- for (const [id, tags] of Object.entries(INTENT_TAGS)) {
31699
- if (id === use.id || !graph.nodes.has(id)) continue;
31700
- if (tags.some((tag) => decisive.has(tag))) continue;
31701
- const overlap = tags.filter((tag) => use.tags.includes(tag));
31702
- const sharesOutsideMatched = overlap.some((tag) => !matchedTags.has(tag));
31703
- if (!sharesOutsideMatched) continue;
31704
- if (overlap.length > bestOverlap) {
31705
- bestOverlap = overlap.length;
31706
- best = { id, tags };
31707
- }
31708
- }
31709
- return best;
31710
- }
31711
- function describeNode(id, graph) {
31712
- const result = describe(id, graph);
31713
- if (result !== null && !Array.isArray(result) && "children" in result) return result;
31714
- return void 0;
31715
- }
31716
- function noMatch() {
31717
- return { note: "no route matched; describe(components) or describe(composites) to browse" };
30808
+ function noMatch() {
30809
+ return { note: "no route matched; describe(components) or describe(composites) to browse" };
31718
30810
  }
31719
30811
 
31720
30812
  // src/mcp/overlay.ts
@@ -32130,7 +31222,7 @@ var RaftersToolHandler = class {
32130
31222
  async ensureCompositesLoaded(workspace) {
32131
31223
  if (!this.builtInCompositesLoaded) {
32132
31224
  await this.loadCompositesFromDirs(
32133
- join12(process.cwd(), "node_modules/@rafters/composites/src/typography")
31225
+ join11(process.cwd(), "node_modules/@rafters/composites/src/typography")
32134
31226
  );
32135
31227
  this.builtInCompositesLoaded = true;
32136
31228
  }
@@ -32148,7 +31240,7 @@ var RaftersToolHandler = class {
32148
31240
  const paths = getRaftersPaths(workspaceRoot);
32149
31241
  try {
32150
31242
  return migrateConfig(
32151
- JSON.parse(await readFile6(paths.config, "utf-8"))
31243
+ JSON.parse(await readFile5(paths.config, "utf-8"))
32152
31244
  );
32153
31245
  } catch {
32154
31246
  return null;
@@ -32157,7 +31249,7 @@ var RaftersToolHandler = class {
32157
31249
  /** Persist a workspace's config back to `.rafters/config.rafters.json`. */
32158
31250
  async writeConfig(workspaceRoot, config2) {
32159
31251
  const paths = getRaftersPaths(workspaceRoot);
32160
- await writeFile4(paths.config, `${JSON.stringify(config2, null, 2)}
31252
+ await writeFile3(paths.config, `${JSON.stringify(config2, null, 2)}
32161
31253
  `);
32162
31254
  }
32163
31255
  /**
@@ -32352,123 +31444,1137 @@ var RaftersToolHandler = class {
32352
31444
  const paths = getRaftersPaths(workspaceRoot);
32353
31445
  const config2 = await this.readConfig(workspaceRoot);
32354
31446
  if (!config2?.compositesPath) {
32355
- return [join12(paths.root, "composites")];
31447
+ return [join11(paths.root, "composites")];
31448
+ }
31449
+ return resolveReadSet(config2.compositesPath, workspaceRoot);
31450
+ }
31451
+ async handleComposite(args) {
31452
+ const { id, query, category, workspace } = args;
31453
+ if (id) {
31454
+ return this.describeById(id, workspace);
31455
+ }
31456
+ const resolved = this.resolve(workspace);
31457
+ if (workspace && !resolved) {
31458
+ return this.workspaceRequiredError();
31459
+ }
31460
+ await this.ensureCompositesLoaded(resolved);
31461
+ let composites2;
31462
+ if (query) {
31463
+ composites2 = search(query);
31464
+ } else if (category) {
31465
+ composites2 = getByCategory(category);
31466
+ } else {
31467
+ composites2 = getAll2();
31468
+ }
31469
+ const result = composites2.map((c4) => ({
31470
+ id: c4.manifest.id,
31471
+ name: c4.manifest.name,
31472
+ category: c4.manifest.category,
31473
+ description: c4.manifest.description,
31474
+ cognitiveLoad: c4.manifest.cognitiveLoad,
31475
+ solves: c4.manifest.solves,
31476
+ appliesWhen: c4.manifest.appliesWhen,
31477
+ usagePatterns: c4.manifest.usagePatterns,
31478
+ input: c4.input,
31479
+ output: c4.output,
31480
+ blockCount: c4.blocks.length,
31481
+ // Surface which blocks carry which validation rules. Rules compile to
31482
+ // native HTML5 constraint attributes at build, so agents need to see
31483
+ // them to reason about a composite's behavior. Only blocks with rules
31484
+ // are listed -- the array is empty when nothing is constrained.
31485
+ blockRules: c4.blocks.filter((b2) => b2.rules && b2.rules.length > 0).map((b2) => ({ id: b2.id, type: b2.type, rules: b2.rules }))
31486
+ }));
31487
+ return this.jsonResult({ composites: result, deprecated: DEPRECATED_MSG });
31488
+ }
31489
+ async handlePattern(args) {
31490
+ const { solves, query, workspace } = args;
31491
+ const resolved = this.resolve(workspace);
31492
+ if (workspace && !resolved) {
31493
+ return this.workspaceRequiredError();
31494
+ }
31495
+ await this.ensureCompositesLoaded(resolved);
31496
+ let composites2;
31497
+ if (solves) {
31498
+ const all = getAll2();
31499
+ const solvesLower = solves.toLowerCase();
31500
+ composites2 = all.filter(
31501
+ (c4) => c4.manifest.solves?.toLowerCase().includes(solvesLower) || c4.manifest.appliesWhen?.some((a2) => a2.toLowerCase().includes(solvesLower))
31502
+ );
31503
+ } else if (query) {
31504
+ composites2 = search(query);
31505
+ } else {
31506
+ composites2 = getAll2().filter((c4) => c4.manifest.usagePatterns);
31507
+ }
31508
+ if (composites2.length === 0) {
31509
+ const all = getAll2();
31510
+ const available = all.filter((c4) => c4.manifest.solves || c4.manifest.usagePatterns).map((c4) => ({
31511
+ id: c4.manifest.id,
31512
+ solves: c4.manifest.solves
31513
+ }));
31514
+ return this.errorResult("No patterns found matching query", {
31515
+ available,
31516
+ deprecated: DEPRECATED_MSG
31517
+ });
31518
+ }
31519
+ const patterns = composites2.map((c4) => ({
31520
+ id: c4.manifest.id,
31521
+ name: c4.manifest.name,
31522
+ solves: c4.manifest.solves,
31523
+ appliesWhen: c4.manifest.appliesWhen,
31524
+ cognitiveLoad: c4.manifest.cognitiveLoad,
31525
+ usagePatterns: c4.manifest.usagePatterns
31526
+ }));
31527
+ return this.jsonResult({ patterns, deprecated: DEPRECATED_MSG });
31528
+ }
31529
+ /**
31530
+ * DEPRECATED alias for `rafters_describe`. A component resolves by id through
31531
+ * the overlay exactly as `describe(<id>)` does (#2072's resolver has no
31532
+ * separate component/composite address form -- `describe(<id>)` resolves either
31533
+ * kind), so this is a direct, lossless forward with the deprecated marker added.
31534
+ */
31535
+ async handleComponent(componentName, workspaceName) {
31536
+ return this.describeById(componentName, workspaceName);
31537
+ }
31538
+ /**
31539
+ * Shared by the deprecated `rafters_component` and `rafters_composite({id})`
31540
+ * paths: resolve one id through the overlay, stamp the deprecated marker.
31541
+ */
31542
+ async describeById(id, workspaceName) {
31543
+ const resolved = this.resolve(workspaceName);
31544
+ if (workspaceName && !resolved) {
31545
+ return this.workspaceRequiredError();
31546
+ }
31547
+ let graph;
31548
+ try {
31549
+ graph = await this.ensureGraph(resolved);
31550
+ } catch (err) {
31551
+ const message = err instanceof Error ? err.message : "Unknown error";
31552
+ return this.errorResult(`failed to build intel graph: ${message}`, {
31553
+ deprecated: DEPRECATED_MSG
31554
+ });
31555
+ }
31556
+ const ctx = await this.overlayContext(resolved);
31557
+ if ("configError" in ctx) {
31558
+ return this.errorResult(ctx.configError, { deprecated: DEPRECATED_MSG });
31559
+ }
31560
+ const out = describeWithOverlay(id, graph, ctx);
31561
+ return this.jsonResult(this.withDeprecated(out));
31562
+ }
31563
+ };
31564
+
31565
+ // src/mcp/agent-contract.ts
31566
+ function liveTools() {
31567
+ return TOOL_DEFINITIONS.filter((t) => !t.description.startsWith("[DEPRECATED"));
31568
+ }
31569
+ function generateAgentContract() {
31570
+ const roster = liveTools().map((t) => `- \`${t.name}\`: ${t.description}`).join("\n");
31571
+ return `# Rafters agent contract
31572
+
31573
+ You are working in a Rafters-powered frontend project. The design system owns
31574
+ every visual decision -- color, spacing, size, weight, radius, shadow, hierarchy,
31575
+ and layout rhythm are already decided. Your job is to assemble the pre-made piece
31576
+ that fits the content and intent, never to author a visual choice. A write-time
31577
+ hook denies any edit that hand-authors a visual decision.
31578
+
31579
+ ## Assembly model -- follow this order every time
31580
+
31581
+ 1. Query the design system before writing any UI. You do not know this system
31582
+ from training; read it from the MCP tools below, every time. Read the project's
31583
+ recorded intent from \`.rafters/config.rafters.json\` first.
31584
+ 2. Prefer a composite or pattern that solves the whole problem (authentication,
31585
+ data entry, navigation, pricing) over assembling lone components by hand.
31586
+ Render its blocks verbatim -- the manifest is the decision.
31587
+ 3. Drop to a single component only for a lone affordance no composite covers.
31588
+ 4. Author a custom component only when the served design system genuinely does not
31589
+ ship the affordance -- the last resort, not the first.
31590
+ 5. Prose and content (headings, paragraphs, lists, quotes) render as bare native
31591
+ HTML inside \`<Container as="article">\` -- no classes, no imports.
31592
+
31593
+ ## Never
31594
+
31595
+ - Never write flex, grid, gap, padding, or margin utilities directly. Container
31596
+ and Grid own all layout.
31597
+ - Never use raw \`<h1>\`/\`<p>\`/\`<span>\` with class attributes for styled text.
31598
+ Use the typography components with token props (size, weight, color).
31599
+ - Never use hex colors, arbitrary values, or \`var()\`. Use semantic color tokens
31600
+ (bg-primary, text-accent).
31601
+ - Never pass \`class\` or \`className\` to a served component. Use token props for
31602
+ overrides.
31603
+
31604
+ If you do not know what to build or how it should look, get a design direction
31605
+ before writing code. The pre-edit hook WILL block violations. Consult the design
31606
+ system BEFORE writing, not after.
31607
+
31608
+ ## MCP tools
31609
+
31610
+ Query these before writing UI:
31611
+
31612
+ ${roster}
31613
+
31614
+ ## Running the rafters MCP server
31615
+
31616
+ Non-Claude-Code hosts wire the server manually. Invoke the \`rafters\` binary that
31617
+ is installed in your project, through your package manager:
31618
+
31619
+ - \`pnpm exec rafters mcp\`
31620
+ - \`npx rafters mcp\`
31621
+ - \`yarn rafters mcp\`
31622
+
31623
+ Run the copy pinned in your own lockfile. Do NOT use \`pnpx rafters@latest\`,
31624
+ \`npx rafters@latest\`, \`pnpm dlx rafters\`, or any \`@latest\` pin: these resolve
31625
+ outside your lockfile and can drift from the version your project installed --
31626
+ the exact staleness that strands you on an old rafters after a new one ships.
31627
+ `;
31628
+ }
31629
+
31630
+ // src/commands/agents.ts
31631
+ var START_MARKER = "<!-- rafters:agent-contract:start -->";
31632
+ var END_MARKER = "<!-- rafters:agent-contract:end -->";
31633
+ function mergeAgentContract(existing, contract) {
31634
+ const block = `${START_MARKER}
31635
+ ${contract}
31636
+ ${END_MARKER}`;
31637
+ if (existing === null) {
31638
+ return `${block}
31639
+ `;
31640
+ }
31641
+ const startIdx = existing.indexOf(START_MARKER);
31642
+ const endIdx = existing.indexOf(END_MARKER);
31643
+ if (startIdx !== -1 && endIdx !== -1 && startIdx < endIdx) {
31644
+ const before = existing.slice(0, startIdx);
31645
+ const after = existing.slice(endIdx + END_MARKER.length);
31646
+ return `${before}${block}${after}`;
31647
+ }
31648
+ return `${existing.trimEnd()}
31649
+
31650
+ ${block}
31651
+ `;
31652
+ }
31653
+ async function agents() {
31654
+ const cwd = process.cwd();
31655
+ const paths = getRaftersPaths(cwd);
31656
+ if (!existsSync6(paths.config)) {
31657
+ throw new Error(
31658
+ `No rafters project found: ${paths.config} is missing. Run \`rafters init\` first.`
31659
+ );
31660
+ }
31661
+ const agentsPath = join12(cwd, "AGENTS.md");
31662
+ const existing = existsSync6(agentsPath) ? await readFile6(agentsPath, "utf-8") : null;
31663
+ const merged = mergeAgentContract(existing, generateAgentContract());
31664
+ await writeFile4(agentsPath, merged);
31665
+ }
31666
+
31667
+ // src/commands/init.ts
31668
+ import { existsSync as existsSync7 } from "fs";
31669
+ import { copyFile, mkdir as mkdir3, readFile as readFile7, rm, writeFile as writeFile5 } from "fs/promises";
31670
+ import { createRequire } from "module";
31671
+ import { join as join13, relative as relative2 } from "path";
31672
+ import { checkbox, confirm, select } from "@inquirer/prompts";
31673
+ var REGISTRY_PLUGINS2 = [scalePlugin, contrastPlugin, statePlugin, invertPlugin];
31674
+ async function backupCss(cssPath) {
31675
+ const backupPath = cssPath.replace(/\.css$/, ".backup.css");
31676
+ await copyFile(cssPath, backupPath);
31677
+ return backupPath;
31678
+ }
31679
+ function cleanSourceCssBlocks(content) {
31680
+ function parseBlocks(src) {
31681
+ const blocks = [];
31682
+ let depth = 0;
31683
+ const headerStack = [];
31684
+ let segmentStart = 0;
31685
+ for (let i = 0; i < src.length; i++) {
31686
+ const ch = src.charAt(i);
31687
+ if (ch === "{") {
31688
+ headerStack.push(segmentStart);
31689
+ depth++;
31690
+ if (depth >= 1) {
31691
+ blocks.push({
31692
+ headerStart: segmentStart,
31693
+ bodyStart: i + 1,
31694
+ end: -1,
31695
+ header: src.slice(segmentStart, i).trim()
31696
+ });
31697
+ }
31698
+ segmentStart = i + 1;
31699
+ } else if (ch === "}") {
31700
+ depth--;
31701
+ const open = blocks.slice().reverse().find((b2) => b2.end === -1);
31702
+ if (open) open.end = i + 1;
31703
+ headerStack.pop();
31704
+ segmentStart = i + 1;
31705
+ } else if (ch === ";") {
31706
+ segmentStart = i + 1;
31707
+ }
31708
+ }
31709
+ return blocks.filter((b2) => b2.end !== -1);
31710
+ }
31711
+ function isContentEmpty(body) {
31712
+ return body.replace(/\/\*[\s\S]*?\*\//g, "").trim().length === 0;
31713
+ }
31714
+ let out = content;
31715
+ for (let pass = 0; pass < 10; pass++) {
31716
+ const blocks = parseBlocks(out);
31717
+ let edited = false;
31718
+ for (const block of blocks.sort((a2, b2) => b2.headerStart - a2.headerStart)) {
31719
+ const header = block.header;
31720
+ if (/^@theme\s+inline$/.test(header)) {
31721
+ out = out.slice(0, block.headerStart) + out.slice(block.end);
31722
+ edited = true;
31723
+ break;
31724
+ }
31725
+ if (header === ":root" || /(^|,)\s*:root\s*$/.test(header)) {
31726
+ const body = out.slice(block.bodyStart, block.end - 1);
31727
+ const cleanedBody = body.replace(/--[\w-]+\s*:[^;{}]*;?/g, "");
31728
+ if (isContentEmpty(cleanedBody)) {
31729
+ out = out.slice(0, block.headerStart) + out.slice(block.end);
31730
+ edited = true;
31731
+ break;
31732
+ }
31733
+ if (cleanedBody !== body) {
31734
+ out = out.slice(0, block.bodyStart) + cleanedBody + out.slice(block.end - 1);
31735
+ edited = true;
31736
+ break;
31737
+ }
31738
+ continue;
31739
+ }
31740
+ if (header.startsWith("@media") || header.startsWith("@supports")) {
31741
+ const body = out.slice(block.bodyStart, block.end - 1);
31742
+ if (isContentEmpty(body)) {
31743
+ out = out.slice(0, block.headerStart) + out.slice(block.end);
31744
+ edited = true;
31745
+ break;
31746
+ }
31747
+ }
31748
+ }
31749
+ if (!edited) break;
31750
+ }
31751
+ return out.replace(/[ \t]+$/gm, "").replace(/\n{3,}/g, "\n\n");
31752
+ }
31753
+ async function stripImportedDeclarations(cwd, cssPath, importedNames) {
31754
+ if (importedNames.length === 0) return;
31755
+ const fullPath = join13(cwd, cssPath);
31756
+ const content = await readFile7(fullPath, "utf-8");
31757
+ const pattern = new RegExp(importedNames.map((n2) => `^\\s*--${n2}[^;]*;\\s*$`).join("|"), "gm");
31758
+ const cleaned = cleanSourceCssBlocks(content.replace(pattern, ""));
31759
+ const collapsed = cleaned.replace(/\n{3,}/g, "\n\n");
31760
+ await writeFile5(fullPath, collapsed);
31761
+ }
31762
+ async function updateMainCss(cwd, cssPath, themePath) {
31763
+ const fullCssPath = join13(cwd, cssPath);
31764
+ const cssContent = await readFile7(fullCssPath, "utf-8");
31765
+ const cssDir = join13(cwd, cssPath, "..");
31766
+ const themeFullPath = join13(cwd, themePath);
31767
+ const relativeThemePath = relative2(cssDir, themeFullPath);
31768
+ if (cssContent.includes(".rafters/output/rafters.css")) {
31769
+ log({ event: "init:css_already_imported", cssPath });
31770
+ return;
31771
+ }
31772
+ await backupCss(fullCssPath);
31773
+ let newContent;
31774
+ if (cssContent.includes('@import "tailwindcss"')) {
31775
+ newContent = cssContent.replace('@import "tailwindcss";', `@import "${relativeThemePath}";`);
31776
+ } else if (cssContent.includes("@import 'tailwindcss'")) {
31777
+ newContent = cssContent.replace("@import 'tailwindcss';", `@import "${relativeThemePath}";`);
31778
+ } else {
31779
+ newContent = `@import "${relativeThemePath}";
31780
+
31781
+ ${cssContent}`;
31782
+ }
31783
+ await writeFile5(fullCssPath, newContent);
31784
+ log({
31785
+ event: "init:css_updated",
31786
+ cssPath,
31787
+ themePath: relativeThemePath
31788
+ });
31789
+ }
31790
+ function isInteractive() {
31791
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
31792
+ }
31793
+ function resolveSourceCssPath(cwd, framework, detectedFramework, projectCssPath, shadcnCssPath, emit) {
31794
+ if (shadcnCssPath) {
31795
+ if (existsSync7(join13(cwd, shadcnCssPath))) return shadcnCssPath;
31796
+ const fallback = framework === detectedFramework ? projectCssPath : findCssPath(cwd, framework);
31797
+ emit({
31798
+ event: "init:shadcn_css_missing",
31799
+ configuredPath: shadcnCssPath,
31800
+ fallbackPath: fallback,
31801
+ message: "components.json points at a CSS file that does not exist. Falling back to the framework canonical location."
31802
+ });
31803
+ return fallback;
31804
+ }
31805
+ return framework === detectedFramework ? projectCssPath : findCssPath(cwd, framework);
31806
+ }
31807
+ function slugifyFontName(name) {
31808
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
31809
+ }
31810
+ function isFontStackValue(value) {
31811
+ if (typeof value !== "string") return false;
31812
+ if (value.startsWith("var(")) return false;
31813
+ if (/[,'"]/.test(value)) return true;
31814
+ return /\b(sans-serif|serif|monospace|cursive|fantasy|system-ui|ui-(?:sans-serif|serif|monospace|rounded))\b/.test(
31815
+ value
31816
+ );
31817
+ }
31818
+ async function resolveFramework(detected, flag, agentMode) {
31819
+ if (flag) {
31820
+ if (!isSelectableFramework(flag)) {
31821
+ throw new Error(
31822
+ `Unknown --framework "${flag}". Valid values: ${SELECTABLE_FRAMEWORKS.join(", ")}.`
31823
+ );
31824
+ }
31825
+ return flag;
31826
+ }
31827
+ if (detected !== "unknown") return detected;
31828
+ if (agentMode || !isInteractive()) return "unknown";
31829
+ const picked = await select({
31830
+ message: "Couldn't auto-detect your framework. Which one is this?",
31831
+ choices: SELECTABLE_FRAMEWORKS.map((value) => ({
31832
+ name: FRAMEWORK_SPECS[value].label ?? value,
31833
+ value
31834
+ }))
31835
+ });
31836
+ return picked;
31837
+ }
31838
+ async function promptExportFormats(existingConfig) {
31839
+ if (!isInteractive()) {
31840
+ return existingConfig ?? DEFAULT_EXPORTS;
31841
+ }
31842
+ const choices = EXPORT_CHOICES.map((choice) => ({
31843
+ name: choice.name,
31844
+ value: choice.value,
31845
+ checked: existingConfig ? existingConfig[choice.value] : choice.checked
31846
+ }));
31847
+ const allChoices = [
31848
+ ...choices,
31849
+ ...FUTURE_EXPORTS.map((choice) => ({
31850
+ name: `${choice.name} (${choice.disabled})`,
31851
+ value: choice.value,
31852
+ checked: false,
31853
+ disabled: true
31854
+ }))
31855
+ ];
31856
+ const selections = await checkbox({
31857
+ message: "What would you like to export?",
31858
+ choices: allChoices,
31859
+ required: true
31860
+ });
31861
+ return selectionsToConfig(selections);
31862
+ }
31863
+ function isTailwindCliInstalled() {
31864
+ const require2 = createRequire(import.meta.url);
31865
+ try {
31866
+ require2.resolve("@tailwindcss/cli/package.json");
31867
+ return true;
31868
+ } catch (err) {
31869
+ if (err instanceof Error && "code" in err && err.code === "MODULE_NOT_FOUND") {
31870
+ return false;
31871
+ }
31872
+ throw err;
31873
+ }
31874
+ }
31875
+ async function ensureTailwindCli(cwd) {
31876
+ if (!isInteractive() || isAgentMode()) {
31877
+ throw new Error(
31878
+ "Standalone CSS export requires @tailwindcss/cli. Install it as a dev dependency in your project."
31879
+ );
31880
+ }
31881
+ const shouldInstall = await confirm({
31882
+ message: "Standalone CSS requires @tailwindcss/cli. Install it now?",
31883
+ default: true
31884
+ });
31885
+ if (!shouldInstall) {
31886
+ throw new Error("Standalone CSS export requires @tailwindcss/cli.");
31887
+ }
31888
+ await updateDependencies([], ["@tailwindcss/cli"], { cwd });
31889
+ if (!isTailwindCliInstalled()) {
31890
+ throw new Error(
31891
+ "@tailwindcss/cli was installed but cannot be resolved. Try installing at the workspace root."
31892
+ );
31893
+ }
31894
+ }
31895
+ async function generateOutputs(cwd, paths, registry2, exports, source, darkMode = "class", config2) {
31896
+ const contentSources = config2 ? resolveContentSources(cwd, config2) : [];
31897
+ if (exports.compiled && !isTailwindCliInstalled()) {
31898
+ log({ event: "init:prompting_exports" });
31899
+ await ensureTailwindCli(cwd);
31900
+ }
31901
+ if (exports.compiled) {
31902
+ log({ event: "init:compiling_css" });
31903
+ }
31904
+ return regenerateOutputs(registry2, {
31905
+ outputDir: paths.output,
31906
+ exports,
31907
+ contentSources,
31908
+ darkMode,
31909
+ includeImport: source !== "shadcn"
31910
+ });
31911
+ }
31912
+ async function regenerateFromExisting(cwd, paths, source, isAgentMode2, framework) {
31913
+ log({ event: "init:regenerate", cwd });
31914
+ let existingConfig = null;
31915
+ try {
31916
+ const configContent = await readFile7(paths.config, "utf-8");
31917
+ existingConfig = migrateConfig(
31918
+ JSON.parse(configContent)
31919
+ );
31920
+ } catch {
31921
+ }
31922
+ if (framework !== "unknown" && existingConfig) {
31923
+ const frameworkPaths = FRAMEWORK_SPECS[framework].components;
31924
+ existingConfig.framework = framework;
31925
+ existingConfig.componentsPath = frameworkPaths.components;
31926
+ existingConfig.primitivesPath = frameworkPaths.primitives;
31927
+ existingConfig.compositesPath = frameworkPaths.composites;
31928
+ existingConfig.rulesPath = frameworkPaths.rules;
31929
+ }
31930
+ await rm(join13(paths.tokens, "elevation.rafters.json"), { force: true });
31931
+ await rm(join13(paths.tokens, "fill.rafters.json"), { force: true });
31932
+ const registry2 = loadRegistryFromDir(paths.tokens, REGISTRY_PLUGINS2);
31933
+ if (registry2.size() === 0) {
31934
+ throw new Error("No tokens found. Cannot regenerate without existing tokens.");
31935
+ }
31936
+ const namespaces = [...new Set(registry2.list().map((t) => t.namespace))];
31937
+ log({
31938
+ event: "init:loaded",
31939
+ tokenCount: registry2.size(),
31940
+ namespaces
31941
+ });
31942
+ let exports;
31943
+ if (isAgentMode2) {
31944
+ exports = existingConfig?.exports ?? DEFAULT_EXPORTS;
31945
+ log({ event: "init:exports_default", exports });
31946
+ } else {
31947
+ if (isInteractive()) {
31948
+ log({ event: "init:prompting_exports" });
31949
+ }
31950
+ exports = await promptExportFormats(existingConfig?.exports);
31951
+ log({ event: "init:exports_selected", exports });
31952
+ }
31953
+ await mkdir3(paths.output, { recursive: true });
31954
+ const outputs = await generateOutputs(
31955
+ cwd,
31956
+ paths,
31957
+ registry2,
31958
+ exports,
31959
+ source,
31960
+ "class",
31961
+ existingConfig
31962
+ );
31963
+ if (existingConfig) {
31964
+ existingConfig.exports = exports;
31965
+ await writeFile5(paths.config, JSON.stringify(existingConfig, null, 2));
31966
+ } else {
31967
+ const frameworkPaths = FRAMEWORK_SPECS[framework].components;
31968
+ const newConfig = {
31969
+ framework,
31970
+ componentsPath: frameworkPaths.components,
31971
+ primitivesPath: frameworkPaths.primitives,
31972
+ compositesPath: frameworkPaths.composites,
31973
+ rulesPath: frameworkPaths.rules,
31974
+ cssPath: null,
31975
+ ...source ? { source } : {},
31976
+ intent: "efficient",
31977
+ fonts: { path: null, imports: [] },
31978
+ exports,
31979
+ installed: { components: [], primitives: [], composites: [], rules: [] }
31980
+ };
31981
+ await writeFile5(paths.config, JSON.stringify(newConfig, null, 2));
31982
+ }
31983
+ log({
31984
+ event: "init:complete",
31985
+ outputs,
31986
+ path: paths.output
31987
+ });
31988
+ }
31989
+ async function resetToDefaults(cwd, paths, source, isAgentMode2, framework) {
31990
+ log({ event: "init:reset", cwd });
31991
+ let existingConfig = null;
31992
+ try {
31993
+ const configContent = await readFile7(paths.config, "utf-8");
31994
+ existingConfig = migrateConfig(
31995
+ JSON.parse(configContent)
31996
+ );
31997
+ } catch {
31998
+ }
31999
+ if (framework !== "unknown" && existingConfig) {
32000
+ const frameworkPaths = FRAMEWORK_SPECS[framework].components;
32001
+ existingConfig.framework = framework;
32002
+ existingConfig.componentsPath = frameworkPaths.components;
32003
+ existingConfig.primitivesPath = frameworkPaths.primitives;
32004
+ existingConfig.compositesPath = frameworkPaths.composites;
32005
+ existingConfig.rulesPath = frameworkPaths.rules;
32006
+ }
32007
+ let existingTokens = [];
32008
+ try {
32009
+ existingTokens = loadRegistryFromDir(paths.tokens, REGISTRY_PLUGINS2).list();
32010
+ } catch {
32011
+ }
32012
+ const overriddenTokens = existingTokens.filter((t) => t.userOverride);
32013
+ if (overriddenTokens.length > 0) {
32014
+ const backup = {
32015
+ resetAt: (/* @__PURE__ */ new Date()).toISOString(),
32016
+ reason: "rafters init --reset",
32017
+ overrides: overriddenTokens.map((t) => ({
32018
+ name: t.name,
32019
+ value: t.value,
32020
+ userOverride: t.userOverride,
32021
+ namespace: t.namespace
32022
+ }))
32023
+ };
32024
+ await mkdir3(paths.output, { recursive: true });
32025
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
32026
+ const backupPath = join13(paths.output, `reset-${timestamp}.json`);
32027
+ await writeFile5(backupPath, JSON.stringify(backup, null, 2));
32028
+ log({
32029
+ event: "init:reset_backup",
32030
+ path: backupPath,
32031
+ overrideCount: overriddenTokens.length
32032
+ });
32033
+ }
32034
+ let exports;
32035
+ if (isAgentMode2) {
32036
+ exports = existingConfig?.exports ?? DEFAULT_EXPORTS;
32037
+ log({ event: "init:exports_default", exports });
32038
+ } else {
32039
+ if (isInteractive()) {
32040
+ log({ event: "init:prompting_exports" });
32041
+ }
32042
+ exports = await promptExportFormats(existingConfig?.exports);
32043
+ log({ event: "init:exports_selected", exports });
32044
+ }
32045
+ const system = generateBaseSystem({});
32046
+ const registry2 = new TokenRegistry(system.allTokens, REGISTRY_PLUGINS2);
32047
+ log({
32048
+ event: "init:reset_generated",
32049
+ tokenCount: registry2.size()
32050
+ });
32051
+ await rm(paths.tokens, { recursive: true, force: true });
32052
+ await mkdir3(paths.tokens, { recursive: true });
32053
+ saveRegistryToDir(paths.tokens, registry2);
32054
+ const allTokensToSave = registry2.list();
32055
+ const namespaceCount = new Set(allTokensToSave.map((t) => t.namespace)).size;
32056
+ log({
32057
+ event: "init:registry_saved",
32058
+ path: paths.tokens,
32059
+ namespaceCount
32060
+ });
32061
+ await mkdir3(paths.output, { recursive: true });
32062
+ const outputs = await generateOutputs(
32063
+ cwd,
32064
+ paths,
32065
+ registry2,
32066
+ exports,
32067
+ source,
32068
+ "class",
32069
+ existingConfig
32070
+ );
32071
+ if (existingConfig) {
32072
+ existingConfig.exports = exports;
32073
+ await writeFile5(paths.config, JSON.stringify(existingConfig, null, 2));
32074
+ } else {
32075
+ const frameworkPaths = FRAMEWORK_SPECS[framework].components;
32076
+ const newConfig = {
32077
+ framework,
32078
+ componentsPath: frameworkPaths.components,
32079
+ primitivesPath: frameworkPaths.primitives,
32080
+ compositesPath: frameworkPaths.composites,
32081
+ rulesPath: frameworkPaths.rules,
32082
+ cssPath: null,
32083
+ ...source ? { source } : {},
32084
+ intent: "efficient",
32085
+ fonts: { path: null, imports: [] },
32086
+ exports,
32087
+ installed: { components: [], primitives: [], composites: [], rules: [] }
32088
+ };
32089
+ await writeFile5(paths.config, JSON.stringify(newConfig, null, 2));
32090
+ }
32091
+ log({
32092
+ event: "init:complete",
32093
+ outputs,
32094
+ path: paths.output
32095
+ });
32096
+ }
32097
+ async function init(options) {
32098
+ setAgentMode(options.agent ?? false);
32099
+ const isAgentMode2 = options.agent ?? false;
32100
+ const cwd = process.cwd();
32101
+ const paths = getRaftersPaths(cwd);
32102
+ log({ event: "init:start", cwd });
32103
+ const project = await detectProject(cwd);
32104
+ const { framework: detectedFramework, shadcn, tailwindVersion, astroHasReact } = project;
32105
+ const source = shadcn ? "shadcn" : void 0;
32106
+ log({
32107
+ event: "init:detected",
32108
+ framework: detectedFramework,
32109
+ tailwindVersion,
32110
+ hasShadcn: !!shadcn
32111
+ });
32112
+ const framework = await resolveFramework(detectedFramework, options.framework, isAgentMode2);
32113
+ if (framework !== detectedFramework) {
32114
+ log({
32115
+ event: "init:framework_resolved",
32116
+ detected: detectedFramework,
32117
+ resolved: framework,
32118
+ source: options.framework ? "flag" : "prompt"
32119
+ });
32120
+ }
32121
+ if (isTailwindV3(tailwindVersion)) {
32122
+ throw new Error("Tailwind v3 detected. Rafters requires Tailwind v4.");
32123
+ }
32124
+ const raftersExists = existsSync7(paths.root);
32125
+ if (options.reset && !raftersExists) {
32126
+ throw new Error("Nothing to reset. No .rafters/ directory found.");
32127
+ }
32128
+ if (raftersExists && options.reset) {
32129
+ await resetToDefaults(cwd, paths, source, isAgentMode2, framework);
32130
+ return;
32131
+ }
32132
+ if (raftersExists && !options.rebuild) {
32133
+ throw new Error(
32134
+ ".rafters/ directory already exists. Use --rebuild to regenerate output files, or --reset to start from defaults."
32135
+ );
32136
+ }
32137
+ if (raftersExists && options.rebuild) {
32138
+ await regenerateFromExisting(cwd, paths, source, isAgentMode2, framework);
32139
+ return;
32140
+ }
32141
+ let exports;
32142
+ if (isAgentMode2) {
32143
+ exports = DEFAULT_EXPORTS;
32144
+ log({ event: "init:exports_default", exports });
32145
+ } else {
32146
+ if (isInteractive()) {
32147
+ log({ event: "init:prompting_exports" });
32356
32148
  }
32357
- return resolveReadSet(config2.compositesPath, workspaceRoot);
32149
+ exports = await promptExportFormats();
32150
+ log({ event: "init:exports_selected", exports });
32358
32151
  }
32359
- async handleComposite(args) {
32360
- const { id, query, category, workspace } = args;
32361
- if (id) {
32362
- return this.describeById(id, workspace);
32363
- }
32364
- const resolved = this.resolve(workspace);
32365
- if (workspace && !resolved) {
32366
- return this.workspaceRequiredError();
32367
- }
32368
- await this.ensureCompositesLoaded(resolved);
32369
- let composites2;
32370
- if (query) {
32371
- composites2 = search(query);
32372
- } else if (category) {
32373
- composites2 = getByCategory(category);
32374
- } else {
32375
- composites2 = getAll2();
32152
+ const adapter = getAdapter(source ?? "tailwind");
32153
+ const baseConfig = {};
32154
+ const sourceCssPathForBase = resolveSourceCssPath(
32155
+ cwd,
32156
+ framework,
32157
+ detectedFramework,
32158
+ project.cssPath,
32159
+ shadcn?.tailwind?.css,
32160
+ log
32161
+ );
32162
+ if (sourceCssPathForBase !== null) {
32163
+ try {
32164
+ const cssForBase = await readFile7(join13(cwd, sourceCssPathForBase), "utf-8");
32165
+ const baseSlots = [
32166
+ {
32167
+ detect: () => adapter.detectSpacing(cssForBase).base,
32168
+ field: "baseSpacingUnit",
32169
+ event: "init:import_spacing_applied",
32170
+ eventKey: "baseSpacingUnit"
32171
+ },
32172
+ {
32173
+ detect: () => adapter.detectRadius(cssForBase).base,
32174
+ field: "baseRadiusOverride",
32175
+ event: "init:import_radius_applied",
32176
+ eventKey: "baseRadius"
32177
+ },
32178
+ {
32179
+ detect: () => adapter.detectFontSize(cssForBase).base,
32180
+ field: "baseFontSizeOverride",
32181
+ event: "init:import_font_size_applied",
32182
+ eventKey: "baseFontSize"
32183
+ },
32184
+ {
32185
+ detect: () => adapter.detectFocusRing(cssForBase).width,
32186
+ field: "focusRingWidthOverride",
32187
+ event: "init:import_focus_ring_applied",
32188
+ eventKey: "focusRingWidth"
32189
+ }
32190
+ // Motion duration is intentionally not imported -- rafters' motion
32191
+ // system is research-backed and most projects' `--duration-base`
32192
+ // values are unresearched defaults. See importers/bases.ts for the
32193
+ // policy note; designers can `rafters set motion-duration-base ...`
32194
+ // after init to override explicitly.
32195
+ ];
32196
+ for (const slot of baseSlots) {
32197
+ const value = slot.detect();
32198
+ if (value === void 0) continue;
32199
+ baseConfig[slot.field] = value;
32200
+ log({
32201
+ event: slot.event,
32202
+ cssPath: sourceCssPathForBase,
32203
+ [slot.eventKey]: value
32204
+ });
32205
+ }
32206
+ } catch (err) {
32207
+ if (!(err instanceof Error && "code" in err && err.code === "ENOENT")) throw err;
32376
32208
  }
32377
- const result = composites2.map((c4) => ({
32378
- id: c4.manifest.id,
32379
- name: c4.manifest.name,
32380
- category: c4.manifest.category,
32381
- description: c4.manifest.description,
32382
- cognitiveLoad: c4.manifest.cognitiveLoad,
32383
- solves: c4.manifest.solves,
32384
- appliesWhen: c4.manifest.appliesWhen,
32385
- usagePatterns: c4.manifest.usagePatterns,
32386
- input: c4.input,
32387
- output: c4.output,
32388
- blockCount: c4.blocks.length,
32389
- // Surface which blocks carry which validation rules. Rules compile to
32390
- // native HTML5 constraint attributes at build, so agents need to see
32391
- // them to reason about a composite's behavior. Only blocks with rules
32392
- // are listed -- the array is empty when nothing is constrained.
32393
- blockRules: c4.blocks.filter((b2) => b2.rules && b2.rules.length > 0).map((b2) => ({ id: b2.id, type: b2.type, rules: b2.rules }))
32394
- }));
32395
- return this.jsonResult({ composites: result, deprecated: DEPRECATED_MSG });
32396
32209
  }
32397
- async handlePattern(args) {
32398
- const { solves, query, workspace } = args;
32399
- const resolved = this.resolve(workspace);
32400
- if (workspace && !resolved) {
32401
- return this.workspaceRequiredError();
32402
- }
32403
- await this.ensureCompositesLoaded(resolved);
32404
- let composites2;
32405
- if (solves) {
32406
- const all = getAll2();
32407
- const solvesLower = solves.toLowerCase();
32408
- composites2 = all.filter(
32409
- (c4) => c4.manifest.solves?.toLowerCase().includes(solvesLower) || c4.manifest.appliesWhen?.some((a2) => a2.toLowerCase().includes(solvesLower))
32410
- );
32411
- } else if (query) {
32412
- composites2 = search(query);
32210
+ const system = generateBaseSystem(baseConfig);
32211
+ const registry2 = new TokenRegistry(system.allTokens, REGISTRY_PLUGINS2);
32212
+ log({
32213
+ event: "init:generated",
32214
+ tokenCount: registry2.size()
32215
+ });
32216
+ await mkdir3(paths.tokens, { recursive: true });
32217
+ await mkdir3(paths.output, { recursive: true });
32218
+ saveRegistryToDir(paths.tokens, registry2);
32219
+ const allTokensToSave = registry2.list();
32220
+ const namespaceCount = new Set(allTokensToSave.map((t) => t.namespace)).size;
32221
+ log({
32222
+ event: "init:registry_saved",
32223
+ path: paths.tokens,
32224
+ namespaceCount
32225
+ });
32226
+ const outputs = await generateOutputs(cwd, paths, registry2, exports, source);
32227
+ let detectedCssPath = null;
32228
+ detectedCssPath = resolveSourceCssPath(
32229
+ cwd,
32230
+ framework,
32231
+ detectedFramework,
32232
+ project.cssPath,
32233
+ shadcn?.tailwind?.css,
32234
+ log
32235
+ );
32236
+ if (source !== "shadcn" && exports.tailwind) {
32237
+ if (detectedCssPath) {
32238
+ await updateMainCss(cwd, detectedCssPath, ".rafters/output/rafters.css");
32413
32239
  } else {
32414
- composites2 = getAll2().filter((c4) => c4.manifest.usagePatterns);
32415
- }
32416
- if (composites2.length === 0) {
32417
- const all = getAll2();
32418
- const available = all.filter((c4) => c4.manifest.solves || c4.manifest.usagePatterns).map((c4) => ({
32419
- id: c4.manifest.id,
32420
- solves: c4.manifest.solves
32421
- }));
32422
- return this.errorResult("No patterns found matching query", {
32423
- available,
32424
- deprecated: DEPRECATED_MSG
32240
+ log({
32241
+ event: "init:css_not_found",
32242
+ message: 'No main CSS file found. Add @import ".rafters/output/rafters.css" manually.',
32243
+ searchedLocations: FRAMEWORK_SPECS[framework].cssLocations
32425
32244
  });
32426
32245
  }
32427
- const patterns = composites2.map((c4) => ({
32428
- id: c4.manifest.id,
32429
- name: c4.manifest.name,
32430
- solves: c4.manifest.solves,
32431
- appliesWhen: c4.manifest.appliesWhen,
32432
- cognitiveLoad: c4.manifest.cognitiveLoad,
32433
- usagePatterns: c4.manifest.usagePatterns
32434
- }));
32435
- return this.jsonResult({ patterns, deprecated: DEPRECATED_MSG });
32436
32246
  }
32437
- /**
32438
- * DEPRECATED alias for `rafters_describe`. A component resolves by id through
32439
- * the overlay exactly as `describe(<id>)` does (#2072's resolver has no
32440
- * separate component/composite address form -- `describe(<id>)` resolves either
32441
- * kind), so this is a direct, lossless forward with the deprecated marker added.
32442
- */
32443
- async handleComponent(componentName, workspaceName) {
32444
- return this.describeById(componentName, workspaceName);
32247
+ let componentTarget = frameworkToTarget(framework);
32248
+ if (framework === "astro" && astroHasReact && isInteractive() && !isAgentMode2) {
32249
+ componentTarget = await select({
32250
+ message: "This Astro project has React integration. Install components as:",
32251
+ choices: [
32252
+ {
32253
+ name: "Astro components (zero client JS, server-rendered)",
32254
+ value: "astro"
32255
+ },
32256
+ {
32257
+ name: "React components (client islands with client:load)",
32258
+ value: "react"
32259
+ }
32260
+ ]
32261
+ });
32445
32262
  }
32446
- /**
32447
- * Shared by the deprecated `rafters_component` and `rafters_composite({id})`
32448
- * paths: resolve one id through the overlay, stamp the deprecated marker.
32449
- */
32450
- async describeById(id, workspaceName) {
32451
- const resolved = this.resolve(workspaceName);
32452
- if (workspaceName && !resolved) {
32453
- return this.workspaceRequiredError();
32263
+ const frameworkPaths = FRAMEWORK_SPECS[framework].components;
32264
+ const config2 = {
32265
+ framework,
32266
+ ...source ? { source } : {},
32267
+ componentTarget,
32268
+ componentsPath: frameworkPaths.components,
32269
+ primitivesPath: frameworkPaths.primitives,
32270
+ compositesPath: frameworkPaths.composites,
32271
+ rulesPath: frameworkPaths.rules,
32272
+ cssPath: detectedCssPath,
32273
+ exports,
32274
+ intent: "efficient",
32275
+ fonts: { path: null, imports: [] },
32276
+ installed: {
32277
+ components: [],
32278
+ primitives: [],
32279
+ composites: [],
32280
+ rules: []
32454
32281
  }
32455
- let graph;
32282
+ };
32283
+ await writeFile5(paths.config, JSON.stringify(config2, null, 2));
32284
+ if (detectedCssPath) {
32285
+ let sourceCss = null;
32456
32286
  try {
32457
- graph = await this.ensureGraph(resolved);
32287
+ sourceCss = await readFile7(join13(cwd, detectedCssPath), "utf-8");
32458
32288
  } catch (err) {
32459
- const message = err instanceof Error ? err.message : "Unknown error";
32460
- return this.errorResult(`failed to build intel graph: ${message}`, {
32461
- deprecated: DEPRECATED_MSG
32462
- });
32289
+ if (!(err instanceof Error && "code" in err && err.code === "ENOENT")) throw err;
32463
32290
  }
32464
- const ctx = await this.overlayContext(resolved);
32465
- if ("configError" in ctx) {
32466
- return this.errorResult(ctx.configError, { deprecated: DEPRECATED_MSG });
32291
+ if (sourceCss !== null) {
32292
+ const summary = senseShadcnCss(sourceCss);
32293
+ if (summary.totalDeclarations > 0) {
32294
+ log({
32295
+ event: "init:import_sensed",
32296
+ cssPath: detectedCssPath,
32297
+ ...summary
32298
+ });
32299
+ const classification = classifyDeclarations(extractShadcnRoot(sourceCss));
32300
+ const colorDecls = adapter.detectColors(sourceCss);
32301
+ const nonColorDecls = [
32302
+ ...classification.byNamespace.typography,
32303
+ ...classification.byNamespace.spacing,
32304
+ ...classification.byNamespace.radius,
32305
+ ...classification.byNamespace.shadow
32306
+ ];
32307
+ const toImportColors = [];
32308
+ for (const color of colorDecls) {
32309
+ const accept = isAgentMode2 ? true : await confirm({
32310
+ message: `Import --${color.name}: ${color.value}?`,
32311
+ default: true
32312
+ });
32313
+ if (accept) toImportColors.push(color);
32314
+ }
32315
+ const toImportNonColors = [];
32316
+ for (const decl of nonColorDecls) {
32317
+ const accept = isAgentMode2 ? true : await confirm({
32318
+ message: `Import --${decl.name}: ${decl.value}?`,
32319
+ default: true
32320
+ });
32321
+ if (accept) toImportNonColors.push(decl);
32322
+ }
32323
+ if (toImportColors.length + toImportNonColors.length > 0) {
32324
+ for (const color of toImportColors) {
32325
+ const familyName = `imported-${color.name}`;
32326
+ const reason = `imported from --${color.name} in ${detectedCssPath}`;
32327
+ for (const t of importColorFamily(familyName, "500", color.oklch)) {
32328
+ registry2.define(t);
32329
+ }
32330
+ if (color.namespace === "semantic") {
32331
+ registry2.set(color.name, { family: familyName, position: "500" }, { reason });
32332
+ }
32333
+ }
32334
+ const skipped = [];
32335
+ for (const decl of toImportNonColors) {
32336
+ const reason = `imported from --${decl.name} in ${detectedCssPath}`;
32337
+ if (registry2.has(decl.name)) {
32338
+ registry2.set(decl.name, decl.value, { reason });
32339
+ } else {
32340
+ skipped.push(decl.name);
32341
+ }
32342
+ }
32343
+ saveRegistryToDir(paths.tokens, registry2);
32344
+ await generateOutputs(cwd, paths, registry2, exports, source);
32345
+ const importedNames = [
32346
+ ...toImportColors.map((c4) => c4.name),
32347
+ ...toImportNonColors.map((d2) => d2.name)
32348
+ ];
32349
+ await stripImportedDeclarations(cwd, detectedCssPath, importedNames);
32350
+ log({
32351
+ event: "init:import_applied",
32352
+ count: toImportColors.length + toImportNonColors.length - skipped.length,
32353
+ cssPath: detectedCssPath,
32354
+ ...skipped.length > 0 ? { skipped } : {}
32355
+ });
32356
+ }
32357
+ }
32358
+ {
32359
+ const themeDecls = extractThemeBlocks2(sourceCss);
32360
+ if (themeDecls.length > 0) {
32361
+ const POSITION_SUFFIX = new RegExp(`^(.+)-(${SCALE_POSITIONS.join("|")})$`);
32362
+ const familySeeds = /* @__PURE__ */ new Map();
32363
+ for (const decl of themeDecls) {
32364
+ if (!decl.name.startsWith("color-")) continue;
32365
+ const oklch2 = tryParseColor(decl.value);
32366
+ if (oklch2 === null) continue;
32367
+ const m3 = decl.name.match(POSITION_SUFFIX);
32368
+ const baseName = ((m3 ? m3[1] : decl.name) ?? decl.name).replace(/^color-/, "");
32369
+ if (!baseName) continue;
32370
+ const seedPosition = m3?.[2] ?? "500";
32371
+ const existing = familySeeds.get(baseName);
32372
+ if (!existing || seedPosition === "500") {
32373
+ familySeeds.set(baseName, { seed: oklch2, seedPosition });
32374
+ }
32375
+ }
32376
+ const families = Array.from(familySeeds, ([name, info]) => ({
32377
+ name,
32378
+ ...info
32379
+ }));
32380
+ if (families.length > 0) {
32381
+ for (const family of families) {
32382
+ for (const t of importColorFamily(family.name, family.seedPosition, family.seed)) {
32383
+ registry2.define(t);
32384
+ }
32385
+ }
32386
+ const ALL_ROLES = [
32387
+ "primary",
32388
+ "secondary",
32389
+ "tertiary",
32390
+ "accent",
32391
+ "highlight",
32392
+ "neutral",
32393
+ "muted",
32394
+ "success",
32395
+ "warning",
32396
+ "destructive",
32397
+ "info"
32398
+ ];
32399
+ const ROLES = ALL_ROLES.filter((r) => {
32400
+ const tok = registry2.get(r);
32401
+ return tok !== void 0 && tok.namespace === "semantic";
32402
+ });
32403
+ const assignments = [];
32404
+ for (const [i, role] of ROLES.entries()) {
32405
+ let familyChoice;
32406
+ if (isAgentMode2) {
32407
+ familyChoice = families[i]?.name ?? null;
32408
+ } else {
32409
+ familyChoice = await select({
32410
+ message: `Which palette is "${role}"?`,
32411
+ choices: [
32412
+ ...families.map((f) => ({ name: f.name, value: f.name })),
32413
+ { name: "(skip -- keep rafters default)", value: null }
32414
+ ]
32415
+ });
32416
+ }
32417
+ if (familyChoice === null) continue;
32418
+ const family = families.find((f) => f.name === familyChoice);
32419
+ if (!family) continue;
32420
+ const position = isAgentMode2 ? family.seedPosition : await select({
32421
+ message: `Which position in "${familyChoice}" for "${role}"?`,
32422
+ choices: SCALE_POSITIONS.map((p2) => ({
32423
+ name: p2,
32424
+ value: p2
32425
+ })),
32426
+ default: family.seedPosition
32427
+ });
32428
+ registry2.set(
32429
+ role,
32430
+ { family: familyChoice, position },
32431
+ {
32432
+ reason: `assigned ${familyChoice}@${position} as ${role} during import from ${detectedCssPath}`
32433
+ }
32434
+ );
32435
+ assignments.push({ role, family: familyChoice, position });
32436
+ }
32437
+ saveRegistryToDir(paths.tokens, registry2);
32438
+ await generateOutputs(cwd, paths, registry2, exports, source);
32439
+ const themeImportedNames = families.flatMap(
32440
+ (f) => SCALE_POSITIONS.map((p2) => `color-${f.name}-${p2}`).concat([`color-${f.name}`])
32441
+ );
32442
+ await stripImportedDeclarations(cwd, detectedCssPath, themeImportedNames);
32443
+ log({
32444
+ event: "init:import_palettes_applied",
32445
+ cssPath: detectedCssPath,
32446
+ palettesDefined: families.map((f) => f.name),
32447
+ assignments
32448
+ });
32449
+ }
32450
+ }
32451
+ }
32452
+ }
32453
+ if (sourceCss !== null) {
32454
+ const detectedFonts = adapter.detectFonts(sourceCss);
32455
+ if (detectedFonts.length > 0) {
32456
+ const typographyTokens = registry2.list({ namespace: "typography" });
32457
+ const baseFamilyNames = new Set(
32458
+ typographyTokens.filter((t) => (!t.dependsOn || t.dependsOn.length === 0) && isFontStackValue(t.value)).map((t) => t.name)
32459
+ );
32460
+ const fontRoles = [];
32461
+ for (const tok of typographyTokens) {
32462
+ const base = tok.dependsOn?.[0];
32463
+ if (base === void 0 || !baseFamilyNames.has(base)) continue;
32464
+ if (typeof tok.value !== "string" || !tok.value.includes(`var(--${base})`)) continue;
32465
+ fontRoles.push({ role: tok.name, base });
32466
+ }
32467
+ const roleTokenNames = new Set(fontRoles.map((r) => r.role));
32468
+ const canonicalTokenNames = /* @__PURE__ */ new Set([...baseFamilyNames, ...roleTokenNames]);
32469
+ const definedFonts = [];
32470
+ for (const font of detectedFonts) {
32471
+ if (font.sourceDeclName && canonicalTokenNames.has(font.sourceDeclName)) continue;
32472
+ const tokenName = font.sourceDeclName ?? `font-${slugifyFontName(font.name)}`;
32473
+ if (registry2.has(tokenName)) continue;
32474
+ registry2.define({
32475
+ name: tokenName,
32476
+ namespace: "typography",
32477
+ category: "typography",
32478
+ value: font.stack,
32479
+ description: `Imported from ${detectedCssPath}. ${font.sourceDeclName ? `Declared as --${font.sourceDeclName}.` : "Loaded via @font-face or @import."}`,
32480
+ userOverride: null,
32481
+ containerQueryAware: false
32482
+ });
32483
+ definedFonts.push(tokenName);
32484
+ }
32485
+ const fontAssignments = [];
32486
+ const declaredByBase = /* @__PURE__ */ new Map();
32487
+ for (const font of detectedFonts) {
32488
+ if (font.sourceDeclName && baseFamilyNames.has(font.sourceDeclName)) {
32489
+ if (!declaredByBase.has(font.sourceDeclName)) {
32490
+ declaredByBase.set(font.sourceDeclName, font);
32491
+ }
32492
+ }
32493
+ }
32494
+ for (const { role, base } of fontRoles) {
32495
+ let choice;
32496
+ if (isAgentMode2) {
32497
+ const declared = declaredByBase.get(base);
32498
+ if (declared !== void 0) {
32499
+ choice = declared.name;
32500
+ } else if (/mono/.test(base)) {
32501
+ choice = detectedFonts.find((f) => /mono/i.test(f.name))?.name ?? null;
32502
+ } else {
32503
+ const sansLike = detectedFonts.find((f) => !/mono/i.test(f.name));
32504
+ choice = sansLike?.name ?? null;
32505
+ }
32506
+ } else {
32507
+ const prompt = role.replace(/^font-/, "");
32508
+ choice = await select({
32509
+ message: `Which font is "${prompt}"?`,
32510
+ choices: [
32511
+ ...detectedFonts.map((f) => ({ name: f.name, value: f.name })),
32512
+ { name: "(skip -- keep rafters default)", value: null }
32513
+ ]
32514
+ });
32515
+ }
32516
+ if (choice === null) continue;
32517
+ const font = detectedFonts.find((f) => f.name === choice);
32518
+ if (font === void 0) continue;
32519
+ const reason = `assigned ${font.name} as ${role} during import from ${detectedCssPath}`;
32520
+ if (registry2.has(role)) {
32521
+ registry2.set(role, font.stack, { reason });
32522
+ }
32523
+ if (registry2.has(base)) {
32524
+ const current = registry2.get(base);
32525
+ if (current?.value !== font.stack) {
32526
+ registry2.set(base, font.stack, { reason });
32527
+ }
32528
+ }
32529
+ fontAssignments.push({ role, family: font.name });
32530
+ }
32531
+ if (fontAssignments.length > 0 || definedFonts.length > 0) {
32532
+ saveRegistryToDir(paths.tokens, registry2);
32533
+ await generateOutputs(cwd, paths, registry2, exports, source);
32534
+ log({
32535
+ event: "init:import_fonts_applied",
32536
+ cssPath: detectedCssPath,
32537
+ fontsDetected: detectedFonts.map((f) => f.name),
32538
+ assignments: fontAssignments,
32539
+ ...definedFonts.length > 0 ? { definedFonts } : {}
32540
+ });
32541
+ }
32542
+ }
32467
32543
  }
32468
- const out = describeWithOverlay(id, graph, ctx);
32469
- return this.jsonResult(this.withDeprecated(out));
32470
32544
  }
32471
- };
32545
+ log({
32546
+ event: "init:complete",
32547
+ outputs: [...outputs, "config.rafters.json"],
32548
+ path: paths.output
32549
+ });
32550
+ }
32551
+
32552
+ // src/commands/mcp.ts
32553
+ import { existsSync as existsSync8 } from "fs";
32554
+ import { basename as basename2, join as join14, resolve as resolve5 } from "path";
32555
+
32556
+ // src/mcp/server.ts
32557
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
32558
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
32559
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
32560
+
32561
+ // src/version.ts
32562
+ import { readFileSync as readFileSync3 } from "fs";
32563
+ var UNKNOWN_VERSION = "0.0.0-unknown";
32564
+ function readVersion() {
32565
+ try {
32566
+ const parsed = JSON.parse(
32567
+ readFileSync3(new URL("../package.json", import.meta.url), "utf-8")
32568
+ );
32569
+ if (parsed !== null && typeof parsed === "object" && "version" in parsed) {
32570
+ const { version: version2 } = parsed;
32571
+ if (typeof version2 === "string" && version2.length > 0) return version2;
32572
+ }
32573
+ } catch {
32574
+ }
32575
+ return UNKNOWN_VERSION;
32576
+ }
32577
+ var VERSION = readVersion();
32472
32578
 
32473
32579
  // src/mcp/server.ts
32474
32580
  async function startMcpServer(workspaces, defaultWorkspace) {
@@ -32518,8 +32624,8 @@ async function mcp(options) {
32518
32624
  let defaultWorkspace;
32519
32625
  if (options.projectRoot) {
32520
32626
  const explicit = resolve5(options.projectRoot);
32521
- const configPath2 = join13(explicit, ".rafters", "config.rafters.json");
32522
- if (!existsSync7(configPath2)) {
32627
+ const configPath2 = join14(explicit, ".rafters", "config.rafters.json");
32628
+ if (!existsSync8(configPath2)) {
32523
32629
  process.stderr.write(
32524
32630
  `--project-root ${explicit} does not contain .rafters/config.rafters.json
32525
32631
  `
@@ -32539,12 +32645,12 @@ async function mcp(options) {
32539
32645
  }
32540
32646
 
32541
32647
  // src/commands/studio.ts
32542
- import { existsSync as existsSync8 } from "fs";
32648
+ import { existsSync as existsSync9 } from "fs";
32543
32649
  import { resolve as resolve6 } from "path";
32544
32650
 
32545
32651
  // ../studio/src/api/vite-plugin.ts
32546
- import { readFile as readFile7, writeFile as writeFile5 } from "fs/promises";
32547
- import { isAbsolute as isAbsolute4, join as join14 } from "path";
32652
+ import { readFile as readFile8, writeFile as writeFile6 } from "fs/promises";
32653
+ import { isAbsolute as isAbsolute4, join as join15 } from "path";
32548
32654
  var REGISTRY_PLUGINS3 = [scalePlugin, contrastPlugin, statePlugin, invertPlugin];
32549
32655
  var STUDIO_REASON_DEFAULT = "studio interactive edit";
32550
32656
  var TokenResponseSchema = external_exports.object({
@@ -32570,8 +32676,8 @@ var ColorBuildOptionsSchema = external_exports.object({
32570
32676
  states: external_exports.record(external_exports.string(), external_exports.string()).optional()
32571
32677
  });
32572
32678
  var projectPath = process.env.RAFTERS_PROJECT_PATH || process.cwd();
32573
- var outputDir = join14(projectPath, ".rafters", "output");
32574
- var configPath = join14(projectPath, ".rafters", "config.rafters.json");
32679
+ var outputDir = join15(projectPath, ".rafters", "output");
32680
+ var configPath = join15(projectPath, ".rafters", "config.rafters.json");
32575
32681
  var FontsConfigSchema = external_exports.object({
32576
32682
  path: external_exports.union([external_exports.string(), external_exports.null()]).optional(),
32577
32683
  imports: external_exports.array(external_exports.string()).optional()
@@ -32588,7 +32694,7 @@ var RaftersConfigSchema = external_exports.object({
32588
32694
  var KNOWN_INTENTS = ["efficient"];
32589
32695
  async function readRaftersConfig() {
32590
32696
  try {
32591
- const raw = JSON.parse(await readFile7(configPath, "utf8"));
32697
+ const raw = JSON.parse(await readFile8(configPath, "utf8"));
32592
32698
  if ("shadcn" in raw && !("source" in raw)) {
32593
32699
  if (raw.shadcn === true) raw.source = "shadcn";
32594
32700
  delete raw.shadcn;
@@ -32998,7 +33104,7 @@ function studioApiPlugin() {
32998
33104
  return {
32999
33105
  name: "rafters-studio-api",
33000
33106
  async configureServer(server) {
33001
- const tokensDir = join14(projectPath, ".rafters", "tokens");
33107
+ const tokensDir = join15(projectPath, ".rafters", "tokens");
33002
33108
  try {
33003
33109
  registry2 = loadRegistryFromDir(tokensDir, REGISTRY_PLUGINS3);
33004
33110
  initialized = true;
@@ -33061,11 +33167,11 @@ function studioApiPlugin() {
33061
33167
  const classDirs = watchConfig ? resolveContentSources(projectPath, watchConfig) : [];
33062
33168
  const newDirs = classDirs.filter((d2) => !trackedClassDirs.has(d2));
33063
33169
  if (newDirs.length > 0) {
33064
- server.watcher.add(newDirs.map((dir) => join14(dir, "**/*.classes.ts")));
33170
+ server.watcher.add(newDirs.map((dir) => join15(dir, "**/*.classes.ts")));
33065
33171
  for (const d2 of newDirs) trackedClassDirs.add(d2);
33066
33172
  }
33067
33173
  };
33068
- server.watcher.add([join14(tokensDir, "*.rafters.json"), configPath]);
33174
+ server.watcher.add([join15(tokensDir, "*.rafters.json"), configPath]);
33069
33175
  await syncWatchTargets();
33070
33176
  let debounce = null;
33071
33177
  const onChange = (changed) => {
@@ -33193,7 +33299,7 @@ function studioApiPlugin() {
33193
33299
  updated.fonts = merged;
33194
33300
  }
33195
33301
  try {
33196
- await writeFile5(configPath, `${JSON.stringify(updated, null, 2)}
33302
+ await writeFile6(configPath, `${JSON.stringify(updated, null, 2)}
33197
33303
  `);
33198
33304
  reply({ ok: true, config: updated });
33199
33305
  } catch (error47) {
@@ -33351,7 +33457,7 @@ import { createServer } from "vite";
33351
33457
  async function studio() {
33352
33458
  const cwd = process.cwd();
33353
33459
  const paths = getRaftersPaths(cwd);
33354
- if (!existsSync8(paths.root)) {
33460
+ if (!existsSync9(paths.root)) {
33355
33461
  console.error('No .rafters/ directory found. Run "rafters init" first.');
33356
33462
  process.exit(1);
33357
33463
  }
@@ -33386,6 +33492,9 @@ program.command("init").description("Initialize .rafters/ with default tokens an
33386
33492
  program.command("add").description("Add rafters components to the project").argument("[components...]", "Component names to add").option("--list", "List available components").option("--overwrite", "Overwrite existing component files").option("--update", "Re-fetch named components from registry").option("--update-all", "Re-fetch all installed components from registry").option("--registry-url <url>", "Custom registry URL").option("--agent", "Output JSON for machine consumption").action(withErrorHandler(add));
33387
33493
  program.command("mcp").description("Start MCP server for AI agent access (stdio)").option("--project-root <path>", "Explicit project root (skips .rafters/ discovery)").action(mcp);
33388
33494
  program.command("studio").description("Open Studio UI for visual token editing").action(studio);
33495
+ program.command("agents").description(
33496
+ "Write or update the rafters contract block in AGENTS.md for non-Claude-Code agent hosts"
33497
+ ).action(withErrorHandler(agents));
33389
33498
  program.parse();
33390
33499
  /*! Bundled license information:
33391
33500