youiyoux 0.1.6 → 0.1.8

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 (74) hide show
  1. package/dist/chunks/{chunk-RHXBYN4W.js → chunk-3HON5Q5K.js} +20 -15
  2. package/dist/chunks/{chunk-XAO3LS7M.js → chunk-6GITMASZ.js} +1 -1
  3. package/dist/chunks/{chunk-W2KFNGQP.js → chunk-BARSW3JO.js} +947 -228
  4. package/dist/chunks/{chunk-5YVH6NHT.js → chunk-DCBF7QQY.js} +4 -4
  5. package/dist/chunks/{chunk-DP54SKL4.js → chunk-DWYDHVLZ.js} +87 -11
  6. package/dist/chunks/{chunk-5Y2MVIGH.js → chunk-F33PKGES.js} +2 -2
  7. package/dist/chunks/{chunk-VOSWAWKC.js → chunk-FLBRX6Y3.js} +94 -23
  8. package/dist/chunks/{chunk-S57F4CMN.js → chunk-HDJ2TOWZ.js} +1 -1
  9. package/dist/chunks/{chunk-BHJCAZXO.js → chunk-Y4IBE6WS.js} +3 -3
  10. package/dist/chunks/{src-URZDH3QH.js → src-BYNWPNRT.js} +13 -1
  11. package/dist/chunks/{src-7RRKYIAS.js → src-DPFJ4S2N.js} +2 -2
  12. package/dist/chunks/{src-VKQJF7KR.js → src-EUVBYTKY.js} +4 -4
  13. package/dist/chunks/{src-6HTTOVZ5.js → src-M3OJMCUE.js} +5 -3
  14. package/dist/chunks/{src-4VYLEZ3Z.js → src-PURAHRCR.js} +6 -2
  15. package/dist/chunks/{src-DZWWMUDR.js → src-YKPJ3PII.js} +6 -6
  16. package/dist/chunks/{src-YYI52B5R.js → src-ZG7JU6YJ.js} +2 -2
  17. package/dist/hub/assets/{CandidatePanel-CnbK4S0j.js → CandidatePanel-ZzbsyZo3.js} +1 -1
  18. package/dist/hub/assets/{CatalogScreen-B5OVL-AR.js → CatalogScreen-DDYlvOHy.js} +1 -1
  19. package/dist/hub/assets/{ChangesScreen-GwGqJwxN.js → ChangesScreen-C8_EyHgJ.js} +1 -1
  20. package/dist/hub/assets/{CheatSheet-BLSOq-xa.js → CheatSheet-DfcPiXdS.js} +1 -1
  21. package/dist/hub/assets/{CommandPalette-CAWQfdXJ.js → CommandPalette-DfFkDTVg.js} +1 -1
  22. package/dist/hub/assets/DiscoverScreen-D6NTgEgk.js +2 -0
  23. package/dist/hub/assets/{DraftEditor-yIDapXTs.js → DraftEditor-CUNGYbg0.js} +2 -2
  24. package/dist/hub/assets/{GapsScreen-DXZAlm6v.js → GapsScreen-CqedCj4z.js} +1 -1
  25. package/dist/hub/assets/{HomeScreen-CEpiVhOa.js → HomeScreen-CIj1QTkw.js} +1 -1
  26. package/dist/hub/assets/{InventoryScreen-CEG3Pmw_.js → InventoryScreen-CyvFXa2_.js} +1 -1
  27. package/dist/hub/assets/{LibraryScreen-IaVnxrX9.js → LibraryScreen-C4WU0f42.js} +1 -1
  28. package/dist/hub/assets/{Opened-DJyf4Hnj.js → Opened-CeRWkA7c.js} +1 -1
  29. package/dist/hub/assets/{Preview-C069zVMP.js → Preview-CgW-FMB7.js} +1 -1
  30. package/dist/hub/assets/{RuleDetail-B4g2-TRe.js → RuleDetail-BDhZ1gCd.js} +1 -1
  31. package/dist/hub/assets/{RulesScreen-CAaXIWjO.js → RulesScreen-D3rSo_z2.js} +1 -1
  32. package/dist/hub/assets/{ScreenImage-DcI7_G3O.js → ScreenImage-DLeA3nRh.js} +1 -1
  33. package/dist/hub/assets/{ScreensScreen-DwczCjZD.js → ScreensScreen-bGuFIyWj.js} +1 -1
  34. package/dist/hub/assets/{SearchField-DaCpNAj6.js → SearchField-DtEgq9-Q.js} +1 -1
  35. package/dist/hub/assets/{ServiceDown-Bv-BPXro.js → ServiceDown-Bn6AcA3J.js} +1 -1
  36. package/dist/hub/assets/{SpecimenCapture-DlsjshVE.js → SpecimenCapture-p6YErF5y.js} +1 -1
  37. package/dist/hub/assets/{Specimens-C-lHp7sr.js → Specimens-DuaE0WeO.js} +2 -2
  38. package/dist/hub/assets/{StackScreen-DkmELsqR.js → StackScreen-DHDulDng.js} +1 -1
  39. package/dist/hub/assets/{StubScreens-Bt7lWLVK.js → StubScreens-CmhVNX_Z.js} +1 -1
  40. package/dist/hub/assets/StyleScreen-LwTqfGID.js +1 -0
  41. package/dist/hub/assets/{Tabs-1QQDS0I6.js → Tabs-Cbh9BCKW.js} +1 -1
  42. package/dist/hub/assets/{TokenDetail-DvCw6GqC.js → TokenDetail-BICobLK1.js} +1 -1
  43. package/dist/hub/assets/{TokensScreen-DoENV8Xy.js → TokensScreen-CyTHn55q.js} +1 -1
  44. package/dist/hub/assets/WorkspaceScreen-uPWqfklh.js +1 -0
  45. package/dist/hub/assets/{client-D7whEyep.js → client-BXHiuV_w.js} +1 -1
  46. package/dist/hub/assets/color-C6bxX90C.js +1 -0
  47. package/dist/hub/assets/contrast-B-uhyF_b.js +1 -0
  48. package/dist/hub/assets/{discover-XjJEViwk.js → discover-CFhyKler.js} +1 -1
  49. package/dist/hub/assets/{dist-BYQYaPBV.js → dist-C6iTWL0b.js} +1 -1
  50. package/dist/hub/assets/{dist-Bk4srtDn.js → dist-DYNYuTjI.js} +1 -1
  51. package/dist/hub/assets/{dist-D0z7Bvsy.js → dist-VmEpVQjP.js} +1 -1
  52. package/dist/hub/assets/{family-tiles-CyTdy_cP.js → family-tiles-B8_onXUi.js} +1 -1
  53. package/dist/hub/assets/folder-tree-YghfTmlZ.js +1 -0
  54. package/dist/hub/assets/main-CfbpuT4Y.js +2 -0
  55. package/dist/hub/assets/{try-live-zX3CSd3P.js → try-live-CX04d2L2.js} +1 -1
  56. package/dist/hub/assets/values-cLekxBZj.js +1 -0
  57. package/dist/hub/assets/workflow-DbBdiOmA.js +1 -0
  58. package/dist/hub/index.html +2 -1
  59. package/dist/hub-previews.js +5 -5
  60. package/dist/hub-server.js +3 -3
  61. package/dist/hub-snapshot.js +9 -8
  62. package/dist/schema/youi.config.schema.json +4 -0
  63. package/dist/schema/youi.project.schema.json +1 -1
  64. package/dist/schema/youi.status.schema.json +306 -0
  65. package/dist/schema/youi.workspace.schema.json +31 -0
  66. package/dist/youi.js +6 -6
  67. package/dist/youiyoux.js +6 -6
  68. package/package.json +4 -4
  69. package/dist/hub/assets/DiscoverScreen-Tl_Z6mgY.js +0 -2
  70. package/dist/hub/assets/StyleScreen-tq1tbRBo.js +0 -1
  71. package/dist/hub/assets/contrast-BkNHUAPa.js +0 -1
  72. package/dist/hub/assets/main-B9CIkk5m.js +0 -2
  73. package/dist/hub/assets/values-DzxR_TlI.js +0 -1
  74. package/dist/hub/assets/workflow-jpV8wh-U.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-3HON5Q5K.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-HDJ2TOWZ.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-6GITMASZ.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-FLBRX6Y3.js";
