youiyoux 0.1.8 → 0.1.10

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.
@@ -4,7 +4,7 @@ import {
4
4
  loadPackage,
5
5
  readTryIndex,
6
6
  send
7
- } from "./chunk-DCBF7QQY.js";
7
+ } from "./chunk-T3KNPVQG.js";
8
8
  import {
9
9
  writeFileAtomic
10
10
  } from "./chunk-DWYDHVLZ.js";
@@ -53,7 +53,7 @@ import {
53
53
  vendoredPath,
54
54
  whereUsed,
55
55
  withFile
56
- } from "./chunk-3HON5Q5K.js";
56
+ } from "./chunk-X2YYMHR4.js";
57
57
  import {
58
58
  escapingLinks,
59
59
  within
@@ -137,6 +137,7 @@ import {
137
137
  sha256,
138
138
  shortHash,
139
139
  toPosix,
140
+ unifiedDiff,
140
141
  writeFileAtomic
141
142
  } from "./chunk-DWYDHVLZ.js";
142
143
  import {
@@ -147,8 +148,8 @@ import {
147
148
  var import_yaml4 = __toESM(require_dist(), 1);
148
149
  import { execFile as execFile5 } from "node:child_process";
149
150
  import { existsSync as existsSync5 } from "node:fs";
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";
151
+ import { appendFile as appendFile2, readFile as readFile10, rm as rm7, rmdir as rmdir3 } from "node:fs/promises";
152
+ import { basename as basename5, dirname as dirname13, join as join26, relative as relative11, resolve as resolve15 } from "node:path";
152
153
  import { parseArgs, promisify as promisify5 } from "node:util";
153
154
 
154
155
  // ../cli/src/check.ts
@@ -166,7 +167,7 @@ import { join as join2, relative, resolve } from "node:path";
166
167
 
167
168
  // ../cli/src/command.ts
168
169
  var COMMAND = "youiyoux";
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"];
170
+ var SUBCOMMANDS = ["show", "where", "bom", "check", "baseline", "rules", "plan", "migrate", "upgrade", "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"];
170
171
 
171
172
  // ../cli/src/agents.ts
172
173
  var import_yaml = __toESM(require_dist(), 1);
@@ -801,6 +802,7 @@ async function readLock(root) {
801
802
  var Writer = class {
802
803
  actions = [];
803
804
  files = {};
805
+ contents = /* @__PURE__ */ new Map();
804
806
  project;
805
807
  options;
806
808
  previous;
@@ -813,6 +815,7 @@ var Writer = class {
813
815
  return join6(this.project.root, file);
814
816
  }
815
817
  async write(file, content) {
818
+ this.contents.set(file, content);
816
819
  if (!this.options.dryRun) await writeFileAtomic(this.path(file), content);
817
820
  }
818
821
  record(action) {
@@ -985,7 +988,7 @@ async function syncProject(project2, options = {}) {
985
988
  };
986
989
  if (!options.dryRun) await writeFileAtomic(join6(project2.root, LOCK_FILE), `${JSON.stringify(lock, null, 2)}
987
990
  `);
988
- return { actions: writer.actions, lock, unknownTools: unknownToolMentions(project2) };
991
+ return { actions: writer.actions, lock, unknownTools: unknownToolMentions(project2), contents: writer.contents };
989
992
  }
990
993
  async function sameFile(a, b) {
991
994
  const [x, y] = await Promise.all([realpath(a).catch(() => null), realpath(b).catch(() => null)]);
@@ -2106,7 +2109,7 @@ async function libraryLicence(id) {
2106
2109
  }
2107
2110
  }
2108
2111
  async function startRuleEditor(options) {
2109
- const { FileStore, RuleWorkflow, serveWorkflow } = await import("./src-EUVBYTKY.js");
2112
+ const { FileStore, RuleWorkflow, serveWorkflow } = await import("./src-6SNQVIZS.js");
2110
2113
  const projectRoot = resolve6(options.project);
2111
2114
  const load2 = { ...options.config ? { configPath: resolve6(options.config) } : {}, ...options.design ? { designRoot: resolve6(options.design) } : {}, ...options.root ? { productRoot: resolve6(options.root) } : {} };
2112
2115
  const project2 = await loadProject(projectRoot, load2);
@@ -2357,14 +2360,26 @@ function moveDeclarations(css, blocks, moved, importPath) {
2357
2360
  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
2361
  if (rules.length === 0) return null;
2359
2362
  const lines = css.split("\n");
2363
+ const byLine = /* @__PURE__ */ new Map();
2364
+ for (const rule2 of rules) for (const d of rule2.declarations) if (moved.has(d.property.slice(2))) byLine.set(d.line - 1, (byLine.get(d.line - 1) ?? /* @__PURE__ */ new Set()).add(d.property));
2365
+ let count = 0;
2366
+ const kept = [];
2360
2367
  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
- }
2368
+ for (const [index, properties] of byLine) {
2369
+ const segments = lines[index].split(";");
2370
+ const left = segments.filter((segment) => {
2371
+ const property = /^\s*(--[\w-]+)\s*:/.exec(segment)?.[1];
2372
+ if (property === void 0 || !properties.has(property)) return true;
2373
+ properties.delete(property);
2374
+ count++;
2375
+ return false;
2376
+ });
2377
+ kept.push(...[...properties].map((p) => p.slice(2)));
2378
+ const text = left.join(";");
2379
+ if (text.trim() === "") drop.add(index);
2380
+ else lines[index] = /^\s/.test(text) || index === 0 ? text : `${lines[index].match(/^\s*/)[0]}${text.trimStart()}`;
2366
2381
  }
2367
- if (drop.size === 0) return null;
2382
+ if (count === 0) return null;
2368
2383
  let content = lines.filter((_, i) => !drop.has(i)).join("\n");
2369
2384
  for (const block of blocks) content = content.replace(new RegExp(`(^|\\n)[ \\t]*${block.selector.replace(/[.[\]()"=:]/g, "\\$&")}\\s*\\{\\s*\\}[ \\t]*(?=\\n|$)`, "g"), "");
2370
2385
  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"), "");
@@ -2374,7 +2389,7 @@ function moveDeclarations(css, blocks, moved, importPath) {
2374
2389
  content = at > 0 ? `${content.slice(0, at)}
2375
2390
  ${statement}${content.slice(at)}` : `${statement}
2376
2391
  ${content}`;
2377
- return { content: content.replace(/\n{3,}/g, "\n\n"), moved: drop.size };
2392
+ return { content: content.replace(/\n{3,}/g, "\n\n"), moved: count, kept: [...new Set(kept)].sort() };
2378
2393
  }
2379
2394
  async function planAdoption(options) {
2380
2395
  const root = options.root;
@@ -2470,6 +2485,10 @@ async function planAdoption(options) {
2470
2485
  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
2486
  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
2487
  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`);
2488
+ const read = [":root", ...darkBlocks.map((b) => [b.outer, b.selector].filter(Boolean).join(" > "))];
2489
+ const others = css ? [...new Set(readRules(css).filter((r) => !read.includes(r.preludes.join(" > ")) && !r.preludes.at(-1).startsWith("@") && r.declarations.some((d) => d.property.startsWith("--"))).map((r) => r.preludes.join(" > ").replace(/\s+/g, " ")))] : [];
2490
+ if (others.length > 0) notes.push(`${plural(others.length, "other block")} in ${stylesheet} set${others.length === 1 ? "s" : ""} variables too and stay${others.length === 1 ? "s" : ""} there, as ${others.length === 1 ? "it was" : "they were"} (${others.slice(0, 4).join(" \xB7 ")}${others.length > 4 ? " \xB7 \u2026" : ""}): the System holds the :root${darkBlocks.length > 0 ? " and dark" : ""} values only`);
2491
+ if (rewritten && rewritten.kept.length > 0) notes.push(`${plural(rewritten.kept.length, "variable")} ${rewritten.kept.length === 1 ? "stays" : "stay"} in ${stylesheet} too, written where adopt cannot take them out alone (beside a selector): ${rewritten.kept.slice(0, 4).map((n) => `--${n}`).join(", ")}${rewritten.kept.length > 4 ? ", \u2026" : ""}; they hold the same values, so remove them by hand`);
2473
2492
  for (const [rule2, content] of starterRules(id, componentsDir)) out.push({ path: `design/rules/${rule2}.rule.yaml`, content });
2474
2493
  out.push({
2475
2494
  path: "design/design.md",
@@ -2634,14 +2653,186 @@ async function extractSystem(project2, dir, options = {}) {
2634
2653
  }
2635
2654
  }
2636
2655
 
2656
+ // ../cli/src/upgrade.ts
2657
+ import { lstat as lstat2, rm as rm5 } from "node:fs/promises";
2658
+ import { join as join16 } from "node:path";
2659
+ var LOCK_FILE2 = "youi.lock.json";
2660
+ var WRITES = /* @__PURE__ */ new Set(["created", "updated", "merged", "adopted", "removed"]);
2661
+ function compareVersions(a, b) {
2662
+ const parse = (v) => {
2663
+ const [core = "", pre = ""] = v.replace(/^v/, "").split("-", 2);
2664
+ return [core.split(".").slice(0, 3).map((n) => Number(n) || 0), pre];
2665
+ };
2666
+ const [[x, xp], [y, yp]] = [parse(a), parse(b)];
2667
+ for (let i = 0; i < 3; i++) if ((x[i] ?? 0) !== (y[i] ?? 0)) return (x[i] ?? 0) - (y[i] ?? 0);
2668
+ if (xp === yp) return 0;
2669
+ return xp === "" ? 1 : yp === "" ? -1 : xp < yp ? -1 : 1;
2670
+ }
2671
+ function admits(range, version) {
2672
+ const parts = range.trim().split(/\s+/);
2673
+ const results = parts.map((part) => {
2674
+ if (part === "*" || part === "x") return true;
2675
+ const xr = /^v?(\d+)(?:\.(\d+|x))?(?:\.(x))?$/.exec(part);
2676
+ if (xr && (xr[2] === "x" || xr[3] === "x" || xr[2] === void 0)) {
2677
+ const [major, minor] = version.split(".").map(Number);
2678
+ return major === Number(xr[1]) && (xr[2] === void 0 || xr[2] === "x" || minor === Number(xr[2]));
2679
+ }
2680
+ const op = /^(\^|~|>=|<=|>|<|=)?v?(\d+\.\d+\.\d+(?:-[\w.]+)?)$/.exec(part);
2681
+ if (!op) return null;
2682
+ const [, kind = "=", base = ""] = op;
2683
+ const cmp = compareVersions(version, base);
2684
+ const [bMajor, bMinor] = base.split(".").map(Number);
2685
+ const [vMajor, vMinor] = version.split(".").map(Number);
2686
+ if (kind === "^") return cmp >= 0 && (bMajor === 0 ? vMajor === 0 && vMinor === bMinor : vMajor === bMajor);
2687
+ if (kind === "~") return cmp >= 0 && vMajor === bMajor && vMinor === bMinor;
2688
+ return { "=": cmp === 0, ">=": cmp >= 0, "<=": cmp <= 0, ">": cmp > 0, "<": cmp < 0 }[kind] ?? null;
2689
+ });
2690
+ return results.includes(null) ? null : results.every(Boolean);
2691
+ }
2692
+ function pendingMigrations(project2, lock) {
2693
+ const entries = Object.entries(lock.files).flatMap(([file, entry]) => entry.kind === "vendored" ? [{ file, ...entry }] : []);
2694
+ const planned = [];
2695
+ for (const { contract, migrations } of project2.system.components) {
2696
+ const entry = entries.find((e) => e.file === vendoredPath(project2, contract.source.file) && e.elements.includes(contract.id)) ?? entries.filter((e) => e.elements.includes(contract.id)).sort((a, b) => a.elements.length - b.elements.length)[0];
2697
+ if (!entry) continue;
2698
+ let at = entry.version;
2699
+ for (const spec of [...migrations].filter((m) => compareVersions(m.to, entry.version) > 0 && compareVersions(m.to, contract.version) <= 0).sort((a, b) => compareVersions(a.to, b.to) || a.id.localeCompare(b.id))) {
2700
+ planned.push({ spec, at, mismatch: admits(spec.from, at) === false });
2701
+ at = spec.to;
2702
+ }
2703
+ }
2704
+ return planned.sort((a, b) => compareVersions(a.spec.to, b.spec.to) || a.spec.id.localeCompare(b.spec.id));
2705
+ }
2706
+ var Snapshot = class {
2707
+ saved = /* @__PURE__ */ new Map();
2708
+ root;
2709
+ constructor(root) {
2710
+ this.root = root;
2711
+ }
2712
+ async keep(files) {
2713
+ for (const file of files) if (!this.saved.has(file)) this.saved.set(file, await readTextOrNull(join16(this.root, file)));
2714
+ }
2715
+ /** A file's content before a step wrote it, unless it was kept already. */
2716
+ remember(file, content) {
2717
+ if (!this.saved.has(file)) this.saved.set(file, content);
2718
+ }
2719
+ /** Put back each file that changed; those that could not be are returned, to name them. */
2720
+ async restore() {
2721
+ const stuck = [];
2722
+ for (const [file, content] of this.saved) {
2723
+ const path = join16(this.root, file);
2724
+ try {
2725
+ if (content !== null) {
2726
+ if (await readTextOrNull(path) !== content) await writeFileAtomic(path, content);
2727
+ } else if (await lstat2(path).then((s) => s.isFile(), () => false)) await rm5(path);
2728
+ } catch {
2729
+ stuck.push(file);
2730
+ }
2731
+ }
2732
+ return stuck;
2733
+ }
2734
+ };
2735
+ var gateFailures = (result) => result.gates.filter((g) => !g.ok).map((g) => `${g.name}: ${g.detail}`).join("; ");
2736
+ async function withRef(configFile, ref) {
2737
+ const text = await readTextOrNull(configFile) ?? "{}";
2738
+ const config = JSON.parse(text);
2739
+ return `${JSON.stringify({ ...config, source: { ...config.source, ref } }, null, 2)}
2740
+ `;
2741
+ }
2742
+ async function upgradeProject(project2, options = {}) {
2743
+ const lock = await readLock(project2.root);
2744
+ const source = project2.config.source;
2745
+ const system = { id: project2.system.config.id, name: project2.system.config.name };
2746
+ const to = { version: project2.system.config.version, commit: project2.designCommit ?? null, ref: options.to ?? ("git" in source ? source.ref : null) };
2747
+ const from = { version: lock?.systemVersion ?? null, commit: lock?.sourceCommit ?? null };
2748
+ const result = (status, parts, next2) => ({ status, system, from, to, sync: parts.sync ?? [], migrations: parts.migrations ?? [], blocked: parts.blocked ?? [], next: next2 });
2749
+ if (!lock) return result("blocked", { blocked: ["this product has never been synced with its System, so there is no earlier version to upgrade from"] }, "youiyoux sync");
2750
+ if (lock.system !== system.id) return result("blocked", { blocked: [`youi.lock.json is for System ${lock.system}, and the config names ${system.id}: an upgrade moves within one System`] }, "youiyoux sync, to switch Systems");
2751
+ const dry = await syncProject(project2, { dryRun: true });
2752
+ const conflicts = dry.actions.filter((a) => a.action === "conflict");
2753
+ const sync = dry.actions.filter((a) => WRITES.has(a.action) || a.action === "conflict" || a.action === "kept-local");
2754
+ const planned = pendingMigrations(project2, lock);
2755
+ const blocked = [
2756
+ ...conflicts.map((a) => `${a.file}: ${a.detail ?? "conflicts with upstream"}`),
2757
+ ...planned.filter((p) => p.mismatch).map((p) => `${p.spec.id} migrates from ${p.spec.from}, but ${p.spec.element} would be at ${p.at} here: no migration takes it from ${p.at} to ${p.spec.from}`)
2758
+ ];
2759
+ if (blocked.length > 0) return result("blocked", { sync, blocked }, conflicts.length > 0 ? "youiyoux sync: resolve the conflicts it reports, then youiyoux sync --adopt records them, then youiyoux upgrade" : "ask the System's owners for the missing migration, or migrate by hand, then youiyoux sync");
2760
+ let staged = Object.fromEntries(dry.actions.filter((a) => a.role === "component" && dry.contents.has(a.file)).map((a) => [a.file, dry.contents.get(a.file)]));
2761
+ const previews = [];
2762
+ for (const { spec } of planned) {
2763
+ const preview = await runMigration({ ...project2, staged }, spec);
2764
+ previews.push(preview);
2765
+ if (preview.status === "rejected") return result("blocked", { sync, migrations: previews, blocked: [`${spec.id} would not hold: ${gateFailures(preview)}`] }, "fix what the failing gate names, or ask the System's owners; nothing was written");
2766
+ staged = { ...staged, ...Object.fromEntries(preview.changes.map((c) => [c.file, c.after])) };
2767
+ }
2768
+ const ref = options.to !== void 0 && "git" in source && options.to !== source.ref ? options.to : null;
2769
+ const changes = sync.some((a) => WRITES.has(a.action)) || previews.some((m) => m.status !== "noop") || ref !== null || lock.systemVersion !== to.version;
2770
+ if (!changes) return result("current", { sync, migrations: previews }, "nothing to do");
2771
+ if (!options.write) return result("preview", { sync, migrations: previews }, `youiyoux upgrade${options.to !== void 0 ? ` --to ${options.to}` : ""} --write`);
2772
+ const configFile = project2.configFile ?? join16(project2.root, PROJECT_FILE);
2773
+ const snapshot = new Snapshot(project2.root);
2774
+ const configBefore = await readTextOrNull(configFile);
2775
+ await snapshot.keep([LOCK_FILE2, ...dry.contents.keys(), ...dry.actions.filter((a) => a.action === "removed").map((a) => a.file), ...previews.flatMap((m) => m.changes.map((c) => c.file))]);
2776
+ const fail = async (why, done) => {
2777
+ const stuck = await snapshot.restore();
2778
+ if (ref !== null && configBefore !== null) await writeFileAtomic(configFile, configBefore).catch(() => stuck.push(PROJECT_FILE));
2779
+ const back = stuck.length === 0 ? "every file the upgrade wrote was put back" : `the files it wrote were put back except ${stuck.join(", ")}: restore those from git`;
2780
+ return result("blocked", { sync, migrations: previews, ...done, blocked: [`${why}; ${back}`] }, "run youiyoux upgrade again to see what changed");
2781
+ };
2782
+ try {
2783
+ if (ref !== null) await writeFileAtomic(configFile, await withRef(configFile, ref));
2784
+ const synced = await syncProject(project2);
2785
+ const late = synced.actions.filter((a) => a.action === "conflict");
2786
+ if (late.length > 0) return await fail(`the sync conflicted in ${late.map((a) => a.file).join(", ")}`, {});
2787
+ const applied = [];
2788
+ for (const { spec } of planned) {
2789
+ const run7 = await runMigration(project2, spec, { write: true });
2790
+ applied.push(run7);
2791
+ for (const change of run7.changes) snapshot.remember(change.file, change.before);
2792
+ if (run7.status === "rejected") return await fail(`${spec.id} did not hold on the written files: ${gateFailures(run7)}`, { migrations: applied });
2793
+ }
2794
+ const residuals = applied.reduce((n, m) => n + m.residuals.length, 0);
2795
+ return result("upgraded", { sync: synced.actions.filter((a) => WRITES.has(a.action) || a.action === "kept-local"), migrations: applied }, residuals > 0 ? `fix the ${residuals === 1 ? "site" : `${residuals} sites`} listed for a person, then youiyoux check` : "youiyoux check");
2796
+ } catch (error) {
2797
+ return fail(error instanceof Error ? error.message : String(error), {});
2798
+ }
2799
+ }
2800
+ var ACTION = { created: style.ok, updated: style.live, merged: style.live, adopted: style.ok, removed: style.live, "kept-local": style.warn, conflict: style.error };
2801
+ function renderUpgrade(r, showDiff) {
2802
+ const version = (v) => v === null ? "never synced" : `v${v}`;
2803
+ const move = r.from.version === r.to.version ? `${version(r.to.version)}${r.from.commit && r.to.commit && r.from.commit !== r.to.commit ? style.dim(` (${r.from.commit.slice(0, 7)} \u2192 ${r.to.commit.slice(0, 7)})`) : ""}` : `${version(r.from.version)} \u2192 v${r.to.version}`;
2804
+ const verdict = { current: style.ok("up to date"), preview: style.live("preview: nothing written"), upgraded: style.ok("upgraded"), blocked: style.error("blocked: nothing written") }[r.status];
2805
+ const lines = [`${style.accent("upgrade")} ${r.system.name} ${move}${r.to.ref ? style.dim(` \xB7 ref ${r.to.ref}`) : ""} ${verdict}`, ""];
2806
+ const row = (a) => ` ${padEnd((ACTION[a.action] ?? style.dim)(a.action), 11)} ${a.file}${a.detail ? style.dim(` ${a.detail}`) : ""}`;
2807
+ const generated = r.sync.filter((a) => a.role !== "component" && a.action !== "conflict");
2808
+ lines.push(style.bold("sync"), ...r.sync.length === 0 ? [style.dim(" nothing to bring")] : r.sync.filter((a) => !generated.includes(a)).map(row));
2809
+ if (generated.length === 1) lines.push(row(generated[0]));
2810
+ else if (generated.length > 1) lines.push(` ${padEnd(style.live("updated"), 11)} ${plural(generated.length, "generated file")}${style.dim(` ${generated.slice(0, 3).map((a) => a.file).join(", ")}${generated.length > 3 ? ", \u2026" : ""}`)}`);
2811
+ lines.push("", style.bold("migrations"));
2812
+ if (r.migrations.length === 0) lines.push(style.dim(r.status === "blocked" && r.sync.length > 0 ? " not run" : r.status === "current" ? " none pending" : " none: no component this product has changed its API in this release"));
2813
+ for (const m of r.migrations) {
2814
+ const mark = m.status === "rejected" ? style.error("\u2716") : m.status === "noop" ? style.dim("\xB7") : style.ok("\u2713");
2815
+ const what = m.status === "noop" ? style.dim("nothing here uses what it changes") : `${plural(m.edited, "site")} rewritten in ${plural(m.changes.length, "file")}${m.residuals.length > 0 ? ` \xB7 ${style.warn(`${plural(m.residuals.length, "site")} for a person`)}` : ""}`;
2816
+ lines.push(` ${mark} ${m.migration} ${what}`);
2817
+ for (const gate of m.gates.filter((g) => !g.ok)) lines.push(` ${style.error(gate.name)} ${style.dim(gate.detail)}`);
2818
+ for (const residual of m.residuals) lines.push(` ${style.dim(`${residual.file}:${residual.line}`)} ${residual.reason}`);
2819
+ }
2820
+ const files = /* @__PURE__ */ new Map();
2821
+ for (const change of r.migrations.flatMap((m) => m.changes)) files.set(change.file, { before: files.get(change.file)?.before ?? change.before, after: change.after });
2822
+ if (showDiff) for (const [file, { before, after }] of files) lines.push("", ...unifiedDiff(file, before, after).trimEnd().split("\n").map((l) => l.startsWith("+") && !l.startsWith("+++") ? style.ok(l) : l.startsWith("-") && !l.startsWith("---") ? style.error(l) : style.dim(l)));
2823
+ if (r.blocked.length > 0) lines.push("", ...r.blocked.map((b) => `${style.error("\u2716")} ${b}`));
2824
+ lines.push("", `${style.bold("Next:")} ${r.next}`);
2825
+ return lines.join("\n");
2826
+ }
2827
+
2637
2828
  // ../cli/src/status.ts
2638
2829
  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";
2830
+ import { dirname as dirname9, join as join17, relative as relative8, resolve as resolve10 } from "node:path";
2640
2831
  var placeOf = (cwd, root) => {
2641
2832
  const rel = relative8(cwd, root) || ".";
2642
2833
  return rel.split(/[\\/]/).filter((part) => part === "..").length > 1 ? resolve10(root) : rel;
2643
2834
  };
2644
- var WRITES = /* @__PURE__ */ new Set(["created", "updated", "merged", "adopted", "conflict", "removed"]);
2835
+ var WRITES2 = /* @__PURE__ */ new Set(["created", "updated", "merged", "adopted", "conflict", "removed"]);
2645
2836
  function sourceOf(project2) {
2646
2837
  if (project2.linked) return `your checkout ${project2.linked}`;
2647
2838
  const source = project2.config.source;
@@ -2649,9 +2840,10 @@ function sourceOf(project2) {
2649
2840
  if ("git" in source) return `git ${source.git} at ${source.ref}`;
2650
2841
  return resolve10(project2.root) === resolve10(project2.repo.root) ? "this repo" : `path ${source.path}`;
2651
2842
  }
2652
- function nextStep(s) {
2843
+ function nextStep(s, migrations) {
2653
2844
  if (s.sync.state === "never") return "youiyoux sync: this product has never been synced with its System";
2654
2845
  if (s.sync.state === "conflict") return "youiyoux sync: resolve the conflicts it reports, then youiyoux sync --adopt records them";
2846
+ if (migrations > 0) return `youiyoux upgrade: ${s.system.name} v${s.system.version} comes with ${plural(migrations, "migration")} for this product's code, which upgrade runs with the sync`;
2655
2847
  if (s.sync.state === "behind") return `youiyoux sync: ${plural(s.sync.changes.length, "file")} would change`;
2656
2848
  if (s.check.incomplete) return "youiyoux check: some files or rules could not be checked, and it says which";
2657
2849
  if (s.check.errors > 0) return `youiyoux check: fix ${plural(s.check.errors, "error")}`;
@@ -2662,13 +2854,13 @@ function nextStep(s) {
2662
2854
  async function projectStatus(project2, cwd = process.cwd()) {
2663
2855
  const lock = await readLock(project2.root);
2664
2856
  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 }));
2857
+ const changes = actions.filter((a) => WRITES2.has(a.action)).map((a) => ({ file: a.file, action: a.action }));
2666
2858
  const baselineFile2 = baselinePath(project2);
2667
2859
  const check = await runCheck(project2, { baseline: await readTextOrNull(baselineFile2) === null ? null : baselineFile2 });
2668
2860
  const comparison = check.baseline?.comparison;
2669
2861
  const counted = comparison ? [...comparison.new, ...comparison.regressed] : check.violations;
2670
2862
  const usages = check.scan.report.usages;
2671
- const { RequestStore } = await import("./src-EUVBYTKY.js");
2863
+ const { RequestStore } = await import("./src-6SNQVIZS.js");
2672
2864
  const requests = await new RequestStore(project2.root, project2.config.id).list();
2673
2865
  const status = {
2674
2866
  id: project2.config.id,
@@ -2686,7 +2878,7 @@ async function projectStatus(project2, cwd = process.cwd()) {
2686
2878
  components: { system: project2.system.components.length, used: new Set(usages.map((u) => u.element)).size, sites: usages.length },
2687
2879
  requests: { open: requests.requests.filter((r) => r.status === "open").length, addressed: requests.requests.filter((r) => r.status === "addressed").length, unreadable: requests.unreadable.length }
2688
2880
  };
2689
- return { ...status, next: nextStep(status) };
2881
+ return { ...status, next: nextStep(status, lock ? pendingMigrations(project2, lock).length : 0) };
2690
2882
  }
2691
2883
  async function statusTargets(cwd, named2) {
2692
2884
  if (named2) return { roots: [resolve10(named2)], workspace: null };
@@ -2694,7 +2886,7 @@ async function statusTargets(cwd, named2) {
2694
2886
  if (product2) return { roots: [product2], workspace: null };
2695
2887
  const at = findUp(cwd, WORKSPACE_FILE);
2696
2888
  if (at) {
2697
- const file = join16(at, WORKSPACE_FILE);
2889
+ const file = join17(at, WORKSPACE_FILE);
2698
2890
  const workspace = await readDefinition(file, WorkspaceConfig);
2699
2891
  return { roots: workspace.projects.map((p) => resolve10(at, p)), workspace: file };
2700
2892
  }
@@ -2716,7 +2908,7 @@ async function workspaceStatus(cwd, named2) {
2716
2908
  projects.push(await projectStatus(await loadProject(root), cwd));
2717
2909
  } catch (error) {
2718
2910
  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)}` });
2911
+ projects.push({ root: label, error: error instanceof Error ? error.message : String(error), next: await readTextOrNull(join17(root, PROJECT_FILE)) === null ? `youiyoux adopt or youiyoux init --design <dir> in ${label}` : `fix ${join17(label, PROJECT_FILE)}` });
2720
2912
  }
2721
2913
  }
2722
2914
  return { schema: "youi.status/v1", generatedAt: (/* @__PURE__ */ new Date()).toISOString(), workspace: workspace ? relative8(cwd, workspace) || WORKSPACE_FILE : null, projects };
@@ -2813,14 +3005,14 @@ async function startWorkspaceHub(options) {
2813
3005
  }
2814
3006
 
2815
3007
  // ../cli/src/index-store.ts
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";
3008
+ import { appendFile, mkdir as mkdir4, readFile as readFile6, stat as stat2, rename as rename2, mkdtemp as mkdtemp4, rm as rm6 } from "node:fs/promises";
3009
+ import { dirname as dirname10, join as join18, resolve as resolve11 } from "node:path";
2818
3010
  var SOURCES_FILE = "index-sources.json";
2819
3011
  var SCANS_LOG = "scans.jsonl";
2820
3012
  var EMPTY = { discover: null, inventories: [], projects: [] };
2821
3013
  async function readIndexSources(home) {
2822
3014
  try {
2823
- const raw = JSON.parse(await readFile6(join17(home, SOURCES_FILE), "utf8"));
3015
+ const raw = JSON.parse(await readFile6(join18(home, SOURCES_FILE), "utf8"));
2824
3016
  return { discover: raw.discover ?? null, inventories: raw.inventories ?? [], projects: raw.projects ?? [] };
2825
3017
  } catch {
2826
3018
  return EMPTY;
@@ -2833,14 +3025,14 @@ async function noteSource(home, change) {
2833
3025
  inventories: change.inventory && !sources.inventories.includes(change.inventory) ? [...sources.inventories, change.inventory] : sources.inventories,
2834
3026
  projects: change.project ? [...sources.projects.filter((p) => p.root !== change.project.root), change.project] : sources.projects
2835
3027
  };
2836
- await writeFileAtomic(join17(home, SOURCES_FILE), `${JSON.stringify(next2, null, 2)}
3028
+ await writeFileAtomic(join18(home, SOURCES_FILE), `${JSON.stringify(next2, null, 2)}
2837
3029
  `);
2838
3030
  }
2839
3031
  async function recordInventory(store, home, report, file) {
2840
3032
  const run7 = inventoryRun(report, resolve11(file));
2841
3033
  const logged = { run: run7.run, source: run7.source, scannedAt: run7.scannedAt, projects: run7.projects, families: run7.families };
2842
3034
  await mkdir4(home, { recursive: true });
2843
- await appendFile(join17(home, SCANS_LOG), `${JSON.stringify({ schema: "youi.scan-log/v1", ...logged })}
3035
+ await appendFile(join18(home, SCANS_LOG), `${JSON.stringify({ schema: "youi.scan-log/v1", ...logged })}
2844
3036
  `);
2845
3037
  await store.recordInventory(run7);
2846
3038
  await noteSource(home, { inventory: run7.source });
@@ -2851,7 +3043,7 @@ async function recordProject(store, project2, usage) {
2851
3043
  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 }));
2852
3044
  }
2853
3045
  async function readLog(home) {
2854
- const text = await readFile6(join17(home, SCANS_LOG), "utf8").catch(() => "");
3046
+ const text = await readFile6(join18(home, SCANS_LOG), "utf8").catch(() => "");
2855
3047
  return text.split("\n").flatMap((line) => {
2856
3048
  if (!line.trim()) return [];
2857
3049
  try {
@@ -2868,7 +3060,7 @@ async function rebuildIndex(path, options = {}) {
2868
3060
  const home = indexHome(path, env);
2869
3061
  await mkdir4(dirname10(path), { recursive: true });
2870
3062
  const scratch = !env.DATABASE_URL ? await mkdtemp4(`${path}.rebuild-`) : null;
2871
- const target = scratch ? join17(scratch, "index.db") : path;
3063
+ const target = scratch ? join18(scratch, "index.db") : path;
2872
3064
  const store = scratch ? await openSqlite(target) : await sharedStore(path, env);
2873
3065
  try {
2874
3066
  if (env.DATABASE_URL) await store.reset();
@@ -2876,7 +3068,7 @@ async function rebuildIndex(path, options = {}) {
2876
3068
  const sources = await readIndexSources(home);
2877
3069
  const cache = options.discoverCache ?? sources.discover;
2878
3070
  let discover = { cache, chosen: 0, all: 0 };
2879
- if (cache && await stat2(join17(cache, "index.json")).then(() => true, () => false)) {
3071
+ if (cache && await stat2(join18(cache, "index.json")).then(() => true, () => false)) {
2880
3072
  const loaded = await ingestCatalog(store, cache);
2881
3073
  discover = { cache, chosen: loaded.chosen, all: loaded.all };
2882
3074
  }
@@ -2904,7 +3096,7 @@ async function rebuildIndex(path, options = {}) {
2904
3096
  }
2905
3097
  try {
2906
3098
  const project2 = await loadProject(source.root, source.options);
2907
- const usage = ScanReport.safeParse(await readJson(source.usage ?? join17(project2.root, ".youi/usage.json")));
3099
+ const usage = ScanReport.safeParse(await readJson(source.usage ?? join18(project2.root, ".youi/usage.json")));
2908
3100
  await recordProject(store, project2, usage.success ? usage.data : null);
2909
3101
  projects++;
2910
3102
  } catch (error) {
@@ -2939,7 +3131,7 @@ async function rebuildIndex(path, options = {}) {
2939
3131
  } finally {
2940
3132
  if (scratch) {
2941
3133
  await store.close();
2942
- await rm5(scratch, { recursive: true, force: true });
3134
+ await rm6(scratch, { recursive: true, force: true });
2943
3135
  }
2944
3136
  }
2945
3137
  }
@@ -3123,7 +3315,7 @@ function renderAdoptionRequest(request) {
3123
3315
  // ../cli/src/report/run.ts
3124
3316
  import { randomBytes as randomBytes3 } from "node:crypto";
3125
3317
  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";
3318
+ import { join as join25, relative as relative10, resolve as resolve14 } from "node:path";
3127
3319
 
3128
3320
  // ../cli/src/report/card.ts
3129
3321
  import { deflateSync } from "node:zlib";
@@ -3359,7 +3551,7 @@ function drawCard(input) {
3359
3551
 
3360
3552
  // ../cli/src/report/model.ts
3361
3553
  import { readFile as readFile8 } from "node:fs/promises";
3362
- import { join as join21 } from "node:path";
3554
+ import { join as join22 } from "node:path";
3363
3555
 
3364
3556
  // ../cli/src/report/filters.ts
3365
3557
  var SHOWCASE_GLOBS = ["**/{__stories__,.storybook,storybook,storybook-static,example,examples,demo,demos,playground,showcase,showcases}/**", "**/*.stories.*", "**/*.story.*"];
@@ -3486,11 +3678,11 @@ function publicLook(caption) {
3486
3678
 
3487
3679
  // ../cli/src/report/groups.ts
3488
3680
  import { existsSync as existsSync3 } from "node:fs";
3489
- import { join as join19 } from "node:path";
3681
+ import { join as join20 } from "node:path";
3490
3682
 
3491
3683
  // ../cli/src/report/imports.ts
3492
3684
  import { realpathSync } from "node:fs";
3493
- import { dirname as dirname11, join as join18, relative as relative9, resolve as resolve12 } from "node:path";
3685
+ import { dirname as dirname11, join as join19, relative as relative9, resolve as resolve12 } from "node:path";
3494
3686
  import ts from "typescript";
3495
3687
  var resolvers = /* @__PURE__ */ new Map();
3496
3688
  function resolverOf(root) {
@@ -3509,13 +3701,13 @@ function clearImportCache() {
3509
3701
  }
3510
3702
  async function verifiedImport(root, keeper, from, suggested) {
3511
3703
  const { options, cache } = resolverOf(root);
3512
- let local = relative9(dirname11(join18(root, from)), join18(root, keeper)).replaceAll("\\", "/").replace(/\.(tsx?|jsx?|mjs)$/, "");
3704
+ let local = relative9(dirname11(join19(root, from)), join19(root, keeper)).replaceAll("\\", "/").replace(/\.(tsx?|jsx?|mjs)$/, "");
3513
3705
  if (!local.startsWith(".")) local = `./${local}`;
3514
3706
  for (const specifier of /* @__PURE__ */ new Set([suggested, local])) {
3515
3707
  const found = ts.resolveModuleName(specifier, resolve12(root, from), options, ts.sys, cache).resolvedModule?.resolvedFileName;
3516
3708
  if (found) {
3517
3709
  try {
3518
- if (realpathSync(found) === realpathSync(join18(root, keeper))) return specifier;
3710
+ if (realpathSync(found) === realpathSync(join19(root, keeper))) return specifier;
3519
3711
  } catch {
3520
3712
  }
3521
3713
  }
@@ -3923,7 +4115,7 @@ function factsOf(project2, id, modules, eligible, workspaceNames, dependencies,
3923
4115
  if (dirs.has(file.slice(0, at))) break;
3924
4116
  dirs.add(file.slice(0, at));
3925
4117
  }
3926
- const roots = [...dirs].filter((dir) => existsSync3(join19(project2.root, dir, "package.json"))).sort((a, b) => b.length - a.length);
4118
+ const roots = [...dirs].filter((dir) => existsSync3(join20(project2.root, dir, "package.json"))).sort((a, b) => b.length - a.length);
3927
4119
  const nested = (file) => roots.find((dir) => file.startsWith(`${dir}/`)) ?? "";
3928
4120
  return { project: project2, id, dependencies, packageImports, jsxCounts, eligible, twins, dirs, importer, packageOf: nested };
3929
4121
  }
@@ -3931,11 +4123,11 @@ function factsOf(project2, id, modules, eligible, workspaceNames, dependencies,
3931
4123
  // ../cli/src/report/projects.ts
3932
4124
  var import_yaml3 = __toESM(require_dist(), 1);
3933
4125
  import { readFile as readFile7 } from "node:fs/promises";
3934
- import { basename as basename4, join as join20, resolve as resolve13 } from "node:path";
4126
+ import { basename as basename4, join as join21, resolve as resolve13 } from "node:path";
3935
4127
  var UI_FILES = ["**/*.{tsx,jsx,astro,vue,svelte}"];
3936
4128
  var strings = (value) => Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
3937
4129
  async function workspaceGlobs(root, pkg) {
3938
- const pnpm = await readFile7(join20(root, "pnpm-workspace.yaml"), "utf8").catch(() => null);
4130
+ const pnpm = await readFile7(join21(root, "pnpm-workspace.yaml"), "utf8").catch(() => null);
3939
4131
  if (pnpm !== null) {
3940
4132
  try {
3941
4133
  const globs2 = strings((0, import_yaml3.parse)(pnpm)?.packages);
@@ -3950,7 +4142,7 @@ async function workspaceGlobs(root, pkg) {
3950
4142
  var hasUi = async (dir) => (await findFiles(dir, UI_FILES, DEFAULT_EXCLUDE)).length > 0;
3951
4143
  async function findProjects(dir) {
3952
4144
  const root = resolve13(dir);
3953
- const pkg = await readJson(join20(root, "package.json"));
4145
+ const pkg = await readJson(join21(root, "package.json"));
3954
4146
  const name = typeof pkg?.name === "string" && pkg.name.trim() ? pkg.name.trim() : basename4(root);
3955
4147
  const single = { dir: ".", root, name: typeof pkg?.name === "string" ? pkg.name : null };
3956
4148
  const workspace = await workspaceGlobs(root, pkg);
@@ -3961,12 +4153,12 @@ async function findProjects(dir) {
3961
4153
  const projects = [];
3962
4154
  const skipped = [];
3963
4155
  for (const member of members) {
3964
- const memberRoot = join20(root, member);
4156
+ const memberRoot = join21(root, member);
3965
4157
  if (!await hasUi(memberRoot)) {
3966
4158
  skipped.push(member);
3967
4159
  continue;
3968
4160
  }
3969
- const memberPkg = await readJson(join20(memberRoot, "package.json"));
4161
+ const memberPkg = await readJson(join21(memberRoot, "package.json"));
3970
4162
  projects.push({ dir: member, root: memberRoot, name: typeof memberPkg?.name === "string" ? memberPkg.name : null });
3971
4163
  }
3972
4164
  if (projects.length === 0) return { root, name, layout: `one project (${workspace.source} names no package with UI files)`, projects: [single], skipped };
@@ -3993,7 +4185,7 @@ var Lines = class {
3993
4185
  if (!this.enabled) return { at };
3994
4186
  let lines = this.files.get(file);
3995
4187
  if (!lines) {
3996
- lines = (await readFile8(join21(this.root, file), "utf8").catch(() => "")).split("\n");
4188
+ lines = (await readFile8(join22(this.root, file), "utf8").catch(() => "")).split("\n");
3997
4189
  this.files.set(file, lines);
3998
4190
  }
3999
4191
  const code = lines[line - 1]?.trim();
@@ -4013,7 +4205,7 @@ async function modulesOf(project2, target, failed2) {
4013
4205
  for (const file of target.files) {
4014
4206
  if (isShowcase(file)) continue;
4015
4207
  try {
4016
- const module = await parseModule(file, await readFile8(join21(project2.root, file), "utf8"));
4208
+ const module = await parseModule(file, await readFile8(join22(project2.root, file), "utf8"));
4017
4209
  if (module) modules.push(module);
4018
4210
  } catch (error) {
4019
4211
  failed2.push(`${repoPath(project2.dir, file)}: ${errorText(error)}`);
@@ -4117,7 +4309,7 @@ async function analyseProject(project2, index, inventory, inventoryError, worksp
4117
4309
  incomplete.push(`Library choices were not compared (${errorText(error)}); run ${NPX} detect ${project2.dir} to see why`);
4118
4310
  }
4119
4311
  }
4120
- const pkg = await readFile8(join21(project2.root, "package.json"), "utf8").then((text) => JSON.parse(text), () => null);
4312
+ const pkg = await readFile8(join22(project2.root, "package.json"), "utf8").then((text) => JSON.parse(text), () => null);
4121
4313
  const dependencies = new Set(["dependencies", "devDependencies", "peerDependencies"].flatMap((key2) => Object.keys(pkg?.[key2] ?? {})));
4122
4314
  const importer = changeProject ? await importOf(changeProject) : () => "";
4123
4315
  const facts = factsOf(project2, projectId ?? null, modules, eligible, workspaceNames, dependencies, target?.files ?? [], importer);
@@ -4561,7 +4753,7 @@ ${model.projects.map((p) => projectSection(p, many, looks)).join("\n")}
4561
4753
  import { spawn as spawn2 } from "node:child_process";
4562
4754
  import { existsSync as existsSync4, readFileSync as readFileSync2 } from "node:fs";
4563
4755
  import { homedir as homedir2 } from "node:os";
4564
- import { dirname as dirname12, join as join22 } from "node:path";
4756
+ import { dirname as dirname12, join as join23 } from "node:path";
4565
4757
  var LOOKS_PER_PROJECT = 4;
4566
4758
  var DOWNLOAD_SIZE = "~100 MB";
4567
4759
  var DEPENDENCIES = "Install the repo's dependencies to see rendered looks";
@@ -4569,26 +4761,26 @@ var UNDRAWABLE = /not installed|loads no stylesheet|drawn from (?:its|their) (?:
4569
4761
  var playwrightDir = () => dirname12(playwrightCli());
4570
4762
  function browsersPath(env) {
4571
4763
  const set = env.PLAYWRIGHT_BROWSERS_PATH;
4572
- if (set === "0") return join22(playwrightDir(), ".local-browsers");
4764
+ if (set === "0") return join23(playwrightDir(), ".local-browsers");
4573
4765
  if (set) return set;
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");
4766
+ if (process.platform === "darwin") return join23(homedir2(), "Library/Caches/ms-playwright");
4767
+ if (process.platform === "win32") return join23(env.LOCALAPPDATA ?? join23(homedir2(), "AppData/Local"), "ms-playwright");
4768
+ return join23(env.XDG_CACHE_HOME ?? join23(homedir2(), ".cache"), "ms-playwright");
4577
4769
  }
4578
4770
  function chromiumStatus(env = process.env) {
4579
4771
  if (env.YOUI_CHROMIUM) return { available: existsSync4(env.YOUI_CHROMIUM), where: env.YOUI_CHROMIUM };
4580
4772
  try {
4581
- const browsers = JSON.parse(readFileSync2(join22(playwrightDir(), "browsers.json"), "utf8"));
4773
+ const browsers = JSON.parse(readFileSync2(join23(playwrightDir(), "browsers.json"), "utf8"));
4582
4774
  const revision2 = browsers.browsers.find((b) => b.name === "chromium-headless-shell")?.revision;
4583
- const dir = join22(browsersPath(env), `chromium_headless_shell-${revision2}`);
4584
- return { available: revision2 !== void 0 && existsSync4(join22(dir, "INSTALLATION_COMPLETE")), where: dir };
4775
+ const dir = join23(browsersPath(env), `chromium_headless_shell-${revision2}`);
4776
+ return { available: revision2 !== void 0 && existsSync4(join23(dir, "INSTALLATION_COMPLETE")), where: dir };
4585
4777
  } catch {
4586
4778
  return { available: false, where: "playwright-core is not installed" };
4587
4779
  }
4588
4780
  }
4589
4781
  function downloadChromium() {
4590
4782
  return new Promise((resolve16) => {
4591
- const child = spawn2(process.execPath, [join22(playwrightDir(), "cli.js"), "install", "chromium-headless-shell"], { stdio: ["ignore", 2, 2] });
4783
+ const child = spawn2(process.execPath, [join23(playwrightDir(), "cli.js"), "install", "chromium-headless-shell"], { stdio: ["ignore", 2, 2] });
4592
4784
  child.on("error", () => resolve16(false));
4593
4785
  child.on("close", (code) => resolve16(code === 0));
4594
4786
  });
@@ -4604,9 +4796,9 @@ async function addLooks(model, repoRoot, options = {}) {
4604
4796
  const where2 = section.dir === "." ? "this repo" : section.dir;
4605
4797
  try {
4606
4798
  const installed = (name) => {
4607
- let dir = join22(repoRoot, section.dir);
4799
+ let dir = join23(repoRoot, section.dir);
4608
4800
  while (true) {
4609
- if (existsSync4(join22(dir, "node_modules", name, "package.json"))) return true;
4801
+ if (existsSync4(join23(dir, "node_modules", name, "package.json"))) return true;
4610
4802
  if (dir === repoRoot || dirname12(dir) === dir) return false;
4611
4803
  dir = dirname12(dir);
4612
4804
  }
@@ -4615,13 +4807,13 @@ async function addLooks(model, repoRoot, options = {}) {
4615
4807
  missing.push(where2);
4616
4808
  continue;
4617
4809
  }
4618
- const projectRoot = join22(repoRoot, section.dir);
4810
+ const projectRoot = join23(repoRoot, section.dir);
4619
4811
  const target = await loadInventoryTarget(projectRoot);
4620
4812
  if (findStylesheets(projectRoot, target.files, { tsconfig: target.tsconfig, override: target.specimenCss, templates: target.templates }).found.length === 0) {
4621
4813
  missing.push(where2);
4622
4814
  continue;
4623
4815
  }
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 } : {} });
4816
+ const report = await renderShots(join23(repoRoot, section.dir), names, { download: false, base: "HEAD", ...options.executablePath ? { executablePath: options.executablePath } : {}, ...options.cache ? { cache: options.cache } : {}, ...options.log ? { log: options.log } : {} });
4625
4817
  const unavailable = report.notes.some((note) => UNDRAWABLE.test(note));
4626
4818
  if (unavailable) {
4627
4819
  missing.push(where2);
@@ -4693,7 +4885,7 @@ function renderMarkdown(model, reportFile) {
4693
4885
  import { realpathSync as realpathSync2 } from "node:fs";
4694
4886
  import { readFile as readFile9 } from "node:fs/promises";
4695
4887
  import { homedir as homedir3, userInfo as userInfo2 } from "node:os";
4696
- import { join as join23 } from "node:path";
4888
+ import { join as join24 } from "node:path";
4697
4889
  var REDACTED = "[redacted]";
4698
4890
  var OUTSIDE = "[path outside the repo]";
4699
4891
  var MIN_VALUE = 6;
@@ -4705,7 +4897,7 @@ async function dotenvValues(root) {
4705
4897
  const files = await findFiles(root, [".env", ".env.*", "**/.env", "**/.env.*"]).catch(() => []);
4706
4898
  const values = [];
4707
4899
  for (const file of files) {
4708
- const text = await readFile9(join23(root, file), "utf8").catch(() => "");
4900
+ const text = await readFile9(join24(root, file), "utf8").catch(() => "");
4709
4901
  for (const line of text.split(/\r?\n/)) {
4710
4902
  const match = /^\s*(?:export\s+)?[A-Za-z_][A-Za-z0-9_.-]*\s*=\s*(.*)$/.exec(line);
4711
4903
  if (!match) continue;
@@ -4878,7 +5070,7 @@ async function runReport(options = {}, io = {}) {
4878
5070
  const clean = redactor.model(model);
4879
5071
  const outDir = resolve14(options.out ?? root);
4880
5072
  await mkdir5(outDir, { recursive: true });
4881
- const files = { report: join24(outDir, REPORT_FILE), card: join24(outDir, CARD_FILE), summary: join24(outDir, SUMMARY_FILE) };
5073
+ const files = { report: join25(outDir, REPORT_FILE), card: join25(outDir, CARD_FILE), summary: join25(outDir, SUMMARY_FILE) };
4882
5074
  const t = clean.totals;
4883
5075
  const card = {
4884
5076
  repo: redactor.scrub(clean.repo),
@@ -5028,6 +5220,12 @@ ${style.bold("System")} ${style.dim("approved elements: build only with these")
5028
5220
  plan <component> --patch <json|file> classify a contract change and show its impact
5029
5221
  plan --token <id> [--value v] impact of a token change
5030
5222
  migrate <migration.yaml> [--write] [--diff] gated codemod for props and renamed or removed exports
5223
+ upgrade [--to ref] [--write] [--diff]
5224
+ move to the System's new release in one step: the sync, and each component's
5225
+ migrations past the version youi.lock.json records, run as gated dry runs on
5226
+ the components as the sync would bring them; a preview unless --write, which
5227
+ writes all of it or, if a step fails, puts every file back. --to moves a git
5228
+ source to another tag or branch (written to the config's source.ref)
5031
5229
  (imports, re-exports, uses); a dry run that prints the diff and the sites
5032
5230
  left for a person (file:line, why) unless --write; --diff shows it after --write too
5033
5231
  rules [--preview <rule.yaml>] whether each rule is checked; preview a draft rule on the current scan
@@ -5143,6 +5341,7 @@ var OPTIONS = {
5143
5341
  inventory: { type: "string" },
5144
5342
  package: { type: "string" },
5145
5343
  fork: { type: "boolean", default: false },
5344
+ to: { type: "string" },
5146
5345
  cache: { type: "string" },
5147
5346
  role: { type: "string" },
5148
5347
  license: { type: "string" },
@@ -5180,7 +5379,7 @@ async function readJsonOrFile(value) {
5180
5379
  async function cmdScan(values) {
5181
5380
  const p = await project(values);
5182
5381
  const { report } = await scanProject(p);
5183
- const out = values.out ?? join25(p.root, ".youi/usage.json");
5382
+ const out = values.out ?? join26(p.root, ".youi/usage.json");
5184
5383
  await writeFileAtomic(resolve15(out), `${JSON.stringify(report)}
5185
5384
  `);
5186
5385
  const recorded = await recordUsage(values, p, report, resolve15(out));
@@ -5227,7 +5426,7 @@ async function cmdInventory(values) {
5227
5426
  if (values.history) return cmdHistory(values, []);
5228
5427
  const dirs = inventoryDirs(values);
5229
5428
  const report = await scanInventory(values, dirs);
5230
- const out = resolve15(values.out ?? join25(dirs[0], ".youi/inventory.json"));
5429
+ const out = resolve15(values.out ?? join26(dirs[0], ".youi/inventory.json"));
5231
5430
  await writeFileAtomic(out, `${JSON.stringify(report, null, 2)}
5232
5431
  `);
5233
5432
  const recorded = await recordScan(values, report, out);
@@ -5257,7 +5456,7 @@ async function cmdIndex(values, positionals) {
5257
5456
  const [action, ...rest] = positionals;
5258
5457
  const { path, home } = indexOf(values);
5259
5458
  if (action === "path") {
5260
- if (values.json) json3({ store: storeLocation({ path }), sources: join25(home, SOURCES_FILE), scans: join25(home, SCANS_LOG) });
5459
+ if (values.json) json3({ store: storeLocation({ path }), sources: join26(home, SOURCES_FILE), scans: join26(home, SCANS_LOG) });
5261
5460
  else print(storeLocation({ path }));
5262
5461
  return 0;
5263
5462
  }
@@ -5428,15 +5627,23 @@ async function cmdLibrary(values, positionals) {
5428
5627
  async function cmdSync(values) {
5429
5628
  const p = await loadProject(values.project ?? process.cwd(), { ...loadOptions(values), refreshDesign: true });
5430
5629
  const result = await syncProject(p, { dryRun: values["dry-run"], adopt: values.adopt });
5431
- if (values.json) json3(result);
5630
+ if (values.json) json3({ actions: result.actions, lock: result.lock, unknownTools: result.unknownTools });
5432
5631
  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"));
5433
5632
  return result.actions.some((a) => a.action === "conflict") ? 1 : 0;
5434
5633
  }
5634
+ async function cmdUpgrade(values) {
5635
+ if (values.to !== void 0 && !/^[\w][\w./-]*$/.test(values.to)) throw new UsageError(`--to ${JSON.stringify(values.to)}: expected a git tag, branch or commit`);
5636
+ const p = await loadProject(values.project ?? process.cwd(), { ...loadOptions(values), refreshDesign: true, ...values.to !== void 0 ? { ref: values.to } : {} });
5637
+ const result = await upgradeProject(p, { write: values.write, ...values.to !== void 0 ? { to: values.to } : {} });
5638
+ if (values.json) json3(result);
5639
+ else print([renderUpgrade(result, values.diff || result.status === "preview"), ...p.linked ? [style.warn(`! linked: the design system is your checkout ${p.linked}, not the config's source (youiyoux unlink to undo)`)] : []].join("\n"));
5640
+ return result.status === "blocked" ? 1 : 0;
5641
+ }
5435
5642
  async function cmdInit(values) {
5436
5643
  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)");
5437
5644
  const root = values.project ?? process.cwd();
5438
5645
  const design = await designArg(values.design, root);
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 });
5646
+ const result = await initProject({ root, ...design, schemaDir: join26(TOOL_ROOT, "schema"), ...values.system ? { system: values.system } : {}, ...values.components ? { componentsDir: values.components } : {}, force: values.force });
5440
5647
  if (!values.json) {
5441
5648
  print(`${style.ok("created")} ${result.file}`);
5442
5649
  for (const note of result.notes) print(style.dim(` \xB7 ${note}`));
@@ -5447,27 +5654,27 @@ async function cmdInit(values) {
5447
5654
  }
5448
5655
  async function cmdAdopt(values) {
5449
5656
  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)`);
5657
+ if (await exists(join26(root, PROJECT_FILE))) throw new Error(`${root} already has ${PROJECT_FILE}: it follows a design system already (youiyoux sync updates it)`);
5451
5658
  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);
5659
+ const taken = (await Promise.all(plan.files.map(async (f) => await exists(join26(root, f.path)) ? f.path : null))).filter((f) => f !== null);
5453
5660
  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
5661
  if (values["dry-run"]) {
5455
5662
  if (values.json) json3(plan);
5456
5663
  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
5664
  return 0;
5458
5665
  }
5459
- const stylesheet = plan.stylesheet ? await readFile10(join25(root, plan.stylesheet.path), "utf8") : null;
5666
+ const stylesheet = plan.stylesheet ? await readFile10(join26(root, plan.stylesheet.path), "utf8") : null;
5460
5667
  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 });
5668
+ for (const file of plan.files) await writeFileAtomic(join26(root, file.path), file.content);
5669
+ if (plan.stylesheet) await writeFileAtomic(join26(root, plan.stylesheet.path), plan.stylesheet.content);
5670
+ await initProject({ root, design: root, schemaDir: join26(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
5671
  await syncProject(await loadProject(root), { dryRun: true });
5465
5672
  } 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);
5673
+ for (const file of [...plan.files.map((f) => f.path), PROJECT_FILE]) await rm7(join26(root, file), { force: true });
5674
+ for (const folder of [...new Set(plan.files.map((f) => dirname13(f.path)))].sort((a, b) => b.length - a.length)) await rmdir3(join26(root, folder)).catch(() => void 0);
5675
+ await rmdir3(join26(root, "design/components")).catch(() => void 0);
5676
+ await rmdir3(join26(root, "design")).catch(() => void 0);
5677
+ if (plan.stylesheet && stylesheet !== null) await writeFileAtomic(join26(root, plan.stylesheet.path), stylesheet);
5471
5678
  throw new Error(`adopt was undone, nothing kept: ${error instanceof Error ? error.message : String(error)}`);
5472
5679
  }
5473
5680
  if (!values.json) print([...adoptLines(plan), ""].join("\n"));
@@ -5541,7 +5748,7 @@ async function cmdUnlink(values) {
5541
5748
  }
5542
5749
  async function designArg(value, root) {
5543
5750
  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 };
5751
+ return { design: await designRootOf({ package: value }, join26(resolve15(root), PROJECT_FILE), root), package: value };
5545
5752
  }
5546
5753
  async function cmdBuild(values) {
5547
5754
  const root = resolve15(values.design ?? process.cwd());
@@ -5550,10 +5757,10 @@ async function cmdBuild(values) {
5550
5757
  const output = OutputConfig.parse({ cssVarPrefix: values.prefix ?? "youi", targets: ["css-vars", "tailwind-v4", "ts", "react-native"] });
5551
5758
  const started = performance.now();
5552
5759
  const built = await buildTokens(root, system.config, output, { contracts: system.components.map((c) => c.contract) });
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);
5760
+ const out = resolve15(values.out ?? join26(root, "build", system.config.id));
5761
+ const boundary = values.out ? out : join26(root, "build");
5762
+ if (!within(boundary, out) || built.files.some((file) => !within(out, join26(out, file.path)))) throw new Error(`Cannot build system ${JSON.stringify(system.config.id)}: an output path leaves the output directory.`);
5763
+ for (const file of built.files) await writeFileAtomic(join26(out, file.path), file.content);
5557
5764
  if (values.json) json3({ out, files: built.files.map((f) => f.path), tokens: built.tokens.length });
5558
5765
  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)})`)}`);
5559
5766
  return 0;
@@ -5578,16 +5785,16 @@ async function cmdParity(values) {
5578
5785
  async function cmdSchema(values) {
5579
5786
  const schemas = renderJsonSchemas();
5580
5787
  if (values.check) {
5581
- const dir = join25(values.design ? resolve15(values.design) : TOOL_ROOT, "schema");
5788
+ const dir = join26(values.design ? resolve15(values.design) : TOOL_ROOT, "schema");
5582
5789
  const stale = [];
5583
- for (const { file, content } of schemas) if (await readFile10(join25(dir, file), "utf8").catch(() => null) !== content) stale.push(file);
5790
+ for (const { file, content } of schemas) if (await readFile10(join26(dir, file), "utf8").catch(() => null) !== content) stale.push(file);
5584
5791
  if (values.json) json3({ dir, stale });
5585
5792
  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>" : ""}`);
5586
5793
  return stale.length === 0 ? 0 : 1;
5587
5794
  }
5588
5795
  if (values.write) {
5589
5796
  const root = values.design ? resolve15(values.design) : TOOL_ROOT;
5590
- for (const { file, content } of schemas) await writeFileAtomic(join25(root, "schema", file), content);
5797
+ for (const { file, content } of schemas) await writeFileAtomic(join26(root, "schema", file), content);
5591
5798
  print(`${style.ok("wrote")} ${schemas.map((s) => s.file).join(", ")}`);
5592
5799
  } else json3(Object.fromEntries(schemas.map((s) => [s.file, JSON.parse(s.content)])));
5593
5800
  return 0;
@@ -5610,7 +5817,7 @@ async function cmdHooks(values, positionals) {
5610
5817
  if (action !== "install") throw new UsageError("youiyoux hooks install [--claude] [--git] [--ci [--diff]] [--strict] or youiyoux hooks run claude|pre-commit");
5611
5818
  const p = await loadCheckProject(values.project ?? process.cwd(), loadOptions(values), !values.project);
5612
5819
  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");
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 } : {} };
5820
+ const target = { ...hookTarget(values), project: p.root, ...configured(p) && p.configFile && p.configFile !== join26(p.root, PROJECT_FILE) ? { config: p.configFile } : {}, ...values.root !== void 0 ? { root: p.root } : {} };
5614
5821
  const all = !values.claude && !values.git && !values.ci;
5615
5822
  const results = [];
5616
5823
  if (values.claude || all) results.push({ kind: "Claude Code", ...await installClaudeHook(await repoRootOf(p.root), hookCommand(TOOL_ROOT, "claude", target)) });
@@ -5629,7 +5836,7 @@ async function cmdAgents(values, positionals) {
5629
5836
  const repoRoot = await repoRootOf(projectRoot);
5630
5837
  const target = { project: projectRoot, ...values.config ? { config: resolve15(values.config) } : {}, ...values.design ? { design: resolve15(values.design) } : {}, ...values.root !== void 0 ? { root: resolve15(values.root || ".") } : {} };
5631
5838
  const server = mcpCommand(TOOL_ROOT, target);
5632
- const system = existsSync5(resolve15(values.config ?? join25(projectRoot, PROJECT_FILE))) ? await project(values) : null;
5839
+ const system = existsSync5(resolve15(values.config ?? join26(projectRoot, PROJECT_FILE))) ? await project(values) : null;
5633
5840
  const claudeServer = system && system.root === repoRoot ? { command: "node", args: mcpArgs(system) } : relativeTo(repoRoot, server);
5634
5841
  const both = !values.claude && !values.codex;
5635
5842
  const results = [];
@@ -5771,7 +5978,7 @@ async function cmdRequests(values, positionals) {
5771
5978
  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>";
5772
5979
  const [action, id, ...rest] = positionals;
5773
5980
  if (rest.length > 0) throw new UsageError(usage);
5774
- const { RequestStore, requestBrief } = await import("./src-EUVBYTKY.js");
5981
+ const { RequestStore, requestBrief } = await import("./src-6SNQVIZS.js");
5775
5982
  const { project: project2, root } = await product(values);
5776
5983
  const store = new RequestStore(root, project2?.config.id ?? basename5(root));
5777
5984
  const agent = { kind: "agent", name: "youiyoux CLI" };
@@ -5804,8 +6011,8 @@ async function cmdScreens(values, positionals, io) {
5804
6011
  if (!["capture", "login"].includes(positionals[0] ?? "") || positionals.length > 1 || !values.base) throw new UsageError(usage);
5805
6012
  const { root } = await product(values);
5806
6013
  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"))) {
6014
+ const dir = resolve15(values.out ?? join26(root, ".youi/screens"));
6015
+ if (!existsSync5(join26(dir, "screens.json"))) {
5809
6016
  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":"/"}]}
5810
6017
  `);
5811
6018
  return 1;
@@ -5870,7 +6077,7 @@ async function cmdReport(values, positionals, io) {
5870
6077
  return result.code;
5871
6078
  }
5872
6079
  async function cmdMcp(values) {
5873
- const { startStdioServer } = await import("./src-YKPJ3PII.js");
6080
+ const { startStdioServer } = await import("./src-75ODGHE3.js");
5874
6081
  await startStdioServer({ projectRoot: values.project ?? process.cwd(), ...loadOptions(values), ...values.cache ? { discoverCache: values.cache } : {} });
5875
6082
  return -1;
5876
6083
  }
@@ -5945,6 +6152,7 @@ var COMMANDS = {
5945
6152
  rules: cmdRules,
5946
6153
  plan: cmdPlan,
5947
6154
  migrate: cmdMigrate,
6155
+ upgrade: cmdUpgrade,
5948
6156
  init: cmdInit,
5949
6157
  sync: cmdSync,
5950
6158
  scan: cmdScan,
@@ -5996,7 +6204,7 @@ ${HELP}
5996
6204
  }
5997
6205
  return guarded(() => handler(located(command2, parsed.values), rest, io));
5998
6206
  }
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"]);
6207
+ var IN_A_PRODUCT = /* @__PURE__ */ new Set(["system", "link", "unlink", "show", "where", "bom", "check", "baseline", "rules", "plan", "migrate", "upgrade", "sync", "scan", "watch", "hooks", "agents", "mcp", "ui", "library", "render", "screens", "requests"]);
6000
6208
  var IN_A_DESIGN_REPO = /* @__PURE__ */ new Set(["build", "parity"]);
6001
6209
  function located(command2, values, cwd = process.cwd()) {
6002
6210
  if (IN_A_PRODUCT.has(command2) && values.project === void 0 && values.config === void 0 && values.root === void 0) {
@@ -9,7 +9,7 @@ import {
9
9
  scanProject,
10
10
  screenSlug,
11
11
  tokenImpact
12
- } from "./chunk-3HON5Q5K.js";
12
+ } from "./chunk-X2YYMHR4.js";
13
13
  import {
14
14
  buildTokens,
15
15
  contrastText,
@@ -3,7 +3,7 @@ import { createRequire as __youiRequire } from "node:module"; import { dirname a
3
3
  // src/hub-packages.js
4
4
  var PACKAGES = {
5
5
  core: () => import("./src-BYNWPNRT.js"),
6
- scanner: () => import("./src-M3OJMCUE.js"),
6
+ scanner: () => import("./src-XTNHB65M.js"),
7
7
  discover: () => import("./src-DPFJ4S2N.js"),
8
8
  tokens: () => import("./src-PURAHRCR.js")
9
9
  };
@@ -751,7 +751,13 @@ async function loadProject(root, options = {}) {
751
751
  const productRoot = options.productRoot !== void 0 ? resolve2(options.productRoot || ".") : config.root ? fromConfig(config.root) : resolve2(root);
752
752
  if (!await isDirectory2(productRoot)) throw new ProjectRootError(`product root ${productRoot} is not a directory (config ${configFile}); ${ROOT_HINT}`);
753
753
  const linked = options.designRoot ? null : await linkedDesign(productRoot);
754
- const located = options.designRoot || linked ? { root: resolve2(options.designRoot || linked), commit: null } : await locateDesign(config.source, configFile, productRoot, { refresh: options.refreshDesign });
754
+ if (options.ref !== void 0) {
755
+ if (options.designRoot || linked) throw new Error(`the design system is ${options.designRoot ? "the --design folder" : `your checkout ${linked}`}, not the config's source: check out ${options.ref} there${linked ? ", or youiyoux unlink" : ""}`);
756
+ if ("package" in config.source) throw new Error(`${config.source.package} is an npm package: install the version you want (npm install ${config.source.package}@<version>), then run this again without --to`);
757
+ if ("path" in config.source) throw new Error(`the design system is the folder ${config.source.path}: check out ${options.ref} there, then run this again without --to`);
758
+ }
759
+ const source = options.ref !== void 0 && "git" in config.source ? { ...config.source, ref: options.ref } : config.source;
760
+ const located = options.designRoot || linked ? { root: resolve2(options.designRoot || linked), commit: null } : await locateDesign(source, configFile, productRoot, { refresh: options.refreshDesign || options.ref !== void 0 });
755
761
  const repo = await loadDesignRepo(located.root);
756
762
  return {
757
763
  root: productRoot,
@@ -2408,7 +2414,7 @@ async function parseAll(project, files, overrides, parse3) {
2408
2414
  }
2409
2415
  async function scanProject(project, options = {}) {
2410
2416
  const started = performance.now();
2411
- const overrides = options.overrides ?? {};
2417
+ const overrides = { ...project.staged, ...options.overrides };
2412
2418
  const { cache } = options;
2413
2419
  cache?.beginScan();
2414
2420
  const parse3 = cache?.parse ?? parseModule;
@@ -57,8 +57,8 @@ import {
57
57
  validateContent,
58
58
  validateLibraryContent,
59
59
  validateStyleContent
60
- } from "./chunk-Y4IBE6WS.js";
61
- import "./chunk-3HON5Q5K.js";
60
+ } from "./chunk-OCZJ3JGH.js";
61
+ import "./chunk-X2YYMHR4.js";
62
62
  import "./chunk-42YJX3W5.js";
63
63
  import "./chunk-FLBRX6Y3.js";
64
64
  import "./chunk-DWYDHVLZ.js";
@@ -2,7 +2,7 @@ import { createRequire as __youiRequire } from "node:module"; import { dirname a
2
2
  import {
3
3
  RequestStore,
4
4
  requestBrief
5
- } from "./chunk-Y4IBE6WS.js";
5
+ } from "./chunk-OCZJ3JGH.js";
6
6
  import {
7
7
  PROJECT_FILE,
8
8
  ScanCache,
@@ -30,7 +30,7 @@ import {
30
30
  shotsText,
31
31
  tokenRows,
32
32
  whereUsed
33
- } from "./chunk-3HON5Q5K.js";
33
+ } from "./chunk-X2YYMHR4.js";
34
34
  import "./chunk-42YJX3W5.js";
35
35
  import {
36
36
  SHADCN_SLOTS,
@@ -188,7 +188,7 @@ import {
188
188
  whyNoLines,
189
189
  withFile,
190
190
  withoutCredentials
191
- } from "./chunk-3HON5Q5K.js";
191
+ } from "./chunk-X2YYMHR4.js";
192
192
  import {
193
193
  escapingLinks,
194
194
  relativeInside,
@@ -16,8 +16,8 @@ import {
16
16
  selectCandidates,
17
17
  thumbsDir,
18
18
  writeThumbIndex
19
- } from "./chunks/chunk-F33PKGES.js";
20
- import "./chunks/chunk-DCBF7QQY.js";
19
+ } from "./chunks/chunk-3RP664IP.js";
20
+ import "./chunks/chunk-T3KNPVQG.js";
21
21
  import "./chunks/chunk-FVVL5JCY.js";
22
22
  import "./chunks/chunk-DWYDHVLZ.js";
23
23
  import "./chunks/chunk-WNTS7ZO4.js";
@@ -2,7 +2,7 @@
2
2
  import { createRequire as __youiRequire } from "node:module"; import { dirname as __youiDir } from "node:path"; import { fileURLToPath as __youiPath } from "node:url"; const require = __youiRequire(import.meta.url); const __youiDirname = __youiDir(__youiPath(import.meta.url));
3
3
  import {
4
4
  discoverThumbsMiddleware
5
- } from "./chunks/chunk-F33PKGES.js";
5
+ } from "./chunks/chunk-3RP664IP.js";
6
6
  import {
7
7
  isLocalHost,
8
8
  loadPackage,
@@ -10,7 +10,7 @@ import {
10
10
  readTryIndex,
11
11
  send,
12
12
  tryMiddleware
13
- } from "./chunks/chunk-DCBF7QQY.js";
13
+ } from "./chunks/chunk-T3KNPVQG.js";
14
14
  import "./chunks/chunk-FVVL5JCY.js";
15
15
  import "./chunks/chunk-DWYDHVLZ.js";
16
16
  import "./chunks/chunk-WNTS7ZO4.js";
@@ -3,7 +3,7 @@ import { createRequire as __youiRequire } from "node:module"; import { dirname a
3
3
  import {
4
4
  loadPackage,
5
5
  tryProject
6
- } from "./chunks/chunk-DCBF7QQY.js";
6
+ } from "./chunks/chunk-T3KNPVQG.js";
7
7
  import "./chunks/chunk-FVVL5JCY.js";
8
8
  import "./chunks/chunk-WNTS7ZO4.js";
9
9
 
package/dist/youi.js CHANGED
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __youiRequire } from "node:module"; import { dirname as __youiDir } from "node:path"; import { fileURLToPath as __youiPath } from "node:url"; const require = __youiRequire(import.meta.url); const __youiDirname = __youiDir(__youiPath(import.meta.url));
3
3
  import {
4
4
  main
5
- } from "./chunks/chunk-BARSW3JO.js";
6
- import "./chunks/chunk-3HON5Q5K.js";
5
+ } from "./chunks/chunk-75DBKEAH.js";
6
+ import "./chunks/chunk-X2YYMHR4.js";
7
7
  import "./chunks/chunk-42YJX3W5.js";
8
8
  import "./chunks/chunk-HDJ2TOWZ.js";
9
9
  import "./chunks/chunk-6GITMASZ.js";
package/dist/youiyoux.js CHANGED
@@ -3,8 +3,8 @@ import { createRequire as __youiRequire } from "node:module"; import { dirname a
3
3
  import {
4
4
  COMMAND_NAMES,
5
5
  main
6
- } from "./chunks/chunk-BARSW3JO.js";
7
- import "./chunks/chunk-3HON5Q5K.js";
6
+ } from "./chunks/chunk-75DBKEAH.js";
7
+ import "./chunks/chunk-X2YYMHR4.js";
8
8
  import "./chunks/chunk-42YJX3W5.js";
9
9
  import "./chunks/chunk-HDJ2TOWZ.js";
10
10
  import "./chunks/chunk-6GITMASZ.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "youiyoux",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Your agent rebuilds components you already have. youiyoux catches it: npx youiyoux turns any repo into a shareable report of near-duplicate components, hardcoded token values and off-library imports, with the fixes.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -46,9 +46,9 @@
46
46
  "typescript": "6.0.3"
47
47
  },
48
48
  "devDependencies": {
49
- "@youiyoux/cli": "0.1.8",
50
- "@youiyoux/core": "0.1.8",
51
- "@youiyoux/scanner": "0.1.8"
49
+ "@youiyoux/cli": "0.1.10",
50
+ "@youiyoux/core": "0.1.10",
51
+ "@youiyoux/scanner": "0.1.10"
52
52
  },
53
53
  "exports": {
54
54
  "./package.json": "./package.json"