youiyoux 0.1.6 → 0.1.7

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 (68) hide show
  1. package/dist/chunks/{chunk-XAO3LS7M.js → chunk-3SQQNQDN.js} +1 -1
  2. package/dist/chunks/{chunk-BHJCAZXO.js → chunk-4DVBWN7H.js} +3 -3
  3. package/dist/chunks/{chunk-S57F4CMN.js → chunk-7P4UXAIY.js} +1 -1
  4. package/dist/chunks/{chunk-5YVH6NHT.js → chunk-BITS5O6H.js} +4 -4
  5. package/dist/chunks/{chunk-W2KFNGQP.js → chunk-N6RVMQCV.js} +710 -211
  6. package/dist/chunks/{chunk-RHXBYN4W.js → chunk-O66KB7BU.js} +20 -15
  7. package/dist/chunks/{chunk-5Y2MVIGH.js → chunk-SIXL67TZ.js} +2 -2
  8. package/dist/chunks/{chunk-VOSWAWKC.js → chunk-TV5UX3FE.js} +94 -23
  9. package/dist/chunks/{chunk-DP54SKL4.js → chunk-YWBQO7PO.js} +13 -5
  10. package/dist/chunks/{src-VKQJF7KR.js → src-4JGIPP5A.js} +4 -4
  11. package/dist/chunks/{src-6HTTOVZ5.js → src-BKPSGYS2.js} +5 -3
  12. package/dist/chunks/{src-7RRKYIAS.js → src-DYZIE4XZ.js} +2 -2
  13. package/dist/chunks/{src-YYI52B5R.js → src-J6GPLCVO.js} +2 -2
  14. package/dist/chunks/{src-DZWWMUDR.js → src-NWPGCDGH.js} +6 -6
  15. package/dist/chunks/{src-URZDH3QH.js → src-SDJCFXRI.js} +3 -1
  16. package/dist/chunks/{src-4VYLEZ3Z.js → src-WP66UHF7.js} +6 -2
  17. package/dist/hub/assets/{CandidatePanel-CnbK4S0j.js → CandidatePanel-D6Tenu7w.js} +1 -1
  18. package/dist/hub/assets/{CatalogScreen-B5OVL-AR.js → CatalogScreen-C1tvk0sB.js} +1 -1
  19. package/dist/hub/assets/{ChangesScreen-GwGqJwxN.js → ChangesScreen-D_6yzFGP.js} +1 -1
  20. package/dist/hub/assets/{CheatSheet-BLSOq-xa.js → CheatSheet-Ci7mG0sm.js} +1 -1
  21. package/dist/hub/assets/{CommandPalette-CAWQfdXJ.js → CommandPalette-C4twwce1.js} +1 -1
  22. package/dist/hub/assets/{DiscoverScreen-Tl_Z6mgY.js → DiscoverScreen-SOJMVxIq.js} +2 -2
  23. package/dist/hub/assets/{DraftEditor-yIDapXTs.js → DraftEditor-DGK0Fbp1.js} +2 -2
  24. package/dist/hub/assets/{GapsScreen-DXZAlm6v.js → GapsScreen-pYuuMGEF.js} +1 -1
  25. package/dist/hub/assets/{HomeScreen-CEpiVhOa.js → HomeScreen-5rm4q3ZO.js} +1 -1
  26. package/dist/hub/assets/{InventoryScreen-CEG3Pmw_.js → InventoryScreen-CMKKrpTo.js} +1 -1
  27. package/dist/hub/assets/{LibraryScreen-IaVnxrX9.js → LibraryScreen-CV8lwjT6.js} +1 -1
  28. package/dist/hub/assets/{Opened-DJyf4Hnj.js → Opened-BvT3JnuG.js} +1 -1
  29. package/dist/hub/assets/{Preview-C069zVMP.js → Preview-DNOsVJ9-.js} +1 -1
  30. package/dist/hub/assets/{RuleDetail-B4g2-TRe.js → RuleDetail-BNQbhncD.js} +1 -1
  31. package/dist/hub/assets/{RulesScreen-CAaXIWjO.js → RulesScreen-ByYZB2Rs.js} +1 -1
  32. package/dist/hub/assets/{ScreenImage-DcI7_G3O.js → ScreenImage-Dypjm8_v.js} +1 -1
  33. package/dist/hub/assets/{ScreensScreen-DwczCjZD.js → ScreensScreen-bgwSlccW.js} +1 -1
  34. package/dist/hub/assets/{SearchField-DaCpNAj6.js → SearchField-DyTJpGOP.js} +1 -1
  35. package/dist/hub/assets/{ServiceDown-Bv-BPXro.js → ServiceDown-f9NYtkr_.js} +1 -1
  36. package/dist/hub/assets/{SpecimenCapture-DlsjshVE.js → SpecimenCapture-DFoGMhOM.js} +1 -1
  37. package/dist/hub/assets/{Specimens-C-lHp7sr.js → Specimens-BGwJj05V.js} +2 -2
  38. package/dist/hub/assets/{StackScreen-DkmELsqR.js → StackScreen-q132_QG3.js} +1 -1
  39. package/dist/hub/assets/{StubScreens-Bt7lWLVK.js → StubScreens-DWhYvJku.js} +1 -1
  40. package/dist/hub/assets/StyleScreen-CcgUv16e.js +1 -0
  41. package/dist/hub/assets/{Tabs-1QQDS0I6.js → Tabs-Dy1Z02s7.js} +1 -1
  42. package/dist/hub/assets/{TokenDetail-DvCw6GqC.js → TokenDetail-DsYgNrPw.js} +1 -1
  43. package/dist/hub/assets/{TokensScreen-DoENV8Xy.js → TokensScreen-CTT0GbAH.js} +1 -1
  44. package/dist/hub/assets/{client-D7whEyep.js → client-CIo9EOzr.js} +1 -1
  45. package/dist/hub/assets/color-C6bxX90C.js +1 -0
  46. package/dist/hub/assets/contrast-B-uhyF_b.js +1 -0
  47. package/dist/hub/assets/{discover-XjJEViwk.js → discover-DGlqvyIe.js} +1 -1
  48. package/dist/hub/assets/{dist-BYQYaPBV.js → dist-2euSoh6x.js} +1 -1
  49. package/dist/hub/assets/{dist-Bk4srtDn.js → dist-CpzxT6kj.js} +1 -1
  50. package/dist/hub/assets/{dist-D0z7Bvsy.js → dist-D8LFTd5W.js} +1 -1
  51. package/dist/hub/assets/{family-tiles-CyTdy_cP.js → family-tiles-Cc7tTwyr.js} +1 -1
  52. package/dist/hub/assets/main-Bjq_U0cU.js +2 -0
  53. package/dist/hub/assets/{try-live-zX3CSd3P.js → try-live-C-dwP1zF.js} +1 -1
  54. package/dist/hub/assets/values-cLekxBZj.js +1 -0
  55. package/dist/hub/assets/{workflow-jpV8wh-U.js → workflow-CnSY-W41.js} +1 -1
  56. package/dist/hub/index.html +1 -1
  57. package/dist/hub-previews.js +5 -5
  58. package/dist/hub-server.js +3 -3
  59. package/dist/hub-snapshot.js +7 -7
  60. package/dist/schema/youi.config.schema.json +4 -0
  61. package/dist/schema/youi.project.schema.json +1 -1
  62. package/dist/youi.js +6 -6
  63. package/dist/youiyoux.js +6 -6
  64. package/package.json +4 -4
  65. package/dist/hub/assets/StyleScreen-tq1tbRBo.js +0 -1
  66. package/dist/hub/assets/contrast-BkNHUAPa.js +0 -1
  67. package/dist/hub/assets/main-B9CIkk5m.js +0 -2
  68. package/dist/hub/assets/values-DzxR_TlI.js +0 -1
@@ -13,6 +13,7 @@ import {
13
13
  captureText,
14
14
  checkCapabilities,
15
15
  chosenSession,
16
+ collectComponents,
16
17
  compareToBaseline,
17
18
  componentCard,
18
19
  createBaseline,
@@ -50,9 +51,11 @@ import {
50
51
  shotsText,
51
52
  substituteVars,
52
53
  vendoredPath,
53
- whereUsed
54
- } from "./chunk-RHXBYN4W.js";
54
+ whereUsed,
55
+ withFile
56
+ } from "./chunk-O66KB7BU.js";
55
57
  import {
58
+ escapingLinks,
56
59
  within
57
60
  } from "./chunk-42YJX3W5.js";