98
105
  import {
99
106
  Baseline,
100
107
  DESIGN_CONFIG_FILE,
@@ -108,11 +115,14 @@ import {
108
115
  PACKAGE_NAME,
109
116
  ProjectConfig,
110
117
  ScanReport,
118
+ WORKSPACE_FILE,
119
+ WorkspaceConfig,
111
120
  copyleft,
112
121
  executionLabel,
113
122
  findFiles,
114
123
  findUp,
115
124
  getSystem,
125
+ implementationsDir,
116
126
  loadDesignRepo,
117
127
  loadLibrary,
118
128
  matchesAny,
@@ -128,17 +138,17 @@ import {
128
138
  shortHash,
129
139
  toPosix,
130
140
  writeFileAtomic
131
- } from "./chunk-DP54SKL4.js";
141
+ } from "./chunk-DWYDHVLZ.js";
132
142
  import {
133
143
  __toESM
134
144
  } from "./chunk-WNTS7ZO4.js";
135
145
 
136
146
  // ../cli/src/main.ts
137
- var import_yaml3 = __toESM(require_dist(), 1);
147
+ var import_yaml4 = __toESM(require_dist(), 1);
138
148
  import { execFile as execFile5 } from "node:child_process";
139
149
  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";
150
+ import { appendFile as appendFile2, readFile as readFile10, rm as rm6, rmdir as rmdir3 } from "node:fs/promises";
151
+ import { basename as basename5, dirname as dirname13, join as join25, relative as relative11, resolve as resolve15 } from "node:path";
142
152
  import { parseArgs, promisify as promisify5 } from "node:util";
143
153
 
144
154
  // ../cli/src/check.ts
@@ -156,7 +166,7 @@ import { join as join2, relative, resolve } from "node:path";
156
166
 
157
167
  // ../cli/src/command.ts
158
168
  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"];
169
+ var SUBCOMMANDS = ["show", "where", "bom", "check", "baseline", "rules", "plan", "migrate", "init", "adopt", "status", "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
170
 
161
171
  // ../cli/src/agents.ts
162
172
  var import_yaml = __toESM(require_dist(), 1);
@@ -206,7 +216,7 @@ function enumSummary(contract) {
206
216
  return Object.entries(contract.props).filter(([, spec]) => spec.type === "enum").map(([name, spec]) => `${name}: ${spec.values.join("|")}`).join("; ");
207
217
  }
208
218
  function importHint(config) {
209
- return config.importFrom ? `\`${config.importFrom}\`` : `\`${config.componentsDir}\` (by a relative path from your file)`;
219
+ return config.importFrom ? `\`${withFile(config.importFrom)}\`` : `\`${config.componentsDir}\` (by a relative path from your file)`;
210
220
  }
211
221
  function agentsBlock(project2) {
212
222
  const { config, system } = project2;
@@ -260,7 +270,7 @@ function frontmatter(project2, build) {
260
270
  if (dark !== colors[key(t)]) colors[`${key(t)}-dark`] = dark;
261
271
  }
262
272
  const typography = {};
263
- for (const t of byGroup("typography")) {
273
+ for (const t of byGroup("typography").filter((t2) => t2.type === "typography")) {
264
274
  const v = t.light;
265
275
  typography[key(t)] = {
266
276
  fontFamily: cssValue("fontFamily", v.fontFamily),
@@ -466,8 +476,9 @@ async function initProject(options) {
466
476
  }
467
477
  notes.push(`owned components: ${componentsDir}`);
468
478
  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}`);
479
+ const barrel = (await Promise.all(["index.ts", "index.tsx", "index.js"].map((f) => exists(join2(root, componentsDir, f))))).some(Boolean);
480
+ const importFrom = alias !== void 0 && componentsDir.startsWith(alias) ? `@/${componentsDir.slice(alias.length)}${barrel ? "" : "/{file}"}` : void 0;
481
+ if (importFrom) notes.push(`imports use the @/ alias: ${importFrom}${barrel ? "" : " (each component from its own file; no index barrel)"}`);
471
482
  const include = (await Promise.all(
472
483
  [
473
484
  ["app", "app/**/*.{ts,tsx,css}"],
@@ -491,7 +502,8 @@ async function initProject(options) {
491
502
  componentsDir,
492
503
  ...importFrom ? { importFrom } : {},
493
504
  routes: expo ? { strategy: "expo-router", appDir } : { strategy: "none" },
494
- ...prefix ? { outputs: { cssVarPrefix: prefix } } : {}
505
+ ...options.outputs ? { outputs: options.outputs } : prefix ? { outputs: { cssVarPrefix: prefix } } : {},
506
+ ...options.tokenSources && options.tokenSources.length > 0 ? { tokenSources: [...options.tokenSources] } : {}
495
507
  });
496
508
  await writeFileAtomic(file, `${JSON.stringify(config, null, 2)}
497
509
  `);
@@ -617,7 +629,7 @@ var DRIFT_SEVERITY = { "near-duplicate": "warn", "hardcoded-value": "error", "of
617
629
  async function importOf(project2) {
618
630
  const alias = await aliasRoot(project2.root, project2.config.tsconfig);
619
631
  return (file, fromFile) => {
620
- if (project2.system && file.startsWith(`${project2.config.componentsDir}/`)) return importSpecifier(project2.config, fromFile);
632
+ if (project2.system && file.startsWith(`${project2.config.componentsDir}/`)) return importSpecifier(project2.config, fromFile, file);
621
633
  if (alias !== void 0 && file.startsWith(alias)) return `@/${file.slice(alias.length).replace(/\.(tsx?|jsx?|mjs)$/, "").replace(/\/index$/, "")}`;
622
634
  const path = posix.relative(posix.dirname(fromFile), file).replace(/\.(tsx?|jsx?|mjs|astro)$/, "").replace(/\/index$/, "");
623
635
  return path.startsWith(".") ? path : `./${path}`;
@@ -634,7 +646,7 @@ async function chosenLibraries(project2, added, notes) {
634
646
  const chosen = /* @__PURE__ */ new Map();
635
647
  for (const { contract } of project2.system?.components ?? []) {
636
648
  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)}"` });
649
+ 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
650
  }
639
651
  let hits;
640
652
  try {
@@ -917,23 +929,24 @@ var Writer = class {
917
929
  }
918
930
  async link(file, target) {
919
931
  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" });
932
+ const stat4 = await lstat(path).catch(() => null);
933
+ if (stat4?.isSymbolicLink() && await readlink(path) === target) return this.record({ file, role: "agents", action: "unchanged" });
934
+ if (stat4 && !stat4.isSymbolicLink()) return this.record({ file, role: "agents", action: "skipped", detail: "a real directory is in the way" });
923
935
  if (!this.options.dryRun) {
924
936
  await mkdir(dirname2(path), { recursive: true });
925
- if (stat3) await rm(path);
937
+ if (stat4) await rm(path);
926
938
  await symlink(target, path);
927
939
  }
928
- this.record({ file, role: "agents", action: stat3 ? "updated" : "created", detail: `\u2192 ${target}` });
940
+ this.record({ file, role: "agents", action: stat4 ? "updated" : "created", detail: `\u2192 ${target}` });
929
941
  }
930
942
  };
931
943
  async function syncProject(project2, options = {}) {
932
944
  const { config, system, repo } = project2;
933
945
  const previous = await readLock(project2.root);
934
946
  const writer = new Writer(project2, options, previous);
947
+ const implementations = implementationsDir(system.config);
935
948
  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`);
949
+ 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
950
  }
938
951
  const build = await buildTokens(repo.root, system.config, config.outputs, { contracts: system.components.map((c) => c.contract) });
939
952
  for (const file of build.files) await writer.generated(`${config.outputs.dir}/${file.path}`, file.content, "tokens");
@@ -952,11 +965,15 @@ async function syncProject(project2, options = {}) {
952
965
  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
966
  const upstreams = /* @__PURE__ */ new Map();
954
967
  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`);
968
+ const upstream = await readTextOrNull(join6(repo.root, implementations, source));
969
+ if (upstream === null) throw new Error(`${implementations}/${source} is named by a contract but does not exist`);
957
970
  upstreams.set(source, upstream);
958
971
  }
959
- for (const [source, { elements, version }] of sources) await writer.owned(vendoredPath(project2, source), upstreams.get(source), elements, version);
972
+ for (const [source, { elements, version }] of sources) {
973
+ const file = vendoredPath(project2, source);
974
+ 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" });
975
+ else await writer.owned(file, upstreams.get(source), elements, version);
976
+ }
960
977
  await writer.retire();
961
978
  const lock = {
962
979
  schema: "youi.lock/v1",
@@ -970,6 +987,10 @@ async function syncProject(project2, options = {}) {
970
987
  `);
971
988
  return { actions: writer.actions, lock, unknownTools: unknownToolMentions(project2) };
972
989
  }
990
+ async function sameFile(a, b) {
991
+ const [x, y] = await Promise.all([realpath(a).catch(() => null), realpath(b).catch(() => null)]);
992
+ return x !== null && x === y;
993
+ }
973
994
  function mcpArgs(project2) {
974
995
  const rel = (path) => relative3(project2.root, path) || ".";
975
996
  return [
@@ -1039,7 +1060,7 @@ async function runCheck(project2, options = {}) {
1039
1060
  const { report } = scan;
1040
1061
  const checked = new Set(scan.files);
1041
1062
  const unmatched = scope ? [...scope].filter((f) => !checked.has(f)) : [];
1042
- const failed = report.coverage.failed.filter((f) => !scope || scope.has(f.file) || !/\.(tsx?|css)$/.test(f.file));
1063
+ const failed2 = report.coverage.failed.filter((f) => !scope || scope.has(f.file) || !/\.(tsx?|css)$/.test(f.file));
1043
1064
  const wantedRule = (id) => !options.rule || id.endsWith(`/rule/${options.rule}`);
1044
1065
  const violations = report.violations.filter((v) => wantedRule(v.rule) && (!options.quiet || v.severity === "error"));
1045
1066
  const notRun = failedChecks(report.checks).filter((c) => wantedRule(c.rule));
@@ -1050,7 +1071,7 @@ async function runCheck(project2, options = {}) {
1050
1071
  const failing = baseline ? [...baseline.comparison.new, ...baseline.comparison.regressed] : violations;
1051
1072
  const errors = failing.filter((v) => v.severity === "error").length + (lock ?? []).filter((i) => i.severity === "error").length;
1052
1073
  const drift = diff ? await analyseChange(project2, scan, diff, { violations }) : null;
1053
- return { identity: scan.identity, scan, scope, unmatched, failed, notRun, violations, lock, baseline, errors, incomplete: unmatched.length > 0 || failed.length > 0 || notRun.length > 0 || (drift?.incomplete.length ?? 0) > 0, diff: drift };
1074
+ return { identity: scan.identity, scan, scope, unmatched, failed: failed2, notRun, violations, lock, baseline, errors, incomplete: unmatched.length > 0 || failed2.length > 0 || notRun.length > 0 || (drift?.incomplete.length ?? 0) > 0, diff: drift };
1054
1075
  }
1055
1076
  async function lockIssues(project2) {
1056
1077
  const lock = await verifyLock(project2);
@@ -1760,18 +1781,18 @@ async function runClaudeHook(input, target = {}) {
1760
1781
  try {
1761
1782
  const project2 = await load(target, dirname5(file));
1762
1783
  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]));
1784
+ const relative12 = relPath(await real(project2.root), await real(file));
1785
+ if (!isChecked(project2, relative12)) return SILENT;
1786
+ const found = await changeFor(() => workingTreeChange(project2, void 0, [relative12]));
1766
1787
  if (found.problem !== void 0) return claudeContext(found.problem, project2.root);
1767
1788
  const { change } = found;
1768
1789
  const result = configured(project2) ? await runCheck(project2, { files: [file], lock: false, baseline: await baselineFor(project2, target), diff: change }) : await runRepoCheck(project2, change);
1769
1790
  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;
1791
+ const see = COMPONENT_FILE.test(relative12) ? `See it: youiyoux render ${quoted(relative12)} (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;
1792
+ if (lines.length === 0) return see ? claudeContext(`youiyoux: ${relative12} passes the design-system check. ${see}`, project2.root) : SILENT;
1772
1793
  const older = "violations" in result ? result.violations.filter((v) => !isAdded(change.added, v.file, v.line)).length : 0;
1773
1794
  const known = older > 0 ? ` (${older} older findings on unchanged lines omitted)` : "";
1774
- return claudeContext(`youiyoux design-system check (advisory) on ${relative9}${known}:
1795
+ return claudeContext(`youiyoux design-system check (advisory) on ${relative12}${known}:
1775
1796
  ${lines.join("\n")}${!configured(project2) ? `
1776
1797
  ${result.diff?.notes.join("\n")}` : ""}
1777
1798
  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 +1940,30 @@ var serialize = ({ data, indent, newline }) => `${JSON.stringify(data, null, ind
1919
1940
  async function installClaudeMcp(repoRoot, server) {
1920
1941
  const file = join11(repoRoot, CLAUDE_MCP);
1921
1942
  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 } } } };
1943
+ const json4 = readJson2(file, before);
1944
+ const servers = json4.data.mcpServers ?? {};
1945
+ const next2 = { ...json4, data: { ...json4.data, mcpServers: { ...servers, [MCP_NAME]: { command: server.command, args: server.args } } } };
1925
1946
  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
1947
  }
1927
1948
  async function removeClaudeMcp(repoRoot) {
1928
1949
  const file = join11(repoRoot, CLAUDE_MCP);
1929
1950
  const before = await readTextOrNull(file);
1930
1951
  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 ?? {} };
1952
+ const json4 = readJson2(file, before);
1953
+ const servers = { ...json4.data.mcpServers ?? {} };
1933
1954
  if (!(MCP_NAME in servers)) return { file, action: "unchanged", guarantee: `no ${MCP_NAME} server here` };
1934
1955
  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`);
1956
+ const { mcpServers: _, ...rest } = json4.data;
1957
+ const data = Object.keys(servers).length > 0 ? { ...json4.data, mcpServers: servers } : rest;
1958
+ return removeOrWrite(file, Object.keys(data).length > 0 ? serialize({ ...json4, data }) : "", `${MCP_NAME} removed; other servers kept`);
1938
1959
  }
1939
1960
  var tomlString = (value) => JSON.stringify(value);
1940
1961
  function codexBlock(server) {
1941
1962
  return [TOML_BEGIN, `[mcp_servers.${MCP_NAME}]`, `command = ${tomlString(server.command)}`, `args = [${server.args.map(tomlString).join(", ")}]`, TOML_END].join("\n");
1942
1963
  }
1943
1964
  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);
1965
+ const inside3 = withoutBlock(text, TOML_BEGIN, TOML_END) ?? text;
1966
+ return new RegExp(`^\\s*\\[\\s*mcp_servers\\s*\\.\\s*"?${MCP_NAME}"?\\s*\\]`, "m").test(inside3);
1946
1967
  }
1947
1968
  async function installCodexMcp(repoRoot, server) {
1948
1969
  const file = join11(repoRoot, CODEX_CONFIG);
@@ -2066,7 +2087,7 @@ import { homedir, userInfo } from "node:os";
2066
2087
  import { join as join12, resolve as resolve6 } from "node:path";
2067
2088
  async function swapImpact(candidate, family) {
2068
2089
  try {
2069
- const { discoverIndexPath: discoverIndexPath2, sharedStore: sharedStore2 } = await import("./src-7RRKYIAS.js");
2090
+ const { discoverIndexPath: discoverIndexPath2, sharedStore: sharedStore2 } = await import("./src-DPFJ4S2N.js");
2070
2091
  const path = discoverIndexPath2();
2071
2092
  if (!process.env.DATABASE_URL && !existsSync(path)) return null;
2072
2093
  const impact = await (await sharedStore2(path, process.env, { readOnly: true })).swapImpact(candidate, family);
@@ -2077,7 +2098,7 @@ async function swapImpact(candidate, family) {
2077
2098
  }
2078
2099
  async function libraryLicence(id) {
2079
2100
  try {
2080
- const { discoverCacheDir: discoverCacheDir2, findListing } = await import("./src-7RRKYIAS.js");
2101
+ const { discoverCacheDir: discoverCacheDir2, findListing } = await import("./src-DPFJ4S2N.js");
2081
2102
  const { entry } = await findListing(discoverCacheDir2(void 0, process.env), id);
2082
2103
  return entry.id === id ? entry.license : null;
2083
2104
  } catch {
@@ -2085,7 +2106,7 @@ async function libraryLicence(id) {
2085
2106
  }
2086
2107
  }
2087
2108
  async function startRuleEditor(options) {
2088
- const { FileStore, RuleWorkflow, serveWorkflow } = await import("./src-VKQJF7KR.js");
2109
+ const { FileStore, RuleWorkflow, serveWorkflow } = await import("./src-EUVBYTKY.js");
2089
2110
  const projectRoot = resolve6(options.project);
2090
2111
  const load2 = { ...options.config ? { configPath: resolve6(options.config) } : {}, ...options.design ? { designRoot: resolve6(options.design) } : {}, ...options.root ? { productRoot: resolve6(options.root) } : {} };
2091
2112
  const project2 = await loadProject(projectRoot, load2);
@@ -2126,7 +2147,7 @@ function builtHubPlan(toolRoot, options, out) {
2126
2147
  if (options.watch && !namesSystem(options)) throw new Error("--watch keeps a system's snapshot live: name it with --design or --project (the demo system is not watched)");
2127
2148
  const hub = join13(toolRoot, BUILT_HUB);
2128
2149
  const snapshot = flags.length > 0 ? [join13(toolRoot, "hub-snapshot.js"), "--out", out, ...flags] : null;
2129
- const server = [join13(toolRoot, "hub-server.js"), "--dist", hub, "--kit", join13(hub, "kit"), "--snapshot", snapshot ? out : join13(toolRoot, BUILT_DEMO), ...snapshot ? ["--build-frame"] : [], ...options.port ? ["--port", options.port] : [], ...options.api ? ["--api", options.api] : [], ...options.watch ? ["--live"] : []];
2150
+ const server = [join13(toolRoot, "hub-server.js"), "--dist", hub, "--kit", join13(hub, "kit"), "--snapshot", snapshot ? out : join13(toolRoot, BUILT_DEMO), ...snapshot ? ["--build-frame"] : [], ...options.port ? ["--port", options.port] : [], ...options.api ? ["--api", options.api] : [], ...options.watch || options.workspace ? ["--live"] : []];
2130
2151
  return { snapshot, server, watch: options.watch && snapshot ? [...snapshot, "--watch"] : null };
2131
2152
  }
2132
2153
  function run5(args, cwd, env, started) {
@@ -2151,6 +2172,7 @@ async function runBuiltHub(toolRoot, options) {
2151
2172
  const code = await run5(plan.snapshot, process.cwd(), {}, (child) => children.add(child));
2152
2173
  if (code !== 0) return code;
2153
2174
  }
2175
+ options.workspace?.((project2) => run5(builtHubPlan(toolRoot, { ...options, project: project2 }, out).snapshot, process.cwd(), {}, (child) => children.add(child)));
2154
2176
  if (plan.watch) children.add(spawn(process.execPath, plan.watch, { cwd: process.cwd(), stdio: "inherit" }));
2155
2177
  return await run5(plan.server, process.cwd(), {}, (child) => children.add(child));
2156
2178
  } finally {
@@ -2170,6 +2192,7 @@ async function runHub(toolRoot, options) {
2170
2192
  const code = await run5(plan.snapshot, process.cwd(), {});
2171
2193
  if (code !== 0) return code;
2172
2194
  }
2195
+ options.workspace?.((project2) => run5(hubPlan(toolRoot, { ...options, project: project2 }).snapshot, process.cwd(), {}));
2173
2196
  const watcher = plan.watch ? spawn(process.execPath, plan.watch, { cwd: process.cwd(), stdio: "inherit" }) : null;
2174
2197
  try {
2175
2198
  return await run5(plan.vite, plan.web, plan.env);
@@ -2217,15 +2240,587 @@ function renderRequests(briefs, status, unreadable) {
2217
2240
  return lines.join("\n\n");
2218
2241
  }
2219
2242
 
2243
+ // ../cli/src/adopt.ts
2244
+ import { readFile as readFile4 } from "node:fs/promises";
2245
+ 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";
2246
+ var import_yaml2 = __toESM(require_dist(), 1);
2247
+ var OUT_DIR = "design/build";
2248
+ var DARK_BLOCKS = [
2249
+ { selector: ".dark" },
2250
+ { selector: ":root.dark" },
2251
+ { selector: '[data-theme="dark"]' },
2252
+ { selector: ':root[data-theme="dark"]' },
2253
+ { selector: ":root", outer: "@media (prefers-color-scheme: dark)" },
2254
+ { selector: ':root:not([data-theme="light"])', outer: "@media (prefers-color-scheme: dark)" }
2255
+ ];
2256
+ var kebab = (name) => name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
2257
+ var pascal = (file) => basename2(file).replace(/\.[^.]+$/, "").split(/[-_.]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
2258
+ var json = (value) => `${JSON.stringify(value, null, 2)}
2259
+ `;
2260
+ function slugOf(name) {
2261
+ return name.replace(/^@[^/]+\//, "").toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^[^a-z]+/, "").replace(/-+$/, "") || "app";
2262
+ }
2263
+ function inside2(root, path, what) {
2264
+ const normal = posix3.normalize(path.split("\\").join("/")).replace(/^\.\//, "").replace(/\/+$/, "");
2265
+ 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`);
2266
+ return normal;
2267
+ }
2268
+ function exportedNames(source) {
2269
+ const names = /* @__PURE__ */ new Set();
2270
+ 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]);
2271
+ for (const match of source.matchAll(/^export\s+\{([^}]*)\}/gm)) {
2272
+ for (const part of match[1].split(",")) {
2273
+ const spec = part.trim();
2274
+ if (!spec || spec.startsWith("type ")) continue;
2275
+ names.add(spec.split(/\s+as\s+/).at(-1).trim());
2276
+ }
2277
+ }
2278
+ return [...names];
2279
+ }
2280
+ function defaultVariants(source) {
2281
+ const body = /defaultVariants\s*:\s*\{([^}]*)\}/.exec(source)?.[1] ?? "";
2282
+ return Object.fromEntries([...body.matchAll(/([A-Za-z_$][\w$]*)\s*:\s*["'`]([^"'`]+)["'`]/g)].map((m) => [m[1], m[2]]));
2283
+ }
2284
+ function propsOf(facts, defaults) {
2285
+ const props = {};
2286
+ for (const prop of facts?.props ?? []) {
2287
+ 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] } : {} };
2288
+ else if (prop.name === "className") props[prop.name] = { type: "string", required: false, description: "Extra classes, merged last." };
2289
+ else if (prop.name === "children") props[prop.name] = { type: "node", required: false };
2290
+ else if (/^on[A-Z]/.test(prop.name)) props[prop.name] = { type: "function", required: false };
2291
+ }
2292
+ return props;
2293
+ }
2294
+ function generatedOf(file, css) {
2295
+ const head = css.slice(0, 400);
2296
+ if (!head.includes(GENERATED_MARK)) return null;
2297
+ const from = /@generated by youi from the (.+?) design language v(\d+\.\d+\.\d+(?:-[\w.-]+?)?)\./.exec(head);
2298
+ return { file, count: rootVariables(css).length, system: from?.[1] ?? null, version: from?.[2] ?? null };
2299
+ }
2300
+ async function stylesheetOf(root, asked, fork) {
2301
+ const generated = [];
2302
+ let forked = null;
2303
+ const usable = async (file) => {
2304
+ const css = await readTextOrNull(join14(root, file));
2305
+ if (css === null) return false;
2306
+ const made = generatedOf(file, css);
2307
+ if (made && !generated.some((g) => g.file === file)) generated.push(made);
2308
+ if (made && fork) forked = made;
2309
+ return !made || fork;
2310
+ };
2311
+ if (asked) {
2312
+ 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`);
2313
+ return { file: asked, generated, forked };
2314
+ }
2315
+ const shadcn = (await readJson(join14(root, "components.json")))?.tailwind?.css;
2316
+ if (typeof shadcn === "string" && await usable(shadcn)) return { file: posix3.normalize(shadcn), generated, forked };
2317
+ let best = null;
2318
+ for (const file of await findFiles(root, ["**/*.css"], ["**/build/**", "**/out/**", "**/.next/**", `${OUT_DIR}/**`])) {
2319
+ const css = await readTextOrNull(join14(root, file)) ?? "";
2320
+ const count = rootVariables(css).length;
2321
+ if (count === 0) continue;
2322
+ const made = generatedOf(file, css);
2323
+ if (made && !generated.some((g) => g.file === file)) generated.push(made);
2324
+ if (made && !fork) continue;
2325
+ if (!best || count > best.count) best = { file, count, made };
2326
+ }
2327
+ return { file: best?.file ?? null, generated, forked: best?.made ?? forked };
2328
+ }
2329
+ function rootVariables(css) {
2330
+ 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 }));
2331
+ }
2332
+ 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"]);
2333
+ function commonPrefix(names) {
2334
+ const counts = /* @__PURE__ */ new Map();
2335
+ for (const name of names) if (name.includes("-")) counts.set(name.split("-")[0], (counts.get(name.split("-")[0]) ?? 0) + 1);
2336
+ const [word, count] = [...counts].sort((a, b) => b[1] - a[1])[0] ?? ["", 0];
2337
+ return count >= 3 && count >= names.length * 0.8 && /^[a-z][a-z0-9]*$/.test(word) && !NOT_A_PREFIX.has(word) ? word : "";
2338
+ }
2339
+ function lengthPath(name, value) {
2340
+ if (parseDimension(value) === null && !/^var\(/.test(value)) return null;
2341
+ if (/radius/.test(name)) return `radius.${name.replace(/^radius-(?=.)/, "")}`;
2342
+ if (/^typography-/.test(name)) return `typography.${name.slice("typography-".length)}`;
2343
+ if (/^(?:text|font)-|-(?:font-size|line-height|letter-spacing|font-weight)$/.test(name)) return `typography.${name}`;
2344
+ return null;
2345
+ }
2346
+ function variableNames(tree, path = []) {
2347
+ return Object.entries(tree).flatMap(([key2, node]) => {
2348
+ if (key2.startsWith("$")) return [];
2349
+ if (node && typeof node === "object" && "$value" in node) {
2350
+ const css = node.$extensions?.[DL_EXT]?.css;
2351
+ return [css ?? [...path, key2].join("-")];
2352
+ }
2353
+ return variableNames(node, [...path, key2]);
2354
+ });
2355
+ }
2356
+ function moveDeclarations(css, blocks, moved, importPath) {
2357
+ 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)));
2358
+ if (rules.length === 0) return null;
2359
+ const lines = css.split("\n");
2360
+ const drop = /* @__PURE__ */ new Set();
2361
+ for (const rule2 of rules) {
2362
+ for (const d of rule2.declarations) {
2363
+ const line = lines[d.line - 1] ?? "";
2364
+ if (moved.has(d.property.slice(2)) && new RegExp(`^\\s*${d.property.replace(/[-]/g, "\\-")}\\s*:[^;]*;?\\s*$`).test(line)) drop.add(d.line - 1);
2365
+ }
2366
+ }
2367
+ if (drop.size === 0) return null;
2368
+ let content = lines.filter((_, i) => !drop.has(i)).join("\n");
2369
+ for (const block of blocks) content = content.replace(new RegExp(`(^|\\n)[ \\t]*${block.selector.replace(/[.[\]()"=:]/g, "\\$&")}\\s*\\{\\s*\\}[ \\t]*(?=\\n|$)`, "g"), "");
2370
+ 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"), "");
2371
+ const statement = `@import "${importPath}";`;
2372
+ const imports = [...content.matchAll(/^@import[^;]*;[ \t]*$/gm)];
2373
+ const at = imports.length > 0 ? imports.at(-1).index + imports.at(-1)[0].length : 0;
2374
+ content = at > 0 ? `${content.slice(0, at)}
2375
+ ${statement}${content.slice(at)}` : `${statement}
2376
+ ${content}`;
2377
+ return { content: content.replace(/\n{3,}/g, "\n\n"), moved: drop.size };
2378
+ }
2379
+ async function planAdoption(options) {
2380
+ const root = options.root;
2381
+ const pkg = await readJson(join14(root, "package.json"));
2382
+ const name = typeof pkg?.name === "string" ? pkg.name : basename2(root);
2383
+ const id = options.system ?? slugOf(name);
2384
+ 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>)`);
2385
+ const notes = [];
2386
+ const facts = await collectComponents(root);
2387
+ const chosen = options.componentsDir ?? facts.shadcnUi ?? await uiFolder(root) ?? facts.componentsDir;
2388
+ const componentsDir = chosen ? inside2(root, chosen, "the components folder") : null;
2389
+ 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)");
2390
+ const inDir = (file) => posix3.dirname(file) === componentsDir;
2391
+ const files = (await findFiles(root, [`${componentsDir}/*.{ts,tsx,js,jsx}`])).filter((f) => !/^index\./.test(posix3.basename(f)) && !/\.(test|spec|stories|d)\.[^.]+$/.test(f));
2392
+ const byFile = /* @__PURE__ */ new Map();
2393
+ 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]);
2394
+ const out = [];
2395
+ const components = [];
2396
+ const support = [];
2397
+ const RULES = ["color.no-raw-color", "tokens.no-arbitrary-values", "components.canonical-import"].map((r) => `youi:${id}/rule/${r}`);
2398
+ for (const file of files) {
2399
+ const defined = (byFile.get(file) ?? []).filter((c) => /^[A-Z]/.test(c.export));
2400
+ const source = await readFile4(join14(root, file), "utf8");
2401
+ if (defined.length === 0) {
2402
+ support.push(posix3.basename(file));
2403
+ continue;
2404
+ }
2405
+ const main2 = defined.find((c) => c.export === pascal(file)) ?? defined[0];
2406
+ const folder = kebab(main2.export);
2407
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(folder)) {
2408
+ notes.push(`${file}: ${main2.export} has no contract (its name is not plain letters and digits); write one by hand`);
2409
+ continue;
2410
+ }
2411
+ const exportsOf = exportedNames(source);
2412
+ const sites = main2.sites.length;
2413
+ const contract = {
2414
+ schema: "youi.component/v1",
2415
+ id: `youi:${id}/component/${folder}`,
2416
+ name: main2.export,
2417
+ version: "0.1.0",
2418
+ status: "draft",
2419
+ summary: `The product's own ${main2.export} (${file}), used at ${sites} ${sites === 1 ? "site" : "sites"}.`.slice(0, 200),
2420
+ source: { file: posix3.basename(file), export: main2.export, also: defined.filter((c) => c !== main2).map((c) => c.export) },
2421
+ platforms: facts.dependencies.includes("react-native") && !facts.dependencies.includes("react-dom") ? ["ios", "android"] : ["web"],
2422
+ props: propsOf(main2, defaultVariants(source)),
2423
+ tokens: {},
2424
+ a11y: { notes: [] },
2425
+ rules: RULES,
2426
+ guidance: { do: [], dont: [] },
2427
+ examples: [],
2428
+ aliases: exportsOf.filter((n) => !/^[A-Z]/.test(n)),
2429
+ owners: []
2430
+ };
2431
+ out.push({ path: `design/components/${folder}/${folder}.contract.json`, content: json(contract) });
2432
+ components.push(main2.export);
2433
+ }
2434
+ if (components.length === 0) throw new Error(`${componentsDir} exports no React components: pass --components <dir> (the folder your app imports its UI components from)`);
2435
+ const found = await stylesheetOf(root, options.css, options.fork === true);
2436
+ const stylesheet = found.file ? inside2(root, found.file, "the stylesheet") : null;
2437
+ const css = stylesheet ? await readTextOrNull(join14(root, stylesheet)) : null;
2438
+ if (stylesheet && css === null) throw new Error(`${stylesheet} does not exist (--css <file>: the stylesheet that defines your :root variables)`);
2439
+ 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("--")))) : [];
2440
+ const darkBlock = darkBlocks[0];
2441
+ const variables = css ? rootVariables(css) : [];
2442
+ const prefix = commonPrefix(variables.map((v) => v.name));
2443
+ const full = (name2) => prefix ? `${prefix}-${name2}` : name2;
2444
+ 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 }));
2445
+ const unprefixed = variables.filter((v) => prefix && !v.name.startsWith(`${prefix}-`)).map((v) => v.name);
2446
+ 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" : ""}`);
2447
+ const paths = Object.fromEntries(own.flatMap((v) => lengthPath(v.name, v.value) ? [[v.name, lengthPath(v.name, v.value)]] : []));
2448
+ const imported = css ? importTokens({ css: { css, prefix, light: { selector: ":root" }, ...darkBlock ? { dark: darkBlock } : {} }, paths }) : null;
2449
+ const tokenCount = imported?.report.fromCss.length ?? 0;
2450
+ const hasDark = Boolean(imported && Object.keys(imported.dark).length > 0);
2451
+ out.push({ path: `design/tokens/${id}.tokens.json`, content: json(imported?.base ?? {}) });
2452
+ if (hasDark) out.push({ path: `design/tokens/${id}.dark.tokens.json`, content: json(imported.dark) });
2453
+ out.push({
2454
+ path: `design/tokens/${id}.resolver.json`,
2455
+ content: json({
2456
+ $schema: "https://www.designtokens.org/schemas/2025.10/resolver.json",
2457
+ name,
2458
+ version: "2025.10",
2459
+ description: `${name}'s tokens, adopted from ${stylesheet ?? "no stylesheet"}.`,
2460
+ sets: { base: { sources: [{ $ref: `${id}.tokens.json` }] } },
2461
+ ...hasDark ? { modifiers: { theme: { contexts: { light: [], dark: [{ $ref: `${id}.dark.tokens.json` }] }, default: "light" } } } : {},
2462
+ resolutionOrder: [{ $ref: "#/sets/base" }, ...hasDark ? [{ $ref: "#/modifiers/theme" }] : []]
2463
+ })
2464
+ });
2465
+ if (!stylesheet) notes.push("no stylesheet defines :root variables, so the System has no tokens yet: add them to design/tokens");
2466
+ const left = (imported?.report.unparsed ?? []).map((l) => ({ ...l, name: full(l.name) }));
2467
+ const moved = new Set(imported ? variableNames(imported.base).map(full) : []);
2468
+ const importPath = stylesheet ? relative6(dirname7(join14(root, stylesheet)), join14(root, OUT_DIR, "tokens.css")).split("\\").join("/") : "";
2469
+ const moving = css && moved.size > 0 ? moveDeclarations(css, [{ selector: ":root" }, ...darkBlocks], moved, importPath.startsWith(".") ? importPath : `./${importPath}`) : null;
2470
+ 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;
2471
+ 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"}`);
2472
+ 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`);
2473
+ for (const [rule2, content] of starterRules(id, componentsDir)) out.push({ path: `design/rules/${rule2}.rule.yaml`, content });
2474
+ out.push({
2475
+ path: "design/design.md",
2476
+ content: `# ${name}
2477
+
2478
+ 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.
2479
+ `
2480
+ });
2481
+ const system = {
2482
+ id,
2483
+ name,
2484
+ version: "0.1.0",
2485
+ description: `${name}'s own components and tokens, adopted with youiyoux adopt.`,
2486
+ tokens: `design/tokens/${id}.resolver.json`,
2487
+ ...hasDark ? { themeModifier: { name: "theme", light: "light", dark: "dark" } } : {},
2488
+ components: "design/components",
2489
+ implementations: componentsDir,
2490
+ support,
2491
+ rules: "design/rules",
2492
+ design: "design/design.md"
2493
+ };
2494
+ out.unshift({ path: "youi.config.json", content: json({ schema: "youi.config/v1", name, systems: [system] }) });
2495
+ return {
2496
+ system,
2497
+ componentsDir,
2498
+ files: out,
2499
+ components,
2500
+ support,
2501
+ tokens: { stylesheet, count: tokenCount, dark: hasDark, left },
2502
+ stylesheet: rewritten && stylesheet ? { path: stylesheet, ...rewritten } : null,
2503
+ outputs: { dir: OUT_DIR, cssVarPrefix: prefix, dark: darkBlocks.map((b) => ({ ...b.outer ? { media: b.outer.replace(/^@media\s+/, "") } : {}, selector: b.selector })), targets: ["css-vars"] },
2504
+ tokenSources: stylesheet ? [stylesheet] : [],
2505
+ notes,
2506
+ follows: found.generated.filter((g) => g.file !== found.forked?.file),
2507
+ forked: found.forked
2508
+ };
2509
+ }
2510
+ async function uiFolder(root) {
2511
+ const counts = /* @__PURE__ */ new Map();
2512
+ 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);
2513
+ return [...counts].sort((a, b) => b[1] - a[1] || a[0].length - b[0].length)[0]?.[0] ?? null;
2514
+ }
2515
+ function starterRules(id, componentsDir) {
2516
+ const rule2 = (spec) => (0, import_yaml2.stringify)({ schema: "youi.rule/v1", ...spec });
2517
+ return [
2518
+ ["color.no-raw-color", rule2({
2519
+ id: `youi:${id}/rule/color.no-raw-color`,
2520
+ title: "No raw colours",
2521
+ severity: "warn",
2522
+ 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.",
2523
+ rationale: "A literal colour cannot follow the dark scheme or a token change, and it hides from every audit.",
2524
+ 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.",
2525
+ check: { engine: "youi", id: "raw-color", options: {} },
2526
+ files: ["**/*.{ts,tsx,css}"]
2527
+ })],
2528
+ ["tokens.no-arbitrary-values", rule2({
2529
+ id: `youi:${id}/rule/tokens.no-arbitrary-values`,
2530
+ title: "Use token utilities, not arbitrary values",
2531
+ severity: "warn",
2532
+ statement: "When a token holds a value, use its utility instead of a Tailwind arbitrary value.",
2533
+ rationale: "An arbitrary value copies a token's value once; it does not follow when the token changes.",
2534
+ agent: "Prefer token utilities over [arbitrary] values; youiyoux check names the utility.",
2535
+ check: { engine: "youi", id: "arbitrary-value", options: {} },
2536
+ files: ["**/*.tsx"]
2537
+ })],
2538
+ ["components.canonical-import", rule2({
2539
+ id: `youi:${id}/rule/components.canonical-import`,
2540
+ title: "Render the System's component, not a lookalike",
2541
+ severity: "warn",
2542
+ 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.`,
2543
+ rationale: "Two Buttons drift apart: a fix to one never reaches the other.",
2544
+ agent: `Import components only from ${componentsDir}; never define or import another component with one of their names.`,
2545
+ check: { engine: "youi", id: "lookalike-component", options: {} },
2546
+ files: ["**/*.tsx"]
2547
+ })]
2548
+ ];
2549
+ }
2550
+
2551
+ // ../cli/src/extract.ts
2552
+ 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";
2553
+ 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";
2554
+ var json2 = (value) => `${JSON.stringify(value, null, 2)}
2555
+ `;
2556
+ var toPosix2 = (path) => path.split(sep2).join("/");
2557
+ async function isDirectory(path) {
2558
+ return (await stat(path).catch(() => null))?.isDirectory() ?? false;
2559
+ }
2560
+ async function heldInPlace(project2) {
2561
+ const [repo, root] = await Promise.all([realpath4(project2.repo.root), realpath4(project2.root)]);
2562
+ return repo === root && project2.system.config.implementations !== void 0;
2563
+ }
2564
+ function ownedPaths(project2, paths, kept) {
2565
+ const root = project2.repo.root;
2566
+ for (const path of paths) {
2567
+ const own = normalizeRelPath(path);
2568
+ 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`);
2569
+ for (const keep of kept.map((k) => normalizeRelPath(toPosix2(k)))) {
2570
+ 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`);
2571
+ }
2572
+ }
2573
+ }
2574
+ async function extractSystem(project2, dir, options = {}) {
2575
+ 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`);
2576
+ const target = resolve9(dir);
2577
+ if (await isDirectory(target)) {
2578
+ if ((await readdir3(target)).length > 0) throw new Error(`${target} already exists and is not empty: extract into a new folder`);
2579
+ } else if (await stat(target).catch(() => null) !== null) throw new Error(`${target} already exists: extract into a new folder`);
2580
+ const { repo, system } = project2;
2581
+ const config = system.config;
2582
+ const from = (path) => join15(repo.root, path);
2583
+ const components = from(config.components);
2584
+ const implementations = from(implementationsDir(config));
2585
+ const tokensDir = posix4.dirname(config.tokens);
2586
+ const removed = [DESIGN_CONFIG_FILE, config.components, tokensDir, config.rules, ...config.design ? [config.design] : []];
2587
+ ownedPaths(project2, removed, [implementationsDir(config), relative7(project2.root, project2.configFile ?? join15(project2.root, PROJECT_FILE)), project2.config.outputs.dir, relative7(project2.root, target)]);
2588
+ const { implementations: _inPlace, ...rest } = config;
2589
+ const shared = config.support.map((file) => `_shared/${posix4.basename(file)}`);
2590
+ const next2 = { ...rest, tokens: `tokens/${posix4.relative(tokensDir, config.tokens)}`, components: "components", support: shared, rules: "rules", ...config.design ? { design: "design.md" } : {} };
2591
+ const written = [];
2592
+ const temp = await mkdtemp3(join15(dirname8(target), `.${basename3(target)}.`));
2593
+ try {
2594
+ const copy = async (source2, to) => {
2595
+ await mkdir3(dirname8(join15(temp, to)), { recursive: true });
2596
+ await cp(source2, join15(temp, to), { recursive: true });
2597
+ };
2598
+ await copy(components, "components");
2599
+ const placed = /* @__PURE__ */ new Map();
2600
+ for (const { contract, dir: folder } of system.components) {
2601
+ const contractFile = (await readdir3(folder)).find((name) => name.endsWith(".contract.json"));
2602
+ const at = toPosix2(relative7(components, folder));
2603
+ const file = placed.get(contract.source.file) ?? `${at}/${posix4.basename(contract.source.file)}`;
2604
+ if (!placed.has(contract.source.file)) {
2605
+ await copy(join15(implementations, contract.source.file), `components/${file}`);
2606
+ placed.set(contract.source.file, file);
2607
+ }
2608
+ const raw = JSON.parse(await readFile5(join15(folder, contractFile), "utf8"));
2609
+ await writeFileAtomic(join15(temp, "components", at, contractFile), json2({ ...raw, source: { ...raw.source, file } }));
2610
+ }
2611
+ for (const [i, file] of config.support.entries()) await copy(join15(implementations, file), `components/${shared[i]}`);
2612
+ await copy(from(tokensDir), "tokens");
2613
+ if (await isDirectory(from(config.rules))) await copy(from(config.rules), "rules");
2614
+ if (config.design) await copy(from(config.design), "design.md");
2615
+ await writeFileAtomic(join15(temp, DESIGN_CONFIG_FILE), json2({ schema: "youi.config/v1", name: repo.config.name, systems: [next2] }));
2616
+ 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"] : []] }));
2617
+ const leaving = escapingLinks(temp);
2618
+ if (leaving.length > 0) throw new Error(`the System has links that lead outside it (${leaving.slice(0, 3).join(", ")}); nothing was changed`);
2619
+ await loadDesignRepo(temp);
2620
+ for (const entry of await readdir3(temp, { recursive: true })) if (!await isDirectory(join15(temp, entry))) written.push(toPosix2(entry));
2621
+ written.sort();
2622
+ const source = { path: toPosix2(relative7(project2.root, target)) || "." };
2623
+ if (!options.dryRun) {
2624
+ if (await isDirectory(target)) await rmdir2(target);
2625
+ await rename(temp, target);
2626
+ const configFile = project2.configFile ?? join15(project2.root, PROJECT_FILE);
2627
+ const raw = JSON.parse(await readTextOrNull(configFile) ?? "{}");
2628
+ await writeFileAtomic(configFile, json2({ ...raw, source }));
2629
+ for (const path of removed) await rm4(join15(repo.root, path), { recursive: true, force: true });
2630
+ }
2631
+ return { target, system: next2, written, removed, source };
2632
+ } finally {
2633
+ await rm4(temp, { recursive: true, force: true });
2634
+ }
2635
+ }
2636
+
2637
+ // ../cli/src/status.ts
2638
+ import { realpath as realpath5 } from "node:fs/promises";
2639
+ import { dirname as dirname9, join as join16, relative as relative8, resolve as resolve10 } from "node:path";
2640
+ var placeOf = (cwd, root) => {
2641
+ const rel = relative8(cwd, root) || ".";
2642
+ return rel.split(/[\\/]/).filter((part) => part === "..").length > 1 ? resolve10(root) : rel;
2643
+ };
2644
+ var WRITES = /* @__PURE__ */ new Set(["created", "updated", "merged", "adopted", "conflict", "removed"]);
2645
+ function sourceOf(project2) {
2646
+ if (project2.linked) return `your checkout ${project2.linked}`;
2647
+ const source = project2.config.source;
2648
+ if ("package" in source) return `package ${source.package}`;
2649
+ if ("git" in source) return `git ${source.git} at ${source.ref}`;
2650
+ return resolve10(project2.root) === resolve10(project2.repo.root) ? "this repo" : `path ${source.path}`;
2651
+ }
2652
+ function nextStep(s) {
2653
+ if (s.sync.state === "never") return "youiyoux sync: this product has never been synced with its System";
2654
+ if (s.sync.state === "conflict") return "youiyoux sync: resolve the conflicts it reports, then youiyoux sync --adopt records them";
2655
+ if (s.sync.state === "behind") return `youiyoux sync: ${plural(s.sync.changes.length, "file")} would change`;
2656
+ if (s.check.incomplete) return "youiyoux check: some files or rules could not be checked, and it says which";
2657
+ if (s.check.errors > 0) return `youiyoux check: fix ${plural(s.check.errors, "error")}`;
2658
+ if (s.requests.open > 0) return `youiyoux requests: answer ${plural(s.requests.open, "open request")}`;
2659
+ if (s.check.warnings > 0 && !s.check.baseline) return `youiyoux check: ${plural(s.check.warnings, "warning")} to fix, or youiyoux baseline records them as accepted debt`;
2660
+ return "nothing to do";
2661
+ }
2662
+ async function projectStatus(project2, cwd = process.cwd()) {
2663
+ const lock = await readLock(project2.root);
2664
+ const { actions } = await syncProject(project2, { dryRun: true });
2665
+ const changes = actions.filter((a) => WRITES.has(a.action)).map((a) => ({ file: a.file, action: a.action }));
2666
+ const baselineFile2 = baselinePath(project2);
2667
+ const check = await runCheck(project2, { baseline: await readTextOrNull(baselineFile2) === null ? null : baselineFile2 });
2668
+ const comparison = check.baseline?.comparison;
2669
+ const counted = comparison ? [...comparison.new, ...comparison.regressed] : check.violations;
2670
+ const usages = check.scan.report.usages;
2671
+ const { RequestStore } = await import("./src-EUVBYTKY.js");
2672
+ const requests = await new RequestStore(project2.root, project2.config.id).list();
2673
+ const status = {
2674
+ id: project2.config.id,
2675
+ root: placeOf(cwd, project2.root),
2676
+ system: { id: project2.system.config.id, name: project2.system.config.name, version: project2.system.config.version, source: sourceOf(project2), linked: Boolean(project2.linked), commit: project2.designCommit ?? null },
2677
+ lock: lock ? { systemVersion: lock.systemVersion, syncedAt: lock.syncedAt } : null,
2678
+ sync: { state: lock === null ? "never" : actions.some((a) => a.action === "conflict") ? "conflict" : changes.length > 0 ? "behind" : "current", changes },
2679
+ drift: (await verifyLock(project2) ?? []).map((i) => ({ file: i.file, severity: i.severity, message: i.message })),
2680
+ check: {
2681
+ errors: check.errors,
2682
+ warnings: counted.filter((v) => v.severity === "warn").length,
2683
+ baseline: comparison ? { known: comparison.known.length, new: comparison.new.length + comparison.regressed.length, fixed: comparison.fixed.reduce((n, f) => n + f.gone, 0) } : null,
2684
+ incomplete: check.incomplete
2685
+ },
2686
+ components: { system: project2.system.components.length, used: new Set(usages.map((u) => u.element)).size, sites: usages.length },
2687
+ requests: { open: requests.requests.filter((r) => r.status === "open").length, addressed: requests.requests.filter((r) => r.status === "addressed").length, unreadable: requests.unreadable.length }
2688
+ };
2689
+ return { ...status, next: nextStep(status) };
2690
+ }
2691
+ async function statusTargets(cwd, named2) {
2692
+ if (named2) return { roots: [resolve10(named2)], workspace: null };
2693
+ const product2 = findUp(cwd, PROJECT_FILE);
2694
+ if (product2) return { roots: [product2], workspace: null };
2695
+ const at = findUp(cwd, WORKSPACE_FILE);
2696
+ if (at) {
2697
+ const file = join16(at, WORKSPACE_FILE);
2698
+ const workspace = await readDefinition(file, WorkspaceConfig);
2699
+ return { roots: workspace.projects.map((p) => resolve10(at, p)), workspace: file };
2700
+ }
2701
+ const top = findUp(cwd, ".git") ?? null;
2702
+ if (top === null) return { roots: [], workspace: null };
2703
+ const configs = await findFiles(cwd, [`**/${PROJECT_FILE}`]);
2704
+ const roots = [];
2705
+ for (const config of configs) {
2706
+ const dir = resolve10(cwd, dirname9(config));
2707
+ if (await realpath5(findUp(dir, ".git") ?? dir) === await realpath5(top)) roots.push(dir);
2708
+ }
2709
+ return { roots, workspace: null };
2710
+ }
2711
+ async function workspaceStatus(cwd, named2) {
2712
+ const { roots, workspace } = await statusTargets(cwd, named2);
2713
+ const projects = [];
2714
+ for (const root of roots) {
2715
+ try {
2716
+ projects.push(await projectStatus(await loadProject(root), cwd));
2717
+ } catch (error) {
2718
+ const label = placeOf(cwd, root);
2719
+ projects.push({ root: label, error: error instanceof Error ? error.message : String(error), next: await readTextOrNull(join16(root, PROJECT_FILE)) === null ? `youiyoux adopt or youiyoux init --design <dir> in ${label}` : `fix ${join16(label, PROJECT_FILE)}` });
2720
+ }
2721
+ }
2722
+ return { schema: "youi.status/v1", generatedAt: (/* @__PURE__ */ new Date()).toISOString(), workspace: workspace ? relative8(cwd, workspace) || WORKSPACE_FILE : null, projects };
2723
+ }
2724
+ var failed = (p) => "error" in p;
2725
+ function projectLines(s) {
2726
+ const row = (label, text) => ` ${padEnd(label, 11)} ${text}`;
2727
+ const synced = s.lock ? `synced ${s.lock.syncedAt.slice(0, 10)}${s.lock.systemVersion !== s.system.version ? ` at v${s.lock.systemVersion}` : ""}` : "never synced";
2728
+ const sync = { current: style.ok("up to date"), behind: style.warn(`behind: ${plural(s.sync.changes.length, "file")} would change`), conflict: style.error(`conflict: ${plural(s.sync.changes.filter((c) => c.action === "conflict").length, "file")}`), never: style.warn("never synced") }[s.sync.state];
2729
+ const edited = s.drift.filter((d) => d.severity === "warn");
2730
+ const broken = s.drift.filter((d) => d.severity === "error");
2731
+ const drift = s.drift.length === 0 ? style.ok("none") : [broken.length > 0 ? style.error(`${plural(broken.length, "file")} wrong (${broken.slice(0, 3).map((d) => d.file.split("/").at(-1)).join(", ")})`) : "", edited.length > 0 ? style.warn(`${plural(edited.length, "owned file")} edited locally, merged on sync (${edited.slice(0, 3).map((d) => d.file.split("/").at(-1)).join(", ")})`) : ""].filter(Boolean).join("; ");
2732
+ const { check } = s;
2733
+ const counts = `${check.errors > 0 ? style.error(plural(check.errors, "error")) : style.ok("0 errors")} \xB7 ${plural(check.warnings, "warning")}`;
2734
+ const baseline = check.baseline ? style.dim(` beyond the baseline (${check.baseline.known} accepted${check.baseline.fixed > 0 ? `, ${check.baseline.fixed} fixed` : ""})`) : "";
2735
+ return [
2736
+ `${style.bold(s.id)}${s.root === s.id ? "" : ` ${style.dim(`\xB7 ${s.root}`)}`} \xB7 ${s.system.name} v${s.system.version} ${style.dim(`(${s.system.source}${s.system.linked ? ", linked" : ""}) \xB7 ${synced}`)}`,
2737
+ row("sync", sync),
2738
+ row("drift", drift),
2739
+ row("check", `${counts}${baseline}${check.incomplete ? style.warn(" \xB7 incomplete: not a pass") : ""}`),
2740
+ row("components", `${s.components.used} of ${s.components.system} System components used, at ${plural(s.components.sites, "site")}`),
2741
+ row("requests", s.requests.open + s.requests.addressed + s.requests.unreadable === 0 ? style.dim("none") : [s.requests.open > 0 ? style.warn(`${s.requests.open} open`) : "0 open", `${s.requests.addressed} addressed`, ...s.requests.unreadable > 0 ? [style.error(`${s.requests.unreadable} unreadable`)] : []].join(" \xB7 ")),
2742
+ `${style.bold("Next:")} ${s.next}`
2743
+ ];
2744
+ }
2745
+ function renderStatus(report) {
2746
+ if (report.projects.length === 0) return `${style.warn("!")} no product here: no ${PROJECT_FILE} upward, no ${WORKSPACE_FILE}, and none in this repository below here
2747
+ Next: youiyoux adopt makes this product's own UI its design system, or youiyoux init --design <dir> follows one`;
2748
+ const blocks = report.projects.map((p) => failed(p) ? [`${style.bold(p.root)} ${style.error("\u2716")} ${p.error}`, `${style.bold("Next:")} ${p.next}`] : projectLines(p));
2749
+ const head = report.projects.length > 1 ? [`${style.bold("workspace")} ${style.dim(`\xB7 ${plural(report.projects.length, "product")}${report.workspace ? ` from ${report.workspace}` : " in this repository"}`)}`, ""] : [];
2750
+ return [...head, ...blocks.flatMap((b, i) => i === 0 ? b : ["", ...b])].join("\n");
2751
+ }
2752
+ var statusExitCode = (report) => report.projects.some((p) => failed(p) || p.check.incomplete || p.check.errors > 0 || p.sync.state === "conflict") ? 1 : 0;
2753
+
2754
+ // ../cli/src/workspace-hub.ts
2755
+ import { randomBytes as randomBytes2 } from "node:crypto";
2756
+ import { createServer } from "node:http";
2757
+ var KEY_HEADER = "x-youi-session";
2758
+ function send(res, status, body) {
2759
+ res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store" });
2760
+ res.end(JSON.stringify(body));
2761
+ }
2762
+ async function readBody(req) {
2763
+ let text = "";
2764
+ for await (const chunk2 of req) {
2765
+ text += String(chunk2);
2766
+ if (text.length > 4096) throw new Error("request too large");
2767
+ }
2768
+ return text ? JSON.parse(text) : {};
2769
+ }
2770
+ async function startWorkspaceHub(options) {
2771
+ const key2 = randomBytes2(18).toString("base64url");
2772
+ let report = await options.status();
2773
+ let current = options.current;
2774
+ let busy = false;
2775
+ const server = createServer((req, res) => {
2776
+ void (async () => {
2777
+ const path = (req.url ?? "").split("?")[0];
2778
+ if (req.method === "GET" && path === "/api/workspace") return send(res, 200, { report, current });
2779
+ if (req.method !== "POST" || path !== "/api/workspace/open" && path !== "/api/workspace/refresh") {
2780
+ return send(res, 404, { error: "Not in a workspace hub: Rules and Changes are read-only here; youiyoux ui --project <dir> opens the rule editor on one product" });
2781
+ }
2782
+ if (req.headers[KEY_HEADER] !== key2) return send(res, 401, { error: "this tab does not carry this run's key" });
2783
+ if (busy) return send(res, 409, { error: "another product is opening; try again in a moment" });
2784
+ busy = true;
2785
+ try {
2786
+ if (path === "/api/workspace/refresh") {
2787
+ report = await options.status();
2788
+ return send(res, 200, { report, current });
2789
+ }
2790
+ const { index } = await readBody(req);
2791
+ const entry = typeof index === "number" && Number.isInteger(index) ? report.projects[index] : void 0;
2792
+ if (entry === void 0 || options.roots[index] === void 0) return send(res, 400, { error: `no product ${JSON.stringify(index)} in this workspace` });
2793
+ if ("error" in entry) return send(res, 400, { error: `${entry.root} cannot be opened: ${entry.error}`, problems: [entry.next] });
2794
+ const code = await options.open(options.roots[index]);
2795
+ if (code !== 0) return send(res, 500, { error: `the snapshot of ${entry.root} failed (exit ${code}); the hub still shows the product it showed` });
2796
+ current = index;
2797
+ return send(res, 200, { report, current });
2798
+ } catch (error) {
2799
+ return send(res, 400, { error: error instanceof Error ? error.message : String(error) });
2800
+ } finally {
2801
+ busy = false;
2802
+ }
2803
+ })();
2804
+ });
2805
+ await new Promise((done) => server.listen(0, "127.0.0.1", done));
2806
+ const address = server.address();
2807
+ const port = typeof address === "object" && address ? address.port : 0;
2808
+ return {
2809
+ url: `http://127.0.0.1:${port}`,
2810
+ key: key2,
2811
+ close: () => new Promise((done) => server.close(() => done()))
2812
+ };
2813
+ }
2814
+
2220
2815
  // ../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";
2816
+ import { appendFile, mkdir as mkdir4, readFile as readFile6, stat as stat2, rename as rename2, mkdtemp as mkdtemp4, rm as rm5 } from "node:fs/promises";
2817
+ import { dirname as dirname10, join as join17, resolve as resolve11 } from "node:path";
2223
2818
  var SOURCES_FILE = "index-sources.json";
2224
2819
  var SCANS_LOG = "scans.jsonl";
2225
2820
  var EMPTY = { discover: null, inventories: [], projects: [] };
2226
2821
  async function readIndexSources(home) {
2227
2822
  try {
2228
- const raw = JSON.parse(await readFile4(join14(home, SOURCES_FILE), "utf8"));
2823
+ const raw = JSON.parse(await readFile6(join17(home, SOURCES_FILE), "utf8"));
2229
2824
  return { discover: raw.discover ?? null, inventories: raw.inventories ?? [], projects: raw.projects ?? [] };
2230
2825
  } catch {
2231
2826
  return EMPTY;
@@ -2238,14 +2833,14 @@ async function noteSource(home, change) {
2238
2833
  inventories: change.inventory && !sources.inventories.includes(change.inventory) ? [...sources.inventories, change.inventory] : sources.inventories,
2239
2834
  projects: change.project ? [...sources.projects.filter((p) => p.root !== change.project.root), change.project] : sources.projects
2240
2835
  };
2241
- await writeFileAtomic(join14(home, SOURCES_FILE), `${JSON.stringify(next2, null, 2)}
2836
+ await writeFileAtomic(join17(home, SOURCES_FILE), `${JSON.stringify(next2, null, 2)}
2242
2837
  `);
2243
2838
  }
2244
2839
  async function recordInventory(store, home, report, file) {
2245
- const run7 = inventoryRun(report, resolve8(file));
2840
+ const run7 = inventoryRun(report, resolve11(file));
2246
2841
  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 })}
2842
+ await mkdir4(home, { recursive: true });
2843
+ await appendFile(join17(home, SCANS_LOG), `${JSON.stringify({ schema: "youi.scan-log/v1", ...logged })}
2249
2844
  `);
2250
2845
  await store.recordInventory(run7);
2251
2846
  await noteSource(home, { inventory: run7.source });
@@ -2256,7 +2851,7 @@ async function recordProject(store, project2, usage) {
2256
2851
  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
2852
  }
2258
2853
  async function readLog(home) {
2259
- const text = await readFile4(join14(home, SCANS_LOG), "utf8").catch(() => "");
2854
+ const text = await readFile6(join17(home, SCANS_LOG), "utf8").catch(() => "");
2260
2855
  return text.split("\n").flatMap((line) => {
2261
2856
  if (!line.trim()) return [];
2262
2857
  try {
@@ -2271,9 +2866,9 @@ async function rebuildIndex(path, options = {}) {
2271
2866
  const started = performance.now();
2272
2867
  const env = options.env ?? process.env;
2273
2868
  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;
2869
+ await mkdir4(dirname10(path), { recursive: true });
2870
+ const scratch = !env.DATABASE_URL ? await mkdtemp4(`${path}.rebuild-`) : null;
2871
+ const target = scratch ? join17(scratch, "index.db") : path;
2277
2872
  const store = scratch ? await openSqlite(target) : await sharedStore(path, env);
2278
2873
  try {
2279
2874
  if (env.DATABASE_URL) await store.reset();
@@ -2281,7 +2876,7 @@ async function rebuildIndex(path, options = {}) {
2281
2876
  const sources = await readIndexSources(home);
2282
2877
  const cache = options.discoverCache ?? sources.discover;
2283
2878
  let discover = { cache, chosen: 0, all: 0 };
2284
- if (cache && await stat(join14(cache, "index.json")).then(() => true, () => false)) {
2879
+ if (cache && await stat2(join17(cache, "index.json")).then(() => true, () => false)) {
2285
2880
  const loaded = await ingestCatalog(store, cache);
2286
2881
  discover = { cache, chosen: loaded.chosen, all: loaded.all };
2287
2882
  }
@@ -2298,53 +2893,53 @@ async function rebuildIndex(path, options = {}) {
2298
2893
  await store.recordInventory(inventoryRun(parsed.data, file));
2299
2894
  replayed++;
2300
2895
  }
2301
- const failed = [];
2896
+ const failed2 = [];
2302
2897
  let projects = 0;
2303
2898
  const gone = [];
2304
2899
  for (const source of sources.projects) {
2305
- if (!await stat(source.root).then((s) => s.isDirectory(), () => false)) {
2900
+ if (!await stat2(source.root).then((s) => s.isDirectory(), () => false)) {
2306
2901
  gone.push(source.root);
2307
- failed.push({ root: source.root, reason: "folder not found (moved or deleted), skipped" });
2902
+ failed2.push({ root: source.root, reason: "folder not found (moved or deleted), skipped" });
2308
2903
  continue;
2309
2904
  }
2310
2905
  try {
2311
2906
  const project2 = await loadProject(source.root, source.options);
2312
- const usage = ScanReport.safeParse(await readJson(source.usage ?? join14(project2.root, ".youi/usage.json")));
2907
+ const usage = ScanReport.safeParse(await readJson(source.usage ?? join17(project2.root, ".youi/usage.json")));
2313
2908
  await recordProject(store, project2, usage.success ? usage.data : null);
2314
2909
  projects++;
2315
2910
  } catch (error) {
2316
- failed.push({ root: source.root, reason: error instanceof Error ? error.message.split("\n")[0] : String(error) });
2911
+ failed2.push({ root: source.root, reason: error instanceof Error ? error.message.split("\n")[0] : String(error) });
2317
2912
  }
2318
2913
  }
2319
- const broken = failed.filter((f) => !gone.includes(f.root));
2914
+ const broken = failed2.filter((f) => !gone.includes(f.root));
2320
2915
  if (broken.length) throw new Error(`Index rebuild failed: ${broken.map((f) => `${f.root}: ${f.reason}`).join("; ")}`);
2321
2916
  await linkCandidates(store);
2322
2917
  const tables = (await store.stats()).tables;
2323
2918
  if (scratch) {
2324
2919
  await store.close();
2325
2920
  checkpointSqlite(target);
2326
- if (await stat(path).then(() => true, () => false)) checkpointSqlite(path);
2921
+ if (await stat2(path).then(() => true, () => false)) checkpointSqlite(path);
2327
2922
  const moved = [];
2328
2923
  try {
2329
2924
  for (const suffix of ["-wal", "-shm"]) {
2330
2925
  try {
2331
- await rename(`${path}${suffix}`, `${target}.old${suffix}`);
2926
+ await rename2(`${path}${suffix}`, `${target}.old${suffix}`);
2332
2927
  moved.push(suffix);
2333
2928
  } catch (error) {
2334
2929
  if (error.code !== "ENOENT") throw error;
2335
2930
  }
2336
2931
  }
2337
- await rename(target, path);
2932
+ await rename2(target, path);
2338
2933
  } catch (error) {
2339
- for (const suffix of moved) await rename(`${target}.old${suffix}`, `${path}${suffix}`);
2934
+ for (const suffix of moved) await rename2(`${target}.old${suffix}`, `${path}${suffix}`);
2340
2935
  throw error;
2341
2936
  }
2342
2937
  }
2343
- return { location: scratch ? path : store.location, discover, scans: log.length, inventories: { replayed, missing }, projects: { replayed: projects, failed }, tables, durationMs: Math.round(performance.now() - started) };
2938
+ return { location: scratch ? path : store.location, discover, scans: log.length, inventories: { replayed, missing }, projects: { replayed: projects, failed: failed2 }, tables, durationMs: Math.round(performance.now() - started) };
2344
2939
  } finally {
2345
2940
  if (scratch) {
2346
2941
  await store.close();
2347
- await rm4(scratch, { recursive: true, force: true });
2942
+ await rm5(scratch, { recursive: true, force: true });
2348
2943
  }
2349
2944
  }
2350
2945
  }
@@ -2526,9 +3121,9 @@ function renderAdoptionRequest(request) {
2526
3121
  }
2527
3122
 
2528
3123
  // ../cli/src/report/run.ts
2529
- 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";
3124
+ import { randomBytes as randomBytes3 } from "node:crypto";
3125
+ import { mkdir as mkdir5, rename as rename3, stat as stat3, writeFile as writeFile2 } from "node:fs/promises";
3126
+ import { join as join24, relative as relative10, resolve as resolve14 } from "node:path";
2532
3127
 
2533
3128
  // ../cli/src/report/card.ts
2534
3129
  import { deflateSync } from "node:zlib";
@@ -2763,8 +3358,8 @@ function drawCard(input) {
2763
3358
  }
2764
3359
 
2765
3360
  // ../cli/src/report/model.ts
2766
- import { readFile as readFile6 } from "node:fs/promises";
2767
- import { join as join18 } from "node:path";
3361
+ import { readFile as readFile8 } from "node:fs/promises";
3362
+ import { join as join21 } from "node:path";
2768
3363
 
2769
3364
  // ../cli/src/report/filters.ts
2770
3365
  var SHOWCASE_GLOBS = ["**/{__stories__,.storybook,storybook,storybook-static,example,examples,demo,demos,playground,showcase,showcases}/**", "**/*.stories.*", "**/*.story.*"];
@@ -2891,11 +3486,11 @@ function publicLook(caption) {
2891
3486
 
2892
3487
  // ../cli/src/report/groups.ts
2893
3488
  import { existsSync as existsSync3 } from "node:fs";
2894
- import { join as join16 } from "node:path";
3489
+ import { join as join19 } from "node:path";
2895
3490
 
2896
3491
  // ../cli/src/report/imports.ts
2897
3492
  import { realpathSync } from "node:fs";
2898
- import { dirname as dirname8, join as join15, relative as relative6, resolve as resolve9 } from "node:path";
3493
+ import { dirname as dirname11, join as join18, relative as relative9, resolve as resolve12 } from "node:path";
2899
3494
  import ts from "typescript";
2900
3495
  var resolvers = /* @__PURE__ */ new Map();
2901
3496
  function resolverOf(root) {
@@ -2903,7 +3498,7 @@ function resolverOf(root) {
2903
3498
  if (!found) {
2904
3499
  const config = ts.findConfigFile(root, ts.sys.fileExists, "tsconfig.json") ?? ts.findConfigFile(root, ts.sys.fileExists, "jsconfig.json");
2905
3500
  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 : {};
3501
+ const options = config && read && !read.error ? ts.parseJsonConfigFileContent(read.config, ts.sys, dirname11(config)).options : {};
2907
3502
  found = { options, cache: ts.createModuleResolutionCache(root, (name) => name, options) };
2908
3503
  resolvers.set(root, found);
2909
3504
  }
@@ -2914,13 +3509,13 @@ function clearImportCache() {
2914
3509
  }
2915
3510
  async function verifiedImport(root, keeper, from, suggested) {
2916
3511
  const { options, cache } = resolverOf(root);
2917
- let local = relative6(dirname8(join15(root, from)), join15(root, keeper)).replaceAll("\\", "/").replace(/\.(tsx?|jsx?|mjs)$/, "");
3512
+ let local = relative9(dirname11(join18(root, from)), join18(root, keeper)).replaceAll("\\", "/").replace(/\.(tsx?|jsx?|mjs)$/, "");
2918
3513
  if (!local.startsWith(".")) local = `./${local}`;
2919
3514
  for (const specifier of /* @__PURE__ */ new Set([suggested, local])) {
2920
- const found = ts.resolveModuleName(specifier, resolve9(root, from), options, ts.sys, cache).resolvedModule?.resolvedFileName;
3515
+ const found = ts.resolveModuleName(specifier, resolve12(root, from), options, ts.sys, cache).resolvedModule?.resolvedFileName;
2921
3516
  if (found) {
2922
3517
  try {
2923
- if (realpathSync(found) === realpathSync(join15(root, keeper))) return specifier;
3518
+ if (realpathSync(found) === realpathSync(join18(root, keeper))) return specifier;
2924
3519
  } catch {
2925
3520
  }
2926
3521
  }
@@ -3033,7 +3628,7 @@ var NEAR_IDENTICAL = 0.5;
3033
3628
  var MIN_STRUCTURE = 6;
3034
3629
  var GENERIC_IDENTICAL = 0.85;
3035
3630
  var repoPath = (dir, file) => dir === "." ? file : `${dir}/${file}`;
3036
- var kebab = (name) => name.replace(/[a-z0-9](?=[A-Z])/g, "$&-").toLowerCase();
3631
+ var kebab2 = (name) => name.replace(/[a-z0-9](?=[A-Z])/g, "$&-").toLowerCase();
3037
3632
  function shapeWords(shape) {
3038
3633
  const { height, paddingX, radius, fontSize } = shape;
3039
3634
  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 +3729,7 @@ async function nearDuplicateGroups(input) {
3134
3729
  const name = EXTRACT_NAMES[kind];
3135
3730
  const fileIn = (f, dir2) => {
3136
3731
  for (let n = 1; ; n++) {
3137
- const file = `${dir2 ? `${dir2}/` : ""}${kebab(name)}${n === 1 ? "" : `-${n}`}.tsx`;
3732
+ const file = `${dir2 ? `${dir2}/` : ""}${kebab2(name)}${n === 1 ? "" : `-${n}`}.tsx`;
3138
3733
  if (![...defs.values()].some((d) => d.facts === f && d.def.file === file)) return file;
3139
3734
  }
3140
3735
  };
@@ -3328,22 +3923,22 @@ function factsOf(project2, id, modules, eligible, workspaceNames, dependencies,
3328
3923
  if (dirs.has(file.slice(0, at))) break;
3329
3924
  dirs.add(file.slice(0, at));
3330
3925
  }
3331
- const roots = [...dirs].filter((dir) => existsSync3(join16(project2.root, dir, "package.json"))).sort((a, b) => b.length - a.length);
3926
+ const roots = [...dirs].filter((dir) => existsSync3(join19(project2.root, dir, "package.json"))).sort((a, b) => b.length - a.length);
3332
3927
  const nested = (file) => roots.find((dir) => file.startsWith(`${dir}/`)) ?? "";
3333
3928
  return { project: project2, id, dependencies, packageImports, jsxCounts, eligible, twins, dirs, importer, packageOf: nested };
3334
3929
  }
3335
3930
 
3336
3931
  // ../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";
3932
+ var import_yaml3 = __toESM(require_dist(), 1);
3933
+ import { readFile as readFile7 } from "node:fs/promises";
3934
+ import { basename as basename4, join as join20, resolve as resolve13 } from "node:path";
3340
3935
  var UI_FILES = ["**/*.{tsx,jsx,astro,vue,svelte}"];
3341
3936
  var strings = (value) => Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
3342
3937
  async function workspaceGlobs(root, pkg) {
3343
- const pnpm = await readFile5(join17(root, "pnpm-workspace.yaml"), "utf8").catch(() => null);
3938
+ const pnpm = await readFile7(join20(root, "pnpm-workspace.yaml"), "utf8").catch(() => null);
3344
3939
  if (pnpm !== null) {
3345
3940
  try {
3346
- const globs2 = strings((0, import_yaml2.parse)(pnpm)?.packages);
3941
+ const globs2 = strings((0, import_yaml3.parse)(pnpm)?.packages);
3347
3942
  if (globs2.length > 0) return { globs: globs2, source: "pnpm-workspace.yaml" };
3348
3943
  } catch {
3349
3944
  }
@@ -3354,9 +3949,9 @@ async function workspaceGlobs(root, pkg) {
3354
3949
  }
3355
3950
  var hasUi = async (dir) => (await findFiles(dir, UI_FILES, DEFAULT_EXCLUDE)).length > 0;
3356
3951
  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);
3952
+ const root = resolve13(dir);
3953
+ const pkg = await readJson(join20(root, "package.json"));
3954
+ const name = typeof pkg?.name === "string" && pkg.name.trim() ? pkg.name.trim() : basename4(root);
3360
3955
  const single = { dir: ".", root, name: typeof pkg?.name === "string" ? pkg.name : null };
3361
3956
  const workspace = await workspaceGlobs(root, pkg);
3362
3957
  if (!workspace) return { root, name, layout: "one project", projects: [single], skipped: [] };
@@ -3366,12 +3961,12 @@ async function findProjects(dir) {
3366
3961
  const projects = [];
3367
3962
  const skipped = [];
3368
3963
  for (const member of members) {
3369
- const memberRoot = join17(root, member);
3964
+ const memberRoot = join20(root, member);
3370
3965
  if (!await hasUi(memberRoot)) {
3371
3966
  skipped.push(member);
3372
3967
  continue;
3373
3968
  }
3374
- const memberPkg = await readJson(join17(memberRoot, "package.json"));
3969
+ const memberPkg = await readJson(join20(memberRoot, "package.json"));
3375
3970
  projects.push({ dir: member, root: memberRoot, name: typeof memberPkg?.name === "string" ? memberPkg.name : null });
3376
3971
  }
3377
3972
  if (projects.length === 0) return { root, name, layout: `one project (${workspace.source} names no package with UI files)`, projects: [single], skipped };
@@ -3398,7 +3993,7 @@ var Lines = class {
3398
3993
  if (!this.enabled) return { at };
3399
3994
  let lines = this.files.get(file);
3400
3995
  if (!lines) {
3401
- lines = (await readFile6(join18(this.root, file), "utf8").catch(() => "")).split("\n");
3996
+ lines = (await readFile8(join21(this.root, file), "utf8").catch(() => "")).split("\n");
3402
3997
  this.files.set(file, lines);
3403
3998
  }
3404
3999
  const code = lines[line - 1]?.trim();
@@ -3413,15 +4008,15 @@ function hardcodedValue(finding3) {
3413
4008
  const value = bracket ?? (declaration ? `${declaration[2]} (${declaration[1]})` : finding3.text);
3414
4009
  return { word: word === "type" ? "text size" : word, value, tokens };
3415
4010
  }
3416
- async function modulesOf(project2, target, failed) {
4011
+ async function modulesOf(project2, target, failed2) {
3417
4012
  const modules = [];
3418
4013
  for (const file of target.files) {
3419
4014
  if (isShowcase(file)) continue;
3420
4015
  try {
3421
- const module = await parseModule(file, await readFile6(join18(project2.root, file), "utf8"));
4016
+ const module = await parseModule(file, await readFile8(join21(project2.root, file), "utf8"));
3422
4017
  if (module) modules.push(module);
3423
4018
  } catch (error) {
3424
- failed.push(`${repoPath(project2.dir, file)}: ${errorText(error)}`);
4019
+ failed2.push(`${repoPath(project2.dir, file)}: ${errorText(error)}`);
3425
4020
  }
3426
4021
  }
3427
4022
  return modules;
@@ -3442,7 +4037,7 @@ async function grouped(project2, found, lines, describe) {
3442
4037
  return findings2.sort((a, b) => b.count - a.count || a.title.localeCompare(b.title));
3443
4038
  }
3444
4039
  async function analyseProject(project2, index, inventory, inventoryError, workspaceNames, lines) {
3445
- const failed = [];
4040
+ const failed2 = [];
3446
4041
  const incomplete = [];
3447
4042
  const notes = [];
3448
4043
  const projectId = inventory?.projects[index]?.id;
@@ -3450,7 +4045,7 @@ async function analyseProject(project2, index, inventory, inventoryError, worksp
3450
4045
  let target = null;
3451
4046
  try {
3452
4047
  target = await loadInventoryTarget(project2.root);
3453
- modules = await modulesOf(project2, target, failed);
4048
+ modules = await modulesOf(project2, target, failed2);
3454
4049
  } catch (error) {
3455
4050
  incomplete.push(`The code could not be read (${errorText(error)})`);
3456
4051
  }
@@ -3493,7 +4088,7 @@ async function analyseProject(project2, index, inventory, inventoryError, worksp
3493
4088
  const components = [...byComponent.values()].map((c) => ({ name: c.name, file: c.file, uses: c.roles.length, role: [...new Set(c.roles)].sort((a, b) => c.roles.filter((r) => r === b).length - c.roles.filter((r) => r === a).length)[0] })).sort((a, b) => b.uses - a.uses || a.name.localeCompare(b.name)).slice(0, TOP_COMPONENTS);
3494
4089
  const inventoryProject = inventory?.projects[index];
3495
4090
  if (inventoryError) incomplete.push(`The component inventory could not be built (${inventoryError}); run ${NPX} inventory --project ${project2.dir} to see why`);
3496
- for (const f of inventoryProject?.failed ?? []) failed.push(`${repoPath(project2.dir, f.file)}: ${f.reason}`);
4091
+ for (const f of inventoryProject?.failed ?? []) failed2.push(`${repoPath(project2.dir, f.file)}: ${f.reason}`);
3497
4092
  let hardcoded = [];
3498
4093
  let offLibrary = [];
3499
4094
  const everyLine = () => true;
@@ -3522,7 +4117,7 @@ async function analyseProject(project2, index, inventory, inventoryError, worksp
3522
4117
  incomplete.push(`Library choices were not compared (${errorText(error)}); run ${NPX} detect ${project2.dir} to see why`);
3523
4118
  }
3524
4119
  }
3525
- const pkg = await readFile6(join18(project2.root, "package.json"), "utf8").then((text) => JSON.parse(text), () => null);
4120
+ const pkg = await readFile8(join21(project2.root, "package.json"), "utf8").then((text) => JSON.parse(text), () => null);
3526
4121
  const dependencies = new Set(["dependencies", "devDependencies", "peerDependencies"].flatMap((key2) => Object.keys(pkg?.[key2] ?? {})));
3527
4122
  const importer = changeProject ? await importOf(changeProject) : () => "";
3528
4123
  const facts = factsOf(project2, projectId ?? null, modules, eligible, workspaceNames, dependencies, target?.files ?? [], importer);
@@ -3530,7 +4125,7 @@ async function analyseProject(project2, index, inventory, inventoryError, worksp
3530
4125
  dir: project2.dir,
3531
4126
  name: project2.name,
3532
4127
  files: inventoryProject?.files ?? modules.length,
3533
- failed,
4128
+ failed: failed2,
3534
4129
  libraries,
3535
4130
  unused,
3536
4131
  definitions: inventory ? inventory.definitions.filter((d) => d.project === projectId && !isShowcase(d.file)).length : 0,
@@ -3966,36 +4561,36 @@ ${model.projects.map((p) => projectSection(p, many, looks)).join("\n")}
3966
4561
  import { spawn as spawn2 } from "node:child_process";
3967
4562
  import { existsSync as existsSync4, readFileSync as readFileSync2 } from "node:fs";
3968
4563
  import { homedir as homedir2 } from "node:os";
3969
- import { dirname as dirname9, join as join19 } from "node:path";
4564
+ import { dirname as dirname12, join as join22 } from "node:path";
3970
4565
  var LOOKS_PER_PROJECT = 4;
3971
4566
  var DOWNLOAD_SIZE = "~100 MB";
3972
4567
  var DEPENDENCIES = "Install the repo's dependencies to see rendered looks";
3973
4568
  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());
4569
+ var playwrightDir = () => dirname12(playwrightCli());
3975
4570
  function browsersPath(env) {
3976
4571
  const set = env.PLAYWRIGHT_BROWSERS_PATH;
3977
- if (set === "0") return join19(playwrightDir(), ".local-browsers");
4572
+ if (set === "0") return join22(playwrightDir(), ".local-browsers");
3978
4573
  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");
4574
+ if (process.platform === "darwin") return join22(homedir2(), "Library/Caches/ms-playwright");
4575
+ if (process.platform === "win32") return join22(env.LOCALAPPDATA ?? join22(homedir2(), "AppData/Local"), "ms-playwright");
4576
+ return join22(env.XDG_CACHE_HOME ?? join22(homedir2(), ".cache"), "ms-playwright");
3982
4577
  }
3983
4578
  function chromiumStatus(env = process.env) {
3984
4579
  if (env.YOUI_CHROMIUM) return { available: existsSync4(env.YOUI_CHROMIUM), where: env.YOUI_CHROMIUM };
3985
4580
  try {
3986
- const browsers = JSON.parse(readFileSync2(join19(playwrightDir(), "browsers.json"), "utf8"));
4581
+ const browsers = JSON.parse(readFileSync2(join22(playwrightDir(), "browsers.json"), "utf8"));
3987
4582
  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 };
4583
+ const dir = join22(browsersPath(env), `chromium_headless_shell-${revision2}`);
4584
+ return { available: revision2 !== void 0 && existsSync4(join22(dir, "INSTALLATION_COMPLETE")), where: dir };
3990
4585
  } catch {
3991
4586
  return { available: false, where: "playwright-core is not installed" };
3992
4587
  }
3993
4588
  }
3994
4589
  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));
4590
+ return new Promise((resolve16) => {
4591
+ const child = spawn2(process.execPath, [join22(playwrightDir(), "cli.js"), "install", "chromium-headless-shell"], { stdio: ["ignore", 2, 2] });
4592
+ child.on("error", () => resolve16(false));
4593
+ child.on("close", (code) => resolve16(code === 0));
3999
4594
  });
4000
4595
  }
4001
4596
  var dataUri = (file) => file && existsSync4(file) ? `data:image/png;base64,${readFileSync2(file).toString("base64")}` : null;
@@ -4009,24 +4604,24 @@ async function addLooks(model, repoRoot, options = {}) {
4009
4604
  const where2 = section.dir === "." ? "this repo" : section.dir;
4010
4605
  try {
4011
4606
  const installed = (name) => {
4012
- let dir = join19(repoRoot, section.dir);
4607
+ let dir = join22(repoRoot, section.dir);
4013
4608
  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);
4609
+ if (existsSync4(join22(dir, "node_modules", name, "package.json"))) return true;
4610
+ if (dir === repoRoot || dirname12(dir) === dir) return false;
4611
+ dir = dirname12(dir);
4017
4612
  }
4018
4613
  };
4019
4614
  if (!installed("react") || !installed("react-dom")) {
4020
4615
  missing.push(where2);
4021
4616
  continue;
4022
4617
  }
4023
- const projectRoot = join19(repoRoot, section.dir);
4618
+ const projectRoot = join22(repoRoot, section.dir);
4024
4619
  const target = await loadInventoryTarget(projectRoot);
4025
4620
  if (findStylesheets(projectRoot, target.files, { tsconfig: target.tsconfig, override: target.specimenCss, templates: target.templates }).found.length === 0) {
4026
4621
  missing.push(where2);
4027
4622
  continue;
4028
4623
  }
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 } : {} });
4624
+ const report = await renderShots(join22(repoRoot, section.dir), names, { download: false, base: "HEAD", ...options.executablePath ? { executablePath: options.executablePath } : {}, ...options.cache ? { cache: options.cache } : {}, ...options.log ? { log: options.log } : {} });
4030
4625
  const unavailable = report.notes.some((note) => UNDRAWABLE.test(note));
4031
4626
  if (unavailable) {
4032
4627
  missing.push(where2);
@@ -4096,9 +4691,9 @@ function renderMarkdown(model, reportFile) {
4096
4691
 
4097
4692
  // ../cli/src/report/safe.ts
4098
4693
  import { realpathSync as realpathSync2 } from "node:fs";
4099
- import { readFile as readFile7 } from "node:fs/promises";
4694
+ import { readFile as readFile9 } from "node:fs/promises";
4100
4695
  import { homedir as homedir3, userInfo as userInfo2 } from "node:os";
4101
- import { join as join20 } from "node:path";
4696
+ import { join as join23 } from "node:path";
4102
4697
  var REDACTED = "[redacted]";
4103
4698
  var OUTSIDE = "[path outside the repo]";
4104
4699
  var MIN_VALUE = 6;
@@ -4110,7 +4705,7 @@ async function dotenvValues(root) {
4110
4705
  const files = await findFiles(root, [".env", ".env.*", "**/.env", "**/.env.*"]).catch(() => []);
4111
4706
  const values = [];
4112
4707
  for (const file of files) {
4113
- const text = await readFile7(join20(root, file), "utf8").catch(() => "");
4708
+ const text = await readFile9(join23(root, file), "utf8").catch(() => "");
4114
4709
  for (const line of text.split(/\r?\n/)) {
4115
4710
  const match = /^\s*(?:export\s+)?[A-Za-z_][A-Za-z0-9_.-]*\s*=\s*(.*)$/.exec(line);
4116
4711
  if (!match) continue;
@@ -4237,12 +4832,12 @@ async function askTerminal(question) {
4237
4832
  }
4238
4833
  }
4239
4834
  async function writeBinaryAtomic(file, bytes) {
4240
- const temp = `${file}.${process.pid}.${randomBytes2(6).toString("hex")}.tmp`;
4835
+ const temp = `${file}.${process.pid}.${randomBytes3(6).toString("hex")}.tmp`;
4241
4836
  await writeFile2(temp, bytes);
4242
- await rename2(temp, file);
4837
+ await rename3(temp, file);
4243
4838
  }
4244
4839
  var shown = (file) => {
4245
- const rel = relative7(process.cwd(), file);
4840
+ const rel = relative10(process.cwd(), file);
4246
4841
  return rel && !rel.startsWith("..") ? rel : file;
4247
4842
  };
4248
4843
  async function runReport(options = {}, io = {}) {
@@ -4251,8 +4846,8 @@ async function runReport(options = {}, io = {}) {
4251
4846
  const error = io.error ?? ((text) => void process.stderr.write(`${text}
4252
4847
  `));
4253
4848
  const env = io.env ?? process.env;
4254
- const root = resolve11(options.path ?? process.cwd());
4255
- if (!(await stat2(root).catch(() => null))?.isDirectory()) {
4849
+ const root = resolve14(options.path ?? process.cwd());
4850
+ if (!(await stat3(root).catch(() => null))?.isDirectory()) {
4256
4851
  error(`\u2716 ${options.path ?? root} is not a directory: run ${NPX} in a repo, or pass its path (${NPX} path/to/repo)`);
4257
4852
  return { code: 2, files: null, model: null };
4258
4853
  }
@@ -4281,9 +4876,9 @@ async function runReport(options = {}, io = {}) {
4281
4876
  model.durationMs += Math.round(performance.now() - started);
4282
4877
  const redactor = await Redactor.forRepo(root, { includeCode: options.includeCode === true, env });
4283
4878
  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) };
4879
+ const outDir = resolve14(options.out ?? root);
4880
+ await mkdir5(outDir, { recursive: true });
4881
+ const files = { report: join24(outDir, REPORT_FILE), card: join24(outDir, CARD_FILE), summary: join24(outDir, SUMMARY_FILE) };
4287
4882
  const t = clean.totals;
4288
4883
  const card = {
4289
4884
  repo: redactor.scrub(clean.repo),
@@ -4367,6 +4962,23 @@ ${style.bold("Anywhere")} ${style.dim("no design system needed")}
4367
4962
  an unchanged component comes from the cache (YOUI_SPECIMEN_CACHE)
4368
4963
 
4369
4964
  ${style.bold("In a product repo")}
4965
+ status [--json] where the product stands with its design system, in a few lines: the System
4966
+ and its source, what a sync would change (a dry run, offline), local drift,
4967
+ errors and warnings (beyond the baseline), components used, open requests,
4968
+ and the one thing to do next. In a workspace (youi.workspace.json, or a repo
4969
+ holding several products), one block per product
4970
+ adopt [--components dir] [--css file] [--system id] [--fork] [--dry-run]
4971
+ make this product's own UI its design system, kept in this repo: a draft
4972
+ contract per component in the components folder (the files stay where they
4973
+ are), the stylesheet's :root and dark variables as tokens (moved: the
4974
+ stylesheet imports the generated tokens.css, same names and values), three
4975
+ starter rules as warnings, then init and sync; --dry-run writes nothing.
4976
+ Variables youiyoux generated from another System are left to it (adopt says
4977
+ which); --fork takes them as the start of this product's own System
4978
+ system extract <dir> [--package name] [--dry-run]
4979
+ move the System adopt keeps here into a design repo of its own at <dir>
4980
+ (contracts with their implementations, tokens, rules), for a second app to
4981
+ follow; this product then follows it by path, and sync vendors its components
4370
4982
  init --design <dir|package> write youi.project.json pointing at a design repo checkout, or at
4371
4983
  the npm package that holds it (named, so no machine's folder), then sync;
4372
4984
  a git repository goes in its source: { "git": "github:acme/design", "ref": "v3" }
@@ -4458,7 +5070,8 @@ ${style.bold("Detect")} ${style.dim("which UI libraries a repo or website uses
4458
5070
 
4459
5071
  ${style.bold("The hub")}
4460
5072
  ui [--design dir] [--project dir] [--config file] [--root dir] [--port n] [--watch] [--state dir] [--inventory file] [--detect file]
4461
- open the web hub on a design system (default: the demo system);
5073
+ open the web hub on a design system (default: the demo system; beside a
5074
+ youi.workspace.json, its products, with Work > Workspace to switch);
4462
5075
  --project adds that product's usage map (read-only scan) and the
4463
5076
  rule editor (System > Rules, Work > Changes): drafts, impact preview,
4464
5077
  approval, a review branch in the design repo (drafts kept in --state);
@@ -4528,6 +5141,8 @@ var OPTIONS = {
4528
5141
  dark: { type: "string" },
4529
5142
  state: { type: "string" },
4530
5143
  inventory: { type: "string" },
5144
+ package: { type: "string" },
5145
+ fork: { type: "boolean", default: false },
4531
5146
  cache: { type: "string" },
4532
5147
  role: { type: "string" },
4533
5148
  license: { type: "string" },
@@ -4551,7 +5166,7 @@ var UsageError = class extends Error {
4551
5166
  var run6 = promisify5(execFile5);
4552
5167
  var print = (text) => void process.stdout.write(`${text}
4553
5168
  `);
4554
- var json = (value) => print(JSON.stringify(value, null, 2));
5169
+ var json3 = (value) => print(JSON.stringify(value, null, 2));
4555
5170
  function loadOptions(values) {
4556
5171
  return { ...values.config ? { configPath: values.config } : {}, ...values.design ? { designRoot: values.design } : {}, ...values.root !== void 0 ? { productRoot: values.root || "." } : {} };
4557
5172
  }
@@ -4559,17 +5174,17 @@ async function project(values) {
4559
5174
  return loadProject(values.project ?? process.cwd(), loadOptions(values));
4560
5175
  }
4561
5176
  async function readJsonOrFile(value) {
4562
- const text = value.trim().startsWith("{") ? value : await readFile8(value, "utf8");
5177
+ const text = value.trim().startsWith("{") ? value : await readFile10(value, "utf8");
4563
5178
  return JSON.parse(text);
4564
5179
  }
4565
5180
  async function cmdScan(values) {
4566
5181
  const p = await project(values);
4567
5182
  const { report } = await scanProject(p);
4568
- const out = values.out ?? join22(p.root, ".youi/usage.json");
4569
- await writeFileAtomic(resolve12(out), `${JSON.stringify(report)}
5183
+ const out = values.out ?? join25(p.root, ".youi/usage.json");
5184
+ await writeFileAtomic(resolve15(out), `${JSON.stringify(report)}
4570
5185
  `);
4571
- const recorded = await recordUsage(values, p, report, resolve12(out));
4572
- if (values.json) json(report);
5186
+ const recorded = await recordUsage(values, p, report, resolve15(out));
5187
+ if (values.json) json3(report);
4573
5188
  else print([renderScan(report, out), ...recorded ? [recorded] : []].join("\n"));
4574
5189
  return 0;
4575
5190
  }
@@ -4577,7 +5192,7 @@ async function recordUsage(values, p, report, usage) {
4577
5192
  const { path, home } = indexOf(values);
4578
5193
  try {
4579
5194
  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 } });
5195
+ await noteSource(home, { project: { root: resolve15(values.project ?? process.cwd()), options: Object.fromEntries(Object.entries(loadOptions(values)).map(([key2, value]) => [key2, resolve15(value)])), usage } });
4581
5196
  return null;
4582
5197
  } catch (error) {
4583
5198
  return notRecorded(error);
@@ -4592,19 +5207,19 @@ function indexOf(values) {
4592
5207
  }
4593
5208
  async function cmdGaps(values) {
4594
5209
  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);
5210
+ const inventory = values.inventory ? InventoryReport.parse(JSON.parse(await readFile10(resolve15(values.inventory), "utf8"))) : await scanInventory(values, dirs);
5211
+ const cwd = values.inventory ? dirname13(resolve15(values.inventory)) : process.cwd();
5212
+ const project2 = await loadProject(resolve15(dirs[0]), loadOptions(values)).catch(() => null);
4598
5213
  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) => {
5214
+ const store = await readCatalog(resolve15(discoverCacheDir(values.cache)), indexOf(values).path).catch((error) => {
4600
5215
  if (error instanceof MissingIndexError) return null;
4601
5216
  throw error;
4602
5217
  });
4603
- const roots = new Map(inventory.projects.map((p) => [p.id, resolve12(cwd, p.root)]));
5218
+ const roots = new Map(inventory.projects.map((p) => [p.id, resolve15(cwd, p.root)]));
4604
5219
  const report = await buildGaps({ inventory, system, dependencies: declaredPackages(roots) }, store);
4605
- if (values.out) await writeFileAtomic(resolve12(values.out), `${JSON.stringify(report, null, 2)}
5220
+ if (values.out) await writeFileAtomic(resolve15(values.out), `${JSON.stringify(report, null, 2)}
4606
5221
  `);
4607
- if (values.json) json(report);
5222
+ if (values.json) json3(report);
4608
5223
  else print(renderGaps(report));
4609
5224
  return 0;
4610
5225
  }
@@ -4612,12 +5227,12 @@ async function cmdInventory(values) {
4612
5227
  if (values.history) return cmdHistory(values, []);
4613
5228
  const dirs = inventoryDirs(values);
4614
5229
  const report = await scanInventory(values, dirs);
4615
- const out = resolve12(values.out ?? join22(dirs[0], ".youi/inventory.json"));
5230
+ const out = resolve15(values.out ?? join25(dirs[0], ".youi/inventory.json"));
4616
5231
  await writeFileAtomic(out, `${JSON.stringify(report, null, 2)}
4617
5232
  `);
4618
5233
  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"));
5234
+ if (values.json) json3(report);
5235
+ else print([renderInventory(report, relative11(process.cwd(), out).startsWith("..") ? out : relative11(process.cwd(), out)), recorded].join("\n"));
4621
5236
  return 0;
4622
5237
  }
4623
5238
  async function recordScan(values, report, out) {
@@ -4634,7 +5249,7 @@ async function recordScan(values, report, out) {
4634
5249
  async function cmdHistory(values, positionals) {
4635
5250
  const { path } = indexOf(values);
4636
5251
  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);
5252
+ if (values.json) json3(scans);
4638
5253
  else print(renderHistory(scans));
4639
5254
  return 0;
4640
5255
  }
@@ -4642,19 +5257,19 @@ async function cmdIndex(values, positionals) {
4642
5257
  const [action, ...rest] = positionals;
4643
5258
  const { path, home } = indexOf(values);
4644
5259
  if (action === "path") {
4645
- if (values.json) json({ store: storeLocation({ path }), sources: join22(home, SOURCES_FILE), scans: join22(home, SCANS_LOG) });
5260
+ if (values.json) json3({ store: storeLocation({ path }), sources: join25(home, SOURCES_FILE), scans: join25(home, SCANS_LOG) });
4646
5261
  else print(storeLocation({ path }));
4647
5262
  return 0;
4648
5263
  }
4649
5264
  if (action === "stats") {
4650
5265
  const stats = await (await sharedStore(path, process.env, { readOnly: true })).stats();
4651
- if (values.json) json(stats);
5266
+ if (values.json) json3(stats);
4652
5267
  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
5268
  return 0;
4654
5269
  }
4655
5270
  if (action === "rebuild") {
4656
- const summary = await rebuildIndex(path, values.cache ? { discoverCache: resolve12(values.cache) } : {});
4657
- if (values.json) json(summary);
5271
+ const summary = await rebuildIndex(path, values.cache ? { discoverCache: resolve15(values.cache) } : {});
5272
+ if (values.json) json3(summary);
4658
5273
  else print(renderRebuild(summary));
4659
5274
  return 0;
4660
5275
  }
@@ -4668,7 +5283,7 @@ async function cmdWhere(values, positionals) {
4668
5283
  const { report } = await scanProject(p);
4669
5284
  const limit = values.all ? Number.MAX_SAFE_INTEGER : Number(values.limit ?? (values.json ? 20 : 24));
4670
5285
  const where2 = whereUsed(p, report, query, { ...values.route ? { route: values.route } : {}, limit });
4671
- if (values.json) json(where2);
5286
+ if (values.json) json3(where2);
4672
5287
  else print(renderWhere(where2, report));
4673
5288
  return 0;
4674
5289
  }
@@ -4679,7 +5294,7 @@ async function cmdShow(values, positionals) {
4679
5294
  const contract = findContract(p, query);
4680
5295
  if (!contract) throw new Error(`no component "${query}"; known: ${p.system.components.map((c) => c.contract.name).join(", ")}`);
4681
5296
  const card = componentCard(p, contract, (await scanProject(p)).report);
4682
- if (values.json) json(card);
5297
+ if (values.json) json3(card);
4683
5298
  else print(renderShow(card));
4684
5299
  return 0;
4685
5300
  }
@@ -4688,12 +5303,12 @@ async function cmdBom(values, positionals) {
4688
5303
  if (!route) throw new UsageError("youiyoux bom <route>");
4689
5304
  const p = await project(values);
4690
5305
  const bom = screenBom(p, (await scanProject(p)).report, route);
4691
- if (values.json) json(bom);
5306
+ if (values.json) json3(bom);
4692
5307
  else print(renderBom(bom));
4693
5308
  return 0;
4694
5309
  }
4695
5310
  function baselineFile(values, p) {
4696
- if (values["baseline-file"]) return resolve12(values["baseline-file"]);
5311
+ if (values["baseline-file"]) return resolve15(values["baseline-file"]);
4697
5312
  return values.baseline ? baselinePath(p, values.config) : null;
4698
5313
  }
4699
5314
  var checkScope = (values, positionals) => ({ ...positionals.length > 0 ? { files: positionals } : {}, ...values.rule ? { rule: values.rule } : {}, quiet: values.quiet });
@@ -4712,7 +5327,7 @@ async function cmdCheck(values, positionals) {
4712
5327
  const result = await runCheck(p, { ...checkScope(values, positionals), baseline: baselineFile(values, p) });
4713
5328
  if (format === "json") {
4714
5329
  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 });
5330
+ 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
5331
  } else if (format === "github") {
4717
5332
  for (const line of githubAnnotations(result, await repoPrefix(p.root))) print(line);
4718
5333
  print(renderCheckText(result));
@@ -4724,7 +5339,7 @@ async function cmdCheckDiff(values, positionals, p, format) {
4724
5339
  if (values.baseline) throw new UsageError("youiyoux check --diff looks only at added lines, so a baseline does not apply; drop --baseline");
4725
5340
  const change = await workingTreeChange(p, values.base);
4726
5341
  const result = configured(p) ? await runCheck(p, { ...checkScope(values, positionals), diff: change }) : await runRepoCheck(p, change);
4727
- if (format === "json") json(diffJson(result));
5342
+ if (format === "json") json3(diffJson(result));
4728
5343
  else if (format === "github") {
4729
5344
  for (const line of githubDiffAnnotations(result, await repoPrefix(p.root))) print(line);
4730
5345
  print(renderDiffText(result));
@@ -4748,9 +5363,9 @@ async function cmdBaseline(values) {
4748
5363
  const file = baselineFile(values, p) ?? baselinePath(p, values.config);
4749
5364
  const written = await writeBaseline(p, file, { force: values.force });
4750
5365
  const { totals } = written.baseline;
4751
- if (values.json) json({ file: written.file, action: written.action, totals, notAdded: written.notAdded, identity: written.baseline.identity });
5366
+ if (values.json) json3({ file: written.file, action: written.action, totals, notAdded: written.notAdded, identity: written.baseline.identity });
4752
5367
  else {
4753
- print(`${style.ok(written.action)} ${relative8(process.cwd(), file) || file}: ${plural(totals.errors, "error")} \xB7 ${plural(totals.warnings, "warning")} accepted`);
5368
+ print(`${style.ok(written.action)} ${relative11(process.cwd(), file) || file}: ${plural(totals.errors, "error")} \xB7 ${plural(totals.warnings, "warning")} accepted`);
4754
5369
  print(style.dim(` rules ${describeIdentity(written.baseline.identity)}`));
4755
5370
  if (written.notAdded > 0) print(style.warn(` ${plural(written.notAdded, "current violation")} not added: fix them, or accept them deliberately with --force`));
4756
5371
  }
@@ -4764,30 +5379,30 @@ async function cmdPlan(values, positionals) {
4764
5379
  else if (positionals[0] && values.patch) input = { element: positionals[0], patch: await readJsonOrFile(values.patch) };
4765
5380
  else throw new UsageError("youiyoux plan <component> --patch <json|file> or youiyoux plan --token <id> [--value v]");
4766
5381
  const plan = await planChange(p, scan, input);
4767
- if (values.json) json(plan);
5382
+ if (values.json) json3(plan);
4768
5383
  else print(renderPlan(plan));
4769
5384
  return 0;
4770
5385
  }
4771
5386
  async function cmdRules(values) {
4772
5387
  const p = await project(values);
4773
5388
  if (values.preview) {
4774
- const draft = (0, import_yaml3.parse)(await readFile8(values.preview, "utf8"));
5389
+ const draft = (0, import_yaml4.parse)(await readFile10(values.preview, "utf8"));
4775
5390
  const evaluation = evaluateRule(p, await scanProject(p), draft);
4776
- if (values.json) json(evaluation);
5391
+ if (values.json) json3(evaluation);
4777
5392
  else print(renderRulePreview(evaluation));
4778
5393
  return evaluation.ok && evaluation.findings !== null ? 0 : 1;
4779
5394
  }
4780
5395
  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 })) });
5396
+ if (values.json) json3({ capabilities: checkCapabilities(), rules: rules.map(({ rule: rule2, execution }) => ({ id: rule2.id, severity: rule2.severity, ...execution })) });
4782
5397
  else print(renderRules(rules));
4783
5398
  return rules.some(({ execution }) => execution.state === "unsupported") ? 1 : 0;
4784
5399
  }
4785
5400
  async function cmdMigrate(values, positionals) {
4786
5401
  const file = positionals[0];
4787
5402
  if (!file) throw new UsageError("youiyoux migrate <migration.yaml> [--write] [--diff]");
4788
- const spec = MigrationSpec.parse((0, import_yaml3.parse)(await readFile8(file, "utf8")));
5403
+ const spec = MigrationSpec.parse((0, import_yaml4.parse)(await readFile10(file, "utf8")));
4789
5404
  const result = await runMigration(await project(values), spec, { write: values.write });
4790
- if (values.json) json(result);
5405
+ if (values.json) json3(result);
4791
5406
  else print(renderMigration(result, values.diff || !values.write));
4792
5407
  return result.status === "rejected" ? 1 : 0;
4793
5408
  }
@@ -4797,14 +5412,14 @@ async function cmdLibrary(values, positionals) {
4797
5412
  const p = await project(values);
4798
5413
  const words = positionals.slice(1).join(" ");
4799
5414
  const result = searchLibrary(await loadLibrary(p.repo.root), words, Number(values.limit ?? 20));
4800
- if (values.json) json(result);
5415
+ if (values.json) json3(result);
4801
5416
  else print(renderLibrarySearch(result, words));
4802
5417
  return 0;
4803
5418
  }
4804
5419
  if (action === "request" && query && values.reason) {
4805
5420
  const p = await project(values);
4806
5421
  const request = adoptionRequest(p, await loadLibrary(p.repo.root), query, values.reason, await scanProject(p));
4807
- if (values.json) json(request);
5422
+ if (values.json) json3(request);
4808
5423
  else print(renderAdoptionRequest(request));
4809
5424
  return 0;
4810
5425
  }
@@ -4813,7 +5428,7 @@ async function cmdLibrary(values, positionals) {
4813
5428
  async function cmdSync(values) {
4814
5429
  const p = await loadProject(values.project ?? process.cwd(), { ...loadOptions(values), refreshDesign: true });
4815
5430
  const result = await syncProject(p, { dryRun: values["dry-run"], adopt: values.adopt });
4816
- if (values.json) json(result);
5431
+ if (values.json) json3(result);
4817
5432
  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
5433
  return result.actions.some((a) => a.action === "conflict") ? 1 : 0;
4819
5434
  }
@@ -4821,7 +5436,7 @@ async function cmdInit(values) {
4821
5436
  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
5437
  const root = values.project ?? process.cwd();
4823
5438
  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 });
5439
+ const result = await initProject({ root, ...design, schemaDir: join25(TOOL_ROOT, "schema"), ...values.system ? { system: values.system } : {}, ...values.components ? { componentsDir: values.components } : {}, force: values.force });
4825
5440
  if (!values.json) {
4826
5441
  print(`${style.ok("created")} ${result.file}`);
4827
5442
  for (const note of result.notes) print(style.dim(` \xB7 ${note}`));
@@ -4830,6 +5445,82 @@ async function cmdInit(values) {
4830
5445
  const { design: _named, ...rest } = values;
4831
5446
  return cmdSync({ ...design.package ? rest : values, project: root });
4832
5447
  }
5448
+ async function cmdAdopt(values) {
5449
+ const root = resolve15(values.project ?? process.cwd());
5450
+ if (await exists(join25(root, PROJECT_FILE))) throw new Error(`${root} already has ${PROJECT_FILE}: it follows a design system already (youiyoux sync updates it)`);
5451
+ const plan = await planAdoption({ root, ...values.components ? { componentsDir: values.components } : {}, ...values.css ? { css: values.css } : {}, ...values.system ? { system: values.system } : {}, fork: values.fork });
5452
+ const taken = (await Promise.all(plan.files.map(async (f) => await exists(join25(root, f.path)) ? f.path : null))).filter((f) => f !== null);
5453
+ 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" : ""}`);
5454
+ if (values["dry-run"]) {
5455
+ if (values.json) json3(plan);
5456
+ 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"));
5457
+ return 0;
5458
+ }
5459
+ const stylesheet = plan.stylesheet ? await readFile10(join25(root, plan.stylesheet.path), "utf8") : null;
5460
+ try {
5461
+ for (const file of plan.files) await writeFileAtomic(join25(root, file.path), file.content);
5462
+ if (plan.stylesheet) await writeFileAtomic(join25(root, plan.stylesheet.path), plan.stylesheet.content);
5463
+ await initProject({ root, design: root, schemaDir: join25(TOOL_ROOT, "schema"), system: plan.system.id, componentsDir: plan.componentsDir, outputs: { ...plan.outputs, targets: [...plan.outputs.targets], dark: [...plan.outputs.dark] }, tokenSources: plan.tokenSources });
5464
+ await syncProject(await loadProject(root), { dryRun: true });
5465
+ } catch (error) {
5466
+ for (const file of [...plan.files.map((f) => f.path), PROJECT_FILE]) await rm6(join25(root, file), { force: true });
5467
+ for (const folder of [...new Set(plan.files.map((f) => dirname13(f.path)))].sort((a, b) => b.length - a.length)) await rmdir3(join25(root, folder)).catch(() => void 0);
5468
+ await rmdir3(join25(root, "design/components")).catch(() => void 0);
5469
+ await rmdir3(join25(root, "design")).catch(() => void 0);
5470
+ if (plan.stylesheet && stylesheet !== null) await writeFileAtomic(join25(root, plan.stylesheet.path), stylesheet);
5471
+ throw new Error(`adopt was undone, nothing kept: ${error instanceof Error ? error.message : String(error)}`);
5472
+ }
5473
+ if (!values.json) print([...adoptLines(plan), ""].join("\n"));
5474
+ const synced = await cmdSync({ ...values, project: root, components: void 0, css: void 0, system: void 0 });
5475
+ 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`);
5476
+ return synced;
5477
+ }
5478
+ function adoptLines(plan, dryRun = false) {
5479
+ const { tokens } = plan;
5480
+ const shown2 = plan.components.slice(0, 6).join(", ") + (plan.components.length > 6 ? `, \u2026 ${plan.components.length - 6} more` : "");
5481
+ return [
5482
+ `${style.ok(dryRun ? "would adopt" : "adopted")} ${plan.system.name} ${style.dim(`(system ${plan.system.id}) from this product's own UI`)}`,
5483
+ ` 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)` : ""}`,
5484
+ 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)`,
5485
+ ...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)`] : [],
5486
+ ` rules 3 starter rules, as warnings: design/rules`,
5487
+ ` config youi.config.json (the System) and youi.project.json (this product follows it)`,
5488
+ ...plan.notes.map((n) => style.warn(` ! ${n}`)),
5489
+ // The product follows a System already: its values were left to it, and here is how to keep one System, or fork it.
5490
+ ...plan.follows.map((g) => {
5491
+ const system = g.system ?? "a System";
5492
+ 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`);
5493
+ })
5494
+ ];
5495
+ }
5496
+ async function cmdStatus(values) {
5497
+ const report = await workspaceStatus(process.cwd(), values.project);
5498
+ if (values.json) json3(report);
5499
+ else print(renderStatus(report));
5500
+ return report.projects.length === 0 ? 1 : statusExitCode(report);
5501
+ }
5502
+ async function cmdSystem(values, positionals) {
5503
+ const [action, dir, ...extra] = positionals;
5504
+ 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)");
5505
+ 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`);
5506
+ const p = await project(values);
5507
+ const result = await extractSystem(p, dir, { ...values.package ? { package: values.package } : {}, dryRun: values["dry-run"] });
5508
+ const contracts = result.written.filter((f) => f.endsWith(".contract.json")).length;
5509
+ const lines = [
5510
+ `${style.ok(values["dry-run"] ? "would extract" : "extracted")} ${p.system.config.name} ${style.dim(`(system ${result.system.id})`)} \u2192 ${result.target}`,
5511
+ ` 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})` : ""}`,
5512
+ ` product youi.project.json follows it (source: ${JSON.stringify(result.source)}); ${values["dry-run"] ? "would remove" : "removed"} its System files: ${result.removed.join(", ")}`
5513
+ ];
5514
+ if (values["dry-run"]) {
5515
+ print([...lines, "", style.dim("dry run: nothing written or removed")].join("\n"));
5516
+ return 0;
5517
+ }
5518
+ print([...lines, ""].join("\n"));
5519
+ const synced = await cmdSync({ ...values, project: p.root, package: void 0 });
5520
+ const git3 = `push it and set source to { "git": "<its url>", "ref": "v${result.system.version}" }`;
5521
+ print(`Next: make ${relative11(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`);
5522
+ return synced;
5523
+ }
4833
5524
  async function cmdLink(values, positionals) {
4834
5525
  const [dir] = positionals;
4835
5526
  const p = await loadProject(values.project ?? process.cwd(), { ...loadOptions(values), ...dir !== void 0 ? { designRoot: dir } : {} });
@@ -4845,37 +5536,37 @@ async function cmdUnlink(values) {
4845
5536
  const root = values.project ?? process.cwd();
4846
5537
  const linked = await linkedDesign(root);
4847
5538
  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`);
5539
+ print(linked ? `${style.ok("unlinked")} ${resolve15(root)} uses its config's source again` : `${resolve15(root)} was not linked`);
4849
5540
  return 0;
4850
5541
  }
4851
5542
  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 };
5543
+ if (existsSync5(resolve15(value)) || !PACKAGE_NAME.test(value)) return { design: value };
5544
+ return { design: await designRootOf({ package: value }, join25(resolve15(root), PROJECT_FILE), root), package: value };
4854
5545
  }