58
61
  import {
@@ -64,7 +67,7 @@ import {
64
67
  libraryOfPackage,
65
68
  packageOf,
66
69
  playwrightCli
67
- } from "./chunk-S57F4CMN.js";
70
+ } from "./chunk-7P4UXAIY.js";
68
71
  import {
69
72
  MissingIndexError,
70
73
  buildGaps,
@@ -88,13 +91,17 @@ import {
88
91
  showFromCatalog,
89
92
  storeLocation,
90
93
  syncIndex
91
- } from "./chunk-XAO3LS7M.js";
94
+ } from "./chunk-3SQQNQDN.js";
92
95
  import {
96
+ DL_EXT,
93
97
  buildTokens,
94
98
  compareParity,
95
99
  cssValue,
96
- parseCssColor
97
- } from "./chunk-VOSWAWKC.js";
100
+ importTokens,
101
+ parseCssColor,
102
+ parseDimension,
103
+ readRules
104
+ } from "./chunk-TV5UX3FE.js";
98
105
  import {
99
106
  Baseline,
100
107
  DESIGN_CONFIG_FILE,
@@ -113,6 +120,7 @@ import {
113
120
  findFiles,
114
121
  findUp,
115
122
  getSystem,
123
+ implementationsDir,
116
124
  loadDesignRepo,
117
125
  loadLibrary,
118
126
  matchesAny,
@@ -128,17 +136,17 @@ import {
128
136
  shortHash,
129
137
  toPosix,
130
138
  writeFileAtomic
131
- } from "./chunk-DP54SKL4.js";
139
+ } from "./chunk-YWBQO7PO.js";
132
140
  import {
133
141
  __toESM
134
142
  } from "./chunk-WNTS7ZO4.js";
135
143
 
136
144
  // ../cli/src/main.ts
137
- var import_yaml3 = __toESM(require_dist(), 1);
145
+ var import_yaml4 = __toESM(require_dist(), 1);
138
146
  import { execFile as execFile5 } from "node:child_process";
139
147
  import { existsSync as existsSync5 } from "node:fs";
140
- import { appendFile as appendFile2, readFile as readFile8 } from "node:fs/promises";
141
- import { basename as basename3, dirname as dirname10, join as join22, relative as relative8, resolve as resolve12 } from "node:path";
148
+ import { appendFile as appendFile2, readFile as readFile10, rm as rm6, rmdir as rmdir3 } from "node:fs/promises";
149
+ import { basename as basename5, dirname as dirname12, join as join24, relative as relative10, resolve as resolve14 } from "node:path";
142
150
  import { parseArgs, promisify as promisify5 } from "node:util";
143
151
 
144
152
  // ../cli/src/check.ts
@@ -156,7 +164,7 @@ import { join as join2, relative, resolve } from "node:path";
156
164
 
157
165
  // ../cli/src/command.ts
158
166
  var COMMAND = "youiyoux";
159
- var SUBCOMMANDS = ["show", "where", "bom", "check", "baseline", "rules", "plan", "migrate", "init", "sync", "link", "unlink", "scan", "watch", "hooks", "agents", "mcp", "ui", "library", "discover", "index", "report", "render", "screens", "requests", "detect", "build", "parity", "schema", "inventory", "gaps", "help"];
167
+ var SUBCOMMANDS = ["show", "where", "bom", "check", "baseline", "rules", "plan", "migrate", "init", "adopt", "system", "sync", "link", "unlink", "scan", "watch", "hooks", "agents", "mcp", "ui", "library", "discover", "index", "report", "render", "screens", "requests", "detect", "build", "parity", "schema", "inventory", "gaps", "help"];
160
168
 
161
169
  // ../cli/src/agents.ts
162
170
  var import_yaml = __toESM(require_dist(), 1);
@@ -206,7 +214,7 @@ function enumSummary(contract) {
206
214
  return Object.entries(contract.props).filter(([, spec]) => spec.type === "enum").map(([name, spec]) => `${name}: ${spec.values.join("|")}`).join("; ");
207
215
  }
208
216
  function importHint(config) {
209
- return config.importFrom ? `\`${config.importFrom}\`` : `\`${config.componentsDir}\` (by a relative path from your file)`;
217
+ return config.importFrom ? `\`${withFile(config.importFrom)}\`` : `\`${config.componentsDir}\` (by a relative path from your file)`;
210
218
  }
211
219
  function agentsBlock(project2) {
212
220
  const { config, system } = project2;
@@ -260,7 +268,7 @@ function frontmatter(project2, build) {
260
268
  if (dark !== colors[key(t)]) colors[`${key(t)}-dark`] = dark;
261
269
  }
262
270
  const typography = {};
263
- for (const t of byGroup("typography")) {
271
+ for (const t of byGroup("typography").filter((t2) => t2.type === "typography")) {
264
272
  const v = t.light;
265
273
  typography[key(t)] = {
266
274
  fontFamily: cssValue("fontFamily", v.fontFamily),
@@ -466,8 +474,9 @@ async function initProject(options) {
466
474
  }
467
475
  notes.push(`owned components: ${componentsDir}`);
468
476
  const alias = await aliasRoot(root);
469
- const importFrom = alias !== void 0 && componentsDir.startsWith(alias) ? `@/${componentsDir.slice(alias.length)}` : void 0;
470
- if (importFrom) notes.push(`imports use the @/ alias: ${importFrom}`);
477
+ const barrel = (await Promise.all(["index.ts", "index.tsx", "index.js"].map((f) => exists(join2(root, componentsDir, f))))).some(Boolean);
478
+ const importFrom = alias !== void 0 && componentsDir.startsWith(alias) ? `@/${componentsDir.slice(alias.length)}${barrel ? "" : "/{file}"}` : void 0;
479
+ if (importFrom) notes.push(`imports use the @/ alias: ${importFrom}${barrel ? "" : " (each component from its own file; no index barrel)"}`);
471
480
  const include = (await Promise.all(
472
481
  [
473
482
  ["app", "app/**/*.{ts,tsx,css}"],
@@ -491,7 +500,8 @@ async function initProject(options) {
491
500
  componentsDir,
492
501
  ...importFrom ? { importFrom } : {},
493
502
  routes: expo ? { strategy: "expo-router", appDir } : { strategy: "none" },
494
- ...prefix ? { outputs: { cssVarPrefix: prefix } } : {}
503
+ ...options.outputs ? { outputs: options.outputs } : prefix ? { outputs: { cssVarPrefix: prefix } } : {},
504
+ ...options.tokenSources && options.tokenSources.length > 0 ? { tokenSources: [...options.tokenSources] } : {}
495
505
  });
496
506
  await writeFileAtomic(file, `${JSON.stringify(config, null, 2)}
497
507
  `);
@@ -617,7 +627,7 @@ var DRIFT_SEVERITY = { "near-duplicate": "warn", "hardcoded-value": "error", "of
617
627
  async function importOf(project2) {
618
628
  const alias = await aliasRoot(project2.root, project2.config.tsconfig);
619
629
  return (file, fromFile) => {
620
- if (project2.system && file.startsWith(`${project2.config.componentsDir}/`)) return importSpecifier(project2.config, fromFile);
630
+ if (project2.system && file.startsWith(`${project2.config.componentsDir}/`)) return importSpecifier(project2.config, fromFile, file);
621
631
  if (alias !== void 0 && file.startsWith(alias)) return `@/${file.slice(alias.length).replace(/\.(tsx?|jsx?|mjs)$/, "").replace(/\/index$/, "")}`;
622
632
  const path = posix.relative(posix.dirname(fromFile), file).replace(/\.(tsx?|jsx?|mjs|astro)$/, "").replace(/\/index$/, "");
623
633
  return path.startsWith(".") ? path : `./${path}`;
@@ -634,7 +644,7 @@ async function chosenLibraries(project2, added, notes) {
634
644
  const chosen = /* @__PURE__ */ new Map();
635
645
  for (const { contract } of project2.system?.components ?? []) {
636
646
  const type = EXPORT_TYPES[contract.name];
637
- if (type && !chosen.has(type)) chosen.set(type, { library: "system", label: `the System's ${contract.name} (${contract.id})`, fix: (from) => `import { ${contract.name} } from "${importSpecifier(project2.config, from)}"` });
647
+ if (type && !chosen.has(type)) chosen.set(type, { library: "system", label: `the System's ${contract.name} (${contract.id})`, fix: (from) => `import { ${contract.name} } from "${importSpecifier(project2.config, from, contract.source.file)}"` });
638
648
  }
639
649
  let hits;
640
650
  try {
@@ -917,23 +927,24 @@ var Writer = class {
917
927
  }
918
928
  async link(file, target) {
919
929
  const path = this.path(file);
920
- const stat3 = await lstat(path).catch(() => null);
921
- if (stat3?.isSymbolicLink() && await readlink(path) === target) return this.record({ file, role: "agents", action: "unchanged" });
922
- if (stat3 && !stat3.isSymbolicLink()) return this.record({ file, role: "agents", action: "skipped", detail: "a real directory is in the way" });
930
+ const stat4 = await lstat(path).catch(() => null);
931
+ if (stat4?.isSymbolicLink() && await readlink(path) === target) return this.record({ file, role: "agents", action: "unchanged" });
932
+ if (stat4 && !stat4.isSymbolicLink()) return this.record({ file, role: "agents", action: "skipped", detail: "a real directory is in the way" });
923
933
  if (!this.options.dryRun) {
924
934
  await mkdir(dirname2(path), { recursive: true });
925
- if (stat3) await rm(path);
935
+ if (stat4) await rm(path);
926
936
  await symlink(target, path);
927
937
  }
928
- this.record({ file, role: "agents", action: stat3 ? "updated" : "created", detail: `\u2192 ${target}` });
938
+ this.record({ file, role: "agents", action: stat4 ? "updated" : "created", detail: `\u2192 ${target}` });
929
939
  }
930
940
  };
931
941
  async function syncProject(project2, options = {}) {
932
942
  const { config, system, repo } = project2;
933
943
  const previous = await readLock(project2.root);
934
944
  const writer = new Writer(project2, options, previous);
945
+ const implementations = implementationsDir(system.config);
935
946
  for (const { contract } of system.components) {
936
- if (await readTextOrNull(join6(repo.root, system.config.components, contract.source.file)) === null) throw new Error(`${system.config.components}/${contract.source.file} is named by ${contract.id} but does not exist; nothing was written`);
947
+ if (await readTextOrNull(join6(repo.root, implementations, contract.source.file)) === null) throw new Error(`${implementations}/${contract.source.file} is named by ${contract.id} but does not exist; nothing was written`);
937
948
  }
938
949
  const build = await buildTokens(repo.root, system.config, config.outputs, { contracts: system.components.map((c) => c.contract) });
939
950
  for (const file of build.files) await writer.generated(`${config.outputs.dir}/${file.path}`, file.content, "tokens");
@@ -952,11 +963,15 @@ async function syncProject(project2, options = {}) {
952
963
  for (const support of system.config.support) if (!sources.has(support)) sources.set(support, { elements: system.components.map((c) => c.contract.id), version: system.config.version });
953
964
  const upstreams = /* @__PURE__ */ new Map();
954
965
  for (const source of sources.keys()) {
955
- const upstream = await readTextOrNull(join6(repo.root, system.config.components, source));
956
- if (upstream === null) throw new Error(`${system.config.components}/${source} is named by a contract but does not exist`);
966
+ const upstream = await readTextOrNull(join6(repo.root, implementations, source));
967
+ if (upstream === null) throw new Error(`${implementations}/${source} is named by a contract but does not exist`);
957
968
  upstreams.set(source, upstream);
958
969
  }
959
- for (const [source, { elements, version }] of sources) await writer.owned(vendoredPath(project2, source), upstreams.get(source), elements, version);
970
+ for (const [source, { elements, version }] of sources) {
971
+ const file = vendoredPath(project2, source);
972
+ if (await sameFile(join6(repo.root, implementations, source), join6(project2.root, file))) writer.record({ file, role: "component", action: "unchanged", detail: "the system's own implementation, in this project" });
973
+ else await writer.owned(file, upstreams.get(source), elements, version);
974
+ }
960
975
  await writer.retire();
961
976
  const lock = {
962
977
  schema: "youi.lock/v1",
@@ -970,6 +985,10 @@ async function syncProject(project2, options = {}) {
970
985
  `);
971
986
  return { actions: writer.actions, lock, unknownTools: unknownToolMentions(project2) };
972
987
  }
988
+ async function sameFile(a, b) {
989
+ const [x, y] = await Promise.all([realpath(a).catch(() => null), realpath(b).catch(() => null)]);
990
+ return x !== null && x === y;
991
+ }
973
992
  function mcpArgs(project2) {
974
993
  const rel = (path) => relative3(project2.root, path) || ".";
975
994
  return [
@@ -1760,18 +1779,18 @@ async function runClaudeHook(input, target = {}) {
1760
1779
  try {
1761
1780
  const project2 = await load(target, dirname5(file));
1762
1781
  const real = (path) => realpath3(path).catch(() => path);
1763
- const relative9 = relPath(await real(project2.root), await real(file));
1764
- if (!isChecked(project2, relative9)) return SILENT;
1765
- const found = await changeFor(() => workingTreeChange(project2, void 0, [relative9]));
1782
+ const relative11 = relPath(await real(project2.root), await real(file));
1783
+ if (!isChecked(project2, relative11)) return SILENT;
1784
+ const found = await changeFor(() => workingTreeChange(project2, void 0, [relative11]));
1766
1785
  if (found.problem !== void 0) return claudeContext(found.problem, project2.root);
1767
1786
  const { change } = found;
1768
1787
  const result = configured(project2) ? await runCheck(project2, { files: [file], lock: false, baseline: await baselineFor(project2, target), diff: change }) : await runRepoCheck(project2, change);
1769
1788
  const lines = findingLines(result, project2.root);
1770
- const see = COMPONENT_FILE.test(relative9) ? `See it: youiyoux render ${quoted(relative9)} (or the MCP tool ui_render), then open the images: light and dark, desktop and phone, accessibility issues and the diff against the base.` : null;
1771
- if (lines.length === 0) return see ? claudeContext(`youiyoux: ${relative9} passes the design-system check. ${see}`, project2.root) : SILENT;
1789
+ const see = COMPONENT_FILE.test(relative11) ? `See it: youiyoux render ${quoted(relative11)} (or the MCP tool ui_render), then open the images: light and dark, desktop and phone, accessibility issues and the diff against the base.` : null;
1790
+ if (lines.length === 0) return see ? claudeContext(`youiyoux: ${relative11} passes the design-system check. ${see}`, project2.root) : SILENT;
1772
1791
  const older = "violations" in result ? result.violations.filter((v) => !isAdded(change.added, v.file, v.line)).length : 0;
1773
1792
  const known = older > 0 ? ` (${older} older findings on unchanged lines omitted)` : "";
1774
- return claudeContext(`youiyoux design-system check (advisory) on ${relative9}${known}:
1793
+ return claudeContext(`youiyoux design-system check (advisory) on ${relative11}${known}:
1775
1794
  ${lines.join("\n")}${!configured(project2) ? `
1776
1795
  ${result.diff?.notes.join("\n")}` : ""}
1777
1796
  Fix these before finishing (each "Fix:" says what to write); \`youiyoux show <component>\` and the youiyoux MCP tools give the legal values. CI runs the authoritative check.${see ? `
@@ -1919,30 +1938,30 @@ var serialize = ({ data, indent, newline }) => `${JSON.stringify(data, null, ind
1919
1938
  async function installClaudeMcp(repoRoot, server) {
1920
1939
  const file = join11(repoRoot, CLAUDE_MCP);
1921
1940
  const before = await readTextOrNull(file);
1922
- const json2 = readJson2(file, before);
1923
- const servers = json2.data.mcpServers ?? {};
1924
- const next2 = { ...json2, data: { ...json2.data, mcpServers: { ...servers, [MCP_NAME]: { command: server.command, args: server.args } } } };
1941
+ const json4 = readJson2(file, before);
1942
+ const servers = json4.data.mcpServers ?? {};
1943
+ const next2 = { ...json4, data: { ...json4.data, mcpServers: { ...servers, [MCP_NAME]: { command: server.command, args: server.args } } } };
1925
1944
  return write(file, serialize(next2), before, `Claude Code starts the ${MCP_NAME} MCP server in this repo (it asks once before trusting a project .mcp.json); the command uses the installed tool or portable npx invocation; youiyoux sync writes the same entry`);
1926
1945
  }
1927
1946
  async function removeClaudeMcp(repoRoot) {
1928
1947
  const file = join11(repoRoot, CLAUDE_MCP);
1929
1948
  const before = await readTextOrNull(file);
1930
1949
  if (before === null) return { file, action: "unchanged", guarantee: `no ${MCP_NAME} server here` };
1931
- const json2 = readJson2(file, before);
1932
- const servers = { ...json2.data.mcpServers ?? {} };
1950
+ const json4 = readJson2(file, before);
1951
+ const servers = { ...json4.data.mcpServers ?? {} };
1933
1952
  if (!(MCP_NAME in servers)) return { file, action: "unchanged", guarantee: `no ${MCP_NAME} server here` };
1934
1953
  delete servers[MCP_NAME];
1935
- const { mcpServers: _, ...rest } = json2.data;
1936
- const data = Object.keys(servers).length > 0 ? { ...json2.data, mcpServers: servers } : rest;
1937
- return removeOrWrite(file, Object.keys(data).length > 0 ? serialize({ ...json2, data }) : "", `${MCP_NAME} removed; other servers kept`);
1954
+ const { mcpServers: _, ...rest } = json4.data;
1955
+ const data = Object.keys(servers).length > 0 ? { ...json4.data, mcpServers: servers } : rest;
1956
+ return removeOrWrite(file, Object.keys(data).length > 0 ? serialize({ ...json4, data }) : "", `${MCP_NAME} removed; other servers kept`);
1938
1957
  }
1939
1958
  var tomlString = (value) => JSON.stringify(value);
1940
1959
  function codexBlock(server) {
1941
1960
  return [TOML_BEGIN, `[mcp_servers.${MCP_NAME}]`, `command = ${tomlString(server.command)}`, `args = [${server.args.map(tomlString).join(", ")}]`, TOML_END].join("\n");
1942
1961
  }
1943
1962
  function foreignTable(text) {
1944
- const inside2 = withoutBlock(text, TOML_BEGIN, TOML_END) ?? text;
1945
- return new RegExp(`^\\s*\\[\\s*mcp_servers\\s*\\.\\s*"?${MCP_NAME}"?\\s*\\]`, "m").test(inside2);
1963
+ const inside3 = withoutBlock(text, TOML_BEGIN, TOML_END) ?? text;
1964
+ return new RegExp(`^\\s*\\[\\s*mcp_servers\\s*\\.\\s*"?${MCP_NAME}"?\\s*\\]`, "m").test(inside3);
1946
1965
  }
1947
1966
  async function installCodexMcp(repoRoot, server) {
1948
1967
  const file = join11(repoRoot, CODEX_CONFIG);
@@ -2066,7 +2085,7 @@ import { homedir, userInfo } from "node:os";
2066
2085
  import { join as join12, resolve as resolve6 } from "node:path";
2067
2086
  async function swapImpact(candidate, family) {
2068
2087
  try {
2069
- const { discoverIndexPath: discoverIndexPath2, sharedStore: sharedStore2 } = await import("./src-7RRKYIAS.js");
2088
+ const { discoverIndexPath: discoverIndexPath2, sharedStore: sharedStore2 } = await import("./src-DYZIE4XZ.js");
2070
2089
  const path = discoverIndexPath2();
2071
2090
  if (!process.env.DATABASE_URL && !existsSync(path)) return null;
2072
2091
  const impact = await (await sharedStore2(path, process.env, { readOnly: true })).swapImpact(candidate, family);
@@ -2077,7 +2096,7 @@ async function swapImpact(candidate, family) {
2077
2096
  }
2078
2097
  async function libraryLicence(id) {
2079
2098
  try {
2080
- const { discoverCacheDir: discoverCacheDir2, findListing } = await import("./src-7RRKYIAS.js");
2099
+ const { discoverCacheDir: discoverCacheDir2, findListing } = await import("./src-DYZIE4XZ.js");
2081
2100
  const { entry } = await findListing(discoverCacheDir2(void 0, process.env), id);
2082
2101
  return entry.id === id ? entry.license : null;
2083
2102
  } catch {
@@ -2085,7 +2104,7 @@ async function libraryLicence(id) {
2085
2104
  }
2086
2105
  }
2087
2106
  async function startRuleEditor(options) {
2088
- const { FileStore, RuleWorkflow, serveWorkflow } = await import("./src-VKQJF7KR.js");
2107
+ const { FileStore, RuleWorkflow, serveWorkflow } = await import("./src-4JGIPP5A.js");
2089
2108
  const projectRoot = resolve6(options.project);
2090
2109
  const load2 = { ...options.config ? { configPath: resolve6(options.config) } : {}, ...options.design ? { designRoot: resolve6(options.design) } : {}, ...options.root ? { productRoot: resolve6(options.root) } : {} };
2091
2110
  const project2 = await loadProject(projectRoot, load2);
@@ -2217,15 +2236,409 @@ function renderRequests(briefs, status, unreadable) {
2217
2236
  return lines.join("\n\n");
2218
2237
  }
2219
2238
 
2239
+ // ../cli/src/adopt.ts
2240
+ import { readFile as readFile4 } from "node:fs/promises";
2241
+ import { basename as basename2, dirname as dirname7, isAbsolute as isAbsolute5, join as join14, posix as posix3, relative as relative6, resolve as resolve8 } from "node:path";
2242
+ var import_yaml2 = __toESM(require_dist(), 1);
2243
+ var OUT_DIR = "design/build";
2244
+ var DARK_BLOCKS = [
2245
+ { selector: ".dark" },
2246
+ { selector: ":root.dark" },
2247
+ { selector: '[data-theme="dark"]' },
2248
+ { selector: ':root[data-theme="dark"]' },
2249
+ { selector: ":root", outer: "@media (prefers-color-scheme: dark)" },
2250
+ { selector: ':root:not([data-theme="light"])', outer: "@media (prefers-color-scheme: dark)" }
2251
+ ];
2252
+ var kebab = (name) => name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
2253
+ var pascal = (file) => basename2(file).replace(/\.[^.]+$/, "").split(/[-_.]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
2254
+ var json = (value) => `${JSON.stringify(value, null, 2)}
2255
+ `;
2256
+ function slugOf(name) {
2257
+ return name.replace(/^@[^/]+\//, "").toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^[^a-z]+/, "").replace(/-+$/, "") || "app";
2258
+ }
2259
+ function inside2(root, path, what) {
2260
+ const normal = posix3.normalize(path.split("\\").join("/")).replace(/^\.\//, "").replace(/\/+$/, "");
2261
+ if (isAbsolute5(normal) || normal === ".." || normal.startsWith("../") || !within(root, resolve8(root, normal))) throw new Error(`${what} ${JSON.stringify(path)} leads outside ${root}: adopt reads and writes only inside the product`);
2262
+ return normal;
2263
+ }
2264
+ function exportedNames(source) {
2265
+ const names = /* @__PURE__ */ new Set();
2266
+ for (const match of source.matchAll(/^export\s+(?:declare\s+)?(?:const|let|var|function\*?|class|async\s+function)\s+([A-Za-z_$][\w$]*)/gm)) names.add(match[1]);
2267
+ for (const match of source.matchAll(/^export\s+\{([^}]*)\}/gm)) {
2268
+ for (const part of match[1].split(",")) {
2269
+ const spec = part.trim();
2270
+ if (!spec || spec.startsWith("type ")) continue;
2271
+ names.add(spec.split(/\s+as\s+/).at(-1).trim());
2272
+ }
2273
+ }
2274
+ return [...names];
2275
+ }
2276
+ function defaultVariants(source) {
2277
+ const body = /defaultVariants\s*:\s*\{([^}]*)\}/.exec(source)?.[1] ?? "";
2278
+ return Object.fromEntries([...body.matchAll(/([A-Za-z_$][\w$]*)\s*:\s*["'`]([^"'`]+)["'`]/g)].map((m) => [m[1], m[2]]));
2279
+ }
2280
+ function propsOf(facts, defaults) {
2281
+ const props = {};
2282
+ for (const prop of facts?.props ?? []) {
2283
+ if (prop.values && prop.values.length > 0) props[prop.name] = { type: "enum", values: [...prop.values], required: false, ...defaults[prop.name] && prop.values.includes(defaults[prop.name]) ? { default: defaults[prop.name] } : {} };
2284
+ else if (prop.name === "className") props[prop.name] = { type: "string", required: false, description: "Extra classes, merged last." };
2285
+ else if (prop.name === "children") props[prop.name] = { type: "node", required: false };
2286
+ else if (/^on[A-Z]/.test(prop.name)) props[prop.name] = { type: "function", required: false };
2287
+ }
2288
+ return props;
2289
+ }
2290
+ function generatedOf(file, css) {
2291
+ const head = css.slice(0, 400);
2292
+ if (!head.includes(GENERATED_MARK)) return null;
2293
+ const from = /@generated by youi from the (.+?) design language v(\d+\.\d+\.\d+(?:-[\w.-]+?)?)\./.exec(head);
2294
+ return { file, count: rootVariables(css).length, system: from?.[1] ?? null, version: from?.[2] ?? null };
2295
+ }
2296
+ async function stylesheetOf(root, asked, fork) {
2297
+ const generated = [];
2298
+ let forked = null;
2299
+ const usable = async (file) => {
2300
+ const css = await readTextOrNull(join14(root, file));
2301
+ if (css === null) return false;
2302
+ const made = generatedOf(file, css);
2303
+ if (made && !generated.some((g) => g.file === file)) generated.push(made);
2304
+ if (made && fork) forked = made;
2305
+ return !made || fork;
2306
+ };
2307
+ if (asked) {
2308
+ if (!await usable(asked) && generated.length > 0) throw new Error(`${asked} is generated by youiyoux sync${generated[0].system ? ` from ${generated[0].system}` : ""}: name the stylesheet the product writes its own variables in, or take that System's values as the product's own with youiyoux adopt --fork`);
2309
+ return { file: asked, generated, forked };
2310
+ }
2311
+ const shadcn = (await readJson(join14(root, "components.json")))?.tailwind?.css;
2312
+ if (typeof shadcn === "string" && await usable(shadcn)) return { file: posix3.normalize(shadcn), generated, forked };
2313
+ let best = null;
2314
+ for (const file of await findFiles(root, ["**/*.css"], ["**/build/**", "**/out/**", "**/.next/**", `${OUT_DIR}/**`])) {
2315
+ const css = await readTextOrNull(join14(root, file)) ?? "";
2316
+ const count = rootVariables(css).length;
2317
+ if (count === 0) continue;
2318
+ const made = generatedOf(file, css);
2319
+ if (made && !generated.some((g) => g.file === file)) generated.push(made);
2320
+ if (made && !fork) continue;
2321
+ if (!best || count > best.count) best = { file, count, made };
2322
+ }
2323
+ return { file: best?.file ?? null, generated, forked: best?.made ?? forked };
2324
+ }
2325
+ function rootVariables(css) {
2326
+ return readRules(css).filter((r) => r.preludes.length === 1 && r.preludes[0] === ":root").flatMap((r) => r.declarations).filter((d) => d.property.startsWith("--") && !d.property.startsWith("---")).map((d) => ({ name: d.property.slice(2), value: d.value }));
2327
+ }
2328
+ var NOT_A_PREFIX = /* @__PURE__ */ new Set(["color", "colors", "bg", "fg", "radius", "rounded", "font", "text", "shadow", "space", "spacing", "size", "ease", "easing", "duration", "motion", "z", "leading", "tracking", "border", "ring", "chart", "sidebar", "background", "foreground", "primary", "secondary", "muted", "accent", "destructive", "card", "popover", "input", "typography", "brand", "gray", "grey", "neutral"]);
2329
+ function commonPrefix(names) {
2330
+ const counts = /* @__PURE__ */ new Map();
2331
+ for (const name of names) if (name.includes("-")) counts.set(name.split("-")[0], (counts.get(name.split("-")[0]) ?? 0) + 1);
2332
+ const [word, count] = [...counts].sort((a, b) => b[1] - a[1])[0] ?? ["", 0];
2333
+ return count >= 3 && count >= names.length * 0.8 && /^[a-z][a-z0-9]*$/.test(word) && !NOT_A_PREFIX.has(word) ? word : "";
2334
+ }
2335
+ function lengthPath(name, value) {
2336
+ if (parseDimension(value) === null && !/^var\(/.test(value)) return null;
2337
+ if (/radius/.test(name)) return `radius.${name.replace(/^radius-(?=.)/, "")}`;
2338
+ if (/^typography-/.test(name)) return `typography.${name.slice("typography-".length)}`;
2339
+ if (/^(?:text|font)-|-(?:font-size|line-height|letter-spacing|font-weight)$/.test(name)) return `typography.${name}`;
2340
+ return null;
2341
+ }
2342
+ function variableNames(tree, path = []) {
2343
+ return Object.entries(tree).flatMap(([key2, node]) => {
2344
+ if (key2.startsWith("$")) return [];
2345
+ if (node && typeof node === "object" && "$value" in node) {
2346
+ const css = node.$extensions?.[DL_EXT]?.css;
2347
+ return [css ?? [...path, key2].join("-")];
2348
+ }
2349
+ return variableNames(node, [...path, key2]);
2350
+ });
2351
+ }
2352
+ function moveDeclarations(css, blocks, moved, importPath) {
2353
+ const rules = readRules(css).filter((r) => blocks.some((b) => r.preludes.at(-1) === b.selector && (b.outer ? r.preludes.length === 2 && r.preludes[0] === b.outer : r.preludes.length === 1)));
2354
+ if (rules.length === 0) return null;
2355
+ const lines = css.split("\n");
2356
+ const drop = /* @__PURE__ */ new Set();
2357
+ for (const rule2 of rules) {
2358
+ for (const d of rule2.declarations) {
2359
+ const line = lines[d.line - 1] ?? "";
2360
+ if (moved.has(d.property.slice(2)) && new RegExp(`^\\s*${d.property.replace(/[-]/g, "\\-")}\\s*:[^;]*;?\\s*$`).test(line)) drop.add(d.line - 1);
2361
+ }
2362
+ }
2363
+ if (drop.size === 0) return null;
2364
+ let content = lines.filter((_, i) => !drop.has(i)).join("\n");
2365
+ for (const block of blocks) content = content.replace(new RegExp(`(^|\\n)[ \\t]*${block.selector.replace(/[.[\]()"=:]/g, "\\$&")}\\s*\\{\\s*\\}[ \\t]*(?=\\n|$)`, "g"), "");
2366
+ for (const outer of new Set(blocks.flatMap((b) => b.outer ? [b.outer] : []))) content = content.replace(new RegExp(`(^|\\n)[ \\t]*${outer.replace(/[.[\]()"=:]/g, "\\$&")}\\s*\\{\\s*\\}[ \\t]*(?=\\n|$)`, "g"), "");
2367
+ const statement = `@import "${importPath}";`;
2368
+ const imports = [...content.matchAll(/^@import[^;]*;[ \t]*$/gm)];
2369
+ const at = imports.length > 0 ? imports.at(-1).index + imports.at(-1)[0].length : 0;
2370
+ content = at > 0 ? `${content.slice(0, at)}
2371
+ ${statement}${content.slice(at)}` : `${statement}
2372
+ ${content}`;
2373
+ return { content: content.replace(/\n{3,}/g, "\n\n"), moved: drop.size };
2374
+ }
2375
+ async function planAdoption(options) {
2376
+ const root = options.root;
2377
+ const pkg = await readJson(join14(root, "package.json"));
2378
+ const name = typeof pkg?.name === "string" ? pkg.name : basename2(root);
2379
+ const id = options.system ?? slugOf(name);
2380
+ if (!/^[a-z][a-z0-9-]*$/.test(id)) throw new Error(`"${id}" cannot be a system id: use lowercase letters, digits and hyphens, starting with a letter (--system <id>)`);
2381
+ const notes = [];
2382
+ const facts = await collectComponents(root);
2383
+ const chosen = options.componentsDir ?? facts.shadcnUi ?? await uiFolder(root) ?? facts.componentsDir;
2384
+ const componentsDir = chosen ? inside2(root, chosen, "the components folder") : null;
2385
+ if (!componentsDir || !await exists(join14(root, componentsDir))) throw new Error("no folder of components found: pass --components <dir> (the folder your app imports its UI components from)");
2386
+ const inDir = (file) => posix3.dirname(file) === componentsDir;
2387
+ const files = (await findFiles(root, [`${componentsDir}/*.{ts,tsx,js,jsx}`])).filter((f) => !/^index\./.test(posix3.basename(f)) && !/\.(test|spec|stories|d)\.[^.]+$/.test(f));
2388
+ const byFile = /* @__PURE__ */ new Map();
2389
+ for (const c of facts.components) if (c.definition.kind === "react" && inDir(c.definition.file)) byFile.set(c.definition.file, [...byFile.get(c.definition.file) ?? [], c]);
2390
+ const out = [];
2391
+ const components = [];
2392
+ const support = [];
2393
+ const RULES = ["color.no-raw-color", "tokens.no-arbitrary-values", "components.canonical-import"].map((r) => `youi:${id}/rule/${r}`);
2394
+ for (const file of files) {
2395
+ const defined = (byFile.get(file) ?? []).filter((c) => /^[A-Z]/.test(c.export));
2396
+ const source = await readFile4(join14(root, file), "utf8");
2397
+ if (defined.length === 0) {
2398
+ support.push(posix3.basename(file));
2399
+ continue;
2400
+ }
2401
+ const main2 = defined.find((c) => c.export === pascal(file)) ?? defined[0];
2402
+ const folder = kebab(main2.export);
2403
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(folder)) {
2404
+ notes.push(`${file}: ${main2.export} has no contract (its name is not plain letters and digits); write one by hand`);
2405
+ continue;
2406
+ }
2407
+ const exportsOf = exportedNames(source);
2408
+ const sites = main2.sites.length;
2409
+ const contract = {
2410
+ schema: "youi.component/v1",
2411
+ id: `youi:${id}/component/${folder}`,
2412
+ name: main2.export,
2413
+ version: "0.1.0",
2414
+ status: "draft",
2415
+ summary: `The product's own ${main2.export} (${file}), used at ${sites} ${sites === 1 ? "site" : "sites"}.`.slice(0, 200),
2416
+ source: { file: posix3.basename(file), export: main2.export, also: defined.filter((c) => c !== main2).map((c) => c.export) },
2417
+ platforms: facts.dependencies.includes("react-native") && !facts.dependencies.includes("react-dom") ? ["ios", "android"] : ["web"],
2418
+ props: propsOf(main2, defaultVariants(source)),
2419
+ tokens: {},
2420
+ a11y: { notes: [] },
2421
+ rules: RULES,
2422
+ guidance: { do: [], dont: [] },
2423
+ examples: [],
2424
+ aliases: exportsOf.filter((n) => !/^[A-Z]/.test(n)),
2425
+ owners: []
2426
+ };
2427
+ out.push({ path: `design/components/${folder}/${folder}.contract.json`, content: json(contract) });
2428
+ components.push(main2.export);
2429
+ }
2430
+ if (components.length === 0) throw new Error(`${componentsDir} exports no React components: pass --components <dir> (the folder your app imports its UI components from)`);
2431
+ const found = await stylesheetOf(root, options.css, options.fork === true);
2432
+ const stylesheet = found.file ? inside2(root, found.file, "the stylesheet") : null;
2433
+ const css = stylesheet ? await readTextOrNull(join14(root, stylesheet)) : null;
2434
+ if (stylesheet && css === null) throw new Error(`${stylesheet} does not exist (--css <file>: the stylesheet that defines your :root variables)`);
2435
+ const darkBlocks = css ? DARK_BLOCKS.filter((b) => readRules(css).some((r) => r.preludes.at(-1) === b.selector && (b.outer ? r.preludes.length === 2 && r.preludes[0] === b.outer : r.preludes.length === 1) && r.declarations.some((d) => d.property.startsWith("--")))) : [];
2436
+ const darkBlock = darkBlocks[0];
2437
+ const variables = css ? rootVariables(css) : [];
2438
+ const prefix = commonPrefix(variables.map((v) => v.name));
2439
+ const full = (name2) => prefix ? `${prefix}-${name2}` : name2;
2440
+ const own = variables.filter((v) => !prefix || v.name.startsWith(`${prefix}-`)).map((v) => ({ name: prefix ? v.name.slice(prefix.length + 1) : v.name, value: v.value }));
2441
+ const unprefixed = variables.filter((v) => prefix && !v.name.startsWith(`${prefix}-`)).map((v) => v.name);
2442
+ if (unprefixed.length > 0) notes.push(`${plural(unprefixed.length, "variable")} without the --${prefix}- prefix stay in ${stylesheet}: ${unprefixed.slice(0, 4).map((n) => `--${n}`).join(", ")}${unprefixed.length > 4 ? ", \u2026" : ""}`);
2443
+ const paths = Object.fromEntries(own.flatMap((v) => lengthPath(v.name, v.value) ? [[v.name, lengthPath(v.name, v.value)]] : []));
2444
+ const imported = css ? importTokens({ css: { css, prefix, light: { selector: ":root" }, ...darkBlock ? { dark: darkBlock } : {} }, paths }) : null;
2445
+ const tokenCount = imported?.report.fromCss.length ?? 0;
2446
+ const hasDark = Boolean(imported && Object.keys(imported.dark).length > 0);
2447
+ out.push({ path: `design/tokens/${id}.tokens.json`, content: json(imported?.base ?? {}) });
2448
+ if (hasDark) out.push({ path: `design/tokens/${id}.dark.tokens.json`, content: json(imported.dark) });
2449
+ out.push({
2450
+ path: `design/tokens/${id}.resolver.json`,
2451
+ content: json({
2452
+ $schema: "https://www.designtokens.org/schemas/2025.10/resolver.json",
2453
+ name,
2454
+ version: "2025.10",
2455
+ description: `${name}'s tokens, adopted from ${stylesheet ?? "no stylesheet"}.`,
2456
+ sets: { base: { sources: [{ $ref: `${id}.tokens.json` }] } },
2457
+ ...hasDark ? { modifiers: { theme: { contexts: { light: [], dark: [{ $ref: `${id}.dark.tokens.json` }] }, default: "light" } } } : {},
2458
+ resolutionOrder: [{ $ref: "#/sets/base" }, ...hasDark ? [{ $ref: "#/modifiers/theme" }] : []]
2459
+ })
2460
+ });
2461
+ if (!stylesheet) notes.push("no stylesheet defines :root variables, so the System has no tokens yet: add them to design/tokens");
2462
+ const left = (imported?.report.unparsed ?? []).map((l) => ({ ...l, name: full(l.name) }));
2463
+ const moved = new Set(imported ? variableNames(imported.base).map(full) : []);
2464
+ const importPath = stylesheet ? relative6(dirname7(join14(root, stylesheet)), join14(root, OUT_DIR, "tokens.css")).split("\\").join("/") : "";
2465
+ const moving = css && moved.size > 0 ? moveDeclarations(css, [{ selector: ":root" }, ...darkBlocks], moved, importPath.startsWith(".") ? importPath : `./${importPath}`) : null;
2466
+ const rewritten = moving && found.forked ? { ...moving, content: moving.content.split("\n").filter((line) => !line.includes(GENERATED_MARK) && !/^\/\* Built with Terrazzo/.test(line)).join("\n") } : moving;
2467
+ if (found.forked) notes.push(`forked from ${found.forked.system ?? "the System"}${found.forked.version ? ` v${found.forked.version}` : ""}: this product's System starts from the ${found.forked.count} variables it generated into ${found.forked.file}, and the product no longer follows ${found.forked.system ?? "it"}`);
2468
+ if (css && moved.size > 0 && !rewritten) notes.push(`${stylesheet}'s variables were copied, not moved (their blocks are nested or share lines): import ${OUT_DIR}/tokens.css from it and remove them by hand`);
2469
+ for (const [rule2, content] of starterRules(id, componentsDir)) out.push({ path: `design/rules/${rule2}.rule.yaml`, content });
2470
+ out.push({
2471
+ path: "design/design.md",
2472
+ content: `# ${name}
2473
+
2474
+ The design system of ${name}, adopted from its own code: the components in \`${componentsDir}\` and the variables ${stylesheet ? `\`${stylesheet}\`` : "its stylesheet"} defined. Write here what each colour and component is for and the rules people follow; agents read it in DESIGN.md.
2475
+ `
2476
+ });
2477
+ const system = {
2478
+ id,
2479
+ name,
2480
+ version: "0.1.0",
2481
+ description: `${name}'s own components and tokens, adopted with youiyoux adopt.`,
2482
+ tokens: `design/tokens/${id}.resolver.json`,
2483
+ ...hasDark ? { themeModifier: { name: "theme", light: "light", dark: "dark" } } : {},
2484
+ components: "design/components",
2485
+ implementations: componentsDir,
2486
+ support,
2487
+ rules: "design/rules",
2488
+ design: "design/design.md"
2489
+ };
2490
+ out.unshift({ path: "youi.config.json", content: json({ schema: "youi.config/v1", name, systems: [system] }) });
2491
+ return {
2492
+ system,
2493
+ componentsDir,
2494
+ files: out,
2495
+ components,
2496
+ support,
2497
+ tokens: { stylesheet, count: tokenCount, dark: hasDark, left },
2498
+ stylesheet: rewritten && stylesheet ? { path: stylesheet, ...rewritten } : null,
2499
+ outputs: { dir: OUT_DIR, cssVarPrefix: prefix, dark: darkBlocks.map((b) => ({ ...b.outer ? { media: b.outer.replace(/^@media\s+/, "") } : {}, selector: b.selector })), targets: ["css-vars"] },
2500
+ tokenSources: stylesheet ? [stylesheet] : [],
2501
+ notes,
2502
+ follows: found.generated.filter((g) => g.file !== found.forked?.file),
2503
+ forked: found.forked
2504
+ };
2505
+ }
2506
+ async function uiFolder(root) {
2507
+ const counts = /* @__PURE__ */ new Map();
2508
+ for (const file of await findFiles(root, ["**/components/ui/*.{tsx,jsx}"], ["**/build/**", "**/out/**", "**/.next/**"])) counts.set(posix3.dirname(file), (counts.get(posix3.dirname(file)) ?? 0) + 1);
2509
+ return [...counts].sort((a, b) => b[1] - a[1] || a[0].length - b[0].length)[0]?.[0] ?? null;
2510
+ }
2511
+ function starterRules(id, componentsDir) {
2512
+ const rule2 = (spec) => (0, import_yaml2.stringify)({ schema: "youi.rule/v1", ...spec });
2513
+ return [
2514
+ ["color.no-raw-color", rule2({
2515
+ id: `youi:${id}/rule/color.no-raw-color`,
2516
+ title: "No raw colours",
2517
+ severity: "warn",
2518
+ statement: "Colour comes from the tokens. Never write a hex, rgb(), hsl() or oklch() literal in a component, class name, inline style or stylesheet; only the token source may define colour values.",
2519
+ rationale: "A literal colour cannot follow the dark scheme or a token change, and it hides from every audit.",
2520
+ agent: "Never hard-code a colour: use the token utilities (bg-primary, text-muted-foreground) or the variables (var(--primary)); look up the role with system_get_tokens.",
2521
+ check: { engine: "youi", id: "raw-color", options: {} },
2522
+ files: ["**/*.{ts,tsx,css}"]
2523
+ })],
2524
+ ["tokens.no-arbitrary-values", rule2({
2525
+ id: `youi:${id}/rule/tokens.no-arbitrary-values`,
2526
+ title: "Use token utilities, not arbitrary values",
2527
+ severity: "warn",
2528
+ statement: "When a token holds a value, use its utility instead of a Tailwind arbitrary value.",
2529
+ rationale: "An arbitrary value copies a token's value once; it does not follow when the token changes.",
2530
+ agent: "Prefer token utilities over [arbitrary] values; youiyoux check names the utility.",
2531
+ check: { engine: "youi", id: "arbitrary-value", options: {} },
2532
+ files: ["**/*.tsx"]
2533
+ })],
2534
+ ["components.canonical-import", rule2({
2535
+ id: `youi:${id}/rule/components.canonical-import`,
2536
+ title: "Render the System's component, not a lookalike",
2537
+ severity: "warn",
2538
+ statement: `A component named like one of the System's (Button, Input, Card) must be the one in ${componentsDir}. A second one with the same name splits the System in two.`,
2539
+ rationale: "Two Buttons drift apart: a fix to one never reaches the other.",
2540
+ agent: `Import components only from ${componentsDir}; never define or import another component with one of their names.`,
2541
+ check: { engine: "youi", id: "lookalike-component", options: {} },
2542
+ files: ["**/*.tsx"]
2543
+ })]
2544
+ ];
2545
+ }
2546
+
2547
+ // ../cli/src/extract.ts
2548
+ import { cp, mkdir as mkdir3, mkdtemp as mkdtemp3, readdir as readdir3, readFile as readFile5, realpath as realpath4, rename, rm as rm4, rmdir as rmdir2, stat } from "node:fs/promises";
2549
+ import { basename as basename3, dirname as dirname8, join as join15, posix as posix4, relative as relative7, resolve as resolve9, sep as sep2 } from "node:path";
2550
+ var json2 = (value) => `${JSON.stringify(value, null, 2)}
2551
+ `;
2552
+ var toPosix2 = (path) => path.split(sep2).join("/");
2553
+ async function isDirectory(path) {
2554
+ return (await stat(path).catch(() => null))?.isDirectory() ?? false;
2555
+ }
2556
+ async function heldInPlace(project2) {
2557
+ const [repo, root] = await Promise.all([realpath4(project2.repo.root), realpath4(project2.root)]);
2558
+ return repo === root && project2.system.config.implementations !== void 0;
2559
+ }
2560
+ function ownedPaths(project2, paths, kept) {
2561
+ const root = project2.repo.root;
2562
+ for (const path of paths) {
2563
+ const own = normalizeRelPath(path);
2564
+ if (own === "." || own.startsWith("../") || !within(root, join15(root, own)) || resolve9(root, own) === resolve9(root)) throw new Error(`the System's ${path} is not a folder of its own inside ${root}: move its files into one (design/\u2026) before extracting; nothing was changed`);
2565
+ for (const keep of kept.map((k) => normalizeRelPath(toPosix2(k)))) {
2566
+ if (keep === own || keep.startsWith(`${own}/`)) throw new Error(`the System's ${own} holds ${keep}, which stays in the product: move the System's files into a folder of their own before extracting; nothing was changed`);
2567
+ }
2568
+ }
2569
+ }
2570
+ async function extractSystem(project2, dir, options = {}) {
2571
+ if (!await heldInPlace(project2)) throw new Error(`${project2.root}'s System is already a design repo of its own (${project2.repo.root}): extract moves a System youiyoux adopt keeps inside the product`);
2572
+ const target = resolve9(dir);
2573
+ if (await isDirectory(target)) {
2574
+ if ((await readdir3(target)).length > 0) throw new Error(`${target} already exists and is not empty: extract into a new folder`);
2575
+ } else if (await stat(target).catch(() => null) !== null) throw new Error(`${target} already exists: extract into a new folder`);
2576
+ const { repo, system } = project2;
2577
+ const config = system.config;
2578
+ const from = (path) => join15(repo.root, path);
2579
+ const components = from(config.components);
2580
+ const implementations = from(implementationsDir(config));
2581
+ const tokensDir = posix4.dirname(config.tokens);
2582
+ const removed = [DESIGN_CONFIG_FILE, config.components, tokensDir, config.rules, ...config.design ? [config.design] : []];
2583
+ ownedPaths(project2, removed, [implementationsDir(config), relative7(project2.root, project2.configFile ?? join15(project2.root, PROJECT_FILE)), project2.config.outputs.dir, relative7(project2.root, target)]);
2584
+ const { implementations: _inPlace, ...rest } = config;
2585
+ const shared = config.support.map((file) => `_shared/${posix4.basename(file)}`);
2586
+ const next2 = { ...rest, tokens: `tokens/${posix4.relative(tokensDir, config.tokens)}`, components: "components", support: shared, rules: "rules", ...config.design ? { design: "design.md" } : {} };
2587
+ const written = [];
2588
+ const temp = await mkdtemp3(join15(dirname8(target), `.${basename3(target)}.`));
2589
+ try {
2590
+ const copy = async (source2, to) => {
2591
+ await mkdir3(dirname8(join15(temp, to)), { recursive: true });
2592
+ await cp(source2, join15(temp, to), { recursive: true });
2593
+ };
2594
+ await copy(components, "components");
2595
+ const placed = /* @__PURE__ */ new Map();
2596
+ for (const { contract, dir: folder } of system.components) {
2597
+ const contractFile = (await readdir3(folder)).find((name) => name.endsWith(".contract.json"));
2598
+ const at = toPosix2(relative7(components, folder));
2599
+ const file = placed.get(contract.source.file) ?? `${at}/${posix4.basename(contract.source.file)}`;
2600
+ if (!placed.has(contract.source.file)) {
2601
+ await copy(join15(implementations, contract.source.file), `components/${file}`);
2602
+ placed.set(contract.source.file, file);
2603
+ }
2604
+ const raw = JSON.parse(await readFile5(join15(folder, contractFile), "utf8"));
2605
+ await writeFileAtomic(join15(temp, "components", at, contractFile), json2({ ...raw, source: { ...raw.source, file } }));
2606
+ }
2607
+ for (const [i, file] of config.support.entries()) await copy(join15(implementations, file), `components/${shared[i]}`);
2608
+ await copy(from(tokensDir), "tokens");
2609
+ if (await isDirectory(from(config.rules))) await copy(from(config.rules), "rules");
2610
+ if (config.design) await copy(from(config.design), "design.md");
2611
+ await writeFileAtomic(join15(temp, DESIGN_CONFIG_FILE), json2({ schema: "youi.config/v1", name: repo.config.name, systems: [next2] }));
2612
+ if (options.package) await writeFileAtomic(join15(temp, "package.json"), json2({ name: options.package, version: config.version, description: config.description, files: [DESIGN_CONFIG_FILE, "components", "tokens", "rules", ...config.design ? ["design.md"] : []] }));
2613
+ const leaving = escapingLinks(temp);
2614
+ if (leaving.length > 0) throw new Error(`the System has links that lead outside it (${leaving.slice(0, 3).join(", ")}); nothing was changed`);
2615
+ await loadDesignRepo(temp);
2616
+ for (const entry of await readdir3(temp, { recursive: true })) if (!await isDirectory(join15(temp, entry))) written.push(toPosix2(entry));
2617
+ written.sort();
2618
+ const source = { path: toPosix2(relative7(project2.root, target)) || "." };
2619
+ if (!options.dryRun) {
2620
+ if (await isDirectory(target)) await rmdir2(target);
2621
+ await rename(temp, target);
2622
+ const configFile = project2.configFile ?? join15(project2.root, PROJECT_FILE);
2623
+ const raw = JSON.parse(await readTextOrNull(configFile) ?? "{}");
2624
+ await writeFileAtomic(configFile, json2({ ...raw, source }));
2625
+ for (const path of removed) await rm4(join15(repo.root, path), { recursive: true, force: true });
2626
+ }
2627
+ return { target, system: next2, written, removed, source };
2628
+ } finally {
2629
+ await rm4(temp, { recursive: true, force: true });
2630
+ }
2631
+ }
2632
+
2220
2633
  // ../cli/src/index-store.ts
2221
- import { appendFile, mkdir as mkdir3, readFile as readFile4, stat, rename, mkdtemp as mkdtemp3, rm as rm4 } from "node:fs/promises";
2222
- import { dirname as dirname7, join as join14, resolve as resolve8 } from "node:path";
2634
+ import { appendFile, mkdir as mkdir4, readFile as readFile6, stat as stat2, rename as rename2, mkdtemp as mkdtemp4, rm as rm5 } from "node:fs/promises";
2635
+ import { dirname as dirname9, join as join16, resolve as resolve10 } from "node:path";
2223
2636
  var SOURCES_FILE = "index-sources.json";
2224
2637
  var SCANS_LOG = "scans.jsonl";
2225
2638
  var EMPTY = { discover: null, inventories: [], projects: [] };
2226
2639
  async function readIndexSources(home) {
2227
2640
  try {
2228
- const raw = JSON.parse(await readFile4(join14(home, SOURCES_FILE), "utf8"));
2641
+ const raw = JSON.parse(await readFile6(join16(home, SOURCES_FILE), "utf8"));
2229
2642
  return { discover: raw.discover ?? null, inventories: raw.inventories ?? [], projects: raw.projects ?? [] };
2230
2643
  } catch {
2231
2644
  return EMPTY;
@@ -2238,14 +2651,14 @@ async function noteSource(home, change) {
2238
2651
  inventories: change.inventory && !sources.inventories.includes(change.inventory) ? [...sources.inventories, change.inventory] : sources.inventories,
2239
2652
  projects: change.project ? [...sources.projects.filter((p) => p.root !== change.project.root), change.project] : sources.projects
2240
2653
  };
2241
- await writeFileAtomic(join14(home, SOURCES_FILE), `${JSON.stringify(next2, null, 2)}
2654
+ await writeFileAtomic(join16(home, SOURCES_FILE), `${JSON.stringify(next2, null, 2)}
2242
2655
  `);
2243
2656
  }
2244
2657
  async function recordInventory(store, home, report, file) {
2245
- const run7 = inventoryRun(report, resolve8(file));
2658
+ const run7 = inventoryRun(report, resolve10(file));
2246
2659
  const logged = { run: run7.run, source: run7.source, scannedAt: run7.scannedAt, projects: run7.projects, families: run7.families };
2247
- await mkdir3(home, { recursive: true });
2248
- await appendFile(join14(home, SCANS_LOG), `${JSON.stringify({ schema: "youi.scan-log/v1", ...logged })}
2660
+ await mkdir4(home, { recursive: true });
2661
+ await appendFile(join16(home, SCANS_LOG), `${JSON.stringify({ schema: "youi.scan-log/v1", ...logged })}
2249
2662
  `);
2250
2663
  await store.recordInventory(run7);
2251
2664
  await noteSource(home, { inventory: run7.source });
@@ -2256,7 +2669,7 @@ async function recordProject(store, project2, usage) {
2256
2669
  await store.replaceGraph(`project:${project2.root}`, projectGraph({ project: project2.config.id, rules: project2.system.rules, components: project2.system.components.map((c) => ({ id: c.contract.id, name: c.contract.name })), usage }));
2257
2670
  }
2258
2671
  async function readLog(home) {
2259
- const text = await readFile4(join14(home, SCANS_LOG), "utf8").catch(() => "");
2672
+ const text = await readFile6(join16(home, SCANS_LOG), "utf8").catch(() => "");
2260
2673
  return text.split("\n").flatMap((line) => {
2261
2674
  if (!line.trim()) return [];
2262
2675
  try {
@@ -2271,9 +2684,9 @@ async function rebuildIndex(path, options = {}) {
2271
2684
  const started = performance.now();
2272
2685
  const env = options.env ?? process.env;
2273
2686
  const home = indexHome(path, env);
2274
- await mkdir3(dirname7(path), { recursive: true });
2275
- const scratch = !env.DATABASE_URL ? await mkdtemp3(`${path}.rebuild-`) : null;
2276
- const target = scratch ? join14(scratch, "index.db") : path;
2687
+ await mkdir4(dirname9(path), { recursive: true });
2688
+ const scratch = !env.DATABASE_URL ? await mkdtemp4(`${path}.rebuild-`) : null;
2689
+ const target = scratch ? join16(scratch, "index.db") : path;
2277
2690
  const store = scratch ? await openSqlite(target) : await sharedStore(path, env);
2278
2691
  try {
2279
2692
  if (env.DATABASE_URL) await store.reset();
@@ -2281,7 +2694,7 @@ async function rebuildIndex(path, options = {}) {
2281
2694
  const sources = await readIndexSources(home);
2282
2695
  const cache = options.discoverCache ?? sources.discover;
2283
2696
  let discover = { cache, chosen: 0, all: 0 };
2284
- if (cache && await stat(join14(cache, "index.json")).then(() => true, () => false)) {
2697
+ if (cache && await stat2(join16(cache, "index.json")).then(() => true, () => false)) {
2285
2698
  const loaded = await ingestCatalog(store, cache);
2286
2699
  discover = { cache, chosen: loaded.chosen, all: loaded.all };
2287
2700
  }
@@ -2302,14 +2715,14 @@ async function rebuildIndex(path, options = {}) {
2302
2715
  let projects = 0;
2303
2716
  const gone = [];
2304
2717
  for (const source of sources.projects) {
2305
- if (!await stat(source.root).then((s) => s.isDirectory(), () => false)) {
2718
+ if (!await stat2(source.root).then((s) => s.isDirectory(), () => false)) {
2306
2719
  gone.push(source.root);
2307
2720
  failed.push({ root: source.root, reason: "folder not found (moved or deleted), skipped" });
2308
2721
  continue;
2309
2722
  }
2310
2723
  try {
2311
2724
  const project2 = await loadProject(source.root, source.options);
2312
- const usage = ScanReport.safeParse(await readJson(source.usage ?? join14(project2.root, ".youi/usage.json")));
2725
+ const usage = ScanReport.safeParse(await readJson(source.usage ?? join16(project2.root, ".youi/usage.json")));
2313
2726
  await recordProject(store, project2, usage.success ? usage.data : null);
2314
2727
  projects++;
2315
2728
  } catch (error) {
@@ -2323,20 +2736,20 @@ async function rebuildIndex(path, options = {}) {
2323
2736
  if (scratch) {
2324
2737
  await store.close();
2325
2738
  checkpointSqlite(target);
2326
- if (await stat(path).then(() => true, () => false)) checkpointSqlite(path);
2739
+ if (await stat2(path).then(() => true, () => false)) checkpointSqlite(path);
2327
2740
  const moved = [];
2328
2741
  try {
2329
2742
  for (const suffix of ["-wal", "-shm"]) {
2330
2743
  try {
2331
- await rename(`${path}${suffix}`, `${target}.old${suffix}`);
2744
+ await rename2(`${path}${suffix}`, `${target}.old${suffix}`);
2332
2745
  moved.push(suffix);
2333
2746
  } catch (error) {
2334
2747
  if (error.code !== "ENOENT") throw error;
2335
2748
  }
2336
2749
  }
2337
- await rename(target, path);
2750
+ await rename2(target, path);
2338
2751
  } catch (error) {
2339
- for (const suffix of moved) await rename(`${target}.old${suffix}`, `${path}${suffix}`);
2752
+ for (const suffix of moved) await rename2(`${target}.old${suffix}`, `${path}${suffix}`);
2340
2753
  throw error;
2341
2754
  }
2342
2755
  }
@@ -2344,7 +2757,7 @@ async function rebuildIndex(path, options = {}) {
2344
2757
  } finally {
2345
2758
  if (scratch) {
2346
2759
  await store.close();
2347
- await rm4(scratch, { recursive: true, force: true });
2760
+ await rm5(scratch, { recursive: true, force: true });
2348
2761
  }
2349
2762
  }
2350
2763
  }
@@ -2527,8 +2940,8 @@ function renderAdoptionRequest(request) {
2527
2940
 
2528
2941
  // ../cli/src/report/run.ts
2529
2942
  import { randomBytes as randomBytes2 } from "node:crypto";
2530
- import { mkdir as mkdir4, rename as rename2, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
2531
- import { join as join21, relative as relative7, resolve as resolve11 } from "node:path";
2943
+ import { mkdir as mkdir5, rename as rename3, stat as stat3, writeFile as writeFile2 } from "node:fs/promises";
2944
+ import { join as join23, relative as relative9, resolve as resolve13 } from "node:path";
2532
2945
 
2533
2946
  // ../cli/src/report/card.ts
2534
2947
  import { deflateSync } from "node:zlib";
@@ -2763,8 +3176,8 @@ function drawCard(input) {
2763
3176
  }
2764
3177
 
2765
3178
  // ../cli/src/report/model.ts
2766
- import { readFile as readFile6 } from "node:fs/promises";
2767
- import { join as join18 } from "node:path";
3179
+ import { readFile as readFile8 } from "node:fs/promises";
3180
+ import { join as join20 } from "node:path";
2768
3181
 
2769
3182
  // ../cli/src/report/filters.ts
2770
3183
  var SHOWCASE_GLOBS = ["**/{__stories__,.storybook,storybook,storybook-static,example,examples,demo,demos,playground,showcase,showcases}/**", "**/*.stories.*", "**/*.story.*"];
@@ -2891,11 +3304,11 @@ function publicLook(caption) {
2891
3304
 
2892
3305
  // ../cli/src/report/groups.ts
2893
3306
  import { existsSync as existsSync3 } from "node:fs";
2894
- import { join as join16 } from "node:path";
3307
+ import { join as join18 } from "node:path";
2895
3308
 
2896
3309
  // ../cli/src/report/imports.ts
2897
3310
  import { realpathSync } from "node:fs";
2898
- import { dirname as dirname8, join as join15, relative as relative6, resolve as resolve9 } from "node:path";
3311
+ import { dirname as dirname10, join as join17, relative as relative8, resolve as resolve11 } from "node:path";
2899
3312
  import ts from "typescript";
2900
3313
  var resolvers = /* @__PURE__ */ new Map();
2901
3314
  function resolverOf(root) {
@@ -2903,7 +3316,7 @@ function resolverOf(root) {
2903
3316
  if (!found) {
2904
3317
  const config = ts.findConfigFile(root, ts.sys.fileExists, "tsconfig.json") ?? ts.findConfigFile(root, ts.sys.fileExists, "jsconfig.json");
2905
3318
  const read = config ? ts.readConfigFile(config, ts.sys.readFile) : null;
2906
- const options = config && read && !read.error ? ts.parseJsonConfigFileContent(read.config, ts.sys, dirname8(config)).options : {};
3319
+ const options = config && read && !read.error ? ts.parseJsonConfigFileContent(read.config, ts.sys, dirname10(config)).options : {};
2907
3320
  found = { options, cache: ts.createModuleResolutionCache(root, (name) => name, options) };
2908
3321
  resolvers.set(root, found);
2909
3322
  }
@@ -2914,13 +3327,13 @@ function clearImportCache() {
2914
3327
  }
2915
3328
  async function verifiedImport(root, keeper, from, suggested) {
2916
3329
  const { options, cache } = resolverOf(root);
2917
- let local = relative6(dirname8(join15(root, from)), join15(root, keeper)).replaceAll("\\", "/").replace(/\.(tsx?|jsx?|mjs)$/, "");
3330
+ let local = relative8(dirname10(join17(root, from)), join17(root, keeper)).replaceAll("\\", "/").replace(/\.(tsx?|jsx?|mjs)$/, "");
2918
3331
  if (!local.startsWith(".")) local = `./${local}`;
2919
3332
  for (const specifier of /* @__PURE__ */ new Set([suggested, local])) {
2920
- const found = ts.resolveModuleName(specifier, resolve9(root, from), options, ts.sys, cache).resolvedModule?.resolvedFileName;
3333
+ const found = ts.resolveModuleName(specifier, resolve11(root, from), options, ts.sys, cache).resolvedModule?.resolvedFileName;
2921
3334
  if (found) {
2922
3335
  try {
2923
- if (realpathSync(found) === realpathSync(join15(root, keeper))) return specifier;
3336
+ if (realpathSync(found) === realpathSync(join17(root, keeper))) return specifier;
2924
3337
  } catch {
2925
3338
  }
2926
3339
  }
@@ -3033,7 +3446,7 @@ var NEAR_IDENTICAL = 0.5;
3033
3446
  var MIN_STRUCTURE = 6;
3034
3447
  var GENERIC_IDENTICAL = 0.85;
3035
3448
  var repoPath = (dir, file) => dir === "." ? file : `${dir}/${file}`;
3036
- var kebab = (name) => name.replace(/[a-z0-9](?=[A-Z])/g, "$&-").toLowerCase();
3449
+ var kebab2 = (name) => name.replace(/[a-z0-9](?=[A-Z])/g, "$&-").toLowerCase();
3037
3450
  function shapeWords(shape) {
3038
3451
  const { height, paddingX, radius, fontSize } = shape;
3039
3452
  return [height !== null ? `${height}px high` : null, paddingX !== null ? `${paddingX}px side padding` : null, radius !== null ? radius === 9999 ? "pill-shaped" : `${radius}px corners` : null, fontSize !== null ? `${fontSize}px text` : null].filter(Boolean).join(", ");
@@ -3134,7 +3547,7 @@ async function nearDuplicateGroups(input) {
3134
3547
  const name = EXTRACT_NAMES[kind];
3135
3548
  const fileIn = (f, dir2) => {
3136
3549
  for (let n = 1; ; n++) {
3137
- const file = `${dir2 ? `${dir2}/` : ""}${kebab(name)}${n === 1 ? "" : `-${n}`}.tsx`;
3550
+ const file = `${dir2 ? `${dir2}/` : ""}${kebab2(name)}${n === 1 ? "" : `-${n}`}.tsx`;
3138
3551
  if (![...defs.values()].some((d) => d.facts === f && d.def.file === file)) return file;
3139
3552
  }
3140
3553
  };
@@ -3328,22 +3741,22 @@ function factsOf(project2, id, modules, eligible, workspaceNames, dependencies,
3328
3741
  if (dirs.has(file.slice(0, at))) break;
3329
3742
  dirs.add(file.slice(0, at));
3330
3743
  }
3331
- const roots = [...dirs].filter((dir) => existsSync3(join16(project2.root, dir, "package.json"))).sort((a, b) => b.length - a.length);
3744
+ const roots = [...dirs].filter((dir) => existsSync3(join18(project2.root, dir, "package.json"))).sort((a, b) => b.length - a.length);
3332
3745
  const nested = (file) => roots.find((dir) => file.startsWith(`${dir}/`)) ?? "";
3333
3746
  return { project: project2, id, dependencies, packageImports, jsxCounts, eligible, twins, dirs, importer, packageOf: nested };
3334
3747
  }
3335
3748
 
3336
3749
  // ../cli/src/report/projects.ts
3337
- var import_yaml2 = __toESM(require_dist(), 1);
3338
- import { readFile as readFile5 } from "node:fs/promises";
3339
- import { basename as basename2, join as join17, resolve as resolve10 } from "node:path";
3750
+ var import_yaml3 = __toESM(require_dist(), 1);
3751
+ import { readFile as readFile7 } from "node:fs/promises";
3752
+ import { basename as basename4, join as join19, resolve as resolve12 } from "node:path";
3340
3753
  var UI_FILES = ["**/*.{tsx,jsx,astro,vue,svelte}"];
3341
3754
  var strings = (value) => Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
3342
3755
  async function workspaceGlobs(root, pkg) {
3343
- const pnpm = await readFile5(join17(root, "pnpm-workspace.yaml"), "utf8").catch(() => null);
3756
+ const pnpm = await readFile7(join19(root, "pnpm-workspace.yaml"), "utf8").catch(() => null);
3344
3757
  if (pnpm !== null) {
3345
3758
  try {
3346
- const globs2 = strings((0, import_yaml2.parse)(pnpm)?.packages);
3759
+ const globs2 = strings((0, import_yaml3.parse)(pnpm)?.packages);
3347
3760
  if (globs2.length > 0) return { globs: globs2, source: "pnpm-workspace.yaml" };
3348
3761
  } catch {
3349
3762
  }
@@ -3354,9 +3767,9 @@ async function workspaceGlobs(root, pkg) {
3354
3767
  }
3355
3768
  var hasUi = async (dir) => (await findFiles(dir, UI_FILES, DEFAULT_EXCLUDE)).length > 0;
3356
3769
  async function findProjects(dir) {
3357
- const root = resolve10(dir);
3358
- const pkg = await readJson(join17(root, "package.json"));
3359
- const name = typeof pkg?.name === "string" && pkg.name.trim() ? pkg.name.trim() : basename2(root);
3770
+ const root = resolve12(dir);
3771
+ const pkg = await readJson(join19(root, "package.json"));
3772
+ const name = typeof pkg?.name === "string" && pkg.name.trim() ? pkg.name.trim() : basename4(root);
3360
3773
  const single = { dir: ".", root, name: typeof pkg?.name === "string" ? pkg.name : null };
3361
3774
  const workspace = await workspaceGlobs(root, pkg);
3362
3775
  if (!workspace) return { root, name, layout: "one project", projects: [single], skipped: [] };
@@ -3366,12 +3779,12 @@ async function findProjects(dir) {
3366
3779
  const projects = [];
3367
3780
  const skipped = [];
3368
3781
  for (const member of members) {
3369
- const memberRoot = join17(root, member);
3782
+ const memberRoot = join19(root, member);
3370
3783
  if (!await hasUi(memberRoot)) {
3371
3784
  skipped.push(member);
3372
3785
  continue;
3373
3786
  }
3374
- const memberPkg = await readJson(join17(memberRoot, "package.json"));
3787
+ const memberPkg = await readJson(join19(memberRoot, "package.json"));
3375
3788
  projects.push({ dir: member, root: memberRoot, name: typeof memberPkg?.name === "string" ? memberPkg.name : null });
3376
3789
  }
3377
3790
  if (projects.length === 0) return { root, name, layout: `one project (${workspace.source} names no package with UI files)`, projects: [single], skipped };
@@ -3398,7 +3811,7 @@ var Lines = class {
3398
3811
  if (!this.enabled) return { at };
3399
3812
  let lines = this.files.get(file);
3400
3813
  if (!lines) {
3401
- lines = (await readFile6(join18(this.root, file), "utf8").catch(() => "")).split("\n");
3814
+ lines = (await readFile8(join20(this.root, file), "utf8").catch(() => "")).split("\n");
3402
3815
  this.files.set(file, lines);
3403
3816
  }
3404
3817
  const code = lines[line - 1]?.trim();
@@ -3418,7 +3831,7 @@ async function modulesOf(project2, target, failed) {
3418
3831
  for (const file of target.files) {
3419
3832
  if (isShowcase(file)) continue;
3420
3833
  try {
3421
- const module = await parseModule(file, await readFile6(join18(project2.root, file), "utf8"));
3834
+ const module = await parseModule(file, await readFile8(join20(project2.root, file), "utf8"));
3422
3835
  if (module) modules.push(module);
3423
3836
  } catch (error) {
3424
3837
  failed.push(`${repoPath(project2.dir, file)}: ${errorText(error)}`);
@@ -3522,7 +3935,7 @@ async function analyseProject(project2, index, inventory, inventoryError, worksp
3522
3935
  incomplete.push(`Library choices were not compared (${errorText(error)}); run ${NPX} detect ${project2.dir} to see why`);
3523
3936
  }
3524
3937
  }
3525
- const pkg = await readFile6(join18(project2.root, "package.json"), "utf8").then((text) => JSON.parse(text), () => null);
3938
+ const pkg = await readFile8(join20(project2.root, "package.json"), "utf8").then((text) => JSON.parse(text), () => null);
3526
3939
  const dependencies = new Set(["dependencies", "devDependencies", "peerDependencies"].flatMap((key2) => Object.keys(pkg?.[key2] ?? {})));
3527
3940
  const importer = changeProject ? await importOf(changeProject) : () => "";
3528
3941
  const facts = factsOf(project2, projectId ?? null, modules, eligible, workspaceNames, dependencies, target?.files ?? [], importer);
@@ -3966,36 +4379,36 @@ ${model.projects.map((p) => projectSection(p, many, looks)).join("\n")}
3966
4379
  import { spawn as spawn2 } from "node:child_process";
3967
4380
  import { existsSync as existsSync4, readFileSync as readFileSync2 } from "node:fs";
3968
4381
  import { homedir as homedir2 } from "node:os";
3969
- import { dirname as dirname9, join as join19 } from "node:path";
4382
+ import { dirname as dirname11, join as join21 } from "node:path";
3970
4383
  var LOOKS_PER_PROJECT = 4;
3971
4384
  var DOWNLOAD_SIZE = "~100 MB";
3972
4385
  var DEPENDENCIES = "Install the repo's dependencies to see rendered looks";
3973
4386
  var UNDRAWABLE = /not installed|loads no stylesheet|drawn from (?:its|their) (?:tag and )?classes|did not render|bundle failed|unstyled|stub fallback/i;
3974
- var playwrightDir = () => dirname9(playwrightCli());
4387
+ var playwrightDir = () => dirname11(playwrightCli());
3975
4388
  function browsersPath(env) {
3976
4389
  const set = env.PLAYWRIGHT_BROWSERS_PATH;
3977
- if (set === "0") return join19(playwrightDir(), ".local-browsers");
4390
+ if (set === "0") return join21(playwrightDir(), ".local-browsers");
3978
4391
  if (set) return set;
3979
- if (process.platform === "darwin") return join19(homedir2(), "Library/Caches/ms-playwright");
3980
- if (process.platform === "win32") return join19(env.LOCALAPPDATA ?? join19(homedir2(), "AppData/Local"), "ms-playwright");
3981
- return join19(env.XDG_CACHE_HOME ?? join19(homedir2(), ".cache"), "ms-playwright");
4392
+ if (process.platform === "darwin") return join21(homedir2(), "Library/Caches/ms-playwright");
4393
+ if (process.platform === "win32") return join21(env.LOCALAPPDATA ?? join21(homedir2(), "AppData/Local"), "ms-playwright");
4394
+ return join21(env.XDG_CACHE_HOME ?? join21(homedir2(), ".cache"), "ms-playwright");
3982
4395
  }
3983
4396
  function chromiumStatus(env = process.env) {
3984
4397
  if (env.YOUI_CHROMIUM) return { available: existsSync4(env.YOUI_CHROMIUM), where: env.YOUI_CHROMIUM };
3985
4398
  try {
3986
- const browsers = JSON.parse(readFileSync2(join19(playwrightDir(), "browsers.json"), "utf8"));
4399
+ const browsers = JSON.parse(readFileSync2(join21(playwrightDir(), "browsers.json"), "utf8"));
3987
4400
  const revision2 = browsers.browsers.find((b) => b.name === "chromium-headless-shell")?.revision;
3988
- const dir = join19(browsersPath(env), `chromium_headless_shell-${revision2}`);
3989
- return { available: revision2 !== void 0 && existsSync4(join19(dir, "INSTALLATION_COMPLETE")), where: dir };
4401
+ const dir = join21(browsersPath(env), `chromium_headless_shell-${revision2}`);
4402
+ return { available: revision2 !== void 0 && existsSync4(join21(dir, "INSTALLATION_COMPLETE")), where: dir };
3990
4403
  } catch {
3991
4404
  return { available: false, where: "playwright-core is not installed" };
3992
4405
  }
3993
4406
  }
3994
4407
  function downloadChromium() {
3995
- return new Promise((resolve13) => {
3996
- const child = spawn2(process.execPath, [join19(playwrightDir(), "cli.js"), "install", "chromium-headless-shell"], { stdio: ["ignore", 2, 2] });
3997
- child.on("error", () => resolve13(false));
3998
- child.on("close", (code) => resolve13(code === 0));
4408
+ return new Promise((resolve15) => {
4409
+ const child = spawn2(process.execPath, [join21(playwrightDir(), "cli.js"), "install", "chromium-headless-shell"], { stdio: ["ignore", 2, 2] });
4410
+ child.on("error", () => resolve15(false));
4411
+ child.on("close", (code) => resolve15(code === 0));
3999
4412
  });
4000
4413
  }
4001
4414
  var dataUri = (file) => file && existsSync4(file) ? `data:image/png;base64,${readFileSync2(file).toString("base64")}` : null;
@@ -4009,24 +4422,24 @@ async function addLooks(model, repoRoot, options = {}) {
4009
4422
  const where2 = section.dir === "." ? "this repo" : section.dir;
4010
4423
  try {
4011
4424
  const installed = (name) => {
4012
- let dir = join19(repoRoot, section.dir);
4425
+ let dir = join21(repoRoot, section.dir);
4013
4426
  while (true) {
4014
- if (existsSync4(join19(dir, "node_modules", name, "package.json"))) return true;
4015
- if (dir === repoRoot || dirname9(dir) === dir) return false;
4016
- dir = dirname9(dir);
4427
+ if (existsSync4(join21(dir, "node_modules", name, "package.json"))) return true;
4428
+ if (dir === repoRoot || dirname11(dir) === dir) return false;
4429
+ dir = dirname11(dir);
4017
4430
  }
4018
4431
  };
4019
4432
  if (!installed("react") || !installed("react-dom")) {
4020
4433
  missing.push(where2);
4021
4434
  continue;
4022
4435
  }
4023
- const projectRoot = join19(repoRoot, section.dir);
4436
+ const projectRoot = join21(repoRoot, section.dir);
4024
4437
  const target = await loadInventoryTarget(projectRoot);
4025
4438
  if (findStylesheets(projectRoot, target.files, { tsconfig: target.tsconfig, override: target.specimenCss, templates: target.templates }).found.length === 0) {
4026
4439
  missing.push(where2);
4027
4440
  continue;
4028
4441
  }
4029
- const report = await renderShots(join19(repoRoot, section.dir), names, { download: false, base: "HEAD", ...options.executablePath ? { executablePath: options.executablePath } : {}, ...options.cache ? { cache: options.cache } : {}, ...options.log ? { log: options.log } : {} });
4442
+ const report = await renderShots(join21(repoRoot, section.dir), names, { download: false, base: "HEAD", ...options.executablePath ? { executablePath: options.executablePath } : {}, ...options.cache ? { cache: options.cache } : {}, ...options.log ? { log: options.log } : {} });
4030
4443
  const unavailable = report.notes.some((note) => UNDRAWABLE.test(note));
4031
4444
  if (unavailable) {
4032
4445
  missing.push(where2);
@@ -4096,9 +4509,9 @@ function renderMarkdown(model, reportFile) {
4096
4509
 
4097
4510
  // ../cli/src/report/safe.ts
4098
4511
  import { realpathSync as realpathSync2 } from "node:fs";
4099
- import { readFile as readFile7 } from "node:fs/promises";
4512
+ import { readFile as readFile9 } from "node:fs/promises";
4100
4513
  import { homedir as homedir3, userInfo as userInfo2 } from "node:os";
4101
- import { join as join20 } from "node:path";
4514
+ import { join as join22 } from "node:path";
4102
4515
  var REDACTED = "[redacted]";
4103
4516
  var OUTSIDE = "[path outside the repo]";
4104
4517
  var MIN_VALUE = 6;
@@ -4110,7 +4523,7 @@ async function dotenvValues(root) {
4110
4523
  const files = await findFiles(root, [".env", ".env.*", "**/.env", "**/.env.*"]).catch(() => []);
4111
4524
  const values = [];
4112
4525
  for (const file of files) {
4113
- const text = await readFile7(join20(root, file), "utf8").catch(() => "");
4526
+ const text = await readFile9(join22(root, file), "utf8").catch(() => "");
4114
4527
  for (const line of text.split(/\r?\n/)) {
4115
4528
  const match = /^\s*(?:export\s+)?[A-Za-z_][A-Za-z0-9_.-]*\s*=\s*(.*)$/.exec(line);
4116
4529
  if (!match) continue;
@@ -4239,10 +4652,10 @@ async function askTerminal(question) {
4239
4652
  async function writeBinaryAtomic(file, bytes) {
4240
4653
  const temp = `${file}.${process.pid}.${randomBytes2(6).toString("hex")}.tmp`;
4241
4654
  await writeFile2(temp, bytes);
4242
- await rename2(temp, file);
4655
+ await rename3(temp, file);
4243
4656
  }
4244
4657
  var shown = (file) => {
4245
- const rel = relative7(process.cwd(), file);
4658
+ const rel = relative9(process.cwd(), file);
4246
4659
  return rel && !rel.startsWith("..") ? rel : file;
4247
4660
  };
4248
4661
  async function runReport(options = {}, io = {}) {
@@ -4251,8 +4664,8 @@ async function runReport(options = {}, io = {}) {
4251
4664
  const error = io.error ?? ((text) => void process.stderr.write(`${text}
4252
4665
  `));
4253
4666
  const env = io.env ?? process.env;
4254
- const root = resolve11(options.path ?? process.cwd());
4255
- if (!(await stat2(root).catch(() => null))?.isDirectory()) {
4667
+ const root = resolve13(options.path ?? process.cwd());
4668
+ if (!(await stat3(root).catch(() => null))?.isDirectory()) {
4256
4669
  error(`\u2716 ${options.path ?? root} is not a directory: run ${NPX} in a repo, or pass its path (${NPX} path/to/repo)`);
4257
4670
  return { code: 2, files: null, model: null };
4258
4671
  }
@@ -4281,9 +4694,9 @@ async function runReport(options = {}, io = {}) {
4281
4694
  model.durationMs += Math.round(performance.now() - started);
4282
4695
  const redactor = await Redactor.forRepo(root, { includeCode: options.includeCode === true, env });
4283
4696
  const clean = redactor.model(model);
4284
- const outDir = resolve11(options.out ?? root);
4285
- await mkdir4(outDir, { recursive: true });
4286
- const files = { report: join21(outDir, REPORT_FILE), card: join21(outDir, CARD_FILE), summary: join21(outDir, SUMMARY_FILE) };
4697
+ const outDir = resolve13(options.out ?? root);
4698
+ await mkdir5(outDir, { recursive: true });
4699
+ const files = { report: join23(outDir, REPORT_FILE), card: join23(outDir, CARD_FILE), summary: join23(outDir, SUMMARY_FILE) };
4287
4700
  const t = clean.totals;
4288
4701
  const card = {
4289
4702
  repo: redactor.scrub(clean.repo),
@@ -4367,6 +4780,18 @@ ${style.bold("Anywhere")} ${style.dim("no design system needed")}
4367
4780
  an unchanged component comes from the cache (YOUI_SPECIMEN_CACHE)
4368
4781
 
4369
4782
  ${style.bold("In a product repo")}
4783
+ adopt [--components dir] [--css file] [--system id] [--fork] [--dry-run]
4784
+ make this product's own UI its design system, kept in this repo: a draft
4785
+ contract per component in the components folder (the files stay where they
4786
+ are), the stylesheet's :root and dark variables as tokens (moved: the
4787
+ stylesheet imports the generated tokens.css, same names and values), three
4788
+ starter rules as warnings, then init and sync; --dry-run writes nothing.
4789
+ Variables youiyoux generated from another System are left to it (adopt says
4790
+ which); --fork takes them as the start of this product's own System
4791
+ system extract <dir> [--package name] [--dry-run]
4792
+ move the System adopt keeps here into a design repo of its own at <dir>
4793
+ (contracts with their implementations, tokens, rules), for a second app to
4794
+ follow; this product then follows it by path, and sync vendors its components
4370
4795
  init --design <dir|package> write youi.project.json pointing at a design repo checkout, or at
4371
4796
  the npm package that holds it (named, so no machine's folder), then sync;
4372
4797
  a git repository goes in its source: { "git": "github:acme/design", "ref": "v3" }
@@ -4528,6 +4953,8 @@ var OPTIONS = {
4528
4953
  dark: { type: "string" },
4529
4954
  state: { type: "string" },
4530
4955
  inventory: { type: "string" },
4956
+ package: { type: "string" },
4957
+ fork: { type: "boolean", default: false },
4531
4958
  cache: { type: "string" },
4532
4959
  role: { type: "string" },
4533
4960
  license: { type: "string" },
@@ -4551,7 +4978,7 @@ var UsageError = class extends Error {
4551
4978
  var run6 = promisify5(execFile5);
4552
4979
  var print = (text) => void process.stdout.write(`${text}
4553
4980
  `);
4554
- var json = (value) => print(JSON.stringify(value, null, 2));
4981
+ var json3 = (value) => print(JSON.stringify(value, null, 2));
4555
4982
  function loadOptions(values) {
4556
4983
  return { ...values.config ? { configPath: values.config } : {}, ...values.design ? { designRoot: values.design } : {}, ...values.root !== void 0 ? { productRoot: values.root || "." } : {} };
4557
4984
  }
@@ -4559,17 +4986,17 @@ async function project(values) {
4559
4986
  return loadProject(values.project ?? process.cwd(), loadOptions(values));
4560
4987
  }
4561
4988
  async function readJsonOrFile(value) {
4562
- const text = value.trim().startsWith("{") ? value : await readFile8(value, "utf8");
4989
+ const text = value.trim().startsWith("{") ? value : await readFile10(value, "utf8");
4563
4990
  return JSON.parse(text);
4564
4991
  }
4565
4992
  async function cmdScan(values) {
4566
4993
  const p = await project(values);
4567
4994
  const { report } = await scanProject(p);
4568
- const out = values.out ?? join22(p.root, ".youi/usage.json");
4569
- await writeFileAtomic(resolve12(out), `${JSON.stringify(report)}
4995
+ const out = values.out ?? join24(p.root, ".youi/usage.json");
4996
+ await writeFileAtomic(resolve14(out), `${JSON.stringify(report)}
4570
4997
  `);
4571
- const recorded = await recordUsage(values, p, report, resolve12(out));
4572
- if (values.json) json(report);
4998
+ const recorded = await recordUsage(values, p, report, resolve14(out));
4999
+ if (values.json) json3(report);
4573
5000
  else print([renderScan(report, out), ...recorded ? [recorded] : []].join("\n"));
4574
5001
  return 0;
4575
5002
  }
@@ -4577,7 +5004,7 @@ async function recordUsage(values, p, report, usage) {
4577
5004
  const { path, home } = indexOf(values);
4578
5005
  try {
4579
5006
  await recordProject(await sharedStore(path), p, report);
4580
- await noteSource(home, { project: { root: resolve12(values.project ?? process.cwd()), options: Object.fromEntries(Object.entries(loadOptions(values)).map(([key2, value]) => [key2, resolve12(value)])), usage } });
5007
+ await noteSource(home, { project: { root: resolve14(values.project ?? process.cwd()), options: Object.fromEntries(Object.entries(loadOptions(values)).map(([key2, value]) => [key2, resolve14(value)])), usage } });
4581
5008
  return null;
4582
5009
  } catch (error) {
4583
5010
  return notRecorded(error);
@@ -4592,19 +5019,19 @@ function indexOf(values) {
4592
5019
  }
4593
5020
  async function cmdGaps(values) {
4594
5021
  const dirs = inventoryDirs(values);
4595
- const inventory = values.inventory ? InventoryReport.parse(JSON.parse(await readFile8(resolve12(values.inventory), "utf8"))) : await scanInventory(values, dirs);
4596
- const cwd = values.inventory ? dirname10(resolve12(values.inventory)) : process.cwd();
4597
- const project2 = await loadProject(resolve12(dirs[0]), loadOptions(values)).catch(() => null);
5022
+ const inventory = values.inventory ? InventoryReport.parse(JSON.parse(await readFile10(resolve14(values.inventory), "utf8"))) : await scanInventory(values, dirs);
5023
+ const cwd = values.inventory ? dirname12(resolve14(values.inventory)) : process.cwd();
5024
+ const project2 = await loadProject(resolve14(dirs[0]), loadOptions(values)).catch(() => null);
4598
5025
  const system = project2 ? { name: project2.system.config.name, components: project2.system.components.map((c) => c.contract.name) } : null;
4599
- const store = await readCatalog(resolve12(discoverCacheDir(values.cache)), indexOf(values).path).catch((error) => {
5026
+ const store = await readCatalog(resolve14(discoverCacheDir(values.cache)), indexOf(values).path).catch((error) => {
4600
5027
  if (error instanceof MissingIndexError) return null;
4601
5028
  throw error;
4602
5029
  });
4603
- const roots = new Map(inventory.projects.map((p) => [p.id, resolve12(cwd, p.root)]));
5030
+ const roots = new Map(inventory.projects.map((p) => [p.id, resolve14(cwd, p.root)]));
4604
5031
  const report = await buildGaps({ inventory, system, dependencies: declaredPackages(roots) }, store);
4605
- if (values.out) await writeFileAtomic(resolve12(values.out), `${JSON.stringify(report, null, 2)}
5032
+ if (values.out) await writeFileAtomic(resolve14(values.out), `${JSON.stringify(report, null, 2)}
4606
5033
  `);
4607
- if (values.json) json(report);
5034
+ if (values.json) json3(report);
4608
5035
  else print(renderGaps(report));
4609
5036
  return 0;
4610
5037
  }
@@ -4612,12 +5039,12 @@ async function cmdInventory(values) {
4612
5039
  if (values.history) return cmdHistory(values, []);
4613
5040
  const dirs = inventoryDirs(values);
4614
5041
  const report = await scanInventory(values, dirs);
4615
- const out = resolve12(values.out ?? join22(dirs[0], ".youi/inventory.json"));
5042
+ const out = resolve14(values.out ?? join24(dirs[0], ".youi/inventory.json"));
4616
5043
  await writeFileAtomic(out, `${JSON.stringify(report, null, 2)}
4617
5044
  `);
4618
5045
  const recorded = await recordScan(values, report, out);
4619
- if (values.json) json(report);
4620
- else print([renderInventory(report, relative8(process.cwd(), out).startsWith("..") ? out : relative8(process.cwd(), out)), recorded].join("\n"));
5046
+ if (values.json) json3(report);
5047
+ else print([renderInventory(report, relative10(process.cwd(), out).startsWith("..") ? out : relative10(process.cwd(), out)), recorded].join("\n"));
4621
5048
  return 0;
4622
5049
  }
4623
5050
  async function recordScan(values, report, out) {
@@ -4634,7 +5061,7 @@ async function recordScan(values, report, out) {
4634
5061
  async function cmdHistory(values, positionals) {
4635
5062
  const { path } = indexOf(values);
4636
5063
  const scans = await (await sharedStore(path, process.env, { readOnly: true })).history({ ...positionals[0] ? { project: positionals[0] } : {}, limit: positiveInt(values.limit, "--limit") ?? 50 });
4637
- if (values.json) json(scans);
5064
+ if (values.json) json3(scans);
4638
5065
  else print(renderHistory(scans));
4639
5066
  return 0;
4640
5067
  }
@@ -4642,19 +5069,19 @@ async function cmdIndex(values, positionals) {
4642
5069
  const [action, ...rest] = positionals;
4643
5070
  const { path, home } = indexOf(values);
4644
5071
  if (action === "path") {
4645
- if (values.json) json({ store: storeLocation({ path }), sources: join22(home, SOURCES_FILE), scans: join22(home, SCANS_LOG) });
5072
+ if (values.json) json3({ store: storeLocation({ path }), sources: join24(home, SOURCES_FILE), scans: join24(home, SCANS_LOG) });
4646
5073
  else print(storeLocation({ path }));
4647
5074
  return 0;
4648
5075
  }
4649
5076
  if (action === "stats") {
4650
5077
  const stats = await (await sharedStore(path, process.env, { readOnly: true })).stats();
4651
- if (values.json) json(stats);
5078
+ if (values.json) json3(stats);
4652
5079
  else print([`${style.bold("index")} ${stats.location} ${style.dim(`\xB7 ${stats.engine} \xB7 schema ${stats.schema} \xB7 generation ${stats.generation ?? "not rebuilt"}`)}`, ...Object.entries(stats.tables).map(([table, n]) => ` ${table.padEnd(16)} ${n.toLocaleString("en-US")}`)].join("\n"));
4653
5080
  return 0;
4654
5081
  }
4655
5082
  if (action === "rebuild") {
4656
- const summary = await rebuildIndex(path, values.cache ? { discoverCache: resolve12(values.cache) } : {});
4657
- if (values.json) json(summary);
5083
+ const summary = await rebuildIndex(path, values.cache ? { discoverCache: resolve14(values.cache) } : {});
5084
+ if (values.json) json3(summary);
4658
5085
  else print(renderRebuild(summary));
4659
5086
  return 0;
4660
5087
  }
@@ -4668,7 +5095,7 @@ async function cmdWhere(values, positionals) {
4668
5095
  const { report } = await scanProject(p);
4669
5096
  const limit = values.all ? Number.MAX_SAFE_INTEGER : Number(values.limit ?? (values.json ? 20 : 24));
4670
5097
  const where2 = whereUsed(p, report, query, { ...values.route ? { route: values.route } : {}, limit });
4671
- if (values.json) json(where2);
5098
+ if (values.json) json3(where2);
4672
5099
  else print(renderWhere(where2, report));
4673
5100
  return 0;
4674
5101
  }
@@ -4679,7 +5106,7 @@ async function cmdShow(values, positionals) {
4679
5106
  const contract = findContract(p, query);
4680
5107
  if (!contract) throw new Error(`no component "${query}"; known: ${p.system.components.map((c) => c.contract.name).join(", ")}`);
4681
5108
  const card = componentCard(p, contract, (await scanProject(p)).report);
4682
- if (values.json) json(card);
5109
+ if (values.json) json3(card);
4683
5110
  else print(renderShow(card));
4684
5111
  return 0;
4685
5112
  }
@@ -4688,12 +5115,12 @@ async function cmdBom(values, positionals) {
4688
5115
  if (!route) throw new UsageError("youiyoux bom <route>");
4689
5116
  const p = await project(values);
4690
5117
  const bom = screenBom(p, (await scanProject(p)).report, route);
4691
- if (values.json) json(bom);
5118
+ if (values.json) json3(bom);
4692
5119
  else print(renderBom(bom));
4693
5120
  return 0;
4694
5121
  }
4695
5122
  function baselineFile(values, p) {
4696
- if (values["baseline-file"]) return resolve12(values["baseline-file"]);
5123
+ if (values["baseline-file"]) return resolve14(values["baseline-file"]);
4697
5124
  return values.baseline ? baselinePath(p, values.config) : null;
4698
5125
  }
4699
5126
  var checkScope = (values, positionals) => ({ ...positionals.length > 0 ? { files: positionals } : {}, ...values.rule ? { rule: values.rule } : {}, quiet: values.quiet });
@@ -4712,7 +5139,7 @@ async function cmdCheck(values, positionals) {
4712
5139
  const result = await runCheck(p, { ...checkScope(values, positionals), baseline: baselineFile(values, p) });
4713
5140
  if (format === "json") {
4714
5141
  const { report } = result.scan;
4715
- json({ errors: result.errors, incomplete: result.incomplete, identity: result.identity, unmatched: result.unmatched, notRun: result.notRun, violations: result.violations, baseline: result.baseline, lock: result.lock, suppressed: report.suppressed, coverage: report.coverage, status: report.status, checks: report.checks });
5142
+ json3({ errors: result.errors, incomplete: result.incomplete, identity: result.identity, unmatched: result.unmatched, notRun: result.notRun, violations: result.violations, baseline: result.baseline, lock: result.lock, suppressed: report.suppressed, coverage: report.coverage, status: report.status, checks: report.checks });
4716
5143
  } else if (format === "github") {
4717
5144
  for (const line of githubAnnotations(result, await repoPrefix(p.root))) print(line);
4718
5145
  print(renderCheckText(result));
@@ -4724,7 +5151,7 @@ async function cmdCheckDiff(values, positionals, p, format) {
4724
5151
  if (values.baseline) throw new UsageError("youiyoux check --diff looks only at added lines, so a baseline does not apply; drop --baseline");
4725
5152
  const change = await workingTreeChange(p, values.base);
4726
5153
  const result = configured(p) ? await runCheck(p, { ...checkScope(values, positionals), diff: change }) : await runRepoCheck(p, change);
4727
- if (format === "json") json(diffJson(result));
5154
+ if (format === "json") json3(diffJson(result));
4728
5155
  else if (format === "github") {
4729
5156
  for (const line of githubDiffAnnotations(result, await repoPrefix(p.root))) print(line);
4730
5157
  print(renderDiffText(result));
@@ -4748,9 +5175,9 @@ async function cmdBaseline(values) {
4748
5175
  const file = baselineFile(values, p) ?? baselinePath(p, values.config);
4749
5176
  const written = await writeBaseline(p, file, { force: values.force });
4750
5177
  const { totals } = written.baseline;
4751
- if (values.json) json({ file: written.file, action: written.action, totals, notAdded: written.notAdded, identity: written.baseline.identity });
5178
+ if (values.json) json3({ file: written.file, action: written.action, totals, notAdded: written.notAdded, identity: written.baseline.identity });
4752
5179
  else {
4753
- print(`${style.ok(written.action)} ${relative8(process.cwd(), file) || file}: ${plural(totals.errors, "error")} \xB7 ${plural(totals.warnings, "warning")} accepted`);
5180
+ print(`${style.ok(written.action)} ${relative10(process.cwd(), file) || file}: ${plural(totals.errors, "error")} \xB7 ${plural(totals.warnings, "warning")} accepted`);
4754
5181
  print(style.dim(` rules ${describeIdentity(written.baseline.identity)}`));
4755
5182
  if (written.notAdded > 0) print(style.warn(` ${plural(written.notAdded, "current violation")} not added: fix them, or accept them deliberately with --force`));
4756
5183
  }
@@ -4764,30 +5191,30 @@ async function cmdPlan(values, positionals) {
4764
5191
  else if (positionals[0] && values.patch) input = { element: positionals[0], patch: await readJsonOrFile(values.patch) };
4765
5192
  else throw new UsageError("youiyoux plan <component> --patch <json|file> or youiyoux plan --token <id> [--value v]");
4766
5193
  const plan = await planChange(p, scan, input);
4767
- if (values.json) json(plan);
5194
+ if (values.json) json3(plan);
4768
5195
  else print(renderPlan(plan));
4769
5196
  return 0;
4770
5197
  }
4771
5198
  async function cmdRules(values) {
4772
5199
  const p = await project(values);
4773
5200
  if (values.preview) {
4774
- const draft = (0, import_yaml3.parse)(await readFile8(values.preview, "utf8"));
5201
+ const draft = (0, import_yaml4.parse)(await readFile10(values.preview, "utf8"));
4775
5202
  const evaluation = evaluateRule(p, await scanProject(p), draft);
4776
- if (values.json) json(evaluation);
5203
+ if (values.json) json3(evaluation);
4777
5204
  else print(renderRulePreview(evaluation));
4778
5205
  return evaluation.ok && evaluation.findings !== null ? 0 : 1;
4779
5206
  }
4780
5207
  const rules = p.system.rules.map((rule2) => ({ rule: rule2, execution: ruleCapability(rule2) }));
4781
- if (values.json) json({ capabilities: checkCapabilities(), rules: rules.map(({ rule: rule2, execution }) => ({ id: rule2.id, severity: rule2.severity, ...execution })) });
5208
+ if (values.json) json3({ capabilities: checkCapabilities(), rules: rules.map(({ rule: rule2, execution }) => ({ id: rule2.id, severity: rule2.severity, ...execution })) });
4782
5209
  else print(renderRules(rules));
4783
5210
  return rules.some(({ execution }) => execution.state === "unsupported") ? 1 : 0;
4784
5211
  }
4785
5212
  async function cmdMigrate(values, positionals) {
4786
5213
  const file = positionals[0];
4787
5214
  if (!file) throw new UsageError("youiyoux migrate <migration.yaml> [--write] [--diff]");
4788
- const spec = MigrationSpec.parse((0, import_yaml3.parse)(await readFile8(file, "utf8")));
5215
+ const spec = MigrationSpec.parse((0, import_yaml4.parse)(await readFile10(file, "utf8")));
4789
5216
  const result = await runMigration(await project(values), spec, { write: values.write });
4790
- if (values.json) json(result);
5217
+ if (values.json) json3(result);
4791
5218
  else print(renderMigration(result, values.diff || !values.write));
4792
5219
  return result.status === "rejected" ? 1 : 0;
4793
5220
  }
@@ -4797,14 +5224,14 @@ async function cmdLibrary(values, positionals) {
4797
5224
  const p = await project(values);
4798
5225
  const words = positionals.slice(1).join(" ");
4799
5226
  const result = searchLibrary(await loadLibrary(p.repo.root), words, Number(values.limit ?? 20));
4800
- if (values.json) json(result);
5227
+ if (values.json) json3(result);
4801
5228
  else print(renderLibrarySearch(result, words));
4802
5229
  return 0;
4803
5230
  }
4804
5231
  if (action === "request" && query && values.reason) {
4805
5232
  const p = await project(values);
4806
5233
  const request = adoptionRequest(p, await loadLibrary(p.repo.root), query, values.reason, await scanProject(p));
4807
- if (values.json) json(request);
5234
+ if (values.json) json3(request);
4808
5235
  else print(renderAdoptionRequest(request));
4809
5236
  return 0;
4810
5237
  }
@@ -4813,7 +5240,7 @@ async function cmdLibrary(values, positionals) {
4813
5240
  async function cmdSync(values) {
4814
5241
  const p = await loadProject(values.project ?? process.cwd(), { ...loadOptions(values), refreshDesign: true });
4815
5242
  const result = await syncProject(p, { dryRun: values["dry-run"], adopt: values.adopt });
4816
- if (values.json) json(result);
5243
+ if (values.json) json3(result);
4817
5244
  else print([renderSync(result, values["dry-run"]), ...p.linked ? [style.warn(`! linked: the design system is your checkout ${p.linked}, not the config's source (youiyoux unlink to undo)`)] : []].join("\n"));
4818
5245
  return result.actions.some((a) => a.action === "conflict") ? 1 : 0;
4819
5246
  }
@@ -4821,7 +5248,7 @@ async function cmdInit(values) {
4821
5248
  if (!values.design) throw new UsageError("youiyoux init --design <dir|package> (a design repo checkout, the directory holding youi.config.json, or the npm package that holds it)");
4822
5249
  const root = values.project ?? process.cwd();
4823
5250
  const design = await designArg(values.design, root);
4824
- const result = await initProject({ root, ...design, schemaDir: join22(TOOL_ROOT, "schema"), ...values.system ? { system: values.system } : {}, ...values.components ? { componentsDir: values.components } : {}, force: values.force });
5251
+ const result = await initProject({ root, ...design, schemaDir: join24(TOOL_ROOT, "schema"), ...values.system ? { system: values.system } : {}, ...values.components ? { componentsDir: values.components } : {}, force: values.force });
4825
5252
  if (!values.json) {
4826
5253
  print(`${style.ok("created")} ${result.file}`);
4827
5254
  for (const note of result.notes) print(style.dim(` \xB7 ${note}`));
@@ -4830,6 +5257,76 @@ async function cmdInit(values) {
4830
5257
  const { design: _named, ...rest } = values;
4831
5258
  return cmdSync({ ...design.package ? rest : values, project: root });
4832
5259
  }
5260
+ async function cmdAdopt(values) {
5261
+ const root = resolve14(values.project ?? process.cwd());
5262
+ if (await exists(join24(root, PROJECT_FILE))) throw new Error(`${root} already has ${PROJECT_FILE}: it follows a design system already (youiyoux sync updates it)`);
5263
+ const plan = await planAdoption({ root, ...values.components ? { componentsDir: values.components } : {}, ...values.css ? { css: values.css } : {}, ...values.system ? { system: values.system } : {}, fork: values.fork });
5264
+ const taken = (await Promise.all(plan.files.map(async (f) => await exists(join24(root, f.path)) ? f.path : null))).filter((f) => f !== null);
5265
+ if (taken.length > 0) throw new Error(`nothing was written: ${taken.slice(0, 5).join(", ")}${taken.length > 5 ? ` and ${taken.length - 5} more` : ""} already exist${taken.length === 1 ? "s" : ""}`);
5266
+ if (values["dry-run"]) {
5267
+ if (values.json) json3(plan);
5268
+ else print([...adoptLines(plan, true), "", style.dim(`dry run: nothing written; would write ${plan.files.map((f) => f.path).join(", ")}${plan.stylesheet ? ` and change ${plan.stylesheet.path}` : ""}`)].join("\n"));
5269
+ return 0;
5270
+ }
5271
+ const stylesheet = plan.stylesheet ? await readFile10(join24(root, plan.stylesheet.path), "utf8") : null;
5272
+ try {
5273
+ for (const file of plan.files) await writeFileAtomic(join24(root, file.path), file.content);
5274
+ if (plan.stylesheet) await writeFileAtomic(join24(root, plan.stylesheet.path), plan.stylesheet.content);
5275
+ await initProject({ root, design: root, schemaDir: join24(TOOL_ROOT, "schema"), system: plan.system.id, componentsDir: plan.componentsDir, outputs: { ...plan.outputs, targets: [...plan.outputs.targets], dark: [...plan.outputs.dark] }, tokenSources: plan.tokenSources });
5276
+ await syncProject(await loadProject(root), { dryRun: true });
5277
+ } catch (error) {
5278
+ for (const file of [...plan.files.map((f) => f.path), PROJECT_FILE]) await rm6(join24(root, file), { force: true });
5279
+ for (const folder of [...new Set(plan.files.map((f) => dirname12(f.path)))].sort((a, b) => b.length - a.length)) await rmdir3(join24(root, folder)).catch(() => void 0);
5280
+ await rmdir3(join24(root, "design/components")).catch(() => void 0);
5281
+ await rmdir3(join24(root, "design")).catch(() => void 0);
5282
+ if (plan.stylesheet && stylesheet !== null) await writeFileAtomic(join24(root, plan.stylesheet.path), stylesheet);
5283
+ throw new Error(`adopt was undone, nothing kept: ${error instanceof Error ? error.message : String(error)}`);
5284
+ }
5285
+ if (!values.json) print([...adoptLines(plan), ""].join("\n"));
5286
+ const synced = await cmdSync({ ...values, project: root, components: void 0, css: void 0, system: void 0 });
5287
+ if (!values.json) print(`Next: describe each component in design/components/<name>/<name>.contract.json (its summary, guidance and examples; agents read them), see what is left with youiyoux check, and when a second app needs this System, youiyoux system extract <dir> moves it into its own repo`);
5288
+ return synced;
5289
+ }
5290
+ function adoptLines(plan, dryRun = false) {
5291
+ const { tokens } = plan;
5292
+ const shown2 = plan.components.slice(0, 6).join(", ") + (plan.components.length > 6 ? `, \u2026 ${plan.components.length - 6} more` : "");
5293
+ return [
5294
+ `${style.ok(dryRun ? "would adopt" : "adopted")} ${plan.system.name} ${style.dim(`(system ${plan.system.id}) from this product's own UI`)}`,
5295
+ ` components ${plural(plan.components.length, "component")} in ${plan.componentsDir} (${shown2}): a draft contract each in design/components; the files stay where they are${plan.support.length > 0 ? ` (${plan.support.join(", ")} as support)` : ""}`,
5296
+ tokens.stylesheet ? ` tokens ${plural(tokens.count, "variable")}${tokens.dark ? ", light and dark," : ""} from ${tokens.stylesheet} \u2192 design/tokens${tokens.left.length > 0 ? `; ${tokens.left.length} left in the stylesheet, not typed: ${tokens.left.slice(0, 4).map((l) => `--${l.name} (${l.value})`).join(", ")}${tokens.left.length > 4 ? ", \u2026" : ""}` : ""}` : ` tokens none: no stylesheet defines :root variables (--css <file> names one)`,
5297
+ ...plan.stylesheet ? [` stylesheet ${plan.stylesheet.path}: their ${plural(plan.stylesheet.moved, "declaration")}${tokens.dark ? " (light and dark)" : ""} moved into the System; it imports ${plan.outputs.dir}/tokens.css (same names, same values)`] : [],
5298
+ ` rules 3 starter rules, as warnings: design/rules`,
5299
+ ` config youi.config.json (the System) and youi.project.json (this product follows it)`,
5300
+ ...plan.notes.map((n) => style.warn(` ! ${n}`)),
5301
+ // The product follows a System already: its values were left to it, and here is how to keep one System, or fork it.
5302
+ ...plan.follows.map((g) => {
5303
+ const system = g.system ?? "a System";
5304
+ return style.warn(` ! ${g.file} holds ${plural(g.count, "variable")} youiyoux generated from ${system}${g.version ? ` v${g.version}` : ""}: this product already uses ${g.system ? "that System" : "one"}, so they were left to it. To manage the product with ${system}, run youiyoux init --design <${system}'s repo or package>; to make a System of the product's own from them, run youiyoux adopt --fork`);
5305
+ })
5306
+ ];
5307
+ }
5308
+ async function cmdSystem(values, positionals) {
5309
+ const [action, dir, ...extra] = positionals;
5310
+ if (action !== "extract" || dir === void 0 || extra.length > 0) throw new UsageError("youiyoux system extract <dir> [--package <name>] [--dry-run] (move the System youiyoux adopt keeps in this product into a design repo of its own)");
5311
+ if (values.package !== void 0 && !PACKAGE_NAME.test(values.package)) throw new UsageError(`--package ${JSON.stringify(values.package)}: expected an npm package name such as @acme/design`);
5312
+ const p = await project(values);
5313
+ const result = await extractSystem(p, dir, { ...values.package ? { package: values.package } : {}, dryRun: values["dry-run"] });
5314
+ const contracts = result.written.filter((f) => f.endsWith(".contract.json")).length;
5315
+ const lines = [
5316
+ `${style.ok(values["dry-run"] ? "would extract" : "extracted")} ${p.system.config.name} ${style.dim(`(system ${result.system.id})`)} \u2192 ${result.target}`,
5317
+ ` repo ${plural(result.written.length, "file")}: youi.config.json, ${plural(contracts, "contract")} with their implementations beside them, the tokens, rules${result.system.design ? " and design.md" : ""}${values.package ? `, and package.json (${values.package})` : ""}`,
5318
+ ` product youi.project.json follows it (source: ${JSON.stringify(result.source)}); ${values["dry-run"] ? "would remove" : "removed"} its System files: ${result.removed.join(", ")}`
5319
+ ];
5320
+ if (values["dry-run"]) {
5321
+ print([...lines, "", style.dim("dry run: nothing written or removed")].join("\n"));
5322
+ return 0;
5323
+ }
5324
+ print([...lines, ""].join("\n"));
5325
+ const synced = await cmdSync({ ...values, project: p.root, package: void 0 });
5326
+ const git3 = `push it and set source to { "git": "<its url>", "ref": "v${result.system.version}" }`;
5327
+ print(`Next: make ${relative10(process.cwd(), result.target) || "."} a repository of its own and commit it. For each app to follow it, ${values.package ? `publish ${values.package}, add it as a dependency and set source to { "package": "${values.package}" }, or ${git3}` : `${git3}, or publish it as an npm package (--package names it)`}. Change components there from now on: youiyoux sync merges them into each product`);
5328
+ return synced;
5329
+ }
4833
5330
  async function cmdLink(values, positionals) {
4834
5331
  const [dir] = positionals;
4835
5332
  const p = await loadProject(values.project ?? process.cwd(), { ...loadOptions(values), ...dir !== void 0 ? { designRoot: dir } : {} });
@@ -4845,37 +5342,37 @@ async function cmdUnlink(values) {
4845
5342
  const root = values.project ?? process.cwd();
4846
5343
  const linked = await linkedDesign(root);
4847
5344
  if (linked !== null) await setLink(root, null);
4848
- print(linked ? `${style.ok("unlinked")} ${resolve12(root)} uses its config's source again` : `${resolve12(root)} was not linked`);
5345
+ print(linked ? `${style.ok("unlinked")} ${resolve14(root)} uses its config's source again` : `${resolve14(root)} was not linked`);
4849
5346
  return 0;
4850
5347
  }
4851
5348
  async function designArg(value, root) {
4852
- if (existsSync5(resolve12(value)) || !PACKAGE_NAME.test(value)) return { design: value };
4853
- return { design: await designRootOf({ package: value }, join22(resolve12(root), PROJECT_FILE), root), package: value };
5349
+ if (existsSync5(resolve14(value)) || !PACKAGE_NAME.test(value)) return { design: value };
5350
+ return { design: await designRootOf({ package: value }, join24(resolve14(root), PROJECT_FILE), root), package: value };
4854
5351
  }
4855
5352
  async function cmdBuild(values) {
4856
- const root = resolve12(values.design ?? process.cwd());
5353
+ const root = resolve14(values.design ?? process.cwd());
4857
5354
  const system = getSystem(await loadDesignRepo(root), values.system);
4858
5355
  if (safeId(system.config.id) === null) throw new Error(`Cannot build system ${JSON.stringify(system.config.id)}: the id must be a plain name without ".." or path separators.`);
4859
5356
  const output = OutputConfig.parse({ cssVarPrefix: values.prefix ?? "youi", targets: ["css-vars", "tailwind-v4", "ts", "react-native"] });
4860
5357
  const started = performance.now();
4861
5358
  const built = await buildTokens(root, system.config, output, { contracts: system.components.map((c) => c.contract) });
4862
- const out = resolve12(values.out ?? join22(root, "build", system.config.id));
4863
- const boundary = values.out ? out : join22(root, "build");
4864
- if (!within(boundary, out) || built.files.some((file) => !within(out, join22(out, file.path)))) throw new Error(`Cannot build system ${JSON.stringify(system.config.id)}: an output path leaves the output directory.`);
4865
- for (const file of built.files) await writeFileAtomic(join22(out, file.path), file.content);
4866
- if (values.json) json({ out, files: built.files.map((f) => f.path), tokens: built.tokens.length });
5359
+ const out = resolve14(values.out ?? join24(root, "build", system.config.id));
5360
+ const boundary = values.out ? out : join24(root, "build");
5361
+ if (!within(boundary, out) || built.files.some((file) => !within(out, join24(out, file.path)))) throw new Error(`Cannot build system ${JSON.stringify(system.config.id)}: an output path leaves the output directory.`);
5362
+ for (const file of built.files) await writeFileAtomic(join24(out, file.path), file.content);
5363
+ if (values.json) json3({ out, files: built.files.map((f) => f.path), tokens: built.tokens.length });
4867
5364
  else print(`${style.ok("built")} ${plural(built.tokens.length, "token")} \u2192 ${built.files.map((f) => f.path).join(", ")} ${style.dim(`in ${out} (${ms(performance.now() - started)})`)}`);
4868
5365
  return 0;
4869
5366
  }
4870
5367
  async function cmdParity(values) {
4871
5368
  if (!values.css) throw new UsageError("youiyoux parity --css <file> [--prefix p]");
4872
- const root = resolve12(values.design ?? process.cwd());
5369
+ const root = resolve14(values.design ?? process.cwd());
4873
5370
  const system = getSystem(await loadDesignRepo(root), values.system);
4874
5371
  const prefix = values.prefix ?? "youi";
4875
5372
  const dark = { selector: values.dark ?? ':root[data-theme="dark"]' };
4876
5373
  const built = await buildTokens(root, system.config, OutputConfig.parse({ cssVarPrefix: prefix, dark: [dark], targets: ["css-vars"] }));
4877
- const report = compareParity(await readFile8(values.css, "utf8"), { light: { selector: values.light ?? ":root" }, dark }, built.files[0].content, { light: { selector: ":root" }, dark }, prefix);
4878
- if (values.json) json(report);
5374
+ const report = compareParity(await readFile10(values.css, "utf8"), { light: { selector: values.light ?? ":root" }, dark }, built.files[0].content, { light: { selector: ":root" }, dark }, prefix);
5375
+ if (values.json) json3(report);
4879
5376
  else {
4880
5377
  const ok = report.different.length === 0 && report.missing.length === 0;
4881
5378
  print(`${ok ? style.ok("\u2713 parity") : style.error("\u2716 parity")} ${report.matched} matched \xB7 ${report.different.length} different \xB7 ${report.missing.length} missing \xB7 ${style.dim(`${report.added.length} new in the build`)}`);
@@ -4887,18 +5384,18 @@ async function cmdParity(values) {
4887
5384
  async function cmdSchema(values) {
4888
5385
  const schemas = renderJsonSchemas();
4889
5386
  if (values.check) {
4890
- const dir = join22(values.design ? resolve12(values.design) : TOOL_ROOT, "schema");
5387
+ const dir = join24(values.design ? resolve14(values.design) : TOOL_ROOT, "schema");
4891
5388
  const stale = [];
4892
- for (const { file, content } of schemas) if (await readFile8(join22(dir, file), "utf8").catch(() => null) !== content) stale.push(file);
4893
- if (values.json) json({ dir, stale });
5389
+ for (const { file, content } of schemas) if (await readFile10(join24(dir, file), "utf8").catch(() => null) !== content) stale.push(file);
5390
+ if (values.json) json3({ dir, stale });
4894
5391
  else print(stale.length === 0 ? `${style.ok("\u2713")} ${dir} matches this youi's schemas` : `${style.error("\u2716")} out of date in ${dir}: ${stale.join(", ")}; run youiyoux schema --write${values.design ? " --design <dir>" : ""}`);
4895
5392
  return stale.length === 0 ? 0 : 1;
4896
5393
  }
4897
5394
  if (values.write) {
4898
- const root = values.design ? resolve12(values.design) : TOOL_ROOT;
4899
- for (const { file, content } of schemas) await writeFileAtomic(join22(root, "schema", file), content);
5395
+ const root = values.design ? resolve14(values.design) : TOOL_ROOT;
5396
+ for (const { file, content } of schemas) await writeFileAtomic(join24(root, "schema", file), content);
4900
5397
  print(`${style.ok("wrote")} ${schemas.map((s) => s.file).join(", ")}`);
4901
- } else json(Object.fromEntries(schemas.map((s) => [s.file, JSON.parse(s.content)])));
5398
+ } else json3(Object.fromEntries(schemas.map((s) => [s.file, JSON.parse(s.content)])));
4902
5399
  return 0;
4903
5400
  }
4904
5401
  function hookTarget(values) {
@@ -4919,14 +5416,14 @@ async function cmdHooks(values, positionals) {
4919
5416
  if (action !== "install") throw new UsageError("youiyoux hooks install [--claude] [--git] [--ci [--diff]] [--strict] or youiyoux hooks run claude|pre-commit");
4920
5417
  const p = await loadCheckProject(values.project ?? process.cwd(), loadOptions(values), !values.project);
4921
5418
  if (values.ci && !configured(p)) throw new UsageError("Run youiyoux init --design <dir> before installing the pinned CI workflow; repo-mode CI can run youiyoux check --diff <base> --strict directly");
4922
- const target = { ...hookTarget(values), project: p.root, ...configured(p) && p.configFile && p.configFile !== join22(p.root, PROJECT_FILE) ? { config: p.configFile } : {}, ...values.root !== void 0 ? { root: p.root } : {} };
5419
+ const target = { ...hookTarget(values), project: p.root, ...configured(p) && p.configFile && p.configFile !== join24(p.root, PROJECT_FILE) ? { config: p.configFile } : {}, ...values.root !== void 0 ? { root: p.root } : {} };
4923
5420
  const all = !values.claude && !values.git && !values.ci;
4924
5421
  const results = [];
4925
5422
  if (values.claude || all) results.push({ kind: "Claude Code", ...await installClaudeHook(await repoRootOf(p.root), hookCommand(TOOL_ROOT, "claude", target)) });
4926
5423
  if (values.git || all) results.push({ kind: "git pre-commit", ...await installPreCommit(p.root, hookCommand(TOOL_ROOT, "pre-commit", target, values.strict ? ["--strict"] : []), values.strict) });
4927
5424
  if (values.git || all) results.push({ kind: "Codex", ...configured(p) ? codexFallback(p.root, p.config.agents.agentsMd) : await installRepoAgentInstructions(p.root) });
4928
5425
  if (values.ci && configured(p)) results.push({ kind: "CI", ...await installWorkflow(p, TOOL_ROOT, target, values.force, { diff: values.diff }) });
4929
- if (values.json) json(results);
5426
+ if (values.json) json3(results);
4930
5427
  else for (const r of results) print(`${r.action === "unchanged" || r.action === "skipped" ? style.dim(r.action) : style.ok(r.action)} ${r.kind}: ${r.file}
4931
5428
  ${style.dim(r.guarantee)}`);
4932
5429
  return 0;
@@ -4934,11 +5431,11 @@ async function cmdHooks(values, positionals) {
4934
5431
  async function cmdAgents(values, positionals) {
4935
5432
  const [action] = positionals;
4936
5433
  if (action !== "install" && action !== "remove") throw new UsageError("youiyoux agents install|remove [--claude] [--codex] [--json]");
4937
- const projectRoot = resolve12(values.project ?? process.cwd());
5434
+ const projectRoot = resolve14(values.project ?? process.cwd());
4938
5435
  const repoRoot = await repoRootOf(projectRoot);
4939
- const target = { project: projectRoot, ...values.config ? { config: resolve12(values.config) } : {}, ...values.design ? { design: resolve12(values.design) } : {}, ...values.root !== void 0 ? { root: resolve12(values.root || ".") } : {} };
5436
+ const target = { project: projectRoot, ...values.config ? { config: resolve14(values.config) } : {}, ...values.design ? { design: resolve14(values.design) } : {}, ...values.root !== void 0 ? { root: resolve14(values.root || ".") } : {} };
4940
5437
  const server = mcpCommand(TOOL_ROOT, target);
4941
- const system = existsSync5(resolve12(values.config ?? join22(projectRoot, PROJECT_FILE))) ? await project(values) : null;
5438
+ const system = existsSync5(resolve14(values.config ?? join24(projectRoot, PROJECT_FILE))) ? await project(values) : null;
4942
5439
  const claudeServer = system && system.root === repoRoot ? { command: "node", args: mcpArgs(system) } : relativeTo(repoRoot, server);
4943
5440
  const both = !values.claude && !values.codex;
4944
5441
  const results = [];
@@ -4946,7 +5443,7 @@ async function cmdAgents(values, positionals) {
4946
5443
  results.push(...rule2.map((r) => ({ kind: "agent rule", ...r })));
4947
5444
  if (values.claude || both) results.push({ kind: "Claude Code MCP", ...action === "install" ? await installClaudeMcp(repoRoot, claudeServer) : await removeClaudeMcp(repoRoot) });
4948
5445
  if (values.codex || both) results.push({ kind: "Codex MCP", ...action === "install" ? await installCodexMcp(repoRoot, server) : await removeCodexMcp(repoRoot) });
4949
- if (values.json) json(results);
5446
+ if (values.json) json3(results);
4950
5447
  else {
4951
5448
  for (const r of results) print(`${r.action === "unchanged" ? style.dim("unchanged") : style.ok(r.action)} ${r.kind}: ${r.file}
4952
5449
  ${style.dim(r.guarantee)}`);
@@ -5021,23 +5518,23 @@ async function cmdUi(values) {
5021
5518
  async function cmdDetect(values, positionals) {
5022
5519
  const target = detectTarget(positionals);
5023
5520
  if (!target) throw new UsageError("youiyoux detect <repo-path> or youiyoux detect <url\u2026> [--expose] (one repo, or one or more URLs; not both)");
5024
- const { repoReport, siteReport, writeReport } = await import("./src-YYI52B5R.js");
5025
- const out = resolve12(values.out ?? ".youi/detect");
5521
+ const { repoReport, siteReport, writeReport } = await import("./src-J6GPLCVO.js");
5522
+ const out = resolve14(values.out ?? ".youi/detect");
5026
5523
  const report = target.kind === "repo" ? await repoReport(target.path) : await siteReport(target.urls, { out, expose: values.expose, ...process.env.YOUI_CHROMIUM ? { executablePath: process.env.YOUI_CHROMIUM } : {} });
5027
5524
  const file = await writeReport(report, out);
5028
- if (values.json) json(report);
5525
+ if (values.json) json3(report);
5029
5526
  else {
5030
- const shown2 = relative8(process.cwd(), file);
5527
+ const shown2 = relative10(process.cwd(), file);
5031
5528
  print(renderDetect(report, shown2 && !shown2.startsWith("..") ? shown2 : file));
5032
5529
  }
5033
5530
  return 0;
5034
5531
  }
5035
5532
  async function cmdRender(values, positionals, io) {
5036
- const root = resolve12(values.root ?? values.project ?? process.cwd());
5037
- const targets = positionals.map((t) => existsSync5(resolve12(t)) ? resolve12(t) : t);
5533
+ const root = resolve14(values.root ?? values.project ?? process.cwd());
5534
+ const targets = positionals.map((t) => existsSync5(resolve14(t)) ? resolve14(t) : t);
5038
5535
  try {
5039
5536
  const report = await renderShots(root, targets, { ...values.base ? { base: values.base } : {}, ...process.env.YOUI_CHROMIUM ? { executablePath: process.env.YOUI_CHROMIUM } : {}, ...io.render });
5040
- if (values.json) json({ ...report, next: shotsNext(report, (t) => `run youiyoux render${t ? ` ${t}` : ""} again`) });
5537
+ if (values.json) json3({ ...report, next: shotsNext(report, (t) => `run youiyoux render${t ? ` ${t}` : ""} again`) });
5041
5538
  else print(renderShots2(report));
5042
5539
  return targets.length > 0 && report.components.every((c) => c.shots.length === 0) ? 1 : 0;
5043
5540
  } catch (error) {
@@ -5048,20 +5545,20 @@ async function cmdRender(values, positionals, io) {
5048
5545
  }
5049
5546
  }
5050
5547
  async function product(values) {
5051
- const project2 = await loadProject(resolve12(values.project ?? process.cwd()), loadOptions(values)).catch(() => null);
5052
- return { project: project2, root: project2?.root ?? resolve12(values.root ?? values.project ?? process.cwd()) };
5548
+ const project2 = await loadProject(resolve14(values.project ?? process.cwd()), loadOptions(values)).catch(() => null);
5549
+ return { project: project2, root: project2?.root ?? resolve14(values.root ?? values.project ?? process.cwd()) };
5053
5550
  }
5054
5551
  async function cmdRequests(values, positionals) {
5055
5552
  const usage = "youiyoux requests [--status open|addressed|closed|all] [--route r] or youiyoux requests show <id> | reply <id> --message <text> | addressed <id> --message <what you changed>";
5056
5553
  const [action, id, ...rest] = positionals;
5057
5554
  if (rest.length > 0) throw new UsageError(usage);
5058
- const { RequestStore, requestBrief } = await import("./src-VKQJF7KR.js");
5555
+ const { RequestStore, requestBrief } = await import("./src-4JGIPP5A.js");
5059
5556
  const { project: project2, root } = await product(values);
5060
- const store = new RequestStore(root, project2?.config.id ?? basename3(root));
5557
+ const store = new RequestStore(root, project2?.config.id ?? basename5(root));
5061
5558
  const agent = { kind: "agent", name: "youiyoux CLI" };
5062
5559
  const one = (request) => {
5063
5560
  const brief = requestBrief(request, root);
5064
- if (values.json) json(brief);
5561
+ if (values.json) json3(brief);
5065
5562
  else print(renderBrief(brief, true));
5066
5563
  return 0;
5067
5564
  };
@@ -5071,7 +5568,7 @@ async function cmdRequests(values, positionals) {
5071
5568
  if (!["open", "addressed", "closed", "all"].includes(status)) throw new UsageError("--status takes open, addressed, closed or all");
5072
5569
  const { requests, unreadable } = await store.list();
5073
5570
  const briefs = requests.filter((r) => (status === "all" || r.status === status) && (!values.route || r.target.route === values.route)).map((r) => requestBrief(r, root));
5074
- if (values.json) json({ requests: briefs, unreadable });
5571
+ if (values.json) json3({ requests: briefs, unreadable });
5075
5572
  else print(renderRequests(briefs, status, unreadable));
5076
5573
  return unreadable.length ? 1 : 0;
5077
5574
  }
@@ -5087,22 +5584,22 @@ async function cmdScreens(values, positionals, io) {
5087
5584
  const usage = "youiyoux screens capture --base <url of the app in development> [--route <one route>] [--project dir] [--out <screens dir>] [--storage-state <signed-in session>] [--json] or youiyoux screens login --base <url> [--project dir] [--storage-state <file>]";
5088
5585
  if (!["capture", "login"].includes(positionals[0] ?? "") || positionals.length > 1 || !values.base) throw new UsageError(usage);
5089
5586
  const { root } = await product(values);
5090
- if (positionals[0] === "login") return screensLogin(values.base, values["storage-state"] ? resolve12(values["storage-state"]) : sessionFile(root), io);
5091
- const dir = resolve12(values.out ?? join22(root, ".youi/screens"));
5092
- if (!existsSync5(join22(dir, "screens.json"))) {
5587
+ if (positionals[0] === "login") return screensLogin(values.base, values["storage-state"] ? resolve14(values["storage-state"]) : sessionFile(root), io);
5588
+ const dir = resolve14(values.out ?? join24(root, ".youi/screens"));
5589
+ if (!existsSync5(join24(dir, "screens.json"))) {
5093
5590
  process.stderr.write(`${style.error("\u2716")} no screens.json in ${dir}: list the routes to capture there, as {"schema":"youiyoux.screens/v1","project":"<name>","source":"<what the app is>","capturedAt":"","viewport":{"width":1440,"height":900},"screens":[{"route":"/","path":"/"}]}
5094
5591
  `);
5095
5592
  return 1;
5096
5593
  }
5097
- const session = chosenSession(root, values["storage-state"] ? resolve12(values["storage-state"]) : void 0);
5594
+ const session = chosenSession(root, values["storage-state"] ? resolve14(values["storage-state"]) : void 0);
5098
5595
  if (session?.saved) process.stderr.write(`${style.dim(`signed in with the session youiyoux screens login saved (${session.file}); delete it to capture signed out`)}
5099
5596
  `);
5100
- const { launchBrowser } = await import("./src-YYI52B5R.js");
5597
+ const { launchBrowser } = await import("./src-J6GPLCVO.js");
5101
5598
  const browser = await launchBrowser(process.env.YOUI_CHROMIUM, { purpose: { what: "youiyoux screens capture", command: "youiyoux screens capture" } });
5102
5599
  try {
5103
5600
  const report = await captureScreens(dir, { base: values.base, root, browser, ...values.route ? { routes: [values.route] } : {}, ...session ? { storageState: session.file } : {}, log: (line) => process.stderr.write(`${style.dim(line)}
5104
5601
  `) });
5105
- if (values.json) json(report);
5602
+ if (values.json) json3(report);
5106
5603
  else print(captureText(report));
5107
5604
  return report.screens.every((s) => s.error) ? 1 : 0;
5108
5605
  } finally {
@@ -5154,7 +5651,7 @@ async function cmdReport(values, positionals, io) {
5154
5651
  return result.code;
5155
5652
  }
5156
5653
  async function cmdMcp(values) {
5157
- const { startStdioServer } = await import("./src-DZWWMUDR.js");
5654
+ const { startStdioServer } = await import("./src-NWPGCDGH.js");
5158
5655
  await startStdioServer({ projectRoot: values.project ?? process.cwd(), ...loadOptions(values), ...values.cache ? { discoverCache: values.cache } : {} });
5159
5656
  return -1;
5160
5657
  }
@@ -5165,13 +5662,13 @@ function positiveInt(value, flag) {
5165
5662
  return n;
5166
5663
  }
5167
5664
  async function cmdDiscover(values, positionals, io) {
5168
- const cacheDir = resolve12(discoverCacheDir(values.cache));
5665
+ const cacheDir = resolve14(discoverCacheDir(values.cache));
5169
5666
  const { path, home } = indexOf(values);
5170
5667
  const catalog = () => readCatalog(cacheDir, path);
5171
5668
  const [action, ...rest] = positionals;
5172
5669
  if (action === "sources") {
5173
5670
  const names = await selectSources(cacheDir, rest[0], rest[1]);
5174
- if (values.json) json(names);
5671
+ if (values.json) json3(names);
5175
5672
  else print(names.join("\n"));
5176
5673
  return 0;
5177
5674
  }
@@ -5181,7 +5678,7 @@ async function cmdDiscover(values, positionals, io) {
5181
5678
  const summary = await syncIndex({ ...io.sleep ? { sleep: io.sleep } : {}, cacheDir, icons: values.icons ?? false, ...io.fetcher ? { fetcher: io.fetcher } : {}, ...io.timeoutMs ? { timeoutMs: io.timeoutMs } : {}, ...token ? { token } : {}, ...maxRegistries !== void 0 ? { maxRegistries } : {} });
5182
5679
  const loaded = await ingestCatalog(await sharedStore(path), cacheDir);
5183
5680
  await noteSource(home, { discover: cacheDir });
5184
- if (values.json) json(summary);
5681
+ if (values.json) json3(summary);
5185
5682
  else print(`${renderDiscoverSync(summary)}
5186
5683
  ${style.dim(`store \u2192 ${storeLocation({ path })} (${plural(loaded.chosen, "listing")})`)}`);
5187
5684
  return 0;
@@ -5194,11 +5691,11 @@ ${style.dim(`store \u2192 ${storeLocation({ path })} (${plural(loaded.chosen, "l
5194
5691
  if (action === "show") {
5195
5692
  if (!rest[0]) throw new UsageError("youiyoux discover show <id|ref>");
5196
5693
  if (values.source) {
5197
- json(await candidateSource(cacheDir, rest.join(" "), io));
5694
+ json3(await candidateSource(cacheDir, rest.join(" "), io));
5198
5695
  return 0;
5199
5696
  }
5200
5697
  const shown2 = await showFromCatalog(await catalog(), rest.join(" "));
5201
- if (values.json) json(shown2);
5698
+ if (values.json) json3(shown2);
5202
5699
  else print(renderDiscoverShow(shown2));
5203
5700
  return 0;
5204
5701
  }
@@ -5211,11 +5708,13 @@ ${style.dim(`store \u2192 ${storeLocation({ path })} (${plural(loaded.chosen, "l
5211
5708
  await noteSource(home, { discover: cacheDir });
5212
5709
  }
5213
5710
  const result = await searchCatalog(await catalog(), need, { ...values.role ? { role: values.role } : {}, ...licenses?.length ? { constraints: { licenses } } : {}, limit: positiveInt(values.limit, "--limit") ?? 10, offset: positiveInt(values.offset, "--offset") ?? 0, all: values.all, facets: true });
5214
- if (values.json) json(result);
5711
+ if (values.json) json3(result);
5215
5712
  else print(renderDiscoverSearch(result));
5216
5713
  return 0;
5217
5714
  }
5218
5715
  var COMMANDS = {
5716
+ adopt: cmdAdopt,
5717
+ system: cmdSystem,
5219
5718
  link: cmdLink,
5220
5719
  unlink: cmdUnlink,
5221
5720
  show: cmdShow,
@@ -5277,7 +5776,7 @@ ${HELP}
5277
5776
  }
5278
5777
  return guarded(() => handler(located(command2, parsed.values), rest, io));
5279
5778
  }
5280
- var IN_A_PRODUCT = /* @__PURE__ */ new Set(["link", "unlink", "show", "where", "bom", "check", "baseline", "rules", "plan", "migrate", "sync", "scan", "watch", "hooks", "agents", "mcp", "ui", "library", "render", "screens", "requests"]);
5779
+ var IN_A_PRODUCT = /* @__PURE__ */ new Set(["system", "link", "unlink", "show", "where", "bom", "check", "baseline", "rules", "plan", "migrate", "sync", "scan", "watch", "hooks", "agents", "mcp", "ui", "library", "render", "screens", "requests"]);
5281
5780
  var IN_A_DESIGN_REPO = /* @__PURE__ */ new Set(["build", "parity"]);
5282
5781
  function located(command2, values, cwd = process.cwd()) {
5283
5782
  if (IN_A_PRODUCT.has(command2) && values.project === void 0 && values.config === void 0 && values.root === void 0) {