4855
5546
  async function cmdBuild(values) {
4856
- const root = resolve12(values.design ?? process.cwd());
5547
+ const root = resolve15(values.design ?? process.cwd());
4857
5548
  const system = getSystem(await loadDesignRepo(root), values.system);
4858
5549
  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
5550
  const output = OutputConfig.parse({ cssVarPrefix: values.prefix ?? "youi", targets: ["css-vars", "tailwind-v4", "ts", "react-native"] });
4860
5551
  const started = performance.now();
4861
5552
  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 });
5553
+ const out = resolve15(values.out ?? join25(root, "build", system.config.id));
5554
+ const boundary = values.out ? out : join25(root, "build");
5555
+ if (!within(boundary, out) || built.files.some((file) => !within(out, join25(out, file.path)))) throw new Error(`Cannot build system ${JSON.stringify(system.config.id)}: an output path leaves the output directory.`);
5556
+ for (const file of built.files) await writeFileAtomic(join25(out, file.path), file.content);
5557
+ if (values.json) json3({ out, files: built.files.map((f) => f.path), tokens: built.tokens.length });
4867
5558
  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
5559
  return 0;
4869
5560
  }
4870
5561
  async function cmdParity(values) {
4871
5562
  if (!values.css) throw new UsageError("youiyoux parity --css <file> [--prefix p]");
4872
- const root = resolve12(values.design ?? process.cwd());
5563
+ const root = resolve15(values.design ?? process.cwd());
4873
5564
  const system = getSystem(await loadDesignRepo(root), values.system);
4874
5565
  const prefix = values.prefix ?? "youi";
4875
5566
  const dark = { selector: values.dark ?? ':root[data-theme="dark"]' };
4876
5567
  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);
5568
+ const report = compareParity(await readFile10(values.css, "utf8"), { light: { selector: values.light ?? ":root" }, dark }, built.files[0].content, { light: { selector: ":root" }, dark }, prefix);
5569
+ if (values.json) json3(report);
4879
5570
  else {
4880
5571
  const ok = report.different.length === 0 && report.missing.length === 0;
4881
5572
  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 +5578,18 @@ async function cmdParity(values) {
4887
5578
  async function cmdSchema(values) {
4888
5579
  const schemas = renderJsonSchemas();
4889
5580
  if (values.check) {
4890
- const dir = join22(values.design ? resolve12(values.design) : TOOL_ROOT, "schema");
5581
+ const dir = join25(values.design ? resolve15(values.design) : TOOL_ROOT, "schema");
4891
5582
  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 });
5583
+ for (const { file, content } of schemas) if (await readFile10(join25(dir, file), "utf8").catch(() => null) !== content) stale.push(file);
5584
+ if (values.json) json3({ dir, stale });
4894
5585
  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
5586
  return stale.length === 0 ? 0 : 1;
4896
5587
  }
4897
5588
  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);
5589
+ const root = values.design ? resolve15(values.design) : TOOL_ROOT;
5590
+ for (const { file, content } of schemas) await writeFileAtomic(join25(root, "schema", file), content);
4900
5591
  print(`${style.ok("wrote")} ${schemas.map((s) => s.file).join(", ")}`);
4901
- } else json(Object.fromEntries(schemas.map((s) => [s.file, JSON.parse(s.content)])));
5592
+ } else json3(Object.fromEntries(schemas.map((s) => [s.file, JSON.parse(s.content)])));
4902
5593
  return 0;
4903
5594
  }
4904
5595
  function hookTarget(values) {
@@ -4919,14 +5610,14 @@ async function cmdHooks(values, positionals) {
4919
5610
  if (action !== "install") throw new UsageError("youiyoux hooks install [--claude] [--git] [--ci [--diff]] [--strict] or youiyoux hooks run claude|pre-commit");
4920
5611
  const p = await loadCheckProject(values.project ?? process.cwd(), loadOptions(values), !values.project);
4921
5612
  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 } : {} };
5613
+ const target = { ...hookTarget(values), project: p.root, ...configured(p) && p.configFile && p.configFile !== join25(p.root, PROJECT_FILE) ? { config: p.configFile } : {}, ...values.root !== void 0 ? { root: p.root } : {} };
4923
5614
  const all = !values.claude && !values.git && !values.ci;
4924
5615
  const results = [];
4925
5616
  if (values.claude || all) results.push({ kind: "Claude Code", ...await installClaudeHook(await repoRootOf(p.root), hookCommand(TOOL_ROOT, "claude", target)) });
4926
5617
  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
5618
  if (values.git || all) results.push({ kind: "Codex", ...configured(p) ? codexFallback(p.root, p.config.agents.agentsMd) : await installRepoAgentInstructions(p.root) });
4928
5619
  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);
5620
+ if (values.json) json3(results);
4930
5621
  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
5622
  ${style.dim(r.guarantee)}`);
4932
5623
  return 0;
@@ -4934,11 +5625,11 @@ async function cmdHooks(values, positionals) {
4934
5625
  async function cmdAgents(values, positionals) {
4935
5626
  const [action] = positionals;
4936
5627
  if (action !== "install" && action !== "remove") throw new UsageError("youiyoux agents install|remove [--claude] [--codex] [--json]");
4937
- const projectRoot = resolve12(values.project ?? process.cwd());
5628
+ const projectRoot = resolve15(values.project ?? process.cwd());
4938
5629
  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 || ".") } : {} };
5630
+ const target = { project: projectRoot, ...values.config ? { config: resolve15(values.config) } : {}, ...values.design ? { design: resolve15(values.design) } : {}, ...values.root !== void 0 ? { root: resolve15(values.root || ".") } : {} };
4940
5631
  const server = mcpCommand(TOOL_ROOT, target);
4941
- const system = existsSync5(resolve12(values.config ?? join22(projectRoot, PROJECT_FILE))) ? await project(values) : null;
5632
+ const system = existsSync5(resolve15(values.config ?? join25(projectRoot, PROJECT_FILE))) ? await project(values) : null;
4942
5633
  const claudeServer = system && system.root === repoRoot ? { command: "node", args: mcpArgs(system) } : relativeTo(repoRoot, server);
4943
5634
  const both = !values.claude && !values.codex;
4944
5635
  const results = [];
@@ -4946,7 +5637,7 @@ async function cmdAgents(values, positionals) {
4946
5637
  results.push(...rule2.map((r) => ({ kind: "agent rule", ...r })));
4947
5638
  if (values.claude || both) results.push({ kind: "Claude Code MCP", ...action === "install" ? await installClaudeMcp(repoRoot, claudeServer) : await removeClaudeMcp(repoRoot) });
4948
5639
  if (values.codex || both) results.push({ kind: "Codex MCP", ...action === "install" ? await installCodexMcp(repoRoot, server) : await removeCodexMcp(repoRoot) });
4949
- if (values.json) json(results);
5640
+ if (values.json) json3(results);
4950
5641
  else {
4951
5642
  for (const r of results) print(`${r.action === "unchanged" ? style.dim("unchanged") : style.ok(r.action)} ${r.kind}: ${r.file}
4952
5643
  ${style.dim(r.guarantee)}`);
@@ -5009,7 +5700,32 @@ async function startEditor(values, port) {
5009
5700
  return null;
5010
5701
  }
5011
5702
  }
5703
+ async function workspaceUi(values) {
5704
+ if (values.design || values.project || values.config || values.root) return null;
5705
+ const { roots, workspace } = await statusTargets(process.cwd());
5706
+ if (workspace === null || roots.length === 0) return null;
5707
+ print(style.dim(`reading the status of ${plural(roots.length, "product")}\u2026`));
5708
+ const report = await workspaceStatus(process.cwd());
5709
+ const first = report.projects.findIndex((p) => !("error" in p));
5710
+ if (first < 0) {
5711
+ print(renderStatus(report));
5712
+ throw new Error("no product in this workspace could be read: see each one's Next above");
5713
+ }
5714
+ const port = values.port ?? DEFAULT_HUB_PORT;
5715
+ let open = null;
5716
+ const hub = await startWorkspaceHub({ roots, current: first, status: () => workspaceStatus(process.cwd()), open: (root) => open ? open(root) : Promise.resolve(1) });
5717
+ print(`${style.ok("workspace hub")} http://localhost:${port}/workspace#key=${hub.key}`);
5718
+ print(style.dim(` ${plural(roots.length, "product")}; showing ${report.projects[first].root} first. Open the link: it carries this run's key, which switching needs`));
5719
+ print(style.dim(" Rules and Changes are read-only here; youiyoux ui --project <dir> opens the rule editor on one product"));
5720
+ try {
5721
+ return await runHub(TOOL_ROOT, { project: roots[first], port, api: hub.url, workspace: (snapshot) => open = snapshot, ...values.inventory ? { inventory: values.inventory } : {}, ...values.detect ? { detect: values.detect } : {} });
5722
+ } finally {
5723
+ await hub.close();
5724
+ }
5725
+ }
5012
5726
  async function cmdUi(values) {
5727
+ const workspace = await workspaceUi(values);
5728
+ if (workspace !== null) return workspace;
5013
5729
  const port = values.port ?? DEFAULT_HUB_PORT;
5014
5730
  const editor = await startEditor(values, port);
5015
5731
  try {
@@ -5021,23 +5737,23 @@ async function cmdUi(values) {
5021
5737
  async function cmdDetect(values, positionals) {
5022
5738
  const target = detectTarget(positionals);
5023
5739
  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");
5740
+ const { repoReport, siteReport, writeReport } = await import("./src-ZG7JU6YJ.js");
5741
+ const out = resolve15(values.out ?? ".youi/detect");
5026
5742
  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
5743
  const file = await writeReport(report, out);
5028
- if (values.json) json(report);
5744
+ if (values.json) json3(report);
5029
5745
  else {
5030
- const shown2 = relative8(process.cwd(), file);
5746
+ const shown2 = relative11(process.cwd(), file);
5031
5747
  print(renderDetect(report, shown2 && !shown2.startsWith("..") ? shown2 : file));
5032
5748
  }
5033
5749
  return 0;
5034
5750
  }
5035
5751
  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);
5752
+ const root = resolve15(values.root ?? values.project ?? process.cwd());
5753
+ const targets = positionals.map((t) => existsSync5(resolve15(t)) ? resolve15(t) : t);
5038
5754
  try {
5039
5755
  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`) });
5756
+ if (values.json) json3({ ...report, next: shotsNext(report, (t) => `run youiyoux render${t ? ` ${t}` : ""} again`) });
5041
5757
  else print(renderShots2(report));
5042
5758
  return targets.length > 0 && report.components.every((c) => c.shots.length === 0) ? 1 : 0;
5043
5759
  } catch (error) {
@@ -5048,20 +5764,20 @@ async function cmdRender(values, positionals, io) {
5048
5764
  }
5049
5765
  }
5050
5766
  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()) };
5767
+ const project2 = await loadProject(resolve15(values.project ?? process.cwd()), loadOptions(values)).catch(() => null);
5768
+ return { project: project2, root: project2?.root ?? resolve15(values.root ?? values.project ?? process.cwd()) };
5053
5769
  }
5054
5770
  async function cmdRequests(values, positionals) {
5055
5771
  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
5772
  const [action, id, ...rest] = positionals;
5057
5773
  if (rest.length > 0) throw new UsageError(usage);
5058
- const { RequestStore, requestBrief } = await import("./src-VKQJF7KR.js");
5774
+ const { RequestStore, requestBrief } = await import("./src-EUVBYTKY.js");
5059
5775
  const { project: project2, root } = await product(values);
5060
- const store = new RequestStore(root, project2?.config.id ?? basename3(root));
5776
+ const store = new RequestStore(root, project2?.config.id ?? basename5(root));
5061
5777
  const agent = { kind: "agent", name: "youiyoux CLI" };
5062
5778
  const one = (request) => {
5063
5779
  const brief = requestBrief(request, root);
5064
- if (values.json) json(brief);
5780
+ if (values.json) json3(brief);
5065
5781
  else print(renderBrief(brief, true));
5066
5782
  return 0;
5067
5783
  };
@@ -5071,7 +5787,7 @@ async function cmdRequests(values, positionals) {
5071
5787
  if (!["open", "addressed", "closed", "all"].includes(status)) throw new UsageError("--status takes open, addressed, closed or all");
5072
5788
  const { requests, unreadable } = await store.list();
5073
5789
  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 });
5790
+ if (values.json) json3({ requests: briefs, unreadable });
5075
5791
  else print(renderRequests(briefs, status, unreadable));
5076
5792
  return unreadable.length ? 1 : 0;
5077
5793
  }
@@ -5087,22 +5803,22 @@ async function cmdScreens(values, positionals, io) {
5087
5803
  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
5804
  if (!["capture", "login"].includes(positionals[0] ?? "") || positionals.length > 1 || !values.base) throw new UsageError(usage);
5089
5805
  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"))) {
5806
+ if (positionals[0] === "login") return screensLogin(values.base, values["storage-state"] ? resolve15(values["storage-state"]) : sessionFile(root), io);
5807
+ const dir = resolve15(values.out ?? join25(root, ".youi/screens"));
5808
+ if (!existsSync5(join25(dir, "screens.json"))) {
5093
5809
  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
5810
  `);
5095
5811
  return 1;
5096
5812
  }
5097
- const session = chosenSession(root, values["storage-state"] ? resolve12(values["storage-state"]) : void 0);
5813
+ const session = chosenSession(root, values["storage-state"] ? resolve15(values["storage-state"]) : void 0);
5098
5814
  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
5815
  `);
5100
- const { launchBrowser } = await import("./src-YYI52B5R.js");
5816
+ const { launchBrowser } = await import("./src-ZG7JU6YJ.js");
5101
5817
  const browser = await launchBrowser(process.env.YOUI_CHROMIUM, { purpose: { what: "youiyoux screens capture", command: "youiyoux screens capture" } });
5102
5818
  try {
5103
5819
  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
5820
  `) });
5105
- if (values.json) json(report);
5821
+ if (values.json) json3(report);
5106
5822
  else print(captureText(report));
5107
5823
  return report.screens.every((s) => s.error) ? 1 : 0;
5108
5824
  } finally {
@@ -5154,7 +5870,7 @@ async function cmdReport(values, positionals, io) {
5154
5870
  return result.code;
5155
5871
  }
5156
5872
  async function cmdMcp(values) {
5157
- const { startStdioServer } = await import("./src-DZWWMUDR.js");
5873
+ const { startStdioServer } = await import("./src-YKPJ3PII.js");
5158
5874
  await startStdioServer({ projectRoot: values.project ?? process.cwd(), ...loadOptions(values), ...values.cache ? { discoverCache: values.cache } : {} });
5159
5875
  return -1;
5160
5876
  }
@@ -5165,13 +5881,13 @@ function positiveInt(value, flag) {
5165
5881
  return n;
5166
5882
  }
5167
5883
  async function cmdDiscover(values, positionals, io) {
5168
- const cacheDir = resolve12(discoverCacheDir(values.cache));
5884
+ const cacheDir = resolve15(discoverCacheDir(values.cache));
5169
5885
  const { path, home } = indexOf(values);
5170
5886
  const catalog = () => readCatalog(cacheDir, path);
5171
5887
  const [action, ...rest] = positionals;
5172
5888
  if (action === "sources") {
5173
5889
  const names = await selectSources(cacheDir, rest[0], rest[1]);
5174
- if (values.json) json(names);
5890
+ if (values.json) json3(names);
5175
5891
  else print(names.join("\n"));
5176
5892
  return 0;
5177
5893
  }
@@ -5181,7 +5897,7 @@ async function cmdDiscover(values, positionals, io) {
5181
5897
  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
5898
  const loaded = await ingestCatalog(await sharedStore(path), cacheDir);
5183
5899
  await noteSource(home, { discover: cacheDir });
5184
- if (values.json) json(summary);
5900
+ if (values.json) json3(summary);
5185
5901
  else print(`${renderDiscoverSync(summary)}
5186
5902
  ${style.dim(`store \u2192 ${storeLocation({ path })} (${plural(loaded.chosen, "listing")})`)}`);
5187
5903
  return 0;
@@ -5194,11 +5910,11 @@ ${style.dim(`store \u2192 ${storeLocation({ path })} (${plural(loaded.chosen, "l
5194
5910
  if (action === "show") {
5195
5911
  if (!rest[0]) throw new UsageError("youiyoux discover show <id|ref>");
5196
5912
  if (values.source) {
5197
- json(await candidateSource(cacheDir, rest.join(" "), io));
5913
+ json3(await candidateSource(cacheDir, rest.join(" "), io));
5198
5914
  return 0;
5199
5915
  }
5200
5916
  const shown2 = await showFromCatalog(await catalog(), rest.join(" "));
5201
- if (values.json) json(shown2);
5917
+ if (values.json) json3(shown2);
5202
5918
  else print(renderDiscoverShow(shown2));
5203
5919
  return 0;
5204
5920
  }
@@ -5211,11 +5927,14 @@ ${style.dim(`store \u2192 ${storeLocation({ path })} (${plural(loaded.chosen, "l
5211
5927
  await noteSource(home, { discover: cacheDir });
5212
5928
  }
5213
5929
  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);
5930
+ if (values.json) json3(result);
5215
5931
  else print(renderDiscoverSearch(result));
5216
5932
  return 0;
5217
5933
  }
5218
5934
  var COMMANDS = {
5935
+ adopt: cmdAdopt,
5936
+ status: cmdStatus,
5937
+ system: cmdSystem,
5219
5938
  link: cmdLink,
5220
5939
  unlink: cmdUnlink,
5221
5940
  show: cmdShow,
@@ -5277,7 +5996,7 @@ ${HELP}
5277
5996
  }
5278
5997
  return guarded(() => handler(located(command2, parsed.values), rest, io));
5279
5998
  }
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"]);
5999
+ 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
6000
  var IN_A_DESIGN_REPO = /* @__PURE__ */ new Set(["build", "parity"]);
5282
6001
  function located(command2, values, cwd = process.cwd()) {
5283
6002
  if (IN_A_PRODUCT.has(command2) && values.project === void 0 && values.config === void 0 && values.root === void 0) {